From 51d2243e733e259e9842d52a9a17ba136080f576 Mon Sep 17 00:00:00 2001 From: PandaDEV <70103896+0PandaDEV@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:32:38 +1000 Subject: [PATCH] added new icons --- app.vue | 22 ++-- assets/css/style.scss | 7 +- components/FileIcon.vue | 3 - components/Icons/Code.vue | 3 + components/Icons/File.vue | 3 + components/Icons/Image.vue | 3 + components/Icons/Text.vue | 3 + public/Code.svg | 7 + public/Image.svg | 7 + public/Logo.svg | 62 --------- public/Text.svg | 7 + public/file.svg | 23 +--- src-tauri/src/api/clipboard.rs | 230 +++++++++++++++++++++------------ 13 files changed, 195 insertions(+), 185 deletions(-) delete mode 100644 components/FileIcon.vue create mode 100644 components/Icons/Code.vue create mode 100644 components/Icons/File.vue create mode 100644 components/Icons/Image.vue create mode 100644 components/Icons/Text.vue create mode 100644 public/Code.svg create mode 100644 public/Image.svg delete mode 100644 public/Logo.svg create mode 100644 public/Text.svg diff --git a/app.vue b/app.vue index f736871..41027d2 100644 --- a/app.vue +++ b/app.vue @@ -5,7 +5,7 @@ spellcheck="false" class="search" type="text" placeholder="Type to filter entries...">
- +

Qopy

