mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-08 17:51:20 +02:00
Merge 4c39979460
into 1a3e8e7ecf
This commit is contained in:
commit
11a72780da
@ -24,7 +24,7 @@ indent_size = 2
|
|||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
[{*.js,*.jsx,*.ts,*.tsx}]
|
[{*.js,*.jsx,*.mjs,*.ts,*.tsx}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.css]
|
[*.css]
|
||||||
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -147,6 +147,8 @@ jobs:
|
|||||||
cache-dependency-path: frontend/package-lock.json
|
cache-dependency-path: frontend/package-lock.json
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
run: cd frontend && npm ci
|
run: cd frontend && npm ci
|
||||||
|
- name: Lint frontend
|
||||||
|
run: cd frontend && npm run lint
|
||||||
- name: Build frontend
|
- name: Build frontend
|
||||||
run: cd frontend && npm run build
|
run: cd frontend && npm run build
|
||||||
- name: Run frontend tests
|
- name: Run frontend tests
|
||||||
|
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@ -19,5 +19,6 @@
|
|||||||
"yzhang.markdown-all-in-one", // Markdown All-in-One extension for enhanced Markdown editing
|
"yzhang.markdown-all-in-one", // Markdown All-in-One extension for enhanced Markdown editing
|
||||||
"stylelint.vscode-stylelint", // Stylelint extension for CSS and SCSS linting
|
"stylelint.vscode-stylelint", // Stylelint extension for CSS and SCSS linting
|
||||||
"redhat.vscode-yaml", // YAML extension for Visual Studio Code
|
"redhat.vscode-yaml", // YAML extension for Visual Studio Code
|
||||||
|
"dbaeumer.vscode-eslint", // ESLint extension for TypeScript linting
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
31
frontend/eslint.config.mjs
Normal file
31
frontend/eslint.config.mjs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// @ts-check
|
||||||
|
|
||||||
|
import eslint from '@eslint/js';
|
||||||
|
import { defineConfig } from 'eslint/config';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default defineConfig(
|
||||||
|
eslint.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"dist", // Contains 3rd party code
|
||||||
|
"public", // Contains 3rd party code
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
"no-empty": "off", // Temporarily disabled until codebase conformant
|
||||||
|
"no-empty-pattern": "off", // Temporarily disabled until codebase conformant
|
||||||
|
"no-undef": "off", // Temporarily disabled until codebase conformant
|
||||||
|
"no-useless-escape": "off", // Temporarily disabled until codebase conformant
|
||||||
|
"prefer-const": "off", // Temporarily disabled until codebase conformant
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off", // Temporarily disabled until codebase conformant
|
||||||
|
"@typescript-eslint/no-empty-object-type": "off", // Temporarily disabled until codebase conformant
|
||||||
|
"@typescript-eslint/no-explicit-any": "off", // Temporarily disabled until codebase conformant
|
||||||
|
"@typescript-eslint/no-require-imports": "off", // Temporarily disabled until codebase conformant
|
||||||
|
"@typescript-eslint/no-unused-expressions": "off", // Temporarily disabled until codebase conformant
|
||||||
|
"@typescript-eslint/no-unused-vars": "off", // Temporarily disabled until codebase conformant
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
934
frontend/package-lock.json
generated
934
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -40,6 +40,7 @@
|
|||||||
"predev": "npm run generate-icons",
|
"predev": "npm run generate-icons",
|
||||||
"dev": "npx tsc --noEmit && vite",
|
"dev": "npx tsc --noEmit && vite",
|
||||||
"prebuild": "npm run generate-icons",
|
"prebuild": "npm run generate-icons",
|
||||||
|
"lint": "npx eslint",
|
||||||
"build": "npx tsc --noEmit && vite build",
|
"build": "npx tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
@ -72,6 +73,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.34.0",
|
||||||
"@iconify-json/material-symbols": "^1.2.33",
|
"@iconify-json/material-symbols": "^1.2.33",
|
||||||
"@iconify/utils": "^3.0.1",
|
"@iconify/utils": "^3.0.1",
|
||||||
"@playwright/test": "^1.40.0",
|
"@playwright/test": "^1.40.0",
|
||||||
@ -80,6 +82,7 @@
|
|||||||
"@types/react-dom": "^19.1.5",
|
"@types/react-dom": "^19.1.5",
|
||||||
"@vitejs/plugin-react": "^4.5.0",
|
"@vitejs/plugin-react": "^4.5.0",
|
||||||
"@vitest/coverage-v8": "^1.0.0",
|
"@vitest/coverage-v8": "^1.0.0",
|
||||||
|
"eslint": "^9.34.0",
|
||||||
"jsdom": "^23.0.0",
|
"jsdom": "^23.0.0",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"madge": "^8.0.0",
|
"madge": "^8.0.0",
|
||||||
@ -87,7 +90,8 @@
|
|||||||
"postcss-cli": "^11.0.1",
|
"postcss-cli": "^11.0.1",
|
||||||
"postcss-preset-mantine": "^1.17.0",
|
"postcss-preset-mantine": "^1.17.0",
|
||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.9.2",
|
||||||
|
"typescript-eslint": "^8.42.0",
|
||||||
"vite": "^6.3.5",
|
"vite": "^6.3.5",
|
||||||
"vitest": "^1.0.0"
|
"vitest": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -174,7 +174,7 @@ export const useToolOperation = <TParams>(
|
|||||||
let processedFiles: File[];
|
let processedFiles: File[];
|
||||||
|
|
||||||
switch (config.toolType) {
|
switch (config.toolType) {
|
||||||
case ToolType.singleFile:
|
case ToolType.singleFile: {
|
||||||
// Individual file processing - separate API call per file
|
// Individual file processing - separate API call per file
|
||||||
const apiCallsConfig: ApiCallsConfig<TParams> = {
|
const apiCallsConfig: ApiCallsConfig<TParams> = {
|
||||||
endpoint: config.endpoint,
|
endpoint: config.endpoint,
|
||||||
@ -190,8 +190,9 @@ export const useToolOperation = <TParams>(
|
|||||||
actions.setStatus
|
actions.setStatus
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case ToolType.multiFile:
|
case ToolType.multiFile: {
|
||||||
// Multi-file processing - single API call with all files
|
// Multi-file processing - single API call with all files
|
||||||
actions.setStatus('Processing files...');
|
actions.setStatus('Processing files...');
|
||||||
const formData = config.buildFormData(params, validFiles);
|
const formData = config.buildFormData(params, validFiles);
|
||||||
@ -204,7 +205,7 @@ export const useToolOperation = <TParams>(
|
|||||||
// Use custom responseHandler for multi-file (handles ZIP extraction)
|
// Use custom responseHandler for multi-file (handles ZIP extraction)
|
||||||
processedFiles = await config.responseHandler(response.data, validFiles);
|
processedFiles = await config.responseHandler(response.data, validFiles);
|
||||||
} else if (response.data.type === 'application/pdf' ||
|
} else if (response.data.type === 'application/pdf' ||
|
||||||
(response.headers && response.headers['content-type'] === 'application/pdf')) {
|
(response.headers && response.headers['content-type'] === 'application/pdf')) {
|
||||||
// Single PDF response (e.g. split with merge option) - use original filename
|
// Single PDF response (e.g. split with merge option) - use original filename
|
||||||
const originalFileName = validFiles[0]?.name || 'document.pdf';
|
const originalFileName = validFiles[0]?.name || 'document.pdf';
|
||||||
const singleFile = new File([response.data], originalFileName, { type: 'application/pdf' });
|
const singleFile = new File([response.data], originalFileName, { type: 'application/pdf' });
|
||||||
@ -219,6 +220,7 @@ export const useToolOperation = <TParams>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case ToolType.custom:
|
case ToolType.custom:
|
||||||
actions.setStatus('Processing files...');
|
actions.setStatus('Processing files...');
|
||||||
@ -243,7 +245,7 @@ export const useToolOperation = <TParams>(
|
|||||||
// Replace input files with processed files (consumeFiles handles pinning)
|
// Replace input files with processed files (consumeFiles handles pinning)
|
||||||
const inputFileIds: FileId[] = [];
|
const inputFileIds: FileId[] = [];
|
||||||
const inputFileRecords: FileRecord[] = [];
|
const inputFileRecords: FileRecord[] = [];
|
||||||
|
|
||||||
// Build parallel arrays of IDs and records for undo tracking
|
// Build parallel arrays of IDs and records for undo tracking
|
||||||
for (const file of validFiles) {
|
for (const file of validFiles) {
|
||||||
const fileId = findFileId(file);
|
const fileId = findFileId(file);
|
||||||
@ -259,9 +261,9 @@ export const useToolOperation = <TParams>(
|
|||||||
console.warn(`No file ID found for file: ${file.name}`);
|
console.warn(`No file ID found for file: ${file.name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputFileIds = await consumeFiles(inputFileIds, processedFiles);
|
const outputFileIds = await consumeFiles(inputFileIds, processedFiles);
|
||||||
|
|
||||||
// Store operation data for undo (only store what we need to avoid memory bloat)
|
// Store operation data for undo (only store what we need to avoid memory bloat)
|
||||||
lastOperationRef.current = {
|
lastOperationRef.current = {
|
||||||
inputFiles: validFiles, // Keep original File objects for undo
|
inputFiles: validFiles, // Keep original File objects for undo
|
||||||
@ -326,17 +328,17 @@ export const useToolOperation = <TParams>(
|
|||||||
try {
|
try {
|
||||||
// Undo the consume operation
|
// Undo the consume operation
|
||||||
await undoConsumeFiles(inputFiles, inputFileRecords, outputFileIds);
|
await undoConsumeFiles(inputFiles, inputFileRecords, outputFileIds);
|
||||||
|
|
||||||
// Clear results and operation tracking
|
// Clear results and operation tracking
|
||||||
resetResults();
|
resetResults();
|
||||||
lastOperationRef.current = null;
|
lastOperationRef.current = null;
|
||||||
|
|
||||||
// Show success message
|
// Show success message
|
||||||
actions.setStatus(t('undoSuccess', 'Operation undone successfully'));
|
actions.setStatus(t('undoSuccess', 'Operation undone successfully'));
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
let errorMessage = extractErrorMessage(error);
|
let errorMessage = extractErrorMessage(error);
|
||||||
|
|
||||||
// Provide more specific error messages based on error type
|
// Provide more specific error messages based on error type
|
||||||
if (error.message?.includes('Mismatch between input files')) {
|
if (error.message?.includes('Mismatch between input files')) {
|
||||||
errorMessage = t('undoDataMismatch', 'Cannot undo: operation data is corrupted');
|
errorMessage = t('undoDataMismatch', 'Cannot undo: operation data is corrupted');
|
||||||
@ -345,9 +347,9 @@ export const useToolOperation = <TParams>(
|
|||||||
} else if (error.name === 'QuotaExceededError') {
|
} else if (error.name === 'QuotaExceededError') {
|
||||||
errorMessage = t('undoQuotaError', 'Cannot undo: insufficient storage space');
|
errorMessage = t('undoQuotaError', 'Cannot undo: insufficient storage space');
|
||||||
}
|
}
|
||||||
|
|
||||||
actions.setError(`${t('undoFailed', 'Failed to undo operation')}: ${errorMessage}`);
|
actions.setError(`${t('undoFailed', 'Failed to undo operation')}: ${errorMessage}`);
|
||||||
|
|
||||||
// Don't clear the operation data if undo failed - user might want to try again
|
// Don't clear the operation data if undo failed - user might want to try again
|
||||||
}
|
}
|
||||||
}, [undoConsumeFiles, resetResults, actions, t]);
|
}, [undoConsumeFiles, resetResults, actions, t]);
|
||||||
|
@ -182,7 +182,7 @@ export class EnhancedPDFProcessingService {
|
|||||||
): Promise<ProcessedFile> {
|
): Promise<ProcessedFile> {
|
||||||
const arrayBuffer = await file.arrayBuffer();
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
const pdf = await pdfWorkerManager.createDocument(arrayBuffer);
|
const pdf = await pdfWorkerManager.createDocument(arrayBuffer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const totalPages = pdf.numPages;
|
const totalPages = pdf.numPages;
|
||||||
|
|
||||||
@ -459,11 +459,12 @@ export class EnhancedPDFProcessingService {
|
|||||||
case 'failed':
|
case 'failed':
|
||||||
this.metrics.failedFiles++;
|
this.metrics.failedFiles++;
|
||||||
break;
|
break;
|
||||||
case 'cacheHit':
|
case 'cacheHit': {
|
||||||
// Update cache hit rate
|
// Update cache hit rate
|
||||||
const totalAttempts = this.metrics.totalFiles + 1;
|
const totalAttempts = this.metrics.totalFiles + 1;
|
||||||
this.metrics.cacheHitRate = (this.metrics.cacheHitRate * this.metrics.totalFiles + 1) / totalAttempts;
|
this.metrics.cacheHitRate = (this.metrics.cacheHitRate * this.metrics.totalFiles + 1) / totalAttempts;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,15 +148,17 @@ export class FileAnalyzer {
|
|||||||
case 'immediate_full':
|
case 'immediate_full':
|
||||||
return pageCount * baseTime;
|
return pageCount * baseTime;
|
||||||
|
|
||||||
case 'priority_pages':
|
case 'priority_pages': {
|
||||||
// Estimate time for priority pages (first 10)
|
// Estimate time for priority pages (first 10)
|
||||||
const priorityPages = Math.min(pageCount, 10);
|
const priorityPages = Math.min(pageCount, 10);
|
||||||
return priorityPages * baseTime;
|
return priorityPages * baseTime;
|
||||||
|
}
|
||||||
|
|
||||||
case 'progressive_chunked':
|
case 'progressive_chunked': {
|
||||||
// Estimate time for first chunk (20 pages)
|
// Estimate time for first chunk (20 pages)
|
||||||
const firstChunk = Math.min(pageCount, 20);
|
const firstChunk = Math.min(pageCount, 20);
|
||||||
return firstChunk * baseTime;
|
return firstChunk * baseTime;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return pageCount * baseTime;
|
return pageCount * baseTime;
|
||||||
|
@ -130,7 +130,7 @@ export class PDFExportService {
|
|||||||
newDoc.setModificationDate(new Date());
|
newDoc.setModificationDate(new Date());
|
||||||
|
|
||||||
const pdfBytes = await newDoc.save();
|
const pdfBytes = await newDoc.save();
|
||||||
return new Blob([pdfBytes], { type: 'application/pdf' });
|
return new Blob([pdfBytes as BlobPart], { type: 'application/pdf' });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -176,7 +176,7 @@ export class PDFExportService {
|
|||||||
newDoc.setModificationDate(new Date());
|
newDoc.setModificationDate(new Date());
|
||||||
|
|
||||||
const pdfBytes = await newDoc.save();
|
const pdfBytes = await newDoc.save();
|
||||||
return new Blob([pdfBytes], { type: 'application/pdf' });
|
return new Blob([pdfBytes as BlobPart], { type: 'application/pdf' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user