cyberia 3.0.2 → 3.0.3

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 (47) hide show
  1. package/CHANGELOG.md +323 -290
  2. package/CLI-HELP.md +2 -1
  3. package/bin/build.js +0 -1
  4. package/bin/cyberia.js +98 -4
  5. package/bin/index.js +98 -4
  6. package/conf.js +192 -0
  7. package/deployment.yaml +72 -2
  8. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  9. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  10. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  11. package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
  12. package/package.json +13 -9
  13. package/proxy.yaml +56 -9
  14. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +40 -7
  15. package/src/api/object-layer/object-layer.model.js +61 -19
  16. package/src/api/object-layer/object-layer.service.js +4 -9
  17. package/src/cli/index.js +6 -0
  18. package/src/cli/run.js +30 -2
  19. package/src/client/Underpost.index.js +36 -0
  20. package/src/client/components/core/Modal.js +2 -0
  21. package/src/client/components/core/PublicProfile.js +3 -3
  22. package/src/client/components/core/Router.js +34 -1
  23. package/src/client/components/core/Worker.js +1 -1
  24. package/src/client/components/cryptokoyn/CssCryptokoyn.js +63 -1
  25. package/src/client/components/cyberia/ObjectLayerEngineModal.js +145 -119
  26. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +64 -6
  27. package/src/client/components/itemledger/CssItemledger.js +62 -0
  28. package/src/client/components/underpost/CommonUnderpost.js +29 -0
  29. package/src/client/components/underpost/CssUnderpost.js +222 -0
  30. package/src/client/components/underpost/CyberpunkBloggerUnderpost.js +879 -0
  31. package/src/client/components/underpost/DocumentSearchProvider.js +448 -0
  32. package/src/client/components/underpost/ElementsUnderpost.js +38 -0
  33. package/src/client/components/underpost/LabGalleryUnderpost.js +82 -0
  34. package/src/client/components/underpost/LogInUnderpost.js +20 -0
  35. package/src/client/components/underpost/LogOutUnderpost.js +13 -0
  36. package/src/client/components/underpost/MenuUnderpost.js +605 -0
  37. package/src/client/components/underpost/RoutesUnderpost.js +45 -0
  38. package/src/client/components/underpost/SettingsUnderpost.js +16 -0
  39. package/src/client/components/underpost/SignUpUnderpost.js +9 -0
  40. package/src/client/components/underpost/SocketIoUnderpost.js +54 -0
  41. package/src/client/components/underpost/TranslateUnderpost.js +10 -0
  42. package/src/client/services/object-layer/object-layer.management.js +23 -4
  43. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +83 -0
  44. package/src/client/ssr/head/UnderpostScripts.js +6 -0
  45. package/src/index.js +1 -1
  46. package/src/server/object-layer.js +13 -10
  47. package/src/server/semantic-layer-generator.js +1 -0
package/src/cli/run.js CHANGED
@@ -32,7 +32,6 @@ const logger = loggerFactory(import.meta);
32
32
  * @property {string} podName - The name of the pod to run.
33
33
  * @property {string} nodeName - The name of the node to run.
34
34
  * @property {number} port - Custom port to use.
35
- * @property {boolean} etcHosts - Whether to modify /etc/hosts.
36
35
  * @property {string} volumeHostPath - The host path for the volume.
37
36
  * @property {string} volumeMountPath - The mount path for the volume.
38
37
  * @property {string} imageName - The name of the image to run.
@@ -85,9 +84,12 @@ const logger = loggerFactory(import.meta);
85
84
  * @property {string} monitorStatusKindType - The monitor status kind type option.
86
85
  * @property {string} monitorStatusDeltaMs - The monitor status delta in milliseconds.
87
86
  * @property {string} monitorStatusMaxAttempts - The maximum number of attempts for monitor status.
87
+ * @property {boolean} logs - Whether to enable logs.
88
88
  * @property {boolean} dryRun - Whether to perform a dry run.
89
89
  * @property {boolean} createJobNow - Whether to create the job immediately.
