@things-factory/figure-ui 10.1.70 → 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.
Files changed (69) hide show
  1. package/client/graphql/index.ts +28 -69
  2. package/client/modeller/figure-v3-parts.ts +16 -136
  3. package/client/modeller/figure-v3-recipe-catalog.ts +366 -0
  4. package/client/modeller/v3-editor.ts +25 -5
  5. package/client/modeller/v3-fields.ts +21 -16
  6. package/client/modeller/v3-gizmo.ts +1 -1
  7. package/client/modeller/v3-links.ts +7 -66
  8. package/client/modeller/v3-palette.ts +3 -0
  9. package/client/modeller/v3-recipe-source.ts +15 -0
  10. package/client/modeller/v3-recipes.ts +50 -27
  11. package/client/pages/figure-v3-modeller-page.ts +16 -13
  12. package/client/route.ts +0 -4
  13. package/dist-client/graphql/index.d.ts +11 -18
  14. package/dist-client/graphql/index.js +18 -59
  15. package/dist-client/graphql/index.js.map +1 -1
  16. package/dist-client/modeller/figure-v3-parts.d.ts +5 -12
  17. package/dist-client/modeller/figure-v3-parts.js +17 -147
  18. package/dist-client/modeller/figure-v3-parts.js.map +1 -1
  19. package/dist-client/modeller/figure-v3-recipe-catalog.d.ts +38 -0
  20. package/dist-client/modeller/figure-v3-recipe-catalog.js +399 -0
  21. package/dist-client/modeller/figure-v3-recipe-catalog.js.map +1 -0
  22. package/dist-client/modeller/v3-clips.d.ts +1 -1
  23. package/dist-client/modeller/v3-editor.d.ts +8 -3
  24. package/dist-client/modeller/v3-editor.js +35 -6
  25. package/dist-client/modeller/v3-editor.js.map +1 -1
  26. package/dist-client/modeller/v3-fields.d.ts +2 -2
  27. package/dist-client/modeller/v3-fields.js +20 -18
  28. package/dist-client/modeller/v3-fields.js.map +1 -1
  29. package/dist-client/modeller/v3-gizmo.js +1 -1
  30. package/dist-client/modeller/v3-gizmo.js.map +1 -1
  31. package/dist-client/modeller/v3-links.d.ts +8 -25
  32. package/dist-client/modeller/v3-links.js +4 -66
  33. package/dist-client/modeller/v3-links.js.map +1 -1
  34. package/dist-client/modeller/v3-palette.js +3 -0
  35. package/dist-client/modeller/v3-palette.js.map +1 -1
  36. package/dist-client/modeller/v3-recipe-source.d.ts +8 -0
  37. package/dist-client/modeller/v3-recipe-source.js +14 -0
  38. package/dist-client/modeller/v3-recipe-source.js.map +1 -0
  39. package/dist-client/modeller/v3-recipes.d.ts +25 -14
  40. package/dist-client/modeller/v3-recipes.js +42 -28
  41. package/dist-client/modeller/v3-recipes.js.map +1 -1
  42. package/dist-client/pages/figure-v3-modeller-page.d.ts +5 -3
  43. package/dist-client/pages/figure-v3-modeller-page.js +16 -13
  44. package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
  45. package/dist-client/route.d.ts +1 -1
  46. package/dist-client/route.js +0 -3
  47. package/dist-client/route.js.map +1 -1
  48. package/dist-client/tsconfig.fui.tsbuildinfo +1 -1
  49. package/dist-client/tsconfig.tsbuildinfo +1 -1
  50. package/package.json +5 -4
  51. package/scripts/template-thumbnails.mjs +53 -0
  52. package/test/browser/build.mjs +15 -3
  53. package/test/browser/modeller-page.js +26 -3
  54. package/test/browser/modeller.check.mjs +73 -46
  55. package/test/browser/serve.mjs +7 -1
  56. package/test/browser/thumbnail-page.js +14 -0
  57. package/test/v3-fields.test.ts +5 -1
  58. package/test/v3-links.test.ts +12 -0
  59. package/test/v3-recipes.test.ts +47 -25
  60. package/things-factory.config.js +0 -1
  61. package/translations/en.json +38 -23
  62. package/translations/ja.json +38 -23
  63. package/translations/ko.json +38 -23
  64. package/translations/ms.json +38 -23
  65. package/translations/zh.json +38 -23
  66. package/client/pages/figure-recipe-page.ts +0 -338
  67. package/dist-client/pages/figure-recipe-page.d.ts +0 -41
  68. package/dist-client/pages/figure-recipe-page.js +0 -352
  69. package/dist-client/pages/figure-recipe-page.js.map +0 -1
