mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-07-06 12:55:24 +02:00
A bunch more theme stuff
This commit is contained in:
@@ -36,11 +36,11 @@ jobs:
|
|||||||
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
|
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||||
# webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2.
|
|
||||||
# You can remove the one that doesn't apply to your app to speed up the workflow a bit.
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
- name: Run lint
|
||||||
|
run: npm run lint
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: npm test
|
run: npm test
|
||||||
- uses: tauri-apps/tauri-action@v0
|
- uses: tauri-apps/tauri-action@v0
|
||||||
|
|||||||
Generated
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/commands": "^6.2.1",
|
"@codemirror/commands": "^6.2.1",
|
||||||
"@codemirror/lang-javascript": "^6.1.4",
|
"@codemirror/lang-javascript": "^6.2.2",
|
||||||
"@codemirror/lang-json": "^6.0.1",
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
"@codemirror/lang-xml": "^6.0.2",
|
"@codemirror/lang-xml": "^6.0.2",
|
||||||
"@codemirror/language": "^6.6.0",
|
"@codemirror/language": "^6.6.0",
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/commands": "^6.2.1",
|
"@codemirror/commands": "^6.2.1",
|
||||||
"@codemirror/lang-javascript": "^6.1.4",
|
"@codemirror/lang-javascript": "^6.2.2",
|
||||||
"@codemirror/lang-json": "^6.0.1",
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
"@codemirror/lang-xml": "^6.0.2",
|
"@codemirror/lang-xml": "^6.0.2",
|
||||||
"@codemirror/language": "^6.6.0",
|
"@codemirror/language": "^6.6.0",
|
||||||
|
|||||||
Generated
-12
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n UPDATE settings SET (\n theme, appearance, update_channel\n ) = (?, ?, ?) WHERE id = 'default';\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 3
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "48ec5fdf20f34add763c540061caa25054545503704e19f149987f99b1a0e4f0"
|
|
||||||
}
|
|
||||||
Generated
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n UPDATE settings SET (\n theme, appearance, theme_dark, theme_light, update_channel\n ) = (?, ?, ?, ?, ?) WHERE id = 'default';\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 5
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "8e88c7070a34a6e151da66f521deeafaea9a12e2aa68081daaf235d2003b513d"
|
||||||
|
}
|
||||||
+15
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n id, model, created_at, updated_at, theme, appearance, update_channel\n FROM settings\n WHERE id = 'default'\n ",
|
"query": "\n SELECT\n id, model, created_at, updated_at, theme, appearance,\n theme_dark, theme_light, update_channel\n FROM settings\n WHERE id = 'default'\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -34,9 +34,19 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "update_channel",
|
"name": "theme_dark",
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "theme_light",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "update_channel",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -49,8 +59,10 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "b32994b09ae7a06eb0f031069d327e55127a5bce60cbb499b83d1701386a23cb"
|
"hash": "cae02809532d086fbde12a246d12e3839ec8610b66e08315106dbdbf25d8699c"
|
||||||
}
|
}
|
||||||
Generated
+11
@@ -2350,6 +2350,15 @@ version = "0.4.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex_color"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d37f101bf4c633f7ca2e4b5e136050314503dd198e78e325ea602c327c484ef0"
|
||||||
|
dependencies = [
|
||||||
|
"rand 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hkdf"
|
name = "hkdf"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
@@ -7650,6 +7659,7 @@ dependencies = [
|
|||||||
"cocoa",
|
"cocoa",
|
||||||
"datetime",
|
"datetime",
|
||||||
"grpc",
|
"grpc",
|
||||||
|
"hex_color",
|
||||||
"http 0.2.10",
|
"http 0.2.10",
|
||||||
"log",
|
"log",
|
||||||
"objc",
|
"objc",
|
||||||
@@ -7674,6 +7684,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"windows 0.56.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ tauri-build = { version = "2.0.0-beta", features = [] }
|
|||||||
objc = "0.2.7"
|
objc = "0.2.7"
|
||||||
cocoa = "0.25.0"
|
cocoa = "0.25.0"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
windows = { version = "0.56.0", features = [
|
||||||
|
"Win32_Graphics_Dwm",
|
||||||
|
"Win32_Foundation",
|
||||||
|
"Win32_UI_Controls",
|
||||||
|
] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
openssl-sys = { version = "0.9", features = ["vendored"] } # For Ubuntu installation to work
|
openssl-sys = { version = "0.9", features = ["vendored"] } # For Ubuntu installation to work
|
||||||
|
|
||||||
@@ -51,3 +58,4 @@ reqwest_cookie_store = "0.6.0"
|
|||||||
grpc = { path = "./grpc" }
|
grpc = { path = "./grpc" }
|
||||||
tokio-stream = "0.1.15"
|
tokio-stream = "0.1.15"
|
||||||
regex = "1.10.2"
|
regex = "1.10.2"
|
||||||
|
hex_color = "3.0.0"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE settings
|
||||||
|
ADD COLUMN theme_dark TEXT DEFAULT 'yaak-dark' NOT NULL;
|
||||||
|
ALTER TABLE settings
|
||||||
|
ADD COLUMN theme_light TEXT DEFAULT 'yaak-light' NOT NULL;
|
||||||
+75
-79
@@ -34,7 +34,6 @@ use tokio::sync::Mutex;
|
|||||||
|
|
||||||
use ::grpc::{Code, deserialize_message, serialize_message, ServiceDefinition};
|
use ::grpc::{Code, deserialize_message, serialize_message, ServiceDefinition};
|
||||||
use ::grpc::manager::{DynamicMessage, GrpcHandle};
|
use ::grpc::manager::{DynamicMessage, GrpcHandle};
|
||||||
use window_ext::TrafficLightWindowExt;
|
|
||||||
|
|
||||||
use crate::analytics::{AnalyticsAction, AnalyticsResource};
|
use crate::analytics::{AnalyticsAction, AnalyticsResource};
|
||||||
use crate::grpc::metadata_to_map;
|
use crate::grpc::metadata_to_map;
|
||||||
@@ -69,8 +68,11 @@ mod notifications;
|
|||||||
mod plugin;
|
mod plugin;
|
||||||
mod render;
|
mod render;
|
||||||
mod updates;
|
mod updates;
|
||||||
mod window_ext;
|
|
||||||
mod window_menu;
|
mod window_menu;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
mod mac;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
mod win;
|
||||||
|
|
||||||
async fn migrate_db(app_handle: &AppHandle, db: &Mutex<Pool<Sqlite>>) -> Result<(), String> {
|
async fn migrate_db(app_handle: &AppHandle, db: &Mutex<Pool<Sqlite>>) -> Result<(), String> {
|
||||||
let pool = &*db.lock().await;
|
let pool = &*db.lock().await;
|
||||||
@@ -230,8 +232,8 @@ async fn cmd_grpc_go(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
};
|
};
|
||||||
let conn_id = conn.id.clone();
|
let conn_id = conn.id.clone();
|
||||||
|
|
||||||
@@ -328,8 +330,8 @@ async fn cmd_grpc_go(
|
|||||||
..base_msg.clone()
|
..base_msg.clone()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -344,8 +346,8 @@ async fn cmd_grpc_go(
|
|||||||
..base_msg.clone()
|
..base_msg.clone()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(IncomingMsg::Commit) => {
|
Ok(IncomingMsg::Commit) => {
|
||||||
@@ -384,8 +386,8 @@ async fn cmd_grpc_go(
|
|||||||
..base_event.clone()
|
..base_event.clone()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let (maybe_stream, maybe_msg) = match (
|
let (maybe_stream, maybe_msg) = match (
|
||||||
@@ -431,8 +433,8 @@ async fn cmd_grpc_go(
|
|||||||
..base_event.clone()
|
..base_event.clone()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
match maybe_msg {
|
match maybe_msg {
|
||||||
@@ -446,13 +448,13 @@ async fn cmd_grpc_go(
|
|||||||
} else {
|
} else {
|
||||||
"Received response with metadata"
|
"Received response with metadata"
|
||||||
}
|
}
|
||||||
.to_string(),
|
.to_string(),
|
||||||
event_type: GrpcEventType::Info,
|
event_type: GrpcEventType::Info,
|
||||||
..base_event.clone()
|
..base_event.clone()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
upsert_grpc_event(
|
upsert_grpc_event(
|
||||||
&w,
|
&w,
|
||||||
&GrpcEvent {
|
&GrpcEvent {
|
||||||
@@ -461,8 +463,8 @@ async fn cmd_grpc_go(
|
|||||||
..base_event.clone()
|
..base_event.clone()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
upsert_grpc_event(
|
upsert_grpc_event(
|
||||||
&w,
|
&w,
|
||||||
&GrpcEvent {
|
&GrpcEvent {
|
||||||
@@ -472,8 +474,8 @@ async fn cmd_grpc_go(
|
|||||||
..base_event.clone()
|
..base_event.clone()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
Some(Err(e)) => {
|
Some(Err(e)) => {
|
||||||
upsert_grpc_event(
|
upsert_grpc_event(
|
||||||
@@ -496,8 +498,8 @@ async fn cmd_grpc_go(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Server streaming doesn't return initial message
|
// Server streaming doesn't return initial message
|
||||||
@@ -515,13 +517,13 @@ async fn cmd_grpc_go(
|
|||||||
} else {
|
} else {
|
||||||
"Received response with metadata"
|
"Received response with metadata"
|
||||||
}
|
}
|
||||||
.to_string(),
|
.to_string(),
|
||||||
event_type: GrpcEventType::Info,
|
event_type: GrpcEventType::Info,
|
||||||
..base_event.clone()
|
..base_event.clone()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
stream.into_inner()
|
stream.into_inner()
|
||||||
}
|
}
|
||||||
Some(Err(e)) => {
|
Some(Err(e)) => {
|
||||||
@@ -545,8 +547,8 @@ async fn cmd_grpc_go(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
None => return,
|
None => return,
|
||||||
@@ -564,8 +566,8 @@ async fn cmd_grpc_go(
|
|||||||
..base_event.clone()
|
..base_event.clone()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
let trailers = stream
|
let trailers = stream
|
||||||
@@ -583,8 +585,8 @@ async fn cmd_grpc_go(
|
|||||||
..base_event.clone()
|
..base_event.clone()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(status) => {
|
Err(status) => {
|
||||||
@@ -598,8 +600,8 @@ async fn cmd_grpc_go(
|
|||||||
..base_event.clone()
|
..base_event.clone()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -700,7 +702,7 @@ async fn cmd_send_ephemeral_request(
|
|||||||
None,
|
None,
|
||||||
&mut cancel_rx,
|
&mut cancel_rx,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -767,7 +769,7 @@ async fn cmd_import_data(
|
|||||||
AnalyticsAction::Import,
|
AnalyticsAction::Import,
|
||||||
Some(json!({ "plugin": plugin_name })),
|
Some(json!({ "plugin": plugin_name })),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
result = Some(r);
|
result = Some(r);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -798,7 +800,7 @@ async fn cmd_import_data(
|
|||||||
let maybe_gen_id_opt = |id: Option<String>,
|
let maybe_gen_id_opt = |id: Option<String>,
|
||||||
model: ModelType,
|
model: ModelType,
|
||||||
ids: &mut HashMap<String, String>|
|
ids: &mut HashMap<String, String>|
|
||||||
-> Option<String> {
|
-> Option<String> {
|
||||||
match id {
|
match id {
|
||||||
Some(id) => Some(maybe_gen_id(id.as_str(), model, ids)),
|
Some(id) => Some(maybe_gen_id(id.as_str(), model, ids)),
|
||||||
None => None,
|
None => None,
|
||||||
@@ -944,7 +946,7 @@ async fn cmd_export_data(
|
|||||||
AnalyticsAction::Export,
|
AnalyticsAction::Export,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -995,8 +997,8 @@ async fn cmd_send_http_request(
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create response");
|
.expect("Failed to create response");
|
||||||
|
|
||||||
let download_path = if let Some(p) = download_dir {
|
let download_path = if let Some(p) = download_dir {
|
||||||
Some(std::path::Path::new(p).to_path_buf())
|
Some(std::path::Path::new(p).to_path_buf())
|
||||||
@@ -1021,7 +1023,7 @@ async fn cmd_send_http_request(
|
|||||||
download_path,
|
download_path,
|
||||||
&mut cancel_rx,
|
&mut cancel_rx,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn response_err(
|
async fn response_err(
|
||||||
@@ -1129,8 +1131,8 @@ async fn cmd_create_cookie_jar(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -1149,8 +1151,8 @@ async fn cmd_create_environment(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -1171,8 +1173,8 @@ async fn cmd_create_grpc_request(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -1281,8 +1283,8 @@ async fn cmd_create_folder(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -1412,8 +1414,8 @@ async fn cmd_list_cookie_jars(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create CookieJar");
|
.expect("Failed to create CookieJar");
|
||||||
Ok(vec![cookie_jar])
|
Ok(vec![cookie_jar])
|
||||||
} else {
|
} else {
|
||||||
Ok(cookie_jars)
|
Ok(cookie_jars)
|
||||||
@@ -1482,8 +1484,8 @@ async fn cmd_list_workspaces(w: WebviewWindow) -> Result<Vec<Workspace>, String>
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create Workspace");
|
.expect("Failed to create Workspace");
|
||||||
Ok(vec![workspace])
|
Ok(vec![workspace])
|
||||||
} else {
|
} else {
|
||||||
Ok(workspaces)
|
Ok(workspaces)
|
||||||
@@ -1733,16 +1735,16 @@ fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
|
|||||||
window_id,
|
window_id,
|
||||||
WebviewUrl::App(url.unwrap_or_default().into()),
|
WebviewUrl::App(url.unwrap_or_default().into()),
|
||||||
)
|
)
|
||||||
.resizable(true)
|
.resizable(true)
|
||||||
.fullscreen(false)
|
.fullscreen(false)
|
||||||
.disable_drag_drop_handler() // Required for frontend Dnd on windows
|
.disable_drag_drop_handler() // Required for frontend Dnd on windows
|
||||||
.inner_size(1100.0, 600.0)
|
.inner_size(1100.0, 600.0)
|
||||||
.position(
|
.position(
|
||||||
// Randomly offset so windows don't stack exactly
|
// Randomly offset so windows don't stack exactly
|
||||||
100.0 + random::<f64>() * 30.0,
|
100.0 + random::<f64>() * 30.0,
|
||||||
100.0 + random::<f64>() * 30.0,
|
100.0 + random::<f64>() * 30.0,
|
||||||
)
|
)
|
||||||
.title(handle.package_info().name.to_string());
|
.title(handle.package_info().name.to_string());
|
||||||
|
|
||||||
// Add macOS-only things
|
// Add macOS-only things
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -1798,25 +1800,19 @@ fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let win3 = win.clone();
|
#[cfg(target_os = "macos")]
|
||||||
win.on_window_event(move |e| {
|
{
|
||||||
let apply_offset = || {
|
use mac::setup_mac_window;
|
||||||
win3.position_traffic_lights();
|
let mut m_win = win.clone();
|
||||||
};
|
setup_mac_window(&mut m_win);
|
||||||
|
};
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
use win::setup_win_window;
|
||||||
|
let mut m_win = win.clone();
|
||||||
|
setup_win_window(&mut m_win);
|
||||||
|
}
|
||||||
|
|
||||||
match e {
|
|
||||||
WindowEvent::Resized(..) => apply_offset(),
|
|
||||||
WindowEvent::ThemeChanged(..) => apply_offset(),
|
|
||||||
WindowEvent::Focused(..) => apply_offset(),
|
|
||||||
WindowEvent::ScaleFactorChanged { .. } => apply_offset(),
|
|
||||||
WindowEvent::CloseRequested { .. } => {
|
|
||||||
// api.prevent_close();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
win.position_traffic_lights();
|
|
||||||
win
|
win
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,355 @@
|
|||||||
|
// Borrowed from our friends at Hoppscotch
|
||||||
|
// https://github.com/hoppscotch/hoppscotch/blob/286fcd2bb08a84f027b10308d1e18da368f95ebf/packages/hoppscotch-selfhost-desktop/src-tauri/src/mac/window.rs
|
||||||
|
|
||||||
|
use hex_color::HexColor;
|
||||||
|
use tauri::{Manager, WebviewWindow};
|
||||||
|
|
||||||
|
struct UnsafeWindowHandle(*mut std::ffi::c_void);
|
||||||
|
|
||||||
|
unsafe impl Send for UnsafeWindowHandle {}
|
||||||
|
|
||||||
|
unsafe impl Sync for UnsafeWindowHandle {}
|
||||||
|
|
||||||
|
const WINDOW_CONTROL_PAD_X: f64 = 13.0;
|
||||||
|
const WINDOW_CONTROL_PAD_Y: f64 = 18.0;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn update_window_theme(window: &WebviewWindow, color: HexColor) {
|
||||||
|
use cocoa::appkit::{
|
||||||
|
NSAppearance, NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight, NSWindow,
|
||||||
|
};
|
||||||
|
|
||||||
|
let brightness = (color.r as u64 + color.g as u64 + color.b as u64) / 3;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
|
||||||
|
|
||||||
|
let _ = window.run_on_main_thread(move || {
|
||||||
|
let handle = window_handle;
|
||||||
|
|
||||||
|
let selected_appearance = if brightness >= 128 {
|
||||||
|
NSAppearance(NSAppearanceNameVibrantLight)
|
||||||
|
} else {
|
||||||
|
NSAppearance(NSAppearanceNameVibrantDark)
|
||||||
|
};
|
||||||
|
|
||||||
|
NSWindow::setAppearance(handle.0 as cocoa::base::id, selected_appearance);
|
||||||
|
set_window_controls_pos(
|
||||||
|
handle.0 as cocoa::base::id,
|
||||||
|
WINDOW_CONTROL_PAD_X,
|
||||||
|
WINDOW_CONTROL_PAD_Y,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn set_window_controls_pos(window: cocoa::base::id, x: f64, y: f64) {
|
||||||
|
use cocoa::{
|
||||||
|
appkit::{NSView, NSWindow, NSWindowButton},
|
||||||
|
foundation::NSRect,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let close = window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
|
||||||
|
let miniaturize = window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
|
||||||
|
let zoom = window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
|
||||||
|
|
||||||
|
let title_bar_container_view = close.superview().superview();
|
||||||
|
|
||||||
|
let close_rect: NSRect = msg_send![close, frame];
|
||||||
|
let button_height = close_rect.size.height;
|
||||||
|
|
||||||
|
let title_bar_frame_height = button_height + y;
|
||||||
|
let mut title_bar_rect = NSView::frame(title_bar_container_view);
|
||||||
|
title_bar_rect.size.height = title_bar_frame_height;
|
||||||
|
title_bar_rect.origin.y = NSView::frame(window).size.height - title_bar_frame_height;
|
||||||
|
let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
|
||||||
|
|
||||||
|
let window_buttons = vec![close, miniaturize, zoom];
|
||||||
|
let space_between = NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
|
||||||
|
|
||||||
|
for (i, button) in window_buttons.into_iter().enumerate() {
|
||||||
|
let mut rect: NSRect = NSView::frame(button);
|
||||||
|
rect.origin.x = x + (i as f64 * space_between);
|
||||||
|
button.setFrameOrigin(rect.origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct AppState {
|
||||||
|
window: WebviewWindow,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub fn setup_mac_window(window: &mut WebviewWindow) {
|
||||||
|
use cocoa::delegate;
|
||||||
|
use cocoa::appkit::NSWindow;
|
||||||
|
use cocoa::base::{BOOL, id};
|
||||||
|
use cocoa::foundation::NSUInteger;
|
||||||
|
use objc::runtime::{Object, Sel};
|
||||||
|
use std::ffi::c_void;
|
||||||
|
|
||||||
|
fn with_app_state<F: FnOnce(&mut AppState) -> T, T>(this: &Object, func: F) {
|
||||||
|
let ptr = unsafe {
|
||||||
|
let x: *mut c_void = *this.get_ivar("yaakApp");
|
||||||
|
&mut *(x as *mut AppState)
|
||||||
|
};
|
||||||
|
func(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let ns_win = window.ns_window().unwrap() as id;
|
||||||
|
|
||||||
|
let current_delegate: id = ns_win.delegate();
|
||||||
|
|
||||||
|
extern "C" fn on_window_should_close(this: &Object, _cmd: Sel, sender: id) -> BOOL {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
msg_send![super_del, windowShouldClose: sender]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_window_will_close(this: &Object, _cmd: Sel, notification: id) {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![super_del, windowWillClose: notification];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_window_did_resize(this: &Object, _cmd: Sel, notification: id) {
|
||||||
|
unsafe {
|
||||||
|
with_app_state(&*this, |state| {
|
||||||
|
let id = state.window.ns_window().unwrap() as id;
|
||||||
|
|
||||||
|
set_window_controls_pos(id, WINDOW_CONTROL_PAD_X, WINDOW_CONTROL_PAD_Y);
|
||||||
|
});
|
||||||
|
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![super_del, windowDidResize: notification];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_window_did_move(this: &Object, _cmd: Sel, notification: id) {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![super_del, windowDidMove: notification];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_window_did_change_backing_properties(
|
||||||
|
this: &Object,
|
||||||
|
_cmd: Sel,
|
||||||
|
notification: id,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![super_del, windowDidChangeBackingProperties: notification];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_window_did_become_key(this: &Object, _cmd: Sel, notification: id) {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![super_del, windowDidBecomeKey: notification];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_window_did_resign_key(this: &Object, _cmd: Sel, notification: id) {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![super_del, windowDidResignKey: notification];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_dragging_entered(this: &Object, _cmd: Sel, notification: id) -> BOOL {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
msg_send![super_del, draggingEntered: notification]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_prepare_for_drag_operation(
|
||||||
|
this: &Object,
|
||||||
|
_cmd: Sel,
|
||||||
|
notification: id,
|
||||||
|
) -> BOOL {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
msg_send![super_del, prepareForDragOperation: notification]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_perform_drag_operation(this: &Object, _cmd: Sel, sender: id) -> BOOL {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
msg_send![super_del, performDragOperation: sender]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_conclude_drag_operation(this: &Object, _cmd: Sel, notification: id) {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![super_del, concludeDragOperation: notification];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_dragging_exited(this: &Object, _cmd: Sel, notification: id) {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![super_del, draggingExited: notification];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_window_will_use_full_screen_presentation_options(
|
||||||
|
this: &Object,
|
||||||
|
_cmd: Sel,
|
||||||
|
window: id,
|
||||||
|
proposed_options: NSUInteger,
|
||||||
|
) -> NSUInteger {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_window_did_enter_full_screen(this: &Object, _cmd: Sel, notification: id) {
|
||||||
|
unsafe {
|
||||||
|
with_app_state(&*this, |state| {
|
||||||
|
state.window.emit("did-enter-fullscreen", ()).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![super_del, windowDidEnterFullScreen: notification];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_window_will_enter_full_screen(this: &Object, _cmd: Sel, notification: id) {
|
||||||
|
unsafe {
|
||||||
|
with_app_state(&*this, |state| {
|
||||||
|
state.window.emit("will-enter-fullscreen", ()).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![super_del, windowWillEnterFullScreen: notification];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_window_did_exit_full_screen(this: &Object, _cmd: Sel, notification: id) {
|
||||||
|
unsafe {
|
||||||
|
with_app_state(&*this, |state| {
|
||||||
|
state.window.emit("did-exit-fullscreen", ()).unwrap();
|
||||||
|
|
||||||
|
let id = state.window.ns_window().unwrap() as id;
|
||||||
|
set_window_controls_pos(id, WINDOW_CONTROL_PAD_X, WINDOW_CONTROL_PAD_Y);
|
||||||
|
});
|
||||||
|
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![super_del, windowDidExitFullScreen: notification];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_window_will_exit_full_screen(this: &Object, _cmd: Sel, notification: id) {
|
||||||
|
unsafe {
|
||||||
|
with_app_state(&*this, |state| {
|
||||||
|
state.window.emit("will-exit-fullscreen", ()).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![super_del, windowWillExitFullScreen: notification];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_window_did_fail_to_enter_full_screen(
|
||||||
|
this: &Object,
|
||||||
|
_cmd: Sel,
|
||||||
|
window: id,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![super_del, windowDidFailToEnterFullScreen: window];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_effective_appearance_did_change(
|
||||||
|
this: &Object,
|
||||||
|
_cmd: Sel,
|
||||||
|
notification: id,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![super_del, effectiveAppearanceDidChange: notification];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern "C" fn on_effective_appearance_did_changed_on_main_thread(
|
||||||
|
this: &Object,
|
||||||
|
_cmd: Sel,
|
||||||
|
notification: id,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
let _: () = msg_send![
|
||||||
|
super_del,
|
||||||
|
effectiveAppearanceDidChangedOnMainThread: notification
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extern fn on_dealloc(this: &Object, cmd: Sel) {
|
||||||
|
// unsafe {
|
||||||
|
// let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
// let _: () = msg_send![super_del, dealloc];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// extern fn on_mark_is_checking_zoomed_in(this: &Object, cmd: Sel) {
|
||||||
|
// unsafe {
|
||||||
|
// let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
// let _: () = msg_send![super_del, markIsCheckingZoomedIn];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// extern fn on_clear_is_checking_zoomed_in(this: &Object, cmd: Sel) {
|
||||||
|
// unsafe {
|
||||||
|
// let super_del: id = *this.get_ivar("super_delegate");
|
||||||
|
// let _: () = msg_send![super_del, clearIsCheckingZoomedIn];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Are we deallocing this properly ? (I miss safe Rust :( )
|
||||||
|
let w = window.clone();
|
||||||
|
let app_state = AppState { window: w };
|
||||||
|
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
|
||||||
|
set_window_controls_pos(ns_win, WINDOW_CONTROL_PAD_X, WINDOW_CONTROL_PAD_Y);
|
||||||
|
|
||||||
|
ns_win.setDelegate_(delegate!("MainWindowDelegate", {
|
||||||
|
window: id = ns_win,
|
||||||
|
yaakApp: *mut c_void = app_box,
|
||||||
|
toolbar: id = cocoa::base::nil,
|
||||||
|
super_delegate: id = current_delegate,
|
||||||
|
// (dealloc) => on_dealloc as extern fn(&Object, Sel),
|
||||||
|
// (markIsCheckingZoomedIn) => on_mark_is_checking_zoomed_in as extern fn(&Object, Sel),
|
||||||
|
// (clearIsCheckingZoomedIn) => on_clear_is_checking_zoomed_in as extern fn(&Object, Sel),
|
||||||
|
(windowShouldClose:) => on_window_should_close as extern fn(&Object, Sel, id) -> BOOL,
|
||||||
|
(windowWillClose:) => on_window_will_close as extern fn(&Object, Sel, id),
|
||||||
|
(windowDidResize:) => on_window_did_resize as extern fn(&Object, Sel, id),
|
||||||
|
(windowDidMove:) => on_window_did_move as extern fn(&Object, Sel, id),
|
||||||
|
(windowDidChangeBackingProperties:) => on_window_did_change_backing_properties as extern fn(&Object, Sel, id),
|
||||||
|
(windowDidBecomeKey:) => on_window_did_become_key as extern fn(&Object, Sel, id),
|
||||||
|
(windowDidResignKey:) => on_window_did_resign_key as extern fn(&Object, Sel, id),
|
||||||
|
(draggingEntered:) => on_dragging_entered as extern fn(&Object, Sel, id) -> BOOL,
|
||||||
|
(prepareForDragOperation:) => on_prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
|
||||||
|
(performDragOperation:) => on_perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
|
||||||
|
(concludeDragOperation:) => on_conclude_drag_operation as extern fn(&Object, Sel, id),
|
||||||
|
(draggingExited:) => on_dragging_exited as extern fn(&Object, Sel, id),
|
||||||
|
(window:willUseFullScreenPresentationOptions:) => on_window_will_use_full_screen_presentation_options as extern fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
|
||||||
|
(windowDidEnterFullScreen:) => on_window_did_enter_full_screen as extern fn(&Object, Sel, id),
|
||||||
|
(windowWillEnterFullScreen:) => on_window_will_enter_full_screen as extern fn(&Object, Sel, id),
|
||||||
|
(windowDidExitFullScreen:) => on_window_did_exit_full_screen as extern fn(&Object, Sel, id),
|
||||||
|
(windowWillExitFullScreen:) => on_window_will_exit_full_screen as extern fn(&Object, Sel, id),
|
||||||
|
(windowDidFailToEnterFullScreen:) => on_window_did_fail_to_enter_full_screen as extern fn(&Object, Sel, id),
|
||||||
|
(effectiveAppearanceDidChange:) => on_effective_appearance_did_change as extern fn(&Object, Sel, id),
|
||||||
|
(effectiveAppearanceDidChangedOnMainThread:) => on_effective_appearance_did_changed_on_main_thread as extern fn(&Object, Sel, id)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
let app = window.app_handle();
|
||||||
|
let window = window.clone();
|
||||||
|
update_window_theme(&window, HexColor::WHITE);
|
||||||
|
|
||||||
|
// Control window theme based on app update_window
|
||||||
|
let window = window.clone();
|
||||||
|
app.listen_any("yaak_bg_changed", move |ev| {
|
||||||
|
let payload = serde_json::from_str::<&str>(ev.payload())
|
||||||
|
.unwrap()
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
let color = HexColor::parse_rgb(payload).unwrap();
|
||||||
|
|
||||||
|
update_window_theme(&window, color);
|
||||||
|
});
|
||||||
|
}
|
||||||
+10
-5
@@ -4,9 +4,9 @@ use std::fs;
|
|||||||
use log::error;
|
use log::error;
|
||||||
use rand::distributions::{Alphanumeric, DistString};
|
use rand::distributions::{Alphanumeric, DistString};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::types::chrono::NaiveDateTime;
|
|
||||||
use sqlx::types::{Json, JsonValue};
|
|
||||||
use sqlx::{Pool, Sqlite};
|
use sqlx::{Pool, Sqlite};
|
||||||
|
use sqlx::types::{Json, JsonValue};
|
||||||
|
use sqlx::types::chrono::NaiveDateTime;
|
||||||
use tauri::{AppHandle, Manager, WebviewWindow, Wry};
|
use tauri::{AppHandle, Manager, WebviewWindow, Wry};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
@@ -52,6 +52,8 @@ pub struct Settings {
|
|||||||
pub updated_at: NaiveDateTime,
|
pub updated_at: NaiveDateTime,
|
||||||
pub theme: String,
|
pub theme: String,
|
||||||
pub appearance: String,
|
pub appearance: String,
|
||||||
|
pub theme_dark: String,
|
||||||
|
pub theme_light: String,
|
||||||
pub update_channel: String,
|
pub update_channel: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -883,7 +885,8 @@ async fn get_settings(mgr: &impl Manager<Wry>) -> Result<Settings, sqlx::Error>
|
|||||||
Settings,
|
Settings,
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
id, model, created_at, updated_at, theme, appearance, update_channel
|
id, model, created_at, updated_at, theme, appearance,
|
||||||
|
theme_dark, theme_light, update_channel
|
||||||
FROM settings
|
FROM settings
|
||||||
WHERE id = 'default'
|
WHERE id = 'default'
|
||||||
"#,
|
"#,
|
||||||
@@ -919,11 +922,13 @@ pub async fn update_settings(
|
|||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
UPDATE settings SET (
|
UPDATE settings SET (
|
||||||
theme, appearance, update_channel
|
theme, appearance, theme_dark, theme_light, update_channel
|
||||||
) = (?, ?, ?) WHERE id = 'default';
|
) = (?, ?, ?, ?, ?) WHERE id = 'default';
|
||||||
"#,
|
"#,
|
||||||
settings.theme,
|
settings.theme,
|
||||||
settings.appearance,
|
settings.appearance,
|
||||||
|
settings.theme_dark,
|
||||||
|
settings.theme_light,
|
||||||
settings.update_channel
|
settings.update_channel
|
||||||
)
|
)
|
||||||
.execute(&db)
|
.execute(&db)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use log::{debug, info};
|
use log::info;
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use tauri_plugin_dialog::DialogExt;
|
use tauri_plugin_dialog::DialogExt;
|
||||||
use tauri_plugin_updater::UpdaterExt;
|
use tauri_plugin_updater::UpdaterExt;
|
||||||
@@ -118,7 +118,6 @@ impl YaakUpdater {
|
|||||||
|
|
||||||
// Don't check if dev
|
// Don't check if dev
|
||||||
if is_dev() {
|
if is_dev() {
|
||||||
debug!("Not checking for updates in dev");
|
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// Borrowed from our friends at Hoppscotch
|
||||||
|
// https://github.com/hoppscotch/hoppscotch/blob/286fcd2bb08a84f027b10308d1e18da368f95ebf/packages/hoppscotch-selfhost-desktop/src-tauri/src/mac/window.rs
|
||||||
|
use std::mem::transmute;
|
||||||
|
|
||||||
|
use hex_color::HexColor;
|
||||||
|
|
||||||
|
use windows::Win32::Foundation::BOOL;
|
||||||
|
use windows::Win32::Foundation::COLORREF;
|
||||||
|
use windows::Win32::Foundation::HWND;
|
||||||
|
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
|
||||||
|
use windows::Win32::Graphics::Dwm::DWMWA_CAPTION_COLOR;
|
||||||
|
use windows::Win32::Graphics::Dwm::DWMWA_USE_IMMERSIVE_DARK_MODE;
|
||||||
|
use windows::Win32::UI::Controls::{WTA_NONCLIENT, WTNCA_NODRAWICON, WTNCA_NOMIRRORHELP, WTNCA_NOSYSMENU};
|
||||||
|
use windows::Win32::UI::Controls::SetWindowThemeAttribute;
|
||||||
|
use windows::Win32::UI::Controls::WTNCA_NODRAWCAPTION;
|
||||||
|
|
||||||
|
fn hex_color_to_colorref(color: HexColor) -> COLORREF {
|
||||||
|
// TODO: Remove this unsafe, This operation doesn't need to be unsafe!
|
||||||
|
unsafe {
|
||||||
|
COLORREF(transmute::<[u8; 4], u32>([color.r, color.g, color.b, 0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WinThemeAttribute {
|
||||||
|
flag: u32,
|
||||||
|
mask: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn update_bg_color(hwnd: &HWND, bg_color: HexColor) {
|
||||||
|
|
||||||
|
let use_dark_mode = BOOL::from(true);
|
||||||
|
|
||||||
|
let final_color = hex_color_to_colorref(bg_color);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
DwmSetWindowAttribute(
|
||||||
|
HWND(hwnd.0),
|
||||||
|
DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||||||
|
ptr::addr_of!(use_dark_mode) as *const c_void,
|
||||||
|
size_of::<BOOL>().try_into().unwrap()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
DwmSetWindowAttribute(
|
||||||
|
HWND(hwnd.0),
|
||||||
|
DWMWA_CAPTION_COLOR,
|
||||||
|
ptr::addr_of!(final_color) as *const c_void,
|
||||||
|
size_of::<COLORREF>().try_into().unwrap()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
let flags = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON;
|
||||||
|
let mask = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON | WTNCA_NOSYSMENU | WTNCA_NOMIRRORHELP;
|
||||||
|
let options = WinThemeAttribute { flag: flags, mask };
|
||||||
|
|
||||||
|
SetWindowThemeAttribute(
|
||||||
|
HWND(hwnd.0),
|
||||||
|
WTA_NONCLIENT,
|
||||||
|
ptr::addr_of!(options) as *const c_void,
|
||||||
|
size_of::<WinThemeAttribute>().try_into().unwrap()
|
||||||
|
).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub fn setup_win_window(window: &mut WebviewWindow) {
|
||||||
|
let win_handle = window.hwnd().unwrap();
|
||||||
|
let win_clone = win_handle.clone();
|
||||||
|
|
||||||
|
window.listen_global("yaak_bg_changed", move |ev| {
|
||||||
|
let payload = serde_json::from_str::<&str>(ev.payload().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
let color = HexColor::parse_rgb(payload).unwrap();
|
||||||
|
update_bg_color(&HWND(win_clone.0), color);
|
||||||
|
});
|
||||||
|
|
||||||
|
update_bg_color(&HWND(win_handle.0), HexColor::rgb(23, 23, 23));
|
||||||
|
}
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
use tauri::WebviewWindow;
|
|
||||||
|
|
||||||
const TRAFFIC_LIGHT_OFFSET_X: f64 = 13.0;
|
|
||||||
const TRAFFIC_LIGHT_OFFSET_Y: f64 = 18.0;
|
|
||||||
|
|
||||||
pub trait TrafficLightWindowExt {
|
|
||||||
fn position_traffic_lights(&self);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TrafficLightWindowExt for WebviewWindow {
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
fn position_traffic_lights(&self) {
|
|
||||||
// No-op on other platforms
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
fn position_traffic_lights(&self) {
|
|
||||||
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
|
|
||||||
use cocoa::foundation::NSRect;
|
|
||||||
|
|
||||||
let window = self.ns_window().unwrap() as cocoa::base::id;
|
|
||||||
|
|
||||||
let x = TRAFFIC_LIGHT_OFFSET_X;
|
|
||||||
let y = TRAFFIC_LIGHT_OFFSET_Y;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let close = window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
|
|
||||||
let miniaturize =
|
|
||||||
window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
|
|
||||||
let zoom = window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
|
|
||||||
|
|
||||||
let title_bar_container_view = close.superview().superview();
|
|
||||||
|
|
||||||
let close_rect: NSRect = msg_send![close, frame];
|
|
||||||
let button_height = close_rect.size.height;
|
|
||||||
|
|
||||||
let title_bar_frame_height = button_height + y;
|
|
||||||
let mut title_bar_rect = NSView::frame(title_bar_container_view);
|
|
||||||
title_bar_rect.size.height = title_bar_frame_height;
|
|
||||||
title_bar_rect.origin.y = NSView::frame(window).size.height - title_bar_frame_height;
|
|
||||||
let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
|
|
||||||
|
|
||||||
let window_buttons = vec![close, miniaturize, zoom];
|
|
||||||
let space_between = NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
|
|
||||||
|
|
||||||
for (i, button) in window_buttons.into_iter().enumerate() {
|
|
||||||
let mut rect: NSRect = NSView::frame(button);
|
|
||||||
rect.origin.x = x + (i as f64 * space_between);
|
|
||||||
button.setFrameOrigin(rect.origin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { getCurrent } from '@tauri-apps/api/webviewWindow';
|
import { getCurrent } from '@tauri-apps/api/webviewWindow';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import { useCommandPalette } from '../hooks/useCommandPalette';
|
import { useCommandPalette } from '../hooks/useCommandPalette';
|
||||||
import { cookieJarsQueryKey } from '../hooks/useCookieJars';
|
import { cookieJarsQueryKey } from '../hooks/useCookieJars';
|
||||||
import { foldersQueryKey } from '../hooks/useFolders';
|
import { foldersQueryKey } from '../hooks/useFolders';
|
||||||
@@ -19,12 +17,11 @@ import { useRecentRequests } from '../hooks/useRecentRequests';
|
|||||||
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||||
import { settingsQueryKey } from '../hooks/useSettings';
|
import { settingsQueryKey } from '../hooks/useSettings';
|
||||||
import { useSyncAppearance } from '../hooks/useSyncAppearance';
|
import { useSyncThemeToDocument } from '../hooks/useSyncThemeToDocument';
|
||||||
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
|
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
|
||||||
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
||||||
import type { Model } from '../lib/models';
|
import type { Model } from '../lib/models';
|
||||||
import { modelsEq } from '../lib/models';
|
import { modelsEq } from '../lib/models';
|
||||||
import { setPathname } from '../lib/persistPathname';
|
|
||||||
|
|
||||||
const DEFAULT_FONT_SIZE = 16;
|
const DEFAULT_FONT_SIZE = 16;
|
||||||
|
|
||||||
@@ -35,7 +32,7 @@ export function GlobalHooks() {
|
|||||||
useRecentRequests();
|
useRecentRequests();
|
||||||
|
|
||||||
// Other useful things
|
// Other useful things
|
||||||
useSyncAppearance();
|
useSyncThemeToDocument();
|
||||||
useSyncWindowTitle();
|
useSyncWindowTitle();
|
||||||
useGlobalCommands();
|
useGlobalCommands();
|
||||||
useCommandPalette();
|
useCommandPalette();
|
||||||
@@ -44,12 +41,6 @@ export function GlobalHooks() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
||||||
|
|
||||||
// Listen for location changes and update the pathname
|
|
||||||
const location = useLocation();
|
|
||||||
useEffect(() => {
|
|
||||||
setPathname(location.pathname).catch(console.error);
|
|
||||||
}, [location.pathname]);
|
|
||||||
|
|
||||||
interface ModelPayload {
|
interface ModelPayload {
|
||||||
model: Model;
|
model: Model;
|
||||||
windowLabel: string;
|
windowLabel: string;
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { useAppInfo } from '../hooks/useAppInfo';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IsDev({ children }: Props) {
|
||||||
|
const appInfo = useAppInfo();
|
||||||
|
if (!appInfo?.isDev) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
@@ -20,9 +20,11 @@ import { TabContent, Tabs } from './core/Tabs/Tabs';
|
|||||||
import { EmptyStateText } from './EmptyStateText';
|
import { EmptyStateText } from './EmptyStateText';
|
||||||
import { RecentResponsesDropdown } from './RecentResponsesDropdown';
|
import { RecentResponsesDropdown } from './RecentResponsesDropdown';
|
||||||
import { ResponseHeaders } from './ResponseHeaders';
|
import { ResponseHeaders } from './ResponseHeaders';
|
||||||
|
import { AudioViewer } from './responseViewers/AudioViewer';
|
||||||
import { CsvViewer } from './responseViewers/CsvViewer';
|
import { CsvViewer } from './responseViewers/CsvViewer';
|
||||||
import { ImageViewer } from './responseViewers/ImageViewer';
|
import { ImageViewer } from './responseViewers/ImageViewer';
|
||||||
import { TextViewer } from './responseViewers/TextViewer';
|
import { TextViewer } from './responseViewers/TextViewer';
|
||||||
|
import { VideoViewer } from './responseViewers/VideoViewer';
|
||||||
import { WebPageViewer } from './responseViewers/WebPageViewer';
|
import { WebPageViewer } from './responseViewers/WebPageViewer';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -76,7 +78,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
|||||||
'x-theme-responsePane',
|
'x-theme-responsePane',
|
||||||
'max-h-full h-full',
|
'max-h-full h-full',
|
||||||
'bg-background rounded-md border border-background-highlight',
|
'bg-background rounded-md border border-background-highlight',
|
||||||
'shadow relative',
|
'relative',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{activeResponse == null ? (
|
{activeResponse == null ? (
|
||||||
@@ -154,6 +156,10 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
|||||||
</div>
|
</div>
|
||||||
) : contentType?.startsWith('image') ? (
|
) : contentType?.startsWith('image') ? (
|
||||||
<ImageViewer className="pb-2" response={activeResponse} />
|
<ImageViewer className="pb-2" response={activeResponse} />
|
||||||
|
) : contentType?.startsWith('audio') ? (
|
||||||
|
<AudioViewer response={activeResponse} />
|
||||||
|
) : contentType?.startsWith('video') ? (
|
||||||
|
<VideoViewer response={activeResponse} />
|
||||||
) : activeResponse.contentLength > 2 * 1000 * 1000 ? (
|
) : activeResponse.contentLength > 2 * 1000 * 1000 ? (
|
||||||
<EmptyStateText>Cannot preview text responses larger than 2MB</EmptyStateText>
|
<EmptyStateText>Cannot preview text responses larger than 2MB</EmptyStateText>
|
||||||
) : viewMode === 'pretty' && contentType?.includes('html') ? (
|
) : viewMode === 'pretty' && contentType?.includes('html') ? (
|
||||||
@@ -161,7 +167,11 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
|||||||
) : contentType?.match(/csv|tab-separated/) ? (
|
) : contentType?.match(/csv|tab-separated/) ? (
|
||||||
<CsvViewer className="pb-2" response={activeResponse} />
|
<CsvViewer className="pb-2" response={activeResponse} />
|
||||||
) : (
|
) : (
|
||||||
<TextViewer response={activeResponse} pretty={viewMode === 'pretty'} />
|
<TextViewer
|
||||||
|
className="-mr-2" // Pull to the right
|
||||||
|
response={activeResponse}
|
||||||
|
pretty={viewMode === 'pretty'}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</TabContent>
|
</TabContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
||||||
|
import { useResolvedAppearance } from '../../hooks/useResolvedAppearance';
|
||||||
|
import { useResolvedTheme } from '../../hooks/useResolvedTheme';
|
||||||
|
import { useSettings } from '../../hooks/useSettings';
|
||||||
|
import { useThemes } from '../../hooks/useThemes';
|
||||||
|
import { useUpdateSettings } from '../../hooks/useUpdateSettings';
|
||||||
|
import { trackEvent } from '../../lib/analytics';
|
||||||
|
import { isThemeDark } from '../../lib/theme/window';
|
||||||
|
import type { ButtonProps } from '../core/Button';
|
||||||
|
import { Editor } from '../core/Editor';
|
||||||
|
import type { IconProps } from '../core/Icon';
|
||||||
|
import { IconButton } from '../core/IconButton';
|
||||||
|
import type { SelectOption } from '../core/Select';
|
||||||
|
import { Select } from '../core/Select';
|
||||||
|
import { HStack, VStack } from '../core/Stacks';
|
||||||
|
|
||||||
|
const buttonColors: ButtonProps['color'][] = [
|
||||||
|
'primary',
|
||||||
|
'info',
|
||||||
|
'success',
|
||||||
|
'notice',
|
||||||
|
'warning',
|
||||||
|
'danger',
|
||||||
|
'secondary',
|
||||||
|
'default',
|
||||||
|
];
|
||||||
|
|
||||||
|
const icons: IconProps['icon'][] = [
|
||||||
|
'info',
|
||||||
|
'box',
|
||||||
|
'update',
|
||||||
|
'alert',
|
||||||
|
'arrowBigRightDash',
|
||||||
|
'download',
|
||||||
|
'copy',
|
||||||
|
'magicWand',
|
||||||
|
'settings',
|
||||||
|
'trash',
|
||||||
|
'sparkles',
|
||||||
|
'pencil',
|
||||||
|
'paste',
|
||||||
|
'search',
|
||||||
|
'sendHorizontal',
|
||||||
|
];
|
||||||
|
|
||||||
|
export function SettingsAppearance() {
|
||||||
|
const workspace = useActiveWorkspace();
|
||||||
|
const settings = useSettings();
|
||||||
|
const updateSettings = useUpdateSettings();
|
||||||
|
const appearance = useResolvedAppearance();
|
||||||
|
const { themes } = useThemes();
|
||||||
|
const activeTheme = useResolvedTheme();
|
||||||
|
|
||||||
|
if (settings == null || workspace == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lightThemes: SelectOption<string>[] = themes
|
||||||
|
.filter((theme) => !isThemeDark(theme))
|
||||||
|
.map((theme) => ({
|
||||||
|
label: theme.name,
|
||||||
|
value: theme.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const darkThemes: SelectOption<string>[] = themes
|
||||||
|
.filter((theme) => isThemeDark(theme))
|
||||||
|
.map((theme) => ({
|
||||||
|
label: theme.name,
|
||||||
|
value: theme.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VStack space={2} className="mb-4">
|
||||||
|
<Select
|
||||||
|
name="appearance"
|
||||||
|
label="Appearance"
|
||||||
|
labelPosition="left"
|
||||||
|
size="sm"
|
||||||
|
value={settings.appearance}
|
||||||
|
onChange={async (appearance) => {
|
||||||
|
await updateSettings.mutateAsync({ ...settings, appearance });
|
||||||
|
trackEvent('setting', 'update', { appearance });
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{ label: 'Sync with OS', value: 'system' },
|
||||||
|
{ label: 'Light', value: 'light' },
|
||||||
|
{ label: 'Dark', value: 'dark' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<Select
|
||||||
|
name="lightTheme"
|
||||||
|
label={'Light Theme' + (appearance !== 'dark' ? ' (active)' : '')}
|
||||||
|
labelPosition="top"
|
||||||
|
size="sm"
|
||||||
|
value={activeTheme.light.id}
|
||||||
|
options={lightThemes}
|
||||||
|
onChange={async (themeLight) => {
|
||||||
|
await updateSettings.mutateAsync({ ...settings, themeLight });
|
||||||
|
trackEvent('setting', 'update', { themeLight });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
name="darkTheme"
|
||||||
|
label={'Dark Theme' + (appearance === 'dark' ? ' (active)' : '')}
|
||||||
|
labelPosition="top"
|
||||||
|
size="sm"
|
||||||
|
value={activeTheme.dark.id}
|
||||||
|
options={darkThemes}
|
||||||
|
onChange={async (themeDark) => {
|
||||||
|
await updateSettings.mutateAsync({ ...settings, themeDark });
|
||||||
|
trackEvent('setting', 'update', { themeDark });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<VStack
|
||||||
|
space={3}
|
||||||
|
className="mt-3 w-full bg-background p-3 border border-dashed border-background-highlight rounded"
|
||||||
|
>
|
||||||
|
<div className="text-sm text-fg font-bold">
|
||||||
|
Theme Preview <span className="text-fg-subtle">({appearance})</span>
|
||||||
|
</div>
|
||||||
|
<HStack space={1.5} alignItems="center" className="w-full">
|
||||||
|
{buttonColors.map((c, i) => (
|
||||||
|
<IconButton
|
||||||
|
key={c}
|
||||||
|
color={c}
|
||||||
|
size="2xs"
|
||||||
|
iconSize="xs"
|
||||||
|
icon={icons[i % icons.length]!}
|
||||||
|
iconClassName="text-fg"
|
||||||
|
title={`${c}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{buttonColors.map((c, i) => (
|
||||||
|
<IconButton
|
||||||
|
key={c}
|
||||||
|
color={c}
|
||||||
|
variant="border"
|
||||||
|
size="2xs"
|
||||||
|
iconSize="xs"
|
||||||
|
icon={icons[i % icons.length]!}
|
||||||
|
iconClassName="text-fg"
|
||||||
|
title={`${c}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</HStack>
|
||||||
|
<Editor
|
||||||
|
defaultValue={[
|
||||||
|
'let foo = { // Demo code editor',
|
||||||
|
' foo: ("bar" || "baz" ?? \'qux\'),',
|
||||||
|
' baz: [1, 10.2, null, false, true],',
|
||||||
|
'};',
|
||||||
|
].join('\n')}
|
||||||
|
heightMode="auto"
|
||||||
|
contentType="application/javascript"
|
||||||
|
/>
|
||||||
|
</VStack>
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { capitalize } from '../../lib/capitalize';
|
||||||
|
import { Banner } from '../core/Banner';
|
||||||
|
import { Button } from '../core/Button';
|
||||||
|
import { Editor } from '../core/Editor';
|
||||||
|
import type { IconProps } from '../core/Icon';
|
||||||
|
import { Icon } from '../core/Icon';
|
||||||
|
import { IconButton } from '../core/IconButton';
|
||||||
|
import { Input } from '../core/Input';
|
||||||
|
|
||||||
|
const buttonColors = [
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'info',
|
||||||
|
'success',
|
||||||
|
'warning',
|
||||||
|
'danger',
|
||||||
|
'default',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const icons: IconProps['icon'][] = [
|
||||||
|
'info',
|
||||||
|
'box',
|
||||||
|
'update',
|
||||||
|
'alert',
|
||||||
|
'arrowBigRightDash',
|
||||||
|
'download',
|
||||||
|
'copy',
|
||||||
|
'magicWand',
|
||||||
|
'settings',
|
||||||
|
'trash',
|
||||||
|
'sparkles',
|
||||||
|
'pencil',
|
||||||
|
'paste',
|
||||||
|
'search',
|
||||||
|
'sendHorizontal',
|
||||||
|
];
|
||||||
|
|
||||||
|
export function SettingsDesign() {
|
||||||
|
return (
|
||||||
|
<div className="p-2 flex flex-col gap-3">
|
||||||
|
<Input
|
||||||
|
label="Field Label"
|
||||||
|
name="demo"
|
||||||
|
placeholder="Placeholder"
|
||||||
|
size="sm"
|
||||||
|
rightSlot={<IconButton title="search" size="xs" className="w-8 m-0.5" icon="search" />}
|
||||||
|
/>
|
||||||
|
<Editor
|
||||||
|
defaultValue={[
|
||||||
|
'// Demo code editor',
|
||||||
|
'let foo = {',
|
||||||
|
' foo: ("bar" || "baz" ?? \'qux\'),',
|
||||||
|
' baz: [1, 10.2, null, false, true],',
|
||||||
|
'};',
|
||||||
|
].join('\n')}
|
||||||
|
heightMode="auto"
|
||||||
|
contentType="application/javascript"
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{buttonColors.map((c, i) => (
|
||||||
|
<Button key={c} color={c} size="sm" leftSlot={<Icon size="sm" icon={icons[i]!} />}>
|
||||||
|
{capitalize(c).slice(0, 4)}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{buttonColors.map((c, i) => (
|
||||||
|
<Button
|
||||||
|
key={c}
|
||||||
|
color={c}
|
||||||
|
variant="border"
|
||||||
|
size="sm"
|
||||||
|
leftSlot={<Icon size="sm" icon={icons[i]!} />}
|
||||||
|
>
|
||||||
|
{capitalize(c).slice(0, 4)}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
{icons.map((v, i) => (
|
||||||
|
<IconButton
|
||||||
|
color={buttonColors[i % buttonColors.length]}
|
||||||
|
title={v}
|
||||||
|
variant="border"
|
||||||
|
size="sm"
|
||||||
|
key={v}
|
||||||
|
icon={v}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<Banner color="primary">Primary banner</Banner>
|
||||||
|
<Banner color="secondary">Secondary banner</Banner>
|
||||||
|
<Banner color="danger">Danger banner</Banner>
|
||||||
|
<Banner color="warning">Warning banner</Banner>
|
||||||
|
<Banner color="success">Success banner</Banner>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import { createGlobalState } from 'react-use';
|
||||||
|
import { useAppInfo } from '../../hooks/useAppInfo';
|
||||||
|
import { capitalize } from '../../lib/capitalize';
|
||||||
|
import { TabContent, Tabs } from '../core/Tabs/Tabs';
|
||||||
|
import { SettingsAppearance } from './SettingsAppearance';
|
||||||
|
import { SettingsDesign } from './SettingsDesign';
|
||||||
|
import { SettingsGeneral } from './SettingsGeneral';
|
||||||
|
|
||||||
|
enum Tab {
|
||||||
|
General = 'general',
|
||||||
|
Appearance = 'appearance',
|
||||||
|
|
||||||
|
// Dev-only
|
||||||
|
Design = 'design',
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabs = [Tab.General, Tab.Appearance, Tab.Design];
|
||||||
|
|
||||||
|
const useTabState = createGlobalState<string>(Tab.Appearance);
|
||||||
|
|
||||||
|
export const SettingsDialog = () => {
|
||||||
|
const [tab, setTab] = useTabState();
|
||||||
|
const appInfo = useAppInfo();
|
||||||
|
const isDev = appInfo?.isDev ?? false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames('w-[70vw] max-w-[40rem]', 'h-[80vh]')}>
|
||||||
|
<Tabs
|
||||||
|
value={tab}
|
||||||
|
addBorders
|
||||||
|
label="Settings"
|
||||||
|
tabListClassName="h-md !-ml-1 mt-2"
|
||||||
|
onChangeValue={setTab}
|
||||||
|
tabs={tabs
|
||||||
|
.filter((t) => t !== Tab.Design || isDev)
|
||||||
|
.map((value) => ({ value, label: capitalize(value) }))}
|
||||||
|
>
|
||||||
|
<TabContent value={Tab.General} className="pt-3 overflow-y-auto h-full px-4">
|
||||||
|
<SettingsGeneral />
|
||||||
|
</TabContent>
|
||||||
|
<TabContent value={Tab.Appearance} className="pt-3 overflow-y-auto h-full px-4">
|
||||||
|
<SettingsAppearance />
|
||||||
|
</TabContent>
|
||||||
|
<TabContent value={Tab.Design} className="pt-3 overflow-y-auto h-full px-4">
|
||||||
|
<SettingsDesign />
|
||||||
|
</TabContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+20
-46
@@ -1,20 +1,20 @@
|
|||||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
||||||
import { useAppInfo } from '../hooks/useAppInfo';
|
import { useAppInfo } from '../../hooks/useAppInfo';
|
||||||
import { useCheckForUpdates } from '../hooks/useCheckForUpdates';
|
import { useCheckForUpdates } from '../../hooks/useCheckForUpdates';
|
||||||
import { useSettings } from '../hooks/useSettings';
|
import { useSettings } from '../../hooks/useSettings';
|
||||||
import { useUpdateSettings } from '../hooks/useUpdateSettings';
|
import { useUpdateSettings } from '../../hooks/useUpdateSettings';
|
||||||
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
import { useUpdateWorkspace } from '../../hooks/useUpdateWorkspace';
|
||||||
import { trackEvent } from '../lib/analytics';
|
import { trackEvent } from '../../lib/analytics';
|
||||||
import { Checkbox } from './core/Checkbox';
|
import { Checkbox } from '../core/Checkbox';
|
||||||
import { Heading } from './core/Heading';
|
import { Heading } from '../core/Heading';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from '../core/IconButton';
|
||||||
import { Input } from './core/Input';
|
import { Input } from '../core/Input';
|
||||||
import { KeyValueRow, KeyValueRows } from './core/KeyValueRow';
|
import { KeyValueRow, KeyValueRows } from '../core/KeyValueRow';
|
||||||
import { Select } from './core/Select';
|
import { Select } from '../core/Select';
|
||||||
import { Separator } from './core/Separator';
|
import { Separator } from '../core/Separator';
|
||||||
import { VStack } from './core/Stacks';
|
import { VStack } from '../core/Stacks';
|
||||||
|
|
||||||
export const SettingsDialog = () => {
|
export function SettingsGeneral() {
|
||||||
const workspace = useActiveWorkspace();
|
const workspace = useActiveWorkspace();
|
||||||
const updateWorkspace = useUpdateWorkspace(workspace?.id ?? null);
|
const updateWorkspace = useUpdateWorkspace(workspace?.id ?? null);
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
@@ -28,32 +28,6 @@ export const SettingsDialog = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack space={2} className="mb-4">
|
<VStack space={2} className="mb-4">
|
||||||
<Select
|
|
||||||
name="appearance"
|
|
||||||
label="Appearance"
|
|
||||||
labelPosition="left"
|
|
||||||
size="sm"
|
|
||||||
value={settings.appearance}
|
|
||||||
onChange={async (appearance) => {
|
|
||||||
await updateSettings.mutateAsync({ ...settings, appearance });
|
|
||||||
trackEvent('setting', 'update', { appearance });
|
|
||||||
}}
|
|
||||||
options={[
|
|
||||||
{
|
|
||||||
label: 'System',
|
|
||||||
value: 'system',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Light',
|
|
||||||
value: 'light',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Dark',
|
|
||||||
value: 'dark',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-[minmax(0,1fr)_auto] gap-1">
|
<div className="grid grid-cols-[minmax(0,1fr)_auto] gap-1">
|
||||||
<Select
|
<Select
|
||||||
name="updateChannel"
|
name="updateChannel"
|
||||||
@@ -133,10 +107,10 @@ export const SettingsDialog = () => {
|
|||||||
|
|
||||||
<Heading size={2}>App Info</Heading>
|
<Heading size={2}>App Info</Heading>
|
||||||
<KeyValueRows>
|
<KeyValueRows>
|
||||||
<KeyValueRow label="Version" value={appInfo.data?.version} />
|
<KeyValueRow label="Version" value={appInfo?.version} />
|
||||||
<KeyValueRow label="Data Directory" value={appInfo.data?.appDataDir} />
|
<KeyValueRow label="Data Directory" value={appInfo?.appDataDir} />
|
||||||
<KeyValueRow label="Logs Directory" value={appInfo.data?.appLogDir} />
|
<KeyValueRow label="Logs Directory" value={appInfo?.appLogDir} />
|
||||||
</KeyValueRows>
|
</KeyValueRows>
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
@@ -11,7 +11,7 @@ import { Icon } from './core/Icon';
|
|||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
import { useDialog } from './DialogContext';
|
import { useDialog } from './DialogContext';
|
||||||
import { KeyboardShortcutsDialog } from './KeyboardShortcutsDialog';
|
import { KeyboardShortcutsDialog } from './KeyboardShortcutsDialog';
|
||||||
import { SettingsDialog } from './SettingsDialog';
|
import { SettingsDialog } from './Settings/SettingsDialog';
|
||||||
|
|
||||||
export function SettingsDropdown() {
|
export function SettingsDropdown() {
|
||||||
const importData = useImportData();
|
const importData = useImportData();
|
||||||
@@ -24,8 +24,9 @@ export function SettingsDropdown() {
|
|||||||
const showSettings = () => {
|
const showSettings = () => {
|
||||||
dialog.show({
|
dialog.show({
|
||||||
id: 'settings',
|
id: 'settings',
|
||||||
size: 'md',
|
size: 'dynamic',
|
||||||
title: 'Settings',
|
noScroll: true,
|
||||||
|
noPadding: true,
|
||||||
render: () => <SettingsDialog />,
|
render: () => <SettingsDialog />,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -69,7 +70,7 @@ export function SettingsDropdown() {
|
|||||||
leftSlot: <Icon icon="folderOutput" />,
|
leftSlot: <Icon icon="folderOutput" />,
|
||||||
onSelect: () => exportData.mutate(),
|
onSelect: () => exportData.mutate(),
|
||||||
},
|
},
|
||||||
{ type: 'separator', label: `Yaak v${appInfo.data?.version}` },
|
{ type: 'separator', label: `Yaak v${appInfo?.version}` },
|
||||||
{
|
{
|
||||||
key: 'update-check',
|
key: 'update-check',
|
||||||
label: 'Check for Updates',
|
label: 'Check for Updates',
|
||||||
@@ -88,7 +89,7 @@ export function SettingsDropdown() {
|
|||||||
label: 'Changelog',
|
label: 'Changelog',
|
||||||
leftSlot: <Icon icon="cake" />,
|
leftSlot: <Icon icon="cake" />,
|
||||||
rightSlot: <Icon icon="externalLink" />,
|
rightSlot: <Icon icon="externalLink" />,
|
||||||
onSelect: () => open(`https://yaak.app/changelog/${appInfo.data?.version}`),
|
onSelect: () => open(`https://yaak.app/changelog/${appInfo?.version}`),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -468,47 +468,6 @@ export function Sidebar({ className }: Props) {
|
|||||||
handleEnd={handleEnd}
|
handleEnd={handleEnd}
|
||||||
handleDragStart={handleDragStart}
|
handleDragStart={handleDragStart}
|
||||||
/>
|
/>
|
||||||
{/*<div className="p-2 flex flex-col gap-1">*/}
|
|
||||||
{/* <div className="flex flex-wrap gap-1">*/}
|
|
||||||
{/* <Button color="primary">Primary</Button>*/}
|
|
||||||
{/* <Button color="secondary">Secondary</Button>*/}
|
|
||||||
{/* <Button color="info">Info</Button>*/}
|
|
||||||
{/* <Button color="success">Success</Button>*/}
|
|
||||||
{/* <Button color="warning">Warning</Button>*/}
|
|
||||||
{/* <Button color="danger">Danger</Button>*/}
|
|
||||||
{/* <Button color="default">Default</Button>*/}
|
|
||||||
{/* </div>*/}
|
|
||||||
{/* <div className="flex flex-wrap gap-1">*/}
|
|
||||||
{/* <Button variant="border" color="primary">*/}
|
|
||||||
{/* Primary*/}
|
|
||||||
{/* </Button>*/}
|
|
||||||
{/* <Button variant="border" color="secondary">*/}
|
|
||||||
{/* Secondary*/}
|
|
||||||
{/* </Button>*/}
|
|
||||||
{/* <Button variant="border" color="info">*/}
|
|
||||||
{/* Info*/}
|
|
||||||
{/* </Button>*/}
|
|
||||||
{/* <Button variant="border" color="success">*/}
|
|
||||||
{/* Success*/}
|
|
||||||
{/* </Button>*/}
|
|
||||||
{/* <Button variant="border" color="warning">*/}
|
|
||||||
{/* Warning*/}
|
|
||||||
{/* </Button>*/}
|
|
||||||
{/* <Button variant="border" color="danger">*/}
|
|
||||||
{/* Danger*/}
|
|
||||||
{/* </Button>*/}
|
|
||||||
{/* <Button variant="border" color="default">*/}
|
|
||||||
{/* Default*/}
|
|
||||||
{/* </Button>*/}
|
|
||||||
{/* </div>*/}
|
|
||||||
{/* <div className="flex flex-col gap-1">*/}
|
|
||||||
{/* <Banner color="primary">Primary banner</Banner>*/}
|
|
||||||
{/* <Banner color="secondary">Secondary banner</Banner>*/}
|
|
||||||
{/* <Banner color="danger">Danger banner</Banner>*/}
|
|
||||||
{/* <Banner color="warning">Warning banner</Banner>*/}
|
|
||||||
{/* <Banner color="success">Success banner</Banner>*/}
|
|
||||||
{/* </div>*/}
|
|
||||||
{/*</div>*/}
|
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -800,11 +759,14 @@ const SidebarItem = forwardRef(function SidebarItem(
|
|||||||
const name = await prompt({
|
const name = await prompt({
|
||||||
id: 'rename-request',
|
id: 'rename-request',
|
||||||
title: 'Rename Request',
|
title: 'Rename Request',
|
||||||
description: (
|
description:
|
||||||
<>
|
itemName === '' ? (
|
||||||
Enter a new name for <InlineCode>{itemName}</InlineCode>
|
'Enter a new name'
|
||||||
</>
|
) : (
|
||||||
),
|
<>
|
||||||
|
Enter a new name for <InlineCode>{itemName}</InlineCode>
|
||||||
|
</>
|
||||||
|
),
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
placeholder: 'New Name',
|
placeholder: 'New Name',
|
||||||
@@ -893,7 +855,7 @@ const SidebarItem = forwardRef(function SidebarItem(
|
|||||||
{isResponseLoading(latestHttpResponse) ? (
|
{isResponseLoading(latestHttpResponse) ? (
|
||||||
<Icon spin size="sm" icon="refresh" className="text-fg-subtler" />
|
<Icon spin size="sm" icon="refresh" className="text-fg-subtler" />
|
||||||
) : (
|
) : (
|
||||||
<StatusTag className="text-2xs dark:opacity-80" response={latestHttpResponse} />
|
<StatusTag className="text-2xs" response={latestHttpResponse} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export const UrlBar = memo(function UrlBar({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className={classNames(className)}>
|
<form onSubmit={handleSubmit} className={classNames('x-theme-urlBar', className)}>
|
||||||
<Input
|
<Input
|
||||||
autocompleteVariables
|
autocompleteVariables
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
@@ -76,7 +76,7 @@ export const UrlBar = memo(function UrlBar({
|
|||||||
onFocus={() => setIsFocused(true)}
|
onFocus={() => setIsFocused(true)}
|
||||||
onBlur={() => setIsFocused(false)}
|
onBlur={() => setIsFocused(false)}
|
||||||
onPaste={onPaste}
|
onPaste={onPaste}
|
||||||
containerClassName="shadow bg-background border"
|
containerClassName="bg-background border border-background-highlight"
|
||||||
onChange={onUrlChange}
|
onChange={onUrlChange}
|
||||||
defaultValue={url}
|
defaultValue={url}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { ReactNode } from 'react';
|
|||||||
interface Props {
|
interface Props {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
color?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger';
|
color?: 'primary' | 'secondary' | 'success' | 'notice' | 'warning' | 'danger';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Banner({ children, className, color = 'secondary' }: Props) {
|
export function Banner({ children, className, color = 'secondary' }: Props) {
|
||||||
|
|||||||
@@ -14,11 +14,12 @@ export type ButtonProps = Omit<HTMLAttributes<HTMLButtonElement>, 'color'> & {
|
|||||||
| 'primary'
|
| 'primary'
|
||||||
| 'info'
|
| 'info'
|
||||||
| 'success'
|
| 'success'
|
||||||
|
| 'notice'
|
||||||
| 'warning'
|
| 'warning'
|
||||||
| 'danger';
|
| 'danger';
|
||||||
variant?: 'border' | 'solid';
|
variant?: 'border' | 'solid';
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
size?: 'xs' | 'sm' | 'md';
|
size?: '2xs' | 'xs' | 'sm' | 'md';
|
||||||
justify?: 'start' | 'center';
|
justify?: 'start' | 'center';
|
||||||
type?: 'button' | 'submit';
|
type?: 'button' | 'submit';
|
||||||
forDropdown?: boolean;
|
forDropdown?: boolean;
|
||||||
@@ -60,24 +61,27 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
|||||||
`x-theme-button--${variant}`,
|
`x-theme-button--${variant}`,
|
||||||
`x-theme-button--${variant}--${color}`,
|
`x-theme-button--${variant}--${color}`,
|
||||||
'text-fg',
|
'text-fg',
|
||||||
|
'border', // They all have borders to ensure the same width
|
||||||
'max-w-full min-w-0', // Help with truncation
|
'max-w-full min-w-0', // Help with truncation
|
||||||
'hocus:opacity-100', // Force opacity for certain hover effects
|
'hocus:opacity-100', // Force opacity for certain hover effects
|
||||||
'whitespace-nowrap outline-none',
|
'whitespace-nowrap outline-none',
|
||||||
'flex-shrink-0 flex items-center',
|
'flex-shrink-0 flex items-center',
|
||||||
'focus-visible-or-class:ring rounded-md',
|
'focus-visible-or-class:ring',
|
||||||
disabled ? 'pointer-events-none opacity-disabled' : 'pointer-events-auto',
|
disabled ? 'pointer-events-none opacity-disabled' : 'pointer-events-auto',
|
||||||
justify === 'start' && 'justify-start',
|
justify === 'start' && 'justify-start',
|
||||||
justify === 'center' && 'justify-center',
|
justify === 'center' && 'justify-center',
|
||||||
size === 'md' && 'h-md px-3',
|
size === 'md' && 'h-md px-3 rounded-md',
|
||||||
size === 'sm' && 'h-sm px-2.5 text-sm',
|
size === 'sm' && 'h-sm px-2.5 text-sm rounded-md',
|
||||||
size === 'xs' && 'h-xs px-2 text-sm',
|
size === 'xs' && 'h-xs px-2 text-sm rounded-md',
|
||||||
|
size === '2xs' && 'h-5 px-1 text-xs rounded',
|
||||||
|
|
||||||
// Solids
|
// Solids
|
||||||
|
variant === 'solid' && 'border-transparent',
|
||||||
|
variant === 'solid' && color === 'custom' && 'ring-blue-400',
|
||||||
variant === 'solid' &&
|
variant === 'solid' &&
|
||||||
color !== 'custom' &&
|
color !== 'custom' &&
|
||||||
color !== 'default' &&
|
color !== 'default' &&
|
||||||
'bg-background enabled:hocus:bg-background-highlight ring-background-highlight-secondary',
|
'bg-background enabled:hocus:bg-background-highlight ring-background-highlight-secondary',
|
||||||
variant === 'solid' && color === 'custom' && 'ring-blue-400',
|
|
||||||
variant === 'solid' &&
|
variant === 'solid' &&
|
||||||
color === 'default' &&
|
color === 'default' &&
|
||||||
'enabled:hocus:bg-background-highlight ring-fg-info',
|
'enabled:hocus:bg-background-highlight ring-fg-info',
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function Checkbox({
|
|||||||
alignItems="center"
|
alignItems="center"
|
||||||
className={classNames(className, 'text-fg text-sm', disabled && 'opacity-disabled')}
|
className={classNames(className, 'text-fg text-sm', disabled && 'opacity-disabled')}
|
||||||
>
|
>
|
||||||
<div className={classNames(inputWrapperClassName, 'relative flex')}>
|
<div className={classNames(inputWrapperClassName, 'x-theme-input', 'relative flex')}>
|
||||||
<input
|
<input
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export function Dialog({
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
'h-full w-full grid grid-cols-[minmax(0,1fr)]',
|
'h-full w-full grid grid-cols-[minmax(0,1fr)]',
|
||||||
!noPadding && 'px-6 py-2',
|
!noPadding && 'px-6 py-2',
|
||||||
!noScroll && 'overflow-y-auto',
|
!noScroll && 'overflow-y-auto overflow-x-hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -413,7 +413,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
|||||||
)}
|
)}
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Overlay open variant="transparent" portalName="dropdown" zIndex={50}>
|
<Overlay open variant="transparent" portalName="dropdown" zIndex={50}>
|
||||||
<div className="x-theme-dialog">
|
<div className="x-theme-menu">
|
||||||
<div tabIndex={-1} aria-hidden className="fixed inset-0 z-30" onClick={handleClose} />
|
<div tabIndex={-1} aria-hidden className="fixed inset-0 z-30" onClick={handleClose} />
|
||||||
<motion.div
|
<motion.div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
.cm-cursor {
|
.cm-cursor {
|
||||||
@apply border-fg !important;
|
@apply border-fg !important;
|
||||||
/* Widen the cursor */
|
/* Widen the cursor */
|
||||||
@apply border-l-2;
|
@apply border-l-[2px];
|
||||||
}
|
}
|
||||||
|
|
||||||
&.cm-focused {
|
&.cm-focused {
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group relative h-full w-full">
|
<div className="group relative h-full w-full x-theme-editor bg-background">
|
||||||
{cmContainer}
|
{cmContainer}
|
||||||
{decoratedActions && (
|
{decoratedActions && (
|
||||||
<HStack
|
<HStack
|
||||||
|
|||||||
@@ -46,45 +46,23 @@ export const myHighlightStyle = HighlightStyle.define([
|
|||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.paren],
|
tag: [t.paren, t.bracket, t.brace],
|
||||||
color: 'var(--fg)',
|
color: 'var(--fg)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: [t.name, t.tagName, t.angleBracket, t.docString, t.number],
|
tag: [t.link, t.name, t.tagName, t.angleBracket, t.docString, t.number],
|
||||||
color: 'var(--fg-info)',
|
color: 'var(--fg-info)',
|
||||||
},
|
},
|
||||||
{ tag: [t.variableName], color: 'var(--fg-success)' },
|
{ tag: [t.variableName], color: 'var(--fg-success)' },
|
||||||
{ tag: [t.bool], color: 'var(--fg-info)' }, // TODO: Should be pink
|
{ tag: [t.bool], color: 'var(--fg-warning)' },
|
||||||
{ tag: [t.attributeName, t.propertyName], color: 'var(--fg-primary)' },
|
{ tag: [t.attributeName, t.propertyName], color: 'var(--fg-primary)' },
|
||||||
{ tag: [t.attributeValue], color: 'var(--fg-warning)' },
|
{ tag: [t.attributeValue], color: 'var(--fg-warning)' },
|
||||||
{ tag: [t.string], color: 'var(--fg-warning)' }, // TODO: Should be yellow
|
{ tag: [t.string], color: 'var(--fg-notice)' },
|
||||||
{ tag: [t.keyword, t.meta, t.operator], color: 'var(--fg-danger)' },
|
{ tag: [t.atom, t.meta, t.operator, t.bool, t.null, t.keyword], color: 'var(--fg-danger)' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const myTheme = EditorView.theme({}, { dark: true });
|
const myTheme = EditorView.theme({}, { dark: true });
|
||||||
|
|
||||||
// export const defaultHighlightStyle = HighlightStyle.define([
|
|
||||||
// { tag: t.meta, color: '#404740' },
|
|
||||||
// { tag: t.link, textDecoration: 'underline' },
|
|
||||||
// { tag: t.heading, textDecoration: 'underline', fontWeight: 'bold' },
|
|
||||||
// { tag: t.emphasis, fontStyle: 'italic' },
|
|
||||||
// { tag: t.strong, fontWeight: 'bold' },
|
|
||||||
// { tag: t.strikethrough, textDecoration: 'line-through' },
|
|
||||||
// { tag: t.keyword, color: '#708' },
|
|
||||||
// { tag: [t.atom, t.bool, t.url, t.contentSeparator, t.labelName], color: '#219' },
|
|
||||||
// { tag: [t.literal, t.inserted], color: '#164' },
|
|
||||||
// { tag: [t.string, t.deleted], color: '#a11' },
|
|
||||||
// { tag: [t.regexp, t.escape, t.special(t.string)], color: '#e40' },
|
|
||||||
// { tag: t.definition(t.variableName), color: '#00f' },
|
|
||||||
// { tag: t.local(t.variableName), color: '#30a' },
|
|
||||||
// { tag: [t.typeName, t.namespace], color: '#085' },
|
|
||||||
// { tag: t.className, color: '#167' },
|
|
||||||
// { tag: [t.special(t.variableName), t.macroName], color: '#256' },
|
|
||||||
// { tag: t.definition(t.propertyName), color: '#00c' },
|
|
||||||
// { tag: t.comment, color: '#940' },
|
|
||||||
// { tag: t.invalid, color: '#f00' },
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
const syntaxExtensions: Record<string, LanguageSupport> = {
|
const syntaxExtensions: Record<string, LanguageSupport> = {
|
||||||
'application/graphql': graphqlLanguageSupport(),
|
'application/graphql': graphqlLanguageSupport(),
|
||||||
'application/json': json(),
|
'application/json': json(),
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const icons = {
|
|||||||
export interface IconProps {
|
export interface IconProps {
|
||||||
icon: keyof typeof icons;
|
icon: keyof typeof icons;
|
||||||
className?: string;
|
className?: string;
|
||||||
size?: 'xs' | 'sm' | 'md' | 'lg';
|
size?: '2xs' | 'xs' | 'sm' | 'md' | 'lg';
|
||||||
spin?: boolean;
|
spin?: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
@@ -83,6 +83,7 @@ export const Icon = memo(function Icon({ icon, spin, size = 'md', className, tit
|
|||||||
size === 'md' && 'h-4 w-4',
|
size === 'md' && 'h-4 w-4',
|
||||||
size === 'sm' && 'h-3.5 w-3.5',
|
size === 'sm' && 'h-3.5 w-3.5',
|
||||||
size === 'xs' && 'h-3 w-3',
|
size === 'xs' && 'h-3 w-3',
|
||||||
|
size === '2xs' && 'h-2.5 w-2.5',
|
||||||
spin && 'animate-spin',
|
spin && 'animate-spin',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
|
|||||||
size === 'md' && 'w-9',
|
size === 'md' && 'w-9',
|
||||||
size === 'sm' && 'w-8',
|
size === 'sm' && 'w-8',
|
||||||
size === 'xs' && 'w-6',
|
size === 'xs' && 'w-6',
|
||||||
|
size === '2xs' && 'w-5',
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ export function InlineCode({ className, ...props }: HTMLAttributes<HTMLSpanEleme
|
|||||||
<code
|
<code
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'font-mono text-xs bg-background-highlight-secondary',
|
'font-mono text-xs bg-background-highlight-secondary border border-background-highlight',
|
||||||
'px-1.5 py-0.5 rounded text-fg shadow-inner',
|
'px-1.5 py-0.5 rounded text-fg-info shadow-inner',
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
|||||||
alignItems="stretch"
|
alignItems="stretch"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
containerClassName,
|
containerClassName,
|
||||||
|
'x-theme-input',
|
||||||
'relative w-full rounded-md text-fg',
|
'relative w-full rounded-md text-fg',
|
||||||
'border',
|
'border',
|
||||||
focused ? 'border-border-focus' : 'border-background-highlight',
|
focused ? 'border-border-focus' : 'border-background-highlight',
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import type { DropdownItem, DropdownItemSeparator, DropdownProps } from './Dropdown';
|
import type { DropdownItem, DropdownItemSeparator, DropdownProps } from './Dropdown';
|
||||||
import { Dropdown } from './Dropdown';
|
import { Dropdown } from './Dropdown';
|
||||||
@@ -9,6 +10,7 @@ export type RadioDropdownItem<T = string | null> =
|
|||||||
label: string;
|
label: string;
|
||||||
shortLabel?: string;
|
shortLabel?: string;
|
||||||
value: T;
|
value: T;
|
||||||
|
rightSlot?: ReactNode;
|
||||||
}
|
}
|
||||||
| DropdownItemSeparator;
|
| DropdownItemSeparator;
|
||||||
|
|
||||||
@@ -37,9 +39,10 @@ export function RadioDropdown<T = string | null>({
|
|||||||
key: item.label,
|
key: item.label,
|
||||||
label: item.label,
|
label: item.label,
|
||||||
shortLabel: item.shortLabel,
|
shortLabel: item.shortLabel,
|
||||||
|
rightSlot: item.rightSlot,
|
||||||
onSelect: () => onChange(item.value),
|
onSelect: () => onChange(item.value),
|
||||||
leftSlot: <Icon icon={value === item.value ? 'check' : 'empty'} />,
|
leftSlot: <Icon icon={value === item.value ? 'check' : 'empty'} />,
|
||||||
};
|
} as DropdownProps['items'][0];
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
...((extraItems ? [{ type: 'separator' }, ...extraItems] : []) as DropdownItem[]),
|
...((extraItems ? [{ type: 'separator' }, ...extraItems] : []) as DropdownItem[]),
|
||||||
|
|||||||
@@ -7,12 +7,17 @@ interface Props<T extends string> {
|
|||||||
labelClassName?: string;
|
labelClassName?: string;
|
||||||
hideLabel?: boolean;
|
hideLabel?: boolean;
|
||||||
value: T;
|
value: T;
|
||||||
options: { label: string; value: T }[];
|
options: SelectOption<T>[];
|
||||||
onChange: (value: T) => void;
|
onChange: (value: T) => void;
|
||||||
size?: 'xs' | 'sm' | 'md' | 'lg';
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SelectOption<T extends string> {
|
||||||
|
label: string;
|
||||||
|
value: T;
|
||||||
|
}
|
||||||
|
|
||||||
export function Select<T extends string>({
|
export function Select<T extends string>({
|
||||||
labelPosition = 'top',
|
labelPosition = 'top',
|
||||||
name,
|
name,
|
||||||
@@ -30,6 +35,7 @@ export function Select<T extends string>({
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
|
'x-theme-input',
|
||||||
'w-full',
|
'w-full',
|
||||||
'pointer-events-auto', // Just in case we're placing in disabled parent
|
'pointer-events-auto', // Just in case we're placing in disabled parent
|
||||||
labelPosition === 'left' && 'flex items-center gap-2',
|
labelPosition === 'left' && 'flex items-center gap-2',
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { memo, useCallback, useEffect, useRef } from 'react';
|
import { memo, useCallback, useEffect, useRef } from 'react';
|
||||||
import { Button } from '../Button';
|
|
||||||
import { Icon } from '../Icon';
|
import { Icon } from '../Icon';
|
||||||
import type { RadioDropdownProps } from '../RadioDropdown';
|
import type { RadioDropdownProps } from '../RadioDropdown';
|
||||||
import { RadioDropdown } from '../RadioDropdown';
|
import { RadioDropdown } from '../RadioDropdown';
|
||||||
@@ -25,6 +24,7 @@ interface Props {
|
|||||||
tabListClassName?: string;
|
tabListClassName?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
addBorders?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Tabs({
|
export function Tabs({
|
||||||
@@ -35,6 +35,7 @@ export function Tabs({
|
|||||||
tabs,
|
tabs,
|
||||||
className,
|
className,
|
||||||
tabListClassName,
|
tabListClassName,
|
||||||
|
addBorders,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
@@ -78,12 +79,15 @@ export function Tabs({
|
|||||||
'-ml-5 pl-3 pr-1 py-1',
|
'-ml-5 pl-3 pr-1 py-1',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<HStack space={2} className="flex-shrink-0">
|
<HStack space={2} className="h-full flex-shrink-0">
|
||||||
{tabs.map((t) => {
|
{tabs.map((t) => {
|
||||||
const isActive = t.value === value;
|
const isActive = t.value === value;
|
||||||
const btnClassName = classNames(
|
const btnClassName = classNames(
|
||||||
isActive ? 'text-fg' : 'text-fg-subtler hover:text-fg-subtle',
|
'h-full flex items-center text-sm rounded',
|
||||||
'!px-2 ml-[1px]',
|
'!px-2 ml-[1px]',
|
||||||
|
addBorders && 'border',
|
||||||
|
isActive ? 'text-fg' : 'text-fg-subtler hover:text-fg-subtle',
|
||||||
|
isActive && addBorders ? 'border-background-highlight' : 'border-transparent',
|
||||||
);
|
);
|
||||||
|
|
||||||
if ('options' in t) {
|
if ('options' in t) {
|
||||||
@@ -97,39 +101,34 @@ export function Tabs({
|
|||||||
value={t.options.value}
|
value={t.options.value}
|
||||||
onChange={t.options.onChange}
|
onChange={t.options.onChange}
|
||||||
>
|
>
|
||||||
<Button
|
<button
|
||||||
color="custom"
|
color="custom"
|
||||||
size="sm"
|
|
||||||
onClick={isActive ? undefined : () => handleTabChange(t.value)}
|
onClick={isActive ? undefined : () => handleTabChange(t.value)}
|
||||||
className={btnClassName}
|
className={btnClassName}
|
||||||
rightSlot={
|
|
||||||
<Icon
|
|
||||||
size="sm"
|
|
||||||
icon="chevronDown"
|
|
||||||
className={classNames(
|
|
||||||
'-mr-1.5 mt-0.5',
|
|
||||||
isActive ? 'text-fg-subtle' : 'opacity-50',
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{option && 'shortLabel' in option
|
{option && 'shortLabel' in option
|
||||||
? option.shortLabel
|
? option.shortLabel
|
||||||
: option?.label ?? 'Unknown'}
|
: option?.label ?? 'Unknown'}
|
||||||
</Button>
|
<TabAccent enabled isActive={isActive} />
|
||||||
|
<Icon
|
||||||
|
size="sm"
|
||||||
|
icon="chevronDown"
|
||||||
|
className={classNames('ml-1', isActive ? 'text-fg-subtle' : 'opacity-50')}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</RadioDropdown>
|
</RadioDropdown>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Button
|
<button
|
||||||
key={t.value}
|
key={t.value}
|
||||||
color="custom"
|
color="custom"
|
||||||
size="sm"
|
|
||||||
onClick={() => handleTabChange(t.value)}
|
onClick={() => handleTabChange(t.value)}
|
||||||
className={btnClassName}
|
className={btnClassName}
|
||||||
>
|
>
|
||||||
{t.label}
|
{t.label}
|
||||||
</Button>
|
<TabAccent enabled isActive={isActive} />
|
||||||
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
@@ -161,3 +160,14 @@ export const TabContent = memo(function TabContent({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function TabAccent({ isActive, enabled }: { isActive: boolean; enabled: boolean }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'w-full opacity-40 border-b-2',
|
||||||
|
isActive && enabled ? 'border-b-background-highlight' : 'border-b-transparent',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ export function Toast({
|
|||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'x-theme-dialog',
|
'x-theme-toast',
|
||||||
'pointer-events-auto',
|
'pointer-events-auto',
|
||||||
'relative bg-background pointer-events-auto',
|
'relative bg-background pointer-events-auto',
|
||||||
'rounded-lg',
|
'rounded-lg',
|
||||||
'border border-background-highlight dark:border-background-highlight-secondary shadow-xl',
|
'border border-background-highlight shadow-lg',
|
||||||
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-6rem)]',
|
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-6rem)]',
|
||||||
'w-[22rem] max-h-[80vh]',
|
'w-[22rem] max-h-[80vh]',
|
||||||
'm-2 grid grid-cols-[1fr_auto]',
|
'm-2 grid grid-cols-[1fr_auto]',
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||||
|
import React from 'react';
|
||||||
|
import type { HttpResponse } from '../../lib/models';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
response: HttpResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AudioViewer({ response }: Props) {
|
||||||
|
if (response.bodyPath === null) {
|
||||||
|
return <div>Empty response body</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const src = convertFileSrc(response.bodyPath);
|
||||||
|
|
||||||
|
// eslint-disable-next-line jsx-a11y/media-has-caption
|
||||||
|
return <audio className="w-full" controls src={src}></audio>;
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ import { convertFileSrc } from '@tauri-apps/api/core';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { HttpResponse } from '../../lib/models';
|
import type { HttpResponse } from '../../lib/models';
|
||||||
import { Button } from '../core/Button';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
response: HttpResponse;
|
response: HttpResponse;
|
||||||
@@ -23,13 +22,9 @@ export function ImageViewer({ response, className }: Props) {
|
|||||||
<>
|
<>
|
||||||
<div className="text-sm italic text-fg-subtler">
|
<div className="text-sm italic text-fg-subtler">
|
||||||
Response body is too large to preview.{' '}
|
Response body is too large to preview.{' '}
|
||||||
<Button
|
<button className="cursor-pointer underline hover:text-fg" onClick={() => setShow(true)}>
|
||||||
className="cursor-pointer underline hover:text-fg"
|
|
||||||
color="secondary"
|
|
||||||
onClick={() => setShow(true)}
|
|
||||||
>
|
|
||||||
Show anyway
|
Show anyway
|
||||||
</Button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ const extraExtensions = [hyperlink];
|
|||||||
interface Props {
|
interface Props {
|
||||||
response: HttpResponse;
|
response: HttpResponse;
|
||||||
pretty: boolean;
|
pretty: boolean;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TextViewer({ response, pretty }: Props) {
|
export function TextViewer({ response, pretty, className }: Props) {
|
||||||
const [isSearching, toggleIsSearching] = useToggle();
|
const [isSearching, toggleIsSearching] = useToggle();
|
||||||
const [filterText, setDebouncedFilterText, setFilterText] = useDebouncedState<string>('', 400);
|
const [filterText, setDebouncedFilterText, setFilterText] = useDebouncedState<string>('', 400);
|
||||||
|
|
||||||
@@ -85,6 +86,7 @@ export function TextViewer({ response, pretty }: Props) {
|
|||||||
return (
|
return (
|
||||||
<Editor
|
<Editor
|
||||||
readOnly
|
readOnly
|
||||||
|
className={className}
|
||||||
forceUpdateKey={body}
|
forceUpdateKey={body}
|
||||||
defaultValue={body}
|
defaultValue={body}
|
||||||
contentType={contentType}
|
contentType={contentType}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||||
|
import React from 'react';
|
||||||
|
import type { HttpResponse } from '../../lib/models';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
response: HttpResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VideoViewer({ response }: Props) {
|
||||||
|
if (response.bodyPath === null) {
|
||||||
|
return <div>Empty response body</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const src = convertFileSrc(response.bodyPath);
|
||||||
|
|
||||||
|
// eslint-disable-next-line jsx-a11y/media-has-caption
|
||||||
|
return <video className="w-full" controls src={src}></video>;
|
||||||
|
}
|
||||||
@@ -16,5 +16,5 @@ export function useAppInfo() {
|
|||||||
const metadata = await invoke('cmd_metadata');
|
const metadata = await invoke('cmd_metadata');
|
||||||
return metadata as AppInfo;
|
return metadata as AppInfo;
|
||||||
},
|
},
|
||||||
});
|
}).data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export function useCommandPalette() {
|
|||||||
const appInfo = useAppInfo();
|
const appInfo = useAppInfo();
|
||||||
useHotKey('command_palette.toggle', () => {
|
useHotKey('command_palette.toggle', () => {
|
||||||
// Disabled in production for now
|
// Disabled in production for now
|
||||||
if (!appInfo.data?.isDev) {
|
if (!appInfo?.isDev) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,23 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import type { Appearance } from '../lib/theme/window';
|
import type { Appearance } from '../lib/theme/window';
|
||||||
import {
|
import { getPreferredAppearance, subscribeToPreferredAppearanceChange } from '../lib/theme/window';
|
||||||
setAppearanceOnDocument,
|
|
||||||
getPreferredAppearance,
|
|
||||||
subscribeToPreferredAppearanceChange,
|
|
||||||
} from '../lib/theme/window';
|
|
||||||
import { useSettings } from './useSettings';
|
import { useSettings } from './useSettings';
|
||||||
|
|
||||||
export function useSyncAppearance() {
|
export function useResolvedAppearance() {
|
||||||
const [preferredAppearance, setPreferredAppearance] = useState<Appearance>(
|
const [preferredAppearance, setPreferredAppearance] = useState<Appearance>(
|
||||||
getPreferredAppearance(),
|
getPreferredAppearance(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const settings = useSettings();
|
|
||||||
|
|
||||||
// Set appearance when preferred theme changes
|
// Set appearance when preferred theme changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return subscribeToPreferredAppearanceChange(setPreferredAppearance);
|
return subscribeToPreferredAppearanceChange(setPreferredAppearance);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const settings = useSettings();
|
||||||
const appearance =
|
const appearance =
|
||||||
settings == null || settings?.appearance === 'system'
|
settings == null || settings?.appearance === 'system'
|
||||||
? preferredAppearance
|
? preferredAppearance
|
||||||
: settings.appearance;
|
: settings.appearance;
|
||||||
|
|
||||||
useEffect(() => {
|
return appearance;
|
||||||
if (settings == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setAppearanceOnDocument(settings.appearance as Appearance);
|
|
||||||
}, [appearance, settings]);
|
|
||||||
|
|
||||||
return { appearance };
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { isThemeDark } from '../lib/theme/window';
|
||||||
|
import { useResolvedAppearance } from './useResolvedAppearance';
|
||||||
|
import { useSettings } from './useSettings';
|
||||||
|
import { useThemes } from './useThemes';
|
||||||
|
|
||||||
|
export function useResolvedTheme() {
|
||||||
|
const appearance = useResolvedAppearance();
|
||||||
|
const settings = useSettings();
|
||||||
|
const { themes, fallback } = useThemes();
|
||||||
|
|
||||||
|
const darkThemes = themes.filter((t) => isThemeDark(t));
|
||||||
|
const lightThemes = themes.filter((t) => !isThemeDark(t));
|
||||||
|
|
||||||
|
const dark = darkThemes.find((t) => t.id === settings?.themeDark) ?? fallback.dark;
|
||||||
|
const light = lightThemes.find((t) => t.id === settings?.themeLight) ?? fallback.light;
|
||||||
|
|
||||||
|
const active = appearance === 'dark' ? dark : light;
|
||||||
|
|
||||||
|
return { dark, light, active };
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { emit } from '@tauri-apps/api/event';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import type { YaakTheme } from '../lib/theme/window';
|
||||||
|
import { addThemeStylesToDocument, setThemeOnDocument } from '../lib/theme/window';
|
||||||
|
import { useResolvedTheme } from './useResolvedTheme';
|
||||||
|
|
||||||
|
export function useSyncThemeToDocument() {
|
||||||
|
const theme = useResolvedTheme();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setThemeOnDocument(theme.active);
|
||||||
|
emitBgChange(theme.active);
|
||||||
|
}, [theme.active]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addThemeStylesToDocument(theme.active);
|
||||||
|
}, [theme.active]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitBgChange(t: YaakTheme) {
|
||||||
|
if (t.background == null) return;
|
||||||
|
emit('yaak_bg_changed', t.background.hex()).catch(console.error);
|
||||||
|
}
|
||||||
@@ -1,23 +1,24 @@
|
|||||||
// import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
// import { fallbackRequestName } from '../lib/fallbackRequestName';
|
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||||
// import { useActiveEnvironment } from './useActiveEnvironment';
|
import { useActiveEnvironment } from './useActiveEnvironment';
|
||||||
// import { useActiveRequest } from './useActiveRequest';
|
import { useActiveRequest } from './useActiveRequest';
|
||||||
// import { useActiveWorkspace } from './useActiveWorkspace';
|
import { useActiveWorkspace } from './useActiveWorkspace';
|
||||||
|
|
||||||
export function useSyncWindowTitle() {
|
export function useSyncWindowTitle() {
|
||||||
// const activeRequest = useActiveRequest();
|
const activeRequest = useActiveRequest();
|
||||||
// const activeWorkspace = useActiveWorkspace();
|
const activeWorkspace = useActiveWorkspace();
|
||||||
// const activeEnvironment = useActiveEnvironment();
|
const activeEnvironment = useActiveEnvironment();
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// let newTitle = activeWorkspace ? activeWorkspace.name : 'Yaak';
|
let newTitle = activeWorkspace ? activeWorkspace.name : 'Yaak';
|
||||||
// if (activeEnvironment) {
|
if (activeEnvironment) {
|
||||||
// newTitle += ` [${activeEnvironment.name}]`;
|
newTitle += ` [${activeEnvironment.name}]`;
|
||||||
// }
|
}
|
||||||
// if (activeRequest) {
|
if (activeRequest) {
|
||||||
// newTitle += ` – ${fallbackRequestName(activeRequest)}`;
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
// }
|
newTitle += ` – ${fallbackRequestName(activeRequest)}`;
|
||||||
//
|
}
|
||||||
// // TODO: This resets the stoplight position so we can't use it yet
|
|
||||||
// // getCurrent().setTitle(newTitle).catch(console.error);
|
// TODO: This resets the stoplight position so we can't use it yet
|
||||||
// }, [activeEnvironment, activeRequest, activeWorkspace]);
|
// invoke('cmd_set_title', { title: newTitle }).catch(console.error);
|
||||||
|
}, [activeEnvironment, activeRequest, activeWorkspace]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { yaakDark, yaakLight, yaakThemes } from '../lib/theme/themes';
|
||||||
|
|
||||||
|
export function useThemes() {
|
||||||
|
const dark = yaakDark;
|
||||||
|
const light = yaakLight;
|
||||||
|
|
||||||
|
const otherThemes = yaakThemes
|
||||||
|
.filter((t) => t.id !== dark.id && t.id !== light.id)
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
const themes = [dark, light, ...otherThemes];
|
||||||
|
return { themes, fallback: { dark, light } };
|
||||||
|
}
|
||||||
@@ -23,7 +23,6 @@ export function fallbackRequestName(r: HttpRequest | GrpcRequest | null): string
|
|||||||
try {
|
try {
|
||||||
const url = new URL(fixedUrl);
|
const url = new URL(fixedUrl);
|
||||||
const pathname = url.pathname === '/' ? '' : url.pathname;
|
const pathname = url.pathname === '/' ? '' : url.pathname;
|
||||||
console.log('hello', fixedUrl);
|
|
||||||
return `${url.host}${pathname}`;
|
return `${url.host}${pathname}`;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Nothing
|
// Nothing
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ export interface Settings extends BaseModel {
|
|||||||
readonly model: 'settings';
|
readonly model: 'settings';
|
||||||
theme: string;
|
theme: string;
|
||||||
appearance: string;
|
appearance: string;
|
||||||
|
themeLight: string;
|
||||||
|
themeDark: string;
|
||||||
updateChannel: string;
|
updateChannel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
import { getCurrent } from '@tauri-apps/api/webviewWindow';
|
|
||||||
import { getKeyValue, setKeyValue } from './keyValueStore';
|
|
||||||
|
|
||||||
const key = ['window_pathname', getCurrent().label];
|
|
||||||
const namespace = 'no_sync';
|
|
||||||
const fallback = undefined;
|
|
||||||
|
|
||||||
export async function setPathname(value: string) {
|
|
||||||
await setKeyValue<string | undefined>({ key, namespace, value });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function maybeRestorePathname() {
|
|
||||||
if (window.location.pathname !== '/') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathname = await getKeyValue<string | undefined>({ key, namespace, fallback });
|
|
||||||
if (pathname != null) {
|
|
||||||
window.location.replace(pathname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,15 @@ export class Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static transparent(): Color {
|
static transparent(): Color {
|
||||||
return new Color('rgba(0, 0, 0, 0.1)', 'light');
|
return new Color('rgb(0,0,0)', 'light').translucify(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static white(): Color {
|
||||||
|
return new Color('rgb(0,0,0)', 'light').lower(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static black(): Color {
|
||||||
|
return new Color('rgb(0,0,0)', 'light').lift(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private clone(): Color {
|
private clone(): Color {
|
||||||
@@ -80,6 +88,10 @@ export class Color {
|
|||||||
return `hsla(${h}, ${s}%, ${l}%, ${a})`;
|
return `hsla(${h}, ${s}%, ${l}%, ${a})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hex(): string {
|
||||||
|
return parseColor(this.css()).hex;
|
||||||
|
}
|
||||||
|
|
||||||
private _lighten(mod: number): Color {
|
private _lighten(mod: number): Color {
|
||||||
const c = this.clone();
|
const c = this.clone();
|
||||||
c.lightness = this.lightness + (100 - this.lightness) * mod;
|
c.lightness = this.lightness + (100 - this.lightness) * mod;
|
||||||
|
|||||||
@@ -0,0 +1,376 @@
|
|||||||
|
import { Color } from './color';
|
||||||
|
import type { YaakTheme } from './window';
|
||||||
|
|
||||||
|
export const yaakLight: YaakTheme = {
|
||||||
|
id: 'yaak-light',
|
||||||
|
name: 'Yaak',
|
||||||
|
background: new Color('#f2f4f7', 'light').lower(1),
|
||||||
|
foreground: new Color('hsl(219,23%,15%)', 'light'),
|
||||||
|
colors: {
|
||||||
|
primary: new Color('hsl(266,100%,70%)', 'light'),
|
||||||
|
secondary: new Color('hsl(220,24%,59%)', 'light'),
|
||||||
|
info: new Color('hsl(206,100%,48%)', 'light'),
|
||||||
|
success: new Color('hsl(155,95%,33%)', 'light'),
|
||||||
|
notice: new Color('hsl(45,100%,41%)', 'light'),
|
||||||
|
warning: new Color('hsl(30,100%,43%)', 'light'),
|
||||||
|
danger: new Color('hsl(335,75%,57%)', 'light'),
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
sidebar: {
|
||||||
|
background: new Color('#f2f4f7', 'light').lower(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const yaakDark: YaakTheme = {
|
||||||
|
id: 'yaak-dark',
|
||||||
|
name: 'Yaak',
|
||||||
|
background: new Color('hsl(244,23%,12%)', 'dark'),
|
||||||
|
backgroundHighlight: new Color('hsl(244,23%,12%)', 'dark').lift(0.17),
|
||||||
|
backgroundHighlightSecondary: new Color('hsl(244,23%,12%)', 'dark').lift(0.1),
|
||||||
|
foreground: new Color('#bcbad4', 'dark'),
|
||||||
|
|
||||||
|
colors: {
|
||||||
|
primary: new Color('hsl(266,100%,79%)', 'dark'),
|
||||||
|
secondary: new Color('hsl(245,23%,60%)', 'dark'),
|
||||||
|
info: new Color('hsl(206,100%,63%)', 'dark'),
|
||||||
|
success: new Color('hsl(150,99%,44%)', 'dark'),
|
||||||
|
notice: new Color('hsl(48,80%,63%)', 'dark'),
|
||||||
|
warning: new Color('hsl(28,100%,61%)', 'dark'),
|
||||||
|
danger: new Color('hsl(342,90%,68%)', 'dark'),
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
button: {
|
||||||
|
colors: {
|
||||||
|
primary: new Color('hsl(266,100%,79%)', 'dark').lower(0.1),
|
||||||
|
secondary: new Color('hsl(245,23%,60%)', 'dark').lower(0.1),
|
||||||
|
info: new Color('hsl(206,100%,63%)', 'dark').lower(0.1),
|
||||||
|
success: new Color('hsl(150,99%,44%)', 'dark').lower(0.1),
|
||||||
|
notice: new Color('hsl(48,80%,63%)', 'dark').lower(0.1),
|
||||||
|
warning: new Color('hsl(28,100%,61%)', 'dark').lower(0.1),
|
||||||
|
danger: new Color('hsl(342,90%,68%)', 'dark').lower(0.1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
backgroundHighlight: new Color('hsl(244,23%,12%)', 'dark').lift(0.18),
|
||||||
|
},
|
||||||
|
dialog: {
|
||||||
|
backgroundHighlight: new Color('hsl(244,23%,12%)', 'dark').lift(0.11),
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
background: new Color('hsl(243,23%,15%)', 'dark'),
|
||||||
|
backgroundHighlight: new Color('hsl(244,23%,12%)', 'dark').lift(0.09),
|
||||||
|
},
|
||||||
|
responsePane: {
|
||||||
|
background: new Color('hsl(243,23%,15%)', 'dark'),
|
||||||
|
backgroundHighlight: new Color('hsl(244,23%,12%)', 'dark').lift(0.09),
|
||||||
|
},
|
||||||
|
appHeader: {
|
||||||
|
background: new Color('hsl(244,23%,12%)', 'dark'),
|
||||||
|
backgroundHighlight: new Color('hsl(244,23%,12%)', 'dark').lift(0.1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const monokaiProOctagon: YaakTheme = {
|
||||||
|
id: 'monokai-pro-octagon',
|
||||||
|
name: 'Monokai Pro Octagon',
|
||||||
|
background: new Color('#282a3a', 'dark'),
|
||||||
|
foreground: new Color('#eaf2f1', 'dark'),
|
||||||
|
foregroundSubtle: new Color('#b2b9bd', 'dark'),
|
||||||
|
foregroundSubtler: new Color('#767b81', 'dark'),
|
||||||
|
|
||||||
|
colors: {
|
||||||
|
primary: new Color('#c39ac9', 'dark'),
|
||||||
|
secondary: new Color('#b2b9bd', 'dark'),
|
||||||
|
info: new Color('#9cd1bb', 'dark'),
|
||||||
|
success: new Color('#bad761', 'dark'),
|
||||||
|
notice: new Color('#ffd76d', 'dark'),
|
||||||
|
warning: new Color('#ff9b5e', 'dark'),
|
||||||
|
danger: new Color('#ff657a', 'dark'),
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
appHeader: {
|
||||||
|
background: new Color('#1e1f2b', 'dark'),
|
||||||
|
foreground: new Color('#b2b9bd', 'dark'),
|
||||||
|
foregroundSubtle: new Color('#767b81', 'dark'),
|
||||||
|
foregroundSubtler: new Color('#696d77', 'dark'),
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
colors: {
|
||||||
|
primary: new Color('#c39ac9', 'dark').lower(0.1).desaturate(0.1),
|
||||||
|
secondary: new Color('#b2b9bd', 'dark').lower(0.1).desaturate(0.1),
|
||||||
|
info: new Color('#9cd1bb', 'dark').lower(0.1).desaturate(0.1),
|
||||||
|
success: new Color('#bad761', 'dark').lower(0.1).desaturate(0.1),
|
||||||
|
notice: new Color('#ffd76d', 'dark').lower(0.1).desaturate(0.1),
|
||||||
|
warning: new Color('#ff9b5e', 'dark').lower(0.1).desaturate(0.1),
|
||||||
|
danger: new Color('#ff657a', 'dark').lower(0.1).desaturate(0.1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const catppuccinLatte: YaakTheme = {
|
||||||
|
name: 'Catppuccin Latte',
|
||||||
|
id: 'catppuccin-light',
|
||||||
|
background: new Color('#eff1f5', 'light'),
|
||||||
|
foreground: new Color('#4c4f69', 'dark'),
|
||||||
|
foregroundSubtle: new Color('#6c6f85', 'light'),
|
||||||
|
foregroundSubtler: new Color('#8c8fa1', 'light'),
|
||||||
|
colors: {
|
||||||
|
primary: new Color('#8839ef', 'light'),
|
||||||
|
secondary: new Color('#6c6f85', 'light'),
|
||||||
|
info: new Color('#7287fd', 'light'),
|
||||||
|
success: new Color('#179299', 'light'),
|
||||||
|
notice: new Color('#df8e1d', 'light'),
|
||||||
|
warning: new Color('#fe640b', 'light'),
|
||||||
|
danger: new Color('#e64553', 'light'),
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
sidebar: {
|
||||||
|
background: new Color('#e6e9ef', 'light'),
|
||||||
|
backgroundHighlight: new Color('#e6e9ef', 'light').lift(0.05),
|
||||||
|
foregroundSubtler: new Color('#7287fd', 'light'),
|
||||||
|
},
|
||||||
|
appHeader: {
|
||||||
|
background: new Color('#dce0e8', 'light'),
|
||||||
|
backgroundHighlight: new Color('#e6e9ef', 'light').lift(0.05),
|
||||||
|
foregroundSubtler: new Color('#7287fd', 'light'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const catppuccinMocha: YaakTheme = {
|
||||||
|
name: 'Catppuccin Mocha',
|
||||||
|
id: 'catppuccin-mocha',
|
||||||
|
background: new Color('#181825', 'dark'),
|
||||||
|
foreground: new Color('#cdd6f4', 'dark'),
|
||||||
|
foregroundSubtle: new Color('#a6adc8', 'dark'),
|
||||||
|
foregroundSubtler: new Color('#7f849c', 'dark'),
|
||||||
|
colors: {
|
||||||
|
primary: new Color('#c6a0f6', 'dark'),
|
||||||
|
secondary: new Color('#bac2de', 'dark'),
|
||||||
|
info: new Color('#89b4fa', 'dark'),
|
||||||
|
success: new Color('#a6e3a1', 'dark'),
|
||||||
|
notice: new Color('#f9e2af', 'dark'),
|
||||||
|
warning: new Color('#fab387', 'dark'),
|
||||||
|
danger: new Color('#f38ba8', 'dark'),
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
dialog: {
|
||||||
|
background: new Color('#181825', 'dark'),
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
background: new Color('#1e1e2e', 'dark'),
|
||||||
|
backgroundHighlight: new Color('#1e1e2e', 'dark').lift(0.05),
|
||||||
|
},
|
||||||
|
appHeader: {
|
||||||
|
background: new Color('#11111b', 'dark'),
|
||||||
|
backgroundHighlight: new Color('#11111b', 'dark').lift(0.1),
|
||||||
|
},
|
||||||
|
responsePane: {
|
||||||
|
background: new Color('#1e1e2e', 'dark'),
|
||||||
|
backgroundHighlight: new Color('#1e1e2e', 'dark').lift(0.05),
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
colors: {
|
||||||
|
primary: new Color('#cba6f7', 'dark').lower(0.2).desaturate(0.2),
|
||||||
|
secondary: new Color('#bac2de', 'dark').lower(0.2).desaturate(0.2),
|
||||||
|
info: new Color('#89b4fa', 'dark').lower(0.2).desaturate(0.2),
|
||||||
|
success: new Color('#a6e3a1', 'dark').lower(0.2).desaturate(0.2),
|
||||||
|
notice: new Color('#f9e2af', 'dark').lower(0.2).desaturate(0.2),
|
||||||
|
warning: new Color('#fab387', 'dark').lower(0.2).desaturate(0.2),
|
||||||
|
danger: new Color('#f38ba8', 'dark').lower(0.2).desaturate(0.2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const relaxing: YaakTheme = {
|
||||||
|
name: 'Relaxing',
|
||||||
|
id: 'relaxing',
|
||||||
|
background: new Color('#2b1e3b', 'dark'),
|
||||||
|
foreground: new Color('#ede2f5', 'dark'),
|
||||||
|
colors: {
|
||||||
|
primary: new Color('#cba6f7', 'dark'),
|
||||||
|
secondary: new Color('#bac2de', 'dark'),
|
||||||
|
info: new Color('#89b4fa', 'dark'),
|
||||||
|
success: new Color('#a6e3a1', 'dark'),
|
||||||
|
notice: new Color('#f9e2af', 'dark'),
|
||||||
|
warning: new Color('#fab387', 'dark'),
|
||||||
|
danger: new Color('#f38ba8', 'dark'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rosePineMoon: YaakTheme = {
|
||||||
|
id: 'rose-pine-moon',
|
||||||
|
name: 'Rosé Pine Moon',
|
||||||
|
background: new Color('#232136', 'dark'),
|
||||||
|
foreground: new Color('#e0def4', 'dark'),
|
||||||
|
foregroundSubtle: new Color('#908caa', 'dark'),
|
||||||
|
foregroundSubtler: new Color('#6e6a86', 'dark'),
|
||||||
|
colors: {
|
||||||
|
primary: new Color('#c4a7e7', 'dark'),
|
||||||
|
secondary: new Color('#908caa', 'dark'),
|
||||||
|
info: new Color('#68aeca', 'dark'),
|
||||||
|
success: new Color('#9ccfd8', 'dark'),
|
||||||
|
notice: new Color('#f6c177', 'dark'),
|
||||||
|
warning: new Color('#ea9a97', 'dark'),
|
||||||
|
danger: new Color('#eb6f92', 'dark'),
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
responsePane: {
|
||||||
|
background: new Color('#2a273f', 'dark'),
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
background: new Color('#2a273f', 'dark'),
|
||||||
|
},
|
||||||
|
menu: {
|
||||||
|
background: new Color('#393552', 'dark'),
|
||||||
|
foregroundSubtle: new Color('#908caa', 'dark').lift(0.15),
|
||||||
|
foregroundSubtler: new Color('#6e6a86', 'dark').lift(0.15),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rosePineDawn: YaakTheme = {
|
||||||
|
id: 'rose-pine-dawn',
|
||||||
|
name: 'Rosé Pine Dawn',
|
||||||
|
background: new Color('#faf4ed', 'light'),
|
||||||
|
backgroundHighlight: new Color('#dfdad9', 'light'),
|
||||||
|
backgroundHighlightSecondary: new Color('#f4ede8', 'light'),
|
||||||
|
foreground: new Color('#575279', 'light'),
|
||||||
|
foregroundSubtle: new Color('#797593', 'light'),
|
||||||
|
foregroundSubtler: new Color('#9893a5', 'light'),
|
||||||
|
colors: {
|
||||||
|
primary: new Color('#9070ad', 'light'),
|
||||||
|
secondary: new Color('#6e6a86', 'light'),
|
||||||
|
info: new Color('#2d728d', 'light'),
|
||||||
|
success: new Color('#4f8c96', 'light'),
|
||||||
|
notice: new Color('#cb862d', 'light'),
|
||||||
|
warning: new Color('#ce7b78', 'light'),
|
||||||
|
danger: new Color('#b4637a', 'light'),
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
responsePane: {
|
||||||
|
backgroundHighlight: new Color('#e8e4e2', 'light'),
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
backgroundHighlight: new Color('#e8e4e2', 'light'),
|
||||||
|
},
|
||||||
|
appHeader: {
|
||||||
|
backgroundHighlight: new Color('#e8e4e2', 'light'),
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
backgroundHighlight: new Color('#dfdad9', 'light'),
|
||||||
|
},
|
||||||
|
dialog: {
|
||||||
|
backgroundHighlight: new Color('#e8e4e2', 'light'),
|
||||||
|
},
|
||||||
|
menu: {
|
||||||
|
background: new Color('#f2e9e1', 'light'),
|
||||||
|
backgroundHighlight: new Color('#dfdad9', 'light'),
|
||||||
|
backgroundHighlightSecondary: new Color('#6e6a86', 'light'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rosePine: YaakTheme = {
|
||||||
|
id: 'rose-pine',
|
||||||
|
name: 'Rosé Pine',
|
||||||
|
background: new Color('#191724', 'dark'),
|
||||||
|
foreground: new Color('#e0def4', 'dark'),
|
||||||
|
foregroundSubtle: new Color('#908caa', 'dark'),
|
||||||
|
foregroundSubtler: new Color('#6e6a86', 'dark'),
|
||||||
|
colors: {
|
||||||
|
primary: new Color('#c4a7e7', 'dark'),
|
||||||
|
secondary: new Color('#6e6a86', 'dark'),
|
||||||
|
info: new Color('#67abcb', 'dark'),
|
||||||
|
success: new Color('#9cd8d8', 'dark'),
|
||||||
|
notice: new Color('#f6c177', 'dark'),
|
||||||
|
warning: new Color('#f1a3a1', 'dark'),
|
||||||
|
danger: new Color('#eb6f92', 'dark'),
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
responsePane: {
|
||||||
|
background: new Color('#1f1d2e', 'dark'),
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
background: new Color('#1f1d2e', 'dark'),
|
||||||
|
},
|
||||||
|
menu: {
|
||||||
|
background: new Color('#393552', 'dark'),
|
||||||
|
foregroundSubtle: new Color('#908caa', 'dark').lift(0.15),
|
||||||
|
foregroundSubtler: new Color('#6e6a86', 'dark').lift(0.15),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const githubDark: YaakTheme = {
|
||||||
|
id: 'github-dark',
|
||||||
|
name: 'GitHub',
|
||||||
|
background: new Color('#0d1218', 'dark'),
|
||||||
|
backgroundHighlight: new Color('#171c23', 'dark'),
|
||||||
|
backgroundHighlightSecondary: new Color('#1c2127', 'dark'),
|
||||||
|
foreground: new Color('#dce3eb', 'dark'),
|
||||||
|
foregroundSubtle: new Color('#88919b', 'dark'),
|
||||||
|
foregroundSubtler: new Color('#6b727d', 'dark'),
|
||||||
|
colors: {
|
||||||
|
primary: new Color('#a579ef', 'dark').lift(0.1),
|
||||||
|
secondary: new Color('#6b727d', 'dark').lift(0.1),
|
||||||
|
info: new Color('#458def', 'dark').lift(0.1),
|
||||||
|
success: new Color('#3eb24f', 'dark').lift(0.1),
|
||||||
|
notice: new Color('#dca132', 'dark').lift(0.1),
|
||||||
|
warning: new Color('#ec7934', 'dark').lift(0.1),
|
||||||
|
danger: new Color('#ee5049', 'dark').lift(0.1),
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
button: {
|
||||||
|
colors: {
|
||||||
|
primary: new Color('#a579ef', 'dark'),
|
||||||
|
secondary: new Color('#6b727d', 'dark'),
|
||||||
|
info: new Color('#458def', 'dark'),
|
||||||
|
success: new Color('#3eb24f', 'dark'),
|
||||||
|
notice: new Color('#dca132', 'dark'),
|
||||||
|
warning: new Color('#ec7934', 'dark'),
|
||||||
|
danger: new Color('#ee5049', 'dark'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const githubLight: YaakTheme = {
|
||||||
|
id: 'github-light',
|
||||||
|
name: 'GitHub',
|
||||||
|
background: new Color('#ffffff', 'light'),
|
||||||
|
backgroundHighlight: new Color('#e8ebee', 'light'),
|
||||||
|
backgroundHighlightSecondary: new Color('#f6f8fa', 'light'),
|
||||||
|
foreground: new Color('#1f2328', 'light'),
|
||||||
|
foregroundSubtle: new Color('#636c76', 'light'),
|
||||||
|
foregroundSubtler: new Color('#828d94', 'light'),
|
||||||
|
colors: {
|
||||||
|
primary: new Color('#8250df', 'light'),
|
||||||
|
secondary: new Color('#6e7781', 'light'),
|
||||||
|
info: new Color('#0969da', 'light'),
|
||||||
|
success: new Color('#1a7f37', 'light'),
|
||||||
|
notice: new Color('#9a6700', 'light'),
|
||||||
|
warning: new Color('#bc4c00', 'light'),
|
||||||
|
danger: new Color('#d1242f', 'light'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const yaakThemes = [
|
||||||
|
yaakLight,
|
||||||
|
yaakDark,
|
||||||
|
catppuccinMocha,
|
||||||
|
catppuccinLatte,
|
||||||
|
relaxing,
|
||||||
|
monokaiProOctagon,
|
||||||
|
rosePine,
|
||||||
|
rosePineMoon,
|
||||||
|
rosePineDawn,
|
||||||
|
githubLight,
|
||||||
|
githubDark,
|
||||||
|
];
|
||||||
+35
-115
@@ -1,10 +1,9 @@
|
|||||||
|
import { getCurrent } from '@tauri-apps/api/webviewWindow';
|
||||||
import { indent } from '../indent';
|
import { indent } from '../indent';
|
||||||
import { Color } from './color';
|
import { Color } from './color';
|
||||||
|
|
||||||
export type Appearance = 'dark' | 'light' | 'system';
|
export type Appearance = 'dark' | 'light' | 'system';
|
||||||
|
|
||||||
const DEFAULT_APPEARANCE: Appearance = 'system';
|
|
||||||
|
|
||||||
interface ThemeComponent {
|
interface ThemeComponent {
|
||||||
background?: Color;
|
background?: Color;
|
||||||
backgroundHighlight?: Color;
|
backgroundHighlight?: Color;
|
||||||
@@ -13,19 +12,26 @@ interface ThemeComponent {
|
|||||||
foreground?: Color;
|
foreground?: Color;
|
||||||
foregroundSubtle?: Color;
|
foregroundSubtle?: Color;
|
||||||
foregroundSubtler?: Color;
|
foregroundSubtler?: Color;
|
||||||
|
shadow?: Color;
|
||||||
colors?: Partial<RootColors>;
|
colors?: Partial<RootColors>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface YaakTheme extends ThemeComponent {
|
export interface YaakTheme extends ThemeComponent {
|
||||||
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
components?: {
|
components?: {
|
||||||
dialog?: ThemeComponent;
|
dialog?: ThemeComponent;
|
||||||
|
menu?: ThemeComponent;
|
||||||
|
toast?: ThemeComponent;
|
||||||
sidebar?: ThemeComponent;
|
sidebar?: ThemeComponent;
|
||||||
responsePane?: ThemeComponent;
|
responsePane?: ThemeComponent;
|
||||||
appHeader?: ThemeComponent;
|
appHeader?: ThemeComponent;
|
||||||
button?: ThemeComponent;
|
button?: ThemeComponent;
|
||||||
banner?: ThemeComponent;
|
banner?: ThemeComponent;
|
||||||
placeholder?: ThemeComponent;
|
placeholder?: ThemeComponent;
|
||||||
|
urlBar?: ThemeComponent;
|
||||||
|
editor?: ThemeComponent;
|
||||||
|
input?: ThemeComponent;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,94 +48,6 @@ interface RootColors {
|
|||||||
type ColorName = keyof RootColors;
|
type ColorName = keyof RootColors;
|
||||||
type ComponentName = keyof NonNullable<YaakTheme['components']>;
|
type ComponentName = keyof NonNullable<YaakTheme['components']>;
|
||||||
|
|
||||||
const yaakThemes: Record<string, YaakTheme> = {
|
|
||||||
yaakLight: {
|
|
||||||
name: 'Yaak (Light)',
|
|
||||||
background: new Color('#f2f4f7', 'light').lower(1),
|
|
||||||
foreground: new Color('hsl(219,23%,15%)', 'light'),
|
|
||||||
colors: {
|
|
||||||
primary: new Color('hsl(266,100%,70%)', 'light'),
|
|
||||||
secondary: new Color('hsl(220,24%,59%)', 'light'),
|
|
||||||
info: new Color('hsl(206,100%,48%)', 'light'),
|
|
||||||
success: new Color('hsl(155,95%,33%)', 'light'),
|
|
||||||
notice: new Color('hsl(45,100%,41%)', 'light'),
|
|
||||||
warning: new Color('hsl(30,100%,43%)', 'light'),
|
|
||||||
danger: new Color('hsl(335,75%,57%)', 'light'),
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
sidebar: {
|
|
||||||
background: new Color('#f2f4f7', 'light'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as YaakTheme,
|
|
||||||
|
|
||||||
yaakDark: {
|
|
||||||
name: 'Yaak Dark',
|
|
||||||
background: new Color('hsl(244,23%,12%)', 'dark'),
|
|
||||||
foreground: new Color('#bcbad4', 'dark'),
|
|
||||||
|
|
||||||
colors: {
|
|
||||||
primary: new Color('hsl(266,100%,79%)', 'dark'),
|
|
||||||
secondary: new Color('hsl(245,23%,60%)', 'dark'),
|
|
||||||
info: new Color('hsl(206,100%,63%)', 'dark'),
|
|
||||||
success: new Color('hsl(150,100%,37%)', 'dark'),
|
|
||||||
notice: new Color('hsl(48,80%,63%)', 'dark'),
|
|
||||||
warning: new Color('hsl(28,100%,61%)', 'dark'),
|
|
||||||
danger: new Color('hsl(342,90%,68%)', 'dark'),
|
|
||||||
},
|
|
||||||
|
|
||||||
components: {
|
|
||||||
sidebar: {
|
|
||||||
background: new Color('hsl(243,23%,15%)', 'dark'),
|
|
||||||
},
|
|
||||||
responsePane: {
|
|
||||||
background: new Color('hsl(243,23%,15%)', 'dark'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
catppuccin: {
|
|
||||||
name: 'Catppuccin',
|
|
||||||
background: new Color('#181825', 'dark'),
|
|
||||||
foreground: new Color('#cdd6f4', 'dark'),
|
|
||||||
foregroundSubtle: new Color('#cdd6f4', 'dark').lower(0.1).translucify(0.3),
|
|
||||||
foregroundSubtler: new Color('#cdd6f4', 'dark').lower(0.1).translucify(0.55),
|
|
||||||
colors: {
|
|
||||||
primary: new Color('#cba6f7', 'dark'),
|
|
||||||
secondary: new Color('#bac2de', 'dark'),
|
|
||||||
info: new Color('#89b4fa', 'dark'),
|
|
||||||
success: new Color('#a6e3a1', 'dark'),
|
|
||||||
notice: new Color('#f9e2af', 'dark'),
|
|
||||||
warning: new Color('#fab387', 'dark'),
|
|
||||||
danger: new Color('#f38ba8', 'dark'),
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
dialog: {
|
|
||||||
background: new Color('#181825', 'dark'),
|
|
||||||
},
|
|
||||||
sidebar: {
|
|
||||||
background: new Color('#1e1e2e', 'dark'),
|
|
||||||
},
|
|
||||||
appHeader: {
|
|
||||||
background: new Color('#11111b', 'dark'),
|
|
||||||
},
|
|
||||||
responsePane: {
|
|
||||||
background: new Color('#1e1e2e', 'dark'),
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
colors: {
|
|
||||||
primary: new Color('#cba6f7', 'dark').lower(0.2),
|
|
||||||
secondary: new Color('#bac2de', 'dark').lower(0.2),
|
|
||||||
info: new Color('#89b4fa', 'dark').lower(0.2),
|
|
||||||
success: new Color('#a6e3a1', 'dark').lower(0.2),
|
|
||||||
notice: new Color('#f9e2af', 'dark').lower(0.2),
|
|
||||||
warning: new Color('#fab387', 'dark').lower(0.2),
|
|
||||||
danger: new Color('#f38ba8', 'dark').lower(0.2),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type CSSVariables = Record<string, string | undefined>;
|
type CSSVariables = Record<string, string | undefined>;
|
||||||
|
|
||||||
function themeVariables(theme?: ThemeComponent, base?: CSSVariables): CSSVariables | null {
|
function themeVariables(theme?: ThemeComponent, base?: CSSVariables): CSSVariables | null {
|
||||||
@@ -147,6 +65,7 @@ function themeVariables(theme?: ThemeComponent, base?: CSSVariables): CSSVariabl
|
|||||||
'--fg-subtle': theme?.foregroundSubtle?.css() ?? theme?.foreground?.lower(0.2).css(),
|
'--fg-subtle': theme?.foregroundSubtle?.css() ?? theme?.foreground?.lower(0.2).css(),
|
||||||
'--fg-subtler': theme?.foregroundSubtler?.css() ?? theme?.foreground?.lower(0.3).css(),
|
'--fg-subtler': theme?.foregroundSubtler?.css() ?? theme?.foreground?.lower(0.3).css(),
|
||||||
'--border-focus': theme?.colors?.info?.css(),
|
'--border-focus': theme?.colors?.info?.css(),
|
||||||
|
'--shadow': theme?.shadow?.css() ?? Color.black().translucify(0.7).css(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [color, value] of Object.entries(theme?.colors ?? {})) {
|
for (const [color, value] of Object.entries(theme?.colors ?? {})) {
|
||||||
@@ -261,7 +180,7 @@ function placeholderCSS(color: ColorName, colors?: Partial<RootColors>): string
|
|||||||
].join('\n\n');
|
].join('\n\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function isThemeDark(theme: YaakTheme): boolean {
|
export function isThemeDark(theme: YaakTheme): boolean {
|
||||||
if (theme.background && theme.foreground) {
|
if (theme.background && theme.foreground) {
|
||||||
return theme.foreground.lighterThan(theme.background);
|
return theme.foreground.lighterThan(theme.background);
|
||||||
}
|
}
|
||||||
@@ -269,10 +188,11 @@ function isThemeDark(theme: YaakTheme): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setThemeOnDocument(yaakThemes.yaakLight!);
|
|
||||||
setThemeOnDocument(yaakThemes.yaakDark!);
|
|
||||||
|
|
||||||
export function getThemeCSS(theme: YaakTheme): string {
|
export function getThemeCSS(theme: YaakTheme): string {
|
||||||
|
theme.components = theme.components ?? {};
|
||||||
|
// Toast defaults to menu styles
|
||||||
|
theme.components.toast = theme.components.toast ?? theme.components.menu;
|
||||||
|
|
||||||
let themeCSS = '';
|
let themeCSS = '';
|
||||||
try {
|
try {
|
||||||
const baseCss = variablesToCSS(null, themeVariables(theme));
|
const baseCss = variablesToCSS(null, themeVariables(theme));
|
||||||
@@ -298,29 +218,24 @@ export function getThemeCSS(theme: YaakTheme): string {
|
|||||||
return themeCSS;
|
return themeCSS;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setAppearanceOnDocument(appearance: Appearance = DEFAULT_APPEARANCE) {
|
export function addThemeStylesToDocument(theme: YaakTheme) {
|
||||||
const resolvedAppearance = appearance === 'system' ? getPreferredAppearance() : appearance;
|
let styleEl = document.head.querySelector(`style[data-theme]`);
|
||||||
document.documentElement.setAttribute('data-resolved-appearance', resolvedAppearance);
|
if (!styleEl) {
|
||||||
}
|
styleEl = document.createElement('style');
|
||||||
|
|
||||||
export function setThemeOnDocument(theme: YaakTheme) {
|
|
||||||
document.documentElement.setAttribute('data-theme', theme.name);
|
|
||||||
|
|
||||||
const darkOrLight = isThemeDark(theme) ? 'dark' : 'light';
|
|
||||||
let existingStyleEl = document.head.querySelector(`style[data-theme-definition=${darkOrLight}]`);
|
|
||||||
if (!existingStyleEl) {
|
|
||||||
const styleEl = document.createElement('style');
|
|
||||||
document.head.appendChild(styleEl);
|
document.head.appendChild(styleEl);
|
||||||
existingStyleEl = styleEl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
existingStyleEl.textContent = [
|
styleEl.setAttribute('data-theme', theme.id);
|
||||||
|
styleEl.textContent = [
|
||||||
`/* ${theme.name} */`,
|
`/* ${theme.name} */`,
|
||||||
`[data-resolved-appearance="${isThemeDark(theme) ? 'dark' : 'light'}"] {`,
|
`[data-theme="${theme.id}"] {`,
|
||||||
getThemeCSS(theme),
|
getThemeCSS(theme),
|
||||||
'}',
|
'}',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
existingStyleEl.setAttribute('data-theme-definition', darkOrLight);
|
}
|
||||||
|
|
||||||
|
export function setThemeOnDocument(theme: YaakTheme) {
|
||||||
|
document.documentElement.setAttribute('data-theme', theme.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPreferredAppearance(): Appearance {
|
export function getPreferredAppearance(): Appearance {
|
||||||
@@ -330,8 +245,13 @@ export function getPreferredAppearance(): Appearance {
|
|||||||
export function subscribeToPreferredAppearanceChange(
|
export function subscribeToPreferredAppearanceChange(
|
||||||
cb: (appearance: Appearance) => void,
|
cb: (appearance: Appearance) => void,
|
||||||
): () => void {
|
): () => void {
|
||||||
const listener = (e: MediaQueryListEvent) => cb(e.matches ? 'dark' : 'light');
|
const container = { unsubscribe: () => {} };
|
||||||
const m = window.matchMedia('(prefers-color-scheme: dark)');
|
|
||||||
m.addEventListener('change', listener);
|
getCurrent()
|
||||||
return () => m.removeEventListener('change', listener);
|
.onThemeChanged((t) => cb(t.payload))
|
||||||
|
.then((l) => {
|
||||||
|
container.unsubscribe = l;
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => container.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-8
@@ -2,11 +2,9 @@ import { getCurrent } from '@tauri-apps/api/webviewWindow';
|
|||||||
import { type } from '@tauri-apps/plugin-os';
|
import { type } from '@tauri-apps/plugin-os';
|
||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import { attachConsole } from 'tauri-plugin-log-api';
|
||||||
import { App } from './components/App';
|
import { App } from './components/App';
|
||||||
import './main.css';
|
import './main.css';
|
||||||
import { getSettings } from './lib/store';
|
|
||||||
import type { Appearance } from './lib/theme/window';
|
|
||||||
import { setAppearanceOnDocument } from './lib/theme/window';
|
|
||||||
|
|
||||||
// Hide decorations here because it doesn't work in Rust for some reason (bug?)
|
// Hide decorations here because it doesn't work in Rust for some reason (bug?)
|
||||||
const osType = await type();
|
const osType = await type();
|
||||||
@@ -14,11 +12,7 @@ if (osType !== 'macos') {
|
|||||||
await getCurrent().setDecorations(false);
|
await getCurrent().setDecorations(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// await attachConsole();
|
await attachConsole();
|
||||||
// await maybeRestorePathname();
|
|
||||||
|
|
||||||
const settings = await getSettings();
|
|
||||||
setAppearanceOnDocument(settings.appearance as Appearance);
|
|
||||||
|
|
||||||
window.addEventListener('keydown', (e) => {
|
window.addEventListener('keydown', (e) => {
|
||||||
// Hack to not go back in history on backspace. Check for document body
|
// Hack to not go back in history on backspace. Check for document body
|
||||||
|
|||||||
+5
-1
@@ -59,6 +59,10 @@ module.exports = {
|
|||||||
'4xl': '2.5rem',
|
'4xl': '2.5rem',
|
||||||
'5xl': '3rem',
|
'5xl': '3rem',
|
||||||
},
|
},
|
||||||
|
boxShadow: {
|
||||||
|
DEFAULT: '0 1px 3px 0 var(--shadow);',
|
||||||
|
lg: '0 10px 15px -3px var(--shadow)',
|
||||||
|
},
|
||||||
colors: {
|
colors: {
|
||||||
'transparent': 'transparent',
|
'transparent': 'transparent',
|
||||||
'placeholder': 'var(--fg-subtler)',
|
'placeholder': 'var(--fg-subtler)',
|
||||||
@@ -88,7 +92,7 @@ module.exports = {
|
|||||||
require('@tailwindcss/container-queries'),
|
require('@tailwindcss/container-queries'),
|
||||||
plugin(function ({addVariant}) {
|
plugin(function ({addVariant}) {
|
||||||
addVariant('hocus', ['&:hover', '&:focus-visible', '&.focus:focus']);
|
addVariant('hocus', ['&:hover', '&:focus-visible', '&.focus:focus']);
|
||||||
addVariant('focus-visible-or-class', ['&:focus-visible']);
|
addVariant('focus-visible-or-class', ['&:focus-visible', '&.focus:focus']);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user