mirror of
https://github.com/stashapp/stash.git
synced 2025-12-29 11:45:56 +01:00
studios
This commit is contained in:
parent
17ed91bab9
commit
de0a03f73e
3 changed files with 117 additions and 4 deletions
|
|
@ -14,15 +14,17 @@ import { Icon } from "src/components/Shared/Icon";
|
|||
import {
|
||||
stashBoxPerformerQuery,
|
||||
stashBoxSceneQuery,
|
||||
stashBoxStudioQuery,
|
||||
} from "src/core/StashService";
|
||||
import { useToast } from "src/hooks/Toast";
|
||||
import { stringToGender } from "src/utils/gender";
|
||||
|
||||
type SearchResultItem =
|
||||
| GQL.ScrapedPerformerDataFragment
|
||||
| GQL.ScrapedSceneDataFragment;
|
||||
| GQL.ScrapedSceneDataFragment
|
||||
| GQL.ScrapedStudioDataFragment;
|
||||
|
||||
export type StashBoxEntityType = "performer" | "scene";
|
||||
export type StashBoxEntityType = "performer" | "scene" | "studio";
|
||||
|
||||
interface IProps {
|
||||
entityType: StashBoxEntityType;
|
||||
|
|
@ -190,6 +192,46 @@ export const SceneSearchResult: React.FC<ISceneResultProps> = ({ scene }) => {
|
|||
);
|
||||
};
|
||||
|
||||
// Studio Result Component
|
||||
interface IStudioResultProps {
|
||||
studio: GQL.ScrapedStudioDataFragment;
|
||||
}
|
||||
|
||||
const StudioSearchResultDetails: React.FC<IStudioResultProps> = ({
|
||||
studio,
|
||||
}) => {
|
||||
return (
|
||||
<div className="studio-result">
|
||||
<Row>
|
||||
<SearchResultImage imageUrl={studio.image} />
|
||||
<div className="col flex-column">
|
||||
<h4 className="studio-name">
|
||||
<span>{studio.name}</span>
|
||||
</h4>
|
||||
{studio.parent?.name && (
|
||||
<h5 className="studio-parent">
|
||||
<span>{studio.parent.name}</span>
|
||||
</h5>
|
||||
)}
|
||||
{studio.urls && studio.urls.length > 0 && (
|
||||
<div className="studio-url text-muted small">{studio.urls[0]}</div>
|
||||
)}
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const StudioSearchResult: React.FC<IStudioResultProps> = ({
|
||||
studio,
|
||||
}) => {
|
||||
return (
|
||||
<div className="mt-3 search-item" style={{ cursor: "pointer" }}>
|
||||
<StudioSearchResultDetails studio={studio} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Helper to get entity type display name for i18n
|
||||
function getEntityTypeDisplayName(entityType: StashBoxEntityType): string {
|
||||
switch (entityType) {
|
||||
|
|
@ -197,6 +239,8 @@ function getEntityTypeDisplayName(entityType: StashBoxEntityType): string {
|
|||
return "Performer";
|
||||
case "scene":
|
||||
return "Scene";
|
||||
case "studio":
|
||||
return "Studio";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -207,6 +251,8 @@ function getFoundMessageId(entityType: StashBoxEntityType): string {
|
|||
return "dialogs.performers_found";
|
||||
case "scene":
|
||||
return "dialogs.scenes_found";
|
||||
case "studio":
|
||||
return "dialogs.studios_found";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -264,6 +310,14 @@ export const StashBoxIDSearchModal: React.FC<IProps> = ({
|
|||
setResults(queryData.data?.scrapeSingleScene ?? []);
|
||||
break;
|
||||
}
|
||||
case "studio": {
|
||||
const queryData = await stashBoxStudioQuery(
|
||||
query,
|
||||
selectedStashBox.endpoint
|
||||
);
|
||||
setResults(queryData.data?.scrapeSingleStudio ?? []);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Toast.error(error);
|
||||
|
|
@ -299,6 +353,10 @@ export const StashBoxIDSearchModal: React.FC<IProps> = ({
|
|||
return (
|
||||
<SceneSearchResult scene={item as GQL.ScrapedSceneDataFragment} />
|
||||
);
|
||||
case "studio":
|
||||
return (
|
||||
<StudioSearchResult studio={item as GQL.ScrapedStudioDataFragment} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,18 +5,22 @@ import * as yup from "yup";
|
|||
import Mousetrap from "mousetrap";
|
||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||
import { DetailsEditNavbar } from "src/components/Shared/DetailsEditNavbar";
|
||||
import { Form } from "react-bootstrap";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import ImageUtils from "src/utils/image";
|
||||
import { getStashIDs } from "src/utils/stashIds";
|
||||
import { useFormik } from "formik";
|
||||
import { Prompt } from "react-router-dom";
|
||||
import isEqual from "lodash-es/isEqual";
|
||||
import { useToast } from "src/hooks/Toast";
|
||||
import { useConfigurationContext } from "src/hooks/Config";
|
||||
import { handleUnsavedChanges } from "src/utils/navigation";
|
||||
import { formikUtils } from "src/utils/form";
|
||||
import { yupFormikValidate, yupUniqueAliases } from "src/utils/yup";
|
||||
import { Studio, StudioSelect } from "../StudioSelect";
|
||||
import { useTagsEdit } from "src/hooks/tagsEdit";
|
||||
import { Icon } from "src/components/Shared/Icon";
|
||||
import StashBoxIDSearchModal from "src/components/Shared/StashBoxIDSearchModal";
|
||||
|
||||
interface IStudioEditPanel {
|
||||
studio: Partial<GQL.StudioDataFragment>;
|
||||
|
|
@ -37,9 +41,13 @@ export const StudioEditPanel: React.FC<IStudioEditPanel> = ({
|
|||
}) => {
|
||||
const intl = useIntl();
|
||||
const Toast = useToast();
|
||||
const { configuration: stashConfig } = useConfigurationContext();
|
||||
|
||||
const isNew = studio.id === undefined;
|
||||
|
||||
// Editing state
|
||||
const [isStashIDSearchOpen, setIsStashIDSearchOpen] = useState(false);
|
||||
|
||||
// Network state
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
|
|
@ -143,6 +151,24 @@ export const StudioEditPanel: React.FC<IStudioEditPanel> = ({
|
|||
ImageUtils.onImageChange(event, onImageLoad);
|
||||
}
|
||||
|
||||
function onStashIDSelected(item?: GQL.StashIdInput) {
|
||||
if (!item) return;
|
||||
|
||||
const existingIndex = formik.values.stash_ids.findIndex(
|
||||
(s) => s.endpoint === item.endpoint
|
||||
);
|
||||
|
||||
let newStashIDs;
|
||||
if (existingIndex >= 0) {
|
||||
newStashIDs = [...formik.values.stash_ids];
|
||||
newStashIDs[existingIndex] = item;
|
||||
} else {
|
||||
newStashIDs = [...formik.values.stash_ids, item];
|
||||
}
|
||||
|
||||
formik.setFieldValue("stash_ids", newStashIDs);
|
||||
}
|
||||
|
||||
const {
|
||||
renderField,
|
||||
renderInputField,
|
||||
|
|
@ -173,6 +199,20 @@ export const StudioEditPanel: React.FC<IStudioEditPanel> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
{isStashIDSearchOpen && (
|
||||
<StashBoxIDSearchModal
|
||||
entityType="studio"
|
||||
stashBoxes={stashConfig?.general.stashBoxes ?? []}
|
||||
excludedStashBoxEndpoints={formik.values.stash_ids.map(
|
||||
(s) => s.endpoint
|
||||
)}
|
||||
onSelectItem={(item) => {
|
||||
onStashIDSelected(item);
|
||||
setIsStashIDSearchOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Prompt
|
||||
when={formik.dirty}
|
||||
message={(location, action) => {
|
||||
|
|
@ -191,7 +231,21 @@ export const StudioEditPanel: React.FC<IStudioEditPanel> = ({
|
|||
{renderInputField("details", "textarea")}
|
||||
{renderParentStudioField()}
|
||||
{renderTagsField()}
|
||||
{renderStashIDsField("stash_ids", "studios")}
|
||||
{renderStashIDsField(
|
||||
"stash_ids",
|
||||
"studios",
|
||||
"stash_ids",
|
||||
undefined,
|
||||
<Button
|
||||
variant="success"
|
||||
className="mr-2 py-0"
|
||||
onClick={() => setIsStashIDSearchOpen(true)}
|
||||
disabled={!stashConfig?.general.stashBoxes?.length}
|
||||
title={intl.formatMessage({ id: "actions.add_stash_id" })}
|
||||
>
|
||||
<Icon icon={faPlus} />
|
||||
</Button>
|
||||
)}
|
||||
<hr />
|
||||
{renderInputField("ignore_auto_tag", "checkbox")}
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -1014,6 +1014,7 @@
|
|||
"video_previews_tooltip": "Video previews which play when hovering over a scene"
|
||||
},
|
||||
"scenes_found": "{count} scenes found",
|
||||
"studios_found": "{count} studios found",
|
||||
"scrape_entity_query": "{entity_type} Scrape Query",
|
||||
"scrape_entity_title": "{entity_type} Scrape Results",
|
||||
"scrape_results_existing": "Existing",
|
||||
|
|
|
|||
Loading…
Reference in a new issue