@things-factory/figure-ui 10.1.16 → 10.1.17
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/modeller/figure-animations.ts +12 -12
- package/client/modeller/figure-canvas.ts +32 -48
- package/client/modeller/figure-capabilities.ts +10 -10
- package/client/modeller/figure-inspector.ts +39 -28
- package/client/modeller/figure-parts.ts +1 -1
- package/client/modeller/figure-preview.ts +122 -25
- package/client/modeller/figure-report.ts +24 -6
- package/client/modeller/figure-settings.ts +45 -2
- package/client/modeller/figure-side.ts +6 -6
- package/client/modeller/figure-source.ts +52 -32
- package/client/modeller/figure-thumbnail.ts +5 -5
- package/client/modeller/figure-view.ts +53 -0
- package/client/modeller/part-edits.ts +36 -36
- package/client/modeller/proposal.ts +25 -4
- package/client/pages/figure-modeller-page.ts +64 -58
- package/dist-client/modeller/figure-animations.d.ts +4 -4
- package/dist-client/modeller/figure-animations.js +10 -10
- package/dist-client/modeller/figure-animations.js.map +1 -1
- package/dist-client/modeller/figure-canvas.d.ts +7 -7
- package/dist-client/modeller/figure-canvas.js +27 -40
- package/dist-client/modeller/figure-canvas.js.map +1 -1
- package/dist-client/modeller/figure-capabilities.d.ts +2 -2
- package/dist-client/modeller/figure-capabilities.js +9 -9
- package/dist-client/modeller/figure-capabilities.js.map +1 -1
- package/dist-client/modeller/figure-inspector.d.ts +16 -5
- package/dist-client/modeller/figure-inspector.js +38 -27
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/figure-parts.d.ts +1 -1
- package/dist-client/modeller/figure-parts.js +1 -1
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +39 -0
- package/dist-client/modeller/figure-preview.js +112 -23
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-report.d.ts +9 -0
- package/dist-client/modeller/figure-report.js +23 -5
- package/dist-client/modeller/figure-report.js.map +1 -1
- package/dist-client/modeller/figure-settings.js +42 -2
- package/dist-client/modeller/figure-settings.js.map +1 -1
- package/dist-client/modeller/figure-side.d.ts +2 -2
- package/dist-client/modeller/figure-side.js +5 -5
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/modeller/figure-source.d.ts +17 -15
- package/dist-client/modeller/figure-source.js +40 -21
- package/dist-client/modeller/figure-source.js.map +1 -1
- package/dist-client/modeller/figure-thumbnail.d.ts +2 -2
- package/dist-client/modeller/figure-thumbnail.js +3 -3
- package/dist-client/modeller/figure-thumbnail.js.map +1 -1
- package/dist-client/modeller/figure-view.d.ts +45 -0
- package/dist-client/modeller/figure-view.js +27 -0
- package/dist-client/modeller/figure-view.js.map +1 -1
- package/dist-client/modeller/part-edits.d.ts +16 -16
- package/dist-client/modeller/part-edits.js +35 -35
- package/dist-client/modeller/part-edits.js.map +1 -1
- package/dist-client/modeller/proposal.d.ts +1 -1
- package/dist-client/modeller/proposal.js +15 -3
- package/dist-client/modeller/proposal.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +9 -10
- package/dist-client/pages/figure-modeller-page.js +60 -54
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/test/ai-proposal-contract.test.ts +393 -12
- package/test/expect.ts +55 -0
- package/test/figure-source.test.ts +562 -0
- package/test/i18n-prefix-guard.test.ts +1 -1
- package/{client/modeller → test}/part-edits.test.ts +50 -45
- package/translations/en.json +31 -28
- package/translations/ja.json +16 -21
- package/translations/ko.json +41 -38
- package/translations/ms.json +16 -21
- package/translations/zh.json +16 -21
- package/client/modeller/figure-ask.ts +0 -265
- package/client/modeller/figure-source.test.ts +0 -352
- package/dist-client/modeller/figure-ask.d.ts +0 -40
- package/dist-client/modeller/figure-ask.js +0 -282
- package/dist-client/modeller/figure-ask.js.map +0 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import '@material/web/icon/icon.js'
|
|
2
2
|
import '../modeller/figure-side.js'
|
|
3
|
-
import '../modeller/figure-ask.js'
|
|
4
3
|
import '../modeller/figure-canvas.js'
|
|
5
4
|
import { captureThumbnail } from '../modeller/figure-thumbnail.js'
|
|
6
5
|
import '../modeller/figure-preview.js'
|
|
@@ -23,7 +22,7 @@ import * as proposals from '../modeller/proposal.js'
|
|
|
23
22
|
import * as proposalSessions from '../modeller/proposal-session.js'
|
|
24
23
|
import type { ProposalSession } from '../modeller/proposal-session.js'
|
|
25
24
|
import { toFigureSource, fromFigureSource } from '../modeller/figure-source.js'
|
|
26
|
-
import type {
|
|
25
|
+
import type { FigureDraft, PartModel, PlaneAxis } from '../modeller/figure-source.js'
|
|
27
26
|
import { DEFAULT_VIEW } from '../modeller/figure-view.js'
|
|
28
27
|
import type { ViewSettings } from '../modeller/figure-view.js'
|
|
29
28
|
import { createFigure, fetchFigure, fetchFigureTypeNames, updateFigure } from '../graphql/index.js'
|
|
@@ -86,9 +85,9 @@ function rememberPanes(panes: Panes): void {
|
|
|
86
85
|
* 빈 화면이 가르치는 자리다: 부품 목록이 「위에서 도형을 골라 시작하세요」를 이미 말하고 팔레트가
|
|
87
86
|
* 바로 그 위에 있다(`figure-parts`).
|
|
88
87
|
*/
|
|
89
|
-
function startingModel(type: string): {
|
|
88
|
+
function startingModel(type: string): { draft: FigureDraft; parts: PartModel[] } {
|
|
90
89
|
return {
|
|
91
|
-
|
|
90
|
+
draft: { width: 300, height: 300, depth: 300, figureType: type, detailLevel: 'M' },
|
|
92
91
|
parts: []
|
|
93
92
|
}
|
|
94
93
|
}
|
|
@@ -107,7 +106,7 @@ function startingModel(type: string): { board: BoardModel; parts: PartModel[] }
|
|
|
107
106
|
*
|
|
108
107
|
* ## 편집 데이터는 씬 모델 하나뿐이다
|
|
109
108
|
*
|
|
110
|
-
* 이 페이지가 갖는 것은
|
|
109
|
+
* 이 페이지가 갖는 것은 초안 하나와 부품 목록 — 미리보기에 그대로 세워지는 씬 모델이다.
|
|
111
110
|
* 저장 형식(`FigureSource`)으로 바꾸는 것은 **경계에서만** 한다: 저장할 때, 판단할 때,
|
|
112
111
|
* AI 후보와 비교할 때. 그 변환은 `figure-source.ts` 한 곳에서만 일어난다.
|
|
113
112
|
*
|
|
@@ -623,7 +622,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
623
622
|
/** 편집 중인 Figure. 새로 만드는 중이면 `id` 가 없다. */
|
|
624
623
|
@state() private figure?: Figure
|
|
625
624
|
/** 편집 데이터 — 미리보기에 그대로 세워지는 씬 모델이다. */
|
|
626
|
-
@state() private
|
|
625
|
+
@state() private draft?: FigureDraft
|
|
627
626
|
@state() private parts: PartModel[] = []
|
|
628
627
|
@state() private selected = -1
|
|
629
628
|
/*
|
|
@@ -668,8 +667,8 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
668
667
|
/** 인라인 요청창과 AI 도크가 함께 쓰는 현재 Figure의 제한된 세션 피드백. */
|
|
669
668
|
@state() private proposalFeedback: ProposalFeedback[] = []
|
|
670
669
|
|
|
671
|
-
@state() private undoStack: {
|
|
672
|
-
@state() private redoStack: {
|
|
670
|
+
@state() private undoStack: { draft?: FigureDraft; parts: PartModel[]; selected: number }[] = []
|
|
671
|
+
@state() private redoStack: { draft?: FigureDraft; parts: PartModel[]; selected: number }[] = []
|
|
673
672
|
@state() private sceneRevision = 0
|
|
674
673
|
private lastCoalesceKey?: string
|
|
675
674
|
private lastHistoryTime = 0
|
|
@@ -683,15 +682,15 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
683
682
|
private folded?: FigureSource
|
|
684
683
|
|
|
685
684
|
willUpdate(changed: Map<string, unknown>) {
|
|
686
|
-
if (changed.has('
|
|
687
|
-
this.folded = this.
|
|
685
|
+
if (changed.has('draft') || changed.has('parts')) {
|
|
686
|
+
this.folded = this.draft ? toFigureSource(this.draft, this.parts) : undefined
|
|
688
687
|
}
|
|
689
688
|
}
|
|
690
689
|
|
|
691
690
|
/**
|
|
692
691
|
* 페이지 밖 AI 도크에도 **지금 손에 든 정본**을 알려 준다.
|
|
693
692
|
*
|
|
694
|
-
*
|
|
693
|
+
* asidebar 도크는 주소의 id만
|
|
695
694
|
* 알았으므로 DB에 저장된 옛 판을 읽었다. 저장 전 변경을 두고 "왜 발행이 안 되나" 또는
|
|
696
695
|
* "이 부품을 줄여 달라"고 물으면 대화가 엉뚱한 판을 보게 되는 결함이었다.
|
|
697
696
|
*
|
|
@@ -703,7 +702,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
703
702
|
if (changed.has('figure') && (changed.get('figure') as Figure | undefined)?.id !== this.figure?.id) {
|
|
704
703
|
this.proposalFeedback = []
|
|
705
704
|
}
|
|
706
|
-
if (changed.has('
|
|
705
|
+
if (changed.has('draft') || changed.has('parts') || changed.has('figure')) {
|
|
707
706
|
window.dispatchEvent(
|
|
708
707
|
new CustomEvent('figure-ai-context', {
|
|
709
708
|
detail: { figureId: this.figure?.id, source: this.folded }
|
|
@@ -753,15 +752,15 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
753
752
|
/**
|
|
754
753
|
* A candidate from the dock opens the review lane. It does not become the draft.
|
|
755
754
|
*
|
|
756
|
-
* This used to write `
|
|
755
|
+
* This used to write `draft` and `parts` straight onto the working draft and then set
|
|
757
756
|
* `proposalSession = undefined` — the dock's candidate overwrote what the author had open and
|
|
758
757
|
* erased the lane built to review it. There was no moment where the person said yes, and a
|
|
759
758
|
* draft has no version history to go back to, so the previous work was simply gone.
|
|
760
759
|
*
|
|
761
760
|
* `ai-client-base`'s analysis contract states this as a literal type: `approvalRequired: true`,
|
|
762
761
|
* and of `proposal`, "It is never an instruction to mutate canonical state." The in-page
|
|
763
|
-
*
|
|
764
|
-
* the same door. Applying is what the take button does, from the lane, per change.
|
|
762
|
+
* The in-page entrance honoured this through `openAiProposal` before it was removed, and the dock
|
|
763
|
+
* entrance is now the same door. Applying is what the take button does, from the lane, per change.
|
|
765
764
|
*
|
|
766
765
|
* ## Why the overwrite was there — so it does not come back
|
|
767
766
|
*
|
|
@@ -899,13 +898,13 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
899
898
|
@toggle-part-hidden=${(e: CustomEvent) => this.toggleHidden(e.detail.index)}
|
|
900
899
|
@set-all-parts-hidden=${(e: CustomEvent) => this.setAllHidden(e.detail.hidden)}
|
|
901
900
|
@change-detail-level=${(e: CustomEvent) => this.changeDetailLevel(e.detail.level)}
|
|
902
|
-
@update-
|
|
901
|
+
@update-draft=${(e: CustomEvent) => this.updateDraft(e.detail.draft)}
|
|
903
902
|
@part-tool=${(e: CustomEvent) => this.runTool(e.detail.index, e.detail.tool, e.detail.axis)}
|
|
904
903
|
@pick-part=${(e: CustomEvent) => this.pickPart(e.detail.name)}
|
|
905
904
|
>
|
|
906
905
|
<figure-parts
|
|
907
906
|
.parts=${this.parts}
|
|
908
|
-
.level=${(this.
|
|
907
|
+
.level=${(this.draft?.detailLevel ?? 'M') as DetailLevel}
|
|
909
908
|
.selected=${this.selected}
|
|
910
909
|
></figure-parts>
|
|
911
910
|
|
|
@@ -919,7 +918,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
919
918
|
<h4>${i18next.t('figure.label.now')}</h4>
|
|
920
919
|
<figure-canvas
|
|
921
920
|
.figureId=${this.figure?.id || this.figure?.type}
|
|
922
|
-
.
|
|
921
|
+
.draft=${this.draft}
|
|
923
922
|
.parts=${this.parts}
|
|
924
923
|
.picked=${this.pickedName}
|
|
925
924
|
.view=${this.view}
|
|
@@ -942,10 +941,10 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
942
941
|
</div>
|
|
943
942
|
`
|
|
944
943
|
: this.mode === 'preview'
|
|
945
|
-
? html`<figure-preview .source=${source}></figure-preview>`
|
|
944
|
+
? html`<figure-preview .source=${source} .view=${this.view}></figure-preview>`
|
|
946
945
|
: html`<figure-canvas
|
|
947
946
|
.figureId=${this.figure?.id || this.figure?.type}
|
|
948
|
-
.
|
|
947
|
+
.draft=${this.draft}
|
|
949
948
|
.parts=${this.parts}
|
|
950
949
|
.picked=${this.pickedName}
|
|
951
950
|
.view=${this.view}
|
|
@@ -961,7 +960,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
961
960
|
.figureState=${this.figure?.state}
|
|
962
961
|
.figureVersion=${this.figure?.version ?? 0}
|
|
963
962
|
.dirty=${this.dirty}
|
|
964
|
-
.
|
|
963
|
+
.draft=${this.draft}
|
|
965
964
|
.parts=${this.parts}
|
|
966
965
|
.source=${source}
|
|
967
966
|
.selected=${this.selected}
|
|
@@ -1085,7 +1084,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1085
1084
|
private renderProposed() {
|
|
1086
1085
|
if (!this.proposalSession) return ''
|
|
1087
1086
|
|
|
1088
|
-
return html`<figure-preview .source=${this.proposalSession.source}></figure-preview>`
|
|
1087
|
+
return html`<figure-preview .source=${this.proposalSession.source} .view=${this.view}></figure-preview>`
|
|
1089
1088
|
}
|
|
1090
1089
|
|
|
1091
1090
|
/**
|
|
@@ -1171,15 +1170,22 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1171
1170
|
|
|
1172
1171
|
/** 변경 하나. 무엇을 하겠다는 것인지 부호로 먼저 말한다. */
|
|
1173
1172
|
private renderChange(change: proposals.ProposalChange) {
|
|
1174
|
-
|
|
1173
|
+
/*
|
|
1174
|
+
Every ChangeKind needs a row here. A kind the map does not answer draws an empty icon and a
|
|
1175
|
+
key with no name beside it, which reads as a broken row rather than a missing case — the
|
|
1176
|
+
`satisfies` makes the compiler say so instead.
|
|
1177
|
+
*/
|
|
1178
|
+
const icon = ({
|
|
1175
1179
|
added: 'add',
|
|
1176
1180
|
changed: 'edit',
|
|
1177
1181
|
removed: 'remove',
|
|
1178
1182
|
base: 'aspect_ratio',
|
|
1183
|
+
placement: 'vertical_align_bottom',
|
|
1179
1184
|
detail: 'tune',
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1185
|
+
styleKit: 'palette',
|
|
1186
|
+
animations: 'animation',
|
|
1187
|
+
capabilities: 'hub'
|
|
1188
|
+
} satisfies Record<proposals.ChangeKind, string>)[change.kind]
|
|
1183
1189
|
|
|
1184
1190
|
const on = this.proposalSession?.picked.has(change.key)
|
|
1185
1191
|
|
|
@@ -1214,7 +1220,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1214
1220
|
/**
|
|
1215
1221
|
* 발행 게이트의 구조화된 수정안 하나를 현재 작업 사본에만 겹쳐 본다. 서버가 준 값은
|
|
1216
1222
|
* `part.anchor.axis = to`라는 데이터이지 사람이 읽을 문장을 다시 해석한 결과가 아니다.
|
|
1217
|
-
* 원본 `
|
|
1223
|
+
* 원본 `draft`/`parts`는 여기서 전혀 바꾸지 않고, 아래의 `take()`만 바꾼 사본을 적용한다.
|
|
1218
1224
|
*/
|
|
1219
1225
|
private previewSizingFix(fix: FigureGateFix) {
|
|
1220
1226
|
if (!this.folded) return
|
|
@@ -1245,9 +1251,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1245
1251
|
|
|
1246
1252
|
this.recordHistory()
|
|
1247
1253
|
const taken = proposals.applyProposal(this.folded, this.proposalSession.source, this.proposalSession.picked)
|
|
1248
|
-
const {
|
|
1254
|
+
const { draft, parts } = fromFigureSource(taken)
|
|
1249
1255
|
|
|
1250
|
-
this.
|
|
1256
|
+
this.draft = draft
|
|
1251
1257
|
this.parts = parts
|
|
1252
1258
|
this.selected = parts.length ? 0 : -1
|
|
1253
1259
|
this.dirty = true
|
|
@@ -1398,7 +1404,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1398
1404
|
// 서버가 준 것을 그대로 쓴다. 읽지 못하면 오류보고없이 빈 상태로 바꾸지 않는다.
|
|
1399
1405
|
// 그러면 사용자가 저장을 선택해 원본을 덮어쓴다.
|
|
1400
1406
|
const model = figure.source ? fromFigureSource(JSON.parse(figure.source) as FigureSource) : undefined
|
|
1401
|
-
this.
|
|
1407
|
+
this.draft = model?.draft
|
|
1402
1408
|
this.parts = model?.parts ?? []
|
|
1403
1409
|
|
|
1404
1410
|
/*
|
|
@@ -1428,10 +1434,10 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1428
1434
|
this.mode = 'edit'
|
|
1429
1435
|
// 타입 이름을 사람이 정하기 전까지는 임시 이름을 쓴다. 저장할 때 확정한다.
|
|
1430
1436
|
const draftType = `FIGURE_${Date.now().toString(36).toUpperCase()}`
|
|
1431
|
-
const {
|
|
1437
|
+
const { draft, parts } = startingModel(draftType)
|
|
1432
1438
|
|
|
1433
1439
|
this.figure = { id: '', type: draftType, name: '' } as Figure
|
|
1434
|
-
this.
|
|
1440
|
+
this.draft = draft
|
|
1435
1441
|
this.parts = parts
|
|
1436
1442
|
/* 고른 것이 없다 — 부품이 없으니 속성 패널도 고를 것을 기다린다. */
|
|
1437
1443
|
this.selected = -1
|
|
@@ -1452,9 +1458,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1452
1458
|
private typeChanged(type: string) {
|
|
1453
1459
|
if (!type || this.figure?.id) return
|
|
1454
1460
|
const sanitized = type.replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()
|
|
1455
|
-
if (this.
|
|
1461
|
+
if (this.draft) this.draft.figureType = sanitized
|
|
1456
1462
|
this.figure = { ...this.figure, type: sanitized } as Figure
|
|
1457
|
-
this.folded = this.
|
|
1463
|
+
this.folded = this.draft ? toFigureSource(this.draft, this.parts) : undefined
|
|
1458
1464
|
this.dirty = true
|
|
1459
1465
|
}
|
|
1460
1466
|
|
|
@@ -1471,9 +1477,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1471
1477
|
this.dirty = true
|
|
1472
1478
|
}
|
|
1473
1479
|
|
|
1474
|
-
private snapshotState(): {
|
|
1480
|
+
private snapshotState(): { draft?: FigureDraft; parts: PartModel[]; selected: number } {
|
|
1475
1481
|
return {
|
|
1476
|
-
|
|
1482
|
+
draft: this.draft ? structuredClone(this.draft) : undefined,
|
|
1477
1483
|
parts: structuredClone(this.parts),
|
|
1478
1484
|
selected: this.selected
|
|
1479
1485
|
}
|
|
@@ -1499,7 +1505,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1499
1505
|
this.undoStack = this.undoStack.slice(0, -1)
|
|
1500
1506
|
this.redoStack = [this.snapshotState(), ...this.redoStack.slice(0, 49)]
|
|
1501
1507
|
|
|
1502
|
-
this.
|
|
1508
|
+
this.draft = prev.draft ? structuredClone(prev.draft) : undefined
|
|
1503
1509
|
this.parts = structuredClone(prev.parts)
|
|
1504
1510
|
this.selected = this.parts.length ? Math.max(-1, Math.min(prev.selected, this.parts.length - 1)) : -1
|
|
1505
1511
|
this.dirty = true
|
|
@@ -1512,7 +1518,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1512
1518
|
this.redoStack = this.redoStack.slice(1)
|
|
1513
1519
|
this.undoStack = [...this.undoStack.slice(-49), this.snapshotState()]
|
|
1514
1520
|
|
|
1515
|
-
this.
|
|
1521
|
+
this.draft = next.draft ? structuredClone(next.draft) : undefined
|
|
1516
1522
|
this.parts = structuredClone(next.parts)
|
|
1517
1523
|
this.selected = this.parts.length ? Math.max(-1, Math.min(next.selected, this.parts.length - 1)) : -1
|
|
1518
1524
|
this.dirty = true
|
|
@@ -1533,9 +1539,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1533
1539
|
}
|
|
1534
1540
|
|
|
1535
1541
|
private addPart(primitive: PrimitiveKind) {
|
|
1536
|
-
if (!this.
|
|
1542
|
+
if (!this.draft) return
|
|
1537
1543
|
// 추가한 것을 바로 수정할 수 있게 선택 상태로 둔다.
|
|
1538
|
-
this.put(edits.addPart(this.
|
|
1544
|
+
this.put(edits.addPart(this.draft, this.parts, primitive), this.parts.length)
|
|
1539
1545
|
}
|
|
1540
1546
|
|
|
1541
1547
|
private removePart(index: number) {
|
|
@@ -1560,9 +1566,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1560
1566
|
* 부품 목록은 건드리지 않는다. `put` 은 부품을 함께 받으므로 여기서 쓰면 능력 하나를
|
|
1561
1567
|
* 고칠 때마다 부품 목록이 새 배열이 되고, 그리는 쪽이 전부 다시 그린다.
|
|
1562
1568
|
*/
|
|
1563
|
-
private
|
|
1564
|
-
this.recordHistory('
|
|
1565
|
-
this.
|
|
1569
|
+
private updateDraft(draft: FigureDraft) {
|
|
1570
|
+
this.recordHistory('draft')
|
|
1571
|
+
this.draft = draft
|
|
1566
1572
|
this.dirty = true
|
|
1567
1573
|
}
|
|
1568
1574
|
|
|
@@ -1570,17 +1576,17 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1570
1576
|
* 디테일 등급을 바꾼다.
|
|
1571
1577
|
*
|
|
1572
1578
|
* 이 값은 부품 수 한도를 정하고 성숙도의 한 축이 된다. 부품보다 위에 있는 값이라
|
|
1573
|
-
*
|
|
1579
|
+
* 초안에 담는다.
|
|
1574
1580
|
*
|
|
1575
1581
|
* 한도를 내려서 이미 넣은 부품 수보다 작아질 수 있다. 그때 부품을 지우지 않는다.
|
|
1576
1582
|
* 검증이 위반으로 알리고, 성숙도가 등급에 안 맞는다고 말한다. 저작자가 무엇을 뺄지
|
|
1577
1583
|
* 정할 일이지 도구가 정할 일이 아니다.
|
|
1578
1584
|
*/
|
|
1579
1585
|
private changeDetailLevel(level: DetailLevel) {
|
|
1580
|
-
if (!this.
|
|
1586
|
+
if (!this.draft) return
|
|
1581
1587
|
|
|
1582
1588
|
this.recordHistory('detailLevel')
|
|
1583
|
-
this.
|
|
1589
|
+
this.draft = { ...this.draft, detailLevel: level }
|
|
1584
1590
|
this.dirty = true
|
|
1585
1591
|
}
|
|
1586
1592
|
|
|
@@ -1627,14 +1633,14 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1627
1633
|
* 규칙은 `part-edits` 가 갖는다. 여기서는 어느 것을 부를지만 정한다.
|
|
1628
1634
|
*/
|
|
1629
1635
|
private runTool(index: number, tool: string, axis: PlaneAxis) {
|
|
1630
|
-
const
|
|
1631
|
-
if (!
|
|
1636
|
+
const draft = this.draft
|
|
1637
|
+
if (!draft) return
|
|
1632
1638
|
|
|
1633
|
-
if (tool === 'center') this.put(edits.centerPart(
|
|
1639
|
+
if (tool === 'center') this.put(edits.centerPart(draft, this.parts, index, axis))
|
|
1634
1640
|
else if (tool === 'floor') this.put(edits.alignPartToStart(this.parts, index, axis))
|
|
1635
|
-
else if (tool === 'snap') this.put(edits.snapPart(
|
|
1641
|
+
else if (tool === 'snap') this.put(edits.snapPart(draft, this.parts, index))
|
|
1636
1642
|
// 대칭 복제는 부품이 하나 늘어난다 — 새로 생긴 것을 선택 상태로 둔다
|
|
1637
|
-
else if (tool === 'mirror') this.put(edits.mirrorPart(
|
|
1643
|
+
else if (tool === 'mirror') this.put(edits.mirrorPart(draft, this.parts, index, axis), index + 1)
|
|
1638
1644
|
}
|
|
1639
1645
|
|
|
1640
1646
|
/**
|
|
@@ -1689,7 +1695,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1689
1695
|
private async fillThumbnail(id: string): Promise<void> {
|
|
1690
1696
|
await this.updateComplete
|
|
1691
1697
|
|
|
1692
|
-
const thumbnail = await captureThumbnail(this.
|
|
1698
|
+
const thumbnail = await captureThumbnail(this.draft, this.parts)
|
|
1693
1699
|
if (!thumbnail || this.figure?.id !== id) {
|
|
1694
1700
|
return
|
|
1695
1701
|
}
|
|
@@ -1711,7 +1717,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1711
1717
|
|
|
1712
1718
|
if (!this.figure.id) {
|
|
1713
1719
|
// 신규 저장: 타입 코드와 표시 이름을 명확히 팝업으로 확인/입력받음
|
|
1714
|
-
let defaultType = this.
|
|
1720
|
+
let defaultType = this.draft?.figureType || this.figure.type || ''
|
|
1715
1721
|
if (!defaultType || defaultType.startsWith('FIGURE_')) {
|
|
1716
1722
|
defaultType = 'FIGURE'
|
|
1717
1723
|
}
|
|
@@ -1734,7 +1740,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1734
1740
|
|
|
1735
1741
|
try {
|
|
1736
1742
|
if (!this.figure.id) {
|
|
1737
|
-
const typeToSave = (this.saveModalType || this.
|
|
1743
|
+
const typeToSave = (this.saveModalType || this.draft?.figureType || this.figure.type || 'FIGURE').trim().toUpperCase()
|
|
1738
1744
|
const nameToSave = (this.saveModalName || this.figure.name || typeToSave).trim()
|
|
1739
1745
|
|
|
1740
1746
|
if (!typeToSave) {
|
|
@@ -1746,11 +1752,11 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1746
1752
|
throw new Error(i18next.t('figure.text.type-name-already-taken', { type: typeToSave }))
|
|
1747
1753
|
}
|
|
1748
1754
|
|
|
1749
|
-
if (this.
|
|
1755
|
+
if (this.draft) this.draft.figureType = typeToSave
|
|
1750
1756
|
this.figure = { ...this.figure, type: typeToSave, name: nameToSave }
|
|
1751
1757
|
this.folded = { ...this.folded, type: typeToSave }
|
|
1752
1758
|
const sourceJson = JSON.stringify(this.folded)
|
|
1753
|
-
const thumbnail = await captureThumbnail(this.
|
|
1759
|
+
const thumbnail = await captureThumbnail(this.draft, this.parts)
|
|
1754
1760
|
|
|
1755
1761
|
const result = await createFigure({
|
|
1756
1762
|
type: typeToSave,
|
|
@@ -1772,10 +1778,10 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1772
1778
|
// 기존 모델 업데이트: folded.type이 figure.type과 일치하도록 보장
|
|
1773
1779
|
if (this.figure.type && this.folded.type !== this.figure.type) {
|
|
1774
1780
|
this.folded = { ...this.folded, type: this.figure.type }
|
|
1775
|
-
if (this.
|
|
1781
|
+
if (this.draft) this.draft.figureType = this.figure.type
|
|
1776
1782
|
}
|
|
1777
1783
|
const sourceJson = JSON.stringify(this.folded)
|
|
1778
|
-
const thumbnail = await captureThumbnail(this.
|
|
1784
|
+
const thumbnail = await captureThumbnail(this.draft, this.parts)
|
|
1779
1785
|
|
|
1780
1786
|
const result = await updateFigure(this.figure.id, {
|
|
1781
1787
|
name: this.figure.name,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { LitElement, type TemplateResult } from 'lit';
|
|
2
|
-
import type {
|
|
2
|
+
import type { FigureDraft, PartModel } from './figure-source.js';
|
|
3
3
|
declare const FigureAnimations_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
4
4
|
export declare class FigureAnimations extends FigureAnimations_base {
|
|
5
5
|
static styles: import("lit").CSSResult[];
|
|
6
|
-
|
|
6
|
+
draft?: FigureDraft;
|
|
7
7
|
parts: PartModel[];
|
|
8
8
|
/** 펼쳐 둔 clip. 이름이 아니라 자리로 잡는다 — 이름은 편집 중에 바뀐다. */
|
|
9
9
|
private open;
|
|
@@ -62,9 +62,9 @@ export declare class FigureAnimations extends FigureAnimations_base {
|
|
|
62
62
|
*/
|
|
63
63
|
private renderBudget;
|
|
64
64
|
/**
|
|
65
|
-
*
|
|
65
|
+
* 초안에 실어 올려보낸다.
|
|
66
66
|
*
|
|
67
|
-
* `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-
|
|
67
|
+
* `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-draft` 로
|
|
68
68
|
* 간다. 빈 배열은 칸을 지운다 — clip 을 다 지운 도형은 `animations: []` 가 아니라 없는 것이다.
|
|
69
69
|
*/
|
|
70
70
|
private tell;
|
|
@@ -246,7 +246,7 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
246
246
|
`
|
|
247
247
|
]; }
|
|
248
248
|
get clips() {
|
|
249
|
-
return this.
|
|
249
|
+
return this.draft?.animations ?? [];
|
|
250
250
|
}
|
|
251
251
|
/** 부품 이름 — 채널이 이것을 가리킨다. */
|
|
252
252
|
get partNames() {
|
|
@@ -266,7 +266,7 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
266
266
|
return moving.size;
|
|
267
267
|
}
|
|
268
268
|
render() {
|
|
269
|
-
if (!this.
|
|
269
|
+
if (!this.draft)
|
|
270
270
|
return html ``;
|
|
271
271
|
return html `
|
|
272
272
|
<section>
|
|
@@ -550,20 +550,20 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
550
550
|
}
|
|
551
551
|
// ─── 고치기 ───────────────────────────────────────────────────────────────
|
|
552
552
|
/**
|
|
553
|
-
*
|
|
553
|
+
* 초안에 실어 올려보낸다.
|
|
554
554
|
*
|
|
555
|
-
* `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-
|
|
555
|
+
* `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-draft` 로
|
|
556
556
|
* 간다. 빈 배열은 칸을 지운다 — clip 을 다 지운 도형은 `animations: []` 가 아니라 없는 것이다.
|
|
557
557
|
*/
|
|
558
558
|
tell(animations) {
|
|
559
|
-
if (!this.
|
|
559
|
+
if (!this.draft)
|
|
560
560
|
return;
|
|
561
|
-
const
|
|
561
|
+
const draft = { ...this.draft };
|
|
562
562
|
if (animations.length === 0)
|
|
563
|
-
delete
|
|
563
|
+
delete draft.animations;
|
|
564
564
|
else
|
|
565
|
-
|
|
566
|
-
this.dispatchEvent(new CustomEvent('update-
|
|
565
|
+
draft.animations = animations;
|
|
566
|
+
this.dispatchEvent(new CustomEvent('update-draft', { detail: { draft }, bubbles: true, composed: true }));
|
|
567
567
|
}
|
|
568
568
|
patchClip(at, patch) {
|
|
569
569
|
this.tell(this.clips.map((clip, i) => (i === at ? { ...clip, ...patch } : clip)));
|
|
@@ -688,7 +688,7 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
688
688
|
__decorate([
|
|
689
689
|
property({ type: Object }),
|
|
690
690
|
__metadata("design:type", Object)
|
|
691
|
-
], FigureAnimations.prototype, "
|
|
691
|
+
], FigureAnimations.prototype, "draft", void 0);
|
|
692
692
|
__decorate([
|
|
693
693
|
property({ type: Array }),
|
|
694
694
|
__metadata("design:type", Array)
|