@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
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* What the modeller sweep of 2026-10-27 found on the side panel and the preview, in a real browser
|
|
3
|
+
* (operato-figure docs/audits/modeller-ux-sweep-2026-10-27.md, U1 U2 U4 U5 U6 U7): each as a person would meet it -- a
|
|
4
|
+
* box that reads as a box, a heading nothing is drawn over, a delete that looks like one, the empty animation said in
|
|
5
|
+
* its own words, a field added by Enter, the preview's values starting in one column and its help in the bar's look.
|
|
6
|
+
* The page loads no translations, so the words read are i18n keys.
|
|
7
|
+
*
|
|
8
|
+
* Run: node --test test/browser/side-panel-sweep.check.mjs
|
|
9
|
+
*/
|
|
10
|
+
import { test, describe, before, after } from 'node:test'
|
|
11
|
+
import assert from 'node:assert/strict'
|
|
12
|
+
|
|
13
|
+
import { addFigureBox, createFigureAsset, serializeFigureAsset } from '@hatiolab/figure-model'
|
|
14
|
+
|
|
15
|
+
import { buildPage } from './build.mjs'
|
|
16
|
+
import { openPage } from './serve.mjs'
|
|
17
|
+
|
|
18
|
+
const sleep = ms => new Promise(done => setTimeout(done, ms))
|
|
19
|
+
|
|
20
|
+
let opened
|
|
21
|
+
before(async () => {
|
|
22
|
+
opened = await openPage(await buildPage('links-page.js'))
|
|
23
|
+
})
|
|
24
|
+
after(async () => {
|
|
25
|
+
await opened?.close()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const page = () => opened.page
|
|
29
|
+
const read = (fn, ...args) => page().evaluate(fn, ...args)
|
|
30
|
+
const source = () => read(() => probe.source())
|
|
31
|
+
const settled = () => read(() => document.querySelector('figure-editor').updateComplete)
|
|
32
|
+
|
|
33
|
+
async function until(fn, args, what, ms = 8000) {
|
|
34
|
+
const deadline = Date.now() + ms
|
|
35
|
+
for (;;) {
|
|
36
|
+
const got = await read(fn, ...args)
|
|
37
|
+
if (got) return got
|
|
38
|
+
if (Date.now() > deadline) assert.fail(`waited for ${what}`)
|
|
39
|
+
await sleep(50)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function mount(kind, text = null) {
|
|
43
|
+
await read((kind, text) => harness.mount(kind, text), kind, text)
|
|
44
|
+
await until(
|
|
45
|
+
() => {
|
|
46
|
+
try {
|
|
47
|
+
return probe.ids().some(id => !!probe.partOnPage(id))
|
|
48
|
+
} catch {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
[],
|
|
53
|
+
'the figure drawn in 3D',
|
|
54
|
+
15000
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
async function click(point, what) {
|
|
58
|
+
assert.ok(point, `${what} is on the page to click`)
|
|
59
|
+
await page().mouse.click(point.x, point.y)
|
|
60
|
+
await settled()
|
|
61
|
+
}
|
|
62
|
+
const tab = name => read(l => probe.tab(l), `figure.label.tab-short-${name}`)
|
|
63
|
+
|
|
64
|
+
/** Every element matching the selector through every shadow root, shown on the page. */
|
|
65
|
+
const DEEP = `(selector => {
|
|
66
|
+
const deep = (root = document) => [...root.querySelectorAll(selector)].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
67
|
+
return deep().filter(e => e.getBoundingClientRect().height > 0)
|
|
68
|
+
})`
|
|
69
|
+
|
|
70
|
+
describe('the side panel', () => {
|
|
71
|
+
before(() => mount('box'))
|
|
72
|
+
|
|
73
|
+
test('U1 the capability tab: the item box keeps its size beside its long note', async () => {
|
|
74
|
+
await click(await tab('capability'), 'the capability tab')
|
|
75
|
+
/* The note as Korean reads it: words that wrap (the key alone is one word that never does). */
|
|
76
|
+
const width = await read(DEEP => {
|
|
77
|
+
eval(DEEP)('label[item] em')[0].textContent = '홀더가 자리에 앉혀 나르는 물건의 모양입니다. 릴 · 트레이 · 매거진. 아이템 타입 표에서 이 피규어를 고를 수 있습니다.'
|
|
78
|
+
return eval(DEEP)('label[item] > input')[0]?.getBoundingClientRect().width
|
|
79
|
+
}, DEEP)
|
|
80
|
+
assert.ok(width >= 12, `the item box is ${width}px wide`)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('U2 scrolled under the heading, the places chips are drawn below it, not over it', async () => {
|
|
84
|
+
/* A short window, so the panel scrolls. */
|
|
85
|
+
await page().setViewport({ width: 1400, height: 520 })
|
|
86
|
+
await click(await read(id => probe.row(id), 'box'), 'the box in the parts list')
|
|
87
|
+
await click(await tab('capability'), 'the capability tab')
|
|
88
|
+
const hit = await read(DEEP => {
|
|
89
|
+
const chips = eval(DEEP)('ox-input-select-buttons[places-by]')[0]
|
|
90
|
+
const subject = eval(DEEP)('div[subject]')[0]
|
|
91
|
+
if (!chips || !subject) return 'no chips or no heading'
|
|
92
|
+
let scroller = chips.parentNode
|
|
93
|
+
for (;;) {
|
|
94
|
+
if (!scroller) return 'nothing scrolls'
|
|
95
|
+
if (scroller instanceof ShadowRoot) scroller = scroller.host
|
|
96
|
+
else if (scroller instanceof Element && /auto|scroll/.test(getComputedStyle(scroller).overflowY) && scroller.scrollHeight > scroller.clientHeight) break
|
|
97
|
+
else scroller = scroller.parentNode
|
|
98
|
+
}
|
|
99
|
+
const head = subject.getBoundingClientRect()
|
|
100
|
+
const before = chips.getBoundingClientRect()
|
|
101
|
+
scroller.scrollTop += before.top - (head.top + head.height / 2) + 4
|
|
102
|
+
const at = chips.getBoundingClientRect()
|
|
103
|
+
const x = at.left + Math.min(40, at.width / 2),
|
|
104
|
+
y = at.top + 6
|
|
105
|
+
if (y > head.bottom || y < head.top) return `the chips stand at ${Math.round(y)}, the heading ${Math.round(head.top)}..${Math.round(head.bottom)}`
|
|
106
|
+
let el = document.elementFromPoint(x, y)
|
|
107
|
+
while (el?.shadowRoot) {
|
|
108
|
+
const inner = el.shadowRoot.elementFromPoint(x, y)
|
|
109
|
+
if (!inner || inner === el) break
|
|
110
|
+
el = inner
|
|
111
|
+
}
|
|
112
|
+
for (let n = el; n; n = n.parentNode instanceof ShadowRoot ? n.parentNode.host : n.parentNode) if (n === subject) return 'heading'
|
|
113
|
+
return el?.tagName.toLowerCase()
|
|
114
|
+
}, DEEP)
|
|
115
|
+
await page().setViewport({ width: 1400, height: 900 })
|
|
116
|
+
assert.equal(hit, 'heading')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('U5 the motion tab: no animation yet is said in its own words, the controls\' words said once', async () => {
|
|
120
|
+
await click(await tab('motion'), 'the motion tab')
|
|
121
|
+
const hints = await read(DEEP => eval(DEEP)('p[hint]').map(p => p.textContent.trim()), DEEP)
|
|
122
|
+
assert.ok(hints.includes('figure.text.no-clips'), hints.join(' | '))
|
|
123
|
+
assert.equal(hints.filter(h => h === 'figure.text.no-controls').length, 1)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('U6 the data tab: a field name and Enter adds the field', async () => {
|
|
127
|
+
await click(await tab('data'), 'the data tab')
|
|
128
|
+
await click(await read(() => look.at('input[aria-label="figure.label.data-name"]')), 'the field name')
|
|
129
|
+
await page().keyboard.type('speed')
|
|
130
|
+
await page().keyboard.press('Enter')
|
|
131
|
+
await settled()
|
|
132
|
+
await until(() => probe.source().dataInterface?.schema?.some(f => f.name === 'speed'), [], 'the field added')
|
|
133
|
+
assert.equal((await read(DEEP => eval(DEEP)('input[aria-label="figure.label.data-name"]')[0].value, DEEP)), '')
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('U4 a field opened: its delete is the bin, not a cross that reads as close', async () => {
|
|
137
|
+
await click(await read(() => look.at('button', 'speed')), 'the speed field')
|
|
138
|
+
const icon = await until(DEEP => eval(DEEP)('button[title="figure.button.remove-data-field"] md-icon')[0]?.textContent.trim(), [DEEP], 'the field opened')
|
|
139
|
+
assert.equal(icon, 'delete')
|
|
140
|
+
assert.ok((await source()).dataInterface.schema.some(f => f.name === 'speed'))
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
describe('the preview', () => {
|
|
145
|
+
/* A number and a text, of names far apart in length, each read by a table: the preview gives each a field. */
|
|
146
|
+
const asset = addFigureBox(createFigureAsset('Probe'), { id: 'box', size: { x: 400, y: 400, z: 400 }, position: { x: 0, y: 200, z: 0 } })
|
|
147
|
+
asset.dataInterface = {
|
|
148
|
+
version: 'figure-data-interface-2',
|
|
149
|
+
schema: [
|
|
150
|
+
{ name: 'A', kind: 'number' },
|
|
151
|
+
{ name: 'CARRIERSTATUS', kind: 'text' }
|
|
152
|
+
],
|
|
153
|
+
bindings: [
|
|
154
|
+
{ id: 'a', accessor: 'A', rule: 'table', lookup: 'ranges', targets: { parts: ['box'] }, table: { '~10': { color: '#ff0000' }, default: { color: '#0000ff' } } },
|
|
155
|
+
{ id: 's', accessor: 'CARRIERSTATUS', rule: 'table', lookup: 'code', targets: { parts: ['box'] }, table: { X: { color: '#00ff00' } } }
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
before(async () => {
|
|
159
|
+
await mount('box', serializeFigureAsset(asset))
|
|
160
|
+
await click(await read(() => probe.modeButton('preview')), 'the preview')
|
|
161
|
+
await until(DEEP => eval(DEEP)('figure-preview')[0]?.shadowRoot?.querySelectorAll('div[clips] input').length === 2, [DEEP], 'the two values in the preview')
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('U7 a number\'s field and a text\'s start in one column, whatever the names\' lengths', async () => {
|
|
165
|
+
const lefts = await read(DEEP => [...eval(DEEP)('figure-preview')[0].shadowRoot.querySelectorAll('div[clips] input')].map(i => Math.round(i.getBoundingClientRect().left)), DEEP)
|
|
166
|
+
assert.equal(lefts[0], lefts[1], `the fields start at ${lefts.join(' and ')}`)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('U7 the size help\'s choice and button are in the bar\'s look, not the browser\'s grey', async () => {
|
|
170
|
+
const looks = await read(DEEP => {
|
|
171
|
+
const root = eval(DEEP)('figure-preview')[0].shadowRoot
|
|
172
|
+
return ['div[setup] select', 'div[setup] button', 'div[clips] input'].map(s => {
|
|
173
|
+
const c = getComputedStyle(root.querySelector(s))
|
|
174
|
+
return { radius: c.borderTopLeftRadius, background: c.backgroundColor }
|
|
175
|
+
})
|
|
176
|
+
}, DEEP)
|
|
177
|
+
assert.deepEqual(looks[0], looks[1])
|
|
178
|
+
assert.deepEqual(looks[0], looks[2])
|
|
179
|
+
assert.equal(looks[0].radius, '5px')
|
|
180
|
+
assert.notEqual(looks[0].background, 'rgb(239, 239, 239)')
|
|
181
|
+
})
|
|
182
|
+
})
|
|
@@ -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'])
|
|
@@ -95,7 +95,7 @@ test('a new figure keeps its creation id through a new figure\'s proposal and th
|
|
|
95
95
|
return { id: q.id ?? q.creationId!, source: q.source }
|
|
96
96
|
}
|
|
97
97
|
})
|
|
98
|
-
session.startNew()
|
|
98
|
+
session.startNew({ name: 'New' })
|
|
99
99
|
const creationId = session.read().creationId
|
|
100
100
|
assert.match(creationId ?? '', /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/)
|
|
101
101
|
const create: FigureApplyDetail = { key: K1, steps: lid, create: true }
|
package/test/first-save.test.ts
CHANGED
|
@@ -47,7 +47,7 @@ function recording() {
|
|
|
47
47
|
test('타입은 새 피규어를 만드는 저장에만 실린다 — 이미 있는 피규어의 저장은 타입을 보내지 않는다', async () => {
|
|
48
48
|
const { store, requests } = recording()
|
|
49
49
|
const session = createFigureProductSession(store)
|
|
50
|
-
session.startNew()
|
|
50
|
+
session.startNew({ name: 'New' })
|
|
51
51
|
await session.save({ type: 'agv-lifter' })
|
|
52
52
|
assert.equal(requests[0]!.type, 'agv-lifter')
|
|
53
53
|
assert.equal(requests[0]!.id, undefined, '만드는 저장')
|
|
@@ -61,7 +61,7 @@ test('타입은 새 피규어를 만드는 저장에만 실린다 — 이미 있
|
|
|
61
61
|
test('타입을 주지 않으면 싣지 않는다 — 서버가 임시 이름을 붙인다', async () => {
|
|
62
62
|
const { store, requests } = recording()
|
|
63
63
|
const session = createFigureProductSession(store)
|
|
64
|
-
session.startNew()
|
|
64
|
+
session.startNew({ name: 'New' })
|
|
65
65
|
await session.save()
|
|
66
66
|
assert.equal('type' in requests[0]!, false)
|
|
67
67
|
})
|
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
|
/*
|
|
@@ -28,7 +28,7 @@ test('existing model -> new clears geometry, ID, history and derived state', asy
|
|
|
28
28
|
const s = createFigureProductSession(store)
|
|
29
29
|
await s.open('A')
|
|
30
30
|
assert.equal(s.read().geometry.length, 1)
|
|
31
|
-
const v = s.startNew()
|
|
31
|
+
const v = s.startNew({ name: 'New' })
|
|
32
32
|
assert.equal(v.id, undefined)
|
|
33
33
|
assert.equal(v.geometry.length, 0)
|
|
34
34
|
assert.equal(v.canUndo, false)
|
|
@@ -46,7 +46,7 @@ test('late load cannot overwrite another model or a new figure', async () => {
|
|
|
46
46
|
const p = deferred<FigureProductRecord>(),
|
|
47
47
|
t = createFigureProductSession({ ...store, load: () => p.promise })
|
|
48
48
|
const loading = t.open('A')
|
|
49
|
-
t.startNew()
|
|
49
|
+
t.startNew({ name: 'New' })
|
|
50
50
|
p.resolve({ id: 'A', source: source() })
|
|
51
51
|
await loading
|
|
52
52
|
assert.equal(t.read().id, undefined)
|
|
@@ -95,10 +95,10 @@ test('save carries exact baseline and acknowledges only matching response', asyn
|
|
|
95
95
|
test('pending save locks edits/navigation and duplicate create', async () => {
|
|
96
96
|
const p = deferred<FigureProductRecord>(),
|
|
97
97
|
s = createFigureProductSession({ ...store, save: () => p.promise })
|
|
98
|
-
s.startNew()
|
|
98
|
+
s.startNew({ name: 'New' })
|
|
99
99
|
const text = s.source(),
|
|
100
100
|
saving = s.save()
|
|
101
|
-
assert.throws(() => s.startNew())
|
|
101
|
+
assert.throws(() => s.startNew({ name: 'New' }))
|
|
102
102
|
assert.throws(() => s.undo())
|
|
103
103
|
await assert.rejects(s.save())
|
|
104
104
|
p.resolve({ id: 'new', source: text })
|
|
@@ -124,7 +124,7 @@ test('retry after a lost create response reuses the same creation identity', asy
|
|
|
124
124
|
return { id: q.creationId!, source: q.source }
|
|
125
125
|
}
|
|
126
126
|
})
|
|
127
|
-
s.startNew()
|
|
127
|
+
s.startNew({ name: 'New' })
|
|
128
128
|
await assert.rejects(s.save())
|
|
129
129
|
await s.save()
|
|
130
130
|
assert.equal(keys.length, 2)
|
|
@@ -67,14 +67,14 @@ test('a save names the proposals its text holds: undone, not; redone, again; und
|
|
|
67
67
|
test('a new figure opened for a proposal is created under its key; one taken on the open new figure keeps its id', async () => {
|
|
68
68
|
const { store, saves } = server()
|
|
69
69
|
const s = createFigureProductSession(store)
|
|
70
|
-
s.startNew()
|
|
70
|
+
s.startNew({ name: 'New' })
|
|
71
71
|
s.createUnder(K1)
|
|
72
72
|
s.applyProposal(lid, s.read().revision!, s.read().context!, K1)
|
|
73
73
|
await s.save({ type: 'LIDDED' })
|
|
74
74
|
assert.equal(saves.at(-1)!.creationId, K1, 'created under the proposal key: taken twice, it meets the first figure')
|
|
75
75
|
assert.deepEqual(saves.at(-1)!.proposalKeys, [K1])
|
|
76
76
|
|
|
77
|
-
s.startNew({ discard: true })
|
|
77
|
+
s.startNew({ discard: true, name: 'New' })
|
|
78
78
|
const own = s.read().creationId
|
|
79
79
|
const detail: FigureApplyDetail = { key: K2, steps: lid, create: true }
|
|
80
80
|
assert.equal(takeFigureOffer(s, detail, { onSavedFigure: false, say }), true)
|
|
@@ -82,7 +82,7 @@ test('a new figure opened for a proposal is created under its key; one taken on
|
|
|
82
82
|
assert.deepEqual({ creationId: saves.at(-1)!.creationId, proposalKeys: saves.at(-1)!.proposalKeys }, { creationId: own, proposalKeys: [K2] })
|
|
83
83
|
|
|
84
84
|
/* A new figure the person has touched is theirs: the proposal is not put on it (NEW_FIGURE_UNSAVED), its id stays. */
|
|
85
|
-
s.startNew({ discard: true })
|
|
85
|
+
s.startNew({ discard: true, name: 'New' })
|
|
86
86
|
s.command({ kind: 'rename', name: 'Mine' } as never)
|
|
87
87
|
assert.throws(() => s.createUnder(K2))
|
|
88
88
|
/* Nor on a saved one. */
|
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
|
})
|
|
@@ -50,7 +50,7 @@ test('the source a revert expects is what the server is confirmed to hold -- not
|
|
|
50
50
|
return { id: q.id ?? 'a', source: row }
|
|
51
51
|
}
|
|
52
52
|
})
|
|
53
|
-
session.startNew()
|
|
53
|
+
session.startNew({ name: 'New' })
|
|
54
54
|
assert.equal(session.confirmedSource(), undefined, 'nothing saved: nothing to revert over')
|
|
55
55
|
await session.open('a')
|
|
56
56
|
const loaded = row
|
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.",
|
|
@@ -640,6 +651,7 @@
|
|
|
640
651
|
"figure.label.controls": "Controls",
|
|
641
652
|
"figure.text.controls-explained": "A board or a connection sets these values to pose the figure. Move a slider to try one.",
|
|
642
653
|
"figure.text.no-controls": "No controls yet. Adding a motion to a part makes one.",
|
|
654
|
+
"figure.text.no-clips": "No motion to play yet.",
|
|
643
655
|
"figure.button.keep-pose": "Keep this pose as the default",
|
|
644
656
|
"figure.label.control-name": "Name",
|
|
645
657
|
"figure.text.control-starts-at": "Starts at {value} {unit} when placed on a board.",
|
|
@@ -847,16 +859,17 @@
|
|
|
847
859
|
"figure.label.tab-short-capability": "Capability",
|
|
848
860
|
"figure.label.tab-short-motion": "Motion",
|
|
849
861
|
"figure.label.tab-short-verdict": "Check",
|
|
862
|
+
"figure.label.tab-verdict": "Check · maturity and the release verdict",
|
|
850
863
|
"figure.label.tab-short-view": "View",
|
|
851
864
|
"figure.label.tab-short-history": "History",
|
|
852
865
|
"figure.text.start-question": "What would you like to start with?",
|
|
853
866
|
"figure.label.start-shape": "Add a basic shape",
|
|
854
867
|
"figure.text.start-shape-more": "More shapes are in the list on the left.",
|
|
868
|
+
"figure.button.start-shape-more": "More parts",
|
|
855
869
|
"figure.label.start-recipe": "Start from a recipe",
|
|
856
870
|
"figure.button.start-recipe": "Browse recipes",
|
|
857
871
|
"figure.label.start-ai": "Draft with AI",
|
|
858
872
|
"figure.button.start-ai": "Ask AI for a draft",
|
|
859
|
-
"figure.text.start-ai-optional": "Optional. You can build it yourself.",
|
|
860
873
|
"figure.text.empty-figure-next": "Add a part to see its size and placement.",
|
|
861
874
|
"figure.label.figure-info-later": "Figure information · you can set this later",
|
|
862
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.",
|
|
@@ -1203,7 +1216,6 @@
|
|
|
1203
1216
|
"figure.text.size-unpaired": "it lies at an angle, so the axis to follow cannot be told. pick a source and an axis.",
|
|
1204
1217
|
"figure.label.repeat-inactive": "Not applied",
|
|
1205
1218
|
"figure.label.repeat-active": "Applied",
|
|
1206
|
-
"figure.label.tab-arrangement": "Layout",
|
|
1207
1219
|
"figure.text.repeat-intro": "Repeat this part along the length of the part it is attached to.",
|
|
1208
1220
|
"figure.text.repeat-direction": "Direction uses the attached parent’s axes.",
|
|
1209
1221
|
"figure.text.repeat-draft": "Preview only. Apply to confirm this layout.",
|
|
@@ -1215,11 +1227,8 @@
|
|
|
1215
1227
|
"figure.button.state-size-fix": "Back to fixed",
|
|
1216
1228
|
"figure.label.state-size-measure": "Measure",
|
|
1217
1229
|
"figure.label.state-size-unit": "Unit",
|
|
1218
|
-
"figure.label.state-
|
|
1219
|
-
"figure.label.state-
|
|
1220
|
-
"figure.label.state-unit-mm": "mm",
|
|
1221
|
-
"figure.label.state-size-at-min": "At its least",
|
|
1222
|
-
"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}",
|
|
1223
1232
|
"figure.button.add-state-size": "Add size by state",
|
|
1224
1233
|
"figure.text.summary-size-state": "Its {axes} changes with {control}.",
|
|
1225
1234
|
"figure.label.follows-state": "by {control}",
|
|
@@ -1278,9 +1287,12 @@
|
|
|
1278
1287
|
"figure.text.data-table-target-part": "Part {id} is not there, or is chosen twice.",
|
|
1279
1288
|
"figure.label.product-capabilities": "Product capabilities",
|
|
1280
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 ",
|
|
1281
1292
|
"figure.text.product-capability-unknown": "The figure wears {name}, a product capability this modeller does not know.",
|
|
1282
1293
|
"figure.button.product-capability-remove": "Remove",
|
|
1283
1294
|
"figure.text.data-capability-fields": "Data {name} reads itself: {fields}.",
|
|
1284
|
-
"figure.
|
|
1285
|
-
"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}"
|
|
1286
1298
|
}
|
package/translations/es.json
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"figure.label.group-more": "Más",
|
|
20
20
|
"figure.label.group-place": "Posición",
|
|
21
21
|
"figure.label.group-position-response": "Posición",
|
|
22
|
-
"figure.label.tab-response": "
|
|
23
|
-
"figure.label.tab-short-response": "
|
|
22
|
+
"figure.label.tab-response": "Tamaño · cuando cambia el tamaño",
|
|
23
|
+
"figure.label.tab-short-response": "Tamaño",
|
|
24
24
|
"figure.text.sizing-uniform-proposed": "esta figura tiene articulaciones. escalarla con una sola proporción mantiene unidas las articulaciones en cualquier tamaño.",
|
|
25
25
|
"figure.text.sizing-uniform-explained": "escala toda la figura según la media geométrica de los tres tamaños que le asigna un tablero. los pies quedan en el suelo; el espacio que sobra encima de la figura queda vacío.",
|
|
26
26
|
"figure.button.mirror-x": "al otro lado de X",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"figure.text.gizmo-joint-origin-follows": "la posición de la articulación {joint} sigue a otra pieza. cámbiela en sujeción, en la pestaña de movimiento.",
|
|
78
78
|
"figure.text.gizmo-joint-off-axis": "la articulación {joint} solo gira alrededor de {axis}. use el control de rotación de {axis}.",
|
|
79
79
|
"figure.text.gizmo-joint-several-axes": "la articulación {joint} se mueve en varios ejes, así que no se puede arrastrar. ajuste sus valores en la pestaña de movimiento.",
|
|
80
|
-
"figure.text.gizmo-size-follows": "el tamaño en {axis} de {part} sigue a otra medida. cámbielo en
|
|
80
|
+
"figure.text.gizmo-size-follows": "el tamaño en {axis} de {part} sigue a otra medida. cámbielo en la pestaña de tamaño.",
|
|
81
81
|
"figure.text.gizmo-fastening-places": "{part} está sujeta a una cara de otra pieza, que determina su posición. sepárela primero para moverla.",
|
|
82
82
|
"figure.text.size-range-leaves-out-size": "el rango {axis} en un plano, {range} mm, no incluye su tamaño actual, {at} mm.",
|
|
83
83
|
"figure.text.size-range-from-zero": "el {axis} más pequeño en un plano debe ser mayor que 0.",
|
|
@@ -360,6 +360,15 @@
|
|
|
360
360
|
"figure.label.triangles": "triángulos",
|
|
361
361
|
"figure.label.unit-none": "sin unidad",
|
|
362
362
|
"figure.label.unit-type-it": "escribirla…",
|
|
363
|
+
"figure.label.unit-mm": "mm",
|
|
364
|
+
"figure.label.unit-cm": "cm",
|
|
365
|
+
"figure.label.unit-m": "m",
|
|
366
|
+
"figure.label.unit-rad": "radianes",
|
|
367
|
+
"figure.label.unit-deg": "grados",
|
|
368
|
+
"figure.label.unit-s": "segundos",
|
|
369
|
+
"figure.label.unit-ratio": "proporción",
|
|
370
|
+
"figure.label.unit-percent": "%",
|
|
371
|
+
"figure.label.unit-count": "unidades",
|
|
363
372
|
"figure.label.weight": "coste de dibujo",
|
|
364
373
|
"figure.text.accepts-anything": "vacío significa cualquiera",
|
|
365
374
|
"figure.text.accepts-explained": "tipos permitidos en esta ranura. sepárelos con comas; déjelo vacío para no limitar.",
|
|
@@ -381,6 +390,8 @@
|
|
|
381
390
|
"figure.text.trend-flat-lines-overlap": "las líneas planas se superponen en {value}.",
|
|
382
391
|
"figure.text.cannot-release-now": "aún no se puede publicar",
|
|
383
392
|
"figure.text.nothing-blocks-release": "nada en la figura tal como está impide publicarla.",
|
|
393
|
+
"figure.text.release-needs-a-part": "Se evalúa cuando haya una pieza.",
|
|
394
|
+
"figure.text.save-needs-a-part": "Coloque una pieza para guardar.",
|
|
384
395
|
"figure.text.verdict-asking": "comprobando…",
|
|
385
396
|
"figure.text.verdict-not-received": "la comprobación no obtuvo respuesta: {reason}",
|
|
386
397
|
"figure.text.verdict-waiting": "la comprobación sigue a sus cambios.",
|
|
@@ -640,6 +651,7 @@
|
|
|
640
651
|
"figure.label.controls": "Controles",
|
|
641
652
|
"figure.text.controls-explained": "Un tablero o una conexión define estos valores para dar postura a la figura. Mueva un control deslizante para probar uno.",
|
|
642
653
|
"figure.text.no-controls": "Aún no hay controles. Al añadir un movimiento a una pieza se crea uno.",
|
|
654
|
+
"figure.text.no-clips": "Todavía no hay movimientos para reproducir.",
|
|
643
655
|
"figure.button.keep-pose": "Mantener esta postura como predeterminada",
|
|
644
656
|
"figure.label.control-name": "Nombre",
|
|
645
657
|
"figure.text.control-starts-at": "Empieza en {value} {unit} al colocarla en un tablero.",
|
|
@@ -847,16 +859,17 @@
|
|
|
847
859
|
"figure.label.tab-short-capability": "Capacidad",
|
|
848
860
|
"figure.label.tab-short-motion": "Movimiento",
|
|
849
861
|
"figure.label.tab-short-verdict": "Comprobación",
|
|
862
|
+
"figure.label.tab-verdict": "Revisión · madurez y dictamen de publicación",
|
|
850
863
|
"figure.label.tab-short-view": "Vista",
|
|
851
864
|
"figure.label.tab-short-history": "Historial",
|
|
852
865
|
"figure.text.start-question": "¿Con qué desea empezar?",
|
|
853
866
|
"figure.label.start-shape": "Añadir una forma básica",
|
|
854
867
|
"figure.text.start-shape-more": "Hay más formas en la lista de la izquierda.",
|
|
868
|
+
"figure.button.start-shape-more": "Ver más piezas",
|
|
855
869
|
"figure.label.start-recipe": "Empezar desde una receta",
|
|
856
870
|
"figure.button.start-recipe": "Explorar recetas",
|
|
857
871
|
"figure.label.start-ai": "Crear un borrador con IA",
|
|
858
872
|
"figure.button.start-ai": "Pedir un borrador a la IA",
|
|
859
|
-
"figure.text.start-ai-optional": "Opcional. Puede crearla usted mismo.",
|
|
860
873
|
"figure.text.empty-figure-next": "Añada una pieza para ver su tamaño y colocación.",
|
|
861
874
|
"figure.label.figure-info-later": "Información de la figura · puede definirla más tarde",
|
|
862
875
|
"figure.text.parts-panel-folded-narrow": "La pantalla es estrecha, por lo que la lista de piezas está contraída. El botón de panel de la barra de herramientas la vuelve a abrir.",
|
|
@@ -1203,7 +1216,6 @@
|
|
|
1203
1216
|
"figure.text.size-unpaired": "está en ángulo, así que no se sabe qué eje seguir. elija una referencia y un eje.",
|
|
1204
1217
|
"figure.label.repeat-inactive": "Sin aplicar",
|
|
1205
1218
|
"figure.label.repeat-active": "Aplicada",
|
|
1206
|
-
"figure.label.tab-arrangement": "Distribución",
|
|
1207
1219
|
"figure.text.repeat-intro": "Repita esta pieza a lo largo de la pieza a la que está unida.",
|
|
1208
1220
|
"figure.text.repeat-direction": "La dirección usa los ejes de la pieza de unión.",
|
|
1209
1221
|
"figure.text.repeat-draft": "Vista previa. Pulse Aplicar para confirmar.",
|
|
@@ -1215,11 +1227,8 @@
|
|
|
1215
1227
|
"figure.button.state-size-fix": "Volver a fijo",
|
|
1216
1228
|
"figure.label.state-size-measure": "Medida",
|
|
1217
1229
|
"figure.label.state-size-unit": "Unidad",
|
|
1218
|
-
"figure.label.state-
|
|
1219
|
-
"figure.label.state-
|
|
1220
|
-
"figure.label.state-unit-mm": "mm",
|
|
1221
|
-
"figure.label.state-size-at-min": "En el mínimo",
|
|
1222
|
-
"figure.label.state-size-at-max": "En el máximo",
|
|
1230
|
+
"figure.label.state-size-at-min": "en {at}",
|
|
1231
|
+
"figure.label.state-size-at-max": "en {at}",
|
|
1223
1232
|
"figure.button.add-state-size": "Añadir tamaño según estado",
|
|
1224
1233
|
"figure.text.summary-size-state": "Su {axes} cambia con {control}.",
|
|
1225
1234
|
"figure.label.follows-state": "según {control}",
|
|
@@ -1278,9 +1287,12 @@
|
|
|
1278
1287
|
"figure.text.data-table-target-part": "La pieza {id} no existe o está elegida dos veces.",
|
|
1279
1288
|
"figure.label.product-capabilities": "Capacidades del producto",
|
|
1280
1289
|
"figure.text.product-capability-needs": "Necesita antes: {needs}",
|
|
1290
|
+
"figure.text.product-capability-needs-rungs": "la función {rungs}",
|
|
1291
|
+
"figure.label.or-between": " o ",
|
|
1281
1292
|
"figure.text.product-capability-unknown": "La figura lleva {name}, una capacidad que este modelador no conoce.",
|
|
1282
1293
|
"figure.button.product-capability-remove": "Quitar",
|
|
1283
1294
|
"figure.text.data-capability-fields": "Datos que {name} lee por sí misma: {fields}.",
|
|
1284
|
-
"figure.
|
|
1285
|
-
"figure.text.data-binding-rests
|
|
1295
|
+
"figure.label.data-binding-resting": "en pausa",
|
|
1296
|
+
"figure.text.data-binding-rests": "En {where} la capacidad {name} se encarga en su lugar.",
|
|
1297
|
+
"figure.text.data-binding-rests-targets": "En {where} la capacidad {name} se encarga en su lugar de: {targets}"
|
|
1286
1298
|
}
|