mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 09:08:32 +02:00
Appearance setting and gzip/etc support
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n UPDATE settings SET (\n follow_redirects,\n validate_certificates,\n theme\n ) = (?, ?, ?) WHERE id = 'default';\n ",
|
"query": "\n UPDATE settings SET (\n follow_redirects,\n validate_certificates,\n theme,\n appearance\n ) = (?, ?, ?, ?) WHERE id = 'default';\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 3
|
"Right": 4
|
||||||
},
|
},
|
||||||
"nullable": []
|
"nullable": []
|
||||||
},
|
},
|
||||||
"hash": "daf3fc4a8c620af81be9e2ef25a4fd352d9498f9b6c7c567635edcbbe9ac5127"
|
"hash": "09a8074f7ef8d734607f95391819e38f822488933905dcc7a7788bd184bc7796"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n id,\n model,\n created_at,\n updated_at,\n follow_redirects,\n validate_certificates,\n theme\n FROM settings\n WHERE id = 'default'\n ",
|
"query": "\n SELECT\n id,\n model,\n created_at,\n updated_at,\n follow_redirects,\n validate_certificates,\n request_timeout,\n theme,\n appearance\n FROM settings\n WHERE id = 'default'\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -34,8 +34,18 @@
|
|||||||
"type_info": "Bool"
|
"type_info": "Bool"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "theme",
|
"name": "request_timeout",
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
|
"type_info": "Int64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "theme",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "appearance",
|
||||||
|
"ordinal": 8,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -49,8 +59,10 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "a25253024aec650aff34b38684de49b94514f676863acf02e0411da1161af067"
|
"hash": "f27d45f7ea2b04fc203e46a85be96a591a6495794dc042e1e2f3460c9ed65a5c"
|
||||||
}
|
}
|
||||||
15
src-tauri/Cargo.lock
generated
15
src-tauri/Cargo.lock
generated
@@ -81,6 +81,20 @@ version = "1.0.75"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-compression"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5"
|
||||||
|
dependencies = [
|
||||||
|
"brotli",
|
||||||
|
"flate2",
|
||||||
|
"futures-core",
|
||||||
|
"memchr",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atk"
|
name = "atk"
|
||||||
version = "0.15.1"
|
version = "0.15.1"
|
||||||
@@ -3337,6 +3351,7 @@ version = "0.11.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
|
checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-compression",
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ chrono = { version = "0.4.23", features = ["serde"] }
|
|||||||
futures = "0.3.26"
|
futures = "0.3.26"
|
||||||
http = "0.2.8"
|
http = "0.2.8"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
reqwest = { version = "0.11.14", features = ["json", "multipart"] }
|
reqwest = { version = "0.11.14", features = ["json", "multipart", "gzip", "brotli", "deflate"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = { version = "1.0", features = ["raw_value"] }
|
serde_json = { version = "1.0", features = ["raw_value"] }
|
||||||
sqlx = { version = "0.7.2", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time"] }
|
sqlx = { version = "0.7.2", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time"] }
|
||||||
|
|||||||
@@ -7,5 +7,7 @@ CREATE TABLE settings
|
|||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
follow_redirects BOOLEAN DEFAULT TRUE NOT NULL,
|
follow_redirects BOOLEAN DEFAULT TRUE NOT NULL,
|
||||||
validate_certificates BOOLEAN DEFAULT TRUE NOT NULL,
|
validate_certificates BOOLEAN DEFAULT TRUE NOT NULL,
|
||||||
theme TEXT DEFAULT 'system' NOT NULL
|
request_timeout INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
theme TEXT DEFAULT 'default' NOT NULL,
|
||||||
|
appearance TEXT DEFAULT 'system' NOT NULL
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ pub struct Settings {
|
|||||||
pub validate_certificates: bool,
|
pub validate_certificates: bool,
|
||||||
pub follow_redirects: bool,
|
pub follow_redirects: bool,
|
||||||
pub theme: String,
|
pub theme: String,
|
||||||
|
pub appearance: String,
|
||||||
|
pub request_timeout: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
@@ -312,7 +314,9 @@ async fn get_settings(pool: &Pool<Sqlite>) -> Result<Settings, sqlx::Error> {
|
|||||||
updated_at,
|
updated_at,
|
||||||
follow_redirects,
|
follow_redirects,
|
||||||
validate_certificates,
|
validate_certificates,
|
||||||
theme
|
request_timeout,
|
||||||
|
theme,
|
||||||
|
appearance
|
||||||
FROM settings
|
FROM settings
|
||||||
WHERE id = 'default'
|
WHERE id = 'default'
|
||||||
"#,
|
"#,
|
||||||
@@ -347,12 +351,14 @@ pub async fn update_settings(
|
|||||||
UPDATE settings SET (
|
UPDATE settings SET (
|
||||||
follow_redirects,
|
follow_redirects,
|
||||||
validate_certificates,
|
validate_certificates,
|
||||||
theme
|
theme,
|
||||||
) = (?, ?, ?) WHERE id = 'default';
|
appearance
|
||||||
|
) = (?, ?, ?, ?) WHERE id = 'default';
|
||||||
"#,
|
"#,
|
||||||
settings.follow_redirects,
|
settings.follow_redirects,
|
||||||
settings.validate_certificates,
|
settings.validate_certificates,
|
||||||
settings.theme,
|
settings.theme,
|
||||||
|
settings.appearance,
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::{create_dir_all, File};
|
use std::fs::{create_dir_all, File};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use http::{HeaderMap, HeaderName, HeaderValue, Method};
|
|
||||||
use http::header::{ACCEPT, USER_AGENT};
|
use http::header::{ACCEPT, USER_AGENT};
|
||||||
|
use http::{HeaderMap, HeaderName, HeaderValue, Method};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use reqwest::multipart;
|
use reqwest::multipart;
|
||||||
use reqwest::redirect::Policy;
|
use reqwest::redirect::Policy;
|
||||||
use sqlx::{Pool, Sqlite};
|
|
||||||
use sqlx::types::Json;
|
use sqlx::types::Json;
|
||||||
|
use sqlx::{Pool, Sqlite};
|
||||||
use tauri::{AppHandle, Wry};
|
use tauri::{AppHandle, Wry};
|
||||||
|
|
||||||
use crate::{emit_side_effect, models, render, response_err};
|
use crate::{emit_side_effect, models, render, response_err};
|
||||||
@@ -38,17 +39,26 @@ pub async fn actually_send_request(
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to get settings");
|
.expect("Failed to get settings");
|
||||||
|
|
||||||
let client = reqwest::Client::builder()
|
let mut client_builder = reqwest::Client::builder()
|
||||||
.redirect(match settings.follow_redirects {
|
.redirect(match settings.follow_redirects {
|
||||||
true => Policy::limited(10), // TODO: Handle redirects natively
|
true => Policy::limited(10), // TODO: Handle redirects natively
|
||||||
false => Policy::none(),
|
false => Policy::none(),
|
||||||
})
|
})
|
||||||
|
.gzip(true)
|
||||||
|
.brotli(true)
|
||||||
|
.deflate(true)
|
||||||
|
.referer(false)
|
||||||
.danger_accept_invalid_certs(!settings.validate_certificates)
|
.danger_accept_invalid_certs(!settings.validate_certificates)
|
||||||
.connection_verbose(true) // TODO: Capture this log somehow
|
.connection_verbose(true) // TODO: Capture this log somehow
|
||||||
.tls_info(true)
|
.tls_info(true);
|
||||||
// .use_rustls_tls() // TODO: Make this configurable (maybe)
|
|
||||||
.build()
|
if settings.request_timeout > 0 {
|
||||||
.expect("Failed to build client");
|
client_builder =
|
||||||
|
client_builder.timeout(Duration::from_millis(settings.request_timeout.unsigned_abs()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// .use_rustls_tls() // TODO: Make this configurable (maybe)
|
||||||
|
let client = client_builder.build().expect("Failed to build client");
|
||||||
|
|
||||||
let m = Method::from_bytes(request.method.to_uppercase().as_bytes())
|
let m = Method::from_bytes(request.method.to_uppercase().as_bytes())
|
||||||
.expect("Failed to create method");
|
.expect("Failed to create method");
|
||||||
@@ -258,6 +268,7 @@ pub async fn actually_send_request(
|
|||||||
response.url = v.url().to_string();
|
response.url = v.url().to_string();
|
||||||
let body_bytes = v.bytes().await.expect("Failed to get body").to_vec();
|
let body_bytes = v.bytes().await.expect("Failed to get body").to_vec();
|
||||||
response.content_length = Some(body_bytes.len() as i64);
|
response.content_length = Some(body_bytes.len() as i64);
|
||||||
|
println!("Response: {:?}", body_bytes.len());
|
||||||
|
|
||||||
{
|
{
|
||||||
// Write body to FS
|
// Write body to FS
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
|||||||
import { requestsQueryKey } from '../hooks/useRequests';
|
import { requestsQueryKey } from '../hooks/useRequests';
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||||
import { responsesQueryKey } from '../hooks/useResponses';
|
import { responsesQueryKey } from '../hooks/useResponses';
|
||||||
|
import { settingsQueryKey } from '../hooks/useSettings';
|
||||||
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
|
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
|
||||||
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
||||||
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
||||||
@@ -49,6 +50,8 @@ export function GlobalHooks() {
|
|||||||
? workspacesQueryKey(payload)
|
? workspacesQueryKey(payload)
|
||||||
: payload.model === 'key_value'
|
: payload.model === 'key_value'
|
||||||
? keyValueQueryKey(payload)
|
? keyValueQueryKey(payload)
|
||||||
|
: payload.model === 'settings'
|
||||||
|
? settingsQueryKey()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (queryKey === null) {
|
if (queryKey === null) {
|
||||||
@@ -74,6 +77,8 @@ export function GlobalHooks() {
|
|||||||
? workspacesQueryKey(payload)
|
? workspacesQueryKey(payload)
|
||||||
: payload.model === 'key_value'
|
: payload.model === 'key_value'
|
||||||
? keyValueQueryKey(payload)
|
? keyValueQueryKey(payload)
|
||||||
|
: payload.model === 'settings'
|
||||||
|
? settingsQueryKey()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (queryKey === null) {
|
if (queryKey === null) {
|
||||||
@@ -107,6 +112,8 @@ export function GlobalHooks() {
|
|||||||
queryClient.setQueryData<HttpResponse[]>(responsesQueryKey(payload), removeById(payload));
|
queryClient.setQueryData<HttpResponse[]>(responsesQueryKey(payload), removeById(payload));
|
||||||
} else if (payload.model === 'key_value') {
|
} else if (payload.model === 'key_value') {
|
||||||
queryClient.setQueryData(keyValueQueryKey(payload), undefined);
|
queryClient.setQueryData(keyValueQueryKey(payload), undefined);
|
||||||
|
} else if (payload.model === 'settings') {
|
||||||
|
queryClient.setQueryData(settingsQueryKey(), undefined);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
useListenToTauriEvent<number>('zoom', ({ payload: zoomDelta, windowLabel }) => {
|
useListenToTauriEvent<number>('zoom', ({ payload: zoomDelta, windowLabel }) => {
|
||||||
|
|||||||
@@ -1,33 +1,83 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
import { useSettings } from '../hooks/useSettings';
|
import { useSettings } from '../hooks/useSettings';
|
||||||
import { useTheme } from '../hooks/useTheme';
|
import { useTheme } from '../hooks/useTheme';
|
||||||
import { useUpdateSettings } from '../hooks/useUpdateSettings';
|
import { useUpdateSettings } from '../hooks/useUpdateSettings';
|
||||||
|
import type { Appearance } from '../lib/theme/window';
|
||||||
import { Checkbox } from './core/Checkbox';
|
import { Checkbox } from './core/Checkbox';
|
||||||
|
import { Input } from './core/Input';
|
||||||
import { VStack } from './core/Stacks';
|
import { VStack } from './core/Stacks';
|
||||||
|
|
||||||
export const SettingsDialog = () => {
|
export const SettingsDialog = () => {
|
||||||
const { appearance, toggleAppearance } = useTheme();
|
const { appearance, setAppearance } = useTheme();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const updateSettings = useUpdateSettings();
|
const updateSettings = useUpdateSettings();
|
||||||
|
|
||||||
if (settings == null) {
|
if (settings == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
console.log('SETTINGS', settings);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack space={2}>
|
<VStack space={2}>
|
||||||
<Checkbox
|
<div className="w-full gap-2 grid grid-cols-[auto_1fr] gap-x-6 auto-rows-[2rem] items-center">
|
||||||
checked={settings.validateCertificates}
|
<Checkbox
|
||||||
title="Validate TLS Certificates"
|
className="col-span-full"
|
||||||
onChange={(validateCertificates) =>
|
checked={settings.validateCertificates}
|
||||||
updateSettings.mutateAsync({ ...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
|
||||||
/>
|
className="col-span-full"
|
||||||
<Checkbox checked={appearance === 'dark'} title="Dark Mode" onChange={toggleAppearance} />
|
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>
|
</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 type { Appearance } from '../lib/theme/window';
|
||||||
import {
|
import {
|
||||||
getAppearance,
|
setAppearanceOnDocument,
|
||||||
setAppearance,
|
getPreferredAppearance,
|
||||||
subscribeToPreferredAppearanceChange,
|
subscribeToPreferredAppearanceChange,
|
||||||
} from '../lib/theme/window';
|
} from '../lib/theme/window';
|
||||||
import { useKeyValue } from './useKeyValue';
|
import { useSettings } from './useSettings';
|
||||||
|
|
||||||
export function useTheme() {
|
export function useTheme() {
|
||||||
const appearanceKv = useKeyValue<Appearance>({
|
const [preferredAppearance, setPreferredAppearance] = useState<Appearance>(
|
||||||
key: 'appearance',
|
getPreferredAppearance(),
|
||||||
defaultValue: getAppearance(),
|
);
|
||||||
});
|
|
||||||
|
|
||||||
const handleToggleAppearance = async () => {
|
const settings = useSettings();
|
||||||
appearanceKv.set(appearanceKv.value === 'dark' ? 'light' : 'dark');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set appearance when preferred theme changes
|
// Set appearance when preferred theme changes
|
||||||
useEffect(() => subscribeToPreferredAppearanceChange(appearanceKv.set), [appearanceKv.set]);
|
useEffect(() => {
|
||||||
|
return subscribeToPreferredAppearanceChange(setPreferredAppearance);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Sync appearance when k/v changes
|
const appearance =
|
||||||
useEffect(() => setAppearance(appearanceKv.value), [appearanceKv.value]);
|
settings == null || settings?.appearance === 'system'
|
||||||
|
? preferredAppearance
|
||||||
|
: settings.appearance;
|
||||||
|
|
||||||
return {
|
useEffect(() => {
|
||||||
appearance: appearanceKv.value,
|
if (settings == null) {
|
||||||
toggleAppearance: handleToggleAppearance,
|
return;
|
||||||
};
|
}
|
||||||
|
setAppearanceOnDocument(settings.appearance as Appearance);
|
||||||
|
}, [appearance, settings]);
|
||||||
|
|
||||||
|
return { appearance };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ export interface Settings extends BaseModel {
|
|||||||
readonly model: 'settings';
|
readonly model: 'settings';
|
||||||
validateCertificates: boolean;
|
validateCertificates: boolean;
|
||||||
followRedirects: boolean;
|
followRedirects: boolean;
|
||||||
|
requestTimeout: number;
|
||||||
theme: string;
|
theme: string;
|
||||||
|
appearance: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Workspace extends BaseModel {
|
export interface Workspace extends BaseModel {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import type { AppTheme, AppThemeColors } from './theme';
|
import type { AppTheme, AppThemeColors } from './theme';
|
||||||
import { generateCSS, toTailwindVariable } 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 {
|
enum Theme {
|
||||||
yaak = 'yaak',
|
yaak = 'yaak',
|
||||||
@@ -61,19 +63,11 @@ const lightTheme: AppTheme = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getAppearance(): Appearance {
|
export function setAppearanceOnDocument(appearance: Appearance = DEFAULT_APPEARANCE) {
|
||||||
const docAppearance = document.documentElement.getAttribute('data-appearance');
|
const resolvedAppearance = appearance === 'system' ? getPreferredAppearance() : appearance;
|
||||||
if (docAppearance === 'dark' || docAppearance === 'light') {
|
const theme = resolvedAppearance === 'dark' ? darkTheme : lightTheme;
|
||||||
return docAppearance;
|
|
||||||
}
|
|
||||||
return getPreferredAppearance();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setAppearance(a?: Appearance) {
|
document.documentElement.setAttribute('data-resolved-appearance', resolvedAppearance);
|
||||||
const appearance = a ?? getPreferredAppearance();
|
|
||||||
const theme = appearance === 'dark' ? darkTheme : lightTheme;
|
|
||||||
|
|
||||||
document.documentElement.setAttribute('data-appearance', appearance);
|
|
||||||
document.documentElement.setAttribute('data-theme', theme.name);
|
document.documentElement.setAttribute('data-theme', theme.name);
|
||||||
|
|
||||||
let existingStyleEl = document.head.querySelector(`style[data-theme-definition]`);
|
let existingStyleEl = document.head.querySelector(`style[data-theme-definition]`);
|
||||||
@@ -85,11 +79,11 @@ export function setAppearance(a?: Appearance) {
|
|||||||
|
|
||||||
existingStyleEl.textContent = [
|
existingStyleEl.textContent = [
|
||||||
`/* ${darkTheme.name} */`,
|
`/* ${darkTheme.name} */`,
|
||||||
`[data-appearance="dark"] {`,
|
`[data-resolved-appearance="dark"] {`,
|
||||||
...generateCSS(darkTheme).map(toTailwindVariable),
|
...generateCSS(darkTheme).map(toTailwindVariable),
|
||||||
'}',
|
'}',
|
||||||
`/* ${lightTheme.name} */`,
|
`/* ${lightTheme.name} */`,
|
||||||
`[data-appearance="light"] {`,
|
`[data-resolved-appearance="light"] {`,
|
||||||
...generateCSS(lightTheme).map(toTailwindVariable),
|
...generateCSS(lightTheme).map(toTailwindVariable),
|
||||||
'}',
|
'}',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|||||||
@@ -68,4 +68,14 @@
|
|||||||
--color-white: 255 100% 100%;
|
--color-white: 255 100% 100%;
|
||||||
--color-black: 255 0% 0%;
|
--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 { createRoot } from 'react-dom/client';
|
||||||
import { attachConsole } from 'tauri-plugin-log-api';
|
import { attachConsole } from 'tauri-plugin-log-api';
|
||||||
import { App } from './components/App';
|
import { App } from './components/App';
|
||||||
import { getKeyValue } from './lib/keyValueStore';
|
|
||||||
import { maybeRestorePathname } from './lib/persistPathname';
|
import { maybeRestorePathname } from './lib/persistPathname';
|
||||||
import { getPreferredAppearance, setAppearance } from './lib/theme/window';
|
|
||||||
import './main.css';
|
import './main.css';
|
||||||
|
|
||||||
await attachConsole();
|
await attachConsole();
|
||||||
@@ -15,13 +13,6 @@ document.addEventListener('keydown', (e) => {
|
|||||||
if (e.key === 'Backspace') e.preventDefault();
|
if (e.key === 'Backspace') e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
setAppearance(
|
|
||||||
await getKeyValue({
|
|
||||||
key: 'appearance',
|
|
||||||
fallback: getPreferredAppearance(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
createRoot(document.getElementById('root') as HTMLElement).render(
|
createRoot(document.getElementById('root') as HTMLElement).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<App />
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const height = {
|
|||||||
|
|
||||||
/** @type {import("tailwindcss").Config} */
|
/** @type {import("tailwindcss").Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
darkMode: ['class', '[data-appearance="dark"]'],
|
darkMode: ['class', '[data-resolved-appearance="dark"]'],
|
||||||
content: ['./index.html', './src-web/**/*.{html,js,jsx,ts,tsx}'],
|
content: ['./index.html', './src-web/**/*.{html,js,jsx,ts,tsx}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
|||||||
Reference in New Issue
Block a user