mirror of
https://codeberg.org/PLG-Development/PLG-MuDiCS
synced 2026-07-06 00:47:09 +00:00
add rough left side of frontend
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import { get_shifted_color } from '../ts/stores/ui_behavior';
|
||||
import type { MenuOption } from '../ts/types';
|
||||
|
||||
let {
|
||||
className = '',
|
||||
bg = 'bg-stone-700',
|
||||
hover_bg = get_shifted_color(bg, 100),
|
||||
active_bg = get_shifted_color(bg, 200),
|
||||
disabled = false,
|
||||
click_function = (e: MouseEvent) => {},
|
||||
menu_options = null,
|
||||
children
|
||||
} = $props<{
|
||||
className?: string;
|
||||
bg?: string;
|
||||
hover_bg?: string;
|
||||
active_bg?: string;
|
||||
disabled?: boolean;
|
||||
click_function?: (e: MouseEvent) => void;
|
||||
menu_options?: MenuOption[]|null;
|
||||
children?: any;
|
||||
}>();
|
||||
|
||||
if (menu_options !== null) {
|
||||
|
||||
}
|
||||
</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>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { GripHorizontal } from 'lucide-svelte';
|
||||
import { dragHandle } from 'svelte-dnd-action';
|
||||
|
||||
let { bg, className = "" } = $props<{
|
||||
bg: string;
|
||||
className?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<div
|
||||
use:dragHandle
|
||||
class="{className} bg-transparent hover:{bg} active:{bg} p-2 rounded-xl duration-200 transition-colors"
|
||||
>
|
||||
<GripHorizontal />
|
||||
</div>
|
||||
@@ -0,0 +1,111 @@
|
||||
<script lang="ts">
|
||||
import { dragHandleZone, TRIGGERS } from 'svelte-dnd-action';
|
||||
import { dnd_flip_duration_ms, get_selectable_color_classes, is_display_drag, is_group_drag } from '../ts/stores/ui_behavior';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import { flip } from 'svelte/animate';
|
||||
import DisplayObject from './DisplayObject.svelte';
|
||||
import {
|
||||
add_empty_display_group,
|
||||
all_displays_of_group_selected,
|
||||
is_selected,
|
||||
remove_empty_display_groups,
|
||||
select,
|
||||
select_all_of_group,
|
||||
selected_display_ids,
|
||||
set_new_display_group_data
|
||||
} from '../ts/stores/displays';
|
||||
import DNDGrip from './DNDGrip.svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
let { display_group } = $props<{
|
||||
display_group: DisplayGroup;
|
||||
}>();
|
||||
|
||||
let hovering_selectable = $state(false);
|
||||
|
||||
function select_all_of_this_group() {
|
||||
const new_value = !all_displays_of_group_selected(display_group, $selected_display_ids);
|
||||
select_all_of_group(display_group, new_value);
|
||||
}
|
||||
|
||||
function handle_consider(e: CustomEvent) {
|
||||
const { items, info } = e.detail;
|
||||
|
||||
if (items.length !== 1 && info.trigger === TRIGGERS.DRAG_STARTED) {
|
||||
$is_display_drag = true;
|
||||
add_empty_display_group();
|
||||
}
|
||||
set_new_display_group_data(display_group.id, items);
|
||||
}
|
||||
|
||||
function handle_finalize(e: CustomEvent) {
|
||||
remove_empty_display_groups();
|
||||
$is_display_drag = false;
|
||||
set_new_display_group_data(display_group.id, e.detail.items);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
transition:fade={{ duration: 100 }}
|
||||
class="{get_selectable_color_classes(
|
||||
all_displays_of_group_selected(display_group, $selected_display_ids),
|
||||
{
|
||||
bg: true,
|
||||
hover: hovering_selectable,
|
||||
active: hovering_selectable,
|
||||
text: true
|
||||
},
|
||||
-150,
|
||||
-50
|
||||
)} transition-colors duration-200 rounded-2xl flex flex-row cursor-pointer"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col min-w-0 pl-2 py-2 gap-2 w-full"
|
||||
use:dragHandleZone={{
|
||||
items: display_group.data,
|
||||
type: 'item',
|
||||
flipDurationMs: dnd_flip_duration_ms,
|
||||
dropTargetStyle: { outline: 'none' },
|
||||
}}
|
||||
onconsider={handle_consider}
|
||||
onfinalize={handle_finalize}
|
||||
>
|
||||
{#each display_group.data as display (display.id)}
|
||||
<!-- Each Group -->
|
||||
<section
|
||||
animate:flip={{ duration: $is_group_drag ? 0 : dnd_flip_duration_ms, easing: cubicOut }}
|
||||
class="outline-none"
|
||||
role="figure"
|
||||
>
|
||||
<DisplayObject {display} />
|
||||
</section>
|
||||
{/each}
|
||||
|
||||
{#if display_group.data.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>
|
||||
{/if}
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center px-2"
|
||||
onclick={(e) => select_all_of_this_group()}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') select_all_of_this_group();
|
||||
}}
|
||||
onmouseenter={() => (hovering_selectable = true)}
|
||||
onmouseleave={() => (hovering_selectable = false)}
|
||||
>
|
||||
<DNDGrip
|
||||
bg={get_selectable_color_classes(
|
||||
all_displays_of_group_selected(display_group, $selected_display_ids),
|
||||
{
|
||||
bg: true
|
||||
},
|
||||
-150
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,136 @@
|
||||
<script lang="ts">
|
||||
import Button from './Button.svelte';
|
||||
import { is_selected, select, selected_display_ids } from '../ts/stores/displays';
|
||||
import {
|
||||
display_screen_height,
|
||||
get_selectable_color_classes,
|
||||
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 { fade } from 'svelte/transition';
|
||||
import OnlineState from './OnlineState.svelte';
|
||||
|
||||
let { display } = $props<{
|
||||
display: Display;
|
||||
}>();
|
||||
|
||||
let hovering_unselectable = $state(false);
|
||||
|
||||
function onclick(e: Event) {
|
||||
select(display.id);
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
function on_preview_click(e: MouseEvent) {
|
||||
if ($pinned_display_id === display.id) {
|
||||
$pinned_display_id = null;
|
||||
} else {
|
||||
$pinned_display_id = display.id;
|
||||
}
|
||||
e.stopPropagation();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') onclick(e);
|
||||
}}
|
||||
{onclick}
|
||||
class="p-1 {get_selectable_color_classes(is_selected(display.id, $selected_display_ids), {
|
||||
bg: true,
|
||||
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"
|
||||
>
|
||||
<div class="flex flex-row gap-4 min-w-0 flex-1">
|
||||
<!-- Left Preview Screen -->
|
||||
<button
|
||||
class="group relative aspect-16/9 {$pinned_display_id === display.id
|
||||
? 'bg-stone-800'
|
||||
: 'bg-black'} h-full rounded-lg overflow-hidden cursor-pointer text-stone-200 transition-colors duration-200"
|
||||
onmouseenter={() => (hovering_unselectable = true)}
|
||||
onmouseleave={() => (hovering_unselectable = false)}
|
||||
onclick={on_preview_click}
|
||||
>
|
||||
<div class="flex h-full w-full items-center justify-center">
|
||||
{#if $pinned_display_id === display.id}
|
||||
<div class="size-[50%]">
|
||||
<Pin class="size-full" />
|
||||
</div>
|
||||
{:else}
|
||||
<!-- No Signal -->
|
||||
<VideoOff class="size-[30%]" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Hover Effect -->
|
||||
<span
|
||||
class="pointer-events-none absolute inset-0 {$pinned_display_id === display.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}
|
||||
<PinOff class="size-[50%]" aria-hidden="true" />
|
||||
{:else}
|
||||
<Pin class="size-[50%]" aria-hidden="true" />
|
||||
{/if}
|
||||
</span>
|
||||
</button>
|
||||
<!-- Middle Text Block -->
|
||||
<div
|
||||
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>
|
||||
|
||||
<OnlineState
|
||||
selected={is_selected(display.id, $selected_display_ids)}
|
||||
status={display.status}
|
||||
/>
|
||||
</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: true
|
||||
})}
|
||||
/>
|
||||
|
||||
<div
|
||||
role="figure"
|
||||
onmouseenter={() => (hovering_unselectable = true)}
|
||||
onmouseleave={() => (hovering_unselectable = false)}
|
||||
>
|
||||
<Button
|
||||
bg="bg-transparent"
|
||||
hover_bg={get_selectable_color_classes(is_selected(display.id, $selected_display_ids), {
|
||||
bg: true
|
||||
})}
|
||||
active_bg={get_selectable_color_classes(is_selected(display.id, $selected_display_ids), {
|
||||
bg: true
|
||||
})}
|
||||
click_function={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Menu />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
let { selected, status, className = "" } = $props<{
|
||||
selected: boolean;
|
||||
status: string;
|
||||
className?: string;
|
||||
}>();
|
||||
|
||||
function get_text_color(selected: boolean, status: string) {
|
||||
switch (status) {
|
||||
case 'Online':
|
||||
return selected ? 'text-green-700' : 'text-green-400';
|
||||
case 'Lädt':
|
||||
return selected ? 'text-amber-700' : 'text-amber-400';
|
||||
case 'Offline':
|
||||
return selected ? 'text-red-700' : 'text-red-400';
|
||||
default:
|
||||
return selected ? 'text-stone-700' : 'text-stone-400';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="{get_text_color(selected, status)} {className} transition-colors duration-100">
|
||||
{status}
|
||||
</div>
|
||||
@@ -0,0 +1,279 @@
|
||||
<div id="splash">
|
||||
<svg id="splash-svg" viewBox="0 0 950 550">
|
||||
<!-- Paths -->
|
||||
<!-- Disabled Paths -->
|
||||
<path class="splash-stroke" d="M 385 350 V 270 H 100 V 112.5" />
|
||||
<path class="splash-stroke" d="M 445 350 V 210 H 350 V 112.5" />
|
||||
<path class="splash-stroke" d="M 505 350 V 210 H 600 V 112.5" />
|
||||
<path class="splash-stroke" d="M 565 350 V 270 H 850 V 112.5" />
|
||||
<!-- Masks -->
|
||||
<defs>
|
||||
<mask id="m1" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse">
|
||||
<path class="splash-mask" pathLength="100" d="M 385 350 V 270 H 100 V 112.5" />
|
||||
</mask>
|
||||
<mask id="m2" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse">
|
||||
<path class="splash-mask" pathLength="100" d="M 445 350 V 210 H 350 V 112.5" />
|
||||
</mask>
|
||||
<mask id="m3" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse">
|
||||
<path class="splash-mask" pathLength="100" d="M 505 350 V 210 H 600 V 112.5" />
|
||||
</mask>
|
||||
<mask id="m4" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse">
|
||||
<path class="splash-mask" pathLength="100" d="M 565 350 V 270 H 850 V 112.5" />
|
||||
</mask>
|
||||
</defs>
|
||||
<!-- Active Paths -->
|
||||
<path class="splash-stroke-active" mask="url(#m1)" d="M 385 350 V 270 H 100 V 112.5" />
|
||||
<path class="splash-stroke-active" mask="url(#m2)" d="M 445 350 V 210 H 350 V 112.5" />
|
||||
<path class="splash-stroke-active" mask="url(#m3)" d="M 505 350 V 210 H 600 V 112.5" />
|
||||
<path class="splash-stroke-active" mask="url(#m4)" d="M 565 350 V 270 H 850 V 112.5" />
|
||||
|
||||
<!-- Displays -->
|
||||
<rect x="0" y="0" width="200" height="112.5" rx="8" ry="8" class="splash-monitor" />
|
||||
<rect x="250" y="0" width="200" height="112.5" rx="8" ry="8" class="splash-monitor" />
|
||||
<rect x="500" y="0" width="200" height="112.5" rx="8" ry="8" class="splash-monitor" />
|
||||
<rect x="750" y="0" width="200" height="112.5" rx="8" ry="8" class="splash-monitor" />
|
||||
|
||||
<!-- Text -->
|
||||
<text class="splash-text" x="100" y="66.25" font-size="90">Mu</text>
|
||||
<text class="splash-text" x="350" y="66.25" font-size="90">Di</text>
|
||||
<text class="splash-text" x="600" y="66.25" font-size="90">C</text>
|
||||
<text class="splash-text" x="850" y="66.25" font-size="90">S</text>
|
||||
|
||||
<!-- Controller -->
|
||||
<rect
|
||||
x="325"
|
||||
y="350"
|
||||
width="300"
|
||||
height="168.75"
|
||||
rx="8"
|
||||
ry="8"
|
||||
class="splash-control-monitor"
|
||||
/>
|
||||
<rect x="335" y="390" width="135" height="120" rx="8" ry="8" class="splash-other-button" />
|
||||
<rect x="480" y="390" width="135" height="55" rx="8" ry="8" class="splash-other-button" />
|
||||
<rect x="480" y="455" width="135" height="55" rx="8" ry="8" class="splash-start-button" />
|
||||
|
||||
<!-- Window Controls -->
|
||||
<path class="window-controls" d="M 600 360 L 615 375" />
|
||||
<path class="window-controls" d="M 600 375 L 615 360" />
|
||||
<path class="window-controls" fill="none" d="M 575 360 H 590 V 375 H 575 V 360" />
|
||||
<path class="window-controls" d="M 550 375 H 565" />
|
||||
|
||||
<svg
|
||||
class="cursor"
|
||||
width="50"
|
||||
height="50"
|
||||
viewBox="0 0 24 24"
|
||||
x="510"
|
||||
y="480"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<g
|
||||
stroke="#ffffff"
|
||||
fill="#000000"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="#000000" d="M 6.5 10 H 18 V 19 H 6" />
|
||||
<path d="M22 14a8 8 0 0 1-8 8" />
|
||||
<path d="M18 11v-1a2 2 0 0 0-2-2a2 2 0 0 0-2 2" />
|
||||
<path d="M14 10V9a2 2 0 0 0-2-2a2 2 0 0 0-2 2v1" />
|
||||
<path d="M10 9.5V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v10" />
|
||||
<path
|
||||
d="M18 11a2 2 0 1 1 4 0v3a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--base-stroke: #444444;
|
||||
--base-stroke-active: #109e00;
|
||||
|
||||
--splash-bg: oklch(21.6% 0.006 56.043);
|
||||
--monitor-dark: #000000;
|
||||
--monitor-active: #ffffff;
|
||||
--window-controls: #cccccc;
|
||||
--start-button-inactive: #cccccc;
|
||||
--start-button-hover: #77a3b1;
|
||||
--start-button-active: #2c819b;
|
||||
--other-button: oklch(55.3% 0.013 58.071);
|
||||
--splash-control-monitor: oklch(44.4% 0.011 73.639);
|
||||
|
||||
--stroke-strength: 8;
|
||||
--dash: 16;
|
||||
--gap: 20;
|
||||
}
|
||||
|
||||
|
||||
#splash {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: var(--splash-bg);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
animation: splash-fade 1s ease 4s forwards;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
#splash-svg {
|
||||
max-height: 40vh;
|
||||
max-width: 80vw;
|
||||
opacity: 0;
|
||||
animation: fade-in 1s ease 500ms forwards;
|
||||
}
|
||||
|
||||
.splash-monitor {
|
||||
aspect-ratio: 16/9;
|
||||
rx: 8;
|
||||
ry: 8;
|
||||
width: 200;
|
||||
fill: var(--monitor-dark);
|
||||
}
|
||||
|
||||
.splash-control-monitor {
|
||||
fill: var(--splash-control-monitor);
|
||||
}
|
||||
|
||||
.splash-other-button {
|
||||
fill: var(--other-button);
|
||||
}
|
||||
|
||||
.splash-stroke,
|
||||
.splash-stroke-active {
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: var(--stroke-strength);
|
||||
stroke-dasharray: var(--dash) var(--gap);
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
stroke: var(--window-controls);
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.splash-stroke {
|
||||
stroke: var(--base-stroke);
|
||||
}
|
||||
|
||||
.splash-stroke-active {
|
||||
stroke: var(--base-stroke-active);
|
||||
}
|
||||
|
||||
.splash-mask {
|
||||
fill: none;
|
||||
stroke: #fff;
|
||||
stroke-linecap: round;
|
||||
stroke-width: var(--stroke-strength);
|
||||
stroke-dasharray: 100;
|
||||
stroke-dashoffset: 100;
|
||||
animation: draw 1s ease-in-out 1.35s forwards;
|
||||
}
|
||||
|
||||
.splash-text {
|
||||
text-anchor: middle;
|
||||
dominant-baseline: middle;
|
||||
fill: var(--monitor-dark);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.splash-monitor {
|
||||
fill: var(--monitor-dark);
|
||||
animation: activate-monitor 0ms ease 2.35s forwards;
|
||||
}
|
||||
|
||||
.splash-start-button {
|
||||
fill: var(--start-button-inactive);
|
||||
animation: start-button-hover-press 1s ease-in-out 450ms forwards;
|
||||
}
|
||||
|
||||
.cursor {
|
||||
animation: move-mouse 1s ease-in-out 0ms forwards;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes move-mouse {
|
||||
0% {
|
||||
transform: translateX(-100px);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateX(-100px);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(0px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes start-button-hover-press {
|
||||
0% {
|
||||
fill: var(--start-button-inactive);
|
||||
}
|
||||
|
||||
30% {
|
||||
fill: var(--start-button-inactive);
|
||||
}
|
||||
|
||||
40% {
|
||||
fill: var(--start-button-hover);
|
||||
}
|
||||
|
||||
70% {
|
||||
fill: var(--start-button-hover);
|
||||
}
|
||||
|
||||
80% {
|
||||
fill: var(--start-button-active);
|
||||
}
|
||||
|
||||
90% {
|
||||
fill: var(--start-button-active);
|
||||
}
|
||||
|
||||
100% {
|
||||
fill: var(--start-button-hover);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes draw {
|
||||
to {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes activate-monitor {
|
||||
to {
|
||||
fill: var(--monitor-active);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes splash-fade {
|
||||
to {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
rect {
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
/* verhindert, dass die Linien beim Skalieren dicker werden */
|
||||
.cursor * {
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user