react-os-shell 4.15.0 → 4.21.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/README.md +46 -0
- package/dist/{Browser-XV34CJFP.js → Browser-6ACRB3XK.js} +9 -6
- package/dist/Browser-6ACRB3XK.js.map +1 -0
- package/dist/{Calculator-FFCYNYU3.js → Calculator-QCAJ6ITI.js} +8 -5
- package/dist/Calculator-QCAJ6ITI.js.map +1 -0
- package/dist/ConfirmDialog-WAM5OKAV.js +3 -0
- package/dist/{ConfirmDialog-CXHJCDUL.js.map → ConfirmDialog-WAM5OKAV.js.map} +1 -1
- package/dist/{CurrencyConverter-3NCK7WY6.js → CurrencyConverter-PCYC6RL5.js} +8 -5
- package/dist/CurrencyConverter-PCYC6RL5.js.map +1 -0
- package/dist/{Documents-S6EQOLUF.js → Documents-XME7555L.js} +10 -7
- package/dist/Documents-XME7555L.js.map +1 -0
- package/dist/{Files-TFZNYXAH.js → Files-6Q4HZKZD.js} +16 -12
- package/dist/Files-6Q4HZKZD.js.map +1 -0
- package/dist/{Notepad-EAA6CXZX.js → Notepad-W6LXMKWD.js} +10 -7
- package/dist/Notepad-W6LXMKWD.js.map +1 -0
- package/dist/{PomodoroTimer-BXI752XM.js → PomodoroTimer-YABFEPEV.js} +10 -7
- package/dist/PomodoroTimer-YABFEPEV.js.map +1 -0
- package/dist/{Preview-ZKYM7A5X.js → Preview-TYJQ6ASR.js} +11 -8
- package/dist/Preview-TYJQ6ASR.js.map +1 -0
- package/dist/{Sidebar-IMACKSHS.js → Sidebar-B3LY5T2U.js} +5 -4
- package/dist/Sidebar-B3LY5T2U.js.map +1 -0
- package/dist/{Spreadsheet-BXOPROMB.js → Spreadsheet-TAO2PWRL.js} +10 -7
- package/dist/Spreadsheet-TAO2PWRL.js.map +1 -0
- package/dist/{Stock-BEQCSYNT.js → Stock-IPLLRUOC.js} +8 -5
- package/dist/Stock-IPLLRUOC.js.map +1 -0
- package/dist/{Weather-QPV6G7UN.js → Weather-SI5QN7X2.js} +8 -5
- package/dist/Weather-SI5QN7X2.js.map +1 -0
- package/dist/{WorldClock-I3ZE57V4.js → WorldClock-WYDSMUWX.js} +8 -5
- package/dist/WorldClock-WYDSMUWX.js.map +1 -0
- package/dist/apps/index.js +22 -19
- package/dist/apps/index.js.map +1 -1
- package/dist/chunk-22BNPLM4.js +46 -0
- package/dist/chunk-22BNPLM4.js.map +1 -0
- package/dist/{chunk-IVLDAUYM.js → chunk-3CUSBZWG.js} +3 -56
- package/dist/chunk-3CUSBZWG.js.map +1 -0
- package/dist/{chunk-VENYVK3L.js → chunk-4AKVHY4B.js} +48 -12
- package/dist/chunk-4AKVHY4B.js.map +1 -0
- package/dist/chunk-4OPGKWMT.js +7 -0
- package/dist/{chunk-F562QDVS.js.map → chunk-4OPGKWMT.js.map} +1 -1
- package/dist/chunk-C22EFSND.js +188 -0
- package/dist/chunk-C22EFSND.js.map +1 -0
- package/dist/{chunk-H4IEH6FK.js → chunk-EWDYCO65.js} +3 -3
- package/dist/{chunk-H4IEH6FK.js.map → chunk-EWDYCO65.js.map} +1 -1
- package/dist/{chunk-DINNWYVB.js → chunk-HYAW6KIL.js} +4 -45
- package/dist/chunk-HYAW6KIL.js.map +1 -0
- package/dist/{chunk-H3HFM4JQ.js → chunk-IGPSRRL7.js} +262 -712
- package/dist/chunk-IGPSRRL7.js.map +1 -0
- package/dist/chunk-L2O6B5KT.js +394 -0
- package/dist/chunk-L2O6B5KT.js.map +1 -0
- package/dist/{chunk-YS4ZW2P7.js → chunk-OI253GOA.js} +4 -4
- package/dist/{chunk-YS4ZW2P7.js.map → chunk-OI253GOA.js.map} +1 -1
- package/dist/{chunk-ZPCGIR62.js → chunk-OQ7GRU7G.js} +3 -3
- package/dist/{chunk-ZPCGIR62.js.map → chunk-OQ7GRU7G.js.map} +1 -1
- package/dist/{chunk-7J3HTJJ3.js → chunk-Q3AOIJ7B.js} +3 -3
- package/dist/{chunk-7J3HTJJ3.js.map → chunk-Q3AOIJ7B.js.map} +1 -1
- package/dist/{chunk-N2UW3CAS.js → chunk-QJS6RBH4.js} +3 -3
- package/dist/{chunk-N2UW3CAS.js.map → chunk-QJS6RBH4.js.map} +1 -1
- package/dist/chunk-UPGCISED.js +5015 -0
- package/dist/chunk-UPGCISED.js.map +1 -0
- package/dist/{chunk-LFTHOYKW.js → chunk-VX634UB2.js} +5 -5
- package/dist/{chunk-LFTHOYKW.js.map → chunk-VX634UB2.js.map} +1 -1
- package/dist/chunk-WNLPTLT6.js +56 -0
- package/dist/chunk-WNLPTLT6.js.map +1 -0
- package/dist/{chunk-QU5L5ZYM.js → chunk-X5PZYTAK.js} +3 -3
- package/dist/{chunk-QU5L5ZYM.js.map → chunk-X5PZYTAK.js.map} +1 -1
- package/dist/index-Czn1re5M.d.ts +1939 -0
- package/dist/index.d.ts +7 -1375
- package/dist/index.js +159 -4082
- package/dist/index.js.map +1 -1
- package/dist/shell.css +105 -0
- package/dist/styles.css +12 -501
- package/dist/ui/index.d.ts +3 -0
- package/dist/ui/index.js +11 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui.css +425 -0
- package/package.json +7 -2
- package/dist/Browser-XV34CJFP.js.map +0 -1
- package/dist/Calculator-FFCYNYU3.js.map +0 -1
- package/dist/ConfirmDialog-CXHJCDUL.js +0 -4
- package/dist/CurrencyConverter-3NCK7WY6.js.map +0 -1
- package/dist/Documents-S6EQOLUF.js.map +0 -1
- package/dist/Files-TFZNYXAH.js.map +0 -1
- package/dist/Notepad-EAA6CXZX.js.map +0 -1
- package/dist/PomodoroTimer-BXI752XM.js.map +0 -1
- package/dist/Preview-ZKYM7A5X.js.map +0 -1
- package/dist/Sidebar-IMACKSHS.js.map +0 -1
- package/dist/Spreadsheet-BXOPROMB.js.map +0 -1
- package/dist/Stock-BEQCSYNT.js.map +0 -1
- package/dist/Weather-QPV6G7UN.js.map +0 -1
- package/dist/WorldClock-I3ZE57V4.js.map +0 -1
- package/dist/chunk-DINNWYVB.js.map +0 -1
- package/dist/chunk-F562QDVS.js +0 -7
- package/dist/chunk-H3HFM4JQ.js.map +0 -1
- package/dist/chunk-IVLDAUYM.js.map +0 -1
- package/dist/chunk-VENYVK3L.js.map +0 -1
|
@@ -1,64 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { useIsMobile, useShellPrefs, PopupMenu, PopupMenuItem, PopupMenuDivider } from './chunk-C22EFSND.js';
|
|
2
|
+
import { confirm, runEscapeInterceptors } from './chunk-L2O6B5KT.js';
|
|
3
|
+
import { useShellAuth, navIcons } from './chunk-WNLPTLT6.js';
|
|
4
|
+
import { createContext, Component, useContext, useRef, useLayoutEffect, useState, useEffect, useCallback, useSyncExternalStore, useMemo, isValidElement, cloneElement, useReducer, useId, Suspense } from 'react';
|
|
5
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
5
6
|
import { createPortal } from 'react-dom';
|
|
7
|
+
import { XMarkIcon } from '@heroicons/react/24/outline';
|
|
6
8
|
import { useLocation } from 'react-router-dom';
|
|
7
9
|
import { useQueryClient, useQuery } from '@tanstack/react-query';
|
|
8
|
-
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
9
|
-
|
|
10
|
-
// src/api/client.ts
|
|
11
|
-
var _apiClient = null;
|
|
12
|
-
function setShellApiClient(instance) {
|
|
13
|
-
_apiClient = instance;
|
|
14
|
-
}
|
|
15
|
-
function isShellApiClientConfigured() {
|
|
16
|
-
return _apiClient !== null;
|
|
17
|
-
}
|
|
18
|
-
var NOOP_METHODS = /* @__PURE__ */ new Set([
|
|
19
|
-
"get",
|
|
20
|
-
"post",
|
|
21
|
-
"put",
|
|
22
|
-
"patch",
|
|
23
|
-
"delete",
|
|
24
|
-
"head",
|
|
25
|
-
"options",
|
|
26
|
-
"request"
|
|
27
|
-
]);
|
|
28
|
-
var apiClient = new Proxy({}, {
|
|
29
|
-
get(_t, prop) {
|
|
30
|
-
if (_apiClient) return _apiClient[prop];
|
|
31
|
-
if (typeof prop === "string" && NOOP_METHODS.has(prop)) {
|
|
32
|
-
return () => Promise.resolve({ data: null, status: 0, statusText: "", headers: {}, config: {} });
|
|
33
|
-
}
|
|
34
|
-
return void 0;
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
var client_default = apiClient;
|
|
38
|
-
|
|
39
|
-
// src/shell/entityFetchPolicy.ts
|
|
40
|
-
var RETRYABLE_CLIENT_STATUSES = /* @__PURE__ */ new Set([
|
|
41
|
-
408,
|
|
42
|
-
// Request Timeout
|
|
43
|
-
429
|
|
44
|
-
// Too Many Requests
|
|
45
|
-
]);
|
|
46
|
-
var ENTITY_FETCH_MAX_RETRIES = 3;
|
|
47
|
-
var ENTITY_REFETCH_INTERVAL_MS = 6e4;
|
|
48
|
-
function isPermanentClientError(error) {
|
|
49
|
-
const status = error?.response?.status;
|
|
50
|
-
if (typeof status !== "number") return false;
|
|
51
|
-
return status >= 400 && status < 500 && !RETRYABLE_CLIENT_STATUSES.has(status);
|
|
52
|
-
}
|
|
53
|
-
function shouldRetryEntityFetch(failureCount, error) {
|
|
54
|
-
return !isPermanentClientError(error) && failureCount < ENTITY_FETCH_MAX_RETRIES;
|
|
55
|
-
}
|
|
56
|
-
function entityRefetchInterval(error) {
|
|
57
|
-
return isPermanentClientError(error) ? false : ENTITY_REFETCH_INTERVAL_MS;
|
|
58
|
-
}
|
|
59
|
-
function entityDetailUrl(endpoint, entityId) {
|
|
60
|
-
return `${endpoint}${encodeURIComponent(entityId)}/`;
|
|
61
|
-
}
|
|
62
10
|
|
|
63
11
|
// src/windowRegistry/types.ts
|
|
64
12
|
function isPageEntry(entry) {
|
|
@@ -88,6 +36,35 @@ var WINDOW_REGISTRY = new Proxy({}, {
|
|
|
88
36
|
return void 0;
|
|
89
37
|
}
|
|
90
38
|
});
|
|
39
|
+
|
|
40
|
+
// src/api/client.ts
|
|
41
|
+
var _apiClient = null;
|
|
42
|
+
function setShellApiClient(instance) {
|
|
43
|
+
_apiClient = instance;
|
|
44
|
+
}
|
|
45
|
+
function isShellApiClientConfigured() {
|
|
46
|
+
return _apiClient !== null;
|
|
47
|
+
}
|
|
48
|
+
var NOOP_METHODS = /* @__PURE__ */ new Set([
|
|
49
|
+
"get",
|
|
50
|
+
"post",
|
|
51
|
+
"put",
|
|
52
|
+
"patch",
|
|
53
|
+
"delete",
|
|
54
|
+
"head",
|
|
55
|
+
"options",
|
|
56
|
+
"request"
|
|
57
|
+
]);
|
|
58
|
+
var apiClient = new Proxy({}, {
|
|
59
|
+
get(_t, prop) {
|
|
60
|
+
if (_apiClient) return _apiClient[prop];
|
|
61
|
+
if (typeof prop === "string" && NOOP_METHODS.has(prop)) {
|
|
62
|
+
return () => Promise.resolve({ data: null, status: 0, statusText: "", headers: {}, config: {} });
|
|
63
|
+
}
|
|
64
|
+
return void 0;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
var client_default = apiClient;
|
|
91
68
|
function WindowCrashedFallback({ error, onReload }) {
|
|
92
69
|
return /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-3 px-6 py-12 text-center", children: [
|
|
93
70
|
/* @__PURE__ */ jsx("svg", { className: "h-8 w-8 text-amber-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" }) }),
|
|
@@ -124,215 +101,28 @@ var WindowErrorBoundary = class extends Component {
|
|
|
124
101
|
}
|
|
125
102
|
};
|
|
126
103
|
|
|
127
|
-
// src/
|
|
128
|
-
var
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
future: []
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
case "undo": {
|
|
144
|
-
if (state.past.length === 0) return state;
|
|
145
|
-
return {
|
|
146
|
-
past: state.past.slice(0, -1),
|
|
147
|
-
future: [action.redoStep, ...state.future]
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
case "redo": {
|
|
151
|
-
if (state.future.length === 0) return state;
|
|
152
|
-
return {
|
|
153
|
-
past: [...state.past, action.undoStep],
|
|
154
|
-
future: state.future.slice(1)
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
case "clear":
|
|
158
|
-
return state.past.length === 0 && state.future.length === 0 ? state : emptyUndoState;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
var TEXT_ENTRY = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA", "SELECT"]);
|
|
162
|
-
function matchUndoHotkey(e) {
|
|
163
|
-
if (!(e.metaKey || e.ctrlKey) || e.altKey) return null;
|
|
164
|
-
const key = e.key.toLowerCase();
|
|
165
|
-
const action = key === "z" ? e.shiftKey ? "redo" : "undo" : key === "y" && e.ctrlKey && !e.metaKey && !e.shiftKey ? "redo" : null;
|
|
166
|
-
if (!action) return null;
|
|
167
|
-
const target = e.target;
|
|
168
|
-
if (target?.isContentEditable) return null;
|
|
169
|
-
if (target?.tagName && TEXT_ENTRY.has(target.tagName.toUpperCase())) return null;
|
|
170
|
-
return action;
|
|
171
|
-
}
|
|
172
|
-
var UndoContext = createContext(null);
|
|
173
|
-
var RUNAWAY_LIMIT = 500;
|
|
174
|
-
var RUNAWAY_QUIET_MS = 250;
|
|
175
|
-
function UndoProvider({ children, canEdit = true, perms, windowId }) {
|
|
176
|
-
const [state, dispatch] = useReducer(undoReducer, emptyUndoState);
|
|
177
|
-
const slices = useRef(/* @__PURE__ */ new Map());
|
|
178
|
-
const { hasAnyPerm } = useShellAuth();
|
|
179
|
-
const enabled = canEdit && (perms && perms.length > 0 ? hasAnyPerm(perms) : true);
|
|
180
|
-
const pending = useRef(null);
|
|
181
|
-
const suspended = useRef(false);
|
|
182
|
-
const snapshot = useCallback(() => {
|
|
183
|
-
const values = {};
|
|
184
|
-
for (const [id, slice] of slices.current) values[id] = slice.getLast();
|
|
185
|
-
return values;
|
|
186
|
-
}, []);
|
|
187
|
-
const register = useCallback((id, slice) => {
|
|
188
|
-
slices.current.set(id, slice);
|
|
189
|
-
}, []);
|
|
190
|
-
const unregister = useCallback((id) => {
|
|
191
|
-
slices.current.delete(id);
|
|
192
|
-
}, []);
|
|
193
|
-
const runaway = useRef({ last: 0, count: 0, tripped: false });
|
|
194
|
-
const record = useCallback((label, coalesceKey) => {
|
|
195
|
-
if (!enabled || suspended.current || pending.current) return;
|
|
196
|
-
const r = runaway.current;
|
|
197
|
-
if (r.tripped) return;
|
|
198
|
-
const now = Date.now();
|
|
199
|
-
if (now - r.last > RUNAWAY_QUIET_MS) r.count = 0;
|
|
200
|
-
r.last = now;
|
|
201
|
-
if (++r.count > RUNAWAY_LIMIT) {
|
|
202
|
-
r.tripped = true;
|
|
203
|
-
console.error(
|
|
204
|
-
`[react-os-shell] UndoProvider: "${label}" recorded ${RUNAWAY_LIMIT} undo steps with no pause between them, which no user action can do. The value passed to useUndoable for this slice is almost certainly rebuilt on every render (a .map/.filter/object literal in the call) rather than held in state, so it reads as changed every time \u2014 and since recording a step re-renders the form, that is a loop. Undo recording is now off for this form to keep the tab responsive.`
|
|
205
|
-
);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
pending.current = { values: snapshot(), label, coalesceKey };
|
|
209
|
-
queueMicrotask(() => {
|
|
210
|
-
const step = pending.current;
|
|
211
|
-
pending.current = null;
|
|
212
|
-
if (step) dispatch({ type: "record", step });
|
|
213
|
-
});
|
|
214
|
-
}, [enabled, snapshot]);
|
|
215
|
-
const applyValues = useCallback((values) => {
|
|
216
|
-
for (const [id, value2] of Object.entries(values)) {
|
|
217
|
-
const slice = slices.current.get(id);
|
|
218
|
-
if (!slice) continue;
|
|
219
|
-
slice.setLast(value2);
|
|
220
|
-
slice.apply(value2);
|
|
221
|
-
}
|
|
222
|
-
}, []);
|
|
223
|
-
const undo = useCallback(() => {
|
|
224
|
-
const step = state.past[state.past.length - 1];
|
|
225
|
-
if (!step) return;
|
|
226
|
-
const redoStep = { values: snapshot(), label: step.label, coalesceKey: null };
|
|
227
|
-
applyValues(step.values);
|
|
228
|
-
dispatch({ type: "undo", redoStep });
|
|
229
|
-
}, [state.past, snapshot, applyValues]);
|
|
230
|
-
const redo = useCallback(() => {
|
|
231
|
-
const step = state.future[0];
|
|
232
|
-
if (!step) return;
|
|
233
|
-
const undoStep = { values: snapshot(), label: step.label, coalesceKey: null };
|
|
234
|
-
applyValues(step.values);
|
|
235
|
-
dispatch({ type: "redo", undoStep });
|
|
236
|
-
}, [state.future, snapshot, applyValues]);
|
|
237
|
-
const [baselineToken, setBaselineToken] = useState(0);
|
|
238
|
-
const baseline = useCallback(() => {
|
|
239
|
-
suspended.current = true;
|
|
240
|
-
pending.current = null;
|
|
241
|
-
setBaselineToken((t) => t + 1);
|
|
242
|
-
dispatch({ type: "clear" });
|
|
243
|
-
}, []);
|
|
244
|
-
useEffect(() => {
|
|
245
|
-
if (!suspended.current) return;
|
|
246
|
-
suspended.current = false;
|
|
247
|
-
pending.current = null;
|
|
248
|
-
dispatch({ type: "clear" });
|
|
249
|
-
}, [baselineToken]);
|
|
250
|
-
const clear = baseline;
|
|
251
|
-
const canUndo = enabled && state.past.length > 0;
|
|
252
|
-
const canRedo = enabled && state.future.length > 0;
|
|
253
|
-
const isActive = useIsActiveWindow(windowId);
|
|
254
|
-
const keyed = useRef({ undo, redo, canUndo, canRedo });
|
|
255
|
-
keyed.current = { undo, redo, canUndo, canRedo };
|
|
256
|
-
useEffect(() => {
|
|
257
|
-
if (!isActive || !enabled) return;
|
|
258
|
-
const handler = (e) => {
|
|
259
|
-
const action = matchUndoHotkey(e);
|
|
260
|
-
if (!action) return;
|
|
261
|
-
const k = keyed.current;
|
|
262
|
-
if (action === "undo" ? !k.canUndo : !k.canRedo) return;
|
|
263
|
-
e.preventDefault();
|
|
264
|
-
if (action === "undo") k.undo();
|
|
265
|
-
else k.redo();
|
|
266
|
-
};
|
|
267
|
-
window.addEventListener("keydown", handler);
|
|
268
|
-
return () => window.removeEventListener("keydown", handler);
|
|
269
|
-
}, [isActive, enabled]);
|
|
270
|
-
const value = useMemo(() => ({
|
|
271
|
-
register,
|
|
272
|
-
unregister,
|
|
273
|
-
record,
|
|
274
|
-
undo,
|
|
275
|
-
redo,
|
|
276
|
-
clear,
|
|
277
|
-
baseline,
|
|
278
|
-
canUndo,
|
|
279
|
-
canRedo,
|
|
280
|
-
enabled,
|
|
281
|
-
undoLabel: canUndo ? state.past[state.past.length - 1].label : null,
|
|
282
|
-
redoLabel: canRedo ? state.future[0].label : null
|
|
283
|
-
}), [register, unregister, record, undo, redo, clear, baseline, canUndo, canRedo, enabled, state.past, state.future]);
|
|
284
|
-
return /* @__PURE__ */ jsx(UndoContext.Provider, { value, children });
|
|
104
|
+
// src/shell/entityFetchPolicy.ts
|
|
105
|
+
var RETRYABLE_CLIENT_STATUSES = /* @__PURE__ */ new Set([
|
|
106
|
+
408,
|
|
107
|
+
// Request Timeout
|
|
108
|
+
429
|
|
109
|
+
// Too Many Requests
|
|
110
|
+
]);
|
|
111
|
+
var ENTITY_FETCH_MAX_RETRIES = 3;
|
|
112
|
+
var ENTITY_REFETCH_INTERVAL_MS = 6e4;
|
|
113
|
+
function isPermanentClientError(error) {
|
|
114
|
+
const status = error?.response?.status;
|
|
115
|
+
if (typeof status !== "number") return false;
|
|
116
|
+
return status >= 400 && status < 500 && !RETRYABLE_CLIENT_STATUSES.has(status);
|
|
285
117
|
}
|
|
286
|
-
function
|
|
287
|
-
|
|
288
|
-
const noop = useCallback(() => {
|
|
289
|
-
}, []);
|
|
290
|
-
return {
|
|
291
|
-
undo: ctx?.undo ?? noop,
|
|
292
|
-
redo: ctx?.redo ?? noop,
|
|
293
|
-
clear: ctx?.clear ?? noop,
|
|
294
|
-
baseline: ctx?.baseline ?? noop,
|
|
295
|
-
canUndo: ctx?.canUndo ?? false,
|
|
296
|
-
canRedo: ctx?.canRedo ?? false,
|
|
297
|
-
undoLabel: ctx?.undoLabel ?? null,
|
|
298
|
-
redoLabel: ctx?.redoLabel ?? null,
|
|
299
|
-
enabled: ctx?.enabled ?? false
|
|
300
|
-
};
|
|
118
|
+
function shouldRetryEntityFetch(failureCount, error) {
|
|
119
|
+
return !isPermanentClientError(error) && failureCount < ENTITY_FETCH_MAX_RETRIES;
|
|
301
120
|
}
|
|
302
|
-
function
|
|
303
|
-
|
|
304
|
-
const id = useId();
|
|
305
|
-
const last = useRef(value);
|
|
306
|
-
const applyRef = useRef(apply);
|
|
307
|
-
applyRef.current = apply;
|
|
308
|
-
useEffect(() => {
|
|
309
|
-
if (!ctx) return;
|
|
310
|
-
ctx.register(id, {
|
|
311
|
-
getLast: () => last.current,
|
|
312
|
-
setLast: (v) => {
|
|
313
|
-
last.current = v;
|
|
314
|
-
},
|
|
315
|
-
apply: (v) => applyRef.current(v)
|
|
316
|
-
});
|
|
317
|
-
return () => ctx.unregister(id);
|
|
318
|
-
}, [ctx, id]);
|
|
319
|
-
const { label, coalesceKey = null } = opts;
|
|
320
|
-
const ctxRef = useRef(ctx);
|
|
321
|
-
ctxRef.current = ctx;
|
|
322
|
-
const optsRef = useRef({ label, coalesceKey });
|
|
323
|
-
optsRef.current = { label, coalesceKey };
|
|
324
|
-
useEffect(() => {
|
|
325
|
-
const c = ctxRef.current;
|
|
326
|
-
if (!c) return;
|
|
327
|
-
if (Object.is(value, last.current)) return;
|
|
328
|
-
c.record(optsRef.current.label, optsRef.current.coalesceKey);
|
|
329
|
-
last.current = value;
|
|
330
|
-
}, [value]);
|
|
121
|
+
function entityRefetchInterval(error) {
|
|
122
|
+
return isPermanentClientError(error) ? false : ENTITY_REFETCH_INTERVAL_MS;
|
|
331
123
|
}
|
|
332
|
-
function
|
|
333
|
-
|
|
334
|
-
useUndoable(value, (next) => setValue(() => next), opts);
|
|
335
|
-
return [value, setValue];
|
|
124
|
+
function entityDetailUrl(endpoint, entityId) {
|
|
125
|
+
return `${endpoint}${encodeURIComponent(entityId)}/`;
|
|
336
126
|
}
|
|
337
127
|
function LoadingSpinner() {
|
|
338
128
|
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-8 text-sm text-gray-400", children: "Loading..." });
|
|
@@ -1123,151 +913,6 @@ function WindowManagerProvider({ children, windowAccentForRoute }) {
|
|
|
1123
913
|
})
|
|
1124
914
|
] });
|
|
1125
915
|
}
|
|
1126
|
-
function getDensity() {
|
|
1127
|
-
return getComputedStyle(document.documentElement).getPropertyValue("--menu-density")?.trim() || "normal";
|
|
1128
|
-
}
|
|
1129
|
-
function PopupMenu({ children, style, className = "", onClose, minWidth = 180, portal = false }) {
|
|
1130
|
-
const ref = useRef(null);
|
|
1131
|
-
useEffect(() => {
|
|
1132
|
-
if (!onClose) return;
|
|
1133
|
-
const handleClick = (e) => {
|
|
1134
|
-
if (e.target.closest("[data-menu-toggle]")) return;
|
|
1135
|
-
if (ref.current && !ref.current.contains(e.target)) onClose();
|
|
1136
|
-
};
|
|
1137
|
-
const handleKey = (e) => {
|
|
1138
|
-
if (e.key === "Escape") onClose();
|
|
1139
|
-
};
|
|
1140
|
-
window.addEventListener("pointerdown", handleClick);
|
|
1141
|
-
window.addEventListener("keydown", handleKey);
|
|
1142
|
-
return () => {
|
|
1143
|
-
window.removeEventListener("pointerdown", handleClick);
|
|
1144
|
-
window.removeEventListener("keydown", handleKey);
|
|
1145
|
-
};
|
|
1146
|
-
}, [onClose]);
|
|
1147
|
-
useEffect(() => {
|
|
1148
|
-
const el = ref.current;
|
|
1149
|
-
if (!el) return;
|
|
1150
|
-
requestAnimationFrame(() => {
|
|
1151
|
-
const rect = el.getBoundingClientRect();
|
|
1152
|
-
const margin = 8;
|
|
1153
|
-
if (rect.right > window.innerWidth - margin) {
|
|
1154
|
-
const overflow = rect.right - window.innerWidth + margin;
|
|
1155
|
-
if (el.style.left) {
|
|
1156
|
-
el.style.left = `${parseFloat(el.style.left) - overflow}px`;
|
|
1157
|
-
} else if (!el.style.right) {
|
|
1158
|
-
el.style.left = `${rect.left - overflow}px`;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
if (rect.left < margin) {
|
|
1162
|
-
if (el.style.left) {
|
|
1163
|
-
el.style.left = `${margin}px`;
|
|
1164
|
-
} else if (el.style.right) {
|
|
1165
|
-
el.style.right = `${window.innerWidth - rect.width - margin}px`;
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
if (rect.bottom > window.innerHeight - margin) {
|
|
1169
|
-
const overflow = rect.bottom - window.innerHeight + margin;
|
|
1170
|
-
if (el.style.top) {
|
|
1171
|
-
el.style.top = `${parseFloat(el.style.top) - overflow}px`;
|
|
1172
|
-
} else if (el.style.bottom) {
|
|
1173
|
-
el.style.bottom = `${margin}px`;
|
|
1174
|
-
} else {
|
|
1175
|
-
el.style.top = `${rect.top - overflow}px`;
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
if (rect.top < margin) {
|
|
1179
|
-
if (el.style.top) {
|
|
1180
|
-
el.style.top = `${margin}px`;
|
|
1181
|
-
} else if (el.style.bottom) {
|
|
1182
|
-
el.style.bottom = `${window.innerHeight - rect.height - margin}px`;
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
});
|
|
1186
|
-
});
|
|
1187
|
-
const density = getDensity();
|
|
1188
|
-
const menu = /* @__PURE__ */ jsxs(
|
|
1189
|
-
"div",
|
|
1190
|
-
{
|
|
1191
|
-
ref,
|
|
1192
|
-
className: `fixed z-[400] rounded-2xl ${density === "tight" ? "py-1" : density === "large" ? "py-2" : "py-1.5"} ${className}`,
|
|
1193
|
-
style: { minWidth, animation: "popup-in 0.12s ease-out", ...glassStyle(), ...style },
|
|
1194
|
-
children: [
|
|
1195
|
-
children,
|
|
1196
|
-
/* @__PURE__ */ jsx("style", { children: `@keyframes popup-in { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }` })
|
|
1197
|
-
]
|
|
1198
|
-
}
|
|
1199
|
-
);
|
|
1200
|
-
return portal ? createPortal(menu, document.body) : menu;
|
|
1201
|
-
}
|
|
1202
|
-
function PopupMenuItem({ onClick, children, className = "", danger, disabled }) {
|
|
1203
|
-
const density = getDensity();
|
|
1204
|
-
const itemPadY = density === "tight" ? "0.25rem" : density === "large" ? "0.6rem" : "max(0.25rem, calc(var(--menu-padding-y, 0.5rem) - 0.1rem))";
|
|
1205
|
-
return /* @__PURE__ */ jsx(
|
|
1206
|
-
"button",
|
|
1207
|
-
{
|
|
1208
|
-
onClick,
|
|
1209
|
-
disabled,
|
|
1210
|
-
className: `w-full flex items-center gap-2 text-left transition-colors rounded-lg mx-auto
|
|
1211
|
-
${danger ? "text-red-600 hover:bg-red-50" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700"}
|
|
1212
|
-
${disabled ? "opacity-40 cursor-not-allowed" : ""}
|
|
1213
|
-
${className}`,
|
|
1214
|
-
style: {
|
|
1215
|
-
width: "calc(100% - 8px)",
|
|
1216
|
-
marginLeft: 4,
|
|
1217
|
-
marginRight: 4,
|
|
1218
|
-
fontSize: "var(--menu-font-size, 14px)",
|
|
1219
|
-
paddingLeft: "var(--menu-padding-x, 1rem)",
|
|
1220
|
-
paddingRight: "var(--menu-padding-x, 1rem)",
|
|
1221
|
-
paddingTop: itemPadY,
|
|
1222
|
-
paddingBottom: itemPadY
|
|
1223
|
-
},
|
|
1224
|
-
children
|
|
1225
|
-
}
|
|
1226
|
-
);
|
|
1227
|
-
}
|
|
1228
|
-
function PopupMenuDivider() {
|
|
1229
|
-
const density = getDensity();
|
|
1230
|
-
return /* @__PURE__ */ jsx("div", { className: `border-t ${GLASS_DIVIDER} ${density === "tight" ? "my-0.5" : density === "large" ? "my-1.5" : "my-1"} mx-3` });
|
|
1231
|
-
}
|
|
1232
|
-
function PopupMenuLabel({ children }) {
|
|
1233
|
-
const density = getDensity();
|
|
1234
|
-
const labelPadY = density === "tight" ? "0.125rem" : density === "large" ? "0.375rem" : "0.25rem";
|
|
1235
|
-
return /* @__PURE__ */ jsx(
|
|
1236
|
-
"div",
|
|
1237
|
-
{
|
|
1238
|
-
className: "text-[10px] font-medium text-gray-400 uppercase tracking-wider",
|
|
1239
|
-
style: {
|
|
1240
|
-
paddingLeft: "var(--menu-padding-x, 1rem)",
|
|
1241
|
-
paddingRight: "var(--menu-padding-x, 1rem)",
|
|
1242
|
-
paddingTop: labelPadY,
|
|
1243
|
-
paddingBottom: labelPadY
|
|
1244
|
-
},
|
|
1245
|
-
children
|
|
1246
|
-
}
|
|
1247
|
-
);
|
|
1248
|
-
}
|
|
1249
|
-
var QUERY = "(max-width: 767px), (pointer: coarse)";
|
|
1250
|
-
function getSnapshot() {
|
|
1251
|
-
if (typeof window === "undefined" || !window.matchMedia) return false;
|
|
1252
|
-
return window.matchMedia(QUERY).matches;
|
|
1253
|
-
}
|
|
1254
|
-
function getServerSnapshot() {
|
|
1255
|
-
return false;
|
|
1256
|
-
}
|
|
1257
|
-
function subscribe(cb) {
|
|
1258
|
-
if (typeof window === "undefined" || !window.matchMedia) return () => {
|
|
1259
|
-
};
|
|
1260
|
-
const mql = window.matchMedia(QUERY);
|
|
1261
|
-
if (mql.addEventListener) {
|
|
1262
|
-
mql.addEventListener("change", cb);
|
|
1263
|
-
return () => mql.removeEventListener("change", cb);
|
|
1264
|
-
}
|
|
1265
|
-
mql.addListener(cb);
|
|
1266
|
-
return () => mql.removeListener(cb);
|
|
1267
|
-
}
|
|
1268
|
-
function useIsMobile() {
|
|
1269
|
-
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
1270
|
-
}
|
|
1271
916
|
|
|
1272
917
|
// src/shell/mobileSwipeStore.ts
|
|
1273
918
|
var _parentKey = null;
|
|
@@ -1321,40 +966,6 @@ function drainPerfEvents() {
|
|
|
1321
966
|
counts = empty();
|
|
1322
967
|
return drained;
|
|
1323
968
|
}
|
|
1324
|
-
var ShellPrefsContext = createContext(null);
|
|
1325
|
-
function ShellPrefsProvider({
|
|
1326
|
-
value,
|
|
1327
|
-
children
|
|
1328
|
-
}) {
|
|
1329
|
-
return /* @__PURE__ */ jsx(ShellPrefsContext.Provider, { value, children });
|
|
1330
|
-
}
|
|
1331
|
-
function useLocalStoragePrefs(storageKey = "react-os-shell:prefs", defaults) {
|
|
1332
|
-
const [prefs, setPrefs] = useState(() => {
|
|
1333
|
-
try {
|
|
1334
|
-
const stored = JSON.parse(localStorage.getItem(storageKey) || "{}");
|
|
1335
|
-
return { ...defaults ?? {}, ...stored };
|
|
1336
|
-
} catch {
|
|
1337
|
-
return { ...defaults ?? {} };
|
|
1338
|
-
}
|
|
1339
|
-
});
|
|
1340
|
-
const save = useCallback((patch) => {
|
|
1341
|
-
setPrefs((prev) => {
|
|
1342
|
-
const next = { ...prev, ...patch };
|
|
1343
|
-
try {
|
|
1344
|
-
localStorage.setItem(storageKey, JSON.stringify(next));
|
|
1345
|
-
} catch {
|
|
1346
|
-
}
|
|
1347
|
-
return next;
|
|
1348
|
-
});
|
|
1349
|
-
}, [storageKey]);
|
|
1350
|
-
return { prefs, save };
|
|
1351
|
-
}
|
|
1352
|
-
function useShellPrefs() {
|
|
1353
|
-
const ctx = useContext(ShellPrefsContext);
|
|
1354
|
-
if (ctx) return ctx;
|
|
1355
|
-
return { prefs: {}, save: () => {
|
|
1356
|
-
} };
|
|
1357
|
-
}
|
|
1358
969
|
|
|
1359
970
|
// src/shell/workArea.ts
|
|
1360
971
|
var SIDEBAR_STRIP_W = 280;
|
|
@@ -1497,7 +1108,7 @@ function CancelButton({ onClick, children, className }) {
|
|
|
1497
1108
|
const ctx = useContext(ModalActionsContext);
|
|
1498
1109
|
const handleClick = async () => {
|
|
1499
1110
|
if (ctx?.isDirty) {
|
|
1500
|
-
const { confirm: confirm2 } = await import('./ConfirmDialog-
|
|
1111
|
+
const { confirm: confirm2 } = await import('./ConfirmDialog-WAM5OKAV.js');
|
|
1501
1112
|
const ok = await confirm2({
|
|
1502
1113
|
title: "Discard changes?",
|
|
1503
1114
|
message: "You have unsaved changes. Are you sure you want to cancel? All changes will be lost.",
|
|
@@ -1897,27 +1508,11 @@ function subscribeActive(cb) {
|
|
|
1897
1508
|
activeListeners.delete(cb);
|
|
1898
1509
|
};
|
|
1899
1510
|
}
|
|
1900
|
-
var escapeInterceptors = /* @__PURE__ */ new Set();
|
|
1901
|
-
function registerModalEscapeInterceptor(fn) {
|
|
1902
|
-
escapeInterceptors.add(fn);
|
|
1903
|
-
return () => {
|
|
1904
|
-
escapeInterceptors.delete(fn);
|
|
1905
|
-
};
|
|
1906
|
-
}
|
|
1907
1511
|
var MODAL_CLOSE_REQUEST_EVENT = "modal-close-request";
|
|
1908
1512
|
function requestModalClose(windowKey) {
|
|
1909
1513
|
window.dispatchEvent(new CustomEvent(MODAL_CLOSE_REQUEST_EVENT, {
|
|
1910
|
-
detail: { windowKey }
|
|
1911
|
-
}));
|
|
1912
|
-
}
|
|
1913
|
-
function runEscapeInterceptors(e) {
|
|
1914
|
-
for (const fn of escapeInterceptors) {
|
|
1915
|
-
try {
|
|
1916
|
-
if (fn(e)) return true;
|
|
1917
|
-
} catch {
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1920
|
-
return false;
|
|
1514
|
+
detail: { windowKey }
|
|
1515
|
+
}));
|
|
1921
1516
|
}
|
|
1922
1517
|
function useIsActiveModal(modalId) {
|
|
1923
1518
|
const activeId = useSyncExternalStore(subscribeActive, getActiveModalId);
|
|
@@ -3315,263 +2910,218 @@ function Modal({ open, onClose, title, icon, copyText, size = "lg", dirty = fals
|
|
|
3315
2910
|
document.body
|
|
3316
2911
|
);
|
|
3317
2912
|
}
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
var
|
|
3321
|
-
var
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
2913
|
+
|
|
2914
|
+
// src/hooks/undoHistory.ts
|
|
2915
|
+
var UNDO_LIMIT = 100;
|
|
2916
|
+
var emptyUndoState = { past: [], future: [] };
|
|
2917
|
+
function undoReducer(state, action) {
|
|
2918
|
+
switch (action.type) {
|
|
2919
|
+
case "record": {
|
|
2920
|
+
const newest = state.past[state.past.length - 1];
|
|
2921
|
+
if (action.step.coalesceKey !== null && newest?.coalesceKey === action.step.coalesceKey) {
|
|
2922
|
+
return state.future.length === 0 ? state : { ...state, future: [] };
|
|
2923
|
+
}
|
|
2924
|
+
const past = [...state.past, action.step];
|
|
2925
|
+
return {
|
|
2926
|
+
past: past.length > UNDO_LIMIT ? past.slice(past.length - UNDO_LIMIT) : past,
|
|
2927
|
+
future: []
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
case "undo": {
|
|
2931
|
+
if (state.past.length === 0) return state;
|
|
2932
|
+
return {
|
|
2933
|
+
past: state.past.slice(0, -1),
|
|
2934
|
+
future: [action.redoStep, ...state.future]
|
|
2935
|
+
};
|
|
2936
|
+
}
|
|
2937
|
+
case "redo": {
|
|
2938
|
+
if (state.future.length === 0) return state;
|
|
2939
|
+
return {
|
|
2940
|
+
past: [...state.past, action.undoStep],
|
|
2941
|
+
future: state.future.slice(1)
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2944
|
+
case "clear":
|
|
2945
|
+
return state.past.length === 0 && state.future.length === 0 ? state : emptyUndoState;
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
var TEXT_ENTRY = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA", "SELECT"]);
|
|
2949
|
+
function matchUndoHotkey(e) {
|
|
2950
|
+
if (!(e.metaKey || e.ctrlKey) || e.altKey) return null;
|
|
2951
|
+
const key = e.key.toLowerCase();
|
|
2952
|
+
const action = key === "z" ? e.shiftKey ? "redo" : "undo" : key === "y" && e.ctrlKey && !e.metaKey && !e.shiftKey ? "redo" : null;
|
|
2953
|
+
if (!action) return null;
|
|
2954
|
+
const target = e.target;
|
|
2955
|
+
if (target?.isContentEditable) return null;
|
|
2956
|
+
if (target?.tagName && TEXT_ENTRY.has(target.tagName.toUpperCase())) return null;
|
|
2957
|
+
return action;
|
|
2958
|
+
}
|
|
2959
|
+
var UndoContext = createContext(null);
|
|
2960
|
+
var RUNAWAY_LIMIT = 500;
|
|
2961
|
+
var RUNAWAY_QUIET_MS = 250;
|
|
2962
|
+
function UndoProvider({ children, canEdit = true, perms, windowId }) {
|
|
2963
|
+
const [state, dispatch] = useReducer(undoReducer, emptyUndoState);
|
|
2964
|
+
const slices = useRef(/* @__PURE__ */ new Map());
|
|
2965
|
+
const { hasAnyPerm } = useShellAuth();
|
|
2966
|
+
const enabled = canEdit && (perms && perms.length > 0 ? hasAnyPerm(perms) : true);
|
|
2967
|
+
const pending = useRef(null);
|
|
2968
|
+
const suspended = useRef(false);
|
|
2969
|
+
const snapshot = useCallback(() => {
|
|
2970
|
+
const values = {};
|
|
2971
|
+
for (const [id, slice] of slices.current) values[id] = slice.getLast();
|
|
2972
|
+
return values;
|
|
3340
2973
|
}, []);
|
|
3341
|
-
const
|
|
3342
|
-
|
|
3343
|
-
return new Promise((resolve) => {
|
|
3344
|
-
const request = { id: ++nextConfirmIdRef.current, options: normalized, resolve };
|
|
3345
|
-
if (activeConfirmIdRef.current === null) showConfirm(request);
|
|
3346
|
-
else confirmQueueRef.current.push(request);
|
|
3347
|
-
});
|
|
3348
|
-
}, [showConfirm]);
|
|
3349
|
-
const [dOpen, setDOpen] = useState(false);
|
|
3350
|
-
const [dOptions, setDOptions] = useState({ message: "", confirmWord: "Delete" });
|
|
3351
|
-
const [dInput, setDInput] = useState("");
|
|
3352
|
-
const dResolveRef = useRef();
|
|
3353
|
-
const destructiveConfirmFn = useCallback((opts) => {
|
|
3354
|
-
setDOptions(opts);
|
|
3355
|
-
setDInput("");
|
|
3356
|
-
setDOpen(true);
|
|
3357
|
-
return new Promise((resolve) => {
|
|
3358
|
-
dResolveRef.current = resolve;
|
|
3359
|
-
});
|
|
2974
|
+
const register = useCallback((id, slice) => {
|
|
2975
|
+
slices.current.set(id, slice);
|
|
3360
2976
|
}, []);
|
|
3361
|
-
const
|
|
3362
|
-
|
|
3363
|
-
const [pInput, setPInput] = useState("");
|
|
3364
|
-
const pResolveRef = useRef();
|
|
3365
|
-
const promptFn = useCallback((opts) => {
|
|
3366
|
-
const normalized = typeof opts === "string" ? { message: opts } : opts;
|
|
3367
|
-
setPOptions(normalized);
|
|
3368
|
-
setPInput(normalized.defaultValue ?? "");
|
|
3369
|
-
setPOpen(true);
|
|
3370
|
-
return new Promise((resolve) => {
|
|
3371
|
-
pResolveRef.current = resolve;
|
|
3372
|
-
});
|
|
2977
|
+
const unregister = useCallback((id) => {
|
|
2978
|
+
slices.current.delete(id);
|
|
3373
2979
|
}, []);
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
if (
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
2980
|
+
const runaway = useRef({ last: 0, count: 0, tripped: false });
|
|
2981
|
+
const record = useCallback((label, coalesceKey) => {
|
|
2982
|
+
if (!enabled || suspended.current || pending.current) return;
|
|
2983
|
+
const r = runaway.current;
|
|
2984
|
+
if (r.tripped) return;
|
|
2985
|
+
const now = Date.now();
|
|
2986
|
+
if (now - r.last > RUNAWAY_QUIET_MS) r.count = 0;
|
|
2987
|
+
r.last = now;
|
|
2988
|
+
if (++r.count > RUNAWAY_LIMIT) {
|
|
2989
|
+
r.tripped = true;
|
|
2990
|
+
console.error(
|
|
2991
|
+
`[react-os-shell] UndoProvider: "${label}" recorded ${RUNAWAY_LIMIT} undo steps with no pause between them, which no user action can do. The value passed to useUndoable for this slice is almost certainly rebuilt on every render (a .map/.filter/object literal in the call) rather than held in state, so it reads as changed every time \u2014 and since recording a step re-renders the form, that is a loop. Undo recording is now off for this form to keep the tab responsive.`
|
|
2992
|
+
);
|
|
2993
|
+
return;
|
|
3388
2994
|
}
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
};
|
|
3396
|
-
const
|
|
3397
|
-
|
|
3398
|
-
const
|
|
3399
|
-
if (!
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
return;
|
|
3403
|
-
}
|
|
3404
|
-
setPOpen(false);
|
|
3405
|
-
pResolveRef.current?.(trimmed);
|
|
3406
|
-
} else {
|
|
3407
|
-
setPOpen(false);
|
|
3408
|
-
pResolveRef.current?.(null);
|
|
2995
|
+
pending.current = { values: snapshot(), label, coalesceKey };
|
|
2996
|
+
queueMicrotask(() => {
|
|
2997
|
+
const step = pending.current;
|
|
2998
|
+
pending.current = null;
|
|
2999
|
+
if (step) dispatch({ type: "record", step });
|
|
3000
|
+
});
|
|
3001
|
+
}, [enabled, snapshot]);
|
|
3002
|
+
const applyValues = useCallback((values) => {
|
|
3003
|
+
for (const [id, value2] of Object.entries(values)) {
|
|
3004
|
+
const slice = slices.current.get(id);
|
|
3005
|
+
if (!slice) continue;
|
|
3006
|
+
slice.setLast(value2);
|
|
3007
|
+
slice.apply(value2);
|
|
3409
3008
|
}
|
|
3009
|
+
}, []);
|
|
3010
|
+
const undo = useCallback(() => {
|
|
3011
|
+
const step = state.past[state.past.length - 1];
|
|
3012
|
+
if (!step) return;
|
|
3013
|
+
const redoStep = { values: snapshot(), label: step.label, coalesceKey: null };
|
|
3014
|
+
applyValues(step.values);
|
|
3015
|
+
dispatch({ type: "undo", redoStep });
|
|
3016
|
+
}, [state.past, snapshot, applyValues]);
|
|
3017
|
+
const redo = useCallback(() => {
|
|
3018
|
+
const step = state.future[0];
|
|
3019
|
+
if (!step) return;
|
|
3020
|
+
const undoStep = { values: snapshot(), label: step.label, coalesceKey: null };
|
|
3021
|
+
applyValues(step.values);
|
|
3022
|
+
dispatch({ type: "redo", undoStep });
|
|
3023
|
+
}, [state.future, snapshot, applyValues]);
|
|
3024
|
+
const [baselineToken, setBaselineToken] = useState(0);
|
|
3025
|
+
const baseline = useCallback(() => {
|
|
3026
|
+
suspended.current = true;
|
|
3027
|
+
pending.current = null;
|
|
3028
|
+
setBaselineToken((t) => t + 1);
|
|
3029
|
+
dispatch({ type: "clear" });
|
|
3030
|
+
}, []);
|
|
3031
|
+
useEffect(() => {
|
|
3032
|
+
if (!suspended.current) return;
|
|
3033
|
+
suspended.current = false;
|
|
3034
|
+
pending.current = null;
|
|
3035
|
+
dispatch({ type: "clear" });
|
|
3036
|
+
}, [baselineToken]);
|
|
3037
|
+
const clear = baseline;
|
|
3038
|
+
const canUndo = enabled && state.past.length > 0;
|
|
3039
|
+
const canRedo = enabled && state.future.length > 0;
|
|
3040
|
+
const isActive = useIsActiveWindow(windowId);
|
|
3041
|
+
const keyed = useRef({ undo, redo, canUndo, canRedo });
|
|
3042
|
+
keyed.current = { undo, redo, canUndo, canRedo };
|
|
3043
|
+
useEffect(() => {
|
|
3044
|
+
if (!isActive || !enabled) return;
|
|
3045
|
+
const handler = (e) => {
|
|
3046
|
+
const action = matchUndoHotkey(e);
|
|
3047
|
+
if (!action) return;
|
|
3048
|
+
const k = keyed.current;
|
|
3049
|
+
if (action === "undo" ? !k.canUndo : !k.canRedo) return;
|
|
3050
|
+
e.preventDefault();
|
|
3051
|
+
if (action === "undo") k.undo();
|
|
3052
|
+
else k.redo();
|
|
3053
|
+
};
|
|
3054
|
+
window.addEventListener("keydown", handler);
|
|
3055
|
+
return () => window.removeEventListener("keydown", handler);
|
|
3056
|
+
}, [isActive, enabled]);
|
|
3057
|
+
const value = useMemo(() => ({
|
|
3058
|
+
register,
|
|
3059
|
+
unregister,
|
|
3060
|
+
record,
|
|
3061
|
+
undo,
|
|
3062
|
+
redo,
|
|
3063
|
+
clear,
|
|
3064
|
+
baseline,
|
|
3065
|
+
canUndo,
|
|
3066
|
+
canRedo,
|
|
3067
|
+
enabled,
|
|
3068
|
+
undoLabel: canUndo ? state.past[state.past.length - 1].label : null,
|
|
3069
|
+
redoLabel: canRedo ? state.future[0].label : null
|
|
3070
|
+
}), [register, unregister, record, undo, redo, clear, baseline, canUndo, canRedo, enabled, state.past, state.future]);
|
|
3071
|
+
return /* @__PURE__ */ jsx(UndoContext.Provider, { value, children });
|
|
3072
|
+
}
|
|
3073
|
+
function useUndo() {
|
|
3074
|
+
const ctx = useContext(UndoContext);
|
|
3075
|
+
const noop = useCallback(() => {
|
|
3076
|
+
}, []);
|
|
3077
|
+
return {
|
|
3078
|
+
undo: ctx?.undo ?? noop,
|
|
3079
|
+
redo: ctx?.redo ?? noop,
|
|
3080
|
+
clear: ctx?.clear ?? noop,
|
|
3081
|
+
baseline: ctx?.baseline ?? noop,
|
|
3082
|
+
canUndo: ctx?.canUndo ?? false,
|
|
3083
|
+
canRedo: ctx?.canRedo ?? false,
|
|
3084
|
+
undoLabel: ctx?.undoLabel ?? null,
|
|
3085
|
+
redoLabel: ctx?.redoLabel ?? null,
|
|
3086
|
+
enabled: ctx?.enabled ?? false
|
|
3410
3087
|
};
|
|
3088
|
+
}
|
|
3089
|
+
function useUndoable(value, apply, opts) {
|
|
3090
|
+
const ctx = useContext(UndoContext);
|
|
3091
|
+
const id = useId();
|
|
3092
|
+
const last = useRef(value);
|
|
3093
|
+
const applyRef = useRef(apply);
|
|
3094
|
+
applyRef.current = apply;
|
|
3411
3095
|
useEffect(() => {
|
|
3412
|
-
if (!
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
}
|
|
3418
|
-
|
|
3419
|
-
handleDClose(false);
|
|
3420
|
-
return true;
|
|
3421
|
-
}
|
|
3422
|
-
if (open) {
|
|
3423
|
-
handleClose(confirmId, false);
|
|
3424
|
-
return true;
|
|
3425
|
-
}
|
|
3426
|
-
return false;
|
|
3096
|
+
if (!ctx) return;
|
|
3097
|
+
ctx.register(id, {
|
|
3098
|
+
getLast: () => last.current,
|
|
3099
|
+
setLast: (v) => {
|
|
3100
|
+
last.current = v;
|
|
3101
|
+
},
|
|
3102
|
+
apply: (v) => applyRef.current(v)
|
|
3427
3103
|
});
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
const
|
|
3431
|
-
const
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
{
|
|
3448
|
-
type: "button",
|
|
3449
|
-
onClick: () => handleClose(confirmId, false),
|
|
3450
|
-
className: "bg-white text-gray-700 border border-gray-300 px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-50",
|
|
3451
|
-
children: options.cancelLabel || "Cancel"
|
|
3452
|
-
}
|
|
3453
|
-
),
|
|
3454
|
-
/* @__PURE__ */ jsx(
|
|
3455
|
-
"button",
|
|
3456
|
-
{
|
|
3457
|
-
type: "button",
|
|
3458
|
-
onClick: () => handleClose(confirmId, true),
|
|
3459
|
-
className: `px-4 py-2 text-sm font-medium rounded-lg ${confirmBtnClass}`,
|
|
3460
|
-
children: options.confirmLabel || "OK"
|
|
3461
|
-
}
|
|
3462
|
-
)
|
|
3463
|
-
] })
|
|
3464
|
-
] }) })
|
|
3465
|
-
] }),
|
|
3466
|
-
/* @__PURE__ */ jsxs(Dialog, { open: dOpen, onClose: () => handleDClose(false), className: "relative z-[9999]", children: [
|
|
3467
|
-
/* @__PURE__ */ jsx(DialogBackdrop, { className: "fixed inset-0 bg-black/30 transition-opacity" }),
|
|
3468
|
-
/* @__PURE__ */ jsx("div", { className: "fixed inset-0 flex items-center justify-center p-4", children: /* @__PURE__ */ jsxs(DialogPanel, { className: "relative w-full max-w-md rounded-lg bg-white p-6 shadow-xl", children: [
|
|
3469
|
-
/* @__PURE__ */ jsx(
|
|
3470
|
-
"button",
|
|
3471
|
-
{
|
|
3472
|
-
type: "button",
|
|
3473
|
-
onClick: () => handleDClose(false),
|
|
3474
|
-
"aria-label": "Close",
|
|
3475
|
-
className: "absolute right-4 top-4 rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600",
|
|
3476
|
-
children: /* @__PURE__ */ jsx(XMarkIcon, { className: "h-5 w-5" })
|
|
3477
|
-
}
|
|
3478
|
-
),
|
|
3479
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
|
|
3480
|
-
/* @__PURE__ */ jsx("div", { className: `shrink-0 w-10 h-10 rounded-full flex items-center justify-center ${dOptions.variant === "warning" ? "text-yellow-600 bg-yellow-100" : "text-red-600 bg-red-100"}`, children: /* @__PURE__ */ jsx(ExclamationTriangleIcon, { className: "h-5 w-5" }) }),
|
|
3481
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
3482
|
-
/* @__PURE__ */ jsx(DialogTitle, { className: "text-base font-semibold text-gray-900 pr-6", children: dOptions.title || "Confirm Action" }),
|
|
3483
|
-
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-gray-600", children: dOptions.message }),
|
|
3484
|
-
/* @__PURE__ */ jsxs("p", { className: "mt-3 text-sm text-gray-700", children: [
|
|
3485
|
-
"Type ",
|
|
3486
|
-
/* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 bg-gray-50 px-1.5 py-0.5 text-xs font-bold text-red-600 font-mono", children: dOptions.confirmWord }),
|
|
3487
|
-
" to confirm:"
|
|
3488
|
-
] }),
|
|
3489
|
-
/* @__PURE__ */ jsx(
|
|
3490
|
-
"input",
|
|
3491
|
-
{
|
|
3492
|
-
autoFocus: true,
|
|
3493
|
-
type: "text",
|
|
3494
|
-
value: dInput,
|
|
3495
|
-
onChange: (e) => setDInput(e.target.value),
|
|
3496
|
-
onKeyDown: (e) => {
|
|
3497
|
-
if (e.key === "Enter" && dInput === dOptions.confirmWord) handleDClose(true);
|
|
3498
|
-
},
|
|
3499
|
-
placeholder: dOptions.confirmWord,
|
|
3500
|
-
className: "mt-2 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-red-500 focus:ring-red-500"
|
|
3501
|
-
}
|
|
3502
|
-
)
|
|
3503
|
-
] })
|
|
3504
|
-
] }),
|
|
3505
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex justify-end gap-3", children: [
|
|
3506
|
-
/* @__PURE__ */ jsx(
|
|
3507
|
-
"button",
|
|
3508
|
-
{
|
|
3509
|
-
type: "button",
|
|
3510
|
-
onClick: () => handleDClose(false),
|
|
3511
|
-
className: "bg-white text-gray-700 border border-gray-300 px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-50",
|
|
3512
|
-
children: "Dismiss"
|
|
3513
|
-
}
|
|
3514
|
-
),
|
|
3515
|
-
/* @__PURE__ */ jsx(
|
|
3516
|
-
"button",
|
|
3517
|
-
{
|
|
3518
|
-
type: "button",
|
|
3519
|
-
onClick: () => handleDClose(true),
|
|
3520
|
-
disabled: dInput !== dOptions.confirmWord,
|
|
3521
|
-
className: `px-4 py-2 text-sm font-medium rounded-lg disabled:opacity-40 ${dOptions.variant === "warning" ? "bg-yellow-600 hover:bg-yellow-700 text-white" : "bg-red-600 hover:bg-red-700 text-white"}`,
|
|
3522
|
-
children: dOptions.confirmWord
|
|
3523
|
-
}
|
|
3524
|
-
)
|
|
3525
|
-
] })
|
|
3526
|
-
] }) })
|
|
3527
|
-
] }),
|
|
3528
|
-
/* @__PURE__ */ jsxs(Dialog, { open: pOpen, onClose: () => handlePClose(false), className: "relative z-[9999]", children: [
|
|
3529
|
-
/* @__PURE__ */ jsx(DialogBackdrop, { className: "fixed inset-0 bg-black/30 transition-opacity" }),
|
|
3530
|
-
/* @__PURE__ */ jsx("div", { className: "fixed inset-0 flex items-center justify-center p-4", children: /* @__PURE__ */ jsxs(DialogPanel, { className: "w-full max-w-md rounded-lg bg-white p-6 shadow-xl", children: [
|
|
3531
|
-
/* @__PURE__ */ jsx(DialogTitle, { className: "text-base font-semibold text-gray-900", children: pOptions.title || "Enter a value" }),
|
|
3532
|
-
pOptions.message && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-gray-600", children: pOptions.message }),
|
|
3533
|
-
/* @__PURE__ */ jsx(
|
|
3534
|
-
"input",
|
|
3535
|
-
{
|
|
3536
|
-
autoFocus: true,
|
|
3537
|
-
type: "text",
|
|
3538
|
-
value: pInput,
|
|
3539
|
-
onChange: (e) => setPInput(e.target.value),
|
|
3540
|
-
onKeyDown: (e) => {
|
|
3541
|
-
if (e.key === "Enter") handlePClose(true);
|
|
3542
|
-
else if (e.key === "Escape") handlePClose(false);
|
|
3543
|
-
},
|
|
3544
|
-
onFocus: (e) => e.target.select(),
|
|
3545
|
-
placeholder: pOptions.placeholder,
|
|
3546
|
-
className: "mt-3 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
|
3547
|
-
}
|
|
3548
|
-
),
|
|
3549
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex justify-end gap-3", children: [
|
|
3550
|
-
/* @__PURE__ */ jsx(
|
|
3551
|
-
"button",
|
|
3552
|
-
{
|
|
3553
|
-
type: "button",
|
|
3554
|
-
onClick: () => handlePClose(false),
|
|
3555
|
-
className: "bg-white text-gray-700 border border-gray-300 px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-50",
|
|
3556
|
-
children: pOptions.cancelLabel || "Cancel"
|
|
3557
|
-
}
|
|
3558
|
-
),
|
|
3559
|
-
/* @__PURE__ */ jsx(
|
|
3560
|
-
"button",
|
|
3561
|
-
{
|
|
3562
|
-
type: "button",
|
|
3563
|
-
onClick: () => handlePClose(true),
|
|
3564
|
-
disabled: !pOptions.allowEmpty && !pInput.trim(),
|
|
3565
|
-
className: "bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 text-sm font-medium rounded-lg disabled:opacity-40",
|
|
3566
|
-
children: pOptions.confirmLabel || "OK"
|
|
3567
|
-
}
|
|
3568
|
-
)
|
|
3569
|
-
] })
|
|
3570
|
-
] }) })
|
|
3571
|
-
] })
|
|
3572
|
-
] });
|
|
3104
|
+
return () => ctx.unregister(id);
|
|
3105
|
+
}, [ctx, id]);
|
|
3106
|
+
const { label, coalesceKey = null } = opts;
|
|
3107
|
+
const ctxRef = useRef(ctx);
|
|
3108
|
+
ctxRef.current = ctx;
|
|
3109
|
+
const optsRef = useRef({ label, coalesceKey });
|
|
3110
|
+
optsRef.current = { label, coalesceKey };
|
|
3111
|
+
useEffect(() => {
|
|
3112
|
+
const c = ctxRef.current;
|
|
3113
|
+
if (!c) return;
|
|
3114
|
+
if (Object.is(value, last.current)) return;
|
|
3115
|
+
c.record(optsRef.current.label, optsRef.current.coalesceKey);
|
|
3116
|
+
last.current = value;
|
|
3117
|
+
}, [value]);
|
|
3118
|
+
}
|
|
3119
|
+
function useUndoableState(initial, opts) {
|
|
3120
|
+
const [value, setValue] = useState(initial);
|
|
3121
|
+
useUndoable(value, (next) => setValue(() => next), opts);
|
|
3122
|
+
return [value, setValue];
|
|
3573
3123
|
}
|
|
3574
3124
|
|
|
3575
|
-
export { CancelButton,
|
|
3576
|
-
//# sourceMappingURL=chunk-
|
|
3577
|
-
//# sourceMappingURL=chunk-
|
|
3125
|
+
export { CancelButton, CopyButton, DocFavStar, LoadingSpinner, Modal, ModalActions, SIDEBAR_STRIP_W, UndoProvider, WINDOW_REGISTRY, WindowCrashedFallback, WindowErrorBoundary, WindowManagerProvider, WindowTitle, beginWindowGesture, cancelWindowFront, client_default, commitExposeHighlight, drainPerfEvents, exitExposeMode, forgetMaximizedWindowBoxes, getActiveModalId, getActiveWindowRoute, getExposeHighlight, getWindowPosition, isEntityEntry, isPageEntry, isShellApiClientConfigured, markMenuOpen, requestWindowFront, setExposeHighlight, setPerfCollecting, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, setWindowPosition, subscribeExposeHighlight, toggleExposeMode, useIsActiveWindow, useModalActive, useUndo, useUndoable, useUndoableState, useWidgetSettings, useWindowDirty, useWindowManager, useWindowMenuItem, useWindowTitle };
|
|
3126
|
+
//# sourceMappingURL=chunk-IGPSRRL7.js.map
|
|
3127
|
+
//# sourceMappingURL=chunk-IGPSRRL7.js.map
|