mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 08:15:48 +00:00
Cast plugin config values in frontend
This commit is contained in:
@@ -8,7 +8,7 @@ import {PluginEndpoint} from "Frontend/generated/endpoints";
|
|||||||
import PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/dto/PluginDto";
|
import PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/dto/PluginDto";
|
||||||
import {ArrowClockwise} from "@phosphor-icons/react";
|
import {ArrowClockwise} from "@phosphor-icons/react";
|
||||||
import PluginConfigMetadataDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/dto/PluginConfigMetadataDto";
|
import PluginConfigMetadataDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/dto/PluginConfigMetadataDto";
|
||||||
import PluginConfigFormField from "Frontend/components/general/input/PluginConfigFormField";
|
import PluginConfigFormField from "Frontend/components/general/plugin/PluginConfigFormField";
|
||||||
|
|
||||||
interface PluginDetailsModalProps {
|
interface PluginDetailsModalProps {
|
||||||
plugin: PluginDto;
|
plugin: PluginDto;
|
||||||
@@ -35,18 +35,26 @@ export default function PluginDetailsModal({plugin, isOpen, onOpenChange}: Plugi
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEffectiveConfig(): Record<string, string> {
|
function getEffectiveConfig(): Record<string, any> {
|
||||||
const effectiveConfig: Record<string, string> = {};
|
const effectiveConfig: Record<string, any> = {};
|
||||||
if (!plugin.configMetadata) return effectiveConfig;
|
if (!plugin.configMetadata) return effectiveConfig;
|
||||||
|
|
||||||
for (const meta of plugin.configMetadata) {
|
for (const meta of plugin.configMetadata) {
|
||||||
const key = meta.key;
|
const key = meta.key;
|
||||||
let value = plugin.config?.[key]?.toString();
|
let value = plugin.config?.[key] ?? meta.default;
|
||||||
if (value == null && meta.default != null) {
|
|
||||||
value = meta.default.toString();
|
if (value != null) {
|
||||||
}
|
switch (meta.type.toLowerCase()) {
|
||||||
if (value) {
|
case "float":
|
||||||
effectiveConfig[key] = value;
|
case "int":
|
||||||
|
effectiveConfig[key] = Number(value);
|
||||||
|
break;
|
||||||
|
case "boolean":
|
||||||
|
effectiveConfig[key] = value === true || value === "true";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
effectiveConfig[key] = value.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return effectiveConfig;
|
return effectiveConfig;
|
||||||
@@ -55,132 +63,138 @@ export default function PluginDetailsModal({plugin, isOpen, onOpenChange}: Plugi
|
|||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange} backdrop="opaque" size="lg">
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange} backdrop="opaque" size="lg">
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
{(onClose) => (
|
{(onClose) => {
|
||||||
<Formik initialValues={getEffectiveConfig()}
|
|
||||||
initialErrors={plugin.configValidation?.errors}
|
|
||||||
enableReinitialize={true}
|
|
||||||
onSubmit={async (values: any) => {
|
|
||||||
await saveConfig(values);
|
|
||||||
onClose();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(formik: any) => (
|
|
||||||
<Form>
|
|
||||||
<ModalHeader className="flex flex-col gap-1">
|
|
||||||
Plugin configuration for {plugin.name}
|
|
||||||
</ModalHeader>
|
|
||||||
<ModalBody>
|
|
||||||
<div className="flex flex-col text-sm">
|
|
||||||
<div className="flex flex-row items-center gap-8 mb-4">
|
|
||||||
<PluginLogo plugin={plugin}/>
|
|
||||||
<table className="text-left table-auto">
|
|
||||||
<tbody>
|
|
||||||
{Object.entries({
|
|
||||||
"Author": plugin.author,
|
|
||||||
"Version": plugin.version,
|
|
||||||
"License": plugin.license,
|
|
||||||
"URL": <Link isExternal
|
|
||||||
showAnchorIcon
|
|
||||||
color="foreground"
|
|
||||||
size="sm"
|
|
||||||
href={plugin.url}>
|
|
||||||
{plugin.url}
|
|
||||||
</Link>,
|
|
||||||
}).map(([key, value]) => {
|
|
||||||
if (!value) return;
|
|
||||||
return (
|
|
||||||
<tr key={key}>
|
|
||||||
<td className="text-default-500 w-0 min-w-20">{key}</td>
|
|
||||||
<td className="flex flex-row gap-1">{value}</td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<p className="text-default-500">Description</p>
|
|
||||||
<Markdown
|
|
||||||
remarkPlugins={[remarkBreaks]}
|
|
||||||
components={{
|
|
||||||
a(props) {
|
|
||||||
return <Link isExternal
|
|
||||||
showAnchorIcon
|
|
||||||
color="foreground"
|
|
||||||
underline="always"
|
|
||||||
href={props.href}
|
|
||||||
size="sm">
|
|
||||||
{props.children}
|
|
||||||
</Link>
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>{plugin.description}</Markdown>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-row items-center mt-4 gap-2">
|
async function handleSubmit(values: Record<string, string>): Promise<void> {
|
||||||
<h4 className="text-l font-bold">Configuration</h4>
|
await saveConfig(values);
|
||||||
{(plugin.configMetadata && plugin.configMetadata.length > 0) && <>
|
onClose();
|
||||||
<div className="flex-1"/>
|
}
|
||||||
{(() => {
|
|
||||||
switch (configValidated) {
|
return (
|
||||||
case ValidationState.VALID:
|
<Formik initialValues={getEffectiveConfig()}
|
||||||
return <p className="text-small text-success">
|
initialErrors={plugin.configValidation?.errors}
|
||||||
Configuration valid
|
enableReinitialize={true}
|
||||||
</p>;
|
onSubmit={handleSubmit}
|
||||||
case ValidationState.INVALID:
|
>
|
||||||
return <p className="text-small text-danger">
|
{(formik: any) => (
|
||||||
Configuration invalid
|
<Form>
|
||||||
</p>;
|
<ModalHeader className="flex flex-col gap-1">
|
||||||
default:
|
Plugin configuration for {plugin.name}
|
||||||
return null;
|
</ModalHeader>
|
||||||
}
|
<ModalBody>
|
||||||
})()}
|
<div className="flex flex-col text-sm">
|
||||||
<Tooltip content="Re-validate configuration" placement="bottom"
|
<div className="flex flex-row items-center gap-8 mb-4">
|
||||||
color="foreground">
|
<PluginLogo plugin={plugin}/>
|
||||||
<Button isIconOnly variant="light" size="sm"
|
<table className="text-left table-auto">
|
||||||
isLoading={configValidated === ValidationState.IN_PROGRESS}
|
<tbody>
|
||||||
onPress={async () => {
|
{Object.entries({
|
||||||
setConfigValidated(ValidationState.IN_PROGRESS);
|
"Author": plugin.author,
|
||||||
let result = await PluginEndpoint.validateNewConfig(plugin.id, formik.values)
|
"Version": plugin.version,
|
||||||
if (result.errors) {
|
"License": plugin.license,
|
||||||
formik.setErrors(result.errors);
|
"URL": <Link isExternal
|
||||||
setConfigValidated(ValidationState.INVALID);
|
showAnchorIcon
|
||||||
} else {
|
color="foreground"
|
||||||
setConfigValidated(ValidationState.VALID);
|
size="sm"
|
||||||
}
|
href={plugin.url}>
|
||||||
setTimeout(() => setConfigValidated(ValidationState.UNCHECKED), 5000);
|
{plugin.url}
|
||||||
}}>
|
</Link>,
|
||||||
<ArrowClockwise/>
|
}).map(([key, value]) => {
|
||||||
</Button>
|
if (!value) return;
|
||||||
</Tooltip>
|
return (
|
||||||
</>}
|
<tr key={key}>
|
||||||
</div>
|
<td className="text-default-500 w-0 min-w-20">{key}</td>
|
||||||
{(plugin.configMetadata && plugin.configMetadata.length > 0) ?
|
<td className="flex flex-row gap-1">{value}</td>
|
||||||
plugin.configMetadata.map((entry: PluginConfigMetadataDto) => (
|
</tr>
|
||||||
<PluginConfigFormField
|
)
|
||||||
key={entry.key}
|
})}
|
||||||
pluginConfigMetadata={entry}
|
</tbody>
|
||||||
showErrorUntouched={true}/>
|
</table>
|
||||||
)) : "This plugin has no configuration options."
|
</div>
|
||||||
}
|
<p className="text-default-500">Description</p>
|
||||||
</ModalBody>
|
<Markdown
|
||||||
<ModalFooter>
|
remarkPlugins={[remarkBreaks]}
|
||||||
<Button variant="light" onPress={onClose}>
|
components={{
|
||||||
Cancel
|
a(props) {
|
||||||
</Button>
|
return <Link isExternal
|
||||||
{(plugin.configMetadata && plugin.configMetadata?.length > 0) ?
|
showAnchorIcon
|
||||||
<Button
|
color="foreground"
|
||||||
color="primary"
|
underline="always"
|
||||||
isLoading={formik.isSubmitting}
|
href={props.href}
|
||||||
isDisabled={formik.isSubmitting || !formik.dirty}
|
size="sm">
|
||||||
type="submit"
|
{props.children}
|
||||||
>
|
</Link>
|
||||||
{formik.isSubmitting ? "" : "Save"}
|
}
|
||||||
</Button> : ""}
|
}}
|
||||||
</ModalFooter>
|
>{plugin.description}</Markdown>
|
||||||
</Form>
|
</div>
|
||||||
)}
|
|
||||||
</Formik>
|
<div className="flex flex-row items-center mt-4 gap-2">
|
||||||
)}
|
<h4 className="text-l font-bold">Configuration</h4>
|
||||||
|
{(plugin.configMetadata && plugin.configMetadata.length > 0) && <>
|
||||||
|
<div className="flex-1"/>
|
||||||
|
{(() => {
|
||||||
|
switch (configValidated) {
|
||||||
|
case ValidationState.VALID:
|
||||||
|
return <p className="text-small text-success">
|
||||||
|
Configuration valid
|
||||||
|
</p>;
|
||||||
|
case ValidationState.INVALID:
|
||||||
|
return <p className="text-small text-danger">
|
||||||
|
Configuration invalid
|
||||||
|
</p>;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
<Tooltip content="Re-validate configuration" placement="bottom"
|
||||||
|
color="foreground">
|
||||||
|
<Button isIconOnly variant="light" size="sm"
|
||||||
|
isLoading={configValidated === ValidationState.IN_PROGRESS}
|
||||||
|
onPress={async () => {
|
||||||
|
setConfigValidated(ValidationState.IN_PROGRESS);
|
||||||
|
let result = await PluginEndpoint.validateNewConfig(plugin.id, formik.values)
|
||||||
|
if (result.errors) {
|
||||||
|
formik.setErrors(result.errors);
|
||||||
|
setConfigValidated(ValidationState.INVALID);
|
||||||
|
} else {
|
||||||
|
setConfigValidated(ValidationState.VALID);
|
||||||
|
}
|
||||||
|
setTimeout(() => setConfigValidated(ValidationState.UNCHECKED), 5000);
|
||||||
|
}}>
|
||||||
|
<ArrowClockwise/>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</>}
|
||||||
|
</div>
|
||||||
|
{(plugin.configMetadata && plugin.configMetadata.length > 0) ?
|
||||||
|
plugin.configMetadata.map((entry: PluginConfigMetadataDto) => (
|
||||||
|
<PluginConfigFormField
|
||||||
|
key={entry.key}
|
||||||
|
pluginConfigMetadata={entry}
|
||||||
|
showErrorUntouched={true}/>
|
||||||
|
)) : "This plugin has no configuration options."
|
||||||
|
}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant="light" onPress={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
{(plugin.configMetadata && plugin.configMetadata?.length > 0) ?
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
isLoading={formik.isSubmitting}
|
||||||
|
isDisabled={formik.isSubmitting || !formik.dirty}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{formik.isSubmitting ? "" : "Save"}
|
||||||
|
</Button> : ""}
|
||||||
|
</ModalFooter>
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Formik>
|
||||||
|
)
|
||||||
|
}}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user