mirror of
https://github.com/0PandaDEV/Qopy.git
synced 2025-04-21 13:14:04 +02:00
fixed display of images
This commit is contained in:
parent
fb535a4521
commit
fd4ce395f5
5 changed files with 84 additions and 63 deletions
103
app.vue
103
app.vue
|
@ -33,10 +33,15 @@
|
||||||
@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 }">
|
||||||
<template v-if="item.content_type === 'image'">
|
<template v-if="item.content_type === 'image'">
|
||||||
<img :src="getComputedImageUrl(item)" alt="Image" class="image" @error="onImageError">
|
<img v-if="!imageLoading && !imageLoadError"
|
||||||
<IconsImage v-show="imageLoadError" class="icon" />
|
:src="getComputedImageUrl(item)"
|
||||||
|
alt="Image"
|
||||||
|
class="image"
|
||||||
|
@error="onImageError">
|
||||||
|
<IconsImage v-if="imageLoading || imageLoadError" class="icon" />
|
||||||
</template>
|
</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">
|
||||||
<IconsFile class="icon" v-else-if="item.content_type === 'files'" />
|
<IconsFile class="icon" v-else-if="item.content_type === 'files'" />
|
||||||
<IconsText class="icon" v-else-if="item.content_type === 'text'" />
|
<IconsText class="icon" v-else-if="item.content_type === 'text'" />
|
||||||
<IconsCode class="icon" v-else-if="item.content_type === 'code'" />
|
<IconsCode class="icon" v-else-if="item.content_type === 'code'" />
|
||||||
|
@ -49,8 +54,8 @@
|
||||||
<img :src="getComputedImageUrl(selectedItem)" alt="Image" class="image">
|
<img :src="getComputedImageUrl(selectedItem)" alt="Image" class="image">
|
||||||
</div>
|
</div>
|
||||||
<OverlayScrollbarsComponent v-else class="content">
|
<OverlayScrollbarsComponent v-else class="content">
|
||||||
<img v-if="selectedItem?.content && isYoutubeWatchUrl(selectedItem.content)" :src="getYoutubeThumbnail(selectedItem.content)"
|
<img v-if="selectedItem?.content && isYoutubeWatchUrl(selectedItem.content)"
|
||||||
alt="YouTube Thumbnail" class="full-image">
|
:src="getYoutubeThumbnail(selectedItem.content)" alt="YouTube Thumbnail" class="full-image">
|
||||||
<span v-else>{{ selectedItem?.content || '' }}</span>
|
<span v-else>{{ selectedItem?.content || '' }}</span>
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
<Noise />
|
<Noise />
|
||||||
|
@ -95,6 +100,9 @@ const selectedItemIndex: Ref<number> = ref(0);
|
||||||
const selectedElement: Ref<HTMLElement | null> = ref(null);
|
const selectedElement: Ref<HTMLElement | null> = ref(null);
|
||||||
const searchInput: Ref<HTMLInputElement | null> = ref(null);
|
const searchInput: Ref<HTMLInputElement | null> = ref(null);
|
||||||
const os: Ref<string> = ref('');
|
const os: Ref<string> = ref('');
|
||||||
|
const imageLoadError = ref(false);
|
||||||
|
const imageLoading = ref(true);
|
||||||
|
const imageUrls: Ref<Record<number, string>> = shallowRef({});
|
||||||
|
|
||||||
const groupedHistory: ComputedRef<GroupedHistory[]> = computed(() => {
|
const groupedHistory: ComputedRef<GroupedHistory[]> = computed(() => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
@ -255,52 +263,74 @@ const getFaviconFromDb = (favicon: string): string => {
|
||||||
const getImageDimensions = (path: string): Promise<string> => {
|
const getImageDimensions = (path: string): Promise<string> => {
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.onload = () => resolve(`${img.width}x${img.height}`);
|
img.onload = () => {
|
||||||
img.onerror = () => resolve('0x0');
|
imageLoadError.value = false;
|
||||||
if (path.includes('AppData\\Roaming\\net.pandadev.qopy\\images\\')) {
|
imageLoading.value = false;
|
||||||
const filename = path.split('\\').pop();
|
resolve(`${img.width}x${img.height}`);
|
||||||
try {
|
};
|
||||||
const imageData = await invoke<Uint8Array>("read_image", { filename: filename });
|
img.onerror = (e) => {
|
||||||
const blob = new Blob([imageData], { type: 'image/png' });
|
console.error('Error loading image:', e);
|
||||||
img.src = URL.createObjectURL(blob);
|
imageLoadError.value = true;
|
||||||
} catch (error) {
|
imageLoading.value = false;
|
||||||
console.error('Error reading image file:', error);
|
resolve('0x0');
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
imageLoading.value = true;
|
||||||
|
const dataUrl = await getImageUrl(path);
|
||||||
|
img.src = dataUrl;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting image URL:', error);
|
||||||
|
imageLoadError.value = true;
|
||||||
|
imageLoading.value = false;
|
||||||
resolve('0x0');
|
resolve('0x0');
|
||||||
}
|
|
||||||
} else {
|
|
||||||
img.src = `data:image/png;base64,${path}`;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const imageUrls: Ref<Record<number, string>> = shallowRef({});
|
const getImageUrl = async (path: string): Promise<string> => {
|
||||||
|
const isWindows = path.includes('\\');
|
||||||
|
const separator = isWindows ? '\\' : '/';
|
||||||
|
const filename = path.split(separator).pop();
|
||||||
|
|
||||||
|
try {
|
||||||
|
imageLoading.value = true;
|
||||||
|
const base64 = await invoke<string>("read_image", { filename });
|
||||||
|
console.log('Image data received, length:', base64.length);
|
||||||
|
if (!base64 || base64.length === 0) {
|
||||||
|
throw new Error('Received empty image data');
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataUrl = `data:image/png;base64,${base64}`;
|
||||||
|
|
||||||
|
console.log('Data URL preview:', dataUrl.substring(0, 50) + '...');
|
||||||
|
|
||||||
|
imageLoadError.value = false;
|
||||||
|
imageLoading.value = false;
|
||||||
|
return dataUrl;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reading image file:', error);
|
||||||
|
imageLoadError.value = true;
|
||||||
|
imageLoading.value = false;
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getComputedImageUrl = (item: HistoryItem): string => {
|
const getComputedImageUrl = (item: HistoryItem): string => {
|
||||||
if (!imageUrls.value[item.id]) {
|
if (!imageUrls.value[item.id]) {
|
||||||
imageUrls.value[item.id] = '';
|
imageUrls.value[item.id] = '';
|
||||||
getImageUrl(item.content).then(url => {
|
getImageUrl(item.content)
|
||||||
|
.then(url => {
|
||||||
imageUrls.value = { ...imageUrls.value, [item.id]: url };
|
imageUrls.value = { ...imageUrls.value, [item.id]: url };
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Failed to get image URL:', error);
|
||||||
|
imageUrls.value = { ...imageUrls.value, [item.id]: '' };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return imageUrls.value[item.id] || '';
|
return imageUrls.value[item.id] || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getImageUrl = async (path: string): Promise<string> => {
|
|
||||||
if (path.includes('AppData\\Roaming\\net.pandadev.qopy\\images\\')) {
|
|
||||||
const filename = path.split('\\').pop();
|
|
||||||
try {
|
|
||||||
const imageData = await invoke<Uint8Array>("read_image", { filename: filename });
|
|
||||||
const blob = new Blob([imageData], { type: 'image/png' });
|
|
||||||
return URL.createObjectURL(blob);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error reading image file:', error);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return `data:image/png;base64,${path}`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadHistoryChunk = async (): Promise<void> => {
|
const loadHistoryChunk = async (): Promise<void> => {
|
||||||
if (!db.value || isLoading) return;
|
if (!db.value || isLoading) return;
|
||||||
|
|
||||||
|
@ -328,6 +358,7 @@ const loadHistoryChunk = async (): Promise<void> => {
|
||||||
const processedChunk = await Promise.all(results.map(async item => {
|
const processedChunk = await Promise.all(results.map(async item => {
|
||||||
if (item.content_type === 'image') {
|
if (item.content_type === 'image') {
|
||||||
const dimensions = await getImageDimensions(item.content);
|
const dimensions = await getImageDimensions(item.content);
|
||||||
|
getComputedImageUrl(item);
|
||||||
return { ...item, dimensions };
|
return { ...item, dimensions };
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
"core:window:allow-show",
|
"core:window:allow-show",
|
||||||
"core:window:allow-set-focus",
|
"core:window:allow-set-focus",
|
||||||
"core:window:allow-is-focused",
|
"core:window:allow-is-focused",
|
||||||
"core:window:allow-is-visible"
|
"core:window:allow-is-visible",
|
||||||
|
"fs:allow-read"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -24,11 +24,12 @@ pub fn set_app_data_dir(path: std::path::PathBuf) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn read_image(filename: String) -> Result<Vec<u8>, String> {
|
pub fn read_image(filename: String) -> Result<String, String> {
|
||||||
let app_data_dir = APP_DATA_DIR.lock().unwrap();
|
let app_data_dir = APP_DATA_DIR.lock().unwrap();
|
||||||
let app_data_dir = app_data_dir.as_ref().expect("App data directory not set");
|
let app_data_dir = app_data_dir.as_ref().expect("App data directory not set");
|
||||||
let image_path = app_data_dir.join("images").join(filename);
|
let image_path = app_data_dir.join("images").join(filename);
|
||||||
fs::read(image_path).map_err(|e| e.to_string())
|
let image_data = fs::read(image_path).map_err(|e| e.to_string())?;
|
||||||
|
Ok(STANDARD.encode(image_data))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
@ -107,26 +108,22 @@ pub fn setup<R: Runtime>(app: &AppHandle<R>) {
|
||||||
let app = app.clone();
|
let app = app.clone();
|
||||||
runtime.block_on(async move {
|
runtime.block_on(async move {
|
||||||
if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) {
|
if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) {
|
||||||
println!("Ignoring programmatic paste");
|
|
||||||
return;
|
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");
|
|
||||||
|
|
||||||
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);
|
|
||||||
insert_content_if_not_exists(
|
insert_content_if_not_exists(
|
||||||
app.clone(),
|
app.clone(),
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
"image",
|
"image",
|
||||||
base64_image,
|
image_data,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use tauri::Manager;
|
||||||
|
|
||||||
use crate::utils::commands::center_window_on_current_monitor;
|
use crate::utils::commands::center_window_on_current_monitor;
|
||||||
|
|
||||||
|
#[warn(dead_code)]
|
||||||
pub fn setup(app_handle: tauri::AppHandle) {
|
pub fn setup(app_handle: tauri::AppHandle) {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let mut meta_pressed = false;
|
let mut meta_pressed = false;
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use tauri::{
|
use tauri::{
|
||||||
menu::{MenuBuilder, MenuItemBuilder},
|
menu::{MenuBuilder, MenuItemBuilder},
|
||||||
tray::{MouseButton, TrayIconBuilder, TrayIconEvent},
|
tray::TrayIconBuilder,
|
||||||
Manager,
|
Manager,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let window = app.get_webview_window("main").unwrap();
|
let window = app.get_webview_window("main").unwrap();
|
||||||
let window_clone_for_tray = window.clone();
|
let window_clone_for_tray = window.clone();
|
||||||
let window_clone_for_click = window.clone();
|
|
||||||
|
|
||||||
let icon_bytes = include_bytes!("../../icons/Square71x71Logo.png");
|
let icon_bytes = include_bytes!("../../icons/Square71x71Logo.png");
|
||||||
let icon = tauri::image::Image::from_bytes(icon_bytes).unwrap();
|
let icon = tauri::image::Image::from_bytes(icon_bytes).unwrap();
|
||||||
|
@ -37,14 +36,6 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
})
|
})
|
||||||
// .on_tray_icon_event(move |_tray, event| {
|
|
||||||
// if let TrayIconEvent::Click { button, .. } = event {
|
|
||||||
// if button == MouseButton::Left {
|
|
||||||
// window_clone_for_click.show().unwrap();
|
|
||||||
// window_clone_for_click.set_focus().unwrap();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
.icon(icon)
|
.icon(icon)
|
||||||
.build(app)?;
|
.build(app)?;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue