Skip to content

Commit

Permalink
feat: added loading of additional grid pages
Browse files Browse the repository at this point in the history
  • Loading branch information
Tormak9970 committed Mar 24, 2024
1 parent a292816 commit 279e36a
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 77 deletions.
90 changes: 52 additions & 38 deletions src/components/core/grids/Grids.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
import type { Unsubscriber } from "svelte/store";
import { AppController } from "../../../lib/controllers/AppController";
import type { SGDBGame, SGDBImage } from "../../../lib/models/SGDB";
import { dbFilters, gridType, GridTypes, isOnline, needsSGDBAPIKey, selectedGameAppId, selectedGameName, steamGridDBKey, type DBFilters, currentPlatform, selectedSteamGridGameId, steamGridSearchCache, Platforms, selectedResultPage, appLibraryCache, manualSteamGames, customGameNames, steamGames, nonSteamGames, gridsSize } from "../../../stores/AppState";
import { dbFilters, gridType, GridTypes, isOnline, needsSGDBAPIKey, selectedGameAppId, selectedGameName, steamGridDBKey, type DBFilters, currentPlatform, selectedSteamGridGameId, steamGridSearchCache, Platforms, appLibraryCache, manualSteamGames, customGameNames, steamGames, nonSteamGames, gridsSize, lastPageCache } from "../../../stores/AppState";
import SectionTitle from "../SectionTitle.svelte";
import Grid from "./Grid.svelte";
import DropDown from "../../interactables/DropDown.svelte";
import Pages from "../../layout/pagination/Pages.svelte";
import IconButton from "../../interactables/IconButton.svelte";
import { debounce, filterGrids } from "../../../lib/utils/Utils";
import Divider from "../Divider.svelte";
Expand All @@ -18,6 +17,7 @@
import Spacer from "../../layout/Spacer.svelte";
import PaddedScrollContainer from "../../layout/PaddedScrollContainer.svelte";
import { SMALL_GRID_DIMENSIONS } from "../../../lib/utils/ImageConstants";
import InfiniteScroll from "../../layout/pagination/InfiniteScroll.svelte";
let windowWidth: number;
let skipUpdate = false;
Expand All @@ -28,7 +28,6 @@
let selectedPlatformUnsub: Unsubscriber;
let selectedAppIdUnsub: Unsubscriber;
let dbFiltersUnsub: Unsubscriber;
let sgdbPageUnsub: Unsubscriber;
let gridTypeUnsub: Unsubscriber;
let onlineUnsub: Unsubscriber;
let apiKeyUnsub: Unsubscriber;
Expand All @@ -48,13 +47,18 @@
}
let isLoading = false;
let hasMoreSearchResults = true;
let availableSteamGridGames = [ { label: "None", data: "None" } ];
let steamGridTypes = Object.values(GridTypes).map((gridType) => { return { label: gridType, data: gridType }});
let grids: SGDBImage[] = [];
let numPages = 1;
let hasCustomName = !!$customGameNames[$selectedGameAppId];
$: originalName = ($steamGames.find((game) => game.appid === $selectedGameAppId) ?? $nonSteamGames.find((game) => game.appid === $selectedGameAppId))?.name;
function getPageNumberForGame(sgdbGameId: string) {
const id = parseInt(sgdbGameId);
if (!lastPageCache[id]) lastPageCache[id] = 0;
return lastPageCache[id];
}
/**
* Handles when the user changes the custom game name
Expand Down Expand Up @@ -109,11 +113,11 @@
*/
async function onSgdbGameChange(id: string): Promise<void> {
if ($isOnline && $steamGridDBKey !== "" && !!$selectedGameAppId && oldSelectedGameId !== id) {
grids = filterGridsWrapper(await AppController.getSteamGridArt($selectedGameAppId, $selectedResultPage, id, false), $gridType, $dbFilters);
numPages = $steamGridSearchCache[$selectedGameAppId]?.find((game) => game.id.toString() === id)?.numResultPages ?? 3;
grids = filterGridsWrapper(await AppController.getSteamGridArt($selectedGameAppId, getPageNumberForGame(id), id, false), $gridType, $dbFilters);
}
oldSelectedGameId = id;
hasMoreSearchResults = true;
}
/**
Expand All @@ -129,8 +133,6 @@
"data": value.id.toString()
}
});
numPages = searchCache[selectedAppId]?.find((game) => game.id.toString() === $selectedSteamGridGameId)?.numResultPages ?? 3;
}
}
Expand All @@ -142,6 +144,7 @@
$selectedGameAppId = null;
$selectedGameName = null;
$selectedSteamGridGameId = "None";
hasMoreSearchResults = true;
grids = [];
}
Expand All @@ -163,6 +166,19 @@
}
}
async function handleLoadOnScroll() {
const lastPageLoaded = getPageNumberForGame($selectedSteamGridGameId);
const oldGridsLength = grids.length;
await filterGridsOnStateChange($steamGridDBKey, $isOnline, $selectedGameAppId, $gridType, lastPageLoaded + 1, $dbFilters, $selectedSteamGridGameId, hasCustomName);
if (oldGridsLength !== grids.length) {
lastPageCache[parseInt($selectedSteamGridGameId)] = lastPageLoaded + 1;
} else {
hasMoreSearchResults = false;
}
}
const debouncedWidthUpdate = debounce(() => windowWidth = window.innerWidth, 50);
onMount(() => {
Expand All @@ -186,12 +202,12 @@
hasCustomName = true;
$selectedGameName = customNames[$selectedGameAppId];
delete $steamGridSearchCache[$selectedGameAppId];
await filterGridsOnStateChange($steamGridDBKey, $isOnline, $selectedGameAppId, $gridType, $selectedResultPage, $dbFilters, null, true);
await filterGridsOnStateChange($steamGridDBKey, $isOnline, $selectedGameAppId, $gridType, getPageNumberForGame($selectedSteamGridGameId), $dbFilters, null, true);
} else if (!customNames[$selectedGameAppId] && hasCustomName) {
hasCustomName = false;
$selectedGameName = originalName;
delete $steamGridSearchCache[$selectedGameAppId];
await filterGridsOnStateChange($steamGridDBKey, $isOnline, $selectedGameAppId, $gridType, $selectedResultPage, $dbFilters, null, true);
await filterGridsOnStateChange($steamGridDBKey, $isOnline, $selectedGameAppId, $gridType, getPageNumberForGame($selectedSteamGridGameId), $dbFilters, null, true);
}
} else {
skipUpdate = false;
Expand All @@ -206,37 +222,32 @@
selectedAppIdUnsub = selectedGameAppId.subscribe(async (id) => {
isLoading = true;
hasCustomName = !!$customGameNames[$selectedGameAppId];
await filterGridsOnStateChange($steamGridDBKey, $isOnline, id, $gridType, $selectedResultPage, $dbFilters, null, hasCustomName);
await filterGridsOnStateChange($steamGridDBKey, $isOnline, id, $gridType, getPageNumberForGame($selectedSteamGridGameId), $dbFilters, null, hasCustomName);
isLoading = false;
});
onlineUnsub = isOnline.subscribe(async (online) => {
isLoading = true;
await filterGridsOnStateChange($steamGridDBKey, online, $selectedGameAppId, $gridType, $selectedResultPage, $dbFilters, $selectedSteamGridGameId, hasCustomName);
isLoading = false;
});
sgdbPageUnsub = selectedResultPage.subscribe(async (page) => {
isLoading = true;
await filterGridsOnStateChange($steamGridDBKey, $isOnline, $selectedGameAppId, $gridType, page, $dbFilters, $selectedSteamGridGameId, hasCustomName);
await filterGridsOnStateChange($steamGridDBKey, online, $selectedGameAppId, $gridType, getPageNumberForGame($selectedSteamGridGameId), $dbFilters, $selectedSteamGridGameId, hasCustomName);
isLoading = false;
});
gridTypeUnsub = gridType.subscribe(async (type) => {
isLoading = true;
await filterGridsOnStateChange($steamGridDBKey, $isOnline, $selectedGameAppId, type, $selectedResultPage, $dbFilters, $selectedSteamGridGameId, hasCustomName);
await filterGridsOnStateChange($steamGridDBKey, $isOnline, $selectedGameAppId, type, getPageNumberForGame($selectedSteamGridGameId), $dbFilters, $selectedSteamGridGameId, hasCustomName);
isLoading = false;
});
apiKeyUnsub = steamGridDBKey.subscribe(async (key) => {
isLoading = true;
if (key !== "" && AppController.sgdbClientInitialized()) {
await filterGridsOnStateChange(key, $isOnline, $selectedGameAppId, $gridType, $selectedResultPage, $dbFilters, $selectedSteamGridGameId, hasCustomName);
await filterGridsOnStateChange(key, $isOnline, $selectedGameAppId, $gridType, getPageNumberForGame($selectedSteamGridGameId), $dbFilters, $selectedSteamGridGameId, hasCustomName);
} else {
resetGridStores();
}
isLoading = false;
});
dbFiltersUnsub = dbFilters.subscribe(async (filters) => {
isLoading = true;
await filterGridsOnStateChange($steamGridDBKey, $isOnline, $selectedGameAppId, $gridType, $selectedResultPage, filters, $selectedSteamGridGameId, hasCustomName);
await filterGridsOnStateChange($steamGridDBKey, $isOnline, $selectedGameAppId, $gridType, lastPageCache[$selectedSteamGridGameId], filters, $selectedSteamGridGameId, hasCustomName);
isLoading = false;
});
});
Expand Down Expand Up @@ -310,29 +321,32 @@
{#if $isOnline}
{#if !$needsSGDBAPIKey}
{#if !!$selectedGameAppId}
<Pages numPages={numPages} height="calc(100% - 47px)" bind:selected={$selectedResultPage}>
<PaddedScrollContainer height={"calc(100% - 7px)"} width={"100%"} background={"transparent"} loading={isLoading} marginTop="0px">
{#if isLoading}
<PaddedScrollContainer height={"calc(100% - 7px)"} width={"100%"} background={"transparent"} loading={isLoading} marginTop="0px">
{#if isLoading}
<div class="game-grid" style="--img-width: {SMALL_GRID_DIMENSIONS.widths[$gridType] + padding}px; --img-height: {SMALL_GRID_DIMENSIONS.heights[$gridType] + padding + 18}px;">
{#each new Array(100) as _}
<GridLoadingSkeleton />
{/each}
</div>
{:else}
{#if grids.length > 0}
<div class="game-grid" style="--img-width: {SMALL_GRID_DIMENSIONS.widths[$gridType] + padding}px; --img-height: {SMALL_GRID_DIMENSIONS.heights[$gridType] + padding + 18}px;">
{#each new Array(100) as _}
<GridLoadingSkeleton />
{#each grids as grid (`${$selectedSteamGridGameId}|${grid.id}|${$gridType}`)}
<Grid grid={grid} />
{/each}
</div>
{:else}
{#if grids.length > 0}
<div class="game-grid" style="--img-width: {SMALL_GRID_DIMENSIONS.widths[$gridType] + padding}px; --img-height: {SMALL_GRID_DIMENSIONS.heights[$gridType] + padding + 18}px;">
{#each grids as grid (`${$selectedSteamGridGameId}|${grid.id}|${$gridType}`)}
<Grid grid={grid} />
{/each}
</div>
{:else}
<div class="message">
No results for {$gridType === GridTypes.HERO ? "Heroe" : $gridType}s for "{$selectedGameName}".
</div>
{/if}
<div class="message">
No results for {$gridType === GridTypes.HERO ? "Heroe" : $gridType}s for "{$selectedGameName}".
</div>
{/if}
</PaddedScrollContainer>
</Pages>
{/if}
<InfiniteScroll
hasMore={hasMoreSearchResults}
threshold={100}
on:loadMore={handleLoadOnScroll}
/>
</PaddedScrollContainer>
{:else}
<div class="message">
Select a game to start managing your art!
Expand Down
46 changes: 46 additions & 0 deletions src/components/layout/pagination/InfiniteScroll.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script lang="ts">
import { onDestroy, createEventDispatcher } from "svelte";
export let threshold = 0;
export let horizontal = false;
export let hasMore = true;
const dispatch = createEventDispatcher();
let isLoadMore = false;
let component: any;
$: {
if (component) {
const element = component.parentNode.parentNode;
element.addEventListener("scroll", onScroll);
element.addEventListener("resize", onScroll);
}
}
function onScroll(e: any) {
const offset = horizontal
? e.target.scrollWidth - e.target.clientWidth - e.target.scrollLeft
: e.target.scrollHeight - e.target.clientHeight - e.target.scrollTop;
if (offset <= threshold) {
if (!isLoadMore && hasMore) {
dispatch("loadMore");
}
isLoadMore = true;
} else {
isLoadMore = false;
}
}
onDestroy(() => {
if (component) {
const element = component.parentNode.parentNode;
element.removeEventListener("scroll", null);
element.removeEventListener("resize", null);
}
});
</script>

<div bind:this={component} style="width:0px" />
44 changes: 8 additions & 36 deletions src/lib/controllers/CacheController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { appCacheDir } from "@tauri-apps/api/path";

import { get, type Unsubscriber } from "svelte/store";
import { RequestError, SGDB, type SGDBGame, type SGDBImage } from "../models/SGDB";
import { dowloadingGridId, gridType, GridTypes, steamGridSearchCache, Platforms, selectedGameName, steamGridDBKey, gridsCache, selectedSteamGridGameId, steamGridSteamAppIdMap, selectedResultPage, canSave, appLibraryCache, steamGames, nonSteamGames, steamShortcuts, dbFilters, requestTimeoutLength, manualSteamGames } from "../../stores/AppState";
import { dowloadingGridId, gridType, GridTypes, steamGridSearchCache, Platforms, steamGridDBKey, gridsCache, lastPageCache, selectedSteamGridGameId, steamGridSteamAppIdMap, canSave, appLibraryCache, steamGames, nonSteamGames, steamShortcuts, dbFilters, requestTimeoutLength, manualSteamGames } from "../../stores/AppState";
import { batchApplyWasCancelled, showBatchApplyProgress, batchApplyProgress, batchApplyMessage } from "../../stores/Modals";
import { LogController } from "./LogController";
import { RustInterop } from "./RustInterop";
Expand Down Expand Up @@ -211,54 +211,29 @@ export class CacheController {
const types = Object.keys(gridsCache[appId.toString()]);

if (types.includes(type)) {
const pages = Object.keys(gridsCache[appId.toString()][type]);

if (pages.includes(page.toString())) {
logToFile(`Using in memory cache for nonSteam ${appId}'s ${type}.`, useCoreFile);
return gridsCache[appId.toString()][type][page];
} else {
logToFile(`Need to fetch nonSteam ${type} for ${appId}.`, useCoreFile);
const grids = await this.client[`get${type.includes("Capsule") ? "Grid": (type === GridTypes.HERO ? "Heroe" : type)}sById`](appId, undefined, undefined, undefined, [ "static", "animated" ], "any", "any", "any", page);
gridsCache[appId.toString()][type][page.toString()] = grids;
return grids;
}
logToFile(`Need to fetch nonSteam ${type} for ${appId}.`, useCoreFile);
const grids = await this.client[`get${type.includes("Capsule") ? "Grid": (type === GridTypes.HERO ? "Heroe" : type)}sById`](appId, undefined, undefined, undefined, [ "static", "animated" ], "any", "any", "any", page);
gridsCache[appId.toString()][type] = gridsCache[appId.toString()][type].concat(grids);
} else {
logToFile(`Need to fetch nonSteam ${type} for ${appId}.`, useCoreFile);
const grids = await this.client[`get${type.includes("Capsule") ? "Grid": (type === GridTypes.HERO ? "Heroe" : type)}sById`](appId, undefined, undefined, undefined, [ "static", "animated" ], "any", "any", "any", page);
gridsCache[appId.toString()][type] = {};
gridsCache[appId.toString()][type][page.toString()] = grids;
return grids;
gridsCache[appId.toString()][type] = grids;
}
} else {
logToFile(`Need to fetch nonSteam ${type} for ${appId}.`, useCoreFile);
const grids = await this.client[`get${type.includes("Capsule") ? "Grid": (type === GridTypes.HERO ? "Heroe" : type)}sById`](appId, undefined, undefined, undefined, [ "static", "animated" ], "any", "any", "any", page);
gridsCache[appId.toString()] = {};
gridsCache[appId.toString()][type] = {};
gridsCache[appId.toString()][type][page.toString()] = grids;
return grids;
gridsCache[appId.toString()][type] = grids;
}

return gridsCache[appId.toString()][type];
} catch (e: any) {
logErrorToFile(`Error fetching grids for non steam game: ${appId}. Error: ${e.message}.`, useCoreFile);
ToastController.showWarningToast("Error fetching grids for game.");
return [];
}
}

/**
* ! Placeholder until SGDB API V3 is live.
* Gets the number of result pages for each game in the results list.
* @param results The SGDBGame array.
* @param platform The platform of the games.
* @param type The type of grids to get.
* ! Logging Needed?
*/
private async getNumPages(results: SGDBGame[], platform: Platforms, type: GridTypes): Promise<void> {
results = results.map((game) => {
game.numResultPages = 1;
return game;
});
}

