@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/.vscode/settings.json +7 -8
- package/README.md +2 -2
- package/bin/build.js +21 -5
- package/bin/deploy.js +10 -0
- package/bin/file.js +2 -1
- package/bin/util.js +0 -17
- package/cli.md +2 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +2 -4
- package/scripts/rocky-setup.sh +12 -39
- package/src/api/document/document.service.js +1 -1
- package/src/cli/cluster.js +5 -9
- package/src/cli/repository.js +9 -9
- package/src/cli/run.js +108 -106
- package/src/client/components/core/Content.js +54 -4
- package/src/client/components/core/FullScreen.js +202 -9
- package/src/client/components/core/Panel.js +91 -22
- package/src/client/components/core/PanelForm.js +5 -2
- package/src/client/components/core/Translate.js +8 -0
- package/src/client/components/core/VanillaJs.js +80 -29
- package/src/client/services/default/default.management.js +324 -136
- package/src/index.js +58 -20
- package/src/client/components/core/ObjectLayerEngine.js +0 -1520
- package/src/client/components/core/ObjectLayerEngineModal.js +0 -1245
- package/src/client/components/core/ObjectLayerEngineViewer.js +0 -880
- package/src/server/object-layer.js +0 -335
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
|
|
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)}
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
215
|
+
checked: this._fullScreenSwitch,
|
|
21
216
|
on: {
|
|
22
217
|
unchecked: () => {
|
|
23
|
-
|
|
24
|
-
if (checkFullScreen()) fullScreenOut();
|
|
218
|
+
this._exitFullScreen();
|
|
25
219
|
},
|
|
26
220
|
checked: () => {
|
|
27
|
-
|
|
28
|
-
if (!checkFullScreen()) fullScreenIn();
|
|
221
|
+
this._enterFullScreen();
|
|
29
222
|
},
|
|
30
223
|
},
|
|
31
224
|
})}
|