Skip to content

[Website] Allow users to set Playground name before saving and rename saved Playgrounds #2124

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: trunk
Choose a base branch
from
4 changes: 4 additions & 0 deletions packages/playground/website/src/components/layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
import { ImportFormModal } from '../import-form-modal';
import { PreviewPRModal } from '../../github/preview-pr';
import { MissingSiteModal } from '../missing-site-modal';
import { RenameSiteModal } from '../rename-site-modal';

acquireOAuthTokenIfNeeded();

Expand All @@ -45,6 +46,7 @@ export const modalSlugs = {
PREVIEW_PR_WP: 'preview-pr-wordpress',
PREVIEW_PR_GUTENBERG: 'preview-pr-gutenberg',
MISSING_SITE_PROMPT: 'missing-site-prompt',
RENAME_SITE: 'rename-site',
};

const displayMode = getDisplayModeFromQuery();
Expand Down Expand Up @@ -222,6 +224,8 @@ function Modals(blueprint: BlueprintDeclaration) {
}}
/>
);
} else if (currentModal === modalSlugs.RENAME_SITE) {
return <RenameSiteModal />;
} else if (currentModal === modalSlugs.MISSING_SITE_PROMPT) {
return <MissingSiteModal />;
}
Expand Down
18 changes: 10 additions & 8 deletions packages/playground/website/src/components/modal/modal-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ interface ModalButtonsProps {
onCancel?: () => void;
onSubmit?: (e: any) => void;
}
export default function ModalButtons({ submitText = 'Submit', areDisabled = false, areBusy, onCancel, onSubmit }: ModalButtonsProps) {
export default function ModalButtons({
submitText = 'Submit',
areDisabled = false,
areBusy,
onCancel,
onSubmit,
}: ModalButtonsProps) {
return (
<Flex
justify="end"
className={css.modalButtons}
>
<Flex justify="end" gap={4} className={css.modalButtons}>
<Button
isBusy={areBusy}
disabled={areDisabled}
disabled={areDisabled || areBusy}
variant="link"
onClick={onCancel}
>
Expand All @@ -32,5 +34,5 @@ export default function ModalButtons({ submitText = 'Submit', areDisabled = fals
{submitText}
</Button>
</Flex>
)
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useDispatch } from 'react-redux';
import SiteNameForm from '../site-name-form';
import { Modal } from '../modal';
import { setActiveModal } from '../../lib/state/redux/slice-ui';
import type { PlaygroundDispatch } from '../../lib/state/redux/store';
import { useActiveSite } from '../../lib/state/redux/store';
import { updateSiteMetadata } from '../../lib/state/redux/slice-sites';
import { useState } from 'react';

export const RenameSiteModal = () => {
const dispatch: PlaygroundDispatch = useDispatch();
const [isUpdating, setIsUpdating] = useState(false);

const activeSite = useActiveSite();

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

async function handleSubmit(newName: string) {
if (!activeSite || !activeSite.slug) {
return null;
}
setIsUpdating(true);
await dispatch(
updateSiteMetadata({
slug: activeSite.slug,
changes: {
name: newName,
},
})
);
setIsUpdating(false);
closeModal();
}

return (
<Modal
title="Rename Playground"
contentLabel='This is a dialog window which overlays the main content of the
page. The modal begins with a heading 2 called "Rename
Playground". Pressing the Cancel button will close
the modal and bring you back to where you were on the page.'
onRequestClose={closeModal}
>
<SiteNameForm
onClose={closeModal}
onSubmit={handleSubmit}
isBusy={isUpdating}
siteName={activeSite?.metadata.name ?? ''}
autoFocusNameInput={true}
/>
</Modal>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useDispatch } from 'react-redux';
import SiteNameForm from '../site-name-form';
import { Modal } from '../modal';
import type { PlaygroundDispatch } from '../../lib/state/redux/store';
import { useActiveSite } from '../../lib/state/redux/store';
import { updateSiteMetadata } from '../../lib/state/redux/slice-sites';
import { useState } from 'react';
import type { SiteStorageType } from '../../lib/site-metadata';
import { persistTemporarySite } from '../../lib/state/redux/persist-temporary-site';

interface SaveSiteModalProps {
storageType: Extract<SiteStorageType, 'opfs' | 'local-fs'>;
onClose: () => void;
}

export const SaveSiteModal = ({ storageType, onClose }: SaveSiteModalProps) => {
const dispatch: PlaygroundDispatch = useDispatch();
const [isSaving, setIsSaving] = useState(false);

const activeSite = useActiveSite();

const closeModal = () => {
onClose();
};

async function handleSubmit(newName: string) {
if (!activeSite || !activeSite.slug) {
return null;
}
setIsSaving(true);
await dispatch(
updateSiteMetadata({
slug: activeSite.slug,
changes: {
name: newName,
},
})
);
await dispatch(persistTemporarySite(activeSite.slug, storageType));
setIsSaving(false);
closeModal();
}

return (
<Modal
title="Save Playground"
contentLabel='This is a dialog window which overlays the main content of the
page. The modal begins with a heading 2 called "Save
Playground". Pressing the Cancel button will close
the modal and bring you back to where you were on the page.'
onRequestClose={closeModal}
>
<SiteNameForm
onClose={closeModal}
onSubmit={handleSubmit}
isBusy={isSaving}
siteName={activeSite?.metadata.name ?? ''}
autoFocusNameInput={true}
/>
</Modal>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { usePlaygroundClientInfo } from '../../../lib/use-playground-client';
import { OfflineNotice } from '../../offline-notice';
import { DownloadAsZipMenuItem } from '../../toolbar-buttons/download-as-zip';
import { GithubExportMenuItem } from '../../toolbar-buttons/github-export-menu-item';
import { RenameMenuItem } from '../../toolbar-buttons/rename-menu-item';
import { ReportError } from '../../toolbar-buttons/report-error';
import { TemporarySiteNotice } from '../temporary-site-notice';
import type { SiteInfo } from '../../../lib/state/redux/slice-sites';
Expand Down Expand Up @@ -222,6 +223,9 @@ export function SiteInfoPanel({
<>
{!isTemporary && (
<MenuGroup>
<RenameMenuItem
onClose={onClose}
/>
<MenuItem
aria-label="Delete this Playground"
className={css.danger}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useAppSelector, useAppDispatch } from '../../../lib/state/redux/store';
import { useAppSelector } from '../../../lib/state/redux/store';
import {
DropdownMenu,
DropdownMenuItem,
Expand All @@ -7,11 +7,12 @@ import {
// @ts-ignore
} from '@wordpress/components/build/dropdown-menu-v2/index.js';
import css from './style.module.css';
import { persistTemporarySite } from '../../../lib/state/redux/persist-temporary-site';
import { selectClientInfoBySiteSlug } from '../../../lib/state/redux/slice-clients';
import { useLocalFsAvailability } from '../../../lib/hooks/use-local-fs-availability';
import { isOpfsAvailable } from '../../../lib/state/opfs/opfs-site-storage';
import type { SiteStorageType } from '../../../lib/site-metadata';
import { useState } from 'react';
import { SaveSiteModal } from '../../save-site-modal';
import type { PersistedSiteStorageType } from '../../../lib/site-metadata';

export function SitePersistButton({
siteSlug,
Expand All @@ -20,34 +21,40 @@ export function SitePersistButton({
}: {
siteSlug: string;
children: React.ReactNode;
storage?: Extract<SiteStorageType, 'opfs' | 'local-fs'> | null;
storage?: PersistedSiteStorageType | null;
}) {
const [selectedStorageType, setSelectedStorageType] =
useState<PersistedSiteStorageType | null>(null);
const clientInfo = useAppSelector((state) =>
selectClientInfoBySiteSlug(state, siteSlug)
);
const localFsAvailability = useLocalFsAvailability(clientInfo?.client);
const dispatch = useAppDispatch();

const persistSiteClick = (storageType: PersistedSiteStorageType) => {
setSelectedStorageType(storageType);
};

if (selectedStorageType) {
return (
<SaveSiteModal
storageType={selectedStorageType}
onClose={() => setSelectedStorageType(null)}
/>
);
}

if (!clientInfo?.opfsSync || clientInfo.opfsSync?.status === 'error') {
let button = null;
if (storage) {
button = (
<div
onClick={() =>
dispatch(persistTemporarySite(siteSlug, storage))
}
>
{children}
</div>
<div onClick={() => persistSiteClick(storage)}>{children}</div>
);
} else {
button = (
<DropdownMenu trigger={children}>
<DropdownMenuItem
disabled={!isOpfsAvailable}
onClick={() =>
dispatch(persistTemporarySite(siteSlug, 'opfs'))
}
onClick={() => persistSiteClick('opfs')}
>
<DropdownMenuItemLabel>
Save in this browser
Expand All @@ -62,9 +69,7 @@ export function SitePersistButton({
</DropdownMenuItem>
<DropdownMenuItem
disabled={localFsAvailability !== 'available'}
onClick={() =>
dispatch(persistTemporarySite(siteSlug, 'local-fs'))
}
onClick={() => persistSiteClick('local-fs')}
>
<DropdownMenuItemLabel>
Save in a local directory
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import { useState } from 'react';
import ModalButtons from '../modal/modal-buttons';
import { TextControl } from '@wordpress/components';

interface SiteNameFormProps {
onClose: () => void;
onSubmit: (newName: string) => void;
isBusy: boolean;
siteName: string;
autoFocusNameInput?: boolean;
}

export default function SiteNameForm({
onClose,
onSubmit,
isBusy,
siteName,
autoFocusNameInput = false,
}: SiteNameFormProps) {
const [newName, setNewName] = useState<string>(siteName);

function submitOnEnter(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Enter') {
onSubmit(newName);
}
}

return (
<>
<TextControl
label="Playground name"
placeholder="My Playground"
value={newName}
onChange={setNewName}
onKeyDown={submitOnEnter}
autoFocus={autoFocusNameInput}
/>

<ModalButtons
areDisabled={!newName}
onCancel={onClose}
onSubmit={() => onSubmit(newName)}
submitText="Save"
areBusy={isBusy}
/>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { MenuItem } from '@wordpress/components';
import { setActiveModal } from '../../lib/state/redux/slice-ui';
import type { PlaygroundDispatch } from '../../lib/state/redux/store';
import { useDispatch } from 'react-redux';
import { modalSlugs } from '../layout';

interface Props {
onClose: () => void;
disabled?: boolean;
}
export function RenameMenuItem({ onClose, disabled }: Props) {
const dispatch: PlaygroundDispatch = useDispatch();
return (
<MenuItem
aria-label="Rename this Playground"
disabled={disabled}
onClick={() => {
dispatch(setActiveModal(modalSlugs.RENAME_SITE));
onClose();
}}
>
Rename
</MenuItem>
);
}
4 changes: 4 additions & 0 deletions packages/playground/website/src/lib/site-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import { resolveBlueprintFromURL } from './state/url/resolve-blueprint-from-url'
*/
export const SiteStorageTypes = ['opfs', 'local-fs', 'none'] as const;
export type SiteStorageType = (typeof SiteStorageTypes)[number];
export type PersistedSiteStorageType = Extract<
SiteStorageType,
'opfs' | 'local-fs'
>;

/**
* The site logo data.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export function getSqliteDriverModuleDetails(
url: string;
} {
switch (version) {

case 'develop':
/** @ts-ignore */
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"develop": "develop",
"v2.1.16": "v2.1.16"
}
"develop": "develop",
"v2.1.16": "v2.1.16"
}
Loading