Skip to content

feat(PdfViewer): add PdfViewer component#450

Merged
ArgoZhang merged 4 commits intomasterfrom
refactor-viewer
Jun 5, 2025
Merged

feat(PdfViewer): add PdfViewer component#450
ArgoZhang merged 4 commits intomasterfrom
refactor-viewer

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Jun 5, 2025

Link issues

fixes #449

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Add a new PdfViewer component to display PDF files in Blazor applications, including the necessary JS module, CSS assets, and project registration in the solution.

New Features:

  • Introduce PdfViewer Blazor component with Url and Height parameters
  • Add JavaScript module to initialize and load PDFs into an iframe
  • Include CSS for PdfViewer container and iframe styling
  • Add BootstrapBlazor.PdfViewer project and register it in the solution

@bb-auto bb-auto Bot added the enhancement New feature or request label Jun 5, 2025
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Jun 5, 2025

Reviewer's Guide

This PR introduces a new PdfViewer component by adding a dedicated project, implementing server-side rendering logic in C#, integrating a JS module for client-side PDF loading, and supplying accompanying CSS and Razor markup.

Sequence Diagram for PdfViewer Initialization and Initial PDF Load

sequenceDiagram
    participant LC as "Component Lifecycle"
    participant CSharp as "PdfViewer.razor.cs"
    participant JS as "PdfViewer.razor.js"
    participant DOM as "Browser DOM"

    LC->>+CSharp: Initialize Component
    CSharp->>CSharp: InvokeInitAsync()
    CSharp->>JS: Invoke JS: init(Id)
    activate JS
    JS->>JS: addLink("./_content/BootstrapBlazor.PdfViewer/pdf-viewer.css")
    JS->>DOM: Get element by Id
    JS->>DOM: Get 'data-bb-url' attribute (url)
    JS->>JS: loadPdf(Id, url)
    activate JS
    alt url is provided
        JS->>DOM: createFrame()
        DOM-->>JS: iframeElement
        JS->>DOM: el.appendChild(iframeElement)
        JS->>DOM: iframeElement.src = url (PDF renders)
    else url is not provided
        JS->>DOM: el.innerHTML = '' (Clear content)
    end
    deactivate JS
    deactivate JS
Loading

Sequence Diagram for PdfViewer URL Update

sequenceDiagram
    participant UserAction as "User/Application Action"
    participant CSharp as "PdfViewer.razor.cs"
    participant JS as "PdfViewer.razor.js"
    participant IFrame as "Browser DOM (iframe)"

    UserAction->>+CSharp: Update 'Url' Parameter for PdfViewer
    CSharp->>CSharp: OnAfterRenderAsync(firstRender=false)
    alt Url has changed (_url != Url)
        CSharp->>CSharp: _url = new Url
        CSharp->>JS: Invoke JS: loadPdf(Id, newUrl)
        activate JS
        JS->>IFrame: Get existing or create new iframe
        JS->>IFrame: iframe.src = newUrl (PDF re-renders)
        deactivate JS
    end
    deactivate CSharp
Loading

File-Level Changes

Change Details Files
Project configuration for PdfViewer component
  • Added new PdfViewer project file
  • Updated solution to include the project
BootstrapBlazor.Extensions.sln
src/components/BootstrapBlazor.PdfViewer/BootstrapBlazor.PdfViewer.csproj
Backend implementation of PdfViewer component
  • Declared Url and Height parameters
  • Built CSS class and style strings via CssBuilder
  • Implemented OnAfterRenderAsync for initial load and URL change detection
  • Overrode InvokeInitAsync to call JS init
src/components/BootstrapBlazor.PdfViewer/PdfViewer.razor.cs
Razor markup with module loader
  • Created PdfViewer.razor with container div, id, data attributes, and CSS/style binding
  • Added JSModuleAutoLoader attribute for client JS integration
  • Inserted _Imports.razor for namespace and Web events
src/components/BootstrapBlazor.PdfViewer/PdfViewer.razor
src/components/BootstrapBlazor.PdfViewer/_Imports.razor
Client-side PDF loading logic
  • Wrote init function to load CSS and initialize viewer state
  • Built loadPdf to create/destroy iframe and set src
  • Utilized shared Data module for per-instance storage
src/components/BootstrapBlazor.PdfViewer/PdfViewer.razor.js
PDF viewer CSS styling
  • Defined container styles with variable height
  • Styled iframe to fill container
src/components/BootstrapBlazor.PdfViewer/wwwroot/pdf-viewer.css

Assessment against linked issues

Issue Objective Addressed Explanation
#449 Implement a PdfViewer component for Blazor applications.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@bb-auto bb-auto Bot added this to the v9.2.0 milestone Jun 5, 2025
@ArgoZhang ArgoZhang merged commit 61b5230 into master Jun 5, 2025
1 check passed
@ArgoZhang ArgoZhang deleted the refactor-viewer branch June 5, 2025 11:02
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @ArgoZhang - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟡 General issues: 5 issues found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Complexity: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@inherits BootstrapModuleComponentBase
@attribute [JSModuleAutoLoader("./_content/BootstrapBlazor.PdfViewer/PdfViewer.razor.js", JSObjectReference = true, AutoInvokeDispose = false)]

<div @attributes="AdditionalAttributes" id="@Id" data-bb-url="@Url" class="@ClassString" style="@StyleString"></div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Merge user-defined inline styles with component-generated styles

Extract the style attribute from AdditionalAttributes and merge it with StyleString to ensure user-defined styles are preserved.

Comment on lines +20 to +21
const viewer = frame || createFrame(el);
viewer.src = url;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Cache the iframe reference in pdfViewer

Assign the new iframe to pdfViewer.frame to avoid creating multiple iframes when updating the URL.

loadPdf(id, url);
}

export function loadPdf(id, url) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Guard against missing pdfViewer in Data

Check if pdfViewer is defined before destructuring to avoid runtime errors.

Comment on lines +6 to +8
.bb-pdf-viewer {
width: 100%;
height: 100%;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider adding border: none; to iframe CSS

This will prevent default borders from appearing in some browsers, ensuring a consistent, borderless PDF viewer.

/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Implement disposal to clean up resources

Override DisposeAsync to remove the injected CSS link, clear the container, and delete the entry from Data to prevent memory leaks.

Suggested implementation:

    /// <summary>
    /// <inheritdoc/>
    /// </summary>
    /// <returns></returns>
    protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id);

    /// <inheritdoc/>
    public override async ValueTask DisposeAsync()
    {
        // Remove the injected CSS link
        await InvokeVoidAsync("removePdfViewerCss", Id);

        // Clear the container
        await InvokeVoidAsync("clearPdfViewerContainer", Id);

        // Remove the entry from Data to prevent memory leaks
        Data.Remove(Id);

        await base.DisposeAsync();
    }
}
  • You must implement the corresponding JavaScript functions removePdfViewerCss and clearPdfViewerContainer if they do not already exist.
  • Ensure that Data is accessible and is the correct dictionary holding component state; adjust the removal logic if needed.
  • If the base class does not have an async Dispose pattern, adjust to match the correct disposal pattern for your component base.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(PdfViewer): add PdfViewer component

1 participant