Removed the `ZoomLevelChanged` parameter from the `DxPdfViewer` component in `EnvelopeReceiverPage.razor` due to lack of support in the installed `DevExpress.Blazor.PdfViewer` package version `25.2.3`. This prevents runtime exceptions caused by the use of an unsupported parameter. Deleted the `OnViewerZoomLevelChanged` method, as it is no longer needed. Updated `RECEIVER_PDF_VIEWER_CONTEXT.md` to reflect the limitations of the installed package and adjusted the recovery plan to bind zoom state directly to `DxPdfViewer.ZoomLevel`. Simplified zoom handling by removing custom JavaScript logic for `ctrl+wheel` zoom and retaining the overlay redraw pipeline. Confirmed that the built-in DevExpress zoom UI now works without runtime errors, and custom zoom duplication has been eliminated.
1049 lines
32 KiB
Markdown
1049 lines
32 KiB
Markdown
# EnvelopeGenerator — Receiver PDF Viewer Context
|
|
|
|
## Purpose
|
|
This document summarizes the active receiver-side PDF viewing and signing experience so that other agents can understand the current implementation quickly without re-reading all related files.
|
|
|
|
This summary is based on the active host and current implementation in:
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage.razor`
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/js/pdf-viewer.js`
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/js/receiver-signature.js`
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/envelope-viewer.css`
|
|
|
|
---
|
|
|
|
## Active Page
|
|
|
|
**Primary receiver page:**
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage.razor`
|
|
|
|
**Route:**
|
|
- `/envelope/{EnvelopeKey}`
|
|
|
|
**Render mode:**
|
|
- `InteractiveServer`
|
|
|
|
This page is the active receiver PDF viewing and signing UI.
|
|
|
|
---
|
|
|
|
## Core Architecture
|
|
|
|
The receiver experience is built from three layers:
|
|
|
|
### 1. Blazor page layer
|
|
`EnvelopeReceiverPage.razor` is responsible for:
|
|
- authorization flow
|
|
- loading receiver-specific document data
|
|
- loading signature placeholders
|
|
- loading and saving cached signature data
|
|
- UI state management
|
|
- toolbar interactions
|
|
- popup interactions
|
|
- JS interop orchestration
|
|
|
|
### 2. PDF rendering layer
|
|
The active implementation currently uses `PDF.js` for:
|
|
- rendering the active PDF page
|
|
- rendering thumbnails
|
|
- handling zoom and page navigation state
|
|
|
|
Referenced assets in the current implementation:
|
|
- `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js`
|
|
- `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf_viewer.min.css`
|
|
|
|
### Planned target rendering layer
|
|
The main migration goal is to replace `PDF.js` in `EnvelopeReceiverPage.razor` with `DxPdfViewer` while preserving the complete receiver signing experience documented in this file.
|
|
|
|
This means `DxPdfViewer` must become the main document rendering surface without regressing:
|
|
- single active page viewing behavior
|
|
- page navigation behavior
|
|
- zoom behavior
|
|
- thumbnail sidebar behavior
|
|
- receiver-specific signature placeholder overlays
|
|
- applied signature overlays
|
|
- signature navigation across pages
|
|
- overlay repositioning and resizing after zoom/page changes
|
|
|
|
### 3. Custom enhancement layer
|
|
Extra behavior is implemented through custom JavaScript and CSS:
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/js/pdf-viewer.js`
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/js/receiver-signature.js`
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/envelope-viewer.css`
|
|
|
|
These files extend the base PDF.js experience with receiver-specific features.
|
|
|
|
---
|
|
|
|
## Main Functional Capabilities
|
|
|
|
## 1. Single-page PDF viewing in the main viewer
|
|
The page shows one active PDF page at a time in the main canvas.
|
|
|
|
Main viewer elements:
|
|
- `pdf-canvas`
|
|
- `pdf-text-layer`
|
|
- `pdf-signature-layer`
|
|
|
|
This means the page is not using a continuous full-document scroll layout in the main area. Instead, it behaves like a single active page viewer with controlled navigation.
|
|
|
|
---
|
|
|
|
## 2. Page navigation
|
|
The page supports multiple navigation methods:
|
|
- previous page button
|
|
- next page button
|
|
- direct page number input
|
|
- thumbnail click navigation
|
|
- signature navigation that may jump to another page automatically
|
|
|
|
Relevant Blazor methods include:
|
|
- `PreviousPage()`
|
|
- `NextPage()`
|
|
- `OnPageInputChanged(...)`
|
|
- `GoToPageFromThumbnail(...)`
|
|
- `OnPageChangedBySignatureNav(...)`
|
|
|
|
---
|
|
|
|
## 3. Zoom support
|
|
The page supports zooming in and out for the active PDF page.
|
|
|
|
Available zoom behavior:
|
|
- zoom in button
|
|
- zoom out button
|
|
- zoom slider
|
|
- programmatic scale setting
|
|
- fit-to-width helper exists in code
|
|
|
|
Relevant methods:
|
|
- `ZoomIn()`
|
|
- `ZoomOut()`
|
|
- `SetZoom(int percentage)`
|
|
- `OnZoomSliderChanged(...)`
|
|
- `FitToWidth()`
|
|
- `OnZoomChanged(double scale)`
|
|
|
|
Current zoom constraints in the page:
|
|
- minimum: `50%`
|
|
- maximum: `300%`
|
|
|
|
Important behavior:
|
|
- when zoom changes, signature placeholders and applied signature overlays are re-rendered/repositioned
|
|
- position and visual size are expected to stay synchronized with the current PDF scale
|
|
|
|
---
|
|
|
|
## 4. Thumbnail sidebar
|
|
The page has a thumbnail sidebar for document page previews.
|
|
|
|
Supported sidebar behavior:
|
|
- show/hide toggle
|
|
- click thumbnail to navigate to page
|
|
- highlight active page
|
|
- resizable width
|
|
- width persistence in `localStorage`
|
|
|
|
Relevant state:
|
|
- `_showThumbnails`
|
|
- `_thumbnailWidth`
|
|
- `_isResizing`
|
|
|
|
Relevant methods:
|
|
- `ToggleThumbnails()`
|
|
- `RenderThumbnailsAsync()`
|
|
- `OnSplitterMouseDown(...)`
|
|
- `OnSplitterMouseMove(...)`
|
|
- `OnSplitterMouseUp()`
|
|
|
|
Persistence key:
|
|
- `envelopeViewer_thumbnailWidth`
|
|
|
|
Important implementation detail:
|
|
- thumbnail rendering is done sequentially with delay to avoid overloading the browser
|
|
|
|
---
|
|
|
|
## 5. Signature placeholder buttons on top of the PDF
|
|
The page places clickable signature buttons over the PDF for receiver-specific signature fields.
|
|
|
|
Behavior:
|
|
- placeholders are loaded server-side
|
|
- placeholders are rendered on the client as overlay elements
|
|
- placeholders are shown for the current page
|
|
- clicking a placeholder applies the captured signature to that location
|
|
|
|
Relevant Blazor method:
|
|
- `RenderSignatureButtonsAsync()`
|
|
|
|
Relevant JS interop call:
|
|
- `pdfViewer.renderSignatureButtons`
|
|
|
|
Important note:
|
|
- this is an overlay workflow, not direct PDF stamping
|
|
- placeholders belong to the current authenticated receiver
|
|
|
|
---
|
|
|
|
## 6. Applying a real signature after clicking a placeholder
|
|
Once a receiver has a captured signature, clicking a signature placeholder applies the real signature overlay to the PDF viewer.
|
|
|
|
Applied signature data includes:
|
|
- signature image data URL
|
|
- signer full name
|
|
- signer position
|
|
- place
|
|
|
|
Relevant method:
|
|
- `OnSignatureButtonClick(int signatureId)`
|
|
|
|
Relevant JS interop call:
|
|
- `pdfViewer.applySignature`
|
|
|
|
Important note:
|
|
- the active implementation visually places the signature in the viewer layer
|
|
- the page is not currently doing final PDF binary stamping in this UI flow
|
|
|
|
---
|
|
|
|
## 7. Signature navigation across all signature fields
|
|
The toolbar supports fast navigation between signature fields.
|
|
|
|
Supported behavior:
|
|
- go to previous signature
|
|
- go to next signature
|
|
- maintain current signature index
|
|
- show signed / unsigned / total counts
|
|
- automatically move between pages if the next signature is on another page
|
|
|
|
Relevant methods:
|
|
- `GoToPreviousSignature()`
|
|
- `GoToNextSignature()`
|
|
- `OnSignatureNavChanged()`
|
|
- `OnPageChangedBySignatureNav(int newPage)`
|
|
- `UpdateSignatureCounterAsync()`
|
|
|
|
Relevant JS interop calls:
|
|
- `pdfViewer.goToPreviousSignature`
|
|
- `pdfViewer.goToNextSignature`
|
|
- `pdfViewer.getSignatureNavState`
|
|
|
|
Counter state maintained in Blazor:
|
|
- `_totalSignatures`
|
|
- `_signedSignatures`
|
|
- `_unsignedSignatures`
|
|
- `_currentSignatureIndex`
|
|
|
|
---
|
|
|
|
## 8. Automatic signature/button repositioning and resizing on zoom
|
|
A critical current feature is that zoom changes should keep overlay elements visually aligned with the document.
|
|
|
|
This applies to:
|
|
- signature placeholder buttons
|
|
- applied signature overlays
|
|
|
|
Expected behavior:
|
|
- position updates when zoom changes
|
|
- size updates when zoom changes
|
|
- re-render happens after page change and zoom change
|
|
|
|
This behavior is triggered from methods such as:
|
|
- `OnZoomChanged(...)`
|
|
- `ZoomIn()`
|
|
- `ZoomOut()`
|
|
- `OnZoomSliderChanged(...)`
|
|
- `NextPage()`
|
|
- `PreviousPage()`
|
|
- `GoToPageFromThumbnail(...)`
|
|
- `OnPageChangedBySignatureNav(...)`
|
|
|
|
In practice, the page repeatedly calls:
|
|
- `RenderSignatureButtonsAsync()`
|
|
|
|
This is one of the key behaviors other agents must preserve.
|
|
|
|
---
|
|
|
|
## 9. Signature capture popup
|
|
Signature creation is handled with a `DxPopup`.
|
|
|
|
Popup capabilities:
|
|
- draw signature
|
|
- create typed signature
|
|
- upload signature image
|
|
- capture required signer metadata
|
|
- validate required fields before save
|
|
|
|
Tabs:
|
|
- `draw`
|
|
- `text`
|
|
- `image`
|
|
|
|
Relevant constants:
|
|
- `SignatureTabDraw`
|
|
- `SignatureTabText`
|
|
- `SignatureTabImage`
|
|
|
|
Relevant canvas/input IDs:
|
|
- `envelope-signature-pad`
|
|
- `envelope-typed-signature-pad`
|
|
- `envelope-signature-image-input`
|
|
- `envelope-image-signature-pad`
|
|
|
|
---
|
|
|
|
## 10. Signature capture modes
|
|
The current UI supports three signature creation modes.
|
|
|
|
### Draw mode
|
|
Receiver signs directly on a canvas.
|
|
|
|
Relevant JS usage:
|
|
- `receiverSignature.initialize`
|
|
- `receiverSignature.clear`
|
|
- `receiverSignature.getDataUrl`
|
|
- `receiverSignature.loadExistingSignature`
|
|
|
|
### Text mode
|
|
Receiver enters a signature as text and chooses a font.
|
|
|
|
Relevant JS usage:
|
|
- `receiverSignature.initializeTyped`
|
|
- `receiverSignature.renderTypedSignature`
|
|
- `receiverSignature.clearTyped`
|
|
- `receiverSignature.getTypedDataUrl`
|
|
|
|
### Image mode
|
|
Receiver uploads an image of their signature.
|
|
|
|
Relevant JS usage:
|
|
- `receiverSignature.initializeImage`
|
|
- `receiverSignature.clearImage`
|
|
- `receiverSignature.getImageDataUrl`
|
|
|
|
---
|
|
|
|
## 11. Signature metadata requirements
|
|
The popup captures extra metadata besides the signature image.
|
|
|
|
### Required
|
|
- full name
|
|
- place
|
|
|
|
### Optional
|
|
- position
|
|
|
|
Relevant bound fields:
|
|
- `_signerFullName`
|
|
- `_signaturePlace`
|
|
- `_signerPosition`
|
|
|
|
Validation behavior in `SaveSignatureAsync()`:
|
|
- full name must not be empty
|
|
- place must not be empty
|
|
- signature image/data must not be empty
|
|
|
|
---
|
|
|
|
## 12. Cached signature reuse
|
|
The page attempts to load a previously cached signature for the receiver.
|
|
|
|
Behavior:
|
|
- if cache exists, the popup does not open automatically
|
|
- if cache does not exist, the popup opens on initial load
|
|
- saving a signature stores it back through the page data service
|
|
|
|
Relevant service usage:
|
|
- `PageDataService.GetCachedSignatureAsync(...)`
|
|
- `PageDataService.SaveCachedSignatureAsync(...)`
|
|
|
|
Related state:
|
|
- `_capturedSignature`
|
|
- `_signaturePopupVisible`
|
|
|
|
Important note:
|
|
- the active cache handling is server-side through distributed cache
|
|
- the receiver signature is not only in browser state
|
|
|
|
---
|
|
|
|
## 13. Signature change locking after signing starts
|
|
The current page prevents changing the captured signature after at least one signature has already been applied.
|
|
|
|
Behavior:
|
|
- signature change button becomes disabled when `_signedSignatures > 0`
|
|
- title explains the signature is locked
|
|
- reset requires restarting the signing session
|
|
|
|
Relevant methods:
|
|
- `GetSignatureButtonTitle()`
|
|
- `HandleSignatureChangeClick()`
|
|
- `RestartSigning()`
|
|
|
|
Important implication:
|
|
- once actual field signing begins, the selected/captured signature is treated as fixed for that session unless the page is reset
|
|
|
|
---
|
|
|
|
## 14. Reset/restart signing flow
|
|
The page includes a reset/restart behavior.
|
|
|
|
Current behavior:
|
|
- page reload is used to reset signing UI state
|
|
- this clears current in-view applied signatures and session UI state
|
|
|
|
Relevant method:
|
|
- `RestartSigning()`
|
|
|
|
Implementation detail:
|
|
- current reset behavior is based on `Navigation.NavigateTo(Navigation.Uri, forceLoad: true)`
|
|
|
|
---
|
|
|
|
## 15. PDF quality and rendering options
|
|
The page sends rendering options from .NET to JavaScript before viewer initialization.
|
|
|
|
Relevant options include:
|
|
- thumbnail base scale
|
|
- thumbnail HiDPI support
|
|
- thumbnail max DPR
|
|
- main canvas HiDPI support
|
|
- main canvas max DPR
|
|
- smooth zoom enablement
|
|
- zoom transition duration
|
|
- rendering opacity
|
|
- zoom step percentage
|
|
|
|
These come from:
|
|
- `IOptions<PdfViewerOptions>`
|
|
|
|
Relevant JS call:
|
|
- `pdfViewer.setQualityOptions(...)`
|
|
|
|
This means rendering quality and viewer performance are configurable from server-side options.
|
|
|
|
---
|
|
|
|
## 16. Local storage usage
|
|
The page currently uses `localStorage` at least for UI preferences.
|
|
|
|
Known usage:
|
|
- thumbnail sidebar width persistence
|
|
|
|
Known key:
|
|
- `envelopeViewer_thumbnailWidth`
|
|
|
|
This is UI preference storage, not the main signature cache mechanism.
|
|
|
|
---
|
|
|
|
## 17. Receiver authorization dependency
|
|
The page is not a public PDF viewer. It depends on receiver-specific authorization.
|
|
|
|
Before loading the document:
|
|
- `ReceiverAuthorizationService.AuthorizeAsync(EnvelopeKey)` is executed
|
|
- unauthorized users are redirected to `/envelope/login/{EnvelopeKey}`
|
|
|
|
Implication for other agents:
|
|
- viewer behavior is tied to authenticated receiver context
|
|
- placeholder loading and cached signature loading are receiver-specific
|
|
|
|
---
|
|
|
|
## 18. Receiver-specific data loading
|
|
The page loads its data through `EnvelopeReceiverPageDataService`.
|
|
|
|
Main server-side data loaded:
|
|
- document bytes
|
|
- signature placeholders
|
|
- receiver envelope data
|
|
- cached signature
|
|
|
|
Important domain behavior:
|
|
- signature placeholders are filtered for the authenticated receiver
|
|
- placeholder coordinates are converted to points before UI use
|
|
|
|
This matters for any future changes involving coordinate systems or overlay placement.
|
|
|
|
---
|
|
|
|
## Important Non-Goals / Current Constraints
|
|
|
|
### Not acceptable as a migration outcome
|
|
The migration is **not** successful if it results in:
|
|
- losing any receiver-side behavior documented in this file
|
|
- replacing the current workflow with a simplified read-only PDF viewer
|
|
- removing signature placeholder overlays or applied signature overlays
|
|
- removing cross-page signature navigation
|
|
- breaking zoom-time overlay synchronization
|
|
- falling back to legacy `ReceiverUI` as the main implementation target
|
|
- changing the flow into direct PDF binary stamping-only behavior in the main receiver UI
|
|
|
|
### Current active model
|
|
The current implementation **is** based on:
|
|
- `EnvelopeGenerator.Server`
|
|
- `EnvelopeReceiverPage.razor`
|
|
- `PDF.js`
|
|
- custom overlay and JS interop behavior
|
|
|
|
### Target model
|
|
The intended target model is:
|
|
- `EnvelopeGenerator.Server`
|
|
- `EnvelopeReceiverPage.razor`
|
|
- `DxPdfViewer` as the main PDF rendering component
|
|
- equivalent custom behavior for overlays, navigation, zoom synchronization, and receiver signing interactions
|
|
- preservation of the current receiver-specific authorization and page data loading flow
|
|
|
|
---
|
|
|
|
## Key Behaviors Other Agents Must Preserve
|
|
|
|
If another agent modifies this area, these behaviors are important to keep intact:
|
|
|
|
1. Receiver authorization before document access
|
|
2. Single active page PDF viewing
|
|
3. Page navigation and thumbnail navigation
|
|
4. Zoom in/out with correct overlay synchronization
|
|
5. Signature placeholder rendering on the correct page
|
|
6. Clicking a placeholder applies the captured signature overlay
|
|
7. Fast previous/next signature navigation across pages
|
|
8. Automatic overlay position and size updates after zoom/page changes
|
|
9. Signature popup with draw/text/image modes
|
|
10. Required metadata validation for full name and place
|
|
11. Cached signature reuse
|
|
12. Signature lock after signing has started
|
|
13. Reset/restart signing behavior
|
|
14. Thumbnail width persistence
|
|
|
|
## Migration Requirement
|
|
|
|
Any migration from `PDF.js` to `DxPdfViewer` in `EnvelopeReceiverPage.razor` must be treated as a rendering engine swap, not a workflow redesign.
|
|
|
|
The expected outcome is:
|
|
- `DxPdfViewer` replaces `PDF.js` as the main viewer technology
|
|
- the receiver authorization model remains unchanged
|
|
- the existing signature capture popup model remains unchanged
|
|
- signature placeholder rendering remains receiver-specific
|
|
- applied signature visuals remain aligned with the rendered document
|
|
- page navigation, zoom, thumbnails, and signature navigation continue to behave equivalently
|
|
- no documented feature in this file is dropped during the migration
|
|
|
|
---
|
|
|
|
## Suggested Mental Model
|
|
|
|
A useful way to think about the page is:
|
|
|
|
- `EnvelopeReceiverPage.razor` = orchestration and state
|
|
- current rendering engine = `PDF.js`
|
|
- target rendering engine = `DxPdfViewer`
|
|
- current viewer behavior layer = `pdf-viewer.js` for overlays, navigation, thumbnails, resize logic
|
|
- `receiver-signature.js` = signature capture/creation logic
|
|
- server page data service = receiver-specific document and signature source
|
|
|
|
---
|
|
|
|
## Current Post-Migration Status and Step-by-Step Feature Recovery Plan
|
|
|
|
This section replaces the earlier generic migration plan and records the current real status after the first `DxPdfViewer` swap attempt.
|
|
|
|
### 1. Current status summary
|
|
|
|
The main document is now visible with `DxPdfViewer`, which means:
|
|
|
|
- receiver authorization still works
|
|
- document loading still works
|
|
- the route and page host still work
|
|
- `DxPdfViewer` is now the visible main rendering surface
|
|
|
|
However, the receiver experience is currently **feature-incomplete**.
|
|
|
|
At the moment, the page is effectively in this state:
|
|
|
|
- PDF is displayed
|
|
- custom workflow shell still exists
|
|
- but the old `PDF.js` behavior contract is no longer fully connected to the new viewer
|
|
|
|
So this is no longer a rendering failure.
|
|
It is now a **behavior recovery** problem.
|
|
|
|
### 2. What is currently missing or unreliable
|
|
|
|
The following features are currently missing, incomplete, or highly likely to be non-functional in the present state.
|
|
|
|
#### 2.1 Page navigation is not truly connected to `DxPdfViewer`
|
|
|
|
Symptoms:
|
|
|
|
- previous page button may update Blazor state only
|
|
- next page button may update Blazor state only
|
|
- direct page input may update Blazor state only
|
|
- thumbnail click may update Blazor state only
|
|
- signature navigation page jumps may update Blazor state only
|
|
|
|
Why:
|
|
|
|
- the earlier attempt tried to use `ActivePageIndex`, but `DxPdfViewer` does not expose it as a component parameter in this version
|
|
- after removing that invalid parameter, the page still updates `_currentPage`, but that does not automatically move the DevExpress viewer to another page
|
|
- in other words, there is currently no verified programmatic page-navigation bridge into `DxPdfViewer`
|
|
|
|
Impact:
|
|
|
|
- all features that depend on true page switching are blocked or only partially simulated
|
|
|
|
#### 2.2 Zoom controls are not truly connected to `DxPdfViewer`
|
|
|
|
Symptoms:
|
|
|
|
- zoom in/out buttons may update `_currentZoom`
|
|
- slider may update `_currentZoom`
|
|
- ctrl+wheel logic may update `_currentZoom`
|
|
- but the visible viewer may not actually zoom accordingly, or may not do so reliably
|
|
|
|
Why:
|
|
|
|
- `ZoomLevel` is exposed as a property on `DxPdfViewer`, but it is not yet confirmed that changing the bound value gives the same runtime behavior expected by the old `PDF.js` flow
|
|
- the current implementation updates Blazor state and triggers overlay refresh, but the real DevExpress zoom lifecycle and its timing have not yet been validated
|
|
- no verified zoom-changed callback from `DxPdfViewer` has been integrated
|
|
|
|
Impact:
|
|
|
|
- zoom UX is incomplete
|
|
- overlay synchronization cannot be trusted until real zoom behavior is confirmed
|
|
|
|
#### 2.3 Overlay positioning is heuristic, not yet proven
|
|
|
|
Symptoms:
|
|
|
|
- signature placeholders may not appear
|
|
- or may appear in the wrong location
|
|
- or may not track zoom/page changes correctly
|
|
|
|
Why:
|
|
|
|
- old implementation owned the page canvas directly
|
|
- new implementation tries to detect the visible page surface inside `DxPdfViewer` DOM heuristically
|
|
- this means the current adapter is guessing which DOM element is the active rendered page surface
|
|
- page geometry, offsets, scaling and scroll surface are not yet bound to a stable DevExpress contract
|
|
|
|
Impact:
|
|
|
|
- placeholder buttons are not yet production-safe
|
|
- applied signatures are not yet production-safe
|
|
|
|
#### 2.4 Applied signature overlay behavior is not yet dependable
|
|
|
|
Symptoms:
|
|
|
|
- clicking a placeholder may not place the visual signature correctly
|
|
- applied signatures may drift after zoom
|
|
- applied signatures may disappear or misalign when navigation changes
|
|
|
|
Why:
|
|
|
|
- applied signatures currently depend on the same unverified geometry bridge as placeholders
|
|
- they also depend on reliable page identity and zoom identity from the DevExpress surface
|
|
|
|
Impact:
|
|
|
|
- the core signing UX is not yet restored
|
|
|
|
#### 2.5 Signature navigation is structurally present but functionally blocked
|
|
|
|
Symptoms:
|
|
|
|
- previous/next signature buttons may update internal navigation state
|
|
- but page-jump and scroll-to-target behavior may fail or be inconsistent
|
|
|
|
Why:
|
|
|
|
- signature navigation depends on:
|
|
- real page navigation
|
|
- real overlay rendering
|
|
- reliable scroll container discovery
|
|
- since all three are not yet stabilized, signature navigation cannot yet be trusted
|
|
|
|
Impact:
|
|
|
|
- navigation across signature fields is not complete
|
|
|
|
#### 2.6 Thumbnail sidebar is only partially preserved
|
|
|
|
What still exists:
|
|
|
|
- sidebar shell
|
|
- width persistence
|
|
- resize logic
|
|
- thumbnail rendering through helper pipeline
|
|
|
|
What is missing or uncertain:
|
|
|
|
- guaranteed synchronization between thumbnail click and visible page in `DxPdfViewer`
|
|
- guaranteed active-page highlight based on real viewer page
|
|
|
|
Why:
|
|
|
|
- active thumbnail currently follows `_currentPage`
|
|
- but `_currentPage` is not yet guaranteed to reflect the actual active page inside `DxPdfViewer`
|
|
|
|
#### 2.7 Single-page behavior is not yet guaranteed to match the old experience
|
|
|
|
Why:
|
|
|
|
- old implementation enforced a single active rendered page in a fully custom surface
|
|
- new implementation uses `DxPdfViewer IsSinglePagePreview="true"`
|
|
- but this still needs runtime verification against:
|
|
- page scroll behavior
|
|
- zoom behavior
|
|
- page switch timing
|
|
- overlay surface alignment
|
|
|
|
### 3. Root cause analysis: why features disappeared
|
|
|
|
The features disappeared for one main reason:
|
|
|
|
- the old receiver experience was not just a PDF viewer
|
|
- it was a **custom stateful viewer platform** built on top of `PDF.js`
|
|
|
|
In the old system:
|
|
|
|
- page navigation was owned by our JavaScript
|
|
- zoom was owned by our JavaScript
|
|
- page metrics were owned by our JavaScript
|
|
- active page surface was owned by our JavaScript
|
|
- overlays were drawn on DOM elements owned by our JavaScript
|
|
|
|
After the swap:
|
|
|
|
- the visible rendering surface belongs to DevExpress
|
|
- but the custom behavior layer still expects low-level ownership similar to `PDF.js`
|
|
|
|
So the missing features are not random regressions.
|
|
They are the direct result of replacing the renderer before fully rebuilding the viewer behavior bridge.
|
|
|
|
### 4. New recommended implementation strategy
|
|
|
|
Do **not** try to restore everything at once.
|
|
|
|
The correct path now is to restore behavior in controlled stages, one capability at a time.
|
|
|
|
### 5. Step-by-step recovery plan
|
|
|
|
#### Step 1 — Confirm the real `DxPdfViewer` API surface
|
|
|
|
Goal:
|
|
|
|
- stop guessing which `DxPdfViewer` properties and behaviors are actually bindable and runtime-reactive
|
|
|
|
Tasks:
|
|
|
|
- inspect the actual supported parameter surface for the installed DevExpress version
|
|
- determine which of the following are truly supported and reactive:
|
|
- zoom binding
|
|
- page navigation binding
|
|
- single-page mode behavior
|
|
- toolbar customization / suppression
|
|
- determine whether DevExpress exposes any JS/API for page switching or current page retrieval
|
|
|
|
Expected output:
|
|
|
|
- a verified list of what `DxPdfViewer` can really do directly
|
|
- a list of behaviors that must remain custom because DevExpress does not expose them
|
|
|
|
This step is mandatory before further feature work.
|
|
|
|
#### Step 2 — Restore real zoom behavior first
|
|
|
|
Goal:
|
|
|
|
- make zoom in/out/slider visibly affect `DxPdfViewer`
|
|
|
|
Why first:
|
|
|
|
- zoom is simpler than full page navigation
|
|
- it is also required before overlay sizing can be trusted
|
|
|
|
Tasks:
|
|
|
|
- verify whether `ZoomLevel` binding actually updates the viewer at runtime
|
|
- if yes, stabilize it
|
|
- if no, find the supported DevExpress-side mechanism
|
|
- disable or adjust any overlay refresh logic until true zoom behavior is confirmed
|
|
|
|
Acceptance for this step:
|
|
|
|
- zoom in button visibly zooms document
|
|
- zoom out button visibly zooms document
|
|
- slider visibly changes viewer zoom
|
|
- `_currentZoom` matches what the user sees
|
|
|
|
#### Step 3 — Restore real page navigation
|
|
|
|
Goal:
|
|
|
|
- make previous/next/page input/thumbnail click truly change the visible page in `DxPdfViewer`
|
|
|
|
Tasks:
|
|
|
|
- identify supported page navigation mechanism for this DevExpress version
|
|
- connect `_currentPage` to the real viewer page
|
|
- if direct binding is unavailable, create a supported indirect mechanism
|
|
|
|
Acceptance for this step:
|
|
|
|
- previous page works
|
|
- next page works
|
|
- direct page input works
|
|
- `_currentPage` reflects visible page
|
|
|
|
This step must be completed before signature navigation or page-specific overlays are considered reliable.
|
|
|
|
#### Step 4 — Stabilize active page geometry extraction
|
|
|
|
Goal:
|
|
|
|
- determine a reliable way to identify the active visible rendered page inside the DevExpress DOM
|
|
|
|
Tasks:
|
|
|
|
- inspect the actual DOM produced by `DxPdfViewer`
|
|
- identify stable selectors for:
|
|
- viewer scroll container
|
|
- active page surface
|
|
- page content bounds
|
|
- replace heuristic “largest visible element” logic if a more stable pattern exists
|
|
|
|
Acceptance for this step:
|
|
|
|
- geometry retrieval returns correct page bounds repeatedly
|
|
- geometry remains stable after zoom
|
|
- geometry remains stable after page changes
|
|
|
|
#### Step 5 — Restore signature placeholder overlay rendering
|
|
|
|
Goal:
|
|
|
|
- make unsigned signature placeholders render at correct positions on the current page
|
|
|
|
Tasks:
|
|
|
|
- connect placeholder rendering to verified page bounds
|
|
- preserve current filtering rules:
|
|
- active page only
|
|
- hide already-signed fields
|
|
- validate size scaling against visible zoom
|
|
|
|
Acceptance for this step:
|
|
|
|
- placeholder appears on correct page
|
|
- placeholder appears in correct location
|
|
- placeholder size follows zoom
|
|
|
|
#### Step 6 — Restore applied signature overlays
|
|
|
|
Goal:
|
|
|
|
- clicking a placeholder should place the actual signature overlay correctly
|
|
|
|
Tasks:
|
|
|
|
- keep current visual block format
|
|
- bind position/size to the same verified geometry system as placeholders
|
|
- ensure page changes hide/show correctly
|
|
|
|
Acceptance for this step:
|
|
|
|
- clicking placeholder places signature correctly
|
|
- applied signature stays aligned after zoom
|
|
- applied signature stays aligned after page change
|
|
|
|
#### Step 7 — Restore signature navigation
|
|
|
|
Goal:
|
|
|
|
- previous/next signature should work across all pages again
|
|
|
|
Tasks:
|
|
|
|
- keep the existing global signature ordering logic
|
|
- connect it to real page switching
|
|
- connect it to real overlay availability
|
|
- connect it to real scroll-to-target behavior
|
|
|
|
Acceptance for this step:
|
|
|
|
- next signature works on same page
|
|
- next signature jumps across pages
|
|
- previous signature works
|
|
- current index / signed / unsigned counters are correct
|
|
|
|
#### Step 8 — Validate and finish thumbnail behavior
|
|
|
|
Goal:
|
|
|
|
- make sidebar behavior trustworthy again
|
|
|
|
Tasks:
|
|
|
|
- confirm thumbnails still render correctly
|
|
- ensure thumbnail click changes actual visible page
|
|
- ensure active thumbnail reflects actual page
|
|
- keep width persistence and resize behavior
|
|
|
|
Acceptance for this step:
|
|
|
|
- thumbnail click works
|
|
- active highlight is correct
|
|
- width persistence still works
|
|
|
|
### 6. Recommended order of implementation
|
|
|
|
The recommended execution order is:
|
|
|
|
1. verify `DxPdfViewer` real API surface
|
|
2. restore zoom
|
|
3. restore page navigation
|
|
4. stabilize page geometry extraction
|
|
5. restore placeholder overlays
|
|
6. restore applied signatures
|
|
7. restore signature navigation
|
|
8. finish thumbnail synchronization
|
|
|
|
This order is important because later features depend on earlier ones.
|
|
|
|
### 7. What should not be changed yet
|
|
|
|
Until the viewer bridge is stable, avoid refactoring these parts unnecessarily:
|
|
|
|
- receiver authorization flow
|
|
- page data loading service
|
|
- cached signature flow
|
|
- popup tabs and signature capture JS
|
|
- signature metadata validation
|
|
- signature lock behavior
|
|
- restart signing behavior
|
|
|
|
These parts are not the current problem.
|
|
|
|
### 8. Next implementation checkpoint
|
|
|
|
The next actual coding step should be:
|
|
|
|
- **Step 1 + Step 2** together if possible:
|
|
- confirm supported `DxPdfViewer` interaction surface
|
|
- restore real zoom behavior first
|
|
|
|
That is the safest first vertical slice because:
|
|
|
|
- it is easy to verify visually
|
|
- it reduces uncertainty in overlay scaling
|
|
- it does not yet require full signature flow completion
|
|
|
|
### 9. Verified DevExpress API findings and current progress
|
|
|
|
The following points are now verified for the currently installed `DevExpress.Blazor.PdfViewer` package version `25.2.3`:
|
|
|
|
- `ZoomLevel` is a real component parameter
|
|
- normal positive zoom values used in this receiver page must be treated as factors in the live UI flow here (`1.5` = `150%`)
|
|
- `ActivePageIndex` is read-only information and is not a page-navigation parameter
|
|
- `ZoomLevelChanged` must not be used in this workspace because the current installed component surface does not expose a matching incoming parameter on `DxPdfViewer`
|
|
|
|
### 10. Recovery progress update
|
|
|
|
The first functional recovery step is zoom restoration.
|
|
|
|
Implemented direction:
|
|
|
|
- bind receiver zoom state to `DxPdfViewer.ZoomLevel`
|
|
- remove competing custom JS ctrl+wheel zoom logic
|
|
- keep overlay redraw pipeline in place
|
|
- avoid `ZoomLevelChanged` usage because it is not available on the installed `25.2.3` `DxPdfViewer` surface
|
|
|
|
Expected outcome after this step:
|
|
|
|
- built-in DevExpress zoom UI can be used without the runtime parameter exception
|
|
- custom zoom duplication is removed
|
|
|
|
### 11. Additional findings after first zoom integration attempt
|
|
|
|
After the first live integration attempt against the installed `DevExpress.Blazor.PdfViewer` package version `25.2.3`, one important runtime behavior was confirmed:
|
|
|
|
- the `DxPdfViewer.ZoomLevel` value must be treated as a zoom factor for normal positive values in the live UI flow used here
|
|
- in practice, `1.5` corresponds to `150%`
|
|
- using `150` as the bound value causes the DevExpress viewer UI to display `15000%`
|
|
|
|
This means the receiver page must keep two different zoom representations:
|
|
|
|
- `_currentZoom` = receiver custom workflow percentage view, for example `150`
|
|
- `_viewerZoomLevel` = DevExpress viewer value, for example `1.5`
|
|
|
|
### 12. Current UI decision for zoom controls
|
|
|
|
The custom toolbar zoom section in `pdf-toolbar__zoom-section` is no longer considered desirable.
|
|
|
|
Current decision:
|
|
|
|
- remove the custom zoom buttons and slider from the receiver toolbar
|
|
- rely on the built-in DevExpress PDF Viewer zoom UI instead
|
|
- keep `ZoomLevelChanged` synchronization so overlay redraw logic can still react to viewer zoom changes
|
|
|
|
Reason:
|
|
|
|
- DevExpress already provides zoom UX
|
|
- duplicate zoom controls create confusing UX and unit mismatch risks
|
|
- the built-in viewer zoom UI is a better source of truth for the current zoom state
|
|
|
|
### 13. Current UI decision for thumbnails
|
|
|
|
The custom thumbnail sidebar is still kept for now.
|
|
|
|
Reason:
|
|
|
|
- current receiver workflow depends on custom thumbnail shell behavior
|
|
- width persistence and resizable splitter are already implemented in the custom sidebar
|
|
- no verified built-in `DxPdfViewer` thumbnail sidebar integration surface has yet been confirmed from the currently inspected API surface
|
|
|
|
So the current decision is:
|
|
|
|
- keep custom thumbnail sidebar for now
|
|
- revisit possible DevExpress-native thumbnail navigation later only if it supports the required receiver workflow behavior
|
|
|
|
---
|
|
|
|
## Files Most Likely Relevant For Future Work
|
|
|
|
### Main page
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage.razor`
|
|
|
|
### JavaScript
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/js/pdf-viewer.js`
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/js/receiver-signature.js`
|
|
|
|
### CSS
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/envelope-viewer.css`
|
|
|
|
### Data/auth services
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverAuthorizationService.cs`
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverPageDataService.cs`
|
|
|
|
---
|
|
|
|
## Summary
|
|
The active receiver document experience is currently a `PDF.js`-based, server-orchestrated, overlay-signing workflow in `EnvelopeGenerator.Server`.
|
|
|
|
It supports:
|
|
- single-page PDF display
|
|
- page navigation
|
|
- zoom
|
|
- thumbnails
|
|
- receiver-specific signature placeholders
|
|
- actual signature overlay placement
|
|
- fast signature navigation across pages
|
|
- overlay rescaling/repositioning during zoom
|
|
- signature capture via draw/text/image
|
|
- cached signature reuse
|
|
|
|
The current migration goal is to move this experience to `DxPdfViewer` in `EnvelopeReceiverPage.razor` without losing any of the capabilities listed above.
|
|
|
|
Any future changes in this area should be made with the assumption that this is a custom receiver signing experience whose rendering engine may change, but whose functional behavior must remain intact; it is not a basic document embed and not a direct PDF stamping pipeline.
|