@@ -372,10 +372,23 @@ export default function Mac({
372372 function handleDrop ( event : React . DragEvent ) {
373373 event . preventDefault ( ) ;
374374 setDragCount ( 0 ) ;
375+ const emulator = emulatorRef . current ;
376+ if ( ! emulator ) {
377+ return ;
378+ }
375379 const files = [ ] ;
376380
377381 if ( event . dataTransfer . items ) {
378382 for ( const item of event . dataTransfer . items ) {
383+ const entry = item . webkitGetAsEntry ?.( ) ;
384+ if ( entry ?. isDirectory ) {
385+ uploadDirectory (
386+ emulator ,
387+ entry as FileSystemDirectoryEntry
388+ ) ;
389+ return ;
390+ }
391+
379392 if ( item . kind === "file" ) {
380393 files . push ( item . getAsFile ( ) ! ) ;
381394 } else if (
@@ -402,7 +415,7 @@ export default function Mac({
402415 }
403416 }
404417
405- uploadFiles ( files ) ;
418+ uploadFiles ( emulator , files ) ;
406419 }
407420
408421 function handleLoadFileClick ( ) {
@@ -415,8 +428,8 @@ export default function Mac({
415428 // the synthetic click event below) because we may not get a change
416429 // event if the user cancels the file picker.
417430 setDragCount ( 1 ) ;
418- if ( input . files ) {
419- uploadFiles ( Array . from ( input . files ) ) ;
431+ if ( input . files && emulatorRef . current ) {
432+ uploadFiles ( emulatorRef . current , Array . from ( input . files ) ) ;
420433 }
421434 input . remove ( ) ;
422435 // Delay removing the overlay a bit so that users have a chance to
@@ -426,29 +439,6 @@ export default function Mac({
426439 input . click ( ) ;
427440 }
428441
429- function uploadFiles ( files : File [ ] ) {
430- const emulator = emulatorRef . current ;
431- if ( ! emulator ) {
432- return ;
433- }
434-
435- let fileCount = 0 ;
436- let diskImageCount = 0 ;
437- for ( const file of files ) {
438- if ( isDiskImageFile ( file . name ) ) {
439- diskImageCount ++ ;
440- } else {
441- fileCount ++ ;
442- }
443- emulator . uploadFile ( file ) ;
444- }
445- varz . incrementMulti ( {
446- "emulator_uploads" : files . length ,
447- "emulator_uploads:files" : fileCount ,
448- "emulator_uploads:disks" : diskImageCount ,
449- } ) ;
450- }
451-
452442 function loadCDROM ( cdrom : EmulatorCDROM ) {
453443 varz . incrementMulti ( {
454444 "emulator_cdroms" : 1 ,
@@ -595,6 +585,97 @@ export default function Mac({
595585 ) ;
596586}
597587
588+ /**
589+ * Handles a full directory upload, including attempting to read resource forks
590+ * for files that have them.
591+ */
592+ function uploadDirectory (
593+ emulator : Emulator ,
594+ directoryEntry : FileSystemDirectoryEntry
595+ ) {
596+ let inProgressCount = 1 ; // we're waiting to read the starting directory
597+ const files : File [ ] = [ ] ;
598+ const names : string [ ] = [ ] ;
599+ const handleFile = ( file : File , path : string [ ] ) => {
600+ files . push ( file ) ;
601+ names . push ( path . join ( "/" ) ) ;
602+ checkDone ( ) ;
603+ } ;
604+
605+ const checkDone = ( ) => {
606+ inProgressCount -- ;
607+ if ( inProgressCount === 0 ) {
608+ uploadFiles ( emulator , files , names ) ;
609+ }
610+ } ;
611+
612+ const readDirectory = (
613+ directoryEntry : FileSystemDirectoryEntry ,
614+ path : string [ ]
615+ ) => {
616+ const directoryReader = directoryEntry . createReader ( ) ;
617+ directoryReader . readEntries ( entries => {
618+ inProgressCount -- ; // We finished reading the directory
619+ entries . forEach ( entry => {
620+ inProgressCount ++ ; //
621+ if ( entry . isDirectory ) {
622+ readDirectory ( entry as FileSystemDirectoryEntry , [
623+ ...path ,
624+ entry . name ,
625+ ] ) ;
626+ return ;
627+ }
628+ const fileEntry = entry as FileSystemFileEntry ;
629+ fileEntry . file ( file => handleFile ( file , [ ...path , entry . name ] ) ) ;
630+
631+ inProgressCount ++ ;
632+ directoryEntry . getFile (
633+ `${ entry . name } /..namedfork/rsrc` ,
634+ { } ,
635+ resourceEntry =>
636+ ( resourceEntry as FileSystemFileEntry ) . file (
637+ resourceFile =>
638+ // Assume that the emulator is using the
639+ // Basilisk II/SheepShaver ExtFS convention of a
640+ // parallel .rsrc directory for resource forks.
641+ handleFile ( resourceFile , [
642+ ...path ,
643+ ".rsrc" ,
644+ entry . name ,
645+ ] ) ,
646+ checkDone // Ignore errors, not all files have resource forks
647+ ) ,
648+ checkDone // Ignore errors, not all files have resource forks
649+ ) ;
650+ } ) ;
651+ } ) ;
652+ } ;
653+
654+ readDirectory ( directoryEntry , [ directoryEntry . name ] ) ;
655+ }
656+
657+ function uploadFiles ( emulator : Emulator , files : File [ ] , names : string [ ] = [ ] ) {
658+ let fileCount = 0 ;
659+ let diskImageCount = 0 ;
660+ for ( const file of files ) {
661+ if ( isDiskImageFile ( file . name ) ) {
662+ diskImageCount ++ ;
663+ } else {
664+ fileCount ++ ;
665+ }
666+ }
667+ const resourceForkCount = names . filter ( name =>
668+ name . includes ( "/.rsrc/" )
669+ ) . length ;
670+ emulator . uploadFiles ( files , names ) ;
671+ varz . incrementMulti ( {
672+ "emulator_uploads" : files . length ,
673+ "emulator_uploads:files" : fileCount ,
674+ "emulator_uploads:disks" : diskImageCount ,
675+ "emulator_uploads:resource_forks" : resourceForkCount ,
676+ } ) ;
677+ }
678+
598679const SMALL_BEZEL_THRESHOLD = 80 ;
599680const MEDIUM_BEZEL_THRESHOLD = 168 ;
600681
0 commit comments