Add folder browser to path filter field (#3570)

This commit is contained in:
WithoutPants
2023-03-22 11:17:31 +11:00
committed by GitHub
parent cf0e7a4574
commit b1608128d6
8 changed files with 95 additions and 23 deletions

View File

@@ -12,6 +12,7 @@ import {
DateCriterion, DateCriterion,
TimestampCriterion, TimestampCriterion,
BooleanCriterion, BooleanCriterion,
PathCriterionOption,
} from "src/models/list-filter/criteria/criterion"; } from "src/models/list-filter/criteria/criterion";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { import {
@@ -36,6 +37,7 @@ import { RatingCriterion } from "../../models/list-filter/criteria/rating";
import { RatingFilter } from "./Filters/RatingFilter"; import { RatingFilter } from "./Filters/RatingFilter";
import { BooleanFilter } from "./Filters/BooleanFilter"; import { BooleanFilter } from "./Filters/BooleanFilter";
import { OptionsListFilter } from "./Filters/OptionsListFilter"; import { OptionsListFilter } from "./Filters/OptionsListFilter";
import { PathFilter } from "./Filters/PathFilter";
interface IGenericCriterionEditor { interface IGenericCriterionEditor {
criterion: Criterion<CriterionValue>; criterion: Criterion<CriterionValue>;
@@ -137,6 +139,11 @@ const GenericCriterionEditor: React.FC<IGenericCriterionEditor> = ({
// <OptionsFilter criterion={criterion} onValueChanged={onValueChanged} /> // <OptionsFilter criterion={criterion} onValueChanged={onValueChanged} />
// ); // );
} }
if (criterion.criterionOption instanceof PathCriterionOption) {
return (
<PathFilter criterion={criterion} onValueChanged={onValueChanged} />
);
}
if (criterion instanceof DurationCriterion) { if (criterion instanceof DurationCriterion) {
return ( return (
<DurationFilter criterion={criterion} onValueChanged={onValueChanged} /> <DurationFilter criterion={criterion} onValueChanged={onValueChanged} />

View File

@@ -0,0 +1,32 @@
import React from "react";
import { Form } from "react-bootstrap";
import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
import { ConfigurationContext } from "src/hooks/Config";
import {
Criterion,
CriterionValue,
} from "../../../models/list-filter/criteria/criterion";
interface IInputFilterProps {
criterion: Criterion<CriterionValue>;
onValueChanged: (value: string) => void;
}
export const PathFilter: React.FC<IInputFilterProps> = ({
criterion,
onValueChanged,
}) => {
const { configuration } = React.useContext(ConfigurationContext);
const libraryPaths = configuration?.general.stashes.map((s) => s.path);
return (
<Form.Group>
<FolderSelect
currentDirectory={criterion.value ? criterion.value.toString() : ""}
setCurrentDirectory={(v) => onValueChanged(v)}
collapsible
defaultDirectories={libraryPaths}
/>
</Form.Group>
);
};

View File

@@ -1,10 +1,10 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { Button, InputGroup, Form } from "react-bootstrap"; import { Button, InputGroup, Form, Collapse } from "react-bootstrap";
import { Icon } from "../Icon"; import { Icon } from "../Icon";
import { LoadingIndicator } from "../LoadingIndicator"; import { LoadingIndicator } from "../LoadingIndicator";
import { useDirectory } from "src/core/StashService"; import { useDirectory } from "src/core/StashService";
import { faTimes } from "@fortawesome/free-solid-svg-icons"; import { faEllipsis, faTimes } from "@fortawesome/free-solid-svg-icons";
import { useDebouncedSetState } from "src/hooks/debounce"; import { useDebouncedSetState } from "src/hooks/debounce";
interface IProps { interface IProps {
@@ -12,6 +12,7 @@ interface IProps {
setCurrentDirectory: (value: string) => void; setCurrentDirectory: (value: string) => void;
defaultDirectories?: string[]; defaultDirectories?: string[];
appendButton?: JSX.Element; appendButton?: JSX.Element;
collapsible?: boolean;
} }
export const FolderSelect: React.FC<IProps> = ({ export const FolderSelect: React.FC<IProps> = ({
@@ -19,7 +20,9 @@ export const FolderSelect: React.FC<IProps> = ({
setCurrentDirectory, setCurrentDirectory,
defaultDirectories, defaultDirectories,
appendButton, appendButton,
collapsible = false,
}) => { }) => {
const [showBrowser, setShowBrowser] = React.useState(false);
const [directory, setDirectory] = useState(currentDirectory); const [directory, setDirectory] = useState(currentDirectory);
const { data, error, loading } = useDirectory(directory); const { data, error, loading } = useDirectory(directory);
const intl = useIntl(); const intl = useIntl();
@@ -31,9 +34,10 @@ export const FolderSelect: React.FC<IProps> = ({
const debouncedSetDirectory = useDebouncedSetState(setDirectory, 250); const debouncedSetDirectory = useDebouncedSetState(setDirectory, 250);
useEffect(() => { useEffect(() => {
if (currentDirectory === "" && !defaultDirectories && data?.directory.path) if (currentDirectory !== directory) {
setCurrentDirectory(data.directory.path); debouncedSetDirectory(currentDirectory);
}, [currentDirectory, setCurrentDirectory, data, defaultDirectories]); }
}, [currentDirectory, directory, debouncedSetDirectory]);
function setInstant(value: string) { function setInstant(value: string) {
setCurrentDirectory(value); setCurrentDirectory(value);
@@ -66,6 +70,7 @@ export const FolderSelect: React.FC<IProps> = ({
<> <>
<InputGroup> <InputGroup>
<Form.Control <Form.Control
className="btn-secondary"
placeholder={intl.formatMessage({ id: "setup.folder.file_path" })} placeholder={intl.formatMessage({ id: "setup.folder.file_path" })}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setDebounced(e.currentTarget.value); setDebounced(e.currentTarget.value);
@@ -76,6 +81,16 @@ export const FolderSelect: React.FC<IProps> = ({
{appendButton ? ( {appendButton ? (
<InputGroup.Append>{appendButton}</InputGroup.Append> <InputGroup.Append>{appendButton}</InputGroup.Append>
) : undefined} ) : undefined}
{collapsible ? (
<InputGroup.Append>
<Button
variant="secondary"
onClick={() => setShowBrowser(!showBrowser)}
>
<Icon icon={faEllipsis} />
</Button>
</InputGroup.Append>
) : undefined}
{!data || !data.directory || loading ? ( {!data || !data.directory || loading ? (
<InputGroup.Append className="align-self-center"> <InputGroup.Append className="align-self-center">
{loading ? ( {loading ? (
@@ -89,18 +104,20 @@ export const FolderSelect: React.FC<IProps> = ({
{error !== undefined && ( {error !== undefined && (
<h5 className="mt-4 text-break">Error: {error.message}</h5> <h5 className="mt-4 text-break">Error: {error.message}</h5>
)} )}
<ul className="folder-list"> <Collapse in={!collapsible || showBrowser}>
{topDirectory} <ul className="folder-list">
{selectableDirectories.map((path) => { {topDirectory}
return ( {selectableDirectories.map((path) => {
<li key={path} className="folder-list-item"> return (
<Button variant="link" onClick={() => setInstant(path)}> <li key={path} className="folder-list-item">
{path} <Button variant="link" onClick={() => setInstant(path)}>
</Button> {path}
</li> </Button>
); </li>
})} );
</ul> })}
</ul>
</Collapse>
</> </>
); );
}; };

View File

@@ -279,6 +279,20 @@ export function createMandatoryStringCriterionOption(
); );
} }
export class PathCriterionOption extends StringCriterionOption {}
export function createPathCriterionOption(
value: CriterionType,
messageID?: string,
parameterName?: string
) {
return new PathCriterionOption(
messageID ?? value,
value,
parameterName ?? messageID ?? value
);
}
export class BooleanCriterionOption extends CriterionOption { export class BooleanCriterionOption extends CriterionOption {
constructor(messageID: string, value: CriterionType, parameterName?: string) { constructor(messageID: string, value: CriterionType, parameterName?: string) {
super({ super({

View File

@@ -15,6 +15,7 @@ import {
DateCriterionOption, DateCriterionOption,
TimestampCriterion, TimestampCriterion,
MandatoryTimestampCriterionOption, MandatoryTimestampCriterionOption,
PathCriterionOption,
} from "./criterion"; } from "./criterion";
import { OrganizedCriterion } from "./organized"; import { OrganizedCriterion } from "./organized";
import { FavoriteCriterion, PerformerFavoriteCriterion } from "./favorite"; import { FavoriteCriterion, PerformerFavoriteCriterion } from "./favorite";
@@ -65,9 +66,7 @@ export function makeCriteria(
return new NoneCriterion(); return new NoneCriterion();
case "name": case "name":
case "path": case "path":
return new StringCriterion( return new StringCriterion(new PathCriterionOption(type, type));
new MandatoryStringCriterionOption(type, type)
);
case "checksum": case "checksum":
return new StringCriterion( return new StringCriterion(
new MandatoryStringCriterionOption("media_info.checksum", type, type) new MandatoryStringCriterionOption("media_info.checksum", type, type)

View File

@@ -4,6 +4,7 @@ import {
NullNumberCriterionOption, NullNumberCriterionOption,
createDateCriterionOption, createDateCriterionOption,
createMandatoryTimestampCriterionOption, createMandatoryTimestampCriterionOption,
createPathCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { PerformerFavoriteCriterionOption } from "./criteria/favorite"; import { PerformerFavoriteCriterionOption } from "./criteria/favorite";
import { GalleryIsMissingCriterionOption } from "./criteria/is-missing"; import { GalleryIsMissingCriterionOption } from "./criteria/is-missing";
@@ -43,7 +44,7 @@ const displayModeOptions = [
const criterionOptions = [ const criterionOptions = [
createStringCriterionOption("title"), createStringCriterionOption("title"),
createStringCriterionOption("details"), createStringCriterionOption("details"),
createStringCriterionOption("path"), createPathCriterionOption("path"),
createStringCriterionOption( createStringCriterionOption(
"galleryChecksum", "galleryChecksum",
"media_info.checksum", "media_info.checksum",

View File

@@ -5,6 +5,7 @@ import {
NullNumberCriterionOption, NullNumberCriterionOption,
createMandatoryTimestampCriterionOption, createMandatoryTimestampCriterionOption,
createDateCriterionOption, createDateCriterionOption,
createPathCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { PerformerFavoriteCriterionOption } from "./criteria/favorite"; import { PerformerFavoriteCriterionOption } from "./criteria/favorite";
import { ImageIsMissingCriterionOption } from "./criteria/is-missing"; import { ImageIsMissingCriterionOption } from "./criteria/is-missing";
@@ -33,7 +34,7 @@ const displayModeOptions = [DisplayMode.Grid, DisplayMode.Wall];
const criterionOptions = [ const criterionOptions = [
createStringCriterionOption("title"), createStringCriterionOption("title"),
createMandatoryStringCriterionOption("checksum", "media_info.checksum"), createMandatoryStringCriterionOption("checksum", "media_info.checksum"),
createMandatoryStringCriterionOption("path"), createPathCriterionOption("path"),
OrganizedCriterionOption, OrganizedCriterionOption,
createMandatoryNumberCriterionOption("o_counter"), createMandatoryNumberCriterionOption("o_counter"),
ResolutionCriterionOption, ResolutionCriterionOption,

View File

@@ -5,6 +5,7 @@ import {
NullNumberCriterionOption, NullNumberCriterionOption,
createDateCriterionOption, createDateCriterionOption,
createMandatoryTimestampCriterionOption, createMandatoryTimestampCriterionOption,
createPathCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { HasMarkersCriterionOption } from "./criteria/has-markers"; import { HasMarkersCriterionOption } from "./criteria/has-markers";
import { SceneIsMissingCriterionOption } from "./criteria/is-missing"; import { SceneIsMissingCriterionOption } from "./criteria/is-missing";
@@ -59,7 +60,7 @@ const displayModeOptions = [
const criterionOptions = [ const criterionOptions = [
createStringCriterionOption("title"), createStringCriterionOption("title"),
createStringCriterionOption("scene_code"), createStringCriterionOption("scene_code"),
createMandatoryStringCriterionOption("path"), createPathCriterionOption("path"),
createStringCriterionOption("details"), createStringCriterionOption("details"),
createStringCriterionOption("director"), createStringCriterionOption("director"),
createMandatoryStringCriterionOption("oshash", "media_info.hash"), createMandatoryStringCriterionOption("oshash", "media_info.hash"),