@things-factory/figure-ui 10.1.272 → 10.1.273
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/data-interface.ts +30 -18
- package/client/modeller/editor-styles.ts +17 -6
- package/client/modeller/editor.ts +2 -0
- package/client/modeller/fasten-form.ts +12 -2
- package/client/modeller/fasten-geometry.ts +5 -3
- package/client/modeller/figure-data-panel.ts +384 -200
- package/client/modeller/figure-fasten-drawing.ts +21 -13
- package/client/modeller/figure-links.ts +24 -9
- package/client/modeller/figure-parts.ts +7 -11
- package/client/modeller/figure-preview.ts +258 -76
- package/dist-client/modeller/clips.d.ts +1 -4
- package/dist-client/modeller/data-interface.d.ts +11 -8
- package/dist-client/modeller/data-interface.js +27 -17
- package/dist-client/modeller/data-interface.js.map +1 -1
- package/dist-client/modeller/editor-styles.js +17 -6
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.js +2 -0
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-form.d.ts +5 -0
- package/dist-client/modeller/fasten-form.js +8 -2
- package/dist-client/modeller/fasten-form.js.map +1 -1
- package/dist-client/modeller/fasten-geometry.d.ts +3 -2
- package/dist-client/modeller/fasten-geometry.js +5 -3
- package/dist-client/modeller/fasten-geometry.js.map +1 -1
- package/dist-client/modeller/figure-data-panel.d.ts +10 -19
- package/dist-client/modeller/figure-data-panel.js +382 -190
- package/dist-client/modeller/figure-data-panel.js.map +1 -1
- package/dist-client/modeller/figure-fasten-drawing.js +21 -13
- package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
- package/dist-client/modeller/figure-links.js +31 -16
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-parts.js +7 -11
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +13 -0
- package/dist-client/modeller/figure-preview.js +257 -77
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/test/browser/editing-page.js +1 -1
- package/test/browser/links.check.mjs +30 -0
- package/test/browser/modeller.check.mjs +2 -2
- package/test/browser/parts-page.js +4 -0
- package/test/browser/parts.check.mjs +16 -10
- package/test/browser/screen-page.js +12 -1
- package/test/browser/screen.check.mjs +142 -11
- package/test/browser/serve-response.mjs +5 -2
- package/test/browser/side-panel-sweep.check.mjs +171 -10
- package/test/data-motion.test.ts +39 -8
- package/test/fasten-form.test.ts +25 -0
- package/test/fasten-geometry.test.ts +13 -0
- package/translations/en.json +23 -11
- package/translations/es.json +22 -10
- package/translations/ja.json +22 -10
- package/translations/ko.json +24 -12
- package/translations/ms.json +22 -10
- package/translations/zh.json +22 -10
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { test, describe, before, after } from 'node:test'
|
|
11
11
|
import assert from 'node:assert/strict'
|
|
12
12
|
|
|
13
|
-
import { addFigureBox, createFigureAsset, serializeFigureAsset } from '@hatiolab/figure-model'
|
|
13
|
+
import { addFigureBox, applyFigureAuthoring, createFigureAsset, serializeFigureAsset } from '@hatiolab/figure-model'
|
|
14
14
|
|
|
15
15
|
import { buildPage } from './build.mjs'
|
|
16
16
|
import { openPage } from './serve.mjs'
|
|
@@ -116,6 +116,17 @@ describe('the side panel', () => {
|
|
|
116
116
|
assert.equal(hit, 'heading')
|
|
117
117
|
})
|
|
118
118
|
|
|
119
|
+
test('the heading is one line, the part\'s name and kind side by side, on a ground the rows under it do not show through (user 2026-11-12)', async () => {
|
|
120
|
+
const head = await read(DEEP => {
|
|
121
|
+
const subject = eval(DEEP)('div[subject]')[0]
|
|
122
|
+
const [name, kind] = [subject.querySelector('b'), subject.querySelector('small')]
|
|
123
|
+
const middle = el => { const r = el.getBoundingClientRect(); return Math.round(r.top + r.height / 2) }
|
|
124
|
+
return { oneLine: Math.abs(middle(name) - middle(kind)) <= 3, ground: getComputedStyle(subject).backgroundColor }
|
|
125
|
+
}, DEEP)
|
|
126
|
+
assert.equal(head.oneLine, true)
|
|
127
|
+
assert.ok(!/rgba\(.*, 0\)$|transparent/.test(head.ground), `a solid ground: ${head.ground}`)
|
|
128
|
+
})
|
|
129
|
+
|
|
119
130
|
test('U5 the motion tab: no animation yet is said in its own words, the controls\' words said once', async () => {
|
|
120
131
|
await click(await tab('motion'), 'the motion tab')
|
|
121
132
|
const hints = await read(DEEP => eval(DEEP)('p[hint]').map(p => p.textContent.trim()), DEEP)
|
|
@@ -125,6 +136,7 @@ describe('the side panel', () => {
|
|
|
125
136
|
|
|
126
137
|
test('U6 the data tab: a field name and Enter adds the field', async () => {
|
|
127
138
|
await click(await tab('data'), 'the data tab')
|
|
139
|
+
assert.equal(await read(DEEP => eval(DEEP)('details[add-field]')[0]?.open, DEEP), true, 'no field yet: the form stands open')
|
|
128
140
|
await click(await read(() => look.at('input[aria-label="figure.label.data-name"]')), 'the field name')
|
|
129
141
|
await page().keyboard.type('speed')
|
|
130
142
|
await page().keyboard.press('Enter')
|
|
@@ -143,27 +155,49 @@ describe('the side panel', () => {
|
|
|
143
155
|
|
|
144
156
|
describe('the preview', () => {
|
|
145
157
|
/* A number and a text, of names far apart in length, each read by a table: the preview gives each a field. */
|
|
146
|
-
|
|
158
|
+
/* Its size declared, so the preview offers the size help over it (shown only where the size can change). */
|
|
159
|
+
const asset = applyFigureAuthoring(addFigureBox(createFigureAsset('Probe'), { id: 'box', size: { x: 400, y: 400, z: 400 }, position: { x: 0, y: 200, z: 0 } }), { kind: 'declare-instance-size', at: { x: 400, y: 400, z: 400 } })
|
|
147
160
|
asset.dataInterface = {
|
|
148
161
|
version: 'figure-data-interface-2',
|
|
149
162
|
schema: [
|
|
150
163
|
{ name: 'A', kind: 'number' },
|
|
151
|
-
{ name: 'CARRIERSTATUS', kind: 'text' }
|
|
164
|
+
{ name: 'CARRIERSTATUS', kind: 'text' },
|
|
165
|
+
{ name: 'LOW', kind: 'boolean' },
|
|
166
|
+
{ name: 'HEADING', kind: 'number', unit: 'deg' }
|
|
152
167
|
],
|
|
153
168
|
bindings: [
|
|
154
169
|
{ id: 'a', accessor: 'A', rule: 'table', lookup: 'ranges', targets: { parts: ['box'] }, table: { '~10': { color: '#ff0000' }, default: { color: '#0000ff' } } },
|
|
170
|
+
{ id: 'h', accessor: 'HEADING', rule: 'table', lookup: 'ranges', targets: { parts: ['box'] }, table: { '~180': { color: '#00ffff' }, default: { color: '#ffff00' } } },
|
|
171
|
+
{ id: 'l', accessor: 'LOW', rule: 'table', lookup: 'code', targets: { parts: ['box'] }, table: { true: { color: '#ff00ff' }, default: { color: '#0000ff' } } },
|
|
155
172
|
{ id: 's', accessor: 'CARRIERSTATUS', rule: 'table', lookup: 'code', targets: { parts: ['box'] }, table: { X: { color: '#00ff00' } } }
|
|
156
173
|
]
|
|
157
174
|
}
|
|
158
175
|
before(async () => {
|
|
159
176
|
await mount('box', serializeFigureAsset(asset))
|
|
160
177
|
await click(await read(() => probe.modeButton('preview')), 'the preview')
|
|
161
|
-
await until(DEEP => eval(DEEP)('figure-preview')[0]?.shadowRoot?.querySelectorAll('div[clips] input').length ===
|
|
178
|
+
await until(DEEP => eval(DEEP)('figure-preview')[0]?.shadowRoot?.querySelectorAll('div[clips] label input').length === 5, [DEEP], 'the four values in the preview')
|
|
162
179
|
})
|
|
163
180
|
|
|
164
|
-
test('U7 a number\'s field and a text\'s start in one column, whatever the names\' lengths', async () => {
|
|
165
|
-
const
|
|
166
|
-
|
|
181
|
+
test('U7 a number\'s field and a text\'s start in one column, whatever the names\' lengths -- and end in one, with the help', async () => {
|
|
182
|
+
const edges = await read(DEEP => {
|
|
183
|
+
const root = eval(DEEP)('figure-preview')[0].shadowRoot
|
|
184
|
+
return [...root.querySelectorAll('div[clips] label input:not([type=checkbox]):not([type=range]):not([typed])'), root.querySelector('div[clips] div[setup] > button')].map(i => {
|
|
185
|
+
const r = i.getBoundingClientRect()
|
|
186
|
+
return { left: Math.round(r.left), right: Math.round(r.right) }
|
|
187
|
+
})
|
|
188
|
+
}, DEEP)
|
|
189
|
+
assert.equal(edges[0].left, edges[1].left, `the fields start at ${JSON.stringify(edges)}`)
|
|
190
|
+
/* A number with no range and a text are as wide as each other, to the help's right edge (user 2026-11-12: every width differed). */
|
|
191
|
+
assert.equal(edges[0].right, edges[1].right, `the fields end at ${JSON.stringify(edges)}`)
|
|
192
|
+
assert.equal(edges[0].right, edges[2].right, `the help ends where they do: ${JSON.stringify(edges)}`)
|
|
193
|
+
})
|
|
194
|
+
test('the names stay on one line: a unit is not put under its name (user 2026-11-12)', async () => {
|
|
195
|
+
const tall = await read(DEEP => [...eval(DEEP)('figure-preview')[0].shadowRoot.querySelectorAll('div[clips] label > span:first-child')].map(s => {
|
|
196
|
+
const range = document.createRange()
|
|
197
|
+
range.selectNodeContents(s)
|
|
198
|
+
return new Set([...range.getClientRects()].map(r => Math.round(r.bottom))).size
|
|
199
|
+
}), DEEP)
|
|
200
|
+
assert.ok(tall.every(n => n === 1), `lines per name: ${tall}`)
|
|
167
201
|
})
|
|
168
202
|
|
|
169
203
|
test('U7 the size help\'s choice and button are in the bar\'s look, not the browser\'s grey', async () => {
|
|
@@ -183,13 +217,140 @@ describe('the preview', () => {
|
|
|
183
217
|
test('a value the sample gives is shown as the sample\'s, not offered as a field; the sample off, the field is back', async () => {
|
|
184
218
|
const rows = () => read(DEEP => {
|
|
185
219
|
const root = eval(DEEP)('figure-preview')[0].shadowRoot
|
|
186
|
-
return { fields: root.querySelectorAll('div[clips] input, div[clips] select').length, sampled: [...root.querySelectorAll('div[clips] label[sampled] output')].map(o => o.textContent.trim()) }
|
|
220
|
+
return { fields: root.querySelectorAll('div[clips] label input, div[clips] label select').length, sampled: [...root.querySelectorAll('div[clips] label[sampled] output')].map(o => o.textContent.trim()) }
|
|
187
221
|
}, DEEP)
|
|
188
222
|
await read(DEEP => (eval(DEEP)('figure-preview')[0].sample = { A: 4 }), DEEP)
|
|
189
223
|
await until(DEEP => eval(DEEP)('figure-preview')[0].shadowRoot.querySelector('div[clips] label[sampled]'), [DEEP], 'the sampled row')
|
|
190
|
-
assert.deepEqual(await rows(), { fields:
|
|
224
|
+
assert.deepEqual(await rows(), { fields: 4, sampled: ['4'] }, 'A from the sample, the others still fields')
|
|
191
225
|
await read(DEEP => (eval(DEEP)('figure-preview')[0].sample = null), DEEP)
|
|
192
226
|
await until(DEEP => !eval(DEEP)('figure-preview')[0].shadowRoot.querySelector('div[clips] label[sampled]'), [DEEP], 'the field back')
|
|
193
|
-
assert.deepEqual(await rows(), { fields:
|
|
227
|
+
assert.deepEqual(await rows(), { fields: 5, sampled: [] })
|
|
228
|
+
})
|
|
229
|
+
test('an angle with no range is a dial, a slider over one turn and a box, as the controls\' angles are (user 2026-11-12)', async () => {
|
|
230
|
+
const row = await read(DEEP => {
|
|
231
|
+
const label = [...eval(DEEP)('figure-preview')[0].shadowRoot.querySelectorAll('div[clips] label')].find(l => l.querySelector('span').textContent.trim().startsWith('HEADING'))
|
|
232
|
+
const slider = label.querySelector('input[type=range]')
|
|
233
|
+
return { dial: !!label.querySelector('svg[dial]'), min: slider?.min, max: slider?.max, box: !!label.querySelector('input[typed]') }
|
|
234
|
+
}, DEEP)
|
|
235
|
+
assert.deepEqual(row, { dial: true, min: '0', max: '360', box: true })
|
|
236
|
+
})
|
|
237
|
+
test('a click on a number\'s name gives it nothing; dragged, it gives the value dragged to (user 2026-11-12)', async () => {
|
|
238
|
+
const name = () => read(DEEP => {
|
|
239
|
+
const label = [...eval(DEEP)('figure-preview')[0].shadowRoot.querySelectorAll('div[clips] label')].find(l => l.querySelector('span').textContent.trim() === 'A')
|
|
240
|
+
const r = label.querySelector('span[scrub]').getBoundingClientRect()
|
|
241
|
+
return { x: r.left + 4, y: r.top + r.height / 2 }
|
|
242
|
+
}, DEEP)
|
|
243
|
+
const valuing = () => read(DEEP => eval(DEEP)('figure-preview')[0].valuing.A, DEEP)
|
|
244
|
+
const at = await name()
|
|
245
|
+
await page().mouse.click(at.x, at.y)
|
|
246
|
+
assert.equal(await valuing(), undefined, 'a click is not a value')
|
|
247
|
+
await page().mouse.move(at.x, at.y)
|
|
248
|
+
await page().mouse.down()
|
|
249
|
+
await page().mouse.move(at.x + 15, at.y)
|
|
250
|
+
await page().mouse.up()
|
|
251
|
+
assert.equal(await valuing(), 15, 'dragged 15 pixels from nothing given')
|
|
252
|
+
})
|
|
253
|
+
test('the data tab\'s fields are a table -- field, kind, what each moves -- one row a field (user 2026-11-12)', async () => {
|
|
254
|
+
await click(await tab('data'), 'the data tab')
|
|
255
|
+
const table = await until(DEEP => {
|
|
256
|
+
const fields = eval(DEEP)('div[fields]')[0]
|
|
257
|
+
if (!fields) return undefined
|
|
258
|
+
const cells = row => [...row.querySelectorAll(':scope > span')].map(c => c.textContent.trim().replace(/\s+/g, ' '))
|
|
259
|
+
return { head: cells(fields.querySelector(':scope > div[head]')), rows: [...fields.querySelectorAll(':scope > button[row]')].map(cells) }
|
|
260
|
+
}, [DEEP], 'the fields table')
|
|
261
|
+
assert.deepEqual(table.head, ['figure.label.data-column-field', 'figure.label.data-column-kind', 'figure.label.data-column-moves'])
|
|
262
|
+
assert.equal(table.rows.length, 4, 'one row a field')
|
|
263
|
+
assert.ok(table.rows.every(r => r.length === 3), JSON.stringify(table.rows))
|
|
264
|
+
/* What a field moves is named, not counted. */
|
|
265
|
+
assert.ok(table.rows.every(r => !r[2].includes('figure.label.data-bound')), JSON.stringify(table.rows))
|
|
266
|
+
const tops = await read(DEEP => [...eval(DEEP)('div[fields]')[0].querySelectorAll(':scope > button[row]')].map(r => [...r.children].map(c => Math.round(c.getBoundingClientRect().left))), DEEP)
|
|
267
|
+
assert.equal(new Set(tops.map(t => t.join())).size, 1, `the columns line up: ${JSON.stringify(tops)}`)
|
|
268
|
+
/* With fields there, the form to add one is folded under its summary; on a figure with none it stands open (U6). */
|
|
269
|
+
assert.equal(await read(DEEP => eval(DEEP)('details[add-field]')[0]?.open, DEEP), false, 'folded')
|
|
270
|
+
})
|
|
271
|
+
test('a field\'s row opened holds its bindings and a folded form to add one; no list of bindings apart (user 2026-11-12)', async () => {
|
|
272
|
+
await read(DEEP => [...eval(DEEP)('div[fields]')[0].querySelectorAll(':scope > button[row]')].find(r => r.textContent.trim().startsWith('A')).click(), DEEP)
|
|
273
|
+
const detail = await until(DEEP => {
|
|
274
|
+
const fields = eval(DEEP)('div[fields]')[0]
|
|
275
|
+
const d = fields.querySelector(':scope > div[detail]')
|
|
276
|
+
if (!d) return undefined
|
|
277
|
+
return {
|
|
278
|
+
lines: [...d.querySelectorAll('div[bindings] button[line] strong')].map(s => s.textContent.trim()),
|
|
279
|
+
folded: d.querySelector('details[add-binding]')?.open === false,
|
|
280
|
+
apart: [...fields.getRootNode().querySelectorAll('section > h3')].map(h => h.textContent.trim()).filter(t => t.startsWith('figure.label.data-bindings'))
|
|
281
|
+
}
|
|
282
|
+
}, [DEEP], 'the A row opened')
|
|
283
|
+
assert.deepEqual(detail, { lines: ['A'], folded: true, apart: [] })
|
|
284
|
+
})
|
|
285
|
+
test('a table\'s targets are a property list and each value a box of its settings, one under another (user 2026-11-12)', async () => {
|
|
286
|
+
/* The A row is open from the test before: open its binding. */
|
|
287
|
+
await read(DEEP => eval(DEEP)('div[fields]')[0].querySelector(':scope > div[detail] button[line]').click(), DEEP)
|
|
288
|
+
const editor = await until(DEEP => {
|
|
289
|
+
const d = eval(DEEP)('div[fields]')[0].querySelector(':scope > div[detail]')
|
|
290
|
+
const targets = d.querySelector('div[targets]')
|
|
291
|
+
if (!targets) return undefined
|
|
292
|
+
const cases = [...d.querySelectorAll('div[cases] > div[case]')].map(c => Math.round(c.getBoundingClientRect().left))
|
|
293
|
+
const names = [...d.querySelectorAll('div[props] > span:nth-child(odd)')].map(n => Math.round(n.getBoundingClientRect().height))
|
|
294
|
+
return {
|
|
295
|
+
targets: [...targets.querySelectorAll(':scope > span:not([none])')].map(t => t.textContent.trim()),
|
|
296
|
+
ticks: targets.querySelectorAll('input[type=checkbox]').length + d.querySelectorAll('div[chips]').length,
|
|
297
|
+
stacked: cases.length >= 2 && new Set(cases).size === 1,
|
|
298
|
+
oneLine: names.length > 0 && new Set(names).size === 1
|
|
299
|
+
}
|
|
300
|
+
}, [DEEP], 'the table editor')
|
|
301
|
+
assert.deepEqual(editor, { targets: ['figure.label.data-target-parts', 'box'], ticks: 0, stacked: true, oneLine: true })
|
|
302
|
+
})
|
|
303
|
+
test('a sample typed as JSON5 is read, in a box with room for it (user 2026-11-12)', async () => {
|
|
304
|
+
await click(await tab('data'), 'the data tab')
|
|
305
|
+
const box = await until(DEEP => eval(DEEP)('textarea[sample]')[0], [DEEP], 'the sample box')
|
|
306
|
+
assert.ok(box)
|
|
307
|
+
const seen = await read(async DEEP => {
|
|
308
|
+
const area = eval(DEEP)('textarea[sample]')[0]
|
|
309
|
+
area.value = "{ A: 7, CARRIERSTATUS: 'X', }"
|
|
310
|
+
area.dispatchEvent(new Event('input'))
|
|
311
|
+
const panel = area.getRootNode().host
|
|
312
|
+
await panel.updateComplete
|
|
313
|
+
return {
|
|
314
|
+
height: Math.round(area.getBoundingClientRect().height),
|
|
315
|
+
refused: [...panel.shadowRoot.querySelectorAll('p[hint]')].some(p => p.textContent.includes('figure.text.data-not-json'))
|
|
316
|
+
}
|
|
317
|
+
}, DEEP)
|
|
318
|
+
assert.equal(seen.refused, false, 'read, not refused as not JSON')
|
|
319
|
+
assert.ok(seen.height >= 240, `room for a record: ${seen.height}px`)
|
|
320
|
+
})
|
|
321
|
+
test('a yes or no is a check box in the fields\' column; checked, the figure is given true (user 2026-11-12)', async () => {
|
|
322
|
+
const box = await read(DEEP => {
|
|
323
|
+
const root = eval(DEEP)('figure-preview')[0].shadowRoot
|
|
324
|
+
const check = root.querySelector('div[clips] label input[type=checkbox]')
|
|
325
|
+
return { found: !!check, choices: root.querySelectorAll('div[clips] label select').length, left: Math.round(check.getBoundingClientRect().left), fields: Math.round(root.querySelector('div[clips] label input:not([type=checkbox])').getBoundingClientRect().left) }
|
|
326
|
+
}, DEEP)
|
|
327
|
+
assert.deepEqual(box, { found: true, choices: 0, left: box.fields, fields: box.fields }, 'no list of two words; the box where the fields start')
|
|
328
|
+
await read(DEEP => eval(DEEP)('figure-preview')[0].shadowRoot.querySelector('div[clips] label input[type=checkbox]').click(), DEEP)
|
|
329
|
+
assert.equal(await read(DEEP => eval(DEEP)('figure-preview')[0].valuing.LOW, DEEP), true)
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
describe('the data table\'s kind column', () => {
|
|
334
|
+
before(async () => {
|
|
335
|
+
const asset = addFigureBox(createFigureAsset('Kinds'), { id: 'box', size: { x: 400, y: 400, z: 400 }, position: { x: 0, y: 200, z: 0 } })
|
|
336
|
+
asset.dataInterface = {
|
|
337
|
+
version: 'figure-data-interface-2',
|
|
338
|
+
schema: [
|
|
339
|
+
{ name: 'MODE', kind: 'text', values: ['idle', 'walk', 'run'] },
|
|
340
|
+
{ name: 'ANGLE', kind: 'number', unit: 'deg', min: -45, max: 45 }
|
|
341
|
+
],
|
|
342
|
+
bindings: []
|
|
343
|
+
}
|
|
344
|
+
await mount('box', serializeFigureAsset(asset))
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
test('says the kind and unit on one line; the values and range in its title (user 2026-11-12)', async () => {
|
|
348
|
+
await click(await tab('data'), 'the data tab')
|
|
349
|
+
const cells = await until(DEEP => {
|
|
350
|
+
const rows = [...(eval(DEEP)('div[fields]')[0]?.querySelectorAll(':scope > button[row]') ?? [])]
|
|
351
|
+
return rows.length === 2 ? rows.map(r => { const c = r.children[1]; return { text: c.textContent.trim(), title: c.title } }) : undefined
|
|
352
|
+
}, [DEEP], 'the two rows')
|
|
353
|
+
assert.ok(cells.every(c => !/[~(]/.test(c.text)), `no range or values in the cell: ${JSON.stringify(cells)}`)
|
|
354
|
+
assert.ok(cells[0].title.includes('idle, walk, run') && cells[1].title.includes('-45~45'), `said in the title: ${JSON.stringify(cells)}`)
|
|
194
355
|
})
|
|
195
356
|
})
|
package/test/data-motion.test.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
fieldOfKind,
|
|
23
23
|
fitsField,
|
|
24
24
|
freeIdOf,
|
|
25
|
+
freeNumberCodeOf,
|
|
25
26
|
legendRoleOf,
|
|
26
27
|
methodsOf,
|
|
27
28
|
motionWrittenByData,
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
schemaFollowingCodes,
|
|
30
31
|
tableColumnsOf,
|
|
31
32
|
tableDraftOf,
|
|
33
|
+
tableWithKeep,
|
|
32
34
|
tableWithKeys,
|
|
33
35
|
tableWithLook,
|
|
34
36
|
tableWithTargets,
|
|
@@ -97,7 +99,7 @@ test('작동 목록과 방법: 값 그대로 · 배율이 움직일 수 있는
|
|
|
97
99
|
assert.deepEqual(fits({ name: 'o', kind: 'object', fields: [] }), [])
|
|
98
100
|
assert.deepEqual(fits({ name: 'l', kind: 'list', item: { kind: 'object', fields: [] } }), ['items'])
|
|
99
101
|
const choices = behaviorsOf(DOG)
|
|
100
|
-
assert.deepEqual(methodsOf({ kind: 'number' }, choices), ['value', 'scale', 'ranges'])
|
|
102
|
+
assert.deepEqual(methodsOf({ kind: 'number' }, choices), ['value', 'scale', 'code', 'ranges'], '숫자도 코드 표 (ADR-0166 개정 1)')
|
|
101
103
|
assert.deepEqual(methodsOf({ kind: 'text' }, choices), ['value', 'code'])
|
|
102
104
|
assert.deepEqual(methodsOf({ kind: 'boolean' }, choices), ['value', 'code'])
|
|
103
105
|
assert.deepEqual(methodsOf({ kind: 'list', item: { kind: 'text' } }, choices), ['value'])
|
|
@@ -169,21 +171,20 @@ test('샘플: 보드의 Figure 와 같은 읽기로 -- 무엇을 움직이는지
|
|
|
169
171
|
assert.ok(Number.isNaN(typedValueOf(row('cadence'), '') as number), '빈 칸은 0 이 아니다')
|
|
170
172
|
})
|
|
171
173
|
|
|
172
|
-
test('표 초안: 닫힌 칸은 값마다 줄, 열린 칸은 줄 없이, 숫자는 한 구간
|
|
174
|
+
test('표 초안: 닫힌 칸은 값마다 줄, 열린 칸은 줄 없이, 숫자는 한 구간 (ADR-0166 10)', () => {
|
|
173
175
|
const mode = tableDraftOf(DOG, { path: 'mode', node: fieldAt(DOG.dataInterface!.schema, 'mode')! }, 'code', { parts: ['body'] })
|
|
174
|
-
assert.deepEqual([mode.binding.id, mode.binding.accessor, mode.binding.table
|
|
176
|
+
assert.deepEqual([mode.binding.id, mode.binding.accessor, mode.binding.table], ['mode', 'mode', { idle: {}, walk: {}, run: {} }])
|
|
175
177
|
editFigurePart(DOG, { kind: 'set-data-binding', binding: mode.binding })
|
|
176
178
|
const open = tableDraftOf(DOG, { path: 'status', node: fieldAt(DOG.dataInterface!.schema, 'status')! }, 'code', { parts: ['head'] })
|
|
177
179
|
assert.deepEqual(open.binding.table, {})
|
|
178
180
|
const pct = tableDraftOf(DOG, { path: 'cadence', node: fieldAt(DOG.dataInterface!.schema, 'cadence')! }, 'ranges', { parts: ['body'] })
|
|
179
181
|
assert.deepEqual(pct.binding.table, { '0~': {} })
|
|
180
182
|
editFigurePart(DOG, { kind: 'set-data-binding', binding: pct.binding })
|
|
181
|
-
/*
|
|
182
|
-
const
|
|
183
|
-
|
|
183
|
+
/* 표는 표의 행이 연 필드에 붙는다: 필드는 먼저 등록한다 (데이터 탭이 필드마다 연결을 둔다, 2026-11-12). */
|
|
184
|
+
const typed = withField(DOG, { name: 'EMPTYTYPE', kind: 'text', values: ['CODE'] })
|
|
185
|
+
const made = tableDraftOf(typed, { path: 'EMPTYTYPE', node: fieldAt(typed.dataInterface!.schema, 'EMPTYTYPE')! }, 'code', { parts: ['body'] })
|
|
184
186
|
assert.deepEqual(made.binding.table, { CODE: {} })
|
|
185
|
-
const asset = editFigurePart(
|
|
186
|
-
assert.deepEqual(tableDraftOf(DOG, { name: 'RATE' }, 'ranges', { parts: ['body'] }).schema!.at(-1), { name: 'RATE', kind: 'number' })
|
|
187
|
+
const asset = editFigurePart(typed, { kind: 'set-data-binding', binding: made.binding })
|
|
187
188
|
/* 닫힌 필드의 값 목록은 표의 키를 따라간다: 코드를 더하면 필드에도. */
|
|
188
189
|
const b = asset.dataInterface!.bindings.find(x => x.id === 'emptytype')! as never
|
|
189
190
|
const grown = tableWithKeys(b, [['FULL', { color: '#3b6d11' }], ['EMPTY', {}]])
|
|
@@ -194,6 +195,25 @@ test('표 초안: 닫힌 칸은 값마다 줄, 열린 칸은 줄 없이, 숫자
|
|
|
194
195
|
assert.equal(legendRoleOf('Body_Status'), 'body-status')
|
|
195
196
|
})
|
|
196
197
|
|
|
198
|
+
/*
|
|
199
|
+
* ADR-0166 개정 1 (2026-11-12, 튜토리얼): MCS 는 STATUS 를 숫자(4002)로 보낸다. 숫자 칸에도 코드 표를 만들고, 「그 밖의 값」은
|
|
200
|
+
* 표가 마지막으로 보인 모양을 그대로 둘 수 있다 -- 4004(이동 중)에 포크가 내려가지 않는다.
|
|
201
|
+
*/
|
|
202
|
+
test('숫자 칸의 코드 표: 키는 숫자, 새 코드도 숫자 -- 그 밖의 값은 「그대로 둠」, 대상을 바꿔도 남는다 (ADR-0166 개정 1)', () => {
|
|
203
|
+
const draft = tableDraftOf(DOG, { path: 'cadence', node: fieldAt(DOG.dataInterface!.schema, 'cadence')! }, 'code', { parts: ['body'] })
|
|
204
|
+
assert.deepEqual(draft.binding.table, { 0: {} }, '숫자 칸의 첫 코드는 0')
|
|
205
|
+
assert.equal(draft.binding.lookup, 'code')
|
|
206
|
+
editFigurePart(DOG, { kind: 'set-data-binding', binding: draft.binding })
|
|
207
|
+
assert.equal(freeNumberCodeOf([]), '0')
|
|
208
|
+
assert.equal(freeNumberCodeOf(['4002', '4003']), '4004')
|
|
209
|
+
assert.equal(freeNumberCodeOf(['-1', 'x']), '0')
|
|
210
|
+
const kept = tableWithKeep(tableWithKeys(draft.binding, [['4002', { color: '#3b6d11' }], ['4003', {}]]), true)
|
|
211
|
+
assert.deepEqual(kept.table, { 4002: { color: '#3b6d11' }, 4003: {}, default: { keep: true } })
|
|
212
|
+
editFigurePart(DOG, { kind: 'set-data-binding', binding: kept })
|
|
213
|
+
assert.deepEqual(tableWithTargets(kept, { parts: ['head'] }).table.default, { keep: true }, '대상을 바꿔도 그대로 둠은 남는다')
|
|
214
|
+
assert.deepEqual(tableWithKeep(kept, false).table.default, {}, '끄면 빈 모양(기본값)')
|
|
215
|
+
})
|
|
216
|
+
|
|
197
217
|
test('표의 대상과 열: 부품은 색·효과, 조작값은 값 하나씩, 동작 그룹은 클립·배속·멈춤 -- 대상을 빼면 그 열도 빠진다', () => {
|
|
198
218
|
const free = editFigurePart(DOG, { kind: 'remove-data-binding', id: 'mode-locomotion' })
|
|
199
219
|
const draftTable = tableDraftOf(free, { path: 'mode', node: fieldAt(free.dataInterface!.schema, 'mode')! }, 'code', { parts: ['body'], inputs: ['fl-knee'], group: 'locomotion' }).binding
|
|
@@ -295,6 +315,17 @@ test('데이터 탭 · 클립 패널 · 미리보기의 배선과 문구', () =>
|
|
|
295
315
|
assert.match(editor, /<figure-data-panel/)
|
|
296
316
|
assert.match(editor, /\.sample=\$\{this\.dataSample\}/)
|
|
297
317
|
assert.match(editor, /this\.dataSample = e\.detail\.data/)
|
|
318
|
+
/*
|
|
319
|
+
* 견본 데이터는 미리보기에서만 보인다: 편집 화면에서 「미리보기에 적용」을 켜면 그렇게 말하고, 단추 하나로 미리보기로 간다
|
|
320
|
+
* (튜토리얼 2026-11-12: 켜도 편집 화면이 그대로라 적용이 안 된 것처럼 보였다). 편집 화면은 기본 자세 그대로다.
|
|
321
|
+
*/
|
|
322
|
+
assert.match(editor, /\?previewing=\$\{this\.middle === 'preview'\}/)
|
|
323
|
+
assert.match(editor, /@show-preview=\$\{\(\) => \(this\.middle = 'preview'\)\}/)
|
|
324
|
+
const panel = read('../client/modeller/figure-data-panel.ts')
|
|
325
|
+
assert.match(panel, /this\.sampling && !this\.previewing/)
|
|
326
|
+
assert.match(panel, /'figure\.text\.data-sample-shown-in-preview'/)
|
|
327
|
+
assert.match(panel, /'figure\.button\.data-sample-show-preview'/)
|
|
328
|
+
assert.match(panel, /new CustomEvent\('show-preview'/)
|
|
298
329
|
/* 「+ 회전」은 여러 명령을 되돌리기 한 단계로: 하나가 거절되면 아무것도 바뀌지 않는다. */
|
|
299
330
|
assert.match(editor, /@part-edits=\$\{\(e: CustomEvent\) => this\.run\(\(\) => this\.session!\.applyRecipe\(e\.detail\.edits\)\)\}/)
|
|
300
331
|
const preview = read('../client/modeller/figure-preview.ts')
|
package/test/fasten-form.test.ts
CHANGED
|
@@ -134,6 +134,31 @@ test('another way of meeting works the gap out again; a number typed stays; a pr
|
|
|
134
134
|
assert.equal(fastenChanged(r, 'lid', pressed, { mode: 'flush' }).gap, 0, 'pressed: left at 0')
|
|
135
135
|
})
|
|
136
136
|
|
|
137
|
+
/*
|
|
138
|
+
* A part already fastened, aimed again through 「바꾸기」, goes where it is aimed with its fastening as it was -- the
|
|
139
|
+
* gap and the place across the face carried to the new face or part (tutorial 2026-11-12: a pillar copied from the front
|
|
140
|
+
* one and turned to the body's back face stayed in front, its gap worked out as the whole body's depth). 「현재 상태로
|
|
141
|
+
* 붙이기」 is a loose part's: fastening it does not move it.
|
|
142
|
+
*/
|
|
143
|
+
test('a fastened part aimed again goes there with its gap and its place across the face; a loose one stays where it stands', () => {
|
|
144
|
+
const r = conveyor()
|
|
145
|
+
const own = fastenFormOf(r, 'roller')
|
|
146
|
+
const under = fastenChanged(r, 'roller', own, { targetFace: 'bottom' })
|
|
147
|
+
assert.deepEqual([under.targetFace, under.gap, under.align.x, under.alignMm.x], ['bottom', 0, 'mm', 300], 'its own gap and place, not where it stands')
|
|
148
|
+
const moved = applyFigureAuthoring(r.asset, fastenPlanOf(r, 'roller', under).action as never)
|
|
149
|
+
const [x, y] = centreOf(moved, 'roller')
|
|
150
|
+
assert.ok(Math.abs(x! - 300) < 1e-6, `still 300 mm across: ${x}`)
|
|
151
|
+
assert.ok(y! < 0, `under the frame now: ${y}`)
|
|
152
|
+
/* Another part: on it, as fastened before. */
|
|
153
|
+
const onLid = aimFasten(r, 'roller', own, { part: 'lid' })
|
|
154
|
+
assert.deepEqual([onLid.target, onLid.gap, onLid.alignMm.x], ['lid', 0, 300])
|
|
155
|
+
/* A mode changed keeps its gap too; a number typed is the author's. */
|
|
156
|
+
assert.equal(fastenChanged(r, 'roller', own, { mode: 'flush' }).gap, 0)
|
|
157
|
+
/* The loose lid is fastened where it stands, as before. */
|
|
158
|
+
const loose = keptGapOf(floating(), 'lid', aimFasten(floating(), 'lid', fastenFormOf(floating(), 'lid'), { part: 'plate' }))
|
|
159
|
+
assert.equal(loose.gap, 40)
|
|
160
|
+
})
|
|
161
|
+
|
|
137
162
|
test('the target list: every other part, the one hanging from this part listed as a loop', () => {
|
|
138
163
|
const r = conveyor()
|
|
139
164
|
assert.deepEqual(fastenTargetsOf(r, 'frame'), [
|
|
@@ -121,3 +121,16 @@ test('the face drawing shows a part bigger than the face whole when lined up wit
|
|
|
121
121
|
assert.equal(drawingSpanOf(600, 300), 900)
|
|
122
122
|
assert.equal(drawingSpanOf(undefined, 200), 600)
|
|
123
123
|
})
|
|
124
|
+
|
|
125
|
+
/*
|
|
126
|
+
* A part far off the face's centre -- a palette box left 1500 mm up, aimed at a body's front -- is drawn whole with its
|
|
127
|
+
* size label: the drawing reaches it, not only the face and the part's own size (roll cradle tutorial 2026-11-12: the
|
|
128
|
+
* label was cut off the drawing's top and could not be pressed).
|
|
129
|
+
*/
|
|
130
|
+
test('the face drawing reaches a part standing far off the face, its far edge inside', () => {
|
|
131
|
+
const span = drawingSpanOf(1200, 300, 1500)
|
|
132
|
+
assert.ok(span / 2 >= 1500 + 300 / 2, `span ${span}`)
|
|
133
|
+
assert.equal(drawingSpanOf(1200, 300, -1500), span, 'either side alike')
|
|
134
|
+
/* Near the centre it keeps the room it had. */
|
|
135
|
+
assert.equal(drawingSpanOf(600, 300, 100), 900)
|
|
136
|
+
})
|
package/translations/en.json
CHANGED
|
@@ -1052,12 +1052,12 @@
|
|
|
1052
1052
|
"figure.label.data-scale": "scale",
|
|
1053
1053
|
"figure.label.data-offset": "offset",
|
|
1054
1054
|
"figure.label.data-sample": "sample data",
|
|
1055
|
-
"figure.text.data-sample-explained": "Enter one record as JSON to see what it moves. It is read the way a figure on a board reads its data, and not saved.",
|
|
1056
|
-
"figure.text.data-sample-moves-all": "A position in the data moves every figure in the preview to it; on a board, figures given the same value stand in the same place too.",
|
|
1055
|
+
"figure.text.data-sample-explained": "Enter one record as JSON or JSON5 to see what it moves. It is read the way a figure on a board reads its data, and not saved.",
|
|
1057
1056
|
"figure.label.data-sample-in-preview": "apply to the preview",
|
|
1057
|
+
"figure.text.data-sample-shown-in-preview": "The sample shows in the preview; the edit view keeps the figure as made.",
|
|
1058
|
+
"figure.button.data-sample-show-preview": "Show the preview",
|
|
1058
1059
|
"figure.label.data-schema": "data shape",
|
|
1059
1060
|
"figure.text.data-schema-explained": "The shape of the data a site sends this figure. Name each field as the site's data names it; group fields in objects and lists.",
|
|
1060
|
-
"figure.label.data-bound": "{count} bound",
|
|
1061
1061
|
"figure.text.data-no-fields": "Add a field to the data shape first.",
|
|
1062
1062
|
"figure.label.data-name": "field name",
|
|
1063
1063
|
"figure.label.data-label": "display name",
|
|
@@ -1068,17 +1068,18 @@
|
|
|
1068
1068
|
"figure.button.remove-data-field": "remove the field",
|
|
1069
1069
|
"figure.label.data-from-sample": "make from a sample",
|
|
1070
1070
|
"figure.text.data-from-sample-explained": "Paste one record of the site's data to guess its fields. Units, ranges and value lists are not guessed; set them after.",
|
|
1071
|
-
"figure.text.data-not-json": "This is not JSON.",
|
|
1071
|
+
"figure.text.data-not-json": "This is not JSON or JSON5.",
|
|
1072
1072
|
"figure.text.data-guessed": "{count} fields found.",
|
|
1073
1073
|
"figure.text.data-guessed-replaces": "{count} fields found. Using them replaces the current data shape.",
|
|
1074
1074
|
"figure.button.data-use-guessed": "use this shape",
|
|
1075
|
-
"figure.text.data-bindings-explained": "Connects data values to the figure. A number
|
|
1076
|
-
"figure.label.data-field": "data value",
|
|
1075
|
+
"figure.text.data-bindings-explained": "Connects data values to the figure. A number applies unchanged, scaled, or by a range table; a text or true/false by a code table. A table sets, per value, parts' colour and effect, controls and a motion group at once. Select a row to edit it.",
|
|
1077
1076
|
"figure.label.data-behavior": "behaviour",
|
|
1078
1077
|
"figure.label.data-seconds": "travel time (s)",
|
|
1079
1078
|
"figure.label.data-paused": "paused",
|
|
1080
1079
|
"figure.label.data-playing": "playing",
|
|
1081
1080
|
"figure.label.data-otherwise": "any other value",
|
|
1081
|
+
"figure.label.data-keep": "keep as is",
|
|
1082
|
+
"figure.text.data-keep": "A value with no row of its own leaves the look this table last showed.",
|
|
1082
1083
|
"figure.label.data-range-from": "from",
|
|
1083
1084
|
"figure.label.data-range-to": "below",
|
|
1084
1085
|
"figure.button.add-data-range": "add an interval",
|
|
@@ -1101,7 +1102,6 @@
|
|
|
1101
1102
|
"figure.button.data-add-rotation": "make the rotation",
|
|
1102
1103
|
"figure.label.data-none": "none",
|
|
1103
1104
|
"figure.button.remove-data-binding": "remove the binding",
|
|
1104
|
-
"figure.label.data-items-count": "{count} items",
|
|
1105
1105
|
"figure.text.data-value-not-a-list": "{path}: not a list.",
|
|
1106
1106
|
"figure.text.data-value-value-unknown": "{path}: not one of its values.",
|
|
1107
1107
|
"figure.text.data-value-json-unreadable": "{path}: the JSON text cannot be read.",
|
|
@@ -1239,10 +1239,7 @@
|
|
|
1239
1239
|
"figure.label.data-legend-role": "Legend role",
|
|
1240
1240
|
"figure.label.data-legend-swappable": "A board may swap it for a legend",
|
|
1241
1241
|
"figure.label.data-method": "Method",
|
|
1242
|
-
"figure.label.data-new-field": "New field",
|
|
1243
1242
|
"figure.label.data-sample-legend": "Sample legend",
|
|
1244
|
-
"figure.label.data-table-legend": "legend {name}",
|
|
1245
|
-
"figure.label.data-table-own": "figure's table",
|
|
1246
1243
|
"figure.label.data-target-group": "Motion group",
|
|
1247
1244
|
"figure.label.data-target-inputs": "Controls",
|
|
1248
1245
|
"figure.label.data-target-parts": "Parts",
|
|
@@ -1410,5 +1407,20 @@
|
|
|
1410
1407
|
"figure.label.clip-interpolation-smooth": "Ease in and out",
|
|
1411
1408
|
"figure.label.clip-interpolation-step": "Jump",
|
|
1412
1409
|
"figure.text.copy-track-off-key": "A smooth track can be shifted only so that it starts at one of its keys. Possible values: {seconds} s",
|
|
1413
|
-
"figure.text.mirror-copies-clips": "The motion in {clips} is copied to the copy too."
|
|
1410
|
+
"figure.text.mirror-copies-clips": "The motion in {clips} is copied to the copy too.",
|
|
1411
|
+
"figure.label.data-bindings-title": "Data bindings",
|
|
1412
|
+
"figure.label.data-column-field": "Field",
|
|
1413
|
+
"figure.label.data-column-kind": "Kind",
|
|
1414
|
+
"figure.label.data-column-moves": "Drives",
|
|
1415
|
+
"figure.text.data-method-does-value": "Applies the data value unchanged.",
|
|
1416
|
+
"figure.text.data-method-does-scale": "Applies the data value multiplied by a scale.",
|
|
1417
|
+
"figure.text.data-method-does-code": "Applies what the table sets for each data value.",
|
|
1418
|
+
"figure.text.data-method-does-ranges": "Applies what the table sets for the range the data value falls in.",
|
|
1419
|
+
"figure.label.data-bindings-without-field": "Bindings without a field",
|
|
1420
|
+
"figure.button.add": "Add",
|
|
1421
|
+
"figure.label.data-targets": "Targets",
|
|
1422
|
+
"figure.label.data-target-add": "Add target",
|
|
1423
|
+
"figure.label.data-target-choose": "Choose a part or control",
|
|
1424
|
+
"figure.text.data-no-targets": "No target chosen.",
|
|
1425
|
+
"figure.button.data-remove-target": "Remove target"
|
|
1414
1426
|
}
|
package/translations/es.json
CHANGED
|
@@ -1052,12 +1052,12 @@
|
|
|
1052
1052
|
"figure.label.data-scale": "escala",
|
|
1053
1053
|
"figure.label.data-offset": "desplazamiento",
|
|
1054
1054
|
"figure.label.data-sample": "datos de ejemplo",
|
|
1055
|
-
"figure.text.data-sample-explained": "Introduzca un registro en JSON para ver qué mueve. Se lee como una figura en un tablero lee sus datos, y no se guarda.",
|
|
1056
|
-
"figure.text.data-sample-moves-all": "Una posición en los datos lleva todas las figuras de la vista previa a ella; en un tablero, las figuras con el mismo valor también quedan en el mismo lugar.",
|
|
1055
|
+
"figure.text.data-sample-explained": "Introduzca un registro en JSON o JSON5 para ver qué mueve. Se lee como una figura en un tablero lee sus datos, y no se guarda.",
|
|
1057
1056
|
"figure.label.data-sample-in-preview": "aplicar a la vista previa",
|
|
1057
|
+
"figure.text.data-sample-shown-in-preview": "Los datos de muestra se ven en la vista previa; la edición muestra la figura tal como se hizo.",
|
|
1058
|
+
"figure.button.data-sample-show-preview": "Ver la vista previa",
|
|
1058
1059
|
"figure.label.data-schema": "forma de los datos",
|
|
1059
1060
|
"figure.text.data-schema-explained": "La forma de los datos que un sitio envía a esta figura. Nombre cada campo como lo nombran los datos del sitio; agrupe campos en objetos y listas.",
|
|
1060
|
-
"figure.label.data-bound": "{count} vinculados",
|
|
1061
1061
|
"figure.text.data-no-fields": "Primero añada un campo a la forma de los datos.",
|
|
1062
1062
|
"figure.label.data-name": "nombre del campo",
|
|
1063
1063
|
"figure.label.data-label": "nombre visible",
|
|
@@ -1068,17 +1068,18 @@
|
|
|
1068
1068
|
"figure.button.remove-data-field": "quitar el campo",
|
|
1069
1069
|
"figure.label.data-from-sample": "crear desde una muestra",
|
|
1070
1070
|
"figure.text.data-from-sample-explained": "Pegue un registro de los datos del sitio para deducir sus campos. Unidades, rangos y listas de valores no se deducen; fíjelos después.",
|
|
1071
|
-
"figure.text.data-not-json": "No es JSON.",
|
|
1071
|
+
"figure.text.data-not-json": "No es JSON ni JSON5.",
|
|
1072
1072
|
"figure.text.data-guessed": "Se encontraron {count} campos.",
|
|
1073
1073
|
"figure.text.data-guessed-replaces": "Se encontraron {count} campos. Usarlos reemplaza la forma de datos actual.",
|
|
1074
1074
|
"figure.button.data-use-guessed": "usar esta forma",
|
|
1075
1075
|
"figure.text.data-bindings-explained": "Conecta valores de datos con la figura. Un número se muestra tal cual, escalado o con una tabla de rangos; un texto o verdadero/falso, con una tabla de códigos. Una tabla fija por valor el color y efecto de las piezas, los controles y un grupo de movimiento. Seleccione una fila para editarla.",
|
|
1076
|
-
"figure.label.data-field": "valor de datos",
|
|
1077
1076
|
"figure.label.data-behavior": "comportamiento",
|
|
1078
1077
|
"figure.label.data-seconds": "tiempo de desplazamiento (s)",
|
|
1079
1078
|
"figure.label.data-paused": "en pausa",
|
|
1080
1079
|
"figure.label.data-playing": "en marcha",
|
|
1081
1080
|
"figure.label.data-otherwise": "cualquier otro valor",
|
|
1081
|
+
"figure.label.data-keep": "mantener",
|
|
1082
|
+
"figure.text.data-keep": "Un valor sin fila propia deja el aspecto que esta tabla mostró por última vez.",
|
|
1082
1083
|
"figure.label.data-range-from": "desde",
|
|
1083
1084
|
"figure.label.data-range-to": "hasta (sin incluir)",
|
|
1084
1085
|
"figure.button.add-data-range": "añadir intervalo",
|
|
@@ -1101,7 +1102,6 @@
|
|
|
1101
1102
|
"figure.button.data-add-rotation": "crear la rotación",
|
|
1102
1103
|
"figure.label.data-none": "ninguno",
|
|
1103
1104
|
"figure.button.remove-data-binding": "quitar el vínculo",
|
|
1104
|
-
"figure.label.data-items-count": "{count} artículos",
|
|
1105
1105
|
"figure.text.data-value-not-a-list": "{path}: no es una lista.",
|
|
1106
1106
|
"figure.text.data-value-value-unknown": "{path}: no es uno de sus valores.",
|
|
1107
1107
|
"figure.text.data-value-json-unreadable": "{path}: no se puede leer el texto JSON.",
|
|
@@ -1239,10 +1239,7 @@
|
|
|
1239
1239
|
"figure.label.data-legend-role": "Rol de leyenda",
|
|
1240
1240
|
"figure.label.data-legend-swappable": "Quien lo usa puede cambiarla por una leyenda",
|
|
1241
1241
|
"figure.label.data-method": "Método",
|
|
1242
|
-
"figure.label.data-new-field": "Campo nuevo",
|
|
1243
1242
|
"figure.label.data-sample-legend": "Leyenda de ejemplo",
|
|
1244
|
-
"figure.label.data-table-legend": "leyenda {name}",
|
|
1245
|
-
"figure.label.data-table-own": "tabla de la figura",
|
|
1246
1243
|
"figure.label.data-target-group": "Grupo de movimiento",
|
|
1247
1244
|
"figure.label.data-target-inputs": "Controles",
|
|
1248
1245
|
"figure.label.data-target-parts": "Piezas",
|
|
@@ -1410,5 +1407,20 @@
|
|
|
1410
1407
|
"figure.label.clip-interpolation-smooth": "Arranque y parada suaves",
|
|
1411
1408
|
"figure.label.clip-interpolation-step": "Salto",
|
|
1412
1409
|
"figure.text.copy-track-off-key": "Una pista suave solo puede desplazarse para que empiece en una de sus claves. Valores posibles: {seconds} s",
|
|
1413
|
-
"figure.text.mirror-copies-clips": "El movimiento de {clips} también se copia en la copia."
|
|
1410
|
+
"figure.text.mirror-copies-clips": "El movimiento de {clips} también se copia en la copia.",
|
|
1411
|
+
"figure.label.data-bindings-title": "Vínculos de datos",
|
|
1412
|
+
"figure.label.data-column-field": "Campo",
|
|
1413
|
+
"figure.label.data-column-kind": "Tipo",
|
|
1414
|
+
"figure.label.data-column-moves": "Mueve",
|
|
1415
|
+
"figure.text.data-method-does-value": "Aplica el valor del dato sin convertirlo.",
|
|
1416
|
+
"figure.text.data-method-does-scale": "Aplica el valor del dato multiplicado por una escala.",
|
|
1417
|
+
"figure.text.data-method-does-code": "Aplica lo que la tabla fija para cada valor del dato.",
|
|
1418
|
+
"figure.text.data-method-does-ranges": "Aplica lo que la tabla fija para el intervalo en que cae el valor.",
|
|
1419
|
+
"figure.label.data-bindings-without-field": "Vínculos sin campo",
|
|
1420
|
+
"figure.button.add": "Añadir",
|
|
1421
|
+
"figure.label.data-targets": "Destinos",
|
|
1422
|
+
"figure.label.data-target-add": "Añadir destino",
|
|
1423
|
+
"figure.label.data-target-choose": "Elija una pieza o un control",
|
|
1424
|
+
"figure.text.data-no-targets": "No hay destinos elegidos.",
|
|
1425
|
+
"figure.button.data-remove-target": "Quitar destino"
|
|
1414
1426
|
}
|
package/translations/ja.json
CHANGED
|
@@ -1052,12 +1052,12 @@
|
|
|
1052
1052
|
"figure.label.data-scale": "倍率",
|
|
1053
1053
|
"figure.label.data-offset": "加算",
|
|
1054
1054
|
"figure.label.data-sample": "サンプルデータ",
|
|
1055
|
-
"figure.text.data-sample-explained": "データを1件JSONで入れて何が動くかを確認します。ボードに置いたFigureと同じ方法で読み、保存しません。",
|
|
1056
|
-
"figure.text.data-sample-moves-all": "位置を決める値はプレビューのFigureをすべてその位置に移します。ボードでも同じ値を受けたFigureは同じ位置に立ちます。",
|
|
1055
|
+
"figure.text.data-sample-explained": "データを1件JSONまたはJSON5で入れて何が動くかを確認します。ボードに置いたFigureと同じ方法で読み、保存しません。",
|
|
1057
1056
|
"figure.label.data-sample-in-preview": "プレビューに適用",
|
|
1057
|
+
"figure.text.data-sample-shown-in-preview": "サンプルデータはプレビューに表示されます。編集画面は作ったままです。",
|
|
1058
|
+
"figure.button.data-sample-show-preview": "プレビューで見る",
|
|
1058
1059
|
"figure.label.data-schema": "データ形式",
|
|
1059
1060
|
"figure.text.data-schema-explained": "現場がこのFigureに送るデータの形です。フィールド名は現場データの名前どおりに書き、オブジェクトとリストでまとめられます。",
|
|
1060
|
-
"figure.label.data-bound": "動作 {count}件",
|
|
1061
1061
|
"figure.text.data-no-fields": "先にデータ形式にフィールドを追加します。",
|
|
1062
1062
|
"figure.label.data-name": "フィールド名",
|
|
1063
1063
|
"figure.label.data-label": "表示名",
|
|
@@ -1068,17 +1068,18 @@
|
|
|
1068
1068
|
"figure.button.remove-data-field": "フィールドを削除",
|
|
1069
1069
|
"figure.label.data-from-sample": "サンプルから作る",
|
|
1070
1070
|
"figure.text.data-from-sample-explained": "現場データを1件貼り付けるとフィールドを推定します。単位・範囲・値の一覧は推定しないので、置き換えた後に決めます。",
|
|
1071
|
-
"figure.text.data-not-json": "JSON形式ではありません。",
|
|
1071
|
+
"figure.text.data-not-json": "JSONまたはJSON5形式ではありません。",
|
|
1072
1072
|
"figure.text.data-guessed": "フィールドが {count} 件見つかりました。",
|
|
1073
1073
|
"figure.text.data-guessed-replaces": "フィールドが {count} 件見つかりました。置き換えると今のデータ形式に代わります。",
|
|
1074
1074
|
"figure.button.data-use-guessed": "この形式に置き換える",
|
|
1075
1075
|
"figure.text.data-bindings-explained": "データの値をフィギュアにつなぎます。数値はそのまま・倍率・区間表で、文字と真偽はコード表で表示します。表は値ごとに部品の色と効果、操作値、動作グループをまとめて決めます。行を選ぶと編集できます。",
|
|
1076
|
-
"figure.label.data-field": "データ値",
|
|
1077
1076
|
"figure.label.data-behavior": "動作",
|
|
1078
1077
|
"figure.label.data-seconds": "移動時間 (秒)",
|
|
1079
1078
|
"figure.label.data-paused": "一時停止",
|
|
1080
1079
|
"figure.label.data-playing": "再生",
|
|
1081
1080
|
"figure.label.data-otherwise": "その他の値",
|
|
1081
|
+
"figure.label.data-keep": "そのまま",
|
|
1082
|
+
"figure.text.data-keep": "表にない値が来ると、この表が最後に示した見た目をそのまま保ちます。",
|
|
1082
1083
|
"figure.label.data-range-from": "以上",
|
|
1083
1084
|
"figure.label.data-range-to": "未満",
|
|
1084
1085
|
"figure.button.add-data-range": "区間を追加",
|
|
@@ -1101,7 +1102,6 @@
|
|
|
1101
1102
|
"figure.button.data-add-rotation": "回転を作る",
|
|
1102
1103
|
"figure.label.data-none": "なし",
|
|
1103
1104
|
"figure.button.remove-data-binding": "連結を削除",
|
|
1104
|
-
"figure.label.data-items-count": "アイテム {count}件",
|
|
1105
1105
|
"figure.text.data-value-not-a-list": "{path}: リストではありません。",
|
|
1106
1106
|
"figure.text.data-value-value-unknown": "{path}: 値の一覧にない値です。",
|
|
1107
1107
|
"figure.text.data-value-json-unreadable": "{path}: JSON文字列を読めません。",
|
|
@@ -1239,10 +1239,7 @@
|
|
|
1239
1239
|
"figure.label.data-legend-role": "凡例の役割",
|
|
1240
1240
|
"figure.label.data-legend-swappable": "使う側が凡例に置き換えられる",
|
|
1241
1241
|
"figure.label.data-method": "方法",
|
|
1242
|
-
"figure.label.data-new-field": "新しいフィールド",
|
|
1243
1242
|
"figure.label.data-sample-legend": "サンプル凡例",
|
|
1244
|
-
"figure.label.data-table-legend": "凡例 {name}",
|
|
1245
|
-
"figure.label.data-table-own": "フィギュアの表",
|
|
1246
1243
|
"figure.label.data-target-group": "動作グループ",
|
|
1247
1244
|
"figure.label.data-target-inputs": "操作値",
|
|
1248
1245
|
"figure.label.data-target-parts": "部品",
|
|
@@ -1410,5 +1407,20 @@
|
|
|
1410
1407
|
"figure.label.clip-interpolation-smooth": "滑らかに発進・停止",
|
|
1411
1408
|
"figure.label.clip-interpolation-step": "即時切り替え",
|
|
1412
1409
|
"figure.text.copy-track-off-key": "滑らかに動くトラックは、キーのある時点に合わせてのみ時間をずらせます。可能な値: {seconds}秒",
|
|
1413
|
-
"figure.text.mirror-copies-clips": "「{clips}」クリップの動きもコピーに一緒に複製されます。"
|
|
1410
|
+
"figure.text.mirror-copies-clips": "「{clips}」クリップの動きもコピーに一緒に複製されます。",
|
|
1411
|
+
"figure.label.data-bindings-title": "データ連結",
|
|
1412
|
+
"figure.label.data-column-field": "フィールド",
|
|
1413
|
+
"figure.label.data-column-kind": "種類",
|
|
1414
|
+
"figure.label.data-column-moves": "連結先",
|
|
1415
|
+
"figure.text.data-method-does-value": "データ値を変換せずに適用します。",
|
|
1416
|
+
"figure.text.data-method-does-scale": "データ値に倍率を掛けて適用します。",
|
|
1417
|
+
"figure.text.data-method-does-code": "データ値ごとに表で指定した設定を適用します。",
|
|
1418
|
+
"figure.text.data-method-does-ranges": "データ値が属する区間ごとに表で指定した設定を適用します。",
|
|
1419
|
+
"figure.label.data-bindings-without-field": "フィールドのない連結",
|
|
1420
|
+
"figure.button.add": "追加",
|
|
1421
|
+
"figure.label.data-targets": "対象",
|
|
1422
|
+
"figure.label.data-target-add": "対象を追加",
|
|
1423
|
+
"figure.label.data-target-choose": "部品または操作値を選択",
|
|
1424
|
+
"figure.text.data-no-targets": "選択した対象がありません。",
|
|
1425
|
+
"figure.button.data-remove-target": "対象を削除"
|
|
1414
1426
|
}
|