diff --git a/src/components/BootstrapBlazor.PdfReader/BootstrapBlazor.PdfReader.csproj b/src/components/BootstrapBlazor.PdfReader/BootstrapBlazor.PdfReader.csproj index a734f066..68ebbfba 100644 --- a/src/components/BootstrapBlazor.PdfReader/BootstrapBlazor.PdfReader.csproj +++ b/src/components/BootstrapBlazor.PdfReader/BootstrapBlazor.PdfReader.csproj @@ -1,7 +1,7 @@  - 10.0.1-beta04 + 10.0.1-beta05 diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor index 080df140..b4410503 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor +++ b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor @@ -8,7 +8,7 @@
- @_docTitle + @_docTitle
/
@@ -39,7 +39,15 @@
} -
-
+
+ @if (Options.EnableThumbnails) + { +
+ } +
+
+
+
+
diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs index db874128..fc5e16c1 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs +++ b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs @@ -8,7 +8,7 @@ namespace BootstrapBlazor.Components; /// -/// Blazor Pdf Reader PDF 阅读器 组件 +/// Blazor Pdf Reader PDF 阅读器 组件 /// [JSModuleAutoLoader("./_content/BootstrapBlazor.PdfReader/PdfReader.razor.js", JSObjectReference = true)] public partial class PdfReader @@ -77,14 +77,12 @@ private void SetCurrentScale(string value) } else if (float.TryParse(value.TrimEnd("%"), out var v)) { - if (v > 500) + v = v switch { - v = 500; - } - else if (v < 25) - { - v = 25; - } + > 500 => 500, + < 25 => 25, + _ => v + }; Options.CurrentScale = v.ToString(CultureInfo.InvariantCulture); } @@ -171,7 +169,9 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { Options.Url, Options.IsFitToPage, - TriggerPagesInit = Options.OnInitAsync != null, + Options.EnableThumbnails, + TriggerPagesInit = Options.OnPagesInitAsync != null, + TriggerPagesLoaded = Options.OnPagesLoadedAsync != null, TriggerPageChanged = Options.OnPageChangedAsync != null, TriggerTowPagesOnViewChanged = Options.OnTwoPagesOneViewAsync != null }); @@ -220,9 +220,22 @@ public async Task RotateRight() [JSInvokable] public async Task PagesInit(int pagesCount) { - if (Options.OnInitAsync != null) + if (Options.OnPagesInitAsync != null) + { + await Options.OnPagesInitAsync(pagesCount); + } + } + + /// + /// 页面加载完毕时回调方法 + /// + /// + [JSInvokable] + public async Task PagesLoaded(int pagesCount) + { + if (Options.OnPagesLoadedAsync != null) { - await Options.OnInitAsync(pagesCount); + await Options.OnPagesLoadedAsync(pagesCount); } } diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.css b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.css index 8af80838..9a930336 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.css +++ b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.css @@ -43,10 +43,6 @@ color: #6c757d; } -.bb-view-bar { - margin-inline-end: 2rem; -} - .bb-view-subject { white-space: nowrap; display: block; @@ -60,8 +56,10 @@ min-width: 0; width: 1%; display: flex; + flex-wrap: nowrap; align-items: center; justify-content: center; + overflow: hidden; } .bb-view-body.fit-page .bb-view-fit-page { @@ -106,10 +104,56 @@ padding: 0 1rem; } +.bb-view-main { + display: flex; + width: 100%; + height: var(--bb-pdf-view-height); + overflow: hidden; +} + +.bb-view-thumbnails { + flex-basis: 0; + overflow: auto; + background-color: #28292a; + padding: 1rem 0; + transition: flex-basis .3s linear; +} + + .bb-view-thumbnails.show { + flex-basis: 300px; + } + +::deep .bb-view-thumbnail-item { + display: block; + text-align: center; +} + + ::deep .bb-view-thumbnail-item:not(:last-child) { + margin-block-end: 1rem; + } + + ::deep .bb-view-thumbnail-item.active img { + border: 2px solid #0d6efd; + } + + ::deep .bb-view-thumbnail-item img { + width: 128px; + padding: 1px; + cursor: pointer; + border: 2px solid #28292a; + } + +.bb-view-content { + position: relative; + flex: 1 1 auto; + min-width: 0; + width: 1%; + height: var(--bb-pdf-view-height); +} + .bb-view-container { overflow: auto; position: absolute; background-color: #000; - width: 100%; - height: var(--bb-pdf-view-height); + inset: 0; } diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js index 1f3be938..e4a4d7bb 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js +++ b/src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js @@ -82,23 +82,9 @@ export function navigateToPage(id, pageNumber) { } export function scale(id, scale) { - const { el, pdfViewer } = Data.get(id); + const { pdfViewer } = Data.get(id); if (pdfViewer) { pdfViewer.currentScaleValue = scale / 100; - - const minus = el.querySelector(".bb-page-minus"); - const plus = el.querySelector(".bb-page-plus"); - - if (scale === "25") { - minus.classList.add("disabled"); - } - else if (scale === "500") { - plus.classList.add("disabled"); - } - else { - minus.classList.remove("disabled"); - plus.classList.remove("disabled"); - } } } @@ -139,6 +125,44 @@ const addEventListener = (el, pdfViewer, eventBus, invoke, options) => { } }); + eventBus.on("pagesloaded", async e => { + if (options.enableThumbnails) { + const thumbnailsContainer = el.querySelector(".bb-view-thumbnails"); + pdfViewer.getPagesOverview().map(async (p, i) => { + const item = document.createElement("div"); + item.classList.add("bb-view-thumbnail-item"); + if (pdfViewer.currentPageNumber === i + 1) { + item.classList.add("active"); + } + item.setAttribute("data-bb-page", `${i + 1}`); + thumbnailsContainer.appendChild(item); + + const page = await pdfViewer.pdfDocument.getPage(i + 1); + const canvas = await makeThumb(page); + const img = document.createElement("img"); + img.src = canvas.toDataURL(); + item.appendChild(img); + }); + + EventHandler.on(thumbnailsContainer, "click", ".bb-view-thumbnail-item", e => { + const active = thumbnailsContainer.querySelector('.active'); + if (active) { + active.classList.remove('active'); + } + + const item = e.delegateTarget; + item.classList.add("active"); + + const index = parseInt(item.getAttribute("data-bb-page")) || 1; + pdfViewer.currentPageNumber = index; + }) + } + + if (options.triggerPagesLoaded === true) { + await invoke.invokeMethodAsync("PagesLoaded", e.pagesCount); + } + }) + eventBus.on("pagechanging", async evt => { const page = evt.pageNumber; const pageNumberEl = el.querySelector(".bb-view-num"); @@ -146,6 +170,18 @@ const addEventListener = (el, pdfViewer, eventBus, invoke, options) => { pageNumberEl.value = page; } + if (options.enableThumbnails) { + const thumbnailsContainer = el.querySelector(".bb-view-thumbnails"); + if (thumbnailsContainer) { + const active = thumbnailsContainer.querySelector('.active'); + active.classList.remove('active'); + + const item = thumbnailsContainer.querySelector(`[data-bb-page='${page}']`); + item.classList.add("active"); + item.scrollIntoView({ behavior: 'smooth', block: "nearest", inline: "start" }); + } + } + if (options.triggerPageChanged === true) { await invoke.invokeMethodAsync("pageChanged", page); } @@ -156,7 +192,22 @@ const addEventListener = (el, pdfViewer, eventBus, invoke, options) => { const scaleEl = el.querySelector(".bb-view-scale"); eventBus.on("scalechanging", evt => { - scaleEl.value = `${Math.round(evt.scale * 100, 0)}%`; + const scale = evt.scale * 100; + scaleEl.value = `${Math.round(scale, 0)}%`; + + const minus = el.querySelector(".bb-page-minus"); + const plus = el.querySelector(".bb-page-plus"); + + if (scale === 25) { + minus.classList.add("disabled"); + } + else if (scale === 500) { + plus.classList.add("disabled"); + } + else { + minus.classList.remove("disabled"); + plus.classList.remove("disabled"); + } }) EventHandler.on(minus, "click", e => updateScale(pdfViewer, e.target, -1)); @@ -173,6 +224,14 @@ const addEventListener = (el, pdfViewer, eventBus, invoke, options) => { } }); } + + const thumbnailsToggle = el.querySelector(".bb-view-bar"); + if (thumbnailsToggle) { + EventHandler.on(thumbnailsToggle, "click", e => { + const thumbnailsEl = el.querySelector(".bb-view-thumbnails"); + thumbnailsEl.classList.toggle("show"); + }); + } } const updateScale = (pdfViewer, button, rate) => { @@ -194,6 +253,22 @@ const updateScale = (pdfViewer, button, rate) => { pdfViewer.currentScaleValue = v / 100; } +const makeThumb = async page => { + const outputScale = window.devicePixelRatio || 1; + const vp = page.getViewport({ scale: 1 }); + const canvas = document.createElement("canvas"); + const scaleSize = 1; + canvas.width = vp.width * scaleSize * outputScale; + canvas.height = vp.height * scaleSize * outputScale; + + await page.render({ + canvasContext: canvas.getContext("2d"), + viewport: page.getViewport({ scale: scaleSize * outputScale }) + }).promise; + + return canvas; +} + export function dispose(id) { Data.remove(id); @@ -212,5 +287,15 @@ export function dispose(id) { if (towPagesOneView) { EventHandler.off(towPagesOneView, "click"); } + + const thumbnailsToggle = el.querySelector(".bb-view-bar"); + if (thumbnailsToggle) { + EventHandler.off(thumbnailsToggle, "click"); + } + + const thumbnailsContainer = el.querySelector(".bb-view-thumbnails"); + if (thumbnailsContainer) { + EventHandler.off(thumbnailsContainer, "click"); + } } } diff --git a/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs b/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs index 757dda09..e553eea0 100644 --- a/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs +++ b/src/components/BootstrapBlazor.PdfReader/PdfReaderOptions.cs @@ -14,6 +14,11 @@ public class PdfReaderOptions /// public bool ShowToolbar { get; set; } = true; + /// + /// 获得/设置 是否显示缩略图 默认 true 显示 + /// + public bool EnableThumbnails { get; set; } = true; + /// /// 获得/设置 PDF 文档路径 /// @@ -52,7 +57,12 @@ public class PdfReaderOptions /// /// 页面初始化回调方法 /// - public Func? OnInitAsync { get; set; } + public Func? OnPagesInitAsync { get; set; } + + /// + /// 页面加载完毕回调方法 + /// + public Func? OnPagesLoadedAsync { get; set; } /// /// 页面初始化回调方法