diff --git a/src/common/fileProcessor.ts b/src/common/fileProcessor.ts index c5fe4bf5..db269efb 100644 --- a/src/common/fileProcessor.ts +++ b/src/common/fileProcessor.ts @@ -61,7 +61,7 @@ export interface ComboOptions { } export interface FileProcessorOptions { - filesPath: string; + filesPath: string | string[]; renameFiles: boolean; findComboOption?: FindComboOption; includeSubFolders?: boolean; @@ -145,15 +145,21 @@ export class FileProcessor { const patterns = [`**/*${SLP_FILE_EXT}`]; const options = { absolute: true, - cwd: opts.filesPath, + cwd: "", onlyFiles: true, deep: opts.includeSubFolders ? undefined : 1, // We occasionally get EPERM errors when globbing in directories we don't have access to. suppressErrors: true, }; + let entries = []; + if (typeof opts.filesPath !== "string") { + entries = opts.filesPath; + } else { + options.cwd = opts.filesPath; + entries = await fg(patterns, options); + } let filesProcessed = 0; - const entries = await fg(patterns, options); for (const [i, fn] of entries.entries()) { // Coerce slashes to match operating system. By default fast glob returns unix style paths. const filename = path.resolve(fn); diff --git a/src/renderer/components/FileInput.tsx b/src/renderer/components/FileInput.tsx index 52d4c447..dbc0af09 100644 --- a/src/renderer/components/FileInput.tsx +++ b/src/renderer/components/FileInput.tsx @@ -43,11 +43,9 @@ export const FileInput: React.FC = (props) => { p = await getFolderPath(); } else { // Handle file selection - let options: any; + const options = {}; if (fileTypeFilters) { - options = { - filters: fileTypeFilters, - }; + options["filters"] = fileTypeFilters; } const filePaths = await getFilePath(options, saveFile); if (filePaths && filePaths.length > 0) { @@ -81,3 +79,53 @@ export const FileInput: React.FC = (props) => { ); }; + +interface MultiFileInputProps extends Record { + value: string[]; + onChange: (value: string[]) => void; + fileTypeFilters?: Array<{ name: string; extensions: string[] }>; +} + +export const MultiFileInput: React.FC = (props) => { + const { value, onChange, fileTypeFilters, placeholder } = props; + const [filesPath, setFilesPath] = React.useState(value); + + React.useEffect(() => { + setFilesPath(value); + }, [value]); + + const selectFromFileSystem = async () => { + const options = {}; + if (fileTypeFilters) { + options["filters"] = fileTypeFilters; + } + + options["properties"] = ["multiSelections", "openFile"]; + + const filePaths = await getFilePath(options, false); + if (filePaths) { + setFilesPath(filePaths); + onChange(filePaths); + } + }; + const actionLabel = "Choose"; + return ( + + openFileOrParentFolder(filesPath[0])} disabled={filesPath.length != 1}> + + + + + } + value={filesPath} + onChange={(_: any, { value }: any) => setFilesPath(value)} + onBlur={() => onChange(filesPath)} + action={} + placeholder={placeholder} + /> + + ); +}; diff --git a/src/renderer/containers/ComboFinder.tsx b/src/renderer/containers/ComboFinder.tsx index 487d6623..6e58a414 100644 --- a/src/renderer/containers/ComboFinder.tsx +++ b/src/renderer/containers/ComboFinder.tsx @@ -1,9 +1,9 @@ import * as React from "react"; import { useDispatch, useSelector } from "react-redux"; -import { Checkbox, Form } from "semantic-ui-react"; +import { Checkbox, Form, Radio } from "semantic-ui-react"; -import { FileInput } from "@/components/FileInput"; +import { FileInput, MultiFileInput } from "@/components/FileInput"; import { ProcessSection } from "@/components/ProcessSection"; import { Field, FormContainer, Label } from "@/components/Form"; @@ -21,6 +21,7 @@ export const ComboFinder: React.FC = () => { renameFiles, findCombos, renameFormat, + processDirectory, } = useSelector((state: iRootState) => state.highlights); const { filesPath, combosFilePath } = useSelector((state: iRootState) => state.filesystem); const dispatch = useDispatch(); @@ -35,20 +36,45 @@ export const ComboFinder: React.FC = () => { console.log("setting combos path to: " + filepath); dispatch.filesystem.setCombosFilePath(filepath); }; - const setFilesPath = (p: string) => dispatch.filesystem.setFilesPath(p); + const setFilesPath = (p: string[] | string) => dispatch.filesystem.setFilesPath(p); + const setProcessDirectory = (checked: boolean) => dispatch.highlights.setProcessDirectory(checked); + const fileFilters = [{ name: "Slippi Replays", extensions: ["slp"] }]; + const fileSelector = processDirectory ? ( + + ) : ( + + ); return (
- -
- -
+ +
{fileSelector}
+ + setProcessDirectory(Boolean(data.checked))} + /> + + + setProcessDirectory(!Boolean(data.checked))} + /> + onSubfolder(Boolean(data.checked))} + disabled={!processDirectory} />
diff --git a/src/renderer/lib/utils.ts b/src/renderer/lib/utils.ts index e8aaace8..65b2c98a 100644 --- a/src/renderer/lib/utils.ts +++ b/src/renderer/lib/utils.ts @@ -20,7 +20,7 @@ const fileOptions = { properties: ["openFile"], }; -export const getFolderPath = async (options?: any): Promise => { +export const getFolderPath = async (options?: Record): Promise => { const dialogOptions = options ? options : folderOptions; const paths = await getFilePath(dialogOptions); if (paths && paths.length > 0) { @@ -29,7 +29,7 @@ export const getFolderPath = async (options?: any): Promise => { return null; }; -export const getFilePath = async (options?: any, save?: boolean): Promise => { +export const getFilePath = async (options?: Record, save?: boolean): Promise => { const dialogOptions = options ? options : fileOptions; try { const p = await ipc.sendSyncWithTimeout( diff --git a/src/renderer/store/models/filesystem.ts b/src/renderer/store/models/filesystem.ts index 11dfc1ca..28b666fc 100644 --- a/src/renderer/store/models/filesystem.ts +++ b/src/renderer/store/models/filesystem.ts @@ -11,7 +11,7 @@ import { getFilePath } from "@/lib/utils"; const homeDirectory = remote.app.getPath("home"); export interface FileSystemState { - filesPath: string; + filesPath: string | string[]; liveSlpFilesPath: string; combosFilePath: string; meleeIsoPath: string; @@ -53,7 +53,7 @@ export const filesystem = createModel({ produce(state, (draft) => { draft.liveSlpFilesPath = payload; }), - setFilesPath: (state: FileSystemState, payload: string): FileSystemState => + setFilesPath: (state: FileSystemState, payload: string | string[]): FileSystemState => produce(state, (draft) => { draft.filesPath = payload; }), diff --git a/src/renderer/store/models/highlights.ts b/src/renderer/store/models/highlights.ts index 60b092c2..5873ff6a 100644 --- a/src/renderer/store/models/highlights.ts +++ b/src/renderer/store/models/highlights.ts @@ -15,6 +15,7 @@ export interface HighlightState { renameFiles: boolean; renameFormat: string; openCombosWhenDone: boolean; + processDirectory: boolean; } export const highlightInitialState: HighlightState = { @@ -26,6 +27,7 @@ export const highlightInitialState: HighlightState = { renameFiles: false, renameFormat: defaultRenameFormat, openCombosWhenDone: false, + processDirectory: true, }; export const highlights = createModel({ @@ -63,5 +65,9 @@ export const highlights = createModel({ produce(state, (draft) => { draft.openCombosWhenDone = payload; }), + setProcessDirectory: (state: HighlightState, payload: boolean): HighlightState => + produce(state, (draft) => { + draft.processDirectory = payload; + }), }, });