defuss-desktop 0.0.4 → 0.0.6

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 CHANGED
@@ -1,25 +1,144 @@
1
- import { createRef, $ as $$1, dequery, CallChainImpl } from 'defuss';
1
+ import { $ as $$1, createRef, dequery, CallChainImpl } from 'defuss';
2
2
  import { debounce, throttle } from 'defuss-runtime';
3
- import { jsx, jsxs } from 'defuss/jsx-runtime';
3
+ import { jsxs, jsx, Fragment } from 'defuss/jsx-runtime';
4
4
  import { access, transval, rule } from 'defuss-transval';
5
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
6
+ class DesktopShellManager {
7
+ constructor(apps = []) {
8
+ this.apps = apps;
9
+ }
10
+ addApp(app) {
11
+ this.apps.push(app);
12
+ console.log(`App added: ${app.config.name}`);
13
+ }
14
+ async registerAppBundle(bundle) {
15
+ const { DefussApp } = await Promise.resolve().then(function () { return app; });
16
+ return DefussApp.fromBundle(bundle);
17
+ }
18
+ /** Find a registered app by executable name (e.g. "notepad.exe") */
19
+ findApp(executable) {
20
+ return this.apps.find((app) => app.bundle?.executable === executable);
21
+ }
22
+ /** Run an app by executable name */
23
+ runApp(executable) {
24
+ const app = this.findApp(executable);
25
+ if (!app) {
26
+ console.error(`App not found: ${executable}`);
27
+ return;
21
28
  }
22
- );
29
+ app.run();
30
+ }
31
+ /** Launch a bundled app by creating a window and passing the container */
32
+ launchApp(app) {
33
+ if (!app.bundle) return;
34
+ const event = new CustomEvent("defuss:launch-app", { detail: { app } });
35
+ document.dispatchEvent(event);
36
+ }
37
+ }
38
+ globalThis.__defussDesktopShellManager = globalThis.__defussDesktopShellManager || new DesktopShellManager();
39
+ const desktopShell = globalThis.__defussDesktopShellManager;
40
+
41
+ class SelectionModel {
42
+ constructor(options) {
43
+ this.options = options;
44
+ }
45
+ selectionDiv = null;
46
+ startX = 0;
47
+ startY = 0;
48
+ debounceTimer = null;
49
+ isSelecting = false;
50
+ debounceDelay = 1;
51
+ init() {
52
+ this.options.desktopElement.addEventListener("mousedown", this.onMouseDown);
53
+ }
54
+ destroy() {
55
+ this.options.desktopElement.removeEventListener("mousedown", this.onMouseDown);
56
+ this.removeSelectionDiv();
57
+ this.clearDebounce();
58
+ document.removeEventListener("mousemove", this.onMouseMove);
59
+ document.removeEventListener("mouseup", this.onMouseUp);
60
+ document.documentElement.removeEventListener("mouseleave", this.onMouseLeave);
61
+ }
62
+ onMouseDown = (e) => {
63
+ if (e.target.closest(".desktop-icon")) return;
64
+ this.startX = e.clientX;
65
+ this.startY = e.clientY;
66
+ this.isSelecting = true;
67
+ this.createSelectionDiv();
68
+ document.addEventListener("mousemove", this.onMouseMove);
69
+ document.addEventListener("mouseup", this.onMouseUp);
70
+ document.documentElement.addEventListener("mouseleave", this.onMouseLeave);
71
+ };
72
+ onMouseMove = (e) => {
73
+ if (!this.isSelecting) return;
74
+ this.clearDebounce();
75
+ this.debounceTimer = window.setTimeout(() => {
76
+ this.updateSelectionDiv(e.clientX, e.clientY);
77
+ this.selectIcons();
78
+ }, this.debounceDelay);
79
+ };
80
+ onMouseUp = () => {
81
+ this.endSelection();
82
+ };
83
+ onMouseLeave = () => {
84
+ this.endSelection();
85
+ };
86
+ endSelection = () => {
87
+ if (!this.isSelecting) return;
88
+ this.isSelecting = false;
89
+ this.clearDebounce();
90
+ this.selectIcons();
91
+ this.removeSelectionDiv();
92
+ document.removeEventListener("mousemove", this.onMouseMove);
93
+ document.removeEventListener("mouseup", this.onMouseUp);
94
+ document.documentElement.removeEventListener("mouseleave", this.onMouseLeave);
95
+ };
96
+ createSelectionDiv() {
97
+ this.selectionDiv = document.createElement("div");
98
+ this.selectionDiv.className = "selection-model";
99
+ this.selectionDiv.style.left = `${this.startX}px`;
100
+ this.selectionDiv.style.top = `${this.startY}px`;
101
+ this.selectionDiv.style.width = "0px";
102
+ this.selectionDiv.style.height = "0px";
103
+ document.body.appendChild(this.selectionDiv);
104
+ }
105
+ updateSelectionDiv(endX, endY) {
106
+ if (!this.selectionDiv) return;
107
+ const left = Math.min(this.startX, endX);
108
+ const top = Math.min(this.startY, endY);
109
+ const width = Math.abs(endX - this.startX);
110
+ const height = Math.abs(endY - this.startY);
111
+ this.selectionDiv.style.left = `${left}px`;
112
+ this.selectionDiv.style.top = `${top}px`;
113
+ this.selectionDiv.style.width = `${width}px`;
114
+ this.selectionDiv.style.height = `${height}px`;
115
+ }
116
+ selectIcons() {
117
+ const icons = this.options.iconsContainer.querySelectorAll(".desktop-icon");
118
+ if (!this.selectionDiv) return;
119
+ const selRect = this.selectionDiv.getBoundingClientRect();
120
+ icons.forEach((icon) => {
121
+ const iconRect = icon.getBoundingClientRect();
122
+ const intersects = !(iconRect.right < selRect.left || iconRect.left > selRect.right || iconRect.bottom < selRect.top || iconRect.top > selRect.bottom);
123
+ if (intersects) {
124
+ icon.classList.add("icon-selected");
125
+ } else {
126
+ icon.classList.remove("icon-selected");
127
+ }
128
+ });
129
+ }
130
+ removeSelectionDiv() {
131
+ if (this.selectionDiv) {
132
+ this.selectionDiv.remove();
133
+ this.selectionDiv = null;
134
+ }
135
+ }
136
+ clearDebounce() {
137
+ if (this.debounceTimer !== null) {
138
+ clearTimeout(this.debounceTimer);
139
+ this.debounceTimer = null;
140
+ }
141
+ }
23
142
  }
