mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-25 02:41:07 +01:00
Appearance setting and gzip/etc support
This commit is contained in:
@@ -10,6 +10,7 @@ import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||
import { requestsQueryKey } from '../hooks/useRequests';
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
import { responsesQueryKey } from '../hooks/useResponses';
|
||||
import { settingsQueryKey } from '../hooks/useSettings';
|
||||
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
|
||||
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
||||
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
||||
@@ -49,6 +50,8 @@ export function GlobalHooks() {
|
||||
? workspacesQueryKey(payload)
|
||||
: payload.model === 'key_value'
|
||||
? keyValueQueryKey(payload)
|
||||
: payload.model === 'settings'
|
||||
? settingsQueryKey()
|
||||
: null;
|
||||
|
||||
if (queryKey === null) {
|
||||
@@ -74,6 +77,8 @@ export function GlobalHooks() {
|
||||
? workspacesQueryKey(payload)
|
||||
: payload.model === 'key_value'
|
||||
? keyValueQueryKey(payload)
|
||||
: payload.model === 'settings'
|
||||
? settingsQueryKey()
|
||||
: null;
|
||||
|
||||
if (queryKey === null) {
|
||||
@@ -107,6 +112,8 @@ export function GlobalHooks() {
|
||||
queryClient.setQueryData<HttpResponse[]>(responsesQueryKey(payload), removeById(payload));
|
||||
} else if (payload.model === 'key_value') {
|
||||
queryClient.setQueryData(keyValueQueryKey(payload), undefined);
|
||||
} else if (payload.model === 'settings') {
|
||||
queryClient.setQueryData(settingsQueryKey(), undefined);
|
||||
}
|
||||
});
|
||||
useListenToTauriEvent<number>('zoom', ({ payload: zoomDelta, windowLabel }) => {
|
||||
|
||||
@@ -1,33 +1,83 @@
|
||||
import classNames from 'classnames';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
import { useTheme } from '../hooks/useTheme';
|
||||
import { useUpdateSettings } from '../hooks/useUpdateSettings';
|
||||
import type { Appearance } from '../lib/theme/window';
|
||||
import { Checkbox } from './core/Checkbox';
|
||||
import { Input } from './core/Input';
|
||||
import { VStack } from './core/Stacks';
|
||||
|
||||
export const SettingsDialog = () => {
|
||||
const { appearance, toggleAppearance } = useTheme();
|
||||
const { appearance, setAppearance } = useTheme();
|
||||
const settings = useSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
|
||||
if (settings == null) {
|
||||
return null;
|
||||
}
|
||||
console.log('SETTINGS', settings);
|
||||
|
||||
return (
|
||||
<VStack space={2}>
|
||||
<Checkbox
|
||||
checked={settings.validateCertificates}
|
||||
title="Validate TLS Certificates"
|
||||
onChange={(validateCertificates) =>
|
||||
updateSettings.mutateAsync({ ...settings, validateCertificates })
|
||||
}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={settings.followRedirects}
|
||||
title="Follow Redirects"
|
||||
onChange={(followRedirects) => updateSettings.mutateAsync({ ...settings, followRedirects })}
|
||||
/>
|
||||
<Checkbox checked={appearance === 'dark'} title="Dark Mode" onChange={toggleAppearance} />
|
||||
<div className="w-full gap-2 grid grid-cols-[auto_1fr] gap-x-6 auto-rows-[2rem] items-center">
|
||||
<Checkbox
|
||||
className="col-span-full"
|
||||
checked={settings.validateCertificates}
|
||||
title="Validate TLS Certificates"
|
||||
onChange={(validateCertificates) =>
|
||||
updateSettings.mutateAsync({ ...settings, validateCertificates })
|
||||
}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
className="col-span-full"
|
||||
checked={settings.followRedirects}
|
||||
title="Follow Redirects"
|
||||
onChange={(followRedirects) =>
|
||||
updateSettings.mutateAsync({ ...settings, followRedirects })
|
||||
}
|
||||
/>
|
||||
|
||||
<div>Request Timeout (ms)</div>
|
||||
<div>
|
||||
<Input
|
||||
size="sm"
|
||||
name="requestTimeout"
|
||||
label="Request Timeout (ms)"
|
||||
containerClassName="col-span-2"
|
||||
hideLabel
|
||||
defaultValue={`${settings.requestTimeout}`}
|
||||
validate={(value) => parseInt(value) >= 0}
|
||||
onChange={(v) =>
|
||||
updateSettings.mutateAsync({ ...settings, requestTimeout: parseInt(v) || 0 })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>Appearance</div>
|
||||
<select
|
||||
value={settings.appearance}
|
||||
style={selectBackgroundStyles}
|
||||
onChange={(e) => updateSettings.mutateAsync({ ...settings, appearance: e.target.value })}
|
||||
className={classNames(
|
||||
'border w-full px-2 outline-none bg-transparent',
|
||||
'border-highlight focus:border-focus',
|
||||
'h-sm',
|
||||
)}
|
||||
>
|
||||
<option value="system">Match System</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
</div>
|
||||
{/*<Checkbox checked={appearance === 'dark'} title="Dark Mode" onChange={toggleAppearance} />*/}
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
const selectBackgroundStyles = {
|
||||
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`,
|
||||
backgroundPosition: 'right 0.5rem center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundSize: '1.5em 1.5em',
|
||||
};
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { Appearance } from '../lib/theme/window';
|
||||
import {
|
||||
getAppearance,
|
||||
setAppearance,
|
||||
setAppearanceOnDocument,
|
||||
getPreferredAppearance,
|
||||
subscribeToPreferredAppearanceChange,
|
||||
} from '../lib/theme/window';
|
||||
import { useKeyValue } from './useKeyValue';
|
||||
import { useSettings } from './useSettings';
|
||||
|
||||
export function useTheme() {
|
||||
const appearanceKv = useKeyValue<Appearance>({
|
||||
key: 'appearance',
|
||||
defaultValue: getAppearance(),
|
||||
});
|
||||
const [preferredAppearance, setPreferredAppearance] = useState<Appearance>(
|
||||
getPreferredAppearance(),
|
||||
);
|
||||
|
||||
const handleToggleAppearance = async () => {
|
||||
appearanceKv.set(appearanceKv.value === 'dark' ? 'light' : 'dark');
|
||||
};
|
||||
const settings = useSettings();
|
||||
|
||||
// Set appearance when preferred theme changes
|
||||
useEffect(() => subscribeToPreferredAppearanceChange(appearanceKv.set), [appearanceKv.set]);
|
||||
useEffect(() => {
|
||||
return subscribeToPreferredAppearanceChange(setPreferredAppearance);
|
||||
}, []);
|
||||
|
||||
// Sync appearance when k/v changes
|
||||
useEffect(() => setAppearance(appearanceKv.value), [appearanceKv.value]);
|
||||
const appearance =
|
||||
settings == null || settings?.appearance === 'system'
|
||||
? preferredAppearance
|
||||
: settings.appearance;
|
||||
|
||||
return {
|
||||
appearance: appearanceKv.value,
|
||||
toggleAppearance: handleToggleAppearance,
|
||||
};
|
||||
useEffect(() => {
|
||||
if (settings == null) {
|
||||
return;
|
||||
}
|
||||
setAppearanceOnDocument(settings.appearance as Appearance);
|
||||
}, [appearance, settings]);
|
||||
|
||||
return { appearance };
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ export interface Settings extends BaseModel {
|
||||
readonly model: 'settings';
|
||||
validateCertificates: boolean;
|
||||
followRedirects: boolean;
|
||||
requestTimeout: number;
|
||||
theme: string;
|
||||
appearance: string;
|
||||
}
|
||||
|
||||
export interface Workspace extends BaseModel {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { AppTheme, AppThemeColors } from './theme';
|
||||
import { generateCSS, toTailwindVariable } from './theme';
|
||||
|
||||
export type Appearance = 'dark' | 'light';
|
||||
export type Appearance = 'dark' | 'light' | 'system';
|
||||
|
||||
const DEFAULT_APPEARANCE: Appearance = 'system';
|
||||
|
||||
enum Theme {
|
||||
yaak = 'yaak',
|
||||
@@ -61,19 +63,11 @@ const lightTheme: AppTheme = {
|
||||
},
|
||||
};
|
||||
|
||||
export function getAppearance(): Appearance {
|
||||
const docAppearance = document.documentElement.getAttribute('data-appearance');
|
||||
if (docAppearance === 'dark' || docAppearance === 'light') {
|
||||
return docAppearance;
|
||||
}
|
||||
return getPreferredAppearance();
|
||||
}
|
||||
export function setAppearanceOnDocument(appearance: Appearance = DEFAULT_APPEARANCE) {
|
||||
const resolvedAppearance = appearance === 'system' ? getPreferredAppearance() : appearance;
|
||||
const theme = resolvedAppearance === 'dark' ? darkTheme : lightTheme;
|
||||
|
||||
export function setAppearance(a?: Appearance) {
|
||||
const appearance = a ?? getPreferredAppearance();
|
||||
const theme = appearance === 'dark' ? darkTheme : lightTheme;
|
||||
|
||||
document.documentElement.setAttribute('data-appearance', appearance);
|
||||
document.documentElement.setAttribute('data-resolved-appearance', resolvedAppearance);
|
||||
document.documentElement.setAttribute('data-theme', theme.name);
|
||||
|
||||
let existingStyleEl = document.head.querySelector(`style[data-theme-definition]`);
|
||||
@@ -85,11 +79,11 @@ export function setAppearance(a?: Appearance) {
|
||||
|
||||
existingStyleEl.textContent = [
|
||||
`/* ${darkTheme.name} */`,
|
||||
`[data-appearance="dark"] {`,
|
||||
`[data-resolved-appearance="dark"] {`,
|
||||
...generateCSS(darkTheme).map(toTailwindVariable),
|
||||
'}',
|
||||
`/* ${lightTheme.name} */`,
|
||||
`[data-appearance="light"] {`,
|
||||
`[data-resolved-appearance="light"] {`,
|
||||
...generateCSS(lightTheme).map(toTailwindVariable),
|
||||
'}',
|
||||
].join('\n');
|
||||
|
||||
@@ -68,4 +68,14 @@
|
||||
--color-white: 255 100% 100%;
|
||||
--color-black: 255 0% 0%;
|
||||
}
|
||||
|
||||
select {
|
||||
@apply appearance-none;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
||||
background-position: right 0.5rem center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1.5em 1.5em;
|
||||
padding-right: 2.5rem;
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { attachConsole } from 'tauri-plugin-log-api';
|
||||
import { App } from './components/App';
|
||||
import { getKeyValue } from './lib/keyValueStore';
|
||||
import { maybeRestorePathname } from './lib/persistPathname';
|
||||
import { getPreferredAppearance, setAppearance } from './lib/theme/window';
|
||||
import './main.css';
|
||||
|
||||
await attachConsole();
|
||||
@@ -15,13 +13,6 @@ document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Backspace') e.preventDefault();
|
||||
});
|
||||
|
||||
setAppearance(
|
||||
await getKeyValue({
|
||||
key: 'appearance',
|
||||
fallback: getPreferredAppearance(),
|
||||
}),
|
||||
);
|
||||
|
||||
createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
|
||||
Reference in New Issue
Block a user