Skip to content

Commit 5da10bc

Browse files
committed
(v0.1.4.0-alpha) Draggable App Items! & Others
FINALLLYY, desktop app items are working!!!!
1 parent d22caad commit 5da10bc

File tree

2 files changed

+222
-159
lines changed

2 files changed

+222
-159
lines changed

src/routes/desktop.jsx

Lines changed: 147 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,75 @@
11
import '../styles/routes/desktop.css';
2-
import {Wifi, Volume2, Bell} from 'lucide-react';
3-
import AppWindow, {INITIAL_Z, getNextZ} from '../core/WindowManager.jsx';
4-
import {useRef, useState} from "react";
5-
import {APP_REGISTRY} from "../core/Apps.js";
2+
import { Wifi, Volume2, Bell } from 'lucide-react';
3+
import AppWindow, { INITIAL_Z, getNextZ } from '../core/WindowManager.jsx';
4+
import { createRef, useRef, useState, useMemo } from "react";
5+
import { APP_REGISTRY } from "../core/Apps.js";
6+
import Draggable from 'react-draggable';
67

78
function Desktop() {
8-
const [apps, setApps] = useState({
9-
explorer: {isOpen: false, minimized: true, fullscreen: false, zIndex: INITIAL_Z, initialX: 0, initialY: 0, data: null},
10-
vscode: {isOpen: false, minimized: true, fullscreen: false, zIndex: INITIAL_Z, initialX: 0, initialY: 0, data: null},
11-
settings: {isOpen: false, minimized: true, fullscreen: false, zIndex: INITIAL_Z, initialX: 0, initialY: 0, data: null},
12-
chrome: {isOpen: false, minimized: true, fullscreen: false, zIndex: INITIAL_Z, initialX: 0, initialY: 0, data: null},
13-
terminal: {isOpen: false, minimized: true, fullscreen: false, zIndex: INITIAL_Z, initialX: 0, initialY: 0, data: null},
9+
const [iconPositions, setIconPositions] = useState({
10+
explorer: { col: 1, row: 1 },
11+
vscode: { col: 1, row: 2 },
12+
settings: { col: 1, row: 3 },
13+
chrome: { col: 2, row: 1 },
14+
terminal: { col: 2, row: 2 },
1415
});
1516

16-
const [focussed, setFocussed] = useState(null)
17+
const [selectedIcon, setSelectedIcon] = useState(null);
18+
const [focussed, setFocussed] = useState(null);
19+
const [apps, setApps] = useState({
20+
explorer: { isOpen: false, minimized: true, fullscreen: false, zIndex: INITIAL_Z, initialX: 0, initialY: 0 },
21+
vscode: { isOpen: false, minimized: true, fullscreen: false, zIndex: INITIAL_Z, initialX: 0, initialY: 0 },
22+
settings: { isOpen: false, minimized: true, fullscreen: false, zIndex: INITIAL_Z, initialX: 0, initialY: 0 },
23+
chrome: { isOpen: false, minimized: true, fullscreen: false, zIndex: INITIAL_Z, initialX: 0, initialY: 0 },
24+
terminal: { isOpen: false, minimized: true, fullscreen: false, zIndex: INITIAL_Z, initialX: 0, initialY: 0 },
25+
});
1726

18-
const lastPos = useRef({x: 100, y: 100});
27+
const lastPos = useRef({ x: 100, y: 100 });
1928
const OFFSET = 20;
2029

21-
function openApp(name) {
22-
const openApps = Object.values(apps).filter(
23-
app => app.isOpen
24-
);
30+
const nodeRefs = useMemo(() => ({
31+
explorer: createRef(),
32+
vscode: createRef(),
33+
settings: createRef(),
34+
chrome: createRef(),
35+
terminal: createRef(),
36+
}), []);
2537

26-
let newX, newY;
38+
const handleIconStop = (e, data, id) => {
39+
const gridX = window.innerWidth / 15;
40+
const gridY = (window.innerHeight - 48) / 6;
2741

28-
if (openApps.length === 0) {
29-
newX = (window.innerWidth / 2) - 300;
30-
newY = (window.innerHeight / 2) - 250;
31-
lastPos.current = {x: newX, y: newY};
42+
const newCol = Math.max(1, Math.min(15, iconPositions[id].col + Math.round(data.x / gridX)));
43+
const newRow = Math.max(1, Math.min(6, iconPositions[id].row + Math.round(data.y / gridY)));
3244

33-
} else {
34-
newX = lastPos.current.x + OFFSET;
35-
newY = lastPos.current.y + OFFSET;
45+
const isOccupied = Object.entries(iconPositions).some(([appId, pos]) => {
46+
return appId !== id && pos.col === newCol && pos.row === newRow;
47+
});
3648

37-
if (newY > window.innerHeight - 500) newY = 100;
38-
if (newX > window.innerWidth - 700) newX = 100;
49+
if (isOccupied) {
50+
return;
51+
}
52+
setIconPositions(prev => ({
53+
...prev,
54+
[id]: { col: newCol, row: newRow }
55+
}));
56+
};
3957

40-
lastPos.current = {x: newX, y: newY};
58+
function openApp(name) {
59+
const openApps = Object.values(apps).filter(app => app.isOpen);
60+
let newX, newY;
61+
62+
if (!apps[name].isOpen) {
63+
if (openApps.length === 0) {
64+
newX = (window.innerWidth / 2) - 300;
65+
newY = (window.innerHeight / 2) - 250;
66+
} else {
67+
newX = lastPos.current.x + OFFSET;
68+
newY = lastPos.current.y + OFFSET;
69+
if (newY > window.innerHeight - 500) newY = 100;
70+
if (newX > window.innerWidth - 700) newX = 100;
71+
}
72+
lastPos.current = { x: newX, y: newY };
4173
}
4274

4375
if (focussed === name) {
@@ -48,187 +80,144 @@ function Desktop() {
4880
isOpen: true,
4981
fullscreen: false,
5082
minimized: !prev[name].minimized,
51-
zIndex: getNextZ()
52-
}
83+
zIndex: getNextZ() }
5384
}));
54-
}
55-
else {
85+
} else {
5686
focusApp(name);
5787
setApps(prev => ({
5888
...prev,
5989
[name]: {
6090
...prev[name],
6191
isOpen: true,
62-
minimized: false,
6392
fullscreen: false,
93+
minimized: false,
6494
zIndex: getNextZ(),
6595
initialX: lastPos.current.x,
6696
initialY: lastPos.current.y,
6797
}
6898
}));
6999
}
70-
71100
}
72101

73-
function focusApp(name) {
74-
setFocussed(name)
102+
const isAnyFullScreen = useMemo(() => {
103+
return Object.values(apps).some(app => app.isOpen && app.fullscreen && !app.minimized);
104+
}, [apps]);
75105

106+
function focusApp(name) {
107+
setFocussed(name);
76108
setApps(prev => ({
77109
...prev,
78-
[name]: {
79-
...prev[name],
80-
zIndex: getNextZ(),
81-
}
110+
[name]: { ...prev[name], zIndex: getNextZ() }
82111
}));
83112
}
84113

85114
function closeApp(name) {
86-
setFocussed(null)
115+
setFocussed(null);
87116
setApps(prev => ({
88117
...prev,
89-
[name]: {
90-
...prev[name],
91-
isOpen: false,
92-
minimized: true
93-
}
118+
[name]: { ...prev[name], isOpen: false, minimized: true }
94119
}));
95120
}
96121

97122
function minimizeApp(name) {
98123
setApps(prev => ({
99124
...prev,
100-
[name]: {
101-
...prev[name],
102-
minimized: true,
103-
isOpen: true,
104-
}
105-
}))
125+
[name]: { ...prev[name], minimized: true }
126+
}));
106127
}
107128

