defuss-desktop 0.0.1

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/dist/index.mjs ADDED
@@ -0,0 +1,1028 @@
1
+ import { createRef, $ as $$1, dequery, CallChainImpl, createCall } from 'defuss';
2
+ import { debounce, throttle } from 'defuss-runtime';
3
+ import { jsx, jsxs } from 'defuss/jsx-runtime';
4
+ import { access, transval, rule } from 'defuss-transval';
5
+
6
+ function Button({
7
+ onClick = () => {
8
+ },
9
+ disabled = false,
10
+ children,
11
+ ref = createRef()
12
+ }) {
13
+ return /* @__PURE__ */ jsx(
14
+ "button",
15
+ {
16
+ ref,
17
+ type: "button",
18
+ onClick: disabled ? void 0 : onClick,
19
+ disabled,
20
+ children
21
+ }
22
+ );
23
+ }
24
+
25
+ const defaultWindowOptions = {
26
+ title: "Untitled",
27
+ resizable: true,
28
+ minimized: false,
29
+ maximized: false,
30
+ minimizable: true,
31
+ maximizable: true,
32
+ width: 800,
33
+ height: 600,
34
+ x: 0,
35
+ y: 0,
36
+ onClose: () => {
37
+ },
38
+ onMinimize: () => {
39
+ },
40
+ onMaximize: () => {
41
+ }
42
+ };
43
+ class WindowManager {
44
+ windows = [];
45
+ constructor() {
46
+ $$1(() => {
47
+ desktopManager.onResize(debounce(this.onDesktopResized.bind(this), 50));
48
+ });
49
+ }
50
+ onDesktopResized(dimensions) {
51
+ this.windows.forEach((win) => {
52
+ if (win.maximized) {
53
+ this.updateWindow(win.id, {
54
+ width: dimensions.width,
55
+ height: dimensions.height
56
+ });
57
+ const $win = $$1(win.el);
58
+ $win.css({
59
+ width: `${dimensions.width + 6}px`,
60
+ height: `${dimensions.height + 9}px`,
61
+ left: "-3px",
62
+ top: "-3px"
63
+ });
64
+ }
65
+ });
66
+ }
67
+ getActiveWindow() {
68
+ for (let i = this.windows.length - 1; i >= 0; i--) {
69
+ if (!this.windows[i].minimized) {
70
+ return this.windows[i];
71
+ }
72
+ }
73
+ return void 0;
74
+ }
75
+ getWindow(id) {
76
+ return this.windows.find((win) => win.id === id);
77
+ }
78
+ setActiveWindow(id) {
79
+ const window = this.getWindow(id);
80
+ if (window) {
81
+ this.windows = this.windows.filter((win) => {
82
+ if (win.id !== id) {
83
+ return true;
84
+ }
85
+ return false;
86
+ });
87
+ this.windows.push(window);
88
+ this.renderWindowsActivationState();
89
+ }
90
+ }
91
+ renderWindowsActivationState() {
92
+ const activeWindow = this.getActiveWindow();
93
+ if (!activeWindow) return;
94
+ this.windows.forEach(async (win, index) => {
95
+ const $win = await $$1(win.el);
96
+ const $titleBar = await $$1(win.el).find(".title-bar");
97
+ await $win.css("z-index", index.toString());
98
+ const hasInactiveClass = async () => await $titleBar.hasClass("inactive");
99
+ if (!await hasInactiveClass()) {
100
+ await $win.addClass("inactive");
101
+ await $titleBar.addClass("inactive");
102
+ }
103
+ if (win.id === activeWindow.id) {
104
+ if (await hasInactiveClass()) {
105
+ await $win.removeClass("inactive");
106
+ await $titleBar.removeClass("inactive");
107
+ }
108
+ }
109
+ });
110
+ }
111
+ addWindow(options) {
112
+ const id = options.id || crypto.randomUUID();
113
+ const state = {
114
+ id,
115
+ title: options.title || defaultWindowOptions.title,
116
+ icon: options.icon || defaultWindowOptions.icon,
117
+ width: options.width || defaultWindowOptions.width,
118
+ height: options.height || defaultWindowOptions.height,
119
+ prevX: options.x || defaultWindowOptions.x,
120
+ prevY: options.y || defaultWindowOptions.y,
121
+ prevWidth: options.width || defaultWindowOptions.width,
122
+ prevHeight: options.height || defaultWindowOptions.height,
123
+ originalDimensionsStored: false,
124
+ x: options.x || defaultWindowOptions.x,
125
+ y: options.y || defaultWindowOptions.y,
126
+ resizable: options.resizable !== void 0 ? options.resizable : defaultWindowOptions.resizable,
127
+ minimizable: options.minimizable !== void 0 ? options.minimizable : defaultWindowOptions.minimizable,
128
+ maximizable: options.maximizable !== void 0 ? options.maximizable : defaultWindowOptions.maximizable,
129
+ minimized: options.minimized !== void 0 ? options.minimized : defaultWindowOptions.minimized,
130
+ maximized: options.maximized !== void 0 ? options.maximized : defaultWindowOptions.maximized
131
+ };
132
+ const activeWindow = this.getActiveWindow();
133
+ if (activeWindow) {
134
+ state.x = activeWindow.x + 20;
135
+ state.y = activeWindow.y + 20;
136
+ }
137
+ this.windows.push(state);
138
+ return state;
139
+ }
140
+ updateWindow(id, options) {
141
+ const win = this.getWindow(id);
142
+ if (!win) return void 0;
143
+ if (win.x) {
144
+ options.prevX = win.x;
145
+ }
146
+ if (win.y) {
147
+ options.prevY = win.y;
148
+ }
149
+ const updatedWindow = {
150
+ ...win,
151
+ ...options,
152
+ id: win.id
153
+ // ensure ID remains unchanged
154
+ };
155
+ this.windows = this.windows.map(
156
+ (win2) => win2.id === id ? {
157
+ ...win2,
158
+ ...updatedWindow
159
+ } : win2
160
+ );
161
+ return updatedWindow;
162
+ }
163
+ closeWindow(id) {
164
+ const win = this.getWindow(id);
165
+ if (!win) return;
166
+ $$1(win.el).remove();
167
+ this.windows = this.windows.filter((win2) => win2.id !== id);
168
+ this.renderWindowsActivationState();
169
+ win.ref.state?.onClose?.();
170
+ }
171
+ maximizeWindow(id) {
172
+ let win = this.getWindow(id);
173
+ if (!win) return;
174
+ const isMaximized = !win.maximized;
175
+ if (isMaximized) {
176
+ if (!win.originalDimensionsStored) {
177
+ const actualWidth = win.el.offsetWidth || win.width;
178
+ const actualHeight = win.el.offsetHeight || win.height;
179
+ win = this.updateWindow(id, {
180
+ prevX: win.x,
181
+ prevY: win.y,
182
+ prevWidth: actualWidth,
183
+ prevHeight: actualHeight,
184
+ originalDimensionsStored: true
185
+ });
186
+ } else {
187
+ win = this.updateWindow(id, {
188
+ prevX: win.x,
189
+ prevY: win.y
190
+ });
191
+ }
192
+ }
193
+ const desktopDimensions = desktopManager.getDimensions();
194
+ const freshWin = this.getWindow(id);
195
+ win = this.updateWindow(id, {
196
+ maximized: isMaximized,
197
+ width: isMaximized ? desktopDimensions.width : freshWin.prevWidth,
198
+ height: isMaximized ? desktopDimensions.height : freshWin.prevHeight,
199
+ x: isMaximized ? 0 : freshWin.prevX,
200
+ y: isMaximized ? 0 : freshWin.prevY
201
+ });
202
+ const $win = $$1(win.el);
203
+ $win.css({
204
+ width: isMaximized ? `${desktopDimensions.width + 6}px` : `${win.width}px`,
205
+ height: isMaximized ? `${desktopDimensions.height + 9}px` : `${win.height}px`,
206
+ left: isMaximized ? "-3px" : `${win.x}px`,
207
+ top: isMaximized ? "-3px" : `${win.y}px`
208
+ });
209
+ this.toggleTitleBarMaximizedButtonState(id);
210
+ if (isMaximized) {
211
+ win.ref.state?.onMaximize?.();
212
+ }
213
+ }
214
+ minimizeWindow(id) {
215
+ let win = this.getWindow(id);
216
+ if (!win) return;
217
+ win = this.updateWindow(id, { prevX: win.x, prevY: win.y });
218
+ const isMinimized = !win.minimized;
219
+ win = this.updateWindow(id, {
220
+ minimized: isMinimized,
221
+ width: isMinimized ? 0 : win.width,
222
+ height: isMinimized ? 0 : win.height,
223
+ x: isMinimized ? -1e4 : win.x,
224
+ // Move off-screen when minimized
225
+ y: isMinimized ? -1e4 : win.y
226
+ });
227
+ const $win = $$1(win.el);
228
+ $win.css({
229
+ width: isMinimized ? "0px" : `${win.width}px`,
230
+ height: isMinimized ? "0px" : `${win.height}px`,
231
+ left: isMinimized ? "-10000px" : `${win.x}px`,
232
+ top: isMinimized ? "-10000px" : `${win.y}px`
233
+ });
234
+ if (isMinimized) {
235
+ win.ref.state?.onMinimize?.();
236
+ }
237
+ }
238
+ restoreWindow(id) {
239
+ let win = this.getWindow(id);
240
+ if (!win) return;
241
+ win = this.updateWindow(id, {
242
+ maximized: false,
243
+ width: win.prevWidth || 800,
244
+ // Use stored previous width
245
+ height: win.prevHeight || 600,
246
+ // Use stored previous height
247
+ x: win.prevX || 0,
248
+ y: win.prevY || 0
249
+ });
250
+ const $win = $$1(win.el);
251
+ $win.css({
252
+ width: `${win.width}px`,
253
+ height: `${win.height}px`,
254
+ left: `${win.x}px`,
255
+ top: `${win.y}px`
256
+ });
257
+ this.toggleTitleBarMaximizedButtonState(id);
258
+ }
259
+ toggleTitleBarMaximizedButtonState(id) {
260
+ const win = this.getWindow(id);
261
+ if (!win) return;
262
+ const isMaximized = !win.maximized;
263
+ $$1(win.el).query(
264
+ isMaximized ? "button[aria-label='Restore']" : "button[aria-label='Maximize']"
265
+ ).attr("aria-label", isMaximized ? "Maximize" : "Restore");
266
+ }
267
+ }
268
+ globalThis.__defussWindowManager = globalThis.__defussWindowManager || new WindowManager();
269
+ const windowManager = globalThis.__defussWindowManager;
270
+
271
+ function Window({
272
+ title = "Untitled",
273
+ height = 300,
274
+ width = 200,
275
+ x = 50,
276
+ y = 100,
277
+ children,
278
+ ref = createRef(),
279
+ resizable = true,
280
+ minimizable = true,
281
+ maximizable = true,
282
+ id = void 0,
283
+ onClose = () => {
284
+ },
285
+ onMinimize = () => {
286
+ },
287
+ onMaximize = () => {
288
+ }
289
+ }) {
290
+ let isDragging = false;
291
+ const initialWindowState = windowManager.addWindow({
292
+ id,
293
+ title,
294
+ width,
295
+ height,
296
+ x,
297
+ y,
298
+ resizable,
299
+ minimizable,
300
+ maximizable
301
+ });
302
+ let dragStart = { x: initialWindowState.x, y: initialWindowState.y };
303
+ ref.state = {
304
+ onClose: () => {
305
+ console.log("Window closed");
306
+ onClose();
307
+ },
308
+ onMinimize: () => {
309
+ console.log("Window minimized");
310
+ onMinimize();
311
+ },
312
+ onMaximize: () => {
313
+ console.log("Window maximized");
314
+ onMaximize();
315
+ },
316
+ minimize: () => {
317
+ windowManager.minimizeWindow(initialWindowState.id);
318
+ },
319
+ maximize: () => {
320
+ windowManager.maximizeWindow(initialWindowState.id);
321
+ },
322
+ restore: () => {
323
+ windowManager.restoreWindow(initialWindowState.id);
324
+ },
325
+ close: () => {
326
+ console.log("Closing window");
327
+ windowManager.closeWindow(initialWindowState.id);
328
+ }
329
+ };
330
+ const updateWindowState = throttle(
331
+ (newState) => {
332
+ windowManager.updateWindow(initialWindowState.id, newState);
333
+ },
334
+ 250
335
+ // 1/4 second throttle
336
+ );
337
+ const onMouseMove = async (event) => {
338
+ if (!isDragging) return;
339
+ const mouseEvent = event;
340
+ const win = await $$1(ref);
341
+ const deltaX = mouseEvent.clientX - dragStart.x;
342
+ const deltaY = mouseEvent.clientY - dragStart.y;
343
+ const currentOffset = await win.offset();
344
+ if (currentOffset) {
345
+ const newX = currentOffset.left + deltaX;
346
+ const newY = currentOffset.top + deltaY;
347
+ updateWindowState({ x: newX, y: newY });
348
+ await win.css({
349
+ left: `${newX}px`,
350
+ top: `${newY}px`
351
+ });
352
+ }
353
+ dragStart = { x: mouseEvent.clientX, y: mouseEvent.clientY };
354
+ };
355
+ const onWindowMouseDown = (event) => windowManager.setActiveWindow(initialWindowState.id);
356
+ const onMouseDown = (event) => {
357
+ if (event.target.tagName === "BUTTON") {
358
+ isDragging = false;
359
+ return;
360
+ }
361
+ isDragging = true;
362
+ dragStart = {
363
+ x: event.clientX,
364
+ y: event.clientY
365
+ };
366
+ $$1(document).on("mousemove", onMouseMove);
367
+ $$1(document).on("mouseup", onMouseUp);
368
+ event.preventDefault();
369
+ };
370
+ const onMouseUp = () => {
371
+ isDragging = false;
372
+ $$1(document).off("mousemove", onMouseMove);
373
+ $$1(document).off("mouseup", onMouseUp);
374
+ };
375
+ const onWindowMounted = () => {
376
+ windowManager.updateWindow(initialWindowState.id, {
377
+ el: ref.current,
378
+ ref
379
+ });
380
+ windowManager.setActiveWindow(initialWindowState.id);
381
+ };
382
+ const onCloseClick = () => windowManager.closeWindow(initialWindowState.id);
383
+ const onMaximizeClick = async () => {
384
+ const currentState = windowManager.getWindow(initialWindowState.id);
385
+ if (currentState?.maximized) {
386
+ windowManager.restoreWindow(initialWindowState.id);
387
+ } else {
388
+ windowManager.maximizeWindow(initialWindowState.id);
389
+ }
390
+ };
391
+ const onMinimizeClick = async () => {
392
+ windowManager.minimizeWindow(initialWindowState.id);
393
+ };
394
+ return /* @__PURE__ */ jsxs(
395
+ "div",
396
+ {
397
+ class: "window crt",
398
+ ref,
399
+ onMouseDown: onWindowMouseDown,
400
+ style: {
401
+ width,
402
+ height,
403
+ left: `${initialWindowState.x}px`,
404
+ top: `${initialWindowState.y}px`,
405
+ position: "absolute"
406
+ },
407
+ children: [
408
+ /* @__PURE__ */ jsxs(
409
+ "div",
410
+ {
411
+ class: "title-bar",
412
+ onMount: onWindowMounted,
413
+ onMouseDown,
414
+ onMouseUp,
415
+ onMouseMove,
416
+ children: [
417
+ /* @__PURE__ */ jsx("div", { class: "title-bar-text", children: title }),
418
+ /* @__PURE__ */ jsxs("div", { class: "title-bar-controls", children: [
419
+ /* @__PURE__ */ jsx(
420
+ "button",
421
+ {
422
+ type: "button",
423
+ "aria-label": "Minimize",
424
+ onClick: onMinimizeClick
425
+ }
426
+ ),
427
+ /* @__PURE__ */ jsx(
428
+ "button",
429
+ {
430
+ type: "button",
431
+ "aria-label": "Maximize",
432
+ onClick: onMaximizeClick
433
+ }
434
+ ),
435
+ /* @__PURE__ */ jsx(
436
+ "button",
437
+ {
438
+ type: "button",
439
+ "aria-label": "Close",
440
+ onClick: onCloseClick
441
+ }
442
+ )
443
+ ] })
444
+ ]
445
+ }
446
+ ),
447
+ /* @__PURE__ */ jsx("div", { class: "window-body", children })
448
+ ]
449
+ }
450
+ );
451
+ }
452
+
453
+ function Desktop({ ref }) {
454
+ $$1(() => {
455
+ console.log("Desktop mounted");
456
+ });
457
+ const onOpenWindow = async () => {
458
+ const winRef = createRef();
459
+ await $$1(ref).append(
460
+ /* @__PURE__ */ jsxs(
461
+ Window,
462
+ {
463
+ width: 300,
464
+ height: 200,
465
+ title: "Test Window",
466
+ ref: winRef,
467
+ onClose: () => {
468
+ console.log("I WAS CLOSED!");
469
+ },
470
+ onMaximize: () => {
471
+ console.log("I WAS MAXIMIZED!");
472
+ },
473
+ onMinimize: () => {
474
+ console.log("I WAS MINIMIZED!");
475
+ },
476
+ children: [
477
+ /* @__PURE__ */ jsx("p", { children: "Hello, world!" }),
478
+ /* @__PURE__ */ jsxs("section", { class: "field-row", style: "justify-content: space-between;", children: [
479
+ /* @__PURE__ */ jsx(
480
+ "button",
481
+ {
482
+ type: "button",
483
+ onClick: () => {
484
+ console.log("Cancel clicked");
485
+ winRef.state?.close();
486
+ },
487
+ children: "Cancel"
488
+ }
489
+ ),
490
+ /* @__PURE__ */ jsx(Button, { onClick: onOpenWindow, children: "Open Window" }),
491
+ /* @__PURE__ */ jsx(
492
+ "button",
493
+ {
494
+ type: "button",
495
+ onClick: () => {
496
+ console.log("OK clicked");
497
+ winRef.state?.close();
498
+ },
499
+ children: "OK"
500
+ }
501
+ )
502
+ ] })
503
+ ]
504
+ }
505
+ )
506
+ );
507
+ };
508
+ return /* @__PURE__ */ jsx("div", { class: "defuss-desktop-panel crt", ref, children: /* @__PURE__ */ jsx(Button, { onClick: onOpenWindow, children: "Open Window" }) });
509
+ }
510
+
511
+ const LogonScreen = ({
512
+ cDriveBasePath = "",
513
+ showGuestUser = true,
514
+ onTurnOffComputer,
515
+ onGuestLogon,
516
+ onUserLogonSubmit,
517
+ ref = createRef()
518
+ }) => {
519
+ const userLoginFormRef = createRef();
520
+ $$1(userLoginFormRef).on("submit", async (event) => {
521
+ event.preventDefault();
522
+ const formData = await $$1(userLoginFormRef).form();
523
+ const loginForm = access();
524
+ console.log("Form submitted with data:", formData);
525
+ const { isValid, getMessages, getData, getField } = transval(
526
+ rule(loginForm.username).asString().isRequired(),
527
+ rule(loginForm.password).asString().isRequired()
528
+ );
529
+ if (!await isValid(formData)) {
530
+ console.error("Form validation failed:", getMessages());
531
+ return;
532
+ }
533
+ const loginFormData = getData();
534
+ const loginResult = await onUserLogonSubmit?.(
535
+ loginFormData.username,
536
+ loginFormData.password
537
+ );
538
+ console.log("Login result:", loginResult);
539
+ });
540
+ const onActivateAccountLogin = (evt) => {
541
+ $$1(evt.target).closest(".logon-screen__account").addClass("active");
542
+ };
543
+ return /* @__PURE__ */ jsxs("div", { class: "logon-screen crt", ref, children: [
544
+ /* @__PURE__ */ jsx("div", { class: "logon-screen__top" }),
545
+ /* @__PURE__ */ jsxs("div", { class: "logon-screen__center", children: [
546
+ /* @__PURE__ */ jsxs("div", { class: "logon-screen__instructions", children: [
547
+ /* @__PURE__ */ jsx(
548
+ "img",
549
+ {
550
+ src: `${cDriveBasePath}/defuss_xp_logo.webp`,
551
+ alt: "defuss XP logo"
552
+ }
553
+ ),
554
+ /* @__PURE__ */ jsx("span", { children: "To begin, click your user name" })
555
+ ] }),
556
+ /* @__PURE__ */ jsxs("div", { class: "logon-screen__accounts", children: [
557
+ /* @__PURE__ */ jsxs(
558
+ "div",
559
+ {
560
+ tabindex: "-1",
561
+ class: "logon-screen__account",
562
+ onClick: onActivateAccountLogin,
563
+ children: [
564
+ /* @__PURE__ */ jsx("div", { class: "logon-screen__account-icon", children: /* @__PURE__ */ jsx(
565
+ "img",
566
+ {
567
+ src: `${cDriveBasePath}/icons/profile_picture_duck.webp`,
568
+ alt: "duck"
569
+ }
570
+ ) }),
571
+ /* @__PURE__ */ jsxs("div", { class: "logon-screen__account-details", children: [
572
+ /* @__PURE__ */ jsx("span", { class: "logon-screen__account-name", children: "Duck" }),
573
+ /* @__PURE__ */ jsxs("div", { class: "logon-screen__password", children: [
574
+ /* @__PURE__ */ jsx("span", { children: "Type your password" }),
575
+ /* @__PURE__ */ jsxs("form", { ref: userLoginFormRef, children: [
576
+ /* @__PURE__ */ jsx(
577
+ "input",
578
+ {
579
+ type: "password",
580
+ autoComplete: "new-password",
581
+ name: "password"
582
+ }
583
+ ),
584
+ /* @__PURE__ */ jsx("input", { type: "hidden", name: "username", value: "Duck" }),
585
+ /* @__PURE__ */ jsx("button", { class: "logon-screen__submit", type: "submit", children: /* @__PURE__ */ jsx("span", {}) }),
586
+ /* @__PURE__ */ jsx("button", { class: "logon-screen__question", type: "button", children: /* @__PURE__ */ jsx("span", { children: "?" }) })
587
+ ] })
588
+ ] })
589
+ ] })
590
+ ]
591
+ }
592
+ ),
593
+ showGuestUser && /* @__PURE__ */ jsxs(
594
+ "div",
595
+ {
596
+ tabindex: "-1",
597
+ class: "logon-screen__account",
598
+ onClick: onGuestLogon,
599
+ children: [
600
+ /* @__PURE__ */ jsx("div", { class: "logon-screen__account-icon", children: /* @__PURE__ */ jsx(
601
+ "img",
602
+ {
603
+ src: `${cDriveBasePath}/icons/profile_picture_chess.webp`,
604
+ alt: "chess"
605
+ }
606
+ ) }),
607
+ /* @__PURE__ */ jsx("div", { class: "logon-screen__account-details", children: /* @__PURE__ */ jsx("span", { class: "logon-screen__account-name", children: "Guest" }) })
608
+ ]
609
+ }
610
+ )
611
+ ] })
612
+ ] }),
613
+ /* @__PURE__ */ jsxs("div", { class: "logon-screen__bottom", children: [
614
+ /* @__PURE__ */ jsxs("div", { class: "logon-screen__turn-off", onClick: onTurnOffComputer, children: [
615
+ /* @__PURE__ */ jsx("button", { type: "button", class: "logon-screen__turn-off-icon" }),
616
+ " ",
617
+ /* @__PURE__ */ jsx("span", { children: "Turn off computer" })
618
+ ] }),
619
+ /* @__PURE__ */ jsx("div", { class: "logon-screen__logon-info", children: /* @__PURE__ */ jsxs("span", { children: [
620
+ "After you log on, you can add or change accounts.",
621
+ /* @__PURE__ */ jsx("br", {}),
622
+ "Just go to Control Panel and click User Accounts."
623
+ ] }) })
624
+ ] })
625
+ ] });
626
+ };
627
+
628
+ class DefussDesktopAppIcon {
629
+ constructor(config) {
630
+ this.config = config;
631
+ }
632
+ }
633
+ const defaultDesktopOptions = {
634
+ icons: [],
635
+ backgroundColor: "#000"
636
+ };
637
+ class DesktopManager {
638
+ constructor(options = defaultDesktopOptions) {
639
+ this.options = options;
640
+ }
641
+ el;
642
+ state;
643
+ resizeObserver;
644
+ resizeCallbacks = /* @__PURE__ */ new Set();
645
+ init(el, options = this.options) {
646
+ this.options = options;
647
+ this.el = el;
648
+ this.state = this.state || {
649
+ icons: this.options.icons.map((icon) => icon.config)
650
+ };
651
+ this.setupResizeObserver();
652
+ this.render(el);
653
+ }
654
+ render(el) {
655
+ el.style.backgroundColor = this.options.backgroundColor;
656
+ if (this.options.backgroundImage) {
657
+ el.style.backgroundImage = `url(${this.options.backgroundImage})`;
658
+ }
659
+ if (this.options.backgroundImageSize) {
660
+ el.style.backgroundSize = this.options.backgroundImageSize || "cover";
661
+ }
662
+ if (this.options.backgroundRepeat) {
663
+ el.style.backgroundRepeat = this.options.backgroundRepeat || "no-repeat";
664
+ }
665
+ if (this.options.backgroundPosition) {
666
+ el.style.backgroundPosition = this.options.backgroundPosition || "center";
667
+ }
668
+ }
669
+ addIcon(icon) {
670
+ this.options.icons.push(icon);
671
+ console.log(`Icon added: ${icon.config.name}`);
672
+ }
673
+ getDimensions() {
674
+ if (!this.el) {
675
+ throw new Error("Desktop not initialized. Call init() first.");
676
+ }
677
+ return {
678
+ width: this.el.offsetWidth,
679
+ height: this.el.offsetHeight - taskbarManager.getDimensions().height
680
+ // destop is root element minus taskbar height
681
+ };
682
+ }
683
+ setupResizeObserver() {
684
+ if (!this.el) return;
685
+ if (this.resizeObserver) {
686
+ this.resizeObserver.disconnect();
687
+ }
688
+ this.resizeObserver = new ResizeObserver(() => {
689
+ const dimensions = this.getDimensions();
690
+ this.resizeCallbacks.forEach((callback) => {
691
+ try {
692
+ callback(dimensions);
693
+ } catch (error) {
694
+ console.error("Error in desktop resize callback:", error);
695
+ }
696
+ });
697
+ });
698
+ this.resizeObserver.observe(this.el);
699
+ }
700
+ /**
701
+ * Register a callback for desktop resize events
702
+ * @param callback Function to call when desktop is resized
703
+ * @returns Unregister function to remove the callback
704
+ */
705
+ onResize(callback) {
706
+ this.resizeCallbacks.add(callback);
707
+ return () => {
708
+ this.resizeCallbacks.delete(callback);
709
+ };
710
+ }
711
+ }
712
+ globalThis.__defussDesktopManager = globalThis.__defussDesktopManager || new DesktopManager();
713
+ const desktopManager = globalThis.__defussDesktopManager;
714
+
715
+ const StartMenu = () => {
716
+ return /* @__PURE__ */ jsxs("div", { class: "slide-open crt", children: [
717
+ /* @__PURE__ */ jsx("div", { class: "top", children: /* @__PURE__ */ jsx("h1", { children: "Aron Homberg" }) }),
718
+ /* @__PURE__ */ jsxs("div", { class: "menu", children: [
719
+ /* @__PURE__ */ jsx("div", { class: "programs", children: /* @__PURE__ */ jsxs("ul", { children: [
720
+ /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
721
+ /* @__PURE__ */ jsx("a", { class: "program-image", href: "#", children: /* @__PURE__ */ jsx("img", { src: "/icons/ie.png", alt: "" }) }),
722
+ /* @__PURE__ */ jsx("a", { href: "#", children: "Internet Explorer" })
723
+ ] }),
724
+ /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
725
+ /* @__PURE__ */ jsx("a", { class: "program-image", href: "#", children: /* @__PURE__ */ jsx("img", { src: "/icons/paint.png", alt: "" }) }),
726
+ /* @__PURE__ */ jsx("a", { href: "#", children: "Paint" })
727
+ ] }),
728
+ /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
729
+ /* @__PURE__ */ jsx("a", { class: "program-image", href: "#", children: /* @__PURE__ */ jsx("img", { src: "/icons/media-player.png", alt: "" }) }),
730
+ /* @__PURE__ */ jsx("a", { href: "#", children: "Windows Media Player" })
731
+ ] }),
732
+ /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
733
+ /* @__PURE__ */ jsx("a", { class: "program-image", href: "#", children: /* @__PURE__ */ jsx("img", { src: "/icons/outlook-express.jpg", alt: "" }) }),
734
+ /* @__PURE__ */ jsx("a", { href: "#", children: "E-mail" })
735
+ ] })
736
+ ] }) }),
737
+ /* @__PURE__ */ jsx("div", { class: "system", children: /* @__PURE__ */ jsxs("ul", { children: [
738
+ /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
739
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/documents.png", alt: "" }) }),
740
+ /* @__PURE__ */ jsx("a", { href: "#", children: "My Documents" })
741
+ ] }),
742
+ /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
743
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/pictures.png", alt: "" }) }),
744
+ /* @__PURE__ */ jsx("a", { href: "#", children: "My Pictures" })
745
+ ] }),
746
+ /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
747
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/music.png", alt: "" }) }),
748
+ /* @__PURE__ */ jsx("a", { href: "#", children: "My Music" })
749
+ ] }),
750
+ /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
751
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/computer.png", alt: "" }) }),
752
+ /* @__PURE__ */ jsx("a", { href: "#", children: "My Computer" })
753
+ ] }),
754
+ /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
755
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/control-panel.png", alt: "" }) }),
756
+ /* @__PURE__ */ jsx("a", { href: "#", children: "Control Panel" })
757
+ ] }),
758
+ /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
759
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/support-help.png", alt: "" }) }),
760
+ /* @__PURE__ */ jsx("a", { href: "#", children: "Help and Support" })
761
+ ] }),
762
+ /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
763
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/search.png", alt: "" }) }),
764
+ /* @__PURE__ */ jsx("a", { href: "#", children: "Search" })
765
+ ] }),
766
+ /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
767
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/run.png", alt: "" }) }),
768
+ /* @__PURE__ */ jsx("a", { href: "#", children: "Run..." })
769
+ ] })
770
+ ] }) })
771
+ ] }),
772
+ /* @__PURE__ */ jsx("div", { class: "bottom" })
773
+ ] });
774
+ };
775
+
776
+ const StartButton = () => {
777
+ return /* @__PURE__ */ jsxs("ul", { class: "start-button", children: [
778
+ /* @__PURE__ */ jsx(
779
+ "img",
780
+ {
781
+ class: "start-button-icon",
782
+ src: "/defuss_xp_quad.webp",
783
+ alt: "Start Icon"
784
+ }
785
+ ),
786
+ /* @__PURE__ */ jsx("a", { href: "#", children: "Start " }),
787
+ /* @__PURE__ */ jsx(StartMenu, {})
788
+ ] });
789
+ };
790
+
791
+ const Taskbar = () => {
792
+ return /* @__PURE__ */ jsxs("div", { class: "bar crt", children: [
793
+ /* @__PURE__ */ jsx(StartButton, {}),
794
+ /* @__PURE__ */ jsx("ul", { class: "taskbar" }),
795
+ /* @__PURE__ */ jsx("div", { class: "tray-toggle", children: /* @__PURE__ */ jsx("div", { class: "arrow" }) }),
796
+ /* @__PURE__ */ jsx("div", { class: "taskbar__clock", children: /* @__PURE__ */ jsx("span", { children: "7:02 AM" }) })
797
+ ] });
798
+ };
799
+
800
+ function Shell({
801
+ ref = createRef(),
802
+ desktopConfig = defaultDesktopOptions
803
+ }) {
804
+ const desktopRef = createRef();
805
+ const taskbarRef = createRef();
806
+ const onMount = () => {
807
+ console.log("Shell mounted", ref.current);
808
+ console.log("Desktop mounted2", desktopRef.current);
809
+ desktopManager.init(desktopRef.current, desktopConfig);
810
+ console.log("Taskbar mounted", taskbarRef.current);
811
+ };
812
+ return /* @__PURE__ */ jsxs("div", { ref, onMount, children: [
813
+ /* @__PURE__ */ jsx(Desktop, { ref: desktopRef }),
814
+ /* @__PURE__ */ jsx(Taskbar, { ref: taskbarRef })
815
+ ] });
816
+ }
817
+
818
+ class DesktopShellManager {
819
+ constructor(apps = []) {
820
+ this.apps = apps;
821
+ }
822
+ addApp(app) {
823
+ this.apps.push(app);
824
+ console.log(`App added: ${app.config.name}`);
825
+ }
826
+ }
827
+ globalThis.__defussDesktopShellManager = globalThis.__defussDesktopShellManager || new DesktopShellManager();
828
+ const desktopShell = globalThis.__defussDesktopShellManager;
829
+
830
+ class DefussApp {
831
+ constructor(config) {
832
+ this.config = config;
833
+ desktopShell.addApp(this);
834
+ }
835
+ run() {
836
+ console.log(`Running app: ${this.config.name}`);
837
+ this.config.main(this, ...this.config.argv || []);
838
+ }
839
+ }
840
+
841
+ class DequeryWithWindowManager extends CallChainImpl {
842
+ /*
843
+ // create a window from any element (not necessarily identifier as an App)
844
+ createDesktopWindow(options: CreateWindowOptions): PromiseLike<Window> {
845
+ return createCall(this, "createWindow", async () => {
846
+ return new WindowManager(options) as NT;
847
+ }) as unknown as PromiseLike<Window>;
848
+ }
849
+ */
850
+ // register an app to the desktop shell
851
+ createDesktopApp(options) {
852
+ return createCall(this, "createDesktopApp", async () => {
853
+ return new DefussApp(options);
854
+ });
855
+ }
856
+ // create a desktop app icon
857
+ createDesktopAppIcon(options) {
858
+ return createCall(this, "createDesktopAppIcon", async () => {
859
+ return new DefussDesktopAppIcon(options);
860
+ });
861
+ }
862
+ }
863
+ const $ = dequery.extend(DequeryWithWindowManager, [
864
+ "createDesktopApp",
865
+ "createWindow",
866
+ "createDesktopAppIcon"
867
+ ]);
868
+
869
+ const defaultTaskbarOptions = {
870
+ position: "bottom",
871
+ stateful: false,
872
+ theme: "default",
873
+ size: "medium"
874
+ };
875
+ class TaskbarManager {
876
+ position;
877
+ theme;
878
+ // e.g., 'windows-xp', 'macos', etc.
879
+ size;
880
+ constructor(options = defaultTaskbarOptions) {
881
+ this.position = options.position || "bottom";
882
+ this.theme = options.theme || "default";
883
+ this.size = options.size || "medium";
884
+ if (options.stateful) ;
885
+ }
886
+ getDimensions() {
887
+ const el = document.querySelector(".taskbar");
888
+ if (!el) {
889
+ return { width: 0, height: 0 };
890
+ }
891
+ return {
892
+ width: el.clientWidth,
893
+ height: el.clientHeight
894
+ };
895
+ }
896
+ }
897
+ globalThis.__defussTaskbarManager = globalThis.__defussTaskbarManager || new TaskbarManager();
898
+ const taskbarManager = globalThis.__defussTaskbarManager;
899
+
900
+ const defaultSystemSoundFilePaths = [
901
+ "/sounds/balloon.ogg",
902
+ "/sounds/batterycritical.ogg",
903
+ "/sounds/batterylow.ogg",
904
+ "/sounds/criticalstop.ogg",
905
+ "/sounds/ding.ogg",
906
+ "/sounds/error.ogg",
907
+ "/sounds/exclamation.ogg",
908
+ "/sounds/hardwarefail.ogg",
909
+ "/sounds/hardwareinsert.ogg",
910
+ "/sounds/logoffsound.ogg",
911
+ "/sounds/logonsound.ogg",
912
+ "/sounds/notify.ogg",
913
+ "/sounds/recycle.ogg",
914
+ "/sounds/shutdown.ogg",
915
+ "/sounds/start.ogg",
916
+ "/sounds/startup.ogg"
917
+ ];
918
+ class SoundManager {
919
+ audioContext = null;
920
+ audioCacheBuffers = /* @__PURE__ */ new Map();
921
+ activeSources = /* @__PURE__ */ new Set();
922
+ async init() {
923
+ this.audioContext = new AudioContext();
924
+ if (this.audioContext.state === "suspended") {
925
+ await this.audioContext.resume();
926
+ }
927
+ }
928
+ preload(url) {
929
+ if (!Array.isArray(url)) {
930
+ url = [url];
931
+ }
932
+ if (!this.audioContext) {
933
+ return Promise.reject(new Error("AudioContext is not initialized"));
934
+ }
935
+ const loadPromises = url.map((singleUrl) => {
936
+ if (this.audioCacheBuffers.has(singleUrl)) {
937
+ return Promise.resolve(this.audioCacheBuffers.get(singleUrl));
938
+ }
939
+ return new Promise((resolve, reject) => {
940
+ const request = new XMLHttpRequest();
941
+ request.open("GET", singleUrl, true);
942
+ request.responseType = "arraybuffer";
943
+ request.onload = () => {
944
+ this.audioContext.decodeAudioData(
945
+ request.response,
946
+ (buffer) => {
947
+ this.audioCacheBuffers.set(singleUrl, buffer);
948
+ resolve(buffer);
949
+ },
950
+ (error) => reject(
951
+ new Error(
952
+ `Failed to decode audio data for ${singleUrl}: ${error}`
953
+ )
954
+ )
955
+ );
956
+ };
957
+ request.onerror = () => {
958
+ reject(new Error(`Failed to load audio file from ${singleUrl}`));
959
+ };
960
+ request.send();
961
+ });
962
+ });
963
+ return url.length === 1 ? loadPromises[0] : Promise.all(loadPromises);
964
+ }
965
+ async play(bufferOrBufferUrl, options) {
966
+ let buffer;
967
+ if (!this.audioContext) {
968
+ throw new Error("AudioContext is not initialized");
969
+ }
970
+ if (this.audioContext.state === "suspended") {
971
+ await this.audioContext.resume();
972
+ }
973
+ if (typeof bufferOrBufferUrl === "string") {
974
+ if (!this.audioCacheBuffers.has(bufferOrBufferUrl)) {
975
+ await this.preload(bufferOrBufferUrl);
976
+ }
977
+ buffer = this.audioCacheBuffers.get(bufferOrBufferUrl);
978
+ if (!buffer) {
979
+ throw new Error(
980
+ `Audio buffer for URL ${bufferOrBufferUrl} is not preloaded.`
981
+ );
982
+ }
983
+ } else if (bufferOrBufferUrl instanceof AudioBuffer) {
984
+ buffer = bufferOrBufferUrl;
985
+ } else {
986
+ throw new Error(
987
+ "Invalid argument: must be an AudioBuffer or a preloaded URL string."
988
+ );
989
+ }
990
+ const source = this.audioContext.createBufferSource();
991
+ source.buffer = buffer;
992
+ if (options?.volume !== void 0) {
993
+ const gainNode = this.audioContext.createGain();
994
+ gainNode.gain.value = options.volume;
995
+ source.connect(gainNode);
996
+ gainNode.connect(this.audioContext.destination);
997
+ } else {
998
+ source.connect(this.audioContext.destination);
999
+ }
1000
+ if (options?.loop) {
1001
+ source.loop = true;
1002
+ }
1003
+ this.activeSources.add(source);
1004
+ source.onended = () => {
1005
+ source.disconnect();
1006
+ this.activeSources.delete(source);
1007
+ };
1008
+ source.start(0);
1009
+ return source;
1010
+ }
1011
+ stopAllSounds() {
1012
+ this.activeSources.forEach((source) => {
1013
+ try {
1014
+ source.stop();
1015
+ source.disconnect();
1016
+ } catch (e) {
1017
+ }
1018
+ });
1019
+ this.activeSources.clear();
1020
+ }
1021
+ clearCache() {
1022
+ this.audioCacheBuffers.clear();
1023
+ }
1024
+ }
1025
+ globalThis.__defussSoundManager = globalThis.__defussSoundManager || new SoundManager();
1026
+ const soundManager = globalThis.__defussSoundManager;
1027
+
1028
+ export { $, Button, DefussApp, DefussDesktopAppIcon, DequeryWithWindowManager, Desktop, DesktopManager, DesktopShellManager, LogonScreen, Shell, SoundManager, StartButton, StartMenu, Taskbar, TaskbarManager, WindowManager, defaultDesktopOptions, defaultSystemSoundFilePaths, defaultTaskbarOptions, defaultWindowOptions, desktopManager, desktopShell, soundManager, taskbarManager, windowManager };