90
- * @property {boolean} logs - Whether to enable logs.
90
+ * @property {string|Array<{ip: string, hostnames: string[]}>} hostAliases - Adds entries to the Pod /etc/hosts via Kubernetes hostAliases.
91
+ * As a string (CLI): semicolon-separated entries of "ip=hostname1,hostname2" (e.g., "127.0.0.1=foo.local,bar.local;10.1.2.3=foo.remote").
92
+ * As an array (programmatic): objects with `ip` and `hostnames` fields (e.g., [{ ip: "127.0.0.1", hostnames: ["foo.local"] }]).
91
93
  * @memberof UnderpostRun
92
94
  */
93
95
  const DEFAULT_OPTION = {
@@ -150,6 +152,7 @@ const DEFAULT_OPTION = {
150
152
  logs: false,
151
153
  dryRun: false,
152
154
  createJobNow: false,
155
+ hostAliases: '',
153
156
  };
154
157
 
155
158
  /**
@@ -1840,6 +1843,30 @@ EOF
1840
1843
  const imagePullPolicy = options.imagePullPolicy || 'IfNotPresent';
1841
1844
  const hostNetwork = options.hostNetwork ? options.hostNetwork : '';
1842
1845
  const apiVersion = options.apiVersion || 'v1';
1846
+ // Parse hostAliases option:
1847
+ // - string from CLI: "ip1=host1,host2;ip2=host3,host4"
1848
+ // - array from programmatic callers: [{ ip: "127.0.0.1", hostnames: ["foo.local"] }]
1849
+ const hostAliases = options.hostAliases
1850
+ ? Array.isArray(options.hostAliases)
1851
+ ? options.hostAliases
1852
+ : options.hostAliases
1853
+ .split(';')
1854
+ .filter((entry) => entry.trim())
1855
+ .map((entry) => {
1856
+ const [ip, hostnamesStr] = entry.split('=');
1857
+ const hostnames = hostnamesStr ? hostnamesStr.split(',').map((h) => h.trim()) : [];
1858
+ return { ip: ip.trim(), hostnames };
1859
+ })
1860
+ : [];
1861
+ const hostAliasesYaml =
1862
+ hostAliases.length > 0
1863
+ ? ` hostAliases:\n${hostAliases
1864
+ .map(
1865
+ (alias) =>
1866
+ ` - ip: "${alias.ip}"\n hostnames:\n${alias.hostnames.map((h) => ` - "${h}"`).join('\n')}`,
1867
+ )
1868
+ .join('\n')}`
1869
+ : '';
1843
1870
  const labels = options.labels
1844
1871
  ? options.labels
1845
1872
  .split(',')
@@ -1870,6 +1897,7 @@ spec:
1870
1897
  restartPolicy: ${restartPolicy}
1871
1898
  ${runtimeClassName ? ` runtimeClassName: ${runtimeClassName}` : ''}
1872
1899
  ${hostNetwork ? ` hostNetwork: ${hostNetwork}` : ''}
1900
+ ${hostAliasesYaml}
1873
1901
  containers:
1874
1902
  - name: ${containerName}
1875
1903
  image: ${imageName}
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ import { Css } from './components/core/Css.js';
4
+ import { Responsive } from './components/core/Responsive.js';
5
+ import { TranslateCore } from './components/core/Translate.js';
6
+ import { LogInUnderpost } from './components/underpost/LogInUnderpost.js';
7
+ import { LogOutUnderpost } from './components/underpost/LogOutUnderpost.js';
8
+ import { SignUpUnderpost } from './components/underpost/SignUpUnderpost.js';
9
+ import { MenuUnderpost } from './components/underpost/MenuUnderpost.js';
10
+ import { RouterUnderpost } from './components/underpost/RoutesUnderpost.js';
11
+ import { TranslateUnderpost } from './components/underpost/TranslateUnderpost.js';
12
+ import { Worker } from './components/core/Worker.js';
13
+ import { UnderpostParams } from './components/underpost/CommonUnderpost.js';
14
+ import { Keyboard } from './components/core/Keyboard.js';
15
+ import { SocketIoUnderpost } from './components/underpost/SocketIoUnderpost.js';
16
+ import { SocketIo } from './components/core/SocketIo.js';
17
+ import { ElementsUnderpost } from './components/underpost/ElementsUnderpost.js';
18
+ import { CssUnderpostDark, CssUnderpostLight } from './components/underpost/CssUnderpost.js';
19
+
20
+ window.onload = () =>
21
+ Worker.instance({
22
+ router: RouterUnderpost,
23
+ render: async () => {
24
+ await Css.loadThemes([CssUnderpostDark, CssUnderpostLight]);
25
+ await TranslateCore.Init();
26
+ await TranslateUnderpost.Init();
27
+ await Responsive.Init();
28
+ await MenuUnderpost.Render();
29
+ await SocketIo.Init({ channels: ElementsUnderpost.Data });
30
+ await SocketIoUnderpost.Init();
31
+ await LogInUnderpost();
32
+ await LogOutUnderpost();
33
+ await SignUpUnderpost();
34
+ await Keyboard.Init({ callBackTime: UnderpostParams.EVENT_CALLBACK_TIME });
35
+ },
36
+ });
@@ -23,6 +23,7 @@ import {
23
23
  coreUI,
24
24
  sanitizeRoute,
25
25
  getQueryParams,
26
+ setRouterReady,
26
27
  } from './Router.js';
27
28
  import { NotificationManager } from './NotificationManager.js';
28
29
  import { EventsUI } from './EventsUI.js';
@@ -1401,6 +1402,7 @@ const Modal = {
1401
1402
  }
1402
1403
  });
1403
1404
  setTimeout(window.onresize);
1405
+ setRouterReady();
1404
1406
  });
1405
1407
  })();
1406
1408
  break;
@@ -196,7 +196,7 @@ const PublicProfile = {
196
196
  user: { _id: userId, username },
197
197
  } = options;
198
198
  const idModal = options.idModal || getId();
199
- const profileId = `public-profile-${userId}`;
199
+ const profileId = `public-profile-${username}`;
200
200
  const waveAnimationId = `${profileId}-wave`;
201
201
  const profileImageClass = `${profileId}-image`;
202
202
  const profileContainerId = `${profileId}-container`;
@@ -673,7 +673,7 @@ const PublicProfile = {
673
673
  "
674
674
  >
675
675
  <div
676
- class="${profileId}-image-container"
676
+ class="${profileId}-image-container public-profile-image-container"
677
677
  style="
678
678
  position: relative;
679
679
  width: 160px;
@@ -688,7 +688,7 @@ const PublicProfile = {
688
688
  "
689
689
  >
690
690
  <img
691
- class="${profileImageClass}"
691
+ class="${profileImageClass} public-profile-image"
692
692
  style="
693
693
  width: 100%;
694
694
  height: 100%;
@@ -39,6 +39,36 @@ const coreUI = ['modal-menu', 'main-body', 'main-body-top', 'bottom-bar', 'board
39
39
  */
40
40
  const closeModalRouteChangeEvents = {};
41
41
 
42
+ /**
43
+ * Deferred promise that resolves once the full UI (including deferred slide-menu DOM
44
+ * setup in Modal) is ready. Any code that depends on the complete DOM — route handlers,
45
+ * session callbacks, panel updates, etc. — can simply `await RouterReady` instead of
46
+ * scattering individual null-checks across every microfrontend.
47
+ *
48
+ * Resolved by calling `setRouterReady()`, which should happen exactly once at the end
49
+ * of Modal's deferred slide-menu `setTimeout` block.
50
+ * @type {Promise<void>}
51
+ * @memberof PwaRouter
52
+ */
53
+ let _routerReadyResolve;
54
+ const RouterReady = new Promise((resolve) => {
55
+ _routerReadyResolve = resolve;
56
+ });
57
+
58
+ /**
59
+ * Signals that the deferred UI setup is complete and the router (and any other
60
+ * awaiter of `RouterReady`) may safely access the full DOM.
61
+ * This must be called exactly once – typically at the end of Modal's deferred
62
+ * slide-menu `setTimeout` block.
63
+ * @memberof PwaRouter
64
+ */
65
+ const setRouterReady = () => {
66
+ if (_routerReadyResolve) {
67
+ _routerReadyResolve();
68
+ _routerReadyResolve = undefined;
69
+ }
70
+ };
71
+
42
72
  /**
43
73
  * Determines the base path for the application, often used for routing within a sub-directory.
44
74
  * It checks the current URL's pathname and `window.Routes` to return the appropriate proxy path.
@@ -209,7 +239,8 @@ const Router = function (options = { Routes: () => {}, e: new PopStateEvent() })
209
239
  * @param {object} RouterInstance - The router instance configuration, including the `Routes` function.
210
240
  * @memberof PwaRouter
211
241
  */
212
- const LoadRouter = function (RouterInstance) {
242
+ const LoadRouter = async function (RouterInstance) {
243
+ await RouterReady;
213
244
  Router(RouterInstance);
214
245
  window.onpopstate = (e) => {
215
246
  Router({ ...RouterInstance, e });
@@ -466,4 +497,6 @@ export {
466
497
  sanitizeRoute,
467
498
  queryParamsChangeListeners,
468
499
  listenQueryParamsChange,
500
+ setRouterReady,
501
+ RouterReady,
469
502
  };
@@ -135,7 +135,7 @@ class PwaWorker {
135
135
  const isInstall = await this.status();
136
136
  if (!isInstall) await this.install();
137
137
  await render();
138
- LoadRouter(this.RouterInstance);
138
+ await LoadRouter(this.RouterInstance);
139
139
  LoadingAnimation.removeSplashScreen();
140
140
  if (this.devMode()) {
141
141
  // const delayLiveReload = 1250;
@@ -30,6 +30,7 @@ const CssCommonCryptokoyn = async () => {
30
30
  --cy-font-retro: 'retro-font';
31
31
  --cy-font-retro-title: 'retro-font-title';
32
32
  --cy-font-retro-sensitive: 'retro-font-sensitive';
33
+ --cy-font-retro-cta: 'retro-font-cta';
33
34
  }
34
35
 
35
36
  @font-face {
@@ -44,6 +45,64 @@ const CssCommonCryptokoyn = async () => {
44
45
  font-family: 'retro-font-sensitive';
45
46
  src: URL('${getProxyPath()}assets/fonts/VT323-Regular.ttf') format('truetype');
46
47
  }
48
+ @font-face {
49
+ font-family: 'retro-font-cta';
50
+ src: URL('${getProxyPath()}assets/fonts/PressStart2P-Regular.ttf') format('truetype');
51
+ }
52
+
53
+ /* Landing Page & Object Viewer Styles */
54
+ .landing-container {
55
+ display: flex;
56
+ flex-direction: column;
57
+ align-items: center;
58
+ justify-content: center;
59
+ height: 100vh;
60
+ width: 100%;
61
+ background: #000;
62
+ color: #fff;
63
+ text-align: center;
64
+ }
65
+
66
+ .landing-title,
67
+ h1,
68
+ h2,
69
+ h3 {
70
+ font-family: var(--cy-font-retro-cta);
71
+ font-size: 5rem;
72
+ color: #ff0d0d;
73
+ text-shadow: 2px 2px 0px #9e0808;
74
+ margin-bottom: 2rem;
75
+ }
76
+
77
+ p {
78
+ font-family: var(--cy-font-retro);
79
+ }
80
+
81
+ .object-layer-viewer-container {
82
+ width: 100% !important;
83
+ font-family: var(--cy-font-retro);
84
+ }
85
+
86
+ .cta-button {
87
+ font-family: var(--cy-font-retro-cta);
88
+ font-size: 1.5rem;
89
+ padding: 1rem 2rem;
90
+ border: 3px solid #ff0d0d;
91
+ background: transparent;
92
+ color: #ff0d0d;
93
+ cursor: pointer;
94
+ transition: all 0.3s ease-in-out;
95
+ text-shadow: 1px 1px 0px #9e0808;
96
+ }
97
+
98
+ .cta-button:hover {
99
+ background: #ff0d0d;
100
+ color: #000;
101
+ box-shadow:
102
+ 0 0 20px #ff0d0d,
103
+ 0 0 40px #ff0d0d;
104
+ text-shadow: none;
105
+ }
47
106
 
48
107
  /* Base typography and smoothing */
49
108
 
@@ -86,6 +145,9 @@ const CssCommonCryptokoyn = async () => {
86
145
  .main-btn-menu {
87
146
  font-size: 20px;
88
147
  }
148
+ .input-container {
149
+ width: 278px;
150
+ }
89
151
  </style>
90
152
 
91
153
  <div class="ag-grid-style"></div>`;
@@ -137,7 +199,7 @@ const CssCryptokoynLight = {
137
199
  }
138
200
  .default-slide-menu-top-bar-fix-title-container-text {
139
201
  font-size: 40px !important;
140
- color: yellow !important;
202
+ color: #ffcc00 !important;
141
203
  }
142
204
  </style>
143
205
  ${borderChar(1, `#010101`, ['.default-slide-menu-top-bar-fix-title-container-text'])}
@@ -192,6 +192,16 @@ const ObjectLayerEngineModal = {
192
192
  const { Elements } = options;
193
193
 
194
194
  const directionCodes = ['08', '18', '02', '12', '04', '14', '06', '16'];
195
+ const directionCodeLabels = {
196
+ '08': 'Down Idle',
197
+ 18: 'Down Walk',
198
+ '02': 'Up Idle',
199
+ 12: 'Up Walk',
200
+ '04': 'Left Idle',
201
+ 14: 'Left Walk',
202
+ '06': 'Right Idle',
203
+ 16: 'Right Walk',
204
+ };
195
205
  const itemTypes = ['skin', 'weapon', 'armor', 'artifact', 'floor'];
196
206
  const statTypes = ['effect', 'resistance', 'agility', 'range', 'intelligence', 'utility'];
197
207
 
@@ -355,7 +365,7 @@ const ObjectLayerEngineModal = {
355
365
  const capturedDirectionCode = directionCode;
356
366
 
357
367
  if (!s(`.frames-${capturedDirectionCode}`)) {
358
- logger.warn(`Frames container for direction code ${capturedDirectionCode} not found`);
368
+ console.warn(`Frames container for direction code ${capturedDirectionCode} not found`);
359
369
  return;
360
370
  }
361
371
 
@@ -481,7 +491,7 @@ const ObjectLayerEngineModal = {
481
491
  let loader = s('object-layer-png-loader');
482
492
 
483
493
  if (!ole || !loader) {
484
- logger.warn('object-layer-engine or object-layer-png-loader component not found after retries');
494
+ console.warn('object-layer-engine or object-layer-png-loader component not found after retries');
485
495
  return;
486
496
  }
487
497
 
@@ -516,7 +526,7 @@ const ObjectLayerEngineModal = {
516
526
  <div class="in section-mp-border">
517
527
  <div class="fl">
518
528
  <div class="in fll">
519
- <div class="in direction-code-bar-frames-title">${directionCode}</div>
529
+ <div class="in direction-code-bar-frames-title">${directionCodeLabels[directionCode]}</div>
520
530
  <div class="in direction-code-bar-frames-btn">
521
531
  ${await BtnIcon.Render({
522
532
  label: html`
@@ -564,139 +574,153 @@ const ObjectLayerEngineModal = {
564
574
  }
565
575
 
566
576
  setTimeout(async () => {
577
+ let loadFramesInProgress = false;
567
578
  const loadFrames = async () => {
568
- showFrameLoading();
569
-
570
- // Clear all frames and data at the start to prevent duplication from multiple calls
571
- // This must happen BEFORE any async operations to avoid race conditions
572
- for (const directionCode of directionCodes) {
573
- // Clear DOM frames for this direction code
574
- const framesContainer = s(`.frames-${directionCode}`);
575
- if (framesContainer) {
576
- framesContainer.innerHTML = '';
577
- }
578
- // Clear data for this direction code
579
- ObjectLayerEngineModal.ObjectLayerData[directionCode] = [];
579
+ // Concurrency guard: skip if already loading to prevent duplicate frames
580
+ if (loadFramesInProgress) {
581
+ console.warn('loadFrames already in progress, skipping duplicate call');
582
+ return;
580
583
  }
584
+ loadFramesInProgress = true;
585
+
586
+ try {
587
+ showFrameLoading();
588
+
589
+ // Clear all frames and data at the start to prevent duplication from multiple calls
590
+ // This must happen BEFORE any async operations to avoid race conditions
591
+ for (const directionCode of directionCodes) {
592
+ // Clear DOM frames for this direction code
593
+ const framesContainer = s(`.frames-${directionCode}`);
594
+ if (framesContainer) {
595
+ framesContainer.innerHTML = '';
596
+ }
597
+ // Clear data for this direction code
598
+ ObjectLayerEngineModal.ObjectLayerData[directionCode] = [];
599
+ }
581
600
 
582
- for (const directionCode of directionCodes) {
583
- // Use IIFE to properly capture directionCode and handle async operations
584
- await (async (currentDirectionCode) => {
585
- // Register frame add button handler after DOM is ready
586
- // Wait longer to ensure all direction bars are rendered
587
-
588
- if (loadedData && loadedData.metadata && loadedData.metadata.data && currentDirectionCode) {
589
- // Show loading animation only once on first direction that has frames
590
-
591
- const { type, id } = loadedData.metadata.data.item;
592
- const directions = ObjectLayerEngineModal.getDirectionsFromDirectionCode(currentDirectionCode);
593
-
594
- console.log(`Loading frames for direction code: ${currentDirectionCode}, directions:`, directions);
595
-
596
- // Check if frames exist for any direction mapped to this direction code
597
- const { frames } = loadedData.objectLayerRenderFramesId;
598
- for (const direction of directions) {
599
- if (frames[direction] && frames[direction].length > 0) {
600
- // Track this direction code as having original data
601
- if (!ObjectLayerEngineModal.originalDirectionCodes.includes(currentDirectionCode)) {
602
- ObjectLayerEngineModal.originalDirectionCodes.push(currentDirectionCode);
603
- }
604
- // Load frames from static PNG URLs sequentially to avoid race conditions
605
- const frameCount = frames[direction].length;
606
- console.log(`Found ${frameCount} frames for direction: ${direction} (code: ${currentDirectionCode})`);
607
- for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) {
608
- const pngUrl = `${getProxyPath()}assets/${type}/${id}/${currentDirectionCode}/${frameIndex}.png`;
601
+ for (const directionCode of directionCodes) {
602
+ // Use IIFE to properly capture directionCode and handle async operations
603
+ await (async (currentDirectionCode) => {
604
+ // Register frame add button handler after DOM is ready
605
+ // Wait longer to ensure all direction bars are rendered
606
+
607
+ if (loadedData && loadedData.metadata && loadedData.metadata.data && currentDirectionCode) {
608
+ // Show loading animation only once on first direction that has frames
609
+
610
+ const { type, id } = loadedData.metadata.data.item;
611
+ const directions = ObjectLayerEngineModal.getDirectionsFromDirectionCode(currentDirectionCode);
612
+
613
+ console.log(`Loading frames for direction code: ${currentDirectionCode}, directions:`, directions);
614
+
615
+ // Check if frames exist for any direction mapped to this direction code
616
+ const { frames } = loadedData.objectLayerRenderFramesId;
617
+ for (const direction of directions) {
618
+ if (frames[direction] && frames[direction].length > 0) {
619
+ // Track this direction code as having original data
620
+ if (!ObjectLayerEngineModal.originalDirectionCodes.includes(currentDirectionCode)) {
621
+ ObjectLayerEngineModal.originalDirectionCodes.push(currentDirectionCode);
622
+ }
623
+ // Load frames from static PNG URLs sequentially to avoid race conditions
624
+ const frameCount = frames[direction].length;
609
625
  console.log(
610
- `Loading frame ${frameIndex} for direction code ${currentDirectionCode} from: ${pngUrl}`,
626
+ `Found ${frameCount} frames for direction: ${direction} (code: ${currentDirectionCode})`,
611
627
  );
612
- await processAndAddFrameFromPngUrl(currentDirectionCode, pngUrl);
628
+ for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) {
629
+ const pngUrl = `${getProxyPath()}assets/${type}/${id}/${currentDirectionCode}/${frameIndex}.png`;
630
+ console.log(
631
+ `Loading frame ${frameIndex} for direction code ${currentDirectionCode} from: ${pngUrl}`,
632
+ );
633
+ await processAndAddFrameFromPngUrl(currentDirectionCode, pngUrl);
634
+ }
635
+ console.log(`Completed loading ${frameCount} frames for direction code: ${currentDirectionCode}`);
636
+ // Once we found frames for this direction code, we can break to avoid duplicates
637
+ break;
613
638
  }
614
- console.log(`Completed loading ${frameCount} frames for direction code: ${currentDirectionCode}`);
615
- // Once we found frames for this direction code, we can break to avoid duplicates
616
- break;
617
639
  }
618
640
  }
619
- }
620
641
 
621
- const buttonSelector = `.direction-code-bar-frames-btn-${currentDirectionCode}`;
622
- console.log(`Registering click handler for: ${buttonSelector}`);
642
+ const buttonSelector = `.direction-code-bar-frames-btn-${currentDirectionCode}`;
643
+ console.log(`Registering click handler for: ${buttonSelector}`);
623
644
 
624
- EventsUI.onClick(buttonSelector, async () => {
625
- console.log(`Add frame button clicked for direction: ${currentDirectionCode}`);
626
- const ole = s('object-layer-engine');
627
- if (!ole) {
628
- console.error('object-layer-engine not found');
629
- return;
630
- }
631
- const image = await ole.toBlob();
632
- const json = ole.exportMatrixJSON();
633
-
634
- // Check if we're in edit mode
635
- if (editingFrameId && editingDirectionCode) {
636
- // Ensure we're clicking the add button for the same direction being edited
637
- if (currentDirectionCode !== editingDirectionCode) {
638
- NotificationManager.Push({
639
- html: `<i class="fa-solid fa-exclamation-circle"></i> Please click the glowing <i class="fa-solid fa-edit"></i> button for direction <strong>${editingDirectionCode}</strong> to save changes, or click <i class="fa-solid fa-times"></i> to cancel.`,
640
- status: 'warning',
641
- });
642
- return; // Don't add a new frame
645
+ EventsUI.onClick(buttonSelector, async () => {
646
+ console.log(`Add frame button clicked for direction: ${currentDirectionCode}`);
647
+ const ole = s('object-layer-engine');
648
+ if (!ole) {
649
+ console.error('object-layer-engine not found');
650
+ return;
643
651
  }
652
+ const image = await ole.toBlob();
653
+ const json = ole.exportMatrixJSON();
654
+
655
+ // Check if we're in edit mode
656
+ if (editingFrameId && editingDirectionCode) {
657
+ // Ensure we're clicking the add button for the same direction being edited
658
+ if (currentDirectionCode !== editingDirectionCode) {
659
+ NotificationManager.Push({
660
+ html: `<i class="fa-solid fa-exclamation-circle"></i> Please click the glowing <i class="fa-solid fa-edit"></i> button for direction <strong>${editingDirectionCode}</strong> to save changes, or click <i class="fa-solid fa-times"></i> to cancel.`,
661
+ status: 'warning',
662
+ });
663
+ return; // Don't add a new frame
664
+ }
644
665
 
645
- // UPDATE existing frame
646
- console.log(`Updating frame ${editingFrameId} in direction ${editingDirectionCode}`);
647
-
648
- // Find the frame in the data array
649
- const frameArray = ObjectLayerEngineModal.ObjectLayerData[editingDirectionCode];
650
- const frameIndex = frameArray?.findIndex((frame) => frame.id === editingFrameId);
651
-
652
- if (frameIndex !== undefined && frameIndex >= 0) {
653
- // Update the frame data while preserving the ID and index
654
- frameArray[frameIndex] = {
655
- id: editingFrameId,
656
- image,
657
- json,
658
- };
659
-
660
- // Update the visual representation
661
- const imgElement = s(`.direction-code-bar-frames-img-${editingFrameId}`);
662
- if (imgElement) {
663
- imgElement.src = URL.createObjectURL(image);
666
+ // UPDATE existing frame
667
+ console.log(`Updating frame ${editingFrameId} in direction ${editingDirectionCode}`);
668
+
669
+ // Find the frame in the data array
670
+ const frameArray = ObjectLayerEngineModal.ObjectLayerData[editingDirectionCode];
671
+ const frameIndex = frameArray?.findIndex((frame) => frame.id === editingFrameId);
672
+
673
+ if (frameIndex !== undefined && frameIndex >= 0) {
674
+ // Update the frame data while preserving the ID and index
675
+ frameArray[frameIndex] = {
676
+ id: editingFrameId,
677
+ image,
678
+ json,
679
+ };
680
+
681
+ // Update the visual representation
682
+ const imgElement = s(`.direction-code-bar-frames-img-${editingFrameId}`);
683
+ if (imgElement) {
684
+ imgElement.src = URL.createObjectURL(image);
685
+ }
686
+
687
+ console.log(`Frame ${editingFrameId} updated successfully at index ${frameIndex}`);
688
+ NotificationManager.Push({
689
+ html: `<i class="fa-solid fa-check-circle"></i> Frame updated successfully at position ${frameIndex + 1}!`,
690
+ status: 'success',
691
+ });
692
+ } else {
693
+ console.error(`Could not find frame ${editingFrameId} in direction ${editingDirectionCode}`);
694
+ NotificationManager.Push({
695
+ html: `<i class="fa-solid fa-exclamation-triangle"></i> Error: Could not find frame to update`,
696
+ status: 'error',
697
+ });
664
698
  }
665
699
 
666
- console.log(`Frame ${editingFrameId} updated successfully at index ${frameIndex}`);
667
- NotificationManager.Push({
668
- html: `<i class="fa-solid fa-check-circle"></i> Frame updated successfully at position ${frameIndex + 1}!`,
669
- status: 'success',
670
- });
700
+ // Exit edit mode and restore UI
701
+ exitEditMode();
671
702
  } else {
672
- console.error(`Could not find frame ${editingFrameId} in direction ${editingDirectionCode}`);
673
- NotificationManager.Push({
674
- html: `<i class="fa-solid fa-exclamation-triangle"></i> Error: Could not find frame to update`,
675
- status: 'error',
676
- });
677
- }
703
+ // ADD new frame (existing behavior)
704
+ const id = `frame-capture-${s4()}-${s4()}`;
705
+ console.log(`Creating new frame ${id} for direction ${currentDirectionCode}`);
706
+
707
+ if (!ObjectLayerEngineModal.ObjectLayerData[currentDirectionCode])
708
+ ObjectLayerEngineModal.ObjectLayerData[currentDirectionCode] = [];
709
+ ObjectLayerEngineModal.ObjectLayerData[currentDirectionCode].push({ id, image, json });
710
+ console.log(
711
+ `Stored frame ${id} in direction code ${currentDirectionCode}. Total frames:`,
712
+ ObjectLayerEngineModal.ObjectLayerData[currentDirectionCode].length,
713
+ );
678
714
 
679
- // Exit edit mode and restore UI
680
- exitEditMode();
681
- } else {
682
- // ADD new frame (existing behavior)
683
- const id = `frame-capture-${s4()}-${s4()}`;
684
- console.log(`Creating new frame ${id} for direction ${currentDirectionCode}`);
685
-
686
- if (!ObjectLayerEngineModal.ObjectLayerData[currentDirectionCode])
687
- ObjectLayerEngineModal.ObjectLayerData[currentDirectionCode] = [];
688
- ObjectLayerEngineModal.ObjectLayerData[currentDirectionCode].push({ id, image, json });
689
- console.log(
690
- `Stored frame ${id} in direction code ${currentDirectionCode}. Total frames:`,
691
- ObjectLayerEngineModal.ObjectLayerData[currentDirectionCode].length,
692
- );
693
-
694
- await addFrameToBar(currentDirectionCode, id, image, json);
695
- }
696
- });
697
- })(directionCode);
715
+ await addFrameToBar(currentDirectionCode, id, image, json);
716
+ }
717
+ });
718
+ })(directionCode);
719
+ }
720
+ hideFrameLoading();
721
+ } finally {
722
+ loadFramesInProgress = false;
698
723
  }
699
- hideFrameLoading();
700
724
  };
701
725
  RouterEvents[`router-${options.idModal}`] = loadFrames;
702
726
 
@@ -748,6 +772,7 @@ const ObjectLayerEngineModal = {
748
772
  data: {
749
773
  stats: {},
750
774
  item: {},
775
+ ledger: { type: 'OFF_CHAIN' },
751
776
  },
752
777
  };
753
778
  for (const directionCode of directionCodes) {
@@ -980,6 +1005,7 @@ const ObjectLayerEngineModal = {
980
1005
  font-weight: bold;
981
1006
  font-size: 1.2rem;
982
1007
  padding: 0.5rem;
1008
+ width: 70px;
983
1009
  }
984
1010
  .direction-code-bar-frames-img {
985
1011
  width: 100px;