108129
function toggleFullscreen(name) {
109130
setApps(prev => ({
110131
...prev,
111-
[name]: {
112-
...prev[name],
113-
fullscreen: !prev[name].fullscreen,
114-
zIndex: getNextZ()
115-
}
132+
[name]: { ...prev[name], fullscreen: !prev[name].fullscreen, zIndex: getNextZ() }
116133
}));
117134
}
118135

119136
return (
120-
<div>
121-
<div className={"desktop"}>
122-
{Object.entries(apps).map(([name, app]) =>
123-
app.isOpen ? (
124-
<AppWindow
125-
key={name}
126-
title={APP_REGISTRY[name].title}
127-
src={APP_REGISTRY[name].src}
128-
imgSrc={APP_REGISTRY[name].imgSrc}
129-
zIndex={app.zIndex}
130-
fullscreen={app.fullscreen}
131-
minimized={app.minimized}
132-
onFocus={() => focusApp(name)}
133-
onMinimize={() => minimizeApp(name)}
134-
onClose={() => closeApp(name)}
135-
onFullscreen={() => toggleFullscreen(name)}
136-
posX={app.initialX}
137-
posY={app.initialY}
138-
/>
139-
) : null
140-
)}
141-
<div className={"taskbar"}>
142-
<div className={"apps"}>
143-
<div className={"app-item"}>
144-
<div className={"start-menu"} id={"start-menu"}></div>
145-
</div>
137+
<div className="desktop" onMouseDown={() => setSelectedIcon(null)}>
138+
<div className="desktop-grid" style={{ position: 'absolute', inset: 0, display: 'grid', zIndex: 1 }}>
139+
{Object.keys(iconPositions).map((id) => (
140+
<Draggable
141+
key={id}
142+
nodeRef={nodeRefs[id]}
143+
position={{ x: 0, y: 0 }}
144+
grid={[window.innerWidth / 14, (window.innerHeight - 48) / 6]}
145+
onStart={(e) => {
146+
e.stopPropagation();
147+
setSelectedIcon(id);
148+
}}
149+
onStop={(e, data) => handleIconStop(e, data, id)}
150+
cancel=".icon, p"
151+
>
146152
<div
147-
className={`app-item ${
148-
!apps.explorer.isOpen
149-
? ""
150-
: (focussed === "explorer" && !apps.explorer.minimized)
151-
? "open focussed active"
152-
: "open"
153-
}`}
154-
onClick={() => openApp("explorer")}
153+
ref={nodeRefs[id]}
154+
className={`grid-app-item ${selectedIcon === id ? 'selected' : ''}`}
155+
onMouseDown={(e) => {
156+
e.stopPropagation()
157+
setSelectedIcon(id);
158+
}}
159+
onDoubleClick={() => openApp(id)}
160+
style={{
161+
gridColumn: iconPositions[id].col,
162+
gridRow: iconPositions[id].row,
163+
}}
155164
>
156-
<div
157-
className={"file-explorer"}
158-
id={"file-explorer"}
159-
></div>
160-
</div>
161-
<div
162-
className={`app-item ${
163-
!apps.vscode.isOpen
164-
? ""
165-
: (focussed === "vscode" && !apps.vscode.minimized)
166-
? "open focussed active"
167-
: "open"
168-
}`}
169-
onClick={() => openApp("vscode")}
170-
>
171-
<div
172-
className={"vs-code"}
173-
id={"vs-code"}
174-
></div>
175-
</div>
176-
<div
177-
className={`app-item ${
178-
!apps.settings.isOpen
179-
? ""
180-
: (focussed === "settings" && !apps.settings.minimized)
181-
? "open focussed active"
182-
: "open"
183-
}`}
184-
onClick={() => openApp("settings")}
185-
>
186-
<div className={"settings"} id={"settings"}></div>
187-
</div>
188-
<div
189-
className={`app-item ${
190-
!apps.chrome.isOpen
191-
? ""
192-
: (focussed === "chrome" && !apps.chrome.minimized)
193-
? "open focussed active"
194-
: "open"
195-
}`}
196-
onClick={() => openApp("chrome")}
197-
>
198-
<div className={"chrome"} id={"chrome"}></div>
165+
<img className="app-icon" src={APP_REGISTRY[id].imgSrc} draggable="false" alt="icon" />
166+
<p className={"app-name"} style={{ pointerEvents: 'none' }}>{APP_REGISTRY[id].title}</p>
199167
</div>
168+
</Draggable>
169+
))}
170+
</div>
171+
172+
{Object.entries(apps).map(([name, app]) =>
173+
app.isOpen ? (
174+
<AppWindow
175+
key={name}
176+
title={APP_REGISTRY[name].title}
177+
src={APP_REGISTRY[name].src}
178+
imgSrc={APP_REGISTRY[name].imgSrc}
179+
zIndex={app.zIndex}
180+
fullscreen={app.fullscreen}
181+
minimized={app.minimized}
182+
onFocus={() => focusApp(name)}
183+
onMinimize={() => minimizeApp(name)}
184+
onClose={() => closeApp(name)}
185+
onFullscreen={() => toggleFullscreen(name)}
186+
posX={app.initialX}
187+
posY={app.initialY}
188+
/>
189+
) : null
190+
)}
191+
192+
<div className={`taskbar ${ isAnyFullScreen ? 'is-fullscreen' : ''}`} onClick={(e) => e.stopPropagation()}>
193+
<div className="apps">
194+
<div className="app-item">
195+
<div className="start-menu" id="start-menu"></div>
196+
</div>
197+
{['explorer', 'vscode', 'settings', 'chrome', 'terminal'].map(id => (
200198
<div
201-
className={`app-item ${
202-
!apps.terminal.isOpen
203-
? ""
204-
: (focussed === "terminal" && !apps.terminal.minimized)
205-
? "open focussed active"
206-
: "open"
207-
}`}
208-
onClick={() => openApp("terminal")}
199+
key={id}
200+
className={`app-item ${!apps[id].isOpen ? "" : (focussed === id && !apps[id].minimized) ? "open focussed active" : "open"}`}
201+
onClick={() => openApp(id)}
209202
>
210-
<div className={"terminal"} id={"terminal"}></div>
203+
<div className={id === 'explorer' ? 'file-explorer' : id === 'vscode' ? 'vs-code' : id} id={id}></div>
211204
</div>
205+
))}
206+
</div>
207+
<div className="tray">
208+
<div className="wifisound tray-container">
209+
<div className="tray-item"><Wifi size={18} /></div>
210+
<div className="tray-item"><Volume2 size={18} /></div>
212211
</div>
213-
<div className={"tray"}>
214-
<div className={"wifisound tray-container"}>
215-
<div className={"tray-item"}>
216-
<Wifi size={18}/>
217-
</div>
218-
<div className={"tray-item"}>
219-
<Volume2 size={18}/>
220-
</div>
221-
</div>
222-
<div className={"datetime tray-container"}>
223-
<p>16:55</p>
224-
<p>18-02-2026</p>
225-
</div>
226-
<Bell className={"notif tray-item tray-container"} size={18}/>
212+
<div className="datetime tray-container">
213+
<p>16:55</p>
214+
<p>18-02-2026</p>
227215
</div>
216+
<Bell className="notif tray-item tray-container" size={18} />
228217
</div>
229218
</div>
230219
</div>
231-
)
220+
);
232221
}
233222

234-
export default Desktop
223+
export default Desktop;

0 commit comments

Comments
 (0)