Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
62433d4
Reorganize the siet management overlay
adamziel Dec 1, 2025
4b842d1
Layout improvements
adamziel Dec 2, 2025
d5422a3
Normalize escape key behavior
adamziel Dec 2, 2025
4f4e1c8
improve the PR icon
adamziel Dec 2, 2025
237c9d3
Bluepritns filtering UI improvements
adamziel Dec 4, 2025
fb8995b
design is more or less there
adamziel Dec 4, 2025
6a1fc76
mostly there!
adamziel Dec 5, 2025
ba4e307
new design seemingly complete
adamziel Dec 5, 2025
ebec338
Fix mobile view and update e2e tests for new overlay
adamziel Dec 5, 2025
910973c
Remove unused imports and variables from SiteManager
adamziel Dec 5, 2025
616009e
Fix e2e test selector to be more specific for site-info-panel
adamziel Dec 5, 2025
f835700
Respect light/dark mode for loading progress bar
adamziel Dec 5, 2025
f617d26
[Website] Add Temporary Playground to Your Playgrounds list
adamziel Dec 5, 2025
11ba127
Use fadeout when switching sites in the overlay
adamziel Dec 5, 2025
88688a4
Show progress bar while site client is booting
adamziel Dec 5, 2025
5bcf922
Only show indeterminate progress bar before site entity exists
adamziel Dec 5, 2025
42a3e06
Tweak the design
adamziel Dec 6, 2025
cb59f08
lint
adamziel Dec 6, 2025
9f42153
remove dev artifacts
adamziel Dec 6, 2025
2ade285
Fix rename button in site info panel to set siteSlugToRename
adamziel Dec 6, 2025
bd00a16
Fix rename test to find site by name instead of position
adamziel Dec 6, 2025
b28a19b
Fix another test expecting saved site to be first in overlay
adamziel Dec 8, 2025
aa48a13
Fix race condition: website build waits for blueprints schema
adamziel Dec 8, 2025
11a0ba6
CSS tweaks
adamziel Dec 15, 2025
6103625
Wrap Blueprints filters on mobile
adamziel Dec 15, 2025
c61ba7c
Display Blueprints as list on mobile (in the initial view)
adamziel Dec 15, 2025
e2fd40f
Tweak the list view styles
adamziel Dec 15, 2025
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
3 changes: 2 additions & 1 deletion packages/playground/remote/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"outputPath": "dist/packages/playground/remote",
"main": "packages/playground/remote/remote.html",
"tsConfig": "packages/playground/remote/tsconfig.lib.json"
}
},
"dependsOn": ["playground-blueprints:build:blueprint-schema"]
},
"dev": {
"executor": "@nx/vite:dev-server",
Expand Down
2 changes: 1 addition & 1 deletion packages/playground/remote/remote.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html>
<head>
<title>WordPress Playground</title>
Expand Down
4 changes: 3 additions & 1 deletion packages/playground/remote/src/lib/boot-playground-remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ import workerV2Url from './playground-worker-endpoint-blueprints-v2.ts?worker&ur
const origin = new URL('/', (import.meta || {}).url).origin;

function getWorkerUrl(): string {
const runner = new URL(document.location.href).searchParams.get('blueprints-runner');
const runner = new URL(document.location.href).searchParams.get(
'blueprints-runner'
);
const isV2 = runner === 'v2';
const selected = isV2 ? workerV2Url : workerV1Url;
return new URL(selected, origin) + '';
Expand Down
64 changes: 40 additions & 24 deletions packages/playground/website/playwright/e2e/opfs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,24 +66,24 @@ test('should switch between sites', async ({ website, browserName }) => {
// Save the temporary site using the modal
await saveSiteViaModal(website.page);

await expect(
website.page.locator('[aria-current="page"]')
).not.toContainText('Temporary Playground', {
// Saving the site takes a while on CI
timeout: 90000,
});
await expect(website.page.getByLabel('Playground title')).not.toContainText(
'Temporary Playground'
'Temporary Playground',
{
// Saving the site takes a while on CI
timeout: 90000,
}
);

// Open the saved playgrounds overlay to switch sites
await website.openSavedPlaygroundsOverlay();

// Click on Temporary Playground in the overlay's site list
await website.page
.locator('button')
.locator('[class*="siteRowContent"]')
.filter({ hasText: 'Temporary Playground' })
.click();

await expect(website.page.locator('[aria-current="page"]')).toContainText(
'Temporary Playground'
);
// The overlay closes and site manager opens with the selected site
await expect(website.page.getByLabel('Playground title')).toContainText(
'Temporary Playground'
);
Expand Down Expand Up @@ -118,27 +118,35 @@ test('should preserve PHP constants when saving a temporary site to OPFS', async
// Save the temporary site using the modal
await saveSiteViaModal(website.page);

await expect(
website.page.locator('[aria-current="page"]')
).not.toContainText('Temporary Playground', {
// Saving the site takes a while on CI
timeout: 90000,
});
await expect(website.page.getByLabel('Playground title')).not.toContainText(
'Temporary Playground',
{
// Saving the site takes a while on CI
timeout: 90000,
}
);

const storedPlaygroundTitleText = await website.page
.getByLabel('Playground title')
.textContent();
await expect(storedPlaygroundTitleText).not.toBeNull();
await expect(storedPlaygroundTitleText).not.toMatch('Temporary Playground');

// Open the saved playgrounds overlay to switch sites
await website.openSavedPlaygroundsOverlay();

// Switch to Temporary Playground
await website.page
.locator('button')
.locator('[class*="siteRowContent"]')
.filter({ hasText: 'Temporary Playground' })
.click();

// Open the overlay again to switch back to the stored site
await website.openSavedPlaygroundsOverlay();

// Switch back to the stored site and confirm the PHP constant is still present.
await website.page
.locator('button')
.locator('[class*="siteRowContent"]')
.filter({ hasText: storedPlaygroundTitleText! })
.click();

Expand Down Expand Up @@ -194,9 +202,13 @@ test('should rename a saved Playground and persist after reload', async ({
await expect(website.page.getByLabel('Playground title')).toContainText(
newName
);

// Verify the name is also updated in the saved playgrounds overlay
await website.openSavedPlaygroundsOverlay();
await expect(
website.page.locator('[aria-current="page"]').first()
).toContainText(newName);
website.page.locator('[class*="siteRowName"]', { hasText: newName })
).toBeVisible();
await website.closeSavedPlaygroundsOverlay();
});

test('should show save site modal with correct elements', async ({
Expand Down Expand Up @@ -335,9 +347,13 @@ test('should save site with custom name', async ({ website, browserName }) => {
timeout: 90000,
}
);
await expect(website.page.locator('[aria-current="page"]')).toContainText(
customName
);

// Verify the name also appears in the saved playgrounds overlay
await website.openSavedPlaygroundsOverlay();
await expect(
website.page.locator('[class*="siteRowName"]', { hasText: customName })
).toBeVisible();
await website.closeSavedPlaygroundsOverlay();
});

test('should not persist save site modal through page refresh', async ({
Expand Down
56 changes: 41 additions & 15 deletions packages/playground/website/playwright/website-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,52 @@ export class WebsitePage {
}

async ensureSiteManagerIsOpen() {
const siteManager = this.page.locator('.main-sidebar');
if (!(await siteManager.isVisible())) {
await this.page
.getByRole('button', { name: 'Open Site Manager' })
.click();
const siteManagerButton = this.page.getByRole('button', {
name: /Site Manager/,
});
const isPressed = await siteManagerButton.getAttribute('aria-pressed');
if (isPressed !== 'true') {
await siteManagerButton.click();
}
await expect(siteManager).toBeVisible();
// Wait for the site info panel section to be visible
await expect(
this.page.locator('section[class*="site-info-panel"]')
).toBeVisible();
}

async ensureSiteManagerIsClosed() {
const siteManager = this.page.locator('.main-sidebar');
if (await siteManager.isVisible()) {
const closeButton = this.page.getByRole('button', {
name: 'Close Site Manager',
});
if (await closeButton.isVisible()) {
await closeButton.click();
}
const siteManagerButton = this.page.getByRole('button', {
name: /Site Manager/,
});
const isPressed = await siteManagerButton.getAttribute('aria-pressed');
if (isPressed === 'true') {
await siteManagerButton.click();
}
// Wait for the site info panel section to be hidden
await expect(
this.page.locator('section[class*="site-info-panel"]')
).not.toBeVisible();
}

async openSavedPlaygroundsOverlay() {
await this.page
.getByRole('button', { name: 'Saved Playgrounds' })
.click();
await expect(
this.page
.locator('[class*="overlay"]')
.filter({ hasText: 'Playground' })
).toBeVisible();
}

async closeSavedPlaygroundsOverlay() {
const overlay = this.page
.locator('[class*="overlay"]')
.filter({ hasText: 'Playground' });
if (await overlay.isVisible()) {
await this.page.keyboard.press('Escape');
}
await expect(siteManager).not.toBeVisible();
await expect(overlay).not.toBeVisible();
}

async getSiteTitle(): Promise<string> {
Expand Down
5 changes: 4 additions & 1 deletion packages/playground/website/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@
"logLevel": "info"
}
},
"dependsOn": ["^build"]
"dependsOn": [
"^build",
"playground-blueprints:build:blueprint-schema"
]
},
"dev": {
"executor": "nx:run-commands",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,12 @@ export const AutosavedBlueprintBundleEditor = forwardRef<

// Otherwise, populate an in-memory filesystem with the Blueprint JSON.
fs = new EventedFilesystem(new InMemoryFilesystemBackend());
await populateFilesystemFromBlueprint(
fs,
originalBlueprint as Blueprint
);
if (originalBlueprint) {
await populateFilesystemFromBlueprint(
fs,
originalBlueprint as Blueprint
);
}
setFilesystem(fs);
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React, { useState } from 'react';
import { TextControl } from '@wordpress/components';
import { useAppDispatch } from '../../lib/state/redux/store';
import {
setActiveModal,
setSiteManagerOpen,
} from '../../lib/state/redux/slice-ui';
import { Modal } from '../modal';
import ModalButtons from '../modal/modal-buttons';
import { PlaygroundRoute, redirectTo } from '../../lib/state/url/router';

export function BlueprintUrlModal() {
const dispatch = useAppDispatch();
const [url, setUrl] = useState<string>('');

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

const handleSubmit = () => {
const trimmed = url.trim();
if (!trimmed) {
return;
}
dispatch(setSiteManagerOpen(false));
closeModal();
redirectTo(
PlaygroundRoute.newTemporarySite({
query: {
'blueprint-url': trimmed,
},
})
);
};

return (
<Modal
title="Run Blueprint from URL"
contentLabel='This is a dialog window which overlays the main content of the page. The modal begins with a heading 2 called "Run Blueprint from URL". 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="Blueprint URL"
value={url}
onChange={(val: string) => setUrl(val)}
placeholder="https://example.com/blueprint.json"
type="url"
autoFocus
/>
<ModalButtons
submitText="Run Blueprint"
areDisabled={!url.trim()}
onCancel={closeModal}
/>
</form>
</Modal>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import {
import { SyncLocalFilesButton } from '../sync-local-files-button';
import { Dropdown, Icon } from '@wordpress/components';
import { Modal } from '../../components/modal';
import { cog } from '@wordpress/icons';
import { cog, category } from '@wordpress/icons';
import Button from '../button';
import { ActiveSiteSettingsForm } from '../site-manager/site-settings-form';
import { setSiteManagerOpen } from '../../lib/state/redux/slice-ui';
import { SiteManagerIcon } from '@wp-playground/components';
import { SavedPlaygroundsOverlay } from '../saved-playgrounds-overlay';

interface BrowserChromeProps {
children?: React.ReactNode;
Expand Down Expand Up @@ -44,9 +45,12 @@ export default function BrowserChrome({
className
);
const isMobileUi = useMediaQuery('(max-width: 875px)');
const [isModalOpen, setIsModalOpen] = React.useState(false);
const onToggle = () => setIsModalOpen(!isModalOpen);
const closeModal = () => setIsModalOpen(false);
const [isSettingsModalOpen, setIsSettingsModalOpen] = React.useState(false);
const [isPlaygroundsOverlayOpen, setIsPlaygroundsOverlayOpen] =
React.useState(false);
const onSettingsToggle = () => setIsSettingsModalOpen(!isSettingsModalOpen);
const closeSettingsModal = () => setIsSettingsModalOpen(false);
const closePlaygroundsOverlay = () => setIsPlaygroundsOverlayOpen(false);

return (
<div className={wrapperClass} data-cy="simulated-browser">
Expand All @@ -67,6 +71,16 @@ export default function BrowserChrome({
</div>

<div className={css.toolbarButtons}>
<Button
variant="browser-chrome"
aria-label="Saved Playgrounds"
onClick={() => setIsPlaygroundsOverlayOpen(true)}
aria-expanded={isPlaygroundsOverlayOpen}
className={css.savedPlaygroundsButton}
>
<Icon icon={category} size={20} />
</Button>

<Button
variant="browser-chrome"
aria-label={
Expand Down Expand Up @@ -95,8 +109,8 @@ export default function BrowserChrome({
<Button
variant="browser-chrome"
aria-label="Edit Playground settings"
onClick={onToggle}
aria-expanded={isModalOpen}
onClick={onSettingsToggle}
aria-expanded={isSettingsModalOpen}
style={{
fill: '#FFF',
alignItems: 'center',
Expand All @@ -105,14 +119,14 @@ export default function BrowserChrome({
>
<Icon icon={cog} size={28} />
</Button>
{isModalOpen && (
{isSettingsModalOpen && (
<Modal
isFullScreen={true}
title="Playground settings"
onRequestClose={closeModal}
onRequestClose={closeSettingsModal}
>
<ActiveSiteSettingsForm
onSubmit={closeModal}
onSubmit={closeSettingsModal}
/>
</Modal>
)}
Expand Down Expand Up @@ -164,6 +178,9 @@ export default function BrowserChrome({
</header>
<div className={css.content}>{children}</div>
</div>
{isPlaygroundsOverlayOpen && (
<SavedPlaygroundsOverlay onClose={closePlaygroundsOverlay} />
)}
</div>
);
}
Loading