Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/common/fileProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export interface ComboOptions {
}

export interface FileProcessorOptions {
filesPath: string;
filesPath: string | string[];
renameFiles: boolean;
findComboOption?: FindComboOption;
includeSubFolders?: boolean;
Expand Down Expand Up @@ -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);
Expand Down
56 changes: 52 additions & 4 deletions src/renderer/components/FileInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,9 @@ export const FileInput: React.FC<FileInputProps> = (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) {
Expand Down Expand Up @@ -81,3 +79,53 @@ export const FileInput: React.FC<FileInputProps> = (props) => {
</Outer>
);
};

interface MultiFileInputProps extends Record<string, any> {
value: string[];
onChange: (value: string[]) => void;
fileTypeFilters?: Array<{ name: string; extensions: string[] }>;
}

export const MultiFileInput: React.FC<MultiFileInputProps> = (props) => {
const { value, onChange, fileTypeFilters, placeholder } = props;
const [filesPath, setFilesPath] = React.useState<string[]>(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 (
<Outer>
<Input
style={{ width: "100%" }}
label={
<Button onClick={() => openFileOrParentFolder(filesPath[0])} disabled={filesPath.length != 1}>
<Labelled title="Open location">
<NoMarginIcon name="folder open outline" />
</Labelled>
</Button>
}
value={filesPath}
onChange={(_: any, { value }: any) => setFilesPath(value)}
onBlur={() => onChange(filesPath)}
action={<Button onClick={() => selectFromFileSystem().catch(console.error)}>{actionLabel}</Button>}
placeholder={placeholder}
/>
</Outer>
);
};
40 changes: 33 additions & 7 deletions src/renderer/containers/ComboFinder.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<Dispatch>();
Expand All @@ -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 ? (
<FileInput
value={filesPath as string}
onChange={setFilesPath}
directory={processDirectory}
fileTypeFilters={fileFilters}
/>
) : (
<MultiFileInput value={filesPath as string[]} onChange={setFilesPath} fileTypeFilters={fileFilters} />
);
return (
<FormContainer>
<Form>
<Field>
<Label>SLP Replay Directory</Label>
<div style={{ marginBottom: "10px" }}>
<FileInput value={filesPath} onChange={setFilesPath} directory={true} />
</div>
<Label>SLP Replay(s)</Label>
<div style={{ marginBottom: "10px" }}>{fileSelector}</div>
<Form.Field>
<Radio
label="Select entire directory"
checked={processDirectory}
onChange={(_, data) => setProcessDirectory(Boolean(data.checked))}
/>
</Form.Field>
<Form.Field>
<Radio
label="Select by individual files"
checked={!processDirectory}
onChange={(_, data) => setProcessDirectory(!Boolean(data.checked))}
/>
</Form.Field>
<Form.Field>
<Checkbox
label="Include subfolders"
checked={includeSubFolders}
onChange={(_, data) => onSubfolder(Boolean(data.checked))}
disabled={!processDirectory}
/>
</Form.Field>
</Field>
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const fileOptions = {
properties: ["openFile"],
};

export const getFolderPath = async (options?: any): Promise<string | null> => {
export const getFolderPath = async (options?: Record<string, unknown>): Promise<string | null> => {
const dialogOptions = options ? options : folderOptions;
const paths = await getFilePath(dialogOptions);
if (paths && paths.length > 0) {
Expand All @@ -29,7 +29,7 @@ export const getFolderPath = async (options?: any): Promise<string | null> => {
return null;
};

export const getFilePath = async (options?: any, save?: boolean): Promise<string[] | null> => {
export const getFilePath = async (options?: Record<string, unknown>, save?: boolean): Promise<string[] | null> => {
const dialogOptions = options ? options : fileOptions;
try {
const p = await ipc.sendSyncWithTimeout(
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/store/models/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}),
Expand Down
6 changes: 6 additions & 0 deletions src/renderer/store/models/highlights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface HighlightState {
renameFiles: boolean;
renameFormat: string;
openCombosWhenDone: boolean;
processDirectory: boolean;
}

export const highlightInitialState: HighlightState = {
Expand All @@ -26,6 +27,7 @@ export const highlightInitialState: HighlightState = {
renameFiles: false,
renameFormat: defaultRenameFormat,
openCombosWhenDone: false,
processDirectory: true,
};

export const highlights = createModel({
Expand Down Expand Up @@ -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;
}),
},
});