@things-factory/figure-ui 10.1.225 → 10.1.227
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 +6 -6
- package/client/modeller/consumer-capabilities.ts +15 -0
- package/client/modeller/editor-styles.ts +18 -1
- package/client/modeller/editor.ts +38 -26
- package/client/modeller/figure-capability-panel.ts +31 -10
- package/client/modeller/figure-clips.ts +2 -1
- package/client/modeller/figure-controls.ts +4 -3
- package/client/modeller/figure-data-panel.ts +24 -7
- package/client/modeller/figure-history-panel.ts +7 -7
- package/client/modeller/figure-links.ts +5 -5
- package/client/modeller/figure-material-panel.ts +2 -0
- package/client/modeller/figure-preview.ts +69 -2
- package/client/modeller/figure-recipe-catalog.ts +4 -0
- package/client/modeller/figure-slot-layout-panel.ts +25 -8
- package/client/modeller/figure-verdict.ts +4 -1
- package/client/modeller/unit-words.ts +11 -0
- package/client/pages/figure-list-page.ts +2 -2
- package/client/pages/figure-modeller-page.ts +7 -6
- package/client/types.ts +2 -2
- package/client/viewparts/figure-thumb.ts +1 -1
- package/dist-client/graphql/index.d.ts +5 -5
- package/dist-client/graphql/index.js +6 -6
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/consumer-capabilities.d.ts +11 -0
- package/dist-client/modeller/consumer-capabilities.js +14 -0
- package/dist-client/modeller/consumer-capabilities.js.map +1 -1
- package/dist-client/modeller/editor-styles.js +18 -1
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.js +38 -26
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-capability-panel.d.ts +3 -0
- package/dist-client/modeller/figure-capability-panel.js +29 -11
- package/dist-client/modeller/figure-capability-panel.js.map +1 -1
- package/dist-client/modeller/figure-clips.js +2 -1
- package/dist-client/modeller/figure-clips.js.map +1 -1
- package/dist-client/modeller/figure-controls.js +4 -3
- package/dist-client/modeller/figure-controls.js.map +1 -1
- package/dist-client/modeller/figure-data-panel.js +24 -7
- package/dist-client/modeller/figure-data-panel.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +5 -5
- package/dist-client/modeller/figure-history-panel.js +7 -7
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-links.js +5 -5
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-material-panel.js +2 -0
- package/dist-client/modeller/figure-material-panel.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +3 -0
- package/dist-client/modeller/figure-preview.js +78 -2
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-recipe-catalog.js +4 -0
- package/dist-client/modeller/figure-recipe-catalog.js.map +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.d.ts +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js +25 -8
- package/dist-client/modeller/figure-slot-layout-panel.js.map +1 -1
- package/dist-client/modeller/figure-verdict.js +4 -1
- package/dist-client/modeller/figure-verdict.js.map +1 -1
- package/dist-client/modeller/unit-words.d.ts +5 -0
- package/dist-client/modeller/unit-words.js +12 -0
- package/dist-client/modeller/unit-words.js.map +1 -0
- package/dist-client/pages/figure-list-page.d.ts +1 -1
- package/dist-client/pages/figure-list-page.js +2 -2
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +3 -3
- package/dist-client/pages/figure-modeller-page.js +7 -6
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +2 -2
- package/dist-client/types.js.map +1 -1
- package/dist-client/viewparts/figure-thumb.js +1 -1
- package/dist-client/viewparts/figure-thumb.js.map +1 -1
- package/package.json +9 -9
- package/test/browser/detail-page.js +1 -1
- package/test/browser/editing-page.js +3 -3
- package/test/browser/editing.check.mjs +5 -5
- package/test/browser/links-page.js +1 -1
- package/test/browser/links.check.mjs +2 -3
- package/test/browser/modeller-page.js +4 -4
- package/test/browser/modeller-revision.check.mjs +5 -5
- package/test/browser/modeller.check.mjs +5 -5
- package/test/browser/parts.check.mjs +2 -2
- package/test/browser/product-page.check.mjs +6 -0
- package/test/browser/props.check.mjs +3 -4
- package/test/browser/row-preview.check.mjs +3 -3
- package/test/browser/screen.check.mjs +13 -11
- package/test/consumer-capabilities.test.ts +7 -0
- package/test/links.test.ts +1 -1
- package/test/refusals.test.ts +1 -1
- package/test/resolve-ts-siblings.mjs +3 -3
- package/translations/en.json +23 -12
- package/translations/es.json +23 -12
- package/translations/ja.json +24 -13
- package/translations/ko.json +83 -72
- package/translations/ms.json +23 -12
- package/translations/zh.json +23 -12
package/dist-client/types.d.ts
CHANGED
package/dist-client/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../client/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * 화면이 다루는 모양. 피규어 자산의 타입은 `@hatiolab/figure-model` 이 정한다 — 여기서 다시 적지 않는다.\n */\n\n/** 서버가 돌려주는 Figure 한 벌. `source` 는 상세 또는 저작면을 열 때만 받는다. */\nexport interface Figure {\n id: string\n /** 컴포넌트 타입 이름. **만든 뒤에 고칠 수 없다.** */\n type: string\n /** Namespace of the full name {namespace}/{type} (ADR-0114). */\n namespace?: string | null\n name: string\n description?: string\n /** 분류 하나 — 팔레트에서 묶어 보이는 기준. */\n category?: string\n /** 태그 여럿 — 분류로는 못 하는 찾기. */\n tags?: string[]\n state?: 'draft' | 'released'\n /** 재활용 점수 0~100. 서버가 정본에서 계산한다 — 화면이 보내는 값이 아니다. */\n score?: number\n triangles?: number\n groups?: number\n thumbnail?: string\n thumbnailUpdatedAt?: string\n updatedAt?: string\n updater?: { id: string; name: string }\n /** 피규어 자산 JSON 문자열. 목록 질의에는 실려 오지 않는다. */\n source?: string\n properties?: string\n version?: number\n /** Retired: still released and served, no longer offered for new use. */\n deprecated?: boolean\n /** The type to use instead of a retired figure, in the same domain. */\n supersededBy?: string | null\n /** Public: other domains read its releases and draw it by full name (LV figure-server design G2). */\n public?: boolean\n /** The domain it belongs to; carried by another domain's public figure, so a screen says whose it is. */\n domain?: { id: string; name?: string; subdomain?: string } | null\n}\n\n/**\n * 발행된
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../client/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * 화면이 다루는 모양. 피규어 자산의 타입은 `@hatiolab/figure-model` 이 정한다 — 여기서 다시 적지 않는다.\n */\n\n/** 서버가 돌려주는 Figure 한 벌. `source` 는 상세 또는 저작면을 열 때만 받는다. */\nexport interface Figure {\n id: string\n /** 컴포넌트 타입 이름. **만든 뒤에 고칠 수 없다.** */\n type: string\n /** Namespace of the full name {namespace}/{type} (ADR-0114). */\n namespace?: string | null\n name: string\n description?: string\n /** 분류 하나 — 팔레트에서 묶어 보이는 기준. */\n category?: string\n /** 태그 여럿 — 분류로는 못 하는 찾기. */\n tags?: string[]\n state?: 'draft' | 'released'\n /** 재활용 점수 0~100. 서버가 정본에서 계산한다 — 화면이 보내는 값이 아니다. */\n score?: number\n triangles?: number\n groups?: number\n thumbnail?: string\n thumbnailUpdatedAt?: string\n updatedAt?: string\n updater?: { id: string; name: string }\n /** 피규어 자산 JSON 문자열. 목록 질의에는 실려 오지 않는다. */\n source?: string\n properties?: string\n version?: number\n /** Retired: still released and served, no longer offered for new use. */\n deprecated?: boolean\n /** The type to use instead of a retired figure, in the same domain. */\n supersededBy?: string | null\n /** Public: other domains read its releases and draw it by full name (LV figure-server design G2). */\n public?: boolean\n /** The domain it belongs to; carried by another domain's public figure, so a screen says whose it is. */\n domain?: { id: string; name?: string; subdomain?: string } | null\n}\n\n/**\n * 발행된 버전 하나.\n *\n * 저작 중 저장은 여기 오지 않는다 — 버전은 발행에서만 생긴다.\n */\nexport interface FigureVersion {\n version: number\n /** 발행하며 남긴 말. */\n comment?: string\n state?: 'draft' | 'released'\n updatedAt?: string\n updater?: { id: string; name: string }\n score?: number\n triangles?: number\n groups?: number\n}\n\nexport interface FigureListResult {\n items: Figure[]\n total: number\n}\n\n/**\n * 새 Figure.\n *\n * `score`·`triangles`·`groups` 가 없다 — **서버가 정본에서 계산한다.** 화면이 보내면\n * 거짓이 들어오고 목록이 실물과 다른 것을 보여 준다.\n */\nexport interface NewFigure {\n type: string\n name: string\n description?: string\n category?: string\n tags?: string[]\n source: string\n properties?: string\n state?: 'draft' | 'released'\n thumbnail?: string\n}\n\n/**\n * 수정.\n *\n * `type` 이 없다 — 저장되는 식별자라 고칠 수 없다. 배치된 도면이 그 이름으로 타입을\n * 찾으므로, 바꾸면 그 도면이 빈 화면이 된다.\n */\nexport interface FigurePatch {\n /** 발행 전 초안에서만 받는다 — 발행된 뒤에는 문이 거절한다(ADR-0063 결정 4). */\n type?: string\n name?: string\n description?: string\n category?: string\n /** 빈 배열은 「전부 지운다」, 안 주는 것은 「건드리지 않는다」. */\n tags?: string[]\n source?: string\n properties?: string\n state?: 'draft' | 'released'\n thumbnail?: string\n}\n\n/** 검증 결과 한 줄. 코드는 figure-model 의 것을 그대로 쓴다. */\nexport interface FigureFinding {\n code: string\n message: string\n at?: string\n}\n\n/**\n * 발행 판정 한 줄.\n *\n * `FigureFinding` 과 달리 **`blocking` 이 있다.** 저장 쪽 발견은 정의상 막지 않는 것들이고,\n * 발행 판정은 막는 것과 알리는 것을 함께 내므로 어느 쪽인지를 값이 스스로 말한다 — 코드를 보고\n * 화면이 다시 분류하면 규칙이 두 벌이 된다.\n */\nexport interface FigureGateFinding {\n code: string\n message: string\n /** 왜 그것이 문제인가 — 사람이 무엇을 보게 되나. */\n why?: string\n /** 무엇을 바꾸면 되나 — 부품 이름과 값으로. */\n how?: string\n /** 갈라진 부품 이름들, 또는 정본 안의 경로. */\n at?: string\n blocking: boolean\n}\n\n/** 발행해도 되나. `blocked` 면 서버가 거절한다. */\nexport interface FigureInspection {\n blocked: boolean\n findings: FigureGateFinding[]\n}\n"]}
|
|
@@ -11,7 +11,7 @@ import { hasBeenReleased } from '../figure-released.js';
|
|
|
11
11
|
* 그림을 부르는 주소.
|
|
12
12
|
*
|
|
13
13
|
* **base64 를 목록 응답에 실어 오지 않는다.** data URL 은 브라우저가 캐시할 수 없어, 카탈로그를
|
|
14
|
-
* 열 때마다 그림 전부가 다시 온다([확인함] 표본 14개 227KB). 주소로 부르면 캐시가 되고,
|
|
14
|
+
* 열 때마다 그림 전부가 다시 온다([확인함] 표본 14개 227KB). 주소로 부르면 캐시가 되고, 버전을
|
|
15
15
|
* 가리는 `v` 가 바뀐 것만 다시 받는다. 내주는 자리는 `figure-thumbnail-router` 다.
|
|
16
16
|
*
|
|
17
17
|
* 도메인 조각(`/domain/{subdomain}`)은 셸이 쓰는 것과 같은 함수로 읽는다 — 화면이 주소를
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-thumb.js","sourceRoot":"","sources":["../../client/viewparts/figure-thumb.ts"],"names":[],"mappings":"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","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'\nimport { hasBeenReleased } from '../figure-released.js'\n\n/**\n * 그림을 부르는 주소.\n *\n * **base64 를 목록 응답에 실어 오지 않는다.** data URL 은 브라우저가 캐시할 수 없어, 카탈로그를\n * 열 때마다 그림 전부가 다시 온다([확인함] 표본 14개 227KB). 주소로 부르면 캐시가 되고,
|
|
1
|
+
{"version":3,"file":"figure-thumb.js","sourceRoot":"","sources":["../../client/viewparts/figure-thumb.ts"],"names":[],"mappings":"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","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'\nimport { hasBeenReleased } from '../figure-released.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 /** Another domain's public figure (G2): its card says whose it is and offers no delete. */\n @property({ type: Boolean }) readonly = false\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=${this.readonly ? figure.domain?.name ?? '' : figure.deprecated ? i18next.t('figure.label.retired') : 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 ${this.readonly || hasBeenReleased(figure) ? '' : html`<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.227",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"clean": "npm run clean:server && npm run clean:client"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@hatiolab/figure-model": "^0.12.
|
|
31
|
-
"@hatiolab/things-scene": "^10.1.
|
|
30
|
+
"@hatiolab/figure-model": "^0.12.1",
|
|
31
|
+
"@hatiolab/things-scene": "^10.1.104",
|
|
32
32
|
"@material/web": "^2.0.0",
|
|
33
33
|
"@operato/data-grist": "^10.29.43",
|
|
34
34
|
"@operato/graphql": "^10.29.43",
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
"@operato/p13n": "^10.29.43",
|
|
40
40
|
"@operato/popup": "^10.29.43",
|
|
41
41
|
"@operato/shell": "^10.29.43",
|
|
42
|
-
"@operato/slot-layout-editor": "^10.29.
|
|
42
|
+
"@operato/slot-layout-editor": "^10.29.58",
|
|
43
43
|
"@operato/styles": "^10.0.0",
|
|
44
44
|
"@operato/utils": "^10.29.43",
|
|
45
|
-
"@things-factory/ai-assistant": "^10.1.
|
|
46
|
-
"@things-factory/figure-service": "^10.1.
|
|
47
|
-
"@things-factory/figure-viewer": "^10.1.
|
|
48
|
-
"@things-factory/setting-base": "^10.1.
|
|
45
|
+
"@things-factory/ai-assistant": "^10.1.227",
|
|
46
|
+
"@things-factory/figure-service": "^10.1.227",
|
|
47
|
+
"@things-factory/figure-viewer": "^10.1.227",
|
|
48
|
+
"@things-factory/setting-base": "^10.1.227",
|
|
49
49
|
"three": "^0.185.1"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "f66591e3864aab08a6fb93fb1fcf1c9de6b546ac"
|
|
52
52
|
}
|
|
@@ -26,7 +26,7 @@ async function start() {
|
|
|
26
26
|
const kept = document.createElement('output'); kept.setAttribute('aria-label','썸네일')
|
|
27
27
|
dock.keepThumbnail = async (id, url) => { kept.textContent = `${id} ${url.slice(0, 15)} ${url.length}` }
|
|
28
28
|
const controls = document.createElement('nav')
|
|
29
|
-
for (const [label, id] of [['클립 모델 열기','motion'],['연결 오류 시험','retry'],['깨진
|
|
29
|
+
for (const [label, id] of [['클립 모델 열기','motion'],['연결 오류 시험','retry'],['깨진 발행 버전 시험','invalid'],['느린 모델 열기','slow']]) {
|
|
30
30
|
const button = document.createElement('button'); button.textContent = label
|
|
31
31
|
button.onclick = () => void dock.show(id); controls.append(button)
|
|
32
32
|
}
|
|
@@ -96,7 +96,7 @@ window.editing = {
|
|
|
96
96
|
propertyInputs() {
|
|
97
97
|
/* The panel shown: the properties, or the 「반응형」 tab a check pressed. */
|
|
98
98
|
const panel = editor().renderRoot.querySelector('div[panel]:not([off])')
|
|
99
|
-
return [...panel.querySelectorAll('input')].map(i => i.getAttribute('aria-label'))
|
|
99
|
+
return [...panel.querySelectorAll('input')].map(i => (i.dataset?.field ?? i.getAttribute('aria-label')))
|
|
100
100
|
},
|
|
101
101
|
/** The page point of the first element a selector finds -- anywhere on the page, or inside the `host` elements -- and what it says. */
|
|
102
102
|
at(selector, host) {
|
|
@@ -126,13 +126,13 @@ window.editing = {
|
|
|
126
126
|
},
|
|
127
127
|
/** The page point of a field's label: the axis letter of a row's cell, or a single field's name. */
|
|
128
128
|
fieldLabel(id) {
|
|
129
|
-
const input = deep('input').find(i => i.getAttribute('aria-label') === id)
|
|
129
|
+
const input = deep('input').find(i => (i.dataset?.field ?? i.getAttribute('aria-label')) === id)
|
|
130
130
|
const label = input?.closest('div[cell]')?.querySelector('span[axis]') ?? input?.previousElementSibling
|
|
131
131
|
return label ? centreOf(label) : null
|
|
132
132
|
},
|
|
133
133
|
/** What a field shows now. */
|
|
134
134
|
fieldValue(id) {
|
|
135
|
-
return deep('input').find(i => i.getAttribute('aria-label') === id)?.value ?? null
|
|
135
|
+
return deep('input').find(i => (i.dataset?.field ?? i.getAttribute('aria-label')) === id)?.value ?? null
|
|
136
136
|
},
|
|
137
137
|
/** How a clip track's head is laid out: its name, the repeat choice and the remove button, as boxes on the page. */
|
|
138
138
|
trackHead() {
|
|
@@ -314,11 +314,11 @@ describe('the material: colours by purpose, one step each, a glow drawn while dr
|
|
|
314
314
|
})
|
|
315
315
|
|
|
316
316
|
/*
|
|
317
|
-
A round body's measures are the
|
|
317
|
+
A round body's measures are the 「크기」 tab's size editor (2026-10-24): its across said as a diameter, written as the
|
|
318
318
|
radius it halves to. The editor writes on change; it draws nothing while a number is typed.
|
|
319
319
|
*/
|
|
320
320
|
test('a diameter typed on the arrangement tab is written once when it is left, as the radius it halves to', async () => {
|
|
321
|
-
await click(await read(() => probe.tab('figure.label.tab-
|
|
321
|
+
await click(await read(() => probe.tab('figure.label.tab-short-response')))
|
|
322
322
|
const start = await revision()
|
|
323
323
|
await type('drum X · figure.label.size-diameter figure.label.size-measure', '600')
|
|
324
324
|
assert.equal(await revision(), start, 'nothing written yet')
|
|
@@ -332,7 +332,7 @@ describe('the material: colours by purpose, one step each, a glow drawn while dr
|
|
|
332
332
|
})
|
|
333
333
|
|
|
334
334
|
/*
|
|
335
|
-
A box's size is the
|
|
335
|
+
A box's size is the 「크기」 tab's size editor now (2026-10-24); the numbers in hand on the properties tab are its place,
|
|
336
336
|
in the position row.
|
|
337
337
|
*/
|
|
338
338
|
describe('values in hand on the properties tab: drawn at once, written on change (user 2026-09-24)', () => {
|
|
@@ -374,7 +374,7 @@ describe('values in hand on the properties tab: drawn at once, written on change
|
|
|
374
374
|
})
|
|
375
375
|
})
|
|
376
376
|
|
|
377
|
-
/* A size of the part's own on the
|
|
377
|
+
/* A size of the part's own on the 「크기」 tab's size editor is a value in hand as any field is (user 2026-09-24). */
|
|
378
378
|
describe('a size in hand on the arrangement tab: drawn while typed or dragged, written once', () => {
|
|
379
379
|
const WIDTH = 'box X figure.label.size-measure'
|
|
380
380
|
const wide = async () => Math.round((await look('box')).size[0])
|
|
@@ -382,7 +382,7 @@ describe('a size in hand on the arrangement tab: drawn while typed or dragged, w
|
|
|
382
382
|
before(async () => {
|
|
383
383
|
await mount('box')
|
|
384
384
|
await pick('box')
|
|
385
|
-
await click(await read(() => probe.tab('figure.label.tab-
|
|
385
|
+
await click(await read(() => probe.tab('figure.label.tab-short-response')))
|
|
386
386
|
})
|
|
387
387
|
|
|
388
388
|
test('a width typed is drawn while it is typed, and written once on Enter', async () => {
|
|
@@ -39,7 +39,7 @@ window.look = {
|
|
|
39
39
|
all(selector, text) {
|
|
40
40
|
return deep(selector)
|
|
41
41
|
.filter(saying(text))
|
|
42
|
-
.map(e => ({ text: words(e), disabled: !!e.disabled, open: !!e.open, value: e.value ?? null, label: e.getAttribute('aria-label'), readOnly: !!e.readOnly, spotlit: e.hasAttribute('spotlit') }))
|
|
42
|
+
.map(e => ({ text: words(e), disabled: !!e.disabled, open: !!e.open, value: e.value ?? null, label: e.dataset?.field ?? e.getAttribute('aria-label'), readOnly: !!e.readOnly, spotlit: e.hasAttribute('spotlit') }))
|
|
43
43
|
},
|
|
44
44
|
/** A face of the target box in the fastening drawing: whether it can be pressed now, and whether it is the one chosen. */
|
|
45
45
|
face(f) {
|
|
@@ -88,9 +88,8 @@ async function pick(part) {
|
|
|
88
88
|
await click(await read(id => probe.row(id), part), `the row of ${part}`)
|
|
89
89
|
await until(id => probe.state().selected === id, [part], `${part} selected`)
|
|
90
90
|
}
|
|
91
|
-
/* A side tab by its short name
|
|
92
|
-
const tab = name =>
|
|
93
|
-
read(n => probe.tab(`figure.label.tab-short-${n}`) ?? (n === 'response' ? probe.tab('figure.label.tab-arrangement') : null), name).then(point => click(point, `the ${name} tab`))
|
|
91
|
+
/* A side tab by its short name: one name whatever is picked (「크기」, sweep 2026-10-27 J2). */
|
|
92
|
+
const tab = name => read(n => probe.tab(`figure.label.tab-short-${n}`), name).then(point => click(point, `the ${name} tab`))
|
|
94
93
|
/** A fastening the part has is said in one line; 「바꾸기」 opens the fastening window on it. */
|
|
95
94
|
async function changeFastening() {
|
|
96
95
|
await until(() => look.all('button[change-fastening]').length === 1, [], 'the fastening said in one line')
|
|
@@ -203,9 +203,9 @@ window.probe = {
|
|
|
203
203
|
const l = Math.hypot(b.x - a.x, b.y - a.y) || 1
|
|
204
204
|
return { x: (b.x - a.x) / l, y: (b.y - a.y) / l }
|
|
205
205
|
},
|
|
206
|
-
/** The page point of an input or checkbox by its aria-label. */
|
|
206
|
+
/** The page point of an input or checkbox by its field id (data-field) or its aria-label. */
|
|
207
207
|
labelled(label) {
|
|
208
|
-
const input = deep('input').find(i => i.getAttribute('aria-label') === label)
|
|
208
|
+
const input = deep('input').find(i => (i.dataset?.field ?? i.getAttribute('aria-label')) === label)
|
|
209
209
|
return input ? centreOf(input) : null
|
|
210
210
|
},
|
|
211
211
|
/** The page point of a button by an attribute it carries. */
|
|
@@ -262,7 +262,7 @@ window.probe = {
|
|
|
262
262
|
focused() {
|
|
263
263
|
let at = document.activeElement
|
|
264
264
|
while (at?.shadowRoot?.activeElement) at = at.shadowRoot.activeElement
|
|
265
|
-
return at ? { tag: at.localName, label: at.getAttribute('aria-label') } : null
|
|
265
|
+
return at ? { tag: at.localName, label: at.dataset?.field ?? at.getAttribute('aria-label') } : null
|
|
266
266
|
},
|
|
267
267
|
/** The page point of an input by its aria-label, or of the part name field. */
|
|
268
268
|
nameField() {
|
|
@@ -276,7 +276,7 @@ window.probe = {
|
|
|
276
276
|
},
|
|
277
277
|
/** Whether an input with this label is on the page. */
|
|
278
278
|
hasInput(label) {
|
|
279
|
-
return deep('input').some(i => i.getAttribute('aria-label') === label)
|
|
279
|
+
return deep('input').some(i => (i.dataset?.field ?? i.getAttribute('aria-label')) === label)
|
|
280
280
|
},
|
|
281
281
|
/** The page point of a part's row in the parts list. */
|
|
282
282
|
row(id) {
|
|
@@ -74,7 +74,7 @@ describe('work counted on the robot arm', () => {
|
|
|
74
74
|
|
|
75
75
|
/*
|
|
76
76
|
The tabs are drawn out of sight with the rest: the selected part's motion is evaluated at its two ends, and the size
|
|
77
|
-
editor on
|
|
77
|
+
editor on 「크기」 asks once which of the parent's axes each of the part's own lies along, the figure at rest
|
|
78
78
|
(figureAxisPairsOf, ADR-0159 3) -- one evaluation for all three rows.
|
|
79
79
|
*/
|
|
80
80
|
test('picking a part on the canvas compiles nothing; it evaluates the two ends of the part\'s motion and its axes at rest', async () => {
|
|
@@ -98,8 +98,8 @@ describe('work counted on the robot arm', () => {
|
|
|
98
98
|
ends, and its axes paired at rest for the size editor once more on the new version.
|
|
99
99
|
*/
|
|
100
100
|
test('a value typed into its field: the edit and its read, and nothing else while it is drawn', async () => {
|
|
101
|
-
/* The part's measures are the
|
|
102
|
-
await click(await read(() => probe.tab('figure.label.tab-
|
|
101
|
+
/* The part's measures are the 「크기」 tab's size editor (2026-10-24). */
|
|
102
|
+
await click(await read(() => probe.tab('figure.label.tab-short-response')))
|
|
103
103
|
await settled()
|
|
104
104
|
const field = await read(() => {
|
|
105
105
|
const deep = (root = document) => [...root.querySelectorAll('input[type=number]')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
@@ -189,8 +189,8 @@ describe('the edit canvas and the preview stay standing', () => {
|
|
|
189
189
|
test('a value changed beside the preview is drawn inside the scene that stands', async () => {
|
|
190
190
|
await click(await read(() => probe.row('box')))
|
|
191
191
|
await sleep(300)
|
|
192
|
-
/* The box's size is the
|
|
193
|
-
await click(await read(() => probe.tab('figure.label.tab-
|
|
192
|
+
/* The box's size is the 「크기」 tab's (2026-10-24). */
|
|
193
|
+
await click(await read(() => probe.tab('figure.label.tab-short-response')))
|
|
194
194
|
await sleep(300)
|
|
195
195
|
const field = await read(() => probe.labelled('box Y figure.label.size-measure'))
|
|
196
196
|
assert.ok(field, 'the height field')
|
|
@@ -54,10 +54,10 @@ async function click(point) {
|
|
|
54
54
|
await sleep(400)
|
|
55
55
|
}
|
|
56
56
|
/*
|
|
57
|
-
A side tab pressed, by its short name; how a part answers a size is the
|
|
58
|
-
|
|
57
|
+
A side tab pressed, by its short name; how a part answers a size is the 「크기」 tab's (user 2026-10-17, one name
|
|
58
|
+
whatever is picked since the sweep of 2026-10-27, J2).
|
|
59
59
|
*/
|
|
60
|
-
const tab = async name => click(await read(n => probe.tab(`figure.label.tab-short-${n}`)
|
|
60
|
+
const tab = async name => click(await read(n => probe.tab(`figure.label.tab-short-${n}`), name))
|
|
61
61
|
|
|
62
62
|
const KEYS = { translate: 'KeyW', rotate: 'KeyE', scale: 'KeyR' }
|
|
63
63
|
|
|
@@ -430,7 +430,7 @@ describe('a figure made in the modeller is given its size from its parts (user 2
|
|
|
430
430
|
const field = id =>
|
|
431
431
|
page().evaluateHandle(id => {
|
|
432
432
|
const deep = (root = document) => [...root.querySelectorAll('input')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
433
|
-
return deep().find(i => i.getAttribute('aria-label') === id) ?? null
|
|
433
|
+
return deep().find(i => (i.dataset?.field ?? i.getAttribute('aria-label')) === id) ?? null
|
|
434
434
|
}, id)
|
|
435
435
|
const declared = () =>
|
|
436
436
|
page().evaluate(() => {
|
|
@@ -474,7 +474,7 @@ describe('a figure made in the modeller is given its size from its parts (user 2
|
|
|
474
474
|
const field = () =>
|
|
475
475
|
page().evaluateHandle(() => {
|
|
476
476
|
const deep = (root = document) => [...root.querySelectorAll('input')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
477
|
-
return deep().find(i => i.getAttribute('aria-label') === 'size.x.max') ?? null
|
|
477
|
+
return deep().find(i => (i.dataset?.field ?? i.getAttribute('aria-label')) === 'size.x.max') ?? null
|
|
478
478
|
})
|
|
479
479
|
/* Selected where it is drawn, as a person does, then typed over. */
|
|
480
480
|
const typeOver = async keys => {
|
|
@@ -76,9 +76,9 @@ async function press(...keys) {
|
|
|
76
76
|
for (const key of keys.slice(0, -1).reverse()) await page().keyboard.up(key)
|
|
77
77
|
await sleep(400)
|
|
78
78
|
}
|
|
79
|
-
/* A side tab by its short name
|
|
79
|
+
/* A side tab by its short name: one name whatever is picked (「크기」, sweep 2026-10-27 J2). */
|
|
80
80
|
async function tab(name) {
|
|
81
|
-
await click(await read(n => probe.tab(`figure.label.tab-short-${n}`)
|
|
81
|
+
await click(await read(n => probe.tab(`figure.label.tab-short-${n}`), name), `the ${name} tab`)
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
describe('the parts list: each button does what it says, one step of the session each', () => {
|
|
@@ -189,6 +189,9 @@ describe('the first save of a new figure', () => {
|
|
|
189
189
|
})
|
|
190
190
|
|
|
191
191
|
test('the question asks the name first, focused, with the type written before', async () => {
|
|
192
|
+
/* A new figure saves once it has a part (sweep 2026-10-27 U24): the start's box. */
|
|
193
|
+
assert.equal(await read(() => product.deep('figure-editor >>> button[save]')[0].disabled), true, 'nothing to save before a part')
|
|
194
|
+
await press('figure-editor >>> button[start-shape="box"]', 'a box from the start')
|
|
192
195
|
await press('button[save]', 'save')
|
|
193
196
|
await until(() => product.deep('figure-first-save')[0]?.shadowRoot.querySelector('#type'), 'the first save asked')
|
|
194
197
|
await until(() => { let a = document.activeElement; while (a?.shadowRoot?.activeElement) a = a.shadowRoot.activeElement; return a?.id === 'name' }, 'the name focused')
|
|
@@ -283,6 +286,8 @@ describe('opening another figure', () => {
|
|
|
283
286
|
await read(() => product.deep('figure-editor')[0].updateComplete)
|
|
284
287
|
assert.equal(await read(() => product.deep('figure-editor >>> section[empty-figure]').length), 0)
|
|
285
288
|
assert.equal(await read(() => product.page().context.title), 'figure.label.figure')
|
|
289
|
+
/* Named by the address before it is read: the rail marks the catalogue, not "new figure" (sweep 2026-10-27 U30). */
|
|
290
|
+
assert.equal(await read(() => product.page().context.menu), 'figure-list')
|
|
286
291
|
await read(() => window.letB())
|
|
287
292
|
await until(() => product.page().shadowRoot.querySelector('label[type-field] input')?.value === 'type-b', 'b\'s type')
|
|
288
293
|
})
|
|
@@ -603,6 +608,7 @@ describe('the page around the figure: the shell, the dock, the properties', () =
|
|
|
603
608
|
assert.deepEqual(await read(() => assistant.surface.anchor()), { anchorType: 'figure', anchorId: creationId })
|
|
604
609
|
assert.deepEqual(await read(() => { const { figureId, editorMode } = assistant.surface.hostContext(); return { figureId, editorMode } }), { figureId: creationId, editorMode: 'new' })
|
|
605
610
|
assert.equal((await read(() => product.context())).menu, undefined)
|
|
611
|
+
await press('figure-editor >>> button[start-shape="box"]', 'a box from the start')
|
|
606
612
|
await rename('Gate')
|
|
607
613
|
await read(() => { assistant.forget(); product.forget() })
|
|
608
614
|
await press('button[save]', 'save')
|
|
@@ -94,10 +94,9 @@ const groupsInOrder = () =>
|
|
|
94
94
|
.map(([name]) => name)
|
|
95
95
|
})
|
|
96
96
|
|
|
97
|
-
/* A side tab pressed, by its name
|
|
97
|
+
/* A side tab pressed, by its name: one name whatever is picked (「크기」, sweep 2026-10-27 J2). */
|
|
98
98
|
async function tab(name) {
|
|
99
|
-
|
|
100
|
-
await press(name === 'response' ? `${named}, aside button[tab][aria-label="figure.label.tab-arrangement"]` : named, `the ${name} tab`)
|
|
99
|
+
await press(`aside button[tab][aria-label="figure.label.tab-short-${name}"]`, `the ${name} tab`)
|
|
101
100
|
await read(() => document.querySelector('figure-editor').updateComplete)
|
|
102
101
|
}
|
|
103
102
|
|
|
@@ -152,7 +151,7 @@ describe('the side: six tabs, the view and the releases from the bar', () => {
|
|
|
152
151
|
describe('a part\'s groups in one order, whatever its state', () => {
|
|
153
152
|
before(() => mount('rollers'))
|
|
154
153
|
|
|
155
|
-
/* A box's size is the
|
|
154
|
+
/* A box's size is the 「크기」 tab's (2026-10-24): it has no measures of its own shape left on the properties. */
|
|
156
155
|
test('a part fastened in a row: name, place, look, more; on 「반응형」 its size and its row', async () => {
|
|
157
156
|
await pick('roller')
|
|
158
157
|
await tab('properties')
|
|
@@ -60,10 +60,10 @@ describe('a row\'s preview follows its basis', () => {
|
|
|
60
60
|
const at = await read(() => probe.row('bin'))
|
|
61
61
|
await page().mouse.click(at.x, at.y)
|
|
62
62
|
await until(() => probe.state().selected === 'bin', [], 'bin selected')
|
|
63
|
-
/* The row is the 「반응형」 tab's (user 2026-10-17), named
|
|
64
|
-
const tab = await read(() => probe.tab('figure.label.tab-
|
|
63
|
+
/* The row is the 「반응형」 tab's (user 2026-10-17), named 「크기」 (sweep 2026-10-27 J2). */
|
|
64
|
+
const tab = await read(() => probe.tab('figure.label.tab-short-response'))
|
|
65
65
|
await page().mouse.click(tab.x, tab.y)
|
|
66
|
-
await until(() => document.querySelector('figure-editor').shadowRoot.querySelector('button[tab][on]')?.getAttribute('aria-label') === 'figure.label.tab-
|
|
66
|
+
await until(() => document.querySelector('figure-editor').shadowRoot.querySelector('button[tab][on]')?.getAttribute('aria-label') === 'figure.label.tab-short-response', [], 'the 크기 tab on')
|
|
67
67
|
await click('details[row-fold] > summary', {}, 'the row\'s fold')
|
|
68
68
|
})
|
|
69
69
|
|
|
@@ -82,8 +82,8 @@ const tabOn = () => read(() => screenCheck.editor().shadowRoot.querySelector('bu
|
|
|
82
82
|
const pressBar = action => press(action, a => screenCheck.editor().shadowRoot.querySelector('ox-canvas-bar').shadowRoot.querySelector(`button[data-action=${a}]`), action)
|
|
83
83
|
const pressTab = name => press(name, name => screenCheck.editor().shadowRoot.querySelector(`button[tab][aria-label="${name}"]`), name)
|
|
84
84
|
const TAB = name => `figure.label.tab-short-${name}`
|
|
85
|
-
/*
|
|
86
|
-
const ARRANGEMENT = 'figure.label.tab-
|
|
85
|
+
/* The size tab, one name whatever is picked (「크기」, sweep 2026-10-27 J2; it was 「배치」 while a part was chosen). */
|
|
86
|
+
const ARRANGEMENT = 'figure.label.tab-short-response'
|
|
87
87
|
/*
|
|
88
88
|
The view settings or the releases, opened from the bar over the work (they were tabs once); left open if open. The
|
|
89
89
|
releases open from 「발행」, the bar's one way to them (user 2026-10-18).
|
|
@@ -220,7 +220,9 @@ describe('the frame: parts · canvas · one side of seven tabs, and the bar over
|
|
|
220
220
|
test('the bar: undo and redo with their keys, the status, save, and the left pane folded to give the canvas its width', async () => {
|
|
221
221
|
assert.equal(await read(() => screenCheck.editor().shadowRoot.querySelector('ox-canvas-bar').pane === screenCheck.editor().shadowRoot.querySelector('ox-three-pane')), true, 'the bar folds the editor\'s own frame')
|
|
222
222
|
const titles = await read(() => ['undo', 'redo'].map(a => screenCheck.editor().shadowRoot.querySelector('ox-canvas-bar').shadowRoot.querySelector(`button[data-action=${a}]`).title))
|
|
223
|
-
|
|
223
|
+
/* The keys as this platform writes them (sweep 2026-10-27 U26): ⌘Z · ⇧⌘Z on a Mac, Ctrl+Z · Ctrl+Y elsewhere. */
|
|
224
|
+
const mac = await read(() => navigator.userAgent.includes('Mac'))
|
|
225
|
+
assert.ok(titles[0].endsWith(mac ? '(⌘Z)' : '(Ctrl+Z)') && titles[1].endsWith(mac ? '(⇧⌘Z)' : '(Ctrl+Y)'), `the keys in the titles: ${titles}`)
|
|
224
226
|
/* Saved or not is the save button's: pressable with something to save, dimmed without -- no words beside it. */
|
|
225
227
|
const status = () => read(() => screenCheck.editor().shadowRoot.querySelector('ox-canvas-bar [role=status]')?.textContent.trim() ?? null)
|
|
226
228
|
const saveOn = () => read(() => !screenCheck.editor().shadowRoot.querySelector('ox-canvas-bar button[save]').disabled)
|
|
@@ -228,7 +230,7 @@ describe('the frame: parts · canvas · one side of seven tabs, and the bar over
|
|
|
228
230
|
assert.equal(await saveOn(), false, 'saved: the save button is dimmed')
|
|
229
231
|
|
|
230
232
|
await pressRow('box')
|
|
231
|
-
/* A part's size is the
|
|
233
|
+
/* A part's size is the 「크기」 tab's (2026-10-24). */
|
|
232
234
|
await pressTab(ARRANGEMENT)
|
|
233
235
|
const height = await element('the height field', () => screenCheck.deep('input').find(i => i.getAttribute('aria-label') === 'box Y figure.label.size-measure'))
|
|
234
236
|
await height.click({ clickCount: 3 })
|
|
@@ -339,10 +341,10 @@ describe('a part chosen: each tab in its place, the figure-wide tabs give way, t
|
|
|
339
341
|
await pressTab(TAB('properties'))
|
|
340
342
|
let now = await holds()
|
|
341
343
|
assert.ok(now.subject, 'the part\'s head')
|
|
342
|
-
/* A box's size is the
|
|
344
|
+
/* A box's size is the 「크기」 tab's, in its size editor (2026-10-24): the properties keep its place. */
|
|
343
345
|
assert.ok(now.links.includes('figure.label.group-place') && !now.links.includes('figure.label.group-size'), `the shape's links: ${now.links}`)
|
|
344
346
|
assert.ok(now.material, 'its material')
|
|
345
|
-
/* How it answers a size has a tab of its own (user 2026-10-17, 「반응형」), named
|
|
347
|
+
/* How it answers a size has a tab of its own (user 2026-10-17, 「반응형」), named 「크기」. */
|
|
346
348
|
await pressTab(ARRANGEMENT)
|
|
347
349
|
now = await holds()
|
|
348
350
|
assert.ok(now.subject, 'the part\'s head')
|
|
@@ -732,7 +734,7 @@ describe('the camera follows a size typed far from the view (first 15 minutes, 2
|
|
|
732
734
|
test('a width typed ten times larger fits the view again; a little wider leaves the camera where it is', async () => {
|
|
733
735
|
await mount('box')
|
|
734
736
|
await pressRow('box')
|
|
735
|
-
/* A part's size is the
|
|
737
|
+
/* A part's size is the 「크기」 tab's (2026-10-24). */
|
|
736
738
|
await pressTab(ARRANGEMENT)
|
|
737
739
|
await read(() => {
|
|
738
740
|
const canvas = screenCheck.deep('figure-canvas')[0]
|
|
@@ -806,7 +808,7 @@ describe('a part\'s size row: a rule\'s numbers only where a rule sets every sid
|
|
|
806
808
|
})
|
|
807
809
|
|
|
808
810
|
/*
|
|
809
|
-
A box's size is the
|
|
811
|
+
A box's size is the 「크기」 tab's size editor (2026-10-24), one row a side: made to follow on every side, each row
|
|
810
812
|
shows the size the rule gives it, and the properties draw no size row.
|
|
811
813
|
*/
|
|
812
814
|
test('a box made to follow the figure\'s size on every side shows the size the rule gives it', async () => {
|
|
@@ -848,7 +850,7 @@ describe('the side panel is found as it was left, per part', () => {
|
|
|
848
850
|
before(() => mount('arm'))
|
|
849
851
|
|
|
850
852
|
/*
|
|
851
|
-
The part's properties fit a tall window since its size moved to
|
|
853
|
+
The part's properties fit a tall window since its size moved to 「크기」 (2026-10-24): a shorter one gives the panel
|
|
852
854
|
something to scroll.
|
|
853
855
|
*/
|
|
854
856
|
test('scrolled and a fold opened on one part; another part opens at its top; back, the first is as it was left', async () => {
|
|
@@ -1106,7 +1108,7 @@ describe('the modeller page: the editor with the page\'s figure panel, verdict a
|
|
|
1106
1108
|
|
|
1107
1109
|
test('an edit is judged as it stands on screen: the source the server is sent is the one edited', async () => {
|
|
1108
1110
|
await pressRow('left')
|
|
1109
|
-
/* A part's size is the
|
|
1111
|
+
/* A part's size is the 「크기」 tab's (2026-10-24). */
|
|
1110
1112
|
await pressTab(ARRANGEMENT)
|
|
1111
1113
|
const height = await element('the height field', () => screenCheck.deep('input').find(i => i.getAttribute('aria-label') === 'left Y figure.label.size-measure'))
|
|
1112
1114
|
await height.click({ clickCount: 3 })
|
|
@@ -1148,7 +1150,7 @@ describe('the modeller page: the editor with the page\'s figure panel, verdict a
|
|
|
1148
1150
|
}
|
|
1149
1151
|
})
|
|
1150
1152
|
|
|
1151
|
-
/* The part's properties fit a tall window since its size moved to
|
|
1153
|
+
/* The part's properties fit a tall window since its size moved to 「크기」 (2026-10-24): a shorter one scrolls. */
|
|
1152
1154
|
test('another figure opened starts its panels clean: a part of the same name opens at its top', async () => {
|
|
1153
1155
|
const viewport = page().viewport()
|
|
1154
1156
|
await page().setViewport({ ...viewport, height: 600 })
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
inputTableAt,
|
|
15
15
|
namesWith,
|
|
16
16
|
needsMissing,
|
|
17
|
+
needsOffered,
|
|
17
18
|
partsAt,
|
|
18
19
|
settingsIssues,
|
|
19
20
|
settingsOf,
|
|
@@ -58,6 +59,12 @@ test('what a capability still needs: the item mark, a name; put on and off keeps
|
|
|
58
59
|
assert.deepEqual(needsMissing(CARRIER, asset), ['item'])
|
|
59
60
|
assert.deepEqual(needsMissing(CARRIER, { ...asset, item: true }), [])
|
|
60
61
|
assert.deepEqual(needsMissing({ ...(CARRIER as object), needs: ['Holdable', ['Capacity', 'FlowNode']] } as never, asset), ['Holdable', 'Capacity | FlowNode'])
|
|
62
|
+
/* Offered as the tab offers it (sweep 2026-10-27 U19): the item tag, and the rungs that give what is needed -- never its names. */
|
|
63
|
+
assert.deepEqual(needsOffered(CARRIER, asset), { item: true, rungs: [], names: [] })
|
|
64
|
+
const holder = { ...(CARRIER as object), needs: [['Holdable', 'FlowNode'], ['Capacity', 'FlowNode']] } as never
|
|
65
|
+
assert.deepEqual(needsOffered(holder, asset), { item: false, rungs: ['hold', 'flow'], names: [] })
|
|
66
|
+
assert.deepEqual(needsOffered({ ...(CARRIER as object), needs: ['FlowNode'] } as never, asset), { item: false, rungs: ['flow'], names: [] })
|
|
67
|
+
assert.deepEqual(needsOffered({ ...(CARRIER as object), needs: ['Nowhere'] } as never, asset), { item: false, rungs: [], names: ['Nowhere'] })
|
|
61
68
|
const held = editFigurePart(asset, { kind: 'set-capabilities', names: ['Capacity', 'Holdable'] })
|
|
62
69
|
assert.deepEqual(namesWith(held, 'acme.Carrier', true), ['Capacity', 'Holdable', 'acme.Carrier'])
|
|
63
70
|
assert.deepEqual(namesWith(editFigurePart(held, { kind: 'set-capabilities', names: namesWith(held, 'acme.Carrier', true) }), 'acme.Carrier', false), ['Capacity', 'Holdable'])
|
package/test/links.test.ts
CHANGED
|
@@ -630,7 +630,7 @@ test('팔레트의 통은 통의 크기로 서고, 세 축 모두 크기를 따
|
|
|
630
630
|
assert.deepEqual(followableAxes(asset, 'tote'), ['x', 'y', 'z'])
|
|
631
631
|
assert.deepEqual(gizmoReachOf(asset as never, 'tote').sizes, ['tote.dimension.width', 'tote.dimension.height', 'tote.dimension.depth'])
|
|
632
632
|
const ko = wordsOf('ko')
|
|
633
|
-
assert.equal(ko['figure.label.face-inside-bottom'], '안쪽
|
|
633
|
+
assert.equal(ko['figure.label.face-inside-bottom'], '안쪽 바닥면')
|
|
634
634
|
})
|
|
635
635
|
|
|
636
636
|
/*
|
package/test/refusals.test.ts
CHANGED
|
@@ -205,7 +205,7 @@ test('일상에서 코드로만 보이던 거절 넷 — 줄이 안 들어감 ·
|
|
|
205
205
|
layout: { tiers: [{ tierNo: 1, anchorId: 'bin', mount: 'ON', position: { x: 0, y: 0, z: 0 }, rotation: 0, layout: { pattern: 'SINGLE', slot: { allowed: { width: 100, depth: 100, height: 100 }, accepts: ['TOTE'] } } }] }
|
|
206
206
|
})
|
|
207
207
|
const held = editRefusalOf(refusalOf(() => act(slotted, { kind: 'make-repeat', part: 'bin', axis: 'x', pitch: 150 })), say)
|
|
208
|
-
assert.ok(held.includes('bin') && held.includes('
|
|
208
|
+
assert.ok(held.includes('bin') && held.includes('자리 배치') && !held.includes('SURFACE_PROVENANCE'), held)
|
|
209
209
|
/* A new part named as the row's part is (its copies drawn under ids of their own): the same words as a rename. */
|
|
210
210
|
assert.match(editRefusalOf(refusalOf(() => fm.addFigureBox(conveyor(), { id: 'roller', size: { x: 1, y: 1, z: 1 }, position: { x: 0, y: 0, z: 0 } })), keyed), /part-name-taken .*"name":"roller"/)
|
|
211
211
|
})
|
|
@@ -49,10 +49,10 @@ function tsSibling(specifier, parentURL, stripExtension) {
|
|
|
49
49
|
|
|
50
50
|
/*
|
|
51
51
|
* ── 패키지 이름 하나만 예외다: `@hatiolab/things-scene` (2026-09-24) ─────────────────────────
|
|
52
|
-
* 이 패키지의 client 는 브라우저 코드이고, things-scene 은
|
|
53
|
-
* 서버 전송용 `things-scene-transfer.mjs`. Node 는 `exports` 의 `node` 조건으로 전송
|
|
52
|
+
* 이 패키지의 client 는 브라우저 코드이고, things-scene 은 버전이 둘이다 — 브라우저용 `things-scene.mjs` 와
|
|
53
|
+
* 서버 전송용 `things-scene-transfer.mjs`. Node 는 `exports` 의 `node` 조건으로 전송 버전을 고르는데, 그 버전에는
|
|
54
54
|
* figure 가 없다(`buildFigureGeometry` · `registerFigure` …). 모델러 소스를 Node 에서 시험하면 **브라우저가
|
|
55
|
-
* 받을
|
|
55
|
+
* 받을 버전**을 봐야 맞다. 그래서 이 이름만 브라우저 버전으로 되짚는다. 다른 패키지 이름은 그대로 둔다.
|
|
56
56
|
*/
|
|
57
57
|
const SCENE_BROWSER_BUNDLE = (() => {
|
|
58
58
|
try {
|