@operato/slot-layout-editor 10.29.51 → 10.29.56
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 +17 -0
- package/dist/src/draft.d.ts +53 -6
- package/dist/src/draft.js +132 -23
- package/dist/src/draft.js.map +1 -1
- package/dist/src/issue-text.d.ts +13 -0
- package/dist/src/issue-text.js +78 -7
- package/dist/src/issue-text.js.map +1 -1
- package/dist/src/ox-property-editor-slot-layout.d.ts +1 -0
- package/dist/src/ox-property-editor-slot-layout.js +2 -0
- package/dist/src/ox-property-editor-slot-layout.js.map +1 -1
- package/dist/src/ox-slot-layout-editor.d.ts +66 -14
- package/dist/src/ox-slot-layout-editor.js +739 -239
- package/dist/src/ox-slot-layout-editor.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/translations/en.json +76 -35
- package/translations/es.json +84 -43
- package/translations/ja.json +90 -49
- package/translations/ko.json +89 -48
- package/translations/ms.json +90 -49
- package/translations/zh.json +90 -49
|
@@ -1,25 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license Copyright © HatioLab Inc. All rights reserved.
|
|
3
3
|
*
|
|
4
|
-
* 자리 레이아웃 편집기. 홀더의 Tier·Slot 구성을 고치고, 검증하고, 미리 보고,
|
|
4
|
+
* 자리 레이아웃 편집기. 홀더의 Tier·Slot 구성을 고치고, 검증하고, 미리 보고, 호스트에 알린다.
|
|
5
5
|
* Tier 는 홀더가 주는 Anchor 하나에 서고, 그 Anchor 기준 위치·회전과 적재 방식(mount)을 갖는다.
|
|
6
6
|
*
|
|
7
|
+
* 맨 위 그림은 Anchor 면(호스트가 크기를 주면)과 그 위의 자리, 실린 아이템을 Anchor 기준으로 보인다. 자리를 끌면 Tier 위치가,
|
|
8
|
+
* 끝 자리의 점을 끌면 간격이 바뀐다. 끄는 동안은 그림만 바뀌고, 놓을 때 한 번 편집이 된다. Esc 는 끌기 전으로 돌린다.
|
|
9
|
+
*
|
|
7
10
|
* 컴포넌트 상태를 직접 바꾸지 않는다. 호스트에 두 가지만 알린다:
|
|
8
|
-
* change — 적용한 레이아웃 (this.value)
|
|
11
|
+
* change — 적용한 레이아웃 (this.value). live 호스트에는 맞는 편집마다, 아니면 [적용]으로
|
|
9
12
|
* slot-layout-use-default — 개별 설정을 지우고 기본값을 쓰겠다는 의도 (값을 비우는 편집과 다르다)
|
|
13
|
+
* 맞지 않는 초안은 알리지 않는다: 칸 옆에 문제를 보이고, 장면에는 마지막으로 알린 값이 남는다.
|
|
10
14
|
* 기본값을 쓰게 된 뒤 보일 값은 호스트가 options.effective 로 다시 준다.
|
|
11
15
|
*/
|
|
12
16
|
import { __decorate } from "tslib";
|
|
13
17
|
import { css, html, nothing, LitElement, svg } from 'lit';
|
|
14
18
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
15
19
|
import { i18next, localize } from '@operato/i18n';
|
|
16
|
-
import { persistenceIssues, slotSpaceWarnings, validateSlotLayout } from '@hatiolab/things-scene';
|
|
17
|
-
import { addCustomSlot, addressImpact, applyPreset, copyTier,
|
|
18
|
-
import { issueText as defaultIssueText, spaceWarningText, t } from './issue-text.js';
|
|
20
|
+
import { persistenceIssues, rotatedFootprint, slotSpaceWarnings, validateSlotLayout } from '@hatiolab/things-scene';
|
|
21
|
+
import { addCustomSlot, addressImpact, anchorFaceSlots, applyPreset, copyTier, fitTierToFace, heldItemStatuses, pitchToPoint, removeCustomSlot, removeTier, setTierPattern, updatePattern, updateSlotSpec, updateTier } from './draft.js';
|
|
22
|
+
import { fitFaceText, fitText, issueField, issueMessage, issueText as defaultIssueText, ISSUE_TEXT, spaceWarningText, t } from './issue-text.js';
|
|
19
23
|
const num = (e) => {
|
|
20
24
|
const n = e.target.valueAsNumber;
|
|
21
25
|
return Number.isFinite(n) ? n : NaN;
|
|
22
26
|
};
|
|
27
|
+
const shown = (v) => (v === undefined || v === null || Number.isNaN(v) ? '' : String(v));
|
|
28
|
+
const snap = (v) => Math.round(v / 10) * 10;
|
|
29
|
+
const ERROR = '#c62828';
|
|
30
|
+
const SLOT = '#1976d2';
|
|
31
|
+
/** 칸 이름 (칸 옆에 오지 못한 문제를 목록에 적을 때) */
|
|
32
|
+
const FIELD_LABEL = {
|
|
33
|
+
anchor: ['face', 'Face'],
|
|
34
|
+
mount: ['mount', 'Mount'],
|
|
35
|
+
'position-x': ['position-x', 'Position X'],
|
|
36
|
+
'position-y': ['position-y', 'Position Y (height)'],
|
|
37
|
+
'position-z': ['position-z', 'Position Z'],
|
|
38
|
+
'tier-rotation': ['tier-rotation', 'Tier rotation (deg)'],
|
|
39
|
+
pattern: ['pattern', 'Arrangement'],
|
|
40
|
+
count: ['count', 'Slots'],
|
|
41
|
+
pitch: ['pitch', 'Pitch'],
|
|
42
|
+
'pitch-x': ['pitch-x', 'Pitch X'],
|
|
43
|
+
'pitch-z': ['pitch-z', 'Pitch Z'],
|
|
44
|
+
axis: ['axis', 'Direction'],
|
|
45
|
+
origin: ['origin', 'Slot 1 at'],
|
|
46
|
+
'origin-x': ['origin-x', 'Slot 1 at X'],
|
|
47
|
+
'origin-z': ['origin-z', 'Slot 1 at Z'],
|
|
48
|
+
rows: ['rows', 'Rows (Z)'],
|
|
49
|
+
cols: ['cols', 'Columns (X)'],
|
|
50
|
+
order: ['order', 'Numbering'],
|
|
51
|
+
'allowed-width': ['allowed-width', 'Width (X)'],
|
|
52
|
+
'allowed-depth': ['allowed-depth', 'Depth (Z)'],
|
|
53
|
+
'allowed-height': ['allowed-height', 'Height'],
|
|
54
|
+
'stack-limit': ['stack-limit', 'Stack limit'],
|
|
55
|
+
rotation: ['rotation', 'Slot rotation (deg)'],
|
|
56
|
+
accepts: ['accepts', 'Accepts']
|
|
57
|
+
};
|
|
58
|
+
const fieldLabel = (field) => (FIELD_LABEL[field] ? t(FIELD_LABEL[field][0], FIELD_LABEL[field][1]) : field);
|
|
23
59
|
let OxSlotLayoutEditor = class OxSlotLayoutEditor extends localize(i18next)(LitElement) {
|
|
24
60
|
constructor() {
|
|
25
61
|
super(...arguments);
|
|
@@ -29,6 +65,46 @@ let OxSlotLayoutEditor = class OxSlotLayoutEditor extends localize(i18next)(LitE
|
|
|
29
65
|
this.presetKey = '';
|
|
30
66
|
this.presetSize = {};
|
|
31
67
|
this.presetParams = {};
|
|
68
|
+
this._onDragKey = (e) => {
|
|
69
|
+
if (e.key !== 'Escape' || !this._drag)
|
|
70
|
+
return;
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
e.stopPropagation();
|
|
73
|
+
this._cancelDrag();
|
|
74
|
+
};
|
|
75
|
+
this._onDragMove = (e) => {
|
|
76
|
+
const d = this._drag;
|
|
77
|
+
const p = d && this._point(e);
|
|
78
|
+
if (!d || !p)
|
|
79
|
+
return;
|
|
80
|
+
const tier = d.before.tiers.find(x => x.tierNo === d.tierNo);
|
|
81
|
+
if (d.kind === 'move') {
|
|
82
|
+
const position = { ...tier.position, x: snap(tier.position.x + p.x - d.from.x), z: snap(tier.position.z + p.z - d.from.z) };
|
|
83
|
+
this.draft = updateTier(d.before, d.tierNo, { position });
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const pitch = pitchToPoint(d.before, d.tierNo, p);
|
|
87
|
+
if (pitch !== null)
|
|
88
|
+
this.draft = updatePattern(d.before, d.tierNo, { pitch: Math.max(10, snap(pitch)) });
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
/** 놓으면 한 번의 편집. 움직이지 않았으면 아무 일도 없다 */
|
|
92
|
+
this._onDragEnd = () => {
|
|
93
|
+
const d = this._endDrag();
|
|
94
|
+
if (!d)
|
|
95
|
+
return;
|
|
96
|
+
if (JSON.stringify(this.draft) === JSON.stringify(d.before)) {
|
|
97
|
+
this.draft = d.before;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this._edit(this.draft);
|
|
101
|
+
};
|
|
102
|
+
this._cancelDrag = () => {
|
|
103
|
+
const d = this._endDrag();
|
|
104
|
+
if (!d)
|
|
105
|
+
return;
|
|
106
|
+
this.draft = d.before;
|
|
107
|
+
};
|
|
32
108
|
}
|
|
33
109
|
willUpdate(changed) {
|
|
34
110
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
@@ -44,16 +120,19 @@ let OxSlotLayoutEditor = class OxSlotLayoutEditor extends localize(i18next)(LitE
|
|
|
44
120
|
if (JSON.stringify((_h = before === null || before === void 0 ? void 0 : before.presetSize) !== null && _h !== void 0 ? _h : {}) !== JSON.stringify((_k = (_j = this.options) === null || _j === void 0 ? void 0 : _j.presetSize) !== null && _k !== void 0 ? _k : {}))
|
|
45
121
|
this.presetSize = { ...((_m = (_l = this.options) === null || _l === void 0 ? void 0 : _l.presetSize) !== null && _m !== void 0 ? _m : {}) };
|
|
46
122
|
}
|
|
47
|
-
/** 편집 중인 값을 버리고 개별 설정(없으면 호스트 기본값)에서 다시
|
|
123
|
+
/** 편집 중인 값을 버리고 개별 설정(없으면 호스트 기본값)에서 다시 시작한다. 고른 Tier 는 남아 있으면 그대로 */
|
|
48
124
|
reset() {
|
|
49
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
125
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
50
126
|
const start = (_a = this.value) !== null && _a !== void 0 ? _a : (_b = this.options) === null || _b === void 0 ? void 0 : _b.effective;
|
|
51
127
|
this.draft = start ? structuredClone(start) : undefined;
|
|
52
|
-
|
|
128
|
+
if (!((_d = (_c = this.draft) === null || _c === void 0 ? void 0 : _c.tiers) === null || _d === void 0 ? void 0 : _d.some(x => (x === null || x === void 0 ? void 0 : x.tierNo) === this.tier)))
|
|
129
|
+
this.tier = (_h = (_g = (_f = (_e = this.draft) === null || _e === void 0 ? void 0 : _e.tiers) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.tierNo) !== null && _h !== void 0 ? _h : 1;
|
|
53
130
|
this.dirty = false;
|
|
54
|
-
this.
|
|
55
|
-
|
|
56
|
-
|
|
131
|
+
this.fitNote = undefined;
|
|
132
|
+
this._endDrag();
|
|
133
|
+
this.presetSize = { ...((_k = (_j = this.options) === null || _j === void 0 ? void 0 : _j.presetSize) !== null && _k !== void 0 ? _k : {}) };
|
|
134
|
+
if (!((_m = (_l = this.options) === null || _l === void 0 ? void 0 : _l.presets) === null || _m === void 0 ? void 0 : _m.some(p => p.key === this.presetKey)))
|
|
135
|
+
this.presetKey = (_r = (_q = (_p = (_o = this.options) === null || _o === void 0 ? void 0 : _o.presets) === null || _p === void 0 ? void 0 : _p[0]) === null || _q === void 0 ? void 0 : _q.key) !== null && _r !== void 0 ? _r : '';
|
|
57
136
|
}
|
|
58
137
|
get issues() {
|
|
59
138
|
var _a, _b, _c, _d, _e;
|
|
@@ -61,58 +140,95 @@ let OxSlotLayoutEditor = class OxSlotLayoutEditor extends localize(i18next)(LitE
|
|
|
61
140
|
return [];
|
|
62
141
|
const anchors = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.anchors) === null || _b === void 0 ? void 0 : _b.map(a => a.id);
|
|
63
142
|
const invalid = [...validateSlotLayout(this.draft, { anchors }), ...((_e = (_d = (_c = this.options) === null || _c === void 0 ? void 0 : _c.validate) === null || _d === void 0 ? void 0 : _d.call(_c, this.draft)) !== null && _e !== void 0 ? _e : [])];
|
|
143
|
+
// 호스트 검증이 같은 문제를 다시 내도 한 번만 적는다
|
|
144
|
+
const seen = new Set();
|
|
145
|
+
const unique = invalid.filter(i => !seen.has(`${i.code}@${i.path}`) && !!seen.add(`${i.code}@${i.path}`));
|
|
64
146
|
// 저장 불가 검사는 검증을 통과한 값에만 보인다 (같은 문제를 두 번 적지 않는다)
|
|
65
|
-
return
|
|
147
|
+
return unique.length ? unique : persistenceIssues(this.draft);
|
|
66
148
|
}
|
|
67
|
-
/** 자리끼리 겹치거나 면 밖으로 나가는
|
|
149
|
+
/** 자리끼리 겹치거나 면 밖으로 나가는 설정, 면 크기를 몰라 확인하지 못한 Anchor. 적용을 막지 않는다 */
|
|
68
150
|
get warnings() {
|
|
69
151
|
var _a;
|
|
70
152
|
if (!this.draft || this.issues.length)
|
|
71
153
|
return [];
|
|
72
|
-
|
|
73
|
-
return slotSpaceWarnings(this.draft, { anchorFaces: faces }).filter(w => faces || w.code !== 'ANCHOR_SPACE_UNKNOWN');
|
|
154
|
+
return slotSpaceWarnings(this.draft, { anchorFaces: (_a = this.options) === null || _a === void 0 ? void 0 : _a.anchorFaces });
|
|
74
155
|
}
|
|
75
156
|
/** 적용하면 뜻이 바뀌는 주소 (지금 적용된 값 기준) */
|
|
76
157
|
get impact() {
|
|
77
158
|
var _a, _b;
|
|
78
159
|
return this.dirty && !this.issues.length ? addressImpact((_a = this.value) !== null && _a !== void 0 ? _a : (_b = this.options) === null || _b === void 0 ? void 0 : _b.effective, this.draft) : { moved: [], gone: [] };
|
|
79
160
|
}
|
|
161
|
+
_message(issue) {
|
|
162
|
+
var _a, _b, _c;
|
|
163
|
+
// 편집기가 아는 코드는 칸 옆에 위치 없이 보인다. 호스트 고유 코드는 호스트 문구
|
|
164
|
+
if (issue.code in ISSUE_TEXT)
|
|
165
|
+
return issueMessage(issue);
|
|
166
|
+
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 : issueMessage(issue);
|
|
167
|
+
}
|
|
80
168
|
_text(issue) {
|
|
81
169
|
var _a, _b, _c;
|
|
82
170
|
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);
|
|
83
171
|
}
|
|
84
172
|
_edit(next) {
|
|
85
|
-
var _a, _b;
|
|
173
|
+
var _a, _b, _c;
|
|
86
174
|
this.draft = next;
|
|
87
175
|
this.dirty = true;
|
|
176
|
+
this.fitNote = undefined;
|
|
88
177
|
if (!next.tiers.some(x => x.tierNo === this.tier))
|
|
89
178
|
this.tier = (_b = (_a = next.tiers[next.tiers.length - 1]) === null || _a === void 0 ? void 0 : _a.tierNo) !== null && _b !== void 0 ? _b : 1;
|
|
179
|
+
if (((_c = this.options) === null || _c === void 0 ? void 0 : _c.live) && !this.issues.length)
|
|
180
|
+
this._apply();
|
|
90
181
|
}
|
|
91
182
|
_apply() {
|
|
92
183
|
this.value = structuredClone(this.draft);
|
|
184
|
+
this.dirty = false;
|
|
93
185
|
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
|
94
186
|
}
|
|
95
187
|
_useDefault() {
|
|
96
188
|
this.dispatchEvent(new CustomEvent('slot-layout-use-default', { bubbles: true, composed: true }));
|
|
97
189
|
}
|
|
98
190
|
render() {
|
|
99
|
-
var _a, _b, _c
|
|
191
|
+
var _a, _b, _c;
|
|
100
192
|
const issues = this.issues;
|
|
101
|
-
const
|
|
193
|
+
const live = !!((_a = this.options) === null || _a === void 0 ? void 0 : _a.live);
|
|
194
|
+
const canUseDefault = (_c = (_b = this.options) === null || _b === void 0 ? void 0 : _b.canUseDefault) !== null && _c !== void 0 ? _c : this.value !== undefined;
|
|
102
195
|
return html `
|
|
103
|
-
${(
|
|
104
|
-
? html `<div class="notes">
|
|
105
|
-
${this.options.notes.map(n => html `<div class=${n.level === 'error' ? 'error' : 'note'}>${n.text}</div>`)}
|
|
106
|
-
</div>`
|
|
107
|
-
: nothing}
|
|
108
|
-
${this._renderPreset()} ${this.draft ? this._renderDraft(issues) : nothing} ${this._renderImpact()}
|
|
196
|
+
${this._renderNotes()} ${this.draft ? this._renderDraft(issues) : nothing} ${this._renderPreset()} ${live ? nothing : this._renderImpact()}
|
|
109
197
|
<div class="actions">
|
|
110
198
|
${canUseDefault ? html `<button data-action="use-default" @click=${this._useDefault}>${t('use-default', 'Use default')}</button>` : nothing}
|
|
111
|
-
|
|
112
|
-
|
|
199
|
+
${live
|
|
200
|
+
? nothing
|
|
201
|
+
: html `
|
|
202
|
+
<button data-action="discard" ?disabled=${!this.dirty} @click=${() => this.reset()}>${t('discard', 'Discard')}</button>
|
|
203
|
+
<button data-action="apply" ?disabled=${!this.dirty || !this.draft || issues.length > 0} @click=${this._apply}>${t('apply', 'Apply')}</button>
|
|
204
|
+
`}
|
|
113
205
|
</div>
|
|
114
206
|
`;
|
|
115
207
|
}
|
|
208
|
+
_renderNotes() {
|
|
209
|
+
var _a, _b;
|
|
210
|
+
const notes = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.notes) !== null && _b !== void 0 ? _b : [];
|
|
211
|
+
if (!notes.length)
|
|
212
|
+
return nothing;
|
|
213
|
+
const text = (n) => {
|
|
214
|
+
var _a;
|
|
215
|
+
return (_a = n.text) !== null && _a !== void 0 ? _a : (n.code === 'OVERRIDE_INVALID' && n.issue
|
|
216
|
+
? t('override-invalid', "This placement's layout does not hold and offers no slots: {text}", { text: this._located(n.issue), interpolation: { escapeValue: false } })
|
|
217
|
+
: n.issue
|
|
218
|
+
? this._located(n.issue)
|
|
219
|
+
: '');
|
|
220
|
+
};
|
|
221
|
+
return html `<div class="notes">${notes.map(n => html `<div class=${n.level === 'error' ? 'error' : 'note'}>${text(n)}</div>`)}</div>`;
|
|
222
|
+
}
|
|
223
|
+
/** 문제를 Tier·칸 이름과 함께. 칸을 모르면 경로 그대로 */
|
|
224
|
+
_located(issue) {
|
|
225
|
+
var _a, _b, _c, _d;
|
|
226
|
+
const { tierIndex, field } = issueField(issue);
|
|
227
|
+
const tierNo = tierIndex === undefined ? undefined : (_d = (_c = (_b = (_a = this.draft) === null || _a === void 0 ? void 0 : _a.tiers) === null || _b === void 0 ? void 0 : _b[tierIndex]) === null || _c === void 0 ? void 0 : _c.tierNo) !== null && _d !== void 0 ? _d : tierIndex + 1;
|
|
228
|
+
if (tierNo === undefined || !field)
|
|
229
|
+
return this._text(issue);
|
|
230
|
+
return `${t('tier-n', 'Tier {n}', { n: tierNo })} · ${fieldLabel(field)}: ${this._message(issue)}`;
|
|
231
|
+
}
|
|
116
232
|
_renderPreset() {
|
|
117
233
|
var _a, _b, _c, _d;
|
|
118
234
|
const presets = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.presets) !== null && _b !== void 0 ? _b : [];
|
|
@@ -121,29 +237,29 @@ let OxSlotLayoutEditor = class OxSlotLayoutEditor extends localize(i18next)(LitE
|
|
|
121
237
|
const preset = (_c = presets.find(p => p.key === this.presetKey)) !== null && _c !== void 0 ? _c : presets[0];
|
|
122
238
|
const built = applyPreset(preset, this.presetSize, this.presetParams);
|
|
123
239
|
const numberRow = (label, value, onChange, key) => html `
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
<input type="number" data-preset=${key} .value=${value === undefined || Number.isNaN(value) ? '' : String(value)} @change=${(e) => onChange(num(e))} />
|
|
127
|
-
</div>
|
|
240
|
+
<label class="l">${label}</label>
|
|
241
|
+
<input type="number" data-preset=${key} .value=${shown(value)} @change=${(e) => onChange(num(e))} />
|
|
128
242
|
`;
|
|
129
243
|
return html `
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
244
|
+
<details data-section="preset" ?open=${!this.draft}>
|
|
245
|
+
<summary>${t('preset', 'Start again from a preset')}</summary>
|
|
246
|
+
<section>
|
|
247
|
+
<label class="l">${t('preset-kind', 'Kind')}</label>
|
|
248
|
+
<select data-preset="kind" @change=${(e) => (this.presetKey = e.target.value)}>
|
|
249
|
+
${presets.map(p => html `<option value=${p.key} ?selected=${p.key === preset.key}>${t(`preset.${p.key}`, p.key)}</option>`)}
|
|
250
|
+
</select>
|
|
251
|
+
${numberRow(t('width', 'Width (X)'), this.presetSize.width, n => (this.presetSize = { ...this.presetSize, width: n }), 'width')}
|
|
252
|
+
${numberRow(t('depth', 'Depth (Z)'), this.presetSize.depth, n => (this.presetSize = { ...this.presetSize, depth: n }), 'depth')}
|
|
253
|
+
${numberRow(t('slot-height', 'Slot height'), this.presetSize.slotHeight, n => (this.presetSize = { ...this.presetSize, slotHeight: n }), 'slotHeight')}
|
|
254
|
+
${((_d = preset.params) !== null && _d !== void 0 ? _d : []).map(p => numberRow(t(`preset-param.${p.key}`, p.key), this.presetParams[p.key], n => (this.presetParams = { ...this.presetParams, [p.key]: n }), p.key))}
|
|
255
|
+
<div class="hint">${t('preset-note', 'Pitch and slot size are split evenly as starting values. Confirm them against the real equipment.')}</div>
|
|
256
|
+
</section>
|
|
257
|
+
<div class="actions">
|
|
258
|
+
<button data-action="start-preset" ?disabled=${!built.layout} @click=${() => built.layout && this._startFrom(built.layout)}>
|
|
259
|
+
${t('start-from-preset', 'Start from preset')}
|
|
260
|
+
</button>
|
|
261
|
+
</div>
|
|
262
|
+
</details>
|
|
147
263
|
`;
|
|
148
264
|
}
|
|
149
265
|
/** 적용 전에 뜻이 바뀌는 주소를 보인다. 놓인 Carrier 는 주소로 다시 읽혀 다른 자리에 그려지거나 예외가 된다 */
|
|
@@ -164,174 +280,432 @@ let OxSlotLayoutEditor = class OxSlotLayoutEditor extends localize(i18next)(LitE
|
|
|
164
280
|
this._edit({ ...extra, ...structuredClone(layout) });
|
|
165
281
|
}
|
|
166
282
|
_renderDraft(issues) {
|
|
167
|
-
var _a, _b, _c, _d, _e, _f;
|
|
283
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
168
284
|
const draft = this.draft;
|
|
169
285
|
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];
|
|
170
|
-
const warnings = this.warnings;
|
|
171
|
-
const issueList = issues.length
|
|
172
|
-
? html `<div class="error" data-issues>${issues.map(i => html `<div>· ${this._text(i)}</div>`)}</div>`
|
|
173
|
-
: warnings.length
|
|
174
|
-
? html `<div class="note" data-warnings>${warnings.map(w => html `<div>· ${spaceWarningText(w)}</div>`)}</div>`
|
|
175
|
-
: nothing;
|
|
176
286
|
// 층이 없는 값(잘못된 저장값)은 편집할 층이 없으므로 문제만 보인다. 프리셋으로 다시 시작한다
|
|
177
287
|
if (!tier)
|
|
178
|
-
return
|
|
288
|
+
return issues.length ? html `<div class="error" data-issues>${issues.map(i => html `<div>· ${this._text(i)}</div>`)}</div>` : nothing;
|
|
289
|
+
const index = draft.tiers.indexOf(tier);
|
|
290
|
+
// 고른 Tier 의 문제는 칸 옆에, 나머지(다른 Tier, 칸이 없는 경로)는 목록에
|
|
291
|
+
const errors = new Map();
|
|
292
|
+
const listed = [];
|
|
293
|
+
for (const issue of issues) {
|
|
294
|
+
const { tierIndex, field } = issueField(issue);
|
|
295
|
+
if (tierIndex === index && field)
|
|
296
|
+
errors.set(field, [...((_d = errors.get(field)) !== null && _d !== void 0 ? _d : []), this._message(issue)]);
|
|
297
|
+
else
|
|
298
|
+
listed.push(issue);
|
|
299
|
+
}
|
|
179
300
|
return html `
|
|
180
|
-
|
|
181
|
-
<div class="
|
|
182
|
-
${draft.tiers.map(x => html `<button data-tier=${x.tierNo}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
<button data-action="copy-tier" @click=${() => this._copyTier(draft, tier.tierNo)}>${t('copy-tier', 'Copy tier')}</button>
|
|
301
|
+
${this._renderPicture(draft, tier.tierNo)}
|
|
302
|
+
<div class="tiers">
|
|
303
|
+
${draft.tiers.map(x => html `<button data-tier=${x.tierNo} aria-pressed=${x.tierNo === tier.tierNo ? 'true' : 'false'} @click=${() => (this.tier = x.tierNo)}>
|
|
304
|
+
${t('tier-n', 'Tier {n}', { n: x.tierNo })}
|
|
305
|
+
</button>`)}
|
|
306
|
+
<button class="end" data-action="copy-tier" @click=${() => this._copyTier(draft, tier.tierNo)}>${t('copy-tier', 'Copy tier')}</button>
|
|
186
307
|
<button data-action="remove-tier" ?disabled=${draft.tiers.length <= 1} @click=${() => this._edit(removeTier(draft, tier.tierNo))}>${t('remove-tier', 'Remove tier')}</button>
|
|
187
308
|
</div>
|
|
188
|
-
${
|
|
189
|
-
|
|
190
|
-
|
|
309
|
+
${listed.length || (issues.length && ((_e = this.options) === null || _e === void 0 ? void 0 : _e.live))
|
|
310
|
+
? html `<div class="error" data-issues>
|
|
311
|
+
${listed.map(i => html `<div>· ${this._located(i)}</div>`)}
|
|
312
|
+
${((_f = this.options) === null || _f === void 0 ? void 0 : _f.live) && issues.length ? html `<div class="note" data-held>${t('held-back', 'Not applied until fixed: the scene keeps the last sound layout.')}</div>` : nothing}
|
|
313
|
+
</div>`
|
|
314
|
+
: nothing}
|
|
315
|
+
${this._renderTier(draft, tier.tierNo, errors)} ${(_j = (_h = (_g = this.options) === null || _g === void 0 ? void 0 : _g.renderExtra) === null || _h === void 0 ? void 0 : _h.call(_g, draft, next => this._edit(next))) !== null && _j !== void 0 ? _j : nothing}
|
|
191
316
|
`;
|
|
192
317
|
}
|
|
193
318
|
/** 복사한 Tier 를 골라 둔다. 같은 자리에 겹쳐 생기므로 바로 이어서 배치를 고친다 */
|
|
194
319
|
_copyTier(draft, tierNo) {
|
|
195
320
|
const next = copyTier(draft, tierNo);
|
|
196
|
-
this._edit(next);
|
|
197
321
|
this.tier = next.tiers[next.tiers.length - 1].tierNo;
|
|
322
|
+
this._edit(next);
|
|
323
|
+
}
|
|
324
|
+
// ── 그림 ──────────────────────────────────────────────────────────────────
|
|
325
|
+
_anchorName(id) {
|
|
326
|
+
var _a, _b, _c, _d;
|
|
327
|
+
const a = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.anchors) === null || _b === void 0 ? void 0 : _b.find(x => x.id === id);
|
|
328
|
+
if (a === null || a === void 0 ? void 0 : a.label)
|
|
329
|
+
return a.label;
|
|
330
|
+
if (a === null || a === void 0 ? void 0 : a.part)
|
|
331
|
+
return `${id} · ${t(`face-${(_c = a.face) !== null && _c !== void 0 ? _c : 'top'}`, `{part} ${(_d = a.face) !== null && _d !== void 0 ? _d : 'top'}`, { part: a.part })}`;
|
|
332
|
+
return id;
|
|
333
|
+
}
|
|
334
|
+
_unit() {
|
|
335
|
+
var _a;
|
|
336
|
+
return ((_a = this.options) === null || _a === void 0 ? void 0 : _a.unit) ? html ` <span class="unit">${this.options.unit}</span>` : nothing;
|
|
198
337
|
}
|
|
199
|
-
|
|
338
|
+
/** 고른 Tier 의 Anchor 면 위에서 본 자리 (Anchor 기준). 오른쪽이 +X, 위쪽이 −Z */
|
|
339
|
+
_renderPicture(draft, tierNo) {
|
|
200
340
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
201
341
|
const tier = draft.tiers.find(x => x.tierNo === tierNo);
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
342
|
+
const face = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.anchorFaces) === null || _b === void 0 ? void 0 : _b[tier.anchorId];
|
|
343
|
+
const slots = anchorFaceSlots(draft, tier.anchorId);
|
|
344
|
+
const mine = slots.filter(s => s.tier === tierNo);
|
|
345
|
+
if (!mine.length)
|
|
346
|
+
return html `<div class="note" data-preview="none">${t('preview-none', 'No slots to show with the current values.')}</div>`;
|
|
347
|
+
const boxes = slots.map(s => ({ s, fp: rotatedFootprint({ width: s.width, depth: s.depth, height: 0 }, s.rotation) }));
|
|
348
|
+
const xs = boxes.flatMap(({ s, fp }) => [s.x - fp.width / 2, s.x + fp.width / 2]).concat(face ? [-face.width / 2, face.width / 2] : []);
|
|
349
|
+
const zs = boxes.flatMap(({ s, fp }) => [s.z - fp.depth / 2, s.z + fp.depth / 2]).concat(face ? [-face.depth / 2, face.depth / 2] : []);
|
|
350
|
+
const [minX, maxX, minZ, maxZ] = [Math.min(...xs), Math.max(...xs), Math.min(...zs), Math.max(...zs)];
|
|
351
|
+
const size = Math.max(maxX - minX, maxZ - minZ, 1);
|
|
352
|
+
const pad = size * 0.06;
|
|
353
|
+
const viewBox = (_d = (_c = this._drag) === null || _c === void 0 ? void 0 : _c.viewBox) !== null && _d !== void 0 ? _d : `${minX - pad} ${minZ - pad} ${maxX - minX + pad * 2} ${maxZ - minZ + pad * 2}`;
|
|
354
|
+
const font = size * 0.045;
|
|
355
|
+
const stroke = size * 0.004;
|
|
356
|
+
const flagged = new Map();
|
|
357
|
+
for (const w of this.warnings) {
|
|
358
|
+
const id = `t${(_e = w.params) === null || _e === void 0 ? void 0 : _e.tier}_s${(_f = w.params) === null || _f === void 0 ? void 0 : _f.slot}`;
|
|
359
|
+
if (w.code === 'SLOT_OUTSIDE_ANCHOR')
|
|
360
|
+
flagged.set(id, 'out');
|
|
361
|
+
else if (w.code === 'SLOT_OVERLAP' && !flagged.has(id))
|
|
362
|
+
flagged.set(id, 'overlap');
|
|
363
|
+
}
|
|
364
|
+
const items = (_h = (_g = this.options) === null || _g === void 0 ? void 0 : _g.items) !== null && _h !== void 0 ? _h : [];
|
|
365
|
+
const onSlot = (id) => items.filter(i => i.slot === id).map(i => i.id);
|
|
366
|
+
const tl = tier.layout;
|
|
367
|
+
const last = tl.pattern === 'ROW' && tl.count >= 2 ? mine.find(s => s.slotNo === tl.count) : undefined;
|
|
368
|
+
/* 간격 손잡이: 끝 자리 안, 줄과 직각으로 가장자리 쪽에 둔다(글자를 가리지 않게). 간격은 줄 방향 거리로만 정해진다 */
|
|
369
|
+
const handle = last && (() => {
|
|
370
|
+
const first = mine.find(s => s.slotNo === 1);
|
|
371
|
+
const length = Math.hypot(last.x - first.x, last.z - first.z) || 1;
|
|
372
|
+
const across = { x: -(last.z - first.z) / length, z: (last.x - first.x) / length };
|
|
373
|
+
const room = Math.min(last.width, last.depth) / 2 - font;
|
|
374
|
+
return { x: last.x + across.x * room, z: last.z + across.z * room };
|
|
375
|
+
})();
|
|
217
376
|
return html `
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
377
|
+
<svg
|
|
378
|
+
data-preview
|
|
379
|
+
viewBox=${viewBox}
|
|
380
|
+
preserveAspectRatio="xMidYMid meet"
|
|
381
|
+
tabindex="0"
|
|
382
|
+
@pointermove=${this._onDragMove}
|
|
383
|
+
@pointerup=${this._onDragEnd}
|
|
384
|
+
@pointercancel=${this._cancelDrag}
|
|
385
|
+
>
|
|
386
|
+
${face
|
|
387
|
+
? svg `<rect data-face x=${-face.width / 2} y=${-face.depth / 2} width=${face.width} height=${face.depth}
|
|
388
|
+
fill="rgba(0,0,0,0.04)" stroke="rgba(0,0,0,0.45)" stroke-width=${stroke} />`
|
|
226
389
|
: nothing}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
390
|
+
${slots
|
|
391
|
+
.filter(s => s.tier !== tierNo)
|
|
392
|
+
.map(s => svg `<g transform="translate(${s.x} ${s.z}) rotate(${-s.rotation})">
|
|
393
|
+
<rect data-other-slot=${s.id} x=${-s.width / 2} y=${-s.depth / 2} width=${s.width} height=${s.depth}
|
|
394
|
+
fill="none" stroke="rgba(0,0,0,0.35)" stroke-dasharray="${stroke * 4} ${stroke * 3}" stroke-width=${stroke} />
|
|
395
|
+
</g>`)}
|
|
396
|
+
${mine.map(s => {
|
|
397
|
+
const flag = flagged.get(s.id);
|
|
398
|
+
const colour = flag ? ERROR : SLOT;
|
|
399
|
+
return svg `<g transform="translate(${s.x} ${s.z}) rotate(${-s.rotation})">
|
|
400
|
+
<rect data-slot=${s.slotNo} data-slot-id=${s.id} data-flag=${flag !== null && flag !== void 0 ? flag : ''} x=${-s.width / 2} y=${-s.depth / 2} width=${s.width} height=${s.depth}
|
|
401
|
+
fill=${flag ? 'rgba(198,40,40,0.1)' : 'rgba(25,118,210,0.1)'} stroke=${colour} stroke-width=${stroke}
|
|
402
|
+
stroke-dasharray=${flag ? `${stroke * 4} ${stroke * 3}` : 'none'}
|
|
403
|
+
@pointerdown=${(e) => this._startDrag(e, 'move', tierNo)} />
|
|
404
|
+
</g>`;
|
|
405
|
+
})}
|
|
406
|
+
${mine.map(s => {
|
|
407
|
+
const held = onSlot(s.id);
|
|
408
|
+
return svg `<text x=${s.x} y=${s.z - (held.length ? font * 0.6 : 0)} font-size=${font} text-anchor="middle" dominant-baseline="central"
|
|
409
|
+
fill=${flagged.has(s.id) ? '#791f1f' : '#0d47a1'} pointer-events="none">${s.id}</text>
|
|
410
|
+
${held.length
|
|
411
|
+
? svg `<text data-held-on=${s.id} x=${s.x} y=${s.z + font * 0.7} font-size=${font * 0.85} text-anchor="middle" dominant-baseline="central"
|
|
412
|
+
fill="rgba(0,0,0,0.6)" pointer-events="none">${held.length > 2 ? `${held.slice(0, 2).join(', ')} +${held.length - 2}` : held.join(', ')}</text>`
|
|
413
|
+
: nothing}`;
|
|
414
|
+
})}
|
|
415
|
+
${handle
|
|
416
|
+
? svg `<circle data-handle="pitch" cx=${handle.x} cy=${handle.z} r=${font * 0.45} fill=${SLOT} stroke="#fff" stroke-width=${stroke}
|
|
417
|
+
@pointerdown=${(e) => this._startDrag(e, 'pitch', tierNo)} />`
|
|
236
418
|
: nothing}
|
|
237
|
-
|
|
419
|
+
</svg>
|
|
420
|
+
<div class="caption">
|
|
421
|
+
<span data-face-caption>
|
|
422
|
+
${face
|
|
423
|
+
? html `${this._anchorName(tier.anchorId)} · ${face.width} × ${face.depth}${this._unit()}`
|
|
424
|
+
: html `<span class="error">${t('face-unknown', 'Face size unknown: whether slots stay on it is not checked')}</span>`}
|
|
425
|
+
</span>
|
|
426
|
+
<span>${t('preview-axes', 'Right = +X, up = −Z')}</span>
|
|
427
|
+
</div>
|
|
428
|
+
${this._renderSpaceWarnings(tier.anchorId, !!face)} ${this._renderItems(draft)}
|
|
238
429
|
`;
|
|
239
430
|
}
|
|
240
|
-
/**
|
|
241
|
-
|
|
242
|
-
var _a
|
|
431
|
+
/** 면 밖·겹침 경고. 고른 Tier 의 Anchor 면을 모른다는 것은 그림 아래에 이미 적었다 */
|
|
432
|
+
_renderSpaceWarnings(anchorId, faceKnown) {
|
|
433
|
+
const warnings = this.warnings.filter(w => { var _a; return !(w.code === 'ANCHOR_SPACE_UNKNOWN' && !faceKnown && ((_a = w.params) === null || _a === void 0 ? void 0 : _a.anchorId) === anchorId); });
|
|
434
|
+
if (!warnings.length)
|
|
435
|
+
return nothing;
|
|
436
|
+
return html `<div class="error" data-warnings>${warnings.map(w => html `<div>· ${spaceWarningText(w)}</div>`)}</div>`;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* 실린 아이템이 편집 중인 레이아웃에서 어떻게 되는지. 자리가 없어지면 미배치로 남고(지우거나 옮기지 않는다), 받는 타입·규격은
|
|
440
|
+
* 공통 판정으로 본다. 치수를 모르는 아이템은 판정하지 않았다고 적는다 — 모양을 자리에 맞춰 그리는 것은 판정이 아니다.
|
|
441
|
+
*/
|
|
442
|
+
_renderItems(draft) {
|
|
443
|
+
var _a, _b;
|
|
444
|
+
const items = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.items) !== null && _b !== void 0 ? _b : [];
|
|
445
|
+
if (!items.length)
|
|
446
|
+
return nothing;
|
|
447
|
+
const statuses = heldItemStatuses(draft, items);
|
|
448
|
+
const shownItems = statuses.slice(0, 12);
|
|
449
|
+
return html `<div class="items" data-items>
|
|
450
|
+
${shownItems.map(s => {
|
|
451
|
+
var _a;
|
|
452
|
+
const line = [];
|
|
453
|
+
if (!s.placed)
|
|
454
|
+
line.push(html `<span class="bad">${fitText('SLOT_NOT_FOUND')}</span>`);
|
|
455
|
+
else {
|
|
456
|
+
if (s.unregistered)
|
|
457
|
+
line.push(html `<span class="warn">${t('unregistered-type', 'Type not registered')}</span>`);
|
|
458
|
+
for (const r of s.reasons)
|
|
459
|
+
line.push(html `<span class=${r === 'SPEC_UNKNOWN' ? 'note' : 'bad'}>${fitText(r)}</span>`);
|
|
460
|
+
if (s.verdict === 'OK')
|
|
461
|
+
line.push(html `<span class="note">OK</span>`);
|
|
462
|
+
}
|
|
463
|
+
return html `<div data-item=${s.item.id} data-verdict=${s.placed ? s.verdict : 'UNPLACED'}>
|
|
464
|
+
<b>${s.item.id}</b> · ${(_a = s.item.slot) !== null && _a !== void 0 ? _a : '—'}${s.item.code ? html ` · ${s.item.code}` : nothing} — ${line.map((x, i) => html `${i ? ', ' : ''}${x}`)}
|
|
465
|
+
</div>`;
|
|
466
|
+
})}
|
|
467
|
+
${statuses.length > shownItems.length ? html `<div class="note">… (${statuses.length})</div>` : nothing}
|
|
468
|
+
</div>`;
|
|
469
|
+
}
|
|
470
|
+
// ── 끌기 ──────────────────────────────────────────────────────────────────
|
|
471
|
+
_point(e) {
|
|
472
|
+
const svgEl = this.renderRoot.querySelector('svg[data-preview]');
|
|
473
|
+
const m = svgEl === null || svgEl === void 0 ? void 0 : svgEl.getScreenCTM();
|
|
474
|
+
if (!m)
|
|
475
|
+
return null;
|
|
476
|
+
const p = new DOMPoint(e.clientX, e.clientY).matrixTransform(m.inverse());
|
|
477
|
+
return { x: p.x, z: p.y };
|
|
478
|
+
}
|
|
479
|
+
_startDrag(e, kind, tierNo) {
|
|
480
|
+
var _a;
|
|
481
|
+
const from = this._point(e);
|
|
482
|
+
const svgEl = this.renderRoot.querySelector('svg[data-preview]');
|
|
483
|
+
if (!from || !svgEl || !this.draft)
|
|
484
|
+
return;
|
|
485
|
+
e.preventDefault();
|
|
486
|
+
e.stopPropagation();
|
|
487
|
+
try {
|
|
488
|
+
svgEl.setPointerCapture(e.pointerId);
|
|
489
|
+
}
|
|
490
|
+
catch (_b) {
|
|
491
|
+
// 이미 놓인 포인터: 끌기는 svg 안에서만 이어진다
|
|
492
|
+
}
|
|
493
|
+
this._drag = { kind, tierNo, from, before: structuredClone(this.draft), viewBox: (_a = svgEl.getAttribute('viewBox')) !== null && _a !== void 0 ? _a : '' };
|
|
494
|
+
// 끄는 동안의 Esc 는 포커스가 어디 있든 끌기를 거둔다
|
|
495
|
+
window.addEventListener('keydown', this._onDragKey, true);
|
|
496
|
+
}
|
|
497
|
+
_endDrag() {
|
|
498
|
+
const d = this._drag;
|
|
499
|
+
this._drag = undefined;
|
|
500
|
+
window.removeEventListener('keydown', this._onDragKey, true);
|
|
501
|
+
return d;
|
|
502
|
+
}
|
|
503
|
+
disconnectedCallback() {
|
|
504
|
+
super.disconnectedCallback();
|
|
505
|
+
this._endDrag();
|
|
506
|
+
}
|
|
507
|
+
// ── 칸 ────────────────────────────────────────────────────────────────────
|
|
508
|
+
_row(field, label, control, errors) {
|
|
509
|
+
const messages = errors.get(field);
|
|
510
|
+
return html `
|
|
511
|
+
<label class="l">${label}</label>
|
|
512
|
+
${control} ${messages ? html `<div class="err" data-error=${field}>${messages.join(' · ')}</div>` : nothing}
|
|
513
|
+
`;
|
|
514
|
+
}
|
|
515
|
+
_num(field, value, onChange, errors, extra = {}) {
|
|
516
|
+
var _a, _b;
|
|
517
|
+
return html `<input
|
|
518
|
+
type="number"
|
|
519
|
+
data-field=${field}
|
|
520
|
+
?invalid=${errors.has(field)}
|
|
521
|
+
min=${(_a = extra.min) !== null && _a !== void 0 ? _a : nothing}
|
|
522
|
+
step=${(_b = extra.step) !== null && _b !== void 0 ? _b : nothing}
|
|
523
|
+
.value=${shown(value)}
|
|
524
|
+
@change=${(e) => onChange(num(e))}
|
|
525
|
+
/>`;
|
|
526
|
+
}
|
|
527
|
+
_seg(field, value, options, onChange, errors) {
|
|
528
|
+
return html `<div class="seg" role="radiogroup" data-field=${field} ?invalid=${errors.has(field)}>
|
|
529
|
+
${options.map(([v, label]) => html `<button type="button" role="radio" data-value=${v} aria-checked=${v === value ? 'true' : 'false'} title=${label} @click=${() => v !== value && onChange(v)}>
|
|
530
|
+
${label}
|
|
531
|
+
</button>`)}
|
|
532
|
+
</div>`;
|
|
533
|
+
}
|
|
534
|
+
_renderTier(draft, tierNo, errors) {
|
|
535
|
+
var _a, _b, _c, _d, _e;
|
|
243
536
|
const tier = draft.tiers.find(x => x.tierNo === tierNo);
|
|
244
537
|
const anchors = (_a = this.options) === null || _a === void 0 ? void 0 : _a.anchors;
|
|
245
|
-
const
|
|
538
|
+
const editTier = (patch) => this._edit(updateTier(draft, tierNo, patch));
|
|
246
539
|
const position = (_b = tier.position) !== null && _b !== void 0 ? _b : { x: 0, y: 0, z: 0 };
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
<
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
540
|
+
const fine = ['position-x', 'position-y', 'position-z', 'tier-rotation'].some(f => errors.has(f));
|
|
541
|
+
const u = this._unit();
|
|
542
|
+
return html `
|
|
543
|
+
<section data-section="where">
|
|
544
|
+
<h4>${t('where', 'Where')}</h4>
|
|
545
|
+
${this._row('anchor', t('face', 'Face'), (anchors === null || anchors === void 0 ? void 0 : anchors.length)
|
|
546
|
+
? html `<select data-field="anchor" ?invalid=${errors.has('anchor')} @change=${(e) => editTier({ anchorId: e.target.value })}>
|
|
547
|
+
${anchors.some(a => a.id === tier.anchorId) ? nothing : html `<option value=${(_c = tier.anchorId) !== null && _c !== void 0 ? _c : ''} selected>${(_d = tier.anchorId) !== null && _d !== void 0 ? _d : ''}</option>`}
|
|
548
|
+
${anchors.map(a => html `<option value=${a.id} ?selected=${a.id === tier.anchorId}>${this._anchorName(a.id)}</option>`)}
|
|
549
|
+
</select>`
|
|
550
|
+
: html `<input data-field="anchor" ?invalid=${errors.has('anchor')} .value=${(_e = tier.anchorId) !== null && _e !== void 0 ? _e : ''} @change=${(e) => editTier({ anchorId: e.target.value.trim() })} />`, errors)}
|
|
551
|
+
${this._row('mount', t('mount', 'Mount'), this._seg('mount', tier.mount, [
|
|
552
|
+
['ON', t('mount.ON', 'On')],
|
|
553
|
+
['IN', t('mount.IN', 'In')],
|
|
554
|
+
['HANG', t('mount.HANG', 'Hang')]
|
|
555
|
+
], mount => editTier({ mount }), errors), errors)}
|
|
556
|
+
</section>
|
|
557
|
+
${this._renderArrangement(draft, tierNo, errors)} ${this._renderSlot(draft, tierNo, errors)}
|
|
558
|
+
<details data-section="fine" ?open=${fine}>
|
|
559
|
+
<summary>${t('fine', 'Position and rotation')}</summary>
|
|
560
|
+
<section>
|
|
561
|
+
${this._row('position-x', html `${t('position-x', 'Position X')}${u}`, this._num('position-x', position.x, n => editTier({ position: { ...position, x: n } }), errors), errors)}
|
|
562
|
+
${this._row('position-y', html `${t('position-y', 'Position Y (height)')}${u}`, this._num('position-y', position.y, n => editTier({ position: { ...position, y: n } }), errors), errors)}
|
|
563
|
+
${this._row('position-z', html `${t('position-z', 'Position Z')}${u}`, this._num('position-z', position.z, n => editTier({ position: { ...position, z: n } }), errors), errors)}
|
|
564
|
+
${this._row('tier-rotation', t('tier-rotation', 'Tier rotation (deg)'), this._num('tier-rotation', tier.rotation, n => editTier({ rotation: n }), errors), errors)}
|
|
565
|
+
</section>
|
|
566
|
+
</details>
|
|
254
567
|
`;
|
|
568
|
+
}
|
|
569
|
+
/** 자리를 어떻게 늘어놓는가: 패턴, 수, 간격, 방향, 1번 자리. 그리고 [면에 맞춤] */
|
|
570
|
+
_renderArrangement(draft, tierNo, errors) {
|
|
571
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
572
|
+
const tier = draft.tiers.find(x => x.tierNo === tierNo);
|
|
573
|
+
const l = tier.layout;
|
|
574
|
+
const edit = (patch) => this._edit(updatePattern(draft, tierNo, patch));
|
|
575
|
+
const u = this._unit();
|
|
576
|
+
const side = (axis) => axis === 'X'
|
|
577
|
+
? [
|
|
578
|
+
['MIN', t('side.left', 'Left')],
|
|
579
|
+
['MAX', t('side.right', 'Right')]
|
|
580
|
+
]
|
|
581
|
+
: [
|
|
582
|
+
['MIN', t('side.top', 'Top')],
|
|
583
|
+
['MAX', t('side.bottom', 'Bottom')]
|
|
584
|
+
];
|
|
585
|
+
const fit = () => {
|
|
586
|
+
var _a, _b;
|
|
587
|
+
const result = fitTierToFace(draft, tierNo, (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.anchorFaces) === null || _b === void 0 ? void 0 : _b[tier.anchorId]);
|
|
588
|
+
if ('layout' in result)
|
|
589
|
+
this._edit(result.layout);
|
|
590
|
+
else
|
|
591
|
+
this.fitNote = fitFaceText(result.reason);
|
|
255
592
|
};
|
|
593
|
+
const fitButton = html `<button data-action="fit-face" title=${t('fit-face-tip', 'Centre on the face and spread the slots end to end, once')} @click=${fit}>
|
|
594
|
+
${t('fit-face', 'Fit to face')}
|
|
595
|
+
</button>`;
|
|
256
596
|
return html `
|
|
257
|
-
<
|
|
258
|
-
<
|
|
259
|
-
${(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
597
|
+
<section data-section="arrangement">
|
|
598
|
+
<h4>${t('arrangement', 'Arrangement')}</h4>
|
|
599
|
+
${this._row('pattern', t('pattern', 'Arrangement'), this._seg('pattern', l.pattern, [
|
|
600
|
+
['SINGLE', t('pattern.SINGLE', 'One')],
|
|
601
|
+
['ROW', t('pattern.ROW', 'Row')],
|
|
602
|
+
['GRID', t('pattern.GRID', 'Grid')],
|
|
603
|
+
['CUSTOM', t('pattern.CUSTOM', 'Listed')]
|
|
604
|
+
], v => this._edit(setTierPattern(draft, tierNo, v)), errors), errors)}
|
|
605
|
+
${l.pattern === 'ROW'
|
|
606
|
+
? html `
|
|
607
|
+
${this._row('count', t('count', 'Slots'), this._num('count', l.count, n => edit({ count: n }), errors, { min: '1', step: '1' }), errors)}
|
|
608
|
+
${this._row('pitch', html `${t('pitch', 'Pitch')}${u}`, html `<div class="inline">${this._num('pitch', l.pitch, n => edit({ pitch: n }), errors)} ${fitButton}</div>`, errors)}
|
|
609
|
+
${this._row('axis', t('axis', 'Direction'), this._seg('axis', l.axis, [
|
|
610
|
+
['X', t('axis.X', 'Across (X)')],
|
|
611
|
+
['Z', t('axis.Z', 'Down (Z)')]
|
|
612
|
+
], axis => edit({ axis }), errors), errors)}
|
|
613
|
+
${this._row('origin', t('origin', 'Slot 1 at'), this._seg('origin', (_a = l.origin) !== null && _a !== void 0 ? _a : 'MIN', side(l.axis), origin => edit({ origin }), errors), errors)}
|
|
614
|
+
`
|
|
615
|
+
: nothing}
|
|
616
|
+
${l.pattern === 'GRID'
|
|
617
|
+
? html `
|
|
618
|
+
${this._row('rows', t('rows', 'Rows (Z)'), this._num('rows', l.rows, n => edit({ rows: n }), errors, { min: '1', step: '1' }), errors)}
|
|
619
|
+
${this._row('cols', t('cols', 'Columns (X)'), this._num('cols', l.cols, n => edit({ cols: n }), errors, { min: '1', step: '1' }), errors)}
|
|
620
|
+
${this._row('pitch-x', html `${t('pitch-x', 'Pitch X')}${u}`, this._num('pitch-x', (_b = l.pitch) === null || _b === void 0 ? void 0 : _b.x, n => edit({ pitch: { ...l.pitch, x: n } }), errors), errors)}
|
|
621
|
+
${this._row('pitch-z', html `${t('pitch-z', 'Pitch Z')}${u}`, html `<div class="inline">${this._num('pitch-z', (_c = l.pitch) === null || _c === void 0 ? void 0 : _c.z, n => edit({ pitch: { ...l.pitch, z: n } }), errors)} ${fitButton}</div>`, errors)}
|
|
622
|
+
${this._row('order', t('order', 'Numbering'), html `<select data-field="order" @change=${(e) => edit({ order: e.target.value })}>
|
|
623
|
+
${['ROW_MAJOR', 'COL_MAJOR', 'SERPENTINE_ROW', 'SERPENTINE_COL'].map(o => { var _a; return html `<option value=${o} ?selected=${o === ((_a = l.order) !== null && _a !== void 0 ? _a : 'ROW_MAJOR')}>${t(`order.${o}`, o)}</option>`; })}
|
|
624
|
+
</select>`, errors)}
|
|
625
|
+
${this._row('origin-x', t('origin-x', 'Slot 1 at X'), this._seg('origin-x', (_e = (_d = l.origin) === null || _d === void 0 ? void 0 : _d.x) !== null && _e !== void 0 ? _e : 'MIN', side('X'), x => edit({ origin: { z: 'MIN', ...l.origin, x } }), errors), errors)}
|
|
626
|
+
${this._row('origin-z', t('origin-z', 'Slot 1 at Z'), this._seg('origin-z', (_g = (_f = l.origin) === null || _f === void 0 ? void 0 : _f.z) !== null && _g !== void 0 ? _g : 'MIN', side('Z'), z => edit({ origin: { x: 'MIN', ...l.origin, z } }), errors), errors)}
|
|
627
|
+
`
|
|
628
|
+
: nothing}
|
|
629
|
+
${l.pattern === 'SINGLE' ? html `<label class="l"></label><div class="inline">${fitButton}</div>` : nothing}
|
|
630
|
+
${this.fitNote ? html `<div class="err" data-fit-note>${this.fitNote}</div>` : nothing}
|
|
631
|
+
</section>
|
|
632
|
+
${l.pattern === 'CUSTOM' ? this._renderCustom(draft, tierNo) : nothing}
|
|
277
633
|
`;
|
|
278
634
|
}
|
|
279
|
-
/** 받는 타입:
|
|
280
|
-
_acceptsEditor(spec, onChange) {
|
|
281
|
-
var _a;
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
635
|
+
/** 받는 타입: 목록에서 고른 코드(목록에 없는 코드는 그대로 두고 표시), 목록이 없으면 직접 적는다. 필수가 아니면 [모든 타입]도 */
|
|
636
|
+
_acceptsEditor(spec, onChange, errors) {
|
|
637
|
+
var _a, _b, _c;
|
|
638
|
+
const required = !!((_a = this.options) === null || _a === void 0 ? void 0 : _a.acceptsRequired);
|
|
639
|
+
const restricted = required || Array.isArray(spec.accepts);
|
|
640
|
+
const types = (_b = this.options) === null || _b === void 0 ? void 0 : _b.itemTypes;
|
|
641
|
+
const accepts = (_c = spec.accepts) !== null && _c !== void 0 ? _c : [];
|
|
642
|
+
const mode = required
|
|
643
|
+
? nothing
|
|
644
|
+
: this._row('accepts-mode', t('accepts', 'Accepts'), this._seg('accepts-mode', restricted ? 'LIST' : 'ALL', [
|
|
645
|
+
['ALL', t('accepts-all', 'Any type')],
|
|
646
|
+
['LIST', t('accepts-list', 'Listed types only')]
|
|
647
|
+
], v => { var _a; return onChange(v === 'ALL' ? undefined : ((_a = spec.accepts) !== null && _a !== void 0 ? _a : [])); }, errors), errors);
|
|
648
|
+
if (!restricted)
|
|
649
|
+
return mode;
|
|
650
|
+
const label = required ? t('accepts', 'Accepts') : t('accepts-types', 'Types');
|
|
651
|
+
const control = types
|
|
652
|
+
? html `<div class="chips" data-field="accepts" ?invalid=${errors.has('accepts')}>
|
|
653
|
+
${accepts.map(code => {
|
|
654
|
+
const known = types.find(x => x.code === code);
|
|
655
|
+
return html `<span class="chip" data-code=${code} ?unregistered=${!known} title=${known ? code : t('unregistered-type', 'Type not registered')}>
|
|
656
|
+
${(known === null || known === void 0 ? void 0 : known.name) && known.name !== code ? `${known.name} (${code})` : code}${known ? nothing : ' ⚠'}
|
|
657
|
+
<button aria-label=${t('remove', 'Remove')} @click=${() => onChange(accepts.filter(c => c !== code))}>×</button>
|
|
658
|
+
</span>`;
|
|
659
|
+
})}
|
|
660
|
+
<select
|
|
661
|
+
data-field="accepts-add"
|
|
662
|
+
@change=${(e) => {
|
|
663
|
+
const select = e.target;
|
|
664
|
+
const code = select.value;
|
|
665
|
+
select.value = '';
|
|
666
|
+
if (code)
|
|
667
|
+
onChange([...accepts, code]);
|
|
668
|
+
}}
|
|
669
|
+
>
|
|
670
|
+
<option value="">${t('accepts-add', '+ Add type')}</option>
|
|
671
|
+
${types.filter(x => !accepts.includes(x.code)).map(x => html `<option value=${x.code}>${x.name && x.name !== x.code ? `${x.name} (${x.code})` : x.code}</option>`)}
|
|
672
|
+
</select>
|
|
673
|
+
</div>`
|
|
674
|
+
: html `<input
|
|
675
|
+
data-field="accepts"
|
|
676
|
+
?invalid=${errors.has('accepts')}
|
|
677
|
+
.value=${accepts.join(', ')}
|
|
678
|
+
placeholder=${t('accepts-none', 'Empty = accepts nothing')}
|
|
679
|
+
@change=${(e) => onChange(e.target.value
|
|
299
680
|
.split(',')
|
|
300
681
|
.map(s => s.trim())
|
|
301
682
|
.filter(Boolean))}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
: nothing}
|
|
305
|
-
`;
|
|
683
|
+
/>`;
|
|
684
|
+
return html `${mode} ${this._row('accepts', label, control, errors)}`;
|
|
306
685
|
}
|
|
307
|
-
|
|
308
|
-
|
|
686
|
+
/** 자리 하나의 크기·받는 것·쌓기 (이 Tier 의 모든 자리 공통). CUSTOM 은 자리 표에서 */
|
|
687
|
+
_renderSlot(draft, tierNo, errors) {
|
|
688
|
+
var _a, _b, _c;
|
|
689
|
+
const tier = draft.tiers.find(x => x.tierNo === tierNo);
|
|
690
|
+
if (tier.layout.pattern === 'CUSTOM')
|
|
691
|
+
return nothing;
|
|
692
|
+
const spec = tier.layout.slot;
|
|
309
693
|
const edit = (patch) => this._edit(updateSlotSpec(draft, tierNo, patch));
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
return html `
|
|
313
|
-
<div class="row">
|
|
314
|
-
<label>${label}</label>
|
|
315
|
-
<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) } })} />
|
|
316
|
-
</div>
|
|
317
|
-
`;
|
|
318
|
-
};
|
|
694
|
+
const u = this._unit();
|
|
695
|
+
const allowed = (k, field, label) => { var _a; return this._row(field, html `${label}${u}`, this._num(field, (_a = spec.allowed) === null || _a === void 0 ? void 0 : _a[k], n => edit({ allowed: { ...spec.allowed, [k]: n } }), errors), errors); };
|
|
319
696
|
return html `
|
|
320
|
-
<
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
<label>${t('rotation', 'Rotation (deg)')}</label>
|
|
329
|
-
<input type="number" data-field="rotation" .value=${String((_b = spec.rotation) !== null && _b !== void 0 ? _b : 0)} @change=${(e) => edit({ rotation: num(e) || undefined })} />
|
|
330
|
-
</div>
|
|
331
|
-
${this._acceptsEditor(spec, accepts => edit({ accepts }))}
|
|
697
|
+
<section data-section="slot">
|
|
698
|
+
<h4>${t('slot-spec', 'Each slot')}</h4>
|
|
699
|
+
${this._acceptsEditor(spec, accepts => edit({ accepts }), errors)} ${allowed('width', 'allowed-width', t('allowed-width', 'Width (X)'))}
|
|
700
|
+
${allowed('depth', 'allowed-depth', t('allowed-depth', 'Depth (Z)'))} ${allowed('height', 'allowed-height', t('allowed-height', 'Height'))}
|
|
701
|
+
${((_a = this.options) === null || _a === void 0 ? void 0 : _a.itemTypes) ? html `<div class="hint">${t('drawn-at-slot', 'An item that gives no size is drawn at this size.')}</div>` : nothing}
|
|
702
|
+
${this._row('stack-limit', t('stack-limit', 'Stack limit'), this._num('stack-limit', (_b = spec.stackLimit) !== null && _b !== void 0 ? _b : 1, n => edit({ stackLimit: n }), errors, { min: '1', step: '1' }), errors)}
|
|
703
|
+
${this._row('rotation', t('rotation', 'Slot rotation (deg)'), this._num('rotation', (_c = spec.rotation) !== null && _c !== void 0 ? _c : 0, n => edit({ rotation: n || undefined }), errors), errors)}
|
|
704
|
+
</section>
|
|
332
705
|
`;
|
|
333
706
|
}
|
|
334
707
|
_renderCustom(draft, tierNo) {
|
|
708
|
+
var _a, _b, _c;
|
|
335
709
|
const tier = draft.tiers.find(x => x.tierNo === tierNo);
|
|
336
710
|
const slots = [...tier.layout.slots].sort((a, b) => a.slotNo - b.slotNo);
|
|
337
711
|
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>`;
|
|
@@ -366,30 +740,12 @@ let OxSlotLayoutEditor = class OxSlotLayoutEditor extends localize(i18next)(LitE
|
|
|
366
740
|
})}
|
|
367
741
|
</table>
|
|
368
742
|
<div class="actions"><button data-action="add-slot" @click=${() => this._edit(addCustomSlot(draft, tierNo))}>${t('add-slot', 'Add slot')}</button></div>
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
return html `<div class="note" data-preview="none">${t('preview-none', 'No slots to show with the current values.')}</div>`;
|
|
376
|
-
const minX = Math.min(...slots.map(s => s.x - s.width / 2));
|
|
377
|
-
const maxX = Math.max(...slots.map(s => s.x + s.width / 2));
|
|
378
|
-
const minZ = Math.min(...slots.map(s => s.z - s.depth / 2));
|
|
379
|
-
const maxZ = Math.max(...slots.map(s => s.z + s.depth / 2));
|
|
380
|
-
const w = Math.max(maxX - minX, 1);
|
|
381
|
-
const h = Math.max(maxZ - minZ, 1);
|
|
382
|
-
const pad = Math.max(w, h) * 0.08;
|
|
383
|
-
const font = Math.max(w, h) * 0.07;
|
|
384
|
-
return html `
|
|
385
|
-
<svg data-preview viewBox="${minX - pad} ${minZ - pad} ${w + pad * 2} ${h + pad * 2}" preserveAspectRatio="xMidYMid meet">
|
|
386
|
-
${slots.map(s => svg `
|
|
387
|
-
<rect data-slot=${s.slotNo} x=${s.x - s.width / 2} y=${s.z - s.depth / 2} width=${s.width} height=${s.depth}
|
|
388
|
-
fill="rgba(25,118,210,0.08)" stroke="rgba(25,118,210,0.8)" stroke-width=${Math.max(w, h) * 0.006} />
|
|
389
|
-
<text x=${s.x} y=${s.z} font-size=${font} text-anchor="middle" dominant-baseline="central" fill="#0d47a1">${s.slotNo}</text>
|
|
390
|
-
`)}
|
|
391
|
-
</svg>
|
|
392
|
-
<div class="note">${t('preview-axes', 'Right = +X, up = −Z')}</div>
|
|
743
|
+
${this._acceptsEditor({ allowed: (_b = (_a = slots[0]) === null || _a === void 0 ? void 0 : _a.allowed) !== null && _b !== void 0 ? _b : { width: 1, depth: 1, height: 1 }, accepts: (_c = slots[0]) === null || _c === void 0 ? void 0 : _c.accepts }, accepts => {
|
|
744
|
+
let next = draft;
|
|
745
|
+
for (const s of slots)
|
|
746
|
+
next = updateSlotSpec(next, tierNo, { accepts }, s.slotNo);
|
|
747
|
+
this._edit(next);
|
|
748
|
+
}, new Map())}
|
|
393
749
|
`;
|
|
394
750
|
}
|
|
395
751
|
};
|
|
@@ -397,14 +753,42 @@ OxSlotLayoutEditor.styles = css `
|
|
|
397
753
|
:host {
|
|
398
754
|
display: flex;
|
|
399
755
|
flex-direction: column;
|
|
400
|
-
gap:
|
|
756
|
+
gap: 8px;
|
|
757
|
+
min-width: 0;
|
|
401
758
|
font-size: 12px;
|
|
759
|
+
color: var(--property-editor-color, inherit);
|
|
402
760
|
}
|
|
403
|
-
|
|
761
|
+
section {
|
|
404
762
|
display: grid;
|
|
405
|
-
grid-template-columns:
|
|
763
|
+
grid-template-columns: minmax(64px, 34%) minmax(0, 1fr);
|
|
764
|
+
gap: 4px 6px;
|
|
406
765
|
align-items: center;
|
|
407
|
-
|
|
766
|
+
}
|
|
767
|
+
h4,
|
|
768
|
+
summary {
|
|
769
|
+
grid-column: 1 / -1;
|
|
770
|
+
margin: 4px 0 0;
|
|
771
|
+
font-size: 12px;
|
|
772
|
+
font-weight: bold;
|
|
773
|
+
}
|
|
774
|
+
summary {
|
|
775
|
+
cursor: pointer;
|
|
776
|
+
}
|
|
777
|
+
details > section {
|
|
778
|
+
margin-top: 4px;
|
|
779
|
+
}
|
|
780
|
+
label.l {
|
|
781
|
+
text-align: right;
|
|
782
|
+
font: var(--property-editor-label-font, inherit);
|
|
783
|
+
color: var(--property-editor-label-color, inherit);
|
|
784
|
+
}
|
|
785
|
+
.unit {
|
|
786
|
+
opacity: 0.6;
|
|
787
|
+
}
|
|
788
|
+
.err {
|
|
789
|
+
grid-column: 2;
|
|
790
|
+
color: #c62828;
|
|
791
|
+
font-size: 11px;
|
|
408
792
|
}
|
|
409
793
|
.notes {
|
|
410
794
|
padding: 4px 6px;
|
|
@@ -414,63 +798,176 @@ OxSlotLayoutEditor.styles = css `
|
|
|
414
798
|
.error {
|
|
415
799
|
color: #c62828;
|
|
416
800
|
}
|
|
417
|
-
.note
|
|
801
|
+
.note,
|
|
802
|
+
.hint {
|
|
418
803
|
color: rgba(0, 0, 0, 0.55);
|
|
419
804
|
}
|
|
420
|
-
.
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
gap: 4px;
|
|
805
|
+
.hint {
|
|
806
|
+
grid-column: 2;
|
|
807
|
+
font-size: 11px;
|
|
424
808
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
809
|
+
input,
|
|
810
|
+
select {
|
|
811
|
+
box-sizing: border-box;
|
|
812
|
+
width: 100%;
|
|
813
|
+
min-width: 0;
|
|
814
|
+
min-height: var(--property-editor-field-height, 24px);
|
|
815
|
+
padding: var(--property-editor-field-padding, 1px 4px);
|
|
816
|
+
border: var(--property-editor-border, 1px solid rgba(0, 0, 0, 0.2));
|
|
817
|
+
border-radius: var(--property-editor-border-radius, 4px);
|
|
818
|
+
background-color: var(--property-editor-background, field);
|
|
819
|
+
color: inherit;
|
|
820
|
+
font: var(--property-editor-field-font, inherit);
|
|
821
|
+
outline: none;
|
|
822
|
+
}
|
|
823
|
+
input[type='number'] {
|
|
824
|
+
font-variant-numeric: tabular-nums;
|
|
825
|
+
}
|
|
826
|
+
input[invalid],
|
|
827
|
+
select[invalid],
|
|
828
|
+
.seg[invalid],
|
|
829
|
+
.chips[invalid] {
|
|
830
|
+
border-color: #c62828;
|
|
831
|
+
}
|
|
832
|
+
.seg {
|
|
833
|
+
display: flex;
|
|
834
|
+
box-sizing: border-box;
|
|
835
|
+
min-height: var(--property-editor-field-height, 24px);
|
|
836
|
+
border: var(--property-editor-border, 1px solid rgba(0, 0, 0, 0.2));
|
|
837
|
+
border-radius: var(--property-editor-border-radius, 4px);
|
|
838
|
+
background-color: var(--property-editor-background, field);
|
|
839
|
+
overflow: hidden;
|
|
840
|
+
}
|
|
841
|
+
.seg button {
|
|
842
|
+
flex: 1;
|
|
843
|
+
min-width: 0;
|
|
844
|
+
min-height: 0;
|
|
845
|
+
padding: 0 4px;
|
|
846
|
+
border: 0;
|
|
847
|
+
border-left: 1px solid rgba(0, 0, 0, 0.12);
|
|
848
|
+
border-radius: 0;
|
|
849
|
+
background: transparent;
|
|
850
|
+
white-space: nowrap;
|
|
851
|
+
overflow: hidden;
|
|
852
|
+
text-overflow: ellipsis;
|
|
853
|
+
}
|
|
854
|
+
.seg button:first-child {
|
|
855
|
+
border-left: 0;
|
|
856
|
+
}
|
|
857
|
+
.seg button[aria-checked='true'] {
|
|
858
|
+
background: var(--md-sys-color-primary, #1976d2);
|
|
859
|
+
color: var(--md-sys-color-on-primary, #fff);
|
|
429
860
|
}
|
|
430
861
|
button {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
border
|
|
435
|
-
|
|
862
|
+
box-sizing: border-box;
|
|
863
|
+
min-height: var(--property-editor-field-height, 24px);
|
|
864
|
+
padding: 0 8px;
|
|
865
|
+
border: var(--property-editor-border, 1px solid rgba(0, 0, 0, 0.2));
|
|
866
|
+
border-radius: var(--property-editor-border-radius, 4px);
|
|
867
|
+
background-color: var(--property-editor-background, field);
|
|
868
|
+
color: inherit;
|
|
869
|
+
font: var(--property-editor-field-font, inherit);
|
|
436
870
|
cursor: pointer;
|
|
437
871
|
}
|
|
438
872
|
button[disabled] {
|
|
439
873
|
opacity: 0.4;
|
|
440
874
|
cursor: default;
|
|
441
875
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
876
|
+
.tiers,
|
|
877
|
+
.chips,
|
|
878
|
+
.actions,
|
|
879
|
+
.inline {
|
|
880
|
+
display: flex;
|
|
881
|
+
flex-wrap: wrap;
|
|
882
|
+
gap: 4px;
|
|
883
|
+
align-items: center;
|
|
446
884
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
width: 100%;
|
|
885
|
+
.actions {
|
|
886
|
+
justify-content: flex-end;
|
|
450
887
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
888
|
+
.tiers button[aria-pressed='true'] {
|
|
889
|
+
border-color: var(--md-sys-color-primary, #1976d2);
|
|
890
|
+
color: var(--md-sys-color-primary, #1976d2);
|
|
891
|
+
font-weight: bold;
|
|
455
892
|
}
|
|
456
|
-
|
|
457
|
-
|
|
893
|
+
.tiers .end {
|
|
894
|
+
margin-left: auto;
|
|
895
|
+
}
|
|
896
|
+
.inline > input {
|
|
897
|
+
flex: 1;
|
|
898
|
+
}
|
|
899
|
+
.chips {
|
|
458
900
|
box-sizing: border-box;
|
|
901
|
+
padding: 2px;
|
|
902
|
+
border: 1px solid transparent;
|
|
903
|
+
border-radius: var(--property-editor-border-radius, 4px);
|
|
459
904
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
905
|
+
.chip {
|
|
906
|
+
display: inline-flex;
|
|
907
|
+
align-items: center;
|
|
908
|
+
gap: 2px;
|
|
909
|
+
padding: 1px 2px 1px 6px;
|
|
910
|
+
border-radius: 10px;
|
|
911
|
+
background: rgba(25, 118, 210, 0.1);
|
|
912
|
+
color: #0d47a1;
|
|
913
|
+
}
|
|
914
|
+
.chip[unregistered] {
|
|
915
|
+
background: rgba(239, 159, 39, 0.18);
|
|
916
|
+
color: #633806;
|
|
917
|
+
}
|
|
918
|
+
.chip button {
|
|
919
|
+
min-height: 0;
|
|
920
|
+
padding: 0 4px;
|
|
921
|
+
border: 0;
|
|
922
|
+
background: transparent;
|
|
923
|
+
}
|
|
924
|
+
.chips select {
|
|
925
|
+
flex: 1;
|
|
926
|
+
min-width: 80px;
|
|
927
|
+
width: auto;
|
|
463
928
|
}
|
|
464
929
|
svg {
|
|
930
|
+
display: block;
|
|
465
931
|
width: 100%;
|
|
466
|
-
height:
|
|
932
|
+
height: auto;
|
|
933
|
+
max-height: 240px;
|
|
934
|
+
touch-action: none;
|
|
467
935
|
background: rgba(0, 0, 0, 0.02);
|
|
468
936
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
937
|
+
border-radius: 4px;
|
|
469
938
|
}
|
|
470
|
-
|
|
939
|
+
svg [data-slot] {
|
|
940
|
+
cursor: move;
|
|
941
|
+
}
|
|
942
|
+
svg [data-handle] {
|
|
943
|
+
cursor: ew-resize;
|
|
944
|
+
}
|
|
945
|
+
.caption {
|
|
471
946
|
display: flex;
|
|
472
|
-
|
|
473
|
-
|
|
947
|
+
justify-content: space-between;
|
|
948
|
+
gap: 6px;
|
|
949
|
+
color: rgba(0, 0, 0, 0.55);
|
|
950
|
+
font-size: 11px;
|
|
951
|
+
}
|
|
952
|
+
.items {
|
|
953
|
+
display: flex;
|
|
954
|
+
flex-direction: column;
|
|
955
|
+
gap: 2px;
|
|
956
|
+
}
|
|
957
|
+
.items .bad {
|
|
958
|
+
color: #c62828;
|
|
959
|
+
}
|
|
960
|
+
.items .warn {
|
|
961
|
+
color: #854f0b;
|
|
962
|
+
}
|
|
963
|
+
table {
|
|
964
|
+
border-collapse: collapse;
|
|
965
|
+
width: 100%;
|
|
966
|
+
}
|
|
967
|
+
td,
|
|
968
|
+
th {
|
|
969
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
970
|
+
padding: 1px;
|
|
474
971
|
}
|
|
475
972
|
`;
|
|
476
973
|
__decorate([
|
|
@@ -497,6 +994,9 @@ __decorate([
|
|
|
497
994
|
__decorate([
|
|
498
995
|
state()
|
|
499
996
|
], OxSlotLayoutEditor.prototype, "presetParams", void 0);
|
|
997
|
+
__decorate([
|
|
998
|
+
state()
|
|
999
|
+
], OxSlotLayoutEditor.prototype, "fitNote", void 0);
|
|
500
1000
|
OxSlotLayoutEditor = __decorate([
|
|
501
1001
|
customElement('ox-slot-layout-editor')
|
|
502
1002
|
], OxSlotLayoutEditor);
|