From cb1fa323e42f1c31efcc306c7652d0088127678f Mon Sep 17 00:00:00 2001 From: Infinite Date: Wed, 8 Jan 2020 10:51:33 +0100 Subject: [PATCH] Changes --- ui/v2.5/package.json | 2 + ui/v2.5/src/.index.scss.swp | Bin 20480 -> 0 bytes ui/v2.5/src/App.tsx | 31 +-- ui/v2.5/src/components/list/AddFilter.tsx | 25 +- .../scenes/SceneDetails/SceneEditPanel.tsx | 17 +- .../scenes/SceneDetails/SceneMarkersPanel.tsx | 18 +- .../components/scenes/SceneFilenameParser.tsx | 15 +- .../scenes/SceneSelectedOptions.tsx | 17 +- .../src/components/select/FilterSelect.tsx | 213 ++++++++++-------- ui/v2.5/src/core/StashService.ts | 9 +- ui/v2.5/src/index.scss | 12 + ui/v2.5/src/styles/._variables.scss.swp | Bin 12288 -> 0 bytes ui/v2.5/src/utils/table.tsx | 13 +- ui/v2.5/yarn.lock | 170 +++++++++++++- 14 files changed, 366 insertions(+), 176 deletions(-) delete mode 100644 ui/v2.5/src/.index.scss.swp delete mode 100644 ui/v2.5/src/styles/._variables.scss.swp 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 f3efde583400b8f0c108bbe7ac591853314c672b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 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 (
-
- - - - {/* */} - - - - - - - - -
+ +
+ + + + {/* */} + + + + + + + + +
+
); 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