react-os-shell 0.1.0
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/LICENSE +21 -0
- package/README.md +242 -0
- package/dist/Calculator-BNBRNV4P.js +184 -0
- package/dist/Calculator-BNBRNV4P.js.map +1 -0
- package/dist/Calendar-5EYUVGUU.js +423 -0
- package/dist/Calendar-5EYUVGUU.js.map +1 -0
- package/dist/Checkers-MIAHIKJH.js +214 -0
- package/dist/Checkers-MIAHIKJH.js.map +1 -0
- package/dist/Chess-C5BY45NA.js +190 -0
- package/dist/Chess-C5BY45NA.js.map +1 -0
- package/dist/ConfirmDialog-ZP4AHVUD.js +3 -0
- package/dist/ConfirmDialog-ZP4AHVUD.js.map +1 -0
- package/dist/CurrencyConverter-TYPU2IRF.js +223 -0
- package/dist/CurrencyConverter-TYPU2IRF.js.map +1 -0
- package/dist/Email-JEYYJ3YV.js +1835 -0
- package/dist/Email-JEYYJ3YV.js.map +1 -0
- package/dist/Game2048-3RH3ELRD.js +191 -0
- package/dist/Game2048-3RH3ELRD.js.map +1 -0
- package/dist/GeminiChat-BXLBJFT4.js +184 -0
- package/dist/GeminiChat-BXLBJFT4.js.map +1 -0
- package/dist/Minesweeper-VQGLAZON.js +270 -0
- package/dist/Minesweeper-VQGLAZON.js.map +1 -0
- package/dist/Notepad-YTZRCAXX.js +389 -0
- package/dist/Notepad-YTZRCAXX.js.map +1 -0
- package/dist/PomodoroTimer-HARIJN4S.js +196 -0
- package/dist/PomodoroTimer-HARIJN4S.js.map +1 -0
- package/dist/Spreadsheet-IOKEDNS6.js +446 -0
- package/dist/Spreadsheet-IOKEDNS6.js.map +1 -0
- package/dist/Sudoku-XHLYCEVT.js +197 -0
- package/dist/Sudoku-XHLYCEVT.js.map +1 -0
- package/dist/Tetris-ZHCZYL24.js +243 -0
- package/dist/Tetris-ZHCZYL24.js.map +1 -0
- package/dist/Weather-ROZ7TRNW.js +310 -0
- package/dist/Weather-ROZ7TRNW.js.map +1 -0
- package/dist/apps/index.d.ts +55 -0
- package/dist/apps/index.js +48 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/chunk-5O2KEISQ.js +155 -0
- package/dist/chunk-5O2KEISQ.js.map +1 -0
- package/dist/chunk-D7PYW2QS.js +265 -0
- package/dist/chunk-D7PYW2QS.js.map +1 -0
- package/dist/chunk-GP4Y3VCB.js +806 -0
- package/dist/chunk-GP4Y3VCB.js.map +1 -0
- package/dist/chunk-NSU7OHPC.js +39 -0
- package/dist/chunk-NSU7OHPC.js.map +1 -0
- package/dist/chunk-PDFQNHW7.js +24 -0
- package/dist/chunk-PDFQNHW7.js.map +1 -0
- package/dist/chunk-RFTLYCSF.js +144 -0
- package/dist/chunk-RFTLYCSF.js.map +1 -0
- package/dist/chunk-SVBID2P6.js +142 -0
- package/dist/chunk-SVBID2P6.js.map +1 -0
- package/dist/chunk-TFGOLXGD.js +41 -0
- package/dist/chunk-TFGOLXGD.js.map +1 -0
- package/dist/chunk-WIJ45SYD.js +120 -0
- package/dist/chunk-WIJ45SYD.js.map +1 -0
- package/dist/chunk-WQIS72NL.js +1470 -0
- package/dist/chunk-WQIS72NL.js.map +1 -0
- package/dist/index.d.ts +642 -0
- package/dist/index.js +3443 -0
- package/dist/index.js.map +1 -0
- package/dist/sounds-NT4DEZGD.js +3 -0
- package/dist/sounds-NT4DEZGD.js.map +1 -0
- package/dist/styles.css +174 -0
- package/dist/types-CFIZ1_xt.d.ts +67 -0
- package/package.json +76 -0
|
@@ -0,0 +1,1470 @@
|
|
|
1
|
+
import { confirm } from './chunk-RFTLYCSF.js';
|
|
2
|
+
import 'axios';
|
|
3
|
+
import { createContext, useRef, useEffect, useContext, useState, useSyncExternalStore, isValidElement, cloneElement, useCallback, Suspense, useMemo } from 'react';
|
|
4
|
+
import { useLocation } from 'react-router-dom';
|
|
5
|
+
import { createPortal } from 'react-dom';
|
|
6
|
+
import { useQueryClient, useQuery } from '@tanstack/react-query';
|
|
7
|
+
import { XMarkIcon } from '@heroicons/react/24/outline';
|
|
8
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
9
|
+
|
|
10
|
+
// src/shell/nav-types.ts
|
|
11
|
+
function isSection(item) {
|
|
12
|
+
return "items" in item;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/shell-config/nav.tsx
|
|
16
|
+
var _navIcons = {};
|
|
17
|
+
var navIcons = new Proxy(_navIcons, {
|
|
18
|
+
get(_t, k) {
|
|
19
|
+
return _navIcons[k];
|
|
20
|
+
},
|
|
21
|
+
has(_t, k) {
|
|
22
|
+
return k in _navIcons;
|
|
23
|
+
},
|
|
24
|
+
ownKeys() {
|
|
25
|
+
return Object.keys(_navIcons);
|
|
26
|
+
},
|
|
27
|
+
getOwnPropertyDescriptor(_t, k) {
|
|
28
|
+
if (k in _navIcons) return { configurable: true, enumerable: true, value: _navIcons[k] };
|
|
29
|
+
return void 0;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
function setShellNavIcons(icons) {
|
|
33
|
+
for (const k of Object.keys(_navIcons)) delete _navIcons[k];
|
|
34
|
+
Object.assign(_navIcons, icons);
|
|
35
|
+
}
|
|
36
|
+
var sectionIcons = {};
|
|
37
|
+
var navSections = [];
|
|
38
|
+
var startMenuCategories = { erp: [], system: [], virtual: [] };
|
|
39
|
+
|
|
40
|
+
// src/windowRegistry/types.ts
|
|
41
|
+
function isPageEntry(entry) {
|
|
42
|
+
return "component" in entry;
|
|
43
|
+
}
|
|
44
|
+
function isEntityEntry(entry) {
|
|
45
|
+
return "endpoint" in entry;
|
|
46
|
+
}
|
|
47
|
+
var _registry = {};
|
|
48
|
+
function setShellWindowRegistry(registry) {
|
|
49
|
+
_registry = registry;
|
|
50
|
+
}
|
|
51
|
+
var WINDOW_REGISTRY = new Proxy({}, {
|
|
52
|
+
get(_t, prop) {
|
|
53
|
+
return _registry[prop];
|
|
54
|
+
},
|
|
55
|
+
has(_t, prop) {
|
|
56
|
+
return prop in _registry;
|
|
57
|
+
},
|
|
58
|
+
ownKeys() {
|
|
59
|
+
return Object.keys(_registry);
|
|
60
|
+
},
|
|
61
|
+
getOwnPropertyDescriptor(_t, prop) {
|
|
62
|
+
if (prop in _registry) {
|
|
63
|
+
return { configurable: true, enumerable: true, value: _registry[prop] };
|
|
64
|
+
}
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
var _apiClient = null;
|
|
69
|
+
function setShellApiClient(instance) {
|
|
70
|
+
_apiClient = instance;
|
|
71
|
+
}
|
|
72
|
+
var apiClient = new Proxy({}, {
|
|
73
|
+
get(_t, prop) {
|
|
74
|
+
if (!_apiClient) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`react-os-shell: apiClient.${String(prop)}() called before setShellApiClient(). Wire your axios instance once at app startup.`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return _apiClient[prop];
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
var client_default = apiClient;
|
|
83
|
+
|
|
84
|
+
// src/utils/glass.ts
|
|
85
|
+
function getMenuOpacity() {
|
|
86
|
+
try {
|
|
87
|
+
const val = getComputedStyle(document.documentElement).getPropertyValue("--menu-opacity")?.trim();
|
|
88
|
+
if (val) return parseFloat(val);
|
|
89
|
+
} catch {
|
|
90
|
+
}
|
|
91
|
+
return 0.95;
|
|
92
|
+
}
|
|
93
|
+
function isDarkTheme() {
|
|
94
|
+
try {
|
|
95
|
+
return document.documentElement.getAttribute("data-theme") === "dark";
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function glassStyle(opacity) {
|
|
101
|
+
const o = opacity ?? getMenuOpacity();
|
|
102
|
+
if (isDarkTheme()) {
|
|
103
|
+
return {
|
|
104
|
+
background: `linear-gradient(135deg, rgba(30,30,46,${o * 0.85}) 0%, rgba(24,24,37,${o * 0.75}) 50%, rgba(30,30,46,${o * 0.85}) 100%)`,
|
|
105
|
+
backdropFilter: "blur(40px) saturate(1.6)",
|
|
106
|
+
WebkitBackdropFilter: "blur(40px) saturate(1.6)",
|
|
107
|
+
border: "1px solid rgba(255,255,255,0.08)",
|
|
108
|
+
boxShadow: "inset 0 1px 0 rgba(255,255,255,0.06), inset 0 -1px 0 rgba(0,0,0,0.4), 0 8px 32px rgba(0,0,0,0.5), 0 2px 8px rgba(0,0,0,0.3)"
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
background: `linear-gradient(135deg, rgba(255,255,255,${o * 0.85}) 0%, rgba(255,255,255,${o * 0.65}) 50%, rgba(255,255,255,${o * 0.75}) 100%)`,
|
|
113
|
+
backdropFilter: "blur(40px) saturate(1.8)",
|
|
114
|
+
WebkitBackdropFilter: "blur(40px) saturate(1.8)",
|
|
115
|
+
border: "1px solid rgba(255,255,255,0.35)",
|
|
116
|
+
boxShadow: "inset 0 1px 0 rgba(255,255,255,0.4), inset 0 -1px 0 rgba(255,255,255,0.1), 0 8px 32px rgba(0,0,0,0.15), 0 2px 8px rgba(0,0,0,0.08)"
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
var GLASS_DIVIDER = "border-white/20";
|
|
120
|
+
var GLASS_INPUT_BG = "bg-white/15";
|
|
121
|
+
function getDensity() {
|
|
122
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--menu-density")?.trim() || "normal";
|
|
123
|
+
}
|
|
124
|
+
function PopupMenu({ children, style, className = "", onClose, minWidth = 180 }) {
|
|
125
|
+
const ref = useRef(null);
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!onClose) return;
|
|
128
|
+
const handleClick = (e) => {
|
|
129
|
+
if (e.target.closest("[data-menu-toggle]")) return;
|
|
130
|
+
if (ref.current && !ref.current.contains(e.target)) onClose();
|
|
131
|
+
};
|
|
132
|
+
const handleKey = (e) => {
|
|
133
|
+
if (e.key === "Escape") onClose();
|
|
134
|
+
};
|
|
135
|
+
window.addEventListener("pointerdown", handleClick);
|
|
136
|
+
window.addEventListener("keydown", handleKey);
|
|
137
|
+
return () => {
|
|
138
|
+
window.removeEventListener("pointerdown", handleClick);
|
|
139
|
+
window.removeEventListener("keydown", handleKey);
|
|
140
|
+
};
|
|
141
|
+
}, [onClose]);
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
const el = ref.current;
|
|
144
|
+
if (!el) return;
|
|
145
|
+
requestAnimationFrame(() => {
|
|
146
|
+
const rect = el.getBoundingClientRect();
|
|
147
|
+
const margin = 8;
|
|
148
|
+
if (rect.right > window.innerWidth - margin) {
|
|
149
|
+
const overflow = rect.right - window.innerWidth + margin;
|
|
150
|
+
if (el.style.left) {
|
|
151
|
+
el.style.left = `${parseFloat(el.style.left) - overflow}px`;
|
|
152
|
+
} else if (!el.style.right) {
|
|
153
|
+
el.style.left = `${rect.left - overflow}px`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (rect.left < margin) {
|
|
157
|
+
if (el.style.left) {
|
|
158
|
+
el.style.left = `${margin}px`;
|
|
159
|
+
} else if (el.style.right) {
|
|
160
|
+
el.style.right = `${window.innerWidth - rect.width - margin}px`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (rect.bottom > window.innerHeight - margin) {
|
|
164
|
+
const overflow = rect.bottom - window.innerHeight + margin;
|
|
165
|
+
if (el.style.top) {
|
|
166
|
+
el.style.top = `${parseFloat(el.style.top) - overflow}px`;
|
|
167
|
+
} else if (el.style.bottom) {
|
|
168
|
+
el.style.bottom = `${margin}px`;
|
|
169
|
+
} else {
|
|
170
|
+
el.style.top = `${rect.top - overflow}px`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (rect.top < margin) {
|
|
174
|
+
if (el.style.top) {
|
|
175
|
+
el.style.top = `${margin}px`;
|
|
176
|
+
} else if (el.style.bottom) {
|
|
177
|
+
el.style.bottom = `${window.innerHeight - rect.height - margin}px`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
const tight = getDensity() === "tight";
|
|
183
|
+
return /* @__PURE__ */ jsxs(
|
|
184
|
+
"div",
|
|
185
|
+
{
|
|
186
|
+
ref,
|
|
187
|
+
className: `fixed z-[400] rounded-2xl ${tight ? "py-1" : "py-1.5"} ${className}`,
|
|
188
|
+
style: { minWidth, animation: "popup-in 0.12s ease-out", ...glassStyle(), ...style },
|
|
189
|
+
children: [
|
|
190
|
+
children,
|
|
191
|
+
/* @__PURE__ */ jsx("style", { children: `@keyframes popup-in { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }` })
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
function PopupMenuItem({ onClick, children, className = "", danger, disabled }) {
|
|
197
|
+
const tight = getDensity() === "tight";
|
|
198
|
+
return /* @__PURE__ */ jsx(
|
|
199
|
+
"button",
|
|
200
|
+
{
|
|
201
|
+
onClick,
|
|
202
|
+
disabled,
|
|
203
|
+
className: `w-full flex items-center gap-2 text-left transition-colors rounded-lg mx-auto
|
|
204
|
+
${danger ? "text-red-600 hover:bg-red-50" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700"}
|
|
205
|
+
${disabled ? "opacity-40 cursor-not-allowed" : ""}
|
|
206
|
+
${className}`,
|
|
207
|
+
style: {
|
|
208
|
+
width: "calc(100% - 8px)",
|
|
209
|
+
marginLeft: 4,
|
|
210
|
+
marginRight: 4,
|
|
211
|
+
fontSize: "var(--menu-font-size, 14px)",
|
|
212
|
+
paddingLeft: "var(--menu-padding-x, 1rem)",
|
|
213
|
+
paddingRight: "var(--menu-padding-x, 1rem)",
|
|
214
|
+
paddingTop: tight ? "0.25rem" : "var(--menu-padding-y, 0.5rem)",
|
|
215
|
+
paddingBottom: tight ? "0.25rem" : "var(--menu-padding-y, 0.5rem)"
|
|
216
|
+
},
|
|
217
|
+
children
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
function PopupMenuDivider() {
|
|
222
|
+
const tight = getDensity() === "tight";
|
|
223
|
+
return /* @__PURE__ */ jsx("div", { className: `border-t ${GLASS_DIVIDER} ${tight ? "my-0.5" : "my-1"} mx-3` });
|
|
224
|
+
}
|
|
225
|
+
function PopupMenuLabel({ children }) {
|
|
226
|
+
const tight = getDensity() === "tight";
|
|
227
|
+
return /* @__PURE__ */ jsx(
|
|
228
|
+
"div",
|
|
229
|
+
{
|
|
230
|
+
className: "text-[10px] font-medium text-gray-400 uppercase tracking-wider",
|
|
231
|
+
style: {
|
|
232
|
+
paddingLeft: "var(--menu-padding-x, 1rem)",
|
|
233
|
+
paddingRight: "var(--menu-padding-x, 1rem)",
|
|
234
|
+
paddingTop: tight ? "0.125rem" : "0.25rem",
|
|
235
|
+
paddingBottom: tight ? "0.125rem" : "0.25rem"
|
|
236
|
+
},
|
|
237
|
+
children
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
var ModalIdContext = createContext("");
|
|
242
|
+
function useWidgetSettings(handler) {
|
|
243
|
+
const id = useContext(ModalIdContext);
|
|
244
|
+
useEffect(() => {
|
|
245
|
+
if (!id) return;
|
|
246
|
+
const listener = (e) => {
|
|
247
|
+
if (e.detail === id) handler();
|
|
248
|
+
};
|
|
249
|
+
window.addEventListener("widget-open-settings", listener);
|
|
250
|
+
return () => window.removeEventListener("widget-open-settings", listener);
|
|
251
|
+
}, [handler, id]);
|
|
252
|
+
}
|
|
253
|
+
var _extraMenuItems = {};
|
|
254
|
+
function useWindowMenuItem(label, onClick, icon) {
|
|
255
|
+
const id = useContext(ModalIdContext);
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
if (!id) return;
|
|
258
|
+
if (!_extraMenuItems[id]) _extraMenuItems[id] = [];
|
|
259
|
+
const item = { label, icon, onClick };
|
|
260
|
+
_extraMenuItems[id].push(item);
|
|
261
|
+
return () => {
|
|
262
|
+
_extraMenuItems[id] = (_extraMenuItems[id] || []).filter((i) => i !== item);
|
|
263
|
+
};
|
|
264
|
+
}, [id, label, onClick, icon]);
|
|
265
|
+
}
|
|
266
|
+
function WindowTitle({ title }) {
|
|
267
|
+
const ref = useRef(null);
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
const handle = setTimeout(() => {
|
|
270
|
+
const panel = ref.current?.closest("[data-modal-id]");
|
|
271
|
+
const id = panel?.getAttribute("data-modal-id");
|
|
272
|
+
if (id) window.dispatchEvent(new CustomEvent("window-title-update", { detail: { id, title } }));
|
|
273
|
+
}, 0);
|
|
274
|
+
return () => clearTimeout(handle);
|
|
275
|
+
}, [title]);
|
|
276
|
+
return /* @__PURE__ */ jsx("span", { ref, style: { display: "none" }, "aria-hidden": "true" });
|
|
277
|
+
}
|
|
278
|
+
function useWindowTitle(_title) {
|
|
279
|
+
}
|
|
280
|
+
function CopyButton({ text }) {
|
|
281
|
+
const [copied, setCopied] = useState(false);
|
|
282
|
+
const handleCopy = (e) => {
|
|
283
|
+
e.stopPropagation();
|
|
284
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
285
|
+
setCopied(true);
|
|
286
|
+
setTimeout(() => setCopied(false), 1500);
|
|
287
|
+
});
|
|
288
|
+
};
|
|
289
|
+
return /* @__PURE__ */ jsx(
|
|
290
|
+
"button",
|
|
291
|
+
{
|
|
292
|
+
onClick: handleCopy,
|
|
293
|
+
title: copied ? "Copied!" : `Copy ${text}`,
|
|
294
|
+
className: "shrink-0 text-gray-400 hover:text-gray-600 transition-colors",
|
|
295
|
+
children: copied ? /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 text-green-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }) : /* @__PURE__ */ jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" }) })
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
var ModalActionsContext = createContext(null);
|
|
300
|
+
function useModalActive() {
|
|
301
|
+
const activeId = useSyncExternalStore(subscribeActive, getActiveModalId);
|
|
302
|
+
const ctx = useContext(ModalActionsContext);
|
|
303
|
+
return ctx?.active ?? (activationOrder.length <= 1 || activeId != null);
|
|
304
|
+
}
|
|
305
|
+
function ModalActions({ children, position = "right" }) {
|
|
306
|
+
const ctx = useContext(ModalActionsContext);
|
|
307
|
+
const [target, setTarget] = useState(null);
|
|
308
|
+
useEffect(() => {
|
|
309
|
+
if (!ctx) return;
|
|
310
|
+
const ref = position === "left" ? ctx.leftRef : ctx.rightRef;
|
|
311
|
+
const check = () => {
|
|
312
|
+
if (ref.current) {
|
|
313
|
+
setTarget(ref.current);
|
|
314
|
+
ctx.notify();
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
return false;
|
|
318
|
+
};
|
|
319
|
+
if (check()) return;
|
|
320
|
+
const t = setInterval(() => {
|
|
321
|
+
if (check()) clearInterval(t);
|
|
322
|
+
}, 50);
|
|
323
|
+
const cleanup = setTimeout(() => clearInterval(t), 2e3);
|
|
324
|
+
return () => {
|
|
325
|
+
clearInterval(t);
|
|
326
|
+
clearTimeout(cleanup);
|
|
327
|
+
};
|
|
328
|
+
}, [ctx, position]);
|
|
329
|
+
if (!target) return null;
|
|
330
|
+
return createPortal(children, target);
|
|
331
|
+
}
|
|
332
|
+
function CancelButton({ onClick, children, className }) {
|
|
333
|
+
const ctx = useContext(ModalActionsContext);
|
|
334
|
+
const handleClick = async () => {
|
|
335
|
+
if (ctx?.isDirty) {
|
|
336
|
+
const { confirm: confirm2 } = await import('./ConfirmDialog-ZP4AHVUD.js');
|
|
337
|
+
const ok = await confirm2({
|
|
338
|
+
title: "Discard changes?",
|
|
339
|
+
message: "You have unsaved changes. Are you sure you want to cancel? All changes will be lost.",
|
|
340
|
+
confirmLabel: "Discard",
|
|
341
|
+
cancelLabel: "Keep Editing",
|
|
342
|
+
variant: "warning"
|
|
343
|
+
});
|
|
344
|
+
if (!ok) return;
|
|
345
|
+
}
|
|
346
|
+
onClick();
|
|
347
|
+
};
|
|
348
|
+
return /* @__PURE__ */ jsx(
|
|
349
|
+
"button",
|
|
350
|
+
{
|
|
351
|
+
type: "button",
|
|
352
|
+
onClick: handleClick,
|
|
353
|
+
className: className || "bg-white text-gray-700 border border-gray-300 px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-50",
|
|
354
|
+
children: children || "Cancel"
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
var WP_KEY = "erp_window_positions";
|
|
359
|
+
var _windowPositions = {};
|
|
360
|
+
try {
|
|
361
|
+
const stored = localStorage.getItem(WP_KEY);
|
|
362
|
+
if (stored) _windowPositions = JSON.parse(stored);
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
365
|
+
var _saveTimer = null;
|
|
366
|
+
function _savePositionsDebounced() {
|
|
367
|
+
if (_saveTimer) clearTimeout(_saveTimer);
|
|
368
|
+
_saveTimer = setTimeout(() => {
|
|
369
|
+
try {
|
|
370
|
+
localStorage.setItem(WP_KEY, JSON.stringify(_windowPositions));
|
|
371
|
+
} catch {
|
|
372
|
+
}
|
|
373
|
+
}, 500);
|
|
374
|
+
}
|
|
375
|
+
var sizeDefaults = {
|
|
376
|
+
sm: 384,
|
|
377
|
+
md: 512,
|
|
378
|
+
lg: 672,
|
|
379
|
+
xl: 896,
|
|
380
|
+
"2xl": 1152
|
|
381
|
+
};
|
|
382
|
+
var activationOrder = [];
|
|
383
|
+
var activeListeners = /* @__PURE__ */ new Set();
|
|
384
|
+
function activateModal(id) {
|
|
385
|
+
const idx = activationOrder.indexOf(id);
|
|
386
|
+
if (idx !== -1) activationOrder.splice(idx, 1);
|
|
387
|
+
activationOrder.push(id);
|
|
388
|
+
activeListeners.forEach((fn) => fn());
|
|
389
|
+
window.dispatchEvent(new CustomEvent("modal-reorder"));
|
|
390
|
+
}
|
|
391
|
+
function deactivateAllModals() {
|
|
392
|
+
activationOrder.length = 0;
|
|
393
|
+
activeListeners.forEach((fn) => fn());
|
|
394
|
+
window.dispatchEvent(new CustomEvent("modal-reorder"));
|
|
395
|
+
}
|
|
396
|
+
window.addEventListener("deactivate-all-modals", deactivateAllModals);
|
|
397
|
+
function getZForModal(id) {
|
|
398
|
+
const idx = activationOrder.indexOf(id);
|
|
399
|
+
if (idx === -1) return -1;
|
|
400
|
+
return 50 + idx * 10;
|
|
401
|
+
}
|
|
402
|
+
function getActiveModalId() {
|
|
403
|
+
return activationOrder[activationOrder.length - 1] || null;
|
|
404
|
+
}
|
|
405
|
+
function subscribeActive(cb) {
|
|
406
|
+
activeListeners.add(cb);
|
|
407
|
+
return () => {
|
|
408
|
+
activeListeners.delete(cb);
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function useIsActiveModal(modalId) {
|
|
412
|
+
const activeId = useSyncExternalStore(subscribeActive, getActiveModalId);
|
|
413
|
+
return activationOrder.length <= 1 || activeId === modalId;
|
|
414
|
+
}
|
|
415
|
+
function triggerSplitView() {
|
|
416
|
+
window.dispatchEvent(new CustomEvent("modal-split-view"));
|
|
417
|
+
}
|
|
418
|
+
function Modal({ open, onClose, title, icon, copyText, size = "lg", dirty = false, onNext, onPrev, footer, bodyScroll, onMinimize, initialBox, actions, actionsLeft, allowPinOnTop, initialPosition, widget, compact, autoHeight, widgetMenu, dimensions, windowKey, children }) {
|
|
419
|
+
const [displayTitle, setDisplayTitle] = useState(title);
|
|
420
|
+
useEffect(() => {
|
|
421
|
+
setDisplayTitle(title);
|
|
422
|
+
}, [title]);
|
|
423
|
+
const [touched, setTouched] = useState(false);
|
|
424
|
+
const [pinnedOnTop, setPinnedOnTop] = useState(false);
|
|
425
|
+
const [windowMenu, setWindowMenu] = useState(null);
|
|
426
|
+
const [ctxMenu, setCtxMenu] = useState(null);
|
|
427
|
+
const [widgetAnchor, setWidgetAnchor] = useState(initialPosition === "top-right" ? "right" : "left");
|
|
428
|
+
const closingRef = useRef(false);
|
|
429
|
+
const panelRef = useRef(null);
|
|
430
|
+
const actionsRef = useRef(null);
|
|
431
|
+
const actionsLeftRef = useRef(null);
|
|
432
|
+
const [hasActions, setHasActions] = useState(false);
|
|
433
|
+
const fallbackIcon = /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3.75 6.75A2.25 2.25 0 016 4.5h12a2.25 2.25 0 012.25 2.25v10.5A2.25 2.25 0 0118 19.5H6a2.25 2.25 0 01-2.25-2.25V6.75z M3.75 9h16.5" }) });
|
|
434
|
+
const effectiveIcon = icon && isValidElement(icon) ? cloneElement(icon, {
|
|
435
|
+
className: `h-4 w-4 ${icon.props?.className ?? ""}`.trim()
|
|
436
|
+
}) : icon ?? fallbackIcon;
|
|
437
|
+
const renderIconButton = () => /* @__PURE__ */ jsx(
|
|
438
|
+
"button",
|
|
439
|
+
{
|
|
440
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
441
|
+
onClick: (e) => {
|
|
442
|
+
e.stopPropagation();
|
|
443
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
444
|
+
setWindowMenu((prev) => prev ? null : { x: rect.left, y: rect.bottom + 4 });
|
|
445
|
+
},
|
|
446
|
+
className: "shrink-0 p-0.5 rounded hover:bg-gray-200/50 transition-colors",
|
|
447
|
+
title: "Window menu",
|
|
448
|
+
children: effectiveIcon
|
|
449
|
+
}
|
|
450
|
+
);
|
|
451
|
+
const padding = 40;
|
|
452
|
+
const { minimize: globalMinimize, items: minimizedItems, restoreIfMinimized } = useWindowManager();
|
|
453
|
+
const modalId = useRef(`modal-${Math.random().toString(36).slice(2, 8)}`).current;
|
|
454
|
+
useEffect(() => {
|
|
455
|
+
const handler = (e) => {
|
|
456
|
+
const detail = e.detail;
|
|
457
|
+
if (detail?.id === modalId) setDisplayTitle(detail.title);
|
|
458
|
+
};
|
|
459
|
+
window.addEventListener("window-title-update", handler);
|
|
460
|
+
return () => window.removeEventListener("window-title-update", handler);
|
|
461
|
+
}, [modalId]);
|
|
462
|
+
const [zIndex, setZIndex] = useState(50);
|
|
463
|
+
const isActive = useIsActiveModal(modalId);
|
|
464
|
+
useEffect(() => {
|
|
465
|
+
const r = actionsRef.current;
|
|
466
|
+
const l = actionsLeftRef.current;
|
|
467
|
+
if (!r && !l) return;
|
|
468
|
+
const check = () => setHasActions((r?.childElementCount ?? 0) + (l?.childElementCount ?? 0) > 0);
|
|
469
|
+
check();
|
|
470
|
+
const obs = new MutationObserver(check);
|
|
471
|
+
if (r) obs.observe(r, { childList: true });
|
|
472
|
+
if (l) obs.observe(l, { childList: true });
|
|
473
|
+
return () => obs.disconnect();
|
|
474
|
+
}, [open]);
|
|
475
|
+
useEffect(() => {
|
|
476
|
+
const handler = (e) => {
|
|
477
|
+
const btn = e.target.closest('button[type="submit"]');
|
|
478
|
+
if (btn && panelRef.current) {
|
|
479
|
+
const form = panelRef.current.querySelector("form");
|
|
480
|
+
if (form) {
|
|
481
|
+
e.preventDefault();
|
|
482
|
+
form.requestSubmit();
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
const r = actionsRef.current;
|
|
487
|
+
const l = actionsLeftRef.current;
|
|
488
|
+
if (r) r.addEventListener("click", handler);
|
|
489
|
+
if (l) l.addEventListener("click", handler);
|
|
490
|
+
return () => {
|
|
491
|
+
if (r) r.removeEventListener("click", handler);
|
|
492
|
+
if (l) l.removeEventListener("click", handler);
|
|
493
|
+
};
|
|
494
|
+
}, [open]);
|
|
495
|
+
const isNested = useRef(false);
|
|
496
|
+
const calcMaximized = useCallback(() => {
|
|
497
|
+
const taskbarH = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--taskbar-height")) || 0;
|
|
498
|
+
const taskbarW = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--taskbar-width")) || 0;
|
|
499
|
+
const tbPos = getComputedStyle(document.documentElement).getPropertyValue("--taskbar-position")?.trim() || "bottom";
|
|
500
|
+
const x = tbPos === "left" ? taskbarW : 0;
|
|
501
|
+
const y = tbPos === "top" ? taskbarH : 0;
|
|
502
|
+
const w = window.innerWidth - (tbPos === "left" || tbPos === "right" ? taskbarW : 0);
|
|
503
|
+
const h = window.innerHeight - (tbPos === "top" || tbPos === "bottom" ? taskbarH : 0);
|
|
504
|
+
return { x, y, w, h };
|
|
505
|
+
}, []);
|
|
506
|
+
const calcWindowed = useCallback(() => {
|
|
507
|
+
const sw = 0;
|
|
508
|
+
const taskbarH = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--taskbar-height")) || 0;
|
|
509
|
+
const taskbarW = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--taskbar-width")) || 0;
|
|
510
|
+
const tbPos = getComputedStyle(document.documentElement).getPropertyValue("--taskbar-position")?.trim() || "bottom";
|
|
511
|
+
const leftOffset = tbPos === "left" ? taskbarW : 0;
|
|
512
|
+
const rightOffset = tbPos === "right" ? taskbarW : 0;
|
|
513
|
+
const targetW = dimensions ? dimensions[0] : sizeDefaults[size] || 672;
|
|
514
|
+
const availW = window.innerWidth - sw - leftOffset - rightOffset - padding * 2;
|
|
515
|
+
const availH = window.innerHeight - taskbarH - padding * 2;
|
|
516
|
+
const w = Math.min(targetW, availW);
|
|
517
|
+
const h = dimensions ? Math.min(dimensions[1], availH) : (() => {
|
|
518
|
+
const minH = size === "sm" ? 300 : 400;
|
|
519
|
+
const maxH = size === "sm" ? 500 : size === "md" ? 600 : size === "lg" ? 700 : availH;
|
|
520
|
+
return Math.max(minH, Math.min(maxH, window.innerHeight - taskbarH - 80));
|
|
521
|
+
})();
|
|
522
|
+
const posMode = getComputedStyle(document.documentElement).getPropertyValue("--window-position")?.trim() || "cascade";
|
|
523
|
+
const offset = posMode === "cascade" ? (activationOrder.length - 1) * 30 : 0;
|
|
524
|
+
const maxOffsetX = availW - w;
|
|
525
|
+
const maxOffsetY = availH - h;
|
|
526
|
+
if (initialPosition === "top-right") {
|
|
527
|
+
return { x: window.innerWidth - w - padding, y: padding, w, h };
|
|
528
|
+
}
|
|
529
|
+
if (initialPosition === "top-left") {
|
|
530
|
+
return { x: sw + padding, y: padding, w, h };
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
x: sw + Math.max(padding, (availW - w) / 2 + padding) + Math.min(offset, Math.max(0, maxOffsetX)),
|
|
534
|
+
y: Math.max(padding, (availH - h) / 2) + Math.min(offset, Math.max(0, maxOffsetY)),
|
|
535
|
+
w,
|
|
536
|
+
h
|
|
537
|
+
};
|
|
538
|
+
}, [size]);
|
|
539
|
+
const interceptedRef = useRef(false);
|
|
540
|
+
if (!open) interceptedRef.current = false;
|
|
541
|
+
useEffect(() => {
|
|
542
|
+
if (!open) return;
|
|
543
|
+
activateModal(modalId);
|
|
544
|
+
isNested.current = false;
|
|
545
|
+
setZIndex(getZForModal(modalId));
|
|
546
|
+
const onReorder = () => {
|
|
547
|
+
setZIndex(getZForModal(modalId));
|
|
548
|
+
};
|
|
549
|
+
const onSplitView = () => {
|
|
550
|
+
if (allowPinOnTop) return;
|
|
551
|
+
const allPanels = document.querySelectorAll("[data-modal-panel]");
|
|
552
|
+
let nonUtilityCount = 0;
|
|
553
|
+
let myNonUtilIdx = -1;
|
|
554
|
+
activationOrder.forEach((id, _i) => {
|
|
555
|
+
const panel = Array.from(allPanels).find((p) => p.getAttribute("data-modal-id") === id);
|
|
556
|
+
if (panel && !panel.hasAttribute("data-utility")) {
|
|
557
|
+
if (id === modalId) myNonUtilIdx = nonUtilityCount;
|
|
558
|
+
nonUtilityCount++;
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
const count = nonUtilityCount;
|
|
562
|
+
if (count < 2) return;
|
|
563
|
+
const myIdx = myNonUtilIdx;
|
|
564
|
+
if (myIdx < 0) return;
|
|
565
|
+
const padding2 = 20;
|
|
566
|
+
const taskbarH = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--taskbar-height")) || 56;
|
|
567
|
+
const availW = window.innerWidth - padding2 * 2;
|
|
568
|
+
const availH = window.innerHeight - taskbarH - padding2 * 2;
|
|
569
|
+
const colW = Math.floor((availW - padding2 * (count - 1)) / count);
|
|
570
|
+
const x = padding2 + myIdx * (colW + padding2);
|
|
571
|
+
setBox({ x, y: padding2, w: colW, h: availH });
|
|
572
|
+
setMaximized(false);
|
|
573
|
+
};
|
|
574
|
+
const onCenter = (e) => {
|
|
575
|
+
const label = e.detail?.label;
|
|
576
|
+
if (!label) return;
|
|
577
|
+
const titleEl = panelRef.current?.querySelector(".text-lg, .text-sm");
|
|
578
|
+
if (!titleEl?.textContent?.includes(label)) return;
|
|
579
|
+
activateModal(modalId);
|
|
580
|
+
const taskbarH = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--taskbar-height")) || 0;
|
|
581
|
+
const taskbarW = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--taskbar-width")) || 0;
|
|
582
|
+
const tbPos = getComputedStyle(document.documentElement).getPropertyValue("--taskbar-position")?.trim() || "bottom";
|
|
583
|
+
const leftOff = tbPos === "left" ? taskbarW : 0;
|
|
584
|
+
const rightOff = tbPos === "right" ? taskbarW : 0;
|
|
585
|
+
const availW = window.innerWidth - leftOff - rightOff;
|
|
586
|
+
const availH = window.innerHeight - taskbarH;
|
|
587
|
+
const w = Math.min(boxRef.current.w, availW - 40);
|
|
588
|
+
const h = Math.min(boxRef.current.h, availH - 40);
|
|
589
|
+
const x = leftOff + (availW - w) / 2;
|
|
590
|
+
const y = (tbPos === "top" ? taskbarH : 0) + (availH - h) / 2;
|
|
591
|
+
setBox({ x, y, w, h });
|
|
592
|
+
setMaximized(false);
|
|
593
|
+
};
|
|
594
|
+
const onCtxMenu = (e) => {
|
|
595
|
+
const { label, x, y } = e.detail || {};
|
|
596
|
+
if (!label) return;
|
|
597
|
+
const titleEl = panelRef.current?.querySelector(".text-lg, .text-sm");
|
|
598
|
+
if (!titleEl?.textContent?.includes(label)) return;
|
|
599
|
+
activateModal(modalId);
|
|
600
|
+
setWindowMenu({ x, y });
|
|
601
|
+
};
|
|
602
|
+
window.addEventListener("modal-reorder", onReorder);
|
|
603
|
+
window.addEventListener("modal-split-view", onSplitView);
|
|
604
|
+
window.addEventListener("modal-center", onCenter);
|
|
605
|
+
window.addEventListener("modal-context-menu", onCtxMenu);
|
|
606
|
+
return () => {
|
|
607
|
+
const aidx = activationOrder.indexOf(modalId);
|
|
608
|
+
if (aidx !== -1) {
|
|
609
|
+
activationOrder.splice(aidx, 1);
|
|
610
|
+
activeListeners.forEach((fn) => fn());
|
|
611
|
+
}
|
|
612
|
+
window.removeEventListener("modal-reorder", onReorder);
|
|
613
|
+
window.removeEventListener("modal-split-view", onSplitView);
|
|
614
|
+
window.removeEventListener("modal-center", onCenter);
|
|
615
|
+
window.removeEventListener("modal-context-menu", onCtxMenu);
|
|
616
|
+
window.dispatchEvent(new CustomEvent("modal-reorder"));
|
|
617
|
+
};
|
|
618
|
+
}, [open, modalId, calcMaximized]);
|
|
619
|
+
const boxKey = windowKey || copyText || null;
|
|
620
|
+
const [box, setBox] = useState(() => {
|
|
621
|
+
if (boxKey && _windowPositions[boxKey]) {
|
|
622
|
+
const saved = { ..._windowPositions[boxKey] };
|
|
623
|
+
if (dimensions) {
|
|
624
|
+
saved.w = dimensions[0];
|
|
625
|
+
if (!autoHeight) saved.h = dimensions[1];
|
|
626
|
+
}
|
|
627
|
+
return saved;
|
|
628
|
+
}
|
|
629
|
+
return initialBox ? { x: initialBox.x, y: initialBox.y, w: initialBox.w, h: initialBox.h } : calcWindowed();
|
|
630
|
+
});
|
|
631
|
+
const [maximized, setMaximized] = useState(false);
|
|
632
|
+
const boxRef = useRef(box);
|
|
633
|
+
boxRef.current = box;
|
|
634
|
+
useEffect(() => {
|
|
635
|
+
if (open && boxKey) {
|
|
636
|
+
_windowPositions[boxKey] = box;
|
|
637
|
+
_savePositionsDebounced();
|
|
638
|
+
}
|
|
639
|
+
}, [box, open, boxKey]);
|
|
640
|
+
useEffect(() => {
|
|
641
|
+
if (!open) return;
|
|
642
|
+
const sync = () => {
|
|
643
|
+
if (maximized) setBox(calcMaximized());
|
|
644
|
+
};
|
|
645
|
+
window.addEventListener("resize", sync);
|
|
646
|
+
let observer = null;
|
|
647
|
+
if (maximized) {
|
|
648
|
+
observer = new MutationObserver(sync);
|
|
649
|
+
observer.observe(document.body, { childList: true, subtree: false, attributes: true, attributeFilter: ["class"] });
|
|
650
|
+
}
|
|
651
|
+
return () => {
|
|
652
|
+
window.removeEventListener("resize", sync);
|
|
653
|
+
observer?.disconnect();
|
|
654
|
+
};
|
|
655
|
+
}, [open, maximized, calcMaximized]);
|
|
656
|
+
useEffect(() => {
|
|
657
|
+
if (!open) return;
|
|
658
|
+
setTouched(false);
|
|
659
|
+
closingRef.current = false;
|
|
660
|
+
if (boxKey && _windowPositions[boxKey]) {
|
|
661
|
+
setBox({ ..._windowPositions[boxKey] });
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
if (initialBox) {
|
|
665
|
+
setBox({ x: initialBox.x, y: initialBox.y, w: initialBox.w, h: initialBox.h });
|
|
666
|
+
setMaximized(initialBox.maximized);
|
|
667
|
+
}
|
|
668
|
+
}, [open]);
|
|
669
|
+
useEffect(() => {
|
|
670
|
+
if (!open || dirty !== "auto") return;
|
|
671
|
+
const handler = (e) => {
|
|
672
|
+
if (panelRef.current?.contains(e.target)) setTouched(true);
|
|
673
|
+
};
|
|
674
|
+
document.addEventListener("input", handler, true);
|
|
675
|
+
document.addEventListener("change", handler, true);
|
|
676
|
+
return () => {
|
|
677
|
+
document.removeEventListener("input", handler, true);
|
|
678
|
+
document.removeEventListener("change", handler, true);
|
|
679
|
+
};
|
|
680
|
+
}, [open, dirty]);
|
|
681
|
+
const isDirty = dirty === "auto" ? touched : dirty === true;
|
|
682
|
+
const guardedClose = useCallback(async () => {
|
|
683
|
+
if (closingRef.current) return;
|
|
684
|
+
if (isDirty) {
|
|
685
|
+
closingRef.current = true;
|
|
686
|
+
const ok = await confirm({ title: "Discard changes?", message: "You have unsaved changes. Are you sure you want to close? All changes will be lost.", confirmLabel: "Discard", cancelLabel: "Keep Editing", variant: "warning" });
|
|
687
|
+
closingRef.current = false;
|
|
688
|
+
if (!ok) return;
|
|
689
|
+
}
|
|
690
|
+
onClose();
|
|
691
|
+
}, [isDirty, onClose]);
|
|
692
|
+
const startDrag = useCallback((e) => {
|
|
693
|
+
if (e.button !== 0) return;
|
|
694
|
+
if (e.target.closest("button, input, a, kbd, select, textarea")) return;
|
|
695
|
+
setWindowMenu(null);
|
|
696
|
+
activateModal(modalId);
|
|
697
|
+
e.preventDefault();
|
|
698
|
+
const sx = e.clientX, sy = e.clientY;
|
|
699
|
+
const panel = panelRef.current;
|
|
700
|
+
const rect = panel?.getBoundingClientRect();
|
|
701
|
+
const ox = rect ? rect.left : boxRef.current.x;
|
|
702
|
+
const oy = rect ? rect.top : boxRef.current.y;
|
|
703
|
+
const actualH = rect ? rect.height : boxRef.current.h;
|
|
704
|
+
setMaximized(false);
|
|
705
|
+
setBox((b) => ({ ...b, x: ox, y: oy, h: actualH }));
|
|
706
|
+
const move = (ev) => {
|
|
707
|
+
const nx = ox + ev.clientX - sx;
|
|
708
|
+
const ny = Math.max(0, oy + ev.clientY - sy);
|
|
709
|
+
if (panel) {
|
|
710
|
+
panel.style.left = `${nx}px`;
|
|
711
|
+
panel.style.top = `${ny}px`;
|
|
712
|
+
}
|
|
713
|
+
boxRef.current = { ...boxRef.current, x: nx, y: ny };
|
|
714
|
+
};
|
|
715
|
+
const up = () => {
|
|
716
|
+
window.removeEventListener("pointermove", move);
|
|
717
|
+
window.removeEventListener("pointerup", up);
|
|
718
|
+
const finalBox = { ...boxRef.current };
|
|
719
|
+
if (widget) {
|
|
720
|
+
const centerX = finalBox.x + finalBox.w / 2;
|
|
721
|
+
const mid = window.innerWidth / 2;
|
|
722
|
+
setWidgetAnchor(centerX > mid ? "right" : "left");
|
|
723
|
+
}
|
|
724
|
+
setBox(finalBox);
|
|
725
|
+
};
|
|
726
|
+
window.addEventListener("pointermove", move);
|
|
727
|
+
window.addEventListener("pointerup", up);
|
|
728
|
+
}, []);
|
|
729
|
+
const startResizeCorner = useCallback((e, corner) => {
|
|
730
|
+
if (e.button !== 0) return;
|
|
731
|
+
e.preventDefault();
|
|
732
|
+
e.stopPropagation();
|
|
733
|
+
const sx = e.clientX, sy = e.clientY;
|
|
734
|
+
const panel = panelRef.current;
|
|
735
|
+
const rect = panel?.getBoundingClientRect();
|
|
736
|
+
const ox = rect ? rect.left : boxRef.current.x;
|
|
737
|
+
const oy = rect ? rect.top : boxRef.current.y;
|
|
738
|
+
const ow = rect ? rect.width : boxRef.current.w;
|
|
739
|
+
const oh = rect ? rect.height : boxRef.current.h;
|
|
740
|
+
setMaximized(false);
|
|
741
|
+
const move = (ev) => {
|
|
742
|
+
const minH = 400;
|
|
743
|
+
const dx = ev.clientX - sx;
|
|
744
|
+
const dy = ev.clientY - sy;
|
|
745
|
+
const newBox = { x: ox, y: oy, w: ow, h: oh };
|
|
746
|
+
if (corner === "se") {
|
|
747
|
+
newBox.w = Math.max(384, ow + dx);
|
|
748
|
+
newBox.h = Math.max(minH, oh + dy);
|
|
749
|
+
} else if (corner === "sw") {
|
|
750
|
+
newBox.w = Math.max(384, ow - dx);
|
|
751
|
+
newBox.x = ox + dx;
|
|
752
|
+
newBox.h = Math.max(minH, oh + dy);
|
|
753
|
+
if (newBox.w <= 384) newBox.x = ox + ow - 384;
|
|
754
|
+
} else if (corner === "ne") {
|
|
755
|
+
newBox.w = Math.max(384, ow + dx);
|
|
756
|
+
newBox.h = Math.max(minH, oh - dy);
|
|
757
|
+
newBox.y = oy + dy;
|
|
758
|
+
if (newBox.h <= minH) newBox.y = oy + oh - minH;
|
|
759
|
+
} else if (corner === "nw") {
|
|
760
|
+
newBox.w = Math.max(384, ow - dx);
|
|
761
|
+
newBox.x = ox + dx;
|
|
762
|
+
newBox.h = Math.max(minH, oh - dy);
|
|
763
|
+
newBox.y = oy + dy;
|
|
764
|
+
if (newBox.w <= 384) newBox.x = ox + ow - 384;
|
|
765
|
+
if (newBox.h <= minH) newBox.y = oy + oh - minH;
|
|
766
|
+
} else if (corner === "e") {
|
|
767
|
+
newBox.w = Math.max(384, ow + dx);
|
|
768
|
+
} else if (corner === "w") {
|
|
769
|
+
newBox.w = Math.max(384, ow - dx);
|
|
770
|
+
newBox.x = ox + dx;
|
|
771
|
+
if (newBox.w <= 384) newBox.x = ox + ow - 384;
|
|
772
|
+
} else if (corner === "s") {
|
|
773
|
+
newBox.h = Math.max(minH, oh + dy);
|
|
774
|
+
} else if (corner === "n") {
|
|
775
|
+
newBox.h = Math.max(minH, oh - dy);
|
|
776
|
+
newBox.y = oy + dy;
|
|
777
|
+
if (newBox.h <= minH) newBox.y = oy + oh - minH;
|
|
778
|
+
}
|
|
779
|
+
if (panel) {
|
|
780
|
+
panel.style.left = `${newBox.x}px`;
|
|
781
|
+
panel.style.top = `${newBox.y}px`;
|
|
782
|
+
panel.style.width = `${newBox.w}px`;
|
|
783
|
+
panel.style.height = `${newBox.h}px`;
|
|
784
|
+
}
|
|
785
|
+
boxRef.current = newBox;
|
|
786
|
+
};
|
|
787
|
+
const up = () => {
|
|
788
|
+
window.removeEventListener("pointermove", move);
|
|
789
|
+
window.removeEventListener("pointerup", up);
|
|
790
|
+
setBox({ ...boxRef.current });
|
|
791
|
+
};
|
|
792
|
+
window.addEventListener("pointermove", move);
|
|
793
|
+
window.addEventListener("pointerup", up);
|
|
794
|
+
}, []);
|
|
795
|
+
const reset = () => {
|
|
796
|
+
setMaximized(true);
|
|
797
|
+
setBox(calcMaximized());
|
|
798
|
+
};
|
|
799
|
+
const submitModal = useCallback(() => {
|
|
800
|
+
const panel = panelRef.current;
|
|
801
|
+
if (!panel) return;
|
|
802
|
+
const form = panel.querySelector("form");
|
|
803
|
+
if (form) {
|
|
804
|
+
form.requestSubmit();
|
|
805
|
+
} else {
|
|
806
|
+
const btn = panel.querySelector('button[type="submit"], button[data-submit], button.bg-green-600, button.bg-blue-600');
|
|
807
|
+
if (btn && !btn.disabled) btn.click();
|
|
808
|
+
}
|
|
809
|
+
}, []);
|
|
810
|
+
useEffect(() => {
|
|
811
|
+
if (!open) return;
|
|
812
|
+
const handler = (e) => {
|
|
813
|
+
if (activationOrder[activationOrder.length - 1] !== modalId) return;
|
|
814
|
+
if (e.key === "Enter") {
|
|
815
|
+
const target = e.target;
|
|
816
|
+
if (target.tagName === "TEXTAREA" || target.isContentEditable) return;
|
|
817
|
+
if (e.metaKey || e.ctrlKey) {
|
|
818
|
+
e.preventDefault();
|
|
819
|
+
submitModal();
|
|
820
|
+
} else if (target.tagName === "INPUT" && target.closest("form")) {
|
|
821
|
+
return;
|
|
822
|
+
} else {
|
|
823
|
+
e.preventDefault();
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
window.addEventListener("keydown", handler, true);
|
|
828
|
+
return () => window.removeEventListener("keydown", handler, true);
|
|
829
|
+
}, [open, submitModal, modalId]);
|
|
830
|
+
useEffect(() => {
|
|
831
|
+
if (!open) return;
|
|
832
|
+
const handler = (e) => {
|
|
833
|
+
if (activationOrder[activationOrder.length - 1] !== modalId) return;
|
|
834
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "s") {
|
|
835
|
+
e.preventDefault();
|
|
836
|
+
document.dispatchEvent(new CustomEvent("modal-save"));
|
|
837
|
+
setTouched(false);
|
|
838
|
+
} else if (e.altKey && e.shiftKey && (e.code === "KeyD" || e.key === "D" || e.key === "d")) {
|
|
839
|
+
e.preventDefault();
|
|
840
|
+
document.dispatchEvent(new CustomEvent("modal-duplicate"));
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
window.addEventListener("keydown", handler, true);
|
|
844
|
+
return () => window.removeEventListener("keydown", handler, true);
|
|
845
|
+
}, [open]);
|
|
846
|
+
useEffect(() => {
|
|
847
|
+
if (!open) return;
|
|
848
|
+
if (widget) return;
|
|
849
|
+
const handler = (e) => {
|
|
850
|
+
if (e.key === "Escape" && activationOrder[activationOrder.length - 1] === modalId) {
|
|
851
|
+
e.preventDefault();
|
|
852
|
+
e.stopPropagation();
|
|
853
|
+
guardedClose();
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
window.addEventListener("keydown", handler, true);
|
|
857
|
+
return () => window.removeEventListener("keydown", handler, true);
|
|
858
|
+
}, [open, guardedClose, modalId, widget]);
|
|
859
|
+
const hasNav = open && (onNext !== void 0 || onPrev !== void 0);
|
|
860
|
+
const showBoundaryToast = useCallback((edge) => {
|
|
861
|
+
const existing = document.getElementById("nav-boundary-toast");
|
|
862
|
+
if (existing) existing.remove();
|
|
863
|
+
const lines = edge === "bottom" ? ["That's the last one, chief.", "You've hit rock bottom... of the list.", "End of the line, buddy.", "Nothing left. Go outside.", "Congratulations, you scrolled to the void.", "There's no more. I checked.", "You've reached the edge of the known universe.", "Plot twist: there is no next one.", "Even scrolling has limits. Unlike your ambition.", "Last one. Time to touch grass."] : ["Already at the top. Overachiever.", "There is no item above this. Trust me.", "You're #1. Literally.", "Can't go higher. You've peaked.", "This is it. The summit. The top.", "First item. No VIP lounge above this.", "Going up? Nope. Elevator's broken.", "You want negative indexes? Bold.", "Top of the list. Top of the world.", "The only way is down from here."];
|
|
864
|
+
const msg = lines[Math.floor(Math.random() * lines.length)];
|
|
865
|
+
const toast = document.createElement("div");
|
|
866
|
+
toast.id = "nav-boundary-toast";
|
|
867
|
+
toast.className = "fixed bottom-6 left-1/2 -translate-x-1/2 z-[9999] bg-gray-900 text-white px-5 py-2.5 rounded-lg shadow-lg text-sm font-medium transition-opacity duration-300";
|
|
868
|
+
toast.textContent = msg;
|
|
869
|
+
document.body.appendChild(toast);
|
|
870
|
+
setTimeout(() => {
|
|
871
|
+
toast.style.opacity = "0";
|
|
872
|
+
setTimeout(() => toast.remove(), 300);
|
|
873
|
+
}, 2e3);
|
|
874
|
+
}, []);
|
|
875
|
+
useEffect(() => {
|
|
876
|
+
if (!open) return;
|
|
877
|
+
const handler = (e) => {
|
|
878
|
+
if (activationOrder[activationOrder.length - 1] !== modalId) return;
|
|
879
|
+
const tag = e.target.tagName;
|
|
880
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
|
|
881
|
+
if (e.target.isContentEditable) return;
|
|
882
|
+
if (e.key === "j" && hasNav) {
|
|
883
|
+
e.preventDefault();
|
|
884
|
+
if (onNext) onNext();
|
|
885
|
+
else showBoundaryToast("bottom");
|
|
886
|
+
}
|
|
887
|
+
if (e.key === "k" && hasNav) {
|
|
888
|
+
e.preventDefault();
|
|
889
|
+
if (onPrev) onPrev();
|
|
890
|
+
else showBoundaryToast("top");
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
window.addEventListener("keydown", handler);
|
|
894
|
+
return () => window.removeEventListener("keydown", handler);
|
|
895
|
+
}, [open, onNext, onPrev, hasNav, showBoundaryToast, modalId]);
|
|
896
|
+
if (!open || interceptedRef.current) return null;
|
|
897
|
+
const content = /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
|
|
898
|
+
"div",
|
|
899
|
+
{
|
|
900
|
+
ref: panelRef,
|
|
901
|
+
"data-modal-panel": true,
|
|
902
|
+
"data-modal-id": modalId,
|
|
903
|
+
...allowPinOnTop ? { "data-utility": "" } : {},
|
|
904
|
+
className: `fixed rounded-lg flex flex-col overflow-hidden group ${widget ? isActive ? "shadow-2xl" : "shadow-lg" : `border ${isActive ? "shadow-2xl border-gray-200" : "shadow-lg border-gray-300"}`}`,
|
|
905
|
+
onMouseDown: (e) => {
|
|
906
|
+
setWindowMenu(null);
|
|
907
|
+
const targetPanel = e.target.closest("[data-modal-panel]");
|
|
908
|
+
if (targetPanel && targetPanel !== panelRef.current) return;
|
|
909
|
+
if (!e.target.closest("button, input, a, select, textarea")) {
|
|
910
|
+
activateModal(modalId);
|
|
911
|
+
}
|
|
912
|
+
},
|
|
913
|
+
style: {
|
|
914
|
+
zIndex: pinnedOnTop ? 999 : zIndex + 1,
|
|
915
|
+
width: box.w,
|
|
916
|
+
height: autoHeight ? "auto" : box.h,
|
|
917
|
+
top: box.y,
|
|
918
|
+
...widget && widgetAnchor === "right" ? { right: window.innerWidth - box.x - box.w } : { left: box.x },
|
|
919
|
+
...zIndex < 0 && !pinnedOnTop ? { display: "none" } : {}
|
|
920
|
+
},
|
|
921
|
+
children: [
|
|
922
|
+
widget ? (
|
|
923
|
+
/* Widget: no title bar — drag via body, close via right-click context menu */
|
|
924
|
+
null
|
|
925
|
+
) : compact ? (
|
|
926
|
+
/* Compact: smaller title bar with title + close only */
|
|
927
|
+
/* @__PURE__ */ jsxs(
|
|
928
|
+
"div",
|
|
929
|
+
{
|
|
930
|
+
onPointerDown: startDrag,
|
|
931
|
+
className: `flex items-center justify-between px-3 py-1.5 border-b border-gray-200 shrink-0 cursor-move select-none rounded-t-lg ${isActive ? "backdrop-blur-sm" : ""}`,
|
|
932
|
+
style: { touchAction: "none", backgroundColor: isActive ? `rgb(var(--window-header-rgb) / var(--active-header-opacity, 0.8))` : `rgb(var(--window-header-rgb) / var(--inactive-header-opacity, 0.7))` },
|
|
933
|
+
children: [
|
|
934
|
+
/* @__PURE__ */ jsxs("div", { className: "text-sm font-medium min-w-0 flex-1 truncate flex items-center gap-1.5", style: { color: isActive ? "rgb(17 24 39)" : "rgb(156 163 175)" }, children: [
|
|
935
|
+
renderIconButton(),
|
|
936
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: displayTitle })
|
|
937
|
+
] }),
|
|
938
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0 ml-2", children: [
|
|
939
|
+
allowPinOnTop && /* @__PURE__ */ jsx(
|
|
940
|
+
"button",
|
|
941
|
+
{
|
|
942
|
+
onClick: () => setPinnedOnTop((p) => !p),
|
|
943
|
+
title: pinnedOnTop ? "Unpin from top" : "Pin on top",
|
|
944
|
+
className: `p-0.5 rounded hover:bg-gray-200 ${pinnedOnTop ? "text-blue-600" : "text-gray-400 hover:text-gray-600"}`,
|
|
945
|
+
children: /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: pinnedOnTop ? "currentColor" : "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.5 3.75V16.5L12 14.25 7.5 16.5V3.75m9 0H18A2.25 2.25 0 0120.25 6v12A2.25 2.25 0 0118 20.25H6A2.25 2.25 0 013.75 18V6A2.25 2.25 0 016 3.75h1.5m9 0h-9" }) })
|
|
946
|
+
}
|
|
947
|
+
),
|
|
948
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: guardedClose, className: "rounded p-0.5 text-gray-400 hover:text-gray-600 hover:bg-gray-200", children: /* @__PURE__ */ jsx(XMarkIcon, { className: "h-4 w-4" }) })
|
|
949
|
+
] })
|
|
950
|
+
]
|
|
951
|
+
}
|
|
952
|
+
)
|
|
953
|
+
) : /* @__PURE__ */ jsxs(
|
|
954
|
+
"div",
|
|
955
|
+
{
|
|
956
|
+
onPointerDown: startDrag,
|
|
957
|
+
className: `flex items-center justify-between px-4 py-2.5 border-b border-gray-200 shrink-0 cursor-move select-none rounded-t-lg ${isActive ? "backdrop-blur-sm" : ""}`,
|
|
958
|
+
style: { touchAction: "none", backgroundColor: isActive ? `rgb(var(--window-header-rgb) / var(--active-header-opacity, 0.8))` : `rgb(var(--window-header-rgb) / var(--inactive-header-opacity, 0.7))` },
|
|
959
|
+
children: [
|
|
960
|
+
/* @__PURE__ */ jsxs("div", { className: "text-lg font-semibold min-w-0 flex-1 truncate flex items-center gap-2", style: { color: isActive ? "var(--window-title-active, rgb(17 24 39))" : "var(--window-title-inactive, rgb(156 163 175))" }, children: [
|
|
961
|
+
renderIconButton(),
|
|
962
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: displayTitle })
|
|
963
|
+
] }),
|
|
964
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 shrink-0 ml-4", children: [
|
|
965
|
+
hasNav && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 mr-1 text-[10px] text-gray-400", children: [
|
|
966
|
+
/* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 bg-gray-200 px-1.5 py-0.5 font-medium text-gray-500", children: "K" }),
|
|
967
|
+
/* @__PURE__ */ jsx("span", { children: "Prev" }),
|
|
968
|
+
/* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 bg-gray-200 px-1.5 py-0.5 font-medium ml-1 text-gray-500", children: "J" }),
|
|
969
|
+
/* @__PURE__ */ jsx("span", { children: "Next" })
|
|
970
|
+
] }),
|
|
971
|
+
allowPinOnTop && /* @__PURE__ */ jsx(
|
|
972
|
+
"button",
|
|
973
|
+
{
|
|
974
|
+
onClick: () => setPinnedOnTop((p) => !p),
|
|
975
|
+
title: pinnedOnTop ? "Unpin from top" : "Pin on top",
|
|
976
|
+
className: `text-xs px-2 py-1 rounded hover:bg-gray-200 ${pinnedOnTop ? "text-blue-600" : "text-gray-400 hover:text-gray-600"}`,
|
|
977
|
+
children: /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: pinnedOnTop ? "currentColor" : "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.5 3.75V16.5L12 14.25 7.5 16.5V3.75m9 0H18A2.25 2.25 0 0120.25 6v12A2.25 2.25 0 0118 20.25H6A2.25 2.25 0 013.75 18V6A2.25 2.25 0 016 3.75h1.5m9 0h-9" }) })
|
|
978
|
+
}
|
|
979
|
+
),
|
|
980
|
+
/* @__PURE__ */ jsx("button", { onClick: () => {
|
|
981
|
+
const idx = activationOrder.indexOf(modalId);
|
|
982
|
+
if (idx !== -1) activationOrder.splice(idx, 1);
|
|
983
|
+
activeListeners.forEach((fn) => fn());
|
|
984
|
+
window.dispatchEvent(new CustomEvent("modal-reorder"));
|
|
985
|
+
}, title: "Minimize", className: "text-gray-400 hover:text-gray-600 text-xs px-2 py-1 rounded hover:bg-gray-200", children: "\u2500" }),
|
|
986
|
+
/* @__PURE__ */ jsx("button", { onClick: () => {
|
|
987
|
+
if (maximized) {
|
|
988
|
+
setMaximized(false);
|
|
989
|
+
setBox(calcWindowed());
|
|
990
|
+
} else {
|
|
991
|
+
reset();
|
|
992
|
+
}
|
|
993
|
+
}, title: maximized ? "Windowed" : "Maximize", className: "text-gray-400 hover:text-gray-600 text-xs px-2 py-1 rounded hover:bg-gray-200", children: maximized ? "\u2750" : "\u2922" }),
|
|
994
|
+
/* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 bg-gray-200 px-1.5 py-0.5 text-[10px] font-medium text-gray-400", children: "ESC" }),
|
|
995
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: guardedClose, className: "rounded-md text-gray-400 hover:text-gray-600", children: /* @__PURE__ */ jsx(XMarkIcon, { className: "h-5 w-5" }) })
|
|
996
|
+
] })
|
|
997
|
+
]
|
|
998
|
+
}
|
|
999
|
+
),
|
|
1000
|
+
/* @__PURE__ */ jsx(ModalIdContext.Provider, { value: modalId, children: /* @__PURE__ */ jsx(ModalActionsContext.Provider, { value: { rightRef: actionsRef, leftRef: actionsLeftRef, notify: () => setHasActions(true), active: isActive, isDirty }, children: /* @__PURE__ */ jsx(
|
|
1001
|
+
"div",
|
|
1002
|
+
{
|
|
1003
|
+
...widget ? { onPointerDown: startDrag, onContextMenu: (e) => {
|
|
1004
|
+
e.preventDefault();
|
|
1005
|
+
setCtxMenu({ x: e.clientX, y: e.clientY });
|
|
1006
|
+
} } : {},
|
|
1007
|
+
className: `flex-1 min-h-0 flex flex-col ${widget ? "p-0 cursor-move" : compact ? "p-2" : "p-4"} ${widget ? "" : "backdrop-blur-sm"} ${bodyScroll === false ? "overflow-hidden" : "overflow-y-auto"} ${widget ? "rounded-lg select-none" : ""}`,
|
|
1008
|
+
style: { ...widget ? { touchAction: "none" } : {}, backgroundColor: widget ? "transparent" : isActive ? `rgb(var(--window-content-rgb) / var(--active-content-opacity, 0.9))` : `rgb(var(--window-content-rgb) / var(--inactive-content-opacity, 0.8))` },
|
|
1009
|
+
children
|
|
1010
|
+
}
|
|
1011
|
+
) }) }),
|
|
1012
|
+
widget && ctxMenu && /* @__PURE__ */ jsxs(PopupMenu, { minWidth: 160, style: { left: ctxMenu.x, top: ctxMenu.y }, onClose: () => setCtxMenu(null), children: [
|
|
1013
|
+
/* @__PURE__ */ jsx(PopupMenuItem, { onClick: () => {
|
|
1014
|
+
setCtxMenu(null);
|
|
1015
|
+
window.dispatchEvent(new CustomEvent("widget-open-settings", { detail: modalId }));
|
|
1016
|
+
}, children: "Settings" }),
|
|
1017
|
+
/* @__PURE__ */ jsx(PopupMenuDivider, {}),
|
|
1018
|
+
widgetMenu && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1019
|
+
widgetMenu,
|
|
1020
|
+
/* @__PURE__ */ jsx(PopupMenuDivider, {})
|
|
1021
|
+
] }),
|
|
1022
|
+
/* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => {
|
|
1023
|
+
setCtxMenu(null);
|
|
1024
|
+
setPinnedOnTop((p) => !p);
|
|
1025
|
+
}, children: [
|
|
1026
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1", children: "Always on Top" }),
|
|
1027
|
+
pinnedOnTop && /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-blue-600 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) })
|
|
1028
|
+
] }),
|
|
1029
|
+
/* @__PURE__ */ jsx(PopupMenuDivider, {}),
|
|
1030
|
+
/* @__PURE__ */ jsx(PopupMenuItem, { onClick: () => {
|
|
1031
|
+
setCtxMenu(null);
|
|
1032
|
+
guardedClose();
|
|
1033
|
+
}, children: "Close" })
|
|
1034
|
+
] }),
|
|
1035
|
+
/* @__PURE__ */ jsxs(
|
|
1036
|
+
"div",
|
|
1037
|
+
{
|
|
1038
|
+
onPointerDown: startDrag,
|
|
1039
|
+
className: `px-4 py-2 border-t border-gray-200 shrink-0 flex items-center justify-between text-xs select-none cursor-move${isActive ? " backdrop-blur-sm" : ""}${widget || compact || !footer && !hasActions && !actions && !actionsLeft ? " hidden" : ""}`,
|
|
1040
|
+
style: { touchAction: "none", backgroundColor: isActive ? `rgb(var(--window-footer-rgb) / var(--active-header-opacity, 0.8))` : `rgb(var(--window-footer-rgb) / var(--inactive-header-opacity, 0.7))` },
|
|
1041
|
+
children: [
|
|
1042
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
1043
|
+
actionsLeft,
|
|
1044
|
+
/* @__PURE__ */ jsx("div", { ref: actionsLeftRef, "data-modal-actions-left": true, className: "flex items-center gap-2" }),
|
|
1045
|
+
footer
|
|
1046
|
+
] }),
|
|
1047
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 ml-auto", children: [
|
|
1048
|
+
/* @__PURE__ */ jsx("div", { ref: actionsRef, "data-modal-actions": true, className: "flex items-center gap-2" }),
|
|
1049
|
+
actions
|
|
1050
|
+
] })
|
|
1051
|
+
]
|
|
1052
|
+
}
|
|
1053
|
+
),
|
|
1054
|
+
!widget && isActive && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1055
|
+
/* @__PURE__ */ jsx("div", { onPointerDown: (e) => startResizeCorner(e, "se"), className: "absolute bottom-0 right-0 w-3 h-3 cursor-nwse-resize z-10" }),
|
|
1056
|
+
/* @__PURE__ */ jsx("div", { onPointerDown: (e) => startResizeCorner(e, "sw"), className: "absolute bottom-0 left-0 w-3 h-3 cursor-nesw-resize z-10" }),
|
|
1057
|
+
/* @__PURE__ */ jsx("div", { onPointerDown: (e) => startResizeCorner(e, "ne"), className: "absolute top-0 right-0 w-3 h-3 cursor-nesw-resize z-10" }),
|
|
1058
|
+
/* @__PURE__ */ jsx("div", { onPointerDown: (e) => startResizeCorner(e, "nw"), className: "absolute top-0 left-0 w-3 h-3 cursor-nwse-resize z-10" }),
|
|
1059
|
+
/* @__PURE__ */ jsx("div", { onPointerDown: (e) => startResizeCorner(e, "n"), className: "absolute top-0 left-3 right-3 h-1 cursor-ns-resize" }),
|
|
1060
|
+
/* @__PURE__ */ jsx("div", { onPointerDown: (e) => startResizeCorner(e, "s"), className: "absolute bottom-0 left-3 right-3 h-1 cursor-ns-resize" }),
|
|
1061
|
+
/* @__PURE__ */ jsx("div", { onPointerDown: (e) => startResizeCorner(e, "e"), className: "absolute top-3 bottom-3 right-0 w-1 cursor-ew-resize" }),
|
|
1062
|
+
/* @__PURE__ */ jsx("div", { onPointerDown: (e) => startResizeCorner(e, "w"), className: "absolute top-3 bottom-3 left-0 w-1 cursor-ew-resize" })
|
|
1063
|
+
] })
|
|
1064
|
+
]
|
|
1065
|
+
}
|
|
1066
|
+
) });
|
|
1067
|
+
const windowMenuEl = windowMenu && /* @__PURE__ */ jsxs(PopupMenu, { style: { left: windowMenu.x, top: windowMenu.y }, onClose: () => setWindowMenu(null), minWidth: 160, children: [
|
|
1068
|
+
!widget && !compact && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1069
|
+
/* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => {
|
|
1070
|
+
const idx = activationOrder.indexOf(modalId);
|
|
1071
|
+
if (idx !== -1) activationOrder.splice(idx, 1);
|
|
1072
|
+
activeListeners.forEach((fn) => fn());
|
|
1073
|
+
window.dispatchEvent(new CustomEvent("modal-reorder"));
|
|
1074
|
+
setWindowMenu(null);
|
|
1075
|
+
}, children: [
|
|
1076
|
+
/* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 12h-15" }) }),
|
|
1077
|
+
"Minimize"
|
|
1078
|
+
] }),
|
|
1079
|
+
maximized ? /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => {
|
|
1080
|
+
setMaximized(false);
|
|
1081
|
+
setBox(calcWindowed());
|
|
1082
|
+
setWindowMenu(null);
|
|
1083
|
+
}, children: [
|
|
1084
|
+
/* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 4.5v15m6-15v15M4.5 9h15M4.5 15h15" }) }),
|
|
1085
|
+
"Windowed"
|
|
1086
|
+
] }) : /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => {
|
|
1087
|
+
reset();
|
|
1088
|
+
setWindowMenu(null);
|
|
1089
|
+
}, children: [
|
|
1090
|
+
/* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" }) }),
|
|
1091
|
+
"Maximize"
|
|
1092
|
+
] })
|
|
1093
|
+
] }),
|
|
1094
|
+
allowPinOnTop && /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => {
|
|
1095
|
+
setPinnedOnTop((p) => !p);
|
|
1096
|
+
setWindowMenu(null);
|
|
1097
|
+
}, children: [
|
|
1098
|
+
/* @__PURE__ */ jsx("svg", { className: `h-4 w-4 ${pinnedOnTop ? "text-blue-600" : "text-gray-400"}`, fill: pinnedOnTop ? "currentColor" : "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.5 3.75V16.5L12 14.25 7.5 16.5V3.75m9 0H18A2.25 2.25 0 0120.25 6v12A2.25 2.25 0 0118 20.25H6A2.25 2.25 0 013.75 18V6A2.25 2.25 0 016 3.75h1.5m9 0h-9" }) }),
|
|
1099
|
+
pinnedOnTop ? "Unpin from Top" : "Pin on Top"
|
|
1100
|
+
] }),
|
|
1101
|
+
(_extraMenuItems[modalId] || []).length > 0 && /* @__PURE__ */ jsx(Fragment, { children: (_extraMenuItems[modalId] || []).map((item, i) => /* @__PURE__ */ jsxs(PopupMenuItem, { onClick: () => {
|
|
1102
|
+
setWindowMenu(null);
|
|
1103
|
+
item.onClick();
|
|
1104
|
+
}, children: [
|
|
1105
|
+
item.icon,
|
|
1106
|
+
item.label
|
|
1107
|
+
] }, i)) }),
|
|
1108
|
+
/* @__PURE__ */ jsx(PopupMenuDivider, {}),
|
|
1109
|
+
/* @__PURE__ */ jsxs(PopupMenuItem, { danger: true, onClick: () => {
|
|
1110
|
+
setWindowMenu(null);
|
|
1111
|
+
guardedClose();
|
|
1112
|
+
}, children: [
|
|
1113
|
+
/* @__PURE__ */ jsx(XMarkIcon, { className: "h-4 w-4" }),
|
|
1114
|
+
"Close"
|
|
1115
|
+
] })
|
|
1116
|
+
] });
|
|
1117
|
+
return createPortal(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1118
|
+
content,
|
|
1119
|
+
windowMenuEl
|
|
1120
|
+
] }), document.body);
|
|
1121
|
+
}
|
|
1122
|
+
function LoadingSpinner() {
|
|
1123
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-8 text-sm text-gray-400", children: "Loading..." });
|
|
1124
|
+
}
|
|
1125
|
+
var MinimizedContext = createContext({
|
|
1126
|
+
openWindows: [],
|
|
1127
|
+
openEntity: () => {
|
|
1128
|
+
},
|
|
1129
|
+
openPage: () => {
|
|
1130
|
+
},
|
|
1131
|
+
closeEntity: () => {
|
|
1132
|
+
},
|
|
1133
|
+
minimize: () => {
|
|
1134
|
+
},
|
|
1135
|
+
items: [],
|
|
1136
|
+
restore: () => {
|
|
1137
|
+
},
|
|
1138
|
+
remove: () => {
|
|
1139
|
+
},
|
|
1140
|
+
restoreIfMinimized: () => false,
|
|
1141
|
+
openItems: []
|
|
1142
|
+
});
|
|
1143
|
+
function useWindowManager() {
|
|
1144
|
+
return useContext(MinimizedContext);
|
|
1145
|
+
}
|
|
1146
|
+
function DesktopShortcutMenuItem({ item }) {
|
|
1147
|
+
const queryClient = useQueryClient();
|
|
1148
|
+
const { data: profile } = useQuery({ queryKey: ["my-profile-sidebar"], queryFn: () => client_default.get("/auth/me/").then((r) => r.data) });
|
|
1149
|
+
const favDocs = (profile?.preferences || {}).favorite_documents || [];
|
|
1150
|
+
const favType = item.type === "page" ? "page" : item.entityType || "";
|
|
1151
|
+
const favId = item.type === "page" ? item.route || "" : item.entityId || "";
|
|
1152
|
+
const isFav = favDocs.some((d) => d.entityType === favType && d.entityId === favId);
|
|
1153
|
+
const toggle = useCallback(() => {
|
|
1154
|
+
const next = isFav ? favDocs.filter((d) => !(d.entityType === favType && d.entityId === favId)) : [...favDocs, { entityType: favType, entityId: favId, label: item.label }];
|
|
1155
|
+
client_default.patch("/auth/me/", { preferences: { favorite_documents: next } }).then(() => {
|
|
1156
|
+
queryClient.invalidateQueries({ queryKey: ["my-profile-sidebar"] });
|
|
1157
|
+
});
|
|
1158
|
+
}, [isFav, favDocs, favType, favId, item.label, queryClient]);
|
|
1159
|
+
const icon = useMemo(() => /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: isFav ? "currentColor" : "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" }) }), [isFav]);
|
|
1160
|
+
useWindowMenuItem(isFav ? "Remove from Desktop" : "Add to Desktop", toggle, icon);
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
function PageWindow({ item, onClose }) {
|
|
1164
|
+
const raw = WINDOW_REGISTRY[item.route];
|
|
1165
|
+
if (!raw || !isPageEntry(raw)) return null;
|
|
1166
|
+
const entry = raw;
|
|
1167
|
+
const Component = entry.component;
|
|
1168
|
+
return /* @__PURE__ */ jsxs(Modal, { open: true, onClose, icon: navIcons[item.route], title: entry.label, size: entry.size || "2xl", allowPinOnTop: entry.allowPinOnTop, initialPosition: entry.initialPosition, widget: entry.widget, compact: entry.compact, autoHeight: entry.autoHeight, dimensions: entry.dimensions, windowKey: item.id, children: [
|
|
1169
|
+
/* @__PURE__ */ jsx(DesktopShortcutMenuItem, { item }),
|
|
1170
|
+
/* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx(LoadingSpinner, {}) }), children: /* @__PURE__ */ jsx(Component, {}) })
|
|
1171
|
+
] });
|
|
1172
|
+
}
|
|
1173
|
+
function DocFavStar({ entityType, entityId, label }) {
|
|
1174
|
+
const queryClient = useQueryClient();
|
|
1175
|
+
const { data: profile } = useQuery({ queryKey: ["my-profile-sidebar"], queryFn: () => client_default.get("/auth/me/").then((r) => r.data) });
|
|
1176
|
+
const favDocs = (profile?.preferences || {}).favorite_documents || [];
|
|
1177
|
+
const isFav = favDocs.some((d) => d.entityType === entityType && d.entityId === entityId);
|
|
1178
|
+
const toggle = () => {
|
|
1179
|
+
const next = isFav ? favDocs.filter((d) => !(d.entityType === entityType && d.entityId === entityId)) : [...favDocs, { entityType, entityId, label }];
|
|
1180
|
+
client_default.patch("/auth/me/", { preferences: { favorite_documents: next } }).then(() => {
|
|
1181
|
+
queryClient.invalidateQueries({ queryKey: ["my-profile-sidebar"] });
|
|
1182
|
+
});
|
|
1183
|
+
};
|
|
1184
|
+
return /* @__PURE__ */ jsx(
|
|
1185
|
+
"button",
|
|
1186
|
+
{
|
|
1187
|
+
onClick: toggle,
|
|
1188
|
+
title: isFav ? "Remove from desktop" : "Add to desktop",
|
|
1189
|
+
className: `shrink-0 transition-colors ${isFav ? "text-yellow-500 hover:text-yellow-600" : "text-gray-300 hover:text-yellow-400"}`,
|
|
1190
|
+
children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: isFav ? "currentColor" : "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" }) })
|
|
1191
|
+
}
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
function RestoredRegistryModal({ item, onClose, onMinimize }) {
|
|
1195
|
+
const raw = WINDOW_REGISTRY[item.entityType];
|
|
1196
|
+
if (!raw || !isEntityEntry(raw)) return null;
|
|
1197
|
+
const entry = raw;
|
|
1198
|
+
const [editing, setEditing] = useState(false);
|
|
1199
|
+
const qkPrefix = entry.queryKey || entry.endpoint.replace(/^\/|\/$/g, "").split("/").pop() || item.entityType;
|
|
1200
|
+
const isDuplicate = item.entitySnapshot?._duplicate;
|
|
1201
|
+
const { data: entity, isLoading, refetch } = useQuery({
|
|
1202
|
+
queryKey: [qkPrefix, item.entityId],
|
|
1203
|
+
queryFn: () => client_default.get(`${entry.endpoint}${item.entityId}/`).then((r) => r.data),
|
|
1204
|
+
initialData: item.entitySnapshot,
|
|
1205
|
+
initialDataUpdatedAt: 0,
|
|
1206
|
+
// Treat snapshot as stale so query refetches immediately
|
|
1207
|
+
enabled: !entry.selfFetching && !isDuplicate,
|
|
1208
|
+
staleTime: 0,
|
|
1209
|
+
refetchOnWindowFocus: true,
|
|
1210
|
+
refetchOnMount: "always",
|
|
1211
|
+
refetchInterval: 6e4,
|
|
1212
|
+
// Fallback polling; WebSocket provides instant updates
|
|
1213
|
+
refetchIntervalInBackground: false
|
|
1214
|
+
});
|
|
1215
|
+
useEffect(() => {
|
|
1216
|
+
if (entry.selfFetching || isDuplicate) return;
|
|
1217
|
+
const handler = () => {
|
|
1218
|
+
refetch();
|
|
1219
|
+
};
|
|
1220
|
+
window.addEventListener("modal-reorder", handler);
|
|
1221
|
+
return () => window.removeEventListener("modal-reorder", handler);
|
|
1222
|
+
}, [refetch, entry.selfFetching, isDuplicate]);
|
|
1223
|
+
const handleClose = useCallback(() => {
|
|
1224
|
+
if (editing) {
|
|
1225
|
+
setEditing(false);
|
|
1226
|
+
} else {
|
|
1227
|
+
onClose();
|
|
1228
|
+
}
|
|
1229
|
+
}, [editing, onClose]);
|
|
1230
|
+
const titleBase = entry.selfFetching ? item.label : entity ? entry.title(entity, editing, setEditing) : item.label;
|
|
1231
|
+
const entityIconKey = entry.icon;
|
|
1232
|
+
const entityIcon = entityIconKey ? navIcons[entityIconKey] : null;
|
|
1233
|
+
const titleContent = titleBase;
|
|
1234
|
+
const footerContent = !entry.selfFetching && entity && entry.footer && !isDuplicate ? entry.footer(entity) : void 0;
|
|
1235
|
+
if (entry.rendersOwnModal) {
|
|
1236
|
+
if (entry.selfFetching) {
|
|
1237
|
+
return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(LoadingSpinner, {}), children: entry.render(entity ?? item.entitySnapshot, handleClose, item.entityId, editing, setEditing) });
|
|
1238
|
+
}
|
|
1239
|
+
return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(LoadingSpinner, {}), children: isLoading && !entity ? /* @__PURE__ */ jsx(LoadingSpinner, {}) : entity ? entry.render(entity, handleClose, item.entityId, editing, setEditing) : /* @__PURE__ */ jsx(Modal, { open: true, onClose, title: item.label, size: entry.size || "2xl", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 py-8 text-center", children: "Not found." }) }) });
|
|
1240
|
+
}
|
|
1241
|
+
return /* @__PURE__ */ jsxs(
|
|
1242
|
+
Modal,
|
|
1243
|
+
{
|
|
1244
|
+
open: true,
|
|
1245
|
+
onClose: handleClose,
|
|
1246
|
+
onMinimize,
|
|
1247
|
+
initialBox: item.savedBox,
|
|
1248
|
+
icon: entityIcon,
|
|
1249
|
+
title: titleContent,
|
|
1250
|
+
footer: footerContent,
|
|
1251
|
+
copyText: item.id,
|
|
1252
|
+
windowKey: item.id,
|
|
1253
|
+
size: entry.size || "2xl",
|
|
1254
|
+
children: [
|
|
1255
|
+
/* @__PURE__ */ jsx(DesktopShortcutMenuItem, { item }),
|
|
1256
|
+
/* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(LoadingSpinner, {}), children: entry.selfFetching ? entry.render(null, handleClose, item.entityId, editing, setEditing) : isLoading && !entity ? /* @__PURE__ */ jsx(LoadingSpinner, {}) : entity ? entry.render(entity, handleClose, item.entityId, editing, setEditing) : /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 py-8 text-center", children: "Not found." }) })
|
|
1257
|
+
]
|
|
1258
|
+
}
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
function TaskbarWindows({ openWindows, onRemove, onCloseAll, onSplitView, onActivate }) {
|
|
1262
|
+
const [target, setTarget] = useState(null);
|
|
1263
|
+
useEffect(() => {
|
|
1264
|
+
const el = document.getElementById("taskbar-windows");
|
|
1265
|
+
if (el) setTarget(el);
|
|
1266
|
+
else {
|
|
1267
|
+
const t = setInterval(() => {
|
|
1268
|
+
const e = document.getElementById("taskbar-windows");
|
|
1269
|
+
if (e) {
|
|
1270
|
+
setTarget(e);
|
|
1271
|
+
clearInterval(t);
|
|
1272
|
+
}
|
|
1273
|
+
}, 100);
|
|
1274
|
+
return () => clearInterval(t);
|
|
1275
|
+
}
|
|
1276
|
+
}, []);
|
|
1277
|
+
const activeModalId = useSyncExternalStore(subscribeActive, getActiveModalId);
|
|
1278
|
+
const tabWindows = openWindows.filter((item) => !item.route || !WINDOW_REGISTRY[item.route]?.utility);
|
|
1279
|
+
if (!target || tabWindows.length === 0) return null;
|
|
1280
|
+
return createPortal(
|
|
1281
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1282
|
+
tabWindows.map((item) => {
|
|
1283
|
+
const icon = item.route ? navIcons[item.route] : null;
|
|
1284
|
+
let isActive = false;
|
|
1285
|
+
if (activeModalId) {
|
|
1286
|
+
const panel = document.querySelector(`[data-modal-id="${activeModalId}"]`);
|
|
1287
|
+
if (panel) {
|
|
1288
|
+
const titleEl = panel.querySelector(".text-lg, .text-sm.font-medium");
|
|
1289
|
+
if (titleEl?.textContent?.includes(item.label)) isActive = true;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
return /* @__PURE__ */ jsxs(
|
|
1293
|
+
"button",
|
|
1294
|
+
{
|
|
1295
|
+
onClick: () => onActivate(item.label),
|
|
1296
|
+
onDoubleClick: (e) => {
|
|
1297
|
+
e.stopPropagation();
|
|
1298
|
+
window.dispatchEvent(new CustomEvent("modal-center", { detail: { label: item.label } }));
|
|
1299
|
+
},
|
|
1300
|
+
onContextMenu: (e) => {
|
|
1301
|
+
e.preventDefault();
|
|
1302
|
+
e.stopPropagation();
|
|
1303
|
+
window.dispatchEvent(new CustomEvent("modal-context-menu", { detail: { label: item.label, x: e.clientX, y: e.clientY } }));
|
|
1304
|
+
},
|
|
1305
|
+
style: { width: "var(--window-tab-width, 200px)", fontSize: "var(--window-tab-font-size, 12px)" },
|
|
1306
|
+
className: `group flex items-center gap-1.5 rounded-lg px-3 py-2 font-medium transition-all min-w-0 shrink ${isActive ? "bg-blue-100/60 border border-blue-400/60 text-blue-700" : "bg-gray-50/40 border border-gray-200/40 text-gray-700 hover:bg-gray-200/40"}`,
|
|
1307
|
+
children: [
|
|
1308
|
+
icon && isValidElement(icon) ? cloneElement(icon, { className: `h-3.5 w-3.5 shrink-0 ${isActive ? "text-blue-600" : "text-gray-400"}` }) : /* @__PURE__ */ jsx("svg", { className: `h-3.5 w-3.5 shrink-0 ${isActive ? "text-blue-600" : "text-gray-400"}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" }) }),
|
|
1309
|
+
/* @__PURE__ */ jsx("span", { className: "truncate flex-1", children: item.label }),
|
|
1310
|
+
/* @__PURE__ */ jsx("span", { role: "button", onClick: (e) => {
|
|
1311
|
+
e.stopPropagation();
|
|
1312
|
+
onRemove(item.id);
|
|
1313
|
+
}, className: "ml-auto text-gray-400 hover:text-red-500 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity", children: /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) }) })
|
|
1314
|
+
]
|
|
1315
|
+
},
|
|
1316
|
+
item.id
|
|
1317
|
+
);
|
|
1318
|
+
}),
|
|
1319
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
1320
|
+
tabWindows.length >= 2 && /* @__PURE__ */ jsxs(
|
|
1321
|
+
"button",
|
|
1322
|
+
{
|
|
1323
|
+
onClick: onSplitView,
|
|
1324
|
+
className: "flex items-center gap-1 rounded px-2 py-1 text-[11px] font-medium text-blue-600 border border-blue-300 hover:bg-blue-50 transition-colors shrink-0",
|
|
1325
|
+
children: [
|
|
1326
|
+
/* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 4H5a1 1 0 00-1 1v14a1 1 0 001 1h4m6-16h4a1 1 0 011 1v14a1 1 0 01-1 1h-4m-6 0V4" }) }),
|
|
1327
|
+
"Split"
|
|
1328
|
+
]
|
|
1329
|
+
}
|
|
1330
|
+
)
|
|
1331
|
+
] }),
|
|
1332
|
+
target
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
var SESSION_KEY = "erp_open_windows";
|
|
1336
|
+
function saveWindowState(windows) {
|
|
1337
|
+
try {
|
|
1338
|
+
const serializable = windows.map((w) => ({ ...w, entitySnapshot: void 0 }));
|
|
1339
|
+
localStorage.setItem(SESSION_KEY, JSON.stringify(serializable));
|
|
1340
|
+
} catch {
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
var DEFAULT_WIDGETS = [
|
|
1344
|
+
{ id: "page:/weather", type: "page", label: "Weather", route: "/weather" },
|
|
1345
|
+
{ id: "page:/currency", type: "page", label: "Currency", route: "/currency" }
|
|
1346
|
+
];
|
|
1347
|
+
function restoreWindowState() {
|
|
1348
|
+
try {
|
|
1349
|
+
if (window.location.pathname === "/login") return [];
|
|
1350
|
+
if (!localStorage.getItem("access_token")) return [];
|
|
1351
|
+
const stored = localStorage.getItem(SESSION_KEY);
|
|
1352
|
+
if (stored) return JSON.parse(stored);
|
|
1353
|
+
} catch {
|
|
1354
|
+
}
|
|
1355
|
+
return DEFAULT_WIDGETS;
|
|
1356
|
+
}
|
|
1357
|
+
var AUTH_PAGES = ["/login", "/forgot-password", "/reset-password", "/force-change-password"];
|
|
1358
|
+
function WindowManagerProvider({ children }) {
|
|
1359
|
+
const location = useLocation();
|
|
1360
|
+
const isAuthPage = AUTH_PAGES.some((p) => location.pathname.startsWith(p));
|
|
1361
|
+
const [openWindows, setOpenWindows] = useState(() => restoreWindowState());
|
|
1362
|
+
const hasUserActed = useRef(false);
|
|
1363
|
+
useEffect(() => {
|
|
1364
|
+
if (openWindows.length > 0) hasUserActed.current = true;
|
|
1365
|
+
if (hasUserActed.current) saveWindowState(openWindows);
|
|
1366
|
+
}, [openWindows]);
|
|
1367
|
+
const closeEntity = useCallback((id) => {
|
|
1368
|
+
setOpenWindows((prev) => prev.filter((m) => m.id !== id));
|
|
1369
|
+
}, []);
|
|
1370
|
+
const openEntity = useCallback((entityType, entityId, snapshot, label, route) => {
|
|
1371
|
+
if (!WINDOW_REGISTRY[entityType] || !isEntityEntry(WINDOW_REGISTRY[entityType])) return;
|
|
1372
|
+
const id = label || entityId;
|
|
1373
|
+
setOpenWindows((prev) => {
|
|
1374
|
+
const existing = prev.find((m) => m.entityId === entityId && m.entityType === entityType);
|
|
1375
|
+
if (existing) {
|
|
1376
|
+
setTimeout(() => {
|
|
1377
|
+
const panels = document.querySelectorAll("[data-modal-panel]");
|
|
1378
|
+
panels.forEach((p) => {
|
|
1379
|
+
const titleEl = p.querySelector(".text-lg");
|
|
1380
|
+
if (titleEl?.textContent?.includes(existing.label)) {
|
|
1381
|
+
const mid = p.getAttribute("data-modal-id");
|
|
1382
|
+
if (mid) activateModal(mid);
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
}, 50);
|
|
1386
|
+
return prev;
|
|
1387
|
+
}
|
|
1388
|
+
return [...prev, {
|
|
1389
|
+
id,
|
|
1390
|
+
type: "modal",
|
|
1391
|
+
label: label || entityId,
|
|
1392
|
+
route: route || window.location.pathname,
|
|
1393
|
+
entityType,
|
|
1394
|
+
entityId,
|
|
1395
|
+
entitySnapshot: snapshot
|
|
1396
|
+
}];
|
|
1397
|
+
});
|
|
1398
|
+
}, []);
|
|
1399
|
+
const openPage = useCallback((path) => {
|
|
1400
|
+
if (!WINDOW_REGISTRY[path] || !isPageEntry(WINDOW_REGISTRY[path])) return;
|
|
1401
|
+
const entry = WINDOW_REGISTRY[path];
|
|
1402
|
+
setOpenWindows((prev) => {
|
|
1403
|
+
const existing = prev.find((m) => m.type === "page" && m.route === path);
|
|
1404
|
+
if (existing) {
|
|
1405
|
+
if (entry.widget) {
|
|
1406
|
+
return prev.filter((m) => m !== existing);
|
|
1407
|
+
}
|
|
1408
|
+
setTimeout(() => {
|
|
1409
|
+
const panels = document.querySelectorAll("[data-modal-panel]");
|
|
1410
|
+
panels.forEach((p) => {
|
|
1411
|
+
const titleEl = p.querySelector(".text-lg");
|
|
1412
|
+
if (titleEl?.textContent?.includes(existing.label)) {
|
|
1413
|
+
const mid = p.getAttribute("data-modal-id");
|
|
1414
|
+
if (mid) activateModal(mid);
|
|
1415
|
+
}
|
|
1416
|
+
});
|
|
1417
|
+
}, 50);
|
|
1418
|
+
return prev;
|
|
1419
|
+
}
|
|
1420
|
+
return [...prev, {
|
|
1421
|
+
id: `page:${path}`,
|
|
1422
|
+
type: "page",
|
|
1423
|
+
label: entry.label,
|
|
1424
|
+
route: path
|
|
1425
|
+
}];
|
|
1426
|
+
});
|
|
1427
|
+
}, []);
|
|
1428
|
+
const minimize = useCallback(() => {
|
|
1429
|
+
}, []);
|
|
1430
|
+
const restore = useCallback(() => {
|
|
1431
|
+
}, []);
|
|
1432
|
+
const remove = closeEntity;
|
|
1433
|
+
const restoreIfMinimized = () => false;
|
|
1434
|
+
return /* @__PURE__ */ jsxs(MinimizedContext.Provider, { value: { openWindows, openEntity, openPage, closeEntity, minimize, items: [], openItems: openWindows, restore, remove, restoreIfMinimized }, children: [
|
|
1435
|
+
children,
|
|
1436
|
+
/* @__PURE__ */ jsx(
|
|
1437
|
+
TaskbarWindows,
|
|
1438
|
+
{
|
|
1439
|
+
openWindows,
|
|
1440
|
+
onRemove: closeEntity,
|
|
1441
|
+
onCloseAll: () => setOpenWindows([]),
|
|
1442
|
+
onSplitView: triggerSplitView,
|
|
1443
|
+
onActivate: (label) => {
|
|
1444
|
+
const panels = document.querySelectorAll("[data-modal-panel]");
|
|
1445
|
+
panels.forEach((p) => {
|
|
1446
|
+
const titleEl = p.querySelector(".text-lg");
|
|
1447
|
+
if (titleEl?.textContent?.includes(label)) {
|
|
1448
|
+
const mid = p.getAttribute("data-modal-id");
|
|
1449
|
+
if (mid) activateModal(mid);
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
),
|
|
1455
|
+
!isAuthPage && openWindows.map((item) => item.type === "page" ? /* @__PURE__ */ jsx(PageWindow, { item, onClose: () => closeEntity(item.id) }, item.id) : /* @__PURE__ */ jsx(
|
|
1456
|
+
RestoredRegistryModal,
|
|
1457
|
+
{
|
|
1458
|
+
item,
|
|
1459
|
+
onClose: () => closeEntity(item.id),
|
|
1460
|
+
onMinimize: () => {
|
|
1461
|
+
}
|
|
1462
|
+
},
|
|
1463
|
+
item.id
|
|
1464
|
+
))
|
|
1465
|
+
] });
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
export { CancelButton, CopyButton, DocFavStar, GLASS_DIVIDER, GLASS_INPUT_BG, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, WindowManagerProvider, WindowTitle, client_default, glassStyle, isEntityEntry, isPageEntry, isSection, navIcons, navSections, sectionIcons, setShellApiClient, setShellNavIcons, setShellWindowRegistry, startMenuCategories, useModalActive, useWidgetSettings, useWindowManager, useWindowMenuItem, useWindowTitle };
|
|
1469
|
+
//# sourceMappingURL=chunk-WQIS72NL.js.map
|
|
1470
|
+
//# sourceMappingURL=chunk-WQIS72NL.js.map
|