Skip to content

Commit

Permalink
feat: support global data variables (#4870)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
TrySound authored Feb 14, 2025
1 parent 13a4660 commit 8ed3fbb
Show file tree
Hide file tree
Showing 20 changed files with 484 additions and 175 deletions.
11 changes: 3 additions & 8 deletions apps/builder/app/builder/features/inspector/inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useRef } from "react";
import { computed } from "nanostores";
import { useStore } from "@nanostores/react";
import type { Instance } from "@webstudio-is/sdk";
import { rootComponent } from "@webstudio-is/sdk";
import {
theme,
PanelTabs,
Expand All @@ -20,7 +19,7 @@ import {
FloatingPanelProvider,
} from "@webstudio-is/design-system";
import { ModeMenu, StylePanel } from "~/builder/features/style-panel";
import { SettingsPanelContainer } from "~/builder/features/settings-panel";
import { SettingsPanel } from "~/builder/features/settings-panel";
import {
$registeredComponentMetas,
$dragAndDropState,
Expand Down Expand Up @@ -93,6 +92,7 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => {
type PanelName = "style" | "settings";

const availablePanels = new Set<PanelName>();
availablePanels.add("settings");
if (
// forbid styling body in xml document
documentType === "html" &&
Expand All @@ -102,11 +102,6 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => {
) {
availablePanels.add("style");
}
// @todo hide root component settings until
// global data sources are implemented
if (selectedInstance.component !== rootComponent) {
availablePanels.add("settings");
}

return (
<EnhancedTooltipProvider
Expand Down Expand Up @@ -197,7 +192,7 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => {
>
<InstanceInfo instance={selectedInstance} />
</Flex>
<SettingsPanelContainer
<SettingsPanel
// Re-render when instance changes
key={selectedInstance.id}
selectedInstance={selectedInstance}
Expand Down
3 changes: 2 additions & 1 deletion apps/builder/app/builder/features/pages/page-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
encodeDataSourceVariable,
ROOT_FOLDER_ID,
isRootFolder,
ROOT_INSTANCE_ID,
} from "@webstudio-is/sdk";
import { removeByMutable } from "~/shared/array-utils";
import {
Expand Down Expand Up @@ -255,7 +256,7 @@ export const $pageRootScope = computed(
}
const values =
variableValuesByInstanceSelector.get(
getInstanceKey([page.rootInstanceId])
getInstanceKey([page.rootInstanceId, ROOT_INSTANCE_ID])
) ?? new Map<string, unknown>();
for (const [dataSourceId, value] of values) {
const dataSource = dataSources.get(dataSourceId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ import {
import { parseCurl, type CurlRequest } from "./curl";
import {
$selectedInstance,
$selectedInstanceKey,
$selectedInstanceKeyWithRoot,
$selectedPage,
} from "~/shared/awareness";
import { updateWebstudioData } from "~/shared/instance-utils";
Expand Down Expand Up @@ -384,7 +384,7 @@ const $hiddenDataSourceIds = computed(

const $selectedInstanceScope = computed(
[
$selectedInstanceKey,
$selectedInstanceKeyWithRoot,
$variableValuesByInstanceSelector,
$dataSources,
$hiddenDataSourceIds,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { useStore } from "@nanostores/react";
import cmsUpgradeBanner from "./cms-upgrade-banner.svg?url";
import { $isDesignMode, $userPlanFeatures } from "~/shared/nano-states";

export const SettingsPanelContainer = ({
export const SettingsPanel = ({
selectedInstance,
}: {
selectedInstance: Instance;
Expand Down
8 changes: 6 additions & 2 deletions apps/builder/app/builder/features/settings-panel/shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
} from "~/shared/nano-states";
import type { BindingVariant } from "~/builder/shared/binding-popover";
import { humanizeString } from "~/shared/string-utils";
import { $selectedInstanceKey } from "~/shared/awareness";
import { $selectedInstanceKeyWithRoot } from "~/shared/awareness";

export type PropValue =
| { type: "number"; value: number }
Expand Down Expand Up @@ -314,7 +314,11 @@ export const Row = ({
);

export const $selectedInstanceScope = computed(
[$selectedInstanceKey, $variableValuesByInstanceSelector, $dataSources],
[
$selectedInstanceKeyWithRoot,
$variableValuesByInstanceSelector,
$dataSources,
],
(instanceKey, variableValuesByInstanceSelector, dataSources) => {
const scope: Record<string, unknown> = {};
const aliases = new Map<string, string>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,45 +51,40 @@ import {
} from "./variable-popover";
import {
$selectedInstance,
$selectedInstanceKey,
$selectedInstancePath,
$selectedInstanceKeyWithRoot,
$selectedPage,
} from "~/shared/awareness";
import { updateWebstudioData } from "~/shared/instance-utils";
import { deleteVariableMutable } from "~/shared/data-variables";
import {
deleteVariableMutable,
findAvailableVariables,
} from "~/shared/data-variables";

/**
* find variables defined specifically on this selected instance
*/
const $availableVariables = computed(
[$selectedInstancePath, $dataSources],
(instancePath, dataSources) => {
if (instancePath === undefined) {
[$selectedInstance, $instances, $dataSources],
(selectedInstance, instances, dataSources) => {
if (selectedInstance === undefined) {
return [];
}
const [{ instanceSelector }] = instancePath;
const [selectedInstanceId] = instanceSelector;
const availableVariables = new Map<DataSource["name"], DataSource>();
// 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);
}
}
}
const availableVariables = findAvailableVariables({
startingInstanceId: selectedInstance.id,
instances,
dataSources,
});
// order local variables first
return Array.from(availableVariables.values()).sort((left, right) => {
const leftRank = left.scopeInstanceId === selectedInstanceId ? 0 : 1;
const rightRank = right.scopeInstanceId === selectedInstanceId ? 0 : 1;
const leftRank = left.scopeInstanceId === selectedInstance.id ? 0 : 1;
const rightRank = right.scopeInstanceId === selectedInstance.id ? 0 : 1;
return leftRank - rightRank;
});
}
);

const $instanceVariableValues = computed(
[$selectedInstanceKey, $variableValuesByInstanceSelector],
[$selectedInstanceKeyWithRoot, $variableValuesByInstanceSelector],
(instanceKey, variableValuesByInstanceSelector) =>
variableValuesByInstanceSelector.get(instanceKey ?? "") ??
new Map<string, unknown>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import type { Instance, Instances } from "@webstudio-is/sdk";
import { blockTemplateComponent } from "@webstudio-is/sdk";
import { shallowEqual } from "shallow-equal";
import { selectInstance } from "~/shared/awareness";
import { findAvailableVariables } from "~/shared/data-variables";
import {
extractWebstudioFragment,
findAllEditableInstanceSelector,
findAvailableDataSources,
getWebstudioData,
insertInstanceChildrenMutable,
insertWebstudioFragmentCopy,
Expand Down Expand Up @@ -107,11 +107,10 @@ export const insertListItemAt = (listItemSelector: InstanceSelector) => {
const { newInstanceIds } = insertWebstudioFragmentCopy({
data,
fragment,
availableDataSources: findAvailableDataSources(
data.dataSources,
data.instances,
target.parentSelector
),
availableVariables: findAvailableVariables({
...data,
startingInstanceId: target.parentSelector[0],
}),
});
const newRootInstanceId = newInstanceIds.get(fragment.instances[0].id);
if (newRootInstanceId === undefined) {
Expand Down Expand Up @@ -170,11 +169,10 @@ export const insertTemplateAt = (
const { newInstanceIds } = insertWebstudioFragmentCopy({
data,
fragment,
availableDataSources: findAvailableDataSources(
data.dataSources,
data.instances,
target.parentSelector
),
availableVariables: findAvailableVariables({
...data,
startingInstanceId: target.parentSelector[0],
}),
});
const newRootInstanceId = newInstanceIds.get(fragment.instances[0].id);
if (newRootInstanceId === undefined) {
Expand Down
11 changes: 5 additions & 6 deletions apps/builder/app/builder/shared/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
} from "~/shared/breakpoints";
import {
deleteInstanceMutable,
findAvailableDataSources,
extractWebstudioFragment,
insertWebstudioFragmentCopy,
updateWebstudioData,
Expand All @@ -44,6 +43,7 @@ import {
isTreeMatching,
} from "~/shared/matcher";
import { getSetting, setSetting } from "./client-settings";
import { findAvailableVariables } from "~/shared/data-variables";

const makeBreakpointCommand = <CommandName extends string>(
name: CommandName,
Expand Down Expand Up @@ -424,11 +424,10 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({
const { newInstanceIds } = insertWebstudioFragmentCopy({
data,
fragment,
availableDataSources: findAvailableDataSources(
data.dataSources,
data.instances,
parentItem.instanceSelector
),
availableVariables: findAvailableVariables({
...data,
startingInstanceId: parentItem.instanceSelector[0],
}),
});
const newRootInstanceId = newInstanceIds.get(
selectedItem.instance.id
Expand Down
13 changes: 13 additions & 0 deletions apps/builder/app/shared/awareness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@ export const getInstanceKey = <
): (InstanceSelector extends undefined ? undefined : never) | string =>
JSON.stringify(instanceSelector);

export const $selectedInstanceKeyWithRoot = computed(
$awareness,
(awareness) => {
const instanceSelector = awareness?.instanceSelector;
if (instanceSelector) {
if (instanceSelector[0] === ROOT_INSTANCE_ID) {
return getInstanceKey(instanceSelector);
}
return getInstanceKey([...instanceSelector, ROOT_INSTANCE_ID]);
}
}
);

export const $selectedInstanceKey = computed($awareness, (awareness) =>
getInstanceKey(awareness?.instanceSelector)
);
Expand Down
Loading

0 comments on commit 8ed3fbb

Please sign in to comment.