mirror of
https://github.com/0PandaDEV/Qopy.git
synced 2025-04-21 13:14:04 +02:00
Merge branch 'main' of https://github.com/0PandaDEV/Qopy
This commit is contained in:
commit
fa2744069a
12 changed files with 191 additions and 114 deletions
9
app.vue
9
app.vue
|
@ -32,9 +32,14 @@
|
||||||
:class="['result clothoid-corner', { 'selected': isSelected(groupIndex, index) }]"
|
:class="['result clothoid-corner', { 'selected': isSelected(groupIndex, index) }]"
|
||||||
@click="selectItem(groupIndex, index)"
|
@click="selectItem(groupIndex, index)"
|
||||||
:ref="el => { if (isSelected(groupIndex, index)) selectedElement = el as HTMLElement }">
|
:ref="el => { if (isSelected(groupIndex, index)) selectedElement = el as HTMLElement }">
|
||||||
<img v-if="item.content_type === 'image'" :src="getComputedImageUrl(item)" alt="Image" class="favicon-image">
|
<template v-if="item.content_type === 'image'">
|
||||||
|
<img :src="getComputedImageUrl(item)" alt="Image" class="image" @error="onImageError">
|
||||||
|
<IconsImage v-show="imageLoadError" class="icon" />
|
||||||
|
</template>
|
||||||
<img v-else-if="hasFavicon(item.favicon ?? '')" :src="getFaviconFromDb(item.favicon ?? '')" alt="Favicon" class="favicon">
|
<img v-else-if="hasFavicon(item.favicon ?? '')" :src="getFaviconFromDb(item.favicon ?? '')" alt="Favicon" class="favicon">
|
||||||
<FileIcon class="file" v-else />
|
<IconsFile class="icon" v-else-if="item.content_type === 'files'" />
|
||||||
|
<IconsText class="icon" v-else-if="item.content_type === 'text'" />
|
||||||
|
<IconsCode class="icon" v-else-if="item.content_type === 'code'" />
|
||||||
<span v-if="item.content_type === 'image'">Image ({{ item.dimensions || 'Loading...' }})</span>
|
<span v-if="item.content_type === 'image'">Image ({{ item.dimensions || 'Loading...' }})</span>
|
||||||
<span v-else>{{ truncateContent(item.content) }}</span>
|
<span v-else>{{ truncateContent(item.content) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -132,13 +132,14 @@ body,
|
||||||
width: 20px;
|
width: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.favicon-image {
|
.image {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file {
|
.icon {
|
||||||
margin-inline: 2px;
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
<template>
|
|
||||||
<img src="/file.svg" alt="">
|
|
||||||
</template>
|
|
3
components/Icons/Code.vue
Normal file
3
components/Icons/Code.vue
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<img src="/Code.svg" alt="">
|
||||||
|
</template>
|
3
components/Icons/File.vue
Normal file
3
components/Icons/File.vue
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<img src="/File.svg" alt="">
|
||||||
|
</template>
|
3
components/Icons/Image.vue
Normal file
3
components/Icons/Image.vue
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<img src="/Image.svg" alt="">
|
||||||
|
</template>
|
3
components/Icons/Text.vue
Normal file
3
components/Icons/Text.vue
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<img src="/Text.svg" alt="">
|
||||||
|
</template>
|
7
public/Code.svg
Normal file
7
public/Code.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg width="15px" height="18px" viewBox="0 0 15 18" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Code" fill-opacity="1">
|
||||||
|
<path d="M15 0L15 0L15 18L0 18L0 0L15 0Z" id="Code" fill="none" stroke="none" />
|
||||||
|
<path d="M3.75 16.0714L11.25 16.0714C12.2855 16.0714 13.125 15.208 13.125 14.1429L13.125 8.02672C13.1248 7.5147 12.9269 7.02399 12.5748 6.66239L8.52375 2.493C8.17225 2.13169 7.69568 1.92868 7.19875 1.92857L3.75 1.92857C2.71447 1.92857 1.875 2.79202 1.875 3.85714L1.875 14.1429C1.875 15.208 2.71447 16.0714 3.75 16.0714M15 8.02672C15.0003 7.00424 14.6053 6.02271 13.9018 5.29904L9.85 1.13143C9.1465 0.406921 8.19178 -0.000123228 7.19625 0L3.75 0C1.67893 0 0 1.7269 0 3.85714L0 14.1429C2.98023e-07 16.2731 1.67893 18 3.75 18L11.25 18C13.3211 18 15 16.2731 15 14.1429L15 8.02672ZM8.4 12.2529C8.03443 11.8764 8.03443 11.2665 8.4 10.89L9.6125 9.64286L8.4 8.39571C8.0558 8.01577 8.06595 7.4237 8.42297 7.05648C8.77999 6.68927 9.35561 6.67882 9.725 7.03286L11.6 8.96143C11.9656 9.33791 11.9656 9.94781 11.6 10.3243L9.725 12.2529C9.35898 12.6289 8.76602 12.6289 8.4 12.2529M6.6 8.39571C6.9442 8.01577 6.93404 7.4237 6.57703 7.05649C6.22001 6.68927 5.64439 6.67882 5.275 7.03286L3.4 8.96143C3.03443 9.33791 3.03443 9.94781 3.4 10.3243L5.275 12.2529C5.50871 12.5108 5.86069 12.617 6.19286 12.5298C6.52502 12.4425 6.7844 12.1757 6.86923 11.8341C6.95406 11.4924 6.85081 11.1304 6.6 10.89L5.3875 9.64286L6.6 8.39571Z" id="Shape" fill="#E5DFD5" fill-rule="evenodd" stroke="none" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
7
public/Image.svg
Normal file
7
public/Image.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Image" fill-opacity="1">
|
||||||
|
<path d="M18 0L18 0L18 18L0 18L0 0L18 0Z" id="Image" fill="none" stroke="none" />
|
||||||
|
<path d="M13.8462 2.07692L4.15385 2.07692C3.00679 2.07692 2.07692 3.00679 2.07692 4.15385L2.07692 11.1143L3.40892 10.1451C4.26934 9.51991 5.43685 9.5289 6.28754 10.1672L7.57246 11.1309L10.8512 8.32016C11.7843 7.52111 13.1676 7.54669 14.0705 8.37969L15.9231 10.0897L15.9231 4.15385C15.9231 3.00679 14.9932 2.07692 13.8462 2.07692M18 12.4588L18 4.15385C18 1.85974 16.1403 0 13.8462 0L4.15385 0C1.85974 0 0 1.85974 0 4.15385L0 13.8462C3.30118e-07 16.1403 1.85974 18 4.15385 18L13.8462 18C16.1403 18 18 16.1403 18 13.8462L18 12.4588ZM15.9231 12.9157L12.6623 9.90554C12.5333 9.78671 12.3358 9.78314 12.2026 9.89723L8.29108 13.2508L7.65831 13.7935L6.99231 13.2937L5.04 11.8302C4.91867 11.7398 4.75269 11.7386 4.63015 11.8274L2.07692 13.6814L2.07692 13.8462C2.07692 14.9932 3.00679 15.9231 4.15385 15.9231L13.8462 15.9231C14.9932 15.9231 15.9231 14.9932 15.9231 13.8462L15.9231 12.9157ZM8.30769 6.23077C8.30769 7.37782 7.37782 8.30769 6.23077 8.30769C5.08372 8.30769 4.15385 7.37782 4.15385 6.23077C4.15385 5.08372 5.08372 4.15385 6.23077 4.15385C7.37782 4.15385 8.30769 5.08372 8.30769 6.23077" id="Shape" fill="#E5DFD5" fill-rule="evenodd" stroke="none" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
7
public/Text.svg
Normal file
7
public/Text.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg width="15px" height="18px" viewBox="0 0 15 18" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Text" fill-opacity="1">
|
||||||
|
<path d="M15 0L15 0L15 18L0 18L0 0L15 0Z" id="Text" fill="none" stroke="none" />
|
||||||
|
<path d="M3.75 16.0714L11.25 16.0714C12.2855 16.0714 13.125 15.208 13.125 14.1429L13.125 8.02672C13.1248 7.5147 12.9269 7.02399 12.5748 6.66239L8.52375 2.493C8.17225 2.13169 7.69568 1.92868 7.19875 1.92857L3.75 1.92857C2.71447 1.92857 1.875 2.79202 1.875 3.85714L1.875 14.1429C1.875 15.208 2.71447 16.0714 3.75 16.0714M15 8.02672C15.0003 7.00424 14.6053 6.02271 13.9018 5.29904L9.85 1.13143C9.1465 0.406921 8.19178 -0.000123228 7.19625 0L3.75 0C1.67893 0 0 1.7269 0 3.85714L0 14.1429C2.98023e-07 16.2731 1.67893 18 3.75 18L11.25 18C13.3211 18 15 16.2731 15 14.1429L15 8.02672ZM3.75 9.32143C3.75 8.78887 4.16973 8.35714 4.6875 8.35714L10.3125 8.35714C10.8303 8.35714 11.25 8.78887 11.25 9.32143C11.25 9.85399 10.8303 10.2857 10.3125 10.2857L4.6875 10.2857C4.16973 10.2857 3.75 9.85399 3.75 9.32143M4.6875 12.2143C4.35256 12.2143 4.04307 12.3981 3.8756 12.6964C3.70813 12.9948 3.70813 13.3624 3.8756 13.6607C4.04307 13.9591 4.35256 14.1429 4.6875 14.1429L7.8125 14.1429C8.14744 14.1429 8.45693 13.9591 8.6244 13.6607C8.79187 13.3624 8.79187 12.9948 8.6244 12.6964C8.45693 12.3981 8.14744 12.2143 7.8125 12.2143L4.6875 12.2143Z" id="Shape" fill="#E5DFD5" fill-rule="evenodd" stroke="none" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -1,26 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg width="15px" height="18px" viewBox="0 0 15 18" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
|
<svg width="15px" height="18px" viewBox="0 0 15 18" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
|
||||||
<defs>
|
|
||||||
<path d="M2.99805 6.72744e-06L8.5 1.90735e-05L15 6.00002L15 15.002Q15 15.1493 14.9856 15.2958Q14.9711 15.4424 14.9424 15.5869Q14.9137 15.7313 14.8709 15.8723Q14.8282 16.0132 14.7718 16.1493Q14.7154 16.2854 14.646 16.4152Q14.5766 16.5451 14.4947 16.6676Q14.4129 16.7901 14.3195 16.9039Q14.226 17.0178 14.1219 17.1219Q14.0177 17.2261 13.9039 17.3195Q13.79 17.4129 13.6676 17.4948Q13.5451 17.5766 13.4152 17.646Q13.2853 17.7155 13.1493 17.7718Q13.0132 17.8282 12.8722 17.8709Q12.7313 17.9137 12.5868 17.9424Q12.4424 17.9712 12.2958 17.9856Q12.1492 18 12.002 18L2.99804 18Q2.85076 18 2.70418 17.9856Q2.55761 17.9711 2.41315 17.9424Q2.2687 17.9137 2.12776 17.8709Q1.98681 17.8282 1.85074 17.7718Q1.71467 17.7154 1.58477 17.646Q1.45488 17.5766 1.33242 17.4947Q1.20996 17.4129 1.0961 17.3195Q0.982251 17.226 0.878106 17.1219Q0.77396 17.0178 0.680524 16.9039Q0.587088 16.79 0.505261 16.6676Q0.423434 16.5451 0.354005 16.4152Q0.284575 16.2853 0.228212 16.1493Q0.171849 16.0132 0.129095 15.8722Q0.0863402 15.7313 0.0576065 15.5869Q0.0288728 15.4424 0.0144364 15.2958Q0 15.1492 0 15.002L0 2.99805Q0 2.85077 0.0144365 2.70419Q0.0288729 2.55762 0.0576068 2.41316Q0.0863407 2.26871 0.129095 2.12777Q0.17185 1.98682 0.228213 1.85075Q0.284577 1.71468 0.354007 1.58478Q0.423436 1.45489 0.505263 1.33243Q0.587091 1.20996 0.680527 1.09611Q0.773964 0.982258 0.87811 0.878112Q0.982256 0.773966 1.09611 0.68053Q1.20996 0.587093 1.33242 0.505266Q1.45489 0.423439 1.58478 0.35401Q1.71467 0.284581 1.85075 0.228217Q1.98682 0.171854 2.12776 0.1291Q2.26871 0.0863458 2.41316 0.0576122Q2.55762 0.0288786 2.70419 0.0144425Q2.85077 6.39694e-06 2.99805 6.72744e-06L2.99805 6.72744e-06Z" id="path_1" />
|
|
||||||
<path d="M7.5 1.61429e-06L8.49999 0L15 6L15 8L9.49902 8Q9.40082 8 9.30309 7.99038Q9.20535 7.98075 9.10903 7.96159Q9.01272 7.94243 8.91874 7.91393Q8.82476 7.88542 8.73403 7.84784Q8.6433 7.81026 8.55669 7.76396Q8.47008 7.71767 8.38842 7.66311Q8.30677 7.60855 8.23086 7.54625Q8.15494 7.48395 8.0855 7.4145Q8.01606 7.34506 7.95376 7.26915Q7.89146 7.19323 7.8369 7.11158Q7.78234 7.02992 7.73604 6.94331Q7.68975 6.8567 7.65217 6.76597Q7.61458 6.67524 7.58608 6.58127Q7.55757 6.48729 7.53841 6.39097Q7.51925 6.29465 7.50963 6.19692Q7.5 6.09919 7.5 6.00098L7.5 1.61429e-06Z" id="path_2" />
|
|
||||||
<clipPath id="clip_1">
|
|
||||||
<use xlink:href="#path_1" />
|
|
||||||
</clipPath>
|
|
||||||
<clipPath id="clip_2">
|
|
||||||
<use xlink:href="#path_2" />
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
<g id="File" fill-opacity="1">
|
<g id="File" fill-opacity="1">
|
||||||
<path d="M-134 -1664L-134 -1664L-134 -1646L-149 -1646L-149 -1664L-134 -1664Z" id="File" fill="none" stroke="none" />
|
<path d="M15 0L15 0L15 18L0 18L0 0L15 0Z" id="File" fill="none" stroke="none" />
|
||||||
<g id="Rectangle">
|
<path d="M11.25 16.0714L3.75 16.0714C2.71447 16.0714 1.875 15.208 1.875 14.1429L1.875 3.85714C1.875 2.79202 2.71447 1.92857 3.75 1.92857L6.25 1.92857L6.25 5.14286C6.25 7.2731 7.92893 9 10 9L13.125 9L13.125 14.1429C13.125 15.208 12.2855 16.0714 11.25 16.0714M12.8788 7.07143C12.7961 6.92188 12.6944 6.78437 12.5763 6.66257L8.5225 2.493C8.40408 2.37151 8.2704 2.26687 8.125 2.18186L8.125 5.14286C8.125 6.20798 8.96447 7.07143 10 7.07143L12.8788 7.07143ZM13.9013 5.29843C14.6049 6.02193 15.0001 7.00338 15 8.02672L15 14.1429C15 16.2731 13.3211 18 11.25 18L3.75 18C1.67893 18 0 16.2731 0 14.1429L0 3.85714C-5.96046e-07 1.7269 1.67893 0 3.75 0L7.19625 0C8.19116 -0.000122309 9.14535 0.406423 9.84875 1.13014L13.9013 5.29843Z" id="Shape" fill="#E5DFD5" fill-rule="evenodd" stroke="none" />
|
||||||
<g clip-path="url(#clip_1)">
|
|
||||||
<use xlink:href="#path_1" fill="none" stroke="#E5DFD5" stroke-width="4" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g id="Rectangle">
|
|
||||||
<g clip-path="url(#clip_2)">
|
|
||||||
<use xlink:href="#path_2" fill="none" stroke="#E5DFD5" stroke-width="4" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 1.1 KiB |
|
@ -1,22 +1,17 @@
|
||||||
use base64::Engine;
|
|
||||||
use base64::engine::general_purpose::STANDARD;
|
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 tauri_plugin_clipboard::Clipboard;
|
||||||
use tokio::runtime::Runtime as TokioRuntime;
|
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! {
|
lazy_static! {
|
||||||
static ref APP_DATA_DIR: Mutex<Option<std::path::PathBuf>> = Mutex::new(None);
|
static ref APP_DATA_DIR: Mutex<Option<std::path::PathBuf>> = Mutex::new(None);
|
||||||
|
@ -37,17 +32,30 @@ pub fn read_image(filename: String) -> Result<Vec<u8>, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn write_and_paste<R: Runtime>(app_handle: tauri::AppHandle<R>, content: String, content_type: String) -> Result<(), String> {
|
pub async fn write_and_paste<R: Runtime>(
|
||||||
|
app_handle: tauri::AppHandle<R>,
|
||||||
|
content: String,
|
||||||
|
content_type: String,
|
||||||
|
) -> Result<(), String> {
|
||||||
let clipboard = app_handle.state::<Clipboard>();
|
let clipboard = app_handle.state::<Clipboard>();
|
||||||
|
|
||||||
match content_type.as_str() {
|
match content_type.as_str() {
|
||||||
"text" => clipboard.write_text(content).map_err(|e| e.to_string())?,
|
"text" => clipboard.write_text(content).map_err(|e| e.to_string())?,
|
||||||
"image" => {
|
"image" => {
|
||||||
clipboard.write_image_base64(content).map_err(|e| e.to_string())?;
|
clipboard
|
||||||
},
|
.write_image_base64(content)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
"files" => {
|
"files" => {
|
||||||
clipboard.write_files_uris(content.split(", ").map(|file| file.to_string()).collect::<Vec<String>>()).map_err(|e| e.to_string())?;
|
clipboard
|
||||||
},
|
.write_files_uris(
|
||||||
|
content
|
||||||
|
.split(", ")
|
||||||
|
.map(|file| file.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
_ => return Err("Unsupported content type".to_string()),
|
_ => return Err("Unsupported content type".to_string()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +89,10 @@ fn simulate_paste() {
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_image_path(app_handle: tauri::AppHandle, filename: String) -> String {
|
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);
|
let image_path = app_data_dir.join("images").join(filename);
|
||||||
image_path.to_str().unwrap_or("").to_string()
|
image_path.to_str().unwrap_or("").to_string()
|
||||||
}
|
}
|
||||||
|
@ -90,61 +101,94 @@ pub fn setup<R: Runtime>(app: &AppHandle<R>) {
|
||||||
let app = app.clone();
|
let app = app.clone();
|
||||||
let runtime = TokioRuntime::new().expect("Failed to create Tokio runtime");
|
let runtime = TokioRuntime::new().expect("Failed to create Tokio runtime");
|
||||||
|
|
||||||
app.clone().listen("plugin:clipboard://clipboard-monitor/update", move |_event| {
|
app.clone().listen(
|
||||||
let app = app.clone();
|
"plugin:clipboard://clipboard-monitor/update",
|
||||||
runtime.block_on(async move {
|
move |_event| {
|
||||||
if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) {
|
let app = app.clone();
|
||||||
println!("Ignoring programmatic paste");
|
runtime.block_on(async move {
|
||||||
return;
|
if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) {
|
||||||
}
|
println!("Ignoring programmatic paste");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let clipboard = app.state::<Clipboard>();
|
let clipboard = app.state::<Clipboard>();
|
||||||
let available_types = clipboard.available_types().unwrap();
|
let available_types = clipboard.available_types().unwrap();
|
||||||
|
|
||||||
println!("Clipboard update detected");
|
println!("Clipboard update detected");
|
||||||
|
|
||||||
match get_pool(&app).await {
|
match get_pool(&app).await {
|
||||||
Ok(pool) => {
|
Ok(pool) => {
|
||||||
if available_types.image {
|
if available_types.image {
|
||||||
println!("Handling image change");
|
println!("Handling image change");
|
||||||
if let Ok(image_data) = clipboard.read_image_base64() {
|
if let Ok(image_data) = clipboard.read_image_base64() {
|
||||||
let base64_image = STANDARD.encode(&image_data);
|
let base64_image = STANDARD.encode(&image_data);
|
||||||
insert_content_if_not_exists(app.clone(), pool.clone(), "image", base64_image).await;
|
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 {
|
Err(e) => {
|
||||||
println!("Handling files change");
|
println!("Failed to get database pool: {}", e);
|
||||||
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);
|
},
|
||||||
}
|
);
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_pool<R: Runtime>(app_handle: &AppHandle<R>) -> Result<SqlitePool, Box<dyn std::error::Error + Send + Sync>> {
|
async fn get_pool<R: Runtime>(
|
||||||
let app_data_dir = app_handle.path().app_data_dir().expect("Failed to get app data directory");
|
app_handle: &AppHandle<R>,
|
||||||
|
) -> Result<SqlitePool, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
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 db_path = app_data_dir.join("data.db");
|
||||||
let database_url = format!("sqlite:{}", db_path.to_str().unwrap());
|
let database_url = format!("sqlite:{}", db_path.to_str().unwrap());
|
||||||
SqlitePool::connect(&database_url).await.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
|
SqlitePool::connect(&database_url)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn insert_content_if_not_exists<R: Runtime>(app_handle: AppHandle<R>, pool: SqlitePool, content_type: &str, content: String) {
|
async fn insert_content_if_not_exists<R: Runtime>(
|
||||||
|
app_handle: AppHandle<R>,
|
||||||
|
pool: SqlitePool,
|
||||||
|
content_type: &str,
|
||||||
|
content: String,
|
||||||
|
) {
|
||||||
let last_content: Option<String> = sqlx::query_scalar(
|
let last_content: Option<String> = sqlx::query_scalar(
|
||||||
"SELECT content FROM history WHERE content_type = ? ORDER BY timestamp DESC LIMIT 1",
|
"SELECT content FROM history WHERE content_type = ? ORDER BY timestamp DESC LIMIT 1",
|
||||||
)
|
)
|
||||||
|
@ -172,21 +216,25 @@ async fn insert_content_if_not_exists<R: Runtime>(app_handle: AppHandle<R>, pool
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
.collect();
|
.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" {
|
||||||
let favicon_base64 = if content_type == "text" && url_regex.is_match(&content) {
|
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();
|
||||||
match url::Url::parse(&content) {
|
if url_regex.is_match(&content) {
|
||||||
Ok(url) => match fetch_favicon_as_base64(url).await {
|
match url::Url::parse(&content) {
|
||||||
Ok(Some(favicon)) => Some(favicon),
|
Ok(url) => match fetch_favicon_as_base64(url).await {
|
||||||
Ok(None) => None,
|
Ok(Some(favicon)) => Some(favicon),
|
||||||
|
Ok(None) => None,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to fetch favicon: {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Failed to fetch favicon: {}", e);
|
println!("Failed to parse URL: {}", e);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
println!("Failed to parse URL: {}", e);
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -204,14 +252,20 @@ async fn insert_content_if_not_exists<R: Runtime>(app_handle: AppHandle<R>, pool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn save_image<R: Runtime>(app_handle: &AppHandle<R>, base64_image: &str) -> Result<String, Box<dyn std::error::Error>> {
|
async fn save_image<R: Runtime>(
|
||||||
|
app_handle: &AppHandle<R>,
|
||||||
|
base64_image: &str,
|
||||||
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
let image_data = STANDARD.decode(base64_image)?;
|
let image_data = STANDARD.decode(base64_image)?;
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(&image_data);
|
hasher.update(&image_data);
|
||||||
let hash = hasher.finalize();
|
let hash = hasher.finalize();
|
||||||
let filename = format!("{:x}.png", hash);
|
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 images_dir = app_data_dir.join("images");
|
||||||
let path = images_dir.join(&filename);
|
let path = images_dir.join(&filename);
|
||||||
|
|
||||||
|
@ -223,7 +277,9 @@ async fn save_image<R: Runtime>(app_handle: &AppHandle<R>, base64_image: &str) -
|
||||||
Ok(path.to_str().unwrap().to_string())
|
Ok(path.to_str().unwrap().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_favicon_as_base64(url: url::Url) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
async fn fetch_favicon_as_base64(
|
||||||
|
url: url::Url,
|
||||||
|
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let favicon_url = format!("https://favicone.com/{}", url.host_str().unwrap());
|
let favicon_url = format!("https://favicone.com/{}", url.host_str().unwrap());
|
||||||
let response = client.get(&favicon_url).send().await?;
|
let response = client.get(&favicon_url).send().await?;
|
||||||
|
@ -242,7 +298,11 @@ async fn fetch_favicon_as_base64(url: url::Url) -> Result<Option<String>, Box<dy
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn start_monitor(app_handle: AppHandle) -> Result<(), String> {
|
pub fn start_monitor(app_handle: AppHandle) -> Result<(), String> {
|
||||||
let clipboard = app_handle.state::<Clipboard>();
|
let clipboard = app_handle.state::<Clipboard>();
|
||||||
clipboard.start_monitor(app_handle.clone()).map_err(|e| e.to_string())?;
|
clipboard
|
||||||
app_handle.emit("plugin:clipboard://clipboard-monitor/status", true).map_err(|e| e.to_string())?;
|
.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(())
|
Ok(())
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue