mirror of
https://codeberg.org/PLG-Development/PLG-MuDiCS
synced 2026-07-05 16:37: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 PathBar from './PathBar.svelte';
|
||||
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 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';
|
||||
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';
|
||||
|
||||
let current_name: string = '';
|
||||
let current_valid: boolean = false;
|
||||
let current_name: string = $state('');
|
||||
let current_valid: boolean = $state(false);
|
||||
|
||||
let popup_content: PopupContent = $state({
|
||||
open: false,
|
||||
@@ -40,6 +53,8 @@
|
||||
}
|
||||
|
||||
const show_new_folder_popup = () => {
|
||||
current_name = '';
|
||||
current_valid = false;
|
||||
popup_content = {
|
||||
open: true,
|
||||
snippet: new_folder_popup,
|
||||
@@ -48,35 +63,120 @@
|
||||
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>
|
||||
|
||||
{#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
|
||||
)
|
||||
{#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
|
||||
focused_on_start={true}
|
||||
bind:current_value={current_name}
|
||||
bind: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'];
|
||||
)
|
||||
return [false, 'Name bereits verwendet'];
|
||||
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 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>
|
||||
{/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-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">
|
||||
@@ -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 bg-stone-750 rounded-xl">
|
||||
<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">
|
||||
<Button
|
||||
title="Neuen Ordner erstellen (Neuen Ordner mit ausgewählten Objekten erstellen)"
|
||||
@@ -152,13 +252,14 @@
|
||||
hover_bg="bg-red-400"
|
||||
active_bg="bg-red-500"
|
||||
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 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}
|
||||
<span class="text-stone-450 px-10 py-6 leading-relaxed text-center">
|
||||
Es wurden keine Bildschirme ausgewählt.
|
||||
|
||||
@@ -46,7 +46,10 @@
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
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);
|
||||
// Update thumbnail_url automatically if data is available
|
||||
@@ -88,18 +91,21 @@
|
||||
}
|
||||
|
||||
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 factor = is_selected ? -1 : 1;
|
||||
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 {
|
||||
if (not_interactable) return 'border-stone-550';
|
||||
const color = is_selected ? 'border-stone-450' : 'border-stone-550';
|
||||
const factor = is_selected ? 1 : 1;
|
||||
return `${color} group-hover:${get_shifted_color(color, factor * 100)} group-active:${get_shifted_color(color, factor * 150)}`;
|
||||
}
|
||||
|
||||
function onclick(e: Event) {
|
||||
if (not_interactable) return;
|
||||
select(selected_file_ids, file.id);
|
||||
e.stopPropagation();
|
||||
}
|
||||
@@ -115,51 +121,53 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row h-{$current_height.file} w-full">
|
||||
<div class="h-{$current_height.file} aspect-square max-w-15 flex">
|
||||
<Button
|
||||
disabled={!is_folder && get_file_type(file) === null}
|
||||
title={!is_folder && get_file_type(file) === null ? 'Dateityp nicht unterstützt' : ''}
|
||||
className="flex rounded-l-lg rounded-r-none {is_folder
|
||||
? 'text-stone-450'
|
||||
: 'text-stone-800'} w-full"
|
||||
div_class="w-full"
|
||||
bg={get_selectable_color_classes(
|
||||
!is_folder && get_file_type(file) !== null,
|
||||
{
|
||||
bg: true
|
||||
},
|
||||
-50
|
||||
)}
|
||||
hover_bg={get_selectable_color_classes(
|
||||
!is_folder,
|
||||
{
|
||||
bg: true
|
||||
},
|
||||
50
|
||||
)}
|
||||
active_bg={get_selectable_color_classes(
|
||||
!is_folder,
|
||||
{
|
||||
bg: true
|
||||
},
|
||||
100
|
||||
)}
|
||||
click_function={(e) => {
|
||||
open();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{#if is_folder}
|
||||
<ArrowRight class="size-full" strokeWidth="3" />
|
||||
{:else if get_display_ids_where_file_is_missing($current_file_path, file, $selected_display_ids, $all_files)[0].length !== 0}
|
||||
<RefreshPlay className="size-full" />
|
||||
{:else if get_file_type(file) !== null}
|
||||
<Play class="size-full" strokeWidth="3" />
|
||||
{:else}
|
||||
<Ban class="size-full" strokeWidth="3" />
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
{#if !not_interactable}
|
||||
<div class="h-{$current_height.file} aspect-square max-w-15 flex">
|
||||
<Button
|
||||
disabled={!is_folder && get_file_type(file) === null}
|
||||
title={!is_folder && get_file_type(file) === null ? 'Dateityp nicht unterstützt' : ''}
|
||||
className="flex rounded-l-lg rounded-r-none {is_folder
|
||||
? 'text-stone-450'
|
||||
: 'text-stone-800'} w-full"
|
||||
div_class="w-full"
|
||||
bg={get_selectable_color_classes(
|
||||
!is_folder && get_file_type(file) !== null,
|
||||
{
|
||||
bg: true
|
||||
},
|
||||
-50
|
||||
)}
|
||||
hover_bg={get_selectable_color_classes(
|
||||
!is_folder,
|
||||
{
|
||||
bg: true
|
||||
},
|
||||
50
|
||||
)}
|
||||
active_bg={get_selectable_color_classes(
|
||||
!is_folder,
|
||||
{
|
||||
bg: true
|
||||
},
|
||||
100
|
||||
)}
|
||||
click_function={(e) => {
|
||||
open();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{#if is_folder}
|
||||
<ArrowRight class="size-full" strokeWidth="3" />
|
||||
{:else if get_display_ids_where_file_is_missing($current_file_path, file, $selected_display_ids, $all_files)[0].length !== 0}
|
||||
<RefreshPlay className="size-full" />
|
||||
{:else if get_file_type(file) !== null}
|
||||
<Play class="size-full" strokeWidth="3" />
|
||||
{:else}
|
||||
<Ban class="size-full" strokeWidth="3" />
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@@ -167,12 +175,17 @@
|
||||
if (e.key === 'Enter' || e.key === ' ') onclick(e);
|
||||
}}
|
||||
{onclick}
|
||||
class="{get_selectable_color_classes(is_selected(file.id, $selected_file_ids), {
|
||||
bg: true,
|
||||
hover: true,
|
||||
active: 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"
|
||||
class="{get_selectable_color_classes(
|
||||
!not_interactable && is_selected(file.id, $selected_file_ids),
|
||||
{
|
||||
bg: true,
|
||||
hover: !not_interactable,
|
||||
active: !not_interactable,
|
||||
text: true
|
||||
}
|
||||
)} {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="aspect-square rounded-md flex justify-center items-center">
|
||||
@@ -203,7 +216,7 @@
|
||||
is_selected(file.id, $selected_file_ids)
|
||||
)} 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
|
||||
className="h-8 aspect-square transition-colors duration-200 !p-1.5 text-stone-100"
|
||||
bg="bg-red-500"
|
||||
@@ -229,7 +242,7 @@
|
||||
>
|
||||
<RefreshCcwDot class="size-full" />
|
||||
</Button>
|
||||
{/if}
|
||||
{/if} -->
|
||||
<div
|
||||
class="w-14 content-center text-center select-none text-xs whitespace-nowrap"
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
name: string;
|
||||
type: string;
|
||||
@@ -44,25 +44,20 @@ export async function get_file_data(ip: string, path: string): Promise<FolderEle
|
||||
created: string;
|
||||
}
|
||||
|
||||
const options = {
|
||||
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")
|
||||
size=$(stat -c '%s' -- "$f")
|
||||
created=$(stat -c '%w' -- "$f")
|
||||
[ "$created" = "-" ] && created=$(stat -c '%y' -- "$f")
|
||||
jq -n --arg name "$f" --arg type "$typ" --arg size "$size" --arg created "$created" \
|
||||
'{name:$name, type:$type, size:($size|tostring), created:$created}' | tr -d '\n'
|
||||
echo
|
||||
done
|
||||
` })
|
||||
};
|
||||
const raw_response = await request_display(ip, '/shellCommand', options);
|
||||
if (!raw_response.ok || !raw_response.json) return [];
|
||||
const raw_response = await run_shell_command(ip, `cd ".${path}" && find . -maxdepth 1 -mindepth 1 -print0 | while IFS= read -r -d '' f; do
|
||||
typ=$(file -b --mime-type -- "$f")
|
||||
size=$(stat -c '%s' -- "$f")
|
||||
created=$(stat -c '%w' -- "$f")
|
||||
[ "$created" = "-" ] && created=$(stat -c '%y' -- "$f")
|
||||
jq -n --arg name "$f" --arg type "$typ" --arg size "$size" --arg created "$created" \
|
||||
'{name:$name, type:$type, size:($size|tostring), created:$created}' | tr -d '\n'
|
||||
echo
|
||||
done
|
||||
`);
|
||||
if (!raw_response.ok || !raw_response.json) return null;
|
||||
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()
|
||||
.split("\n")
|
||||
@@ -94,14 +89,7 @@ done
|
||||
}
|
||||
|
||||
export async function get_file_tree_data(ip: string, path: string): Promise<TreeElement[] | null> {
|
||||
const options = {
|
||||
method: 'PATCH',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
command: `cd ".${path}" && tree -Js`
|
||||
})
|
||||
};
|
||||
const raw_response = await request_display(ip, '/shellCommand', options);
|
||||
const raw_response = await run_shell_command(ip, `cd ".${path}" && tree -Js`);
|
||||
|
||||
if (!raw_response.ok || !raw_response.json) return null;
|
||||
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;
|
||||
}
|
||||
|
||||
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> {
|
||||
const options = {
|
||||
@@ -122,19 +129,17 @@ export async function show_blackscreen(ip: string): Promise<void> {
|
||||
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> {
|
||||
const raw_response = await request_display(ip, `/file/preview${path_to_file}`, { method: 'GET' }, [415]);
|
||||
if (!raw_response.ok || !raw_response.blob) return null
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -203,4 +208,15 @@ function is_cd_directory_error(ip: string, shell_response: ShellCommandResponse)
|
||||
}
|
||||
if (shell_response.stdout.trim() === '') return true;
|
||||
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 type { Display, FolderElement, TreeElement } from "../types";
|
||||
import { displays } from "./displays";
|
||||
import { selected_file_ids } from "./select";
|
||||
import { displays, get_display_by_id } from "./displays";
|
||||
import { selected_display_ids, 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";
|
||||
@@ -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[][] {
|
||||
if (!all_files.hasOwnProperty(path)) return [];
|
||||
if (!all_files.hasOwnProperty(path)) return [selected_display_ids, []];
|
||||
const missing: string[] = [];
|
||||
const colliding: string[] = [];
|
||||
Display:
|
||||
@@ -68,6 +77,17 @@ export function get_display_ids_where_file_is_missing(path: string, file: Folder
|
||||
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> {
|
||||
const current_folder = await get_file_tree_data(display.ip, 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> {
|
||||
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[]>>) => {
|
||||
if (!files.hasOwnProperty(file_path)) {
|
||||
files[file_path] = {};
|
||||
@@ -223,8 +244,54 @@ function sort_files(files: FolderElement[]) {
|
||||
if (nameCompare !== 0) return nameCompare;
|
||||
|
||||
// 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 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