11import { useState , useRef , useCallback } from "react" ;
22import { generateId } from "../utils/generateId" ;
3+ import { screenBounds } from "../utils/canvasMath" ;
4+ import { filenameToScreenName , gridPositions , resolveOverlaps } from "../utils/dropImport" ;
35import {
46 DEFAULT_SCREEN_WIDTH ,
57 CENTER_HEIGHT_ESTIMATE ,
@@ -14,6 +16,7 @@ import {
1416 VIEWPORT_FALLBACK_HEIGHT ,
1517 DEFAULT_STATE_NAME ,
1618 SCREEN_NAME_TEMPLATE ,
19+ HEADER_HEIGHT ,
1720} from "../constants" ;
1821
1922export function useScreenManager ( pan , zoom , canvasRef ) {
@@ -163,6 +166,35 @@ export function useScreenManager(pan, zoom, canvasRef) {
163166 setSelectedScreen ( newScreen . id ) ;
164167 } , [ screens , connections , documents , pushHistory , pan , zoom , canvasRef ] ) ;
165168
169+ const addScreensBatch = useCallback ( ( screenDefs ) => {
170+ if ( screenDefs . length === 0 ) return 0 ;
171+ pushHistory ( screens , connections , documents ) ;
172+ const newScreens = screenDefs . map ( ( def ) => ( {
173+ id : generateId ( ) ,
174+ name : def . name ,
175+ x : def . x ,
176+ y : def . y ,
177+ width : DEFAULT_SCREEN_WIDTH ,
178+ imageData : def . imageData ,
179+ description : "" ,
180+ notes : "" ,
181+ codeRef : "" ,
182+ status : "new" ,
183+ acceptanceCriteria : [ ] ,
184+ hotspots : [ ] ,
185+ stateGroup : null ,
186+ stateName : "" ,
187+ tbd : false ,
188+ tbdNote : "" ,
189+ roles : [ ] ,
190+ figmaSource : null ,
191+ } ) ) ;
192+ setScreens ( ( prev ) => [ ...prev , ...newScreens ] ) ;
193+ setSelectedScreen ( newScreens [ 0 ] . id ) ;
194+ screenCounter . current += newScreens . length ;
195+ return newScreens . length ;
196+ } , [ screens , connections , documents , pushHistory ] ) ;
197+
166198 const removeScreen = useCallback ( ( id ) => {
167199 pushHistory ( screens , connections , documents ) ;
168200 const removedScreen = screens . find ( ( s ) => s . id === id ) ;
@@ -325,17 +357,59 @@ export function useScreenManager(pan, zoom, canvasRef) {
325357 } ) ;
326358 } , [ addScreenAtCenter , selectedScreen , screens , assignScreenImage ] ) ;
327359
328- const handleCanvasDrop = useCallback ( ( e ) => {
360+ const handleCanvasDrop = useCallback ( ( e , worldX , worldY ) => {
329361 e . preventDefault ( ) ;
330- const files = Array . from ( e . dataTransfer . files ) . filter ( ( f ) => f . type . startsWith ( "image/" ) ) ;
331- files . forEach ( ( file ) => {
332- const reader = new FileReader ( ) ;
333- reader . onload = ( ev ) => {
334- addScreen ( ev . target . result , file . name . replace ( / \. [ ^ . ] + $ / , "" ) ) ;
335- } ;
336- reader . readAsDataURL ( file ) ;
362+ const files = Array . from ( e . dataTransfer . files ) . filter (
363+ ( f ) => f . type === "image/png" || f . type === "image/jpeg"
364+ ) ;
365+ if ( files . length === 0 ) return ;
366+
367+ Promise . all (
368+ files . map (
369+ ( file ) =>
370+ new Promise ( ( resolve ) => {
371+ const reader = new FileReader ( ) ;
372+ reader . onload = ( ev ) => {
373+ const dataUrl = ev . target . result ;
374+ const img = new Image ( ) ;
375+ img . onload = ( ) => {
376+ const renderedHeight = ( img . naturalHeight / img . naturalWidth ) * DEFAULT_SCREEN_WIDTH ;
377+ resolve ( {
378+ imageData : dataUrl ,
379+ filename : file . name ,
380+ imageHeight : renderedHeight ,
381+ } ) ;
382+ } ;
383+ img . onerror = ( ) => {
384+ resolve ( { imageData : dataUrl , filename : file . name , imageHeight : 120 } ) ;
385+ } ;
386+ img . src = dataUrl ;
387+ } ;
388+ reader . readAsDataURL ( file ) ;
389+ } )
390+ )
391+ ) . then ( ( results ) => {
392+ const itemHeights = results . map ( ( r ) => r . imageHeight + HEADER_HEIGHT ) ;
393+ const positions = gridPositions ( itemHeights , worldX , worldY ) ;
394+ const candidateRects = positions . map ( ( pos , i ) => ( {
395+ x : pos . x ,
396+ y : pos . y ,
397+ width : DEFAULT_SCREEN_WIDTH ,
398+ height : itemHeights [ i ] ,
399+ } ) ) ;
400+ const existingRects = screens . map ( ( s ) => screenBounds ( s , HEADER_HEIGHT ) ) ;
401+ const adjusted = resolveOverlaps ( candidateRects , existingRects ) ;
402+
403+ const screenDefs = results . map ( ( r , i ) => ( {
404+ imageData : r . imageData ,
405+ name : filenameToScreenName ( r . filename ) ,
406+ x : adjusted [ i ] . x ,
407+ y : adjusted [ i ] . y ,
408+ } ) ) ;
409+
410+ addScreensBatch ( screenDefs ) ;
337411 } ) ;
338- } , [ addScreen ] ) ;
412+ } , [ screens , addScreensBatch ] ) ;
339413
340414 const saveHotspot = useCallback ( ( screenId , hotspot ) => {
341415 pushHistory ( screens , connections , documents ) ;
@@ -872,6 +946,7 @@ export function useScreenManager(pan, zoom, canvasRef) {
872946 fileInputRef,
873947 addScreen,
874948 addScreenAtCenter,
949+ addScreensBatch,
875950 removeScreen,
876951 removeScreens,
877952 renameScreen,
0 commit comments