Overhaul startpage (#821)

* chore: bump version to v2.3.0-preview

* Customize start page (#803)

* Update ConfigService to support complex Objects
Implemented tests for ConfigService

* Added DB migration for config table

* Fixed version in banner.txt not being displayed

* Implement Library ordering
Implement "Show recently added games on homepage"

* Fix build.gradle.kts

* FIx bug when creating libraries

* Fix TypeScript errors
Fix library sorting

* Bump actions/checkout from 5 to 6 (#811)

Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Added automatic scanning using file system watchers (#813)

* Implement collections (#814)

* Backend implementation for collections

* Fix database schema and migration script

* Refactor some config values
Fix ArrayInput not being deactivatable

* Remove "AutoRegisterNewUsers" config option

* Fix bug when removing ignored paths

* Add UI for collections (WIP)

* Fix table actions not synced with state
Fix tests

* Finish implementation of collection feature

* Fix tests

* Bump actions/checkout from 5 to 6 (#815)

Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Fix "allow guests to create game requests" not being enabled when guest access is activated

* Fix: Disable loading of EditGameMetadataModal and MatchGameModal in GameView when user is not admin

* WIP: Update start page layout

* Performance improvements (lazy loading and virtualized grids/lists)
Fix various smaller issues

* Implement use of blurhash for all images in backend and covers in frontend

* Fix bugs and test

* Fix code analysis issues

* Remove "UI settings" since they have been made obsolete

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Simon
2025-12-10 00:22:58 +01:00
committed by GitHub
parent 38b95ae102
commit 8d8dca32d8
137 changed files with 5218 additions and 809 deletions
@@ -1,113 +1,39 @@
import React from "react";
import {addToast, Button, Chip, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader} from "@heroui/react";
import {ListBox, ListBoxItem, useDragAndDrop} from "react-aria-components";
import { CaretUpDownIcon } from "@phosphor-icons/react";
import {useListData} from "@react-stately/data";
import PluginDto from "Frontend/generated/org/gameyfin/app/core/plugins/dto/PluginDto";
import {PluginEndpoint} from "Frontend/generated/endpoints";
import PrioritiesModal from "./PrioritiesModal";
import {useSnapshot} from "valtio/react";
import {pluginState} from "Frontend/state/PluginState";
interface PluginPrioritiesModalProps {
plugins: PluginDto[];
isOpen: boolean;
onOpenChange: () => void;
type: string;
}
export default function PluginPrioritiesModal({plugins, isOpen, onOpenChange}: PluginPrioritiesModalProps) {
export default function PluginPrioritiesModal({isOpen, onOpenChange, type}: PluginPrioritiesModalProps) {
const plugins = useSnapshot(pluginState).sortedByType[type];
const sortedPlugins = useListData({
initialItems: plugins, // Already sorted in parent
getKey: (plugin) => plugin.id
});
const updatePlugins = async (reorderedPlugins: PluginDto[]) => {
const prioritiesMap: Record<string, number> = {};
const totalPlugins = reorderedPlugins.length;
let {dragAndDropHooks} = useDragAndDrop({
getItems: (keys) =>
[...keys].map((key) => ({'text/plain': sortedPlugins.getItem(key)!.name})),
onReorder(e) {
if (e.keys.has(e.target.key)) return;
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 priority based on new position (reversed)
sortedPlugins.items.forEach((plugin, index) => {
const reversedPriority = sortedPlugins.items.length - index;
sortedPlugins.update(plugin.id, {...plugin, priority: reversedPriority});
});
}
});
function generatePrioritiesMap(): Record<string, number> {
let map: Record<string, number> = {};
const totalPlugins = sortedPlugins.items.length;
sortedPlugins.items.forEach((plugin, index) => {
map[plugin.id] = totalPlugins - index; // Reverse order
reorderedPlugins.forEach((plugin, index) => {
// Reverse order: first item gets highest priority
prioritiesMap[plugin.id] = totalPlugins - index;
});
return map;
}
async function setPluginPriorities(onClose: () => void) {
try {
const prioritiesMap = generatePrioritiesMap();
await PluginEndpoint.setPluginPriorities(prioritiesMap);
addToast({
title: "Plugin order updated",
description: "Plugin order has been updated successfully.",
color: "success"
});
onClose();
} catch (e) {
addToast({
title: "Error",
description: "An error occurred while updating plugin order.",
color: "warning"
});
}
}
await PluginEndpoint.setPluginPriorities(prioritiesMap);
};
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.findIndex(p => p.id === plugin.id) + 1}
</Chip>
<p className="font-normal text-small">{plugin.name}</p>
</div>
<CaretUpDownIcon/>
</ListBoxItem>
)}
</ListBox>
</ModalBody>
<ModalFooter>
<Button variant="light" onPress={onClose}>
Cancel
</Button>
<Button color="primary" onPress={() => setPluginPriorities(onClose)}>
Save
</Button>
</ModalFooter>
</>
)}
</ModalContent>
</Modal>
<PrioritiesModal
title="Edit plugin order"
subtitle="Plugins higher on the list are preferred"
items={plugins}
updateItems={updatePlugins}
isOpen={isOpen}
onOpenChange={onOpenChange}
/>
);
}