mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-03-04 02:20:19 +01:00
More any type fixes
This commit is contained in:
@@ -1,22 +1,32 @@
|
||||
import { ToastOptions } from '@app/components/toast/types';
|
||||
import { ToastOptions, ToastApi } from '@app/components/toast/types';
|
||||
import { useToast, ToastProvider } from '@app/components/toast/ToastContext';
|
||||
import ToastRenderer from '@app/components/toast/ToastRenderer';
|
||||
|
||||
export { useToast, ToastProvider, ToastRenderer };
|
||||
|
||||
// Global imperative API via module singleton
|
||||
let _api: ReturnType<typeof createImperativeApi> | null = null;
|
||||
type ToastContextInstance = ReturnType<typeof useToast>;
|
||||
|
||||
function createImperativeApi() {
|
||||
const subscribers: Array<(fn: any) => void> = [];
|
||||
let api: any = null;
|
||||
interface ImperativeApi {
|
||||
provide(instance: ToastContextInstance): void;
|
||||
get(): ToastContextInstance | null;
|
||||
onReady(cb: (api: ToastContextInstance) => void): void;
|
||||
}
|
||||
|
||||
// Global imperative API via module singleton
|
||||
let _api: ImperativeApi | null = null;
|
||||
|
||||
function createImperativeApi(): ImperativeApi {
|
||||
const subscribers: Array<(api: ToastContextInstance) => void> = [];
|
||||
let api: ToastContextInstance | null = null;
|
||||
return {
|
||||
provide(instance: any) {
|
||||
provide(instance: ToastContextInstance) {
|
||||
api = instance;
|
||||
subscribers.splice(0).forEach(cb => cb(api));
|
||||
const queued = [...subscribers];
|
||||
subscribers.length = 0;
|
||||
queued.forEach(cb => cb(instance));
|
||||
},
|
||||
get(): any | null { return api; },
|
||||
onReady(cb: (api: any) => void) {
|
||||
get(): ToastContextInstance | null { return api; },
|
||||
onReady(cb: (readyApi: ToastContextInstance) => void) {
|
||||
if (api) cb(api); else subscribers.push(cb);
|
||||
}
|
||||
};
|
||||
@@ -32,30 +42,33 @@ export function ToastPortalBinder() {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function alert(options: ToastOptions) {
|
||||
if (_api?.get()) {
|
||||
return _api.get()!.show(options);
|
||||
function getImperativeApi(): ToastApi | null {
|
||||
return _api?.get() ?? null;
|
||||
}
|
||||
|
||||
export function alert(options: ToastOptions): string {
|
||||
const api = getImperativeApi();
|
||||
if (api) {
|
||||
return api.show(options);
|
||||
}
|
||||
// Queue until provider mounts
|
||||
let id = '';
|
||||
_api?.onReady((api) => { id = api.show(options); });
|
||||
_api?.onReady((readyApi) => { id = readyApi.show(options); });
|
||||
return id;
|
||||
}
|
||||
|
||||
export function updateToast(id: string, options: Partial<ToastOptions>) {
|
||||
_api?.get()?.update(id, options);
|
||||
export function updateToast(id: string, options: Partial<ToastOptions>): void {
|
||||
getImperativeApi()?.update(id, options);
|
||||
}
|
||||
|
||||
export function updateToastProgress(id: string, progress: number) {
|
||||
_api?.get()?.updateProgress(id, progress);
|
||||
export function updateToastProgress(id: string, progress: number): void {
|
||||
getImperativeApi()?.updateProgress(id, progress);
|
||||
}
|
||||
|
||||
export function dismissToast(id: string) {
|
||||
_api?.get()?.dismiss(id);
|
||||
export function dismissToast(id: string): void {
|
||||
getImperativeApi()?.dismiss(id);
|
||||
}
|
||||
|
||||
export function dismissAllToasts() {
|
||||
_api?.get()?.dismissAll();
|
||||
export function dismissAllToasts(): void {
|
||||
getImperativeApi()?.dismissAll();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,17 +4,23 @@ import { AdjustContrastParameters, defaultParameters } from '@app/hooks/tools/ad
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { applyAdjustmentsToCanvas } from '@app/components/tools/adjustContrast/utils';
|
||||
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
|
||||
import { createFileFromApiResponse } from '@app/utils/fileResponseUtils';
|
||||
import { createFileFromApiResponse, toArrayBuffer } from '@app/utils/fileResponseUtils';
|
||||
import type { PDFDocumentProxy, PDFPageProxy, RenderParameters } from 'pdfjs-dist/types/src/display/api';
|
||||
|
||||
async function renderPdfPageToCanvas(pdf: any, pageNumber: number, scale: number): Promise<HTMLCanvasElement> {
|
||||
const page = await pdf.getPage(pageNumber);
|
||||
async function renderPdfPageToCanvas(pdf: PDFDocumentProxy, pageNumber: number, scale: number): Promise<HTMLCanvasElement> {
|
||||
const page: PDFPageProxy = await pdf.getPage(pageNumber);
|
||||
const viewport = page.getViewport({ scale });
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) throw new Error('Canvas 2D context unavailable');
|
||||
await page.render({ canvasContext: ctx, viewport }).promise;
|
||||
const renderConfig: RenderParameters = {
|
||||
canvasContext: ctx,
|
||||
viewport,
|
||||
canvas,
|
||||
};
|
||||
await page.render(renderConfig).promise;
|
||||
return canvas;
|
||||
}
|
||||
|
||||
@@ -41,7 +47,11 @@ async function buildAdjustedPdfForFile(file: File, params: AdjustContrastParamet
|
||||
}
|
||||
|
||||
const pdfBytes = await newDoc.save();
|
||||
const out = createFileFromApiResponse(pdfBytes, { 'content-type': 'application/pdf' }, file.name);
|
||||
const out = createFileFromApiResponse(
|
||||
toArrayBuffer(pdfBytes),
|
||||
{ 'content-type': 'application/pdf' },
|
||||
file.name
|
||||
);
|
||||
pdfWorkerManager.destroyDocument(pdf);
|
||||
return out;
|
||||
}
|
||||
@@ -91,4 +101,3 @@ export const useAdjustContrastOperation = () => {
|
||||
getErrorMessage: () => t('adjustContrast.error.failed', 'Failed to adjust colors/contrast')
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useCallback } from 'react';
|
||||
import { PDFDocument, PDFPage } from '@app/types/pageEditor';
|
||||
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
|
||||
import { createQuickKey } from '@app/types/fileContext';
|
||||
import type { PDFDocumentProxy, RenderParameters } from 'pdfjs-dist/types/src/display/api';
|
||||
|
||||
export function usePDFProcessor() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -27,7 +28,12 @@ export function usePDFProcessor() {
|
||||
throw new Error('Could not get canvas context');
|
||||
}
|
||||
|
||||
await page.render({ canvasContext: context, viewport, canvas }).promise;
|
||||
const renderConfig: RenderParameters = {
|
||||
canvasContext: context,
|
||||
viewport,
|
||||
canvas,
|
||||
};
|
||||
await page.render(renderConfig).promise;
|
||||
const thumbnail = canvas.toDataURL();
|
||||
|
||||
// Clean up using worker manager
|
||||
@@ -42,7 +48,7 @@ export function usePDFProcessor() {
|
||||
|
||||
// Internal function to generate thumbnail from already-opened PDF
|
||||
const generateThumbnailFromPDF = useCallback(async (
|
||||
pdf: any,
|
||||
pdf: PDFDocumentProxy,
|
||||
pageNumber: number,
|
||||
scale: number = 0.5
|
||||
): Promise<string> => {
|
||||
@@ -58,7 +64,12 @@ export function usePDFProcessor() {
|
||||
throw new Error('Could not get canvas context');
|
||||
}
|
||||
|
||||
await page.render({ canvasContext: context, viewport }).promise;
|
||||
const renderConfig: RenderParameters = {
|
||||
canvasContext: context,
|
||||
viewport,
|
||||
canvas,
|
||||
};
|
||||
await page.render(renderConfig).promise;
|
||||
return canvas.toDataURL();
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
* Service for managing automation configurations in IndexedDB
|
||||
*/
|
||||
|
||||
import type { AutomationParameters } from '@app/types/automation';
|
||||
|
||||
export interface AutomationConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
operations: Array<{
|
||||
operation: string;
|
||||
parameters: any;
|
||||
parameters: AutomationParameters;
|
||||
}>;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
@@ -180,4 +182,4 @@ class AutomationStorage {
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const automationStorage = new AutomationStorage();
|
||||
export const automationStorage = new AutomationStorage();
|
||||
|
||||
@@ -5,6 +5,7 @@ import { FileAnalyzer } from '@app/services/fileAnalyzer';
|
||||
import { ProcessingErrorHandler } from '@app/services/processingErrorHandler';
|
||||
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
|
||||
import { createQuickKey } from '@app/types/fileContext';
|
||||
import type { PDFPageProxy, RenderParameters } from 'pdfjs-dist/types/src/display/api';
|
||||
|
||||
export class EnhancedPDFProcessingService {
|
||||
private static instance: EnhancedPDFProcessingService;
|
||||
@@ -400,7 +401,7 @@ export class EnhancedPDFProcessingService {
|
||||
/**
|
||||
* Render a page thumbnail with specified quality
|
||||
*/
|
||||
private async renderPageThumbnail(page: any, quality: 'low' | 'medium' | 'high'): Promise<string> {
|
||||
private async renderPageThumbnail(page: PDFPageProxy, quality: 'low' | 'medium' | 'high'): Promise<string> {
|
||||
const scales = { low: 0.2, medium: 0.5, high: 0.8 }; // Reduced low quality for page editor
|
||||
const scale = scales[quality];
|
||||
|
||||
@@ -414,7 +415,12 @@ export class EnhancedPDFProcessingService {
|
||||
throw new Error('Could not get canvas context');
|
||||
}
|
||||
|
||||
await page.render({ canvasContext: context, viewport }).promise;
|
||||
const renderConfig: RenderParameters = {
|
||||
canvasContext: context,
|
||||
viewport,
|
||||
canvas,
|
||||
};
|
||||
await page.render(renderConfig).promise;
|
||||
return canvas.toDataURL('image/jpeg', 0.8); // Use JPEG for better compression
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import { FileId } from '@app/types/file';
|
||||
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
|
||||
import { PDFDocumentProxy } from 'pdfjs-dist';
|
||||
import type { PDFDocumentProxy, RenderParameters } from 'pdfjs-dist/types/src/display/api';
|
||||
|
||||
interface ThumbnailResult {
|
||||
pageNumber: number;
|
||||
@@ -49,7 +49,7 @@ export class ThumbnailGenerationService {
|
||||
/**
|
||||
* Get or create a cached PDF document
|
||||
*/
|
||||
private async getCachedPDFDocument(fileId: FileId, pdfArrayBuffer: ArrayBuffer): Promise<any> {
|
||||
private async getCachedPDFDocument(fileId: FileId, pdfArrayBuffer: ArrayBuffer): Promise<PDFDocumentProxy> {
|
||||
const cached = this.pdfDocumentCache.get(fileId);
|
||||
if (cached) {
|
||||
cached.lastUsed = Date.now();
|
||||
@@ -176,7 +176,12 @@ export class ThumbnailGenerationService {
|
||||
throw new Error('Could not get canvas context');
|
||||
}
|
||||
|
||||
await page.render({ canvasContext: context, viewport }).promise;
|
||||
const renderConfig: RenderParameters = {
|
||||
canvasContext: context,
|
||||
viewport,
|
||||
canvas,
|
||||
};
|
||||
await page.render(renderConfig).promise;
|
||||
const thumbnail = canvas.toDataURL('image/jpeg', quality);
|
||||
|
||||
allResults.push({ pageNumber, thumbnail, success: true });
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import apiClient from '@app/services/apiClient';
|
||||
import type { AxiosRequestConfig } from 'axios';
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
@@ -29,10 +30,17 @@ export interface AdminSettingsData {
|
||||
disabledUsers: number;
|
||||
currentUsername?: string;
|
||||
roleDetails?: Record<string, string>;
|
||||
teams?: any[];
|
||||
teams?: TeamSummary[];
|
||||
maxPaidUsers?: number;
|
||||
}
|
||||
|
||||
export interface TeamSummary {
|
||||
id: number;
|
||||
name: string;
|
||||
memberCount?: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface CreateUserRequest {
|
||||
username: string;
|
||||
password?: string;
|
||||
@@ -100,9 +108,10 @@ export const userManagementService = {
|
||||
if (data.forceChange !== undefined) {
|
||||
formData.append('forceChange', data.forceChange.toString());
|
||||
}
|
||||
await apiClient.post('/api/v1/user/admin/saveUser', formData, {
|
||||
suppressErrorToast: true, // Component will handle error display
|
||||
} as any);
|
||||
const config: AxiosRequestConfig & { suppressErrorToast?: boolean } = {
|
||||
suppressErrorToast: true,
|
||||
};
|
||||
await apiClient.post('/api/v1/user/admin/saveUser', formData, config);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -115,9 +124,10 @@ export const userManagementService = {
|
||||
if (data.teamId) {
|
||||
formData.append('teamId', data.teamId.toString());
|
||||
}
|
||||
await apiClient.post('/api/v1/user/admin/changeRole', formData, {
|
||||
const config: AxiosRequestConfig & { suppressErrorToast?: boolean } = {
|
||||
suppressErrorToast: true,
|
||||
} as any);
|
||||
};
|
||||
await apiClient.post('/api/v1/user/admin/changeRole', formData, config);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -126,18 +136,20 @@ export const userManagementService = {
|
||||
async toggleUserEnabled(username: string, enabled: boolean): Promise<void> {
|
||||
const formData = new FormData();
|
||||
formData.append('enabled', enabled.toString());
|
||||
await apiClient.post(`/api/v1/user/admin/changeUserEnabled/${username}`, formData, {
|
||||
const config: AxiosRequestConfig & { suppressErrorToast?: boolean } = {
|
||||
suppressErrorToast: true,
|
||||
} as any);
|
||||
};
|
||||
await apiClient.post(`/api/v1/user/admin/changeUserEnabled/${username}`, formData, config);
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a user (admin only)
|
||||
*/
|
||||
async deleteUser(username: string): Promise<void> {
|
||||
await apiClient.post(`/api/v1/user/admin/deleteUser/${username}`, null, {
|
||||
const config: AxiosRequestConfig & { suppressErrorToast?: boolean } = {
|
||||
suppressErrorToast: true,
|
||||
} as any);
|
||||
};
|
||||
await apiClient.post(`/api/v1/user/admin/deleteUser/${username}`, null, config);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -153,12 +165,14 @@ export const userManagementService = {
|
||||
formData.append('teamId', data.teamId.toString());
|
||||
}
|
||||
|
||||
const config: AxiosRequestConfig & { suppressErrorToast?: boolean } = {
|
||||
suppressErrorToast: true,
|
||||
};
|
||||
|
||||
const response = await apiClient.post<InviteUsersResponse>(
|
||||
'/api/v1/user/admin/inviteUsers',
|
||||
formData,
|
||||
{
|
||||
suppressErrorToast: true, // Component will handle error display
|
||||
} as any
|
||||
config
|
||||
);
|
||||
|
||||
return response.data;
|
||||
|
||||
@@ -2,6 +2,7 @@ import JSZip, { JSZipObject } from 'jszip';
|
||||
import { StirlingFileStub, createStirlingFile } from '@app/types/fileContext';
|
||||
import { generateThumbnailForFile } from '@app/utils/thumbnailUtils';
|
||||
import { fileStorage } from '@app/services/fileStorage';
|
||||
import { toArrayBuffer } from '@app/utils/fileResponseUtils';
|
||||
|
||||
// Undocumented interface in JSZip for JSZipObject._data
|
||||
interface CompressedObject {
|
||||
@@ -13,7 +14,8 @@ interface CompressedObject {
|
||||
}
|
||||
|
||||
const getData = (zipEntry: JSZipObject): CompressedObject | undefined => {
|
||||
return (zipEntry as any)._data as CompressedObject;
|
||||
const candidate = zipEntry as JSZipObject & { _data?: CompressedObject };
|
||||
return candidate._data;
|
||||
};
|
||||
|
||||
export interface ZipExtractionResult {
|
||||
@@ -216,7 +218,7 @@ export class ZipFileService {
|
||||
const content = await zipEntry.async('uint8array');
|
||||
|
||||
// Create File object
|
||||
const extractedFile = new File([content as any], this.sanitizeFilename(filename), {
|
||||
const extractedFile = new File([toArrayBuffer(content)], this.sanitizeFilename(filename), {
|
||||
type: 'application/pdf',
|
||||
lastModified: zipEntry.date?.getTime() || Date.now()
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFileSelection } from "@app/contexts/FileContext";
|
||||
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";
|
||||
@@ -29,8 +29,12 @@ const AddAttachments = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =
|
||||
if (operation.files && onComplete) {
|
||||
onComplete(operation.files);
|
||||
}
|
||||
} catch (error: any) {
|
||||
onError?.(error?.message || t("AddAttachmentsRequest.error.failed", "Add attachments operation failed"));
|
||||
} catch (error: unknown) {
|
||||
const message =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: t("AddAttachmentsRequest.error.failed", "Add attachments operation failed");
|
||||
onError?.(message);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -56,7 +60,7 @@ const AddAttachments = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =
|
||||
});
|
||||
|
||||
const getSteps = () => {
|
||||
const steps: any[] = [];
|
||||
const steps: MiddleStepConfig[] = [];
|
||||
|
||||
// Step 1: Attachments Selection
|
||||
steps.push({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFileSelection } from "@app/contexts/FileContext";
|
||||
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";
|
||||
@@ -30,8 +30,12 @@ const AddPageNumbers = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =
|
||||
if (operation.files && onComplete) {
|
||||
onComplete(operation.files);
|
||||
}
|
||||
} catch (error: any) {
|
||||
onError?.(error?.message || t("addPageNumbers.error.failed", "Add page numbers operation failed"));
|
||||
} catch (error: unknown) {
|
||||
const message =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: t("addPageNumbers.error.failed", "Add page numbers operation failed");
|
||||
onError?.(message);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -58,7 +62,7 @@ const AddPageNumbers = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =
|
||||
});
|
||||
|
||||
const getSteps = () => {
|
||||
const steps: any[] = [];
|
||||
const steps: MiddleStepConfig[] = [];
|
||||
|
||||
// Step 1: Position Selection & Pages/Starting Number
|
||||
steps.push({
|
||||
@@ -123,4 +127,4 @@ const AddPageNumbers = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =
|
||||
|
||||
AddPageNumbers.tool = () => useAddPageNumbersOperation;
|
||||
|
||||
export default AddPageNumbers as ToolComponent;
|
||||
export default AddPageNumbers as ToolComponent;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFileSelection } from "@app/contexts/FileContext";
|
||||
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 { useAddStampParameters } from "@app/components/tools/addStamp/useAddStampParameters";
|
||||
@@ -39,8 +39,12 @@ const AddStamp = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
if (operation.files && onComplete) {
|
||||
onComplete(operation.files);
|
||||
}
|
||||
} catch (error: any) {
|
||||
onError?.(error?.message || t("AddStampRequest.error.failed", "Add stamp operation failed"));
|
||||
} catch (error: unknown) {
|
||||
const message =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: t("AddStampRequest.error.failed", "Add stamp operation failed");
|
||||
onError?.(message);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -67,7 +71,7 @@ const AddStamp = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
});
|
||||
|
||||
const getSteps = () => {
|
||||
const steps: any[] = [];
|
||||
const steps: MiddleStepConfig[] = [];
|
||||
|
||||
// Step 1: Stamp Setup
|
||||
steps.push({
|
||||
@@ -185,4 +189,3 @@ AddStamp.tool = () => useAddStampOperation;
|
||||
|
||||
export default AddStamp as ToolComponent;
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } 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 { useFileSelection } from "@app/contexts/FileContext";
|
||||
@@ -29,8 +29,12 @@ const ReorganizePages = ({ onPreviewFile, onComplete, onError }: BaseToolProps)
|
||||
if (operation.files && onComplete) {
|
||||
onComplete(operation.files);
|
||||
}
|
||||
} catch (error: any) {
|
||||
onError?.(error?.message || t("reorganizePages.error.failed", "Failed to reorganize pages"));
|
||||
} catch (error: unknown) {
|
||||
const message =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: t("reorganizePages.error.failed", "Failed to reorganize pages");
|
||||
onError?.(message);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,7 +59,7 @@ const ReorganizePages = ({ onPreviewFile, onComplete, onError }: BaseToolProps)
|
||||
}
|
||||
});
|
||||
|
||||
const steps = [
|
||||
const steps: MiddleStepConfig[] = [
|
||||
{
|
||||
title: t("reorganizePages.settings.title", "Settings"),
|
||||
isCollapsed: accordion.getCollapsedState(Step.SETTINGS),
|
||||
@@ -97,8 +101,8 @@ const ReorganizePages = ({ onPreviewFile, onComplete, onError }: BaseToolProps)
|
||||
});
|
||||
};
|
||||
|
||||
(ReorganizePages as any).tool = () => useReorganizePagesOperation;
|
||||
|
||||
export default ReorganizePages as ToolComponent;
|
||||
const ReorganizePagesTool = ReorganizePages as ToolComponent;
|
||||
ReorganizePagesTool.tool = () => useReorganizePagesOperation;
|
||||
|
||||
export default ReorganizePagesTool;
|
||||
|
||||
|
||||
@@ -30,16 +30,58 @@ export const getFilenameFromHeaders = (contentDisposition: string = ''): string
|
||||
* @param fallbackFilename - Filename to use if none provided in headers
|
||||
* @returns File object
|
||||
*/
|
||||
type HeaderSource =
|
||||
| Record<string, string | null | undefined>
|
||||
| Headers
|
||||
| Array<[string, string]>
|
||||
| undefined
|
||||
| null;
|
||||
|
||||
function getHeaderValue(headers: HeaderSource, key: string): string | undefined {
|
||||
if (!headers) return undefined;
|
||||
if (headers instanceof Headers) {
|
||||
const value = headers.get(key);
|
||||
return value === null ? undefined : value;
|
||||
}
|
||||
if (Array.isArray(headers)) {
|
||||
const entry = headers.find(([headerKey]) => headerKey.toLowerCase() === key.toLowerCase());
|
||||
return entry?.[1];
|
||||
}
|
||||
const value = headers[key];
|
||||
return value === null || value === undefined ? undefined : value;
|
||||
}
|
||||
|
||||
export const createFileFromApiResponse = (
|
||||
responseData: any,
|
||||
headers: any,
|
||||
responseData: BlobPart,
|
||||
headers: HeaderSource,
|
||||
fallbackFilename: string
|
||||
): File => {
|
||||
const contentType = headers?.['content-type'] || 'application/octet-stream';
|
||||
const contentDisposition = headers?.['content-disposition'] || '';
|
||||
const contentType = getHeaderValue(headers, 'content-type') || 'application/octet-stream';
|
||||
const contentDisposition = getHeaderValue(headers, 'content-disposition') || '';
|
||||
|
||||
const filename = getFilenameFromHeaders(contentDisposition) || fallbackFilename;
|
||||
const blob = new Blob([responseData], { type: contentType });
|
||||
|
||||
return new File([blob], filename, { type: contentType });
|
||||
};
|
||||
|
||||
export function toArrayBuffer(view: ArrayBufferView): ArrayBuffer {
|
||||
const { buffer, byteOffset, byteLength } = view;
|
||||
|
||||
const start = byteOffset;
|
||||
const end = byteOffset + byteLength;
|
||||
|
||||
if (typeof SharedArrayBuffer !== 'undefined' && buffer instanceof SharedArrayBuffer) {
|
||||
const out = new ArrayBuffer(byteLength);
|
||||
new Uint8Array(out).set(new Uint8Array(buffer, start, byteLength));
|
||||
return out;
|
||||
}
|
||||
|
||||
if (buffer instanceof ArrayBuffer && typeof buffer.slice === 'function') {
|
||||
return buffer.slice(start, end);
|
||||
}
|
||||
|
||||
const out = new ArrayBuffer(byteLength);
|
||||
new Uint8Array(out).set(new Uint8Array(buffer, start, byteLength));
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
|
||||
import type { PDFDocumentProxy, RenderParameters } from 'pdfjs-dist/types/src/display/api';
|
||||
|
||||
export interface ThumbnailWithMetadata {
|
||||
thumbnail: string; // Always returns a thumbnail (placeholder if needed)
|
||||
@@ -256,7 +257,7 @@ function drawLargeLockIcon(ctx: CanvasRenderingContext2D, centerX: number, cente
|
||||
/**
|
||||
* Generate standard PDF thumbnail by rendering first page
|
||||
*/
|
||||
async function generateStandardPDFThumbnail(pdf: any, scale: number): Promise<string> {
|
||||
async function generateStandardPDFThumbnail(pdf: PDFDocumentProxy, scale: number): Promise<string> {
|
||||
const page = await pdf.getPage(1);
|
||||
const viewport = page.getViewport({ scale });
|
||||
const canvas = document.createElement("canvas");
|
||||
@@ -268,7 +269,12 @@ async function generateStandardPDFThumbnail(pdf: any, scale: number): Promise<st
|
||||
throw new Error('Could not get canvas context');
|
||||
}
|
||||
|
||||
await page.render({ canvasContext: context, viewport }).promise;
|
||||
const renderConfig: RenderParameters = {
|
||||
canvasContext: context,
|
||||
viewport,
|
||||
canvas,
|
||||
};
|
||||
await page.render(renderConfig).promise;
|
||||
return canvas.toDataURL();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* - No email confirmation flow (auto-confirmed on registration)
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import apiClient from '@app/services/apiClient';
|
||||
|
||||
// Auth types
|
||||
@@ -17,7 +18,7 @@ export interface User {
|
||||
role: string;
|
||||
enabled?: boolean;
|
||||
is_anonymous?: boolean;
|
||||
app_metadata?: Record<string, any>;
|
||||
app_metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
@@ -46,6 +47,35 @@ export type AuthChangeEvent =
|
||||
|
||||
type AuthChangeCallback = (event: AuthChangeEvent, session: Session | null) => void;
|
||||
|
||||
type OAuthQueryParams = Record<string, string | number | boolean | null | undefined>;
|
||||
|
||||
interface ErrorResponsePayload {
|
||||
message?: string;
|
||||
error?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
function extractAxiosErrorMessage(error: unknown, fallback: string): string {
|
||||
if (axios.isAxiosError<ErrorResponsePayload>(error)) {
|
||||
const data = error.response?.data;
|
||||
if (data && typeof data === 'object') {
|
||||
if (typeof data.message === 'string' && data.message) return data.message;
|
||||
if (typeof data.error === 'string' && data.error) return data.error;
|
||||
}
|
||||
if (typeof error.message === 'string' && error.message) {
|
||||
return error.message;
|
||||
}
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function getAxiosStatus(error: unknown): number | undefined {
|
||||
return axios.isAxiosError(error) ? error.response?.status : undefined;
|
||||
}
|
||||
|
||||
class SpringAuthClient {
|
||||
private listeners: AuthChangeCallback[] = [];
|
||||
private sessionCheckInterval: NodeJS.Timeout | null = null;
|
||||
@@ -105,21 +135,22 @@ class SpringAuthClient {
|
||||
|
||||
console.debug('[SpringAuth] getSession: Session retrieved successfully');
|
||||
return { data: { session }, error: null };
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('[SpringAuth] getSession error:', error);
|
||||
|
||||
// If 401/403, token is invalid - clear it
|
||||
if (error?.response?.status === 401 || error?.response?.status === 403) {
|
||||
localStorage.removeItem('stirling_jwt');
|
||||
console.debug('[SpringAuth] getSession: Not authenticated');
|
||||
return { data: { session: null }, error: null };
|
||||
if (axios.isAxiosError(error)) {
|
||||
const status = error.response?.status;
|
||||
if (status === 401 || status === 403) {
|
||||
localStorage.removeItem('stirling_jwt');
|
||||
console.debug('[SpringAuth] getSession: Not authenticated');
|
||||
return { data: { session: null }, error: null };
|
||||
}
|
||||
}
|
||||
|
||||
// Clear potentially invalid token on other errors too
|
||||
localStorage.removeItem('stirling_jwt');
|
||||
return {
|
||||
data: { session: null },
|
||||
error: { message: error?.response?.data?.message || error?.message || 'Unknown error' },
|
||||
error: { message: extractAxiosErrorMessage(error, 'Unknown error') },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -157,9 +188,9 @@ class SpringAuthClient {
|
||||
this.notifyListeners('SIGNED_IN', session);
|
||||
|
||||
return { user: data.user, session, error: null };
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('[SpringAuth] signInWithPassword error:', error);
|
||||
const errorMessage = error?.response?.data?.error || error?.message || 'Login failed';
|
||||
const errorMessage = extractAxiosErrorMessage(error, 'Login failed');
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
@@ -189,9 +220,9 @@ class SpringAuthClient {
|
||||
// Note: Spring backend auto-confirms users (no email verification)
|
||||
// Return user but no session (user needs to login)
|
||||
return { user: data.user, session: null, error: null };
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('[SpringAuth] signUp error:', error);
|
||||
const errorMessage = error?.response?.data?.error || error?.message || 'Registration failed';
|
||||
const errorMessage = extractAxiosErrorMessage(error, 'Registration failed');
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
@@ -206,7 +237,7 @@ class SpringAuthClient {
|
||||
*/
|
||||
async signInWithOAuth(params: {
|
||||
provider: 'github' | 'google' | 'apple' | 'azure';
|
||||
options?: { redirectTo?: string; queryParams?: Record<string, any> };
|
||||
options?: { redirectTo?: string; queryParams?: OAuthQueryParams };
|
||||
}): Promise<{ error: AuthError | null }> {
|
||||
try {
|
||||
const redirectUrl = `/oauth2/authorization/${params.provider}`;
|
||||
@@ -233,13 +264,11 @@ class SpringAuthClient {
|
||||
});
|
||||
|
||||
return { data: {}, error: null };
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('[SpringAuth] resetPasswordForEmail error:', error);
|
||||
return {
|
||||
data: {},
|
||||
error: {
|
||||
message: error?.response?.data?.error || error?.message || 'Password reset failed',
|
||||
},
|
||||
error: { message: extractAxiosErrorMessage(error, 'Password reset failed') },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -267,12 +296,10 @@ class SpringAuthClient {
|
||||
this.notifyListeners('SIGNED_OUT', null);
|
||||
|
||||
return { error: null };
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('[SpringAuth] signOut error:', error);
|
||||
return {
|
||||
error: {
|
||||
message: error?.response?.data?.error || error?.message || 'Logout failed',
|
||||
},
|
||||
error: { message: extractAxiosErrorMessage(error, 'Logout failed') },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -306,18 +333,19 @@ class SpringAuthClient {
|
||||
this.notifyListeners('TOKEN_REFRESHED', session);
|
||||
|
||||
return { data: { session }, error: null };
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
console.error('[SpringAuth] refreshSession error:', error);
|
||||
localStorage.removeItem('stirling_jwt');
|
||||
|
||||
// Handle different error statuses
|
||||
if (error?.response?.status === 401 || error?.response?.status === 403) {
|
||||
const status = getAxiosStatus(error);
|
||||
if (status === 401 || status === 403) {
|
||||
return { data: { session: null }, error: { message: 'Token refresh failed - please log in again' } };
|
||||
}
|
||||
|
||||
return {
|
||||
data: { session: null },
|
||||
error: { message: error?.response?.data?.message || error?.message || 'Token refresh failed' },
|
||||
error: { message: extractAxiosErrorMessage(error, 'Token refresh failed') },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user