mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 16:34:02 +01:00
Add FileSize component and refactor file size rendering in various components (#5695)
This commit is contained in:
parent
a391fa4345
commit
ce2d779dbc
9 changed files with 53 additions and 125 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState } from "react";
|
||||
import { Accordion, Button, Card } from "react-bootstrap";
|
||||
import { FormattedMessage, FormattedNumber, FormattedTime } from "react-intl";
|
||||
import { FormattedMessage, FormattedTime } from "react-intl";
|
||||
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
||||
import { DeleteFilesDialog } from "src/components/Shared/DeleteFilesDialog";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
|
|
@ -8,6 +8,7 @@ import { mutateImageSetPrimaryFile } from "src/core/StashService";
|
|||
import { useToast } from "src/hooks/Toast";
|
||||
import TextUtils from "src/utils/text";
|
||||
import { TextField, URLField, URLsField } from "src/utils/field";
|
||||
import { FileSize } from "src/components/Shared/FileSize";
|
||||
|
||||
interface IFileInfoPanelProps {
|
||||
file: GQL.ImageFileDataFragment | GQL.VideoFileDataFragment;
|
||||
|
|
@ -21,29 +22,6 @@ interface IFileInfoPanelProps {
|
|||
const FileInfoPanel: React.FC<IFileInfoPanelProps> = (
|
||||
props: IFileInfoPanelProps
|
||||
) => {
|
||||
function renderFileSize() {
|
||||
if (props.file.size === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { size, unit } = TextUtils.fileSize(props.file.size ?? 0);
|
||||
|
||||
return (
|
||||
<TextField id="filesize">
|
||||
<span className="text-truncate">
|
||||
<FormattedNumber
|
||||
value={size}
|
||||
// eslint-disable-next-line react/style-prop-object
|
||||
style="unit"
|
||||
unit={unit}
|
||||
unitDisplay="narrow"
|
||||
maximumFractionDigits={2}
|
||||
/>
|
||||
</span>
|
||||
</TextField>
|
||||
);
|
||||
}
|
||||
|
||||
const checksum = props.file.fingerprints.find((f) => f.type === "md5");
|
||||
|
||||
return (
|
||||
|
|
@ -64,7 +42,11 @@ const FileInfoPanel: React.FC<IFileInfoPanelProps> = (
|
|||
value={`file://${props.file.path}`}
|
||||
truncate
|
||||
/>
|
||||
{renderFileSize()}
|
||||
<TextField id="filesize">
|
||||
<span className="text-truncate">
|
||||
<FileSize size={props.file.size} />
|
||||
</span>
|
||||
</TextField>
|
||||
<TextField id="file_mod_time">
|
||||
<FormattedTime
|
||||
dateStyle="medium"
|
||||
|
|
|
|||
|
|
@ -23,11 +23,11 @@ import "flexbin/flexbin.css";
|
|||
import Gallery from "react-photo-gallery";
|
||||
import { ExportDialog } from "../Shared/ExportDialog";
|
||||
import { objectTitle } from "src/core/files";
|
||||
import TextUtils from "src/utils/text";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { ImageGridCard } from "./ImageGridCard";
|
||||
import { View } from "../List/views";
|
||||
import { IItemListOperation } from "../List/FilteredListToolbar";
|
||||
import { FileSize } from "../Shared/FileSize";
|
||||
|
||||
interface IImageWallProps {
|
||||
images: GQL.SlimImageDataFragment[];
|
||||
|
|
@ -230,7 +230,6 @@ function getCount(result: GQL.FindImagesQueryResult) {
|
|||
function renderMetadataByline(result: GQL.FindImagesQueryResult) {
|
||||
const megapixels = result?.data?.findImages?.megapixels;
|
||||
const size = result?.data?.findImages?.filesize;
|
||||
const filesize = size ? TextUtils.fileSize(size) : undefined;
|
||||
|
||||
if (!megapixels && !size) {
|
||||
return;
|
||||
|
|
@ -247,15 +246,9 @@ function renderMetadataByline(result: GQL.FindImagesQueryResult) {
|
|||
</span>
|
||||
) : undefined}
|
||||
{separator}
|
||||
{size && filesize ? (
|
||||
{size ? (
|
||||
<span className="images-size">
|
||||
<FormattedNumber
|
||||
value={filesize.size}
|
||||
maximumFractionDigits={TextUtils.fileSizeFractionalDigits(
|
||||
filesize.unit
|
||||
)}
|
||||
/>
|
||||
{` ${TextUtils.formatFileSizeUnit(filesize.unit)}`}
|
||||
<FileSize size={size} />
|
||||
</span>
|
||||
) : undefined}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import {
|
|||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { SceneMergeModal } from "../Scenes/SceneMergeDialog";
|
||||
import { objectTitle } from "src/core/files";
|
||||
import { FileSize } from "../Shared/FileSize";
|
||||
|
||||
const CLASSNAME = "duplicate-checker";
|
||||
|
||||
|
|
@ -326,19 +327,6 @@ export const SceneDuplicateChecker: React.FC = () => {
|
|||
resetCheckboxSelection();
|
||||
}
|
||||
|
||||
const renderFilesize = (filesize: number | null | undefined) => {
|
||||
const { size: parsedSize, unit } = TextUtils.fileSize(filesize ?? 0);
|
||||
return (
|
||||
<FormattedNumber
|
||||
value={parsedSize}
|
||||
style="unit"
|
||||
unit={unit}
|
||||
unitDisplay="narrow"
|
||||
maximumFractionDigits={2}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function maybeRenderMissingPhashWarning() {
|
||||
const missingPhashes = missingPhash?.findScenes.count ?? 0;
|
||||
if (missingPhashes > 0) {
|
||||
|
|
@ -917,7 +905,9 @@ export const SceneDuplicateChecker: React.FC = () => {
|
|||
{file?.duration &&
|
||||
TextUtils.secondsToTimestamp(file.duration)}
|
||||
</td>
|
||||
<td>{renderFilesize(file?.size ?? 0)}</td>
|
||||
<td>
|
||||
<FileSize size={file?.size ?? 0} />
|
||||
</td>
|
||||
<td>{`${file?.width ?? 0}x${file?.height ?? 0}`}</td>
|
||||
<td>
|
||||
<FormattedNumber
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { ConfigurationContext } from "src/hooks/Config";
|
|||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
|
||||
import { RatingBanner } from "../Shared/RatingBanner";
|
||||
import { FormattedMessage, FormattedNumber } from "react-intl";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import {
|
||||
faBox,
|
||||
faCopy,
|
||||
|
|
@ -30,6 +30,7 @@ import { PatchComponent } from "src/patch";
|
|||
import ScreenUtils from "src/utils/screen";
|
||||
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
|
||||
import { GroupTag } from "../Groups/GroupTag";
|
||||
import { FileSize } from "../Shared/FileSize";
|
||||
|
||||
interface IScenePreviewProps {
|
||||
isPortrait: boolean;
|
||||
|
|
@ -362,21 +363,11 @@ const SceneCardImage = PatchComponent(
|
|||
);
|
||||
|
||||
function maybeRenderSceneSpecsOverlay() {
|
||||
let sizeObj = null;
|
||||
if (file?.size) {
|
||||
sizeObj = TextUtils.fileSize(file.size);
|
||||
}
|
||||
return (
|
||||
<div className="scene-specs-overlay">
|
||||
{sizeObj != null ? (
|
||||
{file?.size !== undefined ? (
|
||||
<span className="overlay-filesize extra-scene-info">
|
||||
<FormattedNumber
|
||||
value={sizeObj.size}
|
||||
maximumFractionDigits={TextUtils.fileSizeFractionalDigits(
|
||||
sizeObj.unit
|
||||
)}
|
||||
/>
|
||||
{TextUtils.formatFileSizeUnit(sizeObj.unit)}
|
||||
<FileSize size={file.size} />
|
||||
</span>
|
||||
) : (
|
||||
""
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import TextUtils from "src/utils/text";
|
|||
import { TextField, URLField, URLsField } from "src/utils/field";
|
||||
import { StashIDPill } from "src/components/Shared/StashID";
|
||||
import { PatchComponent } from "../../../patch";
|
||||
import { FileSize } from "src/components/Shared/FileSize";
|
||||
|
||||
interface IFileInfoPanelProps {
|
||||
sceneID: string;
|
||||
|
|
@ -36,25 +37,6 @@ const FileInfoPanel: React.FC<IFileInfoPanelProps> = (
|
|||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
|
||||
function renderFileSize() {
|
||||
const { size, unit } = TextUtils.fileSize(props.file.size);
|
||||
|
||||
return (
|
||||
<TextField id="filesize">
|
||||
<span className="text-truncate">
|
||||
<FormattedNumber
|
||||
value={size}
|
||||
// eslint-disable-next-line react/style-prop-object
|
||||
style="unit"
|
||||
unit={unit}
|
||||
unitDisplay="narrow"
|
||||
maximumFractionDigits={2}
|
||||
/>
|
||||
</span>
|
||||
</TextField>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO - generalise fingerprints
|
||||
const oshash = props.file.fingerprints.find((f) => f.type === "oshash");
|
||||
const phash = props.file.fingerprints.find((f) => f.type === "phash");
|
||||
|
|
@ -94,7 +76,11 @@ const FileInfoPanel: React.FC<IFileInfoPanelProps> = (
|
|||
value={`file://${props.file.path}`}
|
||||
truncate
|
||||
/>
|
||||
{renderFileSize()}
|
||||
<TextField id="filesize">
|
||||
<span className="text-truncate">
|
||||
<FileSize size={props.file.size} />
|
||||
</span>
|
||||
</TextField>
|
||||
<TextField id="file_mod_time">
|
||||
<FormattedTime
|
||||
dateStyle="medium"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState } from "react";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import { FormattedNumber, useIntl } from "react-intl";
|
||||
import { useIntl } from "react-intl";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import Mousetrap from "mousetrap";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
|
|
@ -25,6 +25,7 @@ import { SceneMergeModal } from "./SceneMergeDialog";
|
|||
import { objectTitle } from "src/core/files";
|
||||
import TextUtils from "src/utils/text";
|
||||
import { View } from "../List/views";
|
||||
import { FileSize } from "../Shared/FileSize";
|
||||
|
||||
function getItems(result: GQL.FindScenesQueryResult) {
|
||||
return result?.data?.findScenes?.scenes ?? [];
|
||||
|
|
@ -37,7 +38,6 @@ function getCount(result: GQL.FindScenesQueryResult) {
|
|||
function renderMetadataByline(result: GQL.FindScenesQueryResult) {
|
||||
const duration = result?.data?.findScenes?.duration;
|
||||
const size = result?.data?.findScenes?.filesize;
|
||||
const filesize = size ? TextUtils.fileSize(size) : undefined;
|
||||
|
||||
if (!duration && !size) {
|
||||
return;
|
||||
|
|
@ -54,15 +54,9 @@ function renderMetadataByline(result: GQL.FindScenesQueryResult) {
|
|||
</span>
|
||||
) : undefined}
|
||||
{separator}
|
||||
{size && filesize ? (
|
||||
{size ? (
|
||||
<span className="scenes-size">
|
||||
<FormattedNumber
|
||||
value={filesize.size}
|
||||
maximumFractionDigits={TextUtils.fileSizeFractionalDigits(
|
||||
filesize.unit
|
||||
)}
|
||||
/>
|
||||
{` ${TextUtils.formatFileSizeUnit(filesize.unit)}`}
|
||||
<FileSize size={size} />
|
||||
</span>
|
||||
) : undefined}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Link } from "react-router-dom";
|
|||
import * as GQL from "src/core/generated-graphql";
|
||||
import NavUtils from "src/utils/navigation";
|
||||
import TextUtils from "src/utils/text";
|
||||
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { objectTitle } from "src/core/files";
|
||||
import { galleryTitle } from "src/core/galleries";
|
||||
import SceneQueue from "src/models/sceneQueue";
|
||||
|
|
@ -11,6 +11,7 @@ import { RatingSystem } from "../Shared/Rating/RatingSystem";
|
|||
import { useSceneUpdate } from "src/core/StashService";
|
||||
import { IColumn, ListTable } from "../List/ListTable";
|
||||
import { useTableColumns } from "src/hooks/useTableColumns";
|
||||
import { FileSize } from "../Shared/FileSize";
|
||||
|
||||
interface ISceneListTableProps {
|
||||
scenes: GQL.SlimSceneDataFragment[];
|
||||
|
|
@ -169,24 +170,12 @@ export const SceneListTable: React.FC<ISceneListTableProps> = (
|
|||
</ul>
|
||||
);
|
||||
|
||||
function renderFileSize(file: { size: number | undefined }) {
|
||||
const { size, unit } = TextUtils.fileSize(file.size);
|
||||
|
||||
return (
|
||||
<FormattedNumber
|
||||
value={size}
|
||||
style="unit"
|
||||
unit={unit}
|
||||
unitDisplay="narrow"
|
||||
maximumFractionDigits={2}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const FileSizeCell = (scene: GQL.SlimSceneDataFragment) => (
|
||||
<ul className="comma-list">
|
||||
{scene.files.map((file) => (
|
||||
<li key={file.id}>{renderFileSize(file)}</li>
|
||||
<li key={file.id}>
|
||||
<FileSize size={file.size} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
|
|
|
|||
17
ui/v2.5/src/components/Shared/FileSize.tsx
Normal file
17
ui/v2.5/src/components/Shared/FileSize.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import React from "react";
|
||||
import { FormattedNumber } from "react-intl";
|
||||
import TextUtils from "src/utils/text";
|
||||
|
||||
export const FileSize: React.FC<{ size: number }> = ({ size: fileSize }) => {
|
||||
const { size, unit } = TextUtils.fileSize(fileSize);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormattedNumber
|
||||
value={size}
|
||||
maximumFractionDigits={TextUtils.fileSizeFractionalDigits(unit)}
|
||||
/>
|
||||
{` ${TextUtils.formatFileSizeUnit(unit)}`}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -3,6 +3,7 @@ import { useStats } from "src/core/StashService";
|
|||
import { FormattedMessage, FormattedNumber } from "react-intl";
|
||||
import { LoadingIndicator } from "src/components/Shared/LoadingIndicator";
|
||||
import TextUtils from "src/utils/text";
|
||||
import { FileSize } from "./Shared/FileSize";
|
||||
|
||||
export const Stats: React.FC = () => {
|
||||
const { data, error, loading } = useStats();
|
||||
|
|
@ -10,9 +11,6 @@ export const Stats: React.FC = () => {
|
|||
if (error) return <span>{error.message}</span>;
|
||||
if (loading || !data) return <LoadingIndicator />;
|
||||
|
||||
const scenesSize = TextUtils.fileSize(data.stats.scenes_size);
|
||||
const imagesSize = TextUtils.fileSize(data.stats.images_size);
|
||||
|
||||
const scenesDuration = TextUtils.secondsAsTimeString(
|
||||
data.stats.scenes_duration,
|
||||
3
|
||||
|
|
@ -28,13 +26,7 @@ export const Stats: React.FC = () => {
|
|||
<div className="col col-sm-8 m-sm-auto row stats">
|
||||
<div className="stats-element">
|
||||
<p className="title">
|
||||
<FormattedNumber
|
||||
value={scenesSize.size}
|
||||
maximumFractionDigits={TextUtils.fileSizeFractionalDigits(
|
||||
scenesSize.unit
|
||||
)}
|
||||
/>
|
||||
{` ${TextUtils.formatFileSizeUnit(scenesSize.unit)}`}
|
||||
<FileSize size={data.stats.scenes_size} />
|
||||
</p>
|
||||
<p className="heading">
|
||||
<FormattedMessage id="stats.scenes_size" />
|
||||
|
|
@ -74,13 +66,7 @@ export const Stats: React.FC = () => {
|
|||
<div className="col col-sm-8 m-sm-auto row stats">
|
||||
<div className="stats-element">
|
||||
<p className="title">
|
||||
<FormattedNumber
|
||||
value={imagesSize.size}
|
||||
maximumFractionDigits={TextUtils.fileSizeFractionalDigits(
|
||||
imagesSize.unit
|
||||
)}
|
||||
/>
|
||||
{` ${TextUtils.formatFileSizeUnit(imagesSize.unit)}`}
|
||||
<FileSize size={data.stats.images_size} />
|
||||
</p>
|
||||
<p className="heading">
|
||||
<FormattedMessage id="stats.image_size" />
|
||||
|
|
|
|||
Loading…
Reference in a new issue