mirror of
https://github.com/open-webui/open-webui.git
synced 2026-07-19 04:10:26 -05:00
refac
This commit is contained in:
@@ -92,6 +92,35 @@
|
||||
let loading = false;
|
||||
let error: string | null = null;
|
||||
|
||||
// ── Sort state ──────────────────────────────────────────────────────
|
||||
type SortMode = 'name' | 'date';
|
||||
let sortBy: SortMode = 'name';
|
||||
let sortAsc = true;
|
||||
|
||||
const sortEntries = (items: FileEntry[]): FileEntry[] => {
|
||||
return [...items].sort((a, b) => {
|
||||
// Directories always first
|
||||
if (a.type !== b.type) return a.type === 'directory' ? -1 : 1;
|
||||
if (sortBy === 'date') {
|
||||
const aTime = a.modified ?? 0;
|
||||
const bTime = b.modified ?? 0;
|
||||
return sortAsc ? aTime - bTime : bTime - aTime;
|
||||
}
|
||||
const cmp = a.name.localeCompare(b.name);
|
||||
return sortAsc ? cmp : -cmp;
|
||||
});
|
||||
};
|
||||
|
||||
const toggleSort = (mode: SortMode) => {
|
||||
if (sortBy === mode) {
|
||||
sortAsc = !sortAsc;
|
||||
} else {
|
||||
sortBy = mode;
|
||||
sortAsc = mode === 'name'; // name defaults asc, date defaults asc (oldest first)
|
||||
}
|
||||
entries = sortEntries(entries);
|
||||
};
|
||||
|
||||
// ── Navigation history ──────────────────────────────────────────────
|
||||
type NavEntry = { path: string; file: string | null };
|
||||
let navHistory: NavEntry[] = [];
|
||||
@@ -341,10 +370,7 @@
|
||||
'Failed to load directory. Check your Terminal connection in Settings → Integrations.';
|
||||
entries = [];
|
||||
} else {
|
||||
entries = result.sort((a, b) => {
|
||||
if (a.type !== b.type) return a.type === 'directory' ? -1 : 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
entries = sortEntries(result);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -918,6 +944,8 @@
|
||||
{loading}
|
||||
{canGoBack}
|
||||
{canGoForward}
|
||||
{sortBy}
|
||||
{sortAsc}
|
||||
onGoBack={goBack}
|
||||
onGoForward={goForward}
|
||||
onNavigate={loadDir}
|
||||
@@ -934,6 +962,7 @@
|
||||
onUploadFiles={handleUploadFiles}
|
||||
onDownloadDir={() => downloadFile(currentPath)}
|
||||
onMove={handleMove}
|
||||
onSort={toggleSort}
|
||||
>
|
||||
{#if fileImageUrl !== null || (fileOfficeSlides !== null && fileOfficeSlides.length > 0)}
|
||||
<Tooltip content={$i18n.t('Reset view')}>
|
||||
@@ -1355,6 +1384,7 @@
|
||||
onRename={handleRename}
|
||||
onSelect={handleSelect}
|
||||
onLongPress={enterSelectionMode}
|
||||
showDate={sortBy === 'date'}
|
||||
/>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
@@ -30,6 +30,17 @@
|
||||
export let selectedPaths: Set<string> = new Set();
|
||||
export let onSelect: (entry: FileEntry, event: MouseEvent) => void = () => {};
|
||||
export let onLongPress: () => void = () => {};
|
||||
export let showDate: boolean = false;
|
||||
|
||||
const formatRelativeTime = (epoch: number): string => {
|
||||
const diff = Math.floor(Date.now() / 1000) - epoch;
|
||||
if (diff < 60) return 'just now';
|
||||
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
||||
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
|
||||
if (diff < 2592000) return `${Math.floor(diff / 86400)}d ago`;
|
||||
if (diff < 31536000) return `${Math.floor(diff / 2592000)}mo ago`;
|
||||
return `${Math.floor(diff / 31536000)}y ago`;
|
||||
};
|
||||
|
||||
let dragOverFolder = false;
|
||||
|
||||
@@ -271,7 +282,12 @@
|
||||
</span>
|
||||
{/if}
|
||||
{#if entry.type === 'file' && entry.size !== undefined && !renaming}
|
||||
{#if showDate && entry.modified}
|
||||
<span class="text-[10px] text-gray-400 shrink-0">{formatRelativeTime(entry.modified)}</span>
|
||||
{/if}
|
||||
<span class="text-xs text-gray-400 shrink-0">{formatFileSize(entry.size)}</span>
|
||||
{:else if entry.type === 'directory' && showDate && entry.modified && !renaming}
|
||||
<span class="text-[10px] text-gray-400 shrink-0">{formatRelativeTime(entry.modified)}</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import FilePlusAlt from '../../icons/FilePlusAlt.svelte';
|
||||
import Spinner from '../../common/Spinner.svelte';
|
||||
import Tooltip from '../../common/Tooltip.svelte';
|
||||
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
@@ -21,6 +22,11 @@
|
||||
export let onDownloadDir: () => void = () => {};
|
||||
export let onMove: (source: string, destFolder: string) => void = () => {};
|
||||
|
||||
// Sort controls
|
||||
export let sortBy: 'name' | 'date' = 'name';
|
||||
export let sortAsc: boolean = true;
|
||||
export let onSort: (mode: 'name' | 'date') => void = () => {};
|
||||
|
||||
// Back / forward navigation
|
||||
export let canGoBack = false;
|
||||
export let canGoForward = false;
|
||||
@@ -161,6 +167,49 @@
|
||||
</Tooltip>
|
||||
|
||||
{#if !selectedFile}
|
||||
<Dropdown align="end" sideOffset={4}>
|
||||
<Tooltip content={$i18n.t('Sort')}>
|
||||
<button
|
||||
class="shrink-0 p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400"
|
||||
aria-label={$i18n.t('Sort')}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-3.5">
|
||||
<path d="M2 3.75A.75.75 0 0 1 2.75 3h11.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 3.75ZM2 7.5a.75.75 0 0 1 .75-.75h7.508a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 7.5ZM14 7a.75.75 0 0 1 .75.75v6.69l1.72-1.72a.75.75 0 1 1 1.06 1.06l-3 3a.75.75 0 0 1-1.06 0l-3-3a.75.75 0 1 1 1.06-1.06l1.72 1.72V7.75A.75.75 0 0 1 14 7ZM2 11.25a.75.75 0 0 1 .75-.75h4.562a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1-.75-.75Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<div slot="content">
|
||||
<div
|
||||
class="min-w-[150px] rounded-2xl p-1 z-[9999999] bg-white dark:bg-gray-850 dark:text-white shadow-lg border border-gray-100 dark:border-gray-800"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="select-none flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition items-center gap-2 text-sm"
|
||||
on:click={() => onSort('name')}
|
||||
>
|
||||
<span class="flex-1 text-left">{$i18n.t('Name')}</span>
|
||||
{#if sortBy === 'name'}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-3 text-gray-500 dark:text-gray-400 transition-transform {sortAsc ? '' : 'rotate-180'}">
|
||||
<path fill-rule="evenodd" d="M11.78 9.78a.75.75 0 0 1-1.06 0L8 7.06 5.28 9.78a.75.75 0 0 1-1.06-1.06l3.25-3.25a.75.75 0 0 1 1.06 0l3.25 3.25a.75.75 0 0 1 0 1.06Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="select-none flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition items-center gap-2 text-sm"
|
||||
on:click={() => onSort('date')}
|
||||
>
|
||||
<span class="flex-1 text-left">{$i18n.t('Date Modified')}</span>
|
||||
{#if sortBy === 'date'}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-3 text-gray-500 dark:text-gray-400 transition-transform {sortAsc ? '' : 'rotate-180'}">
|
||||
<path fill-rule="evenodd" d="M11.78 9.78a.75.75 0 0 1-1.06 0L8 7.06 5.28 9.78a.75.75 0 0 1-1.06-1.06l3.25-3.25a.75.75 0 0 1 1.06 0l3.25 3.25a.75.75 0 0 1 0 1.06Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
<Tooltip content={$i18n.t('New Folder')}>
|
||||
<button
|
||||
class="shrink-0 p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400"
|
||||
|
||||
@@ -497,6 +497,7 @@
|
||||
"Danger Zone": "",
|
||||
"Dark": "",
|
||||
"Data Controls": "",
|
||||
"Date Modified": "",
|
||||
"Database": "",
|
||||
"Datalab Marker API": "",
|
||||
"Day": "",
|
||||
@@ -1900,6 +1901,8 @@
|
||||
"Sonar Reasoning Pro": "",
|
||||
"Sort": "",
|
||||
"Sort by": "",
|
||||
"Sort by date": "",
|
||||
"Sort by name": "",
|
||||
"Sougou Search API sID": "",
|
||||
"Sougou Search API SK": "",
|
||||
"Source": "",
|
||||
|
||||
Reference in New Issue
Block a user