mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 16:34:02 +01:00
Adjust UI to account for fuzzy dates
This commit is contained in:
parent
71587b9dc9
commit
45c1c0b008
10 changed files with 93 additions and 30 deletions
|
|
@ -6,7 +6,7 @@ import {
|
||||||
RouteComponentProps,
|
RouteComponentProps,
|
||||||
Redirect,
|
Redirect,
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
import { FormattedDate, FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
|
|
@ -44,6 +44,7 @@ import { useRatingKeybinds } from "src/hooks/keybinds";
|
||||||
import { useConfigurationContext } from "src/hooks/Config";
|
import { useConfigurationContext } from "src/hooks/Config";
|
||||||
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
||||||
import { goBackOrReplace } from "src/utils/history";
|
import { goBackOrReplace } from "src/utils/history";
|
||||||
|
import { FormattedDate } from "src/components/Shared/Date";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
gallery: GQL.GalleryDataFragment;
|
gallery: GQL.GalleryDataFragment;
|
||||||
|
|
@ -410,11 +411,7 @@ export const GalleryPage: React.FC<IProps> = ({ gallery, add }) => {
|
||||||
<div className="gallery-subheader">
|
<div className="gallery-subheader">
|
||||||
{!!gallery.date && (
|
{!!gallery.date && (
|
||||||
<span className="date" data-value={gallery.date}>
|
<span className="date" data-value={gallery.date}>
|
||||||
<FormattedDate
|
<FormattedDate value={gallery.date} />
|
||||||
value={gallery.date}
|
|
||||||
format="long"
|
|
||||||
timeZone="utc"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ const GalleryWallCard: React.FC<IProps> = ({ gallery }) => {
|
||||||
)}
|
)}
|
||||||
<TruncatedText text={performers.join(", ")} />
|
<TruncatedText text={performers.join(", ")} />
|
||||||
<div>
|
<div>
|
||||||
{gallery.date && TextUtils.formatDate(intl, gallery.date)}
|
{gallery.date && TextUtils.formatFuzzyDate(intl, gallery.date)}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ export const GroupDetailsPanel: React.FC<IGroupDetailsPanel> = ({
|
||||||
/>
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
id="date"
|
id="date"
|
||||||
value={group.date ? TextUtils.formatDate(intl, group.date) : ""}
|
value={group.date ? TextUtils.formatFuzzyDate(intl, group.date) : ""}
|
||||||
fullWidth={fullWidth}
|
fullWidth={fullWidth}
|
||||||
/>
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Tab, Nav, Dropdown } from "react-bootstrap";
|
import { Tab, Nav, Dropdown } from "react-bootstrap";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { FormattedDate, FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { useHistory, Link, RouteComponentProps } from "react-router-dom";
|
import { useHistory, Link, RouteComponentProps } from "react-router-dom";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import {
|
import {
|
||||||
|
|
@ -35,6 +35,7 @@ import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
||||||
import { goBackOrReplace } from "src/utils/history";
|
import { goBackOrReplace } from "src/utils/history";
|
||||||
|
import { FormattedDate } from "src/components/Shared/Date";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
image: GQL.ImageDataFragment;
|
image: GQL.ImageDataFragment;
|
||||||
|
|
@ -319,13 +320,7 @@ const ImagePage: React.FC<IProps> = ({ image }) => {
|
||||||
|
|
||||||
<div className="image-subheader">
|
<div className="image-subheader">
|
||||||
<span className="date" data-value={image.date}>
|
<span className="date" data-value={image.date}>
|
||||||
{!!image.date && (
|
{!!image.date && <FormattedDate value={image.date} />}
|
||||||
<FormattedDate
|
|
||||||
value={image.date}
|
|
||||||
format="long"
|
|
||||||
timeZone="utc"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
{resolution ? (
|
{resolution ? (
|
||||||
<span className="resolution" data-value={resolution}>
|
<span className="resolution" data-value={resolution}>
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,10 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> =
|
||||||
}
|
}
|
||||||
title={
|
title={
|
||||||
!fullWidth
|
!fullWidth
|
||||||
? TextUtils.formatDate(intl, performer.birthdate ?? undefined)
|
? TextUtils.formatFuzzyDate(
|
||||||
|
intl,
|
||||||
|
performer.birthdate ?? undefined
|
||||||
|
)
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
fullWidth={fullWidth}
|
fullWidth={fullWidth}
|
||||||
|
|
@ -218,7 +221,7 @@ export const CompressedPerformerDetailsPanel: React.FC<IPerformerDetails> =
|
||||||
<span className="detail-divider">/</span>
|
<span className="detail-divider">/</span>
|
||||||
<span
|
<span
|
||||||
className="performer-age"
|
className="performer-age"
|
||||||
title={TextUtils.formatDate(
|
title={TextUtils.formatFuzzyDate(
|
||||||
intl,
|
intl,
|
||||||
performer.birthdate ?? undefined
|
performer.birthdate ?? undefined
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ export const PerformerListTable: React.FC<IPerformerListTableProps> = (
|
||||||
<span
|
<span
|
||||||
title={
|
title={
|
||||||
performer.birthdate
|
performer.birthdate
|
||||||
? TextUtils.formatDate(intl, performer.birthdate ?? undefined)
|
? TextUtils.formatFuzzyDate(intl, performer.birthdate ?? undefined)
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import React, {
|
||||||
useRef,
|
useRef,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { FormattedDate, FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { Link, RouteComponentProps } from "react-router-dom";
|
import { Link, RouteComponentProps } from "react-router-dom";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
|
|
@ -51,6 +51,7 @@ import cx from "classnames";
|
||||||
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
||||||
import { PatchComponent, PatchContainerComponent } from "src/patch";
|
import { PatchComponent, PatchContainerComponent } from "src/patch";
|
||||||
import { goBackOrReplace } from "src/utils/history";
|
import { goBackOrReplace } from "src/utils/history";
|
||||||
|
import { FormattedDate } from "src/components/Shared/Date";
|
||||||
|
|
||||||
const SubmitStashBoxDraft = lazyComponent(
|
const SubmitStashBoxDraft = lazyComponent(
|
||||||
() => import("src/components/Dialogs/SubmitDraft")
|
() => import("src/components/Dialogs/SubmitDraft")
|
||||||
|
|
@ -613,13 +614,7 @@ const ScenePage: React.FC<IProps> = PatchComponent("ScenePage", (props) => {
|
||||||
|
|
||||||
<div className="scene-subheader">
|
<div className="scene-subheader">
|
||||||
<span className="date" data-value={scene.date}>
|
<span className="date" data-value={scene.date}>
|
||||||
{!!scene.date && (
|
{!!scene.date && <FormattedDate value={scene.date} />}
|
||||||
<FormattedDate
|
|
||||||
value={scene.date}
|
|
||||||
format="long"
|
|
||||||
timeZone="utc"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
<VideoFrameRateResolution
|
<VideoFrameRateResolution
|
||||||
width={file?.width}
|
width={file?.width}
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,9 @@ export const SceneWallItem: React.FC<
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<TruncatedText text={performers.join(", ")} />
|
<TruncatedText text={performers.join(", ")} />
|
||||||
<div>{scene.date && TextUtils.formatDate(intl, scene.date)}</div>
|
<div>
|
||||||
|
{scene.date && TextUtils.formatFuzzyDate(intl, scene.date)}
|
||||||
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
36
ui/v2.5/src/components/Shared/Date.tsx
Normal file
36
ui/v2.5/src/components/Shared/Date.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React from "react";
|
||||||
|
import { FormattedDate as IntlDate } from "react-intl";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
|
// wraps FormattedDate to handle year or year/month dates
|
||||||
|
export const FormattedDate: React.FC<{
|
||||||
|
value: string | number | Date | undefined;
|
||||||
|
}> = PatchComponent("Date", ({ value }) => {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
// try parsing as year or year/month
|
||||||
|
const yearMatch = value.match(/^(\d{4})$/);
|
||||||
|
if (yearMatch) {
|
||||||
|
const year = parseInt(yearMatch[1], 10);
|
||||||
|
return (
|
||||||
|
<IntlDate value={Date.UTC(year, 0)} year="numeric" timeZone="utc" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const yearMonthMatch = value.match(/^(\d{4})-(\d{2})$/);
|
||||||
|
if (yearMonthMatch) {
|
||||||
|
const year = parseInt(yearMonthMatch[1], 10);
|
||||||
|
const month = parseInt(yearMonthMatch[2], 10) - 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntlDate
|
||||||
|
value={Date.UTC(year, month)}
|
||||||
|
year="numeric"
|
||||||
|
month="long"
|
||||||
|
timeZone="utc"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <IntlDate value={value} format="long" timeZone="utc" />;
|
||||||
|
});
|
||||||
|
|
@ -336,8 +336,10 @@ function dateTimeToString(date: Date) {
|
||||||
const getAge = (dateString?: string | null, fromDateString?: string | null) => {
|
const getAge = (dateString?: string | null, fromDateString?: string | null) => {
|
||||||
if (!dateString) return 0;
|
if (!dateString) return 0;
|
||||||
|
|
||||||
const birthdate = stringToDate(dateString);
|
const birthdate = stringToFuzzyDate(dateString);
|
||||||
const fromDate = fromDateString ? stringToDate(fromDateString) : new Date();
|
const fromDate = fromDateString
|
||||||
|
? stringToFuzzyDate(fromDateString)
|
||||||
|
: new Date();
|
||||||
|
|
||||||
if (!birthdate || !fromDate) return 0;
|
if (!birthdate || !fromDate) return 0;
|
||||||
|
|
||||||
|
|
@ -459,6 +461,38 @@ const formatDate = (intl: IntlShape, date?: string, utc = true) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatFuzzyDate = (intl: IntlShape, date?: string, utc = true) => {
|
||||||
|
if (!date) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle year or year/month dates
|
||||||
|
const yearMatch = date.match(/^(\d{4})$/);
|
||||||
|
if (yearMatch) {
|
||||||
|
const year = parseInt(yearMatch[1], 10);
|
||||||
|
return intl.formatDate(Date.UTC(year, 0), {
|
||||||
|
year: "numeric",
|
||||||
|
timeZone: utc ? "utc" : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const yearMonthMatch = date.match(/^(\d{4})-(\d{2})$/);
|
||||||
|
if (yearMonthMatch) {
|
||||||
|
const year = parseInt(yearMonthMatch[1], 10);
|
||||||
|
const month = parseInt(yearMonthMatch[2], 10) - 1;
|
||||||
|
return intl.formatDate(Date.UTC(year, month), {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
timeZone: utc ? "utc" : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return intl.formatDate(date, {
|
||||||
|
format: "long",
|
||||||
|
timeZone: utc ? "utc" : undefined,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const formatDateTime = (intl: IntlShape, dateTime?: string, utc = false) =>
|
const formatDateTime = (intl: IntlShape, dateTime?: string, utc = false) =>
|
||||||
`${formatDate(intl, dateTime, utc)} ${intl.formatTime(dateTime, {
|
`${formatDate(intl, dateTime, utc)} ${intl.formatTime(dateTime, {
|
||||||
timeZone: utc ? "utc" : undefined,
|
timeZone: utc ? "utc" : undefined,
|
||||||
|
|
@ -519,6 +553,7 @@ const TextUtils = {
|
||||||
sanitiseURL,
|
sanitiseURL,
|
||||||
domainFromURL,
|
domainFromURL,
|
||||||
formatDate,
|
formatDate,
|
||||||
|
formatFuzzyDate,
|
||||||
formatDateTime,
|
formatDateTime,
|
||||||
secondsAsTimeString,
|
secondsAsTimeString,
|
||||||
abbreviateCounter,
|
abbreviateCounter,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue