mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-17 00:30:04 +00:00
Refactor and extend the plugin MANIFEST.MF parser
Redesign the PluginDetailsModal
This commit is contained in:
Generated
+1233
-3
File diff suppressed because it is too large
Load Diff
@@ -43,8 +43,10 @@
|
|||||||
"react-aria-components": "^1.7.1",
|
"react-aria-components": "^1.7.1",
|
||||||
"react-confetti-boom": "^1.0.0",
|
"react-confetti-boom": "^1.0.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
|
"react-markdown": "^10.1.0",
|
||||||
"react-player": "^2.16.0",
|
"react-player": "^2.16.0",
|
||||||
"react-router": "7.5.2",
|
"react-router": "7.5.2",
|
||||||
|
"remark-breaks": "^4.0.0",
|
||||||
"swiper": "^11.2.6",
|
"swiper": "^11.2.6",
|
||||||
"yup": "^1.6.1"
|
"yup": "^1.6.1"
|
||||||
},
|
},
|
||||||
@@ -132,7 +134,9 @@
|
|||||||
"rand-seed": "$rand-seed",
|
"rand-seed": "$rand-seed",
|
||||||
"react-router": "$react-router",
|
"react-router": "$react-router",
|
||||||
"swiper": "$swiper",
|
"swiper": "$swiper",
|
||||||
"react-player": "$react-player"
|
"react-player": "$react-player",
|
||||||
|
"react-markdown": "$react-markdown",
|
||||||
|
"remark-breaks": "$remark-breaks"
|
||||||
},
|
},
|
||||||
"vaadin": {
|
"vaadin": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -193,6 +197,6 @@
|
|||||||
"workbox-core": "7.3.0",
|
"workbox-core": "7.3.0",
|
||||||
"workbox-precaching": "7.3.0"
|
"workbox-precaching": "7.3.0"
|
||||||
},
|
},
|
||||||
"hash": "b7f2b9b343406ec77ce90fe42104642df3b7205f522d2cc60db71051097a32de"
|
"hash": "f66bddf01ef8dd09a431102050ddd561b4587fdd43bcbff127b93872febbee92"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import {addToast, Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader} from "@heroui/react";
|
import {addToast, Button, Link, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader} from "@heroui/react";
|
||||||
import {Form, Formik} from "formik";
|
import {Form, Formik} from "formik";
|
||||||
import {PluginConfigEndpoint, PluginManagementEndpoint} from "Frontend/generated/endpoints";
|
import {PluginConfigEndpoint, PluginManagementEndpoint} from "Frontend/generated/endpoints";
|
||||||
import PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/management/PluginDto";
|
import PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/management/PluginDto";
|
||||||
import PluginConfigElement from "Frontend/generated/de/grimsi/gameyfin/pluginapi/core/PluginConfigElement";
|
import PluginConfigElement from "Frontend/generated/de/grimsi/gameyfin/pluginapi/core/PluginConfigElement";
|
||||||
import Input from "Frontend/components/general/input/Input";
|
import Input from "Frontend/components/general/input/Input";
|
||||||
import PluginLogo from "Frontend/components/general/PluginLogo";
|
import PluginLogo from "Frontend/components/general/PluginLogo";
|
||||||
|
import Markdown from "react-markdown";
|
||||||
|
import remarkBreaks from "remark-breaks";
|
||||||
|
|
||||||
interface PluginDetailsModalProps {
|
interface PluginDetailsModalProps {
|
||||||
plugin: PluginDto;
|
plugin: PluginDto;
|
||||||
@@ -58,18 +60,53 @@ export default function PluginDetailsModal({plugin, isOpen, onOpenChange, update
|
|||||||
Plugin configuration for {plugin.name}
|
Plugin configuration for {plugin.name}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<h4 className="text-l font-bold">Details</h4>
|
<div className="flex flex-col text-sm">
|
||||||
<div className="flex flex-row gap-8">
|
<div className="flex flex-row items-center gap-8 mb-4">
|
||||||
<PluginLogo plugin={plugin}/>
|
<PluginLogo plugin={plugin}/>
|
||||||
<div className="grid grid-cols-2">
|
<table className="text-left table-auto">
|
||||||
<p>Author: {plugin.author}</p>
|
<tbody>
|
||||||
<p>Version: {plugin.version}</p>
|
{Object.entries({
|
||||||
<p>Plugin ID: {plugin.id}</p>
|
"Author": plugin.author,
|
||||||
<p>Status: {plugin.state?.toLowerCase()}</p>
|
"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-foreground/60 w-0 min-w-20">{key}</td>
|
||||||
|
<td className="flex flex-row gap-1">{value}</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-foreground/60">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>
|
||||||
|
|
||||||
<h4 className="text-l font-bold mt-6">Configuration</h4>
|
<h4 className="text-l font-bold mt-4">Configuration</h4>
|
||||||
{(pluginConfigMeta && pluginConfigMeta.length > 0) ?
|
{(pluginConfigMeta && pluginConfigMeta.length > 0) ?
|
||||||
pluginConfigMeta.map((entry: PluginConfigElement) => (
|
pluginConfigMeta.map((entry: PluginConfigElement) => (
|
||||||
<Input key={entry.key} name={entry.key} label={entry.name}
|
<Input key={entry.key} name={entry.key} label={entry.name}
|
||||||
|
|||||||
+3
-5
@@ -10,13 +10,11 @@ class DatabasePluginStatusProvider(
|
|||||||
) : PluginStatusProvider {
|
) : PluginStatusProvider {
|
||||||
|
|
||||||
override fun isPluginDisabled(pluginId: String): Boolean {
|
override fun isPluginDisabled(pluginId: String): Boolean {
|
||||||
var pluginManagement = pluginManagementRepository.findByIdOrNull(pluginId)
|
val pluginManagement = pluginManagementRepository.findByIdOrNull(pluginId)
|
||||||
|
|
||||||
if (pluginManagement == null) {
|
if (pluginManagement == null) return true
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return pluginManagement.enabled != true
|
return !pluginManagement.enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disablePlugin(pluginId: String) {
|
override fun disablePlugin(pluginId: String) {
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package de.grimsi.gameyfin.core.plugins.management
|
||||||
|
|
||||||
|
import org.pf4j.ManifestPluginDescriptorFinder
|
||||||
|
import java.util.jar.Manifest
|
||||||
|
|
||||||
|
class GameyfinManifestPluginDescriptorFinder() : ManifestPluginDescriptorFinder() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PLUGIN_NAME: String = "Plugin-Name"
|
||||||
|
const val PLUGIN_AUTHOR: String = "Plugin-Author"
|
||||||
|
const val PLUGIN_URL: String = "Plugin-Url"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createPluginDescriptor(manifest: Manifest?): GameyfinPluginDescriptor {
|
||||||
|
if (manifest == null) throw IllegalArgumentException("Manifest cannot be null")
|
||||||
|
|
||||||
|
val pluginDescriptor = super.createPluginDescriptor(manifest)
|
||||||
|
|
||||||
|
val attributes = manifest.mainAttributes
|
||||||
|
|
||||||
|
return GameyfinPluginDescriptor(
|
||||||
|
descriptor = pluginDescriptor,
|
||||||
|
name = attributes.getValue(PLUGIN_NAME)
|
||||||
|
?: throw IllegalStateException("Plugin-Name not found in manifest"),
|
||||||
|
author = attributes.getValue(PLUGIN_AUTHOR)
|
||||||
|
?: throw IllegalStateException("Plugin-Author not found in manifest"),
|
||||||
|
url = attributes.getValue(PLUGIN_URL) ?: "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package de.grimsi.gameyfin.core.plugins.management
|
||||||
|
|
||||||
|
import org.pf4j.DefaultPluginDescriptor
|
||||||
|
import org.pf4j.PluginDescriptor
|
||||||
|
|
||||||
|
data class GameyfinPluginDescriptor(
|
||||||
|
var pluginUrl: String,
|
||||||
|
var pluginName: String,
|
||||||
|
var author: String
|
||||||
|
) : DefaultPluginDescriptor() {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
descriptor: PluginDescriptor,
|
||||||
|
url: String,
|
||||||
|
name: String,
|
||||||
|
author: String
|
||||||
|
) : this(
|
||||||
|
pluginUrl = url,
|
||||||
|
pluginName = name,
|
||||||
|
author = author
|
||||||
|
) {
|
||||||
|
this.pluginId = descriptor.pluginId
|
||||||
|
// The Manifest parser ignores newlines in the description
|
||||||
|
this.pluginDescription = descriptor.pluginDescription.replace("<br>", "\n")
|
||||||
|
this.pluginClass = descriptor.pluginClass
|
||||||
|
this.setPluginVersion(descriptor.version)
|
||||||
|
this.requires = descriptor.requires
|
||||||
|
this.license = descriptor.license
|
||||||
|
|
||||||
|
// Use reflection to access the private 'dependencies' field
|
||||||
|
// This is because the internal (List<PluginDependency>) and external (List<String>) representation of the field differ
|
||||||
|
this.javaClass.superclass.getDeclaredField("dependencies").let {
|
||||||
|
it.isAccessible = true
|
||||||
|
it.set(this, descriptor.dependencies)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getProvider() = author
|
||||||
|
}
|
||||||
+10
-1
@@ -62,8 +62,17 @@ class GameyfinPluginManager(
|
|||||||
return dbPluginStatusProvider
|
return dbPluginStatusProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createPluginDescriptorFinder(): PluginDescriptorFinder {
|
||||||
|
return GameyfinManifestPluginDescriptorFinder()
|
||||||
|
}
|
||||||
|
|
||||||
override fun loadPluginFromPath(pluginPath: Path?): PluginWrapper? {
|
override fun loadPluginFromPath(pluginPath: Path?): PluginWrapper? {
|
||||||
val pluginWrapper = super.loadPluginFromPath(pluginPath)
|
val pluginWrapper = try {
|
||||||
|
super.loadPluginFromPath(pluginPath)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error { "Failed to load plugin $pluginPath: ${e.message}" }
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
if (pluginWrapper == null || pluginPath == null) return null
|
if (pluginWrapper == null || pluginPath == null) return null
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import org.pf4j.PluginState
|
|||||||
data class PluginDto(
|
data class PluginDto(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
val description: String,
|
||||||
val version: String,
|
val version: String,
|
||||||
val author: String,
|
val author: String,
|
||||||
|
val license: String? = null,
|
||||||
|
val url: String? = null,
|
||||||
val hasLogo: Boolean,
|
val hasLogo: Boolean,
|
||||||
val state: PluginState,
|
val state: PluginState,
|
||||||
val priority: Int,
|
val priority: Int,
|
||||||
val trustLevel: PluginTrustLevel
|
val trustLevel: PluginTrustLevel,
|
||||||
)
|
)
|
||||||
+13
-8
@@ -87,15 +87,20 @@ class PluginManagementService(
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val descriptor = pluginWrapper.descriptor as GameyfinPluginDescriptor
|
||||||
|
|
||||||
return PluginDto(
|
return PluginDto(
|
||||||
pluginWrapper.pluginId,
|
id = descriptor.pluginId,
|
||||||
pluginWrapper.descriptor.pluginDescription,
|
name = descriptor.pluginName,
|
||||||
pluginWrapper.descriptor.version,
|
description = descriptor.pluginDescription,
|
||||||
pluginWrapper.descriptor.provider,
|
version = descriptor.version,
|
||||||
hasLogo,
|
author = descriptor.author,
|
||||||
pluginWrapper.pluginState,
|
license = descriptor.license,
|
||||||
pluginManagementEntry.priority,
|
url = descriptor.pluginUrl,
|
||||||
pluginManagementEntry.trustLevel
|
hasLogo = hasLogo,
|
||||||
|
state = pluginWrapper.pluginState,
|
||||||
|
priority = pluginManagementEntry.priority,
|
||||||
|
trustLevel = pluginManagementEntry.trustLevel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -3,7 +3,7 @@ package de.grimsi.gameyfin.pluginapi.gamemetadata
|
|||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
class GameMetadata(
|
data class GameMetadata(
|
||||||
val originalId: String,
|
val originalId: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val description: String? = null,
|
val description: String? = null,
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
Manifest-Version: 1.0
|
Plugin-Version: 1.0.0-alpha6
|
||||||
Plugin-Class: de.grimsi.gameyfin.plugins.igdb.IgdbPlugin
|
Plugin-Class: de.grimsi.gameyfin.plugins.igdb.IgdbPlugin
|
||||||
Plugin-Id: igdb
|
Plugin-Id: de.grimsi.gameyfin.igdb
|
||||||
Plugin-Description: IGDB Metadata
|
Plugin-Name: IGDB Metadata
|
||||||
Plugin-Version: 1.0.0-alpha5
|
Plugin-Description: Fetches metadata from IGDB.<br>
|
||||||
Plugin-Provider: grimsi
|
Requires a Twitch account and IGDB API credentials.<br>
|
||||||
|
Details see in the [IGDB API docs](https://api-docs.igdb.com/#account-creation).
|
||||||
|
Plugin-Author: grimsi
|
||||||
|
Plugin-License: MIT
|
||||||
|
Plugin-Url: https://github.com/gameyfin
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
Manifest-Version: 1.0
|
Plugin-Version: 1.0.0-alpha7
|
||||||
Plugin-Class: de.grimsi.gameyfin.plugins.steam.SteamPlugin
|
Plugin-Class: de.grimsi.gameyfin.plugins.steam.SteamPlugin
|
||||||
Plugin-Id: steam
|
Plugin-Id: de.grimsi.gameyfin.steam
|
||||||
Plugin-Description: Steam Metadata
|
Plugin-Name: Steam Metadata
|
||||||
Plugin-Version: 1.0.0-alpha6
|
Plugin-Description: Fetches metadata from Steam using undocumented public API endpoints.<br>
|
||||||
Plugin-Provider: grimsi
|
This is more of a proof of concept and is prone to breaking when the Steam API changes.
|
||||||
|
Plugin-Author: grimsi
|
||||||
|
Plugin-License: MIT
|
||||||
|
Plugin-Url: https://github.com/gameyfin
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
Manifest-Version: 1.0
|
Plugin-Version: 1.0.0-alpha3
|
||||||
Plugin-Class: de.grimsi.gameyfin.plugins.steamgriddb.SteamGridDbPlugin
|
Plugin-Class: de.grimsi.gameyfin.plugins.steamgriddb.SteamGridDbPlugin
|
||||||
Plugin-Id: steamgriddb
|
Plugin-Id: de.grimsi.gameyfin.steamgriddb
|
||||||
Plugin-Description: SteamGridDB covers
|
Plugin-Name: SteamGridDB Covers
|
||||||
Plugin-Version: 1.0.0-alpha2
|
Plugin-Description: Fetches covers from SteamGridDB.<br>
|
||||||
Plugin-Provider: grimsi
|
Requires a SteamGridDB account and an API key.<br>
|
||||||
|
The API key can be obtained [here](https://www.steamgriddb.com/profile/preferences/api).
|
||||||
|
Plugin-Author: grimsi
|
||||||
|
Plugin-License: MIT
|
||||||
|
Plugin-Url: https://github.com/gameyfin
|
||||||
|
|||||||
Reference in New Issue
Block a user