Merge branch 'V2' of https://github.com/Stirling-Tools/Stirling-PDF into feature/v2/pagemanager-improvements

This commit is contained in:
Reece Browne
2025-08-25 17:30:37 +01:00
123 changed files with 6996 additions and 1248 deletions

View File

@@ -0,0 +1,183 @@
/**
* Service for managing automation configurations in IndexedDB
*/
export interface AutomationConfig {
id: string;
name: string;
description?: string;
operations: Array<{
operation: string;
parameters: any;
}>;
createdAt: string;
updatedAt: string;
}
class AutomationStorage {
private dbName = 'StirlingPDF_Automations';
private dbVersion = 1;
private storeName = 'automations';
private db: IDBDatabase | null = null;
async init(): Promise<void> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.dbVersion);
request.onerror = () => {
reject(new Error('Failed to open automation storage database'));
};
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains(this.storeName)) {
const store = db.createObjectStore(this.storeName, { keyPath: 'id' });
store.createIndex('name', 'name', { unique: false });
store.createIndex('createdAt', 'createdAt', { unique: false });
}
};
});
}
async ensureDB(): Promise<IDBDatabase> {
if (!this.db) {
await this.init();
}
if (!this.db) {
throw new Error('Database not initialized');
}
return this.db;
}
async saveAutomation(automation: Omit<AutomationConfig, 'id' | 'createdAt' | 'updatedAt'>): Promise<AutomationConfig> {
const db = await this.ensureDB();
const timestamp = new Date().toISOString();
const automationWithMeta: AutomationConfig = {
id: `automation-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
...automation,
createdAt: timestamp,
updatedAt: timestamp
};
return new Promise((resolve, reject) => {
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.add(automationWithMeta);
request.onsuccess = () => {
resolve(automationWithMeta);
};
request.onerror = () => {
reject(new Error('Failed to save automation'));
};
});
}
async updateAutomation(automation: AutomationConfig): Promise<AutomationConfig> {
const db = await this.ensureDB();
const updatedAutomation: AutomationConfig = {
...automation,
updatedAt: new Date().toISOString()
};
return new Promise((resolve, reject) => {
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.put(updatedAutomation);
request.onsuccess = () => {
resolve(updatedAutomation);
};
request.onerror = () => {
reject(new Error('Failed to update automation'));
};
});
}
async getAutomation(id: string): Promise<AutomationConfig | null> {
const db = await this.ensureDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.get(id);
request.onsuccess = () => {
resolve(request.result || null);
};
request.onerror = () => {
reject(new Error('Failed to get automation'));
};
});
}
async getAllAutomations(): Promise<AutomationConfig[]> {
const db = await this.ensureDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.getAll();
request.onsuccess = () => {
const automations = request.result || [];
// Sort by creation date, newest first
automations.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
resolve(automations);
};
request.onerror = () => {
reject(new Error('Failed to get automations'));
};
});
}
async deleteAutomation(id: string): Promise<void> {
const db = await this.ensureDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.delete(id);
request.onsuccess = () => {
resolve();
};
request.onerror = () => {
reject(new Error('Failed to delete automation'));
};
});
}
async searchAutomations(query: string): Promise<AutomationConfig[]> {
const automations = await this.getAllAutomations();
if (!query.trim()) {
return automations;
}
const lowerQuery = query.toLowerCase();
return automations.filter(automation =>
automation.name.toLowerCase().includes(lowerQuery) ||
(automation.description && automation.description.toLowerCase().includes(lowerQuery)) ||
automation.operations.some(op => op.operation.toLowerCase().includes(lowerQuery))
);
}
}
// Export singleton instance
export const automationStorage = new AutomationStorage();

View File

@@ -32,7 +32,7 @@ export class PDFExportService {
const sourceDoc = await PDFLibDocument.load(originalPDFBytes);
const blob = await this.createSingleDocument(sourceDoc, pagesToExport);
const exportFilename = this.generateFilename(filename || pdfDocument.name, selectedOnly);
return { blob, filename: exportFilename };
} catch (error) {
console.error('PDF export error:', error);
@@ -63,7 +63,7 @@ export class PDFExportService {
const blob = await this.createMultiSourceDocument(sourceFiles, pagesToExport);
const exportFilename = this.generateFilename(filename || pdfDocument.name, selectedOnly);
return { blob, filename: exportFilename };
} catch (error) {
console.error('Multi-file PDF export error:', error);
@@ -79,10 +79,10 @@ export class PDFExportService {
pages: PDFPage[]
): Promise<Blob> {
const newDoc = await PDFLibDocument.create();
// Load all source documents once and cache them
const loadedDocs = new Map<string, PDFLibDocument>();
for (const [fileId, file] of sourceFiles) {
try {
const arrayBuffer = await file.arrayBuffer();
@@ -97,7 +97,7 @@ export class PDFExportService {
if (page.isBlankPage || page.originalPageNumber === -1) {
// Create a blank page
const blankPage = newDoc.addPage(PageSizes.A4);
// Apply rotation if needed
if (page.rotation !== 0) {
blankPage.setRotation(degrees(page.rotation));
@@ -146,7 +146,7 @@ export class PDFExportService {
if (page.isBlankPage || page.originalPageNumber === -1) {
// Create a blank page
const blankPage = newDoc.addPage(PageSizes.A4);
// Apply rotation if needed
if (page.rotation !== 0) {
blankPage.setRotation(degrees(page.rotation));
@@ -183,7 +183,7 @@ export class PDFExportService {
/**
* Generate appropriate filename for export
*/
private generateFilename(originalName: string, selectedOnly: boolean): string {
private generateFilename(originalName: string, selectedOnly: boolean, appendSuffix: boolean): string {
const baseName = originalName.replace(/\.pdf$/i, '');
return `${baseName}.pdf`;
}