All files / client/src/hooks use-file-manager.ts

97.43% Statements 38/39
85.71% Branches 18/21
100% Functions 7/7
100% Lines 36/36

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74                      23x 23x   23x 1x     23x 2x 2x 1x 1x   1x     1x 1x 1x 1x 1x 1x 1x 1x 1x       1x 1x 1x         23x 3x 3x 2x 2x 2x 1x 1x 1x     2x 1x 1x     2x     23x                
import { useRef, useCallback, useState } from "react";
 
type FileEntry = { name: string; content: string };
 
interface UseFileManagerOptions {
  tabs?: Array<{ name: string; content: string }>;
  onFilesLoaded?: (files: FileEntry[], replaceAll: boolean) => void;
  toast?: (params: { title: string; description?: string; variant?: string }) => void;
}
 
export function useFileManager({ tabs = [], onFilesLoaded, toast }: UseFileManagerOptions = {}) {
  const fileInputRef = useRef<HTMLInputElement | null>(null);
  const [lastLoadedFiles, setLastLoadedFiles] = useState<FileEntry[] | null>(null);
 
  const onLoadFiles = useCallback(() => {
    fileInputRef.current?.click();
  }, []);
 
  const downloadAllFiles = useCallback(async (providedTabs?: Array<{ name: string; content: string }>) => {
    const which = providedTabs ?? tabs ?? [];
    if (!which || which.length === 0) {
      try {
        toast?.({ title: "Nothing to download", description: "There are no open files to download" });
      } catch {}
      return;
    }
 
    which.forEach((tab, index) => {
      setTimeout(() => {
        const element = document.createElement("a");
        element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(tab.content));
        element.setAttribute("download", tab.name);
        (element as any).style.display = "none";
        document.body.appendChild(element);
        (element as any).click();
        element.remove();
      }, index * 200);
    });
 
    setTimeout(() => {
      try {
        toast?.({ title: "Download started", description: `${which.length} file(s) will be downloaded` });
      } catch {}
    }, which.length * 200 + 100);
  }, [tabs, toast]);
 
  const handleHiddenFileInput = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
    const fl = e.target.files;
    if (!fl || fl.length === 0) return;
    const files: FileEntry[] = [];
    for (const f of Array.from(fl)) {
      if (!f.name.endsWith(".ino") && !f.name.endsWith(".h")) continue;
      try {
        const txt = await f.text();
        files.push({ name: f.name, content: txt });
      } catch {}
    }
    if (files.length > 0) {
      setLastLoadedFiles(files);
      onFilesLoaded?.(files, false);
    }
    // clear input value to allow re-upload of same file
    Iif (fileInputRef.current) fileInputRef.current.value = "";
  }, [onFilesLoaded]);
 
  return {
    fileInputRef,
    onLoadFiles,
    downloadAllFiles,
    handleHiddenFileInput,
    lastLoadedFiles,
  } as const;
}