mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Add page titles using react-helmet (#1831)
* add titles with react-helmet
This commit is contained in:
@@ -58,6 +58,7 @@
|
|||||||
"react": "17.0.1",
|
"react": "17.0.1",
|
||||||
"react-bootstrap": "1.4.3",
|
"react-bootstrap": "1.4.3",
|
||||||
"react-dom": "17.0.1",
|
"react-dom": "17.0.1",
|
||||||
|
"react-helmet": "^6.1.0",
|
||||||
"react-intl": "^5.10.16",
|
"react-intl": "^5.10.16",
|
||||||
"react-jw-player": "1.19.1",
|
"react-jw-player": "1.19.1",
|
||||||
"react-markdown": "^5.0.3",
|
"react-markdown": "^5.0.3",
|
||||||
@@ -89,6 +90,7 @@
|
|||||||
"@types/node": "14.14.22",
|
"@types/node": "14.14.22",
|
||||||
"@types/react": "17.0.0",
|
"@types/react": "17.0.0",
|
||||||
"@types/react-dom": "^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-bootstrap": "^0.24.5",
|
||||||
"@types/react-router-dom": "5.1.7",
|
"@types/react-router-dom": "5.1.7",
|
||||||
"@types/react-router-hash-link": "^1.2.1",
|
"@types/react-router-hash-link": "^1.2.1",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Route, Switch, useRouteMatch } from "react-router-dom";
|
import { Route, Switch, useRouteMatch } from "react-router-dom";
|
||||||
import { IntlProvider } from "react-intl";
|
import { IntlProvider } from "react-intl";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
import { mergeWith } from "lodash";
|
import { mergeWith } from "lodash";
|
||||||
import { ToastProvider } from "src/hooks/Toast";
|
import { ToastProvider } from "src/hooks/Toast";
|
||||||
import LightboxProvider from "src/hooks/Lightbox/context";
|
import LightboxProvider from "src/hooks/Lightbox/context";
|
||||||
@@ -30,7 +31,7 @@ import Images from "./components/Images/Images";
|
|||||||
import { Setup } from "./components/Setup/Setup";
|
import { Setup } from "./components/Setup/Setup";
|
||||||
import { Migrate } from "./components/Setup/Migrate";
|
import { Migrate } from "./components/Setup/Migrate";
|
||||||
import * as GQL from "./core/generated-graphql";
|
import * as GQL from "./core/generated-graphql";
|
||||||
import { LoadingIndicator } from "./components/Shared";
|
import { LoadingIndicator, TITLE_SUFFIX } from "./components/Shared";
|
||||||
import ConfigurationProvider from "./hooks/Config";
|
import ConfigurationProvider from "./hooks/Config";
|
||||||
|
|
||||||
initPolyfills();
|
initPolyfills();
|
||||||
@@ -145,6 +146,10 @@ export const App: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<LightboxProvider>
|
<LightboxProvider>
|
||||||
|
<Helmet
|
||||||
|
titleTemplate={`%s ${TITLE_SUFFIX}`}
|
||||||
|
defaultTitle="Stash"
|
||||||
|
/>
|
||||||
{maybeRenderNavbar()}
|
{maybeRenderNavbar()}
|
||||||
<div className="main container-fluid">{renderContent()}</div>
|
<div className="main container-fluid">{renderContent()}</div>
|
||||||
</LightboxProvider>
|
</LightboxProvider>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
* Added interface options to disable creating performers/studios/tags from dropdown selectors. ([#1814](https://github.com/stashapp/stash/pull/1814))
|
* Added interface options to disable creating performers/studios/tags from dropdown selectors. ([#1814](https://github.com/stashapp/stash/pull/1814))
|
||||||
|
|
||||||
### 🎨 Improvements
|
### 🎨 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))
|
* 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))
|
* 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))
|
* Include total duration/megapixels and filesize information on Scenes and Images pages. ([#1776](https://github.com/stashapp/stash/pull/1776))
|
||||||
|
|||||||
@@ -1,11 +1,25 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Route, Switch } from "react-router-dom";
|
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 { PersistanceLevel } from "src/hooks/ListHook";
|
||||||
import Gallery from "./GalleryDetails/Gallery";
|
import Gallery from "./GalleryDetails/Gallery";
|
||||||
import GalleryCreate from "./GalleryDetails/GalleryCreate";
|
import GalleryCreate from "./GalleryDetails/GalleryCreate";
|
||||||
import { GalleryList } from "./GalleryList";
|
import { GalleryList } from "./GalleryList";
|
||||||
|
|
||||||
const Galleries = () => (
|
const Galleries = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const title_template = `${intl.formatMessage({
|
||||||
|
id: "galleries",
|
||||||
|
})} ${TITLE_SUFFIX}`;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet
|
||||||
|
defaultTitle={title_template}
|
||||||
|
titleTemplate={`%s | ${title_template}`}
|
||||||
|
/>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
@@ -17,6 +31,8 @@ const Galleries = () => (
|
|||||||
<Route exact path="/galleries/new" component={GalleryCreate} />
|
<Route exact path="/galleries/new" component={GalleryCreate} />
|
||||||
<Route path="/galleries/:id/:tab?" component={Gallery} />
|
<Route path="/galleries/:id/:tab?" component={Gallery} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Galleries;
|
export default Galleries;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Tab, Nav, Dropdown } from "react-bootstrap";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useParams, useHistory, Link } from "react-router-dom";
|
import { useParams, useHistory, Link } from "react-router-dom";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
mutateMetadataScan,
|
mutateMetadataScan,
|
||||||
@@ -270,6 +271,11 @@ export const GalleryPage: React.FC<IProps> = ({ gallery }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
<Helmet>
|
||||||
|
<title>
|
||||||
|
{gallery.title ?? TextUtils.fileNameFromPath(gallery.path ?? "")}
|
||||||
|
</title>
|
||||||
|
</Helmet>
|
||||||
{maybeRenderDeleteDialog()}
|
{maybeRenderDeleteDialog()}
|
||||||
<div className="gallery-tabs">
|
<div className="gallery-tabs">
|
||||||
<div className="d-none d-xl-block">
|
<div className="d-none d-xl-block">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Tab, Nav, Dropdown } from "react-bootstrap";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { useParams, useHistory, Link } from "react-router-dom";
|
import { useParams, useHistory, Link } from "react-router-dom";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
import {
|
import {
|
||||||
useFindImage,
|
useFindImage,
|
||||||
useImageIncrementO,
|
useImageIncrementO,
|
||||||
@@ -262,6 +263,10 @@ export const Image: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
<Helmet>
|
||||||
|
<title>{image.title ?? TextUtils.fileNameFromPath(image.path)}</title>
|
||||||
|
</Helmet>
|
||||||
|
|
||||||
{maybeRenderDeleteDialog()}
|
{maybeRenderDeleteDialog()}
|
||||||
<div className="image-tabs order-xl-first order-last">
|
<div className="image-tabs order-xl-first order-last">
|
||||||
<div className="d-none d-xl-block">
|
<div className="d-none d-xl-block">
|
||||||
|
|||||||
@@ -1,10 +1,24 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Route, Switch } from "react-router-dom";
|
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 { PersistanceLevel } from "src/hooks/ListHook";
|
||||||
import { Image } from "./ImageDetails/Image";
|
import { Image } from "./ImageDetails/Image";
|
||||||
import { ImageList } from "./ImageList";
|
import { ImageList } from "./ImageList";
|
||||||
|
|
||||||
const Images = () => (
|
const Images: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const title_template = `${intl.formatMessage({
|
||||||
|
id: "images",
|
||||||
|
})} ${TITLE_SUFFIX}`;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet
|
||||||
|
defaultTitle={title_template}
|
||||||
|
titleTemplate={`%s | ${title_template}`}
|
||||||
|
/>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
@@ -15,6 +29,8 @@ const Images = () => (
|
|||||||
/>
|
/>
|
||||||
<Route path="/images/:id" component={Image} />
|
<Route path="/images/:id" component={Image} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Images;
|
export default Images;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
@@ -174,6 +175,10 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
|
|||||||
// TODO: CSS class
|
// TODO: CSS class
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
<Helmet>
|
||||||
|
<title>{movie?.name}</title>
|
||||||
|
</Helmet>
|
||||||
|
|
||||||
<div className="movie-details mb-3 col col-xl-4 col-lg-6">
|
<div className="movie-details mb-3 col col-xl-4 col-lg-6">
|
||||||
<div className="logo w-100">
|
<div className="logo w-100">
|
||||||
{encodingImage ? (
|
{encodingImage ? (
|
||||||
|
|||||||
@@ -1,15 +1,31 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Route, Switch } from "react-router-dom";
|
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 Movie from "./MovieDetails/Movie";
|
||||||
import MovieCreate from "./MovieDetails/MovieCreate";
|
import MovieCreate from "./MovieDetails/MovieCreate";
|
||||||
import { MovieList } from "./MovieList";
|
import { MovieList } from "./MovieList";
|
||||||
|
|
||||||
const Movies = () => (
|
const Movies: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const title_template = `${intl.formatMessage({
|
||||||
|
id: "movies",
|
||||||
|
})} ${TITLE_SUFFIX}`;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet
|
||||||
|
defaultTitle={title_template}
|
||||||
|
titleTemplate={`%s | ${title_template}`}
|
||||||
|
/>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/movies" component={MovieList} />
|
<Route exact path="/movies" component={MovieList} />
|
||||||
<Route exact path="/movies/new" component={MovieCreate} />
|
<Route exact path="/movies/new" component={MovieCreate} />
|
||||||
<Route path="/movies/:id/:tab?" component={Movie} />
|
<Route path="/movies/:id/:tab?" component={Movie} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Movies;
|
export default Movies;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from "react";
|
|||||||
import { Button, Tabs, Tab } from "react-bootstrap";
|
import { Button, Tabs, Tab } from "react-bootstrap";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { useParams, useHistory } from "react-router-dom";
|
import { useParams, useHistory } from "react-router-dom";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
@@ -299,6 +300,10 @@ const PerformerPage: React.FC<IProps> = ({ performer }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="performer-page" className="row">
|
<div id="performer-page" className="row">
|
||||||
|
<Helmet>
|
||||||
|
<title>{performer.name}</title>
|
||||||
|
</Helmet>
|
||||||
|
|
||||||
<div className="performer-image-container col-md-4 text-center">
|
<div className="performer-image-container col-md-4 text-center">
|
||||||
{imageEncoding ? (
|
{imageEncoding ? (
|
||||||
<LoadingIndicator message="Encoding image..." />
|
<LoadingIndicator message="Encoding image..." />
|
||||||
|
|||||||
@@ -1,11 +1,25 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Route, Switch } from "react-router-dom";
|
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 { PersistanceLevel } from "src/hooks/ListHook";
|
||||||
import Performer from "./PerformerDetails/Performer";
|
import Performer from "./PerformerDetails/Performer";
|
||||||
import PerformerCreate from "./PerformerDetails/PerformerCreate";
|
import PerformerCreate from "./PerformerDetails/PerformerCreate";
|
||||||
import { PerformerList } from "./PerformerList";
|
import { PerformerList } from "./PerformerList";
|
||||||
|
|
||||||
const Performers = () => (
|
const Performers: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const title_template = `${intl.formatMessage({
|
||||||
|
id: "performers",
|
||||||
|
})} ${TITLE_SUFFIX}`;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet
|
||||||
|
defaultTitle={title_template}
|
||||||
|
titleTemplate={`%s | ${title_template}`}
|
||||||
|
/>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
@@ -17,6 +31,7 @@ const Performers = () => (
|
|||||||
<Route path="/performers/new" component={PerformerCreate} />
|
<Route path="/performers/new" component={PerformerCreate} />
|
||||||
<Route path="/performers/:id/:tab?" component={Performer} />
|
<Route path="/performers/:id/:tab?" component={Performer} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
export default Performers;
|
export default Performers;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import queryString from "query-string";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { useParams, useLocation, useHistory, Link } from "react-router-dom";
|
import { useParams, useLocation, useHistory, Link } from "react-router-dom";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
mutateMetadataScan,
|
mutateMetadataScan,
|
||||||
@@ -566,6 +567,9 @@ const ScenePage: React.FC<IProps> = ({ scene, refetch }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
<Helmet>
|
||||||
|
<title>{scene.title ?? TextUtils.fileNameFromPath(scene.path)}</title>
|
||||||
|
</Helmet>
|
||||||
{maybeRenderSceneGenerateDialog()}
|
{maybeRenderSceneGenerateDialog()}
|
||||||
{maybeRenderDeleteDialog()}
|
{maybeRenderDeleteDialog()}
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import _ from "lodash";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { TITLE_SUFFIX } from "src/components/Shared";
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
import { FindSceneMarkersQueryResult } from "src/core/generated-graphql";
|
import { FindSceneMarkersQueryResult } from "src/core/generated-graphql";
|
||||||
import { queryFindSceneMarkers } from "src/core/StashService";
|
import { queryFindSceneMarkers } from "src/core/StashService";
|
||||||
@@ -81,6 +83,18 @@ export const SceneMarkerList: React.FC<ISceneMarkerList> = ({ filterHook }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const title_template = `${intl.formatMessage({
|
||||||
|
id: "markers",
|
||||||
|
})} ${TITLE_SUFFIX}`;
|
||||||
|
|
||||||
return listData.template;
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet
|
||||||
|
defaultTitle={title_template}
|
||||||
|
titleTemplate={`%s | ${title_template}`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{listData.template}
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,25 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Route, Switch } from "react-router-dom";
|
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 { PersistanceLevel } from "src/hooks/ListHook";
|
||||||
import Scene from "./SceneDetails/Scene";
|
import Scene from "./SceneDetails/Scene";
|
||||||
import { SceneList } from "./SceneList";
|
import { SceneList } from "./SceneList";
|
||||||
import { SceneMarkerList } from "./SceneMarkerList";
|
import { SceneMarkerList } from "./SceneMarkerList";
|
||||||
|
|
||||||
const Scenes = () => (
|
const Scenes: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const title_template = `${intl.formatMessage({
|
||||||
|
id: "scenes",
|
||||||
|
})} ${TITLE_SUFFIX}`;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet
|
||||||
|
defaultTitle={title_template}
|
||||||
|
titleTemplate={`%s | ${title_template}`}
|
||||||
|
/>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
@@ -17,6 +31,7 @@ const Scenes = () => (
|
|||||||
<Route exact path="/scenes/markers" component={SceneMarkerList} />
|
<Route exact path="/scenes/markers" component={SceneMarkerList} />
|
||||||
<Route path="/scenes/:id" component={Scene} />
|
<Route path="/scenes/:id" component={Scene} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
export default Scenes;
|
export default Scenes;
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import React from "react";
|
|||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { Card, Tab, Nav, Row, Col } from "react-bootstrap";
|
import { Card, Tab, Nav, Row, Col } from "react-bootstrap";
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
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 { SettingsAboutPanel } from "./SettingsAboutPanel";
|
||||||
import { SettingsConfigurationPanel } from "./SettingsConfigurationPanel";
|
import { SettingsConfigurationPanel } from "./SettingsConfigurationPanel";
|
||||||
import { SettingsInterfacePanel } from "./SettingsInterfacePanel/SettingsInterfacePanel";
|
import { SettingsInterfacePanel } from "./SettingsInterfacePanel/SettingsInterfacePanel";
|
||||||
@@ -14,14 +16,22 @@ import { SettingsToolsPanel } from "./SettingsToolsPanel";
|
|||||||
import { SettingsDLNAPanel } from "./SettingsDLNAPanel";
|
import { SettingsDLNAPanel } from "./SettingsDLNAPanel";
|
||||||
|
|
||||||
export const Settings: React.FC = () => {
|
export const Settings: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const defaultTab = queryString.parse(location.search).tab ?? "tasks";
|
const defaultTab = queryString.parse(location.search).tab ?? "tasks";
|
||||||
|
|
||||||
const onSelect = (val: string) => history.push(`?tab=${val}`);
|
const onSelect = (val: string) => history.push(`?tab=${val}`);
|
||||||
|
|
||||||
|
const title_template = `${intl.formatMessage({
|
||||||
|
id: "settings",
|
||||||
|
})} ${TITLE_SUFFIX}`;
|
||||||
return (
|
return (
|
||||||
<Card className="col col-lg-9 mx-auto">
|
<Card className="col col-lg-9 mx-auto">
|
||||||
|
<Helmet
|
||||||
|
defaultTitle={title_template}
|
||||||
|
titleTemplate={`%s | ${title_template}`}
|
||||||
|
/>
|
||||||
<Tab.Container
|
<Tab.Container
|
||||||
activeKey={defaultTab}
|
activeKey={defaultTab}
|
||||||
id="configuration-tabs"
|
id="configuration-tabs"
|
||||||
|
|||||||
@@ -18,3 +18,4 @@ export { RatingStars } from "./RatingStars";
|
|||||||
export { ExportDialog } from "./ExportDialog";
|
export { ExportDialog } from "./ExportDialog";
|
||||||
export { default as DeleteEntityDialog } from "./DeleteEntityDialog";
|
export { default as DeleteEntityDialog } from "./DeleteEntityDialog";
|
||||||
export { OperationButton } from "./OperationButton";
|
export { OperationButton } from "./OperationButton";
|
||||||
|
export const TITLE_SUFFIX = " | Stash";
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Tabs, Tab } from "react-bootstrap";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useParams, useHistory } from "react-router-dom";
|
import { useParams, useHistory } from "react-router-dom";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
|
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
@@ -179,6 +180,11 @@ const StudioPage: React.FC<IProps> = ({ studio }) => {
|
|||||||
</div>
|
</div>
|
||||||
{!isEditing ? (
|
{!isEditing ? (
|
||||||
<>
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>
|
||||||
|
{studio.name ?? intl.formatMessage({ id: "studio" })}
|
||||||
|
</title>
|
||||||
|
</Helmet>
|
||||||
<StudioDetailsPanel studio={studio} />
|
<StudioDetailsPanel studio={studio} />
|
||||||
<DetailsEditNavbar
|
<DetailsEditNavbar
|
||||||
objectName={studio.name ?? intl.formatMessage({ id: "studio" })}
|
objectName={studio.name ?? intl.formatMessage({ id: "studio" })}
|
||||||
|
|||||||
@@ -1,15 +1,30 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Route, Switch } from "react-router-dom";
|
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 Studio from "./StudioDetails/Studio";
|
import Studio from "./StudioDetails/Studio";
|
||||||
import StudioCreate from "./StudioDetails/StudioCreate";
|
import StudioCreate from "./StudioDetails/StudioCreate";
|
||||||
import { StudioList } from "./StudioList";
|
import { StudioList } from "./StudioList";
|
||||||
|
|
||||||
const Studios = () => (
|
const Studios: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const title_template = `${intl.formatMessage({
|
||||||
|
id: "studios",
|
||||||
|
})} ${TITLE_SUFFIX}`;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet
|
||||||
|
defaultTitle={title_template}
|
||||||
|
titleTemplate={`%s | ${title_template}`}
|
||||||
|
/>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/studios" component={StudioList} />
|
<Route exact path="/studios" component={StudioList} />
|
||||||
<Route exact path="/studios/new" component={StudioCreate} />
|
<Route exact path="/studios/new" component={StudioCreate} />
|
||||||
<Route path="/studios/:id/:tab?" component={Studio} />
|
<Route path="/studios/:id/:tab?" component={Studio} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
export default Studios;
|
export default Studios;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Tabs, Tab, Dropdown } from "react-bootstrap";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useParams, useHistory } from "react-router-dom";
|
import { useParams, useHistory } from "react-router-dom";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
|
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
@@ -248,6 +249,10 @@ const TagPage: React.FC<IProps> = ({ tag }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>{tag.name}</title>
|
||||||
|
</Helmet>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="tag-details col-md-4">
|
<div className="tag-details col-md-4">
|
||||||
<div className="text-center logo-container">
|
<div className="text-center logo-container">
|
||||||
@@ -304,7 +309,10 @@ const TagPage: React.FC<IProps> = ({ tag }) => {
|
|||||||
>
|
>
|
||||||
<TagGalleriesPanel tag={tag} />
|
<TagGalleriesPanel tag={tag} />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab eventKey="markers" title={intl.formatMessage({ id: "markers" })}>
|
<Tab
|
||||||
|
eventKey="markers"
|
||||||
|
title={intl.formatMessage({ id: "markers" })}
|
||||||
|
>
|
||||||
<TagMarkersPanel tag={tag} />
|
<TagMarkersPanel tag={tag} />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
@@ -318,6 +326,7 @@ const TagPage: React.FC<IProps> = ({ tag }) => {
|
|||||||
{renderDeleteAlert()}
|
{renderDeleteAlert()}
|
||||||
{renderMergeDialog()}
|
{renderMergeDialog()}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,31 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Route, Switch } from "react-router-dom";
|
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 Tag from "./TagDetails/Tag";
|
||||||
import TagCreate from "./TagDetails/TagCreate";
|
import TagCreate from "./TagDetails/TagCreate";
|
||||||
import { TagList } from "./TagList";
|
import { TagList } from "./TagList";
|
||||||
|
|
||||||
const Tags = () => (
|
const Tags: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const title_template = `${intl.formatMessage({
|
||||||
|
id: "tags",
|
||||||
|
})} ${TITLE_SUFFIX}`;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet
|
||||||
|
defaultTitle={title_template}
|
||||||
|
titleTemplate={`%s | ${title_template}`}
|
||||||
|
/>
|
||||||
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/tags" component={TagList} />
|
<Route exact path="/tags" component={TagList} />
|
||||||
<Route exact path="/tags/new" component={TagCreate} />
|
<Route exact path="/tags/new" component={TagCreate} />
|
||||||
<Route path="/tags/:id/:tab?" component={Tag} />
|
<Route path="/tags/:id/:tab?" component={Tag} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
export default Tags;
|
export default Tags;
|
||||||
|
|||||||
@@ -3072,6 +3072,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@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":
|
"@types/react-router-bootstrap@^0.24.5":
|
||||||
version "0.24.5"
|
version "0.24.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-router-bootstrap/-/react-router-bootstrap-0.24.5.tgz#9257ba3dfb01cda201aac9fa05cde3eb09ea5b27"
|
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"
|
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
||||||
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
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:
|
react-input-autosize@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85"
|
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-input-autosize "^3.0.0"
|
||||||
react-transition-group "^4.3.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:
|
react-transition-group@^4.3.0, react-transition-group@^4.4.1:
|
||||||
version "4.4.1"
|
version "4.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
|
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
|
||||||
|
|||||||
Reference in New Issue
Block a user