mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-12-18 20:04:17 +01:00
fix selection bug
This commit is contained in:
parent
6ee32bf26a
commit
14ff8144bf
@ -1,7 +1,9 @@
|
||||
import React, { createContext, useContext, useState, useCallback, useMemo } from 'react';
|
||||
import { useFileHandler } from '@app/hooks/useFileHandler';
|
||||
import { useFileActions } from '@app/contexts/FileContext';
|
||||
import { useFileContext } from '@app/contexts/file/fileHooks';
|
||||
import { StirlingFileStub } from '@app/types/fileContext';
|
||||
import type { FileId } from '@app/types/file';
|
||||
import { fileStorage } from '@app/services/fileStorage';
|
||||
|
||||
interface FilesModalContextType {
|
||||
@ -19,6 +21,7 @@ const FilesModalContext = createContext<FilesModalContextType | null>(null);
|
||||
export const FilesModalProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { addFiles } = useFileHandler();
|
||||
const { actions } = useFileActions();
|
||||
const fileCtx = useFileContext();
|
||||
const [isFilesModalOpen, setIsFilesModalOpen] = useState(false);
|
||||
const [onModalClose, setOnModalClose] = useState<(() => void) | undefined>();
|
||||
const [insertAfterPage, setInsertAfterPage] = useState<number | undefined>();
|
||||
@ -37,16 +40,25 @@ export const FilesModalProvider: React.FC<{ children: React.ReactNode }> = ({ ch
|
||||
onModalClose?.();
|
||||
}, [onModalClose]);
|
||||
|
||||
const handleFileUpload = useCallback((files: File[]) => {
|
||||
const handleFileUpload = useCallback(async (files: File[]) => {
|
||||
if (customHandler) {
|
||||
// Use custom handler for special cases (like page insertion)
|
||||
customHandler(files, insertAfterPage);
|
||||
} else {
|
||||
// Use normal file handling
|
||||
addFiles(files);
|
||||
// 1) Add via standard flow (auto-selects new files)
|
||||
await addFiles(files);
|
||||
// 2) Merge all requested file IDs (covers already-present files too)
|
||||
const ids = files
|
||||
.map((f) => fileCtx.findFileId(f) as FileId | undefined)
|
||||
.filter((id): id is FileId => Boolean(id));
|
||||
if (ids.length > 0) {
|
||||
const currentSelected = fileCtx.selectors.getSelectedStirlingFileStubs().map((s) => s.id);
|
||||
const nextSelection = Array.from(new Set([...currentSelected, ...ids]));
|
||||
actions.setSelectedFiles(nextSelection);
|
||||
}
|
||||
}
|
||||
closeFilesModal();
|
||||
}, [addFiles, closeFilesModal, insertAfterPage, customHandler]);
|
||||
}, [addFiles, closeFilesModal, insertAfterPage, customHandler, actions, fileCtx]);
|
||||
|
||||
const handleRecentFileSelect = useCallback(async (stirlingFileStubs: StirlingFileStub[]) => {
|
||||
if (customHandler) {
|
||||
@ -67,15 +79,22 @@ export const FilesModalProvider: React.FC<{ children: React.ReactNode }> = ({ ch
|
||||
console.error('Failed to load files for custom handler:', error);
|
||||
}
|
||||
} else {
|
||||
// Normal case - use addStirlingFileStubs to preserve metadata
|
||||
// Normal case - use addStirlingFileStubs to preserve metadata (auto-selects new)
|
||||
if (actions.addStirlingFileStubs) {
|
||||
actions.addStirlingFileStubs(stirlingFileStubs, { selectFiles: true });
|
||||
await actions.addStirlingFileStubs(stirlingFileStubs, { selectFiles: true });
|
||||
// Merge all requested IDs into selection (covers files that already existed)
|
||||
const requestedIds = stirlingFileStubs.map((s) => s.id);
|
||||
if (requestedIds.length > 0) {
|
||||
const currentSelected = fileCtx.selectors.getSelectedStirlingFileStubs().map((s) => s.id);
|
||||
const nextSelection = Array.from(new Set([...currentSelected, ...requestedIds]));
|
||||
actions.setSelectedFiles(nextSelection);
|
||||
}
|
||||
} else {
|
||||
console.error('addStirlingFileStubs action not available');
|
||||
}
|
||||
}
|
||||
closeFilesModal();
|
||||
}, [actions.addStirlingFileStubs, closeFilesModal, customHandler, insertAfterPage]);
|
||||
}, [actions.addStirlingFileStubs, closeFilesModal, customHandler, insertAfterPage, actions, fileCtx]);
|
||||
|
||||
const setModalCloseCallback = useCallback((callback: () => void) => {
|
||||
setOnModalClose(() => callback);
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useFileActions } from '@app/contexts/FileContext';
|
||||
import type { StirlingFile } from '@app/types/fileContext';
|
||||
|
||||
export const useFileHandler = () => {
|
||||
const { actions } = useFileActions();
|
||||
|
||||
const addFiles = useCallback(async (files: File[], options: { insertAfterPageId?: string; selectFiles?: boolean } = {}) => {
|
||||
const addFiles = useCallback(async (files: File[], options: { insertAfterPageId?: string; selectFiles?: boolean } = {}): Promise<StirlingFile[]> => {
|
||||
// Merge default options with passed options - passed options take precedence
|
||||
const mergedOptions = { selectFiles: true, ...options };
|
||||
// Let FileContext handle deduplication with quickKey logic
|
||||
await actions.addFiles(files, mergedOptions);
|
||||
const result = await actions.addFiles(files, mergedOptions);
|
||||
return result;
|
||||
}, [actions.addFiles]);
|
||||
|
||||
return {
|
||||
|
||||
@ -18,7 +18,7 @@ import {
|
||||
import CompareWorkbenchView from '@app/components/tools/compare/CompareWorkbenchView';
|
||||
import { useToolWorkflow } from '@app/contexts/ToolWorkflowContext';
|
||||
import { useNavigationActions } from '@app/contexts/NavigationContext';
|
||||
import { useFileContext } from '@app/contexts/file/fileHooks';
|
||||
import { useFileContext, useFileState } from '@app/contexts/file/fileHooks';
|
||||
import type { FileId } from '@app/types/file';
|
||||
import type { StirlingFile } from '@app/types/fileContext';
|
||||
import DocumentThumbnail from '@app/components/shared/filePreview/DocumentThumbnail';
|
||||
@ -26,8 +26,6 @@ import type { CompareWorkbenchData } from '@app/types/compare';
|
||||
import FitText from '@app/components/shared/FitText';
|
||||
import { getDefaultWorkbench } from '@app/types/workbench';
|
||||
import { useFilesModalContext } from '@app/contexts/FilesModalContext';
|
||||
import { createQuickKey } from '@app/types/fileContext';
|
||||
import { useFileHandler } from '@app/hooks/useFileHandler';
|
||||
|
||||
const CUSTOM_VIEW_ID = 'compareWorkbenchView';
|
||||
const CUSTOM_WORKBENCH_ID = 'custom:compareWorkbenchView' as const;
|
||||
@ -42,8 +40,8 @@ const Compare = (props: BaseToolProps) => {
|
||||
clearCustomWorkbenchViewData,
|
||||
} = useToolWorkflow();
|
||||
const { selectors, actions: fileActions } = useFileContext();
|
||||
const { state: fileState } = useFileState();
|
||||
const { openFilesModal } = useFilesModalContext();
|
||||
const { addFiles } = useFileHandler();
|
||||
|
||||
const base = useBaseTool(
|
||||
'compare',
|
||||
@ -98,7 +96,8 @@ const Compare = (props: BaseToolProps) => {
|
||||
// Auto-map from workbench selection: always reflect the first two selected files in order.
|
||||
// This also handles deselection by promoting the remaining selection to base and clearing comparison.
|
||||
useEffect(() => {
|
||||
const selectedIds = base.selectedFiles.map(f => f.fileId as FileId);
|
||||
// Use selected IDs directly from state so it works even if File objects aren't loaded yet
|
||||
const selectedIds = (fileState.ui.selectedFileIds as FileId[]) ?? [];
|
||||
|
||||
// Determine next base: keep current if still selected; otherwise use the first selected id
|
||||
const nextBase: FileId | null = params.baseFileId && selectedIds.includes(params.baseFileId)
|
||||
@ -120,7 +119,7 @@ const Compare = (props: BaseToolProps) => {
|
||||
comparisonFileId: nextComp,
|
||||
}));
|
||||
}
|
||||
}, [base.selectedFiles, base.params, params.baseFileId, params.comparisonFileId]);
|
||||
}, [fileState.ui.selectedFileIds, base.params, params.baseFileId, params.comparisonFileId]);
|
||||
|
||||
// Track workbench data and drive loading/result state transitions
|
||||
const lastProcessedAtRef = useRef<number | null>(null);
|
||||
@ -310,48 +309,7 @@ const Compare = (props: BaseToolProps) => {
|
||||
}
|
||||
}, [base.params, operation.result, params.baseFileId, params.comparisonFileId, runCompareWithIds]);
|
||||
|
||||
// Custom file handler that selects existing files instead of adding duplicates
|
||||
const handleFileSelection = useCallback(async (files: File[]) => {
|
||||
const allStubs = selectors.getStirlingFileStubs();
|
||||
const existingFilesByQuickKey = new Map<string, FileId>();
|
||||
|
||||
// Build a map of quickKey -> fileId for all existing files
|
||||
allStubs.forEach(stub => {
|
||||
if (stub.quickKey) {
|
||||
existingFilesByQuickKey.set(stub.quickKey, stub.id);
|
||||
}
|
||||
});
|
||||
|
||||
const filesToAdd: File[] = [];
|
||||
const existingFileIds: FileId[] = [];
|
||||
|
||||
// Check each file to see if it already exists
|
||||
for (const file of files) {
|
||||
const quickKey = createQuickKey(file);
|
||||
const existingFileId = existingFilesByQuickKey.get(quickKey);
|
||||
|
||||
if (existingFileId) {
|
||||
// File exists, just select it
|
||||
existingFileIds.push(existingFileId);
|
||||
} else {
|
||||
// File doesn't exist, add it
|
||||
filesToAdd.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
// Add new files (they will be auto-selected by default)
|
||||
if (filesToAdd.length > 0) {
|
||||
await addFiles(filesToAdd, { selectFiles: true });
|
||||
}
|
||||
|
||||
// If we found existing files, merge them with current selection
|
||||
if (existingFileIds.length > 0) {
|
||||
// After adding new files, get the updated selection and merge with existing file IDs
|
||||
const updatedSelection = selectors.getSelectedStirlingFileStubs().map(s => s.id);
|
||||
const allToSelect = [...new Set([...updatedSelection, ...existingFileIds])];
|
||||
fileActions.setSelectedFiles(allToSelect);
|
||||
}
|
||||
}, [addFiles, fileActions, selectors]);
|
||||
// No custom handler; rely on global add flow which auto-selects added files
|
||||
|
||||
const handleSwap = useCallback(() => {
|
||||
const baseId = params.baseFileId as FileId | null;
|
||||
@ -389,7 +347,7 @@ const Compare = (props: BaseToolProps) => {
|
||||
justifyContent: 'space-between',
|
||||
cursor: shouldShowAddButton ? 'pointer' : 'default',
|
||||
}}
|
||||
onClick={shouldShowAddButton ? () => openFilesModal({ customHandler: handleFileSelection }) : undefined}
|
||||
onClick={shouldShowAddButton ? () => openFilesModal({}) : undefined}
|
||||
>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t(
|
||||
@ -404,7 +362,7 @@ const Compare = (props: BaseToolProps) => {
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openFilesModal({ customHandler: handleFileSelection });
|
||||
openFilesModal({});
|
||||
}}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
@ -462,11 +420,19 @@ const Compare = (props: BaseToolProps) => {
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
[params.baseFileId, params.comparisonFileId, selectors, t, openFilesModal, handleFileSelection]
|
||||
[params.baseFileId, params.comparisonFileId, selectors, t, openFilesModal]
|
||||
);
|
||||
|
||||
const baseStub = params.baseFileId ? selectors.getStirlingFileStub(params.baseFileId) : undefined;
|
||||
const compStub = params.comparisonFileId ? selectors.getStirlingFileStub(params.comparisonFileId) : undefined;
|
||||
const canExecute = Boolean(
|
||||
params.baseFileId && params.comparisonFileId && params.baseFileId !== params.comparisonFileId && !base.operation.isLoading && base.endpointEnabled !== false
|
||||
params.baseFileId &&
|
||||
params.comparisonFileId &&
|
||||
params.baseFileId !== params.comparisonFileId &&
|
||||
baseStub &&
|
||||
compStub &&
|
||||
!base.operation.isLoading &&
|
||||
base.endpointEnabled !== false
|
||||
);
|
||||
|
||||
const hasBothSelected = Boolean(params.baseFileId && params.comparisonFileId);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user