{
e.preventDefault();
dragOver = true;
}}
ondragleave={() => (dragOver = false)}
ondrop={onDrop}
>
{#if error}
{:else if showSkeleton && isEmpty}
{:else if isEmpty}
{:else}
{#if groupBy !== ''}
{@render fileListHeader()}
{#each groups as group (group.key)}
{#each group.folders as folder (folder.id)}
{@render folderRow(folder)}
{/each}
{#each group.files as file (file.id)}
{@render fileRow(file)}
{/each}
{/each}
{:else if filesStore.viewMode === 'list'}
{@render fileListHeader()}
{:else}
{/if}
{/if}
{#snippet fileListHeader()}
{/snippet}
{#snippet entryRow(e: Entry)}
{#if e.kind === 'folder'}
{@render folderRow(e.folder)}
{:else}
{@render fileRow(e.file)}
{/if}
{/snippet}
{#snippet folderRow(folder: FolderItem)}
onItemDragStart(e, 'folder', folder.id, folder.name)}
ondragover={(e) => {
if (e.dataTransfer?.types.includes(DRAG_TYPE)) {
e.preventDefault();
dropFolderId = folder.id;
}
}}
ondragleave={() => {
if (dropFolderId === folder.id) dropFolderId = null;
}}
ondrop={(e) => onFolderDrop(e, folder)}
ondblclick={() => openFolder(folder)}
onclick={(e) => {
if (!handleSelectionClick(e, folder.id)) openFolder(folder);
}}
oncontextmenu={(e) => openContext(e, 'folder', folder.id, folder.name)}
onkeydown={(e) => e.key === 'Enter' && openFolder(folder)}
>
{
e.stopPropagation();
toggleSelected(folder.id);
}}
/>
{folder.name}
{#if favoriteIds.has(folder.id)}
{/if}
{#if sharedIds.has(folder.id)}
{/if}
{relativeTimeAgo(folder.modified_at)}
{ownerLabel(folder.created_by, session.user?.id ?? null)}
{t('files.file_types.folder', 'Folder')}
—
{formatDate(folder.modified_at)}
{
e.stopPropagation();
void toggleFavorite('folder', folder.id);
}}>
{
e.stopPropagation();
openShare('folder', folder.id, folder.name);
}}>
{
e.stopPropagation();
openMove('folder', folder.id, folder.name);
}}>
{
e.stopPropagation();
renameItem('folder', folder.id, folder.name);
}}>
{
e.stopPropagation();
deleteItem('folder', folder.id, folder.name);
}}>
openContext(e, 'folder', folder.id, folder.name)}
>
{/snippet}
{#snippet fileRow(file: FileItem)}
{@const iconName = iconNameFromClass(file.icon_class)}
onItemDragStart(e, 'file', file.id, file.name)}
ondblclick={() => openFile(file)}
onclick={(e) => {
if (!handleSelectionClick(e, file.id)) openFile(file);
}}
oncontextmenu={(e) => openContext(e, 'file', file.id, file.name)}
onkeydown={(e) => e.key === 'Enter' && openFile(file)}
>
{
e.stopPropagation();
toggleSelected(file.id);
}}
/>
{#if canThumbnail(file)}
{
// Server-side thumbnail is missing (404) — try client-side
// generation for image / PDF / video and PUT the result
// back so the next viewer gets the server thumbnail.
// Ported from the legacy static/js/features/thumbnail.js.
const img = e.currentTarget as HTMLImageElement;
img.style.display = 'none';
if (!canThumbnailClientSide(file)) return;
if (file.mime_type === 'application/pdf') preloadPdf();
void queueThumbnailGenerate(file, (dataUrl) => {
img.src = dataUrl;
img.style.display = '';
});
}}
/>
{/if}
{file.name}
{#if favoriteIds.has(file.id)}
{/if}
{#if sharedIds.has(file.id)}
{/if}
{relativeTimeAgo(file.modified_at)}
{#if file.size != null}{formatBytes(file.size)} {/if}
{ownerLabel(file.created_by, session.user?.id ?? null)}
{typeLabel(file.category)}
{file.size != null ? formatBytes(file.size) : ''}
{formatDate(file.modified_at)}
{
e.stopPropagation();
void toggleFavorite('file', file.id);
}}>
{
e.stopPropagation();
openShare('file', file.id, file.name);
}}>
{
e.stopPropagation();
openMove('file', file.id, file.name);
}}>
e.stopPropagation()}>
{
e.stopPropagation();
renameItem('file', file.id, file.name);
}}>
{
e.stopPropagation();
deleteItem('file', file.id, file.name);
}}>
openContext(e, 'file', file.id, file.name)}
>
{/snippet}
{#if moveDialog.component}
{@const MoveDialog = moveDialog.component}