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

[UX] Stored Playgrounds (no more data loss), multiple Playgrounds, UI WebApp Redesign #1731

Merged
merged 128 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
cddec5e
Bring in a router and ensure site-slug is always present in the URL
adamziel Sep 5, 2024
dad4104
Move Playground boot to an isolated component that responds to URL pa…
adamziel Sep 5, 2024
1549065
Temporarily rename "browser" storage to "opfs". TODO: Rename it back
adamziel Sep 5, 2024
60a7465
Fix sidebar navigation
adamziel Sep 5, 2024
d25b5e6
Store Playground client in the redux store
adamziel Sep 5, 2024
6bb40a4
Get initial Playground boot to work
adamziel Sep 5, 2024
9158c0b
Use the correct OPFS mount handle when booting
adamziel Sep 5, 2024
51d5abf
Remove siteViewRef
adamziel Sep 5, 2024
7d4726c
Display the initial selected site info in the site manager
adamziel Sep 5, 2024
f211db9
Rip out PlaygroundContext
adamziel Sep 5, 2024
9e5ad4a
Remove unnecessary useCallback usage
brandonpayton Sep 6, 2024
1dcb88e
Get rid of useNavigatorParams
brandonpayton Sep 6, 2024
6c21093
Stop using the Navigator component
brandonpayton Sep 6, 2024
126bcdf
Use usePlaygroundClient where reasonable
brandonpayton Sep 6, 2024
c836577
Fix setting initial client info
brandonpayton Sep 6, 2024
4fa44dd
Add a TODO to fix logs being shared across sites
brandonpayton Sep 6, 2024
50e4bd6
Avoid re-rendering JustViewport when the site manager is expanded
adamziel Sep 6, 2024
b054876
store opfs mount descriptor with the current client info, not as a
adamziel Sep 6, 2024
b833a3e
Rename addSite to createSite and removeSite to deleteSite
adamziel Sep 6, 2024
1aec606
Adjust the TODO comment
adamziel Sep 6, 2024
c406b66
Only execute the Blueprint on the first site boot. Never execute a Bl…
adamziel Sep 6, 2024
51aac0b
Load the most recently created site when no slug is present (instead …
adamziel Sep 6, 2024
6251c89
Add another todo to JustViewport
adamziel Sep 6, 2024
8dcdfdc
Fix the Add Site button
adamziel Sep 6, 2024
1ad41dd
Remove the `?storage` query arg. Use ?site-manager as a feature flag.
adamziel Sep 6, 2024
588900f
Replace all query args when navigating between sites.
adamziel Sep 6, 2024
488f7a9
Reduce type proliferation: use the partial Blueprint type to store th…
adamziel Sep 6, 2024
4f00aa0
Add a TODO
adamziel Sep 6, 2024
1a69968
Make SiteMetadata a field on SiteInfo instead of using inheritance
adamziel Sep 6, 2024
746dc23
Implement the "Save this site" button
adamziel Sep 6, 2024
e554fad
Inline onAddSite
adamziel Sep 6, 2024
45c9007
Decouple saving site to OPFS
adamziel Sep 7, 2024
e656467
Explore a local-fs save button
adamziel Sep 7, 2024
6160e60
Stop gating site-manager
brandonpayton Sep 7, 2024
f61a7b6
Explain why we use a worker to write to OPFS
brandonpayton Sep 7, 2024
1b73682
Store Local FS site metadata in OPFS
adamziel Sep 7, 2024
7f0e84e
Display local directory name in site info panel
adamziel Sep 7, 2024
b42eb63
Add a rudimentary AddSiteForm for the new modal
adamziel Sep 7, 2024
1e10f85
Actually create new sites via the Add Site Form
adamziel Sep 7, 2024
1af4424
Navigating to temporary sites restores the original URL used to creat…
adamziel Sep 7, 2024
b4b70bb
Make the temporary site notice non dismissible
adamziel Sep 7, 2024
49d18d2
Remove the old site settings modal
adamziel Sep 7, 2024
e85b14a
Add "language" select when creating a site
adamziel Sep 7, 2024
5a46d08
Edit site settings form
adamziel Sep 7, 2024
6f86ea1
Avoid redux serialization warning for updateClientInfo action
brandonpayton Sep 8, 2024
10c0f40
Tweak local-fs site config-saving after conflicting changes
brandonpayton Sep 8, 2024
ce42039
Handle site update operation
adamziel Sep 9, 2024
99f8e6b
Add Multisite checkbox to the site creation modal
adamziel Sep 9, 2024
d6771f7
Move hamburger menu inside the SiteView
adamziel Sep 9, 2024
795bf8e
Restore the delete site icon
adamziel Sep 9, 2024
a4b0092
"Duplicate" -> "Duplicate Site"
adamziel Sep 9, 2024
202676e
Inline the SiteView component
adamziel Sep 9, 2024
c8c43b8
Remove the old Playground Configuration Group component
adamziel Sep 9, 2024
7c95df2
Extract the temporary site notice into a separate component, open the…
adamziel Sep 9, 2024
337355c
Fix property merging in createNewSiteInfo()
adamziel Sep 9, 2024
b68c984
Design adjustments
adamziel Sep 9, 2024
0a5d313
Improve design of the site settings modal, use correct language codes
adamziel Sep 9, 2024
19e0346
Responsive interactions
adamziel Sep 10, 2024
66ff550
Responsiveness
adamziel Sep 10, 2024
ff083af
Navigation buttons in mobile sidebar
adamziel Sep 10, 2024
00f2ff7
Another batch of responsive design changes
adamziel Sep 10, 2024
a0646f1
Responsive navigation buttons and animations
adamziel Sep 10, 2024
e0c9690
Design updates: Updated labels, margins, placements
adamziel Sep 10, 2024
3ecbb64
Fix react warnings
adamziel Sep 10, 2024
4d63af7
Use dropdown V2 for site saving options
adamziel Sep 10, 2024
2400ba3
Cleanup OPFS storage API
adamziel Sep 10, 2024
6961270
Remove OPFSHelper class
adamziel Sep 10, 2024
c14e586
Reorganize state management files
adamziel Sep 10, 2024
5fa8b31
Split redux store into separate slices
adamziel Sep 10, 2024
e7c1839
Extract bootSiteClient to a separate thunk
adamziel Sep 10, 2024
1088758
Persist site updates to OPFS at redux level
adamziel Sep 10, 2024
b96a09e
Update local-fs sites metadata on redux store update
adamziel Sep 10, 2024
caba900
Centralized router
adamziel Sep 10, 2024
d8f3f46
Communicate lack of local FS support in the UI
adamziel Sep 11, 2024
3ae1070
Communicate site boot error messages
adamziel Sep 11, 2024
2fe3a29
Fix TS Types
adamziel Sep 11, 2024
de3ba17
Cleanup all TODOs
adamziel Sep 11, 2024
b7f6a7b
Cleanup one last todo and handle more errors
adamziel Sep 11, 2024
96d5514
Update docs
adamziel Sep 11, 2024
f01ba81
Correctly populate the site edit form for temp sites
adamziel Sep 11, 2024
5e03e90
Axe devtools: Fix the reported problems
adamziel Sep 11, 2024
89e5cdc
Consolidate makeBlueprint and resolveBlueprint
adamziel Sep 13, 2024
09e1205
Preserve or remove the hash when navigating between sites
adamziel Sep 13, 2024
59b1685
Retain temporary sites state even if the user navigates away from them
adamziel Sep 13, 2024
9eb59fa
Handle edge cases: no site selected, no sites available
adamziel Sep 13, 2024
f301903
Simplify EnsurePlaygroundSiteIsSelected to fix boot bugs
adamziel Sep 13, 2024
cce879e
Disable options requiring a site client until the site is booted
adamziel Sep 13, 2024
4255bda
Change language from Site to Playground
adamziel Sep 14, 2024
9f0e676
Merge branch 'trunk' into routing
bgrgicak Sep 16, 2024
ce5ef1d
Fix URL blueprint literal parsing to decode Base64 string
brandonpayton Sep 17, 2024
da9384e
Use structuredClone() to deep clone blueprint
brandonpayton Sep 17, 2024
fe2ce73
Avoid top-level declarations that are only used to initialize other t…
brandonpayton Sep 17, 2024
519e50f
Fix single char typo
brandonpayton Sep 17, 2024
1c1ee48
Remove stale comment
brandonpayton Sep 17, 2024
a4f90f2
Note possibilities for edit/fork existing site
brandonpayton Sep 18, 2024
a8b62c8
Clarify a bit about local FS availability detection
brandonpayton Sep 18, 2024
9df2f79
Add locale list TODO
brandonpayton Sep 18, 2024
0d9af8a
Add sync-local-files-button TODO
brandonpayton Sep 18, 2024
180f6e0
Fix restore-from-zip ARIA label
brandonpayton Sep 18, 2024
20647bd
Remove unused generateUniqueSiteName function
brandonpayton Sep 18, 2024
43d17cc
Allow deletion of broken sites with loading errors
brandonpayton Sep 18, 2024
f45d1c4
Fix saving temporary sites in Safari
brandonpayton Sep 18, 2024
d8d7225
Allow a limited number of concurrent writes in Safari
brandonpayton Sep 18, 2024
6197165
Continue writing files as soon as possible in Safari
brandonpayton Sep 18, 2024
f9831a2
Redirect to the updated site instead of passing a stub
brandonpayton Sep 19, 2024
b16eb95
Restore bug-related TODO until we confirm it is resolved
brandonpayton Sep 19, 2024
387337a
Remove unused siteSlug prop from StartPlaygroundOptions
brandonpayton Sep 19, 2024
092a895
Restore supplemental types needed for FileSystemDirectoryHandle methods
brandonpayton Sep 19, 2024
6169c26
Use createSyncAccessHandle for all browsers because it has been faste…
brandonpayton Sep 21, 2024
f9accc8
Clean up metadata writer worker a bit
brandonpayton Sep 21, 2024
e306b74
Address memfs-to-OPFS performance in Safari
brandonpayton Sep 21, 2024
af97321
Merge branch 'trunk' into routing
brandonpayton Sep 23, 2024
4ac62c3
Make sure all OPFS writes settle before resolving copyMemfsToOpfs()
brandonpayton Sep 23, 2024
27ede18
Expose temp-to-OPFS error to the user
brandonpayton Sep 23, 2024
670d158
Support retrying OPFS save after initial failure
brandonpayton Sep 23, 2024
e2fd6ee
Fix login by default
bgrgicak Sep 17, 2024
a4d6709
Fix default PHP and WP versions
bgrgicak Sep 16, 2024
d6f37ab
Migrate legacy sites to modern sites on-demand
brandonpayton Sep 25, 2024
baeee9d
Merge branch 'trunk' into routing
bgrgicak Sep 26, 2024
0b8b359
Add Playwright tests for UI redesign changes (#1769)
bgrgicak Sep 26, 2024
df2eeb4
Revert "Use createSyncAccessHandle for all browsers because it has be…
brandonpayton Sep 26, 2024
0dcf4ac
Add more log messages for moving legacy OPFS sites
brandonpayton Sep 26, 2024
dc1217d
Speed up saving site to local FS
brandonpayton Sep 27, 2024
568b9a4
Stop overlapping text with long name in site info view
brandonpayton Sep 27, 2024
7e6e7b2
Stop temp site notice from forcing mobile open-site buttons off botto…
brandonpayton Sep 27, 2024
da2fb8d
Make open-site label consistent among site info view buttons
brandonpayton Sep 27, 2024
886648a
More fixes for bottom buttons in mobile site info view
brandonpayton Sep 27, 2024
49f335f
Differentiate between SiteCreateButton and StartSimilarSiteButton
adamziel Sep 27, 2024
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
30 changes: 30 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"react-transition-group": "4.4.5",
"unzipper": "0.10.11",
"vite-plugin-api": "1.0.4",
"wouter": "3.3.5",
"xterm": "5.3.0",
"xterm-addon-fit": "0.8.0",
"yargs": "17.7.2"
Expand Down
4 changes: 2 additions & 2 deletions packages/playground/website/cypress/e2e/remote-assets.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ describe('Remote Assets', () => {
const testedStorageOptions = [
'none',
// TODO: Re-enable this option once the tests are more stable
//'browser'
//'opfs'
];

testedStorageOptions.forEach((storage) => {
Expand All @@ -15,7 +15,7 @@ describe('Remote Assets', () => {
cy.visit(`/?storage=${storage}#${blueprint}`);
runAssertions();

if (storage === 'browser') {
if (storage === 'opfs') {
// Reload and re-assert to test when loading from browser storage
cy.reload();
runAssertions();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default function BrowserChrome({
* TODO: Remove this once the site manager supports all storage options.
*/
const query = new URLSearchParams(window.location.search);
const showSiteManager = query.get('storage') === 'browser';
const showSiteManager = query.get('storage') === 'opfs';

return (
<div
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useEffect } from 'react';
import { resolveBlueprint } from '../../lib/resolve-blueprint';
import { useCurrentUrl, useSearchParams } from '../../lib/router-hooks';
import {
addSite,
setActiveSite,
useAppDispatch,
useAppSelector,
} from '../../lib/redux-store';
import {
createNewSiteInfo,
getSiteInfoBySlug,
SiteStorageType,
} from '../../lib/site-storage';

/**
* Ensures the redux store always has an activeSite value.
* It uses the URL as the source of truth and assumes the
* `site-slug` and `storage` query args are always set.
*/
export function EnsurePlaygroundSiteIsSelected({
children,
}: {
children: React.ReactNode;
}) {
const activeSite = useAppSelector((state) => state.activeSite);
const dispatch = useAppDispatch();
const [query] = useSearchParams();
const urlString = useCurrentUrl();
const requestedSiteSlug = query.get('site-slug');
const storage = query.get('storage')! as SiteStorageType;

useEffect(() => {
async function ensureSiteIsSelected() {
if (activeSite && activeSite.slug === requestedSiteSlug) {
if (activeSite.storage !== 'opfs') {
alert(
'Site slugs only work with browser storage. The site slug will be ignored.'
);
}
return;
}

console.log({ requestedSiteSlug });

Check warning on line 44 in packages/playground/website/src/components/ensure-playground-site/ensure-playground-site-is-selected.tsx

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Unexpected console statement
if (requestedSiteSlug !== 'create') {
const siteInfo = await getSiteInfoBySlug(requestedSiteSlug!);
console.log({ siteInfo });

Check warning on line 47 in packages/playground/website/src/components/ensure-playground-site/ensure-playground-site-is-selected.tsx

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Unexpected console statement
dispatch(setActiveSite(siteInfo!));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the identified site does not exist?

One scenario:

  1. User bookmarks Playground link for specific site slug/ID
  2. User deletes site, perhaps unintentionally, or clears browser storage
  3. User visits bookmark

Copy link
Collaborator Author

@adamziel adamziel Sep 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a notification system to tell the user what happened as we redirect to the first site that exists. There are WordPress components for that. It should be fine to delay that to another PR.

return;
}

const url = new URL(urlString);
const blueprint = await resolveBlueprint(url);
const newSiteInfo = createNewSiteInfo({
originalBlueprint: blueprint,
storage: storage,
});
await dispatch(addSite(newSiteInfo));
dispatch(setActiveSite(newSiteInfo!));
}

ensureSiteIsSelected();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [urlString, requestedSiteSlug, dispatch]);

if (!activeSite) {
return null;
}

return children;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useEffect } from 'react';
import { useLocation } from 'wouter';
import { urlContainsSiteConfiguration } from '../../lib/resolve-blueprint';
import { listSites, SiteStorageTypes } from '../../lib/site-storage';
import { useCurrentUrl, useSearchParams } from '../../lib/router-hooks';

// @ts-ignore
const opfsSupported = typeof navigator?.storage?.getDirectory !== 'undefined';

// Treats the URL as the source of truth. Ensures we're always at a URL
// that contains a site slug.
export function EnsurePlaygroundSiteSlug({
children,
}: {
children: React.ReactNode;
}) {
const [query, setQuery] = useSearchParams();
const siteSlug = query.get('site-slug');
const [, setLocation] = useLocation();
const urlString = useCurrentUrl();

// Ensure the site slug is always present in the URL.
useEffect(() => {
/*
* @TODO: Change the entire mental model of the `storage` parameter.
* For example, `storage=none` + an existing site slug makes
* no sense. We don't load where to load the site from, e.g.
* should it come from OPFS? Local directory? Network? We could
* separate the "load from" and "save" to operations, but they
* make more sense as user interactions than URL parameters.
* Perhaps we only need a single `load-site-from` URL parameter?
brandonpayton marked this conversation as resolved.
Show resolved Hide resolved
*/
// Ensure the optional storage query arg points to a valid storage type.
const storage = query.get('storage');
if (
storage &&
(!opfsSupported || !SiteStorageTypes.includes(storage as any))
) {
setQuery({ storage: 'none' });
return;
}

async function ensureSiteSlug() {
// @TODO: Restrict `create` as a system slug that cannot be assigned to a user
// site.
if (siteSlug) {
if (!storage) {
setQuery({ storage: opfsSupported ? 'opfs' : 'none' });
}
return;
}

if (urlContainsSiteConfiguration(new URL(urlString))) {
if (!storage || storage === 'none') {
setQuery({ 'site-slug': 'create', storage: 'none' });
} else {
setQuery({ 'site-slug': 'create' });
}
} else {
// @TODO: Sort sites by most recently used
adamziel marked this conversation as resolved.
Show resolved Hide resolved
const sites = await listSites();
if (sites.length > 0) {
setQuery({ 'site-slug': sites[0].slug });
} else {
setQuery({ 'site-slug': 'create' });
}
}
}
ensureSiteSlug();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [siteSlug, setLocation]);

if (!siteSlug || (siteSlug !== 'create' && !query.get('storage'))) {
return null;
}

return children;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { EnsurePlaygroundSiteIsSelected } from './ensure-playground-site-is-selected';
import { EnsurePlaygroundSiteSlug } from './ensure-playground-site-slug';

export function EnsurePlaygroundSite({
children,
}: {
children: React.ReactNode;
}) {
return (
<EnsurePlaygroundSiteSlug>
<EnsurePlaygroundSiteIsSelected>
{children}
adamziel marked this conversation as resolved.
Show resolved Hide resolved
</EnsurePlaygroundSiteIsSelected>
</EnsurePlaygroundSiteSlug>
);
}
46 changes: 15 additions & 31 deletions packages/playground/website/src/components/layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,24 @@ import css from './style.module.css';

import { SiteView } from '../site-view/site-view';
import { SiteManager } from '../site-manager';
import { useRef } from '@wordpress/element';
import { CSSTransition } from 'react-transition-group';
import { __experimentalUseNavigator as useNavigator } from '@wordpress/components';
import { Blueprint, PlaygroundClient } from '@wp-playground/client';
import { StorageType } from '../../types';
import { useAppSelector } from '../../lib/redux-store';
import { PlaygroundConfiguration } from '../playground-configuration-group/form';

export function Layout({
playground,
url,
iframeRef,
blueprint,
storage,
currentConfiguration,
siteSlug,
setSiteSlug,
}: {
playground: PlaygroundClient;
url: string;
iframeRef: React.RefObject<HTMLIFrameElement>;
blueprint: Blueprint;
storage: StorageType;
currentConfiguration: PlaygroundConfiguration;
siteSlug: string | undefined;
setSiteSlug: (siteSlug?: string) => void;
}) {
const siteViewRef = useRef<HTMLDivElement>(null);
export function Layout() {
const activeSite = useAppSelector((state) => state.activeSite!);
const blueprint = activeSite.originalBlueprint || {};
const storage = activeSite.storage;
// @TODO: Use SiteMetadata directly
const currentConfiguration: PlaygroundConfiguration = {
storage: storage ?? 'none',
wp: activeSite.wpVersion,
php: activeSite.phpVersion,
withExtensions: activeSite.phpExtensionBundle === 'kitchen-sink',
withNetworking: blueprint?.features?.networking || false,
resetSite: false,
};

const {
goTo,
Expand All @@ -51,10 +42,7 @@ export function Layout({
unmountOnExit
>
<div className={css.sidebar}>
<SiteManager
onSiteChange={setSiteSlug}
siteViewRef={siteViewRef}
/>
<SiteManager />
</div>
</CSSTransition>
<div className={css.siteView}>
Expand All @@ -65,13 +53,9 @@ export function Layout({
/>
)}
<SiteView
siteViewRef={siteViewRef}
blueprint={blueprint}
currentConfiguration={currentConfiguration}
storage={storage}
playground={playground}
url={url}
iframeRef={iframeRef}
className={css.siteViewContent}
hideToolbar={path?.startsWith('/manager')}
/>
Expand Down
Loading
Loading