|
50 | 50 | </div> |
51 | 51 | } |
52 | 52 |
|
53 | | -<div class="mb-3"> |
| 53 | +<div class="drop-zone mb-3 @(_dragCount > 0 ? "drop-zone--active" : "")" |
| 54 | + @ondragenter="@(() => _dragCount++)" |
| 55 | + @ondragleave="@(() => _dragCount--)" |
| 56 | + @ondrop="@(() => _dragCount = 0)" |
| 57 | + @ondragover:preventDefault> |
54 | 58 | @if (compressionMode == CompressionMode.Compress) |
55 | 59 | { |
56 | | - <label class="form-label">Choose files to compress to @FormatName</label> |
| 60 | + <p class="mb-1 fw-medium">Choose files to compress to @FormatName</p> |
57 | 61 | } |
58 | 62 | else |
59 | 63 | { |
60 | | - <label class="form-label">Choose @FormatName files to decompress</label> |
| 64 | + <p class="mb-1 fw-medium">Choose @FormatName files to decompress</p> |
61 | 65 | } |
62 | | - <InputFile OnChange="OnFilesChange" multiple class="form-control" /> |
| 66 | + <p class="text-muted small mb-0">or drag and drop here</p> |
| 67 | + <InputFile OnChange="OnFilesChange" multiple class="drop-zone-input" |
| 68 | + accept="@(compressionMode == CompressionMode.Decompress ? Extension : "")" /> |
63 | 69 | </div> |
64 | 70 |
|
65 | 71 | <div class="mb-3"> |
|
97 | 103 | @if(file.Status == "Finished") |
98 | 104 | { |
99 | 105 | <span class="text-success">✔ Finished</span> |
| 106 | + <span class="text-muted ms-2 small">@FormatBytes(file.OriginalSize) → @FormatBytes(file.OutputSize)</span> |
| 107 | + @if (compressionMode == CompressionMode.Compress) |
| 108 | + { |
| 109 | + <span class="@RatioBadgeCss(file.OriginalSize, file.OutputSize) ms-1">@RatioText(file.OriginalSize, file.OutputSize)</span> |
| 110 | + } |
100 | 111 | } |
101 | 112 | </li> |
102 | 113 | } |
103 | 114 | </ul> |
104 | 115 |
|
105 | 116 | @code { |
106 | | - private enum CompressionFormat { GZip, Brotli, Deflate, ZLib } |
| 117 | + private enum CompressionFormat { GZip, Brotli, Deflate, ZLib, ZStd } |
107 | 118 |
|
108 | 119 | private CompressionMode compressionMode = CompressionMode.Compress; |
109 | 120 | private CompressionLevel compressionLevel = CompressionLevel.Optimal; |
110 | 121 | private List<Models.File> files = new List<Models.File>(); |
111 | 122 | private bool anyFiles => files.Any(); |
112 | 123 |
|
113 | 124 | private CompressionFormat _format = CompressionFormat.GZip; |
| 125 | + private int _dragCount; |
114 | 126 | private string FormatName => _format switch |
115 | 127 | { |
116 | 128 | CompressionFormat.Brotli => "brotli", |
117 | 129 | CompressionFormat.Deflate => "deflate", |
118 | 130 | CompressionFormat.ZLib => "zlib", |
| 131 | + CompressionFormat.ZStd => "zstd", |
119 | 132 | _ => "gzip" |
120 | 133 | }; |
121 | 134 | private string Extension => _format switch |
122 | 135 | { |
123 | 136 | CompressionFormat.Brotli => ".br", |
124 | 137 | CompressionFormat.Deflate => ".deflate", |
125 | 138 | CompressionFormat.ZLib => ".zlib", |
| 139 | + CompressionFormat.ZStd => ".zst", |
126 | 140 | _ => ".gz" |
127 | 141 | }; |
128 | 142 |
|
|
134 | 148 | _ when host.Contains("brotli") => CompressionFormat.Brotli, |
135 | 149 | _ when host.Contains("deflate") => CompressionFormat.Deflate, |
136 | 150 | _ when host.Contains("zlib") => CompressionFormat.ZLib, |
| 151 | + _ when host.Contains("zstd") => CompressionFormat.ZStd, |
137 | 152 | _ => CompressionFormat.GZip |
138 | 153 | }; |
139 | 154 | } |
140 | 155 |
|
141 | | - private Stream CreateCompressStream(Stream output, CompressionLevel level) => _format switch |
| 156 | + private Stream CreateCompressStream(Stream output, CompressionLevel level) |
142 | 157 | { |
143 | | - CompressionFormat.Brotli => new BrotliStream(output, level), |
144 | | - CompressionFormat.Deflate => new DeflateStream(output, level), |
145 | | - CompressionFormat.ZLib => new ZLibStream(output, level), |
146 | | - _ => new GZipStream(output, level) |
| 158 | + if (_format == CompressionFormat.Brotli) |
| 159 | + { |
| 160 | + var s = new BrotliSharpLib.BrotliStream(output, CompressionMode.Compress); |
| 161 | + s.SetQuality(ToBrotliQuality(level)); |
| 162 | + return s; |
| 163 | + } |
| 164 | + if (_format == CompressionFormat.ZStd) |
| 165 | + return new ZstdSharp.CompressionStream(output, ToZstdLevel(level)); |
| 166 | + return _format switch |
| 167 | + { |
| 168 | + CompressionFormat.Deflate => new DeflateStream(output, level), |
| 169 | + CompressionFormat.ZLib => new ZLibStream(output, level), |
| 170 | + _ => new GZipStream(output, level) |
| 171 | + }; |
| 172 | + } |
| 173 | + |
| 174 | + private static int ToBrotliQuality(CompressionLevel level) => level switch |
| 175 | + { |
| 176 | + CompressionLevel.NoCompression => 0, |
| 177 | + CompressionLevel.Fastest => 1, |
| 178 | + CompressionLevel.SmallestSize => 11, |
| 179 | + _ => 6 |
| 180 | + }; |
| 181 | + |
| 182 | + private static int ToZstdLevel(CompressionLevel level) => level switch |
| 183 | + { |
| 184 | + CompressionLevel.NoCompression => 1, |
| 185 | + CompressionLevel.Fastest => 1, |
| 186 | + CompressionLevel.SmallestSize => 19, |
| 187 | + _ => 3 |
147 | 188 | }; |
148 | 189 |
|
149 | 190 | private Stream CreateDecompressStream(Stream input) => _format switch |
150 | 191 | { |
151 | | - CompressionFormat.Brotli => new BrotliStream(input, CompressionMode.Decompress), |
| 192 | + CompressionFormat.Brotli => new BrotliSharpLib.BrotliStream(input, CompressionMode.Decompress), |
152 | 193 | CompressionFormat.Deflate => new DeflateStream(input, CompressionMode.Decompress), |
153 | 194 | CompressionFormat.ZLib => new ZLibStream(input, CompressionMode.Decompress), |
| 195 | + CompressionFormat.ZStd => new ZstdSharp.DecompressionStream(input), |
154 | 196 | _ => new GZipStream(input, CompressionMode.Decompress) |
155 | 197 | }; |
156 | 198 |
|
| 199 | + private static string FormatBytes(long bytes) |
| 200 | + { |
| 201 | + if (bytes < 1024) return $"{bytes} B"; |
| 202 | + if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB"; |
| 203 | + return $"{bytes / (1024.0 * 1024):F1} MB"; |
| 204 | + } |
| 205 | + |
| 206 | + private static string RatioText(long original, long output) |
| 207 | + { |
| 208 | + if (original == 0) return ""; |
| 209 | + var pct = (1.0 - (double)output / original) * 100; |
| 210 | + return pct >= 0 ? $"{pct:F0}% smaller" : $"{-pct:F0}% larger"; |
| 211 | + } |
| 212 | + |
| 213 | + private static string RatioBadgeCss(long original, long output) |
| 214 | + { |
| 215 | + var pct = original > 0 ? (1.0 - (double)output / original) * 100 : 0; |
| 216 | + return pct >= 0 ? "badge bg-success" : "badge bg-warning text-dark"; |
| 217 | + } |
| 218 | + |
157 | 219 | private void OnFilesChange(InputFileChangeEventArgs e) |
158 | 220 | { |
159 | 221 | files.Clear(); |
|
181 | 243 | var browserFile = file.BrowserFile; |
182 | 244 | var buffer = new byte[browserFile.Size]; |
183 | 245 | await browserFile.OpenReadStream(long.MaxValue).ReadAsync(buffer); |
| 246 | + file.OriginalSize = buffer.Length; |
184 | 247 | using (var outputStream = new MemoryStream()) |
185 | 248 | { |
186 | 249 | using (var compressionStream = CreateCompressStream(outputStream, compressionLevel)) |
187 | 250 | { |
188 | 251 | await compressionStream.WriteAsync(buffer, 0, buffer.Length); |
189 | 252 | } |
190 | | - await BlazorDownloadFileService.DownloadFile(file.NewName, outputStream.ToArray(), "application/octet-stream"); |
| 253 | + var outputBytes = outputStream.ToArray(); |
| 254 | + file.OutputSize = outputBytes.Length; |
| 255 | + await BlazorDownloadFileService.DownloadFile(file.NewName, outputBytes, "application/octet-stream"); |
191 | 256 | } |
192 | 257 |
|
193 | 258 | file.Status = "Finished"; |
|
220 | 285 | var browserFile = file.BrowserFile; |
221 | 286 | var buffer = new byte[browserFile.Size]; |
222 | 287 | await browserFile.OpenReadStream(long.MaxValue).ReadAsync(buffer); |
| 288 | + file.OriginalSize = buffer.Length; |
223 | 289 | using (var inputStream = new MemoryStream(buffer)) |
224 | 290 | using (var outputStream = new MemoryStream()) |
225 | 291 | { |
226 | 292 | using (var compressionStream = CreateDecompressStream(inputStream)) |
227 | 293 | { |
228 | 294 | await compressionStream.CopyToAsync(outputStream); |
229 | 295 | } |
230 | | - await BlazorDownloadFileService.DownloadFile(file.NewName, outputStream.ToArray(), "application/octet-stream"); |
| 296 | + var outputBytes = outputStream.ToArray(); |
| 297 | + file.OutputSize = outputBytes.Length; |
| 298 | + await BlazorDownloadFileService.DownloadFile(file.NewName, outputBytes, "application/octet-stream"); |
231 | 299 | } |
232 | 300 |
|
233 | 301 | file.Status = "Finished"; |
|
0 commit comments