mirror of
https://codeberg.org/PLG-Development/PLG-MuDiCS
synced 2026-07-06 00:47:09 +00:00
developement of right control side started
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user