@things-factory/figure-ui 10.1.160 → 10.1.162
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/data-interface.ts +17 -0
- package/client/modeller/figure-preview.ts +51 -15
- package/client/pages/figure-list-page.ts +5 -72
- package/client/setting-let/figure-namespace-setting-let.ts +2 -1
- package/client/viewparts/figure-thumb.ts +10 -37
- package/dist-client/modeller/data-interface.d.ts +9 -0
- package/dist-client/modeller/data-interface.js +18 -0
- package/dist-client/modeller/data-interface.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +5 -0
- package/dist-client/modeller/figure-preview.js +51 -13
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/pages/figure-list-page.js +5 -72
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/setting-let/figure-namespace-setting-let.js +2 -1
- package/dist-client/setting-let/figure-namespace-setting-let.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/viewparts/figure-thumb.d.ts +3 -1
- package/dist-client/viewparts/figure-thumb.js +10 -37
- package/dist-client/viewparts/figure-thumb.js.map +1 -1
- package/package.json +4 -4
- package/test/browser/product-page.check.mjs +1 -1
- package/test/browser/screen.check.mjs +7 -7
- package/test/data-motion.test.ts +16 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import '@material/web/icon/icon.js';
|
|
2
2
|
import { LitElement } from 'lit';
|
|
3
|
+
import '@operato/data-grist/ox-card-thumb.js';
|
|
3
4
|
import '@things-factory/figure-viewer/dist-client/figure-thumb-mark.js';
|
|
4
5
|
import type { Figure } from '../types.js';
|
|
5
6
|
declare const FigureThumb_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
6
7
|
/**
|
|
7
|
-
* 카드의 그림 칸.
|
|
8
|
+
* 카드의 그림 칸. 그림 · 그림 없음 · 초안 표시는 모든 카드 카탈로그의 `ox-card-thumb`(@operato/data-grist)가
|
|
9
|
+
* 그리고, 여기서는 그 위에 이 카탈로그의 것(태그 · 삭제)만 얹는다(ADR-0117 개정 2026-10-13).
|
|
8
10
|
*
|
|
9
11
|
* ## 왜 컴포넌트인가
|
|
10
12
|
*
|
|
@@ -3,6 +3,7 @@ import '@material/web/icon/icon.js';
|
|
|
3
3
|
import { css, html, LitElement } from 'lit';
|
|
4
4
|
import { customElement, property } from 'lit/decorators.js';
|
|
5
5
|
import { i18next, localize } from '@operato/i18n';
|
|
6
|
+
import '@operato/data-grist/ox-card-thumb.js';
|
|
6
7
|
import '@things-factory/figure-viewer/dist-client/figure-thumb-mark.js';
|
|
7
8
|
import { getPathInfo } from '@operato/utils';
|
|
8
9
|
/**
|
|
@@ -24,7 +25,8 @@ function thumbnailSrc(figure) {
|
|
|
24
25
|
return `${contextPath}/figure-thumbnail/${figure.id}?v=${stamp}`;
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
|
-
* 카드의 그림 칸.
|
|
28
|
+
* 카드의 그림 칸. 그림 · 그림 없음 · 초안 표시는 모든 카드 카탈로그의 `ox-card-thumb`(@operato/data-grist)가
|
|
29
|
+
* 그리고, 여기서는 그 위에 이 카탈로그의 것(태그 · 삭제)만 얹는다(ADR-0117 개정 2026-10-13).
|
|
28
30
|
*
|
|
29
31
|
* ## 왜 컴포넌트인가
|
|
30
32
|
*
|
|
@@ -45,39 +47,9 @@ function thumbnailSrc(figure) {
|
|
|
45
47
|
let FigureThumb = class FigureThumb extends localize(i18next)(LitElement) {
|
|
46
48
|
static { this.styles = css `
|
|
47
49
|
:host {
|
|
48
|
-
position: relative;
|
|
49
50
|
display: block;
|
|
50
51
|
width: 100%;
|
|
51
52
|
height: 100%;
|
|
52
|
-
overflow: hidden;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
img {
|
|
56
|
-
display: block;
|
|
57
|
-
width: 100%;
|
|
58
|
-
height: 100%;
|
|
59
|
-
/* 형상을 자르지 않는다 — 긴 것(컨베이어)과 높은 것(시그널타워)이 한 줄에 선다 */
|
|
60
|
-
object-fit: contain;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/*
|
|
64
|
-
초안 표시.
|
|
65
|
-
|
|
66
|
-
발행된 것에는 아무 말도 하지 않는다 — 카탈로그에 있는 것은 대개 발행된 것이라, 카드마다
|
|
67
|
-
「발행됨」이 붙으면 아무것도 가르지 않고 자리만 먹는다. 초안만 말하면 이 표시가 있는 카드가
|
|
68
|
-
곧 「아직 쓰면 안 되는 것」이 된다.
|
|
69
|
-
*/
|
|
70
|
-
span[draft] {
|
|
71
|
-
position: absolute;
|
|
72
|
-
top: 6px;
|
|
73
|
-
left: 6px;
|
|
74
|
-
padding: 1px 7px;
|
|
75
|
-
font: var(--label-font, inherit);
|
|
76
|
-
font-size: 0.68rem;
|
|
77
|
-
line-height: 1.5;
|
|
78
|
-
color: var(--md-sys-color-on-secondary-container);
|
|
79
|
-
background-color: var(--md-sys-color-secondary-container);
|
|
80
|
-
border-radius: 4px;
|
|
81
53
|
}
|
|
82
54
|
|
|
83
55
|
/*
|
|
@@ -175,12 +147,12 @@ let FigureThumb = class FigureThumb extends localize(i18next)(LitElement) {
|
|
|
175
147
|
}
|
|
176
148
|
const src = thumbnailSrc(figure);
|
|
177
149
|
return html `
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
150
|
+
<ox-card-thumb
|
|
151
|
+
src=${src || ''}
|
|
152
|
+
alt=${figure.name || ''}
|
|
153
|
+
badge=${figure.state === 'released' ? '' : i18next.t('figure.label.draft')}
|
|
154
|
+
>
|
|
155
|
+
<figure-thumb-mark slot="empty" .label=${i18next.t('figure.text.thumbnail-not-made-yet')}></figure-thumb-mark>
|
|
184
156
|
${figure.tags?.length
|
|
185
157
|
? html `<div tags>
|
|
186
158
|
${figure.tags.map(tag => html `
|
|
@@ -208,6 +180,7 @@ let FigureThumb = class FigureThumb extends localize(i18next)(LitElement) {
|
|
|
208
180
|
>
|
|
209
181
|
<md-icon>delete</md-icon>
|
|
210
182
|
</button>
|
|
183
|
+
</ox-card-thumb>
|
|
211
184
|
`;
|
|
212
185
|
}
|
|
213
186
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-thumb.js","sourceRoot":"","sources":["../../client/viewparts/figure-thumb.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AAEnC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAE3D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,gEAAgE,CAAA;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAI5C;;;;;;;;;GASG;AACH,SAAS,YAAY,CAAC,MAAc;IAClC,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,EAAE,WAAW,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACtD,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,OAAO,EAAE,CAAA;IAE3D,OAAO,GAAG,WAAW,qBAAqB,MAAM,CAAC,EAAE,MAAM,KAAK,EAAE,CAAA;AAClE,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"figure-thumb.js","sourceRoot":"","sources":["../../client/viewparts/figure-thumb.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AAEnC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAE3D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,sCAAsC,CAAA;AAC7C,OAAO,gEAAgE,CAAA;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAI5C;;;;;;;;;GASG;AACH,SAAS,YAAY,CAAC,MAAc;IAClC,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,EAAE,WAAW,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACtD,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,OAAO,EAAE,CAAA;IAE3D,OAAO,GAAG,WAAW,qBAAqB,MAAM,CAAC,EAAE,MAAM,KAAK,EAAE,CAAA;AAClE,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AAEI,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;aACrD,WAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8FlB,AA9FY,CA8FZ;IAID,MAAM;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;QAC1B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,IAAI,CAAA,EAAE,CAAA;QACf,CAAC;QAED,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;QAEhC,OAAO,IAAI,CAAA;;cAED,GAAG,IAAI,EAAE;cACT,MAAM,CAAC,IAAI,IAAI,EAAE;gBACf,MAAM,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,oBAAoB,CAAC;;iDAEjC,OAAO,CAAC,CAAC,CAAC,oCAAoC,CAAC;QACxF,MAAM,CAAC,IAAI,EAAE,MAAM;YACnB,CAAC,CAAC,IAAI,CAAA;cACA,MAAM,CAAC,IAAI,CAAC,GAAG,CACf,GAAG,CAAC,EAAE,CAAC,IAAI,CAAA;;0BAEC,OAAO,CAAC,CAAC,CAAC,8BAA8B,CAAC;2BACxC,CAAC,CAAQ,EAAE,EAAE;gBACpB,CAAC,CAAC,eAAe,EAAE,CAAA;gBACnB,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAClF,CAAA;YACH,CAAC;;oBAEC,GAAG;;eAER,CACF;iBACI;YACT,CAAC,CAAC,EAAE;;;;gBAII,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC;iBACvC,CAAC,CAAQ,EAAE,EAAE;YACpB,sCAAsC;YACtC,CAAC,CAAC,eAAe,EAAE,CAAA;YACnB,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAA;QAC/C,CAAC;;;;;KAKJ,CAAA;IACH,CAAC;;AAlD2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;2CAAgB;AAjGhC,WAAW;IADvB,aAAa,CAAC,cAAc,CAAC;GACjB,WAAW,CAoJvB","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport '@operato/data-grist/ox-card-thumb.js'\nimport '@things-factory/figure-viewer/dist-client/figure-thumb-mark.js'\nimport { getPathInfo } from '@operato/utils'\n\nimport type { Figure } from '../types.js'\n\n/**\n * 그림을 부르는 주소.\n *\n * **base64 를 목록 응답에 실어 오지 않는다.** data URL 은 브라우저가 캐시할 수 없어, 카탈로그를\n * 열 때마다 그림 전부가 다시 온다([확인함] 표본 14개 227KB). 주소로 부르면 캐시가 되고, 판을\n * 가리는 `v` 가 바뀐 것만 다시 받는다. 내주는 자리는 `figure-thumbnail-router` 다.\n *\n * 도메인 조각(`/domain/{subdomain}`)은 셸이 쓰는 것과 같은 함수로 읽는다 — 화면이 주소를\n * 손으로 짜맞추면 도메인이 바뀌는 배치에서 조용히 틀린다.\n */\nfunction thumbnailSrc(figure: Figure): string | undefined {\n if (!figure.thumbnailUpdatedAt) {\n return undefined\n }\n\n const { contextPath } = getPathInfo(location.pathname)\n const stamp = new Date(figure.thumbnailUpdatedAt).getTime()\n\n return `${contextPath}/figure-thumbnail/${figure.id}?v=${stamp}`\n}\n\n/**\n * 카드의 그림 칸. 그림 · 그림 없음 · 초안 표시는 모든 카드 카탈로그의 `ox-card-thumb`(@operato/data-grist)가\n * 그리고, 여기서는 그 위에 이 카탈로그의 것(태그 · 삭제)만 얹는다(ADR-0117 개정 2026-10-13).\n *\n * ## 왜 컴포넌트인가\n *\n * 카탈로그는 `ox-grist` 의 CARD 모드로 그린다. 격자에게 그림 칸은 컬럼 하나이고, 그 칸을 그리는\n * 것은 컬럼의 `record.renderer` 다. 그림 위에 얹히는 것들(초안 표시, 삭제)은 **그림 칸 안에**\n * 있어야 하므로 renderer 가 그려야 하는데, renderer 의 결과는 `ox-card-field` 의 shadow root\n * 안으로 들어간다 — 화면의 `static styles` 가 닿지 않고, 인라인 style 로는 `:hover` 를 쓸 수 없다.\n *\n * 그래서 그림 칸만 컴포넌트로 둔다. 자기 스타일을 갖고 있으니 hover 도 테마도 정상으로 돈다.\n * 격자로 옮기기 전 손으로 만들었던 카드의 위쪽 절반이 그대로 이 자리다.\n *\n * ## 삭제가 왜 여기 있나\n *\n * 격자의 gutter 줄에 두면 카드 아래에 단추 줄이 하나 생긴다. 감춰도 자리가 남고, 자리를 없애면\n * 눌릴 곳이 사라진다. 되돌릴 수 없는 조작은 **고르는 눈길에 걸리지 않아야** 하므로 그림 오른쪽\n * 위, 손이 갔을 때만 나오는 자리에 둔다.\n */\n@customElement('figure-thumb')\nexport class FigureThumb extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n /*\n 태그 — **그림 위에 얹는다.**\n\n 카드 본문에 한 줄로 두었더니 태그가 있는 카드만 그만큼 높아져 격자가 들쭉날쭉해졌다.\n 태그는 도형마다 개수가 다르므로 본문에 두는 한 이 문제가 남는다. 그림 위는 이미 자리가\n 잡혀 있고(초안 표시·삭제), 얹어도 카드 높이가 변하지 않는다.\n\n 아래쪽에 둔다 — 위는 초안 표시와 삭제 단추가 쓴다.\n\n **투명한 알약이다.** 면을 꽉 채우면 그림 위에 흰 띠가 생겨 형상을 가린다 — 카탈로그에서\n 먼저 보아야 하는 것은 형상이다. 그래서 바탕은 거의 비우고, 읽히는 것은 뒤를 흐리는 것과\n 실낱 테두리가 맡는다. 흐림이 안 되는 환경에서도 테두리가 남아 글자가 형상에 묻히지 않는다.\n */\n div[tags] {\n position: absolute;\n right: 6px;\n bottom: 6px;\n left: 6px;\n display: flex;\n flex-wrap: wrap-reverse;\n justify-content: flex-end;\n gap: 3px;\n /* 태그가 많아도 그림을 덮지 않는다 — 넘치는 것은 위로 잘린다 */\n max-height: 44px;\n overflow: hidden;\n /* 알약 사이의 빈 자리는 그림이 받는다 — 거기서 누르면 저작면이 열린다 */\n pointer-events: none;\n }\n /*\n **알약은 누를 수 있다.** 누르면 그 태그로 목록을 거른다.\n\n 태그를 눈에 보이게 한 다음의 물음은 「그래서 이걸로 뭘 하나」다. 같은 태그가 붙은 것을\n 보려고 태그를 눈으로 읽어 검색창에 옮겨 적게 두면, 보여 준 뜻이 절반만 산다.\n\n 카드 자체가 저작면을 여는 자리이므로 알약에서 그 클릭을 멈춘다 — 안 멈추면 태그를 누른\n 사람이 편집기로 끌려간다.\n */\n div[tags] button {\n pointer-events: auto;\n cursor: pointer;\n padding: 0 7px;\n font: var(--label-font, inherit);\n font-size: 0.66rem;\n line-height: 1.7;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n background-color: color-mix(in srgb, var(--md-sys-color-surface, #fff) 34%, transparent);\n border: 1px solid color-mix(in srgb, var(--md-sys-color-outline, #888) 42%, transparent);\n backdrop-filter: blur(4px);\n border-radius: 999em;\n white-space: nowrap;\n }\n div[tags] button:hover,\n div[tags] button:focus-visible {\n color: var(--md-sys-color-on-surface);\n background-color: color-mix(in srgb, var(--md-sys-color-surface, #fff) 72%, transparent);\n border-color: var(--md-sys-color-primary);\n outline: none;\n }\n\n button[remove] {\n position: absolute;\n top: 5px;\n right: 5px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 3px;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n /* 그림이 밝을 수도 어두울 수도 있다 — 옅은 면을 깔아 아이콘이 어디서나 읽히게 한다 */\n background-color: color-mix(in srgb, var(--md-sys-color-surface, #fff) 82%, transparent);\n border: none;\n border-radius: 4px;\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.12s ease-out;\n }\n button[remove] md-icon {\n --md-icon-size: 18px;\n }\n :host(:hover) button[remove],\n button[remove]:focus-visible {\n opacity: 1;\n }\n button[remove]:hover {\n color: var(--md-sys-color-on-error, #fff);\n background-color: var(--md-sys-color-error);\n }\n `\n\n @property({ type: Object }) figure!: Figure\n\n render() {\n const figure = this.figure\n if (!figure) {\n return html``\n }\n\n const src = thumbnailSrc(figure)\n\n return html`\n <ox-card-thumb\n src=${src || ''}\n alt=${figure.name || ''}\n badge=${figure.state === 'released' ? '' : i18next.t('figure.label.draft')}\n >\n <figure-thumb-mark slot=\"empty\" .label=${i18next.t('figure.text.thumbnail-not-made-yet')}></figure-thumb-mark>\n ${figure.tags?.length\n ? html`<div tags>\n ${figure.tags.map(\n tag => html`\n <button\n title=${i18next.t('figure.text.find-by-this-tag')}\n @click=${(e: Event) => {\n e.stopPropagation()\n this.dispatchEvent(\n new CustomEvent('search-tag', { detail: { tag }, bubbles: true, composed: true })\n )\n }}\n >\n ${tag}\n </button>\n `\n )}\n </div>`\n : ''}\n\n <button\n remove\n title=${i18next.t('figure.button.delete-figure')}\n @click=${(e: Event) => {\n /* 카드 클릭(=저작면 열기)까지 올라가지 않게 여기서 멈춘다 */\n e.stopPropagation()\n this.dispatchEvent(new CustomEvent('remove'))\n }}\n >\n <md-icon>delete</md-icon>\n </button>\n </ox-card-thumb>\n `\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-ui",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.162",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"@operato/slot-layout-editor": "^10.29.25",
|
|
42
42
|
"@operato/styles": "^10.0.0",
|
|
43
43
|
"@operato/utils": "^10.0.0",
|
|
44
|
-
"@things-factory/ai-assistant": "^10.1.
|
|
45
|
-
"@things-factory/figure-service": "^10.1.
|
|
44
|
+
"@things-factory/ai-assistant": "^10.1.161",
|
|
45
|
+
"@things-factory/figure-service": "^10.1.161",
|
|
46
46
|
"@things-factory/figure-viewer": "^10.1.155",
|
|
47
47
|
"@things-factory/setting-base": "^10.1.159",
|
|
48
48
|
"three": "^0.185.1"
|
|
49
49
|
},
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "7be9e6436b89213940c684c0cdc60343e8148e73"
|
|
51
51
|
}
|
|
@@ -698,7 +698,7 @@ describe('the catalogue preview', () => {
|
|
|
698
698
|
product.forget()
|
|
699
699
|
product.openDetail('a')
|
|
700
700
|
})
|
|
701
|
-
await press('figure-detail-dock >>>
|
|
701
|
+
await press('figure-detail-dock >>> button[edit]', 'edit')
|
|
702
702
|
await until(() => product.navigations().length, 'the modeller opened')
|
|
703
703
|
assert.deepEqual(await read(() => product.navigations().map(({ path, detailShown }) => ({ path, detailShown }))), [{ path: '/figure-modeller/a', detailShown: false }])
|
|
704
704
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* The modeller's screen in a real browser: its frame (three panes, the side's
|
|
2
|
+
* The modeller's screen in a real browser: its frame (three panes, the side's seven tabs, the bar over the work), a new
|
|
3
3
|
* figure's first minute, the side panel as parts are chosen, the view tab, the preview, the verdict tab and the card's
|
|
4
4
|
* picture -- each read as a person meets it, after doing what a person does (a click, a key, a narrower window).
|
|
5
5
|
*
|
|
@@ -150,7 +150,7 @@ describe('the verdict tab over a figure, as the modeller page mounts it', () =>
|
|
|
150
150
|
})
|
|
151
151
|
})
|
|
152
152
|
|
|
153
|
-
describe('the frame: parts · canvas · one side of
|
|
153
|
+
describe('the frame: parts · canvas · one side of seven tabs, and the bar over the work', () => {
|
|
154
154
|
before(() => mount('box'))
|
|
155
155
|
|
|
156
156
|
test('three panes: the parts list on the left, the canvas in the middle, the side on the right -- one side, nothing under the canvas', async () => {
|
|
@@ -185,13 +185,13 @@ describe('the frame: parts · canvas · one side of six tabs, and the bar over t
|
|
|
185
185
|
assert.equal(await read(() => getComputedStyle(screenCheck.editor()).containerType), 'inline-size')
|
|
186
186
|
})
|
|
187
187
|
|
|
188
|
-
test('the side\'s
|
|
188
|
+
test('the side\'s seven tabs, in order, each saying its name under its icon -- the name it is read by', async () => {
|
|
189
189
|
const tabs = await read(() =>
|
|
190
190
|
[...screenCheck.editor().shadowRoot.querySelectorAll('aside button[tab]')].map(b => ({ label: b.getAttribute('aria-label'), shows: b.querySelector('span[tab-label]')?.textContent.trim() }))
|
|
191
191
|
)
|
|
192
192
|
assert.deepEqual(
|
|
193
193
|
tabs.map(t => t.label),
|
|
194
|
-
['properties', 'capability', 'motion', 'verdict', 'view', 'history'].map(TAB)
|
|
194
|
+
['properties', 'capability', 'motion', 'data', 'verdict', 'view', 'history'].map(TAB)
|
|
195
195
|
)
|
|
196
196
|
for (const tab of tabs) assert.equal(tab.shows, tab.label, `${tab.label} shows its name`)
|
|
197
197
|
assert.equal(await read(() => screenCheck.editor().shadowRoot.querySelector('aside').firstElementChild.matches('div[tabs]')), true, 'the tabs are the side\'s first row')
|
|
@@ -200,15 +200,15 @@ describe('the frame: parts · canvas · one side of six tabs, and the bar over t
|
|
|
200
200
|
test('a tab shows its panel and hides the others without taking them down -- the verdict keeps what it holds', async () => {
|
|
201
201
|
const panels = () =>
|
|
202
202
|
read(() => [...screenCheck.editor().shadowRoot.querySelectorAll('aside > div[panel]')].map(p => ({ seen: p.getBoundingClientRect().height > 0, connected: p.isConnected, verdict: !!p.querySelector('slot[name=verdict]') })))
|
|
203
|
-
const tabs = ['properties', 'capability', 'motion', 'verdict', 'view', 'history']
|
|
203
|
+
const tabs = ['properties', 'capability', 'motion', 'data', 'verdict', 'view', 'history']
|
|
204
204
|
for (const [at, tab] of tabs.entries()) {
|
|
205
205
|
await pressTab(TAB(tab))
|
|
206
206
|
await until(`${tab} on`, name => screenCheck.editor().shadowRoot.querySelector('button[tab][on]')?.getAttribute('aria-label') === name, TAB(tab))
|
|
207
207
|
const now = await panels()
|
|
208
|
-
assert.equal(now.length,
|
|
208
|
+
assert.equal(now.length, 7, 'seven panels, all standing')
|
|
209
209
|
assert.deepEqual(now.map(p => p.seen), tabs.map((_, i) => i === at), `${tab}: its panel seen, the rest hidden`)
|
|
210
210
|
assert.ok(now.every(p => p.connected))
|
|
211
|
-
assert.ok(now[
|
|
211
|
+
assert.ok(now[tabs.indexOf('verdict')].verdict, 'the verdict panel holds the verdict slot whichever tab is on')
|
|
212
212
|
}
|
|
213
213
|
await pressTab(TAB('properties'))
|
|
214
214
|
})
|
package/test/data-motion.test.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { editFigurePart, quadrupedRobotFigure, robotDogFigure } from '@hatiolab/
|
|
|
13
13
|
import type { FigureAsset, FigureDataProperty } from '@hatiolab/figure-model'
|
|
14
14
|
|
|
15
15
|
import { clipGroupsOf, freeGroupIdOf, groupEdit, undrivenOf } from '../client/modeller/clips.ts'
|
|
16
|
-
import { connectionDraftOf, dataSampleOf, dataTargetsOf, freeDataIdOf, propertyDraftOf, sampleValueOf } from '../client/modeller/data-interface.ts'
|
|
16
|
+
import { connectionDraftOf, dataSampleOf, dataTargetsOf, freeDataIdOf, motionWrittenByData, propertyDraftOf, sampleValueOf } from '../client/modeller/data-interface.ts'
|
|
17
17
|
import { editRefusalOf } from '../client/modeller/refusals.ts'
|
|
18
18
|
|
|
19
19
|
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
@@ -105,6 +105,21 @@ test('데이터 · 동작 거절은 커널 문장이 아니라 reason 과 값으
|
|
|
105
105
|
assert.match(refusalOf(() => editFigurePart(lone, { kind: 'set-driver', state: 'fl-knee', clip: 'run', duration: 1, keys: [{ seconds: 0, value: 0 }, { seconds: 1, value: 10 }] })), /^figure\.text\.driver-writers /)
|
|
106
106
|
})
|
|
107
107
|
|
|
108
|
+
test('미리보기: 데이터가 정하는 그룹 필드는 그룹 칸이 아니라 데이터 값으로 움직인다 (2026-10-14, 로봇 개)', () => {
|
|
109
|
+
/* The dog's clip and speed are both written by data (mode, cadence): a clip chosen on the group moved nothing. */
|
|
110
|
+
assert.deepEqual(motionWrittenByData(DOG), { locomotion: { clip: true, speed: true } })
|
|
111
|
+
/* Only the speed connected: the clip stays the group's to choose. */
|
|
112
|
+
const speedOnly = { ...DOG, dataInterface: { ...DOG.dataInterface!, connections: DOG.dataInterface!.connections.filter(c => c.target.kind !== 'motion-clip') } }
|
|
113
|
+
assert.deepEqual(motionWrittenByData(speedOnly), { locomotion: { clip: false, speed: true } })
|
|
114
|
+
/* No data interface: every group field is the group's own. */
|
|
115
|
+
const { dataInterface: _gone, ...bare } = DOG
|
|
116
|
+
assert.deepEqual(motionWrittenByData(bare as FigureAsset), {})
|
|
117
|
+
const preview = read('../client/modeller/figure-preview.ts')
|
|
118
|
+
assert.match(preview, /tellInstances\('figureValues', this\.valuing\)/)
|
|
119
|
+
assert.match(preview, /byData\.clip\s*\? nothing/)
|
|
120
|
+
assert.match(preview, /byData\.speed\s*\? nothing/)
|
|
121
|
+
})
|
|
122
|
+
|
|
108
123
|
test('데이터 탭 · 클립 패널 · 미리보기의 배선과 문구', () => {
|
|
109
124
|
const editor = read('../client/modeller/editor.ts')
|
|
110
125
|
assert.match(editor, /renderTab\('data'/)
|