Compare commits

...

2 Commits

Author SHA1 Message Date
7aeaba7c12 Add EnvelopeGenerator.ReceiverUI project and UI components
Introduced a new Razor Components-based web application, `EnvelopeGenerator.ReceiverUI`, to the solution. This includes:

- Added `EnvelopeGenerator.ReceiverUI` project to `EnvelopeGenerator.sln` with Debug and Release configurations.
- Created `EnvelopeGenerator.ReceiverUI.csproj` targeting `.NET 8.0` with nullable and implicit usings enabled.
- Added Razor components (`App.razor`, `Routes.razor`, `MainLayout.razor`, `NavMenu.razor`, `Counter.razor`, `Error.razor`, `Home.razor`, `Weather.razor`) for layout, navigation, and pages.
- Included styles (`MainLayout.razor.css`, `NavMenu.razor.css`, `app.css`) and assets (`favicon.png`, `bootstrap.min.css`).
- Configured logging and allowed hosts in `appsettings.json` and `appsettings.Development.json`.
- Added `Program.cs` to configure and run the application.
- Added `launchSettings.json` for development and debugging profiles.

These changes enable a modern, interactive web interface with Bootstrap styling and development-friendly configurations.
2025-12-05 09:08:57 +01:00
2301a81a1c init receiver ui with react 2025-12-04 18:11:47 +01:00
51 changed files with 13267 additions and 0 deletions

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="EnvelopeGenerator.ReceiverUI.styles.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />
</head>
<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
</body>
</html>

View File

@ -0,0 +1,23 @@
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

View File

@ -0,0 +1,96 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

View File

@ -0,0 +1,30 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">EnvelopeGenerator.ReceiverUI</a>
</div>
</div>
<input type="checkbox" title="Navigation menu" class="navbar-toggler" />
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="weather">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
</NavLink>
</div>
</nav>
</div>

View File

