Skip to content

Commit 1e51248

Browse files
Fix: Import & Export from Github causes reloading the playground even 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]>
1 parent 33e7d55 commit 1e51248

File tree

9 files changed

+127
-99
lines changed

9 files changed

+127
-99
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"fix-asyncify": "node packages/php-wasm/node/bin/rebuild-while-asyncify-functions-missing.mjs",
2222
"typecheck": "nx run-many --all --target=typecheck",
2323
"prepare": "husky install",
24+
"reset": "nx reset",
2425
"recompile:php": "npm run recompile:php:web && npm run recompile:php:node",
2526
"recompile:php:web:jspi:all": "nx recompile-php:jspi:all php-wasm-web",
2627
"recompile:php:web:jspi:8.3": "nx recompile-php:jspi php-wasm-web -- --PHP_VERSION=8.3 ",

packages/playground/website/src/components/ensure-playground-site/ensure-playground-site-is-selected.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import {
88
setTemporarySiteSpec,
99
} from '../../lib/state/redux/slice-sites';
1010
import {
11+
selectActiveSite,
1112
setActiveSite,
1213
useAppDispatch,
1314
useAppSelector,
1415
} from '../../lib/state/redux/store';
1516
import { redirectTo } from '../../lib/state/url/router';
1617
import { logger } from '@php-wasm/logger';
1718
import { Blueprint } from '@wp-playground/blueprints';
19+
import { usePrevious } from '../../lib/hooks/use-previous';
1820

