improve folder view - test with display-api

This commit is contained in:
E44
2025-10-30 08:59:22 +01:00
parent d92d8abc77
commit 9df03b6ccb
20 changed files with 1067 additions and 197 deletions
+25
View File
@@ -0,0 +1,25 @@
export async function get_file_data(ip: string, path: string = './') {
const options = {
method: 'PATCH',
headers: { 'content-type': 'application/json' },
body: `{"command":"cd ${path} && find . -maxdepth 1 -mindepth 1 -print0 | while IFS= read -r -d \'\' f; do\n typ=$(file -b --mime-type -- \"$f\")\n size=$(stat -c \'%s\' -- \"$f\")\n created=$(stat -c \'%w\' -- \"$f\")\n [ \"$created\" = \"-\" ] && created=$(stat -c \'%y\' -- \"$f\")\n jq -n --arg name \"$f\" --arg type \"$typ\" --arg size \"$size\" --arg created \"$created\" \'{name:$name, type:$type, size:($size|tonumber), created:$created}\' | tr -d \'\\n\'\n echo\n done\n"}`
};
console.log(request(ip, '/shellCommand', options));
}
async function request(ip: string, api_route: string, options: { method: string, headers?: Record<string, string>, body?: string }) {
try {
const url = `http://${ip}:1323/api${api_route}`;
const response = await fetch(url, options);
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}
+10 -28
View File
@@ -1,14 +1,12 @@
import { get, writable, type Writable } from "svelte/store";
import type { Display, DisplayGroup } from "../types";
import { is_selected, select, selected_display_ids } from "./select";
export const displays: Writable<DisplayGroup[]> = writable<DisplayGroup[]>([{
id: crypto.randomUUID(),
data: []
}]);
export const selected_display_ids: Writable<string[]> = writable<string[]>([]);
add_testing_displays();
function add_display(ip: string, mac: string, name: string, status: string) {
displays.update((displays: DisplayGroup[]) => {
@@ -17,24 +15,6 @@ function add_display(ip: string, mac: string, name: string, status: string) {
});
}
export function select(display_id: string, new_value: boolean | null = null) {
selected_display_ids.update((all_ids: string[]) => {
if (all_ids.includes(display_id)) {
const index = all_ids.indexOf(display_id);
if (index > -1 && new_value !== true) {
all_ids.splice(index, 1);
}
} else if (new_value !== false) {
all_ids.push(display_id);
}
return all_ids;
});
}
export function is_selected(display_id: string, current_selected_display_ids: string[]): boolean {
return current_selected_display_ids.includes(display_id);
}
export function all_displays_of_group_selected(display_group: DisplayGroup, current_selected_displays: string[]) {
if (display_group.data.length === 0) return false;
for (const display of display_group.data) {
@@ -47,7 +27,7 @@ export function all_displays_of_group_selected(display_group: DisplayGroup, curr
export function select_all_of_group(display_group: DisplayGroup, new_value: boolean | null = null) {
for (const display of display_group.data) {
select(display.id, new_value);
select(selected_display_ids, display.id, new_value);
}
}
@@ -99,10 +79,12 @@ export function remove_empty_display_groups() {
add_testing_displays();
function add_testing_displays() {
const names = ["Vorne Rechts", "Vorne Links", "Vorne Mitte", "Fernseher Rechts", "Fernseher Bühne", "UIUIUIUIUIUIUISEHRLANGERTEXT DER IST WIRKLICH LANG, DER TEXT, so lang, dass er wirklich nirgendswo hinpasst, nichtmal da oben /\\"];
for (const name of names) {
add_display("192.168.1.42", "00:1A:2B:3C:4D:5E", name, "Offline");
}
// const names = ["Vorne Rechts", "Vorne Links", "Vorne Mitte", "Fernseher Rechts", "Fernseher Bühne", "UIUIUIUIUIUIUISEHRLANGERTEXT DER IST WIRKLICH LANG, DER TEXT, so lang, dass er wirklich nirgendswo hinpasst, nichtmal da oben /\\"];
// for (const name of names) {
// add_display("127.0.0.1", "00:1A:2B:3C:4D:5E", name, "Offline");
// }
add_display("127.0.0.1", "00:1A:2B:3C:4D:5E", "Test", "Offline")
}
+239
View File
@@ -0,0 +1,239 @@
import { get, writable, type Writable } from "svelte/store";
import type { FolderElement } from "../types";
import { displays } from "./displays";
import { selected_display_ids, selected_file_ids } from "./select";
import { get_file_data } from "../api_handler";
export const all_files: Writable<Record<string, Record<string, FolderElement[]>>> = writable<Record<string, Record<string, FolderElement[]>>>({});
// {
// path: {
// display_id: FolderElement[]
// ...
// },
// path2: {
// display_id: FolderElement[]
// ...
// },
// ...
// }
export const current_file_path: Writable<string> = writable<string>('/');
export function change_file_path(new_path: string) {
current_file_path.update(() => {
return new_path;
});
selected_file_ids.update(() => {
return [];
})
}
export function get_display_ids_where_file_is_missing(path: string, file: FolderElement, selected_display_ids: string[], all_files: Record<string, Record<string, FolderElement[]>>): string[][] {
if (!all_files.hasOwnProperty(path)) return [];
const missing: string[] = [];
const colliding: string[] = [];
Display:
for (const selected_display_id of selected_display_ids) {
if (!all_files[path].hasOwnProperty(selected_display_id)) {
missing.push(selected_display_id);
continue;
}
for (const folder_element of all_files[path][selected_display_id]) {
if (folder_element.name === file.name) {
if (folder_element.hash !== file.hash) {
colliding.push(selected_display_id);
}
continue Display;
}
}
missing.push(selected_display_id);
}
return [missing, colliding];
}
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)) {
files[file_path] = {};
}
for (const new_folder_element of new_folder_elements) {
new_folder_element.id = crypto.randomUUID();
}
files[file_path][display_id] = new_folder_elements;
return files;
});
}
function get_files_on_all_displays() {
for (const display_group of get(displays)) {
for (const display of display_group.data) {
get_file_data(display.ip)
}
}
}
export function get_current_folder_elements(all_files: Record<string, Record<string, FolderElement[]>>, current_file_path: string, selected_display_ids: string[]) {
if (!all_files.hasOwnProperty(current_file_path)) {
get_files_on_all_displays();
return [];
}
const files_on_display_array = all_files[current_file_path];
const files: FolderElement[] = [];
for (const key of Object.keys(files_on_display_array)) {
if (selected_display_ids.includes(key)) {
FileOnDisplay:
for (const file_on_display of files_on_display_array[key]) {
for (const existing_file of files) {
if (file_on_display.name === existing_file.name) {
if (file_on_display.hash === existing_file.hash) {
continue FileOnDisplay;
}
}
}
files.push(file_on_display);
}
}
}
return sort_files(files);
}
function sort_files(files: FolderElement[]) {
files.sort((a, b) => {
const isDirA = a.type === 'inode/directory';
const isDirB = b.type === 'inode/directory';
// Ordner zuerst
if (isDirA && !isDirB) return -1;
if (!isDirA && isDirB) return 1;
// Danach alphabetisch nach name (case-insensitive)
const nameCompare = a.name.localeCompare(b.name, undefined, { sensitivity: 'base' });
if (nameCompare !== 0) return nameCompare;
// Wenn name gleich, absteigend nach date_created
return b.date_created.getTime() - a.date_created.getTime();
});
return files;
}
add_test_files();
export function add_test_files() {
// updates_files_on_display(get(displays)[0].data[0].id, [
// {
// hash: "ok",
// thumbnail: null,
// name: "ok.png",
// type: "image/png",
// date_created: new Date("2022-09-10"),
// size: "32 KB"
// },
// {
// hash: "schön",
// thumbnail: null,
// name: "schön.png",
// type: "image/png",
// date_created: new Date("2023-09-10"),
// size: "2 GB"
// },
// {
// hash: "lang",
// thumbnail: null,
// name: "langer Bildname der wirklich gar nicht mehr aufhört, sodass man mal gut testen kann, wie die UI darauf reagiert.odp",
// type: "application/vnd.oasis.opendocument.presentation",
// date_created: new Date("2028-09-10"),
// size: "324 KB"
// },
// {
// hash: "Schön hier aber waren Sie mal in Baden Würtemberg?",
// thumbnail: null,
// name: "Schön hier aber waren Sie mal in Baden Würtemberg?.mp4",
// type: "video/mp4",
// date_created: new Date("2024-09-10"),
// size: "32 KB"
// },
// {
// hash: "lan4234g",
// thumbnail: null,
// name: "Ein schöner Ordner",
// type: 'inode/directory',
// date_created: new Date("2025-01-02"),
// size: "324 TB"
// },
// {
// hash: "Schön hi23424er aber waren Sie mal in Baden Würtemberg?",
// thumbnail: null,
// name: "Ein hässlicher Ordner",
// type: 'inode/directory',
// date_created: new Date("2025-10-22"),
// size: "1 B"
// },
// ], '/');
// updates_files_on_display(get(displays)[0].data[0].id, [
// {
// hash: "ok",
// thumbnail: null,
// name: "ok.png",
// type: "image/png",
// date_created: new Date("2022-09-10"),
// size: "32 KB"
// },
// {
// hash: "schön",
// thumbnail: null,
// name: "schön.png",
// type: "image/png",
// date_created: new Date("2023-09-10"),
// size: "2 GB"
// },
// ], '/Ein hässlicher Ordner/');
// updates_files_on_display(get(displays)[0].data[0].id, [
// {
// hash: "nö",
// thumbnail: null,
// name: "nö.png",
// type: "image/png",
// date_created: new Date("2022-09-10"),
// size: "32 KB"
// },
// {
// hash: "na gut",
// thumbnail: null,
// name: "na gut.png",
// type: "image/png",
// date_created: new Date("2023-09-10"),
// size: "2 GB"
// },
// ], '/Ein schöner Ordner/');
// updates_files_on_display(get(displays)[0].data[1].id, [
// {
// hash: "okk",
// thumbnail: null,
// name: "ok.png",
// type: "image/png",
// date_created: new Date("2022-09-10"),
// size: "32 KB"
// },
// {
// hash: "schön",
// thumbnail: null,
// name: "schön.png",
// type: "image/png",
// date_created: new Date("2023-09-10"),
// size: "2 GB"
// },], '/');
}
+23
View File
@@ -0,0 +1,23 @@
import { writable, type Writable } from "svelte/store";
export const selected_file_ids: Writable<string[]> = writable<string[]>([]);
export const selected_display_ids: Writable<string[]> = writable<string[]>([]);
export function select(selected_ids: Writable<string[]>, id: string, new_value: boolean | null = null) {
selected_ids.update((all_ids: string[]) => {
if (all_ids.includes(id)) {
const index = all_ids.indexOf(id);
if (index > -1 && new_value !== true) {
all_ids.splice(index, 1);
}
} else if (new_value !== false) {
all_ids.push(id);
}
return all_ids;
});
}
export function is_selected(id: string, selected_ids: string[]): boolean {
return selected_ids.includes(id);
}
+33 -18
View File
@@ -1,36 +1,51 @@
import type { NumericRange } from "@sveltejs/kit";
import { get, writable, type Writable } from "svelte/store";
import { writable, type Writable } from "svelte/store";
const screen_height_step_size = 5;
export const dnd_flip_duration_ms = 300;
const min_display_screen_height = 15;
const max_display_screen_height = 40;
export const display_screen_height: Writable<number> = writable<number>(25);
const heights_options: Record<string, { min: number; max: number; step: number }> = {
display: {
min: 15,
max: 40,
step: 5,
},
file: {
min: 10,
max: 20,
step: 5,
}
}
export const current_height: Writable<Record<string, number>> = writable<Record<string, number>>({
display: 25,
file: 10,
});
export const is_group_drag: Writable<boolean> = writable<boolean>(false);
export const is_display_drag: Writable<boolean> = writable<boolean>(false);
export const pinned_display_id: Writable<string | null> = writable<string | null>(null);
export function change_display_screen_height(factor: number) {
display_screen_height.update((current_height) => {
const new_size = current_height + (factor * screen_height_step_size);
if (new_size > max_display_screen_height || new_size < min_display_screen_height) {
return current_height;
} else {
return new_size;
}
export function change_height(key: 'display' | 'file', factor: number) {
current_height.update((height) => {
height[key] = next_height_step_size(key, height, factor) || height[key];
return height;
});
}
export function next_step_possible(current_height: number, factor: number) {
const new_size = current_height + (factor * screen_height_step_size);
return new_size > max_display_screen_height || new_size < min_display_screen_height;
export function next_height_step_size(key: 'display' | 'file', current_height_array: Record<string, number>, factor: number): number {
const new_size = current_height_array[key] + (factor * heights_options[key].step);
if (new_size > heights_options[key].max || new_size < heights_options[key].min) {
return 0;
} else {
return new_size;
}
}
export function get_selectable_color_classes(
selected: boolean,
returning_classes: { bg?: boolean; hover?: boolean; active?: boolean; text?: boolean } = {},
+57 -3
View File
@@ -1,4 +1,59 @@
import type { X } from "lucide-svelte";
import { FileBox, FileImage, FileVideoCamera, ImagePlay, type X } from "lucide-svelte";
export type SupportedFileType = {
display_name:
string; mime_type:
string; icon: typeof X;
};
export const supported_file_types: Record<string, SupportedFileType> = {
'.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 type FolderElement = {
id?: string;
hash: string;
thumbnail: Blob | null;
name: string;
type: string;
date_created: Date;
size: string;
}
export type Display = {
id: string;
@@ -20,5 +75,4 @@ export type MenuOption = {
class?: string;
on_select?: () => void;
disabled?: boolean;
}
}