mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
New scene select with additional fields (#4832)
This commit is contained in:
parent
c8aeb7966a
commit
ca5febc65b
10 changed files with 383 additions and 75 deletions
|
|
@ -79,3 +79,19 @@ fragment SceneData on Scene {
|
|||
label
|
||||
}
|
||||
}
|
||||
|
||||
fragment SelectSceneData on Scene {
|
||||
id
|
||||
title
|
||||
date
|
||||
code
|
||||
studio {
|
||||
name
|
||||
}
|
||||
files {
|
||||
path
|
||||
}
|
||||
paths {
|
||||
screenshot
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,3 +89,16 @@ query SceneStreams($id: ID!) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
query FindScenesForSelect(
|
||||
$filter: FindFilterType
|
||||
$scene_filter: SceneFilterType
|
||||
$ids: [ID!]
|
||||
) {
|
||||
findScenes(filter: $filter, scene_filter: $scene_filter, ids: $ids) {
|
||||
count
|
||||
scenes {
|
||||
...SelectSceneData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,14 +18,12 @@ import {
|
|||
useListGalleryScrapers,
|
||||
mutateReloadScrapers,
|
||||
} from "src/core/StashService";
|
||||
import { SceneSelect } from "src/components/Shared/Select";
|
||||
import { Icon } from "src/components/Shared/Icon";
|
||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||
import { useToast } from "src/hooks/Toast";
|
||||
import { useFormik } from "formik";
|
||||
import { GalleryScrapeDialog } from "./GalleryScrapeDialog";
|
||||
import { faSyncAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
import { galleryTitle } from "src/core/galleries";
|
||||
import isEqual from "lodash-es/isEqual";
|
||||
import { handleUnsavedChanges } from "src/utils/navigation";
|
||||
import {
|
||||
|
|
@ -40,6 +38,7 @@ import {
|
|||
import { formikUtils } from "src/utils/form";
|
||||
import { Tag, TagSelect } from "src/components/Tags/TagSelect";
|
||||
import { Studio, StudioSelect } from "src/components/Studios/StudioSelect";
|
||||
import { Scene, SceneSelect } from "src/components/Scenes/SceneSelect";
|
||||
|
||||
interface IProps {
|
||||
gallery: Partial<GQL.GalleryDataFragment>;
|
||||
|
|
@ -56,12 +55,7 @@ export const GalleryEditPanel: React.FC<IProps> = ({
|
|||
}) => {
|
||||
const intl = useIntl();
|
||||
const Toast = useToast();
|
||||
const [scenes, setScenes] = useState<{ id: string; title: string }[]>(
|
||||
(gallery?.scenes ?? []).map((s) => ({
|
||||
id: s.id,
|
||||
title: galleryTitle(s),
|
||||
}))
|
||||
);
|
||||
const [scenes, setScenes] = useState<Scene[]>([]);
|
||||
|
||||
const [performers, setPerformers] = useState<Performer[]>([]);
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
|
|
@ -116,12 +110,7 @@ export const GalleryEditPanel: React.FC<IProps> = ({
|
|||
onSubmit: (values) => onSave(schema.cast(values)),
|
||||
});
|
||||
|
||||
interface ISceneSelectValue {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
function onSetScenes(items: ISceneSelectValue[]) {
|
||||
function onSetScenes(items: Scene[]) {
|
||||
setScenes(items);
|
||||
formik.setFieldValue(
|
||||
"scene_ids",
|
||||
|
|
@ -162,6 +151,10 @@ export const GalleryEditPanel: React.FC<IProps> = ({
|
|||
setStudio(gallery.studio ?? null);
|
||||
}, [gallery.studio]);
|
||||
|
||||
useEffect(() => {
|
||||
setScenes(gallery.scenes ?? []);
|
||||
}, [gallery.scenes]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
Mousetrap.bind("s s", () => {
|
||||
|
|
@ -412,7 +405,7 @@ export const GalleryEditPanel: React.FC<IProps> = ({
|
|||
const title = intl.formatMessage({ id: "scenes" });
|
||||
const control = (
|
||||
<SceneSelect
|
||||
selected={scenes}
|
||||
values={scenes}
|
||||
onSelect={(items) => onSetScenes(items)}
|
||||
isMulti
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import React, { useEffect, useMemo, useState } from "react";
|
|||
import * as GQL from "src/core/generated-graphql";
|
||||
import { Icon } from "../Shared/Icon";
|
||||
import { LoadingIndicator } from "../Shared/LoadingIndicator";
|
||||
import { StringListSelect, GallerySelect, SceneSelect } from "../Shared/Select";
|
||||
import { StringListSelect, GallerySelect } from "../Shared/Select";
|
||||
import * as FormUtils from "src/utils/form";
|
||||
import ImageUtils from "src/utils/image";
|
||||
import TextUtils from "src/utils/text";
|
||||
|
|
@ -35,6 +35,7 @@ import {
|
|||
ScrapedStudioRow,
|
||||
ScrapedTagsRow,
|
||||
} from "../Shared/ScrapeDialog/ScrapedObjectsRow";
|
||||
import { Scene, SceneSelect } from "src/components/Scenes/SceneSelect";
|
||||
|
||||
interface IStashIDsField {
|
||||
values: GQL.StashId[];
|
||||
|
|
@ -645,12 +646,8 @@ export const SceneMergeModal: React.FC<ISceneMergeModalProps> = ({
|
|||
onClose,
|
||||
scenes,
|
||||
}) => {
|
||||
const [sourceScenes, setSourceScenes] = useState<
|
||||
{ id: string; title: string }[]
|
||||
>([]);
|
||||
const [destScene, setDestScene] = useState<{ id: string; title: string }[]>(
|
||||
[]
|
||||
);
|
||||
const [sourceScenes, setSourceScenes] = useState<Scene[]>([]);
|
||||
const [destScene, setDestScene] = useState<Scene[]>([]);
|
||||
|
||||
const [loadedSources, setLoadedSources] = useState<
|
||||
GQL.SlimSceneDataFragment[]
|
||||
|
|
@ -773,7 +770,7 @@ export const SceneMergeModal: React.FC<ISceneMergeModalProps> = ({
|
|||
<SceneSelect
|
||||
isMulti
|
||||
onSelect={(items) => setSourceScenes(items)}
|
||||
selected={sourceScenes}
|
||||
values={sourceScenes}
|
||||
/>
|
||||
</Col>
|
||||
</Form.Group>
|
||||
|
|
@ -805,7 +802,7 @@ export const SceneMergeModal: React.FC<ISceneMergeModalProps> = ({
|
|||
<Col sm={9} xl={12}>
|
||||
<SceneSelect
|
||||
onSelect={(items) => setDestScene(items)}
|
||||
selected={destScene}
|
||||
values={destScene}
|
||||
/>
|
||||
</Col>
|
||||
</Form.Group>
|
||||
|
|
|
|||
264
ui/v2.5/src/components/Scenes/SceneSelect.tsx
Normal file
264
ui/v2.5/src/components/Scenes/SceneSelect.tsx
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
OptionProps,
|
||||
components as reactSelectComponents,
|
||||
MultiValueGenericProps,
|
||||
SingleValueProps,
|
||||
} from "react-select";
|
||||
import cx from "classnames";
|
||||
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import {
|
||||
queryFindScenesForSelect,
|
||||
queryFindScenesByIDForSelect,
|
||||
} from "src/core/StashService";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { useIntl } from "react-intl";
|
||||
import { defaultMaxOptionsShown } from "src/core/config";
|
||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||
import {
|
||||
FilterSelectComponent,
|
||||
IFilterIDProps,
|
||||
IFilterProps,
|
||||
IFilterValueProps,
|
||||
Option as SelectOption,
|
||||
} from "../Shared/FilterSelect";
|
||||
import { useCompare } from "src/hooks/state";
|
||||
import { Placement } from "react-bootstrap/esm/Overlay";
|
||||
import { sortByRelevance } from "src/utils/query";
|
||||
import { objectTitle } from "src/core/files";
|
||||
import { PatchComponent } from "src/patch";
|
||||
import {
|
||||
Criterion,
|
||||
CriterionValue,
|
||||
} from "src/models/list-filter/criteria/criterion";
|
||||
import { TruncatedText } from "../Shared/TruncatedText";
|
||||
|
||||
export type Scene = Pick<GQL.Scene, "id" | "title" | "date" | "code"> & {
|
||||
studio?: Pick<GQL.Studio, "name"> | null;
|
||||
} & {
|
||||
files?: Pick<GQL.VideoFile, "path">[];
|
||||
} & {
|
||||
paths?: Pick<GQL.ScenePathsType, "screenshot">;
|
||||
};
|
||||
|
||||
type Option = SelectOption<Scene>;
|
||||
|
||||
type ExtraSceneProps = {
|
||||
hoverPlacement?: Placement;
|
||||
excludeIds?: string[];
|
||||
extraCriteria?: Array<Criterion<CriterionValue>>;
|
||||
};
|
||||
|
||||
const _SceneSelect: React.FC<
|
||||
IFilterProps & IFilterValueProps<Scene> & ExtraSceneProps
|
||||
> = (props) => {
|
||||
const { configuration } = React.useContext(ConfigurationContext);
|
||||
const intl = useIntl();
|
||||
const maxOptionsShown =
|
||||
configuration?.ui.maxOptionsShown ?? defaultMaxOptionsShown;
|
||||
|
||||
const exclude = useMemo(() => props.excludeIds ?? [], [props.excludeIds]);
|
||||
|
||||
async function loadScenes(input: string): Promise<Option[]> {
|
||||
const filter = new ListFilterModel(GQL.FilterMode.Scenes);
|
||||
filter.searchTerm = input;
|
||||
filter.currentPage = 1;
|
||||
filter.itemsPerPage = maxOptionsShown;
|
||||
filter.sortBy = "title";
|
||||
filter.sortDirection = GQL.SortDirectionEnum.Asc;
|
||||
|
||||
if (props.extraCriteria) {
|
||||
filter.criteria = [...props.extraCriteria];
|
||||
}
|
||||
|
||||
const query = await queryFindScenesForSelect(filter);
|
||||
let ret = query.data.findScenes.scenes.filter((scene) => {
|
||||
// HACK - we should probably exclude these in the backend query, but
|
||||
// this will do in the short-term
|
||||
return !exclude.includes(scene.id.toString());
|
||||
});
|
||||
|
||||
return sortByRelevance(input, ret, objectTitle, (s) => {
|
||||
return s.files.map((f) => f.path);
|
||||
}).map((scene) => ({
|
||||
value: scene.id,
|
||||
object: scene,
|
||||
}));
|
||||
}
|
||||
|
||||
const SceneOption: React.FC<OptionProps<Option, boolean>> = (optionProps) => {
|
||||
let thisOptionProps = optionProps;
|
||||
|
||||
const { object } = optionProps.data;
|
||||
|
||||
const title = objectTitle(object);
|
||||
|
||||
// if title does not match the input value but the path does, show the path
|
||||
const { inputValue } = optionProps.selectProps;
|
||||
let matchedPath: string | undefined = "";
|
||||
if (!title.toLowerCase().includes(inputValue.toLowerCase())) {
|
||||
matchedPath = object.files?.find((a) =>
|
||||
a.path.toLowerCase().includes(inputValue.toLowerCase())
|
||||
)?.path;
|
||||
}
|
||||
|
||||
thisOptionProps = {
|
||||
...optionProps,
|
||||
children: (
|
||||
<span className="scene-select-option">
|
||||
<span className="scene-select-row">
|
||||
{object.paths?.screenshot && (
|
||||
<img
|
||||
className="scene-select-image"
|
||||
src={object.paths.screenshot}
|
||||
loading="lazy"
|
||||
/>
|
||||
)}
|
||||
|
||||
<span className="scene-select-details">
|
||||
<TruncatedText
|
||||
className="scene-select-title"
|
||||
text={title}
|
||||
lineCount={1}
|
||||
/>
|
||||
|
||||
{object.studio?.name && (
|
||||
<span className="scene-select-studio">
|
||||
{object.studio?.name}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{object.date && (
|
||||
<span className="scene-select-date">{object.date}</span>
|
||||
)}
|
||||
|
||||
{object.code && (
|
||||
<span className="scene-select-code">{object.code}</span>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
{matchedPath && (
|
||||
<span className="scene-select-alias">{`(${matchedPath})`}</span>
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
};
|
||||
|
||||
return <reactSelectComponents.Option {...thisOptionProps} />;
|
||||
};
|
||||
|
||||
const SceneMultiValueLabel: React.FC<
|
||||
MultiValueGenericProps<Option, boolean>
|
||||
> = (optionProps) => {
|
||||
let thisOptionProps = optionProps;
|
||||
|
||||
const { object } = optionProps.data;
|
||||
|
||||
thisOptionProps = {
|
||||
...optionProps,
|
||||
children: objectTitle(object),
|
||||
};
|
||||
|
||||
return <reactSelectComponents.MultiValueLabel {...thisOptionProps} />;
|
||||
};
|
||||
|
||||
const SceneValueLabel: React.FC<SingleValueProps<Option, boolean>> = (
|
||||
optionProps
|
||||
) => {
|
||||
let thisOptionProps = optionProps;
|
||||
|
||||
const { object } = optionProps.data;
|
||||
|
||||
thisOptionProps = {
|
||||
...optionProps,
|
||||
children: <>{objectTitle(object)}</>,
|
||||
};
|
||||
|
||||
return <reactSelectComponents.SingleValue {...thisOptionProps} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<FilterSelectComponent<Scene, boolean>
|
||||
{...props}
|
||||
className={cx(
|
||||
"scene-select",
|
||||
{
|
||||
"scene-select-active": props.active,
|
||||
},
|
||||
props.className
|
||||
)}
|
||||
loadOptions={loadScenes}
|
||||
components={{
|
||||
Option: SceneOption,
|
||||
MultiValueLabel: SceneMultiValueLabel,
|
||||
SingleValue: SceneValueLabel,
|
||||
}}
|
||||
isMulti={props.isMulti ?? false}
|
||||
placeholder={
|
||||
props.noSelectionString ??
|
||||
intl.formatMessage(
|
||||
{ id: "actions.select_entity" },
|
||||
{
|
||||
entityType: intl.formatMessage({
|
||||
id: props.isMulti ? "scenes" : "scene",
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
closeMenuOnSelect={!props.isMulti}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SceneSelect = PatchComponent("SceneSelect", _SceneSelect);
|
||||
|
||||
const _SceneIDSelect: React.FC<
|
||||
IFilterProps & IFilterIDProps<Scene> & ExtraSceneProps
|
||||
> = (props) => {
|
||||
const { ids, onSelect: onSelectValues } = props;
|
||||
|
||||
const [values, setValues] = useState<Scene[]>([]);
|
||||
const idsChanged = useCompare(ids);
|
||||
|
||||
function onSelect(items: Scene[]) {
|
||||
setValues(items);
|
||||
onSelectValues?.(items);
|
||||
}
|
||||
|
||||
async function loadObjectsByID(idsToLoad: string[]): Promise<Scene[]> {
|
||||
const query = await queryFindScenesByIDForSelect(idsToLoad);
|
||||
const { scenes: loadedScenes } = query.data.findScenes;
|
||||
|
||||
return loadedScenes;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!idsChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ids || ids?.length === 0) {
|
||||
setValues([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// load the values if we have ids and they haven't been loaded yet
|
||||
const filteredValues = values.filter((v) => ids.includes(v.id.toString()));
|
||||
if (filteredValues.length === ids.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const load = async () => {
|
||||
const items = await loadObjectsByID(ids);
|
||||
setValues(items);
|
||||
};
|
||||
|
||||
load();
|
||||
}, [ids, idsChanged, values]);
|
||||
|
||||
return <SceneSelect {...props} values={values} onSelect={onSelect} />;
|
||||
};
|
||||
|
||||
export const SceneIDSelect = PatchComponent("SceneIDSelect", _SceneIDSelect);
|
||||
|
|
@ -842,3 +842,52 @@ input[type="range"].blue-slider {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scene-select-option {
|
||||
.scene-select-row {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.scene-select-image {
|
||||
background-color: $body-bg;
|
||||
margin-right: 0.4em;
|
||||
max-height: 50px;
|
||||
max-width: 89px;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.scene-select-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
max-height: 4.1rem;
|
||||
overflow: hidden;
|
||||
|
||||
.scene-select-title {
|
||||
flex-shrink: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.scene-select-date,
|
||||
.scene-select-studio,
|
||||
.scene-select-code {
|
||||
color: $text-muted;
|
||||
flex-shrink: 0;
|
||||
font-size: 0.9rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scene-select-alias {
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import React, { useState } from "react";
|
||||
import { ModalComponent } from "./Modal";
|
||||
import { SceneSelect } from "./Select";
|
||||
import { useToast } from "src/hooks/Toast";
|
||||
import { useIntl } from "react-intl";
|
||||
import { faSignOutAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
import { Col, Form, Row } from "react-bootstrap";
|
||||
import * as FormUtils from "src/utils/form";
|
||||
import { mutateSceneAssignFile } from "src/core/StashService";
|
||||
import { Scene, SceneSelect } from "src/components/Scenes/SceneSelect";
|
||||
|
||||
interface IFile {
|
||||
id: string;
|
||||
|
|
@ -21,7 +21,7 @@ interface IReassignFilesDialogProps {
|
|||
export const ReassignFilesDialog: React.FC<IReassignFilesDialogProps> = (
|
||||
props: IReassignFilesDialogProps
|
||||
) => {
|
||||
const [scenes, setScenes] = useState<{ id: string; title: string }[]>([]);
|
||||
const [scenes, setScenes] = useState<Scene[]>([]);
|
||||
|
||||
const intl = useIntl();
|
||||
const singularEntity = intl.formatMessage({ id: "file" });
|
||||
|
|
@ -89,7 +89,7 @@ export const ReassignFilesDialog: React.FC<IReassignFilesDialogProps> = (
|
|||
})}
|
||||
<Col sm={9} xl={12}>
|
||||
<SceneSelect
|
||||
selected={scenes}
|
||||
values={scenes}
|
||||
onSelect={(items) => setScenes(items)}
|
||||
/>
|
||||
</Col>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { TagIDSelect } from "../Tags/TagSelect";
|
|||
import { StudioIDSelect } from "../Studios/StudioSelect";
|
||||
import { GalleryIDSelect } from "../Galleries/GallerySelect";
|
||||
import { MovieIDSelect } from "../Movies/MovieSelect";
|
||||
import { SceneIDSelect } from "../Scenes/SceneSelect";
|
||||
|
||||
export type SelectObject = {
|
||||
id: string;
|
||||
|
|
@ -254,54 +255,10 @@ export const GallerySelect: React.FC<
|
|||
return <GalleryIDSelect {...props} />;
|
||||
};
|
||||
|
||||
export const SceneSelect: React.FC<ITitledSelect> = (props) => {
|
||||
const [query, setQuery] = useState<string>("");
|
||||
const { data, loading } = GQL.useFindScenesQuery({
|
||||
skip: query === "",
|
||||
variables: {
|
||||
filter: {
|
||||
q: query,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const scenes = data?.findScenes.scenes ?? [];
|
||||
const items = scenes.map((s) => ({
|
||||
label: objectTitle(s),
|
||||
value: s.id,
|
||||
}));
|
||||
|
||||
const onInputChange = useDebounce(setQuery, 500);
|
||||
|
||||
const onChange = (selectedItems: OnChangeValue<Option, boolean>) => {
|
||||
const selected = getSelectedItems(selectedItems);
|
||||
props.onSelect(
|
||||
(selected ?? []).map((s) => ({
|
||||
id: s.value,
|
||||
title: s.label,
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
const options = props.selected.map((s) => ({
|
||||
value: s.id,
|
||||
label: s.title,
|
||||
}));
|
||||
|
||||
return (
|
||||
<SelectComponent
|
||||
onChange={onChange}
|
||||
onInputChange={onInputChange}
|
||||
isLoading={loading}
|
||||
items={items}
|
||||
selectedOptions={options}
|
||||
isMulti={props.isMulti ?? false}
|
||||
placeholder="Search for scene..."
|
||||
noOptionsMessage={query === "" ? null : "No scenes found."}
|
||||
showDropdown={false}
|
||||
isDisabled={props.disabled}
|
||||
/>
|
||||
);
|
||||
export const SceneSelect: React.FC<IFilterProps & { excludeIds?: string[] }> = (
|
||||
props
|
||||
) => {
|
||||
return <SceneIDSelect {...props} />;
|
||||
};
|
||||
|
||||
export const ImageSelect: React.FC<ITitledSelect> = (props) => {
|
||||
|
|
|
|||
|
|
@ -166,6 +166,23 @@ export const queryFindScenesByID = (sceneIDs: number[]) =>
|
|||
},
|
||||
});
|
||||
|
||||
export const queryFindScenesForSelect = (filter: ListFilterModel) =>
|
||||
client.query<GQL.FindScenesForSelectQuery>({
|
||||
query: GQL.FindScenesForSelectDocument,
|
||||
variables: {
|
||||
filter: filter.makeFindFilter(),
|
||||
scene_filter: filter.makeFilter(),
|
||||
},
|
||||
});
|
||||
|
||||
export const queryFindScenesByIDForSelect = (sceneIDs: string[]) =>
|
||||
client.query<GQL.FindScenesForSelectQuery>({
|
||||
query: GQL.FindScenesForSelectDocument,
|
||||
variables: {
|
||||
ids: sceneIDs,
|
||||
},
|
||||
});
|
||||
|
||||
export const querySceneByPathRegex = (filter: GQL.FindFilterType) =>
|
||||
client.query<GQL.FindScenesByPathRegexQuery>({
|
||||
query: GQL.FindScenesByPathRegexDocument,
|
||||
|
|
|
|||
2
ui/v2.5/src/pluginApi.d.ts
vendored
2
ui/v2.5/src/pluginApi.d.ts
vendored
|
|
@ -672,6 +672,8 @@ declare namespace PluginApi {
|
|||
GalleryIDSelect: React.FC<any>;
|
||||
MovieSelect: React.FC<any>;
|
||||
MovieIDSelect: React.FC<any>;
|
||||
SceneSelect: React.FC<any>;
|
||||
SceneIDSelect: React.FC<any>;
|
||||
DateInput: React.FC<any>;
|
||||
CountrySelect: React.FC<any>;
|
||||
FolderSelect: React.FC<any>;
|
||||
|
|
|
|||
Loading…
Reference in a new issue