@things-factory/figure-ui 10.1.4 → 10.1.6
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 +122 -15
- package/client/modeller/figure-ask.ts +5 -5
- package/client/modeller/figure-canvas.ts +1 -1
- package/client/modeller/figure-capabilities.ts +372 -0
- package/client/modeller/figure-history-panel.ts +477 -0
- package/client/modeller/figure-inspector.ts +392 -76
- package/client/modeller/figure-parts.ts +15 -15
- package/client/modeller/figure-preview.ts +2 -2
- package/client/modeller/figure-report.ts +102 -37
- package/client/modeller/figure-settings.ts +20 -20
- package/client/modeller/figure-side.ts +54 -8
- package/client/modeller/figure-source.test.ts +15 -1
- package/client/modeller/figure-source.ts +31 -15
- package/client/modeller/figure-thumbnail.ts +49 -4
- package/client/modeller/maturity.ts +10 -10
- package/client/pages/figure-list-page.ts +450 -227
- package/client/pages/figure-modeller-page.ts +282 -43
- package/client/types.ts +42 -0
- package/client/viewparts/figure-thumb.ts +172 -0
- package/dist-client/graphql/index.d.ts +31 -4
- package/dist-client/graphql/index.js +102 -12
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-ask.js +5 -5
- package/dist-client/modeller/figure-ask.js.map +1 -1
- package/dist-client/modeller/figure-canvas.js +1 -1
- package/dist-client/modeller/figure-canvas.js.map +1 -1
- package/dist-client/modeller/figure-capabilities.d.ts +42 -0
- package/dist-client/modeller/figure-capabilities.js +344 -0
- package/dist-client/modeller/figure-capabilities.js.map +1 -0
- package/dist-client/modeller/figure-history-panel.d.ts +56 -0
- package/dist-client/modeller/figure-history-panel.js +490 -0
- package/dist-client/modeller/figure-history-panel.js.map +1 -0
- package/dist-client/modeller/figure-inspector.d.ts +84 -0
- package/dist-client/modeller/figure-inspector.js +379 -75
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/figure-parts.js +15 -15
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-preview.js +2 -2
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-report.d.ts +18 -1
- package/dist-client/modeller/figure-report.js +105 -37
- package/dist-client/modeller/figure-report.js.map +1 -1
- package/dist-client/modeller/figure-settings.js +19 -19
- package/dist-client/modeller/figure-settings.js.map +1 -1
- package/dist-client/modeller/figure-side.d.ts +7 -0
- package/dist-client/modeller/figure-side.js +66 -7
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/modeller/figure-source.d.ts +15 -8
- package/dist-client/modeller/figure-source.js +16 -7
- package/dist-client/modeller/figure-source.js.map +1 -1
- package/dist-client/modeller/figure-thumbnail.js +40 -4
- package/dist-client/modeller/figure-thumbnail.js.map +1 -1
- package/dist-client/modeller/maturity.js +10 -10
- package/dist-client/modeller/maturity.js.map +1 -1
- package/dist-client/pages/figure-list-page.d.ts +45 -25
- package/dist-client/pages/figure-list-page.js +439 -247
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +54 -0
- package/dist-client/pages/figure-modeller-page.js +279 -43
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +42 -0
- package/dist-client/types.js.map +1 -1
- package/dist-client/viewparts/figure-thumb.d.ts +29 -0
- package/dist-client/viewparts/figure-thumb.js +168 -0
- package/dist-client/viewparts/figure-thumb.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -5
- package/test/i18n-prefix-guard.test.ts +129 -0
- package/test/korean-register-guard.test.ts +83 -0
- package/translations/en.json +307 -249
- package/translations/ja.json +275 -249
- package/translations/ko.json +307 -249
- package/translations/ms.json +275 -249
- package/translations/zh.json +275 -249
- package/client/viewparts/figure-card.ts +0 -226
- package/dist-client/viewparts/figure-card.d.ts +0 -24
- package/dist-client/viewparts/figure-card.js +0 -221
- package/dist-client/viewparts/figure-card.js.map +0 -1
|
@@ -2,6 +2,18 @@ import { __decorate, __metadata } from "tslib";
|
|
|
2
2
|
import '@material/web/icon/icon.js';
|
|
3
3
|
import '../modeller/figure-side.js';
|
|
4
4
|
import '../modeller/figure-ask.js';
|
|
5
|
+
/*
|
|
6
|
+
대화 도크. **`/client` 서브패스로 적으면 안 된다.**
|
|
7
|
+
|
|
8
|
+
그 패키지에 `exports` 맵이 없어서 `/client` 는 소스 디렉터리로 떨어지고, webpack 이 `.ts` 를 직접
|
|
9
|
+
컴파일한다. 그 경로로 나온 데코레이터가 `Reflect.metadata` 를 가드 없이 불러 **브라우저에서만**
|
|
10
|
+
죽는다(빌드 로그는 오류 0건). board-ai 와 plant 가 차례로 밟았다.
|
|
11
|
+
|
|
12
|
+
그리고 더 센 이유가 있다 — `board-ui/dist-client` 의 모델러가 같은 엘리먼트를 **빌드된 경로로**
|
|
13
|
+
등록한다. 다른 경로로 적으면 webpack 이 두 모듈로 보고, 두 곳이 같은 커스텀 엘리먼트 이름을
|
|
14
|
+
등록해 나중에 뜨는 화면이 죽는다. 즉 「어느 경로가 되나」가 아니라 **「모두 같은 경로여야 한다」**다.
|
|
15
|
+
*/
|
|
16
|
+
import '@things-factory/ai-assistant/dist-client/index.js';
|
|
5
17
|
import '../modeller/figure-canvas.js';
|
|
6
18
|
import { captureThumbnail } from '../modeller/figure-thumbnail.js';
|
|
7
19
|
import '../modeller/figure-preview.js';
|
|
@@ -12,6 +24,7 @@ import { customElement, state } from 'lit/decorators.js';
|
|
|
12
24
|
import { i18next, localize } from '@operato/i18n';
|
|
13
25
|
import { navigate, PageView } from '@operato/shell';
|
|
14
26
|
import { validate } from '@hatiolab/figure-model';
|
|
27
|
+
import { activePalette } from '@hatiolab/things-scene';
|
|
15
28
|
import * as edits from '../modeller/part-edits.js';
|
|
16
29
|
import * as proposals from '../modeller/proposal.js';
|
|
17
30
|
import { toFigureSource, fromFigureSource } from '../modeller/figure-source.js';
|
|
@@ -47,29 +60,23 @@ function rememberPanes(panes) {
|
|
|
47
60
|
}
|
|
48
61
|
}
|
|
49
62
|
/**
|
|
50
|
-
* 새로 만든 Figure 의 시작
|
|
63
|
+
* 새로 만든 Figure 의 시작 상태 — **빈 상자 하나뿐이다.**
|
|
64
|
+
*
|
|
65
|
+
* 전에는 부품(300 상자)을 하나 놓아 주었다. 형식이 부품 0개를 오류로 보기 때문이다
|
|
66
|
+
* (`missing-parts` — parts 가 없다). 그래서 아무것도 안 했는데 붉은 판정이 뜨는 것을 피하려고
|
|
67
|
+
* 상자를 깔았다.
|
|
68
|
+
*
|
|
69
|
+
* **그런데 「무조건 상자」는 대개 첫 동작이 그것을 지우는 일이 된다** — 컨베이어를 만들 사람도,
|
|
70
|
+
* 시그널타워를 만들 사람도 상자부터 치운다. 그래서 비운다. 형식은 그대로 두고(부품 없는 것은
|
|
71
|
+
* 발행할 수 없는 것이 맞다) **화면이 「아직 저작 안 함」과 「형식을 어김」을 가른다.**
|
|
51
72
|
*
|
|
52
|
-
* 빈
|
|
53
|
-
*
|
|
54
|
-
* 것**이 이 도구를 익히는 첫 단계다.
|
|
73
|
+
* 빈 화면이 가르치는 자리다: 부품 목록이 「위에서 도형을 골라 시작하세요」를 이미 말하고 팔레트가
|
|
74
|
+
* 바로 그 위에 있다(`figure-parts`).
|
|
55
75
|
*/
|
|
56
76
|
function startingModel(type) {
|
|
57
77
|
return {
|
|
58
78
|
board: { width: 300, height: 300, depth: 300, figureType: type, detailLevel: 'M' },
|
|
59
|
-
parts: [
|
|
60
|
-
{
|
|
61
|
-
type: 'figure-part',
|
|
62
|
-
name: 'body',
|
|
63
|
-
primitive: 'cube',
|
|
64
|
-
left: 0,
|
|
65
|
-
top: 0,
|
|
66
|
-
width: 300,
|
|
67
|
-
height: 300,
|
|
68
|
-
zPos: 0,
|
|
69
|
-
depth: 300,
|
|
70
|
-
token: 'palette.primary'
|
|
71
|
-
}
|
|
72
|
-
]
|
|
79
|
+
parts: []
|
|
73
80
|
};
|
|
74
81
|
}
|
|
75
82
|
/**
|
|
@@ -122,6 +129,18 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
122
129
|
this.dirty = false;
|
|
123
130
|
this.saving = false;
|
|
124
131
|
this.violations = [];
|
|
132
|
+
/**
|
|
133
|
+
* 저장이 왜 안 됐나.
|
|
134
|
+
*
|
|
135
|
+
* **`save()` 에 `catch` 가 없었다.** `try … finally` 뿐이라 서버가 거절하면(형식 위반 · 타입 이름
|
|
136
|
+
* 중복 · 권한 없음 · 네트워크) 단추만 멈추고 화면은 **아무 말도 하지 않았다.** 저작자는 눌렀는데
|
|
137
|
+
* 아무 일도 안 난 것으로 보고, 저장됐는지 아닌지도 알 수 없었다.
|
|
138
|
+
*
|
|
139
|
+
* 미저장 표시(`dirty`)는 그대로 남으므로 데이터를 잃지는 않았다. 잃은 것은 **사유**다.
|
|
140
|
+
*/
|
|
141
|
+
this.saveFailure = '';
|
|
142
|
+
/** 대화 도크가 열려 있나. 저작면의 보기 상태라 저장하지 않는다. */
|
|
143
|
+
this.dockOpen = false;
|
|
125
144
|
/** 받기로 고른 변경들. 처음에는 전부 골라 둔다. */
|
|
126
145
|
this.picked = new Set();
|
|
127
146
|
}
|
|
@@ -147,10 +166,50 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
147
166
|
div[edits] {
|
|
148
167
|
flex: 1;
|
|
149
168
|
display: grid;
|
|
150
|
-
grid-template-columns: 220px 1fr 320px;
|
|
169
|
+
grid-template-columns: 220px 1fr 320px 0;
|
|
151
170
|
min-height: 0;
|
|
152
171
|
overflow: hidden;
|
|
153
172
|
}
|
|
173
|
+
/*
|
|
174
|
+
대화 도크는 **불렀을 때만 자리를 받는다.** 저작의 주인공은 캔버스이고, 대화는 필요할 때
|
|
175
|
+
여는 것이다. 늘 열어 두면 넷으로 나뉜 화면에서 캔버스가 가장 좁아진다.
|
|
176
|
+
*/
|
|
177
|
+
div[edits][dock] {
|
|
178
|
+
grid-template-columns: 220px 1fr 320px 360px;
|
|
179
|
+
}
|
|
180
|
+
div[edits] > ox-board-ai-chat {
|
|
181
|
+
grid-column: 4;
|
|
182
|
+
min-width: 0;
|
|
183
|
+
border-left: 1px solid var(--md-sys-color-outline-variant);
|
|
184
|
+
background-color: var(--md-sys-color-surface-container-lowest);
|
|
185
|
+
}
|
|
186
|
+
div[edits]:not([dock]) > ox-board-ai-chat {
|
|
187
|
+
display: none;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* 도크를 여는 손잡이. 머리줄의 저장 단추 옆에 둔다 */
|
|
191
|
+
button[dock] {
|
|
192
|
+
flex: none;
|
|
193
|
+
display: flex;
|
|
194
|
+
align-items: center;
|
|
195
|
+
gap: 4px;
|
|
196
|
+
padding: 5px 10px;
|
|
197
|
+
font: var(--label-font, inherit);
|
|
198
|
+
font-size: 0.78rem;
|
|
199
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
200
|
+
background: none;
|
|
201
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
202
|
+
border-radius: 6px;
|
|
203
|
+
cursor: pointer;
|
|
204
|
+
}
|
|
205
|
+
button[dock][on] {
|
|
206
|
+
color: var(--md-sys-color-primary);
|
|
207
|
+
border-color: var(--md-sys-color-primary);
|
|
208
|
+
background-color: var(--md-sys-color-surface-container);
|
|
209
|
+
}
|
|
210
|
+
button[dock] md-icon {
|
|
211
|
+
font-size: 16px;
|
|
212
|
+
}
|
|
154
213
|
|
|
155
214
|
/*
|
|
156
215
|
**세 칸의 자리를 못 박는다.**
|
|
@@ -258,6 +317,23 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
258
317
|
cursor: default;
|
|
259
318
|
}
|
|
260
319
|
|
|
320
|
+
/*
|
|
321
|
+
저장이 거절된 사유. 단추 **바로 왼쪽**에 둔다 — 누른 자리에서 답을 봐야 한다. 길면 줄이지만
|
|
322
|
+
전문은 title 로 남긴다(서버 문장이 길 수 있다).
|
|
323
|
+
*/
|
|
324
|
+
span[save-failed] {
|
|
325
|
+
max-width: 280px;
|
|
326
|
+
overflow: hidden;
|
|
327
|
+
text-overflow: ellipsis;
|
|
328
|
+
white-space: nowrap;
|
|
329
|
+
padding: 4px 8px;
|
|
330
|
+
font: var(--label-font, inherit);
|
|
331
|
+
font-size: 0.72rem;
|
|
332
|
+
color: var(--md-sys-color-on-error-container, var(--md-sys-color-on-surface));
|
|
333
|
+
background-color: var(--md-sys-color-error-container);
|
|
334
|
+
border-radius: 6px;
|
|
335
|
+
}
|
|
336
|
+
|
|
261
337
|
/*
|
|
262
338
|
칸을 벡터 저작도구의 관례대로 놓는다 — 왼쪽에 무엇이 있나(레이어), 오른쪽에
|
|
263
339
|
고른 것의 속성. 쓰는 사람이 이미 아는 자리라 배울 것이 하나 줄어든다.
|
|
@@ -494,7 +570,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
494
570
|
}
|
|
495
571
|
get context() {
|
|
496
572
|
return {
|
|
497
|
-
title: this.figure?.name ?? i18next.t('title.new-figure'),
|
|
573
|
+
title: this.figure?.name ?? i18next.t('figure.title.new-figure'),
|
|
498
574
|
/*
|
|
499
575
|
**제목 줄은 끈다.** 이름은 그대로 둔다.
|
|
500
576
|
|
|
@@ -517,7 +593,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
517
593
|
하네스 시절의 자리였다 — 앱에서는 셸이 페이지 제목으로 같은 이름을 그리므로 글자가 두 번
|
|
518
594
|
나오고, 캔버스가 주인공인 화면에서 그만큼 높이를 잃었다.
|
|
519
595
|
-->
|
|
520
|
-
${this.dirty ? html `<span dirty>${i18next.t('text.unsaved-changes')}</span>` : ''}
|
|
596
|
+
${this.dirty ? html `<span dirty>${i18next.t('figure.text.unsaved-changes')}</span>` : ''}
|
|
521
597
|
<!--
|
|
522
598
|
말로 시키는 줄이 머리줄의 남는 폭을 쓴다. 따로 두면 화면 위가 두 줄이 되고, 그 위 줄은
|
|
523
599
|
이름을 페이지 제목과 겹쳐 적는다.
|
|
@@ -527,8 +603,17 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
527
603
|
.type=${source?.type ?? ''}
|
|
528
604
|
@proposed=${(e) => this.receive(e.detail.proposal)}
|
|
529
605
|
></figure-ask>
|
|
606
|
+
${this.saveFailure ? html `<span save-failed title=${this.saveFailure}>${this.saveFailure}</span>` : ''}
|
|
607
|
+
<button
|
|
608
|
+
dock
|
|
609
|
+
?on=${this.dockOpen}
|
|
610
|
+
title=${i18next.t('figure.text.dock-explained')}
|
|
611
|
+
@click=${() => (this.dockOpen = !this.dockOpen)}
|
|
612
|
+
>
|
|
613
|
+
<md-icon>forum</md-icon>${i18next.t('figure.button.dock')}
|
|
614
|
+
</button>
|
|
530
615
|
<button save ?disabled=${!this.canSave} @click=${() => this.save()}>
|
|
531
|
-
${this.saving ? i18next.t('text.saving') : i18next.t('button.save')}
|
|
616
|
+
${this.saving ? i18next.t('figure.text.saving-figure') : i18next.t('figure.button.save')}
|
|
532
617
|
</button>
|
|
533
618
|
</div>
|
|
534
619
|
|
|
@@ -536,7 +621,10 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
536
621
|
edits
|
|
537
622
|
?collapse-left=${this.collapsed.left}
|
|
538
623
|
?collapse-right=${this.collapsed.right}
|
|
624
|
+
?dock=${this.dockOpen}
|
|
539
625
|
@figure-rename=${(e) => this.rename(e.detail.name)}
|
|
626
|
+
@figure-released=${(e) => this.released(e.detail.figure)}
|
|
627
|
+
@figure-reverted=${() => this.figure?.id && this.load(this.figure.id)}
|
|
540
628
|
@add-part=${(e) => this.addPart(e.detail.primitive)}
|
|
541
629
|
@select-part=${(e) => (this.selected = e.detail.index)}
|
|
542
630
|
@remove-part=${(e) => this.removePart(e.detail.index)}
|
|
@@ -548,6 +636,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
548
636
|
@toggle-part-hidden=${(e) => this.toggleHidden(e.detail.index)}
|
|
549
637
|
@set-all-parts-hidden=${(e) => this.setAllHidden(e.detail.hidden)}
|
|
550
638
|
@change-detail-level=${(e) => this.changeDetailLevel(e.detail.level)}
|
|
639
|
+
@update-board=${(e) => this.updateBoard(e.detail.board)}
|
|
551
640
|
@part-tool=${(e) => this.runTool(e.detail.index, e.detail.tool, e.detail.axis)}
|
|
552
641
|
@pick-part=${(e) => this.pickPart(e.detail.name)}
|
|
553
642
|
>
|
|
@@ -564,7 +653,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
564
653
|
? html `
|
|
565
654
|
<div lanes>
|
|
566
655
|
<div lane>
|
|
567
|
-
<h4>${i18next.t('label.now')}</h4>
|
|
656
|
+
<h4>${i18next.t('figure.label.now')}</h4>
|
|
568
657
|
<figure-canvas
|
|
569
658
|
.board=${this.board}
|
|
570
659
|
.parts=${this.parts}
|
|
@@ -574,11 +663,11 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
574
663
|
</div>
|
|
575
664
|
<div lane candidate>
|
|
576
665
|
<h4>
|
|
577
|
-
${i18next.t('label.proposed')}
|
|
666
|
+
${i18next.t('figure.label.proposed')}
|
|
578
667
|
${this.proposalOf
|
|
579
668
|
? html `<small>
|
|
580
669
|
${this.proposalOf.grade} ·
|
|
581
|
-
${i18next.t('text.n-triangles', { count: this.proposalOf.triangles })}
|
|
670
|
+
${i18next.t('figure.text.n-triangles', { count: this.proposalOf.triangles })}
|
|
582
671
|
</small>`
|
|
583
672
|
: ''}
|
|
584
673
|
</h4>
|
|
@@ -597,6 +686,10 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
597
686
|
|
|
598
687
|
<figure-side
|
|
599
688
|
.figureName=${this.figure?.name ?? ''}
|
|
689
|
+
.figureId=${this.figure?.id ?? ''}
|
|
690
|
+
.figureState=${this.figure?.state}
|
|
691
|
+
.figureVersion=${this.figure?.version ?? 0}
|
|
692
|
+
.dirty=${this.dirty}
|
|
600
693
|
.board=${this.board}
|
|
601
694
|
.parts=${this.parts}
|
|
602
695
|
.source=${source}
|
|
@@ -604,9 +697,66 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
604
697
|
.view=${this.view}
|
|
605
698
|
@update-view=${(e) => (this.view = e.detail.view)}
|
|
606
699
|
></figure-side>
|
|
700
|
+
|
|
701
|
+
<!-- 대화 도크. 무엇을 켜고 끄는지는 renderDock 위에 적어 두었다 -->
|
|
702
|
+
<ox-board-ai-chat
|
|
703
|
+
.boardTools=${false}
|
|
704
|
+
.toolCategories=${['figure-authoring']}
|
|
705
|
+
.hostContext=${this.hostContext}
|
|
706
|
+
.placeholder=${i18next.t('figure.text.dock-placeholder')}
|
|
707
|
+
@board-ai-proposal-execute=${(e) => this.takeProposal(e.detail.proposal)}
|
|
708
|
+
></ox-board-ai-chat>
|
|
607
709
|
</div>
|
|
608
710
|
`;
|
|
609
711
|
}
|
|
712
|
+
/*
|
|
713
|
+
* ── 대화 도크를 무엇으로 켜나 ───────────────────────────────────────────────
|
|
714
|
+
*
|
|
715
|
+
* board 의 컴포넌트를 그대로 쓰지만 **board 인 척하지 않는다.** `boardTools` 를 끄고 figure 의
|
|
716
|
+
* 도구 범주(`figure-authoring`)만 연다. 끄지 않으면 보드 편집 도구가 함께 들려, 모델이 이
|
|
717
|
+
* 화면에서 할 수 없는 일을 시도한다.
|
|
718
|
+
*
|
|
719
|
+
* board 모양의 인자(`currentBoard` · `knownTypes` · `componentSchemas` · `selectedRefids`)는
|
|
720
|
+
* 주지 않는다 — 전부 선택이고, 채우려면 없는 것을 지어내야 한다.
|
|
721
|
+
*/
|
|
722
|
+
/**
|
|
723
|
+
* 어시스턴트가 「지금 무엇을 열고 있나」를 아는 길.
|
|
724
|
+
*
|
|
725
|
+
* 도구가 대상을 안 받았을 때 이 문맥에서 해소한다 — 사용자에게 식별자를 되묻지 않는다.
|
|
726
|
+
* 팔레트도 함께 보낸다: 모르면 모델이 색을 지어내고, 그 후보는 검증을 지나 **그리는 시점에**
|
|
727
|
+
* 죽는다.
|
|
728
|
+
*/
|
|
729
|
+
get hostContext() {
|
|
730
|
+
return {
|
|
731
|
+
surface: 'figure-modeller',
|
|
732
|
+
figureId: this.figure?.id || undefined,
|
|
733
|
+
figureType: this.board?.figureType,
|
|
734
|
+
figureName: this.figure?.name,
|
|
735
|
+
palette: Object.keys(activePalette())
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* 도크의 후보를 받는다.
|
|
740
|
+
*
|
|
741
|
+
* **후보가 화면에 닿는 길은 하나뿐이다** — 말로 시키는 줄이든 도크든 `receive()` 로 모인다.
|
|
742
|
+
* 두 길을 두면 「지금 / 후보」 두 칸을 두 곳에서 그리게 되고, 어느 쪽이 이기는지 저작자가
|
|
743
|
+
* 매번 생각해야 한다.
|
|
744
|
+
*/
|
|
745
|
+
takeProposal(proposal) {
|
|
746
|
+
const source = proposal?.source;
|
|
747
|
+
if (!source) {
|
|
748
|
+
// 카드는 왔는데 후보가 없다 — 조용히 넘기지 않는다. 저작자가 눌렀으니 답이 있어야 한다.
|
|
749
|
+
this.saveFailure = i18next.t('figure.text.proposal-had-no-source');
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
this.receive({
|
|
753
|
+
source: JSON.stringify(source),
|
|
754
|
+
grade: proposal.score?.grade ?? '',
|
|
755
|
+
triangles: proposal.cost?.triangles ?? 0,
|
|
756
|
+
groups: proposal.cost?.groups ?? 0,
|
|
757
|
+
attempts: proposal.attempts ?? 1
|
|
758
|
+
});
|
|
759
|
+
}
|
|
610
760
|
/**
|
|
611
761
|
* 가운데 칸 위의 띠 — 무엇을 보고 있나와, 양쪽 칸을 접는 손잡이.
|
|
612
762
|
*
|
|
@@ -629,10 +779,10 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
629
779
|
<button
|
|
630
780
|
mode
|
|
631
781
|
?on=${this.mode === mode}
|
|
632
|
-
title=${i18next.t('text.mode-' + mode + '-explained')}
|
|
782
|
+
title=${i18next.t('figure.text.mode-' + mode + '-explained')}
|
|
633
783
|
@click=${() => (this.mode = mode)}
|
|
634
784
|
>
|
|
635
|
-
${i18next.t('label.mode-' + mode)}
|
|
785
|
+
${i18next.t('figure.label.mode-' + mode)}
|
|
636
786
|
</button>
|
|
637
787
|
`;
|
|
638
788
|
return html `<div modes>
|
|
@@ -655,7 +805,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
655
805
|
return html `
|
|
656
806
|
<button
|
|
657
807
|
collapse
|
|
658
|
-
title=${i18next.t(`text.${gone ? 'expand' : 'collapse'}-${pane}-pane`)}
|
|
808
|
+
title=${i18next.t(`figure.text.${gone ? 'expand' : 'collapse'}-${pane}-pane`)}
|
|
659
809
|
@click=${() => this.toggleCollapse(side)}
|
|
660
810
|
>
|
|
661
811
|
<md-icon>${gone ? back : shut}</md-icon>
|
|
@@ -700,11 +850,11 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
700
850
|
const { errors, violations } = validate(picked);
|
|
701
851
|
return html `
|
|
702
852
|
<div decide>
|
|
703
|
-
<span>${i18next.t('text.assistant-proposed-a-figure')}</span>
|
|
853
|
+
<span>${i18next.t('figure.text.assistant-proposed-a-figure')}</span>
|
|
704
854
|
<div spacer></div>
|
|
705
|
-
<button drop @click=${() => this.discard()}>${i18next.t('button.discard')}</button>
|
|
855
|
+
<button drop @click=${() => this.discard()}>${i18next.t('figure.button.discard')}</button>
|
|
706
856
|
<button take ?disabled=${errors.length > 0 || this.picked.size === 0} @click=${() => this.take()}>
|
|
707
|
-
${i18next.t('button.take-n-changes', { n: this.picked.size })}
|
|
857
|
+
${i18next.t('figure.button.take-n-changes', { n: this.picked.size })}
|
|
708
858
|
</button>
|
|
709
859
|
</div>
|
|
710
860
|
|
|
@@ -716,7 +866,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
716
866
|
`
|
|
717
867
|
: ''}
|
|
718
868
|
${errors.length > 0
|
|
719
|
-
? html `<p blocked>${i18next.t('text.picked-changes-do-not-validate')} — ${errors[0].message}</p>`
|
|
869
|
+
? html `<p blocked>${i18next.t('figure.text.picked-changes-do-not-validate')} — ${errors[0].message}</p>`
|
|
720
870
|
: violations.length > 0
|
|
721
871
|
? html `<p blocked warn>${violations[0].message}</p>`
|
|
722
872
|
: ''}
|
|
@@ -738,7 +888,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
738
888
|
<button
|
|
739
889
|
change
|
|
740
890
|
?on=${on}
|
|
741
|
-
title=${i18next.t('text.change-' + change.kind)}
|
|
891
|
+
title=${i18next.t('figure.text.change-' + change.kind)}
|
|
742
892
|
@click=${() => this.toggle(change.key)}
|
|
743
893
|
>
|
|
744
894
|
<md-icon>${icon}</md-icon>
|
|
@@ -798,9 +948,18 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
798
948
|
*
|
|
799
949
|
* 형식을 어긴 원본 데이터로는 저장을 누를 수 없게 한다. 눌렀다가 서버가 받아주지 않는 것보다,
|
|
800
950
|
* 누를 수 없는 이유가 판단 패널에 떠 있는 것이 낫다.
|
|
951
|
+
*
|
|
952
|
+
* ## 바뀐 것이 없으면 누를 수 없다
|
|
953
|
+
*
|
|
954
|
+
* **`dirty` 를 안 보고 있었다.** 저장한 직후에도 단추가 살아 있어 같은 정본을 몇 번이고 다시 쓸
|
|
955
|
+
* 수 있었고, 저장마다 썸네일을 새로 찍어 20KB 를 다시 넣고 `thumbnailUpdatedAt` 을 올렸다 —
|
|
956
|
+
* 그림 주소의 판이 바뀌므로 브라우저 캐시도 함께 버려진다. 바뀐 것이 없는데 일어날 일이 아니다.
|
|
957
|
+
*
|
|
958
|
+
* 새로 만드는 중인 것은 `startNew` 가 `dirty` 로 시작하므로 첫 저장이 막히지 않는다.
|
|
959
|
+
* 저장이 거절되면 `dirty` 를 그대로 두므로 다시 누를 수 있다.
|
|
801
960
|
*/
|
|
802
961
|
get canSave() {
|
|
803
|
-
if (this.saving || !this.folded || !this.figure?.name) {
|
|
962
|
+
if (this.saving || !this.dirty || !this.folded || !this.figure?.name) {
|
|
804
963
|
return false;
|
|
805
964
|
}
|
|
806
965
|
return validate(this.folded).errors.length === 0;
|
|
@@ -810,18 +969,51 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
810
969
|
return;
|
|
811
970
|
}
|
|
812
971
|
const id = lifecycle?.resourceId;
|
|
813
|
-
if (id
|
|
814
|
-
|
|
972
|
+
if (id) {
|
|
973
|
+
if (id !== this.figure?.id) {
|
|
974
|
+
await this.load(id);
|
|
975
|
+
}
|
|
976
|
+
return;
|
|
815
977
|
}
|
|
816
|
-
|
|
978
|
+
/*
|
|
979
|
+
── id 없이 들어온 것은 「새로 만든다」는 뜻이다 ─────────────────────────────
|
|
980
|
+
전에는 `!this.figure` 일 때만 새로 시작했다. 셸은 페이지를 살려 두므로(MDI 탭) 카탈로그에서
|
|
981
|
+
하나 열어 본 뒤에는 이 페이지가 그 도형을 계속 들고 있고, 그래서 「새 figure」가 **아무 일도
|
|
982
|
+
하지 않았다.**
|
|
983
|
+
|
|
984
|
+
그것보다 나쁜 것이 있었다 — 화면에는 옛 도형이 그대로 있으니 사람은 새 것인 줄 알고 고치고
|
|
985
|
+
저장한다. 그러면 **열려 있던 도형에 덮어쓴다.** 「안 열린다」로 보이던 것이 실은 그 문이었다.
|
|
986
|
+
|
|
987
|
+
들고 있는 것이 **저장된 도형이면**(id 가 있으면) 새로 시작한다. 아직 저장 안 한 새 도형을
|
|
988
|
+
들고 있으면 그대로 둔다 — 페이지를 다시 들렀다고 저작 중인 것을 버리지 않는다.
|
|
989
|
+
*/
|
|
990
|
+
if (!this.figure) {
|
|
991
|
+
this.startNew();
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if (this.figure.id) {
|
|
995
|
+
/*
|
|
996
|
+
고치던 것이 있으면 묻는다. 저작한 것을 말없이 버리지 않는다 — 되돌릴 수 없다.
|
|
997
|
+
그만두면 보고 있던 도형의 주소로 되돌려 화면과 주소가 어긋나지 않게 한다.
|
|
998
|
+
*/
|
|
999
|
+
if (this.dirty && !confirm(i18next.t('figure.text.discard-changes-and-start-new'))) {
|
|
1000
|
+
navigate(`figure-modeller/${this.figure.id}`, true);
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
817
1003
|
this.startNew();
|
|
818
1004
|
}
|
|
819
1005
|
}
|
|
820
1006
|
async load(id) {
|
|
821
1007
|
const figure = await fetchFigure(id);
|
|
822
1008
|
this.figure = figure;
|
|
823
|
-
/*
|
|
824
|
-
|
|
1009
|
+
/*
|
|
1010
|
+
그림이 없으면 이 자리에서 채운다(아래 `fillThumbnail`). 기다리지 않는다 — 화면이 먼저다.
|
|
1011
|
+
|
|
1012
|
+
**`thumbnail` 이 아니라 `thumbnailUpdatedAt` 으로 판정한다.** 그림 자체는 목록·낱개 질의가
|
|
1013
|
+
가져오지 않는다(base64 라서 무겁고 캐시가 안 된다 — 주소로 부른다). 없는 칸으로 판정하면
|
|
1014
|
+
늘 「그림이 없다」가 되어 저작면을 열 때마다 다시 찍어 저장하게 된다.
|
|
1015
|
+
*/
|
|
1016
|
+
if (!figure.thumbnailUpdatedAt && figure.id) {
|
|
825
1017
|
void this.fillThumbnail(figure.id);
|
|
826
1018
|
}
|
|
827
1019
|
// 서버가 준 것을 그대로 쓴다. 읽지 못하면 오류보고없이 빈 상태로 바꾸지 않는다.
|
|
@@ -851,7 +1043,8 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
851
1043
|
this.figure = { id: '', type: draftType, name: '' };
|
|
852
1044
|
this.board = board;
|
|
853
1045
|
this.parts = parts;
|
|
854
|
-
|
|
1046
|
+
/* 고른 것이 없다 — 부품이 없으니 속성 패널도 고를 것을 기다린다. */
|
|
1047
|
+
this.selected = -1;
|
|
855
1048
|
this.dirty = true;
|
|
856
1049
|
this.violations = [];
|
|
857
1050
|
}
|
|
@@ -890,6 +1083,16 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
890
1083
|
updatePart(index, part) {
|
|
891
1084
|
this.put(edits.updatePart(this.parts, index, part));
|
|
892
1085
|
}
|
|
1086
|
+
/**
|
|
1087
|
+
* 판 자체의 값을 갈아 끼운다 — 능력처럼 부품보다 위에 있는 것들.
|
|
1088
|
+
*
|
|
1089
|
+
* 부품 목록은 건드리지 않는다. `put` 은 부품을 함께 받으므로 여기서 쓰면 능력 하나를
|
|
1090
|
+
* 고칠 때마다 부품 목록이 새 배열이 되고, 그리는 쪽이 전부 다시 그린다.
|
|
1091
|
+
*/
|
|
1092
|
+
updateBoard(board) {
|
|
1093
|
+
this.board = board;
|
|
1094
|
+
this.dirty = true;
|
|
1095
|
+
}
|
|
893
1096
|
/**
|
|
894
1097
|
* 디테일 등급을 바꾼다.
|
|
895
1098
|
*
|
|
@@ -992,6 +1195,20 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
992
1195
|
* 그림만 보낸다. 정본을 건드리지 않으므로 미저장 표시도 켜지 않는다 — 열어 봤다는 것이 고쳤다는
|
|
993
1196
|
* 뜻이 되면 저작자가 저장할지 말지를 매번 고민한다.
|
|
994
1197
|
*/
|
|
1198
|
+
/**
|
|
1199
|
+
* 발행되었다 — **정본을 든 자리가 그 사실을 받는다.**
|
|
1200
|
+
*
|
|
1201
|
+
* 이력 패널은 발행을 시키고 결과를 알릴 뿐, 이 페이지의 상태를 고쳐 쓰지 않는다. 판 번호와 상태가
|
|
1202
|
+
* 여기서 갱신되어야 머리줄·이력 탭이 같은 것을 본다.
|
|
1203
|
+
*
|
|
1204
|
+
* 정본(source)은 발행이 건드리지 않으므로 다시 불러오지 않는다 — 저작 중인 화면을 되감지 않는다.
|
|
1205
|
+
*/
|
|
1206
|
+
released(figure) {
|
|
1207
|
+
if (!this.figure || figure?.id !== this.figure.id) {
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
this.figure = { ...this.figure, version: figure.version, state: figure.state, updatedAt: figure.updatedAt };
|
|
1211
|
+
}
|
|
995
1212
|
async fillThumbnail(id) {
|
|
996
1213
|
await this.updateComplete;
|
|
997
1214
|
const thumbnail = await captureThumbnail(this.board, this.parts);
|
|
@@ -999,8 +1216,9 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
999
1216
|
return;
|
|
1000
1217
|
}
|
|
1001
1218
|
try {
|
|
1002
|
-
await updateFigure(id, { thumbnail });
|
|
1003
|
-
|
|
1219
|
+
const { figure } = await updateFigure(id, { thumbnail });
|
|
1220
|
+
/* 서버가 찍은 시각을 그대로 받는다 — 이 값이 그림을 부르는 주소의 판 번호가 된다. */
|
|
1221
|
+
this.figure = { ...this.figure, thumbnailUpdatedAt: figure?.thumbnailUpdatedAt };
|
|
1004
1222
|
}
|
|
1005
1223
|
catch (e) {
|
|
1006
1224
|
/* 볼 권한만 있는 사람일 수 있다. 화면은 그대로 두고 조용히 넘어간다. */
|
|
@@ -1012,6 +1230,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
1012
1230
|
return;
|
|
1013
1231
|
}
|
|
1014
1232
|
this.saving = true;
|
|
1233
|
+
this.saveFailure = '';
|
|
1015
1234
|
try {
|
|
1016
1235
|
const sourceJson = JSON.stringify(this.folded);
|
|
1017
1236
|
/*
|
|
@@ -1037,7 +1256,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
1037
1256
|
// 만든 뒤에는 고칠 수 없으므로 여기서 걸러야 한다.
|
|
1038
1257
|
const taken = await fetchFigureTypeNames();
|
|
1039
1258
|
if (taken.includes(this.folded.type)) {
|
|
1040
|
-
throw new Error(i18next.t('text.type-name-already-taken', { type: this.folded.type }));
|
|
1259
|
+
throw new Error(i18next.t('figure.text.type-name-already-taken', { type: this.folded.type }));
|
|
1041
1260
|
}
|
|
1042
1261
|
const result = await createFigure({
|
|
1043
1262
|
type: this.folded.type,
|
|
@@ -1051,6 +1270,15 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
1051
1270
|
}
|
|
1052
1271
|
this.dirty = false;
|
|
1053
1272
|
}
|
|
1273
|
+
catch (e) {
|
|
1274
|
+
/*
|
|
1275
|
+
**서버가 말한 사유를 그대로 올린다.** 여기서 문장을 다시 지으면 무엇이 걸렸는지 알 수 없다 —
|
|
1276
|
+
타입 이름이 이미 쓰였다는 것과 권한이 없다는 것은 저작자가 할 일이 다르다.
|
|
1277
|
+
|
|
1278
|
+
`dirty` 는 건드리지 않는다. 저장이 안 됐으므로 미저장인 것이 사실이다.
|
|
1279
|
+
*/
|
|
1280
|
+
this.saveFailure = e.message;
|
|
1281
|
+
}
|
|
1054
1282
|
finally {
|
|
1055
1283
|
this.saving = false;
|
|
1056
1284
|
}
|
|
@@ -1096,6 +1324,14 @@ __decorate([
|
|
|
1096
1324
|
state(),
|
|
1097
1325
|
__metadata("design:type", Array)
|
|
1098
1326
|
], FigureModellerPage.prototype, "violations", void 0);
|
|
1327
|
+
__decorate([
|
|
1328
|
+
state(),
|
|
1329
|
+
__metadata("design:type", Object)
|
|
1330
|
+
], FigureModellerPage.prototype, "saveFailure", void 0);
|
|
1331
|
+
__decorate([
|
|
1332
|
+
state(),
|
|
1333
|
+
__metadata("design:type", Object)
|
|
1334
|
+
], FigureModellerPage.prototype, "dockOpen", void 0);
|
|
1099
1335
|
__decorate([
|
|
1100
1336
|
state(),
|
|
1101
1337
|
__metadata("design:type", Object)
|