underpost 2.8.843 → 2.8.845
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/.github/workflows/{ghpkg.yml → ghpkg.ci.yml} +1 -1
- package/.github/workflows/{npmpkg.yml → npmpkg.ci.yml} +1 -1
- package/.github/workflows/{publish.yml → publish.ci.yml} +1 -1
- package/.github/workflows/{pwa-microservices-template.page.yml → pwa-microservices-template-page.cd.yml} +1 -1
- package/.github/workflows/{pwa-microservices-template.test.yml → pwa-microservices-template-test.ci.yml} +1 -1
- package/.vscode/settings.json +0 -1
- package/README.md +18 -2
- package/bin/build.js +8 -5
- package/bin/deploy.js +10 -69
- package/bin/file.js +15 -11
- package/cli.md +47 -43
- package/docker-compose.yml +1 -1
- package/manifests/deployment/dd-template-development/deployment.yaml +2 -2
- package/manifests/maas/gpu-diag.sh +1 -1
- package/package.json +3 -5
- package/src/api/user/user.router.js +24 -1
- package/src/cli/cluster.js +19 -10
- package/src/cli/index.js +1 -0
- package/src/cli/run.js +21 -0
- package/src/client/components/core/Css.js +52 -2
- package/src/client/components/core/CssCore.js +0 -4
- package/src/client/components/core/Docs.js +10 -57
- package/src/client/components/core/DropDown.js +128 -82
- package/src/client/components/core/EventsUI.js +92 -5
- package/src/client/components/core/Modal.js +451 -120
- package/src/client/components/core/NotificationManager.js +2 -2
- package/src/client/components/core/Panel.js +2 -2
- package/src/client/components/core/PanelForm.js +12 -2
- package/src/client/components/core/Recover.js +1 -1
- package/src/client/components/core/Router.js +63 -2
- package/src/client/components/core/Translate.js +2 -2
- package/src/index.js +1 -1
- package/src/server/client-build-docs.js +205 -0
- package/src/server/client-build.js +11 -140
- package/src/server/valkey.js +102 -41
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
renderStatus,
|
|
25
25
|
renderCssAttr,
|
|
26
26
|
} from './Css.js';
|
|
27
|
-
import { setDocTitle } from './Router.js';
|
|
27
|
+
import { setDocTitle, closeModalRouteChangeEvent, handleModalViewRoute } from './Router.js';
|
|
28
28
|
import { NotificationManager } from './NotificationManager.js';
|
|
29
29
|
import { EventsUI } from './EventsUI.js';
|
|
30
30
|
import { Translate } from './Translate.js';
|
|
@@ -39,6 +39,7 @@ const logger = loggerFactory(import.meta);
|
|
|
39
39
|
|
|
40
40
|
const Modal = {
|
|
41
41
|
Data: {},
|
|
42
|
+
|
|
42
43
|
Render: async function (
|
|
43
44
|
options = {
|
|
44
45
|
id: '',
|
|
@@ -86,14 +87,14 @@ const Modal = {
|
|
|
86
87
|
onBarUiOpen: {},
|
|
87
88
|
onBarUiClose: {},
|
|
88
89
|
onHome: {},
|
|
90
|
+
homeModals: options.homeModals ? options.homeModals : [],
|
|
89
91
|
query: options.query ? `${window.location.search}` : undefined,
|
|
90
92
|
};
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
top = `${
|
|
94
|
-
left = `${
|
|
95
|
-
}
|
|
96
|
-
if (idModal !== 'main-body') setCenterRestore();
|
|
93
|
+
|
|
94
|
+
if (idModal !== 'main-body' && options.mode !== 'view') {
|
|
95
|
+
top = `${window.innerHeight / 2 - height / 2}px`;
|
|
96
|
+
left = `${window.innerWidth / 2 - width / 2}px`;
|
|
97
|
+
}
|
|
97
98
|
if (options && 'mode' in options) {
|
|
98
99
|
this.Data[idModal][options.mode] = {};
|
|
99
100
|
switch (options.mode) {
|
|
@@ -104,6 +105,7 @@ const Modal = {
|
|
|
104
105
|
options.style = {
|
|
105
106
|
...options.style,
|
|
106
107
|
'min-width': `${minWidth}px`,
|
|
108
|
+
width: '100%',
|
|
107
109
|
};
|
|
108
110
|
|
|
109
111
|
if (this.mobileModal()) {
|
|
@@ -124,19 +126,13 @@ const Modal = {
|
|
|
124
126
|
};
|
|
125
127
|
Responsive.Event[`view-${idModal}`]();
|
|
126
128
|
|
|
127
|
-
//
|
|
128
|
-
if (options.route)
|
|
129
|
-
(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (path !== newPath) {
|
|
135
|
-
// console.warn('SET MODAL URI', newPath);
|
|
136
|
-
setPath(`${newPath}`); // ${location.search}
|
|
137
|
-
setDocTitle({ ...options.RouterInstance, route: options.route });
|
|
138
|
-
}
|
|
139
|
-
})();
|
|
129
|
+
// Handle view mode modal route
|
|
130
|
+
if (options.route) {
|
|
131
|
+
handleModalViewRoute({
|
|
132
|
+
route: options.route,
|
|
133
|
+
RouterInstance: options.RouterInstance,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
140
136
|
|
|
141
137
|
break;
|
|
142
138
|
case 'slide-menu':
|
|
@@ -434,8 +430,14 @@ const Modal = {
|
|
|
434
430
|
rules: [] /*{ type: 'isEmpty' }, { type: 'isEmail' }*/,
|
|
435
431
|
},
|
|
436
432
|
];
|
|
437
|
-
|
|
438
|
-
let
|
|
433
|
+
// Reusable hover/focus controller for search history panel
|
|
434
|
+
let unbindDocSearch = null;
|
|
435
|
+
const hoverFocusCtl = EventsUI.HoverFocusController({
|
|
436
|
+
inputSelector: `.top-bar-search-box-container`,
|
|
437
|
+
panelSelector: `.${id}`,
|
|
438
|
+
activeElementId: inputSearchBoxId,
|
|
439
|
+
onDismiss: () => dismissSearchBox(),
|
|
440
|
+
});
|
|
439
441
|
let currentKeyBoardSearchBoxIndex = 0;
|
|
440
442
|
let results = [];
|
|
441
443
|
let historySearchBox = [];
|
|
@@ -592,7 +594,7 @@ const Modal = {
|
|
|
592
594
|
const isSearchBoxActiveElement = isActiveElement(inputSearchBoxId);
|
|
593
595
|
checkHistoryBoxTitleStatus();
|
|
594
596
|
checkShortcutContainerInfoEnabled();
|
|
595
|
-
if (!isSearchBoxActiveElement && !
|
|
597
|
+
if (!isSearchBoxActiveElement && !hoverFocusCtl.shouldStay()) {
|
|
596
598
|
Modal.removeModal(searchBoxHistoryId);
|
|
597
599
|
return;
|
|
598
600
|
}
|
|
@@ -676,27 +678,20 @@ const Modal = {
|
|
|
676
678
|
barMode: options.barMode,
|
|
677
679
|
});
|
|
678
680
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
681
|
+
// Bind hover/focus and click-outside to dismiss
|
|
682
|
+
hoverFocusCtl.bind();
|
|
683
|
+
unbindDocSearch = EventsUI.bindDismissOnDocumentClick({
|
|
684
|
+
shouldStay: hoverFocusCtl.shouldStay,
|
|
685
|
+
onDismiss: () => dismissSearchBox(),
|
|
686
|
+
anchors: [`.top-bar-search-box-container`, `.${id}`],
|
|
687
|
+
});
|
|
688
|
+
// Ensure cleanup when modal closes
|
|
689
|
+
Modal.Data[id].onCloseListener[`unbind-doc-${id}`] = () => unbindDocSearch && unbindDocSearch();
|
|
684
690
|
|
|
685
|
-
|
|
691
|
+
Modal.MoveTitleToBar(id);
|
|
686
692
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
};
|
|
690
|
-
s(`.top-bar-search-box-container`).onmouseout = () => {
|
|
691
|
-
hoverInputBox = false;
|
|
692
|
-
};
|
|
693
|
-
s(`.${id}`).onmouseover = () => {
|
|
694
|
-
hoverHistBox = true;
|
|
695
|
-
};
|
|
696
|
-
s(`.${id}`).onmouseout = () => {
|
|
697
|
-
hoverHistBox = false;
|
|
698
|
-
s(`.${inputSearchBoxId}`).focus();
|
|
699
|
-
};
|
|
693
|
+
prepend(`.btn-bar-modal-container-${id}`, html`<div class="hide">${inputInfoNode.outerHTML}</div>`);
|
|
694
|
+
if (s(`.slide-menu-top-bar-fix`)) s(`.slide-menu-top-bar-fix`).classList.add('hide');
|
|
700
695
|
}
|
|
701
696
|
};
|
|
702
697
|
|
|
@@ -708,14 +703,24 @@ const Modal = {
|
|
|
708
703
|
searchBoxHistoryOpen();
|
|
709
704
|
searchBoxCallBack(formDataInfoNode[0]);
|
|
710
705
|
};
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
706
|
+
|
|
707
|
+
const dismissSearchBox = () => {
|
|
708
|
+
if (unbindDocSearch) {
|
|
709
|
+
try {
|
|
710
|
+
unbindDocSearch();
|
|
711
|
+
} catch (e) {}
|
|
714
712
|
}
|
|
713
|
+
Modal.removeModal(searchBoxHistoryId);
|
|
714
|
+
if (s(`.slide-menu-top-bar-fix`)) s(`.slide-menu-top-bar-fix`).classList.remove('hide');
|
|
715
|
+
};
|
|
716
|
+
s('.top-bar-search-box').onblur = () => {
|
|
717
|
+
hoverFocusCtl.checkDismiss();
|
|
715
718
|
};
|
|
716
719
|
EventsUI.onClick(`.top-bar-search-box-container`, () => {
|
|
717
720
|
searchBoxHistoryOpen();
|
|
718
721
|
searchBoxCallBack(formDataInfoNode[0]);
|
|
722
|
+
const inputEl = s(`.${inputSearchBoxId}`);
|
|
723
|
+
if (inputEl && inputEl.focus) inputEl.focus();
|
|
719
724
|
});
|
|
720
725
|
|
|
721
726
|
const timePressDelay = 100;
|
|
@@ -881,6 +886,9 @@ const Modal = {
|
|
|
881
886
|
],
|
|
882
887
|
eventCallBack: () => {
|
|
883
888
|
if (s(`.top-bar-search-box`)) {
|
|
889
|
+
if (s(`.main-body-btn-ui-close`).classList.contains('hide')) {
|
|
890
|
+
s(`.main-body-btn-ui-open`).click();
|
|
891
|
+
}
|
|
884
892
|
s(`.top-bar-search-box`).blur();
|
|
885
893
|
s(`.top-bar-search-box`).focus();
|
|
886
894
|
s(`.top-bar-search-box`).select();
|
|
@@ -897,8 +905,10 @@ const Modal = {
|
|
|
897
905
|
barConfig.buttons.menu.disabled = true;
|
|
898
906
|
barConfig.buttons.close.disabled = true;
|
|
899
907
|
const id = 'bottom-bar';
|
|
900
|
-
if (
|
|
901
|
-
|
|
908
|
+
if (!this.Data[idModal].homeModals) this.Data[idModal].homeModals = [];
|
|
909
|
+
if (!this.Data[idModal].homeModals.includes(id)) {
|
|
910
|
+
this.Data[idModal].homeModals.push(id);
|
|
911
|
+
}
|
|
902
912
|
const html = async () => html`
|
|
903
913
|
<style>
|
|
904
914
|
.top-bar-search-box-container {
|
|
@@ -1048,13 +1058,80 @@ const Modal = {
|
|
|
1048
1058
|
|
|
1049
1059
|
{
|
|
1050
1060
|
htmls(`.action-btn-lang-render`, html` ${s('html').lang}`);
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1061
|
+
// old method
|
|
1062
|
+
// EventsUI.onClick(`.action-btn-lang`, () => {
|
|
1063
|
+
// let lang = 'en';
|
|
1064
|
+
// if (s('html').lang === 'en') lang = 'es';
|
|
1065
|
+
// if (s(`.dropdown-option-${lang}`))
|
|
1066
|
+
// DropDown.Tokens['settings-lang'].onClickEvents[`dropdown-option-${lang}`]();
|
|
1067
|
+
// else Translate.renderLang(lang);
|
|
1068
|
+
// });
|
|
1069
|
+
|
|
1070
|
+
// Open lightweight empty modal on language button, with shared dismiss logic
|
|
1071
|
+
EventsUI.onClick(
|
|
1072
|
+
`.action-btn-lang`,
|
|
1073
|
+
async () => {
|
|
1074
|
+
const id = 'action-btn-lang-modal';
|
|
1075
|
+
if (s(`.${id}`)) {
|
|
1076
|
+
return s(`.btn-close-${id}`).click();
|
|
1077
|
+
}
|
|
1078
|
+
const { barConfig } = await Themes[Css.currentTheme]();
|
|
1079
|
+
barConfig.buttons.maximize.disabled = true;
|
|
1080
|
+
barConfig.buttons.minimize.disabled = true;
|
|
1081
|
+
barConfig.buttons.restore.disabled = true;
|
|
1082
|
+
barConfig.buttons.menu.disabled = true;
|
|
1083
|
+
barConfig.buttons.close.disabled = false;
|
|
1084
|
+
await Modal.Render({
|
|
1085
|
+
id,
|
|
1086
|
+
barConfig,
|
|
1087
|
+
title: html`${renderViewTitle({
|
|
1088
|
+
icon: html`<i class="fas fa-language mini-title"></i>`,
|
|
1089
|
+
text: Translate.Render('select lang'),
|
|
1090
|
+
})}`,
|
|
1091
|
+
html: async () => html`${await Translate.RenderSetting('action-drop-modal' + id)}`,
|
|
1092
|
+
titleClass: 'mini-title',
|
|
1093
|
+
style: {
|
|
1094
|
+
resize: 'none',
|
|
1095
|
+
width: '100% !important',
|
|
1096
|
+
height: '350px !important',
|
|
1097
|
+
'max-width': '350px !important',
|
|
1098
|
+
'z-index': 7,
|
|
1099
|
+
},
|
|
1100
|
+
dragDisabled: true,
|
|
1101
|
+
maximize: true,
|
|
1102
|
+
heightBottomBar: 0,
|
|
1103
|
+
heightTopBar: originHeightTopBar,
|
|
1104
|
+
barMode: options.barMode,
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
// Move title inside the bar container to align with control buttons
|
|
1108
|
+
Modal.MoveTitleToBar(id);
|
|
1109
|
+
|
|
1110
|
+
// Position the language selection modal relative to the language button
|
|
1111
|
+
Modal.positionRelativeToAnchor({
|
|
1112
|
+
modalSelector: `.${id}`,
|
|
1113
|
+
anchorSelector: '.action-btn-lang',
|
|
1114
|
+
align: 'right',
|
|
1115
|
+
offset: { x: 0, y: 6 },
|
|
1116
|
+
autoVertical: true,
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
// Hover/focus controller uses the button as input anchor
|
|
1120
|
+
const hoverFocusCtl = EventsUI.HoverFocusController({
|
|
1121
|
+
inputSelector: `.action-btn-lang`,
|
|
1122
|
+
panelSelector: `.${id}`,
|
|
1123
|
+
onDismiss: () => Modal.removeModal(id),
|
|
1124
|
+
});
|
|
1125
|
+
hoverFocusCtl.bind();
|
|
1126
|
+
const unbindDoc = EventsUI.bindDismissOnDocumentClick({
|
|
1127
|
+
shouldStay: hoverFocusCtl.shouldStay,
|
|
1128
|
+
onDismiss: () => Modal.removeModal(id),
|
|
1129
|
+
anchors: [`.action-btn-lang`, `.${id}`],
|
|
1130
|
+
});
|
|
1131
|
+
Modal.Data[id].onCloseListener[`unbind-doc-${id}`] = () => unbindDoc();
|
|
1132
|
+
},
|
|
1133
|
+
{ context: 'modal', noGate: true, noLoading: true },
|
|
1134
|
+
);
|
|
1058
1135
|
}
|
|
1059
1136
|
|
|
1060
1137
|
{
|
|
@@ -1348,9 +1425,7 @@ const Modal = {
|
|
|
1348
1425
|
this.onHomeRouterEvent = async () => {
|
|
1349
1426
|
for (const keyModal of Object.keys(this.Data)) {
|
|
1350
1427
|
if (
|
|
1351
|
-
![idModal, 'main-body-top', 'main-body']
|
|
1352
|
-
.concat(options?.homeModals ? options.homeModals : [])
|
|
1353
|
-
.includes(keyModal)
|
|
1428
|
+
![idModal, 'main-body-top', 'main-body'].concat(this.Data[idModal]?.homeModals || []).includes(keyModal)
|
|
1354
1429
|
)
|
|
1355
1430
|
s(`.btn-close-${keyModal}`).click();
|
|
1356
1431
|
backMenuButtonEvent();
|
|
@@ -1429,42 +1504,139 @@ const Modal = {
|
|
|
1429
1504
|
default:
|
|
1430
1505
|
break;
|
|
1431
1506
|
}
|
|
1507
|
+
// Track drag position for consistency
|
|
1508
|
+
let dragPosition = { x: 0, y: 0 };
|
|
1509
|
+
|
|
1510
|
+
// Initialize drag options with proper bounds and smooth transitions
|
|
1432
1511
|
let dragOptions = {
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1512
|
+
handle: handle,
|
|
1513
|
+
bounds: {
|
|
1514
|
+
top: 0,
|
|
1515
|
+
left: 0,
|
|
1516
|
+
right: 0,
|
|
1517
|
+
bottom: 0,
|
|
1518
|
+
},
|
|
1519
|
+
preventDefault: true,
|
|
1520
|
+
position: { x: 0, y: 0 },
|
|
1521
|
+
onDragStart: () => {
|
|
1522
|
+
const modal = s(`.${idModal}`);
|
|
1523
|
+
if (!modal) return false; // Prevent drag if modal not found
|
|
1524
|
+
|
|
1525
|
+
// Store current position
|
|
1526
|
+
const computedStyle = window.getComputedStyle(modal);
|
|
1527
|
+
const matrix = new DOMMatrixReadOnly(computedStyle.transform);
|
|
1528
|
+
dragPosition = {
|
|
1529
|
+
x: matrix.m41 || 0,
|
|
1530
|
+
y: matrix.m42 || 0,
|
|
1531
|
+
};
|
|
1532
|
+
|
|
1533
|
+
modal.style.transition = 'none';
|
|
1534
|
+
modal.style.willChange = 'transform';
|
|
1535
|
+
return true; // Allow drag to start
|
|
1439
1536
|
},
|
|
1440
1537
|
onDrag: (data) => {
|
|
1441
|
-
|
|
1442
|
-
|
|
1538
|
+
// Update position based on drag delta
|
|
1539
|
+
dragPosition = { x: data.x, y: data.y };
|
|
1443
1540
|
},
|
|
1444
|
-
onDragEnd: (
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1541
|
+
onDragEnd: () => {
|
|
1542
|
+
const modal = s(`.${idModal}`);
|
|
1543
|
+
if (!modal) return;
|
|
1544
|
+
|
|
1545
|
+
modal.style.willChange = '';
|
|
1546
|
+
modal.style.transition = transition;
|
|
1547
|
+
|
|
1548
|
+
// Update drag instance with current position
|
|
1549
|
+
if (dragInstance) {
|
|
1550
|
+
dragInstance.updateOptions({
|
|
1551
|
+
position: dragPosition,
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// Notify listeners
|
|
1556
|
+
Object.keys(this.Data[idModal].onDragEndListener || {}).forEach((keyListener) => {
|
|
1557
|
+
this.Data[idModal].onDragEndListener[keyListener]?.();
|
|
1558
|
+
});
|
|
1451
1559
|
},
|
|
1452
1560
|
};
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1561
|
+
|
|
1562
|
+
let dragInstance = null;
|
|
1563
|
+
|
|
1564
|
+
// Initialize or update drag instance
|
|
1565
|
+
const setDragInstance = () => {
|
|
1566
|
+
if (options?.dragDisabled) {
|
|
1567
|
+
if (dragInstance) {
|
|
1568
|
+
dragInstance.destroy();
|
|
1569
|
+
dragInstance = null;
|
|
1570
|
+
}
|
|
1571
|
+
return null;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
const modal = s(`.${idModal}`);
|
|
1575
|
+
if (!modal) {
|
|
1576
|
+
console.warn(`Modal element .${idModal} not found for drag initialization`);
|
|
1577
|
+
return null;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// Ensure the modal has position: absolute for proper dragging
|
|
1581
|
+
if (window.getComputedStyle(modal).position !== 'absolute') {
|
|
1582
|
+
modal.style.position = 'absolute';
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// Clean up existing instance
|
|
1586
|
+
if (dragInstance) {
|
|
1587
|
+
dragInstance.destroy();
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
try {
|
|
1591
|
+
// Create new instance with updated options
|
|
1592
|
+
dragInstance = new Draggable(modal, dragOptions);
|
|
1593
|
+
return dragInstance;
|
|
1594
|
+
} catch (error) {
|
|
1595
|
+
console.error('Failed to initialize draggable:', error);
|
|
1596
|
+
return null;
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
// Expose method to update drag options
|
|
1456
1601
|
this.Data[idModal].setDragInstance = (updateDragOptions) => {
|
|
1457
|
-
|
|
1458
|
-
...dragOptions,
|
|
1459
|
-
|
|
1460
|
-
};
|
|
1602
|
+
if (updateDragOptions) {
|
|
1603
|
+
dragOptions = { ...dragOptions, ...updateDragOptions };
|
|
1604
|
+
}
|
|
1461
1605
|
dragInstance = setDragInstance();
|
|
1462
1606
|
this.Data[idModal].dragInstance = dragInstance;
|
|
1463
1607
|
this.Data[idModal].dragOptions = dragOptions;
|
|
1464
1608
|
};
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1609
|
+
// Initialize modal with proper transitions
|
|
1610
|
+
const modal = s(`.${idModal}`);
|
|
1611
|
+
if (modal) {
|
|
1612
|
+
// Initial state
|
|
1613
|
+
modal.style.transition = 'opacity 0.15s ease, transform 0.3s ease';
|
|
1614
|
+
modal.style.opacity = '0';
|
|
1615
|
+
|
|
1616
|
+
// Trigger fade-in after a small delay to allow initial render
|
|
1617
|
+
requestAnimationFrame(() => {
|
|
1618
|
+
if (!modal) return;
|
|
1619
|
+
modal.style.opacity = '1';
|
|
1620
|
+
|
|
1621
|
+
// Set final transition after initial animation completes
|
|
1622
|
+
setTimeout(() => {
|
|
1623
|
+
if (modal) {
|
|
1624
|
+
modal.style.transition = transition;
|
|
1625
|
+
|
|
1626
|
+
// Initialize drag after transitions are set
|
|
1627
|
+
if (!options.dragDisabled) {
|
|
1628
|
+
setDragInstance();
|
|
1629
|
+
if (!options.mode) {
|
|
1630
|
+
dragInstance.updateOptions({
|
|
1631
|
+
position: { x: 0, y: 0 },
|
|
1632
|
+
disabled: false, // Ensure drag is enabled after restore
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}, 150);
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1468
1640
|
|
|
1469
1641
|
const btnCloseEvent = () => {
|
|
1470
1642
|
Object.keys(this.Data[idModal].onCloseListener).map((keyListener) =>
|
|
@@ -1480,62 +1652,122 @@ const Modal = {
|
|
|
1480
1652
|
setTimeout(() => {
|
|
1481
1653
|
if (!s(`.${idModal}`)) return;
|
|
1482
1654
|
this.removeModal(idModal);
|
|
1483
|
-
//
|
|
1484
|
-
if (options.route)
|
|
1485
|
-
(
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
if (this.Data[subIdModal].options.route) {
|
|
1492
|
-
newPath = `${newPath}${this.Data[subIdModal].options.route}`;
|
|
1493
|
-
// console.warn('SET MODAL URI', newPath);
|
|
1494
|
-
setPath(newPath);
|
|
1495
|
-
this.setTopModalCallback(subIdModal);
|
|
1496
|
-
return setDocTitle({ ...options.RouterInstance, route: this.Data[subIdModal].options.route });
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
// console.warn('SET MODAL URI', newPath);
|
|
1500
|
-
setPath(`${newPath}${Modal.homeCid ? `?cid=${Modal.homeCid}` : ''}`);
|
|
1501
|
-
return setDocTitle({ ...options.RouterInstance, route: '' });
|
|
1502
|
-
}
|
|
1503
|
-
})();
|
|
1655
|
+
// Handle modal route change
|
|
1656
|
+
if (options.route) {
|
|
1657
|
+
closeModalRouteChangeEvent({
|
|
1658
|
+
route: options.route,
|
|
1659
|
+
RouterInstance: options.RouterInstance,
|
|
1660
|
+
homeCid: Modal.homeCid,
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1504
1663
|
}, 300);
|
|
1505
1664
|
};
|
|
1506
1665
|
s(`.btn-close-${idModal}`).onclick = btnCloseEvent;
|
|
1507
1666
|
|
|
1667
|
+
// Minimize button handler
|
|
1508
1668
|
s(`.btn-minimize-${idModal}`).onclick = () => {
|
|
1509
|
-
|
|
1510
|
-
|
|
1669
|
+
const modal = s(`.${idModal}`);
|
|
1670
|
+
if (!modal) return;
|
|
1671
|
+
|
|
1672
|
+
if (options.slideMenu) {
|
|
1673
|
+
delete this.Data[idModal].slideMenu;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
// Keep drag enabled when minimized
|
|
1677
|
+
if (dragInstance) {
|
|
1678
|
+
dragInstance.updateOptions({ disabled: false });
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// Set up transition
|
|
1682
|
+
modal.style.transition = 'height 0.3s ease, transform 0.3s ease';
|
|
1683
|
+
|
|
1684
|
+
// Update button states
|
|
1511
1685
|
s(`.btn-minimize-${idModal}`).style.display = 'none';
|
|
1512
1686
|
s(`.btn-maximize-${idModal}`).style.display = null;
|
|
1513
1687
|
s(`.btn-restore-${idModal}`).style.display = null;
|
|
1514
|
-
|
|
1515
|
-
|
|
1688
|
+
|
|
1689
|
+
// Collapse to header height
|
|
1690
|
+
const header = s(`.bar-default-modal-${idModal}`);
|
|
1691
|
+
if (header) {
|
|
1692
|
+
modal.style.height = `${header.clientHeight}px`;
|
|
1693
|
+
modal.style.overflow = 'hidden';
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// Restore transition after animation
|
|
1697
|
+
setTimeout(() => {
|
|
1698
|
+
if (modal) {
|
|
1699
|
+
modal.style.transition = transition;
|
|
1700
|
+
}
|
|
1701
|
+
}, 300);
|
|
1516
1702
|
};
|
|
1703
|
+
// Restore button handler
|
|
1517
1704
|
s(`.btn-restore-${idModal}`).onclick = () => {
|
|
1518
|
-
|
|
1519
|
-
|
|
1705
|
+
const modal = s(`.${idModal}`);
|
|
1706
|
+
if (!modal) return;
|
|
1707
|
+
|
|
1708
|
+
if (options.slideMenu) {
|
|
1709
|
+
delete this.Data[idModal].slideMenu;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
// Re-enable dragging
|
|
1713
|
+
if (dragInstance) {
|
|
1714
|
+
dragInstance.updateOptions({ disabled: false });
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// Set up transition
|
|
1718
|
+
modal.style.transition = 'all 0.3s ease';
|
|
1719
|
+
|
|
1720
|
+
// Update button states
|
|
1520
1721
|
s(`.btn-restore-${idModal}`).style.display = 'none';
|
|
1521
1722
|
s(`.btn-minimize-${idModal}`).style.display = null;
|
|
1522
1723
|
s(`.btn-maximize-${idModal}`).style.display = null;
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1724
|
+
|
|
1725
|
+
// Restore original dimensions and position
|
|
1726
|
+
modal.style.transform = '';
|
|
1727
|
+
modal.style.height = '';
|
|
1728
|
+
left = 0;
|
|
1729
|
+
width = 300;
|
|
1730
|
+
modal.style.left = `${left}px`;
|
|
1731
|
+
modal.style.width = `${width}px`;
|
|
1732
|
+
modal.style.overflow = '';
|
|
1733
|
+
|
|
1734
|
+
// Reset drag position
|
|
1735
|
+
dragPosition = { x: 0, y: 0 };
|
|
1736
|
+
|
|
1737
|
+
// Set new position
|
|
1738
|
+
modal.style.transform = `translate(0, 0)`;
|
|
1739
|
+
|
|
1740
|
+
// Adjust top position based on top bar visibility
|
|
1741
|
+
const heightDefaultTopBar = 40; // Default top bar height if not specified
|
|
1742
|
+
s(`.${idModal}`).style.top = s(`.main-body-btn-ui-close`).classList.contains('hide')
|
|
1743
|
+
? `0px`
|
|
1744
|
+
: `${options.heightTopBar ? options.heightTopBar : heightDefaultTopBar}px`;
|
|
1745
|
+
|
|
1746
|
+
// Re-enable drag after restore
|
|
1747
|
+
if (dragInstance) {
|
|
1748
|
+
dragInstance.updateOptions({
|
|
1749
|
+
position: { x: 0, y: 0 },
|
|
1750
|
+
disabled: false, // Ensure drag is enabled after restore
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
setTimeout(() => (s(`.${idModal}`) ? (s(`.${idModal}`).style.transition = transition) : null), 300);
|
|
1531
1754
|
};
|
|
1532
1755
|
s(`.btn-maximize-${idModal}`).onclick = () => {
|
|
1533
|
-
s(`.${idModal}`)
|
|
1534
|
-
|
|
1756
|
+
const modal = s(`.${idModal}`);
|
|
1757
|
+
if (!modal) return;
|
|
1758
|
+
|
|
1759
|
+
// Disable drag when maximizing
|
|
1760
|
+
if (dragInstance) {
|
|
1761
|
+
dragInstance.updateOptions({ disabled: true });
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
modal.style.transition = '0.3s';
|
|
1765
|
+
setTimeout(() => (modal ? (modal.style.transition = transition) : null), 300);
|
|
1766
|
+
|
|
1535
1767
|
s(`.btn-maximize-${idModal}`).style.display = 'none';
|
|
1536
1768
|
s(`.btn-restore-${idModal}`).style.display = null;
|
|
1537
1769
|
s(`.btn-minimize-${idModal}`).style.display = null;
|
|
1538
|
-
|
|
1770
|
+
modal.style.transform = null;
|
|
1539
1771
|
|
|
1540
1772
|
if (options.slideMenu) {
|
|
1541
1773
|
const idSlide = this.Data[options.slideMenu]['slide-menu']
|
|
@@ -1770,6 +2002,105 @@ const Modal = {
|
|
|
1770
2002
|
};
|
|
1771
2003
|
});
|
|
1772
2004
|
},
|
|
2005
|
+
// Move modal title element into the bar's render container so it aligns with control buttons
|
|
2006
|
+
/**
|
|
2007
|
+
* Position a modal relative to an anchor element
|
|
2008
|
+
* @param {Object} options - Positioning options
|
|
2009
|
+
* @param {string} options.modalSelector - CSS selector for the modal element
|
|
2010
|
+
* @param {string} options.anchorSelector - CSS selector for the anchor element
|
|
2011
|
+
* @param {Object} [options.offset={x: 0, y: 6}] - Offset from anchor
|
|
2012
|
+
* @param {string} [options.align='right'] - Horizontal alignment ('left' or 'right')
|
|
2013
|
+
* @param {boolean} [options.autoVertical=true] - Whether to automatically determine vertical position
|
|
2014
|
+
* @param {boolean} [options.placeAbove] - Force position above/below anchor (overrides autoVertical)
|
|
2015
|
+
*/
|
|
2016
|
+
positionRelativeToAnchor({
|
|
2017
|
+
modalSelector,
|
|
2018
|
+
anchorSelector,
|
|
2019
|
+
offset = { x: 0, y: 6 },
|
|
2020
|
+
align = 'right',
|
|
2021
|
+
autoVertical = true,
|
|
2022
|
+
placeAbove,
|
|
2023
|
+
}) {
|
|
2024
|
+
try {
|
|
2025
|
+
const modal = s(modalSelector);
|
|
2026
|
+
const anchor = s(anchorSelector);
|
|
2027
|
+
|
|
2028
|
+
if (!modal || !anchor || !anchor.getBoundingClientRect) return;
|
|
2029
|
+
|
|
2030
|
+
// First, position the modal near its final position but off-screen
|
|
2031
|
+
const arect = anchor.getBoundingClientRect();
|
|
2032
|
+
const vh = window.innerHeight;
|
|
2033
|
+
const vw = window.innerWidth;
|
|
2034
|
+
const safeMargin = 6;
|
|
2035
|
+
|
|
2036
|
+
// Determine vertical position
|
|
2037
|
+
let finalPlaceAbove = placeAbove;
|
|
2038
|
+
if (autoVertical && placeAbove === undefined) {
|
|
2039
|
+
const inBottomBar = anchor.closest && anchor.closest('.bottom-bar');
|
|
2040
|
+
const inTopBar = anchor.closest && anchor.closest('.slide-menu-top-bar');
|
|
2041
|
+
|
|
2042
|
+
if (inBottomBar) finalPlaceAbove = true;
|
|
2043
|
+
else if (inTopBar) finalPlaceAbove = false;
|
|
2044
|
+
else finalPlaceAbove = arect.top > vh / 2; // heuristic fallback
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
// Set initial position (slightly offset from final position)
|
|
2048
|
+
const initialOffset = finalPlaceAbove ? 20 : -20;
|
|
2049
|
+
modal.style.position = 'fixed';
|
|
2050
|
+
modal.style.opacity = '0';
|
|
2051
|
+
modal.style.transition = 'opacity 150ms ease-out, transform 150ms ease-out';
|
|
2052
|
+
|
|
2053
|
+
// Position near the anchor but slightly offset
|
|
2054
|
+
modal.style.top = `${finalPlaceAbove ? arect.top - 40 : arect.bottom + 20}px`;
|
|
2055
|
+
modal.style.left = `${align === 'right' ? arect.right - 200 : arect.left}px`;
|
|
2056
|
+
modal.style.transform = 'translateY(0)';
|
|
2057
|
+
|
|
2058
|
+
// Force reflow to ensure initial styles are applied
|
|
2059
|
+
modal.offsetHeight;
|
|
2060
|
+
|
|
2061
|
+
// Now calculate final position
|
|
2062
|
+
const mrect = modal.getBoundingClientRect();
|
|
2063
|
+
|
|
2064
|
+
// Calculate final top position
|
|
2065
|
+
const top = finalPlaceAbove ? arect.top - mrect.height - offset.y : arect.bottom + offset.y;
|
|
2066
|
+
|
|
2067
|
+
// Calculate final left position based on alignment
|
|
2068
|
+
let left;
|
|
2069
|
+
if (align === 'right') {
|
|
2070
|
+
left = arect.right - mrect.width - offset.x; // align right edges
|
|
2071
|
+
} else {
|
|
2072
|
+
left = arect.left + offset.x; // align left edges
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
// Ensure modal stays within viewport bounds
|
|
2076
|
+
left = Math.max(safeMargin, Math.min(left, vw - mrect.width - safeMargin));
|
|
2077
|
+
const finalTop = Math.max(safeMargin, Math.min(top, vh - mrect.height - safeMargin));
|
|
2078
|
+
|
|
2079
|
+
// Apply final position with smooth transition
|
|
2080
|
+
requestAnimationFrame(() => {
|
|
2081
|
+
modal.style.top = `${Math.round(finalTop)}px`;
|
|
2082
|
+
modal.style.left = `${Math.round(left)}px`;
|
|
2083
|
+
modal.style.opacity = '1';
|
|
2084
|
+
});
|
|
2085
|
+
} catch (e) {
|
|
2086
|
+
console.error('Error positioning modal:', e);
|
|
2087
|
+
}
|
|
2088
|
+
},
|
|
2089
|
+
|
|
2090
|
+
MoveTitleToBar(idModal) {
|
|
2091
|
+
try {
|
|
2092
|
+
const titleEl = s(`.title-modal-${idModal}`);
|
|
2093
|
+
const container = s(`.btn-bar-modal-container-render-${idModal}`);
|
|
2094
|
+
if (!titleEl || !container) return;
|
|
2095
|
+
const titleNode = titleEl.cloneNode(true);
|
|
2096
|
+
titleEl.remove();
|
|
2097
|
+
container.classList.add('in');
|
|
2098
|
+
container.classList.add('fll');
|
|
2099
|
+
container.appendChild(titleNode);
|
|
2100
|
+
} catch (e) {
|
|
2101
|
+
// non-fatal: keep default placement if structure not present
|
|
2102
|
+
}
|
|
2103
|
+
},
|
|
1773
2104
|
headerTitleHeight: 40,
|
|
1774
2105
|
actionBtnCenter: function () {
|
|
1775
2106
|
if (!s(`.btn-close-modal-menu`).classList.contains('hide')) {
|