mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
New patchable performer page components (#5897)
This commit is contained in:
parent
d9a316d083
commit
c66ef42480
8 changed files with 263 additions and 213 deletions
|
|
@ -46,6 +46,7 @@ import { AliasList } from "src/components/Shared/DetailsPage/AliasList";
|
||||||
import { HeaderImage } from "src/components/Shared/DetailsPage/HeaderImage";
|
import { HeaderImage } from "src/components/Shared/DetailsPage/HeaderImage";
|
||||||
import { LightboxLink } from "src/hooks/Lightbox/LightboxLink";
|
import { LightboxLink } from "src/hooks/Lightbox/LightboxLink";
|
||||||
import { PatchComponent } from "src/patch";
|
import { PatchComponent } from "src/patch";
|
||||||
|
import { ILightboxImage } from "src/hooks/Lightbox/types";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
performer: GQL.PerformerDataFragment;
|
performer: GQL.PerformerDataFragment;
|
||||||
|
|
@ -201,6 +202,34 @@ const PerformerTabs: React.FC<{
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface IPerformerHeaderImageProps {
|
||||||
|
activeImage: string | null | undefined;
|
||||||
|
collapsed: boolean;
|
||||||
|
encodingImage: boolean;
|
||||||
|
lightboxImages: ILightboxImage[];
|
||||||
|
performer: GQL.PerformerDataFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PerformerHeaderImage: React.FC<IPerformerHeaderImageProps> =
|
||||||
|
PatchComponent(
|
||||||
|
"PerformerHeaderImage",
|
||||||
|
({ encodingImage, activeImage, lightboxImages, performer }) => {
|
||||||
|
return (
|
||||||
|
<HeaderImage encodingImage={encodingImage}>
|
||||||
|
{!!activeImage && (
|
||||||
|
<LightboxLink images={lightboxImages}>
|
||||||
|
<DetailImage
|
||||||
|
className="performer"
|
||||||
|
src={activeImage}
|
||||||
|
alt={performer.name}
|
||||||
|
/>
|
||||||
|
</LightboxLink>
|
||||||
|
)}
|
||||||
|
</HeaderImage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const PerformerPage: React.FC<IProps> = PatchComponent(
|
const PerformerPage: React.FC<IProps> = PatchComponent(
|
||||||
"PerformerPage",
|
"PerformerPage",
|
||||||
({ performer, tabKey }) => {
|
({ performer, tabKey }) => {
|
||||||
|
|
@ -364,18 +393,13 @@ const PerformerPage: React.FC<IProps> = PatchComponent(
|
||||||
show={enableBackgroundImage && !isEditing}
|
show={enableBackgroundImage && !isEditing}
|
||||||
/>
|
/>
|
||||||
<div className="detail-container">
|
<div className="detail-container">
|
||||||
<HeaderImage encodingImage={encodingImage}>
|
<PerformerHeaderImage
|
||||||
{!!activeImage && (
|
activeImage={activeImage}
|
||||||
<LightboxLink images={lightboxImages}>
|
collapsed={collapsed}
|
||||||
<DetailImage
|
encodingImage={encodingImage}
|
||||||
className="performer"
|
lightboxImages={lightboxImages}
|
||||||
src={activeImage}
|
performer={performer}
|
||||||
alt={performer.name}
|
|
||||||
/>
|
/>
|
||||||
</LightboxLink>
|
|
||||||
)}
|
|
||||||
</HeaderImage>
|
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="performer-head col">
|
<div className="performer-head col">
|
||||||
<DetailTitle
|
<DetailTitle
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { cloneDeep } from "@apollo/client/utilities";
|
||||||
import { Icon } from "./Icon";
|
import { Icon } from "./Icon";
|
||||||
import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
|
import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
const maxFieldNameLength = 64;
|
const maxFieldNameLength = 64;
|
||||||
|
|
||||||
|
|
@ -90,7 +91,9 @@ const CustomFieldInput: React.FC<{
|
||||||
onChange: (field: string, value: unknown) => void;
|
onChange: (field: string, value: unknown) => void;
|
||||||
isNew?: boolean;
|
isNew?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
}> = ({ field, value, onChange, isNew = false, error }) => {
|
}> = PatchComponent(
|
||||||
|
"CustomFieldInput",
|
||||||
|
({ field, value, onChange, isNew = false, error }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [currentField, setCurrentField] = useState(field);
|
const [currentField, setCurrentField] = useState(field);
|
||||||
const [currentValue, setCurrentValue] = useState(value as string);
|
const [currentValue, setCurrentValue] = useState(value as string);
|
||||||
|
|
@ -113,7 +116,9 @@ const CustomFieldInput: React.FC<{
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<Row className={cx("custom-fields-row", { "custom-fields-new": isNew })}>
|
<Row
|
||||||
|
className={cx("custom-fields-row", { "custom-fields-new": isNew })}
|
||||||
|
>
|
||||||
<Col sm={3} xl={2} className="custom-fields-field">
|
<Col sm={3} xl={2} className="custom-fields-field">
|
||||||
{isNew ? (
|
{isNew ? (
|
||||||
<>
|
<>
|
||||||
|
|
@ -122,8 +127,12 @@ const CustomFieldInput: React.FC<{
|
||||||
className="input-control"
|
className="input-control"
|
||||||
type="text"
|
type="text"
|
||||||
value={currentField ?? ""}
|
value={currentField ?? ""}
|
||||||
placeholder={intl.formatMessage({ id: "custom_fields.field" })}
|
placeholder={intl.formatMessage({
|
||||||
onChange={(event) => setCurrentField(event.currentTarget.value)}
|
id: "custom_fields.field",
|
||||||
|
})}
|
||||||
|
onChange={(event) =>
|
||||||
|
setCurrentField(event.currentTarget.value)
|
||||||
|
}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
@ -159,7 +168,8 @@ const CustomFieldInput: React.FC<{
|
||||||
<Form.Control.Feedback type="invalid">{error}</Form.Control.Feedback>
|
<Form.Control.Feedback type="invalid">{error}</Form.Control.Feedback>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
interface ICustomField {
|
interface ICustomField {
|
||||||
field: string;
|
field: string;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { useLayoutEffect, useRef } from "react";
|
import { useLayoutEffect, useRef } from "react";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
import { remToPx } from "src/utils/units";
|
import { remToPx } from "src/utils/units";
|
||||||
|
|
||||||
const DEFAULT_WIDTH = Math.round(remToPx(30));
|
const DEFAULT_WIDTH = Math.round(remToPx(30));
|
||||||
|
|
@ -6,7 +7,9 @@ const DEFAULT_WIDTH = Math.round(remToPx(30));
|
||||||
// Props used by the <img> element
|
// Props used by the <img> element
|
||||||
type IDetailImageProps = JSX.IntrinsicElements["img"];
|
type IDetailImageProps = JSX.IntrinsicElements["img"];
|
||||||
|
|
||||||
export const DetailImage = (props: IDetailImageProps) => {
|
export const DetailImage = PatchComponent(
|
||||||
|
"DetailImage",
|
||||||
|
(props: IDetailImageProps) => {
|
||||||
const imgRef = useRef<HTMLImageElement>(null);
|
const imgRef = useRef<HTMLImageElement>(null);
|
||||||
|
|
||||||
function fixWidth() {
|
function fixWidth() {
|
||||||
|
|
@ -36,4 +39,5 @@ export const DetailImage = (props: IDetailImageProps) => {
|
||||||
}, [props.src]);
|
}, [props.src]);
|
||||||
|
|
||||||
return <img ref={imgRef} onLoad={() => fixWidth()} {...props} />;
|
return <img ref={imgRef} onLoad={() => fixWidth()} {...props} />;
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from "react";
|
||||||
import { LoadingIndicator } from "../LoadingIndicator";
|
import { LoadingIndicator } from "../LoadingIndicator";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
export const HeaderImage: React.FC<
|
export const HeaderImage: React.FC<
|
||||||
PropsWithChildren<{
|
PropsWithChildren<{
|
||||||
encodingImage: boolean;
|
encodingImage: boolean;
|
||||||
}>
|
}>
|
||||||
> = ({ encodingImage, children }) => {
|
> = PatchComponent("HeaderImage", ({ encodingImage, children }) => {
|
||||||
return (
|
return (
|
||||||
<div className="detail-header-image">
|
<div className="detail-header-image">
|
||||||
{encodingImage ? (
|
{encodingImage ? (
|
||||||
|
|
@ -18,4 +19,4 @@ export const HeaderImage: React.FC<
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { useIntl } from "react-intl";
|
||||||
import { ModalComponent } from "./Modal";
|
import { ModalComponent } from "./Modal";
|
||||||
import { Icon } from "./Icon";
|
import { Icon } from "./Icon";
|
||||||
import { faFile, faLink } from "@fortawesome/free-solid-svg-icons";
|
import { faFile, faLink } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
interface IImageInput {
|
interface IImageInput {
|
||||||
isEditing: boolean;
|
isEditing: boolean;
|
||||||
|
|
@ -24,13 +25,9 @@ function acceptExtensions(acceptSVG: boolean = false) {
|
||||||
return `.jpg,.jpeg,.png,.webp,.gif${acceptSVG ? ",.svg" : ""}`;
|
return `.jpg,.jpeg,.png,.webp,.gif${acceptSVG ? ",.svg" : ""}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ImageInput: React.FC<IImageInput> = ({
|
export const ImageInput: React.FC<IImageInput> = PatchComponent(
|
||||||
isEditing,
|
"ImageInput",
|
||||||
text,
|
({ isEditing, text, onImageChange, onImageURL, acceptSVG = false }) => {
|
||||||
onImageChange,
|
|
||||||
onImageURL,
|
|
||||||
acceptSVG = false,
|
|
||||||
}) => {
|
|
||||||
const [isShowDialog, setIsShowDialog] = useState(false);
|
const [isShowDialog, setIsShowDialog] = useState(false);
|
||||||
const [url, setURL] = useState("");
|
const [url, setURL] = useState("");
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
@ -142,4 +139,5 @@ export const ImageInput: React.FC<IImageInput> = ({
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,9 @@ Returns `void`.
|
||||||
- `CompressedPerformerDetailsPanel`
|
- `CompressedPerformerDetailsPanel`
|
||||||
- `ConstantSetting`
|
- `ConstantSetting`
|
||||||
- `CountrySelect`
|
- `CountrySelect`
|
||||||
|
- `CustomFieldInput`
|
||||||
- `DateInput`
|
- `DateInput`
|
||||||
|
- `DetailImage`
|
||||||
- `ExternalLinkButtons`
|
- `ExternalLinkButtons`
|
||||||
- `ExternalLinksButton`
|
- `ExternalLinksButton`
|
||||||
- `FolderSelect`
|
- `FolderSelect`
|
||||||
|
|
@ -162,9 +164,12 @@ Returns `void`.
|
||||||
- `GalleryIDSelect`
|
- `GalleryIDSelect`
|
||||||
- `GallerySelect`
|
- `GallerySelect`
|
||||||
- `GallerySelect.sort`
|
- `GallerySelect.sort`
|
||||||
|
- `HeaderImage`
|
||||||
- `HoverPopover`
|
- `HoverPopover`
|
||||||
- `Icon`
|
- `Icon`
|
||||||
- `ImageDetailPanel`
|
- `ImageDetailPanel`
|
||||||
|
- `ImageInput`
|
||||||
|
- `LightboxLink`
|
||||||
- `LoadingIndicator`
|
- `LoadingIndicator`
|
||||||
- `ModalSetting`
|
- `ModalSetting`
|
||||||
- `GroupIDSelect`
|
- `GroupIDSelect`
|
||||||
|
|
@ -186,6 +191,7 @@ Returns `void`.
|
||||||
- `PerformerSelect.sort`
|
- `PerformerSelect.sort`
|
||||||
- `PerformerGalleriesPanel`
|
- `PerformerGalleriesPanel`
|
||||||
- `PerformerGroupsPanel`
|
- `PerformerGroupsPanel`
|
||||||
|
- `PerformerHeaderImage`
|
||||||
- `PerformerImagesPanel`
|
- `PerformerImagesPanel`
|
||||||
- `PerformerScenesPanel`
|
- `PerformerScenesPanel`
|
||||||
- `PluginRoutes`
|
- `PluginRoutes`
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ import { PropsWithChildren } from "react";
|
||||||
import { useLightbox } from "./hooks";
|
import { useLightbox } from "./hooks";
|
||||||
import { ILightboxImage } from "./types";
|
import { ILightboxImage } from "./types";
|
||||||
import { Button } from "react-bootstrap";
|
import { Button } from "react-bootstrap";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
export const LightboxLink: React.FC<
|
export const LightboxLink: React.FC<
|
||||||
PropsWithChildren<{ images?: ILightboxImage[] | undefined; index?: number }>
|
PropsWithChildren<{ images?: ILightboxImage[] | undefined; index?: number }>
|
||||||
> = ({ images, index, children }) => {
|
> = PatchComponent("LightboxLink", ({ images, index, children }) => {
|
||||||
const showLightbox = useLightbox();
|
const showLightbox = useLightbox();
|
||||||
|
|
||||||
if (!images || images.length === 0) {
|
if (!images || images.length === 0) {
|
||||||
|
|
@ -20,4 +21,4 @@ export const LightboxLink: React.FC<
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
|
||||||
6
ui/v2.5/src/pluginApi.d.ts
vendored
6
ui/v2.5/src/pluginApi.d.ts
vendored
|
|
@ -694,12 +694,18 @@ declare namespace PluginApi {
|
||||||
PerformerAppearsWithPanel: React.FC<any>;
|
PerformerAppearsWithPanel: React.FC<any>;
|
||||||
PerformerGalleriesPanel: React.FC<any>;
|
PerformerGalleriesPanel: React.FC<any>;
|
||||||
PerformerGroupsPanel: React.FC<any>;
|
PerformerGroupsPanel: React.FC<any>;
|
||||||
|
PerformerHeaderImage: React.FC<any>;
|
||||||
PerformerScenesPanel: React.FC<any>;
|
PerformerScenesPanel: React.FC<any>;
|
||||||
PerformerImagesPanel: React.FC<any>;
|
PerformerImagesPanel: React.FC<any>;
|
||||||
TabTitleCounter: React.FC<any>;
|
TabTitleCounter: React.FC<any>;
|
||||||
PerformerCard: React.FC<any>;
|
PerformerCard: React.FC<any>;
|
||||||
ExternalLinkButtons: React.FC<any>;
|
ExternalLinkButtons: React.FC<any>;
|
||||||
ExternalLinksButton: React.FC<any>;
|
ExternalLinksButton: React.FC<any>;
|
||||||
|
CustomFieldInput: React.FC<any>;
|
||||||
|
ImageInput: React.FC<any>;
|
||||||
|
DetailImage: React.FC<any>;
|
||||||
|
HeaderImage: React.FC<any>;
|
||||||
|
LightboxLink: React.FC<any>;
|
||||||
"PerformerCard.Popovers": React.FC<any>;
|
"PerformerCard.Popovers": React.FC<any>;
|
||||||
"PerformerCard.Details": React.FC<any>;
|
"PerformerCard.Details": React.FC<any>;
|
||||||
"PerformerCard.Overlays": React.FC<any>;
|
"PerformerCard.Overlays": React.FC<any>;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue