Skip to content

Commit 963e704

Browse files
committed
add backups for art images
1 parent e402783 commit 963e704

5 files changed

Lines changed: 84 additions & 14 deletions

File tree

apps/oneclient/frontend/src/bindings.gen.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ export type SettingProfileModel = { name: string; java_id: number | null; res: R
250250

251251
export type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; seen_onboarding: boolean; mod_list_use_grid: boolean; max_concurrent_requests: number; settings_version: number; native_window_frame: boolean; log_debug_info: boolean; show_tanstack_dev_tools: boolean }
252252

253-
export type SettingsOsExtra = { enable_gamemode: boolean | null }
253+
export type SettingsOsExtra = Record<string, never>
254254

255255
export type SkinVariant = "classic" | "slim"
256256

@@ -317,12 +317,10 @@ export type VersionType =
317317
*/
318318
"old_beta"
319319

320-
const ARGS_MAP = { 'debug':'{"getFullDebugInfoParsedString":[],"getBuildTimestamp":[],"getPackageVersion":[],"openDevTools":[],"getFamily":[],"getArch":[],"isInDev":[],"getFullDebugInfo":[],"getType":[],"getLocale":[],"getPlatform":[],"getOsDistro":[],"getGitCommitHash":[],"getOsVersion":[],"getFullDebugInfoParsed":[]}', 'oneclient':'{"updateBundlePackages":["cluster_id"],"installUpdate":[],"checkForUpdate":[],"cacheArt":["path"],"extractBundleOverrides":["bundle_path","cluster_id"],"isBundleSyncing":[],"getBundlesFor":["cluster_id"],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"],"getVersions":[],"getClustersGroupedByMajor":[],"refreshArt":["path"]}', 'events':'{"ingress":["event"],"message":["event"],"process":["event"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'core':'{"removeCape":["access_token"],"changeSkin":["access_token","skin_url","skin_variant"],"getLinkedPackages":["cluster_id"],"fetchMinecraftProfile":["uuid"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"updateClusterById":["id","request"],"getLogs":["id"],"openMsaLogin":[],"getGameVersions":[],"setDefaultUser":["uuid"],"syncCluster":["cluster_id"],"refreshAccount":["uuid"],"removeCluster":["id"],"removePackage":["cluster_id","package_hash"],"convertUsernameUUID":["username_uuid"],"getClusterById":["id"],"fetchLoggedInProfile":["access_token"],"removeUser":["uuid"],"getLogByName":["id","name"],"launchCluster":["id","uuid","search_for_java"],"readSettings":[],"createCluster":["options"],"getGlobalProfile":[],"isClusterRunning":["cluster_id"],"searchPackages":["provider","query"],"getRunningProcessesByClusterId":["cluster_id"],"refreshAccounts":[],"getDefaultUser":["fallback"],"getUsers":[],"getPackage":["provider","slug"],"getPackageBody":["provider","body"],"getClusters":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"setDiscordRPCMessage":["message"],"updateClusterProfile":["name","profile"],"setClusterStage":["id","stage"],"getUser":["uuid"],"killProcess":["pid"],"getWorlds":["id"],"installModpack":["modpack","cluster_id"],"open":["input"],"createSettingsProfile":["name"],"getProcessLogTail":["id","max_lines"],"getMultiplePackages":["provider","slugs"],"getRunningProcesses":[],"getLoadersForVersion":["mc_version"],"getProfileOrDefault":["name"],"writeSettings":["setting"],"getScreenshots":["id"],"getUsersFromAuthor":["provider","author"],"togglePackage":["cluster_id","package_hash"],"changeCape":["access_token","cape_uuid"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"]}' }
320+
const ARGS_MAP = { 'debug':'{"openDevTools":[],"getLocale":[],"getFullDebugInfoParsedString":[],"getArch":[],"getPlatform":[],"getOsVersion":[],"getOsDistro":[],"isInDev":[],"getFullDebugInfoParsed":[],"getFamily":[],"getGitCommitHash":[],"getType":[],"getPackageVersion":[],"getFullDebugInfo":[],"getBuildTimestamp":[]}', 'events':'{"message":["event"],"process":["event"],"ingress":["event"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'core':'{"launchCluster":["id","uuid","search_for_java"],"getRunningProcessesByClusterId":["cluster_id"],"getDefaultUser":["fallback"],"refreshAccounts":[],"getPackage":["provider","slug"],"searchPackages":["provider","query"],"getProcessLogTail":["id","max_lines"],"getMultiplePackages":["provider","slugs"],"getUsersFromAuthor":["provider","author"],"togglePackage":["cluster_id","package_hash"],"fetchLoggedInProfile":["access_token"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"changeCape":["access_token","cape_uuid"],"fetchMinecraftProfile":["uuid"],"setClusterStage":["id","stage"],"convertUsernameUUID":["username_uuid"],"getGlobalProfile":[],"writeSettings":["setting"],"getScreenshots":["id"],"removeUser":["uuid"],"setDiscordRPCMessage":["message"],"isClusterRunning":["cluster_id"],"updateClusterProfile":["name","profile"],"getGameVersions":[],"installModpack":["modpack","cluster_id"],"getRunningProcesses":[],"syncCluster":["cluster_id"],"changeSkin":["access_token","skin_url","skin_variant"],"removeCape":["access_token"],"getLoadersForVersion":["mc_version"],"open":["input"],"getLinkedPackages":["cluster_id"],"getWorlds":["id"],"getLogs":["id"],"createCluster":["options"],"getLogByName":["id","name"],"getClusters":[],"getProfileOrDefault":["name"],"getUser":["uuid"],"createSettingsProfile":["name"],"openMsaLogin":[],"readSettings":[],"getPackageBody":["provider","body"],"removeCluster":["id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getClusterById":["id"],"updateClusterById":["id","request"],"killProcess":["pid"],"setDefaultUser":["uuid"],"removePackage":["cluster_id","package_hash"],"getUsers":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"refreshAccount":["uuid"]}', 'oneclient':'{"cacheArt":["path"],"getClustersGroupedByMajor":[],"refreshArt":["path"],"installUpdate":[],"extractBundleOverrides":["bundle_path","cluster_id"],"checkForUpdate":[],"updateBundlePackages":["cluster_id"],"getBundlesFor":["cluster_id"],"isBundleSyncing":[],"getVersions":[],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"]}' }
321321
export type Router = { 'events': { ingress: (event: IngressPayload) => Promise<void>,
322322
message: (event: MessagePayload) => Promise<void>,
323323
process: (event: ProcessPayload) => Promise<void> },
324-
'folders': { fromCluster: (folderName: string) => Promise<string>,
325-
openCluster: (folderName: string) => Promise<null> },
326324
'debug': { openDevTools: () => Promise<void>,
327325
isInDev: () => Promise<boolean>,
328326
getArch: () => Promise<string>,
@@ -338,6 +336,8 @@ getPackageVersion: () => Promise<string>,
338336
getFullDebugInfo: () => Promise<DebugInfoData>,
339337
getFullDebugInfoParsed: () => Promise<DebugInfoParsedLine[]>,
340338
getFullDebugInfoParsedString: () => Promise<string> },
339+
'folders': { fromCluster: (folderName: string) => Promise<string>,
340+
openCluster: (folderName: string) => Promise<null> },
341341
'core': { getClusters: () => Promise<ClusterModel[]>,
342342
getClusterById: (id: number) => Promise<ClusterModel | null>,
343343
removeCluster: (id: number) => Promise<null>,

apps/oneclient/frontend/src/hooks/useCachedImage.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,52 @@ import { useEffect, useState } from 'react';
55
// Track which paths have already been refreshed this session so we only
66
// hit the network once per path, not on every component mount.
77
const refreshedPaths: Set<string> = new Set();
8+
const prefetchedPaths: Set<string> = new Set();
9+
10+
function normalizePaths(paths: Array<string | null | undefined>): Array<string> {
11+
return Array.from(new Set(paths.filter((path): path is string => Boolean(path))));
12+
}
13+
14+
/**
15+
* Eagerly caches a list of art paths with bounded concurrency.
16+
* This is intended for onboarding prefetch so version cards can render
17+
* immediately from disk cache instead of waiting for per-card fetches.
18+
*/
19+
export async function prefetchCachedImages(
20+
paths: Array<string | null | undefined>,
21+
options?: { concurrency?: number },
22+
): Promise<void> {
23+
const normalizedPaths = normalizePaths(paths).filter(path => !prefetchedPaths.has(path));
24+
if (normalizedPaths.length === 0)
25+
return;
26+
27+
const concurrency = Math.max(1, options?.concurrency ?? 4);
28+
let nextIndex = 0;
29+
30+
const worker = async () => {
31+
while (nextIndex < normalizedPaths.length) {
32+
const index = nextIndex++;
33+
const path = normalizedPaths[index];
34+
if (!path)
35+
continue;
36+
37+
prefetchedPaths.add(path);
38+
try {
39+
await bindings.oneclient.cacheArt(path);
40+
if (!refreshedPaths.has(path)) {
41+
refreshedPaths.add(path);
42+
bindings.oneclient.refreshArt(path).catch(() => {});
43+
}
44+
}
45+
catch {
46+
// Keep failures non-fatal and allow retry in a future prefetch call.
47+
prefetchedPaths.delete(path);
48+
}
49+
}
50+
};
51+
52+
await Promise.all(Array.from({ length: Math.min(concurrency, normalizedPaths.length) }, worker));
53+
}
854

955
/**
1056
* Returns the best URL for an art image at the given relative path (e.g. `/versions/art/Foo.png`).

apps/oneclient/frontend/src/routes/onboarding/preferences/version.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,17 @@ function VersionCard({ cluster, versionData: _versionData, version, fullVersionN
8787
return (
8888
<AriaButton className={twMerge('group overflow-hidden cursor-pointer w-full rounded-xl transition-[outline] outline-2 hover:outline-brand', isSelected ? 'outline-brand' : 'outline-ghost-overlay')} onPress={toggle}>
8989
<div className="relative w-full">
90-
<img
91-
alt={`Minecraft ${fullVersionName} landscape`}
92-
className={twMerge('w-full rounded-xl h-32 object-cover transition-[filter] group-hover:brightness-100 group-hover:grayscale-0', isSelected ? 'brightness-100 grayscale-0' : 'brightness-70 grayscale-25')}
93-
src={artSrc}
94-
/>
90+
{artSrc
91+
? (
92+
<img
93+
alt={`Minecraft ${fullVersionName} landscape`}
94+
className={twMerge('w-full rounded-xl h-32 object-cover transition-[filter] group-hover:brightness-100 group-hover:grayscale-0', isSelected ? 'brightness-100 grayscale-0' : 'brightness-70 grayscale-25')}
95+
src={artSrc}
96+
/>
97+
)
98+
: (
99+
<div className={twMerge('w-full rounded-xl h-32 bg-page-elevated transition-[filter] group-hover:brightness-100 group-hover:grayscale-0', isSelected ? 'brightness-100 grayscale-0' : 'brightness-70 grayscale-25')} />
100+
)}
95101

96102
<div className="absolute top-3 left-3 flex flex-wrap gap-1">
97103
{

apps/oneclient/frontend/src/routes/onboarding/preferences/versionCategory.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,17 @@ function ModCategoryCard({ art, fullVersionName, bundle, mods, setMods, clusterI
220220
return (
221221
<AriaButton className={twMerge('group cursor-pointer w-full rounded-xl transition-[outline] outline-2 hover:outline-brand', isSelected ? 'outline-brand' : 'outline-ghost-overlay')} onPress={handleDownload}>
222222
<div className="relative w-full">
223-
<img
224-
alt={`Minecraft ${fullVersionName} landscape`}
225-
className={twMerge('w-full rounded-xl h-32 object-cover transition-[filter] group-hover:brightness-100 group-hover:grayscale-0', isSelected ? 'brightness-100 grayscale-0' : 'brightness-70 grayscale-25')}
226-
src={artSrc}
227-
/>
223+
{artSrc
224+
? (
225+
<img
226+
alt={`Minecraft ${fullVersionName} landscape`}
227+
className={twMerge('w-full rounded-xl h-32 object-cover transition-[filter] group-hover:brightness-100 group-hover:grayscale-0', isSelected ? 'brightness-100 grayscale-0' : 'brightness-70 grayscale-25')}
228+
src={artSrc}
229+
/>
230+
)
231+
: (
232+
<div className={twMerge('w-full rounded-xl h-32 bg-page-elevated transition-[filter] group-hover:brightness-100 group-hover:grayscale-0', isSelected ? 'brightness-100 grayscale-0' : 'brightness-70 grayscale-25')} />
233+
)}
228234

229235
<div className={twMerge('absolute -top-2 right-3', isSelected ? 'block' : 'hidden group-hover:block')}>
230236
<div className="bg-[#D0D7F3] rounded-xl text-brand text-sm px-2 py-1">

apps/oneclient/frontend/src/routes/onboarding/route.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { DownloadModsRef } from '@/components';
22
import type { PropsWithChildren } from 'react';
33
import LauncherLogo from '@/assets/logos/oneclient.svg?react';
44
import { GameBackground, LoaderSuspense, MadeBy, NavbarButton, Overlay, Stepper, SuperSecretDevOptions } from '@/components';
5+
import { prefetchCachedImages } from '@/hooks/useCachedImage';
56
import { bindings } from '@/main';
67
import { useCommandSuspense } from '@onelauncher/common';
78
import { Button } from '@onelauncher/common/components';
@@ -105,6 +106,7 @@ function RouteComponent() {
105106
// Prefetch data so that onboarding/preferences/versionCategory is fast
106107
const queryClient = useQueryClient();
107108
const { data: clusters } = useCommandSuspense(['getClusters'], () => bindings.core.getClusters());
109+
const { data: versions } = useCommandSuspense(['getVersions'], () => bindings.oneclient.getVersions());
108110
useEffect(() => {
109111
clusters.forEach((cluster) => {
110112
queryClient.prefetchQuery({
@@ -113,6 +115,16 @@ function RouteComponent() {
113115
});
114116
});
115117
}, [clusters, queryClient]);
118+
useEffect(() => {
119+
const artPaths: Array<string | null | undefined> = ['/versions/art/Horse_Update.jpg'];
120+
for (const cluster of versions.clusters) {
121+
artPaths.push(cluster.art);
122+
for (const entry of cluster.entries)
123+
artPaths.push(entry.art);
124+
}
125+
126+
void prefetchCachedImages(artPaths);
127+
}, [versions.clusters]);
116128

117129
const { currentLinearStepIndex } = Route.useLoaderData();
118130

0 commit comments

Comments
 (0)