Feature/v2/get all info on pdf (#5105)

# Description of Changes

- Addition of the get all info on PDF tool

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
This commit is contained in:
EthanHealy01
2025-12-03 20:02:42 +00:00
committed by GitHub
parent e59c717dc0
commit f8dbf171e1
19 changed files with 1526 additions and 8 deletions
+188
View File
@@ -0,0 +1,188 @@
import { useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
import LinkIcon from '@mui/icons-material/Link';
import { Stack, Group, Divider, Text, UnstyledButton } from '@mantine/core';
import { createToolFlow } from '@app/components/tools/shared/createToolFlow';
import { useBaseTool } from '@app/hooks/tools/shared/useBaseTool';
import { BaseToolProps, ToolComponent } from '@app/types/tool';
import { useGetPdfInfoParameters, defaultParameters } from '@app/hooks/tools/getPdfInfo/useGetPdfInfoParameters';
import GetPdfInfoResults from '@app/components/tools/getPdfInfo/GetPdfInfoResults';
import { useGetPdfInfoOperation, GetPdfInfoOperationHook } from '@app/hooks/tools/getPdfInfo/useGetPdfInfoOperation';
import GetPdfInfoReportView from '@app/components/tools/getPdfInfo/GetPdfInfoReportView';
import { useToolWorkflow } from '@app/contexts/ToolWorkflowContext';
import { useNavigationActions, useNavigationState } from '@app/contexts/NavigationContext';
import type { PdfInfoReportData } from '@app/types/getPdfInfo';
const CHAPTERS = [
{ id: 'summary', labelKey: 'getPdfInfo.summary.title', fallback: 'PDF Summary' },
{ id: 'metadata', labelKey: 'getPdfInfo.sections.metadata', fallback: 'Metadata' },
{ id: 'formFields', labelKey: 'getPdfInfo.sections.formFields', fallback: 'Form Fields' },
{ id: 'basicInfo', labelKey: 'getPdfInfo.sections.basicInfo', fallback: 'Basic Info' },
{ id: 'documentInfo', labelKey: 'getPdfInfo.sections.documentInfo', fallback: 'Document Info' },
{ id: 'compliance', labelKey: 'getPdfInfo.sections.compliance', fallback: 'Compliance' },
{ id: 'encryption', labelKey: 'getPdfInfo.sections.encryption', fallback: 'Encryption' },
{ id: 'permissions', labelKey: 'getPdfInfo.sections.permissions', fallback: 'Permissions' },
{ id: 'toc', labelKey: 'getPdfInfo.sections.tableOfContents', fallback: 'Table of Contents' },
{ id: 'other', labelKey: 'getPdfInfo.sections.other', fallback: 'Other' },
{ id: 'perPage', labelKey: 'getPdfInfo.sections.perPageInfo', fallback: 'Per Page Info' },
];
const GetPdfInfo = (props: BaseToolProps) => {
const { t } = useTranslation();
const { actions: navigationActions } = useNavigationActions();
const navigationState = useNavigationState();
const {
registerCustomWorkbenchView,
unregisterCustomWorkbenchView,
setCustomWorkbenchViewData,
clearCustomWorkbenchViewData,
} = useToolWorkflow();
const REPORT_VIEW_ID = 'getPdfInfoReport';
const REPORT_WORKBENCH_ID = 'custom:getPdfInfoReport' as const;
const reportIcon = useMemo(() => <PictureAsPdfIcon fontSize="small" />, []);
const base = useBaseTool(
'getPdfInfo',
useGetPdfInfoParameters,
useGetPdfInfoOperation,
props
);
const operation = base.operation as GetPdfInfoOperationHook;
const hasResults = operation.results.length > 0;
const showResultsStep = hasResults || base.operation.isLoading || !!base.operation.errorMessage;
useEffect(() => {
registerCustomWorkbenchView({
id: REPORT_VIEW_ID,
workbenchId: REPORT_WORKBENCH_ID,
label: t('getPdfInfo.report.shortTitle', 'PDF Information'),
icon: reportIcon,
component: GetPdfInfoReportView,
});
return () => {
clearCustomWorkbenchViewData(REPORT_VIEW_ID);
unregisterCustomWorkbenchView(REPORT_VIEW_ID);
};
}, [
clearCustomWorkbenchViewData,
registerCustomWorkbenchView,
reportIcon,
t,
unregisterCustomWorkbenchView,
]);
const reportData = useMemo<PdfInfoReportData | null>(() => {
if (operation.results.length === 0) return null;
const generatedAt = operation.results[0].summaryGeneratedAt ?? Date.now();
return {
generatedAt,
entries: operation.results,
};
}, [operation.results]);
const lastReportGeneratedAtRef = useRef<number | null>(null);
useEffect(() => {
if (reportData) {
setCustomWorkbenchViewData(REPORT_VIEW_ID, reportData);
const generatedAt = reportData.generatedAt ?? null;
const isNewReport = generatedAt && generatedAt !== lastReportGeneratedAtRef.current;
if (isNewReport) {
lastReportGeneratedAtRef.current = generatedAt;
if (navigationState.selectedTool === 'getPdfInfo' && navigationState.workbench !== REPORT_WORKBENCH_ID) {
navigationActions.setWorkbench(REPORT_WORKBENCH_ID);
}
}
} else {
clearCustomWorkbenchViewData(REPORT_VIEW_ID);
lastReportGeneratedAtRef.current = null;
}
}, [
clearCustomWorkbenchViewData,
navigationActions,
navigationState.selectedTool,
navigationState.workbench,
reportData,
setCustomWorkbenchViewData,
]);
return createToolFlow({
files: {
selectedFiles: base.selectedFiles,
isCollapsed: hasResults,
},
steps: [
{
title: t('getPdfInfo.indexTitle', 'Index'),
isVisible: Boolean(reportData),
isCollapsed: false,
content: (
<Stack gap={0}>
{CHAPTERS.map((c, idx) => (
<Stack key={c.id} gap={0}>
<UnstyledButton
onClick={() => {
if (!reportData) return;
setCustomWorkbenchViewData(REPORT_VIEW_ID, { ...reportData, scrollTo: c.id });
if (navigationState.workbench !== REPORT_WORKBENCH_ID) {
navigationActions.setWorkbench(REPORT_WORKBENCH_ID);
}
}}
style={{ width: '100%', textAlign: 'left', padding: '8px 4px' }}
>
<Group justify="flex-start" gap="sm">
<LinkIcon fontSize="small" style={{ opacity: 0.7 }} />
<Text size="md" c="dimmed">
{t(c.labelKey, c.fallback)}
</Text>
</Group>
</UnstyledButton>
{idx < CHAPTERS.length - 1 && <Divider my={6} />}
</Stack>
))}
</Stack>
),
},
{
title: t('getPdfInfo.results', 'Results'),
isVisible: showResultsStep,
isCollapsed: false,
content: (
<GetPdfInfoResults
operation={operation}
isLoading={base.operation.isLoading}
errorMessage={base.operation.errorMessage}
/>
),
},
],
executeButton: {
text: t('getPdfInfo.submit', 'Generate'),
loadingText: t('loading', 'Loading...'),
onClick: base.handleExecute,
disabled:
!base.params.validateParameters() ||
!base.hasFiles ||
base.operation.isLoading ||
!base.endpointEnabled,
isVisible: true,
},
review: {
isVisible: false,
operation: base.operation,
title: t('getPdfInfo.results', 'Results'),
onUndo: base.handleUndo,
},
});
};
const GetPdfInfoTool = GetPdfInfo as ToolComponent;
GetPdfInfoTool.tool = () => useGetPdfInfoOperation;
GetPdfInfoTool.getDefaultParameters = () => ({ ...defaultParameters });
export default GetPdfInfoTool;