mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-13 16:40:01 +00:00
Implement metadata completeness indicator
Show metadata 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 MatchGameModal from "Frontend/components/general/modals/MatchGameModal";
|
||||
import {GameAdminDto} from "Frontend/dtos/GameDtos";
|
||||
import MetadataCompletenessIndicator from "Frontend/components/general/MetadataCompletenessIndicator";
|
||||
import {metadataCompleteness} from "Frontend/util/utils";
|
||||
|
||||
interface LibraryManagementGamesProps {
|
||||
library: LibraryDto;
|
||||
@@ -67,6 +69,9 @@ export default function LibraryManagementGames({library}: LibraryManagementGames
|
||||
case "downloadCount":
|
||||
cmp = a.metadata.downloadCount - b.metadata.downloadCount;
|
||||
break;
|
||||
case "completeness":
|
||||
cmp = metadataCompleteness(a) - metadataCompleteness(b);
|
||||
break;
|
||||
default:
|
||||
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="downloadCount" allowsSorting>Download count</TableColumn>
|
||||
<TableColumn>Path</TableColumn>
|
||||
<TableColumn key="completeness" allowsSorting>Completeness</TableColumn>
|
||||
{/* width={1} keeps the column as far to the right as possible*/}
|
||||
<TableColumn width={1}>Actions</TableColumn>
|
||||
</TableHeader>
|
||||
@@ -182,6 +188,9 @@ export default function LibraryManagementGames({library}: LibraryManagementGames
|
||||
<TableCell>
|
||||
{item.metadata.path}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<MetadataCompletenessIndicator game={item}/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-row gap-2">
|
||||
<Button isIconOnly size="sm" onPress={() => toggleMatchConfirmed(item)}>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {getCsrfToken} from "Frontend/util/auth";
|
||||
import moment from 'moment-timezone';
|
||||
import GameDto from "Frontend/generated/org/gameyfin/app/games/dto/GameDto";
|
||||
|
||||
export function isAdmin(auth: any): boolean {
|
||||
return auth.state.user?.roles?.some((a: string) => a?.includes("ADMIN"));
|
||||
@@ -207,4 +208,23 @@ export function fileNameFromPath(path: string, includeExtension: boolean = true)
|
||||
}
|
||||
const dotIndex = fileName.lastIndexOf('.');
|
||||
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
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonInclude(JsonInclude.Include.ALWAYS)
|
||||
data class GameUserDto(
|
||||
override val id: Long,
|
||||
override val createdAt: Instant,
|
||||
@@ -55,7 +55,7 @@ data class GameUserDto(
|
||||
override val metadata: GameMetadataUserDto
|
||||
) : GameDto
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonInclude(JsonInclude.Include.ALWAYS)
|
||||
data class GameAdminDto(
|
||||
override val id: Long,
|
||||
override val createdAt: Instant,
|
||||
|
||||
Reference in New Issue
Block a user