184 lines
6.0 KiB
TypeScript
184 lines
6.0 KiB
TypeScript
import React, { useMemo } from 'react';
|
|
import DesktopPreviewCard from './DesktopPreviewCard';
|
|
import { resolveCorrespondents } from '../documents/correspondents';
|
|
import { getTagColorStyle } from '../utils/colors';
|
|
import { preventAll } from './events';
|
|
|
|
type DocumentLike = {
|
|
id?: string | number;
|
|
title?: string;
|
|
tags?: Array<{ id?: string | number; label?: string; color?: string | null }>;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
interface PendingRemovalTag {
|
|
docId?: string | number;
|
|
tagId?: string | number;
|
|
}
|
|
|
|
interface DesktopDocumentCardProps {
|
|
doc: DocumentLike;
|
|
style?: React.CSSProperties;
|
|
shouldLoad?: boolean;
|
|
dragging?: boolean;
|
|
matchesFilter?: boolean;
|
|
tagTargetActive?: boolean;
|
|
tagTargetPending?: boolean;
|
|
selected?: boolean;
|
|
docTagTokens?: string;
|
|
ensureAssetUrl?: (...args: any[]) => Promise<unknown>;
|
|
getDocumentAsset?: (...args: any[]) => unknown;
|
|
handleNavigatorSnapshot?: (...args: any[]) => void;
|
|
cardPointerHandlers?: React.HTMLAttributes<HTMLDivElement>;
|
|
onInspectDocument?: (id: string | number) => void;
|
|
onTagDragEnter?: (event: React.DragEvent<HTMLDivElement>, docId: string | number) => void;
|
|
onTagDragOver?: (event: React.DragEvent<HTMLDivElement>, docId: string | number) => void;
|
|
onTagDragLeave?: (event: React.DragEvent<HTMLDivElement>, docId: string | number) => void;
|
|
onTagDrop?: (event: React.DragEvent<HTMLDivElement>, doc: DocumentLike) => void;
|
|
onDocTagPointerDown?: (event: React.PointerEvent<HTMLElement>, doc: DocumentLike, tag: any) => void;
|
|
onDocTagDragStart?: (event: React.DragEvent<HTMLElement>, doc: DocumentLike, tag: any) => void;
|
|
onDocTagDrag?: (event: React.DragEvent<HTMLElement>) => void;
|
|
onDocTagDragEnd?: (event: React.DragEvent<HTMLElement>) => void;
|
|
pendingRemovalTag?: PendingRemovalTag | null;
|
|
registerNode?: (node: HTMLDivElement | null) => void;
|
|
}
|
|
|
|
const DesktopDocumentCard: React.FC<DesktopDocumentCardProps> = ({
|
|
doc,
|
|
style,
|
|
shouldLoad,
|
|
dragging,
|
|
matchesFilter,
|
|
tagTargetActive,
|
|
tagTargetPending,
|
|
selected,
|
|
docTagTokens,
|
|
ensureAssetUrl,
|
|
getDocumentAsset,
|
|
handleNavigatorSnapshot,
|
|
cardPointerHandlers,
|
|
onInspectDocument,
|
|
onTagDragEnter,
|
|
onTagDragOver,
|
|
onTagDragLeave,
|
|
onTagDrop,
|
|
onDocTagPointerDown,
|
|
onDocTagDragStart,
|
|
onDocTagDrag,
|
|
onDocTagDragEnd,
|
|
pendingRemovalTag,
|
|
registerNode,
|
|
}) => {
|
|
const correspondents = useMemo(() => resolveCorrespondents(doc), [doc]);
|
|
const tags = Array.isArray(doc?.tags) ? doc.tags : [];
|
|
|
|
const itemClasses = ['desk-item'];
|
|
if (dragging) itemClasses.push('is-dragging');
|
|
if (tagTargetActive) itemClasses.push('is-tag-target');
|
|
if (tagTargetPending) itemClasses.push('is-tag-pending');
|
|
if (!matchesFilter) itemClasses.push('is-filtered-out');
|
|
if (selected) itemClasses.push('is-selected');
|
|
|
|
const ariaHidden = matchesFilter ? undefined : 'true';
|
|
const dataTagIds = docTagTokens || undefined;
|
|
|
|
return (
|
|
<div
|
|
key={doc.id}
|
|
className={itemClasses.join(' ')}
|
|
style={style}
|
|
role="button"
|
|
data-doc-id={doc.id}
|
|
data-tag-ids={dataTagIds}
|
|
aria-hidden={ariaHidden}
|
|
ref={registerNode ?? undefined}
|
|
{...cardPointerHandlers}
|
|
onDragEnter={(event) => {
|
|
if (doc?.id == null) {
|
|
return;
|
|
}
|
|
onTagDragEnter?.(event, doc.id);
|
|
}}
|
|
onDragOver={(event) => {
|
|
if (doc?.id == null) {
|
|
return;
|
|
}
|
|
onTagDragOver?.(event, doc.id);
|
|
}}
|
|
onDragLeave={(event) => {
|
|
if (doc?.id == null) {
|
|
return;
|
|
}
|
|
onTagDragLeave?.(event, doc.id);
|
|
}}
|
|
onDrop={(event) => {
|
|
onTagDrop?.(event, doc);
|
|
}}
|
|
onKeyDown={(event) => {
|
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
preventAll(event);
|
|
onInspectDocument?.(doc.id);
|
|
}
|
|
}}
|
|
>
|
|
<div className="desk-item__body">
|
|
<DesktopPreviewCard
|
|
doc={doc}
|
|
title={doc.title}
|
|
ensureAssetUrl={ensureAssetUrl}
|
|
getDocumentAsset={getDocumentAsset}
|
|
onNavigatorSnapshot={handleNavigatorSnapshot}
|
|
shouldLoad={shouldLoad}
|
|
/>
|
|
{correspondents.length > 0 && (
|
|
<div className="desk-item__correspondents" aria-hidden="true">
|
|
{correspondents.map((correspondent) => (
|
|
<span
|
|
key={correspondent.key}
|
|
className="badge desk-correspondent-chip"
|
|
title={correspondent.name}
|
|
>
|
|
<span className="desk-correspondent-chip__label">{correspondent.name}</span>
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
{tags.length > 0 && (
|
|
<div className="desk-item__tags" aria-hidden="true">
|
|
{tags.map((tag) => {
|
|
if (pendingRemovalTag && pendingRemovalTag.docId === doc.id && pendingRemovalTag.tagId === tag.id) {
|
|
return null;
|
|
}
|
|
const colorStyle = getTagColorStyle(tag.color);
|
|
const pendingRemoval =
|
|
pendingRemovalTag && pendingRemovalTag.docId === doc.id && pendingRemovalTag.tagId === tag.id;
|
|
const tagClasses = ['badge', 'tag-chip', 'tag-chip--draggable'];
|
|
if (pendingRemoval) tagClasses.push('tag-chip--tear-pending');
|
|
return (
|
|
<span
|
|
key={tag.id}
|
|
className={tagClasses.join(' ')}
|
|
style={colorStyle || undefined}
|
|
title={tag.label}
|
|
draggable
|
|
data-desk-tag-chip="true"
|
|
onPointerDownCapture={(event) => {
|
|
onDocTagPointerDown?.(event, doc, tag);
|
|
}}
|
|
onDragStart={(event) => onDocTagDragStart?.(event, doc, tag)}
|
|
onDrag={onDocTagDrag}
|
|
onDragEnd={(event) => onDocTagDragEnd?.(event)}
|
|
>
|
|
<span className="tag-chip__label">{tag.label}</span>
|
|
</span>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default React.memo(DesktopDocumentCard);
|