@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
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* 자리 레이아웃 편집기. 홀더의 층·자리 구성을 고치고, 검증하고, 미리 보고, [적용]으로 알린다.
|
|
5
|
+
*
|
|
6
|
+
* 컴포넌트 상태를 직접 바꾸지 않는다. 호스트에 두 가지만 알린다:
|
|
7
|
+
* change — 적용한 레이아웃 (this.value)
|
|
8
|
+
* slot-layout-use-default — 개별 설정을 지우고 기본값을 쓰겠다는 의도 (값을 비우는 편집과 다르다)
|
|
9
|
+
* 기본값을 쓰게 된 뒤 보일 값은 호스트가 options.effective 로 다시 준다.
|
|
10
|
+
*/
|
|
11
|
+
import { LitElement } from 'lit';
|
|
12
|
+
import { type SlotLayout, type SlotMount } from '@hatiolab/things-scene';
|
|
13
|
+
import { type PresetSize, type SlotLayoutPreset } from './draft.js';
|
|
14
|
+
import { type EditorIssue } from './issue-text.js';
|
|
15
|
+
export interface SlotLayoutEditorNote {
|
|
16
|
+
text: string;
|
|
17
|
+
level?: 'info' | 'error';
|
|
18
|
+
}
|
|
19
|
+
export interface SlotLayoutEditorOptions<L extends SlotLayout = SlotLayout> {
|
|
20
|
+
/** 개별 설정이 없을 때 보이는 값 (호스트가 고른 기본값). 편집 시작점이 된다 */
|
|
21
|
+
effective?: L;
|
|
22
|
+
/** [기본값 사용]을 보일지. 기본은 개별 설정(value)이 있을 때 */
|
|
23
|
+
canUseDefault?: boolean;
|
|
24
|
+
/** 호스트가 주는 프리셋 */
|
|
25
|
+
presets?: SlotLayoutPreset<L>[];
|
|
26
|
+
/** 프리셋 크기 초기값 */
|
|
27
|
+
presetSize?: Partial<PresetSize>;
|
|
28
|
+
/** 상태 문구 (값의 출처, 단위 상태 등) */
|
|
29
|
+
notes?: SlotLayoutEditorNote[];
|
|
30
|
+
/** 프리셋 없이 새로 시작할 때의 mount. 기본 ON */
|
|
31
|
+
mount?: SlotMount;
|
|
32
|
+
/** 호스트 고유 검증 (경로는 레이아웃 기준) */
|
|
33
|
+
validate?: (layout: L) => EditorIssue[];
|
|
34
|
+
/** 문제 문구. undefined 를 돌려주면 기본 문구 */
|
|
35
|
+
issueText?: (issue: EditorIssue) => string | undefined;
|
|
36
|
+
/** 레이아웃에 덧붙인 호스트 키를 고치는 구역 (예: 실시간 주소 읽기). edit 로 새 레이아웃을 넘긴다 */
|
|
37
|
+
renderExtra?: (draft: L, edit: (next: L) => void) => unknown;
|
|
38
|
+
}
|
|
39
|
+
declare const OxSlotLayoutEditor_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
40
|
+
export declare class OxSlotLayoutEditor extends OxSlotLayoutEditor_base {
|
|
41
|
+
static styles: import("lit").CSSResult;
|
|
42
|
+
/** 개별 설정. 없으면 undefined */
|
|
43
|
+
value: SlotLayout | undefined;
|
|
44
|
+
options: SlotLayoutEditorOptions;
|
|
45
|
+
draft: SlotLayout | undefined;
|
|
46
|
+
tier: number;
|
|
47
|
+
dirty: boolean;
|
|
48
|
+
presetKey: string;
|
|
49
|
+
presetSize: Partial<PresetSize>;
|
|
50
|
+
presetParams: Record<string, number>;
|
|
51
|
+
copyElevation: number;
|
|
52
|
+
willUpdate(changed: Map<string, unknown>): void;
|
|
53
|
+
/** 편집 중인 값을 버리고 개별 설정(없으면 호스트 기본값)에서 다시 시작한다 */
|
|
54
|
+
reset(): void;
|
|
55
|
+
get issues(): EditorIssue[];
|
|
56
|
+
private _text;
|
|
57
|
+
private _edit;
|
|
58
|
+
private _apply;
|
|
59
|
+
private _useDefault;
|
|
60
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
61
|
+
private _renderPreset;
|
|
62
|
+
/** 프리셋으로 다시 시작한다. 지금 값에 덧붙은 호스트 키(층·자리 밖의 키)는 가져온다 */
|
|
63
|
+
private _startFrom;
|
|
64
|
+
private _renderDraft;
|
|
65
|
+
private _renderTier;
|
|
66
|
+
/** 받는 타입: 모든 타입(accepts 생략) / 지정 타입만(빈 목록이면 아무것도 받지 않음) */
|
|
67
|
+
private _acceptsEditor;
|
|
68
|
+
private _renderSpec;
|
|
69
|
+
private _renderCustom;
|
|
70
|
+
/** 위에서 본 자리 배치. 오른쪽이 +X, 위쪽이 −Z */
|
|
71
|
+
private _renderPreview;
|
|
72
|
+
}
|
|
73
|
+
declare global {
|
|
74
|
+
interface HTMLElementTagNameMap {
|
|
75
|
+
'ox-slot-layout-editor': OxSlotLayoutEditor;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export {};
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* 자리 레이아웃 편집기. 홀더의 층·자리 구성을 고치고, 검증하고, 미리 보고, [적용]으로 알린다.
|
|
5
|
+
*
|
|
6
|
+
* 컴포넌트 상태를 직접 바꾸지 않는다. 호스트에 두 가지만 알린다:
|
|
7
|
+
* change — 적용한 레이아웃 (this.value)
|
|
8
|
+
* slot-layout-use-default — 개별 설정을 지우고 기본값을 쓰겠다는 의도 (값을 비우는 편집과 다르다)
|
|
9
|
+
* 기본값을 쓰게 된 뒤 보일 값은 호스트가 options.effective 로 다시 준다.
|
|
10
|
+
*/
|
|
11
|
+
import { __decorate } from "tslib";
|
|
12
|
+
import { css, html, nothing, LitElement, svg } from 'lit';
|
|
13
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
14
|
+
import { i18next, localize } from '@operato/i18n';
|
|
15
|
+
import { persistenceIssues, validateSlotLayout } from '@hatiolab/things-scene';
|
|
16
|
+
import { addCustomSlot, applyPreset, copyTier, previewTier, removeCustomSlot, removeTier, setTierPattern, updatePattern, updateSlotSpec, updateTier } from './draft.js';
|
|
17
|
+
import { issueText as defaultIssueText, t } from './issue-text.js';
|
|
18
|
+
const num = (e) => {
|
|
19
|
+
const n = e.target.valueAsNumber;
|
|
20
|
+
return Number.isFinite(n) ? n : NaN;
|
|
21
|
+
};
|
|
22
|
+
let OxSlotLayoutEditor = class OxSlotLayoutEditor extends localize(i18next)(LitElement) {
|
|
23
|
+
constructor() {
|
|
24
|
+
super(...arguments);
|
|
25
|
+
this.options = {};
|
|
26
|
+
this.tier = 1;
|
|
27
|
+
this.dirty = false;
|
|
28
|
+
this.presetKey = '';
|
|
29
|
+
this.presetSize = {};
|
|
30
|
+
this.presetParams = {};
|
|
31
|
+
this.copyElevation = NaN;
|
|
32
|
+
}
|
|
33
|
+
willUpdate(changed) {
|
|
34
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
35
|
+
// 호스트가 렌더마다 새 options 를 넘겨도 편집 중인 값을 잃지 않도록, 값이나 호스트 기본값이 바뀔 때만 다시 시작한다
|
|
36
|
+
const before = changed.get('options');
|
|
37
|
+
if (changed.has('value') || (changed.has('options') && (before === null || before === void 0 ? void 0 : before.effective) !== ((_a = this.options) === null || _a === void 0 ? void 0 : _a.effective)))
|
|
38
|
+
this.reset();
|
|
39
|
+
else if (changed.has('options') && !((_c = (_b = this.options) === null || _b === void 0 ? void 0 : _b.presets) === null || _c === void 0 ? void 0 : _c.some(p => p.key === this.presetKey)))
|
|
40
|
+
this.presetKey = (_g = (_f = (_e = (_d = this.options) === null || _d === void 0 ? void 0 : _d.presets) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.key) !== null && _g !== void 0 ? _g : '';
|
|
41
|
+
}
|
|
42
|
+
/** 편집 중인 값을 버리고 개별 설정(없으면 호스트 기본값)에서 다시 시작한다 */
|
|
43
|
+
reset() {
|
|
44
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
45
|
+
const start = (_a = this.value) !== null && _a !== void 0 ? _a : (_b = this.options) === null || _b === void 0 ? void 0 : _b.effective;
|
|
46
|
+
this.draft = start ? structuredClone(start) : undefined;
|
|
47
|
+
this.tier = 1;
|
|
48
|
+
this.dirty = false;
|
|
49
|
+
this.presetSize = { ...((_d = (_c = this.options) === null || _c === void 0 ? void 0 : _c.presetSize) !== null && _d !== void 0 ? _d : {}) };
|
|
50
|
+
if (!((_f = (_e = this.options) === null || _e === void 0 ? void 0 : _e.presets) === null || _f === void 0 ? void 0 : _f.some(p => p.key === this.presetKey)))
|
|
51
|
+
this.presetKey = (_k = (_j = (_h = (_g = this.options) === null || _g === void 0 ? void 0 : _g.presets) === null || _h === void 0 ? void 0 : _h[0]) === null || _j === void 0 ? void 0 : _j.key) !== null && _k !== void 0 ? _k : '';
|
|
52
|
+
}
|
|
53
|
+
get issues() {
|
|
54
|
+
var _a, _b, _c;
|
|
55
|
+
if (!this.draft)
|
|
56
|
+
return [];
|
|
57
|
+
const invalid = [...validateSlotLayout(this.draft), ...((_c = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.validate) === null || _b === void 0 ? void 0 : _b.call(_a, this.draft)) !== null && _c !== void 0 ? _c : [])];
|
|
58
|
+
// 저장 불가 검사는 검증을 통과한 값에만 보인다 (같은 문제를 두 번 적지 않는다)
|
|
59
|
+
return invalid.length ? invalid : persistenceIssues(this.draft);
|
|
60
|
+
}
|
|
61
|
+
_text(issue) {
|
|
62
|
+
var _a, _b, _c;
|
|
63
|
+
return (_c = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.issueText) === null || _b === void 0 ? void 0 : _b.call(_a, issue)) !== null && _c !== void 0 ? _c : defaultIssueText(issue);
|
|
64
|
+
}
|
|
65
|
+
_edit(next) {
|
|
66
|
+
var _a, _b;
|
|
67
|
+
this.draft = next;
|
|
68
|
+
this.dirty = true;
|
|
69
|
+
if (!next.tiers.some(x => x.tierNo === this.tier))
|
|
70
|
+
this.tier = (_b = (_a = next.tiers[next.tiers.length - 1]) === null || _a === void 0 ? void 0 : _a.tierNo) !== null && _b !== void 0 ? _b : 1;
|
|
71
|
+
}
|
|
72
|
+
_apply() {
|
|
73
|
+
this.value = structuredClone(this.draft);
|
|
74
|
+
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
|
75
|
+
}
|
|
76
|
+
_useDefault() {
|
|
77
|
+
this.dispatchEvent(new CustomEvent('slot-layout-use-default', { bubbles: true, composed: true }));
|
|
78
|
+
}
|
|
79
|
+
render() {
|
|
80
|
+
var _a, _b, _c, _d;
|
|
81
|
+
const issues = this.issues;
|
|
82
|
+
const canUseDefault = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.canUseDefault) !== null && _b !== void 0 ? _b : this.value !== undefined;
|
|
83
|
+
return html `
|
|
84
|
+
${((_d = (_c = this.options) === null || _c === void 0 ? void 0 : _c.notes) === null || _d === void 0 ? void 0 : _d.length)
|
|
85
|
+
? html `<div class="notes">
|
|
86
|
+
${this.options.notes.map(n => html `<div class=${n.level === 'error' ? 'error' : 'note'}>${n.text}</div>`)}
|
|
87
|
+
</div>`
|
|
88
|
+
: nothing}
|
|
89
|
+
${this._renderPreset()} ${this.draft ? this._renderDraft(issues) : nothing}
|
|
90
|
+
<div class="actions">
|
|
91
|
+
${canUseDefault ? html `<button data-action="use-default" @click=${this._useDefault}>${t('use-default', 'Use default')}</button>` : nothing}
|
|
92
|
+
<button data-action="discard" ?disabled=${!this.dirty} @click=${() => this.reset()}>${t('discard', 'Discard')}</button>
|
|
93
|
+
<button data-action="apply" ?disabled=${!this.dirty || !this.draft || issues.length > 0} @click=${this._apply}>${t('apply', 'Apply')}</button>
|
|
94
|
+
</div>
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
_renderPreset() {
|
|
98
|
+
var _a, _b, _c, _d;
|
|
99
|
+
const presets = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.presets) !== null && _b !== void 0 ? _b : [];
|
|
100
|
+
if (!presets.length)
|
|
101
|
+
return nothing;
|
|
102
|
+
const preset = (_c = presets.find(p => p.key === this.presetKey)) !== null && _c !== void 0 ? _c : presets[0];
|
|
103
|
+
const built = applyPreset(preset, this.presetSize, this.presetParams);
|
|
104
|
+
const numberRow = (label, value, onChange, key) => html `
|
|
105
|
+
<div class="row">
|
|
106
|
+
<label>${label}</label>
|
|
107
|
+
<input type="number" data-preset=${key} .value=${value === undefined || Number.isNaN(value) ? '' : String(value)} @change=${(e) => onChange(num(e))} />
|
|
108
|
+
</div>
|
|
109
|
+
`;
|
|
110
|
+
return html `
|
|
111
|
+
<h4>${t('preset', 'Preset')}</h4>
|
|
112
|
+
<div class="row">
|
|
113
|
+
<label>${t('preset-kind', 'Kind')}</label>
|
|
114
|
+
<select data-preset="kind" @change=${(e) => (this.presetKey = e.target.value)}>
|
|
115
|
+
${presets.map(p => html `<option value=${p.key} ?selected=${p.key === preset.key}>${p.label}</option>`)}
|
|
116
|
+
</select>
|
|
117
|
+
</div>
|
|
118
|
+
${numberRow(t('width', 'Width (X)'), this.presetSize.width, n => (this.presetSize = { ...this.presetSize, width: n }), 'width')}
|
|
119
|
+
${numberRow(t('depth', 'Depth (Z)'), this.presetSize.depth, n => (this.presetSize = { ...this.presetSize, depth: n }), 'depth')}
|
|
120
|
+
${numberRow(t('slot-height', 'Slot height'), this.presetSize.slotHeight, n => (this.presetSize = { ...this.presetSize, slotHeight: n }), 'slotHeight')}
|
|
121
|
+
${((_d = preset.params) !== null && _d !== void 0 ? _d : []).map(p => numberRow(p.label, this.presetParams[p.key], n => (this.presetParams = { ...this.presetParams, [p.key]: n }), p.key))}
|
|
122
|
+
<div class="note">${t('preset-note', 'Pitch and slot size are split evenly as starting values. Confirm them against the real equipment.')}</div>
|
|
123
|
+
<div class="actions">
|
|
124
|
+
<button data-action="start-preset" ?disabled=${!built.layout} @click=${() => built.layout && this._startFrom(built.layout)}>
|
|
125
|
+
${t('start-from-preset', 'Start from preset')}
|
|
126
|
+
</button>
|
|
127
|
+
</div>
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
/** 프리셋으로 다시 시작한다. 지금 값에 덧붙은 호스트 키(층·자리 밖의 키)는 가져온다 */
|
|
131
|
+
_startFrom(layout) {
|
|
132
|
+
var _a;
|
|
133
|
+
const { mount: _m, tiers: _t, ...extra } = ((_a = this.draft) !== null && _a !== void 0 ? _a : {});
|
|
134
|
+
this._edit({ ...extra, ...structuredClone(layout) });
|
|
135
|
+
}
|
|
136
|
+
_renderDraft(issues) {
|
|
137
|
+
var _a, _b, _c, _d, _e, _f;
|
|
138
|
+
const draft = this.draft;
|
|
139
|
+
const tier = (_b = (_a = draft.tiers) === null || _a === void 0 ? void 0 : _a.find(x => x.tierNo === this.tier)) !== null && _b !== void 0 ? _b : (_c = draft.tiers) === null || _c === void 0 ? void 0 : _c[0];
|
|
140
|
+
const issueList = issues.length ? html `<div class="error" data-issues>${issues.map(i => html `<div>· ${this._text(i)}</div>`)}</div>` : nothing;
|
|
141
|
+
// 층이 없는 값(잘못된 저장값)은 편집할 층이 없으므로 문제만 보인다. 프리셋으로 다시 시작한다
|
|
142
|
+
if (!tier)
|
|
143
|
+
return issueList;
|
|
144
|
+
return html `
|
|
145
|
+
<h4>${t('tiers', 'Tiers')}</h4>
|
|
146
|
+
<div class="chips">
|
|
147
|
+
${draft.tiers.map(x => html `<button data-tier=${x.tierNo} ?active=${x.tierNo === tier.tierNo} @click=${() => (this.tier = x.tierNo)}>${t('tier-n', 'Tier {n}', { n: x.tierNo })}</button>`)}
|
|
148
|
+
</div>
|
|
149
|
+
<div class="row">
|
|
150
|
+
<label>${t('copy-elevation', 'New tier elevation')}</label>
|
|
151
|
+
<input type="number" data-field="copy-elevation" .value=${Number.isFinite(this.copyElevation) ? String(this.copyElevation) : ''} @change=${(e) => (this.copyElevation = num(e))} />
|
|
152
|
+
</div>
|
|
153
|
+
<div class="actions">
|
|
154
|
+
<button data-action="copy-tier" ?disabled=${!Number.isFinite(this.copyElevation)} @click=${() => this._edit(copyTier(draft, tier.tierNo, this.copyElevation))}>
|
|
155
|
+
${t('copy-tier', 'Copy tier')}
|
|
156
|
+
</button>
|
|
157
|
+
<button data-action="remove-tier" ?disabled=${draft.tiers.length <= 1} @click=${() => this._edit(removeTier(draft, tier.tierNo))}>${t('remove-tier', 'Remove tier')}</button>
|
|
158
|
+
</div>
|
|
159
|
+
${this._renderTier(draft, tier.tierNo)} ${(_f = (_e = (_d = this.options) === null || _d === void 0 ? void 0 : _d.renderExtra) === null || _e === void 0 ? void 0 : _e.call(_d, draft, next => this._edit(next))) !== null && _f !== void 0 ? _f : nothing}
|
|
160
|
+
<h4>${t('preview', 'Preview (top view, tier {n})', { n: tier.tierNo })}</h4>
|
|
161
|
+
${this._renderPreview(draft, tier.tierNo)} ${issueList}
|
|
162
|
+
`;
|
|
163
|
+
}
|
|
164
|
+
_renderTier(draft, tierNo) {
|
|
165
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
166
|
+
const tier = draft.tiers.find(x => x.tierNo === tierNo);
|
|
167
|
+
const l = tier.layout;
|
|
168
|
+
const patternInput = (k, label, value, parse = num) => html `
|
|
169
|
+
<div class="row">
|
|
170
|
+
<label>${label}</label>
|
|
171
|
+
<input type="number" data-field=${k} .value=${value === undefined || Number.isNaN(value) ? '' : String(value)} @change=${(e) => this._edit(updatePattern(draft, tierNo, { [k]: parse(e) }))} />
|
|
172
|
+
</div>
|
|
173
|
+
`;
|
|
174
|
+
const select = (field, label, value, options, onChange) => html `
|
|
175
|
+
<div class="row">
|
|
176
|
+
<label>${label}</label>
|
|
177
|
+
<select data-field=${field} @change=${(e) => onChange(e.target.value)}>
|
|
178
|
+
${options.map(o => html `<option value=${o} ?selected=${o === value}>${o}</option>`)}
|
|
179
|
+
</select>
|
|
180
|
+
</div>
|
|
181
|
+
`;
|
|
182
|
+
return html `
|
|
183
|
+
<div class="row">
|
|
184
|
+
<label>${t('elevation', 'Elevation')}</label>
|
|
185
|
+
<input type="number" data-field="elevation" .value=${String(tier.elevation)} @change=${(e) => this._edit(updateTier(draft, tierNo, { elevation: num(e) }))} />
|
|
186
|
+
</div>
|
|
187
|
+
${select('pattern', t('pattern', 'Pattern'), l.pattern, ['SINGLE', 'ROW', 'GRID', 'CUSTOM'], v => this._edit(setTierPattern(draft, tierNo, v)))}
|
|
188
|
+
${l.pattern === 'ROW'
|
|
189
|
+
? html `
|
|
190
|
+
${patternInput('count', t('count', 'Slots'), l.count)} ${patternInput('pitch', t('pitch', 'Pitch'), l.pitch)}
|
|
191
|
+
${select('axis', t('axis', 'Axis'), l.axis, ['X', 'Z'], v => this._edit(updatePattern(draft, tierNo, { axis: v })))}
|
|
192
|
+
${select('origin', t('origin', 'Slot 1 at'), (_a = l.origin) !== null && _a !== void 0 ? _a : 'MIN', ['MIN', 'MAX'], v => this._edit(updatePattern(draft, tierNo, { origin: v })))}
|
|
193
|
+
`
|
|
194
|
+
: nothing}
|
|
195
|
+
${l.pattern === 'GRID'
|
|
196
|
+
? html `
|
|
197
|
+
${patternInput('rows', t('rows', 'Rows (Z)'), l.rows)} ${patternInput('cols', t('cols', 'Columns (X)'), l.cols)}
|
|
198
|
+
${patternInput('pitch', t('pitch-x', 'Pitch X'), (_b = l.pitch) === null || _b === void 0 ? void 0 : _b.x, e => ({ ...l.pitch, x: num(e) }))}
|
|
199
|
+
${patternInput('pitch', t('pitch-z', 'Pitch Z'), (_c = l.pitch) === null || _c === void 0 ? void 0 : _c.z, e => ({ ...l.pitch, z: num(e) }))}
|
|
200
|
+
${select('order', t('order', 'Numbering'), (_d = l.order) !== null && _d !== void 0 ? _d : 'ROW_MAJOR', ['ROW_MAJOR', 'COL_MAJOR', 'SERPENTINE_ROW', 'SERPENTINE_COL'], v => this._edit(updatePattern(draft, tierNo, { order: v })))}
|
|
201
|
+
${select('origin-x', t('origin-x', 'Slot 1 at X'), (_f = (_e = l.origin) === null || _e === void 0 ? void 0 : _e.x) !== null && _f !== void 0 ? _f : 'MIN', ['MIN', 'MAX'], v => this._edit(updatePattern(draft, tierNo, { origin: { z: 'MIN', ...l.origin, x: v } })))}
|
|
202
|
+
${select('origin-z', t('origin-z', 'Slot 1 at Z'), (_h = (_g = l.origin) === null || _g === void 0 ? void 0 : _g.z) !== null && _h !== void 0 ? _h : 'MIN', ['MIN', 'MAX'], v => this._edit(updatePattern(draft, tierNo, { origin: { x: 'MIN', ...l.origin, z: v } })))}
|
|
203
|
+
`
|
|
204
|
+
: nothing}
|
|
205
|
+
${l.pattern === 'CUSTOM' ? this._renderCustom(draft, tierNo) : this._renderSpec(draft, tierNo, l.slot)}
|
|
206
|
+
`;
|
|
207
|
+
}
|
|
208
|
+
/** 받는 타입: 모든 타입(accepts 생략) / 지정 타입만(빈 목록이면 아무것도 받지 않음) */
|
|
209
|
+
_acceptsEditor(spec, onChange) {
|
|
210
|
+
var _a;
|
|
211
|
+
const restricted = Array.isArray(spec.accepts);
|
|
212
|
+
return html `
|
|
213
|
+
<div class="row">
|
|
214
|
+
<label>${t('accepts', 'Accepts')}</label>
|
|
215
|
+
<select data-field="accepts-mode" @change=${(e) => { var _a; return onChange(e.target.value === 'ALL' ? undefined : (_a = spec.accepts) !== null && _a !== void 0 ? _a : []); }}>
|
|
216
|
+
<option value="ALL" ?selected=${!restricted}>${t('accepts-all', 'Any type')}</option>
|
|
217
|
+
<option value="LIST" ?selected=${restricted}>${t('accepts-list', 'Listed types only')}</option>
|
|
218
|
+
</select>
|
|
219
|
+
</div>
|
|
220
|
+
${restricted
|
|
221
|
+
? html `<div class="row">
|
|
222
|
+
<label>${t('accepts-types', 'Types (comma)')}</label>
|
|
223
|
+
<input
|
|
224
|
+
data-field="accepts"
|
|
225
|
+
.value=${((_a = spec.accepts) !== null && _a !== void 0 ? _a : []).join(', ')}
|
|
226
|
+
placeholder=${t('accepts-none', 'Empty = accepts nothing')}
|
|
227
|
+
@change=${(e) => onChange(e.target.value
|
|
228
|
+
.split(',')
|
|
229
|
+
.map(s => s.trim())
|
|
230
|
+
.filter(Boolean))}
|
|
231
|
+
/>
|
|
232
|
+
</div>`
|
|
233
|
+
: nothing}
|
|
234
|
+
`;
|
|
235
|
+
}
|
|
236
|
+
_renderSpec(draft, tierNo, spec) {
|
|
237
|
+
var _a, _b;
|
|
238
|
+
const edit = (patch) => this._edit(updateSlotSpec(draft, tierNo, patch));
|
|
239
|
+
const allowed = (k, label) => {
|
|
240
|
+
var _a, _b;
|
|
241
|
+
return html `
|
|
242
|
+
<div class="row">
|
|
243
|
+
<label>${label}</label>
|
|
244
|
+
<input type="number" data-field=${`allowed-${k}`} .value=${String((_b = (_a = spec.allowed) === null || _a === void 0 ? void 0 : _a[k]) !== null && _b !== void 0 ? _b : '')} @change=${(e) => edit({ allowed: { ...spec.allowed, [k]: num(e) } })} />
|
|
245
|
+
</div>
|
|
246
|
+
`;
|
|
247
|
+
};
|
|
248
|
+
return html `
|
|
249
|
+
<h4>${t('slot-spec', 'Slot (same for every slot in this tier)')}</h4>
|
|
250
|
+
${allowed('width', t('allowed-width', 'Allowed width'))} ${allowed('depth', t('allowed-depth', 'Allowed depth'))}
|
|
251
|
+
${allowed('height', t('allowed-height', 'Allowed height'))}
|
|
252
|
+
<div class="row">
|
|
253
|
+
<label>${t('stack-limit', 'Stack limit')}</label>
|
|
254
|
+
<input type="number" data-field="stack-limit" min="1" step="1" .value=${String((_a = spec.stackLimit) !== null && _a !== void 0 ? _a : 1)} @change=${(e) => edit({ stackLimit: num(e) })} />
|
|
255
|
+
</div>
|
|
256
|
+
<div class="row">
|
|
257
|
+
<label>${t('rotation', 'Rotation (deg)')}</label>
|
|
258
|
+
<input type="number" data-field="rotation" .value=${String((_b = spec.rotation) !== null && _b !== void 0 ? _b : 0)} @change=${(e) => edit({ rotation: num(e) || undefined })} />
|
|
259
|
+
</div>
|
|
260
|
+
${this._acceptsEditor(spec, accepts => edit({ accepts }))}
|
|
261
|
+
`;
|
|
262
|
+
}
|
|
263
|
+
_renderCustom(draft, tierNo) {
|
|
264
|
+
const tier = draft.tiers.find(x => x.tierNo === tierNo);
|
|
265
|
+
const slots = [...tier.layout.slots].sort((a, b) => a.slotNo - b.slotNo);
|
|
266
|
+
const cell = (slotNo, value, patch) => html `<td><input type="number" .value=${String(value !== null && value !== void 0 ? value : '')} @change=${(e) => this._edit(updateSlotSpec(draft, tierNo, patch(e), slotNo))} /></td>`;
|
|
267
|
+
return html `
|
|
268
|
+
<h4>${t('custom-slots', 'Slots')}</h4>
|
|
269
|
+
<table>
|
|
270
|
+
<tr>
|
|
271
|
+
<th>#</th>
|
|
272
|
+
<th>X</th>
|
|
273
|
+
<th>Z</th>
|
|
274
|
+
<th>${t('w', 'W')}</th>
|
|
275
|
+
<th>${t('d', 'D')}</th>
|
|
276
|
+
<th>${t('h', 'H')}</th>
|
|
277
|
+
<th>${t('stack', 'Stack')}</th>
|
|
278
|
+
<th>${t('rot', 'Rot')}</th>
|
|
279
|
+
<th></th>
|
|
280
|
+
</tr>
|
|
281
|
+
${slots.map(s => {
|
|
282
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
283
|
+
return html `
|
|
284
|
+
<tr>
|
|
285
|
+
<td>${s.slotNo}</td>
|
|
286
|
+
${cell(s.slotNo, (_a = s.offset) === null || _a === void 0 ? void 0 : _a.x, e => ({ offset: { ...s.offset, x: num(e) } }))}
|
|
287
|
+
${cell(s.slotNo, (_b = s.offset) === null || _b === void 0 ? void 0 : _b.z, e => ({ offset: { ...s.offset, z: num(e) } }))}
|
|
288
|
+
${cell(s.slotNo, (_c = s.allowed) === null || _c === void 0 ? void 0 : _c.width, e => ({ allowed: { ...s.allowed, width: num(e) } }))}
|
|
289
|
+
${cell(s.slotNo, (_d = s.allowed) === null || _d === void 0 ? void 0 : _d.depth, e => ({ allowed: { ...s.allowed, depth: num(e) } }))}
|
|
290
|
+
${cell(s.slotNo, (_e = s.allowed) === null || _e === void 0 ? void 0 : _e.height, e => ({ allowed: { ...s.allowed, height: num(e) } }))}
|
|
291
|
+
${cell(s.slotNo, (_f = s.stackLimit) !== null && _f !== void 0 ? _f : 1, e => ({ stackLimit: num(e) }))} ${cell(s.slotNo, (_g = s.rotation) !== null && _g !== void 0 ? _g : 0, e => ({ rotation: num(e) || undefined }))}
|
|
292
|
+
<td><button ?disabled=${slots.length <= 1} @click=${() => this._edit(removeCustomSlot(draft, tierNo, s.slotNo))}>−</button></td>
|
|
293
|
+
</tr>
|
|
294
|
+
`;
|
|
295
|
+
})}
|
|
296
|
+
</table>
|
|
297
|
+
<div class="actions"><button data-action="add-slot" @click=${() => this._edit(addCustomSlot(draft, tierNo))}>${t('add-slot', 'Add slot')}</button></div>
|
|
298
|
+
`;
|
|
299
|
+
}
|
|
300
|
+
/** 위에서 본 자리 배치. 오른쪽이 +X, 위쪽이 −Z */
|
|
301
|
+
_renderPreview(draft, tierNo) {
|
|
302
|
+
const slots = previewTier(draft, tierNo);
|
|
303
|
+
if (!slots.length)
|
|
304
|
+
return html `<div class="note" data-preview="none">${t('preview-none', 'No slots to show with the current values.')}</div>`;
|
|
305
|
+
const minX = Math.min(...slots.map(s => s.x - s.width / 2));
|
|
306
|
+
const maxX = Math.max(...slots.map(s => s.x + s.width / 2));
|
|
307
|
+
const minZ = Math.min(...slots.map(s => s.z - s.depth / 2));
|
|
308
|
+
const maxZ = Math.max(...slots.map(s => s.z + s.depth / 2));
|
|
309
|
+
const w = Math.max(maxX - minX, 1);
|
|
310
|
+
const h = Math.max(maxZ - minZ, 1);
|
|
311
|
+
const pad = Math.max(w, h) * 0.08;
|
|
312
|
+
const font = Math.max(w, h) * 0.07;
|
|
313
|
+
return html `
|
|
314
|
+
<svg data-preview viewBox="${minX - pad} ${minZ - pad} ${w + pad * 2} ${h + pad * 2}" preserveAspectRatio="xMidYMid meet">
|
|
315
|
+
${slots.map(s => svg `
|
|
316
|
+
<rect data-slot=${s.slotNo} x=${s.x - s.width / 2} y=${s.z - s.depth / 2} width=${s.width} height=${s.depth}
|
|
317
|
+
fill="rgba(25,118,210,0.08)" stroke="rgba(25,118,210,0.8)" stroke-width=${Math.max(w, h) * 0.006} />
|
|
318
|
+
<text x=${s.x} y=${s.z} font-size=${font} text-anchor="middle" dominant-baseline="central" fill="#0d47a1">${s.slotNo}</text>
|
|
319
|
+
`)}
|
|
320
|
+
</svg>
|
|
321
|
+
<div class="note">${t('preview-axes', 'Right = +X, up = −Z')}</div>
|
|
322
|
+
`;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
OxSlotLayoutEditor.styles = css `
|
|
326
|
+
:host {
|
|
327
|
+
display: flex;
|
|
328
|
+
flex-direction: column;
|
|
329
|
+
gap: 6px;
|
|
330
|
+
font-size: 12px;
|
|
331
|
+
}
|
|
332
|
+
.row {
|
|
333
|
+
display: grid;
|
|
334
|
+
grid-template-columns: 40% 1fr;
|
|
335
|
+
align-items: center;
|
|
336
|
+
gap: 4px;
|
|
337
|
+
}
|
|
338
|
+
.notes {
|
|
339
|
+
padding: 4px 6px;
|
|
340
|
+
border-radius: 4px;
|
|
341
|
+
background: rgba(0, 0, 0, 0.04);
|
|
342
|
+
}
|
|
343
|
+
.error {
|
|
344
|
+
color: #c62828;
|
|
345
|
+
}
|
|
346
|
+
.note {
|
|
347
|
+
color: rgba(0, 0, 0, 0.55);
|
|
348
|
+
}
|
|
349
|
+
.chips {
|
|
350
|
+
display: flex;
|
|
351
|
+
flex-wrap: wrap;
|
|
352
|
+
gap: 4px;
|
|
353
|
+
}
|
|
354
|
+
.chips button[active] {
|
|
355
|
+
border-color: var(--md-sys-color-primary, #1976d2);
|
|
356
|
+
color: var(--md-sys-color-primary, #1976d2);
|
|
357
|
+
font-weight: bold;
|
|
358
|
+
}
|
|
359
|
+
button {
|
|
360
|
+
font-size: 12px;
|
|
361
|
+
padding: 2px 8px;
|
|
362
|
+
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
363
|
+
border-radius: 4px;
|
|
364
|
+
background: #fff;
|
|
365
|
+
cursor: pointer;
|
|
366
|
+
}
|
|
367
|
+
button[disabled] {
|
|
368
|
+
opacity: 0.4;
|
|
369
|
+
cursor: default;
|
|
370
|
+
}
|
|
371
|
+
input,
|
|
372
|
+
select {
|
|
373
|
+
font-size: 12px;
|
|
374
|
+
min-width: 0;
|
|
375
|
+
}
|
|
376
|
+
table {
|
|
377
|
+
border-collapse: collapse;
|
|
378
|
+
width: 100%;
|
|
379
|
+
}
|
|
380
|
+
td,
|
|
381
|
+
th {
|
|
382
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
383
|
+
padding: 1px;
|
|
384
|
+
}
|
|
385
|
+
td input {
|
|
386
|
+
width: 100%;
|
|
387
|
+
box-sizing: border-box;
|
|
388
|
+
}
|
|
389
|
+
h4 {
|
|
390
|
+
margin: 6px 0 2px;
|
|
391
|
+
font-size: 12px;
|
|
392
|
+
}
|
|
393
|
+
svg {
|
|
394
|
+
width: 100%;
|
|
395
|
+
height: 160px;
|
|
396
|
+
background: rgba(0, 0, 0, 0.02);
|
|
397
|
+
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
398
|
+
}
|
|
399
|
+
.actions {
|
|
400
|
+
display: flex;
|
|
401
|
+
gap: 4px;
|
|
402
|
+
justify-content: flex-end;
|
|
403
|
+
}
|
|
404
|
+
`;
|
|
405
|
+
__decorate([
|
|
406
|
+
property({ attribute: false })
|
|
407
|
+
], OxSlotLayoutEditor.prototype, "value", void 0);
|
|
408
|
+
__decorate([
|
|
409
|
+
property({ attribute: false })
|
|
410
|
+
], OxSlotLayoutEditor.prototype, "options", void 0);
|
|
411
|
+
__decorate([
|
|
412
|
+
state()
|
|
413
|
+
], OxSlotLayoutEditor.prototype, "draft", void 0);
|
|
414
|
+
__decorate([
|
|
415
|
+
state()
|
|
416
|
+
], OxSlotLayoutEditor.prototype, "tier", void 0);
|
|
417
|
+
__decorate([
|
|
418
|
+
state()
|
|
419
|
+
], OxSlotLayoutEditor.prototype, "dirty", void 0);
|
|
420
|
+
__decorate([
|
|
421
|
+
state()
|
|
422
|
+
], OxSlotLayoutEditor.prototype, "presetKey", void 0);
|
|
423
|
+
__decorate([
|
|
424
|
+
state()
|
|
425
|
+
], OxSlotLayoutEditor.prototype, "presetSize", void 0);
|
|
426
|
+
__decorate([
|
|
427
|
+
state()
|
|
428
|
+
], OxSlotLayoutEditor.prototype, "presetParams", void 0);
|
|
429
|
+
__decorate([
|
|
430
|
+
state()
|
|
431
|
+
], OxSlotLayoutEditor.prototype, "copyElevation", void 0);
|
|
432
|
+
OxSlotLayoutEditor = __decorate([
|
|
433
|
+
customElement('ox-slot-layout-editor')
|
|
434
|
+
], OxSlotLayoutEditor);
|
|
435
|
+
export { OxSlotLayoutEditor };
|
|
436
|
+
//# sourceMappingURL=ox-slot-layout-editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ox-slot-layout-editor.js","sourceRoot":"","sources":["../../src/ox-slot-layout-editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;;AAEH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AACzD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAoF,MAAM,wBAAwB,CAAA;AAEhK,OAAO,EACL,aAAa,EACb,WAAW,EACX,QAAQ,EACR,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,aAAa,EACb,cAAc,EACd,UAAU,EAGX,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,SAAS,IAAI,gBAAgB,EAAE,CAAC,EAAoB,MAAM,iBAAiB,CAAA;AA4BpF,MAAM,GAAG,GAAG,CAAC,CAAQ,EAAE,EAAE;IACvB,MAAM,CAAC,GAAI,CAAC,CAAC,MAA2B,CAAC,aAAa,CAAA;IACtD,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;AACrC,CAAC,CAAA;AAGM,IAAM,kBAAkB,GAAxB,MAAM,kBAAmB,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;IAA9D;;QAoF2B,YAAO,GAA4B,EAAE,CAAA;QAG5D,SAAI,GAAG,CAAC,CAAA;QACR,UAAK,GAAG,KAAK,CAAA;QACb,cAAS,GAAG,EAAE,CAAA;QACd,eAAU,GAAwB,EAAE,CAAA;QACpC,iBAAY,GAA2B,EAAE,CAAA;QACzC,kBAAa,GAAG,GAAG,CAAA;IA6S9B,CAAC;IA3SC,UAAU,CAAC,OAA6B;;QACtC,wEAAwE;QACxE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAwC,CAAA;QAC5E,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,SAAS,OAAK,MAAA,IAAI,CAAC,OAAO,0CAAE,SAAS,CAAA,CAAC;YAAE,IAAI,CAAC,KAAK,EAAE,CAAA;aAC9G,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,OAAO,0CAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,SAAS,CAAC,CAAA;YAC5F,IAAI,CAAC,SAAS,GAAG,MAAA,MAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,OAAO,0CAAG,CAAC,CAAC,0CAAE,GAAG,mCAAI,EAAE,CAAA;IAC1D,CAAC;IAED,gDAAgD;IAChD,KAAK;;QACH,MAAM,KAAK,GAAG,MAAA,IAAI,CAAC,KAAK,mCAAI,MAAA,IAAI,CAAC,OAAO,0CAAE,SAAS,CAAA;QACnD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QACvD,IAAI,CAAC,IAAI,GAAG,CAAC,CAAA;QACb,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,UAAU,GAAG,EAAE,GAAG,CAAC,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,UAAU,mCAAI,EAAE,CAAC,EAAE,CAAA;QACzD,IAAI,CAAC,CAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,OAAO,0CAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,SAAS,CAAC,CAAA;YAAE,IAAI,CAAC,SAAS,GAAG,MAAA,MAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,OAAO,0CAAG,CAAC,CAAC,0CAAE,GAAG,mCAAI,EAAE,CAAA;IACzH,CAAC;IAED,IAAI,MAAM;;QACR,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,CAAA;QAC1B,MAAM,OAAO,GAAkB,CAAC,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,MAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,QAAQ,mDAAG,IAAI,CAAC,KAAK,CAAC,mCAAI,EAAE,CAAC,CAAC,CAAA;QACnH,gDAAgD;QAChD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACjE,CAAC;IAEO,KAAK,CAAC,KAAkB;;QAC9B,OAAO,MAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,SAAS,mDAAG,KAAK,CAAC,mCAAI,gBAAgB,CAAC,KAAK,CAAC,CAAA;IACpE,CAAC;IAEO,KAAK,CAAC,IAAgB;;QAC5B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,IAAI,CAAC;YAAE,IAAI,CAAC,IAAI,GAAG,MAAA,MAAA,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,0CAAE,MAAM,mCAAI,CAAC,CAAA;IAC/G,CAAC;IAEO,MAAM;QACZ,IAAI,CAAC,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACxC,IAAI,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAC5E,CAAC;IAEO,WAAW;QACjB,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,yBAAyB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IACnG,CAAC;IAED,MAAM;;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;QAC1B,MAAM,aAAa,GAAG,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,aAAa,mCAAI,IAAI,CAAC,KAAK,KAAK,SAAS,CAAA;QAC7E,OAAO,IAAI,CAAA;QACP,CAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,KAAK,0CAAE,MAAM;YAC3B,CAAC,CAAC,IAAI,CAAA;cACA,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAA,cAAc,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,QAAQ,CAAC;iBACpG;YACT,CAAC,CAAC,OAAO;QACT,IAAI,CAAC,aAAa,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO;;UAEtE,aAAa,CAAC,CAAC,CAAC,IAAI,CAAA,4CAA4C,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC,aAAa,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO;kDAChG,CAAC,IAAI,CAAC,KAAK,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC;gDACrE,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,WAAW,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC;;KAEvI,CAAA;IACH,CAAC;IAEO,aAAa;;QACnB,MAAM,OAAO,GAAG,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,OAAO,mCAAI,EAAE,CAAA;QAC3C,IAAI,CAAC,OAAO,CAAC,MAAM;YAAE,OAAO,OAAO,CAAA;QACnC,MAAM,MAAM,GAAG,MAAA,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,SAAS,CAAC,mCAAI,OAAO,CAAC,CAAC,CAAC,CAAA;QACxE,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,CAAA;QACrE,MAAM,SAAS,GAAG,CAAC,KAAa,EAAE,KAAc,EAAE,QAA6B,EAAE,GAAW,EAAE,EAAE,CAAC,IAAI,CAAA;;iBAExF,KAAK;2CACqB,GAAG,WAAW,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;;KAE7J,CAAA;QACD,OAAO,IAAI,CAAA;YACH,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC;;iBAEhB,CAAC,CAAC,aAAa,EAAE,MAAM,CAAC;6CACI,CAAC,CAAQ,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,GAAI,CAAC,CAAC,MAA4B,CAAC,KAAK,CAAC;YACvG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAA,iBAAiB,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,WAAW,CAAC;;;QAGxG,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC;QAC7H,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC;QAC7H,SAAS,CAAC,CAAC,CAAC,aAAa,EAAE,aAAa,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,EAAE,YAAY,CAAC;QACpJ,CAAC,MAAA,MAAM,CAAC,MAAM,mCAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAC9B,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CACrH;0BACmB,CAAC,CAAC,aAAa,EAAE,mGAAmG,CAAC;;uDAExF,CAAC,KAAK,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC;YACtH,CAAC,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;;;KAGlD,CAAA;IACH,CAAC;IAED,sDAAsD;IAC9C,UAAU,CAAC,MAAkB;;QACnC,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,KAAK,EAAE,GAAG,CAAC,MAAA,IAAI,CAAC,KAAK,mCAAI,EAAE,CAA4B,CAAA;QACxF,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,EAAE,GAAG,eAAe,CAAC,MAAM,CAAC,EAAgB,CAAC,CAAA;IACpE,CAAC;IAEO,YAAY,CAAC,MAAqB;;QACxC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAM,CAAA;QACzB,MAAM,IAAI,GAAG,MAAA,MAAA,KAAK,CAAC,KAAK,0CAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,IAAI,CAAC,mCAAI,MAAA,KAAK,CAAC,KAAK,0CAAG,CAAC,CAAC,CAAA;QAC/E,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA,kCAAkC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAA,UAAU,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAA;QAC9I,uDAAuD;QACvD,IAAI,CAAC,IAAI;YAAE,OAAO,SAAS,CAAA;QAC3B,OAAO,IAAI,CAAA;YACH,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC;;UAErB,KAAK,CAAC,KAAK,CAAC,GAAG,CACf,CAAC,CAAC,EAAE,CAAC,IAAI,CAAA,qBAAqB,CAAC,CAAC,MAAM,YAAY,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,CACzK;;;iBAGQ,CAAC,CAAC,gBAAgB,EAAE,oBAAoB,CAAC;kEACQ,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,CAAQ,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;;;oDAG1I,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;YACzJ,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC;;sDAEe,KAAK,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,aAAa,CAAC;;QAEnK,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,MAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,WAAW,mDAAG,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,mCAAI,OAAO;YAC3G,CAAC,CAAC,SAAS,EAAE,8BAA8B,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;QACpE,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,SAAS;KACvD,CAAA;IACH,CAAC;IAEO,WAAW,CAAC,KAAiB,EAAE,MAAc;;QACnD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAE,CAAA;QACxD,MAAM,CAAC,GAAG,IAAI,CAAC,MAA2C,CAAA;QAC1D,MAAM,YAAY,GAAG,CAAC,CAAS,EAAE,KAAa,EAAE,KAAc,EAAE,QAA+B,GAAG,EAAE,EAAE,CAAC,IAAI,CAAA;;iBAE9F,KAAK;0CACoB,CAAC,WAAW,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;;KAErM,CAAA;QACD,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,KAAa,EAAE,KAAa,EAAE,OAAiB,EAAE,QAA6B,EAAE,EAAE,CAAC,IAAI,CAAA;;iBAEzG,KAAK;6BACO,KAAK,YAAY,CAAC,CAAQ,EAAE,EAAE,CAAC,QAAQ,CAAE,CAAC,CAAC,MAA4B,CAAC,KAAK,CAAC;YAC/F,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAA,iBAAiB,CAAC,cAAc,CAAC,KAAK,KAAK,IAAI,CAAC,WAAW,CAAC;;;KAGxF,CAAA;QACD,OAAO,IAAI,CAAA;;iBAEE,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC;6DACiB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;;QAEjK,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,CAA2B,CAAC,CAAC,CAAC;QACvK,CAAC,CAAC,OAAO,KAAK,KAAK;YACnB,CAAC,CAAC,IAAI,CAAA;cACA,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC;cAC1G,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,CAAc,EAAE,CAAC,CAAC,CAAC;cAC9H,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,MAAA,CAAC,CAAC,MAAM,mCAAI,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAkB,EAAE,CAAC,CAAC,CAAC;WAC/J;YACH,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,OAAO,KAAK,MAAM;YACpB,CAAC,CAAC,IAAI,CAAA;cACA,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC;cAC7G,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,MAAA,CAAC,CAAC,KAAK,0CAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;cAC5F,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,MAAA,CAAC,CAAC,KAAK,0CAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;cAC5F,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,MAAA,CAAC,CAAC,KAAK,mCAAI,WAAW,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,EAAE,CAAC,CAAC,EAAE,CACrI,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAc,EAAE,CAAC,CAAC,CACpE;cACC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,aAAa,CAAC,EAAE,MAAA,MAAA,CAAC,CAAC,MAAM,0CAAE,CAAC,mCAAI,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,CAC3F,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAkB,EAAE,EAAE,CAAC,CAAC,CACvG;cACC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,aAAa,CAAC,EAAE,MAAA,MAAA,CAAC,CAAC,MAAM,0CAAE,CAAC,mCAAI,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,CAC3F,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAkB,EAAE,EAAE,CAAC,CAAC,CACvG;WACF;YACH,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC;KACvG,CAAA;IACH,CAAC;IAED,2DAA2D;IACnD,cAAc,CAAC,IAAc,EAAE,QAAiD;;QACtF,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAC9C,OAAO,IAAI,CAAA;;iBAEE,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC;oDACY,CAAC,CAAQ,EAAE,EAAE,WAAC,OAAA,QAAQ,CAAE,CAAC,CAAC,MAA4B,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAA,IAAI,CAAC,OAAO,mCAAI,EAAE,CAAC,CAAA,EAAA;0CAClH,CAAC,UAAU,IAAI,CAAC,CAAC,aAAa,EAAE,UAAU,CAAC;2CAC1C,UAAU,IAAI,CAAC,CAAC,cAAc,EAAE,mBAAmB,CAAC;;;QAGvF,UAAU;YACV,CAAC,CAAC,IAAI,CAAA;qBACO,CAAC,CAAC,eAAe,EAAE,eAAe,CAAC;;;uBAGjC,CAAC,MAAA,IAAI,CAAC,OAAO,mCAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;4BAC1B,CAAC,CAAC,cAAc,EAAE,yBAAyB,CAAC;wBAChD,CAAC,CAAQ,EAAE,EAAE,CACrB,QAAQ,CACL,CAAC,CAAC,MAA2B,CAAC,KAAK;iBACjC,KAAK,CAAC,GAAG,CAAC;iBACV,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;iBAClB,MAAM,CAAC,OAAO,CAAC,CACnB;;iBAEA;YACT,CAAC,CAAC,OAAO;KACZ,CAAA;IACH,CAAC;IAEO,WAAW,CAAC,KAAiB,EAAE,MAAc,EAAE,IAAc;;QACnE,MAAM,IAAI,GAAG,CAAC,KAAwB,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAA;QAC3F,MAAM,OAAO,GAAG,CAAC,CAA+B,EAAE,KAAa,EAAE,EAAE;;YAAC,OAAA,IAAI,CAAA;;iBAE3D,KAAK;0CACoB,WAAW,CAAC,EAAE,WAAW,MAAM,CAAC,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAG,CAAC,CAAC,mCAAI,EAAE,CAAC,YAAY,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;;KAE1K,CAAA;SAAA,CAAA;QACD,OAAO,IAAI,CAAA;YACH,CAAC,CAAC,WAAW,EAAE,yCAAyC,CAAC;QAC7D,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;QAC9G,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC;;iBAE/C,CAAC,CAAC,aAAa,EAAE,aAAa,CAAC;gFACgC,MAAM,CAAC,MAAA,IAAI,CAAC,UAAU,mCAAI,CAAC,CAAC,YAAY,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;;;iBAGjJ,CAAC,CAAC,UAAU,EAAE,gBAAgB,CAAC;4DACY,MAAM,CAAC,MAAA,IAAI,CAAC,QAAQ,mCAAI,CAAC,CAAC,YAAY,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,SAAS,EAAE,CAAC;;QAE/I,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;KAC1D,CAAA;IACH,CAAC;IAEO,aAAa,CAAC,KAAiB,EAAE,MAAc;QACrD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAE,CAAA;QACxD,MAAM,KAAK,GAAG,CAAC,GAAI,IAAI,CAAC,MAAsD,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAA;QACzH,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,KAAc,EAAE,KAA4C,EAAE,EAAE,CAC5F,IAAI,CAAA,mCAAmC,MAAM,CAAC,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,EAAE,CAAC,YAAY,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,UAAU,CAAA;QAC3J,OAAO,IAAI,CAAA;YACH,CAAC,CAAC,cAAc,EAAE,OAAO,CAAC;;;;;;gBAMtB,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC;gBACX,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC;gBACX,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC;gBACX,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC;gBACnB,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC;;;UAGrB,KAAK,CAAC,GAAG,CACT,CAAC,CAAC,EAAE;;YAAC,OAAA,IAAI,CAAA;;oBAEC,CAAC,CAAC,MAAM;gBACZ,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,MAAA,CAAC,CAAC,MAAM,0CAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC1E,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,MAAA,CAAC,CAAC,MAAM,0CAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC1E,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,MAAA,CAAC,CAAC,OAAO,0CAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACrF,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,MAAA,CAAC,CAAC,OAAO,0CAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACrF,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,MAAA,CAAC,CAAC,OAAO,0CAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACvF,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,MAAA,CAAC,CAAC,UAAU,mCAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,MAAA,CAAC,CAAC,QAAQ,mCAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC;sCACvH,KAAK,CAAC,MAAM,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;;WAElH,CAAA;SAAA,CACF;;mEAE0D,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC;KACzI,CAAA;IACH,CAAC;IAED,mCAAmC;IAC3B,cAAc,CAAC,KAAiB,EAAE,MAAc;QACtD,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;QACxC,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,OAAO,IAAI,CAAA,yCAAyC,CAAC,CAAC,cAAc,EAAE,2CAA2C,CAAC,QAAQ,CAAA;QAC7I,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAA;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAA;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAA;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAA;QAC3D,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC,CAAA;QAClC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC,CAAA;QAClC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAA;QACjC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAA;QAClC,OAAO,IAAI,CAAA;mCACoB,IAAI,GAAG,GAAG,IAAI,IAAI,GAAG,GAAG,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;UAC/E,KAAK,CAAC,GAAG,CACT,CAAC,CAAC,EAAE,CAAC,GAAG,CAAA;8BACY,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,UAAU,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,KAAK;wFAC/B,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK;sBACxF,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,IAAI,oEAAoE,CAAC,CAAC,MAAM;WACrH,CACF;;0BAEiB,CAAC,CAAC,cAAc,EAAE,qBAAqB,CAAC;KAC7D,CAAA;IACH,CAAC;;AAvYM,yBAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+ElB,AA/EY,CA+EZ;AAG+B;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;iDAA8B;AAC7B;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;mDAAsC;AAE5D;IAAR,KAAK,EAAE;iDAA8B;AAC7B;IAAR,KAAK,EAAE;gDAAS;AACR;IAAR,KAAK,EAAE;iDAAc;AACb;IAAR,KAAK,EAAE;qDAAe;AACd;IAAR,KAAK,EAAE;sDAAqC;AACpC;IAAR,KAAK,EAAE;wDAA0C;AACzC;IAAR,KAAK,EAAE;yDAAoB;AA5FjB,kBAAkB;IAD9B,aAAa,CAAC,uBAAuB,CAAC;GAC1B,kBAAkB,CAyY9B","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * 자리 레이아웃 편집기. 홀더의 층·자리 구성을 고치고, 검증하고, 미리 보고, [적용]으로 알린다.\n *\n * 컴포넌트 상태를 직접 바꾸지 않는다. 호스트에 두 가지만 알린다:\n * change — 적용한 레이아웃 (this.value)\n * slot-layout-use-default — 개별 설정을 지우고 기본값을 쓰겠다는 의도 (값을 비우는 편집과 다르다)\n * 기본값을 쓰게 된 뒤 보일 값은 호스트가 options.effective 로 다시 준다.\n */\n\nimport { css, html, nothing, LitElement, svg } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { persistenceIssues, validateSlotLayout, type GridOrder, type SlotLayout, type SlotMount, type SlotPattern, type SlotSpec } from '@hatiolab/things-scene'\n\nimport {\n addCustomSlot,\n applyPreset,\n copyTier,\n previewTier,\n removeCustomSlot,\n removeTier,\n setTierPattern,\n updatePattern,\n updateSlotSpec,\n updateTier,\n type PresetSize,\n type SlotLayoutPreset\n} from './draft.js'\nimport { issueText as defaultIssueText, t, type EditorIssue } from './issue-text.js'\n\nexport interface SlotLayoutEditorNote {\n text: string\n level?: 'info' | 'error'\n}\n\nexport interface SlotLayoutEditorOptions<L extends SlotLayout = SlotLayout> {\n /** 개별 설정이 없을 때 보이는 값 (호스트가 고른 기본값). 편집 시작점이 된다 */\n effective?: L\n /** [기본값 사용]을 보일지. 기본은 개별 설정(value)이 있을 때 */\n canUseDefault?: boolean\n /** 호스트가 주는 프리셋 */\n presets?: SlotLayoutPreset<L>[]\n /** 프리셋 크기 초기값 */\n presetSize?: Partial<PresetSize>\n /** 상태 문구 (값의 출처, 단위 상태 등) */\n notes?: SlotLayoutEditorNote[]\n /** 프리셋 없이 새로 시작할 때의 mount. 기본 ON */\n mount?: SlotMount\n /** 호스트 고유 검증 (경로는 레이아웃 기준) */\n validate?: (layout: L) => EditorIssue[]\n /** 문제 문구. undefined 를 돌려주면 기본 문구 */\n issueText?: (issue: EditorIssue) => string | undefined\n /** 레이아웃에 덧붙인 호스트 키를 고치는 구역 (예: 실시간 주소 읽기). edit 로 새 레이아웃을 넘긴다 */\n renderExtra?: (draft: L, edit: (next: L) => void) => unknown\n}\n\nconst num = (e: Event) => {\n const n = (e.target as HTMLInputElement).valueAsNumber\n return Number.isFinite(n) ? n : NaN\n}\n\n@customElement('ox-slot-layout-editor')\nexport class OxSlotLayoutEditor extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n gap: 6px;\n font-size: 12px;\n }\n .row {\n display: grid;\n grid-template-columns: 40% 1fr;\n align-items: center;\n gap: 4px;\n }\n .notes {\n padding: 4px 6px;\n border-radius: 4px;\n background: rgba(0, 0, 0, 0.04);\n }\n .error {\n color: #c62828;\n }\n .note {\n color: rgba(0, 0, 0, 0.55);\n }\n .chips {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n }\n .chips button[active] {\n border-color: var(--md-sys-color-primary, #1976d2);\n color: var(--md-sys-color-primary, #1976d2);\n font-weight: bold;\n }\n button {\n font-size: 12px;\n padding: 2px 8px;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 4px;\n background: #fff;\n cursor: pointer;\n }\n button[disabled] {\n opacity: 0.4;\n cursor: default;\n }\n input,\n select {\n font-size: 12px;\n min-width: 0;\n }\n table {\n border-collapse: collapse;\n width: 100%;\n }\n td,\n th {\n border-bottom: 1px solid rgba(0, 0, 0, 0.08);\n padding: 1px;\n }\n td input {\n width: 100%;\n box-sizing: border-box;\n }\n h4 {\n margin: 6px 0 2px;\n font-size: 12px;\n }\n svg {\n width: 100%;\n height: 160px;\n background: rgba(0, 0, 0, 0.02);\n border: 1px solid rgba(0, 0, 0, 0.08);\n }\n .actions {\n display: flex;\n gap: 4px;\n justify-content: flex-end;\n }\n `\n\n /** 개별 설정. 없으면 undefined */\n @property({ attribute: false }) value: SlotLayout | undefined\n @property({ attribute: false }) options: SlotLayoutEditorOptions = {}\n\n @state() draft: SlotLayout | undefined\n @state() tier = 1\n @state() dirty = false\n @state() presetKey = ''\n @state() presetSize: Partial<PresetSize> = {}\n @state() presetParams: Record<string, number> = {}\n @state() copyElevation = NaN\n\n willUpdate(changed: Map<string, unknown>) {\n // 호스트가 렌더마다 새 options 를 넘겨도 편집 중인 값을 잃지 않도록, 값이나 호스트 기본값이 바뀔 때만 다시 시작한다\n const before = changed.get('options') as SlotLayoutEditorOptions | undefined\n if (changed.has('value') || (changed.has('options') && before?.effective !== this.options?.effective)) this.reset()\n else if (changed.has('options') && !this.options?.presets?.some(p => p.key === this.presetKey))\n this.presetKey = this.options?.presets?.[0]?.key ?? ''\n }\n\n /** 편집 중인 값을 버리고 개별 설정(없으면 호스트 기본값)에서 다시 시작한다 */\n reset() {\n const start = this.value ?? this.options?.effective\n this.draft = start ? structuredClone(start) : undefined\n this.tier = 1\n this.dirty = false\n this.presetSize = { ...(this.options?.presetSize ?? {}) }\n if (!this.options?.presets?.some(p => p.key === this.presetKey)) this.presetKey = this.options?.presets?.[0]?.key ?? ''\n }\n\n get issues(): EditorIssue[] {\n if (!this.draft) return []\n const invalid: EditorIssue[] = [...validateSlotLayout(this.draft), ...(this.options?.validate?.(this.draft) ?? [])]\n // 저장 불가 검사는 검증을 통과한 값에만 보인다 (같은 문제를 두 번 적지 않는다)\n return invalid.length ? invalid : persistenceIssues(this.draft)\n }\n\n private _text(issue: EditorIssue) {\n return this.options?.issueText?.(issue) ?? defaultIssueText(issue)\n }\n\n private _edit(next: SlotLayout) {\n this.draft = next\n this.dirty = true\n if (!next.tiers.some(x => x.tierNo === this.tier)) this.tier = next.tiers[next.tiers.length - 1]?.tierNo ?? 1\n }\n\n private _apply() {\n this.value = structuredClone(this.draft)\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n\n private _useDefault() {\n this.dispatchEvent(new CustomEvent('slot-layout-use-default', { bubbles: true, composed: true }))\n }\n\n render() {\n const issues = this.issues\n const canUseDefault = this.options?.canUseDefault ?? this.value !== undefined\n return html`\n ${this.options?.notes?.length\n ? html`<div class=\"notes\">\n ${this.options.notes.map(n => html`<div class=${n.level === 'error' ? 'error' : 'note'}>${n.text}</div>`)}\n </div>`\n : nothing}\n ${this._renderPreset()} ${this.draft ? this._renderDraft(issues) : nothing}\n <div class=\"actions\">\n ${canUseDefault ? html`<button data-action=\"use-default\" @click=${this._useDefault}>${t('use-default', 'Use default')}</button>` : nothing}\n <button data-action=\"discard\" ?disabled=${!this.dirty} @click=${() => this.reset()}>${t('discard', 'Discard')}</button>\n <button data-action=\"apply\" ?disabled=${!this.dirty || !this.draft || issues.length > 0} @click=${this._apply}>${t('apply', 'Apply')}</button>\n </div>\n `\n }\n\n private _renderPreset() {\n const presets = this.options?.presets ?? []\n if (!presets.length) return nothing\n const preset = presets.find(p => p.key === this.presetKey) ?? presets[0]\n const built = applyPreset(preset, this.presetSize, this.presetParams)\n const numberRow = (label: string, value: unknown, onChange: (n: number) => void, key: string) => html`\n <div class=\"row\">\n <label>${label}</label>\n <input type=\"number\" data-preset=${key} .value=${value === undefined || Number.isNaN(value) ? '' : String(value)} @change=${(e: Event) => onChange(num(e))} />\n </div>\n `\n return html`\n <h4>${t('preset', 'Preset')}</h4>\n <div class=\"row\">\n <label>${t('preset-kind', 'Kind')}</label>\n <select data-preset=\"kind\" @change=${(e: Event) => (this.presetKey = (e.target as HTMLSelectElement).value)}>\n ${presets.map(p => html`<option value=${p.key} ?selected=${p.key === preset.key}>${p.label}</option>`)}\n </select>\n </div>\n ${numberRow(t('width', 'Width (X)'), this.presetSize.width, n => (this.presetSize = { ...this.presetSize, width: n }), 'width')}\n ${numberRow(t('depth', 'Depth (Z)'), this.presetSize.depth, n => (this.presetSize = { ...this.presetSize, depth: n }), 'depth')}\n ${numberRow(t('slot-height', 'Slot height'), this.presetSize.slotHeight, n => (this.presetSize = { ...this.presetSize, slotHeight: n }), 'slotHeight')}\n ${(preset.params ?? []).map(p =>\n numberRow(p.label, this.presetParams[p.key], n => (this.presetParams = { ...this.presetParams, [p.key]: n }), p.key)\n )}\n <div class=\"note\">${t('preset-note', 'Pitch and slot size are split evenly as starting values. Confirm them against the real equipment.')}</div>\n <div class=\"actions\">\n <button data-action=\"start-preset\" ?disabled=${!built.layout} @click=${() => built.layout && this._startFrom(built.layout)}>\n ${t('start-from-preset', 'Start from preset')}\n </button>\n </div>\n `\n }\n\n /** 프리셋으로 다시 시작한다. 지금 값에 덧붙은 호스트 키(층·자리 밖의 키)는 가져온다 */\n private _startFrom(layout: SlotLayout) {\n const { mount: _m, tiers: _t, ...extra } = (this.draft ?? {}) as Record<string, unknown>\n this._edit({ ...extra, ...structuredClone(layout) } as SlotLayout)\n }\n\n private _renderDraft(issues: EditorIssue[]) {\n const draft = this.draft!\n const tier = draft.tiers?.find(x => x.tierNo === this.tier) ?? draft.tiers?.[0]\n const issueList = issues.length ? html`<div class=\"error\" data-issues>${issues.map(i => html`<div>· ${this._text(i)}</div>`)}</div>` : nothing\n // 층이 없는 값(잘못된 저장값)은 편집할 층이 없으므로 문제만 보인다. 프리셋으로 다시 시작한다\n if (!tier) return issueList\n return html`\n <h4>${t('tiers', 'Tiers')}</h4>\n <div class=\"chips\">\n ${draft.tiers.map(\n x => html`<button data-tier=${x.tierNo} ?active=${x.tierNo === tier.tierNo} @click=${() => (this.tier = x.tierNo)}>${t('tier-n', 'Tier {n}', { n: x.tierNo })}</button>`\n )}\n </div>\n <div class=\"row\">\n <label>${t('copy-elevation', 'New tier elevation')}</label>\n <input type=\"number\" data-field=\"copy-elevation\" .value=${Number.isFinite(this.copyElevation) ? String(this.copyElevation) : ''} @change=${(e: Event) => (this.copyElevation = num(e))} />\n </div>\n <div class=\"actions\">\n <button data-action=\"copy-tier\" ?disabled=${!Number.isFinite(this.copyElevation)} @click=${() => this._edit(copyTier(draft, tier.tierNo, this.copyElevation))}>\n ${t('copy-tier', 'Copy tier')}\n </button>\n <button data-action=\"remove-tier\" ?disabled=${draft.tiers.length <= 1} @click=${() => this._edit(removeTier(draft, tier.tierNo))}>${t('remove-tier', 'Remove tier')}</button>\n </div>\n ${this._renderTier(draft, tier.tierNo)} ${this.options?.renderExtra?.(draft, next => this._edit(next)) ?? nothing}\n <h4>${t('preview', 'Preview (top view, tier {n})', { n: tier.tierNo })}</h4>\n ${this._renderPreview(draft, tier.tierNo)} ${issueList}\n `\n }\n\n private _renderTier(draft: SlotLayout, tierNo: number) {\n const tier = draft.tiers.find(x => x.tierNo === tierNo)!\n const l = tier.layout as SlotPattern & Record<string, any>\n const patternInput = (k: string, label: string, value: unknown, parse: (e: Event) => unknown = num) => html`\n <div class=\"row\">\n <label>${label}</label>\n <input type=\"number\" data-field=${k} .value=${value === undefined || Number.isNaN(value) ? '' : String(value)} @change=${(e: Event) => this._edit(updatePattern(draft, tierNo, { [k]: parse(e) }))} />\n </div>\n `\n const select = (field: string, label: string, value: string, options: string[], onChange: (v: string) => void) => html`\n <div class=\"row\">\n <label>${label}</label>\n <select data-field=${field} @change=${(e: Event) => onChange((e.target as HTMLSelectElement).value)}>\n ${options.map(o => html`<option value=${o} ?selected=${o === value}>${o}</option>`)}\n </select>\n </div>\n `\n return html`\n <div class=\"row\">\n <label>${t('elevation', 'Elevation')}</label>\n <input type=\"number\" data-field=\"elevation\" .value=${String(tier.elevation)} @change=${(e: Event) => this._edit(updateTier(draft, tierNo, { elevation: num(e) }))} />\n </div>\n ${select('pattern', t('pattern', 'Pattern'), l.pattern, ['SINGLE', 'ROW', 'GRID', 'CUSTOM'], v => this._edit(setTierPattern(draft, tierNo, v as SlotPattern['pattern'])))}\n ${l.pattern === 'ROW'\n ? html`\n ${patternInput('count', t('count', 'Slots'), l.count)} ${patternInput('pitch', t('pitch', 'Pitch'), l.pitch)}\n ${select('axis', t('axis', 'Axis'), l.axis, ['X', 'Z'], v => this._edit(updatePattern(draft, tierNo, { axis: v as 'X' | 'Z' })))}\n ${select('origin', t('origin', 'Slot 1 at'), l.origin ?? 'MIN', ['MIN', 'MAX'], v => this._edit(updatePattern(draft, tierNo, { origin: v as 'MIN' | 'MAX' })))}\n `\n : nothing}\n ${l.pattern === 'GRID'\n ? html`\n ${patternInput('rows', t('rows', 'Rows (Z)'), l.rows)} ${patternInput('cols', t('cols', 'Columns (X)'), l.cols)}\n ${patternInput('pitch', t('pitch-x', 'Pitch X'), l.pitch?.x, e => ({ ...l.pitch, x: num(e) }))}\n ${patternInput('pitch', t('pitch-z', 'Pitch Z'), l.pitch?.z, e => ({ ...l.pitch, z: num(e) }))}\n ${select('order', t('order', 'Numbering'), l.order ?? 'ROW_MAJOR', ['ROW_MAJOR', 'COL_MAJOR', 'SERPENTINE_ROW', 'SERPENTINE_COL'], v =>\n this._edit(updatePattern(draft, tierNo, { order: v as GridOrder }))\n )}\n ${select('origin-x', t('origin-x', 'Slot 1 at X'), l.origin?.x ?? 'MIN', ['MIN', 'MAX'], v =>\n this._edit(updatePattern(draft, tierNo, { origin: { z: 'MIN', ...l.origin, x: v as 'MIN' | 'MAX' } }))\n )}\n ${select('origin-z', t('origin-z', 'Slot 1 at Z'), l.origin?.z ?? 'MIN', ['MIN', 'MAX'], v =>\n this._edit(updatePattern(draft, tierNo, { origin: { x: 'MIN', ...l.origin, z: v as 'MIN' | 'MAX' } }))\n )}\n `\n : nothing}\n ${l.pattern === 'CUSTOM' ? this._renderCustom(draft, tierNo) : this._renderSpec(draft, tierNo, l.slot)}\n `\n }\n\n /** 받는 타입: 모든 타입(accepts 생략) / 지정 타입만(빈 목록이면 아무것도 받지 않음) */\n private _acceptsEditor(spec: SlotSpec, onChange: (accepts: string[] | undefined) => void) {\n const restricted = Array.isArray(spec.accepts)\n return html`\n <div class=\"row\">\n <label>${t('accepts', 'Accepts')}</label>\n <select data-field=\"accepts-mode\" @change=${(e: Event) => onChange((e.target as HTMLSelectElement).value === 'ALL' ? undefined : spec.accepts ?? [])}>\n <option value=\"ALL\" ?selected=${!restricted}>${t('accepts-all', 'Any type')}</option>\n <option value=\"LIST\" ?selected=${restricted}>${t('accepts-list', 'Listed types only')}</option>\n </select>\n </div>\n ${restricted\n ? html`<div class=\"row\">\n <label>${t('accepts-types', 'Types (comma)')}</label>\n <input\n data-field=\"accepts\"\n .value=${(spec.accepts ?? []).join(', ')}\n placeholder=${t('accepts-none', 'Empty = accepts nothing')}\n @change=${(e: Event) =>\n onChange(\n (e.target as HTMLInputElement).value\n .split(',')\n .map(s => s.trim())\n .filter(Boolean)\n )}\n />\n </div>`\n : nothing}\n `\n }\n\n private _renderSpec(draft: SlotLayout, tierNo: number, spec: SlotSpec) {\n const edit = (patch: Partial<SlotSpec>) => this._edit(updateSlotSpec(draft, tierNo, patch))\n const allowed = (k: 'width' | 'depth' | 'height', label: string) => html`\n <div class=\"row\">\n <label>${label}</label>\n <input type=\"number\" data-field=${`allowed-${k}`} .value=${String(spec.allowed?.[k] ?? '')} @change=${(e: Event) => edit({ allowed: { ...spec.allowed, [k]: num(e) } })} />\n </div>\n `\n return html`\n <h4>${t('slot-spec', 'Slot (same for every slot in this tier)')}</h4>\n ${allowed('width', t('allowed-width', 'Allowed width'))} ${allowed('depth', t('allowed-depth', 'Allowed depth'))}\n ${allowed('height', t('allowed-height', 'Allowed height'))}\n <div class=\"row\">\n <label>${t('stack-limit', 'Stack limit')}</label>\n <input type=\"number\" data-field=\"stack-limit\" min=\"1\" step=\"1\" .value=${String(spec.stackLimit ?? 1)} @change=${(e: Event) => edit({ stackLimit: num(e) })} />\n </div>\n <div class=\"row\">\n <label>${t('rotation', 'Rotation (deg)')}</label>\n <input type=\"number\" data-field=\"rotation\" .value=${String(spec.rotation ?? 0)} @change=${(e: Event) => edit({ rotation: num(e) || undefined })} />\n </div>\n ${this._acceptsEditor(spec, accepts => edit({ accepts }))}\n `\n }\n\n private _renderCustom(draft: SlotLayout, tierNo: number) {\n const tier = draft.tiers.find(x => x.tierNo === tierNo)!\n const slots = [...(tier.layout as Extract<SlotPattern, { pattern: 'CUSTOM' }>).slots].sort((a, b) => a.slotNo - b.slotNo)\n const cell = (slotNo: number, value: unknown, patch: (e: Event) => Record<string, unknown>) =>\n html`<td><input type=\"number\" .value=${String(value ?? '')} @change=${(e: Event) => this._edit(updateSlotSpec(draft, tierNo, patch(e), slotNo))} /></td>`\n return html`\n <h4>${t('custom-slots', 'Slots')}</h4>\n <table>\n <tr>\n <th>#</th>\n <th>X</th>\n <th>Z</th>\n <th>${t('w', 'W')}</th>\n <th>${t('d', 'D')}</th>\n <th>${t('h', 'H')}</th>\n <th>${t('stack', 'Stack')}</th>\n <th>${t('rot', 'Rot')}</th>\n <th></th>\n </tr>\n ${slots.map(\n s => html`\n <tr>\n <td>${s.slotNo}</td>\n ${cell(s.slotNo, s.offset?.x, e => ({ offset: { ...s.offset, x: num(e) } }))}\n ${cell(s.slotNo, s.offset?.z, e => ({ offset: { ...s.offset, z: num(e) } }))}\n ${cell(s.slotNo, s.allowed?.width, e => ({ allowed: { ...s.allowed, width: num(e) } }))}\n ${cell(s.slotNo, s.allowed?.depth, e => ({ allowed: { ...s.allowed, depth: num(e) } }))}\n ${cell(s.slotNo, s.allowed?.height, e => ({ allowed: { ...s.allowed, height: num(e) } }))}\n ${cell(s.slotNo, s.stackLimit ?? 1, e => ({ stackLimit: num(e) }))} ${cell(s.slotNo, s.rotation ?? 0, e => ({ rotation: num(e) || undefined }))}\n <td><button ?disabled=${slots.length <= 1} @click=${() => this._edit(removeCustomSlot(draft, tierNo, s.slotNo))}>−</button></td>\n </tr>\n `\n )}\n </table>\n <div class=\"actions\"><button data-action=\"add-slot\" @click=${() => this._edit(addCustomSlot(draft, tierNo))}>${t('add-slot', 'Add slot')}</button></div>\n `\n }\n\n /** 위에서 본 자리 배치. 오른쪽이 +X, 위쪽이 −Z */\n private _renderPreview(draft: SlotLayout, tierNo: number) {\n const slots = previewTier(draft, tierNo)\n if (!slots.length) return html`<div class=\"note\" data-preview=\"none\">${t('preview-none', 'No slots to show with the current values.')}</div>`\n const minX = Math.min(...slots.map(s => s.x - s.width / 2))\n const maxX = Math.max(...slots.map(s => s.x + s.width / 2))\n const minZ = Math.min(...slots.map(s => s.z - s.depth / 2))\n const maxZ = Math.max(...slots.map(s => s.z + s.depth / 2))\n const w = Math.max(maxX - minX, 1)\n const h = Math.max(maxZ - minZ, 1)\n const pad = Math.max(w, h) * 0.08\n const font = Math.max(w, h) * 0.07\n return html`\n <svg data-preview viewBox=\"${minX - pad} ${minZ - pad} ${w + pad * 2} ${h + pad * 2}\" preserveAspectRatio=\"xMidYMid meet\">\n ${slots.map(\n s => svg`\n <rect data-slot=${s.slotNo} x=${s.x - s.width / 2} y=${s.z - s.depth / 2} width=${s.width} height=${s.depth}\n fill=\"rgba(25,118,210,0.08)\" stroke=\"rgba(25,118,210,0.8)\" stroke-width=${Math.max(w, h) * 0.006} />\n <text x=${s.x} y=${s.z} font-size=${font} text-anchor=\"middle\" dominant-baseline=\"central\" fill=\"#0d47a1\">${s.slotNo}</text>\n `\n )}\n </svg>\n <div class=\"note\">${t('preview-axes', 'Right = +X, up = −Z')}</div>\n `\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ox-slot-layout-editor': OxSlotLayoutEditor\n }\n}\n"]}
|