mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-13 16:40:01 +00:00
Add priority management to plugins
This commit is contained in:
Generated
+1516
-5770
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@
|
||||
"@material-tailwind/react": "^2.1.10",
|
||||
"@phosphor-icons/react": "^2.1.7",
|
||||
"@polymer/polymer": "3.5.2",
|
||||
"@react-stately/data": "^3.12.2",
|
||||
"@react-types/shared": "^3.28.0",
|
||||
"@vaadin/bundles": "24.6.6",
|
||||
"@vaadin/common-frontend": "0.0.19",
|
||||
@@ -37,6 +38,7 @@
|
||||
"moment-timezone": "^0.5.47",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "18.3.1",
|
||||
"react-aria-components": "^1.7.1",
|
||||
"react-confetti-boom": "^1.0.0",
|
||||
"react-dom": "18.3.1",
|
||||
"react-router-dom": "6.29.0",
|
||||
|
||||
@@ -2,15 +2,26 @@ import React, {useEffect, useState} from "react";
|
||||
import {PluginManagementEndpoint} from "Frontend/generated/endpoints";
|
||||
import PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/management/PluginDto";
|
||||
import {PluginManagementCard} from "Frontend/components/general/PluginManagementCard";
|
||||
import {Divider} from "@heroui/react";
|
||||
import {Button, Divider, Tooltip, useDisclosure} from "@heroui/react";
|
||||
import {ListNumbers} from "@phosphor-icons/react";
|
||||
import PluginPrioritiesModal from "Frontend/components/general/PluginPrioritiesModal";
|
||||
|
||||
export default function PluginManagement() {
|
||||
const [plugins, setPlugins] = useState<PluginDto[]>([]);
|
||||
const pluginPrioritiesModal = useDisclosure();
|
||||
|
||||
useEffect(() => {
|
||||
PluginManagementEndpoint.getPlugins().then((response) => {
|
||||
if (response === undefined) return;
|
||||
setPlugins(response as PluginDto[]);
|
||||
|
||||
let sortedPlugins: PluginDto[] = response
|
||||
.filter(p => !!p)
|
||||
.sort((a: PluginDto, b: PluginDto) => {
|
||||
if (a.name === undefined || b.name === undefined) return 0;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
setPlugins(sortedPlugins);
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -27,6 +38,12 @@ export default function PluginManagement() {
|
||||
|
||||
<div className="flex flex-row flex-grow justify-between mb-8">
|
||||
<h2 className="text-xl font-bold">Metadata</h2>
|
||||
|
||||
<Tooltip color="foreground" placement="left" content="Change plugin order">
|
||||
<Button isIconOnly variant="flat" onPress={pluginPrioritiesModal.onOpen}>
|
||||
<ListNumbers/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-300px gap-4">
|
||||
@@ -40,6 +57,11 @@ export default function PluginManagement() {
|
||||
<h2 className="text-xl font-bold">Notifications</h2>
|
||||
</div>
|
||||
<p>Notification plugins not yet supported.</p>
|
||||
|
||||
<PluginPrioritiesModal plugins={plugins}
|
||||
isOpen={pluginPrioritiesModal.isOpen}
|
||||
onOpenChange={pluginPrioritiesModal.onOpenChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -42,7 +42,7 @@ function UserManagementLayout({getConfig, formik}: any) {
|
||||
message="Automatic user registration for SSO users is disabled"/>
|
||||
}
|
||||
<Tooltip content="Invite new user">
|
||||
<Button isIconOnly variant="faded" onPress={inviteUserModal.onOpen}>
|
||||
<Button isIconOnly variant="flat" onPress={inviteUserModal.onOpen}>
|
||||
<UserPlus/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
.react-aria-ListBoxItem {
|
||||
&[data-dragging] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.react-aria-DropIndicator[data-drop-target] {
|
||||
outline: 1px solid theme('colors.primary');
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import React, {useEffect} from "react";
|
||||
import {addToast, Button, Chip, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader} from "@heroui/react";
|
||||
import {PluginManagementEndpoint} from "Frontend/generated/endpoints";
|
||||
import PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/management/PluginDto";
|
||||
import {ListBox, ListBoxItem, useDragAndDrop} from "react-aria-components";
|
||||
import {CaretUpDown} from "@phosphor-icons/react";
|
||||
import {ListData, useListData} from "@react-stately/data";
|
||||
import './PluginPrioritiesModal.css';
|
||||
|
||||
interface PluginPrioritiesModalProps {
|
||||
plugins: PluginDto[];
|
||||
isOpen: boolean;
|
||||
onOpenChange: () => void;
|
||||
}
|
||||
|
||||
export default function PluginPrioritiesModal({plugins, isOpen, onOpenChange}: PluginPrioritiesModalProps) {
|
||||
|
||||
let sortedPlugins: ListData<PluginDto> = useListData({
|
||||
initialItems: plugins,
|
||||
getKey: (plugin) => plugin.id!!
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
clearSortedPlugins();
|
||||
sortedPlugins.append(...sortPlugins(plugins));
|
||||
}, [plugins]);
|
||||
|
||||
function sortPlugins(plugins: PluginDto[]): PluginDto[] {
|
||||
return [...plugins].sort((a, b) => {
|
||||
if (a.priority === undefined || b.priority === undefined) return 0;
|
||||
return b.priority - a.priority;
|
||||
});
|
||||
}
|
||||
|
||||
function clearSortedPlugins() {
|
||||
const keyList = sortedPlugins.items.map(plugin => plugin.id!!);
|
||||
keyList.forEach(key => sortedPlugins.remove(key));
|
||||
}
|
||||
|
||||
let {dragAndDropHooks} = useDragAndDrop({
|
||||
// @ts-ignore
|
||||
getItems: (keys) =>
|
||||
// @ts-ignore
|
||||
[...keys].map((key) => ({'text/plain': sortedPlugins.getItem(key).name})),
|
||||
onReorder(e) {
|
||||
if (e.keys.has(e.target.key)) {
|
||||
return; // Avoid placing a plugin before or after itself
|
||||
}
|
||||
|
||||
if (e.target.dropPosition === 'before' || e.target.dropPosition === 'on') {
|
||||
sortedPlugins.moveBefore(e.target.key, e.keys);
|
||||
} else if (e.target.dropPosition === 'after') {
|
||||
sortedPlugins.moveAfter(e.target.key, e.keys);
|
||||
}
|
||||
|
||||
// Recalculate priorities
|
||||
sortedPlugins.items.forEach((plugin, index) => {
|
||||
sortedPlugins.update(plugin.id!!, {...plugin, priority: index + 1});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function generatePrioritiesMap(): Record<string, number> {
|
||||
return sortedPlugins.items.reduce((acc, plugin) => {
|
||||
if (plugin.id === undefined || plugin.priority === undefined) return acc;
|
||||
acc[plugin.id] = plugin.priority;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
}
|
||||
|
||||
async function setPluginPriorities(onClose: () => void) {
|
||||
try {
|
||||
const prioritiesMap = generatePrioritiesMap();
|
||||
await PluginManagementEndpoint.setPluginPriorities(prioritiesMap);
|
||||
|
||||
addToast({
|
||||
title: "Plugin order updated",
|
||||
description: "Plugin order have been updated successfully.",
|
||||
color: "success"
|
||||
});
|
||||
onClose();
|
||||
} catch (e) {
|
||||
addToast({
|
||||
title: "Error",
|
||||
description: "An error occurred while updating plugin order.",
|
||||
color: "warning"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange} backdrop="opaque" size="lg">
|
||||
<ModalContent>
|
||||
{(onClose) => (
|
||||
<>
|
||||
<ModalHeader className="flex flex-col gap-1">
|
||||
<p>Edit plugin order</p>
|
||||
<p className="text-small font-normal">Plugins higher on the list are preferred</p>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<ListBox items={sortedPlugins.items}
|
||||
dragAndDropHooks={dragAndDropHooks}
|
||||
className="flex flex-col gap-2">
|
||||
{(plugin: PluginDto) => (
|
||||
<ListBoxItem
|
||||
key={plugin.id}
|
||||
className="flex flex-row p-2 rounded-lg justify-between items-center bg-foreground/5">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<Chip size="sm"
|
||||
color="primary">{sortedPlugins.items.length - plugin.priority + 1}</Chip>
|
||||
<p className="font-normal text-small">{plugin.name}</p>
|
||||
</div>
|
||||
<CaretUpDown/>
|
||||
</ListBoxItem>
|
||||
)}
|
||||
</ListBox>
|
||||
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant="light" onPress={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button color="success" onPress={() => setPluginPriorities(onClose)}>
|
||||
Save
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -7,5 +7,6 @@ data class PluginDto(
|
||||
val name: String,
|
||||
val version: String,
|
||||
val author: String,
|
||||
val state: PluginState
|
||||
val state: PluginState,
|
||||
val priority: Int
|
||||
)
|
||||
+6
@@ -24,4 +24,10 @@ class PluginManagementEndpoint(
|
||||
fun disablePlugin(pluginId: String) = pluginManagementService.disablePlugin(pluginId)
|
||||
|
||||
fun validatePluginConfig(pluginId: String): Boolean = pluginManagementService.validatePluginConfig(pluginId)
|
||||
|
||||
fun setPluginPriority(pluginId: String, priority: Int) =
|
||||
pluginManagementService.setPluginPriority(pluginId, priority)
|
||||
|
||||
fun setPluginPriorities(pluginPriorities: Map<String, Int>) =
|
||||
pluginManagementService.setPluginPriorities(pluginPriorities)
|
||||
}
|
||||
+18
-2
@@ -16,7 +16,8 @@ class PluginManagementService(
|
||||
it.descriptor.pluginDescription,
|
||||
it.descriptor.version,
|
||||
it.descriptor.provider,
|
||||
it.pluginState
|
||||
it.pluginState,
|
||||
getPluginManagementEntry(it.pluginId).priority
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -28,7 +29,8 @@ class PluginManagementService(
|
||||
plugin.descriptor.pluginDescription,
|
||||
plugin.descriptor.version,
|
||||
plugin.descriptor.provider,
|
||||
plugin.pluginState
|
||||
plugin.pluginState,
|
||||
getPluginManagementEntry(pluginId).priority
|
||||
)
|
||||
}
|
||||
|
||||
@@ -66,4 +68,18 @@ class PluginManagementService(
|
||||
fun validatePluginConfig(pluginId: String): Boolean {
|
||||
return pluginManager.validatePluginConfig(pluginId)
|
||||
}
|
||||
|
||||
fun setPluginPriority(pluginId: String, priority: Int) {
|
||||
val pluginManagementEntry = getPluginManagementEntry(pluginId)
|
||||
pluginManagementEntry.priority = priority
|
||||
pluginManagementRepository.save(pluginManagementEntry)
|
||||
}
|
||||
|
||||
fun setPluginPriorities(pluginPriorities: Map<String, Int>) {
|
||||
pluginPriorities.forEach { (pluginId, priority) ->
|
||||
val pluginManagementEntry = getPluginManagementEntry(pluginId)
|
||||
pluginManagementEntry.priority = priority
|
||||
pluginManagementRepository.save(pluginManagementEntry)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user