@zoneflow/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clone.d.ts +14 -0
- package/dist/clone.js +54 -0
- package/dist/hierarchy.d.ts +5 -0
- package/dist/hierarchy.js +18 -0
- package/dist/ids.d.ts +3 -0
- package/dist/ids.js +7 -0
- package/dist/import.d.ts +13 -0
- package/dist/import.js +129 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/layout.d.ts +39 -0
- package/dist/layout.js +279 -0
- package/dist/lookup.d.ts +10 -0
- package/dist/lookup.js +44 -0
- package/dist/mutation.d.ts +29 -0
- package/dist/mutation.js +251 -0
- package/dist/path.d.ts +4 -0
- package/dist/path.js +29 -0
- package/dist/remap.d.ts +8 -0
- package/dist/remap.js +72 -0
- package/dist/traversal.d.ts +3 -0
- package/dist/traversal.js +16 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.js +1 -0
- package/dist/unwrap.d.ts +12 -0
- package/dist/unwrap.js +81 -0
- package/dist/validation.d.ts +2 -0
- package/dist/validation.js +48 -0
- package/dist/wrap.d.ts +19 -0
- package/dist/wrap.js +103 -0
- package/dist/zoneCapabilities.d.ts +4 -0
- package/dist/zoneCapabilities.js +9 -0
- package/package.json +25 -0
package/dist/clone.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { UniverseModel, ZoneId } from "./types";
|
|
2
|
+
export type CloneZoneSubtreeOptions = {
|
|
3
|
+
nextParentZoneId?: ZoneId | null;
|
|
4
|
+
rename?: (originalName: string) => string;
|
|
5
|
+
};
|
|
6
|
+
export type CloneZoneSubtreeResult = {
|
|
7
|
+
model: UniverseModel;
|
|
8
|
+
clonedRootZoneId: ZoneId;
|
|
9
|
+
zoneIdMap: Record<ZoneId, ZoneId>;
|
|
10
|
+
};
|
|
11
|
+
export declare function cloneZoneSubtree(model: UniverseModel, sourceRootZoneId: ZoneId, options?: CloneZoneSubtreeOptions): CloneZoneSubtreeResult;
|
|
12
|
+
export type DuplicateZoneSubtreeOptions = CloneZoneSubtreeOptions;
|
|
13
|
+
export type DuplicateZoneSubtreeResult = CloneZoneSubtreeResult;
|
|
14
|
+
export declare function duplicateZoneSubtree(model: UniverseModel, sourceRootZoneId: ZoneId, options?: DuplicateZoneSubtreeOptions): DuplicateZoneSubtreeResult;
|
package/dist/clone.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getZone } from "./lookup";
|
|
2
|
+
import { remapSubtreeIds } from "./remap";
|
|
3
|
+
export function cloneZoneSubtree(model, sourceRootZoneId, options = {}) {
|
|
4
|
+
const sourceRootZone = getZone(model, sourceRootZoneId);
|
|
5
|
+
if (!sourceRootZone) {
|
|
6
|
+
return {
|
|
7
|
+
model,
|
|
8
|
+
clonedRootZoneId: sourceRootZoneId,
|
|
9
|
+
zoneIdMap: {},
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const { nextParentZoneId = sourceRootZone.parentZoneId, rename = (name) => `${name} Copy`, } = options;
|
|
13
|
+
const remapped = remapSubtreeIds(model, sourceRootZoneId);
|
|
14
|
+
const clonedRootZoneId = remapped.rootZoneIds[0];
|
|
15
|
+
const nextZonesById = {
|
|
16
|
+
...model.zonesById,
|
|
17
|
+
...remapped.zonesById,
|
|
18
|
+
};
|
|
19
|
+
// 복제 루트의 parent 재설정 + 이름 변경
|
|
20
|
+
nextZonesById[clonedRootZoneId] = {
|
|
21
|
+
...nextZonesById[clonedRootZoneId],
|
|
22
|
+
parentZoneId: nextParentZoneId,
|
|
23
|
+
name: rename(sourceRootZone.name),
|
|
24
|
+
};
|
|
25
|
+
let nextRootZoneIds = [...model.rootZoneIds];
|
|
26
|
+
// 루트에 붙이는 경우
|
|
27
|
+
if (nextParentZoneId === null) {
|
|
28
|
+
nextRootZoneIds.push(clonedRootZoneId);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const parent = nextZonesById[nextParentZoneId];
|
|
32
|
+
if (parent) {
|
|
33
|
+
nextZonesById[nextParentZoneId] = {
|
|
34
|
+
...parent,
|
|
35
|
+
childZoneIds: [...parent.childZoneIds, clonedRootZoneId],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
model: {
|
|
41
|
+
...model,
|
|
42
|
+
rootZoneIds: nextRootZoneIds,
|
|
43
|
+
zonesById: nextZonesById,
|
|
44
|
+
},
|
|
45
|
+
clonedRootZoneId,
|
|
46
|
+
zoneIdMap: remapped.zoneIdMap,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function duplicateZoneSubtree(model, sourceRootZoneId, options = {}) {
|
|
50
|
+
return cloneZoneSubtree(model, sourceRootZoneId, {
|
|
51
|
+
rename: options.rename ?? ((name) => `${name} Copy`),
|
|
52
|
+
nextParentZoneId: options.nextParentZoneId,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { UniverseModel, ZoneId } from "./types";
|
|
2
|
+
export declare function isRootZone(model: UniverseModel, zoneId: ZoneId): boolean;
|
|
3
|
+
export declare function getAncestorZoneIds(model: UniverseModel, zoneId: ZoneId): ZoneId[];
|
|
4
|
+
export declare function getZoneDepth(model: UniverseModel, zoneId: ZoneId): number;
|
|
5
|
+
export declare function isDescendantZone(model: UniverseModel, parentZoneId: ZoneId, childZoneId: ZoneId): boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function isRootZone(model, zoneId) {
|
|
2
|
+
return model.rootZoneIds.includes(zoneId);
|
|
3
|
+
}
|
|
4
|
+
export function getAncestorZoneIds(model, zoneId) {
|
|
5
|
+
const result = [];
|
|
6
|
+
let current = model.zonesById[zoneId];
|
|
7
|
+
while (current?.parentZoneId) {
|
|
8
|
+
result.push(current.parentZoneId);
|
|
9
|
+
current = model.zonesById[current.parentZoneId];
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
export function getZoneDepth(model, zoneId) {
|
|
14
|
+
return getAncestorZoneIds(model, zoneId).length;
|
|
15
|
+
}
|
|
16
|
+
export function isDescendantZone(model, parentZoneId, childZoneId) {
|
|
17
|
+
return getAncestorZoneIds(model, childZoneId).includes(parentZoneId);
|
|
18
|
+
}
|
package/dist/ids.d.ts
ADDED
package/dist/ids.js
ADDED
package/dist/import.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { UniverseModel, ZoneId } from "./types";
|
|
2
|
+
export type ExternalTargetPolicy = "preserve" | "drop" | "mark-unresolved";
|
|
3
|
+
export type ImportZoneSubtreeOptions = {
|
|
4
|
+
nextParentZoneId?: ZoneId | null;
|
|
5
|
+
rename?: (originalName: string) => string;
|
|
6
|
+
externalTargetPolicy?: ExternalTargetPolicy;
|
|
7
|
+
};
|
|
8
|
+
export type ImportZoneSubtreeResult = {
|
|
9
|
+
model: UniverseModel;
|
|
10
|
+
importedRootZoneId: ZoneId;
|
|
11
|
+
zoneIdMap: Record<ZoneId, ZoneId>;
|
|
12
|
+
};
|
|
13
|
+
export declare function importZoneSubtree(targetModel: UniverseModel, sourceModel: UniverseModel, sourceRootZoneId: ZoneId, options?: ImportZoneSubtreeOptions): ImportZoneSubtreeResult;
|
package/dist/import.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { createPathId, createZoneId } from "./ids";
|
|
2
|
+
import { flattenSubtree } from "./traversal";
|
|
3
|
+
export function importZoneSubtree(targetModel, sourceModel, sourceRootZoneId, options = {}) {
|
|
4
|
+
const { nextParentZoneId = null, rename = (name) => name, externalTargetPolicy = "preserve", } = options;
|
|
5
|
+
const sourceRootZone = sourceModel.zonesById[sourceRootZoneId];
|
|
6
|
+
if (!sourceRootZone) {
|
|
7
|
+
return {
|
|
8
|
+
model: targetModel,
|
|
9
|
+
importedRootZoneId: sourceRootZoneId,
|
|
10
|
+
zoneIdMap: {},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const sourceZones = flattenSubtree(sourceModel, sourceRootZoneId);
|
|
14
|
+
const zoneIdMap = {};
|
|
15
|
+
const pathIdMap = {};
|
|
16
|
+
// 1) Zone ID 재발급
|
|
17
|
+
for (const zone of sourceZones) {
|
|
18
|
+
zoneIdMap[zone.id] = createZoneId();
|
|
19
|
+
}
|
|
20
|
+
// 2) Path ID 재발급
|
|
21
|
+
for (const zone of sourceZones) {
|
|
22
|
+
for (const pathId of zone.pathIds) {
|
|
23
|
+
const path = zone.pathsById[pathId];
|
|
24
|
+
if (!path)
|
|
25
|
+
continue;
|
|
26
|
+
pathIdMap[path.id] = createPathId();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// 3) Zone clone + 내부 참조 재작성
|
|
30
|
+
const importedZonesById = {};
|
|
31
|
+
for (const sourceZone of sourceZones) {
|
|
32
|
+
const nextZoneId = zoneIdMap[sourceZone.id];
|
|
33
|
+
const nextChildZoneIds = sourceZone.childZoneIds
|
|
34
|
+
.map((childId) => zoneIdMap[childId])
|
|
35
|
+
.filter((id) => Boolean(id));
|
|
36
|
+
const nextPathIds = sourceZone.pathIds
|
|
37
|
+
.map((pathId) => {
|
|
38
|
+
const path = sourceZone.pathsById[pathId];
|
|
39
|
+
if (!path)
|
|
40
|
+
return undefined;
|
|
41
|
+
return pathIdMap[path.id];
|
|
42
|
+
})
|
|
43
|
+
.filter((id) => Boolean(id));
|
|
44
|
+
const nextPathsById = {};
|
|
45
|
+
for (const oldPathId of sourceZone.pathIds) {
|
|
46
|
+
const oldPath = sourceZone.pathsById[oldPathId];
|
|
47
|
+
if (!oldPath)
|
|
48
|
+
continue;
|
|
49
|
+
const nextPathId = pathIdMap[oldPath.id];
|
|
50
|
+
let nextTarget = oldPath.target ?? null;
|
|
51
|
+
let nextMeta = oldPath.meta ? { ...oldPath.meta } : undefined;
|
|
52
|
+
if (nextTarget) {
|
|
53
|
+
const isSameSourceUniverse = nextTarget.universeId === sourceModel.universeId;
|
|
54
|
+
const isInternalTarget = Boolean(zoneIdMap[nextTarget.zoneId]);
|
|
55
|
+
// source subtree 내부 참조면 새 ID로 교체
|
|
56
|
+
if (isSameSourceUniverse && isInternalTarget) {
|
|
57
|
+
nextTarget = {
|
|
58
|
+
universeId: targetModel.universeId,
|
|
59
|
+
zoneId: zoneIdMap[nextTarget.zoneId],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// 외부 참조 정책 적용
|
|
64
|
+
if (externalTargetPolicy === "drop") {
|
|
65
|
+
nextTarget = null;
|
|
66
|
+
}
|
|
67
|
+
else if (externalTargetPolicy === "mark-unresolved") {
|
|
68
|
+
nextMeta = {
|
|
69
|
+
...nextMeta,
|
|
70
|
+
unresolvedTarget: true,
|
|
71
|
+
originalTarget: oldPath.target,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// preserve는 그대로 둠
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
nextPathsById[nextPathId] = {
|
|
78
|
+
...oldPath,
|
|
79
|
+
id: nextPathId,
|
|
80
|
+
target: nextTarget,
|
|
81
|
+
meta: nextMeta,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
importedZonesById[nextZoneId] = {
|
|
85
|
+
...sourceZone,
|
|
86
|
+
id: nextZoneId,
|
|
87
|
+
parentZoneId: sourceZone.parentZoneId
|
|
88
|
+
? zoneIdMap[sourceZone.parentZoneId] ?? null
|
|
89
|
+
: null,
|
|
90
|
+
childZoneIds: nextChildZoneIds,
|
|
91
|
+
pathIds: nextPathIds,
|
|
92
|
+
pathsById: nextPathsById,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const importedRootZoneId = zoneIdMap[sourceRootZoneId];
|
|
96
|
+
// 4) 가져온 루트 zone 이름/부모 수정
|
|
97
|
+
importedZonesById[importedRootZoneId] = {
|
|
98
|
+
...importedZonesById[importedRootZoneId],
|
|
99
|
+
parentZoneId: nextParentZoneId,
|
|
100
|
+
name: rename(sourceRootZone.name),
|
|
101
|
+
};
|
|
102
|
+
// 5) targetModel에 병합
|
|
103
|
+
const nextZonesById = {
|
|
104
|
+
...targetModel.zonesById,
|
|
105
|
+
...importedZonesById,
|
|
106
|
+
};
|
|
107
|
+
let nextRootZoneIds = [...targetModel.rootZoneIds];
|
|
108
|
+
if (nextParentZoneId === null) {
|
|
109
|
+
nextRootZoneIds.push(importedRootZoneId);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
const parent = nextZonesById[nextParentZoneId];
|
|
113
|
+
if (parent) {
|
|
114
|
+
nextZonesById[nextParentZoneId] = {
|
|
115
|
+
...parent,
|
|
116
|
+
childZoneIds: [...parent.childZoneIds, importedRootZoneId],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
model: {
|
|
122
|
+
...targetModel,
|
|
123
|
+
rootZoneIds: nextRootZoneIds,
|
|
124
|
+
zonesById: nextZonesById,
|
|
125
|
+
},
|
|
126
|
+
importedRootZoneId,
|
|
127
|
+
zoneIdMap,
|
|
128
|
+
};
|
|
129
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from "./types";
|
|
2
|
+
export * from "./zoneCapabilities";
|
|
3
|
+
export * from "./lookup";
|
|
4
|
+
export * from "./path";
|
|
5
|
+
export * from "./hierarchy";
|
|
6
|
+
export * from "./traversal";
|
|
7
|
+
export * from "./validation";
|
|
8
|
+
export * from "./mutation";
|
|
9
|
+
export * from "./layout";
|
|
10
|
+
export * from "./ids";
|
|
11
|
+
export * from "./remap";
|
|
12
|
+
export * from "./clone";
|
|
13
|
+
export * from "./import";
|
|
14
|
+
export * from "./wrap";
|
|
15
|
+
export * from "./unwrap";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from "./types";
|
|
2
|
+
export * from "./zoneCapabilities";
|
|
3
|
+
export * from "./lookup";
|
|
4
|
+
export * from "./path";
|
|
5
|
+
export * from "./hierarchy";
|
|
6
|
+
export * from "./traversal";
|
|
7
|
+
export * from "./validation";
|
|
8
|
+
export * from "./mutation";
|
|
9
|
+
export * from "./layout";
|
|
10
|
+
export * from "./ids";
|
|
11
|
+
export * from "./remap";
|
|
12
|
+
export * from "./clone";
|
|
13
|
+
export * from "./import";
|
|
14
|
+
export * from "./wrap";
|
|
15
|
+
export * from "./unwrap";
|
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Layout, PathId, PathLayout, UniverseId, UniverseLayoutModel, UniverseModel, ZoneId, ZoneLayout } from "./types";
|
|
2
|
+
export type CreateUniverseLayoutModelInput = {
|
|
3
|
+
universeId: UniverseId;
|
|
4
|
+
version?: string;
|
|
5
|
+
zoneLayoutsById?: Record<ZoneId, ZoneLayout>;
|
|
6
|
+
pathLayoutsById?: Record<PathId, PathLayout>;
|
|
7
|
+
meta?: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
export declare function createUniverseLayoutModel(input: CreateUniverseLayoutModelInput): UniverseLayoutModel;
|
|
10
|
+
export declare function createZoneLayout(input: {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
}): ZoneLayout;
|
|
16
|
+
export declare function getZoneLayout(layoutModel: UniverseLayoutModel, zoneId: ZoneId): ZoneLayout | undefined;
|
|
17
|
+
export declare function setZoneLayout(layoutModel: UniverseLayoutModel, zoneId: ZoneId, layout: ZoneLayout | undefined): UniverseLayoutModel;
|
|
18
|
+
export declare function updateZoneLayout(layoutModel: UniverseLayoutModel, zoneId: ZoneId, patch: Partial<ZoneLayout>): UniverseLayoutModel;
|
|
19
|
+
export declare function getPathLayout(layoutModel: UniverseLayoutModel, pathId: PathId): PathLayout | undefined;
|
|
20
|
+
export declare function setPathLayout(layoutModel: UniverseLayoutModel, pathId: PathId, layout: PathLayout | undefined): UniverseLayoutModel;
|
|
21
|
+
export declare function updatePathLayout(layoutModel: UniverseLayoutModel, pathId: PathId, patch: Partial<PathLayout>): UniverseLayoutModel;
|
|
22
|
+
export declare function getPathComponentLayout(layoutModel: UniverseLayoutModel, pathId: PathId, componentId: string): Layout | undefined;
|
|
23
|
+
export declare function setPathComponentLayout(layoutModel: UniverseLayoutModel, pathId: PathId, componentId: string, layout: Layout | undefined): UniverseLayoutModel;
|
|
24
|
+
export declare function updatePathComponentLayout(layoutModel: UniverseLayoutModel, pathId: PathId, componentId: string, patch: Partial<Layout>): UniverseLayoutModel;
|
|
25
|
+
export declare function removeZoneLayouts(layoutModel: UniverseLayoutModel, zoneIds: ZoneId[]): UniverseLayoutModel;
|
|
26
|
+
export declare function removePathLayouts(layoutModel: UniverseLayoutModel, pathIds: PathId[]): UniverseLayoutModel;
|
|
27
|
+
export declare function pruneLayoutModel(model: UniverseModel, layoutModel: UniverseLayoutModel): UniverseLayoutModel;
|
|
28
|
+
export declare function computeAutoLayoutForZoneTree(model: UniverseModel, layoutModel: UniverseLayoutModel, zoneId: ZoneId, options?: {
|
|
29
|
+
paddingX?: number;
|
|
30
|
+
paddingY?: number;
|
|
31
|
+
verticalGap?: number;
|
|
32
|
+
defaultWidth?: number;
|
|
33
|
+
defaultHeight?: number;
|
|
34
|
+
}): ZoneLayout | undefined;
|
|
35
|
+
export declare function computeWrapperLayoutFromChildren(model: UniverseModel, layoutModel: UniverseLayoutModel, zoneIds: ZoneId[], options?: {
|
|
36
|
+
padding?: number;
|
|
37
|
+
minWidth?: number;
|
|
38
|
+
minHeight?: number;
|
|
39
|
+
}): ZoneLayout | undefined;
|
package/dist/layout.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
function createDefaultAnchors() {
|
|
2
|
+
return {
|
|
3
|
+
inlet: {
|
|
4
|
+
point: { x: 0, y: 0 },
|
|
5
|
+
},
|
|
6
|
+
outlet: {
|
|
7
|
+
point: { x: 0, y: 0 },
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function cloneAnchor(anchor) {
|
|
12
|
+
return {
|
|
13
|
+
point: {
|
|
14
|
+
x: anchor?.point.x ?? 0,
|
|
15
|
+
y: anchor?.point.y ?? 0,
|
|
16
|
+
},
|
|
17
|
+
rect: anchor?.rect ? { ...anchor.rect } : undefined,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function cloneAnchors(anchors) {
|
|
21
|
+
return {
|
|
22
|
+
inlet: cloneAnchor(anchors?.inlet),
|
|
23
|
+
outlet: cloneAnchor(anchors?.outlet),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function mergeAnchor(current, patch) {
|
|
27
|
+
return {
|
|
28
|
+
point: {
|
|
29
|
+
x: patch?.point?.x ?? current?.point.x ?? 0,
|
|
30
|
+
y: patch?.point?.y ?? current?.point.y ?? 0,
|
|
31
|
+
},
|
|
32
|
+
rect: patch?.rect !== undefined
|
|
33
|
+
? patch.rect
|
|
34
|
+
? { ...patch.rect }
|
|
35
|
+
: undefined
|
|
36
|
+
: current?.rect
|
|
37
|
+
? { ...current.rect }
|
|
38
|
+
: undefined,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function mergeAnchors(current, patch) {
|
|
42
|
+
return {
|
|
43
|
+
inlet: mergeAnchor(current?.inlet, patch?.inlet),
|
|
44
|
+
outlet: mergeAnchor(current?.outlet, patch?.outlet),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function createUniverseLayoutModel(input) {
|
|
48
|
+
return {
|
|
49
|
+
version: input.version ?? "1.0.0",
|
|
50
|
+
universeId: input.universeId,
|
|
51
|
+
zoneLayoutsById: input.zoneLayoutsById ? { ...input.zoneLayoutsById } : {},
|
|
52
|
+
pathLayoutsById: input.pathLayoutsById ? { ...input.pathLayoutsById } : {},
|
|
53
|
+
meta: input.meta ? { ...input.meta } : undefined,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function createZoneLayout(input) {
|
|
57
|
+
const { x, y, width, height } = input;
|
|
58
|
+
return {
|
|
59
|
+
x,
|
|
60
|
+
y,
|
|
61
|
+
width,
|
|
62
|
+
height,
|
|
63
|
+
anchors: {
|
|
64
|
+
inlet: {
|
|
65
|
+
point: {
|
|
66
|
+
x: 0,
|
|
67
|
+
y: height / 2,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
outlet: {
|
|
71
|
+
point: {
|
|
72
|
+
x: width,
|
|
73
|
+
y: height / 2,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export function getZoneLayout(layoutModel, zoneId) {
|
|
80
|
+
return layoutModel.zoneLayoutsById[zoneId];
|
|
81
|
+
}
|
|
82
|
+
export function setZoneLayout(layoutModel, zoneId, layout) {
|
|
83
|
+
const nextZoneLayoutsById = { ...layoutModel.zoneLayoutsById };
|
|
84
|
+
if (layout) {
|
|
85
|
+
nextZoneLayoutsById[zoneId] = layout;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
delete nextZoneLayoutsById[zoneId];
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
...layoutModel,
|
|
92
|
+
zoneLayoutsById: nextZoneLayoutsById,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export function updateZoneLayout(layoutModel, zoneId, patch) {
|
|
96
|
+
const currentLayout = layoutModel.zoneLayoutsById[zoneId];
|
|
97
|
+
return setZoneLayout(layoutModel, zoneId, {
|
|
98
|
+
x: patch.x ?? currentLayout?.x ?? 0,
|
|
99
|
+
y: patch.y ?? currentLayout?.y ?? 0,
|
|
100
|
+
width: patch.width ?? currentLayout?.width,
|
|
101
|
+
height: patch.height ?? currentLayout?.height,
|
|
102
|
+
anchors: mergeAnchors(currentLayout?.anchors, patch.anchors),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
export function getPathLayout(layoutModel, pathId) {
|
|
106
|
+
return layoutModel.pathLayoutsById[pathId];
|
|
107
|
+
}
|
|
108
|
+
export function setPathLayout(layoutModel, pathId, layout) {
|
|
109
|
+
const nextPathLayoutsById = { ...layoutModel.pathLayoutsById };
|
|
110
|
+
if (layout) {
|
|
111
|
+
nextPathLayoutsById[pathId] = layout;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
delete nextPathLayoutsById[pathId];
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
...layoutModel,
|
|
118
|
+
pathLayoutsById: nextPathLayoutsById,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export function updatePathLayout(layoutModel, pathId, patch) {
|
|
122
|
+
const currentLayout = layoutModel.pathLayoutsById[pathId];
|
|
123
|
+
return setPathLayout(layoutModel, pathId, {
|
|
124
|
+
...currentLayout,
|
|
125
|
+
...patch,
|
|
126
|
+
componentLayoutsById: patch.componentLayoutsById ??
|
|
127
|
+
currentLayout?.componentLayoutsById,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
export function getPathComponentLayout(layoutModel, pathId, componentId) {
|
|
131
|
+
return layoutModel.pathLayoutsById[pathId]?.componentLayoutsById?.[componentId];
|
|
132
|
+
}
|
|
133
|
+
export function setPathComponentLayout(layoutModel, pathId, componentId, layout) {
|
|
134
|
+
const currentPathLayout = layoutModel.pathLayoutsById[pathId] ?? {};
|
|
135
|
+
const nextComponentLayoutsById = {
|
|
136
|
+
...(currentPathLayout.componentLayoutsById ?? {}),
|
|
137
|
+
};
|
|
138
|
+
if (layout) {
|
|
139
|
+
nextComponentLayoutsById[componentId] = layout;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
delete nextComponentLayoutsById[componentId];
|
|
143
|
+
}
|
|
144
|
+
const hasComponents = Object.keys(nextComponentLayoutsById).length > 0;
|
|
145
|
+
const nextPathLayout = {
|
|
146
|
+
...currentPathLayout,
|
|
147
|
+
componentLayoutsById: hasComponents
|
|
148
|
+
? nextComponentLayoutsById
|
|
149
|
+
: undefined,
|
|
150
|
+
};
|
|
151
|
+
if (!nextPathLayout.routeOffset && !nextPathLayout.componentLayoutsById) {
|
|
152
|
+
return setPathLayout(layoutModel, pathId, undefined);
|
|
153
|
+
}
|
|
154
|
+
return setPathLayout(layoutModel, pathId, nextPathLayout);
|
|
155
|
+
}
|
|
156
|
+
export function updatePathComponentLayout(layoutModel, pathId, componentId, patch) {
|
|
157
|
+
const currentLayout = layoutModel.pathLayoutsById[pathId]?.componentLayoutsById?.[componentId];
|
|
158
|
+
return setPathComponentLayout(layoutModel, pathId, componentId, {
|
|
159
|
+
x: patch.x ?? currentLayout?.x ?? 0,
|
|
160
|
+
y: patch.y ?? currentLayout?.y ?? 0,
|
|
161
|
+
width: patch.width ?? currentLayout?.width,
|
|
162
|
+
height: patch.height ?? currentLayout?.height,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
export function removeZoneLayouts(layoutModel, zoneIds) {
|
|
166
|
+
if (zoneIds.length === 0)
|
|
167
|
+
return layoutModel;
|
|
168
|
+
const zoneIdSet = new Set(zoneIds);
|
|
169
|
+
const nextZoneLayoutsById = {};
|
|
170
|
+
for (const [zoneId, layout] of Object.entries(layoutModel.zoneLayoutsById)) {
|
|
171
|
+
if (!zoneIdSet.has(zoneId)) {
|
|
172
|
+
nextZoneLayoutsById[zoneId] = layout;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
...layoutModel,
|
|
177
|
+
zoneLayoutsById: nextZoneLayoutsById,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
export function removePathLayouts(layoutModel, pathIds) {
|
|
181
|
+
if (pathIds.length === 0)
|
|
182
|
+
return layoutModel;
|
|
183
|
+
const pathIdSet = new Set(pathIds);
|
|
184
|
+
const nextPathLayoutsById = {};
|
|
185
|
+
for (const [pathId, layout] of Object.entries(layoutModel.pathLayoutsById)) {
|
|
186
|
+
if (!pathIdSet.has(pathId)) {
|
|
187
|
+
nextPathLayoutsById[pathId] = layout;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
...layoutModel,
|
|
192
|
+
pathLayoutsById: nextPathLayoutsById,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
export function pruneLayoutModel(model, layoutModel) {
|
|
196
|
+
const zoneIds = new Set(Object.keys(model.zonesById));
|
|
197
|
+
const pathIds = new Set();
|
|
198
|
+
for (const zone of Object.values(model.zonesById)) {
|
|
199
|
+
for (const pathId of zone.pathIds) {
|
|
200
|
+
const path = zone.pathsById[pathId];
|
|
201
|
+
if (path) {
|
|
202
|
+
pathIds.add(path.id);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const nextZoneLayoutsById = {};
|
|
207
|
+
for (const [zoneId, layout] of Object.entries(layoutModel.zoneLayoutsById)) {
|
|
208
|
+
if (zoneIds.has(zoneId)) {
|
|
209
|
+
nextZoneLayoutsById[zoneId] = layout;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const nextPathLayoutsById = {};
|
|
213
|
+
for (const [pathId, layout] of Object.entries(layoutModel.pathLayoutsById)) {
|
|
214
|
+
if (pathIds.has(pathId)) {
|
|
215
|
+
nextPathLayoutsById[pathId] = layout;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
...layoutModel,
|
|
220
|
+
universeId: model.universeId,
|
|
221
|
+
zoneLayoutsById: nextZoneLayoutsById,
|
|
222
|
+
pathLayoutsById: nextPathLayoutsById,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
export function computeAutoLayoutForZoneTree(model, layoutModel, zoneId, options = {}) {
|
|
226
|
+
const { paddingX = 32, paddingY = 24, verticalGap = 24, defaultWidth = 160, defaultHeight = 100, } = options;
|
|
227
|
+
const zone = model.zonesById[zoneId];
|
|
228
|
+
if (!zone)
|
|
229
|
+
return undefined;
|
|
230
|
+
const ownLayout = layoutModel.zoneLayoutsById[zone.id];
|
|
231
|
+
const childLayouts = zone.childZoneIds
|
|
232
|
+
.map((childId) => computeAutoLayoutForZoneTree(model, layoutModel, childId, options))
|
|
233
|
+
.filter((layout) => Boolean(layout));
|
|
234
|
+
const ownWidth = ownLayout?.width ?? defaultWidth;
|
|
235
|
+
const ownHeight = ownLayout?.height ?? defaultHeight;
|
|
236
|
+
if (childLayouts.length === 0) {
|
|
237
|
+
return {
|
|
238
|
+
x: ownLayout?.x ?? 0,
|
|
239
|
+
y: ownLayout?.y ?? 0,
|
|
240
|
+
width: ownWidth,
|
|
241
|
+
height: ownHeight,
|
|
242
|
+
anchors: cloneAnchors(ownLayout?.anchors),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const minChildX = Math.min(...childLayouts.map((layout) => layout.x));
|
|
246
|
+
const minChildY = Math.min(...childLayouts.map((layout) => layout.y));
|
|
247
|
+
const maxChildX = Math.max(...childLayouts.map((layout) => layout.x + (layout.width ?? defaultWidth)));
|
|
248
|
+
const maxChildY = Math.max(...childLayouts.map((layout) => layout.y + (layout.height ?? defaultHeight)));
|
|
249
|
+
return {
|
|
250
|
+
x: ownLayout?.x ?? minChildX - paddingX,
|
|
251
|
+
y: ownLayout?.y ?? minChildY - (ownHeight + verticalGap / 2),
|
|
252
|
+
width: Math.max(maxChildX - minChildX + paddingX * 2, ownWidth),
|
|
253
|
+
height: Math.max(ownHeight + verticalGap + (maxChildY - minChildY) + paddingY * 2, ownHeight),
|
|
254
|
+
anchors: cloneAnchors(ownLayout?.anchors),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
export function computeWrapperLayoutFromChildren(model, layoutModel, zoneIds, options = {}) {
|
|
258
|
+
const { padding = 32, minWidth = 160, minHeight = 120 } = options;
|
|
259
|
+
const layouts = zoneIds
|
|
260
|
+
.map((zoneId) => {
|
|
261
|
+
if (!model.zonesById[zoneId])
|
|
262
|
+
return undefined;
|
|
263
|
+
return layoutModel.zoneLayoutsById[zoneId];
|
|
264
|
+
})
|
|
265
|
+
.filter((layout) => Boolean(layout));
|
|
266
|
+
if (layouts.length === 0)
|
|
267
|
+
return undefined;
|
|
268
|
+
const minX = Math.min(...layouts.map((layout) => layout.x));
|
|
269
|
+
const minY = Math.min(...layouts.map((layout) => layout.y));
|
|
270
|
+
const maxX = Math.max(...layouts.map((layout) => layout.x + (layout.width ?? 0)));
|
|
271
|
+
const maxY = Math.max(...layouts.map((layout) => layout.y + (layout.height ?? 0)));
|
|
272
|
+
return {
|
|
273
|
+
x: minX - padding,
|
|
274
|
+
y: minY - padding,
|
|
275
|
+
width: Math.max(maxX - minX + padding * 2, minWidth),
|
|
276
|
+
height: Math.max(maxY - minY + padding * 2, minHeight),
|
|
277
|
+
anchors: createDefaultAnchors(),
|
|
278
|
+
};
|
|
279
|
+
}
|
package/dist/lookup.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { UniverseModel, Zone, ZoneId, Path, PathId } from "./types";
|
|
2
|
+
export declare function getZone(model: UniverseModel, zoneId: ZoneId): Zone | undefined;
|
|
3
|
+
export declare function getRootZones(model: UniverseModel): Zone[];
|
|
4
|
+
export declare function getChildZones(model: UniverseModel, zoneId: ZoneId): Zone[];
|
|
5
|
+
export declare function getParentZone(model: UniverseModel, zoneId: ZoneId): Zone | undefined;
|
|
6
|
+
export declare function getPath(zone: Zone, pathId: PathId): Path | undefined;
|
|
7
|
+
export declare function getPaths(zone: Zone): Path[];
|
|
8
|
+
export declare function getPathByKey(zone: Zone, key: string): Path | undefined;
|
|
9
|
+
export declare function findPathSourceZoneId(model: UniverseModel, pathId: PathId): ZoneId | undefined;
|
|
10
|
+
export declare function findPathSourceZone(model: UniverseModel, pathId: PathId): Zone | undefined;
|
package/dist/lookup.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export function getZone(model, zoneId) {
|
|
2
|
+
return model.zonesById[zoneId];
|
|
3
|
+
}
|
|
4
|
+
export function getRootZones(model) {
|
|
5
|
+
return model.rootZoneIds
|
|
6
|
+
.map((id) => model.zonesById[id])
|
|
7
|
+
.filter((zone) => Boolean(zone));
|
|
8
|
+
}
|
|
9
|
+
export function getChildZones(model, zoneId) {
|
|
10
|
+
const zone = model.zonesById[zoneId];
|
|
11
|
+
if (!zone)
|
|
12
|
+
return [];
|
|
13
|
+
return zone.childZoneIds
|
|
14
|
+
.map((id) => model.zonesById[id])
|
|
15
|
+
.filter((child) => Boolean(child));
|
|
16
|
+
}
|
|
17
|
+
export function getParentZone(model, zoneId) {
|
|
18
|
+
const zone = model.zonesById[zoneId];
|
|
19
|
+
if (!zone?.parentZoneId)
|
|
20
|
+
return undefined;
|
|
21
|
+
return model.zonesById[zone.parentZoneId];
|
|
22
|
+
}
|
|
23
|
+
export function getPath(zone, pathId) {
|
|
24
|
+
return zone.pathsById[pathId];
|
|
25
|
+
}
|
|
26
|
+
export function getPaths(zone) {
|
|
27
|
+
return zone.pathIds
|
|
28
|
+
.map((id) => zone.pathsById[id])
|
|
29
|
+
.filter((path) => Boolean(path));
|
|
30
|
+
}
|
|
31
|
+
export function getPathByKey(zone, key) {
|
|
32
|
+
return getPaths(zone).find((path) => path.key === key);
|
|
33
|
+
}
|
|
34
|
+
export function findPathSourceZoneId(model, pathId) {
|
|
35
|
+
for (const zone of Object.values(model.zonesById)) {
|
|
36
|
+
if (zone.pathsById[pathId])
|
|
37
|
+
return zone.id;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
export function findPathSourceZone(model, pathId) {
|
|
42
|
+
const zoneId = findPathSourceZoneId(model, pathId);
|
|
43
|
+
return zoneId ? model.zonesById[zoneId] : undefined;
|
|
44
|
+
}
|