mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-03-04 02:20:19 +01:00
Fix more any types
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import type { AddStampParameters } from '@app/components/tools/addStamp/useAddStampParameters';
|
||||
|
||||
export type ContainerSize = { width: number; height: number };
|
||||
@@ -48,8 +49,6 @@ export const getFirstSelectedPage = (input: string): number => {
|
||||
return 1;
|
||||
};
|
||||
|
||||
export type StampPreviewStyle = { container: any; item: any };
|
||||
|
||||
// Unified per-alphabet preview adjustments
|
||||
export type Alphabet = 'roman' | 'arabic' | 'japanese' | 'korean' | 'chinese' | 'thai';
|
||||
export type AlphabetTweaks = { scale: number; rowOffsetRem: [number, number, number]; lineHeight: number; capHeightRatio: number; defaultFontSize: number };
|
||||
@@ -62,11 +61,19 @@ export const ALPHABET_PREVIEW_TWEAKS: Record<Alphabet, AlphabetTweaks> = {
|
||||
chinese: { scale: 1/1.2, rowOffsetRem: [0, 2, 2.8], lineHeight: 1, capHeightRatio: 0.72, defaultFontSize: 30 }, // temporary default font size so that it fits on the PDF
|
||||
thai: { scale: 1/1.2, rowOffsetRem: [-1, 0, .8], lineHeight: 1, capHeightRatio: 0.66, defaultFontSize: 80 },
|
||||
};
|
||||
export const getAlphabetPreviewScale = (alphabet: string): number => (ALPHABET_PREVIEW_TWEAKS as any)[alphabet]?.scale ?? 1.0;
|
||||
const getAlphabetTweaks = (alphabet: string): AlphabetTweaks | undefined =>
|
||||
ALPHABET_PREVIEW_TWEAKS[alphabet as Alphabet];
|
||||
|
||||
export const getDefaultFontSizeForAlphabet = (alphabet: string): number => {
|
||||
return (ALPHABET_PREVIEW_TWEAKS as any)[alphabet]?.defaultFontSize ?? 80;
|
||||
};
|
||||
export const getAlphabetPreviewScale = (alphabet: string): number =>
|
||||
getAlphabetTweaks(alphabet)?.scale ?? 1.0;
|
||||
|
||||
export const getDefaultFontSizeForAlphabet = (alphabet: string): number =>
|
||||
getAlphabetTweaks(alphabet)?.defaultFontSize ?? 80;
|
||||
|
||||
export interface StampPreviewStyle {
|
||||
container: CSSProperties;
|
||||
item: CSSProperties;
|
||||
}
|
||||
|
||||
export function computeStampPreviewStyle(
|
||||
parameters: AddStampParameters,
|
||||
@@ -83,7 +90,7 @@ export function computeStampPreviewStyle(
|
||||
const heightPts = pageSize?.heightPts ?? 841.89; // A4 height at 72 DPI
|
||||
const scaleX = pageWidthPx / widthPts;
|
||||
const scaleY = pageHeightPx / heightPts;
|
||||
if (pageWidthPx <= 0 || pageHeightPx <= 0) return { item: {}, container: {} } as any;
|
||||
if (pageWidthPx <= 0 || pageHeightPx <= 0) return { item: {}, container: {} };
|
||||
|
||||
const marginPts = (widthPts + heightPts) / 2 * (marginFactorMap[parameters.customMargin] ?? 0.035);
|
||||
|
||||
@@ -110,24 +117,15 @@ export function computeStampPreviewStyle(
|
||||
// Convert measured px width back to PDF points using horizontal scale
|
||||
widthPtsContent = measuredWidthPx / scaleX;
|
||||
|
||||
let adjustmentFactor = 1.0;
|
||||
switch (parameters.alphabet) {
|
||||
case 'roman':
|
||||
adjustmentFactor = 0.90;
|
||||
break;
|
||||
case 'arabic':
|
||||
case 'thai':
|
||||
adjustmentFactor = 0.92;
|
||||
break;
|
||||
case 'japanese':
|
||||
case 'korean':
|
||||
case 'chinese':
|
||||
adjustmentFactor = 0.88;
|
||||
break;
|
||||
default:
|
||||
adjustmentFactor = 0.93;
|
||||
}
|
||||
widthPtsContent *= adjustmentFactor;
|
||||
const adjustments: Partial<Record<AddStampParameters['alphabet'], number>> = {
|
||||
roman: 0.90,
|
||||
arabic: 0.92,
|
||||
thai: 0.92,
|
||||
japanese: 0.88,
|
||||
korean: 0.88,
|
||||
chinese: 0.88,
|
||||
};
|
||||
widthPtsContent *= adjustments[parameters.alphabet] ?? 0.93;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +148,7 @@ export function computeStampPreviewStyle(
|
||||
if (parameters.overrideX >= 0 && parameters.overrideY >= 0) return parameters.overrideY;
|
||||
// For text, backend positions using cap height, not full font size
|
||||
const heightForY = parameters.stampType === 'text'
|
||||
? heightPtsContent * ((ALPHABET_PREVIEW_TWEAKS as any)[parameters.alphabet]?.capHeightRatio ?? 0.70)
|
||||
? heightPtsContent * (getAlphabetTweaks(parameters.alphabet)?.capHeightRatio ?? 0.70)
|
||||
: heightPtsContent;
|
||||
switch (Math.floor((position - 1) / 3)) {
|
||||
case 0: // Top
|
||||
@@ -172,12 +170,11 @@ export function computeStampPreviewStyle(
|
||||
try {
|
||||
const rootFontSizePx = parseFloat(getComputedStyle(document.documentElement).fontSize || '16') || 16;
|
||||
const rowIndex = Math.floor((position - 1) / 3); // 0 top, 1 middle, 2 bottom
|
||||
const offsets = (ALPHABET_PREVIEW_TWEAKS as any)[parameters.alphabet]?.rowOffsetRem ?? [0, 0, 0];
|
||||
const offsets = getAlphabetTweaks(parameters.alphabet)?.rowOffsetRem ?? [0, 0, 0];
|
||||
const offsetRem = offsets[rowIndex] ?? 0;
|
||||
yPx += offsetRem * rootFontSizePx;
|
||||
} catch (e) {
|
||||
// no-op
|
||||
console.error(e);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
const widthPx = widthPtsContent * scaleX;
|
||||
@@ -226,7 +223,7 @@ export function computeStampPreviewStyle(
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-start',
|
||||
lineHeight: (ALPHABET_PREVIEW_TWEAKS as any)[parameters.alphabet]?.lineHeight ?? 1,
|
||||
lineHeight: getAlphabetTweaks(parameters.alphabet)?.lineHeight ?? 1,
|
||||
alignItems,
|
||||
cursor: showQuickGrid ? 'default' : 'move',
|
||||
pointerEvents: showQuickGrid ? 'none' : 'auto',
|
||||
|
||||
@@ -17,15 +17,31 @@ import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { ToolRegistry } from '@app/data/toolsTaxonomy';
|
||||
import { ToolId } from '@app/types/toolId';
|
||||
import { getAvailableToExtensions } from '@app/utils/convertUtils';
|
||||
import type { AutomationParameters } from '@app/types/automation';
|
||||
|
||||
type BaseSettingsComponent = React.ComponentType<{
|
||||
parameters: AutomationParameters;
|
||||
onParameterChange: (key: string, value: unknown) => void;
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
|
||||
type ConvertSettingsComponent = React.ComponentType<{
|
||||
parameters: AutomationParameters;
|
||||
onParameterChange: (key: string, value: unknown) => void;
|
||||
getAvailableToExtensions: typeof getAvailableToExtensions;
|
||||
selectedFiles: File[];
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
|
||||
interface ToolConfigurationModalProps {
|
||||
opened: boolean;
|
||||
tool: {
|
||||
id: string;
|
||||
operation: string;
|
||||
name: string;
|
||||
parameters?: any;
|
||||
parameters?: AutomationParameters;
|
||||
};
|
||||
onSave: (parameters: any) => void;
|
||||
onSave: (parameters: AutomationParameters) => void;
|
||||
onCancel: () => void;
|
||||
toolRegistry: Partial<ToolRegistry>;
|
||||
}
|
||||
@@ -33,11 +49,11 @@ interface ToolConfigurationModalProps {
|
||||
export default function ToolConfigurationModal({ opened, tool, onSave, onCancel, toolRegistry }: ToolConfigurationModalProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [parameters, setParameters] = useState<any>({});
|
||||
const [parameters, setParameters] = useState<AutomationParameters>({});
|
||||
|
||||
// Get tool info from registry
|
||||
const toolInfo = toolRegistry[tool.operation as ToolId];
|
||||
const SettingsComponent = toolInfo?.automationSettings;
|
||||
const SettingsComponent = toolInfo?.automationSettings as BaseSettingsComponent | ConvertSettingsComponent | undefined;
|
||||
|
||||
// Initialize parameters from tool (which should contain defaults from registry)
|
||||
useEffect(() => {
|
||||
@@ -49,6 +65,13 @@ export default function ToolConfigurationModal({ opened, tool, onSave, onCancel,
|
||||
}
|
||||
}, [tool.parameters, tool.operation]);
|
||||
|
||||
const updateParameter = (key: string, value: unknown) => {
|
||||
setParameters(prev => ({
|
||||
...prev,
|
||||
[key]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
// Render the settings component
|
||||
const renderToolSettings = () => {
|
||||
if (!SettingsComponent) {
|
||||
@@ -63,12 +86,11 @@ export default function ToolConfigurationModal({ opened, tool, onSave, onCancel,
|
||||
|
||||
// Special handling for ConvertSettings which needs additional props
|
||||
if (tool.operation === 'convert') {
|
||||
const ConvertComponent = SettingsComponent as ConvertSettingsComponent;
|
||||
return (
|
||||
<SettingsComponent
|
||||
<ConvertComponent
|
||||
parameters={parameters}
|
||||
onParameterChange={(key: string, value: any) => {
|
||||
setParameters((prev: any) => ({ ...prev, [key]: value }));
|
||||
}}
|
||||
onParameterChange={updateParameter}
|
||||
getAvailableToExtensions={getAvailableToExtensions}
|
||||
selectedFiles={[]}
|
||||
disabled={false}
|
||||
@@ -76,12 +98,11 @@ export default function ToolConfigurationModal({ opened, tool, onSave, onCancel,
|
||||
);
|
||||
}
|
||||
|
||||
const GenericComponent = SettingsComponent as BaseSettingsComponent;
|
||||
return (
|
||||
<SettingsComponent
|
||||
<GenericComponent
|
||||
parameters={parameters}
|
||||
onParameterChange={(key: string, value: any) => {
|
||||
setParameters((prev: any) => ({ ...prev, [key]: value }));
|
||||
}}
|
||||
onParameterChange={updateParameter}
|
||||
disabled={false}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
* File lifecycle management - Resource cleanup and memory management
|
||||
*/
|
||||
|
||||
import type { Dispatch, MutableRefObject } from 'react';
|
||||
import { FileId } from '@app/types/file';
|
||||
import { FileContextAction, StirlingFileStub, ProcessedFilePage } from '@app/types/fileContext';
|
||||
import { FileContextAction, StirlingFileStub, ProcessedFilePage, FileContextState } from '@app/types/fileContext';
|
||||
|
||||
const DEBUG = process.env.NODE_ENV === 'development';
|
||||
|
||||
@@ -16,8 +17,8 @@ export class FileLifecycleManager {
|
||||
private fileGenerations = new Map<string, number>(); // Generation tokens to prevent stale cleanup
|
||||
|
||||
constructor(
|
||||
private filesRef: React.MutableRefObject<Map<FileId, File>>,
|
||||
private dispatch: React.Dispatch<FileContextAction>
|
||||
private filesRef: MutableRefObject<Map<FileId, File>>,
|
||||
private dispatch: Dispatch<FileContextAction>
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -34,7 +35,7 @@ export class FileLifecycleManager {
|
||||
/**
|
||||
* Clean up resources for a specific file (with stateRef access for complete cleanup)
|
||||
*/
|
||||
cleanupFile = (fileId: FileId, stateRef?: React.MutableRefObject<any>): void => {
|
||||
cleanupFile = (fileId: FileId, stateRef?: MutableRefObject<FileContextState>): void => {
|
||||
// Use comprehensive cleanup (same as removeFiles)
|
||||
this.cleanupAllResourcesForFile(fileId, stateRef);
|
||||
|
||||
@@ -68,7 +69,7 @@ export class FileLifecycleManager {
|
||||
/**
|
||||
* Schedule delayed cleanup for a file with generation token to prevent stale cleanup
|
||||
*/
|
||||
scheduleCleanup = (fileId: FileId, delay: number = 30000, stateRef?: React.MutableRefObject<any>): void => {
|
||||
scheduleCleanup = (fileId: FileId, delay: number = 30000, stateRef?: MutableRefObject<FileContextState>): void => {
|
||||
// Cancel existing timer
|
||||
const existingTimer = this.cleanupTimers.get(fileId);
|
||||
if (existingTimer) {
|
||||
@@ -101,7 +102,7 @@ export class FileLifecycleManager {
|
||||
/**
|
||||
* Remove a file immediately with complete resource cleanup
|
||||
*/
|
||||
removeFiles = (fileIds: FileId[], stateRef?: React.MutableRefObject<any>): void => {
|
||||
removeFiles = (fileIds: FileId[], stateRef?: MutableRefObject<FileContextState>): void => {
|
||||
fileIds.forEach(fileId => {
|
||||
// Clean up all resources for this file
|
||||
this.cleanupAllResourcesForFile(fileId, stateRef);
|
||||
@@ -114,7 +115,7 @@ export class FileLifecycleManager {
|
||||
/**
|
||||
* Complete resource cleanup for a single file
|
||||
*/
|
||||
private cleanupAllResourcesForFile = (fileId: FileId, stateRef?: React.MutableRefObject<any>): void => {
|
||||
private cleanupAllResourcesForFile = (fileId: FileId, stateRef?: MutableRefObject<FileContextState>): void => {
|
||||
// Remove from files ref
|
||||
this.filesRef.current.delete(fileId);
|
||||
|
||||
@@ -166,7 +167,7 @@ export class FileLifecycleManager {
|
||||
/**
|
||||
* Update file record with race condition guards
|
||||
*/
|
||||
updateStirlingFileStub = (fileId: FileId, updates: Partial<StirlingFileStub>, stateRef?: React.MutableRefObject<any>): void => {
|
||||
updateStirlingFileStub = (fileId: FileId, updates: Partial<StirlingFileStub>, stateRef?: MutableRefObject<FileContextState>): void => {
|
||||
// Guard against updating removed files (race condition protection)
|
||||
if (!this.filesRef.current.has(fileId)) {
|
||||
if (DEBUG) console.warn(`🗂️ Attempted to update removed file (filesRef): ${fileId}`);
|
||||
|
||||
@@ -14,14 +14,15 @@ export interface ProcessedFilePage {
|
||||
pageNumber?: number;
|
||||
rotation?: number;
|
||||
splitBefore?: boolean;
|
||||
[key: string]: any;
|
||||
splitAfter?: boolean;
|
||||
originalPageNumber?: number;
|
||||
}
|
||||
|
||||
export interface ProcessedFileMetadata {
|
||||
pages: ProcessedFilePage[];
|
||||
totalPages?: number;
|
||||
lastProcessed?: number;
|
||||
[key: string]: any;
|
||||
thumbnailUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,8 +82,8 @@ export interface StirlingFile extends File {
|
||||
|
||||
// Type guard to check if a File object has an embedded fileId
|
||||
export function isStirlingFile(file: File): file is StirlingFile {
|
||||
return 'fileId' in file && typeof (file as any).fileId === 'string' &&
|
||||
'quickKey' in file && typeof (file as any).quickKey === 'string';
|
||||
const withIds = file as Partial<StirlingFile>;
|
||||
return typeof withIds.fileId === 'string' && typeof withIds.quickKey === 'string';
|
||||
}
|
||||
|
||||
// Create a StirlingFile from a regular File object
|
||||
@@ -125,13 +126,16 @@ export function extractFiles(files: StirlingFile[]): File[] {
|
||||
}
|
||||
|
||||
// Check if an object is a File or StirlingFile (replaces instanceof File checks)
|
||||
export function isFileObject(obj: any): obj is File | StirlingFile {
|
||||
return obj &&
|
||||
typeof obj.name === 'string' &&
|
||||
typeof obj.size === 'number' &&
|
||||
typeof obj.type === 'string' &&
|
||||
typeof obj.lastModified === 'number' &&
|
||||
typeof obj.arrayBuffer === 'function';
|
||||
export function isFileObject(obj: unknown): obj is File | StirlingFile {
|
||||
if (!obj || typeof obj !== 'object') return false;
|
||||
const candidate = obj as Partial<File>;
|
||||
return (
|
||||
typeof candidate.name === 'string' &&
|
||||
typeof candidate.size === 'number' &&
|
||||
typeof candidate.type === 'string' &&
|
||||
typeof candidate.lastModified === 'number' &&
|
||||
typeof candidate.arrayBuffer === 'function'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
import apiClient from '@app/services/apiClient';
|
||||
|
||||
// Retry configuration
|
||||
@@ -12,6 +13,24 @@ function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
interface ErrorPayload {
|
||||
message?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
function extractErrorMessage(error: unknown, fallback: string): string {
|
||||
if (axios.isAxiosError<ErrorPayload>(error)) {
|
||||
return error.response?.data?.message
|
||||
|| error.response?.data?.error
|
||||
|| error.message
|
||||
|| fallback;
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
baseUrl?: string;
|
||||
contextPath?: string;
|
||||
@@ -80,21 +99,22 @@ export const AppConfigProvider: React.FC<{ children: React.ReactNode }> = ({ chi
|
||||
console.log('[AppConfig] Successfully fetched app config');
|
||||
setLoading(false);
|
||||
return; // Success - exit function
|
||||
} catch (err: any) {
|
||||
const status = err?.response?.status;
|
||||
} catch (error: unknown) {
|
||||
const status = axios.isAxiosError(error) ? error.response?.status : undefined;
|
||||
|
||||
// Check if we should retry (network errors or 5xx errors)
|
||||
const shouldRetry = (!status || status >= 500) && attempt < MAX_RETRIES;
|
||||
|
||||
if (shouldRetry) {
|
||||
console.warn(`[AppConfig] Attempt ${attempt + 1} failed (status ${status || 'network error'}):`, err.message, '- will retry...');
|
||||
const message = extractErrorMessage(error, 'Unknown error');
|
||||
console.warn(`[AppConfig] Attempt ${attempt + 1} failed (status ${status || 'network error'}):`, message, '- will retry...');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Final attempt failed or non-retryable error (4xx)
|
||||
const errorMessage = err?.response?.data?.message || err?.message || 'Unknown error occurred';
|
||||
const errorMessage = extractErrorMessage(error, 'Unknown error occurred');
|
||||
setError(errorMessage);
|
||||
console.error(`[AppConfig] Failed to fetch app config after ${attempt + 1} attempts:`, err);
|
||||
console.error(`[AppConfig] Failed to fetch app config after ${attempt + 1} attempts:`, error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,29 @@
|
||||
import { useMemo, useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
import apiClient from '@app/services/apiClient';
|
||||
|
||||
interface EndpointConfig {
|
||||
backendUrl: string;
|
||||
}
|
||||
|
||||
interface ErrorPayload {
|
||||
message?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
function extractErrorMessage(error: unknown, fallback: string): string {
|
||||
if (axios.isAxiosError<ErrorPayload>(error)) {
|
||||
return error.response?.data?.message
|
||||
|| error.response?.data?.error
|
||||
|| error.message
|
||||
|| fallback;
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Desktop-specific endpoint checker that hits the backend directly via axios.
|
||||
*/
|
||||
@@ -34,8 +53,8 @@ export function useEndpointEnabled(endpoint: string): {
|
||||
});
|
||||
|
||||
setEnabled(response.data);
|
||||
} catch (err: any) {
|
||||
const message = err?.response?.data?.message || err?.message || 'Unknown error occurred';
|
||||
} catch (error: unknown) {
|
||||
const message = extractErrorMessage(error, 'Unknown error occurred');
|
||||
setError(message);
|
||||
setEnabled(null);
|
||||
} finally {
|
||||
@@ -83,8 +102,8 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
});
|
||||
|
||||
setEndpointStatus(response.data);
|
||||
} catch (err: any) {
|
||||
const message = err?.response?.data?.message || err?.message || 'Unknown error occurred';
|
||||
} catch (error: unknown) {
|
||||
const message = extractErrorMessage(error, 'Unknown error occurred');
|
||||
setError(message);
|
||||
|
||||
const fallbackStatus = endpoints.reduce((acc, endpointName) => {
|
||||
|
||||
Reference in New Issue
Block a user