mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Plugin api improvements (#5703)
* Add ReactSelect to PluginApi.libraries * Make Performer tabs patchable * Make PerformerCard patchable * Use registration pattern for HoverPopover, TagLink and LoadingIndicator Initialising the components map to include these was causing an initialisation error. * Add showZero property to PopoverCountButton * Make TagCard patchable * Make ScenePage and ScenePlayer patchable * Pass properties to container components * Add example for scene tabs * Make FrontPage patchable * Add FrontPage example
This commit is contained in:
@@ -14,7 +14,11 @@ interface IPluginApi {
|
||||
Button: React.FC<any>;
|
||||
Nav: React.FC<any> & {
|
||||
Link: React.FC<any>;
|
||||
Item: React.FC<any>;
|
||||
};
|
||||
Tab: React.FC<any> & {
|
||||
Pane: React.FC<any>;
|
||||
}
|
||||
},
|
||||
FontAwesomeSolid: {
|
||||
faEthernet: any;
|
||||
@@ -45,7 +49,7 @@ interface IPluginApi {
|
||||
const React = PluginApi.React;
|
||||
const GQL = PluginApi.GQL;
|
||||
|
||||
const { Button } = PluginApi.libraries.Bootstrap;
|
||||
const { Button, Nav, Tab } = PluginApi.libraries.Bootstrap;
|
||||
const { faEthernet } = PluginApi.libraries.FontAwesomeSolid;
|
||||
const {
|
||||
Link,
|
||||
@@ -144,6 +148,10 @@ interface IPluginApi {
|
||||
return <><Overlays />{original({...props})}</>;
|
||||
});
|
||||
|
||||
PluginApi.patch.instead("FrontPage", function (props: any, _: any, original: (props: any) => any) {
|
||||
return <><p>Hello from Test React!</p>{original({...props})}</>;
|
||||
});
|
||||
|
||||
const TestPage: React.FC = () => {
|
||||
const componentsToLoad = [
|
||||
PluginApi.loadableComponents.SceneCard,
|
||||
@@ -237,5 +245,37 @@ interface IPluginApi {
|
||||
)
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
PluginApi.patch.before("ScenePage.Tabs", function (props: any) {
|
||||
return [
|
||||
{
|
||||
children: (
|
||||
<>
|
||||
{props.children}
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey="test-react-tab">
|
||||
Test React tab
|
||||
</Nav.Link>
|
||||
</Nav.Item>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
PluginApi.patch.before("ScenePage.TabContent", function (props: any) {
|
||||
return [
|
||||
{
|
||||
children: (
|
||||
<>
|
||||
{props.children}
|
||||
<Tab.Pane eventKey="test-react-tab">
|
||||
Test React tab content {props.scene.id}
|
||||
</Tab.Pane>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
});
|
||||
})();
|
||||
@@ -13,8 +13,9 @@ import {
|
||||
getFrontPageContent,
|
||||
} from "src/core/config";
|
||||
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
const FrontPage: React.FC = () => {
|
||||
const FrontPage: React.FC = PatchComponent("FrontPage", () => {
|
||||
const intl = useIntl();
|
||||
const Toast = useToast();
|
||||
|
||||
@@ -81,6 +82,6 @@ const FrontPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default FrontPage;
|
||||
|
||||
@@ -23,6 +23,7 @@ import { usePerformerUpdate } from "src/core/StashService";
|
||||
import { ILabeledId } from "src/models/list-filter/types";
|
||||
import ScreenUtils from "src/utils/screen";
|
||||
import { FavoriteIcon } from "../Shared/FavoriteIcon";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
export interface IPerformerCardExtraCriteria {
|
||||
scenes?: ModifierCriterion<CriterionValue>[];
|
||||
@@ -43,75 +44,9 @@ interface IPerformerCardProps {
|
||||
extraCriteria?: IPerformerCardExtraCriteria;
|
||||
}
|
||||
|
||||
export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
||||
performer,
|
||||
containerWidth,
|
||||
ageFromDate,
|
||||
selecting,
|
||||
selected,
|
||||
zoomIndex,
|
||||
onSelectedChanged,
|
||||
extraCriteria,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const age = TextUtils.age(
|
||||
performer.birthdate,
|
||||
ageFromDate ?? performer.death_date
|
||||
);
|
||||
const ageL10nId = ageFromDate
|
||||
? "media_info.performer_card.age_context"
|
||||
: "media_info.performer_card.age";
|
||||
const ageL10String = intl.formatMessage({
|
||||
id: "years_old",
|
||||
defaultMessage: "years old",
|
||||
});
|
||||
const ageString = intl.formatMessage(
|
||||
{ id: ageL10nId },
|
||||
{ age, years_old: ageL10String }
|
||||
);
|
||||
|
||||
const [updatePerformer] = usePerformerUpdate();
|
||||
const [cardWidth, setCardWidth] = useState<number>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerWidth || zoomIndex === undefined || ScreenUtils.isMobile())
|
||||
return;
|
||||
|
||||
let zoomValue = zoomIndex;
|
||||
let preferredCardWidth: number;
|
||||
switch (zoomValue) {
|
||||
case 0:
|
||||
preferredCardWidth = 240;
|
||||
break;
|
||||
case 1:
|
||||
preferredCardWidth = 300;
|
||||
break;
|
||||
case 2:
|
||||
preferredCardWidth = 375;
|
||||
break;
|
||||
case 3:
|
||||
preferredCardWidth = 470;
|
||||
}
|
||||
let fittedCardWidth = calculateCardWidth(
|
||||
containerWidth,
|
||||
preferredCardWidth!
|
||||
);
|
||||
setCardWidth(fittedCardWidth);
|
||||
}, [containerWidth, zoomIndex]);
|
||||
|
||||
function onToggleFavorite(v: boolean) {
|
||||
if (performer.id) {
|
||||
updatePerformer({
|
||||
variables: {
|
||||
input: {
|
||||
id: performer.id,
|
||||
favorite: v,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const PerformerCardPopovers: React.FC<IPerformerCardProps> = PatchComponent(
|
||||
"PerformerCard.Popovers",
|
||||
({ performer, extraCriteria }) => {
|
||||
function maybeRenderScenesPopoverButton() {
|
||||
if (!performer.scene_count) return;
|
||||
|
||||
@@ -212,7 +147,6 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
function maybeRenderPopoverButtonGroup() {
|
||||
if (
|
||||
performer.scene_count ||
|
||||
performer.image_count ||
|
||||
@@ -235,6 +169,27 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
const PerformerCardOverlays: React.FC<IPerformerCardProps> = PatchComponent(
|
||||
"PerformerCard.Overlays",
|
||||
({ performer }) => {
|
||||
const [updatePerformer] = usePerformerUpdate();
|
||||
|
||||
function onToggleFavorite(v: boolean) {
|
||||
if (performer.id) {
|
||||
updatePerformer({
|
||||
variables: {
|
||||
input: {
|
||||
id: performer.id,
|
||||
favorite: v,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function maybeRenderRatingBanner() {
|
||||
@@ -262,34 +217,6 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<GridCard
|
||||
className={`performer-card zoom-${zoomIndex}`}
|
||||
url={`/performers/${performer.id}`}
|
||||
width={cardWidth}
|
||||
pretitleIcon={
|
||||
<GenderIcon className="gender-icon" gender={performer.gender} />
|
||||
}
|
||||
title={
|
||||
<div>
|
||||
<span className="performer-name">{performer.name}</span>
|
||||
{performer.disambiguation && (
|
||||
<span className="performer-disambiguation">
|
||||
{` (${performer.disambiguation})`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
image={
|
||||
<>
|
||||
<img
|
||||
loading="lazy"
|
||||
className="performer-card-image"
|
||||
alt={performer.name ?? ""}
|
||||
src={performer.image_path ?? ""}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
overlays={
|
||||
<>
|
||||
<FavoriteIcon
|
||||
favorite={performer.favorite}
|
||||
@@ -300,8 +227,31 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
||||
{maybeRenderRatingBanner()}
|
||||
{maybeRenderFlag()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
details={
|
||||
);
|
||||
|
||||
const PerformerCardDetails: React.FC<IPerformerCardProps> = PatchComponent(
|
||||
"PerformerCard.Details",
|
||||
({ performer, ageFromDate }) => {
|
||||
const intl = useIntl();
|
||||
const age = TextUtils.age(
|
||||
performer.birthdate,
|
||||
ageFromDate ?? performer.death_date
|
||||
);
|
||||
const ageL10nId = ageFromDate
|
||||
? "media_info.performer_card.age_context"
|
||||
: "media_info.performer_card.age";
|
||||
const ageL10String = intl.formatMessage({
|
||||
id: "years_old",
|
||||
defaultMessage: "years old",
|
||||
});
|
||||
const ageString = intl.formatMessage(
|
||||
{ id: ageL10nId },
|
||||
{ age, years_old: ageL10String }
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{age !== 0 ? (
|
||||
<div className="performer-card__age">{ageString}</div>
|
||||
@@ -309,11 +259,99 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
||||
""
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
popovers={maybeRenderPopoverButtonGroup()}
|
||||
);
|
||||
|
||||
const PerformerCardImage: React.FC<IPerformerCardProps> = PatchComponent(
|
||||
"PerformerCard.Image",
|
||||
({ performer }) => {
|
||||
return (
|
||||
<>
|
||||
<img
|
||||
loading="lazy"
|
||||
className="performer-card-image"
|
||||
alt={performer.name ?? ""}
|
||||
src={performer.image_path ?? ""}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const PerformerCardTitle: React.FC<IPerformerCardProps> = PatchComponent(
|
||||
"PerformerCard.Title",
|
||||
({ performer }) => {
|
||||
return (
|
||||
<div>
|
||||
<span className="performer-name">{performer.name}</span>
|
||||
{performer.disambiguation && (
|
||||
<span className="performer-disambiguation">
|
||||
{` (${performer.disambiguation})`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const PerformerCard: React.FC<IPerformerCardProps> = PatchComponent(
|
||||
"PerformerCard",
|
||||
(props) => {
|
||||
const {
|
||||
performer,
|
||||
containerWidth,
|
||||
selecting,
|
||||
selected,
|
||||
onSelectedChanged,
|
||||
zoomIndex,
|
||||
} = props;
|
||||
|
||||
const [cardWidth, setCardWidth] = useState<number>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerWidth || zoomIndex === undefined || ScreenUtils.isMobile())
|
||||
return;
|
||||
|
||||
let zoomValue = zoomIndex;
|
||||
let preferredCardWidth: number;
|
||||
switch (zoomValue) {
|
||||
case 0:
|
||||
preferredCardWidth = 240;
|
||||
break;
|
||||
case 1:
|
||||
preferredCardWidth = 300;
|
||||
break;
|
||||
case 2:
|
||||
preferredCardWidth = 375;
|
||||
break;
|
||||
case 3:
|
||||
preferredCardWidth = 470;
|
||||
}
|
||||
let fittedCardWidth = calculateCardWidth(
|
||||
containerWidth,
|
||||
preferredCardWidth!
|
||||
);
|
||||
setCardWidth(fittedCardWidth);
|
||||
}, [containerWidth, zoomIndex]);
|
||||
|
||||
return (
|
||||
<GridCard
|
||||
className={`performer-card zoom-${zoomIndex}`}
|
||||
url={`/performers/${performer.id}`}
|
||||
width={cardWidth}
|
||||
pretitleIcon={
|
||||
<GenderIcon className="gender-icon" gender={performer.gender} />
|
||||
}
|
||||
title={<PerformerCardTitle {...props} />}
|
||||
image={<PerformerCardImage {...props} />}
|
||||
overlays={<PerformerCardOverlays {...props} />}
|
||||
details={<PerformerCardDetails {...props} />}
|
||||
popovers={<PerformerCardPopovers {...props} />}
|
||||
selected={selected}
|
||||
selecting={selecting}
|
||||
onSelectedChanged={onSelectedChanged}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -45,6 +45,7 @@ import { FavoriteIcon } from "src/components/Shared/FavoriteIcon";
|
||||
import { AliasList } from "src/components/Shared/DetailsPage/AliasList";
|
||||
import { HeaderImage } from "src/components/Shared/DetailsPage/HeaderImage";
|
||||
import { LightboxLink } from "src/hooks/Lightbox/LightboxLink";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
interface IProps {
|
||||
performer: GQL.PerformerDataFragment;
|
||||
@@ -200,7 +201,9 @@ const PerformerTabs: React.FC<{
|
||||
);
|
||||
};
|
||||
|
||||
const PerformerPage: React.FC<IProps> = ({ performer, tabKey }) => {
|
||||
const PerformerPage: React.FC<IProps> = PatchComponent(
|
||||
"PerformerPage",
|
||||
({ performer, tabKey }) => {
|
||||
const Toast = useToast();
|
||||
const history = useHistory();
|
||||
const intl = useIntl();
|
||||
@@ -283,7 +286,9 @@ const PerformerPage: React.FC<IProps> = ({ performer, tabKey }) => {
|
||||
Toast.success(
|
||||
intl.formatMessage(
|
||||
{ id: "toast.updated_entity" },
|
||||
{ entity: intl.formatMessage({ id: "performer" }).toLocaleLowerCase() }
|
||||
{
|
||||
entity: intl.formatMessage({ id: "performer" }).toLocaleLowerCase(),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -465,7 +470,8 @@ const PerformerPage: React.FC<IProps> = ({ performer, tabKey }) => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const PerformerLoader: React.FC<RouteComponentProps<IPerformerParams>> = ({
|
||||
location,
|
||||
|
||||
@@ -3,16 +3,15 @@ import * as GQL from "src/core/generated-graphql";
|
||||
import { GalleryList } from "src/components/Galleries/GalleryList";
|
||||
import { usePerformerFilterHook } from "src/core/performers";
|
||||
import { View } from "src/components/List/views";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
interface IPerformerDetailsProps {
|
||||
active: boolean;
|
||||
performer: GQL.PerformerDataFragment;
|
||||
}
|
||||
|
||||
export const PerformerGalleriesPanel: React.FC<IPerformerDetailsProps> = ({
|
||||
active,
|
||||
performer,
|
||||
}) => {
|
||||
export const PerformerGalleriesPanel: React.FC<IPerformerDetailsProps> =
|
||||
PatchComponent("PerformerGalleriesPanel", ({ active, performer }) => {
|
||||
const filterHook = usePerformerFilterHook(performer);
|
||||
return (
|
||||
<GalleryList
|
||||
@@ -21,4 +20,4 @@ export const PerformerGalleriesPanel: React.FC<IPerformerDetailsProps> = ({
|
||||
view={View.PerformerGalleries}
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -3,16 +3,15 @@ import * as GQL from "src/core/generated-graphql";
|
||||
import { GroupList } from "src/components/Groups/GroupList";
|
||||
import { usePerformerFilterHook } from "src/core/performers";
|
||||
import { View } from "src/components/List/views";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
interface IPerformerDetailsProps {
|
||||
active: boolean;
|
||||
performer: GQL.PerformerDataFragment;
|
||||
}
|
||||
|
||||
export const PerformerGroupsPanel: React.FC<IPerformerDetailsProps> = ({
|
||||
active,
|
||||
performer,
|
||||
}) => {
|
||||
export const PerformerGroupsPanel: React.FC<IPerformerDetailsProps> =
|
||||
PatchComponent("PerformerGroupsPanel", ({ active, performer }) => {
|
||||
const filterHook = usePerformerFilterHook(performer);
|
||||
return (
|
||||
<GroupList
|
||||
@@ -21,4 +20,4 @@ export const PerformerGroupsPanel: React.FC<IPerformerDetailsProps> = ({
|
||||
view={View.PerformerGroups}
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -3,16 +3,15 @@ import * as GQL from "src/core/generated-graphql";
|
||||
import { ImageList } from "src/components/Images/ImageList";
|
||||
import { usePerformerFilterHook } from "src/core/performers";
|
||||
import { View } from "src/components/List/views";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
interface IPerformerImagesPanel {
|
||||
active: boolean;
|
||||
performer: GQL.PerformerDataFragment;
|
||||
}
|
||||
|
||||
export const PerformerImagesPanel: React.FC<IPerformerImagesPanel> = ({
|
||||
active,
|
||||
performer,
|
||||
}) => {
|
||||
export const PerformerImagesPanel: React.FC<IPerformerImagesPanel> =
|
||||
PatchComponent("PerformerImagesPanel", ({ active, performer }) => {
|
||||
const filterHook = usePerformerFilterHook(performer);
|
||||
return (
|
||||
<ImageList
|
||||
@@ -21,4 +20,4 @@ export const PerformerImagesPanel: React.FC<IPerformerImagesPanel> = ({
|
||||
view={View.PerformerImages}
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -3,16 +3,15 @@ import * as GQL from "src/core/generated-graphql";
|
||||
import { SceneList } from "src/components/Scenes/SceneList";
|
||||
import { usePerformerFilterHook } from "src/core/performers";
|
||||
import { View } from "src/components/List/views";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
interface IPerformerDetailsProps {
|
||||
active: boolean;
|
||||
performer: GQL.PerformerDataFragment;
|
||||
}
|
||||
|
||||
export const PerformerScenesPanel: React.FC<IPerformerDetailsProps> = ({
|
||||
active,
|
||||
performer,
|
||||
}) => {
|
||||
export const PerformerScenesPanel: React.FC<IPerformerDetailsProps> =
|
||||
PatchComponent("PerformerScenesPanel", ({ active, performer }) => {
|
||||
const filterHook = usePerformerFilterHook(performer);
|
||||
return (
|
||||
<SceneList
|
||||
@@ -21,4 +20,4 @@ export const PerformerScenesPanel: React.FC<IPerformerDetailsProps> = ({
|
||||
view={View.PerformerScenes}
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -3,16 +3,15 @@ import * as GQL from "src/core/generated-graphql";
|
||||
import { PerformerList } from "src/components/Performers/PerformerList";
|
||||
import { usePerformerFilterHook } from "src/core/performers";
|
||||
import { View } from "src/components/List/views";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
interface IPerformerDetailsProps {
|
||||
active: boolean;
|
||||
performer: GQL.PerformerDataFragment;
|
||||
}
|
||||
|
||||
export const PerformerAppearsWithPanel: React.FC<IPerformerDetailsProps> = ({
|
||||
active,
|
||||
performer,
|
||||
}) => {
|
||||
export const PerformerAppearsWithPanel: React.FC<IPerformerDetailsProps> =
|
||||
PatchComponent("PerformerAppearsWithPanel", ({ active, performer }) => {
|
||||
const performerValue = {
|
||||
id: performer.id,
|
||||
label: performer.name ?? `Performer ${performer.id}`,
|
||||
@@ -32,4 +31,4 @@ export const PerformerAppearsWithPanel: React.FC<IPerformerDetailsProps> = ({
|
||||
view={View.PerformerAppearsWith}
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -46,6 +46,7 @@ import airplay from "@silvermine/videojs-airplay";
|
||||
import chromecast from "@silvermine/videojs-chromecast";
|
||||
import abLoopPlugin from "videojs-abloop";
|
||||
import ScreenUtils from "src/utils/screen";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
// register videojs plugins
|
||||
airplay(videojs);
|
||||
@@ -210,7 +211,9 @@ interface IScenePlayerProps {
|
||||
onPrevious: () => void;
|
||||
}
|
||||
|
||||
export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
||||
export const ScenePlayer: React.FC<IScenePlayerProps> = PatchComponent(
|
||||
"ScenePlayer",
|
||||
({
|
||||
scene,
|
||||
hideScrubberOverride,
|
||||
autoplay,
|
||||
@@ -892,7 +895,10 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx("VideoPlayer", { portrait: isPortrait, "no-file": !file })}
|
||||
className={cx("VideoPlayer", {
|
||||
portrait: isPortrait,
|
||||
"no-file": !file,
|
||||
})}
|
||||
onKeyDownCapture={onKeyDown}
|
||||
>
|
||||
<div className="video-wrapper" ref={videoRef} />
|
||||
@@ -910,6 +916,7 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export default ScenePlayer;
|
||||
|
||||
@@ -50,6 +50,7 @@ import { useRatingKeybinds } from "src/hooks/keybinds";
|
||||
import { lazyComponent } from "src/utils/lazyComponent";
|
||||
import cx from "classnames";
|
||||
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
||||
import { PatchComponent, PatchContainerComponent } from "src/patch";
|
||||
|
||||
const SubmitStashBoxDraft = lazyComponent(
|
||||
() => import("src/components/Dialogs/SubmitDraft")
|
||||
@@ -153,7 +154,13 @@ interface ISceneParams {
|
||||
id: string;
|
||||
}
|
||||
|
||||
const ScenePage: React.FC<IProps> = ({
|
||||
const ScenePageTabs = PatchContainerComponent<IProps>("ScenePage.Tabs");
|
||||
const ScenePageTabContent = PatchContainerComponent<IProps>(
|
||||
"ScenePage.TabContent"
|
||||
);
|
||||
|
||||
const ScenePage: React.FC<IProps> = PatchComponent("ScenePage", (props) => {
|
||||
const {
|
||||
scene,
|
||||
setTimestamp,
|
||||
queueScenes,
|
||||
@@ -170,7 +177,8 @@ const ScenePage: React.FC<IProps> = ({
|
||||
collapsed,
|
||||
setCollapsed,
|
||||
setContinuePlaylist,
|
||||
}) => {
|
||||
} = props;
|
||||
|
||||
const Toast = useToast();
|
||||
const intl = useIntl();
|
||||
const [updateScene] = useSceneUpdate();
|
||||
@@ -423,6 +431,7 @@ const ScenePage: React.FC<IProps> = ({
|
||||
>
|
||||
<div>
|
||||
<Nav variant="tabs" className="mr-auto">
|
||||
<ScenePageTabs {...props}>
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey="scene-details-panel">
|
||||
<FormattedMessage id="details" />
|
||||
@@ -485,10 +494,12 @@ const ScenePage: React.FC<IProps> = ({
|
||||
<FormattedMessage id="actions.edit" />
|
||||
</Nav.Link>
|
||||
</Nav.Item>
|
||||
</ScenePageTabs>
|
||||
</Nav>
|
||||
</div>
|
||||
|
||||
<Tab.Content>
|
||||
<ScenePageTabContent {...props}>
|
||||
<Tab.Pane eventKey="scene-details-panel">
|
||||
<SceneDetailPanel scene={scene} />
|
||||
</Tab.Pane>
|
||||
@@ -529,7 +540,10 @@ const ScenePage: React.FC<IProps> = ({
|
||||
<Tab.Pane eventKey="scene-video-filter-panel">
|
||||
<SceneVideoFilterPanel scene={scene} />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane className="file-info-panel" eventKey="scene-file-info-panel">
|
||||
<Tab.Pane
|
||||
className="file-info-panel"
|
||||
eventKey="scene-file-info-panel"
|
||||
>
|
||||
<SceneFileInfoPanel scene={scene} />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="scene-edit-panel" mountOnEnter>
|
||||
@@ -543,6 +557,7 @@ const ScenePage: React.FC<IProps> = ({
|
||||
<Tab.Pane eventKey="scene-history-panel">
|
||||
<SceneHistoryPanel scene={scene} />
|
||||
</Tab.Pane>
|
||||
</ScenePageTabContent>
|
||||
</Tab.Content>
|
||||
</Tab.Container>
|
||||
);
|
||||
@@ -657,7 +672,7 @@ const ScenePage: React.FC<IProps> = ({
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
const SceneLoader: React.FC<RouteComponentProps<ISceneParams>> = ({
|
||||
location,
|
||||
|
||||
@@ -2,19 +2,23 @@ import { FormattedMessage } from "react-intl";
|
||||
import { Counter } from "../Counter";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
export const TabTitleCounter: React.FC<{
|
||||
messageID: string;
|
||||
count: number;
|
||||
abbreviateCounter: boolean;
|
||||
}> = ({ messageID, count, abbreviateCounter }) => {
|
||||
}> = PatchComponent(
|
||||
"TabTitleCounter",
|
||||
({ messageID, count, abbreviateCounter }) => {
|
||||
return (
|
||||
<>
|
||||
<FormattedMessage id={messageID} />
|
||||
<Counter count={count} abbreviateCounter={abbreviateCounter} hideZero />
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export function useTabKey(props: {
|
||||
tabKey: string | undefined;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useCallback, useEffect, useRef } from "react";
|
||||
import { Overlay, Popover, OverlayProps } from "react-bootstrap";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
interface IHoverPopover {
|
||||
enterDelay?: number;
|
||||
@@ -12,7 +13,9 @@ interface IHoverPopover {
|
||||
target?: React.RefObject<HTMLElement>;
|
||||
}
|
||||
|
||||
export const HoverPopover: React.FC<IHoverPopover> = ({
|
||||
export const HoverPopover: React.FC<IHoverPopover> = PatchComponent(
|
||||
"HoverPopover",
|
||||
({
|
||||
enterDelay = 200,
|
||||
leaveDelay = 200,
|
||||
content,
|
||||
@@ -80,4 +83,5 @@ export const HoverPopover: React.FC<IHoverPopover> = ({
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import { Spinner } from "react-bootstrap";
|
||||
import cx from "classnames";
|
||||
import { useIntl } from "react-intl";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
interface ILoadingProps {
|
||||
message?: JSX.Element | string;
|
||||
@@ -13,19 +14,20 @@ interface ILoadingProps {
|
||||
const CLASSNAME = "LoadingIndicator";
|
||||
const CLASSNAME_MESSAGE = `${CLASSNAME}-message`;
|
||||
|
||||
export const LoadingIndicator: React.FC<ILoadingProps> = ({
|
||||
message,
|
||||
inline = false,
|
||||
small = false,
|
||||
card = false,
|
||||
}) => {
|
||||
export const LoadingIndicator: React.FC<ILoadingProps> = PatchComponent(
|
||||
"LoadingIndicator",
|
||||
({ message, inline = false, small = false, card = false }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const text = intl.formatMessage({ id: "loading.generic" });
|
||||
|
||||
return (
|
||||
<div className={cx(CLASSNAME, { inline, small, "card-based": card })}>
|
||||
<Spinner animation="border" role="status" size={small ? "sm" : undefined}>
|
||||
<Spinner
|
||||
animation="border"
|
||||
role="status"
|
||||
size={small ? "sm" : undefined}
|
||||
>
|
||||
<span className="sr-only">{text}</span>
|
||||
</Spinner>
|
||||
{message !== "" && (
|
||||
@@ -33,4 +35,5 @@ export const LoadingIndicator: React.FC<ILoadingProps> = ({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -53,6 +53,7 @@ interface IProps {
|
||||
url: string;
|
||||
type: PopoverLinkType;
|
||||
count: number;
|
||||
showZero?: boolean;
|
||||
}
|
||||
|
||||
export const PopoverCountButton: React.FC<IProps> = ({
|
||||
@@ -60,9 +61,14 @@ export const PopoverCountButton: React.FC<IProps> = ({
|
||||
url,
|
||||
type,
|
||||
count,
|
||||
showZero = true,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
if (!showZero && count === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO - refactor - create SceneIcon, ImageIcon etc components
|
||||
function getIcon() {
|
||||
switch (type) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Placement } from "react-bootstrap/esm/Overlay";
|
||||
import { faFolderTree } from "@fortawesome/free-solid-svg-icons";
|
||||
import { Icon } from "../Shared/Icon";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
type SceneMarkerFragment = Pick<GQL.SceneMarker, "id" | "title" | "seconds"> & {
|
||||
scene: Pick<GQL.Scene, "id">;
|
||||
@@ -243,7 +244,9 @@ interface ITagLinkProps {
|
||||
hierarchyTooltipID?: string;
|
||||
}
|
||||
|
||||
export const TagLink: React.FC<ITagLinkProps> = ({
|
||||
export const TagLink: React.FC<ITagLinkProps> = PatchComponent(
|
||||
"TagLink",
|
||||
({
|
||||
tag,
|
||||
linkType = "scene",
|
||||
className,
|
||||
@@ -305,4 +308,5 @@ export const TagLink: React.FC<ITagLinkProps> = ({
|
||||
</TagPopover>
|
||||
</SortNameLinkComponent>
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PatchComponent } from "src/patch";
|
||||
import { Button, ButtonGroup } from "react-bootstrap";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -12,6 +13,7 @@ import { Icon } from "../Shared/Icon";
|
||||
import { faHeart } from "@fortawesome/free-solid-svg-icons";
|
||||
import cx from "classnames";
|
||||
import { useTagUpdate } from "src/core/StashService";
|
||||
|
||||
interface IProps {
|
||||
tag: GQL.TagDataFragment;
|
||||
containerWidth?: number;
|
||||
@@ -21,53 +23,73 @@ interface IProps {
|
||||
onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void;
|
||||
}
|
||||
|
||||
export const TagCard: React.FC<IProps> = ({
|
||||
tag,
|
||||
containerWidth,
|
||||
zoomIndex,
|
||||
selecting,
|
||||
selected,
|
||||
onSelectedChanged,
|
||||
}) => {
|
||||
const [cardWidth, setCardWidth] = useState<number>();
|
||||
const [updateTag] = useTagUpdate();
|
||||
useEffect(() => {
|
||||
if (!containerWidth || zoomIndex === undefined || ScreenUtils.isMobile())
|
||||
return;
|
||||
|
||||
let zoomValue = zoomIndex;
|
||||
let preferredCardWidth: number;
|
||||
switch (zoomValue) {
|
||||
case 0:
|
||||
preferredCardWidth = 280;
|
||||
break;
|
||||
case 1:
|
||||
preferredCardWidth = 340;
|
||||
break;
|
||||
case 2:
|
||||
preferredCardWidth = 480;
|
||||
break;
|
||||
case 3:
|
||||
preferredCardWidth = 640;
|
||||
}
|
||||
let fittedCardWidth = calculateCardWidth(
|
||||
containerWidth,
|
||||
preferredCardWidth!
|
||||
);
|
||||
setCardWidth(fittedCardWidth);
|
||||
}, [containerWidth, zoomIndex]);
|
||||
|
||||
function maybeRenderDescription() {
|
||||
if (tag.description) {
|
||||
const TagCardPopovers: React.FC<IProps> = PatchComponent(
|
||||
"TagCard.Popovers",
|
||||
({ tag }) => {
|
||||
return (
|
||||
<TruncatedText
|
||||
className="tag-description"
|
||||
text={tag.description}
|
||||
lineCount={3}
|
||||
<>
|
||||
<hr />
|
||||
<ButtonGroup className="card-popovers">
|
||||
<PopoverCountButton
|
||||
className="scene-count"
|
||||
type="scene"
|
||||
count={tag.scene_count}
|
||||
url={NavUtils.makeTagScenesUrl(tag)}
|
||||
showZero={false}
|
||||
/>
|
||||
<PopoverCountButton
|
||||
className="image-count"
|
||||
type="image"
|
||||
count={tag.image_count}
|
||||
url={NavUtils.makeTagImagesUrl(tag)}
|
||||
showZero={false}
|
||||
/>
|
||||
<PopoverCountButton
|
||||
className="gallery-count"
|
||||
type="gallery"
|
||||
count={tag.gallery_count}
|
||||
url={NavUtils.makeTagGalleriesUrl(tag)}
|
||||
showZero={false}
|
||||
/>
|
||||
<PopoverCountButton
|
||||
className="group-count"
|
||||
type="group"
|
||||
count={tag.group_count}
|
||||
url={NavUtils.makeTagGroupsUrl(tag)}
|
||||
showZero={false}
|
||||
/>
|
||||
<PopoverCountButton
|
||||
className="marker-count"
|
||||
type="marker"
|
||||
count={tag.scene_marker_count}
|
||||
url={NavUtils.makeTagSceneMarkersUrl(tag)}
|
||||
showZero={false}
|
||||
/>
|
||||
<PopoverCountButton
|
||||
className="performer-count"
|
||||
type="performer"
|
||||
count={tag.performer_count}
|
||||
url={NavUtils.makeTagPerformersUrl(tag)}
|
||||
showZero={false}
|
||||
/>
|
||||
<PopoverCountButton
|
||||
className="studio-count"
|
||||
type="studio"
|
||||
count={tag.studio_count}
|
||||
url={NavUtils.makeTagStudiosUrl(tag)}
|
||||
showZero={false}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const TagCardOverlays: React.FC<IProps> = PatchComponent(
|
||||
"TagCard.Overlays",
|
||||
({ tag }) => {
|
||||
const [updateTag] = useTagUpdate();
|
||||
|
||||
function renderFavoriteIcon() {
|
||||
return (
|
||||
<Link to="" onClick={(e) => e.preventDefault()}>
|
||||
@@ -98,6 +120,26 @@ export const TagCard: React.FC<IProps> = ({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return <>{renderFavoriteIcon()}</>;
|
||||
}
|
||||
);
|
||||
|
||||
const TagCardDetails: React.FC<IProps> = PatchComponent(
|
||||
"TagCard.Details",
|
||||
({ tag }) => {
|
||||
function maybeRenderDescription() {
|
||||
if (tag.description) {
|
||||
return (
|
||||
<TruncatedText
|
||||
className="tag-description"
|
||||
text={tag.description}
|
||||
lineCount={3}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function maybeRenderParents() {
|
||||
if (tag.parents.length === 1) {
|
||||
const parent = tag.parents[0];
|
||||
@@ -158,143 +200,89 @@ export const TagCard: React.FC<IProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
function maybeRenderScenesPopoverButton() {
|
||||
if (!tag.scene_count) return;
|
||||
|
||||
return (
|
||||
<PopoverCountButton
|
||||
className="scene-count"
|
||||
type="scene"
|
||||
count={tag.scene_count}
|
||||
url={NavUtils.makeTagScenesUrl(tag)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function maybeRenderSceneMarkersPopoverButton() {
|
||||
if (!tag.scene_marker_count) return;
|
||||
|
||||
return (
|
||||
<PopoverCountButton
|
||||
className="marker-count"
|
||||
type="marker"
|
||||
count={tag.scene_marker_count}
|
||||
url={NavUtils.makeTagSceneMarkersUrl(tag)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function maybeRenderImagesPopoverButton() {
|
||||
if (!tag.image_count) return;
|
||||
|
||||
return (
|
||||
<PopoverCountButton
|
||||
className="image-count"
|
||||
type="image"
|
||||
count={tag.image_count}
|
||||
url={NavUtils.makeTagImagesUrl(tag)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function maybeRenderGalleriesPopoverButton() {
|
||||
if (!tag.gallery_count) return;
|
||||
|
||||
return (
|
||||
<PopoverCountButton
|
||||
className="gallery-count"
|
||||
type="gallery"
|
||||
count={tag.gallery_count}
|
||||
url={NavUtils.makeTagGalleriesUrl(tag)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function maybeRenderPerformersPopoverButton() {
|
||||
if (!tag.performer_count) return;
|
||||
|
||||
return (
|
||||
<PopoverCountButton
|
||||
className="performer-count"
|
||||
type="performer"
|
||||
count={tag.performer_count}
|
||||
url={NavUtils.makeTagPerformersUrl(tag)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function maybeRenderStudiosPopoverButton() {
|
||||
if (!tag.studio_count) return;
|
||||
|
||||
return (
|
||||
<PopoverCountButton
|
||||
className="studio-count"
|
||||
type="studio"
|
||||
count={tag.studio_count}
|
||||
url={NavUtils.makeTagStudiosUrl(tag)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function maybeRenderGroupsPopoverButton() {
|
||||
if (!tag.group_count) return;
|
||||
|
||||
return (
|
||||
<PopoverCountButton
|
||||
className="group-count"
|
||||
type="group"
|
||||
count={tag.group_count}
|
||||
url={NavUtils.makeTagGroupsUrl(tag)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function maybeRenderPopoverButtonGroup() {
|
||||
if (tag) {
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<ButtonGroup className="card-popovers">
|
||||
{maybeRenderScenesPopoverButton()}
|
||||
{maybeRenderImagesPopoverButton()}
|
||||
{maybeRenderGalleriesPopoverButton()}
|
||||
{maybeRenderGroupsPopoverButton()}
|
||||
{maybeRenderSceneMarkersPopoverButton()}
|
||||
{maybeRenderPerformersPopoverButton()}
|
||||
{maybeRenderStudiosPopoverButton()}
|
||||
</ButtonGroup>
|
||||
{maybeRenderDescription()}
|
||||
{maybeRenderParents()}
|
||||
{maybeRenderChildren()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const TagCardImage: React.FC<IProps> = PatchComponent(
|
||||
"TagCard.Image",
|
||||
({ tag }) => {
|
||||
return (
|
||||
<GridCard
|
||||
className={`tag-card zoom-${zoomIndex}`}
|
||||
url={`/tags/${tag.id}`}
|
||||
width={cardWidth}
|
||||
title={tag.name ?? ""}
|
||||
linkClassName="tag-card-header"
|
||||
image={
|
||||
<>
|
||||
<img
|
||||
loading="lazy"
|
||||
className="tag-card-image"
|
||||
alt={tag.name}
|
||||
src={tag.image_path ?? ""}
|
||||
/>
|
||||
}
|
||||
details={
|
||||
<>
|
||||
{maybeRenderDescription()}
|
||||
{maybeRenderParents()}
|
||||
{maybeRenderChildren()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
overlays={<>{renderFavoriteIcon()}</>}
|
||||
popovers={maybeRenderPopoverButtonGroup()}
|
||||
);
|
||||
|
||||
const TagCardTitle: React.FC<IProps> = PatchComponent(
|
||||
"TagCard.Title",
|
||||
({ tag }) => {
|
||||
return <>{tag.name ?? ""}</>;
|
||||
}
|
||||
);
|
||||
|
||||
export const TagCard: React.FC<IProps> = PatchComponent("TagCard", (props) => {
|
||||
const {
|
||||
tag,
|
||||
containerWidth,
|
||||
zoomIndex,
|
||||
selecting,
|
||||
selected,
|
||||
onSelectedChanged,
|
||||
} = props;
|
||||
const [cardWidth, setCardWidth] = useState<number>();
|
||||
useEffect(() => {
|
||||
if (!containerWidth || zoomIndex === undefined || ScreenUtils.isMobile())
|
||||
return;
|
||||
|
||||
let zoomValue = zoomIndex;
|
||||
let preferredCardWidth: number;
|
||||
switch (zoomValue) {
|
||||
case 0:
|
||||
preferredCardWidth = 280;
|
||||
break;
|
||||
case 1:
|
||||
preferredCardWidth = 340;
|
||||
break;
|
||||
case 2:
|
||||
preferredCardWidth = 480;
|
||||
break;
|
||||
case 3:
|
||||
preferredCardWidth = 640;
|
||||
}
|
||||
let fittedCardWidth = calculateCardWidth(
|
||||
containerWidth,
|
||||
preferredCardWidth!
|
||||
);
|
||||
setCardWidth(fittedCardWidth);
|
||||
}, [containerWidth, zoomIndex]);
|
||||
|
||||
return (
|
||||
<GridCard
|
||||
className={`tag-card zoom-${zoomIndex}`}
|
||||
url={`/tags/${tag.id}`}
|
||||
width={cardWidth}
|
||||
title={<TagCardTitle {...props} />}
|
||||
linkClassName="tag-card-header"
|
||||
image={<TagCardImage {...props} />}
|
||||
details={<TagCardDetails {...props} />}
|
||||
overlays={<TagCardOverlays {...props} />}
|
||||
popovers={<TagCardPopovers {...props} />}
|
||||
selected={selected}
|
||||
selecting={selecting}
|
||||
onSelectedChanged={onSelectedChanged}
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -31,6 +31,7 @@ This namespace contains the generated graphql client interface. This is a low-le
|
||||
- `FontAwesomeSolid`
|
||||
- `Mousetrap`
|
||||
- `MousetrapPause`
|
||||
- `ReactSelect`
|
||||
|
||||
### `register`
|
||||
|
||||
@@ -147,21 +148,36 @@ Returns `void`.
|
||||
- `CountrySelect`
|
||||
- `DateInput`
|
||||
- `FolderSelect`
|
||||
- `FrontPage`
|
||||
- `GalleryIDSelect`
|
||||
- `GallerySelect`
|
||||
- `GallerySelect.sort`
|
||||
- `HoverPopover`
|
||||
- `Icon`
|
||||
- `ImageDetailPanel`
|
||||
- `LoadingIndicator`
|
||||
- `ModalSetting`
|
||||
- `GroupIDSelect`
|
||||
- `GroupSelect`
|
||||
- `GroupSelect.sort`
|
||||
- `NumberSetting`
|
||||
- `PerformerAppearsWithPanel`
|
||||
- `PerformerCard`
|
||||
- `PerformerCard.Details`
|
||||
- `PerformerCard.Image`
|
||||
- `PerformerCard.Overlays`
|
||||
- `PerformerCard.Popovers`
|
||||
- `PerformerCard.Title`
|
||||
- `PerformerDetailsPanel`
|
||||
- `PerformerDetailsPanel.DetailGroup`
|
||||
- `PerformerIDSelect`
|
||||
- `PerformerPage`
|
||||
- `PerformerSelect`
|
||||
- `PerformerSelect.sort`
|
||||
- `PerformerGalleriesPanel`
|
||||
- `PerformerGroupsPanel`
|
||||
- `PerformerImagesPanel`
|
||||
- `PerformerScenesPanel`
|
||||
- `PluginRoutes`
|
||||
- `SceneCard`
|
||||
- `SceneCard.Details`
|
||||
@@ -169,6 +185,10 @@ Returns `void`.
|
||||
- `SceneCard.Overlays`
|
||||
- `SceneCard.Popovers`
|
||||
- `SceneIDSelect`
|
||||
- `ScenePage`
|
||||
- `ScenePage.Tabs`
|
||||
- `ScenePage.TabContent`
|
||||
- `ScenePlayer`
|
||||
- `SceneSelect`
|
||||
- `SceneSelect.sort`
|
||||
- `SelectSetting`
|
||||
@@ -179,6 +199,15 @@ Returns `void`.
|
||||
- `StudioIDSelect`
|
||||
- `StudioSelect`
|
||||
- `StudioSelect.sort`
|
||||
- `TabTitleCounter`
|
||||
- `TagCard`
|
||||
- `TagCard.Details`
|
||||
- `TagCard.Image`
|
||||
- `TagCard.Overlays`
|
||||
- `TagCard.Popovers`
|
||||
- `TagCard.Title`
|
||||
- `TagLink`
|
||||
- `TabTitleCounter`
|
||||
- `TagIDSelect`
|
||||
- `TagSelect`
|
||||
- `TagSelect.sort`
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import React from "react";
|
||||
import { HoverPopover } from "./components/Shared/HoverPopover";
|
||||
import { TagLink } from "./components/Shared/TagLink";
|
||||
import { LoadingIndicator } from "./components/Shared/LoadingIndicator";
|
||||
|
||||
export const components: Record<string, Function> = {
|
||||
HoverPopover,
|
||||
TagLink,
|
||||
LoadingIndicator,
|
||||
};
|
||||
export let components: Record<string, Function> = {};
|
||||
|
||||
const beforeFns: Record<string, Function[]> = {};
|
||||
const insteadFns: Record<string, Function[]> = {};
|
||||
@@ -118,10 +111,12 @@ export function PatchComponent<T>(
|
||||
}
|
||||
|
||||
// patches a component and registers it in the pluginapi components object
|
||||
export function PatchContainerComponent(
|
||||
export function PatchContainerComponent<T = {}>(
|
||||
component: string
|
||||
): React.FC<React.PropsWithChildren<{}>> {
|
||||
const fn = (props: React.PropsWithChildren<{}>) => {
|
||||
): React.FC<React.PropsWithChildren<T>> {
|
||||
const fn: React.FC<React.PropsWithChildren<T>> = (
|
||||
props: React.PropsWithChildren<T>
|
||||
) => {
|
||||
return <>{props.children}</>;
|
||||
};
|
||||
|
||||
|
||||
24
ui/v2.5/src/pluginApi.d.ts
vendored
24
ui/v2.5/src/pluginApi.d.ts
vendored
@@ -616,6 +616,7 @@ declare namespace PluginApi {
|
||||
const FontAwesomeSolid: typeof import("@fortawesome/free-solid-svg-icons");
|
||||
const Intl: typeof import("react-intl");
|
||||
const Mousetrap: typeof import("mousetrap");
|
||||
const ReactSelect: typeof import("react-select");
|
||||
|
||||
// @ts-expect-error
|
||||
import { MousetrapStatic } from "mousetrap";
|
||||
@@ -688,6 +689,29 @@ declare namespace PluginApi {
|
||||
StringListSetting: React.FC<any>;
|
||||
ConstantSetting: React.FC<any>;
|
||||
SceneFileInfoPanel: React.FC<any>;
|
||||
PerformerPage: React.FC<any>;
|
||||
PerformerAppearsWithPanel: React.FC<any>;
|
||||
PerformerGalleriesPanel: React.FC<any>;
|
||||
PerformerGroupsPanel: React.FC<any>;
|
||||
PerformerScenesPanel: React.FC<any>;
|
||||
PerformerImagesPanel: React.FC<any>;
|
||||
TabTitleCounter: React.FC<any>;
|
||||
PerformerCard: React.FC<any>;
|
||||
"PerformerCard.Popovers": React.FC<any>;
|
||||
"PerformerCard.Details": React.FC<any>;
|
||||
"PerformerCard.Overlays": React.FC<any>;
|
||||
"PerformerCard.Image": React.FC<any>;
|
||||
"PerformerCard.Title": React.FC<any>;
|
||||
"TagCard.Popovers": React.FC<any>;
|
||||
"TagCard.Details": React.FC<any>;
|
||||
"TagCard.Overlays": React.FC<any>;
|
||||
"TagCard.Image": React.FC<any>;
|
||||
"TagCard.Title": React.FC<any>;
|
||||
ScenePage: React.FC<any>;
|
||||
"ScenePage.Tabs": React.FC<any>;
|
||||
"ScenePage.TabContent": React.FC<any>;
|
||||
ScenePlayer: React.FC<any>;
|
||||
FrontPage: React.FC<any>;
|
||||
};
|
||||
type PatchableComponentNames = keyof typeof components | string;
|
||||
namespace utils {
|
||||
|
||||
@@ -11,6 +11,7 @@ import * as Bootstrap from "react-bootstrap";
|
||||
import * as Intl from "react-intl";
|
||||
import * as FontAwesomeSolid from "@fortawesome/free-solid-svg-icons";
|
||||
import * as FontAwesomeRegular from "@fortawesome/free-regular-svg-icons";
|
||||
import * as ReactSelect from "react-select";
|
||||
import { useSpriteInfo } from "./hooks/sprite";
|
||||
import { useToast } from "./hooks/Toast";
|
||||
import Event from "./hooks/event";
|
||||
@@ -73,6 +74,7 @@ export const PluginApi = {
|
||||
FontAwesomeSolid,
|
||||
Mousetrap,
|
||||
MousetrapPause,
|
||||
ReactSelect,
|
||||
},
|
||||
register: {
|
||||
// register a route to be added to the main router
|
||||
|
||||
Reference in New Issue
Block a user