@things-factory/figure-ui 10.1.5 → 10.1.7
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 +36 -3
- package/client/modeller/figure-animations.ts +712 -0
- 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 +171 -18
- package/client/modeller/figure-inspector.ts +439 -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 +91 -15
- 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 +204 -66
- package/client/pages/figure-modeller-page.ts +158 -42
- package/client/types.ts +31 -0
- package/client/viewparts/figure-thumb.ts +250 -0
- package/dist-client/graphql/index.d.ts +8 -1
- package/dist-client/graphql/index.js +33 -3
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-animations.d.ts +93 -0
- package/dist-client/modeller/figure-animations.js +655 -0
- package/dist-client/modeller/figure-animations.js.map +1 -0
- 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 +15 -1
- package/dist-client/modeller/figure-history-panel.js +165 -18
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-inspector.d.ts +98 -0
- package/dist-client/modeller/figure-inspector.js +425 -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 +24 -0
- package/dist-client/modeller/figure-side.js +94 -14
- 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 +11 -4
- package/dist-client/pages/figure-list-page.js +201 -63
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +33 -0
- package/dist-client/pages/figure-modeller-page.js +156 -42
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +29 -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 +242 -0
- package/dist-client/viewparts/figure-thumb.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -5
- package/test/i18n-prefix-guard.test.ts +171 -0
- package/test/korean-register-guard.test.ts +83 -0
- package/translations/en.json +351 -264
- package/translations/ja.json +276 -264
- package/translations/ko.json +351 -264
- package/translations/ms.json +276 -264
- package/translations/zh.json +276 -264
|
@@ -54,6 +54,16 @@ export declare class FigureModellerPage extends FigureModellerPageBase {
|
|
|
54
54
|
private dirty;
|
|
55
55
|
private saving;
|
|
56
56
|
private violations;
|
|
57
|
+
/**
|
|
58
|
+
* 저장이 왜 안 됐나.
|
|
59
|
+
*
|
|
60
|
+
* **`save()` 에 `catch` 가 없었다.** `try … finally` 뿐이라 서버가 거절하면(형식 위반 · 타입 이름
|
|
61
|
+
* 중복 · 권한 없음 · 네트워크) 단추만 멈추고 화면은 **아무 말도 하지 않았다.** 저작자는 눌렀는데
|
|
62
|
+
* 아무 일도 안 난 것으로 보고, 저장됐는지 아닌지도 알 수 없었다.
|
|
63
|
+
*
|
|
64
|
+
* 미저장 표시(`dirty`)는 그대로 남으므로 데이터를 잃지는 않았다. 잃은 것은 **사유**다.
|
|
65
|
+
*/
|
|
66
|
+
private saveFailure;
|
|
57
67
|
/** 저작 보조가 낸 후보. 받기 전에는 원본 데이터가 아니다. */
|
|
58
68
|
private proposal?;
|
|
59
69
|
/** 그 후보의 셈. 무엇을 받는 것인지 숫자로도 보여 준다. */
|
|
@@ -144,6 +154,15 @@ export declare class FigureModellerPage extends FigureModellerPageBase {
|
|
|
144
154
|
*
|
|
145
155
|
* 형식을 어긴 원본 데이터로는 저장을 누를 수 없게 한다. 눌렀다가 서버가 받아주지 않는 것보다,
|
|
146
156
|
* 누를 수 없는 이유가 판단 패널에 떠 있는 것이 낫다.
|
|
157
|
+
*
|
|
158
|
+
* ## 바뀐 것이 없으면 누를 수 없다
|
|
159
|
+
*
|
|
160
|
+
* **`dirty` 를 안 보고 있었다.** 저장한 직후에도 단추가 살아 있어 같은 정본을 몇 번이고 다시 쓸
|
|
161
|
+
* 수 있었고, 저장마다 썸네일을 새로 찍어 20KB 를 다시 넣고 `thumbnailUpdatedAt` 을 올렸다 —
|
|
162
|
+
* 그림 주소의 판이 바뀌므로 브라우저 캐시도 함께 버려진다. 바뀐 것이 없는데 일어날 일이 아니다.
|
|
163
|
+
*
|
|
164
|
+
* 새로 만드는 중인 것은 `startNew` 가 `dirty` 로 시작하므로 첫 저장이 막히지 않는다.
|
|
165
|
+
* 저장이 거절되면 `dirty` 를 그대로 두므로 다시 누를 수 있다.
|
|
147
166
|
*/
|
|
148
167
|
private get canSave();
|
|
149
168
|
pageUpdated(changes: Record<string, unknown>, lifecycle: {
|
|
@@ -152,6 +171,13 @@ export declare class FigureModellerPage extends FigureModellerPageBase {
|
|
|
152
171
|
private load;
|
|
153
172
|
private startNew;
|
|
154
173
|
private rename;
|
|
174
|
+
/**
|
|
175
|
+
* 분류와 태그를 고친다.
|
|
176
|
+
*
|
|
177
|
+
* 정본(`source`)이 아니라 **카탈로그의 값**이다 — 도형이 무엇으로 생겼는지가 아니라 어디에
|
|
178
|
+
* 걸리는지다. 그래서 점수도 발행도 흔들리지 않는다.
|
|
179
|
+
*/
|
|
180
|
+
private catalog;
|
|
155
181
|
/**
|
|
156
182
|
* 부품 목록을 갈아 끼운다.
|
|
157
183
|
*
|
|
@@ -164,6 +190,13 @@ export declare class FigureModellerPage extends FigureModellerPageBase {
|
|
|
164
190
|
private duplicatePart;
|
|
165
191
|
private movePart;
|
|
166
192
|
private updatePart;
|
|
193
|
+
/**
|
|
194
|
+
* 판 자체의 값을 갈아 끼운다 — 능력처럼 부품보다 위에 있는 것들.
|
|
195
|
+
*
|
|
196
|
+
* 부품 목록은 건드리지 않는다. `put` 은 부품을 함께 받으므로 여기서 쓰면 능력 하나를
|
|
197
|
+
* 고칠 때마다 부품 목록이 새 배열이 되고, 그리는 쪽이 전부 다시 그린다.
|
|
198
|
+
*/
|
|
199
|
+
private updateBoard;
|
|
167
200
|
/**
|
|
168
201
|
* 디테일 등급을 바꾼다.
|
|
169
202
|
*
|
|
@@ -47,29 +47,23 @@ function rememberPanes(panes) {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* 새로 만든 Figure 의 시작
|
|
50
|
+
* 새로 만든 Figure 의 시작 상태 — **빈 상자 하나뿐이다.**
|
|
51
51
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
52
|
+
* 전에는 부품(300 상자)을 하나 놓아 주었다. 형식이 부품 0개를 오류로 보기 때문이다
|
|
53
|
+
* (`missing-parts` — parts 가 없다). 그래서 아무것도 안 했는데 붉은 판정이 뜨는 것을 피하려고
|
|
54
|
+
* 상자를 깔았다.
|
|
55
|
+
*
|
|
56
|
+
* **그런데 「무조건 상자」는 대개 첫 동작이 그것을 지우는 일이 된다** — 컨베이어를 만들 사람도,
|
|
57
|
+
* 시그널타워를 만들 사람도 상자부터 치운다. 그래서 비운다. 형식은 그대로 두고(부품 없는 것은
|
|
58
|
+
* 발행할 수 없는 것이 맞다) **화면이 「아직 저작 안 함」과 「형식을 어김」을 가른다.**
|
|
59
|
+
*
|
|
60
|
+
* 빈 화면이 가르치는 자리다: 부품 목록이 「위에서 도형을 골라 시작하세요」를 이미 말하고 팔레트가
|
|
61
|
+
* 바로 그 위에 있다(`figure-parts`).
|
|
55
62
|
*/
|
|
56
63
|
function startingModel(type) {
|
|
57
64
|
return {
|
|
58
65
|
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
|
-
]
|
|
66
|
+
parts: []
|
|
73
67
|
};
|
|
74
68
|
}
|
|
75
69
|
/**
|
|
@@ -122,6 +116,16 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
122
116
|
this.dirty = false;
|
|
123
117
|
this.saving = false;
|
|
124
118
|
this.violations = [];
|
|
119
|
+
/**
|
|
120
|
+
* 저장이 왜 안 됐나.
|
|
121
|
+
*
|
|
122
|
+
* **`save()` 에 `catch` 가 없었다.** `try … finally` 뿐이라 서버가 거절하면(형식 위반 · 타입 이름
|
|
123
|
+
* 중복 · 권한 없음 · 네트워크) 단추만 멈추고 화면은 **아무 말도 하지 않았다.** 저작자는 눌렀는데
|
|
124
|
+
* 아무 일도 안 난 것으로 보고, 저장됐는지 아닌지도 알 수 없었다.
|
|
125
|
+
*
|
|
126
|
+
* 미저장 표시(`dirty`)는 그대로 남으므로 데이터를 잃지는 않았다. 잃은 것은 **사유**다.
|
|
127
|
+
*/
|
|
128
|
+
this.saveFailure = '';
|
|
125
129
|
/** 받기로 고른 변경들. 처음에는 전부 골라 둔다. */
|
|
126
130
|
this.picked = new Set();
|
|
127
131
|
}
|
|
@@ -258,6 +262,23 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
258
262
|
cursor: default;
|
|
259
263
|
}
|
|
260
264
|
|
|
265
|
+
/*
|
|
266
|
+
저장이 거절된 사유. 단추 **바로 왼쪽**에 둔다 — 누른 자리에서 답을 봐야 한다. 길면 줄이지만
|
|
267
|
+
전문은 title 로 남긴다(서버 문장이 길 수 있다).
|
|
268
|
+
*/
|
|
269
|
+
span[save-failed] {
|
|
270
|
+
max-width: 280px;
|
|
271
|
+
overflow: hidden;
|
|
272
|
+
text-overflow: ellipsis;
|
|
273
|
+
white-space: nowrap;
|
|
274
|
+
padding: 4px 8px;
|
|
275
|
+
font: var(--label-font, inherit);
|
|
276
|
+
font-size: 0.72rem;
|
|
277
|
+
color: var(--md-sys-color-on-error-container, var(--md-sys-color-on-surface));
|
|
278
|
+
background-color: var(--md-sys-color-error-container);
|
|
279
|
+
border-radius: 6px;
|
|
280
|
+
}
|
|
281
|
+
|
|
261
282
|
/*
|
|
262
283
|
칸을 벡터 저작도구의 관례대로 놓는다 — 왼쪽에 무엇이 있나(레이어), 오른쪽에
|
|
263
284
|
고른 것의 속성. 쓰는 사람이 이미 아는 자리라 배울 것이 하나 줄어든다.
|
|
@@ -494,7 +515,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
494
515
|
}
|
|
495
516
|
get context() {
|
|
496
517
|
return {
|
|
497
|
-
title: this.figure?.name ?? i18next.t('title.new-figure'),
|
|
518
|
+
title: this.figure?.name ?? i18next.t('figure.title.new-figure'),
|
|
498
519
|
/*
|
|
499
520
|
**제목 줄은 끈다.** 이름은 그대로 둔다.
|
|
500
521
|
|
|
@@ -517,7 +538,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
517
538
|
하네스 시절의 자리였다 — 앱에서는 셸이 페이지 제목으로 같은 이름을 그리므로 글자가 두 번
|
|
518
539
|
나오고, 캔버스가 주인공인 화면에서 그만큼 높이를 잃었다.
|
|
519
540
|
-->
|
|
520
|
-
${this.dirty ? html `<span dirty>${i18next.t('text.unsaved-changes')}</span>` : ''}
|
|
541
|
+
${this.dirty ? html `<span dirty>${i18next.t('figure.text.unsaved-changes')}</span>` : ''}
|
|
521
542
|
<!--
|
|
522
543
|
말로 시키는 줄이 머리줄의 남는 폭을 쓴다. 따로 두면 화면 위가 두 줄이 되고, 그 위 줄은
|
|
523
544
|
이름을 페이지 제목과 겹쳐 적는다.
|
|
@@ -527,8 +548,9 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
527
548
|
.type=${source?.type ?? ''}
|
|
528
549
|
@proposed=${(e) => this.receive(e.detail.proposal)}
|
|
529
550
|
></figure-ask>
|
|
551
|
+
${this.saveFailure ? html `<span save-failed title=${this.saveFailure}>${this.saveFailure}</span>` : ''}
|
|
530
552
|
<button save ?disabled=${!this.canSave} @click=${() => this.save()}>
|
|
531
|
-
${this.saving ? i18next.t('text.saving') : i18next.t('button.save')}
|
|
553
|
+
${this.saving ? i18next.t('figure.text.saving-figure') : i18next.t('figure.button.save')}
|
|
532
554
|
</button>
|
|
533
555
|
</div>
|
|
534
556
|
|
|
@@ -537,6 +559,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
537
559
|
?collapse-left=${this.collapsed.left}
|
|
538
560
|
?collapse-right=${this.collapsed.right}
|
|
539
561
|
@figure-rename=${(e) => this.rename(e.detail.name)}
|
|
562
|
+
@figure-catalog=${(e) => this.catalog(e.detail)}
|
|
540
563
|
@figure-released=${(e) => this.released(e.detail.figure)}
|
|
541
564
|
@figure-reverted=${() => this.figure?.id && this.load(this.figure.id)}
|
|
542
565
|
@add-part=${(e) => this.addPart(e.detail.primitive)}
|
|
@@ -550,6 +573,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
550
573
|
@toggle-part-hidden=${(e) => this.toggleHidden(e.detail.index)}
|
|
551
574
|
@set-all-parts-hidden=${(e) => this.setAllHidden(e.detail.hidden)}
|
|
552
575
|
@change-detail-level=${(e) => this.changeDetailLevel(e.detail.level)}
|
|
576
|
+
@update-board=${(e) => this.updateBoard(e.detail.board)}
|
|
553
577
|
@part-tool=${(e) => this.runTool(e.detail.index, e.detail.tool, e.detail.axis)}
|
|
554
578
|
@pick-part=${(e) => this.pickPart(e.detail.name)}
|
|
555
579
|
>
|
|
@@ -566,7 +590,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
566
590
|
? html `
|
|
567
591
|
<div lanes>
|
|
568
592
|
<div lane>
|
|
569
|
-
<h4>${i18next.t('label.now')}</h4>
|
|
593
|
+
<h4>${i18next.t('figure.label.now')}</h4>
|
|
570
594
|
<figure-canvas
|
|
571
595
|
.board=${this.board}
|
|
572
596
|
.parts=${this.parts}
|
|
@@ -576,11 +600,11 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
576
600
|
</div>
|
|
577
601
|
<div lane candidate>
|
|
578
602
|
<h4>
|
|
579
|
-
${i18next.t('label.proposed')}
|
|
603
|
+
${i18next.t('figure.label.proposed')}
|
|
580
604
|
${this.proposalOf
|
|
581
605
|
? html `<small>
|
|
582
606
|
${this.proposalOf.grade} ·
|
|
583
|
-
${i18next.t('text.n-triangles', { count: this.proposalOf.triangles })}
|
|
607
|
+
${i18next.t('figure.text.n-triangles', { count: this.proposalOf.triangles })}
|
|
584
608
|
</small>`
|
|
585
609
|
: ''}
|
|
586
610
|
</h4>
|
|
@@ -599,6 +623,8 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
599
623
|
|
|
600
624
|
<figure-side
|
|
601
625
|
.figureName=${this.figure?.name ?? ''}
|
|
626
|
+
.figureCategory=${this.figure?.category}
|
|
627
|
+
.figureTags=${this.figure?.tags}
|
|
602
628
|
.figureId=${this.figure?.id ?? ''}
|
|
603
629
|
.figureState=${this.figure?.state}
|
|
604
630
|
.figureVersion=${this.figure?.version ?? 0}
|
|
@@ -635,10 +661,10 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
635
661
|
<button
|
|
636
662
|
mode
|
|
637
663
|
?on=${this.mode === mode}
|
|
638
|
-
title=${i18next.t('text.mode-' + mode + '-explained')}
|
|
664
|
+
title=${i18next.t('figure.text.mode-' + mode + '-explained')}
|
|
639
665
|
@click=${() => (this.mode = mode)}
|
|
640
666
|
>
|
|
641
|
-
${i18next.t('label.mode-' + mode)}
|
|
667
|
+
${i18next.t('figure.label.mode-' + mode)}
|
|
642
668
|
</button>
|
|
643
669
|
`;
|
|
644
670
|
return html `<div modes>
|
|
@@ -661,7 +687,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
661
687
|
return html `
|
|
662
688
|
<button
|
|
663
689
|
collapse
|
|
664
|
-
title=${i18next.t(`text.${gone ? 'expand' : 'collapse'}-${pane}-pane`)}
|
|
690
|
+
title=${i18next.t(`figure.text.${gone ? 'expand' : 'collapse'}-${pane}-pane`)}
|
|
665
691
|
@click=${() => this.toggleCollapse(side)}
|
|
666
692
|
>
|
|
667
693
|
<md-icon>${gone ? back : shut}</md-icon>
|
|
@@ -706,11 +732,11 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
706
732
|
const { errors, violations } = validate(picked);
|
|
707
733
|
return html `
|
|
708
734
|
<div decide>
|
|
709
|
-
<span>${i18next.t('text.assistant-proposed-a-figure')}</span>
|
|
735
|
+
<span>${i18next.t('figure.text.assistant-proposed-a-figure')}</span>
|
|
710
736
|
<div spacer></div>
|
|
711
|
-
<button drop @click=${() => this.discard()}>${i18next.t('button.discard')}</button>
|
|
737
|
+
<button drop @click=${() => this.discard()}>${i18next.t('figure.button.discard')}</button>
|
|
712
738
|
<button take ?disabled=${errors.length > 0 || this.picked.size === 0} @click=${() => this.take()}>
|
|
713
|
-
${i18next.t('button.take-n-changes', { n: this.picked.size })}
|
|
739
|
+
${i18next.t('figure.button.take-n-changes', { n: this.picked.size })}
|
|
714
740
|
</button>
|
|
715
741
|
</div>
|
|
716
742
|
|
|
@@ -722,7 +748,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
722
748
|
`
|
|
723
749
|
: ''}
|
|
724
750
|
${errors.length > 0
|
|
725
|
-
? html `<p blocked>${i18next.t('text.picked-changes-do-not-validate')} — ${errors[0].message}</p>`
|
|
751
|
+
? html `<p blocked>${i18next.t('figure.text.picked-changes-do-not-validate')} — ${errors[0].message}</p>`
|
|
726
752
|
: violations.length > 0
|
|
727
753
|
? html `<p blocked warn>${violations[0].message}</p>`
|
|
728
754
|
: ''}
|
|
@@ -744,7 +770,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
744
770
|
<button
|
|
745
771
|
change
|
|
746
772
|
?on=${on}
|
|
747
|
-
title=${i18next.t('text.change-' + change.kind)}
|
|
773
|
+
title=${i18next.t('figure.text.change-' + change.kind)}
|
|
748
774
|
@click=${() => this.toggle(change.key)}
|
|
749
775
|
>
|
|
750
776
|
<md-icon>${icon}</md-icon>
|
|
@@ -804,9 +830,18 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
804
830
|
*
|
|
805
831
|
* 형식을 어긴 원본 데이터로는 저장을 누를 수 없게 한다. 눌렀다가 서버가 받아주지 않는 것보다,
|
|
806
832
|
* 누를 수 없는 이유가 판단 패널에 떠 있는 것이 낫다.
|
|
833
|
+
*
|
|
834
|
+
* ## 바뀐 것이 없으면 누를 수 없다
|
|
835
|
+
*
|
|
836
|
+
* **`dirty` 를 안 보고 있었다.** 저장한 직후에도 단추가 살아 있어 같은 정본을 몇 번이고 다시 쓸
|
|
837
|
+
* 수 있었고, 저장마다 썸네일을 새로 찍어 20KB 를 다시 넣고 `thumbnailUpdatedAt` 을 올렸다 —
|
|
838
|
+
* 그림 주소의 판이 바뀌므로 브라우저 캐시도 함께 버려진다. 바뀐 것이 없는데 일어날 일이 아니다.
|
|
839
|
+
*
|
|
840
|
+
* 새로 만드는 중인 것은 `startNew` 가 `dirty` 로 시작하므로 첫 저장이 막히지 않는다.
|
|
841
|
+
* 저장이 거절되면 `dirty` 를 그대로 두므로 다시 누를 수 있다.
|
|
807
842
|
*/
|
|
808
843
|
get canSave() {
|
|
809
|
-
if (this.saving || !this.folded || !this.figure?.name) {
|
|
844
|
+
if (this.saving || !this.dirty || !this.folded || !this.figure?.name) {
|
|
810
845
|
return false;
|
|
811
846
|
}
|
|
812
847
|
return validate(this.folded).errors.length === 0;
|
|
@@ -816,18 +851,51 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
816
851
|
return;
|
|
817
852
|
}
|
|
818
853
|
const id = lifecycle?.resourceId;
|
|
819
|
-
if (id
|
|
820
|
-
|
|
854
|
+
if (id) {
|
|
855
|
+
if (id !== this.figure?.id) {
|
|
856
|
+
await this.load(id);
|
|
857
|
+
}
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
/*
|
|
861
|
+
── id 없이 들어온 것은 「새로 만든다」는 뜻이다 ─────────────────────────────
|
|
862
|
+
전에는 `!this.figure` 일 때만 새로 시작했다. 셸은 페이지를 살려 두므로(MDI 탭) 카탈로그에서
|
|
863
|
+
하나 열어 본 뒤에는 이 페이지가 그 도형을 계속 들고 있고, 그래서 「새 figure」가 **아무 일도
|
|
864
|
+
하지 않았다.**
|
|
865
|
+
|
|
866
|
+
그것보다 나쁜 것이 있었다 — 화면에는 옛 도형이 그대로 있으니 사람은 새 것인 줄 알고 고치고
|
|
867
|
+
저장한다. 그러면 **열려 있던 도형에 덮어쓴다.** 「안 열린다」로 보이던 것이 실은 그 문이었다.
|
|
868
|
+
|
|
869
|
+
들고 있는 것이 **저장된 도형이면**(id 가 있으면) 새로 시작한다. 아직 저장 안 한 새 도형을
|
|
870
|
+
들고 있으면 그대로 둔다 — 페이지를 다시 들렀다고 저작 중인 것을 버리지 않는다.
|
|
871
|
+
*/
|
|
872
|
+
if (!this.figure) {
|
|
873
|
+
this.startNew();
|
|
874
|
+
return;
|
|
821
875
|
}
|
|
822
|
-
|
|
876
|
+
if (this.figure.id) {
|
|
877
|
+
/*
|
|
878
|
+
고치던 것이 있으면 묻는다. 저작한 것을 말없이 버리지 않는다 — 되돌릴 수 없다.
|
|
879
|
+
그만두면 보고 있던 도형의 주소로 되돌려 화면과 주소가 어긋나지 않게 한다.
|
|
880
|
+
*/
|
|
881
|
+
if (this.dirty && !confirm(i18next.t('figure.text.discard-changes-and-start-new'))) {
|
|
882
|
+
navigate(`figure-modeller/${this.figure.id}`, true);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
823
885
|
this.startNew();
|
|
824
886
|
}
|
|
825
887
|
}
|
|
826
888
|
async load(id) {
|
|
827
889
|
const figure = await fetchFigure(id);
|
|
828
890
|
this.figure = figure;
|
|
829
|
-
/*
|
|
830
|
-
|
|
891
|
+
/*
|
|
892
|
+
그림이 없으면 이 자리에서 채운다(아래 `fillThumbnail`). 기다리지 않는다 — 화면이 먼저다.
|
|
893
|
+
|
|
894
|
+
**`thumbnail` 이 아니라 `thumbnailUpdatedAt` 으로 판정한다.** 그림 자체는 목록·낱개 질의가
|
|
895
|
+
가져오지 않는다(base64 라서 무겁고 캐시가 안 된다 — 주소로 부른다). 없는 칸으로 판정하면
|
|
896
|
+
늘 「그림이 없다」가 되어 저작면을 열 때마다 다시 찍어 저장하게 된다.
|
|
897
|
+
*/
|
|
898
|
+
if (!figure.thumbnailUpdatedAt && figure.id) {
|
|
831
899
|
void this.fillThumbnail(figure.id);
|
|
832
900
|
}
|
|
833
901
|
// 서버가 준 것을 그대로 쓴다. 읽지 못하면 오류보고없이 빈 상태로 바꾸지 않는다.
|
|
@@ -857,7 +925,8 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
857
925
|
this.figure = { id: '', type: draftType, name: '' };
|
|
858
926
|
this.board = board;
|
|
859
927
|
this.parts = parts;
|
|
860
|
-
|
|
928
|
+
/* 고른 것이 없다 — 부품이 없으니 속성 패널도 고를 것을 기다린다. */
|
|
929
|
+
this.selected = -1;
|
|
861
930
|
this.dirty = true;
|
|
862
931
|
this.violations = [];
|
|
863
932
|
}
|
|
@@ -867,6 +936,18 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
867
936
|
this.figure = { ...this.figure, name };
|
|
868
937
|
this.dirty = true;
|
|
869
938
|
}
|
|
939
|
+
/**
|
|
940
|
+
* 분류와 태그를 고친다.
|
|
941
|
+
*
|
|
942
|
+
* 정본(`source`)이 아니라 **카탈로그의 값**이다 — 도형이 무엇으로 생겼는지가 아니라 어디에
|
|
943
|
+
* 걸리는지다. 그래서 점수도 발행도 흔들리지 않는다.
|
|
944
|
+
*/
|
|
945
|
+
catalog(patch) {
|
|
946
|
+
if (!this.figure)
|
|
947
|
+
return;
|
|
948
|
+
this.figure = { ...this.figure, ...patch };
|
|
949
|
+
this.dirty = true;
|
|
950
|
+
}
|
|
870
951
|
/**
|
|
871
952
|
* 부품 목록을 갈아 끼운다.
|
|
872
953
|
*
|
|
@@ -896,6 +977,16 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
896
977
|
updatePart(index, part) {
|
|
897
978
|
this.put(edits.updatePart(this.parts, index, part));
|
|
898
979
|
}
|
|
980
|
+
/**
|
|
981
|
+
* 판 자체의 값을 갈아 끼운다 — 능력처럼 부품보다 위에 있는 것들.
|
|
982
|
+
*
|
|
983
|
+
* 부품 목록은 건드리지 않는다. `put` 은 부품을 함께 받으므로 여기서 쓰면 능력 하나를
|
|
984
|
+
* 고칠 때마다 부품 목록이 새 배열이 되고, 그리는 쪽이 전부 다시 그린다.
|
|
985
|
+
*/
|
|
986
|
+
updateBoard(board) {
|
|
987
|
+
this.board = board;
|
|
988
|
+
this.dirty = true;
|
|
989
|
+
}
|
|
899
990
|
/**
|
|
900
991
|
* 디테일 등급을 바꾼다.
|
|
901
992
|
*
|
|
@@ -1019,8 +1110,9 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
1019
1110
|
return;
|
|
1020
1111
|
}
|
|
1021
1112
|
try {
|
|
1022
|
-
await updateFigure(id, { thumbnail });
|
|
1023
|
-
|
|
1113
|
+
const { figure } = await updateFigure(id, { thumbnail });
|
|
1114
|
+
/* 서버가 찍은 시각을 그대로 받는다 — 이 값이 그림을 부르는 주소의 판 번호가 된다. */
|
|
1115
|
+
this.figure = { ...this.figure, thumbnailUpdatedAt: figure?.thumbnailUpdatedAt };
|
|
1024
1116
|
}
|
|
1025
1117
|
catch (e) {
|
|
1026
1118
|
/* 볼 권한만 있는 사람일 수 있다. 화면은 그대로 두고 조용히 넘어간다. */
|
|
@@ -1032,6 +1124,7 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
1032
1124
|
return;
|
|
1033
1125
|
}
|
|
1034
1126
|
this.saving = true;
|
|
1127
|
+
this.saveFailure = '';
|
|
1035
1128
|
try {
|
|
1036
1129
|
const sourceJson = JSON.stringify(this.folded);
|
|
1037
1130
|
/*
|
|
@@ -1046,6 +1139,12 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
1046
1139
|
if (this.figure.id) {
|
|
1047
1140
|
const result = await updateFigure(this.figure.id, {
|
|
1048
1141
|
name: this.figure.name,
|
|
1142
|
+
/*
|
|
1143
|
+
카탈로그의 값도 함께 보낸다. 화면에서 고쳐 놓고 저장에 안 실으면 저장 뒤에 조용히
|
|
1144
|
+
사라진다 — 저작자는 저장했다고 보고 다시 열면 없다.
|
|
1145
|
+
*/
|
|
1146
|
+
category: this.figure.category,
|
|
1147
|
+
tags: this.figure.tags,
|
|
1049
1148
|
source: sourceJson,
|
|
1050
1149
|
thumbnail
|
|
1051
1150
|
});
|
|
@@ -1057,11 +1156,13 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
1057
1156
|
// 만든 뒤에는 고칠 수 없으므로 여기서 걸러야 한다.
|
|
1058
1157
|
const taken = await fetchFigureTypeNames();
|
|
1059
1158
|
if (taken.includes(this.folded.type)) {
|
|
1060
|
-
throw new Error(i18next.t('text.type-name-already-taken', { type: this.folded.type }));
|
|
1159
|
+
throw new Error(i18next.t('figure.text.type-name-already-taken', { type: this.folded.type }));
|
|
1061
1160
|
}
|
|
1062
1161
|
const result = await createFigure({
|
|
1063
1162
|
type: this.folded.type,
|
|
1064
1163
|
name: this.figure.name,
|
|
1164
|
+
category: this.figure.category,
|
|
1165
|
+
tags: this.figure.tags,
|
|
1065
1166
|
source: sourceJson,
|
|
1066
1167
|
thumbnail
|
|
1067
1168
|
});
|
|
@@ -1071,6 +1172,15 @@ let FigureModellerPage = class FigureModellerPage extends FigureModellerPageBase
|
|
|
1071
1172
|
}
|
|
1072
1173
|
this.dirty = false;
|
|
1073
1174
|
}
|
|
1175
|
+
catch (e) {
|
|
1176
|
+
/*
|
|
1177
|
+
**서버가 말한 사유를 그대로 올린다.** 여기서 문장을 다시 지으면 무엇이 걸렸는지 알 수 없다 —
|
|
1178
|
+
타입 이름이 이미 쓰였다는 것과 권한이 없다는 것은 저작자가 할 일이 다르다.
|
|
1179
|
+
|
|
1180
|
+
`dirty` 는 건드리지 않는다. 저장이 안 됐으므로 미저장인 것이 사실이다.
|
|
1181
|
+
*/
|
|
1182
|
+
this.saveFailure = e.message;
|
|
1183
|
+
}
|
|
1074
1184
|
finally {
|
|
1075
1185
|
this.saving = false;
|
|
1076
1186
|
}
|
|
@@ -1116,6 +1226,10 @@ __decorate([
|
|
|
1116
1226
|
state(),
|
|
1117
1227
|
__metadata("design:type", Array)
|
|
1118
1228
|
], FigureModellerPage.prototype, "violations", void 0);
|
|
1229
|
+
__decorate([
|
|
1230
|
+
state(),
|
|
1231
|
+
__metadata("design:type", Object)
|
|
1232
|
+
], FigureModellerPage.prototype, "saveFailure", void 0);
|
|
1119
1233
|
__decorate([
|
|
1120
1234
|
state(),
|
|
1121
1235
|
__metadata("design:type", Object)
|