mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 00:30:02 +00:00
Sort plugins by type in frontend
This commit is contained in:
@@ -1,31 +1,11 @@
|
|||||||
import React, {useEffect, useState} from "react";
|
import React from "react";
|
||||||
import {PluginManagementEndpoint} from "Frontend/generated/endpoints";
|
import {Divider} from "@heroui/react";
|
||||||
import PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/management/PluginDto";
|
import {PluginManagementSection} from "Frontend/components/general/PluginManagementSection";
|
||||||
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";
|
|
||||||
|
|
||||||
export default function PluginManagement() {
|
export default function PluginManagement() {
|
||||||
const [plugins, setPlugins] = useState<PluginDto[]>([]);
|
|
||||||
const pluginPrioritiesModal = useDisclosure();
|
|
||||||
|
|
||||||
useEffect(() => {
|
// Defined manually for now to control the layout (order of categories)
|
||||||
PluginManagementEndpoint.getPlugins().then((response) => {
|
const pluginTypes = ["GameMetadataProvider", "DownloadProvider"];
|
||||||
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 (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
@@ -34,34 +14,11 @@ export default function PluginManagement() {
|
|||||||
</div>
|
</div>
|
||||||
<Divider className="mb-4"/>
|
<Divider className="mb-4"/>
|
||||||
|
|
||||||
<div className="flex flex-row flex-grow justify-between mb-8">
|
<div className="flex flex-col gap-8">
|
||||||
<h2 className="text-xl font-bold">Metadata</h2>
|
{pluginTypes.map(type =>
|
||||||
|
<PluginManagementSection key={type} pluginType={type}/>
|
||||||
<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>
|
</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>
|
</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(' ');
|
}).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) {
|
export function roleToColor(role: string) {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case "ROLE_SUPERADMIN":
|
case "ROLE_SUPERADMIN":
|
||||||
|
|||||||
+15
@@ -175,6 +175,21 @@ class GameyfinPluginManager(
|
|||||||
return PluginConfigValidationResult.INVALID
|
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) {
|
private fun configurePlugin(pluginWrapper: PluginWrapper) {
|
||||||
val plugin = pluginWrapper.plugin
|
val plugin = pluginWrapper.plugin
|
||||||
if (plugin is Configurable) {
|
if (plugin is Configurable) {
|
||||||
|
|||||||
+5
-1
@@ -10,7 +10,11 @@ import jakarta.annotation.security.RolesAllowed
|
|||||||
class PluginManagementEndpoint(
|
class PluginManagementEndpoint(
|
||||||
private val pluginManagementService: PluginManagementService
|
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)
|
fun getPlugin(pluginId: String) = pluginManagementService.getPluginDto(pluginId)
|
||||||
|
|
||||||
|
|||||||
+21
-2
@@ -12,8 +12,27 @@ class PluginManagementService(
|
|||||||
private val pluginManager: GameyfinPluginManager,
|
private val pluginManager: GameyfinPluginManager,
|
||||||
private val pluginManagementRepository: PluginManagementRepository,
|
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 {
|
fun getPluginDto(pluginId: String): PluginDto {
|
||||||
|
|||||||
Reference in New Issue
Block a user