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