|
| 1 | +@page "/" |
| 2 | +@inject IJSRuntime JS |
| 3 | + |
| 4 | +<PageTitle>MiniPdf Converter</PageTitle> |
| 5 | + |
| 6 | +<div class="card"> |
| 7 | + <div class="logo"> |
| 8 | + <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#4f46e5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 9 | + <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> |
| 10 | + <polyline points="14 2 14 8 20 8"/> |
| 11 | + <line x1="12" y1="18" x2="12" y2="12"/> |
| 12 | + <line x1="9" y1="15" x2="15" y2="15"/> |
| 13 | + </svg> |
| 14 | + <h1>MiniPdf</h1> |
| 15 | + </div> |
| 16 | + <p class="subtitle">Convert .docx / .xlsx to PDF in your browser — powered by <a href="https://github.com/mini-software/MiniPdf" target="_blank">MiniPdf</a></p> |
| 17 | + |
| 18 | + <div class="drop-zone @(isDragOver ? "drag-over" : "")" |
| 19 | + @ondragenter="HandleDragEnter" |
| 20 | + @ondragleave="HandleDragLeave" |
| 21 | + @ondragover:preventDefault> |
| 22 | + |
| 23 | + <InputFile OnChange="HandleFileSelected" accept=".docx,.xlsx" /> |
| 24 | + |
| 25 | + @if (selectedFile is null) |
| 26 | + { |
| 27 | + <div class="drop-zone-content"> |
| 28 | + <svg class="upload-icon" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"> |
| 29 | + <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> |
| 30 | + <polyline points="17 8 12 3 7 8"/> |
| 31 | + <line x1="12" y1="3" x2="12" y2="15"/> |
| 32 | + </svg> |
| 33 | + <p><strong>Drop file here</strong> or click to browse</p> |
| 34 | + <div class="badges"> |
| 35 | + <span class="badge badge-docx">.docx</span> |
| 36 | + <span class="badge badge-xlsx">.xlsx</span> |
| 37 | + </div> |
| 38 | + </div> |
| 39 | + } |
| 40 | + else |
| 41 | + { |
| 42 | + <div class="drop-zone-content"> |
| 43 | + <div class="file-info"> |
| 44 | + <div class="file-icon-wrap @FileExt"> |
| 45 | + <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 46 | + <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> |
| 47 | + <polyline points="14 2 14 8 20 8"/> |
| 48 | + </svg> |
| 49 | + </div> |
| 50 | + <div class="file-meta"> |
| 51 | + <div class="file-name">@selectedFile.Name</div> |
| 52 | + <div class="file-size">@FormatSize(selectedFile.Size)</div> |
| 53 | + </div> |
| 54 | + </div> |
| 55 | + </div> |
| 56 | + } |
| 57 | + </div> |
| 58 | + |
| 59 | + <button class="convert-btn" @onclick="ConvertFile" disabled="@(!CanConvert)"> |
| 60 | + @if (isConverting) |
| 61 | + { |
| 62 | + <span class="spinner"></span> |
| 63 | + <span>Converting...</span> |
| 64 | + } |
| 65 | + else |
| 66 | + { |
| 67 | + <span>Convert to PDF</span> |
| 68 | + } |
| 69 | + </button> |
| 70 | + |
| 71 | + @if (errorMessage is not null) |
| 72 | + { |
| 73 | + <div class="alert alert-error">@errorMessage</div> |
| 74 | + } |
| 75 | + |
| 76 | + @if (successMessage is not null) |
| 77 | + { |
| 78 | + <div class="alert alert-success">@successMessage</div> |
| 79 | + } |
| 80 | +</div> |
| 81 | + |
| 82 | +<div class="intro"> |
| 83 | + <h2>About MiniPdf</h2> |
| 84 | + <p>A lightweight, zero-dependency .NET library for converting <strong>.docx</strong> and <strong>.xlsx</strong> files to PDF. No Office installation required.</p> |
| 85 | + <div class="intro-links"> |
| 86 | + <a href="https://github.com/mini-software/MiniPdf" target="_blank"> |
| 87 | + <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 .3a12 12 0 0 0-3.8 23.38c.6.12.83-.26.83-.57L9 20.86c-3.37.74-4.08-1.63-4.08-1.63-.55-1.4-1.34-1.77-1.34-1.77-1.1-.75.08-.73.08-.73 1.21.08 1.85 1.24 1.85 1.24 1.08 1.84 2.83 1.31 3.52 1 .1-.78.42-1.31.76-1.61-2.69-.31-5.52-1.35-5.52-6a4.68 4.68 0 0 1 1.25-3.25 4.35 4.35 0 0 1 .12-3.21s1.02-.33 3.34 1.24a11.5 11.5 0 0 1 6.08 0c2.32-1.57 3.33-1.24 3.33-1.24a4.35 4.35 0 0 1 .12 3.21 4.68 4.68 0 0 1 1.25 3.25c0 4.66-2.84 5.69-5.54 5.99.43.37.82 1.1.82 2.22l-.01 3.29c0 .31.22.7.84.57A12 12 0 0 0 12 .3"/></svg> |
| 88 | + GitHub |
| 89 | + </a> |
| 90 | + <a href="https://www.nuget.org/packages/MiniPdf" target="_blank"> |
| 91 | + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg> |
| 92 | + NuGet |
| 93 | + </a> |
| 94 | + </div> |
| 95 | + <div class="feedback"> |
| 96 | + <p>Encountered a bug or have a suggestion? <a href="https://github.com/mini-software/MiniPdf/issues" target="_blank">Open an issue on GitHub</a></p> |
| 97 | + </div> |
| 98 | +</div> |
| 99 | + |
| 100 | +@code { |
| 101 | + private IBrowserFile? selectedFile; |
| 102 | + private bool isConverting; |
| 103 | + private bool isDragOver; |
| 104 | + private string? errorMessage; |
| 105 | + private string? successMessage; |
| 106 | + |
| 107 | + private bool CanConvert => selectedFile is not null && !isConverting; |
| 108 | + private string FileExt => selectedFile is not null |
| 109 | + ? Path.GetExtension(selectedFile.Name).ToLowerInvariant().TrimStart('.') |
| 110 | + : ""; |
| 111 | + |
| 112 | + private void HandleFileSelected(InputFileChangeEventArgs e) |
| 113 | + { |
| 114 | + errorMessage = null; |
| 115 | + successMessage = null; |
| 116 | + selectedFile = e.File; |
| 117 | + |
| 118 | + var ext = Path.GetExtension(selectedFile.Name).ToLowerInvariant(); |
| 119 | + if (ext is not ".docx" and not ".xlsx") |
| 120 | + { |
| 121 | + errorMessage = "Unsupported file type. Please select a .docx or .xlsx file."; |
| 122 | + selectedFile = null; |
| 123 | + } |
| 124 | + } |
| 125 | + |
| 126 | + private void HandleDragEnter() => isDragOver = true; |
| 127 | + private void HandleDragLeave() => isDragOver = false; |
| 128 | + |
| 129 | + private async Task ConvertFile() |
| 130 | + { |
| 131 | + if (selectedFile is null) return; |
| 132 | + |
| 133 | + isConverting = true; |
| 134 | + errorMessage = null; |
| 135 | + successMessage = null; |
| 136 | + StateHasChanged(); |
| 137 | + await Task.Yield(); |
| 138 | + |
| 139 | + try |
| 140 | + { |
| 141 | + using var stream = selectedFile.OpenReadStream(maxAllowedSize: 50 * 1024 * 1024); |
| 142 | + using var ms = new MemoryStream(); |
| 143 | + await stream.CopyToAsync(ms); |
| 144 | + ms.Position = 0; |
| 145 | + |
| 146 | + var ext = Path.GetExtension(selectedFile.Name).ToLowerInvariant(); |
| 147 | + byte[] pdfBytes = ext == ".docx" |
| 148 | + ? MiniSoftware.MiniPdf.ConvertDocxToPdf(ms) |
| 149 | + : MiniSoftware.MiniPdf.ConvertToPdf(ms); |
| 150 | + |
| 151 | + var outputName = Path.GetFileNameWithoutExtension(selectedFile.Name) + ".pdf"; |
| 152 | + await JS.InvokeVoidAsync("downloadFile", outputName, "application/pdf", pdfBytes); |
| 153 | + successMessage = $"{outputName} downloaded successfully!"; |
| 154 | + } |
| 155 | + catch (Exception ex) |
| 156 | + { |
| 157 | + errorMessage = $"Conversion error: {ex.Message}"; |
| 158 | + } |
| 159 | + finally |
| 160 | + { |
| 161 | + isConverting = false; |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + private static string FormatSize(long bytes) |
| 166 | + { |
| 167 | + if (bytes >= 1024 * 1024) |
| 168 | + return $"{bytes / (1024.0 * 1024.0):F1} MB"; |
| 169 | + if (bytes >= 1024) |
| 170 | + return $"{bytes / 1024.0:F1} KB"; |
| 171 | + return $"{bytes} B"; |
| 172 | + } |
| 173 | +} |
0 commit comments