24
143
 
25
144
  const defaultTaskbarOptions = {
@@ -160,6 +279,18 @@ const defaultWindowOptions = {
160
279
  };
161
280
  class WindowManager {
162
281
  windows = [];
282
+ listeners = /* @__PURE__ */ new Set();
283
+ subscribe(listener) {
284
+ this.listeners.add(listener);
285
+ return () => {
286
+ this.listeners.delete(listener);
287
+ };
288
+ }
289
+ emitChanged() {
290
+ for (const listener of this.listeners) {
291
+ listener();
292
+ }
293
+ }
163
294
  constructor() {
164
295
  $$1(() => {
165
296
  if (desktopManager?.onResize) {
@@ -206,6 +337,7 @@ class WindowManager {
206
337
  });
207
338
  this.windows.push(window);
208
339
  this.renderWindowsActivationState();
340
+ this.emitChanged();
209
341
  }
210
342
  }
211
343
  renderWindowsActivationState() {
@@ -255,6 +387,7 @@ class WindowManager {
255
387
  state.y = activeWindow.y + 20;
256
388
  }
257
389
  this.windows.push(state);
390
+ this.emitChanged();
258
391
  return state;
259
392
  }
260
393
  updateWindow(id, options) {
@@ -278,6 +411,7 @@ class WindowManager {
278
411
  ...updatedWindow
279
412
  } : win2
280
413
  );
414
+ this.emitChanged();
281
415
  return updatedWindow;
282
416
  }
283
417
  closeWindow(id) {
@@ -286,6 +420,7 @@ class WindowManager {
286
420
  $$1(win.el).remove();
287
421
  this.windows = this.windows.filter((win2) => win2.id !== id);
288
422
  this.renderWindowsActivationState();
423
+ this.emitChanged();
289
424
  win.ref.state?.onClose?.();
290
425
  }
291
426
  maximizeWindow(id) {
@@ -327,6 +462,7 @@ class WindowManager {
327
462
  top: isMaximized ? "-3px" : `${win.y}px`
328
463
  });
329
464
  this.toggleTitleBarMaximizedButtonState(id);
465
+ this.emitChanged();
330
466
  if (isMaximized) {
331
467
  win.ref.state?.onMaximize?.();
332
468
  }
@@ -334,31 +470,33 @@ class WindowManager {
334
470
  minimizeWindow(id) {
335
471
  let win = this.getWindow(id);
336
472
  if (!win) return;
337
- win = this.updateWindow(id, { prevX: win.x, prevY: win.y });
338
- const isMinimized = !win.minimized;
473
+ if (win.minimized) {
474
+ this.restoreWindow(id);
475
+ return;
476
+ }
477
+ const actualWidth = win.el.offsetWidth || win.width;
478
+ const actualHeight = win.el.offsetHeight || win.height;
339
479
  win = this.updateWindow(id, {
340
- minimized: isMinimized,
341
- width: isMinimized ? 0 : win.width,
342
- height: isMinimized ? 0 : win.height,
343
- x: isMinimized ? -1e4 : win.x,
344
- // Move off-screen when minimized
345
- y: isMinimized ? -1e4 : win.y
480
+ prevX: win.x,
481
+ prevY: win.y,
482
+ prevWidth: actualWidth,
483
+ prevHeight: actualHeight,
484
+ minimized: true
346
485
  });
347
486
  const $win = $$1(win.el);
348
487
  $win.css({
349
- width: isMinimized ? "0px" : `${win.width}px`,
350
- height: isMinimized ? "0px" : `${win.height}px`,
351
- left: isMinimized ? "-10000px" : `${win.x}px`,
352
- top: isMinimized ? "-10000px" : `${win.y}px`
488
+ left: "-10000px",
489
+ top: "-10000px"
353
490
  });
354
- if (isMinimized) {
355
- win.ref.state?.onMinimize?.();
356
- }
491
+ this.renderWindowsActivationState();
492
+ this.emitChanged();
493
+ win.ref.state?.onMinimize?.();
357
494
  }
358
495
  restoreWindow(id) {
359
496
  let win = this.getWindow(id);
360
497
  if (!win) return;
361
498
  win = this.updateWindow(id, {
499
+ minimized: false,
362
500
  maximized: false,
363
501
  width: win.prevWidth || 800,
364
502
  // Use stored previous width
@@ -375,6 +513,8 @@ class WindowManager {
375
513
  top: `${win.y}px`
376
514
  });
377
515
  this.toggleTitleBarMaximizedButtonState(id);
516
+ this.setActiveWindow(id);
517
+ this.emitChanged();
378
518
  }
379
519
  toggleTitleBarMaximizedButtonState(id) {
380
520
  const win = this.getWindow(id);
@@ -407,12 +547,20 @@ function Window({
407
547
  onMaximize = () => {
408
548
  }
409
549
  }) {
550
+ const MIN_WINDOW_WIDTH = 240;
551
+ const MIN_WINDOW_HEIGHT = 160;
410
552
  let isDragging = false;
411
553
  let dragPointerId = null;
412
554
  let dragStartMouse = { x: 0, y: 0 };
413
555
  let dragStartWin = { x: 0, y: 0 };
414
556
  let lastWin = { x, y };
415
557
  let capturedTitleBar = null;
558
+ let isResizing = false;
559
+ let resizePointerId = null;
560
+ let resizeDirection = null;
561
+ let capturedResizeHandle = null;
562
+ let resizeStartMouse = { x: 0, y: 0 };
563
+ let resizeStartWin = { x, y, width, height };
416
564
  const initialWindowState = windowManager.addWindow({
417
565
  id,
418
566
  title,
@@ -476,7 +624,33 @@ function Window({
476
624
  capturedTitleBar = null;
477
625
  windowManager.updateWindow(initialWindowState.id, { x: lastWin.x, y: lastWin.y });
478
626
  };
627
+ const stopResizing = (event) => {
628
+ if (!isResizing) return;
629
+ if (event && capturedResizeHandle && typeof capturedResizeHandle.releasePointerCapture === "function" && resizePointerId === event.pointerId) {
630
+ try {
631
+ capturedResizeHandle.releasePointerCapture(event.pointerId);
632
+ } catch {
633
+ }
634
+ }
635
+ isResizing = false;
636
+ resizePointerId = null;
637
+ resizeDirection = null;
638
+ capturedResizeHandle = null;
639
+ const el = ref.current;
640
+ if (!el) return;
641
+ const left = Number.parseFloat(el.style.left);
642
+ const top = Number.parseFloat(el.style.top);
643
+ const width2 = Number.parseFloat(el.style.width);
644
+ const height2 = Number.parseFloat(el.style.height);
645
+ windowManager.updateWindow(initialWindowState.id, {
646
+ x: Number.isFinite(left) ? left : lastWin.x,
647
+ y: Number.isFinite(top) ? top : lastWin.y,
648
+ width: Number.isFinite(width2) ? width2 : el.offsetWidth,
649
+ height: Number.isFinite(height2) ? height2 : el.offsetHeight
650
+ });
651
+ };
479
652
  const onTitlePointerDown = (event) => {
653
+ if (isResizing) return;
480
654
  if (event.pointerType === "mouse" && event.button !== 0) return;
481
655
  const target = event.target;
482
656
  if (target?.tagName === "BUTTON") return;
@@ -493,6 +667,85 @@ function Window({
493
667
  }
494
668
  event.preventDefault();
495
669
  };
670
+ const onResizePointerDown = (event) => {
671
+ if (!resizable) return;
672
+ if (isDragging) return;
673
+ if (event.pointerType === "mouse" && event.button !== 0) return;
674
+ const currentState = windowManager.getWindow(initialWindowState.id);
675
+ if (currentState?.maximized) return;
676
+ const target = event.target;
677
+ const handle = target?.closest?.(".window-resize-handle");
678
+ const direction = handle?.getAttribute("data-dir");
679
+ if (!handle || !direction) return;
680
+ const currentPos = getCurrentWinPos();
681
+ const el = ref.current;
682
+ if (!el) return;
683
+ isResizing = true;
684
+ resizePointerId = event.pointerId;
685
+ resizeDirection = direction;
686
+ capturedResizeHandle = handle;
687
+ resizeStartMouse = { x: event.clientX, y: event.clientY };
688
+ resizeStartWin = {
689
+ x: currentPos.x,
690
+ y: currentPos.y,
691
+ width: el.offsetWidth,
692
+ height: el.offsetHeight
693
+ };
694
+ if (typeof handle.setPointerCapture === "function") {
695
+ handle.setPointerCapture(event.pointerId);
696
+ }
697
+ windowManager.setActiveWindow(initialWindowState.id);
698
+ event.preventDefault();
699
+ event.stopPropagation();
700
+ };
701
+ const onResizePointerMove = (event) => {
702
+ if (!isResizing) return;
703
+ if (resizePointerId !== null && event.pointerId !== resizePointerId) return;
704
+ if (!resizeDirection) return;
705
+ const el = ref.current;
706
+ if (!el) return;
707
+ const deltaX = event.clientX - resizeStartMouse.x;
708
+ const deltaY = event.clientY - resizeStartMouse.y;
709
+ let newX = resizeStartWin.x;
710
+ let newY = resizeStartWin.y;
711
+ let newWidth = resizeStartWin.width;
712
+ let newHeight = resizeStartWin.height;
713
+ if (resizeDirection.includes("e")) {
714
+ newWidth = Math.max(MIN_WINDOW_WIDTH, resizeStartWin.width + deltaX);
715
+ }
716
+ if (resizeDirection.includes("s")) {
717
+ newHeight = Math.max(MIN_WINDOW_HEIGHT, resizeStartWin.height + deltaY);
718
+ }
719
+ if (resizeDirection.includes("w")) {
720
+ const tentativeWidth = resizeStartWin.width - deltaX;
721
+ newWidth = Math.max(MIN_WINDOW_WIDTH, tentativeWidth);
722
+ newX = resizeStartWin.x + (resizeStartWin.width - newWidth);
723
+ }
724
+ if (resizeDirection.includes("n")) {
725
+ const tentativeHeight = resizeStartWin.height - deltaY;
726
+ newHeight = Math.max(MIN_WINDOW_HEIGHT, tentativeHeight);
727
+ newY = resizeStartWin.y + (resizeStartWin.height - newHeight);
728
+ }
729
+ lastWin = { x: newX, y: newY };
730
+ el.style.left = `${newX}px`;
731
+ el.style.top = `${newY}px`;
732
+ el.style.width = `${newWidth}px`;
733
+ el.style.height = `${newHeight}px`;
734
+ updateWindowState({
735
+ x: newX,
736
+ y: newY,
737
+ width: newWidth,
738
+ height: newHeight
739
+ });
740
+ event.preventDefault();
741
+ event.stopPropagation();
742
+ };
743
+ const onResizePointerUp = (event) => {
744
+ stopResizing(event);
745
+ };
746
+ const onResizePointerCancel = (event) => {
747
+ stopResizing(event);
748
+ };
496
749
  const onTitlePointerMove = (event) => {
497
750
  if (!isDragging) return;
498
751
  if (dragPointerId !== null && event.pointerId !== dragPointerId) return;
@@ -521,7 +774,10 @@ function Window({
521
774
  windowManager.setActiveWindow(initialWindowState.id);
522
775
  $$1(document).on("blur", () => stopDragging());
523
776
  $$1(document).on("visibilitychange", () => {
524
- if (document.hidden) stopDragging();
777
+ if (document.hidden) {
778
+ stopDragging();
779
+ stopResizing();
780
+ }
525
781
  });
526
782
  };
527
783
  const onCloseClick = () => windowManager.closeWindow(initialWindowState.id);
@@ -538,6 +794,7 @@ function Window({
538
794
  {
539
795
  class: "window crt",
540
796
  ref,
797
+ "data-resizable": String(resizable),
541
798
  onMouseDown: onWindowMouseDown,
542
799
  style: {
543
800
  width,
@@ -568,68 +825,230 @@ function Window({
568
825
  ]
569
826
  }
570
827
  ),
571
- /* @__PURE__ */ jsx("div", { class: "window-body", children })
828
+ /* @__PURE__ */ jsx("div", { class: "window-body", children }),
829
+ resizable && /* @__PURE__ */ jsxs(Fragment, { children: [
830
+ /* @__PURE__ */ jsx(
831
+ "div",
832
+ {
833
+ class: "window-resize-handle window-resize-handle--n",
834
+ "data-dir": "n",
835
+ onPointerDown: onResizePointerDown,
836
+ onPointerMove: onResizePointerMove,
837
+ onPointerUp: onResizePointerUp,
838
+ onPointerCancel: onResizePointerCancel
839
+ }
840
+ ),
841
+ /* @__PURE__ */ jsx(
842
+ "div",
843
+ {
844
+ class: "window-resize-handle window-resize-handle--e",
845
+ "data-dir": "e",
846
+ onPointerDown: onResizePointerDown,
847
+ onPointerMove: onResizePointerMove,
848
+ onPointerUp: onResizePointerUp,
849
+ onPointerCancel: onResizePointerCancel
850
+ }
851
+ ),
852
+ /* @__PURE__ */ jsx(
853
+ "div",
854
+ {
855
+ class: "window-resize-handle window-resize-handle--s",
856
+ "data-dir": "s",
857
+ onPointerDown: onResizePointerDown,
858
+ onPointerMove: onResizePointerMove,
859
+ onPointerUp: onResizePointerUp,
860
+ onPointerCancel: onResizePointerCancel
861
+ }
862
+ ),
863
+ /* @__PURE__ */ jsx(
864
+ "div",
865
+ {
866
+ class: "window-resize-handle window-resize-handle--w",
867
+ "data-dir": "w",
868
+ onPointerDown: onResizePointerDown,
869
+ onPointerMove: onResizePointerMove,
870
+ onPointerUp: onResizePointerUp,
871
+ onPointerCancel: onResizePointerCancel
872
+ }
873
+ ),
874
+ /* @__PURE__ */ jsx(
875
+ "div",
876
+ {
877
+ class: "window-resize-handle window-resize-handle--ne",
878
+ "data-dir": "ne",
879
+ onPointerDown: onResizePointerDown,
880
+ onPointerMove: onResizePointerMove,
881
+ onPointerUp: onResizePointerUp,
882
+ onPointerCancel: onResizePointerCancel
883
+ }
884
+ ),
885
+ /* @__PURE__ */ jsx(
886
+ "div",
887
+ {
888
+ class: "window-resize-handle window-resize-handle--se",
889
+ "data-dir": "se",
890
+ onPointerDown: onResizePointerDown,
891
+ onPointerMove: onResizePointerMove,
892
+ onPointerUp: onResizePointerUp,
893
+ onPointerCancel: onResizePointerCancel
894
+ }
895
+ ),
896
+ /* @__PURE__ */ jsx(
897
+ "div",
898
+ {
899
+ class: "window-resize-handle window-resize-handle--sw",
900
+ "data-dir": "sw",
901
+ onPointerDown: onResizePointerDown,
902
+ onPointerMove: onResizePointerMove,
903
+ onPointerUp: onResizePointerUp,
904
+ onPointerCancel: onResizePointerCancel
905
+ }
906
+ ),
907
+ /* @__PURE__ */ jsx(
908
+ "div",
909
+ {
910
+ class: "window-resize-handle window-resize-handle--nw",
911
+ "data-dir": "nw",
912
+ onPointerDown: onResizePointerDown,
913
+ onPointerMove: onResizePointerMove,
914
+ onPointerUp: onResizePointerUp,
915
+ onPointerCancel: onResizePointerCancel
916
+ }
917
+ )
918
+ ] })
572
919
  ]
573
920
  }
574
921
  );
575
922
  }
576
923
 
924
+ function DesktopIcon({ app }) {
925
+ const bundle = app.bundle;
926
+ const iconSrc = bundle?.icon ?? app.config.icon;
927
+ const label = bundle?.displayName ?? app.config.name;
928
+ const onClick = (e) => {
929
+ if (e.detail > 1) return;
930
+ const allIcons = document.querySelectorAll(".desktop-icon");
931
+ allIcons.forEach((icon) => icon.classList.remove("icon-selected"));
932
+ const target = e.target.closest(".desktop-icon");
933
+ if (target) {
934
+ target.classList.add("icon-selected");
935
+ }
936
+ };
937
+ const onDblClick = () => {
938
+ app.run();
939
+ };
940
+ return /* @__PURE__ */ jsxs("div", { class: "desktop-icon", onClick, onDblClick, children: [
941
+ /* @__PURE__ */ jsx("div", { class: "desktop-icon__image", style: { "--icon-src": `url(${iconSrc})` }, children: /* @__PURE__ */ jsx("img", { src: iconSrc, alt: label, draggable: false }) }),
942
+ /* @__PURE__ */ jsx("span", { class: "desktop-icon__label", children: label })
943
+ ] }, bundle?.executable ?? label);
944
+ }
945
+
577
946
  function Desktop({ ref }) {
578
- $$1(() => {
579
- console.log("Desktop mounted");
580
- });
581
- const onOpenWindow = async () => {
947
+ const appIconsRef = createRef();
948
+ const onLaunchApp = (event) => {
949
+ const customEvent = event;
950
+ const app = customEvent.detail?.app;
951
+ if (!app?.bundle) return;
952
+ void runBundledApp(app);
953
+ };
954
+ const resolveAppMain = async (app) => {
955
+ const bundle = app.bundle;
956
+ if (!bundle) return void 0;
957
+ if (bundle.main) {
958
+ return bundle.main;
959
+ }
960
+ if (bundle.load) {
961
+ const loaded = await bundle.load();
962
+ return loaded.main;
963
+ }
964
+ return void 0;
965
+ };
966
+ const runBundledApp = async (app) => {
967
+ const bundle = app.bundle;
968
+ if (!bundle) return;
969
+ const main = await resolveAppMain(app);
970
+ if (!main) {
971
+ console.error(`No app main() available for ${bundle.executable}`);
972
+ return;
973
+ }
974
+ const appRootRef = createRef();
582
975
  const winRef = createRef();
583
976
  await $$1(ref).append(
584
- /* @__PURE__ */ jsxs(
977
+ /* @__PURE__ */ jsx(
585
978
  Window,
586
979
  {
587
- width: 300,
588
- height: 200,
589
- title: "Test Window",
980
+ width: bundle.width ?? 720,
981
+ height: bundle.height ?? 480,
982
+ title: bundle.displayName,
983
+ id: bundle.executable,
590
984
  ref: winRef,
591
985
  onClose: () => {
592
- console.log("I WAS CLOSED!");
986
+ console.log(`${bundle.executable} was closed`);
593
987
  },
594
- onMaximize: () => {
595
- console.log("I WAS MAXIMIZED!");
596
- },
597
- onMinimize: () => {
598
- console.log("I WAS MINIMIZED!");
599
- },
600
- children: [
601
- /* @__PURE__ */ jsx("p", { children: "Hello, world!" }),
602
- /* @__PURE__ */ jsxs("section", { class: "field-row", style: "justify-content: space-between;", children: [
603
- /* @__PURE__ */ jsx(
604
- "button",
605
- {
606
- type: "button",
607
- onClick: () => {
608
- console.log("Cancel clicked");
609
- winRef.state?.close();
610
- },
611
- children: "Cancel"
612
- }
613
- ),
614
- /* @__PURE__ */ jsx(Button, { onClick: onOpenWindow, children: "Open Window" }),
615
- /* @__PURE__ */ jsx(
616
- "button",
617
- {
618
- type: "button",
619
- onClick: () => {
620
- console.log("OK clicked");
621
- winRef.state?.close();
622
- },
623
- children: "OK"
624
- }
625
- )
626
- ] })
627
- ]
988
+ children: /* @__PURE__ */ jsx(
989
+ "div",
990
+ {
991
+ class: "defuss-app-root",
992
+ ref: appRootRef,
993
+ onMount: () => {
994
+ void main({ app, container: appRootRef.current });
995
+ }
996
+ }
997
+ )
628
998
  }
629
999
  )
630
1000
  );
631
1001
  };
632
- return /* @__PURE__ */ jsx("div", { class: "defuss-desktop-panel crt", ref, children: /* @__PURE__ */ jsx(Button, { onClick: onOpenWindow, children: "Open Window" }) });
1002
+ const renderDesktopIcons = async () => {
1003
+ const appIcons = desktopShell.apps.filter((app) => app.bundle);
1004
+ await $$1(appIconsRef).update(
1005
+ /* @__PURE__ */ jsx(Fragment, { children: appIcons.map((app) => /* @__PURE__ */ jsx(DesktopIcon, { app }, app.bundle.executable)) })
1006
+ );
1007
+ };
1008
+ let selectionModel = null;
1009
+ const onMountDesktop = () => {
1010
+ void renderDesktopIcons();
1011
+ document.addEventListener("defuss:launch-app", onLaunchApp);
1012
+ selectionModel = new SelectionModel({
1013
+ desktopElement: ref.current,
1014
+ iconsContainer: appIconsRef.current
1015
+ });
1016
+ selectionModel.init();
1017
+ };
1018
+ const onUnmountDesktop = () => {
1019
+ document.removeEventListener("defuss:launch-app", onLaunchApp);
1020
+ selectionModel?.destroy();
1021
+ selectionModel = null;
1022
+ };
1023
+ return /* @__PURE__ */ jsx(
1024
+ "div",
1025
+ {
1026
+ class: "defuss-desktop-panel crt",
1027
+ ref,
1028
+ onMount: onMountDesktop,
1029
+ onUnmount: onUnmountDesktop,
1030
+ children: /* @__PURE__ */ jsx("div", { class: "desktop-icons-grid", ref: appIconsRef })
1031
+ }
1032
+ );
1033
+ }
1034
+
1035
+ function Button({
1036
+ onClick = () => {
1037
+ },
1038
+ disabled = false,
1039
+ children,
1040
+ ref = createRef()
1041
+ }) {
1042
+ return /* @__PURE__ */ jsx(
1043
+ "button",
1044
+ {
1045
+ ref,
1046
+ type: "button",
1047
+ onClick: disabled ? void 0 : onClick,
1048
+ disabled,
1049
+ children
1050
+ }
1051
+ );
633
1052
  }
634
1053
 
635
1054
  const LogonScreen = ({
@@ -750,58 +1169,66 @@ const LogonScreen = ({
750
1169
  };
751
1170
 
752
1171
  const StartMenu = () => {
1172
+ const bundledApps = desktopShell.apps.filter((app) => app.bundle);
753
1173
  return /* @__PURE__ */ jsxs("div", { class: "slide-open crt", children: [
754
1174
  /* @__PURE__ */ jsx("div", { class: "top", children: /* @__PURE__ */ jsx("h1", { children: "Aron Homberg" }) }),
755
1175
  /* @__PURE__ */ jsxs("div", { class: "menu", children: [
756
- /* @__PURE__ */ jsx("div", { class: "programs", children: /* @__PURE__ */ jsxs("ul", { children: [
757
- /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
758
- /* @__PURE__ */ jsx("a", { class: "program-image", href: "#", children: /* @__PURE__ */ jsx("img", { src: "/icons/ie.png", alt: "" }) }),
759
- /* @__PURE__ */ jsx("a", { href: "#", children: "Internet Explorer" })
760
- ] }),
761
- /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
762
- /* @__PURE__ */ jsx("a", { class: "program-image", href: "#", children: /* @__PURE__ */ jsx("img", { src: "/icons/paint.png", alt: "" }) }),
763
- /* @__PURE__ */ jsx("a", { href: "#", children: "Paint" })
764
- ] }),
765
- /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
766
- /* @__PURE__ */ jsx("a", { class: "program-image", href: "#", children: /* @__PURE__ */ jsx("img", { src: "/icons/media-player.png", alt: "" }) }),
767
- /* @__PURE__ */ jsx("a", { href: "#", children: "Windows Media Player" })
768
- ] }),
769
- /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
770
- /* @__PURE__ */ jsx("a", { class: "program-image", href: "#", children: /* @__PURE__ */ jsx("img", { src: "/icons/outlook-express.jpg", alt: "" }) }),
771
- /* @__PURE__ */ jsx("a", { href: "#", children: "E-mail" })
772
- ] })
773
- ] }) }),
1176
+ /* @__PURE__ */ jsx("div", { class: "programs", children: /* @__PURE__ */ jsx("ul", { children: bundledApps.map((app) => /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
1177
+ /* @__PURE__ */ jsx(
1178
+ "a",
1179
+ {
1180
+ class: "program-image",
1181
+ href: "#",
1182
+ onClick: (event) => {
1183
+ event.preventDefault();
1184
+ desktopShell.runApp(app.bundle.executable);
1185
+ },
1186
+ children: /* @__PURE__ */ jsx("img", { src: app.bundle.icon, alt: "" })
1187
+ }
1188
+ ),
1189
+ /* @__PURE__ */ jsx(
1190
+ "a",
1191
+ {
1192
+ href: "#",
1193
+ onClick: (event) => {
1194
+ event.preventDefault();
1195
+ desktopShell.runApp(app.bundle.executable);
1196
+ },
1197
+ children: app.bundle.displayName
1198
+ }
1199
+ )
1200
+ ] }, app.bundle.executable)) }) }),
774
1201
  /* @__PURE__ */ jsx("div", { class: "system", children: /* @__PURE__ */ jsxs("ul", { children: [
775
1202
  /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
776
- /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/documents.png", alt: "" }) }),
1203
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/desktop/documents.png", alt: "" }) }),
777
1204
  /* @__PURE__ */ jsx("a", { href: "#", children: "My Documents" })
778
1205
  ] }),
779
1206
  /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
780
- /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/pictures.png", alt: "" }) }),
1207
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/desktop/pictures.png", alt: "" }) }),
781
1208
  /* @__PURE__ */ jsx("a", { href: "#", children: "My Pictures" })
782
1209
  ] }),
783
1210
  /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
784
- /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/music.png", alt: "" }) }),
1211
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/desktop/music.png", alt: "" }) }),
785
1212
  /* @__PURE__ */ jsx("a", { href: "#", children: "My Music" })
786
1213
  ] }),
787
1214
  /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
788
- /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/computer.png", alt: "" }) }),
1215
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/desktop/computer.png", alt: "" }) }),
789
1216
  /* @__PURE__ */ jsx("a", { href: "#", children: "My Computer" })
790
1217
  ] }),
791
1218
  /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
792
- /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/control-panel.png", alt: "" }) }),
1219
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/desktop/control-panel.png", alt: "" }) }),
793
1220
  /* @__PURE__ */ jsx("a", { href: "#", children: "Control Panel" })
794
1221
  ] }),
