fix(control): Drag and Drop in DisplayView was completely buggy, now its just a little bit buggy

This commit is contained in:
E44
2026-01-14 18:48:10 +01:00
parent 5ca2487598
commit 5b93250e22
8 changed files with 1368 additions and 1501 deletions
@@ -11,22 +11,25 @@
import DisplayObject from './DisplayObject.svelte';
import {
all_displays_of_group_selected,
get_display_ids_in_group,
set_select_for_group,
set_new_display_order
update_db_displays,
local_displays,
set_new_display_order,
} from '$lib/ts/stores/displays';
import DNDGrip from '$lib/components/DNDGrip.svelte';
import { fade } from 'svelte/transition';
import type { MenuOption } from '$lib/ts/types';
import type { Display, DisplayIdGroup, MenuOption } from '$lib/ts/types';
import { selected_display_ids } from '$lib/ts/stores/select';
import { liveQuery, type Observable } from 'dexie';
import { onMount } from 'svelte';
import { get_uuid } from '$lib/ts/utils';
let {
display_group_id,
display_id_group,
get_display_menu_options,
close_pinned_display
}: {
display_group_id: string;
display_id_group: DisplayIdGroup;
get_display_menu_options: (display_id: string) => MenuOption[];
close_pinned_display: () => void;
} = $props();
@@ -34,14 +37,14 @@
let all_selected: Observable<boolean> | undefined = $state();
$effect(() => {
const sdi = $selected_display_ids;
all_selected = liveQuery(() => all_displays_of_group_selected(display_group_id, sdi));
all_selected = liveQuery(() => all_displays_of_group_selected(display_id_group.id, sdi));
});
let display_ids_in_group = liveQuery(() => get_display_ids_in_group(display_group_id));
let hovering_selectable = $state(false);
async function select_all_of_this_group() {
const new_value = !($all_selected || false);
await set_select_for_group(display_group_id, new_value);
await set_select_for_group(display_id_group.id, new_value);
}
async function handle_consider(e: CustomEvent) {
@@ -49,14 +52,22 @@
if (items.length !== 1 && info.trigger === TRIGGERS.DRAG_STARTED) {
$is_display_drag = true;
// add_empty_display_group();
// add empty display group if its not just one group
if (!($local_displays.length === 1 && $local_displays[0].displays.length === 1)) {
$local_displays.push({
id: get_uuid(),
displays: []
});
}
}
await set_new_display_order(items);
set_new_display_order(display_id_group.id, items);
}
async function handle_finalize(e: CustomEvent) {
set_new_display_order(display_id_group.id, e.detail.items);
// $local_displays = $local_displays.filter((g) => g.displays.length !== 0);
await update_db_displays();
$is_display_drag = false;
await set_new_display_order(e.detail.items);
}
</script>
@@ -77,7 +88,7 @@
<div
class="flex flex-col min-w-0 pl-2 py-2 gap-2 w-full"
use:dragHandleZone={{
items: $display_ids_in_group || [],
items: display_id_group.displays,
type: 'item',
flipDurationMs: dnd_flip_duration_ms,
dropTargetStyle: { outline: 'none' }
@@ -85,18 +96,15 @@
onconsider={handle_consider}
onfinalize={handle_finalize}
>
{#each $display_ids_in_group || [] as display (display.id)}
{#each display_id_group.displays as display_id_object (display_id_object.id)}
<!-- Each Group -->
<section
animate:flip={{ duration: $is_group_drag ? 0 : dnd_flip_duration_ms, easing: cubicOut }}
class="outline-none"
role="figure"
>
<DisplayObject {display} {get_display_menu_options} {close_pinned_display} />
<section class="outline-none" role="figure">
<!-- animate:flip={{ duration: $is_group_drag ? 0 : dnd_flip_duration_ms, easing: cubicOut }} -->
<DisplayObject {display_id_object} {get_display_menu_options} {close_pinned_display} />
</section>
{/each}
{#if ($display_ids_in_group || []).length === 0}
{#if display_id_group.displays.length === 0}
<div class="min-h-10 h-full w-full pl-2 py-2 flex justify-center items-center">
Hier in leere neue Gruppe ablegen
</div>
@@ -8,38 +8,40 @@
import DNDGrip from '$lib/components/DNDGrip.svelte';
import { Menu, Pin, PinOff, VideoOff } from 'lucide-svelte';
import OnlineState from './OnlineState.svelte';
import type { Display, MenuOption } from '$lib/ts/types';
import type { Display, DisplayIdObject, MenuOption } from '$lib/ts/types';
import { is_selected, select, selected_display_ids } from '$lib/ts/stores/select';
import { screenshot_loop } from '$lib/ts/stores/displays';
import { get_display_by_id, screenshot_loop } from '$lib/ts/stores/displays';
import { change_file_path, current_file_path } from '$lib/ts/stores/files';
import { liveQuery } from 'dexie';
let {
display,
display_id_object,
get_display_menu_options,
close_pinned_display
}: {
display: Display;
display_id_object: DisplayIdObject;
get_display_menu_options: (display_id: string) => MenuOption[];
close_pinned_display: () => void;
} = $props();
let hovering_unselectable = $state(false);
let display = liveQuery(() => get_display_by_id(display_id_object.id));
async function onclick(e: Event) {
e.stopPropagation();
select(selected_display_ids, display.id, 'toggle');
select(selected_display_ids, display_id_object.id, 'toggle');
// force file view update
await change_file_path($current_file_path);
}
async function on_preview_click(e: MouseEvent) {
if ($pinned_display_id === display.id) {
if ($pinned_display_id === display_id_object.id) {
close_pinned_display();
} else {
$pinned_display_id = display.id;
$pinned_display_id = display_id_object.id;
}
await screenshot_loop(display.id);
await screenshot_loop(display_id_object.id);
e.stopPropagation();
}
</script>
@@ -52,7 +54,7 @@
if (e.key === 'Enter' || e.key === ' ') onclick(e);
}}
{onclick}
class="p-1 {get_selectable_color_classes(is_selected(display.id, $selected_display_ids), {
class="p-1 {get_selectable_color_classes(is_selected(display_id_object.id, $selected_display_ids), {
bg: true,
hover: true,
active: !hovering_unselectable,
@@ -68,12 +70,12 @@
onclick={on_preview_click}
>
<div class="flex h-full w-full items-center justify-center">
{#if $pinned_display_id === display.id}
{#if $pinned_display_id === display_id_object.id}
<div class="size-[50%]">
<Pin class="size-full" />
</div>
{:else if display.preview.url}
<img src={display.preview.url} alt="preview" class="w-full object-cover bg-black" />
{:else if $display && $display.preview.url}
<img src={$display.preview.url} alt="preview" class="w-full object-cover bg-black" />
{:else}
<!-- No Signal -->
<div class="size-full bg-black flex justify-center items-center">
@@ -84,11 +86,11 @@
<!-- Hover Effect -->
<span
class="pointer-events-none absolute inset-0 {$pinned_display_id === display.id
class="pointer-events-none absolute inset-0 {$pinned_display_id === display_id_object.id
? 'bg-stone-700'
: 'bg-stone-700/70'} opacity-0 transition-opacity duration-200 flex items-center justify-center group-hover:opacity-100"
>
{#if $pinned_display_id === display.id}
{#if $pinned_display_id === display_id_object.id}
<PinOff class="size-[50%]" aria-hidden="true" />
{:else}
<Pin class="size-[50%]" aria-hidden="true" />
@@ -100,20 +102,20 @@
class="h-full flex flex-col justify-center gap-1 select-none
min-w-0 basis-0 flex-1"
>
<div class="text-xl font-bold truncate w-full" title={display.name}>
{display.name}
<div class="text-xl font-bold truncate w-full" title={$display?.name ?? ''}>
{$display?.name ?? ''}
</div>
<OnlineState
selected={is_selected(display.id, $selected_display_ids)}
status={display.status}
selected={is_selected(display_id_object.id, $selected_display_ids)}
status={$display?.status ?? null}
/>
</div>
</div>
<!-- Right Controls -->
<div class="flex flex-row h-full items-center gap-2 pr-3">
<DNDGrip
bg={get_selectable_color_classes(is_selected(display.id, $selected_display_ids), {
bg={get_selectable_color_classes(is_selected(display_id_object.id, $selected_display_ids), {
bg: true
})}
/>
@@ -125,16 +127,16 @@
>
<Button
bg="bg-transparent"
hover_bg={get_selectable_color_classes(is_selected(display.id, $selected_display_ids), {
hover_bg={get_selectable_color_classes(is_selected(display_id_object.id, $selected_display_ids), {
bg: true
})}
active_bg={get_selectable_color_classes(is_selected(display.id, $selected_display_ids), {
active_bg={get_selectable_color_classes(is_selected(display_id_object.id, $selected_display_ids), {
bg: true
})}
click_function={(e) => {
e.stopPropagation();
}}
menu_options={get_display_menu_options(display.id)}
menu_options={get_display_menu_options(display_id_object.id)}
>
<Menu />
</Button>
@@ -293,7 +293,6 @@ async function start_task_processing() {
async function start_task_loop() {
while (tasks.length > 0) {
console.log('NOCH', tasks.length, 'ZU TUN');
const current_task = tasks[0];
if (current_task.data.type === 'upload') {
await upload(current_task);
+61 -17
View File
@@ -1,5 +1,11 @@
import { get } from 'svelte/store';
import type { Display, DisplayGroup, DisplayStatus } from '../types';
import { get, writable, type Writable } from 'svelte/store';
import type {
Display,
DisplayGroup,
DisplayIdGroup,
DisplayIdObject,
DisplayStatus
} from '../types';
import { is_selected, select, selected_display_ids } from './select';
import { get_uuid, image_content_hash } from '../utils';
import { get_screenshot } from '../api_handler';
@@ -7,6 +13,8 @@ import { delete_and_deselect_unique_files_from_display } from './files';
import { db } from '../database';
import { dev } from '$app/environment';
export const local_displays: Writable<DisplayIdGroup[]> = writable<DisplayIdGroup[]>([]);
export async function is_display_name_taken(name: string): Promise<boolean> {
const exists = await db.displays.where('name').equals(name).first();
return !!exists;
@@ -169,26 +177,62 @@ export async function get_display_groups(): Promise<DisplayGroup[]> {
return await db.display_groups.orderBy('position').toArray();
}
export async function get_display_ids_in_group(display_group_id: string): Promise<Display[]> {
const displays: Display[] = await db.displays
.where('group_id')
.equals(display_group_id)
.sortBy('position');
return displays;
export async function update_local_displays() {
const display_groups = await db.display_groups.orderBy('position').toArray();
const displays = await db.displays.orderBy('position').toArray();
const out: DisplayIdGroup[] = [];
for (const group of display_groups) {
out.push({
id: group.id,
displays: (await displays).filter((d) => d.group_id === group.id).map((d) => ({ id: d.id }))
});
}
return out;
}
export async function set_new_display_order(new_ordered_items: Display[]) {
for (let i = 0; i < new_ordered_items.length; i++) {
new_ordered_items[i].position = i;
await db.displays.put(new_ordered_items[i]);
export async function update_db_displays() {
// local_displays.update((groups) => groups.filter((g) => g.displays.length !== 0));
const filtered_local_display_groups = get(local_displays)
// .filter(
// (group) => group.displays.length !== 0
// );
// local_displays.set(filtered_local_display_groups);
const db_display_group_ids = (await db.display_groups.toArray()).map((group) => group.id);
console.log('JOJ', filtered_local_display_groups, db_display_group_ids);
const local_display_group_ids = filtered_local_display_groups.map((group) => group.id);
const display_group_ids_to_delete = db_display_group_ids.filter(
(group) => !local_display_group_ids.includes(group)
);
await db.display_groups.where('id').anyOf(display_group_ids_to_delete).delete();
for (let i = 0; i < filtered_local_display_groups.length; i++) {
const group = filtered_local_display_groups[i];
if (db_display_group_ids.includes(group.id)) {
await db.display_groups.update(group.id, { position: i });
} else {
await db.display_groups.put({
id: group.id,
position: i
});
}
for (let j = 0; j < group.displays.length; j++) {
const display_id = group.displays[j].id;
await db.displays.update(display_id, { position: j, group_id: group.id });
}
}
}
export async function set_new_display_group_order(new_ordered_items: DisplayGroup[]) {
for (let i = 0; i < new_ordered_items.length; i++) {
new_ordered_items[i].position = i;
await db.display_groups.put(new_ordered_items[i]);
}
export function set_new_display_order(display_id_group_id: string, new_data: DisplayIdObject[]) {
local_displays.update((local_displays: DisplayIdGroup[]) => {
for (const display_id_group of local_displays) {
if (display_id_group.id === display_id_group_id) {
display_id_group.displays = new_data;
}
}
return local_displays;
});
}
if (dev) {
+20 -11
View File
@@ -37,20 +37,20 @@ export type FileTransferTask = {
bytes_total: number; // if type === 'sync' -> bytes_total = file_size * 2 (1x download + 1x upload)
};
export type FileTransferTaskData = {
type: 'upload',
file: File,
} | {
type: "sync",
destination_displays: ShortDisplay[],
}
export type FileTransferTaskData =
| {
type: 'upload';
file: File;
}
| {
type: 'sync';
destination_displays: ShortDisplay[];
};
export type ShortDisplay = {
id: string;
ip: string;
}
};
export type FileOnDisplay = {
display_id: string;
@@ -64,7 +64,7 @@ export type FileLoadingData = {
percentage: number;
bytes_per_second: number;
seconds_until_finish: number;
}
};
export type Inode = {
path: string;
@@ -101,6 +101,15 @@ export type DisplayGroup = {
position: number;
};
export type DisplayIdGroup = {
id: string;
displays: DisplayIdObject[];
};
export type DisplayIdObject = {
id: string;
};
export type PreviewObject = {
currently_updating: boolean;
url: string | null;