Skip to content

[Website] Renaming stored Playgrounds #2486

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 3 commits into
base: trunk
Choose a base branch
from
Open
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
51 changes: 51 additions & 0 deletions packages/playground/website/playwright/e2e/website-ui.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,57 @@ test('should preserve PHP constants when saving a temporary site to OPFS', async
await expect(wordpress.locator('body')).toContainText('E2E_TEST_VALUE');
});

test('should rename a saved Playground and persist after reload', async ({
website,
browserName,
}) => {
test.skip(
browserName === 'webkit',
`This test relies on OPFS which isn't available in Playwright's flavor of Safari.`
);

await website.goto('./');
await website.ensureSiteManagerIsOpen();

// Save the temporary site to OPFS so rename is available
await expect(website.page.getByText('Save')).toBeEnabled();
await website.page.getByText('Save').click();
await website.page.getByText('Save in this browser').waitFor();
await website.page.getByText('Save in this browser').click({ force: true });
await expect(website.page.getByLabel('Playground title')).not.toContainText(
'Temporary Playground',
{
timeout: 90000,
}
);

// Open actions menu and trigger Rename
await website.page
.getByRole('button', { name: 'Additional actions' })
.click();
await website.page.getByRole('menuitem', { name: 'Rename' }).click();

const newName = 'My Renamed Playground';
const nameInput = website.page.getByLabel('Name');
await nameInput.fill('');
await nameInput.type(newName);
await nameInput.press('Enter');

await expect(website.page.getByLabel('Playground title')).toContainText(
newName
);

// Reload and verify the name persists
await website.page.reload();
await website.ensureSiteManagerIsOpen();
await expect(website.page.getByLabel('Playground title')).toContainText(
newName
);
await expect(
website.page.locator('[aria-current="page"]').first()
).toContainText(newName);
});

SupportedPHPVersions.forEach(async (version) => {
test(`should switch PHP version to ${version}`, async ({ website }) => {
await website.goto(`./`);
Expand Down
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 @@ -224,6 +226,8 @@ function Modals(blueprint: BlueprintDeclaration) {
);
} else if (currentModal === modalSlugs.MISSING_SITE_PROMPT) {
return <MissingSiteModal />;
} else if (currentModal === modalSlugs.RENAME_SITE) {
return <RenameSiteModal />;
}

if (query.get('gh-ensure-auth') === 'yes') {
Expand Down
17 changes: 11 additions & 6 deletions packages/playground/website/src/components/modal/modal-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +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
type="button"
isBusy={areBusy}
disabled={areDisabled}
variant="link"
Expand All @@ -24,6 +28,7 @@ export default function ModalButtons({ submitText = 'Submit', areDisabled = fals
Cancel
</Button>
<Button
type="submit"
isBusy={areBusy}
disabled={areDisabled}
variant="primary"
Expand All @@ -32,5 +37,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,77 @@
import React, { useMemo, useState } from 'react';
import { TextControl } from '@wordpress/components';
import { useAppDispatch, useAppSelector } from '../../lib/state/redux/store';
import { setActiveModal } from '../../lib/state/redux/slice-ui';
import { updateSiteMetadata } from '../../lib/state/redux/slice-sites';
import { Modal } from '../modal';
import ModalButtons from '../modal/modal-buttons';

export function RenameSiteModal() {
const dispatch = useAppDispatch();
const site = useAppSelector((state) =>
state.ui.activeSite?.slug
? state.sites.entities[state.ui.activeSite.slug]
: undefined
);

const initialName = useMemo(() => site?.metadata?.name ?? '', [site]);
const [name, setName] = useState<string>(initialName);
const [isSubmitting, setIsSubmitting] = useState(false);

if (!site) {
// Nothing to rename
return null;
}

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

const handleSubmit = async () => {
const trimmed = name.trim();
if (!trimmed) {
return;
}
try {
setIsSubmitting(true);
await dispatch(
updateSiteMetadata({
slug: site.slug,
changes: { name: trimmed },
}) as any
);
closeModal();
} finally {
setIsSubmitting(false);
}
};

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 Close button will close the modal and bring you back to where you were on the page.'
onRequestClose={closeModal}
small
>
<form
onSubmit={(e) => {
e.preventDefault();
handleSubmit();
}}
style={{ display: 'flex', flexDirection: 'column', gap: 12 }}
>
<TextControl
__nextHasNoMarginBottom
label="Name"
value={name}
onChange={(val: string) => setName(val)}
autoFocus
/>
<ModalButtons
submitText="Rename"
areDisabled={!name.trim()}
areBusy={isSubmitting}
onCancel={closeModal}
/>
</form>
</Modal>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import { selectClientInfoBySiteSlug } from '../../../lib/state/redux/slice-clien
import { encodeStringAsBase64 } from '../../../lib/base64';
import { ActiveSiteSettingsForm } from '../site-settings-form/active-site-settings-form';
import { getRelativeDate } from '../../../lib/get-relative-date';
import { setActiveModal } from '../../../lib/state/redux/slice-ui';
import { modalSlugs } from '../../layout';
import { removeSite } from '../../../lib/state/redux/slice-sites';

export function SiteInfoPanel({
Expand Down Expand Up @@ -222,6 +224,19 @@ export function SiteInfoPanel({
<>
{!isTemporary && (
<MenuGroup>
<MenuItem
aria-label="Rename this Playground"
onClick={() => {
dispatch(
setActiveModal(
modalSlugs.RENAME_SITE
)
);
onClose();
}}
>
Rename
</MenuItem>
<MenuItem
aria-label="Delete this Playground"
className={css.danger}
Expand Down
Loading