refactor: DRY `getAttachmentDisplayItems` function + fix UI

This commit is contained in:
Aleksander Grygier 2025-11-28 15:58:52 +01:00
parent 1cf5daa8c0
commit 68b653ef45
4 changed files with 60 additions and 77 deletions

View File

@ -145,7 +145,9 @@
<div class="flex flex-col gap-0.5"> <div class="flex flex-col gap-0.5">
<span <span
class="max-w-24 truncate text-sm font-medium text-foreground group-hover:pr-6 md:max-w-32" class="max-w-24 truncate text-sm font-medium text-foreground {readonly
? ''
: 'group-hover:pr-6'} md:max-w-32"
> >
{name} {name}
</span> </span>

View File

@ -2,10 +2,8 @@
import { ChatAttachmentThumbnailImage, ChatAttachmentThumbnailFile } from '$lib/components/app'; import { ChatAttachmentThumbnailImage, ChatAttachmentThumbnailFile } from '$lib/components/app';
import { Button } from '$lib/components/ui/button'; import { Button } from '$lib/components/ui/button';
import { ChevronLeft, ChevronRight } from '@lucide/svelte'; import { ChevronLeft, ChevronRight } from '@lucide/svelte';
import { getFileTypeCategory } from '$lib/utils/file-type';
import { FileTypeCategory } from '$lib/enums';
import { isImageFile } from '$lib/utils/attachment-type';
import { DialogChatAttachmentPreview, DialogChatAttachmentsViewAll } from '$lib/components/app'; import { DialogChatAttachmentPreview, DialogChatAttachmentsViewAll } from '$lib/components/app';
import { getAttachmentDisplayItems } from '$lib/utils/attachment-display';
import type { ChatAttachmentDisplayItem, ChatAttachmentPreviewItem } from '$lib/types/chat'; import type { ChatAttachmentDisplayItem, ChatAttachmentPreviewItem } from '$lib/types/chat';
import type { DatabaseMessageExtra } from '$lib/types/database'; import type { DatabaseMessageExtra } from '$lib/types/database';
@ -40,7 +38,7 @@
limitToSingleRow = false limitToSingleRow = false
}: Props = $props(); }: Props = $props();
let displayItems = $derived(getDisplayItems()); let displayItems = $derived(getAttachmentDisplayItems({ uploadedFiles, attachments }));
let canScrollLeft = $state(false); let canScrollLeft = $state(false);
let canScrollRight = $state(false); let canScrollRight = $state(false);
@ -51,40 +49,6 @@
let showViewAll = $derived(limitToSingleRow && displayItems.length > 0 && isScrollable); let showViewAll = $derived(limitToSingleRow && displayItems.length > 0 && isScrollable);
let viewAllDialogOpen = $state(false); let viewAllDialogOpen = $state(false);
function getDisplayItems(): ChatAttachmentDisplayItem[] {
const items: ChatAttachmentDisplayItem[] = [];
// Add uploaded files (ChatForm)
for (const file of uploadedFiles) {
items.push({
id: file.id,
name: file.name,
size: file.size,
preview: file.preview,
isImage: getFileTypeCategory(file.type) === FileTypeCategory.IMAGE,
uploadedFile: file,
textContent: file.textContent
});
}
// Add stored attachments (ChatMessage)
for (const [index, attachment] of attachments.entries()) {
const isImage = isImageFile(attachment);
items.push({
id: `attachment-${index}`,
name: attachment.name,
preview: isImage && 'base64Url' in attachment ? attachment.base64Url : undefined,
isImage,
attachment,
attachmentIndex: index,
textContent: 'content' in attachment ? attachment.content : undefined
});
}
return items.reverse();
}
function openPreview(item: ChatAttachmentDisplayItem, event?: MouseEvent) { function openPreview(item: ChatAttachmentDisplayItem, event?: MouseEvent) {
event?.stopPropagation(); event?.stopPropagation();
event?.preventDefault(); event?.preventDefault();
@ -218,7 +182,7 @@
</div> </div>
{/if} {/if}
{:else} {:else}
<div class="flex flex-wrap justify-end gap-3"> <div class="flex flex-wrap items-start justify-end gap-3">
{#each displayItems as item (item.id)} {#each displayItems as item (item.id)}
{#if item.isImage && item.preview} {#if item.isImage && item.preview}
<ChatAttachmentThumbnailImage <ChatAttachmentThumbnailImage

View File

@ -4,10 +4,8 @@
ChatAttachmentThumbnailFile, ChatAttachmentThumbnailFile,
DialogChatAttachmentPreview DialogChatAttachmentPreview
} from '$lib/components/app'; } from '$lib/components/app';
import { FileTypeCategory } from '$lib/enums'; import { getAttachmentDisplayItems } from '$lib/utils/attachment-display';
import { getFileTypeCategory } from '$lib/utils/file-type'; import type { ChatAttachmentPreviewItem } from '$lib/types/chat';
import { isImageFile } from '$lib/utils/attachment-type';
import type { ChatAttachmentDisplayItem, ChatAttachmentPreviewItem } from '$lib/types/chat';
import type { DatabaseMessageExtra } from '$lib/types/database'; import type { DatabaseMessageExtra } from '$lib/types/database';
interface Props { interface Props {
@ -33,42 +31,10 @@
let previewDialogOpen = $state(false); let previewDialogOpen = $state(false);
let previewItem = $state<ChatAttachmentPreviewItem | null>(null); let previewItem = $state<ChatAttachmentPreviewItem | null>(null);
let displayItems = $derived(getDisplayItems()); let displayItems = $derived(getAttachmentDisplayItems({ uploadedFiles, attachments }));
let imageItems = $derived(displayItems.filter((item) => item.isImage)); let imageItems = $derived(displayItems.filter((item) => item.isImage));
let fileItems = $derived(displayItems.filter((item) => !item.isImage)); let fileItems = $derived(displayItems.filter((item) => !item.isImage));
function getDisplayItems(): ChatAttachmentDisplayItem[] {
const items: ChatAttachmentDisplayItem[] = [];
for (const file of uploadedFiles) {
items.push({
id: file.id,
name: file.name,
size: file.size,
preview: file.preview,
isImage: getFileTypeCategory(file.type) === FileTypeCategory.IMAGE,
uploadedFile: file,
textContent: file.textContent
});
}
for (const [index, attachment] of attachments.entries()) {
const isImage = isImageFile(attachment);
items.push({
id: `attachment-${index}`,
name: attachment.name,
preview: isImage && 'base64Url' in attachment ? attachment.base64Url : undefined,
isImage,
attachment,
attachmentIndex: index,
textContent: 'content' in attachment ? attachment.content : undefined
});
}
return items.reverse();
}
function openPreview(item: (typeof displayItems)[0], event?: Event) { function openPreview(item: (typeof displayItems)[0], event?: Event) {
if (event) { if (event) {
event.preventDefault(); event.preventDefault();

View File

@ -0,0 +1,51 @@
import { FileTypeCategory } from '$lib/enums';
import type { ChatAttachmentDisplayItem } from '$lib/types/chat';
import type { DatabaseMessageExtra } from '$lib/types/database';
import { isImageFile } from '$lib/utils/attachment-type';
import { getFileTypeCategory } from '$lib/utils/file-type';
export interface AttachmentDisplayItemsOptions {
uploadedFiles?: ChatUploadedFile[];
attachments?: DatabaseMessageExtra[];
}
/**
* Creates a unified list of display items from uploaded files and stored attachments.
* Items are returned in reverse order (newest first).
*/
export function getAttachmentDisplayItems(
options: AttachmentDisplayItemsOptions
): ChatAttachmentDisplayItem[] {
const { uploadedFiles = [], attachments = [] } = options;
const items: ChatAttachmentDisplayItem[] = [];
// Add uploaded files (ChatForm)
for (const file of uploadedFiles) {
items.push({
id: file.id,
name: file.name,
size: file.size,
preview: file.preview,
isImage: getFileTypeCategory(file.type) === FileTypeCategory.IMAGE,
uploadedFile: file,
textContent: file.textContent
});
}
// Add stored attachments (ChatMessage)
for (const [index, attachment] of attachments.entries()) {
const isImage = isImageFile(attachment);
items.push({
id: `attachment-${index}`,
name: attachment.name,
preview: isImage && 'base64Url' in attachment ? attachment.base64Url : undefined,
isImage,
attachment,
attachmentIndex: index,
textContent: 'content' in attachment ? attachment.content : undefined
});
}
return items.reverse();
}