@things-factory/board-ui 10.0.0-beta.64 → 10.0.0-beta.65
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-client/pages/board-action-dispatch.d.ts +31 -0
- package/dist-client/pages/board-action-dispatch.js +80 -0
- package/dist-client/pages/board-action-dispatch.js.map +1 -0
- package/dist-client/pages/board-action-dispatch.test.d.ts +1 -0
- package/dist-client/pages/board-action-dispatch.test.js +235 -0
- package/dist-client/pages/board-action-dispatch.test.js.map +1 -0
- package/dist-client/pages/board-edit-dispatch.d.ts +74 -0
- package/dist-client/pages/board-edit-dispatch.js +299 -0
- package/dist-client/pages/board-edit-dispatch.js.map +1 -0
- package/dist-client/pages/board-edit-dispatch.test.d.ts +1 -0
- package/dist-client/pages/board-edit-dispatch.test.js +858 -0
- package/dist-client/pages/board-edit-dispatch.test.js.map +1 -0
- package/dist-client/pages/board-modeller-page.d.ts +40 -28
- package/dist-client/pages/board-modeller-page.js +206 -186
- package/dist-client/pages/board-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { mergeComponent } from '@things-factory/board-ai';
|
|
2
|
+
import { findSceneComponent } from './board-action-dispatch';
|
|
3
|
+
const NOOP_RESULT = { applied: false, inverseOps: [] };
|
|
4
|
+
/**
|
|
5
|
+
* 단일 BoardEditOp 를 things-scene 에 in-place 적용.
|
|
6
|
+
*
|
|
7
|
+
* `replace` 는 받지 않음 (호스트의 wholesale 경로). 미지원 op / 누락 컴포넌트 시
|
|
8
|
+
* `applied: false` 반환. 호스트가 missed 로 분류해 사용자 경고.
|
|
9
|
+
*/
|
|
10
|
+
export function dispatchBoardEditOp(scene, op, ctx = {}) {
|
|
11
|
+
if (!scene || !op)
|
|
12
|
+
return NOOP_RESULT;
|
|
13
|
+
const normalize = ctx.normalize ?? ((c) => c);
|
|
14
|
+
switch (op.op) {
|
|
15
|
+
case 'add': {
|
|
16
|
+
const normalized = normalize(op.component);
|
|
17
|
+
// add 의 inverse 는 새로 발급될 refid 가 필요 → scene.add 직전/직후 차집합으로 캡처.
|
|
18
|
+
const prevRefids = new Set(collectAllRefids(scene));
|
|
19
|
+
scene.add(normalized, {});
|
|
20
|
+
const newRefids = collectAllRefids(scene).filter(r => !prevRefids.has(r));
|
|
21
|
+
const inverseOps = newRefids.map(refid => ({ op: 'remove', refid }));
|
|
22
|
+
return { applied: true, inverseOps };
|
|
23
|
+
}
|
|
24
|
+
case 'remove': {
|
|
25
|
+
const target = findSceneComponent(scene, { refid: op.refid });
|
|
26
|
+
if (!target || !target.parent)
|
|
27
|
+
return NOOP_RESULT;
|
|
28
|
+
const savedModel = JSON.parse(JSON.stringify(target.model));
|
|
29
|
+
const prevSelected = scene.selected ?? [];
|
|
30
|
+
scene.selected = [target];
|
|
31
|
+
scene.remove();
|
|
32
|
+
scene.selected = prevSelected.filter((c) => c !== target);
|
|
33
|
+
return { applied: true, inverseOps: [{ op: 'add', component: savedModel }] };
|
|
34
|
+
}
|
|
35
|
+
case 'modify': {
|
|
36
|
+
const target = findSceneComponent(scene, { refid: op.refid });
|
|
37
|
+
if (!target)
|
|
38
|
+
return NOOP_RESULT;
|
|
39
|
+
const oldValues = captureOldKeys(target.model, op.patch);
|
|
40
|
+
const merged = mergeComponent(target.model, op.patch);
|
|
41
|
+
target.set(merged);
|
|
42
|
+
// change 는 자동 snapshot 안 함 → commander.execute 로 명시 push
|
|
43
|
+
scene.commander?.execute(null, false);
|
|
44
|
+
return {
|
|
45
|
+
applied: true,
|
|
46
|
+
inverseOps: [{ op: 'modify', refid: op.refid, patch: oldValues }]
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
case 'modifyBoard': {
|
|
50
|
+
const root = scene.root;
|
|
51
|
+
if (!root || typeof root.set !== 'function')
|
|
52
|
+
return NOOP_RESULT;
|
|
53
|
+
const cleanPatch = { ...(op.patch || {}) };
|
|
54
|
+
delete cleanPatch.components; // 자식 변경은 별도 op
|
|
55
|
+
const oldValues = captureOldKeys(root.model, cleanPatch);
|
|
56
|
+
const merged = mergeComponent(root.model, cleanPatch);
|
|
57
|
+
root.set(merged);
|
|
58
|
+
scene.commander?.execute(null, false);
|
|
59
|
+
return { applied: true, inverseOps: [{ op: 'modifyBoard', patch: oldValues }] };
|
|
60
|
+
}
|
|
61
|
+
case 'align': {
|
|
62
|
+
const targets = op.refids
|
|
63
|
+
.map(r => findSceneComponent(scene, { refid: r }))
|
|
64
|
+
.filter((c) => c);
|
|
65
|
+
if (targets.length < 2)
|
|
66
|
+
return NOOP_RESULT;
|
|
67
|
+
const beforeBounds = targets.map((c) => ({
|
|
68
|
+
refid: c.get('refid'),
|
|
69
|
+
left: c.get('left'),
|
|
70
|
+
top: c.get('top'),
|
|
71
|
+
width: c.get('width'),
|
|
72
|
+
height: c.get('height')
|
|
73
|
+
}));
|
|
74
|
+
const prevSelected = scene.selected ?? [];
|
|
75
|
+
scene.selected = targets;
|
|
76
|
+
scene.align(op.direction);
|
|
77
|
+
scene.selected = prevSelected;
|
|
78
|
+
// 정렬 직전 좌표를 modify inverse 시퀀스로 — 정확한 좌표 복원
|
|
79
|
+
const inverseOps = beforeBounds.map(b => ({
|
|
80
|
+
op: 'modify',
|
|
81
|
+
refid: b.refid,
|
|
82
|
+
patch: { left: b.left, top: b.top, width: b.width, height: b.height }
|
|
83
|
+
}));
|
|
84
|
+
return { applied: true, inverseOps };
|
|
85
|
+
}
|
|
86
|
+
case 'distribute': {
|
|
87
|
+
const targets = op.refids
|
|
88
|
+
.map(r => findSceneComponent(scene, { refid: r }))
|
|
89
|
+
.filter((c) => c);
|
|
90
|
+
if (targets.length < 2)
|
|
91
|
+
return NOOP_RESULT;
|
|
92
|
+
const beforeBounds = targets.map((c) => ({
|
|
93
|
+
refid: c.get('refid'),
|
|
94
|
+
left: c.get('left'),
|
|
95
|
+
top: c.get('top')
|
|
96
|
+
}));
|
|
97
|
+
const prevSelected = scene.selected ?? [];
|
|
98
|
+
scene.selected = targets;
|
|
99
|
+
// things-scene API 는 'VERTICAL' / 'HORIZONTAL' 대문자
|
|
100
|
+
scene.distribute(op.axis === 'horizontal' ? 'HORIZONTAL' : 'VERTICAL');
|
|
101
|
+
scene.selected = prevSelected;
|
|
102
|
+
const inverseOps = beforeBounds.map(b => ({
|
|
103
|
+
op: 'modify',
|
|
104
|
+
refid: b.refid,
|
|
105
|
+
patch: { left: b.left, top: b.top }
|
|
106
|
+
}));
|
|
107
|
+
return { applied: true, inverseOps };
|
|
108
|
+
}
|
|
109
|
+
case 'group': {
|
|
110
|
+
const targets = op.refids
|
|
111
|
+
.map(r => findSceneComponent(scene, { refid: r }))
|
|
112
|
+
.filter((c) => c);
|
|
113
|
+
if (targets.length < 2)
|
|
114
|
+
return NOOP_RESULT;
|
|
115
|
+
const prevRefids = new Set(collectAllRefids(scene));
|
|
116
|
+
const prevSelected = scene.selected ?? [];
|
|
117
|
+
scene.selected = targets;
|
|
118
|
+
scene.group();
|
|
119
|
+
scene.selected = prevSelected;
|
|
120
|
+
const newRefids = collectAllRefids(scene).filter(r => !prevRefids.has(r));
|
|
121
|
+
const inverseOps = newRefids.map(refid => ({ op: 'ungroup', refid }));
|
|
122
|
+
return { applied: true, inverseOps };
|
|
123
|
+
}
|
|
124
|
+
case 'ungroup': {
|
|
125
|
+
const target = findSceneComponent(scene, { refid: op.refid });
|
|
126
|
+
if (!target)
|
|
127
|
+
return NOOP_RESULT;
|
|
128
|
+
const childRefids = [];
|
|
129
|
+
const children = target.components ?? [];
|
|
130
|
+
for (const child of children) {
|
|
131
|
+
const r = child.get?.('refid');
|
|
132
|
+
if (typeof r === 'number')
|
|
133
|
+
childRefids.push(r);
|
|
134
|
+
}
|
|
135
|
+
const prevSelected = scene.selected ?? [];
|
|
136
|
+
scene.selected = [target];
|
|
137
|
+
scene.ungroup();
|
|
138
|
+
scene.selected = prevSelected.filter((c) => c !== target);
|
|
139
|
+
const inverseOps = childRefids.length >= 2 ? [{ op: 'group', refids: childRefids }] : [];
|
|
140
|
+
return { applied: true, inverseOps };
|
|
141
|
+
}
|
|
142
|
+
case 'zorder': {
|
|
143
|
+
const target = findSceneComponent(scene, { refid: op.refid });
|
|
144
|
+
if (!target)
|
|
145
|
+
return NOOP_RESULT;
|
|
146
|
+
const prevSelected = scene.selected ?? [];
|
|
147
|
+
scene.selected = [target];
|
|
148
|
+
scene.zorder(op.direction);
|
|
149
|
+
scene.selected = prevSelected;
|
|
150
|
+
// Best-effort: forward↔backward, front↔back. 정확하지 않을 수 있지만
|
|
151
|
+
// 단일 step 단순 반전이라 대부분 OK.
|
|
152
|
+
const opp = {
|
|
153
|
+
forward: 'backward',
|
|
154
|
+
backward: 'forward',
|
|
155
|
+
front: 'back',
|
|
156
|
+
back: 'front'
|
|
157
|
+
};
|
|
158
|
+
const dir = opp[op.direction];
|
|
159
|
+
const inverseOps = dir
|
|
160
|
+
? [{ op: 'zorder', refid: op.refid, direction: dir }]
|
|
161
|
+
: [];
|
|
162
|
+
return { applied: true, inverseOps };
|
|
163
|
+
}
|
|
164
|
+
case 'arrange': {
|
|
165
|
+
// Sugar layout — host 측에서 grid/row/column 위치 계산.
|
|
166
|
+
// align/distribute 와 달리 things-scene 의 native API 가 없으므로 직접 좌표 계산
|
|
167
|
+
// 해서 target.set() 으로 적용. left/top 만 변경, width/height 유지.
|
|
168
|
+
const targets = op.refids
|
|
169
|
+
.map(r => findSceneComponent(scene, { refid: r }))
|
|
170
|
+
.filter((c) => c);
|
|
171
|
+
if (targets.length < 2)
|
|
172
|
+
return NOOP_RESULT;
|
|
173
|
+
const beforePositions = targets.map((c) => ({
|
|
174
|
+
refid: c.get('refid'),
|
|
175
|
+
left: c.get('left'),
|
|
176
|
+
top: c.get('top')
|
|
177
|
+
}));
|
|
178
|
+
const sizes = targets.map((c) => ({
|
|
179
|
+
width: typeof c.get('width') === 'number' ? c.get('width') : 0,
|
|
180
|
+
height: typeof c.get('height') === 'number' ? c.get('height') : 0
|
|
181
|
+
}));
|
|
182
|
+
const positions = computeArrangePositions(op.layout, beforePositions, sizes);
|
|
183
|
+
for (let i = 0; i < targets.length; i++) {
|
|
184
|
+
const t = targets[i];
|
|
185
|
+
const pos = positions[i];
|
|
186
|
+
const merged = mergeComponent(t.model, { left: pos.left, top: pos.top });
|
|
187
|
+
t.set(merged);
|
|
188
|
+
}
|
|
189
|
+
// 단일 snapshot — N 개 modify 가 한 undo step
|
|
190
|
+
scene.commander?.execute(null, false);
|
|
191
|
+
const inverseOps = beforePositions.map(b => ({
|
|
192
|
+
op: 'modify',
|
|
193
|
+
refid: b.refid,
|
|
194
|
+
patch: { left: b.left, top: b.top }
|
|
195
|
+
}));
|
|
196
|
+
return { applied: true, inverseOps };
|
|
197
|
+
}
|
|
198
|
+
case 'replace':
|
|
199
|
+
// 'replace' 는 호스트의 wholesale 경로 — dispatcher 는 받지 않음.
|
|
200
|
+
return NOOP_RESULT;
|
|
201
|
+
default:
|
|
202
|
+
return NOOP_RESULT;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Sugar layout 위치 계산 — grid / row / column.
|
|
207
|
+
*
|
|
208
|
+
* 정책:
|
|
209
|
+
* - left/top 만 변경. width/height 는 보존 (사용자 의도).
|
|
210
|
+
* - grid: cell size = max(width)/max(height) — 컴포넌트 사이즈 다를 때 겹침 방지.
|
|
211
|
+
* Row-major 채움.
|
|
212
|
+
* - row/column: 각 컴포넌트의 실 size + gap 으로 누적. align (start/center/end) 으로
|
|
213
|
+
* cross-axis 정렬.
|
|
214
|
+
* - anchor 미지정 시 첫 컴포넌트의 현재 (left, top) — 예측 가능.
|
|
215
|
+
*
|
|
216
|
+
* 입력: layout 정의 + 각 target 의 현재 (left, top) (anchor default 용) + sizes.
|
|
217
|
+
* 출력: 각 target 의 새 (left, top). targets 와 동일 순서.
|
|
218
|
+
*
|
|
219
|
+
* Pure function — host dispatcher 외부에서도 단위 테스트 가능.
|
|
220
|
+
*/
|
|
221
|
+
export function computeArrangePositions(layout, current, sizes) {
|
|
222
|
+
if (current.length === 0)
|
|
223
|
+
return [];
|
|
224
|
+
const anchor = layout.anchor ?? { left: current[0].left, top: current[0].top };
|
|
225
|
+
const gap = typeof layout.gap === 'number' ? layout.gap : 10;
|
|
226
|
+
if (layout.type === 'grid') {
|
|
227
|
+
const cols = Math.max(1, Math.floor(layout.cols));
|
|
228
|
+
const cellW = sizes.reduce((m, s) => Math.max(m, s.width), 0);
|
|
229
|
+
const cellH = sizes.reduce((m, s) => Math.max(m, s.height), 0);
|
|
230
|
+
return current.map((_, i) => {
|
|
231
|
+
const row = Math.floor(i / cols);
|
|
232
|
+
const col = i % cols;
|
|
233
|
+
return {
|
|
234
|
+
left: anchor.left + col * (cellW + gap),
|
|
235
|
+
top: anchor.top + row * (cellH + gap)
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (layout.type === 'row') {
|
|
240
|
+
const align = layout.align ?? 'start';
|
|
241
|
+
const maxH = sizes.reduce((m, s) => Math.max(m, s.height), 0);
|
|
242
|
+
const out = [];
|
|
243
|
+
let cursor = anchor.left;
|
|
244
|
+
for (let i = 0; i < sizes.length; i++) {
|
|
245
|
+
const s = sizes[i];
|
|
246
|
+
let top = anchor.top;
|
|
247
|
+
if (align === 'center')
|
|
248
|
+
top = anchor.top + (maxH - s.height) / 2;
|
|
249
|
+
else if (align === 'end')
|
|
250
|
+
top = anchor.top + (maxH - s.height);
|
|
251
|
+
out.push({ left: cursor, top });
|
|
252
|
+
cursor += s.width + gap;
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
// column
|
|
257
|
+
const align = layout.align ?? 'start';
|
|
258
|
+
const maxW = sizes.reduce((m, s) => Math.max(m, s.width), 0);
|
|
259
|
+
const out = [];
|
|
260
|
+
let cursor = anchor.top;
|
|
261
|
+
for (let i = 0; i < sizes.length; i++) {
|
|
262
|
+
const s = sizes[i];
|
|
263
|
+
let left = anchor.left;
|
|
264
|
+
if (align === 'center')
|
|
265
|
+
left = anchor.left + (maxW - s.width) / 2;
|
|
266
|
+
else if (align === 'end')
|
|
267
|
+
left = anchor.left + (maxW - s.width);
|
|
268
|
+
out.push({ left, top: cursor });
|
|
269
|
+
cursor += s.height + gap;
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* scene 의 모든 컴포넌트 refid 수집 — add/group 의 inverse 계산용.
|
|
275
|
+
*
|
|
276
|
+
* scene.add / scene.group 직전·직후 호출해 차집합으로 새 발급 refid 식별.
|
|
277
|
+
*/
|
|
278
|
+
export function collectAllRefids(scene) {
|
|
279
|
+
const refids = [];
|
|
280
|
+
const map = scene?.rootContainer?.refidIndexMap;
|
|
281
|
+
if (map && typeof map.forEach === 'function') {
|
|
282
|
+
map.forEach((_, refid) => refids.push(refid));
|
|
283
|
+
}
|
|
284
|
+
return refids;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* patch 가 변경하려는 키들에 대해 model 의 현재 값을 deep-clone 으로 캡처.
|
|
288
|
+
* inverse modify / modifyBoard op 의 patch 로 사용. 원본에 없던 키는 null 로 보관
|
|
289
|
+
* (revert 시 명시적 null reset).
|
|
290
|
+
*/
|
|
291
|
+
export function captureOldKeys(model, patch) {
|
|
292
|
+
const out = {};
|
|
293
|
+
for (const k of Object.keys(patch || {})) {
|
|
294
|
+
const v = model?.[k];
|
|
295
|
+
out[k] = v === undefined ? null : JSON.parse(JSON.stringify(v));
|
|
296
|
+
}
|
|
297
|
+
return out;
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=board-edit-dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board-edit-dispatch.js","sourceRoot":"","sources":["../../client/pages/board-edit-dispatch.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Host 측 BoardEditOp dispatcher — AI 의 model 변경 op 를 things-scene 에 in-place\n * 적용 + revert 용 inverse op 캡처.\n *\n * 핵심:\n * - scene 의 in-place mutation API (scene.add / target.set / scene.remove 등)\n * 로 적용 → commander 가 자동 snapshot push → CMD+Z / dirty / selection 보존.\n * - 'replace' 는 부득이 wholesale 경로 (호스트가 별도 처리). dispatcher 는 받지 않음.\n * - 각 op 의 inverse 를 적용 직전/직후 scene 상태로 계산해 함께 반환 — 호스트가\n * 누적해 두면 나중에 같은 dispatcher 로 역순 적용해 revert 구현.\n *\n * Pure function — Lit element 외부에서 mock scene 으로 단위 테스트 가능.\n */\nimport type { BoardEditOp, ArrangeLayout } from '@things-factory/board-ai'\nimport { mergeComponent } from '@things-factory/board-ai'\nimport { findSceneComponent } from './board-action-dispatch'\n\nexport interface DispatchContext {\n /**\n * `add` op 의 component 를 things-scene 의 default 와 deep-merge.\n * 호스트가 BoardModeller 의 template registry 를 보고 채운다 — dispatcher 는 호출만.\n * 미지정 시 component 를 그대로 scene.add 에 전달.\n */\n normalize?: (c: any) => any\n}\n\nexport interface DispatchResult {\n /** op 가 실제로 scene 에 반영됐는지. silent no-op (없는 refid 등) → false */\n applied: boolean\n /** 적용 직전/직후 상태로 계산한 revert 용 inverse — 단일 op 가 다중 inverse 를\n * 생성할 수 있음 (예: align 1개 → modify N개, group 1개 → ungroup 1개). */\n inverseOps: BoardEditOp[]\n}\n\nconst NOOP_RESULT: DispatchResult = { applied: false, inverseOps: [] }\n\n/**\n * 단일 BoardEditOp 를 things-scene 에 in-place 적용.\n *\n * `replace` 는 받지 않음 (호스트의 wholesale 경로). 미지원 op / 누락 컴포넌트 시\n * `applied: false` 반환. 호스트가 missed 로 분류해 사용자 경고.\n */\nexport function dispatchBoardEditOp(\n scene: any,\n op: BoardEditOp,\n ctx: DispatchContext = {}\n): DispatchResult {\n if (!scene || !op) return NOOP_RESULT\n const normalize = ctx.normalize ?? ((c: any) => c)\n\n switch (op.op) {\n case 'add': {\n const normalized = normalize(op.component)\n // add 의 inverse 는 새로 발급될 refid 가 필요 → scene.add 직전/직후 차집합으로 캡처.\n const prevRefids = new Set<number>(collectAllRefids(scene))\n scene.add(normalized, {})\n const newRefids = collectAllRefids(scene).filter(r => !prevRefids.has(r))\n const inverseOps: BoardEditOp[] = newRefids.map(refid => ({ op: 'remove', refid }))\n return { applied: true, inverseOps }\n }\n\n case 'remove': {\n const target = findSceneComponent(scene, { refid: op.refid })\n if (!target || !target.parent) return NOOP_RESULT\n const savedModel = JSON.parse(JSON.stringify(target.model))\n const prevSelected = scene.selected ?? []\n scene.selected = [target]\n scene.remove()\n scene.selected = prevSelected.filter((c: any) => c !== target)\n return { applied: true, inverseOps: [{ op: 'add', component: savedModel }] }\n }\n\n case 'modify': {\n const target = findSceneComponent(scene, { refid: op.refid })\n if (!target) return NOOP_RESULT\n const oldValues = captureOldKeys(target.model, op.patch as any)\n const merged = mergeComponent(target.model, op.patch as any)\n target.set(merged)\n // change 는 자동 snapshot 안 함 → commander.execute 로 명시 push\n scene.commander?.execute(null, false)\n return {\n applied: true,\n inverseOps: [{ op: 'modify', refid: op.refid, patch: oldValues }]\n }\n }\n\n case 'modifyBoard': {\n const root = scene.root\n if (!root || typeof root.set !== 'function') return NOOP_RESULT\n const cleanPatch = { ...((op.patch as any) || {}) }\n delete cleanPatch.components // 자식 변경은 별도 op\n const oldValues = captureOldKeys(root.model, cleanPatch)\n const merged = mergeComponent(root.model, cleanPatch)\n root.set(merged)\n scene.commander?.execute(null, false)\n return { applied: true, inverseOps: [{ op: 'modifyBoard', patch: oldValues }] }\n }\n\n case 'align': {\n const targets = op.refids\n .map(r => findSceneComponent(scene, { refid: r }))\n .filter((c: any) => c)\n if (targets.length < 2) return NOOP_RESULT\n const beforeBounds = targets.map((c: any) => ({\n refid: c.get('refid'),\n left: c.get('left'),\n top: c.get('top'),\n width: c.get('width'),\n height: c.get('height')\n }))\n const prevSelected = scene.selected ?? []\n scene.selected = targets\n scene.align(op.direction)\n scene.selected = prevSelected\n // 정렬 직전 좌표를 modify inverse 시퀀스로 — 정확한 좌표 복원\n const inverseOps: BoardEditOp[] = beforeBounds.map(b => ({\n op: 'modify',\n refid: b.refid,\n patch: { left: b.left, top: b.top, width: b.width, height: b.height } as any\n }))\n return { applied: true, inverseOps }\n }\n\n case 'distribute': {\n const targets = op.refids\n .map(r => findSceneComponent(scene, { refid: r }))\n .filter((c: any) => c)\n if (targets.length < 2) return NOOP_RESULT\n const beforeBounds = targets.map((c: any) => ({\n refid: c.get('refid'),\n left: c.get('left'),\n top: c.get('top')\n }))\n const prevSelected = scene.selected ?? []\n scene.selected = targets\n // things-scene API 는 'VERTICAL' / 'HORIZONTAL' 대문자\n scene.distribute(op.axis === 'horizontal' ? 'HORIZONTAL' : 'VERTICAL')\n scene.selected = prevSelected\n const inverseOps: BoardEditOp[] = beforeBounds.map(b => ({\n op: 'modify',\n refid: b.refid,\n patch: { left: b.left, top: b.top } as any\n }))\n return { applied: true, inverseOps }\n }\n\n case 'group': {\n const targets = op.refids\n .map(r => findSceneComponent(scene, { refid: r }))\n .filter((c: any) => c)\n if (targets.length < 2) return NOOP_RESULT\n const prevRefids = new Set<number>(collectAllRefids(scene))\n const prevSelected = scene.selected ?? []\n scene.selected = targets\n scene.group()\n scene.selected = prevSelected\n const newRefids = collectAllRefids(scene).filter(r => !prevRefids.has(r))\n const inverseOps: BoardEditOp[] = newRefids.map(refid => ({ op: 'ungroup', refid }))\n return { applied: true, inverseOps }\n }\n\n case 'ungroup': {\n const target = findSceneComponent(scene, { refid: op.refid })\n if (!target) return NOOP_RESULT\n const childRefids: number[] = []\n const children = (target as any).components ?? []\n for (const child of children) {\n const r = child.get?.('refid')\n if (typeof r === 'number') childRefids.push(r)\n }\n const prevSelected = scene.selected ?? []\n scene.selected = [target]\n scene.ungroup()\n scene.selected = prevSelected.filter((c: any) => c !== target)\n const inverseOps: BoardEditOp[] =\n childRefids.length >= 2 ? [{ op: 'group', refids: childRefids }] : []\n return { applied: true, inverseOps }\n }\n\n case 'zorder': {\n const target = findSceneComponent(scene, { refid: op.refid })\n if (!target) return NOOP_RESULT\n const prevSelected = scene.selected ?? []\n scene.selected = [target]\n scene.zorder(op.direction)\n scene.selected = prevSelected\n // Best-effort: forward↔backward, front↔back. 정확하지 않을 수 있지만\n // 단일 step 단순 반전이라 대부분 OK.\n const opp: Record<string, 'front' | 'back' | 'forward' | 'backward'> = {\n forward: 'backward',\n backward: 'forward',\n front: 'back',\n back: 'front'\n }\n const dir = opp[op.direction]\n const inverseOps: BoardEditOp[] = dir\n ? [{ op: 'zorder', refid: op.refid, direction: dir }]\n : []\n return { applied: true, inverseOps }\n }\n\n case 'arrange': {\n // Sugar layout — host 측에서 grid/row/column 위치 계산.\n // align/distribute 와 달리 things-scene 의 native API 가 없으므로 직접 좌표 계산\n // 해서 target.set() 으로 적용. left/top 만 변경, width/height 유지.\n const targets = op.refids\n .map(r => findSceneComponent(scene, { refid: r }))\n .filter((c: any) => c)\n if (targets.length < 2) return NOOP_RESULT\n\n const beforePositions = targets.map((c: any) => ({\n refid: c.get('refid'),\n left: c.get('left'),\n top: c.get('top')\n }))\n const sizes = targets.map((c: any) => ({\n width: typeof c.get('width') === 'number' ? c.get('width') : 0,\n height: typeof c.get('height') === 'number' ? c.get('height') : 0\n }))\n\n const positions = computeArrangePositions(op.layout, beforePositions, sizes)\n for (let i = 0; i < targets.length; i++) {\n const t = targets[i]\n const pos = positions[i]\n const merged = mergeComponent(t.model, { left: pos.left, top: pos.top } as any)\n t.set(merged)\n }\n // 단일 snapshot — N 개 modify 가 한 undo step\n scene.commander?.execute(null, false)\n\n const inverseOps: BoardEditOp[] = beforePositions.map(b => ({\n op: 'modify',\n refid: b.refid,\n patch: { left: b.left, top: b.top } as any\n }))\n return { applied: true, inverseOps }\n }\n\n case 'replace':\n // 'replace' 는 호스트의 wholesale 경로 — dispatcher 는 받지 않음.\n return NOOP_RESULT\n\n default:\n return NOOP_RESULT\n }\n}\n\n/**\n * Sugar layout 위치 계산 — grid / row / column.\n *\n * 정책:\n * - left/top 만 변경. width/height 는 보존 (사용자 의도).\n * - grid: cell size = max(width)/max(height) — 컴포넌트 사이즈 다를 때 겹침 방지.\n * Row-major 채움.\n * - row/column: 각 컴포넌트의 실 size + gap 으로 누적. align (start/center/end) 으로\n * cross-axis 정렬.\n * - anchor 미지정 시 첫 컴포넌트의 현재 (left, top) — 예측 가능.\n *\n * 입력: layout 정의 + 각 target 의 현재 (left, top) (anchor default 용) + sizes.\n * 출력: 각 target 의 새 (left, top). targets 와 동일 순서.\n *\n * Pure function — host dispatcher 외부에서도 단위 테스트 가능.\n */\nexport function computeArrangePositions(\n layout: ArrangeLayout,\n current: Array<{ left: number; top: number }>,\n sizes: Array<{ width: number; height: number }>\n): Array<{ left: number; top: number }> {\n if (current.length === 0) return []\n const anchor = layout.anchor ?? { left: current[0].left, top: current[0].top }\n const gap = typeof layout.gap === 'number' ? layout.gap : 10\n\n if (layout.type === 'grid') {\n const cols = Math.max(1, Math.floor(layout.cols))\n const cellW = sizes.reduce((m, s) => Math.max(m, s.width), 0)\n const cellH = sizes.reduce((m, s) => Math.max(m, s.height), 0)\n return current.map((_, i) => {\n const row = Math.floor(i / cols)\n const col = i % cols\n return {\n left: anchor.left + col * (cellW + gap),\n top: anchor.top + row * (cellH + gap)\n }\n })\n }\n\n if (layout.type === 'row') {\n const align = layout.align ?? 'start'\n const maxH = sizes.reduce((m, s) => Math.max(m, s.height), 0)\n const out: Array<{ left: number; top: number }> = []\n let cursor = anchor.left\n for (let i = 0; i < sizes.length; i++) {\n const s = sizes[i]\n let top = anchor.top\n if (align === 'center') top = anchor.top + (maxH - s.height) / 2\n else if (align === 'end') top = anchor.top + (maxH - s.height)\n out.push({ left: cursor, top })\n cursor += s.width + gap\n }\n return out\n }\n\n // column\n const align = layout.align ?? 'start'\n const maxW = sizes.reduce((m, s) => Math.max(m, s.width), 0)\n const out: Array<{ left: number; top: number }> = []\n let cursor = anchor.top\n for (let i = 0; i < sizes.length; i++) {\n const s = sizes[i]\n let left = anchor.left\n if (align === 'center') left = anchor.left + (maxW - s.width) / 2\n else if (align === 'end') left = anchor.left + (maxW - s.width)\n out.push({ left, top: cursor })\n cursor += s.height + gap\n }\n return out\n}\n\n/**\n * scene 의 모든 컴포넌트 refid 수집 — add/group 의 inverse 계산용.\n *\n * scene.add / scene.group 직전·직후 호출해 차집합으로 새 발급 refid 식별.\n */\nexport function collectAllRefids(scene: any): number[] {\n const refids: number[] = []\n const map = scene?.rootContainer?.refidIndexMap\n if (map && typeof map.forEach === 'function') {\n map.forEach((_: any, refid: number) => refids.push(refid))\n }\n return refids\n}\n\n/**\n * patch 가 변경하려는 키들에 대해 model 의 현재 값을 deep-clone 으로 캡처.\n * inverse modify / modifyBoard op 의 patch 로 사용. 원본에 없던 키는 null 로 보관\n * (revert 시 명시적 null reset).\n */\nexport function captureOldKeys(model: any, patch: any): any {\n const out: any = {}\n for (const k of Object.keys(patch || {})) {\n const v = model?.[k]\n out[k] = v === undefined ? null : JSON.parse(JSON.stringify(v))\n }\n return out\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|