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">
|
<script lang="ts">
|
||||||
|
import { flip } from 'svelte/animate';
|
||||||
import { get_shifted_color } from '../ts/stores/ui_behavior';
|
import { get_shifted_color } from '../ts/stores/ui_behavior';
|
||||||
import type { MenuOption } from '../ts/types';
|
import type { MenuOption } from '../ts/types';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
className = '',
|
className = '',
|
||||||
@@ -22,16 +24,160 @@
|
|||||||
children?: any;
|
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>
|
</script>
|
||||||
|
|
||||||
|
<div class="relative">
|
||||||
<button
|
<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"
|
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}
|
{disabled}
|
||||||
onclick={click_function}
|
{onclick}
|
||||||
>
|
>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</button>
|
</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';
|
} 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';
|
||||||
|
|
||||||
let { display_group } = $props<{
|
let { display_group } = $props<{
|
||||||
display_group: DisplayGroup;
|
display_group: DisplayGroup;
|
||||||
|
|||||||
@@ -7,18 +7,10 @@
|
|||||||
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 {
|
import { Menu, Pencil, Pin, PinOff, Trash2, VideoOff, X } from 'lucide-svelte';
|
||||||
ArrowUpFromLine,
|
|
||||||
Eye,
|
|
||||||
Menu,
|
|
||||||
Pin,
|
|
||||||
PinOff,
|
|
||||||
SquareArrowOutDownLeft,
|
|
||||||
SquareArrowOutUpRight,
|
|
||||||
VideoOff
|
|
||||||
} from 'lucide-svelte';
|
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import OnlineState from './OnlineState.svelte';
|
import OnlineState from './OnlineState.svelte';
|
||||||
|
import type { Display } from '../ts/types';
|
||||||
|
|
||||||
let { display } = $props<{
|
let { display } = $props<{
|
||||||
display: Display;
|
display: Display;
|
||||||
@@ -53,7 +45,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} 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">
|
<div class="flex flex-row gap-4 min-w-0 flex-1">
|
||||||
<!-- Left Preview Screen -->
|
<!-- Left Preview Screen -->
|
||||||
@@ -128,6 +120,17 @@
|
|||||||
click_function={(e) => {
|
click_function={(e) => {
|
||||||
e.stopPropagation();
|
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 />
|
<Menu />
|
||||||
</Button>
|
</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">
|
<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 Button from '../components/Button.svelte';
|
||||||
import SplashScreen from '../components/SplashScreen.svelte';
|
import SplashScreen from '../components/SplashScreen.svelte';
|
||||||
import {
|
import {
|
||||||
@@ -27,6 +49,7 @@
|
|||||||
import { blur, draw, fade, fly, scale, slide } from 'svelte/transition';
|
import { blur, draw, fade, fly, scale, slide } from 'svelte/transition';
|
||||||
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';
|
||||||
|
|
||||||
let displays_scroll_box: HTMLElement;
|
let displays_scroll_box: HTMLElement;
|
||||||
|
|
||||||
@@ -71,12 +94,12 @@
|
|||||||
</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)))] 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}
|
{#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 }}>
|
||||||
<div
|
<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 }}
|
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">
|
<div class="bg-stone-700 flex justify-between w-full p-1 min-w-0 basis-0 flex-1">
|
||||||
@@ -95,9 +118,20 @@
|
|||||||
<Button
|
<Button
|
||||||
className="aspect-square !p-1"
|
className="aspect-square !p-1"
|
||||||
bg="bg-stone-600"
|
bg="bg-stone-600"
|
||||||
click_function={() => {
|
click_function={(e) => {
|
||||||
change_display_screen_height(1);
|
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 />
|
<Menu />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -207,8 +241,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-start-2 h-[calc(100dvh-3rem-(12*var(--spacing)))] bg-stone-800 rounded-2xl">
|
<div
|
||||||
ok
|
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>
|
</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>
|
</main>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Component } from "lucide-svelte";
|
import type { X } from "lucide-svelte";
|
||||||
|
|
||||||
export type Display = {
|
export type Display = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -15,8 +15,9 @@ export type DisplayGroup = {
|
|||||||
|
|
||||||
|
|
||||||
export type MenuOption = {
|
export type MenuOption = {
|
||||||
icon?: Component;
|
icon?: typeof X;
|
||||||
name: string;
|
name: string;
|
||||||
|
class?: string;
|
||||||
on_select?: () => void;
|
on_select?: () => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user