Files
gameyfin/app/src/main/frontend/views/CollectionManagementView.tsx
T
Simon 09953a3f78 Release 2.3.0 (#804)
* 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

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

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>

* Overhaul startpage (#823)

* 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

* Remove length limit from "image.originalUrl" (#824)

* Remove alpine based image (#825)

* Fix bug when games from library are still in a collection, thus prevention deletion of said library

* Delete image files in background

* Fix layout

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 12:58:14 +01:00

127 lines
5.4 KiB
TypeScript

import {useNavigate, useParams} from "react-router";
import React, {useEffect} from "react";
import {addToast, Button} from "@heroui/react";
import {ArrowLeftIcon, CheckIcon} from "@phosphor-icons/react";
import {useSnapshot} from "valtio/react";
import CollectionAdminDto from "Frontend/generated/org/gameyfin/app/collections/dto/CollectionAdminDto";
import {collectionState} from "Frontend/state/CollectionState";
import {Form, Formik} from "formik";
import * as Yup from "yup";
import Input from "Frontend/components/general/input/Input";
import Section from "Frontend/components/general/Section";
import {deepDiff} from "Frontend/util/utils";
import {CollectionEndpoint} from "Frontend/generated/endpoints";
import CollectionUpdateDto from "Frontend/generated/org/gameyfin/app/collections/dto/CollectionUpdateDto";
import TextAreaInput from "Frontend/components/general/input/TextAreaInput";
import CollectionHeader from "Frontend/components/general/covers/CollectionHeader";
import CollectionGamesTable from "Frontend/components/general/modals/CollectionGamesTable";
import CheckboxInput from "Frontend/components/general/input/CheckboxInput";
export default function CollectionManagementView() {
const {collectionId} = useParams();
const navigate = useNavigate();
const [collectionSaved, setCollectionSaved] = React.useState(false);
const collections = useSnapshot(collectionState);
// Parse and validate collectionId early
const collectionIdNum = collectionId ? parseInt(collectionId) : null;
// Early return if invalid collection ID
useEffect(() => {
if (!collectionIdNum || (collections.isLoaded && !collections.state[collectionIdNum])) {
navigate("/administration/games");
}
}, [collections, collectionIdNum, navigate]);
// If collectionId is invalid, return null (will redirect via useEffect)
if (!collectionIdNum) {
return null;
}
// At this point, collectionIdNum is guaranteed to be a number
const collection = collections.state[collectionIdNum] as CollectionAdminDto;
async function handleSubmit(values: CollectionUpdateDto): Promise<void> {
const changed = deepDiff(collection, values) as CollectionUpdateDto;
if (Object.keys(changed).length === 0) return;
changed.id = collection.id;
await CollectionEndpoint.updateCollection(changed);
setCollectionSaved(true);
setTimeout(() => setCollectionSaved(false), 2000);
}
async function deleteCollection(): Promise<void> {
try {
await CollectionEndpoint.deleteCollection(collection.id);
addToast({
title: "Collection deleted",
description: `Collection ${collection.name} deleted!`,
color: "success"
});
navigate("/administration/games");
} catch (e) {
addToast({
title: "Error deleting collection",
description: `Collection ${collection.name} could not be deleted!`,
color: "warning"
});
}
}
return collection && (
<div className="flex flex-col gap-4">
<div className="flex flex-row gap-4 items-center">
<Button isIconOnly variant="light" onPress={() => history.back()}>
<ArrowLeftIcon/>
</Button>
<h1 className="text-2xl font-bold">Manage Collection</h1>
</div>
<CollectionHeader collection={collection} className="h-32"/>
<Formik
initialValues={collection}
onSubmit={handleSubmit}
enableReinitialize={true}
validationSchema={Yup.object({
name: Yup.string()
.required("Collection name is required")
.max(255, "Collection name must be 255 characters or less")
})}
>
{(formik) => (
<Form>
<div className="flex flex-row grow justify-between mb-4">
<h1 className="text-2xl font-bold">Edit collection details</h1>
<Button
color="primary"
isLoading={formik.isSubmitting}
isDisabled={formik.isSubmitting || collectionSaved || !formik.dirty}
type="submit"
>
{formik.isSubmitting ? "" : collectionSaved ? <CheckIcon/> : "Save"}
</Button>
</div>
<Input label="Collection name" name="name"/>
<TextAreaInput label="Collection description" name="description"/>
<CheckboxInput label="Display on homepage" name="metadata.displayOnHomepage" className="mb-4"/>
<div className="flex flex-col gap-4">
<h1 className="text-2xl font-bold">Manage games in collection</h1>
<CollectionGamesTable collectionId={collectionIdNum}/>
</div>
<Section title="Danger zone"/>
<Button color="danger" onPress={deleteCollection}>
Delete collection
</Button>
</Form>
)}
</Formik>
</div>
);
}