From 0b6ab994ce64004069dd3f7a2c40e369a62c5f20 Mon Sep 17 00:00:00 2001
From: E44 <129310925+programmer-44@users.noreply.github.com>
Date: Thu, 13 Nov 2025 19:55:05 +0100
Subject: [PATCH] finalize thumbnails and add shared supported_file_types json
---
.../frontend/src/components/FileView.svelte | 66 +++++++++++++++++--
.../src/components/FolderElementObject.svelte | 27 ++------
.../frontend/src/components/TextInput.svelte | 5 +-
control/frontend/src/ts/stores/thumbnails.ts | 6 +-
control/frontend/src/ts/types.ts | 52 ++++-----------
control/frontend/src/ts/utils.ts | 47 ++++++++++---
shared/supported_file_types.json | 34 ++++++++++
7 files changed, 158 insertions(+), 79 deletions(-)
create mode 100644 shared/supported_file_types.json
diff --git a/control/frontend/src/components/FileView.svelte b/control/frontend/src/components/FileView.svelte
index b422d44..9101d1a 100644
--- a/control/frontend/src/components/FileView.svelte
+++ b/control/frontend/src/components/FileView.svelte
@@ -20,8 +20,62 @@
import { slide } from 'svelte/transition';
import FolderElementObject from './FolderElementObject.svelte';
import PopUp from './PopUp.svelte';
+ import type { PopupContent } from '../ts/types';
+ import TextInput from './TextInput.svelte';
+ import { is_valid_name } from '../ts/utils';
+
+ let current_name: string = '';
+ let current_valid: boolean = false;
+
+ let popup_content: PopupContent = $state({
+ open: false,
+ snippet: null,
+ title: '',
+ closable: true
+ });
+
+ function popup_close_function() {
+ popup_content.open = false;
+ }
+
+ const show_new_folder_popup = () => {
+ popup_content = {
+ open: true,
+ snippet: new_folder_popup,
+ title: 'Neuen Ordner erstellen',
+ title_icon: FolderPlus,
+ closable: true
+ };
+ };
+{#snippet new_folder_popup()}
+
+ {
+ if (input.startsWith('.')) return [false, 'Name darf nicht mit . beginnen'];
+ const trimmed_input = input.trim();
+ if (trimmed_input.length === 0 || trimmed_input.length > 50)
+ return [false, 'Ungültige Länge'];
+ if (!is_valid_name(trimmed_input)) return [false, 'Name enthält ungültige Zeichen'];
+ if (
+ get_current_folder_elements($all_files, $current_file_path, $selected_display_ids).some(
+ (e) => e.name === trimmed_input
+ )
+ )
+ return [false, 'Name bereits verwendet'];
+ return [true, 'Gültiger Name'];
+ }}
+ />
+
+
+
+
+{/snippet}
+
@@ -59,7 +113,8 @@
@@ -125,8 +180,11 @@
{/if}
-
+
diff --git a/control/frontend/src/components/FolderElementObject.svelte b/control/frontend/src/components/FolderElementObject.svelte
index c499c0e..75d9442 100644
--- a/control/frontend/src/components/FolderElementObject.svelte
+++ b/control/frontend/src/components/FolderElementObject.svelte
@@ -14,7 +14,8 @@
get_shifted_color
} from '../ts/stores/ui_behavior';
import Button from './Button.svelte';
- import { supported_file_types, type FolderElement, type SupportedFileType } from '../ts/types';
+ import { supported_file_type_icon, type FolderElement, type SupportedFileType } from '../ts/types';
+
import {
is_selected,
select,
@@ -28,7 +29,7 @@
get_display_ids_where_file_is_missing
} from '../ts/stores/files';
import RefreshPlay from './RefreshPlay.svelte';
- import { get_file_size_display_string } from '../ts/utils';
+ import { get_file_size_display_string, get_file_type } from '../ts/utils';
import { open_file } from '../ts/api_handler';
import {
displays,
@@ -55,22 +56,6 @@
const is_folder = file.type === 'inode/directory';
- function get_file_type(file: FolderElement): SupportedFileType | null {
- for (const key of Object.keys(supported_file_types)) {
- if (file.type === supported_file_types[key].mime_type) {
- return supported_file_types[key];
- }
- }
- // Fallback:
- const extension = file.name.split('.').pop();
- if (extension) {
- if (Object.keys(supported_file_types).includes('.' + extension)) {
- return supported_file_types['.' + extension];
- }
- }
- return null;
- }
-
function get_created_string(date_object: Date, full_string = false) {
if (full_string) {
return (
@@ -191,11 +176,11 @@
- {:else if get_file_type(file)?.icon}
- {@const Icon = get_file_type(file)?.icon}
+ {:else if supported_file_type_icon[get_file_type(file)?.display_name || '']}
+ {@const Icon = supported_file_type_icon[get_file_type(file)?.display_name || '']}
{:else}
diff --git a/control/frontend/src/components/TextInput.svelte b/control/frontend/src/components/TextInput.svelte
index a4a4575..118abb3 100644
--- a/control/frontend/src/components/TextInput.svelte
+++ b/control/frontend/src/components/TextInput.svelte
@@ -49,7 +49,10 @@
{title}:
{#if is_valid_function && focussed}
-
+
{current_info}
{/if}
diff --git a/control/frontend/src/ts/stores/thumbnails.ts b/control/frontend/src/ts/stores/thumbnails.ts
index 064d8e1..04c6a15 100644
--- a/control/frontend/src/ts/stores/thumbnails.ts
+++ b/control/frontend/src/ts/stores/thumbnails.ts
@@ -1,12 +1,14 @@
import { get, writable, type Writable } from "svelte/store";
import { get_thumbnail_blob } from "../api_handler";
-import { supported_file_types, type FolderElement } from "../types";
+import { type FolderElement } from "../types";
import { db } from "../indexdb/file_thumbnails.db";
+import { get_file_type } from "../utils";
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 supported_file_type = get_file_type(folder_element);
+ if (!supported_file_type || !folder_element.hash) return;
const hash: string = folder_element.hash;
if (await db.thumbnail_blobs.get(hash)) return;
diff --git a/control/frontend/src/ts/types.ts b/control/frontend/src/ts/types.ts
index 5a34cab..d57f325 100644
--- a/control/frontend/src/ts/types.ts
+++ b/control/frontend/src/ts/types.ts
@@ -1,48 +1,18 @@
-import { FileBox, FileImage, FileVideoCamera, ImagePlay, type X } from "lucide-svelte";
+import { FileBox, FileImage, FileText, FileVideoCamera, ImagePlay, type X } from "lucide-svelte";
import type { Snippet } from "svelte";
export type SupportedFileType = {
- display_name:
- string; mime_type:
- string; icon: typeof X;
+ display_name: string;
+ mime_type: string;
};
-
-export const supported_file_types: Record = {
- '.mp4': {
- display_name: 'MP4',
- mime_type: 'video/mp4',
- icon: FileVideoCamera,
- },
- '.jpg': {
- display_name: 'JPG',
- mime_type: 'image/jpg',
- icon: FileImage,
- },
- '.jpeg': {
- display_name: 'JPG',
- mime_type: 'image/jpeg',
- icon: FileImage,
- },
- '.png': {
- display_name: 'PNG',
- mime_type: 'image/png',
- icon: FileImage,
- },
- '.gif': {
- display_name: 'GIF',
- mime_type: 'image/gif',
- icon: ImagePlay,
- },
- '.pptx': {
- display_name: 'PPTX',
- mime_type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
- icon: FileBox,
- },
- '.odp': {
- display_name: 'ODP',
- mime_type: 'application/vnd.oasis.opendocument.presentation',
- icon: FileBox,
- }
+export const supported_file_type_icon: Record = {
+ 'MP4': FileVideoCamera,
+ 'JPG': FileImage,
+ 'PNG': FileImage,
+ 'GIF': ImagePlay,
+ 'PPTX': FileBox,
+ 'ODP': FileBox,
+ 'PDF': FileText
}
export type FolderElement = {
diff --git a/control/frontend/src/ts/utils.ts b/control/frontend/src/ts/utils.ts
index 216f0d7..e0d7c28 100644
--- a/control/frontend/src/ts/utils.ts
+++ b/control/frontend/src/ts/utils.ts
@@ -1,19 +1,40 @@
-export function get_uuid(): string {
- return crypto.randomUUID();
+import type { FolderElement, SupportedFileType } from "./types";
+import supported_file_types_json from './../../../../shared/supported_file_types.json';
+
+const supported_file_types: Record = supported_file_types_json as Record;
+
+export function get_file_type(file: FolderElement): SupportedFileType | null {
+ for (const key of Object.keys(supported_file_types)) {
+ if (file.type === supported_file_types[key].mime_type) {
+ return supported_file_types[key];
+ }
+ }
+ // Fallback:
+ const extension = file.name.split('.').pop();
+ if (extension) {
+ if (Object.keys(supported_file_types).includes('.' + extension)) {
+ return supported_file_types['.' + extension];
+ }
+ }
+ return null;
}
-export function get_file_size_display_string(size: number, toFixed: number|null = null): string {
- if (size === 0) return "0 B";
+export function get_uuid(): string {
+ return crypto.randomUUID();
+}
- const k = 1024;
- const sizes = ["B", "KB", "MB", "GB", "TB"];
+export function get_file_size_display_string(size: number, toFixed: number | null = null): string {
+ if (size === 0) return "0 B";
- const i = Math.floor(Math.log(size) / Math.log(k));
- const value = size / Math.pow(k, i);
+ const k = 1024;
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
- const size_string = `${value.toFixed(toFixed !== null ? toFixed : Math.max(0, 2 - Math.floor(Math.log10(value))))} ${sizes[i]}`;
+ const i = Math.floor(Math.log(size) / Math.log(k));
+ const value = size / Math.pow(k, i);
- return size_string.replace('.', ',');
+ const size_string = `${value.toFixed(toFixed !== null ? toFixed : Math.max(0, 2 - Math.floor(Math.log10(value))))} ${sizes[i]}`;
+
+ return size_string.replace('.', ',');
}
@@ -40,3 +61,9 @@ export async function image_content_hash(blob: Blob, size = 32): Promise
return hash >>> 0; // unsigned int
}
+
+
+export function is_valid_name(input: string): boolean {
+ return /^[\p{L}\p{N}\p{M}\-_.+,()[\]{}@!§$%&=~^ ]+$/u.test(input);
+}
+
diff --git a/shared/supported_file_types.json b/shared/supported_file_types.json
new file mode 100644
index 0000000..df79c21
--- /dev/null
+++ b/shared/supported_file_types.json
@@ -0,0 +1,34 @@
+{
+ ".mp4": {
+ "display_name": "MP4",
+ "mime_type": "video/mp4"
+ },
+ ".jpg": {
+ "display_name": "JPG",
+ "mime_type": "image/jpg"
+ },
+ ".jpeg": {
+ "display_name": "JPG",
+ "mime_type": "image/jpeg"
+ },
+ ".png": {
+ "display_name": "PNG",
+ "mime_type": "image/png"
+ },
+ ".gif": {
+ "display_name": "GIF",
+ "mime_type": "image/gif"
+ },
+ ".pptx": {
+ "display_name": "PPTX",
+ "mime_type": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
+ },
+ ".odp": {
+ "display_name": "ODP",
+ "mime_type": "application/vnd.oasis.opendocument.presentation"
+ },
+ ".pdf": {
+ "display_name": "PDF",
+ "mime_type": "application/pdf"
+ }
+}
\ No newline at end of file