Files
webstudio/apps/builder/app/shared/instance-utils.ts
T
Ivan Starkov 1ecea88ddb Speedup add remove nodes (#1415)
## Description

Reduces amount of renders from 6 to 2 on addding/deleting instances

Reduction to 1 render is impossible in our current architecture as
following situation happens.

On add node, we set selectedInstanceSelectorStore from the builder, what
cause

In the canvas "SelectedInstanceConnector" sets computed styles etc and
sync back. Both are causing double rendering.

Can be avoided in the future allowing to set store from the builder in a
canvas.


## Steps for reproduction

1. click button
2. expect xyz

## Code Review

- [ ] hi @kof, I need you to do
  - conceptual review (architecture, feature-correctness)
  - detailed review (read every line)
  - test it on preview

## Before requesting a review

- [ ] made a self-review
- [ ] added inline comments where things may be not obvious (the "why",
not "what")

## Before merging

- [ ] tested locally and on preview environment (preview dev login:
5de6)
- [ ] updated [test
cases](https://github.com/webstudio-is/webstudio-builder/blob/main/apps/builder/docs/test-cases.md)
document
- [ ] added tests
- [ ] if any new env variables are added, added them to `.env.example`
and the `builder/env-check.js` if mandatory
2023-04-12 12:19:07 +04:00

145 lines
4.3 KiB
TypeScript

import store from "immerhin";
import { findTreeInstanceIdsExcludingSlotDescendants } from "@webstudio-is/project-build";
import {
propsStore,
stylesStore,
selectedInstanceSelectorStore,
styleSourceSelectionsStore,
styleSourcesStore,
instancesStore,
} from "./nano-states";
import {
type DroppableTarget,
type InstanceSelector,
createComponentInstance,
findSubtreeLocalStyleSources,
insertInstancesMutable,
reparentInstanceMutable,
getAncestorInstanceSelector,
} from "./tree-utils";
import { removeByMutable } from "./array-utils";
import { unstable_batchedUpdates as batchedUpdates } from "react-dom";
export const insertNewComponentInstance = (
component: string,
dropTarget: DroppableTarget
) => {
batchedUpdates(() => {
const instance = createComponentInstance(component);
store.createTransaction([instancesStore], (instances) => {
insertInstancesMutable(instances, [instance], [instance.id], dropTarget);
});
selectedInstanceSelectorStore.set([
instance.id,
...dropTarget.parentSelector,
]);
});
};
export const reparentInstance = (
targetInstanceSelector: InstanceSelector,
dropTarget: DroppableTarget
) => {
batchedUpdates(() => {
store.createTransaction([instancesStore], (instances) => {
reparentInstanceMutable(instances, targetInstanceSelector, dropTarget);
});
selectedInstanceSelectorStore.set(targetInstanceSelector);
});
};
export const deleteInstance = (instanceSelector: InstanceSelector) => {
batchedUpdates(() => {
store.createTransaction(
[
instancesStore,
propsStore,
styleSourceSelectionsStore,
styleSourcesStore,
stylesStore,
],
(instances, props, styleSourceSelections, styleSources, styles) => {
let targetInstanceId = instanceSelector[0];
const parentInstanceId = instanceSelector[1];
const grandparentInstanceId = instanceSelector[2];
let parentInstance =
parentInstanceId === undefined
? undefined
: instances.get(parentInstanceId);
// delete parent fragment too if its last child is going to be deleted
// use case for slots: slot became empty and remove display: contents
// to be displayed properly on canvas
if (
parentInstance?.component === "Fragment" &&
parentInstance.children.length === 1 &&
grandparentInstanceId !== undefined
) {
targetInstanceId = parentInstance.id;
parentInstance = instances.get(grandparentInstanceId);
}
const subtreeIds = findTreeInstanceIdsExcludingSlotDescendants(
instances,
targetInstanceId
);
const subtreeLocalStyleSourceIds = findSubtreeLocalStyleSources(
subtreeIds,
styleSources,
styleSourceSelections
);
// may not exist when delete root
if (parentInstance) {
removeByMutable(
parentInstance.children,
(child) => child.type === "id" && child.value === targetInstanceId
);
}
for (const instanceId of subtreeIds) {
instances.delete(instanceId);
}
// delete props and styles of deleted instance and its descendants
for (const prop of props.values()) {
if (subtreeIds.has(prop.instanceId)) {
props.delete(prop.id);
}
}
for (const instanceId of subtreeIds) {
styleSourceSelections.delete(instanceId);
}
for (const styleSourceId of subtreeLocalStyleSourceIds) {
styleSources.delete(styleSourceId);
}
for (const [styleDeclKey, styleDecl] of styles) {
if (subtreeLocalStyleSourceIds.has(styleDecl.styleSourceId)) {
styles.delete(styleDeclKey);
}
}
if (parentInstance) {
selectedInstanceSelectorStore.set(
getAncestorInstanceSelector(instanceSelector, parentInstance.id)
);
}
}
);
});
};
export const deleteSelectedInstance = () => {
batchedUpdates(() => {
const selectedInstanceSelector = selectedInstanceSelectorStore.get();
// @todo tell user they can't delete root
if (
selectedInstanceSelector === undefined ||
selectedInstanceSelector.length === 1
) {
return;
}
deleteInstance(selectedInstanceSelector);
});
};