diff --git a/apps/designer/app/canvas/canvas.tsx b/apps/designer/app/canvas/canvas.tsx
index 1cece89fa6..a0d54ee2cf 100644
--- a/apps/designer/app/canvas/canvas.tsx
+++ b/apps/designer/app/canvas/canvas.tsx
@@ -37,6 +37,7 @@ import {
useSubscribeScrollState,
useIsPreviewMode,
useSetAssets,
+ useSetTreeId,
} from "~/shared/nano-states";
import { usePublishScrollState } from "./shared/use-publish-scroll-state";
import { useDragAndDrop } from "./shared/use-drag-drop";
@@ -119,6 +120,7 @@ export const Canvas = ({ data }: CanvasProps): JSX.Element | null => {
throw new Error("Tree is null");
}
const isDesignerReady = useSubscribeDesignerReady();
+ useSetTreeId(data.tree.id);
useSetAssets(data.assets);
useSetBreakpoints(data.build.breakpoints);
useSetProps(data.tree.props);
diff --git a/apps/designer/app/designer/designer.tsx b/apps/designer/app/designer/designer.tsx
index f82602756a..e26039f64a 100644
--- a/apps/designer/app/designer/designer.tsx
+++ b/apps/designer/app/designer/designer.tsx
@@ -35,6 +35,7 @@ import {
useIsPreviewMode,
useSetAuthPermit,
useSetIsPreviewMode,
+ useSetTreeId,
} from "~/shared/nano-states";
import { useClientSettings } from "./shared/client-settings";
import { Navigator } from "./features/sidebar-left";
@@ -266,6 +267,7 @@ export const Designer = ({
useSetAuthPermit(authPermit);
useSubscribeBreakpoints();
useSetProject(project);
+ useSetTreeId(treeId);
useSetPages(pages);
useSetCurrentPageId(pageId);
const [publish, publishRef] = usePublish();
@@ -354,7 +356,7 @@ export const Designer = ({
{dragAndDropState.isDragging ? (
) : (
-
+
)}
diff --git a/apps/designer/app/designer/features/inspector/inspector.tsx b/apps/designer/app/designer/features/inspector/inspector.tsx
index 888b8c1a94..fa705a3a8b 100644
--- a/apps/designer/app/designer/features/inspector/inspector.tsx
+++ b/apps/designer/app/designer/features/inspector/inspector.tsx
@@ -1,6 +1,5 @@
import { useRef } from "react";
import { useStore } from "@nanostores/react";
-import { Tree } from "@webstudio-is/project-build";
import type { Publish } from "~/shared/pubsub";
import {
DeprecatedText2,
@@ -21,7 +20,6 @@ import { theme } from "@webstudio-is/design-system";
import { selectedInstanceStore } from "~/shared/nano-states";
type InspectorProps = {
- treeId: Tree["id"];
publish: Publish;
};
@@ -31,7 +29,7 @@ const contentStyle = {
overflow: "auto",
};
-export const Inspector = ({ treeId, publish }: InspectorProps) => {
+export const Inspector = ({ publish }: InspectorProps) => {
const selectedInstance = useStore(selectedInstanceStore);
const tabsRef = useRef(null);
@@ -71,15 +69,10 @@ export const Inspector = ({ treeId, publish }: InspectorProps) => {
-
+
= () => {
]);
return (
= () => {
propsStore.set([]);
return (
= () => {
propsStore.set([]);
return (
= () => {
);
return (
{
+export const PropsPanel = ({ selectedInstance, publish }: PropsPanelProps) => {
const instanceId = selectedInstance.id;
const instanceProps = useInstanceProps(instanceId);
@@ -255,7 +250,6 @@ export const PropsPanel = ({
});
const { setProperty: setCssProperty } = useStyleData({
- treeId,
selectedInstance,
publish,
});
diff --git a/apps/designer/app/designer/features/style-panel/shared/use-style-data.ts b/apps/designer/app/designer/features/style-panel/shared/use-style-data.ts
index d65b0312bc..0f9fa88cf0 100644
--- a/apps/designer/app/designer/features/style-panel/shared/use-style-data.ts
+++ b/apps/designer/app/designer/features/style-panel/shared/use-style-data.ts
@@ -1,14 +1,13 @@
-import { useCallback, useMemo } from "react";
-import { useStore } from "@nanostores/react";
-import { nanoid } from "nanoid";
+import { useCallback } from "react";
import store from "immerhin";
import warnOnce from "warn-once";
-import type { Instance, Tree } from "@webstudio-is/project-build";
+import type { Instance } from "@webstudio-is/project-build";
import type { StyleUpdates } from "@webstudio-is/project";
import type { StyleProperty, StyleValue } from "@webstudio-is/css-data";
import { type Publish } from "~/shared/pubsub";
import {
- styleSourceSelectionsIndexStore,
+ selectedInstanceStyleSourcesStore,
+ selectedStyleSourceStore,
styleSourceSelectionsStore,
styleSourcesStore,
stylesStore,
@@ -21,7 +20,6 @@ import {
// @todo: must be removed, now it's only for compatibility with existing code
import { parseCssValue } from "./parse-css-value";
import { useStyleInfo } from "./style-info";
-import { shallowComputed } from "~/shared/store-utils";
declare module "~/shared/pubsub" {
export interface PubsubMap {
@@ -31,7 +29,6 @@ declare module "~/shared/pubsub" {
}
type UseStyleData = {
- treeId: Tree["id"];
publish: Publish;
selectedInstance: Instance;
};
@@ -56,34 +53,22 @@ export type CreateBatchUpdate = () => {
publish: (options?: StyleUpdateOptions) => void;
};
-export const useStyleData = ({
- treeId,
- selectedInstance,
- publish,
-}: UseStyleData) => {
+export const useStyleData = ({ selectedInstance, publish }: UseStyleData) => {
const [selectedBreakpoint] = useSelectedBreakpoint();
- const instanceStyleSourcesStore = useMemo(() => {
- return shallowComputed(
- [styleSourceSelectionsIndexStore],
- (styleSourceSelectionsIndex) => {
- return (
- styleSourceSelectionsIndex.styleSourcesByInstanceId.get(
- selectedInstance.id
- ) ?? []
- );
- }
- );
- }, [selectedInstance.id]);
- const instanceStyleSources = useStore(instanceStyleSourcesStore);
- const localStyleSource = instanceStyleSources.find(
- (styleSource) => styleSource.type === "local"
- );
const currentStyle = useStyleInfo();
const publishUpdates = useCallback(
(type: "update" | "preview", updates: StyleUpdates["updates"]) => {
- if (updates.length === 0 || selectedBreakpoint === undefined) {
+ const selectedStyleSource = selectedStyleSourceStore.get();
+ const selectedInstanceStyleSources =
+ selectedInstanceStyleSourcesStore.get();
+
+ if (
+ updates.length === 0 ||
+ selectedBreakpoint === undefined ||
+ selectedStyleSource === undefined
+ ) {
return;
}
publish({
@@ -104,24 +89,24 @@ export const useStyleData = ({
(styleSourceSelections, styleSources, styles) => {
const instanceId = selectedInstance.id;
const breakpointId = selectedBreakpoint.id;
- const styleSourceId = localStyleSource?.id ?? nanoid();
- // crate style source and its selection on currently selected instance
- if (localStyleSource === undefined) {
- styleSources.push({
- type: "local",
- id: styleSourceId,
- treeId,
- });
- replaceByOrAppendMutable(
- styleSourceSelections,
- {
- instanceId,
- values: [styleSourceId],
- },
- (styleSourceSelection) =>
- styleSourceSelection.instanceId === instanceId
- );
- }
+ const styleSourceId = selectedStyleSource.id;
+ replaceByOrAppendMutable(
+ styleSources,
+ selectedStyleSource,
+ (item) => item.id === styleSourceId
+ );
+ const selections = selectedInstanceStyleSources.map(
+ (styleSource) => styleSource.id
+ );
+ replaceByOrAppendMutable(
+ styleSourceSelections,
+ {
+ instanceId,
+ values: [...selections, styleSourceId],
+ },
+ (styleSourceSelection) =>
+ styleSourceSelection.instanceId === instanceId
+ );
for (const update of updates) {
if (update.operation === "set") {
@@ -153,7 +138,7 @@ export const useStyleData = ({
}
);
},
- [publish, selectedBreakpoint, selectedInstance, localStyleSource, treeId]
+ [publish, selectedBreakpoint, selectedInstance]
);
// @deprecated should not be called
diff --git a/apps/designer/app/designer/features/style-panel/style-panel.tsx b/apps/designer/app/designer/features/style-panel/style-panel.tsx
index 2c7e78af79..c4fd5c7121 100644
--- a/apps/designer/app/designer/features/style-panel/style-panel.tsx
+++ b/apps/designer/app/designer/features/style-panel/style-panel.tsx
@@ -6,7 +6,7 @@ import {
DeprecatedParagraph,
SearchField,
} from "@webstudio-is/design-system";
-import type { Instance, Tree } from "@webstudio-is/project-build";
+import type { Instance } from "@webstudio-is/project-build";
import { useStyleData } from "./shared/use-style-data";
import { StyleSettings } from "./style-settings";
import { useState } from "react";
@@ -17,19 +17,13 @@ import {
import { theme } from "@webstudio-is/design-system";
type StylePanelProps = {
- treeId: Tree["id"];
publish: Publish;
selectedInstance: Instance;
};
-export const StylePanel = ({
- treeId,
- selectedInstance,
- publish,
-}: StylePanelProps) => {
+export const StylePanel = ({ selectedInstance, publish }: StylePanelProps) => {
const { currentStyle, setProperty, deleteProperty, createBatchUpdate } =
useStyleData({
- treeId,
selectedInstance,
publish,
});
diff --git a/apps/designer/app/shared/nano-states/nano-states.ts b/apps/designer/app/shared/nano-states/nano-states.ts
index 11e4b7cfc5..f4201e68c2 100644
--- a/apps/designer/app/shared/nano-states/nano-states.ts
+++ b/apps/designer/app/shared/nano-states/nano-states.ts
@@ -1,15 +1,17 @@
import { useMemo } from "react";
import { atom, computed, type WritableAtom } from "nanostores";
import { useStore } from "@nanostores/react";
+import { nanoid } from "nanoid";
import type { AuthPermit } from "@webstudio-is/trpc-interface";
import type { Asset } from "@webstudio-is/asset-uploader";
-import type {
+import {
Instance,
Props,
Styles,
StyleSource,
StyleSources,
StyleSourceSelections,
+ Tree,
} from "@webstudio-is/project-build";
import type { Breakpoint, Style } from "@webstudio-is/css-data";
import type {
@@ -29,6 +31,13 @@ const useValue = (atom: WritableAtom) => {
return [value, atom.set] as const;
};
+export const treeIdStore = atom(undefined);
+export const useSetTreeId = (treeId: Tree["id"]) => {
+ useSyncInitializeOnce(() => {
+ treeIdStore.set(treeId);
+ });
+};
+
export const rootInstanceContainer = atom();
export const useRootInstance = () => useValue(rootInstanceContainer);
export const useSetRootInstance = (root: Instance) => {
@@ -124,26 +133,8 @@ export const useSetStyleSourceSelections = (
});
};
-export const styleSourceSelectionsIndexStore = computed(
- [styleSourceSelectionsStore, styleSourcesIndexStore],
- (styleSourceSelections, styleSourcesIndex) => {
- const { styleSourcesById } = styleSourcesIndex;
- const styleSourcesByInstanceId = new Map();
- for (const { instanceId, values } of styleSourceSelections) {
- const styleSources: StyleSources = [];
- for (const styleSourceId of values) {
- const styleSource = styleSourcesById.get(styleSourceId);
- if (styleSource === undefined) {
- continue;
- }
- styleSources.push(styleSource);
- }
- styleSourcesByInstanceId.set(instanceId, styleSources);
- }
- return {
- styleSourcesByInstanceId,
- };
- }
+const selectedStyleSourceIdStore = atom(
+ undefined
);
/**
@@ -236,6 +227,55 @@ export const selectedInstanceStore = computed(
);
export const selectedInstanceBrowserStyleStore = atom();
+export const selectedInstanceStyleSourcesStore = computed(
+ [
+ styleSourceSelectionsStore,
+ styleSourcesIndexStore,
+ selectedInstanceIdStore,
+ treeIdStore,
+ ],
+ (styleSourceSelections, styleSourcesIndex, selectedInstanceId, treeId) => {
+ const styleSourceIds =
+ styleSourceSelections.find(
+ (styleSourceSelection) =>
+ styleSourceSelection.instanceId === selectedInstanceId
+ )?.values ?? [];
+ const { styleSourcesById } = styleSourcesIndex;
+ const selectedInstanceStyleSources: StyleSources = [];
+ // generate style source when selection is empty or not exist
+ // it is synchronized whenever instance style sources
+ // or styles are updated
+ if (styleSourceIds.length === 0 && treeId !== undefined) {
+ selectedInstanceStyleSources.push({
+ type: "local",
+ treeId,
+ id: nanoid(),
+ });
+ }
+ for (const styleSourceId of styleSourceIds) {
+ const styleSource = styleSourcesById.get(styleSourceId);
+ if (styleSource !== undefined) {
+ selectedInstanceStyleSources.push(styleSource);
+ }
+ }
+ return selectedInstanceStyleSources;
+ }
+);
+
+/**
+ * Provide selected style source with fallback
+ * to local style source of selected instance
+ */
+export const selectedStyleSourceStore = computed(
+ [selectedInstanceStyleSourcesStore, selectedStyleSourceIdStore],
+ (styleSources, selectedStyleSourceId) => {
+ return (
+ styleSources.find((item) => item.id === selectedStyleSourceId) ??
+ styleSources.find((item) => item.type === "local")
+ );
+ }
+);
+
export const hoveredInstanceIdStore = atom(
undefined
);