mirror of
https://github.com/0PandaDEV/Qopy.git
synced 2025-04-22 05:34:04 +02:00
refactor: Clean up code formatting in utils module
This commit is contained in:
parent
60b670c7a7
commit
22fcd84b8d
14 changed files with 485 additions and 394 deletions
|
@ -1,14 +1,14 @@
|
|||
use tauri_plugin_aptabase::EventTracker;
|
||||
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||
use base64::{ engine::general_purpose::STANDARD, Engine };
|
||||
// use hyperpolyglot;
|
||||
use lazy_static::lazy_static;
|
||||
use rdev::{simulate, EventType, Key};
|
||||
use rdev::{ simulate, EventType, Key };
|
||||
use regex::Regex;
|
||||
use sqlx::SqlitePool;
|
||||
use std::fs;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{thread, time::Duration};
|
||||
use tauri::{AppHandle, Emitter, Listener, Manager};
|
||||
use std::sync::atomic::{ AtomicBool, Ordering };
|
||||
use std::{ thread, time::Duration };
|
||||
use tauri::{ AppHandle, Emitter, Listener, Manager };
|
||||
use tauri_plugin_clipboard::Clipboard;
|
||||
use tokio::runtime::Runtime as TokioRuntime;
|
||||
use url::Url;
|
||||
|
@ -17,7 +17,7 @@ use uuid::Uuid;
|
|||
use crate::db;
|
||||
use crate::utils::commands::get_app_info;
|
||||
use crate::utils::favicon::fetch_favicon_as_base64;
|
||||
use crate::utils::types::{ContentType, HistoryItem};
|
||||
use crate::utils::types::{ ContentType, HistoryItem };
|
||||
|
||||
lazy_static! {
|
||||
static ref IS_PROGRAMMATIC_PASTE: AtomicBool = AtomicBool::new(false);
|
||||
|
@ -27,16 +27,14 @@ lazy_static! {
|
|||
pub async fn write_and_paste(
|
||||
app_handle: AppHandle,
|
||||
content: String,
|
||||
content_type: String,
|
||||
content_type: String
|
||||
) -> Result<(), String> {
|
||||
let clipboard = app_handle.state::<Clipboard>();
|
||||
|
||||
match content_type.as_str() {
|
||||
"text" => clipboard.write_text(content).map_err(|e| e.to_string())?,
|
||||
"image" => {
|
||||
clipboard
|
||||
.write_image_base64(content)
|
||||
.map_err(|e| e.to_string())?;
|
||||
clipboard.write_image_base64(content).map_err(|e| e.to_string())?;
|
||||
}
|
||||
"files" => {
|
||||
clipboard
|
||||
|
@ -44,11 +42,13 @@ pub async fn write_and_paste(
|
|||
content
|
||||
.split(", ")
|
||||
.map(|file| file.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
.collect::<Vec<String>>()
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
_ => return Err("Unsupported content type".to_string()),
|
||||
_ => {
|
||||
return Err("Unsupported content type".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
IS_PROGRAMMATIC_PASTE.store(true, Ordering::SeqCst);
|
||||
|
@ -65,7 +65,7 @@ pub async fn write_and_paste(
|
|||
EventType::KeyPress(modifier_key),
|
||||
EventType::KeyPress(Key::KeyV),
|
||||
EventType::KeyRelease(Key::KeyV),
|
||||
EventType::KeyRelease(modifier_key),
|
||||
EventType::KeyRelease(modifier_key)
|
||||
];
|
||||
|
||||
for event in events {
|
||||
|
@ -81,9 +81,12 @@ pub async fn write_and_paste(
|
|||
IS_PROGRAMMATIC_PASTE.store(false, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
let _ = app_handle.track_event("clipboard_paste", Some(serde_json::json!({
|
||||
let _ = app_handle.track_event(
|
||||
"clipboard_paste",
|
||||
Some(serde_json::json!({
|
||||
"content_type": content_type
|
||||
})));
|
||||
}))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -92,79 +95,92 @@ pub fn setup(app: &AppHandle) {
|
|||
let app_handle = app.clone();
|
||||
let runtime = TokioRuntime::new().expect("Failed to create Tokio runtime");
|
||||
|
||||
app_handle.clone().listen(
|
||||
"plugin:clipboard://clipboard-monitor/update",
|
||||
move |_event| {
|
||||
let app_handle = app_handle.clone();
|
||||
runtime.block_on(async move {
|
||||
if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
app_handle.clone().listen("plugin:clipboard://clipboard-monitor/update", move |_event| {
|
||||
let app_handle = app_handle.clone();
|
||||
runtime.block_on(async move {
|
||||
if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
|
||||
let clipboard = app_handle.state::<Clipboard>();
|
||||
let available_types = clipboard.available_types().unwrap();
|
||||
let clipboard = app_handle.state::<Clipboard>();
|
||||
let available_types = clipboard.available_types().unwrap();
|
||||
|
||||
let (app_name, app_icon) = get_app_info();
|
||||
let (app_name, app_icon) = get_app_info();
|
||||
|
||||
match get_pool(&app_handle).await {
|
||||
Ok(pool) => {
|
||||
if available_types.image {
|
||||
println!("Handling image change");
|
||||
if let Ok(image_data) = clipboard.read_image_base64() {
|
||||
let file_path = save_image_to_file(&app_handle, &image_data)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.unwrap_or_else(|e| e);
|
||||
match get_pool(&app_handle).await {
|
||||
Ok(pool) => {
|
||||
if available_types.image {
|
||||
println!("Handling image change");
|
||||
if let Ok(image_data) = clipboard.read_image_base64() {
|
||||
let file_path = save_image_to_file(&app_handle, &image_data).await
|
||||
.map_err(|e| e.to_string())
|
||||
.unwrap_or_else(|e| e);
|
||||
let _ = db::history::add_history_item(
|
||||
app_handle.clone(),
|
||||
pool,
|
||||
HistoryItem::new(
|
||||
app_name,
|
||||
ContentType::Image,
|
||||
file_path,
|
||||
None,
|
||||
app_icon,
|
||||
None
|
||||
)
|
||||
).await;
|
||||
}
|
||||
} else if available_types.files {
|
||||
println!("Handling files change");
|
||||
if let Ok(files) = clipboard.read_files() {
|
||||
for file in files {
|
||||
let _ = db::history::add_history_item(
|
||||
app_handle.clone(),
|
||||
pool,
|
||||
HistoryItem::new(app_name, ContentType::Image, file_path, None, app_icon, None)
|
||||
pool.clone(),
|
||||
HistoryItem::new(
|
||||
app_name.clone(),
|
||||
ContentType::File,
|
||||
file,
|
||||
None,
|
||||
app_icon.clone(),
|
||||
None
|
||||
)
|
||||
).await;
|
||||
}
|
||||
} else if available_types.files {
|
||||
println!("Handling files change");
|
||||
if let Ok(files) = clipboard.read_files() {
|
||||
for file in files {
|
||||
}
|
||||
} else if available_types.text {
|
||||
println!("Handling text change");
|
||||
if let Ok(text) = clipboard.read_text() {
|
||||
let text = text.to_string();
|
||||
let url_regex = Regex::new(
|
||||
r"^https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)$"
|
||||
).unwrap();
|
||||
|
||||
if url_regex.is_match(&text) {
|
||||
if let Ok(url) = Url::parse(&text) {
|
||||
let favicon = match fetch_favicon_as_base64(url).await {
|
||||
Ok(Some(f)) => Some(f),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let _ = db::history::add_history_item(
|
||||
app_handle.clone(),
|
||||
pool.clone(),
|
||||
pool,
|
||||
HistoryItem::new(
|
||||
app_name.clone(),
|
||||
ContentType::File,
|
||||
file,
|
||||
None,
|
||||
app_icon.clone(),
|
||||
app_name,
|
||||
ContentType::Link,
|
||||
text,
|
||||
favicon,
|
||||
app_icon,
|
||||
None
|
||||
),
|
||||
)
|
||||
).await;
|
||||
}
|
||||
}
|
||||
} else if available_types.text {
|
||||
println!("Handling text change");
|
||||
if let Ok(text) = clipboard.read_text() {
|
||||
let text = text.to_string();
|
||||
let url_regex = Regex::new(r"^https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)$").unwrap();
|
||||
} else {
|
||||
if text.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if url_regex.is_match(&text) {
|
||||
if let Ok(url) = Url::parse(&text) {
|
||||
let favicon = match fetch_favicon_as_base64(url).await {
|
||||
Ok(Some(f)) => Some(f),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let _ = db::history::add_history_item(
|
||||
app_handle.clone(),
|
||||
pool,
|
||||
HistoryItem::new(app_name, ContentType::Link, text, favicon, app_icon, None)
|
||||
).await;
|
||||
}
|
||||
} else {
|
||||
if text.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Temporarily disabled code detection
|
||||
/*if let Some(detection) = hyperpolyglot::detect_from_text(&text) {
|
||||
// Temporarily disabled code detection
|
||||
/*if let Some(detection) = hyperpolyglot::detect_from_text(&text) {
|
||||
let language = match detection {
|
||||
hyperpolyglot::Detection::Heuristics(lang) => lang.to_string(),
|
||||
_ => detection.language().to_string(),
|
||||
|
@ -175,43 +191,61 @@ pub fn setup(app: &AppHandle) {
|
|||
HistoryItem::new(app_name, ContentType::Code, text, None, app_icon, Some(language))
|
||||
).await;
|
||||
} else*/ if crate::utils::commands::detect_color(&text) {
|
||||
let _ = db::history::add_history_item(
|
||||
app_handle.clone(),
|
||||
pool,
|
||||
HistoryItem::new(app_name, ContentType::Color, text, None, app_icon, None)
|
||||
).await;
|
||||
} else {
|
||||
let _ = db::history::add_history_item(
|
||||
app_handle.clone(),
|
||||
pool,
|
||||
HistoryItem::new(app_name, ContentType::Text, text.clone(), None, app_icon, None)
|
||||
).await;
|
||||
}
|
||||
let _ = db::history::add_history_item(
|
||||
app_handle.clone(),
|
||||
pool,
|
||||
HistoryItem::new(
|
||||
app_name,
|
||||
ContentType::Color,
|
||||
text,
|
||||
None,
|
||||
app_icon,
|
||||
None
|
||||
)
|
||||
).await;
|
||||
} else {
|
||||
let _ = db::history::add_history_item(
|
||||
app_handle.clone(),
|
||||
pool,
|
||||
HistoryItem::new(
|
||||
app_name,
|
||||
ContentType::Text,
|
||||
text.clone(),
|
||||
None,
|
||||
app_icon,
|
||||
None
|
||||
)
|
||||
).await;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Unknown clipboard content type");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to get database pool: {}", e);
|
||||
} else {
|
||||
println!("Unknown clipboard content type");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to get database pool: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = app_handle.emit("clipboard-content-updated", ());
|
||||
let _ = app_handle.track_event("clipboard_copied", Some(serde_json::json!({
|
||||
let _ = app_handle.emit("clipboard-content-updated", ());
|
||||
let _ = app_handle.track_event(
|
||||
"clipboard_copied",
|
||||
Some(
|
||||
serde_json::json!({
|
||||
"content_type": if available_types.image { "image" }
|
||||
else if available_types.files { "files" }
|
||||
else if available_types.text { "text" }
|
||||
else { "unknown" }
|
||||
})));
|
||||
});
|
||||
},
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async fn get_pool(
|
||||
app_handle: &AppHandle,
|
||||
app_handle: &AppHandle
|
||||
) -> Result<tauri::State<'_, SqlitePool>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
Ok(app_handle.state::<SqlitePool>())
|
||||
}
|
||||
|
@ -219,9 +253,7 @@ async fn get_pool(
|
|||
#[tauri::command]
|
||||
pub fn start_monitor(app_handle: AppHandle) -> Result<(), String> {
|
||||
let clipboard = app_handle.state::<Clipboard>();
|
||||
clipboard
|
||||
.start_monitor(app_handle.clone())
|
||||
.map_err(|e| e.to_string())?;
|
||||
clipboard.start_monitor(app_handle.clone()).map_err(|e| e.to_string())?;
|
||||
app_handle
|
||||
.emit("plugin:clipboard://clipboard-monitor/status", true)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
@ -230,7 +262,7 @@ pub fn start_monitor(app_handle: AppHandle) -> Result<(), String> {
|
|||
|
||||
async fn save_image_to_file(
|
||||
app_handle: &AppHandle,
|
||||
base64_data: &str,
|
||||
base64_data: &str
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let app_data_dir = app_handle.path().app_data_dir().unwrap();
|
||||
let images_dir = app_data_dir.join("images");
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
use crate::utils::commands::center_window_on_current_monitor;
|
||||
use crate::utils::keys::KeyCode;
|
||||
use global_hotkey::{
|
||||
hotkey::{Code, HotKey, Modifiers},
|
||||
GlobalHotKeyEvent, GlobalHotKeyManager, HotKeyState,
|
||||
hotkey::{ Code, HotKey, Modifiers },
|
||||
GlobalHotKeyEvent,
|
||||
GlobalHotKeyManager,
|
||||
HotKeyState,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Mutex;
|
||||
use tauri::{AppHandle, Listener, Manager};
|
||||
use tauri::{ AppHandle, Listener, Manager };
|
||||
use tauri_plugin_aptabase::EventTracker;
|
||||
|
||||
lazy_static! {
|
||||
|
@ -110,10 +112,18 @@ fn parse_hotkey(shortcut: &[String]) -> Result<HotKey, Box<dyn std::error::Error
|
|||
|
||||
for part in shortcut {
|
||||
match part.as_str() {
|
||||
"ControlLeft" => modifiers |= Modifiers::CONTROL,
|
||||
"AltLeft" => modifiers |= Modifiers::ALT,
|
||||
"ShiftLeft" => modifiers |= Modifiers::SHIFT,
|
||||
"MetaLeft" => modifiers |= Modifiers::META,
|
||||
"ControlLeft" => {
|
||||
modifiers |= Modifiers::CONTROL;
|
||||
}
|
||||
"AltLeft" => {
|
||||
modifiers |= Modifiers::ALT;
|
||||
}
|
||||
"ShiftLeft" => {
|
||||
modifiers |= Modifiers::SHIFT;
|
||||
}
|
||||
"MetaLeft" => {
|
||||
modifiers |= Modifiers::META;
|
||||
}
|
||||
key => {
|
||||
code = Some(Code::from(KeyCode::from_str(key)?));
|
||||
}
|
||||
|
@ -144,8 +154,10 @@ fn handle_hotkey_event(app_handle: &AppHandle) {
|
|||
|
||||
let _ = app_handle.track_event(
|
||||
"hotkey_triggered",
|
||||
Some(serde_json::json!({
|
||||
Some(
|
||||
serde_json::json!({
|
||||
"action": if window.is_visible().unwrap() { "hide" } else { "show" }
|
||||
})),
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use tauri::{
|
||||
menu::{MenuBuilder, MenuItemBuilder},
|
||||
tray::TrayIconBuilder,
|
||||
Emitter, Manager,
|
||||
};
|
||||
use tauri::{ menu::{ MenuBuilder, MenuItemBuilder }, tray::TrayIconBuilder, Emitter, Manager };
|
||||
use tauri_plugin_aptabase::EventTracker;
|
||||
|
||||
pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let window = app.get_webview_window("main").unwrap();
|
||||
let is_visible = window.is_visible().unwrap();
|
||||
let _ = app.track_event("tray_toggle", Some(serde_json::json!({
|
||||
let _ = app.track_event(
|
||||
"tray_toggle",
|
||||
Some(serde_json::json!({
|
||||
"action": if is_visible { "hide" } else { "show" }
|
||||
})));
|
||||
}))
|
||||
);
|
||||
|
||||
let icon_bytes = include_bytes!("../../icons/Square71x71Logo.png");
|
||||
let icon = tauri::image::Image::from_bytes(icon_bytes).unwrap();
|
||||
|
@ -18,37 +17,42 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
|||
let _tray = TrayIconBuilder::new()
|
||||
.menu(
|
||||
&MenuBuilder::new(app)
|
||||
.items(&[&MenuItemBuilder::with_id("app_name", "Qopy")
|
||||
.enabled(false)
|
||||
.build(app)?])
|
||||
.items(&[&MenuItemBuilder::with_id("app_name", "Qopy").enabled(false).build(app)?])
|
||||
.items(&[&MenuItemBuilder::with_id("show", "Show/Hide").build(app)?])
|
||||
.items(&[&MenuItemBuilder::with_id("settings", "Settings").build(app)?])
|
||||
.items(&[&MenuItemBuilder::with_id("quit", "Quit").build(app)?])
|
||||
.build()?,
|
||||
.build()?
|
||||
)
|
||||
.on_menu_event(move |_app, event| match event.id().as_ref() {
|
||||
"quit" => {
|
||||
let _ = _app.track_event("app_quit", None);
|
||||
std::process::exit(0);
|
||||
}
|
||||
"show" => {
|
||||
let _ = _app.track_event("tray_toggle", Some(serde_json::json!({
|
||||
"action": if is_visible { "hide" } else { "show" }
|
||||
})));
|
||||
let is_visible = window.is_visible().unwrap();
|
||||
if is_visible {
|
||||
window.hide().unwrap();
|
||||
} else {
|
||||
window.show().unwrap();
|
||||
window.set_focus().unwrap();
|
||||
.on_menu_event(move |_app, event| {
|
||||
match event.id().as_ref() {
|
||||
"quit" => {
|
||||
let _ = _app.track_event("app_quit", None);
|
||||
std::process::exit(0);
|
||||
}
|
||||
window.emit("main_route", ()).unwrap();
|
||||
"show" => {
|
||||
let _ = _app.track_event(
|
||||
"tray_toggle",
|
||||
Some(
|
||||
serde_json::json!({
|
||||
"action": if is_visible { "hide" } else { "show" }
|
||||
})
|
||||
)
|
||||
);
|
||||
let is_visible = window.is_visible().unwrap();
|
||||
if is_visible {
|
||||
window.hide().unwrap();
|
||||
} else {
|
||||
window.show().unwrap();
|
||||
window.set_focus().unwrap();
|
||||
}
|
||||
window.emit("main_route", ()).unwrap();
|
||||
}
|
||||
"settings" => {
|
||||
let _ = _app.track_event("tray_settings", None);
|
||||
window.emit("settings", ()).unwrap();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
"settings" => {
|
||||
let _ = _app.track_event("tray_settings", None);
|
||||
window.emit("settings", ()).unwrap();
|
||||
}
|
||||
_ => (),
|
||||
})
|
||||
.icon(icon)
|
||||
.build(app)?;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use tauri::{async_runtime, AppHandle};
|
||||
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogKind};
|
||||
use tauri::{ async_runtime, AppHandle };
|
||||
use tauri_plugin_dialog::{ DialogExt, MessageDialogButtons, MessageDialogKind };
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
|
||||
pub async fn check_for_updates(app: AppHandle) {
|
||||
|
@ -21,18 +21,35 @@ pub async fn check_for_updates(app: AppHandle) {
|
|||
app.dialog()
|
||||
.message(msg)
|
||||
.title("Qopy Update Available")
|
||||
.buttons(MessageDialogButtons::OkCancelCustom(String::from("Install"), String::from("Cancel")))
|
||||
.buttons(
|
||||
MessageDialogButtons::OkCancelCustom(
|
||||
String::from("Install"),
|
||||
String::from("Cancel")
|
||||
)
|
||||
)
|
||||
.show(move |response| {
|
||||
if !response {
|
||||
return;
|
||||
}
|
||||
async_runtime::spawn(async move {
|
||||
match update.download_and_install(|_, _| {}, || {}).await {
|
||||
match
|
||||
update.download_and_install(
|
||||
|_, _| {},
|
||||
|| {}
|
||||
).await
|
||||
{
|
||||
Ok(_) => {
|
||||
app.dialog()
|
||||
.message("Update installed successfully. The application needs to restart to apply the changes.")
|
||||
.message(
|
||||
"Update installed successfully. The application needs to restart to apply the changes."
|
||||
)
|
||||
.title("Qopy Update Installed")
|
||||
.buttons(MessageDialogButtons::OkCancelCustom(String::from("Restart"), String::from("Cancel")))
|
||||
.buttons(
|
||||
MessageDialogButtons::OkCancelCustom(
|
||||
String::from("Restart"),
|
||||
String::from("Cancel")
|
||||
)
|
||||
)
|
||||
.show(move |response| {
|
||||
if response {
|
||||
app.restart();
|
||||
|
@ -42,7 +59,9 @@ pub async fn check_for_updates(app: AppHandle) {
|
|||
Err(e) => {
|
||||
println!("Error installing new update: {:?}", e);
|
||||
app.dialog()
|
||||
.message("Failed to install new update. The new update can be downloaded from Github")
|
||||
.message(
|
||||
"Failed to install new update. The new update can be downloaded from Github"
|
||||
)
|
||||
.kind(MessageDialogKind::Error)
|
||||
.show(|_| {});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use include_dir::{include_dir, Dir};
|
||||
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
|
||||
use include_dir::{ include_dir, Dir };
|
||||
use sqlx::sqlite::{ SqlitePool, SqlitePoolOptions };
|
||||
use std::fs;
|
||||
use tauri::Manager;
|
||||
use tokio::runtime::Runtime as TokioRuntime;
|
||||
|
@ -25,8 +25,7 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
|||
let pool = rt.block_on(async {
|
||||
SqlitePoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(&db_url)
|
||||
.await
|
||||
.connect(&db_url).await
|
||||
.expect("Failed to create pool")
|
||||
});
|
||||
|
||||
|
@ -49,24 +48,22 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
|
||||
async fn apply_migrations(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
sqlx::query(
|
||||
"CREATE TABLE IF NOT EXISTS schema_version (
|
||||
sqlx
|
||||
::query(
|
||||
"CREATE TABLE IF NOT EXISTS schema_version (
|
||||
version INTEGER PRIMARY KEY,
|
||||
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);",
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
);"
|
||||
)
|
||||
.execute(pool).await?;
|
||||
|
||||
let current_version: Option<i64> =
|
||||
sqlx::query_scalar("SELECT MAX(version) FROM schema_version")
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
let current_version: Option<i64> = sqlx
|
||||
::query_scalar("SELECT MAX(version) FROM schema_version")
|
||||
.fetch_one(pool).await?;
|
||||
|
||||
let current_version = current_version.unwrap_or(0);
|
||||
|
||||
let mut migration_files: Vec<(i64, &str)> = MIGRATIONS_DIR
|
||||
.files()
|
||||
let mut migration_files: Vec<(i64, &str)> = MIGRATIONS_DIR.files()
|
||||
.filter_map(|file| {
|
||||
let file_name = file.path().file_name()?.to_str()?;
|
||||
if file_name.ends_with(".sql") && file_name.starts_with("v") {
|
||||
|
@ -93,16 +90,16 @@ async fn apply_migrations(pool: &SqlitePool) -> Result<(), Box<dyn std::error::E
|
|||
.collect();
|
||||
|
||||
for statement in statements {
|
||||
sqlx::query(statement)
|
||||
.execute(pool)
|
||||
.await
|
||||
sqlx
|
||||
::query(statement)
|
||||
.execute(pool).await
|
||||
.map_err(|e| format!("Failed to execute migration {}: {}", version, e))?;
|
||||
}
|
||||
|
||||
sqlx::query("INSERT INTO schema_version (version) VALUES (?)")
|
||||
sqlx
|
||||
::query("INSERT INTO schema_version (version) VALUES (?)")
|
||||
.bind(version)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
.execute(pool).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,39 +1,35 @@
|
|||
use crate::utils::types::{ContentType, HistoryItem};
|
||||
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||
use crate::utils::types::{ ContentType, HistoryItem };
|
||||
use base64::{ engine::general_purpose::STANDARD, Engine };
|
||||
use rand::distributions::Alphanumeric;
|
||||
use rand::{thread_rng, Rng};
|
||||
use sqlx::{Row, SqlitePool};
|
||||
use rand::{ thread_rng, Rng };
|
||||
use sqlx::{ Row, SqlitePool };
|
||||
use std::fs;
|
||||
use tauri_plugin_aptabase::EventTracker;
|
||||
|
||||
pub async fn initialize_history(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let id: String = thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(16)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
let id: String = thread_rng().sample_iter(&Alphanumeric).take(16).map(char::from).collect();
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO history (id, source, content_type, content, timestamp) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)"
|
||||
)
|
||||
.bind(id)
|
||||
.bind("System")
|
||||
.bind("text")
|
||||
.bind("Welcome to your clipboard history!")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
sqlx
|
||||
::query(
|
||||
"INSERT INTO history (id, source, content_type, content, timestamp) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)"
|
||||
)
|
||||
.bind(id)
|
||||
.bind("System")
|
||||
.bind("text")
|
||||
.bind("Welcome to your clipboard history!")
|
||||
.execute(pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_history(pool: tauri::State<'_, SqlitePool>) -> Result<Vec<HistoryItem>, String> {
|
||||
let rows = sqlx::query(
|
||||
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history ORDER BY timestamp DESC",
|
||||
)
|
||||
.fetch_all(&*pool)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let rows = sqlx
|
||||
::query(
|
||||
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history ORDER BY timestamp DESC"
|
||||
)
|
||||
.fetch_all(&*pool).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let items = rows
|
||||
.iter()
|
||||
|
@ -56,50 +52,53 @@ pub async fn get_history(pool: tauri::State<'_, SqlitePool>) -> Result<Vec<Histo
|
|||
pub async fn add_history_item(
|
||||
app_handle: tauri::AppHandle,
|
||||
pool: tauri::State<'_, SqlitePool>,
|
||||
item: HistoryItem,
|
||||
item: HistoryItem
|
||||
) -> Result<(), String> {
|
||||
let (id, source, source_icon, content_type, content, favicon, timestamp, language) =
|
||||
item.to_row();
|
||||
|
||||
let existing = sqlx::query("SELECT id FROM history WHERE content = ? AND content_type = ?")
|
||||
let existing = sqlx
|
||||
::query("SELECT id FROM history WHERE content = ? AND content_type = ?")
|
||||
.bind(&content)
|
||||
.bind(&content_type)
|
||||
.fetch_optional(&*pool)
|
||||
.await
|
||||
.fetch_optional(&*pool).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
match existing {
|
||||
Some(_) => {
|
||||
sqlx::query(
|
||||
"UPDATE history SET timestamp = strftime('%Y-%m-%dT%H:%M:%f+00:00', 'now') WHERE content = ? AND content_type = ?"
|
||||
)
|
||||
.bind(&content)
|
||||
.bind(&content_type)
|
||||
.execute(&*pool)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
sqlx
|
||||
::query(
|
||||
"UPDATE history SET timestamp = strftime('%Y-%m-%dT%H:%M:%f+00:00', 'now') WHERE content = ? AND content_type = ?"
|
||||
)
|
||||
.bind(&content)
|
||||
.bind(&content_type)
|
||||
.execute(&*pool).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
None => {
|
||||
sqlx::query(
|
||||
"INSERT INTO history (id, source, source_icon, content_type, content, favicon, timestamp, language) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
)
|
||||
.bind(id)
|
||||
.bind(source)
|
||||
.bind(source_icon)
|
||||
.bind(content_type)
|
||||
.bind(content)
|
||||
.bind(favicon)
|
||||
.bind(timestamp)
|
||||
.bind(language)
|
||||
.execute(&*pool)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
sqlx
|
||||
::query(
|
||||
"INSERT INTO history (id, source, source_icon, content_type, content, favicon, timestamp, language) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
)
|
||||
.bind(id)
|
||||
.bind(source)
|
||||
.bind(source_icon)
|
||||
.bind(content_type)
|
||||
.bind(content)
|
||||
.bind(favicon)
|
||||
.bind(timestamp)
|
||||
.bind(language)
|
||||
.execute(&*pool).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
}
|
||||
|
||||
let _ = app_handle.track_event("history_item_added", Some(serde_json::json!({
|
||||
let _ = app_handle.track_event(
|
||||
"history_item_added",
|
||||
Some(serde_json::json!({
|
||||
"content_type": item.content_type.to_string()
|
||||
})));
|
||||
}))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -107,16 +106,16 @@ pub async fn add_history_item(
|
|||
#[tauri::command]
|
||||
pub async fn search_history(
|
||||
pool: tauri::State<'_, SqlitePool>,
|
||||
query: String,
|
||||
query: String
|
||||
) -> Result<Vec<HistoryItem>, String> {
|
||||
let query = format!("%{}%", query);
|
||||
let rows = sqlx::query(
|
||||
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history WHERE content LIKE ? ORDER BY timestamp DESC"
|
||||
)
|
||||
.bind(query)
|
||||
.fetch_all(&*pool)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let rows = sqlx
|
||||
::query(
|
||||
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history WHERE content LIKE ? ORDER BY timestamp DESC"
|
||||
)
|
||||
.bind(query)
|
||||
.fetch_all(&*pool).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let items = rows
|
||||
.iter()
|
||||
|
@ -139,16 +138,16 @@ pub async fn search_history(
|
|||
pub async fn load_history_chunk(
|
||||
pool: tauri::State<'_, SqlitePool>,
|
||||
offset: i64,
|
||||
limit: i64,
|
||||
limit: i64
|
||||
) -> Result<Vec<HistoryItem>, String> {
|
||||
let rows = sqlx::query(
|
||||
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history ORDER BY timestamp DESC LIMIT ? OFFSET ?"
|
||||
)
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(&*pool)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let rows = sqlx
|
||||
::query(
|
||||
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history ORDER BY timestamp DESC LIMIT ? OFFSET ?"
|
||||
)
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(&*pool).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let items = rows
|
||||
.iter()
|
||||
|
@ -171,12 +170,12 @@ pub async fn load_history_chunk(
|
|||
pub async fn delete_history_item(
|
||||
app_handle: tauri::AppHandle,
|
||||
pool: tauri::State<'_, SqlitePool>,
|
||||
id: String,
|
||||
id: String
|
||||
) -> Result<(), String> {
|
||||
sqlx::query("DELETE FROM history WHERE id = ?")
|
||||
sqlx
|
||||
::query("DELETE FROM history WHERE id = ?")
|
||||
.bind(id)
|
||||
.execute(&*pool)
|
||||
.await
|
||||
.execute(&*pool).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let _ = app_handle.track_event("history_item_deleted", None);
|
||||
|
@ -189,9 +188,9 @@ pub async fn clear_history(
|
|||
app_handle: tauri::AppHandle,
|
||||
pool: tauri::State<'_, SqlitePool>
|
||||
) -> Result<(), String> {
|
||||
sqlx::query("DELETE FROM history")
|
||||
.execute(&*pool)
|
||||
.await
|
||||
sqlx
|
||||
::query("DELETE FROM history")
|
||||
.execute(&*pool).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let _ = app_handle.track_event("history_cleared", None);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde::{ Deserialize, Serialize };
|
||||
use serde_json;
|
||||
use sqlx::Row;
|
||||
use sqlx::SqlitePool;
|
||||
use tauri::{Emitter, Manager};
|
||||
use tauri::{ Emitter, Manager };
|
||||
use tauri_plugin_aptabase::EventTracker;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
|
@ -16,10 +16,10 @@ pub async fn initialize_settings(pool: &SqlitePool) -> Result<(), Box<dyn std::e
|
|||
};
|
||||
let json = serde_json::to_string(&default_keybind)?;
|
||||
|
||||
sqlx::query("INSERT INTO settings (key, value) VALUES ('keybind', ?)")
|
||||
sqlx
|
||||
::query("INSERT INTO settings (key, value) VALUES ('keybind', ?)")
|
||||
.bind(json)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
.execute(pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -28,23 +28,24 @@ pub async fn initialize_settings(pool: &SqlitePool) -> Result<(), Box<dyn std::e
|
|||
pub async fn save_keybind(
|
||||
app_handle: tauri::AppHandle,
|
||||
pool: tauri::State<'_, SqlitePool>,
|
||||
keybind: Vec<String>,
|
||||
keybind: Vec<String>
|
||||
) -> Result<(), String> {
|
||||
app_handle
|
||||
.emit("update-shortcut", &keybind)
|
||||
.map_err(|e| e.to_string())?;
|
||||
app_handle.emit("update-shortcut", &keybind).map_err(|e| e.to_string())?;
|
||||
|
||||
let json = serde_json::to_string(&keybind).map_err(|e| e.to_string())?;
|
||||
|
||||
sqlx::query("INSERT OR REPLACE INTO settings (key, value) VALUES ('keybind', ?)")
|
||||
sqlx
|
||||
::query("INSERT OR REPLACE INTO settings (key, value) VALUES ('keybind', ?)")
|
||||
.bind(json)
|
||||
.execute(&*pool)
|
||||
.await
|
||||
.execute(&*pool).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let _ = app_handle.track_event("keybind_saved", Some(serde_json::json!({
|
||||
let _ = app_handle.track_event(
|
||||
"keybind_saved",
|
||||
Some(serde_json::json!({
|
||||
"keybind": keybind
|
||||
})));
|
||||
}))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -52,12 +53,12 @@ pub async fn save_keybind(
|
|||
#[tauri::command]
|
||||
pub async fn get_setting(
|
||||
pool: tauri::State<'_, SqlitePool>,
|
||||
key: String,
|
||||
key: String
|
||||
) -> Result<String, String> {
|
||||
let row = sqlx::query("SELECT value FROM settings WHERE key = ?")
|
||||
let row = sqlx
|
||||
::query("SELECT value FROM settings WHERE key = ?")
|
||||
.bind(key)
|
||||
.fetch_optional(&*pool)
|
||||
.await
|
||||
.fetch_optional(&*pool).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(row.map(|r| r.get("value")).unwrap_or_default())
|
||||
|
@ -68,18 +69,21 @@ pub async fn save_setting(
|
|||
app_handle: tauri::AppHandle,
|
||||
pool: tauri::State<'_, SqlitePool>,
|
||||
key: String,
|
||||
value: String,
|
||||
value: String
|
||||
) -> Result<(), String> {
|
||||
sqlx::query("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)")
|
||||
sqlx
|
||||
::query("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)")
|
||||
.bind(key.clone())
|
||||
.bind(value)
|
||||
.execute(&*pool)
|
||||
.await
|
||||
.execute(&*pool).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let _ = app_handle.track_event("setting_saved", Some(serde_json::json!({
|
||||
let _ = app_handle.track_event(
|
||||
"setting_saved",
|
||||
Some(serde_json::json!({
|
||||
"key": key
|
||||
})));
|
||||
}))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -88,15 +92,18 @@ pub async fn save_setting(
|
|||
pub async fn get_keybind(app_handle: tauri::AppHandle) -> Result<Vec<String>, String> {
|
||||
let pool = app_handle.state::<SqlitePool>();
|
||||
|
||||
let row = sqlx::query("SELECT value FROM settings WHERE key = 'keybind'")
|
||||
.fetch_optional(&*pool)
|
||||
.await
|
||||
let row = sqlx
|
||||
::query("SELECT value FROM settings WHERE key = 'keybind'")
|
||||
.fetch_optional(&*pool).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let json = row.map(|r| r.get::<String, _>("value")).unwrap_or_else(|| {
|
||||
serde_json::to_string(&vec!["MetaLeft".to_string(), "KeyV".to_string()])
|
||||
.expect("Failed to serialize default keybind")
|
||||
});
|
||||
let json = row
|
||||
.map(|r| r.get::<String, _>("value"))
|
||||
.unwrap_or_else(|| {
|
||||
serde_json
|
||||
::to_string(&vec!["MetaLeft".to_string(), "KeyV".to_string()])
|
||||
.expect("Failed to serialize default keybind")
|
||||
});
|
||||
|
||||
serde_json::from_str::<Vec<String>>(&json).map_err(|e| e.to_string())
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
#![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")]
|
||||
|
||||
mod api;
|
||||
mod db;
|
||||
|
@ -10,7 +7,7 @@ mod utils;
|
|||
use sqlx::sqlite::SqlitePoolOptions;
|
||||
use std::fs;
|
||||
use tauri::Manager;
|
||||
use tauri_plugin_aptabase::{EventTracker, InitOptions};
|
||||
use tauri_plugin_aptabase::{ EventTracker, InitOptions };
|
||||
use tauri_plugin_autostart::MacosLauncher;
|
||||
use tauri_plugin_prevent_default::Flags;
|
||||
|
||||
|
@ -18,7 +15,8 @@ fn main() {
|
|||
let runtime = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
|
||||
let _guard = runtime.enter();
|
||||
|
||||
tauri::Builder::default()
|
||||
tauri::Builder
|
||||
::default()
|
||||
.plugin(tauri_plugin_clipboard::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_sql::Builder::default().build())
|
||||
|
@ -26,34 +24,37 @@ fn main() {
|
|||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_updater::Builder::default().build())
|
||||
.plugin(
|
||||
tauri_plugin_aptabase::Builder::new("A-SH-8937252746")
|
||||
tauri_plugin_aptabase::Builder
|
||||
::new("A-SH-8937252746")
|
||||
.with_options(InitOptions {
|
||||
host: Some("https://aptabase.pandadev.net".to_string()),
|
||||
flush_interval: None,
|
||||
})
|
||||
.with_panic_hook(Box::new(|client, info, msg| {
|
||||
let location = info
|
||||
.location()
|
||||
.map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()))
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
.with_panic_hook(
|
||||
Box::new(|client, info, msg| {
|
||||
let location = info
|
||||
.location()
|
||||
.map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()))
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
|
||||
let _ = client.track_event(
|
||||
"panic",
|
||||
Some(serde_json::json!({
|
||||
let _ = client.track_event(
|
||||
"panic",
|
||||
Some(
|
||||
serde_json::json!({
|
||||
"info": format!("{} ({})", msg, location),
|
||||
})),
|
||||
);
|
||||
}))
|
||||
.build(),
|
||||
})
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.plugin(tauri_plugin_autostart::init(
|
||||
MacosLauncher::LaunchAgent,
|
||||
Some(vec![]),
|
||||
))
|
||||
.plugin(tauri_plugin_autostart::init(MacosLauncher::LaunchAgent, Some(vec![])))
|
||||
.plugin(
|
||||
tauri_plugin_prevent_default::Builder::new()
|
||||
tauri_plugin_prevent_default::Builder
|
||||
::new()
|
||||
.with_flags(Flags::all().difference(Flags::CONTEXT_MENU))
|
||||
.build(),
|
||||
.build()
|
||||
)
|
||||
.setup(|app| {
|
||||
let app_data_dir = app.path().app_data_dir().unwrap();
|
||||
|
@ -75,8 +76,7 @@ fn main() {
|
|||
tauri::async_runtime::spawn(async move {
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(&db_url)
|
||||
.await
|
||||
.connect(&db_url).await
|
||||
.expect("Failed to create pool");
|
||||
|
||||
app_handle_clone.manage(pool);
|
||||
|
@ -91,7 +91,10 @@ fn main() {
|
|||
let _ = api::clipboard::start_monitor(app_handle.clone());
|
||||
|
||||
utils::commands::center_window_on_current_monitor(main_window.as_ref().unwrap());
|
||||
main_window.as_ref().map(|w| w.hide()).unwrap_or(Ok(()))?;
|
||||
main_window
|
||||
.as_ref()
|
||||
.map(|w| w.hide())
|
||||
.unwrap_or(Ok(()))?;
|
||||
|
||||
let _ = app.track_event("app_started", None);
|
||||
|
||||
|
@ -109,21 +112,23 @@ fn main() {
|
|||
}
|
||||
}
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
api::clipboard::write_and_paste,
|
||||
db::history::get_history,
|
||||
db::history::add_history_item,
|
||||
db::history::search_history,
|
||||
db::history::load_history_chunk,
|
||||
db::history::delete_history_item,
|
||||
db::history::clear_history,
|
||||
db::history::read_image,
|
||||
db::settings::get_setting,
|
||||
db::settings::save_setting,
|
||||
db::settings::save_keybind,
|
||||
db::settings::get_keybind,
|
||||
utils::commands::fetch_page_meta,
|
||||
])
|
||||
.invoke_handler(
|
||||
tauri::generate_handler![
|
||||
api::clipboard::write_and_paste,
|
||||
db::history::get_history,
|
||||
db::history::add_history_item,
|
||||
db::history::search_history,
|
||||
db::history::load_history_chunk,
|
||||
db::history::delete_history_item,
|
||||
db::history::clear_history,
|
||||
db::history::read_image,
|
||||
db::settings::get_setting,
|
||||
db::settings::save_setting,
|
||||
db::settings::save_keybind,
|
||||
db::settings::get_keybind,
|
||||
utils::commands::fetch_page_meta
|
||||
]
|
||||
)
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
|
|
@ -1,34 +1,37 @@
|
|||
use active_win_pos_rs::get_active_window;
|
||||
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||
use base64::{ engine::general_purpose::STANDARD, Engine };
|
||||
use image::codecs::png::PngEncoder;
|
||||
use tauri::PhysicalPosition;
|
||||
use meta_fetcher;
|
||||
|
||||
pub fn center_window_on_current_monitor(window: &tauri::WebviewWindow) {
|
||||
if let Some(monitor) = window.available_monitors().unwrap().iter().find(|m| {
|
||||
let primary_monitor = window
|
||||
.primary_monitor()
|
||||
if
|
||||
let Some(monitor) = window
|
||||
.available_monitors()
|
||||
.unwrap()
|
||||
.expect("Failed to get primary monitor");
|
||||
let mouse_position = primary_monitor.position();
|
||||
let monitor_position = m.position();
|
||||
let monitor_size = m.size();
|
||||
mouse_position.x >= monitor_position.x
|
||||
&& mouse_position.x < monitor_position.x + monitor_size.width as i32
|
||||
&& mouse_position.y >= monitor_position.y
|
||||
&& mouse_position.y < monitor_position.y + monitor_size.height as i32
|
||||
}) {
|
||||
.iter()
|
||||
.find(|m| {
|
||||
let primary_monitor = window
|
||||
.primary_monitor()
|
||||
.unwrap()
|
||||
.expect("Failed to get primary monitor");
|
||||
let mouse_position = primary_monitor.position();
|
||||
let monitor_position = m.position();
|
||||
let monitor_size = m.size();
|
||||
mouse_position.x >= monitor_position.x &&
|
||||
mouse_position.x < monitor_position.x + (monitor_size.width as i32) &&
|
||||
mouse_position.y >= monitor_position.y &&
|
||||
mouse_position.y < monitor_position.y + (monitor_size.height as i32)
|
||||
})
|
||||
{
|
||||
let monitor_size = monitor.size();
|
||||
let window_size = window.outer_size().unwrap();
|
||||
|
||||
let x = (monitor_size.width as i32 - window_size.width as i32) / 2;
|
||||
let y = (monitor_size.height as i32 - window_size.height as i32) / 2;
|
||||
let x = ((monitor_size.width as i32) - (window_size.width as i32)) / 2;
|
||||
let y = ((monitor_size.height as i32) - (window_size.height as i32)) / 2;
|
||||
|
||||
window
|
||||
.set_position(PhysicalPosition::new(
|
||||
monitor.position().x + x,
|
||||
monitor.position().y + y,
|
||||
))
|
||||
.set_position(PhysicalPosition::new(monitor.position().x + x, monitor.position().y + y))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -51,59 +54,64 @@ fn _process_icon_to_base64(path: &str) -> Result<String, Box<dyn std::error::Err
|
|||
Ok(STANDARD.encode(png_buffer))
|
||||
}
|
||||
|
||||
|
||||
pub fn detect_color(color: &str) -> bool {
|
||||
let color = color.trim().to_lowercase();
|
||||
|
||||
|
||||
// hex
|
||||
if color.starts_with('#') && color.len() == color.trim_end_matches(char::is_whitespace).len() {
|
||||
let hex = &color[1..];
|
||||
return match hex.len() {
|
||||
3 | 6 | 8 => hex.chars().all(|c| c.is_ascii_hexdigit()),
|
||||
_ => false
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// rgb/rgba
|
||||
if (color.starts_with("rgb(") || color.starts_with("rgba(")) && color.ends_with(")") && !color[..color.len()-1].contains(")") {
|
||||
if
|
||||
(color.starts_with("rgb(") || color.starts_with("rgba(")) &&
|
||||
color.ends_with(")") &&
|
||||
!color[..color.len() - 1].contains(")")
|
||||
{
|
||||
let values = color
|
||||
.trim_start_matches("rgba(")
|
||||
.trim_start_matches("rgb(")
|
||||
.trim_end_matches(')')
|
||||
.split(',')
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
|
||||
return match values.len() {
|
||||
3 | 4 => values.iter().all(|v| v.trim().parse::<f32>().is_ok()),
|
||||
_ => false
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
// hsl/hsla
|
||||
if (color.starts_with("hsl(") || color.starts_with("hsla(")) && color.ends_with(")") && !color[..color.len()-1].contains(")") {
|
||||
|
||||
// hsl/hsla
|
||||
if
|
||||
(color.starts_with("hsl(") || color.starts_with("hsla(")) &&
|
||||
color.ends_with(")") &&
|
||||
!color[..color.len() - 1].contains(")")
|
||||
{
|
||||
let values = color
|
||||
.trim_start_matches("hsla(")
|
||||
.trim_start_matches("hsl(")
|
||||
.trim_end_matches(')')
|
||||
.split(',')
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
|
||||
return match values.len() {
|
||||
3 | 4 => values.iter().all(|v| v.trim().parse::<f32>().is_ok()),
|
||||
_ => false
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn fetch_page_meta(url: String) -> Result<(String, Option<String>), String> {
|
||||
let metadata = meta_fetcher::fetch_metadata(&url)
|
||||
let metadata = meta_fetcher
|
||||
::fetch_metadata(&url)
|
||||
.map_err(|e| format!("Failed to fetch metadata: {}", e))?;
|
||||
|
||||
Ok((
|
||||
metadata.title.unwrap_or_else(|| "No title found".to_string()),
|
||||
metadata.image
|
||||
))
|
||||
}
|
||||
|
||||
Ok((metadata.title.unwrap_or_else(|| "No title found".to_string()), metadata.image))
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use reqwest;
|
|||
use url::Url;
|
||||
|
||||
pub async fn fetch_favicon_as_base64(
|
||||
url: Url,
|
||||
url: Url
|
||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
let client = reqwest::Client::new();
|
||||
let favicon_url = format!("https://favicone.com/{}", url.host_str().unwrap());
|
||||
|
|
|
@ -105,7 +105,9 @@ impl FromStr for KeyCode {
|
|||
"F10" => Code::F10,
|
||||
"F11" => Code::F11,
|
||||
"F12" => Code::F12,
|
||||
_ => return Err(format!("Unknown key code: {}", s)),
|
||||
_ => {
|
||||
return Err(format!("Unknown key code: {}", s));
|
||||
}
|
||||
};
|
||||
Ok(KeyCode(code))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use chrono;
|
||||
use log::{LevelFilter, SetLoggerError};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use log::{ LevelFilter, SetLoggerError };
|
||||
use std::fs::{ File, OpenOptions };
|
||||
use std::io::Write;
|
||||
use std::panic;
|
||||
|
||||
|
@ -16,7 +16,7 @@ impl log::Log for FileLogger {
|
|||
fn log(&self, record: &log::Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
let mut file = self.file.try_clone().expect("Failed to clone file handle");
|
||||
|
||||
|
||||
// Format: timestamp [LEVEL] target: message (file:line)
|
||||
writeln!(
|
||||
file,
|
||||
|
@ -50,32 +50,38 @@ pub fn init_logger(app_data_dir: &std::path::Path) -> Result<(), SetLoggerError>
|
|||
|
||||
// Set up panic hook
|
||||
let panic_file = file.try_clone().expect("Failed to clone file handle");
|
||||
panic::set_hook(Box::new(move |panic_info| {
|
||||
let mut file = panic_file.try_clone().expect("Failed to clone file handle");
|
||||
|
||||
let location = panic_info.location()
|
||||
.map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()))
|
||||
.unwrap_or_else(|| "unknown location".to_string());
|
||||
panic::set_hook(
|
||||
Box::new(move |panic_info| {
|
||||
let mut file = panic_file.try_clone().expect("Failed to clone file handle");
|
||||
|
||||
let message = match panic_info.payload().downcast_ref::<&str>() {
|
||||
Some(s) => *s,
|
||||
None => match panic_info.payload().downcast_ref::<String>() {
|
||||
Some(s) => s.as_str(),
|
||||
None => "Unknown panic message",
|
||||
},
|
||||
};
|
||||
let location = panic_info
|
||||
.location()
|
||||
.map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()))
|
||||
.unwrap_or_else(|| "unknown location".to_string());
|
||||
|
||||
let _ = writeln!(
|
||||
file,
|
||||
"{} [PANIC] rust_panic: {} ({})",
|
||||
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
|
||||
message,
|
||||
location
|
||||
);
|
||||
}));
|
||||
let message = match panic_info.payload().downcast_ref::<&str>() {
|
||||
Some(s) => *s,
|
||||
None =>
|
||||
match panic_info.payload().downcast_ref::<String>() {
|
||||
Some(s) => s.as_str(),
|
||||
None => "Unknown panic message",
|
||||
}
|
||||
};
|
||||
|
||||
let _ = writeln!(
|
||||
file,
|
||||
"{} [PANIC] rust_panic: {} ({})",
|
||||
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
|
||||
message,
|
||||
location
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
let logger = Box::new(FileLogger { file });
|
||||
unsafe { log::set_logger_racy(Box::leak(logger))? };
|
||||
unsafe {
|
||||
log::set_logger_racy(Box::leak(logger))?;
|
||||
}
|
||||
log::set_max_level(LevelFilter::Debug);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,4 +2,4 @@ pub mod commands;
|
|||
pub mod favicon;
|
||||
pub mod types;
|
||||
pub mod logger;
|
||||
pub mod keys;
|
||||
pub mod keys;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::{ DateTime, Utc };
|
||||
use serde::{ Deserialize, Serialize };
|
||||
use std::fmt;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -115,7 +115,7 @@ impl HistoryItem {
|
|||
content: String,
|
||||
favicon: Option<String>,
|
||||
source_icon: Option<String>,
|
||||
language: Option<String>,
|
||||
language: Option<String>
|
||||
) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
|
@ -130,7 +130,7 @@ impl HistoryItem {
|
|||
}
|
||||
|
||||
pub fn to_row(
|
||||
&self,
|
||||
&self
|
||||
) -> (
|
||||
String,
|
||||
String,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue