From 0da0d56dad4eb3639aaf1bf08cedebc8e6de5ef2 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Wed, 22 Jan 2025 22:44:53 +0700 Subject: [PATCH] feat: show inherited data variables Here made a few changes to align data variables with css variables. 1. inherited variables are also shown in "Data Variables" section in Settings 2. local and remote variables have blue and orange labels 3. local variables with the same name "mask" inherited variables in the list --- .../settings-panel/variables-section.tsx | 84 +++++++++++-------- .../app/builder/shared/binding-popover.tsx | 2 +- apps/builder/app/shared/instance-utils.ts | 6 +- .../app/shared/nano-states/props.test.ts | 6 ++ .../src/components/css-value-list-item.tsx | 2 +- packages/sdk/src/schema/data-sources.ts | 6 +- 6 files changed, 64 insertions(+), 42 deletions(-) diff --git a/apps/builder/app/builder/features/settings-panel/variables-section.tsx b/apps/builder/app/builder/features/settings-panel/variables-section.tsx index 7ac61f4bf0b1..00e284e518e5 100644 --- a/apps/builder/app/builder/features/settings-panel/variables-section.tsx +++ b/apps/builder/app/builder/features/settings-panel/variables-section.tsx @@ -48,25 +48,30 @@ import { import { $selectedInstance, $selectedInstanceKey, + $selectedInstancePath, $selectedPage, } from "~/shared/awareness"; /** * find variables defined specifically on this selected instance */ -const $instanceVariables = computed( - [$selectedInstance, $dataSources], - (instance, dataSources) => { - const matchedVariables: DataSource[] = []; - if (instance === undefined) { - return matchedVariables; +const $availableVariables = computed( + [$selectedInstancePath, $dataSources], + (instancePath, dataSources) => { + if (instancePath === undefined) { + return []; } - for (const dataSource of dataSources.values()) { - if (instance.id === dataSource.scopeInstanceId) { - matchedVariables.push(dataSource); + const availableVariables = new Map(); + // order from ancestor to descendant + // so descendants can override ancestor variables + for (const { instance } of instancePath.slice().reverse()) { + for (const dataSource of dataSources.values()) { + if (dataSource.scopeInstanceId === instance.id) { + availableVariables.set(dataSource.name, dataSource); + } } } - return matchedVariables; + return Array.from(availableVariables.values()); } ); @@ -181,19 +186,19 @@ const EmptyVariables = () => { const VariablesItem = ({ variable, + source, index, value, usageCount, }: { variable: DataSource; + source: "local" | "remote"; index: number; value: unknown; usageCount: number; }) => { - const label = - value === undefined - ? variable.name - : `${variable.name}: ${formatValuePreview(value)}`; + const labelValue = + value === undefined ? "" : `: ${formatValuePreview(value)}`; const [inspectDialogOpen, setInspectDialogOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false); return ( @@ -201,7 +206,13 @@ const VariablesItem = ({ {label}} + label={ + + + {labelValue} + + } + disabled={source === "remote"} data-state={isMenuOpen ? "open" : undefined} buttons={ <> @@ -234,13 +245,15 @@ const VariablesItem = ({ setInspectDialogOpen(true)}> Inspect - 0} - onSelect={() => deleteVariable(variable.id)} - > - Delete {usageCount > 0 && `(${usageCount} bindings)`} - + {source === "local" && ( + 0} + onSelect={() => deleteVariable(variable.id)} + > + Delete {usageCount > 0 && `(${usageCount} bindings)`} + + )} @@ -252,7 +265,8 @@ const VariablesItem = ({ }; const VariablesList = () => { - const availableVariables = useStore($instanceVariables); + const instance = useStore($selectedInstance); + const availableVariables = useStore($availableVariables); const variableValues = useStore($instanceVariableValues); const usedVariables = useStore($usedVariables); @@ -262,18 +276,18 @@ const VariablesList = () => { return ( - {availableVariables.map((variable, index) => { - const value = variableValues.get(variable.id); - return ( - - ); - })} + {availableVariables.map((variable, index) => ( + + ))} ); }; diff --git a/apps/builder/app/builder/shared/binding-popover.tsx b/apps/builder/app/builder/shared/binding-popover.tsx index 50396662886f..c68b5868f0f8 100644 --- a/apps/builder/app/builder/shared/binding-popover.tsx +++ b/apps/builder/app/builder/shared/binding-popover.tsx @@ -8,6 +8,7 @@ import { createContext, type ReactNode, } from "react"; +import { useStore } from "@nanostores/react"; import { DotIcon, InfoCircleIcon, @@ -47,7 +48,6 @@ import { $isDesignMode, computeExpression, } from "~/shared/nano-states"; -import { useStore } from "@nanostores/react"; export const evaluateExpressionWithinScope = ( expression: string, diff --git a/apps/builder/app/shared/instance-utils.ts b/apps/builder/app/shared/instance-utils.ts index c99d1bb514e7..dc2c153de4f0 100644 --- a/apps/builder/app/shared/instance-utils.ts +++ b/apps/builder/app/shared/instance-utils.ts @@ -1087,14 +1087,16 @@ export const insertWebstudioFragmentCopy = ({ dataSources.set(newDataSourceId, { ...dataSource, id: newDataSourceId, - scopeInstanceId: newInstanceIds.get(scopeInstanceId), + scopeInstanceId: + newInstanceIds.get(scopeInstanceId) ?? scopeInstanceId, resourceId: newResourceId, }); } else { dataSources.set(newDataSourceId, { ...dataSource, id: newDataSourceId, - scopeInstanceId: newInstanceIds.get(scopeInstanceId), + scopeInstanceId: + newInstanceIds.get(scopeInstanceId) ?? scopeInstanceId, }); } } diff --git a/apps/builder/app/shared/nano-states/props.test.ts b/apps/builder/app/shared/nano-states/props.test.ts index 1ea43050287c..debc382a3d2f 100644 --- a/apps/builder/app/shared/nano-states/props.test.ts +++ b/apps/builder/app/shared/nano-states/props.test.ts @@ -93,12 +93,14 @@ test("compute expression prop values", () => { toMap([ { id: "var1", + scopeInstanceId: "box", type: "variable", name: "", value: { type: "number", value: 1 }, }, { id: "var2", + scopeInstanceId: "box", type: "variable", name: "", value: { type: "string", value: "Hello" }, @@ -162,6 +164,7 @@ test("generate action prop callbacks", () => { toMap([ { id: "var", + scopeInstanceId: "box", type: "variable", name: "", value: { type: "number", value: 1 }, @@ -292,6 +295,7 @@ test("compute expression from collection items", () => { toMap([ { id: "itemId", + scopeInstanceId: "list", type: "parameter", name: "item", }, @@ -362,6 +366,7 @@ test("access parameter value from variables values", () => { toMap([ { id: "parameterId", + scopeInstanceId: "body", type: "parameter", name: "paramName", }, @@ -400,6 +405,7 @@ test("compute props bound to resource variables", () => { toMap([ { id: "resourceVariableId", + scopeInstanceId: "body", type: "resource", name: "paramName", resourceId: "resourceId", diff --git a/packages/design-system/src/components/css-value-list-item.tsx b/packages/design-system/src/components/css-value-list-item.tsx index 54728f82ca1c..e77cb787b311 100644 --- a/packages/design-system/src/components/css-value-list-item.tsx +++ b/packages/design-system/src/components/css-value-list-item.tsx @@ -180,7 +180,7 @@ export const CssValueListItem = forwardRef( {...listItemAttributes} {...rest} hidden={hidden} - disabled={hidden === true} + disabled={hidden === true || rest.disabled} > diff --git a/packages/sdk/src/schema/data-sources.ts b/packages/sdk/src/schema/data-sources.ts index 8ad76ba47023..003a8a82ceaf 100644 --- a/packages/sdk/src/schema/data-sources.ts +++ b/packages/sdk/src/schema/data-sources.ts @@ -30,20 +30,20 @@ export const DataSource = z.union([ z.object({ type: z.literal("variable"), id: DataSourceId, - scopeInstanceId: z.optional(z.string()), + scopeInstanceId: z.string(), name: z.string(), value: DataSourceVariableValue, }), z.object({ type: z.literal("parameter"), id: DataSourceId, - scopeInstanceId: z.optional(z.string()), + scopeInstanceId: z.string(), name: z.string(), }), z.object({ type: z.literal("resource"), id: DataSourceId, - scopeInstanceId: z.optional(z.string()), + scopeInstanceId: z.string(), name: z.string(), resourceId: z.string(), }),