Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into saqib
This commit is contained in:
@@ -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}
|
||||
{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]" />
|
||||
)}
|
||||
</CustomButton>
|
||||
@@ -134,22 +137,18 @@ export const InspectorPanel: React.FC<InspectorPanelProps> = (p) => {
|
||||
{/* Body */}
|
||||
<div className="custom-scrollbar min-h-0 flex-1 overflow-y-auto">
|
||||
{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 === 'properties' && <PropertiesTab metadata={p.metadata} sizeBytes={p.sizeBytes} totalPages={p.totalPages} filename={selectedDoc?.filename} />}
|
||||
{p.activeTab === 'fonts' && <FontsTab fonts={p.fonts} />}
|
||||
{p.activeTab === 'outline' && <OutlineTab outline={p.outline} onNavigate={p.onNavigateOutline} />}
|
||||
{p.activeTab === 'forms' && (
|
||||
<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." />
|
||||
)}
|
||||
{p.activeTab === 'forms' && <FormsTab fields={p.annotations.filter((a) => a.type === 'widget')} onNavigate={p.onNavigateAnnotation} />}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
/* ----------------------------------------------------------------- Pages */
|
||||
const PagesTab: React.FC<InspectorPanelProps> = (p) => {
|
||||
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 }) => {
|
||||
if (annotations.length === 0) {
|
||||
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 }) => {
|
||||
if (outline.length === 0) {
|
||||
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) => {
|
||||
return (
|
||||
<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 rows: [string, string | undefined][] = [
|
||||
['File name', filename],
|
||||
@@ -370,12 +365,10 @@ const PropertiesTab: React.FC<{ metadata: DocumentMetadata | null; sizeBytes?: n
|
||||
);
|
||||
};
|
||||
|
||||
/* ----------------------------------------------------------------- Fonts */
|
||||
const FontsTab: React.FC<{ fonts: FontInfo[] }> = ({ fonts }) => {
|
||||
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." />;
|
||||
}
|
||||
// De-duplicate by name
|
||||
const seen = new Set<string>();
|
||||
const unique = fonts.filter((f) => (seen.has(f.fontName) ? false : (seen.add(f.fontName), true)));
|
||||
return (
|
||||
@@ -408,3 +401,34 @@ const Badge: React.FC<{ label: string; ok?: boolean; warn?: boolean }> = ({ labe
|
||||
{label}
|
||||
</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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user