Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"fix-asyncify": "node packages/php-wasm/node/bin/rebuild-while-asyncify-functions-missing.mjs",
"typecheck": "nx run-many --all --target=typecheck",
"prepare": "husky install",
"reset": "nx reset",
"recompile:php": "npm run recompile:php:web && npm run recompile:php:node",
"recompile:php:web:jspi:all": "nx recompile-php:jspi:all php-wasm-web",
"recompile:php:web:jspi:8.3": "nx recompile-php:jspi php-wasm-web -- --PHP_VERSION=8.3 ",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import {
setTemporarySiteSpec,
} from '../../lib/state/redux/slice-sites';
import {
selectActiveSite,
setActiveSite,
useAppDispatch,
useAppSelector,
} from '../../lib/state/redux/store';
import { redirectTo } from '../../lib/state/url/router';
import { logger } from '@php-wasm/logger';
import { Blueprint } from '@wp-playground/blueprints';
import { usePrevious } from '../../lib/hooks/use-previous';

/**
* Ensures the redux store always has an activeSite value.
Expand All @@ -32,12 +34,14 @@ export function EnsurePlaygroundSiteIsSelected({
const siteListingStatus = useAppSelector(
(state) => state.sites.loadingState
);
const activeSite = useAppSelector((state) => selectActiveSite(state));
const dispatch = useAppDispatch();
const url = useCurrentUrl();
const requestedSiteSlug = url.searchParams.get('site-slug');
const requestedSiteObject = useAppSelector((state) =>
selectSiteBySlug(state, requestedSiteSlug!)
);
const prevUrl = usePrevious(url);

useEffect(() => {
if (!opfsSiteStorage) {
Expand Down Expand Up @@ -82,13 +86,25 @@ export function EnsurePlaygroundSiteIsSelected({
return;
}

// If only the 'modal' parameter changes in searchParams, don't reload the page
const notRefreshingParam = 'modal';
const oldParams = new URLSearchParams(prevUrl?.search);
const newParams = new URLSearchParams(url?.search);
oldParams.delete(notRefreshingParam);
newParams.delete(notRefreshingParam);
const avoidUnnecessaryTempSiteReload =
activeSite && oldParams.toString() === newParams.toString();
if (avoidUnnecessaryTempSiteReload) {
return;
}

// If the site slug is missing, create a new temporary site.
// Lean on the Query API parameters and the Blueprint API to
// create the new site.
const url = new URL(window.location.href);
const newUrl = new URL(window.location.href);
Copy link
Member

Choose a reason for hiding this comment

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

Nice

let blueprint: Blueprint | undefined = undefined;
try {
blueprint = await resolveBlueprintFromURL(url);
blueprint = await resolveBlueprintFromURL(newUrl);
} catch (e) {
logger.error('Error resolving blueprint:', e);
}
Expand All @@ -99,8 +115,8 @@ export function EnsurePlaygroundSiteIsSelected({
originalBlueprint: blueprint,
},
originalUrlParams: {
searchParams: parseSearchParams(url.searchParams),
hash: url.hash,
searchParams: parseSearchParams(newUrl.searchParams),
hash: newUrl.hash,
},
})
);
Expand Down
84 changes: 42 additions & 42 deletions packages/playground/website/src/components/layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ export const modalSlugs = {
ERROR_REPORT: 'error-report',
START_ERROR: 'start-error',
IMPORT_FORM: 'import-form',
};
GITHUB_IMPORT: 'github-import',
GITHUB_EXPORT: 'github-export'
}

const displayMode = getDisplayModeFromQuery();
function getDisplayModeFromQuery(): DisplayMode {
Expand Down Expand Up @@ -176,47 +178,45 @@ function Modals(blueprint: Blueprint) {
return <StartErrorModal />;
} else if (currentModal === modalSlugs.IMPORT_FORM) {
return <ImportFormModal />;
} else if (currentModal === modalSlugs.GITHUB_IMPORT) {
return <GithubImportModal
onImported={({
url,
path,
files,
pluginOrThemeName,
contentType,
urlInformation: { owner, repo, type, pr },
}) => {
setGithubExportValues({
repoUrl: url,
prNumber: pr?.toString(),
toPathInRepo: path,
prAction: pr ? 'update' : 'create',
contentType,
plugin: pluginOrThemeName,
theme: pluginOrThemeName,
});
setGithubExportFiles(files);
}}
/>;
} else if (currentModal === modalSlugs.GITHUB_EXPORT) {
return <GithubExportModal
allowZipExport={
(query.get('ghexport-allow-include-zip') ?? 'yes') === 'yes'
}
initialValues={githubExportValues}
initialFilesBeforeChanges={githubExportFiles}
onExported={(prUrl, formValues) => {
setGithubExportValues(formValues);
setGithubExportFiles(undefined);
}}
/>;
}

return (
<>
{query.get('gh-ensure-auth') === 'yes' ? (
<GitHubOAuthGuardModal />
) : (
''
)}
<GithubImportModal
onImported={({
url,
path,
files,
pluginOrThemeName,
contentType,
urlInformation: { owner, repo, type, pr },
}) => {
setGithubExportValues({
repoUrl: url,
prNumber: pr?.toString(),
toPathInRepo: path,
prAction: pr ? 'update' : 'create',
contentType,
plugin: pluginOrThemeName,
theme: pluginOrThemeName,
});
setGithubExportFiles(files);
}}
/>
<GithubExportModal
allowZipExport={
(query.get('ghexport-allow-include-zip') ?? 'yes') === 'yes'
}
initialValues={githubExportValues}
initialFilesBeforeChanges={githubExportFiles}
onExported={(prUrl, formValues) => {
setGithubExportValues(formValues);
setGithubExportFiles(undefined);
}}
/>
</>
);
if (query.get('gh-ensure-auth') === 'yes') {
return <GitHubOAuthGuardModal />;
}

return;
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { MenuItem } from '@wordpress/components';
import { openModal } from '../../github/github-export-form/modal';
import { setActiveModal } from '../../lib/state/redux/slice-ui';
import { PlaygroundDispatch } from '../../lib/state/redux/store';
import { useDispatch } from 'react-redux';
import { modalSlugs } from '../layout';

interface Props {
onClose: () => void;
disabled?: boolean;
}
export function GithubExportMenuItem({ onClose, disabled }: Props) {
const dispatch: PlaygroundDispatch = useDispatch();
return (
<MenuItem
aria-label="Export WordPress theme, plugin, or wp-content directory to a GitHub repository as a Pull Request."
disabled={disabled}
onClick={() => {
openModal();
dispatch(setActiveModal(modalSlugs.GITHUB_EXPORT));
onClose();
}}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { MenuItem } from '@wordpress/components';
import { openModal } from '../../github/github-import-form/modal';
import { setActiveModal } from '../../lib/state/redux/slice-ui';
import { PlaygroundDispatch } from '../../lib/state/redux/store';
import { useDispatch } from 'react-redux';
import { modalSlugs } from '../layout';

interface Props {
onClose: () => void;
disabled?: boolean;
}
export function GithubImportMenuItem({ onClose, disabled }: Props) {
const dispatch: PlaygroundDispatch = useDispatch();
return (
<MenuItem
aria-label="Import WordPress theme, plugin, or wp-content directory from a GitHub repository."
disabled={disabled}
onClick={() => {
openModal();
dispatch(setActiveModal(modalSlugs.GITHUB_IMPORT));
onClose();
}}
>
Expand Down
40 changes: 17 additions & 23 deletions packages/playground/website/src/github/github-export-form/modal.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,43 @@
import { signal } from '@preact/signals-react';

import Modal, { defaultStyles } from '../../components/modal';
import GitHubExportForm, { GitHubExportFormProps } from './form';
import { usePlaygroundClient } from '../../lib/use-playground-client';

const query = new URLSearchParams(window.location.search);
export const isGitHubExportModalOpen = signal(
query.get('state') === 'github-export'
);
import { PlaygroundDispatch } from '../../lib/state/redux/store';
import { useDispatch } from 'react-redux';
import { setActiveModal } from '../../lib/state/redux/slice-ui';
import { useEffect } from 'react';

interface GithubExportModalProps {
allowZipExport: GitHubExportFormProps['allowZipExport'];
onExported?: GitHubExportFormProps['onExported'];
initialFilesBeforeChanges?: GitHubExportFormProps['initialFilesBeforeChanges'];
initialValues?: GitHubExportFormProps['initialValues'];
}
export function closeModal() {
isGitHubExportModalOpen.value = false;
// Remove ?state=github-export from the URL.
const url = new URL(window.location.href);
url.searchParams.delete('state');
window.history.replaceState({}, '', url.href);
}
export function openModal() {
isGitHubExportModalOpen.value = true;
// Add a ?state=github-export to the URL so that the user can refresh the page
// and still see the modal.
const url = new URL(window.location.href);
url.searchParams.set('state', 'github-export');
window.history.replaceState({}, '', url.href);
}
export function GithubExportModal({
onExported,
allowZipExport,
initialValues,
initialFilesBeforeChanges,
}: GithubExportModalProps) {
const dispatch: PlaygroundDispatch = useDispatch();
const playground = usePlaygroundClient();

useEffect(() => {
const url = new URL(window.location.href);
url.searchParams.set('modal', 'github-export');
window.history.replaceState({}, '', url.href);
}, []);

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

return (
<Modal
style={{
...defaultStyles,
content: { ...defaultStyles.content, width: 600 },
}}
isOpen={isGitHubExportModalOpen.value}
isOpen
onRequestClose={closeModal}
>
<GitHubExportForm
Expand Down
41 changes: 19 additions & 22 deletions packages/playground/website/src/github/github-import-form/modal.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,35 @@
import { signal } from '@preact/signals-react';
import Modal, { defaultStyles } from '../../components/modal';
import GitHubImportForm, { GitHubImportFormProps } from './form';
import { usePlaygroundClient } from '../../lib/use-playground-client';

const query = new URLSearchParams(window.location.search);
export const isGitHubModalOpen = signal(query.get('state') === 'github-import');
import { setActiveModal } from '../../lib/state/redux/slice-ui';
import { PlaygroundDispatch } from '../../lib/state/redux/store';
import { useDispatch } from 'react-redux';
import { useEffect } from 'react';

interface GithubImportModalProps {
defaultOpen?: boolean;
onImported?: GitHubImportFormProps['onImported'];
}
export function closeModal() {
isGitHubModalOpen.value = false;
// Remove ?state=github-import from the URL.
const url = new URL(window.location.href);
url.searchParams.delete('state');
window.history.replaceState({}, '', url.href);
}
export function openModal() {
isGitHubModalOpen.value = true;
// Add a ?state=github-import to the URL so that the user can refresh the page
// and still see the modal.
const url = new URL(window.location.href);
url.searchParams.set('state', 'github-import');
window.history.replaceState({}, '', url.href);
}
export function GithubImportModal({ onImported }: GithubImportModalProps) {
export function GithubImportModal({ defaultOpen, onImported }: GithubImportModalProps) {
const dispatch: PlaygroundDispatch = useDispatch();
const playground = usePlaygroundClient();

useEffect(() => {
const url = new URL(window.location.href);
url.searchParams.set('modal', 'github-import');
window.history.replaceState({}, '', url.href);
}, []);

const closeModal = () => {
dispatch(setActiveModal(null));
}
return (
<Modal
style={{
...defaultStyles,
content: { ...defaultStyles.content, width: 600 },
}}
isOpen={isGitHubModalOpen.value}
isOpen
onRequestClose={closeModal}
>
<GitHubImportForm
Expand All @@ -44,8 +41,8 @@ export function GithubImportModal({ onImported }: GithubImportModalProps) {
alert(
'Import finished! Your Playground site has been updated.'
);
closeModal();
onImported?.(details);
closeModal();
Copy link
Member

Choose a reason for hiding this comment

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

This seems like a good change because we'll have a chance to notice the modal remaining open if an error is thrown.

}}
/>
</Modal>
Expand Down
9 changes: 9 additions & 0 deletions packages/playground/website/src/lib/hooks/use-previous.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useEffect, useRef } from "react";

export const usePrevious = <T>(value: T): T | undefined => {
const ref = useRef<T>();
useEffect(() => {
Copy link
Member

Choose a reason for hiding this comment

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

This is a fun use of useEffect to get the previous value. Nice!

ref.current = value;
});
return ref.current;
};
11 changes: 7 additions & 4 deletions packages/playground/website/src/lib/state/redux/slice-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ const isMobile = window.innerWidth < 875;
const shouldOpenSiteManagerByDefault = false;

const initialState: UIState = {
activeModal:
query.get('modal') === 'mount-markdown-directory'
? 'mount-markdown-directory'
: null,
activeModal: query.get('modal') || null,
offline: !navigator.onLine,
// NOTE: Please do not eliminate the cases in this siteManagerIsOpen expression,
// even if they seem redundant. We may experiment which toggling the manager
Expand Down Expand Up @@ -67,6 +64,12 @@ const uiSlice = createSlice({
}
},
setActiveModal: (state, action: PayloadAction<string | null>) => {
if (action.payload === null) {
const url = new URL(window.location.href);
url.searchParams.delete('modal');
window.history.replaceState({}, '', url.href);
}

state.activeModal = action.payload;
},
setOffline: (state, action: PayloadAction<boolean>) => {
Expand Down