@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
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { UniverseModel, Zone, ZoneAction, ZoneId, ZoneType, Path, PathId, ZoneRef } from "./types";
|
|
2
|
+
export type CreateZoneInput = {
|
|
3
|
+
id: ZoneId;
|
|
4
|
+
name: string;
|
|
5
|
+
parentZoneId?: ZoneId | null;
|
|
6
|
+
zoneType?: ZoneType;
|
|
7
|
+
inputDisabled?: boolean;
|
|
8
|
+
outputDisabled?: boolean;
|
|
9
|
+
action?: ZoneAction;
|
|
10
|
+
meta?: Record<string, unknown>;
|
|
11
|
+
};
|
|
12
|
+
export type CreatePathInput = {
|
|
13
|
+
id: PathId;
|
|
14
|
+
key: string;
|
|
15
|
+
name: string;
|
|
16
|
+
target?: ZoneRef | null;
|
|
17
|
+
rule: Path["rule"];
|
|
18
|
+
meta?: Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
export declare function createZone(model: UniverseModel, input: CreateZoneInput): UniverseModel;
|
|
21
|
+
export declare function updateZone(model: UniverseModel, zoneId: ZoneId, patch: Partial<Omit<Zone, "id">>): UniverseModel;
|
|
22
|
+
export declare function moveZone(model: UniverseModel, zoneId: ZoneId, nextParentZoneId: ZoneId | null): UniverseModel;
|
|
23
|
+
export declare function collectSubtreeZoneIds(model: UniverseModel, zoneId: ZoneId): ZoneId[];
|
|
24
|
+
export declare function removeZone(model: UniverseModel, zoneId: ZoneId): UniverseModel;
|
|
25
|
+
export declare function addPath(model: UniverseModel, zoneId: ZoneId, input: CreatePathInput): UniverseModel;
|
|
26
|
+
export declare function updatePath(model: UniverseModel, zoneId: ZoneId, pathId: PathId, patch: Partial<Path>): UniverseModel;
|
|
27
|
+
export declare function removePath(model: UniverseModel, zoneId: ZoneId, pathId: PathId): UniverseModel;
|
|
28
|
+
export declare function setPathTarget(model: UniverseModel, zoneId: ZoneId, pathId: PathId, target: ZoneRef | null): UniverseModel;
|
|
29
|
+
export declare function reorderPaths(model: UniverseModel, zoneId: ZoneId, nextPathIds: PathId[]): UniverseModel;
|
package/dist/mutation.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
export function createZone(model, input) {
|
|
2
|
+
const { id, name, parentZoneId = null, zoneType = "container", inputDisabled, outputDisabled, action, meta, } = input;
|
|
3
|
+
if (model.zonesById[id])
|
|
4
|
+
return model;
|
|
5
|
+
const newZone = {
|
|
6
|
+
id,
|
|
7
|
+
parentZoneId,
|
|
8
|
+
name,
|
|
9
|
+
zoneType,
|
|
10
|
+
inputDisabled,
|
|
11
|
+
outputDisabled,
|
|
12
|
+
childZoneIds: [],
|
|
13
|
+
pathIds: [],
|
|
14
|
+
pathsById: {},
|
|
15
|
+
action,
|
|
16
|
+
meta,
|
|
17
|
+
};
|
|
18
|
+
const nextZonesById = {
|
|
19
|
+
...model.zonesById,
|
|
20
|
+
[id]: newZone,
|
|
21
|
+
};
|
|
22
|
+
if (parentZoneId === null) {
|
|
23
|
+
return {
|
|
24
|
+
...model,
|
|
25
|
+
rootZoneIds: [...model.rootZoneIds, id],
|
|
26
|
+
zonesById: nextZonesById,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const parent = model.zonesById[parentZoneId];
|
|
30
|
+
if (!parent)
|
|
31
|
+
return model;
|
|
32
|
+
return {
|
|
33
|
+
...model,
|
|
34
|
+
zonesById: {
|
|
35
|
+
...nextZonesById,
|
|
36
|
+
[parentZoneId]: {
|
|
37
|
+
...parent,
|
|
38
|
+
childZoneIds: [...parent.childZoneIds, id],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function updateZone(model, zoneId, patch) {
|
|
44
|
+
const zone = model.zonesById[zoneId];
|
|
45
|
+
if (!zone)
|
|
46
|
+
return model;
|
|
47
|
+
return {
|
|
48
|
+
...model,
|
|
49
|
+
zonesById: {
|
|
50
|
+
...model.zonesById,
|
|
51
|
+
[zoneId]: {
|
|
52
|
+
...zone,
|
|
53
|
+
...patch,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function moveZone(model, zoneId, nextParentZoneId) {
|
|
59
|
+
const zone = model.zonesById[zoneId];
|
|
60
|
+
if (!zone)
|
|
61
|
+
return model;
|
|
62
|
+
const prevParentZoneId = zone.parentZoneId;
|
|
63
|
+
if (prevParentZoneId === nextParentZoneId)
|
|
64
|
+
return model;
|
|
65
|
+
let nextModel = model;
|
|
66
|
+
if (prevParentZoneId !== null) {
|
|
67
|
+
const prevParent = nextModel.zonesById[prevParentZoneId];
|
|
68
|
+
if (prevParent) {
|
|
69
|
+
nextModel = {
|
|
70
|
+
...nextModel,
|
|
71
|
+
zonesById: {
|
|
72
|
+
...nextModel.zonesById,
|
|
73
|
+
[prevParentZoneId]: {
|
|
74
|
+
...prevParent,
|
|
75
|
+
childZoneIds: prevParent.childZoneIds.filter((id) => id !== zoneId),
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
nextModel = {
|
|
83
|
+
...nextModel,
|
|
84
|
+
rootZoneIds: nextModel.rootZoneIds.filter((id) => id !== zoneId),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (nextParentZoneId !== null) {
|
|
88
|
+
const nextParent = nextModel.zonesById[nextParentZoneId];
|
|
89
|
+
if (!nextParent)
|
|
90
|
+
return model;
|
|
91
|
+
nextModel = {
|
|
92
|
+
...nextModel,
|
|
93
|
+
zonesById: {
|
|
94
|
+
...nextModel.zonesById,
|
|
95
|
+
[nextParentZoneId]: {
|
|
96
|
+
...nextParent,
|
|
97
|
+
childZoneIds: [...nextParent.childZoneIds, zoneId],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
nextModel = {
|
|
104
|
+
...nextModel,
|
|
105
|
+
rootZoneIds: [...nextModel.rootZoneIds, zoneId],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
...nextModel,
|
|
110
|
+
zonesById: {
|
|
111
|
+
...nextModel.zonesById,
|
|
112
|
+
[zoneId]: {
|
|
113
|
+
...nextModel.zonesById[zoneId],
|
|
114
|
+
parentZoneId: nextParentZoneId,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export function collectSubtreeZoneIds(model, zoneId) {
|
|
120
|
+
const result = [];
|
|
121
|
+
const stack = [zoneId];
|
|
122
|
+
while (stack.length > 0) {
|
|
123
|
+
const currentId = stack.pop();
|
|
124
|
+
const current = model.zonesById[currentId];
|
|
125
|
+
if (!current)
|
|
126
|
+
continue;
|
|
127
|
+
result.push(currentId);
|
|
128
|
+
stack.push(...current.childZoneIds);
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
export function removeZone(model, zoneId) {
|
|
133
|
+
const zone = model.zonesById[zoneId];
|
|
134
|
+
if (!zone)
|
|
135
|
+
return model;
|
|
136
|
+
const zoneIdsToDelete = collectSubtreeZoneIds(model, zoneId);
|
|
137
|
+
const nextZonesById = { ...model.zonesById };
|
|
138
|
+
for (const id of zoneIdsToDelete) {
|
|
139
|
+
delete nextZonesById[id];
|
|
140
|
+
}
|
|
141
|
+
const nextRootZoneIds = model.rootZoneIds.filter((id) => !zoneIdsToDelete.includes(id));
|
|
142
|
+
if (zone.parentZoneId && nextZonesById[zone.parentZoneId]) {
|
|
143
|
+
nextZonesById[zone.parentZoneId] = {
|
|
144
|
+
...nextZonesById[zone.parentZoneId],
|
|
145
|
+
childZoneIds: nextZonesById[zone.parentZoneId].childZoneIds.filter((id) => id !== zoneId),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
...model,
|
|
150
|
+
rootZoneIds: nextRootZoneIds,
|
|
151
|
+
zonesById: nextZonesById,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export function addPath(model, zoneId, input) {
|
|
155
|
+
const zone = model.zonesById[zoneId];
|
|
156
|
+
if (!zone)
|
|
157
|
+
return model;
|
|
158
|
+
if (zone.pathsById[input.id])
|
|
159
|
+
return model;
|
|
160
|
+
const newPath = {
|
|
161
|
+
id: input.id,
|
|
162
|
+
key: input.key,
|
|
163
|
+
name: input.name,
|
|
164
|
+
target: input.target,
|
|
165
|
+
rule: input.rule ?? null,
|
|
166
|
+
meta: input.meta,
|
|
167
|
+
};
|
|
168
|
+
return {
|
|
169
|
+
...model,
|
|
170
|
+
zonesById: {
|
|
171
|
+
...model.zonesById,
|
|
172
|
+
[zoneId]: {
|
|
173
|
+
...zone,
|
|
174
|
+
pathIds: [...zone.pathIds, input.id],
|
|
175
|
+
pathsById: {
|
|
176
|
+
...zone.pathsById,
|
|
177
|
+
[input.id]: newPath,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
export function updatePath(model, zoneId, pathId, patch) {
|
|
184
|
+
const zone = model.zonesById[zoneId];
|
|
185
|
+
if (!zone)
|
|
186
|
+
return model;
|
|
187
|
+
const path = zone.pathsById[pathId];
|
|
188
|
+
if (!path)
|
|
189
|
+
return model;
|
|
190
|
+
return {
|
|
191
|
+
...model,
|
|
192
|
+
zonesById: {
|
|
193
|
+
...model.zonesById,
|
|
194
|
+
[zoneId]: {
|
|
195
|
+
...zone,
|
|
196
|
+
pathsById: {
|
|
197
|
+
...zone.pathsById,
|
|
198
|
+
[pathId]: {
|
|
199
|
+
...path,
|
|
200
|
+
...patch,
|
|
201
|
+
rule: Object.prototype.hasOwnProperty.call(patch, "rule")
|
|
202
|
+
? (patch.rule ?? null)
|
|
203
|
+
: path.rule,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
export function removePath(model, zoneId, pathId) {
|
|
211
|
+
const zone = model.zonesById[zoneId];
|
|
212
|
+
if (!zone)
|
|
213
|
+
return model;
|
|
214
|
+
if (!zone.pathsById[pathId])
|
|
215
|
+
return model;
|
|
216
|
+
const nextPathsById = { ...zone.pathsById };
|
|
217
|
+
delete nextPathsById[pathId];
|
|
218
|
+
return {
|
|
219
|
+
...model,
|
|
220
|
+
zonesById: {
|
|
221
|
+
...model.zonesById,
|
|
222
|
+
[zoneId]: {
|
|
223
|
+
...zone,
|
|
224
|
+
pathIds: zone.pathIds.filter((id) => id !== pathId),
|
|
225
|
+
pathsById: nextPathsById,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
export function setPathTarget(model, zoneId, pathId, target) {
|
|
231
|
+
return updatePath(model, zoneId, pathId, { target });
|
|
232
|
+
}
|
|
233
|
+
export function reorderPaths(model, zoneId, nextPathIds) {
|
|
234
|
+
const zone = model.zonesById[zoneId];
|
|
235
|
+
if (!zone)
|
|
236
|
+
return model;
|
|
237
|
+
const sameLength = zone.pathIds.length === nextPathIds.length;
|
|
238
|
+
const allExist = nextPathIds.every((id) => Boolean(zone.pathsById[id]));
|
|
239
|
+
if (!sameLength || !allExist)
|
|
240
|
+
return model;
|
|
241
|
+
return {
|
|
242
|
+
...model,
|
|
243
|
+
zonesById: {
|
|
244
|
+
...model.zonesById,
|
|
245
|
+
[zoneId]: {
|
|
246
|
+
...zone,
|
|
247
|
+
pathIds: [...nextPathIds],
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
package/dist/path.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { UniverseModel, Path, Zone, ZoneId } from "./types";
|
|
2
|
+
export declare function resolvePathTarget(model: UniverseModel, path: Path): Zone | undefined;
|
|
3
|
+
export declare function getOutgoingZones(model: UniverseModel, zoneId: ZoneId): Zone[];
|
|
4
|
+
export declare function getIncomingPaths(model: UniverseModel, zoneId: ZoneId): Path[];
|
package/dist/path.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getPaths } from "./lookup";
|
|
2
|
+
export function resolvePathTarget(model, path) {
|
|
3
|
+
if (!path.target)
|
|
4
|
+
return undefined;
|
|
5
|
+
if (path.target.universeId !== model.universeId)
|
|
6
|
+
return undefined;
|
|
7
|
+
return model.zonesById[path.target.zoneId];
|
|
8
|
+
}
|
|
9
|
+
export function getOutgoingZones(model, zoneId) {
|
|
10
|
+
const zone = model.zonesById[zoneId];
|
|
11
|
+
if (!zone)
|
|
12
|
+
return [];
|
|
13
|
+
return getPaths(zone)
|
|
14
|
+
.map((path) => resolvePathTarget(model, path))
|
|
15
|
+
.filter((target) => Boolean(target));
|
|
16
|
+
}
|
|
17
|
+
export function getIncomingPaths(model, zoneId) {
|
|
18
|
+
const result = [];
|
|
19
|
+
for (const zone of Object.values(model.zonesById)) {
|
|
20
|
+
for (const path of getPaths(zone)) {
|
|
21
|
+
if (path.target &&
|
|
22
|
+
path.target.universeId === model.universeId &&
|
|
23
|
+
path.target.zoneId === zoneId) {
|
|
24
|
+
result.push(path);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
package/dist/remap.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { UniverseModel, Zone, ZoneId, PathId } from "./types";
|
|
2
|
+
export type RemapResult = {
|
|
3
|
+
zonesById: Record<ZoneId, Zone>;
|
|
4
|
+
rootZoneIds: ZoneId[];
|
|
5
|
+
zoneIdMap: Record<ZoneId, ZoneId>;
|
|
6
|
+
pathIdMap: Record<PathId, PathId>;
|
|
7
|
+
};
|
|
8
|
+
export declare function remapSubtreeIds(model: UniverseModel, sourceRootZoneId: ZoneId): RemapResult;
|
package/dist/remap.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createPathId, createZoneId } from "./ids";
|
|
2
|
+
import { flattenSubtree } from "./traversal";
|
|
3
|
+
export function remapSubtreeIds(model, sourceRootZoneId) {
|
|
4
|
+
const sourceZones = flattenSubtree(model, sourceRootZoneId);
|
|
5
|
+
const zoneIdMap = {};
|
|
6
|
+
const pathIdMap = {};
|
|
7
|
+
// 1) zone id 재발급
|
|
8
|
+
for (const zone of sourceZones) {
|
|
9
|
+
zoneIdMap[zone.id] = createZoneId();
|
|
10
|
+
}
|
|
11
|
+
// 2) path id 재발급
|
|
12
|
+
for (const zone of sourceZones) {
|
|
13
|
+
for (const pathId of zone.pathIds) {
|
|
14
|
+
const path = zone.pathsById[pathId];
|
|
15
|
+
if (!path)
|
|
16
|
+
continue;
|
|
17
|
+
pathIdMap[path.id] = createPathId();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// 3) zone clone + 참조 재작성
|
|
21
|
+
const nextZonesById = {};
|
|
22
|
+
for (const zone of sourceZones) {
|
|
23
|
+
const nextZoneId = zoneIdMap[zone.id];
|
|
24
|
+
const nextChildZoneIds = zone.childZoneIds
|
|
25
|
+
.map((childId) => zoneIdMap[childId])
|
|
26
|
+
.filter((id) => Boolean(id));
|
|
27
|
+
const nextPathIds = zone.pathIds
|
|
28
|
+
.map((pathId) => {
|
|
29
|
+
const path = zone.pathsById[pathId];
|
|
30
|
+
if (!path)
|
|
31
|
+
return undefined;
|
|
32
|
+
return pathIdMap[path.id];
|
|
33
|
+
})
|
|
34
|
+
.filter((id) => Boolean(id));
|
|
35
|
+
const nextPathsById = {};
|
|
36
|
+
for (const oldPathId of zone.pathIds) {
|
|
37
|
+
const oldPath = zone.pathsById[oldPathId];
|
|
38
|
+
if (!oldPath)
|
|
39
|
+
continue;
|
|
40
|
+
const nextPathId = pathIdMap[oldPath.id];
|
|
41
|
+
let nextTarget = oldPath.target ?? null;
|
|
42
|
+
// 내부 참조면 zoneId 재작성
|
|
43
|
+
if (nextTarget &&
|
|
44
|
+
nextTarget.universeId === model.universeId &&
|
|
45
|
+
zoneIdMap[nextTarget.zoneId]) {
|
|
46
|
+
nextTarget = {
|
|
47
|
+
...nextTarget,
|
|
48
|
+
zoneId: zoneIdMap[nextTarget.zoneId],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
nextPathsById[nextPathId] = {
|
|
52
|
+
...oldPath,
|
|
53
|
+
id: nextPathId,
|
|
54
|
+
target: nextTarget,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
nextZonesById[nextZoneId] = {
|
|
58
|
+
...zone,
|
|
59
|
+
id: nextZoneId,
|
|
60
|
+
parentZoneId: zone.parentZoneId ? zoneIdMap[zone.parentZoneId] ?? null : null,
|
|
61
|
+
childZoneIds: nextChildZoneIds,
|
|
62
|
+
pathIds: nextPathIds,
|
|
63
|
+
pathsById: nextPathsById,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
zonesById: nextZonesById,
|
|
68
|
+
rootZoneIds: [zoneIdMap[sourceRootZoneId]],
|
|
69
|
+
zoneIdMap,
|
|
70
|
+
pathIdMap,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function walkZonesDepthFirst(model, zoneId, visit) {
|
|
2
|
+
const zone = model.zonesById[zoneId];
|
|
3
|
+
if (!zone)
|
|
4
|
+
return;
|
|
5
|
+
visit(zone);
|
|
6
|
+
for (const childId of zone.childZoneIds) {
|
|
7
|
+
walkZonesDepthFirst(model, childId, visit);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function flattenSubtree(model, zoneId) {
|
|
11
|
+
const result = [];
|
|
12
|
+
walkZonesDepthFirst(model, zoneId, (zone) => {
|
|
13
|
+
result.push(zone);
|
|
14
|
+
});
|
|
15
|
+
return result;
|
|
16
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export type UniverseId = string;
|
|
2
|
+
export type ZoneId = string;
|
|
3
|
+
export type PathId = string;
|
|
4
|
+
export type ZoneType = "container" | "action" | string;
|
|
5
|
+
export type Layout = {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width?: number;
|
|
9
|
+
height?: number;
|
|
10
|
+
};
|
|
11
|
+
export type Point = {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
};
|
|
15
|
+
export type AnchorRect = Layout;
|
|
16
|
+
export type AnchorLayout = {
|
|
17
|
+
point: Point;
|
|
18
|
+
rect?: AnchorRect;
|
|
19
|
+
};
|
|
20
|
+
export type ZoneLayout = Layout & {
|
|
21
|
+
anchors: {
|
|
22
|
+
inlet: AnchorLayout;
|
|
23
|
+
outlet: AnchorLayout;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export type PathLayout = {
|
|
27
|
+
routeOffset?: Point;
|
|
28
|
+
componentLayoutsById?: Record<string, Layout>;
|
|
29
|
+
};
|
|
30
|
+
export type ZoneAction = {
|
|
31
|
+
type: string;
|
|
32
|
+
payload?: Record<string, unknown>;
|
|
33
|
+
};
|
|
34
|
+
export type PathRule = {
|
|
35
|
+
type: string;
|
|
36
|
+
payload?: Record<string, unknown>;
|
|
37
|
+
};
|
|
38
|
+
export type ZoneRef = {
|
|
39
|
+
universeId: UniverseId;
|
|
40
|
+
zoneId: ZoneId;
|
|
41
|
+
};
|
|
42
|
+
export type Path = {
|
|
43
|
+
id: PathId;
|
|
44
|
+
key: string;
|
|
45
|
+
name: string;
|
|
46
|
+
target?: ZoneRef | null;
|
|
47
|
+
rule: PathRule | null;
|
|
48
|
+
meta?: Record<string, unknown>;
|
|
49
|
+
};
|
|
50
|
+
export type Zone = {
|
|
51
|
+
id: ZoneId;
|
|
52
|
+
parentZoneId: ZoneId | null;
|
|
53
|
+
name: string;
|
|
54
|
+
zoneType: ZoneType;
|
|
55
|
+
inputDisabled?: boolean;
|
|
56
|
+
outputDisabled?: boolean;
|
|
57
|
+
childZoneIds: ZoneId[];
|
|
58
|
+
action?: ZoneAction;
|
|
59
|
+
pathIds: PathId[];
|
|
60
|
+
pathsById: Record<PathId, Path>;
|
|
61
|
+
meta?: Record<string, unknown>;
|
|
62
|
+
};
|
|
63
|
+
export type UniverseModel = {
|
|
64
|
+
version: string;
|
|
65
|
+
universeId: UniverseId;
|
|
66
|
+
rootZoneIds: ZoneId[];
|
|
67
|
+
zonesById: Record<ZoneId, Zone>;
|
|
68
|
+
meta?: Record<string, unknown>;
|
|
69
|
+
};
|
|
70
|
+
export type UniverseLayoutModel = {
|
|
71
|
+
version: string;
|
|
72
|
+
universeId: UniverseId;
|
|
73
|
+
zoneLayoutsById: Record<ZoneId, ZoneLayout>;
|
|
74
|
+
pathLayoutsById: Record<PathId, PathLayout>;
|
|
75
|
+
meta?: Record<string, unknown>;
|
|
76
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/unwrap.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { UniverseModel, ZoneId } from "./types";
|
|
2
|
+
export type UnwrapZoneResult = {
|
|
3
|
+
model: UniverseModel;
|
|
4
|
+
unwrappedZoneId: ZoneId;
|
|
5
|
+
movedChildZoneIds: ZoneId[];
|
|
6
|
+
};
|
|
7
|
+
export type CanUnwrapZoneResult = {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
reason?: "MISSING_ZONE" | "HAS_NO_CHILDREN" | "MISSING_PARENT" | "ROOT_WITHOUT_CHILDREN";
|
|
10
|
+
};
|
|
11
|
+
export declare function canUnwrapZone(model: UniverseModel, zoneId: ZoneId): CanUnwrapZoneResult;
|
|
12
|
+
export declare function unwrapZone(model: UniverseModel, zoneId: ZoneId): UnwrapZoneResult;
|
package/dist/unwrap.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { getParentZone, getZone } from "./lookup";
|
|
2
|
+
export function canUnwrapZone(model, zoneId) {
|
|
3
|
+
const zone = getZone(model, zoneId);
|
|
4
|
+
if (!zone) {
|
|
5
|
+
return { ok: false, reason: "MISSING_ZONE" };
|
|
6
|
+
}
|
|
7
|
+
if (zone.childZoneIds.length === 0) {
|
|
8
|
+
return {
|
|
9
|
+
ok: false,
|
|
10
|
+
reason: zone.parentZoneId === null ? "ROOT_WITHOUT_CHILDREN" : "HAS_NO_CHILDREN",
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
if (zone.parentZoneId !== null && !getParentZone(model, zoneId)) {
|
|
14
|
+
return { ok: false, reason: "MISSING_PARENT" };
|
|
15
|
+
}
|
|
16
|
+
return { ok: true };
|
|
17
|
+
}
|
|
18
|
+
export function unwrapZone(model, zoneId) {
|
|
19
|
+
const canUnwrap = canUnwrapZone(model, zoneId);
|
|
20
|
+
if (!canUnwrap.ok) {
|
|
21
|
+
return {
|
|
22
|
+
model,
|
|
23
|
+
unwrappedZoneId: zoneId,
|
|
24
|
+
movedChildZoneIds: [],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const zone = getZone(model, zoneId);
|
|
28
|
+
const parentZoneId = zone.parentZoneId;
|
|
29
|
+
const movedChildZoneIds = [...zone.childZoneIds];
|
|
30
|
+
let nextZonesById = { ...model.zonesById };
|
|
31
|
+
let nextRootZoneIds = [...model.rootZoneIds];
|
|
32
|
+
// 1) 자식들의 parent를 wrapper의 parent로 변경
|
|
33
|
+
for (const childZoneId of movedChildZoneIds) {
|
|
34
|
+
const child = nextZonesById[childZoneId];
|
|
35
|
+
if (!child)
|
|
36
|
+
continue;
|
|
37
|
+
nextZonesById[childZoneId] = {
|
|
38
|
+
...child,
|
|
39
|
+
parentZoneId,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// 2) 부모가 있으면 부모 childZoneIds에서 wrapper 제거하고 자식들 삽입
|
|
43
|
+
if (parentZoneId !== null) {
|
|
44
|
+
const parent = nextZonesById[parentZoneId];
|
|
45
|
+
if (parent) {
|
|
46
|
+
const wrapperIndex = parent.childZoneIds.indexOf(zoneId);
|
|
47
|
+
const before = wrapperIndex >= 0
|
|
48
|
+
? parent.childZoneIds.slice(0, wrapperIndex)
|
|
49
|
+
: parent.childZoneIds;
|
|
50
|
+
const after = wrapperIndex >= 0
|
|
51
|
+
? parent.childZoneIds.slice(wrapperIndex + 1)
|
|
52
|
+
: [];
|
|
53
|
+
nextZonesById[parentZoneId] = {
|
|
54
|
+
...parent,
|
|
55
|
+
childZoneIds: [...before, ...movedChildZoneIds, ...after],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// 3) 루트 wrapper면 rootZoneIds에서 wrapper 제거하고 자식들 삽입
|
|
61
|
+
const wrapperIndex = nextRootZoneIds.indexOf(zoneId);
|
|
62
|
+
const before = wrapperIndex >= 0
|
|
63
|
+
? nextRootZoneIds.slice(0, wrapperIndex)
|
|
64
|
+
: nextRootZoneIds;
|
|
65
|
+
const after = wrapperIndex >= 0
|
|
66
|
+
? nextRootZoneIds.slice(wrapperIndex + 1)
|
|
67
|
+
: [];
|
|
68
|
+
nextRootZoneIds = [...before, ...movedChildZoneIds, ...after];
|
|
69
|
+
}
|
|
70
|
+
// 4) wrapper 제거
|
|
71
|
+
delete nextZonesById[zoneId];
|
|
72
|
+
return {
|
|
73
|
+
model: {
|
|
74
|
+
...model,
|
|
75
|
+
rootZoneIds: nextRootZoneIds,
|
|
76
|
+
zonesById: nextZonesById,
|
|
77
|
+
},
|
|
78
|
+
unwrappedZoneId: zoneId,
|
|
79
|
+
movedChildZoneIds,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export function validateUniverseModel(model) {
|
|
2
|
+
const errors = [];
|
|
3
|
+
for (const rootId of model.rootZoneIds) {
|
|
4
|
+
if (!model.zonesById[rootId]) {
|
|
5
|
+
errors.push(`Root zone not found: ${rootId}`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
for (const zone of Object.values(model.zonesById)) {
|
|
9
|
+
if (zone.parentZoneId && !model.zonesById[zone.parentZoneId]) {
|
|
10
|
+
errors.push(`Zone "${zone.id}" has invalid parentZoneId "${zone.parentZoneId}"`);
|
|
11
|
+
}
|
|
12
|
+
for (const childId of zone.childZoneIds) {
|
|
13
|
+
if (!model.zonesById[childId]) {
|
|
14
|
+
errors.push(`Zone "${zone.id}" has invalid childZoneId "${childId}"`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const seenPathIds = new Set();
|
|
18
|
+
const seenPathKeys = new Set();
|
|
19
|
+
for (const pathId of zone.pathIds) {
|
|
20
|
+
const path = zone.pathsById[pathId];
|
|
21
|
+
if (!path) {
|
|
22
|
+
errors.push(`Zone "${zone.id}" pathIds includes missing path "${pathId}"`);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (seenPathIds.has(path.id)) {
|
|
26
|
+
errors.push(`Zone "${zone.id}" has duplicate path id "${path.id}"`);
|
|
27
|
+
}
|
|
28
|
+
seenPathIds.add(path.id);
|
|
29
|
+
if (seenPathKeys.has(path.key)) {
|
|
30
|
+
errors.push(`Zone "${zone.id}" has duplicate path key "${path.key}"`);
|
|
31
|
+
}
|
|
32
|
+
seenPathKeys.add(path.key);
|
|
33
|
+
if (path.target) {
|
|
34
|
+
if (path.target.universeId === model.universeId) {
|
|
35
|
+
if (!model.zonesById[path.target.zoneId]) {
|
|
36
|
+
errors.push(`Path "${path.id}" in zone "${zone.id}" points to missing zone "${path.target.zoneId}"`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const path of Object.values(zone.pathsById)) {
|
|
42
|
+
if (!zone.pathIds.includes(path.id)) {
|
|
43
|
+
errors.push(`Zone "${zone.id}" has path "${path.id}" in pathsById but not in pathIds`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return errors;
|
|
48
|
+
}
|
package/dist/wrap.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { UniverseModel, ZoneAction, ZoneId, ZoneType } from "./types";
|
|
2
|
+
export type CanWrapZonesResult = {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
reason?: "EMPTY_SELECTION" | "DUPLICATE_IDS" | "MISSING_ZONE" | "DIFFERENT_PARENTS";
|
|
5
|
+
};
|
|
6
|
+
export type WrapZonesWithNewParentInput = {
|
|
7
|
+
zoneIds: ZoneId[];
|
|
8
|
+
name: string;
|
|
9
|
+
zoneType?: ZoneType;
|
|
10
|
+
action?: ZoneAction;
|
|
11
|
+
meta?: Record<string, unknown>;
|
|
12
|
+
newZoneId?: ZoneId;
|
|
13
|
+
};
|
|
14
|
+
export type WrapZonesWithNewParentResult = {
|
|
15
|
+
model: UniverseModel;
|
|
16
|
+
wrapperZoneId: ZoneId;
|
|
17
|
+
};
|
|
18
|
+
export declare function canWrapZones(model: UniverseModel, zoneIds: ZoneId[]): CanWrapZonesResult;
|
|
19
|
+
export declare function wrapZonesWithNewParent(model: UniverseModel, input: WrapZonesWithNewParentInput): WrapZonesWithNewParentResult;
|