Skip to content
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useState } from 'react';
import { Notice, __experimentalText as Text } from '@wordpress/components';
import { useAppDispatch, useAppSelector } from '../../lib/state/redux/store';
import {
setActiveModal,
setSiteSlugToDelete,
} from '../../lib/state/redux/slice-ui';
import { useSitesAPI } from '../../lib/state/redux/site-management-api-middleware';
import { Modal } from '../modal';
import ModalButtons from '../modal/modal-buttons';
import css from '../modal/style.module.css';

export function DeleteSiteModal() {
const dispatch = useAppDispatch();
const sitesAPI = useSitesAPI();
const siteSlugToDelete = useAppSelector(
(state) => state.ui.siteSlugToDelete
);
const site = useAppSelector((state) =>
siteSlugToDelete ? state.sites.entities[siteSlugToDelete] : undefined
);

const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);

if (!site || site.metadata.storage === 'none') {
return null;
}

const closeModal = () => {
dispatch(setActiveModal(null));
dispatch(setSiteSlugToDelete(undefined));
};

const handleSubmit = async () => {
try {
setIsSubmitting(true);
setError(null);
await sitesAPI.delete(site.slug);
closeModal();
} catch (e) {
setError(
e instanceof Error
? e.message
: 'Deleting failed. Please try again.'
);
} finally {
setIsSubmitting(false);
}
};

return (
<Modal
title="Delete Playground"
contentLabel='This is a dialog window which overlays the main content of the page. The modal begins with a heading 2 called "Delete Playground". Pressing the Close button will close the modal and bring you back to where you were on the page.'
onRequestClose={closeModal}
small
>
<form
className={css.modalForm}
onSubmit={(e) => {
e.preventDefault();
handleSubmit();
}}
>
<Text>
Are you sure you want to delete the site &ldquo;
{site.metadata.name}&rdquo;? This action cannot be undone.
</Text>
{error ? (
<Notice status="error" isDismissible={false}>
{error}
</Notice>
) : null}
<ModalButtons
submitText="Delete"
areBusy={isSubmitting}
onCancel={closeModal}
/>
</form>
</Modal>
);
}
3 changes: 3 additions & 0 deletions packages/playground/website/src/components/layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from '../playground-viewport';
import { MissingSiteModal } from '../missing-site-modal';
import { RenameSiteModal } from '../rename-site-modal';
import { DeleteSiteModal } from '../delete-site-modal';
import { SaveSiteModal } from '../save-site-modal';
import { modalSlugs } from '../../lib/state/redux/slice-ui';
import { GitHubPrivateRepoAuthModal } from '../github-private-repo-auth-modal';
Expand Down Expand Up @@ -167,6 +168,8 @@ function Modals() {
return <MissingSiteModal />;
} else if (currentModal === modalSlugs.RENAME_SITE) {
return <RenameSiteModal />;
} else if (currentModal === modalSlugs.DELETE_SITE) {
return <DeleteSiteModal />;
} else if (currentModal === modalSlugs.SAVE_SITE) {
return <SaveSiteModal />;
} else if (currentModal === modalSlugs.GITHUB_PRIVATE_REPO_AUTH) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
max-width: 350px;
}

.modal-form {
display: flex;
flex-direction: column;
gap: 12px;
}

