@operato/slot-layout-editor 0.0.0-stage → 10.29.14
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 +11 -0
- package/LICENSE +21 -0
- package/dist/src/draft.d.ts +86 -0
- package/dist/src/draft.js +208 -0
- package/dist/src/draft.js.map +1 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/issue-text.d.ts +18 -0
- package/dist/src/issue-text.js +32 -0
- package/dist/src/issue-text.js.map +1 -0
- package/dist/src/ox-property-editor-slot-layout.d.ts +37 -0
- package/dist/src/ox-property-editor-slot-layout.js +74 -0
- package/dist/src/ox-property-editor-slot-layout.js.map +1 -0
- package/dist/src/ox-slot-layout-editor.d.ts +78 -0
- package/dist/src/ox-slot-layout-editor.js +436 -0
- package/dist/src/ox-slot-layout-editor.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +55 -4
- package/translations/en.json +64 -0
- package/translations/ja.json +64 -0
- package/translations/ko.json +64 -0
- package/translations/ms.json +64 -0
- package/translations/zh.json +64 -0
- package/README.md +0 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
### [10.29.14](https://github.com/hatiolab/operato/compare/v10.29.13...v10.29.14) (2026-10-07)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### :rocket: New Features
|
|
10
|
+
|
|
11
|
+
* **slot-layout-editor:** an editor for a holder's tiers and slots ([b7a13a5](https://github.com/hatiolab/operato/commit/b7a13a50ebee71a8dfd3ca0686db410dea3fb0d8))
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ox-property-panel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* 자리 레이아웃 편집 동작. 입력 레이아웃을 바꾸지 않고 새 레이아웃을 돌려준다.
|
|
5
|
+
* 레이아웃에 호스트가 덧붙인 키(예: 실시간 주소 읽기 설정)는 그대로 보존한다.
|
|
6
|
+
*/
|
|
7
|
+
import { type GridOrder, type LayoutIssue, type SlotLayout, type SlotPattern, type SlotSpec } from '@hatiolab/things-scene';
|
|
8
|
+
/** 프리셋이 받는 크기. slotHeight 는 자리 허용 높이 */
|
|
9
|
+
export interface PresetSize {
|
|
10
|
+
width: number;
|
|
11
|
+
depth: number;
|
|
12
|
+
slotHeight: number;
|
|
13
|
+
}
|
|
14
|
+
/** 프리셋이 크기 외에 더 받는 값 */
|
|
15
|
+
export interface PresetParam {
|
|
16
|
+
key: string;
|
|
17
|
+
label: string;
|
|
18
|
+
/** 양의 정수만 받는다 */
|
|
19
|
+
integer?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** 호스트가 넘기는 프리셋 */
|
|
22
|
+
export interface SlotLayoutPreset<L extends SlotLayout = SlotLayout> {
|
|
23
|
+
key: string;
|
|
24
|
+
label: string;
|
|
25
|
+
params?: PresetParam[];
|
|
26
|
+
build(size: PresetSize, params: Record<string, number>): L;
|
|
27
|
+
}
|
|
28
|
+
/** 크기·값을 검사하고 프리셋으로 레이아웃을 만든다. 모자라거나 잘못되면 layout 없이 문제 목록 */
|
|
29
|
+
export declare function applyPreset<L extends SlotLayout>(preset: SlotLayoutPreset<L>, size: Partial<PresetSize>, params?: Record<string, unknown>): {
|
|
30
|
+
layout?: L;
|
|
31
|
+
issues: LayoutIssue[];
|
|
32
|
+
};
|
|
33
|
+
/** 층 설정을 복사해 맨 위 층으로 붙인다. 번호는 마지막 + 1, 높이는 받은 값. 설정만 복사한다 */
|
|
34
|
+
export declare function copyTier<L extends SlotLayout>(layout: L, tierNo: number, elevation: number): L;
|
|
35
|
+
/** 층을 지우고 위층 번호를 당긴다. 마지막 남은 층은 지우지 않는다 */
|
|
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;
|
|
40
|
+
/**
|
|
41
|
+
* 층의 패턴을 바꾼다. 자리 규격은 그대로 두고 자리 수·간격은 지금 배치를 이어받는다.
|
|
42
|
+
* CUSTOM 으로 바꾸면 지금 자리들의 위치·규격을 그대로 적어 둔다.
|
|
43
|
+
*/
|
|
44
|
+
export declare function setTierPattern<L extends SlotLayout>(layout: L, tierNo: number, pattern: SlotPattern['pattern']): L;
|
|
45
|
+
export interface PatternPatch {
|
|
46
|
+
count?: number;
|
|
47
|
+
pitch?: number | {
|
|
48
|
+
x: number;
|
|
49
|
+
z: number;
|
|
50
|
+
};
|
|
51
|
+
axis?: 'X' | 'Z';
|
|
52
|
+
origin?: 'MIN' | 'MAX' | {
|
|
53
|
+
x: 'MIN' | 'MAX';
|
|
54
|
+
z: 'MIN' | 'MAX';
|
|
55
|
+
};
|
|
56
|
+
rows?: number;
|
|
57
|
+
cols?: number;
|
|
58
|
+
order?: GridOrder;
|
|
59
|
+
}
|
|
60
|
+
/** 패턴 값(자리 수·간격·축·번호 규칙)을 고친다. 그 패턴에 없는 키는 무시한다 */
|
|
61
|
+
export declare function updatePattern<L extends SlotLayout>(layout: L, tierNo: number, patch: PatternPatch): L;
|
|
62
|
+
export type SlotSpecPatch = Partial<SlotSpec> & {
|
|
63
|
+
offset?: {
|
|
64
|
+
x: number;
|
|
65
|
+
z: number;
|
|
66
|
+
};
|
|
67
|
+
name?: string;
|
|
68
|
+
};
|
|
69
|
+
/** SINGLE·ROW·GRID 의 공통 자리 규격, CUSTOM 이면 slotNo 자리를 고친다. undefined 값은 키를 지운다 */
|
|
70
|
+
export declare function updateSlotSpec<L extends SlotLayout>(layout: L, tierNo: number, patch: SlotSpecPatch, slotNo?: number): L;
|
|
71
|
+
/** CUSTOM 층에 자리를 하나 더한다 (마지막 자리 규격 복사, 번호는 마지막 + 1) */
|
|
72
|
+
export declare function addCustomSlot<L extends SlotLayout>(layout: L, tierNo: number): L;
|
|
73
|
+
/** CUSTOM 층의 자리를 지우고 뒤 번호를 당긴다. 마지막 남은 자리는 지우지 않는다 */
|
|
74
|
+
export declare function removeCustomSlot<L extends SlotLayout>(layout: L, tierNo: number, slotNo: number): L;
|
|
75
|
+
export interface PreviewSlot {
|
|
76
|
+
slotNo: number;
|
|
77
|
+
name?: string;
|
|
78
|
+
/** 자리 중심 (홀더 로컬). 화면 위가 -z */
|
|
79
|
+
x: number;
|
|
80
|
+
z: number;
|
|
81
|
+
/** 회전한 허용 공간의 외접 폭·깊이 */
|
|
82
|
+
width: number;
|
|
83
|
+
depth: number;
|
|
84
|
+
}
|
|
85
|
+
/** 층 하나의 자리 위치와 크기. 자리를 만들 수 없는 값이면 빈 목록 */
|
|
86
|
+
export declare function previewTier(layout: SlotLayout | undefined, tierNo: number): PreviewSlot[];
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* 자리 레이아웃 편집 동작. 입력 레이아웃을 바꾸지 않고 새 레이아웃을 돌려준다.
|
|
5
|
+
* 레이아웃에 호스트가 덧붙인 키(예: 실시간 주소 읽기 설정)는 그대로 보존한다.
|
|
6
|
+
*/
|
|
7
|
+
import { resolveSlot, rotatedFootprint, slotCount } from '@hatiolab/things-scene';
|
|
8
|
+
// JSON 복사는 NaN·Infinity 를 null 로 바꿔 잘못된 값을 숨기므로 쓰지 않는다
|
|
9
|
+
const clone = (v) => structuredClone(v);
|
|
10
|
+
/** 크기·값을 검사하고 프리셋으로 레이아웃을 만든다. 모자라거나 잘못되면 layout 없이 문제 목록 */
|
|
11
|
+
export function applyPreset(preset, size, params = {}) {
|
|
12
|
+
var _a;
|
|
13
|
+
const issues = [];
|
|
14
|
+
const positive = (v) => typeof v === 'number' && Number.isFinite(v) && v > 0;
|
|
15
|
+
const positiveInt = (v) => Number.isSafeInteger(v) && v > 0;
|
|
16
|
+
for (const k of ['width', 'depth', 'slotHeight'])
|
|
17
|
+
if (!positive(size[k]))
|
|
18
|
+
issues.push({ code: 'POSITIVE_NUMBER_REQUIRED', path: k });
|
|
19
|
+
for (const p of (_a = preset.params) !== null && _a !== void 0 ? _a : [])
|
|
20
|
+
if (!(p.integer ? positiveInt(params[p.key]) : positive(params[p.key])))
|
|
21
|
+
issues.push({ code: p.integer ? 'POSITIVE_INTEGER_REQUIRED' : 'POSITIVE_NUMBER_REQUIRED', path: p.key });
|
|
22
|
+
if (issues.length)
|
|
23
|
+
return { issues };
|
|
24
|
+
return { layout: preset.build(size, params), issues };
|
|
25
|
+
}
|
|
26
|
+
// ── 층 ─────────────────────────────────────────────────────────────────────
|
|
27
|
+
/** 층 설정을 복사해 맨 위 층으로 붙인다. 번호는 마지막 + 1, 높이는 받은 값. 설정만 복사한다 */
|
|
28
|
+
export function copyTier(layout, tierNo, elevation) {
|
|
29
|
+
const next = clone(layout);
|
|
30
|
+
const source = next.tiers.find(t => t.tierNo === tierNo);
|
|
31
|
+
if (!source)
|
|
32
|
+
return next;
|
|
33
|
+
const last = next.tiers[next.tiers.length - 1];
|
|
34
|
+
next.tiers.push({ ...clone(source), tierNo: last.tierNo + 1, elevation });
|
|
35
|
+
return next;
|
|
36
|
+
}
|
|
37
|
+
/** 층을 지우고 위층 번호를 당긴다. 마지막 남은 층은 지우지 않는다 */
|
|
38
|
+
export function removeTier(layout, tierNo) {
|
|
39
|
+
const next = clone(layout);
|
|
40
|
+
if (next.tiers.length <= 1)
|
|
41
|
+
return next;
|
|
42
|
+
next.tiers = next.tiers.filter(t => t.tierNo !== tierNo).map((t, i) => ({ ...t, tierNo: i + 1 }));
|
|
43
|
+
return next;
|
|
44
|
+
}
|
|
45
|
+
export function updateTier(layout, tierNo, patch) {
|
|
46
|
+
const next = clone(layout);
|
|
47
|
+
const tier = next.tiers.find(t => t.tierNo === tierNo);
|
|
48
|
+
if (tier)
|
|
49
|
+
Object.assign(tier, patch);
|
|
50
|
+
return next;
|
|
51
|
+
}
|
|
52
|
+
// ── 패턴 ───────────────────────────────────────────────────────────────────
|
|
53
|
+
const specOf = (l) => {
|
|
54
|
+
if (l.pattern === 'CUSTOM') {
|
|
55
|
+
const first = [...l.slots].sort((a, b) => a.slotNo - b.slotNo)[0];
|
|
56
|
+
const { allowed, accepts, stackLimit, rotation } = first !== null && first !== void 0 ? first : { allowed: { width: 1, depth: 1, height: 1 } };
|
|
57
|
+
return clone({ allowed, accepts, stackLimit, rotation });
|
|
58
|
+
}
|
|
59
|
+
return clone(l.slot);
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* 층의 패턴을 바꾼다. 자리 규격은 그대로 두고 자리 수·간격은 지금 배치를 이어받는다.
|
|
63
|
+
* CUSTOM 으로 바꾸면 지금 자리들의 위치·규격을 그대로 적어 둔다.
|
|
64
|
+
*/
|
|
65
|
+
export function setTierPattern(layout, tierNo, pattern) {
|
|
66
|
+
const next = clone(layout);
|
|
67
|
+
const tier = next.tiers.find(t => t.tierNo === tierNo);
|
|
68
|
+
if (!tier || tier.layout.pattern === pattern)
|
|
69
|
+
return next;
|
|
70
|
+
const cur = tier.layout;
|
|
71
|
+
const spec = specOf(cur);
|
|
72
|
+
const count = slotCount(cur);
|
|
73
|
+
switch (pattern) {
|
|
74
|
+
case 'SINGLE':
|
|
75
|
+
tier.layout = { pattern, slot: spec };
|
|
76
|
+
break;
|
|
77
|
+
case 'ROW':
|
|
78
|
+
tier.layout = {
|
|
79
|
+
pattern,
|
|
80
|
+
count: Math.max(count, 1),
|
|
81
|
+
pitch: cur.pattern === 'ROW' ? cur.pitch : cur.pattern === 'GRID' ? cur.pitch.x : spec.allowed.width,
|
|
82
|
+
axis: 'X',
|
|
83
|
+
origin: 'MIN',
|
|
84
|
+
slot: spec
|
|
85
|
+
};
|
|
86
|
+
break;
|
|
87
|
+
case 'GRID':
|
|
88
|
+
tier.layout = {
|
|
89
|
+
pattern,
|
|
90
|
+
rows: cur.pattern === 'GRID' ? cur.rows : 1,
|
|
91
|
+
cols: cur.pattern === 'GRID' ? cur.cols : Math.max(count, 1),
|
|
92
|
+
pitch: { x: cur.pattern === 'ROW' ? cur.pitch : spec.allowed.width, z: spec.allowed.depth },
|
|
93
|
+
order: 'ROW_MAJOR',
|
|
94
|
+
origin: { x: 'MIN', z: 'MIN' },
|
|
95
|
+
slot: spec
|
|
96
|
+
};
|
|
97
|
+
break;
|
|
98
|
+
case 'CUSTOM': {
|
|
99
|
+
const slots = [];
|
|
100
|
+
for (let n = 1; n <= count; n++) {
|
|
101
|
+
const r = resolveSlot(layout, { tier: tierNo, slot: n });
|
|
102
|
+
slots.push({
|
|
103
|
+
slotNo: n,
|
|
104
|
+
offset: { ...r.offset },
|
|
105
|
+
allowed: { ...r.allowed },
|
|
106
|
+
...(r.accepts ? { accepts: [...r.accepts] } : {}),
|
|
107
|
+
...(r.stackLimit !== 1 ? { stackLimit: r.stackLimit } : {}),
|
|
108
|
+
...(r.rotation ? { rotation: r.rotation } : {})
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
tier.layout = { pattern, slots };
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return next;
|
|
116
|
+
}
|
|
117
|
+
/** 패턴 값(자리 수·간격·축·번호 규칙)을 고친다. 그 패턴에 없는 키는 무시한다 */
|
|
118
|
+
export function updatePattern(layout, tierNo, patch) {
|
|
119
|
+
const next = clone(layout);
|
|
120
|
+
const tier = next.tiers.find(t => t.tierNo === tierNo);
|
|
121
|
+
if (!tier)
|
|
122
|
+
return next;
|
|
123
|
+
const allowedKeys = {
|
|
124
|
+
SINGLE: [],
|
|
125
|
+
ROW: ['count', 'pitch', 'axis', 'origin'],
|
|
126
|
+
GRID: ['rows', 'cols', 'pitch', 'order', 'origin'],
|
|
127
|
+
CUSTOM: []
|
|
128
|
+
};
|
|
129
|
+
for (const [k, v] of Object.entries(patch))
|
|
130
|
+
if (allowedKeys[tier.layout.pattern].includes(k))
|
|
131
|
+
tier.layout[k] = clone(v);
|
|
132
|
+
return next;
|
|
133
|
+
}
|
|
134
|
+
/** SINGLE·ROW·GRID 의 공통 자리 규격, CUSTOM 이면 slotNo 자리를 고친다. undefined 값은 키를 지운다 */
|
|
135
|
+
export function updateSlotSpec(layout, tierNo, patch, slotNo) {
|
|
136
|
+
const next = clone(layout);
|
|
137
|
+
const tier = next.tiers.find(t => t.tierNo === tierNo);
|
|
138
|
+
if (!tier)
|
|
139
|
+
return next;
|
|
140
|
+
const target = (tier.layout.pattern === 'CUSTOM' ? tier.layout.slots.find(s => s.slotNo === slotNo) : tier.layout.slot);
|
|
141
|
+
if (!target)
|
|
142
|
+
return next;
|
|
143
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
144
|
+
if (v === undefined)
|
|
145
|
+
delete target[k];
|
|
146
|
+
else
|
|
147
|
+
target[k] = clone(v);
|
|
148
|
+
}
|
|
149
|
+
if (tier.layout.pattern !== 'CUSTOM')
|
|
150
|
+
for (const k of ['offset', 'name'])
|
|
151
|
+
delete target[k];
|
|
152
|
+
return next;
|
|
153
|
+
}
|
|
154
|
+
/** CUSTOM 층에 자리를 하나 더한다 (마지막 자리 규격 복사, 번호는 마지막 + 1) */
|
|
155
|
+
export function addCustomSlot(layout, tierNo) {
|
|
156
|
+
const next = clone(layout);
|
|
157
|
+
const tier = next.tiers.find(t => t.tierNo === tierNo);
|
|
158
|
+
if (!tier || tier.layout.pattern !== 'CUSTOM')
|
|
159
|
+
return next;
|
|
160
|
+
const last = [...tier.layout.slots].sort((a, b) => a.slotNo - b.slotNo).pop();
|
|
161
|
+
if (last) {
|
|
162
|
+
const { name: _name, ...rest } = clone(last);
|
|
163
|
+
tier.layout.slots.push({ ...rest, slotNo: last.slotNo + 1 });
|
|
164
|
+
}
|
|
165
|
+
else
|
|
166
|
+
tier.layout.slots.push({ slotNo: 1, offset: { x: 0, z: 0 }, allowed: { width: 1, depth: 1, height: 1 } });
|
|
167
|
+
return next;
|
|
168
|
+
}
|
|
169
|
+
/** CUSTOM 층의 자리를 지우고 뒤 번호를 당긴다. 마지막 남은 자리는 지우지 않는다 */
|
|
170
|
+
export function removeCustomSlot(layout, tierNo, slotNo) {
|
|
171
|
+
const next = clone(layout);
|
|
172
|
+
const tier = next.tiers.find(t => t.tierNo === tierNo);
|
|
173
|
+
if (!tier || tier.layout.pattern !== 'CUSTOM' || tier.layout.slots.length <= 1)
|
|
174
|
+
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 }));
|
|
179
|
+
return next;
|
|
180
|
+
}
|
|
181
|
+
/** 층 하나의 자리 위치와 크기. 자리를 만들 수 없는 값이면 빈 목록 */
|
|
182
|
+
export function previewTier(layout, tierNo) {
|
|
183
|
+
var _a;
|
|
184
|
+
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
|
+
if (!layout || !tier)
|
|
186
|
+
return [];
|
|
187
|
+
let count = 0;
|
|
188
|
+
try {
|
|
189
|
+
count = slotCount(tier.layout);
|
|
190
|
+
}
|
|
191
|
+
catch (_b) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
if (!Number.isSafeInteger(count) || count <= 0 || count > 10000)
|
|
195
|
+
return [];
|
|
196
|
+
const out = [];
|
|
197
|
+
for (let n = 1; n <= count; n++) {
|
|
198
|
+
const s = resolveSlot(layout, { tier: tierNo, slot: n });
|
|
199
|
+
if (!s)
|
|
200
|
+
continue;
|
|
201
|
+
const fp = rotatedFootprint({ width: s.allowed.width, depth: s.allowed.depth, height: s.allowed.height }, s.rotation);
|
|
202
|
+
if (![s.offset.x, s.offset.z, fp.width, fp.depth].every(Number.isFinite))
|
|
203
|
+
return [];
|
|
204
|
+
out.push({ slotNo: n, name: s.name, x: s.offset.x, z: s.offset.z, width: fp.width, depth: fp.depth });
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=draft.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './draft.js';
|
|
2
|
+
export * from './issue-text.js';
|
|
3
|
+
export * from './ox-slot-layout-editor.js';
|
|
4
|
+
export * from './ox-property-editor-slot-layout.js';
|
|
5
|
+
/** 속성 편집기 등록용. things-scene.config 의 editors 목록에 넣는다 */
|
|
6
|
+
export declare const SLOT_LAYOUT_EDITORS: {
|
|
7
|
+
type: string;
|
|
8
|
+
element: string;
|
|
9
|
+
}[];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './draft.js';
|
|
2
|
+
export * from './issue-text.js';
|
|
3
|
+
export * from './ox-slot-layout-editor.js';
|
|
4
|
+
export * from './ox-property-editor-slot-layout.js';
|
|
5
|
+
/** 속성 편집기 등록용. things-scene.config 의 editors 목록에 넣는다 */
|
|
6
|
+
export const SLOT_LAYOUT_EDITORS = [{ type: 'slot-layout', element: 'ox-property-editor-slot-layout' }];
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,4BAA4B,CAAA;AAC1C,cAAc,qCAAqC,CAAA;AAEnD,wDAAwD;AACxD,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,gCAAgC,EAAE,CAAC,CAAA","sourcesContent":["export * from './draft.js'\nexport * from './issue-text.js'\nexport * from './ox-slot-layout-editor.js'\nexport * from './ox-property-editor-slot-layout.js'\n\n/** 속성 편집기 등록용. things-scene.config 의 editors 목록에 넣는다 */\nexport const SLOT_LAYOUT_EDITORS = [{ type: 'slot-layout', element: 'ox-property-editor-slot-layout' }]\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* 검증 문제(코드·경로·매개변수)를 화면 문구로. 번역 키는 label.slot-layout.issue.{코드} 이고,
|
|
5
|
+
* 번역이 없으면 아래 영어 기본 문구를 쓴다. 호스트 고유 코드는 호스트가 번역을 넣거나 issueText 옵션으로 바꾼다.
|
|
6
|
+
*/
|
|
7
|
+
import type { LayoutIssueCode } from '@hatiolab/things-scene';
|
|
8
|
+
/** 문제 하나. 호스트 고유 코드도 같은 모양으로 온다 */
|
|
9
|
+
export interface EditorIssue {
|
|
10
|
+
code: string;
|
|
11
|
+
path: string;
|
|
12
|
+
params?: Record<string, string | number>;
|
|
13
|
+
}
|
|
14
|
+
/** things-scene 검증 코드의 기본 문구. 자리표시자가 중괄호 하나라서 문구에 중괄호 글자를 쓰지 않는다 */
|
|
15
|
+
export declare const ISSUE_TEXT: Record<LayoutIssueCode, string>;
|
|
16
|
+
export declare const t: (key: string, fallback: string, opts?: Record<string, unknown>) => string;
|
|
17
|
+
/** 문제 하나를 화면 문구로. 앞에 값의 위치를 붙인다 */
|
|
18
|
+
export declare function issueText(issue: EditorIssue): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* 검증 문제(코드·경로·매개변수)를 화면 문구로. 번역 키는 label.slot-layout.issue.{코드} 이고,
|
|
5
|
+
* 번역이 없으면 아래 영어 기본 문구를 쓴다. 호스트 고유 코드는 호스트가 번역을 넣거나 issueText 옵션으로 바꾼다.
|
|
6
|
+
*/
|
|
7
|
+
import { i18next } from '@operato/i18n';
|
|
8
|
+
/** things-scene 검증 코드의 기본 문구. 자리표시자가 중괄호 하나라서 문구에 중괄호 글자를 쓰지 않는다 */
|
|
9
|
+
export const ISSUE_TEXT = {
|
|
10
|
+
TIERS_REQUIRED: 'At least one tier is required',
|
|
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',
|
|
14
|
+
FINITE_NUMBER_REQUIRED: 'A number is required',
|
|
15
|
+
POSITIVE_NUMBER_REQUIRED: 'A number greater than 0 is required',
|
|
16
|
+
POSITIVE_INTEGER_REQUIRED: 'A whole number of 1 or more is required',
|
|
17
|
+
ARRAY_REQUIRED: 'A list is required',
|
|
18
|
+
OBJECT_REQUIRED: 'The value is not a layout',
|
|
19
|
+
AXIS_INVALID: 'Must be X or Z',
|
|
20
|
+
PATTERN_INVALID: 'Must be SINGLE, ROW, GRID or CUSTOM',
|
|
21
|
+
SLOT_NUMBER_SEQUENCE: 'Slot numbers must run 1, 2, 3… without gaps',
|
|
22
|
+
NOT_PERSISTABLE: 'Cannot be saved ({value})'
|
|
23
|
+
};
|
|
24
|
+
export const t = (key, fallback, opts = {}) => i18next.t(`label.slot-layout.${key}`, { defaultValue: fallback, ...opts });
|
|
25
|
+
/** 문제 하나를 화면 문구로. 앞에 값의 위치를 붙인다 */
|
|
26
|
+
export function issueText(issue) {
|
|
27
|
+
var _a;
|
|
28
|
+
const fallback = (_a = ISSUE_TEXT[issue.code]) !== null && _a !== void 0 ? _a : issue.code;
|
|
29
|
+
const text = t(`issue.${issue.code}`, fallback, { ...issue.params, interpolation: { escapeValue: false } });
|
|
30
|
+
return issue.path ? `${issue.path}: ${text}` : text;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=issue-text.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* 속성 패널용 자리 레이아웃 편집기 (편집기 타입 'slot-layout').
|
|
5
|
+
*
|
|
6
|
+
* 기존 속성 편집기 계약을 따른다: 값은 this.value, 바뀌면 change 를 보내고 호스트(속성 패널)가 컴포넌트에 반영한다.
|
|
7
|
+
* 선택 컴포넌트가 필요한 옵션(값의 출처, 기본값, 상태 문구)은 'i-need-selected' 로 선택 목록을 받아
|
|
8
|
+
* 속성 정의의 property.options(selected) 가 만든다. 편집기는 컴포넌트를 직접 바꾸지 않는다.
|
|
9
|
+
*
|
|
10
|
+
* [기본값 사용]은 값을 비우는 편집과 구분해 'property-use-default'(detail.name, 취소 가능) 를 먼저 알린다.
|
|
11
|
+
* 호스트가 그것을 처리하고 preventDefault() 하면 끝이고, 아니면 값을 비우는 change 하나를 보낸다.
|
|
12
|
+
* 그 뒤 보일 기본값은 호스트가 property.options 로 다시 준다.
|
|
13
|
+
*
|
|
14
|
+
* 속성 정의 예:
|
|
15
|
+
* { type: 'slot-layout', name: 'loadLayout', label: 'load-slots',
|
|
16
|
+
* property: { options: selected => ({ effective, notes, presets, ... }) } }
|
|
17
|
+
*/
|
|
18
|
+
import { OxPropertyEditor, type PropertySpec } from '@operato/property-editor';
|
|
19
|
+
import './ox-slot-layout-editor.js';
|
|
20
|
+
import type { SlotLayoutEditorOptions } from './ox-slot-layout-editor.js';
|
|
21
|
+
export interface SlotLayoutPropertyOptions {
|
|
22
|
+
/** 선택 컴포넌트로 편집기 옵션을 만든다. 값이 바뀔 때마다 다시 부른다 */
|
|
23
|
+
options?: (selected: unknown[]) => SlotLayoutEditorOptions | undefined;
|
|
24
|
+
}
|
|
25
|
+
export declare class OxPropertyEditorSlotLayout extends OxPropertyEditor {
|
|
26
|
+
private _options;
|
|
27
|
+
private _optionsFor;
|
|
28
|
+
editorTemplate(value: any, spec: PropertySpec): import("lit-html").TemplateResult<1>;
|
|
29
|
+
/** 'i-need-selected' 로 선택 목록을 받아 property.options 를 부른다. 받을 호스트가 없으면 옵션 없음 */
|
|
30
|
+
private _resolveOptions;
|
|
31
|
+
private _onUseDefault;
|
|
32
|
+
}
|
|
33
|
+
declare global {
|
|
34
|
+
interface HTMLElementTagNameMap {
|
|
35
|
+
'ox-property-editor-slot-layout': OxPropertyEditorSlotLayout;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* 속성 패널용 자리 레이아웃 편집기 (편집기 타입 'slot-layout').
|
|
5
|
+
*
|
|
6
|
+
* 기존 속성 편집기 계약을 따른다: 값은 this.value, 바뀌면 change 를 보내고 호스트(속성 패널)가 컴포넌트에 반영한다.
|
|
7
|
+
* 선택 컴포넌트가 필요한 옵션(값의 출처, 기본값, 상태 문구)은 'i-need-selected' 로 선택 목록을 받아
|
|
8
|
+
* 속성 정의의 property.options(selected) 가 만든다. 편집기는 컴포넌트를 직접 바꾸지 않는다.
|
|
9
|
+
*
|
|
10
|
+
* [기본값 사용]은 값을 비우는 편집과 구분해 'property-use-default'(detail.name, 취소 가능) 를 먼저 알린다.
|
|
11
|
+
* 호스트가 그것을 처리하고 preventDefault() 하면 끝이고, 아니면 값을 비우는 change 하나를 보낸다.
|
|
12
|
+
* 그 뒤 보일 기본값은 호스트가 property.options 로 다시 준다.
|
|
13
|
+
*
|
|
14
|
+
* 속성 정의 예:
|
|
15
|
+
* { type: 'slot-layout', name: 'loadLayout', label: 'load-slots',
|
|
16
|
+
* property: { options: selected => ({ effective, notes, presets, ... }) } }
|
|
17
|
+
*/
|
|
18
|
+
import { __decorate } from "tslib";
|
|
19
|
+
import { html } from 'lit';
|
|
20
|
+
import { customElement } from 'lit/decorators.js';
|
|
21
|
+
import { OxPropertyEditor } from '@operato/property-editor';
|
|
22
|
+
import './ox-slot-layout-editor.js';
|
|
23
|
+
let OxPropertyEditorSlotLayout = class OxPropertyEditorSlotLayout extends OxPropertyEditor {
|
|
24
|
+
constructor() {
|
|
25
|
+
super(...arguments);
|
|
26
|
+
this._options = {};
|
|
27
|
+
this._optionsFor = Symbol('none');
|
|
28
|
+
}
|
|
29
|
+
editorTemplate(value, spec) {
|
|
30
|
+
var _a;
|
|
31
|
+
// 값이 바뀌었을 때만 옵션을 다시 만든다 (렌더마다 새 옵션을 주면 편집 중인 값이 초기화된다)
|
|
32
|
+
if (this._optionsFor !== value) {
|
|
33
|
+
this._optionsFor = value;
|
|
34
|
+
this._options = (_a = this._resolveOptions(spec)) !== null && _a !== void 0 ? _a : {};
|
|
35
|
+
}
|
|
36
|
+
return html `
|
|
37
|
+
<ox-slot-layout-editor
|
|
38
|
+
id="editor"
|
|
39
|
+
.value=${value}
|
|
40
|
+
.options=${this._options}
|
|
41
|
+
@slot-layout-use-default=${this._onUseDefault}
|
|
42
|
+
></ox-slot-layout-editor>
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
45
|
+
/** 'i-need-selected' 로 선택 목록을 받아 property.options 를 부른다. 받을 호스트가 없으면 옵션 없음 */
|
|
46
|
+
_resolveOptions(spec) {
|
|
47
|
+
var _a;
|
|
48
|
+
const make = (_a = spec.property) === null || _a === void 0 ? void 0 : _a.options;
|
|
49
|
+
if (typeof make !== 'function')
|
|
50
|
+
return undefined;
|
|
51
|
+
let selected;
|
|
52
|
+
this.dispatchEvent(new CustomEvent('i-need-selected', {
|
|
53
|
+
bubbles: true,
|
|
54
|
+
composed: true,
|
|
55
|
+
detail: { callback: (s) => (selected = s) }
|
|
56
|
+
}));
|
|
57
|
+
return selected ? make(selected) : undefined;
|
|
58
|
+
}
|
|
59
|
+
_onUseDefault(e) {
|
|
60
|
+
e.stopPropagation();
|
|
61
|
+
// 의도를 먼저 알린다. 호스트가 직접 처리하고 preventDefault() 하면 값 변경을 보내지 않는다 (두 번 저장하지 않도록).
|
|
62
|
+
// 처리하지 않는 호스트(기존 속성 패널)에는 값을 비우는 change 하나로 알린다.
|
|
63
|
+
const intent = new CustomEvent('property-use-default', { bubbles: true, composed: true, cancelable: true, detail: { name: this.name } });
|
|
64
|
+
if (!this.dispatchEvent(intent))
|
|
65
|
+
return;
|
|
66
|
+
this.value = undefined;
|
|
67
|
+
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
OxPropertyEditorSlotLayout = __decorate([
|
|
71
|
+
customElement('ox-property-editor-slot-layout')
|
|
72
|
+
], OxPropertyEditorSlotLayout);
|
|
73
|
+
export { OxPropertyEditorSlotLayout };
|
|
74
|
+
//# sourceMappingURL=ox-property-editor-slot-layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ox-property-editor-slot-layout.js","sourceRoot":"","sources":["../../src/ox-property-editor-slot-layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAqB,MAAM,0BAA0B,CAAA;AAE9E,OAAO,4BAA4B,CAAA;AAS5B,IAAM,0BAA0B,GAAhC,MAAM,0BAA2B,SAAQ,gBAAgB;IAAzD;;QACG,aAAQ,GAA4B,EAAE,CAAA;QACtC,gBAAW,GAAY,MAAM,CAAC,MAAM,CAAC,CAAA;IA0C/C,CAAC;IAxCC,cAAc,CAAC,KAAU,EAAE,IAAkB;;QAC3C,uDAAuD;QACvD,IAAI,IAAI,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;YAC/B,IAAI,CAAC,WAAW,GAAG,KAAK,CAAA;YACxB,IAAI,CAAC,QAAQ,GAAG,MAAA,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,mCAAI,EAAE,CAAA;QAClD,CAAC;QACD,OAAO,IAAI,CAAA;;;iBAGE,KAAK;mBACH,IAAI,CAAC,QAAQ;mCACG,IAAI,CAAC,aAAa;;KAEhD,CAAA;IACH,CAAC;IAED,8EAA8E;IACtE,eAAe,CAAC,IAAkB;;QACxC,MAAM,IAAI,GAAG,MAAC,IAAI,CAAC,QAAkD,0CAAE,OAAO,CAAA;QAC9E,IAAI,OAAO,IAAI,KAAK,UAAU;YAAE,OAAO,SAAS,CAAA;QAChD,IAAI,QAA+B,CAAA;QACnC,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,iBAAiB,EAAE;YACjC,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAY,EAAE,EAAE,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE;SACvD,CAAC,CACH,CAAA;QACD,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAC9C,CAAC;IAEO,aAAa,CAAC,CAAQ;QAC5B,CAAC,CAAC,eAAe,EAAE,CAAA;QACnB,6EAA6E;QAC7E,iDAAiD;QACjD,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,sBAAsB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;QACxI,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC;YAAE,OAAM;QACvC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAA;QACtB,IAAI,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAC5E,CAAC;CACF,CAAA;AA5CY,0BAA0B;IADtC,aAAa,CAAC,gCAAgC,CAAC;GACnC,0BAA0B,CA4CtC","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * 속성 패널용 자리 레이아웃 편집기 (편집기 타입 'slot-layout').\n *\n * 기존 속성 편집기 계약을 따른다: 값은 this.value, 바뀌면 change 를 보내고 호스트(속성 패널)가 컴포넌트에 반영한다.\n * 선택 컴포넌트가 필요한 옵션(값의 출처, 기본값, 상태 문구)은 'i-need-selected' 로 선택 목록을 받아\n * 속성 정의의 property.options(selected) 가 만든다. 편집기는 컴포넌트를 직접 바꾸지 않는다.\n *\n * [기본값 사용]은 값을 비우는 편집과 구분해 'property-use-default'(detail.name, 취소 가능) 를 먼저 알린다.\n * 호스트가 그것을 처리하고 preventDefault() 하면 끝이고, 아니면 값을 비우는 change 하나를 보낸다.\n * 그 뒤 보일 기본값은 호스트가 property.options 로 다시 준다.\n *\n * 속성 정의 예:\n * { type: 'slot-layout', name: 'loadLayout', label: 'load-slots',\n * property: { options: selected => ({ effective, notes, presets, ... }) } }\n */\n\nimport { html } from 'lit'\nimport { customElement } from 'lit/decorators.js'\nimport { OxPropertyEditor, type PropertySpec } from '@operato/property-editor'\n\nimport './ox-slot-layout-editor.js'\nimport type { SlotLayoutEditorOptions } from './ox-slot-layout-editor.js'\n\nexport interface SlotLayoutPropertyOptions {\n /** 선택 컴포넌트로 편집기 옵션을 만든다. 값이 바뀔 때마다 다시 부른다 */\n options?: (selected: unknown[]) => SlotLayoutEditorOptions | undefined\n}\n\n@customElement('ox-property-editor-slot-layout')\nexport class OxPropertyEditorSlotLayout extends OxPropertyEditor {\n private _options: SlotLayoutEditorOptions = {}\n private _optionsFor: unknown = Symbol('none')\n\n editorTemplate(value: any, spec: PropertySpec) {\n // 값이 바뀌었을 때만 옵션을 다시 만든다 (렌더마다 새 옵션을 주면 편집 중인 값이 초기화된다)\n if (this._optionsFor !== value) {\n this._optionsFor = value\n this._options = this._resolveOptions(spec) ?? {}\n }\n return html`\n <ox-slot-layout-editor\n id=\"editor\"\n .value=${value}\n .options=${this._options}\n @slot-layout-use-default=${this._onUseDefault}\n ></ox-slot-layout-editor>\n `\n }\n\n /** 'i-need-selected' 로 선택 목록을 받아 property.options 를 부른다. 받을 호스트가 없으면 옵션 없음 */\n private _resolveOptions(spec: PropertySpec): SlotLayoutEditorOptions | undefined {\n const make = (spec.property as SlotLayoutPropertyOptions | undefined)?.options\n if (typeof make !== 'function') return undefined\n let selected: unknown[] | undefined\n this.dispatchEvent(\n new CustomEvent('i-need-selected', {\n bubbles: true,\n composed: true,\n detail: { callback: (s: unknown[]) => (selected = s) }\n })\n )\n return selected ? make(selected) : undefined\n }\n\n private _onUseDefault(e: Event) {\n e.stopPropagation()\n // 의도를 먼저 알린다. 호스트가 직접 처리하고 preventDefault() 하면 값 변경을 보내지 않는다 (두 번 저장하지 않도록).\n // 처리하지 않는 호스트(기존 속성 패널)에는 값을 비우는 change 하나로 알린다.\n const intent = new CustomEvent('property-use-default', { bubbles: true, composed: true, cancelable: true, detail: { name: this.name } })\n if (!this.dispatchEvent(intent)) return\n this.value = undefined\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ox-property-editor-slot-layout': OxPropertyEditorSlotLayout\n }\n}\n"]}
|