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.
This commit is contained in:
Bogdan Chadkin authored and GitHub committed 2024-08-28 16:43:25 +02:00
1 parent 1643fcde54
commit f67e2e3778
5 files changed
+129 -164

No files matched your search

@@ -163,7 +163,7 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => {
<Separator />
<PanelTabsContent value="style" css={contentStyle} tabIndex={-1}>
<InstanceInfo instance={selectedInstance} />
<StylePanel selectedInstance={selectedInstance} />
<StylePanel />
</PanelTabsContent>
<PanelTabsContent
value="settings"
@@ -21,7 +21,6 @@ import {
$breakpoints,
$instances,
$registeredComponentMetas,
$selectedInstanceSelector,
$styleSources,
} from "~/shared/nano-states";
import { getInstanceLabel } from "~/shared/instance-utils";
@@ -31,7 +30,7 @@ import type {
} from "~/shared/style-object-model";
import { createComputedStyleDeclStore } from "./shared/model";
import { StyleSourceBadge } from "./style-source";
import { useStyleData } from "./shared/use-style-data";
import { createBatchUpdate } from "./shared/use-style-data";
export const PropertyInfo = ({
title,
@@ -185,8 +184,6 @@ export const PropertyLabel = ({
description: string;
properties: [StyleProperty, ...StyleProperty[]];
}) => {
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),
@@ -374,16 +374,8 @@ const LayoutSectionFlex = ({
<ToggleControl
property="flexWrap"
items={[
{
name: "nowrap",
label: "No Wrap",
icon: NoWrapIcon,
},
{
name: "wrap",
label: "Wrap",
icon: WrapIcon,
},
{ name: "nowrap", label: "No Wrap", icon: NoWrapIcon },
{ name: "wrap", label: "Wrap", icon: WrapIcon },
]}
currentStyle={currentStyle}
setProperty={setProperty}
@@ -1,5 +1,3 @@
import { useCallback } from "react";
import { useStore } from "@nanostores/react";
import {
type Breakpoint,
type Instance,
@@ -9,13 +7,13 @@ import {
import type { StyleProperty, StyleValue } from "@webstudio-is/css-engine";
import {
$selectedBreakpoint,
$selectedInstanceSelector,
$selectedOrLastStyleSourceSelector,
$selectedStyleSource,
$styleSourceSelections,
$styleSources,
$styles,
} from "~/shared/nano-states";
import { useStyleInfo } from "./style-info";
import { serverSyncStore } from "~/shared/sync";
import { $ephemeralStyles } from "~/canvas/stores";
@@ -57,161 +55,145 @@ export type CreateBatchUpdate = () => {
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<typeof $ephemeralStyles.get> = [];
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<typeof $ephemeralStyles.get> = [];
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<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 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,
};
};
@@ -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);