OneLauncher-Cracked/apps/oneclient/frontend/src/hooks/useSettings.ts
Wyvest 3b885cdb0f
Improve modpack download resiliency, onboarding
Track and surface partial modpack download failures across core and frontend. Adds a PartialModpackInstall error and counts failed/attempted downloads in the mrpack flow, returning an error if some files failed. Frontend download pipeline now runs parallel downloads that return per-item results, reports/logs failures, and exposes retry/continue UI in onboarding.

Backend robustness improvements: deduplicate concurrent package inserts by reusing existing row on unique-hash constraint, detect hard-link collisions and verify hashes before skipping or erroring, and return meaningful errors when partial installs occur.

Other changes: propagate richer error context when stamping migrated cluster created_at, invalidate relevant queries after migration, make settings writes awaitable and update the query cache optimistically, add queryClient usage in version-migration hook, and various type/bindings updates to reflect the new PartialModpackInstall error. These changes improve reliability and UX during batch package installs and onboarding downloads.
2026-03-30 05:50:29 -04:00

76 lines
2.6 KiB
TypeScript

import type { ClusterModel, ProfileUpdate, Settings } from '@/bindings.gen';
import { bindings } from '@/main';
import { useCommandSuspense } from '@onelauncher/common';
import { useEffect, useMemo, useState } from 'react';
export function useSettings() {
const settings = useCommandSuspense(['readSettings'], bindings.core.readSettings);
const setting = useMemo(() => {
return function setting<TKey extends keyof Settings>(key: TKey) {
return settings.data[key];
};
}, [settings]);
const setSetting = useMemo(() => {
return async function setSetting<TKey extends keyof Settings>(key: TKey, value: Settings[TKey]) {
await bindings.core.writeSettings({
...settings.data,
[key]: value,
});
await settings.refetch();
};
}, [settings]);
type Setter<TKey extends keyof Settings> = (value: Settings[TKey]) => void;
const createSetting = useMemo(() => {
return function createSetting<TKey extends keyof Settings>(key: TKey): [Settings[TKey], Setter<TKey>] {
return [setting(key), value => setSetting(key, value)];
};
}, [setting, setSetting]);
return { setting, setSetting, createSetting };
}
const emptyUpdate: ProfileUpdate = {
res: null,
force_fullscreen: null,
mem_max: null,
launch_args: null,
launch_env: null,
hook_pre: null,
hook_wrapper: null,
hook_post: null,
};
export function useClusterProfile(cluster: ClusterModel) {
const [profileName, setProfileName] = useState(cluster.setting_profile_name);
const profileSrc = useCommandSuspense(['getProfileOrDefault', profileName], () => bindings.core.getProfileOrDefault(profileName));
const [profile, setProfile] = useState(profileSrc.data);
useEffect(() => {
setProfile(profileSrc.data);
}, [profileSrc.data]);
const updateProfile = useMemo(() =>
async (update: Partial<ProfileUpdate>) => {
setProfile(profile => ({ ...profile, ...update }));
let name = profileName;
if (!profileName || profileSrc.data.name !== profileName) {
name = globalThis.crypto.randomUUID();
await bindings.core.createSettingsProfile(name);
await bindings.core.updateClusterById(cluster.id, {
setting_profile_name: name,
name: null,
icon_url: null,
});
setProfileName(name);
}
if (!name)
throw new Error('No settings profile name');
await bindings.core.updateClusterProfile(name, { ...emptyUpdate, ...update });
profileSrc.refetch();
}, [profileName, cluster.id, profileSrc]);
// const updateProfile = useMemo(() => (update: Partial<ProfileUpdate>) => setProfile({ ...profile.data, ...update }), [setProfile, profile.data]);
return { profile, setProfile, updateProfile };
}