/**
* Gets the current type of grid for the provided app id.
* @param appId The id of the app to fetch.
Expand All @@ -275,16 +250,13 @@ export class CacheController {
logToFile(`Fetching grids for game ${appId}...`, useCoreFile);

const type = get(gridType);

const searchCache = get(steamGridSearchCache);

let results = searchCache[appId];

if (!results) {
try {
results = await this.client.searchGame(gameName);
await this.getNumPages(results, Platforms.STEAM, type);
// await this.cacheAllGridsForGame(results, Platforms.STEAM, type, useCoreFile);
searchCache[appId] = results;
} catch (e: any) {
logErrorToFile(`Error searching for game on SGDB. Game: ${gameName}. Platform: ${selectedPlatform}. Error: ${e.message}.`, useCoreFile);
Expand Down
5 changes: 2 additions & 3 deletions src/stores/AppState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ export const steamKey = writable("");

export const steamInstallPath = writable("");

export const selectedResultPage = writable(0);

export const canSave = writable(false);
export const isOnline = writable(false);
export const loadingGames = writable(true);
Expand Down Expand Up @@ -85,7 +83,8 @@ export const appLibraryCache: Writable<{ [appid: string]: LibraryCacheEntry }> =
export const steamGridSteamAppIdMap: { [appid: number]: string } = {};
export const steamGridSearchCache:Writable<{ [appid: number]: SGDBGame[] }> = writable({});
export const steamGridNameSearchCache: { [query: string]: SGDBGame[] } = {};
export const gridsCache:{ [steamGridId: number]: SGDBImage[] } = {};
export const gridsCache: { [steamGridId: number]: SGDBImage[] } = {};
export const lastPageCache: { [steamGridId: number]: number } = {};
export const selectedSteamGridGameId = writable("None");

export const originalLogoPositions:Writable<{ [appid: string]: SteamLogoConfig }> = writable({});
Expand Down

0 comments on commit 279e36a

Please sign in to comment.