Files
webstudio/apps/builder/app/shared/pages/use-switch-page.ts
T
Bogdan Chadkin 1b0fc0eadc refactor: move builder data loading to client side (#3464)
To synchronize data between tabs it should be consistent and be easily
accessible. Loading from server creates huge latency and data can be
changed by loading from another tab.

Here moved data loading to client side so it can be shared through
IndexedDB.

Added new `/rest/data/:buildId` endpoint and defined global setter and
getter for current state.
2024-06-01 14:46:29 +03:00

145 lines
3.7 KiB
TypeScript

import { useEffect } from "react";
import { useStore } from "@nanostores/react";
import { useNavigate } from "@remix-run/react";
import { findPageByIdOrPath, type Page } from "@webstudio-is/sdk";
import {
$authToken,
$pages,
$project,
$selectedPage,
$selectedPageId,
$selectedPageHash,
$selectedInstanceSelector,
$isPreviewMode,
} from "~/shared/nano-states";
import { builderPath } from "~/shared/router-utils";
export const switchPage = (pageId: Page["id"], pageHash: string = "") => {
const pages = $pages.get();
if (pages === undefined) {
return;
}
const page = findPageByIdOrPath(pageId, pages);
if (page === undefined) {
return;
}
$selectedPageHash.set(pageHash);
$selectedPageId.set(page.id);
$selectedInstanceSelector.set([page.rootInstanceId]);
};
const setPageStateFromUrl = () => {
const searchParams = new URLSearchParams(window.location.search);
const pages = $pages.get();
if (pages === undefined) {
return;
}
const pageId = searchParams.get("pageId") ?? pages.homePage.id;
const pageHash = searchParams.get("pageHash") ?? undefined;
$isPreviewMode.set(searchParams.get("mode") === "preview");
switchPage(pageId, pageHash);
};
/**
* Sync
* - searchParams to atoms
* - initial loading
* - popstate
*
* - atoms to searchParams
* - on atom change
*/
export const useSyncPageUrl = () => {
const navigate = useNavigate();
const page = useStore($selectedPage);
const pageHash = useStore($selectedPageHash);
const isPreviewMode = useStore($isPreviewMode);
// Get pageId and pageHash from URL
// once pages are loaded
useEffect(() => {
const unsubscribe = $pages.subscribe((pages) => {
if (pages) {
unsubscribe();
setPageStateFromUrl();
}
});
return unsubscribe;
}, []);
useEffect(() => {
window.addEventListener("popstate", setPageStateFromUrl);
return () => {
window.removeEventListener("popstate", setPageStateFromUrl);
};
}, []);
useEffect(() => {
const project = $project.get();
const pages = $pages.get();
if (page === undefined || project === undefined || pages === undefined) {
return;
}
const searchParams = new URLSearchParams(window.location.search);
const searchParamsPageId = searchParams.get("pageId") ?? pages.homePage.id;
const searchParamsPageHash = searchParams.get("pageHash") ?? "";
const searchParamsIsPreviewMode = searchParams.get("mode") === "preview";
// Do not navigate on popstate change
if (
searchParamsPageId === page.id &&
searchParamsPageHash === pageHash &&
searchParamsIsPreviewMode === isPreviewMode
) {
return;
}
navigate(
builderPath({
projectId: project.id,
pageId: page.id === pages.homePage.id ? undefined : page.id,
authToken: $authToken.get(),
pageHash: pageHash === "" ? undefined : pageHash,
mode: isPreviewMode ? "preview" : undefined,
})
);
}, [isPreviewMode, navigate, page, pageHash]);
};
/**
* Synchronize pageHash with scrolling position
*/
export const useHashLinkSync = () => {
const pageHash = useStore($selectedPageHash);
useEffect(() => {
if (pageHash === "") {
// native browser behavior is to do nothing if hash is empty
// remix scroll to top, we emulate native
return;
}
let elementId = decodeURIComponent(pageHash);
if (elementId.startsWith("#")) {
elementId = elementId.slice(1);
}
// Try find element to scroll to
const element = document.getElementById(elementId);
if (element !== null) {
element.scrollIntoView();
}
// Remix scroll to top if element not found
// browser do nothing
}, [pageHash]);
};