mirror of
https://codeberg.org/PLG-Development/PLG-MuDiCS
synced 2026-07-06 00:47:09 +00:00
chore: add display text
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user