diff --git a/frontend/editor/src/core/pages/MobileScannerPage.tsx b/frontend/editor/src/core/pages/MobileScannerPage.tsx index 0424125aa8..0d14ce7ee8 100644 --- a/frontend/editor/src/core/pages/MobileScannerPage.tsx +++ b/frontend/editor/src/core/pages/MobileScannerPage.tsx @@ -20,12 +20,32 @@ import PhotoCameraRoundedIcon from "@mui/icons-material/PhotoCameraRounded"; import UploadRoundedIcon from "@mui/icons-material/UploadRounded"; import AddPhotoAlternateRoundedIcon from "@mui/icons-material/AddPhotoAlternateRounded"; import CheckCircleRoundedIcon from "@mui/icons-material/CheckCircleRounded"; -import { loadJscanify } from "@app/utils/loadJscanify"; +import { + loadJscanify, + type JscanifyCornerPoints, + type JscanifyScanner, +} from "@app/utils/loadJscanify"; import apiClient from "@app/services/apiClient"; // Use the configured API base (e.g. api.stirling.com), not the page origin. const API_BASE = (apiClient.defaults.baseURL ?? "").replace(/\/+$/, ""); +// Experimental camera controls (W3C Image Capture / MediaStream extensions) that +// are not yet part of the standard DOM lib typings but are widely shipped on +// mobile browsers and required for document scanning. +declare global { + interface MediaTrackCapabilities { + focusMode?: string[]; + exposureMode?: string[]; + torch?: boolean; + } + interface MediaTrackConstraintSet { + focusMode?: ConstrainDOMString; + exposureMode?: ConstrainDOMString; + torch?: ConstrainBoolean; + } +} + /** * MobileScannerPage * @@ -63,7 +83,7 @@ export default function MobileScannerPage() { const highlightCanvasRef = useRef(null); const streamRef = useRef(null); const fileInputRef = useRef(null); - const scannerRef = useRef(null); + const scannerRef = useRef(null); const highlightIntervalRef = useRef(null); // Detection resolution - extremely low for mobile performance @@ -254,15 +274,15 @@ export default function MobileScannerPage() { // Configure camera capabilities for document scanning try { - const capabilities = videoTrack.getCapabilities() as any; // Cast to any for experimental camera APIs - const constraints: any = { advanced: [] }; + const capabilities = videoTrack.getCapabilities(); + const advanced: MediaTrackConstraintSet[] = []; // 1. Enable continuous autofocus if ( capabilities.focusMode && capabilities.focusMode.includes("continuous") ) { - constraints.advanced.push({ focusMode: "continuous" }); + advanced.push({ focusMode: "continuous" }); console.log("✓ Continuous autofocus enabled"); } @@ -271,7 +291,7 @@ export default function MobileScannerPage() { capabilities.exposureMode && capabilities.exposureMode.includes("continuous") ) { - constraints.advanced.push({ exposureMode: "continuous" }); + advanced.push({ exposureMode: "continuous" }); console.log("✓ Auto-exposure enabled"); } @@ -282,8 +302,8 @@ export default function MobileScannerPage() { } // Apply all constraints - if (constraints.advanced.length > 0) { - await videoTrack.applyConstraints(constraints); + if (advanced.length > 0) { + await videoTrack.applyConstraints({ advanced }); } } catch (err) { console.log("Could not configure camera features:", err); @@ -444,15 +464,19 @@ export default function MobileScannerPage() { // Step 2: Simple jscanify detection const detectionStart = performance.now(); - let corners = null; + let corners: JscanifyCornerPoints | null = null; // Run jscanify detection directly - convert canvas to Mat first - const mat = (window as any).cv.imread(detectionCanvas); - const contour = scannerRef.current.findPaperContour(mat); - mat.delete(); + const cv = window.cv; + const scanner = scannerRef.current; + if (cv && scanner) { + const mat = cv.imread(detectionCanvas); + const contour = scanner.findPaperContour(mat); + mat.delete(); - if (contour) { - corners = scannerRef.current.getCornerPoints(contour); + if (contour) { + corners = scanner.getCornerPoints(contour); + } } const detectionTime = performance.now() - detectionStart; @@ -660,7 +684,9 @@ export default function MobileScannerPage() { let finalDataUrl: string; // Apply jscanify processing if enabled and available - if (autoEnhance && scannerRef.current && openCvReady) { + const cv = window.cv; + const scanner = scannerRef.current; + if (autoEnhance && scanner && openCvReady && cv) { try { // Create low-res canvas for detection (faster processing) const detectionCanvas = document.createElement("canvas"); @@ -683,11 +709,11 @@ export default function MobileScannerPage() { ); // Run detection on low-res image - const mat = (window as any).cv.imread(detectionCanvas); - const contour = scannerRef.current.findPaperContour(mat); + const mat = cv.imread(detectionCanvas); + const contour = scanner.findPaperContour(mat); if (contour) { - const cornerPoints = scannerRef.current.getCornerPoints(contour); + const cornerPoints = scanner.getCornerPoints(contour); // Scale corner points back to full resolution if (cornerPoints) { @@ -746,7 +772,7 @@ export default function MobileScannerPage() { const docHeight = Math.round((leftHeight + rightHeight) / 2); // Extract paper from full-resolution canvas with scaled corner points - const resultCanvas = scannerRef.current.extractPaper( + const resultCanvas = scanner.extractPaper( canvas, docWidth, docHeight, @@ -891,8 +917,8 @@ export default function MobileScannerPage() { try { const videoTrack = streamRef.current.getVideoTracks()[0]; await videoTrack.applyConstraints({ - advanced: [{ torch: !torchEnabled } as any], // Cast to any for experimental torch API - } as any); + advanced: [{ torch: !torchEnabled }], + }); setTorchEnabled(!torchEnabled); console.log("Torch:", !torchEnabled ? "ON" : "OFF"); } catch (err) { diff --git a/frontend/editor/src/core/tests/convert/ConvertIntegration.test.tsx b/frontend/editor/src/core/tests/convert/ConvertIntegration.test.tsx index e0555d25f8..06f9198f05 100644 --- a/frontend/editor/src/core/tests/convert/ConvertIntegration.test.tsx +++ b/frontend/editor/src/core/tests/convert/ConvertIntegration.test.tsx @@ -123,7 +123,7 @@ describe("Convert Tool Integration Tests", () => { beforeEach(() => { vi.clearAllMocks(); // Setup default apiClient mock - mockedApiClient.post = vi.fn() as any; + mockedApiClient.post = vi.fn() as typeof mockedApiClient.post; }); afterEach(() => { diff --git a/frontend/editor/src/core/tests/live/watched-folders.spec.ts b/frontend/editor/src/core/tests/live/watched-folders.spec.ts index 8f6e09d4b6..6ee7dddd7c 100644 --- a/frontend/editor/src/core/tests/live/watched-folders.spec.ts +++ b/frontend/editor/src/core/tests/live/watched-folders.spec.ts @@ -83,7 +83,10 @@ async function getIDBFolders( const all = tx.objectStore(storeName).getAll(); all.onsuccess = () => resolve( - (all.result || []).map((f: any) => ({ id: f.id, name: f.name })), + (all.result || []).map((f: { id: string; name: string }) => ({ + id: f.id, + name: f.name, + })), ); all.onerror = () => resolve([]); }; @@ -275,7 +278,7 @@ test.describe("Watched Folders — Create / Edit / Delete", () => { dbName: string, storeName: string, key: string, - value: any, + value: unknown, ) => new Promise((resolve) => { const req = indexedDB.open(dbName); diff --git a/frontend/editor/src/core/tools/AddAttachments.tsx b/frontend/editor/src/core/tools/AddAttachments.tsx index 22c9713b68..17e5d7b0b1 100644 --- a/frontend/editor/src/core/tools/AddAttachments.tsx +++ b/frontend/editor/src/core/tools/AddAttachments.tsx @@ -1,7 +1,10 @@ import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import { useViewScopedFiles } from "@app/hooks/tools/shared/useViewScopedFiles"; -import { createToolFlow } from "@app/components/tools/shared/createToolFlow"; +import { + createToolFlow, + type MiddleStepConfig, +} from "@app/components/tools/shared/createToolFlow"; import { BaseToolProps, ToolComponent } from "@app/types/tool"; import { useEndpointEnabled } from "@app/hooks/useEndpointConfig"; import { useAddAttachmentsParameters } from "@app/hooks/tools/addAttachments/useAddAttachmentsParameters"; @@ -36,9 +39,9 @@ const AddAttachments = ({ if (operation.files && onComplete) { onComplete(operation.files); } - } catch (error: any) { + } catch (error) { onError?.( - error?.message || + (error instanceof Error ? error.message : undefined) || t( "AddAttachmentsRequest.error.failed", "Add attachments operation failed", @@ -70,7 +73,7 @@ const AddAttachments = ({ }); const getSteps = () => { - const steps: any[] = []; + const steps: MiddleStepConfig[] = []; // Step 1: Attachments Selection steps.push({ diff --git a/frontend/editor/src/core/tools/AddPageNumbers.tsx b/frontend/editor/src/core/tools/AddPageNumbers.tsx index db6e5404e5..7001dc2894 100644 --- a/frontend/editor/src/core/tools/AddPageNumbers.tsx +++ b/frontend/editor/src/core/tools/AddPageNumbers.tsx @@ -1,7 +1,10 @@ import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import { useViewScopedFiles } from "@app/hooks/tools/shared/useViewScopedFiles"; -import { createToolFlow } from "@app/components/tools/shared/createToolFlow"; +import { + createToolFlow, + type MiddleStepConfig, +} from "@app/components/tools/shared/createToolFlow"; import { BaseToolProps, ToolComponent } from "@app/types/tool"; import { useEndpointEnabled } from "@app/hooks/useEndpointConfig"; import { useAddPageNumbersParameters } from "@app/components/tools/addPageNumbers/useAddPageNumbersParameters"; @@ -35,9 +38,9 @@ const AddPageNumbers = ({ if (operation.files && onComplete) { onComplete(operation.files); } - } catch (error: any) { + } catch (error) { onError?.( - error?.message || + (error instanceof Error ? error.message : undefined) || t("addPageNumbers.error.failed", "Add page numbers operation failed"), ); } @@ -67,7 +70,7 @@ const AddPageNumbers = ({ }); const getSteps = () => { - const steps: any[] = []; + const steps: MiddleStepConfig[] = []; // Step 1: Position Selection & Pages/Starting Number steps.push({ diff --git a/frontend/editor/src/core/tools/AddStamp.tsx b/frontend/editor/src/core/tools/AddStamp.tsx index b5c5d913fc..9a7422188e 100644 --- a/frontend/editor/src/core/tools/AddStamp.tsx +++ b/frontend/editor/src/core/tools/AddStamp.tsx @@ -1,6 +1,9 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { createToolFlow } from "@app/components/tools/shared/createToolFlow"; +import { + createToolFlow, + type MiddleStepConfig, +} from "@app/components/tools/shared/createToolFlow"; import { BaseToolProps, ToolComponent } from "@app/types/tool"; import { useEndpointEnabled } from "@app/hooks/useEndpointConfig"; import { useViewScopedFiles } from "@app/hooks/tools/shared/useViewScopedFiles"; @@ -41,9 +44,9 @@ const AddStamp = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { if (operation.files && onComplete) { onComplete(operation.files); } - } catch (error: any) { + } catch (error) { onError?.( - error?.message || + (error instanceof Error ? error.message : undefined) || t("AddStampRequest.error.failed", "Add stamp operation failed"), ); } @@ -73,7 +76,7 @@ const AddStamp = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { }); const getSteps = () => { - const steps: any[] = []; + const steps: MiddleStepConfig[] = []; // Step 1: Stamp Setup steps.push({ diff --git a/frontend/editor/src/core/tools/EditTableOfContents.tsx b/frontend/editor/src/core/tools/EditTableOfContents.tsx index 725c64124c..85769e0649 100644 --- a/frontend/editor/src/core/tools/EditTableOfContents.tsx +++ b/frontend/editor/src/core/tools/EditTableOfContents.tsx @@ -26,6 +26,7 @@ import { useNavigationState, } from "@app/contexts/NavigationContext"; import { useFileSelection } from "@app/contexts/FileContext"; +import { isStirlingFile } from "@app/types/fileContext"; const extractBookmarks = async (file: File): Promise => { const formData = new FormData(); @@ -39,7 +40,7 @@ const extractBookmarks = async (file: File): Promise => { return response.data as BookmarkPayload[]; }; -const useStableCallback = any>( +const useStableCallback = unknown>( callback: T, ): T => { const callbackRef = useRef(callback); @@ -112,7 +113,7 @@ const EditTableOfContents = (props: BaseToolProps) => { const payload = await extractBookmarks(file); const bookmarks = hydrateBookmarkPayload(payload); setBookmarks(bookmarks); - setLastLoadedFileId((file as any)?.fileId ?? file.name); + setLastLoadedFileId(isStirlingFile(file) ? file.fileId : file.name); if (showToast) { alert({ @@ -164,7 +165,7 @@ const EditTableOfContents = (props: BaseToolProps) => { return; } - const fileId = (selectedFile as any)?.fileId ?? selectedFile.name; + const fileId = selectedFile.fileId; if (fileId === lastLoadedFileId) { return; } @@ -466,6 +467,7 @@ const EditTableOfContents = (props: BaseToolProps) => { }); }; -(EditTableOfContents as any).tool = () => useEditTableOfContentsOperation; +(EditTableOfContents as ToolComponent).tool = () => + useEditTableOfContentsOperation; export default EditTableOfContents as ToolComponent; diff --git a/frontend/editor/src/core/tools/ReorganizePages.tsx b/frontend/editor/src/core/tools/ReorganizePages.tsx index dcf5c1ae9c..024469bee4 100644 --- a/frontend/editor/src/core/tools/ReorganizePages.tsx +++ b/frontend/editor/src/core/tools/ReorganizePages.tsx @@ -34,9 +34,9 @@ const ReorganizePages = ({ if (operation.files && onComplete) { onComplete(operation.files); } - } catch (error: any) { + } catch (error) { onError?.( - error?.message || + (error instanceof Error ? error.message : undefined) || t("reorganizePages.error.failed", "Failed to reorganize pages"), ); } @@ -107,6 +107,6 @@ const ReorganizePages = ({ }); }; -(ReorganizePages as any).tool = () => useReorganizePagesOperation; +(ReorganizePages as ToolComponent).tool = () => useReorganizePagesOperation; export default ReorganizePages as ToolComponent; diff --git a/frontend/editor/src/core/tools/formFill/FormFill.tsx b/frontend/editor/src/core/tools/formFill/FormFill.tsx index 846c1dcc83..0e4829df69 100644 --- a/frontend/editor/src/core/tools/formFill/FormFill.tsx +++ b/frontend/editor/src/core/tools/formFill/FormFill.tsx @@ -28,6 +28,7 @@ import { ActionIcon, } from "@mantine/core"; import { useTranslation } from "react-i18next"; +import { isAxiosError } from "axios"; import { useFormFill, useAllFormValues, @@ -267,13 +268,15 @@ const FormFill = (_props: BaseToolProps) => { detail: { blob: filledBlob }, }); window.dispatchEvent(event); - } catch (err: any) { + } catch (err) { + const status = isAxiosError(err) ? err.response?.status : undefined; const message = - err?.response?.status === 413 + status === 413 ? "File too large. Try reducing the PDF size first." - : err?.response?.status === 400 + : status === 400 ? "Invalid form data. Please check all fields." - : err?.message || "Failed to save filled form"; + : (err instanceof Error ? err.message : undefined) || + "Failed to save filled form"; setSaveError(message); console.error("[FormFill] Save failed:", err); } finally { diff --git a/frontend/editor/src/core/tools/formFill/FormFillContext.tsx b/frontend/editor/src/core/tools/formFill/FormFillContext.tsx index 217b568bbc..4377984111 100644 --- a/frontend/editor/src/core/tools/formFill/FormFillContext.tsx +++ b/frontend/editor/src/core/tools/formFill/FormFillContext.tsx @@ -30,6 +30,7 @@ import React, { useSyncExternalStore, } from "react"; import { useDebouncedCallback } from "@mantine/hooks"; +import { isAxiosError } from "axios"; import type { FormField, FormFillState, @@ -361,11 +362,13 @@ export function FormFillProvider({ forFileIdRef.current = fileId ?? null; setForFileId(fileId ?? null); dispatch({ type: "FETCH_SUCCESS", fields }); - } catch (err: any) { + } catch (err) { if (fetchVersionRef.current !== version) return; // stale const msg = - err?.response?.data?.message || - err?.message || + (isAxiosError<{ message?: string }>(err) + ? err.response?.data?.message + : undefined) || + (err instanceof Error ? err.message : undefined) || "Failed to fetch form fields"; dispatch({ type: "FETCH_ERROR", error: msg }); } diff --git a/frontend/editor/src/core/tools/pdfTextEditor/PdfTextEditor.tsx b/frontend/editor/src/core/tools/pdfTextEditor/PdfTextEditor.tsx index bf9628f5b3..fee07ac6ea 100644 --- a/frontend/editor/src/core/tools/pdfTextEditor/PdfTextEditor.tsx +++ b/frontend/editor/src/core/tools/pdfTextEditor/PdfTextEditor.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useMemo, useState, useRef } from "react"; import { useTranslation } from "react-i18next"; +import { isAxiosError } from "axios"; import DescriptionIcon from "@mui/icons-material/DescriptionOutlined"; import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext"; @@ -16,6 +17,7 @@ import { import { useViewer } from "@app/contexts/ViewerContext"; import { createStirlingFilesAndStubs } from "@app/services/fileStubHelpers"; import { BaseToolProps, ToolComponent } from "@app/types/tool"; +import type { FileId } from "@app/types/file"; import { getDefaultWorkbench } from "@app/types/workbench"; import { CONVERSION_ENDPOINTS } from "@app/constants/convertConstants"; import apiClient from "@app/services/apiClient"; @@ -294,7 +296,7 @@ const PdfTextEditor = ({ onComplete, onError }: BaseToolProps) => { const imagesByPageRef = useRef([]); const lastLoadedFileRef = useRef(null); const autoLoadKeyRef = useRef(null); - const sourceFileIdRef = useRef(null); + const sourceFileIdRef = useRef(null); const loadRequestIdRef = useRef(0); const latestPdfRequestIdRef = useRef(null); const loadedDocumentRef = useRef(null); @@ -339,8 +341,8 @@ const PdfTextEditor = ({ onComplete, onError }: BaseToolProps) => { }; }, []); - const isCacheUnavailableError = useCallback((error: any): boolean => { - const status = error?.response?.status; + const isCacheUnavailableError = useCallback((error: unknown): boolean => { + const status = isAxiosError(error) ? error.response?.status : undefined; // Treat any 410 as cache unavailable, since responseType: 'blob' makes // it impossible to reliably check the JSON body return status === 410; @@ -804,14 +806,20 @@ const PdfTextEditor = ({ onComplete, onError }: BaseToolProps) => { } else { console.log("Job not complete yet, continuing to poll..."); } - } catch (pollError: any) { + } catch (pollError) { console.error("Error polling job status:", pollError); + const status = isAxiosError(pollError) + ? pollError.response?.status + : undefined; console.error("Poll error details:", { - status: pollError?.response?.status, - data: pollError?.response?.data, - message: pollError?.message, + status, + data: isAxiosError(pollError) + ? pollError.response?.data + : undefined, + message: + pollError instanceof Error ? pollError.message : undefined, }); - if (pollError?.response?.status === 404) { + if (status === 404) { throw new Error("Job not found on server", { cause: pollError, }); @@ -864,12 +872,12 @@ const PdfTextEditor = ({ onComplete, onError }: BaseToolProps) => { cachedJobIdRef.current = newJobId; setFileName(file.name); setErrorMessage(null); - } catch (error: any) { + } catch (error) { console.error("Failed to load file", error); console.error("Error details:", { - message: error?.message, - response: error?.response?.data, - stack: error?.stack, + message: error instanceof Error ? error.message : undefined, + response: isAxiosError(error) ? error.response?.data : undefined, + stack: error instanceof Error ? error.stack : undefined, }); if (loadRequestIdRef.current !== requestId) { @@ -885,7 +893,7 @@ const PdfTextEditor = ({ onComplete, onError }: BaseToolProps) => { if (isPdf) { const errorMsg = - error?.message || + (error instanceof Error ? error.message : undefined) || t( "pdfTextEditor.conversionFailed", "Failed to convert PDF. Please try again.", @@ -1406,11 +1414,11 @@ const PdfTextEditor = ({ onComplete, onError }: BaseToolProps) => { onComplete([pdfFile]); } setErrorMessage(null); - } catch (error: any) { + } catch (error) { console.error("Failed to convert JSON back to PDF", error); const message = - error?.response?.data || - error?.message || + (isAxiosError(error) ? error.response?.data : undefined) || + (error instanceof Error ? error.message : undefined) || t( "pdfTextEditor.errors.pdfConversion", "Unable to convert the edited JSON back into a PDF.", @@ -1451,9 +1459,8 @@ const PdfTextEditor = ({ onComplete, onError }: BaseToolProps) => { return; } - const parentStub = selectors.getStirlingFileStub( - sourceFileIdRef.current as any, - ); + const sourceFileId = sourceFileIdRef.current; + const parentStub = selectors.getStirlingFileStub(sourceFileId); if (!parentStub) { console.warn( "[PdfTextEditor] Could not find parent stub for save to workbench", @@ -1660,11 +1667,7 @@ const PdfTextEditor = ({ onComplete, onError }: BaseToolProps) => { ); // Replace the original file with the edited version - await consumeFiles( - [sourceFileIdRef.current as any], - stirlingFiles, - stubs, - ); + await consumeFiles([sourceFileId], stirlingFiles, stubs); // Update the source file ID to point to the new file sourceFileIdRef.current = stubs[0].id; @@ -1676,11 +1679,11 @@ const PdfTextEditor = ({ onComplete, onError }: BaseToolProps) => { // Set flag to trigger navigation after state update is processed setShouldNavigateAfterSave(true); - } catch (error: any) { + } catch (error) { console.error("Failed to save to workbench", error); const message = - error?.response?.data || - error?.message || + (isAxiosError(error) ? error.response?.data : undefined) || + (error instanceof Error ? error.message : undefined) || t( "pdfTextEditor.errors.pdfConversion", "Unable to save changes to workbench.", @@ -1955,7 +1958,7 @@ const PdfTextEditor = ({ onComplete, onError }: BaseToolProps) => { autoLoadKeyRef.current = fileKey; // Capture the source file ID for save-to-workbench functionality - sourceFileIdRef.current = (autoLoadFile as any).fileId ?? null; + sourceFileIdRef.current = autoLoadFile.fileId ?? null; void handleLoadFile(autoLoadFile); }, [autoLoadFile, navigationState.selectedTool, handleLoadFile]); diff --git a/frontend/editor/src/core/utils/loadJscanify.ts b/frontend/editor/src/core/utils/loadJscanify.ts index 93dec00394..94b141bba2 100644 --- a/frontend/editor/src/core/utils/loadJscanify.ts +++ b/frontend/editor/src/core/utils/loadJscanify.ts @@ -1,9 +1,52 @@ import { withBasePath } from "@app/constants/app"; +/** A single point in image space, as returned by jscanify corner detection. */ +export interface JscanifyPoint { + x: number; + y: number; +} + +/** The four detected document corners returned by {@link JscanifyScanner.getCornerPoints}. */ +export interface JscanifyCornerPoints { + topLeftCorner: JscanifyPoint; + topRightCorner: JscanifyPoint; + bottomLeftCorner: JscanifyPoint; + bottomRightCorner: JscanifyPoint; +} + +/** Minimal subset of an OpenCV.js `Mat` that this app interacts with directly. */ +export interface OpenCVMat { + delete(): void; +} + +/** Minimal subset of the OpenCV.js runtime exposed on `window.cv`. */ +export interface OpenCV { + /** Defined only once the WASM runtime has finished initializing. */ + readonly Mat: unknown; + imread(source: HTMLImageElement | HTMLCanvasElement | string): OpenCVMat; +} + +/** The jscanify scanner instance API used by the mobile scanner. */ +export interface JscanifyScanner { + findPaperContour(image: OpenCVMat): OpenCVMat | undefined; + getCornerPoints(contour: OpenCVMat): JscanifyCornerPoints; + extractPaper( + image: HTMLCanvasElement, + resultWidth: number, + resultHeight: number, + cornerPoints?: JscanifyCornerPoints, + ): HTMLCanvasElement; +} + +/** Constructor for jscanify, exposed on `window.jscanify`. */ +export interface JscanifyConstructor { + new (): JscanifyScanner; +} + declare global { interface Window { - cv?: any; - jscanify?: any; + cv?: OpenCV; + jscanify?: JscanifyConstructor; } } diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs index 55950ad1b7..a176ef8541 100644 --- a/frontend/eslint.config.mjs +++ b/frontend/eslint.config.mjs @@ -215,10 +215,9 @@ export default defineConfig( "editor/src/core/contexts/**/*.{js,mjs,jsx,ts,tsx}", "editor/src/core/data/**/*.{js,mjs,jsx,ts,tsx}", "editor/src/core/hooks/**/*.{js,mjs,jsx,ts,tsx}", - "editor/src/core/pages/**/*.{js,mjs,jsx,ts,tsx}", "editor/src/core/services/**/*.{js,mjs,jsx,ts,tsx}", - "editor/src/core/tests/**/*.{js,mjs,jsx,ts,tsx}", - "editor/src/core/tools/**/*.{js,mjs,jsx,ts,tsx}", + "editor/src/core/tools/Automate.tsx", + "editor/src/core/tools/annotate/useAnnotationSelection.ts", "editor/src/core/types/**/*.{js,mjs,jsx,ts,tsx}", "editor/src/core/utils/**/*.{js,mjs,jsx,ts,tsx}", ],