@things-factory/figure-ui 10.1.16 → 10.1.18
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 +331 -124
- 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 +151 -41
- 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 +62 -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 +27 -4
- package/client/pages/figure-modeller-page.ts +65 -58
- package/dist-client/modeller/figure-animations.d.ts +37 -8
- package/dist-client/modeller/figure-animations.js +293 -122
- 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 +56 -6
- package/dist-client/modeller/figure-preview.js +132 -39
- 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 +25 -15
- package/dist-client/modeller/figure-source.js +42 -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 +16 -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 +61 -54
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/test/ai-proposal-contract.test.ts +433 -12
- package/test/expect.ts +55 -0
- package/test/figure-source.test.ts +572 -0
- package/test/i18n-prefix-guard.test.ts +9 -12
- package/{client/modeller → test}/part-edits.test.ts +50 -45
- package/translations/en.json +46 -35
- package/translations/ja.json +16 -21
- package/translations/ko.json +56 -45
- 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,23 @@ 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
|
+
parameters: 'tune',
|
|
1188
|
+
capabilities: 'hub'
|
|
1189
|
+
} satisfies Record<proposals.ChangeKind, string>)[change.kind]
|
|
1183
1190
|
|
|
1184
1191
|
const on = this.proposalSession?.picked.has(change.key)
|
|
1185
1192
|
|
|
@@ -1214,7 +1221,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1214
1221
|
/**
|
|
1215
1222
|
* 발행 게이트의 구조화된 수정안 하나를 현재 작업 사본에만 겹쳐 본다. 서버가 준 값은
|
|
1216
1223
|
* `part.anchor.axis = to`라는 데이터이지 사람이 읽을 문장을 다시 해석한 결과가 아니다.
|
|
1217
|
-
* 원본 `
|
|
1224
|
+
* 원본 `draft`/`parts`는 여기서 전혀 바꾸지 않고, 아래의 `take()`만 바꾼 사본을 적용한다.
|
|
1218
1225
|
*/
|
|
1219
1226
|
private previewSizingFix(fix: FigureGateFix) {
|
|
1220
1227
|
if (!this.folded) return
|
|
@@ -1245,9 +1252,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1245
1252
|
|
|
1246
1253
|
this.recordHistory()
|
|
1247
1254
|
const taken = proposals.applyProposal(this.folded, this.proposalSession.source, this.proposalSession.picked)
|
|
1248
|
-
const {
|
|
1255
|
+
const { draft, parts } = fromFigureSource(taken)
|
|
1249
1256
|
|
|
1250
|
-
this.
|
|
1257
|
+
this.draft = draft
|
|
1251
1258
|
this.parts = parts
|
|
1252
1259
|
this.selected = parts.length ? 0 : -1
|
|
1253
1260
|
this.dirty = true
|
|
@@ -1398,7 +1405,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1398
1405
|
// 서버가 준 것을 그대로 쓴다. 읽지 못하면 오류보고없이 빈 상태로 바꾸지 않는다.
|
|
1399
1406
|
// 그러면 사용자가 저장을 선택해 원본을 덮어쓴다.
|
|
1400
1407
|
const model = figure.source ? fromFigureSource(JSON.parse(figure.source) as FigureSource) : undefined
|
|
1401
|
-
this.
|
|
1408
|
+
this.draft = model?.draft
|
|
1402
1409
|
this.parts = model?.parts ?? []
|
|
1403
1410
|
|
|
1404
1411
|
/*
|
|
@@ -1428,10 +1435,10 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1428
1435
|
this.mode = 'edit'
|
|
1429
1436
|
// 타입 이름을 사람이 정하기 전까지는 임시 이름을 쓴다. 저장할 때 확정한다.
|
|
1430
1437
|
const draftType = `FIGURE_${Date.now().toString(36).toUpperCase()}`
|
|
1431
|
-
const {
|
|
1438
|
+
const { draft, parts } = startingModel(draftType)
|
|
1432
1439
|
|
|
1433
1440
|
this.figure = { id: '', type: draftType, name: '' } as Figure
|
|
1434
|
-
this.
|
|
1441
|
+
this.draft = draft
|
|
1435
1442
|
this.parts = parts
|
|
1436
1443
|
/* 고른 것이 없다 — 부품이 없으니 속성 패널도 고를 것을 기다린다. */
|
|
1437
1444
|
this.selected = -1
|
|
@@ -1452,9 +1459,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1452
1459
|
private typeChanged(type: string) {
|
|
1453
1460
|
if (!type || this.figure?.id) return
|
|
1454
1461
|
const sanitized = type.replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()
|
|
1455
|
-
if (this.
|
|
1462
|
+
if (this.draft) this.draft.figureType = sanitized
|
|
1456
1463
|
this.figure = { ...this.figure, type: sanitized } as Figure
|
|
1457
|
-
this.folded = this.
|
|
1464
|
+
this.folded = this.draft ? toFigureSource(this.draft, this.parts) : undefined
|
|
1458
1465
|
this.dirty = true
|
|
1459
1466
|
}
|
|
1460
1467
|
|
|
@@ -1471,9 +1478,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1471
1478
|
this.dirty = true
|
|
1472
1479
|
}
|
|
1473
1480
|
|
|
1474
|
-
private snapshotState(): {
|
|
1481
|
+
private snapshotState(): { draft?: FigureDraft; parts: PartModel[]; selected: number } {
|
|
1475
1482
|
return {
|
|
1476
|
-
|
|
1483
|
+
draft: this.draft ? structuredClone(this.draft) : undefined,
|
|
1477
1484
|
parts: structuredClone(this.parts),
|
|
1478
1485
|
selected: this.selected
|
|
1479
1486
|
}
|
|
@@ -1499,7 +1506,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1499
1506
|
this.undoStack = this.undoStack.slice(0, -1)
|
|
1500
1507
|
this.redoStack = [this.snapshotState(), ...this.redoStack.slice(0, 49)]
|
|
1501
1508
|
|
|
1502
|
-
this.
|
|
1509
|
+
this.draft = prev.draft ? structuredClone(prev.draft) : undefined
|
|
1503
1510
|
this.parts = structuredClone(prev.parts)
|
|
1504
1511
|
this.selected = this.parts.length ? Math.max(-1, Math.min(prev.selected, this.parts.length - 1)) : -1
|
|
1505
1512
|
this.dirty = true
|
|
@@ -1512,7 +1519,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1512
1519
|
this.redoStack = this.redoStack.slice(1)
|
|
1513
1520
|
this.undoStack = [...this.undoStack.slice(-49), this.snapshotState()]
|
|
1514
1521
|
|
|
1515
|
-
this.
|
|
1522
|
+
this.draft = next.draft ? structuredClone(next.draft) : undefined
|
|
1516
1523
|
this.parts = structuredClone(next.parts)
|
|
1517
1524
|
this.selected = this.parts.length ? Math.max(-1, Math.min(next.selected, this.parts.length - 1)) : -1
|
|
1518
1525
|
this.dirty = true
|
|
@@ -1533,9 +1540,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1533
1540
|
}
|
|
1534
1541
|
|
|
1535
1542
|
private addPart(primitive: PrimitiveKind) {
|
|
1536
|
-
if (!this.
|
|
1543
|
+
if (!this.draft) return
|
|
1537
1544
|
// 추가한 것을 바로 수정할 수 있게 선택 상태로 둔다.
|
|
1538
|
-
this.put(edits.addPart(this.
|
|
1545
|
+
this.put(edits.addPart(this.draft, this.parts, primitive), this.parts.length)
|
|
1539
1546
|
}
|
|
1540
1547
|
|
|
1541
1548
|
private removePart(index: number) {
|
|
@@ -1560,9 +1567,9 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1560
1567
|
* 부품 목록은 건드리지 않는다. `put` 은 부품을 함께 받으므로 여기서 쓰면 능력 하나를
|
|
1561
1568
|
* 고칠 때마다 부품 목록이 새 배열이 되고, 그리는 쪽이 전부 다시 그린다.
|
|
1562
1569
|
*/
|
|
1563
|
-
private
|
|
1564
|
-
this.recordHistory('
|
|
1565
|
-
this.
|
|
1570
|
+
private updateDraft(draft: FigureDraft) {
|
|
1571
|
+
this.recordHistory('draft')
|
|
1572
|
+
this.draft = draft
|
|
1566
1573
|
this.dirty = true
|
|
1567
1574
|
}
|
|
1568
1575
|
|
|
@@ -1570,17 +1577,17 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1570
1577
|
* 디테일 등급을 바꾼다.
|
|
1571
1578
|
*
|
|
1572
1579
|
* 이 값은 부품 수 한도를 정하고 성숙도의 한 축이 된다. 부품보다 위에 있는 값이라
|
|
1573
|
-
*
|
|
1580
|
+
* 초안에 담는다.
|
|
1574
1581
|
*
|
|
1575
1582
|
* 한도를 내려서 이미 넣은 부품 수보다 작아질 수 있다. 그때 부품을 지우지 않는다.
|
|
1576
1583
|
* 검증이 위반으로 알리고, 성숙도가 등급에 안 맞는다고 말한다. 저작자가 무엇을 뺄지
|
|
1577
1584
|
* 정할 일이지 도구가 정할 일이 아니다.
|
|
1578
1585
|
*/
|
|
1579
1586
|
private changeDetailLevel(level: DetailLevel) {
|
|
1580
|
-
if (!this.
|
|
1587
|
+
if (!this.draft) return
|
|
1581
1588
|
|
|
1582
1589
|
this.recordHistory('detailLevel')
|
|
1583
|
-
this.
|
|
1590
|
+
this.draft = { ...this.draft, detailLevel: level }
|
|
1584
1591
|
this.dirty = true
|
|
1585
1592
|
}
|
|
1586
1593
|
|
|
@@ -1627,14 +1634,14 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1627
1634
|
* 규칙은 `part-edits` 가 갖는다. 여기서는 어느 것을 부를지만 정한다.
|
|
1628
1635
|
*/
|
|
1629
1636
|
private runTool(index: number, tool: string, axis: PlaneAxis) {
|
|
1630
|
-
const
|
|
1631
|
-
if (!
|
|
1637
|
+
const draft = this.draft
|
|
1638
|
+
if (!draft) return
|
|
1632
1639
|
|
|
1633
|
-
if (tool === 'center') this.put(edits.centerPart(
|
|
1640
|
+
if (tool === 'center') this.put(edits.centerPart(draft, this.parts, index, axis))
|
|
1634
1641
|
else if (tool === 'floor') this.put(edits.alignPartToStart(this.parts, index, axis))
|
|
1635
|
-
else if (tool === 'snap') this.put(edits.snapPart(
|
|
1642
|
+
else if (tool === 'snap') this.put(edits.snapPart(draft, this.parts, index))
|
|
1636
1643
|
// 대칭 복제는 부품이 하나 늘어난다 — 새로 생긴 것을 선택 상태로 둔다
|
|
1637
|
-
else if (tool === 'mirror') this.put(edits.mirrorPart(
|
|
1644
|
+
else if (tool === 'mirror') this.put(edits.mirrorPart(draft, this.parts, index, axis), index + 1)
|
|
1638
1645
|
}
|
|
1639
1646
|
|
|
1640
1647
|
/**
|
|
@@ -1689,7 +1696,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1689
1696
|
private async fillThumbnail(id: string): Promise<void> {
|
|
1690
1697
|
await this.updateComplete
|
|
1691
1698
|
|
|
1692
|
-
const thumbnail = await captureThumbnail(this.
|
|
1699
|
+
const thumbnail = await captureThumbnail(this.draft, this.parts)
|
|
1693
1700
|
if (!thumbnail || this.figure?.id !== id) {
|
|
1694
1701
|
return
|
|
1695
1702
|
}
|
|
@@ -1711,7 +1718,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1711
1718
|
|
|
1712
1719
|
if (!this.figure.id) {
|
|
1713
1720
|
// 신규 저장: 타입 코드와 표시 이름을 명확히 팝업으로 확인/입력받음
|
|
1714
|
-
let defaultType = this.
|
|
1721
|
+
let defaultType = this.draft?.figureType || this.figure.type || ''
|
|
1715
1722
|
if (!defaultType || defaultType.startsWith('FIGURE_')) {
|
|
1716
1723
|
defaultType = 'FIGURE'
|
|
1717
1724
|
}
|
|
@@ -1734,7 +1741,7 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1734
1741
|
|
|
1735
1742
|
try {
|
|
1736
1743
|
if (!this.figure.id) {
|
|
1737
|
-
const typeToSave = (this.saveModalType || this.
|
|
1744
|
+
const typeToSave = (this.saveModalType || this.draft?.figureType || this.figure.type || 'FIGURE').trim().toUpperCase()
|
|
1738
1745
|
const nameToSave = (this.saveModalName || this.figure.name || typeToSave).trim()
|
|
1739
1746
|
|
|
1740
1747
|
if (!typeToSave) {
|
|
@@ -1746,11 +1753,11 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1746
1753
|
throw new Error(i18next.t('figure.text.type-name-already-taken', { type: typeToSave }))
|
|
1747
1754
|
}
|
|
1748
1755
|
|
|
1749
|
-
if (this.
|
|
1756
|
+
if (this.draft) this.draft.figureType = typeToSave
|
|
1750
1757
|
this.figure = { ...this.figure, type: typeToSave, name: nameToSave }
|
|
1751
1758
|
this.folded = { ...this.folded, type: typeToSave }
|
|
1752
1759
|
const sourceJson = JSON.stringify(this.folded)
|
|
1753
|
-
const thumbnail = await captureThumbnail(this.
|
|
1760
|
+
const thumbnail = await captureThumbnail(this.draft, this.parts)
|
|
1754
1761
|
|
|
1755
1762
|
const result = await createFigure({
|
|
1756
1763
|
type: typeToSave,
|
|
@@ -1772,10 +1779,10 @@ export class FigureModellerPage extends FigureModellerPageBase {
|
|
|
1772
1779
|
// 기존 모델 업데이트: folded.type이 figure.type과 일치하도록 보장
|
|
1773
1780
|
if (this.figure.type && this.folded.type !== this.figure.type) {
|
|
1774
1781
|
this.folded = { ...this.folded, type: this.figure.type }
|
|
1775
|
-
if (this.
|
|
1782
|
+
if (this.draft) this.draft.figureType = this.figure.type
|
|
1776
1783
|
}
|
|
1777
1784
|
const sourceJson = JSON.stringify(this.folded)
|
|
1778
|
-
const thumbnail = await captureThumbnail(this.
|
|
1785
|
+
const thumbnail = await captureThumbnail(this.draft, this.parts)
|
|
1779
1786
|
|
|
1780
1787
|
const result = await updateFigure(this.figure.id, {
|
|
1781
1788
|
name: this.figure.name,
|
|
@@ -1,14 +1,22 @@
|
|
|
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
|
+
/**
|
|
9
|
+
* 펼쳐 둔 것. 이름이 아니라 **목록과 자리**로 잡는다 — 이름은 편집 중에 바뀐다.
|
|
10
|
+
*
|
|
11
|
+
* 목록이 둘이므로 자리 하나로는 모자란다. `clip:0` 과 `parameter:0` 은 다른 것이다.
|
|
12
|
+
*/
|
|
9
13
|
private open;
|
|
10
14
|
private recipeTarget;
|
|
11
15
|
private get clips();
|
|
16
|
+
private get parameters();
|
|
17
|
+
/** 두 목록의 이름을 한자리에서 본다 — 인스턴스가 이름 하나로 값을 준다. */
|
|
18
|
+
private get allNames();
|
|
19
|
+
private channelsOf;
|
|
12
20
|
/** 부품 이름 — 채널이 이것을 가리킨다. */
|
|
13
21
|
private get partNames();
|
|
14
22
|
/**
|
|
@@ -26,8 +34,17 @@ export declare class FigureAnimations extends FigureAnimations_base {
|
|
|
26
34
|
* 그래서 무엇을 만들 수 있는지 예를 들고, 그것이 어떻게 움직이는지(사실이 들어온다)를 적는다.
|
|
27
35
|
*/
|
|
28
36
|
private renderRecipes;
|
|
29
|
-
|
|
37
|
+
/** 이름이 다른 것과 겹치나. 두 목록을 함께 본다 — 인스턴스는 이름 하나로 값을 준다. */
|
|
38
|
+
private taken;
|
|
30
39
|
private renderClip;
|
|
40
|
+
/**
|
|
41
|
+
* 파라미터 하나 — **이름·범위·자세.**
|
|
42
|
+
*
|
|
43
|
+
* 범위가 화면에 있어야 하는 이유는 그것이 **인스턴스와 주고받는 말**이기 때문이다.
|
|
44
|
+
* 곡선은 0~1 이고 인스턴스는 mm 로 준다. 그 사이를 런타임이 맵하는데, 무엇부터 무엇
|
|
45
|
+
* 까지인지 저작자가 정하지 않으면 값을 줄 수가 없다.
|
|
46
|
+
*/
|
|
47
|
+
private renderParameter;
|
|
31
48
|
private renderChannels;
|
|
32
49
|
/**
|
|
33
50
|
* 채널 하나 — 어느 부품의 어느 속성을 움직이나.
|
|
@@ -62,20 +79,32 @@ export declare class FigureAnimations extends FigureAnimations_base {
|
|
|
62
79
|
*/
|
|
63
80
|
private renderBudget;
|
|
64
81
|
/**
|
|
65
|
-
*
|
|
82
|
+
* 초안에 실어 올려보낸다.
|
|
66
83
|
*
|
|
67
|
-
* `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-
|
|
84
|
+
* `animations` 는 도형 전체의 것이므로 부품 편집(`update-part`)이 아니라 `update-draft` 로
|
|
68
85
|
* 간다. 빈 배열은 칸을 지운다 — clip 을 다 지운 도형은 `animations: []` 가 아니라 없는 것이다.
|
|
69
86
|
*/
|
|
70
87
|
private tell;
|
|
71
88
|
private patchClip;
|
|
89
|
+
private patchParameter;
|
|
90
|
+
/** 채널 묶음을 통째로 얹는다. **어느 목록이든 같은 자리에 닿는다.** */
|
|
91
|
+
private setChannels;
|
|
72
92
|
private patchChannel;
|
|
73
93
|
private addClip;
|
|
74
|
-
|
|
94
|
+
private addParameter;
|
|
95
|
+
/**
|
|
96
|
+
* Recipe creates a valid, editable two-pose motion; it never saves or publishes the Figure.
|
|
97
|
+
*
|
|
98
|
+
* 어느 목록으로 가는지는 레시피가 안다 — 도는 롤러는 애니메이션이고 열리는 문은
|
|
99
|
+
* 파라미터다. 저작자에게 그 갈래를 먼저 고르게 하지 않는 이유가 이것이다.
|
|
100
|
+
*/
|
|
75
101
|
private addRecipe;
|
|
76
102
|
private renameClip;
|
|
77
|
-
private
|
|
103
|
+
private renameParameter;
|
|
104
|
+
private setRange;
|
|
105
|
+
private setDefault;
|
|
78
106
|
private removeClip;
|
|
107
|
+
private removeParameter;
|
|
79
108
|
private addChannel;
|
|
80
109
|
private setChannel;
|
|
81
110
|
/**
|