mirror of
https://codeberg.org/PLG-Development/PLG-MuDiCS
synced 2026-07-06 00:47:09 +00:00
add file thumbnails
This commit is contained in:
Generated
+7
@@ -9,6 +9,7 @@
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@thisux/sveltednd": "^0.0.20",
|
||||
"dexie": "^4.2.1",
|
||||
"lucide-svelte": "^0.545.0",
|
||||
"svelte-splitpanes": "^8.0.9"
|
||||
},
|
||||
@@ -2136,6 +2137,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dexie": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dexie/-/dexie-4.2.1.tgz",
|
||||
"integrity": "sha512-Ckej0NS6jxQ4Po3OrSQBFddayRhTCic2DoCAG5zacOfOVB9P2Q5Xc5uL/nVa7ZVs+HdMnvUPzLFCB/JwpB6Csg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.3",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@thisux/sveltednd": "^0.0.20",
|
||||
"dexie": "^4.2.1",
|
||||
"lucide-svelte": "^0.545.0",
|
||||
"svelte-splitpanes": "^8.0.9"
|
||||
}
|
||||
|
||||
@@ -36,9 +36,23 @@
|
||||
run_on_all_selected_displays,
|
||||
update_screenshot
|
||||
} from '../ts/stores/displays';
|
||||
import { get_thumbnail_url } from '../ts/stores/thumbnails';
|
||||
import { db } from '../ts/indexdb/file_thumbnails.db';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { liveQuery } from 'dexie';
|
||||
|
||||
let { file } = $props<{ file: FolderElement }>();
|
||||
|
||||
let thumbnail_url: string | null = $state(null);
|
||||
// Update thumbnail_url automatically if data is available
|
||||
const subscription = liveQuery(() => db.thumbnail_blobs.get(file.hash)).subscribe({
|
||||
next: async () => {
|
||||
thumbnail_url = await get_thumbnail_url(file.hash);
|
||||
},
|
||||
error: (err) => console.error('Dexie subscription error:', err)
|
||||
});
|
||||
onDestroy(() => subscription.unsubscribe());
|
||||
|
||||
const is_folder = file.type === 'inode/directory';
|
||||
|
||||
function get_file_type(file: FolderElement): SupportedFileType | null {
|
||||
@@ -168,16 +182,23 @@
|
||||
})} rounded-r-lg transition-colors duration-200 gap-4 flex flex-row justify-between cursor-pointer group w-full min-w-0"
|
||||
>
|
||||
<div class="flex flex-row gap-2 min-w-0 w-full">
|
||||
<div class="aspect-square rounded-md flex justify-center items-center p-2">
|
||||
<div
|
||||
class="aspect-square rounded-md flex justify-center items-center"
|
||||
>
|
||||
{#if is_folder}
|
||||
<Folder class="size-full" />
|
||||
{:else if file.thumbnail}
|
||||
<div></div>
|
||||
<Folder class="size-full p-2" />
|
||||
{:else if thumbnail_url}
|
||||
<img
|
||||
src={thumbnail_url}
|
||||
alt="file_thumbnail"
|
||||
class="object-contain size-full select-none block"
|
||||
draggable="false"
|
||||
/>
|
||||
{:else if get_file_type(file)?.icon}
|
||||
{@const Icon = get_file_type(file)?.icon}
|
||||
<Icon class="size-full" />
|
||||
<Icon class="size-full p-2" />
|
||||
{:else}
|
||||
<FileIcon />
|
||||
<FileIcon class="size-full p-2" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="content-center truncate select-none w-full" title={file.name}>
|
||||
|
||||
@@ -201,6 +201,7 @@ module.exports = {
|
||||
'group-active:border-stone-900',
|
||||
'group-active:border-stone-950',
|
||||
|
||||
'h-10',
|
||||
'h-15',
|
||||
'h-20',
|
||||
'h-25',
|
||||
|
||||
@@ -2,7 +2,7 @@ import { notifications } from "./stores/notification";
|
||||
import { to_display_status, type DisplayStatus, type FolderElement, type TreeElement } from "./types";
|
||||
import { get_uuid } from "./utils";
|
||||
|
||||
export async function get_screenshot(ip: string) {
|
||||
export async function get_screenshot(ip: string): Promise<Blob|null> {
|
||||
const options = { method: 'PATCH' };
|
||||
return await request_display(ip, '/takeScreenshot', options);
|
||||
}
|
||||
@@ -63,8 +63,7 @@ done
|
||||
if (response_element.name.charAt(2) !== '.') {
|
||||
const folder_element: FolderElement = {
|
||||
id: get_uuid(),
|
||||
hash: JSON.stringify(response),
|
||||
thumbnail_url: null,
|
||||
hash: JSON.stringify(response_element),
|
||||
name: response_element.name.slice(2), // remove "./"
|
||||
type: response_element.type,
|
||||
date_created: new Date(response_element.created),
|
||||
@@ -111,6 +110,11 @@ export async function ping_ip(ip: string): Promise<DisplayStatus> {
|
||||
return raw_response.status ? to_display_status(raw_response.status) : null;
|
||||
}
|
||||
|
||||
export async function get_thumbnail_blob(ip: string, path_to_file: string): Promise<Blob|null> {
|
||||
const raw_response = await request_display(ip, `/file/preview${path_to_file}`, { method: 'GET' });
|
||||
return raw_response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import Dexie, { type EntityTable } from "dexie";
|
||||
|
||||
export interface ThumbnailBlobDBEntry {
|
||||
hash: string;
|
||||
blob: Blob;
|
||||
}
|
||||
|
||||
export class FileDatabase extends Dexie {
|
||||
thumbnail_blobs!: EntityTable<ThumbnailBlobDBEntry, "hash">; // (Type, PrimaryKey)
|
||||
|
||||
constructor() {
|
||||
super("FileDatabase");
|
||||
|
||||
this.version(1).stores({
|
||||
thumbnail_blobs: "++hash, blob" // primary key
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const db = new FileDatabase();
|
||||
@@ -3,10 +3,12 @@ import { displays, run_on_all_selected_displays, update_displays_with_map, updat
|
||||
import { ping_ip } from "./api_handler";
|
||||
import type { Display } from "./types";
|
||||
import { change_file_path, update_folder_elements_recursively } from "./stores/files";
|
||||
import { db } from "./indexdb/file_thumbnails.db";
|
||||
|
||||
const update_display_status_interval_seconds = 20;
|
||||
|
||||
export async function on_start() {
|
||||
await db.thumbnail_blobs.clear();
|
||||
await update_all_display_status();
|
||||
await setInterval(update_all_display_status, update_display_status_interval_seconds * 1000);
|
||||
|
||||
|
||||
@@ -109,6 +109,13 @@ export async function update_screenshot(display_id: string, check_type: "first_c
|
||||
const display_ip = get_display_by_id(display_id, get(displays))?.ip;
|
||||
if (!display_ip) return;
|
||||
const new_blob = await get_screenshot(display_ip);
|
||||
if (!new_blob) {
|
||||
update_displays_with_map((display: Display) => {
|
||||
if (display.id !== display_id) return display;
|
||||
return { ...display, preview_url: null, preview_timeout_id: null };
|
||||
})
|
||||
return;
|
||||
}
|
||||
const display = get_display_by_id(display_id, get(displays));
|
||||
|
||||
let update_needed = check_type === "first_check";
|
||||
|
||||
@@ -5,6 +5,7 @@ import { selected_file_ids } from "./select";
|
||||
import { get_file_data, get_file_tree_data } from "../api_handler";
|
||||
import { notifications } from "./notification";
|
||||
import { CirclePoundSterling } from "lucide-svelte";
|
||||
import { deactivate_old_thumbnail_urls, generate_thumbnail } from "./thumbnails";
|
||||
|
||||
export const all_files: Writable<Record<string, Record<string, FolderElement[]>>> = writable<Record<string, Record<string, FolderElement[]>>>({});
|
||||
// {
|
||||
@@ -30,6 +31,8 @@ export async function change_file_path(new_path: string) {
|
||||
return [];
|
||||
})
|
||||
|
||||
deactivate_old_thumbnail_urls();
|
||||
|
||||
for (const display_group of get(displays)) {
|
||||
for (const display of display_group.data) {
|
||||
const changed_paths = await get_changed_directory_paths(display, new_path);
|
||||
@@ -111,6 +114,13 @@ export async function update_folder_elements_recursively(display: Display, file_
|
||||
const existing_folder_elements = files[file_path].hasOwnProperty(display.id) ? files[file_path][display.id] : [];
|
||||
|
||||
const diff = get_folder_elements_difference(existing_folder_elements, new_folder_elements);
|
||||
// Generate Thumbnails:
|
||||
setTimeout(async () => {
|
||||
for (const folder_element of diff.new) {
|
||||
await generate_thumbnail(display.ip, file_path, folder_element);
|
||||
}
|
||||
}, 0)
|
||||
|
||||
files[file_path][display.id].push(...diff.new);
|
||||
return remove_folder_elements_recursively(files, display, diff.deleted, file_path);
|
||||
})
|
||||
@@ -171,6 +181,7 @@ function get_folder_elements_difference(old_elements: FolderElement[], new_eleme
|
||||
|
||||
|
||||
|
||||
|
||||
// export function updates_files_on_display(display_id: string, new_folder_elements: FolderElement[], file_path: string) {
|
||||
// all_files.update((files) => {
|
||||
// if (!files.hasOwnProperty(file_path)) {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import { get_thumbnail_blob } from "../api_handler";
|
||||
import { supported_file_types, type FolderElement } from "../types";
|
||||
import { db } from "../indexdb/file_thumbnails.db";
|
||||
|
||||
export const active_thumbnail_urls: Writable<string[]> = writable<string[]>([]);
|
||||
|
||||
export async function generate_thumbnail(display_ip: string, path: string, folder_element: FolderElement): Promise<void> {
|
||||
if (!Object.values(supported_file_types).some(e => e.mime_type === folder_element.type) || !folder_element.hash) return;
|
||||
const hash: string = folder_element.hash;
|
||||
if (await db.thumbnail_blobs.get(hash)) return;
|
||||
|
||||
const thumbnail_blob = await get_thumbnail_blob(display_ip, path + folder_element.name);
|
||||
if (!thumbnail_blob) return;
|
||||
await db.thumbnail_blobs.add({ hash: hash, blob: thumbnail_blob });
|
||||
}
|
||||
|
||||
export async function get_thumbnail_url(hash: string): Promise<string | null> {
|
||||
if (hash === null) return null;
|
||||
const thumbnail_blob = await db.thumbnail_blobs.get(hash);
|
||||
if (!thumbnail_blob) return null;
|
||||
const new_url = URL.createObjectURL(thumbnail_blob.blob);
|
||||
active_thumbnail_urls.update((current: string[]) => {
|
||||
current.push(new_url);
|
||||
return current;
|
||||
})
|
||||
return new_url;
|
||||
}
|
||||
|
||||
export function deactivate_old_thumbnail_urls() {
|
||||
const current_urls = get(active_thumbnail_urls);
|
||||
for (const url of current_urls) {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
active_thumbnail_urls.update(() => { return []; })
|
||||
}
|
||||
@@ -48,7 +48,6 @@ export const supported_file_types: Record<string, SupportedFileType> = {
|
||||
export type FolderElement = {
|
||||
id?: string;
|
||||
hash: string | null;
|
||||
thumbnail_url: string | null;
|
||||
name: string;
|
||||
type: string;
|
||||
date_created: Date;
|
||||
|
||||
Reference in New Issue
Block a user