chore: port useSettings

i have to refactor settings one more time
This commit is contained in:
emirsassan 2025-06-27 10:27:51 +03:00
parent 855d421eaa
commit 7d1be7f2d3
5 changed files with 257 additions and 75 deletions

View file

@ -0,0 +1,138 @@
/* eslint-disable no-console -- ok */
/* eslint-disable react-hooks/rules-of-hooks -- stfu */
/* eslint-disable ts/naming-convention -- please shut the fuck up i need to see if its working or not */
import type { Settings } from '@/bindings.gen';
import { bindings } from '@/main';
import { useCommand } from '@onelauncher/common';
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
interface SettingsControllerType {
settings: Settings | undefined;
saveChangedSettings: () => void;
settingsToSave: Partial<Settings> | null;
setSettingsToSave: (settings: Partial<Settings> | null) => void;
settingsChanged: boolean;
setSettingsChanged: (changed: boolean) => void;
createSetting: <K extends keyof Settings, V = Settings[K]>(name: K, value: V) => [V, (value: V) => void];
}
const SettingsContext = createContext<SettingsControllerType | null>(null);
interface SettingsProviderProps {
children: React.ReactNode;
}
export function SettingsProvider({ children }: SettingsProviderProps) {
const { data: settings } = useCommand('readSettings', bindings.core.readSettings);
const [settingsChanged, setSettingsChanged] = useState(false);
const [settingsToSave, setSettingsToSave] = useState<Partial<Settings> | null>(null);
const filteredSettingsToSave = settingsToSave
? Object.fromEntries(
Object.entries(settingsToSave),
) as Partial<Settings>
: {};
const patchedSettings: Settings = {
...settings,
...filteredSettingsToSave,
} as Settings;
const writeQuery = useCommand('writeSettings', () => bindings.core.writeSettings(patchedSettings), {
enabled: false,
subscribed: false,
});
useEffect(() => {
const handleBeforeUnload = () => {
setSettingsChanged(false);
};
window.addEventListener('beforeunload', handleBeforeUnload);
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
}, []);
const saveChangedSettings = () => {
if (!settings)
return;
console.log(settings);
console.log(settingsToSave);
writeQuery.refetch();
setSettingsChanged(false);
};
const handleSetSettingsToSave = useCallback((newSettings: Partial<Settings> | null) => {
setSettingsToSave(newSettings);
setSettingsChanged(true);
}, []);
const createSetting = useCallback(<K extends keyof Settings, V = Settings[K]>(
name: K,
initialValue: V,
): [V, (value: V) => void] => {
const [value, setValue] = useState<V>(initialValue);
useEffect(() => {
const didChange = settings?.[name] !== value;
console.log(`did change? ${didChange}`);
if (didChange && settings) {
setSettingsToSave(prev => ({
...prev,
[name]: value,
}));
setSettingsChanged(true);
}
}, [value, name]);
return [value, setValue];
}, [settings]);
const controller: SettingsControllerType = {
settings,
saveChangedSettings,
settingsToSave,
setSettingsToSave: handleSetSettingsToSave,
settingsChanged,
setSettingsChanged,
createSetting,
};
if (settings === undefined)
return null;
return (
<SettingsContext.Provider value={controller}>
{children}
{settingsChanged && (
<div className="pointer-events-none absolute bottom-5 left-1/2 w-full flex flex-col items-center justify-center -translate-x-1/2">
<div className="pointer-events-auto flex flex-row gap-1 border border-component-border rounded-md bg-component-bg p-3 px-4">
You have unsaved changes!
<button
className="text-brand filter-brightness-130 hover:underline"
onClick={saveChangedSettings}
type="button"
>
Save Now
</button>
</div>
</div>
)}
</SettingsContext.Provider>
);
}
export function useSettings(): SettingsControllerType {
const context = useContext(SettingsContext);
if (!context)
throw new Error('useSettings should be called inside its SettingsProvider');
return context;
}
export default useSettings;

View file

@ -6,6 +6,7 @@ import ReactDOM from 'react-dom/client';
import { createTauRPCProxy } from './bindings.gen';
import { NotificationProvider } from './hooks/useNotification';
import { SettingsProvider } from './hooks/useSettings';
import { routeTree } from './routeTree.gen';
import './fonts';
import 'overlayscrollbars/overlayscrollbars.css';
@ -56,9 +57,11 @@ if (rootElement && !rootElement.innerHTML) {
root.render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<NotificationProvider>
<RouterProvider router={router} />
</NotificationProvider>
<SettingsProvider>
<NotificationProvider>
<RouterProvider router={router} />
</NotificationProvider>
</SettingsProvider>
</QueryClientProvider>
</StrictMode>,
);

View file

