mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 08:15:48 +00:00
Implement metadata completeness indicator
Implement sorting by completeness
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
import GameDto from "Frontend/generated/org/gameyfin/app/games/dto/GameDto";
|
||||||
|
import {useMemo} from "react";
|
||||||
|
import {CircularProgress} from "@heroui/react";
|
||||||
|
import {metadataCompleteness} from "Frontend/util/utils";
|
||||||
|
|
||||||
|
interface MetadataCompletenessIndicatorProps {
|
||||||
|
game: GameDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MetadataCompletenessIndicator({game}: MetadataCompletenessIndicatorProps) {
|
||||||
|
const completeness = useMemo(() => metadataCompleteness(game), [game]);
|
||||||
|
|
||||||
|
const color = useMemo(() => {
|
||||||
|
return completeness > 80 ? "success" : completeness > 50 ? "warning" : "danger";
|
||||||
|
}, [completeness]);
|
||||||
|
|
||||||
|
return <div className="flex flex-row items-center gap-1">
|
||||||
|
<CircularProgress
|
||||||
|
color={color}
|
||||||
|
value={completeness}
|
||||||
|
disableAnimation
|
||||||
|
size="sm"
|
||||||
|
strokeWidth={5}
|
||||||
|
/>
|
||||||
|
<p>{completeness}% </p>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
@@ -26,6 +26,8 @@ import {useMemo, useState} from "react";
|
|||||||
import EditGameMetadataModal from "Frontend/components/general/modals/EditGameMetadataModal";
|
import EditGameMetadataModal from "Frontend/components/general/modals/EditGameMetadataModal";
|
||||||
import MatchGameModal from "Frontend/components/general/modals/MatchGameModal";
|
import MatchGameModal from "Frontend/components/general/modals/MatchGameModal";
|
||||||
import {GameAdminDto} from "Frontend/dtos/GameDtos";
|
import {GameAdminDto} from "Frontend/dtos/GameDtos";
|
||||||
|
import MetadataCompletenessIndicator from "Frontend/components/general/MetadataCompletenessIndicator";
|
||||||
|
import {metadataCompleteness} from "Frontend/util/utils";
|
||||||
|
|
||||||
interface LibraryManagementGamesProps {
|
interface LibraryManagementGamesProps {
|
||||||
library: LibraryDto;
|
library: LibraryDto;
|
||||||
@@ -67,6 +69,9 @@ export default function LibraryManagementGames({library}: LibraryManagementGames
|
|||||||
case "downloadCount":
|
case "downloadCount":
|
||||||
cmp = a.metadata.downloadCount - b.metadata.downloadCount;
|
cmp = a.metadata.downloadCount - b.metadata.downloadCount;
|
||||||
break;
|
break;
|
||||||
|
case "completeness":
|
||||||
|
cmp = metadataCompleteness(a) - metadataCompleteness(b);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return 0; // No sorting if the column is not recognized
|
return 0; // No sorting if the column is not recognized
|
||||||
}
|
}
|
||||||
@@ -160,6 +165,7 @@ export default function LibraryManagementGames({library}: LibraryManagementGames
|
|||||||
<TableColumn key="addedToLibrary" allowsSorting>Added to library</TableColumn>
|
<TableColumn key="addedToLibrary" allowsSorting>Added to library</TableColumn>
|
||||||
<TableColumn key="downloadCount" allowsSorting>Download count</TableColumn>
|
<TableColumn key="downloadCount" allowsSorting>Download count</TableColumn>
|
||||||
<TableColumn>Path</TableColumn>
|
<TableColumn>Path</TableColumn>
|
||||||
|
<TableColumn key="completeness" allowsSorting>Completeness</TableColumn>
|
||||||
{/* width={1} keeps the column as far to the right as possible*/}
|
{/* width={1} keeps the column as far to the right as possible*/}
|
||||||
<TableColumn width={1}>Actions</TableColumn>
|
<TableColumn width={1}>Actions</TableColumn>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
@@ -182,6 +188,9 @@ export default function LibraryManagementGames({library}: LibraryManagementGames
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
{item.metadata.path}
|
{item.metadata.path}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<MetadataCompletenessIndicator game={item}/>
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
<Button isIconOnly size="sm" onPress={() => toggleMatchConfirmed(item)}>
|
<Button isIconOnly size="sm" onPress={() => toggleMatchConfirmed(item)}>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {getCsrfToken} from "Frontend/util/auth";
|
import {getCsrfToken} from "Frontend/util/auth";
|
||||||
import moment from 'moment-timezone';
|
import moment from 'moment-timezone';
|
||||||
|
import GameDto from "Frontend/generated/org/gameyfin/app/games/dto/GameDto";
|
||||||
|
|
||||||
export function isAdmin(auth: any): boolean {
|
export function isAdmin(auth: any): boolean {
|
||||||
return auth.state.user?.roles?.some((a: string) => a?.includes("ADMIN"));
|
return auth.state.user?.roles?.some((a: string) => a?.includes("ADMIN"));
|
||||||
@@ -208,3 +209,22 @@ export function fileNameFromPath(path: string, includeExtension: boolean = true)
|
|||||||
const dotIndex = fileName.lastIndexOf('.');
|
const dotIndex = fileName.lastIndexOf('.');
|
||||||
return dotIndex < 0 ? fileName : fileName.substring(0, dotIndex);
|
return dotIndex < 0 ? fileName : fileName.substring(0, dotIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Calculate the completeness of a GameDto
|
||||||
|
* @param game
|
||||||
|
* @returns completeness percentage (0-100)
|
||||||
|
*/
|
||||||
|
export function metadataCompleteness(game: GameDto) {
|
||||||
|
// Total number of fields considered for completeness
|
||||||
|
// Includes all fields except "comment"
|
||||||
|
const totalFields = 21;
|
||||||
|
|
||||||
|
const filledFields = Object.values(game).filter(value => {
|
||||||
|
if (value === null || value === undefined) return false;
|
||||||
|
if (Array.isArray(value)) return value.length > 0;
|
||||||
|
if (typeof value === "string") return value.trim().length > 0;
|
||||||
|
return true;
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
return Math.round((filledFields / totalFields) * 100);
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ sealed interface GameDto {
|
|||||||
val metadata: GameMetadataDto
|
val metadata: GameMetadataDto
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.ALWAYS)
|
||||||
data class GameUserDto(
|
data class GameUserDto(
|
||||||
override val id: Long,
|
override val id: Long,
|
||||||
override val createdAt: Instant,
|
override val createdAt: Instant,
|
||||||
@@ -55,7 +55,7 @@ data class GameUserDto(
|
|||||||
override val metadata: GameMetadataUserDto
|
override val metadata: GameMetadataUserDto
|
||||||
) : GameDto
|
) : GameDto
|
||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.ALWAYS)
|
||||||
data class GameAdminDto(
|
data class GameAdminDto(
|
||||||
override val id: Long,
|
override val id: Long,
|
||||||
override val createdAt: Instant,
|
override val createdAt: Instant,
|
||||||
|
|||||||
Reference in New Issue
Block a user