Skip to content

Commit

Permalink
Fix: Import & Export from Github causes reloading the playground even…
Browse files Browse the repository at this point in the history
… before accept this step. (#1908)

## Motivation for the change, related issues

Issue #1902 

## Implementation details

Import & Export from Github causes reloading the playground even before
accept this step.
I know that it is because of adding new params in the URL.
So I kept functionality to fire those modal via URL, but I also added
logic to open those modals without adding params.

## Testing Instructions (or ideally a Blueprint)
1. open http://127.0.0.1:5400/website-server/
2. Go to playground settings
3. Click 3 dots next to "Homepage" button
4. Click export or import from GitHub
5. Modal should appear without reloading

Also you can check, that opening on slug is still working (but with
reloading on closing popup):
http://127.0.0.1:5400/website-server/?modal=github-import

---------

Co-authored-by: Brandon Payton <[email protected]>
  • Loading branch information
ajotka and brandonpayton authored Oct 31, 2024
1 parent 33e7d55 commit 1e51248
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 99 deletions.
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);
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();
}}
/>
</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(() => {
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

0 comments on commit 1e51248

Please sign in to comment.