@topconsultnpm/sdkui-react 6.22.0-dev3.9 → 6.22.0-dev4.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/lib/components/base/TMAlert.js +6 -8
- package/lib/components/base/TMAreaManager.js +53 -55
- package/lib/components/base/TMButton.js +4 -2
- package/lib/components/base/TMCustomButton.js +40 -48
- package/lib/components/base/TMDataGrid.js +219 -219
- package/lib/components/base/TMPanel.js +173 -170
- package/lib/components/base/TMSpinner.d.ts +2 -1
- package/lib/components/base/TMSpinner.js +9 -7
- package/lib/components/base/TMTabControl.d.ts +5 -0
- package/lib/components/base/TMTabControl.js +18 -7
- package/lib/components/base/TMTreeView.js +20 -0
- package/lib/components/base/TMWaitPanel.js +1 -1
- package/lib/components/base/waitPanel/TMWaitPanelConfirm.d.ts +12 -0
- package/lib/components/base/waitPanel/TMWaitPanelConfirm.js +185 -0
- package/lib/components/base/waitPanel/TMWaitPanelManager.d.ts +169 -0
- package/lib/components/base/waitPanel/TMWaitPanelManager.js +320 -0
- package/lib/components/base/waitPanel/TMWaitPanelView.d.ts +53 -0
- package/lib/components/base/waitPanel/TMWaitPanelView.js +274 -0
- package/lib/components/base/waitPanel/index.d.ts +1 -0
- package/lib/components/base/waitPanel/index.js +1 -0
- package/lib/components/choosers/TMPathChooser.js +17 -34
- package/lib/components/editors/TMTextBox.d.ts +4 -0
- package/lib/components/editors/TMTextBox.js +99 -11
- package/lib/components/features/archive/TMArchive.js +7 -2
- package/lib/components/features/assistant/TMHelpOnLineChat.d.ts +29 -0
- package/lib/components/features/assistant/TMHelpOnLineChat.js +1384 -0
- package/lib/components/features/assistant/TMToppyDraggableHelpCenter.js +2 -2
- package/lib/components/features/assistant/dataset/Designer-Suggested-Questions.json +102 -0
- package/lib/components/features/assistant/dataset/Login-Suggested-Questions.json +32 -0
- package/lib/components/features/assistant/dataset/Surfer-Suggested-Questions.json +102 -0
- package/lib/components/features/documents/TMBatchUpdateForm.js +3 -4
- package/lib/components/features/documents/TMCopyToFolderForm.js +53 -84
- package/lib/components/features/documents/TMDcmtForm.js +50 -61
- package/lib/components/features/documents/TMDcmtIcon.d.ts +1 -3
- package/lib/components/features/documents/TMDcmtIcon.js +3 -5
- package/lib/components/features/documents/TMDcmtPreview.js +5 -3
- package/lib/components/features/documents/TMDcmtTasks.js +2 -9
- package/lib/components/features/documents/TMFileUploader.js +26 -26
- package/lib/components/features/documents/TMMasterDetailDcmts.d.ts +2 -0
- package/lib/components/features/documents/TMMasterDetailDcmts.js +53 -148
- package/lib/components/features/documents/TMMasterInfoFields.js +3 -2
- package/lib/components/features/documents/TMMergeToPdfForm.js +49 -54
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +14 -15
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +1 -1
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +3 -3
- package/lib/components/features/documents/TMRelationViewer.d.ts +13 -14
- package/lib/components/features/documents/TMRelationViewer.js +206 -143
- package/lib/components/features/documents/relationTreeState.d.ts +32 -0
- package/lib/components/features/documents/relationTreeState.js +89 -0
- package/lib/components/features/search/TMSearch.js +3 -4
- package/lib/components/features/search/TMSearchQueryPanel.js +0 -4
- package/lib/components/features/search/TMSearchResult.d.ts +5 -0
- package/lib/components/features/search/TMSearchResult.js +39 -19
- package/lib/components/features/search/TMTreeSelector.js +23 -27
- package/lib/components/features/search/TMViewHistoryDcmt.js +14 -25
- package/lib/components/features/tasks/TMTasksView.js +91 -90
- package/lib/components/features/wg/TMWGsCopyMoveForm.js +91 -91
- package/lib/components/forms/TMResultDialog.js +1 -1
- package/lib/components/grids/TMBlogsPost.js +92 -96
- package/lib/components/index.d.ts +5 -1
- package/lib/components/index.js +4 -1
- package/lib/components/layout/panelManager/TMPanelManagerContainer.js +16 -15
- package/lib/components/layout/panelManager/TMPanelManagerContext.d.ts +1 -0
- package/lib/components/layout/panelManager/TMPanelManagerContext.js +5 -1
- package/lib/components/layout/panelManager/TMPanelManagerLoader.d.ts +6 -0
- package/lib/components/layout/panelManager/TMPanelManagerLoader.js +18 -0
- package/lib/components/layout/panelManager/TMPanelManagerToolbar.js +4 -8
- package/lib/components/layout/panelManager/TMPanelManagerWithPersistenceProvider.d.ts +1 -1
- package/lib/components/layout/panelManager/TMPanelManagerWithPersistenceProvider.js +2 -1
- package/lib/components/pages/TMPage.js +29 -31
- package/lib/components/query/TMQueryEditor.d.ts +3 -0
- package/lib/components/query/TMQueryEditor.js +2 -1
- package/lib/components/sidebar/TMHeader.d.ts +1 -0
- package/lib/components/sidebar/TMHeader.js +2 -2
- package/lib/helper/Enum_Localizator.js +1 -0
- package/lib/helper/SDKUI_Globals.d.ts +3 -0
- package/lib/helper/SDKUI_Globals.js +3 -0
- package/lib/helper/SDKUI_Localizator.d.ts +21 -0
- package/lib/helper/SDKUI_Localizator.js +210 -0
- package/lib/helper/ZipManager.d.ts +1 -1
- package/lib/helper/ZipManager.js +2 -1
- package/lib/helper/dcmtsHelper.d.ts +2 -0
- package/lib/helper/dcmtsHelper.js +3 -1
- package/lib/hooks/useCheckInOutOperations.d.ts +0 -6
- package/lib/hooks/useCheckInOutOperations.js +14 -34
- package/lib/hooks/useDcmtOperations.d.ts +3 -19
- package/lib/hooks/useDcmtOperations.js +160 -222
- package/lib/hooks/useDocumentOperations.d.ts +1 -0
- package/lib/hooks/useDocumentOperations.js +14 -25
- package/lib/hooks/useMultiMasterDetailDcmts.d.ts +0 -15
- package/lib/hooks/useMultiMasterDetailDcmts.js +65 -67
- package/lib/hooks/usePreventFileDrop.js +9 -0
- package/lib/hooks/useWorkflowApprove.js +33 -17
- package/lib/services/caseflow/CaseFlowService.d.ts +36 -6
- package/lib/services/caseflow/CaseFlowService.js +55 -14
- package/package.json +69 -69
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useRef } from 'react';
|
|
3
|
+
import styled, { keyframes } from 'styled-components';
|
|
4
|
+
import toppy from '../../../assets/Toppy-generico.png';
|
|
5
|
+
import { AppModules, SDK_Globals } from '@topconsultnpm/sdk-ts';
|
|
6
|
+
import { getAppModuleGradient, SDKUI_Localizator } from '../../../helper';
|
|
7
|
+
/** Modale di conferma dell'interruzione, sopra al wait panel: non usa TMMessageBox perche' deve stare sopra gli overlay DevExtreme. */
|
|
8
|
+
/** Gradiente dell'applicazione per la barra del titolo. SDK_Globals.tmSession lancia senza sessione. */
|
|
9
|
+
const getTitleBarGradient = () => {
|
|
10
|
+
let appModuleID = AppModules.SURFER;
|
|
11
|
+
try {
|
|
12
|
+
appModuleID = SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER;
|
|
13
|
+
}
|
|
14
|
+
catch { /* nessuna sessione: resta il default */ }
|
|
15
|
+
return getAppModuleGradient(appModuleID);
|
|
16
|
+
};
|
|
17
|
+
const fadeIn = keyframes `
|
|
18
|
+
from { opacity: 0; }
|
|
19
|
+
to { opacity: 1; }
|
|
20
|
+
`;
|
|
21
|
+
const StyledConfirmOverlay = styled.div `
|
|
22
|
+
display: none;
|
|
23
|
+
position: fixed;
|
|
24
|
+
inset: 0;
|
|
25
|
+
z-index: ${props => props.$zIndex};
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
|
|
29
|
+
&::before {
|
|
30
|
+
content: '';
|
|
31
|
+
position: absolute;
|
|
32
|
+
inset: 0;
|
|
33
|
+
background-color: rgba(17, 24, 39, 0.35);
|
|
34
|
+
animation: ${fadeIn} 120ms ease-out;
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
const StyledConfirmCard = styled.div `
|
|
38
|
+
position: relative;
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
width: 420px;
|
|
42
|
+
max-width: calc(100% - 24px);
|
|
43
|
+
max-height: calc(100% - 24px);
|
|
44
|
+
overflow-y: auto;
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
background: #ffffff;
|
|
47
|
+
border-radius: 16px;
|
|
48
|
+
box-shadow: 0 18px 48px rgba(16, 24, 40, 0.28);
|
|
49
|
+
overflow: hidden; /* tiene la barra del titolo dentro il raggio della card */
|
|
50
|
+
font-family: inherit;
|
|
51
|
+
`;
|
|
52
|
+
const StyledTitleBar = styled.div `
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: row;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: space-between;
|
|
57
|
+
gap: 8px;
|
|
58
|
+
padding: 6px 8px 6px 14px;
|
|
59
|
+
background: ${props => props.$gradient};
|
|
60
|
+
color: #ffffff;
|
|
61
|
+
cursor: move;
|
|
62
|
+
user-select: none;
|
|
63
|
+
touch-action: none; /* necessario perche' i pointer event di drag arrivino su touch */
|
|
64
|
+
`;
|
|
65
|
+
const StyledTitle = styled.div `
|
|
66
|
+
font-size: 14px;
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
white-space: nowrap;
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
text-overflow: ellipsis;
|
|
71
|
+
`;
|
|
72
|
+
const StyledCloseButton = styled.button `
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
justify-content: center;
|
|
76
|
+
width: 26px;
|
|
77
|
+
height: 26px;
|
|
78
|
+
flex: 0 0 auto;
|
|
79
|
+
padding: 0;
|
|
80
|
+
border: none;
|
|
81
|
+
border-radius: 6px;
|
|
82
|
+
background: transparent;
|
|
83
|
+
color: inherit;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
|
|
86
|
+
&:hover { background: rgba(255, 255, 255, 0.18); }
|
|
87
|
+
&:active { background: rgba(255, 255, 255, 0.28); }
|
|
88
|
+
`;
|
|
89
|
+
const StyledBody = styled.div `
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-direction: row;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 16px;
|
|
94
|
+
padding: 20px 24px 0 24px;
|
|
95
|
+
`;
|
|
96
|
+
const StyledToppyClip = styled.div `
|
|
97
|
+
height: 61px;
|
|
98
|
+
width: 68px;
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
flex: 0 0 auto;
|
|
101
|
+
`;
|
|
102
|
+
const StyledToppy = styled.img `
|
|
103
|
+
display: block;
|
|
104
|
+
width: 68px;
|
|
105
|
+
height: 79px;
|
|
106
|
+
user-select: none;
|
|
107
|
+
-webkit-user-drag: none;
|
|
108
|
+
`;
|
|
109
|
+
const StyledQuestion = styled.div `
|
|
110
|
+
flex: 1;
|
|
111
|
+
min-width: 0;
|
|
112
|
+
font-size: 17px;
|
|
113
|
+
font-weight: 600;
|
|
114
|
+
color: #1f2937;
|
|
115
|
+
line-height: 1.3;
|
|
116
|
+
overflow-wrap: anywhere;
|
|
117
|
+
`;
|
|
118
|
+
const StyledFooter = styled.div `
|
|
119
|
+
display: flex;
|
|
120
|
+
flex-direction: row;
|
|
121
|
+
justify-content: flex-end;
|
|
122
|
+
gap: 10px;
|
|
123
|
+
padding: 18px 24px;
|
|
124
|
+
`;
|
|
125
|
+
const StyledButton = styled.button `
|
|
126
|
+
min-width: 92px;
|
|
127
|
+
padding: 9px 18px;
|
|
128
|
+
border-radius: 10px;
|
|
129
|
+
font-size: 13px;
|
|
130
|
+
font-family: inherit;
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
transition: background-color 120ms ease, border-color 120ms ease;
|
|
133
|
+
|
|
134
|
+
border: 1px solid ${props => (props.$primary ? '#b3172f' : '#d5dbe5')};
|
|
135
|
+
background: ${props => (props.$primary ? '#c81e39' : '#ffffff')};
|
|
136
|
+
color: ${props => (props.$primary ? '#ffffff' : '#374151')};
|
|
137
|
+
|
|
138
|
+
&:hover { background: ${props => (props.$primary ? '#b3172f' : '#f3f5f9')}; }
|
|
139
|
+
&:active { transform: translateY(1px); }
|
|
140
|
+
`;
|
|
141
|
+
const IconClose = () => (_jsxs("svg", { width: '15', height: '15', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2.4', strokeLinecap: 'round', children: [_jsx("line", { x1: '18', y1: '6', x2: '6', y2: '18' }), _jsx("line", { x1: '6', y1: '6', x2: '18', y2: '18' })] }));
|
|
142
|
+
/** Il ref punta all'overlay, che il manager mostra e nasconde via `style.display`. */
|
|
143
|
+
const TMWaitPanelConfirm = forwardRef(({ zIndex = 2147483602, onYes, onNo }, ref) => {
|
|
144
|
+
const cardRef = useRef(null);
|
|
145
|
+
// Scostamento fra il puntatore e l'angolo della card, fissato al primo pointerdown.
|
|
146
|
+
const grabRef = useRef(null);
|
|
147
|
+
const handlePointerDown = (e) => {
|
|
148
|
+
const card = cardRef.current;
|
|
149
|
+
if (!card || e.button !== 0)
|
|
150
|
+
return;
|
|
151
|
+
// Niente drag dalla X: con la pointer capture il click finirebbe sulla barra e non sul bottone.
|
|
152
|
+
if (e.target.closest('button'))
|
|
153
|
+
return;
|
|
154
|
+
// La card nasce centrata dal flex: al primo drag passo a coordinate esplicite.
|
|
155
|
+
const rect = card.getBoundingClientRect();
|
|
156
|
+
card.style.position = 'absolute';
|
|
157
|
+
card.style.margin = '0';
|
|
158
|
+
// Pixel interi: una posizione frazionaria fa ricampionare Toppy e lo sgrana.
|
|
159
|
+
card.style.left = `${Math.round(rect.left)}px`;
|
|
160
|
+
card.style.top = `${Math.round(rect.top)}px`;
|
|
161
|
+
grabRef.current = { dx: e.clientX - rect.left, dy: e.clientY - rect.top };
|
|
162
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
163
|
+
};
|
|
164
|
+
const handlePointerMove = (e) => {
|
|
165
|
+
const grab = grabRef.current;
|
|
166
|
+
const card = cardRef.current;
|
|
167
|
+
if (!grab || !card)
|
|
168
|
+
return;
|
|
169
|
+
// Clamp al viewport: una volta uscita non si riuscirebbe piu' ad afferrarla.
|
|
170
|
+
const maxLeft = Math.max(0, window.innerWidth - card.offsetWidth);
|
|
171
|
+
const maxTop = Math.max(0, window.innerHeight - card.offsetHeight);
|
|
172
|
+
card.style.left = `${Math.round(Math.min(Math.max(0, e.clientX - grab.dx), maxLeft))}px`;
|
|
173
|
+
card.style.top = `${Math.round(Math.min(Math.max(0, e.clientY - grab.dy), maxTop))}px`;
|
|
174
|
+
};
|
|
175
|
+
const handlePointerUp = (e) => {
|
|
176
|
+
if (!grabRef.current)
|
|
177
|
+
return;
|
|
178
|
+
grabRef.current = null;
|
|
179
|
+
if (e.currentTarget.hasPointerCapture(e.pointerId))
|
|
180
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
181
|
+
};
|
|
182
|
+
return (_jsx(StyledConfirmOverlay, { ref: ref, $zIndex: zIndex, children: _jsxs(StyledConfirmCard, { ref: cardRef, role: 'alertdialog', children: [_jsxs(StyledTitleBar, { $gradient: getTitleBarGradient(), onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerCancel: handlePointerUp, children: [_jsx(StyledTitle, { children: SDKUI_Localizator.Abort }), _jsx(StyledCloseButton, { type: 'button', "aria-label": SDKUI_Localizator.No, onClick: onNo, children: _jsx(IconClose, {}) })] }), _jsxs(StyledBody, { children: [_jsx(StyledToppyClip, { children: _jsx(StyledToppy, { src: toppy, alt: '', draggable: false }) }), _jsx(StyledQuestion, { children: SDKUI_Localizator.Abort_Confirm })] }), _jsxs(StyledFooter, { children: [_jsx(StyledButton, { type: 'button', $primary: true, onClick: onYes, children: SDKUI_Localizator.Yes }), _jsx(StyledButton, { type: 'button', onClick: onNo, children: SDKUI_Localizator.No })] })] }) }));
|
|
183
|
+
});
|
|
184
|
+
TMWaitPanelConfirm.displayName = 'TMWaitPanelConfirm';
|
|
185
|
+
export default TMWaitPanelConfirm;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/** Wait panel imperativo ad alte prestazioni. */
|
|
2
|
+
export interface ITMWaitPanelOptions {
|
|
3
|
+
/** Titolo mostrato in alto. */
|
|
4
|
+
title?: string;
|
|
5
|
+
/** Totale degli elementi da elaborare. Puo' essere impostato dopo con `setMax()`. */
|
|
6
|
+
max?: number;
|
|
7
|
+
/** Sottotitolo statico, usato quando non e' valorizzato `subtitleTemplate`. */
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
/** Template del sottotitolo: `{{0}}` = elemento corrente, `{{1}}` = totale. */
|
|
10
|
+
subtitleTemplate?: string;
|
|
11
|
+
/** Mostra il bottone di interruzione (e abilita ESC). */
|
|
12
|
+
cancelable?: boolean;
|
|
13
|
+
/** Mostra la riga "trascorsi / rimanenti". Default: true. */
|
|
14
|
+
showEta?: boolean;
|
|
15
|
+
/** Mostra le tre pill numeriche ok / warning / error, sempre visibili anche a 0. Default: true. */
|
|
16
|
+
showCounters?: boolean;
|
|
17
|
+
/** Riserva la seconda barra, quella dell'operazione di dettaglio */
|
|
18
|
+
showSecondary?: boolean;
|
|
19
|
+
/** Testo iniziale della barra secondaria. */
|
|
20
|
+
secondaryText?: string;
|
|
21
|
+
/** Didascalie fisse sopra le due barre, es. "Documenti" e "File corrente". Omesse: nessuna riga. */
|
|
22
|
+
primaryLabel?: string;
|
|
23
|
+
secondaryLabel?: string;
|
|
24
|
+
/** Invocato quando l'utente interrompe */
|
|
25
|
+
onAbort?: () => void;
|
|
26
|
+
}
|
|
27
|
+
export interface ITMWaitPanelCounters {
|
|
28
|
+
ok: number;
|
|
29
|
+
warning: number;
|
|
30
|
+
error: number;
|
|
31
|
+
}
|
|
32
|
+
export interface ITMWaitPanelUpdate {
|
|
33
|
+
title?: string;
|
|
34
|
+
subtitle?: string;
|
|
35
|
+
value?: number;
|
|
36
|
+
max?: number;
|
|
37
|
+
ok?: number;
|
|
38
|
+
warning?: number;
|
|
39
|
+
error?: number;
|
|
40
|
+
}
|
|
41
|
+
/** Aggiornamento della barra secondaria: i campi omessi restano invariati. */
|
|
42
|
+
export interface ITMWaitPanelSecondaryUpdate {
|
|
43
|
+
value?: number;
|
|
44
|
+
max?: number;
|
|
45
|
+
text?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface ITMWaitPanelHandle {
|
|
48
|
+
/** True quando l'utente ha interrotto l'operazione. */
|
|
49
|
+
readonly aborted: boolean;
|
|
50
|
+
/** AbortController del pannello, da passare alle chiamate annullabili. */
|
|
51
|
+
readonly abortController: AbortController;
|
|
52
|
+
readonly signal: AbortSignal;
|
|
53
|
+
readonly value: number;
|
|
54
|
+
readonly max: number;
|
|
55
|
+
readonly counters: ITMWaitPanelCounters;
|
|
56
|
+
readonly isOpen: boolean;
|
|
57
|
+
/** True mentre e' aperta la richiesta di conferma dell'interruzione. */
|
|
58
|
+
readonly paused: boolean;
|
|
59
|
+
/** Avanza il contatore (default +1). Da attendere con `await`: resta pendente durante la conferma di interruzione. */
|
|
60
|
+
step(delta?: number): Promise<void>;
|
|
61
|
+
/** Come il gate di `step()`, per chi deve attendere la pausa senza avanzare. */
|
|
62
|
+
waitWhilePaused(): Promise<void>;
|
|
63
|
+
setValue(value: number): void;
|
|
64
|
+
setMax(max: number): void;
|
|
65
|
+
setTitle(title: string): void;
|
|
66
|
+
/** Sottotitolo esplicito: sovrascrive `subtitleTemplate`. `undefined` ripristina il template. */
|
|
67
|
+
setSubtitle(subtitle: string | undefined): void;
|
|
68
|
+
/** Incrementa il contatore verde (default +1). */
|
|
69
|
+
ok(delta?: number): void;
|
|
70
|
+
/** Incrementa il contatore giallo (default +1). */
|
|
71
|
+
warning(delta?: number): void;
|
|
72
|
+
/** Incrementa il contatore rosso (default +1). */
|
|
73
|
+
error(delta?: number): void;
|
|
74
|
+
/** Aggiornamento cumulativo: i contatori qui sono valori assoluti, non incrementi. */
|
|
75
|
+
update(patch: ITMWaitPanelUpdate): void;
|
|
76
|
+
/** Aggiorna la barra secondaria. Senza `showSecondary` non ha effetto. */
|
|
77
|
+
setSecondary(patch: ITMWaitPanelSecondaryUpdate): void;
|
|
78
|
+
close(): void;
|
|
79
|
+
}
|
|
80
|
+
declare class TMWaitPanelInstance implements ITMWaitPanelHandle {
|
|
81
|
+
readonly abortController: AbortController;
|
|
82
|
+
private readonly options;
|
|
83
|
+
private readonly container;
|
|
84
|
+
private readonly root;
|
|
85
|
+
private nodes;
|
|
86
|
+
private closed;
|
|
87
|
+
private readonly elapsedLabel;
|
|
88
|
+
private readonly remainingLabel;
|
|
89
|
+
private _paused;
|
|
90
|
+
private pauseGate;
|
|
91
|
+
private resolvePauseGate;
|
|
92
|
+
private pausedTotalMs;
|
|
93
|
+
private pauseStartedAt;
|
|
94
|
+
private readonly startTime;
|
|
95
|
+
private rafId;
|
|
96
|
+
private tickerId;
|
|
97
|
+
private _value;
|
|
98
|
+
private _max;
|
|
99
|
+
private _ok;
|
|
100
|
+
private _warning;
|
|
101
|
+
private _error;
|
|
102
|
+
private _title;
|
|
103
|
+
private _subtitleOverride;
|
|
104
|
+
private _secondaryValue;
|
|
105
|
+
private _secondaryMax;
|
|
106
|
+
private _secondaryText;
|
|
107
|
+
private lastPercentText;
|
|
108
|
+
private lastSubtitleText;
|
|
109
|
+
private lastElapsedText;
|
|
110
|
+
private lastRemainingText;
|
|
111
|
+
private lastOk;
|
|
112
|
+
private lastWarning;
|
|
113
|
+
private lastError;
|
|
114
|
+
private lastTitleText;
|
|
115
|
+
private lastSecondaryPercentText;
|
|
116
|
+
private lastSecondaryText;
|
|
117
|
+
constructor(options: ITMWaitPanelOptions, zIndex: number);
|
|
118
|
+
get aborted(): boolean;
|
|
119
|
+
get signal(): AbortSignal;
|
|
120
|
+
get value(): number;
|
|
121
|
+
get max(): number;
|
|
122
|
+
get counters(): ITMWaitPanelCounters;
|
|
123
|
+
get isOpen(): boolean;
|
|
124
|
+
get paused(): boolean;
|
|
125
|
+
waitWhilePaused: () => Promise<void>;
|
|
126
|
+
step: (delta?: number) => Promise<void>;
|
|
127
|
+
setValue: (value: number) => void;
|
|
128
|
+
setMax: (max: number) => void;
|
|
129
|
+
setTitle: (title: string) => void;
|
|
130
|
+
setSubtitle: (subtitle: string | undefined) => void;
|
|
131
|
+
ok: (delta?: number) => void;
|
|
132
|
+
warning: (delta?: number) => void;
|
|
133
|
+
error: (delta?: number) => void;
|
|
134
|
+
setSecondary: (patch: ITMWaitPanelSecondaryUpdate) => void;
|
|
135
|
+
update: (patch: ITMWaitPanelUpdate) => void;
|
|
136
|
+
close: () => void;
|
|
137
|
+
private handleMount;
|
|
138
|
+
private handleKeyDown;
|
|
139
|
+
/** Interrompi non annulla subito: mette in pausa il loop e chiede conferma. */
|
|
140
|
+
private handleAbort;
|
|
141
|
+
/** Risposta Si: interrompe davvero e lascia ripartire il loop, che uscira' subito. */
|
|
142
|
+
private confirmAbort;
|
|
143
|
+
private pause;
|
|
144
|
+
/** Risposta No, oppure chiusura del pannello: sblocca il loop e riprende. */
|
|
145
|
+
private resume;
|
|
146
|
+
private computeSubtitle;
|
|
147
|
+
/** Programma al massimo un flush per frame. E' l'unico costo pagato da `step()`. */
|
|
148
|
+
private schedule;
|
|
149
|
+
/** Riversa sul DOM lo stato corrente (barra, percentuale, testi, contatori), saltando cio' che non e' cambiato. */
|
|
150
|
+
private flush;
|
|
151
|
+
/** Riversa sul DOM la seconda barra. Invocata solo quando la view l'ha renderizzata. */
|
|
152
|
+
private flushSecondary;
|
|
153
|
+
/** Aggiorna la riga dei tempi: trascorso al netto delle pause e stima del rimanente. */
|
|
154
|
+
private flushEta;
|
|
155
|
+
}
|
|
156
|
+
export declare class TMWaitPanelManager {
|
|
157
|
+
private static _panels;
|
|
158
|
+
/** Apre un wait panel e restituisce l'handle con cui pilotarlo. */
|
|
159
|
+
static show(options?: ITMWaitPanelOptions): ITMWaitPanelHandle;
|
|
160
|
+
/** Pannello aperto piu' recente, se presente. */
|
|
161
|
+
static get current(): ITMWaitPanelHandle | undefined;
|
|
162
|
+
/** True finche' almeno un wait panel e' aperto: flag "operazione massiva in corso" per i refresh periodici. */
|
|
163
|
+
static get isBusy(): boolean;
|
|
164
|
+
/** Chiude tutti i pannelli aperti (rete di sicurezza in caso di unmount della pagina). */
|
|
165
|
+
static closeAll(): void;
|
|
166
|
+
/** @internal */
|
|
167
|
+
static _unregister(panel: TMWaitPanelInstance): void;
|
|
168
|
+
}
|
|
169
|
+
export default TMWaitPanelManager;
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import { SDKUI_Localizator } from '../../../helper';
|
|
4
|
+
import TMWaitPanelView from './TMWaitPanelView';
|
|
5
|
+
const Z_INDEX_BASE = 2147483601;
|
|
6
|
+
const RESOLVED = Promise.resolve();
|
|
7
|
+
const pad2 = (n) => (n < 10 ? '0' + n : String(n));
|
|
8
|
+
/** Formatta una durata in millisecondi come "42s", "3m 07s", "1h 12m". */
|
|
9
|
+
const formatDuration = (ms) => {
|
|
10
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1000));
|
|
11
|
+
if (totalSeconds < 60)
|
|
12
|
+
return `${totalSeconds}s`;
|
|
13
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
14
|
+
if (totalMinutes < 60)
|
|
15
|
+
return `${totalMinutes}m ${pad2(totalSeconds % 60)}s`;
|
|
16
|
+
return `${Math.floor(totalMinutes / 60)}h ${pad2(totalMinutes % 60)}m`;
|
|
17
|
+
};
|
|
18
|
+
class TMWaitPanelInstance {
|
|
19
|
+
constructor(options, zIndex) {
|
|
20
|
+
this.abortController = new AbortController();
|
|
21
|
+
this.nodes = null;
|
|
22
|
+
this.closed = false;
|
|
23
|
+
this.elapsedLabel = SDKUI_Localizator.Elapsed;
|
|
24
|
+
this.remainingLabel = SDKUI_Localizator.Remaining;
|
|
25
|
+
// Pausa in attesa della conferma di interruzione.
|
|
26
|
+
this._paused = false;
|
|
27
|
+
this.pauseGate = null;
|
|
28
|
+
this.resolvePauseGate = null;
|
|
29
|
+
this.pausedTotalMs = 0;
|
|
30
|
+
this.pauseStartedAt = 0;
|
|
31
|
+
this.startTime = performance.now();
|
|
32
|
+
this.rafId = 0;
|
|
33
|
+
this._value = 0;
|
|
34
|
+
this._max = 0;
|
|
35
|
+
this._ok = 0;
|
|
36
|
+
this._warning = 0;
|
|
37
|
+
this._error = 0;
|
|
38
|
+
this._secondaryValue = 0;
|
|
39
|
+
this._secondaryMax = 0;
|
|
40
|
+
// Ultimi valori scritti sul DOM: servono a non riscrivere testo identico.
|
|
41
|
+
this.lastPercentText = '';
|
|
42
|
+
this.lastSubtitleText = '';
|
|
43
|
+
this.lastElapsedText = '';
|
|
44
|
+
this.lastRemainingText = '';
|
|
45
|
+
this.lastOk = 0;
|
|
46
|
+
this.lastWarning = 0;
|
|
47
|
+
this.lastError = 0;
|
|
48
|
+
this.lastTitleText = '';
|
|
49
|
+
this.lastSecondaryPercentText = '';
|
|
50
|
+
this.lastSecondaryText = '';
|
|
51
|
+
this.waitWhilePaused = () => this.pauseGate ?? RESOLVED;
|
|
52
|
+
this.step = (delta = 1) => { this._value += delta; this.schedule(); return this.pauseGate ?? RESOLVED; };
|
|
53
|
+
this.setValue = (value) => { this._value = value; this.schedule(); };
|
|
54
|
+
this.setMax = (max) => { this._max = max; this.schedule(); };
|
|
55
|
+
this.setTitle = (title) => { this._title = title; this.schedule(); };
|
|
56
|
+
this.setSubtitle = (subtitle) => { this._subtitleOverride = subtitle; this.schedule(); };
|
|
57
|
+
this.ok = (delta = 1) => { this._ok += delta; this.schedule(); };
|
|
58
|
+
this.warning = (delta = 1) => { this._warning += delta; this.schedule(); };
|
|
59
|
+
this.error = (delta = 1) => { this._error += delta; this.schedule(); };
|
|
60
|
+
this.setSecondary = (patch) => {
|
|
61
|
+
if (patch.value !== undefined)
|
|
62
|
+
this._secondaryValue = patch.value;
|
|
63
|
+
if (patch.max !== undefined)
|
|
64
|
+
this._secondaryMax = patch.max;
|
|
65
|
+
if (patch.text !== undefined)
|
|
66
|
+
this._secondaryText = patch.text;
|
|
67
|
+
this.schedule();
|
|
68
|
+
};
|
|
69
|
+
this.update = (patch) => {
|
|
70
|
+
if (patch.title !== undefined)
|
|
71
|
+
this._title = patch.title;
|
|
72
|
+
if (patch.subtitle !== undefined)
|
|
73
|
+
this._subtitleOverride = patch.subtitle;
|
|
74
|
+
if (patch.value !== undefined)
|
|
75
|
+
this._value = patch.value;
|
|
76
|
+
if (patch.max !== undefined)
|
|
77
|
+
this._max = patch.max;
|
|
78
|
+
if (patch.ok !== undefined)
|
|
79
|
+
this._ok = patch.ok;
|
|
80
|
+
if (patch.warning !== undefined)
|
|
81
|
+
this._warning = patch.warning;
|
|
82
|
+
if (patch.error !== undefined)
|
|
83
|
+
this._error = patch.error;
|
|
84
|
+
this.schedule();
|
|
85
|
+
};
|
|
86
|
+
this.close = () => {
|
|
87
|
+
if (this.closed)
|
|
88
|
+
return;
|
|
89
|
+
this.closed = true;
|
|
90
|
+
// Sblocca un loop eventualmente fermo in attesa della conferma.
|
|
91
|
+
this.resume();
|
|
92
|
+
if (this.rafId) {
|
|
93
|
+
cancelAnimationFrame(this.rafId);
|
|
94
|
+
this.rafId = 0;
|
|
95
|
+
}
|
|
96
|
+
if (this.tickerId !== undefined) {
|
|
97
|
+
clearInterval(this.tickerId);
|
|
98
|
+
this.tickerId = undefined;
|
|
99
|
+
}
|
|
100
|
+
document.removeEventListener('keydown', this.handleKeyDown, true);
|
|
101
|
+
// Smonto fuori dal render corrente: close() puo' arrivare durante un render.
|
|
102
|
+
this.container.style.display = 'none';
|
|
103
|
+
const root = this.root;
|
|
104
|
+
const container = this.container;
|
|
105
|
+
setTimeout(() => { root.unmount(); container.remove(); }, 0);
|
|
106
|
+
TMWaitPanelManager._unregister(this);
|
|
107
|
+
};
|
|
108
|
+
// #endregion
|
|
109
|
+
this.handleMount = (nodes) => {
|
|
110
|
+
if (this.closed)
|
|
111
|
+
return;
|
|
112
|
+
this.nodes = nodes;
|
|
113
|
+
// Il chiamante puo' aver gia' avanzato prima del montaggio.
|
|
114
|
+
this.flush();
|
|
115
|
+
};
|
|
116
|
+
this.handleKeyDown = (e) => {
|
|
117
|
+
if (this.closed)
|
|
118
|
+
return;
|
|
119
|
+
// Con la conferma aperta la tastiera risponde a lei: Esc = No, Invio = Si.
|
|
120
|
+
if (this._paused) {
|
|
121
|
+
if (e.key === 'Escape') {
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
e.stopPropagation();
|
|
124
|
+
this.resume();
|
|
125
|
+
}
|
|
126
|
+
else if (e.key === 'Enter') {
|
|
127
|
+
e.preventDefault();
|
|
128
|
+
e.stopPropagation();
|
|
129
|
+
this.confirmAbort();
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (e.key !== 'Escape')
|
|
134
|
+
return;
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
e.stopPropagation();
|
|
137
|
+
this.handleAbort();
|
|
138
|
+
};
|
|
139
|
+
/** Interrompi non annulla subito: mette in pausa il loop e chiede conferma. */
|
|
140
|
+
this.handleAbort = () => {
|
|
141
|
+
if (this.closed || this.aborted || this._paused)
|
|
142
|
+
return;
|
|
143
|
+
this.pause();
|
|
144
|
+
};
|
|
145
|
+
/** Risposta Si: interrompe davvero e lascia ripartire il loop, che uscira' subito. */
|
|
146
|
+
this.confirmAbort = () => {
|
|
147
|
+
if (this.closed || this.aborted)
|
|
148
|
+
return;
|
|
149
|
+
this.abortController.abort();
|
|
150
|
+
const abortButton = this.nodes?.abortButton;
|
|
151
|
+
if (abortButton) {
|
|
152
|
+
abortButton.disabled = true;
|
|
153
|
+
abortButton.textContent = SDKUI_Localizator.Aborting;
|
|
154
|
+
}
|
|
155
|
+
this.options.onAbort?.();
|
|
156
|
+
this.resume();
|
|
157
|
+
};
|
|
158
|
+
/** Risposta No, oppure chiusura del pannello: sblocca il loop e riprende. */
|
|
159
|
+
this.resume = () => {
|
|
160
|
+
if (!this._paused)
|
|
161
|
+
return;
|
|
162
|
+
this._paused = false;
|
|
163
|
+
this.pausedTotalMs += performance.now() - this.pauseStartedAt;
|
|
164
|
+
if (this.nodes)
|
|
165
|
+
this.nodes.confirm.style.display = 'none';
|
|
166
|
+
const resolve = this.resolvePauseGate;
|
|
167
|
+
this.pauseGate = null;
|
|
168
|
+
this.resolvePauseGate = null;
|
|
169
|
+
resolve?.();
|
|
170
|
+
this.schedule();
|
|
171
|
+
};
|
|
172
|
+
/** Programma al massimo un flush per frame. E' l'unico costo pagato da `step()`. */
|
|
173
|
+
this.schedule = () => {
|
|
174
|
+
if (this.closed || this.rafId || !this.nodes)
|
|
175
|
+
return;
|
|
176
|
+
this.rafId = requestAnimationFrame(this.flush);
|
|
177
|
+
};
|
|
178
|
+
/** Riversa sul DOM lo stato corrente (barra, percentuale, testi, contatori), saltando cio' che non e' cambiato. */
|
|
179
|
+
this.flush = () => {
|
|
180
|
+
this.rafId = 0;
|
|
181
|
+
const nodes = this.nodes;
|
|
182
|
+
if (this.closed || !nodes)
|
|
183
|
+
return;
|
|
184
|
+
const ratio = this._max > 0 ? Math.min(1, Math.max(0, this._value / this._max)) : 0;
|
|
185
|
+
// scaleX invece di width: resta sul compositor, niente layout.
|
|
186
|
+
nodes.fill.style.transform = `scaleX(${ratio})`;
|
|
187
|
+
const percentText = `${(ratio * 100).toFixed(2)}%`;
|
|
188
|
+
if (percentText !== this.lastPercentText) {
|
|
189
|
+
this.lastPercentText = percentText;
|
|
190
|
+
nodes.percent.textContent = percentText;
|
|
191
|
+
}
|
|
192
|
+
if (this._title !== this.lastTitleText) {
|
|
193
|
+
this.lastTitleText = this._title;
|
|
194
|
+
nodes.title.textContent = this._title;
|
|
195
|
+
}
|
|
196
|
+
const subtitleText = this.computeSubtitle();
|
|
197
|
+
if (subtitleText !== this.lastSubtitleText) {
|
|
198
|
+
this.lastSubtitleText = subtitleText;
|
|
199
|
+
nodes.subtitle.textContent = subtitleText;
|
|
200
|
+
}
|
|
201
|
+
if (this._ok !== this.lastOk) {
|
|
202
|
+
this.lastOk = this._ok;
|
|
203
|
+
nodes.okValue.textContent = String(this._ok);
|
|
204
|
+
}
|
|
205
|
+
if (this._warning !== this.lastWarning) {
|
|
206
|
+
this.lastWarning = this._warning;
|
|
207
|
+
nodes.warningValue.textContent = String(this._warning);
|
|
208
|
+
}
|
|
209
|
+
if (this._error !== this.lastError) {
|
|
210
|
+
this.lastError = this._error;
|
|
211
|
+
nodes.errorValue.textContent = String(this._error);
|
|
212
|
+
}
|
|
213
|
+
if (nodes.secondaryFill)
|
|
214
|
+
this.flushSecondary(nodes);
|
|
215
|
+
if (this.options.showEta ?? true)
|
|
216
|
+
this.flushEta(nodes, ratio);
|
|
217
|
+
};
|
|
218
|
+
this.options = options;
|
|
219
|
+
this._max = options.max ?? 0;
|
|
220
|
+
this._title = options.title ?? '';
|
|
221
|
+
this._secondaryText = options.secondaryText ?? '';
|
|
222
|
+
this.lastTitleText = this._title;
|
|
223
|
+
this.lastSecondaryText = this._secondaryText;
|
|
224
|
+
this.lastSubtitleText = this.computeSubtitle();
|
|
225
|
+
this.container = document.createElement('div');
|
|
226
|
+
document.body.appendChild(this.container);
|
|
227
|
+
this.root = ReactDOM.createRoot(this.container);
|
|
228
|
+
this.root.render(_jsx(TMWaitPanelView, { title: this._title, subtitle: this.lastSubtitleText, cancelable: options.cancelable ?? false, showEta: options.showEta ?? true, showCounters: options.showCounters ?? true, showSecondary: options.showSecondary ?? false, secondaryText: this._secondaryText, primaryLabel: options.primaryLabel ?? '', secondaryLabel: options.secondaryLabel ?? '', zIndex: zIndex, onAbort: this.handleAbort, onConfirmYes: this.confirmAbort, onConfirmNo: this.resume, onMount: this.handleMount }));
|
|
229
|
+
if (options.cancelable ?? false)
|
|
230
|
+
document.addEventListener('keydown', this.handleKeyDown, true);
|
|
231
|
+
if (options.showEta ?? true)
|
|
232
|
+
this.tickerId = setInterval(this.schedule, 500);
|
|
233
|
+
}
|
|
234
|
+
// #region API pubblica
|
|
235
|
+
get aborted() { return this.abortController.signal.aborted; }
|
|
236
|
+
get signal() { return this.abortController.signal; }
|
|
237
|
+
get value() { return this._value; }
|
|
238
|
+
get max() { return this._max; }
|
|
239
|
+
get counters() { return { ok: this._ok, warning: this._warning, error: this._error }; }
|
|
240
|
+
get isOpen() { return !this.closed; }
|
|
241
|
+
get paused() { return this._paused; }
|
|
242
|
+
pause() {
|
|
243
|
+
if (this._paused)
|
|
244
|
+
return;
|
|
245
|
+
this._paused = true;
|
|
246
|
+
this.pauseStartedAt = performance.now();
|
|
247
|
+
this.pauseGate = new Promise(resolve => { this.resolvePauseGate = resolve; });
|
|
248
|
+
if (this.nodes)
|
|
249
|
+
this.nodes.confirm.style.display = 'flex';
|
|
250
|
+
}
|
|
251
|
+
computeSubtitle() {
|
|
252
|
+
if (this._subtitleOverride !== undefined)
|
|
253
|
+
return this._subtitleOverride;
|
|
254
|
+
const template = this.options.subtitleTemplate;
|
|
255
|
+
if (template)
|
|
256
|
+
return template.replace('{{0}}', String(this._value)).replace('{{1}}', String(this._max));
|
|
257
|
+
return this.options.subtitle ?? '';
|
|
258
|
+
}
|
|
259
|
+
/** Riversa sul DOM la seconda barra. Invocata solo quando la view l'ha renderizzata. */
|
|
260
|
+
flushSecondary(nodes) {
|
|
261
|
+
const ratio = this._secondaryMax > 0 ? Math.min(1, Math.max(0, this._secondaryValue / this._secondaryMax)) : 0;
|
|
262
|
+
nodes.secondaryFill.style.transform = `scaleX(${ratio})`;
|
|
263
|
+
const percentText = `${(ratio * 100).toFixed(2)}%`;
|
|
264
|
+
if (percentText !== this.lastSecondaryPercentText) {
|
|
265
|
+
this.lastSecondaryPercentText = percentText;
|
|
266
|
+
nodes.secondaryPercent.textContent = percentText;
|
|
267
|
+
}
|
|
268
|
+
if (this._secondaryText !== this.lastSecondaryText) {
|
|
269
|
+
this.lastSecondaryText = this._secondaryText;
|
|
270
|
+
nodes.secondaryText.textContent = this._secondaryText;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/** Aggiorna la riga dei tempi: trascorso al netto delle pause e stima del rimanente. */
|
|
274
|
+
flushEta(nodes, ratio) {
|
|
275
|
+
const pausedMs = this.pausedTotalMs + (this._paused ? performance.now() - this.pauseStartedAt : 0);
|
|
276
|
+
const elapsedMs = performance.now() - this.startTime - pausedMs;
|
|
277
|
+
const elapsedText = `${formatDuration(elapsedMs)} ${this.elapsedLabel}`;
|
|
278
|
+
if (elapsedText !== this.lastElapsedText) {
|
|
279
|
+
this.lastElapsedText = elapsedText;
|
|
280
|
+
nodes.elapsed.textContent = elapsedText;
|
|
281
|
+
}
|
|
282
|
+
// Sotto 1s la stima balla troppo.
|
|
283
|
+
let remainingText = '';
|
|
284
|
+
if (ratio > 0 && ratio < 1 && elapsedMs > 1000) {
|
|
285
|
+
remainingText = `${formatDuration(elapsedMs / ratio - elapsedMs)} ${this.remainingLabel}`;
|
|
286
|
+
}
|
|
287
|
+
if (remainingText !== this.lastRemainingText) {
|
|
288
|
+
this.lastRemainingText = remainingText;
|
|
289
|
+
nodes.remaining.textContent = remainingText;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
export class TMWaitPanelManager {
|
|
294
|
+
/** Apre un wait panel e restituisce l'handle con cui pilotarlo. */
|
|
295
|
+
static show(options = {}) {
|
|
296
|
+
const panel = new TMWaitPanelInstance(options, Z_INDEX_BASE + this._panels.length);
|
|
297
|
+
this._panels.push(panel);
|
|
298
|
+
return panel;
|
|
299
|
+
}
|
|
300
|
+
/** Pannello aperto piu' recente, se presente. */
|
|
301
|
+
static get current() {
|
|
302
|
+
return this._panels[this._panels.length - 1];
|
|
303
|
+
}
|
|
304
|
+
/** True finche' almeno un wait panel e' aperto: flag "operazione massiva in corso" per i refresh periodici. */
|
|
305
|
+
static get isBusy() {
|
|
306
|
+
return this._panels.length > 0;
|
|
307
|
+
}
|
|
308
|
+
/** Chiude tutti i pannelli aperti (rete di sicurezza in caso di unmount della pagina). */
|
|
309
|
+
static closeAll() {
|
|
310
|
+
[...this._panels].forEach(panel => panel.close());
|
|
311
|
+
}
|
|
312
|
+
/** @internal */
|
|
313
|
+
static _unregister(panel) {
|
|
314
|
+
const index = this._panels.indexOf(panel);
|
|
315
|
+
if (index >= 0)
|
|
316
|
+
this._panels.splice(index, 1);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
TMWaitPanelManager._panels = [];
|
|
320
|
+
export default TMWaitPanelManager;
|