mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
Plugin api improvements (#4935)
* Support hook into App component * Add hookable PluginSettings component * Add useSettings to plugin hooks * Make setting inputs hookable * Add hooks for performer details panel * Update docs
This commit is contained in:
@@ -50,6 +50,7 @@ import { PluginRoutes } from "./plugins";
|
|||||||
// import plugin_api to run code
|
// import plugin_api to run code
|
||||||
import "./pluginApi";
|
import "./pluginApi";
|
||||||
import { ConnectionMonitor } from "./ConnectionMonitor";
|
import { ConnectionMonitor } from "./ConnectionMonitor";
|
||||||
|
import { PatchFunction } from "./patch";
|
||||||
|
|
||||||
const Performers = lazyComponent(
|
const Performers = lazyComponent(
|
||||||
() => import("./components/Performers/Performers")
|
() => import("./components/Performers/Performers")
|
||||||
@@ -144,6 +145,13 @@ function sortPlugins(plugins: PluginList) {
|
|||||||
return sorted;
|
return sorted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AppContainer: React.FC<React.PropsWithChildren<{}>> = PatchFunction(
|
||||||
|
"App",
|
||||||
|
(props: React.PropsWithChildren<{}>) => {
|
||||||
|
return <>{props.children}</>;
|
||||||
|
}
|
||||||
|
) as React.FC;
|
||||||
|
|
||||||
export const App: React.FC = () => {
|
export const App: React.FC = () => {
|
||||||
const config = useConfiguration();
|
const config = useConfiguration();
|
||||||
const [saveUI] = useConfigureUI();
|
const [saveUI] = useConfigureUI();
|
||||||
@@ -357,41 +365,43 @@ export const App: React.FC = () => {
|
|||||||
const titleProps = makeTitleProps();
|
const titleProps = makeTitleProps();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<AppContainer>
|
||||||
{messages ? (
|
<ErrorBoundary>
|
||||||
<IntlProvider
|
{messages ? (
|
||||||
locale={language}
|
<IntlProvider
|
||||||
messages={messages}
|
locale={language}
|
||||||
formats={intlFormats}
|
messages={messages}
|
||||||
>
|
formats={intlFormats}
|
||||||
<ConfigurationProvider
|
|
||||||
configuration={config.data?.configuration}
|
|
||||||
loading={config.loading}
|
|
||||||
>
|
>
|
||||||
{maybeRenderReleaseNotes()}
|
<ConfigurationProvider
|
||||||
<ToastProvider>
|
configuration={config.data?.configuration}
|
||||||
<ConnectionMonitor />
|
loading={config.loading}
|
||||||
<Suspense fallback={<LoadingIndicator />}>
|
>
|
||||||
<LightboxProvider>
|
{maybeRenderReleaseNotes()}
|
||||||
<ManualProvider>
|
<ToastProvider>
|
||||||
<InteractiveProvider>
|
<ConnectionMonitor />
|
||||||
<Helmet {...titleProps} />
|
<Suspense fallback={<LoadingIndicator />}>
|
||||||
{maybeRenderNavbar()}
|
<LightboxProvider>
|
||||||
<div
|
<ManualProvider>
|
||||||
className={`main container-fluid ${
|
<InteractiveProvider>
|
||||||
appleRendering ? "apple" : ""
|
<Helmet {...titleProps} />
|
||||||
}`}
|
{maybeRenderNavbar()}
|
||||||
>
|
<div
|
||||||
{renderContent()}
|
className={`main container-fluid ${
|
||||||
</div>
|
appleRendering ? "apple" : ""
|
||||||
</InteractiveProvider>
|
}`}
|
||||||
</ManualProvider>
|
>
|
||||||
</LightboxProvider>
|
{renderContent()}
|
||||||
</Suspense>
|
</div>
|
||||||
</ToastProvider>
|
</InteractiveProvider>
|
||||||
</ConfigurationProvider>
|
</ManualProvider>
|
||||||
</IntlProvider>
|
</LightboxProvider>
|
||||||
) : null}
|
</Suspense>
|
||||||
</ErrorBoundary>
|
</ToastProvider>
|
||||||
|
</ConfigurationProvider>
|
||||||
|
</IntlProvider>
|
||||||
|
) : null}
|
||||||
|
</ErrorBoundary>
|
||||||
|
</AppContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import React, { PropsWithChildren } from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import { TagLink } from "src/components/Shared/TagLink";
|
import { TagLink } from "src/components/Shared/TagLink";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
FormatPenisLength,
|
FormatPenisLength,
|
||||||
FormatWeight,
|
FormatWeight,
|
||||||
} from "../PerformerList";
|
} from "../PerformerList";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
interface IPerformerDetails {
|
interface IPerformerDetails {
|
||||||
performer: GQL.PerformerDataFragment;
|
performer: GQL.PerformerDataFragment;
|
||||||
@@ -20,231 +21,236 @@ interface IPerformerDetails {
|
|||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
const PerformerDetailGroup: React.FC<PropsWithChildren<IPerformerDetails>> =
|
||||||
performer,
|
PatchComponent("PerformerDetailsPanel.DetailGroup", ({ children }) => {
|
||||||
collapsed,
|
return <div className="detail-group">{children}</div>;
|
||||||
fullWidth,
|
});
|
||||||
}) => {
|
|
||||||
// Network state
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
function renderTagsField() {
|
export const PerformerDetailsPanel: React.FC<IPerformerDetails> =
|
||||||
if (!performer.tags.length) {
|
PatchComponent("PerformerDetailsPanel", (props) => {
|
||||||
return;
|
const { performer, collapsed, fullWidth } = props;
|
||||||
}
|
|
||||||
return (
|
|
||||||
<ul className="pl-0">
|
|
||||||
{(performer.tags ?? []).map((tag) => (
|
|
||||||
<TagLink key={tag.id} linkType="performer" tag={tag} />
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderStashIDs() {
|
// Network state
|
||||||
if (!performer.stash_ids.length) {
|
const intl = useIntl();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
function renderTagsField() {
|
||||||
<ul className="pl-0">
|
if (!performer.tags.length) {
|
||||||
{performer.stash_ids.map((stashID) => (
|
return;
|
||||||
<li key={stashID.stash_id} className="row no-gutters">
|
}
|
||||||
<StashIDPill stashID={stashID} linkType="performers" />
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function maybeRenderExtraDetails() {
|
|
||||||
if (!collapsed) {
|
|
||||||
/* Remove extra urls provided in details since they will be present by perfomr name */
|
|
||||||
/* This code can be removed once multple urls are supported for performers */
|
|
||||||
let details = performer?.details
|
|
||||||
?.replace(/\[((?:http|www\.)[^\n\]]+)\]/gm, "")
|
|
||||||
.trim();
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ul className="pl-0">
|
||||||
<DetailItem
|
{(performer.tags ?? []).map((tag) => (
|
||||||
id="tattoos"
|
<TagLink key={tag.id} linkType="performer" tag={tag} />
|
||||||
value={performer?.tattoos}
|
))}
|
||||||
fullWidth={fullWidth}
|
</ul>
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
id="piercings"
|
|
||||||
value={performer?.piercings}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
id="career_length"
|
|
||||||
value={performer?.career_length}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
/>
|
|
||||||
<DetailItem id="details" value={details} fullWidth={fullWidth} />
|
|
||||||
<DetailItem
|
|
||||||
id="tags"
|
|
||||||
value={renderTagsField()}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
id="stash_ids"
|
|
||||||
value={renderStashIDs()}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
function renderStashIDs() {
|
||||||
<div className="detail-group">
|
if (!performer.stash_ids.length) {
|
||||||
{performer.gender ? (
|
return;
|
||||||
<DetailItem
|
}
|
||||||
id="gender"
|
|
||||||
value={intl.formatMessage({ id: "gender_types." + performer.gender })}
|
return (
|
||||||
fullWidth={fullWidth}
|
<ul className="pl-0">
|
||||||
/>
|
{performer.stash_ids.map((stashID) => (
|
||||||
) : (
|
<li key={stashID.stash_id} className="row no-gutters">
|
||||||
""
|
<StashIDPill stashID={stashID} linkType="performers" />
|
||||||
)}
|
</li>
|
||||||
<DetailItem
|
))}
|
||||||
id="age"
|
</ul>
|
||||||
value={
|
);
|
||||||
!fullWidth
|
}
|
||||||
? TextUtils.age(performer.birthdate, performer.death_date)
|
|
||||||
: FormatAge(performer.birthdate, performer.death_date)
|
function maybeRenderExtraDetails() {
|
||||||
}
|
if (!collapsed) {
|
||||||
title={
|
/* Remove extra urls provided in details since they will be present by perfomr name */
|
||||||
!fullWidth
|
/* This code can be removed once multple urls are supported for performers */
|
||||||
? TextUtils.formatDate(intl, performer.birthdate ?? undefined)
|
let details = performer?.details
|
||||||
: ""
|
?.replace(/\[((?:http|www\.)[^\n\]]+)\]/gm, "")
|
||||||
}
|
.trim();
|
||||||
fullWidth={fullWidth}
|
return (
|
||||||
/>
|
<>
|
||||||
<DetailItem id="death_date" value={performer.death_date} />
|
<DetailItem
|
||||||
{performer.country ? (
|
id="tattoos"
|
||||||
<DetailItem
|
value={performer?.tattoos}
|
||||||
id="country"
|
fullWidth={fullWidth}
|
||||||
value={
|
|
||||||
<CountryFlag
|
|
||||||
country={performer.country}
|
|
||||||
className="mr-2"
|
|
||||||
includeName={true}
|
|
||||||
/>
|
/>
|
||||||
|
<DetailItem
|
||||||
|
id="piercings"
|
||||||
|
value={performer?.piercings}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
id="career_length"
|
||||||
|
value={performer?.career_length}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
|
<DetailItem id="details" value={details} fullWidth={fullWidth} />
|
||||||
|
<DetailItem
|
||||||
|
id="tags"
|
||||||
|
value={renderTagsField()}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
id="stash_ids"
|
||||||
|
value={renderStashIDs()}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PerformerDetailGroup {...props}>
|
||||||
|
{performer.gender ? (
|
||||||
|
<DetailItem
|
||||||
|
id="gender"
|
||||||
|
value={intl.formatMessage({
|
||||||
|
id: "gender_types." + performer.gender,
|
||||||
|
})}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
<DetailItem
|
||||||
|
id="age"
|
||||||
|
value={
|
||||||
|
!fullWidth
|
||||||
|
? TextUtils.age(performer.birthdate, performer.death_date)
|
||||||
|
: FormatAge(performer.birthdate, performer.death_date)
|
||||||
|
}
|
||||||
|
title={
|
||||||
|
!fullWidth
|
||||||
|
? TextUtils.formatDate(intl, performer.birthdate ?? undefined)
|
||||||
|
: ""
|
||||||
}
|
}
|
||||||
fullWidth={fullWidth}
|
fullWidth={fullWidth}
|
||||||
/>
|
/>
|
||||||
) : (
|
<DetailItem id="death_date" value={performer.death_date} />
|
||||||
""
|
|
||||||
)}
|
|
||||||
<DetailItem
|
|
||||||
id="ethnicity"
|
|
||||||
value={performer?.ethnicity}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
id="hair_color"
|
|
||||||
value={performer?.hair_color}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
id="eye_color"
|
|
||||||
value={performer?.eye_color}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
id="height"
|
|
||||||
value={FormatHeight(performer.height_cm)}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
id="weight"
|
|
||||||
value={FormatWeight(performer.weight)}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
id="penis_length"
|
|
||||||
value={FormatPenisLength(performer.penis_length)}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
id="circumcised"
|
|
||||||
value={FormatCircumcised(performer.circumcised)}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
id="measurements"
|
|
||||||
value={performer?.measurements}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
id="fake_tits"
|
|
||||||
value={performer?.fake_tits}
|
|
||||||
fullWidth={fullWidth}
|
|
||||||
/>
|
|
||||||
{maybeRenderExtraDetails()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CompressedPerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|
||||||
performer,
|
|
||||||
}) => {
|
|
||||||
// Network state
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
function scrollToTop() {
|
|
||||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="sticky detail-header">
|
|
||||||
<div className="sticky detail-header-group">
|
|
||||||
<a className="performer-name" onClick={() => scrollToTop()}>
|
|
||||||
{performer.name}
|
|
||||||
</a>
|
|
||||||
{performer.gender ? (
|
|
||||||
<>
|
|
||||||
<span className="detail-divider">/</span>
|
|
||||||
<span className="performer-gender">
|
|
||||||
{intl.formatMessage({ id: "gender_types." + performer.gender })}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
{performer.birthdate ? (
|
|
||||||
<>
|
|
||||||
<span className="detail-divider">/</span>
|
|
||||||
<span
|
|
||||||
className="performer-age"
|
|
||||||
title={TextUtils.formatDate(
|
|
||||||
intl,
|
|
||||||
performer.birthdate ?? undefined
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{TextUtils.age(performer.birthdate, performer.death_date)}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
{performer.country ? (
|
{performer.country ? (
|
||||||
<>
|
<DetailItem
|
||||||
<span className="detail-divider">/</span>
|
id="country"
|
||||||
<span className="performer-country">
|
value={
|
||||||
<CountryFlag
|
<CountryFlag
|
||||||
country={performer.country}
|
country={performer.country}
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
includeName={true}
|
includeName={true}
|
||||||
/>
|
/>
|
||||||
</span>
|
}
|
||||||
</>
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
|
<DetailItem
|
||||||
|
id="ethnicity"
|
||||||
|
value={performer?.ethnicity}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
id="hair_color"
|
||||||
|
value={performer?.hair_color}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
id="eye_color"
|
||||||
|
value={performer?.eye_color}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
id="height"
|
||||||
|
value={FormatHeight(performer.height_cm)}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
id="weight"
|
||||||
|
value={FormatWeight(performer.weight)}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
id="penis_length"
|
||||||
|
value={FormatPenisLength(performer.penis_length)}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
id="circumcised"
|
||||||
|
value={FormatCircumcised(performer.circumcised)}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
id="measurements"
|
||||||
|
value={performer?.measurements}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
id="fake_tits"
|
||||||
|
value={performer?.fake_tits}
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
/>
|
||||||
|
{maybeRenderExtraDetails()}
|
||||||
|
</PerformerDetailGroup>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CompressedPerformerDetailsPanel: React.FC<IPerformerDetails> =
|
||||||
|
PatchComponent("CompressedPerformerDetailsPanel", ({ performer }) => {
|
||||||
|
// Network state
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
function scrollToTop() {
|
||||||
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sticky detail-header">
|
||||||
|
<div className="sticky detail-header-group">
|
||||||
|
<a className="performer-name" onClick={() => scrollToTop()}>
|
||||||
|
{performer.name}
|
||||||
|
</a>
|
||||||
|
{performer.gender ? (
|
||||||
|
<>
|
||||||
|
<span className="detail-divider">/</span>
|
||||||
|
<span className="performer-gender">
|
||||||
|
{intl.formatMessage({ id: "gender_types." + performer.gender })}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
{performer.birthdate ? (
|
||||||
|
<>
|
||||||
|
<span className="detail-divider">/</span>
|
||||||
|
<span
|
||||||
|
className="performer-age"
|
||||||
|
title={TextUtils.formatDate(
|
||||||
|
intl,
|
||||||
|
performer.birthdate ?? undefined
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{TextUtils.age(performer.birthdate, performer.death_date)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
{performer.country ? (
|
||||||
|
<>
|
||||||
|
<span className="detail-divider">/</span>
|
||||||
|
<span className="performer-country">
|
||||||
|
<CountryFlag
|
||||||
|
country={performer.country}
|
||||||
|
className="mr-2"
|
||||||
|
includeName={true}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
});
|
||||||
};
|
|
||||||
|
|||||||
@@ -92,60 +92,58 @@ interface ISettingGroup {
|
|||||||
collapsedDefault?: boolean;
|
collapsedDefault?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SettingGroup: React.FC<PropsWithChildren<ISettingGroup>> = ({
|
export const SettingGroup: React.FC<PropsWithChildren<ISettingGroup>> =
|
||||||
settingProps,
|
PatchComponent(
|
||||||
topLevel,
|
"SettingGroup",
|
||||||
collapsible,
|
({ settingProps, topLevel, collapsible, collapsedDefault, children }) => {
|
||||||
collapsedDefault,
|
const [open, setOpen] = useState(!collapsedDefault);
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const [open, setOpen] = useState(!collapsedDefault);
|
|
||||||
|
|
||||||
function renderCollapseButton() {
|
function renderCollapseButton() {
|
||||||
if (!collapsible) return;
|
if (!collapsible) return;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className="setting-group-collapse-button"
|
className="setting-group-collapse-button"
|
||||||
variant="minimal"
|
variant="minimal"
|
||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpen(!open)}
|
||||||
>
|
>
|
||||||
<Icon className="fa-fw" icon={open ? faChevronUp : faChevronDown} />
|
<Icon className="fa-fw" icon={open ? faChevronUp : faChevronDown} />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
function onDivClick(e: React.MouseEvent<HTMLDivElement>) {
|
|
||||||
if (!collapsible) return;
|
|
||||||
|
|
||||||
// ensure button was not clicked
|
|
||||||
let target: HTMLElement | null = e.target as HTMLElement;
|
|
||||||
while (target && target !== e.currentTarget) {
|
|
||||||
if (
|
|
||||||
target.nodeName.toLowerCase() === "button" ||
|
|
||||||
target.nodeName.toLowerCase() === "a"
|
|
||||||
) {
|
|
||||||
// button clicked, swallow event
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
target = target.parentElement;
|
|
||||||
|
function onDivClick(e: React.MouseEvent<HTMLDivElement>) {
|
||||||
|
if (!collapsible) return;
|
||||||
|
|
||||||
|
// ensure button was not clicked
|
||||||
|
let target: HTMLElement | null = e.target as HTMLElement;
|
||||||
|
while (target && target !== e.currentTarget) {
|
||||||
|
if (
|
||||||
|
target.nodeName.toLowerCase() === "button" ||
|
||||||
|
target.nodeName.toLowerCase() === "a"
|
||||||
|
) {
|
||||||
|
// button clicked, swallow event
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
target = target.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpen(!open);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`setting-group ${collapsible ? "collapsible" : ""}`}>
|
||||||
|
<Setting {...settingProps} onClick={onDivClick}>
|
||||||
|
{topLevel}
|
||||||
|
{renderCollapseButton()}
|
||||||
|
</Setting>
|
||||||
|
<Collapse in={open}>
|
||||||
|
<div className="collapsible-section">{children}</div>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setOpen(!open);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`setting-group ${collapsible ? "collapsible" : ""}`}>
|
|
||||||
<Setting {...settingProps} onClick={onDivClick}>
|
|
||||||
{topLevel}
|
|
||||||
{renderCollapseButton()}
|
|
||||||
</Setting>
|
|
||||||
<Collapse in={open}>
|
|
||||||
<div className="collapsible-section">{children}</div>
|
|
||||||
</Collapse>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
interface IBooleanSetting extends ISetting {
|
interface IBooleanSetting extends ISetting {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -153,53 +151,52 @@ interface IBooleanSetting extends ISetting {
|
|||||||
onChange: (v: boolean) => void;
|
onChange: (v: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BooleanSetting: React.FC<IBooleanSetting> = (props) => {
|
export const BooleanSetting: React.FC<IBooleanSetting> = PatchComponent(
|
||||||
const { id, disabled, checked, onChange, ...settingProps } = props;
|
"BooleanSetting",
|
||||||
|
(props) => {
|
||||||
|
const { id, disabled, checked, onChange, ...settingProps } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Setting {...settingProps} disabled={disabled}>
|
<Setting {...settingProps} disabled={disabled}>
|
||||||
<Form.Switch
|
<Form.Switch
|
||||||
id={id}
|
id={id}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
checked={checked ?? false}
|
checked={checked ?? false}
|
||||||
onChange={() => onChange(!checked)}
|
onChange={() => onChange(!checked)}
|
||||||
/>
|
/>
|
||||||
</Setting>
|
</Setting>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
interface ISelectSetting extends ISetting {
|
interface ISelectSetting extends ISetting {
|
||||||
value?: string | number | string[];
|
value?: string | number | string[];
|
||||||
onChange: (v: string) => void;
|
onChange: (v: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SelectSetting: React.FC<PropsWithChildren<ISelectSetting>> = ({
|
export const SelectSetting: React.FC<PropsWithChildren<ISelectSetting>> =
|
||||||
id,
|
PatchComponent(
|
||||||
headingID,
|
"SelectSetting",
|
||||||
subHeadingID,
|
({ id, headingID, subHeadingID, value, children, onChange, advanced }) => {
|
||||||
value,
|
return (
|
||||||
children,
|
<Setting
|
||||||
onChange,
|
advanced={advanced}
|
||||||
advanced,
|
headingID={headingID}
|
||||||
}) => {
|
subHeadingID={subHeadingID}
|
||||||
return (
|
id={id}
|
||||||
<Setting
|
>
|
||||||
advanced={advanced}
|
<Form.Control
|
||||||
headingID={headingID}
|
className="input-control"
|
||||||
subHeadingID={subHeadingID}
|
as="select"
|
||||||
id={id}
|
value={value ?? ""}
|
||||||
>
|
onChange={(e) => onChange(e.currentTarget.value)}
|
||||||
<Form.Control
|
>
|
||||||
className="input-control"
|
{children}
|
||||||
as="select"
|
</Form.Control>
|
||||||
value={value ?? ""}
|
</Setting>
|
||||||
onChange={(e) => onChange(e.currentTarget.value)}
|
);
|
||||||
>
|
}
|
||||||
{children}
|
|
||||||
</Form.Control>
|
|
||||||
</Setting>
|
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
interface IDialogSetting<T> extends ISetting {
|
interface IDialogSetting<T> extends ISetting {
|
||||||
buttonText?: string;
|
buttonText?: string;
|
||||||
@@ -208,8 +205,7 @@ interface IDialogSetting<T> extends ISetting {
|
|||||||
renderValue?: (v: T | undefined) => JSX.Element;
|
renderValue?: (v: T | undefined) => JSX.Element;
|
||||||
onChange: () => void;
|
onChange: () => void;
|
||||||
}
|
}
|
||||||
|
const _ChangeButtonSetting = <T extends {}>(props: IDialogSetting<T>) => {
|
||||||
export const ChangeButtonSetting = <T extends {}>(props: IDialogSetting<T>) => {
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
className,
|
className,
|
||||||
@@ -266,6 +262,11 @@ export const ChangeButtonSetting = <T extends {}>(props: IDialogSetting<T>) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ChangeButtonSetting = PatchComponent(
|
||||||
|
"ChangeButtonSetting",
|
||||||
|
_ChangeButtonSetting
|
||||||
|
) as typeof _ChangeButtonSetting;
|
||||||
|
|
||||||
export interface ISettingModal<T> {
|
export interface ISettingModal<T> {
|
||||||
heading?: React.ReactNode;
|
heading?: React.ReactNode;
|
||||||
headingID?: string;
|
headingID?: string;
|
||||||
@@ -283,7 +284,7 @@ export interface ISettingModal<T> {
|
|||||||
error?: string | undefined;
|
error?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SettingModal = <T extends {}>(props: ISettingModal<T>) => {
|
const _SettingModal = <T extends {}>(props: ISettingModal<T>) => {
|
||||||
const {
|
const {
|
||||||
heading,
|
heading,
|
||||||
headingID,
|
headingID,
|
||||||
@@ -342,6 +343,11 @@ export const SettingModal = <T extends {}>(props: ISettingModal<T>) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SettingModal = PatchComponent(
|
||||||
|
"SettingModal",
|
||||||
|
_SettingModal
|
||||||
|
) as typeof _SettingModal;
|
||||||
|
|
||||||
interface IModalSetting<T> extends ISetting {
|
interface IModalSetting<T> extends ISetting {
|
||||||
value: T | undefined;
|
value: T | undefined;
|
||||||
buttonText?: string;
|
buttonText?: string;
|
||||||
@@ -357,7 +363,7 @@ interface IModalSetting<T> extends ISetting {
|
|||||||
validateChange?: (v: T) => void | undefined;
|
validateChange?: (v: T) => void | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModalSetting = <T extends {}>(props: IModalSetting<T>) => {
|
export const _ModalSetting = <T extends {}>(props: IModalSetting<T>) => {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
className,
|
className,
|
||||||
@@ -435,52 +441,63 @@ export const ModalSetting = <T extends {}>(props: IModalSetting<T>) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ModalSetting = PatchComponent(
|
||||||
|
"ModalSetting",
|
||||||
|
_ModalSetting
|
||||||
|
) as typeof _ModalSetting;
|
||||||
|
|
||||||
interface IStringSetting extends ISetting {
|
interface IStringSetting extends ISetting {
|
||||||
value: string | undefined;
|
value: string | undefined;
|
||||||
onChange: (v: string) => void;
|
onChange: (v: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StringSetting: React.FC<IStringSetting> = (props) => {
|
export const StringSetting: React.FC<IStringSetting> = PatchComponent(
|
||||||
return (
|
"StringSetting",
|
||||||
<ModalSetting<string>
|
(props) => {
|
||||||
{...props}
|
return (
|
||||||
renderField={(value, setValue) => (
|
<ModalSetting<string>
|
||||||
<Form.Control
|
{...props}
|
||||||
className="text-input"
|
renderField={(value, setValue) => (
|
||||||
value={value ?? ""}
|
<Form.Control
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
className="text-input"
|
||||||
setValue(e.currentTarget.value)
|
value={value ?? ""}
|
||||||
}
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
/>
|
setValue(e.currentTarget.value)
|
||||||
)}
|
}
|
||||||
renderValue={(value) => <span>{value}</span>}
|
/>
|
||||||
/>
|
)}
|
||||||
);
|
renderValue={(value) => <span>{value}</span>}
|
||||||
};
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
interface INumberSetting extends ISetting {
|
interface INumberSetting extends ISetting {
|
||||||
value: number | undefined;
|
value: number | undefined;
|
||||||
onChange: (v: number) => void;
|
onChange: (v: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NumberSetting: React.FC<INumberSetting> = (props) => {
|
export const NumberSetting: React.FC<INumberSetting> = PatchComponent(
|
||||||
return (
|
"NumberSetting",
|
||||||
<ModalSetting<number>
|
(props) => {
|
||||||
{...props}
|
return (
|
||||||
renderField={(value, setValue) => (
|
<ModalSetting<number>
|
||||||
<Form.Control
|
{...props}
|
||||||
className="text-input"
|
renderField={(value, setValue) => (
|
||||||
type="number"
|
<Form.Control
|
||||||
value={value ?? 0}
|
className="text-input"
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
type="number"
|
||||||
setValue(Number.parseInt(e.currentTarget.value || "0", 10))
|
value={value ?? 0}
|
||||||
}
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
/>
|
setValue(Number.parseInt(e.currentTarget.value || "0", 10))
|
||||||
)}
|
}
|
||||||
renderValue={(value) => <span>{value}</span>}
|
/>
|
||||||
/>
|
)}
|
||||||
);
|
renderValue={(value) => <span>{value}</span>}
|
||||||
};
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
interface IStringListSetting extends ISetting {
|
interface IStringListSetting extends ISetting {
|
||||||
value: string[] | undefined;
|
value: string[] | undefined;
|
||||||
@@ -488,35 +505,38 @@ interface IStringListSetting extends ISetting {
|
|||||||
onChange: (v: string[]) => void;
|
onChange: (v: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StringListSetting: React.FC<IStringListSetting> = (props) => {
|
export const StringListSetting: React.FC<IStringListSetting> = PatchComponent(
|
||||||
return (
|
"StringListSetting",
|
||||||
<ModalSetting<string[]>
|
(props) => {
|
||||||
{...props}
|
return (
|
||||||
renderField={(value, setValue) => (
|
<ModalSetting<string[]>
|
||||||
<StringListInput
|
{...props}
|
||||||
value={value ?? []}
|
renderField={(value, setValue) => (
|
||||||
setValue={setValue}
|
<StringListInput
|
||||||
placeholder={props.defaultNewValue}
|
value={value ?? []}
|
||||||
/>
|
setValue={setValue}
|
||||||
)}
|
placeholder={props.defaultNewValue}
|
||||||
renderValue={(value) => (
|
/>
|
||||||
<div>
|
)}
|
||||||
{value?.map((v, i) => (
|
renderValue={(value) => (
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
<div>
|
||||||
<div key={i}>{v}</div>
|
{value?.map((v, i) => (
|
||||||
))}
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
</div>
|
<div key={i}>{v}</div>
|
||||||
)}
|
))}
|
||||||
/>
|
</div>
|
||||||
);
|
)}
|
||||||
};
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
interface IConstantSetting<T> extends ISetting {
|
interface IConstantSetting<T> extends ISetting {
|
||||||
value?: T;
|
value?: T;
|
||||||
renderValue?: (v: T | undefined) => JSX.Element;
|
renderValue?: (v: T | undefined) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConstantSetting = <T extends {}>(props: IConstantSetting<T>) => {
|
export const _ConstantSetting = <T extends {}>(props: IConstantSetting<T>) => {
|
||||||
const { id, headingID, subHeading, subHeadingID, renderValue, value } = props;
|
const { id, headingID, subHeading, subHeadingID, renderValue, value } = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
@@ -539,3 +559,8 @@ export const ConstantSetting = <T extends {}>(props: IConstantSetting<T>) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ConstantSetting = PatchComponent(
|
||||||
|
"ConstantSetting",
|
||||||
|
_ConstantSetting
|
||||||
|
) as typeof _ConstantSetting;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
InstalledPluginPackages,
|
InstalledPluginPackages,
|
||||||
} from "./PluginPackageManager";
|
} from "./PluginPackageManager";
|
||||||
import { ExternalLink } from "../Shared/ExternalLink";
|
import { ExternalLink } from "../Shared/ExternalLink";
|
||||||
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
interface IPluginSettingProps {
|
interface IPluginSettingProps {
|
||||||
pluginID: string;
|
pluginID: string;
|
||||||
@@ -75,11 +76,38 @@ const PluginSetting: React.FC<IPluginSettingProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PluginSettings: React.FC<{
|
||||||
|
pluginID: string;
|
||||||
|
settings: GQL.PluginSetting[];
|
||||||
|
}> = PatchComponent("PluginSettings", ({ pluginID, settings }) => {
|
||||||
|
const { plugins, savePluginSettings } = useSettings();
|
||||||
|
const pluginSettings = plugins[pluginID] ?? {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="plugin-settings">
|
||||||
|
{settings.map((setting) => (
|
||||||
|
<PluginSetting
|
||||||
|
key={setting.name}
|
||||||
|
pluginID={pluginID}
|
||||||
|
setting={setting}
|
||||||
|
value={pluginSettings[setting.name]}
|
||||||
|
onChange={(v) =>
|
||||||
|
savePluginSettings(pluginID, {
|
||||||
|
...pluginSettings,
|
||||||
|
[setting.name]: v,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export const SettingsPluginsPanel: React.FC = () => {
|
export const SettingsPluginsPanel: React.FC = () => {
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { loading: configLoading, plugins, savePluginSettings } = useSettings();
|
const { loading: configLoading } = useSettings();
|
||||||
const { data, loading } = usePlugins();
|
const { data, loading } = usePlugins();
|
||||||
|
|
||||||
const [changedPluginID, setChangedPluginID] = React.useState<
|
const [changedPluginID, setChangedPluginID] = React.useState<
|
||||||
@@ -163,7 +191,10 @@ export const SettingsPluginsPanel: React.FC = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{renderPluginHooks(plugin.hooks ?? undefined)}
|
{renderPluginHooks(plugin.hooks ?? undefined)}
|
||||||
{renderPluginSettings(plugin.id, plugin.settings ?? [])}
|
<PluginSettings
|
||||||
|
pluginID={plugin.id}
|
||||||
|
settings={plugin.settings ?? []}
|
||||||
|
/>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -208,37 +239,8 @@ export const SettingsPluginsPanel: React.FC = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderPluginSettings(
|
|
||||||
pluginID: string,
|
|
||||||
settings: GQL.PluginSetting[]
|
|
||||||
) {
|
|
||||||
const pluginSettings = plugins[pluginID] ?? {};
|
|
||||||
|
|
||||||
return settings.map((setting) => (
|
|
||||||
<PluginSetting
|
|
||||||
key={setting.name}
|
|
||||||
pluginID={pluginID}
|
|
||||||
setting={setting}
|
|
||||||
value={pluginSettings[setting.name]}
|
|
||||||
onChange={(v) =>
|
|
||||||
savePluginSettings(pluginID, {
|
|
||||||
...pluginSettings,
|
|
||||||
[setting.name]: v,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return renderPlugins();
|
return renderPlugins();
|
||||||
}, [
|
}, [data?.plugins, intl, Toast, changedPluginID]);
|
||||||
data?.plugins,
|
|
||||||
intl,
|
|
||||||
Toast,
|
|
||||||
changedPluginID,
|
|
||||||
plugins,
|
|
||||||
savePluginSettings,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (loading || configLoading) return <LoadingIndicator />;
|
if (loading || configLoading) return <LoadingIndicator />;
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,11 @@ Returns `void`.
|
|||||||
|
|
||||||
#### Patchable components and functions
|
#### Patchable components and functions
|
||||||
|
|
||||||
|
- `App`
|
||||||
|
- `BooleanSetting`
|
||||||
|
- `ChangeButtonSetting`
|
||||||
|
- `CompressedPerformerDetailsPanel`
|
||||||
|
- `ConstantSetting`
|
||||||
- `CountrySelect`
|
- `CountrySelect`
|
||||||
- `DateInput`
|
- `DateInput`
|
||||||
- `FolderSelect`
|
- `FolderSelect`
|
||||||
@@ -146,9 +151,13 @@ Returns `void`.
|
|||||||
- `GallerySelect`
|
- `GallerySelect`
|
||||||
- `GallerySelect.sort`
|
- `GallerySelect.sort`
|
||||||
- `Icon`
|
- `Icon`
|
||||||
|
- `ModalSetting`
|
||||||
- `MovieIDSelect`
|
- `MovieIDSelect`
|
||||||
- `MovieSelect`
|
- `MovieSelect`
|
||||||
- `MovieSelect.sort`
|
- `MovieSelect.sort`
|
||||||
|
- `NumberSetting`
|
||||||
|
- `PerformerDetailsPanel`
|
||||||
|
- `PerformerDetailsPanel.DetailGroup`
|
||||||
- `PerformerIDSelect`
|
- `PerformerIDSelect`
|
||||||
- `PerformerSelect`
|
- `PerformerSelect`
|
||||||
- `PerformerSelect.sort`
|
- `PerformerSelect.sort`
|
||||||
@@ -161,13 +170,20 @@ Returns `void`.
|
|||||||
- `SceneIDSelect`
|
- `SceneIDSelect`
|
||||||
- `SceneSelect`
|
- `SceneSelect`
|
||||||
- `SceneSelect.sort`
|
- `SceneSelect.sort`
|
||||||
|
- `SelectSetting`
|
||||||
- `Setting`
|
- `Setting`
|
||||||
|
- `SettingModal`
|
||||||
|
- `StringSetting`
|
||||||
|
- `StringListSetting`
|
||||||
- `StudioIDSelect`
|
- `StudioIDSelect`
|
||||||
- `StudioSelect`
|
- `StudioSelect`
|
||||||
- `StudioSelect.sort`
|
- `StudioSelect.sort`
|
||||||
- `TagIDSelect`
|
- `TagIDSelect`
|
||||||
- `TagSelect`
|
- `TagSelect`
|
||||||
- `TagSelect.sort`
|
- `TagSelect.sort`
|
||||||
|
- `PluginSettings`
|
||||||
|
- `Setting`
|
||||||
|
- `SettingGroup`
|
||||||
|
|
||||||
### `PluginApi.Event`
|
### `PluginApi.Event`
|
||||||
|
|
||||||
|
|||||||
41
ui/v2.5/src/pluginApi.d.ts
vendored
41
ui/v2.5/src/pluginApi.d.ts
vendored
@@ -681,7 +681,18 @@ declare namespace PluginApi {
|
|||||||
"SceneCard.Details": React.FC<any>;
|
"SceneCard.Details": React.FC<any>;
|
||||||
"SceneCard.Overlays": React.FC<any>;
|
"SceneCard.Overlays": React.FC<any>;
|
||||||
"SceneCard.Image": React.FC<any>;
|
"SceneCard.Image": React.FC<any>;
|
||||||
SceneCard: React.FC<any>;
|
PluginSettings: React.FC<any>;
|
||||||
|
Setting: React.FC<any>;
|
||||||
|
SettingGroup: React.FC<any>;
|
||||||
|
BooleanSetting: React.FC<any>;
|
||||||
|
SelectSetting: React.FC<any>;
|
||||||
|
ChangeButtonSetting: React.FC<any>;
|
||||||
|
SettingModal: React.FC<any>;
|
||||||
|
ModalSetting: React.FC<any>;
|
||||||
|
StringSetting: React.FC<any>;
|
||||||
|
NumberSetting: React.FC<any>;
|
||||||
|
StringListSetting: React.FC<any>;
|
||||||
|
ConstantSetting: React.FC<any>;
|
||||||
};
|
};
|
||||||
namespace utils {
|
namespace utils {
|
||||||
namespace NavUtils {
|
namespace NavUtils {
|
||||||
@@ -922,6 +933,34 @@ declare namespace PluginApi {
|
|||||||
success(message: JSX.Element | string): void;
|
success(message: JSX.Element | string): void;
|
||||||
error(error: unknown): void;
|
error(error: unknown): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function useSettings(): {
|
||||||
|
loading: boolean;
|
||||||
|
error: any | undefined;
|
||||||
|
general: any;
|
||||||
|
interface: any;
|
||||||
|
defaults: any;
|
||||||
|
scraping: any;
|
||||||
|
dlna: any;
|
||||||
|
ui: any;
|
||||||
|
plugins: any;
|
||||||
|
|
||||||
|
advancedMode: boolean;
|
||||||
|
|
||||||
|
// apikey isn't directly settable, so expose it here
|
||||||
|
apiKey: string;
|
||||||
|
|
||||||
|
saveGeneral: (input: any) => void;
|
||||||
|
saveInterface: (input: any) => void;
|
||||||
|
saveDefaults: (input: any) => void;
|
||||||
|
saveScraping: (input: any) => void;
|
||||||
|
saveDLNA: (input: any) => void;
|
||||||
|
saveUI: (input: any) => void;
|
||||||
|
savePluginSettings: (pluginID: string, input: {}) => void;
|
||||||
|
setAdvancedMode: (value: boolean) => void;
|
||||||
|
|
||||||
|
refetch: () => void;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
namespace patch {
|
namespace patch {
|
||||||
function before(target: string, fn: Function): void;
|
function before(target: string, fn: Function): void;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ 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";
|
||||||
import { before, instead, after, components, RegisterComponent } from "./patch";
|
import { before, instead, after, components, RegisterComponent } from "./patch";
|
||||||
|
import { useSettings } from "./components/Settings/context";
|
||||||
|
|
||||||
// due to code splitting, some components may not have been loaded when a plugin
|
// due to code splitting, some components may not have been loaded when a plugin
|
||||||
// page is loaded. This function will load all components passed to it.
|
// page is loaded. This function will load all components passed to it.
|
||||||
@@ -92,6 +93,7 @@ export const PluginApi = {
|
|||||||
useLoadComponents,
|
useLoadComponents,
|
||||||
useSpriteInfo,
|
useSpriteInfo,
|
||||||
useToast,
|
useToast,
|
||||||
|
useSettings,
|
||||||
},
|
},
|
||||||
patch: {
|
patch: {
|
||||||
// intercept the arguments of supported functions
|
// intercept the arguments of supported functions
|
||||||
|
|||||||
Reference in New Issue
Block a user