1921
/**
2022
* Ensures the redux store always has an activeSite value.
@@ -32,12 +34,14 @@ export function EnsurePlaygroundSiteIsSelected({
3234
const siteListingStatus = useAppSelector(
3335
(state) => state.sites.loadingState
3436
);
37+
const activeSite = useAppSelector((state) => selectActiveSite(state));
3538
const dispatch = useAppDispatch();
3639
const url = useCurrentUrl();
3740
const requestedSiteSlug = url.searchParams.get('site-slug');
3841
const requestedSiteObject = useAppSelector((state) =>
3942
selectSiteBySlug(state, requestedSiteSlug!)
4043
);
44+
const prevUrl = usePrevious(url);
4145

4246
useEffect(() => {
4347
if (!opfsSiteStorage) {
@@ -82,13 +86,25 @@ export function EnsurePlaygroundSiteIsSelected({
8286
return;
8387
}
8488

89+
// If only the 'modal' parameter changes in searchParams, don't reload the page
90+
const notRefreshingParam = 'modal';
91+
const oldParams = new URLSearchParams(prevUrl?.search);
92+
const newParams = new URLSearchParams(url?.search);
93+
oldParams.delete(notRefreshingParam);
94+
newParams.delete(notRefreshingParam);
95+
const avoidUnnecessaryTempSiteReload =
96+
activeSite && oldParams.toString() === newParams.toString();
97+
if (avoidUnnecessaryTempSiteReload) {
98+
return;
99+
}
100+
85101
// If the site slug is missing, create a new temporary site.
86102
// Lean on the Query API parameters and the Blueprint API to
87103
// create the new site.
88-
const url = new URL(window.location.href);
104+
const newUrl = new URL(window.location.href);
89105
let blueprint: Blueprint | undefined = undefined;
90106
try {
91-
blueprint = await resolveBlueprintFromURL(url);
107+
blueprint = await resolveBlueprintFromURL(newUrl);
92108
} catch (e) {
93109
logger.error('Error resolving blueprint:', e);
94110
}
@@ -99,8 +115,8 @@ export function EnsurePlaygroundSiteIsSelected({
99115
originalBlueprint: blueprint,
100116
},
101117
originalUrlParams: {
102-
searchParams: parseSearchParams(url.searchParams),
103-
hash: url.hash,
118+
searchParams: parseSearchParams(newUrl.searchParams),
119+
hash: newUrl.hash,
104120
},
105121
})
106122
);

packages/playground/website/src/components/layout/index.tsx

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ export const modalSlugs = {
4141
ERROR_REPORT: 'error-report',
4242
START_ERROR: 'start-error',
4343
IMPORT_FORM: 'import-form',
44-
};
44+
GITHUB_IMPORT: 'github-import',
45+
GITHUB_EXPORT: 'github-export'
46+
}
4547

4648
const displayMode = getDisplayModeFromQuery();
4749
function getDisplayModeFromQuery(): DisplayMode {
@@ -176,47 +178,45 @@ function Modals(blueprint: Blueprint) {
176178
return <StartErrorModal />;
177179
} else if (currentModal === modalSlugs.IMPORT_FORM) {
178180
return <ImportFormModal />;
181+
} else if (currentModal === modalSlugs.GITHUB_IMPORT) {
182+
return <GithubImportModal
183+
onImported={({
184+
url,
185+
path,
186+
files,
187+
pluginOrThemeName,
188+
contentType,
189+
urlInformation: { owner, repo, type, pr },
190+
}) => {
191+
setGithubExportValues({
192+
repoUrl: url,
193+
prNumber: pr?.toString(),
194+
toPathInRepo: path,
195+
prAction: pr ? 'update' : 'create',
196+
contentType,
197+
plugin: pluginOrThemeName,
198+
theme: pluginOrThemeName,
199+
});
200+
setGithubExportFiles(files);
201+
}}
202+
/>;
203+
} else if (currentModal === modalSlugs.GITHUB_EXPORT) {
204+
return <GithubExportModal
205+
allowZipExport={
206+
(query.get('ghexport-allow-include-zip') ?? 'yes') === 'yes'
207+
}
208+
initialValues={githubExportValues}
209+
initialFilesBeforeChanges={githubExportFiles}
210+
onExported={(prUrl, formValues) => {
211+
setGithubExportValues(formValues);
212+
setGithubExportFiles(undefined);
213+
}}
214+
/>;
179215
}
180216

181-
return (
182-
<>
183-
{query.get('gh-ensure-auth') === 'yes' ? (
184-
<GitHubOAuthGuardModal />
185-
) : (
186-
''
187-
)}
188-
<GithubImportModal
189-
onImported={({
190-
url,
191-
path,
192-
files,
193-
pluginOrThemeName,
194-
contentType,
195-
urlInformation: { owner, repo, type, pr },
196-
}) => {
197-
setGithubExportValues({
198-
repoUrl: url,
199-
prNumber: pr?.toString(),
200-
toPathInRepo: path,
201-
prAction: pr ? 'update' : 'create',
202-
contentType,
203-
plugin: pluginOrThemeName,
204-
theme: pluginOrThemeName,
205-
});
206-
setGithubExportFiles(files);
207-
}}
208-
/>
209-
<GithubExportModal
210-
allowZipExport={
211-
(query.get('ghexport-allow-include-zip') ?? 'yes') === 'yes'
212-
}
213-
initialValues={githubExportValues}
214-
initialFilesBeforeChanges={githubExportFiles}
215-
onExported={(prUrl, formValues) => {
216-
setGithubExportValues(formValues);
217-
setGithubExportFiles(undefined);
218-
}}
219-
/>
220-
</>
221-
);
217+
if (query.get('gh-ensure-auth') === 'yes') {
218+
return <GitHubOAuthGuardModal />;
219+
}
220+
221+
return;
222222
}

packages/playground/website/src/components/toolbar-buttons/github-export-menu-item.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import { MenuItem } from '@wordpress/components';
2-
import { openModal } from '../../github/github-export-form/modal';
2+
import { setActiveModal } from '../../lib/state/redux/slice-ui';
3+
import { PlaygroundDispatch } from '../../lib/state/redux/store';
4+
import { useDispatch } from 'react-redux';
5+
import { modalSlugs } from '../layout';
36

47
interface Props {
58
onClose: () => void;
69
disabled?: boolean;
710
}
811
export function GithubExportMenuItem({ onClose, disabled }: Props) {
12+
const dispatch: PlaygroundDispatch = useDispatch();
913
return (
1014
<MenuItem
1115
aria-label="Export WordPress theme, plugin, or wp-content directory to a GitHub repository as a Pull Request."
1216
disabled={disabled}
1317
onClick={() => {
14-
openModal();
18+
dispatch(setActiveModal(modalSlugs.GITHUB_EXPORT));
1519
onClose();
1620
}}
1721
>

packages/playground/website/src/components/toolbar-buttons/github-import-menu-item.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import { MenuItem } from '@wordpress/components';
2-
import { openModal } from '../../github/github-import-form/modal';
2+
import { setActiveModal } from '../../lib/state/redux/slice-ui';
3+
import { PlaygroundDispatch } from '../../lib/state/redux/store';
4+
import { useDispatch } from 'react-redux';
5+
import { modalSlugs } from '../layout';
36

47
interface Props {
58
onClose: () => void;
69
disabled?: boolean;
710
}
811
export function GithubImportMenuItem({ onClose, disabled }: Props) {
12+
const dispatch: PlaygroundDispatch = useDispatch();
913
return (
1014
<MenuItem
1115
aria-label="Import WordPress theme, plugin, or wp-content directory from a GitHub repository."
1216
disabled={disabled}
1317
onClick={() => {
14-
openModal();
18+
dispatch(setActiveModal(modalSlugs.GITHUB_IMPORT));
1519
onClose();
1620
}}
1721
>

packages/playground/website/src/github/github-export-form/modal.tsx

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,43 @@
1-
import { signal } from '@preact/signals-react';
2-
31
import Modal, { defaultStyles } from '../../components/modal';
42
import GitHubExportForm, { GitHubExportFormProps } from './form';
53
import { usePlaygroundClient } from '../../lib/use-playground-client';
6-
7-
const query = new URLSearchParams(window.location.search);
8-
export const isGitHubExportModalOpen = signal(
9-
query.get('state') === 'github-export'
10-
);
4+
import { PlaygroundDispatch } from '../../lib/state/redux/store';
5+
import { useDispatch } from 'react-redux';
6+
import { setActiveModal } from '../../lib/state/redux/slice-ui';
7+
import { useEffect } from 'react';
118

129
interface GithubExportModalProps {
1310
allowZipExport: GitHubExportFormProps['allowZipExport'];
1411
onExported?: GitHubExportFormProps['onExported'];
1512
initialFilesBeforeChanges?: GitHubExportFormProps['initialFilesBeforeChanges'];
1613
initialValues?: GitHubExportFormProps['initialValues'];
1714
}
18-
export function closeModal() {
19-
isGitHubExportModalOpen.value = false;
20-
// Remove ?state=github-export from the URL.
21-
const url = new URL(window.location.href);
22-
url.searchParams.delete('state');
23-
window.history.replaceState({}, '', url.href);
24-
}
25-
export function openModal() {
26-
isGitHubExportModalOpen.value = true;
27-
// Add a ?state=github-export to the URL so that the user can refresh the page
28-
// and still see the modal.
29-
const url = new URL(window.location.href);
30-
url.searchParams.set('state', 'github-export');
31-
window.history.replaceState({}, '', url.href);
32-
}
3315
export function GithubExportModal({
3416
onExported,
3517
allowZipExport,
3618
initialValues,
3719
initialFilesBeforeChanges,
3820
}: GithubExportModalProps) {
21+
const dispatch: PlaygroundDispatch = useDispatch();
3922
const playground = usePlaygroundClient();
23+
24+
useEffect(() => {
25+
const url = new URL(window.location.href);
26+
url.searchParams.set('modal', 'github-export');
27+
window.history.replaceState({}, '', url.href);
28+
}, []);
29+
30+
const closeModal = () => {
31+
dispatch(setActiveModal(null));
32+
}
33+
4034
return (
4135
<Modal
4236
style={{
4337
...defaultStyles,
4438
content: { ...defaultStyles.content, width: 600 },
4539
}}
46-
isOpen={isGitHubExportModalOpen.value}
40+
isOpen
4741
onRequestClose={closeModal}
4842
>
4943
<GitHubExportForm

packages/playground/website/src/github/github-import-form/modal.tsx

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,35 @@
1-
import { signal } from '@preact/signals-react';
21
import Modal, { defaultStyles } from '../../components/modal';
32
import GitHubImportForm, { GitHubImportFormProps } from './form';
43
import { usePlaygroundClient } from '../../lib/use-playground-client';
5-
6-
const query = new URLSearchParams(window.location.search);
7-
export const isGitHubModalOpen = signal(query.get('state') === 'github-import');
4+
import { setActiveModal } from '../../lib/state/redux/slice-ui';
5+
import { PlaygroundDispatch } from '../../lib/state/redux/store';
6+
import { useDispatch } from 'react-redux';
7+
import { useEffect } from 'react';
88

99
interface GithubImportModalProps {
10+
defaultOpen?: boolean;
1011
onImported?: GitHubImportFormProps['onImported'];
1112
}
12-
export function closeModal() {
13-
isGitHubModalOpen.value = false;
14-
// Remove ?state=github-import from the URL.
15-
const url = new URL(window.location.href);
16-
url.searchParams.delete('state');
17-
window.history.replaceState({}, '', url.href);
18-
}
19-
export function openModal() {
20-
isGitHubModalOpen.value = true;
21-
// Add a ?state=github-import to the URL so that the user can refresh the page
22-
// and still see the modal.
23-
const url = new URL(window.location.href);
24-
url.searchParams.set('state', 'github-import');
25-
window.history.replaceState({}, '', url.href);
26-
}
27-
export function GithubImportModal({ onImported }: GithubImportModalProps) {
13+
export function GithubImportModal({ defaultOpen, onImported }: GithubImportModalProps) {
14+
const dispatch: PlaygroundDispatch = useDispatch();
2815
const playground = usePlaygroundClient();
16+
17+
useEffect(() => {
18+
const url = new URL(window.location.href);
19+
url.searchParams.set('modal', 'github-import');
20+
window.history.replaceState({}, '', url.href);
21+
}, []);
22+
23+
const closeModal = () => {
24+
dispatch(setActiveModal(null));
25+
}
2926
return (
3027
<Modal
3128
style={{
3229
...defaultStyles,
3330
content: { ...defaultStyles.content, width: 600 },
3431
}}
35-
isOpen={isGitHubModalOpen.value}
32+
isOpen
3633
onRequestClose={closeModal}
3734
>
3835
<GitHubImportForm
@@ -44,8 +41,8 @@ export function GithubImportModal({ onImported }: GithubImportModalProps) {
4441
alert(
4542
'Import finished! Your Playground site has been updated.'
4643
);
47-
closeModal();
4844
onImported?.(details);
45+
closeModal();
4946
}}
5047
/>
5148
</Modal>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { useEffect, useRef } from "react";
2+
3+
export const usePrevious = <T>(value: T): T | undefined => {
4+
const ref = useRef<T>();
5+
useEffect(() => {
6+
ref.current = value;
7+
});
8+
return ref.current;
9+
};

packages/playground/website/src/lib/state/redux/slice-ui.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@ const isMobile = window.innerWidth < 875;
2828
const shouldOpenSiteManagerByDefault = false;
2929

3030
const initialState: UIState = {
31-
activeModal:
32-
query.get('modal') === 'mount-markdown-directory'
33-
? 'mount-markdown-directory'
34-
: null,
31+
activeModal: query.get('modal') || null,
3532
offline: !navigator.onLine,
3633
// NOTE: Please do not eliminate the cases in this siteManagerIsOpen expression,
3734
// even if they seem redundant. We may experiment which toggling the manager
@@ -67,6 +64,12 @@ const uiSlice = createSlice({
6764
}
6865
},
6966
setActiveModal: (state, action: PayloadAction<string | null>) => {
67+
if (action.payload === null) {
68+
const url = new URL(window.location.href);
69+
url.searchParams.delete('modal');
70+
window.history.replaceState({}, '', url.href);
71+
}
72+
7073
state.activeModal = action.payload;
7174
},
7275
setOffline: (state, action: PayloadAction<boolean>) => {

0 commit comments

Comments
 (0)