Add pretty UI elements for uploading. Save archive with uploaded file object key.

dashboard
Pat Hartl 2023-01-02 18:32:33 -06:00
parent 6a5afaad5d
commit 5736c5fe56
5 changed files with 165 additions and 64 deletions

View File

@ -191,8 +191,22 @@ namespace LANCommander.Controllers
}
[HttpPost]
public IActionResult AddArchive([Bind("UploadedFile", "Version")] Archive archive)
public async Task<IActionResult> AddArchive(Guid? id, Archive archive)
{
archive.Id = Guid.Empty;
using (Repository<Game> gameRepo = new Repository<Game>(Context, HttpContext))
{
var game = await gameRepo.Find(id.GetValueOrDefault());
using (Repository<Archive> archiveRepo = new Repository<Archive>(Context, HttpContext))
{
archive.Game = game;
archive = await archiveRepo.Add(archive);
await archiveRepo.SaveChanges();
}
}
return RedirectToAction(nameof(Index));
}

View File

@ -9,31 +9,42 @@
<h4>@Html.DisplayFor(m => m.Game.Title)</h4>
<hr />
<div class="row">
<div class="col-md-4">
<div class="col-md-6">
<form asp-action="AddArchive" enctype="multipart/form-data" >
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Version" class="control-label"></label>
<input asp-for="Version" class="form-control" />
<span asp-validation-for="Version" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Changelog" class="control-label"></label>
<textarea asp-for="Changelog" class="form-control"></textarea>
<span asp-validation-for="Changelog" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="UploadedFile" class="control-label"></label>
<input type="file" asp-for="UploadedFile" class="form-control" />
<span asp-validation-for="UploadedFile" class="text-danger"></span>
</div>
<div class="form-group">
<label class="control-label">Last Version</label>
<input class="form-control" value="@Html.DisplayFor(m => m.LastVersion.Version)" />
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
<fieldset>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Version" class="control-label"></label>
<input asp-for="Version" class="form-control" />
<span asp-validation-for="Version" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Changelog" class="control-label"></label>
<textarea asp-for="Changelog" class="form-control"></textarea>
<span asp-validation-for="Changelog" class="text-danger"></span>
</div>
<div class="form-group">
<label for="File" class="control-label">File</label>
<input type="file" id="File" class="form-control" />
</div>
<div class="form-group">
<label class="control-label">Last Version</label>
<input class="form-control" value="@Html.DisplayFor(m => m.LastVersion.Version)" />
</div>
<div class="form-group">
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: 0%"></div>
</div>
</div>
<div class="form-group">
<button class="btn btn-primary" id="UploadButton">Upload</button>
<input type="submit" value="Save" id="SaveButton" class="btn btn-primary" style="display: none;" />
</div>
<input type="hidden" asp-for="ObjectKey" />
</fieldset>
</form>
</div>
</div>
@ -49,6 +60,53 @@
<script>
var uploader = new Uploader();
uploader.Init('UploadedFile');
uploader.Init('File', 'UploadButton');
uploader.OnStart = () => {
$('fieldset').prop('disabled', true);
$('.progress-bar')
.css('width', '0%')
.removeClass('bg-success')
.removeClass('bg-danger')
.addClass('progress-bar-striped')
.addClass('progress-bar-animated')
.text('0%');
$('#UploadButton').show();
$('#SaveButton').hide();
};
uploader.OnComplete = (key) => {
$('#ObjectKey').val(key);
$('fieldset').prop('disabled', false);
$('.progress-bar')
.css('width', '100%')
.removeClass('progress-bar-striped')
.removeClass('progress-bar-animated')
.addClass('bg-success')
.text('Upload Complete!');
$('#UploadButton').hide();
$('#SaveButton').show();
};
uploader.OnProgress = (percent) => {
$('.progress-bar')
.css('width', `${percent * 100}%`)
.text(`${Math.round(percent * 100)}%`);
};
uploader.OnError = () => {
$('fieldset').prop('disabled', false);
$('.progress-bar')
.css('width', '100%')
.removeClass('progress-bar-striped')
.removeClass('progress-bar-animated')
.addClass('bg-danger')
.text('Error!');
$('#UploadButton').show();
$('#SaveButton').hide();
};
</script>
}

View File

