mirror of
https://codeberg.org/PLG-Development/PLG-MuDiCS
synced 2026-07-06 00:47:09 +00:00
chore(control): add delete file and create folder action
This commit is contained in:
@@ -16,17 +16,30 @@
|
|||||||
import Button from './Button.svelte';
|
import Button from './Button.svelte';
|
||||||
import PathBar from './PathBar.svelte';
|
import PathBar from './PathBar.svelte';
|
||||||
import { selected_display_ids, selected_file_ids } from '../ts/stores/select';
|
import { selected_display_ids, selected_file_ids } from '../ts/stores/select';
|
||||||
import { all_files, current_file_path, get_current_folder_elements } from '../ts/stores/files';
|
import {
|
||||||
|
all_files,
|
||||||
|
current_file_path,
|
||||||
|
get_current_folder_elements,
|
||||||
|
get_display_ids_where_file_is_missing,
|
||||||
|
get_display_ids_where_path_does_not_exist,
|
||||||
|
get_file_from_id,
|
||||||
|
get_longest_existing_path_and_needed_parts,
|
||||||
|
run_for_selected_files_on_selected_displays,
|
||||||
|
update_current_folder_on_selected_displays
|
||||||
|
} from '../ts/stores/files';
|
||||||
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 type { PopupContent } from '../ts/types';
|
||||||
import TextInput from './TextInput.svelte';
|
import TextInput from './TextInput.svelte';
|
||||||
import { is_valid_name } from '../ts/utils';
|
import { is_valid_name } from '../ts/utils';
|
||||||
|
import { displays, get_display_by_id, run_on_all_selected_displays } from '../ts/stores/displays';
|
||||||
|
import { create_folders, delete_files } from '../ts/api_handler';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
import HighlightedText from './HighlightedText.svelte';
|
import HighlightedText from './HighlightedText.svelte';
|
||||||
|
|
||||||
let current_name: string = '';
|
let current_name: string = $state('');
|
||||||
let current_valid: boolean = false;
|
let current_valid: boolean = $state(false);
|
||||||
|
|
||||||
let popup_content: PopupContent = $state({
|
let popup_content: PopupContent = $state({
|
||||||
open: false,
|
open: false,
|
||||||
@@ -40,6 +53,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const show_new_folder_popup = () => {
|
const show_new_folder_popup = () => {
|
||||||
|
current_name = '';
|
||||||
|
current_valid = false;
|
||||||
popup_content = {
|
popup_content = {
|
||||||
open: true,
|
open: true,
|
||||||
snippet: new_folder_popup,
|
snippet: new_folder_popup,
|
||||||
@@ -48,13 +63,43 @@
|
|||||||
closable: true
|
closable: true
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const delete_folder_element_popup = () => {
|
||||||
|
popup_content = {
|
||||||
|
open: true,
|
||||||
|
snippet: delete_request_popup,
|
||||||
|
title: `${$selected_file_ids.length} ${$selected_file_ids.length === 1 ? 'Objekt' : 'Objekte'} wirklich löschen?`,
|
||||||
|
title_icon: Trash2,
|
||||||
|
closable: true
|
||||||
|
};
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet new_folder_popup()}
|
{#snippet new_folder_popup()}
|
||||||
<div>
|
{#if get_display_ids_where_path_does_not_exist($current_file_path, $selected_display_ids, $all_files).length > 0}
|
||||||
|
<span class="leading-relaxed"
|
||||||
|
>Der aktuelle Pfad <HighlightedText
|
||||||
|
>{$current_file_path.slice(0, $current_file_path.length - 1)}</HighlightedText
|
||||||
|
> existiert nicht auf {get_display_ids_where_path_does_not_exist(
|
||||||
|
$current_file_path,
|
||||||
|
$selected_display_ids,
|
||||||
|
$all_files
|
||||||
|
).length === 1
|
||||||
|
? 'dem Bildschirm'
|
||||||
|
: 'den Bildschirmen'}
|
||||||
|
{#each get_display_ids_where_path_does_not_exist($current_file_path, $selected_display_ids, $all_files) as display_id, i}
|
||||||
|
{#if i !== 0}
|
||||||
|
,
|
||||||
|
{/if}
|
||||||
|
<HighlightedText>{get_display_by_id(display_id, $displays)?.name}</HighlightedText>
|
||||||
|
{/each}. Mit der Erstellung dieses Ordners wird der Pfad automatisch mit leeren Ordnern bis
|
||||||
|
zum aktuellen Pfad aufgefüllt.
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
<TextInput
|
<TextInput
|
||||||
current_value={current_name}
|
focused_on_start={true}
|
||||||
{current_valid}
|
bind:current_value={current_name}
|
||||||
|
bind:current_valid
|
||||||
title="Ordnername"
|
title="Ordnername"
|
||||||
is_valid_function={(input: string) => {
|
is_valid_function={(input: string) => {
|
||||||
if (input.startsWith('.')) return [false, 'Name darf nicht mit . beginnen'];
|
if (input.startsWith('.')) return [false, 'Name darf nicht mit . beginnen'];
|
||||||
@@ -71,12 +116,67 @@
|
|||||||
return [true, 'Gültiger Name'];
|
return [true, 'Gültiger Name'];
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<div class="flex flex-row justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
className="px-4 font-bold"
|
||||||
|
click_function={async () => {
|
||||||
|
for (const display_id of $selected_display_ids) {
|
||||||
|
const display = get_display_by_id(display_id, $displays);
|
||||||
|
if (!display) continue;
|
||||||
|
const path_data = get_longest_existing_path_and_needed_parts(
|
||||||
|
$current_file_path,
|
||||||
|
display_id,
|
||||||
|
$all_files
|
||||||
|
);
|
||||||
|
await create_folders(display.ip, path_data.existing, [...path_data.needed, current_name]);
|
||||||
|
}
|
||||||
|
await update_current_folder_on_selected_displays();
|
||||||
|
popup_close_function();
|
||||||
|
}}
|
||||||
|
disabled={!current_valid}>Neuen Ordner erstellen</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{#snippet delete_request_popup()}
|
||||||
|
<div class="flex flex-col gap-1 h-full min-h-0 grow-0">
|
||||||
|
<span class="text-stone-400 px-1"
|
||||||
|
>{`${$selected_file_ids.length === 1 ? 'Folgendes Objekt' : `Folgende ${$selected_file_ids.length} Objekte`} löschen? (Wiederherstellung nicht möglich)`}</span
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-2 overflow-auto h-full min-h-0 grow-0">
|
||||||
|
{#each $selected_file_ids
|
||||||
|
.map((file_id) => get_file_from_id(file_id, $all_files, $current_file_path))
|
||||||
|
.filter((element) => element !== null) as file}
|
||||||
|
<FolderElementObject {file} not_interactable />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row justify-end gap-2">
|
<div class="flex flex-row justify-end gap-2">
|
||||||
<Button className="px-4 font-bold" click_function={popup_close_function}>Fertig</Button>
|
<Button className="px-4 font-bold" click_function={popup_close_function}>Abbrechen</Button>
|
||||||
|
<Button
|
||||||
|
hover_bg="bg-red-400"
|
||||||
|
active_bg="bg-red-500"
|
||||||
|
className="px-4 flex text-red-400 hover:text-stone-100"
|
||||||
|
click_function={async () => {
|
||||||
|
await run_for_selected_files_on_selected_displays(
|
||||||
|
async (ip: string, file_names: string[]) => {
|
||||||
|
delete_files(ip, $current_file_path, file_names);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await update_current_folder_on_selected_displays();
|
||||||
|
selected_file_ids.update(() => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
popup_close_function();
|
||||||
|
}}>Löschen</Button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
{#snippet clipboard_hover_snippet()}
|
||||||
|
<div></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">
|
||||||
@@ -110,7 +210,7 @@
|
|||||||
<div class="flex flex-col gap-2 p-2 overflow-hidden relative rounded-b-2xl">
|
<div class="flex flex-col gap-2 p-2 overflow-hidden relative rounded-b-2xl">
|
||||||
<div class="flex flex-col gap-2 p-2 bg-stone-750 rounded-xl">
|
<div class="flex flex-col gap-2 p-2 bg-stone-750 rounded-xl">
|
||||||
<PathBar />
|
<PathBar />
|
||||||
<div class="flex flex-row justify-between gap-6 overflow-x-auto">
|
<div class="flex flex-row justify-between gap-6">
|
||||||
<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)"
|
||||||
@@ -152,13 +252,14 @@
|
|||||||
hover_bg="bg-red-400"
|
hover_bg="bg-red-400"
|
||||||
active_bg="bg-red-500"
|
active_bg="bg-red-500"
|
||||||
className="px-3 flex"
|
className="px-3 flex"
|
||||||
disabled={$selected_file_ids.length === 0}><Trash2 /></Button
|
disabled={$selected_file_ids.length === 0}
|
||||||
|
click_function={delete_folder_element_popup}><Trash2 /></Button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="min-h-0 h-full overflow-y-auto bg-stone-750 rounded-xl">
|
<div class="min-h-0 h-full overflow-y-auto bg-stone-750 rounded-xl">
|
||||||
<div class="flex flex-col gap-2 p-2 min-h-0">
|
<div class="flex flex-col gap-2 p-2 min-h-0 max-w-full">
|
||||||
{#if $selected_display_ids.length === 0}
|
{#if $selected_display_ids.length === 0}
|
||||||
<span class="text-stone-450 px-10 py-6 leading-relaxed text-center">
|
<span class="text-stone-450 px-10 py-6 leading-relaxed text-center">
|
||||||
Es wurden keine Bildschirme ausgewählt.
|
Es wurden keine Bildschirme ausgewählt.
|
||||||
|
|||||||
@@ -46,7 +46,10 @@
|
|||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { liveQuery } from 'dexie';
|
import { liveQuery } from 'dexie';
|
||||||
|
|
||||||
let { file } = $props<{ file: FolderElement }>();
|
let { file, not_interactable = false } = $props<{
|
||||||
|
file: FolderElement;
|
||||||
|
not_interactable?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
let thumbnail_url: string | null = $state(null);
|
let thumbnail_url: string | null = $state(null);
|
||||||
// Update thumbnail_url automatically if data is available
|
// Update thumbnail_url automatically if data is available
|
||||||
@@ -88,18 +91,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function get_grayed_out_text_color_strings(is_selected: boolean): string {
|
function get_grayed_out_text_color_strings(is_selected: boolean): string {
|
||||||
|
if (not_interactable) return 'text-stone-400';
|
||||||
const color = is_selected ? 'text-stone-600' : 'text-stone-400';
|
const color = is_selected ? 'text-stone-600' : 'text-stone-400';
|
||||||
const factor = is_selected ? -1 : 1;
|
const factor = is_selected ? -1 : 1;
|
||||||
return `${color} group-hover:${get_shifted_color(color, factor * 100)} group-active:${get_shifted_color(color, factor * 150)}`;
|
return `${color} group-hover:${get_shifted_color(color, factor * 100)} group-active:${get_shifted_color(color, factor * 150)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_grayed_out_border_color_strings(is_selected: boolean): string {
|
function get_grayed_out_border_color_strings(is_selected: boolean): string {
|
||||||
|
if (not_interactable) return 'border-stone-550';
|
||||||
const color = is_selected ? 'border-stone-450' : 'border-stone-550';
|
const color = is_selected ? 'border-stone-450' : 'border-stone-550';
|
||||||
const factor = is_selected ? 1 : 1;
|
const factor = is_selected ? 1 : 1;
|
||||||
return `${color} group-hover:${get_shifted_color(color, factor * 100)} group-active:${get_shifted_color(color, factor * 150)}`;
|
return `${color} group-hover:${get_shifted_color(color, factor * 100)} group-active:${get_shifted_color(color, factor * 150)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onclick(e: Event) {
|
function onclick(e: Event) {
|
||||||
|
if (not_interactable) return;
|
||||||
select(selected_file_ids, file.id);
|
select(selected_file_ids, file.id);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
@@ -115,6 +121,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-row h-{$current_height.file} w-full">
|
<div class="flex flex-row h-{$current_height.file} w-full">
|
||||||
|
{#if !not_interactable}
|
||||||
<div class="h-{$current_height.file} aspect-square max-w-15 flex">
|
<div class="h-{$current_height.file} aspect-square max-w-15 flex">
|
||||||
<Button
|
<Button
|
||||||
disabled={!is_folder && get_file_type(file) === null}
|
disabled={!is_folder && get_file_type(file) === null}
|
||||||
@@ -160,6 +167,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -167,12 +175,17 @@
|
|||||||
if (e.key === 'Enter' || e.key === ' ') onclick(e);
|
if (e.key === 'Enter' || e.key === ' ') onclick(e);
|
||||||
}}
|
}}
|
||||||
{onclick}
|
{onclick}
|
||||||
class="{get_selectable_color_classes(is_selected(file.id, $selected_file_ids), {
|
class="{get_selectable_color_classes(
|
||||||
|
!not_interactable && is_selected(file.id, $selected_file_ids),
|
||||||
|
{
|
||||||
bg: true,
|
bg: true,
|
||||||
hover: true,
|
hover: !not_interactable,
|
||||||
active: true,
|
active: !not_interactable,
|
||||||
text: true
|
text: true
|
||||||
})} rounded-r-lg transition-colors duration-200 gap-4 flex flex-row justify-between cursor-pointer group w-full min-w-0"
|
}
|
||||||
|
)} {not_interactable
|
||||||
|
? 'rounded-lg'
|
||||||
|
: 'rounded-r-lg cursor-pointer'} transition-colors duration-200 gap-4 flex flex-row justify-between group w-full min-w-0"
|
||||||
>
|
>
|
||||||
<div class="flex flex-row gap-2 min-w-0 w-full">
|
<div class="flex flex-row gap-2 min-w-0 w-full">
|
||||||
<div class="aspect-square rounded-md flex justify-center items-center">
|
<div class="aspect-square rounded-md flex justify-center items-center">
|
||||||
@@ -203,7 +216,7 @@
|
|||||||
is_selected(file.id, $selected_file_ids)
|
is_selected(file.id, $selected_file_ids)
|
||||||
)} duration-200 transition-colors"
|
)} duration-200 transition-colors"
|
||||||
>
|
>
|
||||||
{#if get_display_ids_where_file_is_missing($current_file_path, file, $selected_display_ids, $all_files)[1].length !== 0}
|
<!-- {#if get_display_ids_where_file_is_missing($current_file_path, file, $selected_display_ids, $all_files)[1].length !== 0}
|
||||||
<Button
|
<Button
|
||||||
className="h-8 aspect-square transition-colors duration-200 !p-1.5 text-stone-100"
|
className="h-8 aspect-square transition-colors duration-200 !p-1.5 text-stone-100"
|
||||||
bg="bg-red-500"
|
bg="bg-red-500"
|
||||||
@@ -229,7 +242,7 @@
|
|||||||
>
|
>
|
||||||
<RefreshCcwDot class="size-full" />
|
<RefreshCcwDot class="size-full" />
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if} -->
|
||||||
<div
|
<div
|
||||||
class="w-14 content-center text-center select-none text-xs whitespace-nowrap"
|
class="w-14 content-center text-center select-none text-xs whitespace-nowrap"
|
||||||
title={get_created_string(file.date_created, true)}
|
title={get_created_string(file.date_created, true)}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export async function show_html(ip: string, html: string) {
|
|||||||
await request_display(ip, '/showHTML', options);
|
await request_display(ip, '/showHTML', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function get_file_data(ip: string, path: string): Promise<FolderElement[]> {
|
export async function get_file_data(ip: string, path: string): Promise<FolderElement[] | null> {
|
||||||
interface FileInfo {
|
interface FileInfo {
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
@@ -44,11 +44,7 @@ export async function get_file_data(ip: string, path: string): Promise<FolderEle
|
|||||||
created: string;
|
created: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = {
|
const raw_response = await run_shell_command(ip, `cd ".${path}" && find . -maxdepth 1 -mindepth 1 -print0 | while IFS= read -r -d '' f; do
|
||||||
method: 'PATCH',
|
|
||||||
headers: { 'content-type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
command: `cd ".${path}" && find . -maxdepth 1 -mindepth 1 -print0 | while IFS= read -r -d '' f; do
|
|
||||||
typ=$(file -b --mime-type -- "$f")
|
typ=$(file -b --mime-type -- "$f")
|
||||||
size=$(stat -c '%s' -- "$f")
|
size=$(stat -c '%s' -- "$f")
|
||||||
created=$(stat -c '%w' -- "$f")
|
created=$(stat -c '%w' -- "$f")
|
||||||
@@ -57,12 +53,11 @@ export async function get_file_data(ip: string, path: string): Promise<FolderEle
|
|||||||
'{name:$name, type:$type, size:($size|tostring), created:$created}' | tr -d '\n'
|
'{name:$name, type:$type, size:($size|tostring), created:$created}' | tr -d '\n'
|
||||||
echo
|
echo
|
||||||
done
|
done
|
||||||
` })
|
`);
|
||||||
};
|
if (!raw_response.ok || !raw_response.json) return null;
|
||||||
const raw_response = await request_display(ip, '/shellCommand', options);
|
|
||||||
if (!raw_response.ok || !raw_response.json) return [];
|
|
||||||
const json_response = raw_response.json as ShellCommandResponse;
|
const json_response = raw_response.json as ShellCommandResponse;
|
||||||
if (is_cd_directory_error(ip, json_response)) return [];
|
if (json_response.exitCode === 0 && json_response.stdout.trim() === '') return [];
|
||||||
|
if (is_cd_directory_error(ip, json_response)) return null;
|
||||||
|
|
||||||
const response: FileInfo[] = json_response.stdout.trim()
|
const response: FileInfo[] = json_response.stdout.trim()
|
||||||
.split("\n")
|
.split("\n")
|
||||||
@@ -94,14 +89,7 @@ done
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function get_file_tree_data(ip: string, path: string): Promise<TreeElement[] | null> {
|
export async function get_file_tree_data(ip: string, path: string): Promise<TreeElement[] | null> {
|
||||||
const options = {
|
const raw_response = await run_shell_command(ip, `cd ".${path}" && tree -Js`);
|
||||||
method: 'PATCH',
|
|
||||||
headers: { 'content-type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
command: `cd ".${path}" && tree -Js`
|
|
||||||
})
|
|
||||||
};
|
|
||||||
const raw_response = await request_display(ip, '/shellCommand', options);
|
|
||||||
|
|
||||||
if (!raw_response.ok || !raw_response.json) return null;
|
if (!raw_response.ok || !raw_response.json) return null;
|
||||||
const json_response = raw_response.json as ShellCommandResponse;
|
const json_response = raw_response.json as ShellCommandResponse;
|
||||||
@@ -110,6 +98,25 @@ export async function get_file_tree_data(ip: string, path: string): Promise<Tree
|
|||||||
return (JSON.parse(json_response.stdout.trim()) as [TreeElement, any])[0].contents || null;
|
return (JSON.parse(json_response.stdout.trim()) as [TreeElement, any])[0].contents || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function create_folders(ip: string, path: string, folder_names: string[]): Promise<void> {
|
||||||
|
let command = `cd ".${path}"`;
|
||||||
|
|
||||||
|
for (const part of folder_names) {
|
||||||
|
command += `&& mkdir "${part}" && cd "${part}/"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await run_shell_command(ip, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function delete_files(ip: string, current_path: string, file_names: string[]) {
|
||||||
|
let del_string: string = '';
|
||||||
|
for (const file_name of file_names) {
|
||||||
|
del_string += `&& rm -r "${file_name}"`;
|
||||||
|
}
|
||||||
|
await run_shell_command(ip, `cd ".${current_path}" ${del_string}`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function show_blackscreen(ip: string): Promise<void> {
|
export async function show_blackscreen(ip: string): Promise<void> {
|
||||||
const options = {
|
const options = {
|
||||||
@@ -122,19 +129,17 @@ export async function show_blackscreen(ip: string): Promise<void> {
|
|||||||
await request_display(ip, '/showHTML', options);
|
await request_display(ip, '/showHTML', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function ping_ip(ip: string): Promise<DisplayStatus> {
|
|
||||||
const raw_response = await request_control(`/ping?ip=${ip}`, { method: 'GET' });
|
|
||||||
if (!raw_response.ok || !raw_response.json) return null;
|
|
||||||
return raw_response.json.status ? to_display_status(raw_response.json.status) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function get_thumbnail_blob(ip: string, path_to_file: string): Promise<Blob | 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' }, [415]);
|
const raw_response = await request_display(ip, `/file/preview${path_to_file}`, { method: 'GET' }, [415]);
|
||||||
if (!raw_response.ok || !raw_response.blob) return null
|
if (!raw_response.ok || !raw_response.blob) return null
|
||||||
return raw_response.blob;
|
return raw_response.blob;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function ping_ip(ip: string): Promise<DisplayStatus> {
|
||||||
|
const raw_response = await request_control(`/ping?ip=${ip}`, { method: 'GET' });
|
||||||
|
if (!raw_response.ok || !raw_response.json) return null;
|
||||||
|
return raw_response.json.status ? to_display_status(raw_response.json.status) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -204,3 +209,14 @@ function is_cd_directory_error(ip: string, shell_response: ShellCommandResponse)
|
|||||||
if (shell_response.stdout.trim() === '') return true;
|
if (shell_response.stdout.trim() === '') return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function run_shell_command(ip: string, command: string): Promise<RequestResponse> {
|
||||||
|
const options = {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
command: command
|
||||||
|
})
|
||||||
|
};
|
||||||
|
return await request_display(ip, '/shellCommand', options);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { get, writable, type Writable } from "svelte/store";
|
import { get, writable, type Writable } from "svelte/store";
|
||||||
import type { Display, FolderElement, TreeElement } from "../types";
|
import type { Display, FolderElement, TreeElement } from "../types";
|
||||||
import { displays } from "./displays";
|
import { displays, get_display_by_id } from "./displays";
|
||||||
import { selected_file_ids } from "./select";
|
import { selected_display_ids, selected_file_ids } from "./select";
|
||||||
import { get_file_data, get_file_tree_data } from "../api_handler";
|
import { get_file_data, get_file_tree_data } from "../api_handler";
|
||||||
import { notifications } from "./notification";
|
import { notifications } from "./notification";
|
||||||
import { CirclePoundSterling } from "lucide-svelte";
|
import { CirclePoundSterling } from "lucide-svelte";
|
||||||
@@ -45,8 +45,17 @@ export async function change_file_path(new_path: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function update_current_folder_on_selected_displays() {
|
||||||
|
const current_path = get(current_file_path);
|
||||||
|
for (const display_id of get(selected_display_ids)) {
|
||||||
|
const display = get_display_by_id(display_id, get(displays));
|
||||||
|
if (!display) continue;
|
||||||
|
update_folder_elements_recursively(display, current_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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[][] {
|
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 [];
|
if (!all_files.hasOwnProperty(path)) return [selected_display_ids, []];
|
||||||
const missing: string[] = [];
|
const missing: string[] = [];
|
||||||
const colliding: string[] = [];
|
const colliding: string[] = [];
|
||||||
Display:
|
Display:
|
||||||
@@ -68,6 +77,17 @@ export function get_display_ids_where_file_is_missing(path: string, file: Folder
|
|||||||
return [missing, colliding];
|
return [missing, colliding];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function get_display_ids_where_path_does_not_exist(path: string, selected_display_ids: string[], all_files: Record<string, Record<string, FolderElement[]>>): string[] {
|
||||||
|
if (!all_files.hasOwnProperty(path)) return selected_display_ids;
|
||||||
|
const out: string[] = [];
|
||||||
|
for (const selected_display_id of selected_display_ids) {
|
||||||
|
if (!all_files[path].hasOwnProperty(selected_display_id)) {
|
||||||
|
out.push(selected_display_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
async function get_changed_directory_paths(display: Display, file_path: string): Promise<string[] | null> {
|
async function get_changed_directory_paths(display: Display, file_path: string): Promise<string[] | null> {
|
||||||
const current_folder = await get_file_tree_data(display.ip, file_path);
|
const current_folder = await get_file_tree_data(display.ip, file_path);
|
||||||
if (current_folder === null) return [file_path];
|
if (current_folder === null) return [file_path];
|
||||||
@@ -103,6 +123,7 @@ function get_recursive_changed_directory_paths(display: Display, current_file_pa
|
|||||||
|
|
||||||
export async function update_folder_elements_recursively(display: Display, file_path: string = '/'): Promise<number> {
|
export async function update_folder_elements_recursively(display: Display, file_path: string = '/'): Promise<number> {
|
||||||
const new_folder_elements = await get_file_data(display.ip, file_path);
|
const new_folder_elements = await get_file_data(display.ip, file_path);
|
||||||
|
if (new_folder_elements === null) return 0;
|
||||||
all_files.update((files: Record<string, Record<string, FolderElement[]>>) => {
|
all_files.update((files: Record<string, Record<string, FolderElement[]>>) => {
|
||||||
if (!files.hasOwnProperty(file_path)) {
|
if (!files.hasOwnProperty(file_path)) {
|
||||||
files[file_path] = {};
|
files[file_path] = {};
|
||||||
@@ -223,8 +244,54 @@ function sort_files(files: FolderElement[]) {
|
|||||||
if (nameCompare !== 0) return nameCompare;
|
if (nameCompare !== 0) return nameCompare;
|
||||||
|
|
||||||
// Wenn name gleich, absteigend nach date_created
|
// Wenn name gleich, absteigend nach date_created
|
||||||
|
if (!b.date_created || !a.date_created) return -1;
|
||||||
return b.date_created.getTime() - a.date_created.getTime();
|
return b.date_created.getTime() - a.date_created.getTime();
|
||||||
});
|
});
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function get_file_from_id(file_id: string, all_files: Record<string, Record<string, FolderElement[]>>, current_file_path: string): FolderElement | null {
|
||||||
|
const current_path_elements: Record<string, FolderElement[]> | undefined = all_files[current_file_path];
|
||||||
|
if (!current_path_elements) return null;
|
||||||
|
const all_folder_elements = Object.values(current_path_elements).flat();
|
||||||
|
const found = all_folder_elements.find(el => el.id === file_id);
|
||||||
|
if (!found) return null;
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function run_for_selected_files_on_selected_displays(action: (ip: string, file_names: string[]) => Promise<void>): Promise<void> {
|
||||||
|
const files = get(all_files);
|
||||||
|
const file_path = get(current_file_path);
|
||||||
|
const folder_element_hashs: string[] = get(selected_file_ids)
|
||||||
|
.map((file_id) => get_file_from_id(file_id, files, file_path))
|
||||||
|
.filter((element) => element !== null)
|
||||||
|
.map((folder_element) => folder_element.hash)
|
||||||
|
.filter((hash) => hash !== null);
|
||||||
|
|
||||||
|
for (const display_id of get(selected_display_ids)) {
|
||||||
|
if (!files[file_path].hasOwnProperty(display_id)) continue;
|
||||||
|
const files_on_display = files[file_path][display_id];
|
||||||
|
|
||||||
|
const selected_file_names_on_display: string[] = files_on_display.filter((e) => e.hash && folder_element_hashs.includes(e.hash)).map((folder_element) => folder_element.name);
|
||||||
|
if (selected_file_names_on_display.length === 0) continue;
|
||||||
|
|
||||||
|
const display = get_display_by_id(display_id, get(displays));
|
||||||
|
if (!display) continue
|
||||||
|
|
||||||
|
await action(display.ip, selected_file_names_on_display);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get_longest_existing_path_and_needed_parts(path: string, display_id: string, all_files: Record<string, Record<string, FolderElement[]>>): { existing: string; needed: string[] } {
|
||||||
|
const path_parts = path.slice(0, path.length - 1).split('/');
|
||||||
|
for (let i = path_parts.length; i > 1; i--) {
|
||||||
|
const current_path = [...path_parts].splice(0, i).join('/') + '/';;
|
||||||
|
if (all_files.hasOwnProperty(current_path)) {
|
||||||
|
if (all_files[current_path].hasOwnProperty(display_id)) {
|
||||||
|
return { existing: current_path, needed: [...path_parts].splice(i) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { existing: '/', needed: path_parts };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user