panel actions
This commit is contained in:
@@ -6,6 +6,9 @@ import {
|
||||
ArrowLeftIcon,
|
||||
ArrowRightIcon,
|
||||
ChevronsRightIcon,
|
||||
AnalyzeIcon,
|
||||
WindowMaximizeIcon,
|
||||
TextScanIcon,
|
||||
} from '../ui/icons';
|
||||
import { getTagColorStyle } from '../utils/colors';
|
||||
import { formatFileSize } from '../utils/format';
|
||||
@@ -308,6 +311,15 @@ const DetailPanel = ({
|
||||
const selectedCount = selectedDocuments.length;
|
||||
const singleDoc = selectedCount === 1 ? selectedDocuments[0] : null;
|
||||
|
||||
const singleDownloadHref = useMemo(() => {
|
||||
if (!singleDoc) return null;
|
||||
const downloadPath = singleDoc.current_version?.download_path;
|
||||
if (!downloadPath || !resolveApiPath) {
|
||||
return null;
|
||||
}
|
||||
return resolveApiPath(downloadPath);
|
||||
}, [singleDoc, resolveApiPath]);
|
||||
|
||||
const [titleEditDocId, setTitleEditDocId] = useState(null);
|
||||
const [titleDraft, setTitleDraft] = useState('');
|
||||
const [titleSaving, setTitleSaving] = useState(false);
|
||||
@@ -712,9 +724,6 @@ const DetailPanel = ({
|
||||
}
|
||||
|
||||
const displayName = singleDoc.title || singleDoc.original_name;
|
||||
const downloadHref = singleDoc.current_version?.download_path
|
||||
? resolveApiPath?.(singleDoc.current_version.download_path)
|
||||
: null;
|
||||
const isEditingTitle = titleEditDocId === singleDoc.id;
|
||||
const sizeBytes = Number(singleDoc.current_version?.size_bytes) || 0;
|
||||
const sizeLabel = sizeBytes > 0 ? formatFileSize(sizeBytes) : '—';
|
||||
@@ -864,44 +873,6 @@ const DetailPanel = ({
|
||||
{singleDoc.original_name}
|
||||
</div>
|
||||
</div>
|
||||
<div className="detail-actions">
|
||||
<a
|
||||
className="button-link with-icon"
|
||||
href={downloadHref || '#'}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-disabled={!downloadHref}
|
||||
onClick={(event) => {
|
||||
if (!downloadHref) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DownloadIcon className="icon-inline" />
|
||||
<span>Download</span>
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary"
|
||||
onClick={() => onOpenPreview(singleDoc.id)}
|
||||
>
|
||||
Open preview
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary"
|
||||
onClick={() => onRegenerateThumbnails(singleDoc.id)}
|
||||
>
|
||||
Re-run analysis
|
||||
</button>
|
||||
</div>
|
||||
{hasOcrAsset ? (
|
||||
<div className="detail-ocr-trigger">
|
||||
<button type="button" className="secondary" onClick={openOcrModal}>
|
||||
View OCR text
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
<TagSection
|
||||
title="Tags"
|
||||
tags={tagsForDoc.map((tag) => ({
|
||||
@@ -1081,17 +1052,13 @@ const DetailPanel = ({
|
||||
showCount
|
||||
className="bulk-correspondents"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary"
|
||||
onClick={() => onBulkReanalyze?.()}
|
||||
>
|
||||
Re-analyze selection
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const isBulkSelection = selectedCount > 1;
|
||||
const showOcrAction = Boolean(singleDoc && hasOcrAsset);
|
||||
|
||||
return (
|
||||
<aside className="detail-panel panel">
|
||||
<div className="panel-header">
|
||||
@@ -1101,9 +1068,81 @@ const DetailPanel = ({
|
||||
className="icon-button ghost"
|
||||
onClick={onClose}
|
||||
aria-label="Close detail panel"
|
||||
title="Close detail panel"
|
||||
>
|
||||
<ChevronsRightIcon />
|
||||
</button>
|
||||
<div className="spacer" />
|
||||
{isBulkSelection && onBulkReanalyze ? (
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onBulkReanalyze();
|
||||
}}
|
||||
aria-label="Re-run analysis for selection"
|
||||
title="Re-run analysis for selection"
|
||||
>
|
||||
<AnalyzeIcon />
|
||||
</button>
|
||||
) : null}
|
||||
{singleDoc && singleDownloadHref ? (
|
||||
<a
|
||||
className="icon-button"
|
||||
href={singleDownloadHref}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Download document"
|
||||
title="Download document"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</a>
|
||||
) : null}
|
||||
{singleDoc ? (
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onOpenPreview(singleDoc.id);
|
||||
}}
|
||||
aria-label="Open preview"
|
||||
title="Open preview"
|
||||
disabled={!singleHasPreview}
|
||||
>
|
||||
<WindowMaximizeIcon />
|
||||
</button>
|
||||
) : null}
|
||||
{showOcrAction ? (
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
openOcrModal();
|
||||
}}
|
||||
aria-label="View OCR text"
|
||||
title="View OCR text"
|
||||
>
|
||||
<TextScanIcon />
|
||||
</button>
|
||||
) : null}
|
||||
{singleDoc ? (
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onRegenerateThumbnails(singleDoc.id);
|
||||
}}
|
||||
aria-label="Re-run analysis"
|
||||
title="Re-run analysis"
|
||||
>
|
||||
<AnalyzeIcon />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="panel-body">
|
||||
|
||||
+43
-12
@@ -44,7 +44,6 @@
|
||||
--danger-border: color-mix(in oklch, var(--danger) 45%, transparent);
|
||||
--danger-soft: color-mix(in oklch, var(--danger) 12%, transparent);
|
||||
--danger-subtle: color-mix(in oklch, var(--danger) 8%, transparent);
|
||||
--detail-panel-width: 30em;
|
||||
|
||||
/* --- Explorer states --- */
|
||||
--row-hover-bg: color-mix(in oklch, var(--accent) 6%, transparent);
|
||||
@@ -55,6 +54,9 @@
|
||||
|
||||
font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
font-size: 15px;
|
||||
|
||||
--detail-panel-width: 30em;
|
||||
--documents-grid-title-size: 0.8rem;
|
||||
}
|
||||
|
||||
html,
|
||||
@@ -178,6 +180,41 @@ button.danger:hover:not([disabled]) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.panel-actions .icon-button,
|
||||
.panel-actions button,
|
||||
.panel-actions a.icon-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
padding: 0.25rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.panel-actions .icon-button:hover:not([disabled]),
|
||||
.panel-actions button:hover:not([disabled]),
|
||||
.panel-actions a.icon-button:hover {
|
||||
background: var(--sidebar-hover-bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.panel-actions .icon-button.ghost,
|
||||
.panel-actions button.icon-button.ghost {
|
||||
color: var(--muted);
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.panel-actions .icon-button.ghost:hover:not([disabled]),
|
||||
.panel-actions button.icon-button.ghost:hover:not([disabled]) {
|
||||
color: var(--fg);
|
||||
background: var(--sidebar-hover-bg);
|
||||
}
|
||||
|
||||
.with-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -1019,7 +1056,6 @@ button.danger:hover:not([disabled]) {
|
||||
|
||||
.sidebar-correspondent-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
padding: 0.35rem 0.5rem;
|
||||
@@ -1528,8 +1564,12 @@ button.danger:hover:not([disabled]) {
|
||||
|
||||
.panel-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.panel-actions .spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.detail-panel .panel-body {
|
||||
@@ -1542,15 +1582,6 @@ button.danger:hover:not([disabled]) {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.detail-actions {
|
||||
display: flex;
|
||||
gap: 0.32rem;
|
||||
margin: 0.6rem 0;
|
||||
}
|
||||
|
||||
.detail-ocr-trigger {
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
.detail-ocr__content {
|
||||
max-height: 60vh;
|
||||
|
||||
@@ -10,6 +10,9 @@ import {
|
||||
IconArrowLeft,
|
||||
IconArrowRight,
|
||||
IconChevronsRight,
|
||||
IconAnalyze,
|
||||
IconWindowMaximize,
|
||||
IconTextScan2,
|
||||
} from '@tabler/icons-react';
|
||||
import FolderSvg from '../assets/folder.svg';
|
||||
|
||||
@@ -130,6 +133,24 @@ export const ChevronsRightIcon = ({ className, size = '1em', stroke = 1.6, ...re
|
||||
/>
|
||||
);
|
||||
|
||||
export const AnalyzeIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconAnalyze
|
||||
className={composeClassName('icon', className)}
|
||||
size={size}
|
||||
stroke={stroke}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export const WindowMaximizeIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconWindowMaximize
|
||||
className={composeClassName('icon', className)}
|
||||
size={size}
|
||||
stroke={stroke}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export default {
|
||||
ChevronIcon,
|
||||
TrashIcon,
|
||||
@@ -141,4 +162,15 @@ export default {
|
||||
ViewListIcon,
|
||||
ViewGridIcon,
|
||||
ChevronsRightIcon,
|
||||
AnalyzeIcon,
|
||||
WindowMaximizeIcon,
|
||||
};
|
||||
|
||||
export const TextScanIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconTextScan2
|
||||
className={composeClassName('icon', className)}
|
||||
size={size}
|
||||
stroke={stroke}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user