diff --git a/ui/v2.5/src/components/Changelog/versions/v090.md b/ui/v2.5/src/components/Changelog/versions/v090.md index 29105fbb2..b2157a772 100644 --- a/ui/v2.5/src/components/Changelog/versions/v090.md +++ b/ui/v2.5/src/components/Changelog/versions/v090.md @@ -6,6 +6,7 @@ * Added not equals/greater than/less than modifiers for resolution criteria. ([#1568](https://github.com/stashapp/stash/pull/1568)) ### 🎨 Improvements +* Improve link styling and ensure links open in a new tab. ([#1622](https://github.com/stashapp/stash/pull/1622)) * Added zh-CN language option. ([#1620](https://github.com/stashapp/stash/pull/1620)) * Moved scraping settings into the Scraping settings page. ([#1548](https://github.com/stashapp/stash/pull/1548)) * Show current scene details in tagger view. ([#1605](https://github.com/stashapp/stash/pull/1605)) diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryFileInfoPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryFileInfoPanel.tsx index 456896d16..e03e96178 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryFileInfoPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryFileInfoPanel.tsx @@ -1,7 +1,6 @@ import React from "react"; import * as GQL from "src/core/generated-graphql"; -import { TruncatedText } from "src/components/Shared"; -import { FormattedMessage } from "react-intl"; +import { TextField, URLField } from "src/utils/field"; interface IGalleryFileInfoPanelProps { gallery: GQL.GalleryDataFragment; @@ -10,36 +9,25 @@ interface IGalleryFileInfoPanelProps { export const GalleryFileInfoPanel: React.FC = ( props: IGalleryFileInfoPanelProps ) => { - function renderChecksum() { - return ( -
- - - - -
- ); - } - - function renderPath() { - const filePath = `file://${props.gallery.path}`; - - return ( -
- - - - - - -
- ); - } - return ( -
- {renderChecksum()} - {renderPath()} -
+
+ + + +
); }; diff --git a/ui/v2.5/src/components/Images/ImageDetails/ImageFileInfoPanel.tsx b/ui/v2.5/src/components/Images/ImageDetails/ImageFileInfoPanel.tsx index ba80d14e6..cefacb959 100644 --- a/ui/v2.5/src/components/Images/ImageDetails/ImageFileInfoPanel.tsx +++ b/ui/v2.5/src/components/Images/ImageDetails/ImageFileInfoPanel.tsx @@ -1,8 +1,8 @@ import React from "react"; -import { FormattedMessage, FormattedNumber } from "react-intl"; +import { FormattedNumber } from "react-intl"; import * as GQL from "src/core/generated-graphql"; import { TextUtils } from "src/utils"; -import { TruncatedText } from "src/components/Shared"; +import { TextField, URLField } from "src/utils/field"; interface IImageFileInfoPanelProps { image: GQL.ImageDataFragment; @@ -11,33 +11,6 @@ interface IImageFileInfoPanelProps { export const ImageFileInfoPanel: React.FC = ( props: IImageFileInfoPanelProps ) => { - function renderChecksum() { - return ( -
- - - - -
- ); - } - - function renderPath() { - const { - image: { path }, - } = props; - return ( -
- - - - - - {" "} -
- ); - } - function renderFileSize() { if (props.image.file.size === undefined) { return; @@ -46,9 +19,8 @@ export const ImageFileInfoPanel: React.FC = ( const { size, unit } = TextUtils.fileSize(props.image.file.size ?? 0); return ( -
- File Size - + + = ( maximumFractionDigits={2} /> -
+ ); } - function renderDimensions() { - if (props.image.file.height && props.image.file.width) { - return ( -
- Dimensions - - {props.image.file.width} x {props.image.file.height} - -
- ); - } - } - return ( -
- {renderChecksum()} - {renderPath()} +
+ + {renderFileSize()} - {renderDimensions()} -
+ + ); }; diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx index 1de2007af..2635e99fb 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx @@ -32,14 +32,12 @@ export const MovieDetailsPanel: React.FC = ({ movie }) => { } return ( -
-
- {intl.formatMessage({ id: "rating" })} -
-
+ <> +
{intl.formatMessage({ id: "rating" })}
+
-
+ ); } @@ -52,7 +50,7 @@ export const MovieDetailsPanel: React.FC = ({ movie }) => { {maybeRenderAliases()} -
+
= ({ movie }) => { /> -
+ ); }; diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx index eaeb024bd..4ea7de9bc 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx @@ -23,18 +23,18 @@ export const PerformerDetailsPanel: React.FC = ({ } return ( -
-
+ <> +
-
+
    {(performer.tags ?? []).map((tag) => ( ))}
-
+ ); } @@ -44,14 +44,14 @@ export const PerformerDetailsPanel: React.FC = ({ } return ( -
-
+ <> +
:
-
+
-
+ ); } @@ -61,9 +61,9 @@ export const PerformerDetailsPanel: React.FC = ({ } return ( -
-
StashIDs
-
+ <> +
StashIDs
+
    {performer.stash_ids.map((stashID) => { const base = stashID.endpoint.match(/https?:\/\/.*?\//)?.[0]; @@ -86,7 +86,7 @@ export const PerformerDetailsPanel: React.FC = ({ })}
-
+ ); } @@ -113,7 +113,7 @@ export const PerformerDetailsPanel: React.FC = ({ }; return ( - <> +
= ({ {renderRating()} {renderTagsField()} {renderStashIDs()} - +
); }; diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx index b0a78fda0..31b3ab384 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx @@ -1,8 +1,8 @@ import React from "react"; -import { FormattedMessage, FormattedNumber } from "react-intl"; +import { FormattedNumber } from "react-intl"; import * as GQL from "src/core/generated-graphql"; import { TextUtils } from "src/utils"; -import { TruncatedText } from "src/components/Shared"; +import { TextField, URLField } from "src/utils/field"; interface ISceneFileInfoPanelProps { scene: GQL.SceneDataFragment; @@ -11,61 +11,6 @@ interface ISceneFileInfoPanelProps { export const SceneFileInfoPanel: React.FC = ( props: ISceneFileInfoPanelProps ) => { - function renderOSHash() { - if (props.scene.oshash) { - return ( -
- - - - -
- ); - } - } - - function renderChecksum() { - if (props.scene.checksum) { - return ( -
- - - - -
- ); - } - } - - function renderPath() { - const { - scene: { path }, - } = props; - return ( -
- - - - - - {" "} -
- ); - } - - function renderStream() { - return ( -
- - - - - - {" "} -
- ); - } - function renderFileSize() { if (props.scene.file.size === undefined) { return; @@ -76,11 +21,8 @@ export const SceneFileInfoPanel: React.FC = ( ); return ( -
- - - - + + = ( maximumFractionDigits={2} /> -
- ); - } - - function renderDuration() { - if (props.scene.file.duration === undefined) { - return; - } - return ( -
- - - - -
- ); - } - - function renderDimensions() { - if (props.scene.file.duration === undefined) { - return; - } - return ( -
- - - - -
- ); - } - - function renderFrameRate() { - if (props.scene.file.framerate === undefined) { - return; - } - return ( -
- - - - - frames per - second - -
- ); - } - - function renderbitrate() { - // TODO: An upcoming react-intl version will support compound units, megabits-per-second - if (props.scene.file.bitrate === undefined) { - return; - } - return ( -
- - - - - -  megabits per second - -
- ); - } - - function renderVideoCodec() { - if (props.scene.file.video_codec === undefined) { - return; - } - return ( -
- - - - -
- ); - } - - function renderAudioCodec() { - if (props.scene.file.audio_codec === undefined) { - return; - } - return ( -
- - - - -
- ); - } - - function renderUrl() { - if (!props.scene.url || props.scene.url === "") { - return; - } - return ( -
- - - - - - -
+
); } @@ -216,76 +42,114 @@ export const SceneFileInfoPanel: React.FC = ( } return ( -
- StashIDs -
    - {props.scene.stash_ids.map((stashID) => { - const base = stashID.endpoint.match(/https?:\/\/.*?\//)?.[0]; - const link = base ? ( - - {stashID.stash_id} - - ) : ( - stashID.stash_id - ); - return ( -
  • - {link} -
  • - ); - })} -
-
+ <> +
StashIDs
+
+
    + {props.scene.stash_ids.map((stashID) => { + const base = stashID.endpoint.match(/https?:\/\/.*?\//)?.[0]; + const link = base ? ( + + {stashID.stash_id} + + ) : ( + stashID.stash_id + ); + return ( +
  • + {link} +
  • + ); + })} +
+
+ ); } - function renderPhash() { - if (props.scene.phash) { - return ( -
- - - - -
- ); - } - } - function renderFunscript() { if (props.scene.interactive) { return ( -
- Funscript - - - {" "} -
+ ); } } return ( -
- {renderOSHash()} - {renderChecksum()} - {renderPhash()} - {renderPath()} - {renderStream()} +
+ + + + + {renderFunscript()} {renderFileSize()} - {renderDuration()} - {renderDimensions()} - {renderFrameRate()} - {renderbitrate()} - {renderVideoCodec()} - {renderAudioCodec()} - {renderUrl()} + + + + frames per + second + + + {" "} + frames per second + + + + {renderStashIDs()} -
+ ); }; diff --git a/ui/v2.5/src/components/Shared/styles.scss b/ui/v2.5/src/components/Shared/styles.scss index 99a51bcb8..bff6a5907 100644 --- a/ui/v2.5/src/components/Shared/styles.scss +++ b/ui/v2.5/src/components/Shared/styles.scss @@ -186,6 +186,10 @@ button.collapse-button.btn-primary:not(:disabled):not(.disabled):active { max-width: 300px; white-space: pre-line; } + + .file-info-panel a > & { + word-break: break-all; + } } .RatingStars { diff --git a/ui/v2.5/src/index.scss b/ui/v2.5/src/index.scss index 58c81d61c..3fbad4278 100755 --- a/ui/v2.5/src/index.scss +++ b/ui/v2.5/src/index.scss @@ -643,3 +643,9 @@ div.dropdown-menu { border-bottom-right-radius: 0; } } + +dl.details-list { + display: grid; + grid-column-gap: 10px; + grid-template-columns: minmax(16.67%, auto) 1fr; +} diff --git a/ui/v2.5/src/utils/field.tsx b/ui/v2.5/src/utils/field.tsx index 7eaad5a12..68c8a8230 100644 --- a/ui/v2.5/src/utils/field.tsx +++ b/ui/v2.5/src/utils/field.tsx @@ -1,49 +1,85 @@ import React from "react"; import { FormattedMessage } from "react-intl"; +import { TruncatedText } from "../components/Shared"; interface ITextField { id?: string; name?: string; + abbr?: string | null; value?: string | null; + truncate?: boolean | null; } -export const TextField: React.FC = ({ id, name, value }) => { - if (!value) { +export const TextField: React.FC = ({ + id, + name, + value, + abbr, + truncate, + children, +}) => { + if (!value && !children) { return null; } + + const message = ( + <>{id ? : name}: + ); + return ( -
-
- {id ? : name}: -
-
{value ?? undefined}
-
+ <> +
{abbr ? {message} : message}
+
+ {value ? truncate ? : value : children} +
+ ); }; interface IURLField { id?: string; name?: string; + abbr?: string | null; value?: string | null; url?: string | null; + truncate?: boolean | null; } -export const URLField: React.FC = ({ id, name, value, url }) => { - if (!value) { +export const URLField: React.FC = ({ + id, + name, + value, + url, + abbr, + truncate, + children, +}) => { + if (!value && !children) { return null; } + + const message = ( + <>{id ? : name}: + ); + return ( -
-
- {id ? : name}: -
-
+ <> +
{abbr ? {message} : message}
+
{url ? ( - {value} + {value ? ( + truncate ? ( + + ) : ( + value + ) + ) : ( + children + )} ) : undefined}
-
+ ); };