mirror of
https://codeberg.org/PLG-Development/PLG-MuDiCS
synced 2026-07-06 08:57:09 +00:00
refactor(frontend): split true components from modules
This commit is contained in:
@@ -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,
|
||||
Highlighter,
|
||||
Italic,
|
||||
PaintBucket,
|
||||
QrCode,
|
||||
Strikethrough
|
||||
} from 'lucide-svelte';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import { run_on_all_selected_displays } from '$lib/ts/stores/displays';
|
||||
import { show_html } from '$lib/ts/api_handler';
|
||||
import { get_selectable_color_classes } from '$lib/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()
|
||||
};
|
||||
}
|
||||
|
||||
async function show_text() {
|
||||
const html =
|
||||
editor_state.editor?.getHTML() +
|
||||
`<style>:root {--background-color: ${color_states.bg.value} !important;}
|
||||
</style>`;
|
||||
await 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 data-testid="text-popup" class="flex flex-row gap-2 size-full">
|
||||
<div
|
||||
class="rounded-xl size-full 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
|
||||
aria-label="Color"
|
||||
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