@@ -1,352 +0,0 @@
1
- import { __decorate, __metadata } from "tslib";
2
- import '@material/web/icon/icon.js';
3
- import '../modeller/figure-v3-preview.js';
4
- import { css, html, nothing } from 'lit';
5
- import { customElement, state } from 'lit/decorators.js';
6
- import { i18next, localize } from '@operato/i18n';
7
- import { PageView } from '@operato/shell';
8
- import { OxPrompt } from '@operato/popup';
9
- import { ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles';
10
- import { applyV3Recipe, parseV3Recipe, v3RecipeTestRoom } from '@hatiolab/figure-model';
11
- import { deleteFigureRecipe, fetchFigureRecipeRows, saveFigureRecipe, setFigureRecipeState } from '../graphql/index.js';
12
- import { paramRangeOf, recipeFieldOf, recipeIsOwn, recipesOf, recipeWord, subdomainOf } from '../modeller/v3-recipes.js';
13
- /**
14
- * The recipes (ADR-0095): what each one puts in, shown put into a room at several sizes, and whose it is.
15
- *
16
- * A recipe of this domain's own can be released, taken back to draft, changed or deleted. A parent's -- the ones the
17
- * system domain ships -- is copied in under its name to be changed, and the copy stands in for it. Figures a recipe was
18
- * put into keep their parts whatever happens here.
19
- */
20
- let FigureRecipePage = class FigureRecipePage extends localize(i18next)(PageView) {
21
- constructor() {
22
- super(...arguments);
23
- this.rows = [];
24
- this.chosen = '';
25
- this.error = '';
26
- this.editing = false;
27
- this.text = '';
28
- }
29
- static { this.styles = [
30
- WorkSurfacePageStyles,
31
- ScrollbarStyles,
32
- css `
33
- :host {
34
- display: grid;
35
- grid-template-columns: minmax(220px, 280px) 1fr;
36
- background-color: var(--md-sys-color-surface);
37
- color: var(--md-sys-color-on-surface);
38
- font-size: 0.85rem;
39
- }
40
- @container (max-width: 640px) {
41
- :host {
42
- grid-template-columns: 1fr;
43
- }
44
- }
45
- nav {
46
- overflow: auto;
47
- border-right: 1px solid var(--md-sys-color-outline-variant);
48
- padding: 8px;
49
- }
50
- nav p[intro] {
51
- margin: 4px 4px 10px;
52
- color: var(--md-sys-color-on-surface-variant);
53
- line-height: 1.5;
54
- }
55
- button[row] {
56
- display: grid;
57
- gap: 2px;
58
- width: 100%;
59
- padding: 8px 10px;
60
- text-align: left;
61
- font: inherit;
62
- color: inherit;
63
- background: none;
64
- border: none;
65
- border-radius: 8px;
66
- cursor: pointer;
67
- }
68
- button[row][on] {
69
- background: var(--md-sys-color-secondary-container);
70
- color: var(--md-sys-color-on-secondary-container);
71
- }
72
- button[row] small {
73
- display: flex;
74
- gap: 6px;
75
- color: var(--md-sys-color-on-surface-variant);
76
- }
77
- span[chip] {
78
- padding: 0 6px;
79
- border-radius: 999em;
80
- border: 1px solid var(--md-sys-color-outline-variant);
81
- font-size: 0.7rem;
82
- }
83
- span[chip][draft] {
84
- color: var(--md-sys-color-tertiary);
85
- border-color: var(--md-sys-color-tertiary);
86
- }
87
- main {
88
- overflow: auto;
89
- padding: 12px 16px;
90
- display: grid;
91
- gap: 12px;
92
- align-content: start;
93
- }
94
- main h2 {
95
- margin: 0;
96
- font-size: var(--ui-t-title, 16px);
97
- }
98
- main p {
99
- margin: 0;
100
- line-height: 1.5;
101
- }
102
- p[muted] {
103
- color: var(--md-sys-color-on-surface-variant);
104
- }
105
- table {
106
- border-collapse: collapse;
107
- width: 100%;
108
- max-width: 720px;
109
- }
110
- th,
111
- td {
112
- text-align: left;
113
- padding: 4px 8px;
114
- border-bottom: 1px solid var(--md-sys-color-outline-variant);
115
- }
116
- figure-v3-preview {
117
- height: 420px;
118
- border: 1px solid var(--md-sys-color-outline-variant);
119
- border-radius: 8px;
120
- overflow: hidden;
121
- }
122
- div[actions] {
123
- display: flex;
124
- flex-wrap: wrap;
125
- gap: 8px;
126
- }
127
- div[actions] button {
128
- display: inline-flex;
129
- align-items: center;
130
- gap: 4px;
131
- padding: 6px 12px;
132
- font: inherit;
133
- color: var(--md-sys-color-on-surface);
134
- background: none;
135
- border: 1px solid var(--md-sys-color-outline-variant);
136
- border-radius: 999em;
137
- cursor: pointer;
138
- }
139
- div[actions] button[primary] {
140
- color: var(--md-sys-color-on-primary);
141
- background: var(--md-sys-color-primary);
142
- border-color: transparent;
143
- }
144
- div[actions] button[danger] {
145
- color: var(--md-sys-color-error);
146
- border-color: var(--md-sys-color-error);
147
- }
148
- p[role='alert'] {
149
- padding: 8px;
150
- white-space: pre-wrap;
151
- color: var(--md-sys-color-on-error-container);
152
- background: var(--md-sys-color-error-container);
153
- border-radius: 8px;
154
- }
155
- textarea {
156
- width: 100%;
157
- min-height: 320px;
158
- font: 0.78rem/1.4 monospace;
159
- box-sizing: border-box;
160
- }
161
- `
162
- ]; }
163
- get context() {
164
- return { title: i18next.t('figure.title.recipes') };
165
- }
166
- async pageUpdated(_changes, _lifecycle, before) {
167
- if (this.active && !before?.active)
168
- await this.read();
169
- }
170
- async read() {
171
- try {
172
- this.rows = await fetchFigureRecipeRows();
173
- if (!this.rows.some(r => r.name === this.chosen))
174
- this.chosen = this.rows[0]?.name ?? '';
175
- this.error = '';
176
- }
177
- catch (error) {
178
- this.error = error instanceof Error ? error.message : String(error);
179
- }
180
- }
181
- get subdomain() {
182
- return subdomainOf(location.pathname);
183
- }
184
- render() {
185
- const language = i18next.language || 'en';
186
- const recipes = recipesOf(this.rows);
187
- const row = this.rows.find(r => r.name === this.chosen);
188
- const recipe = recipes.find(r => r.name === this.chosen);
189
- return html `
190
- <nav>
191
- <p intro>${i18next.t('figure.text.recipes-intro')}</p>
192
- ${this.rows.map(one => {
193
- const r = recipes.find(x => x.name === one.name);
194
- return html `<button row ?on=${one.name === this.chosen} @click=${() => this.choose(one.name)}>
195
- <span>${r ? recipeWord(r.title, language) : one.name}</span>
196
- <small>
197
- <span>${one.name}</span>
198
- ${one.state === 'draft' ? html `<span chip draft>${i18next.t('figure.label.recipe-draft')}</span>` : nothing}
199
- ${recipeIsOwn(one, this.subdomain) ? nothing : html `<span chip>${i18next.t('figure.label.recipe-inherited')}</span>`}
200
- </small>
201
- </button>`;
202
- })}
203
- </nav>
204
- <main>
205
- ${this.error ? html `<p role="alert">${this.error}</p>` : nothing}
206
- ${row && recipe ? this.renderRecipe(row, recipe, language) : html `<p muted>${i18next.t('figure.text.recipes-empty')}</p>`}
207
- </main>
208
- `;
209
- }
210
- renderRecipe(row, recipe, language) {
211
- const own = recipeIsOwn(row, this.subdomain);
212
- return html `
213
- <h2>${recipeWord(recipe.title, language)}</h2>
214
- <p>${recipeWord(recipe.description, language)}</p>
215
- <p muted>
216
- ${i18next.t(recipe.target === 'part' ? 'figure.text.recipe-target-part' : 'figure.text.recipe-target-figure')} ·
217
- ${i18next.t(own ? 'figure.text.recipe-own' : 'figure.text.recipe-inherited', { domain: row.domain?.subdomain ?? '' })}
218
- </p>
219
- <table>
220
- <thead>
221
- <tr>
222
- <th>${i18next.t('figure.label.recipe-param')}</th>
223
- <th>${i18next.t('figure.label.recipe-default')}</th>
224
- <th>${i18next.t('figure.label.recipe-range')}</th>
225
- </tr>
226
- </thead>
227
- <tbody>
228
- ${recipe.params.map(p => html `<tr>
229
- <td>${recipeWord(p.label, language) || p.name}</td>
230
- <td>${p.kind === 'flag' ? i18next.t(p.default ? 'figure.label.yes' : 'figure.label.no') : recipeFieldOf(p, p.default)}${p.kind === 'ratio' ? ' %' : ''}</td>
231
- <td>${paramRangeOf(p)}</td>
232
- </tr>`)}
233
- </tbody>
234
- </table>
235
- ${this.renderPreview(recipe)}
236
- <div actions>
237
- ${own
238
- ? html `${row.state === 'draft'
239
- ? html `<button primary @click=${() => this.setState(row.name, 'released')}>${i18next.t('figure.button.recipe-release')}</button>`
240
- : html `<button @click=${() => this.setState(row.name, 'draft')}>${i18next.t('figure.button.recipe-to-draft')}</button>`}
241
- <button danger @click=${() => this.deleteRecipe(row.name)}><md-icon>delete</md-icon>${i18next.t('figure.button.recipe-delete')}</button>`
242
- : html `<button primary @click=${() => this.copyIn(row)}>${i18next.t('figure.button.recipe-copy-in')}</button>`}
243
- <button @click=${() => this.edit(row)}><md-icon>data_object</md-icon>${i18next.t('figure.button.recipe-edit-source')}</button>
244
- </div>
245
- ${this.editing
246
- ? html `<p muted>${i18next.t('figure.text.recipe-edit-source')}</p>
247
- <textarea .value=${this.text} @input=${(e) => (this.text = e.target.value)}></textarea>
248
- <div actions>
249
- <button primary @click=${() => this.saveText(own ? row.state : 'draft')}>${i18next.t('figure.button.save')}</button>
250
- <button @click=${() => (this.editing = false)}>${i18next.t('figure.button.close')}</button>
251
- </div>`
252
- : nothing}
253
- `;
254
- }
255
- /** The recipe put into the room it is checked in, with its defaults: what it makes, at several sizes. */
256
- renderPreview(recipe) {
257
- let asset;
258
- try {
259
- const room = v3RecipeTestRoom();
260
- asset = applyV3Recipe(room.asset, parseV3Recipe(recipe), recipe.target === 'part' ? { target: room.target } : {}).asset;
261
- }
262
- catch (error) {
263
- return html `<p role="alert">${error instanceof Error ? error.message : String(error)}</p>`;
264
- }
265
- return html `<p muted>${i18next.t('figure.text.recipe-preview')}</p>
266
- <figure-v3-preview .asset=${asset} .context=${`recipe:${recipe.name}`}></figure-v3-preview>`;
267
- }
268
- choose(name) {
269
- this.chosen = name;
270
- this.editing = false;
271
- this.error = '';
272
- }
273
- edit(row) {
274
- this.editing = !this.editing;
275
- try {
276
- this.text = JSON.stringify(JSON.parse(row.source), null, 2);
277
- }
278
- catch {
279
- this.text = row.source;
280
- }
281
- }
282
- async run(action) {
283
- try {
284
- await action();
285
- this.error = '';
286
- await this.read();
287
- }
288
- catch (error) {
289
- this.error = error instanceof Error ? error.message : String(error);
290
- }
291
- }
292
- setState(name, state) {
293
- return this.run(() => setFigureRecipeState(name, state));
294
- }
295
- /* The app's own prompt, as every operato screen asks -- not the browser's confirm box (user, 2026-09-24). */
296
- async deleteRecipe(name) {
297
- const sure = await OxPrompt.open({
298
- type: 'warning',
299
- title: i18next.t('figure.button.recipe-delete'),
300
- text: i18next.t('figure.text.recipe-delete-confirm', { name }),
301
- confirmButton: { text: i18next.t('figure.button.recipe-delete') },
302
- cancelButton: { text: i18next.t('figure.button.close') }
303
- });
304
- if (sure)
305
- await this.run(() => deleteFigureRecipe(name));
306
- }
307
- /** A parent's recipe copied into this domain as a draft, to be changed here. */
308
- copyIn(row) {
309
- return this.run(() => saveFigureRecipe(row.source, 'draft'));
310
- }
311
- /** The source as typed: checked by the server in the test room; a new name is a new recipe. */
312
- async saveText(state) {
313
- let name = '';
314
- try {
315
- name = JSON.parse(this.text).name ?? '';
316
- }
317
- catch (error) {
318
- this.error = error instanceof Error ? error.message : String(error);
319
- return;
320
- }
321
- await this.run(async () => {
322
- await saveFigureRecipe(this.text, state);
323
- this.chosen = name;
324
- this.editing = false;
325
- });
326
- }
327
- };
328
- __decorate([
329
- state(),
330
- __metadata("design:type", Array)
331
- ], FigureRecipePage.prototype, "rows", void 0);
332
- __decorate([
333
- state(),
334
- __metadata("design:type", Object)
335
- ], FigureRecipePage.prototype, "chosen", void 0);
336
- __decorate([
337
- state(),
338
- __metadata("design:type", Object)
339
- ], FigureRecipePage.prototype, "error", void 0);
340
- __decorate([
341
- state(),
342
- __metadata("design:type", Object)
343
- ], FigureRecipePage.prototype, "editing", void 0);
344
- __decorate([
345
- state(),
346
- __metadata("design:type", Object)
347
- ], FigureRecipePage.prototype, "text", void 0);
348
- FigureRecipePage = __decorate([
349
- customElement('figure-recipe-page')
350
- ], FigureRecipePage);
351
- export { FigureRecipePage };
352
- //# sourceMappingURL=figure-recipe-page.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"figure-recipe-page.js","sourceRoot":"","sources":["../../client/pages/figure-recipe-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '../modeller/figure-v3-preview.js'\n\nimport { css, html, nothing } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { PageView } from '@operato/shell'\nimport { OxPrompt } from '@operato/popup'\nimport { ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { applyV3Recipe, parseV3Recipe, v3RecipeTestRoom } from '@hatiolab/figure-model'\nimport type { V3Asset, V3Recipe } from '@hatiolab/figure-model'\n\nimport { deleteFigureRecipe, fetchFigureRecipeRows, saveFigureRecipe, setFigureRecipeState, type FigureRecipeRow } from '../graphql/index.js'\nimport { paramRangeOf, recipeFieldOf, recipeIsOwn, recipesOf, recipeWord, subdomainOf } from '../modeller/v3-recipes.js'\n\n/**\n * The recipes (ADR-0095): what each one puts in, shown put into a room at several sizes, and whose it is.\n *\n * A recipe of this domain's own can be released, taken back to draft, changed or deleted. A parent's -- the ones the\n * system domain ships -- is copied in under its name to be changed, and the copy stands in for it. Figures a recipe was\n * put into keep their parts whatever happens here.\n */\n@customElement('figure-recipe-page')\nexport class FigureRecipePage extends localize(i18next)(PageView) {\n static styles = [\n WorkSurfacePageStyles,\n ScrollbarStyles,\n css`\n :host {\n display: grid;\n grid-template-columns: minmax(220px, 280px) 1fr;\n background-color: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n font-size: 0.85rem;\n }\n @container (max-width: 640px) {\n :host {\n grid-template-columns: 1fr;\n }\n }\n nav {\n overflow: auto;\n border-right: 1px solid var(--md-sys-color-outline-variant);\n padding: 8px;\n }\n nav p[intro] {\n margin: 4px 4px 10px;\n color: var(--md-sys-color-on-surface-variant);\n line-height: 1.5;\n }\n button[row] {\n display: grid;\n gap: 2px;\n width: 100%;\n padding: 8px 10px;\n text-align: left;\n font: inherit;\n color: inherit;\n background: none;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n }\n button[row][on] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n }\n button[row] small {\n display: flex;\n gap: 6px;\n color: var(--md-sys-color-on-surface-variant);\n }\n span[chip] {\n padding: 0 6px;\n border-radius: 999em;\n border: 1px solid var(--md-sys-color-outline-variant);\n font-size: 0.7rem;\n }\n span[chip][draft] {\n color: var(--md-sys-color-tertiary);\n border-color: var(--md-sys-color-tertiary);\n }\n main {\n overflow: auto;\n padding: 12px 16px;\n display: grid;\n gap: 12px;\n align-content: start;\n }\n main h2 {\n margin: 0;\n font-size: var(--ui-t-title, 16px);\n }\n main p {\n margin: 0;\n line-height: 1.5;\n }\n p[muted] {\n color: var(--md-sys-color-on-surface-variant);\n }\n table {\n border-collapse: collapse;\n width: 100%;\n max-width: 720px;\n }\n th,\n td {\n text-align: left;\n padding: 4px 8px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n figure-v3-preview {\n height: 420px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 8px;\n overflow: hidden;\n }\n div[actions] {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n }\n div[actions] button {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 6px 12px;\n font: inherit;\n color: var(--md-sys-color-on-surface);\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 999em;\n cursor: pointer;\n }\n div[actions] button[primary] {\n color: var(--md-sys-color-on-primary);\n background: var(--md-sys-color-primary);\n border-color: transparent;\n }\n div[actions] button[danger] {\n color: var(--md-sys-color-error);\n border-color: var(--md-sys-color-error);\n }\n p[role='alert'] {\n padding: 8px;\n white-space: pre-wrap;\n color: var(--md-sys-color-on-error-container);\n background: var(--md-sys-color-error-container);\n border-radius: 8px;\n }\n textarea {\n width: 100%;\n min-height: 320px;\n font: 0.78rem/1.4 monospace;\n box-sizing: border-box;\n }\n `\n ]\n\n @state() private rows: FigureRecipeRow[] = []\n @state() private chosen = ''\n @state() private error = ''\n @state() private editing = false\n @state() private text = ''\n\n get context() {\n return { title: i18next.t('figure.title.recipes') }\n }\n\n async pageUpdated(_changes: Record<string, unknown>, _lifecycle: unknown, before: { active?: boolean }) {\n if (this.active && !before?.active) await this.read()\n }\n\n private async read() {\n try {\n this.rows = await fetchFigureRecipeRows()\n if (!this.rows.some(r => r.name === this.chosen)) this.chosen = this.rows[0]?.name ?? ''\n this.error = ''\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n }\n }\n\n private get subdomain() {\n return subdomainOf(location.pathname)\n }\n\n render() {\n const language = i18next.language || 'en'\n const recipes = recipesOf(this.rows)\n const row = this.rows.find(r => r.name === this.chosen)\n const recipe = recipes.find(r => r.name === this.chosen)\n return html`\n <nav>\n <p intro>${i18next.t('figure.text.recipes-intro')}</p>\n ${this.rows.map(one => {\n const r = recipes.find(x => x.name === one.name)\n return html`<button row ?on=${one.name === this.chosen} @click=${() => this.choose(one.name)}>\n <span>${r ? recipeWord(r.title, language) : one.name}</span>\n <small>\n <span>${one.name}</span>\n ${one.state === 'draft' ? html`<span chip draft>${i18next.t('figure.label.recipe-draft')}</span>` : nothing}\n ${recipeIsOwn(one, this.subdomain) ? nothing : html`<span chip>${i18next.t('figure.label.recipe-inherited')}</span>`}\n </small>\n </button>`\n })}\n </nav>\n <main>\n ${this.error ? html`<p role=\"alert\">${this.error}</p>` : nothing}\n ${row && recipe ? this.renderRecipe(row, recipe, language) : html`<p muted>${i18next.t('figure.text.recipes-empty')}</p>`}\n </main>\n `\n }\n\n private renderRecipe(row: FigureRecipeRow, recipe: V3Recipe, language: string) {\n const own = recipeIsOwn(row, this.subdomain)\n return html`\n <h2>${recipeWord(recipe.title, language)}</h2>\n <p>${recipeWord(recipe.description, language)}</p>\n <p muted>\n ${i18next.t(recipe.target === 'part' ? 'figure.text.recipe-target-part' : 'figure.text.recipe-target-figure')} ·\n ${i18next.t(own ? 'figure.text.recipe-own' : 'figure.text.recipe-inherited', { domain: row.domain?.subdomain ?? '' })}\n </p>\n <table>\n <thead>\n <tr>\n <th>${i18next.t('figure.label.recipe-param')}</th>\n <th>${i18next.t('figure.label.recipe-default')}</th>\n <th>${i18next.t('figure.label.recipe-range')}</th>\n </tr>\n </thead>\n <tbody>\n ${recipe.params.map(\n p => html`<tr>\n <td>${recipeWord(p.label, language) || p.name}</td>\n <td>${p.kind === 'flag' ? i18next.t(p.default ? 'figure.label.yes' : 'figure.label.no') : recipeFieldOf(p, p.default)}${p.kind === 'ratio' ? ' %' : ''}</td>\n <td>${paramRangeOf(p)}</td>\n </tr>`\n )}\n </tbody>\n </table>\n ${this.renderPreview(recipe)}\n <div actions>\n ${own\n ? html`${row.state === 'draft'\n ? html`<button primary @click=${() => this.setState(row.name, 'released')}>${i18next.t('figure.button.recipe-release')}</button>`\n : html`<button @click=${() => this.setState(row.name, 'draft')}>${i18next.t('figure.button.recipe-to-draft')}</button>`}\n <button danger @click=${() => this.deleteRecipe(row.name)}><md-icon>delete</md-icon>${i18next.t('figure.button.recipe-delete')}</button>`\n : html`<button primary @click=${() => this.copyIn(row)}>${i18next.t('figure.button.recipe-copy-in')}</button>`}\n <button @click=${() => this.edit(row)}><md-icon>data_object</md-icon>${i18next.t('figure.button.recipe-edit-source')}</button>\n </div>\n ${this.editing\n ? html`<p muted>${i18next.t('figure.text.recipe-edit-source')}</p>\n <textarea .value=${this.text} @input=${(e: Event) => (this.text = (e.target as HTMLTextAreaElement).value)}></textarea>\n <div actions>\n <button primary @click=${() => this.saveText(own ? row.state : 'draft')}>${i18next.t('figure.button.save')}</button>\n <button @click=${() => (this.editing = false)}>${i18next.t('figure.button.close')}</button>\n </div>`\n : nothing}\n `\n }\n\n /** The recipe put into the room it is checked in, with its defaults: what it makes, at several sizes. */\n private renderPreview(recipe: V3Recipe) {\n let asset: V3Asset\n try {\n const room = v3RecipeTestRoom()\n asset = applyV3Recipe(room.asset, parseV3Recipe(recipe), recipe.target === 'part' ? { target: room.target } : {}).asset\n } catch (error) {\n return html`<p role=\"alert\">${error instanceof Error ? error.message : String(error)}</p>`\n }\n return html`<p muted>${i18next.t('figure.text.recipe-preview')}</p>\n <figure-v3-preview .asset=${asset} .context=${`recipe:${recipe.name}`}></figure-v3-preview>`\n }\n\n private choose(name: string) {\n this.chosen = name\n this.editing = false\n this.error = ''\n }\n\n private edit(row: FigureRecipeRow) {\n this.editing = !this.editing\n try {\n this.text = JSON.stringify(JSON.parse(row.source), null, 2)\n } catch {\n this.text = row.source\n }\n }\n\n private async run(action: () => Promise<unknown>) {\n try {\n await action()\n this.error = ''\n await this.read()\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n }\n }\n\n private setState(name: string, state: 'draft' | 'released') {\n return this.run(() => setFigureRecipeState(name, state))\n }\n\n /* The app's own prompt, as every operato screen asks -- not the browser's confirm box (user, 2026-09-24). */\n private async deleteRecipe(name: string) {\n const sure = await OxPrompt.open({\n type: 'warning',\n title: i18next.t('figure.button.recipe-delete'),\n text: i18next.t('figure.text.recipe-delete-confirm', { name }),\n confirmButton: { text: i18next.t('figure.button.recipe-delete') },\n cancelButton: { text: i18next.t('figure.button.close') }\n })\n if (sure) await this.run(() => deleteFigureRecipe(name))\n }\n\n /** A parent's recipe copied into this domain as a draft, to be changed here. */\n private copyIn(row: FigureRecipeRow) {\n return this.run(() => saveFigureRecipe(row.source, 'draft'))\n }\n\n /** The source as typed: checked by the server in the test room; a new name is a new recipe. */\n private async saveText(state: 'draft' | 'released') {\n let name = ''\n try {\n name = JSON.parse(this.text).name ?? ''\n } catch (error) {\n this.error = error instanceof Error ? error.message : String(error)\n return\n }\n await this.run(async () => {\n await saveFigureRecipe(this.text, state)\n this.chosen = name\n this.editing = false\n })\n }\n}\n"]}