cleanup doclist formatting helpers and view props
This commit is contained in:
parent
eb8820d888
commit
46d5bab647
8 changed files with 84 additions and 109 deletions
|
|
@ -310,6 +310,10 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
}
|
}
|
||||||
}, [documentToDelete, removePDFDocument, removeEPUBDocument, removeHTMLDocument]);
|
}, [documentToDelete, removePDFDocument, removeEPUBDocument, removeHTMLDocument]);
|
||||||
|
|
||||||
|
const handleDeleteDoc = useCallback((doc: DocumentListDocument) => {
|
||||||
|
setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type });
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleDropOnFolder = useCallback(
|
const handleDropOnFolder = useCallback(
|
||||||
(folderId: string, item: DocumentDragItem) => {
|
(folderId: string, item: DocumentDragItem) => {
|
||||||
setFolders((prev) =>
|
setFolders((prev) =>
|
||||||
|
|
@ -478,7 +482,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowHint(false)}
|
onClick={() => setShowHint(false)}
|
||||||
className="h-6 w-6 inline-flex items-center justify-center text-muted hover:text-accent hover:bg-base hover:scale-[1.02] rounded transition-all duration-200 ease-out"
|
className="h-6 w-6 inline-flex items-center justify-center text-muted hover:text-accent hover:bg-base hover:scale-[1.01] rounded transition-all duration-200 ease-out"
|
||||||
aria-label="Dismiss hint"
|
aria-label="Dismiss hint"
|
||||||
>
|
>
|
||||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -498,9 +502,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
collapsedFolders={collapsedFolders}
|
collapsedFolders={collapsedFolders}
|
||||||
onToggleCollapse={toggleFolderCollapse}
|
onToggleCollapse={toggleFolderCollapse}
|
||||||
onDeleteFolder={handleDeleteFolder}
|
onDeleteFolder={handleDeleteFolder}
|
||||||
onDeleteDoc={(doc) =>
|
onDeleteDoc={handleDeleteDoc}
|
||||||
setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type })
|
|
||||||
}
|
|
||||||
onDropOnFolder={handleDropOnFolder}
|
onDropOnFolder={handleDropOnFolder}
|
||||||
onMergeIntoFolder={handleMergeIntoFolder}
|
onMergeIntoFolder={handleMergeIntoFolder}
|
||||||
/>
|
/>
|
||||||
|
|
@ -518,9 +520,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
collapsedFolders={collapsedFolders}
|
collapsedFolders={collapsedFolders}
|
||||||
onToggleCollapse={toggleFolderCollapse}
|
onToggleCollapse={toggleFolderCollapse}
|
||||||
onDeleteFolder={handleDeleteFolder}
|
onDeleteFolder={handleDeleteFolder}
|
||||||
onDeleteDoc={(doc) =>
|
onDeleteDoc={handleDeleteDoc}
|
||||||
setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type })
|
|
||||||
}
|
|
||||||
onDropOnFolder={handleDropOnFolder}
|
onDropOnFolder={handleDropOnFolder}
|
||||||
onMergeIntoFolder={handleMergeIntoFolder}
|
onMergeIntoFolder={handleMergeIntoFolder}
|
||||||
/>
|
/>
|
||||||
|
|
@ -529,9 +529,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
<ColumnsView
|
<ColumnsView
|
||||||
folders={visibleFolders}
|
folders={visibleFolders}
|
||||||
unfolderedDocs={unfolderedDocs}
|
unfolderedDocs={unfolderedDocs}
|
||||||
onDeleteDoc={(doc) =>
|
onDeleteDoc={handleDeleteDoc}
|
||||||
setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type })
|
|
||||||
}
|
|
||||||
onDropOnFolder={handleDropOnFolder}
|
onDropOnFolder={handleDropOnFolder}
|
||||||
onMergeIntoFolder={handleMergeIntoFolder}
|
onMergeIntoFolder={handleMergeIntoFolder}
|
||||||
/>
|
/>
|
||||||
|
|
@ -540,9 +538,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
<GalleryView
|
<GalleryView
|
||||||
folders={visibleFolders}
|
folders={visibleFolders}
|
||||||
unfolderedDocs={unfolderedDocs}
|
unfolderedDocs={unfolderedDocs}
|
||||||
onDeleteDoc={(doc) =>
|
onDeleteDoc={handleDeleteDoc}
|
||||||
setDocumentToDelete({ id: doc.id, name: doc.name, type: doc.type })
|
|
||||||
}
|
|
||||||
onDropOnFolder={handleDropOnFolder}
|
onDropOnFolder={handleDropOnFolder}
|
||||||
onMergeIntoFolder={handleMergeIntoFolder}
|
onMergeIntoFolder={handleMergeIntoFolder}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
primeDocumentPreviewCache,
|
primeDocumentPreviewCache,
|
||||||
setInMemoryDocumentPreviewUrl,
|
setInMemoryDocumentPreviewUrl,
|
||||||
} from '@/lib/client/cache/previews';
|
} from '@/lib/client/cache/previews';
|
||||||
|
import { formatDocumentSize } from './formatSize';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
|
|
||||||
|
|
@ -332,13 +333,7 @@ export function DocumentPreview({ doc }: DocumentPreviewProps) {
|
||||||
<div className="absolute left-1 bottom-1 z-20 rounded bg-black/45 px-1.5 py-0.5 text-[9px] font-semibold tracking-wide text-white/90">
|
<div className="absolute left-1 bottom-1 z-20 rounded bg-black/45 px-1.5 py-0.5 text-[9px] font-semibold tracking-wide text-white/90">
|
||||||
{isGenerating
|
{isGenerating
|
||||||
? '…'
|
? '…'
|
||||||
: `${typeLabel} • ${
|
: `${typeLabel} • ${formatDocumentSize(doc.size)}`}
|
||||||
doc.size >= 1024 * 1024 * 1024
|
|
||||||
? `${(doc.size / 1024 / 1024 / 1024).toFixed(2)} GB`
|
|
||||||
: doc.size >= 1024 * 1024
|
|
||||||
? `${(doc.size / 1024 / 1024).toFixed(2)} MB`
|
|
||||||
: `${(doc.size / 1024).toFixed(1)} KB`
|
|
||||||
}`}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
9
src/components/doclist/formatSize.ts
Normal file
9
src/components/doclist/formatSize.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
export function formatDocumentSize(bytes: number): string {
|
||||||
|
if (bytes >= 1024 * 1024 * 1024) {
|
||||||
|
return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
||||||
|
}
|
||||||
|
if (bytes >= 1024 * 1024) {
|
||||||
|
return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
|
||||||
|
}
|
||||||
|
return `${(bytes / 1024).toFixed(1)} KB`;
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import type { DocumentListDocument, Folder } from '@/types/documents';
|
||||||
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
|
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
|
||||||
import { FolderIcon, ChevronRightSmall } from '../window/finderIcons';
|
import { FolderIcon, ChevronRightSmall } from '../window/finderIcons';
|
||||||
import { DocumentPreview } from '@/components/doclist/DocumentPreview';
|
import { DocumentPreview } from '@/components/doclist/DocumentPreview';
|
||||||
|
import { formatDocumentSize } from '@/components/doclist/formatSize';
|
||||||
import { useDocumentSelection } from '../dnd/DocumentSelectionContext';
|
import { useDocumentSelection } from '../dnd/DocumentSelectionContext';
|
||||||
import { DND_DOCUMENT, type DocumentDragItem } from '../dnd/dndTypes';
|
import { DND_DOCUMENT, type DocumentDragItem } from '../dnd/dndTypes';
|
||||||
|
|
||||||
|
|
@ -216,24 +217,19 @@ export function ColumnsView({
|
||||||
{selected.doc.name}
|
{selected.doc.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-[11px] text-muted">
|
<p className="text-[11px] text-muted">
|
||||||
{selected.doc.type.toUpperCase()} •{' '}
|
{selected.doc.type.toUpperCase()} • {formatDocumentSize(selected.doc.size)}
|
||||||
{selected.doc.size >= 1024 * 1024 * 1024
|
|
||||||
? `${(selected.doc.size / 1024 / 1024 / 1024).toFixed(2)} GB`
|
|
||||||
: selected.doc.size >= 1024 * 1024
|
|
||||||
? `${(selected.doc.size / 1024 / 1024).toFixed(2)} MB`
|
|
||||||
: `${(selected.doc.size / 1024).toFixed(1)} KB`}
|
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-3 flex gap-2">
|
<div className="mt-3 flex gap-2">
|
||||||
<Link
|
<Link
|
||||||
href={`/${selected.doc.type}/${encodeURIComponent(selected.doc.id)}`}
|
href={`/${selected.doc.type}/${encodeURIComponent(selected.doc.id)}`}
|
||||||
className="flex-1 inline-flex items-center justify-center h-8 rounded-md bg-accent text-background text-[12px] font-medium hover:bg-secondary-accent hover:scale-[1.02] transition-all duration-200 ease-out"
|
className="flex-1 inline-flex items-center justify-center h-8 rounded-md bg-accent text-background text-[12px] font-medium hover:bg-secondary-accent hover:scale-[1.01] transition-all duration-200 ease-out"
|
||||||
>
|
>
|
||||||
Open
|
Open
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onDeleteDoc(selected.doc)}
|
onClick={() => onDeleteDoc(selected.doc)}
|
||||||
className="h-8 px-3 rounded-md border border-offbase bg-base text-[12px] text-muted hover:text-accent hover:border-accent hover:bg-offbase hover:scale-[1.03] transition-all duration-200 ease-out"
|
className="h-8 px-3 rounded-md border border-offbase bg-base text-[12px] text-muted hover:text-accent hover:border-accent hover:bg-offbase hover:scale-[1.01] transition-all duration-200 ease-out"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,49 @@ interface DocumentTileProps {
|
||||||
onMergeIntoFolder: (source: DocumentListDocument[], target: DocumentListDocument) => void;
|
onMergeIntoFolder: (source: DocumentListDocument[], target: DocumentListDocument) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SIZE_LABEL: Record<IconSize, string> = { sm: 'S', md: 'M', lg: 'L', xl: 'XL' };
|
const NAME_SIZE_CLASSES: Record<IconSize, string> = {
|
||||||
void SIZE_LABEL;
|
sm: 'text-[10px]',
|
||||||
|
md: 'text-[11px]',
|
||||||
|
lg: 'text-[12px]',
|
||||||
|
xl: 'text-[13px]',
|
||||||
|
};
|
||||||
|
|
||||||
|
const BOTTOM_PADDING_CLASSES: Record<IconSize, string> = {
|
||||||
|
sm: 'px-[7px] py-[4px]',
|
||||||
|
md: 'px-[8px] py-[5px]',
|
||||||
|
lg: 'px-[9px] py-[5px]',
|
||||||
|
xl: 'px-[10px] py-[6px]',
|
||||||
|
};
|
||||||
|
|
||||||
|
const LINK_PADDING_CLASS = 'px-[2px] py-[2px]';
|
||||||
|
|
||||||
|
const GAP_CLASSES: Record<IconSize, string> = {
|
||||||
|
sm: 'gap-1',
|
||||||
|
md: 'gap-1.5',
|
||||||
|
lg: 'gap-2',
|
||||||
|
xl: 'gap-2',
|
||||||
|
};
|
||||||
|
|
||||||
|
const FILE_ICON_CLASSES: Record<IconSize, string> = {
|
||||||
|
sm: 'w-3 h-3',
|
||||||
|
md: 'w-3.5 h-3.5',
|
||||||
|
lg: 'w-3.5 h-3.5',
|
||||||
|
xl: 'w-4 h-4',
|
||||||
|
};
|
||||||
|
|
||||||
|
const TRASH_BTN_CLASSES: Record<IconSize, string> = {
|
||||||
|
sm: 'ml-0.5 h-[18px] w-[18px] rounded-sm',
|
||||||
|
md: 'ml-0.5 h-[21px] w-[21px] rounded-sm',
|
||||||
|
lg: 'ml-1 h-[23px] w-[23px] rounded',
|
||||||
|
xl: 'ml-1.5 h-[25px] w-[25px] rounded',
|
||||||
|
};
|
||||||
|
|
||||||
|
const TRASH_ICON_CLASSES: Record<IconSize, string> = {
|
||||||
|
sm: 'w-[10px] h-[10px]',
|
||||||
|
md: 'w-[11px] h-[11px]',
|
||||||
|
lg: 'w-[12px] h-[12px]',
|
||||||
|
xl: 'w-[13px] h-[13px]',
|
||||||
|
};
|
||||||
|
|
||||||
export function DocumentTile({
|
export function DocumentTile({
|
||||||
doc,
|
doc,
|
||||||
|
|
@ -108,55 +149,6 @@ export function DocumentTile({
|
||||||
return () => clearTimeout(t);
|
return () => clearTimeout(t);
|
||||||
}, [loading]);
|
}, [loading]);
|
||||||
|
|
||||||
const sizeClasses: Record<IconSize, string> = {
|
|
||||||
sm: 'text-[10px]',
|
|
||||||
md: 'text-[11px]',
|
|
||||||
lg: 'text-[12px]',
|
|
||||||
xl: 'text-[13px]',
|
|
||||||
};
|
|
||||||
|
|
||||||
const bottomPaddingClasses: Record<IconSize, string> = {
|
|
||||||
sm: 'px-[7px] py-[4px]',
|
|
||||||
md: 'px-[8px] py-[5px]',
|
|
||||||
lg: 'px-[9px] py-[5px]',
|
|
||||||
xl: 'px-[10px] py-[6px]',
|
|
||||||
};
|
|
||||||
|
|
||||||
const linkPaddingClasses: Record<IconSize, string> = {
|
|
||||||
sm: 'px-[2px] py-[2px]',
|
|
||||||
md: 'px-[2px] py-[2px]',
|
|
||||||
lg: 'px-[2px] py-[2px]',
|
|
||||||
xl: 'px-[2px] py-[2px]',
|
|
||||||
};
|
|
||||||
|
|
||||||
const gapClasses: Record<IconSize, string> = {
|
|
||||||
sm: 'gap-1',
|
|
||||||
md: 'gap-1.5',
|
|
||||||
lg: 'gap-2',
|
|
||||||
xl: 'gap-2',
|
|
||||||
};
|
|
||||||
|
|
||||||
const fileIconClasses: Record<IconSize, string> = {
|
|
||||||
sm: 'w-3 h-3',
|
|
||||||
md: 'w-3.5 h-3.5',
|
|
||||||
lg: 'w-3.5 h-3.5',
|
|
||||||
xl: 'w-4 h-4',
|
|
||||||
};
|
|
||||||
|
|
||||||
const trashBtnClasses: Record<IconSize, string> = {
|
|
||||||
sm: 'ml-0.5 h-[18px] w-[18px] rounded-sm',
|
|
||||||
md: 'ml-0.5 h-[21px] w-[21px] rounded-sm',
|
|
||||||
lg: 'ml-1 h-[23px] w-[23px] rounded',
|
|
||||||
xl: 'ml-1.5 h-[25px] w-[25px] rounded',
|
|
||||||
};
|
|
||||||
|
|
||||||
const trashIconClasses: Record<IconSize, string> = {
|
|
||||||
sm: 'w-[10px] h-[10px]',
|
|
||||||
md: 'w-[11px] h-[11px]',
|
|
||||||
lg: 'w-[12px] h-[12px]',
|
|
||||||
xl: 'w-[13px] h-[13px]',
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={setRefs}
|
ref={setRefs}
|
||||||
|
|
@ -181,26 +173,26 @@ export function DocumentTile({
|
||||||
>
|
>
|
||||||
<DocumentPreview doc={doc} />
|
<DocumentPreview doc={doc} />
|
||||||
</Link>
|
</Link>
|
||||||
<div className={`flex items-center w-full ${bottomPaddingClasses[iconSize]}`}>
|
<div className={`flex items-center w-full ${BOTTOM_PADDING_CLASSES[iconSize]}`}>
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
className={`flex items-center flex-1 min-w-0 rounded-md ${linkPaddingClasses[iconSize]} ${gapClasses[iconSize]}`}
|
className={`flex items-center flex-1 min-w-0 rounded-md ${LINK_PADDING_CLASS} ${GAP_CLASSES[iconSize]}`}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<span className="flex-shrink-0 flex items-center">
|
<span className="flex-shrink-0 flex items-center">
|
||||||
{doc.type === 'pdf' ? (
|
{doc.type === 'pdf' ? (
|
||||||
<PDFIcon className={`${fileIconClasses[iconSize]} text-red-500`} />
|
<PDFIcon className={`${FILE_ICON_CLASSES[iconSize]} text-red-500`} />
|
||||||
) : doc.type === 'epub' ? (
|
) : doc.type === 'epub' ? (
|
||||||
<EPUBIcon className={`${fileIconClasses[iconSize]} text-blue-500`} />
|
<EPUBIcon className={`${FILE_ICON_CLASSES[iconSize]} text-blue-500`} />
|
||||||
) : (
|
) : (
|
||||||
<FileIcon className={`${fileIconClasses[iconSize]} text-muted`} />
|
<FileIcon className={`${FILE_ICON_CLASSES[iconSize]} text-muted`} />
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={
|
className={
|
||||||
'leading-none font-medium truncate flex-1 min-w-0 ' +
|
'leading-none font-medium truncate flex-1 min-w-0 ' +
|
||||||
sizeClasses[iconSize] +
|
NAME_SIZE_CLASSES[iconSize] +
|
||||||
' ' +
|
' ' +
|
||||||
(isSelected ? 'text-accent' : 'text-foreground group-hover:text-accent')
|
(isSelected ? 'text-accent' : 'text-foreground group-hover:text-accent')
|
||||||
}
|
}
|
||||||
|
|
@ -211,10 +203,10 @@ export function DocumentTile({
|
||||||
{showDeleteButton && (
|
{showDeleteButton && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => onDelete(doc)}
|
onClick={() => onDelete(doc)}
|
||||||
className={`inline-flex items-center justify-center text-muted hover:text-accent hover:bg-base focus:outline-none transition-colors duration-200 ${trashBtnClasses[iconSize]}`}
|
className={`inline-flex items-center justify-center text-muted hover:text-accent hover:bg-base focus:outline-none transition-colors duration-200 ${TRASH_BTN_CLASSES[iconSize]}`}
|
||||||
aria-label={`Delete ${doc.name}`}
|
aria-label={`Delete ${doc.name}`}
|
||||||
>
|
>
|
||||||
<svg className={trashIconClasses[iconSize]} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className={TRASH_ICON_CLASSES[iconSize]} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import type { DocumentListDocument, Folder } from '@/types/documents';
|
||||||
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
|
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
|
||||||
import { FolderIcon } from '../window/finderIcons';
|
import { FolderIcon } from '../window/finderIcons';
|
||||||
import { DocumentPreview } from '@/components/doclist/DocumentPreview';
|
import { DocumentPreview } from '@/components/doclist/DocumentPreview';
|
||||||
|
import { formatDocumentSize } from '@/components/doclist/formatSize';
|
||||||
import { useDocumentSelection } from '../dnd/DocumentSelectionContext';
|
import { useDocumentSelection } from '../dnd/DocumentSelectionContext';
|
||||||
import { DND_DOCUMENT, type DocumentDragItem } from '../dnd/dndTypes';
|
import { DND_DOCUMENT, type DocumentDragItem } from '../dnd/dndTypes';
|
||||||
|
|
||||||
|
|
@ -180,25 +181,20 @@ export function GalleryView({
|
||||||
{activeDoc.name}
|
{activeDoc.name}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-[11px] text-muted">
|
<p className="text-[11px] text-muted">
|
||||||
{activeDoc.type.toUpperCase()} •{' '}
|
{activeDoc.type.toUpperCase()} • {formatDocumentSize(activeDoc.size)}
|
||||||
{activeDoc.size >= 1024 * 1024 * 1024
|
|
||||||
? `${(activeDoc.size / 1024 / 1024 / 1024).toFixed(2)} GB`
|
|
||||||
: activeDoc.size >= 1024 * 1024
|
|
||||||
? `${(activeDoc.size / 1024 / 1024).toFixed(2)} MB`
|
|
||||||
: `${(activeDoc.size / 1024).toFixed(1)} KB`}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Link
|
<Link
|
||||||
href={`/${activeDoc.type}/${encodeURIComponent(activeDoc.id)}`}
|
href={`/${activeDoc.type}/${encodeURIComponent(activeDoc.id)}`}
|
||||||
className="h-8 px-4 inline-flex items-center justify-center rounded-md bg-accent text-background text-[12px] font-medium hover:bg-secondary-accent hover:scale-[1.02] transition-all duration-200 ease-out"
|
className="h-8 px-4 inline-flex items-center justify-center rounded-md bg-accent text-background text-[12px] font-medium hover:bg-secondary-accent hover:scale-[1.01] transition-all duration-200 ease-out"
|
||||||
>
|
>
|
||||||
Open
|
Open
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onDeleteDoc(activeDoc)}
|
onClick={() => onDeleteDoc(activeDoc)}
|
||||||
className="h-8 px-3 rounded-md border border-offbase bg-base text-[12px] text-muted hover:text-accent hover:border-accent hover:bg-offbase hover:scale-[1.02] transition-all duration-200 ease-out"
|
className="h-8 px-3 rounded-md border border-offbase bg-base text-[12px] text-muted hover:text-accent hover:border-accent hover:bg-offbase hover:scale-[1.01] transition-all duration-200 ease-out"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import type {
|
||||||
SortDirection,
|
SortDirection,
|
||||||
} from '@/types/documents';
|
} from '@/types/documents';
|
||||||
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
|
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
|
||||||
|
import { formatDocumentSize } from '@/components/doclist/formatSize';
|
||||||
import { FolderIcon } from '../window/finderIcons';
|
import { FolderIcon } from '../window/finderIcons';
|
||||||
import { useDocumentSelection } from '../dnd/DocumentSelectionContext';
|
import { useDocumentSelection } from '../dnd/DocumentSelectionContext';
|
||||||
import { DND_DOCUMENT, type DocumentDragItem } from '../dnd/dndTypes';
|
import { DND_DOCUMENT, type DocumentDragItem } from '../dnd/dndTypes';
|
||||||
|
|
@ -34,14 +35,6 @@ function formatDate(ms: number): string {
|
||||||
return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
|
return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatSize(bytes: number): string {
|
|
||||||
const mb = bytes / 1024 / 1024;
|
|
||||||
if (mb >= 1024) {
|
|
||||||
return `${(mb / 1024).toFixed(2)} GB`;
|
|
||||||
}
|
|
||||||
return mb >= 1 ? `${mb.toFixed(2)} MB` : `${(bytes / 1024).toFixed(1)} KB`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function KindIcon({ doc }: { doc: DocumentListDocument }) {
|
function KindIcon({ doc }: { doc: DocumentListDocument }) {
|
||||||
if (doc.type === 'pdf') return <PDFIcon className="w-4 h-4 text-red-500" />;
|
if (doc.type === 'pdf') return <PDFIcon className="w-4 h-4 text-red-500" />;
|
||||||
if (doc.type === 'epub') return <EPUBIcon className="w-4 h-4 text-blue-500" />;
|
if (doc.type === 'epub') return <EPUBIcon className="w-4 h-4 text-blue-500" />;
|
||||||
|
|
@ -180,7 +173,7 @@ function DocRow({
|
||||||
</Link>
|
</Link>
|
||||||
<span className="px-2 text-[11px] text-muted uppercase tracking-wide">{doc.type}</span>
|
<span className="px-2 text-[11px] text-muted uppercase tracking-wide">{doc.type}</span>
|
||||||
<span className="px-2 text-[11px] text-muted text-right tabular-nums">
|
<span className="px-2 text-[11px] text-muted text-right tabular-nums">
|
||||||
{formatSize(doc.size)}
|
{formatDocumentSize(doc.size)}
|
||||||
</span>
|
</span>
|
||||||
<span className="px-2 text-[11px] text-muted tabular-nums">
|
<span className="px-2 text-[11px] text-muted tabular-nums">
|
||||||
{formatDate(doc.lastModified)}
|
{formatDate(doc.lastModified)}
|
||||||
|
|
@ -259,7 +252,7 @@ function FolderRowGroup({
|
||||||
</button>
|
</button>
|
||||||
<span className="px-2 text-[11px] text-muted">Folder</span>
|
<span className="px-2 text-[11px] text-muted">Folder</span>
|
||||||
<span className="px-2 text-[11px] text-muted text-right tabular-nums">
|
<span className="px-2 text-[11px] text-muted text-right tabular-nums">
|
||||||
{formatSize(totalSize)}
|
{formatDocumentSize(totalSize)}
|
||||||
</span>
|
</span>
|
||||||
<span className="px-2 text-[11px] text-muted tabular-nums">
|
<span className="px-2 text-[11px] text-muted tabular-nums">
|
||||||
{folder.documents.length} items
|
{folder.documents.length} items
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import { useDocuments } from '@/contexts/DocumentContext';
|
||||||
import { uploadDocxAsPdf } from '@/lib/client/api/documents';
|
import { uploadDocxAsPdf } from '@/lib/client/api/documents';
|
||||||
import { useFeatureFlag } from '@/contexts/RuntimeConfigContext';
|
import { useFeatureFlag } from '@/contexts/RuntimeConfigContext';
|
||||||
|
|
||||||
|
|
||||||
interface DocumentUploaderProps {
|
interface DocumentUploaderProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
variant?: 'default' | 'compact' | 'overlay';
|
variant?: 'default' | 'compact' | 'overlay';
|
||||||
|
|
@ -93,14 +92,13 @@ export function DocumentUploader({ className = '', variant = 'default', children
|
||||||
: `${
|
: `${
|
||||||
isDragActive
|
isDragActive
|
||||||
? 'border-2 border-dashed border-accent bg-base text-foreground'
|
? 'border-2 border-dashed border-accent bg-base text-foreground'
|
||||||
: 'border-2 border-dashed border-muted bg-transparent text-foreground hover:border-accent hover:bg-base hover:scale-[1.008]'
|
: 'border-2 border-dashed border-muted bg-transparent text-foreground hover:border-accent hover:bg-base hover:scale-[1.01]'
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const paddingClass = variant === 'compact' ? 'py-1 px-2 rounded-md' : 'py-5 px-3 rounded-lg';
|
const paddingClass = variant === 'compact' ? 'py-1 px-2 rounded-md' : 'py-5 px-3 rounded-lg';
|
||||||
|
|
||||||
if (variant === 'overlay') {
|
if (variant === 'overlay') {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
const rootProps = getRootProps();
|
||||||
const { onClick, onKeyDown, ...rootProps } = getRootProps();
|
|
||||||
return (
|
return (
|
||||||
<div {...rootProps} className={`relative w-full h-full ${className}`}>
|
<div {...rootProps} className={`relative w-full h-full ${className}`}>
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue