1+ // js/events.js
12import { state , setState } from "./state.js" ;
23import { qs } from "./dom.js" ;
34import { renderGrid , applyGridPosition , saveGridState } from "./grid.js" ;
45import { VirtualGrid } from "./grid/virtualGrid.js" ;
56import { showToast } from "./ui/toasts.js" ;
6- import { logger } from "./logger.js" ; // Import Logger
77
8- // Drag State
98let actItem = null ;
109let ghosts = [ ] ;
1110let dragOffsetX , dragOffsetY ;
1211let gridRect ;
1312let updateFrame = null ;
1413let lastMoveResult = null ;
15- let mode = null ; // 'move' or 'resize'
16-
17- // Resize State
14+ let mode = null ;
1815let initResizeX , initResizeY ;
1916let startCols , startRows ;
2017
2118export function initGlobalEvents ( ) {
2219 const dashboard = qs ( '#dashboard' ) ;
23- // NOTE: We don't cache gridLines here anymore to prevent stale references
24-
25- if ( ! dashboard ) {
26- setTimeout ( initGlobalEvents , 100 ) ;
27- return ;
28- }
20+ if ( ! dashboard ) { setTimeout ( initGlobalEvents , 100 ) ; return ; }
2921
3022 dashboard . addEventListener ( 'mousedown' , e => {
3123 if ( ! state . ui . editMode ) return ;
3224 if ( e . target . closest ( '.delete-btn' ) || e . target . closest ( '.edit-btn' ) ) return ;
33-
3425 const card = e . target . closest ( '.app-card' ) ;
3526 if ( ! card ) return ;
36-
37- // Prevent default browser dragging/selection
3827 e . preventDefault ( ) ;
39-
4028 actItem = card ;
41-
42- // REFRESH GRID RECT: Always get the fresh element
4329 const gridLines = qs ( '#gridLines' ) ;
44- if ( gridLines ) {
45- gridRect = gridLines . getBoundingClientRect ( ) ;
46- } else {
47- console . error ( "GridLines missing!" ) ;
48- return ;
49- }
30+ if ( gridLines ) gridRect = gridLines . getBoundingClientRect ( ) ;
31+ else return ;
5032
51- // 1. CHECK MODE
5233 if ( e . target . closest ( '.resize-handle' ) ) {
5334 mode = 'resize' ;
5435 initResizeX = e . clientX ;
@@ -57,11 +38,9 @@ export function initGlobalEvents() {
5738 startRows = parseInt ( actItem . dataset . rows ) || 1 ;
5839 } else {
5940 mode = 'move' ;
60- // Setup Drag Visuals
6141 const rect = actItem . getBoundingClientRect ( ) ;
6242 dragOffsetX = e . clientX - rect . left ;
6343 dragOffsetY = e . clientY - rect . top ;
64-
6544 actItem . classList . add ( 'moving' ) ;
6645 actItem . style . width = rect . width + 'px' ;
6746 actItem . style . height = rect . height + 'px' ;
@@ -70,60 +49,40 @@ export function initGlobalEvents() {
7049 actItem . style . top = rect . top + 'px' ;
7150 actItem . style . zIndex = '1000' ;
7251 }
73-
7452 document . addEventListener ( 'mousemove' , onMouseMove ) ;
7553 document . addEventListener ( 'mouseup' , onMouseUp ) ;
7654 } ) ;
7755
7856 function onMouseMove ( e ) {
7957 if ( ! actItem ) return ;
80-
81- // Update visual position immediately (smoothness)
8258 if ( mode === 'move' ) {
8359 actItem . style . left = ( e . clientX - dragOffsetX ) + 'px' ;
8460 actItem . style . top = ( e . clientY - dragOffsetY ) + 'px' ;
8561 }
86-
87- // Throttle logic with RAF
8862 if ( updateFrame ) return ;
89-
9063 updateFrame = requestAnimationFrame ( ( ) => {
9164 try {
9265 if ( mode === 'move' ) handleMoveLogic ( e ) ;
9366 else if ( mode === 'resize' ) handleResizeLogic ( e ) ;
94- } catch ( err ) {
95- // LOG THE HIDDEN BUG
96- console . error ( "Critical Drag Error:" , err ) ;
97- // Emergency cleanup to prevent "stuck" state
98- onMouseUp ( ) ;
99- } finally {
100- // CRITICAL: Always release the lock
101- updateFrame = null ;
102- }
67+ } catch ( err ) { console . error ( err ) ; onMouseUp ( ) ; }
68+ finally { updateFrame = null ; }
10369 } ) ;
10470 }
10571
10672 function handleMoveLogic ( e ) {
10773 const cols = state . settings . theme . gridColumns || 10 ;
10874 const rows = state . settings . theme . gridRows || 6 ;
109-
11075 const cW = gridRect . width > 0 ? gridRect . width / cols : 100 ;
11176 const cH = gridRect . height > 0 ? gridRect . height / rows : 100 ;
112-
11377 const rawX = e . clientX - dragOffsetX - gridRect . left ;
11478 const rawY = e . clientY - dragOffsetY - gridRect . top ;
115-
11679 let nX = Math . round ( rawX / cW ) + 1 ;
11780 let nY = Math . round ( rawY / cH ) + 1 ;
118-
11981 const appId = parseInt ( actItem . dataset . id ) ;
12082 const sourceApp = state . apps . find ( a => a . id === appId ) ;
121-
12283 if ( ! sourceApp ) return ;
123-
12484 const vGrid = new VirtualGrid ( cols , rows , state . apps ) ;
12585 const result = vGrid . checkMove ( sourceApp , nX , nY ) ;
126-
12786 lastMoveResult = result ;
12887 renderGhosts ( result , sourceApp ) ;
12988 }
@@ -133,22 +92,16 @@ export function initGlobalEvents() {
13392 const rows = state . settings . theme . gridRows || 6 ;
13493 const cW = gridRect . width > 0 ? gridRect . width / cols : 100 ;
13594 const cH = gridRect . height > 0 ? gridRect . height / rows : 100 ;
136-
13795 const deltaX = Math . round ( ( e . clientX - initResizeX ) / cW ) ;
13896 const deltaY = Math . round ( ( e . clientY - initResizeY ) / cH ) ;
139-
14097 let newCols = Math . max ( 1 , startCols + deltaX ) ;
14198 let newRows = Math . max ( 1 , startRows + deltaY ) ;
142-
14399 const x = parseInt ( actItem . dataset . x ) ;
144100 const y = parseInt ( actItem . dataset . y ) ;
145-
146101 if ( x + newCols - 1 > cols ) newCols = cols - x + 1 ;
147102 if ( y + newRows - 1 > rows ) newRows = rows - y + 1 ;
148-
149103 const vGrid = new VirtualGrid ( cols , rows , state . apps ) ;
150104 const appId = parseInt ( actItem . dataset . id ) ;
151-
152105 if ( vGrid . isAreaFree ( x , y , newCols , newRows , appId ) ) {
153106 actItem . style . gridColumnEnd = `span ${ newCols } ` ;
154107 actItem . style . gridRowEnd = `span ${ newRows } ` ;
@@ -160,9 +113,7 @@ export function initGlobalEvents() {
160113 function renderGhosts ( result , sourceApp ) {
161114 ghosts . forEach ( g => g . remove ( ) ) ;
162115 ghosts = [ ] ;
163-
164116 const dashboard = qs ( '#dashboard' ) ;
165-
166117 const mainGhost = createGhost (
167118 result . targetX || sourceApp . x ,
168119 result . targetY || sourceApp . y ,
@@ -172,16 +123,9 @@ export function initGlobalEvents() {
172123 ) ;
173124 dashboard . appendChild ( mainGhost ) ;
174125 ghosts . push ( mainGhost ) ;
175-
176126 if ( result . possible && result . displaced && result . displaced . length > 0 ) {
177127 result . displaced . forEach ( disp => {
178- const g = createGhost (
179- disp . nx ,
180- disp . ny ,
181- disp . app . cols ,
182- disp . app . rows ,
183- 'displaced'
184- ) ;
128+ const g = createGhost ( disp . nx , disp . ny , disp . app . cols , disp . app . rows , 'displaced' ) ;
185129 dashboard . appendChild ( g ) ;
186130 ghosts . push ( g ) ;
187131 } ) ;
@@ -198,24 +142,14 @@ export function initGlobalEvents() {
198142 function onMouseUp ( ) {
199143 document . removeEventListener ( 'mousemove' , onMouseMove ) ;
200144 document . removeEventListener ( 'mouseup' , onMouseUp ) ;
201-
202- // Safety: Cancel any pending frame
203- if ( updateFrame ) {
204- cancelAnimationFrame ( updateFrame ) ;
205- updateFrame = null ;
206- }
207-
145+ if ( updateFrame ) { cancelAnimationFrame ( updateFrame ) ; updateFrame = null ; }
208146 ghosts . forEach ( g => g . remove ( ) ) ;
209147 ghosts = [ ] ;
210148
211149 if ( ! actItem ) return ;
212-
213150 const appId = parseInt ( actItem . dataset . id ) ;
214151 const app = state . apps . find ( a => a . id === appId ) ;
215-
216152 if ( ! app ) {
217- console . error ( "App state missing for ID" , appId ) ;
218- // Reset visual state
219153 actItem . classList . remove ( 'moving' ) ;
220154 actItem . style = '' ;
221155 if ( actItem . dataset . x ) applyGridPosition ( actItem , actItem . dataset . x , actItem . dataset . y , actItem . dataset . cols , actItem . dataset . rows ) ;
@@ -224,6 +158,9 @@ export function initGlobalEvents() {
224158 }
225159
226160 if ( mode === 'move' ) {
161+ // CAPTURE DROP POSITION BEFORE RESET
162+ const dropRect = actItem . getBoundingClientRect ( ) ;
163+
227164 actItem . classList . remove ( 'moving' ) ;
228165 actItem . style . position = '' ;
229166 actItem . style . width = '' ;
@@ -235,18 +172,15 @@ export function initGlobalEvents() {
235172 if ( lastMoveResult && lastMoveResult . possible ) {
236173 app . x = lastMoveResult . targetX ;
237174 app . y = lastMoveResult . targetY ;
238-
239175 if ( lastMoveResult . displaced ) {
240176 lastMoveResult . displaced . forEach ( disp => {
241177 const target = state . apps . find ( a => a . id === disp . app . id ) ;
242- if ( target ) {
243- target . x = disp . nx ;
244- target . y = disp . ny ;
245- }
178+ if ( target ) { target . x = disp . nx ; target . y = disp . ny ; }
246179 } ) ;
247180 }
248181 saveGridState ( ) ;
249- renderGrid ( ) ;
182+ // PASS DROP RECT TO RENDERER FOR ANIMATION
183+ renderGrid ( { id : app . id , rect : dropRect } ) ;
250184 } else {
251185 applyGridPosition ( actItem , app . x , app . y , app . cols , app . rows ) ;
252186 renderGrid ( ) ;
@@ -272,15 +206,13 @@ export function initGlobalEvents() {
272206 }
273207}
274208
275- // Basic toggle
276209export function toggleEditMode ( ) {
277210 const isEdit = ! state . ui . editMode ;
278211 setState ( 'ui.editMode' , isEdit ) ;
279212 const dashboard = qs ( '#dashboard' ) ;
280213 const editBtn = qs ( '#editBtn' ) ;
281214 const addBtn = qs ( '#addBtn' ) ;
282215 const clearBtn = qs ( '#clearBtn' ) ;
283-
284216 if ( isEdit ) {
285217 dashboard . classList . add ( 'edit-mode' ) ;
286218 editBtn . innerHTML = '<i class="fa-solid fa-floppy-disk"></i>' ;
0 commit comments