defuss-desktop 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/index.cjs +204 -201
- package/dist/index.d.cts +15 -15
- package/dist/index.d.mts +15 -15
- package/dist/index.mjs +205 -202
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -92,10 +92,10 @@ Integrating `defuss-wm` in your project
|
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
94
|
# install a decent package manager
|
|
95
|
-
npm i -g
|
|
95
|
+
npm i -g bun@^1.3.9
|
|
96
96
|
|
|
97
97
|
# from your project root folder, add defuss-wm to your dependencies
|
|
98
|
-
|
|
98
|
+
bun install defuss-wm
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
#### 2. Set up your desktop environment:
|
|
@@ -198,8 +198,8 @@ All commands are run from the root of the project, from a terminal:
|
|
|
198
198
|
|
|
199
199
|
| Command | Action |
|
|
200
200
|
| :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
201
|
-
| `
|
|
202
|
-
| `
|
|
201
|
+
| `bun build` | Build a new version of the window manager. |
|
|
202
|
+
| `bun test` | Run the test suite for `defuss-wm`. |
|
|
203
203
|
|
|
204
204
|
---
|
|
205
205
|
|
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,124 @@ function Button({
|
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
const defaultTaskbarOptions = {
|
|
28
|
+
position: "bottom",
|
|
29
|
+
stateful: false,
|
|
30
|
+
theme: "default",
|
|
31
|
+
size: "medium"
|
|
32
|
+
};
|
|
33
|
+
class TaskbarManager {
|
|
34
|
+
position;
|
|
35
|
+
theme;
|
|
36
|
+
// e.g., 'windows-xp', 'macos', etc.
|
|
37
|
+
size;
|
|
38
|
+
constructor(options = defaultTaskbarOptions) {
|
|
39
|
+
this.position = options.position || "bottom";
|
|
40
|
+
this.theme = options.theme || "default";
|
|
41
|
+
this.size = options.size || "medium";
|
|
42
|
+
if (options.stateful) ;
|
|
43
|
+
}
|
|
44
|
+
getDimensions() {
|
|
45
|
+
const el = document.querySelector(".taskbar");
|
|
46
|
+
if (!el) {
|
|
47
|
+
return { width: 0, height: 0 };
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
width: el.clientWidth,
|
|
51
|
+
height: el.clientHeight
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
globalThis.__defussTaskbarManager = globalThis.__defussTaskbarManager || new TaskbarManager();
|
|
56
|
+
const taskbarManager = globalThis.__defussTaskbarManager;
|
|
57
|
+
|
|
58
|
+
class DefussDesktopAppIcon {
|
|
59
|
+
constructor(config) {
|
|
60
|
+
this.config = config;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const defaultDesktopOptions = {
|
|
64
|
+
icons: [],
|
|
65
|
+
backgroundColor: "#000"
|
|
66
|
+
};
|
|
67
|
+
class DesktopManager {
|
|
68
|
+
constructor(options = defaultDesktopOptions) {
|
|
69
|
+
this.options = options;
|
|
70
|
+
}
|
|
71
|
+
el;
|
|
72
|
+
state;
|
|
73
|
+
resizeObserver;
|
|
74
|
+
resizeCallbacks = /* @__PURE__ */ new Set();
|
|
75
|
+
init(el, options = this.options) {
|
|
76
|
+
this.options = options;
|
|
77
|
+
this.el = el;
|
|
78
|
+
this.state = this.state || {
|
|
79
|
+
icons: this.options.icons.map((icon) => icon.config)
|
|
80
|
+
};
|
|
81
|
+
this.setupResizeObserver();
|
|
82
|
+
this.render(el);
|
|
83
|
+
}
|
|
84
|
+
render(el) {
|
|
85
|
+
el.style.backgroundColor = this.options.backgroundColor;
|
|
86
|
+
if (this.options.backgroundImage) {
|
|
87
|
+
el.style.backgroundImage = `url(${this.options.backgroundImage})`;
|
|
88
|
+
}
|
|
89
|
+
if (this.options.backgroundImageSize) {
|
|
90
|
+
el.style.backgroundSize = this.options.backgroundImageSize || "cover";
|
|
91
|
+
}
|
|
92
|
+
if (this.options.backgroundRepeat) {
|
|
93
|
+
el.style.backgroundRepeat = this.options.backgroundRepeat || "no-repeat";
|
|
94
|
+
}
|
|
95
|
+
if (this.options.backgroundPosition) {
|
|
96
|
+
el.style.backgroundPosition = this.options.backgroundPosition || "center";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
addIcon(icon) {
|
|
100
|
+
this.options.icons.push(icon);
|
|
101
|
+
console.log(`Icon added: ${icon.config.name}`);
|
|
102
|
+
}
|
|
103
|
+
getDimensions() {
|
|
104
|
+
if (!this.el) {
|
|
105
|
+
throw new Error("Desktop not initialized. Call init() first.");
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
width: this.el.offsetWidth,
|
|
109
|
+
height: this.el.offsetHeight - taskbarManager.getDimensions().height
|
|
110
|
+
// destop is root element minus taskbar height
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
setupResizeObserver() {
|
|
114
|
+
if (!this.el) return;
|
|
115
|
+
if (this.resizeObserver) {
|
|
116
|
+
this.resizeObserver.disconnect();
|
|
117
|
+
}
|
|
118
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
119
|
+
const dimensions = this.getDimensions();
|
|
120
|
+
this.resizeCallbacks.forEach((callback) => {
|
|
121
|
+
try {
|
|
122
|
+
callback(dimensions);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error("Error in desktop resize callback:", error);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
this.resizeObserver.observe(this.el);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Register a callback for desktop resize events
|
|
132
|
+
* @param callback Function to call when desktop is resized
|
|
133
|
+
* @returns Unregister function to remove the callback
|
|
134
|
+
*/
|
|
135
|
+
onResize(callback) {
|
|
136
|
+
this.resizeCallbacks.add(callback);
|
|
137
|
+
return () => {
|
|
138
|
+
this.resizeCallbacks.delete(callback);
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
globalThis.__defussDesktopManager = globalThis.__defussDesktopManager || new DesktopManager();
|
|
143
|
+
const desktopManager = globalThis.__defussDesktopManager;
|
|
144
|
+
|
|
27
145
|
const defaultWindowOptions = {
|
|
28
146
|
title: "Untitled",
|
|
29
147
|
resizable: true,
|
|
@@ -46,7 +164,9 @@ class WindowManager {
|
|
|
46
164
|
windows = [];
|
|
47
165
|
constructor() {
|
|
48
166
|
defuss.$(() => {
|
|
49
|
-
desktopManager
|
|
167
|
+
if (desktopManager?.onResize) {
|
|
168
|
+
desktopManager.onResize(defussRuntime.debounce(this.onDesktopResized.bind(this), 50));
|
|
169
|
+
}
|
|
50
170
|
});
|
|
51
171
|
}
|
|
52
172
|
onDesktopResized(dimensions) {
|
|
@@ -290,6 +410,11 @@ function Window({
|
|
|
290
410
|
}
|
|
291
411
|
}) {
|
|
292
412
|
let isDragging = false;
|
|
413
|
+
let dragPointerId = null;
|
|
414
|
+
let dragStartMouse = { x: 0, y: 0 };
|
|
415
|
+
let dragStartWin = { x: 0, y: 0 };
|
|
416
|
+
let lastWin = { x, y };
|
|
417
|
+
let capturedTitleBar = null;
|
|
293
418
|
const initialWindowState = windowManager.addWindow({
|
|
294
419
|
id,
|
|
295
420
|
title,
|
|
@@ -301,7 +426,7 @@ function Window({
|
|
|
301
426
|
minimizable,
|
|
302
427
|
maximizable
|
|
303
428
|
});
|
|
304
|
-
|
|
429
|
+
lastWin = { x: initialWindowState.x, y: initialWindowState.y };
|
|
305
430
|
ref.state = {
|
|
306
431
|
onClose: () => {
|
|
307
432
|
console.log("Window closed");
|
|
@@ -315,15 +440,9 @@ function Window({
|
|
|
315
440
|
console.log("Window maximized");
|
|
316
441
|
onMaximize();
|
|
317
442
|
},
|
|
318
|
-
minimize: () =>
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
maximize: () => {
|
|
322
|
-
windowManager.maximizeWindow(initialWindowState.id);
|
|
323
|
-
},
|
|
324
|
-
restore: () => {
|
|
325
|
-
windowManager.restoreWindow(initialWindowState.id);
|
|
326
|
-
},
|
|
443
|
+
minimize: () => windowManager.minimizeWindow(initialWindowState.id),
|
|
444
|
+
maximize: () => windowManager.maximizeWindow(initialWindowState.id),
|
|
445
|
+
restore: () => windowManager.restoreWindow(initialWindowState.id),
|
|
327
446
|
close: () => {
|
|
328
447
|
console.log("Closing window");
|
|
329
448
|
windowManager.closeWindow(initialWindowState.id);
|
|
@@ -334,45 +453,67 @@ function Window({
|
|
|
334
453
|
windowManager.updateWindow(initialWindowState.id, newState);
|
|
335
454
|
},
|
|
336
455
|
250
|
|
337
|
-
// 1/4 second throttle
|
|
338
456
|
);
|
|
339
|
-
const
|
|
457
|
+
const onWindowMouseDown = (_event) => windowManager.setActiveWindow(initialWindowState.id);
|
|
458
|
+
const getCurrentWinPos = () => {
|
|
459
|
+
const el = ref.current;
|
|
460
|
+
if (!el) return { x: lastWin.x, y: lastWin.y };
|
|
461
|
+
const left = Number.parseFloat(el.style.left);
|
|
462
|
+
const top = Number.parseFloat(el.style.top);
|
|
463
|
+
return {
|
|
464
|
+
x: Number.isFinite(left) ? left : el.offsetLeft,
|
|
465
|
+
y: Number.isFinite(top) ? top : el.offsetTop
|
|
466
|
+
};
|
|
467
|
+
};
|
|
468
|
+
const stopDragging = (event) => {
|
|
340
469
|
if (!isDragging) return;
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (currentOffset) {
|
|
347
|
-
const newX = currentOffset.left + deltaX;
|
|
348
|
-
const newY = currentOffset.top + deltaY;
|
|
349
|
-
updateWindowState({ x: newX, y: newY });
|
|
350
|
-
await win.css({
|
|
351
|
-
left: `${newX}px`,
|
|
352
|
-
top: `${newY}px`
|
|
353
|
-
});
|
|
470
|
+
if (event && capturedTitleBar && typeof capturedTitleBar.releasePointerCapture === "function" && dragPointerId === event.pointerId) {
|
|
471
|
+
try {
|
|
472
|
+
capturedTitleBar.releasePointerCapture(event.pointerId);
|
|
473
|
+
} catch {
|
|
474
|
+
}
|
|
354
475
|
}
|
|
355
|
-
|
|
476
|
+
isDragging = false;
|
|
477
|
+
dragPointerId = null;
|
|
478
|
+
capturedTitleBar = null;
|
|
479
|
+
windowManager.updateWindow(initialWindowState.id, { x: lastWin.x, y: lastWin.y });
|
|
356
480
|
};
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
481
|
+
const onTitlePointerDown = (event) => {
|
|
482
|
+
if (event.pointerType === "mouse" && event.button !== 0) return;
|
|
483
|
+
const target = event.target;
|
|
484
|
+
if (target?.tagName === "BUTTON") return;
|
|
485
|
+
const titleBarEl = target?.closest?.(".title-bar");
|
|
486
|
+
if (!titleBarEl) return;
|
|
487
|
+
windowManager.setActiveWindow(initialWindowState.id);
|
|
363
488
|
isDragging = true;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
489
|
+
dragPointerId = event.pointerId;
|
|
490
|
+
capturedTitleBar = titleBarEl;
|
|
491
|
+
dragStartMouse = { x: event.clientX, y: event.clientY };
|
|
492
|
+
dragStartWin = getCurrentWinPos();
|
|
493
|
+
if (typeof titleBarEl.setPointerCapture === "function") {
|
|
494
|
+
titleBarEl.setPointerCapture(event.pointerId);
|
|
495
|
+
}
|
|
370
496
|
event.preventDefault();
|
|
371
497
|
};
|
|
372
|
-
const
|
|
373
|
-
isDragging
|
|
374
|
-
|
|
375
|
-
|
|
498
|
+
const onTitlePointerMove = (event) => {
|
|
499
|
+
if (!isDragging) return;
|
|
500
|
+
if (dragPointerId !== null && event.pointerId !== dragPointerId) return;
|
|
501
|
+
const newX = dragStartWin.x + (event.clientX - dragStartMouse.x);
|
|
502
|
+
const newY = dragStartWin.y + (event.clientY - dragStartMouse.y);
|
|
503
|
+
lastWin = { x: newX, y: newY };
|
|
504
|
+
const el = ref.current;
|
|
505
|
+
if (el) {
|
|
506
|
+
el.style.left = `${newX}px`;
|
|
507
|
+
el.style.top = `${newY}px`;
|
|
508
|
+
}
|
|
509
|
+
updateWindowState({ x: newX, y: newY });
|
|
510
|
+
event.preventDefault();
|
|
511
|
+
};
|
|
512
|
+
const onTitlePointerUp = (event) => {
|
|
513
|
+
stopDragging(event);
|
|
514
|
+
};
|
|
515
|
+
const onTitlePointerCancel = (event) => {
|
|
516
|
+
stopDragging(event);
|
|
376
517
|
};
|
|
377
518
|
const onWindowMounted = () => {
|
|
378
519
|
windowManager.updateWindow(initialWindowState.id, {
|
|
@@ -380,17 +521,18 @@ function Window({
|
|
|
380
521
|
ref
|
|
381
522
|
});
|
|
382
523
|
windowManager.setActiveWindow(initialWindowState.id);
|
|
524
|
+
defuss.$(document).on("blur", () => stopDragging());
|
|
525
|
+
defuss.$(document).on("visibilitychange", () => {
|
|
526
|
+
if (document.hidden) stopDragging();
|
|
527
|
+
});
|
|
383
528
|
};
|
|
384
529
|
const onCloseClick = () => windowManager.closeWindow(initialWindowState.id);
|
|
385
|
-
const onMaximizeClick =
|
|
530
|
+
const onMaximizeClick = () => {
|
|
386
531
|
const currentState = windowManager.getWindow(initialWindowState.id);
|
|
387
|
-
if (currentState?.maximized)
|
|
388
|
-
|
|
389
|
-
} else {
|
|
390
|
-
windowManager.maximizeWindow(initialWindowState.id);
|
|
391
|
-
}
|
|
532
|
+
if (currentState?.maximized) windowManager.restoreWindow(initialWindowState.id);
|
|
533
|
+
else windowManager.maximizeWindow(initialWindowState.id);
|
|
392
534
|
};
|
|
393
|
-
const onMinimizeClick =
|
|
535
|
+
const onMinimizeClick = () => {
|
|
394
536
|
windowManager.minimizeWindow(initialWindowState.id);
|
|
395
537
|
};
|
|
396
538
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -412,36 +554,18 @@ function Window({
|
|
|
412
554
|
{
|
|
413
555
|
class: "title-bar",
|
|
414
556
|
onMount: onWindowMounted,
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
557
|
+
onPointerDown: onTitlePointerDown,
|
|
558
|
+
onPointerMove: onTitlePointerMove,
|
|
559
|
+
onPointerUp: onTitlePointerUp,
|
|
560
|
+
onPointerCancel: onTitlePointerCancel,
|
|
561
|
+
onDblClick: onMaximizeClick,
|
|
562
|
+
style: { touchAction: "none" },
|
|
418
563
|
children: [
|
|
419
564
|
/* @__PURE__ */ jsxRuntime.jsx("div", { class: "title-bar-text", children: title }),
|
|
420
565
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { class: "title-bar-controls", children: [
|
|
421
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
type: "button",
|
|
425
|
-
"aria-label": "Minimize",
|
|
426
|
-
onClick: onMinimizeClick
|
|
427
|
-
}
|
|
428
|
-
),
|
|
429
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
430
|
-
"button",
|
|
431
|
-
{
|
|
432
|
-
type: "button",
|
|
433
|
-
"aria-label": "Maximize",
|
|
434
|
-
onClick: onMaximizeClick
|
|
435
|
-
}
|
|
436
|
-
),
|
|
437
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
438
|
-
"button",
|
|
439
|
-
{
|
|
440
|
-
type: "button",
|
|
441
|
-
"aria-label": "Close",
|
|
442
|
-
onClick: onCloseClick
|
|
443
|
-
}
|
|
444
|
-
)
|
|
566
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Minimize", onClick: onMinimizeClick }),
|
|
567
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Maximize", onClick: onMaximizeClick }),
|
|
568
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Close", onClick: onCloseClick })
|
|
445
569
|
] })
|
|
446
570
|
]
|
|
447
571
|
}
|
|
@@ -627,93 +751,6 @@ const LogonScreen = ({
|
|
|
627
751
|
] });
|
|
628
752
|
};
|
|
629
753
|
|
|
630
|
-
class DefussDesktopAppIcon {
|
|
631
|
-
constructor(config) {
|
|
632
|
-
this.config = config;
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
const defaultDesktopOptions = {
|
|
636
|
-
icons: [],
|
|
637
|
-
backgroundColor: "#000"
|
|
638
|
-
};
|
|
639
|
-
class DesktopManager {
|
|
640
|
-
constructor(options = defaultDesktopOptions) {
|
|
641
|
-
this.options = options;
|
|
642
|
-
}
|
|
643
|
-
el;
|
|
644
|
-
state;
|
|
645
|
-
resizeObserver;
|
|
646
|
-
resizeCallbacks = /* @__PURE__ */ new Set();
|
|
647
|
-
init(el, options = this.options) {
|
|
648
|
-
this.options = options;
|
|
649
|
-
this.el = el;
|
|
650
|
-
this.state = this.state || {
|
|
651
|
-
icons: this.options.icons.map((icon) => icon.config)
|
|
652
|
-
};
|
|
653
|
-
this.setupResizeObserver();
|
|
654
|
-
this.render(el);
|
|
655
|
-
}
|
|
656
|
-
render(el) {
|
|
657
|
-
el.style.backgroundColor = this.options.backgroundColor;
|
|
658
|
-
if (this.options.backgroundImage) {
|
|
659
|
-
el.style.backgroundImage = `url(${this.options.backgroundImage})`;
|
|
660
|
-
}
|
|
661
|
-
if (this.options.backgroundImageSize) {
|
|
662
|
-
el.style.backgroundSize = this.options.backgroundImageSize || "cover";
|
|
663
|
-
}
|
|
664
|
-
if (this.options.backgroundRepeat) {
|
|
665
|
-
el.style.backgroundRepeat = this.options.backgroundRepeat || "no-repeat";
|
|
666
|
-
}
|
|
667
|
-
if (this.options.backgroundPosition) {
|
|
668
|
-
el.style.backgroundPosition = this.options.backgroundPosition || "center";
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
addIcon(icon) {
|
|
672
|
-
this.options.icons.push(icon);
|
|
673
|
-
console.log(`Icon added: ${icon.config.name}`);
|
|
674
|
-
}
|
|
675
|
-
getDimensions() {
|
|
676
|
-
if (!this.el) {
|
|
677
|
-
throw new Error("Desktop not initialized. Call init() first.");
|
|
678
|
-
}
|
|
679
|
-
return {
|
|
680
|
-
width: this.el.offsetWidth,
|
|
681
|
-
height: this.el.offsetHeight - taskbarManager.getDimensions().height
|
|
682
|
-
// destop is root element minus taskbar height
|
|
683
|
-
};
|
|
684
|
-
}
|
|
685
|
-
setupResizeObserver() {
|
|
686
|
-
if (!this.el) return;
|
|
687
|
-
if (this.resizeObserver) {
|
|
688
|
-
this.resizeObserver.disconnect();
|
|
689
|
-
}
|
|
690
|
-
this.resizeObserver = new ResizeObserver(() => {
|
|
691
|
-
const dimensions = this.getDimensions();
|
|
692
|
-
this.resizeCallbacks.forEach((callback) => {
|
|
693
|
-
try {
|
|
694
|
-
callback(dimensions);
|
|
695
|
-
} catch (error) {
|
|
696
|
-
console.error("Error in desktop resize callback:", error);
|
|
697
|
-
}
|
|
698
|
-
});
|
|
699
|
-
});
|
|
700
|
-
this.resizeObserver.observe(this.el);
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
|
-
* Register a callback for desktop resize events
|
|
704
|
-
* @param callback Function to call when desktop is resized
|
|
705
|
-
* @returns Unregister function to remove the callback
|
|
706
|
-
*/
|
|
707
|
-
onResize(callback) {
|
|
708
|
-
this.resizeCallbacks.add(callback);
|
|
709
|
-
return () => {
|
|
710
|
-
this.resizeCallbacks.delete(callback);
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
globalThis.__defussDesktopManager = globalThis.__defussDesktopManager || new DesktopManager();
|
|
715
|
-
const desktopManager = globalThis.__defussDesktopManager;
|
|
716
|
-
|
|
717
754
|
const StartMenu = () => {
|
|
718
755
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "slide-open crt", children: [
|
|
719
756
|
/* @__PURE__ */ jsxRuntime.jsx("div", { class: "top", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { children: "Aron Homberg" }) }),
|
|
@@ -851,15 +888,12 @@ class DequeryWithWindowManager extends defuss.CallChainImpl {
|
|
|
851
888
|
*/
|
|
852
889
|
// register an app to the desktop shell
|
|
853
890
|
createDesktopApp(options) {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
});
|
|
891
|
+
new DefussApp(options);
|
|
892
|
+
return this;
|
|
857
893
|
}
|
|
858
894
|
// create a desktop app icon
|
|
859
895
|
createDesktopAppIcon(options) {
|
|
860
|
-
return
|
|
861
|
-
return new DefussDesktopAppIcon(options);
|
|
862
|
-
});
|
|
896
|
+
return this;
|
|
863
897
|
}
|
|
864
898
|
}
|
|
865
899
|
const $ = defuss.dequery.extend(DequeryWithWindowManager, [
|
|
@@ -868,37 +902,6 @@ const $ = defuss.dequery.extend(DequeryWithWindowManager, [
|
|
|
868
902
|
"createDesktopAppIcon"
|
|
869
903
|
]);
|
|
870
904
|
|
|
871
|
-
const defaultTaskbarOptions = {
|
|
872
|
-
position: "bottom",
|
|
873
|
-
stateful: false,
|
|
874
|
-
theme: "default",
|
|
875
|
-
size: "medium"
|
|
876
|
-
};
|
|
877
|
-
class TaskbarManager {
|
|
878
|
-
position;
|
|
879
|
-
theme;
|
|
880
|
-
// e.g., 'windows-xp', 'macos', etc.
|
|
881
|
-
size;
|
|
882
|
-
constructor(options = defaultTaskbarOptions) {
|
|
883
|
-
this.position = options.position || "bottom";
|
|
884
|
-
this.theme = options.theme || "default";
|
|
885
|
-
this.size = options.size || "medium";
|
|
886
|
-
if (options.stateful) ;
|
|
887
|
-
}
|
|
888
|
-
getDimensions() {
|
|
889
|
-
const el = document.querySelector(".taskbar");
|
|
890
|
-
if (!el) {
|
|
891
|
-
return { width: 0, height: 0 };
|
|
892
|
-
}
|
|
893
|
-
return {
|
|
894
|
-
width: el.clientWidth,
|
|
895
|
-
height: el.clientHeight
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
globalThis.__defussTaskbarManager = globalThis.__defussTaskbarManager || new TaskbarManager();
|
|
900
|
-
const taskbarManager = globalThis.__defussTaskbarManager;
|
|
901
|
-
|
|
902
905
|
const defaultSystemSoundFilePaths = [
|
|
903
906
|
"/sounds/balloon.ogg",
|
|
904
907
|
"/sounds/batterycritical.ogg",
|
package/dist/index.d.cts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import * as defuss from 'defuss';
|
|
2
|
-
import { Props, CallChainImpl, Dequery,
|
|
2
|
+
import { Props, CallChainImpl, Dequery, Ref } from 'defuss';
|
|
3
3
|
|
|
4
|
-
declare function Desktop({ ref }: Props):
|
|
4
|
+
declare function Desktop({ ref }: Props<HTMLDivElement>): JSX.Element;
|
|
5
5
|
|
|
6
|
-
interface ButtonProps extends Props {
|
|
6
|
+
interface ButtonProps extends Props<HTMLButtonElement> {
|
|
7
7
|
onClick?: () => void;
|
|
8
8
|
disabled?: boolean;
|
|
9
9
|
}
|
|
10
|
-
declare function Button({ onClick, disabled, children, ref, }: ButtonProps):
|
|
10
|
+
declare function Button({ onClick, disabled, children, ref, }: ButtonProps): JSX.Element;
|
|
11
11
|
|
|
12
|
-
interface LogonScreenProps extends Props {
|
|
12
|
+
interface LogonScreenProps extends Props<HTMLDivElement> {
|
|
13
13
|
cDriveBasePath?: string;
|
|
14
14
|
showGuestUser?: boolean;
|
|
15
15
|
onTurnOffComputer?: () => void;
|
|
@@ -20,7 +20,7 @@ type LoginForm = {
|
|
|
20
20
|
username: string;
|
|
21
21
|
password: string;
|
|
22
22
|
};
|
|
23
|
-
declare const LogonScreen: ({ cDriveBasePath, showGuestUser, onTurnOffComputer, onGuestLogon, onUserLogonSubmit, ref, }: LogonScreenProps) =>
|
|
23
|
+
declare const LogonScreen: ({ cDriveBasePath, showGuestUser, onTurnOffComputer, onGuestLogon, onUserLogonSubmit, ref, }: LogonScreenProps) => JSX.Element;
|
|
24
24
|
|
|
25
25
|
interface DefussAppConfig {
|
|
26
26
|
name: string;
|
|
@@ -85,20 +85,20 @@ declare class DesktopManager {
|
|
|
85
85
|
}
|
|
86
86
|
declare const desktopManager: DesktopManager;
|
|
87
87
|
|
|
88
|
-
interface ShellProps extends Props {
|
|
88
|
+
interface ShellProps extends Props<HTMLDivElement> {
|
|
89
89
|
desktopConfig: CreateDesktopOptions;
|
|
90
90
|
}
|
|
91
|
-
declare function Shell({ ref, desktopConfig, }: ShellProps):
|
|
91
|
+
declare function Shell({ ref, desktopConfig, }: ShellProps): JSX.Element;
|
|
92
92
|
|
|
93
|
-
declare const Taskbar: () =>
|
|
93
|
+
declare const Taskbar: () => JSX.Element;
|
|
94
94
|
|
|
95
|
-
declare const StartButton: () =>
|
|
95
|
+
declare const StartButton: () => JSX.Element;
|
|
96
96
|
|
|
97
|
-
declare const StartMenu: () =>
|
|
97
|
+
declare const StartMenu: () => JSX.Element;
|
|
98
98
|
|
|
99
99
|
declare class DequeryWithWindowManager<NT> extends CallChainImpl<NT, DequeryWithWindowManager<NT> & Dequery<NT>> {
|
|
100
|
-
createDesktopApp(options: DefussAppConfig):
|
|
101
|
-
createDesktopAppIcon(options: DesktopIconConfig):
|
|
100
|
+
createDesktopApp(options: DefussAppConfig): this & Dequery<NT>;
|
|
101
|
+
createDesktopAppIcon(options: DesktopIconConfig): this & Dequery<NT>;
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
104
104
|
* Extended dequery function with window management capabilities.
|
|
@@ -107,7 +107,7 @@ declare class DequeryWithWindowManager<NT> extends CallChainImpl<NT, DequeryWith
|
|
|
107
107
|
* @param options - Optional dequery options
|
|
108
108
|
* @returns Extended dequery instance with createWindow and createTaskbar methods
|
|
109
109
|
*/
|
|
110
|
-
declare const $: <NT = defuss.
|
|
110
|
+
declare const $: <NT = defuss.NodeType>(selectorRefOrEl: string | defuss.NodeType | defuss.Ref<defuss.NodeType, any> | defuss.RenderInput | Function, options?: (defuss.DequeryOptions<NT> & JSX.HTMLAttributesLowerCase<defuss.DOMElement> & JSX.HTMLAttributesLowerCase<defuss.DOMElement> & {
|
|
111
111
|
html?: string;
|
|
112
112
|
text?: string;
|
|
113
113
|
}) | undefined) => DequeryWithWindowManager<unknown>;
|
|
@@ -142,7 +142,7 @@ interface CreateWindowOptions {
|
|
|
142
142
|
interface WindowState {
|
|
143
143
|
id: string;
|
|
144
144
|
el: HTMLElement;
|
|
145
|
-
ref: Ref<WindowRefState>;
|
|
145
|
+
ref: Ref<HTMLElement, WindowRefState>;
|
|
146
146
|
title: string;
|
|
147
147
|
icon: string;
|
|
148
148
|
width: number;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import * as defuss from 'defuss';
|
|
2
|
-
import { Props, CallChainImpl, Dequery,
|
|
2
|
+
import { Props, CallChainImpl, Dequery, Ref } from 'defuss';
|
|
3
3
|
|
|
4
|
-
declare function Desktop({ ref }: Props):
|
|
4
|
+
declare function Desktop({ ref }: Props<HTMLDivElement>): JSX.Element;
|
|
5
5
|
|
|
6
|
-
interface ButtonProps extends Props {
|
|
6
|
+
interface ButtonProps extends Props<HTMLButtonElement> {
|
|
7
7
|
onClick?: () => void;
|
|
8
8
|
disabled?: boolean;
|
|
9
9
|
}
|
|
10
|
-
declare function Button({ onClick, disabled, children, ref, }: ButtonProps):
|
|
10
|
+
declare function Button({ onClick, disabled, children, ref, }: ButtonProps): JSX.Element;
|
|
11
11
|
|
|
12
|
-
interface LogonScreenProps extends Props {
|
|
12
|
+
interface LogonScreenProps extends Props<HTMLDivElement> {
|
|
13
13
|
cDriveBasePath?: string;
|
|
14
14
|
showGuestUser?: boolean;
|
|
15
15
|
onTurnOffComputer?: () => void;
|
|
@@ -20,7 +20,7 @@ type LoginForm = {
|
|
|
20
20
|
username: string;
|
|
21
21
|
password: string;
|
|
22
22
|
};
|
|
23
|
-
declare const LogonScreen: ({ cDriveBasePath, showGuestUser, onTurnOffComputer, onGuestLogon, onUserLogonSubmit, ref, }: LogonScreenProps) =>
|
|
23
|
+
declare const LogonScreen: ({ cDriveBasePath, showGuestUser, onTurnOffComputer, onGuestLogon, onUserLogonSubmit, ref, }: LogonScreenProps) => JSX.Element;
|
|
24
24
|
|
|
25
25
|
interface DefussAppConfig {
|
|
26
26
|
name: string;
|
|
@@ -85,20 +85,20 @@ declare class DesktopManager {
|
|
|
85
85
|
}
|
|
86
86
|
declare const desktopManager: DesktopManager;
|
|
87
87
|
|
|
88
|
-
interface ShellProps extends Props {
|
|
88
|
+
interface ShellProps extends Props<HTMLDivElement> {
|
|
89
89
|
desktopConfig: CreateDesktopOptions;
|
|
90
90
|
}
|
|
91
|
-
declare function Shell({ ref, desktopConfig, }: ShellProps):
|
|
91
|
+
declare function Shell({ ref, desktopConfig, }: ShellProps): JSX.Element;
|
|
92
92
|
|
|
93
|
-
declare const Taskbar: () =>
|
|
93
|
+
declare const Taskbar: () => JSX.Element;
|
|
94
94
|
|
|
95
|
-
declare const StartButton: () =>
|
|
95
|
+
declare const StartButton: () => JSX.Element;
|
|
96
96
|
|
|
97
|
-
declare const StartMenu: () =>
|
|
97
|
+
declare const StartMenu: () => JSX.Element;
|
|
98
98
|
|
|
99
99
|
declare class DequeryWithWindowManager<NT> extends CallChainImpl<NT, DequeryWithWindowManager<NT> & Dequery<NT>> {
|
|
100
|
-
createDesktopApp(options: DefussAppConfig):
|
|
101
|
-
createDesktopAppIcon(options: DesktopIconConfig):
|
|
100
|
+
createDesktopApp(options: DefussAppConfig): this & Dequery<NT>;
|
|
101
|
+
createDesktopAppIcon(options: DesktopIconConfig): this & Dequery<NT>;
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
104
104
|
* Extended dequery function with window management capabilities.
|
|
@@ -107,7 +107,7 @@ declare class DequeryWithWindowManager<NT> extends CallChainImpl<NT, DequeryWith
|
|
|
107
107
|
* @param options - Optional dequery options
|
|
108
108
|
* @returns Extended dequery instance with createWindow and createTaskbar methods
|
|
109
109
|
*/
|
|
110
|
-
declare const $: <NT = defuss.
|
|
110
|
+
declare const $: <NT = defuss.NodeType>(selectorRefOrEl: string | defuss.NodeType | defuss.Ref<defuss.NodeType, any> | defuss.RenderInput | Function, options?: (defuss.DequeryOptions<NT> & JSX.HTMLAttributesLowerCase<defuss.DOMElement> & JSX.HTMLAttributesLowerCase<defuss.DOMElement> & {
|
|
111
111
|
html?: string;
|
|
112
112
|
text?: string;
|
|
113
113
|
}) | undefined) => DequeryWithWindowManager<unknown>;
|
|
@@ -142,7 +142,7 @@ interface CreateWindowOptions {
|
|
|
142
142
|
interface WindowState {
|
|
143
143
|
id: string;
|
|
144
144
|
el: HTMLElement;
|
|
145
|
-
ref: Ref<WindowRefState>;
|
|
145
|
+
ref: Ref<HTMLElement, WindowRefState>;
|
|
146
146
|
title: string;
|
|
147
147
|
icon: string;
|
|
148
148
|
width: number;
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createRef, $ as $$1, dequery, CallChainImpl
|
|
1
|
+
import { createRef, $ as $$1, dequery, CallChainImpl } from 'defuss';
|
|
2
2
|
import { debounce, throttle } from 'defuss-runtime';
|
|
3
3
|
import { jsx, jsxs } from 'defuss/jsx-runtime';
|
|
4
4
|
import { access, transval, rule } from 'defuss-transval';
|
|
@@ -22,6 +22,124 @@ function Button({
|
|
|
22
22
|
);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const defaultTaskbarOptions = {
|
|
26
|
+
position: "bottom",
|
|
27
|
+
stateful: false,
|
|
28
|
+
theme: "default",
|
|
29
|
+
size: "medium"
|
|
30
|
+
};
|
|
31
|
+
class TaskbarManager {
|
|
32
|
+
position;
|
|
33
|
+
theme;
|
|
34
|
+
// e.g., 'windows-xp', 'macos', etc.
|
|
35
|
+
size;
|
|
36
|
+
constructor(options = defaultTaskbarOptions) {
|
|
37
|
+
this.position = options.position || "bottom";
|
|
38
|
+
this.theme = options.theme || "default";
|
|
39
|
+
this.size = options.size || "medium";
|
|
40
|
+
if (options.stateful) ;
|
|
41
|
+
}
|
|
42
|
+
getDimensions() {
|
|
43
|
+
const el = document.querySelector(".taskbar");
|
|
44
|
+
if (!el) {
|
|
45
|
+
return { width: 0, height: 0 };
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
width: el.clientWidth,
|
|
49
|
+
height: el.clientHeight
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
globalThis.__defussTaskbarManager = globalThis.__defussTaskbarManager || new TaskbarManager();
|
|
54
|
+
const taskbarManager = globalThis.__defussTaskbarManager;
|
|
55
|
+
|
|
56
|
+
class DefussDesktopAppIcon {
|
|
57
|
+
constructor(config) {
|
|
58
|
+
this.config = config;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const defaultDesktopOptions = {
|
|
62
|
+
icons: [],
|
|
63
|
+
backgroundColor: "#000"
|
|
64
|
+
};
|
|
65
|
+
class DesktopManager {
|
|
66
|
+
constructor(options = defaultDesktopOptions) {
|
|
67
|
+
this.options = options;
|
|
68
|
+
}
|
|
69
|
+
el;
|
|
70
|
+
state;
|
|
71
|
+
resizeObserver;
|
|
72
|
+
resizeCallbacks = /* @__PURE__ */ new Set();
|
|
73
|
+
init(el, options = this.options) {
|
|
74
|
+
this.options = options;
|
|
75
|
+
this.el = el;
|
|
76
|
+
this.state = this.state || {
|
|
77
|
+
icons: this.options.icons.map((icon) => icon.config)
|
|
78
|
+
};
|
|
79
|
+
this.setupResizeObserver();
|
|
80
|
+
this.render(el);
|
|
81
|
+
}
|
|
82
|
+
render(el) {
|
|
83
|
+
el.style.backgroundColor = this.options.backgroundColor;
|
|
84
|
+
if (this.options.backgroundImage) {
|
|
85
|
+
el.style.backgroundImage = `url(${this.options.backgroundImage})`;
|
|
86
|
+
}
|
|
87
|
+
if (this.options.backgroundImageSize) {
|
|
88
|
+
el.style.backgroundSize = this.options.backgroundImageSize || "cover";
|
|
89
|
+
}
|
|
90
|
+
if (this.options.backgroundRepeat) {
|
|
91
|
+
el.style.backgroundRepeat = this.options.backgroundRepeat || "no-repeat";
|
|
92
|
+
}
|
|
93
|
+
if (this.options.backgroundPosition) {
|
|
94
|
+
el.style.backgroundPosition = this.options.backgroundPosition || "center";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
addIcon(icon) {
|
|
98
|
+
this.options.icons.push(icon);
|
|
99
|
+
console.log(`Icon added: ${icon.config.name}`);
|
|
100
|
+
}
|
|
101
|
+
getDimensions() {
|
|
102
|
+
if (!this.el) {
|
|
103
|
+
throw new Error("Desktop not initialized. Call init() first.");
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
width: this.el.offsetWidth,
|
|
107
|
+
height: this.el.offsetHeight - taskbarManager.getDimensions().height
|
|
108
|
+
// destop is root element minus taskbar height
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
setupResizeObserver() {
|
|
112
|
+
if (!this.el) return;
|
|
113
|
+
if (this.resizeObserver) {
|
|
114
|
+
this.resizeObserver.disconnect();
|
|
115
|
+
}
|
|
116
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
117
|
+
const dimensions = this.getDimensions();
|
|
118
|
+
this.resizeCallbacks.forEach((callback) => {
|
|
119
|
+
try {
|
|
120
|
+
callback(dimensions);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error("Error in desktop resize callback:", error);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
this.resizeObserver.observe(this.el);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Register a callback for desktop resize events
|
|
130
|
+
* @param callback Function to call when desktop is resized
|
|
131
|
+
* @returns Unregister function to remove the callback
|
|
132
|
+
*/
|
|
133
|
+
onResize(callback) {
|
|
134
|
+
this.resizeCallbacks.add(callback);
|
|
135
|
+
return () => {
|
|
136
|
+
this.resizeCallbacks.delete(callback);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
globalThis.__defussDesktopManager = globalThis.__defussDesktopManager || new DesktopManager();
|
|
141
|
+
const desktopManager = globalThis.__defussDesktopManager;
|
|
142
|
+
|
|
25
143
|
const defaultWindowOptions = {
|
|
26
144
|
title: "Untitled",
|
|
27
145
|
resizable: true,
|
|
@@ -44,7 +162,9 @@ class WindowManager {
|
|
|
44
162
|
windows = [];
|
|
45
163
|
constructor() {
|
|
46
164
|
$$1(() => {
|
|
47
|
-
desktopManager
|
|
165
|
+
if (desktopManager?.onResize) {
|
|
166
|
+
desktopManager.onResize(debounce(this.onDesktopResized.bind(this), 50));
|
|
167
|
+
}
|
|
48
168
|
});
|
|
49
169
|
}
|
|
50
170
|
onDesktopResized(dimensions) {
|
|
@@ -288,6 +408,11 @@ function Window({
|
|
|
288
408
|
}
|
|
289
409
|
}) {
|
|
290
410
|
let isDragging = false;
|
|
411
|
+
let dragPointerId = null;
|
|
412
|
+
let dragStartMouse = { x: 0, y: 0 };
|
|
413
|
+
let dragStartWin = { x: 0, y: 0 };
|
|
414
|
+
let lastWin = { x, y };
|
|
415
|
+
let capturedTitleBar = null;
|
|
291
416
|
const initialWindowState = windowManager.addWindow({
|
|
292
417
|
id,
|
|
293
418
|
title,
|
|
@@ -299,7 +424,7 @@ function Window({
|
|
|
299
424
|
minimizable,
|
|
300
425
|
maximizable
|
|
301
426
|
});
|
|
302
|
-
|
|
427
|
+
lastWin = { x: initialWindowState.x, y: initialWindowState.y };
|
|
303
428
|
ref.state = {
|
|
304
429
|
onClose: () => {
|
|
305
430
|
console.log("Window closed");
|
|
@@ -313,15 +438,9 @@ function Window({
|
|
|
313
438
|
console.log("Window maximized");
|
|
314
439
|
onMaximize();
|
|
315
440
|
},
|
|
316
|
-
minimize: () =>
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
maximize: () => {
|
|
320
|
-
windowManager.maximizeWindow(initialWindowState.id);
|
|
321
|
-
},
|
|
322
|
-
restore: () => {
|
|
323
|
-
windowManager.restoreWindow(initialWindowState.id);
|
|
324
|
-
},
|
|
441
|
+
minimize: () => windowManager.minimizeWindow(initialWindowState.id),
|
|
442
|
+
maximize: () => windowManager.maximizeWindow(initialWindowState.id),
|
|
443
|
+
restore: () => windowManager.restoreWindow(initialWindowState.id),
|
|
325
444
|
close: () => {
|
|
326
445
|
console.log("Closing window");
|
|
327
446
|
windowManager.closeWindow(initialWindowState.id);
|
|
@@ -332,45 +451,67 @@ function Window({
|
|
|
332
451
|
windowManager.updateWindow(initialWindowState.id, newState);
|
|
333
452
|
},
|
|
334
453
|
250
|
|
335
|
-
// 1/4 second throttle
|
|
336
454
|
);
|
|
337
|
-
const
|
|
455
|
+
const onWindowMouseDown = (_event) => windowManager.setActiveWindow(initialWindowState.id);
|
|
456
|
+
const getCurrentWinPos = () => {
|
|
457
|
+
const el = ref.current;
|
|
458
|
+
if (!el) return { x: lastWin.x, y: lastWin.y };
|
|
459
|
+
const left = Number.parseFloat(el.style.left);
|
|
460
|
+
const top = Number.parseFloat(el.style.top);
|
|
461
|
+
return {
|
|
462
|
+
x: Number.isFinite(left) ? left : el.offsetLeft,
|
|
463
|
+
y: Number.isFinite(top) ? top : el.offsetTop
|
|
464
|
+
};
|
|
465
|
+
};
|
|
466
|
+
const stopDragging = (event) => {
|
|
338
467
|
if (!isDragging) return;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
});
|
|
468
|
+
if (event && capturedTitleBar && typeof capturedTitleBar.releasePointerCapture === "function" && dragPointerId === event.pointerId) {
|
|
469
|
+
try {
|
|
470
|
+
capturedTitleBar.releasePointerCapture(event.pointerId);
|
|
471
|
+
} catch {
|
|
472
|
+
}
|
|
352
473
|
}
|
|
353
|
-
|
|
474
|
+
isDragging = false;
|
|
475
|
+
dragPointerId = null;
|
|
476
|
+
capturedTitleBar = null;
|
|
477
|
+
windowManager.updateWindow(initialWindowState.id, { x: lastWin.x, y: lastWin.y });
|
|
354
478
|
};
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
479
|
+
const onTitlePointerDown = (event) => {
|
|
480
|
+
if (event.pointerType === "mouse" && event.button !== 0) return;
|
|
481
|
+
const target = event.target;
|
|
482
|
+
if (target?.tagName === "BUTTON") return;
|
|
483
|
+
const titleBarEl = target?.closest?.(".title-bar");
|
|
484
|
+
if (!titleBarEl) return;
|
|
485
|
+
windowManager.setActiveWindow(initialWindowState.id);
|
|
361
486
|
isDragging = true;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
487
|
+
dragPointerId = event.pointerId;
|
|
488
|
+
capturedTitleBar = titleBarEl;
|
|
489
|
+
dragStartMouse = { x: event.clientX, y: event.clientY };
|
|
490
|
+
dragStartWin = getCurrentWinPos();
|
|
491
|
+
if (typeof titleBarEl.setPointerCapture === "function") {
|
|
492
|
+
titleBarEl.setPointerCapture(event.pointerId);
|
|
493
|
+
}
|
|
368
494
|
event.preventDefault();
|
|
369
495
|
};
|
|
370
|
-
const
|
|
371
|
-
isDragging
|
|
372
|
-
|
|
373
|
-
|
|
496
|
+
const onTitlePointerMove = (event) => {
|
|
497
|
+
if (!isDragging) return;
|
|
498
|
+
if (dragPointerId !== null && event.pointerId !== dragPointerId) return;
|
|
499
|
+
const newX = dragStartWin.x + (event.clientX - dragStartMouse.x);
|
|
500
|
+
const newY = dragStartWin.y + (event.clientY - dragStartMouse.y);
|
|
501
|
+
lastWin = { x: newX, y: newY };
|
|
502
|
+
const el = ref.current;
|
|
503
|
+
if (el) {
|
|
504
|
+
el.style.left = `${newX}px`;
|
|
505
|
+
el.style.top = `${newY}px`;
|
|
506
|
+
}
|
|
507
|
+
updateWindowState({ x: newX, y: newY });
|
|
508
|
+
event.preventDefault();
|
|
509
|
+
};
|
|
510
|
+
const onTitlePointerUp = (event) => {
|
|
511
|
+
stopDragging(event);
|
|
512
|
+
};
|
|
513
|
+
const onTitlePointerCancel = (event) => {
|
|
514
|
+
stopDragging(event);
|
|
374
515
|
};
|
|
375
516
|
const onWindowMounted = () => {
|
|
376
517
|
windowManager.updateWindow(initialWindowState.id, {
|
|
@@ -378,17 +519,18 @@ function Window({
|
|
|
378
519
|
ref
|
|
379
520
|
});
|
|
380
521
|
windowManager.setActiveWindow(initialWindowState.id);
|
|
522
|
+
$$1(document).on("blur", () => stopDragging());
|
|
523
|
+
$$1(document).on("visibilitychange", () => {
|
|
524
|
+
if (document.hidden) stopDragging();
|
|
525
|
+
});
|
|
381
526
|
};
|
|
382
527
|
const onCloseClick = () => windowManager.closeWindow(initialWindowState.id);
|
|
383
|
-
const onMaximizeClick =
|
|
528
|
+
const onMaximizeClick = () => {
|
|
384
529
|
const currentState = windowManager.getWindow(initialWindowState.id);
|
|
385
|
-
if (currentState?.maximized)
|
|
386
|
-
|
|
387
|
-
} else {
|
|
388
|
-
windowManager.maximizeWindow(initialWindowState.id);
|
|
389
|
-
}
|
|
530
|
+
if (currentState?.maximized) windowManager.restoreWindow(initialWindowState.id);
|
|
531
|
+
else windowManager.maximizeWindow(initialWindowState.id);
|
|
390
532
|
};
|
|
391
|
-
const onMinimizeClick =
|
|
533
|
+
const onMinimizeClick = () => {
|
|
392
534
|
windowManager.minimizeWindow(initialWindowState.id);
|
|
393
535
|
};
|
|
394
536
|
return /* @__PURE__ */ jsxs(
|
|
@@ -410,36 +552,18 @@ function Window({
|
|
|
410
552
|
{
|
|
411
553
|
class: "title-bar",
|
|
412
554
|
onMount: onWindowMounted,
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
555
|
+
onPointerDown: onTitlePointerDown,
|
|
556
|
+
onPointerMove: onTitlePointerMove,
|
|
557
|
+
onPointerUp: onTitlePointerUp,
|
|
558
|
+
onPointerCancel: onTitlePointerCancel,
|
|
559
|
+
onDblClick: onMaximizeClick,
|
|
560
|
+
style: { touchAction: "none" },
|
|
416
561
|
children: [
|
|
417
562
|
/* @__PURE__ */ jsx("div", { class: "title-bar-text", children: title }),
|
|
418
563
|
/* @__PURE__ */ jsxs("div", { class: "title-bar-controls", children: [
|
|
419
|
-
/* @__PURE__ */ jsx(
|
|
420
|
-
|
|
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
|
-
)
|
|
564
|
+
/* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Minimize", onClick: onMinimizeClick }),
|
|
565
|
+
/* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Maximize", onClick: onMaximizeClick }),
|
|
566
|
+
/* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Close", onClick: onCloseClick })
|
|
443
567
|
] })
|
|
444
568
|
]
|
|
445
569
|
}
|
|
@@ -625,93 +749,6 @@ const LogonScreen = ({
|
|
|
625
749
|
] });
|
|
626
750
|
};
|
|
627
751
|
|
|
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
752
|
const StartMenu = () => {
|
|
716
753
|
return /* @__PURE__ */ jsxs("div", { class: "slide-open crt", children: [
|
|
717
754
|
/* @__PURE__ */ jsx("div", { class: "top", children: /* @__PURE__ */ jsx("h1", { children: "Aron Homberg" }) }),
|
|
@@ -849,15 +886,12 @@ class DequeryWithWindowManager extends CallChainImpl {
|
|
|
849
886
|
*/
|
|
850
887
|
// register an app to the desktop shell
|
|
851
888
|
createDesktopApp(options) {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
});
|
|
889
|
+
new DefussApp(options);
|
|
890
|
+
return this;
|
|
855
891
|
}
|
|
856
892
|
// create a desktop app icon
|
|
857
893
|
createDesktopAppIcon(options) {
|
|
858
|
-
return
|
|
859
|
-
return new DefussDesktopAppIcon(options);
|
|
860
|
-
});
|
|
894
|
+
return this;
|
|
861
895
|
}
|
|
862
896
|
}
|
|
863
897
|
const $ = dequery.extend(DequeryWithWindowManager, [
|
|
@@ -866,37 +900,6 @@ const $ = dequery.extend(DequeryWithWindowManager, [
|
|
|
866
900
|
"createDesktopAppIcon"
|
|
867
901
|
]);
|
|
868
902
|
|
|
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
903
|
const defaultSystemSoundFilePaths = [
|
|
901
904
|
"/sounds/balloon.ogg",
|
|
902
905
|
"/sounds/batterycritical.ogg",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "defuss-desktop",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
"type": "git"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
|
-
"clean": "rm -rf ./dist && rm -rf ./node_modules/.
|
|
23
|
-
"pretest": "
|
|
24
|
-
"prebuild": "
|
|
22
|
+
"clean": "rm -rf ./dist && rm -rf ./node_modules/.bun",
|
|
23
|
+
"pretest": "bun run build",
|
|
24
|
+
"prebuild": "bun run clean",
|
|
25
25
|
"build": "pkgroll",
|
|
26
26
|
"postbuild": "npm run copy-scss",
|
|
27
27
|
"copy-scss": "mkdir -p dist/scss dist/themes && cp index.scss dist/ && cp xp.scss dist/ && cp -r scss/* dist/scss/ && cp -r themes/* dist/themes/",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"node": "^18.17.1 || ^20.3.0 || >=21.0.0"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"defuss": "^
|
|
57
|
-
"defuss-runtime": "^1.
|
|
56
|
+
"defuss": "^3.4.0",
|
|
57
|
+
"defuss-runtime": "^1.3.1",
|
|
58
58
|
"defuss-transval": "^1.3.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
@@ -65,4 +65,4 @@
|
|
|
65
65
|
"typescript": "^5.9.3",
|
|
66
66
|
"vitest": "^4.0.17"
|
|
67
67
|
}
|
|
68
|
-
}
|
|
68
|
+
}
|