diff --git a/apps/builder/app/builder/features/sidebar-left/panels/pages/page-utils.test.ts b/apps/builder/app/builder/features/sidebar-left/panels/pages/page-utils.test.ts index b136c2aa07..c8c88a9186 100644 --- a/apps/builder/app/builder/features/sidebar-left/panels/pages/page-utils.test.ts +++ b/apps/builder/app/builder/features/sidebar-left/panels/pages/page-utils.test.ts @@ -983,9 +983,22 @@ test("page root scope should prefill default system variable value", () => { expect($pageRootScope.get()).toEqual({ aliases: new Map([["$ws$dataSource$systemId", "system"]]), scope: { - $ws$dataSource$systemId: { params: {}, search: {} }, + $ws$dataSource$systemId: { + origin: "https://undefined.wstd.work", + params: {}, + search: {}, + }, }, - variableValues: new Map([["systemId", { params: {}, search: {} }]]), + variableValues: new Map([ + [ + "systemId", + { + params: {}, + search: {}, + origin: "https://undefined.wstd.work", + }, + ], + ]), }); $dataSourceVariables.set( @@ -994,10 +1007,21 @@ test("page root scope should prefill default system variable value", () => { expect($pageRootScope.get()).toEqual({ aliases: new Map([["$ws$dataSource$systemId", "system"]]), scope: { - $ws$dataSource$systemId: { params: { slug: "my-post" }, search: {} }, + $ws$dataSource$systemId: { + params: { slug: "my-post" }, + search: {}, + origin: "https://undefined.wstd.work", + }, }, variableValues: new Map([ - ["systemId", { params: { slug: "my-post" }, search: {} }], + [ + "systemId", + { + params: { slug: "my-post" }, + search: {}, + origin: "https://undefined.wstd.work", + }, + ], ]), }); }); diff --git a/apps/builder/app/builder/features/sidebar-left/panels/pages/page-utils.ts b/apps/builder/app/builder/features/sidebar-left/panels/pages/page-utils.ts index ba59745e88..8295f391a1 100644 --- a/apps/builder/app/builder/features/sidebar-left/panels/pages/page-utils.ts +++ b/apps/builder/app/builder/features/sidebar-left/panels/pages/page-utils.ts @@ -15,6 +15,7 @@ import { decodeDataSourceVariable, encodeDataSourceVariable, transpileExpression, + type System, } from "@webstudio-is/sdk"; import { removeByMutable } from "~/shared/array-utils"; import { @@ -27,9 +28,12 @@ import { $dataSourceVariables, $dataSources, $pages, + $publishedOrigin, $resourceValues, $selectedInstanceSelector, $selectedPageId, + getPageDefaultSystem, + mergeSystem, } from "~/shared/nano-states"; type TreePage = { @@ -345,9 +349,22 @@ const $editingPage = computed( } ); +const $editingPagePath = computed($editingPage, (page) => page?.path); + +const $editingPageDefaultSystem = computed( + [$publishedOrigin, $editingPagePath], + (origin, path) => getPageDefaultSystem({ origin, path }) +); + const $pageRootVariableValues = computed( - [$editingPage, $dataSources, $dataSourceVariables, $resourceValues], - (page, dataSources, dataSourceVariables, resourceValues) => { + [ + $editingPage, + $dataSources, + $dataSourceVariables, + $resourceValues, + $editingPageDefaultSystem, + ], + (page, dataSources, dataSourceVariables, resourceValues, defaultSystem) => { const variableValues = new Map(); if (page === undefined) { return variableValues; @@ -363,11 +380,11 @@ const $pageRootVariableValues = computed( if (variable.type === "parameter") { const value = dataSourceVariables.get(variable.id); variableValues.set(variable.id, value); - if (variable.id === page.systemDataSourceId && value === undefined) { - variableValues.set(variable.id, { - params: {}, - search: {}, - }); + if (variable.id === page.systemDataSourceId) { + variableValues.set( + variable.id, + mergeSystem(defaultSystem, value as undefined | System) + ); } } if (variable.type === "resource") { diff --git a/apps/builder/app/builder/shared/expression-editor.tsx b/apps/builder/app/builder/shared/expression-editor.tsx index 01f34d025f..f434fcab49 100644 --- a/apps/builder/app/builder/shared/expression-editor.tsx +++ b/apps/builder/app/builder/shared/expression-editor.tsx @@ -64,7 +64,7 @@ export const formatValuePreview = (value: unknown) => { return value.toString(); } if (value === undefined) { - return "undefined"; + return ""; } if (value === null) { return "null"; diff --git a/apps/builder/app/shared/nano-states/components.ts b/apps/builder/app/shared/nano-states/components.ts index 17a561531e..f47a852740 100644 --- a/apps/builder/app/shared/nano-states/components.ts +++ b/apps/builder/app/shared/nano-states/components.ts @@ -1,3 +1,4 @@ +import { shallowEqual } from "shallow-equal"; import type { ExoticComponent } from "react"; import { atom } from "nanostores"; import { @@ -12,10 +13,10 @@ import { import type { Instance } from "@webstudio-is/sdk"; import { decodeDataSourceVariable } from "@webstudio-is/sdk"; import type { InstanceSelector } from "../tree-utils"; -import { $dataSourceVariables, $dataSources, $props } from "./nano-states"; +import { $dataSources, $props } from "./nano-states"; import { $instances, $selectedInstanceSelector } from "./instances"; import { $selectedPage } from "./pages"; -import { shallowEqual } from "shallow-equal"; +import { $dataSourceVariables } from "./variables"; const createHookContext = (): HookContext => { const metas = $registeredComponentMetas.get(); diff --git a/apps/builder/app/shared/nano-states/index.ts b/apps/builder/app/shared/nano-states/index.ts index c863c4c25a..005656ab03 100644 --- a/apps/builder/app/shared/nano-states/index.ts +++ b/apps/builder/app/shared/nano-states/index.ts @@ -4,4 +4,5 @@ export * from "./instances"; export * from "./props"; export * from "./canvas"; export * from "./pages"; +export * from "./variables"; export * from "./components"; diff --git a/apps/builder/app/shared/nano-states/nano-states.ts b/apps/builder/app/shared/nano-states/nano-states.ts index 2005ade9c7..8262eaa1a4 100644 --- a/apps/builder/app/shared/nano-states/nano-states.ts +++ b/apps/builder/app/shared/nano-states/nano-states.ts @@ -6,10 +6,8 @@ import type { AuthPermit } from "@webstudio-is/trpc-interface/index.server"; import type { ItemDropTarget, Placement } from "@webstudio-is/design-system"; import type { Assets, - DataSource, DataSources, Instance, - Page, Prop, Props, Resource, @@ -18,7 +16,6 @@ import type { StyleSource, StyleSources, StyleSourceSelections, - System, } from "@webstudio-is/sdk"; import type { Style } from "@webstudio-is/css-engine"; import type { Project } from "@webstudio-is/project"; @@ -57,30 +54,8 @@ export const $rootInstance = computed( ); export const $dataSources = atom(new Map()); -export const $dataSourceVariables = atom>( - new Map() -); - -export const updateSystem = (page: Page, update: Partial) => { - const dataSourceVariables = new Map($dataSourceVariables.get()); - const system = dataSourceVariables.get(page.systemDataSourceId) as - | undefined - | System; - - const newSystem: System = { - search: {}, - params: {}, - // This value seems like doesn't matter - origin: "", - ...system, - ...update, - }; - dataSourceVariables.set(page.systemDataSourceId, newSystem); - $dataSourceVariables.set(dataSourceVariables); -}; export const $resources = atom(new Map()); -export const $resourceValues = atom(new Map()); export const $props = atom(new Map()); export const $propsIndex = computed($props, (props) => { diff --git a/apps/builder/app/shared/nano-states/props.test.ts b/apps/builder/app/shared/nano-states/props.test.ts index badf2c73df..f06ff3f54a 100644 --- a/apps/builder/app/shared/nano-states/props.test.ts +++ b/apps/builder/app/shared/nano-states/props.test.ts @@ -14,15 +14,9 @@ import { computeExpression, } from "./props"; import { $pages, $selectedPageId } from "./pages"; -import { - $assets, - $dataSourceVariables, - $dataSources, - $props, - $resourceValues, - $resources, -} from "./nano-states"; +import { $assets, $dataSources, $props, $resources } from "./nano-states"; import { $params } from "~/canvas/stores"; +import { $dataSourceVariables, $resourceValues } from "./variables"; setEnv("*"); @@ -547,6 +541,53 @@ test("compute instance text content bound to expression", () => { cleanStores($propValuesByInstanceSelector); }); +test("use default system values in props", () => { + $instances.set( + toMap([ + { + id: "body", + type: "instance", + component: "Body", + children: [], + }, + ]) + ); + $dataSources.set( + toMap([ + { + id: "systemId", + scopeInstanceId: "body", + name: "system", + type: "parameter", + }, + ]) + ); + $props.set( + toMap([ + { + id: "1", + instanceId: "body", + name: "data-origin", + type: "expression", + value: `$ws$dataSource$systemId.origin`, + }, + ]) + ); + selectPageRoot("body"); + expect($propValuesByInstanceSelector.get()).toEqual( + new Map([ + [ + JSON.stringify(["body"]), + new Map([ + ["data-origin", "https://undefined.wstd.work"], + ]), + ], + ]) + ); + + cleanStores($propValuesByInstanceSelector); +}); + test("compute variable values for root", () => { $instances.set( toMap([{ id: "body", type: "instance", component: "Body", children: [] }]) @@ -929,7 +970,14 @@ test("prefill default system variable value", () => { [ JSON.stringify(["body"]), new Map([ - ["systemId", { params: { slug: "my-post" }, search: {} }], + [ + "systemId", + { + params: { slug: "my-post" }, + search: {}, + origin: "https://undefined.wstd.work", + }, + ], ]), ], ]) diff --git a/apps/builder/app/shared/nano-states/props.ts b/apps/builder/app/shared/nano-states/props.ts index 56cac03c60..fdbb0035fd 100644 --- a/apps/builder/app/shared/nano-states/props.ts +++ b/apps/builder/app/shared/nano-states/props.ts @@ -20,20 +20,18 @@ import { } from "@webstudio-is/react-sdk"; import { isFeatureEnabled } from "@webstudio-is/feature-flags"; import { $instances } from "./instances"; -import { - $dataSourceVariables, - $dataSources, - $props, - $assets, - $resources, - $resourceValues, - $publishedOrigin, -} from "./nano-states"; +import { $dataSources, $props, $assets, $resources } from "./nano-states"; import { $selectedPage, $pages } from "./pages"; import { groupBy } from "../array-utils"; import type { InstanceSelector } from "../tree-utils"; import { $params } from "~/canvas/stores"; import { restResourcesLoader } from "../router-utils"; +import { + $dataSourceVariables, + $resourceValues, + $selectedPageDefaultSystem, + mergeSystem, +} from "./variables"; export const getIndexedInstanceId = ( instanceId: Instance["id"], @@ -120,8 +118,14 @@ const getAction = ( // result of executing generated code // includes variables, computed expressions and action callbacks const $dataSourcesLogic = computed( - [$dataSources, $dataSourceVariables, $resourceValues], - (dataSources, dataSourceVariables, resourceValues) => { + [ + $dataSources, + $dataSourceVariables, + $resourceValues, + $selectedPage, + $selectedPageDefaultSystem, + ], + (dataSources, dataSourceVariables, resourceValues, page, defaultSystem) => { const values = new Map(); for (const [dataSourceId, dataSource] of dataSources) { if (dataSource.type === "variable") { @@ -131,7 +135,11 @@ const $dataSourcesLogic = computed( ); } if (dataSource.type === "parameter") { - values.set(dataSourceId, dataSourceVariables.get(dataSourceId)); + let value = dataSourceVariables.get(dataSourceId); + if (dataSource.id === page?.systemDataSourceId) { + value = mergeSystem(defaultSystem, value as undefined | System); + } + values.set(dataSourceId, value); } if (dataSource.type === "resource") { values.set(dataSourceId, resourceValues.get(dataSource.resourceId)); @@ -301,7 +309,7 @@ export const $variableValuesByInstanceSelector = computed( $dataSources, $dataSourceVariables, $resourceValues, - $publishedOrigin, + $selectedPageDefaultSystem, ], ( instances, @@ -310,7 +318,7 @@ export const $variableValuesByInstanceSelector = computed( dataSources, dataSourceVariables, resourceValues, - publishedOrigin + defaultSystem ) => { const propsByInstanceId = groupBy( props.values(), @@ -361,17 +369,11 @@ export const $variableValuesByInstanceSelector = computed( if (variable.type === "parameter") { const value = dataSourceVariables.get(variable.id); variableValues.set(variable.id, value); - if ( - variable.id === page.systemDataSourceId && - value === undefined - ) { - const systemDefaultValue: System = { - params: {}, - search: {}, - origin: publishedOrigin, - }; - - variableValues.set(variable.id, systemDefaultValue); + if (variable.id === page.systemDataSourceId) { + variableValues.set( + variable.id, + mergeSystem(defaultSystem, value as undefined | 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 new file mode 100644 index 0000000000..fc205c5bae --- /dev/null +++ b/apps/builder/app/shared/nano-states/variables.ts @@ -0,0 +1,73 @@ +import { atom, computed } from "nanostores"; +import type { DataSource, Page, Resource, System } from "@webstudio-is/sdk"; +import { tokenizePathnamePattern } from "~/builder/shared/url-pattern"; +import { $publishedOrigin } from "./nano-states"; +import { $selectedPage } from "./pages"; + +export const $dataSourceVariables = atom>( + new Map() +); + +export const $resourceValues = atom(new Map()); + +export const getPageDefaultSystem = ({ + origin, + path, +}: { + origin: string; + path?: string; +}) => { + const defaultSystem: System = { + params: {}, + search: {}, + origin, + }; + if (path) { + const tokens = tokenizePathnamePattern(path); + for (const token of tokens) { + if (token.type === "param") { + defaultSystem.params[token.name] = undefined; + } + } + } + return defaultSystem; +}; + +const $selectedPagePath = computed($selectedPage, (page) => page?.path); + +export const $selectedPageDefaultSystem = computed( + [$publishedOrigin, $selectedPagePath], + (origin, path) => getPageDefaultSystem({ origin, path }) +); + +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) => { + 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); +};