@things-factory/figure-ui 10.1.26 → 10.1.28
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 +23 -0
- package/client/modeller/base-box.ts +104 -0
- package/client/modeller/figure-animations.ts +253 -32
- package/client/modeller/figure-canvas.ts +18 -3
- package/client/modeller/figure-inspector.ts +292 -3
- package/client/modeller/figure-preview.ts +57 -2
- package/client/modeller/figure-report.ts +9 -4
- package/client/modeller/figure-side.ts +9 -1
- package/client/modeller/figure-source.ts +24 -4
- package/client/modeller/figure-view.ts +20 -0
- package/client/modeller/joint-edits.ts +262 -0
- package/client/modeller/joint-gizmo.ts +118 -0
- package/client/modeller/pose-defaults.ts +67 -0
- package/client/modeller/proposal.ts +9 -3
- package/client/modeller/scene-budget.ts +31 -40
- package/client/pages/figure-list-page.ts +4 -9
- package/client/pages/figure-modeller-page.ts +124 -41
- package/dist-client/graphql/index.d.ts +7 -0
- package/dist-client/graphql/index.js +21 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/base-box.d.ts +47 -0
- package/dist-client/modeller/base-box.js +52 -0
- package/dist-client/modeller/base-box.js.map +1 -0
- package/dist-client/modeller/figure-animations.d.ts +29 -0
- package/dist-client/modeller/figure-animations.js +228 -25
- package/dist-client/modeller/figure-animations.js.map +1 -1
- package/dist-client/modeller/figure-canvas.d.ts +9 -0
- package/dist-client/modeller/figure-canvas.js +14 -3
- package/dist-client/modeller/figure-canvas.js.map +1 -1
- package/dist-client/modeller/figure-inspector.d.ts +37 -0
- package/dist-client/modeller/figure-inspector.js +253 -2
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +15 -0
- package/dist-client/modeller/figure-preview.js +48 -2
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-report.d.ts +3 -2
- package/dist-client/modeller/figure-report.js +9 -4
- package/dist-client/modeller/figure-report.js.map +1 -1
- package/dist-client/modeller/figure-side.d.ts +5 -1
- package/dist-client/modeller/figure-side.js +12 -0
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/modeller/figure-source.d.ts +15 -0
- package/dist-client/modeller/figure-source.js +8 -4
- package/dist-client/modeller/figure-source.js.map +1 -1
- package/dist-client/modeller/figure-view.d.ts +16 -0
- package/dist-client/modeller/figure-view.js +15 -0
- package/dist-client/modeller/figure-view.js.map +1 -1
- package/dist-client/modeller/joint-edits.d.ts +49 -0
- package/dist-client/modeller/joint-edits.js +223 -0
- package/dist-client/modeller/joint-edits.js.map +1 -0
- package/dist-client/modeller/joint-gizmo.d.ts +31 -0
- package/dist-client/modeller/joint-gizmo.js +104 -0
- package/dist-client/modeller/joint-gizmo.js.map +1 -0
- package/dist-client/modeller/pose-defaults.d.ts +9 -0
- package/dist-client/modeller/pose-defaults.js +35 -0
- package/dist-client/modeller/pose-defaults.js.map +1 -0
- package/dist-client/modeller/proposal.d.ts +1 -1
- package/dist-client/modeller/proposal.js +8 -3
- package/dist-client/modeller/proposal.js.map +1 -1
- package/dist-client/modeller/scene-budget.d.ts +31 -34
- package/dist-client/modeller/scene-budget.js +28 -37
- package/dist-client/modeller/scene-budget.js.map +1 -1
- package/dist-client/pages/figure-list-page.js +4 -9
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +43 -0
- package/dist-client/pages/figure-modeller-page.js +121 -40
- 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 +3 -3
- package/test/ai-proposal-contract.test.ts +16 -11
- package/test/base-box.test.ts +154 -0
- package/test/figure-source.test.ts +32 -0
- package/test/joint-edits.test.ts +165 -0
- package/test/joint-gizmo.test.ts +174 -0
- package/test/pose-defaults.test.ts +71 -0
- package/test/preview-board-depth.test.ts +27 -0
- package/test/scene-budget.test.ts +53 -0
- package/translations/en.json +37 -2
- package/translations/ja.json +37 -2
- package/translations/ko.json +37 -2
- package/translations/ms.json +37 -2
- package/translations/zh.json +37 -2
|
@@ -5,6 +5,10 @@ import { live } from 'lit/directives/live.js';
|
|
|
5
5
|
import { i18next, localize } from '@operato/i18n';
|
|
6
6
|
import { ScrollbarStyles } from '@operato/styles';
|
|
7
7
|
import { AXES, CHANNEL_PATHS, INTERPOLATIONS, LIMITS } from '@hatiolab/figure-model';
|
|
8
|
+
/** A channel that drives a joint names the joint and carries one number per key (ADR-0066). */
|
|
9
|
+
function isJointChannel(channel) {
|
|
10
|
+
return channel.path === undefined;
|
|
11
|
+
}
|
|
8
12
|
/*
|
|
9
13
|
레시피의 `end` 가 곧 그 범위의 끝 자세다 — 리프트가 100mm 올라가는 곡선이면 범위도
|
|
10
14
|
0~100mm 다. 둘이 어긋나면 저작자가 1200 을 주고도 100 만 올라가는 것을 보게 된다.
|
|
@@ -65,6 +69,16 @@ function freeName(taken, stem) {
|
|
|
65
69
|
if (!used.has(`${stem}-${n}`))
|
|
66
70
|
return `${stem}-${n}`;
|
|
67
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* 파라미터 범위에 적히는 단위 — **권하는 것이지 가두는 것이 아니다.**
|
|
74
|
+
*
|
|
75
|
+
* 관절이 만드는 파라미터가 `deg` 와 `mm` 을 쓰고(회전과 직선), 그리퍼·문 같은 값은 `%` 를 쓴다.
|
|
76
|
+
* 속도는 `mm/s` 와 `rpm`, 시간은 `s` 다. 이 여섯이 지금 쓰이는 전부이고, 목록에 없는 말을 적는
|
|
77
|
+
* 길은 그대로 열려 있다.
|
|
78
|
+
*/
|
|
79
|
+
const PARAMETER_UNITS = ['deg', 'mm', '%', 'mm/s', 'rpm', 's'];
|
|
80
|
+
/** 목록 대신 손으로 적겠다는 표. `select` 의 값으로 쓴다. */
|
|
81
|
+
const UNIT_TYPE_IT = '__type-it';
|
|
68
82
|
let FigureAnimations = class FigureAnimations extends localize(i18next)(LitElement) {
|
|
69
83
|
constructor() {
|
|
70
84
|
super(...arguments);
|
|
@@ -76,6 +90,8 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
76
90
|
*/
|
|
77
91
|
this.open = '';
|
|
78
92
|
this.recipeTarget = '';
|
|
93
|
+
/** 단위를 손으로 적는 중인 파라미터. 목록에 없는 말을 적으려고 연 칸이다. */
|
|
94
|
+
this.typingUnit = new Set();
|
|
79
95
|
}
|
|
80
96
|
static { this.styles = [
|
|
81
97
|
ScrollbarStyles,
|
|
@@ -170,6 +186,21 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
170
186
|
min-width: 3.2em;
|
|
171
187
|
color: var(--md-sys-color-on-surface-variant);
|
|
172
188
|
}
|
|
189
|
+
/*
|
|
190
|
+
칸이 줄어들 줄 알아야 한다. 숫자 입력칸은 제 기본 너비(20자 남짓)를 갖고 있고 flex 는
|
|
191
|
+
기본 크기 아래로는 안 줄이므로, 범위처럼 한 줄에 셋이 서는 줄은 패널 밖으로 밀려
|
|
192
|
+
오른쪽 칸이 잘렸다.
|
|
193
|
+
*/
|
|
194
|
+
div[row] > input {
|
|
195
|
+
flex: 1 1 0;
|
|
196
|
+
min-width: 0;
|
|
197
|
+
}
|
|
198
|
+
/* 단위는 「mm」 정도의 짧은 말이라 숫자 칸보다 좁게 둔다. 고르는 칸도 같은 폭이다. */
|
|
199
|
+
div[row] > input[unit],
|
|
200
|
+
div[row] > select[unit] {
|
|
201
|
+
flex: 0 1 5.5em;
|
|
202
|
+
min-width: 0;
|
|
203
|
+
}
|
|
173
204
|
|
|
174
205
|
/* ── keyframe ─────────────────────────────────────────────────── */
|
|
175
206
|
|
|
@@ -291,6 +322,10 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
291
322
|
get partNames() {
|
|
292
323
|
return this.parts.map(part => part.name).filter(name => !!name);
|
|
293
324
|
}
|
|
325
|
+
/** Joints a channel can drive instead of a part (ADR-0066). */
|
|
326
|
+
get joints() {
|
|
327
|
+
return this.draft?.joints ?? [];
|
|
328
|
+
}
|
|
294
329
|
/**
|
|
295
330
|
* 몇 부품이 움직이나 — 채널이 가리키는 **서로 다른 부품**의 수.
|
|
296
331
|
*
|
|
@@ -460,14 +495,7 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
460
495
|
aria-label="max"
|
|
461
496
|
@change=${(e) => this.setRange(at, { max: Number(e.target.value) || 0 })}
|
|
462
497
|
/>
|
|
463
|
-
|
|
464
|
-
unit
|
|
465
|
-
.value=${live(range.unit ?? '')}
|
|
466
|
-
placeholder="mm"
|
|
467
|
-
aria-label="unit"
|
|
468
|
-
title=${i18next.t('figure.text.unit-is-a-word-not-a-rule')}
|
|
469
|
-
@change=${(e) => this.setRange(at, { unit: e.target.value.trim() })}
|
|
470
|
-
/>
|
|
498
|
+
${this.renderUnit(at, range.unit ?? '')}
|
|
471
499
|
</div>
|
|
472
500
|
${backwards ? html `<p warn>${i18next.t('figure.text.range-needs-room')}</p>` : nothing}
|
|
473
501
|
|
|
@@ -520,23 +548,45 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
520
548
|
* 검증은 저장할 때 말하고 여기서는 **그 자리에서** 보여 준다.
|
|
521
549
|
*/
|
|
522
550
|
renderChannel(kind, channel, at, j) {
|
|
523
|
-
const
|
|
551
|
+
const joint = this.joints.find(one => one.name === channel.target);
|
|
552
|
+
const orphan = !this.partNames.includes(channel.target) && !joint;
|
|
553
|
+
const target = html `
|
|
554
|
+
<div row>
|
|
555
|
+
<label>${i18next.t('figure.label.channel-target')}</label>
|
|
556
|
+
<select
|
|
557
|
+
.value=${live(channel.target ?? '')}
|
|
558
|
+
@change=${(e) => this.retarget(kind, at, j, e.target.value)}
|
|
559
|
+
>
|
|
560
|
+
${orphan ? html `<option value=${channel.target}>${channel.target}</option>` : nothing}
|
|
561
|
+
<optgroup label=${i18next.t('figure.label.channel-target-parts')}>
|
|
562
|
+
${this.partNames.map(name => html `<option value=${name}>${name}</option>`)}
|
|
563
|
+
</optgroup>
|
|
564
|
+
${this.joints.length > 0
|
|
565
|
+
? html `<optgroup label=${i18next.t('figure.label.channel-target-joints')}>
|
|
566
|
+
${this.joints.map(one => html `<option value=${one.name}>${one.name}</option>`)}
|
|
567
|
+
</optgroup>`
|
|
568
|
+
: nothing}
|
|
569
|
+
</select>
|
|
570
|
+
<button plain @click=${() => this.removeChannel(kind, at, j)} title=${i18next.t('figure.button.remove')}>
|
|
571
|
+
<md-icon>close</md-icon>
|
|
572
|
+
</button>
|
|
573
|
+
</div>
|
|
574
|
+
`;
|
|
575
|
+
/* A joint channel has no path or pivot: the joint says what moves. Each key is the joint coordinate. */
|
|
576
|
+
if (isJointChannel(channel)) {
|
|
577
|
+
const unit = joint?.type === 'prismatic' ? 'mm' : 'deg';
|
|
578
|
+
return html `
|
|
579
|
+
<div channel ?orphan=${orphan}>
|
|
580
|
+
${target} ${orphan ? html `<p warn>${i18next.t('figure.text.channel-target-is-gone')}</p>` : nothing}
|
|
581
|
+
<p quiet>${i18next.t('figure.text.joint-channel-value-' + unit)}</p>
|
|
582
|
+
${this.renderJointKeys(kind, channel, at, j, joint?.limits)}
|
|
583
|
+
</div>
|
|
584
|
+
`;
|
|
585
|
+
}
|
|
524
586
|
const turning = channel.path === 'rotation';
|
|
525
587
|
return html `
|
|
526
588
|
<div channel ?orphan=${orphan}>
|
|
527
|
-
|
|
528
|
-
<label>${i18next.t('figure.label.channel-target')}</label>
|
|
529
|
-
<select
|
|
530
|
-
.value=${live(channel.target ?? '')}
|
|
531
|
-
@change=${(e) => this.setChannel(kind, at, j, { target: e.target.value })}
|
|
532
|
-
>
|
|
533
|
-
${orphan ? html `<option value=${channel.target}>${channel.target}</option>` : nothing}
|
|
534
|
-
${this.partNames.map(name => html `<option value=${name}>${name}</option>`)}
|
|
535
|
-
</select>
|
|
536
|
-
<button plain @click=${() => this.removeChannel(kind, at, j)} title=${i18next.t('figure.button.remove')}>
|
|
537
|
-
<md-icon>close</md-icon>
|
|
538
|
-
</button>
|
|
539
|
-
</div>
|
|
589
|
+
${target}
|
|
540
590
|
|
|
541
591
|
${orphan ? html `<p warn>${i18next.t('figure.text.channel-target-is-gone')}</p>` : nothing}
|
|
542
592
|
|
|
@@ -607,6 +657,66 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
607
657
|
* 시각이 오름차순이어야 하는 것도 형식의 규칙이다. 여기서는 어긋난 줄을 붉게 내고 값을
|
|
608
658
|
* 고치지는 않는다 — 저작자가 3 을 1 로 고치던 중일 수 있다.
|
|
609
659
|
*/
|
|
660
|
+
/**
|
|
661
|
+
* Keys of a joint channel: a position and one joint coordinate each. A value outside the joint's limits
|
|
662
|
+
* is marked and left as typed; the format refuses it on save.
|
|
663
|
+
*/
|
|
664
|
+
renderJointKeys(kind, channel, at, j, limits) {
|
|
665
|
+
const keys = channel.keys ?? [];
|
|
666
|
+
const onValues = kind === 'parameter';
|
|
667
|
+
const beyond = (key) => !!limits && (key.value < limits.min || key.value > limits.max);
|
|
668
|
+
return html `
|
|
669
|
+
<table keys>
|
|
670
|
+
<tr>
|
|
671
|
+
<th>${i18next.t(onValues ? 'figure.label.key-at-value' : 'figure.label.key-at')}</th>
|
|
672
|
+
<th>${i18next.t('figure.label.joint-value')}</th>
|
|
673
|
+
<th></th>
|
|
674
|
+
</tr>
|
|
675
|
+
${keys.map((key, k) => {
|
|
676
|
+
const backwards = k > 0 && key.at <= (keys[k - 1].at ?? 0);
|
|
677
|
+
return html `
|
|
678
|
+
<tr>
|
|
679
|
+
<td>
|
|
680
|
+
<input
|
|
681
|
+
type="number"
|
|
682
|
+
step=${onValues ? '0.05' : '0.1'}
|
|
683
|
+
min="0"
|
|
684
|
+
max=${onValues ? '1' : nothing}
|
|
685
|
+
?bad=${backwards || key.at < 0 || (onValues && key.at > 1)}
|
|
686
|
+
.value=${live(String(key.at))}
|
|
687
|
+
@change=${(e) => this.setJointKey(kind, at, j, k, { at: Math.max(0, Number(e.target.value) || 0) })}
|
|
688
|
+
/>
|
|
689
|
+
</td>
|
|
690
|
+
<td>
|
|
691
|
+
<input
|
|
692
|
+
type="number"
|
|
693
|
+
step="1"
|
|
694
|
+
?bad=${beyond(key)}
|
|
695
|
+
.value=${live(String(key.value))}
|
|
696
|
+
title=${beyond(key) ? i18next.t('figure.text.joint-value-beyond-limits') : ''}
|
|
697
|
+
@change=${(e) => this.setJointKey(kind, at, j, k, { value: Number(e.target.value) || 0 })}
|
|
698
|
+
/>
|
|
699
|
+
</td>
|
|
700
|
+
<td drop>
|
|
701
|
+
<button
|
|
702
|
+
plain
|
|
703
|
+
?disabled=${keys.length <= 2}
|
|
704
|
+
title=${keys.length <= 2 ? i18next.t('figure.text.two-keys-at-least') : i18next.t('figure.button.remove')}
|
|
705
|
+
@click=${() => this.removeKey(kind, at, j, k)}
|
|
706
|
+
>
|
|
707
|
+
<md-icon>close</md-icon>
|
|
708
|
+
</button>
|
|
709
|
+
</td>
|
|
710
|
+
</tr>
|
|
711
|
+
`;
|
|
712
|
+
})}
|
|
713
|
+
</table>
|
|
714
|
+
${keys.some(beyond) ? html `<p warn>${i18next.t('figure.text.joint-value-beyond-limits')}</p>` : nothing}
|
|
715
|
+
<button @click=${() => this.addKey(kind, at, j)}>
|
|
716
|
+
<md-icon>add</md-icon>${i18next.t('figure.button.add-key')}
|
|
717
|
+
</button>
|
|
718
|
+
`;
|
|
719
|
+
}
|
|
610
720
|
renderKeys(kind, channel, at, j) {
|
|
611
721
|
const keys = channel.keys ?? [];
|
|
612
722
|
/*
|
|
@@ -797,6 +907,51 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
797
907
|
renameParameter(at, name) {
|
|
798
908
|
this.patchParameter(at, { name: name.trim() });
|
|
799
909
|
}
|
|
910
|
+
/**
|
|
911
|
+
* 단위 칸 — **목록이 보이는 채로, 적는 길도 열어 둔다.**
|
|
912
|
+
*
|
|
913
|
+
* 처음에는 `datalist` 를 붙였다. 그것은 브라우저가 쥐고 있는 기능이라 **눌러 보기 전에는 목록이
|
|
914
|
+
* 있는지조차 안 보인다** — 화살표도 없고, 어디에 어떻게 뜨는지도 브라우저가 정한다. 고를 수
|
|
915
|
+
* 있다는 것을 알리는 것이 이 칸의 목적이므로 `select` 로 바꿨다.
|
|
916
|
+
*
|
|
917
|
+
* 형식은 단위를 규칙으로 쓰지 않으므로(표시용 말이다) 목록에 가두지 않는다. 마지막 줄이
|
|
918
|
+
* 「직접 적기」이고, 목록에 없는 말이 이미 적혀 있으면 처음부터 적는 칸으로 연다.
|
|
919
|
+
*/
|
|
920
|
+
renderUnit(at, unit) {
|
|
921
|
+
const listed = PARAMETER_UNITS.includes(unit);
|
|
922
|
+
const typing = this.typingUnit.has(at) || (unit !== '' && !listed);
|
|
923
|
+
if (typing) {
|
|
924
|
+
return html `<input
|
|
925
|
+
unit
|
|
926
|
+
.value=${live(unit)}
|
|
927
|
+
placeholder="mm"
|
|
928
|
+
aria-label="unit"
|
|
929
|
+
title=${i18next.t('figure.text.unit-is-a-word-not-a-rule')}
|
|
930
|
+
@change=${(e) => this.setRange(at, { unit: e.target.value.trim() })}
|
|
931
|
+
/>`;
|
|
932
|
+
}
|
|
933
|
+
return html `<select
|
|
934
|
+
unit
|
|
935
|
+
aria-label="unit"
|
|
936
|
+
title=${i18next.t('figure.text.unit-is-a-word-not-a-rule')}
|
|
937
|
+
@change=${(e) => this.chooseUnit(at, e.target.value)}
|
|
938
|
+
>
|
|
939
|
+
<option value="" ?selected=${unit === ''}>${i18next.t('figure.label.unit-none')}</option>
|
|
940
|
+
${PARAMETER_UNITS.map(one => html `<option value=${one} ?selected=${one === unit}>${one}</option>`)}
|
|
941
|
+
<option value=${UNIT_TYPE_IT}>${i18next.t('figure.label.unit-type-it')}</option>
|
|
942
|
+
</select>`;
|
|
943
|
+
}
|
|
944
|
+
/** 고른 것을 넣는다. 「직접 적기」는 값이 아니라 **칸을 바꾸는 것**이다. */
|
|
945
|
+
chooseUnit(at, value) {
|
|
946
|
+
if (value === UNIT_TYPE_IT) {
|
|
947
|
+
this.typingUnit = new Set([...this.typingUnit, at]);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
const next = new Set(this.typingUnit);
|
|
951
|
+
next.delete(at);
|
|
952
|
+
this.typingUnit = next;
|
|
953
|
+
this.setRange(at, { unit: value });
|
|
954
|
+
}
|
|
800
955
|
setRange(at, patch) {
|
|
801
956
|
const parameter = this.parameters[at];
|
|
802
957
|
if (!parameter)
|
|
@@ -836,10 +991,36 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
836
991
|
{ target, path: 'translation', keys: keysFor('translation') }
|
|
837
992
|
]);
|
|
838
993
|
}
|
|
839
|
-
|
|
994
|
+
/**
|
|
995
|
+
* Points a channel at another part or joint. Crossing between the two changes the channel's shape: a
|
|
996
|
+
* joint channel has no path and one number per key, a part channel a path and a vector. The key positions
|
|
997
|
+
* are kept, since they are the author's timing; values restart at the rest pose.
|
|
998
|
+
*/
|
|
999
|
+
retarget(kind, at, j, target) {
|
|
840
1000
|
const channel = this.channelsOf(kind, at)[j];
|
|
841
1001
|
if (!channel)
|
|
842
1002
|
return;
|
|
1003
|
+
const toJoint = this.joints.some(one => one.name === target);
|
|
1004
|
+
if (toJoint && !isJointChannel(channel)) {
|
|
1005
|
+
const next = { target, keys: channel.keys.map(key => ({ at: key.at, value: 0 })) };
|
|
1006
|
+
if (channel.interpolation !== undefined)
|
|
1007
|
+
next.interpolation = channel.interpolation;
|
|
1008
|
+
this.patchChannel(kind, at, j, next);
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
if (!toJoint && isJointChannel(channel)) {
|
|
1012
|
+
const next = { target, path: 'translation', keys: channel.keys.map(key => ({ at: key.at, value: startValue('translation') })) };
|
|
1013
|
+
if (channel.interpolation !== undefined)
|
|
1014
|
+
next.interpolation = channel.interpolation;
|
|
1015
|
+
this.patchChannel(kind, at, j, next);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
this.patchChannel(kind, at, j, { ...channel, target });
|
|
1019
|
+
}
|
|
1020
|
+
setChannel(kind, at, j, patch) {
|
|
1021
|
+
const channel = this.channelsOf(kind, at)[j];
|
|
1022
|
+
if (!channel || isJointChannel(channel))
|
|
1023
|
+
return;
|
|
843
1024
|
const next = { ...channel, ...patch };
|
|
844
1025
|
/* `undefined` 를 얹는 것은 지우는 뜻이다. 남겨 두면 저장 형식에 빈 칸이 실린다. */
|
|
845
1026
|
if ('pivot' in patch && patch.pivot === undefined)
|
|
@@ -855,7 +1036,7 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
855
1036
|
*/
|
|
856
1037
|
setPath(kind, at, j, path) {
|
|
857
1038
|
const channel = this.channelsOf(kind, at)[j];
|
|
858
|
-
if (!channel)
|
|
1039
|
+
if (!channel || isJointChannel(channel))
|
|
859
1040
|
return;
|
|
860
1041
|
const crossing = (channel.path === 'scale') !== (path === 'scale');
|
|
861
1042
|
const next = {
|
|
@@ -876,6 +1057,18 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
876
1057
|
const channel = this.channelsOf(kind, at)[j];
|
|
877
1058
|
if (!channel)
|
|
878
1059
|
return;
|
|
1060
|
+
if (isJointChannel(channel)) {
|
|
1061
|
+
const keys = channel.keys;
|
|
1062
|
+
const end = keys[keys.length - 1];
|
|
1063
|
+
if (kind === 'parameter' && keys.length >= 2) {
|
|
1064
|
+
const before = keys[keys.length - 2];
|
|
1065
|
+
const middle = { at: (before.at + end.at) / 2, value: before.value };
|
|
1066
|
+
this.patchChannel(kind, at, j, { ...channel, keys: [...keys.slice(0, -1), middle, end] });
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
this.patchChannel(kind, at, j, { ...channel, keys: [...keys, { at: (end?.at ?? 0) + 1, value: end?.value ?? 0 }] });
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
879
1072
|
const last = channel.keys[channel.keys.length - 1];
|
|
880
1073
|
/*
|
|
881
1074
|
A parameter curve ends at 1, so appending a key would make it unreachable. Insert it between the last two keys.
|
|
@@ -889,9 +1082,15 @@ let FigureAnimations = class FigureAnimations extends localize(i18next)(LitEleme
|
|
|
889
1082
|
const key = { at: (last?.at ?? 0) + 1, value: { ...(last?.value ?? startValue(channel.path)) } };
|
|
890
1083
|
this.patchChannel(kind, at, j, { ...channel, keys: [...channel.keys, key] });
|
|
891
1084
|
}
|
|
1085
|
+
setJointKey(kind, at, j, k, patch) {
|
|
1086
|
+
const channel = this.channelsOf(kind, at)[j];
|
|
1087
|
+
if (!channel || !isJointChannel(channel))
|
|
1088
|
+
return;
|
|
1089
|
+
this.patchChannel(kind, at, j, { ...channel, keys: channel.keys.map((key, i) => (i === k ? { ...key, ...patch } : key)) });
|
|
1090
|
+
}
|
|
892
1091
|
setKey(kind, at, j, k, patch) {
|
|
893
1092
|
const channel = this.channelsOf(kind, at)[j];
|
|
894
|
-
if (!channel)
|
|
1093
|
+
if (!channel || isJointChannel(channel))
|
|
895
1094
|
return;
|
|
896
1095
|
this.patchChannel(kind, at, j, {
|
|
897
1096
|
...channel,
|
|
@@ -922,6 +1121,10 @@ __decorate([
|
|
|
922
1121
|
state(),
|
|
923
1122
|
__metadata("design:type", Object)
|
|
924
1123
|
], FigureAnimations.prototype, "recipeTarget", void 0);
|
|
1124
|
+
__decorate([
|
|
1125
|
+
state(),
|
|
1126
|
+
__metadata("design:type", Object)
|
|
1127
|
+
], FigureAnimations.prototype, "typingUnit", void 0);
|
|
925
1128
|
FigureAnimations = __decorate([
|
|
926
1129
|
customElement('figure-animations')
|
|
927
1130
|
], FigureAnimations);
|