mirror of
https://github.com/bewcloud/bewcloud.git
synced 2026-03-11 08:54:49 +00:00
Implement column sorting
This commit is contained in:
parent
08907295e9
commit
10e857965f
9 changed files with 262 additions and 23 deletions
|
|
@ -1,11 +1,22 @@
|
|||
import { SortColumn, SortOrder } from '/lib/utils/files.ts';
|
||||
|
||||
interface FilesBreadcrumbProps {
|
||||
path: string;
|
||||
isShowingNotes?: boolean;
|
||||
isShowingPhotos?: boolean;
|
||||
fileShareId?: string;
|
||||
sortBy?: SortColumn;
|
||||
sortOrder?: SortOrder;
|
||||
}
|
||||
|
||||
export default function FilesBreadcrumb({ path, isShowingNotes, isShowingPhotos, fileShareId }: FilesBreadcrumbProps) {
|
||||
export default function FilesBreadcrumb({
|
||||
path,
|
||||
isShowingNotes,
|
||||
isShowingPhotos,
|
||||
fileShareId,
|
||||
sortBy = 'name',
|
||||
sortOrder = 'asc'
|
||||
}: FilesBreadcrumbProps) {
|
||||
let routePath = fileShareId ? `file-share/${fileShareId}` : 'files';
|
||||
let rootPath = '/';
|
||||
let itemPluralLabel = 'files';
|
||||
|
|
@ -29,12 +40,13 @@ export default function FilesBreadcrumb({ path, isShowingNotes, isShowingPhotos,
|
|||
}
|
||||
|
||||
const pathParts = path.slice(1, -1).split('/');
|
||||
const sortParams = `&sortBy=${sortBy}&sortOrder=${sortOrder}`;
|
||||
|
||||
return (
|
||||
<h3 class='text-base font-semibold text-white whitespace-nowrap mr-2'>
|
||||
{!isShowingNotes && !isShowingPhotos ? <a href={`/${routePath}?path=/`}>All files</a> : null}
|
||||
{isShowingNotes ? <a href={`/notes?path=/Notes/`}>All notes</a> : null}
|
||||
{isShowingPhotos ? <a href={`/photos?path=/Photos/`}>All photos</a> : null}
|
||||
{!isShowingNotes && !isShowingPhotos ? <a href={`/${routePath}?path=/${sortParams}`}>All files</a> : null}
|
||||
{isShowingNotes ? <a href={`/notes?path=/Notes/${sortParams}`}>All notes</a> : null}
|
||||
{isShowingPhotos ? <a href={`/photos?path=/Photos/${sortParams}`}>All photos</a> : null}
|
||||
{pathParts.map((part, index) => {
|
||||
// Ignore the first directory in special ones
|
||||
if (index === 0 && (isShowingNotes || isShowingPhotos)) {
|
||||
|
|
@ -59,7 +71,7 @@ export default function FilesBreadcrumb({ path, isShowingNotes, isShowingPhotos,
|
|||
return (
|
||||
<>
|
||||
<span class='ml-2 text-xs'>/</span>
|
||||
<a href={`/${routePath}?path=/${encodeURIComponent(fullPathForPart.join('/'))}/`} class='ml-2'>
|
||||
<a href={`/${routePath}?path=/${encodeURIComponent(fullPathForPart.join('/'))}/${sortParams}`} class='ml-2'>
|
||||
{decodeURIComponent(part)}
|
||||
</a>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { join } from '@std/path';
|
||||
import type { ComponentChildren } from 'preact';
|
||||
|
||||
import { Directory, DirectoryFile } from '/lib/types.ts';
|
||||
import { SortColumn, SortOrder } from '/lib/utils/files.ts';
|
||||
import { humanFileSize, TRASH_PATH } from '/lib/utils/files.ts';
|
||||
|
||||
interface ListFilesProps {
|
||||
|
|
@ -21,6 +23,9 @@ interface ListFilesProps {
|
|||
isShowingNotes?: boolean;
|
||||
isShowingPhotos?: boolean;
|
||||
fileShareId?: string;
|
||||
sortBy?: SortColumn;
|
||||
sortOrder?: SortOrder;
|
||||
onClickSort?: (column: SortColumn) => void;
|
||||
}
|
||||
|
||||
export default function ListFiles(
|
||||
|
|
@ -42,6 +47,9 @@ export default function ListFiles(
|
|||
isShowingNotes,
|
||||
isShowingPhotos,
|
||||
fileShareId,
|
||||
sortBy = 'name',
|
||||
sortOrder = 'asc',
|
||||
onClickSort,
|
||||
}: ListFilesProps,
|
||||
) {
|
||||
const dateFormatOptions: Intl.DateTimeFormatOptions = {
|
||||
|
|
@ -55,6 +63,39 @@ export default function ListFiles(
|
|||
|
||||
const dateFormat = new Intl.DateTimeFormat('en-GB', dateFormatOptions);
|
||||
|
||||
function getSortIcon(column: SortColumn): string | null {
|
||||
if (sortBy !== column) return '↕'; // neutral sort icon
|
||||
return sortOrder === 'asc' ? '↑' : '↓';
|
||||
}
|
||||
|
||||
function renderSortableHeader(
|
||||
label: string,
|
||||
column: SortColumn,
|
||||
className?: string,
|
||||
): ComponentChildren {
|
||||
const isActive = sortBy === column;
|
||||
const iconClass = isActive ? 'text-blue-400' : 'text-slate-400';
|
||||
|
||||
if (!onClickSort) {
|
||||
return <th scope='col' class={`px-6 py-4 font-medium text-white ${className || ''}`}>{label}</th>;
|
||||
}
|
||||
|
||||
return (
|
||||
<th scope='col' class={`px-6 py-4 font-medium text-white ${className || ''}`}>
|
||||
<button
|
||||
class={`flex items-center justify-between w-full text-left hover:text-blue-300 ${isActive ? 'text-blue-400' : ''}`}
|
||||
onClick={() => onClickSort(column)}
|
||||
type='button'
|
||||
>
|
||||
<span>{label}</span>
|
||||
<span class={`ml-1 text-xs ${iconClass}`}>
|
||||
{getSortIcon(column)}
|
||||
</span>
|
||||
</button>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
let routePath = fileShareId ? `file-share/${fileShareId}` : 'files';
|
||||
let itemSingleLabel = 'file';
|
||||
let itemPluralLabel = 'files';
|
||||
|
|
@ -104,11 +145,11 @@ export default function ListFiles(
|
|||
/>
|
||||
</th>
|
||||
)}
|
||||
<th scope='col' class='px-6 py-4 font-medium text-white'>Name</th>
|
||||
<th scope='col' class='px-6 py-4 font-medium text-white w-64'>Last update</th>
|
||||
{renderSortableHeader('Name', 'name')}
|
||||
{renderSortableHeader('Last update', 'updated_at', 'w-64')}
|
||||
{isShowingNotes || isShowingPhotos
|
||||
? null
|
||||
: <th scope='col' class='px-6 py-4 font-medium text-white w-32'>Size</th>}
|
||||
: renderSortableHeader('Size', 'size_in_bytes', 'w-32')}
|
||||
{isShowingPhotos || fileShareId
|
||||
? null
|
||||
: <th scope='col' class='px-6 py-4 font-medium text-white w-24'></th>}
|
||||
|
|
@ -137,7 +178,7 @@ export default function ListFiles(
|
|||
)}
|
||||
<td class='flex gap-3 px-6 py-4'>
|
||||
<a
|
||||
href={`/${routePath}?path=${encodeURIComponent(fullPath)}`}
|
||||
href={`/${routePath}?path=${encodeURIComponent(fullPath)}&sortBy=${sortBy}&sortOrder=${sortOrder}`}
|
||||
class='flex items-center font-normal text-white'
|
||||
>
|
||||
<img
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useSignal } from '@preact/signals';
|
||||
|
||||
import { Directory, DirectoryFile } from '/lib/types.ts';
|
||||
import { SortColumn, SortOrder } from '/lib/utils/files.ts';
|
||||
import { ResponseBody as UploadResponseBody } from '/routes/api/files/upload.tsx';
|
||||
import { RequestBody as RenameRequestBody, ResponseBody as RenameResponseBody } from '/routes/api/files/rename.tsx';
|
||||
import { RequestBody as MoveRequestBody, ResponseBody as MoveResponseBody } from '/routes/api/files/move.tsx';
|
||||
|
|
@ -49,6 +50,8 @@ interface MainFilesProps {
|
|||
baseUrl: string;
|
||||
isFileSharingAllowed: boolean;
|
||||
fileShareId?: string;
|
||||
initialSortBy?: SortColumn;
|
||||
initialSortOrder?: SortOrder;
|
||||
}
|
||||
|
||||
export default function MainFiles(
|
||||
|
|
@ -59,6 +62,8 @@ export default function MainFiles(
|
|||
baseUrl,
|
||||
isFileSharingAllowed,
|
||||
fileShareId,
|
||||
initialSortBy = 'name',
|
||||
initialSortOrder = 'asc',
|
||||
}: MainFilesProps,
|
||||
) {
|
||||
const isAdding = useSignal<boolean>(false);
|
||||
|
|
@ -68,6 +73,8 @@ export default function MainFiles(
|
|||
const directories = useSignal<Directory[]>(initialDirectories);
|
||||
const files = useSignal<DirectoryFile[]>(initialFiles);
|
||||
const path = useSignal<string>(initialPath);
|
||||
const sortBy = useSignal<SortColumn>(initialSortBy);
|
||||
const sortOrder = useSignal<SortOrder>(initialSortOrder);
|
||||
const chosenDirectories = useSignal<Pick<Directory, 'parent_path' | 'directory_name'>[]>([]);
|
||||
const chosenFiles = useSignal<Pick<DirectoryFile, 'parent_path' | 'file_name'>[]>([]);
|
||||
const isAnyItemChosen = chosenDirectories.value.length > 0 || chosenFiles.value.length > 0;
|
||||
|
|
@ -84,6 +91,66 @@ export default function MainFiles(
|
|||
const createShareModal = useSignal<{ isOpen: boolean; filePath: string; password?: string } | null>(null);
|
||||
const manageShareModal = useSignal<{ isOpen: boolean; fileShareId: string } | null>(null);
|
||||
|
||||
// Helper functions for sorting persistence
|
||||
function getSortingKey(path: string): string {
|
||||
return `file-sort-${path}`;
|
||||
}
|
||||
|
||||
function loadSortingPreference(path: string): { sortBy: SortColumn; sortOrder: SortOrder } {
|
||||
if (typeof window === 'undefined') {
|
||||
return { sortBy: initialSortBy, sortOrder: initialSortOrder };
|
||||
}
|
||||
|
||||
try {
|
||||
const saved = localStorage.getItem(getSortingKey(path));
|
||||
if (saved) {
|
||||
const parsed = JSON.parse(saved);
|
||||
if (parsed.sortBy && parsed.sortOrder) {
|
||||
return { sortBy: parsed.sortBy, sortOrder: parsed.sortOrder };
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading sorting preference:', error);
|
||||
}
|
||||
return { sortBy: initialSortBy, sortOrder: initialSortOrder };
|
||||
}
|
||||
|
||||
// Initialize sorting from localStorage (prefer saved, fallback to URL params)
|
||||
const savedPreference = loadSortingPreference(initialPath);
|
||||
sortBy.value = savedPreference.sortBy;
|
||||
sortOrder.value = savedPreference.sortOrder;
|
||||
|
||||
function saveSortingPreference(path: string, sortBy: SortColumn, sortOrder: SortOrder) {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
localStorage.setItem(getSortingKey(path), JSON.stringify({ sortBy, sortOrder }));
|
||||
} catch (error) {
|
||||
console.error('Error saving sorting preference:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function onClickSort(column: SortColumn) {
|
||||
let newSortOrder: SortOrder = 'asc';
|
||||
|
||||
if (sortBy.value === column) {
|
||||
// Toggle sort order if clicking the same column
|
||||
newSortOrder = sortOrder.value === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
// Default to ascending for new columns
|
||||
newSortOrder = 'asc';
|
||||
}
|
||||
|
||||
// Save to localStorage
|
||||
saveSortingPreference(path.value, column, newSortOrder);
|
||||
|
||||
// Update URL and navigate to trigger re-render with new sorting
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('sortBy', column);
|
||||
url.searchParams.set('sortOrder', newSortOrder);
|
||||
window.location.href = url.toString();
|
||||
}
|
||||
|
||||
function onClickUploadFile(uploadDirectory = false) {
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
|
|
@ -759,7 +826,12 @@ export default function MainFiles(
|
|||
</section>
|
||||
|
||||
<section class='flex items-center justify-end'>
|
||||
<FilesBreadcrumb path={path.value} fileShareId={fileShareId} />
|
||||
<FilesBreadcrumb
|
||||
path={path.value}
|
||||
fileShareId={fileShareId}
|
||||
sortBy={sortBy.value}
|
||||
sortOrder={sortOrder.value}
|
||||
/>
|
||||
|
||||
{!fileShareId
|
||||
? (
|
||||
|
|
@ -840,6 +912,9 @@ export default function MainFiles(
|
|||
onClickCreateShare={isFileSharingAllowed ? onClickCreateShare : undefined}
|
||||
onClickOpenManageShare={isFileSharingAllowed ? onClickOpenManageShare : undefined}
|
||||
fileShareId={fileShareId}
|
||||
sortBy={sortBy.value}
|
||||
sortOrder={sortOrder.value}
|
||||
onClickSort={onClickSort}
|
||||
/>
|
||||
|
||||
<span
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Directory, DirectoryFile } from '/lib/types.ts';
|
||||
import { SortColumn, SortOrder } from '/lib/utils/files.ts';
|
||||
import MainFiles from '/components/files/MainFiles.tsx';
|
||||
|
||||
interface FilesWrapperProps {
|
||||
|
|
@ -8,6 +9,8 @@ interface FilesWrapperProps {
|
|||
baseUrl: string;
|
||||
isFileSharingAllowed: boolean;
|
||||
fileShareId?: string;
|
||||
initialSortBy?: SortColumn;
|
||||
initialSortOrder?: SortOrder;
|
||||
}
|
||||
|
||||
// This wrapper is necessary because islands need to be the first frontend component, but they don't support functions as props, so the more complex logic needs to live in the component itself
|
||||
|
|
@ -19,6 +22,8 @@ export default function FilesWrapper(
|
|||
baseUrl,
|
||||
isFileSharingAllowed,
|
||||
fileShareId,
|
||||
initialSortBy = 'name',
|
||||
initialSortOrder = 'asc',
|
||||
}: FilesWrapperProps,
|
||||
) {
|
||||
return (
|
||||
|
|
@ -29,6 +34,8 @@ export default function FilesWrapper(
|
|||
baseUrl={baseUrl}
|
||||
isFileSharingAllowed={isFileSharingAllowed}
|
||||
fileShareId={fileShareId}
|
||||
initialSortBy={initialSortBy}
|
||||
initialSortOrder={initialSortOrder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Cookie, getCookies, setCookie } from '@std/http';
|
|||
|
||||
import { AppConfig } from '/lib/config.ts';
|
||||
import { Directory, DirectoryFile, FileShare } from '/lib/types.ts';
|
||||
import { sortDirectoriesByName, sortEntriesByName, sortFilesByName, TRASH_PATH } from '/lib/utils/files.ts';
|
||||
import { sortDirectoriesByName, sortEntriesByName, sortFilesByName, sortDirectories, sortFiles, SortOptions, TRASH_PATH } from '/lib/utils/files.ts';
|
||||
import Database, { sql } from '/lib/interfaces/database.ts';
|
||||
import {
|
||||
COOKIE_NAME as AUTH_COOKIE_NAME,
|
||||
|
|
@ -21,7 +21,7 @@ const COOKIE_NAME = `${AUTH_COOKIE_NAME}-file-share`;
|
|||
const db = new Database();
|
||||
|
||||
export class DirectoryModel {
|
||||
static async list(userId: string, path: string): Promise<Directory[]> {
|
||||
static async list(userId: string, path: string, sortOptions?: SortOptions): Promise<Directory[]> {
|
||||
await ensureUserPathIsValidAndSecurelyAccessible(userId, path);
|
||||
|
||||
const rootPath = join(await AppConfig.getFilesRootPath(), userId, path);
|
||||
|
|
@ -53,9 +53,12 @@ export class DirectoryModel {
|
|||
directories.push(directory);
|
||||
}
|
||||
|
||||
directories.sort(sortDirectoriesByName);
|
||||
|
||||
return directories;
|
||||
if (sortOptions) {
|
||||
return sortDirectories(directories, sortOptions);
|
||||
} else {
|
||||
directories.sort(sortDirectoriesByName);
|
||||
return directories;
|
||||
}
|
||||
}
|
||||
|
||||
static async create(userId: string, path: string, name: string): Promise<boolean> {
|
||||
|
|
@ -167,7 +170,7 @@ export class DirectoryModel {
|
|||
}
|
||||
|
||||
export class FileModel {
|
||||
static async list(userId: string, path: string): Promise<DirectoryFile[]> {
|
||||
static async list(userId: string, path: string, sortOptions?: SortOptions): Promise<DirectoryFile[]> {
|
||||
await ensureUserPathIsValidAndSecurelyAccessible(userId, path);
|
||||
|
||||
const rootPath = join(await AppConfig.getFilesRootPath(), userId, path);
|
||||
|
|
@ -197,9 +200,12 @@ export class FileModel {
|
|||
files.push(file);
|
||||
}
|
||||
|
||||
files.sort(sortFilesByName);
|
||||
|
||||
return files;
|
||||
if (sortOptions) {
|
||||
return sortFiles(files, sortOptions);
|
||||
} else {
|
||||
files.sort(sortFilesByName);
|
||||
return files;
|
||||
}
|
||||
}
|
||||
|
||||
static async create(
|
||||
|
|
@ -598,7 +604,13 @@ export async function ensureUserPathIsValidAndSecurelyAccessible(userId: string,
|
|||
|
||||
const resolvedFullPath = `${resolve(fullPath)}/`;
|
||||
|
||||
if (!resolvedFullPath.startsWith(userRootPath)) {
|
||||
console.log({ userRootPath, fullPath, resolvedFullPath });
|
||||
|
||||
// Normalize path separators for consistent comparison on Windows
|
||||
const normalizedUserRootPath = userRootPath.replace(/\\/g, '/');
|
||||
const normalizedResolvedFullPath = resolvedFullPath.replace(/\\/g, '/');
|
||||
|
||||
if (!normalizedResolvedFullPath.startsWith(normalizedUserRootPath)) {
|
||||
throw new Error('Invalid file path');
|
||||
}
|
||||
}
|
||||
|
|
@ -626,7 +638,11 @@ export async function ensureFileSharePathIsValidAndSecurelyAccessible(
|
|||
|
||||
const resolvedFullPath = `${resolve(fullPath)}/`;
|
||||
|
||||
if (!resolvedFullPath.startsWith(fileShareRootPath)) {
|
||||
// Normalize path separators for consistent comparison on Windows
|
||||
const normalizedFileShareRootPath = fileShareRootPath.replace(/\\/g, '/');
|
||||
const normalizedResolvedFullPath = resolvedFullPath.replace(/\\/g, '/');
|
||||
|
||||
if (!normalizedResolvedFullPath.startsWith(normalizedFileShareRootPath)) {
|
||||
throw new Error('Invalid file path');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,14 @@ export function humanFileSize(bytes: number) {
|
|||
return `${bytes.toFixed(2)} ${units[unitIndex]}`;
|
||||
}
|
||||
|
||||
export type SortColumn = 'name' | 'updated_at' | 'size_in_bytes';
|
||||
export type SortOrder = 'asc' | 'desc';
|
||||
|
||||
export interface SortOptions {
|
||||
sortBy: SortColumn;
|
||||
sortOrder: SortOrder;
|
||||
}
|
||||
|
||||
export function sortEntriesByName(entryA: Deno.DirEntry, entryB: Deno.DirEntry) {
|
||||
const nameA = entryA.name.toLowerCase();
|
||||
const nameB = entryB.name.toLowerCase();
|
||||
|
|
@ -64,3 +72,47 @@ export function sortFilesByName(fileA: DirectoryFile, fileB: DirectoryFile) {
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function sortDirectories(directories: Directory[], options: SortOptions): Directory[] {
|
||||
const sorted = [...directories].sort((a, b) => {
|
||||
let result = 0;
|
||||
|
||||
switch (options.sortBy) {
|
||||
case 'name':
|
||||
result = a.directory_name.toLowerCase().localeCompare(b.directory_name.toLowerCase());
|
||||
break;
|
||||
case 'updated_at':
|
||||
result = new Date(a.updated_at).getTime() - new Date(b.updated_at).getTime();
|
||||
break;
|
||||
case 'size_in_bytes':
|
||||
result = a.size_in_bytes - b.size_in_bytes;
|
||||
break;
|
||||
}
|
||||
|
||||
return options.sortOrder === 'desc' ? -result : result;
|
||||
});
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
export function sortFiles(files: DirectoryFile[], options: SortOptions): DirectoryFile[] {
|
||||
const sorted = [...files].sort((a, b) => {
|
||||
let result = 0;
|
||||
|
||||
switch (options.sortBy) {
|
||||
case 'name':
|
||||
result = a.file_name.toLowerCase().localeCompare(b.file_name.toLowerCase());
|
||||
break;
|
||||
case 'updated_at':
|
||||
result = new Date(a.updated_at).getTime() - new Date(b.updated_at).getTime();
|
||||
break;
|
||||
case 'size_in_bytes':
|
||||
result = a.size_in_bytes - b.size_in_bytes;
|
||||
break;
|
||||
}
|
||||
|
||||
return options.sortOrder === 'desc' ? -result : result;
|
||||
});
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@ import { Handlers } from 'fresh/server.ts';
|
|||
|
||||
import { Directory, FreshContextState } from '/lib/types.ts';
|
||||
import { DirectoryModel } from '/lib/models/files.ts';
|
||||
import { SortColumn, SortOrder } from '/lib/utils/files.ts';
|
||||
|
||||
interface Data {}
|
||||
|
||||
export interface RequestBody {
|
||||
parentPath: string;
|
||||
directoryPathToExclude?: string;
|
||||
sortBy?: SortColumn;
|
||||
sortOrder?: SortOrder;
|
||||
}
|
||||
|
||||
export interface ResponseBody {
|
||||
|
|
@ -29,9 +32,14 @@ export const handler: Handlers<Data, FreshContextState> = {
|
|||
return new Response('Bad Request', { status: 400 });
|
||||
}
|
||||
|
||||
const sortOptions = (requestBody.sortBy && requestBody.sortOrder)
|
||||
? { sortBy: requestBody.sortBy, sortOrder: requestBody.sortOrder }
|
||||
: undefined;
|
||||
|
||||
const directories = await DirectoryModel.list(
|
||||
context.state.user.id,
|
||||
requestBody.parentPath,
|
||||
sortOptions,
|
||||
);
|
||||
|
||||
const filteredDirectories = requestBody.directoryPathToExclude
|
||||
|
|
|
|||
|
|
@ -2,11 +2,14 @@ import { Handlers } from 'fresh/server.ts';
|
|||
|
||||
import { DirectoryFile, FreshContextState } from '/lib/types.ts';
|
||||
import { FileModel } from '/lib/models/files.ts';
|
||||
import { SortColumn, SortOrder } from '/lib/utils/files.ts';
|
||||
|
||||
interface Data {}
|
||||
|
||||
export interface RequestBody {
|
||||
parentPath: string;
|
||||
sortBy?: SortColumn;
|
||||
sortOrder?: SortOrder;
|
||||
}
|
||||
|
||||
export interface ResponseBody {
|
||||
|
|
@ -28,9 +31,14 @@ export const handler: Handlers<Data, FreshContextState> = {
|
|||
return new Response('Bad Request', { status: 400 });
|
||||
}
|
||||
|
||||
const sortOptions = (requestBody.sortBy && requestBody.sortOrder)
|
||||
? { sortBy: requestBody.sortBy, sortOrder: requestBody.sortOrder }
|
||||
: undefined;
|
||||
|
||||
const files = await FileModel.list(
|
||||
context.state.user.id,
|
||||
requestBody.parentPath,
|
||||
sortOptions,
|
||||
);
|
||||
|
||||
const responseBody: ResponseBody = { success: true, files };
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Handlers, PageProps } from 'fresh/server.ts';
|
|||
import { Directory, DirectoryFile, FreshContextState } from '/lib/types.ts';
|
||||
import { DirectoryModel, FileModel } from '/lib/models/files.ts';
|
||||
import { AppConfig } from '/lib/config.ts';
|
||||
import { SortColumn, SortOrder } from '/lib/utils/files.ts';
|
||||
import FilesWrapper from '/islands/files/FilesWrapper.tsx';
|
||||
|
||||
interface Data {
|
||||
|
|
@ -11,6 +12,8 @@ interface Data {
|
|||
currentPath: string;
|
||||
baseUrl: string;
|
||||
isFileSharingAllowed: boolean;
|
||||
sortBy: SortColumn;
|
||||
sortOrder: SortOrder;
|
||||
}
|
||||
|
||||
export const handler: Handlers<Data, FreshContextState> = {
|
||||
|
|
@ -35,9 +38,22 @@ export const handler: Handlers<Data, FreshContextState> = {
|
|||
currentPath = `${currentPath}/`;
|
||||
}
|
||||
|
||||
const userDirectories = await DirectoryModel.list(context.state.user.id, currentPath);
|
||||
// Get sort parameters
|
||||
const sortBy = (searchParams.get('sortBy') as SortColumn) || 'name';
|
||||
const sortOrder = (searchParams.get('sortOrder') as SortOrder) || 'asc';
|
||||
|
||||
// Validate sort parameters
|
||||
const validSortColumns: SortColumn[] = ['name', 'updated_at', 'size_in_bytes'];
|
||||
const validSortOrders: SortOrder[] = ['asc', 'desc'];
|
||||
|
||||
const finalSortBy = validSortColumns.includes(sortBy) ? sortBy : 'name';
|
||||
const finalSortOrder = validSortOrders.includes(sortOrder) ? sortOrder : 'asc';
|
||||
|
||||
const sortOptions = { sortBy: finalSortBy, sortOrder: finalSortOrder };
|
||||
|
||||
const userFiles = await FileModel.list(context.state.user.id, currentPath);
|
||||
const userDirectories = await DirectoryModel.list(context.state.user.id, currentPath, sortOptions);
|
||||
|
||||
const userFiles = await FileModel.list(context.state.user.id, currentPath, sortOptions);
|
||||
|
||||
const isPublicFileSharingAllowed = await AppConfig.isPublicFileSharingAllowed();
|
||||
|
||||
|
|
@ -47,6 +63,8 @@ export const handler: Handlers<Data, FreshContextState> = {
|
|||
currentPath,
|
||||
baseUrl,
|
||||
isFileSharingAllowed: isPublicFileSharingAllowed,
|
||||
sortBy: finalSortBy,
|
||||
sortOrder: finalSortOrder,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -60,6 +78,8 @@ export default function FilesPage({ data }: PageProps<Data, FreshContextState>)
|
|||
initialPath={data.currentPath}
|
||||
baseUrl={data.baseUrl}
|
||||
isFileSharingAllowed={data.isFileSharingAllowed}
|
||||
initialSortBy={data.sortBy}
|
||||
initialSortOrder={data.sortOrder}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue