mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
Refactor to fix circular imports (#4700)
# Description of Changes Refactors code to avoid circular imports everywhere and adds linting for circular imports to ensure it doesn't happen again. Most changes are around the tool registry, making it a provider, and splitting into tool types to make it easier for things like Automate to only have access to tools excluding itself.
This commit is contained in:
parent
3e23dc59b6
commit
c9eee00d66
@ -1,9 +1,10 @@
|
||||
// @ts-check
|
||||
|
||||
import eslint from '@eslint/js';
|
||||
import globals from "globals";
|
||||
import globals from 'globals';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import importPlugin from 'eslint-plugin-import';
|
||||
|
||||
const srcGlobs = [
|
||||
'src/**/*.{js,mjs,jsx,ts,tsx}',
|
||||
@ -14,35 +15,37 @@ const nodeGlobs = [
|
||||
];
|
||||
|
||||
export default defineConfig(
|
||||
{
|
||||
// Everything that contains 3rd party code that we don't want to lint
|
||||
ignores: [
|
||||
'dist',
|
||||
'node_modules',
|
||||
'public',
|
||||
],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
{
|
||||
ignores: [
|
||||
"dist", // Contains 3rd party code
|
||||
"public", // Contains 3rd party code
|
||||
],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
"@typescript-eslint/no-empty-object-type": [
|
||||
"error",
|
||||
'@typescript-eslint/no-empty-object-type': [
|
||||
'error',
|
||||
{
|
||||
// Allow empty extending interfaces because there's no real reason not to, and it makes it obvious where to put extra attributes in the future
|
||||
allowInterfaces: 'with-single-extends',
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": "off", // Temporarily disabled until codebase conformant
|
||||
"@typescript-eslint/no-require-imports": "off", // Temporarily disabled until codebase conformant
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
'@typescript-eslint/no-explicit-any': 'off', // Temporarily disabled until codebase conformant
|
||||
'@typescript-eslint/no-require-imports': 'off', // Temporarily disabled until codebase conformant
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
"args": "all", // All function args must be used (or explicitly ignored)
|
||||
"argsIgnorePattern": "^_", // Allow unused variables beginning with an underscore
|
||||
"caughtErrors": "all", // Caught errors must be used (or explicitly ignored)
|
||||
"caughtErrorsIgnorePattern": "^_", // Allow unused variables beginning with an underscore
|
||||
"destructuredArrayIgnorePattern": "^_", // Allow unused variables beginning with an underscore
|
||||
"varsIgnorePattern": "^_", // Allow unused variables beginning with an underscore
|
||||
"ignoreRestSiblings": true, // Allow unused variables when removing attributes from objects (otherwise this requires explicit renaming like `({ x: _x, ...y }) => y`, which is clunky)
|
||||
'args': 'all', // All function args must be used (or explicitly ignored)
|
||||
'argsIgnorePattern': '^_', // Allow unused variables beginning with an underscore
|
||||
'caughtErrors': 'all', // Caught errors must be used (or explicitly ignored)
|
||||
'caughtErrorsIgnorePattern': '^_', // Allow unused variables beginning with an underscore
|
||||
'destructuredArrayIgnorePattern': '^_', // Allow unused variables beginning with an underscore
|
||||
'varsIgnorePattern': '^_', // Allow unused variables beginning with an underscore
|
||||
'ignoreRestSiblings': true, // Allow unused variables when removing attributes from objects (otherwise this requires explicit renaming like `({ x: _x, ...y }) => y`, which is clunky)
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -65,4 +68,21 @@ export default defineConfig(
|
||||
}
|
||||
}
|
||||
},
|
||||
// Config for import plugin
|
||||
{
|
||||
...importPlugin.flatConfigs.recommended,
|
||||
...importPlugin.flatConfigs.typescript,
|
||||
rules: {
|
||||
// ...importPlugin.flatConfigs.recommended.rules, // Temporarily disabled until codebase conformant
|
||||
...importPlugin.flatConfigs.typescript.rules,
|
||||
'import/no-cycle': 'error',
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
2083
frontend/package-lock.json
generated
2083
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -121,6 +121,8 @@
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"jsdom": "^27.0.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"madge": "^8.0.0",
|
||||
|
||||
@ -2,6 +2,7 @@ import { Suspense } from "react";
|
||||
import { RainbowThemeProvider } from "./components/shared/RainbowThemeProvider";
|
||||
import { FileContextProvider } from "./contexts/FileContext";
|
||||
import { NavigationProvider } from "./contexts/NavigationContext";
|
||||
import { ToolRegistryProvider } from "./contexts/ToolRegistryProvider";
|
||||
import { FilesModalProvider } from "./contexts/FilesModalContext";
|
||||
import { ToolWorkflowProvider } from "./contexts/ToolWorkflowContext";
|
||||
import { HotkeyProvider } from "./contexts/HotkeyContext";
|
||||
@ -48,26 +49,28 @@ export default function App() {
|
||||
<ErrorBoundary>
|
||||
<OnboardingProvider>
|
||||
<FileContextProvider enableUrlSync={true} enablePersistence={true}>
|
||||
<NavigationProvider>
|
||||
<FilesModalProvider>
|
||||
<ToolWorkflowProvider>
|
||||
<HotkeyProvider>
|
||||
<SidebarProvider>
|
||||
<ViewerProvider>
|
||||
<SignatureProvider>
|
||||
<RightRailProvider>
|
||||
<TourOrchestrationProvider>
|
||||
<HomePage />
|
||||
<OnboardingTour />
|
||||
</TourOrchestrationProvider>
|
||||
</RightRailProvider>
|
||||
</SignatureProvider>
|
||||
</ViewerProvider>
|
||||
</SidebarProvider>
|
||||
</HotkeyProvider>
|
||||
</ToolWorkflowProvider>
|
||||
</FilesModalProvider>
|
||||
</NavigationProvider>
|
||||
<ToolRegistryProvider>
|
||||
<NavigationProvider>
|
||||
<FilesModalProvider>
|
||||
<ToolWorkflowProvider>
|
||||
<HotkeyProvider>
|
||||
<SidebarProvider>
|
||||
<ViewerProvider>
|
||||
<SignatureProvider>
|
||||
<RightRailProvider>
|
||||
<TourOrchestrationProvider>
|
||||
<HomePage />
|
||||
<OnboardingTour />
|
||||
</TourOrchestrationProvider>
|
||||
</RightRailProvider>
|
||||
</SignatureProvider>
|
||||
</ViewerProvider>
|
||||
</SidebarProvider>
|
||||
</HotkeyProvider>
|
||||
</ToolWorkflowProvider>
|
||||
</FilesModalProvider>
|
||||
</NavigationProvider>
|
||||
</ToolRegistryProvider>
|
||||
</FileContextProvider>
|
||||
</OnboardingProvider>
|
||||
</ErrorBoundary>
|
||||
|
||||
@ -25,7 +25,7 @@ interface AutomationCreationProps {
|
||||
existingAutomation?: AutomationConfig;
|
||||
onBack: () => void;
|
||||
onComplete: (automation: AutomationConfig) => void;
|
||||
toolRegistry: ToolRegistry;
|
||||
toolRegistry: Partial<ToolRegistry>;
|
||||
}
|
||||
|
||||
export default function AutomationCreation({ mode, existingAutomation, onBack, onComplete, toolRegistry }: AutomationCreationProps) {
|
||||
|
||||
@ -7,7 +7,7 @@ import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import { Tooltip } from '../../shared/Tooltip';
|
||||
import { ToolIcon } from '../../shared/ToolIcon';
|
||||
import { ToolRegistryEntry } from '../../../data/toolsTaxonomy';
|
||||
import { ToolRegistry } from '../../../data/toolsTaxonomy';
|
||||
import { ToolId } from 'src/types/toolId';
|
||||
|
||||
interface AutomationEntryProps {
|
||||
@ -32,7 +32,7 @@ interface AutomationEntryProps {
|
||||
/** Copy handler (for suggested automations) */
|
||||
onCopy?: () => void;
|
||||
/** Tool registry to resolve operation names */
|
||||
toolRegistry?: Record<ToolId, ToolRegistryEntry>;
|
||||
toolRegistry?: Partial<ToolRegistry>;
|
||||
}
|
||||
|
||||
export default function AutomationEntry({
|
||||
@ -56,8 +56,9 @@ export default function AutomationEntry({
|
||||
|
||||
// Helper function to resolve tool display names
|
||||
const getToolDisplayName = (operation: string): string => {
|
||||
if (toolRegistry?.[operation as ToolId]?.name) {
|
||||
return toolRegistry[operation as ToolId].name;
|
||||
const entry = toolRegistry?.[operation as ToolId];
|
||||
if (entry?.name) {
|
||||
return entry.name;
|
||||
}
|
||||
// Fallback to translation or operation key
|
||||
return t(`${operation}.title`, operation);
|
||||
|
||||
@ -4,7 +4,7 @@ import { Button, Text, Stack, Group, Card, Progress } from "@mantine/core";
|
||||
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||
import CheckIcon from "@mui/icons-material/Check";
|
||||
import { useFileSelection } from "../../../contexts/FileContext";
|
||||
import { useFlatToolRegistry } from "../../../data/useTranslatedToolRegistry";
|
||||
import { useToolRegistry } from "../../../contexts/ToolRegistryContext";
|
||||
import { AutomationConfig, ExecutionStep } from "../../../types/automation";
|
||||
import { AUTOMATION_CONSTANTS, EXECUTION_STATUS } from "../../../constants/automation";
|
||||
import { useResourceCleanup } from "../../../utils/resourceManager";
|
||||
@ -18,7 +18,8 @@ interface AutomationRunProps {
|
||||
export default function AutomationRun({ automation, onComplete, automateOperation }: AutomationRunProps) {
|
||||
const { t } = useTranslation();
|
||||
const { selectedFiles } = useFileSelection();
|
||||
const toolRegistry = useFlatToolRegistry();
|
||||
const { regularTools } = useToolRegistry();
|
||||
const toolRegistry = regularTools;
|
||||
const cleanup = useResourceCleanup();
|
||||
|
||||
// Progress tracking state
|
||||
|
||||
@ -6,8 +6,7 @@ import AutomationEntry from "./AutomationEntry";
|
||||
import { useSuggestedAutomations } from "../../../hooks/tools/automate/useSuggestedAutomations";
|
||||
import { AutomationConfig, SuggestedAutomation } from "../../../types/automation";
|
||||
import { iconMap } from './iconMap';
|
||||
import { ToolRegistryEntry } from '../../../data/toolsTaxonomy';
|
||||
import { ToolId } from '../../../types/toolId';
|
||||
import { ToolRegistry } from '../../../data/toolsTaxonomy';
|
||||
|
||||
interface AutomationSelectionProps {
|
||||
savedAutomations: AutomationConfig[];
|
||||
@ -16,7 +15,7 @@ interface AutomationSelectionProps {
|
||||
onEdit: (automation: AutomationConfig) => void;
|
||||
onDelete: (automation: AutomationConfig) => void;
|
||||
onCopyFromSuggested: (automation: SuggestedAutomation) => void;
|
||||
toolRegistry: Record<ToolId, ToolRegistryEntry>;
|
||||
toolRegistry: Partial<ToolRegistry>;
|
||||
}
|
||||
|
||||
export default function AutomationSelection({
|
||||
|
||||
@ -15,6 +15,7 @@ import CheckIcon from '@mui/icons-material/Check';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { ToolRegistry } from '../../../data/toolsTaxonomy';
|
||||
import { ToolId } from '../../../types/toolId';
|
||||
import { getAvailableToExtensions } from '../../../utils/convertUtils';
|
||||
interface ToolConfigurationModalProps {
|
||||
opened: boolean;
|
||||
@ -26,7 +27,7 @@ interface ToolConfigurationModalProps {
|
||||
};
|
||||
onSave: (parameters: any) => void;
|
||||
onCancel: () => void;
|
||||
toolRegistry: ToolRegistry;
|
||||
toolRegistry: Partial<ToolRegistry>;
|
||||
}
|
||||
|
||||
export default function ToolConfigurationModal({ opened, tool, onSave, onCancel, toolRegistry }: ToolConfigurationModalProps) {
|
||||
@ -35,7 +36,7 @@ export default function ToolConfigurationModal({ opened, tool, onSave, onCancel,
|
||||
const [parameters, setParameters] = useState<any>({});
|
||||
|
||||
// Get tool info from registry
|
||||
const toolInfo = toolRegistry[tool.operation as keyof ToolRegistry];
|
||||
const toolInfo = toolRegistry[tool.operation as ToolId];
|
||||
const SettingsComponent = toolInfo?.automationSettings;
|
||||
|
||||
// Initialize parameters from tool (which should contain defaults from registry)
|
||||
|
||||
@ -5,14 +5,14 @@ import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import AddCircleOutline from "@mui/icons-material/AddCircleOutline";
|
||||
import { AutomationTool } from "../../../types/automation";
|
||||
import { ToolRegistryEntry } from "../../../data/toolsTaxonomy";
|
||||
import { ToolRegistry } from "../../../data/toolsTaxonomy";
|
||||
import { ToolId } from "../../../types/toolId";
|
||||
import ToolSelector from "./ToolSelector";
|
||||
import AutomationEntry from "./AutomationEntry";
|
||||
|
||||
interface ToolListProps {
|
||||
tools: AutomationTool[];
|
||||
toolRegistry: Record<ToolId, ToolRegistryEntry>;
|
||||
toolRegistry: Partial<ToolRegistry>;
|
||||
onToolUpdate: (index: number, updates: Partial<AutomationTool>) => void;
|
||||
onToolRemove: (index: number) => void;
|
||||
onToolConfigure: (index: number) => void;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useState, useMemo, useCallback, useRef, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Stack, Text, ScrollArea } from '@mantine/core';
|
||||
import { ToolRegistryEntry, getToolSupportsAutomate } from '../../../data/toolsTaxonomy';
|
||||
import { ToolRegistryEntry, ToolRegistry, getToolSupportsAutomate } from '../../../data/toolsTaxonomy';
|
||||
import { useToolSections } from '../../../hooks/useToolSections';
|
||||
import { renderToolButtons } from '../shared/renderToolButtons';
|
||||
import ToolSearch from '../toolPicker/ToolSearch';
|
||||
@ -11,7 +11,7 @@ import { ToolId } from '../../../types/toolId';
|
||||
interface ToolSelectorProps {
|
||||
onSelect: (toolKey: string) => void;
|
||||
excludeTools?: string[];
|
||||
toolRegistry: Record<ToolId, ToolRegistryEntry>; // Pass registry as prop to break circular dependency
|
||||
toolRegistry: Partial<ToolRegistry>; // Pass registry as prop to break circular dependency
|
||||
selectedValue?: string; // For showing current selection when editing existing tool
|
||||
placeholder?: string; // Custom placeholder text
|
||||
}
|
||||
@ -54,7 +54,7 @@ export default function ToolSelector({
|
||||
|
||||
// Create filtered tool registry for ToolSearch
|
||||
const filteredToolRegistry = useMemo(() => {
|
||||
const registry: Record<ToolId, ToolRegistryEntry> = {} as Record<ToolId, ToolRegistryEntry>;
|
||||
const registry: Partial<ToolRegistry> = {};
|
||||
baseFilteredTools.forEach(([key, tool]) => {
|
||||
registry[key as ToolId] = tool;
|
||||
});
|
||||
@ -142,10 +142,10 @@ export default function ToolSelector({
|
||||
};
|
||||
|
||||
// Get display value for selected tool
|
||||
const selectedTool = selectedValue ? toolRegistry[selectedValue as ToolId] : undefined;
|
||||
|
||||
const getDisplayValue = () => {
|
||||
if (selectedValue && toolRegistry[selectedValue as ToolId]) {
|
||||
return toolRegistry[selectedValue as ToolId].name;
|
||||
}
|
||||
if (selectedTool) return selectedTool.name;
|
||||
return placeholder || t('automate.creation.tools.add', 'Add a tool...');
|
||||
};
|
||||
|
||||
@ -153,12 +153,18 @@ export default function ToolSelector({
|
||||
<div ref={containerRef} className='rounded-xl'>
|
||||
{/* Always show the target - either selected tool or search input */}
|
||||
|
||||
{selectedValue && toolRegistry[selectedValue as ToolId] && !opened ? (
|
||||
{selectedTool && !opened ? (
|
||||
// Show selected tool in AutomationEntry style when tool is selected and dropdown closed
|
||||
<div onClick={handleSearchFocus} style={{ cursor: 'pointer',
|
||||
borderRadius: "var(--mantine-radius-lg)" }}>
|
||||
<ToolButton id={'tool' as ToolId} tool={toolRegistry[selectedValue as ToolId]} isSelected={false}
|
||||
onSelect={()=>{}} rounded={true} disableNavigation={true}></ToolButton>
|
||||
<ToolButton
|
||||
id={'tool' as ToolId}
|
||||
tool={selectedTool}
|
||||
isSelected={false}
|
||||
onSelect={()=>{}}
|
||||
rounded={true}
|
||||
disableNavigation={true}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
// Show search input when no tool selected OR when dropdown is opened
|
||||
|
||||
@ -3,13 +3,7 @@ import { useHistoryCapability } from '@embedpdf/plugin-history/react';
|
||||
import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
|
||||
import { useSignature } from '../../contexts/SignatureContext';
|
||||
import { uuidV4 } from '@embedpdf/models';
|
||||
|
||||
export interface HistoryAPI {
|
||||
undo: () => void;
|
||||
redo: () => void;
|
||||
canUndo: () => boolean;
|
||||
canRedo: () => boolean;
|
||||
}
|
||||
import type { HistoryAPI } from './viewerTypes';
|
||||
|
||||
export const HistoryAPIBridge = forwardRef<HistoryAPI>(function HistoryAPIBridge(_, ref) {
|
||||
const { provides: historyApi } = useHistoryCapability();
|
||||
@ -42,7 +36,7 @@ export const HistoryAPIBridge = forwardRef<HistoryAPI>(function HistoryAPIBridge
|
||||
const currentStoredData = getImageData(annotation.id);
|
||||
// Check if the annotation lacks image data but we have it stored
|
||||
if (currentStoredData && (!annotation.imageSrc || annotation.imageSrc !== currentStoredData)) {
|
||||
|
||||
|
||||
// Generate new ID to avoid React key conflicts
|
||||
const newId = uuidV4();
|
||||
|
||||
@ -113,4 +107,4 @@ export const HistoryAPIBridge = forwardRef<HistoryAPI>(function HistoryAPIBridge
|
||||
return null; // This is a bridge component with no UI
|
||||
});
|
||||
|
||||
HistoryAPIBridge.displayName = 'HistoryAPIBridge';
|
||||
HistoryAPIBridge.displayName = 'HistoryAPIBridge';
|
||||
|
||||
@ -34,8 +34,9 @@ import { SpreadAPIBridge } from './SpreadAPIBridge';
|
||||
import { SearchAPIBridge } from './SearchAPIBridge';
|
||||
import { ThumbnailAPIBridge } from './ThumbnailAPIBridge';
|
||||
import { RotateAPIBridge } from './RotateAPIBridge';
|
||||
import { SignatureAPIBridge, SignatureAPI } from './SignatureAPIBridge';
|
||||
import { HistoryAPIBridge, HistoryAPI } from './HistoryAPIBridge';
|
||||
import { SignatureAPIBridge } from './SignatureAPIBridge';
|
||||
import { HistoryAPIBridge } from './HistoryAPIBridge';
|
||||
import type { SignatureAPI, HistoryAPI } from './viewerTypes';
|
||||
import { ExportAPIBridge } from './ExportAPIBridge';
|
||||
|
||||
interface LocalEmbedPDFProps {
|
||||
|
||||
@ -2,17 +2,7 @@ import { useImperativeHandle, forwardRef, useEffect } from 'react';
|
||||
import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
|
||||
import { PdfAnnotationSubtype, uuidV4 } from '@embedpdf/models';
|
||||
import { useSignature } from '../../contexts/SignatureContext';
|
||||
|
||||
export interface SignatureAPI {
|
||||
addImageSignature: (signatureData: string, x: number, y: number, width: number, height: number, pageIndex: number) => void;
|
||||
activateDrawMode: () => void;
|
||||
activateSignaturePlacementMode: () => void;
|
||||
activateDeleteMode: () => void;
|
||||
deleteAnnotation: (annotationId: string, pageIndex: number) => void;
|
||||
updateDrawSettings: (color: string, size: number) => void;
|
||||
deactivateTools: () => void;
|
||||
getPageAnnotations: (pageIndex: number) => Promise<any[]>;
|
||||
}
|
||||
import type { SignatureAPI } from './viewerTypes';
|
||||
|
||||
export const SignatureAPIBridge = forwardRef<SignatureAPI>(function SignatureAPIBridge(_, ref) {
|
||||
const { provides: annotationApi } = useAnnotationCapability();
|
||||
@ -246,4 +236,4 @@ export const SignatureAPIBridge = forwardRef<SignatureAPI>(function SignatureAPI
|
||||
return null; // This is a bridge component with no UI
|
||||
});
|
||||
|
||||
SignatureAPIBridge.displayName = 'SignatureAPIBridge';
|
||||
SignatureAPIBridge.displayName = 'SignatureAPIBridge';
|
||||
|
||||
24
frontend/src/components/viewer/viewerTypes.ts
Normal file
24
frontend/src/components/viewer/viewerTypes.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export interface SignatureAPI {
|
||||
addImageSignature: (
|
||||
signatureData: string,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
pageIndex: number
|
||||
) => void;
|
||||
activateDrawMode: () => void;
|
||||
activateSignaturePlacementMode: () => void;
|
||||
activateDeleteMode: () => void;
|
||||
deleteAnnotation: (annotationId: string, pageIndex: number) => void;
|
||||
updateDrawSettings: (color: string, size: number) => void;
|
||||
deactivateTools: () => void;
|
||||
getPageAnnotations: (pageIndex: number) => Promise<any[]>;
|
||||
}
|
||||
|
||||
export interface HistoryAPI {
|
||||
undo: () => void;
|
||||
redo: () => void;
|
||||
canUndo: () => boolean;
|
||||
canRedo: () => boolean;
|
||||
}
|
||||
@ -1,10 +1,3 @@
|
||||
import { useAppConfig } from '../hooks/useAppConfig';
|
||||
|
||||
// Get base URL from app config with fallback
|
||||
export const getBaseUrl = (): string => {
|
||||
const { config } = useAppConfig();
|
||||
return config?.baseUrl || 'https://stirling.com';
|
||||
};
|
||||
|
||||
// Base path from Vite config - build-time constant, normalized (no trailing slash)
|
||||
// When no subpath, use empty string instead of '.' to avoid relative path issues
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { createContext, useContext, useReducer, useCallback } from 'react';
|
||||
import { WorkbenchType, getDefaultWorkbench } from '../types/workbench';
|
||||
import { ToolId, isValidToolId } from '../types/toolId';
|
||||
import { useFlatToolRegistry } from '../data/useTranslatedToolRegistry';
|
||||
import { useToolRegistry } from './ToolRegistryContext';
|
||||
|
||||
/**
|
||||
* NavigationContext - Complete navigation management system
|
||||
@ -107,7 +107,7 @@ export const NavigationProvider: React.FC<{
|
||||
enableUrlSync?: boolean;
|
||||
}> = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(navigationReducer, initialState);
|
||||
const toolRegistry = useFlatToolRegistry();
|
||||
const { allTools: toolRegistry } = useToolRegistry();
|
||||
const unsavedChangesCheckerRef = React.useRef<(() => boolean) | null>(null);
|
||||
|
||||
const actions: NavigationContextActions = {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import React, { createContext, useContext, useState, ReactNode, useCallback, useRef } from 'react';
|
||||
import { SignParameters } from '../hooks/tools/sign/useSignParameters';
|
||||
import { SignatureAPI } from '../components/viewer/SignatureAPIBridge';
|
||||
import { HistoryAPI } from '../components/viewer/HistoryAPIBridge';
|
||||
import type { SignatureAPI, HistoryAPI } from '../components/viewer/viewerTypes';
|
||||
|
||||
// Signature state interface
|
||||
interface SignatureState {
|
||||
@ -175,4 +174,4 @@ export const useSignatureMode = () => {
|
||||
isSignatureModeActive: context?.isPlacementMode || false,
|
||||
hasSignatureConfig: context?.signatureConfig !== null,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
30
frontend/src/contexts/ToolRegistryContext.tsx
Normal file
30
frontend/src/contexts/ToolRegistryContext.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
import type {
|
||||
ToolRegistryEntry,
|
||||
ToolRegistry,
|
||||
RegularToolRegistry,
|
||||
SuperToolRegistry,
|
||||
LinkToolRegistry,
|
||||
} from '../data/toolsTaxonomy';
|
||||
import type { ToolId } from '../types/toolId';
|
||||
|
||||
export interface ToolRegistryCatalog {
|
||||
regularTools: RegularToolRegistry;
|
||||
superTools: SuperToolRegistry;
|
||||
linkTools: LinkToolRegistry;
|
||||
allTools: ToolRegistry;
|
||||
getToolById: (toolId: ToolId | null) => ToolRegistryEntry | null;
|
||||
}
|
||||
|
||||
const ToolRegistryContext = createContext<ToolRegistryCatalog | null>(null);
|
||||
|
||||
export const useToolRegistry = (): ToolRegistryCatalog => {
|
||||
const context = useContext(ToolRegistryContext);
|
||||
if (context === null) {
|
||||
throw new Error('useToolRegistry must be used within a ToolRegistryProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export default ToolRegistryContext;
|
||||
44
frontend/src/contexts/ToolRegistryProvider.tsx
Normal file
44
frontend/src/contexts/ToolRegistryProvider.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import type { ToolId } from '../types/toolId';
|
||||
import type { ToolRegistry } from '../data/toolsTaxonomy';
|
||||
import ToolRegistryContext, { ToolRegistryCatalog } from './ToolRegistryContext';
|
||||
import { useTranslatedToolCatalog } from '../data/useTranslatedToolRegistry';
|
||||
|
||||
interface ToolRegistryProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ToolRegistryProvider: React.FC<ToolRegistryProviderProps> = ({ children }) => {
|
||||
const catalog = useTranslatedToolCatalog();
|
||||
|
||||
const contextValue = useMemo<ToolRegistryCatalog>(() => {
|
||||
const { regularTools, superTools, linkTools } = catalog;
|
||||
const allTools: ToolRegistry = {
|
||||
...regularTools,
|
||||
...superTools,
|
||||
...linkTools,
|
||||
};
|
||||
|
||||
const getToolById = (toolId: ToolId | null) => {
|
||||
if (!toolId) {
|
||||
return null;
|
||||
}
|
||||
return allTools[toolId] ?? null;
|
||||
};
|
||||
|
||||
return {
|
||||
regularTools,
|
||||
superTools,
|
||||
linkTools,
|
||||
allTools,
|
||||
getToolById,
|
||||
};
|
||||
}, [catalog]);
|
||||
|
||||
return (
|
||||
<ToolRegistryContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</ToolRegistryContext.Provider>
|
||||
);
|
||||
};
|
||||
@ -20,6 +20,7 @@ import {
|
||||
} from './toolWorkflow/toolWorkflowState';
|
||||
import type { ToolPanelMode } from '../constants/toolPanel';
|
||||
import { usePreferences } from './PreferencesContext';
|
||||
import { useToolRegistry } from './ToolRegistryContext';
|
||||
|
||||
// State interface
|
||||
// Types and reducer/state moved to './toolWorkflow/state'
|
||||
@ -111,10 +112,8 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
||||
const navigationState = useNavigationState();
|
||||
|
||||
// Tool management hook
|
||||
const {
|
||||
toolRegistry,
|
||||
getSelectedTool,
|
||||
} = useToolManagement();
|
||||
const { toolRegistry, getSelectedTool } = useToolManagement();
|
||||
const { allTools } = useToolRegistry();
|
||||
|
||||
// Tool history hook
|
||||
const {
|
||||
@ -315,7 +314,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
||||
// Filter tools based on search query with fuzzy matching (name, description, id, synonyms)
|
||||
const filteredTools = useMemo(() => {
|
||||
if (!toolRegistry) return [];
|
||||
return filterToolRegistryByQuery(toolRegistry as ToolRegistry, state.searchQuery);
|
||||
return filterToolRegistryByQuery(toolRegistry, state.searchQuery);
|
||||
}, [toolRegistry, state.searchQuery]);
|
||||
|
||||
const isPanelVisible = useMemo(() =>
|
||||
@ -327,7 +326,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
||||
navigationState.selectedTool,
|
||||
handleToolSelect,
|
||||
handleBackToTools,
|
||||
toolRegistry as ToolRegistry,
|
||||
allTools,
|
||||
true
|
||||
);
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import React from 'react';
|
||||
import { ToolOperationConfig } from '../hooks/tools/shared/useToolOperation';
|
||||
import { BaseToolProps } from '../types/tool';
|
||||
import { WorkbenchType } from '../types/workbench';
|
||||
import { ToolId } from '../types/toolId';
|
||||
import { LinkToolId, RegularToolId, SuperToolId, ToolId, ToolKind } from '../types/toolId';
|
||||
import DrawRoundedIcon from '@mui/icons-material/DrawRounded';
|
||||
import SecurityRoundedIcon from '@mui/icons-material/SecurityRounded';
|
||||
import VerifiedUserRoundedIcon from '@mui/icons-material/VerifiedUserRounded';
|
||||
@ -47,7 +47,7 @@ export type ToolRegistryEntry = {
|
||||
supportedFormats?: string[];
|
||||
endpoints?: string[];
|
||||
link?: string;
|
||||
type?: string;
|
||||
kind?: ToolKind;
|
||||
// Workbench type for navigation
|
||||
workbench?: WorkbenchType;
|
||||
// Operation configuration for automation
|
||||
@ -60,6 +60,9 @@ export type ToolRegistryEntry = {
|
||||
synonyms?: string[];
|
||||
}
|
||||
|
||||
export type RegularToolRegistry = Record<RegularToolId, ToolRegistryEntry>;
|
||||
export type SuperToolRegistry = Record<SuperToolId, ToolRegistryEntry>;
|
||||
export type LinkToolRegistry = Record<LinkToolId, ToolRegistryEntry>;
|
||||
export type ToolRegistry = Record<ToolId, ToolRegistryEntry>;
|
||||
|
||||
export const SUBCATEGORY_ORDER: SubcategoryId[] = [
|
||||
|
||||
@ -13,7 +13,15 @@ import RemovePages from "../tools/RemovePages";
|
||||
import ReorganizePages from "../tools/ReorganizePages";
|
||||
import { reorganizePagesOperationConfig } from "../hooks/tools/reorganizePages/useReorganizePagesOperation";
|
||||
import RemovePassword from "../tools/RemovePassword";
|
||||
import { SubcategoryId, ToolCategoryId, ToolRegistry } from "./toolsTaxonomy";
|
||||
import {
|
||||
SubcategoryId,
|
||||
ToolCategoryId,
|
||||
ToolRegistry,
|
||||
RegularToolRegistry,
|
||||
SuperToolRegistry,
|
||||
LinkToolRegistry,
|
||||
} from "./toolsTaxonomy";
|
||||
import { isSuperToolId, isLinkToolId } from '../types/toolId';
|
||||
import AdjustContrast from "../tools/AdjustContrast";
|
||||
import AdjustContrastSingleStepSettings from "../components/tools/adjustContrast/AdjustContrastSingleStepSettings";
|
||||
import { adjustContrastOperationConfig } from "../hooks/tools/adjustContrast/useAdjustContrastOperation";
|
||||
@ -109,14 +117,18 @@ import RemoveBlanksSettings from "../components/tools/removeBlanks/RemoveBlanksS
|
||||
import AddPageNumbersAutomationSettings from "../components/tools/addPageNumbers/AddPageNumbersAutomationSettings";
|
||||
import OverlayPdfsSettings from "../components/tools/overlayPdfs/OverlayPdfsSettings";
|
||||
import ValidateSignature from "../tools/ValidateSignature";
|
||||
|
||||
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
|
||||
|
||||
// Convert tool supported file formats
|
||||
import Automate from "../tools/Automate";
|
||||
import { CONVERT_SUPPORTED_FORMATS } from "../constants/convertSupportedFornats";
|
||||
|
||||
export interface TranslatedToolCatalog {
|
||||
allTools: ToolRegistry;
|
||||
regularTools: RegularToolRegistry;
|
||||
superTools: SuperToolRegistry;
|
||||
linkTools: LinkToolRegistry;
|
||||
}
|
||||
|
||||
// Hook to get the translated tool registry
|
||||
export function useFlatToolRegistry(): ToolRegistry {
|
||||
export function useTranslatedToolCatalog(): TranslatedToolCatalog {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useMemo(() => {
|
||||
@ -564,7 +576,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
||||
automate: {
|
||||
icon: <LocalIcon icon="automation-outline" width="1.5rem" height="1.5rem" />,
|
||||
name: t("home.automate.title", "Automate"),
|
||||
component: React.lazy(() => import("../tools/Automate")),
|
||||
component: Automate,
|
||||
description: t(
|
||||
"home.automate.desc",
|
||||
"Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
|
||||
@ -829,15 +841,26 @@ export function useFlatToolRegistry(): ToolRegistry {
|
||||
},
|
||||
};
|
||||
|
||||
if (showPlaceholderTools) {
|
||||
return allTools;
|
||||
}
|
||||
const filteredTools = Object.keys(allTools)
|
||||
.filter((key) => allTools[key as ToolId].component !== null || allTools[key as ToolId].link)
|
||||
.reduce((obj, key) => {
|
||||
obj[key as ToolId] = allTools[key as ToolId];
|
||||
return obj;
|
||||
}, {} as ToolRegistry);
|
||||
return filteredTools;
|
||||
const regularTools = {} as RegularToolRegistry;
|
||||
const superTools = {} as SuperToolRegistry;
|
||||
const linkTools = {} as LinkToolRegistry;
|
||||
|
||||
Object.entries(allTools).forEach(([key, entry]) => {
|
||||
const toolId = key as ToolId;
|
||||
if (isSuperToolId(toolId)) {
|
||||
superTools[toolId] = entry;
|
||||
} else if (isLinkToolId(toolId)) {
|
||||
linkTools[toolId] = entry;
|
||||
} else {
|
||||
regularTools[toolId] = entry;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
allTools,
|
||||
regularTools,
|
||||
superTools,
|
||||
linkTools,
|
||||
};
|
||||
}, [t]); // Only re-compute when translations change
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { ToolType, useToolOperation } from '../shared/useToolOperation';
|
||||
import { useCallback } from 'react';
|
||||
import { executeAutomationSequence } from '../../../utils/automationExecutor';
|
||||
import { useFlatToolRegistry } from '../../../data/useTranslatedToolRegistry';
|
||||
import { useToolRegistry } from '../../../contexts/ToolRegistryContext';
|
||||
import { AutomateParameters } from '../../../types/automation';
|
||||
|
||||
export function useAutomateOperation() {
|
||||
const toolRegistry = useFlatToolRegistry();
|
||||
const { allTools } = useToolRegistry();
|
||||
const toolRegistry = allTools;
|
||||
|
||||
const customProcessor = useCallback(async (params: AutomateParameters, files: File[]) => {
|
||||
console.log('🚀 Starting automation execution via customProcessor', { params, files });
|
||||
|
||||
@ -9,7 +9,7 @@ import { ToolId } from 'src/types/toolId';
|
||||
interface UseAutomationFormProps {
|
||||
mode: AutomationMode;
|
||||
existingAutomation?: AutomationConfig;
|
||||
toolRegistry: ToolRegistry;
|
||||
toolRegistry: Partial<ToolRegistry>;
|
||||
}
|
||||
|
||||
export function useAutomationForm({ mode, existingAutomation, toolRegistry }: UseAutomationFormProps) {
|
||||
@ -21,12 +21,12 @@ export function useAutomationForm({ mode, existingAutomation, toolRegistry }: Us
|
||||
const [selectedTools, setSelectedTools] = useState<AutomationTool[]>([]);
|
||||
|
||||
const getToolName = useCallback((operation: string) => {
|
||||
const tool = toolRegistry?.[operation as keyof ToolRegistry] as any;
|
||||
const tool = toolRegistry?.[operation as ToolId] as any;
|
||||
return tool?.name || t(`tools.${operation}.name`, operation);
|
||||
}, [toolRegistry, t]);
|
||||
|
||||
const getToolDefaultParameters = useCallback((operation: string): Record<string, any> => {
|
||||
const config = toolRegistry[operation as keyof ToolRegistry]?.operationConfig;
|
||||
const config = toolRegistry[operation as ToolId]?.operationConfig;
|
||||
if (config?.defaultParameters) {
|
||||
return { ...config.defaultParameters };
|
||||
}
|
||||
|
||||
6
frontend/src/hooks/useBaseUrl.ts
Normal file
6
frontend/src/hooks/useBaseUrl.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { useAppConfig } from './useAppConfig';
|
||||
|
||||
export const useBaseUrl = (): string => {
|
||||
const { config } = useAppConfig();
|
||||
return config?.baseUrl || 'https://demo.stirlingpdf.com';
|
||||
};
|
||||
@ -1,6 +1,5 @@
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFlatToolRegistry } from "../data/useTranslatedToolRegistry";
|
||||
import { useToolRegistry } from "../contexts/ToolRegistryContext";
|
||||
import { getAllEndpoints, type ToolRegistryEntry, type ToolRegistry } from "../data/toolsTaxonomy";
|
||||
import { useMultipleEndpointsEnabled } from "./useEndpointConfig";
|
||||
import { FileId } from '../types/file';
|
||||
@ -15,19 +14,19 @@ interface ToolManagementResult {
|
||||
}
|
||||
|
||||
export const useToolManagement = (): ToolManagementResult => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [toolSelectedFileIds, setToolSelectedFileIds] = useState<FileId[]>([]);
|
||||
|
||||
// Build endpoints list from registry entries with fallback to legacy mapping
|
||||
const baseRegistry = useFlatToolRegistry();
|
||||
const { allTools } = useToolRegistry();
|
||||
const baseRegistry = allTools;
|
||||
|
||||
const allEndpoints = useMemo(() => getAllEndpoints(baseRegistry), [baseRegistry]);
|
||||
const { endpointStatus, loading: endpointsLoading } = useMultipleEndpointsEnabled(allEndpoints);
|
||||
|
||||
const isToolAvailable = useCallback((toolKey: string): boolean => {
|
||||
if (endpointsLoading) return true;
|
||||
const endpoints = baseRegistry[toolKey as keyof typeof baseRegistry]?.endpoints || [];
|
||||
const tool = baseRegistry[toolKey as ToolId];
|
||||
const endpoints = tool?.endpoints || [];
|
||||
return endpoints.length === 0 || endpoints.some((endpoint: string) => endpointStatus[endpoint] === true);
|
||||
}, [endpointsLoading, endpointStatus, baseRegistry]);
|
||||
|
||||
@ -35,16 +34,18 @@ export const useToolManagement = (): ToolManagementResult => {
|
||||
const availableToolRegistry: Partial<ToolRegistry> = {};
|
||||
(Object.keys(baseRegistry) as ToolId[]).forEach(toolKey => {
|
||||
if (isToolAvailable(toolKey)) {
|
||||
const baseTool = baseRegistry[toolKey as keyof typeof baseRegistry];
|
||||
availableToolRegistry[toolKey as ToolId] = {
|
||||
...baseTool,
|
||||
name: baseTool.name,
|
||||
description: baseTool.description,
|
||||
};
|
||||
const baseTool = baseRegistry[toolKey];
|
||||
if (baseTool) {
|
||||
availableToolRegistry[toolKey] = {
|
||||
...baseTool,
|
||||
name: baseTool.name,
|
||||
description: baseTool.description,
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
return availableToolRegistry;
|
||||
}, [isToolAvailable, t, baseRegistry]);
|
||||
}, [isToolAvailable, baseRegistry]);
|
||||
|
||||
const getSelectedTool = useCallback((toolKey: ToolId | null): ToolRegistryEntry | null => {
|
||||
return toolKey ? toolRegistry[toolKey] || null : null;
|
||||
|
||||
@ -4,7 +4,8 @@ import { useToolWorkflow } from "../contexts/ToolWorkflowContext";
|
||||
import { Group, useMantineColorScheme } from "@mantine/core";
|
||||
import { useSidebarContext } from "../contexts/SidebarContext";
|
||||
import { useDocumentMeta } from "../hooks/useDocumentMeta";
|
||||
import { BASE_PATH, getBaseUrl } from "../constants/app";
|
||||
import { BASE_PATH } from "../constants/app";
|
||||
import { useBaseUrl } from "../hooks/useBaseUrl";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
import AppsIcon from '@mui/icons-material/AppsRounded';
|
||||
|
||||
@ -135,7 +136,7 @@ export default function HomePage() {
|
||||
}
|
||||
}, [isMobile, activeMobileView, selectedTool, setLeftPanelView]);
|
||||
|
||||
const baseUrl = getBaseUrl();
|
||||
const baseUrl = useBaseUrl();
|
||||
|
||||
// Update document meta when tool changes
|
||||
useDocumentMeta({
|
||||
|
||||
@ -119,5 +119,49 @@ Object.defineProperty(window, 'matchMedia', {
|
||||
})),
|
||||
});
|
||||
|
||||
// Provide a minimal DOMMatrix implementation for pdf.js in the test environment
|
||||
if (typeof globalThis.DOMMatrix === 'undefined') {
|
||||
class DOMMatrixStub {
|
||||
a = 1;
|
||||
b = 0;
|
||||
c = 0;
|
||||
d = 1;
|
||||
e = 0;
|
||||
f = 0;
|
||||
|
||||
constructor(init?: string | number[]) {
|
||||
if (Array.isArray(init) && init.length === 6) {
|
||||
[this.a, this.b, this.c, this.d, this.e, this.f] = init as [number, number, number, number, number, number];
|
||||
}
|
||||
}
|
||||
|
||||
multiplySelf(): this {
|
||||
return this;
|
||||
}
|
||||
|
||||
translateSelf(): this {
|
||||
return this;
|
||||
}
|
||||
|
||||
scaleSelf(): this {
|
||||
return this;
|
||||
}
|
||||
|
||||
rotateSelf(): this {
|
||||
return this;
|
||||
}
|
||||
|
||||
inverse(): this {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(globalThis, 'DOMMatrix', {
|
||||
value: DOMMatrixStub,
|
||||
writable: false,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Set global test timeout to prevent hangs
|
||||
vi.setConfig({ testTimeout: 5000, hookTimeout: 5000 });
|
||||
|
||||
@ -12,7 +12,7 @@ import AutomationRun from "../components/tools/automate/AutomationRun";
|
||||
|
||||
import { useAutomateOperation } from "../hooks/tools/automate/useAutomateOperation";
|
||||
import { BaseToolProps } from "../types/tool";
|
||||
import { useFlatToolRegistry } from "../data/useTranslatedToolRegistry";
|
||||
import { useToolRegistry } from "../contexts/ToolRegistryContext";
|
||||
import { useSavedAutomations } from "../hooks/tools/automate/useSavedAutomations";
|
||||
import { AutomationConfig, AutomationStepData, AutomationMode, AutomationStep } from "../types/automation";
|
||||
import { AUTOMATION_STEPS } from "../constants/automation";
|
||||
@ -27,7 +27,7 @@ const Automate = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
const [stepData, setStepData] = useState<AutomationStepData>({ step: AUTOMATION_STEPS.SELECTION });
|
||||
|
||||
const automateOperation = useAutomateOperation();
|
||||
const toolRegistry = useFlatToolRegistry();
|
||||
const { regularTools: toolRegistry } = useToolRegistry();
|
||||
const hasResults = automateOperation.files.length > 0 || automateOperation.downloadUrl !== null;
|
||||
const { savedAutomations, deleteAutomation, refreshAutomations, copyFromSuggested } = useSavedAutomations();
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// Define all possible tool IDs as source of truth
|
||||
export const TOOL_IDS = [
|
||||
export type ToolKind = 'regular' | 'super' | 'link';
|
||||
|
||||
export const REGULAR_TOOL_IDS = [
|
||||
'certSign',
|
||||
'sign',
|
||||
'addPassword',
|
||||
@ -26,7 +27,6 @@ export const TOOL_IDS = [
|
||||
'adjustContrast',
|
||||
'crop',
|
||||
'pdfToSinglePage',
|
||||
'multiTool',
|
||||
'repair',
|
||||
'compare',
|
||||
'addPageNumbers',
|
||||
@ -44,21 +44,52 @@ export const TOOL_IDS = [
|
||||
'overlayPdfs',
|
||||
'getPdfInfo',
|
||||
'validateSignature',
|
||||
'read',
|
||||
'automate',
|
||||
'replaceColor',
|
||||
'showJS',
|
||||
'bookletImposition',
|
||||
] as const;
|
||||
|
||||
export const SUPER_TOOL_IDS = [
|
||||
'multiTool',
|
||||
'read',
|
||||
'automate',
|
||||
] as const;
|
||||
|
||||
const LINK_TOOL_IDS = [
|
||||
'devApi',
|
||||
'devFolderScanning',
|
||||
'devSsoGuide',
|
||||
'devAirgapped',
|
||||
'bookletImposition',
|
||||
] as const;
|
||||
|
||||
const TOOL_IDS = [
|
||||
...REGULAR_TOOL_IDS,
|
||||
...SUPER_TOOL_IDS,
|
||||
...LINK_TOOL_IDS,
|
||||
];
|
||||
|
||||
// Tool identity - what PDF operation we're performing (type-safe)
|
||||
export type ToolId = typeof TOOL_IDS[number];
|
||||
export const isValidToolId = (value: string): value is ToolId =>
|
||||
TOOL_IDS.includes(value as ToolId);
|
||||
|
||||
export type RegularToolId = typeof REGULAR_TOOL_IDS[number];
|
||||
export const isRegularToolId = (toolId: ToolId): toolId is RegularToolId =>
|
||||
REGULAR_TOOL_IDS.includes(toolId as RegularToolId);
|
||||
|
||||
export type SuperToolId = typeof SUPER_TOOL_IDS[number];
|
||||
export const isSuperToolId = (toolId: ToolId): toolId is SuperToolId =>
|
||||
SUPER_TOOL_IDS.includes(toolId as SuperToolId);
|
||||
|
||||
export type LinkToolId = typeof LINK_TOOL_IDS[number];
|
||||
export const isLinkToolId = (toolId: ToolId): toolId is LinkToolId =>
|
||||
LINK_TOOL_IDS.includes(toolId as LinkToolId);
|
||||
|
||||
|
||||
type Assert<A extends true> = A;
|
||||
type Disjoint<A, B> = [A & B] extends [never] ? true : false;
|
||||
|
||||
type _Check1 = Assert<Disjoint<RegularToolId, SuperToolId>>;
|
||||
type _Check2 = Assert<Disjoint<RegularToolId, LinkToolId>>;
|
||||
type _Check3 = Assert<Disjoint<SuperToolId, LinkToolId>>;
|
||||
|
||||
// Type guard using the same source of truth
|
||||
export const isValidToolId = (value: string): value is ToolId => {
|
||||
return TOOL_IDS.includes(value as ToolId);
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { ToolRegistry } from '../data/toolsTaxonomy';
|
||||
import { ToolId } from '../types/toolId';
|
||||
import { AUTOMATION_CONSTANTS } from '../constants/automation';
|
||||
import { AutomationFileProcessor } from './automationFileProcessor';
|
||||
import { ToolType } from '../hooks/tools/shared/useToolOperation';
|
||||
@ -149,7 +150,7 @@ export const executeToolOperationWithPrefix = async (
|
||||
toolRegistry: ToolRegistry,
|
||||
filePrefix: string = AUTOMATION_CONSTANTS.FILE_PREFIX
|
||||
): Promise<File[]> => {
|
||||
const config = toolRegistry[operationName as keyof ToolRegistry]?.operationConfig;
|
||||
const config = toolRegistry[operationName as ToolId]?.operationConfig;
|
||||
if (!config) {
|
||||
throw new Error(`Tool operation not supported: ${operationName}`);
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { PDFDocument, rgb } from 'pdf-lib';
|
||||
import { generateThumbnailWithMetadata } from './thumbnailUtils';
|
||||
import { createProcessedFile, createChildStub } from '../contexts/file/fileActions';
|
||||
import { createStirlingFile, StirlingFile, FileId, StirlingFileStub } from '../types/fileContext';
|
||||
import type { SignatureAPI } from '../components/viewer/SignatureAPIBridge';
|
||||
import type { SignatureAPI } from '../components/viewer/viewerTypes';
|
||||
|
||||
interface MinimalFileContextSelectors {
|
||||
getAllFileIds: () => FileId[];
|
||||
@ -319,4 +319,4 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
console.error('Error flattening signatures:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ToolId } from "src/types/toolId";
|
||||
import { ToolRegistryEntry } from "../data/toolsTaxonomy";
|
||||
import { ToolRegistryEntry, ToolRegistry } from "../data/toolsTaxonomy";
|
||||
import { scoreMatch, minScoreForQuery, normalizeForSearch } from "./fuzzySearch";
|
||||
|
||||
export interface RankedToolItem {
|
||||
@ -8,7 +8,7 @@ export interface RankedToolItem {
|
||||
}
|
||||
|
||||
export function filterToolRegistryByQuery(
|
||||
toolRegistry: Record<ToolId, ToolRegistryEntry>,
|
||||
toolRegistry: Partial<ToolRegistry>,
|
||||
query: string
|
||||
): RankedToolItem[] {
|
||||
const entries = Object.entries(toolRegistry) as [ToolId, ToolRegistryEntry][];
|
||||
@ -96,5 +96,3 @@ export function filterToolRegistryByQuery(
|
||||
return entries.map(([id, tool]) => ({ item: [id, tool] as [ToolId, ToolRegistryEntry] }));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -115,4 +115,3 @@ export function getToolDisplayName(toolId: ToolId, registry: ToolRegistry): stri
|
||||
const tool = registry[toolId];
|
||||
return tool ? tool.name : toolId;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user