Compare commits

...

1 Commits

Author SHA1 Message Date
MarcinZiabek 8551e1f33f Basic previewer implementation 2022-03-19 03:08:37 +01:00
7 changed files with 385 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
using System.Diagnostics;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using QuestPDF.Previewer;
Document
.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontSize(14));
page.Header()
.Text("Hello PDF!")
.SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(30);
columns.RelativeColumn();
columns.ConstantColumn(75);
columns.ConstantColumn(75);
columns.ConstantColumn(75);
});
table.Header(header =>
{
var textStyle = TextStyle.Default.SemiBold();
header.Cell().Element(DefaultCellStyle).Text("#").Style(textStyle);
header.Cell().Element(DefaultCellStyle).Text("Item name").Style(textStyle);
header.Cell().Element(DefaultCellStyle).AlignRight().Text("Price").Style(textStyle);
header.Cell().Element(DefaultCellStyle).AlignRight().Text("Count").Style(textStyle);
header.Cell().Element(DefaultCellStyle).AlignRight().Text("Total").Style(textStyle);
static IContainer DefaultCellStyle(IContainer container)
{
return container.PaddingBottom(5).BorderBottom(1).PaddingBottom(5);
}
});
foreach(var i in Enumerable.Range(1, 100))
{
var price = Placeholders.Random.Next(100, 999) / 100f;
var count = Placeholders.Random.Next(1, 10);
var total = price * count;
table.Cell().Element(DefaultCellStyle).Text(i);
table.Cell().Element(DefaultCellStyle).Text(Placeholders.Label());
table.Cell().Element(DefaultCellStyle).AlignRight().Text($"{price:N2} $");
table.Cell().Element(DefaultCellStyle).AlignRight().Text(count);
table.Cell().Element(DefaultCellStyle).AlignRight().Text($"{total:N2} $");
static IContainer DefaultCellStyle(IContainer container)
{
return container.PaddingBottom(5).BorderBottom(1).BorderColor(Colors.Grey.Lighten1).PaddingBottom(5);
}
}
});
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Page ");
x.CurrentPageNumber();
x.Span(" of ");
x.TotalPages();
});
});
})
.ShowInPreviewer();
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\QuestPDF.Previewer\QuestPDF.Previewer.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Remove="HotReloadManager.cs" />
</ItemGroup>
</Project>
@@ -0,0 +1,125 @@
using System.Diagnostics;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Previewer;
public static class DocumentPreviewerExtensions
{
public static void ShowInPreviewer(this IDocument document)
{
ArgumentNullException.ThrowIfNull(document);
var builder = WebApplication.CreateBuilder();
var app = builder.Build();
var pdfDocumentCache = GeneratePdf(document);
var refreshFlag = false;
static byte[] GenerateDocumentAboutException(Exception exception)
{
return Document.Create(document =>
{
document.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(1, Unit.Inch);
page.PageColor(Colors.Red.Lighten4);
page.DefaultTextStyle(x => x.FontSize(16));
page.Header()
.BorderBottom(2)
.BorderColor(Colors.Red.Medium)
.PaddingBottom(5)
.Text("Ooops! Something went wrong...").FontSize(28).FontColor(Colors.Red.Medium).Bold();
page.Content().PaddingVertical(20).Column(column =>
{
var currentException = exception;
while (currentException != null)
{
column.Item().Text(exception.GetType().Name).FontSize(20).SemiBold();
column.Item().Text(exception.Message).FontSize(14);
column.Item().PaddingTop(10).Text(exception.StackTrace).FontSize(10).Light();
currentException = currentException.InnerException;
if (currentException != null)
column.Item().PaddingVertical(15).LineHorizontal(2).LineColor(Colors.Red.Medium);
}
});
});
}).GeneratePdf();
}
static byte[] GeneratePdf(IDocument document)
{
try
{
return document.GeneratePdf();
}
catch(Exception exception)
{
return GenerateDocumentAboutException(exception);
}
}
HotReloadManager.OnApplicationChanged += () =>
{
pdfDocumentCache = GeneratePdf(document);
refreshFlag = true;
};
app.MapGet("/", () =>
{
var assembly = Assembly.GetExecutingAssembly();
var resourceName = "QuestPDF.Previewer.index.html";
using var stream = assembly.GetManifestResourceStream(resourceName);
using var reader = new StreamReader(stream);
var result = reader.ReadToEnd();
return Results.Content(result, "text/html");
});
app.MapGet("/render", () =>
{
refreshFlag = false;
return Results.File(pdfDocumentCache, "application/pdf");
});
app.MapGet("/listen", async () =>
{
foreach (var i in Enumerable.Range(0, 1000))
{
await Task.Delay(TimeSpan.FromMilliseconds(100));
if (!refreshFlag)
continue;
return Results.Text("true");
}
return Results.Text("false");
});
app.Lifetime.ApplicationStarted.Register(() =>
{
var openBrowserProcess = new Process()
{
StartInfo = new()
{
UseShellExecute = true,
FileName = app.Urls.First()
}
};
openBrowserProcess.Start();
});
app.Run();
}
}
+9
View File
@@ -0,0 +1,9 @@
using System.Reflection.Metadata;
[assembly: MetadataUpdateHandler(typeof(HotReloadManager))]
internal static class HotReloadManager
{
public static event Action? OnApplicationChanged;
public static void UpdateApplication(Type[]? _) => OnApplicationChanged?.Invoke();
}
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="index.html" />
<EmbeddedResource Include="index.html" />
</ItemGroup>
<ItemGroup>
<Content Remove="appsettings.Development.json" />
<Content Remove="appsettings.json" />
</ItemGroup>
</Project>
+117
View File
@@ -0,0 +1,117 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>QuestPDF Previewer</title>
<style>
html {
background-color: #333;
}
body {
margin: 0;
}
embed {
border: none;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.alert {
position: absolute;
top: 0;
left: 0;
right: 0;
width: 100%;
padding: 8px 16px;
color: #FFFA;
font-family: sans-serif;
font-size: 14px;
line-height: 1.5;
box-shadow: 0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12) !important;
}
#recommended-alert {
background-color: #616161;
}
#recommended-alert .close {
color: #FFF;
cursor: pointer;
}
#connection-issue-alert {
visibility: hidden;
background-color: #B71C1C;
color: white;
}
</style>
</head>
<body>
<embed id="previewer" src="/render#toolbar=0&scrollbar=0" />
<div id="recommended-alert" class="alert">
The recommended browser for this previewer is Microsoft Edge. It does preserve document scroll position when refreshing document, giving better overall experience.
<a class="close" onclick="closeRecommendation()">Click to close</a>
</div>
<div id="connection-issue-alert" class="alert">
Cannot connect to QuestPDF previewer host. Possibly your debugging session has ended. Please close this browser.
</div>
<script>
const recommendationAlert = document.getElementById("recommended-alert");
const connectionIssueAlert = document.getElementById("connection-issue-alert");
function closeRecommendation() {
recommendationAlert.parentElement.removeChild(recommendationAlert);
localStorage.setItem("show-recommendation-alert", 'false');
}
function refreshRecommendation() {
const showRecommendation = localStorage.getItem("show-recommendation-alert");
if (showRecommendation !== 'false')
return;
recommendationAlert.parentElement.removeChild(recommendationAlert);
}
function changeConnectionIssueAlertVisibility(show) {
connectionIssueAlert.style.setProperty("visibility", show ? "visible" : "hidden");
}
async function refreshDocumentLoop() {
window.addEventListener("change", () => reloading = true);
while (true) {
try {
const response = await fetch('/listen');
const value = await response.text();
if (value === "true")
window.location.reload();
changeConnectionIssueAlertVisibility(false);
}
catch {
changeConnectionIssueAlertVisibility(true);
}
}
}
refreshRecommendation();
refreshDocumentLoop();
</script>
</body>
</html>
+12
View File
@@ -8,6 +8,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.UnitTests", "Quest
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Examples", "QuestPDF.Examples\QuestPDF.Examples.csproj", "{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Previewer", "QuestPDF.Previewer\QuestPDF.Previewer.csproj", "{B4D3CBBC-2F38-434F-9347-6E5297737047}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Previewer.Example", "QuestPDF.Previewer.Example\QuestPDF.Previewer.Example.csproj", "{012F05A0-C30B-4112-8EB0-E70CE1D31E57}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -30,5 +34,13 @@ Global
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.Build.0 = Release|Any CPU
{B4D3CBBC-2F38-434F-9347-6E5297737047}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B4D3CBBC-2F38-434F-9347-6E5297737047}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B4D3CBBC-2F38-434F-9347-6E5297737047}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B4D3CBBC-2F38-434F-9347-6E5297737047}.Release|Any CPU.Build.0 = Release|Any CPU
{012F05A0-C30B-4112-8EB0-E70CE1D31E57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{012F05A0-C30B-4112-8EB0-E70CE1D31E57}.Debug|Any CPU.Build.0 = Debug|Any CPU
{012F05A0-C30B-4112-8EB0-E70CE1D31E57}.Release|Any CPU.ActiveCfg = Release|Any CPU
{012F05A0-C30B-4112-8EB0-E70CE1D31E57}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal