Skip to content

Commit b2137c1

Browse files
authored
refactor: migrate dnd to new constraints (#4507)
Replaced legacy utilities with matchers List item no longer possible to place into list item, thanks to new parent matcher. Dnd in navigator and on canvas can be tested
1 parent 9cf443a commit b2137c1

File tree

5 files changed

+31
-353
lines changed

5 files changed

+31
-353
lines changed

apps/builder/app/builder/features/navigator/navigator-tree.tsx

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,15 @@ import {
5151
import type { InstanceSelector } from "~/shared/tree-utils";
5252
import { serverSyncStore } from "~/shared/sync";
5353
import { MetaIcon } from "~/builder/shared/meta-icon";
54-
import {
55-
computeInstancesConstraints,
56-
findClosestDroppableComponentIndex,
57-
getInstanceLabel,
58-
reparentInstance,
59-
} from "~/shared/instance-utils";
54+
import { getInstanceLabel, reparentInstance } from "~/shared/instance-utils";
6055
import { emitCommand } from "~/builder/shared/commands";
6156
import { useContentEditable } from "~/shared/dom-hooks";
6257
import {
6358
$selectedPage,
6459
getInstanceKey,
6560
selectInstance,
6661
} from "~/shared/awareness";
62+
import { isTreeMatching } from "~/shared/matcher";
6763

6864
type TreeItem = {
6965
level: number;
@@ -530,17 +526,12 @@ const canDrop = (
530526
return false;
531527
}
532528

533-
const metas = $registeredComponentMetas.get();
534-
const insertConstraints = computeInstancesConstraints(metas, instances, [
535-
dragSelector[0],
536-
]);
537-
const ancestorIndex = findClosestDroppableComponentIndex({
538-
metas,
539-
constraints: insertConstraints,
529+
return isTreeMatching({
540530
instances,
541-
instanceSelector: dropSelector,
531+
metas: $registeredComponentMetas.get(),
532+
// make sure dragging tree can be put inside of drop instance
533+
instanceSelector: [dragSelector[0], ...dropSelector],
542534
});
543-
return ancestorIndex === 0;
544535
};
545536

546537
export const NavigatorTree = () => {

apps/builder/app/builder/features/workspace/canvas-tools/canvas-tools.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ export const CanvasTools = () => {
4646
return;
4747
}
4848

49-
if (
50-
dragAndDropState.isDragging &&
51-
dragAndDropState.placementIndicator !== undefined
52-
) {
49+
if (dragAndDropState.isDragging) {
50+
if (dragAndDropState.placementIndicator === undefined) {
51+
return;
52+
}
5353
const { dropTarget, placementIndicator } = dragAndDropState;
5454
const dropTargetInstance =
5555
dropTarget === undefined

apps/builder/app/canvas/shared/use-drag-drop.ts

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,10 @@ import {
1515
} from "~/shared/nano-states";
1616
import { publish, useSubscribe } from "~/shared/pubsub";
1717
import {
18-
computeInstancesConstraints,
19-
findClosestDroppableComponentIndex,
2018
findClosestDetachableInstanceSelector,
2119
getComponentTemplateData,
2220
insertTemplateData,
2321
reparentInstance,
24-
type InsertConstraints,
2522
} from "~/shared/instance-utils";
2623
import {
2724
getElementByInstanceSelector,
@@ -32,6 +29,10 @@ import {
3229
type InstanceSelector,
3330
areInstanceSelectorsEqual,
3431
} from "~/shared/tree-utils";
32+
import {
33+
findClosestInstanceMatchingFragment,
34+
isTreeMatching,
35+
} from "~/shared/matcher";
3536

3637
declare module "~/shared/pubsub" {
3738
export interface PubsubMap {
@@ -66,39 +67,30 @@ const findClosestDroppableInstanceSelector = (
6667
const instances = $instances.get();
6768
const metas = $registeredComponentMetas.get();
6869

69-
let insertConstraints: undefined | InsertConstraints;
70+
let droppableIndex = -1;
7071
if (dragPayload?.type === "insert") {
71-
const templateData = getComponentTemplateData(dragPayload.dragComponent);
72-
if (templateData) {
73-
const { children, instances } = templateData;
74-
const newInstances = new Map(
75-
instances.map((instance) => [instance.id, instance])
76-
);
77-
const rootInstanceIds = children
78-
.filter((child) => child.type === "id")
79-
.map((child) => child.value);
80-
insertConstraints = computeInstancesConstraints(
72+
const fragment = getComponentTemplateData(dragPayload.dragComponent);
73+
if (fragment) {
74+
droppableIndex = findClosestInstanceMatchingFragment({
75+
instances,
8176
metas,
82-
newInstances,
83-
rootInstanceIds
84-
);
77+
instanceSelector,
78+
fragment,
79+
});
8580
}
8681
}
8782
if (dragPayload?.type === "reparent") {
88-
insertConstraints = computeInstancesConstraints(metas, instances, [
89-
dragPayload.dragInstanceSelector[0],
90-
]);
91-
}
92-
if (insertConstraints === undefined) {
93-
return;
83+
const matches = isTreeMatching({
84+
instances,
85+
metas,
86+
instanceSelector: [
87+
dragPayload.dragInstanceSelector[0],
88+
...instanceSelector,
89+
],
90+
});
91+
droppableIndex = matches ? 0 : -1;
9492
}
9593

96-
const droppableIndex = findClosestDroppableComponentIndex({
97-
metas: $registeredComponentMetas.get(),
98-
constraints: insertConstraints,
99-
instances: $instances.get(),
100-
instanceSelector,
101-
});
10294
if (droppableIndex === -1) {
10395
return;
10496
}

apps/builder/app/shared/instance-utils.test.tsx

Lines changed: 0 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,8 @@ import type {
3131
import { encodeDataSourceVariable, getStyleDeclKey } from "@webstudio-is/sdk";
3232
import type { StyleProperty, StyleValue } from "@webstudio-is/css-engine";
3333
import {
34-
computeInstancesConstraints,
35-
findClosestDroppableComponentIndex,
3634
findClosestEditableInstanceSelector,
3735
insertTemplateData,
38-
type InsertConstraints,
3936
deleteInstanceMutable,
4037
extractWebstudioFragment,
4138
insertWebstudioFragmentCopy,
@@ -189,11 +186,6 @@ const createFontAsset = (id: string, family: string): Asset => {
189186
};
190187
};
191188

192-
const emptyInsertConstraints: InsertConstraints = {
193-
requiredAncestors: new Set(),
194-
invalidAncestors: new Set(),
195-
};
196-
197189
describe("find closest editable instance selector", () => {
198190
test("searches closest container", () => {
199191
const instances: Instances = toMap([
@@ -268,173 +260,6 @@ describe("find closest editable instance selector", () => {
268260
});
269261
});
270262

271-
describe("compute instances constraints", () => {
272-
const base = {
273-
type: "container",
274-
label: "",
275-
icon: "",
276-
} as const;
277-
278-
test("combine required ancestors excluding already resolved ones", () => {
279-
const metas = new Map<string, WsComponentMeta>([
280-
["Button", { ...base, requiredAncestors: ["Form"] }],
281-
["Checkbox", { ...base, requiredAncestors: ["Form", "Label"] }],
282-
["Label", { ...base, requiredAncestors: ["Body"] }],
283-
]);
284-
// button
285-
// label
286-
// checkbox
287-
const instances = new Map<Instance["id"], Instance>([
288-
createInstancePair("button", "Button", []),
289-
createInstancePair("label", "Label", [{ type: "id", value: "checkbox" }]),
290-
createInstancePair("checkbox", "Checkbox", []),
291-
]);
292-
expect(
293-
computeInstancesConstraints(metas, instances, ["button", "label"])
294-
).toEqual({
295-
requiredAncestors: new Set(["Body", "Form"]),
296-
invalidAncestors: new Set(),
297-
});
298-
});
299-
300-
test("combine invalid ancestors of all instances", () => {
301-
const metas = new Map<string, WsComponentMeta>([
302-
["Button", { ...base, invalidAncestors: ["Button"] }],
303-
["Form", { ...base, invalidAncestors: ["Button", "Form"] }],
304-
]);
305-
// form
306-
// button
307-
const instances = new Map<Instance["id"], Instance>([
308-
createInstancePair("form", "Form", [{ type: "id", value: "button" }]),
309-
createInstancePair("button", "Button", []),
310-
]);
311-
expect(computeInstancesConstraints(metas, instances, ["form"])).toEqual({
312-
requiredAncestors: new Set(),
313-
invalidAncestors: new Set(["Button", "Form"]),
314-
});
315-
});
316-
});
317-
318-
describe("find closest droppable component index", () => {
319-
test("finds container", () => {
320-
expect(
321-
findClosestDroppableComponentIndex({
322-
metas: createFakeComponentMetas({}),
323-
constraints: emptyInsertConstraints,
324-
instances: new Map<Instance["id"], Instance>([
325-
createInstancePair("body", "Body", [{ type: "id", value: "box" }]),
326-
createInstancePair("box", "Box", []),
327-
]),
328-
instanceSelector: ["box", "body"],
329-
})
330-
).toEqual(0);
331-
});
332-
333-
test("skips non containers", () => {
334-
expect(
335-
findClosestDroppableComponentIndex({
336-
metas: createFakeComponentMetas({}),
337-
constraints: emptyInsertConstraints,
338-
instances: new Map<Instance["id"], Instance>([
339-
createInstancePair("body", "Body", [{ type: "id", value: "box" }]),
340-
createInstancePair("box", "Box", [{ type: "id", value: "text" }]),
341-
createInstancePair("text", "Text", [{ type: "id", value: "italic" }]),
342-
createInstancePair("italic", "Italic", [
343-
{ type: "id", value: "bold" },
344-
]),
345-
createInstancePair("bold", "Bold", []),
346-
]),
347-
instanceSelector: ["bold", "italic", "text", "box", "body"],
348-
})
349-
).toEqual(2);
350-
});
351-
352-
test("considers invalid ancestors", () => {
353-
expect(
354-
findClosestDroppableComponentIndex({
355-
metas: createFakeComponentMetas({}),
356-
constraints: {
357-
requiredAncestors: new Set(),
358-
invalidAncestors: new Set(["Item"]),
359-
},
360-
instances: new Map<Instance["id"], Instance>([
361-
createInstancePair("body", "Body", [{ type: "id", value: "item" }]),
362-
createInstancePair("item", "Item", [{ type: "id", value: "box" }]),
363-
createInstancePair("box", "Box", []),
364-
]),
365-
instanceSelector: ["box", "item", "body"],
366-
})
367-
).toEqual(2);
368-
});
369-
370-
test("requires some ancestor", () => {
371-
expect(
372-
findClosestDroppableComponentIndex({
373-
metas: createFakeComponentMetas({}),
374-
constraints: {
375-
requiredAncestors: new Set(["Form"]),
376-
invalidAncestors: new Set(),
377-
},
378-
instances: new Map<Instance["id"], Instance>([
379-
createInstancePair("body", "Body", [{ type: "id", value: "box" }]),
380-
createInstancePair("box", "Box", []),
381-
]),
382-
instanceSelector: ["box", "body"],
383-
})
384-
).toEqual(-1);
385-
expect(
386-
findClosestDroppableComponentIndex({
387-
metas: createFakeComponentMetas({}),
388-
constraints: {
389-
requiredAncestors: new Set(["Form"]),
390-
invalidAncestors: new Set(),
391-
},
392-
instances: new Map<Instance["id"], Instance>([
393-
createInstancePair("body", "Body", [{ type: "id", value: "form" }]),
394-
createInstancePair("form", "Form", [{ type: "id", value: "box" }]),
395-
createInstancePair("box", "Box", []),
396-
]),
397-
instanceSelector: ["box", "form", "body"],
398-
})
399-
).toEqual(0);
400-
});
401-
402-
test("considers both required and invalid ancestors", () => {
403-
expect(
404-
findClosestDroppableComponentIndex({
405-
metas: createFakeComponentMetas({}),
406-
constraints: {
407-
requiredAncestors: new Set(["Form"]),
408-
invalidAncestors: new Set(["Box"]),
409-
},
410-
instances: new Map<Instance["id"], Instance>([
411-
createInstancePair("body", "Body", [{ type: "id", value: "form" }]),
412-
createInstancePair("form", "Form", [{ type: "id", value: "box" }]),
413-
createInstancePair("box", "Box", [{ type: "id", value: "div" }]),
414-
createInstancePair("div", "Div", []),
415-
]),
416-
instanceSelector: ["div", "box", "form", "body"],
417-
})
418-
).toEqual(2);
419-
expect(
420-
findClosestDroppableComponentIndex({
421-
metas: createFakeComponentMetas({}),
422-
constraints: {
423-
requiredAncestors: new Set(["Form"]),
424-
invalidAncestors: new Set(["Box"]),
425-
},
426-
instances: new Map<Instance["id"], Instance>([
427-
createInstancePair("body", "Body", [{ type: "id", value: "form" }]),
428-
createInstancePair("form", "Form", [{ type: "id", value: "box" }]),
429-
createInstancePair("box", "Box", [{ type: "id", value: "div" }]),
430-
createInstancePair("div", "Div", []),
431-
]),
432-
instanceSelector: ["div", "form", "box", "body"],
433-
})
434-
).toEqual(-1);
435-
});
436-
});
437-
438263
describe("insert instance children", () => {
439264
test("insert instance children into empty target", () => {
440265
const instances = toMap([createInstance("body", "Body", [])]);

0 commit comments

Comments
 (0)