11import '../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
78function 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