mirror of
https://codeberg.org/PLG-Development/PLG-MuDiCS
synced 2026-07-05 16:37:09 +00:00
add new monitor popup finished
This commit is contained in:
@@ -1,51 +1,69 @@
|
||||
<script lang="ts">
|
||||
import type { X } from 'lucide-svelte';
|
||||
import { X } from 'lucide-svelte';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import Button from './Button.svelte';
|
||||
import type { PopupContent } from '../ts/types';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
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;
|
||||
let { content, close_function } = $props<{
|
||||
content: PopupContent;
|
||||
close_function: () => void;
|
||||
}>();
|
||||
|
||||
// onMount(() => {
|
||||
// const handler = (e: KeyboardEvent) => {
|
||||
// // if (e.key === 'Escape') dispatch('close');
|
||||
// };
|
||||
// window.addEventListener('keydown', handler);
|
||||
// onDestroy(() => window.removeEventListener('keydown', handler));
|
||||
// });
|
||||
function try_to_close() {
|
||||
if (!content.closable || !content.open) return;
|
||||
close_function();
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
try_to_close();
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
window.addEventListener('keydown', handleKeydown);
|
||||
return () => window.removeEventListener('keydown', handleKeydown);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="absolute inset-0 backdrop-blur bg-white/10 flex justify-center items-center">
|
||||
{#if content.open}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="bg-stone-800 rounded-2xl min-w-[40%] min-h-[30%] max-w-[80%] max-h-[80%] flex flex-col overflow-hidden shadow-2xl/30"
|
||||
class="absolute inset-0 backdrop-blur bg-white/10 flex justify-center items-center"
|
||||
onclick={try_to_close}
|
||||
transition:fade={{ duration: 100 }}
|
||||
>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="text-2xl font-bold bg-stone-700 {title_class} px-4 py-2 flex flex-row justify-between"
|
||||
class="bg-stone-800 rounded-2xl min-w-[30%] min-h-[30%] max-w-[80%] max-h-[80%] flex flex-col shadow-2xl/60 overflow-hidden"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div>
|
||||
{title}
|
||||
<div
|
||||
class="text-2xl font-bold bg-stone-700 p-1.5 flex flex-row justify-between gap-6 w-full"
|
||||
>
|
||||
<div class="flex flex-row flex-1 gap-4 pl-2 py-1 items-center grow whitespace-nowrap min-w-0 flex-shrink-0 {content.title_class ?? ''}">
|
||||
{#if content.title_icon}
|
||||
{@const Icon = content.title_icon}
|
||||
<Icon strokeWidth="2.8" class="flex-shrink-0" />
|
||||
{/if}
|
||||
<div class="flex-shrink-0">
|
||||
{content.title}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex aspect-square flex-shrink-0">
|
||||
{#if content.closable}
|
||||
<Button className="aspect-square !p-1.5" click_function={try_to_close}>
|
||||
<X />
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{#if title_icon}
|
||||
{@const Icon = title_icon}
|
||||
<Icon class="size-full" />
|
||||
{/if}
|
||||
<div class="p-2 min-h-0 overflow-auto flex flex-col gap-2">
|
||||
{@render content.snippet()}
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 py-2 min-h-0 overflow-auto">
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
import { get_shifted_color } from '../ts/stores/ui_behavior';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let {
|
||||
current_value = $bindable(),
|
||||
current_valid = $bindable(),
|
||||
className = '',
|
||||
bg = 'bg-stone-750',
|
||||
title,
|
||||
placeholder = '',
|
||||
is_valid_function = null
|
||||
} = $props<{
|
||||
current_value: string;
|
||||
current_valid: boolean;
|
||||
className?: string;
|
||||
bg?: string;
|
||||
title: string;
|
||||
placeholder?: string;
|
||||
is_valid_function?: ((input: string) => [boolean, string]) | null;
|
||||
}>();
|
||||
|
||||
let focus_bg = get_shifted_color(bg, 100);
|
||||
let focussed = $state(false);
|
||||
let current_info = $state('');
|
||||
|
||||
function validate_input() {
|
||||
if (!is_valid_function) return;
|
||||
[current_valid, current_info] = is_valid_function(current_value.trim());
|
||||
}
|
||||
|
||||
function get_highlighting_string(): string {
|
||||
if (!is_valid_function) return '';
|
||||
if (current_valid) {
|
||||
return 'focus:inset-ring-2 focus:inset-ring-green-400';
|
||||
} else {
|
||||
return 'inset-ring-2 inset-ring-red-400';
|
||||
}
|
||||
}
|
||||
onMount(() => {
|
||||
validate_input();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col {className}">
|
||||
<div class="flex flex-row justify-between text-sm px-1">
|
||||
<div class="text-stone-400">
|
||||
{title}:
|
||||
</div>
|
||||
{#if is_valid_function && focussed}
|
||||
<div class={current_valid ? "text-green-400" : "text-red-400"} transition:fade={{ duration: 100 }}>
|
||||
{current_info}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<input
|
||||
bind:value={current_value}
|
||||
type="text"
|
||||
oninput={validate_input}
|
||||
onfocus={() => {
|
||||
focussed = true;
|
||||
}}
|
||||
onfocusout={() => {
|
||||
focussed = false;
|
||||
}}
|
||||
class="{bg} focus:{focus_bg} outline-none py-2 px-3 rounded-xl transition-all duration-100 {get_highlighting_string()}"
|
||||
{placeholder}
|
||||
/>
|
||||
</div>
|
||||
@@ -1,26 +1,147 @@
|
||||
<script lang="ts">
|
||||
import { Plus, Settings } from 'lucide-svelte';
|
||||
import { Monitor, Plus, Radio, Settings, X } from 'lucide-svelte';
|
||||
import Button from '../components/Button.svelte';
|
||||
import FileView from '../components/FileView.svelte';
|
||||
import ControlView from '../components/ControlView.svelte';
|
||||
import DisplayView from '../components/DisplayView.svelte';
|
||||
import SplashScreen from './../../../../shared/splash_screen.html?raw';
|
||||
import PopUp from '../components/PopUp.svelte';
|
||||
import type { PopupContent } from '../ts/types';
|
||||
import TextInput from '../components/TextInput.svelte';
|
||||
import { add_display, is_display_name_taken } from '../ts/stores/displays';
|
||||
import { text } from '@sveltejs/kit';
|
||||
|
||||
const ip_regex =
|
||||
/^(?:(?:10|127)\.(?:25[0-5]|2[0-4]\d|1?\d?\d)\.(?:25[0-5]|2[0-4]\d|1?\d?\d)\.(?:25[0-5]|2[0-4]\d|1?\d?\d)|192\.168\.(?:25[0-5]|2[0-4]\d|1?\d?\d)\.(?:25[0-5]|2[0-4]\d|1?\d?\d)|172\.(?:1[6-9]|2\d|3[0-1])\.(?:25[0-5]|2[0-4]\d|1?\d?\d)\.(?:25[0-5]|2[0-4]\d|1?\d?\d))$/;
|
||||
const mac_regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
|
||||
|
||||
let popup_content: PopupContent = $state({
|
||||
open: false,
|
||||
snippet: null,
|
||||
title: '',
|
||||
closable: true
|
||||
});
|
||||
|
||||
let text_inputs_valid = $state({
|
||||
name: { valid: false, value: '' },
|
||||
ip: { valid: false, value: '' },
|
||||
mac: { valid: false, value: '' }
|
||||
});
|
||||
|
||||
function all_text_inputs_valid(): boolean {
|
||||
for (const entry of Object.values(text_inputs_valid)) {
|
||||
if (!entry.valid) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function finalize_add_new_display() {
|
||||
const ip = text_inputs_valid.ip.value;
|
||||
const mac = text_inputs_valid.mac.value === '' ? null : text_inputs_valid.mac.value;
|
||||
const name = text_inputs_valid.name.value;
|
||||
add_display(ip, mac, name, 'Online');
|
||||
popup_close_function();
|
||||
text_inputs_valid = {
|
||||
name: { valid: false, value: '' },
|
||||
ip: { valid: false, value: '' },
|
||||
mac: { valid: false, value: '' }
|
||||
};
|
||||
}
|
||||
|
||||
function popup_close_function() {
|
||||
popup_content.open = false;
|
||||
}
|
||||
|
||||
const show_new_display_popup = () => {
|
||||
popup_content = {
|
||||
open: true,
|
||||
snippet: add_new_display,
|
||||
title: 'Neuen Bildschirm hinzufügen',
|
||||
title_icon: Monitor,
|
||||
closable: true
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
{#snippet add_new_display()}
|
||||
<TextInput
|
||||
bind:current_value={text_inputs_valid.name.value}
|
||||
bind:current_valid={text_inputs_valid.name.valid}
|
||||
title="Anzeigename"
|
||||
placeholder="z.B. Beamer vorne links"
|
||||
is_valid_function={(input: string) => {
|
||||
return input.length === 0 || input.length > 50
|
||||
? [false, 'Ungültige Länge']
|
||||
: is_display_name_taken(input)
|
||||
? [false, 'Name bereits verwendet']
|
||||
: [true, 'Gültiger Name'];
|
||||
}}
|
||||
/>
|
||||
<div class="flex flex-row gap-2">
|
||||
<TextInput
|
||||
bind:current_value={text_inputs_valid.ip.value}
|
||||
bind:current_valid={text_inputs_valid.ip.valid}
|
||||
title="IP-Adresse"
|
||||
placeholder="z.B. 192.168.176.111"
|
||||
is_valid_function={(input: string) => {
|
||||
return ip_regex.test(input)
|
||||
? [true, 'Gültige IP-Adresse']
|
||||
: [false, 'Ungültige IP-Adresse'];
|
||||
}}
|
||||
className="grow"
|
||||
/>
|
||||
<div class="flex items-end shrink-0">
|
||||
<Button disabled={!text_inputs_valid.ip.valid} className="px-4 gap-2" bg="bg-stone-750"
|
||||
><Radio /> Ping</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<TextInput
|
||||
bind:current_value={text_inputs_valid.mac.value}
|
||||
bind:current_valid={text_inputs_valid.mac.valid}
|
||||
title="MAC-Adresse (optional)"
|
||||
placeholder="z.B. D4:81:A6:C4:BF:3F"
|
||||
is_valid_function={(input: string) => {
|
||||
return input === ''
|
||||
? [true, 'Keine MAC-Adresse (WOL deaktiviert)']
|
||||
: mac_regex.test(input)
|
||||
? [true, 'Gültige MAC-Adresse']
|
||||
: [false, 'Ungültige MAC-Adresse'];
|
||||
}}
|
||||
/>
|
||||
<div class="flex justify-end pt-2">
|
||||
<Button
|
||||
disabled={!all_text_inputs_valid()}
|
||||
className="pl-3 pr-4 gap-2 font-bold"
|
||||
bg="bg-stone-650"
|
||||
click_function={finalize_add_new_display}><Plus /> Bildschirm hinzufügen</Button
|
||||
>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<main class="bg-stone-900 h-dvh w-dvw text-stone-200 px-4 py-2 gap-2 grid grid-rows-[3rem_auto]">
|
||||
<!-- {@html SplashScreen} -->
|
||||
|
||||
<div class="w-[calc(100dvw-(8*var(--spacing)))] flex justify-between">
|
||||
<span class="text-4xl font-bold content-center pl-1"> PLG MuDiCS </span>
|
||||
<Button className="aspect-square" bg="bg-stone-800" div_class="aspect-square" menu_options={[{
|
||||
icon: Plus,
|
||||
name: "Bildschirm hinzufügen",
|
||||
},
|
||||
{
|
||||
icon: Settings,
|
||||
name: "Weitere Einstellungen",
|
||||
}]}>
|
||||
<Button
|
||||
className="aspect-square"
|
||||
bg="bg-stone-800"
|
||||
div_class="aspect-square"
|
||||
menu_options={[
|
||||
{
|
||||
icon: Plus,
|
||||
name: 'Neuen Bildschirm hinzufügen',
|
||||
on_select: show_new_display_popup
|
||||
},
|
||||
{
|
||||
icon: Settings,
|
||||
name: 'Weitere Einstellungen'
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Settings></Settings>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -33,7 +154,5 @@
|
||||
<FileView />
|
||||
</div>
|
||||
</div>
|
||||
<!-- <PopUp title="Einstellungen" title_icon={Settings}>
|
||||
<div>ok</div>
|
||||
</PopUp> -->
|
||||
<PopUp content={popup_content} close_function={popup_close_function} />
|
||||
</main>
|
||||
|
||||
@@ -61,6 +61,26 @@ module.exports = {
|
||||
'active:bg-stone-900',
|
||||
'active:bg-stone-950',
|
||||
|
||||
'focus:bg-stone-50',
|
||||
'focus:bg-stone-100',
|
||||
'focus:bg-stone-150',
|
||||
'focus:bg-stone-200',
|
||||
'focus:bg-stone-250',
|
||||
'focus:bg-stone-300',
|
||||
'focus:bg-stone-350',
|
||||
'focus:bg-stone-400',
|
||||
'focus:bg-stone-450',
|
||||
'focus:bg-stone-500',
|
||||
'focus:bg-stone-550',
|
||||
'focus:bg-stone-600',
|
||||
'focus:bg-stone-650',
|
||||
'focus:bg-stone-700',
|
||||
'focus:bg-stone-750',
|
||||
'focus:bg-stone-800',
|
||||
'focus:bg-stone-850',
|
||||
'focus:bg-stone-900',
|
||||
'focus:bg-stone-950',
|
||||
|
||||
'text-stone-50',
|
||||
'text-stone-150',
|
||||
'text-stone-200',
|
||||
|
||||
@@ -10,7 +10,14 @@ export const displays: Writable<DisplayGroup[]> = writable<DisplayGroup[]>([{
|
||||
}]);
|
||||
|
||||
|
||||
function add_display(ip: string, mac: string, name: string, status: string) {
|
||||
export function is_display_name_taken(name: string): boolean {
|
||||
const display_groups = get(displays);
|
||||
return display_groups.some(group =>
|
||||
group.data.some(display => display.name.trim().toLowerCase() === name.trim().toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
export function add_display(ip: string, mac: string|null, name: string, status: string) {
|
||||
displays.update((displays: DisplayGroup[]) => {
|
||||
displays[0].data.push({ id: get_uuid(), ip, preview_url: null, preview_timeout_id: null, mac, name, status });
|
||||
return displays;
|
||||
@@ -123,6 +130,8 @@ export async function update_screenshot(display_id: string, check_type: "first_c
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
add_testing_displays();
|
||||
function add_testing_displays() {
|
||||
// const names = ["Vorne Rechts", "Vorne Links", "Vorne Mitte", "Fernseher Rechts", "Fernseher Bühne", "UIUIUIUIUIUIUISEHRLANGERTEXT DER IST WIRKLICH LANG, DER TEXT, so lang, dass er wirklich nirgendswo hinpasst, nichtmal da oben /\\"];
|
||||
@@ -130,6 +139,6 @@ function add_testing_displays() {
|
||||
// add_display("127.0.0.1", "00:1A:2B:3C:4D:5E", name, "Offline");
|
||||
// }
|
||||
|
||||
add_display("127.0.0.1", "00:1A:2B:3C:4D:5E", "PC", "Offline");
|
||||
// add_display("127.0.0.1", "00:1A:2B:3C:4D:5E", "PC", "Offline");
|
||||
// add_display("192.168.178.111", "D4:81:D7:C0:DF:3C", "Laptop", "Online");
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export type Display = {
|
||||
ip: string;
|
||||
preview_url: string | null;
|
||||
preview_timeout_id: number | null;
|
||||
mac: string;
|
||||
mac: string|null;
|
||||
name: string;
|
||||
status: string;
|
||||
}
|
||||
@@ -77,4 +77,13 @@ export type MenuOption = {
|
||||
class?: string;
|
||||
on_select?: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export type PopupContent = {
|
||||
open: boolean;
|
||||
snippet: any;
|
||||
title: string;
|
||||
title_class?: string;
|
||||
title_icon?: typeof X | null;
|
||||
closable?: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user