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