diff --git a/frontend/src/lib/components/ModelTable/EvidenceFilePreview.svelte b/frontend/src/lib/components/ModelTable/EvidenceFilePreview.svelte index b761444871..7bb83f382b 100644 --- a/frontend/src/lib/components/ModelTable/EvidenceFilePreview.svelte +++ b/frontend/src/lib/components/ModelTable/EvidenceFilePreview.svelte @@ -1,8 +1,9 @@ -{#snippet displayPreview()} +{#snippet displayPreview(att: Attachment)}
- {#if attachment.type.startsWith('image')} + {#if att.type.startsWith('image')} attachment - {:else if attachment.type === 'application/pdf'} + {:else if att.type === 'application/pdf'} {#if !display}
{/if} @@ -89,18 +169,20 @@
{/snippet} -{#if cell} - {#if attachment} - {#if attachment.type.startsWith('image') || attachment.type === 'application/pdf'} - {@render displayPreview(attachment)} - {:else if !attachment.fileExists} -

{m.couldNotFindAttachmentMessage()}

+
+ {#if cell} + {#if attachment} + {#if attachment.type.startsWith('image') || attachment.type === 'application/pdf'} + {@render displayPreview(attachment)} + {:else if !attachment.fileExists} +

{m.couldNotFindAttachmentMessage()}

+ {:else} +

{m.NoPreviewMessage()}

+ {/if} {:else} -

{m.NoPreviewMessage()}

+ + {m.loading()}... + {/if} - {:else} - - {m.loading()}... - {/if} -{/if} +
diff --git a/frontend/src/lib/stores/attachmentCache.ts b/frontend/src/lib/stores/attachmentCache.ts new file mode 100644 index 0000000000..5c2be5b5d2 --- /dev/null +++ b/frontend/src/lib/stores/attachmentCache.ts @@ -0,0 +1,82 @@ +import { writable, get } from 'svelte/store'; + +/** + * Global cache for attachment blob URLs + * Prevents duplicate downloads of the same evidence attachment + */ + +interface CachedAttachment { + type: string; + url: string; + fileExists: boolean; +} + +interface AttachmentCache { + [key: string]: CachedAttachment; +} + +function createAttachmentCache() { + const { subscribe, set, update } = writable({}); + + return { + subscribe, + /** + * Get a cached attachment by key + */ + get: (key: string): CachedAttachment | undefined => { + const cache = get({ subscribe }); + return cache[key]; + }, + /** + * Store an attachment in the cache + */ + set: (key: string, value: CachedAttachment) => { + update((cache) => { + const prev = cache[key]; + if (prev?.url && prev.url !== value.url) { + URL.revokeObjectURL(prev.url); + } + return { ...cache, [key]: value }; + }); + }, + /** + * Remove an attachment from the cache and revoke its blob URL + */ + remove: (key: string) => { + update((cache) => { + if (cache[key]) { + URL.revokeObjectURL(cache[key].url); + delete cache[key]; + } + return cache; + }); + }, + /** + * Clear all cached attachments and revoke all blob URLs + */ + clear: () => { + update((cache) => { + Object.values(cache).forEach((attachment) => { + URL.revokeObjectURL(attachment.url); + }); + return {}; + }); + }, + /** + * Check if an attachment is in the cache + */ + has: (key: string): boolean => { + const cache = get({ subscribe }); + return key in cache; + } + }; +} + +export const attachmentCache = createAttachmentCache(); + +/** + * Generate a cache key for an attachment + */ +export function generateAttachmentCacheKey(id: string, attachmentName: string): string { + return `${id}-${attachmentName}`; +}