fixed display of images

This commit is contained in:
PandaDEV 2024-08-27 15:38:01 +10:00
parent fb535a4521
commit fd4ce395f5
No known key found for this signature in database
GPG key ID: 13EFF9BAF70EE75C
5 changed files with 84 additions and 63 deletions

103
app.vue
View file

@ -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;

View file

@ -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"
] ]
} }

View file

@ -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;
} }

View file

@ -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;

View file

@ -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)?;