@ -1,83 +1,16 @@
import Modal from '@/components/overlay/Modal';
import useNotifications from '@/hooks/useNotification';
import { bindings } from '@/main';
import { useCommand } from '@onelauncher/common';
import { Button, Dropdown, TextField } from '@onelauncher/common/components';
import { createFileRoute } from '@tanstack/react-router';
import { BracketsEllipsesIcon } from '@untitled-theme/icons-react';
export const Route = createFileRoute('/app/browser/')({
component: RouteComponent,
});
function RouteComponent() {
const { create, list, clear } = useNotifications();
const result = useCommand('read_settings', bindings.core.readSettings);
const addSimpleNotif = () => {
create({
message: 'This is a simple notification message',
title: 'Simple Notification',
});
};
const addProgressNotif = () => {
create({
message: 'Download in progress...',
title: 'Downloading File',
fraction: Math.floor(Math.random() * 100),
});
};
const addErrorNotif = () => {
create({
message: 'Something went wrong while processing your request',
title: 'Error',
});
};
return (
<div>
<p>asdasdas</p>
<TextField />
{/* we can open the modal but we cant close it for some reason */}
<Modal.Trigger>
<Button>Open Modal</Button>
<Modal>
<p>sadsadsad</p>
</Modal>
</Modal.Trigger>
<Dropdown label="Select a version">
<Dropdown.Item>Slmalr</Dropdown.Item>
<Dropdown.Item>Slmalr 2</Dropdown.Item>
</Dropdown>
<div className="flex gap-2 flex-wrap">
<Button onClick={addSimpleNotif}>Add Simple Notification</Button>
<Button onClick={addProgressNotif}>Add Progress Notification</Button>
<Button onClick={addErrorNotif}>Add Error Notification</Button>
<Button onClick={clear}>Clear All Notifications</Button>
</div>
<div className="mt-4">
<h3 className="font-semibold mb-2">
Current Notifications (
{Object.keys(list).length}
):
</h3>
<pre className="text-xs bg-component-bg-hover p-2 rounded overflow-auto max-h-40">
{JSON.stringify(list, null, 2)}
</pre>
</div>
<div className="mt-4">
<h3>Settings</h3>
<pre className="text-xs bg-component-bg-hover p-2 rounded overflow-auto max-h-40">
{JSON.stringify(result.data, null, 2)}
</pre>
<div className="flex flex-col items-center justify-center h-full">
<BracketsEllipsesIcon className="size-40" />
<h3 className="text-xl">WIP</h3>
</div>
</div>
);

View file

@ -0,0 +1,108 @@
import Modal from '@/components/overlay/Modal';
import useNotifications from '@/hooks/useNotification';
import useSettings from '@/hooks/useSettings';
import { bindings } from '@/main';
import { useCommand } from '@onelauncher/common';
import { Button, Dropdown, Switch, TextField } from '@onelauncher/common/components';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/app/dev')({
component: RouteComponent,
});
function RouteComponent() {
const { create, list, clear } = useNotifications();
const { settings, createSetting } = useSettings();
const result = useCommand('read_settings', bindings.core.readSettings);
const addSimpleNotif = () => {
create({
message: 'This is a simple notification message',
title: 'Simple Notification',
});
};
const addProgressNotif = () => {
create({
message: 'Download in progress...',
title: 'Downloading File',
fraction: Math.floor(Math.random() * 100),
});
};
const addErrorNotif = () => {
create({
message: 'Something went wrong while processing your request',
title: 'Error',
});
};
const [parallel, setParallel] = createSetting('allow_parallel_running_clusters', settings?.allow_parallel_running_clusters);
return (
<div>
<p>this is really became my test place fr</p>
<TextField />
{/* we can open the modal but we cant close it for some reason */}
<Modal.Trigger>
<Button>Open Modal</Button>
<Modal>
<p>sadsadsad</p>
</Modal>
</Modal.Trigger>
<Dropdown label="Select a version">
<Dropdown.Item>Slmalr</Dropdown.Item>
<Dropdown.Item>Slmalr 2</Dropdown.Item>
</Dropdown>
<div className="flex gap-2 flex-wrap">
<Button onClick={addSimpleNotif}>Add Simple Notification</Button>
<Button onClick={addProgressNotif}>Add Progress Notification</Button>
<Button onClick={addErrorNotif}>Add Error Notification</Button>
<Button onClick={clear}>Clear All Notifications</Button>
</div>
<div className="mt-4">
<h3 className="font-semibold mb-2">
Current Notifications (
{Object.keys(list).length}
):
</h3>
<pre className="text-xs bg-component-bg-hover p-2 rounded overflow-auto max-h-40">
{JSON.stringify(list, null, 2)}
</pre>
</div>
<div className="flex flex-row">
<div className="w-1/2">
<div className="mt-4">
<h3>Settings</h3>
<pre className="text-xs bg-component-bg-hover p-2 rounded overflow-auto max-h-40">
{JSON.stringify(result.data, null, 2)}
</pre>
</div>
<div className="mt-4">
<h3>Settings again but this time its from useSettings hook</h3>
<pre className="text-xs bg-component-bg-hover p-2 rounded overflow-auto max-h-40">
{JSON.stringify(settings, null, 2)}
</pre>
</div>
</div>
<div className="w-1/2">
<div className="ml-2">
<pre>
{parallel ? 'Parallel running is enabled' : 'Parallel running is disabled'}
</pre>
<Switch defaultSelected={parallel} onChange={val => setParallel(val)} />
</div>
</div>
</div>
</div>
);
}

View file

@ -101,11 +101,11 @@ export function GameSettings(props: GameSettingProps) {
<div className="flex flex-row items-center gap-x-2">
<TextField
className="text-center"
defaultValue={settings.mem_max?.toString()}
onChange={(e) => {
settings.mem_max = Number(e.target.value);
}}
type="number"
value={settings.mem_max ?? 2048}
/>
</div>
</div>