Start library management UI implementation

Minor layout fixes
Minor refactorings
This commit is contained in:
grimsi
2025-03-30 21:54:09 +02:00
parent dd145b466f
commit a589b9dbfb
33 changed files with 81 additions and 32 deletions
@@ -1,8 +1,8 @@
import ConfigEntryDto from "Frontend/generated/de/grimsi/gameyfin/config/dto/ConfigEntryDto";
import React from "react";
import Input from "Frontend/components/general/Input";
import CheckboxInput from "Frontend/components/general/CheckboxInput";
import SelectInput from "Frontend/components/general/SelectInput";
import Input from "Frontend/components/general/input/Input";
import CheckboxInput from "Frontend/components/general/input/CheckboxInput";
import SelectInput from "Frontend/components/general/input/SelectInput";
export default function ConfigFormField({configElement, ...props}: any) {
function inputElement(configElement: ConfigEntryDto) {
@@ -1,16 +1,27 @@
import React from "react";
import React, {useEffect} from "react";
import ConfigFormField from "Frontend/components/administration/ConfigFormField";
import withConfigPage from "Frontend/components/administration/withConfigPage";
import Section from "Frontend/components/general/Section";
import * as Yup from 'yup';
import {Button, Divider, Tooltip} from "@heroui/react";
import {Plus} from "@phosphor-icons/react";
import {LibraryEndpoint} from "Frontend/generated/endpoints";
import LibraryDto from "Frontend/generated/de/grimsi/gameyfin/libraries/LibraryDto";
import {LibraryOverviewCard} from "Frontend/components/general/cards/LibraryOverviewCard";
import {ListData, useListData} from "@react-stately/data";
function LibraryManagementLayout({getConfig, formik}: any) {
const libraries: ListData<LibraryDto> = useListData({});
useEffect(() => {
LibraryEndpoint.getAllLibraries().then(
(response) => libraries.items = response as LibraryDto[]
);
}, []);
return (
<div className="flex flex-col">
<Section title="Library"/>
{/* TODO */}
<Section title="Permissions"/>
<ConfigFormField configElement={getConfig("library.allow-public-access")}/>
@@ -23,6 +34,24 @@ function LibraryManagementLayout({getConfig, formik}: any) {
<ConfigFormField configElement={getConfig("library.metadata.update.schedule")}
isDisabled={!formik.values.library.metadata.update.enabled}/>
</div>
<div className="flex flex-row items-baseline justify-between">
<h2 className={"text-xl font-bold mt-8 mb-1"}>Libraries</h2>
<Tooltip content="Add new library">
<Button isIconOnly variant="flat" onPress={() => {
libraries.append({id: 1, name: "Library", path: "/path/to/library"} as LibraryDto)
}}>
<Plus/>
</Button>
</Tooltip>
</div>
<Divider className="mb-4"/>
{libraries.items.length > 0 ?
<div className="grid grid-cols-300px gap-4">
{libraries.items.map((library) => <LibraryOverviewCard library={library} key={library.name}/>)}
</div> :
"No libraries configured. Add your first library!"
}
</div>
);
}
@@ -1,10 +1,10 @@
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 {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/PluginPrioritiesModal";
import PluginPrioritiesModal from "Frontend/components/general/modals/PluginPrioritiesModal";
export default function PluginManagement() {
const [plugins, setPlugins] = useState<PluginDto[]>([]);
@@ -1,5 +1,5 @@
import Section from "Frontend/components/general/Section";
import Input from "Frontend/components/general/Input";
import Input from "Frontend/components/general/input/Input";
import {addToast, Button, Input as NextUiInput, Tooltip} from "@heroui/react";
import {Form, Formik} from "formik";
import {ArrowCounterClockwise, Check, Info, Trash} from "@phosphor-icons/react";
@@ -45,6 +45,7 @@ function SsoManagementLayout({getConfig, formik, setSaveMessage}: any) {
<div className="flex flex-col">
<div className="flex flex-row">
<div className="flex flex-col flex-1">
<Section title="SSO configuration"/>
<ConfigFormField configElement={getConfig("sso.oidc.enabled")}/>
<Section title="SSO user handling"/>
@@ -4,11 +4,11 @@ import withConfigPage from "Frontend/components/administration/withConfigPage";
import Section from "Frontend/components/general/Section";
import {ConfigEndpoint, UserEndpoint} from "Frontend/generated/endpoints";
import UserInfoDto from "Frontend/generated/de/grimsi/gameyfin/users/dto/UserInfoDto";
import {UserManagementCard} from "Frontend/components/general/UserManagementCard";
import {UserManagementCard} from "Frontend/components/general/cards/UserManagementCard";
import {SmallInfoField} from "Frontend/components/general/SmallInfoField";
import {Info, UserPlus} from "@phosphor-icons/react";
import {Button, Divider, Tooltip, useDisclosure} from "@heroui/react";
import InviteUserModal from "Frontend/components/general/InviteUserModal";
import InviteUserModal from "Frontend/components/general/modals/InviteUserModal";
function UserManagementLayout({getConfig, formik}: any) {
const inviteUserModal = useDisclosure();
@@ -36,7 +36,7 @@ function UserManagementLayout({getConfig, formik}: any) {
</div>
<div className="flex flex-row items-baseline justify-between">
<h2 className={"text-xl font-bold mt-8 mb-1"}>Users</h2>
<h2 className="text-xl font-bold mt-8 mb-1">Users</h2>
{!autoRegisterNewUsers &&
<SmallInfoField className="mb-4 text-warning" icon={Info}
message="Automatic user registration for SSO users is disabled"/>
@@ -1,7 +1,7 @@
import React from "react";
import {Form, Formik} from "formik";
import {addToast, Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader} from "@heroui/react";
import Input from "Frontend/components/general/Input";
import Input from "Frontend/components/general/input/Input";
import {MessageEndpoint} from "Frontend/generated/endpoints";
import * as Yup from "yup";
import MessageTemplateDto from "Frontend/generated/de/grimsi/gameyfin/messages/templates/MessageTemplateDto";
@@ -128,7 +128,7 @@ export default function withConfigPage(WrappedComponent: React.ComponentType<any
>
{(formik) => (
<Form>
<div className="flex flex-row flex-grow justify-between mb-8">
<div className="flex flex-row flex-grow justify-between">
<h1 className="text-2xl font-bold">{title}</h1>
<div className="flex flex-row items-center gap-4">
@@ -11,7 +11,7 @@ export default function PluginLogo({plugin}: PluginLogoProps) {
return (
<>
{plugin.hasLogo ?
<Image src={`/images/plugins/${plugin.id}/logo`} width={64} height={64} radius="none"/> :
<Image isBlurred src={`/images/plugins/${plugin.id}/logo`} width={64} height={64} radius="none"/> :
<Plug size={64} weight="fill"/>
}
</>
@@ -0,0 +1,13 @@
import {Card} from "@heroui/react";
import LibraryDto from "Frontend/generated/de/grimsi/gameyfin/libraries/LibraryDto";
export function LibraryOverviewCard({library}: { library: LibraryDto }) {
return (
<Card className="flex flex-row justify-between p-2">
<div className="flex flex-col flex-1 items-center gap-4">
<p className="text-2xl font-bold">{library.name}</p>
<p>{library.path}</p>
</div>
</Card>
);
}
@@ -13,7 +13,7 @@ import {PluginManagementEndpoint} from "Frontend/generated/endpoints";
import PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/management/PluginDto";
import PluginState from "Frontend/generated/org/pf4j/PluginState";
import React, {ReactNode, useEffect, useState} from "react";
import PluginDetailsModal from "Frontend/components/general/PluginDetailsModal";
import PluginDetailsModal from "Frontend/components/general/modals/PluginDetailsModal";
import PluginLogo from "Frontend/components/general/PluginLogo";
export function PluginManagementCard({plugin, updatePlugin}: {
@@ -5,12 +5,12 @@ import {useEffect, useState} from "react";
import {MessageEndpoint, PasswordResetEndpoint, UserEndpoint} from "Frontend/generated/endpoints";
import {AvatarEndpoint} from "Frontend/endpoints/endpoints";
import Avatar from "Frontend/components/general/Avatar";
import ConfirmUserDeletionModal from "Frontend/components/general/ConfirmUserDeletionModal";
import PasswordResetTokenModal from "Frontend/components/general/PasswortResetTokenModal";
import ConfirmUserDeletionModal from "Frontend/components/general/modals/ConfirmUserDeletionModal";
import PasswordResetTokenModal from "Frontend/components/general/modals/PasswortResetTokenModal";
import TokenDto from "Frontend/generated/de/grimsi/gameyfin/shared/token/TokenDto";
import UserInfoDto from "Frontend/generated/de/grimsi/gameyfin/users/dto/UserInfoDto";
import RoleChip from "Frontend/components/general/RoleChip";
import AssignRolesModal from "Frontend/components/general/AssignRolesModal";
import AssignRolesModal from "Frontend/components/general/modals/AssignRolesModal";
export function UserManagementCard({user}: { user: UserInfoDto }) {
const userDeletionConfirmationModal = useDisclosure();
@@ -4,7 +4,7 @@ import {Form, Formik} from "formik";
import {PluginConfigEndpoint, PluginManagementEndpoint} from "Frontend/generated/endpoints";
import PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/management/PluginDto";
import PluginConfigElement from "Frontend/generated/de/grimsi/gameyfin/pluginapi/core/PluginConfigElement";
import Input from "Frontend/components/general/Input";
import Input from "Frontend/components/general/input/Input";
import PluginLogo from "Frontend/components/general/PluginLogo";
interface PluginDetailsModalProps {
@@ -4,7 +4,7 @@ import {RegistrationEndpoint} from "Frontend/generated/endpoints";
import UserRegistrationDto from "Frontend/generated/de/grimsi/gameyfin/users/dto/UserRegistrationDto";
import {Form, Formik} from "formik";
import * as Yup from "yup";
import Input from "Frontend/components/general/Input";
import Input from "Frontend/components/general/input/Input";
interface SignUpModalProps {
isOpen: boolean;
@@ -34,4 +34,5 @@ const menuItems: MenuItem[] = [
}
]
export const AdministrationView = withSideMenu(menuItems);
export const AdministrationView = withSideMenu(menuItems);
export default AdministrationView;
@@ -1,7 +1,7 @@
import {addToast, Button, Card, CardBody, CardHeader} from "@heroui/react";
import {useNavigate, useSearchParams} from "react-router-dom";
import {Form, Formik} from "formik";
import Input from "Frontend/components/general/Input";
import Input from "Frontend/components/general/input/Input";
import * as Yup from "yup";
import {RegistrationEndpoint} from "Frontend/generated/endpoints";
import React, {useEffect, useState} from "react";
@@ -3,9 +3,9 @@ import {useEffect, useState} from "react";
import {Button, Card, CardBody, CardHeader, Link, useDisclosure} from "@heroui/react";
import {useNavigate} from "react-router-dom";
import {Form, Formik} from "formik";
import Input from "Frontend/components/general/Input";
import PasswordResetModal from "Frontend/components/general/PasswordResetModal";
import SignUpModal from "Frontend/components/general/SignUpModal";
import Input from "Frontend/components/general/input/Input";
import PasswordResetModal from "Frontend/components/general/modals/PasswordResetModal";
import SignUpModal from "Frontend/components/general/modals/SignUpModal";
import {RegistrationEndpoint} from "Frontend/generated/endpoints";
export default function LoginView() {
@@ -83,7 +83,7 @@ export default function MainLayout() {
</div>
</div>
<Divider/>
<Divider className="mt-8"/>
<div className="flex flex-col w-full 2xl:w-3/4 m-auto">
<footer className="flex flex-row items-center justify-between py-4 px-12">
<p>Gameyfin {PackageJson.version}</p>
@@ -1,7 +1,7 @@
import {addToast, Button, Card, CardBody, CardHeader} from "@heroui/react";
import {useNavigate, useSearchParams} from "react-router-dom";
import {Form, Formik} from "formik";
import Input from "Frontend/components/general/Input";
import Input from "Frontend/components/general/input/Input";
import * as Yup from "yup";
import {PasswordResetEndpoint} from "Frontend/generated/endpoints";
import React, {useEffect, useState} from "react";
@@ -2,7 +2,7 @@ import React from 'react';
import * as Yup from 'yup';
import Wizard from "Frontend/components/wizard/Wizard";
import WizardStep from "Frontend/components/wizard/WizardStep";
import Input from "Frontend/components/general/Input";
import Input from "Frontend/components/general/input/Input";
import {HandWaving, Palette, User} from "@phosphor-icons/react";
import {addToast, Card} from "@heroui/react";
import {SetupEndpoint} from "Frontend/generated/endpoints";
@@ -3,7 +3,7 @@ import {addToast, Button, Input} from "@heroui/react";
import {LibraryEndpoint, SystemEndpoint} from "Frontend/generated/endpoints";
import {useState} from "react";
import GameDto from "Frontend/generated/de/grimsi/gameyfin/games/dto/GameDto";
import {GameOverviewCard} from "Frontend/components/games/GameOverviewCard";
import {GameOverviewCard} from "Frontend/components/general/cards/GameOverviewCard";
export default function TestView() {
const [gameTitle, setGameTitle] = useState("");
@@ -123,6 +123,11 @@ class GameService(
return results.filter { it.value.title == bestMatchingTitle }
}
/**
* Merges the results from the metadata plugins into a single Game entity
* The merging is done by taking the first non-null value for each field
* The plugin with the highest possible priority is used as the source for each field
*/
private fun mergeResults(results: List<Map.Entry<GameMetadataProvider, GameMetadata?>>, path: Path): Game {
val mergedGame = Game(path = path.toString())
val metadataMap = mutableMapOf<String, FieldMetadata>()
+1 -1
View File
@@ -6,7 +6,7 @@ dependencies {
ksp("care.better.pf4j:pf4j-kotlin-symbol-processing:${rootProject.extra["pf4jKspVersion"]}")
// IGDB API client
implementation("io.github.husnjak:igdb-api-jvm:1.2.0")
implementation("io.github.husnjak:igdb-api-jvm:1.3.1")
// Fuzzy string matching
implementation("me.xdrop:fuzzywuzzy:1.4.0")