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>
|
||||
@@ -1,5 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { Menu, Minus, PinOff, Plus, Settings, Square, VideoOff, X } from 'lucide-svelte';
|
||||
import {
|
||||
ArrowBigLeft,
|
||||
ArrowBigRight,
|
||||
ChevronDown,
|
||||
Info,
|
||||
Keyboard,
|
||||
Menu,
|
||||
Minus,
|
||||
Pencil,
|
||||
PinOff,
|
||||
Plus,
|
||||
Power,
|
||||
PowerOff,
|
||||
Presentation,
|
||||
Settings,
|
||||
Square,
|
||||
SquareTerminal,
|
||||
TextAlignStart,
|
||||
TrafficCone,
|
||||
Trash2,
|
||||
VideoOff,
|
||||
X
|
||||
} from 'lucide-svelte';
|
||||
import Button from '../components/Button.svelte';
|
||||
import SplashScreen from '../components/SplashScreen.svelte';
|
||||
import {
|
||||
@@ -27,6 +49,7 @@
|
||||
import { blur, draw, fade, fly, scale, slide } from 'svelte/transition';
|
||||
import OnlineState from '../components/OnlineState.svelte';
|
||||
import type { DisplayGroup } from '../ts/types';
|
||||
import PopUp from '../components/PopUp.svelte';
|
||||
|
||||
let displays_scroll_box: HTMLElement;
|
||||
|
||||
@@ -71,12 +94,12 @@
|
||||
</Button>
|
||||
</div>
|
||||
<div class="w-[calc(100dvw-(8*var(--spacing)))] grid grid-cols-2 gap-2">
|
||||
<div class="h-[calc(100dvh-3rem-(12*var(--spacing)))] overflow-hidden flex flex-col gap-2">
|
||||
<div class="h-[calc(100dvh-3rem-(12*var(--spacing)))] flex flex-col gap-2">
|
||||
{#if $pinned_display_id}
|
||||
<!-- Pinned Item -->
|
||||
<div in:fade={{ duration: 140 }} out:fade={{ duration: 120 }}>
|
||||
<div
|
||||
class="grid grid-rows-[2.5rem_auto] overflow-hidden will-change-[height,opacity] rounded-2xl"
|
||||
class="grid grid-rows-[2.5rem_auto] will-change-[height,opacity] overflow-hidden rounded-2xl"
|
||||
transition:slide={{ duration: 260, easing: cubicOut }}
|
||||
>
|
||||
<div class="bg-stone-700 flex justify-between w-full p-1 min-w-0 basis-0 flex-1">
|
||||
@@ -95,9 +118,20 @@
|
||||
<Button
|
||||
className="aspect-square !p-1"
|
||||
bg="bg-stone-600"
|
||||
click_function={() => {
|
||||
change_display_screen_height(1);
|
||||
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>
|
||||
@@ -207,8 +241,42 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-start-2 h-[calc(100dvh-3rem-(12*var(--spacing)))] bg-stone-800 rounded-2xl">
|
||||
ok
|
||||
<div
|
||||
class="col-start-2 h-[calc(100dvh-3rem-(12*var(--spacing)))] bg-stone-800 rounded-2xl grid grid-rows-[2.5rem_auto] overflow-hidden"
|
||||
>
|
||||
<div class="text-xl font-bold pl-3 content-center bg-stone-700">
|
||||
{$selected_display_ids.length + ' Bildschirme steuern'}
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 p-2">
|
||||
<div class="grid grid-cols-[auto_45%] gap-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-row gap-2">
|
||||
<Button className="px-5"><ArrowBigLeft /></Button>
|
||||
<Button className="px-5"><ArrowBigRight /></Button>
|
||||
</div>
|
||||
<Button className="px-3 flex gap-3"><TextAlignStart /> Text anzeigen</Button>
|
||||
<Button className="px-3 flex gap-3"><Presentation />Blackout</Button>
|
||||
<div class="flex flex-row">
|
||||
<Button className="rounded-r-none pl-3 pr-1 flex gap-3"
|
||||
><TrafficCone /> Fallback-Bild anzeigen</Button
|
||||
>
|
||||
<Button className="rounded-l-none pl-1"><ChevronDown /></Button>
|
||||
</div>
|
||||
<Button className="px-3 flex gap-3"><Keyboard /> Tastatur-Inputs durchgeben</Button>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 justify-between">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Button className="px-3 flex gap-3 w-full justify-normal"><Power /> PC hochfahren</Button>
|
||||
<Button className="px-3 flex gap-3 w-full justify-normal"><PowerOff /> PC herunterfahren</Button>
|
||||
</div>
|
||||
<Button className="px-3 flex gap-3 w-full justify-normal"><SquareTerminal /> Shell-Befehl ausführen</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-stone-750 w-full h-full rounded-xl"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <PopUp title="Test" title_icon={Info}>
|
||||
<div>ok schade</div>
|
||||
</PopUp> -->
|
||||
</main>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Component } from "lucide-svelte";
|
||||
import type { X } from "lucide-svelte";
|
||||
|
||||
export type Display = {
|
||||
id: string;
|
||||
@@ -15,8 +15,9 @@ export type DisplayGroup = {
|
||||
|
||||
|
||||
export type MenuOption = {
|
||||
icon?: Component;
|
||||
icon?: typeof X;
|
||||
name: string;
|
||||
class?: string;
|
||||
on_select?: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user