Skip to content

Commit 8ed3fbb

Browse files
authored
feat: support global data variables (#4870)
Ref #4166 #3686 Added "Settings" section to global root. Now users can create data variables which are available for all pages and inside slots. https://github.com/user-attachments/assets/be2cf862-08ec-427e-aea3-076fa03a4437
1 parent 13a4660 commit 8ed3fbb

File tree

20 files changed

+484
-175
lines changed

20 files changed

+484
-175
lines changed

apps/builder/app/builder/features/inspector/inspector.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { useRef } from "react";
22
import { computed } from "nanostores";
33
import { useStore } from "@nanostores/react";
44
import type { Instance } from "@webstudio-is/sdk";
5-
import { rootComponent } from "@webstudio-is/sdk";
65
import {
76
theme,
87
PanelTabs,
@@ -20,7 +19,7 @@ import {
2019
FloatingPanelProvider,
2120
} from "@webstudio-is/design-system";
2221
import { ModeMenu, StylePanel } from "~/builder/features/style-panel";
23-
import { SettingsPanelContainer } from "~/builder/features/settings-panel";
22+
import { SettingsPanel } from "~/builder/features/settings-panel";
2423
import {
2524
$registeredComponentMetas,
2625
$dragAndDropState,
@@ -93,6 +92,7 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => {
9392
type PanelName = "style" | "settings";
9493

9594
const availablePanels = new Set<PanelName>();
95+
availablePanels.add("settings");
9696
if (
9797
// forbid styling body in xml document
9898
documentType === "html" &&
@@ -102,11 +102,6 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => {
102102
) {
103103
availablePanels.add("style");
104104
}
105-
// @todo hide root component settings until
106-
// global data sources are implemented
107-
if (selectedInstance.component !== rootComponent) {
108-
availablePanels.add("settings");
109-
}
110105

111106
return (
112107
<EnhancedTooltipProvider
@@ -197,7 +192,7 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => {
197192
>
198193
<InstanceInfo instance={selectedInstance} />
199194
</Flex>
200-
<SettingsPanelContainer
195+
<SettingsPanel
201196
// Re-render when instance changes
202197
key={selectedInstance.id}
203198
selectedInstance={selectedInstance}

apps/builder/app/builder/features/pages/page-utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
encodeDataSourceVariable,
1212
ROOT_FOLDER_ID,
1313
isRootFolder,
14+
ROOT_INSTANCE_ID,
1415
} from "@webstudio-is/sdk";
1516
import { removeByMutable } from "~/shared/array-utils";
1617
import {
@@ -255,7 +256,7 @@ export const $pageRootScope = computed(
255256
}
256257
const values =
257258
variableValuesByInstanceSelector.get(
258-
getInstanceKey([page.rootInstanceId])
259+
getInstanceKey([page.rootInstanceId, ROOT_INSTANCE_ID])
259260
) ?? new Map<string, unknown>();
260261
for (const [dataSourceId, value] of values) {
261262
const dataSource = dataSources.get(dataSourceId);

apps/builder/app/builder/features/settings-panel/resource-panel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ import {
5555
import { parseCurl, type CurlRequest } from "./curl";
5656
import {
5757
$selectedInstance,
58-
$selectedInstanceKey,
58+
$selectedInstanceKeyWithRoot,
5959
$selectedPage,
6060
} from "~/shared/awareness";
6161
import { updateWebstudioData } from "~/shared/instance-utils";
@@ -384,7 +384,7 @@ const $hiddenDataSourceIds = computed(
384384

385385
const $selectedInstanceScope = computed(
386386
[
387-
$selectedInstanceKey,
387+
$selectedInstanceKeyWithRoot,
388388
$variableValuesByInstanceSelector,
389389
$dataSources,
390390
$hiddenDataSourceIds,

apps/builder/app/builder/features/settings-panel/settings-panel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { useStore } from "@nanostores/react";
1616
import cmsUpgradeBanner from "./cms-upgrade-banner.svg?url";
1717
import { $isDesignMode, $userPlanFeatures } from "~/shared/nano-states";
1818

19-
export const SettingsPanelContainer = ({
19+
export const SettingsPanel = ({
2020
selectedInstance,
2121
}: {
2222
selectedInstance: Instance;

apps/builder/app/builder/features/settings-panel/shared.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
} from "~/shared/nano-states";
3737
import type { BindingVariant } from "~/builder/shared/binding-popover";
3838
import { humanizeString } from "~/shared/string-utils";
39-
import { $selectedInstanceKey } from "~/shared/awareness";
39+
import { $selectedInstanceKeyWithRoot } from "~/shared/awareness";
4040

4141
export type PropValue =
4242
| { type: "number"; value: number }
@@ -314,7 +314,11 @@ export const Row = ({
314314
);
315315

316316
export const $selectedInstanceScope = computed(
317-
[$selectedInstanceKey, $variableValuesByInstanceSelector, $dataSources],
317+
[
318+
$selectedInstanceKeyWithRoot,
319+
$variableValuesByInstanceSelector,
320+
$dataSources,
321+
],
318322
(instanceKey, variableValuesByInstanceSelector, dataSources) => {
319323
const scope: Record<string, unknown> = {};
320324
const aliases = new Map<string, string>();

apps/builder/app/builder/features/settings-panel/variables-section.tsx

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,45 +51,40 @@ import {
5151
} from "./variable-popover";
5252
import {
5353
$selectedInstance,
54-
$selectedInstanceKey,
55-
$selectedInstancePath,
54+
$selectedInstanceKeyWithRoot,
5655
$selectedPage,
5756
} from "~/shared/awareness";
5857
import { updateWebstudioData } from "~/shared/instance-utils";
59-
import { deleteVariableMutable } from "~/shared/data-variables";
58+
import {
59+
deleteVariableMutable,
60+
findAvailableVariables,
61+
} from "~/shared/data-variables";
6062

6163
/**
6264
* find variables defined specifically on this selected instance
6365
*/
6466
const $availableVariables = computed(
65-
[$selectedInstancePath, $dataSources],
66-
(instancePath, dataSources) => {
67-
if (instancePath === undefined) {
67+
[$selectedInstance, $instances, $dataSources],
68+
(selectedInstance, instances, dataSources) => {
69+
if (selectedInstance === undefined) {
6870
return [];
6971
}
70-
const [{ instanceSelector }] = instancePath;
71-
const [selectedInstanceId] = instanceSelector;
72-
const availableVariables = new Map<DataSource["name"], DataSource>();
73-
// order from ancestor to descendant
74-
// so descendants can override ancestor variables
75-
for (const { instance } of instancePath.slice().reverse()) {
76-
for (const dataSource of dataSources.values()) {
77-
if (dataSource.scopeInstanceId === instance.id) {
78-
availableVariables.set(dataSource.name, dataSource);
79-
}
80-
}
81-
}
72+
const availableVariables = findAvailableVariables({
73+
startingInstanceId: selectedInstance.id,
74+
instances,
75+
dataSources,
76+
});
8277
// order local variables first
8378
return Array.from(availableVariables.values()).sort((left, right) => {
84-
const leftRank = left.scopeInstanceId === selectedInstanceId ? 0 : 1;
85-
const rightRank = right.scopeInstanceId === selectedInstanceId ? 0 : 1;
79+
const leftRank = left.scopeInstanceId === selectedInstance.id ? 0 : 1;
80+
const rightRank = right.scopeInstanceId === selectedInstance.id ? 0 : 1;
8681
return leftRank - rightRank;
8782
});
8883
}
8984
);
9085

9186
const $instanceVariableValues = computed(
92-
[$selectedInstanceKey, $variableValuesByInstanceSelector],
87+
[$selectedInstanceKeyWithRoot, $variableValuesByInstanceSelector],
9388
(instanceKey, variableValuesByInstanceSelector) =>
9489
variableValuesByInstanceSelector.get(instanceKey ?? "") ??
9590
new Map<string, unknown>()

apps/builder/app/builder/features/workspace/canvas-tools/outline/block-utils.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import type { Instance, Instances } from "@webstudio-is/sdk";
22
import { blockTemplateComponent } from "@webstudio-is/sdk";
33
import { shallowEqual } from "shallow-equal";
44
import { selectInstance } from "~/shared/awareness";
5+
import { findAvailableVariables } from "~/shared/data-variables";
56
import {
67
extractWebstudioFragment,
78
findAllEditableInstanceSelector,
8-
findAvailableDataSources,
99
getWebstudioData,
1010
insertInstanceChildrenMutable,
1111
insertWebstudioFragmentCopy,
@@ -107,11 +107,10 @@ export const insertListItemAt = (listItemSelector: InstanceSelector) => {
107107
const { newInstanceIds } = insertWebstudioFragmentCopy({
108108
data,
109109
fragment,
110-
availableDataSources: findAvailableDataSources(
111-
data.dataSources,
112-
data.instances,
113-
target.parentSelector
114-
),
110+
availableVariables: findAvailableVariables({
111+
...data,
112+
startingInstanceId: target.parentSelector[0],
113+
}),
115114
});
116115
const newRootInstanceId = newInstanceIds.get(fragment.instances[0].id);
117116
if (newRootInstanceId === undefined) {
@@ -170,11 +169,10 @@ export const insertTemplateAt = (
170169
const { newInstanceIds } = insertWebstudioFragmentCopy({
171170
data,
172171
fragment,
173-
availableDataSources: findAvailableDataSources(
174-
data.dataSources,
175-
data.instances,
176-
target.parentSelector
177-
),
172+
availableVariables: findAvailableVariables({
173+
...data,
174+
startingInstanceId: target.parentSelector[0],
175+
}),
178176
});
179177
const newRootInstanceId = newInstanceIds.get(fragment.instances[0].id);
180178
if (newRootInstanceId === undefined) {

apps/builder/app/builder/shared/commands.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
} from "~/shared/breakpoints";
2121
import {
2222
deleteInstanceMutable,
23-
findAvailableDataSources,
2423
extractWebstudioFragment,
2524
insertWebstudioFragmentCopy,
2625
updateWebstudioData,
@@ -44,6 +43,7 @@ import {
4443
isTreeMatching,
4544
} from "~/shared/matcher";
4645
import { getSetting, setSetting } from "./client-settings";
46+
import { findAvailableVariables } from "~/shared/data-variables";
4747

4848
const makeBreakpointCommand = <CommandName extends string>(
4949
name: CommandName,
@@ -424,11 +424,10 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({
424424
const { newInstanceIds } = insertWebstudioFragmentCopy({
425425
data,
426426
fragment,
427-
availableDataSources: findAvailableDataSources(
428-
data.dataSources,
429-
data.instances,
430-
parentItem.instanceSelector
431-
),
427+
availableVariables: findAvailableVariables({
428+
...data,
429+
startingInstanceId: parentItem.instanceSelector[0],
430+
}),
432431
});
433432
const newRootInstanceId = newInstanceIds.get(
434433
selectedItem.instance.id

apps/builder/app/shared/awareness.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,19 @@ export const getInstanceKey = <
7272
): (InstanceSelector extends undefined ? undefined : never) | string =>
7373
JSON.stringify(instanceSelector);
7474

75+
export const $selectedInstanceKeyWithRoot = computed(
76+
$awareness,
77+
(awareness) => {
78+
const instanceSelector = awareness?.instanceSelector;
79+
if (instanceSelector) {
80+
if (instanceSelector[0] === ROOT_INSTANCE_ID) {
81+
return getInstanceKey(instanceSelector);
82+
}
83+
return getInstanceKey([...instanceSelector, ROOT_INSTANCE_ID]);
84+
}
85+
}
86+
);
87+
7588
export const $selectedInstanceKey = computed($awareness, (awareness) =>
7689
getInstanceKey(awareness?.instanceSelector)
7790
);

0 commit comments

Comments
 (0)