underpost 2.85.1 → 2.89.0

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 (53) hide show
  1. package/.env.development +2 -1
  2. package/.env.production +2 -1
  3. package/.env.test +2 -1
  4. package/.github/workflows/release.cd.yml +3 -3
  5. package/.vscode/zed.keymap.json +22 -0
  6. package/README.md +3 -3
  7. package/bin/build.js +8 -10
  8. package/bin/deploy.js +4 -2
  9. package/bin/file.js +4 -0
  10. package/bin/vs.js +4 -4
  11. package/cli.md +16 -11
  12. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  13. package/manifests/deployment/dd-test-development/deployment.yaml +50 -50
  14. package/manifests/deployment/dd-test-development/proxy.yaml +4 -4
  15. package/package.json +2 -2
  16. package/src/api/file/file.service.js +29 -3
  17. package/src/cli/baremetal.js +4 -5
  18. package/src/cli/deploy.js +26 -4
  19. package/src/cli/index.js +8 -3
  20. package/src/cli/repository.js +42 -45
  21. package/src/cli/run.js +217 -48
  22. package/src/client/components/core/AgGrid.js +42 -3
  23. package/src/client/components/core/CommonJs.js +5 -0
  24. package/src/client/components/core/Css.js +95 -48
  25. package/src/client/components/core/CssCore.js +0 -1
  26. package/src/client/components/core/LoadingAnimation.js +2 -2
  27. package/src/client/components/core/Logger.js +2 -9
  28. package/src/client/components/core/Modal.js +22 -14
  29. package/src/client/components/core/ObjectLayerEngine.js +300 -9
  30. package/src/client/components/core/ObjectLayerEngineModal.js +686 -148
  31. package/src/client/components/core/ObjectLayerEngineViewer.js +1061 -0
  32. package/src/client/components/core/Pagination.js +15 -5
  33. package/src/client/components/core/Router.js +5 -1
  34. package/src/client/components/core/SocketIo.js +5 -1
  35. package/src/client/components/core/Translate.js +4 -0
  36. package/src/client/components/core/Worker.js +8 -1
  37. package/src/client/services/default/default.management.js +86 -16
  38. package/src/client/sw/default.sw.js +193 -97
  39. package/src/client.dev.js +1 -1
  40. package/src/db/mariadb/MariaDB.js +2 -2
  41. package/src/index.js +1 -1
  42. package/src/proxy.js +1 -1
  43. package/src/runtime/express/Express.js +4 -1
  44. package/src/server/auth.js +2 -1
  45. package/src/server/client-build.js +57 -2
  46. package/src/server/conf.js +132 -15
  47. package/src/server/object-layer.js +44 -0
  48. package/src/server/proxy.js +53 -26
  49. package/src/server/start.js +25 -3
  50. package/src/server/tls.js +1 -1
  51. package/src/ws/IoInterface.js +2 -3
  52. package/AUTHORS.md +0 -21
  53. package/src/server/network.js +0 -72
@@ -1,5 +1,6 @@
1
1
  import { darkTheme, renderChessPattern } from './Css.js';
2
2
  import { append, htmls } from './VanillaJs.js';
3
+ import { NotificationManager } from './NotificationManager.js';
3
4
 
