mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-29 15:49:22 +00:00
Posthog, scarf and url navigation overhaul (#4318)
Added post hog project - always enabled Added scarf pixel - Always enabled Reworked Url navigation Forward and back now works without reloading page --------- Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
This commit is contained in:
parent
5b20f11e20
commit
a7d5c80188
65
frontend/package-lock.json
generated
65
frontend/package-lock.json
generated
@ -32,6 +32,7 @@
|
|||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^3.11.174",
|
"pdfjs-dist": "^3.11.174",
|
||||||
|
"posthog-js": "^1.261.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-i18next": "^15.5.2",
|
"react-i18next": "^15.5.2",
|
||||||
@ -1774,6 +1775,12 @@
|
|||||||
"url": "https://opencollective.com/popperjs"
|
"url": "https://opencollective.com/popperjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@posthog/core": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-hWk3rUtJl2crQK0WNmwg13n82hnTwB99BT99/XI5gZSvIlYZ1TPmMZE8H2dhJJ98J/rm9vYJ/UXNzw3RV5HTpQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@rolldown/pluginutils": {
|
"node_modules/@rolldown/pluginutils": {
|
||||||
"version": "1.0.0-beta.9",
|
"version": "1.0.0-beta.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz",
|
||||||
@ -3813,6 +3820,17 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/core-js": {
|
||||||
|
"version": "3.45.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz",
|
||||||
|
"integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/core-js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/core-util-is": {
|
"node_modules/core-util-is": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||||
@ -4614,6 +4632,12 @@
|
|||||||
"reusify": "^1.0.4"
|
"reusify": "^1.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fflate": {
|
||||||
|
"version": "0.4.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
|
||||||
|
"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/file-selector": {
|
"node_modules/file-selector": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz",
|
||||||
@ -7388,6 +7412,47 @@
|
|||||||
"postcss": "^8.2.9"
|
"postcss": "^8.2.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/posthog-js": {
|
||||||
|
"version": "1.261.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.261.0.tgz",
|
||||||
|
"integrity": "sha512-jyiXqyrCU+VlpbNNVRA6OQYAVut0XZMYNELCZH+XvTd981VqbE4jXn4XCBreo7XCL2gdPgDVxUVOuzNvEuKcmw==",
|
||||||
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
|
"dependencies": {
|
||||||
|
"@posthog/core": "1.0.2",
|
||||||
|
"core-js": "^3.38.1",
|
||||||
|
"fflate": "^0.4.8",
|
||||||
|
"preact": "^10.19.3",
|
||||||
|
"web-vitals": "^4.2.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@rrweb/types": "2.0.0-alpha.17",
|
||||||
|
"rrweb-snapshot": "2.0.0-alpha.17"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@rrweb/types": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"rrweb-snapshot": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/posthog-js/node_modules/web-vitals": {
|
||||||
|
"version": "4.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
|
||||||
|
"integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/preact": {
|
||||||
|
"version": "10.27.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.27.1.tgz",
|
||||||
|
"integrity": "sha512-V79raXEWch/rbqoNc7nT9E4ep7lu+mI3+sBmfRD4i1M73R3WLYcCtdI0ibxGVf4eQL8ZIz2nFacqEC+rmnOORQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/precinct": {
|
"node_modules/precinct": {
|
||||||
"version": "12.2.0",
|
"version": "12.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/precinct/-/precinct-12.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/precinct/-/precinct-12.2.0.tgz",
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^3.11.174",
|
"pdfjs-dist": "^3.11.174",
|
||||||
|
"posthog-js": "^1.261.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-i18next": "^15.5.2",
|
"react-i18next": "^15.5.2",
|
||||||
|
@ -182,6 +182,7 @@ function getLicenseUrl(licenseType) {
|
|||||||
|
|
||||||
const licenseUrls = {
|
const licenseUrls = {
|
||||||
'MIT': 'https://opensource.org/licenses/MIT',
|
'MIT': 'https://opensource.org/licenses/MIT',
|
||||||
|
'MIT*': 'https://opensource.org/licenses/MIT',
|
||||||
'Apache-2.0': 'https://www.apache.org/licenses/LICENSE-2.0',
|
'Apache-2.0': 'https://www.apache.org/licenses/LICENSE-2.0',
|
||||||
'Apache License 2.0': 'https://www.apache.org/licenses/LICENSE-2.0',
|
'Apache License 2.0': 'https://www.apache.org/licenses/LICENSE-2.0',
|
||||||
'BSD-3-Clause': 'https://opensource.org/licenses/BSD-3-Clause',
|
'BSD-3-Clause': 'https://opensource.org/licenses/BSD-3-Clause',
|
||||||
@ -270,7 +271,7 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
|||||||
|
|
||||||
// Known good licenses (no warnings needed)
|
// Known good licenses (no warnings needed)
|
||||||
const goodLicenses = new Set([
|
const goodLicenses = new Set([
|
||||||
'MIT', 'Apache-2.0', 'Apache License 2.0', 'BSD-2-Clause', 'BSD-3-Clause', 'BSD',
|
'MIT', 'MIT*', 'Apache-2.0', 'Apache License 2.0', 'BSD-2-Clause', 'BSD-3-Clause', 'BSD',
|
||||||
'ISC', 'CC0-1.0', 'Public Domain', 'Unlicense', '0BSD', 'BlueOak-1.0.0',
|
'ISC', 'CC0-1.0', 'Public Domain', 'Unlicense', '0BSD', 'BlueOak-1.0.0',
|
||||||
'Zlib', 'Artistic-2.0', 'Python-2.0', 'Ruby', 'MPL-2.0', 'CC-BY-4.0',
|
'Zlib', 'Artistic-2.0', 'Python-2.0', 'Ruby', 'MPL-2.0', 'CC-BY-4.0',
|
||||||
'SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE'
|
'SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE'
|
||||||
|
@ -412,9 +412,9 @@ const FileEditor = ({
|
|||||||
if (record) {
|
if (record) {
|
||||||
// Set the file as selected in context and switch to viewer for preview
|
// Set the file as selected in context and switch to viewer for preview
|
||||||
setSelectedFiles([fileId]);
|
setSelectedFiles([fileId]);
|
||||||
navActions.setMode('viewer');
|
navActions.setWorkbench('viewer');
|
||||||
}
|
}
|
||||||
}, [activeFileRecords, setSelectedFiles, navActions.setMode]);
|
}, [activeFileRecords, setSelectedFiles, navActions.setWorkbench]);
|
||||||
|
|
||||||
const handleMergeFromHere = useCallback((fileId: FileId) => {
|
const handleMergeFromHere = useCallback((fileId: FileId) => {
|
||||||
const startIndex = activeFileRecords.findIndex(r => r.id === fileId);
|
const startIndex = activeFileRecords.findIndex(r => r.id === fileId);
|
||||||
|
@ -6,13 +6,13 @@ import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
|
|||||||
import { useFileHandler } from '../../hooks/useFileHandler';
|
import { useFileHandler } from '../../hooks/useFileHandler';
|
||||||
import { useFileState, useFileActions } from '../../contexts/FileContext';
|
import { useFileState, useFileActions } from '../../contexts/FileContext';
|
||||||
import { useNavigationState, useNavigationActions } from '../../contexts/NavigationContext';
|
import { useNavigationState, useNavigationActions } from '../../contexts/NavigationContext';
|
||||||
|
import { useToolManagement } from '../../hooks/useToolManagement';
|
||||||
|
|
||||||
import TopControls from '../shared/TopControls';
|
import TopControls from '../shared/TopControls';
|
||||||
import FileEditor from '../fileEditor/FileEditor';
|
import FileEditor from '../fileEditor/FileEditor';
|
||||||
import PageEditor from '../pageEditor/PageEditor';
|
import PageEditor from '../pageEditor/PageEditor';
|
||||||
import PageEditorControls from '../pageEditor/PageEditorControls';
|
import PageEditorControls from '../pageEditor/PageEditorControls';
|
||||||
import Viewer from '../viewer/Viewer';
|
import Viewer from '../viewer/Viewer';
|
||||||
import ToolRenderer from '../tools/ToolRenderer';
|
|
||||||
import LandingPage from '../shared/LandingPage';
|
import LandingPage from '../shared/LandingPage';
|
||||||
|
|
||||||
// No props needed - component uses contexts directly
|
// No props needed - component uses contexts directly
|
||||||
@ -23,9 +23,9 @@ export default function Workbench() {
|
|||||||
// Use context-based hooks to eliminate all prop drilling
|
// Use context-based hooks to eliminate all prop drilling
|
||||||
const { state } = useFileState();
|
const { state } = useFileState();
|
||||||
const { actions } = useFileActions();
|
const { actions } = useFileActions();
|
||||||
const { currentMode: currentView } = useNavigationState();
|
const { workbench: currentView } = useNavigationState();
|
||||||
const { actions: navActions } = useNavigationActions();
|
const { actions: navActions } = useNavigationActions();
|
||||||
const setCurrentView = navActions.setMode;
|
const setCurrentView = navActions.setWorkbench;
|
||||||
const activeFiles = state.files.ids;
|
const activeFiles = state.files.ids;
|
||||||
const {
|
const {
|
||||||
previewFile,
|
previewFile,
|
||||||
@ -36,7 +36,14 @@ export default function Workbench() {
|
|||||||
setSidebarsVisible
|
setSidebarsVisible
|
||||||
} = useToolWorkflow();
|
} = useToolWorkflow();
|
||||||
|
|
||||||
const { selectedToolKey, selectedTool, handleToolSelect } = useToolWorkflow();
|
const { handleToolSelect } = useToolWorkflow();
|
||||||
|
|
||||||
|
// Get navigation state - this is the source of truth
|
||||||
|
const { selectedTool: selectedToolId } = useNavigationState();
|
||||||
|
|
||||||
|
// Get tool registry to look up selected tool
|
||||||
|
const { toolRegistry } = useToolManagement();
|
||||||
|
const selectedTool = selectedToolId ? toolRegistry[selectedToolId] : null;
|
||||||
const { addToActiveFiles } = useFileHandler();
|
const { addToActiveFiles } = useFileHandler();
|
||||||
|
|
||||||
const handlePreviewClose = () => {
|
const handlePreviewClose = () => {
|
||||||
@ -69,11 +76,11 @@ export default function Workbench() {
|
|||||||
case "fileEditor":
|
case "fileEditor":
|
||||||
return (
|
return (
|
||||||
<FileEditor
|
<FileEditor
|
||||||
toolMode={!!selectedToolKey}
|
toolMode={!!selectedToolId}
|
||||||
showUpload={true}
|
showUpload={true}
|
||||||
showBulkActions={!selectedToolKey}
|
showBulkActions={!selectedToolId}
|
||||||
supportedExtensions={selectedTool?.supportedFormats || ["pdf"]}
|
supportedExtensions={selectedTool?.supportedFormats || ["pdf"]}
|
||||||
{...(!selectedToolKey && {
|
{...(!selectedToolId && {
|
||||||
onOpenPageEditor: (file) => {
|
onOpenPageEditor: (file) => {
|
||||||
setCurrentView("pageEditor");
|
setCurrentView("pageEditor");
|
||||||
},
|
},
|
||||||
@ -127,14 +134,6 @@ export default function Workbench() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Check if it's a tool view
|
|
||||||
if (selectedToolKey && selectedTool) {
|
|
||||||
return (
|
|
||||||
<ToolRenderer
|
|
||||||
selectedToolKey={selectedToolKey}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<LandingPage/>
|
<LandingPage/>
|
||||||
);
|
);
|
||||||
@ -154,7 +153,7 @@ export default function Workbench() {
|
|||||||
<TopControls
|
<TopControls
|
||||||
currentView={currentView}
|
currentView={currentView}
|
||||||
setCurrentView={setCurrentView}
|
setCurrentView={setCurrentView}
|
||||||
selectedToolKey={selectedToolKey}
|
selectedToolKey={selectedToolId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Main content area */}
|
{/* Main content area */}
|
||||||
|
@ -6,7 +6,6 @@ import {
|
|||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useFileState, useFileActions, useCurrentFile, useFileSelection } from "../../contexts/FileContext";
|
import { useFileState, useFileActions, useCurrentFile, useFileSelection } from "../../contexts/FileContext";
|
||||||
import { ModeType } from "../../contexts/NavigationContext";
|
|
||||||
import { PDFDocument, PDFPage, PageEditorFunctions } from "../../types/pageEditor";
|
import { PDFDocument, PDFPage, PageEditorFunctions } from "../../types/pageEditor";
|
||||||
import { ProcessedFile as EnhancedProcessedFile } from "../../types/processing";
|
import { ProcessedFile as EnhancedProcessedFile } from "../../types/processing";
|
||||||
import { pdfExportService } from "../../services/pdfExportService";
|
import { pdfExportService } from "../../services/pdfExportService";
|
||||||
|
@ -25,7 +25,7 @@ export default function RightRail() {
|
|||||||
const [csvInput, setCsvInput] = useState<string>("");
|
const [csvInput, setCsvInput] = useState<string>("");
|
||||||
|
|
||||||
// Navigation view
|
// Navigation view
|
||||||
const { currentMode: currentView } = useNavigationState();
|
const { workbench: currentView } = useNavigationState();
|
||||||
|
|
||||||
// File state and selection
|
// File state and selection
|
||||||
const { state, selectors } = useFileState();
|
const { state, selectors } = useFileState();
|
||||||
|
@ -5,7 +5,7 @@ import rainbowStyles from '../../styles/rainbow.module.css';
|
|||||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||||
import EditNoteIcon from "@mui/icons-material/EditNote";
|
import EditNoteIcon from "@mui/icons-material/EditNote";
|
||||||
import FolderIcon from "@mui/icons-material/Folder";
|
import FolderIcon from "@mui/icons-material/Folder";
|
||||||
import { ModeType, isValidMode } from '../../contexts/NavigationContext';
|
import { WorkbenchType, isValidWorkbench } from '../../types/workbench';
|
||||||
import { Tooltip } from "./Tooltip";
|
import { Tooltip } from "./Tooltip";
|
||||||
|
|
||||||
const viewOptionStyle = {
|
const viewOptionStyle = {
|
||||||
@ -19,7 +19,7 @@ const viewOptionStyle = {
|
|||||||
|
|
||||||
|
|
||||||
// Build view options showing text only for current view; others icon-only with tooltip
|
// Build view options showing text only for current view; others icon-only with tooltip
|
||||||
const createViewOptions = (currentView: ModeType, switchingTo: ModeType | null) => [
|
const createViewOptions = (currentView: WorkbenchType, switchingTo: WorkbenchType | null) => [
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<div style={viewOptionStyle as React.CSSProperties}>
|
<div style={viewOptionStyle as React.CSSProperties}>
|
||||||
@ -70,8 +70,8 @@ const createViewOptions = (currentView: ModeType, switchingTo: ModeType | null)
|
|||||||
];
|
];
|
||||||
|
|
||||||
interface TopControlsProps {
|
interface TopControlsProps {
|
||||||
currentView: ModeType;
|
currentView: WorkbenchType;
|
||||||
setCurrentView: (view: ModeType) => void;
|
setCurrentView: (view: WorkbenchType) => void;
|
||||||
selectedToolKey?: string | null;
|
selectedToolKey?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,25 +81,25 @@ const TopControls = ({
|
|||||||
selectedToolKey,
|
selectedToolKey,
|
||||||
}: TopControlsProps) => {
|
}: TopControlsProps) => {
|
||||||
const { isRainbowMode } = useRainbowThemeContext();
|
const { isRainbowMode } = useRainbowThemeContext();
|
||||||
const [switchingTo, setSwitchingTo] = useState<ModeType | null>(null);
|
const [switchingTo, setSwitchingTo] = useState<WorkbenchType | null>(null);
|
||||||
|
|
||||||
const isToolSelected = selectedToolKey !== null;
|
const isToolSelected = selectedToolKey !== null;
|
||||||
|
|
||||||
const handleViewChange = useCallback((view: string) => {
|
const handleViewChange = useCallback((view: string) => {
|
||||||
if (!isValidMode(view)) {
|
if (!isValidWorkbench(view)) {
|
||||||
// Ignore invalid values defensively
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const mode = view as ModeType;
|
|
||||||
|
const workbench = view;
|
||||||
|
|
||||||
// Show immediate feedback
|
// Show immediate feedback
|
||||||
setSwitchingTo(mode as ModeType);
|
setSwitchingTo(workbench);
|
||||||
|
|
||||||
// Defer the heavy view change to next frame so spinner can render
|
// Defer the heavy view change to next frame so spinner can render
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
// Give the spinner one more frame to show
|
// Give the spinner one more frame to show
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
setCurrentView(mode as ModeType);
|
setCurrentView(workbench);
|
||||||
|
|
||||||
// Clear the loading state after view change completes
|
// Clear the loading state after view change completes
|
||||||
setTimeout(() => setSwitchingTo(null), 300);
|
setTimeout(() => setSwitchingTo(null), 300);
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
Modal
|
Modal
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import CheckIcon from '@mui/icons-material/Check';
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
import { ToolRegistryEntry } from '../../../data/toolsTaxonomy';
|
import { ToolRegistry } from '../../../data/toolsTaxonomy';
|
||||||
import ToolConfigurationModal from './ToolConfigurationModal';
|
import ToolConfigurationModal from './ToolConfigurationModal';
|
||||||
import ToolList from './ToolList';
|
import ToolList from './ToolList';
|
||||||
import IconSelector from './IconSelector';
|
import IconSelector from './IconSelector';
|
||||||
@ -24,7 +24,7 @@ interface AutomationCreationProps {
|
|||||||
existingAutomation?: AutomationConfig;
|
existingAutomation?: AutomationConfig;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onComplete: (automation: AutomationConfig) => void;
|
onComplete: (automation: AutomationConfig) => void;
|
||||||
toolRegistry: Record<string, ToolRegistryEntry>;
|
toolRegistry: ToolRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AutomationCreation({ mode, existingAutomation, onBack, onComplete, toolRegistry }: AutomationCreationProps) {
|
export default function AutomationCreation({ mode, existingAutomation, onBack, onComplete, toolRegistry }: AutomationCreationProps) {
|
||||||
|
@ -33,7 +33,7 @@ export default function AutomationRun({ automation, onComplete, automateOperatio
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (automation?.operations) {
|
if (automation?.operations) {
|
||||||
const steps = automation.operations.map((op: any, index: number) => {
|
const steps = automation.operations.map((op: any, index: number) => {
|
||||||
const tool = toolRegistry[op.operation];
|
const tool = toolRegistry[op.operation as keyof typeof toolRegistry];
|
||||||
return {
|
return {
|
||||||
id: `${op.operation}-${index}`,
|
id: `${op.operation}-${index}`,
|
||||||
operation: op.operation,
|
operation: op.operation,
|
||||||
|
@ -35,7 +35,7 @@ export default function ToolConfigurationModal({ opened, tool, onSave, onCancel,
|
|||||||
const [isValid, setIsValid] = useState(true);
|
const [isValid, setIsValid] = useState(true);
|
||||||
|
|
||||||
// Get tool info from registry
|
// Get tool info from registry
|
||||||
const toolInfo = toolRegistry[tool.operation];
|
const toolInfo = toolRegistry[tool.operation as keyof ToolRegistry];
|
||||||
const SettingsComponent = toolInfo?.settingsComponent;
|
const SettingsComponent = toolInfo?.settingsComponent;
|
||||||
|
|
||||||
// Initialize parameters from tool (which should contain defaults from registry)
|
// Initialize parameters from tool (which should contain defaults from registry)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { createContext, useContext, useReducer, useCallback } from 'react';
|
import React, { createContext, useContext, useReducer, useCallback } from 'react';
|
||||||
import { useNavigationUrlSync } from '../hooks/useUrlSync';
|
import { WorkbenchType, getDefaultWorkbench } from '../types/workbench';
|
||||||
import { ModeType, isValidMode, getDefaultMode } from '../types/navigation';
|
import { ToolId, isValidToolId } from '../types/toolId';
|
||||||
|
import { useFlatToolRegistry } from '../data/useTranslatedToolRegistry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NavigationContext - Complete navigation management system
|
* NavigationContext - Complete navigation management system
|
||||||
@ -11,27 +12,38 @@ import { ModeType, isValidMode, getDefaultMode } from '../types/navigation';
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Navigation state
|
// Navigation state
|
||||||
interface NavigationState {
|
interface NavigationContextState {
|
||||||
currentMode: ModeType;
|
workbench: WorkbenchType;
|
||||||
|
selectedTool: ToolId | null;
|
||||||
hasUnsavedChanges: boolean;
|
hasUnsavedChanges: boolean;
|
||||||
pendingNavigation: (() => void) | null;
|
pendingNavigation: (() => void) | null;
|
||||||
showNavigationWarning: boolean;
|
showNavigationWarning: boolean;
|
||||||
selectedToolKey: string | null; // Add tool selection to navigation state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigation actions
|
// Navigation actions
|
||||||
type NavigationAction =
|
type NavigationAction =
|
||||||
| { type: 'SET_MODE'; payload: { mode: ModeType } }
|
| { type: 'SET_WORKBENCH'; payload: { workbench: WorkbenchType } }
|
||||||
|
| { type: 'SET_SELECTED_TOOL'; payload: { toolId: ToolId | null } }
|
||||||
|
| { type: 'SET_TOOL_AND_WORKBENCH'; payload: { toolId: ToolId | null; workbench: WorkbenchType } }
|
||||||
| { type: 'SET_UNSAVED_CHANGES'; payload: { hasChanges: boolean } }
|
| { type: 'SET_UNSAVED_CHANGES'; payload: { hasChanges: boolean } }
|
||||||
| { type: 'SET_PENDING_NAVIGATION'; payload: { navigationFn: (() => void) | null } }
|
| { type: 'SET_PENDING_NAVIGATION'; payload: { navigationFn: (() => void) | null } }
|
||||||
| { type: 'SHOW_NAVIGATION_WARNING'; payload: { show: boolean } }
|
| { type: 'SHOW_NAVIGATION_WARNING'; payload: { show: boolean } };
|
||||||
| { type: 'SET_SELECTED_TOOL'; payload: { toolKey: string | null } };
|
|
||||||
|
|
||||||
// Navigation reducer
|
// Navigation reducer
|
||||||
const navigationReducer = (state: NavigationState, action: NavigationAction): NavigationState => {
|
const navigationReducer = (state: NavigationContextState, action: NavigationAction): NavigationContextState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'SET_MODE':
|
case 'SET_WORKBENCH':
|
||||||
return { ...state, currentMode: action.payload.mode };
|
return { ...state, workbench: action.payload.workbench };
|
||||||
|
|
||||||
|
case 'SET_SELECTED_TOOL':
|
||||||
|
return { ...state, selectedTool: action.payload.toolId };
|
||||||
|
|
||||||
|
case 'SET_TOOL_AND_WORKBENCH':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selectedTool: action.payload.toolId,
|
||||||
|
workbench: action.payload.workbench
|
||||||
|
};
|
||||||
|
|
||||||
case 'SET_UNSAVED_CHANGES':
|
case 'SET_UNSAVED_CHANGES':
|
||||||
return { ...state, hasUnsavedChanges: action.payload.hasChanges };
|
return { ...state, hasUnsavedChanges: action.payload.hasChanges };
|
||||||
@ -42,43 +54,41 @@ const navigationReducer = (state: NavigationState, action: NavigationAction): Na
|
|||||||
case 'SHOW_NAVIGATION_WARNING':
|
case 'SHOW_NAVIGATION_WARNING':
|
||||||
return { ...state, showNavigationWarning: action.payload.show };
|
return { ...state, showNavigationWarning: action.payload.show };
|
||||||
|
|
||||||
case 'SET_SELECTED_TOOL':
|
|
||||||
return { ...state, selectedToolKey: action.payload.toolKey };
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initial state
|
// Initial state
|
||||||
const initialState: NavigationState = {
|
const initialState: NavigationContextState = {
|
||||||
currentMode: getDefaultMode(),
|
workbench: getDefaultWorkbench(),
|
||||||
|
selectedTool: null,
|
||||||
hasUnsavedChanges: false,
|
hasUnsavedChanges: false,
|
||||||
pendingNavigation: null,
|
pendingNavigation: null,
|
||||||
showNavigationWarning: false,
|
showNavigationWarning: false
|
||||||
selectedToolKey: null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Navigation context actions interface
|
// Navigation context actions interface
|
||||||
export interface NavigationContextActions {
|
export interface NavigationContextActions {
|
||||||
setMode: (mode: ModeType) => void;
|
setWorkbench: (workbench: WorkbenchType) => void;
|
||||||
|
setSelectedTool: (toolId: ToolId | null) => void;
|
||||||
|
setToolAndWorkbench: (toolId: ToolId | null, workbench: WorkbenchType) => void;
|
||||||
setHasUnsavedChanges: (hasChanges: boolean) => void;
|
setHasUnsavedChanges: (hasChanges: boolean) => void;
|
||||||
showNavigationWarning: (show: boolean) => void;
|
showNavigationWarning: (show: boolean) => void;
|
||||||
requestNavigation: (navigationFn: () => void) => void;
|
requestNavigation: (navigationFn: () => void) => void;
|
||||||
confirmNavigation: () => void;
|
confirmNavigation: () => void;
|
||||||
cancelNavigation: () => void;
|
cancelNavigation: () => void;
|
||||||
selectTool: (toolKey: string) => void;
|
|
||||||
clearToolSelection: () => void;
|
clearToolSelection: () => void;
|
||||||
handleToolSelect: (toolId: string) => void;
|
handleToolSelect: (toolId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split context values
|
// Context state values
|
||||||
export interface NavigationContextStateValue {
|
export interface NavigationContextStateValue {
|
||||||
currentMode: ModeType;
|
workbench: WorkbenchType;
|
||||||
|
selectedTool: ToolId | null;
|
||||||
hasUnsavedChanges: boolean;
|
hasUnsavedChanges: boolean;
|
||||||
pendingNavigation: (() => void) | null;
|
pendingNavigation: (() => void) | null;
|
||||||
showNavigationWarning: boolean;
|
showNavigationWarning: boolean;
|
||||||
selectedToolKey: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NavigationContextActionsValue {
|
export interface NavigationContextActionsValue {
|
||||||
@ -95,10 +105,19 @@ export const NavigationProvider: React.FC<{
|
|||||||
enableUrlSync?: boolean;
|
enableUrlSync?: boolean;
|
||||||
}> = ({ children, enableUrlSync = true }) => {
|
}> = ({ children, enableUrlSync = true }) => {
|
||||||
const [state, dispatch] = useReducer(navigationReducer, initialState);
|
const [state, dispatch] = useReducer(navigationReducer, initialState);
|
||||||
|
const toolRegistry = useFlatToolRegistry();
|
||||||
|
|
||||||
const actions: NavigationContextActions = {
|
const actions: NavigationContextActions = {
|
||||||
setMode: useCallback((mode: ModeType) => {
|
setWorkbench: useCallback((workbench: WorkbenchType) => {
|
||||||
dispatch({ type: 'SET_MODE', payload: { mode } });
|
dispatch({ type: 'SET_WORKBENCH', payload: { workbench } });
|
||||||
|
}, []),
|
||||||
|
|
||||||
|
setSelectedTool: useCallback((toolId: ToolId | null) => {
|
||||||
|
dispatch({ type: 'SET_SELECTED_TOOL', payload: { toolId } });
|
||||||
|
}, []),
|
||||||
|
|
||||||
|
setToolAndWorkbench: useCallback((toolId: ToolId | null, workbench: WorkbenchType) => {
|
||||||
|
dispatch({ type: 'SET_TOOL_AND_WORKBENCH', payload: { toolId, workbench } });
|
||||||
}, []),
|
}, []),
|
||||||
|
|
||||||
setHasUnsavedChanges: useCallback((hasChanges: boolean) => {
|
setHasUnsavedChanges: useCallback((hasChanges: boolean) => {
|
||||||
@ -110,75 +129,67 @@ export const NavigationProvider: React.FC<{
|
|||||||
}, []),
|
}, []),
|
||||||
|
|
||||||
requestNavigation: useCallback((navigationFn: () => void) => {
|
requestNavigation: useCallback((navigationFn: () => void) => {
|
||||||
// If no unsaved changes, navigate immediately
|
|
||||||
if (!state.hasUnsavedChanges) {
|
if (!state.hasUnsavedChanges) {
|
||||||
navigationFn();
|
navigationFn();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, store the navigation and show warning
|
|
||||||
dispatch({ type: 'SET_PENDING_NAVIGATION', payload: { navigationFn } });
|
dispatch({ type: 'SET_PENDING_NAVIGATION', payload: { navigationFn } });
|
||||||
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show: true } });
|
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show: true } });
|
||||||
}, [state.hasUnsavedChanges]),
|
}, [state.hasUnsavedChanges]),
|
||||||
|
|
||||||
confirmNavigation: useCallback(() => {
|
confirmNavigation: useCallback(() => {
|
||||||
// Execute pending navigation
|
|
||||||
if (state.pendingNavigation) {
|
if (state.pendingNavigation) {
|
||||||
state.pendingNavigation();
|
state.pendingNavigation();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear navigation state
|
|
||||||
dispatch({ type: 'SET_PENDING_NAVIGATION', payload: { navigationFn: null } });
|
dispatch({ type: 'SET_PENDING_NAVIGATION', payload: { navigationFn: null } });
|
||||||
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show: false } });
|
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show: false } });
|
||||||
}, [state.pendingNavigation]),
|
}, [state.pendingNavigation]),
|
||||||
|
|
||||||
cancelNavigation: useCallback(() => {
|
cancelNavigation: useCallback(() => {
|
||||||
// Clear navigation without executing
|
|
||||||
dispatch({ type: 'SET_PENDING_NAVIGATION', payload: { navigationFn: null } });
|
dispatch({ type: 'SET_PENDING_NAVIGATION', payload: { navigationFn: null } });
|
||||||
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show: false } });
|
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show: false } });
|
||||||
}, []),
|
}, []),
|
||||||
|
|
||||||
selectTool: useCallback((toolKey: string) => {
|
|
||||||
dispatch({ type: 'SET_SELECTED_TOOL', payload: { toolKey } });
|
|
||||||
}, []),
|
|
||||||
|
|
||||||
clearToolSelection: useCallback(() => {
|
clearToolSelection: useCallback(() => {
|
||||||
dispatch({ type: 'SET_SELECTED_TOOL', payload: { toolKey: null } });
|
dispatch({ type: 'SET_TOOL_AND_WORKBENCH', payload: { toolId: null, workbench: getDefaultWorkbench() } });
|
||||||
}, []),
|
}, []),
|
||||||
|
|
||||||
handleToolSelect: useCallback((toolId: string) => {
|
handleToolSelect: useCallback((toolId: string) => {
|
||||||
// Handle special cases
|
|
||||||
if (toolId === 'allTools') {
|
if (toolId === 'allTools') {
|
||||||
dispatch({ type: 'SET_SELECTED_TOOL', payload: { toolKey: null } });
|
dispatch({ type: 'SET_TOOL_AND_WORKBENCH', payload: { toolId: null, workbench: getDefaultWorkbench() } });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special-case: if tool is a dedicated reader tool, enter reader mode
|
|
||||||
if (toolId === 'read' || toolId === 'view-pdf') {
|
if (toolId === 'read' || toolId === 'view-pdf') {
|
||||||
dispatch({ type: 'SET_SELECTED_TOOL', payload: { toolKey: null } });
|
dispatch({ type: 'SET_TOOL_AND_WORKBENCH', payload: { toolId: null, workbench: 'viewer' } });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({ type: 'SET_SELECTED_TOOL', payload: { toolKey: toolId } });
|
// Look up the tool in the registry to get its proper workbench
|
||||||
dispatch({ type: 'SET_MODE', payload: { mode: 'fileEditor' as ModeType } });
|
|
||||||
}, [])
|
const tool = isValidToolId(toolId)? toolRegistry[toolId] : null;
|
||||||
|
const workbench = tool ? (tool.workbench || getDefaultWorkbench()) : getDefaultWorkbench();
|
||||||
|
|
||||||
|
// Validate toolId and convert to ToolId type
|
||||||
|
const validToolId = isValidToolId(toolId) ? toolId : null;
|
||||||
|
dispatch({ type: 'SET_TOOL_AND_WORKBENCH', payload: { toolId: validToolId, workbench } });
|
||||||
|
}, [toolRegistry])
|
||||||
};
|
};
|
||||||
|
|
||||||
const stateValue: NavigationContextStateValue = {
|
const stateValue: NavigationContextStateValue = {
|
||||||
currentMode: state.currentMode,
|
workbench: state.workbench,
|
||||||
|
selectedTool: state.selectedTool,
|
||||||
hasUnsavedChanges: state.hasUnsavedChanges,
|
hasUnsavedChanges: state.hasUnsavedChanges,
|
||||||
pendingNavigation: state.pendingNavigation,
|
pendingNavigation: state.pendingNavigation,
|
||||||
showNavigationWarning: state.showNavigationWarning,
|
showNavigationWarning: state.showNavigationWarning
|
||||||
selectedToolKey: state.selectedToolKey
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const actionsValue: NavigationContextActionsValue = {
|
const actionsValue: NavigationContextActionsValue = {
|
||||||
actions
|
actions
|
||||||
};
|
};
|
||||||
|
|
||||||
// Enable URL synchronization
|
|
||||||
useNavigationUrlSync(state.currentMode, actions.setMode, enableUrlSync);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationStateContext.Provider value={stateValue}>
|
<NavigationStateContext.Provider value={stateValue}>
|
||||||
<NavigationActionsContext.Provider value={actionsValue}>
|
<NavigationActionsContext.Provider value={actionsValue}>
|
||||||
@ -228,13 +239,3 @@ export const useNavigationGuard = () => {
|
|||||||
setShowNavigationWarning: actions.showNavigationWarning
|
setShowNavigationWarning: actions.showNavigationWarning
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Re-export utility functions from types for backward compatibility
|
|
||||||
export { isValidMode, getDefaultMode, type ModeType } from '../types/navigation';
|
|
||||||
|
|
||||||
// TODO: This will be expanded for URL-based routing system
|
|
||||||
// - URL parsing utilities
|
|
||||||
// - Route definitions
|
|
||||||
// - Navigation hooks with URL sync
|
|
||||||
// - History management
|
|
||||||
// - Breadcrumb restoration from URL params
|
|
@ -6,9 +6,11 @@
|
|||||||
import React, { createContext, useContext, useReducer, useCallback, useMemo } from 'react';
|
import React, { createContext, useContext, useReducer, useCallback, useMemo } from 'react';
|
||||||
import { useToolManagement } from '../hooks/useToolManagement';
|
import { useToolManagement } from '../hooks/useToolManagement';
|
||||||
import { PageEditorFunctions } from '../types/pageEditor';
|
import { PageEditorFunctions } from '../types/pageEditor';
|
||||||
import { ToolRegistryEntry } from '../data/toolsTaxonomy';
|
import { ToolRegistryEntry, ToolRegistry } from '../data/toolsTaxonomy';
|
||||||
import { useToolWorkflowUrlSync } from '../hooks/useUrlSync';
|
|
||||||
import { useNavigationActions, useNavigationState } from './NavigationContext';
|
import { useNavigationActions, useNavigationState } from './NavigationContext';
|
||||||
|
import { ToolId, isValidToolId } from '../types/toolId';
|
||||||
|
import { useNavigationUrlSync } from '../hooks/useUrlSync';
|
||||||
|
import { getDefaultWorkbench } from '../types/workbench';
|
||||||
|
|
||||||
// State interface
|
// State interface
|
||||||
interface ToolWorkflowState {
|
interface ToolWorkflowState {
|
||||||
@ -83,7 +85,7 @@ interface ToolWorkflowContextValue extends ToolWorkflowState {
|
|||||||
setSearchQuery: (query: string) => void;
|
setSearchQuery: (query: string) => void;
|
||||||
|
|
||||||
// Tool Actions
|
// Tool Actions
|
||||||
selectTool: (toolId: string) => void;
|
selectTool: (toolId: ToolId | null) => void;
|
||||||
clearToolSelection: () => void;
|
clearToolSelection: () => void;
|
||||||
|
|
||||||
// Tool Reset Actions
|
// Tool Reset Actions
|
||||||
@ -124,7 +126,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
|||||||
} = useToolManagement();
|
} = useToolManagement();
|
||||||
|
|
||||||
// Get selected tool from navigation context
|
// Get selected tool from navigation context
|
||||||
const selectedTool = getSelectedTool(navigationState.selectedToolKey);
|
const selectedTool = getSelectedTool(navigationState.selectedTool);
|
||||||
|
|
||||||
// UI Action creators
|
// UI Action creators
|
||||||
const setSidebarsVisible = useCallback((visible: boolean) => {
|
const setSidebarsVisible = useCallback((visible: boolean) => {
|
||||||
@ -142,7 +144,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
|||||||
const setPreviewFile = useCallback((file: File | null) => {
|
const setPreviewFile = useCallback((file: File | null) => {
|
||||||
dispatch({ type: 'SET_PREVIEW_FILE', payload: file });
|
dispatch({ type: 'SET_PREVIEW_FILE', payload: file });
|
||||||
if (file) {
|
if (file) {
|
||||||
actions.setMode('viewer');
|
actions.setWorkbench('viewer');
|
||||||
}
|
}
|
||||||
}, [actions]);
|
}, [actions]);
|
||||||
|
|
||||||
@ -172,7 +174,17 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
|||||||
|
|
||||||
// Workflow actions (compound actions that coordinate multiple state changes)
|
// Workflow actions (compound actions that coordinate multiple state changes)
|
||||||
const handleToolSelect = useCallback((toolId: string) => {
|
const handleToolSelect = useCallback((toolId: string) => {
|
||||||
actions.handleToolSelect(toolId);
|
// Set the selected tool and determine the appropriate workbench
|
||||||
|
const validToolId = isValidToolId(toolId) ? toolId : null;
|
||||||
|
actions.setSelectedTool(validToolId);
|
||||||
|
|
||||||
|
// Get the tool from registry to determine workbench
|
||||||
|
const tool = getSelectedTool(toolId);
|
||||||
|
if (tool && tool.workbench) {
|
||||||
|
actions.setWorkbench(tool.workbench);
|
||||||
|
} else {
|
||||||
|
actions.setWorkbench(getDefaultWorkbench());
|
||||||
|
}
|
||||||
|
|
||||||
// Clear search query when selecting a tool
|
// Clear search query when selecting a tool
|
||||||
setSearchQuery('');
|
setSearchQuery('');
|
||||||
@ -189,13 +201,13 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
|||||||
setLeftPanelView('toolContent');
|
setLeftPanelView('toolContent');
|
||||||
setReaderMode(false); // Disable read mode when selecting tools
|
setReaderMode(false); // Disable read mode when selecting tools
|
||||||
}
|
}
|
||||||
}, [actions, setLeftPanelView, setReaderMode, setSearchQuery]);
|
}, [actions, getSelectedTool, setLeftPanelView, setReaderMode, setSearchQuery]);
|
||||||
|
|
||||||
const handleBackToTools = useCallback(() => {
|
const handleBackToTools = useCallback(() => {
|
||||||
setLeftPanelView('toolPicker');
|
setLeftPanelView('toolPicker');
|
||||||
setReaderMode(false);
|
setReaderMode(false);
|
||||||
actions.clearToolSelection();
|
actions.setSelectedTool(null);
|
||||||
}, [setLeftPanelView, setReaderMode, actions]);
|
}, [setLeftPanelView, setReaderMode, actions.setSelectedTool]);
|
||||||
|
|
||||||
const handleReaderToggle = useCallback(() => {
|
const handleReaderToggle = useCallback(() => {
|
||||||
setReaderMode(true);
|
setReaderMode(true);
|
||||||
@ -214,14 +226,20 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
|||||||
[state.sidebarsVisible, state.readerMode]
|
[state.sidebarsVisible, state.readerMode]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Enable URL synchronization for tool selection
|
// URL sync for proper tool navigation
|
||||||
useToolWorkflowUrlSync(navigationState.selectedToolKey, actions.selectTool, actions.clearToolSelection, true);
|
useNavigationUrlSync(
|
||||||
|
navigationState.selectedTool,
|
||||||
|
handleToolSelect,
|
||||||
|
handleBackToTools,
|
||||||
|
toolRegistry as ToolRegistry,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
// Properly memoized context value
|
// Properly memoized context value
|
||||||
const contextValue = useMemo((): ToolWorkflowContextValue => ({
|
const contextValue = useMemo((): ToolWorkflowContextValue => ({
|
||||||
// State
|
// State
|
||||||
...state,
|
...state,
|
||||||
selectedToolKey: navigationState.selectedToolKey,
|
selectedToolKey: navigationState.selectedTool,
|
||||||
selectedTool,
|
selectedTool,
|
||||||
toolRegistry,
|
toolRegistry,
|
||||||
|
|
||||||
@ -232,8 +250,8 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
|||||||
setPreviewFile,
|
setPreviewFile,
|
||||||
setPageEditorFunctions,
|
setPageEditorFunctions,
|
||||||
setSearchQuery,
|
setSearchQuery,
|
||||||
selectTool: actions.selectTool,
|
selectTool: actions.setSelectedTool,
|
||||||
clearToolSelection: actions.clearToolSelection,
|
clearToolSelection: () => actions.setSelectedTool(null),
|
||||||
|
|
||||||
// Tool Reset Actions
|
// Tool Reset Actions
|
||||||
registerToolReset,
|
registerToolReset,
|
||||||
@ -249,7 +267,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
|||||||
isPanelVisible,
|
isPanelVisible,
|
||||||
}), [
|
}), [
|
||||||
state,
|
state,
|
||||||
navigationState.selectedToolKey,
|
navigationState.selectedTool,
|
||||||
selectedTool,
|
selectedTool,
|
||||||
toolRegistry,
|
toolRegistry,
|
||||||
setSidebarsVisible,
|
setSidebarsVisible,
|
||||||
@ -258,8 +276,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
|||||||
setPreviewFile,
|
setPreviewFile,
|
||||||
setPageEditorFunctions,
|
setPageEditorFunctions,
|
||||||
setSearchQuery,
|
setSearchQuery,
|
||||||
actions.selectTool,
|
actions.setSelectedTool,
|
||||||
actions.clearToolSelection,
|
|
||||||
registerToolReset,
|
registerToolReset,
|
||||||
resetTool,
|
resetTool,
|
||||||
handleToolSelect,
|
handleToolSelect,
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { type TFunction } from 'i18next';
|
import { type TFunction } from 'i18next';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ToolOperationHook, ToolOperationConfig } from '../hooks/tools/shared/useToolOperation';
|
import { ToolOperationConfig } from '../hooks/tools/shared/useToolOperation';
|
||||||
import { BaseToolProps } from '../types/tool';
|
import { BaseToolProps } from '../types/tool';
|
||||||
import { BaseParameters } from '../types/parameters';
|
import { WorkbenchType } from '../types/workbench';
|
||||||
|
import { ToolId } from '../types/toolId';
|
||||||
|
|
||||||
export enum SubcategoryId {
|
export enum SubcategoryId {
|
||||||
SIGNING = 'signing',
|
SIGNING = 'signing',
|
||||||
@ -28,7 +29,6 @@ export type ToolRegistryEntry = {
|
|||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
name: string;
|
name: string;
|
||||||
component: React.ComponentType<BaseToolProps> | null;
|
component: React.ComponentType<BaseToolProps> | null;
|
||||||
view: 'sign' | 'security' | 'format' | 'extract' | 'view' | 'merge' | 'pageEditor' | 'convert' | 'redact' | 'split' | 'convert' | 'remove' | 'compress' | 'external';
|
|
||||||
description: string;
|
description: string;
|
||||||
categoryId: ToolCategoryId;
|
categoryId: ToolCategoryId;
|
||||||
subcategoryId: SubcategoryId;
|
subcategoryId: SubcategoryId;
|
||||||
@ -37,13 +37,17 @@ export type ToolRegistryEntry = {
|
|||||||
endpoints?: string[];
|
endpoints?: string[];
|
||||||
link?: string;
|
link?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
|
// URL path for routing (e.g., '/split-pdfs', '/compress-pdf')
|
||||||
|
urlPath?: string;
|
||||||
|
// Workbench type for navigation
|
||||||
|
workbench?: WorkbenchType;
|
||||||
// Operation configuration for automation
|
// Operation configuration for automation
|
||||||
operationConfig?: ToolOperationConfig<any>;
|
operationConfig?: ToolOperationConfig<any>;
|
||||||
// Settings component for automation configuration
|
// Settings component for automation configuration
|
||||||
settingsComponent?: React.ComponentType<any>;
|
settingsComponent?: React.ComponentType<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToolRegistry = Record<string /* FIX ME: Should be ToolId */, ToolRegistryEntry>;
|
export type ToolRegistry = Record<ToolId, ToolRegistryEntry>;
|
||||||
|
|
||||||
export const SUBCATEGORY_ORDER: SubcategoryId[] = [
|
export const SUBCATEGORY_ORDER: SubcategoryId[] = [
|
||||||
SubcategoryId.SIGNING,
|
SubcategoryId.SIGNING,
|
||||||
@ -107,3 +111,30 @@ export const getAllApplicationEndpoints = (
|
|||||||
const convEp = extensionToEndpoint ? getConversionEndpoints(extensionToEndpoint) : [];
|
const convEp = extensionToEndpoint ? getConversionEndpoints(extensionToEndpoint) : [];
|
||||||
return Array.from(new Set([...toolEp, ...convEp]));
|
return Array.from(new Set([...toolEp, ...convEp]));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default workbench for tools that don't specify one
|
||||||
|
* Returns null to trigger the default case in Workbench component (ToolRenderer)
|
||||||
|
*/
|
||||||
|
export const getDefaultToolWorkbench = (): WorkbenchType => 'fileEditor';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get workbench type for a tool
|
||||||
|
*/
|
||||||
|
export const getToolWorkbench = (tool: ToolRegistryEntry): WorkbenchType => {
|
||||||
|
return tool.workbench || getDefaultToolWorkbench();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get URL path for a tool
|
||||||
|
*/
|
||||||
|
export const getToolUrlPath = (toolId: string, tool: ToolRegistryEntry): string => {
|
||||||
|
return tool.urlPath || `/${toolId.replace(/([A-Z])/g, '-$1').toLowerCase()}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a tool ID exists in the registry
|
||||||
|
*/
|
||||||
|
export const isValidToolId = (toolId: string, registry: ToolRegistry): boolean => {
|
||||||
|
return toolId in registry;
|
||||||
|
};
|
||||||
|
@ -1,66 +1,128 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from "react";
|
||||||
import LocalIcon from '../components/shared/LocalIcon';
|
import LocalIcon from "../components/shared/LocalIcon";
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from "react-i18next";
|
||||||
import SplitPdfPanel from "../tools/Split";
|
import SplitPdfPanel from "../tools/Split";
|
||||||
import CompressPdfPanel from "../tools/Compress";
|
import CompressPdfPanel from "../tools/Compress";
|
||||||
import OCRPanel from '../tools/OCR';
|
import OCRPanel from "../tools/OCR";
|
||||||
import ConvertPanel from '../tools/Convert';
|
import ConvertPanel from "../tools/Convert";
|
||||||
import Sanitize from '../tools/Sanitize';
|
import Sanitize from "../tools/Sanitize";
|
||||||
import AddPassword from '../tools/AddPassword';
|
import AddPassword from "../tools/AddPassword";
|
||||||
import ChangePermissions from '../tools/ChangePermissions';
|
import ChangePermissions from "../tools/ChangePermissions";
|
||||||
import RemovePassword from '../tools/RemovePassword';
|
import RemovePassword from "../tools/RemovePassword";
|
||||||
import { SubcategoryId, ToolCategoryId, ToolRegistry } from './toolsTaxonomy';
|
import { SubcategoryId, ToolCategoryId, ToolRegistry } from "./toolsTaxonomy";
|
||||||
import AddWatermark from '../tools/AddWatermark';
|
import AddWatermark from "../tools/AddWatermark";
|
||||||
import Repair from '../tools/Repair';
|
import Repair from "../tools/Repair";
|
||||||
import SingleLargePage from '../tools/SingleLargePage';
|
import SingleLargePage from "../tools/SingleLargePage";
|
||||||
import UnlockPdfForms from '../tools/UnlockPdfForms';
|
import UnlockPdfForms from "../tools/UnlockPdfForms";
|
||||||
import RemoveCertificateSign from '../tools/RemoveCertificateSign';
|
import RemoveCertificateSign from "../tools/RemoveCertificateSign";
|
||||||
import { compressOperationConfig } from '../hooks/tools/compress/useCompressOperation';
|
import { compressOperationConfig } from "../hooks/tools/compress/useCompressOperation";
|
||||||
import { splitOperationConfig } from '../hooks/tools/split/useSplitOperation';
|
import { splitOperationConfig } from "../hooks/tools/split/useSplitOperation";
|
||||||
import { addPasswordOperationConfig } from '../hooks/tools/addPassword/useAddPasswordOperation';
|
import { addPasswordOperationConfig } from "../hooks/tools/addPassword/useAddPasswordOperation";
|
||||||
import { removePasswordOperationConfig } from '../hooks/tools/removePassword/useRemovePasswordOperation';
|
import { removePasswordOperationConfig } from "../hooks/tools/removePassword/useRemovePasswordOperation";
|
||||||
import { sanitizeOperationConfig } from '../hooks/tools/sanitize/useSanitizeOperation';
|
import { sanitizeOperationConfig } from "../hooks/tools/sanitize/useSanitizeOperation";
|
||||||
import { repairOperationConfig } from '../hooks/tools/repair/useRepairOperation';
|
import { repairOperationConfig } from "../hooks/tools/repair/useRepairOperation";
|
||||||
import { addWatermarkOperationConfig } from '../hooks/tools/addWatermark/useAddWatermarkOperation';
|
import { addWatermarkOperationConfig } from "../hooks/tools/addWatermark/useAddWatermarkOperation";
|
||||||
import { unlockPdfFormsOperationConfig } from '../hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation';
|
import { unlockPdfFormsOperationConfig } from "../hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation";
|
||||||
import { singleLargePageOperationConfig } from '../hooks/tools/singleLargePage/useSingleLargePageOperation';
|
import { singleLargePageOperationConfig } from "../hooks/tools/singleLargePage/useSingleLargePageOperation";
|
||||||
import { ocrOperationConfig } from '../hooks/tools/ocr/useOCROperation';
|
import { ocrOperationConfig } from "../hooks/tools/ocr/useOCROperation";
|
||||||
import { convertOperationConfig } from '../hooks/tools/convert/useConvertOperation';
|
import { convertOperationConfig } from "../hooks/tools/convert/useConvertOperation";
|
||||||
import { removeCertificateSignOperationConfig } from '../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation';
|
import { removeCertificateSignOperationConfig } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation";
|
||||||
import { changePermissionsOperationConfig } from '../hooks/tools/changePermissions/useChangePermissionsOperation';
|
import { changePermissionsOperationConfig } from "../hooks/tools/changePermissions/useChangePermissionsOperation";
|
||||||
import CompressSettings from '../components/tools/compress/CompressSettings';
|
import CompressSettings from "../components/tools/compress/CompressSettings";
|
||||||
import SplitSettings from '../components/tools/split/SplitSettings';
|
import SplitSettings from "../components/tools/split/SplitSettings";
|
||||||
import AddPasswordSettings from '../components/tools/addPassword/AddPasswordSettings';
|
import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings";
|
||||||
import RemovePasswordSettings from '../components/tools/removePassword/RemovePasswordSettings';
|
import RemovePasswordSettings from "../components/tools/removePassword/RemovePasswordSettings";
|
||||||
import SanitizeSettings from '../components/tools/sanitize/SanitizeSettings';
|
import SanitizeSettings from "../components/tools/sanitize/SanitizeSettings";
|
||||||
import RepairSettings from '../components/tools/repair/RepairSettings';
|
import RepairSettings from "../components/tools/repair/RepairSettings";
|
||||||
import UnlockPdfFormsSettings from '../components/tools/unlockPdfForms/UnlockPdfFormsSettings';
|
import UnlockPdfFormsSettings from "../components/tools/unlockPdfForms/UnlockPdfFormsSettings";
|
||||||
import AddWatermarkSingleStepSettings from '../components/tools/addWatermark/AddWatermarkSingleStepSettings';
|
import AddWatermarkSingleStepSettings from "../components/tools/addWatermark/AddWatermarkSingleStepSettings";
|
||||||
import OCRSettings from '../components/tools/ocr/OCRSettings';
|
import OCRSettings from "../components/tools/ocr/OCRSettings";
|
||||||
import ConvertSettings from '../components/tools/convert/ConvertSettings';
|
import ConvertSettings from "../components/tools/convert/ConvertSettings";
|
||||||
import ChangePermissionsSettings from '../components/tools/changePermissions/ChangePermissionsSettings';
|
import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings";
|
||||||
|
import { ToolId } from "../types/toolId";
|
||||||
|
|
||||||
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
|
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
|
||||||
|
|
||||||
// Convert tool supported file formats
|
// Convert tool supported file formats
|
||||||
export const CONVERT_SUPPORTED_FORMATS = [
|
export const CONVERT_SUPPORTED_FORMATS = [
|
||||||
// Microsoft Office
|
// Microsoft Office
|
||||||
"doc", "docx", "dot", "dotx", "csv", "xls", "xlsx", "xlt", "xltx", "slk", "dif", "ppt", "pptx",
|
"doc",
|
||||||
|
"docx",
|
||||||
|
"dot",
|
||||||
|
"dotx",
|
||||||
|
"csv",
|
||||||
|
"xls",
|
||||||
|
"xlsx",
|
||||||
|
"xlt",
|
||||||
|
"xltx",
|
||||||
|
"slk",
|
||||||
|
"dif",
|
||||||
|
"ppt",
|
||||||
|
"pptx",
|
||||||
// OpenDocument
|
// OpenDocument
|
||||||
"odt", "ott", "ods", "ots", "odp", "otp", "odg", "otg",
|
"odt",
|
||||||
|
"ott",
|
||||||
|
"ods",
|
||||||
|
"ots",
|
||||||
|
"odp",
|
||||||
|
"otp",
|
||||||
|
"odg",
|
||||||
|
"otg",
|
||||||
// Text formats
|
// Text formats
|
||||||
"txt", "text", "xml", "rtf", "html", "lwp", "md",
|
"txt",
|
||||||
|
"text",
|
||||||
|
"xml",
|
||||||
|
"rtf",
|
||||||
|
"html",
|
||||||
|
"lwp",
|
||||||
|
"md",
|
||||||
// Images
|
// Images
|
||||||
"bmp", "gif", "jpeg", "jpg", "png", "tif", "tiff", "pbm", "pgm", "ppm", "ras", "xbm", "xpm", "svg", "svm", "wmf", "webp",
|
"bmp",
|
||||||
|
"gif",
|
||||||
|
"jpeg",
|
||||||
|
"jpg",
|
||||||
|
"png",
|
||||||
|
"tif",
|
||||||
|
"tiff",
|
||||||
|
"pbm",
|
||||||
|
"pgm",
|
||||||
|
"ppm",
|
||||||
|
"ras",
|
||||||
|
"xbm",
|
||||||
|
"xpm",
|
||||||
|
"svg",
|
||||||
|
"svm",
|
||||||
|
"wmf",
|
||||||
|
"webp",
|
||||||
// StarOffice
|
// StarOffice
|
||||||
"sda", "sdc", "sdd", "sdw", "stc", "std", "sti", "stw", "sxd", "sxg", "sxi", "sxw",
|
"sda",
|
||||||
|
"sdc",
|
||||||
|
"sdd",
|
||||||
|
"sdw",
|
||||||
|
"stc",
|
||||||
|
"std",
|
||||||
|
"sti",
|
||||||
|
"stw",
|
||||||
|
"sxd",
|
||||||
|
"sxg",
|
||||||
|
"sxi",
|
||||||
|
"sxw",
|
||||||
// Email formats
|
// Email formats
|
||||||
"eml",
|
"eml",
|
||||||
// Archive formats
|
// Archive formats
|
||||||
"zip",
|
"zip",
|
||||||
// Other
|
// Other
|
||||||
"dbf", "fods", "vsd", "vor", "vor3", "vor4", "uop", "pct", "ps", "pdf"
|
"dbf",
|
||||||
];
|
"fods",
|
||||||
|
"vsd",
|
||||||
|
"vor",
|
||||||
|
"vor3",
|
||||||
|
"vor4",
|
||||||
|
"uop",
|
||||||
|
"pct",
|
||||||
|
"ps",
|
||||||
|
"pdf",
|
||||||
|
];
|
||||||
|
|
||||||
// Hook to get the translated tool registry
|
// Hook to get the translated tool registry
|
||||||
export function useFlatToolRegistry(): ToolRegistry {
|
export function useFlatToolRegistry(): ToolRegistry {
|
||||||
@ -70,119 +132,111 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
const allTools: ToolRegistry = {
|
const allTools: ToolRegistry = {
|
||||||
// Signing
|
// Signing
|
||||||
|
|
||||||
"certSign": {
|
certSign: {
|
||||||
icon: <LocalIcon icon="workspace-premium-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="workspace-premium-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.certSign.title", "Sign with Certificate"),
|
name: t("home.certSign.title", "Sign with Certificate"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "sign",
|
|
||||||
description: t("home.certSign.desc", "Signs a PDF with a Certificate/Key (PEM/P12)"),
|
description: t("home.certSign.desc", "Signs a PDF with a Certificate/Key (PEM/P12)"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.SIGNING
|
subcategoryId: SubcategoryId.SIGNING,
|
||||||
},
|
},
|
||||||
"sign": {
|
sign: {
|
||||||
icon: <LocalIcon icon="signature-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="signature-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.sign.title", "Sign"),
|
name: t("home.sign.title", "Sign"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "sign",
|
|
||||||
description: t("home.sign.desc", "Adds signature to PDF by drawing, text or image"),
|
description: t("home.sign.desc", "Adds signature to PDF by drawing, text or image"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.SIGNING
|
subcategoryId: SubcategoryId.SIGNING,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// Document Security
|
// Document Security
|
||||||
|
|
||||||
"addPassword": {
|
addPassword: {
|
||||||
icon: <LocalIcon icon="password-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="password-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.addPassword.title", "Add Password"),
|
name: t("home.addPassword.title", "Add Password"),
|
||||||
component: AddPassword,
|
component: AddPassword,
|
||||||
view: "security",
|
|
||||||
description: t("home.addPassword.desc", "Add password protection and restrictions to PDF files"),
|
description: t("home.addPassword.desc", "Add password protection and restrictions to PDF files"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["add-password"],
|
endpoints: ["add-password"],
|
||||||
operationConfig: addPasswordOperationConfig,
|
operationConfig: addPasswordOperationConfig,
|
||||||
settingsComponent: AddPasswordSettings
|
settingsComponent: AddPasswordSettings,
|
||||||
},
|
},
|
||||||
"watermark": {
|
addWatermark: {
|
||||||
icon: <LocalIcon icon="branding-watermark-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="branding-watermark-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.watermark.title", "Add Watermark"),
|
name: t("home.watermark.title", "Add Watermark"),
|
||||||
component: AddWatermark,
|
component: AddWatermark,
|
||||||
view: "format",
|
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
description: t("home.watermark.desc", "Add a custom watermark to your PDF document."),
|
description: t("home.watermark.desc", "Add a custom watermark to your PDF document."),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
endpoints: ["add-watermark"],
|
endpoints: ["add-watermark"],
|
||||||
operationConfig: addWatermarkOperationConfig,
|
operationConfig: addWatermarkOperationConfig,
|
||||||
settingsComponent: AddWatermarkSingleStepSettings
|
settingsComponent: AddWatermarkSingleStepSettings,
|
||||||
},
|
},
|
||||||
"add-stamp": {
|
"add-stamp": {
|
||||||
icon: <LocalIcon icon="approval-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="approval-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.AddStampRequest.title", "Add Stamp to PDF"),
|
name: t("home.AddStampRequest.title", "Add Stamp to PDF"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.AddStampRequest.desc", "Add text or add image stamps at set locations"),
|
description: t("home.AddStampRequest.desc", "Add text or add image stamps at set locations"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
},
|
},
|
||||||
"sanitize": {
|
sanitize: {
|
||||||
icon: <LocalIcon icon="cleaning-services-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="cleaning-services-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.sanitize.title", "Sanitize"),
|
name: t("home.sanitize.title", "Sanitize"),
|
||||||
component: Sanitize,
|
component: Sanitize,
|
||||||
view: "security",
|
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
description: t("home.sanitize.desc", "Remove potentially harmful elements from PDF files"),
|
description: t("home.sanitize.desc", "Remove potentially harmful elements from PDF files"),
|
||||||
endpoints: ["sanitize-pdf"],
|
endpoints: ["sanitize-pdf"],
|
||||||
operationConfig: sanitizeOperationConfig,
|
operationConfig: sanitizeOperationConfig,
|
||||||
settingsComponent: SanitizeSettings
|
settingsComponent: SanitizeSettings,
|
||||||
},
|
},
|
||||||
"flatten": {
|
flatten: {
|
||||||
icon: <LocalIcon icon="layers-clear-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="layers-clear-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.flatten.title", "Flatten"),
|
name: t("home.flatten.title", "Flatten"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.flatten.desc", "Remove all interactive elements and forms from a PDF"),
|
description: t("home.flatten.desc", "Remove all interactive elements and forms from a PDF"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
},
|
},
|
||||||
"unlock-pdf-forms": {
|
"unlock-pdf-forms": {
|
||||||
icon: <LocalIcon icon="preview-off-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="preview-off-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.unlockPDFForms.title", "Unlock PDF Forms"),
|
name: t("home.unlockPDFForms.title", "Unlock PDF Forms"),
|
||||||
component: UnlockPdfForms,
|
component: UnlockPdfForms,
|
||||||
view: "security",
|
|
||||||
description: t("home.unlockPDFForms.desc", "Remove read-only property of form fields in a PDF document."),
|
description: t("home.unlockPDFForms.desc", "Remove read-only property of form fields in a PDF document."),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["unlock-pdf-forms"],
|
endpoints: ["unlock-pdf-forms"],
|
||||||
operationConfig: unlockPdfFormsOperationConfig,
|
operationConfig: unlockPdfFormsOperationConfig,
|
||||||
settingsComponent: UnlockPdfFormsSettings
|
settingsComponent: UnlockPdfFormsSettings,
|
||||||
},
|
},
|
||||||
"manage-certificates": {
|
"manage-certificates": {
|
||||||
icon: <LocalIcon icon="license-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="license-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.manageCertificates.title", "Manage Certificates"),
|
name: t("home.manageCertificates.title", "Manage Certificates"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "security",
|
description: t(
|
||||||
description: t("home.manageCertificates.desc", "Import, export, or delete digital certificate files used for signing PDFs."),
|
"home.manageCertificates.desc",
|
||||||
|
"Import, export, or delete digital certificate files used for signing PDFs."
|
||||||
|
),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
},
|
},
|
||||||
"change-permissions": {
|
"change-permissions": {
|
||||||
icon: <LocalIcon icon="lock-outline" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="lock-outline" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.changePermissions.title", "Change Permissions"),
|
name: t("home.changePermissions.title", "Change Permissions"),
|
||||||
component: ChangePermissions,
|
component: ChangePermissions,
|
||||||
view: "security",
|
|
||||||
description: t("home.changePermissions.desc", "Change document restrictions and permissions"),
|
description: t("home.changePermissions.desc", "Change document restrictions and permissions"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["add-password"],
|
endpoints: ["add-password"],
|
||||||
operationConfig: changePermissionsOperationConfig,
|
operationConfig: changePermissionsOperationConfig,
|
||||||
settingsComponent: ChangePermissionsSettings
|
settingsComponent: ChangePermissionsSettings,
|
||||||
},
|
},
|
||||||
// Verification
|
// Verification
|
||||||
|
|
||||||
@ -190,422 +244,390 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
icon: <LocalIcon icon="fact-check-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="fact-check-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.getPdfInfo.title", "Get ALL Info on PDF"),
|
name: t("home.getPdfInfo.title", "Get ALL Info on PDF"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "extract",
|
|
||||||
description: t("home.getPdfInfo.desc", "Grabs any and all information possible on PDFs"),
|
description: t("home.getPdfInfo.desc", "Grabs any and all information possible on PDFs"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.VERIFICATION
|
subcategoryId: SubcategoryId.VERIFICATION,
|
||||||
},
|
},
|
||||||
"validate-pdf-signature": {
|
"validate-pdf-signature": {
|
||||||
icon: <LocalIcon icon="verified-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="verified-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.validateSignature.title", "Validate PDF Signature"),
|
name: t("home.validateSignature.title", "Validate PDF Signature"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "security",
|
|
||||||
description: t("home.validateSignature.desc", "Verify digital signatures and certificates in PDF documents"),
|
description: t("home.validateSignature.desc", "Verify digital signatures and certificates in PDF documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.VERIFICATION
|
subcategoryId: SubcategoryId.VERIFICATION,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// Document Review
|
// Document Review
|
||||||
|
|
||||||
"read": {
|
read: {
|
||||||
icon: <LocalIcon icon="article-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="article-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.read.title", "Read"),
|
name: t("home.read.title", "Read"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "view",
|
workbench: "viewer",
|
||||||
description: t("home.read.desc", "View and annotate PDFs. Highlight text, draw, or insert comments for review and collaboration."),
|
description: t(
|
||||||
|
"home.read.desc",
|
||||||
|
"View and annotate PDFs. Highlight text, draw, or insert comments for review and collaboration."
|
||||||
|
),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_REVIEW
|
subcategoryId: SubcategoryId.DOCUMENT_REVIEW,
|
||||||
},
|
},
|
||||||
"change-metadata": {
|
"change-metadata": {
|
||||||
icon: <LocalIcon icon="assignment-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="assignment-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.changeMetadata.title", "Change Metadata"),
|
name: t("home.changeMetadata.title", "Change Metadata"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.changeMetadata.desc", "Change/Remove/Add metadata from a PDF document"),
|
description: t("home.changeMetadata.desc", "Change/Remove/Add metadata from a PDF document"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_REVIEW
|
subcategoryId: SubcategoryId.DOCUMENT_REVIEW,
|
||||||
},
|
},
|
||||||
// Page Formatting
|
// Page Formatting
|
||||||
|
|
||||||
"cropPdf": {
|
cropPdf: {
|
||||||
icon: <LocalIcon icon="crop-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="crop-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.crop.title", "Crop PDF"),
|
name: t("home.crop.title", "Crop PDF"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.crop.desc", "Crop a PDF to reduce its size (maintains text!)"),
|
description: t("home.crop.desc", "Crop a PDF to reduce its size (maintains text!)"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
},
|
},
|
||||||
"rotate": {
|
rotate: {
|
||||||
icon: <LocalIcon icon="rotate-right-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="rotate-right-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.rotate.title", "Rotate"),
|
name: t("home.rotate.title", "Rotate"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.rotate.desc", "Easily rotate your PDFs."),
|
description: t("home.rotate.desc", "Easily rotate your PDFs."),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
},
|
},
|
||||||
"splitPdf": {
|
split: {
|
||||||
icon: <LocalIcon icon="content-cut-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="content-cut-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.split.title", "Split"),
|
name: t("home.split.title", "Split"),
|
||||||
component: SplitPdfPanel,
|
component: SplitPdfPanel,
|
||||||
view: "split",
|
|
||||||
description: t("home.split.desc", "Split PDFs into multiple documents"),
|
description: t("home.split.desc", "Split PDFs into multiple documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
operationConfig: splitOperationConfig,
|
operationConfig: splitOperationConfig,
|
||||||
settingsComponent: SplitSettings
|
settingsComponent: SplitSettings,
|
||||||
},
|
},
|
||||||
"reorganize-pages": {
|
"reorganize-pages": {
|
||||||
icon: <LocalIcon icon="move-down-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="move-down-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.reorganizePages.title", "Reorganize Pages"),
|
name: t("home.reorganizePages.title", "Reorganize Pages"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "pageEditor",
|
workbench: "pageEditor",
|
||||||
description: t("home.reorganizePages.desc", "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."),
|
description: t(
|
||||||
|
"home.reorganizePages.desc",
|
||||||
|
"Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
|
||||||
|
),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
},
|
},
|
||||||
"adjust-page-size-scale": {
|
"adjust-page-size-scale": {
|
||||||
icon: <LocalIcon icon="crop-free-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="crop-free-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.scalePages.title", "Adjust page size/scale"),
|
name: t("home.scalePages.title", "Adjust page size/scale"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.scalePages.desc", "Change the size/scale of a page and/or its contents."),
|
description: t("home.scalePages.desc", "Change the size/scale of a page and/or its contents."),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
},
|
},
|
||||||
"addPageNumbers": {
|
addPageNumbers: {
|
||||||
icon: <LocalIcon icon="123-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="123-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.addPageNumbers.title", "Add Page Numbers"),
|
name: t("home.addPageNumbers.title", "Add Page Numbers"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.addPageNumbers.desc", "Add Page numbers throughout a document in a set location"),
|
description: t("home.addPageNumbers.desc", "Add Page numbers throughout a document in a set location"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
},
|
},
|
||||||
"multi-page-layout": {
|
"multi-page-layout": {
|
||||||
icon: <LocalIcon icon="dashboard-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="dashboard-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.pageLayout.title", "Multi-Page Layout"),
|
name: t("home.pageLayout.title", "Multi-Page Layout"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.pageLayout.desc", "Merge multiple pages of a PDF document into a single page"),
|
description: t("home.pageLayout.desc", "Merge multiple pages of a PDF document into a single page"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
},
|
},
|
||||||
"single-large-page": {
|
"single-large-page": {
|
||||||
icon: <LocalIcon icon="looks-one-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="looks-one-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.pdfToSinglePage.title", "PDF to Single Large Page"),
|
name: t("home.pdfToSinglePage.title", "PDF to Single Large Page"),
|
||||||
component: SingleLargePage,
|
component: SingleLargePage,
|
||||||
view: "format",
|
|
||||||
description: t("home.pdfToSinglePage.desc", "Merges all PDF pages into one large single page"),
|
description: t("home.pdfToSinglePage.desc", "Merges all PDF pages into one large single page"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["pdf-to-single-page"],
|
endpoints: ["pdf-to-single-page"],
|
||||||
operationConfig: singleLargePageOperationConfig
|
operationConfig: singleLargePageOperationConfig,
|
||||||
},
|
},
|
||||||
"add-attachments": {
|
"add-attachments": {
|
||||||
icon: <LocalIcon icon="attachment-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="attachment-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.attachments.title", "Add Attachments"),
|
name: t("home.attachments.title", "Add Attachments"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.attachments.desc", "Add or remove embedded files (attachments) to/from a PDF"),
|
description: t("home.attachments.desc", "Add or remove embedded files (attachments) to/from a PDF"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// Extraction
|
// Extraction
|
||||||
|
|
||||||
"extractPages": {
|
"extract-page": {
|
||||||
icon: <LocalIcon icon="upload-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="upload-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.extractPages.title", "Extract Pages"),
|
name: t("home.extractPages.title", "Extract Pages"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "extract",
|
|
||||||
description: t("home.extractPages.desc", "Extract specific pages from a PDF document"),
|
description: t("home.extractPages.desc", "Extract specific pages from a PDF document"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.EXTRACTION
|
subcategoryId: SubcategoryId.EXTRACTION,
|
||||||
},
|
},
|
||||||
"extract-images": {
|
"extract-images": {
|
||||||
icon: <LocalIcon icon="filter-alt" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="filter-alt" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.extractImages.title", "Extract Images"),
|
name: t("home.extractImages.title", "Extract Images"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "extract",
|
|
||||||
description: t("home.extractImages.desc", "Extract images from PDF documents"),
|
description: t("home.extractImages.desc", "Extract images from PDF documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.EXTRACTION
|
subcategoryId: SubcategoryId.EXTRACTION,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// Removal
|
// Removal
|
||||||
|
|
||||||
"removePages": {
|
removePages: {
|
||||||
icon: <LocalIcon icon="delete-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="delete-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.removePages.title", "Remove Pages"),
|
name: t("home.removePages.title", "Remove Pages"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "remove",
|
|
||||||
description: t("home.removePages.desc", "Remove specific pages from a PDF document"),
|
description: t("home.removePages.desc", "Remove specific pages from a PDF document"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.REMOVAL
|
subcategoryId: SubcategoryId.REMOVAL,
|
||||||
},
|
},
|
||||||
"remove-blank-pages": {
|
"remove-blank-pages": {
|
||||||
icon: <LocalIcon icon="scan-delete-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="scan-delete-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.removeBlanks.title", "Remove Blank Pages"),
|
name: t("home.removeBlanks.title", "Remove Blank Pages"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "remove",
|
|
||||||
description: t("home.removeBlanks.desc", "Remove blank pages from PDF documents"),
|
description: t("home.removeBlanks.desc", "Remove blank pages from PDF documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.REMOVAL
|
subcategoryId: SubcategoryId.REMOVAL,
|
||||||
},
|
},
|
||||||
"remove-annotations": {
|
"remove-annotations": {
|
||||||
icon: <LocalIcon icon="thread-unread-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="thread-unread-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.removeAnnotations.title", "Remove Annotations"),
|
name: t("home.removeAnnotations.title", "Remove Annotations"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "remove",
|
|
||||||
description: t("home.removeAnnotations.desc", "Remove annotations and comments from PDF documents"),
|
description: t("home.removeAnnotations.desc", "Remove annotations and comments from PDF documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.REMOVAL
|
subcategoryId: SubcategoryId.REMOVAL,
|
||||||
},
|
},
|
||||||
"remove-image": {
|
"remove-image": {
|
||||||
icon: <LocalIcon icon="remove-selection-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="remove-selection-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.removeImagePdf.title", "Remove Image"),
|
name: t("home.removeImagePdf.title", "Remove Image"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.removeImagePdf.desc", "Remove images from PDF documents"),
|
description: t("home.removeImagePdf.desc", "Remove images from PDF documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.REMOVAL
|
subcategoryId: SubcategoryId.REMOVAL,
|
||||||
},
|
},
|
||||||
"remove-password": {
|
"remove-password": {
|
||||||
icon: <LocalIcon icon="lock-open-right-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="lock-open-right-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.removePassword.title", "Remove Password"),
|
name: t("home.removePassword.title", "Remove Password"),
|
||||||
component: RemovePassword,
|
component: RemovePassword,
|
||||||
view: "security",
|
|
||||||
description: t("home.removePassword.desc", "Remove password protection from PDF documents"),
|
description: t("home.removePassword.desc", "Remove password protection from PDF documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.REMOVAL,
|
subcategoryId: SubcategoryId.REMOVAL,
|
||||||
endpoints: ["remove-password"],
|
endpoints: ["remove-password"],
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
operationConfig: removePasswordOperationConfig,
|
operationConfig: removePasswordOperationConfig,
|
||||||
settingsComponent: RemovePasswordSettings
|
settingsComponent: RemovePasswordSettings,
|
||||||
},
|
},
|
||||||
"remove-certificate-sign": {
|
"remove-certificate-sign": {
|
||||||
icon: <LocalIcon icon="remove-moderator-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="remove-moderator-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.removeCertSign.title", "Remove Certificate Sign"),
|
name: t("home.removeCertSign.title", "Remove Certificate Sign"),
|
||||||
component: RemoveCertificateSign,
|
component: RemoveCertificateSign,
|
||||||
view: "security",
|
|
||||||
description: t("home.removeCertSign.desc", "Remove digital signature from PDF documents"),
|
description: t("home.removeCertSign.desc", "Remove digital signature from PDF documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.REMOVAL,
|
subcategoryId: SubcategoryId.REMOVAL,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["remove-certificate-sign"],
|
endpoints: ["remove-certificate-sign"],
|
||||||
operationConfig: removeCertificateSignOperationConfig
|
operationConfig: removeCertificateSignOperationConfig,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// Automation
|
// Automation
|
||||||
|
|
||||||
"automate": {
|
automate: {
|
||||||
icon: <LocalIcon icon="automation-outline" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="automation-outline" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.automate.title", "Automate"),
|
name: t("home.automate.title", "Automate"),
|
||||||
component: React.lazy(() => import('../tools/Automate')),
|
component: React.lazy(() => import("../tools/Automate")),
|
||||||
view: "format",
|
description: t(
|
||||||
description: t("home.automate.desc", "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."),
|
"home.automate.desc",
|
||||||
|
"Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
|
||||||
|
),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.AUTOMATION,
|
subcategoryId: SubcategoryId.AUTOMATION,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
supportedFormats: CONVERT_SUPPORTED_FORMATS,
|
supportedFormats: CONVERT_SUPPORTED_FORMATS,
|
||||||
endpoints: ["handleData"]
|
endpoints: ["handleData"],
|
||||||
},
|
},
|
||||||
"auto-rename-pdf-file": {
|
"auto-rename-pdf-file": {
|
||||||
icon: <LocalIcon icon="match-word-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="match-word-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.auto-rename.title", "Auto Rename PDF File"),
|
name: t("home.auto-rename.title", "Auto Rename PDF File"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.auto-rename.desc", "Automatically rename PDF files based on their content"),
|
description: t("home.auto-rename.desc", "Automatically rename PDF files based on their content"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.AUTOMATION
|
subcategoryId: SubcategoryId.AUTOMATION,
|
||||||
},
|
},
|
||||||
"auto-split-pages": {
|
"auto-split-pages": {
|
||||||
icon: <LocalIcon icon="split-scene-right-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="split-scene-right-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.autoSplitPDF.title", "Auto Split Pages"),
|
name: t("home.autoSplitPDF.title", "Auto Split Pages"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.autoSplitPDF.desc", "Automatically split PDF pages based on content detection"),
|
description: t("home.autoSplitPDF.desc", "Automatically split PDF pages based on content detection"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.AUTOMATION
|
subcategoryId: SubcategoryId.AUTOMATION,
|
||||||
},
|
},
|
||||||
"auto-split-by-size-count": {
|
"auto-split-by-size-count": {
|
||||||
icon: <LocalIcon icon="content-cut-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="content-cut-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.autoSizeSplitPDF.title", "Auto Split by Size/Count"),
|
name: t("home.autoSizeSplitPDF.title", "Auto Split by Size/Count"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.autoSizeSplitPDF.desc", "Automatically split PDFs by file size or page count"),
|
description: t("home.autoSizeSplitPDF.desc", "Automatically split PDFs by file size or page count"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.AUTOMATION
|
subcategoryId: SubcategoryId.AUTOMATION,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// Advanced Formatting
|
// Advanced Formatting
|
||||||
|
|
||||||
"adjustContrast": {
|
"adjust-contrast": {
|
||||||
icon: <LocalIcon icon="palette" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="palette" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.adjustContrast.title", "Adjust Colors/Contrast"),
|
name: t("home.adjustContrast.title", "Adjust Colors/Contrast"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.adjustContrast.desc", "Adjust colors and contrast of PDF documents"),
|
description: t("home.adjustContrast.desc", "Adjust colors and contrast of PDF documents"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
},
|
},
|
||||||
"repair": {
|
repair: {
|
||||||
icon: <LocalIcon icon="build-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="build-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.repair.title", "Repair"),
|
name: t("home.repair.title", "Repair"),
|
||||||
component: Repair,
|
component: Repair,
|
||||||
view: "format",
|
|
||||||
description: t("home.repair.desc", "Repair corrupted or damaged PDF files"),
|
description: t("home.repair.desc", "Repair corrupted or damaged PDF files"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["repair"],
|
endpoints: ["repair"],
|
||||||
operationConfig: repairOperationConfig,
|
operationConfig: repairOperationConfig,
|
||||||
settingsComponent: RepairSettings
|
settingsComponent: RepairSettings,
|
||||||
},
|
},
|
||||||
"detect-split-scanned-photos": {
|
"detect-split-scanned-photos": {
|
||||||
icon: <LocalIcon icon="scanner-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="scanner-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.ScannerImageSplit.title", "Detect & Split Scanned Photos"),
|
name: t("home.ScannerImageSplit.title", "Detect & Split Scanned Photos"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.ScannerImageSplit.desc", "Detect and split scanned photos into separate pages"),
|
description: t("home.ScannerImageSplit.desc", "Detect and split scanned photos into separate pages"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
},
|
},
|
||||||
"overlay-pdfs": {
|
"overlay-pdfs": {
|
||||||
icon: <LocalIcon icon="layers-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="layers-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.overlay-pdfs.title", "Overlay PDFs"),
|
name: t("home.overlay-pdfs.title", "Overlay PDFs"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.overlay-pdfs.desc", "Overlay one PDF on top of another"),
|
description: t("home.overlay-pdfs.desc", "Overlay one PDF on top of another"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
},
|
},
|
||||||
"replace-and-invert-color": {
|
"replace-and-invert-color": {
|
||||||
icon: <LocalIcon icon="format-color-fill-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="format-color-fill-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.replaceColorPdf.title", "Replace & Invert Color"),
|
name: t("home.replaceColorPdf.title", "Replace & Invert Color"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.replaceColorPdf.desc", "Replace or invert colors in PDF documents"),
|
description: t("home.replaceColorPdf.desc", "Replace or invert colors in PDF documents"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
},
|
},
|
||||||
"add-image": {
|
"add-image": {
|
||||||
icon: <LocalIcon icon="image-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="image-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.addImage.title", "Add Image"),
|
name: t("home.addImage.title", "Add Image"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.addImage.desc", "Add images to PDF documents"),
|
description: t("home.addImage.desc", "Add images to PDF documents"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
},
|
},
|
||||||
"edit-table-of-contents": {
|
"edit-table-of-contents": {
|
||||||
icon: <LocalIcon icon="bookmark-add-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="bookmark-add-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.editTableOfContents.title", "Edit Table of Contents"),
|
name: t("home.editTableOfContents.title", "Edit Table of Contents"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.editTableOfContents.desc", "Add or edit bookmarks and table of contents in PDF documents"),
|
description: t("home.editTableOfContents.desc", "Add or edit bookmarks and table of contents in PDF documents"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
},
|
},
|
||||||
"scanner-effect": {
|
"scanner-effect": {
|
||||||
icon: <LocalIcon icon="scanner-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="scanner-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.fakeScan.title", "Scanner Effect"),
|
name: t("home.fakeScan.title", "Scanner Effect"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.fakeScan.desc", "Create a PDF that looks like it was scanned"),
|
description: t("home.fakeScan.desc", "Create a PDF that looks like it was scanned"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// Developer Tools
|
// Developer Tools
|
||||||
|
|
||||||
"show-javascript": {
|
"show-javascript": {
|
||||||
icon: <LocalIcon icon="javascript-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="javascript-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.showJS.title", "Show JavaScript"),
|
name: t("home.showJS.title", "Show JavaScript"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "extract",
|
|
||||||
description: t("home.showJS.desc", "Extract and display JavaScript code from PDF documents"),
|
description: t("home.showJS.desc", "Extract and display JavaScript code from PDF documents"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DEVELOPER_TOOLS
|
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
||||||
},
|
},
|
||||||
"dev-api": {
|
"dev-api": {
|
||||||
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: '#2F7BF6' }} />,
|
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: "#2F7BF6" }} />,
|
||||||
name: t("home.devApi.title", "API"),
|
name: t("home.devApi.title", "API"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "external",
|
|
||||||
description: t("home.devApi.desc", "Link to API documentation"),
|
description: t("home.devApi.desc", "Link to API documentation"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
||||||
link: "https://stirlingpdf.io/swagger-ui/5.21.0/index.html"
|
link: "https://stirlingpdf.io/swagger-ui/5.21.0/index.html",
|
||||||
},
|
},
|
||||||
"dev-folder-scanning": {
|
"dev-folder-scanning": {
|
||||||
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: '#2F7BF6' }} />,
|
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: "#2F7BF6" }} />,
|
||||||
name: t("home.devFolderScanning.title", "Automated Folder Scanning"),
|
name: t("home.devFolderScanning.title", "Automated Folder Scanning"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "external",
|
|
||||||
description: t("home.devFolderScanning.desc", "Link to automated folder scanning guide"),
|
description: t("home.devFolderScanning.desc", "Link to automated folder scanning guide"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
||||||
link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Folder%20Scanning/"
|
link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Folder%20Scanning/",
|
||||||
},
|
},
|
||||||
"dev-sso-guide": {
|
"dev-sso-guide": {
|
||||||
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: '#2F7BF6' }} />,
|
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: "#2F7BF6" }} />,
|
||||||
name: t("home.devSsoGuide.title", "SSO Guide"),
|
name: t("home.devSsoGuide.title", "SSO Guide"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "external",
|
|
||||||
description: t("home.devSsoGuide.desc", "Link to SSO guide"),
|
description: t("home.devSsoGuide.desc", "Link to SSO guide"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
||||||
link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Single%20Sign-On%20Configuration",
|
link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Single%20Sign-On%20Configuration",
|
||||||
},
|
},
|
||||||
"dev-airgapped": {
|
"dev-airgapped": {
|
||||||
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: '#2F7BF6' }} />,
|
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: "#2F7BF6" }} />,
|
||||||
name: t("home.devAirgapped.title", "Air-gapped Setup"),
|
name: t("home.devAirgapped.title", "Air-gapped Setup"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "external",
|
|
||||||
description: t("home.devAirgapped.desc", "Link to air-gapped setup guide"),
|
description: t("home.devAirgapped.desc", "Link to air-gapped setup guide"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
||||||
link: "https://docs.stirlingpdf.com/Pro/#activation"
|
link: "https://docs.stirlingpdf.com/Pro/#activation",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// Recommended Tools
|
// Recommended Tools
|
||||||
"compare": {
|
compare: {
|
||||||
icon: <LocalIcon icon="compare-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="compare-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.compare.title", "Compare"),
|
name: t("home.compare.title", "Compare"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "format",
|
|
||||||
description: t("home.compare.desc", "Compare two PDF documents and highlight differences"),
|
description: t("home.compare.desc", "Compare two PDF documents and highlight differences"),
|
||||||
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.GENERAL
|
subcategoryId: SubcategoryId.GENERAL,
|
||||||
},
|
},
|
||||||
"compress": {
|
compress: {
|
||||||
icon: <LocalIcon icon="zoom-in-map-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="zoom-in-map-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.compress.title", "Compress"),
|
name: t("home.compress.title", "Compress"),
|
||||||
component: CompressPdfPanel,
|
component: CompressPdfPanel,
|
||||||
view: "compress",
|
|
||||||
description: t("home.compress.desc", "Compress PDFs to reduce their file size."),
|
description: t("home.compress.desc", "Compress PDFs to reduce their file size."),
|
||||||
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.GENERAL,
|
subcategoryId: SubcategoryId.GENERAL,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
operationConfig: compressOperationConfig,
|
operationConfig: compressOperationConfig,
|
||||||
settingsComponent: CompressSettings
|
settingsComponent: CompressSettings,
|
||||||
},
|
},
|
||||||
"convert": {
|
convert: {
|
||||||
icon: <LocalIcon icon="sync-alt-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="sync-alt-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.convert.title", "Convert"),
|
name: t("home.convert.title", "Convert"),
|
||||||
component: ConvertPanel,
|
component: ConvertPanel,
|
||||||
view: "convert",
|
|
||||||
description: t("home.convert.desc", "Convert files to and from PDF format"),
|
description: t("home.convert.desc", "Convert files to and from PDF format"),
|
||||||
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.GENERAL,
|
subcategoryId: SubcategoryId.GENERAL,
|
||||||
@ -625,52 +647,50 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
"pdf-to-csv",
|
"pdf-to-csv",
|
||||||
"pdf-to-markdown",
|
"pdf-to-markdown",
|
||||||
"pdf-to-pdfa",
|
"pdf-to-pdfa",
|
||||||
"eml-to-pdf"
|
"eml-to-pdf",
|
||||||
],
|
],
|
||||||
|
|
||||||
operationConfig: convertOperationConfig,
|
operationConfig: convertOperationConfig,
|
||||||
settingsComponent: ConvertSettings
|
settingsComponent: ConvertSettings,
|
||||||
},
|
},
|
||||||
"mergePdfs": {
|
mergePdfs: {
|
||||||
icon: <LocalIcon icon="library-add-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="library-add-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.merge.title", "Merge"),
|
name: t("home.merge.title", "Merge"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "merge",
|
|
||||||
description: t("home.merge.desc", "Merge multiple PDFs into a single document"),
|
description: t("home.merge.desc", "Merge multiple PDFs into a single document"),
|
||||||
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.GENERAL,
|
subcategoryId: SubcategoryId.GENERAL,
|
||||||
maxFiles: -1
|
maxFiles: -1,
|
||||||
},
|
},
|
||||||
"multi-tool": {
|
"multi-tool": {
|
||||||
icon: <LocalIcon icon="dashboard-customize-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="dashboard-customize-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.multiTool.title", "Multi-Tool"),
|
name: t("home.multiTool.title", "Multi-Tool"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "pageEditor",
|
workbench: "pageEditor",
|
||||||
description: t("home.multiTool.desc", "Use multiple tools on a single PDF document"),
|
description: t("home.multiTool.desc", "Use multiple tools on a single PDF document"),
|
||||||
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.GENERAL,
|
subcategoryId: SubcategoryId.GENERAL,
|
||||||
maxFiles: -1
|
maxFiles: -1,
|
||||||
},
|
},
|
||||||
"ocr": {
|
ocr: {
|
||||||
icon: <LocalIcon icon="quick-reference-all-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="quick-reference-all-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.ocr.title", "OCR"),
|
name: t("home.ocr.title", "OCR"),
|
||||||
component: OCRPanel,
|
component: OCRPanel,
|
||||||
view: "convert",
|
|
||||||
description: t("home.ocr.desc", "Extract text from scanned PDFs using Optical Character Recognition"),
|
description: t("home.ocr.desc", "Extract text from scanned PDFs using Optical Character Recognition"),
|
||||||
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.GENERAL,
|
subcategoryId: SubcategoryId.GENERAL,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
operationConfig: ocrOperationConfig,
|
operationConfig: ocrOperationConfig,
|
||||||
settingsComponent: OCRSettings
|
settingsComponent: OCRSettings,
|
||||||
},
|
},
|
||||||
"redact": {
|
redact: {
|
||||||
icon: <LocalIcon icon="visibility-off-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="visibility-off-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
name: t("home.redact.title", "Redact"),
|
name: t("home.redact.title", "Redact"),
|
||||||
component: null,
|
component: null,
|
||||||
view: "redact",
|
|
||||||
description: t("home.redact.desc", "Permanently remove sensitive information from PDF documents"),
|
description: t("home.redact.desc", "Permanently remove sensitive information from PDF documents"),
|
||||||
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.GENERAL
|
subcategoryId: SubcategoryId.GENERAL,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -678,9 +698,9 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
return allTools;
|
return allTools;
|
||||||
}
|
}
|
||||||
const filteredTools = Object.keys(allTools)
|
const filteredTools = Object.keys(allTools)
|
||||||
.filter(key => allTools[key].component !== null || allTools[key].link)
|
.filter((key) => allTools[key as ToolId].component !== null || allTools[key as ToolId].link)
|
||||||
.reduce((obj, key) => {
|
.reduce((obj, key) => {
|
||||||
obj[key] = allTools[key];
|
obj[key as ToolId] = allTools[key as ToolId];
|
||||||
return obj;
|
return obj;
|
||||||
}, {} as ToolRegistry);
|
}, {} as ToolRegistry);
|
||||||
return filteredTools;
|
return filteredTools;
|
||||||
|
@ -2,12 +2,12 @@ import { useState, useEffect, useCallback } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { AutomationTool, AutomationConfig, AutomationMode } from '../../../types/automation';
|
import { AutomationTool, AutomationConfig, AutomationMode } from '../../../types/automation';
|
||||||
import { AUTOMATION_CONSTANTS } from '../../../constants/automation';
|
import { AUTOMATION_CONSTANTS } from '../../../constants/automation';
|
||||||
import { ToolRegistryEntry } from '../../../data/toolsTaxonomy';
|
import { ToolRegistry } from '../../../data/toolsTaxonomy';
|
||||||
|
|
||||||
interface UseAutomationFormProps {
|
interface UseAutomationFormProps {
|
||||||
mode: AutomationMode;
|
mode: AutomationMode;
|
||||||
existingAutomation?: AutomationConfig;
|
existingAutomation?: AutomationConfig;
|
||||||
toolRegistry: Record<string, ToolRegistryEntry>;
|
toolRegistry: ToolRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAutomationForm({ mode, existingAutomation, toolRegistry }: UseAutomationFormProps) {
|
export function useAutomationForm({ mode, existingAutomation, toolRegistry }: UseAutomationFormProps) {
|
||||||
@ -19,12 +19,12 @@ export function useAutomationForm({ mode, existingAutomation, toolRegistry }: Us
|
|||||||
const [selectedTools, setSelectedTools] = useState<AutomationTool[]>([]);
|
const [selectedTools, setSelectedTools] = useState<AutomationTool[]>([]);
|
||||||
|
|
||||||
const getToolName = useCallback((operation: string) => {
|
const getToolName = useCallback((operation: string) => {
|
||||||
const tool = toolRegistry?.[operation] as any;
|
const tool = toolRegistry?.[operation as keyof ToolRegistry] as any;
|
||||||
return tool?.name || t(`tools.${operation}.name`, operation);
|
return tool?.name || t(`tools.${operation}.name`, operation);
|
||||||
}, [toolRegistry, t]);
|
}, [toolRegistry, t]);
|
||||||
|
|
||||||
const getToolDefaultParameters = useCallback((operation: string): Record<string, any> => {
|
const getToolDefaultParameters = useCallback((operation: string): Record<string, any> => {
|
||||||
const config = toolRegistry[operation]?.operationConfig;
|
const config = toolRegistry[operation as keyof ToolRegistry]?.operationConfig;
|
||||||
if (config?.defaultParameters) {
|
if (config?.defaultParameters) {
|
||||||
return { ...config.defaultParameters };
|
return { ...config.defaultParameters };
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useNavigationActions, useNavigationState } from '../contexts/NavigationContext';
|
import { useNavigationActions, useNavigationState } from '../contexts/NavigationContext';
|
||||||
|
import { ToolId } from '../types/toolId';
|
||||||
|
|
||||||
// Material UI Icons
|
// Material UI Icons
|
||||||
import CompressIcon from '@mui/icons-material/Compress';
|
import CompressIcon from '@mui/icons-material/Compress';
|
||||||
@ -9,7 +10,7 @@ import CropIcon from '@mui/icons-material/Crop';
|
|||||||
import TextFieldsIcon from '@mui/icons-material/TextFields';
|
import TextFieldsIcon from '@mui/icons-material/TextFields';
|
||||||
|
|
||||||
export interface SuggestedTool {
|
export interface SuggestedTool {
|
||||||
id: string /* FIX ME: Should be ToolId */;
|
id: ToolId;
|
||||||
title: string;
|
title: string;
|
||||||
icon: React.ComponentType<any>;
|
icon: React.ComponentType<any>;
|
||||||
navigate: () => void;
|
navigate: () => void;
|
||||||
@ -32,7 +33,7 @@ const ALL_SUGGESTED_TOOLS: Omit<SuggestedTool, 'navigate'>[] = [
|
|||||||
icon: CleaningServicesIcon
|
icon: CleaningServicesIcon
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'splitPdf',
|
id: 'split',
|
||||||
title: 'Split',
|
title: 'Split',
|
||||||
icon: CropIcon
|
icon: CropIcon
|
||||||
},
|
},
|
||||||
@ -45,16 +46,16 @@ const ALL_SUGGESTED_TOOLS: Omit<SuggestedTool, 'navigate'>[] = [
|
|||||||
|
|
||||||
export function useSuggestedTools(): SuggestedTool[] {
|
export function useSuggestedTools(): SuggestedTool[] {
|
||||||
const { actions } = useNavigationActions();
|
const { actions } = useNavigationActions();
|
||||||
const { selectedToolKey } = useNavigationState();
|
const { selectedTool } = useNavigationState();
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
// Filter out the current tool
|
// Filter out the current tool
|
||||||
const filteredTools = ALL_SUGGESTED_TOOLS.filter(tool => tool.id !== selectedToolKey);
|
const filteredTools = ALL_SUGGESTED_TOOLS.filter(tool => tool.id !== selectedTool);
|
||||||
|
|
||||||
// Add navigation function to each tool
|
// Add navigation function to each tool
|
||||||
return filteredTools.map(tool => ({
|
return filteredTools.map(tool => ({
|
||||||
...tool,
|
...tool,
|
||||||
navigate: () => actions.handleToolSelect(tool.id)
|
navigate: () => actions.setSelectedTool(tool.id)
|
||||||
}));
|
}));
|
||||||
}, [selectedToolKey, actions]);
|
}, [selectedTool, actions]);
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ export const useToolManagement = (): ToolManagementResult => {
|
|||||||
|
|
||||||
const isToolAvailable = useCallback((toolKey: string): boolean => {
|
const isToolAvailable = useCallback((toolKey: string): boolean => {
|
||||||
if (endpointsLoading) return true;
|
if (endpointsLoading) return true;
|
||||||
const endpoints = baseRegistry[toolKey]?.endpoints || [];
|
const endpoints = baseRegistry[toolKey as keyof typeof baseRegistry]?.endpoints || [];
|
||||||
return endpoints.length === 0 || endpoints.some((endpoint: string) => endpointStatus[endpoint] === true);
|
return endpoints.length === 0 || endpoints.some((endpoint: string) => endpointStatus[endpoint] === true);
|
||||||
}, [endpointsLoading, endpointStatus, baseRegistry]);
|
}, [endpointsLoading, endpointStatus, baseRegistry]);
|
||||||
|
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
// src/hooks/useToolUrlRouting.ts
|
|
||||||
// Focused hook for URL <-> tool-key mapping and browser history sync.
|
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
export interface UseToolUrlRoutingOpts {
|
|
||||||
/** Currently selected tool key (from context). */
|
|
||||||
selectedToolKey: string | null;
|
|
||||||
/** Registry of available tools (key -> tool metadata). */
|
|
||||||
toolRegistry: Record<string, any> | null | undefined;
|
|
||||||
/** Select a tool (no extra side-effects). */
|
|
||||||
selectTool: (toolKey: string) => void;
|
|
||||||
/** Clear selection. */
|
|
||||||
clearToolSelection: () => void;
|
|
||||||
/** Called once during initialization if URL contains a tool; may trigger UI changes. */
|
|
||||||
onInitSelect?: (toolKey: string) => void;
|
|
||||||
/** Called when navigating via back/forward (popstate). Defaults to selectTool. */
|
|
||||||
onPopStateSelect?: (toolKey: string) => void;
|
|
||||||
/** Optional base path if the app isn't served at "/" (no trailing slash). Default: "" (root). */
|
|
||||||
basePath?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useToolUrlRouting(opts: UseToolUrlRoutingOpts) {
|
|
||||||
const {
|
|
||||||
selectedToolKey,
|
|
||||||
toolRegistry,
|
|
||||||
selectTool,
|
|
||||||
clearToolSelection,
|
|
||||||
onInitSelect,
|
|
||||||
onPopStateSelect,
|
|
||||||
basePath = '',
|
|
||||||
} = opts;
|
|
||||||
|
|
||||||
// Central slug map; keep here to co-locate routing policy.
|
|
||||||
const urlMap = useMemo(
|
|
||||||
() =>
|
|
||||||
new Map<string, string>([
|
|
||||||
['compress', 'compress-pdf'],
|
|
||||||
['split', 'split-pdf'],
|
|
||||||
['convert', 'convert-pdf'],
|
|
||||||
['ocr', 'ocr-pdf'],
|
|
||||||
['merge', 'merge-pdf'],
|
|
||||||
['rotate', 'rotate-pdf'],
|
|
||||||
]),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const getToolUrlSlug = useCallback(
|
|
||||||
(toolKey: string) => urlMap.get(toolKey) ?? toolKey,
|
|
||||||
[urlMap]
|
|
||||||
);
|
|
||||||
|
|
||||||
const getToolKeyFromSlug = useCallback(
|
|
||||||
(slug: string) => {
|
|
||||||
for (const [key, value] of urlMap) {
|
|
||||||
if (value === slug) return key;
|
|
||||||
}
|
|
||||||
return slug; // fall back to raw key
|
|
||||||
},
|
|
||||||
[urlMap]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Internal flag to avoid clearing URL on initial mount.
|
|
||||||
const [hasInitialized, setHasInitialized] = useState(false);
|
|
||||||
|
|
||||||
// Normalize a pathname by stripping basePath and leading slash.
|
|
||||||
const normalizePath = useCallback(
|
|
||||||
(fullPath: string) => {
|
|
||||||
let p = fullPath;
|
|
||||||
if (basePath && p.startsWith(basePath)) {
|
|
||||||
p = p.slice(basePath.length);
|
|
||||||
}
|
|
||||||
if (p.startsWith('/')) p = p.slice(1);
|
|
||||||
return p;
|
|
||||||
},
|
|
||||||
[basePath]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update URL when tool changes (but not on first paint before any selection happens).
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedToolKey) {
|
|
||||||
const slug = getToolUrlSlug(selectedToolKey);
|
|
||||||
const newUrl = `${basePath}/${slug}`.replace(/\/+/, '/');
|
|
||||||
window.history.replaceState({}, '', newUrl);
|
|
||||||
setHasInitialized(true);
|
|
||||||
} else if (hasInitialized) {
|
|
||||||
const rootUrl = basePath || '/';
|
|
||||||
window.history.replaceState({}, '', rootUrl);
|
|
||||||
}
|
|
||||||
}, [selectedToolKey, getToolUrlSlug, hasInitialized, basePath]);
|
|
||||||
|
|
||||||
// Initialize from URL when the registry is ready and nothing is selected yet.
|
|
||||||
useEffect(() => {
|
|
||||||
if (!toolRegistry || Object.keys(toolRegistry).length === 0) return;
|
|
||||||
if (selectedToolKey) return; // don't override explicit selection
|
|
||||||
|
|
||||||
const currentPath = normalizePath(window.location.pathname);
|
|
||||||
if (currentPath) {
|
|
||||||
const toolKey = getToolKeyFromSlug(currentPath);
|
|
||||||
if (toolRegistry[toolKey]) {
|
|
||||||
(onInitSelect ?? selectTool)(toolKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [toolRegistry, selectedToolKey, getToolKeyFromSlug, selectTool, onInitSelect, normalizePath]);
|
|
||||||
|
|
||||||
// Handle browser back/forward. NOTE: useRef needs an initial value in TS.
|
|
||||||
const popHandlerRef = useRef<((this: Window, ev: PopStateEvent) => any) | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
popHandlerRef.current = () => {
|
|
||||||
const path = normalizePath(window.location.pathname);
|
|
||||||
if (path) {
|
|
||||||
const toolKey = getToolKeyFromSlug(path);
|
|
||||||
if (toolRegistry && toolRegistry[toolKey]) {
|
|
||||||
(onPopStateSelect ?? selectTool)(toolKey);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clearToolSelection();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handler = (e: PopStateEvent) => popHandlerRef.current?.call(window, e);
|
|
||||||
window.addEventListener('popstate', handler);
|
|
||||||
return () => window.removeEventListener('popstate', handler);
|
|
||||||
}, [toolRegistry, selectTool, clearToolSelection, getToolKeyFromSlug, onPopStateSelect, normalizePath]);
|
|
||||||
|
|
||||||
// Expose pure helpers if you want them elsewhere (optional).
|
|
||||||
return { getToolUrlSlug, getToolKeyFromSlug };
|
|
||||||
}
|
|
@ -1,120 +1,107 @@
|
|||||||
/**
|
/**
|
||||||
* URL synchronization hooks for tool routing
|
* URL synchronization hooks for tool routing with registry support
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useCallback } from 'react';
|
import { useEffect, useCallback, useRef } from 'react';
|
||||||
import { ModeType } from '../types/navigation';
|
import { ToolId } from '../types/toolId';
|
||||||
import { parseToolRoute, updateToolRoute, clearToolRoute } from '../utils/urlRouting';
|
import { parseToolRoute, updateToolRoute, clearToolRoute } from '../utils/urlRouting';
|
||||||
|
import { ToolRegistry } from '../data/toolsTaxonomy';
|
||||||
|
import { firePixel } from '../utils/scarfTracking';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to sync navigation mode with URL
|
* Hook to sync workbench and tool with URL using registry
|
||||||
*/
|
*/
|
||||||
export function useNavigationUrlSync(
|
export function useNavigationUrlSync(
|
||||||
currentMode: ModeType,
|
selectedTool: ToolId | null,
|
||||||
setMode: (mode: ModeType) => void,
|
handleToolSelect: (toolId: string) => void,
|
||||||
|
clearToolSelection: () => void,
|
||||||
|
registry: ToolRegistry,
|
||||||
enableSync: boolean = true
|
enableSync: boolean = true
|
||||||
) {
|
) {
|
||||||
// Initialize mode from URL on mount
|
const hasInitialized = useRef(false);
|
||||||
|
const prevSelectedTool = useRef<ToolId | null>(null);
|
||||||
|
// Initialize workbench and tool from URL on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!enableSync) return;
|
if (!enableSync) return;
|
||||||
|
|
||||||
const route = parseToolRoute();
|
// Fire pixel for initial page load
|
||||||
if (route.mode !== currentMode) {
|
const currentPath = window.location.pathname;
|
||||||
setMode(route.mode);
|
firePixel(currentPath);
|
||||||
|
|
||||||
|
const route = parseToolRoute(registry);
|
||||||
|
if (route.toolId !== selectedTool) {
|
||||||
|
if (route.toolId) {
|
||||||
|
handleToolSelect(route.toolId);
|
||||||
|
} else if (selectedTool !== null) {
|
||||||
|
// Only clear selection if we actually had a tool selected
|
||||||
|
// Don't clear on initial load when selectedTool starts as null
|
||||||
|
clearToolSelection();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasInitialized.current = true;
|
||||||
}, []); // Only run on mount
|
}, []); // Only run on mount
|
||||||
|
|
||||||
// Update URL when mode changes
|
// Update URL when tool or workbench changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!enableSync) return;
|
if (!enableSync) return;
|
||||||
|
|
||||||
if (currentMode === 'pageEditor') {
|
if (selectedTool) {
|
||||||
clearToolRoute();
|
updateToolRoute(selectedTool, registry, false); // Use pushState for user navigation
|
||||||
} else {
|
} else if (prevSelectedTool.current !== null) {
|
||||||
updateToolRoute(currentMode, currentMode);
|
// Only clear URL if we had a tool before (user navigated away)
|
||||||
|
// Don't clear on initial load when both current and previous are null
|
||||||
|
if (window.location.pathname !== '/') {
|
||||||
|
clearToolRoute(false); // Use pushState for user navigation
|
||||||
}
|
}
|
||||||
}, [currentMode, enableSync]);
|
}
|
||||||
|
|
||||||
|
prevSelectedTool.current = selectedTool;
|
||||||
|
}, [selectedTool, registry, enableSync]);
|
||||||
|
|
||||||
// Handle browser back/forward navigation
|
// Handle browser back/forward navigation
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!enableSync) return;
|
if (!enableSync) return;
|
||||||
|
|
||||||
const handlePopState = () => {
|
const handlePopState = () => {
|
||||||
const route = parseToolRoute();
|
const route = parseToolRoute(registry);
|
||||||
if (route.mode !== currentMode) {
|
if (route.toolId !== selectedTool) {
|
||||||
setMode(route.mode);
|
// Fire pixel for back/forward navigation
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
firePixel(currentPath);
|
||||||
|
|
||||||
|
if (route.toolId) {
|
||||||
|
handleToolSelect(route.toolId);
|
||||||
|
} else {
|
||||||
|
clearToolSelection();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('popstate', handlePopState);
|
window.addEventListener('popstate', handlePopState);
|
||||||
return () => window.removeEventListener('popstate', handlePopState);
|
return () => window.removeEventListener('popstate', handlePopState);
|
||||||
}, [currentMode, setMode, enableSync]);
|
}, [selectedTool, handleToolSelect, clearToolSelection, registry, enableSync]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to sync tool workflow with URL
|
* Hook to programmatically navigate to tools with registry support
|
||||||
*/
|
*/
|
||||||
export function useToolWorkflowUrlSync(
|
export function useToolNavigation(registry: ToolRegistry) {
|
||||||
selectedToolKey: string | null,
|
const navigateToTool = useCallback((toolId: ToolId) => {
|
||||||
selectTool: (toolKey: string) => void,
|
updateToolRoute(toolId, registry);
|
||||||
clearTool: () => void,
|
|
||||||
enableSync: boolean = true
|
|
||||||
) {
|
|
||||||
// Initialize tool from URL on mount
|
|
||||||
useEffect(() => {
|
|
||||||
if (!enableSync) return;
|
|
||||||
|
|
||||||
const route = parseToolRoute();
|
|
||||||
if (route.toolKey && route.toolKey !== selectedToolKey) {
|
|
||||||
selectTool(route.toolKey);
|
|
||||||
} else if (!route.toolKey && selectedToolKey) {
|
|
||||||
clearTool();
|
|
||||||
}
|
|
||||||
}, []); // Only run on mount
|
|
||||||
|
|
||||||
// Update URL when tool changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (!enableSync) return;
|
|
||||||
|
|
||||||
if (selectedToolKey) {
|
|
||||||
const route = parseToolRoute();
|
|
||||||
if (route.toolKey !== selectedToolKey) {
|
|
||||||
updateToolRoute(selectedToolKey as ModeType, selectedToolKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [selectedToolKey, enableSync]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to get current URL route information
|
|
||||||
*/
|
|
||||||
export function useCurrentRoute() {
|
|
||||||
const getCurrentRoute = useCallback(() => {
|
|
||||||
return parseToolRoute();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return getCurrentRoute;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to programmatically navigate to tools
|
|
||||||
*/
|
|
||||||
export function useToolNavigation() {
|
|
||||||
const navigateToTool = useCallback((toolKey: string) => {
|
|
||||||
updateToolRoute(toolKey as ModeType, toolKey);
|
|
||||||
|
|
||||||
// Dispatch a custom event to notify other components
|
// Dispatch a custom event to notify other components
|
||||||
window.dispatchEvent(new CustomEvent('toolNavigation', {
|
window.dispatchEvent(new CustomEvent('toolNavigation', {
|
||||||
detail: { toolKey }
|
detail: { toolId }
|
||||||
}));
|
}));
|
||||||
}, []);
|
}, [registry]);
|
||||||
|
|
||||||
const navigateToHome = useCallback(() => {
|
const navigateToHome = useCallback(() => {
|
||||||
clearToolRoute();
|
clearToolRoute();
|
||||||
|
|
||||||
// Dispatch a custom event to notify other components
|
// Dispatch a custom event to notify other components
|
||||||
window.dispatchEvent(new CustomEvent('toolNavigation', {
|
window.dispatchEvent(new CustomEvent('toolNavigation', {
|
||||||
detail: { toolKey: null }
|
detail: { toolId: null }
|
||||||
}));
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -123,3 +110,14 @@ export function useToolNavigation() {
|
|||||||
navigateToHome
|
navigateToHome
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to get current URL route information with registry support
|
||||||
|
*/
|
||||||
|
export function useCurrentRoute(registry: ToolRegistry) {
|
||||||
|
const getCurrentRoute = useCallback(() => {
|
||||||
|
return parseToolRoute(registry);
|
||||||
|
}, [registry]);
|
||||||
|
|
||||||
|
return getCurrentRoute;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import '@mantine/core/styles.css';
|
import '@mantine/core/styles.css';
|
||||||
|
import '../vite-env.d.ts';
|
||||||
import './index.css'; // Import Tailwind CSS
|
import './index.css'; // Import Tailwind CSS
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
@ -6,6 +7,7 @@ import { ColorSchemeScript } from '@mantine/core';
|
|||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import './i18n'; // Initialize i18next
|
import './i18n'; // Initialize i18next
|
||||||
|
import { PostHogProvider } from 'posthog-js/react';
|
||||||
|
|
||||||
// Compute initial color scheme
|
// Compute initial color scheme
|
||||||
function getInitialScheme(): 'light' | 'dark' {
|
function getInitialScheme(): 'light' | 'dark' {
|
||||||
@ -27,9 +29,18 @@ const root = ReactDOM.createRoot(container); // Finds the root DOM element
|
|||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ColorSchemeScript defaultColorScheme={getInitialScheme()} />
|
<ColorSchemeScript defaultColorScheme={getInitialScheme()} />
|
||||||
|
<PostHogProvider
|
||||||
|
apiKey={import.meta.env.VITE_PUBLIC_POSTHOG_KEY}
|
||||||
|
options={{
|
||||||
|
api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST,
|
||||||
|
defaults: '2025-05-24',
|
||||||
|
capture_exceptions: true, // This enables capturing exceptions using Error Tracking, set to false if you don't want this
|
||||||
|
debug: import.meta.env.MODE === 'development',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
</PostHogProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import React, { useState, useMemo, useEffect } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useFileContext } from "../contexts/FileContext";
|
import { useFileContext } from "../contexts/FileContext";
|
||||||
import { useFileSelection } from "../contexts/FileContext";
|
import { useFileSelection } from "../contexts/FileContext";
|
||||||
import { useNavigation } from "../contexts/NavigationContext";
|
import { useNavigationActions } from "../contexts/NavigationContext";
|
||||||
import { useToolWorkflow } from "../contexts/ToolWorkflowContext";
|
import { useToolWorkflow } from "../contexts/ToolWorkflowContext";
|
||||||
|
|
||||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||||
@ -21,7 +21,7 @@ import { AUTOMATION_STEPS } from "../constants/automation";
|
|||||||
const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { selectedFiles } = useFileSelection();
|
const { selectedFiles } = useFileSelection();
|
||||||
const { setMode } = useNavigation();
|
const { actions } = useNavigationActions();
|
||||||
const { registerToolReset } = useToolWorkflow();
|
const { registerToolReset } = useToolWorkflow();
|
||||||
|
|
||||||
const [currentStep, setCurrentStep] = useState<AutomationStep>(AUTOMATION_STEPS.SELECTION);
|
const [currentStep, setCurrentStep] = useState<AutomationStep>(AUTOMATION_STEPS.SELECTION);
|
||||||
@ -161,7 +161,7 @@ const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
const filesPlaceholder = useMemo(() => {
|
const filesPlaceholder = useMemo(() => {
|
||||||
if (currentStep === AUTOMATION_STEPS.RUN && stepData.automation?.operations?.length) {
|
if (currentStep === AUTOMATION_STEPS.RUN && stepData.automation?.operations?.length) {
|
||||||
const firstOperation = stepData.automation.operations[0];
|
const firstOperation = stepData.automation.operations[0];
|
||||||
const toolConfig = toolRegistry[firstOperation.operation];
|
const toolConfig = toolRegistry[firstOperation.operation as keyof typeof toolRegistry];
|
||||||
|
|
||||||
// Check if the tool has supportedFormats that include non-PDF formats
|
// Check if the tool has supportedFormats that include non-PDF formats
|
||||||
if (toolConfig?.supportedFormats && toolConfig.supportedFormats.length > 1) {
|
if (toolConfig?.supportedFormats && toolConfig.supportedFormats.length > 1) {
|
||||||
@ -223,7 +223,7 @@ const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
title: t('automate.reviewTitle', 'Automation Results'),
|
title: t('automate.reviewTitle', 'Automation Results'),
|
||||||
onFileClick: (file: File) => {
|
onFileClick: (file: File) => {
|
||||||
onPreviewFile?.(file);
|
onPreviewFile?.(file);
|
||||||
setMode('viewer');
|
actions.setWorkbench('viewer');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,42 +1,19 @@
|
|||||||
/**
|
/**
|
||||||
* Shared navigation types to avoid circular dependencies
|
* Navigation types for workbench and tool separation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Navigation mode types - complete list to match contexts
|
import { WorkbenchType } from './workbench';
|
||||||
export type ModeType =
|
import { ToolId } from './toolId';
|
||||||
| 'viewer'
|
|
||||||
| 'pageEditor'
|
|
||||||
| 'fileEditor'
|
|
||||||
| 'merge'
|
|
||||||
| 'split'
|
|
||||||
| 'compress'
|
|
||||||
| 'ocr'
|
|
||||||
| 'convert'
|
|
||||||
| 'sanitize'
|
|
||||||
| 'addPassword'
|
|
||||||
| 'changePermissions'
|
|
||||||
| 'addWatermark'
|
|
||||||
| 'removePassword'
|
|
||||||
| 'single-large-page'
|
|
||||||
| 'repair'
|
|
||||||
| 'unlockPdfForms'
|
|
||||||
| 'removeCertificateSign';
|
|
||||||
|
|
||||||
// Utility functions for mode handling
|
// Navigation state
|
||||||
export const isValidMode = (mode: string): mode is ModeType => {
|
export interface NavigationState {
|
||||||
const validModes: ModeType[] = [
|
workbench: WorkbenchType;
|
||||||
'viewer', 'pageEditor', 'fileEditor', 'merge', 'split',
|
selectedTool: ToolId | null;
|
||||||
'compress', 'ocr', 'convert', 'addPassword', 'changePermissions',
|
}
|
||||||
'sanitize', 'addWatermark', 'removePassword', 'single-large-page',
|
|
||||||
'repair', 'unlockPdfForms', 'removeCertificateSign'
|
|
||||||
];
|
|
||||||
return validModes.includes(mode as ModeType);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getDefaultMode = (): ModeType => 'fileEditor';
|
|
||||||
|
|
||||||
// Route parsing result
|
// Route parsing result
|
||||||
export interface ToolRoute {
|
export interface ToolRoute {
|
||||||
mode: ModeType;
|
workbench: WorkbenchType;
|
||||||
toolKey: string | null;
|
toolId: ToolId | null;
|
||||||
}
|
}
|
@ -2,10 +2,12 @@
|
|||||||
* Navigation action interfaces to break circular dependencies
|
* Navigation action interfaces to break circular dependencies
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ModeType } from './navigation';
|
import { WorkbenchType } from './workbench';
|
||||||
|
import { ToolId } from './toolId';
|
||||||
|
|
||||||
export interface NavigationActions {
|
export interface NavigationActions {
|
||||||
setMode: (mode: ModeType) => void;
|
setWorkbench: (workbench: WorkbenchType) => void;
|
||||||
|
setSelectedTool: (toolId: ToolId | null) => void;
|
||||||
setHasUnsavedChanges: (hasChanges: boolean) => void;
|
setHasUnsavedChanges: (hasChanges: boolean) => void;
|
||||||
showNavigationWarning: (show: boolean) => void;
|
showNavigationWarning: (show: boolean) => void;
|
||||||
requestNavigation: (navigationFn: () => void) => void;
|
requestNavigation: (navigationFn: () => void) => void;
|
||||||
@ -14,7 +16,8 @@ export interface NavigationActions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface NavigationState {
|
export interface NavigationState {
|
||||||
currentMode: ModeType;
|
workbench: WorkbenchType;
|
||||||
|
selectedTool: ToolId | null;
|
||||||
hasUnsavedChanges: boolean;
|
hasUnsavedChanges: boolean;
|
||||||
pendingNavigation: (() => void) | null;
|
pendingNavigation: (() => void) | null;
|
||||||
showNavigationWarning: boolean;
|
showNavigationWarning: boolean;
|
||||||
|
25
frontend/src/types/toolId.ts
Normal file
25
frontend/src/types/toolId.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Define all possible tool IDs as source of truth
|
||||||
|
const TOOL_IDS = [
|
||||||
|
'certSign', 'sign', 'addPassword', 'remove-password', 'removePages', 'remove-blank-pages', 'remove-annotations', 'remove-image',
|
||||||
|
'change-permissions', 'addWatermark',
|
||||||
|
'sanitize', 'auto-split-pages', 'auto-split-by-size-count', 'split', 'mergePdfs',
|
||||||
|
'convert', 'ocr', 'add-image', 'rotate',
|
||||||
|
'detect-split-scanned-photos',
|
||||||
|
'edit-table-of-contents',
|
||||||
|
'scanner-effect',
|
||||||
|
'auto-rename-pdf-file', 'multi-page-layout', 'adjust-page-size-scale', 'adjust-contrast', 'cropPdf', 'single-large-page', 'multi-tool',
|
||||||
|
'repair', 'compare', 'addPageNumbers', 'redact',
|
||||||
|
'flatten', 'remove-certificate-sign',
|
||||||
|
'unlock-pdf-forms', 'compress', 'extract-page', 'reorganize-pages', 'extract-images',
|
||||||
|
'add-stamp', 'add-attachments', 'change-metadata', 'overlay-pdfs',
|
||||||
|
'manage-certificates', 'get-all-info-on-pdf', 'validate-pdf-signature', 'read', 'automate', 'replace-and-invert-color',
|
||||||
|
'show-javascript', 'dev-api', 'dev-folder-scanning', 'dev-sso-guide', 'dev-airgapped'
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
// Tool identity - what PDF operation we're performing (type-safe)
|
||||||
|
export type ToolId = typeof TOOL_IDS[number];
|
||||||
|
|
||||||
|
// Type guard using the same source of truth
|
||||||
|
export const isValidToolId = (value: string): value is ToolId => {
|
||||||
|
return TOOL_IDS.includes(value as ToolId);
|
||||||
|
};
|
12
frontend/src/types/workbench.ts
Normal file
12
frontend/src/types/workbench.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Define workbench values once as source of truth
|
||||||
|
const WORKBENCH_TYPES = ['viewer', 'pageEditor', 'fileEditor'] as const;
|
||||||
|
|
||||||
|
// Workbench types - how the user interacts with content
|
||||||
|
export type WorkbenchType = typeof WORKBENCH_TYPES[number];
|
||||||
|
|
||||||
|
export const getDefaultWorkbench = (): WorkbenchType => 'fileEditor';
|
||||||
|
|
||||||
|
// Type guard using the same source of truth
|
||||||
|
export const isValidWorkbench = (value: string): value is WorkbenchType => {
|
||||||
|
return WORKBENCH_TYPES.includes(value as WorkbenchType);
|
||||||
|
};
|
@ -31,7 +31,7 @@ export const executeToolOperationWithPrefix = async (
|
|||||||
): Promise<File[]> => {
|
): Promise<File[]> => {
|
||||||
console.log(`🔧 Executing tool: ${operationName}`, { parameters, fileCount: files.length });
|
console.log(`🔧 Executing tool: ${operationName}`, { parameters, fileCount: files.length });
|
||||||
|
|
||||||
const config = toolRegistry[operationName]?.operationConfig;
|
const config = toolRegistry[operationName as keyof ToolRegistry]?.operationConfig;
|
||||||
if (!config) {
|
if (!config) {
|
||||||
console.error(`❌ Tool operation not supported: ${operationName}`);
|
console.error(`❌ Tool operation not supported: ${operationName}`);
|
||||||
throw new Error(`Tool operation not supported: ${operationName}`);
|
throw new Error(`Tool operation not supported: ${operationName}`);
|
||||||
|
28
frontend/src/utils/scarfTracking.ts
Normal file
28
frontend/src/utils/scarfTracking.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
let lastFiredPathname: string | null = null;
|
||||||
|
let lastFiredTime = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire scarf pixel for analytics tracking
|
||||||
|
* Only fires if pathname is different from last call or enough time has passed
|
||||||
|
*/
|
||||||
|
export function firePixel(pathname: string): void {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// Only fire if pathname changed or it's been at least 1 second since last fire
|
||||||
|
if (pathname === lastFiredPathname && now - lastFiredTime < 250) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastFiredPathname = pathname;
|
||||||
|
lastFiredTime = now;
|
||||||
|
|
||||||
|
const url = 'https://static.scarf.sh/a.png?x-pxid=3c1d68de-8945-4e9f-873f-65320b6fabf7'
|
||||||
|
+ '&path=' + encodeURIComponent(pathname)
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
img.referrerPolicy = "no-referrer-when-downgrade";
|
||||||
|
img.src = url;
|
||||||
|
|
||||||
|
console.log("ScarfPixel: Fire to... " + pathname);
|
||||||
|
}
|
||||||
|
|
33
frontend/src/utils/urlMapping.ts
Normal file
33
frontend/src/utils/urlMapping.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { ToolId } from '../types/toolId';
|
||||||
|
|
||||||
|
// Map URL paths to tool keys (multiple URLs can map to same tool)
|
||||||
|
export const URL_TO_TOOL_MAP: Record<string, ToolId> = {
|
||||||
|
'/split-pdfs': 'split',
|
||||||
|
'/split': 'split',
|
||||||
|
'/merge-pdfs': 'mergePdfs',
|
||||||
|
'/compress-pdf': 'compress',
|
||||||
|
'/convert': 'convert',
|
||||||
|
'/convert-pdf': 'convert',
|
||||||
|
'/file-to-pdf': 'convert',
|
||||||
|
'/eml-to-pdf': 'convert',
|
||||||
|
'/html-to-pdf': 'convert',
|
||||||
|
'/markdown-to-pdf': 'convert',
|
||||||
|
'/pdf-to-csv': 'convert',
|
||||||
|
'/pdf-to-img': 'convert',
|
||||||
|
'/pdf-to-markdown': 'convert',
|
||||||
|
'/pdf-to-pdfa': 'convert',
|
||||||
|
'/pdf-to-word': 'convert',
|
||||||
|
'/pdf-to-xml': 'convert',
|
||||||
|
'/add-password': 'addPassword',
|
||||||
|
'/change-permissions': 'change-permissions',
|
||||||
|
'/sanitize-pdf': 'sanitize',
|
||||||
|
'/ocr': 'ocr',
|
||||||
|
'/ocr-pdf': 'ocr',
|
||||||
|
'/add-watermark': 'addWatermark',
|
||||||
|
'/remove-password': 'remove-password',
|
||||||
|
'/single-large-page': 'single-large-page',
|
||||||
|
'/repair': 'repair',
|
||||||
|
'/unlock-pdf-forms': 'unlock-pdf-forms',
|
||||||
|
'/remove-certificate-sign': 'remove-certificate-sign',
|
||||||
|
'/remove-cert-sign': 'remove-certificate-sign'
|
||||||
|
};
|
@ -1,167 +1,127 @@
|
|||||||
/**
|
/**
|
||||||
* URL routing utilities for tool navigation
|
* URL routing utilities for tool navigation with registry support
|
||||||
* Provides clean URL routing for the V2 tool system
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ModeType, isValidMode as isValidModeType, getDefaultMode, ToolRoute } from '../types/navigation';
|
import { ToolRoute } from '../types/navigation';
|
||||||
|
import { ToolId, isValidToolId } from '../types/toolId';
|
||||||
|
import { getDefaultWorkbench } from '../types/workbench';
|
||||||
|
import { ToolRegistry, getToolWorkbench, getToolUrlPath } from '../data/toolsTaxonomy';
|
||||||
|
import { firePixel } from './scarfTracking';
|
||||||
|
import { URL_TO_TOOL_MAP } from './urlMapping';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the current URL to extract tool routing information
|
* Parse the current URL to extract tool routing information
|
||||||
*/
|
*/
|
||||||
export function parseToolRoute(): ToolRoute {
|
export function parseToolRoute(registry: ToolRegistry): ToolRoute {
|
||||||
const path = window.location.pathname;
|
const path = window.location.pathname;
|
||||||
const searchParams = new URLSearchParams(window.location.search);
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
// Extract tool from URL path (e.g., /split-pdf -> split)
|
// First, check URL mapping for multiple URL aliases
|
||||||
const toolMatch = path.match(/\/([a-zA-Z-]+)(?:-pdf)?$/);
|
const mappedToolId = URL_TO_TOOL_MAP[path];
|
||||||
if (toolMatch) {
|
if (mappedToolId && registry[mappedToolId]) {
|
||||||
const toolKey = toolMatch[1].toLowerCase();
|
const tool = registry[mappedToolId];
|
||||||
|
|
||||||
// Map URL paths to tool keys and modes (excluding internal UI modes)
|
|
||||||
const toolMappings: Record<string, { mode: ModeType; toolKey: string }> = {
|
|
||||||
'split': { mode: 'split', toolKey: 'split' },
|
|
||||||
'merge': { mode: 'merge', toolKey: 'merge' },
|
|
||||||
'compress': { mode: 'compress', toolKey: 'compress' },
|
|
||||||
'convert': { mode: 'convert', toolKey: 'convert' },
|
|
||||||
'add-password': { mode: 'addPassword', toolKey: 'addPassword' },
|
|
||||||
'change-permissions': { mode: 'changePermissions', toolKey: 'changePermissions' },
|
|
||||||
'sanitize': { mode: 'sanitize', toolKey: 'sanitize' },
|
|
||||||
'ocr': { mode: 'ocr', toolKey: 'ocr' }
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapping = toolMappings[toolKey];
|
|
||||||
if (mapping) {
|
|
||||||
return {
|
return {
|
||||||
mode: mapping.mode,
|
workbench: getToolWorkbench(tool),
|
||||||
toolKey: mapping.toolKey
|
toolId: mappedToolId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Try to find tool by primary URL path in registry
|
||||||
|
for (const [toolId, tool] of Object.entries(registry)) {
|
||||||
|
const toolUrlPath = getToolUrlPath(toolId, tool);
|
||||||
|
if (path === toolUrlPath && isValidToolId(toolId)) {
|
||||||
|
return {
|
||||||
|
workbench: getToolWorkbench(tool),
|
||||||
|
toolId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for query parameter fallback (e.g., ?tool=split)
|
// Check for query parameter fallback (e.g., ?tool=split)
|
||||||
const toolParam = searchParams.get('tool');
|
const toolParam = searchParams.get('tool');
|
||||||
if (toolParam && isValidModeType(toolParam)) {
|
if (toolParam && isValidToolId(toolParam) && registry[toolParam]) {
|
||||||
|
const tool = registry[toolParam];
|
||||||
return {
|
return {
|
||||||
mode: toolParam as ModeType,
|
workbench: getToolWorkbench(tool),
|
||||||
toolKey: toolParam
|
toolId: toolParam
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to page editor for home page
|
// Default to fileEditor workbench for home page
|
||||||
return {
|
return {
|
||||||
mode: getDefaultMode(),
|
workbench: getDefaultWorkbench(),
|
||||||
toolKey: null
|
toolId: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the URL to reflect the current tool selection
|
* Update URL and fire analytics pixel
|
||||||
* Internal UI modes (viewer, fileEditor, pageEditor) don't get URLs
|
|
||||||
*/
|
*/
|
||||||
export function updateToolRoute(mode: ModeType, toolKey?: string): void {
|
function updateUrl(newPath: string, searchParams: URLSearchParams, replace: boolean = false): void {
|
||||||
const currentPath = window.location.pathname;
|
const currentPath = window.location.pathname;
|
||||||
const searchParams = new URLSearchParams(window.location.search);
|
const queryString = searchParams.toString();
|
||||||
|
const fullUrl = newPath + (queryString ? `?${queryString}` : '');
|
||||||
|
|
||||||
// Don't create URLs for internal UI modes
|
// Only update URL and fire pixel if something actually changed
|
||||||
if (mode === 'viewer' || mode === 'fileEditor' || mode === 'pageEditor') {
|
if (currentPath !== newPath || window.location.search !== (queryString ? `?${queryString}` : '')) {
|
||||||
// If we're switching to an internal mode, clear any existing tool URL
|
if (replace) {
|
||||||
if (currentPath !== '/') {
|
window.history.replaceState(null, '', fullUrl);
|
||||||
clearToolRoute();
|
} else {
|
||||||
|
window.history.pushState(null, '', fullUrl);
|
||||||
}
|
}
|
||||||
|
firePixel(newPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the URL to reflect the current tool selection
|
||||||
|
*/
|
||||||
|
export function updateToolRoute(toolId: ToolId, registry: ToolRegistry, replace: boolean = false): void {
|
||||||
|
const tool = registry[toolId];
|
||||||
|
if (!tool) {
|
||||||
|
console.warn(`Tool ${toolId} not found in registry`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let newPath = '/';
|
const newPath = getToolUrlPath(toolId, tool);
|
||||||
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
// Map modes to URL paths (only for actual tools)
|
|
||||||
if (toolKey) {
|
|
||||||
const pathMappings: Record<string, string> = {
|
|
||||||
'split': '/split-pdf',
|
|
||||||
'merge': '/merge-pdf',
|
|
||||||
'compress': '/compress-pdf',
|
|
||||||
'convert': '/convert-pdf',
|
|
||||||
'addPassword': '/add-password-pdf',
|
|
||||||
'changePermissions': '/change-permissions-pdf',
|
|
||||||
'sanitize': '/sanitize-pdf',
|
|
||||||
'ocr': '/ocr-pdf'
|
|
||||||
};
|
|
||||||
|
|
||||||
newPath = pathMappings[toolKey] || `/${toolKey}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove tool query parameter since we're using path-based routing
|
// Remove tool query parameter since we're using path-based routing
|
||||||
searchParams.delete('tool');
|
searchParams.delete('tool');
|
||||||
|
|
||||||
// Construct final URL
|
updateUrl(newPath, searchParams, replace);
|
||||||
const queryString = searchParams.toString();
|
|
||||||
const fullUrl = newPath + (queryString ? `?${queryString}` : '');
|
|
||||||
|
|
||||||
// Update URL without triggering page reload
|
|
||||||
if (currentPath !== newPath || window.location.search !== (queryString ? `?${queryString}` : '')) {
|
|
||||||
window.history.replaceState(null, '', fullUrl);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear tool routing and return to home page
|
* Clear tool routing and return to home page
|
||||||
*/
|
*/
|
||||||
export function clearToolRoute(): void {
|
export function clearToolRoute(replace: boolean = false): void {
|
||||||
const searchParams = new URLSearchParams(window.location.search);
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
searchParams.delete('tool');
|
searchParams.delete('tool');
|
||||||
|
|
||||||
const queryString = searchParams.toString();
|
updateUrl('/', searchParams, replace);
|
||||||
const url = '/' + (queryString ? `?${queryString}` : '');
|
|
||||||
|
|
||||||
window.history.replaceState(null, '', url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get clean tool name for display purposes
|
* Get clean tool name for display purposes using registry
|
||||||
*/
|
*/
|
||||||
export function getToolDisplayName(toolKey: string): string {
|
export function getToolDisplayName(toolId: ToolId, registry: ToolRegistry): string {
|
||||||
const displayNames: Record<string, string> = {
|
const tool = registry[toolId];
|
||||||
'split': 'Split PDF',
|
return tool ? tool.name : toolId;
|
||||||
'merge': 'Merge PDF',
|
|
||||||
'compress': 'Compress PDF',
|
|
||||||
'convert': 'Convert PDF',
|
|
||||||
'addPassword': 'Add Password',
|
|
||||||
'changePermissions': 'Change Permissions',
|
|
||||||
'sanitize': 'Sanitize PDF',
|
|
||||||
'ocr': 'OCR PDF'
|
|
||||||
};
|
|
||||||
|
|
||||||
return displayNames[toolKey] || toolKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: isValidMode is now imported from types/navigation.ts
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate shareable URL for current tool state
|
* Generate shareable URL for current tool state using registry
|
||||||
* Only generates URLs for actual tools, not internal UI modes
|
|
||||||
*/
|
*/
|
||||||
export function generateShareableUrl(mode: ModeType, toolKey?: string): string {
|
export function generateShareableUrl(toolId: ToolId | null, registry: ToolRegistry): string {
|
||||||
const baseUrl = window.location.origin;
|
const baseUrl = window.location.origin;
|
||||||
|
|
||||||
// Don't generate URLs for internal UI modes
|
if (!toolId || !registry[toolId]) {
|
||||||
if (mode === 'viewer' || mode === 'fileEditor' || mode === 'pageEditor') {
|
|
||||||
return baseUrl;
|
return baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolKey) {
|
const tool = registry[toolId];
|
||||||
const pathMappings: Record<string, string> = {
|
|
||||||
'split': '/split-pdf',
|
|
||||||
'merge': '/merge-pdf',
|
|
||||||
'compress': '/compress-pdf',
|
|
||||||
'convert': '/convert-pdf',
|
|
||||||
'addPassword': '/add-password-pdf',
|
|
||||||
'changePermissions': '/change-permissions-pdf',
|
|
||||||
'sanitize': '/sanitize-pdf',
|
|
||||||
'ocr': '/ocr-pdf'
|
|
||||||
};
|
|
||||||
|
|
||||||
const path = pathMappings[toolKey] || `/${toolKey}`;
|
const path = getToolUrlPath(toolId, tool);
|
||||||
return `${baseUrl}${path}`;
|
return `${baseUrl}${path}`;
|
||||||
}
|
|
||||||
|
|
||||||
return baseUrl;
|
|
||||||
}
|
}
|
10
frontend/vite-env.d.ts
vendored
Normal file
10
frontend/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_PUBLIC_POSTHOG_KEY: string;
|
||||||
|
readonly VITE_PUBLIC_POSTHOG_HOST: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user