mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 12:54:38 +03:00
Detail redesign round 2 bug fixes (#3990)
* increase full-width for measurement issue in apple devices * trade left margin for more full-width * removed isApple code from details page
This commit is contained in:
@@ -39,6 +39,7 @@ import { IUIConfig } from "./core/config";
|
|||||||
import { releaseNotes } from "./docs/en/ReleaseNotes";
|
import { releaseNotes } from "./docs/en/ReleaseNotes";
|
||||||
import { getPlatformURL } from "./core/createClient";
|
import { getPlatformURL } from "./core/createClient";
|
||||||
import { lazyComponent } from "./utils/lazyComponent";
|
import { lazyComponent } from "./utils/lazyComponent";
|
||||||
|
import { isPlatformUniquelyRenderedByApple } from "./utils/apple";
|
||||||
|
|
||||||
const Performers = lazyComponent(
|
const Performers = lazyComponent(
|
||||||
() => import("./components/Performers/Performers")
|
() => import("./components/Performers/Performers")
|
||||||
@@ -67,6 +68,8 @@ const SceneDuplicateChecker = lazyComponent(
|
|||||||
() => import("./components/SceneDuplicateChecker/SceneDuplicateChecker")
|
() => import("./components/SceneDuplicateChecker/SceneDuplicateChecker")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const appleRendering = isPlatformUniquelyRenderedByApple();
|
||||||
|
|
||||||
initPolyfills();
|
initPolyfills();
|
||||||
|
|
||||||
MousetrapPause(Mousetrap);
|
MousetrapPause(Mousetrap);
|
||||||
@@ -274,7 +277,11 @@ export const App: React.FC = () => {
|
|||||||
defaultTitle="Stash"
|
defaultTitle="Stash"
|
||||||
/>
|
/>
|
||||||
{maybeRenderNavbar()}
|
{maybeRenderNavbar()}
|
||||||
<div className="main container-fluid">
|
<div
|
||||||
|
className={`main container-fluid ${
|
||||||
|
appleRendering ? "apple" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</div>
|
</div>
|
||||||
</InteractiveProvider>
|
</InteractiveProvider>
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import { ConfigurationContext } from "src/hooks/Config";
|
|||||||
import { IUIConfig } from "src/core/config";
|
import { IUIConfig } from "src/core/config";
|
||||||
import ImageUtils from "src/utils/image";
|
import ImageUtils from "src/utils/image";
|
||||||
import { useRatingKeybinds } from "src/hooks/keybinds";
|
import { useRatingKeybinds } from "src/hooks/keybinds";
|
||||||
import { isPlatformUniquelyRenderedByApple } from "src/utils/apple";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
movie: GQL.MovieDataFragment;
|
movie: GQL.MovieDataFragment;
|
||||||
@@ -65,8 +64,6 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
|
|||||||
const [backImage, setBackImage] = useState<string | null>();
|
const [backImage, setBackImage] = useState<string | null>();
|
||||||
const [encodingImage, setEncodingImage] = useState<boolean>(false);
|
const [encodingImage, setEncodingImage] = useState<boolean>(false);
|
||||||
|
|
||||||
const appleRendering = isPlatformUniquelyRenderedByApple();
|
|
||||||
|
|
||||||
const defaultImage =
|
const defaultImage =
|
||||||
movie.front_image_path && movie.front_image_path.includes("default=true")
|
movie.front_image_path && movie.front_image_path.includes("default=true")
|
||||||
? true
|
? true
|
||||||
@@ -417,7 +414,7 @@ const MoviePage: React.FC<IProps> = ({ movie }) => {
|
|||||||
<div
|
<div
|
||||||
className={`detail-header ${isEditing ? "edit" : ""} ${
|
className={`detail-header ${isEditing ? "edit" : ""} ${
|
||||||
collapsed ? "collapsed" : !compactExpandedDetails ? "full-width" : ""
|
collapsed ? "collapsed" : !compactExpandedDetails ? "full-width" : ""
|
||||||
} ${appleRendering ? "apple" : ""}`}
|
}`}
|
||||||
>
|
>
|
||||||
{maybeRenderHeaderBackgroundImage()}
|
{maybeRenderHeaderBackgroundImage()}
|
||||||
<div className="detail-container">
|
<div className="detail-container">
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export const CompressedMovieDetailsPanel: React.FC<IMovieDetailsPanel> = ({
|
|||||||
<a className="movie-name" onClick={() => scrollToTop()}>
|
<a className="movie-name" onClick={() => scrollToTop()}>
|
||||||
{movie.name}
|
{movie.name}
|
||||||
</a>
|
</a>
|
||||||
|
<span className="detail-divider">/</span>
|
||||||
{movie?.studio?.name ? (
|
{movie?.studio?.name ? (
|
||||||
<span className="movie-studio">{movie?.studio?.name}</span>
|
<span className="movie-studio">{movie?.studio?.name}</span>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import { faInstagram, faTwitter } from "@fortawesome/free-brands-svg-icons";
|
|||||||
import { IUIConfig } from "src/core/config";
|
import { IUIConfig } from "src/core/config";
|
||||||
import { useRatingKeybinds } from "src/hooks/keybinds";
|
import { useRatingKeybinds } from "src/hooks/keybinds";
|
||||||
import ImageUtils from "src/utils/image";
|
import ImageUtils from "src/utils/image";
|
||||||
import { isPlatformUniquelyRenderedByApple } from "src/utils/apple";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
performer: GQL.PerformerDataFragment;
|
performer: GQL.PerformerDataFragment;
|
||||||
@@ -73,8 +72,6 @@ const PerformerPage: React.FC<IProps> = ({ performer }) => {
|
|||||||
const [encodingImage, setEncodingImage] = useState<boolean>(false);
|
const [encodingImage, setEncodingImage] = useState<boolean>(false);
|
||||||
const [loadStickyHeader, setLoadStickyHeader] = useState<boolean>(false);
|
const [loadStickyHeader, setLoadStickyHeader] = useState<boolean>(false);
|
||||||
|
|
||||||
const appleRendering = isPlatformUniquelyRenderedByApple();
|
|
||||||
|
|
||||||
const activeImage = useMemo(() => {
|
const activeImage = useMemo(() => {
|
||||||
const performerImage = performer.image_path;
|
const performerImage = performer.image_path;
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
@@ -549,7 +546,7 @@ const PerformerPage: React.FC<IProps> = ({ performer }) => {
|
|||||||
<div
|
<div
|
||||||
className={`detail-header ${isEditing ? "edit" : ""} ${
|
className={`detail-header ${isEditing ? "edit" : ""} ${
|
||||||
collapsed ? "collapsed" : !compactExpandedDetails ? "full-width" : ""
|
collapsed ? "collapsed" : !compactExpandedDetails ? "full-width" : ""
|
||||||
} ${appleRendering ? "apple" : ""}`}
|
}`}
|
||||||
>
|
>
|
||||||
{maybeRenderHeaderBackgroundImage()}
|
{maybeRenderHeaderBackgroundImage()}
|
||||||
<div className="detail-container">
|
<div className="detail-container">
|
||||||
|
|||||||
@@ -97,6 +97,21 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatAge = (birthdate?: string | null, deathdate?: string | null) => {
|
||||||
|
if (!birthdate) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const age = TextUtils.age(birthdate, deathdate);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="performer-age">
|
||||||
|
<span className="age">{age}</span>
|
||||||
|
<span className="birthdate"> ({birthdate})</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const formatWeight = (weight?: number | null) => {
|
const formatWeight = (weight?: number | null) => {
|
||||||
if (!weight) {
|
if (!weight) {
|
||||||
return "";
|
return "";
|
||||||
@@ -220,8 +235,16 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||||||
)}
|
)}
|
||||||
<DetailItem
|
<DetailItem
|
||||||
id="age"
|
id="age"
|
||||||
value={TextUtils.age(performer.birthdate, performer.death_date)}
|
value={
|
||||||
title={TextUtils.formatDate(intl, performer.birthdate ?? undefined)}
|
!fullWidth
|
||||||
|
? TextUtils.age(performer.birthdate, performer.death_date)
|
||||||
|
: formatAge(performer.birthdate, performer.death_date)
|
||||||
|
}
|
||||||
|
title={
|
||||||
|
!fullWidth
|
||||||
|
? TextUtils.formatDate(intl, performer.birthdate ?? undefined)
|
||||||
|
: ""
|
||||||
|
}
|
||||||
fullWidth={fullWidth}
|
fullWidth={fullWidth}
|
||||||
/>
|
/>
|
||||||
<DetailItem id="death_date" value={performer.death_date} />
|
<DetailItem id="death_date" value={performer.death_date} />
|
||||||
@@ -306,6 +329,7 @@ export const CompressedPerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||||||
<a className="performer-name" onClick={() => scrollToTop()}>
|
<a className="performer-name" onClick={() => scrollToTop()}>
|
||||||
{performer.name}
|
{performer.name}
|
||||||
</a>
|
</a>
|
||||||
|
<span className="detail-divider">/</span>
|
||||||
{performer.gender ? (
|
{performer.gender ? (
|
||||||
<span className="performer-gender">
|
<span className="performer-gender">
|
||||||
{intl.formatMessage({ id: "gender_types." + performer.gender })}
|
{intl.formatMessage({ id: "gender_types." + performer.gender })}
|
||||||
@@ -313,6 +337,7 @@ export const CompressedPerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
|
<span className="detail-divider">/</span>
|
||||||
{performer.birthdate ? (
|
{performer.birthdate ? (
|
||||||
<span
|
<span
|
||||||
className="performer-age"
|
className="performer-age"
|
||||||
@@ -323,6 +348,7 @@ export const CompressedPerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
|
<span className="detail-divider">/</span>
|
||||||
{performer.country ? (
|
{performer.country ? (
|
||||||
<span className="performer-country">
|
<span className="performer-country">
|
||||||
<CountryFlag
|
<CountryFlag
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.col-md-8 .details-edit div:nth-last-child(2),
|
||||||
.detail-header.edit .details-edit div:nth-last-child(2) {
|
.detail-header.edit .details-edit div:nth-last-child(2) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import TextUtils from "src/utils/text";
|
|||||||
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
||||||
import ImageUtils from "src/utils/image";
|
import ImageUtils from "src/utils/image";
|
||||||
import { useRatingKeybinds } from "src/hooks/keybinds";
|
import { useRatingKeybinds } from "src/hooks/keybinds";
|
||||||
import { isPlatformUniquelyRenderedByApple } from "src/utils/apple";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
studio: GQL.StudioDataFragment;
|
studio: GQL.StudioDataFragment;
|
||||||
@@ -69,8 +68,6 @@ const StudioPage: React.FC<IProps> = ({ studio }) => {
|
|||||||
const [collapsed, setCollapsed] = useState<boolean>(!showAllDetails);
|
const [collapsed, setCollapsed] = useState<boolean>(!showAllDetails);
|
||||||
const [loadStickyHeader, setLoadStickyHeader] = useState<boolean>(false);
|
const [loadStickyHeader, setLoadStickyHeader] = useState<boolean>(false);
|
||||||
|
|
||||||
const appleRendering = isPlatformUniquelyRenderedByApple();
|
|
||||||
|
|
||||||
// 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);
|
||||||
@@ -507,7 +504,7 @@ const StudioPage: React.FC<IProps> = ({ studio }) => {
|
|||||||
<div
|
<div
|
||||||
className={`detail-header ${isEditing ? "edit" : ""} ${
|
className={`detail-header ${isEditing ? "edit" : ""} ${
|
||||||
collapsed ? "collapsed" : !compactExpandedDetails ? "full-width" : ""
|
collapsed ? "collapsed" : !compactExpandedDetails ? "full-width" : ""
|
||||||
} ${appleRendering ? "apple" : ""}`}
|
}`}
|
||||||
>
|
>
|
||||||
{maybeRenderHeaderBackgroundImage()}
|
{maybeRenderHeaderBackgroundImage()}
|
||||||
<div className="detail-container">
|
<div className="detail-container">
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ export const CompressedStudioDetailsPanel: React.FC<IStudioDetailsPanel> = ({
|
|||||||
<a className="studio-name" onClick={() => scrollToTop()}>
|
<a className="studio-name" onClick={() => scrollToTop()}>
|
||||||
{studio.name}
|
{studio.name}
|
||||||
</a>
|
</a>
|
||||||
|
<span className="detail-divider">/</span>
|
||||||
{studio?.parent_studio?.name ? (
|
{studio?.parent_studio?.name ? (
|
||||||
<span className="studio-parent">{studio?.parent_studio?.name}</span>
|
<span className="studio-parent">{studio?.parent_studio?.name}</span>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import {
|
|||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { IUIConfig } from "src/core/config";
|
import { IUIConfig } from "src/core/config";
|
||||||
import ImageUtils from "src/utils/image";
|
import ImageUtils from "src/utils/image";
|
||||||
import { isPlatformUniquelyRenderedByApple } from "src/utils/apple";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
tag: GQL.TagDataFragment;
|
tag: GQL.TagDataFragment;
|
||||||
@@ -64,8 +63,6 @@ const TagPage: React.FC<IProps> = ({ tag }) => {
|
|||||||
const [collapsed, setCollapsed] = useState<boolean>(!showAllDetails);
|
const [collapsed, setCollapsed] = useState<boolean>(!showAllDetails);
|
||||||
const [loadStickyHeader, setLoadStickyHeader] = useState<boolean>(false);
|
const [loadStickyHeader, setLoadStickyHeader] = useState<boolean>(false);
|
||||||
|
|
||||||
const appleRendering = isPlatformUniquelyRenderedByApple();
|
|
||||||
|
|
||||||
const { tab = "scenes" } = useParams<ITabParams>();
|
const { tab = "scenes" } = useParams<ITabParams>();
|
||||||
|
|
||||||
// Editing state
|
// Editing state
|
||||||
@@ -499,7 +496,7 @@ const TagPage: React.FC<IProps> = ({ tag }) => {
|
|||||||
<div
|
<div
|
||||||
className={`detail-header ${isEditing ? "edit" : ""} ${
|
className={`detail-header ${isEditing ? "edit" : ""} ${
|
||||||
collapsed ? "collapsed" : !compactExpandedDetails ? "full-width" : ""
|
collapsed ? "collapsed" : !compactExpandedDetails ? "full-width" : ""
|
||||||
} ${appleRendering ? "apple" : ""}`}
|
}`}
|
||||||
>
|
>
|
||||||
{maybeRenderHeaderBackgroundImage()}
|
{maybeRenderHeaderBackgroundImage()}
|
||||||
<div className="detail-container">
|
<div className="detail-container">
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ export const CompressedTagDetailsPanel: React.FC<ITagDetails> = ({ tag }) => {
|
|||||||
<a className="tag-name" onClick={() => scrollToTop()}>
|
<a className="tag-name" onClick={() => scrollToTop()}>
|
||||||
{tag.name}
|
{tag.name}
|
||||||
</a>
|
</a>
|
||||||
|
<span className="detail-divider">/</span>
|
||||||
{tag.description ? (
|
{tag.description ? (
|
||||||
<span className="tag-desc">{tag.description}</span>
|
<span className="tag-desc">{tag.description}</span>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -69,26 +69,6 @@ dd {
|
|||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sticky.detail-header-group {
|
|
||||||
padding: 1rem 2.5rem;
|
|
||||||
|
|
||||||
a.movie-name,
|
|
||||||
a.performer-name,
|
|
||||||
a.studio-name,
|
|
||||||
a.tag-name {
|
|
||||||
color: #f5f8fa;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
a,
|
|
||||||
span {
|
|
||||||
color: #d7d9db;
|
|
||||||
font-weight: 600;
|
|
||||||
padding-right: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky.detail-header {
|
.sticky.detail-header {
|
||||||
display: block;
|
display: block;
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
@@ -107,6 +87,32 @@ dd {
|
|||||||
.tag-name {
|
.tag-name {
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sticky.detail-header-group {
|
||||||
|
padding: 1rem 2.5rem;
|
||||||
|
|
||||||
|
a.movie-name,
|
||||||
|
a.performer-name,
|
||||||
|
a.studio-name,
|
||||||
|
a.tag-name {
|
||||||
|
color: #f5f8fa;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
span {
|
||||||
|
color: #d7d9db;
|
||||||
|
font-weight: 600;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-divider {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-expand-collapse {
|
.detail-expand-collapse {
|
||||||
@@ -170,6 +176,11 @@ dd {
|
|||||||
border-bottom: 1px dotted #f5f8fa;
|
border-bottom: 1px dotted #f5f8fa;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.performer-disambiguation {
|
||||||
|
letter-spacing: -0.04rem;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@@ -195,7 +206,7 @@ dd {
|
|||||||
|
|
||||||
.detail-header.edit {
|
.detail-header.edit {
|
||||||
background-color: unset;
|
background-color: unset;
|
||||||
overflow: auto;
|
overflow: visible;
|
||||||
|
|
||||||
form {
|
form {
|
||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
@@ -208,6 +219,11 @@ dd {
|
|||||||
.detail-header-image {
|
.detail-header-image {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* StashID alignment fix */
|
||||||
|
.form-group.row .row.no-gutters {
|
||||||
|
padding-top: calc(0.375rem + 1px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-header.collapsed {
|
.detail-header.collapsed {
|
||||||
@@ -255,14 +271,11 @@ dd {
|
|||||||
|
|
||||||
.detail-item-title {
|
.detail-item-title {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
width: 100px;
|
width: 130px;
|
||||||
}
|
|
||||||
|
|
||||||
.detail-item-value {
|
|
||||||
margin-left: 1.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-item-value.age {
|
.detail-item-value.age {
|
||||||
|
border-bottom: unset;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,13 +373,21 @@ dd {
|
|||||||
|
|
||||||
/* the .apple class denotes areas where rendering on some apple platforms has been inconsistent with other platforms
|
/* the .apple class denotes areas where rendering on some apple platforms has been inconsistent with other platforms
|
||||||
these rules aim to address those inconsistences */
|
these rules aim to address those inconsistences */
|
||||||
.detail-header.apple .detail-container {
|
.apple {
|
||||||
display: flex;
|
.detail-header {
|
||||||
}
|
.detail-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.detail-header.full-width.apple .detail-header-image,
|
.detail-header.edit .row {
|
||||||
.detail-header.edit.apple .detail-header-image {
|
flex: 1;
|
||||||
display: unset;
|
}
|
||||||
|
|
||||||
|
.detail-header.full-width .detail-header-image,
|
||||||
|
.detail-header.edit .detail-header-image {
|
||||||
|
display: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-item-title {
|
.detail-item-title {
|
||||||
@@ -379,6 +400,13 @@ dd {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
|
|
||||||
|
.birthdate,
|
||||||
|
.height-imperial,
|
||||||
|
.penis-length-imperial,
|
||||||
|
.weight-imperial {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-control,
|
.input-control,
|
||||||
|
|||||||
Reference in New Issue
Block a user