mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-13 16:40:01 +00:00
Sort plugins by type in frontend
This commit is contained in:
@@ -1,31 +1,11 @@
|
||||
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/cards/PluginManagementCard";
|
||||
import {Button, Divider, Tooltip, useDisclosure} from "@heroui/react";
|
||||
import {ListNumbers} from "@phosphor-icons/react";
|
||||
import PluginPrioritiesModal from "Frontend/components/general/modals/PluginPrioritiesModal";
|
||||
import React from "react";
|
||||
import {Divider} from "@heroui/react";
|
||||
import {PluginManagementSection} from "Frontend/components/general/PluginManagementSection";
|
||||
|
||||
export default function PluginManagement() {
|
||||
const [plugins, setPlugins] = useState<PluginDto[]>([]);
|
||||
const pluginPrioritiesModal = useDisclosure();
|
||||
|
||||
useEffect(() => {
|
||||
PluginManagementEndpoint.getPlugins().then((response) => {
|
||||
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);
|
||||
});
|
||||
}, []);
|
||||
|
||||
function updatePlugin(plugin: PluginDto) {
|
||||
setPlugins(plugins.map(p => p.id === plugin.id ? plugin : p));
|
||||
}
|
||||
// Defined manually for now to control the layout (order of categories)
|
||||
const pluginTypes = ["GameMetadataProvider", "DownloadProvider"];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
@@ -34,34 +14,11 @@ export default function PluginManagement() {
|
||||
</div>
|
||||
<Divider className="mb-4"/>
|
||||
|
||||
<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">
|
||||
{plugins.map((plugin) => <PluginManagementCard plugin={plugin}
|
||||
updatePlugin={updatePlugin}
|
||||
key={plugin.name}/>
|
||||
<div className="flex flex-col gap-8">
|
||||
{pluginTypes.map(type =>
|
||||
<PluginManagementSection key={type} pluginType={type}/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row flex-grow justify-between my-8">
|
||||
<h2 className="text-xl font-bold">Notifications</h2>
|
||||
</div>
|
||||
<p>Notification plugins not yet supported.</p>
|
||||
|
||||
<PluginPrioritiesModal
|
||||
key={plugins.map(p => p.id + p.priority).join(',')} // force re-mount if plugin order changes
|
||||
plugins={[...plugins].sort((a, b) => b.priority - a.priority)}
|
||||
isOpen={pluginPrioritiesModal.isOpen}
|
||||
onOpenChange={pluginPrioritiesModal.onOpenChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import {Button, Tooltip, useDisclosure} from "@heroui/react";
|
||||
import {ListNumbers} from "@phosphor-icons/react";
|
||||
import {PluginManagementCard} from "Frontend/components/general/cards/PluginManagementCard";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/management/PluginDto";
|
||||
import {PluginManagementEndpoint} from "Frontend/generated/endpoints";
|
||||
import PluginPrioritiesModal from "Frontend/components/general/modals/PluginPrioritiesModal";
|
||||
import {camelCaseToTitle} from "Frontend/util/utils";
|
||||
|
||||
interface PluginManagementSectionProps {
|
||||
pluginType: string;
|
||||
}
|
||||
|
||||
export function PluginManagementSection({pluginType}: PluginManagementSectionProps) {
|
||||
const [plugins, setPlugins] = useState<PluginDto[]>([]);
|
||||
const pluginPrioritiesModal = useDisclosure();
|
||||
|
||||
useEffect(() => {
|
||||
PluginManagementEndpoint.getPlugins(pluginType).then((response) => {
|
||||
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);
|
||||
});
|
||||
}, []);
|
||||
|
||||
function updatePlugin(plugin: PluginDto) {
|
||||
setPlugins(plugins.map(p => p.id === plugin.id ? plugin : p));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-row flex-grow justify-between">
|
||||
<h2 className="text-xl font-bold">{camelCaseToTitle(pluginType)}</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">
|
||||
{plugins.map((plugin) => <PluginManagementCard plugin={plugin}
|
||||
updatePlugin={updatePlugin}
|
||||
key={plugin.name}/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<PluginPrioritiesModal
|
||||
key={plugins.map(p => p.id + p.priority).join(',')} // force re-mount if plugin order changes
|
||||
plugins={[...plugins].sort((a, b) => b.priority - a.priority)}
|
||||
isOpen={pluginPrioritiesModal.isOpen}
|
||||
onOpenChange={pluginPrioritiesModal.onOpenChange}
|
||||
/>
|
||||
</div>);
|
||||
}
|
||||
@@ -30,6 +30,12 @@ export function toTitleCase(str: string) {
|
||||
}).join(' ');
|
||||
}
|
||||
|
||||
export function camelCaseToTitle(text: string): string {
|
||||
return text
|
||||
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
||||
.replace(/^./, str => str.toUpperCase());
|
||||
}
|
||||
|
||||
export function roleToColor(role: string) {
|
||||
switch (role) {
|
||||
case "ROLE_SUPERADMIN":
|
||||
|
||||
+15
@@ -175,6 +175,21 @@ class GameyfinPluginManager(
|
||||
return PluginConfigValidationResult.INVALID
|
||||
}
|
||||
|
||||
fun getExtensionTypeClasses(pluginId: String): Set<Class<ExtensionPoint>> {
|
||||
return getExtensionClasses(pluginId)
|
||||
.flatMap { it.interfaces.toList() }
|
||||
.filterIsInstance<Class<ExtensionPoint>>()
|
||||
.toSet()
|
||||
}
|
||||
|
||||
fun getExtensionTypes(pluginId: String): Set<String> {
|
||||
return getExtensionClasses(pluginId)
|
||||
.flatMap { it.interfaces.toList() }
|
||||
.filterIsInstance<Class<ExtensionPoint>>()
|
||||
.map { it.simpleName }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
private fun configurePlugin(pluginWrapper: PluginWrapper) {
|
||||
val plugin = pluginWrapper.plugin
|
||||
if (plugin is Configurable) {
|
||||
|
||||
+5
-1
@@ -10,7 +10,11 @@ import jakarta.annotation.security.RolesAllowed
|
||||
class PluginManagementEndpoint(
|
||||
private val pluginManagementService: PluginManagementService
|
||||
) {
|
||||
fun getPlugins() = pluginManagementService.getPluginDtos()
|
||||
fun getSupportedPluginTypes() = pluginManagementService.getSupportedPluginTypes()
|
||||
|
||||
fun getPlugins(type: String?) = pluginManagementService.getPluginDtos(type)
|
||||
|
||||
fun getPluginsMappedToTypes() = pluginManagementService.getPluginDtosMappedToTypes()
|
||||
|
||||
fun getPlugin(pluginId: String) = pluginManagementService.getPluginDto(pluginId)
|
||||
|
||||
|
||||
+21
-2
@@ -12,8 +12,27 @@ class PluginManagementService(
|
||||
private val pluginManager: GameyfinPluginManager,
|
||||
private val pluginManagementRepository: PluginManagementRepository,
|
||||
) {
|
||||
fun getPluginDtos(): List<PluginDto> {
|
||||
return pluginManager.plugins.map { toDto(it) }
|
||||
|
||||
fun getSupportedPluginTypes(): Set<String> {
|
||||
return pluginManager.plugins
|
||||
.flatMap { pluginManager.getExtensionTypes(it.pluginId) }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
fun getPluginDtos(type: String?): Set<PluginDto> {
|
||||
return pluginManager.plugins
|
||||
.filter { type == null || type in pluginManager.getExtensionTypes(it.pluginId) }
|
||||
.map { toDto(it) }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
fun getPluginDtosMappedToTypes(): Map<String, List<PluginDto>> {
|
||||
return pluginManager.plugins
|
||||
.flatMap { plugin ->
|
||||
val types = pluginManager.getExtensionTypes(plugin.pluginId)
|
||||
types.map { it to toDto(plugin) }
|
||||
}
|
||||
.groupBy({ it.first }, { it.second })
|
||||
}
|
||||
|
||||
fun getPluginDto(pluginId: String): PluginDto {
|
||||
|
||||
Reference in New Issue
Block a user