mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-13 16:40:01 +00:00
09953a3f78
* 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>
110 lines
3.7 KiB
TypeScript
110 lines
3.7 KiB
TypeScript
import GameDto from "Frontend/generated/org/gameyfin/app/games/dto/GameDto";
|
|
import {GameCover} from "Frontend/components/general/covers/GameCover";
|
|
import type {CellComponentProps} from "react-window";
|
|
import {Grid} from "react-window";
|
|
import {useEffect, useRef, useState} from "react";
|
|
|
|
interface CoverGridProps {
|
|
games: GameDto[];
|
|
}
|
|
|
|
// Constants for grid layout
|
|
const MIN_COLUMN_WIDTH = 180; // Minimum width per item (minmax value from original)
|
|
const MAX_COLUMN_WIDTH = 212; // Maximum width per item (minmax value from original)
|
|
const GAP = 16; // gap-4 = 1rem = 16px
|
|
const ASPECT_RATIO = 12 / 17; // Game cover aspect ratio (width/height)
|
|
|
|
export default function CoverGrid({games}: CoverGridProps) {
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
|
|
// Update container width on resize
|
|
useEffect(() => {
|
|
const updateDimensions = () => {
|
|
if (containerRef.current) {
|
|
setContainerWidth(containerRef.current.offsetWidth);
|
|
}
|
|
};
|
|
|
|
const resizeObserver = new ResizeObserver(updateDimensions);
|
|
if (containerRef.current) {
|
|
resizeObserver.observe(containerRef.current);
|
|
}
|
|
|
|
updateDimensions();
|
|
|
|
return () => resizeObserver.disconnect();
|
|
}, []);
|
|
|
|
// Calculate how many columns can fit
|
|
const columnCount = Math.max(1, Math.floor((containerWidth + GAP) / (MIN_COLUMN_WIDTH + GAP)));
|
|
|
|
// Calculate actual column width to distribute space evenly (up to MAX_COLUMN_WIDTH)
|
|
const actualColumnWidth = Math.min(
|
|
MAX_COLUMN_WIDTH,
|
|
Math.floor((containerWidth - (columnCount - 1) * GAP) / columnCount)
|
|
);
|
|
|
|
// Calculate cover height based on width and aspect ratio
|
|
// GameCover's size prop is the height, so we need to calculate height from width
|
|
const coverHeight = Math.floor(actualColumnWidth / ASPECT_RATIO);
|
|
|
|
// Calculate row count
|
|
const rowCount = Math.ceil(games.length / columnCount);
|
|
|
|
// Cell renderer for react-window Grid
|
|
const Cell = ({
|
|
columnIndex,
|
|
rowIndex,
|
|
style
|
|
}: CellComponentProps<{}>) => {
|
|
const gameIndex = rowIndex * columnCount + columnIndex;
|
|
|
|
// Return empty cell if we're past the end of the games array
|
|
if (gameIndex >= games.length) {
|
|
return <div style={style}/>;
|
|
}
|
|
|
|
const game = games[gameIndex];
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
...style,
|
|
paddingBottom: GAP,
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
boxSizing: 'border-box'
|
|
}}
|
|
>
|
|
<GameCover game={game} interactive={true} size={coverHeight} lazy={true}/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// Column width function to handle the last column differently
|
|
const getColumnWidth = (index: number) => {
|
|
// Last column doesn't need gap after it
|
|
if (index === columnCount - 1) {
|
|
return actualColumnWidth;
|
|
}
|
|
return actualColumnWidth + GAP;
|
|
};
|
|
|
|
return (
|
|
<div ref={containerRef} className="w-full">
|
|
{containerWidth > 0 && (
|
|
<Grid<{}>
|
|
columnCount={columnCount}
|
|
columnWidth={getColumnWidth}
|
|
rowCount={rowCount}
|
|
rowHeight={coverHeight + GAP}
|
|
defaultWidth={containerWidth}
|
|
cellComponent={Cell}
|
|
cellProps={{}}
|
|
style={{overflowX: 'hidden'}}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
} |