@things-factory/figure-ui 10.1.224 → 10.1.226
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/consumer-capabilities.ts +15 -0
- package/client/modeller/editor-styles.ts +21 -3
- package/client/modeller/editor.ts +41 -27
- package/client/modeller/figure-capability-panel.ts +37 -10
- package/client/modeller/figure-clips.ts +3 -2
- package/client/modeller/figure-controls.ts +4 -3
- package/client/modeller/figure-data-panel.ts +41 -21
- package/client/modeller/figure-links.ts +5 -5
- package/client/modeller/figure-material-panel.ts +2 -0
- package/client/modeller/figure-preview.ts +115 -8
- 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/product-session.ts +3 -2
- package/client/modeller/unit-words.ts +11 -0
- package/client/pages/figure-modeller-page.ts +5 -4
- 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 +21 -3
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.js +43 -29
- 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 +35 -11
- package/dist-client/modeller/figure-capability-panel.js.map +1 -1
- package/dist-client/modeller/figure-clips.js +3 -2
- 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 +42 -21
- package/dist-client/modeller/figure-data-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 +124 -8
- 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/product-session.d.ts +4 -2
- package/dist-client/modeller/product-session.js +3 -2
- package/dist-client/modeller/product-session.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-modeller-page.js +5 -4
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/test/ai-proposal.test.ts +2 -2
- 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 +31 -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/browser/side-panel-sweep.check.mjs +182 -0
- package/test/consumer-capabilities.test.ts +7 -0
- package/test/figure-assistant-surface.test.ts +1 -1
- package/test/first-save.test.ts +2 -2
- package/test/links.test.ts +1 -1
- package/test/product-session.test.ts +5 -5
- package/test/proposal-record.test.ts +3 -3
- package/test/refusals.test.ts +1 -1
- package/test/revert-keeping.test.ts +1 -1
- package/translations/en.json +24 -12
- package/translations/es.json +24 -12
- package/translations/ja.json +25 -13
- package/translations/ko.json +83 -71
- package/translations/ms.json +24 -12
- package/translations/zh.json +24 -12
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.226",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "npm run build:server && npm run build:client",
|
|
20
20
|
"test": "FIGURE_FREEZE_COMPILED=1 node --import ./test/resolve-ts-siblings.mjs --test test/*.test.ts",
|
|
21
|
-
"test:browser": "node --test test/browser/build.check.mjs test/browser/modeller.check.mjs test/browser/modeller-revision.check.mjs test/browser/editing.check.mjs test/browser/links.check.mjs test/browser/section.check.mjs test/browser/screen.check.mjs test/browser/product-page.check.mjs test/browser/parts.check.mjs test/browser/places-mesh.check.mjs test/browser/part-ids.check.mjs test/browser/fix-alignment.check.mjs test/browser/mesh-select.check.mjs test/browser/row-preview.check.mjs test/browser/detail-tabs.check.mjs test/browser/props.check.mjs test/browser/retire.check.mjs test/browser/namespace.check.mjs test/browser/public.check.mjs test/browser/category.check.mjs test/browser/property-section.check.mjs test/browser/list.check.mjs test/browser/item.check.mjs",
|
|
21
|
+
"test:browser": "node --test test/browser/build.check.mjs test/browser/modeller.check.mjs test/browser/modeller-revision.check.mjs test/browser/editing.check.mjs test/browser/links.check.mjs test/browser/section.check.mjs test/browser/screen.check.mjs test/browser/product-page.check.mjs test/browser/parts.check.mjs test/browser/places-mesh.check.mjs test/browser/part-ids.check.mjs test/browser/fix-alignment.check.mjs test/browser/mesh-select.check.mjs test/browser/row-preview.check.mjs test/browser/detail-tabs.check.mjs test/browser/props.check.mjs test/browser/retire.check.mjs test/browser/namespace.check.mjs test/browser/public.check.mjs test/browser/category.check.mjs test/browser/property-section.check.mjs test/browser/list.check.mjs test/browser/item.check.mjs test/browser/side-panel-sweep.check.mjs",
|
|
22
22
|
"copy:files": "copyfiles -e \"./client/**/*.{ts,js,json}\" -u 1 \"./client/**/*\" dist-client",
|
|
23
23
|
"build:client": "npm run copy:files && npx tsc --p ./client/tsconfig.json",
|
|
24
24
|
"build:server": "npx tsc --p ./server/tsconfig.json",
|
|
@@ -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.103",
|
|
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.226",
|
|
46
|
+
"@things-factory/figure-service": "^10.1.226",
|
|
47
|
+
"@things-factory/figure-viewer": "^10.1.226",
|
|
48
|
+
"@things-factory/setting-base": "^10.1.226",
|
|
49
49
|
"three": "^0.185.1"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "abc5cf99effa2de579a1cbfda74ddc5eb41dba50"
|
|
52
52
|
}
|
package/test/ai-proposal.test.ts
CHANGED
|
@@ -157,7 +157,7 @@ test('the same new figure\'s card in another tab before the first save makes a s
|
|
|
157
157
|
})
|
|
158
158
|
const made = [{ kind: 'add-part', part: { id: 'lid', kind: 'box', dimensions: { width: 100, height: 10, depth: 100 } } }] as any[]
|
|
159
159
|
const first = tab()
|
|
160
|
-
first.startNew()
|
|
160
|
+
first.startNew({ name: 'New' })
|
|
161
161
|
const detail: FigureApplyDetail = { key: K, steps: made, create: true }
|
|
162
162
|
const here = { ...taker({}), offer: (d: FigureApplyDetail) => (takeFigureOffer(first, d, { onSavedFigure: false, say }), d) }
|
|
163
163
|
assert.equal((await runFigureProposal(detail, here, say)).verdict, 'accepted')
|
|
@@ -166,7 +166,7 @@ test('the same new figure\'s card in another tab before the first save makes a s
|
|
|
166
166
|
const there = {
|
|
167
167
|
...taker({}),
|
|
168
168
|
async openNew(s: FigureProposalStep[], key?: string) {
|
|
169
|
-
second.startNew()
|
|
169
|
+
second.startNew({ name: 'New' })
|
|
170
170
|
second.createUnder(key!)
|
|
171
171
|
second.applyProposal(s, second.read().revision!, second.read().context!, key)
|
|
172
172
|
return { applied: true }
|
|
@@ -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', () => {
|
|
@@ -173,6 +173,11 @@ describe('the first save of a new figure', () => {
|
|
|
173
173
|
return { first: popup.shadowRoot.querySelector('input')?.id, type: popup.shadowRoot.querySelector('#type').value, focused: active?.id }
|
|
174
174
|
})
|
|
175
175
|
|
|
176
|
+
test('a new figure is named in the screen\'s words, the tab too (sweep 2026-10-27 U8)', async () => {
|
|
177
|
+
assert.equal(await read(() => product.page().session.read().name), 'figure.text.new-figure-name')
|
|
178
|
+
assert.equal(await read(() => product.page().context.title), 'figure.text.new-figure-name')
|
|
179
|
+
})
|
|
180
|
+
|
|
176
181
|
test('before it is saved: the type can be written, held by the page, and the release waits', async () => {
|
|
177
182
|
const field = await read(() => { const input = product.page().shadowRoot.querySelector('label[type-field] input'); return input && { disabled: input.disabled } })
|
|
178
183
|
assert.deepEqual(field, { disabled: false })
|
|
@@ -184,6 +189,9 @@ describe('the first save of a new figure', () => {
|
|
|
184
189
|
})
|
|
185
190
|
|
|
186
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')
|
|
187
195
|
await press('button[save]', 'save')
|
|
188
196
|
await until(() => product.deep('figure-first-save')[0]?.shadowRoot.querySelector('#type'), 'the first save asked')
|
|
189
197
|
await until(() => { let a = document.activeElement; while (a?.shadowRoot?.activeElement) a = a.shadowRoot.activeElement; return a?.id === 'name' }, 'the name focused')
|
|
@@ -242,6 +250,22 @@ describe('opening another figure', () => {
|
|
|
242
250
|
})
|
|
243
251
|
})
|
|
244
252
|
|
|
253
|
+
test('nothing open on the page: its properties say nothing of an empty figure (sweep 2026-10-27 U3)', async () => {
|
|
254
|
+
await open('a')
|
|
255
|
+
const empty = await read(async () => {
|
|
256
|
+
const page = product.page()
|
|
257
|
+
/* The state a page is in before it is told which figure, and after it is left: the session holds none. */
|
|
258
|
+
page.session.close()
|
|
259
|
+
const editor = product.deep('figure-editor')[0]
|
|
260
|
+
editor.requestUpdate()
|
|
261
|
+
await editor.updateComplete
|
|
262
|
+
return editor.shadowRoot.querySelectorAll('section[empty-figure]').length
|
|
263
|
+
})
|
|
264
|
+
/* Told another figure, the page opens it again: the checks after this one start from an open page. */
|
|
265
|
+
await open('b')
|
|
266
|
+
assert.equal(empty, 0)
|
|
267
|
+
})
|
|
268
|
+
|
|
245
269
|
test('the row is emptied first: the type of the one before does not stand while the next one loads', async () => {
|
|
246
270
|
const typeField = () => read(() => product.page().shadowRoot.querySelector('label[type-field] input')?.value)
|
|
247
271
|
await open('a')
|
|
@@ -258,6 +282,12 @@ describe('opening another figure', () => {
|
|
|
258
282
|
await until(() => !!window.letB, 'b asked for')
|
|
259
283
|
await read(() => product.page().updateComplete)
|
|
260
284
|
assert.equal(await typeField(), '')
|
|
285
|
+
/* Still loading, the figure is not an empty one: no 「add a part」 (sweep 2026-10-27 U3). */
|
|
286
|
+
await read(() => product.deep('figure-editor')[0].updateComplete)
|
|
287
|
+
assert.equal(await read(() => product.deep('figure-editor >>> section[empty-figure]').length), 0)
|
|
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')
|
|
261
291
|
await read(() => window.letB())
|
|
262
292
|
await until(() => product.page().shadowRoot.querySelector('label[type-field] input')?.value === 'type-b', 'b\'s type')
|
|
263
293
|
})
|
|
@@ -578,6 +608,7 @@ describe('the page around the figure: the shell, the dock, the properties', () =
|
|
|
578
608
|
assert.deepEqual(await read(() => assistant.surface.anchor()), { anchorType: 'figure', anchorId: creationId })
|
|
579
609
|
assert.deepEqual(await read(() => { const { figureId, editorMode } = assistant.surface.hostContext(); return { figureId, editorMode } }), { figureId: creationId, editorMode: 'new' })
|
|
580
610
|
assert.equal((await read(() => product.context())).menu, undefined)
|
|
611
|
+
await press('figure-editor >>> button[start-shape="box"]', 'a box from the start')
|
|
581
612
|
await rename('Gate')
|
|
582
613
|
await read(() => { assistant.forget(); product.forget() })
|
|
583
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 })
|