diff --git a/ui/v2.5/src/components/Changelog/versions/v0130.md b/ui/v2.5/src/components/Changelog/versions/v0130.md index f0276b26b..a389b43cd 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0130.md +++ b/ui/v2.5/src/components/Changelog/versions/v0130.md @@ -1,4 +1,5 @@ ### 🎨 Improvements +* Made Performer page consistent with Studio and Tag pages. ([#2200](https://github.com/stashapp/stash/pull/2200)) * Add gender icons to performers. ([#2179](https://github.com/stashapp/stash/pull/2179)) * Add button to test credentials when adding/editing stash-box endpoints. ([#2173](https://github.com/stashapp/stash/pull/2173)) * Show counts on list tabs in Performer, Studio and Tag pages. ([#2169](https://github.com/stashapp/stash/pull/2169)) diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx index e07e8bf3c..64d70b3e1 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx @@ -470,7 +470,7 @@ export const MovieEditPanel: React.FC = ({ = ({ gender, className }) => { : faTransgenderAlt; return ( diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx index 38bbc9ef8..a2c12c3d6 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState } from "react"; -import { Button, Tabs, Tab, Badge } from "react-bootstrap"; +import { Button, Tabs, Tab, Badge, Col, Row } from "react-bootstrap"; import { FormattedMessage, useIntl } from "react-intl"; import { useParams, useHistory } from "react-router-dom"; import { Helmet } from "react-helmet"; @@ -10,9 +10,11 @@ import { useFindPerformer, usePerformerUpdate, usePerformerDestroy, + mutateMetadataAutoTag, } from "src/core/StashService"; import { CountryFlag, + DetailsEditNavbar, ErrorMessage, Icon, LoadingIndicator, @@ -21,7 +23,6 @@ import { useLightbox, useToast } from "src/hooks"; import { TextUtils } from "src/utils"; import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars"; import { PerformerDetailsPanel } from "./PerformerDetailsPanel"; -import { PerformerOperationsPanel } from "./PerformerOperationsPanel"; import { PerformerScenesPanel } from "./PerformerScenesPanel"; import { PerformerGalleriesPanel } from "./PerformerGalleriesPanel"; import { PerformerMoviesPanel } from "./PerformerMoviesPanel"; @@ -44,6 +45,7 @@ const PerformerPage: React.FC = ({ performer }) => { const [imagePreview, setImagePreview] = useState(); const [imageEncoding, setImageEncoding] = useState(false); + const [isEditing, setIsEditing] = useState(false); // if undefined then get the existing image // if null then get the default (no) image @@ -68,9 +70,7 @@ const PerformerPage: React.FC = ({ performer }) => { tab === "scenes" || tab === "galleries" || tab === "images" || - tab === "movies" || - tab === "edit" || - tab === "operations" + tab === "movies" ? tab : "details"; const setActiveTabKey = (newTab: string | null) => { @@ -84,14 +84,24 @@ const PerformerPage: React.FC = ({ performer }) => { const onImageEncoding = (isEncoding = false) => setImageEncoding(isEncoding); + async function onAutoTag() { + try { + await mutateMetadataAutoTag({ performers: [performer.id] }); + Toast.success({ + content: intl.formatMessage({ id: "toast.started_auto_tagging" }), + }); + } catch (e) { + Toast.error(e); + } + } + // set up hotkeys useEffect(() => { Mousetrap.bind("a", () => setActiveTabKey("details")); - Mousetrap.bind("e", () => setActiveTabKey("edit")); + Mousetrap.bind("e", () => setIsEditing(!isEditing)); Mousetrap.bind("c", () => setActiveTabKey("scenes")); Mousetrap.bind("g", () => setActiveTabKey("galleries")); Mousetrap.bind("m", () => setActiveTabKey("movies")); - Mousetrap.bind("o", () => setActiveTabKey("operations")); Mousetrap.bind("f", () => setFavorite(!performer.favorite)); // numeric keypresses get caught by jwplayer, so blur the element @@ -139,85 +149,108 @@ const PerformerPage: React.FC = ({ performer }) => { } const renderTabs = () => ( - - - - - - {intl.formatMessage({ id: "scenes" })} - - {intl.formatNumber(performer.scene_count ?? 0)} - - - } + + + + { + setIsEditing(!isEditing); + }} + onDelete={onDelete} + onAutoTag={onAutoTag} + isNew={false} + isEditing={false} + onSave={() => {}} + onImageChange={() => {}} + /> + + + - - - - {intl.formatMessage({ id: "galleries" })} - - {intl.formatNumber(performer.gallery_count ?? 0)} - - - } - > - - - - {intl.formatMessage({ id: "images" })} - - {intl.formatNumber(performer.image_count ?? 0)} - - - } - > - - - - {intl.formatMessage({ id: "movies" })} - - {intl.formatNumber(performer.movie_count ?? 0)} - - - } - > - - - + + + + + {intl.formatMessage({ id: "scenes" })} + + {intl.formatNumber(performer.scene_count ?? 0)} + + + } + > + + + + {intl.formatMessage({ id: "galleries" })} + + {intl.formatNumber(performer.gallery_count ?? 0)} + + + } + > + + + + {intl.formatMessage({ id: "images" })} + + {intl.formatNumber(performer.image_count ?? 0)} + + + } + > + + + + {intl.formatMessage({ id: "movies" })} + + {intl.formatNumber(performer.movie_count ?? 0)} + + + } + > + + + + + ); + + function renderTabsOrEditPanel() { + if (isEditing) { + return ( { + setIsEditing(false); + }} /> - - - - - - ); + ); + } else { + return renderTabs(); + } + } function maybeRenderAge() { if (performer?.birthdate) { @@ -375,7 +408,7 @@ const PerformerPage: React.FC = ({ performer }) => {
-
{renderTabs()}
+
{renderTabsOrEditPanel()}
diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx index 75c754142..2c7c16ff3 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx @@ -96,10 +96,10 @@ export const PerformerDetailsPanel: React.FC = ({ return (
diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx index 1cd525e27..99fd8c5bc 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import { Button, Form, Col, Row, Badge, Dropdown } from "react-bootstrap"; -import { FormattedMessage, useIntl } from "react-intl"; +import { FormattedMessage } from "react-intl"; import Mousetrap from "mousetrap"; import * as GQL from "src/core/generated-graphql"; import * as yup from "yup"; @@ -18,7 +18,6 @@ import { ImageInput, LoadingIndicator, CollapseButton, - Modal, TagSelect, URLField, } from "src/components/Shared"; @@ -46,20 +45,19 @@ interface IPerformerDetails { performer: Partial; isNew?: boolean; isVisible: boolean; - onDelete?: () => void; onImageChange?: (image?: string | null) => void; onImageEncoding?: (loading?: boolean) => void; + onCancelEditing?: () => void; } export const PerformerEditPanel: React.FC = ({ performer, isNew, isVisible, - onDelete, onImageChange, onImageEncoding, + onCancelEditing, }) => { - const intl = useIntl(); const Toast = useToast(); const history = useHistory(); @@ -67,7 +65,6 @@ export const PerformerEditPanel: React.FC = ({ const [scraper, setScraper] = useState(); const [newTags, setNewTags] = useState(); const [isScraperModalOpen, setIsScraperModalOpen] = useState(false); - const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); // Network state const [isLoading, setIsLoading] = useState(false); @@ -361,7 +358,17 @@ export const PerformerEditPanel: React.FC = ({ async function onSave(performerInput: InputValues) { setIsLoading(true); try { - if (!isNew) { + if (isNew) { + const input = getCreateValues(performerInput); + const result = await createPerformer({ + variables: { + input, + }, + }); + if (result.data?.performerCreate) { + history.push(`/performers/${result.data.performerCreate.id}`); + } + } else { const input = getUpdateValues(performerInput); await updatePerformer({ @@ -372,20 +379,14 @@ export const PerformerEditPanel: React.FC = ({ }, }, }); - history.push(`/performers/${performer.id}`); - } else { - const input = getCreateValues(performerInput); - const result = await createPerformer({ - variables: { - input, - }, - }); - if (result.data?.performerCreate) { - history.push(`/performers/${result.data.performerCreate.id}`); - } } } catch (e) { Toast.error(e); + setIsLoading(false); + return; + } + if (!isNew && onCancelEditing) { + onCancelEditing(); } setIsLoading(false); } @@ -397,12 +398,6 @@ export const PerformerEditPanel: React.FC = ({ onSave?.(formik.values); }); - if (!isNew) { - Mousetrap.bind("d d", () => { - setIsDeleteAlertOpen(true); - }); - } - return () => { Mousetrap.unbind("s s"); @@ -655,25 +650,17 @@ export const PerformerEditPanel: React.FC = ({ setScraper(undefined); } - function renderButtons() { + function renderButtons(classNames: string) { return ( - - - {!isNew ? ( + + {!isNew && onCancelEditing ? ( ) : ( "" @@ -685,12 +672,19 @@ export const PerformerEditPanel: React.FC = ({ onImageURL={onImageChangeURL} /> + ); @@ -716,28 +710,6 @@ export const PerformerEditPanel: React.FC = ({ ) : undefined; }; - function renderDeleteAlert() { - return ( - setIsDeleteAlertOpen(false) }} - > -

- -

-
- ); - } - function renderTagsField() { return ( @@ -837,7 +809,6 @@ export const PerformerEditPanel: React.FC = ({ return ( <> - {renderDeleteAlert()} {renderScrapeModal()} {maybeRenderScrapeDialog()} @@ -845,6 +816,7 @@ export const PerformerEditPanel: React.FC = ({ when={formik.dirty} message="Unsaved changes. Are you sure you want to leave?" /> + {renderButtons("mb-3")}
@@ -880,7 +852,7 @@ export const PerformerEditPanel: React.FC = ({ - + = ({ {renderStashIDs()} - {renderButtons()} + {renderButtons("mt-3")} ); diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerOperationsPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerOperationsPanel.tsx deleted file mode 100644 index 29723be54..000000000 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerOperationsPanel.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Button } from "react-bootstrap"; -import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; -import * as GQL from "src/core/generated-graphql"; -import { mutateMetadataAutoTag } from "src/core/StashService"; -import { useToast } from "src/hooks"; - -interface IPerformerOperationsProps { - performer: GQL.PerformerDataFragment; -} - -export const PerformerOperationsPanel: React.FC = ({ - performer, -}) => { - const Toast = useToast(); - const intl = useIntl(); - - async function onAutoTag() { - try { - await mutateMetadataAutoTag({ performers: [performer.id] }); - Toast.success({ - content: intl.formatMessage({ id: "toast.started_auto_tagging" }), - }); - } catch (e) { - Toast.error(e); - } - } - - return ( - - ); -}; diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScrapeDialog.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScrapeDialog.tsx index 4ac91ec8b..9225cb976 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScrapeDialog.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScrapeDialog.tsx @@ -429,7 +429,7 @@ export const PerformerScrapeDialog: React.FC = ( onChange={(value) => setAliases(value)} /> {renderScrapedGenderRow( - intl.formatMessage({ id: "gender.gender" }), + intl.formatMessage({ id: "gender" }), gender, (value) => setGender(value) )} diff --git a/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx b/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx index fa793fa12..fa7af6832 100644 --- a/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx +++ b/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx @@ -318,7 +318,7 @@ export const StudioEditPanel: React.FC = ({ = ({ {renderField( "gender", performer.gender - ? intl.formatMessage({ id: "gender." + performer.gender }) + ? intl.formatMessage({ id: "gender_types." + performer.gender }) : "" )} {renderField("birthdate", performer.birthdate)} diff --git a/ui/v2.5/src/components/Tags/TagDetails/TagEditPanel.tsx b/ui/v2.5/src/components/Tags/TagDetails/TagEditPanel.tsx index 379550037..a59e8cea9 100644 --- a/ui/v2.5/src/components/Tags/TagDetails/TagEditPanel.tsx +++ b/ui/v2.5/src/components/Tags/TagDetails/TagEditPanel.tsx @@ -214,7 +214,7 @@ export const TagEditPanel: React.FC = ({