import { platform } from "@yaakapp-internal/platform"; import { Icon, VStack } from "@yaakapp-internal/ui"; import classNames from "classnames"; import { useEffect, useRef, useState } from "react"; import { useLocalStorage } from "react-use"; import { CommercialUseBanner } from "./CommercialUseBanner"; import { Button } from "./core/Button"; import { PlainInput } from "./core/PlainInput"; interface Props { importFile: (filePath: string) => Promise; importUrl: (url: string) => Promise; } /** * An absolute or relative path is unambiguously a file. Everything else is treated as a URL, so a * bare host like `example.com/openapi.json` still works (the backend defaults it to https). */ function isFilePath(value: string): boolean { return ( value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value.startsWith("~/") || value.startsWith("\\\\") || /^[a-zA-Z]:[\\/]/.test(value) ); } function fileName(path: string): string { return path.split(/[/\\]/).at(-1) || path; } export function ImportDataDialog({ importFile, importUrl }: Props) { const [isLoading, setIsLoading] = useState(false); // A file path or a URL. Both inputs write here, so there is only ever one thing to import const [source, setSource] = useLocalStorage("importPathOrUrl", null); const [forceUpdateKey, setForceUpdateKey] = useState(0); const [isHovering, setIsHovering] = useState(false); const ref = useRef(null); const trimmedSource = source?.trim() ?? ""; const filePath = isFilePath(trimmedSource) ? trimmedSource : null; const selectSource = (value: string) => { setSource(value); // Remount the input so it shows the path of the newly-picked file setForceUpdateKey((k) => k + 1); }; // Accept a file dropped anywhere on the dialog, the way SelectFile does for its button useEffect(() => { return platform.window.onDragDrop((event) => { if (event.type === "over") { const p = event.position; const r = ref.current?.getBoundingClientRect(); if (r == null) return; setIsHovering(p.x >= r.left && p.x <= r.right && p.y >= r.top && p.y <= r.bottom); } else if (event.type === "drop" && isHovering) { const p = event.paths[0]; if (p) selectSource(p); setIsHovering(false); } else { setIsHovering(false); } }); }, [isHovering, setSource]); const handleSelectFile = async () => { const selected = await platform.dialog.open({ title: "Select File", multiple: false }); if (selected == null) return; selectSource(selected); }; const handleImport = async () => { setIsLoading(true); try { if (filePath != null) { await importFile(filePath); } else { await importUrl(trimmedSource); } } finally { setIsLoading(false); } }; return ( ); }