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>;
|
Button: React.FC<any>;
|
||||||
Nav: React.FC<any> & {
|
Nav: React.FC<any> & {
|
||||||
Link: React.FC<any>;
|
Link: React.FC<any>;
|
||||||
|
Item: React.FC<any>;
|
||||||
};
|
};
|
||||||
|
Tab: React.FC<any> & {
|
||||||
|
Pane: React.FC<any>;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
FontAwesomeSolid: {
|
FontAwesomeSolid: {
|
||||||
faEthernet: any;
|
faEthernet: any;
|
||||||
@@ -45,7 +49,7 @@ interface IPluginApi {
|
|||||||
const React = PluginApi.React;
|
const React = PluginApi.React;
|
||||||
const GQL = PluginApi.GQL;
|
const GQL = PluginApi.GQL;
|
||||||
|
|
||||||
const { Button } = PluginApi.libraries.Bootstrap;
|
const { Button, Nav, Tab } = PluginApi.libraries.Bootstrap;
|
||||||
const { faEthernet } = PluginApi.libraries.FontAwesomeSolid;
|
const { faEthernet } = PluginApi.libraries.FontAwesomeSolid;
|
||||||
const {
|
const {
|
||||||
Link,
|
Link,
|
||||||
@@ -144,6 +148,10 @@ interface IPluginApi {
|
|||||||
return <><Overlays />{original({...props})}</>;
|
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 TestPage: React.FC = () => {
|
||||||
const componentsToLoad = [
|
const componentsToLoad = [
|
||||||
PluginApi.loadableComponents.SceneCard,
|
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,
|
getFrontPageContent,
|
||||||
} from "src/core/config";
|
} from "src/core/config";
|
||||||
import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
|
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 intl = useIntl();
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
|
|
||||||
@@ -81,6 +82,6 @@ const FrontPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default FrontPage;
|
export default FrontPage;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { usePerformerUpdate } from "src/core/StashService";
|
|||||||
import { ILabeledId } from "src/models/list-filter/types";
|
import { ILabeledId } from "src/models/list-filter/types";
|
||||||
import ScreenUtils from "src/utils/screen";
|
import ScreenUtils from "src/utils/screen";
|
||||||
import { FavoriteIcon } from "../Shared/FavoriteIcon";
|
import { FavoriteIcon } from "../Shared/FavoriteIcon";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
export interface IPerformerCardExtraCriteria {
|
export interface IPerformerCardExtraCriteria {
|
||||||
scenes?: ModifierCriterion<CriterionValue>[];
|
scenes?: ModifierCriterion<CriterionValue>[];
|
||||||
@@ -43,75 +44,9 @@ interface IPerformerCardProps {
|
|||||||
extraCriteria?: IPerformerCardExtraCriteria;
|
extraCriteria?: IPerformerCardExtraCriteria;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
const PerformerCardPopovers: React.FC<IPerformerCardProps> = PatchComponent(
|
||||||
performer,
|
"PerformerCard.Popovers",
|
||||||
containerWidth,
|
({ performer, extraCriteria }) => {
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function maybeRenderScenesPopoverButton() {
|
function maybeRenderScenesPopoverButton() {
|
||||||
if (!performer.scene_count) return;
|
if (!performer.scene_count) return;
|
||||||
|
|
||||||
@@ -212,7 +147,6 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeRenderPopoverButtonGroup() {
|
|
||||||
if (
|
if (
|
||||||
performer.scene_count ||
|
performer.scene_count ||
|
||||||
performer.image_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() {
|
function maybeRenderRatingBanner() {
|
||||||
@@ -262,34 +217,6 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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
|
<FavoriteIcon
|
||||||
favorite={performer.favorite}
|
favorite={performer.favorite}
|
||||||
@@ -300,8 +227,31 @@ export const PerformerCard: React.FC<IPerformerCardProps> = ({
|
|||||||
{maybeRenderRatingBanner()}
|
{maybeRenderRatingBanner()}
|
||||||
{maybeRenderFlag()}
|
{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 ? (
|
{age !== 0 ? (
|
||||||
<div className="performer-card__age">{ageString}</div>
|
<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}
|
selected={selected}
|
||||||
selecting={selecting}
|
selecting={selecting}
|
||||||
onSelectedChanged={onSelectedChanged}
|
onSelectedChanged={onSelectedChanged}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import { FavoriteIcon } from "src/components/Shared/FavoriteIcon";
|
|||||||
import { AliasList } from "src/components/Shared/DetailsPage/AliasList";
|
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";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
performer: GQL.PerformerDataFragment;
|
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 Toast = useToast();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -283,7 +286,9 @@ const PerformerPage: React.FC<IProps> = ({ performer, tabKey }) => {
|
|||||||
Toast.success(
|
Toast.success(
|
||||||
intl.formatMessage(
|
intl.formatMessage(
|
||||||
{ id: "toast.updated_entity" },
|
{ 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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const PerformerLoader: React.FC<RouteComponentProps<IPerformerParams>> = ({
|
const PerformerLoader: React.FC<RouteComponentProps<IPerformerParams>> = ({
|
||||||
location,
|
location,
|
||||||
|
|||||||
@@ -3,16 +3,15 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { GalleryList } from "src/components/Galleries/GalleryList";
|
import { GalleryList } from "src/components/Galleries/GalleryList";
|
||||||
import { usePerformerFilterHook } from "src/core/performers";
|
import { usePerformerFilterHook } from "src/core/performers";
|
||||||
import { View } from "src/components/List/views";
|
import { View } from "src/components/List/views";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
interface IPerformerDetailsProps {
|
interface IPerformerDetailsProps {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
performer: GQL.PerformerDataFragment;
|
performer: GQL.PerformerDataFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PerformerGalleriesPanel: React.FC<IPerformerDetailsProps> = ({
|
export const PerformerGalleriesPanel: React.FC<IPerformerDetailsProps> =
|
||||||
active,
|
PatchComponent("PerformerGalleriesPanel", ({ active, performer }) => {
|
||||||
performer,
|
|
||||||
}) => {
|
|
||||||
const filterHook = usePerformerFilterHook(performer);
|
const filterHook = usePerformerFilterHook(performer);
|
||||||
return (
|
return (
|
||||||
<GalleryList
|
<GalleryList
|
||||||
@@ -21,4 +20,4 @@ export const PerformerGalleriesPanel: React.FC<IPerformerDetailsProps> = ({
|
|||||||
view={View.PerformerGalleries}
|
view={View.PerformerGalleries}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|||||||
@@ -3,16 +3,15 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { GroupList } from "src/components/Groups/GroupList";
|
import { GroupList } from "src/components/Groups/GroupList";
|
||||||
import { usePerformerFilterHook } from "src/core/performers";
|
import { usePerformerFilterHook } from "src/core/performers";
|
||||||
import { View } from "src/components/List/views";
|
import { View } from "src/components/List/views";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
interface IPerformerDetailsProps {
|
interface IPerformerDetailsProps {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
performer: GQL.PerformerDataFragment;
|
performer: GQL.PerformerDataFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PerformerGroupsPanel: React.FC<IPerformerDetailsProps> = ({
|
export const PerformerGroupsPanel: React.FC<IPerformerDetailsProps> =
|
||||||
active,
|
PatchComponent("PerformerGroupsPanel", ({ active, performer }) => {
|
||||||
performer,
|
|
||||||
}) => {
|
|
||||||
const filterHook = usePerformerFilterHook(performer);
|
const filterHook = usePerformerFilterHook(performer);
|
||||||
return (
|
return (
|
||||||
<GroupList
|
<GroupList
|
||||||
@@ -21,4 +20,4 @@ export const PerformerGroupsPanel: React.FC<IPerformerDetailsProps> = ({
|
|||||||
view={View.PerformerGroups}
|
view={View.PerformerGroups}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|||||||
@@ -3,16 +3,15 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { ImageList } from "src/components/Images/ImageList";
|
import { ImageList } from "src/components/Images/ImageList";
|
||||||
import { usePerformerFilterHook } from "src/core/performers";
|
import { usePerformerFilterHook } from "src/core/performers";
|
||||||
import { View } from "src/components/List/views";
|
import { View } from "src/components/List/views";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
interface IPerformerImagesPanel {
|
interface IPerformerImagesPanel {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
performer: GQL.PerformerDataFragment;
|
performer: GQL.PerformerDataFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PerformerImagesPanel: React.FC<IPerformerImagesPanel> = ({
|
export const PerformerImagesPanel: React.FC<IPerformerImagesPanel> =
|
||||||
active,
|
PatchComponent("PerformerImagesPanel", ({ active, performer }) => {
|
||||||
performer,
|
|
||||||
}) => {
|
|
||||||
const filterHook = usePerformerFilterHook(performer);
|
const filterHook = usePerformerFilterHook(performer);
|
||||||
return (
|
return (
|
||||||
<ImageList
|
<ImageList
|
||||||
@@ -21,4 +20,4 @@ export const PerformerImagesPanel: React.FC<IPerformerImagesPanel> = ({
|
|||||||
view={View.PerformerImages}
|
view={View.PerformerImages}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|||||||
@@ -3,16 +3,15 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { SceneList } from "src/components/Scenes/SceneList";
|
import { SceneList } from "src/components/Scenes/SceneList";
|
||||||
import { usePerformerFilterHook } from "src/core/performers";
|
import { usePerformerFilterHook } from "src/core/performers";
|
||||||
import { View } from "src/components/List/views";
|
import { View } from "src/components/List/views";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
interface IPerformerDetailsProps {
|
interface IPerformerDetailsProps {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
performer: GQL.PerformerDataFragment;
|
performer: GQL.PerformerDataFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PerformerScenesPanel: React.FC<IPerformerDetailsProps> = ({
|
export const PerformerScenesPanel: React.FC<IPerformerDetailsProps> =
|
||||||
active,
|
PatchComponent("PerformerScenesPanel", ({ active, performer }) => {
|
||||||
performer,
|
|
||||||
}) => {
|
|
||||||
const filterHook = usePerformerFilterHook(performer);
|
const filterHook = usePerformerFilterHook(performer);
|
||||||
return (
|
return (
|
||||||
<SceneList
|
<SceneList
|
||||||
@@ -21,4 +20,4 @@ export const PerformerScenesPanel: React.FC<IPerformerDetailsProps> = ({
|
|||||||
view={View.PerformerScenes}
|
view={View.PerformerScenes}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|||||||
@@ -3,16 +3,15 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { PerformerList } from "src/components/Performers/PerformerList";
|
import { PerformerList } from "src/components/Performers/PerformerList";
|
||||||
import { usePerformerFilterHook } from "src/core/performers";
|
import { usePerformerFilterHook } from "src/core/performers";
|
||||||
import { View } from "src/components/List/views";
|
import { View } from "src/components/List/views";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
interface IPerformerDetailsProps {
|
interface IPerformerDetailsProps {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
performer: GQL.PerformerDataFragment;
|
performer: GQL.PerformerDataFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PerformerAppearsWithPanel: React.FC<IPerformerDetailsProps> = ({
|
export const PerformerAppearsWithPanel: React.FC<IPerformerDetailsProps> =
|
||||||
active,
|
PatchComponent("PerformerAppearsWithPanel", ({ active, performer }) => {
|
||||||
performer,
|
|
||||||
}) => {
|
|
||||||
const performerValue = {
|
const performerValue = {
|
||||||
id: performer.id,
|
id: performer.id,
|
||||||
label: performer.name ?? `Performer ${performer.id}`,
|
label: performer.name ?? `Performer ${performer.id}`,
|
||||||
@@ -32,4 +31,4 @@ export const PerformerAppearsWithPanel: React.FC<IPerformerDetailsProps> = ({
|
|||||||
view={View.PerformerAppearsWith}
|
view={View.PerformerAppearsWith}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import airplay from "@silvermine/videojs-airplay";
|
|||||||
import chromecast from "@silvermine/videojs-chromecast";
|
import chromecast from "@silvermine/videojs-chromecast";
|
||||||
import abLoopPlugin from "videojs-abloop";
|
import abLoopPlugin from "videojs-abloop";
|
||||||
import ScreenUtils from "src/utils/screen";
|
import ScreenUtils from "src/utils/screen";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
// register videojs plugins
|
// register videojs plugins
|
||||||
airplay(videojs);
|
airplay(videojs);
|
||||||
@@ -210,7 +211,9 @@ interface IScenePlayerProps {
|
|||||||
onPrevious: () => void;
|
onPrevious: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
export const ScenePlayer: React.FC<IScenePlayerProps> = PatchComponent(
|
||||||
|
"ScenePlayer",
|
||||||
|
({
|
||||||
scene,
|
scene,
|
||||||
hideScrubberOverride,
|
hideScrubberOverride,
|
||||||
autoplay,
|
autoplay,
|
||||||
@@ -220,7 +223,7 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
|||||||
onComplete,
|
onComplete,
|
||||||
onNext,
|
onNext,
|
||||||
onPrevious,
|
onPrevious,
|
||||||
}) => {
|
}) => {
|
||||||
const { configuration } = useContext(ConfigurationContext);
|
const { configuration } = useContext(ConfigurationContext);
|
||||||
const interfaceConfig = configuration?.interface;
|
const interfaceConfig = configuration?.interface;
|
||||||
const uiConfig = configuration?.ui;
|
const uiConfig = configuration?.ui;
|
||||||
@@ -892,7 +895,10 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx("VideoPlayer", { portrait: isPortrait, "no-file": !file })}
|
className={cx("VideoPlayer", {
|
||||||
|
portrait: isPortrait,
|
||||||
|
"no-file": !file,
|
||||||
|
})}
|
||||||
onKeyDownCapture={onKeyDown}
|
onKeyDownCapture={onKeyDown}
|
||||||
>
|
>
|
||||||
<div className="video-wrapper" ref={videoRef} />
|
<div className="video-wrapper" ref={videoRef} />
|
||||||
@@ -910,6 +916,7 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default ScenePlayer;
|
export default ScenePlayer;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import { useRatingKeybinds } from "src/hooks/keybinds";
|
|||||||
import { lazyComponent } from "src/utils/lazyComponent";
|
import { lazyComponent } from "src/utils/lazyComponent";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
import { TruncatedText } from "src/components/Shared/TruncatedText";
|
||||||
|
import { PatchComponent, PatchContainerComponent } from "src/patch";
|
||||||
|
|
||||||
const SubmitStashBoxDraft = lazyComponent(
|
const SubmitStashBoxDraft = lazyComponent(
|
||||||
() => import("src/components/Dialogs/SubmitDraft")
|
() => import("src/components/Dialogs/SubmitDraft")
|
||||||
@@ -153,7 +154,13 @@ interface ISceneParams {
|
|||||||
id: string;
|
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,
|
scene,
|
||||||
setTimestamp,
|
setTimestamp,
|
||||||
queueScenes,
|
queueScenes,
|
||||||
@@ -170,7 +177,8 @@ const ScenePage: React.FC<IProps> = ({
|
|||||||
collapsed,
|
collapsed,
|
||||||
setCollapsed,
|
setCollapsed,
|
||||||
setContinuePlaylist,
|
setContinuePlaylist,
|
||||||
}) => {
|
} = props;
|
||||||
|
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [updateScene] = useSceneUpdate();
|
const [updateScene] = useSceneUpdate();
|
||||||
@@ -423,6 +431,7 @@ const ScenePage: React.FC<IProps> = ({
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Nav variant="tabs" className="mr-auto">
|
<Nav variant="tabs" className="mr-auto">
|
||||||
|
<ScenePageTabs {...props}>
|
||||||
<Nav.Item>
|
<Nav.Item>
|
||||||
<Nav.Link eventKey="scene-details-panel">
|
<Nav.Link eventKey="scene-details-panel">
|
||||||
<FormattedMessage id="details" />
|
<FormattedMessage id="details" />
|
||||||
@@ -485,10 +494,12 @@ const ScenePage: React.FC<IProps> = ({
|
|||||||
<FormattedMessage id="actions.edit" />
|
<FormattedMessage id="actions.edit" />
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
|
</ScenePageTabs>
|
||||||
</Nav>
|
</Nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tab.Content>
|
<Tab.Content>
|
||||||
|
<ScenePageTabContent {...props}>
|
||||||
<Tab.Pane eventKey="scene-details-panel">
|
<Tab.Pane eventKey="scene-details-panel">
|
||||||
<SceneDetailPanel scene={scene} />
|
<SceneDetailPanel scene={scene} />
|
||||||
</Tab.Pane>
|
</Tab.Pane>
|
||||||
@@ -529,7 +540,10 @@ const ScenePage: React.FC<IProps> = ({
|
|||||||
<Tab.Pane eventKey="scene-video-filter-panel">
|
<Tab.Pane eventKey="scene-video-filter-panel">
|
||||||
<SceneVideoFilterPanel scene={scene} />
|
<SceneVideoFilterPanel scene={scene} />
|
||||||
</Tab.Pane>
|
</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} />
|
<SceneFileInfoPanel scene={scene} />
|
||||||
</Tab.Pane>
|
</Tab.Pane>
|
||||||
<Tab.Pane eventKey="scene-edit-panel" mountOnEnter>
|
<Tab.Pane eventKey="scene-edit-panel" mountOnEnter>
|
||||||
@@ -543,6 +557,7 @@ const ScenePage: React.FC<IProps> = ({
|
|||||||
<Tab.Pane eventKey="scene-history-panel">
|
<Tab.Pane eventKey="scene-history-panel">
|
||||||
<SceneHistoryPanel scene={scene} />
|
<SceneHistoryPanel scene={scene} />
|
||||||
</Tab.Pane>
|
</Tab.Pane>
|
||||||
|
</ScenePageTabContent>
|
||||||
</Tab.Content>
|
</Tab.Content>
|
||||||
</Tab.Container>
|
</Tab.Container>
|
||||||
);
|
);
|
||||||
@@ -657,7 +672,7 @@ const ScenePage: React.FC<IProps> = ({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
const SceneLoader: React.FC<RouteComponentProps<ISceneParams>> = ({
|
const SceneLoader: React.FC<RouteComponentProps<ISceneParams>> = ({
|
||||||
location,
|
location,
|
||||||
|
|||||||
@@ -2,19 +2,23 @@ import { FormattedMessage } from "react-intl";
|
|||||||
import { Counter } from "../Counter";
|
import { Counter } from "../Counter";
|
||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
export const TabTitleCounter: React.FC<{
|
export const TabTitleCounter: React.FC<{
|
||||||
messageID: string;
|
messageID: string;
|
||||||
count: number;
|
count: number;
|
||||||
abbreviateCounter: boolean;
|
abbreviateCounter: boolean;
|
||||||
}> = ({ messageID, count, abbreviateCounter }) => {
|
}> = PatchComponent(
|
||||||
|
"TabTitleCounter",
|
||||||
|
({ messageID, count, abbreviateCounter }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormattedMessage id={messageID} />
|
<FormattedMessage id={messageID} />
|
||||||
<Counter count={count} abbreviateCounter={abbreviateCounter} hideZero />
|
<Counter count={count} abbreviateCounter={abbreviateCounter} hideZero />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export function useTabKey(props: {
|
export function useTabKey(props: {
|
||||||
tabKey: string | undefined;
|
tabKey: string | undefined;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useCallback, useEffect, useRef } from "react";
|
import React, { useState, useCallback, useEffect, useRef } from "react";
|
||||||
import { Overlay, Popover, OverlayProps } from "react-bootstrap";
|
import { Overlay, Popover, OverlayProps } from "react-bootstrap";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
interface IHoverPopover {
|
interface IHoverPopover {
|
||||||
enterDelay?: number;
|
enterDelay?: number;
|
||||||
@@ -12,7 +13,9 @@ interface IHoverPopover {
|
|||||||
target?: React.RefObject<HTMLElement>;
|
target?: React.RefObject<HTMLElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HoverPopover: React.FC<IHoverPopover> = ({
|
export const HoverPopover: React.FC<IHoverPopover> = PatchComponent(
|
||||||
|
"HoverPopover",
|
||||||
|
({
|
||||||
enterDelay = 200,
|
enterDelay = 200,
|
||||||
leaveDelay = 200,
|
leaveDelay = 200,
|
||||||
content,
|
content,
|
||||||
@@ -22,7 +25,7 @@ export const HoverPopover: React.FC<IHoverPopover> = ({
|
|||||||
onOpen,
|
onOpen,
|
||||||
onClose,
|
onClose,
|
||||||
target,
|
target,
|
||||||
}) => {
|
}) => {
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
const triggerRef = useRef<HTMLDivElement>(null);
|
const triggerRef = useRef<HTMLDivElement>(null);
|
||||||
const enterTimer = useRef<number>();
|
const enterTimer = useRef<number>();
|
||||||
@@ -80,4 +83,5 @@ export const HoverPopover: React.FC<IHoverPopover> = ({
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import { Spinner } from "react-bootstrap";
|
import { Spinner } from "react-bootstrap";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
interface ILoadingProps {
|
interface ILoadingProps {
|
||||||
message?: JSX.Element | string;
|
message?: JSX.Element | string;
|
||||||
@@ -13,19 +14,20 @@ interface ILoadingProps {
|
|||||||
const CLASSNAME = "LoadingIndicator";
|
const CLASSNAME = "LoadingIndicator";
|
||||||
const CLASSNAME_MESSAGE = `${CLASSNAME}-message`;
|
const CLASSNAME_MESSAGE = `${CLASSNAME}-message`;
|
||||||
|
|
||||||
export const LoadingIndicator: React.FC<ILoadingProps> = ({
|
export const LoadingIndicator: React.FC<ILoadingProps> = PatchComponent(
|
||||||
message,
|
"LoadingIndicator",
|
||||||
inline = false,
|
({ message, inline = false, small = false, card = false }) => {
|
||||||
small = false,
|
|
||||||
card = false,
|
|
||||||
}) => {
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const text = intl.formatMessage({ id: "loading.generic" });
|
const text = intl.formatMessage({ id: "loading.generic" });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(CLASSNAME, { inline, small, "card-based": card })}>
|
<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>
|
<span className="sr-only">{text}</span>
|
||||||
</Spinner>
|
</Spinner>
|
||||||
{message !== "" && (
|
{message !== "" && (
|
||||||
@@ -33,4 +35,5 @@ export const LoadingIndicator: React.FC<ILoadingProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ interface IProps {
|
|||||||
url: string;
|
url: string;
|
||||||
type: PopoverLinkType;
|
type: PopoverLinkType;
|
||||||
count: number;
|
count: number;
|
||||||
|
showZero?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PopoverCountButton: React.FC<IProps> = ({
|
export const PopoverCountButton: React.FC<IProps> = ({
|
||||||
@@ -60,9 +61,14 @@ export const PopoverCountButton: React.FC<IProps> = ({
|
|||||||
url,
|
url,
|
||||||
type,
|
type,
|
||||||
count,
|
count,
|
||||||
|
showZero = true,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
if (!showZero && count === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO - refactor - create SceneIcon, ImageIcon etc components
|
// TODO - refactor - create SceneIcon, ImageIcon etc components
|
||||||
function getIcon() {
|
function getIcon() {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { Placement } from "react-bootstrap/esm/Overlay";
|
|||||||
import { faFolderTree } from "@fortawesome/free-solid-svg-icons";
|
import { faFolderTree } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
type SceneMarkerFragment = Pick<GQL.SceneMarker, "id" | "title" | "seconds"> & {
|
type SceneMarkerFragment = Pick<GQL.SceneMarker, "id" | "title" | "seconds"> & {
|
||||||
scene: Pick<GQL.Scene, "id">;
|
scene: Pick<GQL.Scene, "id">;
|
||||||
@@ -243,14 +244,16 @@ interface ITagLinkProps {
|
|||||||
hierarchyTooltipID?: string;
|
hierarchyTooltipID?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TagLink: React.FC<ITagLinkProps> = ({
|
export const TagLink: React.FC<ITagLinkProps> = PatchComponent(
|
||||||
|
"TagLink",
|
||||||
|
({
|
||||||
tag,
|
tag,
|
||||||
linkType = "scene",
|
linkType = "scene",
|
||||||
className,
|
className,
|
||||||
hoverPlacement,
|
hoverPlacement,
|
||||||
showHierarchyIcon = false,
|
showHierarchyIcon = false,
|
||||||
hierarchyTooltipID,
|
hierarchyTooltipID,
|
||||||
}) => {
|
}) => {
|
||||||
const link = useMemo(() => {
|
const link = useMemo(() => {
|
||||||
switch (linkType) {
|
switch (linkType) {
|
||||||
case "scene":
|
case "scene":
|
||||||
@@ -305,4 +308,5 @@ export const TagLink: React.FC<ITagLinkProps> = ({
|
|||||||
</TagPopover>
|
</TagPopover>
|
||||||
</SortNameLinkComponent>
|
</SortNameLinkComponent>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { PatchComponent } from "src/patch";
|
||||||
import { Button, ButtonGroup } from "react-bootstrap";
|
import { Button, ButtonGroup } from "react-bootstrap";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@@ -12,6 +13,7 @@ import { Icon } from "../Shared/Icon";
|
|||||||
import { faHeart } from "@fortawesome/free-solid-svg-icons";
|
import { faHeart } from "@fortawesome/free-solid-svg-icons";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { useTagUpdate } from "src/core/StashService";
|
import { useTagUpdate } from "src/core/StashService";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
tag: GQL.TagDataFragment;
|
tag: GQL.TagDataFragment;
|
||||||
containerWidth?: number;
|
containerWidth?: number;
|
||||||
@@ -21,53 +23,73 @@ interface IProps {
|
|||||||
onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void;
|
onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TagCard: React.FC<IProps> = ({
|
const TagCardPopovers: React.FC<IProps> = PatchComponent(
|
||||||
tag,
|
"TagCard.Popovers",
|
||||||
containerWidth,
|
({ tag }) => {
|
||||||
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) {
|
|
||||||
return (
|
return (
|
||||||
<TruncatedText
|
<>
|
||||||
className="tag-description"
|
<hr />
|
||||||
text={tag.description}
|
<ButtonGroup className="card-popovers">
|
||||||
lineCount={3}
|
<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() {
|
function renderFavoriteIcon() {
|
||||||
return (
|
return (
|
||||||
<Link to="" onClick={(e) => e.preventDefault()}>
|
<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() {
|
function maybeRenderParents() {
|
||||||
if (tag.parents.length === 1) {
|
if (tag.parents.length === 1) {
|
||||||
const parent = tag.parents[0];
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<hr />
|
{maybeRenderDescription()}
|
||||||
<ButtonGroup className="card-popovers">
|
{maybeRenderParents()}
|
||||||
{maybeRenderScenesPopoverButton()}
|
{maybeRenderChildren()}
|
||||||
{maybeRenderImagesPopoverButton()}
|
|
||||||
{maybeRenderGalleriesPopoverButton()}
|
|
||||||
{maybeRenderGroupsPopoverButton()}
|
|
||||||
{maybeRenderSceneMarkersPopoverButton()}
|
|
||||||
{maybeRenderPerformersPopoverButton()}
|
|
||||||
{maybeRenderStudiosPopoverButton()}
|
|
||||||
</ButtonGroup>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
|
|
||||||
|
const TagCardImage: React.FC<IProps> = PatchComponent(
|
||||||
|
"TagCard.Image",
|
||||||
|
({ tag }) => {
|
||||||
return (
|
return (
|
||||||
<GridCard
|
<>
|
||||||
className={`tag-card zoom-${zoomIndex}`}
|
|
||||||
url={`/tags/${tag.id}`}
|
|
||||||
width={cardWidth}
|
|
||||||
title={tag.name ?? ""}
|
|
||||||
linkClassName="tag-card-header"
|
|
||||||
image={
|
|
||||||
<img
|
<img
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
className="tag-card-image"
|
className="tag-card-image"
|
||||||
alt={tag.name}
|
alt={tag.name}
|
||||||
src={tag.image_path ?? ""}
|
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}
|
selected={selected}
|
||||||
selecting={selecting}
|
selecting={selecting}
|
||||||
onSelectedChanged={onSelectedChanged}
|
onSelectedChanged={onSelectedChanged}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ This namespace contains the generated graphql client interface. This is a low-le
|
|||||||
- `FontAwesomeSolid`
|
- `FontAwesomeSolid`
|
||||||
- `Mousetrap`
|
- `Mousetrap`
|
||||||
- `MousetrapPause`
|
- `MousetrapPause`
|
||||||
|
- `ReactSelect`
|
||||||
|
|
||||||
### `register`
|
### `register`
|
||||||
|
|
||||||
@@ -147,21 +148,36 @@ Returns `void`.
|
|||||||
- `CountrySelect`
|
- `CountrySelect`
|
||||||
- `DateInput`
|
- `DateInput`
|
||||||
- `FolderSelect`
|
- `FolderSelect`
|
||||||
|
- `FrontPage`
|
||||||
- `GalleryIDSelect`
|
- `GalleryIDSelect`
|
||||||
- `GallerySelect`
|
- `GallerySelect`
|
||||||
- `GallerySelect.sort`
|
- `GallerySelect.sort`
|
||||||
|
- `HoverPopover`
|
||||||
- `Icon`
|
- `Icon`
|
||||||
- `ImageDetailPanel`
|
- `ImageDetailPanel`
|
||||||
|
- `LoadingIndicator`
|
||||||
- `ModalSetting`
|
- `ModalSetting`
|
||||||
- `GroupIDSelect`
|
- `GroupIDSelect`
|
||||||
- `GroupSelect`
|
- `GroupSelect`
|
||||||
- `GroupSelect.sort`
|
- `GroupSelect.sort`
|
||||||
- `NumberSetting`
|
- `NumberSetting`
|
||||||
|
- `PerformerAppearsWithPanel`
|
||||||
|
- `PerformerCard`
|
||||||
|
- `PerformerCard.Details`
|
||||||
|
- `PerformerCard.Image`
|
||||||
|
- `PerformerCard.Overlays`
|
||||||
|
- `PerformerCard.Popovers`
|
||||||
|
- `PerformerCard.Title`
|
||||||
- `PerformerDetailsPanel`
|
- `PerformerDetailsPanel`
|
||||||
- `PerformerDetailsPanel.DetailGroup`
|
- `PerformerDetailsPanel.DetailGroup`
|
||||||
- `PerformerIDSelect`
|
- `PerformerIDSelect`
|
||||||
|
- `PerformerPage`
|
||||||
- `PerformerSelect`
|
- `PerformerSelect`
|
||||||
- `PerformerSelect.sort`
|
- `PerformerSelect.sort`
|
||||||
|
- `PerformerGalleriesPanel`
|
||||||
|
- `PerformerGroupsPanel`
|
||||||
|
- `PerformerImagesPanel`
|
||||||
|
- `PerformerScenesPanel`
|
||||||
- `PluginRoutes`
|
- `PluginRoutes`
|
||||||
- `SceneCard`
|
- `SceneCard`
|
||||||
- `SceneCard.Details`
|
- `SceneCard.Details`
|
||||||
@@ -169,6 +185,10 @@ Returns `void`.
|
|||||||
- `SceneCard.Overlays`
|
- `SceneCard.Overlays`
|
||||||
- `SceneCard.Popovers`
|
- `SceneCard.Popovers`
|
||||||
- `SceneIDSelect`
|
- `SceneIDSelect`
|
||||||
|
- `ScenePage`
|
||||||
|
- `ScenePage.Tabs`
|
||||||
|
- `ScenePage.TabContent`
|
||||||
|
- `ScenePlayer`
|
||||||
- `SceneSelect`
|
- `SceneSelect`
|
||||||
- `SceneSelect.sort`
|
- `SceneSelect.sort`
|
||||||
- `SelectSetting`
|
- `SelectSetting`
|
||||||
@@ -179,6 +199,15 @@ Returns `void`.
|
|||||||
- `StudioIDSelect`
|
- `StudioIDSelect`
|
||||||
- `StudioSelect`
|
- `StudioSelect`
|
||||||
- `StudioSelect.sort`
|
- `StudioSelect.sort`
|
||||||
|
- `TabTitleCounter`
|
||||||
|
- `TagCard`
|
||||||
|
- `TagCard.Details`
|
||||||
|
- `TagCard.Image`
|
||||||
|
- `TagCard.Overlays`
|
||||||
|
- `TagCard.Popovers`
|
||||||
|
- `TagCard.Title`
|
||||||
|
- `TagLink`
|
||||||
|
- `TabTitleCounter`
|
||||||
- `TagIDSelect`
|
- `TagIDSelect`
|
||||||
- `TagSelect`
|
- `TagSelect`
|
||||||
- `TagSelect.sort`
|
- `TagSelect.sort`
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
import React from "react";
|
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> = {
|
export let components: Record<string, Function> = {};
|
||||||
HoverPopover,
|
|
||||||
TagLink,
|
|
||||||
LoadingIndicator,
|
|
||||||
};
|
|
||||||
|
|
||||||
const beforeFns: Record<string, Function[]> = {};
|
const beforeFns: Record<string, Function[]> = {};
|
||||||
const insteadFns: 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
|
// patches a component and registers it in the pluginapi components object
|
||||||
export function PatchContainerComponent(
|
export function PatchContainerComponent<T = {}>(
|
||||||
component: string
|
component: string
|
||||||
): React.FC<React.PropsWithChildren<{}>> {
|
): React.FC<React.PropsWithChildren<T>> {
|
||||||
const fn = (props: React.PropsWithChildren<{}>) => {
|
const fn: React.FC<React.PropsWithChildren<T>> = (
|
||||||
|
props: React.PropsWithChildren<T>
|
||||||
|
) => {
|
||||||
return <>{props.children}</>;
|
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 FontAwesomeSolid: typeof import("@fortawesome/free-solid-svg-icons");
|
||||||
const Intl: typeof import("react-intl");
|
const Intl: typeof import("react-intl");
|
||||||
const Mousetrap: typeof import("mousetrap");
|
const Mousetrap: typeof import("mousetrap");
|
||||||
|
const ReactSelect: typeof import("react-select");
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
import { MousetrapStatic } from "mousetrap";
|
import { MousetrapStatic } from "mousetrap";
|
||||||
@@ -688,6 +689,29 @@ declare namespace PluginApi {
|
|||||||
StringListSetting: React.FC<any>;
|
StringListSetting: React.FC<any>;
|
||||||
ConstantSetting: React.FC<any>;
|
ConstantSetting: React.FC<any>;
|
||||||
SceneFileInfoPanel: 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;
|
type PatchableComponentNames = keyof typeof components | string;
|
||||||
namespace utils {
|
namespace utils {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import * as Bootstrap from "react-bootstrap";
|
|||||||
import * as Intl from "react-intl";
|
import * as Intl from "react-intl";
|
||||||
import * as FontAwesomeSolid from "@fortawesome/free-solid-svg-icons";
|
import * as FontAwesomeSolid from "@fortawesome/free-solid-svg-icons";
|
||||||
import * as FontAwesomeRegular from "@fortawesome/free-regular-svg-icons";
|
import * as FontAwesomeRegular from "@fortawesome/free-regular-svg-icons";
|
||||||
|
import * as ReactSelect from "react-select";
|
||||||
import { useSpriteInfo } from "./hooks/sprite";
|
import { useSpriteInfo } from "./hooks/sprite";
|
||||||
import { useToast } from "./hooks/Toast";
|
import { useToast } from "./hooks/Toast";
|
||||||
import Event from "./hooks/event";
|
import Event from "./hooks/event";
|
||||||
@@ -73,6 +74,7 @@ export const PluginApi = {
|
|||||||
FontAwesomeSolid,
|
FontAwesomeSolid,
|
||||||
Mousetrap,
|
Mousetrap,
|
||||||
MousetrapPause,
|
MousetrapPause,
|
||||||
|
ReactSelect,
|
||||||
},
|
},
|
||||||
register: {
|
register: {
|
||||||
// register a route to be added to the main router
|
// register a route to be added to the main router
|
||||||
|
|||||||
Reference in New Issue
Block a user