From f67e2e3778a2e6ed8dfe12e14f7365eaa9885c96 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Wed, 28 Aug 2024 17:43:25 +0300 Subject: [PATCH] refactor: decouple style updates from react (#4021) Removed setProperty, deleteProperty and createBatchUpdates from react hook. All parameters can be accessed from global stores. Note: review with hidden spaces. --- .../builder/features/inspector/inspector.tsx | 2 +- .../features/style-panel/property-label.tsx | 7 +- .../style-panel/sections/layout/layout.tsx | 12 +- .../style-panel/shared/use-style-data.ts | 252 ++++++++---------- .../features/style-panel/style-panel.tsx | 20 +- 5 files changed, 129 insertions(+), 164 deletions(-) diff --git a/apps/builder/app/builder/features/inspector/inspector.tsx b/apps/builder/app/builder/features/inspector/inspector.tsx index e2eaaea9cb..eb355c73a3 100644 --- a/apps/builder/app/builder/features/inspector/inspector.tsx +++ b/apps/builder/app/builder/features/inspector/inspector.tsx @@ -163,7 +163,7 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => { - + { - const instanceSelector = useStore($selectedInstanceSelector); - const { createBatchUpdate } = useStyleData(instanceSelector?.[0] ?? ""); const $styles = useMemo(() => { return computed( properties.map(createComputedStyleDeclStore), @@ -253,8 +250,6 @@ export const PropertySectionLabel = ({ description: string; properties: [StyleProperty, ...StyleProperty[]]; }) => { - const instanceSelector = useStore($selectedInstanceSelector); - const { createBatchUpdate } = useStyleData(instanceSelector?.[0] ?? ""); const $styles = useMemo(() => { return computed( properties.map(createComputedStyleDeclStore), diff --git a/apps/builder/app/builder/features/style-panel/sections/layout/layout.tsx b/apps/builder/app/builder/features/style-panel/sections/layout/layout.tsx index 7bafa77428..b25d5dfecd 100644 --- a/apps/builder/app/builder/features/style-panel/sections/layout/layout.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/layout/layout.tsx @@ -374,16 +374,8 @@ const LayoutSectionFlex = ({ { publish: (options?: StyleUpdateOptions) => void; }; -export const useStyleData = (selectedInstanceId: Instance["id"]) => { - const selectedBreakpoint = useStore($selectedBreakpoint); +const publishUpdates = ( + type: "update" | "preview", + updates: StyleUpdates["updates"], + options: StyleUpdateOptions +) => { + if (updates.length === 0) { + return; + } - const currentStyle = useStyleInfo(); + const selectedInstanceSelector = $selectedInstanceSelector.get(); + const selectedInstanceId = selectedInstanceSelector?.[0]; + const selectedBreakpoint = $selectedBreakpoint.get(); + const selectedStyleSource = $selectedStyleSource.get(); + const styleSourceSelector = $selectedOrLastStyleSourceSelector.get(); - const publishUpdates = useCallback( - ( - type: "update" | "preview", - updates: StyleUpdates["updates"], - options: StyleUpdateOptions - ) => { - const selectedStyleSource = $selectedStyleSource.get(); - const styleSourceSelector = $selectedOrLastStyleSourceSelector.get(); + if ( + selectedInstanceId === undefined || + selectedBreakpoint === undefined || + selectedStyleSource === undefined || + styleSourceSelector === undefined + ) { + return; + } + if (type === "preview") { + const ephemeralStyles: ReturnType = []; + for (const update of updates) { + if (update.operation === "set") { + ephemeralStyles.push({ + breakpointId: selectedBreakpoint.id, + styleSourceId: styleSourceSelector.styleSourceId, + state: styleSourceSelector.state, + property: update.property, + value: update.value, + listed: options.listed, + }); + } + } + $ephemeralStyles.set(ephemeralStyles); + return; + } + + $ephemeralStyles.set([]); + serverSyncStore.createTransaction( + [$styleSourceSelections, $styleSources, $styles], + (styleSourceSelections, styleSources, styles) => { + const instanceId = selectedInstanceId; + const breakpointId = selectedBreakpoint.id; + // set only selected style source and update selection with it + // generated local style source will not be written if not selected + styleSources.set(selectedStyleSource.id, selectedStyleSource); + const selectionValues = + styleSourceSelections.get(instanceId)?.values ?? []; if ( - updates.length === 0 || - selectedBreakpoint === undefined || - selectedStyleSource === undefined || - styleSourceSelector === undefined + selectionValues.includes(styleSourceSelector.styleSourceId) === false ) { - return; + styleSourceSelections.set(instanceId, { + instanceId, + values: [...selectionValues, styleSourceSelector.styleSourceId], + }); } - if (type === "preview") { - const ephemeralStyles: ReturnType = []; - for (const update of updates) { - if (update.operation === "set") { - ephemeralStyles.push({ - breakpointId: selectedBreakpoint.id, - styleSourceId: styleSourceSelector.styleSourceId, - state: styleSourceSelector.state, - property: update.property, - value: update.value, - listed: options.listed, - }); - } + for (const update of updates) { + if (update.operation === "set") { + const styleDecl: StyleDecl = { + breakpointId, + styleSourceId: styleSourceSelector.styleSourceId, + state: styleSourceSelector.state, + property: update.property, + value: update.value, + listed: options.listed, + }; + styles.set(getStyleDeclKey(styleDecl), styleDecl); + } + + if (update.operation === "delete") { + const styleDeclKey = getStyleDeclKey({ + breakpointId, + styleSourceId: styleSourceSelector.styleSourceId, + state: styleSourceSelector.state, + property: update.property, + }); + styles.delete(styleDeclKey); } - $ephemeralStyles.set(ephemeralStyles); - return; } - - $ephemeralStyles.set([]); - serverSyncStore.createTransaction( - [$styleSourceSelections, $styleSources, $styles], - (styleSourceSelections, styleSources, styles) => { - const instanceId = selectedInstanceId; - const breakpointId = selectedBreakpoint.id; - // set only selected style source and update selection with it - // generated local style source will not be written if not selected - styleSources.set(selectedStyleSource.id, selectedStyleSource); - const selectionValues = - styleSourceSelections.get(instanceId)?.values ?? []; - if ( - selectionValues.includes(styleSourceSelector.styleSourceId) === - false - ) { - styleSourceSelections.set(instanceId, { - instanceId, - values: [...selectionValues, styleSourceSelector.styleSourceId], - }); - } - - for (const update of updates) { - if (update.operation === "set") { - const styleDecl: StyleDecl = { - breakpointId, - styleSourceId: styleSourceSelector.styleSourceId, - state: styleSourceSelector.state, - property: update.property, - value: update.value, - listed: options.listed, - }; - styles.set(getStyleDeclKey(styleDecl), styleDecl); - } - - if (update.operation === "delete") { - const styleDeclKey = getStyleDeclKey({ - breakpointId, - styleSourceId: styleSourceSelector.styleSourceId, - state: styleSourceSelector.state, - property: update.property, - }); - styles.delete(styleDeclKey); - } - } - } - ); - }, - [selectedBreakpoint, selectedInstanceId] + } ); +}; - const setProperty = useCallback( - (property) => { - return (value, options: StyleUpdateOptions = { isEphemeral: false }) => { - if (value.type !== "invalid") { - const updates = [{ operation: "set" as const, property, value }]; - const type = options.isEphemeral ? "preview" : "update"; - - publishUpdates(type, updates, options); - } - }; - }, - [publishUpdates] - ); - - const deleteProperty = useCallback( - ( - property: StyleProperty, - options: StyleUpdateOptions = { isEphemeral: false } - ) => { - const updates = [{ operation: "delete" as const, property }]; +export const setProperty: SetProperty = (property) => { + return (value, options: StyleUpdateOptions = { isEphemeral: false }) => { + if (value.type !== "invalid") { + const updates = [{ operation: "set" as const, property, value }]; const type = options.isEphemeral ? "preview" : "update"; + publishUpdates(type, updates, options); - }, - [publishUpdates] - ); + } + }; +}; - const createBatchUpdate = useCallback(() => { - let updates: StyleUpdates["updates"] = []; +export const deleteProperty = ( + property: StyleProperty, + options: StyleUpdateOptions = { isEphemeral: false } +) => { + const updates = [{ operation: "delete" as const, property }]; + const type = options.isEphemeral ? "preview" : "update"; + publishUpdates(type, updates, options); +}; - const setProperty = (property: StyleProperty) => { - const setValue = (value: StyleValue) => { - if (value.type === "invalid") { - return; - } +export const createBatchUpdate = () => { + let updates: StyleUpdates["updates"] = []; - updates.push({ operation: "set", property, value }); - }; - return setValue; - }; + const setProperty = (property: StyleProperty) => { + const setValue = (value: StyleValue) => { + if (value.type === "invalid") { + return; + } - const deleteProperty = (property: StyleProperty) => { - updates.push({ operation: "delete", property }); + updates.push({ operation: "set", property, value }); }; + return setValue; + }; - const publish = (options: StyleUpdateOptions = { isEphemeral: false }) => { - if (!updates.length) { - return; - } - const type = options.isEphemeral ? "preview" : "update"; - publishUpdates(type, updates, options); - updates = []; - }; + const deleteProperty = (property: StyleProperty) => { + updates.push({ operation: "delete", property }); + }; - return { - setProperty, - deleteProperty, - publish, - }; - }, [publishUpdates]); + const publish = (options: StyleUpdateOptions = { isEphemeral: false }) => { + if (!updates.length) { + return; + } + const type = options.isEphemeral ? "preview" : "update"; + publishUpdates(type, updates, options); + updates = []; + }; return { - currentStyle, setProperty, deleteProperty, - createBatchUpdate, + publish, }; }; diff --git a/apps/builder/app/builder/features/style-panel/style-panel.tsx b/apps/builder/app/builder/features/style-panel/style-panel.tsx index e2b35c5a0b..153be69397 100644 --- a/apps/builder/app/builder/features/style-panel/style-panel.tsx +++ b/apps/builder/app/builder/features/style-panel/style-panel.tsx @@ -6,13 +6,14 @@ import { Separator, ScrollArea, } from "@webstudio-is/design-system"; -import type { Instance } from "@webstudio-is/sdk"; import { useStore } from "@nanostores/react"; import { computed } from "nanostores"; import type { HtmlTags } from "html-tags"; - -import { useStyleData } from "./shared/use-style-data"; - +import { + setProperty, + deleteProperty, + createBatchUpdate, +} from "./shared/use-style-data"; import { StyleSourcesSection } from "./style-source-section"; import { $selectedInstanceRenderState } from "~/shared/nano-states"; import { @@ -21,7 +22,7 @@ import { } from "~/shared/nano-states"; import { sections } from "./sections"; import { useParentStyle } from "./parent-style"; -import type { StyleInfo } from "./shared/style-info"; +import { useStyleInfo, type StyleInfo } from "./shared/style-info"; import { toValue } from "@webstudio-is/css-engine"; const $selectedInstanceTag = computed( @@ -48,13 +49,8 @@ const shouldRenderCategory = ( return true; }; -type StylePanelProps = { - selectedInstance: Instance; -}; - -export const StylePanel = ({ selectedInstance }: StylePanelProps) => { - const { currentStyle, setProperty, deleteProperty, createBatchUpdate } = - useStyleData(selectedInstance.id); +export const StylePanel = () => { + const currentStyle = useStyleInfo(); const selectedInstanceRenderState = useStore($selectedInstanceRenderState); const selectedInstanceTag = useStore($selectedInstanceTag);