Fix uploader not fulling completing with long uploads. Added smoother progress bar updates. Added upload rate text. Show selected file name for uploader.
parent
a6103a1f82
commit
b6b6eb1e76
|
@ -63,24 +63,40 @@
|
|||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<InputFile id="FileInput" OnChange="FileSelected" hidden />
|
||||
|
||||
<Upload Name="files" FileList="FileList">
|
||||
<label class="ant-btn" for="FileInput">
|
||||
<Icon Type="upload" />
|
||||
Select Archive
|
||||
</label>
|
||||
</Upload>
|
||||
<Space Direction="DirectionVHType.Horizontal">
|
||||
<SpaceItem>
|
||||
<InputFile id="FileInput" OnChange="FileSelected" hidden />
|
||||
<Upload Name="files" FileList="FileList">
|
||||
<label class="ant-btn" for="FileInput">
|
||||
<Icon Type="upload" />
|
||||
@if (File == null)
|
||||
{
|
||||
<Text>Select File</Text>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Text>Change File</Text>
|
||||
}
|
||||
</label>
|
||||
</Upload>
|
||||
</SpaceItem>
|
||||
<SpaceItem>
|
||||
@if (File != null)
|
||||
{
|
||||
<Text>@File.Name (@ByteSizeLib.ByteSize.FromBytes(File.Size))</Text>
|
||||
}
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Progress Percent="Progress" />
|
||||
<Text>@ByteSizeLib.ByteSize.FromBytes(Speed)/s</Text>
|
||||
<Progress Percent="Progress" Status="@CurrentProgressStatus" Class="uploader-progress" />
|
||||
<Text Class="uploader-progress-rate"></Text>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@code {
|
||||
@code {
|
||||
[Parameter] public Game Game { get; set; }
|
||||
|
||||
Archive Archive;
|
||||
|
@ -98,11 +114,26 @@
|
|||
|
||||
int Progress = 0;
|
||||
bool Uploading = false;
|
||||
bool Finished = false;
|
||||
double Speed = 0;
|
||||
|
||||
Stopwatch Watch;
|
||||
long WatchBytesTransferred = 0;
|
||||
|
||||
string Filename;
|
||||
|
||||
ProgressStatus CurrentProgressStatus {
|
||||
get
|
||||
{
|
||||
if (Finished)
|
||||
return ProgressStatus.Success;
|
||||
else if (Uploading)
|
||||
return ProgressStatus.Active;
|
||||
else
|
||||
return ProgressStatus.Normal;
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (Game.Archives == null)
|
||||
|
@ -143,7 +174,7 @@
|
|||
|
||||
await MessageService.Success("Archive deleted!");
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
await MessageService.Error("Archive could not be deleted.");
|
||||
}
|
||||
|
@ -169,37 +200,44 @@
|
|||
{
|
||||
Uploading = true;
|
||||
|
||||
var response = (await JS.InvokeAsync<string>("Uploader.Upload", "FileInput"));
|
||||
var dotNetReference = DotNetObjectReference.Create(this);
|
||||
|
||||
if (Guid.TryParse(response, out var objectKey))
|
||||
{
|
||||
Uploading = false;
|
||||
await UploadComplete(objectKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
await MessageService.Error("Archive failed to upload!");
|
||||
}
|
||||
await JS.InvokeVoidAsync("Uploader.Upload", "FileInput", dotNetReference);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task UploadComplete(Guid objectKey)
|
||||
[JSInvokable]
|
||||
public async void OnUploadComplete(string data)
|
||||
{
|
||||
Archive.ObjectKey = objectKey.ToString();
|
||||
Archive.CompressedSize = File.Size;
|
||||
|
||||
var originalArchive = Game.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault();
|
||||
|
||||
Archive = await ArchiveService.Add(Archive);
|
||||
|
||||
ModalVisible = false;
|
||||
|
||||
await MessageService.Success("Archive uploaded!");
|
||||
|
||||
if (originalArchive != null)
|
||||
if (Guid.TryParse(data, out var objectKey))
|
||||
{
|
||||
BackgroundJob.Enqueue<PatchArchiveBackgroundJob>(x => x.Execute(originalArchive.Id, Archive.Id));
|
||||
Uploading = false;
|
||||
Finished = true;
|
||||
|
||||
Archive.ObjectKey = objectKey.ToString();
|
||||
Archive.CompressedSize = File.Size;
|
||||
|
||||
var originalArchive = Game.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault();
|
||||
|
||||
Archive = await ArchiveService.Add(Archive);
|
||||
|
||||
ModalVisible = false;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await MessageService.Success("Archive uploaded!");
|
||||
|
||||
if (originalArchive != null)
|
||||
BackgroundJob.Enqueue<PatchArchiveBackgroundJob>(x => x.Execute(originalArchive.Id, Archive.Id));
|
||||
}
|
||||
else
|
||||
{
|
||||
ModalVisible = false;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await MessageService.Error("Archive failed to upload!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,4 +8,13 @@
|
|||
|
||||
.ant-card .ant-form > .ant-form-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.uploader-progress .ant-progress-outer {
|
||||
padding-right: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.uploader-progress .ant-progress-bg {
|
||||
transition: none;
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
import Chunk from './Chunk';
|
||||
import UploadInitResponse from './UploadInitResponse';
|
||||
import axios from 'axios';
|
||||
import axios, { AxiosProgressEvent } from 'axios';
|
||||
|
||||
export default class Uploader {
|
||||
FileInput: HTMLInputElement | undefined;
|
||||
UploadButton: HTMLButtonElement | undefined;
|
||||
ObjectKeyInput: HTMLInputElement | undefined;
|
||||
ProgressBar: HTMLElement | undefined;
|
||||
ProgressText: HTMLElement | undefined;
|
||||
ProgressRate: HTMLElement | undefined;
|
||||
|
||||
File: File | undefined;
|
||||
|
||||
|
@ -26,14 +29,13 @@ export default class Uploader {
|
|||
this.ObjectKeyInput = document.getElementById(objectKeyInputId) as HTMLInputElement;
|
||||
|
||||
this.Chunks = [];
|
||||
|
||||
this.UploadButton.onclick = async (e) => {
|
||||
await this.OnUploadButtonClicked(e);
|
||||
}
|
||||
}
|
||||
|
||||
async Upload(fileInputId: string) {
|
||||
async Upload(fileInputId: string, dotNetObject: any) {
|
||||
this.FileInput = document.getElementById(fileInputId) as HTMLInputElement;
|
||||
this.ProgressBar = document.querySelector('.uploader-progress .ant-progress-bg');
|
||||
this.ProgressText = document.querySelector('.uploader-progress .ant-progress-text');
|
||||
this.ProgressRate = document.querySelector('.uploader-progress-rate');
|
||||
this.Chunks = [];
|
||||
|
||||
this.File = this.FileInput.files.item(0);
|
||||
|
@ -52,49 +54,14 @@ export default class Uploader {
|
|||
}
|
||||
}
|
||||
catch (ex) {
|
||||
this.OnError();
|
||||
}
|
||||
|
||||
return this.Key;
|
||||
} catch (ex) {
|
||||
console.error(`Could not init upload: ${ex}`);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async OnUploadButtonClicked(e: MouseEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
this.OnStart();
|
||||
|
||||
this.File = this.FileInput.files.item(0);
|
||||
this.TotalChunks = Math.ceil(this.File.size / this.MaxChunkSize);
|
||||
|
||||
try {
|
||||
var resp = await axios.post<UploadInitResponse>(this.InitRoute);
|
||||
|
||||
this.Key = resp.data.key;
|
||||
|
||||
this.GetChunks();
|
||||
|
||||
try {
|
||||
for (let chunk of this.Chunks) {
|
||||
await this.UploadChunk(chunk);
|
||||
}
|
||||
|
||||
this.ObjectKeyInput.value = this.Key;
|
||||
|
||||
var event = document.createEvent('HTMLEvents');
|
||||
event.initEvent('change', false, true);
|
||||
this.ObjectKeyInput.dispatchEvent(event);
|
||||
this.OnComplete(this.Id, this.Key);
|
||||
}
|
||||
catch (ex) {
|
||||
this.OnError();
|
||||
if (this.OnError != null)
|
||||
this.OnError();
|
||||
}
|
||||
} catch (ex) {
|
||||
this.Key = null;
|
||||
console.error(`Could not init upload: ${ex}`);
|
||||
} finally {
|
||||
dotNetObject.invokeMethodAsync('OnUploadComplete', this.Key);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,16 +81,16 @@ export default class Uploader {
|
|||
method: "post",
|
||||
url: this.ChunkRoute,
|
||||
data: formData,
|
||||
headers: { "Content-Type": "multipart/form-data" }
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
onUploadProgress: (progressEvent: AxiosProgressEvent) => {
|
||||
console.log(progressEvent);
|
||||
|
||||
this.UpdateProgressBar(chunk.Index, progressEvent);
|
||||
}
|
||||
});
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
throw `Error uploading chunk ${chunk.Index}/${this.TotalChunks}`;
|
||||
} finally {
|
||||
var percent = Math.ceil((chunk.Index / this.TotalChunks) * 100);
|
||||
|
||||
let progress: HTMLElement = document.querySelector('.ant-progress-bg');
|
||||
|
||||
progress.style.width = percent + '%';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,6 +106,37 @@ export default class Uploader {
|
|||
}
|
||||
}
|
||||
|
||||
UpdateProgressBar(chunkIndex: number, progressEvent: AxiosProgressEvent) {
|
||||
var percent = ((1 / this.TotalChunks) * progressEvent.progress) + ((chunkIndex - 1) / this.TotalChunks);
|
||||
|
||||
this.ProgressBar.style.width = (percent * 100) + '%';
|
||||
this.ProgressText.innerText = Math.ceil(percent * 100) + '%';
|
||||
|
||||
if (progressEvent.rate > 0)
|
||||
this.ProgressRate.innerText = this.GetHumanFileSize(progressEvent.rate, false, 1) + '/s';
|
||||
}
|
||||
|
||||
GetHumanFileSize(bytes: number, si: boolean, dp: number) {
|
||||
const thresh = si ? 1000 : 1024;
|
||||
|
||||
if (Math.abs(bytes) < thresh) {
|
||||
return bytes + ' B';
|
||||
}
|
||||
|
||||
const units = si
|
||||
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||
let u = -1;
|
||||
const r = 10 ** dp;
|
||||
|
||||
do {
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
|
||||
|
||||
return bytes.toFixed(dp) + ' ' + units[u];
|
||||
}
|
||||
|
||||
OnStart: () => void;
|
||||
OnComplete: (id: string, key: string) => void;
|
||||
OnProgress: (percent: number) => void;
|
||||
|
|
|
@ -62,13 +62,13 @@ class Uploader {
|
|||
this.UploadButton = document.getElementById(uploadButtonId);
|
||||
this.ObjectKeyInput = document.getElementById(objectKeyInputId);
|
||||
this.Chunks = [];
|
||||
this.UploadButton.onclick = (e) => __awaiter(this, void 0, void 0, function* () {
|
||||
yield this.OnUploadButtonClicked(e);
|
||||
});
|
||||
}
|
||||
Upload(fileInputId) {
|
||||
Upload(fileInputId, dotNetObject) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
this.FileInput = document.getElementById(fileInputId);
|
||||
this.ProgressBar = document.querySelector('.uploader-progress .ant-progress-bg');
|
||||
this.ProgressText = document.querySelector('.uploader-progress .ant-progress-text');
|
||||
this.ProgressRate = document.querySelector('.uploader-progress-rate');
|
||||
this.Chunks = [];
|
||||
this.File = this.FileInput.files.item(0);
|
||||
this.TotalChunks = Math.ceil(this.File.size / this.MaxChunkSize);
|
||||
|
@ -82,43 +82,17 @@ class Uploader {
|
|||
}
|
||||
}
|
||||
catch (ex) {
|
||||
this.OnError();
|
||||
}
|
||||
return this.Key;
|
||||
}
|
||||
catch (ex) {
|
||||
console.error(`Could not init upload: ${ex}`);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
OnUploadButtonClicked(e) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
e.preventDefault();
|
||||
this.OnStart();
|
||||
this.File = this.FileInput.files.item(0);
|
||||
this.TotalChunks = Math.ceil(this.File.size / this.MaxChunkSize);
|
||||
try {
|
||||
var resp = yield axios__WEBPACK_IMPORTED_MODULE_1__["default"].post(this.InitRoute);
|
||||
this.Key = resp.data.key;
|
||||
this.GetChunks();
|
||||
try {
|
||||
for (let chunk of this.Chunks) {
|
||||
yield this.UploadChunk(chunk);
|
||||
}
|
||||
this.ObjectKeyInput.value = this.Key;
|
||||
var event = document.createEvent('HTMLEvents');
|
||||
event.initEvent('change', false, true);
|
||||
this.ObjectKeyInput.dispatchEvent(event);
|
||||
this.OnComplete(this.Id, this.Key);
|
||||
}
|
||||
catch (ex) {
|
||||
this.OnError();
|
||||
if (this.OnError != null)
|
||||
this.OnError();
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
this.Key = null;
|
||||
console.error(`Could not init upload: ${ex}`);
|
||||
}
|
||||
finally {
|
||||
dotNetObject.invokeMethodAsync('OnUploadComplete', this.Key);
|
||||
}
|
||||
});
|
||||
}
|
||||
UploadChunk(chunk) {
|
||||
|
@ -135,16 +109,22 @@ class Uploader {
|
|||
method: "post",
|
||||
url: this.ChunkRoute,
|
||||
data: formData,
|
||||
headers: { "Content-Type": "multipart/form-data" }
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
onUploadProgress: (progressEvent) => {
|
||||
console.log(progressEvent);
|
||||
this.UpdateProgressBar(chunk.Index, progressEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (ex) {
|
||||
console.error(ex);
|
||||
throw `Error uploading chunk ${chunk.Index}/${this.TotalChunks}`;
|
||||
}
|
||||
finally {
|
||||
console.log("Updating progress bar");
|
||||
var percent = Math.ceil((chunk.Index / this.TotalChunks) * 100);
|
||||
let progress = document.querySelector('.ant-progress-bg');
|
||||
progress.style.width = percent + '%';
|
||||
this.ProgressBar.style.width = percent + '%';
|
||||
this.ProgressText.innerText = percent + '%';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -157,6 +137,29 @@ class Uploader {
|
|||
this.Chunks.push(new _Chunk__WEBPACK_IMPORTED_MODULE_0__["default"](start, end, currentChunk));
|
||||
}
|
||||
}
|
||||
UpdateProgressBar(chunkIndex, progressEvent) {
|
||||
var percent = ((1 / this.TotalChunks) * progressEvent.progress) + ((chunkIndex - 1) / this.TotalChunks);
|
||||
this.ProgressBar.style.width = (percent * 100) + '%';
|
||||
this.ProgressText.innerText = Math.ceil(percent * 100) + '%';
|
||||
if (progressEvent.rate > 0)
|
||||
this.ProgressRate.innerText = this.GetHumanFileSize(progressEvent.rate, false, 1) + '/s';
|
||||
}
|
||||
GetHumanFileSize(bytes, si, dp) {
|
||||
const thresh = si ? 1000 : 1024;
|
||||
if (Math.abs(bytes) < thresh) {
|
||||
return bytes + ' B';
|
||||
}
|
||||
const units = si
|
||||
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||
let u = -1;
|
||||
const r = Math.pow(10, dp);
|
||||
do {
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
|
||||
return bytes.toFixed(dp) + ' ' + units[u];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue