Skip to content
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

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

Draft
wants to merge 6 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -35,6 +35,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 @@ -48,6 +49,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 @@ -225,6 +227,8 @@ function Modals(blueprint: Blueprint) {
}}
/>
);
} 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" 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,53 @@
import { useDispatch } from 'react-redux';
import SiteNameForm from '../site-name-form';
import { Modal } from '../modal';
import { setActiveModal } from '../../lib/state/redux/slice-ui';
import { PlaygroundDispatch, 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 ?? ''}
/>
</Modal>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useDispatch } from 'react-redux';
import SiteNameForm from '../site-name-form';
import { Modal } from '../modal';
import { PlaygroundDispatch, useActiveSite } from '../../lib/state/redux/store';
import { updateSiteMetadata } from '../../lib/state/redux/slice-sites';
import { useState } from 'react';
import { 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 ?? ''}
/>
</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 { 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 { SiteStorageType } from '../../../lib/site-metadata';
import { useState } from 'react';
import { SaveSiteModal } from '../../save-site-modal';

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

const persistSiteClick = (
storageType: Extract<SiteStorageType, 'opfs' | 'local-fs'>
) => {
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 +73,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,46 @@
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;
}

export default function SiteNameForm({
onClose,
onSubmit,
isBusy,
siteName,
}: 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}
/>

<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 { 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>
);
}
Loading