@operato/slot-layout-editor 10.29.14 → 10.29.15

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/CHANGELOG.md CHANGED
@@ -3,6 +3,15 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ### [10.29.15](https://github.com/hatiolab/operato/compare/v10.29.14...v10.29.15) (2026-10-09)
7
+
8
+
9
+ ### :rocket: New Features
10
+
11
+ * **slot-layout-editor:** tiers stand on anchors and keep their numbers ([59b06bd](https://github.com/hatiolab/operato/commit/59b06bd8be506008cec6dadc702656fe46af675b))
12
+
13
+
14
+
6
15
  ### [10.29.14](https://github.com/hatiolab/operato/compare/v10.29.13...v10.29.14) (2026-10-07)
7
16
 
8
17
 
@@ -4,7 +4,7 @@
4
4
  * 자리 레이아웃 편집 동작. 입력 레이아웃을 바꾸지 않고 새 레이아웃을 돌려준다.
5
5
  * 레이아웃에 호스트가 덧붙인 키(예: 실시간 주소 읽기 설정)는 그대로 보존한다.
6
6
  */
7
- import { type GridOrder, type LayoutIssue, type SlotLayout, type SlotPattern, type SlotSpec } from '@hatiolab/things-scene';
7
+ import { type GridOrder, type LayoutIssue, type SlotLayout, type SlotLayoutTier, type SlotPattern, type SlotSpec } from '@hatiolab/things-scene';
8
8
  /** 프리셋이 받는 크기. slotHeight 는 자리 허용 높이 */
9
9
  export interface PresetSize {
10
10
  width: number;
@@ -30,13 +30,13 @@ export declare function applyPreset<L extends SlotLayout>(preset: SlotLayoutPres
30
30
  layout?: L;
31
31
  issues: LayoutIssue[];
32
32
  };
33
- /** 층 설정을 복사해 맨 위 층으로 붙인다. 번호는 마지막 + 1, 높이는 받은 값. 설정만 복사한다 */
34
- export declare function copyTier<L extends SlotLayout>(layout: L, tierNo: number, elevation: number): L;
35
- /** 층을 지우고 위층 번호를 당긴다. 마지막 남은 층은 지우지 않는다 */
33
+ /** Tier 설정을 복사해 새 Tier 로 붙인다. 번호는 지금 가장 큰 번호 + 1, 배치는 placement 로 바꾼다. 점유는 복사하지 않는다 */
34
+ export declare function copyTier<L extends SlotLayout>(layout: L, tierNo: number, placement?: TierPatch): L;
35
+ /** Tier 를 지운다. 남은 Tier 의 번호는 그대로다. 마지막 남은 Tier 는 지우지 않는다 */
36
36
  export declare function removeTier<L extends SlotLayout>(layout: L, tierNo: number): L;
37
- export declare function updateTier<L extends SlotLayout>(layout: L, tierNo: number, patch: {
38
- elevation?: number;
39
- }): L;
37
+ /** Tier 가 선 Anchor, 적재 방식, Anchor 기준 배치 */
38
+ export type TierPatch = Partial<Pick<SlotLayoutTier, 'anchorId' | 'mount' | 'position' | 'rotation'>>;
39
+ export declare function updateTier<L extends SlotLayout>(layout: L, tierNo: number, patch: TierPatch): L;
40
40
  /**
41
41
  * 층의 패턴을 바꾼다. 자리 규격은 그대로 두고 자리 수·간격은 지금 배치를 이어받는다.
42
42
  * CUSTOM 으로 바꾸면 지금 자리들의 위치·규격을 그대로 적어 둔다.
@@ -70,17 +70,28 @@ export type SlotSpecPatch = Partial<SlotSpec> & {
70
70
  export declare function updateSlotSpec<L extends SlotLayout>(layout: L, tierNo: number, patch: SlotSpecPatch, slotNo?: number): L;
71
71
  /** CUSTOM 층에 자리를 하나 더한다 (마지막 자리 규격 복사, 번호는 마지막 + 1) */
72
72
  export declare function addCustomSlot<L extends SlotLayout>(layout: L, tierNo: number): L;
73
- /** CUSTOM 층의 자리를 지우고 뒤 번호를 당긴다. 마지막 남은 자리는 지우지 않는다 */
73
+ /** CUSTOM Tier 의 자리를 지운다. 남은 자리의 번호는 그대로다. 마지막 남은 자리는 지우지 않는다 */
74
74
  export declare function removeCustomSlot<L extends SlotLayout>(layout: L, tierNo: number, slotNo: number): L;
75
+ /** 바꾼 값을 적용하면 뜻이 바뀌는 주소: 위치가 바뀌는 자리, 없어지는 자리 (자리 id) */
76
+ export interface AddressImpact {
77
+ moved: string[];
78
+ gone: string[];
79
+ }
80
+ /**
81
+ * 앞 레이아웃의 주소마다 새 레이아웃에서 같은 주소가 같은 Anchor 의 같은 위치인지 본다. 놓인 Carrier 는 주소로
82
+ * 다시 읽히므로 (빈 자리로 옮겨 채우지 않는다) 여기 나온 주소의 Carrier 는 다른 곳에 그려지거나 예외가 된다.
83
+ * 어느 쪽이든 쓸 수 없는 값이면 빈 결과.
84
+ */
85
+ export declare function addressImpact(before: SlotLayout | undefined, after: SlotLayout | undefined): AddressImpact;
75
86
  export interface PreviewSlot {
76
87
  slotNo: number;
77
88
  name?: string;
78
- /** 자리 중심 (홀더 로컬). 화면 위가 -z */
89
+ /** 자리 중심 (Tier 로컬). 화면 위가 -z */
79
90
  x: number;
80
91
  z: number;
81
92
  /** 회전한 허용 공간의 외접 폭·깊이 */
82
93
  width: number;
83
94
  depth: number;
84
95
  }
85
- /** 층 하나의 자리 위치와 크기. 자리를 만들 수 없는 값이면 빈 목록 */
96
+ /** Tier 하나의 자리 위치와 크기 (Tier 로컬). 자리를 만들 수 없는 값이면 빈 목록 */
86
97
  export declare function previewTier(layout: SlotLayout | undefined, tierNo: number): PreviewSlot[];
package/dist/src/draft.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * 자리 레이아웃 편집 동작. 입력 레이아웃을 바꾸지 않고 새 레이아웃을 돌려준다.
5
5
  * 레이아웃에 호스트가 덧붙인 키(예: 실시간 주소 읽기 설정)는 그대로 보존한다.
6
6
  */
7
- import { resolveSlot, rotatedFootprint, slotCount } from '@hatiolab/things-scene';
7
+ import { resolveSlot, rotatedFootprint, slotAddresses, slotCount, slotFrame, slotIdOf, slotNumbers } from '@hatiolab/things-scene';
8
8
  // JSON 복사는 NaN·Infinity 를 null 로 바꿔 잘못된 값을 숨기므로 쓰지 않는다
9
9
  const clone = (v) => structuredClone(v);
10
10
  /** 크기·값을 검사하고 프리셋으로 레이아웃을 만든다. 모자라거나 잘못되면 layout 없이 문제 목록 */
@@ -23,30 +23,31 @@ export function applyPreset(preset, size, params = {}) {
23
23
  return { issues };
24
24
  return { layout: preset.build(size, params), issues };
25
25
  }
26
- // ── 층 ─────────────────────────────────────────────────────────────────────
27
- /** 층 설정을 복사해 맨 위 층으로 붙인다. 번호는 마지막 + 1, 높이는 받은 값. 설정만 복사한다 */
28
- export function copyTier(layout, tierNo, elevation) {
26
+ // ── Tier ───────────────────────────────────────────────────────────────────
27
+ // Tier 번호는 홀더 안의 식별 번호다. 지우거나 정렬해도 다시 매기지 않는다 (번호가 바뀌면 놓인 Carrier 의 주소가 바뀐다).
28
+ /** Tier 설정을 복사해 새 Tier 로 붙인다. 번호는 지금 가장 큰 번호 + 1, 배치는 placement 로 바꾼다. 점유는 복사하지 않는다 */
29
+ export function copyTier(layout, tierNo, placement = {}) {
29
30
  const next = clone(layout);
30
31
  const source = next.tiers.find(t => t.tierNo === tierNo);
31
32
  if (!source)
32
33
  return next;
33
- const last = next.tiers[next.tiers.length - 1];
34
- next.tiers.push({ ...clone(source), tierNo: last.tierNo + 1, elevation });
34
+ const newNo = Math.max(...next.tiers.map(t => t.tierNo)) + 1;
35
+ next.tiers.push({ ...clone(source), ...clone(placement), tierNo: newNo });
35
36
  return next;
36
37
  }
37
- /** 층을 지우고 위층 번호를 당긴다. 마지막 남은 층은 지우지 않는다 */
38
+ /** Tier 를 지운다. 남은 Tier 의 번호는 그대로다. 마지막 남은 Tier 는 지우지 않는다 */
38
39
  export function removeTier(layout, tierNo) {
39
40
  const next = clone(layout);
40
41
  if (next.tiers.length <= 1)
41
42
  return next;
42
- next.tiers = next.tiers.filter(t => t.tierNo !== tierNo).map((t, i) => ({ ...t, tierNo: i + 1 }));
43
+ next.tiers = next.tiers.filter(t => t.tierNo !== tierNo);
43
44
  return next;
44
45
  }
45
46
  export function updateTier(layout, tierNo, patch) {
46
47
  const next = clone(layout);
47
48
  const tier = next.tiers.find(t => t.tierNo === tierNo);
48
49
  if (tier)
49
- Object.assign(tier, patch);
50
+ Object.assign(tier, clone(patch));
50
51
  return next;
51
52
  }
52
53
  // ── 패턴 ───────────────────────────────────────────────────────────────────
@@ -97,7 +98,7 @@ export function setTierPattern(layout, tierNo, pattern) {
97
98
  break;
98
99
  case 'CUSTOM': {
99
100
  const slots = [];
100
- for (let n = 1; n <= count; n++) {
101
+ for (const n of slotNumbers(cur)) {
101
102
  const r = resolveSlot(layout, { tier: tierNo, slot: n });
102
103
  slots.push({
103
104
  slotNo: n,
@@ -166,35 +167,61 @@ export function addCustomSlot(layout, tierNo) {
166
167
  tier.layout.slots.push({ slotNo: 1, offset: { x: 0, z: 0 }, allowed: { width: 1, depth: 1, height: 1 } });
167
168
  return next;
168
169
  }
169
- /** CUSTOM 층의 자리를 지우고 뒤 번호를 당긴다. 마지막 남은 자리는 지우지 않는다 */
170
+ /** CUSTOM Tier 의 자리를 지운다. 남은 자리의 번호는 그대로다. 마지막 남은 자리는 지우지 않는다 */
170
171
  export function removeCustomSlot(layout, tierNo, slotNo) {
171
172
  const next = clone(layout);
172
173
  const tier = next.tiers.find(t => t.tierNo === tierNo);
173
174
  if (!tier || tier.layout.pattern !== 'CUSTOM' || tier.layout.slots.length <= 1)
174
175
  return next;
175
- tier.layout.slots = tier.layout.slots
176
- .filter(s => s.slotNo !== slotNo)
177
- .sort((a, b) => a.slotNo - b.slotNo)
178
- .map((s, i) => ({ ...s, slotNo: i + 1 }));
176
+ tier.layout.slots = tier.layout.slots.filter(s => s.slotNo !== slotNo);
179
177
  return next;
180
178
  }
181
- /** 층 하나의 자리 위치와 크기. 자리를 만들 수 없는 값이면 빈 목록 */
179
+ const sameFrame = (a, b) => a.rotation === b.rotation && a.position.x === b.position.x && a.position.y === b.position.y && a.position.z === b.position.z;
180
+ /**
181
+ * 앞 레이아웃의 주소마다 새 레이아웃에서 같은 주소가 같은 Anchor 의 같은 위치인지 본다. 놓인 Carrier 는 주소로
182
+ * 다시 읽히므로 (빈 자리로 옮겨 채우지 않는다) 여기 나온 주소의 Carrier 는 다른 곳에 그려지거나 예외가 된다.
183
+ * 어느 쪽이든 쓸 수 없는 값이면 빈 결과.
184
+ */
185
+ export function addressImpact(before, after) {
186
+ const impact = { moved: [], gone: [] };
187
+ if (!Array.isArray(before === null || before === void 0 ? void 0 : before.tiers) || !Array.isArray(after === null || after === void 0 ? void 0 : after.tiers))
188
+ return impact;
189
+ try {
190
+ for (const address of slotAddresses(before)) {
191
+ const was = resolveSlot(before, address);
192
+ const now = resolveSlot(after, address);
193
+ if (!was)
194
+ continue;
195
+ const id = slotIdOf(address);
196
+ if (!now)
197
+ impact.gone.push(id);
198
+ else if (was.anchorId !== now.anchorId || !sameFrame(slotFrame(was), slotFrame(now)))
199
+ impact.moved.push(id);
200
+ }
201
+ }
202
+ catch (_a) {
203
+ return { moved: [], gone: [] };
204
+ }
205
+ return impact;
206
+ }
207
+ /** Tier 하나의 자리 위치와 크기 (Tier 로컬). 자리를 만들 수 없는 값이면 빈 목록 */
182
208
  export function previewTier(layout, tierNo) {
183
209
  var _a;
184
210
  const tier = (_a = layout === null || layout === void 0 ? void 0 : layout.tiers) === null || _a === void 0 ? void 0 : _a.find(t => (t === null || t === void 0 ? void 0 : t.tierNo) === tierNo);
185
211
  if (!layout || !tier)
186
212
  return [];
187
- let count = 0;
213
+ let numbers = [];
188
214
  try {
189
- count = slotCount(tier.layout);
215
+ const count = slotCount(tier.layout);
216
+ if (!Number.isSafeInteger(count) || count <= 0 || count > 10000)
217
+ return [];
218
+ numbers = slotNumbers(tier.layout);
190
219
  }
191
220
  catch (_b) {
192
221
  return [];
193
222
  }
194
- if (!Number.isSafeInteger(count) || count <= 0 || count > 10000)
195
- return [];
196
223
  const out = [];
197
- for (let n = 1; n <= count; n++) {
224
+ for (const n of numbers) {
198
225
  const s = resolveSlot(layout, { tier: tierNo, slot: n });
199
226
  if (!s)
200
227
  continue;
@@ -1 +1 @@
1
- {"version":3,"file":"draft.js","sourceRoot":"","sources":["../../src/draft.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * 자리 레이아웃 편집 동작. 입력 레이아웃을 바꾸지 않고 새 레이아웃을 돌려준다.\n * 레이아웃에 호스트가 덧붙인 키(예: 실시간 주소 읽기 설정)는 그대로 보존한다.\n */\n\nimport {\n resolveSlot,\n rotatedFootprint,\n slotCount,\n type GridOrder,\n type LayoutIssue,\n type PatternSlot,\n type SlotLayout,\n type SlotPattern,\n type SlotSpec\n} from '@hatiolab/things-scene'\n\n// JSON 복사는 NaN·Infinity 를 null 로 바꿔 잘못된 값을 숨기므로 쓰지 않는다\nconst clone = <T>(v: T): T => structuredClone(v)\n\n// ── 프리셋 ──────────────────────────────────────────────────────────────────\n\n/** 프리셋이 받는 크기. slotHeight 는 자리 허용 높이 */\nexport interface PresetSize {\n width: number\n depth: number\n slotHeight: number\n}\n\n/** 프리셋이 크기 외에 더 받는 값 */\nexport interface PresetParam {\n key: string\n label: string\n /** 양의 정수만 받는다 */\n integer?: boolean\n}\n\n/** 호스트가 넘기는 프리셋 */\nexport interface SlotLayoutPreset<L extends SlotLayout = SlotLayout> {\n key: string\n label: string\n params?: PresetParam[]\n build(size: PresetSize, params: Record<string, number>): L\n}\n\n/** 크기·값을 검사하고 프리셋으로 레이아웃을 만든다. 모자라거나 잘못되면 layout 없이 문제 목록 */\nexport function applyPreset<L extends SlotLayout>(\n preset: SlotLayoutPreset<L>,\n size: Partial<PresetSize>,\n params: Record<string, unknown> = {}\n): { layout?: L; issues: LayoutIssue[] } {\n const issues: LayoutIssue[] = []\n const positive = (v: unknown) => typeof v === 'number' && Number.isFinite(v) && v > 0\n const positiveInt = (v: unknown) => Number.isSafeInteger(v) && (v as number) > 0\n for (const k of ['width', 'depth', 'slotHeight'] as const) if (!positive(size[k])) issues.push({ code: 'POSITIVE_NUMBER_REQUIRED', path: k })\n for (const p of preset.params ?? [])\n if (!(p.integer ? positiveInt(params[p.key]) : positive(params[p.key])))\n issues.push({ code: p.integer ? 'POSITIVE_INTEGER_REQUIRED' : 'POSITIVE_NUMBER_REQUIRED', path: p.key })\n if (issues.length) return { issues }\n return { layout: preset.build(size as PresetSize, params as Record<string, number>), issues }\n}\n\n// ── 층 ─────────────────────────────────────────────────────────────────────\n\n/** 층 설정을 복사해 맨 위 층으로 붙인다. 번호는 마지막 + 1, 높이는 받은 값. 설정만 복사한다 */\nexport function copyTier<L extends SlotLayout>(layout: L, tierNo: number, elevation: number): L {\n const next = clone(layout)\n const source = next.tiers.find(t => t.tierNo === tierNo)\n if (!source) return next\n const last = next.tiers[next.tiers.length - 1]\n next.tiers.push({ ...clone(source), tierNo: last.tierNo + 1, elevation })\n return next\n}\n\n/** 층을 지우고 위층 번호를 당긴다. 마지막 남은 층은 지우지 않는다 */\nexport function removeTier<L extends SlotLayout>(layout: L, tierNo: number): L {\n const next = clone(layout)\n if (next.tiers.length <= 1) return next\n next.tiers = next.tiers.filter(t => t.tierNo !== tierNo).map((t, i) => ({ ...t, tierNo: i + 1 }))\n return next\n}\n\nexport function updateTier<L extends SlotLayout>(layout: L, tierNo: number, patch: { elevation?: number }): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (tier) Object.assign(tier, patch)\n return next\n}\n\n// ── 패턴 ───────────────────────────────────────────────────────────────────\n\nconst specOf = (l: SlotPattern): SlotSpec => {\n if (l.pattern === 'CUSTOM') {\n const first = [...l.slots].sort((a, b) => a.slotNo - b.slotNo)[0]\n const { allowed, accepts, stackLimit, rotation } = first ?? { allowed: { width: 1, depth: 1, height: 1 } }\n return clone({ allowed, accepts, stackLimit, rotation })\n }\n return clone(l.slot)\n}\n\n/**\n * 층의 패턴을 바꾼다. 자리 규격은 그대로 두고 자리 수·간격은 지금 배치를 이어받는다.\n * CUSTOM 으로 바꾸면 지금 자리들의 위치·규격을 그대로 적어 둔다.\n */\nexport function setTierPattern<L extends SlotLayout>(layout: L, tierNo: number, pattern: SlotPattern['pattern']): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier || tier.layout.pattern === pattern) return next\n const cur = tier.layout\n const spec = specOf(cur)\n const count = slotCount(cur)\n\n switch (pattern) {\n case 'SINGLE':\n tier.layout = { pattern, slot: spec }\n break\n case 'ROW':\n tier.layout = {\n pattern,\n count: Math.max(count, 1),\n pitch: cur.pattern === 'ROW' ? cur.pitch : cur.pattern === 'GRID' ? cur.pitch.x : spec.allowed.width,\n axis: 'X',\n origin: 'MIN',\n slot: spec\n }\n break\n case 'GRID':\n tier.layout = {\n pattern,\n rows: cur.pattern === 'GRID' ? cur.rows : 1,\n cols: cur.pattern === 'GRID' ? cur.cols : Math.max(count, 1),\n pitch: { x: cur.pattern === 'ROW' ? cur.pitch : spec.allowed.width, z: spec.allowed.depth },\n order: 'ROW_MAJOR',\n origin: { x: 'MIN', z: 'MIN' },\n slot: spec\n }\n break\n case 'CUSTOM': {\n const slots: PatternSlot[] = []\n for (let n = 1; n <= count; n++) {\n const r = resolveSlot(layout, { tier: tierNo, slot: n })!\n slots.push({\n slotNo: n,\n offset: { ...r.offset },\n allowed: { ...r.allowed },\n ...(r.accepts ? { accepts: [...r.accepts] } : {}),\n ...(r.stackLimit !== 1 ? { stackLimit: r.stackLimit } : {}),\n ...(r.rotation ? { rotation: r.rotation } : {})\n })\n }\n tier.layout = { pattern, slots }\n break\n }\n }\n return next\n}\n\nexport interface PatternPatch {\n count?: number\n pitch?: number | { x: number; z: number }\n axis?: 'X' | 'Z'\n origin?: 'MIN' | 'MAX' | { x: 'MIN' | 'MAX'; z: 'MIN' | 'MAX' }\n rows?: number\n cols?: number\n order?: GridOrder\n}\n\n/** 패턴 값(자리 수·간격·축·번호 규칙)을 고친다. 그 패턴에 없는 키는 무시한다 */\nexport function updatePattern<L extends SlotLayout>(layout: L, tierNo: number, patch: PatternPatch): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier) return next\n const allowedKeys: Record<string, string[]> = {\n SINGLE: [],\n ROW: ['count', 'pitch', 'axis', 'origin'],\n GRID: ['rows', 'cols', 'pitch', 'order', 'origin'],\n CUSTOM: []\n }\n for (const [k, v] of Object.entries(patch))\n if (allowedKeys[tier.layout.pattern].includes(k)) (tier.layout as unknown as Record<string, unknown>)[k] = clone(v)\n return next\n}\n\nexport type SlotSpecPatch = Partial<SlotSpec> & { offset?: { x: number; z: number }; name?: string }\n\n/** SINGLE·ROW·GRID 의 공통 자리 규격, CUSTOM 이면 slotNo 자리를 고친다. undefined 값은 키를 지운다 */\nexport function updateSlotSpec<L extends SlotLayout>(layout: L, tierNo: number, patch: SlotSpecPatch, slotNo?: number): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier) return next\n const target = (tier.layout.pattern === 'CUSTOM' ? tier.layout.slots.find(s => s.slotNo === slotNo) : tier.layout.slot) as\n | Record<string, unknown>\n | undefined\n if (!target) return next\n for (const [k, v] of Object.entries(patch)) {\n if (v === undefined) delete target[k]\n else target[k] = clone(v)\n }\n if (tier.layout.pattern !== 'CUSTOM') for (const k of ['offset', 'name']) delete target[k]\n return next\n}\n\n/** CUSTOM 층에 자리를 하나 더한다 (마지막 자리 규격 복사, 번호는 마지막 + 1) */\nexport function addCustomSlot<L extends SlotLayout>(layout: L, tierNo: number): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier || tier.layout.pattern !== 'CUSTOM') return next\n const last = [...tier.layout.slots].sort((a, b) => a.slotNo - b.slotNo).pop()\n if (last) {\n const { name: _name, ...rest } = clone(last)\n tier.layout.slots.push({ ...rest, slotNo: last.slotNo + 1 })\n } else tier.layout.slots.push({ slotNo: 1, offset: { x: 0, z: 0 }, allowed: { width: 1, depth: 1, height: 1 } })\n return next\n}\n\n/** CUSTOM 층의 자리를 지우고 뒤 번호를 당긴다. 마지막 남은 자리는 지우지 않는다 */\nexport function removeCustomSlot<L extends SlotLayout>(layout: L, tierNo: number, slotNo: number): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier || tier.layout.pattern !== 'CUSTOM' || tier.layout.slots.length <= 1) return next\n tier.layout.slots = tier.layout.slots\n .filter(s => s.slotNo !== slotNo)\n .sort((a, b) => a.slotNo - b.slotNo)\n .map((s, i) => ({ ...s, slotNo: i + 1 }))\n return next\n}\n\n// ── 미리보기 ───────────────────────────────────────────────────────────────\n\nexport interface PreviewSlot {\n slotNo: number\n name?: string\n /** 자리 중심 (홀더 로컬). 화면 위가 -z */\n x: number\n z: number\n /** 회전한 허용 공간의 외접 폭·깊이 */\n width: number\n depth: number\n}\n\n/** 층 하나의 자리 위치와 크기. 자리를 만들 수 없는 값이면 빈 목록 */\nexport function previewTier(layout: SlotLayout | undefined, tierNo: number): PreviewSlot[] {\n const tier = layout?.tiers?.find(t => t?.tierNo === tierNo)\n if (!layout || !tier) return []\n let count = 0\n try {\n count = slotCount(tier.layout)\n } catch {\n return []\n }\n if (!Number.isSafeInteger(count) || count <= 0 || count > 10000) return []\n const out: PreviewSlot[] = []\n for (let n = 1; n <= count; n++) {\n const s = resolveSlot(layout, { tier: tierNo, slot: n })\n if (!s) continue\n const fp = rotatedFootprint({ width: s.allowed.width, depth: s.allowed.depth, height: s.allowed.height }, s.rotation)\n if (![s.offset.x, s.offset.z, fp.width, fp.depth].every(Number.isFinite)) return []\n out.push({ slotNo: n, name: s.name, x: s.offset.x, z: s.offset.z, width: fp.width, depth: fp.depth })\n }\n return out\n}\n"]}
1
+ {"version":3,"file":"draft.js","sourceRoot":"","sources":["../../src/draft.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * 자리 레이아웃 편집 동작. 입력 레이아웃을 바꾸지 않고 새 레이아웃을 돌려준다.\n * 레이아웃에 호스트가 덧붙인 키(예: 실시간 주소 읽기 설정)는 그대로 보존한다.\n */\n\nimport {\n resolveSlot,\n rotatedFootprint,\n slotAddresses,\n slotCount,\n slotFrame,\n slotIdOf,\n slotNumbers,\n type GridOrder,\n type LayoutIssue,\n type PatternSlot,\n type SlotLayout,\n type SlotLayoutTier,\n type SlotPattern,\n type SlotSpec\n} from '@hatiolab/things-scene'\n\n// JSON 복사는 NaN·Infinity 를 null 로 바꿔 잘못된 값을 숨기므로 쓰지 않는다\nconst clone = <T>(v: T): T => structuredClone(v)\n\n// ── 프리셋 ──────────────────────────────────────────────────────────────────\n\n/** 프리셋이 받는 크기. slotHeight 는 자리 허용 높이 */\nexport interface PresetSize {\n width: number\n depth: number\n slotHeight: number\n}\n\n/** 프리셋이 크기 외에 더 받는 값 */\nexport interface PresetParam {\n key: string\n label: string\n /** 양의 정수만 받는다 */\n integer?: boolean\n}\n\n/** 호스트가 넘기는 프리셋 */\nexport interface SlotLayoutPreset<L extends SlotLayout = SlotLayout> {\n key: string\n label: string\n params?: PresetParam[]\n build(size: PresetSize, params: Record<string, number>): L\n}\n\n/** 크기·값을 검사하고 프리셋으로 레이아웃을 만든다. 모자라거나 잘못되면 layout 없이 문제 목록 */\nexport function applyPreset<L extends SlotLayout>(\n preset: SlotLayoutPreset<L>,\n size: Partial<PresetSize>,\n params: Record<string, unknown> = {}\n): { layout?: L; issues: LayoutIssue[] } {\n const issues: LayoutIssue[] = []\n const positive = (v: unknown) => typeof v === 'number' && Number.isFinite(v) && v > 0\n const positiveInt = (v: unknown) => Number.isSafeInteger(v) && (v as number) > 0\n for (const k of ['width', 'depth', 'slotHeight'] as const) if (!positive(size[k])) issues.push({ code: 'POSITIVE_NUMBER_REQUIRED', path: k })\n for (const p of preset.params ?? [])\n if (!(p.integer ? positiveInt(params[p.key]) : positive(params[p.key])))\n issues.push({ code: p.integer ? 'POSITIVE_INTEGER_REQUIRED' : 'POSITIVE_NUMBER_REQUIRED', path: p.key })\n if (issues.length) return { issues }\n return { layout: preset.build(size as PresetSize, params as Record<string, number>), issues }\n}\n\n// ── Tier ───────────────────────────────────────────────────────────────────\n// Tier 번호는 홀더 안의 식별 번호다. 지우거나 정렬해도 다시 매기지 않는다 (번호가 바뀌면 놓인 Carrier 의 주소가 바뀐다).\n\n/** Tier 설정을 복사해 새 Tier 로 붙인다. 번호는 지금 가장 큰 번호 + 1, 배치는 placement 로 바꾼다. 점유는 복사하지 않는다 */\nexport function copyTier<L extends SlotLayout>(layout: L, tierNo: number, placement: TierPatch = {}): L {\n const next = clone(layout)\n const source = next.tiers.find(t => t.tierNo === tierNo)\n if (!source) return next\n const newNo = Math.max(...next.tiers.map(t => t.tierNo)) + 1\n next.tiers.push({ ...clone(source), ...clone(placement), tierNo: newNo })\n return next\n}\n\n/** Tier 를 지운다. 남은 Tier 의 번호는 그대로다. 마지막 남은 Tier 는 지우지 않는다 */\nexport function removeTier<L extends SlotLayout>(layout: L, tierNo: number): L {\n const next = clone(layout)\n if (next.tiers.length <= 1) return next\n next.tiers = next.tiers.filter(t => t.tierNo !== tierNo)\n return next\n}\n\n/** Tier 가 선 Anchor, 적재 방식, Anchor 기준 배치 */\nexport type TierPatch = Partial<Pick<SlotLayoutTier, 'anchorId' | 'mount' | 'position' | 'rotation'>>\n\nexport function updateTier<L extends SlotLayout>(layout: L, tierNo: number, patch: TierPatch): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (tier) Object.assign(tier, clone(patch))\n return next\n}\n\n// ── 패턴 ───────────────────────────────────────────────────────────────────\n\nconst specOf = (l: SlotPattern): SlotSpec => {\n if (l.pattern === 'CUSTOM') {\n const first = [...l.slots].sort((a, b) => a.slotNo - b.slotNo)[0]\n const { allowed, accepts, stackLimit, rotation } = first ?? { allowed: { width: 1, depth: 1, height: 1 } }\n return clone({ allowed, accepts, stackLimit, rotation })\n }\n return clone(l.slot)\n}\n\n/**\n * 층의 패턴을 바꾼다. 자리 규격은 그대로 두고 자리 수·간격은 지금 배치를 이어받는다.\n * CUSTOM 으로 바꾸면 지금 자리들의 위치·규격을 그대로 적어 둔다.\n */\nexport function setTierPattern<L extends SlotLayout>(layout: L, tierNo: number, pattern: SlotPattern['pattern']): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier || tier.layout.pattern === pattern) return next\n const cur = tier.layout\n const spec = specOf(cur)\n const count = slotCount(cur)\n\n switch (pattern) {\n case 'SINGLE':\n tier.layout = { pattern, slot: spec }\n break\n case 'ROW':\n tier.layout = {\n pattern,\n count: Math.max(count, 1),\n pitch: cur.pattern === 'ROW' ? cur.pitch : cur.pattern === 'GRID' ? cur.pitch.x : spec.allowed.width,\n axis: 'X',\n origin: 'MIN',\n slot: spec\n }\n break\n case 'GRID':\n tier.layout = {\n pattern,\n rows: cur.pattern === 'GRID' ? cur.rows : 1,\n cols: cur.pattern === 'GRID' ? cur.cols : Math.max(count, 1),\n pitch: { x: cur.pattern === 'ROW' ? cur.pitch : spec.allowed.width, z: spec.allowed.depth },\n order: 'ROW_MAJOR',\n origin: { x: 'MIN', z: 'MIN' },\n slot: spec\n }\n break\n case 'CUSTOM': {\n const slots: PatternSlot[] = []\n for (const n of slotNumbers(cur)) {\n const r = resolveSlot(layout, { tier: tierNo, slot: n })!\n slots.push({\n slotNo: n,\n offset: { ...r.offset },\n allowed: { ...r.allowed },\n ...(r.accepts ? { accepts: [...r.accepts] } : {}),\n ...(r.stackLimit !== 1 ? { stackLimit: r.stackLimit } : {}),\n ...(r.rotation ? { rotation: r.rotation } : {})\n })\n }\n tier.layout = { pattern, slots }\n break\n }\n }\n return next\n}\n\nexport interface PatternPatch {\n count?: number\n pitch?: number | { x: number; z: number }\n axis?: 'X' | 'Z'\n origin?: 'MIN' | 'MAX' | { x: 'MIN' | 'MAX'; z: 'MIN' | 'MAX' }\n rows?: number\n cols?: number\n order?: GridOrder\n}\n\n/** 패턴 값(자리 수·간격·축·번호 규칙)을 고친다. 그 패턴에 없는 키는 무시한다 */\nexport function updatePattern<L extends SlotLayout>(layout: L, tierNo: number, patch: PatternPatch): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier) return next\n const allowedKeys: Record<string, string[]> = {\n SINGLE: [],\n ROW: ['count', 'pitch', 'axis', 'origin'],\n GRID: ['rows', 'cols', 'pitch', 'order', 'origin'],\n CUSTOM: []\n }\n for (const [k, v] of Object.entries(patch))\n if (allowedKeys[tier.layout.pattern].includes(k)) (tier.layout as unknown as Record<string, unknown>)[k] = clone(v)\n return next\n}\n\nexport type SlotSpecPatch = Partial<SlotSpec> & { offset?: { x: number; z: number }; name?: string }\n\n/** SINGLE·ROW·GRID 의 공통 자리 규격, CUSTOM 이면 slotNo 자리를 고친다. undefined 값은 키를 지운다 */\nexport function updateSlotSpec<L extends SlotLayout>(layout: L, tierNo: number, patch: SlotSpecPatch, slotNo?: number): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier) return next\n const target = (tier.layout.pattern === 'CUSTOM' ? tier.layout.slots.find(s => s.slotNo === slotNo) : tier.layout.slot) as\n | Record<string, unknown>\n | undefined\n if (!target) return next\n for (const [k, v] of Object.entries(patch)) {\n if (v === undefined) delete target[k]\n else target[k] = clone(v)\n }\n if (tier.layout.pattern !== 'CUSTOM') for (const k of ['offset', 'name']) delete target[k]\n return next\n}\n\n/** CUSTOM 층에 자리를 하나 더한다 (마지막 자리 규격 복사, 번호는 마지막 + 1) */\nexport function addCustomSlot<L extends SlotLayout>(layout: L, tierNo: number): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier || tier.layout.pattern !== 'CUSTOM') return next\n const last = [...tier.layout.slots].sort((a, b) => a.slotNo - b.slotNo).pop()\n if (last) {\n const { name: _name, ...rest } = clone(last)\n tier.layout.slots.push({ ...rest, slotNo: last.slotNo + 1 })\n } else tier.layout.slots.push({ slotNo: 1, offset: { x: 0, z: 0 }, allowed: { width: 1, depth: 1, height: 1 } })\n return next\n}\n\n/** CUSTOM Tier 의 자리를 지운다. 남은 자리의 번호는 그대로다. 마지막 남은 자리는 지우지 않는다 */\nexport function removeCustomSlot<L extends SlotLayout>(layout: L, tierNo: number, slotNo: number): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier || tier.layout.pattern !== 'CUSTOM' || tier.layout.slots.length <= 1) return next\n tier.layout.slots = tier.layout.slots.filter(s => s.slotNo !== slotNo)\n return next\n}\n\n// ── 주소 영향 ──────────────────────────────────────────────────────────────\n\n/** 바꾼 값을 적용하면 뜻이 바뀌는 주소: 위치가 바뀌는 자리, 없어지는 자리 (자리 id) */\nexport interface AddressImpact {\n moved: string[]\n gone: string[]\n}\n\nconst sameFrame = (a: ReturnType<typeof slotFrame>, b: ReturnType<typeof slotFrame>) =>\n a.rotation === b.rotation && a.position.x === b.position.x && a.position.y === b.position.y && a.position.z === b.position.z\n\n/**\n * 앞 레이아웃의 주소마다 새 레이아웃에서 같은 주소가 같은 Anchor 의 같은 위치인지 본다. 놓인 Carrier 는 주소로\n * 다시 읽히므로 (빈 자리로 옮겨 채우지 않는다) 여기 나온 주소의 Carrier 는 다른 곳에 그려지거나 예외가 된다.\n * 어느 쪽이든 쓸 수 없는 값이면 빈 결과.\n */\nexport function addressImpact(before: SlotLayout | undefined, after: SlotLayout | undefined): AddressImpact {\n const impact: AddressImpact = { moved: [], gone: [] }\n if (!Array.isArray(before?.tiers) || !Array.isArray(after?.tiers)) return impact\n try {\n for (const address of slotAddresses(before!)) {\n const was = resolveSlot(before!, address)\n const now = resolveSlot(after!, address)\n if (!was) continue\n const id = slotIdOf(address)\n if (!now) impact.gone.push(id)\n else if (was.anchorId !== now.anchorId || !sameFrame(slotFrame(was), slotFrame(now))) impact.moved.push(id)\n }\n } catch {\n return { moved: [], gone: [] }\n }\n return impact\n}\n\n// ── 미리보기 ───────────────────────────────────────────────────────────────\n\nexport interface PreviewSlot {\n slotNo: number\n name?: string\n /** 자리 중심 (Tier 로컬). 화면 위가 -z */\n x: number\n z: number\n /** 회전한 허용 공간의 외접 폭·깊이 */\n width: number\n depth: number\n}\n\n/** Tier 하나의 자리 위치와 크기 (Tier 로컬). 자리를 만들 수 없는 값이면 빈 목록 */\nexport function previewTier(layout: SlotLayout | undefined, tierNo: number): PreviewSlot[] {\n const tier = layout?.tiers?.find(t => t?.tierNo === tierNo)\n if (!layout || !tier) return []\n let numbers: number[] = []\n try {\n const count = slotCount(tier.layout)\n if (!Number.isSafeInteger(count) || count <= 0 || count > 10000) return []\n numbers = slotNumbers(tier.layout)\n } catch {\n return []\n }\n const out: PreviewSlot[] = []\n for (const n of numbers) {\n const s = resolveSlot(layout, { tier: tierNo, slot: n })\n if (!s) continue\n const fp = rotatedFootprint({ width: s.allowed.width, depth: s.allowed.depth, height: s.allowed.height }, s.rotation)\n if (![s.offset.x, s.offset.z, fp.width, fp.depth].every(Number.isFinite)) return []\n out.push({ slotNo: n, name: s.name, x: s.offset.x, z: s.offset.z, width: fp.width, depth: fp.depth })\n }\n return out\n}\n"]}
@@ -4,7 +4,7 @@
4
4
  * 검증 문제(코드·경로·매개변수)를 화면 문구로. 번역 키는 label.slot-layout.issue.{코드} 이고,
5
5
  * 번역이 없으면 아래 영어 기본 문구를 쓴다. 호스트 고유 코드는 호스트가 번역을 넣거나 issueText 옵션으로 바꾼다.
6
6
  */
7
- import type { LayoutIssueCode } from '@hatiolab/things-scene';
7
+ import type { LayoutIssueCode, SlotSpaceCode } from '@hatiolab/things-scene';
8
8
  /** 문제 하나. 호스트 고유 코드도 같은 모양으로 온다 */
9
9
  export interface EditorIssue {
10
10
  code: string;
@@ -14,5 +14,8 @@ export interface EditorIssue {
14
14
  /** things-scene 검증 코드의 기본 문구. 자리표시자가 중괄호 하나라서 문구에 중괄호 글자를 쓰지 않는다 */
15
15
  export declare const ISSUE_TEXT: Record<LayoutIssueCode, string>;
16
16
  export declare const t: (key: string, fallback: string, opts?: Record<string, unknown>) => string;
17
+ /** 자리 공간 경고의 기본 문구. 번역 키는 label.slot-layout.space.{코드} */
18
+ export declare const SPACE_TEXT: Record<SlotSpaceCode, string>;
19
+ export declare function spaceWarningText(warning: EditorIssue): string;
17
20
  /** 문제 하나를 화면 문구로. 앞에 값의 위치를 붙인다 */
18
21
  export declare function issueText(issue: EditorIssue): string;
@@ -9,8 +9,9 @@ import { i18next } from '@operato/i18n';
9
9
  export const ISSUE_TEXT = {
10
10
  TIERS_REQUIRED: 'At least one tier is required',
11
11
  MOUNT_INVALID: 'Must be ON, IN or HANG',
12
- TIER_NUMBER_SEQUENCE: 'Tier numbers must run 1, 2, 3… without gaps',
13
- ELEVATION_NOT_ASCENDING: 'Must be higher than the tier below',
12
+ ANCHOR_REQUIRED: 'Choose an anchor',
13
+ ANCHOR_UNKNOWN: 'This holder has no anchor {anchorId}',
14
+ TIER_NUMBER_DUPLICATE: 'Tier {tierNo} is used twice',
14
15
  FINITE_NUMBER_REQUIRED: 'A number is required',
15
16
  POSITIVE_NUMBER_REQUIRED: 'A number greater than 0 is required',
16
17
  POSITIVE_INTEGER_REQUIRED: 'A whole number of 1 or more is required',
@@ -18,10 +19,22 @@ export const ISSUE_TEXT = {
18
19
  OBJECT_REQUIRED: 'The value is not a layout',
19
20
  AXIS_INVALID: 'Must be X or Z',
20
21
  PATTERN_INVALID: 'Must be SINGLE, ROW, GRID or CUSTOM',
21
- SLOT_NUMBER_SEQUENCE: 'Slot numbers must run 1, 2, 3… without gaps',
22
+ SLOTS_REQUIRED: 'At least one slot is required',
23
+ SLOT_NUMBER_DUPLICATE: 'Slot {slotNo} is used twice',
22
24
  NOT_PERSISTABLE: 'Cannot be saved ({value})'
23
25
  };
24
26
  export const t = (key, fallback, opts = {}) => i18next.t(`label.slot-layout.${key}`, { defaultValue: fallback, ...opts });
27
+ /** 자리 공간 경고의 기본 문구. 번역 키는 label.slot-layout.space.{코드} */
28
+ export const SPACE_TEXT = {
29
+ SLOT_OVERLAP: 'Tier {tier} slot {slot} overlaps tier {otherTier} slot {otherSlot}',
30
+ SLOT_OUTSIDE_ANCHOR: 'Tier {tier} slot {slot} reaches past its anchor face',
31
+ ANCHOR_SPACE_UNKNOWN: 'Anchor {anchorId} gives no face size: room not checked'
32
+ };
33
+ export function spaceWarningText(warning) {
34
+ var _a;
35
+ const fallback = (_a = SPACE_TEXT[warning.code]) !== null && _a !== void 0 ? _a : warning.code;
36
+ return t(`space.${warning.code}`, fallback, { ...warning.params, interpolation: { escapeValue: false } });
37
+ }
25
38
  /** 문제 하나를 화면 문구로. 앞에 값의 위치를 붙인다 */
26
39
  export function issueText(issue) {
27
40
  var _a;
@@ -1 +1 @@
1
- {"version":3,"file":"issue-text.js","sourceRoot":"","sources":["../../src/issue-text.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAUvC,oEAAoE;AACpE,MAAM,CAAC,MAAM,UAAU,GAAoC;IACzD,cAAc,EAAE,+BAA+B;IAC/C,aAAa,EAAE,wBAAwB;IACvC,oBAAoB,EAAE,6CAA6C;IACnE,uBAAuB,EAAE,oCAAoC;IAC7D,sBAAsB,EAAE,sBAAsB;IAC9C,wBAAwB,EAAE,qCAAqC;IAC/D,yBAAyB,EAAE,yCAAyC;IACpE,cAAc,EAAE,oBAAoB;IACpC,eAAe,EAAE,2BAA2B;IAC5C,YAAY,EAAE,gBAAgB;IAC9B,eAAe,EAAE,qCAAqC;IACtD,oBAAoB,EAAE,6CAA6C;IACnE,eAAe,EAAE,2BAA2B;CAC7C,CAAA;AAED,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAW,EAAE,QAAgB,EAAE,OAAgC,EAAE,EAAE,EAAE,CACrF,OAAO,CAAC,CAAC,CAAC,qBAAqB,GAAG,EAAE,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,CAAC,CAAA;AAE5E,mCAAmC;AACnC,MAAM,UAAU,SAAS,CAAC,KAAkB;;IAC1C,MAAM,QAAQ,GAAG,MAAC,UAAqC,CAAC,KAAK,CAAC,IAAI,CAAC,mCAAI,KAAK,CAAC,IAAI,CAAA;IACjF,MAAM,IAAI,GAAG,CAAC,CAAC,SAAS,KAAK,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;IAC3G,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACrD,CAAC","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * 검증 문제(코드·경로·매개변수)를 화면 문구로. 번역 키는 label.slot-layout.issue.{코드} 이고,\n * 번역이 없으면 아래 영어 기본 문구를 쓴다. 호스트 고유 코드는 호스트가 번역을 넣거나 issueText 옵션으로 바꾼다.\n */\n\nimport { i18next } from '@operato/i18n'\nimport type { LayoutIssueCode } from '@hatiolab/things-scene'\n\n/** 문제 하나. 호스트 고유 코드도 같은 모양으로 온다 */\nexport interface EditorIssue {\n code: string\n path: string\n params?: Record<string, string | number>\n}\n\n/** things-scene 검증 코드의 기본 문구. 자리표시자가 중괄호 하나라서 문구에 중괄호 글자를 쓰지 않는다 */\nexport const ISSUE_TEXT: Record<LayoutIssueCode, string> = {\n TIERS_REQUIRED: 'At least one tier is required',\n MOUNT_INVALID: 'Must be ON, IN or HANG',\n TIER_NUMBER_SEQUENCE: 'Tier numbers must run 1, 2, 3… without gaps',\n ELEVATION_NOT_ASCENDING: 'Must be higher than the tier below',\n FINITE_NUMBER_REQUIRED: 'A number is required',\n POSITIVE_NUMBER_REQUIRED: 'A number greater than 0 is required',\n POSITIVE_INTEGER_REQUIRED: 'A whole number of 1 or more is required',\n ARRAY_REQUIRED: 'A list is required',\n OBJECT_REQUIRED: 'The value is not a layout',\n AXIS_INVALID: 'Must be X or Z',\n PATTERN_INVALID: 'Must be SINGLE, ROW, GRID or CUSTOM',\n SLOT_NUMBER_SEQUENCE: 'Slot numbers must run 1, 2, 3… without gaps',\n NOT_PERSISTABLE: 'Cannot be saved ({value})'\n}\n\nexport const t = (key: string, fallback: string, opts: Record<string, unknown> = {}) =>\n i18next.t(`label.slot-layout.${key}`, { defaultValue: fallback, ...opts })\n\n/** 문제 하나를 화면 문구로. 앞에 값의 위치를 붙인다 */\nexport function issueText(issue: EditorIssue): string {\n const fallback = (ISSUE_TEXT as Record<string, string>)[issue.code] ?? issue.code\n const text = t(`issue.${issue.code}`, fallback, { ...issue.params, interpolation: { escapeValue: false } })\n return issue.path ? `${issue.path}: ${text}` : text\n}\n"]}
1
+ {"version":3,"file":"issue-text.js","sourceRoot":"","sources":["../../src/issue-text.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAUvC,oEAAoE;AACpE,MAAM,CAAC,MAAM,UAAU,GAAoC;IACzD,cAAc,EAAE,+BAA+B;IAC/C,aAAa,EAAE,wBAAwB;IACvC,eAAe,EAAE,kBAAkB;IACnC,cAAc,EAAE,sCAAsC;IACtD,qBAAqB,EAAE,6BAA6B;IACpD,sBAAsB,EAAE,sBAAsB;IAC9C,wBAAwB,EAAE,qCAAqC;IAC/D,yBAAyB,EAAE,yCAAyC;IACpE,cAAc,EAAE,oBAAoB;IACpC,eAAe,EAAE,2BAA2B;IAC5C,YAAY,EAAE,gBAAgB;IAC9B,eAAe,EAAE,qCAAqC;IACtD,cAAc,EAAE,+BAA+B;IAC/C,qBAAqB,EAAE,6BAA6B;IACpD,eAAe,EAAE,2BAA2B;CAC7C,CAAA;AAED,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAW,EAAE,QAAgB,EAAE,OAAgC,EAAE,EAAE,EAAE,CACrF,OAAO,CAAC,CAAC,CAAC,qBAAqB,GAAG,EAAE,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,CAAC,CAAA;AAE5E,0DAA0D;AAC1D,MAAM,CAAC,MAAM,UAAU,GAAkC;IACvD,YAAY,EAAE,oEAAoE;IAClF,mBAAmB,EAAE,sDAAsD;IAC3E,oBAAoB,EAAE,wDAAwD;CAC/E,CAAA;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAoB;;IACnD,MAAM,QAAQ,GAAG,MAAC,UAAqC,CAAC,OAAO,CAAC,IAAI,CAAC,mCAAI,OAAO,CAAC,IAAI,CAAA;IACrF,OAAO,CAAC,CAAC,SAAS,OAAO,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;AAC3G,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,SAAS,CAAC,KAAkB;;IAC1C,MAAM,QAAQ,GAAG,MAAC,UAAqC,CAAC,KAAK,CAAC,IAAI,CAAC,mCAAI,KAAK,CAAC,IAAI,CAAA;IACjF,MAAM,IAAI,GAAG,CAAC,CAAC,SAAS,KAAK,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;IAC3G,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACrD,CAAC","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * 검증 문제(코드·경로·매개변수)를 화면 문구로. 번역 키는 label.slot-layout.issue.{코드} 이고,\n * 번역이 없으면 아래 영어 기본 문구를 쓴다. 호스트 고유 코드는 호스트가 번역을 넣거나 issueText 옵션으로 바꾼다.\n */\n\nimport { i18next } from '@operato/i18n'\nimport type { LayoutIssueCode, SlotSpaceCode } from '@hatiolab/things-scene'\n\n/** 문제 하나. 호스트 고유 코드도 같은 모양으로 온다 */\nexport interface EditorIssue {\n code: string\n path: string\n params?: Record<string, string | number>\n}\n\n/** things-scene 검증 코드의 기본 문구. 자리표시자가 중괄호 하나라서 문구에 중괄호 글자를 쓰지 않는다 */\nexport const ISSUE_TEXT: Record<LayoutIssueCode, string> = {\n TIERS_REQUIRED: 'At least one tier is required',\n MOUNT_INVALID: 'Must be ON, IN or HANG',\n ANCHOR_REQUIRED: 'Choose an anchor',\n ANCHOR_UNKNOWN: 'This holder has no anchor {anchorId}',\n TIER_NUMBER_DUPLICATE: 'Tier {tierNo} is used twice',\n FINITE_NUMBER_REQUIRED: 'A number is required',\n POSITIVE_NUMBER_REQUIRED: 'A number greater than 0 is required',\n POSITIVE_INTEGER_REQUIRED: 'A whole number of 1 or more is required',\n ARRAY_REQUIRED: 'A list is required',\n OBJECT_REQUIRED: 'The value is not a layout',\n AXIS_INVALID: 'Must be X or Z',\n PATTERN_INVALID: 'Must be SINGLE, ROW, GRID or CUSTOM',\n SLOTS_REQUIRED: 'At least one slot is required',\n SLOT_NUMBER_DUPLICATE: 'Slot {slotNo} is used twice',\n NOT_PERSISTABLE: 'Cannot be saved ({value})'\n}\n\nexport const t = (key: string, fallback: string, opts: Record<string, unknown> = {}) =>\n i18next.t(`label.slot-layout.${key}`, { defaultValue: fallback, ...opts })\n\n/** 자리 공간 경고의 기본 문구. 번역 키는 label.slot-layout.space.{코드} */\nexport const SPACE_TEXT: Record<SlotSpaceCode, string> = {\n SLOT_OVERLAP: 'Tier {tier} slot {slot} overlaps tier {otherTier} slot {otherSlot}',\n SLOT_OUTSIDE_ANCHOR: 'Tier {tier} slot {slot} reaches past its anchor face',\n ANCHOR_SPACE_UNKNOWN: 'Anchor {anchorId} gives no face size: room not checked'\n}\n\nexport function spaceWarningText(warning: EditorIssue): string {\n const fallback = (SPACE_TEXT as Record<string, string>)[warning.code] ?? warning.code\n return t(`space.${warning.code}`, fallback, { ...warning.params, interpolation: { escapeValue: false } })\n}\n\n/** 문제 하나를 화면 문구로. 앞에 값의 위치를 붙인다 */\nexport function issueText(issue: EditorIssue): string {\n const fallback = (ISSUE_TEXT as Record<string, string>)[issue.code] ?? issue.code\n const text = t(`issue.${issue.code}`, fallback, { ...issue.params, interpolation: { escapeValue: false } })\n return issue.path ? `${issue.path}: ${text}` : text\n}\n"]}
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * @license Copyright © HatioLab Inc. All rights reserved.
3
3
  *
4
- * 자리 레이아웃 편집기. 홀더의 층·자리 구성을 고치고, 검증하고, 미리 보고, [적용]으로 알린다.
4
+ * 자리 레이아웃 편집기. 홀더의 Tier·Slot 구성을 고치고, 검증하고, 미리 보고, [적용]으로 알린다.
5
+ * Tier 는 홀더가 주는 Anchor 하나에 서고, 그 Anchor 기준 위치·회전과 적재 방식(mount)을 갖는다.
5
6
  *
6
7
  * 컴포넌트 상태를 직접 바꾸지 않는다. 호스트에 두 가지만 알린다:
7
8
  * change — 적용한 레이아웃 (this.value)
@@ -9,7 +10,7 @@
9
10
  * 기본값을 쓰게 된 뒤 보일 값은 호스트가 options.effective 로 다시 준다.
10
11
  */
11
12
  import { LitElement } from 'lit';
12
- import { type SlotLayout, type SlotMount } from '@hatiolab/things-scene';
13
+ import { type SlotLayout, type SlotSpaceOptions } from '@hatiolab/things-scene';
13
14
  import { type PresetSize, type SlotLayoutPreset } from './draft.js';
14
15
  import { type EditorIssue } from './issue-text.js';
15
16
  export interface SlotLayoutEditorNote {
@@ -27,8 +28,13 @@ export interface SlotLayoutEditorOptions<L extends SlotLayout = SlotLayout> {
27
28
  presetSize?: Partial<PresetSize>;
28
29
  /** 상태 문구 (값의 출처, 단위 상태 등) */
29
30
  notes?: SlotLayoutEditorNote[];
30
- /** 프리셋 없이 새로 시작할 때의 mount. 기본 ON */
31
- mount?: SlotMount;
31
+ /** 홀더가 주는 Anchor. 주면 Tier 는 이 중 하나를 고르고, 없는 Anchor 는 문제로 보인다 */
32
+ anchors?: {
33
+ id: string;
34
+ label?: string;
35
+ }[];
36
+ /** Anchor 가 주는 적재면 크기 (Anchor 기준 X·Z). 주면 자리가 면 밖으로 나가는지와 확인 불가를 알린다 */
37
+ anchorFaces?: SlotSpaceOptions['anchorFaces'];
32
38
  /** 호스트 고유 검증 (경로는 레이아웃 기준) */
33
39
  validate?: (layout: L) => EditorIssue[];
34
40
  /** 문제 문구. undefined 를 돌려주면 기본 문구 */
@@ -48,21 +54,30 @@ export declare class OxSlotLayoutEditor extends OxSlotLayoutEditor_base {
48
54
  presetKey: string;
49
55
  presetSize: Partial<PresetSize>;
50
56
  presetParams: Record<string, number>;
51
- copyElevation: number;
52
57
  willUpdate(changed: Map<string, unknown>): void;
53
58
  /** 편집 중인 값을 버리고 개별 설정(없으면 호스트 기본값)에서 다시 시작한다 */
54
59
  reset(): void;
55
60
  get issues(): EditorIssue[];
61
+ /** 자리끼리 겹치거나 면 밖으로 나가는 설정 경고. 적용을 막지 않는다. 적재면 크기를 주지 않은 호스트에는 확인 불가를 보이지 않는다 */
62
+ get warnings(): EditorIssue[];
63
+ /** 적용하면 뜻이 바뀌는 주소 (지금 적용된 값 기준) */
64
+ get impact(): import("./draft.js").AddressImpact;
56
65
  private _text;
57
66
  private _edit;
58
67
  private _apply;
59
68
  private _useDefault;
60
69
  render(): import("lit-html").TemplateResult<1>;
61
70
  private _renderPreset;
62
- /** 프리셋으로 다시 시작한다. 지금 값에 덧붙은 호스트 키(층·자리 밖의 키)는 가져온다 */
71
+ /** 적용 전에 뜻이 바뀌는 주소를 보인다. 놓인 Carrier 는 주소로 다시 읽혀 다른 자리에 그려지거나 예외가 된다 */
72
+ private _renderImpact;
73
+ /** 프리셋으로 다시 시작한다. 지금 값에 덧붙은 호스트 키(Tier 밖의 키)는 가져온다 */
63
74
  private _startFrom;
64
75
  private _renderDraft;
76
+ /** 복사한 Tier 를 골라 둔다. 같은 자리에 겹쳐 생기므로 바로 이어서 배치를 고친다 */
77
+ private _copyTier;
65
78
  private _renderTier;
79
+ /** Tier 가 선 Anchor, 적재 방식, Anchor 기준 위치·회전 */
80
+ private _renderPlacement;
66
81
  /** 받는 타입: 모든 타입(accepts 생략) / 지정 타입만(빈 목록이면 아무것도 받지 않음) */
67
82
  private _acceptsEditor;
68
83
  private _renderSpec;