@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.
- package/client/graphql/index.ts +28 -69
- package/client/modeller/figure-v3-parts.ts +16 -136
- package/client/modeller/figure-v3-recipe-catalog.ts +366 -0
- package/client/modeller/v3-editor.ts +25 -5
- package/client/modeller/v3-fields.ts +21 -16
- package/client/modeller/v3-gizmo.ts +1 -1
- package/client/modeller/v3-links.ts +7 -66
- package/client/modeller/v3-palette.ts +3 -0
- package/client/modeller/v3-recipe-source.ts +15 -0
- package/client/modeller/v3-recipes.ts +50 -27
- package/client/pages/figure-v3-modeller-page.ts +16 -13
- package/client/route.ts +0 -4
- package/dist-client/graphql/index.d.ts +11 -18
- package/dist-client/graphql/index.js +18 -59
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-v3-parts.d.ts +5 -12
- package/dist-client/modeller/figure-v3-parts.js +17 -147
- package/dist-client/modeller/figure-v3-parts.js.map +1 -1
- package/dist-client/modeller/figure-v3-recipe-catalog.d.ts +38 -0
- package/dist-client/modeller/figure-v3-recipe-catalog.js +399 -0
- package/dist-client/modeller/figure-v3-recipe-catalog.js.map +1 -0
- package/dist-client/modeller/v3-clips.d.ts +1 -1
- package/dist-client/modeller/v3-editor.d.ts +8 -3
- package/dist-client/modeller/v3-editor.js +35 -6
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-fields.d.ts +2 -2
- package/dist-client/modeller/v3-fields.js +20 -18
- package/dist-client/modeller/v3-fields.js.map +1 -1
- package/dist-client/modeller/v3-gizmo.js +1 -1
- package/dist-client/modeller/v3-gizmo.js.map +1 -1
- package/dist-client/modeller/v3-links.d.ts +8 -25
- package/dist-client/modeller/v3-links.js +4 -66
- package/dist-client/modeller/v3-links.js.map +1 -1
- package/dist-client/modeller/v3-palette.js +3 -0
- package/dist-client/modeller/v3-palette.js.map +1 -1
- package/dist-client/modeller/v3-recipe-source.d.ts +8 -0
- package/dist-client/modeller/v3-recipe-source.js +14 -0
- package/dist-client/modeller/v3-recipe-source.js.map +1 -0
- package/dist-client/modeller/v3-recipes.d.ts +25 -14
- package/dist-client/modeller/v3-recipes.js +42 -28
- package/dist-client/modeller/v3-recipes.js.map +1 -1
- package/dist-client/pages/figure-v3-modeller-page.d.ts +5 -3
- package/dist-client/pages/figure-v3-modeller-page.js +16 -13
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +0 -3
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.fui.tsbuildinfo +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/scripts/template-thumbnails.mjs +53 -0
- package/test/browser/build.mjs +15 -3
- package/test/browser/modeller-page.js +26 -3
- package/test/browser/modeller.check.mjs +73 -46
- package/test/browser/serve.mjs +7 -1
- package/test/browser/thumbnail-page.js +14 -0
- package/test/v3-fields.test.ts +5 -1
- package/test/v3-links.test.ts +12 -0
- package/test/v3-recipes.test.ts +47 -25
- package/things-factory.config.js +0 -1
- package/translations/en.json +38 -23
- package/translations/ja.json +38 -23
- package/translations/ko.json +38 -23
- package/translations/ms.json +38 -23
- package/translations/zh.json +38 -23
- package/client/pages/figure-recipe-page.ts +0 -338
- package/dist-client/pages/figure-recipe-page.d.ts +0 -41
- package/dist-client/pages/figure-recipe-page.js +0 -352
- 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"]}
|