+
{#if is_folder}
-
- {:else if file.thumbnail}
-
+
+ {:else if thumbnail_url}
+

{:else if get_file_type(file)?.icon}
{@const Icon = get_file_type(file)?.icon}
-
+
{:else}
-
+
{/if}
diff --git a/control/frontend/src/tailwind.config.js b/control/frontend/src/tailwind.config.js
index 22263da..717cdf2 100644
--- a/control/frontend/src/tailwind.config.js
+++ b/control/frontend/src/tailwind.config.js
@@ -201,6 +201,7 @@ module.exports = {
'group-active:border-stone-900',
'group-active:border-stone-950',
+ 'h-10',
'h-15',
'h-20',
'h-25',
diff --git a/control/frontend/src/ts/api_handler.ts b/control/frontend/src/ts/api_handler.ts
index 45f514b..3b865bd 100644
--- a/control/frontend/src/ts/api_handler.ts
+++ b/control/frontend/src/ts/api_handler.ts
@@ -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 {
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 {
return raw_response.status ? to_display_status(raw_response.status) : null;
}
+export async function get_thumbnail_blob(ip: string, path_to_file: string): Promise {
+ const raw_response = await request_display(ip, `/file/preview${path_to_file}`, { method: 'GET' });
+ return raw_response;
+}
+
diff --git a/control/frontend/src/ts/indexdb/file_thumbnails.db.ts b/control/frontend/src/ts/indexdb/file_thumbnails.db.ts
new file mode 100644
index 0000000..d1d0771
--- /dev/null
+++ b/control/frontend/src/ts/indexdb/file_thumbnails.db.ts
@@ -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; // (Type, PrimaryKey)
+
+ constructor() {
+ super("FileDatabase");
+
+ this.version(1).stores({
+ thumbnail_blobs: "++hash, blob" // primary key
+ });
+ }
+}
+
+export const db = new FileDatabase();
diff --git a/control/frontend/src/ts/main.ts b/control/frontend/src/ts/main.ts
index 0193427..757f1ca 100644
--- a/control/frontend/src/ts/main.ts
+++ b/control/frontend/src/ts/main.ts
@@ -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);
@@ -28,5 +30,5 @@ async function update_all_display_status() {
async function on_display_start(display: Display) {
await update_folder_elements_recursively(display, '/');
- await update_screenshot(display.id);
+ await update_screenshot(display.id);
}
\ No newline at end of file
diff --git a/control/frontend/src/ts/stores/displays.ts b/control/frontend/src/ts/stores/displays.ts
index bc0e30c..9422cdb 100644
--- a/control/frontend/src/ts/stores/displays.ts
+++ b/control/frontend/src/ts/stores/displays.ts
@@ -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";
diff --git a/control/frontend/src/ts/stores/files.ts b/control/frontend/src/ts/stores/files.ts
index 9350369..01bb1b0 100644
--- a/control/frontend/src/ts/stores/files.ts
+++ b/control/frontend/src/ts/stores/files.ts
@@ -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>> = writable>>({});
// {
@@ -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)) {
diff --git a/control/frontend/src/ts/stores/thumbnails.ts b/control/frontend/src/ts/stores/thumbnails.ts
new file mode 100644
index 0000000..064d8e1
--- /dev/null
+++ b/control/frontend/src/ts/stores/thumbnails.ts
@@ -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 = writable([]);
+
+export async function generate_thumbnail(display_ip: string, path: string, folder_element: FolderElement): Promise {
+ 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 {
+ 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 []; })
+}
\ No newline at end of file
diff --git a/control/frontend/src/ts/types.ts b/control/frontend/src/ts/types.ts
index 197f825..5a34cab 100644
--- a/control/frontend/src/ts/types.ts
+++ b/control/frontend/src/ts/types.ts
@@ -48,7 +48,6 @@ export const supported_file_types: Record = {
export type FolderElement = {
id?: string;
hash: string | null;
- thumbnail_url: string | null;
name: string;
type: string;
date_created: Date;