@things-factory/figure-ui 10.1.57 → 10.1.59
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/v3-editor.ts +32 -4
- package/client/modeller/v3-links.ts +39 -1
- package/dist-client/modeller/v3-clips.d.ts +1 -1
- package/dist-client/modeller/v3-editor.js +31 -5
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-links.d.ts +17 -1
- package/dist-client/modeller/v3-links.js +41 -1
- package/dist-client/modeller/v3-links.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/test/browser/modeller.check.mjs +69 -0
- package/test/v3-links.test.ts +40 -1
- package/translations/en.json +4 -1
- package/translations/ja.json +4 -1
- package/translations/ko.json +4 -1
- package/translations/ms.json +4 -1
- package/translations/zh.json +4 -1
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.59",
|
|
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.1.
|
|
31
|
-
"@hatiolab/things-scene": "^10.1.
|
|
30
|
+
"@hatiolab/figure-model": "^0.1.69",
|
|
31
|
+
"@hatiolab/things-scene": "^10.1.46",
|
|
32
32
|
"@material/web": "^2.0.0",
|
|
33
33
|
"@operato/data-grist": "^10.0.0",
|
|
34
34
|
"@operato/graphql": "^10.0.0",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"@operato/shell": "^10.0.0",
|
|
40
40
|
"@operato/styles": "^10.0.0",
|
|
41
41
|
"@operato/utils": "^10.0.0",
|
|
42
|
-
"@things-factory/ai-assistant": "^10.1.
|
|
43
|
-
"@things-factory/figure-service": "^10.1.
|
|
42
|
+
"@things-factory/ai-assistant": "^10.1.58",
|
|
43
|
+
"@things-factory/figure-service": "^10.1.59",
|
|
44
44
|
"three": "^0.185.1"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "d3306f7f30f7824b9febbc51d6f8f05266fd22e3"
|
|
47
47
|
}
|
|
@@ -377,6 +377,39 @@ describe('a figure made in the modeller is given its size from its parts (user 2
|
|
|
377
377
|
assert.deepEqual(await read(() => probe.fields('size.')), { 'size.x': 400, 'size.y': 400, 'size.z': 400 }, 'the size it is authored at: its parts now')
|
|
378
378
|
})
|
|
379
379
|
|
|
380
|
+
test('the sizes it may be placed at are typed: the largest width, one step, and a smallest past its size refused', async () => {
|
|
381
|
+
const field = id =>
|
|
382
|
+
page().evaluateHandle(id => {
|
|
383
|
+
const deep = (root = document) => [...root.querySelectorAll('input')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
384
|
+
return deep().find(i => i.getAttribute('aria-label') === id) ?? null
|
|
385
|
+
}, id)
|
|
386
|
+
const declared = () =>
|
|
387
|
+
page().evaluate(() => {
|
|
388
|
+
const deep = (root = document) => [...root.querySelectorAll('figure-v3-editor')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
389
|
+
const inputs = JSON.parse(deep()[0].session.source()).document.model.inputs
|
|
390
|
+
return Object.fromEntries(inputs.filter(i => i.id.startsWith('size.')).map(i => [i.id, [i.min, i.max]]))
|
|
391
|
+
})
|
|
392
|
+
const type = async (id, text) => {
|
|
393
|
+
const input = await field(id)
|
|
394
|
+
assert.ok(await input.evaluate(i => !!i), `${id} is a field`)
|
|
395
|
+
await input.evaluate(i => i.scrollIntoView({ block: 'center' }))
|
|
396
|
+
await input.click({ clickCount: 3 })
|
|
397
|
+
await page().keyboard.type(text)
|
|
398
|
+
await page().keyboard.press('Tab')
|
|
399
|
+
await sleep(600)
|
|
400
|
+
}
|
|
401
|
+
const start = await state()
|
|
402
|
+
await type('size.x.max', '3000')
|
|
403
|
+
const after = await state()
|
|
404
|
+
assert.equal(after.error, '', 'not refused')
|
|
405
|
+
assert.equal(after.revision, start.revision + 1, 'one step')
|
|
406
|
+
assert.equal((await declared())['size.x'][1], 3000, 'the largest width is what was typed')
|
|
407
|
+
await type('size.x.min', '500')
|
|
408
|
+
assert.match((await state()).error, /500|400/, 'the figure is 400 wide: a smallest of 500 is refused, and said')
|
|
409
|
+
assert.equal((await declared())['size.x'][0] < 400, true, 'and the range is as it was')
|
|
410
|
+
assert.equal(await (await field('size.x.min')).evaluate(i => Number(i.value) < 400), true, 'the field shows the range it still has')
|
|
411
|
+
})
|
|
412
|
+
|
|
380
413
|
test('a part follows the figure\'s width less a difference: chosen and typed, the width is the size less it', async () => {
|
|
381
414
|
await click(await read(() => probe.partOnPage('box')))
|
|
382
415
|
assert.equal((await state()).selected, 'box')
|
|
@@ -415,6 +448,42 @@ describe('a figure made in the modeller is given its size from its parts (user 2
|
|
|
415
448
|
assert.equal((await state()).error, '', 'taken')
|
|
416
449
|
assert.equal((await body('box')).dimensions.width, 350, 'the figure\'s 400 less 50')
|
|
417
450
|
})
|
|
451
|
+
|
|
452
|
+
test('grown by one ratio, every part is made to follow it in one step, and the offer goes', async () => {
|
|
453
|
+
const button = key =>
|
|
454
|
+
page().evaluateHandle(key => {
|
|
455
|
+
const deep = (root = document) => [...root.querySelectorAll('button')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
456
|
+
return deep().find(b => b.textContent.includes(key)) ?? null
|
|
457
|
+
}, key)
|
|
458
|
+
const press = async key => {
|
|
459
|
+
const b = await button(key)
|
|
460
|
+
assert.ok(await b.evaluate(b => !!b), `${key} is offered`)
|
|
461
|
+
await click(await b.evaluate(b => {
|
|
462
|
+
b.scrollIntoView({ block: 'center' })
|
|
463
|
+
const r = b.getBoundingClientRect()
|
|
464
|
+
return { x: r.left + r.width / 2, y: r.top + r.height / 2 }
|
|
465
|
+
}))
|
|
466
|
+
}
|
|
467
|
+
await page().keyboard.press('Escape')
|
|
468
|
+
await sleep(300)
|
|
469
|
+
await press('figure.button.scale-uniformly')
|
|
470
|
+
assert.equal((await state()).error, '', 'grows by one ratio')
|
|
471
|
+
const start = await state()
|
|
472
|
+
await press('figure.button.follow-size-in-proportion')
|
|
473
|
+
const after = await state()
|
|
474
|
+
assert.equal(after.error, '', 'not refused')
|
|
475
|
+
assert.equal(after.revision, start.revision + 1, 'one step')
|
|
476
|
+
const follows = await page().evaluate(() => {
|
|
477
|
+
const deep = (root = document) => [...root.querySelectorAll('figure-v3-editor')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
478
|
+
const model = JSON.parse(deep()[0].session.source()).document.model
|
|
479
|
+
return ['x', 'y', 'z'].map(axis => {
|
|
480
|
+
const ref = String(model.nodes.find(n => n.id === `box.dimension.${axis}`)?.args[0] ?? '')
|
|
481
|
+
return { ref, own: model.inputs.some(i => i.id === ref) }
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
assert.deepEqual(follows.filter(f => !f.ref || f.own), [], `no side of the box keeps its own number: ${follows.map(f => f.ref).join(', ')}`)
|
|
485
|
+
assert.equal(await (await button('figure.button.follow-size-in-proportion')).evaluate(b => !!b), false, 'nothing is left to offer')
|
|
486
|
+
})
|
|
418
487
|
})
|
|
419
488
|
|
|
420
489
|
describe('a motion is changed where it is (2026-09-25)', () => {
|
package/test/v3-links.test.ts
CHANGED
|
@@ -10,7 +10,7 @@ import assert from 'node:assert/strict'
|
|
|
10
10
|
import { addV3Box, addV3Primitive, applyV3Authoring, createV3Asset } from '@hatiolab/figure-model'
|
|
11
11
|
import type { V3Asset } from '@hatiolab/figure-model'
|
|
12
12
|
|
|
13
|
-
import { acceptsOf, canFasten, canFollow, dimensionNow, instanceRangeOf, rangeDiffers, turnsAboutAJoint, fastenedTo, followableAxes, followOf, hasInstanceSize, positionIsShare, holdsThings, motionOf, placeFacesOf, placeIdFor, placesOn, positionsWorkedOut } from '../client/modeller/v3-links.ts'
|
|
13
|
+
import { acceptsOf, canFasten, canFollow, dimensionNow, instanceRangeOf, proportionToFollow, rangeDiffers, rangeWith, turnsAboutAJoint, valueNow, fastenedTo, followableAxes, followOf, hasInstanceSize, positionIsShare, holdsThings, motionOf, placeFacesOf, placeIdFor, placesOn, positionsWorkedOut } from '../client/modeller/v3-links.ts'
|
|
14
14
|
|
|
15
15
|
const pair = (): V3Asset => {
|
|
16
16
|
let asset = createV3Asset('Pair')
|
|
@@ -226,6 +226,45 @@ test('놓일 수 있는 크기 — 남은 만큼 채우던 부품을 비례로
|
|
|
226
226
|
assert.ok(editor.includes("this.linkCommand({ kind: 'declare-instance-size', range: range.measured, replace: true })"))
|
|
227
227
|
})
|
|
228
228
|
|
|
229
|
+
test('놓일 수 있는 크기 — 저작자가 끝을 적어 좁힌다(갠트리, 09-25)', () => {
|
|
230
|
+
let asset = sizedPair()
|
|
231
|
+
for (const axis of ['x', 'z'] as const) {
|
|
232
|
+
asset.document.model.inputs.push({ id: `size.${axis}`, unit: 'mm', min: 1, max: 20000, role: 'design' } as never)
|
|
233
|
+
asset.designInputs[`size.${axis}`] = 200
|
|
234
|
+
}
|
|
235
|
+
const declared = instanceRangeOf(asset)!.declared
|
|
236
|
+
assert.equal(rangeWith(declared, 'x', 'min', Number.NaN), undefined, '숫자가 아니면 명령이 없다')
|
|
237
|
+
const next = rangeWith(declared, 'x', 'min', 150)!
|
|
238
|
+
assert.deepEqual(next.x, { min: 150, max: 20000 })
|
|
239
|
+
assert.deepEqual(declared.x, { min: 1, max: 20000 }, '선언된 범위는 그대로')
|
|
240
|
+
asset = applyV3Authoring(asset, { kind: 'declare-instance-size', range: next, replace: true })
|
|
241
|
+
assert.deepEqual(instanceRangeOf(asset)!.declared.x, { min: 150, max: 20000 })
|
|
242
|
+
assert.throws(() => applyV3Authoring(asset, { kind: 'declare-instance-size', range: rangeWith(next, 'x', 'min', 300)!, replace: true }), (e: { code?: string }) => e.code === 'INSTANCE_SIZE_RANGE', '저작한 크기(200)를 벗어나면 거절')
|
|
243
|
+
const editor = read('../client/modeller/v3-editor.ts')
|
|
244
|
+
assert.ok(editor.includes("const next = rangeWith(range.declared, axis, bound, (e.target as HTMLInputElement).valueAsNumber)"))
|
|
245
|
+
assert.ok(editor.includes("if (next) this.linkCommand({ kind: 'declare-instance-size', range: next, replace: true })"))
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
test('한 비율로 커지는 도형 — 자기 숫자인 길이를 세고, 한 번에 따라가게 하며, 무엇이 남는지 이름을 댄다(아키텍트 판정 09-25)', () => {
|
|
249
|
+
let asset = createV3Asset('Probe')
|
|
250
|
+
asset = addV3Box(asset, { id: 'base', size: { x: 400, y: 100, z: 400 }, position: { x: 0, y: 0, z: 0 } })
|
|
251
|
+
asset = addV3Box(asset, { id: 'lamp', size: { x: 60, y: 60, z: 60 }, position: { x: 0, y: 0, z: 0 } })
|
|
252
|
+
asset = applyV3Authoring(asset, { kind: 'attach', part: 'base', face: 'bottom', to: { plane: 'mounting-plane' } })
|
|
253
|
+
asset = applyV3Authoring(asset, { kind: 'attach', part: 'lamp', face: 'bottom', to: { part: 'base', face: 'top' }, align: { x: { mm: 120 } } })
|
|
254
|
+
asset = applyV3Authoring(asset, { kind: 'declare-instance-size', range: { x: { min: 40, max: 4000 }, y: { min: 16, max: 1600 }, z: { min: 40, max: 4000 } } })
|
|
255
|
+
assert.equal(proportionToFollow(asset), undefined, '한 비율이 아니면 권하지 않는다')
|
|
256
|
+
asset = applyV3Authoring(asset, { kind: 'scale-uniformly' })
|
|
257
|
+
assert.deepEqual(proportionToFollow(asset), { count: 3 + 3 + 1, kept: [] }, '치수 여섯과 자리 하나')
|
|
258
|
+
const lamp = asset.joints!.find(j => j.id === 'lamp')!
|
|
259
|
+
asset = applyV3Authoring(asset, { kind: 'set-joint', joint: { ...lamp, type: 'prismatic', axis: 'Y', lowerLimit: 0, upperLimit: 50, state: { id: 'rise' } } })
|
|
260
|
+
asset = applyV3Authoring(asset, { kind: 'follow-size-in-proportion' })
|
|
261
|
+
assert.deepEqual(proportionToFollow(asset), { count: 0, kept: ['lamp.motion'] }, '다 따르고, mm 로 움직이는 것만 남는다')
|
|
262
|
+
assert.equal(Math.round(valueNow(asset, 'lamp.dimension.x.value')! * 1e6) / 1e6, 60, '따르게 해도 지금 크기는 그대로')
|
|
263
|
+
const editor = read('../client/modeller/v3-editor.ts')
|
|
264
|
+
assert.ok(editor.includes("this.linkCommand({ kind: 'follow-size-in-proportion' })"))
|
|
265
|
+
assert.ok(editor.includes("?? valueNow(asset, layout?.args[4])"), '길이를 따르는 간격도 숫자로 보인다')
|
|
266
|
+
})
|
|
267
|
+
|
|
229
268
|
test('관절이 있는 도형에는 한 비율로 키우기를 권하고, 선언하면 규칙으로 보인다(수석 아키텍트 판정 09-24)', () => {
|
|
230
269
|
let asset = sizedPair()
|
|
231
270
|
assert.equal(turnsAboutAJoint(asset), false)
|
package/translations/en.json
CHANGED
|
@@ -728,5 +728,8 @@
|
|
|
728
728
|
"figure.button.change-motion": "change the motion",
|
|
729
729
|
"figure.button.apply-motion-change": "apply the change",
|
|
730
730
|
"figure.button.cancel-motion-change": "leave it",
|
|
731
|
-
"figure.label.mounting-plane": "the mounting plane"
|
|
731
|
+
"figure.label.mounting-plane": "the mounting plane",
|
|
732
|
+
"figure.text.proportion-to-follow": "{count} lengths still have their own number. made to follow, they grow by the figure's ratio.",
|
|
733
|
+
"figure.button.follow-size-in-proportion": "make every part follow",
|
|
734
|
+
"figure.text.proportion-kept": "keeps its own number: {what}"
|
|
732
735
|
}
|
package/translations/ja.json
CHANGED
|
@@ -728,5 +728,8 @@
|
|
|
728
728
|
"figure.button.change-motion": "動きを直す",
|
|
729
729
|
"figure.button.apply-motion-change": "動きを変える",
|
|
730
730
|
"figure.button.cancel-motion-change": "直さない",
|
|
731
|
-
"figure.label.mounting-plane": "取付面"
|
|
731
|
+
"figure.label.mounting-plane": "取付面",
|
|
732
|
+
"figure.text.proportion-to-follow": "長さ {count} 個がまだ固有の数値です。追従させると図形と同じ比率で大きくなります。",
|
|
733
|
+
"figure.button.follow-size-in-proportion": "すべての部品を追従させる",
|
|
734
|
+
"figure.text.proportion-kept": "固有の数値のまま: {what}"
|
|
732
735
|
}
|
package/translations/ko.json
CHANGED
|
@@ -728,5 +728,8 @@
|
|
|
728
728
|
"figure.button.change-motion": "움직임 고치기",
|
|
729
729
|
"figure.button.apply-motion-change": "움직임 바꾸기",
|
|
730
730
|
"figure.button.cancel-motion-change": "고치지 않음",
|
|
731
|
-
"figure.label.mounting-plane": "장착 평면"
|
|
731
|
+
"figure.label.mounting-plane": "장착 평면",
|
|
732
|
+
"figure.text.proportion-to-follow": "길이 {count}개가 아직 자기 숫자입니다. 따라가게 하면 도형과 같은 비율로 커집니다.",
|
|
733
|
+
"figure.button.follow-size-in-proportion": "모든 부품이 따라가게",
|
|
734
|
+
"figure.text.proportion-kept": "자기 숫자로 남는 것: {what}"
|
|
732
735
|
}
|
package/translations/ms.json
CHANGED
|
@@ -728,5 +728,8 @@
|
|
|
728
728
|
"figure.button.change-motion": "ubah gerakan",
|
|
729
729
|
"figure.button.apply-motion-change": "guna perubahan",
|
|
730
730
|
"figure.button.cancel-motion-change": "biarkan",
|
|
731
|
-
"figure.label.mounting-plane": "satah pemasangan"
|
|
731
|
+
"figure.label.mounting-plane": "satah pemasangan",
|
|
732
|
+
"figure.text.proportion-to-follow": "{count} panjang masih mempunyai nombor sendiri. apabila dijadikan mengikut, ia membesar mengikut nisbah rajah.",
|
|
733
|
+
"figure.button.follow-size-in-proportion": "jadikan semua bahagian mengikut",
|
|
734
|
+
"figure.text.proportion-kept": "kekal dengan nombor sendiri: {what}"
|
|
732
735
|
}
|
package/translations/zh.json
CHANGED
|
@@ -728,5 +728,8 @@
|
|
|
728
728
|
"figure.button.change-motion": "修改运动",
|
|
729
729
|
"figure.button.apply-motion-change": "应用修改",
|
|
730
730
|
"figure.button.cancel-motion-change": "不修改",
|
|
731
|
-
"figure.label.mounting-plane": "安装平面"
|
|
731
|
+
"figure.label.mounting-plane": "安装平面",
|
|
732
|
+
"figure.text.proportion-to-follow": "还有 {count} 个长度是自身数值。设为跟随后,将按图形的同一比例变大。",
|
|
733
|
+
"figure.button.follow-size-in-proportion": "让所有部件跟随",
|
|
734
|
+
"figure.text.proportion-kept": "保留自身数值: {what}"
|
|
732
735
|
}
|