Switch to normalized props on server (#883)

Ref https://github.com/webstudio-is/webstudio-designer/issues/696

Now all props is migrated on client side, we can drop legacy store and
switch to new format server side.
This commit is contained in:
Bogdan Chadkin authored and GitHub committed 2023-01-28 15:24:23 +03:00
1 parent ff049a000c
commit 8635dc6755
11 files changed
+50 -146

No files matched your search

+6 -11
View File
@@ -1,13 +1,9 @@
import { useEffect } from "react";
import type { Instance, PropsItem } from "@webstudio-is/project-build";
import {
getComponentMeta,
allUserPropsContainer,
} from "@webstudio-is/react-sdk";
import { useSubscribe } from "~/shared/pubsub";
import { utils, type InstanceInsertionSpec } from "@webstudio-is/project";
import store from "immerhin";
import type { Instance, PropsItem } from "@webstudio-is/project-build";
import { getComponentMeta } from "@webstudio-is/react-sdk";
import { utils, type InstanceInsertionSpec } from "@webstudio-is/project";
import { useSubscribe } from "~/shared/pubsub";
import {
propsStore,
rootInstanceContainer,
@@ -64,8 +60,8 @@ export const useInsertInstance = () => {
({ instance, dropTarget, props: insertedProps }) => {
const selectedInstanceId = selectedInstanceIdStore.get();
store.createTransaction(
[rootInstanceContainer, allUserPropsContainer, propsStore],
(rootInstance, allUserProps, props) => {
[rootInstanceContainer, propsStore],
(rootInstance, props) => {
if (rootInstance === undefined) {
return;
}
@@ -78,7 +74,6 @@ export const useInsertInstance = () => {
selectedInstanceIdStore.set(instance.id);
}
if (insertedProps !== undefined) {
allUserProps[instance.id] = insertedProps;
props.push(...insertedProps);
}
}
@@ -1,9 +1,7 @@
import type { ComponentStory } from "@storybook/react";
import type { PropsItem } from "@webstudio-is/project-build";
import {
allUserPropsContainer,
getComponentMetaProps,
} from "@webstudio-is/react-sdk";
import { getComponentMetaProps } from "@webstudio-is/react-sdk";
import { propsStore } from "~/shared/nano-states";
import { PropsPanel } from "./props-panel";
export default {
@@ -12,17 +10,15 @@ export default {
};
export const NoProps: ComponentStory<typeof PropsPanel> = () => {
allUserPropsContainer.set({
"1": [
{
id: "disabled",
instanceId: "instanceId",
name: "disabled",
type: "boolean",
value: true,
},
],
});
propsStore.set([
{
id: "disabled",
instanceId: "1",
name: "disabled",
type: "boolean",
value: true,
},
]);
return (
<PropsPanel
selectedInstance={{
@@ -38,9 +34,7 @@ export const NoProps: ComponentStory<typeof PropsPanel> = () => {
};
export const RequiredProps: ComponentStory<typeof PropsPanel> = () => {
allUserPropsContainer.set({
"1": [],
});
propsStore.set([]);
return (
<PropsPanel
selectedInstance={{
@@ -56,9 +50,7 @@ export const RequiredProps: ComponentStory<typeof PropsPanel> = () => {
};
export const DefaultProps: ComponentStory<typeof PropsPanel> = () => {
allUserPropsContainer.set({
"1": [],
});
propsStore.set([]);
return (
<PropsPanel
selectedInstance={{
@@ -76,15 +68,16 @@ export const DefaultProps: ComponentStory<typeof PropsPanel> = () => {
const meta = getComponentMetaProps("Button") ?? {};
export const AllProps: ComponentStory<typeof PropsPanel> = () => {
allUserPropsContainer.set({
"3": Object.entries(meta).map(([name, value]) => {
propsStore.set(
Object.entries(meta).map(([name, value]) => {
return {
id: name,
instanceId: "3",
name,
value: value?.defaultValue ?? "",
} as PropsItem;
}),
});
})
);
return (
<PropsPanel
selectedInstance={{
@@ -1,14 +1,7 @@
import { useState } from "react";
import store from "immerhin";
import type { Instance, PropsItem } from "@webstudio-is/project-build";
import {
allUserPropsContainer,
getComponentMetaProps,
} from "@webstudio-is/react-sdk";
import { type Publish } from "~/shared/pubsub";
import { propsStore, useInstanceProps } from "~/shared/nano-states";
import { Control } from "./control";
import { CollapsibleSection, ComponentInfo } from "~/designer/shared/inspector";
import { getComponentMetaProps } from "@webstudio-is/react-sdk";
import {
theme,
Box,
@@ -30,10 +23,14 @@ import {
ExclamationTriangleIcon,
ChevronDownIcon,
} from "@webstudio-is/icons";
import { type Publish } from "~/shared/pubsub";
import { propsStore, useInstanceProps } from "~/shared/nano-states";
import { CollapsibleSection, ComponentInfo } from "~/designer/shared/inspector";
import {
removeByMutable,
replaceByOrAppendMutable,
} from "~/shared/array-utils";
import { Control } from "./control";
import { usePropsLogic, type UserPropValue } from "./use-props-logic";
import {
useStyleData,
@@ -236,39 +233,19 @@ export const PropsPanel = ({ selectedInstance, publish }: PropsPanelProps) => {
selectedInstance,
updateProps: (update) => {
store.createTransaction(
[allUserPropsContainer, propsStore],
(allUserProps, props) => {
let instanceProps = allUserProps[instanceId];
if (instanceProps === undefined) {
instanceProps = [];
allUserProps[instanceId] = instanceProps;
}
replaceByOrAppendMutable(
instanceProps,
update,
(item) => item.id === update.id
);
replaceByOrAppendMutable(
props,
update,
(item) => item.id === update.id
);
}
);
store.createTransaction([propsStore], (props) => {
replaceByOrAppendMutable(
props,
update,
(item) => item.id === update.id
);
});
},
deleteProp: (id) => {
store.createTransaction(
[allUserPropsContainer, propsStore],
(allUserProps, props) => {
const instanceProps = allUserProps[instanceId];
if (instanceProps) {
removeByMutable(instanceProps, (prop) => prop.id === id);
}
removeByMutable(props, (prop) => prop.id === id);
}
);
store.createTransaction([propsStore], (props) => {
removeByMutable(props, (prop) => prop.id === id);
});
},
});
+1 -3
View File
@@ -24,9 +24,8 @@ export const loadCanvasData = async (
throw new Error(`Page ${pageIdOrPath} not found`);
}
const [tree, props, breakpoints, designTokens, assets] = await Promise.all([
const [tree, breakpoints, designTokens, assets] = await Promise.all([
projectDb.tree.loadById(page.treeId),
projectDb.props.loadByTreeId(page.treeId),
projectDb.breakpoints.load(build.id),
designTokensDb.load(build.id),
loadByProject(project.id),
@@ -42,7 +41,6 @@ export const loadCanvasData = async (
return {
tree,
props,
breakpoints: breakpoints.values,
designTokens,
buildId: build.id,
+1 -4
View File
@@ -1,7 +1,6 @@
import store, { type Change } from "immerhin";
import type { WritableAtom } from "nanostores";
import { useEffect } from "react";
import { allUserPropsContainer } from "@webstudio-is/react-sdk";
import { type Publish, subscribe } from "~/shared/pubsub";
import {
rootInstanceContainer,
@@ -44,8 +43,7 @@ export const registerContainers = () => {
store.register("breakpoints", breakpointsContainer);
store.register("root", rootInstanceContainer);
store.register("styles", stylesContainer);
store.register("props", allUserPropsContainer);
store.register("props-modern", propsStore);
store.register("props", propsStore);
store.register("designTokens", designTokensContainer);
// synchronize whole states
clientStores.set("selectedInstanceId", selectedInstanceIdStore);
@@ -143,7 +141,6 @@ const syncStoresState = (name: SyncEventSource, publish: Publish) => {
],
},
});
//
})
);
}
+9 -46
View File
@@ -1,6 +1,5 @@
import warnOnce from "warn-once";
import { type Tree, StoredProps, Props } from "@webstudio-is/project-build";
import { type AllUserProps, type InstanceProps } from "@webstudio-is/react-sdk";
import { applyPatches, type Patch } from "immer";
import { prisma } from "@webstudio-is/prisma-client";
import { formatAsset } from "@webstudio-is/asset-uploader/server";
@@ -59,35 +58,6 @@ export const parseProps = async (propsString: string) => {
return props;
};
export const loadByTreeId = async (treeId: Tree["id"]) => {
const tree = await prisma.tree.findUnique({
where: { id: treeId },
});
if (tree === null) {
return [];
}
const props = await parseProps(tree.props);
const instancePropsMap = new Map<string, InstanceProps>();
for (const propsItem of props) {
let instanceProps = instancePropsMap.get(propsItem.instanceId);
if (instanceProps === undefined) {
instanceProps = {
id: "",
instanceId: propsItem.instanceId,
treeId,
props: [],
};
instancePropsMap.set(propsItem.instanceId, instanceProps);
}
instanceProps.props.push(propsItem);
}
return Array.from(instancePropsMap.values());
};
export const serializeProps = (props: Props) => {
const storedProps: StoredProps = [];
for (const prop of props) {
@@ -111,25 +81,18 @@ export const patch = async (
{ treeId }: { treeId: Tree["id"] },
patches: Array<Patch>
) => {
const allProps = await loadByTreeId(treeId);
// We should get rid of this by applying patches on the client to the original
// model instead of the instanceId map.
// The map is handy for accessing props, but probably should be just cached interface, not the main data structure.
const allPropsMapByInstanceId = allProps.reduce((acc, prop) => {
acc[prop.instanceId] = prop.props;
return acc;
}, {} as AllUserProps);
const nextProps = applyPatches<AllUserProps>(
allPropsMapByInstanceId,
patches
);
const props: Props = Object.values(nextProps).flat();
const tree = await prisma.tree.findUnique({
where: { id: treeId },
});
if (tree === null) {
return;
}
const props = await parseProps(tree.props);
const patchedProps = applyPatches(props, patches);
await prisma.tree.update({
data: {
props: serializeProps(props),
props: serializeProps(patchedProps),
},
where: { id: treeId },
});
-1
View File
@@ -1 +0,0 @@
export * from "./types";
-6
View File
@@ -1,6 +0,0 @@
import type { InstanceProps as DbInstanceProps } from "@webstudio-is/prisma-client";
import type { PropsItem } from "@webstudio-is/project-build";
export type InstanceProps = Omit<DbInstanceProps, "props"> & {
props: Array<PropsItem>;
};
-2
View File
@@ -1,9 +1,7 @@
export * from "./css";
export * from "./tree";
export * from "./components";
export * from "./user-props/all-user-props";
export * from "./pubsub";
export * from "./db";
export * from "./app";
export {
customComponents,
-2
View File
@@ -4,7 +4,6 @@ import type { Breakpoint } from "@webstudio-is/css-data";
import type { DesignToken } from "@webstudio-is/design-tokens";
import type { Tree } from "@webstudio-is/project-build";
import type { Asset } from "@webstudio-is/asset-uploader";
import type { InstanceProps } from "../db";
import { createElementsTree } from "./create-elements-tree";
import { WrapperComponent } from "./wrapper-component";
import { registerComponents } from "../components";
@@ -16,7 +15,6 @@ export type Data = {
tree: Tree | null;
breakpoints: Array<Breakpoint>;
designTokens: Array<DesignToken>;
props: Array<InstanceProps>;
assets: Array<Asset>;
params?: Params;
};
@@ -1,8 +0,0 @@
import { atom } from "nanostores";
import type { Instance, PropsItem } from "@webstudio-is/project-build";
export type AllUserProps = {
[id: Instance["id"]]: PropsItem[];
};
export const allUserPropsContainer = atom<AllUserProps>({});