diff --git a/ui/v2.5/package.json b/ui/v2.5/package.json
index 06244ea1a..caf9e0bdb 100644
--- a/ui/v2.5/package.json
+++ b/ui/v2.5/package.json
@@ -30,6 +30,7 @@
"react-router-bootstrap": "^0.25.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.0",
+ "react-select": "^3.0.8",
"subscriptions-transport-ws": "^0.9.16",
"video.js": "^7.6.0"
},
@@ -60,6 +61,7 @@
"@types/react-dom": "16.9.4",
"@types/react-router-bootstrap": "^0.24.5",
"@types/react-router-dom": "5.1.3",
+ "@types/react-select": "^3.0.8",
"@types/video.js": "^7.2.11",
"eslint": "^6.7.2",
"graphql-code-generator": "0.18.2",
diff --git a/ui/v2.5/src/.index.scss.swp b/ui/v2.5/src/.index.scss.swp
deleted file mode 100644
index f3efde583..000000000
Binary files a/ui/v2.5/src/.index.scss.swp and /dev/null differ
diff --git a/ui/v2.5/src/App.tsx b/ui/v2.5/src/App.tsx
index 8bf99a7a5..a5a457ea1 100755
--- a/ui/v2.5/src/App.tsx
+++ b/ui/v2.5/src/App.tsx
@@ -11,6 +11,7 @@ import { Stats } from "./components/Stats";
import Studios from "./components/Studios/Studios";
import Tags from "./components/Tags/Tags";
import { SceneFilenameParser } from "./components/scenes/SceneFilenameParser";
+import { ToastProvider } from './components/Shared/Toast';
import { library } from '@fortawesome/fontawesome-svg-core'
import { fas } from '@fortawesome/free-solid-svg-icons'
@@ -23,20 +24,22 @@ export const App: React.FC = () => (
-
-
-
-
- {/* */}
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ {/* */}
+
+
+
+
+
+
+
+
+
+
);
diff --git a/ui/v2.5/src/components/list/AddFilter.tsx b/ui/v2.5/src/components/list/AddFilter.tsx
index ee44e7cfd..8cb0237f3 100644
--- a/ui/v2.5/src/components/list/AddFilter.tsx
+++ b/ui/v2.5/src/components/list/AddFilter.tsx
@@ -10,7 +10,7 @@ import { StudiosCriterion } from "../../models/list-filter/criteria/studios";
import { TagsCriterion } from "../../models/list-filter/criteria/tags";
import { makeCriteria } from "../../models/list-filter/criteria/utils";
import { ListFilterModel } from "../../models/list-filter/filter";
-import { FilterMultiSelect } from "../select/FilterMultiSelect";
+import { FilterSelect } from "../select/FilterSelect";
interface IAddFilterProps {
onAddCriterion: (criterion: Criterion, oldId?: string) => void;
@@ -113,27 +113,24 @@ export const AddFilter: React.FC = (props: IAddFilterProps) =>
}
if (Array.isArray(criterion.value)) {
- let type: "performers" | "studios" | "tags" | "" = "";
+ let type: "performers" | "studios" | "tags";
if (criterion instanceof PerformersCriterion) {
type = "performers";
} else if (criterion instanceof StudiosCriterion) {
type = "studios";
} else if (criterion instanceof TagsCriterion) {
type = "tags";
+ } else {
+ return;
}
- if (type === "") {
- return (<>todo>);
- } else {
- return (
- criterion.value = items.map((i) => ({id: i.id, label: i.name!}))}
- openOnKeyDown={true}
- initialIds={criterion.value.map((labeled: any) => labeled.id)}
- />
- );
- }
+ return (
+ criterion.value = items.map((i) => ({id: i.id, label: i.name!}))}
+ initialIds={criterion.value.map((labeled: any) => labeled.id)}
+ />
+ );
} else {
if (criterion.options) {
defaultValue.current = criterion.value;
diff --git a/ui/v2.5/src/components/scenes/SceneDetails/SceneEditPanel.tsx b/ui/v2.5/src/components/scenes/SceneDetails/SceneEditPanel.tsx
index e446da27a..d5c223ed3 100644
--- a/ui/v2.5/src/components/scenes/SceneDetails/SceneEditPanel.tsx
+++ b/ui/v2.5/src/components/scenes/SceneDetails/SceneEditPanel.tsx
@@ -3,8 +3,7 @@ import * as GQL from "../../../core/generated-graphql";
import { StashService } from "../../../core/StashService";
import { ErrorUtils } from "../../../utils/errors";
import { ToastUtils } from "../../../utils/toasts";
-import { FilterMultiSelect } from "../../select/FilterMultiSelect";
-import { FilterSelect } from "../../select/FilterSelect";
+import { FilterSelect, StudioSelect } from "../../select/FilterSelect";
import { ValidGalleriesSelect } from "../../select/ValidGalleriesSelect";
import { ImageUtils } from "../../../utils/image";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
@@ -129,11 +128,12 @@ export const SceneEditPanel: React.FC = (props: IProps) => {
props.onDelete();
}
- function renderMultiSelect(type: "performers" | "tags", initialIds: string[] | undefined) {
+ function renderMultiSelect(type: "performers" | "tags", initialIds: string[] = []) {
return (
- {
+ isMulti={true}
+ onSelect={(items) => {
const ids = items.map((i) => i.id);
switch (type) {
case "performers": setPerformerIds(ids); break;
@@ -352,10 +352,9 @@ export const SceneEditPanel: React.FC = (props: IProps) => {
Studio
- setStudioId(item ? item.id : undefined)}
- initialId={studioId}
+ items.length && setStudioId(items[0]?.id)}
+ initialIds={studioId ? [studioId] : []}
/>
diff --git a/ui/v2.5/src/components/scenes/SceneDetails/SceneMarkersPanel.tsx b/ui/v2.5/src/components/scenes/SceneDetails/SceneMarkersPanel.tsx
index 80c497ac4..c5caef146 100644
--- a/ui/v2.5/src/components/scenes/SceneDetails/SceneMarkersPanel.tsx
+++ b/ui/v2.5/src/components/scenes/SceneDetails/SceneMarkersPanel.tsx
@@ -4,8 +4,7 @@ import React, { CSSProperties, FunctionComponent, useState } from "react";
import * as GQL from "../../../core/generated-graphql";
import { StashService } from "../../../core/StashService";
import { TextUtils } from "../../../utils/text";
-import { FilterMultiSelect } from "../../select/FilterMultiSelect";
-import { FilterSelect } from "../../select/FilterSelect";
+import { TagSelect } from "../../select/FilterSelect";
import { MarkerTitleSuggest } from "../../select/MarkerTitleSuggest";
import { WallPanel } from "../../Wall/WallPanel";
import { SceneHelpers } from "../helpers";
@@ -149,19 +148,18 @@ export const SceneMarkersPanel: FunctionComponent = (pr
}
function renderPrimaryTagField(fieldProps: FieldProps) {
return (
- fieldProps.form.setFieldValue("primaryTagId", tag ? tag.id : undefined)}
- initialId={!!editingMarker ? editingMarker.primary_tag.id : undefined}
+ fieldProps.form.setFieldValue("primaryTagId", tags[0]?.id)}
+ initialIds={editingMarker ? [editingMarker.primary_tag.id] : []}
/>
);
}
function renderTagsField(fieldProps: FieldProps) {
return (
- fieldProps.form.setFieldValue("tagIds", tags.map((tag) => tag.id))}
- initialIds={!!editingMarker ? fieldProps.form.values.tagIds : undefined}
+ fieldProps.form.setFieldValue("tagIds", tags.map((tag) => tag.id))}
+ initialIds={editingMarker ? fieldProps.form.values.tagIds : []}
/>
);
}
diff --git a/ui/v2.5/src/components/scenes/SceneFilenameParser.tsx b/ui/v2.5/src/components/scenes/SceneFilenameParser.tsx
index 24b3c8a7b..5ba313de9 100644
--- a/ui/v2.5/src/components/scenes/SceneFilenameParser.tsx
+++ b/ui/v2.5/src/components/scenes/SceneFilenameParser.tsx
@@ -9,8 +9,7 @@ import _ from "lodash";
import { ToastUtils } from "../../utils/toasts";
import { ErrorUtils } from "../../utils/errors";
import { Pagination } from "../list/Pagination";
-import { FilterMultiSelect } from "../select/FilterMultiSelect";
-import { FilterSelect } from "../select/FilterSelect";
+import { FilterSelect, StudioSelect } from "../select/FilterSelect";
class ParserResult {
public value: Maybe;
@@ -760,10 +759,11 @@ export const SceneFilenameParser: React.FC = () => {
function renderNewMultiSelect(type: "performers" | "tags", props : ISceneParserFieldProps, onChange : (value : any) => void) {
return (
- {
+ isMulti={true}
+ onSelect={(items) => {
const ids = items.map((i) => i.id);
onChange(ids);
}}
@@ -782,12 +782,11 @@ export const SceneFilenameParser: React.FC = () => {
function renderNewStudioSelect(props : ISceneParserFieldProps, onChange : (value : any) => void) {
return (
- onChange(item ? item.id : undefined)}
- initialId={props.parserResult.value}
+ onSelect={(items) => onChange(items[0]?.id)}
+ initialIds={props.parserResult.value ? [props.parserResult.value] : []}
/>
);
}
diff --git a/ui/v2.5/src/components/scenes/SceneSelectedOptions.tsx b/ui/v2.5/src/components/scenes/SceneSelectedOptions.tsx
index 5d60d0c6b..23b7d7f41 100644
--- a/ui/v2.5/src/components/scenes/SceneSelectedOptions.tsx
+++ b/ui/v2.5/src/components/scenes/SceneSelectedOptions.tsx
@@ -1,8 +1,7 @@
import _ from "lodash";
import { Button, ButtonGroup, Form, Spinner } from 'react-bootstrap';
import React, { useEffect, useState } from "react";
-import { FilterSelect } from "../select/FilterSelect";
-import { FilterMultiSelect } from "../select/FilterMultiSelect";
+import { FilterSelect, StudioSelect } from "../select/FilterSelect";
import { StashService } from "../../core/StashService";
import * as GQL from "../../core/generated-graphql";
import { ErrorUtils } from "../../utils/errors";
@@ -236,16 +235,17 @@ export const SceneSelectedOptions: React.FC = (props: IList
function renderMultiSelect(type: "performers" | "tags", initialIds: string[] | undefined) {
return (
- {
+ isMulti={true}
+ onSelect={(items) => {
const ids = items.map((i) => i.id);
switch (type) {
case "performers": setPerformerIds(ids); break;
case "tags": setTagIds(ids); break;
}
}}
- initialIds={initialIds}
+ initialIds={initialIds ?? []}
/>
);
}
@@ -268,10 +268,9 @@ export const SceneSelectedOptions: React.FC = (props: IList
Studio
- setStudioId(item ? item.id : undefined)}
- initialId={studioId}
+ setStudioId(items[0]?.id)}
+ initialIds={studioId ? [studioId] : []}
/>
diff --git a/ui/v2.5/src/components/select/FilterSelect.tsx b/ui/v2.5/src/components/select/FilterSelect.tsx
index 7f285c40b..b3d07eb1a 100644
--- a/ui/v2.5/src/components/select/FilterSelect.tsx
+++ b/ui/v2.5/src/components/select/FilterSelect.tsx
@@ -1,117 +1,146 @@
-import * as React from "react";
-import { Button } from 'react-bootstrap';
+import React, { useState } from "react";
+import Select, { ValueType } from 'react-select';
+import CreatableSelect from 'react-select/creatable';
-import { ISelectProps, ItemPredicate, ItemRenderer, Select } from "@blueprintjs/select";
+import { ErrorUtils } from "../../utils/errors";
import * as GQL from "../../core/generated-graphql";
import { StashService } from "../../core/StashService";
-import { HTMLInputProps } from "../../models";
-
-const InternalPerformerSelect = Select.ofType();
-const InternalTagSelect = Select.ofType();
-const InternalStudioSelect = Select.ofType();
+import useToast from '../Shared/Toast';
type ValidTypes =
GQL.AllPerformersForFilterAllPerformers |
GQL.AllTagsForFilterAllTags |
GQL.AllStudiosForFilterAllStudios;
+type Option = { value:string, label:string };
-interface IProps extends HTMLInputProps {
- type: "performers" | "studios" | "tags";
- initialId?: string;
+interface ITypeProps {
+ type: 'performers' | 'studios' | 'tags';
+}
+interface IFilterProps {
+ initialIds: string[];
+ onSelect: (item: ValidTypes[]) => void;
noSelectionString?: string;
- onSelectItem: (item: ValidTypes | undefined) => void;
+ className?: string;
+ isMulti?: boolean;
+}
+interface ISelectProps {
+ className?: string;
+ items: Option[];
+ selectedOptions?: Option[];
+ creatable?: boolean;
+ onCreateOption?: (value: string) => void;
+ isLoading: boolean;
+ onChange: (item: ValueType