@ -0,0 +1,105 @@
.navbar-toggler {
appearance: none;
cursor: pointer;
width: 3.5rem;
height: 2.5rem;
color: white;
position: absolute;
top: 0.5rem;
right: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
}
.navbar-toggler:checked {
background-color: rgba(255, 255, 255, 0.5);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep .nav-link {
color: #d7d7d7;
background: none;
border: none;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
width: 100%;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep .nav-link:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.nav-scrollable {
display: none;
}
.navbar-toggler:checked ~ .nav-scrollable {
display: block;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.nav-scrollable {
/* Never collapse the sidebar for wide screens */
display: block;
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}

View File

@ -0,0 +1,19 @@
@page "/counter"
@rendermode InteractiveServer
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}

View File

@ -0,0 +1,36 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@code{
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}

View File

@ -0,0 +1,7 @@
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.

View File

@ -0,0 +1,64 @@
@page "/weather"
@attribute [StreamRendering]
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
await Task.Delay(500);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}

View File

@ -0,0 +1,6 @@
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>

View File

@ -0,0 +1,10 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using EnvelopeGenerator.ReceiverUI
@using EnvelopeGenerator.ReceiverUI.Components

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,27 @@
using EnvelopeGenerator.ReceiverUI.Components;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();

View File

@ -0,0 +1,38 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:26087",
"sslPort": 44331
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5134",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7124;http://localhost:5134",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1,51 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
a, .btn-link {
color: #006bb7;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
.content {
padding-top: 1.1rem;
}
h1:focus {
outline: none;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid #e50000;
}
.validation-message {
color: #e50000;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.darker-border-checkbox.form-check-input {
border-color: #929292;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,52 @@
{
"homepage": "https://slavik0329.github.io/pdf-sign",
"name": "pdf-sign",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"annotpdf": "^1.0.12",
"dayjs": "^1.9.6",
"pdf-lib": "^1.12.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-draggable": "^4.4.3",
"react-dropzone": "^11.2.4",
"react-icons": "^4.1.0",
"react-pdf": "^5.0.0",
"react-scripts": "4.0.1",
"react-signature-canvas": "^1.0.3",
"web-vitals": "^0.2.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"gh-pages": "^3.1.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Finally, a simple way to put your signature on a PDF for free."
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>PDF-sign</title>
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600;800&display=swap" rel="stylesheet">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,10 @@
body {
font-family: "Open Sans";
}
input:focus,
select:focus,
textarea:focus,
button:focus {
outline: none;
}

View File

@ -0,0 +1,278 @@
import "./App.css";
import { useRef, useState } from "react";
import Drop from "./Drop";
import { Document, Page, pdfjs } from "react-pdf";
import { PDFDocument, rgb } from "pdf-lib";
import { blobToURL } from "./utils/Utils";
import PagingControl from "./components/PagingControl";
import { AddSigDialog } from "./components/AddSigDialog";
import { Header } from "./Header";
import { BigButton } from "./components/BigButton";
import DraggableSignature from "./components/DraggableSignature";
import DraggableText from "./components/DraggableText";
import dayjs from "dayjs";
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
function downloadURI(uri, name) {
var link = document.createElement("a");
link.download = name;
link.href = uri;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
function App() {
const styles = {
container: {
maxWidth: 900,
margin: "0 auto",
},
sigBlock: {
display: "inline-block",
border: "1px solid #000",
},
documentBlock: {
maxWidth: 800,
margin: "20px auto",
marginTop: 8,
border: "1px solid #999",
},
controls: {
maxWidth: 800,
margin: "0 auto",
marginTop: 8,
},
};
const [pdf, setPdf] = useState(null);
const [autoDate, setAutoDate] = useState(true);
const [signatureURL, setSignatureURL] = useState(null);
const [position, setPosition] = useState(null);
const [signatureDialogVisible, setSignatureDialogVisible] = useState(false);
const [textInputVisible, setTextInputVisible] = useState(false);
const [pageNum, setPageNum] = useState(0);
const [totalPages, setTotalPages] = useState(0);
const [pageDetails, setPageDetails] = useState(null);
const documentRef = useRef(null);
return (
<div>
<Header />
<div style={styles.container}>
{signatureDialogVisible ? (
<AddSigDialog
autoDate={autoDate}
setAutoDate={setAutoDate}
onClose={() => setSignatureDialogVisible(false)}
onConfirm={(url) => {
setSignatureURL(url);
setSignatureDialogVisible(false);
}}
/>
) : null}
{!pdf ? (
<Drop
onLoaded={async (files) => {
const URL = await blobToURL(files[0]);
setPdf(URL);
}}
/>
) : null}
{pdf ? (
<div>
<div style={styles.controls}>
{!signatureURL ? (
<BigButton
marginRight={8}
title={"Add signature"}
onClick={() => setSignatureDialogVisible(true)}
/>
) : null}
<BigButton
marginRight={8}
title={"Add Date"}
onClick={() => setTextInputVisible("date")}
/>
<BigButton
marginRight={8}
title={"Add Text"}
onClick={() => setTextInputVisible(true)}
/>
<BigButton
marginRight={8}
title={"Reset"}
onClick={() => {
setTextInputVisible(false);
setSignatureDialogVisible(false);
setSignatureURL(null);
setPdf(null);
setTotalPages(0);
setPageNum(0);
setPageDetails(null);
}}
/>
{pdf ? (
<BigButton
marginRight={8}
inverted={true}
title={"Download"}
onClick={() => {
downloadURI(pdf, "file.pdf");
}}
/>
) : null}
</div>
<div ref={documentRef} style={styles.documentBlock}>
{textInputVisible ? (
<DraggableText
initialText={
textInputVisible === "date"
? dayjs().format("M/d/YYYY")
: null
}
onCancel={() => setTextInputVisible(false)}
onEnd={setPosition}
onSet={async (text) => {
const { originalHeight, originalWidth } = pageDetails;
const scale = originalWidth / documentRef.current.clientWidth;
const y =
documentRef.current.clientHeight -
(position.y +
(12 * scale) -
position.offsetY -
documentRef.current.offsetTop);
const x =
position.x -
166 -
position.offsetX -
documentRef.current.offsetLeft;
// new XY in relation to actual document size
const newY =
(y * originalHeight) / documentRef.current.clientHeight;
const newX =
(x * originalWidth) / documentRef.current.clientWidth;
const pdfDoc = await PDFDocument.load(pdf);
const pages = pdfDoc.getPages();
const firstPage = pages[pageNum];
firstPage.drawText(text, {
x: newX,
y: newY,
size: 20 * scale,
});
const pdfBytes = await pdfDoc.save();
const blob = new Blob([new Uint8Array(pdfBytes)]);
const URL = await blobToURL(blob);
setPdf(URL);
setPosition(null);
setTextInputVisible(false);
}}
/>
) : null}
{signatureURL ? (
<DraggableSignature
url={signatureURL}
onCancel={() => {
setSignatureURL(null);
}}
onSet={async () => {
const { originalHeight, originalWidth } = pageDetails;
const scale = originalWidth / documentRef.current.clientWidth;
const y =
documentRef.current.clientHeight -
(position.y -
position.offsetY +
64 -
documentRef.current.offsetTop);
const x =
position.x -
160 -
position.offsetX -
documentRef.current.offsetLeft;
// new XY in relation to actual document size
const newY =
(y * originalHeight) / documentRef.current.clientHeight;
const newX =
(x * originalWidth) / documentRef.current.clientWidth;
const pdfDoc = await PDFDocument.load(pdf);
const pages = pdfDoc.getPages();
const firstPage = pages[pageNum];
const pngImage = await pdfDoc.embedPng(signatureURL);
const pngDims = pngImage.scale( scale * .3);
firstPage.drawImage(pngImage, {
x: newX,
y: newY,
width: pngDims.width,
height: pngDims.height,
});
if (autoDate) {
firstPage.drawText(
`Signed ${dayjs().format(
"M/d/YYYY HH:mm:ss ZZ"
)}`,
{
x: newX,
y: newY - 10,
size: 14 * scale,
color: rgb(0.074, 0.545, 0.262),
}
);
}
const pdfBytes = await pdfDoc.save();
const blob = new Blob([new Uint8Array(pdfBytes)]);
const URL = await blobToURL(blob);
setPdf(URL);
setPosition(null);
setSignatureURL(null);
}}
onEnd={setPosition}
/>
) : null}
<Document
file={pdf}
onLoadSuccess={(data) => {
setTotalPages(data.numPages);
}}
>
<Page
pageNumber={pageNum + 1}
width={800}
height={1200}
onLoadSuccess={(data) => {
setPageDetails(data);
}}
/>
</Document>
</div>
<PagingControl
pageNum={pageNum}
setPageNum={setPageNum}
totalPages={totalPages}
/>
</div>
) : null}
</div>
</div>
);
}
export default App;

View File

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@ -0,0 +1,37 @@
import React, { useCallback } from "react";
import { useDropzone } from "react-dropzone";
import { cleanBorder, primary45 } from "./utils/colors";
export default function Drop({ onLoaded }) {
const styles = {
container: {
textAlign: "center",
border: cleanBorder,
padding: 20,
marginTop: 12,
color: primary45,
fontSize: 18,
fontWeight: 600,
borderRadius: 4,
userSelect: "none",
outline: 0,
cursor: "pointer",
},
};
const onDrop = useCallback((acceptedFiles) => {
onLoaded(acceptedFiles);
// Do something with the files
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: "application/pdf",
});
return (
<div {...getRootProps()} style={styles.container}>
<input {...getInputProps()} />
{isDragActive ? <p>Drop a PDF here</p> : <p>Drag a PDF here</p>}
</div>
);
}

View File

@ -0,0 +1,15 @@
import {primary45} from "./utils/colors";
export function Header() {
const styles = {
container: {
backgroundColor: primary45,
color: '#FFF',
padding: 12,
fontWeight: 600,
}
}
return <div style={styles.container}>
<div>Open PDF Sign</div>
</div>
}

View File

@ -0,0 +1,77 @@
import { Dialog } from "./Dialog";
import SignatureCanvas from "react-signature-canvas";
import { ConfirmOrCancel } from "./ConfirmOrCancel";
import { primary45 } from "../utils/colors";
import { useRef } from "react";
export function AddSigDialog({ onConfirm, onClose, autoDate, setAutoDate }) {
const sigRef = useRef(null);
const styles = {
sigContainer: {
display: "flex",
justifyContent: "center",
},
sigBlock: {
display: "inline-block",
border: `1px solid ${primary45}`,
},
instructions: {
display: "flex",
justifyContent: "space-between",
textAlign: "center",
color: primary45,
marginTop: 8,
width: 600,
alignSelf: "center",
},
instructionsContainer: {
display: "flex",
justifyContent: "center",
},
};
return (
<Dialog
isVisible={true}
title={"Add signature"}
body={
<div style={styles.container}>
<div style={styles.sigContainer}>
<div style={styles.sigBlock}>
<SignatureCanvas
velocityFilterWeight={1}
ref={sigRef}
canvasProps={{
width: "600",
height: 200,
className: "sigCanvas",
}}
/>
</div>
</div>
<div style={styles.instructionsContainer}>
<div style={styles.instructions}>
<div>
Auto date/time{" "}
<input
type={"checkbox"}
checked={autoDate}
onChange={(e) => setAutoDate(e.target.checked)}
/>
</div>
<div>Draw your signature above</div>
</div>
</div>
<ConfirmOrCancel
onCancel={onClose}
onConfirm={() => {
const sigURL = sigRef.current.toDataURL();
onConfirm(sigURL);
}}
/>
</div>
}
/>
);
}

View File

@ -0,0 +1,81 @@
import React from "react";
import { primary45 } from "../utils/colors";
import useHover from "../hooks/useHover";
export function BigButton({
title,
onClick,
inverted,
fullWidth,
customFillColor,
customWhiteColor,
style,
noHover,
id,
small,
disabled,
marginRight,
}) {
const [hoverRef, isHovered] = useHover();
let fillColor = customFillColor || primary45;
const whiteColor = customWhiteColor || "#FFF";
let initialBg = null;
let hoverBg = fillColor;
let initialColor = fillColor;
let hoverColor = whiteColor;
if (inverted) {
initialBg = fillColor;
hoverBg = null;
initialColor = whiteColor;
hoverColor = fillColor;
}
if (disabled) {
initialBg = "#ddd";
hoverBg = "#ddd";
fillColor = "#ddd";
}
const styles = {
container: {
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
width: fullWidth ? "100%" : null,
backgroundColor: isHovered && !noHover ? hoverBg : initialBg,
color:
isHovered && !noHover && !disabled
? hoverColor
: disabled
? "#999"
: initialColor,
borderRadius: 4,
padding: small ? "2px 4px" : "6px 8px",
fontSize: small ? 14 : null,
border: `1px solid ${fillColor}`,
cursor: !disabled ? "pointer" : null,
userSelect: "none",
boxSizing: "border-box",
marginRight,
},
};
return (
<div
id={id}
ref={hoverRef}
style={{ ...styles.container, ...style }}
onClick={() => {
if (!disabled) {
onClick();
}
}}
>
{title}
</div>
);
}

View File

@ -0,0 +1,37 @@
import { BigButton } from "./BigButton";
import React from "react";
export function ConfirmOrCancel({
onCancel,
onConfirm,
confirmTitle = "Confirm",
leftBlock,
hideCancel,
disabled
}) {
const styles = {
actions: {
display: "flex",
justifyContent: "space-between",
},
cancel: {
marginRight: 8,
},
};
return (
<div style={styles.actions}>
<div>{leftBlock}</div>
<div>
{!hideCancel ? (
<BigButton
title={"Cancel"}
style={styles.cancel}
onClick={onCancel}
/>
) : null}
<BigButton title={confirmTitle} inverted={true} onClick={onConfirm} disabled={disabled}/>
</div>
</div>
);
}

View File

@ -0,0 +1,56 @@
import React from 'react';
import {primary45} from '../utils/colors';
import {FaTimes} from 'react-icons/fa';
import {Modal} from './Modal';
export function Dialog({
isVisible,
body,
onClose,
title,
noPadding,
backgroundColor,
positionTop,
style,
}) {
if (!isVisible) {
return null;
}
const styles = {
header: {
backgroundColor: primary45,
color: '#FFF',
padding: 8,
fontSize: 14,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
},
body: {
padding: noPadding ? 0 : 14,
backgroundColor: backgroundColor ? backgroundColor : '#FFF',
},
xIcon: {
cursor: 'pointer',
},
};
return (
<Modal onClose={onClose} isVisible={isVisible} positionTop={positionTop} style={style}>
<div style={styles.container}>
<div style={styles.header}>
<div>{title}</div>
<FaTimes
color={'#FFF'}
size={16}
style={styles.xIcon}
className={'dialogClose'}
onClick={onClose}
/>
</div>
<div style={styles.body}>{body}</div>
</div>
</Modal>
);
}

View File

@ -0,0 +1,37 @@
import Draggable from "react-draggable";
import {BigButton} from "./BigButton"; // The default
import {FaCheck, FaTimes} from 'react-icons/fa'
import {cleanBorder, errorColor, goodColor, primary45} from "../utils/colors";
export default function DraggableSignature({ url, onEnd, onSet, onCancel }) {
const styles = {
container: {
position: 'absolute',
zIndex: 100000,
border: `2px solid ${primary45}`,
},
controls: {
position: 'absolute',
right: 0,
display: 'inline-block',
backgroundColor: primary45,
// borderRadius: 4,
},
smallButton: {
display: 'inline-block',
cursor: 'pointer',
padding: 4,
}
}
return (
<Draggable onStop={onEnd}>
<div style={styles.container}>
<div style={styles.controls}>
<div style={styles.smallButton} onClick={onSet}><FaCheck color={goodColor}/></div>
<div style={styles.smallButton} onClick={onCancel}><FaTimes color={errorColor}/></div>
</div>
<img src={url} width={200} style={styles.img} draggable={false} />
</div>
</Draggable>
);
}

View File

@ -0,0 +1,66 @@
import Draggable from "react-draggable";
import { FaCheck, FaTimes } from "react-icons/fa";
import { cleanBorder, errorColor, goodColor, primary45 } from "../utils/colors";
import { useState, useEffect, useRef } from "react";
export default function DraggableText({ onEnd, onSet, onCancel, initialText }) {
const [text, setText] = useState("Text");
const inputRef = useRef(null);
useEffect(() => {
if (initialText) {
setText(initialText)
} else {
inputRef.current.focus();
inputRef.current.select()
}
}, [])
const styles = {
container: {
position: "absolute",
zIndex: 100000,
border: `2px solid ${primary45}`,
},
controls: {
position: "absolute",
right: 0,
display: "inline-block",
backgroundColor: primary45,
// borderRadius: 4,
},
smallButton: {
display: "inline-block",
cursor: "pointer",
padding: 4,
},
input: {
border: 0,
fontSize: 20,
padding: 3,
backgroundColor: 'rgba(0,0,0,0)',
cursor: 'move'
}
};
return (
<Draggable onStop={onEnd}>
<div style={styles.container}>
<div style={styles.controls}>
<div style={styles.smallButton} onClick={()=>onSet(text)}>
<FaCheck color={goodColor} />
</div>
<div style={styles.smallButton} onClick={onCancel}>
<FaTimes color={errorColor} />
</div>
</div>
<input
ref={inputRef}
style={styles.input}
value={text}
placeholder={'Text'}
onChange={(e) => setText(e.target.value)}
/>
</div>
</Draggable>
);
}

View File

@ -0,0 +1,43 @@
import React from 'react';
import {primary45} from '../utils/colors';
import {useIsSmallScreen} from '../hooks/useIsSmallScreen';
export function Modal({onClose, children, isVisible, style, positionTop}) {
const isSmallScreen = useIsSmallScreen();
const styles = {
container: {
position: isSmallScreen ? 'fixed' : 'absolute',
backgroundColor: '#FFF',
border: `1px solid ${primary45}`,
borderRadius: 4,
top: positionTop ? positionTop : isSmallScreen ? 60 : 150,
left: '50%',
transform: 'translateX(-50%)',
width: '94%',
fontFamily: 'Open Sans',
zIndex: 10000,
boxShadow: '0 0px 14px hsla(0, 0%, 0%, 0.2)',
},
background: {
position: 'fixed',
width: '100%',
height: '100%',
top: 0,
left: 0,
backgroundColor: '#00000033',
zIndex: 5000,
},
};
if (!isVisible) {
return null;
}
return (
<div style={styles.outer}>
<div style={styles.background} onClick={onClose} />
<div style={{...styles.container, ...style}}>{children}</div>
</div>
);
}

View File

@ -0,0 +1,40 @@
import { BigButton } from "./BigButton";
import {primary45} from "../utils/colors";
export default function PagingControl({totalPages, pageNum, setPageNum}) {
const styles= {
container: {
marginTop: 8,
marginBottom: 8,
},
inlineFlex: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
},
pageInfo: {
padding: 8,
color: primary45,
fontSize: 14,
}
}
return (
<div style={styles.container}>
<div style={styles.inlineFlex}>
<BigButton
title={"<"}
onClick={() => setPageNum(pageNum - 1)}
disabled={pageNum-1===-1}
/>
<div style={styles.pageInfo}>
Page: {pageNum + 1}/{totalPages}
</div>
<BigButton
title={">"}
onClick={() => setPageNum(pageNum + 1)}
disabled={pageNum+1>totalPages-1}
/>
</div>
</div>
);
}

View File

@ -0,0 +1,29 @@
import React, {useCallback, useRef, useState} from 'react';
export default function useHover() {
const [value, setValue] = useState(false);
const handleMouseOver = useCallback(() => setValue(true), []);
const handleMouseOut = useCallback(() => setValue(false), []);
const ref = useRef();
const callbackRef = useCallback(
(node) => {
if (ref.current) {
ref.current.removeEventListener('mouseenter', handleMouseOver);
ref.current.removeEventListener('mouseleave', handleMouseOut);
}
ref.current = node;
if (ref.current) {
ref.current.addEventListener('mouseenter', handleMouseOver);
ref.current.addEventListener('mouseleave', handleMouseOut);
}
},
[handleMouseOver, handleMouseOut],
);
return [callbackRef, value];
}

View File

@ -0,0 +1,6 @@
import {useWindowSize} from './useWindowSize';
export function useIsSmallScreen() {
const windowSize = useWindowSize();
return windowSize.width < 600;
}

View File

@ -0,0 +1,29 @@
import React, {useState, useEffect} from 'react';
export function useWindowSize() {
const isClient = typeof window === 'object';
function getSize() {
return {
width: isClient ? window.innerWidth : undefined,
height: isClient ? window.innerHeight : undefined,
};
}
const [windowSize, setWindowSize] = useState(getSize);
useEffect(() => {
if (!isClient) {
return false;
}
function handleResize() {
setWindowSize(getSize());
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []); // Empty array ensures that effect is only run on mount and unmount
return windowSize;
}

View File

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

View File

@ -0,0 +1,27 @@
export function blobToURL(blob) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function () {
const base64data = reader.result;
resolve(base64data);
};
});
}
export async function fileToBlob(file, handleUpdate) {
const { content, size } = file;
let chunks = [];
let i = 0;
const totalCount = Math.round(size / 250000);
for await (const chunk of content) {
if (handleUpdate) {
handleUpdate(i, totalCount);
}
chunks.push(chunk);
i++;
}
// eslint-disable-next-line no-undef
return new Blob(chunks);
}

View File

@ -0,0 +1,28 @@
export const primary = '#2b6284';
export const primary2 = '#ecf4f9';
export const primary3 = '#9fc7e0';
export const primary35 = '#97bace';
export const primary4 = 'hsl(204,38%,55%)';
export const primary45 = 'hsl(218,49%,66%)';
export const primary46 = '#6778cb';
export const primary15 = 'rgb(241 249 255)';
export const primary5 = '#3881ad';
export const primary6 = '#132b3a';
// export const primary = '#666';
// export const primary2 = '#EEE';
// export const primary3 = '#CCC';
// export const primary4 = '#AAA';
// export const primary5 = '#888';
// export const primary6 = '#333';
export const primary16 = 'hsl(208 100% 96% / 1)';
export const errorColor = '#ef6565';
export const lightErrorColor = '#ef9c9c';
export const goodColor = '#53c171';
export const cleanBorder = '1px solid rgb(208, 227, 239)';
export const lightBorder = 'hsl(203 51% 80% / 1)';

File diff suppressed because it is too large Load Diff

View File

@ -37,6 +37,8 @@ Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "EnvelopeGenerator.Form", "E
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.PdfEditor", "EnvelopeGenerator.PdfEditor\EnvelopeGenerator.PdfEditor.csproj", "{211619F5-AE25-4BA5-A552-BACAFE0632D3}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.PdfEditor", "EnvelopeGenerator.PdfEditor\EnvelopeGenerator.PdfEditor.csproj", "{211619F5-AE25-4BA5-A552-BACAFE0632D3}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.ReceiverUI", "EnvelopeGenerator.ReceiverUI\EnvelopeGenerator.ReceiverUI.csproj", "{34AF8679-E8AF-4AB6-B861-53ED241B1228}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -91,6 +93,10 @@ Global
{211619F5-AE25-4BA5-A552-BACAFE0632D3}.Debug|Any CPU.Build.0 = Debug|Any CPU {211619F5-AE25-4BA5-A552-BACAFE0632D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{211619F5-AE25-4BA5-A552-BACAFE0632D3}.Release|Any CPU.ActiveCfg = Release|Any CPU {211619F5-AE25-4BA5-A552-BACAFE0632D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{211619F5-AE25-4BA5-A552-BACAFE0632D3}.Release|Any CPU.Build.0 = Release|Any CPU {211619F5-AE25-4BA5-A552-BACAFE0632D3}.Release|Any CPU.Build.0 = Release|Any CPU
{34AF8679-E8AF-4AB6-B861-53ED241B1228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{34AF8679-E8AF-4AB6-B861-53ED241B1228}.Debug|Any CPU.Build.0 = Debug|Any CPU
{34AF8679-E8AF-4AB6-B861-53ED241B1228}.Release|Any CPU.ActiveCfg = Release|Any CPU
{34AF8679-E8AF-4AB6-B861-53ED241B1228}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -111,6 +117,7 @@ Global
{A9F9B431-BB9B-49B8-9E2C-0703634A653A} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB} {A9F9B431-BB9B-49B8-9E2C-0703634A653A} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
{6D56C01F-D6CB-4D8A-BD3D-4FD34326998C} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB} {6D56C01F-D6CB-4D8A-BD3D-4FD34326998C} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
{211619F5-AE25-4BA5-A552-BACAFE0632D3} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB} {211619F5-AE25-4BA5-A552-BACAFE0632D3} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
{34AF8679-E8AF-4AB6-B861-53ED241B1228} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {73E60370-756D-45AD-A19A-C40A02DACCC7} SolutionGuid = {73E60370-756D-45AD-A19A-C40A02DACCC7}