{t('nav.files', 'Files')} · OxiCloud
{ e.preventDefault(); dragOver = true; }} ondragleave={() => (dragOver = false)} ondrop={onDrop} >
(sortDir = (sortDir * -1) as 1 | -1)} > {#snippet start()} {#if selectedCount > 0}
{t('files.selected_count', { count: selectedCount }, '{{count}} selected')}
{:else}
{#if uploadMenuOpen}
{/if}
{/if} {/snippet}
{#if error} {:else if showSkeleton && isEmpty} {:else if isEmpty} {:else}
{#if groupBy !== ''}
{@render fileListHeader()} {#each groups as group (group.key)}
{group.label}
{#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()}
0 && selectedCount === totalCount} indeterminate={selectedCount > 0 && selectedCount < totalCount} onchange={toggleSelectAll} />
{#each [{ f: 'name', l: t('files.col_name', 'Name') }, { f: 'owner', l: t('files.col_owner', 'Owner') }, { f: 'type', l: t('files.col_type', 'Type') }, { f: 'size', l: t('files.col_size', 'Size') }, { f: 'modified_at', l: t('files.col_modified', 'Modified') }] as col (col.f)} {#if col.f === 'owner'}
{col.l}
{:else} {/if} {/each}
{/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)}
{/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()}>
{/snippet} {#if moveDialog.component} {@const MoveDialog = moveDialog.component} { clearSelection(); void reload(); }} /> {/if} {#if shareDialog.component} {@const ShareDialog = shareDialog.component} (sharedIds = new SvelteSet(sharedIds).add(id))} /> {/if} {#if fileViewer.component} {@const FileViewer = fileViewer.component} {/if} {#if wopiEditor.component} {@const WopiEditor = wopiEditor.component} {/if} {#if ctxOpen && ctxTarget} {/if}