mirror of
https://codeberg.org/PLG-Development/PLG-MuDiCS
synced 2026-07-06 00:47:09 +00:00
improve folder view - test with display-api
This commit is contained in:
Generated
+6
-6
@@ -1155,9 +1155,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sveltejs/kit": {
|
"node_modules/@sveltejs/kit": {
|
||||||
"version": "2.46.4",
|
"version": "2.47.3",
|
||||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.46.4.tgz",
|
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.47.3.tgz",
|
||||||
"integrity": "sha512-J1fd80WokLzIm6EAV7z7C2+/C02qVAX645LZomARARTRJkbbJSY1Jln3wtBZYibUB8c9/5Z6xqLAV39VdbtWCQ==",
|
"integrity": "sha512-zN2yzBc2dIES2BSzLhNP2weYhwB77kgM/oAktICZVmmljyEmPZrlUwr14jjdK9/eDu7WdAuf6gTdYIJLTcN3Fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@@ -4018,9 +4018,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.1.9",
|
"version": "7.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz",
|
||||||
"integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==",
|
"integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
@theme {
|
@theme {
|
||||||
--color-stone-150: oklch(0.9465 0.002 77.571);
|
--color-stone-150: oklch(0.9465 0.002 77.571);
|
||||||
--color-stone-250: oklch(0.896 0.004 52.542);
|
--color-stone-250: oklch(0.896 0.004 52.542);
|
||||||
--color-stone-350: oklch(0.711 0.009 57.218);
|
--color-stone-350: oklch(0.789 0.0075 56.313);
|
||||||
--color-stone-450: oklch(0.631 0.0115 57.165);
|
--color-stone-450: oklch(0.631 0.0115 57.165);
|
||||||
--color-stone-550: oklch(0.4985 0.012 65.855);
|
--color-stone-550: oklch(0.4985 0.012 65.855);
|
||||||
--color-stone-650: oklch(0.409 0.0105 70.599);
|
--color-stone-650: oklch(0.409 0.0105 70.599);
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
title = '',
|
title = '',
|
||||||
click_function = (e: MouseEvent) => {},
|
click_function = (e: MouseEvent) => {},
|
||||||
menu_options = null,
|
menu_options = null,
|
||||||
|
menu_class = 'right-0',
|
||||||
|
div_class = '',
|
||||||
children
|
children
|
||||||
} = $props<{
|
} = $props<{
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -23,6 +25,8 @@
|
|||||||
title?: string;
|
title?: string;
|
||||||
click_function?: (e: MouseEvent) => void;
|
click_function?: (e: MouseEvent) => void;
|
||||||
menu_options?: MenuOption[] | null;
|
menu_options?: MenuOption[] | null;
|
||||||
|
menu_class?: string;
|
||||||
|
div_class?: string,
|
||||||
children?: any;
|
children?: any;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@@ -126,7 +130,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative min-w-0 flex" {title}>
|
<div class="relative min-w-0 flex {div_class}" {title}>
|
||||||
<button
|
<button
|
||||||
bind:this={button_element}
|
bind:this={button_element}
|
||||||
class="{className} {menu_shown ? hover_bg : bg} {disabled
|
class="{className} {menu_shown ? hover_bg : bg} {disabled
|
||||||
@@ -150,7 +154,7 @@
|
|||||||
transition:fade={{ duration: 50 }}
|
transition:fade={{ duration: 50 }}
|
||||||
class="absolute {position_bottom
|
class="absolute {position_bottom
|
||||||
? 'top-full'
|
? 'top-full'
|
||||||
: 'bottom-full'} right-0 z-100 my-1.5 min-w-64 rounded-xl backdrop-blur bg-stone-600/30 border border-stone-400/10 shadow-xl/20 p-2 flex flex-col gap-2 text-stone-200 cursor-auto"
|
: 'bottom-full'} {menu_class} z-100 my-1.5 min-w-64 rounded-xl backdrop-blur bg-stone-600/30 border border-stone-400/10 shadow-xl/20 p-2 flex flex-col gap-2 text-stone-200 cursor-auto"
|
||||||
onclick={(e) => {
|
onclick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
@@ -175,7 +179,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="pr-2">
|
<div class="truncate min-w-0" title={option.name}>
|
||||||
{option.name}
|
{option.name}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -7,16 +7,14 @@
|
|||||||
import {
|
import {
|
||||||
add_empty_display_group,
|
add_empty_display_group,
|
||||||
all_displays_of_group_selected,
|
all_displays_of_group_selected,
|
||||||
is_selected,
|
|
||||||
remove_empty_display_groups,
|
remove_empty_display_groups,
|
||||||
select,
|
|
||||||
select_all_of_group,
|
select_all_of_group,
|
||||||
selected_display_ids,
|
|
||||||
set_new_display_group_data
|
set_new_display_group_data
|
||||||
} from '../ts/stores/displays';
|
} from '../ts/stores/displays';
|
||||||
import DNDGrip from './DNDGrip.svelte';
|
import DNDGrip from './DNDGrip.svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import type { DisplayGroup } from '../ts/types';
|
import type { DisplayGroup } from '../ts/types';
|
||||||
|
import { selected_display_ids } from '../ts/stores/select';
|
||||||
|
|
||||||
let { display_group } = $props<{
|
let { display_group } = $props<{
|
||||||
display_group: DisplayGroup;
|
display_group: DisplayGroup;
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Button from './Button.svelte';
|
import Button from './Button.svelte';
|
||||||
import { is_selected, select, selected_display_ids } from '../ts/stores/displays';
|
|
||||||
import {
|
import {
|
||||||
display_screen_height,
|
current_height,
|
||||||
get_selectable_color_classes,
|
get_selectable_color_classes,
|
||||||
pinned_display_id
|
pinned_display_id
|
||||||
} from '../ts/stores/ui_behavior';
|
} from '../ts/stores/ui_behavior';
|
||||||
import DNDGrip from './DNDGrip.svelte';
|
import DNDGrip from './DNDGrip.svelte';
|
||||||
import { Menu, Pencil, Pin, PinOff, Trash2, VideoOff, X } from 'lucide-svelte';
|
import { Menu, Pencil, Pin, PinOff, Trash2, VideoOff, X } from 'lucide-svelte';
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
import OnlineState from './OnlineState.svelte';
|
import OnlineState from './OnlineState.svelte';
|
||||||
import type { Display } from '../ts/types';
|
import type { Display } from '../ts/types';
|
||||||
|
import { is_selected, select, selected_display_ids } from '../ts/stores/select';
|
||||||
|
|
||||||
let { display } = $props<{
|
let { display } = $props<{
|
||||||
display: Display;
|
display: Display;
|
||||||
@@ -19,7 +18,7 @@
|
|||||||
let hovering_unselectable = $state(false);
|
let hovering_unselectable = $state(false);
|
||||||
|
|
||||||
function onclick(e: Event) {
|
function onclick(e: Event) {
|
||||||
select(display.id);
|
select(selected_display_ids, display.id);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +44,7 @@
|
|||||||
hover: true,
|
hover: true,
|
||||||
active: !hovering_unselectable,
|
active: !hovering_unselectable,
|
||||||
text: true
|
text: true
|
||||||
})} rounded-xl flex flex-row justify-between h-{$display_screen_height} transition-colors duration-100 gap-2 cursor-pointer w-full text-stone-200"
|
})} rounded-xl flex flex-row justify-between h-{$current_height.display} transition-colors duration-100 gap-2 cursor-pointer w-full text-stone-200"
|
||||||
>
|
>
|
||||||
<div class="flex flex-row gap-4 min-w-0 flex-1">
|
<div class="flex flex-row gap-4 min-w-0 flex-1">
|
||||||
<!-- Left Preview Screen -->
|
<!-- Left Preview Screen -->
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { CloudAlert, Folder, RefreshCcwDot, Shield } from 'lucide-svelte';
|
|
||||||
import { get_selectable_color_classes } from '../ts/stores/ui_behavior';
|
|
||||||
import Button from './Button.svelte';
|
|
||||||
|
|
||||||
let {} = $props<{}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="p-1 {get_selectable_color_classes(false, {
|
|
||||||
bg: true,
|
|
||||||
hover: true,
|
|
||||||
active: true,
|
|
||||||
text: true
|
|
||||||
})} h-10 rounded-lg transition-colors duration-200 gap-4 flex flex-row justify-between cursor-pointer group"
|
|
||||||
>
|
|
||||||
<div class="flex flex-row gap-2 min-w-0">
|
|
||||||
<div class="aspect-square rounded-md flex justify-center items-center">
|
|
||||||
<Folder />
|
|
||||||
</div>
|
|
||||||
<div class="content-center truncate select-none">
|
|
||||||
Tolle
|
|
||||||
Dateifjisodfhsuidfhdufighdfiughdfuighdfuihguifdghiudfhguidfgdfijghdfjkghdfkjghdfkjghdfkughdfjghdfkjghdfkjghfdkghdfkjghdfkghdfkghdfkjghdfkghdfkjghdfgkhdfkghfdkjghdfkghfdkghdfkjghdfjkghdk.zip
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row gap-1 pr-1 text-stone-400 group-hover:text-stone-300 group-active:text-stone-250 duration-200 transition-colors">
|
|
||||||
<Button
|
|
||||||
className="h-8 aspect-square transition-colors duration-200 !p-1.5"
|
|
||||||
bg="bg-transparent"
|
|
||||||
hover_bg={get_selectable_color_classes(false, {
|
|
||||||
bg: true
|
|
||||||
})}
|
|
||||||
active_bg={get_selectable_color_classes(false, {
|
|
||||||
bg: true
|
|
||||||
})}
|
|
||||||
click_function={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RefreshCcwDot class="size-full" />
|
|
||||||
</Button>
|
|
||||||
<div class="content-center select-none text-xs whitespace-nowrap">25.09.25</div>
|
|
||||||
<div class="border border-stone-550 group-hover:border-stone-450 group-active:border-stone-400 duration-200 transition-colors my-1"></div>
|
|
||||||
<div class="content-center select-none text-xs whitespace-nowrap">MPEG</div>
|
|
||||||
<div class="border border-stone-550 group-hover:border-stone-450 group-active:border-stone-400 duration-200 transition-colors my-1"></div>
|
|
||||||
<div class="content-center select-none text-xs whitespace-nowrap">523 MB</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ArrowRight, Folder, Play, RefreshCcwDot, TriangleAlert } from 'lucide-svelte';
|
||||||
|
import {
|
||||||
|
current_height,
|
||||||
|
get_selectable_color_classes,
|
||||||
|
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 {
|
||||||
|
is_selected,
|
||||||
|
select,
|
||||||
|
selected_display_ids,
|
||||||
|
selected_file_ids
|
||||||
|
} from '../ts/stores/select';
|
||||||
|
import {
|
||||||
|
all_files,
|
||||||
|
change_file_path,
|
||||||
|
current_file_path,
|
||||||
|
get_display_ids_where_file_is_missing
|
||||||
|
} from '../ts/stores/files';
|
||||||
|
import RefreshPlay from './RefreshPlay.svelte';
|
||||||
|
|
||||||
|
let { file } = $props<{ file: FolderElement }>();
|
||||||
|
|
||||||
|
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 (
|
||||||
|
get_formated_date_string(date_object, true) + ' ' + get_formated_time_string(date_object)
|
||||||
|
);
|
||||||
|
} else if (date_object.toDateString() === new Date().toDateString()) {
|
||||||
|
return get_formated_time_string(date_object);
|
||||||
|
} else {
|
||||||
|
return get_formated_date_string(date_object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_formated_time_string(date_object: Date) {
|
||||||
|
return `${date_object.getHours().toString().padStart(2, '0')}:${date_object.getMinutes().toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_formated_date_string(date_object: Date, full_year = false) {
|
||||||
|
return `${date_object.getDate().toString().padStart(2, '0')}.${(date_object.getMonth() + 1).toString().padStart(2, '0')}.${date_object
|
||||||
|
.getFullYear()
|
||||||
|
.toString()
|
||||||
|
.slice(full_year ? 0 : 2)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_grayed_out_text_color_strings(is_selected: boolean): string {
|
||||||
|
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 {
|
||||||
|
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) {
|
||||||
|
select(selected_file_ids, file.id);
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
if (is_folder) {
|
||||||
|
change_file_path($current_file_path + file.name + '/');
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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
|
||||||
|
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,
|
||||||
|
{
|
||||||
|
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}
|
||||||
|
<Play class="size-full" strokeWidth="3" />
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
onkeydown={(e) => {
|
||||||
|
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 h-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 p-2">
|
||||||
|
{#if is_folder}
|
||||||
|
<Folder class="size-full" />
|
||||||
|
{:else if file.thumbnail}
|
||||||
|
<div></div>
|
||||||
|
{:else if get_file_type(file)?.icon}
|
||||||
|
{@const Icon = get_file_type(file)?.icon}
|
||||||
|
<Icon class="size-full" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="content-center truncate select-none w-full" title={file.name}>
|
||||||
|
{file.name.includes('.') ? file.name.slice(0, file.name.lastIndexOf('.')) : file.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class=" p-1 flex flex-row items-center gap-1 pr-1 {get_grayed_out_text_color_strings(
|
||||||
|
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}
|
||||||
|
<Button
|
||||||
|
className="h-8 aspect-square transition-colors duration-200 !p-1.5 text-stone-100"
|
||||||
|
bg="bg-red-500"
|
||||||
|
click_function={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TriangleAlert class="size-full" />
|
||||||
|
</Button>
|
||||||
|
{:else if get_display_ids_where_file_is_missing($current_file_path, file, $selected_display_ids, $all_files)[0].length !== 0}
|
||||||
|
<Button
|
||||||
|
className="h-8 aspect-square transition-colors duration-200 !p-1.5"
|
||||||
|
bg="bg-transparent"
|
||||||
|
hover_bg={get_selectable_color_classes(false, {
|
||||||
|
bg: true
|
||||||
|
})}
|
||||||
|
active_bg={get_selectable_color_classes(false, {
|
||||||
|
bg: true
|
||||||
|
})}
|
||||||
|
click_function={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RefreshCcwDot class="size-full" />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
<div
|
||||||
|
class="w-14 content-center text-center select-none text-xs whitespace-nowrap"
|
||||||
|
title={get_created_string(file.date_created, true)}
|
||||||
|
>
|
||||||
|
{get_created_string(file.date_created)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="h-[70%] border {get_grayed_out_border_color_strings(
|
||||||
|
is_selected(file.id, $selected_file_ids)
|
||||||
|
)} duration-200 transition-colors my-1"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="w-12 content-center text-center select-none text-xs whitespace-nowrap truncate"
|
||||||
|
title={file.type}
|
||||||
|
>
|
||||||
|
{is_folder ? 'Ordner' : (get_file_type(file)?.display_name ?? '?')}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="h-[70%] border {get_grayed_out_border_color_strings(
|
||||||
|
is_selected(file.id, $selected_file_ids)
|
||||||
|
)} duration-200 transition-colors"
|
||||||
|
></div>
|
||||||
|
<div class="w-12 content-center text-center select-none text-xs whitespace-nowrap">
|
||||||
|
{file.size}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ChevronRight, House } from 'lucide-svelte';
|
||||||
|
import Button from './Button.svelte';
|
||||||
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
import type { MenuOption } from '../ts/types';
|
||||||
|
import { change_file_path, current_file_path } from '../ts/stores/files';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
import { flip } from 'svelte/animate';
|
||||||
|
import { cubicOut } from 'svelte/easing';
|
||||||
|
|
||||||
|
let { bg = 'bg-stone-700' } = $props<{
|
||||||
|
bg?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
let outside_container: HTMLDivElement;
|
||||||
|
let inside_container: HTMLDivElement;
|
||||||
|
let resize_observer: ResizeObserver;
|
||||||
|
let w10_div: HTMLDivElement;
|
||||||
|
|
||||||
|
let cut_folders: number = $state(0);
|
||||||
|
// let folders_length: number = $state(get_folders($current_file_path).length);
|
||||||
|
// let all_folders_length: number = $state(get_folders($current_file_path).length);
|
||||||
|
|
||||||
|
function get_folders(path: string): string[] {
|
||||||
|
path = path.slice(1); // Cut first '/'
|
||||||
|
if (path === '') return [];
|
||||||
|
if (path.endsWith('/')) {
|
||||||
|
path = path.slice(0, path.length - 1);
|
||||||
|
}
|
||||||
|
return path.split('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_sliced_folders(path: string, cut_folders: number): string[] {
|
||||||
|
const folders = get_folders(path);
|
||||||
|
let sliced_folders: string[] = [];
|
||||||
|
if (cut_folders !== get_folders($current_file_path).length)
|
||||||
|
sliced_folders = folders.slice(cut_folders);
|
||||||
|
return sliced_folders;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_hidden_folders(path: string, cut_folders: number): string[] {
|
||||||
|
const folders = get_folders(path);
|
||||||
|
let hidden_folders: string[] = [];
|
||||||
|
if (cut_folders !== get_folders($current_file_path).length)
|
||||||
|
hidden_folders = folders.slice(0, cut_folders);
|
||||||
|
return hidden_folders;
|
||||||
|
}
|
||||||
|
|
||||||
|
function recalc() {
|
||||||
|
if (!outside_container || !inside_container || !w10_div) return;
|
||||||
|
const first_shrink = cut_folders === 0;
|
||||||
|
const second_last_grow = cut_folders === 2;
|
||||||
|
|
||||||
|
const difference = outside_container.offsetWidth - inside_container.offsetWidth;
|
||||||
|
|
||||||
|
const w10_px = parseFloat(getComputedStyle(w10_div).width);
|
||||||
|
|
||||||
|
if ((!first_shrink && difference < 2 * w10_px) || (first_shrink && difference < w10_px)) {
|
||||||
|
if (cut_folders < get_folders($current_file_path).length) {
|
||||||
|
cut_folders += first_shrink ? 2 : 1;
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
(!second_last_grow && difference > 6 * w10_px) ||
|
||||||
|
(second_last_grow && difference > 7 * w10_px)
|
||||||
|
) {
|
||||||
|
if (cut_folders >= 1) {
|
||||||
|
if (second_last_grow) {
|
||||||
|
cut_folders -= 2;
|
||||||
|
} else {
|
||||||
|
cut_folders -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_hidden_menu_options(hidden_folders: string[], path: string): MenuOption[] {
|
||||||
|
const out: MenuOption[] = [];
|
||||||
|
for (let i = 0; i < hidden_folders.length; i++) {
|
||||||
|
out.push({
|
||||||
|
name: ' '.repeat(i) + hidden_folders[i],
|
||||||
|
class: 'truncate max-w-80',
|
||||||
|
on_select: () => {
|
||||||
|
open_path(i + 1, path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function open_path(index_of_all_folders: number, path: string) {
|
||||||
|
let new_path = '/';
|
||||||
|
const all_folders = get_folders(path);
|
||||||
|
for (let i = 0; i < index_of_all_folders; i++) {
|
||||||
|
new_path += all_folders[i] + '/';
|
||||||
|
}
|
||||||
|
change_file_path(new_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
resize_observer = new ResizeObserver(() => recalc());
|
||||||
|
resize_observer.observe(outside_container);
|
||||||
|
resize_observer.observe(inside_container);
|
||||||
|
// initial
|
||||||
|
setTimeout(recalc, 0);
|
||||||
|
});
|
||||||
|
onDestroy(() => resize_observer?.disconnect());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="{bg} rounded-xl flex" bind:this={outside_container}>
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<div bind:this={w10_div} class="flex">
|
||||||
|
<Button
|
||||||
|
className="py-1 shrink-0 grow-0 w-10"
|
||||||
|
{bg}
|
||||||
|
click_function={(e) => {
|
||||||
|
open_path(0, $current_file_path);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<House class="size-full" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{#if cut_folders !== 0}
|
||||||
|
<Button
|
||||||
|
className="pl-0 py-1 grow"
|
||||||
|
{bg}
|
||||||
|
menu_options={get_hidden_menu_options(
|
||||||
|
get_hidden_folders($current_file_path, cut_folders),
|
||||||
|
$current_file_path
|
||||||
|
)}
|
||||||
|
menu_class="left-0"
|
||||||
|
>
|
||||||
|
<ChevronRight class="shrink-0 text-stone-500 h-full" />
|
||||||
|
<span class="font-bold" title="Weiteren Pfad zeigen"> ... </span>
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row" bind:this={inside_container}>
|
||||||
|
{#each get_sliced_folders($current_file_path, cut_folders) as folder, i (i)}
|
||||||
|
<div animate:flip={{ duration: 100, easing: cubicOut }}>
|
||||||
|
<Button
|
||||||
|
className="shrink-0 py-1 pl-0 {i ===
|
||||||
|
get_sliced_folders($current_file_path, cut_folders).length - 1
|
||||||
|
? 'max-w-80 font-bold'
|
||||||
|
: 'max-w-30'}"
|
||||||
|
{bg}
|
||||||
|
click_function={(e) => {
|
||||||
|
open_path(cut_folders + i + 1, $current_file_path);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ChevronRight class="shrink-0 text-stone-500 h-full" />
|
||||||
|
<span class="truncate" title={folder}>
|
||||||
|
{folder}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let {className = ''} = $props<{className?: string}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2.7"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="{className} lucide lucide-refresh-ccw-dot-icon lucide-refresh-ccw-dot"
|
||||||
|
>
|
||||||
|
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
|
||||||
|
<path d="M3 3v5h5" />
|
||||||
|
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" />
|
||||||
|
<path d="M16 16h5v5" />
|
||||||
|
<path
|
||||||
|
d="M15.033 9.44a.647.647 0 0 1 0 1.12l-4.065 2.352a.645.645 0 0 1-.968-.56V7.648a.645.645 0 0 1 .967-.56z"
|
||||||
|
transform="translate(0,2.0)"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
@@ -7,10 +7,12 @@
|
|||||||
ClipboardPaste,
|
ClipboardPaste,
|
||||||
Download,
|
Download,
|
||||||
FolderOutput,
|
FolderOutput,
|
||||||
|
FolderPlus,
|
||||||
Info,
|
Info,
|
||||||
Keyboard,
|
Keyboard,
|
||||||
Menu,
|
Menu,
|
||||||
Minus,
|
Minus,
|
||||||
|
Pen,
|
||||||
Pencil,
|
Pencil,
|
||||||
PinOff,
|
PinOff,
|
||||||
Plus,
|
Plus,
|
||||||
@@ -33,13 +35,13 @@
|
|||||||
import Button from '../components/Button.svelte';
|
import Button from '../components/Button.svelte';
|
||||||
import SplashScreen from './../../../../shared/splash_screen.html?raw';
|
import SplashScreen from './../../../../shared/splash_screen.html?raw';
|
||||||
import {
|
import {
|
||||||
change_display_screen_height,
|
change_height,
|
||||||
display_screen_height,
|
current_height,
|
||||||
dnd_flip_duration_ms,
|
dnd_flip_duration_ms,
|
||||||
get_selectable_color_classes,
|
get_selectable_color_classes,
|
||||||
is_display_drag,
|
is_display_drag,
|
||||||
is_group_drag,
|
is_group_drag,
|
||||||
next_step_possible,
|
next_height_step_size,
|
||||||
pinned_display_id
|
pinned_display_id
|
||||||
} from '../ts/stores/ui_behavior';
|
} from '../ts/stores/ui_behavior';
|
||||||
import { dragHandleZone } from 'svelte-dnd-action';
|
import { dragHandleZone } from 'svelte-dnd-action';
|
||||||
@@ -47,9 +49,7 @@
|
|||||||
all_displays_of_group_selected,
|
all_displays_of_group_selected,
|
||||||
displays,
|
displays,
|
||||||
get_display_by_id,
|
get_display_by_id,
|
||||||
is_selected,
|
select_all_of_group
|
||||||
select_all_of_group,
|
|
||||||
selected_display_ids
|
|
||||||
} from '../ts/stores/displays';
|
} from '../ts/stores/displays';
|
||||||
import { cubicOut } from 'svelte/easing';
|
import { cubicOut } from 'svelte/easing';
|
||||||
import { flip } from 'svelte/animate';
|
import { flip } from 'svelte/animate';
|
||||||
@@ -58,7 +58,11 @@
|
|||||||
import OnlineState from '../components/OnlineState.svelte';
|
import OnlineState from '../components/OnlineState.svelte';
|
||||||
import type { DisplayGroup } from '../ts/types';
|
import type { DisplayGroup } from '../ts/types';
|
||||||
import PopUp from '../components/PopUp.svelte';
|
import PopUp from '../components/PopUp.svelte';
|
||||||
import FileObject from '../components/FileObject.svelte';
|
import FileObject from '../components/FolderElementObject.svelte';
|
||||||
|
import FolderElementObject from '../components/FolderElementObject.svelte';
|
||||||
|
import PathBar from '../components/PathBar.svelte';
|
||||||
|
import { all_files, current_file_path, get_current_folder_elements } from '../ts/stores/files';
|
||||||
|
import { selected_display_ids, selected_file_ids } from '../ts/stores/select';
|
||||||
|
|
||||||
let displays_scroll_box: HTMLElement;
|
let displays_scroll_box: HTMLElement;
|
||||||
|
|
||||||
@@ -93,17 +97,17 @@
|
|||||||
|
|
||||||
<svelte:window on:wheel={on_wheel} />
|
<svelte:window on:wheel={on_wheel} />
|
||||||
|
|
||||||
<main class="bg-stone-900 h-dvh w-dvw text-stone-200 p-4 gap-4 grid grid-rows-[3rem_auto]">
|
<main class="bg-stone-900 h-dvh w-dvw text-stone-200 px-4 py-2 gap-2 grid grid-rows-[3rem_auto]">
|
||||||
<!-- {@html SplashScreen} -->
|
<!-- {@html SplashScreen} -->
|
||||||
|
|
||||||
<div class="w-[calc(100dvw-(8*var(--spacing)))] flex justify-between">
|
<div class="w-[calc(100dvw-(8*var(--spacing)))] flex justify-between">
|
||||||
<span class="text-4xl font-bold content-center h-full"> PLG MuDiCS </span>
|
<span class="text-4xl font-bold content-center pl-1"> PLG MuDiCS </span>
|
||||||
<Button className="aspect-square" bg="bg-stone-800">
|
<Button className="aspect-square" bg="bg-stone-800" div_class="aspect-square">
|
||||||
<Settings></Settings>
|
<Settings></Settings>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-[calc(100dvw-(8*var(--spacing)))] grid grid-cols-2 gap-2">
|
<div class="w-[calc(100dvw-(8*var(--spacing)))] grid grid-cols-2 gap-2">
|
||||||
<div class="h-[calc(100dvh-3rem-(12*var(--spacing)))] flex flex-col gap-2">
|
<div class="h-[calc(100dvh-3rem-(6*var(--spacing)))] flex flex-col gap-2">
|
||||||
{#if $pinned_display_id}
|
{#if $pinned_display_id}
|
||||||
<!-- Pinned Item -->
|
<!-- Pinned Item -->
|
||||||
<div in:fade={{ duration: 140 }} out:fade={{ duration: 120 }}>
|
<div in:fade={{ duration: 140 }} out:fade={{ duration: 120 }}>
|
||||||
@@ -201,9 +205,9 @@
|
|||||||
title="Bildschirme größer darstellen"
|
title="Bildschirme größer darstellen"
|
||||||
className="aspect-square !p-1 rounded-r-none"
|
className="aspect-square !p-1 rounded-r-none"
|
||||||
bg="bg-stone-600"
|
bg="bg-stone-600"
|
||||||
disabled={next_step_possible($display_screen_height, 1)}
|
disabled={!Boolean(next_height_step_size('display', $current_height, 1))}
|
||||||
click_function={() => {
|
click_function={() => {
|
||||||
change_display_screen_height(1);
|
change_height('display', 1);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Plus />
|
<Plus />
|
||||||
@@ -212,9 +216,9 @@
|
|||||||
title="Bildschirme kleiner darstellen"
|
title="Bildschirme kleiner darstellen"
|
||||||
className="aspect-square !p-1 rounded-l-none"
|
className="aspect-square !p-1 rounded-l-none"
|
||||||
bg="bg-stone-600"
|
bg="bg-stone-600"
|
||||||
disabled={next_step_possible($display_screen_height, -1)}
|
disabled={!Boolean(next_height_step_size('display', $current_height, -1))}
|
||||||
click_function={() => {
|
click_function={() => {
|
||||||
change_display_screen_height(-1);
|
change_height('display', -1);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Minus />
|
<Minus />
|
||||||
@@ -256,7 +260,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="col-start-2 h-[calc(100dvh-3rem-(12*var(--spacing)))] rounded-2xl flex flex-col gap-2"
|
class="col-start-2 h-[calc(100dvh-3rem-(6*var(--spacing)))] rounded-2xl flex flex-col gap-2"
|
||||||
>
|
>
|
||||||
<div class="grid grid-rows-[2.5rem_auto] bg-stone-800 rounded-2xl min-w-0">
|
<div class="grid grid-rows-[2.5rem_auto] bg-stone-800 rounded-2xl min-w-0">
|
||||||
<div
|
<div
|
||||||
@@ -307,7 +311,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-stone-800 h-full rounded-2xl grid grid-rows-[2.5rem_auto]">
|
<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">
|
||||||
Dateien anzeigen und verwalten
|
Dateien anzeigen und verwalten
|
||||||
@@ -317,9 +321,9 @@
|
|||||||
title="Dateien größer darstellen"
|
title="Dateien größer darstellen"
|
||||||
className="aspect-square !p-1 rounded-r-none"
|
className="aspect-square !p-1 rounded-r-none"
|
||||||
bg="bg-stone-600"
|
bg="bg-stone-600"
|
||||||
disabled={next_step_possible($display_screen_height, 1)}
|
disabled={!Boolean(next_height_step_size('file', $current_height, 1))}
|
||||||
click_function={() => {
|
click_function={() => {
|
||||||
change_display_screen_height(1);
|
change_height('file', 1);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Plus />
|
<Plus />
|
||||||
@@ -328,9 +332,9 @@
|
|||||||
title="Dateien kleiner darstellen"
|
title="Dateien kleiner darstellen"
|
||||||
className="aspect-square !p-1 rounded-l-none"
|
className="aspect-square !p-1 rounded-l-none"
|
||||||
bg="bg-stone-600"
|
bg="bg-stone-600"
|
||||||
disabled={next_step_possible($display_screen_height, -1)}
|
disabled={!Boolean(next_height_step_size('file', $current_height, -1))}
|
||||||
click_function={() => {
|
click_function={() => {
|
||||||
change_display_screen_height(-1);
|
change_height('file', -1);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Minus />
|
<Minus />
|
||||||
@@ -338,35 +342,62 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 p-2 overflow-auto">
|
<div class="flex flex-col gap-2 p-2 overflow-auto">
|
||||||
<div class="flex flex-row justify-between gap-6 overflow-x-auto">
|
<div class="flex flex-col gap-2 p-2 bg-stone-750 rounded-xl">
|
||||||
<div class="flex flex-row gap-2">
|
<PathBar />
|
||||||
<Button title="Eine Verzeichnis-Ebene zurück" className="px-3 flex"><FolderOutput /></Button>
|
<div class="flex flex-row justify-between gap-6 overflow-x-auto">
|
||||||
<Button title="Datei anzeigen" className="px-3 flex gap-3">
|
<div class="flex flex-row gap-2 shrink-0">
|
||||||
<TvMinimalPlay class="shrink-0 flex"/>
|
<Button
|
||||||
<span class="min-w-0 hidden xl:flex">Anzeigen</span>
|
title="Neuen Ordner erstellen (Neuen Ordner mit ausgewählten Objekten erstellen)"
|
||||||
</Button>
|
className="px-3 flex"><FolderPlus /></Button
|
||||||
<Button
|
>
|
||||||
title="Dateien zwischen Bildschirmen synchronisieren"
|
<div class="border border-stone-700 my-1"></div>
|
||||||
className="px-3 flex gap-3"
|
<Button title="Datei(en) hochladen" className="px-3 flex"><Upload /></Button>
|
||||||
><RefreshCcw />
|
<Button
|
||||||
<span class="hidden 2xl:flex">Synchronisieren</span>
|
title="Ausgewählte Datei(en) herunterladen"
|
||||||
</Button>
|
className="px-3 flex"
|
||||||
</div>
|
disabled={$selected_file_ids.length === 0}><Download /></Button
|
||||||
<div class="flex flex-row gap-2">
|
>
|
||||||
<Button title="Datei(en) hochladen" className="px-3 flex"><Upload /></Button>
|
<div class="border border-stone-700 my-1"></div>
|
||||||
<Button title="Datei(en) herunterladen" className="px-3 flex"><Download /></Button>
|
<Button
|
||||||
<div class="border border-stone-700 my-1"></div>
|
title="Aktuellen Ordner / Ausgewählte Datei(en) zwischen Bildschirmen synchronisieren"
|
||||||
<Button title="Datei(en) ausschneiden" className="px-3 flex"><Scissors /></Button>
|
className="px-3 flex gap-3"
|
||||||
<Button title="Datei(en) einfügen" className="px-3 flex"><ClipboardPaste /></Button>
|
><RefreshCcw />
|
||||||
<div class="border border-stone-700 my-1"></div>
|
<span class="hidden 2xl:flex">Synchronisieren</span>
|
||||||
<Button title="Datei(en) löschen" className="hover:!bg-red-400 px-3 flex"><Trash2 /></Button>
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row gap-2">
|
||||||
|
<Button
|
||||||
|
title="Ausgewählte Datei(en) ausschneiden"
|
||||||
|
className="px-3 flex"
|
||||||
|
disabled={$selected_file_ids.length === 0}><Scissors /></Button
|
||||||
|
>
|
||||||
|
<Button title="Ausgewählte Datei(en) einfügen" className="px-3 flex"
|
||||||
|
><ClipboardPaste /></Button
|
||||||
|
>
|
||||||
|
<div class="border border-stone-700 my-1"></div>
|
||||||
|
<Button
|
||||||
|
title="Ausgewählte Datei umbenennen"
|
||||||
|
className="px-3 flex"
|
||||||
|
disabled={$selected_file_ids.length !== 1}><Pen /></Button
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
title="Ausgewählte Datei(en) löschen"
|
||||||
|
className="hover:!bg-red-400 px-3 flex"
|
||||||
|
disabled={$selected_file_ids.length === 0}><Trash2 /></Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 p-2 bg-stone-750 h-full rounded-xl">
|
<div class="min-h-0 h-full overflow-y-auto bg-stone-750 rounded-xl">
|
||||||
<FileObject />
|
<div class="flex flex-col gap-2 p-2 min-h-0">
|
||||||
|
{#each get_current_folder_elements($all_files, $current_file_path, $selected_display_ids) as folder_element (folder_element.id)}
|
||||||
|
<section in:slide={{ duration: 100 }} class="outline-none">
|
||||||
|
<FolderElementObject file={folder_element} />
|
||||||
|
</section>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,35 +1,47 @@
|
|||||||
<script lang="ts">
|
<script>
|
||||||
let isHoveringChild = false;
|
import { Minus, Settings, TvMinimalPlay } from 'lucide-svelte';
|
||||||
|
|
||||||
function handleClickParent() {
|
import Button from '../../components/Button.svelte';
|
||||||
if (!isHoveringChild) {
|
import { pinned_display_id } from '../../ts/stores/ui_behavior';
|
||||||
console.log('Parent clicked');
|
import { fade, slide } from 'svelte/transition';
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClickChild(event: MouseEvent) {
|
|
||||||
console.log('Child clicked');
|
|
||||||
// Verhindert, dass das Event nach oben propagiert
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<main class="bg-stone-900 h-dvh w-dvw text-stone-200 p-4 gap-4 grid grid-rows-[3rem_auto]">
|
||||||
class={`relative rounded-lg p-6 transition-colors duration-200
|
<div class="w-[calc(100dvw-(8*var(--spacing)))] flex justify-between">
|
||||||
${isHoveringChild ? 'bg-gray-200' : 'bg-gray-300 hover:bg-gray-400 active:bg-gray-500'}
|
<span class="text-4xl font-bold content-center h-full"> PLG MuDiCS </span>
|
||||||
`}
|
<Button className="aspect-square" bg="bg-stone-800">
|
||||||
on:click={handleClickParent}
|
<Settings></Settings>
|
||||||
>
|
</Button>
|
||||||
<p>Ich bin das Haupt-Div</p>
|
</div>
|
||||||
|
<div class="w-[calc(100dvw-(8*var(--spacing)))] grid grid-cols-2 gap-2">
|
||||||
<div
|
<div class="h-[calc(100dvh-3rem-(12*var(--spacing)))] flex flex-col gap-2"></div>
|
||||||
class="no-display-selectable mt-4 p-4 bg-white border rounded shadow cursor-pointer"
|
<div
|
||||||
on:mouseenter={() => (isHoveringChild = true)}
|
class="col-start-2 h-[calc(100dvh-3rem-(12*var(--spacing)))] rounded-2xl flex flex-col gap-2"
|
||||||
on:mouseleave={() => (isHoveringChild = false)}
|
>
|
||||||
on:click={handleClickChild}
|
<div class="bg-stone-800 h-full rounded-2xl grid grid-rows-[2.5rem_auto]">
|
||||||
>
|
<div class="bg-stone-700 flex justify-between w-full p-1 rounded-t-2xl min-w-0 gap-2">
|
||||||
Ich bin das Kind-Div (no-display-selectable)
|
<span class="text-xl font-bold pl-2 content-center truncate min-w-0">
|
||||||
</div>
|
Dateien anzeigen und verwalten
|
||||||
|
</span>
|
||||||
<p class="mt-4">Noch mehr Inhalt...</p>
|
<div class="flex flex-ro"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-col gap-2 p-2 max-w-full overflow-hidden min-w-0">
|
||||||
|
<div class="flex flex-row justify-between gap-4 min-w-0">
|
||||||
|
<div class="flex flex-row gap-2 min-w-0 flex-1">
|
||||||
|
<Button className="px-3 flex items-center gap-3 min-w-0 flex-1 justify-start">
|
||||||
|
<TvMinimalPlay />
|
||||||
|
<!-- Text: nimmt Restbreite, darf schrumpfen & ellipsieren -->
|
||||||
|
<span class="truncate min-w-0 flex-1">Sehr lange Beschriftung für den Button</span>
|
||||||
|
</Button>
|
||||||
|
<div class="relative min-w-0 flex">
|
||||||
|
<button class="text-xl font-bold pl-2 content-center truncate min-w-0">
|
||||||
|
Dateien anzeigen und verwalten
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|||||||
@@ -61,13 +61,134 @@ module.exports = {
|
|||||||
'active:bg-stone-900',
|
'active:bg-stone-900',
|
||||||
'active:bg-stone-950',
|
'active:bg-stone-950',
|
||||||
|
|
||||||
'h-5',
|
'text-stone-50',
|
||||||
'h-10',
|
'text-stone-150',
|
||||||
|
'text-stone-200',
|
||||||
|
'text-stone-250',
|
||||||
|
'text-stone-300',
|
||||||
|
'text-stone-350',
|
||||||
|
'text-stone-100',
|
||||||
|
'text-stone-400',
|
||||||
|
'text-stone-450',
|
||||||
|
'text-stone-500',
|
||||||
|
'text-stone-550',
|
||||||
|
'text-stone-600',
|
||||||
|
'text-stone-650',
|
||||||
|
'text-stone-700',
|
||||||
|
'text-stone-750',
|
||||||
|
'text-stone-800',
|
||||||
|
'text-stone-850',
|
||||||
|
'text-stone-900',
|
||||||
|
'text-stone-950',
|
||||||
|
|
||||||
|
'group-hover:text-stone-100',
|
||||||
|
'group-hover:text-stone-150',
|
||||||
|
'group-hover:text-stone-200',
|
||||||
|
'group-hover:text-stone-50',
|
||||||
|
'group-hover:text-stone-250',
|
||||||
|
'group-hover:text-stone-300',
|
||||||
|
'group-hover:text-stone-350',
|
||||||
|
'group-hover:text-stone-400',
|
||||||
|
'group-hover:text-stone-450',
|
||||||
|
'group-hover:text-stone-500',
|
||||||
|
'group-hover:text-stone-550',
|
||||||
|
'group-hover:text-stone-600',
|
||||||
|
'group-hover:text-stone-650',
|
||||||
|
'group-hover:text-stone-700',
|
||||||
|
'group-hover:text-stone-750',
|
||||||
|
'group-hover:text-stone-800',
|
||||||
|
'group-hover:text-stone-850',
|
||||||
|
'group-hover:text-stone-900',
|
||||||
|
'group-hover:text-stone-950',
|
||||||
|
|
||||||
|
'group-active:text-stone-50',
|
||||||
|
'group-active:text-stone-100',
|
||||||
|
'group-active:text-stone-150',
|
||||||
|
'group-active:text-stone-200',
|
||||||
|
'group-active:text-stone-250',
|
||||||
|
'group-active:text-stone-300',
|
||||||
|
'group-active:text-stone-350',
|
||||||
|
'group-active:text-stone-400',
|
||||||
|
'group-active:text-stone-450',
|
||||||
|
'group-active:text-stone-500',
|
||||||
|
'group-active:text-stone-550',
|
||||||
|
'group-active:text-stone-600',
|
||||||
|
'group-active:text-stone-650',
|
||||||
|
'group-active:text-stone-700',
|
||||||
|
'group-active:text-stone-750',
|
||||||
|
'group-active:text-stone-800',
|
||||||
|
'group-active:text-stone-850',
|
||||||
|
'group-active:text-stone-950',
|
||||||
|
'group-active:text-stone-900',
|
||||||
|
|
||||||
|
'border-stone-50',
|
||||||
|
'border-stone-100',
|
||||||
|
'border-stone-150',
|
||||||
|
'border-stone-200',
|
||||||
|
'border-stone-250',
|
||||||
|
'border-stone-300',
|
||||||
|
'border-stone-350',
|
||||||
|
'border-stone-400',
|
||||||
|
'border-stone-450',
|
||||||
|
'border-stone-500',
|
||||||
|
'border-stone-550',
|
||||||
|
'border-stone-600',
|
||||||
|
'border-stone-650',
|
||||||
|
'border-stone-700',
|
||||||
|
'border-stone-750',
|
||||||
|
'border-stone-800',
|
||||||
|
'border-stone-850',
|
||||||
|
'border-stone-900',
|
||||||
|
'border-stone-950',
|
||||||
|
|
||||||
|
'group-hover:border-stone-50',
|
||||||
|
'group-hover:border-stone-100',
|
||||||
|
'group-hover:border-stone-150',
|
||||||
|
'group-hover:border-stone-200',
|
||||||
|
'group-hover:border-stone-250',
|
||||||
|
'group-hover:border-stone-300',
|
||||||
|
'group-hover:border-stone-350',
|
||||||
|
'group-hover:border-stone-400',
|
||||||
|
'group-hover:border-stone-450',
|
||||||
|
'group-hover:border-stone-500',
|
||||||
|
'group-hover:border-stone-550',
|
||||||
|
'group-hover:border-stone-600',
|
||||||
|
'group-hover:border-stone-650',
|
||||||
|
'group-hover:border-stone-700',
|
||||||
|
'group-hover:border-stone-750',
|
||||||
|
'group-hover:border-stone-800',
|
||||||
|
'group-hover:border-stone-850',
|
||||||
|
'group-hover:border-stone-950',
|
||||||
|
'group-hover:border-stone-900',
|
||||||
|
|
||||||
|
'group-active:border-stone-50',
|
||||||
|
'group-active:border-stone-100',
|
||||||
|
'group-active:border-stone-150',
|
||||||
|
'group-active:border-stone-200',
|
||||||
|
'group-active:border-stone-250',
|
||||||
|
'group-active:border-stone-300',
|
||||||
|
'group-active:border-stone-350',
|
||||||
|
'group-active:border-stone-400',
|
||||||
|
'group-active:border-stone-450',
|
||||||
|
'group-active:border-stone-500',
|
||||||
|
'group-active:border-stone-550',
|
||||||
|
'group-active:border-stone-600',
|
||||||
|
'group-active:border-stone-650',
|
||||||
|
'group-active:border-stone-700',
|
||||||
|
'group-active:border-stone-750',
|
||||||
|
'group-active:border-stone-800',
|
||||||
|
'group-active:border-stone-850',
|
||||||
|
'group-active:border-stone-900',
|
||||||
|
'group-active:border-stone-950',
|
||||||
|
|
||||||
'h-15',
|
'h-15',
|
||||||
'h-20',
|
'h-20',
|
||||||
'h-25',
|
'h-25',
|
||||||
'h-30',
|
'h-30',
|
||||||
'h-35',
|
'h-35',
|
||||||
'h-40',
|
'h-40',
|
||||||
|
|
||||||
|
'right-0',
|
||||||
|
'left-0',
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
import { get, writable, type Writable } from "svelte/store";
|
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[]>([{
|
export const displays: Writable<DisplayGroup[]> = writable<DisplayGroup[]>([{
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
data: []
|
data: []
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
export const selected_display_ids: Writable<string[]> = writable<string[]>([]);
|
|
||||||
|
|
||||||
|
|
||||||
add_testing_displays();
|
|
||||||
|
|
||||||
function add_display(ip: string, mac: string, name: string, status: string) {
|
function add_display(ip: string, mac: string, name: string, status: string) {
|
||||||
displays.update((displays: DisplayGroup[]) => {
|
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[]) {
|
export function all_displays_of_group_selected(display_group: DisplayGroup, current_selected_displays: string[]) {
|
||||||
if (display_group.data.length === 0) return false;
|
if (display_group.data.length === 0) return false;
|
||||||
for (const display of display_group.data) {
|
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) {
|
export function select_all_of_group(display_group: DisplayGroup, new_value: boolean | null = null) {
|
||||||
for (const display of display_group.data) {
|
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() {
|
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 /\\"];
|
// 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) {
|
// for (const name of names) {
|
||||||
add_display("192.168.1.42", "00:1A:2B:3C:4D:5E", name, "Offline");
|
// 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")
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
|
// },], '/');
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -1,36 +1,51 @@
|
|||||||
import type { NumericRange } from "@sveltejs/kit";
|
import { writable, type Writable } from "svelte/store";
|
||||||
import { get, writable, type Writable } from "svelte/store";
|
|
||||||
|
|
||||||
const screen_height_step_size = 5;
|
|
||||||
export const dnd_flip_duration_ms = 300;
|
export const dnd_flip_duration_ms = 300;
|
||||||
|
|
||||||
const min_display_screen_height = 15;
|
const heights_options: Record<string, { min: number; max: number; step: number }> = {
|
||||||
const max_display_screen_height = 40;
|
display: {
|
||||||
export const display_screen_height: Writable<number> = writable<number>(25);
|
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_group_drag: Writable<boolean> = writable<boolean>(false);
|
||||||
export const is_display_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 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) => {
|
export function change_height(key: 'display' | 'file', factor: number) {
|
||||||
const new_size = current_height + (factor * screen_height_step_size);
|
current_height.update((height) => {
|
||||||
if (new_size > max_display_screen_height || new_size < min_display_screen_height) {
|
height[key] = next_height_step_size(key, height, factor) || height[key];
|
||||||
return current_height;
|
return height;
|
||||||
} else {
|
|
||||||
return new_size;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function next_height_step_size(key: 'display' | 'file', current_height_array: Record<string, number>, factor: number): number {
|
||||||
export function next_step_possible(current_height: number, factor: number) {
|
const new_size = current_height_array[key] + (factor * heights_options[key].step);
|
||||||
const new_size = current_height + (factor * screen_height_step_size);
|
if (new_size > heights_options[key].max || new_size < heights_options[key].min) {
|
||||||
return new_size > max_display_screen_height || new_size < min_display_screen_height;
|
return 0;
|
||||||
|
} else {
|
||||||
|
return new_size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function get_selectable_color_classes(
|
export function get_selectable_color_classes(
|
||||||
selected: boolean,
|
selected: boolean,
|
||||||
returning_classes: { bg?: boolean; hover?: boolean; active?: boolean; text?: boolean } = {},
|
returning_classes: { bg?: boolean; hover?: boolean; active?: boolean; text?: boolean } = {},
|
||||||
|
|||||||
@@ -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 = {
|
export type Display = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -20,5 +75,4 @@ export type MenuOption = {
|
|||||||
class?: string;
|
class?: string;
|
||||||
on_select?: () => void;
|
on_select?: () => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,10 +12,12 @@ patch {
|
|||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"command": "ls"
|
"command": "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"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
settings {
|
settings {
|
||||||
encodeUrl: true
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,16 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
url: 127.0.0.1:1323/api/file/titel.jpg
|
url: 127.0.0.1:1323/api/file/test.mp4
|
||||||
body: file
|
body: file
|
||||||
auth: inherit
|
auth: inherit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body:file {
|
||||||
|
file: @file(/home/fedorra-44/Downloads/10 Revenge Party und Playoff.mov) @contentType(video/quicktime)
|
||||||
|
}
|
||||||
|
|
||||||
settings {
|
settings {
|
||||||
encodeUrl: true
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user