mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Studio select refactor (#4493)
* Add id interface to findStudios * Replace existing selects * Remove unused code * Fix scrape/merge select * Make clearable
This commit is contained in:
parent
217c02f181
commit
de2b28d3f9
19 changed files with 494 additions and 239 deletions
|
|
@ -33,3 +33,16 @@ fragment StudioData on Studio {
|
|||
rating100
|
||||
aliases
|
||||
}
|
||||
|
||||
fragment SelectStudioData on Studio {
|
||||
id
|
||||
name
|
||||
aliases
|
||||
details
|
||||
image_path
|
||||
|
||||
parent_studio {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,6 @@ query MarkerStrings($q: String, $sort: String) {
|
|||
}
|
||||
}
|
||||
|
||||
query AllStudiosForFilter {
|
||||
allStudios {
|
||||
id
|
||||
name
|
||||
aliases
|
||||
}
|
||||
}
|
||||
|
||||
query AllMoviesForFilter {
|
||||
allMovies {
|
||||
id
|
||||
|
|
|
|||
|
|
@ -12,3 +12,16 @@ query FindStudio($id: ID!) {
|
|||
...StudioData
|
||||
}
|
||||
}
|
||||
|
||||
query FindStudiosForSelect(
|
||||
$filter: FindFilterType
|
||||
$studio_filter: StudioFilterType
|
||||
$ids: [ID!]
|
||||
) {
|
||||
findStudios(filter: $filter, studio_filter: $studio_filter, ids: $ids) {
|
||||
count
|
||||
studios {
|
||||
...SelectStudioData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ type Query {
|
|||
findStudios(
|
||||
studio_filter: StudioFilterType
|
||||
filter: FindFilterType
|
||||
ids: [ID!]
|
||||
): FindStudiosResultType!
|
||||
|
||||
"Find a movie by ID"
|
||||
|
|
@ -202,11 +203,11 @@ type Query {
|
|||
allSceneMarkers: [SceneMarker!]!
|
||||
allImages: [Image!]!
|
||||
allGalleries: [Gallery!]!
|
||||
allStudios: [Studio!]!
|
||||
allMovies: [Movie!]!
|
||||
|
||||
allPerformers: [Performer!]! @deprecated(reason: "Use findPerformers instead")
|
||||
allTags: [Tag!]! @deprecated(reason: "Use findTags instead")
|
||||
allStudios: [Studio!]! @deprecated(reason: "Use findStudios instead")
|
||||
|
||||
# Get everything with minimal metadata
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||
)
|
||||
|
||||
func (r *queryResolver) FindStudio(ctx context.Context, id string) (ret *models.Studio, err error) {
|
||||
|
|
@ -24,9 +25,23 @@ func (r *queryResolver) FindStudio(ctx context.Context, id string) (ret *models.
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) FindStudios(ctx context.Context, studioFilter *models.StudioFilterType, filter *models.FindFilterType) (ret *FindStudiosResultType, err error) {
|
||||
func (r *queryResolver) FindStudios(ctx context.Context, studioFilter *models.StudioFilterType, filter *models.FindFilterType, ids []string) (ret *FindStudiosResultType, err error) {
|
||||
idInts, err := stringslice.StringSliceToIntSlice(ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
studios, total, err := r.repository.Studio.Query(ctx, studioFilter, filter)
|
||||
var studios []*models.Studio
|
||||
var err error
|
||||
var total int
|
||||
|
||||
if len(idInts) > 0 {
|
||||
studios, err = r.repository.Studio.FindMany(ctx, idInts)
|
||||
total = len(studios)
|
||||
} else {
|
||||
studios, total, err = r.repository.Studio.Query(ctx, studioFilter, filter)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
useListGalleryScrapers,
|
||||
mutateReloadScrapers,
|
||||
} from "src/core/StashService";
|
||||
import { SceneSelect, StudioSelect } from "src/components/Shared/Select";
|
||||
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";
|
||||
|
|
@ -41,6 +41,7 @@ import {
|
|||
} from "src/utils/yup";
|
||||
import { formikUtils } from "src/utils/form";
|
||||
import { Tag, TagSelect } from "src/components/Tags/TagSelect";
|
||||
import { Studio, StudioSelect } from "src/components/Studios/StudioSelect";
|
||||
|
||||
interface IProps {
|
||||
gallery: Partial<GQL.GalleryDataFragment>;
|
||||
|
|
@ -66,6 +67,7 @@ export const GalleryEditPanel: React.FC<IProps> = ({
|
|||
|
||||
const [performers, setPerformers] = useState<Performer[]>([]);
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
const [studio, setStudio] = useState<Studio | null>(null);
|
||||
|
||||
const isNew = gallery.id === undefined;
|
||||
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
||||
|
|
@ -152,6 +154,11 @@ export const GalleryEditPanel: React.FC<IProps> = ({
|
|||
);
|
||||
}
|
||||
|
||||
function onSetStudio(item: Studio | null) {
|
||||
setStudio(item);
|
||||
formik.setFieldValue("studio_id", item ? item.id : null);
|
||||
}
|
||||
|
||||
useRatingKeybinds(
|
||||
isVisible,
|
||||
stashConfig?.ui?.ratingSystemOptions?.type,
|
||||
|
|
@ -166,6 +173,10 @@ export const GalleryEditPanel: React.FC<IProps> = ({
|
|||
setTags(gallery.tags ?? []);
|
||||
}, [gallery.tags]);
|
||||
|
||||
useEffect(() => {
|
||||
setStudio(gallery.studio ?? null);
|
||||
}, [gallery.studio]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
Mousetrap.bind("s s", () => {
|
||||
|
|
@ -252,6 +263,7 @@ export const GalleryEditPanel: React.FC<IProps> = ({
|
|||
return (
|
||||
<GalleryScrapeDialog
|
||||
gallery={currentGallery}
|
||||
galleryStudio={studio}
|
||||
galleryTags={tags}
|
||||
galleryPerformers={performers}
|
||||
scraped={scrapedGallery}
|
||||
|
|
@ -324,7 +336,11 @@ export const GalleryEditPanel: React.FC<IProps> = ({
|
|||
}
|
||||
|
||||
if (galleryData.studio?.stored_id) {
|
||||
formik.setFieldValue("studio_id", galleryData.studio.stored_id);
|
||||
onSetStudio({
|
||||
id: galleryData.studio.stored_id,
|
||||
name: galleryData.studio.name ?? "",
|
||||
aliases: [],
|
||||
});
|
||||
}
|
||||
|
||||
if (galleryData.performers?.length) {
|
||||
|
|
@ -429,13 +445,8 @@ export const GalleryEditPanel: React.FC<IProps> = ({
|
|||
const title = intl.formatMessage({ id: "studio" });
|
||||
const control = (
|
||||
<StudioSelect
|
||||
onSelect={(items) =>
|
||||
formik.setFieldValue(
|
||||
"studio_id",
|
||||
items.length > 0 ? items[0]?.id : null
|
||||
)
|
||||
}
|
||||
ids={formik.values.studio_id ? [formik.values.studio_id] : []}
|
||||
onSelect={(items) => onSetStudio(items.length > 0 ? items[0] : null)}
|
||||
values={studio ? [studio] : []}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -25,9 +25,11 @@ import {
|
|||
} from "src/components/Shared/ScrapeDialog/createObjects";
|
||||
import { uniq } from "lodash-es";
|
||||
import { Tag } from "src/components/Tags/TagSelect";
|
||||
import { Studio } from "src/components/Studios/StudioSelect";
|
||||
|
||||
interface IGalleryScrapeDialogProps {
|
||||
gallery: Partial<GQL.GalleryUpdateInput>;
|
||||
galleryStudio: Studio | null;
|
||||
galleryTags: Tag[];
|
||||
galleryPerformers: Performer[];
|
||||
scraped: GQL.ScrapedGallery;
|
||||
|
|
@ -37,6 +39,7 @@ interface IGalleryScrapeDialogProps {
|
|||
|
||||
export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
||||
gallery,
|
||||
galleryStudio,
|
||||
galleryTags,
|
||||
galleryPerformers,
|
||||
scraped,
|
||||
|
|
@ -63,8 +66,16 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
|||
const [photographer, setPhotographer] = useState<ScrapeResult<string>>(
|
||||
new ScrapeResult<string>(gallery.photographer, scraped.photographer)
|
||||
);
|
||||
const [studio, setStudio] = useState<ScrapeResult<string>>(
|
||||
new ScrapeResult<string>(gallery.studio_id, scraped.studio?.stored_id)
|
||||
const [studio, setStudio] = useState<ScrapeResult<GQL.ScrapedStudio>>(
|
||||
new ScrapeResult<GQL.ScrapedStudio>(
|
||||
galleryStudio
|
||||
? {
|
||||
stored_id: galleryStudio.id,
|
||||
name: galleryStudio.name,
|
||||
}
|
||||
: undefined,
|
||||
scraped.studio
|
||||
)
|
||||
);
|
||||
const [newStudio, setNewStudio] = useState<GQL.ScrapedStudio | undefined>(
|
||||
scraped.studio && !scraped.studio.stored_id ? scraped.studio : undefined
|
||||
|
|
@ -156,12 +167,7 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
|||
urls: urls.getNewValue(),
|
||||
date: date.getNewValue(),
|
||||
photographer: photographer.getNewValue(),
|
||||
studio: newStudioValue
|
||||
? {
|
||||
stored_id: newStudioValue,
|
||||
name: "",
|
||||
}
|
||||
: undefined,
|
||||
studio: newStudioValue,
|
||||
performers: performers.getNewValue(),
|
||||
tags: tags.getNewValue(),
|
||||
details: details.getNewValue(),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { FormattedMessage, useIntl } from "react-intl";
|
|||
import Mousetrap from "mousetrap";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import * as yup from "yup";
|
||||
import { StudioSelect } from "src/components/Shared/Select";
|
||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||
import { useToast } from "src/hooks/Toast";
|
||||
import { useFormik } from "formik";
|
||||
|
|
@ -23,6 +22,7 @@ import {
|
|||
} from "src/components/Performers/PerformerSelect";
|
||||
import { formikUtils } from "src/utils/form";
|
||||
import { Tag, TagSelect } from "src/components/Tags/TagSelect";
|
||||
import { Studio, StudioSelect } from "src/components/Studios/StudioSelect";
|
||||
|
||||
interface IProps {
|
||||
image: GQL.ImageDataFragment;
|
||||
|
|
@ -47,6 +47,7 @@ export const ImageEditPanel: React.FC<IProps> = ({
|
|||
|
||||
const [performers, setPerformers] = useState<Performer[]>([]);
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
const [studio, setStudio] = useState<Studio | null>(null);
|
||||
|
||||
const schema = yup.object({
|
||||
title: yup.string().ensure(),
|
||||
|
|
@ -103,6 +104,11 @@ export const ImageEditPanel: React.FC<IProps> = ({
|
|||
);
|
||||
}
|
||||
|
||||
function onSetStudio(item: Studio | null) {
|
||||
setStudio(item);
|
||||
formik.setFieldValue("studio_id", item ? item.id : null);
|
||||
}
|
||||
|
||||
useRatingKeybinds(
|
||||
true,
|
||||
configuration?.ui?.ratingSystemOptions?.type,
|
||||
|
|
@ -117,6 +123,10 @@ export const ImageEditPanel: React.FC<IProps> = ({
|
|||
setTags(image.tags ?? []);
|
||||
}, [image.tags]);
|
||||
|
||||
useEffect(() => {
|
||||
setStudio(image.studio ?? null);
|
||||
}, [image.studio]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
Mousetrap.bind("s s", () => {
|
||||
|
|
@ -183,13 +193,8 @@ export const ImageEditPanel: React.FC<IProps> = ({
|
|||
const title = intl.formatMessage({ id: "studio" });
|
||||
const control = (
|
||||
<StudioSelect
|
||||
onSelect={(items) =>
|
||||
formik.setFieldValue(
|
||||
"studio_id",
|
||||
items.length > 0 ? items[0]?.id : null
|
||||
)
|
||||
}
|
||||
ids={formik.values.studio_id ? [formik.values.studio_id] : []}
|
||||
onSelect={(items) => onSetStudio(items.length > 0 ? items[0] : null)}
|
||||
values={studio ? [studio] : []}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
useListMovieScrapers,
|
||||
} from "src/core/StashService";
|
||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||
import { StudioSelect } from "src/components/Shared/Select";
|
||||
import { DetailsEditNavbar } from "src/components/Shared/DetailsEditNavbar";
|
||||
import { URLField } from "src/components/Shared/URLField";
|
||||
import { useToast } from "src/hooks/Toast";
|
||||
|
|
@ -22,6 +21,7 @@ import isEqual from "lodash-es/isEqual";
|
|||
import { handleUnsavedChanges } from "src/utils/navigation";
|
||||
import { formikUtils } from "src/utils/form";
|
||||
import { yupDateString, yupFormikValidate } from "src/utils/yup";
|
||||
import { Studio, StudioSelect } from "src/components/Studios/StudioSelect";
|
||||
|
||||
interface IMovieEditPanel {
|
||||
movie: Partial<GQL.MovieDataFragment>;
|
||||
|
|
@ -55,6 +55,8 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
|
|||
const Scrapers = useListMovieScrapers();
|
||||
const [scrapedMovie, setScrapedMovie] = useState<GQL.ScrapedMovie>();
|
||||
|
||||
const [studio, setStudio] = useState<Studio | null>(null);
|
||||
|
||||
const schema = yup.object({
|
||||
name: yup.string().required(),
|
||||
aliases: yup.string().ensure(),
|
||||
|
|
@ -88,6 +90,15 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
|
|||
onSubmit: (values) => onSave(schema.cast(values)),
|
||||
});
|
||||
|
||||
function onSetStudio(item: Studio | null) {
|
||||
setStudio(item);
|
||||
formik.setFieldValue("studio_id", item ? item.id : null);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setStudio(movie.studio ?? null);
|
||||
}, [movie.studio]);
|
||||
|
||||
// set up hotkeys
|
||||
useEffect(() => {
|
||||
// Mousetrap.bind("u", (e) => {
|
||||
|
|
@ -129,7 +140,11 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
|
|||
}
|
||||
|
||||
if (state.studio && state.studio.stored_id) {
|
||||
formik.setFieldValue("studio_id", state.studio.stored_id);
|
||||
onSetStudio({
|
||||
id: state.studio.stored_id,
|
||||
name: state.studio.name ?? "",
|
||||
aliases: [],
|
||||
});
|
||||
}
|
||||
|
||||
if (state.director) {
|
||||
|
|
@ -324,13 +339,8 @@ export const MovieEditPanel: React.FC<IMovieEditPanel> = ({
|
|||
const title = intl.formatMessage({ id: "studio" });
|
||||
const control = (
|
||||
<StudioSelect
|
||||
onSelect={(items) =>
|
||||
formik.setFieldValue(
|
||||
"studio_id",
|
||||
items.length > 0 ? items[0]?.id : null
|
||||
)
|
||||
}
|
||||
ids={formik.values.studio_id ? [formik.values.studio_id] : []}
|
||||
onSelect={(items) => onSetStudio(items.length > 0 ? items[0] : null)}
|
||||
values={studio ? [studio] : []}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,11 +19,7 @@ import {
|
|||
mutateReloadScrapers,
|
||||
queryScrapeSceneQueryFragment,
|
||||
} from "src/core/StashService";
|
||||
import {
|
||||
StudioSelect,
|
||||
GallerySelect,
|
||||
MovieSelect,
|
||||
} from "src/components/Shared/Select";
|
||||
import { GallerySelect, MovieSelect } from "src/components/Shared/Select";
|
||||
import { Icon } from "src/components/Shared/Icon";
|
||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||
import { ImageInput } from "src/components/Shared/ImageInput";
|
||||
|
|
@ -52,6 +48,7 @@ import {
|
|||
} from "src/components/Performers/PerformerSelect";
|
||||
import { formikUtils } from "src/utils/form";
|
||||
import { Tag, TagSelect } from "src/components/Tags/TagSelect";
|
||||
import { Studio, StudioSelect } from "src/components/Studios/StudioSelect";
|
||||
|
||||
const SceneScrapeDialog = lazyComponent(() => import("./SceneScrapeDialog"));
|
||||
const SceneQueryModal = lazyComponent(() => import("./SceneQueryModal"));
|
||||
|
|
@ -81,6 +78,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||
);
|
||||
const [performers, setPerformers] = useState<Performer[]>([]);
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
const [studio, setStudio] = useState<Studio | null>(null);
|
||||
|
||||
const Scrapers = useListSceneScrapers();
|
||||
const [fragmentScrapers, setFragmentScrapers] = useState<GQL.Scraper[]>([]);
|
||||
|
|
@ -109,6 +107,10 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||
setTags(scene.tags ?? []);
|
||||
}, [scene.tags]);
|
||||
|
||||
useEffect(() => {
|
||||
setStudio(scene.studio ?? null);
|
||||
}, [scene.studio]);
|
||||
|
||||
const { configuration: stashConfig } = React.useContext(ConfigurationContext);
|
||||
|
||||
// Network state
|
||||
|
|
@ -215,6 +217,11 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||
);
|
||||
}
|
||||
|
||||
function onSetStudio(item: Studio | null) {
|
||||
setStudio(item);
|
||||
formik.setFieldValue("studio_id", item ? item.id : null);
|
||||
}
|
||||
|
||||
useRatingKeybinds(
|
||||
isVisible,
|
||||
stashConfig?.ui?.ratingSystemOptions?.type,
|
||||
|
|
@ -394,6 +401,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||
return (
|
||||
<SceneScrapeDialog
|
||||
scene={currentScene}
|
||||
sceneStudio={studio}
|
||||
sceneTags={tags}
|
||||
scenePerformers={performers}
|
||||
scraped={scrapedScene}
|
||||
|
|
@ -554,7 +562,11 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||
}
|
||||
|
||||
if (updatedScene.studio && updatedScene.studio.stored_id) {
|
||||
formik.setFieldValue("studio_id", updatedScene.studio.stored_id);
|
||||
onSetStudio({
|
||||
id: updatedScene.studio.stored_id,
|
||||
name: updatedScene.studio.name ?? "",
|
||||
aliases: [],
|
||||
});
|
||||
}
|
||||
|
||||
if (updatedScene.performers && updatedScene.performers.length > 0) {
|
||||
|
|
@ -726,13 +738,8 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||
const title = intl.formatMessage({ id: "studio" });
|
||||
const control = (
|
||||
<StudioSelect
|
||||
onSelect={(items) =>
|
||||
formik.setFieldValue(
|
||||
"studio_id",
|
||||
items.length > 0 ? items[0]?.id : null
|
||||
)
|
||||
}
|
||||
ids={formik.values.studio_id ? [formik.values.studio_id] : []}
|
||||
onSelect={(items) => onSetStudio(items.length > 0 ? items[0] : null)}
|
||||
values={studio ? [studio] : []}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,9 +29,11 @@ import {
|
|||
useCreateScrapedTag,
|
||||
} from "src/components/Shared/ScrapeDialog/createObjects";
|
||||
import { Tag } from "src/components/Tags/TagSelect";
|
||||
import { Studio } from "src/components/Studios/StudioSelect";
|
||||
|
||||
interface ISceneScrapeDialogProps {
|
||||
scene: Partial<GQL.SceneUpdateInput>;
|
||||
sceneStudio: Studio | null;
|
||||
scenePerformers: Performer[];
|
||||
sceneTags: Tag[];
|
||||
scraped: GQL.ScrapedScene;
|
||||
|
|
@ -42,6 +44,7 @@ interface ISceneScrapeDialogProps {
|
|||
|
||||
export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
||||
scene,
|
||||
sceneStudio,
|
||||
scenePerformers,
|
||||
sceneTags,
|
||||
scraped,
|
||||
|
|
@ -70,8 +73,16 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
|||
const [director, setDirector] = useState<ScrapeResult<string>>(
|
||||
new ScrapeResult<string>(scene.director, scraped.director)
|
||||
);
|
||||
const [studio, setStudio] = useState<ScrapeResult<string>>(
|
||||
new ScrapeResult<string>(scene.studio_id, scraped.studio?.stored_id)
|
||||
const [studio, setStudio] = useState<ScrapeResult<GQL.ScrapedStudio>>(
|
||||
new ScrapeResult<GQL.ScrapedStudio>(
|
||||
sceneStudio
|
||||
? {
|
||||
stored_id: sceneStudio.id,
|
||||
name: sceneStudio.name,
|
||||
}
|
||||
: undefined,
|
||||
scraped.studio?.stored_id ? scraped.studio : undefined
|
||||
)
|
||||
);
|
||||
const [newStudio, setNewStudio] = useState<GQL.ScrapedStudio | undefined>(
|
||||
scraped.studio && !scraped.studio.stored_id ? scraped.studio : undefined
|
||||
|
|
@ -235,12 +246,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
|||
urls: urls.getNewValue(),
|
||||
date: date.getNewValue(),
|
||||
director: director.getNewValue(),
|
||||
studio: newStudioValue
|
||||
? {
|
||||
stored_id: newStudioValue,
|
||||
name: "",
|
||||
}
|
||||
: undefined,
|
||||
studio: newStudioValue,
|
||||
performers: performers.getNewValue(),
|
||||
movies: movies.getNewValue()?.map((m) => {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -87,8 +87,17 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||
new ScrapeResult<number>(dest.play_duration)
|
||||
);
|
||||
|
||||
const [studio, setStudio] = useState<ScrapeResult<string>>(
|
||||
new ScrapeResult<string>(dest.studio?.id)
|
||||
function idToStoredID(o: { id: string; name: string }) {
|
||||
return {
|
||||
stored_id: o.id,
|
||||
name: o.name,
|
||||
};
|
||||
}
|
||||
|
||||
const [studio, setStudio] = useState<ScrapeResult<GQL.ScrapedStudio>>(
|
||||
new ScrapeResult<GQL.ScrapedStudio>(
|
||||
dest.studio ? idToStoredID(dest.studio) : undefined
|
||||
)
|
||||
);
|
||||
|
||||
function sortIdList(idList?: string[] | null) {
|
||||
|
|
@ -105,13 +114,6 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||
return ret;
|
||||
}
|
||||
|
||||
function idToStoredID(o: { id: string; name: string }) {
|
||||
return {
|
||||
stored_id: o.id,
|
||||
name: o.name,
|
||||
};
|
||||
}
|
||||
|
||||
function uniqIDStoredIDs<T extends IHasStoredID>(objs: T[]) {
|
||||
return objs.filter((o, i) => {
|
||||
return objs.findIndex((oo) => oo.stored_id === o.stored_id) === i;
|
||||
|
|
@ -197,10 +199,18 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||
setDate(
|
||||
new ScrapeResult(dest.date, sources.find((s) => s.date)?.date, !dest.date)
|
||||
);
|
||||
|
||||
const foundStudio = sources.find((s) => s.studio)?.studio;
|
||||
|
||||
setStudio(
|
||||
new ScrapeResult(
|
||||
dest.studio?.id,
|
||||
sources.find((s) => s.studio)?.studio?.id,
|
||||
new ScrapeResult<GQL.ScrapedStudio>(
|
||||
dest.studio ? idToStoredID(dest.studio) : undefined,
|
||||
foundStudio
|
||||
? {
|
||||
stored_id: foundStudio.id,
|
||||
name: foundStudio.name,
|
||||
}
|
||||
: undefined,
|
||||
!dest.studio
|
||||
)
|
||||
);
|
||||
|
|
@ -581,7 +591,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||
play_count: playCount.getNewValue(),
|
||||
play_duration: playDuration.getNewValue(),
|
||||
gallery_ids: galleries.getNewValue(),
|
||||
studio_id: studio.getNewValue(),
|
||||
studio_id: studio.getNewValue()?.stored_id,
|
||||
performer_ids: performers.getNewValue()?.map((p) => p.stored_id!),
|
||||
movies: movies.getNewValue()?.map((m) => {
|
||||
// find the equivalent movie in the original scenes
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ const SelectComponent = <T, IsMulti extends boolean>(
|
|||
...props,
|
||||
styles,
|
||||
defaultOptions: true,
|
||||
isClearable: true,
|
||||
value: selectedOptions ?? null,
|
||||
className: cx("react-select", props.className),
|
||||
classNamePrefix: "react-select",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useMemo } from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { MovieSelect, StudioSelect } from "src/components/Shared/Select";
|
||||
import { MovieSelect } from "src/components/Shared/Select";
|
||||
import {
|
||||
ScrapeDialogRow,
|
||||
IHasName,
|
||||
|
|
@ -8,11 +8,12 @@ import {
|
|||
import { PerformerSelect } from "src/components/Performers/PerformerSelect";
|
||||
import { ScrapeResult } from "src/components/Shared/ScrapeDialog/scrapeResult";
|
||||
import { TagSelect } from "src/components/Tags/TagSelect";
|
||||
import { StudioSelect } from "src/components/Studios/StudioSelect";
|
||||
|
||||
interface IScrapedStudioRow {
|
||||
title: string;
|
||||
result: ScrapeResult<string>;
|
||||
onChange: (value: ScrapeResult<string>) => void;
|
||||
result: ScrapeResult<GQL.ScrapedStudio>;
|
||||
onChange: (value: ScrapeResult<GQL.ScrapedStudio>) => void;
|
||||
newStudio?: GQL.ScrapedStudio;
|
||||
onCreateNew?: (value: GQL.ScrapedStudio) => void;
|
||||
}
|
||||
|
|
@ -25,25 +26,34 @@ export const ScrapedStudioRow: React.FC<IScrapedStudioRow> = ({
|
|||
onCreateNew,
|
||||
}) => {
|
||||
function renderScrapedStudio(
|
||||
scrapeResult: ScrapeResult<string>,
|
||||
scrapeResult: ScrapeResult<GQL.ScrapedStudio>,
|
||||
isNew?: boolean,
|
||||
onChangeFn?: (value: string) => void
|
||||
onChangeFn?: (value: GQL.ScrapedStudio) => void
|
||||
) {
|
||||
const resultValue = isNew
|
||||
? scrapeResult.newValue
|
||||
: scrapeResult.originalValue;
|
||||
const value = resultValue ? [resultValue] : [];
|
||||
|
||||
const selectValue = value.map((p) => {
|
||||
const aliases: string[] = [];
|
||||
return {
|
||||
id: p.stored_id ?? "",
|
||||
name: p.name ?? "",
|
||||
aliases,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<StudioSelect
|
||||
className="form-control react-select"
|
||||
isDisabled={!isNew}
|
||||
onSelect={(items) => {
|
||||
if (onChangeFn) {
|
||||
onChangeFn(items[0]?.id);
|
||||
onChangeFn(items[0]);
|
||||
}
|
||||
}}
|
||||
ids={value}
|
||||
values={selectValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ function useCreateObject<T>(
|
|||
}
|
||||
|
||||
interface IUseCreateNewStudioProps {
|
||||
scrapeResult: ScrapeResult<string>;
|
||||
setScrapeResult: (scrapeResult: ScrapeResult<string>) => void;
|
||||
scrapeResult: ScrapeResult<GQL.ScrapedStudio>;
|
||||
setScrapeResult: (scrapeResult: ScrapeResult<GQL.ScrapedStudio>) => void;
|
||||
setNewObject: (newObject: GQL.ScrapedStudio | undefined) => void;
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +62,12 @@ export function useCreateScrapedStudio(props: IUseCreateNewStudioProps) {
|
|||
});
|
||||
|
||||
// set the new studio as the value
|
||||
setScrapeResult(scrapeResult.cloneWithValue(result.data!.studioCreate!.id));
|
||||
setScrapeResult(
|
||||
scrapeResult.cloneWithValue({
|
||||
stored_id: result.data!.studioCreate!.id,
|
||||
name: toCreate.name,
|
||||
})
|
||||
);
|
||||
setNewObject(undefined);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import Select, {
|
||||
OnChangeValue,
|
||||
StylesConfig,
|
||||
|
|
@ -15,9 +15,7 @@ import CreatableSelect from "react-select/creatable";
|
|||
import * as GQL from "src/core/generated-graphql";
|
||||
import {
|
||||
useAllMoviesForFilter,
|
||||
useAllStudiosForFilter,
|
||||
useMarkerStrings,
|
||||
useStudioCreate,
|
||||
useMovieCreate,
|
||||
} from "src/core/StashService";
|
||||
import { useToast } from "src/hooks/Toast";
|
||||
|
|
@ -33,6 +31,7 @@ import { PerformerIDSelect } from "../Performers/PerformerSelect";
|
|||
import { Icon } from "./Icon";
|
||||
import { faTableColumns } from "@fortawesome/free-solid-svg-icons";
|
||||
import { TagIDSelect } from "../Tags/TagSelect";
|
||||
import { StudioIDSelect } from "../Studios/StudioSelect";
|
||||
|
||||
export type SelectObject = {
|
||||
id: string;
|
||||
|
|
@ -534,144 +533,7 @@ export const PerformerSelect: React.FC<IFilterProps> = (props) => {
|
|||
export const StudioSelect: React.FC<
|
||||
IFilterProps & { excludeIds?: string[] }
|
||||
> = (props) => {
|
||||
const [studioAliases, setStudioAliases] = useState<Record<string, string[]>>(
|
||||
{}
|
||||
);
|
||||
const [allAliases, setAllAliases] = useState<string[]>([]);
|
||||
const { data, loading } = useAllStudiosForFilter();
|
||||
const [createStudio] = useStudioCreate();
|
||||
const intl = useIntl();
|
||||
|
||||
const { configuration } = React.useContext(ConfigurationContext);
|
||||
const defaultCreatable =
|
||||
!configuration?.interface.disableDropdownCreate.studio ?? true;
|
||||
|
||||
const exclude = useMemo(() => props.excludeIds ?? [], [props.excludeIds]);
|
||||
const studios = useMemo(
|
||||
() =>
|
||||
(data?.allStudios ?? []).filter((studio) => !exclude.includes(studio.id)),
|
||||
[data?.allStudios, exclude]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// build the studio aliases map
|
||||
const newAliases: Record<string, string[]> = {};
|
||||
const newAll: string[] = [];
|
||||
studios.forEach((s) => {
|
||||
newAliases[s.id] = s.aliases;
|
||||
newAll.push(...s.aliases);
|
||||
});
|
||||
setStudioAliases(newAliases);
|
||||
setAllAliases(newAll);
|
||||
}, [studios]);
|
||||
|
||||
const StudioOption: React.FC<OptionProps<Option, boolean>> = (
|
||||
optionProps
|
||||
) => {
|
||||
const { inputValue } = optionProps.selectProps;
|
||||
|
||||
let thisOptionProps = optionProps;
|
||||
if (
|
||||
inputValue &&
|
||||
!optionProps.label.toLowerCase().includes(inputValue.toLowerCase())
|
||||
) {
|
||||
// must be alias
|
||||
const newLabel = `${optionProps.data.label} (alias)`;
|
||||
thisOptionProps = {
|
||||
...optionProps,
|
||||
children: newLabel,
|
||||
};
|
||||
}
|
||||
|
||||
return <reactSelectComponents.Option {...thisOptionProps} />;
|
||||
};
|
||||
|
||||
const filterOption = (option: Option, rawInput: string): boolean => {
|
||||
if (!rawInput) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const input = rawInput.toLowerCase();
|
||||
const optionVal = option.label.toLowerCase();
|
||||
|
||||
if (optionVal.includes(input)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// search for studio aliases
|
||||
const aliases = studioAliases[option.value];
|
||||
// only match on alias if exact
|
||||
if (aliases && aliases.some((a) => a.toLowerCase() === input)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const onCreate = async (name: string) => {
|
||||
const result = await createStudio({
|
||||
variables: {
|
||||
input: { name },
|
||||
},
|
||||
});
|
||||
return {
|
||||
item: result.data!.studioCreate!,
|
||||
message: intl.formatMessage(
|
||||
{ id: "toast.created_entity" },
|
||||
{ entity: intl.formatMessage({ id: "studio" }).toLocaleLowerCase() }
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const isValidNewOption = (
|
||||
inputValue: string,
|
||||
value: OnChangeValue<Option, boolean>,
|
||||
options: OptionsOrGroups<Option, GroupBase<Option>>
|
||||
) => {
|
||||
if (!inputValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
(options as Options<Option>).some((o: Option) => {
|
||||
return o.label.toLowerCase() === inputValue.toLowerCase();
|
||||
})
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (allAliases.some((a) => a.toLowerCase() === inputValue.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<FilterSelectComponent
|
||||
{...props}
|
||||
filterOption={filterOption}
|
||||
isValidNewOption={isValidNewOption}
|
||||
components={{ Option: StudioOption }}
|
||||
isMulti={props.isMulti ?? false}
|
||||
type="studios"
|
||||
isLoading={loading}
|
||||
items={studios}
|
||||
placeholder={
|
||||
props.noSelectionString ??
|
||||
intl.formatMessage(
|
||||
{ id: "actions.select_entity" },
|
||||
{
|
||||
entityType: intl.formatMessage({
|
||||
id: props.isMulti ? "studios" : "studio",
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
creatable={props.creatable ?? defaultCreatable}
|
||||
onCreate={onCreate}
|
||||
/>
|
||||
);
|
||||
return <StudioIDSelect {...props} />;
|
||||
};
|
||||
|
||||
export const MovieSelect: React.FC<IFilterProps> = (props) => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import * as GQL from "src/core/generated-graphql";
|
|||
import * as yup from "yup";
|
||||
import Mousetrap from "mousetrap";
|
||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||
import { StudioSelect } from "src/components/Shared/Select";
|
||||
import { DetailsEditNavbar } from "src/components/Shared/DetailsEditNavbar";
|
||||
import { Form } from "react-bootstrap";
|
||||
import ImageUtils from "src/utils/image";
|
||||
|
|
@ -16,6 +15,7 @@ import { useToast } from "src/hooks/Toast";
|
|||
import { handleUnsavedChanges } from "src/utils/navigation";
|
||||
import { formikUtils } from "src/utils/form";
|
||||
import { yupFormikValidate, yupUniqueAliases } from "src/utils/yup";
|
||||
import { Studio, StudioSelect } from "../StudioSelect";
|
||||
|
||||
interface IStudioEditPanel {
|
||||
studio: Partial<GQL.StudioDataFragment>;
|
||||
|
|
@ -42,6 +42,8 @@ export const StudioEditPanel: React.FC<IStudioEditPanel> = ({
|
|||
// Network state
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [parentStudio, setParentStudio] = useState<Studio | null>(null);
|
||||
|
||||
const schema = yup.object({
|
||||
name: yup.string().required(),
|
||||
url: yup.string().ensure(),
|
||||
|
|
@ -73,10 +75,27 @@ export const StudioEditPanel: React.FC<IStudioEditPanel> = ({
|
|||
onSubmit: (values) => onSave(schema.cast(values)),
|
||||
});
|
||||
|
||||
function onSetParentStudio(item: Studio | null) {
|
||||
setParentStudio(item);
|
||||
formik.setFieldValue("parent_id", item ? item.id : null);
|
||||
}
|
||||
|
||||
const encodingImage = ImageUtils.usePasteImage((imageData) =>
|
||||
formik.setFieldValue("image", imageData)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setParentStudio(
|
||||
studio.parent_studio
|
||||
? {
|
||||
id: studio.parent_studio.id,
|
||||
name: studio.parent_studio.name,
|
||||
aliases: [],
|
||||
}
|
||||
: null
|
||||
);
|
||||
}, [studio.parent_studio]);
|
||||
|
||||
useEffect(() => {
|
||||
setImage(formik.values.image);
|
||||
}, [formik.values.image, setImage]);
|
||||
|
|
@ -129,12 +148,9 @@ export const StudioEditPanel: React.FC<IStudioEditPanel> = ({
|
|||
const control = (
|
||||
<StudioSelect
|
||||
onSelect={(items) =>
|
||||
formik.setFieldValue(
|
||||
"parent_id",
|
||||
items.length > 0 ? items[0]?.id : null
|
||||
)
|
||||
onSetParentStudio(items.length > 0 ? items[0] : null)
|
||||
}
|
||||
ids={formik.values.parent_id ? [formik.values.parent_id] : []}
|
||||
values={parentStudio ? [parentStudio] : []}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
|||
259
ui/v2.5/src/components/Studios/StudioSelect.tsx
Normal file
259
ui/v2.5/src/components/Studios/StudioSelect.tsx
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
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 {
|
||||
useStudioCreate,
|
||||
queryFindStudiosByIDForSelect,
|
||||
queryFindStudiosForSelect,
|
||||
} from "src/core/StashService";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { useIntl } from "react-intl";
|
||||
import { defaultMaxOptionsShown, IUIConfig } 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";
|
||||
|
||||
export type SelectObject = {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
title?: string | null;
|
||||
};
|
||||
|
||||
export type Studio = Pick<GQL.Studio, "id" | "name" | "aliases" | "image_path">;
|
||||
type Option = SelectOption<Studio>;
|
||||
|
||||
export const StudioSelect: React.FC<
|
||||
IFilterProps &
|
||||
IFilterValueProps<Studio> & {
|
||||
hoverPlacement?: Placement;
|
||||
excludeIds?: string[];
|
||||
}
|
||||
> = (props) => {
|
||||
const [createStudio] = useStudioCreate();
|
||||
|
||||
const { configuration } = React.useContext(ConfigurationContext);
|
||||
const intl = useIntl();
|
||||
const maxOptionsShown =
|
||||
(configuration?.ui as IUIConfig).maxOptionsShown ?? defaultMaxOptionsShown;
|
||||
const defaultCreatable =
|
||||
!configuration?.interface.disableDropdownCreate.studio ?? true;
|
||||
|
||||
const exclude = useMemo(() => props.excludeIds ?? [], [props.excludeIds]);
|
||||
|
||||
async function loadStudios(input: string): Promise<Option[]> {
|
||||
const filter = new ListFilterModel(GQL.FilterMode.Studios);
|
||||
filter.searchTerm = input;
|
||||
filter.currentPage = 1;
|
||||
filter.itemsPerPage = maxOptionsShown;
|
||||
filter.sortBy = "name";
|
||||
filter.sortDirection = GQL.SortDirectionEnum.Asc;
|
||||
const query = await queryFindStudiosForSelect(filter);
|
||||
return query.data.findStudios.studios
|
||||
.filter((studio) => {
|
||||
// HACK - we should probably exclude these in the backend query, but
|
||||
// this will do in the short-term
|
||||
return !exclude.includes(studio.id.toString());
|
||||
})
|
||||
.map((studio) => ({
|
||||
value: studio.id,
|
||||
object: studio,
|
||||
}));
|
||||
}
|
||||
|
||||
const StudioOption: React.FC<OptionProps<Option, boolean>> = (
|
||||
optionProps
|
||||
) => {
|
||||
let thisOptionProps = optionProps;
|
||||
|
||||
const { object } = optionProps.data;
|
||||
|
||||
let { name } = object;
|
||||
|
||||
// if name does not match the input value but an alias does, show the alias
|
||||
const { inputValue } = optionProps.selectProps;
|
||||
let alias: string | undefined = "";
|
||||
if (!name.toLowerCase().includes(inputValue.toLowerCase())) {
|
||||
alias = object.aliases?.find((a) =>
|
||||
a.toLowerCase().includes(inputValue.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
thisOptionProps = {
|
||||
...optionProps,
|
||||
children: (
|
||||
<span className="react-select-image-option">
|
||||
<span>{name}</span>
|
||||
{alias && <span className="alias">{` (${alias})`}</span>}
|
||||
</span>
|
||||
),
|
||||
};
|
||||
|
||||
return <reactSelectComponents.Option {...thisOptionProps} />;
|
||||
};
|
||||
|
||||
const StudioMultiValueLabel: React.FC<
|
||||
MultiValueGenericProps<Option, boolean>
|
||||
> = (optionProps) => {
|
||||
let thisOptionProps = optionProps;
|
||||
|
||||
const { object } = optionProps.data;
|
||||
|
||||
thisOptionProps = {
|
||||
...optionProps,
|
||||
children: object.name,
|
||||
};
|
||||
|
||||
return <reactSelectComponents.MultiValueLabel {...thisOptionProps} />;
|
||||
};
|
||||
|
||||
const StudioValueLabel: React.FC<SingleValueProps<Option, boolean>> = (
|
||||
optionProps
|
||||
) => {
|
||||
let thisOptionProps = optionProps;
|
||||
|
||||
const { object } = optionProps.data;
|
||||
|
||||
thisOptionProps = {
|
||||
...optionProps,
|
||||
children: <>{object.name}</>,
|
||||
};
|
||||
|
||||
return <reactSelectComponents.SingleValue {...thisOptionProps} />;
|
||||
};
|
||||
|
||||
const onCreate = async (name: string) => {
|
||||
const result = await createStudio({
|
||||
variables: { input: { name } },
|
||||
});
|
||||
return {
|
||||
value: result.data!.studioCreate!.id,
|
||||
item: result.data!.studioCreate!,
|
||||
message: "Created studio",
|
||||
};
|
||||
};
|
||||
|
||||
const getNamedObject = (id: string, name: string) => {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
aliases: [],
|
||||
};
|
||||
};
|
||||
|
||||
const isValidNewOption = (inputValue: string, options: Studio[]) => {
|
||||
if (!inputValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
options.some((o) => {
|
||||
return (
|
||||
o.name.toLowerCase() === inputValue.toLowerCase() ||
|
||||
o.aliases?.some((a) => a.toLowerCase() === inputValue.toLowerCase())
|
||||
);
|
||||
})
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<FilterSelectComponent<Studio, boolean>
|
||||
{...props}
|
||||
className={cx(
|
||||
"studio-select",
|
||||
{
|
||||
"studio-select-active": props.active,
|
||||
},
|
||||
props.className
|
||||
)}
|
||||
loadOptions={loadStudios}
|
||||
getNamedObject={getNamedObject}
|
||||
isValidNewOption={isValidNewOption}
|
||||
components={{
|
||||
Option: StudioOption,
|
||||
MultiValueLabel: StudioMultiValueLabel,
|
||||
SingleValue: StudioValueLabel,
|
||||
}}
|
||||
isMulti={props.isMulti ?? false}
|
||||
creatable={props.creatable ?? defaultCreatable}
|
||||
onCreate={onCreate}
|
||||
placeholder={
|
||||
props.noSelectionString ??
|
||||
intl.formatMessage(
|
||||
{ id: "actions.select_entity" },
|
||||
{
|
||||
entityType: intl.formatMessage({
|
||||
id: props.isMulti ? "studios" : "studio",
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
closeMenuOnSelect={!props.isMulti}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const StudioIDSelect: React.FC<IFilterProps & IFilterIDProps<Studio>> = (
|
||||
props
|
||||
) => {
|
||||
const { ids, onSelect: onSelectValues } = props;
|
||||
|
||||
const [values, setValues] = useState<Studio[]>([]);
|
||||
const idsChanged = useCompare(ids);
|
||||
|
||||
function onSelect(items: Studio[]) {
|
||||
setValues(items);
|
||||
onSelectValues?.(items);
|
||||
}
|
||||
|
||||
async function loadObjectsByID(idsToLoad: string[]): Promise<Studio[]> {
|
||||
const studioIDs = idsToLoad.map((id) => parseInt(id));
|
||||
const query = await queryFindStudiosByIDForSelect(studioIDs);
|
||||
const { studios: loadedStudios } = query.data.findStudios;
|
||||
|
||||
return loadedStudios;
|
||||
}
|
||||
|
||||
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 <StudioSelect {...props} values={values} onSelect={onSelect} />;
|
||||
};
|
||||
|
|
@ -319,7 +319,22 @@ export const queryFindStudios = (filter: ListFilterModel) =>
|
|||
},
|
||||
});
|
||||
|
||||
export const useAllStudiosForFilter = () => GQL.useAllStudiosForFilterQuery();
|
||||
export const queryFindStudiosByIDForSelect = (studioIDs: number[]) =>
|
||||
client.query<GQL.FindStudiosForSelectQuery>({
|
||||
query: GQL.FindStudiosForSelectDocument,
|
||||
variables: {
|
||||
ids: studioIDs,
|
||||
},
|
||||
});
|
||||
|
||||
export const queryFindStudiosForSelect = (filter: ListFilterModel) =>
|
||||
client.query<GQL.FindStudiosForSelectQuery>({
|
||||
query: GQL.FindStudiosForSelectDocument,
|
||||
variables: {
|
||||
filter: filter.makeFindFilter(),
|
||||
studio_filter: filter.makeFilter(),
|
||||
},
|
||||
});
|
||||
|
||||
export const useFindTag = (id: string) => {
|
||||
const skip = id === "new" || id === "";
|
||||
|
|
@ -1519,8 +1534,6 @@ export const useStudioCreate = () =>
|
|||
const studio = result.data?.studioCreate;
|
||||
if (!studio || !variables) return;
|
||||
|
||||
appendObject(cache, studio, GQL.AllStudiosForFilterDocument);
|
||||
|
||||
// update stats
|
||||
updateStats(cache, "studio_count", 1);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue