Move package "de.grimsi.gameyfin" to "org.gameyfin"

This commit is contained in:
grimsi
2025-06-14 19:23:12 +02:00
parent be0ba28c54
commit d3d46b6b01
328 changed files with 710 additions and 678 deletions
@@ -0,0 +1,72 @@
import {proxy} from 'valtio';
import ConfigEntryDto from "Frontend/generated/org/gameyfin/app/config/dto/ConfigEntryDto";
import {ConfigEndpoint} from "Frontend/generated/endpoints";
import ConfigUpdateDto from "Frontend/generated/org/gameyfin/app/config/dto/ConfigUpdateDto";
import {Subscription} from "@vaadin/hilla-frontend";
type ConfigState = {
subscription?: Subscription<ConfigUpdateDto[]>;
isLoaded: boolean;
state: Record<string, ConfigEntryDto>;
config: NestedConfig;
};
export const configState = proxy<ConfigState>({
get isLoaded() {
return this.subscription != null;
},
state: {},
get config() {
return toNestedConfig(Object.values(this.state));
}
});
/** Subscribe to and process state updates from backend **/
export async function initializeConfigState() {
if (configState.isLoaded) return;
// Fetch initial configuration data
const initialEntries = await ConfigEndpoint.getAll();
initialEntries.forEach((entry) => {
configState.state[entry.key] = entry;
});
// Subscribe to real-time updates
configState.subscription = ConfigEndpoint.subscribe().onNext((updateDtos: ConfigUpdateDto[]) => {
updateDtos.forEach((updateDto: ConfigUpdateDto) => {
Object.entries(updateDto.updates).forEach(([key, value]) => {
if (configState.state[key]) {
configState.state[key].value = value;
}
});
})
});
}
/** Computed properties **/
export type NestedConfig = {
[field: string]: any;
}
function toNestedConfig(entries: ConfigEntryDto[]): NestedConfig {
const result: Record<string, any> = {};
for (const entry of entries) {
const keys = entry.key.split('.');
let current = result;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (i === keys.length - 1) {
current[key] = entry.value;
} else {
current[key] = current[key] || {};
current = current[key];
}
}
}
return result;
}
+145
View File
@@ -0,0 +1,145 @@
import {Subscription} from "@vaadin/hilla-frontend";
import {proxy} from "valtio/index";
import {GameEndpoint} from "Frontend/generated/endpoints";
import GameEvent from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryEvent";
import GameDto from "Frontend/generated/org/gameyfin/app/games/dto/GameDto";
import Rand from "rand-seed";
type GameState = {
subscription?: Subscription<GameEvent[]>;
isLoaded: boolean;
state: Record<number, GameDto>;
games: GameDto[];
gamesByLibraryId: Record<number, GameDto[]>;
sortedAlphabetically: GameDto[];
recentlyAdded: GameDto[];
recentlyUpdated: GameDto[];
randomlyOrderedGamesByLibraryId: Record<number, GameDto[]>;
knownPublishers: Set<string>;
knownDevelopers: Set<string>;
knownGenres: Set<string>;
knownThemes: Set<string>;
knownKeywords: Set<string>;
knownFeatures: Set<string>;
knownPerspectives: Set<string>;
};
export const gameState = proxy<GameState>({
get isLoaded() {
return this.subscription != null;
},
state: {},
get games() {
return Object.values<GameDto>(this.state);
},
get gamesByLibraryId() {
return this.sortedAlphabetically.reduce((acc: Record<number, GameDto[]>, game: GameDto) => {
(acc[game.libraryId] ||= []).push(game);
return acc;
}, {});
},
get sortedAlphabetically() {
return this.games
.sort((a: GameDto, b: GameDto) => a.title.localeCompare(b.title, undefined, {sensitivity: 'base'}));
},
get recentlyAdded() {
return this.games
.sort((a: GameDto, b: GameDto) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
.slice(0, 25);
},
get recentlyUpdated() {
return this.games
.sort((a: GameDto, b: GameDto) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
.slice(0, 25);
},
get randomlyOrderedGamesByLibraryId() {
const result: Record<number, GameDto[]> = {};
for (const libraryId in this.gamesByLibraryId) {
const rand = new Rand(libraryId.toString());
result[libraryId] = this.gamesByLibraryId[libraryId]
.filter((g: GameDto) => g.coverId && g.imageIds && g.imageIds.length > 0)
.sort((a: GameDto, b: GameDto) => a.id - b.id)
.sort(() => rand.next() - 0.5);
}
return result;
},
get knownPublishers() {
return new Set<string>(
this.games
.flatMap((game: GameDto) => game.publishers ? game.publishers : [])
.sort()
);
},
get knownDevelopers() {
return new Set<string>(
this.games
.flatMap((game: GameDto) => game.developers ? game.developers : [])
.sort()
);
},
get knownGenres() {
return new Set<string>(
this.games
.flatMap((game: GameDto) => game.genres ? game.genres : [])
.sort()
);
},
get knownThemes() {
return new Set<string>(
this.games
.flatMap((game: GameDto) => game.themes ? game.themes : [])
.sort()
);
},
get knownKeywords() {
return new Set<string>(
this.games
.flatMap((game: GameDto) => game.keywords ? game.keywords : [])
.sort()
);
},
get knownFeatures() {
return new Set<string>(
this.games
.flatMap((game: GameDto) => game.features ? game.features : [])
.sort()
);
},
get knownPerspectives() {
return new Set<string>(
this.games
.flatMap((game: GameDto) => game.perspectives ? game.perspectives : [])
.sort()
);
}
});
/** Subscribe to and process state updates from backend **/
export async function initializeGameState() {
if (gameState.isLoaded) return gameState;
// Fetch initial library list
const initialEntries = await GameEndpoint.getAll();
initialEntries.forEach((game: GameDto) => {
gameState.state[game.id] = game;
});
// Subscribe to real-time updates
gameState.subscription = GameEndpoint.subscribe().onNext((gameEvents: GameEvent[]) => {
gameEvents.forEach((gameEvent: GameEvent) => {
switch (gameEvent.type) {
case "created":
case "updated":
//@ts-ignore
gameState.state[gameEvent.game.id] = gameEvent.game;
break;
case "deleted":
//@ts-ignore
delete gameState.state[gameEvent.gameId];
break;
}
})
});
return gameState;
}
@@ -0,0 +1,62 @@
import {Subscription} from "@vaadin/hilla-frontend";
import {proxy} from "valtio/index";
import {LibraryEndpoint} from "Frontend/generated/endpoints";
import LibraryDto from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryDto";
import LibraryEvent from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryEvent";
import {handleLibraryDeletion} from "./ScanState";
type LibraryState = {
subscription?: Subscription<LibraryEvent[]>;
isLoaded: boolean;
state: Record<number, LibraryDto>;
libraries: LibraryDto[];
sorted: LibraryDto[];
};
export const libraryState = proxy<LibraryState>({
get isLoaded() {
return this.subscription != null;
},
state: {},
get libraries() {
return Object.values<LibraryDto>(this.state);
},
get sorted() {
return Object.values<LibraryDto>(this.state).sort((a, b) => {
if (a.name === undefined || b.name === undefined) return 0;
return a.name.localeCompare(b.name);
});
}
});
/** Subscribe to and process state updates from backend **/
export async function initializeLibraryState() {
if (libraryState.isLoaded) return libraryState;
// Fetch initial library list
const initialEntries = await LibraryEndpoint.getAll();
initialEntries.forEach((library: LibraryDto) => {
libraryState.state[library.id] = library;
});
// Subscribe to real-time updates
libraryState.subscription = LibraryEndpoint.subscribeToLibraryEvents().onNext((libraryEvents: LibraryEvent[]) => {
libraryEvents.forEach((libraryEvent: LibraryEvent) => {
switch (libraryEvent.type) {
case "created":
case "updated":
//@ts-ignore
libraryState.state[libraryEvent.library.id] = libraryEvent.library;
break;
case "deleted":
//@ts-ignore
handleLibraryDeletion(libraryEvent.libraryId);
//@ts-ignore
delete libraryState.state[libraryEvent.libraryId];
break;
}
})
});
return libraryState;
}
@@ -0,0 +1,76 @@
import {Subscription} from "@vaadin/hilla-frontend";
import PluginDto from "Frontend/generated/org/gameyfin/app/core/plugins/dto/PluginDto";
import PluginUpdateDto from "Frontend/generated/org/gameyfin/app/core/plugins/dto/PluginUpdateDto";
import {proxy} from "valtio/index";
import {PluginEndpoint} from "Frontend/generated/endpoints";
type PluginState = {
subscription?: Subscription<PluginUpdateDto[]>;
isLoaded: boolean;
state: Record<string, PluginDto>;
plugins: PluginDto[];
pluginsByType: Record<string, PluginDto[]>;
};
export const pluginState = proxy<PluginState>({
get isLoaded() {
return this.subscription != null;
},
state: {},
get plugins() {
return Object.values<PluginDto>(this.state);
},
get pluginsByType() {
return groupPluginsByType(this.state);
}
});
/** Subscribe to and process state updates from backend **/
export async function initializePluginState() {
if (pluginState.isLoaded) return;
// Fetch initial plugin list
const initialEntries = await PluginEndpoint.getAll();
initialEntries.forEach((plugin: PluginDto) => {
pluginState.state[plugin.id] = plugin;
});
// Subscribe to real-time updates
pluginState.subscription = PluginEndpoint.subscribe().onNext((updateDtos: PluginUpdateDto[]) => {
updateDtos.forEach((updateDto: PluginUpdateDto) => {
// Make sure the plugin exists in the state
if (pluginState.state[updateDto.id]) {
// Update the existing plugin by merging the new data using destructuring
pluginState.state[updateDto.id] = {
...pluginState.state[updateDto.id],
...updateDto
};
}
})
});
}
/** Computed **/
function groupPluginsByType(pluginsMap: Record<string, PluginDto>): Record<string, PluginDto[]> {
const pluginsByType: Record<string, PluginDto[]> = {};
// Convert map to array of plugins
const plugins = Object.values(pluginsMap);
// Iterate through each plugin
for (const plugin of plugins) {
// Each plugin can have multiple types
for (const type of plugin.types) {
// Initialize array for this type if it doesn't exist
if (!pluginsByType[type]) {
pluginsByType[type] = [];
}
// Add plugin to the appropriate type array
pluginsByType[type].push(plugin);
}
}
return pluginsByType;
}
+53
View File
@@ -0,0 +1,53 @@
import {proxy} from 'valtio';
import type LibraryScanProgress from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryScanProgress";
import {LibraryEndpoint} from "Frontend/generated/endpoints";
import {Subscription} from "@vaadin/hilla-frontend";
import LibraryScanStatus from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryScanStatus";
import {libraryState} from "Frontend/state/LibraryState";
type ScanState = {
subscription?: Subscription<LibraryScanProgress[]>;
state: Record<string, LibraryScanProgress>;
hasContent: boolean,
isScanning: boolean,
sortedByStartTime: LibraryScanProgress[];
};
export const scanState = proxy<ScanState>({
state: {},
get hasContent(): boolean {
return Object.values(this.state).length > 0;
},
get isScanning(): boolean {
return Object.values(this.state)
.some((scanProgress: LibraryScanProgress) => scanProgress.status === LibraryScanStatus.IN_PROGRESS);
},
get sortedByStartTime(): LibraryScanProgress[] {
return Object.values(this.state).sort((a: LibraryScanProgress, b: LibraryScanProgress) => {
return new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime();
});
}
});
/** Subscribe to and process state updates from backend **/
export function initializeScanState() {
if (scanState.subscription) return;
// Subscribe to real-time updates
scanState.subscription = LibraryEndpoint.subscribeToScanProgressEvents().onNext((scanProgresses: LibraryScanProgress[]) => {
scanProgresses.forEach((scanProgress: LibraryScanProgress) => {
// Filter out scans for libraries that are not in the current state
if (!libraryState.state[scanProgress.libraryId]) return;
scanState.state[scanProgress.scanId] = scanProgress;
})
});
}
export function handleLibraryDeletion(libraryId: number) {
for (const scanId in scanState.state) {
if (scanState.state[scanId].libraryId === libraryId) {
delete scanState.state[scanId];
}
}
}