mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
* #1810 Truncate large numbers on buttons * Apply to card popovers as well Co-authored-by: Roland Karle <roland.karle@aufwind-group.de> Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { Button, Tabs, Tab, Badge, Col, Row } from "react-bootstrap";
|
import { Button, Tabs, Tab, Col, Row } 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 { Helmet } from "react-helmet";
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
mutateMetadataAutoTag,
|
mutateMetadataAutoTag,
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import {
|
import {
|
||||||
|
Counter,
|
||||||
CountryFlag,
|
CountryFlag,
|
||||||
DetailsEditNavbar,
|
DetailsEditNavbar,
|
||||||
ErrorMessage,
|
ErrorMessage,
|
||||||
@@ -20,6 +21,7 @@ import {
|
|||||||
LoadingIndicator,
|
LoadingIndicator,
|
||||||
} from "src/components/Shared";
|
} from "src/components/Shared";
|
||||||
import { useLightbox, useToast } from "src/hooks";
|
import { useLightbox, useToast } from "src/hooks";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import { TextUtils } from "src/utils";
|
import { TextUtils } from "src/utils";
|
||||||
import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars";
|
import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars";
|
||||||
import { PerformerDetailsPanel } from "./PerformerDetailsPanel";
|
import { PerformerDetailsPanel } from "./PerformerDetailsPanel";
|
||||||
@@ -36,6 +38,7 @@ import {
|
|||||||
faHeart,
|
faHeart,
|
||||||
faLink,
|
faLink,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { IUIConfig } from "src/core/config";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
performer: GQL.PerformerDataFragment;
|
performer: GQL.PerformerDataFragment;
|
||||||
@@ -50,6 +53,11 @@ const PerformerPage: React.FC<IProps> = ({ performer }) => {
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { tab = "details" } = useParams<IPerformerParams>();
|
const { tab = "details" } = useParams<IPerformerParams>();
|
||||||
|
|
||||||
|
// Configuration settings
|
||||||
|
const { configuration } = React.useContext(ConfigurationContext);
|
||||||
|
const abbreviateCounter =
|
||||||
|
(configuration?.ui as IUIConfig)?.abbreviateCounters ?? false;
|
||||||
|
|
||||||
const [imagePreview, setImagePreview] = useState<string | null>();
|
const [imagePreview, setImagePreview] = useState<string | null>();
|
||||||
const [imageEncoding, setImageEncoding] = useState<boolean>(false);
|
const [imageEncoding, setImageEncoding] = useState<boolean>(false);
|
||||||
const [isEditing, setIsEditing] = useState<boolean>(false);
|
const [isEditing, setIsEditing] = useState<boolean>(false);
|
||||||
@@ -195,9 +203,10 @@ const PerformerPage: React.FC<IProps> = ({ performer }) => {
|
|||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{intl.formatMessage({ id: "scenes" })}
|
{intl.formatMessage({ id: "scenes" })}
|
||||||
<Badge className="left-spacing" pill variant="secondary">
|
<Counter
|
||||||
{intl.formatNumber(performer.scene_count ?? 0)}
|
abbreviateCounter={abbreviateCounter}
|
||||||
</Badge>
|
count={performer.scene_count ?? 0}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -208,9 +217,10 @@ const PerformerPage: React.FC<IProps> = ({ performer }) => {
|
|||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{intl.formatMessage({ id: "galleries" })}
|
{intl.formatMessage({ id: "galleries" })}
|
||||||
<Badge className="left-spacing" pill variant="secondary">
|
<Counter
|
||||||
{intl.formatNumber(performer.gallery_count ?? 0)}
|
abbreviateCounter={abbreviateCounter}
|
||||||
</Badge>
|
count={performer.gallery_count ?? 0}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -221,9 +231,10 @@ const PerformerPage: React.FC<IProps> = ({ performer }) => {
|
|||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{intl.formatMessage({ id: "images" })}
|
{intl.formatMessage({ id: "images" })}
|
||||||
<Badge className="left-spacing" pill variant="secondary">
|
<Counter
|
||||||
{intl.formatNumber(performer.image_count ?? 0)}
|
abbreviateCounter={abbreviateCounter}
|
||||||
</Badge>
|
count={performer.image_count ?? 0}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -234,9 +245,10 @@ const PerformerPage: React.FC<IProps> = ({ performer }) => {
|
|||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{intl.formatMessage({ id: "movies" })}
|
{intl.formatMessage({ id: "movies" })}
|
||||||
<Badge className="left-spacing" pill variant="secondary">
|
<Counter
|
||||||
{intl.formatNumber(performer.movie_count ?? 0)}
|
abbreviateCounter={abbreviateCounter}
|
||||||
</Badge>
|
count={performer.movie_count ?? 0}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -135,6 +135,14 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||||||
onChange={(v) => saveInterface({ menuItems: v })}
|
onChange={(v) => saveInterface({ menuItems: v })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<BooleanSetting
|
||||||
|
id="abbreviate-counters"
|
||||||
|
headingID="config.ui.abbreviate_counters.heading"
|
||||||
|
subHeadingID="config.ui.abbreviate_counters.description"
|
||||||
|
checked={ui.abbreviateCounters ?? undefined}
|
||||||
|
onChange={(v) => saveUI({ abbreviateCounters: v })}
|
||||||
|
/>
|
||||||
</SettingSection>
|
</SettingSection>
|
||||||
|
|
||||||
<SettingSection headingID="config.ui.desktop_integration.desktop_integration">
|
<SettingSection headingID="config.ui.desktop_integration.desktop_integration">
|
||||||
|
|||||||
37
ui/v2.5/src/components/Shared/Counter.tsx
Normal file
37
ui/v2.5/src/components/Shared/Counter.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Badge } from "react-bootstrap";
|
||||||
|
import { FormattedNumber, useIntl } from "react-intl";
|
||||||
|
import { TextUtils } from "src/utils";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
abbreviateCounter?: boolean;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Counter: React.FC<IProps> = ({
|
||||||
|
abbreviateCounter = false,
|
||||||
|
count,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
if (abbreviateCounter) {
|
||||||
|
const formated = TextUtils.abbreviateCounter(count);
|
||||||
|
return (
|
||||||
|
<Badge className="left-spacing" pill variant="secondary">
|
||||||
|
<FormattedNumber
|
||||||
|
value={formated.size}
|
||||||
|
maximumFractionDigits={formated.digits}
|
||||||
|
/>
|
||||||
|
{formated.unit}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Badge className="left-spacing" pill variant="secondary">
|
||||||
|
{intl.formatNumber(count)}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Counter;
|
||||||
@@ -4,10 +4,13 @@ import {
|
|||||||
faImages,
|
faImages,
|
||||||
faPlayCircle,
|
faPlayCircle,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
import { Button } from "react-bootstrap";
|
import { Button } from "react-bootstrap";
|
||||||
import { useIntl } from "react-intl";
|
import { FormattedNumber, useIntl } from "react-intl";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { IUIConfig } from "src/core/config";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
|
import { TextUtils } from "src/utils";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
|
||||||
type PopoverLinkType = "scene" | "image" | "gallery" | "movie";
|
type PopoverLinkType = "scene" | "image" | "gallery" | "movie";
|
||||||
@@ -25,6 +28,10 @@ export const PopoverCountButton: React.FC<IProps> = ({
|
|||||||
type,
|
type,
|
||||||
count,
|
count,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { configuration } = React.useContext(ConfigurationContext);
|
||||||
|
const abbreviateCounter =
|
||||||
|
(configuration?.ui as IUIConfig)?.abbreviateCounters ?? false;
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
function getIcon() {
|
function getIcon() {
|
||||||
@@ -72,11 +79,28 @@ export const PopoverCountButton: React.FC<IProps> = ({
|
|||||||
return `${count} ${plural}`;
|
return `${count} ${plural}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const countEl = useMemo(() => {
|
||||||
|
if (!abbreviateCounter) {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatted = TextUtils.abbreviateCounter(count);
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<FormattedNumber
|
||||||
|
value={formatted.size}
|
||||||
|
maximumFractionDigits={formatted.digits}
|
||||||
|
/>
|
||||||
|
{formatted.unit}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}, [count, abbreviateCounter]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link className={className} to={url} title={getTitle()}>
|
<Link className={className} to={url} title={getTitle()}>
|
||||||
<Button className="minimal">
|
<Button className="minimal">
|
||||||
<Icon icon={getIcon()} />
|
<Icon icon={getIcon()} />
|
||||||
<span>{count}</span>
|
<span>{countEl}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export { HoverPopover } from "./HoverPopover";
|
|||||||
export { default as LoadingIndicator } from "./LoadingIndicator";
|
export { default as LoadingIndicator } from "./LoadingIndicator";
|
||||||
export { ImageInput } from "./ImageInput";
|
export { ImageInput } from "./ImageInput";
|
||||||
export { SweatDrops } from "./SweatDrops";
|
export { SweatDrops } from "./SweatDrops";
|
||||||
|
export { default as Counter } from "./Counter";
|
||||||
export { default as CountryFlag } from "./CountryFlag";
|
export { default as CountryFlag } from "./CountryFlag";
|
||||||
export { default as SuccessIcon } from "./SuccessIcon";
|
export { default as SuccessIcon } from "./SuccessIcon";
|
||||||
export { default as ErrorMessage } from "./ErrorMessage";
|
export { default as ErrorMessage } from "./ErrorMessage";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Tabs, Tab, Badge } from "react-bootstrap";
|
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";
|
||||||
@@ -14,12 +14,14 @@ import {
|
|||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { ImageUtils } from "src/utils";
|
import { ImageUtils } from "src/utils";
|
||||||
import {
|
import {
|
||||||
|
Counter,
|
||||||
DetailsEditNavbar,
|
DetailsEditNavbar,
|
||||||
Modal,
|
Modal,
|
||||||
LoadingIndicator,
|
LoadingIndicator,
|
||||||
ErrorMessage,
|
ErrorMessage,
|
||||||
} from "src/components/Shared";
|
} from "src/components/Shared";
|
||||||
import { useToast } from "src/hooks";
|
import { useToast } from "src/hooks";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import { StudioScenesPanel } from "./StudioScenesPanel";
|
import { StudioScenesPanel } from "./StudioScenesPanel";
|
||||||
import { StudioGalleriesPanel } from "./StudioGalleriesPanel";
|
import { StudioGalleriesPanel } from "./StudioGalleriesPanel";
|
||||||
import { StudioImagesPanel } from "./StudioImagesPanel";
|
import { StudioImagesPanel } from "./StudioImagesPanel";
|
||||||
@@ -29,6 +31,7 @@ import { StudioEditPanel } from "./StudioEditPanel";
|
|||||||
import { StudioDetailsPanel } from "./StudioDetailsPanel";
|
import { StudioDetailsPanel } from "./StudioDetailsPanel";
|
||||||
import { StudioMoviesPanel } from "./StudioMoviesPanel";
|
import { StudioMoviesPanel } from "./StudioMoviesPanel";
|
||||||
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
import { faTrashAlt } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { IUIConfig } from "src/core/config";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
studio: GQL.StudioDataFragment;
|
studio: GQL.StudioDataFragment;
|
||||||
@@ -44,6 +47,11 @@ const StudioPage: React.FC<IProps> = ({ studio }) => {
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { tab = "details" } = useParams<IStudioParams>();
|
const { tab = "details" } = useParams<IStudioParams>();
|
||||||
|
|
||||||
|
// Configuration settings
|
||||||
|
const { configuration } = React.useContext(ConfigurationContext);
|
||||||
|
const abbreviateCounter =
|
||||||
|
(configuration?.ui as IUIConfig)?.abbreviateCounters ?? false;
|
||||||
|
|
||||||
// Editing state
|
// Editing state
|
||||||
const [isEditing, setIsEditing] = useState<boolean>(false);
|
const [isEditing, setIsEditing] = useState<boolean>(false);
|
||||||
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
|
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
|
||||||
@@ -222,9 +230,10 @@ const StudioPage: React.FC<IProps> = ({ studio }) => {
|
|||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{intl.formatMessage({ id: "scenes" })}
|
{intl.formatMessage({ id: "scenes" })}
|
||||||
<Badge className="left-spacing" pill variant="secondary">
|
<Counter
|
||||||
{intl.formatNumber(studio.scene_count ?? 0)}
|
abbreviateCounter={abbreviateCounter}
|
||||||
</Badge>
|
count={studio.scene_count ?? 0}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -235,9 +244,10 @@ const StudioPage: React.FC<IProps> = ({ studio }) => {
|
|||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{intl.formatMessage({ id: "galleries" })}
|
{intl.formatMessage({ id: "galleries" })}
|
||||||
<Badge className="left-spacing" pill variant="secondary">
|
<Counter
|
||||||
{intl.formatNumber(studio.gallery_count ?? 0)}
|
abbreviateCounter={abbreviateCounter}
|
||||||
</Badge>
|
count={studio.gallery_count ?? 0}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -248,9 +258,10 @@ const StudioPage: React.FC<IProps> = ({ studio }) => {
|
|||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{intl.formatMessage({ id: "images" })}
|
{intl.formatMessage({ id: "images" })}
|
||||||
<Badge className="left-spacing" pill variant="secondary">
|
<Counter
|
||||||
{intl.formatNumber(studio.image_count ?? 0)}
|
abbreviateCounter={abbreviateCounter}
|
||||||
</Badge>
|
count={studio.image_count ?? 0}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -267,9 +278,10 @@ const StudioPage: React.FC<IProps> = ({ studio }) => {
|
|||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{intl.formatMessage({ id: "movies" })}
|
{intl.formatMessage({ id: "movies" })}
|
||||||
<Badge className="left-spacing" pill variant="secondary">
|
<Counter
|
||||||
{intl.formatNumber(studio.movie_count ?? 0)}
|
abbreviateCounter={abbreviateCounter}
|
||||||
</Badge>
|
count={studio.movie_count ?? 0}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -280,9 +292,10 @@ const StudioPage: React.FC<IProps> = ({ studio }) => {
|
|||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{intl.formatMessage({ id: "subsidiary_studios" })}
|
{intl.formatMessage({ id: "subsidiary_studios" })}
|
||||||
<Badge className="left-spacing" pill variant="secondary">
|
<Counter
|
||||||
{intl.formatNumber(studio.child_studios?.length)}
|
abbreviateCounter={false}
|
||||||
</Badge>
|
count={studio.child_studios?.length ?? 0}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Tabs, Tab, Dropdown, Badge } from "react-bootstrap";
|
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";
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { ImageUtils } from "src/utils";
|
import { ImageUtils } from "src/utils";
|
||||||
import {
|
import {
|
||||||
|
Counter,
|
||||||
DetailsEditNavbar,
|
DetailsEditNavbar,
|
||||||
ErrorMessage,
|
ErrorMessage,
|
||||||
Modal,
|
Modal,
|
||||||
@@ -21,6 +22,7 @@ import {
|
|||||||
Icon,
|
Icon,
|
||||||
} from "src/components/Shared";
|
} from "src/components/Shared";
|
||||||
import { useToast } from "src/hooks";
|
import { useToast } from "src/hooks";
|
||||||
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import { tagRelationHook } from "src/core/tags";
|
import { tagRelationHook } from "src/core/tags";
|
||||||
import { TagScenesPanel } from "./TagScenesPanel";
|
import { TagScenesPanel } from "./TagScenesPanel";
|
||||||
import { TagMarkersPanel } from "./TagMarkersPanel";
|
import { TagMarkersPanel } from "./TagMarkersPanel";
|
||||||
@@ -35,6 +37,7 @@ import {
|
|||||||
faSignOutAlt,
|
faSignOutAlt,
|
||||||
faTrashAlt,
|
faTrashAlt,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { IUIConfig } from "src/core/config";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
tag: GQL.TagDataFragment;
|
tag: GQL.TagDataFragment;
|
||||||
@@ -48,6 +51,12 @@ const TagPage: React.FC<IProps> = ({ tag }) => {
|
|||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
// Configuration settings
|
||||||
|
const { configuration } = React.useContext(ConfigurationContext);
|
||||||
|
const abbreviateCounter =
|
||||||
|
(configuration?.ui as IUIConfig)?.abbreviateCounters ?? false;
|
||||||
|
|
||||||
const { tab = "scenes" } = useParams<ITabParams>();
|
const { tab = "scenes" } = useParams<ITabParams>();
|
||||||
|
|
||||||
// Editing state
|
// Editing state
|
||||||
@@ -309,9 +318,10 @@ const TagPage: React.FC<IProps> = ({ tag }) => {
|
|||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{intl.formatMessage({ id: "scenes" })}
|
{intl.formatMessage({ id: "scenes" })}
|
||||||
<Badge className="left-spacing" pill variant="secondary">
|
<Counter
|
||||||
{intl.formatNumber(tag.scene_count ?? 0)}
|
abbreviateCounter={abbreviateCounter}
|
||||||
</Badge>
|
count={tag.scene_count ?? 0}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -322,9 +332,10 @@ const TagPage: React.FC<IProps> = ({ tag }) => {
|
|||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{intl.formatMessage({ id: "images" })}
|
{intl.formatMessage({ id: "images" })}
|
||||||
<Badge className="left-spacing" pill variant="secondary">
|
<Counter
|
||||||
{intl.formatNumber(tag.image_count ?? 0)}
|
abbreviateCounter={abbreviateCounter}
|
||||||
</Badge>
|
count={tag.image_count ?? 0}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -335,9 +346,10 @@ const TagPage: React.FC<IProps> = ({ tag }) => {
|
|||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{intl.formatMessage({ id: "galleries" })}
|
{intl.formatMessage({ id: "galleries" })}
|
||||||
<Badge className="left-spacing" pill variant="secondary">
|
<Counter
|
||||||
{intl.formatNumber(tag.gallery_count ?? 0)}
|
abbreviateCounter={abbreviateCounter}
|
||||||
</Badge>
|
count={tag.gallery_count ?? 0}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -348,9 +360,10 @@ const TagPage: React.FC<IProps> = ({ tag }) => {
|
|||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{intl.formatMessage({ id: "markers" })}
|
{intl.formatMessage({ id: "markers" })}
|
||||||
<Badge className="left-spacing" pill variant="secondary">
|
<Counter
|
||||||
{intl.formatNumber(tag.scene_marker_count ?? 0)}
|
abbreviateCounter={abbreviateCounter}
|
||||||
</Badge>
|
count={tag.scene_marker_count ?? 0}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -361,9 +374,10 @@ const TagPage: React.FC<IProps> = ({ tag }) => {
|
|||||||
title={
|
title={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{intl.formatMessage({ id: "performers" })}
|
{intl.formatMessage({ id: "performers" })}
|
||||||
<Badge className="left-spacing" pill variant="secondary">
|
<Counter
|
||||||
{intl.formatNumber(tag.performer_count ?? 0)}
|
abbreviateCounter={abbreviateCounter}
|
||||||
</Badge>
|
count={tag.performer_count ?? 0}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export interface IUIConfig {
|
|||||||
showChildTagContent?: boolean;
|
showChildTagContent?: boolean;
|
||||||
showChildStudioContent?: boolean;
|
showChildStudioContent?: boolean;
|
||||||
showTagCardOnHover?: boolean;
|
showTagCardOnHover?: boolean;
|
||||||
|
abbreviateCounters?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function recentlyReleased(
|
function recentlyReleased(
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ After migrating, please run a scan on your entire library to populate missing da
|
|||||||
* Import/export schema has changed and is incompatible with the previous version.
|
* Import/export schema has changed and is incompatible with the previous version.
|
||||||
|
|
||||||
### ✨ New Features
|
### ✨ New Features
|
||||||
|
* Added Interface option to abbreviate counts on cards and details pages. ([#2781](https://github.com/stashapp/stash/pull/2781))
|
||||||
* Added description field to Tags. ([#2708](https://github.com/stashapp/stash/pull/2708))
|
* Added description field to Tags. ([#2708](https://github.com/stashapp/stash/pull/2708))
|
||||||
* Added option to include sub-studio/sub-tag content in Studio/Tag page. ([#2832](https://github.com/stashapp/stash/pull/2832))
|
* Added Interface options to include sub-studio/sub-tag content in Studio/Tag pages. ([#2832](https://github.com/stashapp/stash/pull/2832))
|
||||||
* Added backup location configuration setting. ([#2953](https://github.com/stashapp/stash/pull/2953))
|
* Added backup location configuration setting. ([#2953](https://github.com/stashapp/stash/pull/2953))
|
||||||
* Allow overriding UI localisation strings. ([#2837](https://github.com/stashapp/stash/pull/2837))
|
* Allow overriding UI localisation strings. ([#2837](https://github.com/stashapp/stash/pull/2837))
|
||||||
* Populate name from query field when creating new performer/studio/tag/gallery. ([#2701](https://github.com/stashapp/stash/pull/2701))
|
* Populate name from query field when creating new performer/studio/tag/gallery. ([#2701](https://github.com/stashapp/stash/pull/2701))
|
||||||
|
|||||||
@@ -458,6 +458,10 @@
|
|||||||
},
|
},
|
||||||
"heading": "Editing"
|
"heading": "Editing"
|
||||||
},
|
},
|
||||||
|
"abbreviate_counters": {
|
||||||
|
"description": "Abbreviate counters in cards and details view pages, for example \"1831\" will get formated to \"1.8K\".",
|
||||||
|
"heading": "Abbreviate counters"
|
||||||
|
},
|
||||||
"funscript_offset": {
|
"funscript_offset": {
|
||||||
"description": "Time offset in milliseconds for interactive scripts playback.",
|
"description": "Time offset in milliseconds for interactive scripts playback.",
|
||||||
"heading": "Funscript Offset (ms)"
|
"heading": "Funscript Offset (ms)"
|
||||||
|
|||||||
@@ -305,6 +305,29 @@ const capitalize = (val: string) =>
|
|||||||
.replace(/^[-_]*(.)/, (_, c) => c.toUpperCase())
|
.replace(/^[-_]*(.)/, (_, c) => c.toUpperCase())
|
||||||
.replace(/[-_]+(.)/g, (_, c) => ` ${c.toUpperCase()}`);
|
.replace(/[-_]+(.)/g, (_, c) => ` ${c.toUpperCase()}`);
|
||||||
|
|
||||||
|
type CountUnit = "" | "K" | "M" | "B";
|
||||||
|
const CountUnits: CountUnit[] = ["", "K", "M", "B"];
|
||||||
|
|
||||||
|
const abbreviateCounter = (counter: number = 0) => {
|
||||||
|
if (Number.isNaN(parseFloat(String(counter))) || !Number.isFinite(counter))
|
||||||
|
return { size: 0, unit: CountUnits[0] };
|
||||||
|
|
||||||
|
let unit = 0;
|
||||||
|
let digits = 0;
|
||||||
|
let count = counter;
|
||||||
|
while (count >= 1000 && unit + 1 < CountUnits.length) {
|
||||||
|
count /= 1000;
|
||||||
|
unit++;
|
||||||
|
digits = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
size: count,
|
||||||
|
unit: CountUnits[unit],
|
||||||
|
digits: digits,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const TextUtils = {
|
const TextUtils = {
|
||||||
fileSize,
|
fileSize,
|
||||||
formatFileSizeUnit,
|
formatFileSizeUnit,
|
||||||
@@ -322,6 +345,7 @@ const TextUtils = {
|
|||||||
formatDateTime,
|
formatDateTime,
|
||||||
capitalize,
|
capitalize,
|
||||||
secondsAsTimeString,
|
secondsAsTimeString,
|
||||||
|
abbreviateCounter,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TextUtils;
|
export default TextUtils;
|
||||||
|
|||||||
Reference in New Issue
Block a user