finalize thumbnails and add shared supported_file_types json

This commit is contained in:
E44
2025-11-13 19:55:05 +01:00
parent 851cf708e8
commit 0b6ab994ce
7 changed files with 158 additions and 79 deletions
@@ -20,8 +20,62 @@
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
import FolderElementObject from './FolderElementObject.svelte'; import FolderElementObject from './FolderElementObject.svelte';
import PopUp from './PopUp.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
};
};
</script> </script>
{#snippet new_folder_popup()}
<div>
<TextInput
current_value={current_name}
{current_valid}
title="Ordnername"
is_valid_function={(input: string) => {
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'];
}}
/>
</div>
<div class="flex flex-row justify-end gap-2">
<Button className="px-4 font-bold" click_function={popup_close_function}>Fertig</Button>
</div>
{/snippet}
<div class="bg-stone-800 h-full rounded-2xl grid grid-rows-[2.5rem_1fr] min-h-0"> <div class="bg-stone-800 h-full rounded-2xl grid grid-rows-[2.5rem_1fr] min-h-0">
<div class="bg-stone-700 flex justify-between w-full p-1 rounded-t-2xl min-w-0 gap-2"> <div class="bg-stone-700 flex justify-between w-full p-1 rounded-t-2xl min-w-0 gap-2">
<span class="text-xl font-bold pl-2 content-center truncate min-w-0"> <span class="text-xl font-bold pl-2 content-center truncate min-w-0">
@@ -59,7 +113,8 @@
<div class="flex flex-row gap-2 shrink-0"> <div class="flex flex-row gap-2 shrink-0">
<Button <Button
title="Neuen Ordner erstellen (Neuen Ordner mit ausgewählten Objekten erstellen)" title="Neuen Ordner erstellen (Neuen Ordner mit ausgewählten Objekten erstellen)"
className="px-3 flex"><FolderPlus /></Button className="px-3 flex"
click_function={show_new_folder_popup}><FolderPlus /></Button
> >
<div class="border border-stone-700 my-1"></div> <div class="border border-stone-700 my-1"></div>
<Button title="Datei(en) hochladen" className="px-3 flex"><Upload /></Button> <Button title="Datei(en) hochladen" className="px-3 flex"><Upload /></Button>
@@ -125,8 +180,11 @@
{/if} {/if}
</div> </div>
</div> </div>
<!-- <PopUp title="Test" title_icon={Info}> <PopUp
<div>Hier kann beliebiges HTML übergeben werden!</div> content={popup_content}
</PopUp> --> close_function={popup_close_function}
className="rounded-b-2xl"
snippet_container_class="overflow-hidden"
/>
</div> </div>
</div> </div>
@@ -14,7 +14,8 @@
get_shifted_color get_shifted_color
} from '../ts/stores/ui_behavior'; } from '../ts/stores/ui_behavior';
import Button from './Button.svelte'; 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 { import {
is_selected, is_selected,
select, select,
@@ -28,7 +29,7 @@
get_display_ids_where_file_is_missing get_display_ids_where_file_is_missing
} from '../ts/stores/files'; } from '../ts/stores/files';
import RefreshPlay from './RefreshPlay.svelte'; 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 { open_file } from '../ts/api_handler';
import { import {
displays, displays,
@@ -55,22 +56,6 @@
const is_folder = file.type === 'inode/directory'; 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) { function get_created_string(date_object: Date, full_string = false) {
if (full_string) { if (full_string) {
return ( return (
@@ -191,11 +176,11 @@
<img <img
src={thumbnail_url} src={thumbnail_url}
alt="file_thumbnail" alt="file_thumbnail"
class="object-contain size-full select-none block" class="object-contain size-full select-none block p-1"
draggable="false" draggable="false"
/> />
{:else if get_file_type(file)?.icon} {:else if supported_file_type_icon[get_file_type(file)?.display_name || '']}
{@const Icon = get_file_type(file)?.icon} {@const Icon = supported_file_type_icon[get_file_type(file)?.display_name || '']}
<Icon class="size-full p-2" /> <Icon class="size-full p-2" />
{:else} {:else}
<FileIcon class="size-full p-2" /> <FileIcon class="size-full p-2" />
@@ -49,7 +49,10 @@
{title}: {title}:
</div> </div>
{#if is_valid_function && focussed} {#if is_valid_function && focussed}
<div class={current_valid ? "text-green-400" : "text-red-400"} transition:fade={{ duration: 100 }}> <div
class={current_valid ? 'text-green-400' : 'text-red-400'}
transition:fade={{ duration: 100 }}
>
{current_info} {current_info}
</div> </div>
{/if} {/if}
+4 -2
View File
@@ -1,12 +1,14 @@
import { get, writable, type Writable } from "svelte/store"; import { get, writable, type Writable } from "svelte/store";
import { get_thumbnail_blob } from "../api_handler"; 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 { db } from "../indexdb/file_thumbnails.db";
import { get_file_type } from "../utils";
export const active_thumbnail_urls: Writable<string[]> = writable<string[]>([]); export const active_thumbnail_urls: Writable<string[]> = writable<string[]>([]);
export async function generate_thumbnail(display_ip: string, path: string, folder_element: FolderElement): Promise<void> { 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 supported_file_type = get_file_type(folder_element);
if (!supported_file_type || !folder_element.hash) return;
const hash: string = folder_element.hash; const hash: string = folder_element.hash;
if (await db.thumbnail_blobs.get(hash)) return; if (await db.thumbnail_blobs.get(hash)) return;
+11 -41
View File
@@ -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"; import type { Snippet } from "svelte";
export type SupportedFileType = { export type SupportedFileType = {
display_name: display_name: string;
string; mime_type: mime_type: string;
string; icon: typeof X;
}; };
export const supported_file_type_icon: Record<string, typeof X> = {
export const supported_file_types: Record<string, SupportedFileType> = { 'MP4': FileVideoCamera,
'.mp4': { 'JPG': FileImage,
display_name: 'MP4', 'PNG': FileImage,
mime_type: 'video/mp4', 'GIF': ImagePlay,
icon: FileVideoCamera, 'PPTX': FileBox,
}, 'ODP': FileBox,
'.jpg': { 'PDF': FileText
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 type FolderElement = { export type FolderElement = {
+27
View File
@@ -1,3 +1,24 @@
import type { FolderElement, SupportedFileType } from "./types";
import supported_file_types_json from './../../../../shared/supported_file_types.json';
const supported_file_types: Record<string, SupportedFileType> = supported_file_types_json as Record<string, SupportedFileType>;
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_uuid(): string { export function get_uuid(): string {
return crypto.randomUUID(); return crypto.randomUUID();
} }
@@ -40,3 +61,9 @@ export async function image_content_hash(blob: Blob, size = 32): Promise<number>
return hash >>> 0; // unsigned int return hash >>> 0; // unsigned int
} }
export function is_valid_name(input: string): boolean {
return /^[\p{L}\p{N}\p{M}\-_.+,()[\]{}@!§$%&=~^ ]+$/u.test(input);
}
+34
View File
@@ -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"
}
}