developement of right control side started

This commit is contained in:
E44
2025-10-20 17:47:17 +02:00
parent 19cc4c4415
commit f5fd93b1ff
6 changed files with 299 additions and 29 deletions
+155 -9
View File
@@ -1,6 +1,8 @@
<script lang="ts">
import { flip } from 'svelte/animate';
import { get_shifted_color } from '../ts/stores/ui_behavior';
import type { MenuOption } from '../ts/types';
import { fade } from 'svelte/transition';
let {
className = '',
@@ -18,20 +20,164 @@
active_bg?: string;
disabled?: boolean;
click_function?: (e: MouseEvent) => void;
menu_options?: MenuOption[]|null;
menu_options?: MenuOption[] | null;
children?: any;
}>();
if (menu_options !== null) {
let menu_shown = $state(false);
let button_element: HTMLButtonElement;
let menu_element: HTMLDivElement;
let position_bottom = $state(true);
function onclick(e: MouseEvent) {
if (menu_options !== null) {
if (menu_shown) {
close_menu();
} else {
open_menu();
}
}
click_function(e);
}
function getPolygon(): [number, number][] | null {
if (!button_element || !button_element) return null;
const b = button_element.getBoundingClientRect();
const m = menu_element.getBoundingClientRect();
// get polygon coords
if (position_bottom) {
return [
[b.left, b.top],
[b.right, b.top],
[m.right, m.top],
[m.right, m.bottom],
[m.left, m.bottom],
[m.left, m.top],
[b.left, b.top]
];
} else {
return [
[b.left, b.bottom],
[b.right, b.bottom],
[m.right, m.bottom],
[m.right, m.top],
[m.left, m.top],
[m.left, m.bottom],
[b.left, b.bottom]
];
}
}
function handleMouseMove(e: MouseEvent) {
if (!menu_shown) return;
const polygon = getPolygon();
if (!polygon) return;
const inside = pointInPolygon([e.clientX, e.clientY], polygon);
if (!inside) {
close_menu();
}
}
function pointInPolygon(point: [number, number], polygon: [number, number][]) {
let [x, y] = point;
let inside = false;
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
let [xi, yi] = polygon[i];
let [xj, yj] = polygon[j];
let intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
if (intersect) inside = !inside;
}
return inside;
}
function is_enough_space_below(): boolean {
if (!button_element || !menu_element) return true;
const button_rectangle = button_element.getBoundingClientRect();
const menu_height = menu_element.offsetHeight;
const space_below_available = window.innerHeight - button_rectangle.bottom;
return space_below_available > menu_height;
}
function open_menu() {
menu_shown = true;
window.addEventListener('mousemove', handleMouseMove);
setTimeout(() => {
position_bottom = is_enough_space_below();
}, 0);
}
function close_menu() {
window.removeEventListener('mousemove', handleMouseMove);
menu_shown = false;
}
function no_option_has_icon(): boolean {
for (const option of menu_options) {
if (option.icon) {
return false;
}
}
return true;
}
</script>
<button
class="{className} {bg} {disabled ? "text-stone-500 cursor-not-allowed" : "hover:"+hover_bg+" active:"+active_bg+" cursor-pointer"} p-2 rounded-xl flex justify-center items-center transition-colors duration-200"
{disabled}
onclick={click_function}
>
{@render children()}
</button>
<div class="relative">
<button
bind:this={button_element}
class="{className} {menu_shown ? hover_bg : bg} {disabled
? 'text-stone-500 cursor-not-allowed'
: 'hover:' +
hover_bg +
' active:' +
active_bg +
' cursor-pointer'} p-2 rounded-xl flex justify-center items-center transition-colors duration-200"
{disabled}
{onclick}
>
{@render children()}
</button>
{#if menu_shown}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
bind:this={menu_element}
transition:fade={{ duration: 50 }}
class="absolute {position_bottom
? '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"
onclick={(e) => {
e.stopPropagation();
}}
>
{#each menu_options as option}
<button
disabled={option.disabled ?? false}
class="bg-stone-400/20 {option.disabled
? 'text-stone-500 cursor-not-allowed'
: 'hover:bg-stone-300/35 active:bg-stone-300/60 cursor-pointer ' +
option.class} rounded-lg p-2 transition-colors duration-200 select-none flex flex-row gap-2 items-center"
onclick={(e) => {
if (option.on_select) option.on_select();
close_menu();
}}
>
{#if !no_option_has_icon()}
<div class="aspect-square h-[1.2rem]">
{#if option.icon}
{@const Icon = option.icon}
<Icon class="size-full" />
{/if}
</div>
{/if}
<div class="pr-2">
{option.name}
</div>
</button>
{/each}
</div>
{/if}
</div>
@@ -16,6 +16,7 @@
} from '../ts/stores/displays';
import DNDGrip from './DNDGrip.svelte';
import { fade } from 'svelte/transition';
import type { DisplayGroup } from '../ts/types';
let { display_group } = $props<{
display_group: DisplayGroup;
@@ -7,18 +7,10 @@
pinned_display_id
} from '../ts/stores/ui_behavior';
import DNDGrip from './DNDGrip.svelte';
import {
ArrowUpFromLine,
Eye,
Menu,
Pin,
PinOff,
SquareArrowOutDownLeft,
SquareArrowOutUpRight,
VideoOff
} 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 type { Display } from '../ts/types';
let { display } = $props<{
display: Display;
@@ -53,7 +45,7 @@
hover: true,
active: !hovering_unselectable,
text: true
})} rounded-xl flex flex-row justify-between h-{$display_screen_height} overflow-hidde transition-colors duration-100 gap-2 cursor-pointer w-full overflow-hidden text-stone-200"
})} rounded-xl flex flex-row justify-between h-{$display_screen_height} 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">
<!-- Left Preview Screen -->
@@ -128,6 +120,17 @@
click_function={(e) => {
e.stopPropagation();
}}
menu_options={[
{
icon: Pencil,
name: 'Bildschirm bearbeiten'
},
{
icon: Trash2,
name: 'Bildschirm löschen',
class: 'text-red-400 hover:text-stone-200 hover:!bg-red-400'
}
]}
>
<Menu />
</Button>
@@ -0,0 +1,51 @@
<script lang="ts">
import type { X } from 'lucide-svelte';
import { onDestroy, onMount } from 'svelte';
let {
children,
title,
title_class = '',
title_icon,
closable = true,
close_function = null
} = $props<{
children: any;
title: string;
title_class?: string;
title_icon: typeof X;
closable?: boolean;
close_function?: () => void | null;
}>();
// onMount(() => {
// const handler = (e: KeyboardEvent) => {
// // if (e.key === 'Escape') dispatch('close');
// };
// window.addEventListener('keydown', handler);
// onDestroy(() => window.removeEventListener('keydown', handler));
// });
</script>
<div class="fixed size-full backdrop-blur bg-black/30 flex justify-center items-center">
<div
class="bg-stone-800 rounded-2xl min-w-[30dvw] min-h-[20dvh] max-w-[80dvw] max-h-[80dvh] flex flex-col overflow-hidden shadow-2xl/30"
>
<div
class="text-2xl font-bold bg-stone-700 {title_class} px-4 py-2 flex flex-row justify-between"
>
<div>
{title}
</div>
<div>
{#if title_icon}
{@const Icon = title_icon}
<Icon class="size-full" />
{/if}
</div>
</div>
<div class="px-4 py-2 min-h-0 overflow-auto">
{@render children()}
</div>
</div>
</div>