adds ci/cd
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
<script lang="ts">
|
||||
import AttachmentPreview from '$lib/components/AttachmentPreview.svelte';
|
||||
import type { AttachmentLocation, SubDocument, ZipAttachment } from '$lib/zip-processing';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
type Props = {
|
||||
subDocument: SubDocument;
|
||||
blockIndex: number;
|
||||
thumbnailWidth: number;
|
||||
isBlockDragSource?: boolean;
|
||||
isBlockDropTarget?: boolean;
|
||||
onSubDocumentChange?: (next: SubDocument) => void;
|
||||
onRemove?: () => void;
|
||||
onAttachmentMove?: (source: AttachmentLocation, target: AttachmentLocation) => void;
|
||||
onBlockDragStart?: (blockIndex: number, event: DragEvent) => void;
|
||||
onBlockDragOver?: (blockIndex: number, event: DragEvent) => void;
|
||||
onBlockDrop?: (blockIndex: number, event: DragEvent) => void;
|
||||
onBlockDragEnd?: () => void;
|
||||
};
|
||||
|
||||
let {
|
||||
subDocument,
|
||||
blockIndex,
|
||||
thumbnailWidth = 200,
|
||||
isBlockDragSource = false,
|
||||
isBlockDropTarget = false,
|
||||
onSubDocumentChange = () => {},
|
||||
onRemove = () => {},
|
||||
onAttachmentMove = () => {},
|
||||
onBlockDragStart = () => {},
|
||||
onBlockDragOver = () => {},
|
||||
onBlockDrop = () => {},
|
||||
onBlockDragEnd = () => {}
|
||||
}: Props = $props();
|
||||
|
||||
let subDocumentName = $state('');
|
||||
let dragIndex = $state<number | null>(null);
|
||||
let dropIndex = $state<number | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
subDocumentName = subDocument.name;
|
||||
});
|
||||
|
||||
const emitChange = (overrides: Partial<SubDocument> = {}) => {
|
||||
onSubDocumentChange({
|
||||
...subDocument,
|
||||
name: subDocumentName,
|
||||
attachments: subDocument.attachments.map((attachment) => ({ ...attachment })),
|
||||
...overrides
|
||||
});
|
||||
};
|
||||
|
||||
const ATTACHMENT_MIME = 'application/x-juri-merger-attachment';
|
||||
const BLOCK_MIME = 'application/x-juri-merger-block';
|
||||
|
||||
const parseAttachmentPayload = (event: DragEvent): AttachmentLocation | null => {
|
||||
const raw = event.dataTransfer?.getData(ATTACHMENT_MIME);
|
||||
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as AttachmentLocation;
|
||||
|
||||
if (parsed.kind === 'loose' || parsed.kind === 'subDocument') {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCardDragStart = (index: number, event: DragEvent) => {
|
||||
dragIndex = index;
|
||||
dropIndex = index;
|
||||
|
||||
const source: AttachmentLocation = {
|
||||
kind: 'subDocument',
|
||||
id: subDocument.id,
|
||||
index
|
||||
};
|
||||
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
event.dataTransfer.setData(ATTACHMENT_MIME, JSON.stringify(source));
|
||||
// keep a text/plain fallback so the drag is not silently cancelled
|
||||
event.dataTransfer.setData('text/plain', String(index));
|
||||
}
|
||||
};
|
||||
|
||||
const handleCardDragOver = (index: number, event: DragEvent) => {
|
||||
if (event.dataTransfer?.types.includes(ATTACHMENT_MIME)) {
|
||||
event.preventDefault();
|
||||
dropIndex = index;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCardDrop = (index: number, event: DragEvent) => {
|
||||
const source = parseAttachmentPayload(event);
|
||||
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const target: AttachmentLocation = {
|
||||
kind: 'subDocument',
|
||||
id: subDocument.id,
|
||||
index
|
||||
};
|
||||
|
||||
onAttachmentMove(source, target);
|
||||
dragIndex = null;
|
||||
dropIndex = null;
|
||||
};
|
||||
|
||||
const handleCardDragEnd = () => {
|
||||
dragIndex = null;
|
||||
dropIndex = null;
|
||||
};
|
||||
|
||||
const handleNameInput = (event: Event) => {
|
||||
subDocumentName = (event.currentTarget as HTMLInputElement).value;
|
||||
emitChange();
|
||||
};
|
||||
|
||||
const handleHeaderDragStart = (event: DragEvent) => {
|
||||
// do not start a block drag when the user grabbed the remove button or name input
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
event.dataTransfer.setData(BLOCK_MIME, String(blockIndex));
|
||||
event.dataTransfer.setData('text/plain', String(blockIndex));
|
||||
}
|
||||
|
||||
onBlockDragStart(blockIndex, event);
|
||||
};
|
||||
|
||||
const handleHeaderDragOver = (event: DragEvent) => {
|
||||
if (event.dataTransfer?.types.includes(BLOCK_MIME)) {
|
||||
event.preventDefault();
|
||||
onBlockDragOver(blockIndex, event);
|
||||
}
|
||||
};
|
||||
|
||||
const handleHeaderDrop = (event: DragEvent) => {
|
||||
const raw = event.dataTransfer?.getData(BLOCK_MIME);
|
||||
|
||||
if (raw === undefined || raw === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
onBlockDrop(blockIndex, event);
|
||||
};
|
||||
|
||||
const attachments: ZipAttachment[] = $derived(subDocument.attachments);
|
||||
</script>
|
||||
|
||||
<section
|
||||
class={cn(
|
||||
'flex flex-col gap-3 rounded-xl border bg-primary-50/40 px-3 py-3 transition',
|
||||
isBlockDropTarget ? 'border-primary-400 ring-1 ring-primary-200' : 'border-primary-100',
|
||||
isBlockDragSource ? 'opacity-60' : ''
|
||||
)}
|
||||
>
|
||||
<header class="flex flex-wrap items-center gap-2 text-primary">
|
||||
<button
|
||||
class="w-fit cursor-grab rounded-md border border-primary-200 bg-white px-2 py-1 text-sm font-semibold leading-none text-primary-700 active:cursor-grabbing"
|
||||
draggable="true"
|
||||
type="button"
|
||||
aria-label="Teildokument verschieben"
|
||||
ondragstart={handleHeaderDragStart}
|
||||
ondragover={handleHeaderDragOver}
|
||||
ondrop={handleHeaderDrop}
|
||||
ondragend={onBlockDragEnd}
|
||||
>
|
||||
::
|
||||
</button>
|
||||
|
||||
<label class="block min-w-0 flex-1">
|
||||
<span class="sr-only">Teildokumentname</span>
|
||||
<input
|
||||
class="w-full max-w-md rounded-md border border-primary-200 bg-white px-3 py-1.5 text-[15px] font-semibold text-primary-900 outline-none transition focus:border-primary-400 focus:ring-2 focus:ring-primary-200"
|
||||
type="text"
|
||||
value={subDocumentName}
|
||||
oninput={handleNameInput}
|
||||
draggable="false"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<span class="text-[13px] text-primary-700">
|
||||
{attachments.length} Datei{attachments.length === 1 ? '' : 'en'}
|
||||
</span>
|
||||
|
||||
<button
|
||||
class="rounded-md border border-primary-200 bg-white px-2 py-1 text-[13px] font-medium text-primary-700 transition hover:border-red-300 hover:bg-red-50 hover:text-red-700"
|
||||
type="button"
|
||||
onclick={onRemove}
|
||||
draggable="false"
|
||||
ondragstart={(event) => event.preventDefault()}
|
||||
>
|
||||
Auflösen
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{#if attachments.length === 0}
|
||||
<ul
|
||||
class="flex items-center justify-center rounded-md border border-dashed border-primary-200 bg-white/60 px-4 py-6 text-[13px] text-primary-600"
|
||||
ondragover={(event) => {
|
||||
if (event.dataTransfer?.types.includes(ATTACHMENT_MIME)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
ondrop={(event) => {
|
||||
const source = parseAttachmentPayload(event);
|
||||
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const target: AttachmentLocation = {
|
||||
kind: 'subDocument',
|
||||
id: subDocument.id,
|
||||
index: 0
|
||||
};
|
||||
|
||||
onAttachmentMove(source, target);
|
||||
dragIndex = null;
|
||||
dropIndex = null;
|
||||
}}
|
||||
>
|
||||
<li class="w-full text-center">
|
||||
Dieses Teildokument ist leer. Ziehe Dateien hierher, um sie hinzuzufügen.
|
||||
</li>
|
||||
</ul>
|
||||
{:else}
|
||||
<ul class="flex gap-3 overflow-x-auto pb-1">
|
||||
{#each attachments as attachment, index (attachment.path)}
|
||||
<li
|
||||
class={cn(
|
||||
'flex flex-col shrink-0 items-stretch gap-2 rounded-xl border bg-accent p-2 text-primary',
|
||||
dropIndex === index
|
||||
? 'border-primary-400 ring-1 ring-primary-200'
|
||||
: 'border-primary-100',
|
||||
dragIndex === index ? 'opacity-60' : ''
|
||||
)}
|
||||
style={`width: ${thumbnailWidth}px;`}
|
||||
draggable="true"
|
||||
ondragstart={(event) => handleCardDragStart(index, event)}
|
||||
ondragover={(event) => handleCardDragOver(index, event)}
|
||||
ondrop={(event) => handleCardDrop(index, event)}
|
||||
ondragend={handleCardDragEnd}
|
||||
>
|
||||
<div class="flex min-w-0 flex-1 flex-row items-baseline gap-2 py-1">
|
||||
<button
|
||||
class="w-fit cursor-grab rounded-md border border-primary-200 bg-white px-2 py-1 text-sm font-semibold leading-none text-primary-700 active:cursor-grabbing"
|
||||
draggable="false"
|
||||
type="button"
|
||||
aria-label="Datei verschieben"
|
||||
>
|
||||
::
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="truncate text-[13px] font-medium leading-tight text-primary-900"
|
||||
title={attachment.name}
|
||||
>
|
||||
{attachment.name}
|
||||
</div>
|
||||
</div>
|
||||
<AttachmentPreview {attachment} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</section>
|
||||
Reference in New Issue
Block a user