add Keyboard Input, improve PopUp, improve general code

This commit is contained in:
E44
2025-11-07 12:15:58 +01:00
parent 10a09bc45e
commit 0bfe6371fd
11 changed files with 286 additions and 33 deletions
@@ -12,50 +12,90 @@
TrafficCone TrafficCone
} from 'lucide-svelte'; } from 'lucide-svelte';
import Button from './Button.svelte'; import Button from './Button.svelte';
import PopUp from './PopUp.svelte';
import type { PopupContent } from '../ts/types';
import KeyInput from './KeyInput.svelte';
let popup_content: PopupContent = $state({
open: false,
snippet: null,
title: '',
closable: true
});
function popup_close_function() {
popup_content.open = false;
}
const show_send_keys_popup = () => {
popup_content = {
open: true,
snippet: send_keys_popup,
title: 'Tastatur-Eingaben durchgeben',
title_icon: Keyboard,
closable: true
};
};
</script> </script>
{#snippet send_keys_popup()}
<div>
<KeyInput />
</div>
<div class="flex flex-row justify-end gap-2">
<Button className="px-4 font-bold" click_function={popup_close_function}>Fertig</Button>
</div>
{/snippet}
<div class="grid grid-rows-[2.5rem_auto] bg-stone-800 rounded-2xl min-w-0"> <div class="grid grid-rows-[2.5rem_auto] bg-stone-800 rounded-2xl min-w-0">
<div class="text-xl font-bold pl-3 content-center bg-stone-700 rounded-t-2xl truncate min-w-0"> <div class="text-xl font-bold pl-3 content-center bg-stone-700 rounded-t-2xl truncate min-w-0">
Bildschirme steuern Bildschirme steuern
</div> </div>
<div class="flex flex-col gap-2 p-2 overflow-auto"> <div class="relative flex flex-col gap-2 p-2 overflow-auto">
<div class="flex flex-row justify-between gap-2"> <div class="flex flex-row justify-between gap-2">
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex flex-row gap-2 w-70 justify-normal"> <div class="flex flex-row gap-2 w-75 justify-normal">
<Button title="Vorherige Folie (Pfeil nach Links)" className="px-7" <Button title="Vorherige Folie (Pfeil nach Links)" className="px-9"
><ArrowBigLeft /></Button ><ArrowBigLeft /></Button
> >
<Button title="Nächste Folie (Pfeil nach Rechts)" className="px-7" <Button title="Nächste Folie (Pfeil nach Rechts)" className="px-9"
><ArrowBigRight /></Button ><ArrowBigRight /></Button
> >
</div> </div>
<Button className="px-3 flex gap-3 w-70 justify-normal" <Button className="px-3 flex gap-3 w-75 justify-normal"
><TextAlignStart /> Text anzeigen</Button ><TextAlignStart /> Text anzeigen</Button
> >
<Button className="px-3 flex gap-3 w-70 justify-normal"><Presentation />Blackout</Button> <Button className="px-3 flex gap-3 w-75 justify-normal"><Presentation />Blackout</Button>
<div class="flex flex-row justify-normal"> <div class="flex flex-row justify-normal">
<Button className="rounded-r-none pl-3 flex gap-3 grow w-60 justify-normal" <Button className="rounded-r-none pl-3 flex gap-3 grow w-65 justify-normal"
><TrafficCone /> Fallback-Bild anzeigen</Button ><TrafficCone /> Fallback-Bild anzeigen</Button
> >
<Button className="rounded-l-none flex grow-0 w-10"><ChevronDown /></Button> <Button className="rounded-l-none flex grow-0 w-10"><ChevronDown /></Button>
</div> </div>
<Button className="px-3 flex gap-3 w-70 justify-normal" <Button
><Keyboard /> Tastatur-Inputs durchgeben</Button className="px-3 flex gap-3 w-75 justify-normal"
click_function={show_send_keys_popup}><Keyboard /> Tastatur-Eingaben durchgeben</Button
> >
</div> </div>
<div class="flex flex-col gap-2 justify-between"> <div class="flex flex-col gap-2 justify-between">
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<Button className="px-3 flex gap-3 w-full xl:w-70 justify-normal" <Button className="px-3 flex gap-3 w-full xl:w-75 justify-normal"
><Power /> PC hochfahren</Button ><Power /> PC hochfahren</Button
> >
<Button className="px-3 flex gap-3 w-full xl:w-70 justify-normal" <Button className="px-3 flex gap-3 w-full xl:w-75 justify-normal"
><PowerOff /> PC herunterfahren</Button ><PowerOff /> PC herunterfahren</Button
> >
</div> </div>
<Button className="px-3 flex gap-3 w-full xl:w-70 justify-normal" <Button className="px-3 flex gap-3 w-full xl:w-75 justify-normal"
><SquareTerminal /> Shell-Befehl ausführen</Button ><SquareTerminal /> Shell-Befehl ausführen</Button
> >
</div> </div>
</div> </div>
<PopUp
content={popup_content}
close_function={popup_close_function}
className="rounded-b-2xl"
snippet_container_class="overflow-hidden"
/>
</div> </div>
</div> </div>
@@ -10,6 +10,7 @@
import OnlineState from './OnlineState.svelte'; import OnlineState from './OnlineState.svelte';
import type { Display, MenuOption } from '../ts/types'; import type { Display, MenuOption } from '../ts/types';
import { is_selected, select, selected_display_ids } from '../ts/stores/select'; import { is_selected, select, selected_display_ids } from '../ts/stores/select';
import { update_screenshot } from '../ts/stores/displays';
let { display, get_display_menu_options } = $props<{ let { display, get_display_menu_options } = $props<{
display: Display; display: Display;
@@ -29,6 +30,7 @@
} else { } else {
$pinned_display_id = display.id; $pinned_display_id = display.id;
} }
update_screenshot(display.id);
e.stopPropagation(); e.stopPropagation();
} }
</script> </script>
@@ -102,7 +102,7 @@
<div class="flex flex-row gap-1"> <div class="flex flex-row gap-1">
<OnlineState <OnlineState
selected={false} selected={false}
status={pinned_display?.status ?? ''} status={pinned_display?.status ?? null}
className="flex items-center px-2" className="flex items-center px-2"
/> />
<Button <Button
@@ -30,7 +30,12 @@
import RefreshPlay from './RefreshPlay.svelte'; import RefreshPlay from './RefreshPlay.svelte';
import { get_file_size_display_string } from '../ts/utils'; import { get_file_size_display_string } from '../ts/utils';
import { open_file } from '../ts/api_handler'; import { open_file } from '../ts/api_handler';
import { displays, get_display_by_id, update_screenshot } from '../ts/stores/displays'; import {
displays,
get_display_by_id,
run_on_all_selected_displays,
update_screenshot
} from '../ts/stores/displays';
let { file } = $props<{ file: FolderElement }>(); let { file } = $props<{ file: FolderElement }>();
@@ -97,13 +102,7 @@
change_file_path($current_file_path + file.name + '/'); change_file_path($current_file_path + file.name + '/');
} else { } else {
const path_to_file = $current_file_path + file.name; const path_to_file = $current_file_path + file.name;
for (const display_id of $selected_display_ids) { await run_on_all_selected_displays(open_file, true, path_to_file);
const ip = get_display_by_id(display_id, $displays)?.ip ?? null;
if (ip) {
await open_file(ip, path_to_file);
await update_screenshot(display_id);
}
}
} }
} }
</script> </script>
@@ -0,0 +1,70 @@
<script lang="ts">
import { flip } from 'svelte/animate';
import { get_selectable_color_classes } from '../ts/stores/ui_behavior';
import key_map_json from './../../../../shared/keys.json';
import { fade } from 'svelte/transition';
import { selected_display_ids } from '../ts/stores/select';
import { displays, get_display_by_id, run_on_all_selected_displays } from '../ts/stores/displays';
import { send_keyboard_input } from '../ts/api_handler';
const bg = 'bg-stone-700';
const key_map: Record<string, string> = key_map_json as Record<string, string>;
let active = $state(false);
let last_keys: { id: number; key: string }[] = $state([]);
let el: HTMLDivElement;
function add_to_last_keys(name: string) {
const id = Date.now();
last_keys.push({ id, key: name });
setTimeout(() => {
last_keys = last_keys.filter((e) => e.id !== id);
}, 1500);
}
async function on_key_down(e: KeyboardEvent) {
if (!active) return;
const id = key_map[e.code];
if (!id) return;
e.preventDefault();
e.stopPropagation();
add_to_last_keys(e.code);
if (e.repeat) return;
await run_on_all_selected_displays(send_keyboard_input, true, id);
}
</script>
<div
role="textbox"
tabindex="0"
bind:this={el}
onclick={() => {
if (active) {
el.blur();
} else {
el.focus();
active = true;
}
}}
onblur={() => (active = false)}
onkeydown={on_key_down}
class="relative flex justify-center items-center h-15 w-full cursor-pointer rounded-xl transition-colors duration-200 select-none {get_selectable_color_classes(
active,
{
bg: true,
hover: true,
active: true,
text: true
}
)}"
>
{active ? 'Erfassung aktiv' : 'Hier für Erfassung klicken'}
<div class="absolute top-full left-0 ml-1 mt-0.5 flex flex-col-reverse text-sm text-stone-400">
{#each last_keys as key (key.id)}
<span animate:flip={{ duration: 200 }} in:fade={{ duration: 200 }} out:fade={{ duration: 500 }} >{key.key}</span>
{/each}
</div>
</div>
+8 -6
View File
@@ -5,9 +5,11 @@
import type { PopupContent } from '../ts/types'; import type { PopupContent } from '../ts/types';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
let { content, close_function } = $props<{ let { content, close_function, className = '', snippet_container_class = '' } = $props<{
content: PopupContent; content: PopupContent;
close_function: () => void; close_function: () => void;
className?: string;
snippet_container_class?: string;
}>(); }>();
function try_to_close() { function try_to_close() {
@@ -31,7 +33,7 @@
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<div <div
class="absolute inset-0 backdrop-blur bg-white/10 flex justify-center items-center" class="absolute inset-0 backdrop-blur flex justify-center items-center {className}"
onclick={try_to_close} onclick={try_to_close}
transition:fade={{ duration: 100 }} transition:fade={{ duration: 100 }}
> >
@@ -42,12 +44,12 @@
onclick={(e) => e.stopPropagation()} onclick={(e) => e.stopPropagation()}
> >
<div <div
class="text-2xl font-bold bg-stone-700 p-1.5 flex flex-row justify-between gap-6 w-full" class="font-bold bg-stone-700 p-1.5 flex flex-row justify-between gap-8 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 ?? ''}"> <div class="flex flex-row flex-1 gap-3 pl-2 py-1 items-center grow whitespace-nowrap min-w-0 flex-shrink-0 text-lg {content.title_class ?? ''}">
{#if content.title_icon} {#if content.title_icon}
{@const Icon = content.title_icon} {@const Icon = content.title_icon}
<Icon strokeWidth="2.8" class="flex-shrink-0" /> <Icon strokeWidth="2" class="flex-shrink-0" />
{/if} {/if}
<div class="flex-shrink-0"> <div class="flex-shrink-0">
{content.title} {content.title}
@@ -61,7 +63,7 @@
{/if} {/if}
</div> </div>
</div> </div>
<div class="p-2 min-h-0 overflow-auto flex flex-col gap-2"> <div class="p-2 min-h-0 overflow-auto flex flex-col gap-2 {snippet_container_class}">
{@render content.snippet(content.snippet_arg)} {@render content.snippet(content.snippet_arg)}
</div> </div>
</div> </div>
+5 -2
View File
@@ -30,6 +30,7 @@
open: false, open: false,
snippet: null, snippet: null,
title: '', title: '',
title_class: '!text-xl',
closable: true closable: true
}); });
@@ -73,6 +74,7 @@
snippet: add_new_display_popup, snippet: add_new_display_popup,
title: 'Neuen Bildschirm hinzufügen', title: 'Neuen Bildschirm hinzufügen',
title_icon: Monitor, title_icon: Monitor,
title_class: '!text-xl',
closable: true closable: true
}; };
}; };
@@ -83,7 +85,7 @@
snippet: remove_display_popup, snippet: remove_display_popup,
snippet_arg: display_id, snippet_arg: display_id,
title: 'Bildschirm wirklich löschen?', title: 'Bildschirm wirklich löschen?',
title_class: 'text-red-400', title_class: 'text-red-400 !text-xl',
title_icon: Trash2, title_icon: Trash2,
closable: true closable: true
}; };
@@ -103,6 +105,7 @@
snippet_arg: display_id, snippet_arg: display_id,
title: 'Bildschirm bearbeiten', title: 'Bildschirm bearbeiten',
title_icon: Monitor, title_icon: Monitor,
title_class: '!text-xl',
closable: true closable: true
}; };
}; };
@@ -247,5 +250,5 @@
<FileView /> <FileView />
</div> </div>
</div> </div>
<PopUp content={popup_content} close_function={popup_close_function} /> <PopUp content={popup_content} close_function={popup_close_function} className="bg-white/10" />
</main> </main>
+14 -3
View File
@@ -7,10 +7,21 @@ export async function get_screenshot(ip: string) {
return await request_display(ip, '/takeScreenshot', options); return await request_display(ip, '/takeScreenshot', options);
} }
export async function open_file(ip: string, path_to_file: string): Promise<boolean> { export async function open_file(ip: string, path_to_file: string): Promise<void> {
const options = { method: 'PATCH', headers: { 'content-type': 'application/octet-stream' } }; const options = { method: 'PATCH', headers: { 'content-type': 'application/octet-stream' } };
const raw_response = await request_display(ip, `/file${path_to_file}`, options); await request_display(ip, `/file${path_to_file}`, options);
return !!raw_response; }
export async function send_keyboard_input(ip: string, key: string): Promise<void> {
const options = {
method: 'PATCH',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
key: key,
}),
};
console.log(options)
await request_display(ip, '/keyboardInput', options);
} }
export async function get_file_data(ip: string, path: string): Promise<FolderElement[]> { export async function get_file_data(ip: string, path: string): Promise<FolderElement[]> {
@@ -156,6 +156,18 @@ export async function update_displays_with_map(update_function: (display: Displa
displays.set(updated_groups); displays.set(updated_groups);
} }
export async function run_on_all_selected_displays(run_function: ((ip: string, ...args: any[]) => void | Promise<void>), update_screenshot_afterwards: boolean, ...args: any[]) {
for (const display_id of get(selected_display_ids)) {
const display_ip = get_display_by_id(display_id, get(displays))?.ip;
if (display_ip) {
await run_function(display_ip, ...args)
if (update_screenshot_afterwards) {
await update_screenshot(display_id);
}
}
}
}
@@ -14,7 +14,7 @@ function createNotifications() {
function push(type: "error" | "success" | "info", title: string, message: string = "", className: string = "") { function push(type: "error" | "success" | "info", title: string, message: string = "", className: string = "") {
const id = Date.now(); const id = Date.now();
const duration = type === "error" ? 8000 : 4000; const duration = type === "error" ? 16000 : 4000;
update((n) => [...n, { id, title, message, duration, className, type }]); update((n) => [...n, { id, title, message, duration, className, type }]);
setTimeout(() => { setTimeout(() => {
update((n) => n.filter((x) => x.id !== id)); update((n) => n.filter((x) => x.id !== id));
+114
View File
@@ -0,0 +1,114 @@
{
"Escape": "VK_ESC",
"Digit1": "VK_1",
"Digit2": "VK_2",
"Digit3": "VK_3",
"Digit4": "VK_4",
"Digit5": "VK_5",
"Digit6": "VK_6",
"Digit7": "VK_7",
"Digit8": "VK_8",
"Digit9": "VK_9",
"Digit0": "VK_0",
"KeyQ": "VK_Q",
"KeyW": "VK_W",
"KeyE": "VK_E",
"KeyR": "VK_R",
"KeyT": "VK_T",
"KeyY": "VK_Y",
"KeyU": "VK_U",
"KeyI": "VK_I",
"KeyO": "VK_O",
"KeyP": "VK_P",
"KeyA": "VK_A",
"KeyS": "VK_S",
"KeyD": "VK_D",
"KeyF": "VK_F",
"KeyG": "VK_G",
"KeyH": "VK_H",
"KeyJ": "VK_J",
"KeyK": "VK_K",
"KeyL": "VK_L",
"KeyZ": "VK_Z",
"KeyX": "VK_X",
"KeyC": "VK_C",
"KeyV": "VK_V",
"KeyB": "VK_B",
"KeyN": "VK_N",
"KeyM": "VK_M",
"F1": "VK_F1",
"F2": "VK_F2",
"F3": "VK_F3",
"F4": "VK_F4",
"F5": "VK_F5",
"F6": "VK_F6",
"F7": "VK_F7",
"F8": "VK_F8",
"F9": "VK_F9",
"F10": "VK_F10",
"F11": "VK_F11",
"F12": "VK_F12",
"F13": "VK_F13",
"F14": "VK_F14",
"F15": "VK_F15",
"F16": "VK_F16",
"F17": "VK_F17",
"F18": "VK_F18",
"F19": "VK_F19",
"F20": "VK_F20",
"F21": "VK_F21",
"F22": "VK_F22",
"F23": "VK_F23",
"F24": "VK_F24",
"NumLock": "VK_NUMLOCK",
"ScrollLock": "VK_SCROLLLOCK",
"CapsLock": "VK_CAPSLOCK",
"Minus": "VK_SP2",
"Equal": "VK_SP3",
"Backspace": "VK_BACKSPACE",
"Tab": "VK_TAB",
"BracketLeft": "VK_SP4",
"BracketRight": "VK_SP5",
"Enter": "VK_ENTER",
"Semicolon": "VK_SP6",
"Quote": "VK_SP7",
"Backquote": "VK_SP1",
"Backslash": "VK_SP8",
"Comma": "VK_SP9",
"Period": "VK_SP10",
"Slash": "VK_SP11",
"IntlBackslash": "VK_SP12",
"NumpadMultiply": "VK_KPASTERISK",
"NumpadDivide": "VK_KPSLASH",
"NumpadAdd": "VK_KPPLUS",
"NumpadSubtract": "VK_KPMINUS",
"NumpadDecimal": "VK_KPDOT",
"NumpadEnter": "VK_KPENTER",
"Space": "VK_SPACE",
"Numpad0": "VK_KP0",
"Numpad1": "VK_KP1",
"Numpad2": "VK_KP2",
"Numpad3": "VK_KP3",
"Numpad4": "VK_KP4",
"Numpad5": "VK_KP5",
"Numpad6": "VK_KP6",
"Numpad7": "VK_KP7",
"Numpad8": "VK_KP8",
"Numpad9": "VK_KP9",
"PageUp": "VK_PAGEUP",
"PageDown": "VK_PAGEDOWN",
"End": "VK_END",
"Home": "VK_HOME",
"ArrowLeft": "VK_LEFT",
"ArrowUp": "VK_UP",
"ArrowRight": "VK_RIGHT",
"ArrowDown": "VK_DOWN",
"PrintScreen": "VK_PRINT",
"Insert": "VK_INSERT",
"Delete": "VK_DELETE",
"Help": "VK_HELP",
"BrowserBack": "VK_BACK",
"Pause": "VK_PAUSE",
"Lang1": "VK_HANGUEL",
"Lang2": "VK_HANJA"
}