@things-factory/figure-ui 10.1.225 → 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 +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-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-modeller-page.ts +3 -2
- 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-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-modeller-page.js +3 -2
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- 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/translations/en.json +23 -12
- package/translations/es.json +23 -12
- package/translations/ja.json +24 -13
- package/translations/ko.json +82 -71
- package/translations/ms.json +23 -12
- package/translations/zh.json +23 -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"
|
|
@@ -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
|
}
|
|
@@ -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
|
})
|
package/translations/en.json
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"figure.label.group-more": "More",
|
|
20
20
|
"figure.label.group-place": "Place",
|
|
21
21
|
"figure.label.group-position-response": "Position",
|
|
22
|
-
"figure.label.tab-response": "
|
|
23
|
-
"figure.label.tab-short-response": "
|
|
22
|
+
"figure.label.tab-response": "Size · when the size changes",
|
|
23
|
+
"figure.label.tab-short-response": "Size",
|
|
24
24
|
"figure.text.sizing-uniform-proposed": "this figure has joints. growing it by one ratio keeps the joints together at any size.",
|
|
25
25
|
"figure.text.sizing-uniform-explained": "grows the whole figure by the geometric mean of the three sizes a board gives it. the feet stay on the floor; room left above the figure stays empty.",
|
|
26
26
|
"figure.button.mirror-x": "across X",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"figure.text.gizmo-joint-origin-follows": "the {joint} joint's place follows another part. change it under place in the properties tab.",
|
|
78
78
|
"figure.text.gizmo-joint-off-axis": "the {joint} joint turns only about {axis}. use the {axis} rotate handle.",
|
|
79
79
|
"figure.text.gizmo-joint-several-axes": "the {joint} joint moves on several axes, so it cannot be dragged. adjust its values on the motion tab.",
|
|
80
|
-
"figure.text.gizmo-size-follows": "{part}'s {axis} size follows another measure. change it
|
|
80
|
+
"figure.text.gizmo-size-follows": "{part}'s {axis} size follows another measure. change it in the size tab.",
|
|
81
81
|
"figure.text.gizmo-fastening-places": "{part} is fastened to a face of another part, which places it. detach it first to move it.",
|
|
82
82
|
"figure.text.size-range-leaves-out-size": "the {axis} range on a drawing, {range} mm, does not hold its size now, {at} mm.",
|
|
83
83
|
"figure.text.size-range-from-zero": "the smallest {axis} on a drawing must be above 0.",
|
|
@@ -360,6 +360,15 @@
|
|
|
360
360
|
"figure.label.triangles": "triangles",
|
|
361
361
|
"figure.label.unit-none": "no unit",
|
|
362
362
|
"figure.label.unit-type-it": "type it…",
|
|
363
|
+
"figure.label.unit-mm": "mm",
|
|
364
|
+
"figure.label.unit-cm": "cm",
|
|
365
|
+
"figure.label.unit-m": "m",
|
|
366
|
+
"figure.label.unit-rad": "radians",
|
|
367
|
+
"figure.label.unit-deg": "degrees",
|
|
368
|
+
"figure.label.unit-s": "seconds",
|
|
369
|
+
"figure.label.unit-ratio": "ratio",
|
|
370
|
+
"figure.label.unit-percent": "%",
|
|
371
|
+
"figure.label.unit-count": "count",
|
|
363
372
|
"figure.label.weight": "drawing cost",
|
|
364
373
|
"figure.text.accepts-anything": "empty means anything",
|
|
365
374
|
"figure.text.accepts-explained": "types allowed in this slot. separate with commas; leave empty for no limit.",
|
|
@@ -381,6 +390,8 @@
|
|
|
381
390
|
"figure.text.trend-flat-lines-overlap": "the flat lines overlap at {value}.",
|
|
382
391
|
"figure.text.cannot-release-now": "cannot be released yet",
|
|
383
392
|
"figure.text.nothing-blocks-release": "nothing in the figure as it stands blocks releasing it.",
|
|
393
|
+
"figure.text.release-needs-a-part": "Judged once a part is placed.",
|
|
394
|
+
"figure.text.save-needs-a-part": "Place a part to save.",
|
|
384
395
|
"figure.text.verdict-asking": "checking…",
|
|
385
396
|
"figure.text.verdict-not-received": "the check did not come back — {reason}",
|
|
386
397
|
"figure.text.verdict-waiting": "the check follows your edits.",
|
|
@@ -848,16 +859,17 @@
|
|
|
848
859
|
"figure.label.tab-short-capability": "Capability",
|
|
849
860
|
"figure.label.tab-short-motion": "Motion",
|
|
850
861
|
"figure.label.tab-short-verdict": "Check",
|
|
862
|
+
"figure.label.tab-verdict": "Check · maturity and the release verdict",
|
|
851
863
|
"figure.label.tab-short-view": "View",
|
|
852
864
|
"figure.label.tab-short-history": "History",
|
|
853
865
|
"figure.text.start-question": "What would you like to start with?",
|
|
854
866
|
"figure.label.start-shape": "Add a basic shape",
|
|
855
867
|
"figure.text.start-shape-more": "More shapes are in the list on the left.",
|
|
868
|
+
"figure.button.start-shape-more": "More parts",
|
|
856
869
|
"figure.label.start-recipe": "Start from a recipe",
|
|
857
870
|
"figure.button.start-recipe": "Browse recipes",
|
|
858
871
|
"figure.label.start-ai": "Draft with AI",
|
|
859
872
|
"figure.button.start-ai": "Ask AI for a draft",
|
|
860
|
-
"figure.text.start-ai-optional": "Optional. You can build it yourself.",
|
|
861
873
|
"figure.text.empty-figure-next": "Add a part to see its size and placement.",
|
|
862
874
|
"figure.label.figure-info-later": "Figure information · you can set this later",
|
|
863
875
|
"figure.text.parts-panel-folded-narrow": "The screen is narrow, so the parts list is folded. The panel button in the toolbar opens it again.",
|
|
@@ -1204,7 +1216,6 @@
|
|
|
1204
1216
|
"figure.text.size-unpaired": "it lies at an angle, so the axis to follow cannot be told. pick a source and an axis.",
|
|
1205
1217
|
"figure.label.repeat-inactive": "Not applied",
|
|
1206
1218
|
"figure.label.repeat-active": "Applied",
|
|
1207
|
-
"figure.label.tab-arrangement": "Layout",
|
|
1208
1219
|
"figure.text.repeat-intro": "Repeat this part along the length of the part it is attached to.",
|
|
1209
1220
|
"figure.text.repeat-direction": "Direction uses the attached parent’s axes.",
|
|
1210
1221
|
"figure.text.repeat-draft": "Preview only. Apply to confirm this layout.",
|
|
@@ -1216,11 +1227,8 @@
|
|
|
1216
1227
|
"figure.button.state-size-fix": "Back to fixed",
|
|
1217
1228
|
"figure.label.state-size-measure": "Measure",
|
|
1218
1229
|
"figure.label.state-size-unit": "Unit",
|
|
1219
|
-
"figure.label.state-
|
|
1220
|
-
"figure.label.state-
|
|
1221
|
-
"figure.label.state-unit-mm": "mm",
|
|
1222
|
-
"figure.label.state-size-at-min": "At its least",
|
|
1223
|
-
"figure.label.state-size-at-max": "At its most",
|
|
1230
|
+
"figure.label.state-size-at-min": "at {at}",
|
|
1231
|
+
"figure.label.state-size-at-max": "at {at}",
|
|
1224
1232
|
"figure.button.add-state-size": "Add size by state",
|
|
1225
1233
|
"figure.text.summary-size-state": "Its {axes} changes with {control}.",
|
|
1226
1234
|
"figure.label.follows-state": "by {control}",
|
|
@@ -1279,9 +1287,12 @@
|
|
|
1279
1287
|
"figure.text.data-table-target-part": "Part {id} is not there, or is chosen twice.",
|
|
1280
1288
|
"figure.label.product-capabilities": "Product capabilities",
|
|
1281
1289
|
"figure.text.product-capability-needs": "Needs first: {needs}",
|
|
1290
|
+
"figure.text.product-capability-needs-rungs": "the {rungs} function",
|
|
1291
|
+
"figure.label.or-between": " or ",
|
|
1282
1292
|
"figure.text.product-capability-unknown": "The figure wears {name}, a product capability this modeller does not know.",
|
|
1283
1293
|
"figure.button.product-capability-remove": "Remove",
|
|
1284
1294
|
"figure.text.data-capability-fields": "Data {name} reads itself: {fields}.",
|
|
1285
|
-
"figure.
|
|
1286
|
-
"figure.text.data-binding-rests
|
|
1295
|
+
"figure.label.data-binding-resting": "resting",
|
|
1296
|
+
"figure.text.data-binding-rests": "In {where} the {name} capability does this instead.",
|
|
1297
|
+
"figure.text.data-binding-rests-targets": "In {where} the {name} capability does this instead for: {targets}"
|
|
1287
1298
|
}
|