795
1222
  /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
796
- /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/support-help.png", alt: "" }) }),
1223
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/desktop/support-help.png", alt: "" }) }),
797
1224
  /* @__PURE__ */ jsx("a", { href: "#", children: "Help and Support" })
798
1225
  ] }),
799
1226
  /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
800
- /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/search.png", alt: "" }) }),
1227
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/desktop/search.png", alt: "" }) }),
801
1228
  /* @__PURE__ */ jsx("a", { href: "#", children: "Search" })
802
1229
  ] }),
803
1230
  /* @__PURE__ */ jsxs("li", { tabindex: "-1", children: [
804
- /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/icons/run.png", alt: "" }) }),
1231
+ /* @__PURE__ */ jsx("a", { href: "#", class: "program-image", children: /* @__PURE__ */ jsx("img", { src: "/desktop/run.png", alt: "" }) }),
805
1232
  /* @__PURE__ */ jsx("a", { href: "#", children: "Run..." })
806
1233
  ] })
807
1234
  ] }) })
@@ -826,11 +1253,63 @@ const StartButton = () => {
826
1253
  };
827
1254
 
828
1255
  const Taskbar = () => {
829
- return /* @__PURE__ */ jsxs("div", { class: "bar crt", children: [
1256
+ const taskbarListRef = createRef();
1257
+ const clockRef = createRef();
1258
+ const renderTasks = async () => {
1259
+ const windows = [...windowManager.windows];
1260
+ const activeWindow = windowManager.getActiveWindow();
1261
+ await $$1(taskbarListRef).update(
1262
+ /* @__PURE__ */ jsx(Fragment, { children: windows.map((win) => /* @__PURE__ */ jsx("li", { class: activeWindow?.id === win.id && !win.minimized ? "active" : "", children: /* @__PURE__ */ jsx(
1263
+ "button",
1264
+ {
1265
+ type: "button",
1266
+ class: "taskbar__task-btn",
1267
+ onClick: () => {
1268
+ const current = windowManager.getWindow(win.id);
1269
+ if (!current) return;
1270
+ if (current.minimized) {
1271
+ windowManager.restoreWindow(current.id);
1272
+ return;
1273
+ }
1274
+ const active = windowManager.getActiveWindow();
1275
+ if (active?.id === current.id) {
1276
+ windowManager.minimizeWindow(current.id);
1277
+ return;
1278
+ }
1279
+ windowManager.setActiveWindow(current.id);
1280
+ },
1281
+ children: /* @__PURE__ */ jsxs("div", { class: "cell", children: [
1282
+ win.icon ? /* @__PURE__ */ jsx("img", { src: win.icon, alt: "" }) : null,
1283
+ /* @__PURE__ */ jsx("span", { class: "cell-name", children: win.title })
1284
+ ] })
1285
+ }
1286
+ ) }, win.id)) })
1287
+ );
1288
+ };
1289
+ const renderClock = () => {
1290
+ const now = /* @__PURE__ */ new Date();
1291
+ const value = now.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
1292
+ $$1(clockRef).text(value);
1293
+ };
1294
+ const onMount = () => {
1295
+ void renderTasks();
1296
+ renderClock();
1297
+ const unsubscribe = windowManager.subscribe(() => {
1298
+ void renderTasks();
1299
+ });
1300
+ const interval = setInterval(renderClock, 3e4);
1301
+ clockRef.state = { unsubscribe, interval };
1302
+ };
1303
+ const onUnmount = () => {
1304
+ const state = clockRef.state;
1305
+ state?.unsubscribe?.();
1306
+ if (state?.interval) clearInterval(state.interval);
1307
+ };
1308
+ return /* @__PURE__ */ jsxs("div", { class: "bar crt", onMount, onUnmount, children: [
830
1309
  /* @__PURE__ */ jsx(StartButton, {}),
831
- /* @__PURE__ */ jsx("ul", { class: "taskbar" }),
1310
+ /* @__PURE__ */ jsx("ul", { class: "taskbar", ref: taskbarListRef }),
832
1311
  /* @__PURE__ */ jsx("div", { class: "tray-toggle", children: /* @__PURE__ */ jsx("div", { class: "arrow" }) }),
833
- /* @__PURE__ */ jsx("div", { class: "taskbar__clock", children: /* @__PURE__ */ jsx("span", { children: "7:02 AM" }) })
1312
+ /* @__PURE__ */ jsx("div", { class: "taskbar__clock", children: /* @__PURE__ */ jsx("span", { ref: clockRef, children: "--:--" }) })
834
1313
  ] });
835
1314
  };
836
1315
 
@@ -852,29 +1331,37 @@ function Shell({
852
1331
  ] });
853
1332
  }
854
1333
 
855
- class DesktopShellManager {
856
- constructor(apps = []) {
857
- this.apps = apps;
858
- }
859
- addApp(app) {
860
- this.apps.push(app);
861
- console.log(`App added: ${app.config.name}`);
862
- }
863
- }
864
- globalThis.__defussDesktopShellManager = globalThis.__defussDesktopShellManager || new DesktopShellManager();
865
- const desktopShell = globalThis.__defussDesktopShellManager;
866
-
867
1334
  class DefussApp {
868
1335
  constructor(config) {
869
1336
  this.config = config;
870
1337
  desktopShell.addApp(this);
871
1338
  }
1339
+ bundle;
1340
+ static fromBundle(bundle) {
1341
+ const app = new DefussApp({
1342
+ name: bundle.displayName,
1343
+ icon: bundle.icon,
1344
+ main: () => {
1345
+ }
1346
+ });
1347
+ app.bundle = bundle;
1348
+ return app;
1349
+ }
872
1350
  run() {
873
1351
  console.log(`Running app: ${this.config.name}`);
874
- this.config.main(this, ...this.config.argv || []);
1352
+ if (this.bundle) {
1353
+ desktopShell.launchApp(this);
1354
+ } else {
1355
+ this.config.main(this, ...this.config.argv || []);
1356
+ }
875
1357
  }
876
1358
  }
877
1359
 
1360
+ var app = /*#__PURE__*/Object.freeze({
1361
+ __proto__: null,
1362
+ DefussApp: DefussApp
1363
+ });
1364
+
878
1365
  class DequeryWithWindowManager extends CallChainImpl {
879
1366
  /*
880
1367
  // create a window from any element (not necessarily identifier as an App)
@@ -1028,4 +1515,22 @@ class SoundManager {
1028
1515
  globalThis.__defussSoundManager = globalThis.__defussSoundManager || new SoundManager();
1029
1516
  const soundManager = globalThis.__defussSoundManager;
1030
1517
 
1031
- 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 };
1518
+ const notepadAppBundle = {
1519
+ executable: "notepad.exe",
1520
+ displayName: "Notepad",
1521
+ icon: "/desktop/documents.png",
1522
+ width: 760,
1523
+ height: 520,
1524
+ load: () => import('./notepad-Cwg2fv8c.mjs')
1525
+ };
1526
+
1527
+ const internetExplorerAppBundle = {
1528
+ executable: "explorer.exe",
1529
+ displayName: "Internet Explorer",
1530
+ icon: "/desktop/internet-explorer.png",
1531
+ width: 980,
1532
+ height: 680,
1533
+ load: () => import('./internet-explorer-CAgm9-7A.mjs')
1534
+ };
1535
+
1536
+ export { $, Button, DefussApp, DefussDesktopAppIcon, DequeryWithWindowManager, Desktop, DesktopIcon, DesktopManager, DesktopShellManager, LogonScreen, SelectionModel, Shell, SoundManager, StartButton, StartMenu, Taskbar, TaskbarManager, WindowManager, defaultDesktopOptions, defaultSystemSoundFilePaths, defaultTaskbarOptions, defaultWindowOptions, desktopManager, desktopShell, internetExplorerAppBundle, notepadAppBundle, soundManager, taskbarManager, windowManager };