UI redesign staging (#6149)

Co-authored-by: Reece Browne <[email protected]>
Co-authored-by: James Brunton <[email protected]>
This commit is contained in:
EthanHealy01
2026-05-15 15:36:50 +01:00
committed by GitHub
co-authored by Reece Browne James Brunton
parent beb99e273b
commit c731d5fd5d
173 changed files with 7290 additions and 4269 deletions
@@ -5,6 +5,7 @@ import React, {
useRef,
useState,
} from "react";
import { useTranslation } from "react-i18next";
import { Box, Center, Text, ActionIcon, Button, Stack } from "@mantine/core";
import CloseIcon from "@mui/icons-material/Close";
import LockIcon from "@mui/icons-material/Lock";
@@ -27,7 +28,7 @@ import { useRedaction } from "@app/contexts/RedactionContext";
import type { RedactionPendingTrackerAPI } from "@app/components/viewer/RedactionPendingTracker";
import { createStirlingFilesAndStubs } from "@app/services/fileStubHelpers";
import { isStirlingFile, getFormFillFileId } from "@app/types/fileContext";
import { useViewerRightRailButtons } from "@app/components/viewer/useViewerRightRailButtons";
import { useViewerWorkbenchBarButtons } from "@app/components/viewer/useViewerWorkbenchBarButtons";
import { StampPlacementOverlay } from "@app/components/viewer/StampPlacementOverlay";
import {
RulerOverlay,
@@ -138,8 +139,6 @@ export interface EmbedPdfViewerProps {
setSidebarsVisible: (v: boolean) => void;
onClose?: () => void;
previewFile?: File | null;
activeFileIndex?: number;
setActiveFileIndex?: (index: number) => void;
}
const EmbedPdfViewerContent = ({
@@ -147,9 +146,8 @@ const EmbedPdfViewerContent = ({
setSidebarsVisible: _setSidebarsVisible,
onClose,
previewFile,
activeFileIndex: externalActiveFileIndex,
setActiveFileIndex: externalSetActiveFileIndex,
}: EmbedPdfViewerProps) => {
const { t } = useTranslation();
const viewerRef = React.useRef<HTMLDivElement>(null);
const pdfContainerRef = useRef<HTMLDivElement>(null);
const [isViewerHovered, setIsViewerHovered] = React.useState(false);
@@ -178,6 +176,9 @@ const EmbedPdfViewerContent = ({
applyChanges: viewerApplyChanges,
pdfRenderMode,
cyclePdfRenderMode,
setActiveFileIndex: _setActiveFileIndex,
activeFileId,
setActiveFileId,
} = useViewer();
const scrollState = getScrollState();
@@ -327,63 +328,25 @@ const EmbedPdfViewerContent = ({
isInAnnotationTool && isPlacementMode && signatureConfig,
);
// Track which file tab is active
const [internalActiveFileIndex, setInternalActiveFileIndex] = useState(0);
const activeFileIndex = externalActiveFileIndex ?? internalActiveFileIndex;
const setActiveFileIndex =
externalSetActiveFileIndex ?? setInternalActiveFileIndex;
// activeFileId (from ViewerContext) is the stable source of truth.
// We derive activeFileIndex from it so reorders after tool operations don't lose the viewed file.
const { activeFileId, setActiveFileId } = useViewer();
// Stable string key representing the current file list order.
// Using a joined ID string avoids depending on the activeFiles array reference,
// which is a new object every render and would cause an infinite effect loop.
const fileIdsKey = activeFiles.map((f) => f.fileId).join(",");
// When the file list actually changes, re-derive activeFileIndex from the stable activeFileId.
useEffect(() => {
if (!activeFileId || activeFiles.length === 0) return;
const newIndex = activeFiles.findIndex((f) => f.fileId === activeFileId);
if (newIndex !== -1 && newIndex !== activeFileIndex) {
setActiveFileIndex(newIndex);
}
}, [fileIdsKey, activeFileId]); // stable primitives — no infinite loop
// When the user manually switches file tabs, keep activeFileId in sync.
// Skips the initial mount to avoid overwriting an activeFileId set by handleViewFile.
const activeFileIndexMountedRef = useRef(false);
useEffect(() => {
if (!activeFileIndexMountedRef.current) {
activeFileIndexMountedRef.current = true;
return;
}
const fileId = activeFilesRef.current[activeFileIndex]?.fileId;
if (fileId && fileId !== activeFileId) {
setActiveFileId(fileId);
}
}, [activeFileIndex]);
// Reset active tab if it's out of bounds (safety net)
useEffect(() => {
if (activeFileIndex >= activeFiles.length && activeFiles.length > 0) {
setActiveFileIndex(0);
}
}, [activeFiles.length, activeFileIndex]);
// Determine which file to display
// Determine which file to display — use activeFileId (stable) not activeFileIndex (shifts on removal)
const currentFile = React.useMemo(() => {
if (previewFile) {
return previewFile;
} else if (activeFiles.length > 0) {
return activeFiles[activeFileIndex] || activeFiles[0];
const byId = activeFileId
? activeFiles.find(
(f) => isStirlingFile(f) && f.fileId === activeFileId,
)
: null;
return byId || activeFiles[0];
}
return null;
}, [previewFile, activeFiles, activeFileIndex]);
}, [previewFile, activeFiles, activeFileId]);
// Get file with URL for rendering
const fileWithUrl = useFileWithUrl(currentFile);
// Stable id — avoids blob URL churn when FileContext recreates file objects each render.
const currentFileStableId =
currentFile && isStirlingFile(currentFile) ? currentFile.fileId : null;
const fileWithUrl = useFileWithUrl(currentFile, currentFileStableId);
// Determine the effective file to display
const effectiveFile = React.useMemo(() => {
@@ -703,7 +666,7 @@ const EmbedPdfViewerContent = ({
// Step 3: Create StirlingFiles and stubs for version history
// Only consume the current file, not all active files
const currentFileId = activeFiles[activeFileIndex]?.fileId;
const currentFileId = currentFileStableId;
if (!currentFileId) throw new Error("Current file ID not found");
const parentStub = selectors.getStirlingFileStub(currentFileId);
@@ -739,7 +702,6 @@ const EmbedPdfViewerContent = ({
}, [
currentFile,
activeFiles,
activeFileIndex,
exportActions,
actions,
selectors,
@@ -773,7 +735,7 @@ const EmbedPdfViewerContent = ({
});
// Get current file info for creating the updated version
const currentFileId = activeFiles[activeFileIndex]?.fileId;
const currentFileId = currentFileStableId;
if (!currentFileId) throw new Error("Current file ID not found");
const parentStub = selectors.getStirlingFileStub(currentFileId);
@@ -811,7 +773,6 @@ const EmbedPdfViewerContent = ({
[
currentFile,
activeFiles,
activeFileIndex,
actions,
selectors,
activeFileIds.length,
@@ -847,7 +808,7 @@ const EmbedPdfViewerContent = ({
type: "application/pdf",
});
const currentFileId = activeFiles[activeFileIndex]?.fileId;
const currentFileId = currentFileStableId;
if (!currentFileId) throw new Error("Current file ID not found");
const parentStub = selectors.getStirlingFileStub(currentFileId);
@@ -877,7 +838,6 @@ const EmbedPdfViewerContent = ({
[
currentFile,
activeFiles,
activeFileIndex,
actions,
selectors,
activeFileIds.length,
@@ -916,7 +876,7 @@ const EmbedPdfViewerContent = ({
const file = new File([blob], filename, { type: "application/pdf" });
// Create StirlingFiles and stubs for version history
const currentFileId = activeFiles[activeFileIndex]?.fileId;
const currentFileId = currentFileStableId;
if (!currentFileId) throw new Error("Current file ID not found");
const parentStub = selectors.getStirlingFileStub(currentFileId);
@@ -949,7 +909,6 @@ const EmbedPdfViewerContent = ({
redactionsApplied,
currentFile,
activeFiles,
activeFileIndex,
activeFileIds.length,
exportActions,
actions,
@@ -1123,8 +1082,8 @@ const EmbedPdfViewerContent = ({
};
}, [effectiveFile]);
// Register viewer right-rail buttons
useViewerRightRailButtons(isRulerActive, setIsRulerActive);
// Register workbench bar buttons for the viewer
useViewerWorkbenchBarButtons(isRulerActive, setIsRulerActive);
// Auto-fetch form fields when a PDF is loaded in the viewer.
// In normal viewer mode, this uses PDFium WASM (frontend-only).
@@ -1157,7 +1116,11 @@ const EmbedPdfViewerContent = ({
// the effect re-fires before the async fetch completes.
}
if (currentFile && (fileChanged || providerChanged)) {
if (
currentFile &&
!isCurrentFileEncrypted &&
(fileChanged || providerChanged)
) {
console.log("[FormFill] Fetching form fields for:", currentFileId);
fetchFormFields(currentFile, currentFileId ?? undefined);
}
@@ -1219,7 +1182,12 @@ const EmbedPdfViewerContent = ({
<Center style={{ flex: 1 }}>
<Stack align="center" gap="md">
<LockIcon style={{ fontSize: 48, opacity: 0.5 }} />
<Text fw={500}>This PDF is password-protected</Text>
<Text fw={500}>
{t(
"encryptedPdfUnlock.viewerLocked",
"This PDF is password-protected",
)}
</Text>
<Button
variant="filled"
onClick={() => {
@@ -1228,7 +1196,7 @@ const EmbedPdfViewerContent = ({
}
}}
>
Unlock
{t("encryptedPdfUnlock.viewerUnlock", "Unlock")}
</Button>
</Stack>
</Center>
@@ -1329,7 +1297,7 @@ const EmbedPdfViewerContent = ({
<ThumbnailSidebar
visible={isThumbnailSidebarVisible}
onToggle={toggleThumbnailSidebar}
activeFileIndex={activeFileIndex}
activeFileId={activeFileId}
/>
<BookmarkSidebar
visible={isBookmarkSidebarVisible}