@ -20,17 +20,19 @@ class Uploader {
this.ChunkRoute = "/Upload/Chunk";
this.MaxChunkSize = 1024 * 1024 * 25;
}
Init(elementId) {
this.FileInput = document.getElementById(elementId);
Init(fileInputId, uploadButtonId) {
this.FileInput = document.getElementById(fileInputId);
this.UploadButton = document.getElementById(uploadButtonId);
this.ParentForm = this.FileInput.closest("form");
this.Chunks = [];
this.ParentForm.onsubmit = (e) => __awaiter(this, void 0, void 0, function* () {
yield this.HandleFormSubmit(e);
this.UploadButton.onclick = (e) => __awaiter(this, void 0, void 0, function* () {
yield this.OnUploadButtonClicked(e);
});
}
HandleFormSubmit(e) {
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);
var response = yield fetch(this.InitRoute, {
@ -40,19 +42,27 @@ class Uploader {
if (response.ok) {
this.Key = data.key;
this.GetChunks();
for (let chunk of this.Chunks) {
let formData = new FormData();
formData.append('file', this.File.slice(chunk.Start, chunk.End + 1));
formData.append('start', chunk.Start.toString());
formData.append('end', chunk.End.toString());
formData.append('key', this.Key);
formData.append('total', this.File.size.toString());
console.info(`Uploading chunk ${chunk.Index}/${this.TotalChunks}...`);
let response = yield fetch(this.ChunkRoute, {
method: "POST",
body: formData
});
debugger;
try {
for (let chunk of this.Chunks) {
let formData = new FormData();
formData.append('file', this.File.slice(chunk.Start, chunk.End + 1));
formData.append('start', chunk.Start.toString());
formData.append('end', chunk.End.toString());
formData.append('key', this.Key);
formData.append('total', this.File.size.toString());
console.info(`Uploading chunk ${chunk.Index}/${this.TotalChunks}...`);
let chunkResponse = yield fetch(this.ChunkRoute, {
method: "POST",
body: formData
});
if (!chunkResponse)
throw `Error uploading chunk ${chunk.Index}/${this.TotalChunks}`;
this.OnProgress(chunk.Index / this.TotalChunks);
}
this.OnComplete(this.Key);
}
catch (_a) {
this.OnError();
}
}
});

View File

@ -1 +1 @@
{"version":3,"file":"Upload.js","sourceRoot":"","sources":["Upload.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"Upload.js","sourceRoot":"","sources":["Upload.ts"],"names":[],"mappings":"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"}

View File

@ -13,6 +13,7 @@
class Uploader {
ParentForm: HTMLFormElement;
FileInput: HTMLInputElement;
UploadButton: HTMLButtonElement;
File: File;
InitRoute: string = "/Upload/Init";
@ -25,20 +26,23 @@ class Uploader {
Key: string;
Init(elementId: string) {
this.FileInput = document.getElementById(elementId) as HTMLInputElement;
Init(fileInputId: string, uploadButtonId: string) {
this.FileInput = document.getElementById(fileInputId) as HTMLInputElement;
this.UploadButton = document.getElementById(uploadButtonId) as HTMLButtonElement;
this.ParentForm = this.FileInput.closest("form");
this.Chunks = [];
this.ParentForm.onsubmit = async (e) => {
await this.HandleFormSubmit(e);
};
this.UploadButton.onclick = async (e) => {
await this.OnUploadButtonClicked(e);
}
}
async HandleFormSubmit(e: SubmitEvent) {
async OnUploadButtonClicked(e: MouseEvent) {
e.preventDefault();
this.OnStart();
this.File = this.FileInput.files.item(0);
this.TotalChunks = Math.ceil(this.File.size / this.MaxChunkSize);
@ -53,23 +57,33 @@ class Uploader {
this.GetChunks();
for (let chunk of this.Chunks) {
let formData = new FormData();
try {
for (let chunk of this.Chunks) {
let formData = new FormData();
formData.append('file', this.File.slice(chunk.Start, chunk.End + 1));
formData.append('start', chunk.Start.toString());
formData.append('end', chunk.End.toString());
formData.append('key', this.Key);
formData.append('total', this.File.size.toString());
formData.append('file', this.File.slice(chunk.Start, chunk.End + 1));
formData.append('start', chunk.Start.toString());
formData.append('end', chunk.End.toString());
formData.append('key', this.Key);
formData.append('total', this.File.size.toString());
console.info(`Uploading chunk ${chunk.Index}/${this.TotalChunks}...`);
console.info(`Uploading chunk ${chunk.Index}/${this.TotalChunks}...`);
let response = await fetch(this.ChunkRoute, {
method: "POST",
body: formData
});
let chunkResponse = await fetch(this.ChunkRoute, {
method: "POST",
body: formData
});
debugger;
if (!chunkResponse)
throw `Error uploading chunk ${chunk.Index}/${this.TotalChunks}`;
this.OnProgress(chunk.Index / this.TotalChunks);
}
this.OnComplete(this.Key);
}
catch {
this.OnError();
}
}
}
@ -85,4 +99,9 @@ class Uploader {
this.Chunks.push(new Chunk(start, end, currentChunk));
}
}
OnStart: () => void;
OnComplete: (key: string) => void;
OnProgress: (percent: number) => void;
OnError: () => void;
}