@underpostnet/underpost 2.98.0 → 2.98.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.
package/src/cli/run.js CHANGED
@@ -146,111 +146,6 @@ class UnderpostRun {
146
146
  * @memberof UnderpostRun
147
147
  */
148
148
  static RUNNERS = {
149
- /**
150
- * @method spark-template
151
- * @description Creates a new Spark template project using `sbt new` in `/home/dd/spark-template`, initializes a Git repository, and runs `replace_params.sh` and `build.sh`.
152
- * @param {string} path - The input value, identifier, or path for the operation.
153
- * @param {Object} options - The default underpost runner options for customizing workflow
154
- * @memberof UnderpostRun
155
- */
156
- 'spark-template': (path, options = UnderpostRun.DEFAULT_OPTION) => {
157
- const dir = '/home/dd/spark-template';
158
- shellExec(`sudo rm -rf ${dir}`);
159
- shellCd('/home/dd');
160
-
161
- // pbcopy(`cd /home/dd && sbt new ${process.env.GITHUB_USERNAME}/spark-template.g8`);
162
- // await read({ prompt: 'Command copy to clipboard, press enter to continue.\n' });
163
- shellExec(`cd /home/dd && sbt new ${process.env.GITHUB_USERNAME}/spark-template.g8 '--name=spark-template'`);
164
-
165
- shellCd(dir);
166
-
167
- shellExec(`git init && git add . && git commit -m "Base implementation"`);
168
- shellExec(`chmod +x ./replace_params.sh`);
169
- shellExec(`chmod +x ./build.sh`);
170
-
171
- shellExec(`./replace_params.sh`);
172
- shellExec(`./build.sh`);
173
-
174
- shellCd('/home/dd/engine');
175
- },
176
- /**
177
- * @method rmi
178
- * @description Forces the removal of all local Podman images (`podman rmi $(podman images -qa) --force`).
179
- * @param {string} path - The input value, identifier, or path for the operation.
180
- * @param {Object} options - The default underpost runner options for customizing workflow
181
- * @memberof UnderpostRun
182
- */
183
- rmi: (path, options = UnderpostRun.DEFAULT_OPTION) => {
184
- shellExec(`podman rmi $(podman images -qa) --force`);
185
- },
186
- /**
187
- * @method kill
188
- * @description Kills processes listening on the specified port(s). If the `path` contains a `+`, it treats it as a range of ports to kill.
189
- * @param {string} path - The input value, identifier, or path for the operation (used as the port number).
190
- * @param {Object} options - The default underpost runner options for customizing workflow
191
- * @memberof UnderpostRun
192
- */
193
- kill: (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
194
- if (options.pid) return shellExec(`sudo kill -9 ${options.pid}`);
195
- for (const _path of path.split(',')) {
196
- if (_path.split('+')[1]) {
197
- let [port, sumPortOffSet] = _path.split('+');
198
- port = parseInt(port);
199
- sumPortOffSet = parseInt(sumPortOffSet);
200
- for (const sumPort of range(0, sumPortOffSet))
201
- shellExec(`sudo kill -9 $(lsof -t -i:${parseInt(port) + parseInt(sumPort)})`);
202
- } else shellExec(`sudo kill -9 $(lsof -t -i:${_path})`);
203
- }
204
- },
205
- /**
206
- * @method secret
207
- * @description Creates an Underpost secret named 'underpost' from a file, defaulting to `/home/dd/engine/engine-private/conf/dd-cron/.env.production` if no path is provided.
208
- * @param {string} path - The input value, identifier, or path for the operation (used as the optional path to the secret file).
209
- * @param {Object} options - The default underpost runner options for customizing workflow
210
- * @memberof UnderpostRun
211
- */
212
- secret: (path, options = UnderpostRun.DEFAULT_OPTION) => {
213
- shellExec(
214
- `underpost secret underpost --create-from-file ${
215
- path ? path : `/home/dd/engine/engine-private/conf/dd-cron/.env.production`
216
- }`,
217
- );
218
- },
219
- /**
220
- * @method underpost-config
221
- * @description Calls `UnderpostDeploy.API.configMap` to create a Kubernetes ConfigMap, defaulting to the 'production' environment.
222
- * @param {string} path - The input value, identifier, or path for the operation (used as the optional configuration name/environment).
223
- * @param {Object} options - The default underpost runner options for customizing workflow
224
- * @memberof UnderpostRun
225
- */
226
- 'underpost-config': (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
227
- UnderpostDeploy.API.configMap(path ? path : 'production', options.namespace);
228
- },
229
- /**
230
- * @method gpu-env
231
- * @description Sets up a dedicated GPU development environment cluster, resetting and then setting up the cluster with `--dedicated-gpu` and monitoring the pods.
232
- * @param {string} path - The input value, identifier, or path for the operation.
233
- * @param {Object} options - The default underpost runner options for customizing workflow
234
- * @memberof UnderpostRun
235
- */
236
- 'gpu-env': (path, options = UnderpostRun.DEFAULT_OPTION) => {
237
- shellExec(
238
- `node bin cluster --dev --reset && node bin cluster --dev --dedicated-gpu --kubeadm && kubectl get pods --all-namespaces -o wide -w`,
239
- );
240
- },
241
- /**
242
- * @method tf-gpu-test
243
- * @description Deletes existing `tf-gpu-test-script` ConfigMap and `tf-gpu-test-pod`, and applies the test manifest from `manifests/deployment/tensorflow/tf-gpu-test.yaml`.
244
- * @param {string} path - The input value, identifier, or path for the operation.
245
- * @param {Object} options - The default underpost runner options for customizing workflow
246
- * @memberof UnderpostRun
247
- */
248
- 'tf-gpu-test': (path, options = UnderpostRun.DEFAULT_OPTION) => {
249
- const { underpostRoot, namespace } = options;
250
- shellExec(`kubectl delete configmap tf-gpu-test-script -n ${namespace} --ignore-not-found`);
251
- shellExec(`kubectl delete pod tf-gpu-test-pod -n ${namespace} --ignore-not-found`);
252
- shellExec(`kubectl apply -f ${underpostRoot}/manifests/deployment/tensorflow/tf-gpu-test.yaml -n ${namespace}`);
253
- },
254
149
  /**
255
150
  * @method dev-cluster
256
151
  * @description Resets and deploys a full development cluster including MongoDB, Valkey, exposes services, and updates `/etc/hosts` for local access.
@@ -899,7 +794,7 @@ EOF
899
794
  'host-update': async (path, options = UnderpostRun.DEFAULT_OPTION) => {
900
795
  // const baseCommand = options.dev ? 'node bin' : 'underpost';
901
796
  shellExec(`chmod +x ${options.underpostRoot}/scripts/rocky-setup.sh`);
902
- shellExec(`${options.underpostRoot}/scripts/rocky-setup.sh --yes${options.dev ? ' --install-dev' : ``}`);
797
+ shellExec(`${options.underpostRoot}/scripts/rocky-setup.sh${options.dev ? ' --install-dev' : ``}`);
903
798
  },
904
799
 
905
800
  /**
@@ -1600,6 +1495,113 @@ EOF
1600
1495
  ],
1601
1496
  });
1602
1497
  },
1498
+
1499
+ /**
1500
+ * @method spark-template
1501
+ * @description Creates a new Spark template project using `sbt new` in `/home/dd/spark-template`, initializes a Git repository, and runs `replace_params.sh` and `build.sh`.
1502
+ * @param {string} path - The input value, identifier, or path for the operation.
1503
+ * @param {Object} options - The default underpost runner options for customizing workflow
1504
+ * @memberof UnderpostRun
1505
+ */
1506
+ 'spark-template': (path, options = UnderpostRun.DEFAULT_OPTION) => {
1507
+ const dir = '/home/dd/spark-template';
1508
+ shellExec(`sudo rm -rf ${dir}`);
1509
+ shellCd('/home/dd');
1510
+
1511
+ // pbcopy(`cd /home/dd && sbt new ${process.env.GITHUB_USERNAME}/spark-template.g8`);
1512
+ // await read({ prompt: 'Command copy to clipboard, press enter to continue.\n' });
1513
+ shellExec(`cd /home/dd && sbt new ${process.env.GITHUB_USERNAME}/spark-template.g8 '--name=spark-template'`);
1514
+
1515
+ shellCd(dir);
1516
+
1517
+ shellExec(`git init && git add . && git commit -m "Base implementation"`);
1518
+ shellExec(`chmod +x ./replace_params.sh`);
1519
+ shellExec(`chmod +x ./build.sh`);
1520
+
1521
+ shellExec(`./replace_params.sh`);
1522
+ shellExec(`./build.sh`);
1523
+
1524
+ shellCd('/home/dd/engine');
1525
+ },
1526
+ /**
1527
+ * @method rmi
1528
+ * @description Forces the removal of all local Podman images (`podman rmi $(podman images -qa) --force`).
1529
+ * @param {string} path - The input value, identifier, or path for the operation.
1530
+ * @param {Object} options - The default underpost runner options for customizing workflow
1531
+ * @memberof UnderpostRun
1532
+ */
1533
+ rmi: (path, options = UnderpostRun.DEFAULT_OPTION) => {
1534
+ shellExec(`podman rmi $(podman images -qa) --force`);
1535
+ },
1536
+ /**
1537
+ * @method kill
1538
+ * @description Kills processes listening on the specified port(s). If the `path` contains a `+`, it treats it as a range of ports to kill.
1539
+ * @param {string} path - The input value, identifier, or path for the operation (used as the port number).
1540
+ * @param {Object} options - The default underpost runner options for customizing workflow
1541
+ * @memberof UnderpostRun
1542
+ */
1543
+ kill: (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
1544
+ if (options.pid) return shellExec(`sudo kill -9 ${options.pid}`);
1545
+ for (const _path of path.split(',')) {
1546
+ if (_path.split('+')[1]) {
1547
+ let [port, sumPortOffSet] = _path.split('+');
1548
+ port = parseInt(port);
1549
+ sumPortOffSet = parseInt(sumPortOffSet);
1550
+ for (const sumPort of range(0, sumPortOffSet))
1551
+ shellExec(`sudo kill -9 $(lsof -t -i:${parseInt(port) + parseInt(sumPort)})`);
1552
+ } else shellExec(`sudo kill -9 $(lsof -t -i:${_path})`);
1553
+ }
1554
+ },
1555
+ /**
1556
+ * @method secret
1557
+ * @description Creates an Underpost secret named 'underpost' from a file, defaulting to `/home/dd/engine/engine-private/conf/dd-cron/.env.production` if no path is provided.
1558
+ * @param {string} path - The input value, identifier, or path for the operation (used as the optional path to the secret file).
1559
+ * @param {Object} options - The default underpost runner options for customizing workflow
1560
+ * @memberof UnderpostRun
1561
+ */
1562
+ secret: (path, options = UnderpostRun.DEFAULT_OPTION) => {
1563
+ const secretPath = path ? path : `/home/dd/engine/engine-private/conf/dd-cron/.env.production`;
1564
+ const command = options.dev
1565
+ ? `node bin secret underpost --create-from-file ${secretPath}`
1566
+ : `underpost secret underpost --create-from-file ${secretPath}`;
1567
+ shellExec(command);
1568
+ },
1569
+ /**
1570
+ * @method underpost-config
1571
+ * @description Calls `UnderpostDeploy.API.configMap` to create a Kubernetes ConfigMap, defaulting to the 'production' environment.
1572
+ * @param {string} path - The input value, identifier, or path for the operation (used as the optional configuration name/environment).
1573
+ * @param {Object} options - The default underpost runner options for customizing workflow
1574
+ * @memberof UnderpostRun
1575
+ */
1576
+ 'underpost-config': (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
1577
+ UnderpostDeploy.API.configMap(path ? path : 'production', options.namespace);
1578
+ },
1579
+ /**
1580
+ * @method gpu-env
1581
+ * @description Sets up a dedicated GPU development environment cluster, resetting and then setting up the cluster with `--dedicated-gpu` and monitoring the pods.
1582
+ * @param {string} path - The input value, identifier, or path for the operation.
1583
+ * @param {Object} options - The default underpost runner options for customizing workflow
1584
+ * @memberof UnderpostRun
1585
+ */
1586
+ 'gpu-env': (path, options = UnderpostRun.DEFAULT_OPTION) => {
1587
+ shellExec(
1588
+ `node bin cluster --dev --reset && node bin cluster --dev --dedicated-gpu --kubeadm && kubectl get pods --all-namespaces -o wide -w`,
1589
+ );
1590
+ },
1591
+ /**
1592
+ * @method tf-gpu-test
1593
+ * @description Deletes existing `tf-gpu-test-script` ConfigMap and `tf-gpu-test-pod`, and applies the test manifest from `manifests/deployment/tensorflow/tf-gpu-test.yaml`.
1594
+ * @param {string} path - The input value, identifier, or path for the operation.
1595
+ * @param {Object} options - The default underpost runner options for customizing workflow
1596
+ * @memberof UnderpostRun
1597
+ */
1598
+ 'tf-gpu-test': (path, options = UnderpostRun.DEFAULT_OPTION) => {
1599
+ const { underpostRoot, namespace } = options;
1600
+ shellExec(`kubectl delete configmap tf-gpu-test-script -n ${namespace} --ignore-not-found`);
1601
+ shellExec(`kubectl delete pod tf-gpu-test-pod -n ${namespace} --ignore-not-found`);
1602
+ shellExec(`kubectl apply -f ${underpostRoot}/manifests/deployment/tensorflow/tf-gpu-test.yaml -n ${namespace}`);
1603
+ },
1604
+
1603
1605
  /**
1604
1606
  * @method deploy-job
1605
1607
  * @description Creates and applies a custom Kubernetes Pod manifest (Job) for running arbitrary commands inside a container image (defaulting to a TensorFlow/NVIDIA image).
@@ -1,6 +1,6 @@
1
1
  import { marked } from 'marked';
2
2
  import { FileService } from '../../services/file/file.service.js';
3
- import { append, getBlobFromUint8ArrayFile, getRawContentFile, htmls, s } from './VanillaJs.js';
3
+ import { append, getBlobFromUint8ArrayFile, getRawContentFile, htmls, s, sa } from './VanillaJs.js';
4
4
  import { s4 } from './CommonJs.js';
5
5
  import { Translate } from './Translate.js';
6
6
  import { Modal, renderViewTitle } from './Modal.js';
@@ -8,10 +8,53 @@ import { DocumentService } from '../../services/document/document.service.js';
8
8
  import { CoreService, getApiBaseUrl, headersFactory } from '../../services/core/core.service.js';
9
9
  import { loggerFactory } from './Logger.js';
10
10
  import { imageShimmer, renderChessPattern, renderCssAttr, styleFactory } from './Css.js';
11
- import { getQueryParams } from './Router.js';
11
+ import { getQueryParams, setPath } from './Router.js';
12
12
 
13
13
  const logger = loggerFactory(import.meta);
14
14
 
15
+ const attachMarkdownLinkHandlers = (containerSelector) => {
16
+ const container = s(containerSelector);
17
+ if (!container || container.dataset.mdLinkHandler) return;
18
+ container.dataset.mdLinkHandler = 'true';
19
+
20
+ container.addEventListener('click', async (e) => {
21
+ const link = e.target.closest('.markdown-content a[href]');
22
+ if (!link) return;
23
+
24
+ const href = link.getAttribute('href');
25
+ if (!href || href.startsWith('#')) return;
26
+
27
+ e.preventDefault();
28
+ const isExternal = href.startsWith('http://') || href.startsWith('https://');
29
+
30
+ if (isExternal) {
31
+ const result = await Modal.RenderConfirm({
32
+ id: `external-link-${s4()}`,
33
+ html: async () => html`
34
+ <div class="in section-mp" style="text-align: center; padding: 20px;">
35
+ <p>${Translate.Render('external-link-warning')}</p>
36
+ <p style="word-break: break-all; margin-top: 10px;"><strong>${href}</strong></p>
37
+ </div>
38
+ `,
39
+ icon: html`<i class="fas fa-external-link-alt"></i>`,
40
+ style: {
41
+ width: '350px',
42
+ height: '500px',
43
+ overflow: 'auto',
44
+ 'z-index': '11',
45
+ resize: 'none',
46
+ },
47
+ });
48
+
49
+ if (result && result.status === 'confirm') {
50
+ window.open(href, '_blank', 'noopener,noreferrer');
51
+ }
52
+ } else {
53
+ setPath(href);
54
+ }
55
+ });
56
+ };
57
+
15
58
  const Content = {
16
59
  Render: async function (options = { idModal: '' }) {
17
60
  const { idModal } = options;
@@ -171,7 +214,9 @@ const Content = {
171
214
  case 'md':
172
215
  {
173
216
  const content = await Content.getFileContent(file, options);
174
- render += html`<div class="${options.class}" ${styleFactory(options.style)}>${marked.parse(content)}</div>`;
217
+ render += html`<div class="${options.class} markdown-content" ${styleFactory(options.style)}>
218
+ ${marked.parse(content)}
219
+ </div>`;
175
220
  }
176
221
  break;
177
222
 
@@ -239,6 +284,11 @@ ${JSON.stringify(JSON.parse(content), null, 4)}</pre
239
284
 
240
285
  if (options.raw) return render;
241
286
  append(container, render);
287
+
288
+ // Scrape and handle markdown links after DOM insertion
289
+ if (ext === 'md') {
290
+ attachMarkdownLinkHandlers(container);
291
+ }
242
292
  },
243
293
 
244
294
  /**
@@ -274,4 +324,4 @@ ${JSON.stringify(JSON.parse(content), null, 4)}</pre
274
324
  },
275
325
  };
276
326
 
277
- export { Content };
327
+ export { Content, attachMarkdownLinkHandlers };
@@ -1,31 +1,224 @@
1
+ /**
2
+ * Utility module for fullscreen mode management with cross-browser and PWA compatibility.
3
+ * Provides robust fullscreen detection, event handling, and UI synchronization.
4
+ * @module src/client/components/core/FullScreen.js
5
+ * @namespace FullScreenClient
6
+ */
7
+
1
8
  import { Responsive } from './Responsive.js';
2
9
  import { ToggleSwitch } from './ToggleSwitch.js';
3
10
  import { Translate } from './Translate.js';
4
11
  import { checkFullScreen, fullScreenIn, fullScreenOut, s } from './VanillaJs.js';
5
12
 
13
+ /**
14
+ * Manages fullscreen mode state, event handling, and UI synchronization.
15
+ * Supports all major browsers and PWA/Nativefier environments with comprehensive
16
+ * vendor-prefixed API detection.
17
+ * @memberof FullScreenClient
18
+ */
6
19
  const FullScreen = {
20
+ /**
21
+ * Internal state flag tracking the intended fullscreen mode.
22
+ * @type {boolean}
23
+ * @private
24
+ */
25
+ _fullScreenSwitch: false,
26
+
27
+ /**
28
+ * Flag indicating whether event listeners have been attached.
29
+ * Prevents duplicate event listener registration.
30
+ * @type {boolean}
31
+ * @private
32
+ */
33
+ _eventListenersAdded: false,
34
+
35
+ /**
36
+ * Flag preventing concurrent sync operations.
37
+ * Ensures state synchronization happens sequentially.
38
+ * @type {boolean}
39
+ * @private
40
+ */
41
+ _syncInProgress: false,
42
+
43
+ /**
44
+ * Checks if the browser is currently in fullscreen mode.
45
+ * Supports all vendor-prefixed fullscreen APIs for maximum compatibility:
46
+ * - Standard Fullscreen API
47
+ * - Webkit (Chrome, Safari, Opera)
48
+ * - Mozilla (Firefox)
49
+ * - Microsoft (IE/Edge)
50
+ * @private
51
+ * @memberof FullScreenClient.FullScreen
52
+ * @returns {boolean} True if currently in fullscreen mode, false otherwise.
53
+ */
54
+ _isFullScreen: function () {
55
+ return !!(
56
+ document.fullscreenElement ||
57
+ document.webkitFullscreenElement ||
58
+ document.mozFullScreenElement ||
59
+ document.msFullscreenElement ||
60
+ document.fullscreen ||
61
+ document.webkitIsFullScreen ||
62
+ document.mozFullScreen
63
+ );
64
+ },
65
+
66
+ /**
67
+ * Synchronizes the toggle switch UI state with the actual fullscreen state.
68
+ * Prevents race conditions using the _syncInProgress flag.
69
+ * Updates the toggle switch only if there's a mismatch between UI and actual state.
70
+ * @private
71
+ * @memberof FullScreenClient.FullScreen
72
+ * @returns {void}
73
+ */
74
+ _syncToggleState: function () {
75
+ if (this._syncInProgress) return;
76
+ this._syncInProgress = true;
77
+
78
+ const actualFullScreen = this._isFullScreen();
79
+
80
+ // Only update if there's a mismatch
81
+ if (this._fullScreenSwitch !== actualFullScreen) {
82
+ this._fullScreenSwitch = actualFullScreen;
83
+
84
+ // Update toggle switch UI if it exists
85
+ const toggle = s('.fullscreen');
86
+ if (toggle && ToggleSwitch.Tokens[`fullscreen`]) {
87
+ const switchState = ToggleSwitch.Tokens[`fullscreen`].checked;
88
+ if (switchState !== actualFullScreen) {
89
+ ToggleSwitch.Tokens[`fullscreen`].click();
90
+ }
91
+ }
92
+ }
93
+
94
+ setTimeout(() => {
95
+ this._syncInProgress = false;
96
+ }, 100);
97
+ },
98
+
99
+ /**
100
+ * Event handler for fullscreen change events.
101
+ * Triggers UI state synchronization when fullscreen mode changes.
102
+ * @private
103
+ * @memberof FullScreenClient.FullScreen
104
+ * @returns {void}
105
+ */
106
+ _handleFullScreenChange: function () {
107
+ this._syncToggleState();
108
+ },
109
+
110
+ /**
111
+ * Attaches all necessary event listeners for fullscreen mode detection.
112
+ * Handles multiple scenarios:
113
+ * - Vendor-prefixed fullscreen change events (Chrome, Firefox, IE/Edge)
114
+ * - Window resize events (for PWA/Nativefier compatibility)
115
+ * - ESC key detection (fallback for manual fullscreen exit)
116
+ * Only attaches listeners once to prevent duplicates.
117
+ * @private
118
+ * @memberof FullScreenClient.FullScreen
119
+ * @returns {void}
120
+ */
121
+ _addEventListeners: function () {
122
+ if (this._eventListenersAdded) return;
123
+
124
+ const events = ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'];
125
+
126
+ events.forEach((eventName) => {
127
+ document.addEventListener(eventName, () => this._handleFullScreenChange(), false);
128
+ });
129
+
130
+ // Additional check for PWA/Nativefier window resize events
131
+ window.addEventListener(
132
+ 'resize',
133
+ () => {
134
+ setTimeout(() => this._syncToggleState(), 150);
135
+ },
136
+ false,
137
+ );
138
+
139
+ // ESC key detection fallback
140
+ document.addEventListener(
141
+ 'keydown',
142
+ (e) => {
143
+ if (e.key === 'Escape' || e.keyCode === 27) {
144
+ setTimeout(() => this._syncToggleState(), 100);
145
+ }
146
+ },
147
+ false,
148
+ );
149
+
150
+ this._eventListenersAdded = true;
151
+ },
152
+
153
+ /**
154
+ * Enters fullscreen mode if not already in fullscreen.
155
+ * Updates internal state and triggers fullscreen API.
156
+ * Verifies the state change after a delay to ensure synchronization.
157
+ * @private
158
+ * @memberof FullScreenClient.FullScreen
159
+ * @returns {void}
160
+ */
161
+ _enterFullScreen: function () {
162
+ if (this._isFullScreen()) return;
163
+
164
+ this._fullScreenSwitch = true;
165
+ fullScreenIn();
166
+
167
+ // Verify after attempt
168
+ setTimeout(() => this._syncToggleState(), 300);
169
+ },
170
+
171
+ /**
172
+ * Exits fullscreen mode if currently in fullscreen.
173
+ * Updates internal state and triggers fullscreen exit API.
174
+ * Verifies the state change after a delay to ensure synchronization.
175
+ * @private
176
+ * @memberof FullScreenClient.FullScreen
177
+ * @returns {void}
178
+ */
179
+ _exitFullScreen: function () {
180
+ if (!this._isFullScreen()) return;
181
+
182
+ this._fullScreenSwitch = false;
183
+ fullScreenOut();
184
+
185
+ // Verify after attempt
186
+ setTimeout(() => this._syncToggleState(), 300);
187
+ },
188
+
189
+ /**
190
+ * Renders the fullscreen toggle setting UI component.
191
+ * Initializes fullscreen state detection, sets up event listeners,
192
+ * and creates a toggle switch for user interaction.
193
+ * Integrates with the Responsive system for dynamic updates.
194
+ * @memberof FullScreenClient.FullScreen
195
+ * @returns {Promise<string>} A promise resolving to the HTML string for the fullscreen setting component.
196
+ */
7
197
  RenderSetting: async function () {
8
- let fullScreenSwitch = checkFullScreen();
198
+ // Initialize state from actual fullscreen status
199
+ this._fullScreenSwitch = this._isFullScreen();
200
+
201
+ // Setup event listeners once
202
+ this._addEventListeners();
203
+
204
+ // Update responsive event
9
205
  Responsive.Event['full-screen-settings'] = () => {
10
- let fullScreenMode = checkFullScreen();
11
- if ((fullScreenSwitch && !fullScreenMode) || (!fullScreenSwitch && fullScreenMode))
12
- if (s('.fullscreen')) ToggleSwitch.Tokens[`fullscreen`].click();
206
+ this._syncToggleState();
13
207
  };
208
+
14
209
  return html`<div class="in section-mp">
15
210
  ${await ToggleSwitch.Render({
16
211
  wrapper: true,
17
212
  wrapperLabel: html`<i class="fa-solid fa-expand"></i> ${Translate.Render('fullscreen')}`,
18
213
  id: 'fullscreen',
19
214
  disabledOnClick: true,
20
- checked: fullScreenSwitch,
215
+ checked: this._fullScreenSwitch,
21
216
  on: {
22
217
  unchecked: () => {
23
- fullScreenSwitch = false;
24
- if (checkFullScreen()) fullScreenOut();
218
+ this._exitFullScreen();
25
219
  },
26
220
  checked: () => {
27
- fullScreenSwitch = true;
28
- if (!checkFullScreen()) fullScreenIn();
221
+ this._enterFullScreen();
29
222
  },
30
223
  },
31
224
  })}