chore: add display text

This commit is contained in:
E44
2025-11-21 22:08:27 +01:00
parent 94bad9a72f
commit 5b891ae64c
10 changed files with 1887 additions and 84 deletions
@@ -15,13 +15,11 @@
import PopUp from './PopUp.svelte';
import type { PopupContent } from '../ts/types';
import KeyInput from './KeyInput.svelte';
import { send_keyboard_input, show_blackscreen } from '../ts/api_handler';
import { send_keyboard_input, show_blackscreen, show_html } from '../ts/api_handler';
import { run_on_all_selected_displays } from '../ts/stores/displays';
import { selected_display_ids } from '../ts/stores/select';
import { onMount } from 'svelte';
let show_text_body: string = `<p>I've been working on this <strong>new feature</strong> for the past few days.
It's been quite <em>challenging</em> but I think I'm making good progress.</p>`;
import TipTapInput from './TipTapInput.svelte';
let popup_content: PopupContent = $state({
open: false,
@@ -50,28 +48,25 @@
snippet: text_popup,
title: 'Text anzeigen',
title_icon: TextAlignStart,
closable: true
closable: true,
window_class: 'size-full'
};
};
onMount(() => {
show_text_popup();
});
</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 class="overflow-hidden flex flex-col gap-2">
<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>
</div>
{/snippet}
{#snippet text_popup()}
<!-- <div class="flex flex-row justify-end gap-2">
<Button className="px-4 font-bold" click_function={popup_close_function}>Fertig</Button>
</div> -->
<TipTapInput />
{/snippet}
<div class="grid grid-rows-[2.5rem_auto] bg-stone-800 rounded-2xl min-w-0">
@@ -159,7 +154,7 @@
content={popup_content}
close_function={popup_close_function}
className="rounded-b-2xl"
snippet_container_class="overflow-hidden"
snippet_container_class="size-full"
/>
</div>
</div>
@@ -181,7 +181,7 @@
</span>
<div class="flex flex-row gap-1">
<button
class="gap-2 min-w-40 px-4 rounded-xl cursor-pointer duration-200 transition-colors {get_selectable_color_classes(
class="min-w-40 px-4 rounded-xl cursor-pointer duration-200 transition-colors {get_selectable_color_classes(
all_selected($displays, $selected_display_ids),
{
bg: true,
+29 -20
View File
@@ -5,7 +5,12 @@
import type { PopupContent } from '../ts/types';
import { fade } from 'svelte/transition';
let { content, close_function, className = '', snippet_container_class = '' } = $props<{
let {
content,
close_function,
className = '',
snippet_container_class = ''
} = $props<{
content: PopupContent;
close_function: () => void;
className?: string;
@@ -40,29 +45,33 @@
<!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
class="bg-stone-800 rounded-2xl min-w-[30%] max-w-[95%] max-h-[90%] flex flex-col shadow-2xl/60 overflow-hidden"
class="bg-stone-800 rounded-2xl min-w-[30%] max-w-[90%] max-h-[85%] flex flex-col shadow-2xl/60 overflow-hidden {content.window_class ??
''}"
onclick={(e) => e.stopPropagation()}
>
<div
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-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}
{@const Icon = content.title_icon}
<Icon strokeWidth="2" class="flex-shrink-0" />
{/if}
<div class="flex-shrink-0">
{content.title}
{#if content.title}
<div 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-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}
{@const Icon = content.title_icon}
<Icon strokeWidth="2" 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 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>
{/if}
<div class="p-2 min-h-0 overflow-auto flex flex-col gap-2 {snippet_container_class}">
{@render content.snippet(content.snippet_arg)}
</div>
@@ -0,0 +1,255 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { Editor } from '@tiptap/core';
import { StarterKit } from '@tiptap/starter-kit';
import Placeholder from '@tiptap/extension-placeholder';
import {
Baseline,
Bold,
Code,
Highlighter,
Italic,
PaintBucket,
QrCode,
Strikethrough
} from 'lucide-svelte';
import Button from './Button.svelte';
import { run_on_all_selected_displays } from '../ts/stores/displays';
import { show_html } from '../ts/api_handler';
import { get_selectable_color_classes } from '../ts/stores/ui_behavior';
import { TextStyle } from '@tiptap/extension-text-style';
import { Color } from '@tiptap/extension-text-style';
import Highlight from '@tiptap/extension-highlight';
type TextEditOption = {
onclick: () => void;
is_selected: () => boolean;
icon: typeof Bold;
title: string;
color?: ColorElement;
};
type ColorElement = {
color_string: () => string;
color_picker: () => void;
};
type ColorState = {
el: HTMLInputElement | null;
value: string;
};
let element = $state<HTMLElement>();
let editor_state = $state<{ editor: Editor | null }>({ editor: null });
let color_states: { text: ColorState; highlight: ColorState; bg: ColorState } = $state({
text: { el: null, value: '#b91c1c' },
highlight: { el: null, value: '#0c4a6e' },
bg: { el: null, value: '#1c1917' }
});
const text_edit_options: TextEditOption[][] = [
[
{
onclick: () => editor_state.editor?.chain().focus().toggleBold().run(),
is_selected: () => editor_state.editor?.isActive('bold') ?? false,
title: 'Fett (STRG+B)',
icon: Bold
},
{
onclick: () => editor_state.editor?.chain().focus().toggleItalic().run(),
is_selected: () => editor_state.editor?.isActive('italic') ?? false,
title: 'Kursiv (STRG+I)',
icon: Italic
},
{
onclick: () => editor_state.editor?.chain().focus().toggleStrike().run(),
is_selected: () => editor_state.editor?.isActive('strike') ?? false,
title: 'Durchgestrichen',
icon: Strikethrough
},
{
onclick: () => {},
is_selected: () => false,
title: 'QR-Code anfügen',
icon: QrCode
}
],
[
{
onclick: () => editor_state.editor?.chain().focus().setColor(color_states.text.value).run(),
is_selected: () =>
editor_state.editor?.isActive('textStyle', { color: color_states.text.value }) ?? false,
icon: Baseline,
title: 'Textfarbe',
color: get_color_element(color_states.text)
},
{
onclick: () =>
editor_state.editor
?.chain()
.focus()
.toggleHighlight({ color: color_states.highlight.value })
.run(),
is_selected: () =>
editor_state.editor?.isActive('highlight', { color: color_states.highlight.value }) ??
false,
icon: Highlighter,
title: 'Markierungsfarbe',
color: get_color_element(color_states.highlight)
},
{
onclick: () => color_states.bg.el?.click(),
is_selected: () => false,
title: 'Hintergrundfarbe',
icon: PaintBucket
}
]
];
function get_color_element(color_state: ColorState) {
return {
color_string: () => color_state.value,
color_picker: () => color_state.el?.click()
};
}
function show_text() {
const html =
editor_state.editor?.getHTML() +
`<style>:root {--background-color: ${color_states.bg.value} !important;}
</style>`;
run_on_all_selected_displays(show_html, true, html);
}
onMount(() => {
editor_state.editor = new Editor({
element: element,
extensions: [
StarterKit,
Placeholder.configure({
placeholder: 'Text hier eingeben ...'
}),
TextStyle,
Color,
Highlight.configure({
multicolor: true
})
],
content: '',
onTransaction: ({ editor }) => {
// Increment the state signal to force a re-render
editor_state = { editor };
},
autofocus: true
});
});
onDestroy(() => {
editor_state.editor?.destroy();
});
</script>
{#each Object.values(color_states) as color_state}
<input type="color" bind:this={color_state.el} bind:value={color_state.value} class="hidden" />
{/each}
<div class="flex flex-row gap-2 size-full">
<div
class="rounded-xl size-full flex-shrink min-w-0 flex"
style="background-color: {color_states.bg.value};"
>
<div bind:this={element} class="size-full overflow-auto px-3 py-2"></div>
</div>
<div class="flex flex-col gap-2 justify-between">
<div class="flex flex-col gap-2">
{#each text_edit_options as edit_row}
<div class="flex flex-row gap-1">
{#each edit_row as option}
<div class="flex flex-row">
<button
title={option.title}
onclick={option.onclick}
class="p-1 {option.color
? 'rounded-l-xl'
: 'rounded-xl'} cursor-pointer duration-200 transition-colors {get_selectable_color_classes(
option.is_selected(),
{
bg: true,
hover: true,
active: true,
text: true
},
-100,
50
)}"
>
{#if option.icon}
{@const Icon = option.icon}
<Icon class="h-7 w-7" />
{/if}
</button>
{#if option.color}
<button
onclick={option.color.color_picker}
title={option.title}
class="flex p-1 rounded-r-xl cursor-pointer duration-200 transition-colors justify-center items-center {get_selectable_color_classes(
option.is_selected(),
{
bg: true,
hover: true,
active: true,
text: true
},
-100,
50
)}"
>
<span
class="h-7 w-3 rounded-full"
style="background-color: {option.color.color_string()};"
></span>
</button>
{/if}
</div>
{/each}
</div>
{/each}
</div>
<Button click_function={show_text} className="w-full font-bold">Text anzeigen</Button>
</div>
</div>
<style>
:global(.tiptap) {
width: 100%;
height: 100%;
--tw-outline-style: none;
outline-style: none;
}
:global(.tiptap ul),
:global(.tiptap ol) {
padding-left: 1.5rem;
}
:global(.tiptap ul) {
list-style: disc;
}
:global(.tiptap ol) {
list-style: decimal;
}
:global(.tiptap p) {
margin-bottom: 0.5rem;
}
:global(.tiptap p.is-editor-empty:first-child::before) {
content: attr(data-placeholder);
pointer-events: none;
color: oklch(44.4% 0.011 73.639);
float: left;
height: 0;
opacity: 0.7;
}