From b5b041fdee3cdb3576cd4d1c77580f57da0b6435 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Fri, 9 Sep 2022 16:25:52 -0400 Subject: add file picker, add library file picker patch, bump lib, logger tweaks --- .../src/components/modals/filepicker/index.tsx | 159 +++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 frontend/src/components/modals/filepicker/index.tsx (limited to 'frontend/src/components/modals/filepicker/index.tsx') diff --git a/frontend/src/components/modals/filepicker/index.tsx b/frontend/src/components/modals/filepicker/index.tsx new file mode 100644 index 00000000..0847bd14 --- /dev/null +++ b/frontend/src/components/modals/filepicker/index.tsx @@ -0,0 +1,159 @@ +import { DialogButton, Focusable, SteamSpinner, TextField } from 'decky-frontend-lib'; +import { useEffect } from 'react'; +import { FunctionComponent, useState } from 'react'; +import { FileIcon, defaultStyles } from 'react-file-icon'; +import { FaArrowUp, FaFolder } from 'react-icons/fa'; + +import Logger from '../../../logger'; +import { styleDefObj } from './iconCustomizations'; + +const logger = new Logger('FilePicker'); + +export interface FilePickerProps { + startPath: string; + includeFiles?: boolean; + regex?: RegExp; + onSubmit: (val: { path: string; realpath: string }) => void; + closeModal?: () => void; +} + +interface File { + isdir: boolean; + name: string; + realpath: string; +} + +interface FileListing { + realpath: string; + files: File[]; +} + +function getList( + path: string, + includeFiles: boolean = true, +): Promise<{ result: FileListing | string; success: boolean }> { + return window.DeckyPluginLoader.callServerMethod('filepicker_ls', { path, include_files: includeFiles }); +} + +const iconStyles = { + paddingRight: '10px', + width: '1em', +}; + +const FilePicker: FunctionComponent = ({ + startPath, + includeFiles = true, + regex, + onSubmit, + closeModal, +}) => { + if (startPath.endsWith('/')) startPath = startPath.substring(0, startPath.length - 1); // remove trailing path + const [path, setPath] = useState(startPath); + const [listing, setListing] = useState({ files: [], realpath: path }); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + (async () => { + if (error) setError(null); + setLoading(true); + const listing = await getList(path, includeFiles); + if (!listing.success) { + setListing({ files: [], realpath: path }); + setLoading(false); + setError(listing.result as string); + logger.error(listing.result); + return; + } + setLoading(false); + setListing(listing.result as FileListing); + logger.log('reloaded', path, listing); + })(); + }, [path]); + + return ( +
+ + { + const newPathArr = path.split('/'); + newPathArr.pop(); + const newPath = newPathArr.join('/'); + setPath(newPath); + }} + > + + +
+ { + e.target.value && setPath(e.target.value); + }} + style={{ height: '100%' }} + /> +
+
+ + {loading && } + {!loading && + listing.files + .filter((file) => (includeFiles || file.isdir) && (!regex || regex.test(file.name))) + .map((file) => { + let extension = file.realpath.split('.').pop() as string; + return ( + { + const fullPath = `${path}/${file.name}`; + if (file.isdir) setPath(fullPath); + else { + onSubmit({ path: fullPath, realpath: file.realpath }); + closeModal?.(); + } + }} + > +
+ {file.isdir ? ( + + ) : ( +
+ {file.realpath.includes('.') ? ( + + ) : ( + + )} +
+ )} + {file.name} +
+
+ ); + })} + {error} +
+ {!loading && !error && !includeFiles && ( + { + onSubmit({ path, realpath: listing.realpath }); + closeModal?.(); + }} + > + Use this folder + + )} +
+ ); +}; + +export default FilePicker; -- cgit v1.2.3