Fix more any type usages in frontend code (#7334)

# Description of Changes
Continued effort towards removing all uses of the any type in our
frontend code (last PR was #7326). This PR fixes 7 more folders and
removes them from the exclude list. All of them were localised within
the folder in the exclude list so again were pretty easy to fix.
This commit is contained in:
James Brunton
2026-08-10 10:40:47 +00:00
committed by GitHub
parent 4e901f7524
commit cfaf777f2b
22 changed files with 145 additions and 77 deletions
@@ -1,4 +1,5 @@
import React, { createContext, useContext, ReactNode } from "react";
import { SignParameters } from "@app/hooks/tools/sign/useSignParameters";
interface PDFAnnotationContextValue {
// Drawing mode management
@@ -22,8 +23,8 @@ interface PDFAnnotationContextValue {
isPlacementMode: boolean;
// Signature configuration
signatureConfig: any | null;
setSignatureConfig: (config: any | null) => void;
signatureConfig: SignParameters | null;
setSignatureConfig: (config: SignParameters | null) => void;
}
const PDFAnnotationContext = createContext<
@@ -43,8 +44,8 @@ interface PDFAnnotationProviderProps {
storeImageData: (id: string, data: string) => void;
getImageData: (id: string) => string | undefined;
isPlacementMode: boolean;
signatureConfig: any | null;
setSignatureConfig: (config: any | null) => void;
signatureConfig: SignParameters | null;
setSignatureConfig: (config: SignParameters | null) => void;
}
export const PDFAnnotationProvider: React.FC<PDFAnnotationProviderProps> = ({
@@ -14,9 +14,17 @@ export interface AnnotationToolConfig {
placeButtonText?: string;
}
interface InjectedAnnotationToolProps {
selectedColor: string;
signatureData: string | null;
onSignatureDataChange: (data: string | null) => void;
onColorSwatchClick: () => void;
disabled: boolean;
}
interface BaseAnnotationToolProps {
config: AnnotationToolConfig;
children: React.ReactNode;
children: React.ReactElement<Partial<InjectedAnnotationToolProps>>;
onSignatureDataChange?: (data: string | null) => void;
disabled?: boolean;
}
@@ -90,7 +98,7 @@ export const BaseAnnotationTool: React.FC<BaseAnnotationToolProps> = ({
/>
{/* Tool Content */}
{React.cloneElement(children as React.ReactElement<any>, {
{React.cloneElement(children, {
selectedColor,
signatureData,
onSignatureDataChange: handleSignatureDataChange,
@@ -1,5 +1,7 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import DragDropGrid from "@app/components/pageEditor/DragDropGrid";
import DragDropGrid, {
type DragHandleProps,
} from "@app/components/pageEditor/DragDropGrid";
interface MockGridItem {
id: string;
@@ -22,7 +24,7 @@ const renderItem = (
clearBoxSelection: () => void,
activeDragIds: string[],
justMoved: boolean,
dragHandleProps?: any,
dragHandleProps?: DragHandleProps,
zoomLevel?: number,
) => {
const { ref: dndRef, ...restDragProps } = dragHandleProps ?? {};
@@ -20,6 +20,7 @@ import {
DragEndEvent,
DragStartEvent,
DragOverlay,
DraggableAttributes,
useSensor,
useSensors,
PointerSensor,
@@ -28,6 +29,10 @@ import {
useDroppable,
} from "@dnd-kit/core";
export type DragHandleProps = DraggableAttributes & {
ref: React.RefCallback<HTMLElement>;
} & Record<string, unknown>;
interface DragDropItem {
id: string;
splitAfter?: boolean;
@@ -51,7 +56,7 @@ interface DragDropGridProps<T extends DragDropItem> {
clearBoxSelection: () => void,
activeDragIds: string[],
justMoved: boolean,
dragHandleProps?: any,
dragHandleProps?: DragHandleProps,
zoomLevel?: number,
) => React.ReactNode;
getThumbnailData?: (
@@ -232,7 +237,7 @@ interface DraggableItemProps<T extends DragDropItem> {
clearBoxSelection: () => void,
activeDragIds: string[],
justMoved: boolean,
dragHandleProps?: any,
dragHandleProps?: DragHandleProps,
zoomLevel?: number,
) => React.ReactNode;
zoomLevel: number;
@@ -253,7 +258,7 @@ const DraggableItemInner = <T extends DragDropItem>({
zoomLevel,
}: DraggableItemProps<T>) => {
const isPlaceholder = Boolean(item.isPlaceholder);
const pageNumber = (item as any).pageNumber ?? index + 1;
const pageNumber = item.pageNumber ?? index + 1;
const {
attributes,
listeners,
@@ -11,7 +11,9 @@ import { PageEditorFunctions, PDFPage } from "@app/types/pageEditor";
// Thumbnail generation is now handled by individual PageThumbnail components
import "@app/components/pageEditor/PageEditor.module.css";
import PageThumbnail from "@app/components/pageEditor/PageThumbnail";
import DragDropGrid from "@app/components/pageEditor/DragDropGrid";
import DragDropGrid, {
type DragHandleProps,
} from "@app/components/pageEditor/DragDropGrid";
import SkeletonLoader from "@app/components/shared/SkeletonLoader";
import { FileId } from "@app/types/file";
import { GRID_CONSTANTS } from "@app/components/pageEditor/constants";
@@ -33,6 +35,13 @@ export interface PageEditorProps {
onFunctionsReady?: (functions: PageEditorFunctions) => void;
}
interface PageEditorFileEntry {
fileId: FileId;
name: string;
versionNumber: number | undefined;
isSelected: boolean;
}
const PageEditor = ({ onFunctionsReady }: PageEditorProps) => {
const { t } = useTranslation();
// Use split contexts to prevent re-renders
@@ -106,14 +115,14 @@ const PageEditor = ({ onFunctionsReady }: PageEditorProps) => {
const selectedIdsKey = [...state.ui.selectedFileIds].sort().join(",");
const filesSignature = selectors.getFilesSignature();
const fileObjectsRef = useRef(new Map<FileId, any>());
const fileObjectsRef = useRef(new Map<FileId, PageEditorFileEntry>());
const gridItemRefsRef = useRef<React.MutableRefObject<
Map<string, HTMLDivElement>
> | null>(null);
const pageEditorFiles = useMemo(() => {
const cache = fileObjectsRef.current;
const newFiles: any[] = [];
const newFiles: PageEditorFileEntry[] = [];
fileOrder.forEach((fileId) => {
const stub = selectors.getStirlingFileStub(fileId);
@@ -605,7 +614,7 @@ const PageEditor = ({ onFunctionsReady }: PageEditorProps) => {
clearBoxSelection: () => void,
activeDragIds: string[],
justMoved: boolean,
dragHandleProps?: any,
dragHandleProps?: DragHandleProps,
zoomLevelParam?: number,
) => {
gridItemRefsRef.current = refs;
@@ -17,6 +17,7 @@ import AddIcon from "@mui/icons-material/Add";
import { PDFPage, PDFDocument } from "@app/types/pageEditor";
import { useFilesModalContext } from "@app/contexts/FilesModalContext";
import { getFileColorWithOpacity } from "@app/components/pageEditor/fileColors";
import { type DragHandleProps } from "@app/components/pageEditor/DragDropGrid";
import styles from "@app/components/pageEditor/PageEditor.module.css";
import HoverActionMenu, {
HoverAction,
@@ -38,7 +39,7 @@ interface PageThumbnailProps {
activeDragIds: string[];
justMoved?: boolean;
pageRefs: React.MutableRefObject<Map<string, HTMLDivElement>>;
dragHandleProps?: any;
dragHandleProps?: DragHandleProps;
onReorderPages: (
sourcePageNumber: number,
targetIndex: number,
@@ -58,8 +58,8 @@ const FullscreenToolList = ({
);
const recommendedItems = useMemo(() => {
if (!quickSection)
return [] as Array<{ id: string; tool: ToolRegistryEntry }>;
const items: Array<{ id: string; tool: ToolRegistryEntry }> = [];
return [] as Array<{ id: ToolId; tool: ToolRegistryEntry }>;
const items: Array<{ id: ToolId; tool: ToolRegistryEntry }> = [];
quickSection.subcategories.forEach((sc) =>
sc.tools.forEach((t) => items.push(t)),
);
@@ -217,13 +217,13 @@ const FullscreenToolList = ({
</header>
{showDescriptions ? (
<div className="tool-panel__fullscreen-grid tool-panel__fullscreen-grid--detailed">
{recommendedItems.map((item: any) =>
{recommendedItems.map((item) =>
renderToolItem(item.id, item.tool),
)}
</div>
) : (
<div className="tool-panel__fullscreen-list">
{recommendedItems.map((item: any) =>
{recommendedItems.map((item) =>
renderToolItem(item.id, item.tool),
)}
</div>
@@ -5,7 +5,10 @@ import FileUploadButton from "@app/components/shared/FileUploadButton";
interface CertificateFilesSettingsProps {
parameters: CertSignParameters;
onParameterChange: (key: keyof CertSignParameters, value: any) => void;
onParameterChange: <K extends keyof CertSignParameters>(
key: K,
value: CertSignParameters[K],
) => void;
disabled?: boolean;
}
@@ -4,7 +4,10 @@ import { CertSignParameters } from "@app/hooks/tools/certSign/useCertSignParamet
interface CertificateFormatSettingsProps {
parameters: CertSignParameters;
onParameterChange: (key: keyof CertSignParameters, value: any) => void;
onParameterChange: <K extends keyof CertSignParameters>(
key: K,
value: CertSignParameters[K],
) => void;
disabled?: boolean;
}
@@ -7,7 +7,10 @@ import { useAppConfig } from "@app/contexts/AppConfigContext";
interface CertificateTypeSettingsProps {
parameters: CertSignParameters;
onParameterChange: (key: keyof CertSignParameters, value: any) => void;
onParameterChange: <K extends keyof CertSignParameters>(
key: K,
value: CertSignParameters[K],
) => void;
disabled?: boolean;
}
@@ -22,7 +22,10 @@ import {
interface HardwareCertificateSettingsProps {
parameters: CertSignParameters;
onParameterChange: (key: keyof CertSignParameters, value: any) => void;
onParameterChange: <K extends keyof CertSignParameters>(
key: K,
value: CertSignParameters[K],
) => void;
disabled?: boolean;
}
@@ -176,16 +179,20 @@ const HardwareCertificateSettings = ({
setError(null);
listWindowsCertificates()
.then(applyCerts)
.catch((e: any) =>
.catch((e) => {
const err = e as {
response?: { data?: { message?: string } };
message?: string;
};
setError(
e?.response?.data?.message ||
e?.message ||
err?.response?.data?.message ||
err?.message ||
t(
"certSign.hardware.windowsLoadError",
"Could not read the Windows certificate store",
),
),
)
);
})
.finally(() => setLoading(false));
}, [applyCerts, t]);
@@ -221,16 +228,20 @@ const HardwareCertificateSettings = ({
pin: parameters.password,
})
.then(applyCerts)
.catch((e: any) =>
.catch((e) => {
const err = e as {
response?: { data?: { message?: string } };
message?: string;
};
setError(
e?.response?.data?.message ||
e?.message ||
err?.response?.data?.message ||
err?.message ||
t(
"certSign.hardware.pkcs11LoadError",
"Could not read certificates from the token. Check the PIN and driver.",
),
),
)
);
})
.finally(() => setLoading(false));
}, [
applyCerts,
@@ -5,7 +5,10 @@ import { CertSignParameters } from "@app/hooks/tools/certSign/useCertSignParamet
interface SignatureAppearanceSettingsProps {
parameters: CertSignParameters;
onParameterChange: (key: keyof CertSignParameters, value: any) => void;
onParameterChange: <K extends keyof CertSignParameters>(
key: K,
value: CertSignParameters[K],
) => void;
disabled?: boolean;
}
@@ -101,7 +104,12 @@ const SignatureAppearanceSettings = ({
<NumberInput
label={t("certSign.pageNumber", "Page Number")}
value={parameters.pageNumber}
onChange={(value) => onParameterChange("pageNumber", value || 1)}
onChange={(value) =>
onParameterChange(
"pageNumber",
typeof value === "number" ? value : 1,
)
}
min={1}
disabled={disabled}
/>
@@ -24,7 +24,10 @@ const SignatureSettingsInput = ({
}: SignatureSettingsInputProps) => {
const { t } = useTranslation();
const handleChange = (key: keyof SignatureSettings, val: any) => {
const handleChange = <K extends keyof SignatureSettings>(
key: K,
val: SignatureSettings[K],
) => {
onChange({ ...value, [key]: val });
};
@@ -104,7 +107,9 @@ const SignatureSettingsInput = ({
<NumberInput
label={t("certSign.pageNumber", "Page Number")}
value={value.pageNumber || 1}
onChange={(val) => handleChange("pageNumber", val || 1)}
onChange={(val) =>
handleChange("pageNumber", typeof val === "number" ? val : 1)
}
min={1}
disabled={disabled}
size="xs"
@@ -52,6 +52,14 @@ import {
const MAX_RENDER_WIDTH = 820;
const MIN_BOX_SIZE = 18;
// Firefox-only fallback for document.caretRangeFromPoint (not in lib.dom.d.ts).
const docWithCaret = document as Document & {
caretPositionFromPoint?: (
x: number,
y: number,
) => { offsetNode: Node; offset: number } | null;
};
const normalizeFontFormat = (format?: string | null): string => {
if (!format) {
return "ttf";
@@ -352,7 +360,7 @@ const PdfTextEditorView = ({ data }: PdfTextEditorViewProps) => {
new Map(),
);
const draggingImageRef = useRef<string | null>(null);
const rndRefs = useRef<Map<string, any>>(new Map());
const rndRefs = useRef<Map<string, Rnd>>(new Map());
const pendingDragUpdateRef = useRef<number | null>(null);
const [fontFamilies, setFontFamilies] = useState<Map<string, string>>(
new Map(),
@@ -1378,7 +1386,7 @@ const PdfTextEditorView = ({ data }: PdfTextEditorViewProps) => {
const cssTop = (pageHeight - bounds.top) * scale;
// Get current position from Rnd component
const currentState = rndRef.state || {};
const currentState = (rndRef.state as { x?: number; y?: number }) || {};
const currentX = currentState.x ?? 0;
const currentY = currentState.y ?? 0;
@@ -2851,11 +2859,13 @@ const PdfTextEditorView = ({ data }: PdfTextEditorViewProps) => {
}
}
} else if (
(document as any).caretPositionFromPoint
docWithCaret.caretPositionFromPoint
) {
const pos = (
document as any
).caretPositionFromPoint(clickX, clickY);
const pos =
docWithCaret.caretPositionFromPoint(
clickX,
clickY,
);
if (pos) {
const range = document.createRange();
range.setStart(
@@ -447,10 +447,8 @@ export async function addFiles(
if (file.type === "application/pdf") {
try {
if (await FileAnalyzer.isPDFUserPasswordProtected(file)) {
fileStub.processedFile = (fileStub.processedFile || {
pages: [],
}) as any;
fileStub.processedFile!.isEncrypted = true;
fileStub.processedFile = fileStub.processedFile || { pages: [] };
fileStub.processedFile.isEncrypted = true;
}
} catch (error) {
// Never block upload on analysis failure — but log so it's debuggable
@@ -699,7 +697,7 @@ export async function undoConsumeFiles(
file: File,
fileId: FileId,
existingThumbnail?: string,
) => Promise<any>;
) => Promise<StirlingFileStub>;
deleteFile: (fileId: FileId) => Promise<void>;
bumpRevision?: () => void;
} | null,
@@ -398,7 +398,7 @@ export function useFileContext() {
addFiles: actions.addFiles,
consumeFiles: actions.consumeFiles,
undoConsumeFiles: actions.undoConsumeFiles,
recordOperation: (_fileId: FileId, _operation: any) => {}, // Operation tracking not implemented
recordOperation: (_fileId: FileId, _operation: unknown) => {}, // Operation tracking not implemented
markOperationApplied: (_fileId: FileId, _operationId: string) => {}, // Operation tracking not implemented
markOperationFailed: (
_fileId: FileId,
@@ -5,6 +5,7 @@
import { FileId } from "@app/types/file";
import {
FileContextAction,
FileContextState,
StirlingFileStub,
ProcessedFilePage,
} from "@app/types/fileContext";
@@ -39,7 +40,7 @@ export class FileLifecycleManager {
*/
cleanupFile = (
fileId: FileId,
stateRef?: React.MutableRefObject<any>,
stateRef?: React.MutableRefObject<FileContextState>,
): void => {
// Use comprehensive cleanup (same as removeFiles)
this.cleanupAllResourcesForFile(fileId, stateRef);
@@ -77,7 +78,7 @@ export class FileLifecycleManager {
scheduleCleanup = (
fileId: FileId,
delay: number = 30000,
stateRef?: React.MutableRefObject<any>,
stateRef?: React.MutableRefObject<FileContextState>,
): void => {
// Cancel existing timer
const existingTimer = this.cleanupTimers.get(fileId);
@@ -116,7 +117,7 @@ export class FileLifecycleManager {
*/
removeFiles = (
fileIds: FileId[],
stateRef?: React.MutableRefObject<any>,
stateRef?: React.MutableRefObject<FileContextState>,
): void => {
fileIds.forEach((fileId) => {
// Clean up all resources for this file
@@ -132,7 +133,7 @@ export class FileLifecycleManager {
*/
private cleanupAllResourcesForFile = (
fileId: FileId,
stateRef?: React.MutableRefObject<any>,
stateRef?: React.MutableRefObject<FileContextState>,
): void => {
// Remove from files ref
this.filesRef.current.delete(fileId);
@@ -188,7 +189,7 @@ export class FileLifecycleManager {
updateStirlingFileStub = (
fileId: FileId,
updates: Partial<StirlingFileStub>,
stateRef?: React.MutableRefObject<any>,
stateRef?: React.MutableRefObject<FileContextState>,
): void => {
// Guard against updating removed files (race condition protection)
if (!this.filesRef.current.has(fileId)) {
@@ -345,11 +345,15 @@ export const useConvertOperation = (parameters?: ConvertParameters) => {
...convertOperationConfig,
customProcessor: customConvertProcessor, // Use instance-specific processor for translation support
getErrorMessage: (error) => {
if (error.response?.data && typeof error.response.data === "string") {
return error.response.data;
const err = error as {
response?: { data?: unknown };
message?: string;
};
if (err.response?.data && typeof err.response.data === "string") {
return err.response.data;
}
if (error.message) {
return error.message;
if (err.message) {
return err.message;
}
return t(
"convert.errorConversion",
@@ -179,13 +179,15 @@ export const useOCROperation = () => {
const ocrConfig: ToolOperationConfig<OCRParameters> = {
...ocrOperationConfig,
responseHandler,
getErrorMessage: (error) =>
error.message?.includes("OCR tools") &&
error.message?.includes("not installed")
getErrorMessage: (error) => {
const message = (error as { message?: string }).message;
return message?.includes("OCR tools") &&
message?.includes("not installed")
? "OCR tools (OCRmyPDF or Tesseract) are not installed on the server. Use the standard or fat Docker image instead of ultra-lite, or install OCR tools manually."
: createStandardErrorHandler(
t("ocr.error.failed", "OCR operation failed"),
)(error),
)(error);
},
};
return useToolOperation(ocrConfig);
@@ -74,7 +74,7 @@ interface BaseToolOperationConfig<TParams, TEndpoint extends ToolEndpoint> {
responseHandler?: ResponseHandler;
/** Extract user-friendly error messages from API errors */
getErrorMessage?: (error: any) => string;
getErrorMessage?: (error: unknown) => string;
/** Default parameter values for automation */
defaultParameters?: TParams;
@@ -218,7 +218,7 @@ export const useToolOperation = <TParams>(
// Listen for global error file id events from HTTP interceptor during this run
let externalErrorFileIds: string[] = [];
const errorListener = (e: Event) => {
const detail = (e as CustomEvent)?.detail as any;
const detail = (e as CustomEvent<{ fileIds?: unknown }>)?.detail;
if (detail?.fileIds) {
externalErrorFileIds = Array.isArray(detail.fileIds)
? detail.fileIds
@@ -588,7 +588,7 @@ export const useToolOperation = <TParams>(
};
}
}
} catch (error: any) {
} catch (error) {
try {
const handled = await handle422Error(error, (id) =>
fileActions.markFileError(id as FileId),
@@ -691,21 +691,22 @@ export const useToolOperation = <TParams>(
// Show success message
actions.setStatus(t("undoSuccess", "Operation undone successfully"));
} catch (error: any) {
} catch (error) {
let errorMessage = extractErrorMessage(error);
// Provide more specific error messages based on error type
if (error.message?.includes("Mismatch between input files")) {
const err = error as { message?: string; name?: string };
if (err.message?.includes("Mismatch between input files")) {
errorMessage = t(
"undoDataMismatch",
"Cannot undo: operation data is corrupted",
);
} else if (error.message?.includes("IndexedDB")) {
} else if (err.message?.includes("IndexedDB")) {
errorMessage = t(
"undoStorageError",
"Undo completed but some files could not be saved to storage",
);
} else if (error.name === "QuotaExceededError") {
} else if (err.name === "QuotaExceededError") {
errorMessage = t(
"undoQuotaError",
"Cannot undo: insufficient storage space",
-7
View File
@@ -74,21 +74,14 @@ const modernGlobals: OxlintGlobals = {
// Folders not yet conformant to the stricter no-explicit-any rule
const noExplicitAnyExcludes = [
"editor/src/core/components/annotation/**/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/pageEditor/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/shared/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/shared/config/configSections/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/tools/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/tools/addStamp/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/tools/automate/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/tools/certSign/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/tools/pdfTextEditor/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/viewer/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/contexts/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/contexts/file/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/contexts/viewer/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/hooks/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/hooks/tools/shared/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/services/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/tools/annotate/useAnnotationSelection.ts",
"editor/src/core/types/*.{js,mjs,jsx,ts,tsx}",