diff --git a/graphql/documents/data/gallery-slim.graphql b/graphql/documents/data/gallery-slim.graphql index 7247b4f9a..ebec04251 100644 --- a/graphql/documents/data/gallery-slim.graphql +++ b/graphql/documents/data/gallery-slim.graphql @@ -14,10 +14,10 @@ fragment SlimGalleryData on Gallery { } image_count cover { + id files { ...ImageFileData } - paths { thumbnail } diff --git a/ui/v2.5/src/App.tsx b/ui/v2.5/src/App.tsx index cda72d902..9ead5c9d5 100644 --- a/ui/v2.5/src/App.tsx +++ b/ui/v2.5/src/App.tsx @@ -28,7 +28,7 @@ import { ErrorBoundary } from "./components/ErrorBoundary"; import { MainNavbar } from "./components/MainNavbar"; import { PageNotFound } from "./components/PageNotFound"; import * as GQL from "./core/generated-graphql"; -import { TITLE_SUFFIX } from "./components/Shared/constants"; +import { makeTitleProps } from "./hooks/title"; import { LoadingIndicator } from "./components/Shared/LoadingIndicator"; import { ConfigurationProvider } from "./hooks/Config"; @@ -254,6 +254,8 @@ export const App: React.FC = () => { ); } + const titleProps = makeTitleProps(); + return ( {messages ? ( @@ -272,10 +274,7 @@ export const App: React.FC = () => { - + {maybeRenderNavbar()}
{ const intl = useIntl(); @@ -24,6 +25,8 @@ const FrontPage: React.FC = () => { const { configuration, loading } = React.useContext(ConfigurationContext); + useScrollToTopOnMount(); + async function onUpdateConfig(content?: FrontPageContent[]) { setIsEditing(false); diff --git a/ui/v2.5/src/components/Galleries/Galleries.tsx b/ui/v2.5/src/components/Galleries/Galleries.tsx index c2c6e9236..d61f124ac 100644 --- a/ui/v2.5/src/components/Galleries/Galleries.tsx +++ b/ui/v2.5/src/components/Galleries/Galleries.tsx @@ -1,33 +1,26 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; -import { useIntl } from "react-intl"; import { Helmet } from "react-helmet"; -import { TITLE_SUFFIX } from "../Shared/constants"; +import { useTitleProps } from "src/hooks/title"; import { PersistanceLevel } from "../List/ItemList"; import Gallery from "./GalleryDetails/Gallery"; import GalleryCreate from "./GalleryDetails/GalleryCreate"; import { GalleryList } from "./GalleryList"; +import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; const Galleries: React.FC = () => { - const intl = useIntl(); + useScrollToTopOnMount(); - const title_template = `${intl.formatMessage({ - id: "galleries", - })} ${TITLE_SUFFIX}`; + return ; +}; + +const GalleryRoutes: React.FC = () => { + const titleProps = useTitleProps({ id: "galleries" }); return ( <> - + - ( - - )} - /> + @@ -35,4 +28,4 @@ const Galleries: React.FC = () => { ); }; -export default Galleries; +export default GalleryRoutes; diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx index fc94d453d..eb17ddcf7 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx @@ -1,6 +1,11 @@ import { Button, Tab, Nav, Dropdown } from "react-bootstrap"; import React, { useEffect, useMemo, useState } from "react"; -import { useParams, useHistory, Link } from "react-router-dom"; +import { + useHistory, + Link, + RouteComponentProps, + Redirect, +} from "react-router-dom"; import { FormattedMessage, useIntl } from "react-intl"; import { Helmet } from "react-helmet"; import * as GQL from "src/core/generated-graphql"; @@ -31,17 +36,19 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { galleryPath, galleryTitle } from "src/core/galleries"; import { GalleryChapterPanel } from "./GalleryChaptersPanel"; +import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; interface IProps { gallery: GQL.GalleryDataFragment; + add?: boolean; } interface IGalleryParams { + id: string; tab?: string; } -export const GalleryPage: React.FC = ({ gallery }) => { - const { tab = "images" } = useParams(); +export const GalleryPage: React.FC = ({ gallery, add }) => { const history = useHistory(); const Toast = useToast(); const intl = useIntl(); @@ -50,11 +57,12 @@ export const GalleryPage: React.FC = ({ gallery }) => { const [collapsed, setCollapsed] = useState(false); const [activeTabKey, setActiveTabKey] = useState("gallery-details-panel"); - const activeRightTabKey = tab === "images" || tab === "add" ? tab : "images"; - const setActiveRightTabKey = (newTab: string | null) => { - if (tab !== newTab) { - const tabParam = newTab === "images" ? "" : `/${newTab}`; - history.replace(`/galleries/${gallery.id}${tabParam}`); + + const setMainTabKey = (newTabKey: string | null) => { + if (newTabKey === "add") { + history.replace(`/galleries/${gallery.id}/add`); + } else { + history.replace(`/galleries/${gallery.id}`); } }; @@ -281,9 +289,9 @@ export const GalleryPage: React.FC = ({ gallery }) => { return ( k && setActiveRightTabKey(k)} + onSelect={setMainTabKey} >
); }; + +const ImageLoader: React.FC> = ({ + match, +}) => { + const { id } = match.params; + const { data, loading, error } = useFindImage(id); + + useScrollToTopOnMount(); + + if (loading) return ; + if (error) return ; + if (!data?.findImage) + return ; + + return ; +}; + +export default ImageLoader; diff --git a/ui/v2.5/src/components/Images/Images.tsx b/ui/v2.5/src/components/Images/Images.tsx index dae428a5c..90c6858b0 100644 --- a/ui/v2.5/src/components/Images/Images.tsx +++ b/ui/v2.5/src/components/Images/Images.tsx @@ -1,36 +1,29 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; -import { useIntl } from "react-intl"; import { Helmet } from "react-helmet"; -import { TITLE_SUFFIX } from "../Shared/constants"; +import { useTitleProps } from "src/hooks/title"; import { PersistanceLevel } from "../List/ItemList"; -import { Image } from "./ImageDetails/Image"; +import Image from "./ImageDetails/Image"; import { ImageList } from "./ImageList"; +import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; const Images: React.FC = () => { - const intl = useIntl(); + useScrollToTopOnMount(); - const title_template = `${intl.formatMessage({ - id: "images", - })} ${TITLE_SUFFIX}`; + return ; +}; + +const ImageRoutes: React.FC = () => { + const titleProps = useTitleProps({ id: "images" }); return ( <> - + - ( - - )} - /> + ); }; -export default Images; +export default ImageRoutes; diff --git a/ui/v2.5/src/components/List/ItemList.tsx b/ui/v2.5/src/components/List/ItemList.tsx index cfde8fdf3..e8cabe7ce 100644 --- a/ui/v2.5/src/components/List/ItemList.tsx +++ b/ui/v2.5/src/components/List/ItemList.tsx @@ -153,9 +153,7 @@ export function makeItemList({ const [selectedIds, setSelectedIds] = useState>(new Set()); const [lastClickedId, setLastClickedId] = useState(); - const [editingCriterion, setEditingCriterion] = useState< - string | undefined - >(); + const [editingCriterion, setEditingCriterion] = useState(); const [showEditFilter, setShowEditFilter] = useState(false); const result = useResult(filter); @@ -701,7 +699,15 @@ export function makeItemList({ const newFilter = cloneDeep(filter); newFilter.currentPage = page; updateFilter(newFilter); - window.scrollTo(0, 0); + + // if the current page has a detail-header, then + // scroll up relative to that rather than 0, 0 + const detailHeader = document.querySelector(".detail-header"); + if (detailHeader) { + window.scrollTo(0, detailHeader.scrollHeight - 50); + } else { + window.scrollTo(0, 0); + } }, [filter, updateFilter] ); diff --git a/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx b/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx index 845da909b..c93e8e987 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from "react"; import { Button } from "react-bootstrap"; import { FormattedMessage, useIntl } from "react-intl"; import { Helmet } from "react-helmet"; +import cx from "classnames"; import Mousetrap from "mousetrap"; import * as GQL from "src/core/generated-graphql"; import { @@ -9,7 +10,7 @@ import { useMovieUpdate, useMovieDestroy, } from "src/core/StashService"; -import { useParams, useHistory } from "react-router-dom"; +import { useHistory, RouteComponentProps } from "react-router-dom"; import { DetailsEditNavbar } from "src/components/Shared/DetailsEditNavbar"; import { ErrorMessage } from "src/components/Shared/ErrorMessage"; import { LoadingIndicator } from "src/components/Shared/LoadingIndicator"; @@ -33,13 +34,19 @@ import { Icon } from "src/components/Shared/Icon"; import { RatingSystem } from "src/components/Shared/Rating/RatingSystem"; import { ConfigurationContext } from "src/hooks/Config"; import { IUIConfig } from "src/core/config"; -import ImageUtils from "src/utils/image"; +import { DetailImage } from "src/components/Shared/DetailImage"; import { useRatingKeybinds } from "src/hooks/keybinds"; +import { useLoadStickyHeader } from "src/hooks/detailsPanel"; +import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; interface IProps { movie: GQL.MovieDataFragment; } +interface IMovieParams { + id: string; +} + const MoviePage: React.FC = ({ movie }) => { const intl = useIntl(); const history = useHistory(); @@ -53,7 +60,7 @@ const MoviePage: React.FC = ({ movie }) => { const showAllDetails = uiConfig?.showAllDetails ?? true; const [collapsed, setCollapsed] = useState(!showAllDetails); - const [loadStickyHeader, setLoadStickyHeader] = useState(false); + const loadStickyHeader = useLoadStickyHeader(); // Editing state const [isEditing, setIsEditing] = useState(false); @@ -126,21 +133,6 @@ const MoviePage: React.FC = ({ movie }) => { setRating ); - useEffect(() => { - const f = () => { - if (document.documentElement.scrollTop <= 50) { - setLoadStickyHeader(false); - } else { - setLoadStickyHeader(true); - } - }; - - window.addEventListener("scroll", f); - return () => { - window.removeEventListener("scroll", f); - }; - }); - async function onSave(input: GQL.MovieCreateInput) { await updateMovie({ variables: { @@ -240,11 +232,7 @@ const MoviePage: React.FC = ({ movie }) => { if (image && defaultImage) { return (
- Front Cover +
); } else if (image) { @@ -254,11 +242,7 @@ const MoviePage: React.FC = ({ movie }) => { variant="link" onClick={() => showLightbox()} > - Front Cover + ); } @@ -281,11 +265,7 @@ const MoviePage: React.FC = ({ movie }) => { variant="link" onClick={() => showLightbox(index - 1)} > - Back Cover + ); } @@ -405,17 +385,19 @@ const MoviePage: React.FC = ({ movie }) => { if (updating || deleting) return ; + const headerClassName = cx("detail-header", { + edit: isEditing, + collapsed, + "full-width": !collapsed && !compactExpandedDetails, + }); + return (
{movie?.name} -
+
{maybeRenderHeaderBackgroundImage()}
@@ -461,9 +443,13 @@ const MoviePage: React.FC = ({ movie }) => { ); }; -const MovieLoader: React.FC = () => { - const { id } = useParams<{ id?: string }>(); - const { data, loading, error } = useFindMovie(id ?? ""); +const MovieLoader: React.FC> = ({ + match, +}) => { + const { id } = match.params; + const { data, loading, error } = useFindMovie(id); + + useScrollToTopOnMount(); if (loading) return ; if (error) return ; diff --git a/ui/v2.5/src/components/Movies/Movies.tsx b/ui/v2.5/src/components/Movies/Movies.tsx index 2c35759fb..19af947cc 100644 --- a/ui/v2.5/src/components/Movies/Movies.tsx +++ b/ui/v2.5/src/components/Movies/Movies.tsx @@ -1,26 +1,25 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; -import { useIntl } from "react-intl"; import { Helmet } from "react-helmet"; -import { TITLE_SUFFIX } from "src/components/Shared/constants"; +import { useTitleProps } from "src/hooks/title"; import Movie from "./MovieDetails/Movie"; import MovieCreate from "./MovieDetails/MovieCreate"; import { MovieList } from "./MovieList"; +import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; const Movies: React.FC = () => { - const intl = useIntl(); + useScrollToTopOnMount(); - const title_template = `${intl.formatMessage({ - id: "movies", - })} ${TITLE_SUFFIX}`; + return ; +}; + +const MovieRoutes: React.FC = () => { + const titleProps = useTitleProps({ id: "movies" }); return ( <> - + - + @@ -28,4 +27,4 @@ const Movies: React.FC = () => { ); }; -export default Movies; +export default MovieRoutes; diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx index 5771e46e8..015789fe1 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo, useState } from "react"; import { Button, Tabs, Tab, Col, Row } from "react-bootstrap"; import { useIntl } from "react-intl"; -import { useParams, useHistory } from "react-router-dom"; +import { useHistory, Redirect, RouteComponentProps } from "react-router-dom"; import { Helmet } from "react-helmet"; import cx from "classnames"; import Mousetrap from "mousetrap"; @@ -42,20 +42,39 @@ import { import { faInstagram, faTwitter } from "@fortawesome/free-brands-svg-icons"; import { IUIConfig } from "src/core/config"; import { useRatingKeybinds } from "src/hooks/keybinds"; -import ImageUtils from "src/utils/image"; +import { DetailImage } from "src/components/Shared/DetailImage"; +import { useLoadStickyHeader } from "src/hooks/detailsPanel"; +import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; interface IProps { performer: GQL.PerformerDataFragment; + tabKey: TabKey; } + interface IPerformerParams { + id: string; tab?: string; } -const PerformerPage: React.FC = ({ performer }) => { +const validTabs = [ + "scenes", + "galleries", + "images", + "movies", + "appearswith", +] as const; +type TabKey = (typeof validTabs)[number]; + +const defaultTab: TabKey = "scenes"; + +function isTabKey(tab: string): tab is TabKey { + return validTabs.includes(tab as TabKey); +} + +const PerformerPage: React.FC = ({ performer, tabKey }) => { const Toast = useToast(); const history = useHistory(); const intl = useIntl(); - const { tab = "details" } = useParams(); // Configuration settings const { configuration } = React.useContext(ConfigurationContext); @@ -70,7 +89,7 @@ const PerformerPage: React.FC = ({ performer }) => { const [isEditing, setIsEditing] = useState(false); const [image, setImage] = useState(); const [encodingImage, setEncodingImage] = useState(false); - const [loadStickyHeader, setLoadStickyHeader] = useState(false); + const loadStickyHeader = useLoadStickyHeader(); const activeImage = useMemo(() => { const performerImage = performer.image_path; @@ -98,20 +117,16 @@ const PerformerPage: React.FC = ({ performer }) => { const [updatePerformer] = usePerformerUpdate(); const [deletePerformer, { loading: isDestroying }] = usePerformerDestroy(); - const activeTabKey = - tab === "scenes" || - tab === "galleries" || - tab === "images" || - tab === "movies" || - tab == "appearswith" - ? tab - : "scenes"; - const setActiveTabKey = (newTab: string | null) => { - if (tab !== newTab) { - const tabParam = newTab === "scenes" ? "" : `/${newTab}`; - history.replace(`/performers/${performer.id}${tabParam}`); + function setTabKey(newTabKey: string | null) { + if (!newTabKey) newTabKey = defaultTab; + if (newTabKey === tabKey) return; + + if (newTabKey === defaultTab) { + history.replace(`/performers/${performer.id}`); + } else if (isTabKey(newTabKey)) { + history.replace(`/performers/${performer.id}/${newTabKey}`); } - }; + } async function onAutoTag() { try { @@ -133,18 +148,18 @@ const PerformerPage: React.FC = ({ performer }) => { // set up hotkeys useEffect(() => { Mousetrap.bind("e", () => toggleEditing()); - Mousetrap.bind("c", () => setActiveTabKey("scenes")); - Mousetrap.bind("g", () => setActiveTabKey("galleries")); - Mousetrap.bind("m", () => setActiveTabKey("movies")); + Mousetrap.bind("c", () => setTabKey("scenes")); + Mousetrap.bind("g", () => setTabKey("galleries")); + Mousetrap.bind("m", () => setTabKey("movies")); Mousetrap.bind("f", () => setFavorite(!performer.favorite)); Mousetrap.bind(",", () => setCollapsed(!collapsed)); return () => { - Mousetrap.unbind("a"); Mousetrap.unbind("e"); Mousetrap.unbind("c"); + Mousetrap.unbind("g"); + Mousetrap.unbind("m"); Mousetrap.unbind("f"); - Mousetrap.unbind("o"); Mousetrap.unbind(","); }; }); @@ -191,116 +206,114 @@ const PerformerPage: React.FC = ({ performer }) => { if (activeImage) { return ( ); } } const renderTabs = () => ( - - + + {intl.formatMessage({ id: "scenes" })} + + + } > - - {intl.formatMessage({ id: "scenes" })} - - - } - > - - - - {intl.formatMessage({ id: "galleries" })} - - - } - > - - - - {intl.formatMessage({ id: "images" })} - - - } - > - - - - {intl.formatMessage({ id: "movies" })} - - - } - > - - - - {intl.formatMessage({ id: "appears_with" })} - - - } - > - - - - + + + + {intl.formatMessage({ id: "galleries" })} + + + } + > + + + + {intl.formatMessage({ id: "images" })} + + + } + > + + + + {intl.formatMessage({ id: "movies" })} + + + } + > + + + + {intl.formatMessage({ id: "appears_with" })} + + + } + > + + + ); function maybeRenderHeaderBackgroundImage() { @@ -365,21 +378,6 @@ const PerformerPage: React.FC = ({ performer }) => { return collapsed ? faChevronDown : faChevronUp; } - useEffect(() => { - const f = () => { - if (document.documentElement.scrollTop <= 50) { - setLoadStickyHeader(false); - } else { - setLoadStickyHeader(true); - } - }; - - window.addEventListener("scroll", f); - return () => { - window.removeEventListener("scroll", f); - }; - }); - function maybeRenderDetails() { if (!isEditing) { return ( @@ -537,17 +535,19 @@ const PerformerPage: React.FC = ({ performer }) => { /> ); + const headerClassName = cx("detail-header", { + edit: isEditing, + collapsed, + "full-width": !collapsed && !compactExpandedDetails, + }); + return (
{performer.name} -
+
{maybeRenderHeaderBackgroundImage()}
@@ -592,16 +592,36 @@ const PerformerPage: React.FC = ({ performer }) => { ); }; -const PerformerLoader: React.FC = () => { - const { id } = useParams<{ id?: string }>(); - const { data, loading, error } = useFindPerformer(id ?? ""); +const PerformerLoader: React.FC> = ({ + location, + match, +}) => { + const { id, tab } = match.params; + const { data, loading, error } = useFindPerformer(id); + + useScrollToTopOnMount(); if (loading) return ; if (error) return ; if (!data?.findPerformer) return ; - return ; + if (!tab) { + return ; + } + + if (!isTabKey(tab)) { + return ( + + ); + } + + return ; }; export default PerformerLoader; diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx index 1f595b0ee..7cf1fab02 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx @@ -213,7 +213,7 @@ export const PerformerDetailsPanel: React.FC = ({ fullWidth={fullWidth} /> diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx index 5c2d26d3d..34aecd3c1 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx @@ -789,7 +789,7 @@ export const PerformerEditPanel: React.FC = ({ return ( - StashIDs + {intl.formatMessage({ id: "stash_ids" })}
    diff --git a/ui/v2.5/src/components/Performers/Performers.tsx b/ui/v2.5/src/components/Performers/Performers.tsx index 741a84cfe..d5ff46021 100644 --- a/ui/v2.5/src/components/Performers/Performers.tsx +++ b/ui/v2.5/src/components/Performers/Performers.tsx @@ -1,37 +1,31 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; -import { useIntl } from "react-intl"; import { Helmet } from "react-helmet"; -import { TITLE_SUFFIX } from "src/components/Shared/constants"; +import { useTitleProps } from "src/hooks/title"; import { PersistanceLevel } from "../List/ItemList"; import Performer from "./PerformerDetails/Performer"; import PerformerCreate from "./PerformerDetails/PerformerCreate"; import { PerformerList } from "./PerformerList"; +import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; const Performers: React.FC = () => { - const intl = useIntl(); + useScrollToTopOnMount(); - const title_template = `${intl.formatMessage({ - id: "performers", - })} ${TITLE_SUFFIX}`; + return ; +}; + +const PerformerRoutes: React.FC = () => { + const titleProps = useTitleProps({ id: "performers" }); return ( <> - + - ( - - )} - /> + ); }; -export default Performers; + +export default PerformerRoutes; diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx index 447cc8539..fbedf9bb9 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx @@ -8,7 +8,7 @@ import React, { useLayoutEffect, } from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import { useParams, useLocation, useHistory, Link } from "react-router-dom"; +import { Link, RouteComponentProps } from "react-router-dom"; import { Helmet } from "react-helmet"; import * as GQL from "src/core/generated-graphql"; import { @@ -93,6 +93,10 @@ interface IProps { setContinuePlaylist: (value: boolean) => void; } +interface ISceneParams { + id: string; +} + const ScenePage: React.FC = ({ scene, setTimestamp, @@ -539,12 +543,14 @@ const ScenePage: React.FC = ({ ); }; -const SceneLoader: React.FC = () => { - const { id } = useParams<{ id?: string }>(); - const location = useLocation(); - const history = useHistory(); +const SceneLoader: React.FC> = ({ + location, + history, + match, +}) => { + const { id } = match.params; const { configuration } = useContext(ConfigurationContext); - const { data, loading, error } = useFindScene(id ?? ""); + const { data, loading, error } = useFindScene(id); const [scene, setScene] = useState(); diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx index a0d2f5202..c8229d601 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx @@ -191,7 +191,9 @@ export const SceneFileInfoPanel: React.FC = ( return ( <> -
    StashIDs
    +
    + +
    {props.scene.stash_ids.map((stashID) => { diff --git a/ui/v2.5/src/components/Scenes/Scenes.tsx b/ui/v2.5/src/components/Scenes/Scenes.tsx index ea966e74a..995c63e1c 100644 --- a/ui/v2.5/src/components/Scenes/Scenes.tsx +++ b/ui/v2.5/src/components/Scenes/Scenes.tsx @@ -1,10 +1,10 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; -import { useIntl } from "react-intl"; import { Helmet } from "react-helmet"; -import { TITLE_SUFFIX } from "src/components/Shared/constants"; +import { useTitleProps } from "src/hooks/title"; import { PersistanceLevel } from "../List/ItemList"; import { lazyComponent } from "src/utils/lazyComponent"; +import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; const SceneList = lazyComponent(() => import("./SceneList")); const SceneMarkerList = lazyComponent(() => import("./SceneMarkerList")); @@ -12,46 +12,36 @@ const Scene = lazyComponent(() => import("./SceneDetails/Scene")); const SceneCreate = lazyComponent(() => import("./SceneDetails/SceneCreate")); const Scenes: React.FC = () => { - const intl = useIntl(); + useScrollToTopOnMount(); - const title_template = `${intl.formatMessage({ - id: "scenes", - })} ${TITLE_SUFFIX}`; - const marker_title_template = `${intl.formatMessage({ - id: "markers", - })} ${TITLE_SUFFIX}`; + return ; +}; +const SceneMarkers: React.FC = () => { + useScrollToTopOnMount(); + + const titleProps = useTitleProps({ id: "markers" }); return ( <> - + + + + ); +}; + +const SceneRoutes: React.FC = () => { + const titleProps = useTitleProps({ id: "scenes" }); + return ( + <> + - ( - - )} - /> - ( - <> - - - - )} - /> + + ); }; -export default Scenes; + +export default SceneRoutes; diff --git a/ui/v2.5/src/components/Settings/Settings.tsx b/ui/v2.5/src/components/Settings/Settings.tsx index 1b1149592..fb1b95d2a 100644 --- a/ui/v2.5/src/components/Settings/Settings.tsx +++ b/ui/v2.5/src/components/Settings/Settings.tsx @@ -1,9 +1,9 @@ import React from "react"; import { Tab, Nav, Row, Col } from "react-bootstrap"; import { useHistory, useLocation } from "react-router-dom"; -import { FormattedMessage, useIntl } from "react-intl"; +import { FormattedMessage } from "react-intl"; import { Helmet } from "react-helmet"; -import { TITLE_SUFFIX } from "src/components/Shared/constants"; +import { useTitleProps } from "src/hooks/title"; import { SettingsAboutPanel } from "./SettingsAboutPanel"; import { SettingsConfigurationPanel } from "./SettingsSystemPanel"; import { SettingsInterfacePanel } from "./SettingsInterfacePanel/SettingsInterfacePanel"; @@ -19,26 +19,20 @@ import { SettingsSecurityPanel } from "./SettingsSecurityPanel"; import Changelog from "../Changelog/Changelog"; export const Settings: React.FC = () => { - const intl = useIntl(); const location = useLocation(); const history = useHistory(); const defaultTab = new URLSearchParams(location.search).get("tab") ?? "tasks"; const onSelect = (val: string) => history.push(`?tab=${val}`); - const title_template = `${intl.formatMessage({ - id: "settings", - })} ${TITLE_SUFFIX}`; + const titleProps = useTitleProps({ id: "settings" }); return ( tab && onSelect(tab)} > - +