import { OrbitControls } from "@react-three/drei"; import { Canvas, useFrame } from "@react-three/fiber"; import type { FileOrFolder } from "@thorium/.server/data"; import { useShipModel } from "@thorium/components/Starmap/StarmapShip"; import { popoverClass, StyledMenuItem } from "@thorium/components/timelineBuilder/AddBlockMenu"; import { q } from "@thorium/context/AppContext"; import Button from "@thorium/ui/Button"; import { Icon } from "@thorium/ui/Icon"; import { SVGImageLoader } from "@thorium/ui/SVGImageLoader"; import { cn } from "@thorium/utils/cn"; import { playSound } from "@thorium/utils/sounds/playSound"; import { Suspense, useRef, type ReactNode } from "react"; import { Popover, Menu, SubmenuTrigger, MenuTrigger, Button as RAButton, MenuSection, Header, } from "react-aria-components"; import type { Group } from "three"; export function FilesMenu({ value, setValue, pluginId, canUpload, types, children, root, }: { value?: string; setValue?: (value: string) => void; pluginId?: string; canUpload?: boolean; types: ("models" | "sounds" | "videos" | "images" | "pdf")[]; children?: ReactNode; root?: string; }) { const extensions: string[] = []; if (types.includes("models")) { extensions.push("glb", "gltf"); } if (types.includes("videos")) { extensions.push("mov", "mp4", "ogv", "webm", "m4v"); } if (types.includes("sounds")) { extensions.push("m4a", "wav", "mp3", "ogg", "aiff", "aif"); } if (types.includes("images")) { extensions.push("svg", "jpg", "jpeg", "png", "apng", "gif", "webp", "avif"); } if (types.includes("pdf")) { extensions.push("pdf"); } const [flightFiles] = q.flight.assets.useNetRequest(); const [files] = q.thorium.pluginAssets.useNetRequest({ pluginId, extensions, }); const fileRef = useRef(null); const pickResolve = useRef<(value: File | Promise | null) => void>(() => {}); async function pickFile() { const pickPromise = new Promise((res) => { pickResolve.current = res; }); fileRef.current?.click(); return await pickPromise; } return ( <> {children || ( {value || "Pick File"} )} {flightFiles ? (
Flight
file.name === root)?.contents || flightFiles : flightFiles } onAction={(path) => setValue?.(path)} pickFile={canUpload ? pickFile : undefined} path={ root ? flightFiles.find((file) => file.name === root) ? `${root}/` : undefined : undefined } />
) : null} {Object.entries(files).map(([plugin, files]) => (
{plugin}
file.name === root)?.contents || files.files : files.files } onAction={(path) => setValue?.(path)} pluginId={plugin} pickFile={canUpload ? pickFile : undefined} path={ root ? files.files.find((file) => file.name === root) ? `${root}/` : undefined : undefined } />
))}
{canUpload ? ( { const file = e.target.files?.[0]; pickResolve.current?.(file || null); }} /> ) : null} ); } function NestedFilesMenu({ path = "/", files, onAction, pluginId, pickFile, }: { files: FileOrFolder[]; onAction: (path: string) => void; path?: string; pluginId?: string; pickFile?: () => Promise; }) { return ( <> {files.map((f) => f.contents ? ( {f.name} ) : ( onAction(f.fullPath)} className="justify-between gap-2" > {f.name} ), )} {pickFile ? ( { const asset = await pickFile(); if (!asset) return; if (pluginId) { await q.thorium.uploadAsset.netSend({ pluginId, assetPath: `${path}${asset.name}`, asset, }); } else { // Uploading to an active flight const { asset: assetPath } = await q.flight.uploadAsset.netSend({ assetPath: `${path}${asset.name}`, asset, }); onAction(assetPath); return; } onAction(path); }} > Upload File ) : null} ); } function FilePreview({ url }: { url: string }) { const ext1 = url.match(/\..{3,4}$/gi); const ext = ext1 ? ext1[0].replace(".", "").toLowerCase() : ""; switch (ext) { case "glb": case "gltf": return ; case "mov": case "mp4": case "ogv": case "webm": case "m4v": return