refactor(doclist): remove columns view and update text labeling
Eliminate the deprecated "columns" view mode from the document list, toolbar, icons, and skeleton components. Update all related types and logic to only support "icons", "list", and "gallery" views. Standardize user-facing labels from "HTML" to "Text" for improved clarity across the sidebar, segment locator labels, and document counts. Clean up associated code and types to reflect these changes.
This commit is contained in:
parent
c12f795026
commit
98736bf6e3
8 changed files with 11 additions and 247 deletions
|
|
@ -33,7 +33,6 @@ import { FinderSidebar } from './window/FinderSidebar';
|
|||
import { FinderStatusBar } from './window/FinderStatusBar';
|
||||
import { IconsView } from './views/IconsView';
|
||||
import { ListView } from './views/ListView';
|
||||
import { ColumnsView } from './views/ColumnsView';
|
||||
import { GalleryView } from './views/GalleryView';
|
||||
|
||||
let cachedDocumentListState: DocumentListState | null = null;
|
||||
|
|
@ -71,7 +70,8 @@ const DEFAULT_STATE: Required<
|
|||
function normalizeViewMode(stored: DocumentListState['viewMode']): ViewMode {
|
||||
if (stored === 'grid' || stored === undefined) return 'icons';
|
||||
if (stored === 'list') return 'list';
|
||||
return stored;
|
||||
if (stored === 'gallery') return 'gallery';
|
||||
return 'icons';
|
||||
}
|
||||
|
||||
function generateDefaultFolderName(
|
||||
|
|
@ -463,7 +463,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
|||
const parts: string[] = [];
|
||||
if (counts.pdf) parts.push(`${counts.pdf} PDF${counts.pdf === 1 ? '' : 's'}`);
|
||||
if (counts.epub) parts.push(`${counts.epub} EPUB${counts.epub === 1 ? '' : 's'}`);
|
||||
if (counts.html) parts.push(`${counts.html} HTML${counts.html === 1 ? '' : 's'}`);
|
||||
if (counts.html) parts.push(`${counts.html} Text${counts.html === 1 ? ' Doc' : ' Docs'}`);
|
||||
return parts.join(' • ');
|
||||
}, [counts]);
|
||||
|
||||
|
|
@ -474,8 +474,7 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
|||
|
||||
const isLoading = isPDFLoading || isEPUBLoading || isHTMLLoading;
|
||||
|
||||
const fallbackViewMode: ViewMode =
|
||||
viewMode === 'columns' && isNarrow ? 'list' : viewMode;
|
||||
const fallbackViewMode: ViewMode = viewMode;
|
||||
const effectiveSidebarOpen = isNarrow ? mobileSidebarOpen : sidebarOpen;
|
||||
|
||||
return (
|
||||
|
|
@ -500,7 +499,6 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
|||
: setSidebarOpen((p) => !p)
|
||||
}
|
||||
isSidebarOpen={effectiveSidebarOpen}
|
||||
isNarrow={isNarrow}
|
||||
leftSlot={brand}
|
||||
/>
|
||||
}
|
||||
|
|
@ -588,13 +586,6 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
|||
onMergeIntoFolder={handleMergeIntoFolder}
|
||||
/>
|
||||
)}
|
||||
{fallbackViewMode === 'columns' && (
|
||||
<ColumnsView
|
||||
documents={sortedVisible}
|
||||
onDeleteDoc={handleDeleteDoc}
|
||||
onMergeIntoFolder={handleMergeIntoFolder}
|
||||
/>
|
||||
)}
|
||||
{fallbackViewMode === 'gallery' && (
|
||||
<GalleryView
|
||||
documents={sortedVisible}
|
||||
|
|
|
|||
|
|
@ -75,36 +75,6 @@ function ListSkeleton() {
|
|||
);
|
||||
}
|
||||
|
||||
function ColumnsSkeleton() {
|
||||
return (
|
||||
<div className="h-full min-h-0 flex overflow-x-auto overflow-y-hidden">
|
||||
<div className="w-[260px] shrink-0 h-full bg-base border-r border-offbase overflow-y-auto">
|
||||
<div className="sticky top-0 px-3 py-1.5 border-b border-offbase bg-base">
|
||||
<div className="h-2.5 w-20 rounded bg-offbase" />
|
||||
</div>
|
||||
<div className="p-1.5 flex flex-col gap-1">
|
||||
{Array.from({ length: 11 }).map((_, rowIndex) => (
|
||||
<div key={rowIndex} className="h-7 rounded-md bg-offbase" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 min-w-[280px] h-full bg-background overflow-y-auto p-4">
|
||||
<div className="max-w-[360px] mx-auto">
|
||||
<div className="rounded-lg overflow-hidden border border-offbase bg-base">
|
||||
<div className="aspect-[3/4] w-full bg-offbase" />
|
||||
</div>
|
||||
<div className="mt-3 h-3.5 w-4/5 rounded bg-offbase" />
|
||||
<div className="mt-2 h-2.5 w-1/3 rounded bg-offbase" />
|
||||
<div className="mt-3 flex gap-2">
|
||||
<div className="flex-1 h-8 rounded-md bg-offbase" />
|
||||
<div className="w-20 h-8 rounded-md bg-offbase" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function GallerySkeleton() {
|
||||
return (
|
||||
<div className="h-full min-h-0 flex flex-col">
|
||||
|
|
@ -142,8 +112,6 @@ export function DocumentListSkeleton({
|
|||
let body;
|
||||
if (viewMode === 'list') {
|
||||
body = <ListSkeleton />;
|
||||
} else if (viewMode === 'columns') {
|
||||
body = <ColumnsSkeleton />;
|
||||
} else if (viewMode === 'gallery') {
|
||||
body = <GallerySkeleton />;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,179 +0,0 @@
|
|||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import type { DocumentListDocument } from '@/types/documents';
|
||||
import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons';
|
||||
import { DocumentPreview } from '@/components/doclist/DocumentPreview';
|
||||
import { formatDocumentSize } from '@/components/doclist/formatSize';
|
||||
import { useDocumentSelection } from '../dnd/DocumentSelectionContext';
|
||||
import { DND_DOCUMENT, type DocumentDragItem } from '../dnd/dndTypes';
|
||||
|
||||
interface ColumnsViewProps {
|
||||
documents: DocumentListDocument[];
|
||||
onDeleteDoc: (doc: DocumentListDocument) => void;
|
||||
onMergeIntoFolder: (sources: DocumentListDocument[], target: DocumentListDocument) => void;
|
||||
}
|
||||
|
||||
function KindIcon({ doc }: { doc: DocumentListDocument }) {
|
||||
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" />;
|
||||
return <FileIcon className="w-4 h-4 text-muted" />;
|
||||
}
|
||||
|
||||
function ColumnDocRow({
|
||||
doc,
|
||||
active,
|
||||
onClick,
|
||||
onMergeIntoFolder,
|
||||
}: {
|
||||
doc: DocumentListDocument;
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
onMergeIntoFolder: (sources: DocumentListDocument[], target: DocumentListDocument) => void;
|
||||
}) {
|
||||
const selection = useDocumentSelection();
|
||||
const isSelected = selection.isSelected(doc);
|
||||
const isInFolder = Boolean(doc.folderId);
|
||||
|
||||
const [{ isDragging }, dragRef] = useDrag<DocumentDragItem, void, { isDragging: boolean }>(() => ({
|
||||
type: DND_DOCUMENT,
|
||||
item: () => {
|
||||
const sel = selection.getSelectedDocs();
|
||||
const dragging = isSelected && sel.length > 1 ? sel : [doc];
|
||||
if (!isSelected) selection.replace([doc]);
|
||||
return { ids: dragging.map((d) => d.id), docs: dragging, fromFolderId: doc.folderId };
|
||||
},
|
||||
collect: (m) => ({ isDragging: m.isDragging() }),
|
||||
}), [doc, isSelected]);
|
||||
|
||||
const [{ isOver, canDrop }, dropRef] = useDrop<DocumentDragItem, void, { isOver: boolean; canDrop: boolean }>(() => ({
|
||||
accept: DND_DOCUMENT,
|
||||
canDrop: (item) => !isInFolder && !item.ids.includes(doc.id),
|
||||
drop: (item) => onMergeIntoFolder(item.docs, doc),
|
||||
collect: (m) => ({ isOver: m.isOver({ shallow: true }), canDrop: m.canDrop() }),
|
||||
}), [doc, isInFolder, onMergeIntoFolder]);
|
||||
|
||||
const setRefs = (node: HTMLDivElement | null) => {
|
||||
dragRef(node);
|
||||
dropRef(node);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setRefs}
|
||||
data-doc-tile
|
||||
onClick={onClick}
|
||||
className={
|
||||
'group flex items-center gap-2 px-2 py-1 rounded-md text-[12px] border transform transition-all duration-200 ease-out cursor-pointer text-left hover:scale-[1.01] ' +
|
||||
(active || isSelected
|
||||
? 'border-accent bg-offbase text-accent'
|
||||
: 'border-transparent text-foreground hover:border-accent hover:bg-offbase hover:text-accent') +
|
||||
(isOver && canDrop ? ' ring-1 ring-accent' : '') +
|
||||
(isDragging ? ' opacity-50' : '')
|
||||
}
|
||||
>
|
||||
<span className={'w-4 h-4 shrink-0 flex items-center justify-center transition-colors duration-200 ' + ((active || isSelected) ? 'text-accent' : 'text-muted group-hover:text-accent')}>
|
||||
<KindIcon doc={doc} />
|
||||
</span>
|
||||
<span className="truncate flex-1">{doc.name}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Column({ children, title }: { children: React.ReactNode; title?: string }) {
|
||||
return (
|
||||
<div className="w-[260px] shrink-0 h-full bg-base border-r border-offbase overflow-y-auto">
|
||||
{title && (
|
||||
<div className="sticky top-0 px-3 py-1.5 text-[10px] uppercase tracking-wider text-muted bg-base border-b border-offbase">
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<div className="p-1.5 flex flex-col gap-0.5">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ColumnsView({
|
||||
documents,
|
||||
onDeleteDoc,
|
||||
onMergeIntoFolder,
|
||||
}: ColumnsViewProps) {
|
||||
const { setVisibleOrder } = useDocumentSelection();
|
||||
const [selectedDoc, setSelectedDoc] = useState<DocumentListDocument | null>(null);
|
||||
const openHref = selectedDoc
|
||||
? `/${selectedDoc.type}/${encodeURIComponent(selectedDoc.id)}`
|
||||
: null;
|
||||
|
||||
useEffect(() => {
|
||||
setVisibleOrder(documents);
|
||||
}, [documents, setVisibleOrder]);
|
||||
|
||||
useEffect(() => {
|
||||
if (documents.length === 0) {
|
||||
if (selectedDoc !== null) setSelectedDoc(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedDoc) {
|
||||
setSelectedDoc(documents[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedStillExists = documents.some(
|
||||
(d) => d.id === selectedDoc.id && d.type === selectedDoc.type,
|
||||
);
|
||||
if (!selectedStillExists) {
|
||||
setSelectedDoc(documents[0]);
|
||||
}
|
||||
}, [documents, selectedDoc]);
|
||||
|
||||
return (
|
||||
<div className="flex-1 min-h-0 flex overflow-x-auto overflow-y-hidden">
|
||||
<Column title="Documents">
|
||||
{documents.map((doc) => (
|
||||
<ColumnDocRow
|
||||
key={`${doc.type}-${doc.id}`}
|
||||
doc={doc}
|
||||
active={selectedDoc?.id === doc.id && selectedDoc?.type === doc.type}
|
||||
onClick={() => setSelectedDoc(doc)}
|
||||
onMergeIntoFolder={onMergeIntoFolder}
|
||||
/>
|
||||
))}
|
||||
</Column>
|
||||
|
||||
{selectedDoc && (
|
||||
<div className="flex-1 min-w-[280px] h-full bg-background overflow-y-auto p-4">
|
||||
<div className="max-w-[360px] mx-auto">
|
||||
<div className="rounded-lg overflow-hidden border border-offbase">
|
||||
<DocumentPreview doc={selectedDoc} />
|
||||
</div>
|
||||
<h3 className="mt-3 text-[13px] font-semibold text-foreground truncate">
|
||||
{selectedDoc.name}
|
||||
</h3>
|
||||
<p className="text-[11px] text-muted">
|
||||
{selectedDoc.type.toUpperCase()} • {formatDocumentSize(selectedDoc.size)}
|
||||
</p>
|
||||
<div className="mt-3 flex gap-2">
|
||||
<Link
|
||||
href={openHref || '/app'}
|
||||
prefetch={false}
|
||||
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
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onDeleteDoc(selectedDoc)}
|
||||
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
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -246,7 +246,7 @@ export function FinderSidebar({
|
|||
active={filter === 'html'}
|
||||
onClick={() => onFilterChange('html')}
|
||||
icon={<FileIcon className="w-3.5 h-3.5" />}
|
||||
label="HTML / Text"
|
||||
label="Text"
|
||||
count={counts.html}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import type { IconSize, SortBy, SortDirection, ViewMode } from '@/types/document
|
|||
import {
|
||||
IconsViewIcon,
|
||||
ListViewIcon,
|
||||
ColumnsViewIcon,
|
||||
GalleryViewIcon,
|
||||
SearchIcon,
|
||||
HamburgerIcon,
|
||||
|
|
@ -26,8 +25,6 @@ interface FinderToolbarProps {
|
|||
onQueryChange: (q: string) => void;
|
||||
onToggleSidebar: () => void;
|
||||
isSidebarOpen: boolean;
|
||||
/** True when the columns view should be disabled (mobile viewport). */
|
||||
isNarrow: boolean;
|
||||
/** App-level content rendered at the far left (brand/logo). */
|
||||
leftSlot?: ReactNode;
|
||||
/** App-level content rendered at the far right (settings, user menu). */
|
||||
|
|
@ -37,7 +34,6 @@ interface FinderToolbarProps {
|
|||
const VIEW_BUTTONS: Array<{ value: ViewMode; label: string; Icon: typeof IconsViewIcon }> = [
|
||||
{ value: 'icons', label: 'Icons', Icon: IconsViewIcon },
|
||||
{ value: 'list', label: 'List', Icon: ListViewIcon },
|
||||
{ value: 'columns', label: 'Columns', Icon: ColumnsViewIcon },
|
||||
{ value: 'gallery', label: 'Gallery', Icon: GalleryViewIcon },
|
||||
];
|
||||
|
||||
|
|
@ -84,7 +80,6 @@ export function FinderToolbar({
|
|||
onQueryChange,
|
||||
onToggleSidebar,
|
||||
isSidebarOpen,
|
||||
isNarrow,
|
||||
leftSlot,
|
||||
rightSlot,
|
||||
}: FinderToolbarProps) {
|
||||
|
|
@ -113,7 +108,6 @@ export function FinderToolbar({
|
|||
|
||||
<div className={PILL}>
|
||||
{VIEW_BUTTONS.map(({ value, label, Icon }) => {
|
||||
const disabled = value === 'columns' && isNarrow;
|
||||
const active = viewMode === value;
|
||||
const isIconsToggle = value === 'icons';
|
||||
return (
|
||||
|
|
@ -123,16 +117,14 @@ export function FinderToolbar({
|
|||
>
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => onViewModeChange(value)}
|
||||
aria-pressed={active}
|
||||
aria-label={`${label} view`}
|
||||
title={disabled ? `${label} (desktop only)` : `${label} view`}
|
||||
title={`${label} view`}
|
||||
className={
|
||||
PILL_SEGMENT +
|
||||
' h-6 w-7 ' +
|
||||
(active ? PILL_SEGMENT_ACTIVE : PILL_SEGMENT_INACTIVE) +
|
||||
(disabled ? ' opacity-40 cursor-not-allowed hover:bg-transparent hover:text-muted' : '')
|
||||
(active ? PILL_SEGMENT_ACTIVE : PILL_SEGMENT_INACTIVE)
|
||||
}
|
||||
>
|
||||
<Icon className="w-4 h-4" />
|
||||
|
|
|
|||
|
|
@ -57,14 +57,6 @@ export const ListViewIcon = (props: IconProps) => (
|
|||
</svg>
|
||||
);
|
||||
|
||||
export const ColumnsViewIcon = (props: IconProps) => (
|
||||
<svg {...baseSvg(props)}>
|
||||
<rect x="3" y="4" width="5" height="16" rx="1" />
|
||||
<rect x="10" y="4" width="5" height="16" rx="1" />
|
||||
<rect x="17" y="4" width="4" height="16" rx="1" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const GalleryViewIcon = (props: IconProps) => (
|
||||
<svg {...baseSvg(props)}>
|
||||
<rect x="3" y="4" width="18" height="11" rx="1.5" />
|
||||
|
|
|
|||
|
|
@ -118,9 +118,9 @@ function formatLocatorGroupLabel(locator: TTSSegmentLocator | null): string {
|
|||
}
|
||||
if (isHtmlLocator(locator)) {
|
||||
if (/^\d+$/.test(locator.location)) {
|
||||
return `Block ${locator.location} · HTML`;
|
||||
return `Block ${locator.location} · Text`;
|
||||
}
|
||||
return `${locator.location} · HTML`;
|
||||
return `${locator.location} · Text`;
|
||||
}
|
||||
return 'Unknown location';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export interface Folder {
|
|||
export type SortBy = 'name' | 'type' | 'date' | 'size';
|
||||
export type SortDirection = 'asc' | 'desc';
|
||||
|
||||
export type ViewMode = 'icons' | 'list' | 'columns' | 'gallery';
|
||||
export type ViewMode = 'icons' | 'list' | 'gallery';
|
||||
export type IconSize = 'sm' | 'md' | 'lg' | 'xl';
|
||||
|
||||
// Filter applied from the sidebar.
|
||||
|
|
@ -74,7 +74,7 @@ export interface DocumentListState {
|
|||
folders: Folder[];
|
||||
collapsedFolders: string[];
|
||||
showHint: boolean;
|
||||
viewMode?: ViewMode | 'list' | 'grid';
|
||||
viewMode?: ViewMode | 'grid';
|
||||
iconSize?: IconSize;
|
||||
sidebarWidth?: number;
|
||||
sidebarFilter?: SidebarFilter;
|
||||
|
|
|
|||
Loading…
Reference in a new issue