@things-factory/figure-ui 10.1.38 → 10.1.39
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/client/graphql/index.ts +53 -0
- package/client/modeller/figure-history-panel.ts +11 -85
- package/client/modeller/figure-v3-verdict.ts +247 -0
- package/client/modeller/gate-findings.ts +105 -0
- package/client/pages/figure-v3-modeller-page.ts +193 -15
- package/dist-client/graphql/index.d.ts +8 -0
- package/dist-client/graphql/index.js +51 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +6 -0
- package/dist-client/modeller/figure-history-panel.js +9 -85
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-v3-verdict.d.ts +50 -0
- package/dist-client/modeller/figure-v3-verdict.js +268 -0
- package/dist-client/modeller/figure-v3-verdict.js.map +1 -0
- package/dist-client/modeller/gate-findings.d.ts +12 -0
- package/dist-client/modeller/gate-findings.js +102 -0
- package/dist-client/modeller/gate-findings.js.map +1 -0
- package/dist-client/pages/figure-v3-modeller-page.d.ts +26 -0
- package/dist-client/pages/figure-v3-modeller-page.js +204 -14
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/test/v3-verdict.test.ts +82 -0
- package/translations/en.json +6 -0
- package/translations/ja.json +6 -0
- package/translations/ko.json +6 -0
- package/translations/ms.json +6 -0
- package/translations/zh.json +6 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate-findings.js","sourceRoot":"","sources":["../../client/modeller/gate-findings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAuB,MAAM,KAAK,CAAA;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAIvC;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAA0B,EAAE,QAAiB,EAAE,UAAmB,OAAO;IACzG,OAAO,IAAI,CAAA;4BACe,QAAQ;kBAClB,OAAO,CAAC,OAAO;QACzB,OAAO,CAAC,EAAE;QACV,CAAC,CAAC,IAAI,CAAA;;yBAEW,OAAO,CAAC,CAAC,CAAC,iCAAiC,CAAC;4BACzC,OAAO,CAAC,EAAE;;WAE3B;QACH,CAAC,CAAC,OAAO;QACT,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA,kBAAkB,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,OAAO;QAClE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA,0CAA0C,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,OAAO;QAC1F,OAAO;;GAEZ,CAAA;AACH,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwEnC,CAAA","sourcesContent":["import { css, html, nothing, type TemplateResult } from 'lit'\nimport { i18next } from '@operato/i18n'\n\nimport type { FigureGateFinding } from '../types.js'\n\n/**\n * One release-gate finding as a line on screen — the measured fact, where, why it matters, and what to change.\n *\n * Two places draw these: the release panel (`figure-history-panel`) and the V3 modeller's verdict while editing\n * (`figure-v3-verdict`). They show the same findings from the same server door, so they draw them one way; a copy\n * in each would drift the first time one of them is touched. The caller passes its own action buttons, because\n * what can be done about a finding depends on the screen it is shown on.\n */\nexport function renderGateFinding(finding: FigureGateFinding, blocking: boolean, actions: unknown = nothing): TemplateResult {\n return html`\n <li finding ?blocking=${blocking}>\n <span msg>${finding.message}</span>\n ${finding.at\n ? html`\n <details affected>\n <summary>${i18next.t('figure.text.show-affected-parts')}</summary>\n <code where>${finding.at}</code>\n </details>\n `\n : nothing}\n ${finding.why ? html`<span why-line>${finding.why}</span>` : nothing}\n ${finding.how ? html`<span how-line><md-icon>build</md-icon>${finding.how}</span>` : nothing}\n ${actions}\n </li>\n `\n}\n\nexport const gateFindingStyles = css`\n ul[gate] {\n margin: var(--spacing-small, 6px) 0 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 3px;\n }\n\n li[head] {\n font: var(--label-font, inherit);\n font-size: 0.72rem;\n color: var(--md-sys-color-error);\n }\n\n li[head][quiet] {\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n li[finding] {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 3px;\n padding: 3px 6px;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background-color: var(--md-sys-color-surface-container);\n border-radius: 4px;\n }\n\n li[finding][blocking] {\n color: var(--md-sys-color-on-error-container, var(--md-sys-color-on-surface));\n background-color: var(--md-sys-color-error-container);\n }\n\n li[finding] code[where] {\n font-family: var(--mono-font, monospace);\n font-size: 0.68rem;\n opacity: 0.85;\n }\n\n li[finding] details[affected] {\n width: 100%;\n color: inherit;\n }\n\n li[finding] details[affected] summary {\n cursor: pointer;\n font-weight: 600;\n }\n\n /* 왜 나쁜지는 곁글로 — 읽어야 하지만 잰 것보다 먼저 눈에 들 필요는 없다 */\n li[finding] span[why-line] {\n opacity: 0.8;\n }\n\n /* 무엇을 바꾸면 되나 — 이 줄이 실제로 사람이 할 일이다. 연장 표를 달아 눈에 걸리게 한다 */\n li[finding] span[how-line] {\n display: flex;\n align-items: flex-start;\n gap: 4px;\n padding-top: 2px;\n font-weight: 600;\n }\n li[finding] span[how-line] md-icon {\n --md-icon-size: 13px;\n flex: none;\n margin-top: 1px;\n }\n`\n"]}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { PageView } from '@operato/shell';
|
|
2
2
|
import '../modeller/v3-editor.js';
|
|
3
|
+
import '@material/web/icon/icon.js';
|
|
3
4
|
import '../modeller/figure-history-panel.js';
|
|
5
|
+
import '../modeller/figure-v3-verdict.js';
|
|
4
6
|
/**
|
|
5
7
|
* The V3 product page has exactly one authoritative workspace for UI and AI input transactions.
|
|
6
8
|
*
|
|
@@ -26,10 +28,33 @@ export declare class FigureV3ModellerPage extends PageView {
|
|
|
26
28
|
* V2 화면의 패널을 그대로 건다.
|
|
27
29
|
*/
|
|
28
30
|
private row;
|
|
31
|
+
private typeError;
|
|
32
|
+
/** Why 「부품에 맞추기」 did nothing. Shown beside the tabs — the editor stays, since nothing in it was lost. */
|
|
33
|
+
private fitError;
|
|
34
|
+
/**
|
|
35
|
+
* Which tab the side shows. The verdict comes first: it is what the author needs while editing, and the count on
|
|
36
|
+
* its tab stays in sight while the history is open — as on the V2 page.
|
|
37
|
+
*/
|
|
38
|
+
private tab;
|
|
39
|
+
private instances?;
|
|
40
|
+
/**
|
|
41
|
+
* The source the verdict judges. The session refuses to export while it loads or saves, so the last one read is
|
|
42
|
+
* kept rather than blanked — the verdict would otherwise flicker to 「waiting」 on every save.
|
|
43
|
+
*/
|
|
44
|
+
private draft?;
|
|
45
|
+
private draftSource;
|
|
29
46
|
private wasDirty;
|
|
30
47
|
private announce;
|
|
31
48
|
private changed;
|
|
32
49
|
private readRow;
|
|
50
|
+
/**
|
|
51
|
+
* 타입을 정한다 — 보드가 도형을 부르는 이름이고, 발행하면 굳는다(ADR-0063 결정 4).
|
|
52
|
+
*
|
|
53
|
+
* 새 V3 도형은 저장할 때 임시 타입을 받는데, 이 화면에는 그것을 바꿀 칸이 없어서 임시 타입 그대로 발행됐다.
|
|
54
|
+
* 행만 바뀐다 — V3 자산은 타입을 원본에 적지 않으므로(적으면 형식이 거절한다) 편집 중인 원본과 부딪히지 않는다.
|
|
55
|
+
* 서버의 거절(이미 쓰는 타입 · 발행 뒤)은 그 문장 그대로 칸 아래에 둔다.
|
|
56
|
+
*/
|
|
57
|
+
private renameType;
|
|
33
58
|
/** 「부품에 맞추기」 — 공간을 지금 부품에서 다시 선언한다. 저장은 저작자가 따로 한다. */
|
|
34
59
|
private fitToParts;
|
|
35
60
|
private released;
|
|
@@ -48,4 +73,5 @@ export declare class FigureV3ModellerPage extends PageView {
|
|
|
48
73
|
actions: never[];
|
|
49
74
|
};
|
|
50
75
|
render(): import("lit-html").TemplateResult<1>;
|
|
76
|
+
private renderTab;
|
|
51
77
|
}
|
|
@@ -4,10 +4,13 @@ import { customElement, state } from 'lit/decorators.js';
|
|
|
4
4
|
import { PageView } from '@operato/shell';
|
|
5
5
|
import { createV3ProductSession } from '../modeller/v3-product-session.js';
|
|
6
6
|
import { v3ProductStore } from '../graphql/v3-store.js';
|
|
7
|
-
import {
|
|
7
|
+
import { i18next } from '@operato/i18n';
|
|
8
|
+
import { fetchFigure, updateFigure } from '../graphql/index.js';
|
|
8
9
|
import { occupancyFromParts } from '../modeller/v3-occupancy.js';
|
|
9
10
|
import '../modeller/v3-editor.js';
|
|
11
|
+
import '@material/web/icon/icon.js';
|
|
10
12
|
import '../modeller/figure-history-panel.js';
|
|
13
|
+
import '../modeller/figure-v3-verdict.js';
|
|
11
14
|
/**
|
|
12
15
|
* The V3 product page has exactly one authoritative workspace for UI and AI input transactions.
|
|
13
16
|
*
|
|
@@ -33,6 +36,14 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
|
|
|
33
36
|
* V2 화면의 패널을 그대로 건다.
|
|
34
37
|
*/
|
|
35
38
|
this.row = { version: 0 };
|
|
39
|
+
this.typeError = '';
|
|
40
|
+
/** Why 「부품에 맞추기」 did nothing. Shown beside the tabs — the editor stays, since nothing in it was lost. */
|
|
41
|
+
this.fitError = '';
|
|
42
|
+
/**
|
|
43
|
+
* Which tab the side shows. The verdict comes first: it is what the author needs while editing, and the count on
|
|
44
|
+
* its tab stays in sight while the history is open — as on the V2 page.
|
|
45
|
+
*/
|
|
46
|
+
this.tab = 'verdict';
|
|
36
47
|
this.wasDirty = false;
|
|
37
48
|
this.announce = () => {
|
|
38
49
|
const view = this.session.read();
|
|
@@ -50,6 +61,8 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
|
|
|
50
61
|
};
|
|
51
62
|
this.changed = () => {
|
|
52
63
|
this.revision++;
|
|
64
|
+
/* Any edit after a failed fit makes the message stale — the author has done something about it. */
|
|
65
|
+
this.fitError = '';
|
|
53
66
|
const view = this.session.read();
|
|
54
67
|
if (!view.saving)
|
|
55
68
|
this.announce();
|
|
@@ -58,14 +71,48 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
|
|
|
58
71
|
this.readRow(view.id);
|
|
59
72
|
this.wasDirty = !!view.dirty;
|
|
60
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* 타입을 정한다 — 보드가 도형을 부르는 이름이고, 발행하면 굳는다(ADR-0063 결정 4).
|
|
76
|
+
*
|
|
77
|
+
* 새 V3 도형은 저장할 때 임시 타입을 받는데, 이 화면에는 그것을 바꿀 칸이 없어서 임시 타입 그대로 발행됐다.
|
|
78
|
+
* 행만 바뀐다 — V3 자산은 타입을 원본에 적지 않으므로(적으면 형식이 거절한다) 편집 중인 원본과 부딪히지 않는다.
|
|
79
|
+
* 서버의 거절(이미 쓰는 타입 · 발행 뒤)은 그 문장 그대로 칸 아래에 둔다.
|
|
80
|
+
*/
|
|
81
|
+
this.renameType = async (event) => {
|
|
82
|
+
const input = event.target;
|
|
83
|
+
const id = this.session.read().id;
|
|
84
|
+
const wanted = input.value.trim().replace(/[^A-Za-z0-9_]/g, '_').toUpperCase();
|
|
85
|
+
input.value = wanted;
|
|
86
|
+
if (!id || !wanted || wanted === this.row.type)
|
|
87
|
+
return;
|
|
88
|
+
try {
|
|
89
|
+
const result = await updateFigure(id, { type: wanted });
|
|
90
|
+
this.typeError = '';
|
|
91
|
+
this.row = { ...this.row, type: result.figure.type };
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
this.typeError = error instanceof Error ? error.message : String(error);
|
|
95
|
+
input.value = this.row.type ?? '';
|
|
96
|
+
}
|
|
97
|
+
};
|
|
61
98
|
/** 「부품에 맞추기」 — 공간을 지금 부품에서 다시 선언한다. 저장은 저작자가 따로 한다. */
|
|
62
99
|
this.fitToParts = () => {
|
|
63
100
|
try {
|
|
64
101
|
this.session.command(occupancyFromParts(this.session.source()));
|
|
102
|
+
this.fitError = '';
|
|
65
103
|
this.changed();
|
|
66
104
|
}
|
|
67
105
|
catch (error) {
|
|
68
|
-
|
|
106
|
+
/*
|
|
107
|
+
This used to set the page error, which replaces the whole editor with one raw line — for a figure with no
|
|
108
|
+
parts that line was 「OCCUPANCY_NO_PARTS: occupancy: no part could be measured」 (2026-09-24).
|
|
109
|
+
*/
|
|
110
|
+
this.fitError =
|
|
111
|
+
error?.code === 'OCCUPANCY_NO_PARTS'
|
|
112
|
+
? i18next.t('figure.text.fit-needs-parts')
|
|
113
|
+
: error instanceof Error
|
|
114
|
+
? error.message
|
|
115
|
+
: String(error);
|
|
69
116
|
}
|
|
70
117
|
};
|
|
71
118
|
this.released = (event) => {
|
|
@@ -116,23 +163,111 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
|
|
|
116
163
|
min-width: 0;
|
|
117
164
|
min-height: 0;
|
|
118
165
|
}
|
|
119
|
-
|
|
166
|
+
aside {
|
|
120
167
|
width: 320px;
|
|
121
168
|
flex: none;
|
|
122
169
|
min-height: 0;
|
|
123
170
|
overflow: auto;
|
|
171
|
+
display: flex;
|
|
172
|
+
flex-direction: column;
|
|
124
173
|
border-left: 1px solid var(--md-sys-color-outline-variant);
|
|
125
174
|
}
|
|
175
|
+
label[type-field] {
|
|
176
|
+
display: grid;
|
|
177
|
+
gap: 4px;
|
|
178
|
+
padding: 12px 12px 8px;
|
|
179
|
+
font-size: 12px;
|
|
180
|
+
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
181
|
+
}
|
|
182
|
+
label[type-field] input,
|
|
183
|
+
label[type-field] code {
|
|
184
|
+
font: inherit;
|
|
185
|
+
font-family: monospace;
|
|
186
|
+
padding: 6px 8px;
|
|
187
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
188
|
+
border-radius: 4px;
|
|
189
|
+
background: var(--md-sys-color-surface);
|
|
190
|
+
color: var(--md-sys-color-on-surface);
|
|
191
|
+
}
|
|
192
|
+
label[type-field] small {
|
|
193
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
194
|
+
}
|
|
195
|
+
p[fit-error] {
|
|
196
|
+
margin: 0;
|
|
197
|
+
padding: 8px 12px;
|
|
198
|
+
font-size: 12px;
|
|
199
|
+
color: var(--md-sys-color-error);
|
|
200
|
+
}
|
|
201
|
+
label[type-field] small[role='alert'] {
|
|
202
|
+
color: var(--md-sys-color-error);
|
|
203
|
+
}
|
|
204
|
+
/* The V2 side panel's tabs, drawn the same way: one icon per tab, the name in title and aria-label. */
|
|
205
|
+
div[tabs] {
|
|
206
|
+
display: flex;
|
|
207
|
+
flex: none;
|
|
208
|
+
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
209
|
+
}
|
|
210
|
+
button[tab] {
|
|
211
|
+
flex: 1;
|
|
212
|
+
position: relative;
|
|
213
|
+
display: flex;
|
|
214
|
+
align-items: center;
|
|
215
|
+
justify-content: center;
|
|
216
|
+
padding: 7px 6px;
|
|
217
|
+
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
218
|
+
background: none;
|
|
219
|
+
border: none;
|
|
220
|
+
border-bottom: 2px solid transparent;
|
|
221
|
+
cursor: pointer;
|
|
222
|
+
}
|
|
223
|
+
button[tab]:hover:not([on]) {
|
|
224
|
+
background-color: var(--md-sys-color-surface-container);
|
|
225
|
+
}
|
|
226
|
+
button[tab][on] {
|
|
227
|
+
color: var(--md-sys-color-primary);
|
|
228
|
+
border-bottom-color: var(--md-sys-color-primary);
|
|
229
|
+
}
|
|
230
|
+
button[tab] md-icon {
|
|
231
|
+
font-size: 20px;
|
|
232
|
+
width: 20px;
|
|
233
|
+
height: 20px;
|
|
234
|
+
}
|
|
235
|
+
span[badge] {
|
|
236
|
+
position: absolute;
|
|
237
|
+
top: 5px;
|
|
238
|
+
right: 2px;
|
|
239
|
+
padding: 0 5px;
|
|
240
|
+
font-size: 0.62rem;
|
|
241
|
+
font-weight: 700;
|
|
242
|
+
line-height: 1.5;
|
|
243
|
+
color: var(--md-sys-color-on-primary-container);
|
|
244
|
+
background-color: var(--md-sys-color-primary-container);
|
|
245
|
+
border-radius: 999em;
|
|
246
|
+
pointer-events: none;
|
|
247
|
+
}
|
|
248
|
+
/* Hidden, not removed: the verdict keeps its last answer and the panel its list while the other tab is open. */
|
|
249
|
+
[off] {
|
|
250
|
+
display: none;
|
|
251
|
+
}
|
|
126
252
|
.error {
|
|
127
253
|
padding: 16px;
|
|
128
254
|
color: #922716;
|
|
129
255
|
}
|
|
130
256
|
`; }
|
|
257
|
+
draftSource() {
|
|
258
|
+
try {
|
|
259
|
+
this.draft = this.session.source();
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
/* loading or saving — keep the last source */
|
|
263
|
+
}
|
|
264
|
+
return this.draft;
|
|
265
|
+
}
|
|
131
266
|
async readRow(id) {
|
|
132
267
|
try {
|
|
133
268
|
const figure = await fetchFigure(id);
|
|
134
269
|
if (this.session.read().id === id)
|
|
135
|
-
this.row = { state: figure.state, version: figure.version ?? 0 };
|
|
270
|
+
this.row = { state: figure.state, version: figure.version ?? 0, type: figure.type };
|
|
136
271
|
}
|
|
137
272
|
catch {
|
|
138
273
|
/* 행을 못 읽으면 패널은 판 번호 0 으로 보인다 — 발행 문이 서버에서 다시 판단하므로 여기서 지어내지 않는다. */
|
|
@@ -172,6 +307,7 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
|
|
|
172
307
|
const discard = this.discardApproved;
|
|
173
308
|
this.discardApproved = false;
|
|
174
309
|
if (id) {
|
|
310
|
+
this.draft = undefined;
|
|
175
311
|
const opening = this.session.open(id, { discard });
|
|
176
312
|
this.changed();
|
|
177
313
|
await opening;
|
|
@@ -179,7 +315,9 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
|
|
|
179
315
|
}
|
|
180
316
|
else {
|
|
181
317
|
this.session.startNew({ discard });
|
|
318
|
+
this.draft = undefined;
|
|
182
319
|
this.row = { version: 0 };
|
|
320
|
+
this.typeError = '';
|
|
183
321
|
}
|
|
184
322
|
this.routeId = id;
|
|
185
323
|
this.error = '';
|
|
@@ -201,16 +339,52 @@ let FigureV3ModellerPage = class FigureV3ModellerPage extends PageView {
|
|
|
201
339
|
.revision=${this.revision}
|
|
202
340
|
@v3-changed=${this.changed}
|
|
203
341
|
></figure-v3-editor>
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
342
|
+
<aside>
|
|
343
|
+
<label type-field>
|
|
344
|
+
<span>${i18next.t('figure.label.figure-type')}</span>
|
|
345
|
+
${this.row.version > 0
|
|
346
|
+
? html `<code>${this.row.type}</code><small>${i18next.t('figure.text.type-name-cannot-change')}</small>`
|
|
347
|
+
: html `<input
|
|
348
|
+
.value=${this.row.type ?? ''}
|
|
349
|
+
placeholder=${i18next.t('figure.text.figure-type-placeholder')}
|
|
350
|
+
?disabled=${!view.id}
|
|
351
|
+
@change=${this.renameType}
|
|
352
|
+
/>`}
|
|
353
|
+
${this.typeError ? html `<small role="alert">${this.typeError}</small>` : ''}
|
|
354
|
+
</label>
|
|
355
|
+
${this.fitError ? html `<p fit-error role="alert">${this.fitError}</p>` : ''}
|
|
356
|
+
<div tabs>
|
|
357
|
+
${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}
|
|
358
|
+
${this.renderTab('history', 'history', i18next.t('figure.label.history'))}
|
|
359
|
+
</div>
|
|
360
|
+
<figure-v3-verdict
|
|
361
|
+
?off=${this.tab !== 'verdict'}
|
|
362
|
+
.source=${this.draftSource()}
|
|
363
|
+
.figureId=${view.id}
|
|
364
|
+
.figureType=${this.row.type}
|
|
365
|
+
@capacity-changed=${(event) => (this.instances = event.detail.instances)}
|
|
366
|
+
@fit-base-to-parts=${this.fitToParts}
|
|
367
|
+
></figure-v3-verdict>
|
|
368
|
+
<figure-history-panel
|
|
369
|
+
?off=${this.tab !== 'history'}
|
|
370
|
+
format="v3"
|
|
371
|
+
.figureId=${view.id}
|
|
372
|
+
.figureState=${this.row.state}
|
|
373
|
+
.figureVersion=${this.row.version}
|
|
374
|
+
.figureType=${this.row.type}
|
|
375
|
+
.dirty=${!!view.dirty}
|
|
376
|
+
@fit-base-to-parts=${this.fitToParts}
|
|
377
|
+
@figure-released=${this.released}
|
|
378
|
+
@figure-reverted=${this.reverted}
|
|
379
|
+
></figure-history-panel>
|
|
380
|
+
</aside>`;
|
|
381
|
+
}
|
|
382
|
+
renderTab(tab, icon, label, badge) {
|
|
383
|
+
return html `
|
|
384
|
+
<button tab ?on=${this.tab === tab} title=${label} aria-label=${label} @click=${() => (this.tab = tab)}>
|
|
385
|
+
<md-icon>${icon}</md-icon>${badge == null ? '' : html `<span badge>${badge.toLocaleString()}</span>`}
|
|
386
|
+
</button>
|
|
387
|
+
`;
|
|
214
388
|
}
|
|
215
389
|
};
|
|
216
390
|
__decorate([
|
|
@@ -225,6 +399,22 @@ __decorate([
|
|
|
225
399
|
state(),
|
|
226
400
|
__metadata("design:type", Object)
|
|
227
401
|
], FigureV3ModellerPage.prototype, "row", void 0);
|
|
402
|
+
__decorate([
|
|
403
|
+
state(),
|
|
404
|
+
__metadata("design:type", Object)
|
|
405
|
+
], FigureV3ModellerPage.prototype, "typeError", void 0);
|
|
406
|
+
__decorate([
|
|
407
|
+
state(),
|
|
408
|
+
__metadata("design:type", Object)
|
|
409
|
+
], FigureV3ModellerPage.prototype, "fitError", void 0);
|
|
410
|
+
__decorate([
|
|
411
|
+
state(),
|
|
412
|
+
__metadata("design:type", String)
|
|
413
|
+
], FigureV3ModellerPage.prototype, "tab", void 0);
|
|
414
|
+
__decorate([
|
|
415
|
+
state(),
|
|
416
|
+
__metadata("design:type", Number)
|
|
417
|
+
], FigureV3ModellerPage.prototype, "instances", void 0);
|
|
228
418
|
FigureV3ModellerPage = __decorate([
|
|
229
419
|
customElement('figure-v3-modeller-page')
|
|
230
420
|
], FigureV3ModellerPage);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-v3-modeller-page.js","sourceRoot":"","sources":["../../client/pages/figure-v3-modeller-page.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { PageView } from '@operato/shell'\nimport { createV3ProductSession } from '../modeller/v3-product-session.js'\nimport { v3ProductStore } from '../graphql/v3-store.js'\nimport { fetchFigure } from '../graphql/index.js'\nimport { occupancyFromParts } from '../modeller/v3-occupancy.js'\nimport '../modeller/v3-editor.js'\nimport '../modeller/figure-history-panel.js'\n\n/**\n * The V3 product page has exactly one authoritative workspace for UI and AI input transactions.\n *\n * It answers to `figure-v3-modeller` while the catalogue's assets are still V2. The designer's order switches\n * the production data and the consuming paths together (ADR-0087, step 5): until the assets are converted, a\n * `figure-modeller` that opened this page would fail on every figure in the catalogue. Having its own route\n * is what makes steps 3 and 4 — the conversion on a recoverable copy, and the canvas check — possible at all.\n * At step 5 the V2 page goes, and this one takes the `figure-modeller` name and route.\n */\n@customElement('figure-v3-modeller-page')\nexport class FigureV3ModellerPage extends PageView {\n static styles = css`\n :host {\n display: flex;\n flex-direction: row;\n height: 100%;\n min-height: 0;\n /*\n The page must fit the width it is given. Without this the shell sizes it to its content — the editor's\n 660 plus the 320 release panel — and the panel runs off the right edge (seen at 945 px, 2026-09-24). The\n V2 page does the same.\n */\n overflow: hidden;\n }\n figure-v3-editor {\n flex: 1;\n min-width: 0;\n min-height: 0;\n }\n figure-history-panel {\n width: 320px;\n flex: none;\n min-height: 0;\n overflow: auto;\n border-left: 1px solid var(--md-sys-color-outline-variant);\n }\n .error {\n padding: 16px;\n color: #922716;\n }\n `\n private readonly session = createV3ProductSession(v3ProductStore)\n private routeId: string | undefined\n private discardApproved = false\n @state() private revision = 0\n @state() private error = ''\n /**\n * 발행 패널이 읽는 행의 사실 — 상태와 발행 판 번호. 원본은 세션이 들고, 이 둘은 행에 있다.\n *\n * 이 화면에는 발행 패널이 없었다. 저장은 되고 발행 · 판본 · 복원 · 판정을 볼 자리가 없어서, ADR-0087 5단계에서\n * `figure-modeller` 가 이 화면으로 바뀌면 저작자는 발행을 할 수 없었다. 새 V3 도형은 공간을 선언하기 전에는 전부\n * 발행 판정에 막히는데 그 선언을 할 단추도 없었다(2026-09-24 확인). 서버의 문 하나가 두 형식을 다 읽으므로\n * V2 화면의 패널을 그대로 건다.\n */\n @state() private row: { state?: 'draft' | 'released'; version: number } = { version: 0 }\n private wasDirty = false\n private announce = () => {\n const view = this.session.read()\n if (!this.active || view.loading || view.saving || view.error || !view.context) return\n window.dispatchEvent(\n new CustomEvent('figure-ai-context', {\n detail: {\n figureId: view.id,\n source: JSON.parse(this.session.source()),\n context: view.context,\n revision: view.revision,\n format: 'v3'\n }\n })\n )\n }\n private changed = () => {\n this.revision++\n const view = this.session.read()\n if (!view.saving) this.announce()\n /* 저장이 끝난 순간 행을 다시 읽는다 — 발행된 도형을 고쳐 저장하면 서버가 초안으로 내린다. */\n if (this.wasDirty && !view.dirty && !view.saving && view.id) this.readRow(view.id)\n this.wasDirty = !!view.dirty\n }\n private async readRow(id: string) {\n try {\n const figure = await fetchFigure(id)\n if (this.session.read().id === id) this.row = { state: figure.state as 'draft' | 'released', version: figure.version ?? 0 }\n } catch {\n /* 행을 못 읽으면 패널은 판 번호 0 으로 보인다 — 발행 문이 서버에서 다시 판단하므로 여기서 지어내지 않는다. */\n }\n }\n /** 「부품에 맞추기」 — 공간을 지금 부품에서 다시 선언한다. 저장은 저작자가 따로 한다. */\n private fitToParts = () => {\n try {\n this.session.command(occupancyFromParts(this.session.source()))\n this.changed()\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n }\n }\n private released = (event: Event) => {\n const figure = (event as CustomEvent).detail?.figure\n if (figure?.id && figure.id === this.session.read().id) this.row = { state: figure.state, version: figure.version ?? 0 }\n }\n /** 과거 판으로 되돌렸으면 서버의 원본이 바뀐 것이다 — 다시 연다. 패널은 저장 안 된 것이 있으면 되돌리기를 막는다. */\n private reverted = async () => {\n const id = this.session.read().id\n if (!id) return\n await this.session.open(id, { discard: true })\n this.changed()\n this.readRow(id)\n }\n /** The dock must explicitly use this checked transaction contract, never an unversioned overwrite. */\n private applyAI = (event: Event) => {\n const detail = (event as CustomEvent).detail\n if (!this.active || !detail) return\n try {\n this.session.apply(detail.changes, 'ai', detail.revision, detail.context)\n detail.applied = true\n this.changed()\n } catch (error) {\n detail.applied = false\n detail.error = error instanceof Error ? error.message : String(error)\n }\n }\n connectedCallback() {\n super.connectedCallback()\n window.addEventListener('figure-ai-context-request', this.announce)\n window.addEventListener('figure-v3-apply', this.applyAI)\n }\n disconnectedCallback() {\n window.removeEventListener('figure-ai-context-request', this.announce)\n window.removeEventListener('figure-v3-apply', this.applyAI)\n super.disconnectedCallback()\n }\n async canDeactivate() {\n const view = this.session.read()\n if (view.saving) return false\n if (!view.dirty) return true\n // The shell invokes this while the edited model is still visible, before navigating away.\n this.discardApproved = window.confirm('저장하지 않은 변경을 버리고 이동하시겠습니까?')\n return this.discardApproved\n }\n async pageUpdated(changes: Record<string, unknown>, lifecycle: { resourceId?: string }) {\n if (!this.active) {\n if (this.session.read().loading) this.session.close()\n window.dispatchEvent(new CustomEvent('figure-ai-context', { detail: undefined }))\n return\n }\n const id = lifecycle?.resourceId\n if (this.session.read().context && id === this.routeId && !changes.active) return\n try {\n const discard = this.discardApproved\n this.discardApproved = false\n if (id) {\n const opening = this.session.open(id, { discard })\n this.changed()\n await opening\n this.readRow(id)\n } else {\n this.session.startNew({ discard })\n this.row = { version: 0 }\n }\n this.routeId = id\n this.error = ''\n this.changed()\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n }\n }\n get context() {\n return { title: this.session.read().name ?? 'Figure', actions: [] }\n }\n render() {\n if (this.error) return html`<p role=\"alert\" class=\"error\">${this.error}</p>`\n const view = this.session.read()\n return html`<figure-v3-editor\n .session=${this.session}\n .revision=${this.revision}\n @v3-changed=${this.changed}\n ></figure-v3-editor>\n <figure-history-panel\n format=\"v3\"\n .figureId=${view.id}\n .figureState=${this.row.state}\n .figureVersion=${this.row.version}\n .dirty=${!!view.dirty}\n @fit-base-to-parts=${this.fitToParts}\n @figure-released=${this.released}\n @figure-reverted=${this.reverted}\n ></figure-history-panel>`\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-v3-modeller-page.js","sourceRoot":"","sources":["../../client/pages/figure-v3-modeller-page.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { PageView } from '@operato/shell'\nimport { createV3ProductSession } from '../modeller/v3-product-session.js'\nimport { v3ProductStore } from '../graphql/v3-store.js'\nimport { i18next } from '@operato/i18n'\nimport { fetchFigure, updateFigure } from '../graphql/index.js'\nimport { occupancyFromParts } from '../modeller/v3-occupancy.js'\nimport '../modeller/v3-editor.js'\nimport '@material/web/icon/icon.js'\nimport '../modeller/figure-history-panel.js'\nimport '../modeller/figure-v3-verdict.js'\n\n/**\n * The V3 product page has exactly one authoritative workspace for UI and AI input transactions.\n *\n * It answers to `figure-v3-modeller` while the catalogue's assets are still V2. The designer's order switches\n * the production data and the consuming paths together (ADR-0087, step 5): until the assets are converted, a\n * `figure-modeller` that opened this page would fail on every figure in the catalogue. Having its own route\n * is what makes steps 3 and 4 — the conversion on a recoverable copy, and the canvas check — possible at all.\n * At step 5 the V2 page goes, and this one takes the `figure-modeller` name and route.\n */\n@customElement('figure-v3-modeller-page')\nexport class FigureV3ModellerPage extends PageView {\n static styles = css`\n :host {\n display: flex;\n flex-direction: row;\n height: 100%;\n min-height: 0;\n /*\n The page must fit the width it is given. Without this the shell sizes it to its content — the editor's\n 660 plus the 320 release panel — and the panel runs off the right edge (seen at 945 px, 2026-09-24). The\n V2 page does the same.\n */\n overflow: hidden;\n }\n figure-v3-editor {\n flex: 1;\n min-width: 0;\n min-height: 0;\n }\n aside {\n width: 320px;\n flex: none;\n min-height: 0;\n overflow: auto;\n display: flex;\n flex-direction: column;\n border-left: 1px solid var(--md-sys-color-outline-variant);\n }\n label[type-field] {\n display: grid;\n gap: 4px;\n padding: 12px 12px 8px;\n font-size: 12px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n label[type-field] input,\n label[type-field] code {\n font: inherit;\n font-family: monospace;\n padding: 6px 8px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n background: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n }\n label[type-field] small {\n color: var(--md-sys-color-on-surface-variant);\n }\n p[fit-error] {\n margin: 0;\n padding: 8px 12px;\n font-size: 12px;\n color: var(--md-sys-color-error);\n }\n label[type-field] small[role='alert'] {\n color: var(--md-sys-color-error);\n }\n /* The V2 side panel's tabs, drawn the same way: one icon per tab, the name in title and aria-label. */\n div[tabs] {\n display: flex;\n flex: none;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n button[tab] {\n flex: 1;\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 7px 6px;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background: none;\n border: none;\n border-bottom: 2px solid transparent;\n cursor: pointer;\n }\n button[tab]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container);\n }\n button[tab][on] {\n color: var(--md-sys-color-primary);\n border-bottom-color: var(--md-sys-color-primary);\n }\n button[tab] md-icon {\n font-size: 20px;\n width: 20px;\n height: 20px;\n }\n span[badge] {\n position: absolute;\n top: 5px;\n right: 2px;\n padding: 0 5px;\n font-size: 0.62rem;\n font-weight: 700;\n line-height: 1.5;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border-radius: 999em;\n pointer-events: none;\n }\n /* Hidden, not removed: the verdict keeps its last answer and the panel its list while the other tab is open. */\n [off] {\n display: none;\n }\n .error {\n padding: 16px;\n color: #922716;\n }\n `\n private readonly session = createV3ProductSession(v3ProductStore)\n private routeId: string | undefined\n private discardApproved = false\n @state() private revision = 0\n @state() private error = ''\n /**\n * 발행 패널이 읽는 행의 사실 — 상태와 발행 판 번호. 원본은 세션이 들고, 이 둘은 행에 있다.\n *\n * 이 화면에는 발행 패널이 없었다. 저장은 되고 발행 · 판본 · 복원 · 판정을 볼 자리가 없어서, ADR-0087 5단계에서\n * `figure-modeller` 가 이 화면으로 바뀌면 저작자는 발행을 할 수 없었다. 새 V3 도형은 공간을 선언하기 전에는 전부\n * 발행 판정에 막히는데 그 선언을 할 단추도 없었다(2026-09-24 확인). 서버의 문 하나가 두 형식을 다 읽으므로\n * V2 화면의 패널을 그대로 건다.\n */\n @state() private row: { state?: 'draft' | 'released'; version: number; type?: string } = { version: 0 }\n @state() private typeError = ''\n /** Why 「부품에 맞추기」 did nothing. Shown beside the tabs — the editor stays, since nothing in it was lost. */\n @state() private fitError = ''\n /**\n * Which tab the side shows. The verdict comes first: it is what the author needs while editing, and the count on\n * its tab stays in sight while the history is open — as on the V2 page.\n */\n @state() private tab: 'verdict' | 'history' = 'verdict'\n @state() private instances?: number\n /**\n * The source the verdict judges. The session refuses to export while it loads or saves, so the last one read is\n * kept rather than blanked — the verdict would otherwise flicker to 「waiting」 on every save.\n */\n private draft?: string\n private draftSource(): string | undefined {\n try {\n this.draft = this.session.source()\n } catch {\n /* loading or saving — keep the last source */\n }\n return this.draft\n }\n private wasDirty = false\n private announce = () => {\n const view = this.session.read()\n if (!this.active || view.loading || view.saving || view.error || !view.context) return\n window.dispatchEvent(\n new CustomEvent('figure-ai-context', {\n detail: {\n figureId: view.id,\n source: JSON.parse(this.session.source()),\n context: view.context,\n revision: view.revision,\n format: 'v3'\n }\n })\n )\n }\n private changed = () => {\n this.revision++\n /* Any edit after a failed fit makes the message stale — the author has done something about it. */\n this.fitError = ''\n const view = this.session.read()\n if (!view.saving) this.announce()\n /* 저장이 끝난 순간 행을 다시 읽는다 — 발행된 도형을 고쳐 저장하면 서버가 초안으로 내린다. */\n if (this.wasDirty && !view.dirty && !view.saving && view.id) this.readRow(view.id)\n this.wasDirty = !!view.dirty\n }\n private async readRow(id: string) {\n try {\n const figure = await fetchFigure(id)\n if (this.session.read().id === id)\n this.row = { state: figure.state as 'draft' | 'released', version: figure.version ?? 0, type: figure.type }\n } catch {\n /* 행을 못 읽으면 패널은 판 번호 0 으로 보인다 — 발행 문이 서버에서 다시 판단하므로 여기서 지어내지 않는다. */\n }\n }\n /**\n * 타입을 정한다 — 보드가 도형을 부르는 이름이고, 발행하면 굳는다(ADR-0063 결정 4).\n *\n * 새 V3 도형은 저장할 때 임시 타입을 받는데, 이 화면에는 그것을 바꿀 칸이 없어서 임시 타입 그대로 발행됐다.\n * 행만 바뀐다 — V3 자산은 타입을 원본에 적지 않으므로(적으면 형식이 거절한다) 편집 중인 원본과 부딪히지 않는다.\n * 서버의 거절(이미 쓰는 타입 · 발행 뒤)은 그 문장 그대로 칸 아래에 둔다.\n */\n private renameType = async (event: Event) => {\n const input = event.target as HTMLInputElement\n const id = this.session.read().id\n const wanted = input.value.trim().replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()\n input.value = wanted\n if (!id || !wanted || wanted === this.row.type) return\n try {\n const result = await updateFigure(id, { type: wanted })\n this.typeError = ''\n this.row = { ...this.row, type: result.figure.type }\n } catch (error) {\n this.typeError = error instanceof Error ? error.message : String(error)\n input.value = this.row.type ?? ''\n }\n }\n /** 「부품에 맞추기」 — 공간을 지금 부품에서 다시 선언한다. 저장은 저작자가 따로 한다. */\n private fitToParts = () => {\n try {\n this.session.command(occupancyFromParts(this.session.source()))\n this.fitError = ''\n this.changed()\n } catch (error) {\n /*\n This used to set the page error, which replaces the whole editor with one raw line — for a figure with no\n parts that line was 「OCCUPANCY_NO_PARTS: occupancy: no part could be measured」 (2026-09-24).\n */\n this.fitError =\n (error as { code?: string })?.code === 'OCCUPANCY_NO_PARTS'\n ? i18next.t('figure.text.fit-needs-parts')\n : error instanceof Error\n ? error.message\n : String(error)\n }\n }\n private released = (event: Event) => {\n const figure = (event as CustomEvent).detail?.figure\n if (figure?.id && figure.id === this.session.read().id) this.row = { state: figure.state, version: figure.version ?? 0 }\n }\n /** 과거 판으로 되돌렸으면 서버의 원본이 바뀐 것이다 — 다시 연다. 패널은 저장 안 된 것이 있으면 되돌리기를 막는다. */\n private reverted = async () => {\n const id = this.session.read().id\n if (!id) return\n await this.session.open(id, { discard: true })\n this.changed()\n this.readRow(id)\n }\n /** The dock must explicitly use this checked transaction contract, never an unversioned overwrite. */\n private applyAI = (event: Event) => {\n const detail = (event as CustomEvent).detail\n if (!this.active || !detail) return\n try {\n this.session.apply(detail.changes, 'ai', detail.revision, detail.context)\n detail.applied = true\n this.changed()\n } catch (error) {\n detail.applied = false\n detail.error = error instanceof Error ? error.message : String(error)\n }\n }\n connectedCallback() {\n super.connectedCallback()\n window.addEventListener('figure-ai-context-request', this.announce)\n window.addEventListener('figure-v3-apply', this.applyAI)\n }\n disconnectedCallback() {\n window.removeEventListener('figure-ai-context-request', this.announce)\n window.removeEventListener('figure-v3-apply', this.applyAI)\n super.disconnectedCallback()\n }\n async canDeactivate() {\n const view = this.session.read()\n if (view.saving) return false\n if (!view.dirty) return true\n // The shell invokes this while the edited model is still visible, before navigating away.\n this.discardApproved = window.confirm('저장하지 않은 변경을 버리고 이동하시겠습니까?')\n return this.discardApproved\n }\n async pageUpdated(changes: Record<string, unknown>, lifecycle: { resourceId?: string }) {\n if (!this.active) {\n if (this.session.read().loading) this.session.close()\n window.dispatchEvent(new CustomEvent('figure-ai-context', { detail: undefined }))\n return\n }\n const id = lifecycle?.resourceId\n if (this.session.read().context && id === this.routeId && !changes.active) return\n try {\n const discard = this.discardApproved\n this.discardApproved = false\n if (id) {\n this.draft = undefined\n const opening = this.session.open(id, { discard })\n this.changed()\n await opening\n this.readRow(id)\n } else {\n this.session.startNew({ discard })\n this.draft = undefined\n this.row = { version: 0 }\n this.typeError = ''\n }\n this.routeId = id\n this.error = ''\n this.changed()\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n }\n }\n get context() {\n return { title: this.session.read().name ?? 'Figure', actions: [] }\n }\n render() {\n if (this.error) return html`<p role=\"alert\" class=\"error\">${this.error}</p>`\n const view = this.session.read()\n return html`<figure-v3-editor\n .session=${this.session}\n .revision=${this.revision}\n @v3-changed=${this.changed}\n ></figure-v3-editor>\n <aside>\n <label type-field>\n <span>${i18next.t('figure.label.figure-type')}</span>\n ${this.row.version > 0\n ? html`<code>${this.row.type}</code><small>${i18next.t('figure.text.type-name-cannot-change')}</small>`\n : html`<input\n .value=${this.row.type ?? ''}\n placeholder=${i18next.t('figure.text.figure-type-placeholder')}\n ?disabled=${!view.id}\n @change=${this.renameType}\n />`}\n ${this.typeError ? html`<small role=\"alert\">${this.typeError}</small>` : ''}\n </label>\n ${this.fitError ? html`<p fit-error role=\"alert\">${this.fitError}</p>` : ''}\n <div tabs>\n ${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}\n ${this.renderTab('history', 'history', i18next.t('figure.label.history'))}\n </div>\n <figure-v3-verdict\n ?off=${this.tab !== 'verdict'}\n .source=${this.draftSource()}\n .figureId=${view.id}\n .figureType=${this.row.type}\n @capacity-changed=${(event: CustomEvent) => (this.instances = event.detail.instances)}\n @fit-base-to-parts=${this.fitToParts}\n ></figure-v3-verdict>\n <figure-history-panel\n ?off=${this.tab !== 'history'}\n format=\"v3\"\n .figureId=${view.id}\n .figureState=${this.row.state}\n .figureVersion=${this.row.version}\n .figureType=${this.row.type}\n .dirty=${!!view.dirty}\n @fit-base-to-parts=${this.fitToParts}\n @figure-released=${this.released}\n @figure-reverted=${this.reverted}\n ></figure-history-panel>\n </aside>`\n }\n private renderTab(tab: 'verdict' | 'history', icon: string, label: string, badge?: number) {\n return html`\n <button tab ?on=${this.tab === tab} title=${label} aria-label=${label} @click=${() => (this.tab = tab)}>\n <md-icon>${icon}</md-icon>${badge == null ? '' : html`<span badge>${badge.toLocaleString()}</span>`}\n </button>\n `\n }\n}\n"]}
|