Fix/redact bug (#6048)

This commit is contained in:
Reece Browne
2026-04-02 17:39:45 +01:00
committed by GitHub
parent de9625942b
commit 0adcbeedf1
10 changed files with 180 additions and 142 deletions

View File

@@ -1,5 +1,6 @@
import { ReactNode } from 'react';
import { useBanner } from '@app/contexts/BannerContext';
import NavigationWarningModal from '@app/components/shared/NavigationWarningModal';
interface AppLayoutProps {
children: ReactNode;
@@ -26,6 +27,7 @@ export function AppLayout({ children }: AppLayoutProps) {
{children}
</div>
</div>
<NavigationWarningModal />
</>
);
}

View File

@@ -9,7 +9,6 @@ import '@app/components/pageEditor/PageEditor.module.css';
import PageThumbnail from '@app/components/pageEditor/PageThumbnail';
import DragDropGrid from '@app/components/pageEditor/DragDropGrid';
import SkeletonLoader from '@app/components/shared/SkeletonLoader';
import NavigationWarningModal from '@app/components/shared/NavigationWarningModal';
import { FileId } from "@app/types/file";
import { GRID_CONSTANTS } from '@app/components/pageEditor/constants';
import { useInitialPageDocument } from '@app/components/pageEditor/hooks/useInitialPageDocument';
@@ -39,7 +38,7 @@ const PageEditor = ({
const { actions } = useFileActions();
// Navigation guard for unsaved changes
const { setHasUnsavedChanges } = useNavigationGuard();
const { setHasUnsavedChanges, registerNavigationWarningHandlers, unregisterNavigationWarningHandlers } = useNavigationGuard();
const navigationState = useNavigationState();
// Get PageEditor coordination functions
@@ -393,6 +392,19 @@ const PageEditor = ({
updateCurrentPages,
});
// Register navigation warning handlers for the global modal
useEffect(() => {
registerNavigationWarningHandlers({
onApplyAndContinue: async () => {
await applyChanges();
},
onExportAndContinue: async () => {
await onExportAll();
},
});
return () => unregisterNavigationWarningHandlers();
}, [applyChanges, onExportAll, registerNavigationWarningHandlers, unregisterNavigationWarningHandlers]);
// Derived values for right rail and usePageEditorRightRailButtons (must be after displayDocument)
const selectedPageCount = selectedPageIds.length;
const activeFileIds = selectedFileIds;
@@ -704,14 +716,6 @@ const PageEditor = ({
</Box>
)}
<NavigationWarningModal
onApplyAndContinue={async () => {
await applyChanges();
}}
onExportAndContinue={async () => {
await onExportAll();
}}
/>
</div>
);
};

View File

@@ -1,3 +1,4 @@
import { useRef, useEffect } from "react";
import { Modal, Text, Button, Group, Stack } from "@mantine/core";
import { useNavigationGuard } from "@app/contexts/NavigationContext";
import { useTranslation } from "react-i18next";
@@ -6,51 +7,69 @@ import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
import { Z_INDEX_TOAST } from "@app/styles/zIndex";
interface NavigationWarningModalProps {
onApplyAndContinue?: () => Promise<void>;
onExportAndContinue?: () => Promise<void>;
/** Called when discarding - allows saving applied changes while discarding pending ones */
onDiscardAndContinue?: () => Promise<void>;
}
const NavigationWarningModal = ({ onApplyAndContinue, onExportAndContinue, onDiscardAndContinue }: NavigationWarningModalProps) => {
const NavigationWarningModal = () => {
const { t } = useTranslation();
const { showNavigationWarning, hasUnsavedChanges, pendingNavigation, cancelNavigation, confirmNavigation, setHasUnsavedChanges } =
useNavigationGuard();
const {
showNavigationWarning,
hasUnsavedChanges,
pendingNavigation,
cancelNavigation,
setHasUnsavedChanges,
navigationWarningHandlersRef,
} = useNavigationGuard();
// Store pendingNavigation in a ref so async handlers always have the latest,
// not a stale closure captured before an await.
const pendingNavigationRef = useRef(pendingNavigation);
useEffect(() => {
pendingNavigationRef.current = pendingNavigation;
}, [pendingNavigation]);
const handleKeepWorking = () => {
cancelNavigation();
};
const handleDiscardChanges = async () => {
// If a discard handler is provided, call it to save any already-applied changes, then discard the unsaved changes
if (onDiscardAndContinue) {
await onDiscardAndContinue();
}
const finishAndNavigate = () => {
const nav = pendingNavigationRef.current;
setHasUnsavedChanges(false);
confirmNavigation();
cancelNavigation();
if (nav) {
nav();
}
};
const handleDiscardChanges = async () => {
const handlers = navigationWarningHandlersRef.current;
if (handlers?.onDiscardAndContinue) {
await handlers.onDiscardAndContinue();
}
finishAndNavigate();
};
const handleApplyAndContinue = async () => {
if (onApplyAndContinue) {
await onApplyAndContinue();
const handlers = navigationWarningHandlersRef.current;
if (handlers?.onApplyAndContinue) {
await handlers.onApplyAndContinue();
}
setHasUnsavedChanges(false);
confirmNavigation();
finishAndNavigate();
};
const handleExportAndContinue = async () => {
if (onExportAndContinue) {
await onExportAndContinue();
const handlers = navigationWarningHandlersRef.current;
if (handlers?.onExportAndContinue) {
await handlers.onExportAndContinue();
}
setHasUnsavedChanges(false);
confirmNavigation();
finishAndNavigate();
};
// Read handler availability at render time for button visibility
const handlers = navigationWarningHandlersRef.current;
const hasApply = !!handlers?.onApplyAndContinue;
const hasExport = !!handlers?.onExportAndContinue;
const BUTTON_WIDTH = "12rem";
// Only show modal if there are unsaved changes AND there's an actual pending navigation
// This prevents the modal from showing due to spurious state updates
if (!hasUnsavedChanges || !pendingNavigation) {
return null;
}
@@ -87,12 +106,12 @@ const NavigationWarningModal = ({ onApplyAndContinue, onExportAndContinue, onDis
<Button variant="filled" color="var(--mantine-color-red-9)" onClick={handleDiscardChanges} w={BUTTON_WIDTH} leftSection={<DeleteOutlineIcon fontSize="small" />}>
{t("discardChanges", "Discard Changes")}
</Button>
{onApplyAndContinue && (
{hasApply && (
<Button variant="filled" onClick={handleApplyAndContinue} w={BUTTON_WIDTH} leftSection={<CheckCircleOutlineIcon fontSize="small" />}>
{t("applyAndContinue", "Apply & Leave")}
</Button>
)}
{onExportAndContinue && (
{hasExport && (
<Button variant="filled" onClick={handleExportAndContinue} w={BUTTON_WIDTH} leftSection={<CheckCircleOutlineIcon fontSize="small" />}>
{t("exportAndContinue", "Export & Leave")}
</Button>
@@ -108,12 +127,12 @@ const NavigationWarningModal = ({ onApplyAndContinue, onExportAndContinue, onDis
<Button variant="filled" color="var(--mantine-color-red-9)" onClick={handleDiscardChanges} w={BUTTON_WIDTH} leftSection={<DeleteOutlineIcon fontSize="small" />}>
{t("discardChanges", "Discard Changes")}
</Button>
{onApplyAndContinue && (
{hasApply && (
<Button variant="filled" onClick={handleApplyAndContinue} w={BUTTON_WIDTH} leftSection={<CheckCircleOutlineIcon fontSize="small" />}>
{t("applyAndContinue", "Apply & Leave")}
</Button>
)}
{onExportAndContinue && (
{hasExport && (
<Button variant="filled" onClick={handleExportAndContinue} w={BUTTON_WIDTH} leftSection={<CheckCircleOutlineIcon fontSize="small" />}>
{t("exportAndContinue", "Export & Leave")}
</Button>

View File

@@ -28,7 +28,7 @@ import CallSplitIcon from '@mui/icons-material/CallSplit';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import UploadFileIcon from '@mui/icons-material/UploadFileOutlined';
import { Rnd } from 'react-rnd';
import NavigationWarningModal from '@app/components/shared/NavigationWarningModal';
import { useNavigationGuard } from '@app/contexts/NavigationContext';
import { useFileContext } from '@app/contexts/FileContext';
import {
@@ -415,6 +415,15 @@ const PdfTextEditorView = ({ data }: PdfTextEditorViewProps) => {
} : null,
});
// Register navigation warning handlers for the global modal
const { registerNavigationWarningHandlers, unregisterNavigationWarningHandlers } = useNavigationGuard();
useEffect(() => {
registerNavigationWarningHandlers({
onApplyAndContinue: onSaveToWorkbench,
});
return () => unregisterNavigationWarningHandlers();
}, [onSaveToWorkbench, registerNavigationWarningHandlers, unregisterNavigationWarningHandlers]);
const clearSelection = useCallback(() => {
setSelectedGroupIds(new Set());
lastSelectedGroupIdRef.current = null;
@@ -2385,10 +2394,6 @@ const selectionToolbarPosition = useMemo(() => {
</Stack>
)}
{/* Navigation Warning Modal */}
<NavigationWarningModal
onApplyAndContinue={onSaveToWorkbench}
/>
</Stack>
);
};

View File

@@ -1,10 +1,10 @@
import { useTranslation } from 'react-i18next';
import { useEffect, useRef, useCallback } from 'react';
import { Button, Stack, Text, Divider, ColorInput } from '@mantine/core';
import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
import { useRedaction, useRedactionMode } from '@app/contexts/RedactionContext';
import { useViewer } from '@app/contexts/ViewerContext';
import { useSignature } from '@app/contexts/SignatureContext';
import { useNavigationGuard } from '@app/contexts/NavigationContext';
interface ManualRedactionControlsProps {
disabled?: boolean;
@@ -27,40 +27,45 @@ export default function ManualRedactionControls({ disabled = false }: ManualReda
// Get signature context to deactivate annotation tools when switching to redaction
const { signatureApiRef } = useSignature();
// Check if redaction mode is active
const isRedactActive = isRedacting;
// Track if we've auto-activated for the current bridge session
const hasAutoActivated = useRef(false);
// Check if user is navigating away (modal shown) — don't fight the save/leave process
const { showNavigationWarning } = useNavigationGuard();
// Track the previous file index to detect file switches
const prevFileIndexRef = useRef<number>(activeFileIndex);
// Auto-activate selection mode when the API bridge becomes ready
// This ensures Mark Text is pre-selected when entering manual redaction mode
// Guard: pause auto-reactivation during save/export to avoid interfering with EmbedPDF
const isSavingRef = useRef(false);
// Keep redaction tool active at all times while this component is mounted.
// If anything deactivates it (annotation tools, text selection, file switch, etc.)
// this re-enables it automatically — no manual "Activate" button needed.
useEffect(() => {
if (isBridgeReady && !disabled && !hasAutoActivated.current) {
hasAutoActivated.current = true;
// Small delay to ensure EmbedPDF is fully ready
const timer = setTimeout(() => {
// Deactivate annotation mode to show redaction layer
if (disabled || !isBridgeReady || isSavingRef.current || showNavigationWarning) return;
if (!isRedacting || isAnnotationMode) {
// Kill annotation mode if it stole focus
if (isAnnotationMode) {
setAnnotationMode(false);
// Pre-select the Redaction tool
activateManualRedact();
}, 150);
if (signatureApiRef?.current) {
try {
signatureApiRef.current.deactivateTools();
} catch (error) {
console.log('Unable to deactivate annotation tools:', error);
}
}
}
// Small delay to avoid racing with EmbedPDF's own state updates
const timer = setTimeout(() => {
if (!isSavingRef.current) {
activateManualRedact();
}
}, 50);
return () => clearTimeout(timer);
}
}, [isBridgeReady, disabled, activateManualRedact, setAnnotationMode]);
// Reset auto-activation flag when disabled changes or bridge becomes not ready
useEffect(() => {
if (disabled || !isBridgeReady) {
hasAutoActivated.current = false;
}
}, [disabled, isBridgeReady]);
}, [isRedacting, isAnnotationMode, disabled, isBridgeReady, showNavigationWarning, setAnnotationMode, signatureApiRef, activateManualRedact]);
// Reset redaction tool when switching between files
// The new PDF gets a fresh EmbedPDF instance - forcing user to re-select tool ensures it works properly
// The new PDF gets a fresh EmbedPDF instance
useEffect(() => {
if (prevFileIndexRef.current !== activeFileIndex) {
prevFileIndexRef.current = activeFileIndex;
@@ -69,41 +74,24 @@ export default function ManualRedactionControls({ disabled = false }: ManualReda
if (activeType) {
setActiveType(null);
}
// Reset auto-activation flag so new file can auto-activate
hasAutoActivated.current = false;
}
}, [activeFileIndex, activeType, setActiveType]);
const handleRedactClick = () => {
// Deactivate annotation mode and tools to switch to redaction layer
if (isAnnotationMode) {
setAnnotationMode(false);
// Deactivate any active annotation tools (like draw)
if (signatureApiRef?.current) {
try {
signatureApiRef.current.deactivateTools();
} catch (error) {
console.log('Unable to deactivate annotation tools:', error);
}
}
}
activateManualRedact();
};
// Handle saving changes - this will apply pending redactions and save to file
const handleSaveChanges = useCallback(async () => {
if (applyChanges) {
await applyChanges();
isSavingRef.current = true;
try {
await applyChanges();
} finally {
isSavingRef.current = false;
}
}
}, [applyChanges]);
// Check if there are unsaved changes to save (pending redactions OR applied redactions)
// Save Changes button will apply pending redactions and then save everything
const hasUnsavedChanges = pendingCount > 0 || redactionsApplied;
// Check if API is available - use isBridgeReady state instead of ref (refs don't trigger re-renders)
const isApiReady = isBridgeReady;
return (
@@ -128,18 +116,6 @@ export default function ManualRedactionControls({ disabled = false }: ManualReda
popoverProps={{ withinPortal: true }}
/>
<Button
variant={isRedactActive && !isAnnotationMode ? 'filled' : 'outline'}
color={isRedactActive && !isAnnotationMode ? 'blue' : 'gray'}
leftSection={<AutoFixHighIcon style={{ fontSize: 18, flexShrink: 0 }} />}
onClick={handleRedactClick}
disabled={disabled || !isApiReady}
fullWidth
size="sm"
>
{isRedactActive && !isAnnotationMode ? t('redact.manual.active', 'Redaction Mode Active') : t('redact.manual.activate', 'Activate Redaction Tool')}
</Button>
{/* Save Changes Button - applies pending redactions and saves to file */}
<Button
fullWidth
@@ -157,4 +133,3 @@ export default function ManualRedactionControls({ disabled = false }: ManualReda
</>
);
}

View File

@@ -6,16 +6,14 @@ interface RedactModeSelectorProps {
mode: RedactMode;
onModeChange: (mode: RedactMode) => void;
disabled?: boolean;
hasFilesSelected?: boolean; // Files are selected in workbench
hasAnyFiles?: boolean; // Any files exist in workbench (for manual mode)
hasAnyFiles?: boolean;
}
export default function RedactModeSelector({
mode,
onModeChange,
disabled,
hasFilesSelected = false,
hasAnyFiles = false
export default function RedactModeSelector({
mode,
onModeChange,
disabled,
hasAnyFiles = false
}: RedactModeSelectorProps) {
const { t } = useTranslation();
@@ -28,9 +26,9 @@ export default function RedactModeSelector({
{
value: 'automatic' as const,
label: t('redact.modeSelector.automatic', 'Automatic'),
disabled: !hasFilesSelected, // Automatic requires files to be selected
tooltip: !hasFilesSelected
? t('redact.modeSelector.automaticDisabledTooltip', 'Select files in the file manager to redact multiple files at once')
disabled: !hasAnyFiles, // Allow switching to automatic whenever files exist; selection can happen after
tooltip: !hasAnyFiles
? t('redact.modeSelector.automaticDisabledTooltip', 'Upload files to use automatic redaction')
: undefined,
},
{

View File

@@ -16,7 +16,6 @@ import { useSignature } from '@app/contexts/SignatureContext';
import { useRedaction } from '@app/contexts/RedactionContext';
import type { RedactionPendingTrackerAPI } from '@app/components/viewer/RedactionPendingTracker';
import { createStirlingFilesAndStubs } from '@app/services/fileStubHelpers';
import NavigationWarningModal from '@app/components/shared/NavigationWarningModal';
import { isStirlingFile, getFormFillFileId } from '@app/types/fileContext';
import { useViewerRightRailButtons } from '@app/components/viewer/useViewerRightRailButtons';
import { StampPlacementOverlay } from '@app/components/viewer/StampPlacementOverlay';
@@ -196,7 +195,7 @@ const EmbedPdfViewerContent = ({
const selectedFileIds = state.ui.selectedFileIds;
// Navigation guard for unsaved changes
const { setHasUnsavedChanges, registerUnsavedChangesChecker, unregisterUnsavedChangesChecker } = useNavigationGuard();
const { setHasUnsavedChanges, registerUnsavedChangesChecker, unregisterUnsavedChangesChecker, registerNavigationWarningHandlers, unregisterNavigationWarningHandlers } = useNavigationGuard();
const { selectedTool } = useNavigationState();
@@ -619,7 +618,7 @@ const EmbedPdfViewerContent = ({
const parentStub = selectors.getStirlingFileStub(currentFileId);
if (!parentStub) throw new Error('Parent stub not found');
const { stirlingFiles, stubs } = await createStirlingFilesAndStubs([file], parentStub, 'multiTool');
const { stirlingFiles, stubs } = await createStirlingFilesAndStubs([file], parentStub, selectedTool ?? 'multiTool');
// Store the page to restore after file replacement triggers re-render
pendingScrollRestoreRef.current = pageToRestore;
@@ -673,7 +672,7 @@ const EmbedPdfViewerContent = ({
if (!parentStub) throw new Error('Parent stub not found');
// Create StirlingFiles and stubs for version history
const { stirlingFiles, stubs } = await createStirlingFilesAndStubs([file], parentStub, 'multiTool');
const { stirlingFiles, stubs } = await createStirlingFilesAndStubs([file], parentStub, selectedTool ?? 'multiTool');
// Store the page to restore after file replacement
pendingScrollRestoreRef.current = pageToRestore;
@@ -731,7 +730,7 @@ const EmbedPdfViewerContent = ({
const parentStub = selectors.getStirlingFileStub(currentFileId);
if (!parentStub) throw new Error('Parent stub not found');
const { stirlingFiles, stubs } = await createStirlingFilesAndStubs([file], parentStub, 'multiTool');
const { stirlingFiles, stubs } = await createStirlingFilesAndStubs([file], parentStub, selectedTool ?? 'multiTool');
pendingScrollRestoreRef.current = pageToRestore;
scrollRestoreAttemptsRef.current = 0;
@@ -786,7 +785,7 @@ const EmbedPdfViewerContent = ({
const parentStub = selectors.getStirlingFileStub(currentFileId);
if (!parentStub) throw new Error('Parent stub not found');
const { stirlingFiles, stubs } = await createStirlingFilesAndStubs([file], parentStub, 'multiTool');
const { stirlingFiles, stubs } = await createStirlingFilesAndStubs([file], parentStub, selectedTool ?? 'multiTool');
// Store view state to restore after file replacement
pendingScrollRestoreRef.current = pageToRestore;
@@ -807,6 +806,28 @@ const EmbedPdfViewerContent = ({
}
}, [redactionsApplied, currentFile, activeFiles, activeFileIndex, activeFileIds.length, exportActions, actions, selectors, setRedactionsApplied, rotationState.rotation]);
// Register navigation warning handlers so the global modal can call our save/discard logic
useEffect(() => {
if (previewFile) return;
registerNavigationWarningHandlers({
onApplyAndContinue: async () => {
await applyChanges();
},
onDiscardAndContinue: async () => {
await discardAndSaveApplied();
const historyApi = historyApiRef.current;
if (historyApi?.canUndo) {
while (historyApi.canUndo()) {
historyApi.undo?.();
}
}
hasAnnotationChangesRef.current = false;
},
});
return () => unregisterNavigationWarningHandlers();
}, [previewFile, applyChanges, discardAndSaveApplied, registerNavigationWarningHandlers, unregisterNavigationWarningHandlers]);
// Restore scroll position after file replacement or tool switch
// Uses polling with retries to ensure the scroll succeeds
useEffect(() => {
@@ -1122,20 +1143,6 @@ const EmbedPdfViewerContent = ({
onLayersDetected={setHasLayers}
/>
{/* Navigation Warning Modal */}
{!previewFile && (
<NavigationWarningModal
onApplyAndContinue={async () => {
await applyChanges();
}}
onDiscardAndContinue={async () => {
// Save applied redactions (if any) while discarding pending ones
await discardAndSaveApplied();
// Reset annotation changes ref so future show/hide doesn't re-prompt
hasAnnotationChangesRef.current = false;
}}
/>
)}
</Box>
);
};

View File

@@ -31,7 +31,7 @@ export function useViewerRightRailButtons(
const [isPanning, setIsPanning] = useState<boolean>(() => viewer.getPanState()?.isPanning ?? false);
const { sidebarRefs } = useSidebarContext();
const { position: tooltipPosition } = useRightRailTooltipSide(sidebarRefs, 12);
const { handleToolSelect, handleBackToTools } = useToolWorkflow();
const { handleToolSelect, handleToolSelectForced, handleBackToTools } = useToolWorkflow();
const { selectedTool } = useNavigationState();
const { requestNavigation } = useNavigationGuard();
const { redactionsApplied, activeType: redactionActiveType } = useRedaction();
@@ -373,7 +373,9 @@ export function useViewerRightRailButtons(
window.history.pushState(null, '', targetPath);
}
setIsAnnotationsActive(true);
handleToolSelect('annotate');
// Use handleToolSelectForced to bypass the unsaved-changes guard —
// the navigation warning modal already handled that check.
handleToolSelectForced('annotate');
};
if (hasRedactionChanges) {

View File

@@ -1,4 +1,4 @@
import React, { createContext, useContext, useReducer, useCallback, useMemo } from 'react';
import React, { createContext, useContext, useReducer, useCallback, useMemo, useRef } from 'react';
import { WorkbenchType, getDefaultWorkbench } from '@app/types/workbench';
import { ToolId, isValidToolId } from '@app/types/toolId';
import { useToolRegistry } from '@app/contexts/ToolRegistryContext';
@@ -68,6 +68,13 @@ const initialState: NavigationContextState = {
showNavigationWarning: false
};
// Handlers that editors register for the navigation warning modal
export interface NavigationWarningHandlers {
onApplyAndContinue?: () => Promise<void>;
onExportAndContinue?: () => Promise<void>;
onDiscardAndContinue?: () => Promise<void>;
}
// Navigation context actions interface
export interface NavigationContextActions {
setWorkbench: (workbench: WorkbenchType) => void;
@@ -82,6 +89,9 @@ export interface NavigationContextActions {
cancelNavigation: () => void;
clearToolSelection: () => void;
handleToolSelect: (toolId: string) => void;
registerNavigationWarningHandlers: (handlers: NavigationWarningHandlers) => void;
unregisterNavigationWarningHandlers: () => void;
navigationWarningHandlersRef: React.RefObject<NavigationWarningHandlers | null>;
}
// Context state values
@@ -109,6 +119,7 @@ export const NavigationProvider: React.FC<{
const [state, dispatch] = useReducer(navigationReducer, initialState);
const { allTools: toolRegistry } = useToolRegistry();
const unsavedChangesCheckerRef = React.useRef<(() => boolean) | null>(null);
const navigationWarningHandlersRef = useRef<NavigationWarningHandlers | null>(null);
// Memoize individual callbacks
const setWorkbench = useCallback((workbench: WorkbenchType) => {
@@ -191,6 +202,14 @@ export const NavigationProvider: React.FC<{
unsavedChangesCheckerRef.current = null;
}, []);
const registerNavigationWarningHandlers = useCallback((handlers: NavigationWarningHandlers) => {
navigationWarningHandlersRef.current = handlers;
}, []);
const unregisterNavigationWarningHandlers = useCallback(() => {
navigationWarningHandlersRef.current = null;
}, []);
const showNavigationWarning = useCallback((show: boolean) => {
dispatch({ type: 'SHOW_NAVIGATION_WARNING', payload: { show } });
}, []);
@@ -277,6 +296,9 @@ export const NavigationProvider: React.FC<{
cancelNavigation,
clearToolSelection,
handleToolSelect,
registerNavigationWarningHandlers,
unregisterNavigationWarningHandlers,
navigationWarningHandlersRef,
}), [
setWorkbench,
setSelectedTool,
@@ -290,6 +312,8 @@ export const NavigationProvider: React.FC<{
cancelNavigation,
clearToolSelection,
handleToolSelect,
registerNavigationWarningHandlers,
unregisterNavigationWarningHandlers,
]);
const stateValue: NavigationContextStateValue = {
@@ -353,6 +377,9 @@ export const useNavigationGuard = () => {
setHasUnsavedChanges: actions.setHasUnsavedChanges,
setShowNavigationWarning: actions.showNavigationWarning,
registerUnsavedChangesChecker: actions.registerUnsavedChangesChecker,
unregisterUnsavedChangesChecker: actions.unregisterUnsavedChangesChecker
unregisterUnsavedChangesChecker: actions.unregisterUnsavedChangesChecker,
registerNavigationWarningHandlers: actions.registerNavigationWarningHandlers,
unregisterNavigationWarningHandlers: actions.unregisterNavigationWarningHandlers,
navigationWarningHandlersRef: actions.navigationWarningHandlersRef,
};
};

View File

@@ -96,7 +96,7 @@ const Redact = (props: BaseToolProps) => {
// Compute actual collapsed state based on results and user state
const getActualCollapsedState = (userCollapsed: boolean) => {
return (!base.hasFiles || base.hasResults) ? true : userCollapsed; // Force collapse when results are shown
return base.hasResults ? true : userCollapsed; // Force collapse when results are shown
};
// Build conditional steps based on redaction mode
@@ -117,7 +117,6 @@ const Redact = (props: BaseToolProps) => {
mode={base.params.parameters.mode}
onModeChange={handleModeChange}
disabled={base.endpointLoading}
hasFilesSelected={base.hasFiles}
hasAnyFiles={hasAnyFiles}
/>
),