Add MgLazyLoadContent, grid layout refactor, tests
- Introduced MgLazyLoadContent component with JS observer for lazy rendering of heavy content. - Refactored MgGridBase for user-specific, customizable layout persistence using JS interop. - Improved MgGridInfoPanel caching and state batching for performance. - Updated PDF viewer to use lazy loading for better UX. - Added AyCode.Blazor.Components.Tests project with bUnit/MSTest grid layout tests. - Updated solution/project files and removed obsolete code. - Minor UI and JS module loading improvements.
This commit is contained in:
parent
271868b4d5
commit
324f171377
|
|
@ -1,40 +1,28 @@
|
||||||
using AyCode.Blazor.Components.Components.Grids;
|
using AyCode.Blazor.Components.Components.Grids;
|
||||||
using AyCode.Core.Extensions;
|
|
||||||
using AyCode.Core.Interfaces;
|
using AyCode.Core.Interfaces;
|
||||||
using AyCode.Interfaces.Entities;
|
|
||||||
using AyCode.Utils.Extensions;
|
|
||||||
using DevExpress.Blazor;
|
using DevExpress.Blazor;
|
||||||
using FruitBank.Common.Models;
|
using FruitBank.Common.Models;
|
||||||
using FruitBankHybrid.Shared.Services.Loggers;
|
using FruitBankHybrid.Shared.Services.Loggers;
|
||||||
using FruitBankHybrid.Shared.Services.SignalRs;
|
using FruitBankHybrid.Shared.Services.SignalRs;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.JSInterop;
|
|
||||||
|
|
||||||
namespace FruitBankHybrid.Shared.Components.Grids;
|
namespace FruitBankHybrid.Shared.Components.Grids;
|
||||||
|
|
||||||
//var a = new GridDevExtremeDataSource(DataSource.AsQueryable().Where(x=>x.IsMeasurable));
|
|
||||||
|
|
||||||
public class FruitBankGridBase<TDataItem> : MgGridBase<SignalRDataSourceObservable<TDataItem>, TDataItem, int, LoggerClient> where TDataItem : class, IId<int>
|
public class FruitBankGridBase<TDataItem> : MgGridBase<SignalRDataSourceObservable<TDataItem>, TDataItem, int, LoggerClient> where TDataItem : class, IId<int>
|
||||||
{
|
{
|
||||||
[Inject] public required LoggedInModel LoggedInModel { get; set; }
|
[Inject] public required LoggedInModel LoggedInModel { get; set; }
|
||||||
[Inject] public required IJSRuntime JSRuntime { get; set; }
|
|
||||||
|
|
||||||
//[Parameter] public bool IsMasterGrid { get; set; } = false;
|
|
||||||
[Parameter] public string AutoSaveLayoutName { get; set; }
|
|
||||||
|
|
||||||
private bool _isFirstInitializeParameterCore;
|
private bool _isFirstInitializeParameterCore;
|
||||||
private bool _isFirstInitializeParameters;
|
private bool _isFirstInitializeParameters;
|
||||||
public bool PreRendered { get; set; }
|
public bool PreRendered { get; set; }
|
||||||
|
|
||||||
//public virtual Task ReloadDataFromDb(bool forceReload = false)
|
/// <summary>
|
||||||
//{
|
/// Override to provide the logged-in user's ID for layout storage
|
||||||
// throw new NotImplementedException();
|
/// </summary>
|
||||||
//}
|
protected override int GetLayoutUserId() => LoggedInModel.CustomerDto?.Id ?? 0;
|
||||||
|
|
||||||
protected void OnCustomizeElement(GridCustomizeElementEventArgs e)
|
protected void OnCustomizeElement(GridCustomizeElementEventArgs e)
|
||||||
{
|
{
|
||||||
//if (!IsMasterGrid) e.CssClass = "hideDetailButton";
|
|
||||||
|
|
||||||
if (IsMasterGrid && e.ElementType == GridElementType.DataRow && e.VisibleIndex % 2 == 1 && !e.Grid.IsRowSelected(e.VisibleIndex) && !e.Grid.IsRowFocused(e.VisibleIndex))
|
if (IsMasterGrid && e.ElementType == GridElementType.DataRow && e.VisibleIndex % 2 == 1 && !e.Grid.IsRowSelected(e.VisibleIndex) && !e.Grid.IsRowFocused(e.VisibleIndex))
|
||||||
{
|
{
|
||||||
e.CssClass = " alt-item";
|
e.CssClass = " alt-item";
|
||||||
|
|
@ -48,28 +36,21 @@ public class FruitBankGridBase<TDataItem> : MgGridBase<SignalRDataSourceObservab
|
||||||
if (e.ElementType == GridElementType.HeaderCell)
|
if (e.ElementType == GridElementType.HeaderCell)
|
||||||
{
|
{
|
||||||
e.Style = "background-color: #E6E6E6;";
|
e.Style = "background-color: #E6E6E6;";
|
||||||
//e.CssClass = "header-bold";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task SetParametersAsyncCore(ParameterView parameters)
|
protected override async Task SetParametersAsyncCore(ParameterView parameters)
|
||||||
{
|
{
|
||||||
await base.SetParametersAsyncCore(parameters);
|
await base.SetParametersAsyncCore(parameters);
|
||||||
|
|
||||||
if (!_isFirstInitializeParameterCore)
|
if (!_isFirstInitializeParameterCore)
|
||||||
{
|
{
|
||||||
//if (typeof(TDataItem) is IId<Guid> || typeof(TDataItem) is IId<int>)
|
|
||||||
KeyFieldName = "Id";
|
KeyFieldName = "Id";
|
||||||
|
|
||||||
//base.DataItemDeleting = EventCallback.Factory.Create<GridDataItemDeletingEventArgs>(this, OnItemDeleting);
|
|
||||||
//base.EditModelSaving = EventCallback.Factory.Create<GridEditModelSavingEventArgs>(this, OnItemSaving);
|
|
||||||
|
|
||||||
CustomizeElement += OnCustomizeElement;
|
CustomizeElement += OnCustomizeElement;
|
||||||
|
|
||||||
_isFirstInitializeParameterCore = true;
|
_isFirstInitializeParameterCore = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
base.OnParametersSet();
|
base.OnParametersSet();
|
||||||
|
|
@ -102,14 +83,8 @@ public class FruitBankGridBase<TDataItem> : MgGridBase<SignalRDataSourceObservab
|
||||||
EditMode = GridEditMode.EditRow;
|
EditMode = GridEditMode.EditRow;
|
||||||
FocusedRowEnabled = true;
|
FocusedRowEnabled = true;
|
||||||
ColumnResizeMode = GridColumnResizeMode.NextColumn;
|
ColumnResizeMode = GridColumnResizeMode.NextColumn;
|
||||||
//VirtualScrollingEnabled = IsMasterGrid;
|
|
||||||
PageSizeSelectorVisible = true;
|
PageSizeSelectorVisible = true;
|
||||||
|
|
||||||
if (AutoSaveLayoutName.IsNullOrWhiteSpace()) AutoSaveLayoutName = $"Grid{typeof(TDataItem).Name}";
|
|
||||||
|
|
||||||
LayoutAutoLoading = Grid_LayoutAutoLoading;
|
|
||||||
LayoutAutoSaving = Grid_LayoutAutoSaving;
|
|
||||||
|
|
||||||
_isFirstInitializeParameters = true;
|
_isFirstInitializeParameters = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,55 +92,5 @@ public class FruitBankGridBase<TDataItem> : MgGridBase<SignalRDataSourceObservab
|
||||||
protected override void OnAfterRender(bool firstRender)
|
protected override void OnAfterRender(bool firstRender)
|
||||||
{
|
{
|
||||||
base.OnAfterRender(firstRender);
|
base.OnAfterRender(firstRender);
|
||||||
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
//PreRendered = true;
|
|
||||||
//StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task Grid_LayoutAutoLoading(GridPersistentLayoutEventArgs e)
|
|
||||||
{
|
|
||||||
var masterDetailName = IsMasterGrid ? "Master" : ParentDataItem!.GetType().Name;
|
|
||||||
e.Layout = await LoadLayoutFromLocalStorageAsync($"{AutoSaveLayoutName}_{masterDetailName}_AutoSave_{LoggedInModel.CustomerDto?.Id ?? 0}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Grid_LayoutAutoSaving(GridPersistentLayoutEventArgs e)
|
|
||||||
{
|
|
||||||
var masterDetailName = IsMasterGrid ? "Master" : ParentDataItem!.GetType().Name;
|
|
||||||
await SaveLayoutToLocalStorageAsync(e.Layout, $"{AutoSaveLayoutName}_{masterDetailName}_AutoSave_{LoggedInModel.CustomerDto?.Id ?? 0}");
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task<GridPersistentLayout?> LoadLayoutFromLocalStorageAsync(string localStorageKey)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var json = await JSRuntime.InvokeAsync<string>("localStorage.getItem", localStorageKey);
|
|
||||||
|
|
||||||
if (!json.IsNullOrWhiteSpace()) return json.JsonTo<GridPersistentLayout>();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Mute exceptions for the server prerender stage
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
async Task SaveLayoutToLocalStorageAsync(GridPersistentLayout layout, string localStorageKey)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var json = layout.ToJson();
|
|
||||||
await JSRuntime.InvokeVoidAsync("localStorage.setItem", localStorageKey, json);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Mute exceptions for the server prerender stage
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//public abstract class FruitBankObservableGridBase<TDataItem> : MgGridBase<SignalRDataSourceObservable<TDataItem>, TDataItem, int, LoggerClient> where TDataItem : class, IId<int>
|
|
||||||
//{ }
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
@using AyCode.Blazor.Components.Components.Grids
|
@using AyCode.Blazor.Components.Components.Grids
|
||||||
|
@using AyCode.Blazor.Components.Components
|
||||||
@using DevExpress.Blazor
|
@using DevExpress.Blazor
|
||||||
@using FruitBank.Common.Entities
|
@using FruitBank.Common.Entities
|
||||||
@using System.IO
|
@using System.IO
|
||||||
|
|
@ -37,7 +38,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th>Név a dokumentumon</th>
|
<th>Név a dokumentumon</th>
|
||||||
<th>Termék neve</th>
|
<th>Termék neve</th>
|
||||||
<th class="text-end">Rakl.</th>
|
<th>Rakl.</th>
|
||||||
<th class="text-end">Menny.</th>
|
<th class="text-end">Menny.</th>
|
||||||
<th class="text-end">Net.súly</th>
|
<th class="text-end">Net.súly</th>
|
||||||
<th class="text-end">Br.súly</th>
|
<th class="text-end">Br.súly</th>
|
||||||
|
|
@ -68,8 +69,14 @@
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div id="pdfContainer" style="width: 100%; height: 800px; overflow-y: auto; margin-top: 30px;">
|
<MgLazyLoadContent @ref="_lazyContentRef"
|
||||||
|
MinHeight="800px"
|
||||||
|
RootMargin="50px"
|
||||||
|
OnContentVisible="OnPdfContainerVisibleAsync"
|
||||||
|
ContainerStyle="margin-top: 30px;">
|
||||||
|
<div id="pdfContainer" style="width: 100%; height: 800px; overflow-y: auto;">
|
||||||
</div>
|
</div>
|
||||||
|
</MgLazyLoadContent>
|
||||||
}
|
}
|
||||||
</AfterColumnsTemplate>
|
</AfterColumnsTemplate>
|
||||||
|
|
||||||
|
|
@ -90,11 +97,35 @@
|
||||||
"3_BP-30M35_20251113_163816.pdf"
|
"3_BP-30M35_20251113_163816.pdf"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private MgLazyLoadContent? _lazyContentRef;
|
||||||
|
private string? _currentPdfToRender;
|
||||||
|
private string _randomPdf;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
base.OnInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task OnDataItemChangedAsync(object? dataItem)
|
private async Task OnDataItemChangedAsync(object? dataItem)
|
||||||
{
|
{
|
||||||
// Véletlenszerű PDF kiválasztása minden sor váltáskor
|
// Store the PDF to render
|
||||||
var randomPdf = _pdfFiles[Random.Shared.Next(_pdfFiles.Length)];
|
_randomPdf = _pdfFiles[Random.Shared.Next(_pdfFiles.Length)];
|
||||||
var pdfUrls = new[] { $"_content/FruitBankHybrid.Shared/uploads/{randomPdf}" };
|
_currentPdfToRender = $"_content/FruitBankHybrid.Shared/uploads/{_randomPdf}";
|
||||||
|
|
||||||
|
// If MgLazyLoadContent is already visible, render the PDF immediately
|
||||||
|
if (_lazyContentRef is { IsVisible: true })
|
||||||
|
{
|
||||||
|
await _lazyContentRef.TriggerContentVisibleAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnPdfContainerVisibleAsync()
|
||||||
|
{
|
||||||
|
// Render PDF when container becomes visible OR when data changes
|
||||||
|
if (!string.IsNullOrEmpty(_currentPdfToRender))
|
||||||
|
{
|
||||||
|
var pdfUrls = new[] { _currentPdfToRender };
|
||||||
await JS.InvokeVoidAsync("pdfViewer.renderPdfs", "pdfContainer", pdfUrls);
|
await JS.InvokeVoidAsync("pdfViewer.renderPdfs", "pdfContainer", pdfUrls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
<body class="dxbl-theme-fluent">
|
<body class="dxbl-theme-fluent">
|
||||||
<Routes @rendermode="InteractiveWebAssembly" />
|
<Routes @rendermode="InteractiveWebAssembly" />
|
||||||
<script src="_content/AyCode.Blazor.Components/js/mgGridInfoPanel.js"></script>
|
<script src="_content/AyCode.Blazor.Components/js/mgGridInfoPanel.js"></script>
|
||||||
|
<script src="_content/AyCode.Blazor.Components/js/lazyContentObserver.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 18
|
# Visual Studio Version 18
|
||||||
VisualStudioVersion = 18.0.11222.15 d18.0
|
VisualStudioVersion = 18.0.11222.15
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruitBankHybrid", "FruitBankHybrid\FruitBankHybrid.csproj", "{85ADEDE3-C271-47DF-B273-2EDB32792CEF}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruitBankHybrid", "FruitBankHybrid\FruitBankHybrid.csproj", "{85ADEDE3-C271-47DF-B273-2EDB32792CEF}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -31,9 +31,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Maui.Core", "..\..\.
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
..\..\..\Aycode\Source\AyCode.Blazor\AyCode.Blazor.Components\Components\Grids\MgGridSignalRDataSource.txt = ..\..\..\Aycode\Source\AyCode.Blazor\AyCode.Blazor.Components\Components\Grids\MgGridSignalRDataSource.txt
|
||||||
SqlSchemaCompare_Dev_to_Prod.scmp = SqlSchemaCompare_Dev_to_Prod.scmp
|
SqlSchemaCompare_Dev_to_Prod.scmp = SqlSchemaCompare_Dev_to_Prod.scmp
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Blazor.Components.Tests", "..\..\..\Aycode\Source\AyCode.Blazor\AyCode.Blazor.Components.Tests\AyCode.Blazor.Components.Tests.csproj", "{5EFD44C6-DC9E-FEB8-F229-3E07C2E224FA}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -73,7 +76,6 @@ Global
|
||||||
{4E4E4917-1CA3-A7D7-40A8-A24A08673EC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{4E4E4917-1CA3-A7D7-40A8-A24A08673EC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{4E4E4917-1CA3-A7D7-40A8-A24A08673EC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{4E4E4917-1CA3-A7D7-40A8-A24A08673EC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{4E4E4917-1CA3-A7D7-40A8-A24A08673EC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{4E4E4917-1CA3-A7D7-40A8-A24A08673EC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{4E4E4917-1CA3-A7D7-40A8-A24A08673EC1}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{5CE8B5A7-5390-61E4-33B3-FA5F0B75A168}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{5CE8B5A7-5390-61E4-33B3-FA5F0B75A168}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{5CE8B5A7-5390-61E4-33B3-FA5F0B75A168}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{5CE8B5A7-5390-61E4-33B3-FA5F0B75A168}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{5CE8B5A7-5390-61E4-33B3-FA5F0B75A168}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{5CE8B5A7-5390-61E4-33B3-FA5F0B75A168}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
|
@ -96,6 +98,9 @@ Global
|
||||||
{F7C67754-A59C-C355-2A10-C614F0585627}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{F7C67754-A59C-C355-2A10-C614F0585627}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{F7C67754-A59C-C355-2A10-C614F0585627}.Release|Any CPU.Build.0 = Release|Any CPU
|
{F7C67754-A59C-C355-2A10-C614F0585627}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{F7C67754-A59C-C355-2A10-C614F0585627}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
{F7C67754-A59C-C355-2A10-C614F0585627}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
|
{5EFD44C6-DC9E-FEB8-F229-3E07C2E224FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{5EFD44C6-DC9E-FEB8-F229-3E07C2E224FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{5EFD44C6-DC9E-FEB8-F229-3E07C2E224FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
<script src="_framework/blazor.webview.js" autostart="false"></script>
|
<script src="_framework/blazor.webview.js" autostart="false"></script>
|
||||||
<script src="_content/AyCode.Blazor.Components/js/mgGridInfoPanel.js"></script>
|
<script src="_content/AyCode.Blazor.Components/js/mgGridInfoPanel.js"></script>
|
||||||
|
<script src="_content/AyCode.Blazor.Components/js/lazyContentObserver.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue