@things-factory/figure-ui 10.1.71 → 10.1.72
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/figure-v3-recipe-catalog.ts +22 -4
- package/dist-client/modeller/figure-v3-recipe-catalog.d.ts +2 -0
- package/dist-client/modeller/figure-v3-recipe-catalog.js +21 -4
- package/dist-client/modeller/figure-v3-recipe-catalog.js.map +1 -1
- package/dist-client/modeller/v3-clips.d.ts +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/test/browser/build.mjs +12 -0
- package/test/browser/modeller-page.js +2 -1
- package/test/browser/modeller.check.mjs +19 -2
- package/test/browser/serve.mjs +7 -1
- package/test/v3-recipes.test.ts +6 -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.72",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"clean": "npm run clean:server && npm run clean:client"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@hatiolab/figure-model": "^0.1.
|
|
30
|
+
"@hatiolab/figure-model": "^0.1.87",
|
|
31
31
|
"@hatiolab/things-scene": "^10.1.56",
|
|
32
32
|
"@material/web": "^2.0.0",
|
|
33
33
|
"@operato/data-grist": "^10.0.0",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"@operato/styles": "^10.0.0",
|
|
42
42
|
"@operato/utils": "^10.0.0",
|
|
43
43
|
"@things-factory/ai-assistant": "^10.1.58",
|
|
44
|
-
"@things-factory/figure-service": "^10.1.
|
|
44
|
+
"@things-factory/figure-service": "^10.1.72",
|
|
45
45
|
"three": "^0.185.1"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "fdf6d4faac454dbdca62d8fe7409395dff1ada22"
|
|
48
48
|
}
|
package/test/browser/build.mjs
CHANGED
|
@@ -56,6 +56,18 @@ export async function buildPage(page = 'modeller-page.js') {
|
|
|
56
56
|
const file = join(out, 'page.js')
|
|
57
57
|
await bundle.write({ file, format: 'esm', inlineDynamicImports: true })
|
|
58
58
|
await bundle.close()
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
ox-markdown renders in a worker it loads from beside its module (\`../../workers/markdown-webworker.js\`); from
|
|
62
|
+
/page.js that is /workers/. Bundled here and served there, the guides render as in the app -- without it the worker
|
|
63
|
+
never answers and a guide is drawn empty.
|
|
64
|
+
*/
|
|
65
|
+
const worker = await rollup({
|
|
66
|
+
input: join(ROOT, 'node_modules/@operato/markdown/workers/markdown-webworker.js'),
|
|
67
|
+
plugins: [nodeResolve({ browser: true, modulePaths: [join(PACKAGE, 'node_modules'), join(ROOT, 'node_modules')] }), commonjs()]
|
|
68
|
+
})
|
|
69
|
+
await worker.write({ file: join(out, 'workers/markdown-webworker.js'), format: 'esm', inlineDynamicImports: true })
|
|
70
|
+
await worker.close()
|
|
59
71
|
return file
|
|
60
72
|
}
|
|
61
73
|
|
|
@@ -213,7 +213,7 @@ window.probe = {
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
window.harness = {
|
|
216
|
-
async mount(kind, text, templates = []) {
|
|
216
|
+
async mount(kind, text, templates = [], guides = {}) {
|
|
217
217
|
const source = sourceOf(kind, text)
|
|
218
218
|
const session = createV3ProductSession({
|
|
219
219
|
load: async id => ({ id, source }),
|
|
@@ -223,6 +223,7 @@ window.harness = {
|
|
|
223
223
|
const editor = document.createElement('figure-v3-editor')
|
|
224
224
|
editor.session = session
|
|
225
225
|
editor.templates = templates
|
|
226
|
+
editor.guideOf = async name => guides[name] ?? ''
|
|
226
227
|
editor.revision = 0
|
|
227
228
|
/* The modeller page counts a change the same way: the editor re-reads the session on a new revision. */
|
|
228
229
|
editor.addEventListener('v3-changed', () => (editor.revision = editor.revision + 1))
|
|
@@ -702,13 +702,30 @@ describe('the recipe catalog: a template chosen, filled in, put onto the chosen
|
|
|
702
702
|
}
|
|
703
703
|
|
|
704
704
|
test('the room chosen, the catalog opened: the door told in full, a share typed, put in -- the frame, the leaves and the lamp, and the catalog closes', async () => {
|
|
705
|
-
|
|
705
|
+
const guides = { 'sliding-door': readFileSync(resolve(HERE, '../../../figure-service/recipes/sliding-door/guide.en.md'), 'utf-8') }
|
|
706
|
+
await read((list, guides) => harness.mount('room', null, list, guides), templates(), guides)
|
|
706
707
|
await sleep(2500)
|
|
707
708
|
await click(await read(() => probe.row('room')))
|
|
708
709
|
await click(await at('button[recipe-catalog]'))
|
|
709
710
|
assert.ok(await at('figure-v3-recipe-catalog'), 'the catalog is open')
|
|
710
|
-
await click(await at('button[card="
|
|
711
|
+
await click(await at('button[card="hinged-door"]'))
|
|
712
|
+
/* A search that takes the chosen card away tells the first card it leaves (:3300, 2026-09-26). */
|
|
713
|
+
await type('input[search]', 'sliding')
|
|
714
|
+
assert.equal(await read(() => {
|
|
715
|
+
const deep = (root = document) => [...root.querySelectorAll('button[card][on]')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
716
|
+
return deep().map(b => b.getAttribute('card')).join()
|
|
717
|
+
}), 'sliding-door', 'the card told is one the search shows')
|
|
711
718
|
assert.ok(await at('li[parts="2–4"]'), 'the parts it adds, worked out from its choices: 2 to 4')
|
|
719
|
+
/* The guide is drawn as its text, not as the page its words would name (seen on :3300, 2026-09-26). */
|
|
720
|
+
await sleep(1500)
|
|
721
|
+
const guide = await read(() => {
|
|
722
|
+
const deep = (root = document) => [...root.querySelectorAll('ox-markdown')].concat(...[...root.querySelectorAll('*')].filter(e => e.shadowRoot).map(e => deep(e.shadowRoot)))
|
|
723
|
+
const shown = deep()[0]
|
|
724
|
+
return shown ? { headings: [...shown.shadowRoot.querySelectorAll('h2')].map(h => h.textContent.trim()), app: !!shown.shadowRoot.querySelector('ox-app, things-app, body, html') } : null
|
|
725
|
+
})
|
|
726
|
+
assert.ok(guide, 'the guide is shown')
|
|
727
|
+
assert.ok(guide.headings.includes('When to use it'), `the guide's own headings: ${JSON.stringify(guide.headings)}`)
|
|
728
|
+
assert.equal(guide.app, false, 'no page drawn inside it')
|
|
712
729
|
await type('input[param="share"]', '50')
|
|
713
730
|
const start = await state()
|
|
714
731
|
await click(await at('button[insert]'))
|
package/test/browser/serve.mjs
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Opens the built page in a headless browser: a tiny server for page.js, and puppeteer on it at a desktop size.
|
|
3
3
|
*/
|
|
4
4
|
import { createServer } from 'node:http'
|
|
5
|
-
import { readFileSync } from 'node:fs'
|
|
5
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
6
|
+
import { dirname, join, normalize } from 'node:path'
|
|
6
7
|
import puppeteer from 'puppeteer'
|
|
7
8
|
|
|
8
9
|
const HTML = `<!DOCTYPE html>
|
|
@@ -16,9 +17,14 @@ const HTML = `<!DOCTYPE html>
|
|
|
16
17
|
export async function openPage(bundle, { width = 1400, height = 900 } = {}) {
|
|
17
18
|
const js = readFileSync(bundle)
|
|
18
19
|
const server = createServer((req, res) => {
|
|
20
|
+
/* Scripts built beside the page (the markdown worker) are served as they are. */
|
|
21
|
+
const beside = join(dirname(bundle), normalize(req.url.split('?')[0]).replace(/^([/\\])+/, ''))
|
|
19
22
|
if (req.url === '/page.js') {
|
|
20
23
|
res.writeHead(200, { 'Content-Type': 'application/javascript' })
|
|
21
24
|
res.end(js)
|
|
25
|
+
} else if (req.url.endsWith('.js') && existsSync(beside)) {
|
|
26
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript' })
|
|
27
|
+
res.end(readFileSync(beside))
|
|
22
28
|
} else {
|
|
23
29
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
|
24
30
|
res.end(HTML)
|
package/test/v3-recipes.test.ts
CHANGED
|
@@ -53,7 +53,12 @@ test('작동 방식은 레시피에서 셈한다 — 미닫이 문: 두 문짝
|
|
|
53
53
|
{ key: 'figure.text.recipe-controls', words: { count: 1 } },
|
|
54
54
|
{ key: 'figure.text.recipe-shown-by-control', words: { parts: 'lamp' } }
|
|
55
55
|
])
|
|
56
|
-
|
|
56
|
+
/* The rollers spin on one control, every copy (ADR-0093, 2026-09-26). */
|
|
57
|
+
assert.deepEqual(recipeWorkings(JSON.parse(template('roller-row'))), [
|
|
58
|
+
{ key: 'figure.text.recipe-turns', words: { part: 'roller' } },
|
|
59
|
+
{ key: 'figure.text.recipe-controls', words: { count: 1 } },
|
|
60
|
+
{ key: 'figure.text.recipe-row', words: { part: 'roller' } }
|
|
61
|
+
])
|
|
57
62
|
})
|
|
58
63
|
|
|
59
64
|
test('모델러 — 부품 칸의 단추가 카탈로그를 열고, 넣으면 한 단계로 들어가고 창이 닫힌다; 목록은 페이지가 서버에서 한 번 읽는다', () => {
|