mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +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-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",
|
||||
|
||||
@@ -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 = () => {
|
||||
>
|
||||
<ToastProvider>
|
||||
<LightboxProvider>
|
||||
<Helmet
|
||||
titleTemplate={`%s ${TITLE_SUFFIX}`}
|
||||
defaultTitle="Stash"
|
||||
/>
|
||||
{maybeRenderNavbar()}
|
||||
<div className="main container-fluid">{renderContent()}</div>
|
||||
</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))
|
||||
|
||||
### 🎨 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))
|
||||
|
||||
@@ -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 = () => (
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/galleries"
|
||||
render={(props) => (
|
||||
<GalleryList {...props} persistState={PersistanceLevel.ALL} />
|
||||
)}
|
||||
/>
|
||||
<Route exact path="/galleries/new" component={GalleryCreate} />
|
||||
<Route path="/galleries/:id/:tab?" component={Gallery} />
|
||||
</Switch>
|
||||
);
|
||||
const Galleries = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const title_template = `${intl.formatMessage({
|
||||
id: "galleries",
|
||||
})} ${TITLE_SUFFIX}`;
|
||||
return (
|
||||
<>
|
||||
<Helmet
|
||||
defaultTitle={title_template}
|
||||
titleTemplate={`%s | ${title_template}`}
|
||||
/>
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/galleries"
|
||||
render={(props) => (
|
||||
<GalleryList {...props} persistState={PersistanceLevel.ALL} />
|
||||
)}
|
||||
/>
|
||||
<Route exact path="/galleries/new" component={GalleryCreate} />
|
||||
<Route path="/galleries/:id/:tab?" component={Gallery} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Galleries;
|
||||
|
||||
@@ -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<IProps> = ({ gallery }) => {
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<Helmet>
|
||||
<title>
|
||||
{gallery.title ?? TextUtils.fileNameFromPath(gallery.path ?? "")}
|
||||
</title>
|
||||
</Helmet>
|
||||
{maybeRenderDeleteDialog()}
|
||||
<div className="gallery-tabs">
|
||||
<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 { 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 (
|
||||
<div className="row">
|
||||
<Helmet>
|
||||
<title>{image.title ?? TextUtils.fileNameFromPath(image.path)}</title>
|
||||
</Helmet>
|
||||
|
||||
{maybeRenderDeleteDialog()}
|
||||
<div className="image-tabs order-xl-first order-last">
|
||||
<div className="d-none d-xl-block">
|
||||
|
||||
@@ -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 = () => (
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/images"
|
||||
render={(props) => (
|
||||
<ImageList persistState={PersistanceLevel.ALL} {...props} />
|
||||
)}
|
||||
/>
|
||||
<Route path="/images/:id" component={Image} />
|
||||
</Switch>
|
||||
);
|
||||
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>
|
||||
<Route
|
||||
exact
|
||||
path="/images"
|
||||
render={(props) => (
|
||||
<ImageList persistState={PersistanceLevel.ALL} {...props} />
|
||||
)}
|
||||
/>
|
||||
<Route path="/images/:id" component={Image} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Images;
|
||||
|
||||
@@ -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<IProps> = ({ movie }) => {
|
||||
// TODO: CSS class
|
||||
return (
|
||||
<div className="row">
|
||||
<Helmet>
|
||||
<title>{movie?.name}</title>
|
||||
</Helmet>
|
||||
|
||||
<div className="movie-details mb-3 col col-xl-4 col-lg-6">
|
||||
<div className="logo w-100">
|
||||
{encodingImage ? (
|
||||
|
||||
@@ -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 = () => (
|
||||
<Switch>
|
||||
<Route exact path="/movies" component={MovieList} />
|
||||
<Route exact path="/movies/new" component={MovieCreate} />
|
||||
<Route path="/movies/:id/:tab?" component={Movie} />
|
||||
</Switch>
|
||||
);
|
||||
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>
|
||||
<Route exact path="/movies" component={MovieList} />
|
||||
<Route exact path="/movies/new" component={MovieCreate} />
|
||||
<Route path="/movies/:id/:tab?" component={Movie} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Movies;
|
||||
|
||||
@@ -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<IProps> = ({ performer }) => {
|
||||
|
||||
return (
|
||||
<div id="performer-page" className="row">
|
||||
<Helmet>
|
||||
<title>{performer.name}</title>
|
||||
</Helmet>
|
||||
|
||||
<div className="performer-image-container col-md-4 text-center">
|
||||
{imageEncoding ? (
|
||||
<LoadingIndicator message="Encoding image..." />
|
||||
|
||||
@@ -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 = () => (
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/performers"
|
||||
render={(props) => (
|
||||
<PerformerList persistState={PersistanceLevel.ALL} {...props} />
|
||||
)}
|
||||
/>
|
||||
<Route path="/performers/new" component={PerformerCreate} />
|
||||
<Route path="/performers/:id/:tab?" component={Performer} />
|
||||
</Switch>
|
||||
);
|
||||
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>
|
||||
<Route
|
||||
exact
|
||||
path="/performers"
|
||||
render={(props) => (
|
||||
<PerformerList persistState={PersistanceLevel.ALL} {...props} />
|
||||
)}
|
||||
/>
|
||||
<Route path="/performers/new" component={PerformerCreate} />
|
||||
<Route path="/performers/:id/:tab?" component={Performer} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Performers;
|
||||
|
||||
@@ -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<IProps> = ({ scene, refetch }) => {
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<Helmet>
|
||||
<title>{scene.title ?? TextUtils.fileNameFromPath(scene.path)}</title>
|
||||
</Helmet>
|
||||
{maybeRenderSceneGenerateDialog()}
|
||||
{maybeRenderDeleteDialog()}
|
||||
<div
|
||||
|
||||
@@ -2,6 +2,8 @@ import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useIntl } from "react-intl";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { TITLE_SUFFIX } from "src/components/Shared";
|
||||
import Mousetrap from "mousetrap";
|
||||
import { FindSceneMarkersQueryResult } from "src/core/generated-graphql";
|
||||
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,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 = () => (
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/scenes"
|
||||
render={(props) => (
|
||||
<SceneList persistState={PersistanceLevel.ALL} {...props} />
|
||||
)}
|
||||
/>
|
||||
<Route exact path="/scenes/markers" component={SceneMarkerList} />
|
||||
<Route path="/scenes/:id" component={Scene} />
|
||||
</Switch>
|
||||
);
|
||||
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>
|
||||
<Route
|
||||
exact
|
||||
path="/scenes"
|
||||
render={(props) => (
|
||||
<SceneList persistState={PersistanceLevel.ALL} {...props} />
|
||||
)}
|
||||
/>
|
||||
<Route exact path="/scenes/markers" component={SceneMarkerList} />
|
||||
<Route path="/scenes/:id" component={Scene} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Scenes;
|
||||
|
||||
@@ -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 (
|
||||
<Card className="col col-lg-9 mx-auto">
|
||||
<Helmet
|
||||
defaultTitle={title_template}
|
||||
titleTemplate={`%s | ${title_template}`}
|
||||
/>
|
||||
<Tab.Container
|
||||
activeKey={defaultTab}
|
||||
id="configuration-tabs"
|
||||
|
||||
@@ -18,3 +18,4 @@ export { RatingStars } from "./RatingStars";
|
||||
export { ExportDialog } from "./ExportDialog";
|
||||
export { default as DeleteEntityDialog } from "./DeleteEntityDialog";
|
||||
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 { 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";
|
||||
@@ -179,6 +180,11 @@ const StudioPage: React.FC<IProps> = ({ studio }) => {
|
||||
</div>
|
||||
{!isEditing ? (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>
|
||||
{studio.name ?? intl.formatMessage({ id: "studio" })}
|
||||
</title>
|
||||
</Helmet>
|
||||
<StudioDetailsPanel studio={studio} />
|
||||
<DetailsEditNavbar
|
||||
objectName={studio.name ?? intl.formatMessage({ id: "studio" })}
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
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 Studio from "./StudioDetails/Studio";
|
||||
import StudioCreate from "./StudioDetails/StudioCreate";
|
||||
import { StudioList } from "./StudioList";
|
||||
|
||||
const Studios = () => (
|
||||
<Switch>
|
||||
<Route exact path="/studios" component={StudioList} />
|
||||
<Route exact path="/studios/new" component={StudioCreate} />
|
||||
<Route path="/studios/:id/:tab?" component={Studio} />
|
||||
</Switch>
|
||||
);
|
||||
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>
|
||||
<Route exact path="/studios" component={StudioList} />
|
||||
<Route exact path="/studios/new" component={StudioCreate} />
|
||||
<Route path="/studios/:id/:tab?" component={Studio} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Studios;
|
||||
|
||||
@@ -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<IProps> = ({ tag }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="tag-details col-md-4">
|
||||
<div className="text-center logo-container">
|
||||
{imageEncoding ? (
|
||||
<LoadingIndicator message="Encoding image..." />
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{tag.name}</title>
|
||||
</Helmet>
|
||||
<div className="row">
|
||||
<div className="tag-details col-md-4">
|
||||
<div className="text-center logo-container">
|
||||
{imageEncoding ? (
|
||||
<LoadingIndicator message="Encoding image..." />
|
||||
) : (
|
||||
renderImage()
|
||||
)}
|
||||
<h2>{tag.name}</h2>
|
||||
</div>
|
||||
{!isEditing ? (
|
||||
<>
|
||||
<TagDetailsPanel tag={tag} />
|
||||
{/* HACK - this is also rendered in the TagEditPanel */}
|
||||
<DetailsEditNavbar
|
||||
objectName={tag.name}
|
||||
isNew={false}
|
||||
isEditing={isEditing}
|
||||
onToggleEdit={onToggleEdit}
|
||||
onSave={() => {}}
|
||||
onImageChange={() => {}}
|
||||
onClearImage={() => {}}
|
||||
onAutoTag={onAutoTag}
|
||||
onDelete={onDelete}
|
||||
customButtons={renderMergeButton()}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
renderImage()
|
||||
)}
|
||||
<h2>{tag.name}</h2>
|
||||
</div>
|
||||
{!isEditing ? (
|
||||
<>
|
||||
<TagDetailsPanel tag={tag} />
|
||||
{/* HACK - this is also rendered in the TagEditPanel */}
|
||||
<DetailsEditNavbar
|
||||
objectName={tag.name}
|
||||
isNew={false}
|
||||
isEditing={isEditing}
|
||||
onToggleEdit={onToggleEdit}
|
||||
onSave={() => {}}
|
||||
onImageChange={() => {}}
|
||||
onClearImage={() => {}}
|
||||
onAutoTag={onAutoTag}
|
||||
<TagEditPanel
|
||||
tag={tag}
|
||||
onSubmit={onSave}
|
||||
onCancel={onToggleEdit}
|
||||
onDelete={onDelete}
|
||||
customButtons={renderMergeButton()}
|
||||
setImage={setImage}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<TagEditPanel
|
||||
tag={tag}
|
||||
onSubmit={onSave}
|
||||
onCancel={onToggleEdit}
|
||||
onDelete={onDelete}
|
||||
setImage={setImage}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="col col-md-8">
|
||||
<Tabs
|
||||
id="tag-tabs"
|
||||
mountOnEnter
|
||||
activeKey={activeTabKey}
|
||||
onSelect={setActiveTabKey}
|
||||
>
|
||||
<Tab eventKey="scenes" title={intl.formatMessage({ id: "scenes" })}>
|
||||
<TagScenesPanel tag={tag} />
|
||||
</Tab>
|
||||
<Tab eventKey="images" title={intl.formatMessage({ id: "images" })}>
|
||||
<TagImagesPanel tag={tag} />
|
||||
</Tab>
|
||||
<Tab
|
||||
eventKey="galleries"
|
||||
title={intl.formatMessage({ id: "galleries" })}
|
||||
)}
|
||||
</div>
|
||||
<div className="col col-md-8">
|
||||
<Tabs
|
||||
id="tag-tabs"
|
||||
mountOnEnter
|
||||
activeKey={activeTabKey}
|
||||
onSelect={setActiveTabKey}
|
||||
>
|
||||
<TagGalleriesPanel tag={tag} />
|
||||
</Tab>
|
||||
<Tab eventKey="markers" title={intl.formatMessage({ id: "markers" })}>
|
||||
<TagMarkersPanel tag={tag} />
|
||||
</Tab>
|
||||
<Tab
|
||||
eventKey="performers"
|
||||
title={intl.formatMessage({ id: "performers" })}
|
||||
>
|
||||
<TagPerformersPanel tag={tag} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<Tab eventKey="scenes" title={intl.formatMessage({ id: "scenes" })}>
|
||||
<TagScenesPanel tag={tag} />
|
||||
</Tab>
|
||||
<Tab eventKey="images" title={intl.formatMessage({ id: "images" })}>
|
||||
<TagImagesPanel tag={tag} />
|
||||
</Tab>
|
||||
<Tab
|
||||
eventKey="galleries"
|
||||
title={intl.formatMessage({ id: "galleries" })}
|
||||
>
|
||||
<TagGalleriesPanel tag={tag} />
|
||||
</Tab>
|
||||
<Tab
|
||||
eventKey="markers"
|
||||
title={intl.formatMessage({ id: "markers" })}
|
||||
>
|
||||
<TagMarkersPanel tag={tag} />
|
||||
</Tab>
|
||||
<Tab
|
||||
eventKey="performers"
|
||||
title={intl.formatMessage({ id: "performers" })}
|
||||
>
|
||||
<TagPerformersPanel tag={tag} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
{renderDeleteAlert()}
|
||||
{renderMergeDialog()}
|
||||
</div>
|
||||
{renderDeleteAlert()}
|
||||
{renderMergeDialog()}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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 = () => (
|
||||
<Switch>
|
||||
<Route exact path="/tags" component={TagList} />
|
||||
<Route exact path="/tags/new" component={TagCreate} />
|
||||
<Route path="/tags/:id/:tab?" component={Tag} />
|
||||
</Switch>
|
||||
);
|
||||
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>
|
||||
<Route exact path="/tags" component={TagList} />
|
||||
<Route exact path="/tags/new" component={TagCreate} />
|
||||
<Route path="/tags/:id/:tab?" component={Tag} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Tags;
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user