.modal-buttons {
margin-top: 1.5rem;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useMemo, useState } from 'react';
import { TextControl } from '@wordpress/components';
import { Notice, TextControl } from '@wordpress/components';
import { useAppDispatch, useAppSelector } from '../../lib/state/redux/store';
import {
setActiveModal,
Expand All @@ -8,6 +8,7 @@ import {
import { useSitesAPI } from '../../lib/state/redux/site-management-api-middleware';
import { Modal } from '../modal';
import ModalButtons from '../modal/modal-buttons';
import css from '../modal/style.module.css';

export function RenameSiteModal() {
const dispatch = useAppDispatch();
Expand All @@ -22,6 +23,7 @@ export function RenameSiteModal() {
const initialName = useMemo(() => site?.metadata?.name ?? '', [site]);
const [name, setName] = useState<string>(initialName);
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);

if (!site || site.metadata.storage === 'none') {
// Nothing to rename
Expand All @@ -40,8 +42,15 @@ export function RenameSiteModal() {
}
try {
setIsSubmitting(true);
setError(null);
await sitesAPI.rename(trimmed);
closeModal();
} catch (e) {
setError(
e instanceof Error
? e.message
: 'Renaming failed. Please try again.'
);
} finally {
setIsSubmitting(false);
}
Expand All @@ -59,7 +68,7 @@ export function RenameSiteModal() {
e.preventDefault();
handleSubmit();
}}
style={{ display: 'flex', flexDirection: 'column', gap: 12 }}
className={css.modalForm}
>
<TextControl
__nextHasNoMarginBottom
Expand All @@ -70,6 +79,11 @@ export function RenameSiteModal() {
maxLength={80}
autoFocus
/>
{error ? (
<Notice status="error" isDismissible={false}>
{error}
</Notice>
) : null}
<ModalButtons
submitText="Rename"
areDisabled={!name.trim()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
BaseControl,
TextControl,
RadioControl,
Notice,
} from '@wordpress/components';
import { Modal } from '../modal';
import ModalButtons from '../modal/modal-buttons';
Expand All @@ -30,11 +31,6 @@ const helpTextStyle: CSSProperties = {
marginTop: 8,
};

const errorTextStyle: CSSProperties = {
color: '#d63638',
marginTop: 8,
};

export function SaveSiteModal() {
const dispatch = useAppDispatch();
const sitesAPI = useSitesAPI();
Expand Down Expand Up @@ -261,7 +257,11 @@ export function SaveSiteModal() {
// Don't close modal here - useEffect will close it when save completes
} catch (error) {
logger.error(error);
setSubmitError('Saving failed. Please try again.');
setSubmitError(
error instanceof Error
? error.message
: 'Saving failed. Please try again.'
);
setIsSubmitting(false);
}
};
Expand Down Expand Up @@ -370,7 +370,9 @@ export function SaveSiteModal() {
</Button>
</div>
{directoryError ? (
<p style={errorTextStyle}>{directoryError}</p>
<Notice status="error" isDismissible={false}>
{directoryError}
</Notice>
) : null}
</BaseControl>
)}
Expand All @@ -389,16 +391,18 @@ export function SaveSiteModal() {
</p>
</div>
)}
{submitError ? (
<Notice status="error" isDismissible={false}>
{submitError}
</Notice>
) : null}
<ModalButtons
submitText="Save"
onCancel={handleRequestClose}
areDisabled={saveDisabled}
areBusy={false}
style={{ marginTop: 0 }}
/>
{submitError ? (
<p style={errorTextStyle}>{submitError}</p>
) : null}
</form>
</Modal>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
setSiteManagerOpen,
setSiteManagerSection,
setSiteSlugToRename,
setSiteSlugToDelete,
} from '../../lib/state/redux/slice-ui';
import { useSitesAPI } from '../../lib/state/redux/site-management-api-middleware';
import { WordPressIcon } from '@wp-playground/components';
Expand Down Expand Up @@ -247,14 +248,10 @@ export function SavedPlaygroundsOverlay({
return `data:${logo.mime};base64,${logo.data}`;
};

const handleDeleteSite = async (site: SiteInfo, closeMenu: () => void) => {
const proceed = window.confirm(
`Are you sure you want to delete the site '${site.metadata.name}'?`
);
if (proceed) {
await sitesAPI.delete(site.slug);
closeMenu();
}
const handleDeleteSite = (site: SiteInfo, closeMenu: () => void) => {
dispatch(setSiteSlugToDelete(site.slug));
modalDispatch(setActiveModal(modalSlugs.DELETE_SITE));
closeMenu();
};

const handleRenameSite = (site: SiteInfo, closeMenu: () => void) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ import {
modalSlugs,
setActiveModal,
setSiteManagerOpen,
setSiteManagerSection,
setSiteSlugToDelete,
setSiteSlugToRename,
} from '../../../lib/state/redux/slice-ui';
import { useAppDispatch, useAppSelector } from '../../../lib/state/redux/store';
import { useSitesAPI } from '../../../lib/state/redux/site-management-api-middleware';
import { usePlaygroundClientInfo } from '../../../lib/use-playground-client';
import { SiteLogs } from '../../log-modal';
import { OfflineNotice } from '../../offline-notice';
Expand Down Expand Up @@ -85,8 +84,6 @@ export function SiteInfoPanel({
}) {
const offline = useAppSelector((state) => state.ui.offline);
const dispatch = useAppDispatch();
const sitesAPI = useSitesAPI();

// Load the last active tab for this site
const [initialTabName] = useState(() => {
const lastTab = getSiteLastTab(site.slug);
Expand All @@ -103,16 +100,10 @@ export function SiteInfoPanel({

const isTemporary = site.metadata.storage === 'none';

const removeSiteAndCloseMenu = async (onClose: () => void) => {
// TODO: Replace with HTML-based dialog
const proceed = window.confirm(
`Are you sure you want to delete the site '${site.metadata.name}'?`
);
if (proceed) {
await sitesAPI.delete(site.slug);
dispatch(setSiteManagerSection('sidebar'));
onClose();
}
const removeSiteAndCloseMenu = (onClose: () => void) => {
dispatch(setSiteSlugToDelete(site.slug));
dispatch(setActiveModal(modalSlugs.DELETE_SITE));
onClose();
};
const clientInfo = useAppSelector((state) =>
selectClientInfoBySiteSlug(state, site.slug)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export function persistTemporarySite(
skipRenameModal?: boolean;
} = {}
) {
// @TODO: Handle errors
return async (
dispatch: typeof store.dispatch,
getState: () => PlaygroundReduxState
Expand Down Expand Up @@ -135,21 +134,15 @@ export function persistTemporarySite(
} else if (storageType === 'local-fs') {
let dirHandle = options.localFsHandle;
if (!dirHandle) {
try {
// Request permission to access the directory.
// https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker
dirHandle = await (window as any).showDirectoryPicker({
// By specifying an ID, the browser can remember different directories
// for different IDs.If the same ID is used for another picker, the
// picker opens in the same directory.
id: 'playground-directory',
mode: 'readwrite',
});
} catch (e) {
// No directory selected but log the error just in case.
logger.error(e);
return;
}
// Request permission to access the directory.
// https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker
dirHandle = await (window as any).showDirectoryPicker({
// By specifying an ID, the browser can remember different directories
// for different IDs.If the same ID is used for another picker, the
// picker opens in the same directory.
id: 'playground-directory',
mode: 'readwrite',
});
}
await saveDirectoryHandle(siteSlug, dirHandle!);

Expand Down
Loading
Loading