This commit is contained in:
saqib mir
2026-06-11 11:09:23 +05:30
+37 -13
View File
@@ -123,7 +123,10 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = (p) => {
}`} }`}
> >
{React.isValidElement(t.icon) ? React.cloneElement(t.icon as React.ReactElement<{ size?: number }>, { size: 18 }) : t.icon} {React.isValidElement(t.icon) ? React.cloneElement(t.icon as React.ReactElement<{ size?: number }>, { size: 18 }) : t.icon}
{t.id === 'notes' && p.annotations.length > 0 && ( {t.id === 'notes' && p.annotations.some((a) => a.type !== 'widget') && (
<span className="absolute right-1 top-1 h-1.5 w-1.5 rounded-full bg-[#2563eb]" />
)}
{t.id === 'forms' && p.annotations.some((a) => a.type === 'widget') && (
<span className="absolute right-1 top-1 h-1.5 w-1.5 rounded-full bg-[#2563eb]" /> <span className="absolute right-1 top-1 h-1.5 w-1.5 rounded-full bg-[#2563eb]" />
)} )}
</CustomButton> </CustomButton>
@@ -134,22 +137,18 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = (p) => {
{/* Body */} {/* Body */}
<div className="custom-scrollbar min-h-0 flex-1 overflow-y-auto"> <div className="custom-scrollbar min-h-0 flex-1 overflow-y-auto">
{p.activeTab === 'pages' && <PagesTab {...p} />} {p.activeTab === 'pages' && <PagesTab {...p} />}
{p.activeTab === 'notes' && <NotesTab annotations={p.annotations} onNavigate={p.onNavigateAnnotation} onDelete={p.onDeleteAnnotation} />} {p.activeTab === 'notes' && <NotesTab annotations={p.annotations.filter((a) => a.type !== 'widget')} onNavigate={p.onNavigateAnnotation} onDelete={p.onDeleteAnnotation} />}
{p.activeTab === 'search' && <SearchTab {...p} />} {p.activeTab === 'search' && <SearchTab {...p} />}
{p.activeTab === 'properties' && <PropertiesTab metadata={p.metadata} sizeBytes={p.sizeBytes} totalPages={p.totalPages} filename={selectedDoc?.filename} />} {p.activeTab === 'properties' && <PropertiesTab metadata={p.metadata} sizeBytes={p.sizeBytes} totalPages={p.totalPages} filename={selectedDoc?.filename} />}
{p.activeTab === 'fonts' && <FontsTab fonts={p.fonts} />} {p.activeTab === 'fonts' && <FontsTab fonts={p.fonts} />}
{p.activeTab === 'outline' && <OutlineTab outline={p.outline} onNavigate={p.onNavigateOutline} />} {p.activeTab === 'outline' && <OutlineTab outline={p.outline} onNavigate={p.onNavigateOutline} />}
{p.activeTab === 'forms' && ( {p.activeTab === 'forms' && <FormsTab fields={p.annotations.filter((a) => a.type === 'widget')} onNavigate={p.onNavigateAnnotation} />}
<EmptyState badge="Coming soon" icon={<FormsIcon size={30} />} title="Form fields"
hint="Viewing and filling AcroForm fields is on the engine roadmap and will light up here." />
)}
</div> </div>
</div> </div>
</aside> </aside>
); );
}; };
/* ----------------------------------------------------------------- Pages */
const PagesTab: React.FC<InspectorPanelProps> = (p) => { const PagesTab: React.FC<InspectorPanelProps> = (p) => {
const [draggedIdx, setDraggedIdx] = React.useState<number | null>(null); const [draggedIdx, setDraggedIdx] = React.useState<number | null>(null);
@@ -192,7 +191,6 @@ const PagesTab: React.FC<InspectorPanelProps> = (p) => {
); );
}; };
/* ----------------------------------------------------------------- Notes */
const NotesTab: React.FC<{ annotations: Annotation[]; onNavigate: (a: Annotation) => void; onDelete: (a: Annotation) => void }> = ({ annotations, onNavigate, onDelete }) => { const NotesTab: React.FC<{ annotations: Annotation[]; onNavigate: (a: Annotation) => void; onDelete: (a: Annotation) => void }> = ({ annotations, onNavigate, onDelete }) => {
if (annotations.length === 0) { if (annotations.length === 0) {
return <EmptyState icon={<NotesIcon size={30} />} title="No annotations yet" return <EmptyState icon={<NotesIcon size={30} />} title="No annotations yet"
@@ -225,7 +223,6 @@ const NotesTab: React.FC<{ annotations: Annotation[]; onNavigate: (a: Annotation
); );
}; };
/* --------------------------------------------------------------- Outline */
const OutlineTab: React.FC<{ outline: OutlineItem[]; onNavigate: (pageIndex: number) => void }> = ({ outline, onNavigate }) => { const OutlineTab: React.FC<{ outline: OutlineItem[]; onNavigate: (pageIndex: number) => void }> = ({ outline, onNavigate }) => {
if (outline.length === 0) { if (outline.length === 0) {
return <EmptyState icon={<OutlineIcon size={30} />} title="No outline" hint="This document has no bookmarks/outline." />; return <EmptyState icon={<OutlineIcon size={30} />} title="No outline" hint="This document has no bookmarks/outline." />;
@@ -249,7 +246,6 @@ const OutlineTab: React.FC<{ outline: OutlineItem[]; onNavigate: (pageIndex: num
); );
}; };
/* ---------------------------------------------------------------- Search */
const SearchTab: React.FC<InspectorPanelProps> = (p) => { const SearchTab: React.FC<InspectorPanelProps> = (p) => {
return ( return (
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
@@ -342,7 +338,6 @@ const SearchTab: React.FC<InspectorPanelProps> = (p) => {
); );
}; };
/* ------------------------------------------------------------ Properties */
const PropertiesTab: React.FC<{ metadata: DocumentMetadata | null; sizeBytes?: number; totalPages: number; filename?: string }> = ({ metadata, sizeBytes, totalPages, filename }) => { const PropertiesTab: React.FC<{ metadata: DocumentMetadata | null; sizeBytes?: number; totalPages: number; filename?: string }> = ({ metadata, sizeBytes, totalPages, filename }) => {
const rows: [string, string | undefined][] = [ const rows: [string, string | undefined][] = [
['File name', filename], ['File name', filename],
@@ -370,12 +365,10 @@ const PropertiesTab: React.FC<{ metadata: DocumentMetadata | null; sizeBytes?: n
); );
}; };
/* ----------------------------------------------------------------- Fonts */
const FontsTab: React.FC<{ fonts: FontInfo[] }> = ({ fonts }) => { const FontsTab: React.FC<{ fonts: FontInfo[] }> = ({ fonts }) => {
if (fonts.length === 0) { if (fonts.length === 0) {
return <EmptyState icon={<FontsIcon size={30} />} title="No font data" hint="Font inventory appears once a document with embedded fonts is open." />; return <EmptyState icon={<FontsIcon size={30} />} title="No font data" hint="Font inventory appears once a document with embedded fonts is open." />;
} }
// De-duplicate by name
const seen = new Set<string>(); const seen = new Set<string>();
const unique = fonts.filter((f) => (seen.has(f.fontName) ? false : (seen.add(f.fontName), true))); const unique = fonts.filter((f) => (seen.has(f.fontName) ? false : (seen.add(f.fontName), true)));
return ( return (
@@ -408,3 +401,34 @@ const Badge: React.FC<{ label: string; ok?: boolean; warn?: boolean }> = ({ labe
{label} {label}
</span> </span>
); );
const FIELD_TYPE_LABEL: Record<string, string> = { Tx: 'Text', Btn: 'Button', Ch: 'Choice', Sig: 'Signature' };
const FormsTab: React.FC<{ fields: Annotation[]; onNavigate: (a: Annotation) => void }> = ({ fields, onNavigate }) => {
if (fields.length === 0) {
return <EmptyState icon={<FormsIcon size={30} />} title="No form fields" hint="This document has no AcroForm fields." />;
}
return (
<div className="flex flex-col gap-2 p-3">
<p className="px-1 text-[11px] font-medium text-[#98a1ad]">{fields.length} field{fields.length > 1 ? 's' : ''}</p>
{fields.map((f) => (
<CustomButton variant="unstyled" key={f.id} onClick={() => onNavigate(f)}
className="flex flex-col gap-1.5 rounded-[8px] border border-[#ebedf0] bg-[#f6f7f9] p-2.5 text-left transition-colors hover:border-[#dadde2]">
<div className="flex items-center justify-between gap-2">
<span className="min-w-0 flex-1 truncate text-[12.5px] font-semibold text-[#18212e]" title={f.fieldName}>{f.fieldName || '(unnamed field)'}</span>
<span className="shrink-0 rounded bg-[#eef4ff] px-1.5 py-0.5 text-[9px] font-bold uppercase text-[#2563eb]">{FIELD_TYPE_LABEL[f.fieldType || ''] || f.fieldType || 'Field'}</span>
</div>
<div className="flex items-center gap-1.5">
<span className="text-[10px] font-semibold uppercase tracking-wide text-[#98a1ad]">Value</span>
<span className="min-w-0 flex-1 truncate text-[12px] text-[#18212e]">{f.fieldValue && f.fieldValue.trim() ? f.fieldValue : <span className="text-[#98a1ad]"></span>}</span>
{f.pageIndex !== undefined && <span className="shrink-0 text-[10px] tabular-nums text-[#98a1ad]">p.{f.pageIndex + 1}</span>}
</div>
{f.fieldOptions && f.fieldOptions.length > 0 && (
<span className="truncate text-[10px] text-[#98a1ad]">Options: {f.fieldOptions.slice(0, 5).join(', ')}{f.fieldOptions.length > 5 ? '…' : ''}</span>
)}
</CustomButton>
))}
<p className="px-1 pt-1 text-[10.5px] leading-relaxed text-[#98a1ad]">Form fields are view-only filling is on the Phase 3 roadmap.</p>
</div>
);
};