From 6b9be8b080f698d601e66efb1535c689606f3c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Sun, 6 Oct 2024 23:18:41 +0200 Subject: [PATCH 01/13] Initial version of the contextual settings dropdown --- .../src/components/browser-chrome/index.tsx | 39 ++ .../browser-chrome/style.module.css | 4 + .../edit-site-settings-button/index.tsx | 42 ++ .../style.module.css | 3 + .../components/playground-viewport/index.tsx | 19 +- .../src/components/site-manager/icons.tsx | 3 +- .../components/site-manager/sidebar/index.tsx | 183 +++--- .../site-manager/sidebar/style.module.css | 2 +- .../site-manager/site-create-button/index.tsx | 50 -- .../site-manager/site-info-panel/index.tsx | 379 +++--------- .../site-info-panel/style.module.css | 26 +- .../active-site-settings-form.tsx | 35 ++ .../site-manager/site-settings-form/index.tsx | 562 +----------------- .../stored-site-settings-form.tsx} | 89 ++- .../site-settings-form/style.module.css | 43 ++ .../temporary-site-settings-form.tsx} | 54 +- .../unconnected-site-settings-form.tsx | 497 ++++++++++++++++ .../site-manager/storage-type/index.tsx | 4 +- .../website/src/lib/get-relative-date.ts | 28 + 19 files changed, 996 insertions(+), 1066 deletions(-) create mode 100644 packages/playground/website/src/components/edit-site-settings-button/index.tsx create mode 100644 packages/playground/website/src/components/edit-site-settings-button/style.module.css delete mode 100644 packages/playground/website/src/components/site-manager/site-create-button/index.tsx create mode 100644 packages/playground/website/src/components/site-manager/site-settings-form/active-site-settings-form.tsx rename packages/playground/website/src/components/site-manager/{site-edit-button/index.tsx => site-settings-form/stored-site-settings-form.tsx} (52%) rename packages/playground/website/src/components/site-manager/{start-similar-site-button/index.tsx => site-settings-form/temporary-site-settings-form.tsx} (70%) create mode 100644 packages/playground/website/src/components/site-manager/site-settings-form/unconnected-site-settings-form.tsx create mode 100644 packages/playground/website/src/lib/get-relative-date.ts diff --git a/packages/playground/website/src/components/browser-chrome/index.tsx b/packages/playground/website/src/components/browser-chrome/index.tsx index 45efa40405..9aa133372b 100644 --- a/packages/playground/website/src/components/browser-chrome/index.tsx +++ b/packages/playground/website/src/components/browser-chrome/index.tsx @@ -9,6 +9,10 @@ import { useActiveSite, } from '../../lib/state/redux/store'; import { SyncLocalFilesButton } from '../sync-local-files-button'; +import { Dropdown, Icon } from '@wordpress/components'; +import { cog } from '@wordpress/icons'; +import Button from '../button'; +import { ActiveSiteSettingsForm } from '../site-manager/site-settings-form'; interface BrowserChromeProps { children?: React.ReactNode; @@ -57,6 +61,41 @@ export default function BrowserChrome({
+ ( + + )} + renderContent={({ onClose }) => ( +
+
+

+ Playground settings +

+
+ +
+ )} + /> {activeSite?.metadata?.storage === 'local-fs' ? ( ) : null} diff --git a/packages/playground/website/src/components/browser-chrome/style.module.css b/packages/playground/website/src/components/browser-chrome/style.module.css index c29d85585b..9b08d513f1 100644 --- a/packages/playground/website/src/components/browser-chrome/style.module.css +++ b/packages/playground/website/src/components/browser-chrome/style.module.css @@ -77,6 +77,10 @@ body.is-embedded .fake-window-wrapper { pointer-events: none; } +.padded { + padding: 24px; +} + @media (max-width: 1100px) { .has-site-manager { .window-controls { diff --git a/packages/playground/website/src/components/edit-site-settings-button/index.tsx b/packages/playground/website/src/components/edit-site-settings-button/index.tsx new file mode 100644 index 0000000000..c2a89a2ee1 --- /dev/null +++ b/packages/playground/website/src/components/edit-site-settings-button/index.tsx @@ -0,0 +1,42 @@ +import css from './style.module.css'; +import { useAppDispatch } from '../../lib/state/redux/store'; + +import Button from '../button'; +import { setSiteManagerOpen } from '../../lib/state/redux/slice-ui'; + +function SiteManagerIcon() { + return ( + + + + ); +} + +export function OpenSiteManagerButton() { + const dispatch = useAppDispatch(); + + const onClick = () => { + dispatch(setSiteManagerOpen(true)); + }; + return ( + + ); +} diff --git a/packages/playground/website/src/components/edit-site-settings-button/style.module.css b/packages/playground/website/src/components/edit-site-settings-button/style.module.css new file mode 100644 index 0000000000..94c84dd084 --- /dev/null +++ b/packages/playground/website/src/components/edit-site-settings-button/style.module.css @@ -0,0 +1,3 @@ +.open-site-manager-button:global(> svg) { + margin-top: 4px; +} diff --git a/packages/playground/website/src/components/playground-viewport/index.tsx b/packages/playground/website/src/components/playground-viewport/index.tsx index f1d3666950..0614f6a7ac 100644 --- a/packages/playground/website/src/components/playground-viewport/index.tsx +++ b/packages/playground/website/src/components/playground-viewport/index.tsx @@ -19,7 +19,7 @@ import { selectTemporarySites, } from '../../lib/state/redux/slice-sites'; import classNames from 'classnames'; -import { SiteCreateButton } from '../site-manager/site-create-button'; +import { PlaygroundRoute, redirectTo } from '../../lib/state/url/router'; export const supportedDisplayModes = [ 'browser-full-screen', @@ -130,14 +130,15 @@ export const KeepAliveTemporarySitesViewport = () => { className={css.siteErrorContent} style={{ textAlign: 'center' }} > -

You don't have any sites right now

- - {(onClick) => ( - - )} - +

You don't have any Playgrounds right now

+
diff --git a/packages/playground/website/src/components/site-manager/icons.tsx b/packages/playground/website/src/components/site-manager/icons.tsx index a08409089f..aef46a86af 100644 --- a/packages/playground/website/src/components/site-manager/icons.tsx +++ b/packages/playground/website/src/components/site-manager/icons.tsx @@ -93,13 +93,14 @@ export const FolderIcon = ( ); -export const ClockIcon = ( +export const ClockIcon = (props?: React.SVGProps) => ( void; }) { - const sites = useAppSelector(selectSortedSites); + const storedSites = useAppSelector(selectSortedSites).filter( + (site) => site.metadata.storage !== 'none' + ); + const temporarySite = useAppSelector(selectTemporarySites)[0]; const activeSite = useActiveSite(); const dispatch = useAppDispatch(); @@ -76,71 +81,100 @@ export function Sidebar({ {/* */} {site.metadata.name - ) : ( - - )} - - {site.metadata.name} - - - - ); - })} + { + if (temporarySite) { + onSiteClick(temporarySite.slug); + return; + } + redirectTo(PlaygroundRoute.newTemporarySite()); + }} + isSelected={activeSite?.metadata.storage === 'none'} + // eslint-disable-next-line jsx-a11y/aria-role + role="" + title="This is a temporary Playground. Your changes will be lost on page refresh." + > + + + + Temporary Playground + + + + {storedSites.length > 0 && ( + <> + + Saved Playgrounds + + + {storedSites.map((site) => { + /** + * The `wordpress` site is selected when no site slug is provided. + */ + const isSelected = + site.slug === activeSite?.slug; + return ( + onSiteClick(site.slug)} + isSelected={isSelected} + // eslint-disable-next-line jsx-a11y/aria-role + role="" + > + + {site.metadata.logo ? ( + { + ) : ( + + )} + + {site.metadata.name} + + + + ); + })} + + + )}
- - {(onClick) => ( -
- -
- )} -
); } diff --git a/packages/playground/website/src/components/site-manager/sidebar/style.module.css b/packages/playground/website/src/components/site-manager/sidebar/style.module.css index 2fda6d2969..1327a7ca6d 100644 --- a/packages/playground/website/src/components/site-manager/sidebar/style.module.css +++ b/packages/playground/website/src/components/site-manager/sidebar/style.module.css @@ -32,7 +32,7 @@ .sidebar-label { color: #949494 !important; text-transform: uppercase; - padding: 0 12px 8px; + padding: 8px 12px 8px; font-size: 12px !important; } diff --git a/packages/playground/website/src/components/site-manager/site-create-button/index.tsx b/packages/playground/website/src/components/site-manager/site-create-button/index.tsx deleted file mode 100644 index ae7012f9e4..0000000000 --- a/packages/playground/website/src/components/site-manager/site-create-button/index.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Modal } from '@wordpress/components'; -import { useState } from '@wordpress/element'; -import SiteSettingsForm, { SiteFormData } from '../site-settings-form'; -import { PlaygroundRoute, redirectTo } from '../../../lib/state/url/router'; - -export function SiteCreateButton({ - children, -}: { - children: (onClick: () => void) => React.ReactNode; -}) { - const [isModalOpen, setModalOpen] = useState(false); - - const addSite = async (data: SiteFormData) => { - redirectTo( - PlaygroundRoute.newTemporarySite({ - query: { - php: data.phpVersion, - wp: data.wpVersion, - name: data.name, - networking: data.withNetworking ? 'yes' : 'no', - 'php-extension-bundle': data.withExtensions - ? 'kitchen-sink' - : 'light', - language: data.language, - multisite: data.multisite ? 'yes' : 'no', - }, - }) - ); - setModalOpen(false); - }; - - return ( - <> - {children(() => setModalOpen(true))} - - {isModalOpen && ( - setModalOpen(false)} - > - setModalOpen(false)} - submitButtonText="Create a temporary Playground" - /> - - )} - - ); -} diff --git a/packages/playground/website/src/components/site-manager/site-info-panel/index.tsx b/packages/playground/website/src/components/site-manager/site-info-panel/index.tsx index aa9f922cd2..0f4a5be6c0 100644 --- a/packages/playground/website/src/components/site-manager/site-info-panel/index.tsx +++ b/packages/playground/website/src/components/site-manager/site-info-panel/index.tsx @@ -12,12 +12,10 @@ import { TabPanel, } from '@wordpress/components'; import { useMediaQuery } from '@wordpress/compose'; -import { moreVertical, external, copy, chevronLeft } from '@wordpress/icons'; +import { moreVertical, external, chevronLeft } from '@wordpress/icons'; import { SiteLogs } from '../../log-modal'; import { useAppDispatch, useAppSelector } from '../../../lib/state/redux/store'; -import { StorageType } from '../storage-type'; import { usePlaygroundClientInfo } from '../../../lib/use-playground-client'; -import { SiteEditButton } from '../site-edit-button'; import { OfflineNotice } from '../../offline-notice'; import { DownloadAsZipMenuItem } from '../../toolbar-buttons/download-as-zip'; import { GithubExportMenuItem } from '../../toolbar-buttons/github-export-menu-item'; @@ -25,29 +23,12 @@ import { GithubImportMenuItem } from '../../toolbar-buttons/github-import-menu-i import { ReportError } from '../../toolbar-buttons/report-error'; import { RestoreFromZipMenuItem } from '../../toolbar-buttons/restore-from-zip'; import { TemporarySiteNotice } from '../temporary-site-notice'; -import { SitePersistButton } from '../site-persist-button'; import { SiteInfo } from '../../../lib/state/redux/slice-sites'; import { setSiteManagerOpen } from '../../../lib/state/redux/slice-ui'; import { selectClientInfoBySiteSlug } from '../../../lib/state/redux/slice-clients'; import { encodeStringAsBase64 } from '../../../lib/base64'; -import { StartSimilarSiteButton } from '../start-similar-site-button'; - -function SiteInfoRow({ - label, - value, -}: { - label: string; - value: string | JSX.Element; -}) { - return ( - - {label} - - {value} - - - ); -} +import { ActiveSiteSettingsForm } from '../site-settings-form/active-site-settings-form'; +import { getRelativeDate } from '../../../lib/get-relative-date'; export function SiteInfoPanel({ className, @@ -94,6 +75,14 @@ export function SiteInfoPanel({ playground?.goTo(path); } + const isTemporary = site.metadata.storage === 'none'; + + const { opfsMountDescriptor } = usePlaygroundClientInfo(site.slug) || {}; + + const localDirName = + site.metadata?.storage === 'local-fs' + ? (opfsMountDescriptor as any)?.device?.handle?.name + : undefined; return (
- {site.metadata.storage === 'none' ? ( - - ) : null} @@ -164,19 +151,40 @@ export function SiteInfoPanel({ css.siteInfoHeaderDetailsName } > - {site.metadata.name} + {isTemporary + ? 'Temporary Playground' + : site.metadata.name} - - {site.metadata.whenCreated - ? `Created ${new Date( - site.metadata.whenCreated - ).toLocaleString()}` - : 'Created recently'} - + {!isTemporary && ( + + {(function () { + const createdAgo = site.metadata + .whenCreated + ? getRelativeDate( + new Date( + site.metadata.whenCreated + ) + ) + : ''; + switch (site.metadata.storage) { + case 'local-fs': + return ( + 'Saved in a local directory' + + (localDirName + ? ` (${localDirName})` + : '') + + ` ${createdAgo}` + ); + case 'opfs': + return `Saved in this browser ${createdAgo}`; + } + })()}{' '} + + )} @@ -240,20 +248,22 @@ export function SiteInfoPanel({ )} - - - removeSiteAndCloseMenu( - onClose - ) - } - > - Delete - - + {!isTemporary && ( + + + removeSiteAndCloseMenu( + onClose + ) + } + > + Delete + + + )} + + View Blueprint + {tab.name === 'settings' && (
- + {offline ? ( +
+ +
+ ) : null} + + {isTemporary ? ( + + ) : null} + +
)} {tab.name === 'logs' && ( @@ -368,234 +404,3 @@ export function SiteInfoPanel({
); } - -function SiteSettingsTab({ site }: { site: SiteInfo }) { - const username = 'admin'; - const password = 'password'; - - const offline = useAppSelector((state) => state.ui.offline); - - const { opfsMountDescriptor, client } = - usePlaygroundClientInfo(site.slug) || {}; - - const localDirName = - site.metadata?.storage === 'local-fs' - ? (opfsMountDescriptor as any)?.device?.handle?.name - : undefined; - - return ( - <> - {offline ? : null} - - - - -

- Playground details -

-
- - - - - - - {site.metadata.storage === 'none' ? ( - - - - - - ) : null} - - {site.metadata.storage === 'local-fs' && - localDirName ? ( - - {` (${localDirName})`} - - ) : null} -
- } - /> - - - -
- - - - - -

WP Admin

-
- } - iconPosition="right" - onClick={() => { - navigator.clipboard.writeText(username); - }} - label="Copy username" - > - {username} - - } - /> - } - iconPosition="right" - onClick={() => { - navigator.clipboard.writeText(password); - }} - label="Copy password" - > - {password} - - } - /> -
-
- - - - {site.metadata.storage === 'none' ? ( - - {(onClick) => ( - - )} - - ) : ( - - {(onClick) => ( - - )} - - )} - - {site.metadata.originalBlueprint ? ( - - - - ) : null} - - - - - ); -} diff --git a/packages/playground/website/src/components/site-manager/site-info-panel/style.module.css b/packages/playground/website/src/components/site-manager/site-info-panel/style.module.css index 14b38a8ef6..7f65bc3233 100644 --- a/packages/playground/website/src/components/site-manager/site-info-panel/style.module.css +++ b/packages/playground/website/src/components/site-manager/site-info-panel/style.module.css @@ -46,8 +46,6 @@ .tab-contents { width: 100%; flex-direction: column; - padding-top: var(--padding-size); - padding-bottom: var(--padding-size); position: relative; } @@ -61,7 +59,10 @@ } .site-notice { - padding: 40px var(--padding-size); + padding: var(--padding-size); + :global(& .components-notice__content) { + margin: 0; + } } .section-title { @@ -168,25 +169,6 @@ flex: none; } -.site-info-section { - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 0px; - width: 100%; - gap: 16px; -} - -.info-row-label { - min-width: 130px; - flex-basis: 130px; - color: var(--color-gray-600); -} - -.info-row-value { - color: var(--color-gray-900); -} - .max-width { width: 100%; } diff --git a/packages/playground/website/src/components/site-manager/site-settings-form/active-site-settings-form.tsx b/packages/playground/website/src/components/site-manager/site-settings-form/active-site-settings-form.tsx new file mode 100644 index 0000000000..0acad1c12e --- /dev/null +++ b/packages/playground/website/src/components/site-manager/site-settings-form/active-site-settings-form.tsx @@ -0,0 +1,35 @@ +import { useActiveSite } from '../../../lib/state/redux/store'; +import { StoredSiteSettingsForm } from './stored-site-settings-form'; +import { TemporarySiteSettingsForm } from './temporary-site-settings-form'; + +export function ActiveSiteSettingsForm({ + onSubmit, +}: { + onSubmit?: () => void; +}) { + const activeSite = useActiveSite(); + + if (!activeSite) { + return null; + } + + switch (activeSite.metadata?.storage) { + case 'none': + return ( + + ); + case 'opfs': + case 'local-fs': + return ( + + ); + default: + return null; + } +} diff --git a/packages/playground/website/src/components/site-manager/site-settings-form/index.tsx b/packages/playground/website/src/components/site-manager/site-settings-form/index.tsx index 409d8edc9f..cd9d8453eb 100644 --- a/packages/playground/website/src/components/site-manager/site-settings-form/index.tsx +++ b/packages/playground/website/src/components/site-manager/site-settings-form/index.tsx @@ -1,558 +1,4 @@ -import type { SupportedPHPVersion } from '@php-wasm/universal'; -import { SupportedPHPVersionsList } from '@php-wasm/universal'; -import css from './style.module.css'; -import { Button, CheckboxControl, SelectControl } from '@wordpress/components'; -import { useEffect } from 'react'; -import { Controller, useForm } from 'react-hook-form'; -import classNames from 'classnames'; -import { - __experimentalInputControl as InputControl, - __experimentalVStack as VStack, - __experimentalHStack as HStack, -} from '@wordpress/components'; -import { useSupportedWordPressVersions } from './use-supported-wordpress-versions'; -import { randomSiteName } from '../../../lib/state/redux/random-site-name'; - -export interface SiteSettingsFormProps { - onSubmit: (data: any) => void; - onCancel?: () => void; - formFields?: Partial>; - submitButtonText?: string; - defaultValues?: Partial; -} - -export interface SiteFormData { - name: string; - phpVersion: SupportedPHPVersion; - wpVersion: string; - language: string; - withExtensions: boolean; - withNetworking: boolean; - multisite: boolean; -} - -export default function SiteSettingsForm({ - onSubmit, - onCancel, - submitButtonText, - defaultValues = {}, - formFields = { - name: true, - phpVersion: true, - wpVersion: true, - language: true, - withExtensions: true, - withNetworking: true, - multisite: true, - }, -}: SiteSettingsFormProps) { - defaultValues = { - name: randomSiteName(), - phpVersion: '8.0', - wpVersion: 'latest', - language: '', - withExtensions: true, - withNetworking: true, - multisite: false, - ...defaultValues, - }; - const { - handleSubmit, - setValue, - getValues, - control, - formState: { errors }, - } = useForm({ - defaultValues, - }); - - const { supportedWPVersions, latestWPVersion } = - useSupportedWordPressVersions(); - - useEffect(() => { - if ( - latestWPVersion && - ['', 'latest'].includes(getValues('wpVersion')) - ) { - setValue('wpVersion', latestWPVersion); - } - }, [latestWPVersion, setValue, getValues]); - - return ( -
- - {formFields.name && ( - - ( - { - onChange(event); - }} - {...rest} - /> - )} - /> - - )} - - {formFields.wpVersion && ( - ( -
- ({ - label: `WordPress ${supportedWPVersions[version]}`, - value: version, - })), - ] - } - onChange={(value, extra) => { - onChange(extra?.event); - }} - {...rest} - /> - - Need an older version? - -
- )} - /> - )} - - {formFields.language && ( - ( - - a.label.localeCompare(b.label) - )} - onChange={(value, extra) => { - onChange(extra?.event); - }} - {...rest} - /> - )} - /> - )} - {formFields.phpVersion && ( - ( - element. That's not a problem when the select - // options have long names and stretch the