@things-factory/figure-ui 10.1.77 → 10.1.78

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.
@@ -2,6 +2,8 @@ import { LitElement, html, css, nothing, type PropertyValues } from 'lit'
2
2
  import { i18next } from '@operato/i18n'
3
3
  import type { OxThreePane } from '@operato/layout'
4
4
  import '@operato/layout'
5
+ import { notify } from '@operato/layout'
6
+ import { OxPrompt } from '@operato/popup'
5
7
  import { customElement, property, state } from 'lit/decorators.js'
6
8
  import { live } from 'lit/directives/live.js'
7
9
  import { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figureRecipeIdFor } from '@hatiolab/figure-model'
@@ -247,17 +249,6 @@ export class FigureEditor extends LitElement {
247
249
  color: var(--md-sys-color-on-secondary-container);
248
250
  border-color: var(--md-sys-color-primary);
249
251
  }
250
- .error {
251
- padding: 10px;
252
- background: var(--md-sys-color-error-container);
253
- color: var(--md-sys-color-on-error-container);
254
- white-space: pre-wrap;
255
- }
256
- .notice {
257
- padding: 8px 10px;
258
- background: var(--md-sys-color-surface-container);
259
- color: var(--md-sys-color-on-surface);
260
- }
261
252
  .hint {
262
253
  color: var(--md-sys-color-on-surface-variant);
263
254
  line-height: 1.5;
@@ -386,6 +377,35 @@ export class FigureEditor extends LitElement {
386
377
  window.removeEventListener('keydown', this.onKey, true)
387
378
  super.disconnectedCallback()
388
379
  }
380
+ /**
381
+ * Refusals and notices go to the app's shared surfaces, as every screen's do (user, 2026-09-27: 「왜 모두가 사용하는
382
+ * notification 오류 표현 방법을 사용하지 않고」 · 「혹은 oxprompt 에서 확인하고 진행하던지」). A refusal with a way on --
383
+ * delete and fit the space again, narrow the range -- asks in the app's prompt and goes on when confirmed; any
384
+ * other refusal is the app's notification, which stays until it is closed.
385
+ */
386
+ private toldViewError = ''
387
+ private tell(changed: PropertyValues) {
388
+ if (changed.has('error') && this.error) {
389
+ const retry = this.retry
390
+ if (retry) this.ask(this.error, retry)
391
+ else notify({ level: 'error', message: this.error })
392
+ }
393
+ if (changed.has('notice') && this.notice) notify({ level: 'info', message: this.notice })
394
+ const viewError = this.session?.read()?.error ?? ''
395
+ if (viewError && viewError !== this.toldViewError) notify({ level: 'error', message: viewError })
396
+ this.toldViewError = viewError
397
+ }
398
+ private async ask(message: string, retry: { label: string; action?: FigureAuthoringAction; run?: () => void }) {
399
+ const agreed = await OxPrompt.open({
400
+ type: 'question',
401
+ text: message,
402
+ confirmButton: { text: retry.label },
403
+ cancelButton: { text: i18next.t('figure.button.keep-editing') }
404
+ })
405
+ if (!agreed) return
406
+ if (retry.run) retry.run()
407
+ else this.linkCommand(retry.action!)
408
+ }
389
409
  protected willUpdate(_changed: PropertyValues) {
390
410
  const view = this.session?.read()
391
411
  if (view?.context !== this.lastContext) {
@@ -524,10 +544,17 @@ export class FigureEditor extends LitElement {
524
544
  if (this.selected === id) this.selected = ''
525
545
  this.changed()
526
546
  } catch (error) {
527
- /* The refusal a person meets most: another part stands on this one or follows its size. Said in their words. */
547
+ /*
548
+ A part in the declared space goes, and the space is fitted to the parts left, in the same click: every space
549
+ this product declares is fitted to the parts (「부품에 맞추기」, the AI's fit-room -- no padding), so fitting it
550
+ again keeps what it says, and there is nothing to ask (user, 2026-09-27: 「이 확인이 꼭 필요한가 ?」).
551
+ */
552
+ if (!refitRoom && inDeclaredRoom(error)) {
553
+ this.removePart(id, true)
554
+ if (!this.error) this.notice = [this.notice, i18next.t('figure.text.removal-space-refitted')].filter(Boolean).join(' ')
555
+ return
556
+ }
528
557
  this.error = partRefusalOf(error, id, (key, params) => i18next.t(key, params)) ?? this.said(error)
529
- /* In the declared room only: the way on is to delete it and fit the room to the parts left, in one step. */
530
- if (!refitRoom && inDeclaredRoom(error)) this.retry = { label: i18next.t('figure.button.remove-and-refit-room', { part: id }), run: () => this.removePart(id, true) }
531
558
  }
532
559
  }
533
560
  private toggleHiddenPart(id: string) {
@@ -1186,6 +1213,7 @@ export class FigureEditor extends LitElement {
1186
1213
  }
1187
1214
  updated(changed: PropertyValues<this>) {
1188
1215
  super.updated?.(changed)
1216
+ this.tell(changed)
1189
1217
  const panes = this.renderRoot.querySelector<OxThreePane>('ox-three-pane') ?? undefined
1190
1218
  if (panes !== this.panes) this.panes = panes
1191
1219
  }
@@ -1238,15 +1266,6 @@ export class FigureEditor extends LitElement {
1238
1266
  <span slot="status" class="status" role="status" ?dirty=${status === 'figure.text.unsaved-changes'}>${i18next.t(status)}</span>
1239
1267
  <button slot="commit" save ?disabled=${unavailable || (!view.dirty && !!view.id)} @click=${this.save}>${i18next.t('figure.button.save')}</button>
1240
1268
  </ox-canvas-bar>
1241
- ${this.error || view.error
1242
- ? html`<div role="alert" class="error">
1243
- ${this.error || view.error}
1244
- ${this.retry && this.error
1245
- ? html`<button act @click=${() => (this.retry!.run ? this.retry!.run() : this.linkCommand(this.retry!.action!))}><md-icon>straighten</md-icon>${this.retry.label}</button>`
1246
- : null}
1247
- </div>`
1248
- : null}
1249
- ${this.notice ? html`<div role="status" class="notice">${this.notice}</div>` : null}
1250
1269
  ${this.catalogOpen
1251
1270
  ? html`<figure-recipe-catalog
1252
1271
  .templates=${this.templates}
@@ -76,6 +76,15 @@ export declare class FigureEditor extends LitElement {
76
76
  private onKey;
77
77
  connectedCallback(): void;
78
78
  disconnectedCallback(): void;
79
+ /**
80
+ * Refusals and notices go to the app's shared surfaces, as every screen's do (user, 2026-09-27: 「왜 모두가 사용하는
81
+ * notification 오류 표현 방법을 사용하지 않고」 · 「혹은 oxprompt 에서 확인하고 진행하던지」). A refusal with a way on --
82
+ * delete and fit the space again, narrow the range -- asks in the app's prompt and goes on when confirmed; any
83
+ * other refusal is the app's notification, which stays until it is closed.
84
+ */
85
+ private toldViewError;
86
+ private tell;
87
+ private ask;
79
88
  protected willUpdate(_changed: PropertyValues): void;
80
89
  /** The scene toggles: the mode already shown takes the gizmo away. */
81
90
  private useGizmo;
@@ -3,6 +3,8 @@ import { __decorate, __metadata } from "tslib";
3
3
  import { LitElement, html, css, nothing } from 'lit';
4
4
  import { i18next } from '@operato/i18n';
5
5
  import '@operato/layout';
6
+ import { notify } from '@operato/layout';
7
+ import { OxPrompt } from '@operato/popup';
6
8
  import { customElement, property, state } from 'lit/decorators.js';
7
9
  import { live } from 'lit/directives/live.js';
8
10
  import { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figureRecipeIdFor } from '@hatiolab/figure-model';
@@ -92,6 +94,13 @@ let FigureEditor = class FigureEditor extends LitElement {
92
94
  else
93
95
  this.selected = '';
94
96
  };
97
+ /**
98
+ * Refusals and notices go to the app's shared surfaces, as every screen's do (user, 2026-09-27: 「왜 모두가 사용하는
99
+ * notification 오류 표현 방법을 사용하지 않고」 · 「혹은 oxprompt 에서 확인하고 진행하던지」). A refusal with a way on --
100
+ * delete and fit the space again, narrow the range -- asks in the app's prompt and goes on when confirmed; any
101
+ * other refusal is the app's notification, which stays until it is closed.
102
+ */
103
+ this.toldViewError = '';
95
104
  }
96
105
  static { FigureEditor_1 = this; }
97
106
  /* The operato scrollbar, as every scrolling panel of the app has it -- the side panel scrolled with the browser's own. */
@@ -291,17 +300,6 @@ let FigureEditor = class FigureEditor extends LitElement {
291
300
  color: var(--md-sys-color-on-secondary-container);
292
301
  border-color: var(--md-sys-color-primary);
293
302
  }
294
- .error {
295
- padding: 10px;
296
- background: var(--md-sys-color-error-container);
297
- color: var(--md-sys-color-on-error-container);
298
- white-space: pre-wrap;
299
- }
300
- .notice {
301
- padding: 8px 10px;
302
- background: var(--md-sys-color-surface-container);
303
- color: var(--md-sys-color-on-surface);
304
- }
305
303
  .hint {
306
304
  color: var(--md-sys-color-on-surface-variant);
307
305
  line-height: 1.5;
@@ -358,6 +356,35 @@ let FigureEditor = class FigureEditor extends LitElement {
358
356
  window.removeEventListener('keydown', this.onKey, true);
359
357
  super.disconnectedCallback();
360
358
  }
359
+ tell(changed) {
360
+ if (changed.has('error') && this.error) {
361
+ const retry = this.retry;
362
+ if (retry)
363
+ this.ask(this.error, retry);
364
+ else
365
+ notify({ level: 'error', message: this.error });
366
+ }
367
+ if (changed.has('notice') && this.notice)
368
+ notify({ level: 'info', message: this.notice });
369
+ const viewError = this.session?.read()?.error ?? '';
370
+ if (viewError && viewError !== this.toldViewError)
371
+ notify({ level: 'error', message: viewError });
372
+ this.toldViewError = viewError;
373
+ }
374
+ async ask(message, retry) {
375
+ const agreed = await OxPrompt.open({
376
+ type: 'question',
377
+ text: message,
378
+ confirmButton: { text: retry.label },
379
+ cancelButton: { text: i18next.t('figure.button.keep-editing') }
380
+ });
381
+ if (!agreed)
382
+ return;
383
+ if (retry.run)
384
+ retry.run();
385
+ else
386
+ this.linkCommand(retry.action);
387
+ }
361
388
  willUpdate(_changed) {
362
389
  const view = this.session?.read();
363
390
  if (view?.context !== this.lastContext) {
@@ -510,11 +537,18 @@ let FigureEditor = class FigureEditor extends LitElement {
510
537
  this.changed();
511
538
  }
512
539
  catch (error) {
513
- /* The refusal a person meets most: another part stands on this one or follows its size. Said in their words. */
540
+ /*
541
+ A part in the declared space goes, and the space is fitted to the parts left, in the same click: every space
542
+ this product declares is fitted to the parts (「부품에 맞추기」, the AI's fit-room -- no padding), so fitting it
543
+ again keeps what it says, and there is nothing to ask (user, 2026-09-27: 「이 확인이 꼭 필요한가 ?」).
544
+ */
545
+ if (!refitRoom && inDeclaredRoom(error)) {
546
+ this.removePart(id, true);
547
+ if (!this.error)
548
+ this.notice = [this.notice, i18next.t('figure.text.removal-space-refitted')].filter(Boolean).join(' ');
549
+ return;
550
+ }
514
551
  this.error = partRefusalOf(error, id, (key, params) => i18next.t(key, params)) ?? this.said(error);
515
- /* In the declared room only: the way on is to delete it and fit the room to the parts left, in one step. */
516
- if (!refitRoom && inDeclaredRoom(error))
517
- this.retry = { label: i18next.t('figure.button.remove-and-refit-room', { part: id }), run: () => this.removePart(id, true) };
518
552
  }
519
553
  }
520
554
  toggleHiddenPart(id) {
@@ -1198,6 +1232,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1198
1232
  }
1199
1233
  updated(changed) {
1200
1234
  super.updated?.(changed);
1235
+ this.tell(changed);
1201
1236
  const panes = this.renderRoot.querySelector('ox-three-pane') ?? undefined;
1202
1237
  if (panes !== this.panes)
1203
1238
  this.panes = panes;
@@ -1251,15 +1286,6 @@ let FigureEditor = class FigureEditor extends LitElement {
1251
1286
  <span slot="status" class="status" role="status" ?dirty=${status === 'figure.text.unsaved-changes'}>${i18next.t(status)}</span>
1252
1287
  <button slot="commit" save ?disabled=${unavailable || (!view.dirty && !!view.id)} @click=${this.save}>${i18next.t('figure.button.save')}</button>
1253
1288
  </ox-canvas-bar>
1254
- ${this.error || view.error
1255
- ? html `<div role="alert" class="error">
1256
- ${this.error || view.error}
1257
- ${this.retry && this.error
1258
- ? html `<button act @click=${() => (this.retry.run ? this.retry.run() : this.linkCommand(this.retry.action))}><md-icon>straighten</md-icon>${this.retry.label}</button>`
1259
- : null}
1260
- </div>`
1261
- : null}
1262
- ${this.notice ? html `<div role="status" class="notice">${this.notice}</div>` : null}
1263
1289
  ${this.catalogOpen
1264
1290
  ? html `<figure-recipe-catalog
1265
1291
  .templates=${this.templates}