-
-
Notifications
You must be signed in to change notification settings - Fork 7
feat(PdfReader): add OnPageChangedAsync parameter #715
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| // Website: https://www.blazor.zone or https://argozhang.github.io/ | ||
|
|
||
| using Microsoft.AspNetCore.Components; | ||
| using System.Globalization; | ||
|
|
||
| namespace BootstrapBlazor.Components; | ||
|
|
||
|
|
@@ -13,38 +14,43 @@ namespace BootstrapBlazor.Components; | |
| public partial class PdfReader | ||
| { | ||
| /// <summary> | ||
| /// 获得/设置 PDF 文档路径 | ||
| /// 获得/设置 <see cref="PdfReaderOptions"/> 配置项实例 | ||
| /// </summary> | ||
| [Parameter] | ||
| public string? Url { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// 获得/设置 PDF 组件高度 默认 600px | ||
| /// </summary> | ||
| [Parameter] | ||
| public string? ViewHeight { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// 获得/设置 是否适配当前页面宽度 默认 false | ||
| /// </summary> | ||
| [Parameter] | ||
| public bool IsFitToPage { get; set; } | ||
| [NotNull] | ||
| public PdfReaderOptions? Options { get; set; } | ||
|
|
||
| private string? ClassString => CssBuilder.Default("bb-pdf-reader") | ||
| .AddClassFromAttributes(AdditionalAttributes) | ||
| .Build(); | ||
|
|
||
| private string? StyleString => CssBuilder.Default() | ||
| .AddClass($"--bb-pdf-view-height: {ViewHeight};", !string.IsNullOrEmpty(ViewHeight)) | ||
| .AddClass($"--bb-pdf-view-height: {Options.ViewHeight};", !string.IsNullOrEmpty(Options.ViewHeight)) | ||
| .AddClassFromAttributes(AdditionalAttributes) | ||
| .Build(); | ||
|
|
||
| private string? ViewBodyString => CssBuilder.Default("bb-view-body") | ||
| .AddClass("fit-page", IsFitToPage) | ||
| .AddClass("fit-page", Options.IsFitToPage) | ||
| .Build(); | ||
|
|
||
| private string? _docTitle; | ||
| private bool _isFitToPage; | ||
| private uint _currentPage; | ||
| private string? _url; | ||
|
|
||
| private string CurrentPageString | ||
| { | ||
| get => Options.CurrentPage.ToString(CultureInfo.InvariantCulture); | ||
| set => SetCurrentPage(value); | ||
| } | ||
|
|
||
| private void SetCurrentPage(string value) | ||
| { | ||
| if (uint.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var num)) | ||
| { | ||
| Options.CurrentPage = num; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// <inheritdoc/> | ||
|
|
@@ -53,7 +59,13 @@ protected override void OnParametersSet() | |
| { | ||
| base.OnParametersSet(); | ||
|
|
||
| _docTitle = Path.GetFileName(Url); | ||
| Options ??= new PdfReaderOptions(); | ||
|
|
||
| if (Options.CurrentPage == 0) | ||
| { | ||
| Options.CurrentPage = 1; | ||
| } | ||
| _docTitle = Path.GetFileName(Options.Url); | ||
| } | ||
|
|
||
| /// <summary> | ||
|
|
@@ -67,38 +79,57 @@ protected override async Task OnAfterRenderAsync(bool firstRender) | |
|
|
||
| if (firstRender) | ||
| { | ||
| _isFitToPage = IsFitToPage; | ||
| _isFitToPage = Options.IsFitToPage; | ||
| _currentPage = Options.CurrentPage; | ||
| _url = Options.Url; | ||
| } | ||
|
|
||
| if (_isFitToPage != IsFitToPage) | ||
| if (_url != Options.Url) | ||
| { | ||
| _isFitToPage = IsFitToPage; | ||
| await TriggerFit(IsFitToPage ? "fitToPage" : "fitToWidth"); | ||
| _url = Options.Url; | ||
| await InvokeInitAsync(); | ||
| } | ||
|
|
||
| if (_isFitToPage != Options.IsFitToPage) | ||
| { | ||
| _isFitToPage = Options.IsFitToPage; | ||
| await TriggerFit(_isFitToPage ? "fitToPage" : "fitToWidth"); | ||
| } | ||
| if (_currentPage != Options.CurrentPage) | ||
| { | ||
| _currentPage = Options.CurrentPage; | ||
| await NavigateToPageAsync(_currentPage); | ||
| } | ||
|
Comment on lines
+98
to
102
|
||
| } | ||
|
|
||
| /// <summary> | ||
| /// <inheritdoc/> | ||
| /// </summary> | ||
| /// <returns></returns> | ||
| protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new { Url, IsFitToPage }); | ||
| protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new | ||
| { | ||
| Options.Url, | ||
| Options.IsFitToPage, | ||
| TriggerPagesInit = Options.OnInitAsync != null, | ||
| TriggerPageChanged = Options.OnPageChangedAsync != null | ||
| }); | ||
|
|
||
| /// <summary> | ||
| /// 跳转到指定页码方法 | ||
| /// </summary> | ||
| /// <param name="pageNumber"></param> | ||
| /// <returns></returns> | ||
| public Task NavigateToPageAsync(int pageNumber) => InvokeVoidAsync("navigateToPage", Id, pageNumber); | ||
| public Task NavigateToPageAsync(uint pageNumber) => InvokeVoidAsync("navigateToPage", Id, pageNumber); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): CurrentPage/initial page support is only applied after changes, so the initial page value on first load is effectively ignored. Because Suggested implementation: TriggerPagesInit = Options.OnInitAsync != null,
TriggerPageChanged = Options.OnPageChangedAsync != null
});
// Honor an initial non-default CurrentPage by navigating after initialization completes
if (Options is not null && Options.CurrentPage > 1)
{
await NavigateToPageAsync((uint)Options.CurrentPage);
}I assumed this block is inside an async initialization method (likely OnAfterRenderAsync or a similar lifecycle/initialization method) where:
You may need to:
|
||
|
|
||
| /// <summary> | ||
| /// 适应页面宽度 | ||
| /// </summary> | ||
| public void FitToPage() => IsFitToPage = true; | ||
| public void FitToPage() => Options.IsFitToPage = true; | ||
|
|
||
| /// <summary> | ||
| /// 适应文档宽度 | ||
| /// </summary> | ||
| public void FitToWidth() => IsFitToPage = false; | ||
| public void FitToWidth() => Options.IsFitToPage = false; | ||
|
|
||
| /// <summary> | ||
| /// 旋转页面方法 | ||
|
|
@@ -125,18 +156,27 @@ public async Task RotateRight() | |
| /// </summary> | ||
| /// <returns></returns> | ||
| [JSInvokable] | ||
| public Task PagesInit() | ||
| public async Task PagesInit(int pagesCount) | ||
| { | ||
| return Task.CompletedTask; | ||
| if (Options.OnInitAsync != null) | ||
| { | ||
| await Options.OnInitAsync(pagesCount); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// 改变页码时回调方法 | ||
| /// </summary> | ||
| /// <returns></returns> | ||
| [JSInvokable] | ||
| public Task PageChanging() | ||
| public async Task PageChanged(uint pageIndex) | ||
| { | ||
| return Task.CompletedTask; | ||
| _currentPage = pageIndex; | ||
| Options.CurrentPage = pageIndex; | ||
|
|
||
| if (Options.OnPageChangedAsync != null) | ||
| { | ||
| await Options.OnPageChangedAsync(pageIndex); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,25 +8,28 @@ if (pdfjsLib != null) { | |
| } | ||
|
|
||
| export async function init(id, invoke, options) { | ||
| await addLink('./_content/BootstrapBlazor.PdfReader/css/pdf_viewer.css'); | ||
|
|
||
| const el = document.getElementById(id); | ||
| if (el === null) { | ||
| return; | ||
| } | ||
|
|
||
| await addLink('./_content/BootstrapBlazor.PdfReader/css/pdf.css'); | ||
| if (options.url === null) { | ||
| return; | ||
| } | ||
|
|
||
| const loadingTask = pdfjsLib.getDocument(options); | ||
| loadingTask.onProgress = function (progressData) { | ||
| console.log(progressData.loaded, progressData.total); | ||
| }; | ||
|
|
||
| // handle password only when required (optional password support) | ||
| loadingTask.onPassword = function (updatePassword, reason) { | ||
| if (reason === pdfjsLib.PasswordResponses.NEED_PASSWORD) { | ||
| // only prompt if PDF actually requires password | ||
| const password = prompt("This PDF is password protected. Enter password:"); | ||
| updatePassword(password); | ||
| } else if (reason === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) { | ||
| } | ||
| else if (reason === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) { | ||
| const password = prompt("Incorrect password. Please try again:"); | ||
| updatePassword(password); | ||
| } | ||
|
|
@@ -39,43 +42,11 @@ export async function init(id, invoke, options) { | |
| eventBus | ||
| }); | ||
|
|
||
| addEventListener(pdfViewer, eventBus, invoke, options); | ||
|
|
||
| eventBus.on("pagesinit", function () { | ||
| if (options.isFitToPage) { | ||
| pdfViewer.currentScaleValue = "page-width"; | ||
| } | ||
| else { | ||
| pdfViewer.currentScaleValue = 1.0; | ||
| } | ||
| }); | ||
|
|
||
| // handle the promise | ||
| const pdfDocument = await loadingTask.promise; | ||
| pdfViewer.setDocument(pdfDocument); | ||
|
|
||
| // pdfDocument.then(function (doc) { | ||
| // pdf.pdfDoc = doc; | ||
| // pdf.pagesCount = doc.numPages; | ||
| // renderPage(pdf, pdf.pageNum); | ||
|
|
||
| // // notify .NET side that document is loaded | ||
| // invoke.invokeMethodAsync('DocumentLoaded', { | ||
| // pagesCount: pdf.pagesCount, | ||
| // pageNumber: pdf.pageNum | ||
| // }); | ||
| // }) | ||
| // .catch(function (error) { | ||
| // console.error("PDF loading error:", error); | ||
|
|
||
| // // handle password exceptions specifically | ||
| // if (error.name === "PasswordException") { | ||
| // console.error("Password required but not provided"); | ||
| // } | ||
|
|
||
| // // notify .NET side that document loading failed | ||
| // invoke.invokeMethodAsync('DocumentLoadError', error.message); | ||
| // }); | ||
|
|
||
| Data.set(id, pdfViewer); | ||
| } | ||
|
|
||
|
|
@@ -106,6 +77,46 @@ export function dispose(id) { | |
| Data.get(id); | ||
| } | ||
|
|
||
| const addEventListener = (pdfViewer, eventBus, invoke, options) => { | ||
| eventBus.on("pagesinit", async () => { | ||
| if (options.isFitToPage) { | ||
| pdfViewer.currentScaleValue = "page-width"; | ||
| } | ||
| else { | ||
| pdfViewer.currentScaleValue = 1.0; | ||
| } | ||
|
|
||
| const el = pdfViewer.container.parentElement; | ||
| const numPages = pdfViewer.pagesCount; | ||
| const countEl = el.querySelector(".bb-view-pagesCount"); | ||
| if (countEl) { | ||
| countEl.innerHTML = numPages; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. security (javascript.browser.security.insecure-document-method): User controlled data in methods like Source: opengrep There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. security (javascript.browser.security.insecure-innerhtml): User controlled data in a Source: opengrep |
||
| } | ||
|
|
||
| const toolbarEl = el.querySelector(".bb-view-toolbar"); | ||
| if (toolbarEl) { | ||
| toolbarEl.classList.remove("init"); | ||
| } | ||
|
|
||
| if (options.triggerPagesInit === true) { | ||
| await invoke.invokeMethodAsync("pagesInit", numPages); | ||
| } | ||
| }); | ||
|
|
||
| eventBus.on("pagechanging", async evt => { | ||
| const page = evt.pageNumber; | ||
| const el = evt.source.container.parentElement; | ||
| const pageNumberEl = el.querySelector(".bb-view-num"); | ||
| if (pageNumberEl) { | ||
| pageNumberEl.value = page; | ||
| } | ||
|
|
||
| if (options.triggerPageChanged === true) { | ||
| await invoke.invokeMethodAsync("pageChanged", page); | ||
| } | ||
| }, true); | ||
| } | ||
|
|
||
| function getCanvas(item) { | ||
| if (isDomSupported() && typeof item === 'string') { | ||
| item = document.getElementById(item); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
SetCurrentPagemethod silently ignores invalid input without providing feedback. Consider adding validation error handling or at least logging when the parse fails, so developers and users understand why page navigation might not work as expected.