4
5
  class ObjectLayerEngineElement extends HTMLElement {
5
6
  constructor() {
@@ -11,7 +12,13 @@ class ObjectLayerEngineElement extends HTMLElement {
11
12
  --border: 1px solid #bbb;
12
13
  --gap: 8px;
13
14
  display: inline-block;
14
- font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
15
+ font-family:
16
+ system-ui,
17
+ -apple-system,
18
+ 'Segoe UI',
19
+ Roboto,
20
+ 'Helvetica Neue',
21
+ Arial;
15
22
  }
16
23
  .wrap {
17
24
  display: flex;
@@ -58,7 +65,26 @@ class ObjectLayerEngineElement extends HTMLElement {
58
65
 
59
66
  <div class="wrap">
60
67
  <div class="toolbar">
68
+ <button part="undo" title="Undo" disabled>Undo</button>
69
+ <button part="redo" title="Redo" disabled>Redo</button>
70
+
61
71
  <input type="color" part="color" title="Brush color" value="#000000" />
72
+ <label
73
+ >hex
74
+ <input
75
+ type="text"
76
+ part="hex-input"
77
+ title="Hex color (e.g., #FF0000 or #FF0000FF)"
78
+ placeholder="#000000FF"
79
+ style="width:9ch"
80
+ /></label>
81
+ <label
82
+ >rgba
83
+ <input type="number" part="r-input" min="0" max="255" value="0" title="Red (0-255)" style="width:5ch" />
84
+ <input type="number" part="g-input" min="0" max="255" value="0" title="Green (0-255)" style="width:5ch" />
85
+ <input type="number" part="b-input" min="0" max="255" value="0" title="Blue (0-255)" style="width:5ch" />
86
+ <input type="number" part="a-input" min="0" max="255" value="255" title="Alpha (0-255)" style="width:5ch" />
87
+ </label>
62
88
  <select part="tool">
63
89
  <option value="pencil">pencil</option>
64
90
  <option value="eraser">eraser</option>
@@ -112,6 +138,11 @@ class ObjectLayerEngineElement extends HTMLElement {
112
138
  this._pixelCanvas = this.shadowRoot.querySelector('canvas[part="canvas"]');
113
139
  this._gridCanvas = this.shadowRoot.querySelector('canvas[part="grid"]');
114
140
  this._colorInput = this.shadowRoot.querySelector('input[part="color"]');
141
+ this._hexInput = this.shadowRoot.querySelector('input[part="hex-input"]');
142
+ this._rInput = this.shadowRoot.querySelector('input[part="r-input"]');
143
+ this._gInput = this.shadowRoot.querySelector('input[part="g-input"]');
144
+ this._bInput = this.shadowRoot.querySelector('input[part="b-input"]');
145
+ this._aInput = this.shadowRoot.querySelector('input[part="a-input"]');
115
146
  this._toolSelect = this.shadowRoot.querySelector('select[part="tool"]');
116
147
  this._brushSizeInput = this.shadowRoot.querySelector('input[part="brush-size"]');
117
148
  this._pixelSizeInput = this.shadowRoot.querySelector('input[part="pixel-size"]');
@@ -131,6 +162,10 @@ class ObjectLayerEngineElement extends HTMLElement {
131
162
  this._opacityRange = this.shadowRoot.querySelector('input[part="opacity"]');
132
163
  this._opacityNumber = this.shadowRoot.querySelector('input[part="opacity-num"]');
133
164
 
165
+ // undo/redo buttons
166
+ this._undoBtn = this.shadowRoot.querySelector('button[part="undo"]');
167
+ this._redoBtn = this.shadowRoot.querySelector('button[part="redo"]');
168
+
134
169
  // internal state
135
170
  this._width = 16;
136
171
  this._height = 16;
@@ -147,16 +182,25 @@ class ObjectLayerEngineElement extends HTMLElement {
147
182
  this._tool = 'pencil';
148
183
  this._showGrid = false;
149
184
 
185
+ // history (undo/redo)
186
+ this._undoStack = [];
187
+ this._redoStack = [];
188
+ this._maxHistory = 200;
189
+ this._transactionActive = false; // grouping for pointer drags
190
+
150
191
  // binds
151
192
  this._onPointerDown = this._onPointerDown.bind(this);
152
193
  this._onPointerMove = this._onPointerMove.bind(this);
153
194
  this._onPointerUp = this._onPointerUp.bind(this);
195
+ this._onKeyDown = this._onKeyDown.bind(this);
154
196
 
155
197
  // transform methods bound (useful if passing as callbacks)
156
198
  this.flipHorizontal = this.flipHorizontal.bind(this);
157
199
  this.flipVertical = this.flipVertical.bind(this);
158
200
  this.rotateCW = this.rotateCW.bind(this);
159
201
  this.rotateCCW = this.rotateCCW.bind(this);
202
+
203
+ // ensure keyboard handlers bound for undo/redo
160
204
  }
161
205
 
162
206
  static get observedAttributes() {
@@ -185,6 +229,11 @@ class ObjectLayerEngineElement extends HTMLElement {
185
229
 
186
230
  // initialize color & opacity UI
187
231
  if (this._colorInput) this._colorInput.value = this._rgbaToHex(this._brushColor);
232
+ if (this._hexInput) this._hexInput.value = this._rgbaToHexWithAlpha(this._brushColor);
233
+ if (this._rInput) this._rInput.value = String(this._brushColor[0]);
234
+ if (this._gInput) this._gInput.value = String(this._brushColor[1]);
235
+ if (this._bInput) this._bInput.value = String(this._brushColor[2]);
236
+ if (this._aInput) this._aInput.value = String(this._brushColor[3]);
188
237
  if (this._opacityRange) this._opacityRange.value = String(this._brushColor[3]);
189
238
  if (this._opacityNumber) this._opacityNumber.value = String(this._brushColor[3]);
190
239
 
@@ -194,6 +243,28 @@ class ObjectLayerEngineElement extends HTMLElement {
194
243
  // keep current alpha
195
244
  this.setBrushColor([rgb[0], rgb[1], rgb[2], this._brushColor[3]]);
196
245
  });
246
+
247
+ // hex text input with optional alpha
248
+ if (this._hexInput) {
249
+ this._hexInput.addEventListener('change', (e) => {
250
+ const rgba = this._hexToRgbaWithAlpha(e.target.value);
251
+ this.setBrushColor(rgba);
252
+ });
253
+ }
254
+
255
+ // individual RGBA inputs
256
+ const updateFromRGBAInputs = () => {
257
+ const r = Math.max(0, Math.min(255, parseInt(this._rInput.value, 10) || 0));
258
+ const g = Math.max(0, Math.min(255, parseInt(this._gInput.value, 10) || 0));
259
+ const b = Math.max(0, Math.min(255, parseInt(this._bInput.value, 10) || 0));
260
+ const a = Math.max(0, Math.min(255, parseInt(this._aInput.value, 10) || 0));
261
+ this.setBrushColor([r, g, b, a]);
262
+ };
263
+ if (this._rInput) this._rInput.addEventListener('change', updateFromRGBAInputs);
264
+ if (this._gInput) this._gInput.addEventListener('change', updateFromRGBAInputs);
265
+ if (this._bInput) this._bInput.addEventListener('change', updateFromRGBAInputs);
266
+ if (this._aInput) this._aInput.addEventListener('change', updateFromRGBAInputs);
267
+
197
268
  this._toolSelect.addEventListener('change', (e) => this.setTool(e.target.value));
198
269
  this._brushSizeInput.addEventListener('change', (e) => this.setBrushSize(parseInt(e.target.value, 10) || 1));
199
270
  this._pixelSizeInput.addEventListener('change', (e) => {
@@ -232,13 +303,38 @@ class ObjectLayerEngineElement extends HTMLElement {
232
303
  });
233
304
 
234
305
  // transform buttons
235
- if (this._flipHBtn) this._flipHBtn.addEventListener('click', this.flipHorizontal);
236
- if (this._flipVBtn) this._flipVBtn.addEventListener('click', this.flipVertical);
237
- if (this._rotCWBtn) this._rotCWBtn.addEventListener('click', this.rotateCW);
238
- if (this._rotCCWBtn) this._rotCCWBtn.addEventListener('click', this.rotateCCW);
306
+ if (this._flipHBtn)
307
+ this._flipHBtn.addEventListener('click', () => {
308
+ this._beginTransaction();
309
+ this.flipHorizontal();
310
+ this._endTransaction();
311
+ });
312
+ if (this._flipVBtn)
313
+ this._flipVBtn.addEventListener('click', () => {
314
+ this._beginTransaction();
315
+ this.flipVertical();
316
+ this._endTransaction();
317
+ });
318
+ if (this._rotCWBtn)
319
+ this._rotCWBtn.addEventListener('click', () => {
320
+ this._beginTransaction();
321
+ this.rotateCW();
322
+ this._endTransaction();
323
+ });
324
+ if (this._rotCCWBtn)
325
+ this._rotCCWBtn.addEventListener('click', () => {
326
+ this._beginTransaction();
327
+ this.rotateCCW();
328
+ this._endTransaction();
329
+ });
239
330
 
240
331
  // clear button (makes canvas fully transparent)
241
- if (this._clearBtn) this._clearBtn.addEventListener('click', () => this.clear([0, 0, 0, 0]));
332
+ if (this._clearBtn)
333
+ this._clearBtn.addEventListener('click', () => {
334
+ this._beginTransaction();
335
+ this.clear([0, 0, 0, 0]);
336
+ this._endTransaction();
337
+ });
242
338
 
243
339
  // Export/Import
244
340
  this._exportBtn.addEventListener('click', () => this.exportPNG());
@@ -258,19 +354,31 @@ class ObjectLayerEngineElement extends HTMLElement {
258
354
  if (!file) return;
259
355
  const text = await file.text();
260
356
  try {
357
+ this._beginTransaction();
261
358
  this.importMatrixJSON(text);
359
+ this._endTransaction();
262
360
  } catch (err) {
263
361
  console.error(err);
264
362
  alert('Invalid JSON');
265
363
  }
266
364
  });
267
365
 
366
+ // undo/redo
367
+ if (this._undoBtn) this._undoBtn.addEventListener('click', () => this.undo());
368
+ if (this._redoBtn) this._redoBtn.addEventListener('click', () => this.redo());
369
+
268
370
  // Pointer events
269
371
  this._pixelCanvas.addEventListener('pointerdown', this._onPointerDown);
270
372
  window.addEventListener('pointermove', this._onPointerMove);
271
373
  window.addEventListener('pointerup', this._onPointerUp);
272
374
 
375
+ // keyboard for undo/redo
376
+ window.addEventListener('keydown', this._onKeyDown);
377
+
378
+ // initial render and clear history
273
379
  this.render();
380
+ this._clearHistory();
381
+ this._updateToolbarButtons();
274
382
  }
275
383
 
276
384
  disconnectedCallback() {
@@ -285,6 +393,11 @@ class ObjectLayerEngineElement extends HTMLElement {
285
393
  if (this._clearBtn) this._clearBtn.removeEventListener('click', () => this.clear([0, 0, 0, 0]));
286
394
  if (this._opacityRange) this._opacityRange.removeEventListener('input', () => {});
287
395
  if (this._opacityNumber) this._opacityNumber.removeEventListener('change', () => {});
396
+
397
+ if (this._undoBtn) this._undoBtn.removeEventListener('click', () => this.undo());
398
+ if (this._redoBtn) this._redoBtn.removeEventListener('click', () => this.redo());
399
+
400
+ window.removeEventListener('keydown', this._onKeyDown);
288
401
  }
289
402
 
290
403
  // ---------------- Matrix helpers ----------------
@@ -498,6 +611,11 @@ class ObjectLayerEngineElement extends HTMLElement {
498
611
  const a = typeof rgba[3] === 'number' ? this._clampInt(rgba[3]) : this._brushColor[3];
499
612
  this._brushColor = [r, g, b, a];
500
613
  if (this._colorInput) this._colorInput.value = this._rgbaToHex(this._brushColor);
614
+ if (this._hexInput) this._hexInput.value = this._rgbaToHexWithAlpha(this._brushColor);
615
+ if (this._rInput) this._rInput.value = String(this._brushColor[0]);
616
+ if (this._gInput) this._gInput.value = String(this._brushColor[1]);
617
+ if (this._bInput) this._bInput.value = String(this._brushColor[2]);
618
+ if (this._aInput) this._aInput.value = String(this._brushColor[3]);
501
619
  if (this._opacityRange) this._opacityRange.value = String(this._brushColor[3]);
502
620
  if (this._opacityNumber) this._opacityNumber.value = String(this._brushColor[3]);
503
621
  }
@@ -508,8 +626,10 @@ class ObjectLayerEngineElement extends HTMLElement {
508
626
  this._brushColor[3] = v;
509
627
  if (this._opacityRange) this._opacityRange.value = String(v);
510
628
  if (this._opacityNumber) this._opacityNumber.value = String(v);
629
+ if (this._aInput) this._aInput.value = String(v);
511
630
  // keep color input (hex) representing rgb only
512
631
  if (this._colorInput) this._colorInput.value = this._rgbaToHex(this._brushColor);
632
+ if (this._hexInput) this._hexInput.value = this._rgbaToHexWithAlpha(this._brushColor);
513
633
  }
514
634
  getBrushAlpha() {
515
635
  return this._brushColor[3];
@@ -533,9 +653,13 @@ class ObjectLayerEngineElement extends HTMLElement {
533
653
 
534
654
  fillBucket(x, y, targetColor = null) {
535
655
  if (!this._inBounds(x, y)) return;
656
+ this._beginTransaction();
536
657
  const src = this.getPixel(x, y);
537
658
  const newColor = targetColor ? targetColor.slice() : this._brushColor.slice();
538
- if (this._colorsEqual(src, newColor)) return;
659
+ if (this._colorsEqual(src, newColor)) {
660
+ this._endTransaction();
661
+ return;
662
+ }
539
663
  const stack = [[x, y]];
540
664
  while (stack.length) {
541
665
  const [cx, cy] = stack.pop();
@@ -547,6 +671,7 @@ class ObjectLayerEngineElement extends HTMLElement {
547
671
  }
548
672
  this.render();
549
673
  this.dispatchEvent(new CustomEvent('fill', { detail: { x, y } }));
674
+ this._endTransaction();
550
675
  }
551
676
 
552
677
  _colorsEqual(a, b) {
@@ -573,6 +698,8 @@ class ObjectLayerEngineElement extends HTMLElement {
573
698
  this._pixelCanvas.setPointerCapture(evt.pointerId);
574
699
  } catch (e) {}
575
700
  const [x, y] = this._toGridCoords(evt);
701
+ // start transaction for continuous stroke
702
+ this._beginTransaction();
576
703
  this._applyToolAt(x, y, evt);
577
704
  }
578
705
  _onPointerMove(evt) {
@@ -585,6 +712,8 @@ class ObjectLayerEngineElement extends HTMLElement {
585
712
  try {
586
713
  this._pixelCanvas.releasePointerCapture(evt.pointerId);
587
714
  } catch (e) {}
715
+ // finish transaction for the stroke
716
+ this._endTransaction();
588
717
  }
589
718
 
590
719
  _applyToolAt(x, y, evt, continuous = false) {
@@ -613,6 +742,7 @@ class ObjectLayerEngineElement extends HTMLElement {
613
742
  importMatrixJSON(json) {
614
743
  const data = typeof json === 'string' ? JSON.parse(json) : json;
615
744
  if (!data || !Array.isArray(data.matrix)) throw new TypeError('Invalid matrix JSON');
745
+ // wrap import as transactional change
616
746
  this.loadMatrix(data.matrix);
617
747
  }
618
748
 
@@ -712,6 +842,117 @@ class ObjectLayerEngineElement extends HTMLElement {
712
842
  setTimeout(() => URL.revokeObjectURL(url), 5000);
713
843
  }
714
844
 
845
+ // ---------------- Undo/Redo helpers ----------------
846
+ _snapshot() {
847
+ return {
848
+ width: this._width,
849
+ height: this._height,
850
+ matrix: this._matrix.map((r) => r.map((c) => c.slice())),
851
+ };
852
+ }
853
+
854
+ _loadSnapshot(snap) {
855
+ if (!snap) return;
856
+ this._width = snap.width;
857
+ this._height = snap.height;
858
+ this._matrix = snap.matrix.map((r) => r.map((c) => c.slice()));
859
+ if (this._widthInput) this._widthInput.value = String(this._width);
860
+ if (this._heightInput) this._heightInput.value = String(this._height);
861
+ this.setAttribute('width', String(this._width));
862
+ this.setAttribute('height', String(this._height));
863
+ this._setupContextsAndSize();
864
+ this.render();
865
+ this.dispatchEvent(new CustomEvent('matrixload', { detail: { width: this._width, height: this._height } }));
866
+ }
867
+
868
+ _matricesEqual(a, b) {
869
+ if (!a || !b) return false;
870
+ if (a.width !== b.width || a.height !== b.height) return false;
871
+ const h = a.height;
872
+ for (let y = 0; y < h; y++) {
873
+ for (let x = 0; x < a.width; x++) {
874
+ const ac = a.matrix[y][x];
875
+ const bc = b.matrix[y][x];
876
+ for (let i = 0; i < 4; i++) if (ac[i] !== bc[i]) return false;
877
+ }
878
+ }
879
+ return true;
880
+ }
881
+
882
+ _pushUndo(snap) {
883
+ this._undoStack.push(snap);
884
+ if (this._undoStack.length > this._maxHistory) this._undoStack.shift();
885
+ // clear redo
886
+ this._redoStack.length = 0;
887
+ this._updateToolbarButtons();
888
+ }
889
+
890
+ _clearHistory() {
891
+ this._undoStack.length = 0;
892
+ this._redoStack.length = 0;
893
+ this._updateToolbarButtons();
894
+ }
895
+
896
+ _beginTransaction() {
897
+ if (this._transactionActive) return;
898
+ this._transactionActive = true;
899
+ const before = this._snapshot();
900
+ this._pushUndo(before);
901
+ }
902
+
903
+ _endTransaction() {
904
+ if (!this._transactionActive) return;
905
+ this._transactionActive = false;
906
+ // if the last undo state is identical to current (no-op), remove it
907
+ const last = this._undoStack[this._undoStack.length - 1];
908
+ const now = this._snapshot();
909
+ if (this._matricesEqual(last, now)) this._undoStack.pop();
910
+ this._updateToolbarButtons();
911
+ }
912
+
913
+ undo() {
914
+ if (!this._undoStack.length) return;
915
+ const snap = this._undoStack.pop();
916
+ // push current to redo
917
+ this._redoStack.push(this._snapshot());
918
+ this._loadSnapshot(snap);
919
+ this._updateToolbarButtons();
920
+ this.dispatchEvent(new CustomEvent('undo'));
921
+ }
922
+
923
+ redo() {
924
+ if (!this._redoStack.length) return;
925
+ const snap = this._redoStack.pop();
926
+ // push current to undo
927
+ this._undoStack.push(this._snapshot());
928
+ this._loadSnapshot(snap);
929
+ this._updateToolbarButtons();
930
+ this.dispatchEvent(new CustomEvent('redo'));
931
+ }
932
+
933
+ _updateToolbarButtons() {
934
+ if (this._undoBtn) this._undoBtn.disabled = this._undoStack.length === 0;
935
+ if (this._redoBtn) this._redoBtn.disabled = this._redoStack.length === 0;
936
+ }
937
+
938
+ _onKeyDown(e) {
939
+ const meta = e.ctrlKey || e.metaKey;
940
+ if (!meta) return;
941
+ // ctrl/cmd+z -> undo, ctrl/cmd+shift+z or ctrl+ y -> redo
942
+ if (e.key === 'z' || e.key === 'Z') {
943
+ if (e.shiftKey) {
944
+ e.preventDefault();
945
+ this.redo();
946
+ } else {
947
+ e.preventDefault();
948
+ this.undo();
949
+ }
950
+ } else if (e.key === 'y' || e.key === 'Y') {
951
+ e.preventDefault();
952
+ this.redo();
953
+ }
954
+ }
955
+
715
956
  // ---------------- Helpers ----------------
716
957
  _hexToRgba(hex) {
717
958
  const h = (hex || '').replace('#', '');
@@ -730,6 +971,48 @@ class ObjectLayerEngineElement extends HTMLElement {
730
971
  .slice(1)}`;
731
972
  }
732
973
 
974
+ // convert RGBA to hex with alpha channel
975
+ _rgbaToHexWithAlpha(rgba) {
976
+ const [r, g, b, a] = rgba;
977
+ const rHex = this._clampInt(r).toString(16).padStart(2, '0');
978
+ const gHex = this._clampInt(g).toString(16).padStart(2, '0');
979
+ const bHex = this._clampInt(b).toString(16).padStart(2, '0');
980
+ const aHex = this._clampInt(a).toString(16).padStart(2, '0');
981
+ return `#${rHex}${gHex}${bHex}${aHex}`.toUpperCase();
982
+ }
983
+
984
+ // convert hex to RGBA with optional alpha channel support
985
+ _hexToRgbaWithAlpha(hex) {
986
+ const h = (hex || '').replace('#', '');
987
+ if (h.length === 3) {
988
+ // #RGB -> expand to RRGGBB
989
+ return [parseInt(h[0] + h[0], 16), parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16), 255];
990
+ }
991
+ if (h.length === 4) {
992
+ // #RGBA -> expand to RRGGBBAA
993
+ return [
994
+ parseInt(h[0] + h[0], 16),
995
+ parseInt(h[1] + h[1], 16),
996
+ parseInt(h[2] + h[2], 16),
997
+ parseInt(h[3] + h[3], 16),
998
+ ];
999
+ }
1000
+ if (h.length === 6) {
1001
+ // #RRGGBB
1002
+ return [parseInt(h.substring(0, 2), 16), parseInt(h.substring(2, 4), 16), parseInt(h.substring(4, 6), 16), 255];
1003
+ }
1004
+ if (h.length === 8) {
1005
+ // #RRGGBBAA
1006
+ return [
1007
+ parseInt(h.substring(0, 2), 16),
1008
+ parseInt(h.substring(2, 4), 16),
1009
+ parseInt(h.substring(4, 6), 16),
1010
+ parseInt(h.substring(6, 8), 16),
1011
+ ];
1012
+ }
1013
+ return [0, 0, 0, 255];
1014
+ }
1015
+
733
1016
  // ---------------- Transform helpers (flip/rotate) ----------------
734
1017
  flipHorizontal() {
735
1018
  // reverse each row (mirror horizontally)
@@ -869,7 +1152,12 @@ class ObjectLayerPngLoader extends HTMLElement {
869
1152
  <style>
870
1153
  :host {
871
1154
  display: block;
872
- font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Arial;
1155
+ font-family:
1156
+ system-ui,
1157
+ -apple-system,
1158
+ 'Segoe UI',
1159
+ Roboto,
1160
+ Arial;
873
1161
  }
874
1162
  .wrap {
875
1163
  display: flex;
@@ -1045,7 +1333,10 @@ class ObjectLayerPngLoader extends HTMLElement {
1045
1333
  }
1046
1334
 
1047
1335
  _showError(msg) {
1048
- alert(msg);
1336
+ NotificationManager.Push({
1337
+ status: 'error',
1338
+ html: msg,
1339
+ });
1049
1340
  }
1050
1341
  _dispatchLoadedEvent(filename) {
1051
1342
  this.dispatchEvent(new CustomEvent('pngloaded', { detail: { filename } }));