From 00596193add8d5b5c2eadeb8d87147502db09b70 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Tue, 18 Feb 2025 12:37:51 +0700 Subject: [PATCH 1/2] refactor: decouple system store from variables We are moving away from binding system to variables and instead gonna have one global variable. Here moved system values into separate store coupled to pages instead of variables. Also fixed suggestions in address bar, probably broke while design system refactorings. --- .../builder/features/address-bar.stories.tsx | 45 ++------ .../app/builder/features/address-bar.tsx | 46 ++------ .../command-panel/command-panel.stories.tsx | 7 +- .../features/pages/page-settings.stories.tsx | 6 +- .../builder/features/pages/page-utils.test.ts | 8 +- .../project-settings.stories.tsx | 1 - .../features/project-settings/utils.test.ts | 3 - .../props-section/props-section.stories.tsx | 7 +- .../variables-section.stories.tsx | 4 +- .../background-content.stories.tsx | 1 - .../position/inset-control.stories.tsx | 1 - .../transitions/transitions.stories.tsx | 1 - .../app/builder/shared/commands.test.tsx | 2 +- .../text-editor/text-editor.stories.tsx | 2 - apps/builder/app/canvas/interceptor.ts | 8 +- apps/builder/app/shared/copy-paste.test.tsx | 2 +- .../shared/copy-paste/plugin-instance.test.ts | 1 - .../app/shared/instance-utils.test.tsx | 5 +- .../app/shared/nano-states/props.test.tsx | 9 +- apps/builder/app/shared/nano-states/props.ts | 40 +++---- .../app/shared/nano-states/variables.ts | 76 +------------ apps/builder/app/shared/pages/index.ts | 1 - apps/builder/app/shared/pages/pages.ts | 22 ---- apps/builder/app/shared/sync/sync-stores.ts | 4 +- .../{pages/pages.test.ts => system.test.ts} | 39 +++++-- apps/builder/app/shared/system.ts | 101 ++++++++++++++++++ 26 files changed, 181 insertions(+), 261 deletions(-) delete mode 100644 apps/builder/app/shared/pages/pages.ts rename apps/builder/app/shared/{pages/pages.test.ts => system.test.ts} (54%) create mode 100644 apps/builder/app/shared/system.ts diff --git a/apps/builder/app/builder/features/address-bar.stories.tsx b/apps/builder/app/builder/features/address-bar.stories.tsx index 601bd84cef29..0785fb9a4019 100644 --- a/apps/builder/app/builder/features/address-bar.stories.tsx +++ b/apps/builder/app/builder/features/address-bar.stories.tsx @@ -1,31 +1,15 @@ -import { computed } from "nanostores"; import { useStore } from "@nanostores/react"; import type { Meta, StoryFn } from "@storybook/react"; import { Box, Text, theme } from "@webstudio-is/design-system"; import { AddressBarPopover } from "./address-bar"; -import { - $dataSourceVariables, - $dataSources, - $pages, -} from "~/shared/nano-states"; +import { $dataSources, $pages } from "~/shared/nano-states"; import { registerContainers } from "~/shared/sync"; import { $awareness, $selectedPage } from "~/shared/awareness"; +import { $currentSystem } from "~/shared/system"; registerContainers(); -$dataSources.set( - new Map([ - [ - "systemId", - { - id: "systemId", - scopeInstanceId: "rootInstanceId", - name: "system", - type: "parameter", - }, - ], - ]) -); +$dataSources.set(new Map()); $pages.set({ folders: [ @@ -43,7 +27,6 @@ $pages.set({ title: "", meta: {}, rootInstanceId: "", - systemDataSourceId: "", }, pages: [ { @@ -53,23 +36,12 @@ $pages.set({ title: "", meta: {}, rootInstanceId: "rootInstanceId", - systemDataSourceId: "systemId", }, ], }); -const $selectedPageSystem = computed( - [$selectedPage, $dataSourceVariables], - (selectedPage, dataSourceVariables) => { - if (selectedPage?.systemDataSourceId === undefined) { - return {}; - } - return dataSourceVariables.get(selectedPage.systemDataSourceId); - } -); - const SystemInspect = () => { - const system = useStore($selectedPageSystem); + const system = useStore($currentSystem); return ( {JSON.stringify(system, null, 2)} @@ -77,16 +49,11 @@ const SystemInspect = () => { ); }; -const $selectedPageHistory = computed( - $selectedPage, - (page) => page?.history ?? [] -); - const HistoryInspect = () => { - const history = useStore($selectedPageHistory); + const page = useStore($selectedPage); return ( - {JSON.stringify(history, null, 2)} + {JSON.stringify(page?.history, null, 2)} ); }; diff --git a/apps/builder/app/builder/features/address-bar.tsx b/apps/builder/app/builder/features/address-bar.tsx index f186ff232a24..f34c50838397 100644 --- a/apps/builder/app/builder/features/address-bar.tsx +++ b/apps/builder/app/builder/features/address-bar.tsx @@ -31,23 +31,16 @@ import { findParentFolderByChildId, ROOT_FOLDER_ID, getPagePath, - type System, } from "@webstudio-is/sdk"; -import { - $dataSourceVariables, - $pages, - $publishedOrigin, - $selectedPageDefaultSystem, - updateSystem, -} from "~/shared/nano-states"; +import { $pages, $publishedOrigin } from "~/shared/nano-states"; import { compilePathnamePattern, isPathnamePattern, matchPathnamePattern, tokenizePathnamePattern, } from "~/builder/shared/url-pattern"; -import { savePathInHistory } from "~/shared/pages"; import { $selectedPage } from "~/shared/awareness"; +import { $currentSystem, updateCurrentSystem } from "~/shared/system"; const $selectedPagePath = computed([$selectedPage, $pages], (page, pages) => { if (pages === undefined || page === undefined) { @@ -62,19 +55,6 @@ const $selectedPagePath = computed([$selectedPage, $pages], (page, pages) => { .replace(/\/+/g, "/"); }); -const $selectedPagePathParams = computed( - [$selectedPageDefaultSystem, $selectedPage, $dataSourceVariables], - (defaultSystem, selectedPage, dataSourceVariables) => { - if (selectedPage?.systemDataSourceId === undefined) { - return defaultSystem.params; - } - const system = dataSourceVariables.get(selectedPage.systemDataSourceId) as - | undefined - | System; - return system?.params ?? defaultSystem.params; - } -); - const $selectedPageHistory = computed( $selectedPage, (page) => page?.history ?? [] @@ -278,7 +258,7 @@ const AddressBar = forwardRef< return history.filter((item) => matchPathnamePattern(path, item)); }, [history, path]); const [pathParams, setPathParams] = useState( - () => $selectedPagePathParams.get() ?? {} + () => $currentSystem.get().params ); const tokens = tokenizePathnamePattern(path); const compiledPath = compilePathnamePattern(tokens, pathParams); @@ -307,26 +287,11 @@ const AddressBar = forwardRef< return (
{ event.preventDefault(); const formData = new FormData(event.currentTarget); - const path = $selectedPagePath.get(); - const tokens = tokenizePathnamePattern(path); - // delete stale fields - const newParams: Record = {}; - for (const token of tokens) { - if (token.type === "param") { - newParams[token.name] = String(formData.get(token.name) ?? ""); - } - } - const page = $selectedPage.get(); - if (page === undefined) { - return; - } - updateSystem(page, { params: newParams }); - const compiledPath = compilePathnamePattern(tokens, newParams); - savePathInHistory(page.id, compiledPath); + const params = Object.fromEntries(formData) as Record; + updateCurrentSystem({ params }); if (errors.size === 0) { onSubmit(); } @@ -357,6 +322,7 @@ const AddressBar = forwardRef< key={index} name={token.name} fieldSizing="content" + autoComplete="off" css={{ minWidth: theme.spacing[15] }} color={errors.has(token.name) ? "error" : undefined} placeholder={token.name} diff --git a/apps/builder/app/builder/features/command-panel/command-panel.stories.tsx b/apps/builder/app/builder/features/command-panel/command-panel.stories.tsx index b8192f152cc0..ed2962997279 100644 --- a/apps/builder/app/builder/features/command-panel/command-panel.stories.tsx +++ b/apps/builder/app/builder/features/command-panel/command-panel.stories.tsx @@ -40,16 +40,12 @@ $breakpoints.set( ) ); -const pages = createDefaultPages({ - rootInstanceId: "", - systemDataSourceId: "", -}); +const pages = createDefaultPages({ rootInstanceId: "" }); pages.pages.push({ id: "page2", path: "", name: "Second Page", rootInstanceId: "", - systemDataSourceId: "", title: "", meta: {}, }); @@ -58,7 +54,6 @@ pages.pages.push({ path: "", name: "Thrid Page", rootInstanceId: "", - systemDataSourceId: "", title: "", meta: {}, }); diff --git a/apps/builder/app/builder/features/pages/page-settings.stories.tsx b/apps/builder/app/builder/features/pages/page-settings.stories.tsx index 279f07c4e014..68ea06950edd 100644 --- a/apps/builder/app/builder/features/pages/page-settings.stories.tsx +++ b/apps/builder/app/builder/features/pages/page-settings.stories.tsx @@ -37,10 +37,7 @@ $assets.set( ]) ); -const pages = createDefaultPages({ - rootInstanceId: "root-instance-id", - systemDataSourceId: "systemDataSourceId", -}); +const pages = createDefaultPages({ rootInstanceId: "root-instance-id" }); pages.meta = { siteName: "Project name", faviconAssetId: "imageId", @@ -53,7 +50,6 @@ pages.pages.push({ name: "page-name", meta: {}, rootInstanceId: "root-instance-id", - systemDataSourceId: "systemDataSourceId", }); const rootFolder = pages.folders.find(isRootFolder); rootFolder?.children.push("pageId"); diff --git a/apps/builder/app/builder/features/pages/page-utils.test.ts b/apps/builder/app/builder/features/pages/page-utils.test.ts index a47ae630e1e0..881fc48ca40c 100644 --- a/apps/builder/app/builder/features/pages/page-utils.test.ts +++ b/apps/builder/app/builder/features/pages/page-utils.test.ts @@ -26,6 +26,7 @@ import { } from "~/shared/nano-states"; import { registerContainers } from "~/shared/sync"; import { $awareness } from "~/shared/awareness"; +import { updateCurrentSystem } from "~/shared/system"; setEnv("*"); registerContainers(); @@ -525,10 +526,9 @@ test("page root scope should prefill default system variable value", () => { ], ]), }); - - $dataSourceVariables.set( - new Map([["systemId", { params: { slug: "my-post" }, search: {} }]]) - ); + updateCurrentSystem({ + params: { slug: "my-post" }, + }); expect($pageRootScope.get()).toEqual({ aliases: new Map([["$ws$dataSource$systemId", "system"]]), scope: { diff --git a/apps/builder/app/builder/features/project-settings/project-settings.stories.tsx b/apps/builder/app/builder/features/project-settings/project-settings.stories.tsx index a0587092ff8d..d6a6e3fa58a9 100644 --- a/apps/builder/app/builder/features/project-settings/project-settings.stories.tsx +++ b/apps/builder/app/builder/features/project-settings/project-settings.stories.tsx @@ -33,7 +33,6 @@ export const Redirects = () => { title: `"My Title"`, meta: {}, rootInstanceId: "body", - systemDataSourceId: "", }, pages: [], folders: [], diff --git a/apps/builder/app/builder/features/project-settings/utils.test.ts b/apps/builder/app/builder/features/project-settings/utils.test.ts index 192e442d5071..ffde0c340401 100644 --- a/apps/builder/app/builder/features/project-settings/utils.test.ts +++ b/apps/builder/app/builder/features/project-settings/utils.test.ts @@ -6,7 +6,6 @@ describe("getExistingRoutePaths", () => { test("gets all the route paths that exists in the project", () => { const pages = createDefaultPages({ rootInstanceId: "rootInstanceId", - systemDataSourceId: "systemDataSourceId", homePageId: "homePageId", }); @@ -16,7 +15,6 @@ describe("getExistingRoutePaths", () => { name: "Page", path: "/page", rootInstanceId: "rootInstanceId", - systemDataSourceId: "systemDataSourceId", title: `"Page"`, }); @@ -26,7 +24,6 @@ describe("getExistingRoutePaths", () => { name: "Blog", path: "/blog/:id", rootInstanceId: "rootInstanceId", - systemDataSourceId: "systemDataSourceId", title: `"Blog"`, }); diff --git a/apps/builder/app/builder/features/settings-panel/props-section/props-section.stories.tsx b/apps/builder/app/builder/features/settings-panel/props-section/props-section.stories.tsx index 4aa40ac50000..0fcde03c55df 100644 --- a/apps/builder/app/builder/features/settings-panel/props-section/props-section.stories.tsx +++ b/apps/builder/app/builder/features/settings-panel/props-section/props-section.stories.tsx @@ -26,15 +26,10 @@ const page = (name: string, path: string): Page => ({ path, meta: {}, rootInstanceId: unique(), - systemDataSourceId: unique(), }); $pages.set({ - ...createDefaultPages({ - rootInstanceId: unique(), - systemDataSourceId: unique(), - }), - + ...createDefaultPages({ rootInstanceId: unique() }), homePage: page("Home", "") as Page & { path: "" }, pages: [ page("About", "/about"), diff --git a/apps/builder/app/builder/features/settings-panel/variables-section.stories.tsx b/apps/builder/app/builder/features/settings-panel/variables-section.stories.tsx index 640baf8acef9..d5d5a1e04025 100644 --- a/apps/builder/app/builder/features/settings-panel/variables-section.stories.tsx +++ b/apps/builder/app/builder/features/settings-panel/variables-section.stories.tsx @@ -22,9 +22,7 @@ $instances.set( ["box", { id: "box", type: "instance", component: "Box", children: [] }], ]) ); -$pages.set( - createDefaultPages({ rootInstanceId: "box", systemDataSourceId: "system" }) -); +$pages.set(createDefaultPages({ rootInstanceId: "box" })); $awareness.set({ pageId: "home", instanceSelector: ["box"] }); export const VariablesSection: StoryObj = { diff --git a/apps/builder/app/builder/features/style-panel/sections/backgrounds/background-content.stories.tsx b/apps/builder/app/builder/features/style-panel/sections/backgrounds/background-content.stories.tsx index ae5663d322c0..d3cdeaffb99f 100644 --- a/apps/builder/app/builder/features/style-panel/sections/backgrounds/background-content.stories.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/backgrounds/background-content.stories.tsx @@ -37,7 +37,6 @@ $pages.set( createDefaultPages({ homePageId: "homePageId", rootInstanceId: "box", - systemDataSourceId: "systemId", }) ); $awareness.set({ diff --git a/apps/builder/app/builder/features/style-panel/sections/position/inset-control.stories.tsx b/apps/builder/app/builder/features/style-panel/sections/position/inset-control.stories.tsx index 08dcbce32738..b7a0157823ee 100644 --- a/apps/builder/app/builder/features/style-panel/sections/position/inset-control.stories.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/position/inset-control.stories.tsx @@ -47,7 +47,6 @@ $pages.set( createDefaultPages({ homePageId: "homePageId", rootInstanceId: "box", - systemDataSourceId: "systemId", }) ); $awareness.set({ diff --git a/apps/builder/app/builder/features/style-panel/sections/transitions/transitions.stories.tsx b/apps/builder/app/builder/features/style-panel/sections/transitions/transitions.stories.tsx index 86adc70e5d72..bb6e5d7ea9ce 100644 --- a/apps/builder/app/builder/features/style-panel/sections/transitions/transitions.stories.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/transitions/transitions.stories.tsx @@ -56,7 +56,6 @@ $pages.set( createDefaultPages({ homePageId: "homePageId", rootInstanceId: "box", - systemDataSourceId: "systemId", }) ); $awareness.set({ diff --git a/apps/builder/app/builder/shared/commands.test.tsx b/apps/builder/app/builder/shared/commands.test.tsx index dee00a876f83..fdb601764f4e 100644 --- a/apps/builder/app/builder/shared/commands.test.tsx +++ b/apps/builder/app/builder/shared/commands.test.tsx @@ -15,7 +15,7 @@ registerContainers(); const metas = new Map(Object.entries(baseMetas)); $registeredComponentMetas.set(metas); -$pages.set(createDefaultPages({ rootInstanceId: "", systemDataSourceId: "" })); +$pages.set(createDefaultPages({ rootInstanceId: "" })); $awareness.set({ pageId: "" }); describe("deleteInstance", () => { diff --git a/apps/builder/app/canvas/features/text-editor/text-editor.stories.tsx b/apps/builder/app/canvas/features/text-editor/text-editor.stories.tsx index d2e058cdd662..605b692f09f4 100644 --- a/apps/builder/app/canvas/features/text-editor/text-editor.stories.tsx +++ b/apps/builder/app/canvas/features/text-editor/text-editor.stories.tsx @@ -223,7 +223,6 @@ export const CursorPositioningUpDown: StoryFn = () => { path: "", title: "", name: "", - systemDataSourceId: "", }, pages: [ { @@ -232,7 +231,6 @@ export const CursorPositioningUpDown: StoryFn = () => { path: "", title: "", name: "", - systemDataSourceId: "", meta: {}, }, ], diff --git a/apps/builder/app/canvas/interceptor.ts b/apps/builder/app/canvas/interceptor.ts index 9fd2ff2ed3a2..a4b6bf3a04ff 100644 --- a/apps/builder/app/canvas/interceptor.ts +++ b/apps/builder/app/canvas/interceptor.ts @@ -10,9 +10,8 @@ import { $isPreviewMode, $pages, $selectedPageHash, - updateSystem, } from "~/shared/nano-states"; -import { savePathInHistory } from "~/shared/pages"; +import { updateCurrentSystem } from "~/shared/system"; const isAbsoluteUrl = (href: string) => { try { @@ -62,10 +61,9 @@ const switchPageAndUpdateSystem = (href: string, formData?: FormData) => { const search = Object.fromEntries(pageHref.searchParams); $selectedPageHash.set({ hash: pageHref.hash }); selectPage(page.id); - updateSystem(page, { params, search }); - savePathInHistory(page.id, pageHref.pathname); - break; + updateCurrentSystem({ params, search }); } + break; } }; diff --git a/apps/builder/app/shared/copy-paste.test.tsx b/apps/builder/app/shared/copy-paste.test.tsx index 8a4a75d27eb8..2e4f16af18db 100644 --- a/apps/builder/app/shared/copy-paste.test.tsx +++ b/apps/builder/app/shared/copy-paste.test.tsx @@ -33,7 +33,7 @@ $project.set({ id: "current_project" } as Project); const createStub = (element: JSX.Element) => { const project = { - pages: createDefaultPages({ rootInstanceId: "", systemDataSourceId: "" }), + pages: createDefaultPages({ rootInstanceId: "" }), ...renderData(element), }; // global root instance is never stored in data diff --git a/apps/builder/app/shared/copy-paste/plugin-instance.test.ts b/apps/builder/app/shared/copy-paste/plugin-instance.test.ts index 446027f13cb1..65d91fd7daa7 100644 --- a/apps/builder/app/shared/copy-paste/plugin-instance.test.ts +++ b/apps/builder/app/shared/copy-paste/plugin-instance.test.ts @@ -42,7 +42,6 @@ $pages.set( createDefaultPages({ homePageId: "home-page", rootInstanceId: "body0", - systemDataSourceId: "", }) ); $awareness.set({ pageId: "home-page" }); diff --git a/apps/builder/app/shared/instance-utils.test.tsx b/apps/builder/app/shared/instance-utils.test.tsx index 4e4bb74ce176..0070bb9a35ee 100644 --- a/apps/builder/app/shared/instance-utils.test.tsx +++ b/apps/builder/app/shared/instance-utils.test.tsx @@ -62,7 +62,7 @@ import { $awareness, getInstancePath, selectInstance } from "./awareness"; enableMapSet(); registerContainers(); -$pages.set(createDefaultPages({ rootInstanceId: "", systemDataSourceId: "" })); +$pages.set(createDefaultPages({ rootInstanceId: "" })); const defaultMetasMap = new Map( Object.entries({ ...defaultMetas, ...coreMetas }) @@ -713,7 +713,7 @@ describe("reparent instance", () => { const getWebstudioDataStub = ( data?: Partial ): WebstudioData => ({ - pages: createDefaultPages({ rootInstanceId: "", systemDataSourceId: "" }), + pages: createDefaultPages({ rootInstanceId: "" }), assets: new Map(), dataSources: new Map(), resources: new Map(), @@ -1305,7 +1305,6 @@ describe("find closest insertable", () => { createDefaultPages({ homePageId: "homePageId", rootInstanceId: "", - systemDataSourceId: "", }) ); $awareness.set({ diff --git a/apps/builder/app/shared/nano-states/props.test.tsx b/apps/builder/app/shared/nano-states/props.test.tsx index 1d46279e4121..f20485ef0b45 100644 --- a/apps/builder/app/shared/nano-states/props.test.tsx +++ b/apps/builder/app/shared/nano-states/props.test.tsx @@ -27,7 +27,10 @@ import { Variable, ws, } from "@webstudio-is/template"; +import { updateCurrentSystem } from "../system"; +import { registerContainers } from "../sync"; +registerContainers(); setEnv("*"); const getIdValuePair = (item: T) => @@ -899,9 +902,9 @@ test("prefill default system variable value", () => { ], ]) ); - $dataSourceVariables.set( - new Map([[systemId, { params: { slug: "my-post" }, search: {} }]]) - ); + updateCurrentSystem({ + params: { slug: "my-post" }, + }); expect($variableValuesByInstanceSelector.get()).toEqual( new Map([ [JSON.stringify([ROOT_INSTANCE_ID]), new Map()], diff --git a/apps/builder/app/shared/nano-states/props.ts b/apps/builder/app/shared/nano-states/props.ts index 221af295a4ac..a7dbff763b4f 100644 --- a/apps/builder/app/shared/nano-states/props.ts +++ b/apps/builder/app/shared/nano-states/props.ts @@ -5,7 +5,6 @@ import type { Instance, Prop, ResourceRequest, - System, ImageAsset, } from "@webstudio-is/sdk"; import { @@ -31,16 +30,12 @@ import { import { $pages } from "./pages"; import type { InstanceSelector } from "../tree-utils"; import { restResourcesLoader } from "../router-utils"; -import { - $dataSourceVariables, - $resourceValues, - $selectedPageDefaultSystem, - mergeSystem, -} from "./variables"; +import { $dataSourceVariables, $resourceValues } from "./variables"; import { uploadingFileDataToAsset } from "~/builder/shared/assets/asset-utils"; import { fetch } from "~/shared/fetch.client"; import { $selectedPage, getInstanceKey } from "../awareness"; import { computeExpression } from "../data-variables"; +import { $currentSystem, $currentSystemVariableId } from "../system"; export const assetBaseUrl = "/cgi/asset/"; @@ -136,9 +131,9 @@ const $unscopedVariableValues = computed( $dataSourceVariables, $resourceValues, $selectedPage, - $selectedPageDefaultSystem, + $currentSystem, ], - (dataSources, dataSourceVariables, resourceValues, page, defaultSystem) => { + (dataSources, dataSourceVariables, resourceValues, page, system) => { const values = new Map(); for (const [dataSourceId, dataSource] of dataSources) { if (dataSource.type === "variable") { @@ -149,8 +144,9 @@ const $unscopedVariableValues = computed( } if (dataSource.type === "parameter") { let value = dataSourceVariables.get(dataSourceId); + // @todo support global system if (dataSource.id === page?.systemDataSourceId) { - value = mergeSystem(defaultSystem, value as undefined | System); + value = system; } values.set(dataSourceId, value); } @@ -162,11 +158,6 @@ const $unscopedVariableValues = computed( } ); -const $selectedPageSystemId = computed( - $selectedPage, - (page) => page?.systemDataSourceId -); - /** * similar to above but should not depend on resource values * because these values are used to load resources @@ -177,10 +168,10 @@ const $loaderVariableValues = computed( [ $dataSources, $dataSourceVariables, - $selectedPageSystemId, - $selectedPageDefaultSystem, + $currentSystemVariableId, + $currentSystem, ], - (dataSources, dataSourceVariables, systemId, defaultSystem) => { + (dataSources, dataSourceVariables, systemVariableId, system) => { const values = new Map(); for (const [dataSourceId, dataSource] of dataSources) { if (dataSource.type === "variable") { @@ -191,8 +182,8 @@ const $loaderVariableValues = computed( } if (dataSource.type === "parameter") { let value = dataSourceVariables.get(dataSourceId); - if (dataSource.id === systemId) { - value = mergeSystem(defaultSystem, value as undefined | System); + if (dataSource.id === systemVariableId) { + value = system; } values.set(dataSourceId, value); } @@ -386,7 +377,7 @@ export const $variableValuesByInstanceSelector = computed( $dataSources, $dataSourceVariables, $resourceValues, - $selectedPageDefaultSystem, + $currentSystem, ], ( instances, @@ -395,7 +386,7 @@ export const $variableValuesByInstanceSelector = computed( dataSources, dataSourceVariables, resourceValues, - defaultSystem + system ) => { const propsByInstanceId = mapGroupBy( props.values(), @@ -443,10 +434,7 @@ export const $variableValuesByInstanceSelector = computed( const value = dataSourceVariables.get(variable.id); variableValues.set(variable.id, value); if (variable.id === page.systemDataSourceId) { - variableValues.set( - variable.id, - mergeSystem(defaultSystem, value as undefined | System) - ); + variableValues.set(variable.id, system); } } if (variable.type === "resource") { diff --git a/apps/builder/app/shared/nano-states/variables.ts b/apps/builder/app/shared/nano-states/variables.ts index d78477022189..2c6fc0e6dfb9 100644 --- a/apps/builder/app/shared/nano-states/variables.ts +++ b/apps/builder/app/shared/nano-states/variables.ts @@ -1,80 +1,8 @@ -import { atom, computed } from "nanostores"; -import type { DataSource, Page, Resource, System } from "@webstudio-is/sdk"; -import { - matchPathnamePattern, - tokenizePathnamePattern, -} from "~/builder/shared/url-pattern"; -import { $publishedOrigin } from "./misc"; -import { $selectedPage } from "../awareness"; +import { atom } from "nanostores"; +import type { DataSource, Resource } from "@webstudio-is/sdk"; export const $dataSourceVariables = atom>( new Map() ); export const $resourceValues = atom(new Map()); - -const $selectedPagePath = computed($selectedPage, (page) => page?.path); - -const $selectedPageHistory = computed($selectedPage, (page) => page?.history); - -export const $selectedPageDefaultSystem = computed( - [$publishedOrigin, $selectedPagePath, $selectedPageHistory], - (origin, path, history) => { - const defaultSystem: System = { - params: {}, - search: {}, - origin, - }; - if (path) { - const tokens = tokenizePathnamePattern(path); - // try to match the first item in history to let user - // see the page without manually entering params - // or selecting them in address bar - const matchedParams = history - ? matchPathnamePattern(path, history[0]) - : undefined; - for (const token of tokens) { - if (token.type === "param") { - defaultSystem.params[token.name] = - matchedParams?.[token.name] ?? undefined; - } - } - } - return defaultSystem; - } -); - -export const mergeSystem = (left: System, right?: System): System => { - return { - ...left, - ...right, - params: { - ...left.params, - ...right?.params, - }, - search: { - ...left.search, - ...right?.search, - }, - }; -}; - -export const updateSystem = (page: Page, update: Partial) => { - if (page.systemDataSourceId === undefined) { - return; - } - const dataSourceVariables = new Map($dataSourceVariables.get()); - const system = dataSourceVariables.get(page.systemDataSourceId) as - | undefined - | System; - - const newSystem: System = { - search: {}, - params: {}, - origin: $publishedOrigin.get(), - ...system, - ...update, - }; - dataSourceVariables.set(page.systemDataSourceId, newSystem); - $dataSourceVariables.set(dataSourceVariables); -}; diff --git a/apps/builder/app/shared/pages/index.ts b/apps/builder/app/shared/pages/index.ts index 2ccb47c60474..d270d13b9cf2 100644 --- a/apps/builder/app/shared/pages/index.ts +++ b/apps/builder/app/shared/pages/index.ts @@ -1,2 +1 @@ export * from "./use-switch-page"; -export * from "./pages"; diff --git a/apps/builder/app/shared/pages/pages.ts b/apps/builder/app/shared/pages/pages.ts deleted file mode 100644 index 4c7c6bef5a37..000000000000 --- a/apps/builder/app/shared/pages/pages.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { findPageByIdOrPath } from "@webstudio-is/sdk"; -import { $pages } from "../nano-states"; -import { serverSyncStore } from "../sync"; - -/** - * put new path into the beginning of history - * and drop paths in the end when exceeded 20 - */ -export const savePathInHistory = (pageId: string, path: string) => { - serverSyncStore.createTransaction([$pages], (pages) => { - if (pages === undefined) { - return; - } - const page = findPageByIdOrPath(pageId, pages); - if (page === undefined) { - return; - } - const history = Array.from(page.history ?? []); - history.unshift(path); - page.history = Array.from(new Set(history)).slice(0, 20); - }); -}; diff --git a/apps/builder/app/shared/sync/sync-stores.ts b/apps/builder/app/shared/sync/sync-stores.ts index 64cb63d5ae60..d111dcaae4b0 100644 --- a/apps/builder/app/shared/sync/sync-stores.ts +++ b/apps/builder/app/shared/sync/sync-stores.ts @@ -50,7 +50,6 @@ import { $modifierKeys, } from "~/shared/nano-states"; import { $ephemeralStyles } from "~/canvas/stores"; -import { $awareness, $temporaryInstances } from "../awareness"; import { ImmerhinSyncObject, NanostoresSyncObject, @@ -59,6 +58,8 @@ import { type SyncEmitter, } from "../sync-client"; import { $canvasScrollbarSize } from "~/builder/shared/nano-states"; +import { $awareness, $temporaryInstances } from "../awareness"; +import { $systemDataByPage } from "../system"; enableMapSet(); // safari structuredClone fix @@ -165,6 +166,7 @@ export const createObjectPool = () => { ), new NanostoresSyncObject("registeredTemplates", $registeredTemplates), new NanostoresSyncObject("canvasScrollbarWidth", $canvasScrollbarSize), + new NanostoresSyncObject("systemDataByPage", $systemDataByPage), ]); }; diff --git a/apps/builder/app/shared/pages/pages.test.ts b/apps/builder/app/shared/system.test.ts similarity index 54% rename from apps/builder/app/shared/pages/pages.test.ts rename to apps/builder/app/shared/system.test.ts index 6d1205435dc4..4fa4cdf30a1d 100644 --- a/apps/builder/app/shared/pages/pages.test.ts +++ b/apps/builder/app/shared/system.test.ts @@ -2,7 +2,8 @@ import { describe, expect, test } from "vitest"; import type { Page, Pages } from "@webstudio-is/sdk"; import { $pages } from "~/shared/nano-states"; import { registerContainers } from "~/shared/sync"; -import { savePathInHistory } from "./pages"; +import { updateCurrentSystem } from "./system"; +import { selectPage } from "./awareness"; registerContainers(); @@ -22,7 +23,6 @@ const getInitialPages = (page: Page): Pages => ({ title: "", meta: {}, rootInstanceId: "", - systemDataSourceId: "", }, pages: [page], }); @@ -37,13 +37,22 @@ describe("history", () => { title: "", meta: {}, rootInstanceId: "", - systemDataSourceId: "", }) ); - savePathInHistory("dynamicId", "/path1"); - expect($pages.get()?.pages[0].history).toEqual(["/path1"]); - savePathInHistory("dynamicId", "/path2"); - expect($pages.get()?.pages[0].history).toEqual(["/path2", "/path1"]); + selectPage("dynamicId"); + updateCurrentSystem({ + params: { date: "my-date", slug: "my-slug" }, + }); + expect($pages.get()?.pages[0].history).toEqual([ + "/blog/my-date/post/my-slug", + ]); + updateCurrentSystem({ + params: { date: "another-date", slug: "another-slug" }, + }); + expect($pages.get()?.pages[0].history).toEqual([ + "/blog/another-date/post/another-slug", + "/blog/my-date/post/my-slug", + ]); }); test("move existing path to the start", () => { @@ -55,11 +64,19 @@ describe("history", () => { title: "", meta: {}, rootInstanceId: "", - systemDataSourceId: "", - history: ["/path2", "/path1"], + history: [ + "/blog/another-date/post/another-slug", + "/blog/my-date/post/my-slug", + ], }) ); - savePathInHistory("dynamicId", "/path1"); - expect($pages.get()?.pages[0].history).toEqual(["/path1", "/path2"]); + selectPage("dynamicId"); + updateCurrentSystem({ + params: { date: "my-date", slug: "my-slug" }, + }); + expect($pages.get()?.pages[0].history).toEqual([ + "/blog/my-date/post/my-slug", + "/blog/another-date/post/another-slug", + ]); }); }); diff --git a/apps/builder/app/shared/system.ts b/apps/builder/app/shared/system.ts new file mode 100644 index 000000000000..46fa11c77292 --- /dev/null +++ b/apps/builder/app/shared/system.ts @@ -0,0 +1,101 @@ +import { atom, computed, onSet } from "nanostores"; +import { + findPageByIdOrPath, + type Page, + type System, + SYSTEM_VARIABLE_ID, +} from "@webstudio-is/sdk"; +import { + compilePathnamePattern, + matchPathnamePattern, + tokenizePathnamePattern, +} from "~/builder/shared/url-pattern"; +import { $selectedPage } from "./awareness"; +import { $pages, $publishedOrigin } from "./nano-states"; +import { serverSyncStore } from "./sync"; + +export const $currentSystemVariableId = computed( + $selectedPage, + // fallback to global system variable + (page) => page?.systemDataSourceId ?? SYSTEM_VARIABLE_ID +); + +export const $systemDataByPage = atom( + new Map>() +); + +const extractParams = (pattern: string, path?: string) => { + const params: System["params"] = {}; + const tokens = tokenizePathnamePattern(pattern); + // try to match the first item in history to let user + // see the page without manually entering params + // or selecting them in address bar + const matchedParams = path ? matchPathnamePattern(pattern, path) : undefined; + for (const token of tokens) { + if (token.type === "param") { + params[token.name] = matchedParams?.[token.name] ?? undefined; + } + } + return params; +}; + +export const $currentSystem = computed( + [$publishedOrigin, $selectedPage, $systemDataByPage], + (origin, page, systemByPage) => { + const system: System = { + search: {}, + params: {}, + origin, + }; + if (page === undefined) { + return system; + } + const systemData = systemByPage.get(page.id); + const extractedParams = extractParams(page.path, page.history?.[0]); + return { + search: { ...system.search, ...systemData?.search }, + params: { ...extractedParams, ...systemData?.params }, + origin, + }; + } +); + +const compilePath = (pattern: string, params: System["params"]) => { + const tokens = tokenizePathnamePattern(pattern); + return compilePathnamePattern(tokens, params); +}; + +/** + * put new path into the beginning of history + * and drop paths in the end when exceeded 20 + */ +const savePathInHistory = (pageId: string, path: string) => { + serverSyncStore.createTransaction([$pages], (pages) => { + if (pages === undefined) { + return; + } + const page = findPageByIdOrPath(pageId, pages); + if (page === undefined) { + return; + } + const history = Array.from(page.history ?? []); + history.unshift(path); + page.history = Array.from(new Set(history)).slice(0, 20); + }); +}; + +export const updateCurrentSystem = ( + update: Partial> +) => { + const page = $selectedPage.get(); + if (page === undefined) { + return; + } + const systemDataByPage = new Map($systemDataByPage.get()); + const systemData = systemDataByPage.get(page.id); + const search = update.search ?? systemData?.search ?? {}; + const params = update.params ?? systemData?.params ?? {}; + systemDataByPage.set(page.id, { search, params }); + $systemDataByPage.set(systemDataByPage); + savePathInHistory(page.id, compilePath(page.path, params)); +}; From 0e71452bd518b1083ff13278e7cf6d4b9eecfbeb Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Tue, 18 Feb 2025 12:48:37 +0700 Subject: [PATCH 2/2] Fix types --- apps/builder/app/shared/system.ts | 2 +- packages/project-build/src/shared/pages-utils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/builder/app/shared/system.ts b/apps/builder/app/shared/system.ts index 46fa11c77292..c7c38b1eeb8f 100644 --- a/apps/builder/app/shared/system.ts +++ b/apps/builder/app/shared/system.ts @@ -1,4 +1,4 @@ -import { atom, computed, onSet } from "nanostores"; +import { atom, computed } from "nanostores"; import { findPageByIdOrPath, type Page, diff --git a/packages/project-build/src/shared/pages-utils.ts b/packages/project-build/src/shared/pages-utils.ts index 4089047691aa..7b7f43f3bdf9 100644 --- a/packages/project-build/src/shared/pages-utils.ts +++ b/packages/project-build/src/shared/pages-utils.ts @@ -22,7 +22,7 @@ export const createDefaultPages = ({ homePageId = nanoid(), }: { rootInstanceId: Instance["id"]; - systemDataSourceId: DataSource["id"]; + systemDataSourceId?: DataSource["id"]; homePageId?: string; }): Pages => { // This is a root folder that nobody can delete or going to be able to see.