From 5fbda47b41bb81dff233bdecb6a114b3773ba7bb Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Mon, 4 Nov 2024 10:36:53 +0400 Subject: [PATCH] refactor: migrate css preview to new style engine (#4353) Preview now managed by new style engine. ![Screenshot 2024-10-29 at 12 05 25](https://github.com/user-attachments/assets/3017247e-2fc6-4572-bb19-8309067a570d) --- .../features/navigator/css-preview.tsx | 81 ++++++------- .../props-section/props-section.tsx | 2 +- .../sections/advanced/advanced.tsx | 15 ++- .../features/style-panel/shared/model.tsx | 112 ++++++++++++------ apps/builder/package.json | 5 +- pnpm-lock.yaml | 61 +++++----- 6 files changed, 158 insertions(+), 118 deletions(-) diff --git a/apps/builder/app/builder/features/navigator/css-preview.tsx b/apps/builder/app/builder/features/navigator/css-preview.tsx index 6f9f4b0f63..68961f831b 100644 --- a/apps/builder/app/builder/features/navigator/css-preview.tsx +++ b/apps/builder/app/builder/features/navigator/css-preview.tsx @@ -1,14 +1,11 @@ +import { computed } from "nanostores"; +import { useStore } from "@nanostores/react"; import { ScrollArea, css, textVariants, theme, } from "@webstudio-is/design-system"; -// @todo feature should not import another feature -import { - type StyleInfo, - useStyleInfo, -} from "~/builder/features/style-panel/shared/style-info"; import { generateStyleMap, hyphenateProperty, @@ -16,9 +13,10 @@ import { } from "@webstudio-is/css-engine"; import type { StyleMap, StyleProperty } from "@webstudio-is/css-engine"; import { CollapsibleSection } from "~/builder/shared/collapsible-section"; -import { useMemo } from "react"; -import { captureError } from "@webstudio-is/error-utils"; import { highlightCss } from "~/builder/shared/code-highlight"; +import type { ComputedStyleDecl } from "~/shared/style-object-model"; +import { $selectedInstanceSelector } from "~/shared/nano-states"; +import { $definedComputedStyles } from "~/builder/features/style-panel/shared/model"; const preStyle = css(textVariants.mono, { margin: 0, @@ -27,43 +25,40 @@ const preStyle = css(textVariants.mono, { cursor: "text", }); -// - Compiles a CSS string from the style info +// - Compiles a CSS string from the style engine // - Groups by category and separates categories with comments -const getCssText = (currentStyle: StyleInfo) => { +const getCssText = ( + computedStyles: ComputedStyleDecl[], + instanceId: string +) => { const sourceStyles: StyleMap = new Map(); const inheritedStyles: StyleMap = new Map(); const cascadedStyles: StyleMap = new Map(); const presetStyles: StyleMap = new Map(); // Aggregate styles by category so we can group them when rendering. - let property: StyleProperty; - for (property in currentStyle) { - const value = currentStyle[property]; - property = hyphenateProperty(property) as StyleProperty; - if (value === undefined) { - continue; + for (const styleDecl of computedStyles) { + const property = hyphenateProperty(styleDecl.property) as StyleProperty; + let group; + if ( + styleDecl.source.name === "local" || + styleDecl.source.name === "overwritten" + ) { + group = sourceStyles; } - if (value.local ?? value.nextSource?.value ?? value.previousSource?.value) { - sourceStyles.set(property, value.value); - continue; + if (styleDecl.source.name === "remote") { + group = cascadedStyles; } - if (value.preset) { - presetStyles.set(property, value.value); - continue; + if (styleDecl.source.name === "preset") { + group = presetStyles; } - if (value.cascaded?.value) { - cascadedStyles.set(property, value.value); - continue; + if (group) { + if (styleDecl.source.instanceId === instanceId) { + group.set(property, styleDecl.cascadedValue); + } else { + inheritedStyles.set(property, styleDecl.cascadedValue); + } } - if (value.inherited?.value) { - inheritedStyles.set(property, value.value); - continue; - } - if (value.value) { - // Doesn't need handling - continue; - } - captureError(new Error("Unknown style source"), value); } const result: Array = []; @@ -84,25 +79,23 @@ const getCssText = (currentStyle: StyleInfo) => { return result.join("\n"); }; -const useHighlightedCss = () => { - const currentStyle = useStyleInfo(); - - return useMemo(() => { - if (Object.keys(currentStyle).length === 0) { +const $highlightedCss = computed( + [$selectedInstanceSelector, $definedComputedStyles], + (instanceSelector, computedStyles) => { + if (instanceSelector === undefined) { return; } - const cssText = getCssText(currentStyle); + const [instanceId] = instanceSelector; + const cssText = getCssText(computedStyles, instanceId); return highlightCss(cssText); - }, [currentStyle]); -}; + } +); export const CssPreview = () => { - const code = useHighlightedCss(); - + const code = useStore($highlightedCss); if (code === undefined) { return null; } - return ( diff --git a/apps/builder/app/builder/features/settings-panel/props-section/props-section.tsx b/apps/builder/app/builder/features/settings-panel/props-section/props-section.tsx index e682a46fb3..394c72d9d1 100644 --- a/apps/builder/app/builder/features/settings-panel/props-section/props-section.tsx +++ b/apps/builder/app/builder/features/settings-panel/props-section/props-section.tsx @@ -162,7 +162,7 @@ export const PropsSection = (props: PropsSectionProps) => { return ( <> - + {logic.systemProps.map((item) => renderProperty(props, item))} diff --git a/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx b/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx index fa66489084..c0d68b7620 100644 --- a/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx @@ -67,6 +67,8 @@ import { PropertyInfo } from "../../property-label"; import { sections } from "../sections"; import { ColorPopover } from "../../shared/color-picker"; import { + $instances, + $registeredComponentMetas, $selectedInstanceSelector, $styles, $styleSourceSelections, @@ -377,11 +379,20 @@ const initialProperties = new Set([ const $advancedProperties = computed( [ $selectedInstanceSelector, + $instances, + $registeredComponentMetas, $styleSourceSelections, $matchingBreakpoints, $styles, ], - (instanceSelector, styleSourceSelections, matchingBreakpoints, styles) => { + ( + instanceSelector, + instances, + metas, + styleSourceSelections, + matchingBreakpoints, + styles + ) => { if (instanceSelector === undefined) { return []; } @@ -393,6 +404,8 @@ const $advancedProperties = computed( instanceSelector; const definedStyles = getDefinedStyles({ instanceSelector: instanceAndRootSelector, + instances, + metas, matchingBreakpoints, styleSourceSelections, styles, diff --git a/apps/builder/app/builder/features/style-panel/shared/model.tsx b/apps/builder/app/builder/features/style-panel/shared/model.tsx index 3d1dff30d2..77a75e1f3a 100644 --- a/apps/builder/app/builder/features/style-panel/shared/model.tsx +++ b/apps/builder/app/builder/features/style-panel/shared/model.tsx @@ -10,6 +10,7 @@ import { type VarValue, } from "@webstudio-is/css-engine"; import { + Instances, ROOT_INSTANCE_ID, Styles, StyleSourceSelections, @@ -17,7 +18,7 @@ import { type Instance, type StyleDecl, } from "@webstudio-is/sdk"; -import { rootComponent } from "@webstudio-is/react-sdk"; +import { rootComponent, WsComponentMeta } from "@webstudio-is/react-sdk"; import { $breakpoints, $instances, @@ -96,20 +97,36 @@ export const $matchingBreakpoints = computed( export const getDefinedStyles = ({ instanceSelector, + instances, + metas, matchingBreakpoints: matchingBreakpointsArray, styleSourceSelections, styles, }: { instanceSelector: InstanceSelector; + instances: Instances; + metas: Map; matchingBreakpoints: Breakpoint["id"][]; styleSourceSelections: StyleSourceSelections; styles: Styles; }) => { - const definedStyles = new Set(); + const definedStyles = new Set<{ + property: StyleProperty; + listed?: boolean; + }>(); const inheritedStyleSources = new Set(); const instanceStyleSources = new Set(); const matchingBreakpoints = new Set(matchingBreakpointsArray); for (const instanceId of instanceSelector) { + const instance = instances.get(instanceId); + const meta = instance?.component + ? metas.get(instance.component) + : undefined; + for (const presetStyles of Object.values(meta?.presetStyle ?? {})) { + for (const styleDecl of presetStyles) { + definedStyles.add(styleDecl); + } + } const styleSources = styleSourceSelections.get(instanceId)?.values; if (styleSources) { for (const styleSourceId of styleSources) { @@ -143,23 +160,43 @@ export const getDefinedStyles = ({ return definedStyles; }; +const $instanceAndRootSelector = computed( + $selectedInstanceSelector, + (instanceSelector) => { + if (instanceSelector === undefined) { + return; + } + if (instanceSelector[0] === ROOT_INSTANCE_ID) { + return instanceSelector; + } + return [...instanceSelector, ROOT_INSTANCE_ID]; + } +); + export const $definedStyles = computed( [ - $selectedInstanceSelector, + $instanceAndRootSelector, + $instances, + $registeredComponentMetas, $styleSourceSelections, $matchingBreakpoints, $styles, ], - (instanceSelector, styleSourceSelections, matchingBreakpoints, styles) => { + ( + instanceSelector, + instances, + metas, + styleSourceSelections, + matchingBreakpoints, + styles + ) => { if (instanceSelector === undefined) { return new Set(); } - const instanceAndRootSelector = - instanceSelector[0] === ROOT_INSTANCE_ID - ? instanceSelector - : [...instanceSelector, ROOT_INSTANCE_ID]; return getDefinedStyles({ - instanceSelector: instanceAndRootSelector, + instanceSelector, + instances, + metas, matchingBreakpoints, styleSourceSelections, styles, @@ -198,20 +235,7 @@ const $model = computed( } ); -const $instanceAndRootSelector = computed( - $selectedInstanceSelector, - (instanceSelector) => { - if (instanceSelector === undefined) { - return; - } - if (instanceSelector[0] === ROOT_INSTANCE_ID) { - return instanceSelector; - } - return [...instanceSelector, ROOT_INSTANCE_ID]; - } -); - -export const $availableVariables = computed( +export const $definedComputedStyles = computed( [ $definedStyles, $model, @@ -219,25 +243,39 @@ export const $availableVariables = computed( $selectedOrLastStyleSourceSelector, ], (definedStyles, model, instanceSelector, styleSourceSelector) => { - const availableVariables = new Map(); + const computedStyles = new Map(); for (const { property } of definedStyles) { - if (property.startsWith("--")) { - const { computedValue } = getComputedStyleDecl({ - model, - instanceSelector, - styleSourceId: styleSourceSelector?.styleSourceId, - state: styleSourceSelector?.state, - property, - }); - // deduplicate by property name - availableVariables.set(property, { + // deduplicate by property name + if (computedStyles.has(property)) { + continue; + } + const computedStyleDecl = getComputedStyleDecl({ + model, + instanceSelector, + styleSourceId: styleSourceSelector?.styleSourceId, + state: styleSourceSelector?.state, + property, + }); + computedStyles.set(property, computedStyleDecl); + } + return Array.from(computedStyles.values()); + } +); + +export const $availableVariables = computed( + $definedComputedStyles, + (computedStyles) => { + const availableVariables: VarValue[] = []; + for (const styleDecl of computedStyles) { + if (styleDecl.property.startsWith("--")) { + availableVariables.push({ type: "var", - value: property.slice(2), - fallback: toVarFallback(computedValue), + value: styleDecl.property.slice(2), + fallback: toVarFallback(styleDecl.computedValue), }); } } - return Array.from(availableVariables.values()); + return availableVariables; } ); diff --git a/apps/builder/package.json b/apps/builder/package.json index 117b4c4200..21744d306a 100644 --- a/apps/builder/package.json +++ b/apps/builder/package.json @@ -18,7 +18,7 @@ "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "^1.3.1", "@codemirror/autocomplete": "^6.18.1", - "@codemirror/commands": "^6.7.0", + "@codemirror/commands": "^6.7.1", "@codemirror/lang-css": "^6.3.0", "@codemirror/lang-html": "^6.4.9", "@codemirror/lang-javascript": "^6.2.2", @@ -36,7 +36,7 @@ "@lexical/react": "^0.16.0", "@lexical/selection": "^0.16.0", "@lexical/utils": "^0.16.0", - "@lezer/common": "^1.2.2", + "@lezer/common": "^1.2.3", "@lezer/css": "^1.1.9", "@lezer/highlight": "^1.2.1", "@nanostores/react": "^0.7.1", @@ -60,7 +60,6 @@ "@webstudio-is/dashboard": "workspace:*", "@webstudio-is/design-system": "workspace:*", "@webstudio-is/domain": "workspace:*", - "@webstudio-is/error-utils": "workspace:*", "@webstudio-is/feature-flags": "workspace:*", "@webstudio-is/fonts": "workspace:*", "@webstudio-is/http-client": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2cc1c4d4c..51b472385c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,10 +126,10 @@ importers: version: 1.3.1 '@codemirror/autocomplete': specifier: ^6.18.1 - version: 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.2) + version: 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3) '@codemirror/commands': - specifier: ^6.7.0 - version: 6.7.0 + specifier: ^6.7.1 + version: 6.7.1 '@codemirror/lang-css': specifier: ^6.3.0 version: 6.3.0(@codemirror/view@6.34.1) @@ -182,8 +182,8 @@ importers: specifier: ^0.16.0 version: 0.16.0 '@lezer/common': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.2.3 + version: 1.2.3 '@lezer/css': specifier: ^1.1.9 version: 1.1.9 @@ -253,9 +253,6 @@ importers: '@webstudio-is/domain': specifier: workspace:* version: link:../../packages/domain - '@webstudio-is/error-utils': - specifier: workspace:* - version: link:../../packages/error-utils '@webstudio-is/feature-flags': specifier: workspace:* version: link:../../packages/feature-flags @@ -2682,8 +2679,8 @@ packages: '@codemirror/view': ^6.0.0 '@lezer/common': ^1.0.0 - '@codemirror/commands@6.7.0': - resolution: {integrity: sha512-+cduIZ2KbesDhbykV02K25A5xIVrquSPz4UxxYBemRlAT2aW8dhwUgLDwej7q/RJUHKk4nALYcR1puecDvbdqw==} + '@codemirror/commands@6.7.1': + resolution: {integrity: sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==} '@codemirror/lang-css@6.3.0': resolution: {integrity: sha512-CyR4rUNG9OYcXDZwMPvJdtb6PHbBDKUc/6Na2BIwZ6dKab1JQqKa4di+RNRY9Myn7JB81vayKwJeQ7jEdmNVDA==} @@ -3831,8 +3828,8 @@ packages: peerDependencies: yjs: '>=13.5.22' - '@lezer/common@1.2.2': - resolution: {integrity: sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw==} + '@lezer/common@1.2.3': + resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} '@lezer/css@1.1.9': resolution: {integrity: sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==} @@ -10302,67 +10299,67 @@ snapshots: '@cloudflare/workers-types@4.20240701.0': {} - '@codemirror/autocomplete@6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.2)': + '@codemirror/autocomplete@6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3)': dependencies: '@codemirror/language': 6.10.3 '@codemirror/state': 6.4.1 '@codemirror/view': 6.34.1 - '@lezer/common': 1.2.2 + '@lezer/common': 1.2.3 - '@codemirror/commands@6.7.0': + '@codemirror/commands@6.7.1': dependencies: '@codemirror/language': 6.10.3 '@codemirror/state': 6.4.1 '@codemirror/view': 6.34.1 - '@lezer/common': 1.2.2 + '@lezer/common': 1.2.3 '@codemirror/lang-css@6.3.0(@codemirror/view@6.34.1)': dependencies: - '@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.2) + '@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3) '@codemirror/language': 6.10.3 '@codemirror/state': 6.4.1 - '@lezer/common': 1.2.2 + '@lezer/common': 1.2.3 '@lezer/css': 1.1.9 transitivePeerDependencies: - '@codemirror/view' '@codemirror/lang-html@6.4.9': dependencies: - '@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.2) + '@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3) '@codemirror/lang-css': 6.3.0(@codemirror/view@6.34.1) '@codemirror/lang-javascript': 6.2.2 '@codemirror/language': 6.10.3 '@codemirror/state': 6.4.1 '@codemirror/view': 6.34.1 - '@lezer/common': 1.2.2 + '@lezer/common': 1.2.3 '@lezer/css': 1.1.9 '@lezer/html': 1.3.10 '@codemirror/lang-javascript@6.2.2': dependencies: - '@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.2) + '@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3) '@codemirror/language': 6.10.3 '@codemirror/lint': 6.8.2 '@codemirror/state': 6.4.1 '@codemirror/view': 6.34.1 - '@lezer/common': 1.2.2 + '@lezer/common': 1.2.3 '@lezer/javascript': 1.4.19 '@codemirror/lang-markdown@6.3.0': dependencies: - '@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.2) + '@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.3) '@codemirror/lang-html': 6.4.9 '@codemirror/language': 6.10.3 '@codemirror/state': 6.4.1 '@codemirror/view': 6.34.1 - '@lezer/common': 1.2.2 + '@lezer/common': 1.2.3 '@lezer/markdown': 1.3.1 '@codemirror/language@6.10.3': dependencies: '@codemirror/state': 6.4.1 '@codemirror/view': 6.34.1 - '@lezer/common': 1.2.2 + '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 style-mod: 4.1.2 @@ -11265,37 +11262,37 @@ snapshots: lexical: 0.16.0 yjs: 13.5.52 - '@lezer/common@1.2.2': {} + '@lezer/common@1.2.3': {} '@lezer/css@1.1.9': dependencies: - '@lezer/common': 1.2.2 + '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 '@lezer/highlight@1.2.1': dependencies: - '@lezer/common': 1.2.2 + '@lezer/common': 1.2.3 '@lezer/html@1.3.10': dependencies: - '@lezer/common': 1.2.2 + '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 '@lezer/javascript@1.4.19': dependencies: - '@lezer/common': 1.2.2 + '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 '@lezer/lr@1.4.2': dependencies: - '@lezer/common': 1.2.2 + '@lezer/common': 1.2.3 '@lezer/markdown@1.3.1': dependencies: - '@lezer/common': 1.2.2 + '@lezer/common': 1.2.3 '@lezer/highlight': 1.2.1 '@mdx-js/mdx@2.3.0':