summaryrefslogtreecommitdiff
path: root/frontend/src/components/modals/filepicker/index.tsx
diff options
context:
space:
mode:
authorAAGaming <aa@mail.catvibers.me>2022-09-09 16:25:52 -0400
committerAAGaming <aa@mail.catvibers.me>2022-09-09 16:25:52 -0400
commitb5b041fdee3cdb3576cd4d1c77580f57da0b6435 (patch)
tree2b63ccb6410868fe4f0c6f3882614d0f7a180be4 /frontend/src/components/modals/filepicker/index.tsx
parent9d980618a78b41bc3262c5185df67ccf6076a296 (diff)
downloaddecky-loader-b5b041fdee3cdb3576cd4d1c77580f57da0b6435.tar.gz
decky-loader-b5b041fdee3cdb3576cd4d1c77580f57da0b6435.zip
add file picker, add library file picker patch, bump lib, logger tweaks
Diffstat (limited to 'frontend/src/components/modals/filepicker/index.tsx')
-rw-r--r--frontend/src/components/modals/filepicker/index.tsx159
1 files changed, 159 insertions, 0 deletions
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<FilePickerProps> = ({
+ startPath,
+ includeFiles = true,
+ regex,
+ onSubmit,
+ closeModal,
+}) => {
+ if (startPath.endsWith('/')) startPath = startPath.substring(0, startPath.length - 1); // remove trailing path
+ const [path, setPath] = useState<string>(startPath);
+ const [listing, setListing] = useState<FileListing>({ files: [], realpath: path });
+ const [error, setError] = useState<string | null>(null);
+ const [loading, setLoading] = useState<boolean>(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 (
+ <div className="deckyFilePicker">
+ <Focusable style={{ display: 'flex', flexDirection: 'row', paddingBottom: '10px' }}>
+ <DialogButton
+ style={{
+ minWidth: 'unset',
+ width: '40px',
+ flexGrow: '0',
+ borderRadius: 'unset',
+ margin: '0',
+ padding: '10px',
+ }}
+ onClick={() => {
+ const newPathArr = path.split('/');
+ newPathArr.pop();
+ const newPath = newPathArr.join('/');
+ setPath(newPath);
+ }}
+ >
+ <FaArrowUp />
+ </DialogButton>
+ <div style={{ flexGrow: '1', width: '100%' }}>
+ <TextField
+ value={path}
+ onChange={(e) => {
+ e.target.value && setPath(e.target.value);
+ }}
+ style={{ height: '100%' }}
+ />
+ </div>
+ </Focusable>
+ <Focusable style={{ display: 'flex', flexDirection: 'column', height: '60vh', overflow: 'scroll' }}>
+ {loading && <SteamSpinner style={{ height: '100%' }} />}
+ {!loading &&
+ listing.files
+ .filter((file) => (includeFiles || file.isdir) && (!regex || regex.test(file.name)))
+ .map((file) => {
+ let extension = file.realpath.split('.').pop() as string;
+ return (
+ <DialogButton
+ style={{ borderRadius: 'unset', margin: '0', padding: '10px' }}
+ onClick={() => {
+ const fullPath = `${path}/${file.name}`;
+ if (file.isdir) setPath(fullPath);
+ else {
+ onSubmit({ path: fullPath, realpath: file.realpath });
+ closeModal?.();
+ }
+ }}
+ >
+ <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'flex-start' }}>
+ {file.isdir ? (
+ <FaFolder style={iconStyles} />
+ ) : (
+ <div style={iconStyles}>
+ {file.realpath.includes('.') ? (
+ <FileIcon {...defaultStyles[extension]} {...styleDefObj[extension]} extension={''} />
+ ) : (
+ <FileIcon />
+ )}
+ </div>
+ )}
+ {file.name}
+ </div>
+ </DialogButton>
+ );
+ })}
+ {error}
+ </Focusable>
+ {!loading && !error && !includeFiles && (
+ <DialogButton
+ className="Primary"
+ style={{ marginTop: '10px', alignSelf: 'flex-end' }}
+ onClick={() => {
+ onSubmit({ path, realpath: listing.realpath });
+ closeModal?.();
+ }}
+ >
+ Use this folder
+ </DialogButton>
+ )}
+ </div>
+ );
+};
+
+export default FilePicker;