@@ -32,9 +32,14 @@ :class="['result clothoid-corner', { 'selected': isSelected(groupIndex, index) }]" @click="selectItem(groupIndex, index)" :ref="el => { if (isSelected(groupIndex, index)) selectedElement = el as HTMLElement }"> - Image - Favicon - + + Favicon + + + Image ({{ item.dimensions || 'Loading...' }}) {{ truncateContent(item.content) }}
@@ -225,13 +230,8 @@ const truncateContent = (content: string): string => { return content.length > maxChars ? content.slice(0, maxChars - 3) + '...' : content; }; -const isUrl = (str: string): boolean => { - try { - new URL(str); - return true; - } catch { - return false; - } +const hasFavicon = (str: string): boolean => { + return str.trim() !== ''; }; const isYoutubeWatchUrl = (url: string): boolean => { diff --git a/assets/css/style.scss b/assets/css/style.scss index 3ae86fe..b64eb30 100644 --- a/assets/css/style.scss +++ b/assets/css/style.scss @@ -132,13 +132,14 @@ body, width: 20px; } - .favicon-image { + .image { width: 20px; height: 20px; } - .file { - margin-inline: 2px; + .icon { + width: 18px; + height: 18px; } } diff --git a/components/FileIcon.vue b/components/FileIcon.vue deleted file mode 100644 index a38f88e..0000000 --- a/components/FileIcon.vue +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/components/Icons/Code.vue b/components/Icons/Code.vue new file mode 100644 index 0000000..a0a7503 --- /dev/null +++ b/components/Icons/Code.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/components/Icons/File.vue b/components/Icons/File.vue new file mode 100644 index 0000000..f65c400 --- /dev/null +++ b/components/Icons/File.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/components/Icons/Image.vue b/components/Icons/Image.vue new file mode 100644 index 0000000..5903bff --- /dev/null +++ b/components/Icons/Image.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/components/Icons/Text.vue b/components/Icons/Text.vue new file mode 100644 index 0000000..54aee4b --- /dev/null +++ b/components/Icons/Text.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/public/Code.svg b/public/Code.svg new file mode 100644 index 0000000..3ce8640 --- /dev/null +++ b/public/Code.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/public/Image.svg b/public/Image.svg new file mode 100644 index 0000000..e843d91 --- /dev/null +++ b/public/Image.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/public/Logo.svg b/public/Logo.svg deleted file mode 100644 index a757e0c..0000000 --- a/public/Logo.svg +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/Text.svg b/public/Text.svg new file mode 100644 index 0000000..e0a6d03 --- /dev/null +++ b/public/Text.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/public/file.svg b/public/file.svg index ef44dbe..a88580e 100644 --- a/public/file.svg +++ b/public/file.svg @@ -1,26 +1,7 @@ - - - - - - - - - - - - - - - - - - - - - + + \ No newline at end of file diff --git a/src-tauri/src/api/clipboard.rs b/src-tauri/src/api/clipboard.rs index 9f919a7..e382aa0 100644 --- a/src-tauri/src/api/clipboard.rs +++ b/src-tauri/src/api/clipboard.rs @@ -1,22 +1,17 @@ -use base64::Engine; use base64::engine::general_purpose::STANDARD; -use tauri::{AppHandle, Manager, Runtime, Emitter, Listener}; +use base64::Engine; +use image::ImageFormat; +use lazy_static::lazy_static; +use rand::Rng; +use rdev::{simulate, EventType, Key}; +use regex::Regex; +use sha2::{Digest, Sha256}; +use sqlx::SqlitePool; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::{fs, sync::Mutex, thread, time::Duration}; +use tauri::{AppHandle, Emitter, Listener, Manager, Runtime}; use tauri_plugin_clipboard::Clipboard; use tokio::runtime::Runtime as TokioRuntime; -use regex::Regex; -use sqlx::SqlitePool; -use std::{ - fs, - sync::Mutex, - thread, - time::Duration, -}; -use rand::Rng; -use sha2::{Sha256, Digest}; -use rdev::{simulate, Key, EventType}; -use lazy_static::lazy_static; -use image::ImageFormat; -use std::sync::atomic::{AtomicBool, Ordering}; lazy_static! { static ref APP_DATA_DIR: Mutex> = Mutex::new(None); @@ -37,17 +32,30 @@ pub fn read_image(filename: String) -> Result, String> { } #[tauri::command] -pub async fn write_and_paste(app_handle: tauri::AppHandle, content: String, content_type: String) -> Result<(), String> { +pub async fn write_and_paste( + app_handle: tauri::AppHandle, + content: String, + content_type: String, +) -> Result<(), String> { let clipboard = app_handle.state::(); 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.write_files_uris(content.split(", ").map(|file| file.to_string()).collect::>()).map_err(|e| e.to_string())?; - }, + clipboard + .write_files_uris( + content + .split(", ") + .map(|file| file.to_string()) + .collect::>(), + ) + .map_err(|e| e.to_string())?; + } _ => return Err("Unsupported content type".to_string()), } @@ -81,7 +89,10 @@ fn simulate_paste() { #[tauri::command] pub fn get_image_path(app_handle: tauri::AppHandle, filename: String) -> String { - let app_data_dir = app_handle.path().app_data_dir().expect("Failed to get app data directory"); + let app_data_dir = app_handle + .path() + .app_data_dir() + .expect("Failed to get app data directory"); let image_path = app_data_dir.join("images").join(filename); image_path.to_str().unwrap_or("").to_string() } @@ -90,61 +101,94 @@ pub fn setup(app: &AppHandle) { let app = app.clone(); let runtime = TokioRuntime::new().expect("Failed to create Tokio runtime"); - app.clone().listen("plugin:clipboard://clipboard-monitor/update", move |_event| { - let app = app.clone(); - runtime.block_on(async move { - if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) { - println!("Ignoring programmatic paste"); - return; - } + app.clone().listen( + "plugin:clipboard://clipboard-monitor/update", + move |_event| { + let app = app.clone(); + runtime.block_on(async move { + if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) { + println!("Ignoring programmatic paste"); + return; + } - let clipboard = app.state::(); - let available_types = clipboard.available_types().unwrap(); - - println!("Clipboard update detected"); + let clipboard = app.state::(); + let available_types = clipboard.available_types().unwrap(); - match get_pool(&app).await { - Ok(pool) => { - if available_types.image { - println!("Handling image change"); - if let Ok(image_data) = clipboard.read_image_base64() { - let base64_image = STANDARD.encode(&image_data); - insert_content_if_not_exists(app.clone(), pool.clone(), "image", base64_image).await; + println!("Clipboard update detected"); + + match get_pool(&app).await { + Ok(pool) => { + if available_types.image { + println!("Handling image change"); + if let Ok(image_data) = clipboard.read_image_base64() { + let base64_image = STANDARD.encode(&image_data); + insert_content_if_not_exists( + app.clone(), + pool.clone(), + "image", + base64_image, + ) + .await; + } + let _ = app.emit("plugin:clipboard://image-changed", ()); + } else if available_types.files { + println!("Handling files change"); + if let Ok(files) = clipboard.read_files() { + let files_str = files.join(", "); + insert_content_if_not_exists( + app.clone(), + pool.clone(), + "files", + files_str, + ) + .await; + } + let _ = app.emit("plugin:clipboard://files-changed", ()); + } else if available_types.text { + println!("Handling text change"); + if let Ok(text) = clipboard.read_text() { + insert_content_if_not_exists( + app.clone(), + pool.clone(), + "text", + text, + ) + .await; + } + let _ = app.emit("plugin:clipboard://text-changed", ()); + } else { + println!("Unknown clipboard content type"); } - let _ = app.emit("plugin:clipboard://image-changed", ()); - } else if available_types.files { - println!("Handling files change"); - if let Ok(files) = clipboard.read_files() { - let files_str = files.join(", "); - insert_content_if_not_exists(app.clone(), pool.clone(), "files", files_str).await; - } - let _ = app.emit("plugin:clipboard://files-changed", ()); - } else if available_types.text { - println!("Handling text change"); - if let Ok(text) = clipboard.read_text() { - insert_content_if_not_exists(app.clone(), pool.clone(), "text", text).await; - } - let _ = app.emit("plugin:clipboard://text-changed", ()); - } else { - println!("Unknown clipboard content type"); + } + Err(e) => { + println!("Failed to get database pool: {}", e); } } - Err(e) => { - println!("Failed to get database pool: {}", e); - } - } - }); - }); + }); + }, + ); } -async fn get_pool(app_handle: &AppHandle) -> Result> { - let app_data_dir = app_handle.path().app_data_dir().expect("Failed to get app data directory"); +async fn get_pool( + app_handle: &AppHandle, +) -> Result> { + let app_data_dir = app_handle + .path() + .app_data_dir() + .expect("Failed to get app data directory"); let db_path = app_data_dir.join("data.db"); let database_url = format!("sqlite:{}", db_path.to_str().unwrap()); - SqlitePool::connect(&database_url).await.map_err(|e| Box::new(e) as Box) + SqlitePool::connect(&database_url) + .await + .map_err(|e| Box::new(e) as Box) } -async fn insert_content_if_not_exists(app_handle: AppHandle, pool: SqlitePool, content_type: &str, content: String) { +async fn insert_content_if_not_exists( + app_handle: AppHandle, + pool: SqlitePool, + content_type: &str, + content: String, +) { let last_content: Option = sqlx::query_scalar( "SELECT content FROM history WHERE content_type = ? ORDER BY timestamp DESC LIMIT 1", ) @@ -172,21 +216,25 @@ async fn insert_content_if_not_exists(app_handle: AppHandle, pool .map(char::from) .collect(); - 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(); - let favicon_base64 = if content_type == "text" && url_regex.is_match(&content) { - match url::Url::parse(&content) { - Ok(url) => match fetch_favicon_as_base64(url).await { - Ok(Some(favicon)) => Some(favicon), - Ok(None) => None, + let favicon_base64 = if content_type == "text" { + 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(&content) { + match url::Url::parse(&content) { + Ok(url) => match fetch_favicon_as_base64(url).await { + Ok(Some(favicon)) => Some(favicon), + Ok(None) => None, + Err(e) => { + println!("Failed to fetch favicon: {}", e); + None + } + }, Err(e) => { - println!("Failed to fetch favicon: {}", e); + println!("Failed to parse URL: {}", e); None } - }, - Err(e) => { - println!("Failed to parse URL: {}", e); - None } + } else { + None } } else { None @@ -204,26 +252,34 @@ async fn insert_content_if_not_exists(app_handle: AppHandle, pool } } -async fn save_image(app_handle: &AppHandle, base64_image: &str) -> Result> { +async fn save_image( + app_handle: &AppHandle, + base64_image: &str, +) -> Result> { let image_data = STANDARD.decode(base64_image)?; let mut hasher = Sha256::new(); hasher.update(&image_data); let hash = hasher.finalize(); let filename = format!("{:x}.png", hash); - - let app_data_dir = app_handle.path().app_data_dir().expect("Failed to get app data directory"); + + let app_data_dir = app_handle + .path() + .app_data_dir() + .expect("Failed to get app data directory"); let images_dir = app_data_dir.join("images"); let path = images_dir.join(&filename); - + if !path.exists() { fs::create_dir_all(&images_dir)?; fs::write(&path, &image_data)?; } - + Ok(path.to_str().unwrap().to_string()) } -async fn fetch_favicon_as_base64(url: url::Url) -> Result, Box> { +async fn fetch_favicon_as_base64( + url: url::Url, +) -> Result, Box> { let client = reqwest::Client::new(); let favicon_url = format!("https://favicone.com/{}", url.host_str().unwrap()); let response = client.get(&favicon_url).send().await?; @@ -242,7 +298,11 @@ async fn fetch_favicon_as_base64(url: url::Url) -> Result, Box Result<(), String> { let clipboard = app_handle.state::(); - 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())?; + 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())?; Ok(()) } \ No newline at end of file