Compare commits

...

5 Commits

Author SHA1 Message Date
Connor Yoh
8fb5f54e9d Added space to file history header, removed allcaps 2025-09-15 16:32:36 +01:00
ConnorYoh
b5ce46cc7a
Merge branch 'V2' into feature/v2/filehistory 2025-09-15 15:01:07 +01:00
James Brunton
7dad484aa7
Improve type info on param hooks (#4438)
# Description of Changes
Changes it so that callers of `useBaseTool` know what actual type the
parameters hook that they passed in returned, so they can actually make
use of any extra methods that that params hook has.
2025-09-15 14:28:18 +01:00
Connor Yoh
d7f698ead7 File migration 2025-09-15 12:00:26 +01:00
Connor Yoh
bd6c9042d3 Remove unused metadata type 2025-09-15 11:35:34 +01:00
7 changed files with 124 additions and 92 deletions

View File

@ -2299,7 +2299,7 @@
"noFileSelected": "No files selected", "noFileSelected": "No files selected",
"showHistory": "Show History", "showHistory": "Show History",
"hideHistory": "Hide History", "hideHistory": "Hide History",
"fileHistory": "FileHistory", "fileHistory": "File History",
"loadingHistory": "Loading History...", "loadingHistory": "Loading History...",
"lastModified": "Last Modified", "lastModified": "Last Modified",
"toolChain": "Tools Applied", "toolChain": "Tools Applied",

View File

@ -38,7 +38,7 @@ const FileHistoryGroup: React.FC<FileHistoryGroupProps> = ({
<Collapse in={isExpanded}> <Collapse in={isExpanded}>
<Box ml="md" mt="xs" mb="sm"> <Box ml="md" mt="xs" mb="sm">
<Group align="center" mb="sm"> <Group align="center" mb="sm">
<Text size="xs" fw={600} c="dimmed" tt="uppercase"> <Text size="xs" fw={600} c="dimmed">
{t('fileManager.fileHistory', 'File History')} ({sortedHistory.length}) {t('fileManager.fileHistory', 'File History')} ({sortedHistory.length})
</Text> </Text>
</Group> </Group>

View File

@ -53,55 +53,6 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
</Group> </Group>
<Divider /> <Divider />
{/* Standard PDF Metadata */}
{currentFile?.pdfMetadata?.title && (
<>
<Group justify="space-between" py="xs">
<Text size="sm" c="dimmed">{t('fileManager.title', 'Title')}</Text>
<Text size="sm" fw={500} style={{ maxWidth: '60%', textAlign: 'right' }} truncate>
{currentFile.pdfMetadata.title}
</Text>
</Group>
<Divider />
</>
)}
{currentFile?.pdfMetadata?.author && (
<>
<Group justify="space-between" py="xs">
<Text size="sm" c="dimmed">{t('fileManager.author', 'Author')}</Text>
<Text size="sm" fw={500} style={{ maxWidth: '60%', textAlign: 'right' }} truncate>
{currentFile.pdfMetadata.author}
</Text>
</Group>
<Divider />
</>
)}
{currentFile?.pdfMetadata?.subject && (
<>
<Group justify="space-between" py="xs">
<Text size="sm" c="dimmed">{t('fileManager.subject', 'Subject')}</Text>
<Text size="sm" fw={500} style={{ maxWidth: '60%', textAlign: 'right' }} truncate>
{currentFile.pdfMetadata.subject}
</Text>
</Group>
<Divider />
</>
)}
{currentFile?.pdfMetadata?.creationDate && (
<>
<Group justify="space-between" py="xs">
<Text size="sm" c="dimmed">{t('fileManager.created', 'Created')}</Text>
<Text size="sm" fw={500}>
{new Date(currentFile.pdfMetadata.creationDate).toLocaleDateString()}
</Text>
</Group>
<Divider />
</>
)}
<Group justify="space-between" py="xs"> <Group justify="space-between" py="xs">
<Text size="sm" c="dimmed">{t('fileManager.lastModified', 'Last Modified')}</Text> <Text size="sm" c="dimmed">{t('fileManager.lastModified', 'Last Modified')}</Text>
<Text size="sm" fw={500}> <Text size="sm" fw={500}>

View File

@ -6,12 +6,12 @@ import { ToolOperationHook } from './useToolOperation';
import { BaseParametersHook } from './useBaseParameters'; import { BaseParametersHook } from './useBaseParameters';
import { StirlingFile } from '../../../types/fileContext'; import { StirlingFile } from '../../../types/fileContext';
interface BaseToolReturn<TParams> { interface BaseToolReturn<TParams, TParamsHook extends BaseParametersHook<TParams>> {
// File management // File management
selectedFiles: StirlingFile[]; selectedFiles: StirlingFile[];
// Tool-specific hooks // Tool-specific hooks
params: BaseParametersHook<TParams>; params: TParamsHook;
operation: ToolOperationHook<TParams>; operation: ToolOperationHook<TParams>;
// Endpoint validation // Endpoint validation
@ -33,13 +33,13 @@ interface BaseToolReturn<TParams> {
/** /**
* Base tool hook for tool components. Manages standard behaviour for tools. * Base tool hook for tool components. Manages standard behaviour for tools.
*/ */
export function useBaseTool<TParams>( export function useBaseTool<TParams, TParamsHook extends BaseParametersHook<TParams>>(
toolName: string, toolName: string,
useParams: () => BaseParametersHook<TParams>, useParams: () => TParamsHook,
useOperation: () => ToolOperationHook<TParams>, useOperation: () => ToolOperationHook<TParams>,
props: BaseToolProps, props: BaseToolProps,
options?: { minFiles?: number } options?: { minFiles?: number }
): BaseToolReturn<TParams> { ): BaseToolReturn<TParams, TParamsHook> {
const minFiles = options?.minFiles ?? 1; const minFiles = options?.minFiles ?? 1;
const { onPreviewFile, onComplete, onError } = props; const { onPreviewFile, onComplete, onError } = props;

View File

@ -87,45 +87,135 @@ class IndexedDBManager {
request.onupgradeneeded = (event) => { request.onupgradeneeded = (event) => {
const db = request.result; const db = request.result;
const oldVersion = event.oldVersion; const oldVersion = event.oldVersion;
const transaction = request.transaction;
console.log(`Upgrading ${config.name} from v${oldVersion} to v${config.version}`); console.log(`Upgrading ${config.name} from v${oldVersion} to v${config.version}`);
// Create or update object stores // Create or update object stores
config.stores.forEach(storeConfig => { config.stores.forEach(storeConfig => {
let store: IDBObjectStore | undefined;
if (db.objectStoreNames.contains(storeConfig.name)) { if (db.objectStoreNames.contains(storeConfig.name)) {
// Store exists - for now, just continue (could add migration logic here) // Store exists - get reference for migration
console.log(`Object store '${storeConfig.name}' already exists`); console.log(`Object store '${storeConfig.name}' already exists`);
return; store = transaction?.objectStore(storeConfig.name);
// Add new indexes if they don't exist
if (storeConfig.indexes && store) {
storeConfig.indexes.forEach(indexConfig => {
if (!store?.indexNames.contains(indexConfig.name)) {
store?.createIndex(
indexConfig.name,
indexConfig.keyPath,
{ unique: indexConfig.unique }
);
console.log(`Created index '${indexConfig.name}' on '${storeConfig.name}'`);
}
});
}
} else {
// Create new object store
const options: IDBObjectStoreParameters = {};
if (storeConfig.keyPath) {
options.keyPath = storeConfig.keyPath;
}
if (storeConfig.autoIncrement) {
options.autoIncrement = storeConfig.autoIncrement;
}
store = db.createObjectStore(storeConfig.name, options);
console.log(`Created object store '${storeConfig.name}'`);
// Create indexes
if (storeConfig.indexes) {
storeConfig.indexes.forEach(indexConfig => {
store?.createIndex(
indexConfig.name,
indexConfig.keyPath,
{ unique: indexConfig.unique }
);
console.log(`Created index '${indexConfig.name}' on '${storeConfig.name}'`);
});
}
} }
// Create new object store // Perform data migration for files database
const options: IDBObjectStoreParameters = {}; if (config.name === 'stirling-pdf-files' && storeConfig.name === 'files' && store) {
if (storeConfig.keyPath) { this.migrateFileHistoryFields(store, oldVersion);
options.keyPath = storeConfig.keyPath;
}
if (storeConfig.autoIncrement) {
options.autoIncrement = storeConfig.autoIncrement;
}
const store = db.createObjectStore(storeConfig.name, options);
console.log(`Created object store '${storeConfig.name}'`);
// Create indexes
if (storeConfig.indexes) {
storeConfig.indexes.forEach(indexConfig => {
store.createIndex(
indexConfig.name,
indexConfig.keyPath,
{ unique: indexConfig.unique }
);
console.log(`Created index '${indexConfig.name}' on '${storeConfig.name}'`);
});
} }
}); });
}; };
}); });
} }
/**
* Migrate existing file records to include new file history fields
*/
private migrateFileHistoryFields(store: IDBObjectStore, oldVersion: number): void {
// Only migrate if upgrading from a version before file history was added (version < 3)
if (oldVersion >= 3) {
return;
}
console.log('Starting file history migration for existing records...');
const cursor = store.openCursor();
let migratedCount = 0;
cursor.onsuccess = (event) => {
const cursor = (event.target as IDBRequest).result;
if (cursor) {
const record = cursor.value;
let needsUpdate = false;
// Add missing file history fields with sensible defaults
if (record.isLeaf === undefined) {
record.isLeaf = true; // Existing files are unprocessed, should appear in recent files
needsUpdate = true;
}
if (record.versionNumber === undefined) {
record.versionNumber = 1; // Existing files are first version
needsUpdate = true;
}
if (record.originalFileId === undefined) {
record.originalFileId = record.id; // Existing files are their own root
needsUpdate = true;
}
if (record.parentFileId === undefined) {
record.parentFileId = undefined; // No parent for existing files
needsUpdate = true;
}
if (record.toolHistory === undefined) {
record.toolHistory = []; // No history for existing files
needsUpdate = true;
}
// Update the record if any fields were missing
if (needsUpdate) {
try {
cursor.update(record);
migratedCount++;
} catch (error) {
console.error('Failed to migrate record:', record.id, error);
}
}
cursor.continue();
} else {
// Migration complete
console.log(`File history migration completed. Migrated ${migratedCount} records.`);
}
};
cursor.onerror = (event) => {
console.error('File history migration failed:', (event.target as IDBRequest).error);
};
}
/** /**
* Get database connection (must be already opened) * Get database connection (must be already opened)
*/ */

View File

@ -28,8 +28,8 @@ export interface BaseFileMetadata {
createdAt?: number; // When file was added to system createdAt?: number; // When file was added to system
// File history tracking // File history tracking
isLeaf?: boolean; // True if this file hasn't been processed yet isLeaf: boolean; // True if this file hasn't been processed yet
originalFileId?: string; // Root file ID for grouping versions originalFileId: string; // Root file ID for grouping versions
versionNumber: number; // Version number in chain versionNumber: number; // Version number in chain
parentFileId?: FileId; // Immediate parent file ID parentFileId?: FileId; // Immediate parent file ID
toolHistory?: Array<{ toolHistory?: Array<{
@ -37,14 +37,4 @@ export interface BaseFileMetadata {
timestamp: number; timestamp: number;
}>; // Tool chain for history tracking }>; // Tool chain for history tracking
// Standard PDF document metadata
pdfMetadata?: {
title?: string;
author?: string;
subject?: string;
creator?: string;
producer?: string;
creationDate?: Date;
modificationDate?: Date;
};
} }

View File

@ -169,6 +169,7 @@ export function createNewStirlingFileStub(
size: file.size, size: file.size,
type: file.type, type: file.type,
lastModified: file.lastModified, lastModified: file.lastModified,
originalFileId: fileId,
quickKey: createQuickKey(file), quickKey: createQuickKey(file),
createdAt: Date.now(), createdAt: Date.now(),
isLeaf: true, // New files are leaf nodes by default isLeaf: true, // New files are leaf nodes by default