Appearance setting and gzip/etc support

This commit is contained in:
Gregory Schier
2024-01-12 13:39:08 -08:00
parent 1a64d7d9e6
commit 9beac00981
15 changed files with 180 additions and 75 deletions

View File

@@ -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 }) => {

View File

@@ -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',
};

View File

@@ -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 };
}

View File

@@ -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 {

View File

@@ -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');

View File

@@ -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;
}
}

View File

@@ -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 />