From deb3bd15a5ab9f8ad7d9c00b474df483876350ff Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Tue, 27 Aug 2024 02:19:35 -0400 Subject: [PATCH 1/2] WIP: Use selected site settings --- .../src/components/site-manager/index.tsx | 18 +-- .../src/components/site-view/site-view.tsx | 8 +- .../playground/website/src/lib/query-api.ts | 110 ++++++++++++++++++ .../playground/website/src/lib/redux-store.ts | 2 +- .../website/src/lib/resolve-blueprint.ts | 28 +---- .../website/src/lib/use-boot-playground.ts | 6 +- packages/playground/website/src/main.tsx | 105 ++++++++++------- 7 files changed, 199 insertions(+), 78 deletions(-) create mode 100644 packages/playground/website/src/lib/query-api.ts diff --git a/packages/playground/website/src/components/site-manager/index.tsx b/packages/playground/website/src/components/site-manager/index.tsx index 85a2f840cb..a0e4645df9 100644 --- a/packages/playground/website/src/components/site-manager/index.tsx +++ b/packages/playground/website/src/components/site-manager/index.tsx @@ -1,6 +1,8 @@ import { SiteManagerSidebar } from './site-manager-sidebar'; import { __experimentalUseNavigator as useNavigator } from '@wordpress/components'; -import store, { selectSite } from '../../lib/redux-store'; +import store, { selectSite, type AppState } from '../../lib/redux-store'; +import { useSelector } from 'react-redux'; +import { siteInfoToUrl } from '../../lib/query-api'; import css from './style.module.css'; @@ -14,6 +16,7 @@ export function SiteManager({ siteViewRef: React.RefObject; }) { const { goTo } = useNavigator(); + const sites = useSelector((state: AppState) => state.siteListing.sites); const shouldHideSiteManagerOnSiteChange = () => { /** @@ -28,13 +31,14 @@ export function SiteManager({ }; const onSiteClick = async (siteSlug: string) => { - onSiteChange(siteSlug); - const url = new URL(window.location.href); - if (siteSlug) { - url.searchParams.set('site-slug', siteSlug); - } else { - url.searchParams.delete('site-slug'); + const selectedSite = sites.find((site) => site.slug === siteSlug); + if (!selectedSite) { + return; } + + onSiteChange(siteSlug); + + const url = siteInfoToUrl(new URL(window.location.href), selectedSite); window.history.pushState({}, '', url.toString()); await store.dispatch(selectSite(siteSlug)); diff --git a/packages/playground/website/src/components/site-view/site-view.tsx b/packages/playground/website/src/components/site-view/site-view.tsx index 6162466617..c7b7776d37 100644 --- a/packages/playground/website/src/components/site-view/site-view.tsx +++ b/packages/playground/website/src/components/site-view/site-view.tsx @@ -77,8 +77,8 @@ export function SiteView({ iframeRef, siteViewRef, }: { - blueprint: Blueprint; - currentConfiguration: PlaygroundConfiguration; + blueprint?: Blueprint; + currentConfiguration?: PlaygroundConfiguration; storage: StorageType; playground?: PlaygroundClient; url?: string; @@ -173,6 +173,10 @@ export function SiteView({ } }; + if (blueprint === undefined || currentConfiguration === undefined) { + return null; + } + return ( 0) { + // blueprint.plugins.forEach(plugin => query.append('plugin', plugin)); + //} + + // TODO + // if (blueprint.landingPage) { + // query.set('url', blueprint.landingPage); + // } + + // TODO as playgroundFeatures + // if (blueprint.phpExtensionBundles && blueprint.phpExtensionBundles.length > 0) { + // blueprint.phpExtensionBundles.forEach(bundle => query.append('php-extension-bundle', bundle)); + // } + + // TODO: Maybe + // if (blueprint.importSite) { + // query.set('import-site', blueprint.importSite); + // } + + // TODO: Maybe + // if (blueprint.importWxr) { + // query.set('import-wxr', blueprint.importWxr); + // } + + return newUrl; +} + +/** + * Create a Blueprint based on Playground query params. + * + * @param query Query params to convert to a Blueprint + * @returns A Blueprint reflecting the settings specified by query params + */ +export function queryParamsToBlueprint(query: URLSearchParams): Blueprint { + const features: Blueprint['features'] = {}; + + /** + * Networking is disabled by default, so we only need to enable it + * if the query param is explicitly set to "yes". + */ + if (query.get('networking') === 'yes') { + features['networking'] = true; + } + const blueprint = makeBlueprint({ + php: query.get('php') || defaultPhpVersion, + wp: query.get('wp') || 'latest', + theme: query.get('theme') || undefined, + login: !query.has('login') || query.get('login') === 'yes', + multisite: query.get('multisite') === 'yes', + features, + plugins: query.getAll('plugin'), + landingPage: query.get('url') || undefined, + phpExtensionBundles: query.getAll('php-extension-bundle') || [], + importSite: query.get('import-site') || undefined, + importWxr: + query.get('import-wxr') || query.get('import-content') || undefined, + language: query.get('language') || undefined, + }); + + return blueprint; +} diff --git a/packages/playground/website/src/lib/redux-store.ts b/packages/playground/website/src/lib/redux-store.ts index 95e845ce50..e3e074efc6 100644 --- a/packages/playground/website/src/lib/redux-store.ts +++ b/packages/playground/website/src/lib/redux-store.ts @@ -40,7 +40,7 @@ export type SiteListing = { }; // Define the state types -interface AppState { +export interface AppState { activeModal: string | null; offline: boolean; siteListing: SiteListing; diff --git a/packages/playground/website/src/lib/resolve-blueprint.ts b/packages/playground/website/src/lib/resolve-blueprint.ts index 47f62e50ed..f9e516e5b6 100644 --- a/packages/playground/website/src/lib/resolve-blueprint.ts +++ b/packages/playground/website/src/lib/resolve-blueprint.ts @@ -1,5 +1,5 @@ import { Blueprint } from '@wp-playground/client'; -import { makeBlueprint } from './make-blueprint'; +import { queryParamsToBlueprint } from './query-api'; const query = new URL(document.location.href).searchParams; const fragment = decodeURI(document.location.hash || '#').substring(1); @@ -50,31 +50,7 @@ export async function resolveBlueprint() { // If no blueprint was passed, prepare one based on the query params. // @ts-ignore if (typeof blueprint === 'undefined') { - const features: Blueprint['features'] = {}; - /** - * Networking is disabled by default, so we only need to enable it - * if the query param is explicitly set to "yes". - */ - if (query.get('networking') === 'yes') { - features['networking'] = true; - } - blueprint = makeBlueprint({ - php: query.get('php') || '8.0', - wp: query.get('wp') || 'latest', - theme: query.get('theme') || undefined, - login: !query.has('login') || query.get('login') === 'yes', - multisite: query.get('multisite') === 'yes', - features, - plugins: query.getAll('plugin'), - landingPage: query.get('url') || undefined, - phpExtensionBundles: query.getAll('php-extension-bundle') || [], - importSite: query.get('import-site') || undefined, - importWxr: - query.get('import-wxr') || - query.get('import-content') || - undefined, - language: query.get('language') || undefined, - }); + blueprint = queryParamsToBlueprint(query); } return blueprint; diff --git a/packages/playground/website/src/lib/use-boot-playground.ts b/packages/playground/website/src/lib/use-boot-playground.ts index 44ad5c50da..a3e223e67d 100644 --- a/packages/playground/website/src/lib/use-boot-playground.ts +++ b/packages/playground/website/src/lib/use-boot-playground.ts @@ -28,6 +28,10 @@ export function useBootPlayground({ blueprint }: UsePlaygroundOptions) { const dispatch: PlaygroundDispatch = useDispatch(); useEffect(() => { + if (!blueprint) { + return; + } + const remoteUrl = getRemoteUrl(); if (!iframe) { // Iframe ref is likely not set on the initial render. @@ -84,7 +88,7 @@ export function useBootPlayground({ blueprint }: UsePlaygroundOptions) { } doRun(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [iframe, awaitedIframe, mountDescriptor]); + }, [iframe, awaitedIframe, mountDescriptor, blueprint]); return { playground, url, iframeRef }; } diff --git a/packages/playground/website/src/main.tsx b/packages/playground/website/src/main.tsx index ac4b7065f7..1ff0bd499a 100644 --- a/packages/playground/website/src/main.tsx +++ b/packages/playground/website/src/main.tsx @@ -17,63 +17,46 @@ import { __experimentalNavigatorProvider as NavigatorProvider, __experimentalNavigatorScreen as NavigatorScreen, } from '@wordpress/components'; +import { Blueprint } from '@wp-playground/blueprints'; collectWindowErrors(logger); -const query = new URL(document.location.href).searchParams; -const blueprint = await resolveBlueprint(); - -// @ts-ignore -const opfsSupported = typeof navigator?.storage?.getDirectory !== 'undefined'; -let storageRaw = query.get('storage'); -if (StorageTypes.includes(storageRaw as any) && !opfsSupported) { - storageRaw = 'none'; -} else if (!StorageTypes.includes(storageRaw as any)) { - storageRaw = 'none'; -} -const storage = storageRaw as StorageType; - -const currentConfiguration: PlaygroundConfiguration = { - wp: blueprint.preferredVersions?.wp || 'latest', - php: resolveVersion(blueprint.preferredVersions?.php, SupportedPHPVersions), - storage: storage || 'none', - withExtensions: blueprint.phpExtensionBundles?.[0] !== 'light', - withNetworking: blueprint.features?.networking || false, - resetSite: false, -}; +function Main() { + const query = new URL(document.location.href).searchParams; -/* - * The 6.3 release includes a caching bug where - * registered styles aren't enqueued when they - * should be. This isn't present in all environments - * but it does here in the Playground. For now, - * the fix is to define `WP_DEVELOPMENT_MODE = all` - * to bypass the style cache. - * - * @see https://core.trac.wordpress.org/ticket/59056 - */ -if (currentConfiguration.wp === '6.3') { - blueprint.steps?.unshift({ - step: 'defineWpConfigConsts', - consts: { - WP_DEVELOPMENT_MODE: 'all', - }, - }); -} + // @ts-ignore + const opfsSupported = + typeof navigator?.storage?.getDirectory !== 'undefined'; + let storageRaw = query.get('storage'); + if (StorageTypes.includes(storageRaw as any) && !opfsSupported) { + storageRaw = 'none'; + } else if (!StorageTypes.includes(storageRaw as any)) { + storageRaw = 'none'; + } + const storage = storageRaw as StorageType; -function Main() { const siteViewRef = useRef(null); const [siteSlug, setSiteSlug] = useState( query.get('site-slug') ?? undefined ); + const [{ blueprint, currentConfiguration }, setBlueprintAndConfig] = + useState<{ + blueprint?: Blueprint; + currentConfiguration?: PlaygroundConfiguration; + }>({}); useEffect(() => { + async function assignConfigState() { + setBlueprintAndConfig(await compileConfiguration(storage)); + } + if (siteSlug && storage !== 'browser') { alert( 'Site slugs only work with browser storage. The site slug will be ignored.' ); } - }, [siteSlug]); + assignConfigState(); + }, [siteSlug, storage]); const { playground, url, iframeRef } = useBootPlayground({ blueprint }); @@ -112,6 +95,46 @@ root.render( ); +async function compileConfiguration(storage: StorageType) { + const blueprint = await resolveBlueprint(); + + const currentConfiguration: PlaygroundConfiguration = { + wp: blueprint.preferredVersions?.wp || 'latest', + php: resolveVersion( + blueprint.preferredVersions?.php, + SupportedPHPVersions + ), + storage: storage || 'none', + withExtensions: blueprint.phpExtensionBundles?.[0] !== 'light', + withNetworking: blueprint.features?.networking || false, + resetSite: false, + }; + + /* + * The 6.3 release includes a caching bug where + * registered styles aren't enqueued when they + * should be. This isn't present in all environments + * but it does here in the Playground. For now, + * the fix is to define `WP_DEVELOPMENT_MODE = all` + * to bypass the style cache. + * + * @see https://core.trac.wordpress.org/ticket/59056 + */ + if (currentConfiguration.wp === '6.3') { + blueprint.steps?.unshift({ + step: 'defineWpConfigConsts', + consts: { + WP_DEVELOPMENT_MODE: 'all', + }, + }); + } + + return { + blueprint, + currentConfiguration, + }; +} + function resolveVersion( version: string | undefined, allVersions: readonly T[], From a38fb95b1da16476522077f8655756706c817c05 Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Wed, 28 Aug 2024 01:22:20 -0400 Subject: [PATCH 2/2] Avoid double-boot condition This is a fragile change but should be only temporary as we try tighten up the site storage data model. --- .../website/src/lib/use-boot-playground.ts | 25 +++++++------------ packages/playground/website/src/main.tsx | 16 +++++++++--- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/playground/website/src/lib/use-boot-playground.ts b/packages/playground/website/src/lib/use-boot-playground.ts index a3e223e67d..9cebb3773e 100644 --- a/packages/playground/website/src/lib/use-boot-playground.ts +++ b/packages/playground/website/src/lib/use-boot-playground.ts @@ -1,28 +1,24 @@ import { useEffect, useRef, useState } from 'react'; import { Blueprint, startPlaygroundWeb } from '@wp-playground/client'; -import type { PlaygroundClient } from '@wp-playground/client'; +import type { MountDescriptor, PlaygroundClient } from '@wp-playground/client'; import { getRemoteUrl } from './config'; import { logger } from '@php-wasm/logger'; -import { - PlaygroundDispatch, - PlaygroundReduxState, - setActiveModal, -} from './redux-store'; -import { useDispatch, useSelector } from 'react-redux'; +import { PlaygroundDispatch, setActiveModal } from './redux-store'; +import { useDispatch } from 'react-redux'; import { playgroundAvailableInOpfs } from '../components/playground-configuration-group/playground-available-in-opfs'; import { directoryHandleFromMountDevice } from '@wp-playground/storage'; interface UsePlaygroundOptions { blueprint?: Blueprint; + mountDescriptor?: Omit; } -export function useBootPlayground({ blueprint }: UsePlaygroundOptions) { +export function useBootPlayground({ + blueprint, + mountDescriptor, +}: UsePlaygroundOptions) { const iframeRef = useRef(null); const iframe = iframeRef.current; - const started = useRef(undefined); const [url, setUrl] = useState(); - const mountDescriptor = useSelector( - (state: PlaygroundReduxState) => state.opfsMountDescriptor - ); const [playground, setPlayground] = useState(); const [awaitedIframe, setAwaitedIframe] = useState(false); const dispatch: PlaygroundDispatch = useDispatch(); @@ -32,7 +28,6 @@ export function useBootPlayground({ blueprint }: UsePlaygroundOptions) { return; } - const remoteUrl = getRemoteUrl(); if (!iframe) { // Iframe ref is likely not set on the initial render. // Re-render the current component to start the playground. @@ -43,8 +38,6 @@ export function useBootPlayground({ blueprint }: UsePlaygroundOptions) { } async function doRun() { - started.current = remoteUrl.toString(); - let isWordPressInstalled = false; if (mountDescriptor) { isWordPressInstalled = await playgroundAvailableInOpfs( @@ -88,7 +81,7 @@ export function useBootPlayground({ blueprint }: UsePlaygroundOptions) { } doRun(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [iframe, awaitedIframe, mountDescriptor, blueprint]); + }, [iframe, awaitedIframe, blueprint]); return { playground, url, iframeRef }; } diff --git a/packages/playground/website/src/main.tsx b/packages/playground/website/src/main.tsx index 1ff0bd499a..74130c9e4b 100644 --- a/packages/playground/website/src/main.tsx +++ b/packages/playground/website/src/main.tsx @@ -10,9 +10,9 @@ import { SiteView } from './components/site-view/site-view'; import { StorageTypes, StorageType } from './types'; import { SiteManager } from './components/site-manager'; import { useBootPlayground } from './lib/use-boot-playground'; -import { Provider } from 'react-redux'; +import { Provider, useSelector } from 'react-redux'; import { useEffect, useRef, useState } from '@wordpress/element'; -import store from './lib/redux-store'; +import store, { PlaygroundReduxState } from './lib/redux-store'; import { __experimentalNavigatorProvider as NavigatorProvider, __experimentalNavigatorScreen as NavigatorScreen, @@ -35,6 +35,9 @@ function Main() { } const storage = storageRaw as StorageType; + const mountDescriptor = useSelector( + (state: PlaygroundReduxState) => state.opfsMountDescriptor + ); const siteViewRef = useRef(null); const [siteSlug, setSiteSlug] = useState( query.get('site-slug') ?? undefined @@ -49,16 +52,21 @@ function Main() { async function assignConfigState() { setBlueprintAndConfig(await compileConfiguration(storage)); } + assignConfigState(); + }, [storage, mountDescriptor]); + useEffect(() => { if (siteSlug && storage !== 'browser') { alert( 'Site slugs only work with browser storage. The site slug will be ignored.' ); } - assignConfigState(); }, [siteSlug, storage]); - const { playground, url, iframeRef } = useBootPlayground({ blueprint }); + const { playground, url, iframeRef } = useBootPlayground({ + blueprint, + mountDescriptor, + }); return (