From 49cd214c9d1cd0f225f56391e0f5edbfe898de0c Mon Sep 17 00:00:00 2001 From: bdbenim Date: Wed, 13 Mar 2024 16:34:24 -0700 Subject: [PATCH] Make directors and photographers clickable in detail view (#4621) * Make directors and photographers clickable * Make director clickable on movie details page --------- Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com> --- .../GalleryDetails/GalleryDetailPanel.tsx | 7 ++- .../Images/ImageDetails/ImageDetailPanel.tsx | 7 ++- .../Movies/MovieDetails/MovieDetailsPanel.tsx | 13 ++++- .../Scenes/SceneDetails/SceneDetailPanel.tsx | 4 +- ui/v2.5/src/components/Shared/Link.tsx | 37 ++++++++++++ ui/v2.5/src/utils/navigation.ts | 56 +++++++++++++++++++ 6 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 ui/v2.5/src/components/Shared/Link.tsx diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx index b80c281dd..9b03bed1b 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx @@ -9,6 +9,7 @@ import { PerformerCard } from "src/components/Performers/PerformerCard"; import { RatingSystem } from "src/components/Shared/Rating/RatingSystem"; import { sortPerformers } from "src/core/performers"; import { galleryTitle } from "src/core/galleries"; +import { PhotographerLink } from "src/components/Shared/Link"; interface IGalleryDetailProps { gallery: GQL.GalleryDataFragment; @@ -118,7 +119,11 @@ export const GalleryDetailPanel: React.FC = ({ )} {gallery.photographer && (
- : {gallery.photographer}{" "} + :{" "} +
)} diff --git a/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx b/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx index d8b84c79c..dbd8ac43b 100644 --- a/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx +++ b/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx @@ -9,6 +9,7 @@ import { RatingSystem } from "src/components/Shared/Rating/RatingSystem"; import { sortPerformers } from "src/core/performers"; import { FormattedDate, FormattedMessage, useIntl } from "react-intl"; import { objectTitle } from "src/core/files"; +import { PhotographerLink } from "src/components/Shared/Link"; interface IImageDetailProps { image: GQL.ImageDataFragment; } @@ -154,7 +155,11 @@ export const ImageDetailPanel: React.FC = (props) => { )} {props.image.photographer && (
- : {props.image.photographer}{" "} + :{" "} +
)} diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx index b9d0d7ef7..97957f7f8 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx @@ -4,6 +4,7 @@ import * as GQL from "src/core/generated-graphql"; import TextUtils from "src/utils/text"; import { DetailItem } from "src/components/Shared/DetailItem"; import { Link } from "react-router-dom"; +import { DirectorLink } from "src/components/Shared/Link"; interface IMovieDetailsPanel { movie: GQL.MovieDataFragment; @@ -45,7 +46,17 @@ export const MovieDetailsPanel: React.FC = ({ fullWidth={fullWidth} /> - + + ) : ( + "" + ) + } + fullWidth={fullWidth} + /> ); diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneDetailPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneDetailPanel.tsx index e52775960..729be465a 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneDetailPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneDetailPanel.tsx @@ -9,6 +9,7 @@ import { PerformerCard } from "src/components/Performers/PerformerCard"; import { sortPerformers } from "src/core/performers"; import { RatingSystem } from "src/components/Shared/Rating/RatingSystem"; import { objectTitle } from "src/core/files"; +import { DirectorLink } from "src/components/Shared/Link"; interface ISceneDetailProps { scene: GQL.SceneDataFragment; @@ -128,7 +129,8 @@ export const SceneDetailPanel: React.FC = (props) => { )} {props.scene.director && (
- : {props.scene.director}{" "} + :{" "} +
)} diff --git a/ui/v2.5/src/components/Shared/Link.tsx b/ui/v2.5/src/components/Shared/Link.tsx new file mode 100644 index 000000000..7bfeef413 --- /dev/null +++ b/ui/v2.5/src/components/Shared/Link.tsx @@ -0,0 +1,37 @@ +import { useMemo } from "react"; +import { Link } from "react-router-dom"; +import NavUtils from "src/utils/navigation"; + +// common link components + +export const DirectorLink: React.FC<{ + director: string; + linkType: "scene" | "movie"; +}> = ({ director: director, linkType = "scene" }) => { + const link = useMemo(() => { + switch (linkType) { + case "scene": + return NavUtils.makeDirectorScenesUrl(director); + case "movie": + return NavUtils.makeDirectorMoviesUrl(director); + } + }, [director, linkType]); + + return {director}; +}; + +export const PhotographerLink: React.FC<{ + photographer: string; + linkType: "gallery" | "image"; +}> = ({ photographer, linkType = "image" }) => { + const link = useMemo(() => { + switch (linkType) { + case "gallery": + return NavUtils.makePhotographerGalleriesUrl(photographer); + case "image": + return NavUtils.makePhotographerImagesUrl(photographer); + } + }, [photographer, linkType]); + + return {photographer}; +}; diff --git a/ui/v2.5/src/utils/navigation.ts b/ui/v2.5/src/utils/navigation.ts index 0d2509227..1aece914c 100644 --- a/ui/v2.5/src/utils/navigation.ts +++ b/ui/v2.5/src/utils/navigation.ts @@ -15,7 +15,10 @@ import { ListFilterModel } from "src/models/list-filter/filter"; import { MoviesCriterion } from "src/models/list-filter/criteria/movies"; import { Criterion, + CriterionOption, CriterionValue, + StringCriterion, + createStringCriterionOption, } from "src/models/list-filter/criteria/criterion"; import { GalleriesCriterion } from "src/models/list-filter/criteria/galleries"; import { PhashCriterion } from "src/models/list-filter/criteria/phash"; @@ -355,6 +358,55 @@ const makeGalleryImagesUrl = ( return `/images?${filter.makeQueryParameters()}`; }; +function stringEqualsCriterion(option: CriterionOption, value: string) { + const criterion = new StringCriterion(option); + criterion.modifier = GQL.CriterionModifier.Equals; + criterion.value = value; + return criterion; +} + +const makeDirectorScenesUrl = (director: string) => { + if (director.length == 0) return "#"; + const filter = new ListFilterModel(GQL.FilterMode.Scenes, undefined); + filter.criteria.push( + stringEqualsCriterion(createStringCriterionOption("director"), director) + ); + return `/scenes?${filter.makeQueryParameters()}`; +}; + +const makeDirectorMoviesUrl = (director: string) => { + if (director.length == 0) return "#"; + const filter = new ListFilterModel(GQL.FilterMode.Movies, undefined); + filter.criteria.push( + stringEqualsCriterion(createStringCriterionOption("director"), director) + ); + return `/movies?${filter.makeQueryParameters()}`; +}; + +const makePhotographerGalleriesUrl = (photographer: string) => { + if (photographer.length == 0) return "#"; + const filter = new ListFilterModel(GQL.FilterMode.Galleries, undefined); + filter.criteria.push( + stringEqualsCriterion( + createStringCriterionOption("photographer"), + photographer + ) + ); + return `/galleries?${filter.makeQueryParameters()}`; +}; + +const makePhotographerImagesUrl = (photographer: string) => { + if (photographer.length == 0) return "#"; + const filter = new ListFilterModel(GQL.FilterMode.Images, undefined); + filter.criteria.push( + stringEqualsCriterion( + createStringCriterionOption("photographer"), + photographer + ) + ); + return `/images?${filter.makeQueryParameters()}`; +}; + export function handleUnsavedChanges( intl: IntlShape, basepath: string, @@ -394,6 +446,10 @@ const NavUtils = { makeMovieScenesUrl, makeChildStudiosUrl, makeGalleryImagesUrl, + makeDirectorScenesUrl, + makePhotographerGalleriesUrl, + makePhotographerImagesUrl, + makeDirectorMoviesUrl, }; export default NavUtils;