add backups for art images
This commit is contained in:
parent
e40278366c
commit
963e7044f2
5 changed files with 84 additions and 14 deletions
|
|
@ -250,7 +250,7 @@ export type SettingProfileModel = { name: string; java_id: number | null; res: R
|
|||
|
||||
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 }
|
||||
|
||||
export type SettingsOsExtra = { enable_gamemode: boolean | null }
|
||||
export type SettingsOsExtra = Record<string, never>
|
||||
|
||||
export type SkinVariant = "classic" | "slim"
|
||||
|
||||
|
|
@ -317,12 +317,10 @@ export type VersionType =
|
|||
*/
|
||||
"old_beta"
|
||||
|
||||
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"]}' }
|
||||
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"]}' }
|
||||
export type Router = { 'events': { ingress: (event: IngressPayload) => Promise<void>,
|
||||
message: (event: MessagePayload) => Promise<void>,
|
||||
process: (event: ProcessPayload) => Promise<void> },
|
||||
'folders': { fromCluster: (folderName: string) => Promise<string>,
|
||||
openCluster: (folderName: string) => Promise<null> },
|
||||
'debug': { openDevTools: () => Promise<void>,
|
||||
isInDev: () => Promise<boolean>,
|
||||
getArch: () => Promise<string>,
|
||||
|
|
@ -338,6 +336,8 @@ getPackageVersion: () => Promise<string>,
|
|||
getFullDebugInfo: () => Promise<DebugInfoData>,
|
||||
getFullDebugInfoParsed: () => Promise<DebugInfoParsedLine[]>,
|
||||
getFullDebugInfoParsedString: () => Promise<string> },
|
||||
'folders': { fromCluster: (folderName: string) => Promise<string>,
|
||||
openCluster: (folderName: string) => Promise<null> },
|
||||
'core': { getClusters: () => Promise<ClusterModel[]>,
|
||||
getClusterById: (id: number) => Promise<ClusterModel | null>,
|
||||
removeCluster: (id: number) => Promise<null>,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,52 @@ import { useEffect, useState } from 'react';
|
|||
// Track which paths have already been refreshed this session so we only
|
||||
// hit the network once per path, not on every component mount.
|
||||
const refreshedPaths: Set<string> = new Set();
|
||||
const prefetchedPaths: Set<string> = new Set();
|
||||
|
||||
function normalizePaths(paths: Array<string | null | undefined>): Array<string> {
|
||||
return Array.from(new Set(paths.filter((path): path is string => Boolean(path))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Eagerly caches a list of art paths with bounded concurrency.
|
||||
* This is intended for onboarding prefetch so version cards can render
|
||||
* immediately from disk cache instead of waiting for per-card fetches.
|
||||
*/
|
||||
export async function prefetchCachedImages(
|
||||
paths: Array<string | null | undefined>,
|
||||
options?: { concurrency?: number },
|
||||
): Promise<void> {
|
||||
const normalizedPaths = normalizePaths(paths).filter(path => !prefetchedPaths.has(path));
|
||||
if (normalizedPaths.length === 0)
|
||||
return;
|
||||
|
||||
const concurrency = Math.max(1, options?.concurrency ?? 4);
|
||||
let nextIndex = 0;
|
||||
|
||||
const worker = async () => {
|
||||
while (nextIndex < normalizedPaths.length) {
|
||||
const index = nextIndex++;
|
||||
const path = normalizedPaths[index];
|
||||
if (!path)
|
||||
continue;
|
||||
|
||||
prefetchedPaths.add(path);
|
||||
try {
|
||||
await bindings.oneclient.cacheArt(path);
|
||||
if (!refreshedPaths.has(path)) {
|
||||
refreshedPaths.add(path);
|
||||
bindings.oneclient.refreshArt(path).catch(() => {});
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Keep failures non-fatal and allow retry in a future prefetch call.
|
||||
prefetchedPaths.delete(path);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await Promise.all(Array.from({ length: Math.min(concurrency, normalizedPaths.length) }, worker));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best URL for an art image at the given relative path (e.g. `/versions/art/Foo.png`).
|
||||
|
|
|
|||
|
|
@ -87,11 +87,17 @@ function VersionCard({ cluster, versionData: _versionData, version, fullVersionN
|
|||
return (
|
||||
<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}>
|
||||
<div className="relative w-full">
|
||||
<img
|
||||
alt={`Minecraft ${fullVersionName} landscape`}
|
||||
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')}
|
||||
src={artSrc}
|
||||
/>
|
||||
{artSrc
|
||||
? (
|
||||
<img
|
||||
alt={`Minecraft ${fullVersionName} landscape`}
|
||||
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')}
|
||||
src={artSrc}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<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')} />
|
||||
)}
|
||||
|
||||
<div className="absolute top-3 left-3 flex flex-wrap gap-1">
|
||||
{
|
||||
|
|
|
|||
|
|
@ -220,11 +220,17 @@ function ModCategoryCard({ art, fullVersionName, bundle, mods, setMods, clusterI
|
|||
return (
|
||||
<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}>
|
||||
<div className="relative w-full">
|
||||
<img
|
||||
alt={`Minecraft ${fullVersionName} landscape`}
|
||||
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')}
|
||||
src={artSrc}
|
||||
/>
|
||||
{artSrc
|
||||
? (
|
||||
<img
|
||||
alt={`Minecraft ${fullVersionName} landscape`}
|
||||
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')}
|
||||
src={artSrc}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<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')} />
|
||||
)}
|
||||
|
||||
<div className={twMerge('absolute -top-2 right-3', isSelected ? 'block' : 'hidden group-hover:block')}>
|
||||
<div className="bg-[#D0D7F3] rounded-xl text-brand text-sm px-2 py-1">
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { DownloadModsRef } from '@/components';
|
|||
import type { PropsWithChildren } from 'react';
|
||||
import LauncherLogo from '@/assets/logos/oneclient.svg?react';
|
||||
import { GameBackground, LoaderSuspense, MadeBy, NavbarButton, Overlay, Stepper, SuperSecretDevOptions } from '@/components';
|
||||
import { prefetchCachedImages } from '@/hooks/useCachedImage';
|
||||
import { bindings } from '@/main';
|
||||
import { useCommandSuspense } from '@onelauncher/common';
|
||||
import { Button } from '@onelauncher/common/components';
|
||||
|
|
@ -105,6 +106,7 @@ function RouteComponent() {
|
|||
// Prefetch data so that onboarding/preferences/versionCategory is fast
|
||||
const queryClient = useQueryClient();
|
||||
const { data: clusters } = useCommandSuspense(['getClusters'], () => bindings.core.getClusters());
|
||||
const { data: versions } = useCommandSuspense(['getVersions'], () => bindings.oneclient.getVersions());
|
||||
useEffect(() => {
|
||||
clusters.forEach((cluster) => {
|
||||
queryClient.prefetchQuery({
|
||||
|
|
@ -113,6 +115,16 @@ function RouteComponent() {
|
|||
});
|
||||
});
|
||||
}, [clusters, queryClient]);
|
||||
useEffect(() => {
|
||||
const artPaths: Array<string | null | undefined> = ['/versions/art/Horse_Update.jpg'];
|
||||
for (const cluster of versions.clusters) {
|
||||
artPaths.push(cluster.art);
|
||||
for (const entry of cluster.entries)
|
||||
artPaths.push(entry.art);
|
||||
}
|
||||
|
||||
void prefetchCachedImages(artPaths);
|
||||
}, [versions.clusters]);
|
||||
|
||||
const { currentLinearStepIndex } = Route.useLoaderData();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue