From bdb8dc94d3d59e5437068c27abbda9a0e1b2ab69 Mon Sep 17 00:00:00 2001 From: stashcoder42 <92344712+stashcoder42@users.noreply.github.com> Date: Tue, 26 Oct 2021 18:37:18 -0400 Subject: [PATCH] Add page titles using react-helmet (#1831) * add titles with react-helmet --- ui/v2.5/package.json | 2 + ui/v2.5/src/App.tsx | 7 +- .../components/Changelog/versions/v0110.md | 1 + .../src/components/Galleries/Galleries.tsx | 42 ++++-- .../Galleries/GalleryDetails/Gallery.tsx | 6 + .../components/Images/ImageDetails/Image.tsx | 5 + ui/v2.5/src/components/Images/Images.tsx | 40 +++-- .../components/Movies/MovieDetails/Movie.tsx | 5 + ui/v2.5/src/components/Movies/Movies.tsx | 30 +++- .../Performers/PerformerDetails/Performer.tsx | 5 + .../src/components/Performers/Performers.tsx | 41 ++++-- .../components/Scenes/SceneDetails/Scene.tsx | 4 + .../src/components/Scenes/SceneMarkerList.tsx | 16 +- ui/v2.5/src/components/Scenes/Scenes.tsx | 41 ++++-- ui/v2.5/src/components/Settings/Settings.tsx | 12 +- ui/v2.5/src/components/Shared/index.ts | 1 + .../Studios/StudioDetails/Studio.tsx | 6 + ui/v2.5/src/components/Studios/Studios.tsx | 29 +++- .../src/components/Tags/TagDetails/Tag.tsx | 139 ++++++++++-------- ui/v2.5/src/components/Tags/Tags.tsx | 30 +++- ui/v2.5/yarn.lock | 27 ++++ 21 files changed, 349 insertions(+), 140 deletions(-) diff --git a/ui/v2.5/package.json b/ui/v2.5/package.json index 53cae46d5..ca2bb0fd2 100644 --- a/ui/v2.5/package.json +++ b/ui/v2.5/package.json @@ -58,6 +58,7 @@ "react": "17.0.1", "react-bootstrap": "1.4.3", "react-dom": "17.0.1", + "react-helmet": "^6.1.0", "react-intl": "^5.10.16", "react-jw-player": "1.19.1", "react-markdown": "^5.0.3", @@ -89,6 +90,7 @@ "@types/node": "14.14.22", "@types/react": "17.0.0", "@types/react-dom": "^17.0.0", + "@types/react-helmet": "^6.1.3", "@types/react-router-bootstrap": "^0.24.5", "@types/react-router-dom": "5.1.7", "@types/react-router-hash-link": "^1.2.1", diff --git a/ui/v2.5/src/App.tsx b/ui/v2.5/src/App.tsx index 071f9954b..c210c0220 100755 --- a/ui/v2.5/src/App.tsx +++ b/ui/v2.5/src/App.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from "react"; import { Route, Switch, useRouteMatch } from "react-router-dom"; import { IntlProvider } from "react-intl"; +import { Helmet } from "react-helmet"; import { mergeWith } from "lodash"; import { ToastProvider } from "src/hooks/Toast"; import LightboxProvider from "src/hooks/Lightbox/context"; @@ -30,7 +31,7 @@ import Images from "./components/Images/Images"; import { Setup } from "./components/Setup/Setup"; import { Migrate } from "./components/Setup/Migrate"; import * as GQL from "./core/generated-graphql"; -import { LoadingIndicator } from "./components/Shared"; +import { LoadingIndicator, TITLE_SUFFIX } from "./components/Shared"; import ConfigurationProvider from "./hooks/Config"; initPolyfills(); @@ -145,6 +146,10 @@ export const App: React.FC = () => { > + {maybeRenderNavbar()}
{renderContent()}
diff --git a/ui/v2.5/src/components/Changelog/versions/v0110.md b/ui/v2.5/src/components/Changelog/versions/v0110.md index 80a3ea4c5..248d0e43b 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0110.md +++ b/ui/v2.5/src/components/Changelog/versions/v0110.md @@ -5,6 +5,7 @@ * Added interface options to disable creating performers/studios/tags from dropdown selectors. ([#1814](https://github.com/stashapp/stash/pull/1814)) ### 🎨 Improvements +* Added specific page titles. ([#1831](https://github.com/stashapp/stash/pull/1831)) * Added es-ES language option. ([#1886](https://github.com/stashapp/stash/pull/1886)) * Show pagination at top and bottom of page. ([#1776](https://github.com/stashapp/stash/pull/1776)) * Include total duration/megapixels and filesize information on Scenes and Images pages. ([#1776](https://github.com/stashapp/stash/pull/1776)) diff --git a/ui/v2.5/src/components/Galleries/Galleries.tsx b/ui/v2.5/src/components/Galleries/Galleries.tsx index c7c064154..7dd4d3b97 100644 --- a/ui/v2.5/src/components/Galleries/Galleries.tsx +++ b/ui/v2.5/src/components/Galleries/Galleries.tsx @@ -1,22 +1,38 @@ 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"; import { PersistanceLevel } from "src/hooks/ListHook"; import Gallery from "./GalleryDetails/Gallery"; import GalleryCreate from "./GalleryDetails/GalleryCreate"; import { GalleryList } from "./GalleryList"; -const Galleries = () => ( - - ( - - )} - /> - - - -); +const Galleries = () => { + const intl = useIntl(); + + const title_template = `${intl.formatMessage({ + id: "galleries", + })} ${TITLE_SUFFIX}`; + return ( + <> + + + ( + + )} + /> + + + + + ); +}; export default Galleries; diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx index 15ffa2d31..4daadbc05 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx @@ -2,6 +2,7 @@ import { Tab, Nav, Dropdown } from "react-bootstrap"; import React, { useEffect, useState } from "react"; import { useParams, useHistory, Link } from "react-router-dom"; import { FormattedMessage, useIntl } from "react-intl"; +import { Helmet } from "react-helmet"; import * as GQL from "src/core/generated-graphql"; import { mutateMetadataScan, @@ -270,6 +271,11 @@ export const GalleryPage: React.FC = ({ gallery }) => { return (
+ + + {gallery.title ?? TextUtils.fileNameFromPath(gallery.path ?? "")} + + {maybeRenderDeleteDialog()}
diff --git a/ui/v2.5/src/components/Images/ImageDetails/Image.tsx b/ui/v2.5/src/components/Images/ImageDetails/Image.tsx index da1233d58..cfa800fa5 100644 --- a/ui/v2.5/src/components/Images/ImageDetails/Image.tsx +++ b/ui/v2.5/src/components/Images/ImageDetails/Image.tsx @@ -2,6 +2,7 @@ import { Tab, Nav, Dropdown } from "react-bootstrap"; import React, { useEffect, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { useParams, useHistory, Link } from "react-router-dom"; +import { Helmet } from "react-helmet"; import { useFindImage, useImageIncrementO, @@ -262,6 +263,10 @@ export const Image: React.FC = () => { return (
+ + {image.title ?? TextUtils.fileNameFromPath(image.path)} + + {maybeRenderDeleteDialog()}
diff --git a/ui/v2.5/src/components/Images/Images.tsx b/ui/v2.5/src/components/Images/Images.tsx index dfbd6224c..be16ed0b6 100644 --- a/ui/v2.5/src/components/Images/Images.tsx +++ b/ui/v2.5/src/components/Images/Images.tsx @@ -1,20 +1,36 @@ 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"; import { PersistanceLevel } from "src/hooks/ListHook"; import { Image } from "./ImageDetails/Image"; import { ImageList } from "./ImageList"; -const Images = () => ( - - ( - - )} - /> - - -); +const Images: React.FC = () => { + const intl = useIntl(); + + const title_template = `${intl.formatMessage({ + id: "images", + })} ${TITLE_SUFFIX}`; + return ( + <> + + + ( + + )} + /> + + + + ); +}; export default Images; diff --git a/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx b/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx index 77f7066be..2edd6d407 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; +import { Helmet } from "react-helmet"; import Mousetrap from "mousetrap"; import * as GQL from "src/core/generated-graphql"; import { @@ -174,6 +175,10 @@ const MoviePage: React.FC = ({ movie }) => { // TODO: CSS class return (
+ + {movie?.name} + +
{encodingImage ? ( diff --git a/ui/v2.5/src/components/Movies/Movies.tsx b/ui/v2.5/src/components/Movies/Movies.tsx index a07e33903..af9b501b3 100644 --- a/ui/v2.5/src/components/Movies/Movies.tsx +++ b/ui/v2.5/src/components/Movies/Movies.tsx @@ -1,15 +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"; import Movie from "./MovieDetails/Movie"; import MovieCreate from "./MovieDetails/MovieCreate"; import { MovieList } from "./MovieList"; -const Movies = () => ( - - - - - -); +const Movies: React.FC = () => { + const intl = useIntl(); + + const title_template = `${intl.formatMessage({ + id: "movies", + })} ${TITLE_SUFFIX}`; + return ( + <> + + + + + + + + ); +}; export default Movies; diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx index 341dc13e8..c31f7ef0a 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from "react"; import { Button, Tabs, Tab } from "react-bootstrap"; import { FormattedMessage, useIntl } from "react-intl"; import { useParams, useHistory } from "react-router-dom"; +import { Helmet } from "react-helmet"; import cx from "classnames"; import Mousetrap from "mousetrap"; import * as GQL from "src/core/generated-graphql"; @@ -299,6 +300,10 @@ const PerformerPage: React.FC = ({ performer }) => { return (
+ + {performer.name} + +
{imageEncoding ? ( diff --git a/ui/v2.5/src/components/Performers/Performers.tsx b/ui/v2.5/src/components/Performers/Performers.tsx index f221feba4..027b441bd 100644 --- a/ui/v2.5/src/components/Performers/Performers.tsx +++ b/ui/v2.5/src/components/Performers/Performers.tsx @@ -1,22 +1,37 @@ 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"; import { PersistanceLevel } from "src/hooks/ListHook"; import Performer from "./PerformerDetails/Performer"; import PerformerCreate from "./PerformerDetails/PerformerCreate"; import { PerformerList } from "./PerformerList"; -const Performers = () => ( - - ( - - )} - /> - - - -); +const Performers: React.FC = () => { + const intl = useIntl(); + const title_template = `${intl.formatMessage({ + id: "performers", + })} ${TITLE_SUFFIX}`; + return ( + <> + + + ( + + )} + /> + + + + + ); +}; export default Performers; diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx index 413f981e5..dc1fd1b87 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx @@ -3,6 +3,7 @@ import queryString from "query-string"; import React, { useEffect, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { useParams, useLocation, useHistory, Link } from "react-router-dom"; +import { Helmet } from "react-helmet"; import * as GQL from "src/core/generated-graphql"; import { mutateMetadataScan, @@ -566,6 +567,9 @@ const ScenePage: React.FC = ({ scene, refetch }) => { return (
+ + {scene.title ?? TextUtils.fileNameFromPath(scene.path)} + {maybeRenderSceneGenerateDialog()} {maybeRenderDeleteDialog()}
= ({ filterHook }) => { ); } } + const title_template = `${intl.formatMessage({ + id: "markers", + })} ${TITLE_SUFFIX}`; - return listData.template; + return ( + <> + + + {listData.template} + + ); }; diff --git a/ui/v2.5/src/components/Scenes/Scenes.tsx b/ui/v2.5/src/components/Scenes/Scenes.tsx index d485d3a16..b49d5296c 100644 --- a/ui/v2.5/src/components/Scenes/Scenes.tsx +++ b/ui/v2.5/src/components/Scenes/Scenes.tsx @@ -1,22 +1,37 @@ 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"; import { PersistanceLevel } from "src/hooks/ListHook"; import Scene from "./SceneDetails/Scene"; import { SceneList } from "./SceneList"; import { SceneMarkerList } from "./SceneMarkerList"; -const Scenes = () => ( - - ( - - )} - /> - - - -); +const Scenes: React.FC = () => { + const intl = useIntl(); + const title_template = `${intl.formatMessage({ + id: "scenes", + })} ${TITLE_SUFFIX}`; + return ( + <> + + + ( + + )} + /> + + + + + ); +}; export default Scenes; diff --git a/ui/v2.5/src/components/Settings/Settings.tsx b/ui/v2.5/src/components/Settings/Settings.tsx index 49c8f6c38..eb626af99 100644 --- a/ui/v2.5/src/components/Settings/Settings.tsx +++ b/ui/v2.5/src/components/Settings/Settings.tsx @@ -2,7 +2,9 @@ import React from "react"; import queryString from "query-string"; import { Card, Tab, Nav, Row, Col } from "react-bootstrap"; import { useHistory, useLocation } from "react-router-dom"; -import { FormattedMessage } from "react-intl"; +import { FormattedMessage, useIntl } from "react-intl"; +import { Helmet } from "react-helmet"; +import { TITLE_SUFFIX } from "src/components/Shared"; import { SettingsAboutPanel } from "./SettingsAboutPanel"; import { SettingsConfigurationPanel } from "./SettingsConfigurationPanel"; import { SettingsInterfacePanel } from "./SettingsInterfacePanel/SettingsInterfacePanel"; @@ -14,14 +16,22 @@ import { SettingsToolsPanel } from "./SettingsToolsPanel"; import { SettingsDLNAPanel } from "./SettingsDLNAPanel"; export const Settings: React.FC = () => { + const intl = useIntl(); const location = useLocation(); const history = useHistory(); const defaultTab = queryString.parse(location.search).tab ?? "tasks"; const onSelect = (val: string) => history.push(`?tab=${val}`); + const title_template = `${intl.formatMessage({ + id: "settings", + })} ${TITLE_SUFFIX}`; return ( + = ({ studio }) => {
{!isEditing ? ( <> + + + {studio.name ?? intl.formatMessage({ id: "studio" })} + + ( - - - - - -); +const Studios: React.FC = () => { + const intl = useIntl(); + const title_template = `${intl.formatMessage({ + id: "studios", + })} ${TITLE_SUFFIX}`; + return ( + <> + + + + + + + + ); +}; export default Studios; diff --git a/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx b/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx index 7bf1f74af..99259be34 100644 --- a/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx +++ b/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx @@ -2,6 +2,7 @@ import { Tabs, Tab, Dropdown } from "react-bootstrap"; import React, { useEffect, useState } from "react"; import { useParams, useHistory } from "react-router-dom"; import { FormattedMessage, useIntl } from "react-intl"; +import { Helmet } from "react-helmet"; import Mousetrap from "mousetrap"; import * as GQL from "src/core/generated-graphql"; @@ -248,76 +249,84 @@ const TagPage: React.FC = ({ tag }) => { } return ( -
-
-
- {imageEncoding ? ( - + <> + + {tag.name} + +
+
+
+ {imageEncoding ? ( + + ) : ( + renderImage() + )} +

{tag.name}

+
+ {!isEditing ? ( + <> + + {/* HACK - this is also rendered in the TagEditPanel */} + {}} + onImageChange={() => {}} + onClearImage={() => {}} + onAutoTag={onAutoTag} + onDelete={onDelete} + customButtons={renderMergeButton()} + /> + ) : ( - renderImage() - )} -

{tag.name}

-
- {!isEditing ? ( - <> - - {/* HACK - this is also rendered in the TagEditPanel */} - {}} - onImageChange={() => {}} - onClearImage={() => {}} - onAutoTag={onAutoTag} + - - ) : ( - - )} -
-
- - - - - - - - +
+ - - - - - - - - - + + + + + + + + + + + + + + + + +
+ {renderDeleteAlert()} + {renderMergeDialog()}
- {renderDeleteAlert()} - {renderMergeDialog()} -
+ ); }; diff --git a/ui/v2.5/src/components/Tags/Tags.tsx b/ui/v2.5/src/components/Tags/Tags.tsx index b9ffe7d31..c5f8bd9dc 100644 --- a/ui/v2.5/src/components/Tags/Tags.tsx +++ b/ui/v2.5/src/components/Tags/Tags.tsx @@ -1,15 +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"; import Tag from "./TagDetails/Tag"; import TagCreate from "./TagDetails/TagCreate"; import { TagList } from "./TagList"; -const Tags = () => ( - - - - - -); +const Tags: React.FC = () => { + const intl = useIntl(); + const title_template = `${intl.formatMessage({ + id: "tags", + })} ${TITLE_SUFFIX}`; + return ( + <> + + + + + + + + + ); +}; export default Tags; diff --git a/ui/v2.5/yarn.lock b/ui/v2.5/yarn.lock index 1a40e8444..b6b4300c7 100644 --- a/ui/v2.5/yarn.lock +++ b/ui/v2.5/yarn.lock @@ -3072,6 +3072,13 @@ dependencies: "@types/react" "*" +"@types/react-helmet@^6.1.3": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.3.tgz#1a58b26a79e464c59d3f9cdd5b7ece485335937b" + integrity sha512-U4onVxaZxAp78KpXsfmyCIhLjsvJJ3goG3CYFOo+xW0cPYAz9oe5cBAUSAcN7l35OTbrFvu9TuE0YkcZMKGr4A== + dependencies: + "@types/react" "*" + "@types/react-router-bootstrap@^0.24.5": version "0.24.5" resolved "https://registry.yarnpkg.com/@types/react-router-bootstrap/-/react-router-bootstrap-0.24.5.tgz#9257ba3dfb01cda201aac9fa05cde3eb09ea5b27" @@ -12635,6 +12642,21 @@ react-fast-compare@^2.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== +react-fast-compare@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" + integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== + +react-helmet@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" + integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== + dependencies: + object-assign "^4.1.1" + prop-types "^15.7.2" + react-fast-compare "^3.1.1" + react-side-effect "^2.1.0" + react-input-autosize@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85" @@ -12836,6 +12858,11 @@ react-select@^4.0.2: react-input-autosize "^3.0.0" react-transition-group "^4.3.0" +react-side-effect@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3" + integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ== + react-transition-group@^4.3.0, react-transition-group@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"