66 */
77import { fg } from "@triplex/lib/fg" ;
88import { useScreenView } from "@triplex/ux" ;
9+ import { DragEvent , useRef , useState } from "react" ;
910import { preloadSubscription } from "../../hooks/ws" ;
11+ import { sendVSCE } from "../../util/bridge" ;
1012import { AIChat } from "../ai-chat" ;
1113import { FloatingControls } from "../floating-controls" ;
1214import { Panels } from "../panels" ;
15+ import { useSceneContext } from "./context" ;
1316import { Dialogs } from "./dialogs" ;
1417import { EmptyState } from "./empty-state" ;
1518import { Events } from "./events" ;
1619
1720export function AppRoot ( ) {
1821 useScreenView ( "app" , "Screen" ) ;
1922
23+ const context = useSceneContext ( ) ;
24+ const [ isDragging , setIsDragging ] = useState ( false ) ;
25+ const dropZoneRef = useRef < HTMLDivElement > ( null ) ;
26+ const handleDragOver = ( e : DragEvent < HTMLDivElement > ) => {
27+ e . preventDefault ( ) ;
28+ e . stopPropagation ( ) ;
29+ } ;
30+ const handleDragEnter = ( e : DragEvent < HTMLDivElement > ) => {
31+ e . preventDefault ( ) ;
32+ e . stopPropagation ( ) ;
33+ setIsDragging ( true ) ;
34+ } ;
35+ const handleDragLeave = ( e : DragEvent < HTMLDivElement > ) => {
36+ e . preventDefault ( ) ;
37+ e . stopPropagation ( ) ;
38+
39+ // Only set to false if leaving the drop zone itself
40+ if (
41+ e . currentTarget === dropZoneRef . current &&
42+ ! dropZoneRef . current . contains ( e . relatedTarget as Node )
43+ ) {
44+ setIsDragging ( false ) ;
45+ }
46+ } ;
47+
48+ const handleDrop = ( e : DragEvent < HTMLDivElement > ) => {
49+ e . preventDefault ( ) ;
50+ e . stopPropagation ( ) ;
51+ setIsDragging ( false ) ;
52+ const items = e . dataTransfer . items ;
53+ const fileUris : string [ ] = [ ] ;
54+
55+ for ( let i = 0 ; i < items . length ; i ++ ) {
56+ const item = items [ i ] ;
57+ if ( item . kind === "string" && item . type === "text/uri-list" ) {
58+ item . getAsString ( ( uri ) => {
59+ fileUris . push ( uri ) ;
60+ sendVSCE ( "component-insert" , {
61+ componentPath : uri ,
62+ scenePath : context . path ,
63+ } ) ;
64+ } ) ;
65+ }
66+ }
67+ } ;
68+
2069 return (
2170 < div className = "fixed inset-0 flex select-none" >
2271 < Events />
@@ -25,13 +74,53 @@ export function AppRoot() {
2574 < div className = "relative h-full w-full" >
2675 < FloatingControls />
2776 < EmptyState />
28- < iframe
29- allow = "cross-origin-isolated"
30- className = "h-full w-full"
31- data-testid = "scene"
32- id = "scene"
33- src = { `http://localhost:${ window . triplex . env . ports . client } /scene` }
34- />
77+ < div
78+ ref = { dropZoneRef }
79+ onDragOver = { handleDragOver }
80+ onDragEnter = { handleDragEnter }
81+ onDragLeave = { handleDragLeave }
82+ onDrop = { handleDrop }
83+ style = { {
84+ position : "relative" ,
85+ height : "100%" ,
86+ width : "100%" ,
87+ padding : "16px" ,
88+ } }
89+ >
90+ { isDragging && (
91+ < div
92+ style = { {
93+ border : "2px dashed #ccc" ,
94+ borderRadius : "8px" ,
95+ padding : "40px" ,
96+ textAlign : "center" ,
97+ backgroundColor : "#f5f5f5" ,
98+ transition : "all 0.3s ease" ,
99+ minHeight : "200px" ,
100+ display : "flex" ,
101+ flexDirection : "column" ,
102+ justifyContent : "center" ,
103+ alignItems : "center" ,
104+ position : "absolute" ,
105+ top : "0" ,
106+ left : "0" ,
107+ width : "100%" ,
108+ height : "100%" ,
109+ zIndex : "99" ,
110+ pointerEvents : "all" ,
111+ } }
112+ >
113+ < p > Drop Component here</ p >
114+ </ div >
115+ ) }
116+ < iframe
117+ allow = "cross-origin-isolated"
118+ className = "h-full w-full"
119+ data-testid = "scene"
120+ id = "scene"
121+ src = { `http://localhost:${ window . triplex . env . ports . client } /scene` }
122+ />
123+ </ div >
35124 </ div >
36125 { fg ( "ai_chat" ) && < AIChat /> }
37126 </ div >
0 commit comments