Remove additional stuff added back in from trying to restore JS uploader. Trim uploader to not handle UI behavior. Call JS uploader from archive uploader component.

This commit is contained in:
Pat Hartl 2023-04-07 15:26:21 -05:00
parent ed15a50545
commit 9e4abc535b
40 changed files with 113 additions and 3174 deletions

View file

@ -4,6 +4,7 @@
@inject NavigationManager Navigator
@inject ArchiveService ArchiveService
@inject IMessageService MessageService
@inject IJSRuntime JS
<Space Direction="DirectionVHType.Vertical" Style="width: 100%">
<SpaceItem>
@ -40,7 +41,7 @@
@{
RenderFragment Footer =
@<Template>
<Button OnClick="UploadArchive" Disabled="@(File == null || Uploading)" Type="@ButtonType.Primary">Upload</Button>
<Button OnClick="UploadArchiveJS" Disabled="@(File == null || Uploading)" Type="@ButtonType.Primary">Upload</Button>
<Button OnClick="Clear" Disabled="File == null || Uploading" Danger>Clear</Button>
<Button OnClick="Cancel">Cancel</Button>
</Template>;
@ -147,9 +148,18 @@
ModalVisible = false;
}
private void FileSelected(InputFileChangeEventArgs args)
private async void FileSelected(InputFileChangeEventArgs args)
{
File = args.File;
//var buffer = new byte[File.Size];
//await File.OpenReadStream().ReadAsync(buffer);
}
private async Task UploadArchiveJS()
{
var key = (await JS.InvokeAsync<string>("Uploader.Upload", "FileInput"));
}
private async Task UploadArchive()
@ -179,18 +189,20 @@
// This feels hacky, why do we need to do this?
// Only 32256 bytes of the file get read unless we
// loop through like this. Probably kills performance.
while (bytesRead < chunk.Length)
/*while (bytesRead < chunk.Length)
{
bytesRead += await stream.ReadAsync(chunk, bytesRead, chunk.Length - bytesRead);
}
}*/
using (FileStream fs = new FileStream(Path.Combine("Upload", Archive.Id.ToString()), FileMode.Append))
{
await fs.WriteAsync(chunk);
await stream.CopyToAsync(fs, ChunkSize);
//await fs.WriteAsync(chunk);
}
uploadedBytes += chunk.Length;
WatchBytesTransferred += chunk.Length;
uploadedBytes += ChunkSize;
WatchBytesTransferred += ChunkSize;
Progress = (int)(uploadedBytes * 100 / totalBytes);

View file

@ -1,140 +0,0 @@
using LANCommander.Data;
using LANCommander.Data.Models;
using LANCommander.Extensions;
using LANCommander.Models;
using LANCommander.SDK;
using LANCommander.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.IO.Compression;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace LANCommander.Controllers
{
[Authorize(Roles = "Administrator")]
public class ArchivesController : Controller
{
private readonly GameService GameService;
private readonly ArchiveService ArchiveService;
public ArchivesController(GameService gameService, ArchiveService archiveService)
{
GameService = gameService;
ArchiveService = archiveService;
}
public async Task<IActionResult> Add(Guid? id)
{
if (id == null)
return NotFound();
var game = await GameService.Get(id.GetValueOrDefault());
if (game == null)
return NotFound();
Archive lastVersion = null;
if (game.Archives != null && game.Archives.Count > 0)
lastVersion = game.Archives.OrderByDescending(a => a.CreatedOn).First();
return View(new Archive()
{
Game = game,
GameId = game.Id,
LastVersion = lastVersion,
});
}
[HttpPost]
public async Task<IActionResult> Add(Guid? id, Archive archive)
{
archive.Id = Guid.Empty;
var game = await GameService.Get(id.GetValueOrDefault());
if (game == null)
return NotFound();
archive.Game = game;
archive.GameId = game.Id;
if (game.Archives != null && game.Archives.Any(a => a.Version == archive.Version))
ModelState.AddModelError("Version", "An archive for this game is already using that version.");
if (ModelState.IsValid)
{
await ArchiveService.Update(archive);
return RedirectToAction("Edit", "Games", new { id = id });
}
return View(archive);
}
public async Task<IActionResult> Download(Guid id)
{
var archive = await ArchiveService.Get(id);
var content = new FileStream($"Upload/{archive.ObjectKey}".ToPath(), FileMode.Open, FileAccess.Read, FileShare.Read);
return File(content, "application/octet-stream", $"{archive.Game.Title.SanitizeFilename()}.zip");
}
public async Task<IActionResult> Delete(Guid? id)
{
var archive = await ArchiveService.Get(id.GetValueOrDefault());
var gameId = archive.Game.Id;
await ArchiveService.Delete(archive);
return RedirectToAction("Edit", "Games", new { id = gameId });
}
public async Task<IActionResult> Browse(Guid id)
{
var archive = await ArchiveService.Get(id);
return View(archive);
}
public async Task<IActionResult> Validate(Guid id, Archive archive)
{
var path = $"Upload/{id}".ToPath();
string manifestContents = String.Empty;
long compressedSize = 0;
long uncompressedSize = 0;
if (!System.IO.File.Exists(path))
return BadRequest("Specified object does not exist");
var game = await GameService.Get(archive.GameId);
if (game == null)
return BadRequest("The related game is missing or corrupt.");
archive.GameId = game.Id;
archive.Id = Guid.Empty;
archive.CompressedSize = compressedSize;
archive.UncompressedSize = uncompressedSize;
archive.ObjectKey = id.ToString();
try
{
archive = await ArchiveService.Add(archive);
}
catch (Exception ex)
{
}
return Json(new
{
Id = archive.Id,
ObjectKey = archive.ObjectKey,
});
}
}
}

View file

@ -1,166 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using LANCommander.Data;
using LANCommander.Data.Models;
using Microsoft.AspNetCore.Authorization;
namespace LANCommander.Controllers
{
[Authorize(Roles = "Administrator")]
public class CompaniesController : Controller
{
private readonly DatabaseContext _context;
public CompaniesController(DatabaseContext context)
{
_context = context;
}
// GET: Companies
public async Task<IActionResult> Index()
{
return _context.Companies != null ?
View(await _context.Companies.ToListAsync()) :
Problem("Entity set 'DatabaseContext.Companies' is null.");
}
// GET: Companies/Details/5
public async Task<IActionResult> Details(Guid? id)
{
if (id == null || _context.Companies == null)
{
return NotFound();
}
var company = await _context.Companies
.FirstOrDefaultAsync(m => m.Id == id);
if (company == null)
{
return NotFound();
}
return View(company);
}
// GET: Companies/Create
public IActionResult Create()
{
return View();
}
// POST: Companies/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Name,Id,CreatedOn,CreatedById,UpdatedOn,UpdatedById")] Company company)
{
if (ModelState.IsValid)
{
company.Id = Guid.NewGuid();
_context.Add(company);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(company);
}
// GET: Companies/Edit/5
public async Task<IActionResult> Edit(Guid? id)
{
if (id == null || _context.Companies == null)
{
return NotFound();
}
var company = await _context.Companies.FindAsync(id);
if (company == null)
{
return NotFound();
}
return View(company);
}
// POST: Companies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(Guid id, [Bind("Name,Id,CreatedOn,CreatedById,UpdatedOn,UpdatedById")] Company company)
{
if (id != company.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(company);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CompanyExists(company.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(company);
}
// GET: Companies/Delete/5
public async Task<IActionResult> Delete(Guid? id)
{
if (id == null || _context.Companies == null)
{
return NotFound();
}
var company = await _context.Companies
.FirstOrDefaultAsync(m => m.Id == id);
if (company == null)
{
return NotFound();
}
return View(company);
}
// POST: Companies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(Guid id)
{
if (_context.Companies == null)
{
return Problem("Entity set 'DatabaseContext.Companies' is null.");
}
var company = await _context.Companies.FindAsync(id);
if (company != null)
{
_context.Companies.Remove(company);
}
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool CompanyExists(Guid id)
{
return (_context.Companies?.Any(e => e.Id == id)).GetValueOrDefault();
}
}
}

View file

@ -1,577 +0,0 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using LANCommander.Data;
using LANCommander.Data.Models;
using Microsoft.AspNetCore.Authorization;
using LANCommander.Services;
using System.Drawing;
using LANCommander.Models;
using LANCommander.Data.Enums;
using LANCommander.PCGamingWiki;
namespace LANCommander.Controllers
{
[Authorize(Roles = "Administrator")]
public class GamesController : Controller
{
private readonly GameService GameService;
private readonly ArchiveService ArchiveService;
private readonly CategoryService CategoryService;
private readonly TagService TagService;
private readonly GenreService GenreService;
private readonly CompanyService CompanyService;
private readonly IGDBService IGDBService;
private readonly PCGamingWikiClient PCGamingWikiClient;
public GamesController(GameService gameService, ArchiveService archiveService, CategoryService categoryService, TagService tagService, GenreService genreService, CompanyService companyService, IGDBService igdbService)
{
GameService = gameService;
ArchiveService = archiveService;
CategoryService = categoryService;
TagService = tagService;
GenreService = genreService;
CompanyService = companyService;
IGDBService = igdbService;
PCGamingWikiClient = new PCGamingWikiClient();
}
// GET: Games
public async Task<IActionResult> Index()
{
return View(GameService.Get());
}
public async Task<IActionResult> Add(long? igdbid)
{
var viewModel = new GameViewModel()
{
Game = new Game(),
Developers = new List<SelectListItem>(),
Publishers = new List<SelectListItem>(),
Genres = new List<SelectListItem>(),
Tags = new List<SelectListItem>(),
};
if (igdbid == null)
{
viewModel.Game = new Game()
{
Actions = new List<Data.Models.Action>(),
MultiplayerModes = new List<Data.Models.MultiplayerMode>()
};
viewModel.Developers = CompanyService.Get().OrderBy(c => c.Name).Select(c => new SelectListItem() { Text = c.Name, Value = c.Name }).ToList();
viewModel.Publishers = CompanyService.Get().OrderBy(c => c.Name).Select(c => new SelectListItem() { Text = c.Name, Value = c.Name }).ToList();
viewModel.Genres = GenreService.Get().OrderBy(g => g.Name).Select(g => new SelectListItem() { Text = g.Name, Value = g.Name }).ToList();
viewModel.Tags = TagService.Get().OrderBy(t => t.Name).Select(t => new SelectListItem() { Text = t.Name, Value = t.Name }).ToList();
return View(viewModel);
}
var result = await IGDBService.Get(igdbid.Value, "genres.*", "game_modes.*", "multiplayer_modes.*", "release_dates.*", "platforms.*", "keywords.*", "involved_companies.*", "involved_companies.company.*", "cover.*");
viewModel.Game = new Game()
{
IGDBId = result.Id.GetValueOrDefault(),
Title = result.Name,
Description = result.Summary,
ReleasedOn = result.FirstReleaseDate.GetValueOrDefault().UtcDateTime,
Actions = new List<Data.Models.Action>(),
MultiplayerModes = new List<MultiplayerMode>()
};
var playerCounts = await PCGamingWikiClient.GetMultiplayerPlayerCounts(result.Name);
if (playerCounts != null)
{
foreach (var playerCount in playerCounts)
{
MultiplayerType type;
switch (playerCount.Key)
{
case "Local Play":
type = MultiplayerType.Local;
break;
case "LAN Play":
type = MultiplayerType.Lan;
break;
case "Online Play":
type = MultiplayerType.Online;
break;
default:
continue;
}
viewModel.Game.MultiplayerModes.Add(new MultiplayerMode()
{
Type = type,
MaxPlayers = playerCount.Value,
MinPlayers = 2
});
}
}
if (result.GameModes != null && result.GameModes.Values != null)
viewModel.Game.Singleplayer = result.GameModes.Values.Any(gm => gm.Name == "Singleplayer");
#region Multiplayer Modes
if (result.MultiplayerModes != null && result.MultiplayerModes.Values != null)
{
var lan = result.MultiplayerModes.Values.Where(mm => mm.LanCoop.GetValueOrDefault()).OrderByDescending(mm => mm.OnlineMax).FirstOrDefault();
var online = result.MultiplayerModes.Values.Where(mm => mm.OnlineCoop.GetValueOrDefault()).OrderByDescending(mm => mm.OnlineMax).FirstOrDefault();
var offline = result.MultiplayerModes.Values.Where(mm => mm.OfflineCoop.GetValueOrDefault()).OrderByDescending(mm => mm.OnlineMax).FirstOrDefault();
if (lan != null)
{
viewModel.Game.MultiplayerModes.Add(new MultiplayerMode()
{
Type = MultiplayerType.Lan,
MaxPlayers = lan.OnlineMax.GetValueOrDefault(),
});
}
if (online != null)
{
viewModel.Game.MultiplayerModes.Add(new MultiplayerMode()
{
Type = MultiplayerType.Online,
MaxPlayers = online.OnlineMax.GetValueOrDefault(),
});
}
if (offline != null)
{
viewModel.Game.MultiplayerModes.Add(new MultiplayerMode()
{
Type = MultiplayerType.Local,
MaxPlayers = offline.OfflineMax.GetValueOrDefault(),
});
}
}
#endregion
#region Publishers & Developers
var companies = CompanyService.Get();
if (result.InvolvedCompanies != null && result.InvolvedCompanies.Values != null)
{
// Make sure companie
var developerNames = result.InvolvedCompanies.Values.Where(c => c.Developer.GetValueOrDefault()).Select(c => c.Company.Value.Name);
var publisherNames = result.InvolvedCompanies.Values.Where(c => c.Publisher.GetValueOrDefault()).Select(c => c.Company.Value.Name);
viewModel.Developers.AddRange(companies.Select(c => new SelectListItem()
{
Text = c.Name,
Value = c.Name,
Selected = developerNames.Contains(c.Name),
}));
viewModel.Publishers.AddRange(companies.Select(c => new SelectListItem()
{
Text = c.Name,
Value = c.Name,
Selected = publisherNames.Contains(c.Name),
}));
foreach (var developer in developerNames)
{
if (!viewModel.Developers.Any(d => d.Value == developer))
{
viewModel.Developers.Add(new SelectListItem()
{
Text = developer,
Value = developer,
Selected = true
});
}
}
foreach (var publisher in publisherNames)
{
if (!viewModel.Publishers.Any(d => d.Value == publisher))
{
viewModel.Publishers.Add(new SelectListItem()
{
Text = publisher,
Value = publisher,
Selected = true
});
}
}
viewModel.Developers = viewModel.Developers.OrderBy(d => d.Value).ToList();
viewModel.Publishers = viewModel.Publishers.OrderBy(d => d.Value).ToList();
}
#endregion
#region Genres
var genres = GenreService.Get();
if (result.Genres != null && result.Genres.Values != null)
{
var genreNames = result.Genres.Values.Select(g => g.Name);
viewModel.Genres.AddRange(genres.Select(g => new SelectListItem()
{
Text = g.Name,
Value = g.Name,
Selected = genreNames.Contains(g.Name),
}));
foreach (var genre in genreNames)
{
if (!viewModel.Genres.Any(g => g.Value == genre))
{
viewModel.Genres.Add(new SelectListItem()
{
Text = genre,
Value = genre,
Selected = true
});
}
}
viewModel.Genres = viewModel.Genres.OrderBy(g => g.Value).ToList();
}
#endregion
#region Tags
var tags = TagService.Get();
if (result.Keywords != null && result.Keywords.Values != null)
{
var tagNames = result.Keywords.Values.Select(t => t.Name).Take(20);
viewModel.Tags.AddRange(genres.Select(t => new SelectListItem()
{
Text = t.Name,
Value = t.Name,
Selected = tagNames.Contains(t.Name),
}));
foreach (var tag in tagNames)
{
if (!viewModel.Tags.Any(t => t.Value == tag))
{
viewModel.Tags.Add(new SelectListItem()
{
Text = tag,
Value = tag,
Selected = true
});
}
}
}
#endregion
return View(viewModel);
}
// POST: Games/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Add(GameViewModel viewModel)
{
if (ModelState.IsValid)
{
var game = await GameService.Add(viewModel.Game);
if (viewModel.SelectedDevelopers != null && viewModel.SelectedDevelopers.Length > 0)
game.Developers = viewModel.SelectedDevelopers.Select(async d => await CompanyService.AddMissing(x => x.Name == d, new Company() { Name = d })).Select(t => t.Result).ToList();
if (viewModel.SelectedPublishers != null && viewModel.SelectedPublishers.Length > 0)
game.Publishers = viewModel.SelectedPublishers.Select(async p => await CompanyService.AddMissing(x => x.Name == p, new Company() { Name = p })).Select(t => t.Result).ToList();
if (viewModel.SelectedGenres != null && viewModel.SelectedGenres.Length > 0)
game.Genres = viewModel.SelectedGenres.Select(async g => await GenreService.AddMissing(x => x.Name == g, new Genre() { Name = g })).Select(t => t.Result).ToList();
if (viewModel.SelectedTags != null && viewModel.SelectedTags.Length > 0)
game.Tags = viewModel.SelectedTags.Select(async t => await TagService.AddMissing(x => x.Name == t, new Tag() { Name = t })).Select(t => t.Result).ToList();
await GameService.Update(game);
return RedirectToAction(nameof(Edit), new { id = game.Id });
}
return View(viewModel.Game);
}
// GET: Games/Edit/5
public async Task<IActionResult> Edit(Guid? id)
{
var viewModel = new GameViewModel();
viewModel.Game = await GameService.Get(id.GetValueOrDefault());
if (viewModel.Game == null)
return NotFound();
viewModel.Developers = CompanyService.Get()
.OrderBy(c => c.Name)
.Select(c => new SelectListItem() { Text = c.Name, Value = c.Name, Selected = viewModel.Game.Developers.Any(d => d.Id == c.Id) })
.ToList();
viewModel.Publishers = CompanyService.Get()
.OrderBy(c => c.Name)
.Select(c => new SelectListItem() { Text = c.Name, Value = c.Name, Selected = viewModel.Game.Publishers.Any(d => d.Id == c.Id) })
.ToList();
viewModel.Genres = GenreService.Get()
.OrderBy(g => g.Name)
.Select(g => new SelectListItem() { Text = g.Name, Value = g.Name, Selected = viewModel.Game.Genres.Any(x => x.Id == g.Id) })
.ToList();
viewModel.Tags = TagService.Get()
.OrderBy(t => t.Name)
.Select(t => new SelectListItem() { Text = t.Name, Value = t.Name, Selected = viewModel.Game.Tags.Any(x => x.Id == t.Id) })
.ToList();
return View(viewModel);
}
// POST: Games/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(Guid id, GameViewModel viewModel)
{
if (id != viewModel.Game.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
var game = GameService.Get(g => g.Id == viewModel.Game.Id).FirstOrDefault();
game.Title = viewModel.Game.Title;
game.SortTitle = viewModel.Game.SortTitle;
game.DirectoryName = viewModel.Game.DirectoryName;
game.Icon = viewModel.Game.Icon;
game.Description = viewModel.Game.Description;
game.ReleasedOn = viewModel.Game.ReleasedOn;
game.Singleplayer = viewModel.Game.Singleplayer;
#region Update Developers
if (viewModel.SelectedDevelopers == null)
viewModel.SelectedDevelopers = new string[0];
foreach (var developer in game.Developers)
{
if (!viewModel.SelectedDevelopers.Any(d => d == developer.Name))
game.Developers.Remove(developer);
}
foreach (var newDeveloper in viewModel.SelectedDevelopers.Where(sd => !game.Developers.Any(d => d.Name == sd)))
{
game.Developers.Add(new Company()
{
Name = newDeveloper
});
}
#endregion
#region Update Publishers
if (viewModel.SelectedPublishers == null)
viewModel.SelectedPublishers = new string[0];
foreach (var publisher in game.Publishers)
{
if (!viewModel.SelectedPublishers.Any(p => p == publisher.Name))
game.Publishers.Remove(publisher);
}
foreach (var newPublisher in viewModel.SelectedPublishers.Where(sp => !game.Publishers.Any(p => p.Name == sp)))
{
game.Publishers.Add(new Company()
{
Name = newPublisher
});
}
#endregion
#region Update Genres
if (viewModel.SelectedGenres == null)
viewModel.SelectedGenres = new string[0];
foreach (var genre in game.Genres)
{
if (!viewModel.SelectedGenres.Any(g => g == genre.Name))
game.Genres.Remove(genre);
}
foreach (var newGenre in viewModel.SelectedGenres.Where(sg => !game.Genres.Any(g => g.Name == sg)))
{
game.Genres.Add(new Genre()
{
Name = newGenre
});
}
#endregion
#region Update Tags
if (viewModel.SelectedTags == null)
viewModel.SelectedTags = new string[0];
foreach (var tag in game.Tags)
{
if (!viewModel.SelectedTags.Any(t => t == tag.Name))
game.Tags.Remove(tag);
}
foreach (var newTag in viewModel.SelectedTags.Where(st => !game.Tags.Any(t => t.Name == st)))
{
game.Tags.Add(new Tag()
{
Name = newTag
});
}
#endregion
#region Update Actions
if (game.Actions != null)
{
game.Actions.Clear();
if (viewModel.Game.Actions != null)
{
foreach (var action in viewModel.Game.Actions)
{
game.Actions.Add(action);
}
}
}
#endregion
#region Update MultiplayerModes
if (game.MultiplayerModes != null)
{
game.MultiplayerModes.Clear();
if (viewModel.Game.MultiplayerModes != null)
{
foreach (var multiplayerMode in viewModel.Game.MultiplayerModes)
{
game.MultiplayerModes.Add(multiplayerMode);
}
}
}
#endregion
await GameService.Update(game);
return RedirectToAction(nameof(Edit), new { id = id });
}
return View(viewModel);
}
// GET: Games/Delete/5
public async Task<IActionResult> Delete(Guid? id)
{
var game = await GameService.Get(id.GetValueOrDefault());
if (game == null)
{
return NotFound();
}
return View(game);
}
// POST: Games/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(Guid id)
{
var game = await GameService.Get(id);
if (game == null)
return NotFound();
await GameService.Delete(game);
return RedirectToAction(nameof(Index));
}
[HttpPost]
public async Task<IActionResult> Lookup(Game game)
{
var viewModel = new GameLookupResultsViewModel()
{
Search = game.Title
};
var results = await IGDBService.Search(game.Title, "involved_companies.*", "involved_companies.company.*");
if (results == null)
return View(new List<Game>());
viewModel.Results = results.Select(r =>
{
var result = new Game()
{
IGDBId = r.Id.GetValueOrDefault(),
Title = r.Name,
ReleasedOn = r.FirstReleaseDate.GetValueOrDefault().UtcDateTime,
Developers = new List<Company>()
};
if (r.InvolvedCompanies != null && r.InvolvedCompanies.Values != null)
{
result.Developers = r.InvolvedCompanies.Values.Where(c => c.Developer.HasValue && c.Developer.GetValueOrDefault() && c.Company != null && c.Company.Value != null).Select(c => new Company()
{
Name = c.Company.Value.Name
}).ToList();
}
return result;
});
return View(viewModel);
}
/// <summary>
/// Provides a list of possible games based on the given name
/// </summary>
/// <param name="name">Name of the game to lookup against IGDB</param>
/// <returns></returns>
public async Task<IActionResult> SearchMetadata(string name)
{
var metadata = await IGDBService.Search(name, "genres.*", "multiplayer_modes.*", "release_dates.*", "platforms.*", "keywords.*", "involved_companies.*", "involved_companies.company.*", "cover.*");
if (metadata == null)
return NotFound();
return Json(metadata);
}
public async Task<IActionResult> GetIcon(Guid id)
{
try
{
var game = await GameService.Get(id);
return File(GameService.GetIcon(game), "image/png");
}
catch (FileNotFoundException ex)
{
return NotFound();
}
}
}
}

View file

@ -1,110 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using LANCommander.Data;
using LANCommander.Data.Models;
using Microsoft.AspNetCore.Authorization;
using LANCommander.Models;
using LANCommander.Services;
namespace LANCommander.Controllers
{
[Authorize(Roles = "Administrator")]
public class KeysController : Controller
{
private readonly DatabaseContext Context;
private readonly KeyService KeyService;
public KeysController(DatabaseContext context, KeyService keyService)
{
Context = context;
KeyService = keyService;
}
public async Task<IActionResult> Details(Guid? id)
{
using (var repo = new Repository<Game>(Context, HttpContext))
{
var game = await repo.Find(id.GetValueOrDefault());
if (game == null)
return NotFound();
return View(game);
}
}
public async Task<IActionResult> Edit(Guid? id)
{
using (var repo = new Repository<Game>(Context, HttpContext))
{
var game = await repo.Find(id.GetValueOrDefault());
if (game == null)
return NotFound();
var viewModel = new EditKeysViewModel()
{
Game = game,
Keys = String.Join("\n", game.Keys.OrderByDescending(k => k.ClaimedOn).Select(k => k.Value))
};
return View(viewModel);
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(Guid id, EditKeysViewModel viewModel)
{
var keys = viewModel.Keys.Split("\n").Select(k => k.Trim()).Where(k => !String.IsNullOrWhiteSpace(k));
using (var gameRepo = new Repository<Game>(Context, HttpContext))
{
var game = await gameRepo.Find(id);
if (game == null)
return NotFound();
using (var keyRepo = new Repository<Key>(Context, HttpContext))
{
var existingKeys = keyRepo.Get(k => k.Game.Id == id).ToList();
var keysDeleted = existingKeys.Where(k => !keys.Contains(k.Value));
var keysAdded = keys.Where(k => !existingKeys.Any(e => e.Value == k));
foreach (var key in keysDeleted)
keyRepo.Delete(key);
foreach (var key in keysAdded)
await keyRepo.Add(new Key()
{
Game = game,
Value = key,
});
await keyRepo.SaveChanges();
}
}
return RedirectToAction("Edit", "Games", new { id = id });
}
public async Task<IActionResult> Release(Guid id)
{
var existing = await KeyService.Get(id);
if (existing == null)
return NotFound();
await KeyService.Release(id);
return RedirectToAction("Details", "Keys", new { id = existing.Game.Id });
}
}
}

View file

@ -1,102 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using LANCommander.Data;
using LANCommander.Data.Models;
using Microsoft.AspNetCore.Authorization;
using LANCommander.Models;
using LANCommander.Services;
namespace LANCommander.Controllers
{
[Authorize(Roles = "Administrator")]
public class ScriptsController : BaseController
{
private readonly GameService GameService;
private readonly ScriptService ScriptService;
public ScriptsController(GameService gameService, ScriptService scriptService)
{
GameService = gameService;
ScriptService = scriptService;
}
public async Task<IActionResult> Add(Guid? id)
{
var game = await GameService.Get(id.GetValueOrDefault());
if (game == null)
return NotFound();
var script = new Script()
{
GameId = game.Id,
Game = game
};
return View(script);
}
[HttpPost]
public async Task<IActionResult> Add(Script script)
{
script.Id = Guid.Empty;
if (ModelState.IsValid)
{
script = await ScriptService.Add(script);
return RedirectToAction("Edit", "Games", new { id = script.GameId });
}
return View(script);
}
public async Task<IActionResult> Edit(Guid? id)
{
var script = await ScriptService.Get(id.GetValueOrDefault());
if (script == null)
return NotFound();
return View(script);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(Guid id, Script script)
{
if (ModelState.IsValid)
{
await ScriptService.Update(script);
Alert("The script has been saved!", "success");
return RedirectToAction("Edit", "Games", new { id = script.GameId });
}
script.Game = await GameService.Get(script.GameId.GetValueOrDefault());
return View(script);
}
public async Task<IActionResult> Delete(Guid? id)
{
var script = await ScriptService.Get(id.GetValueOrDefault());
if (script == null)
return NotFound();
var gameId = script.GameId;
await ScriptService.Delete(script);
return RedirectToAction("Edit", "Games", new { id = gameId });
}
}
}

View file

@ -1,166 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using LANCommander.Data;
using LANCommander.Data.Models;
using Microsoft.AspNetCore.Authorization;
namespace LANCommander.Controllers
{
[Authorize(Roles = "Administrator")]
public class TagsController : Controller
{
private readonly DatabaseContext _context;
public TagsController(DatabaseContext context)
{
_context = context;
}
// GET: Tags
public async Task<IActionResult> Index()
{
return _context.Tags != null ?
View(await _context.Tags.ToListAsync()) :
Problem("Entity set 'DatabaseContext.Tags' is null.");
}
// GET: Tags/Details/5
public async Task<IActionResult> Details(Guid? id)
{
if (id == null || _context.Tags == null)
{
return NotFound();
}
var tag = await _context.Tags
.FirstOrDefaultAsync(m => m.Id == id);
if (tag == null)
{
return NotFound();
}
return View(tag);
}
// GET: Tags/Create
public IActionResult Create()
{
return View();
}
// POST: Tags/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Name,Id,CreatedOn,CreatedById,UpdatedOn,UpdatedById")] Tag tag)
{
if (ModelState.IsValid)
{
tag.Id = Guid.NewGuid();
_context.Add(tag);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(tag);
}
// GET: Tags/Edit/5
public async Task<IActionResult> Edit(Guid? id)
{
if (id == null || _context.Tags == null)
{
return NotFound();
}
var tag = await _context.Tags.FindAsync(id);
if (tag == null)
{
return NotFound();
}
return View(tag);
}
// POST: Tags/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(Guid id, [Bind("Name,Id,CreatedOn,CreatedById,UpdatedOn,UpdatedById")] Tag tag)
{
if (id != tag.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(tag);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TagExists(tag.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(tag);
}
// GET: Tags/Delete/5
public async Task<IActionResult> Delete(Guid? id)
{
if (id == null || _context.Tags == null)
{
return NotFound();
}
var tag = await _context.Tags
.FirstOrDefaultAsync(m => m.Id == id);
if (tag == null)
{
return NotFound();
}
return View(tag);
}
// POST: Tags/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(Guid id)
{
if (_context.Tags == null)
{
return Problem("Entity set 'DatabaseContext.Tags' is null.");
}
var tag = await _context.Tags.FindAsync(id);
if (tag != null)
{
_context.Tags.Remove(tag);
}
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool TagExists(Guid id)
{
return (_context.Tags?.Any(e => e.Id == id)).GetValueOrDefault();
}
}
}

View file

@ -1,10 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>aspnet-LANCommander-C1F79CFA-9767-4AD7-BD5A-2549F8328A2D</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<Content Remove="wwwroot\js\Upload.ts" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AntDesign" Version="0.14.4" />
@ -24,6 +27,10 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.5" />
<PackageReference Include="rix0rrr.BeaconLib" Version="1.0.2" />
@ -45,6 +52,10 @@
<ProjectReference Include="..\LANCommander.SDK\LANCommander.SDK.csproj" />
</ItemGroup>
<ItemGroup>
<TypeScriptCompile Include="wwwroot\js\Upload.ts" />
</ItemGroup>
<ItemGroup>
<TypeScriptCompile Update="wwwroot\js\Upload.ts">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>

View file

@ -1,10 +0,0 @@
using LANCommander.Data.Models;
namespace LANCommander.Models
{
public class EditKeysViewModel
{
public Game Game { get; set; }
public string Keys { get; set; }
}
}

View file

@ -1,10 +0,0 @@
using LANCommander.Data.Models;
namespace LANCommander.Models
{
public class GameLookupResultsViewModel
{
public string Search { get; set; }
public IEnumerable<Game> Results { get; set; }
}
}

View file

@ -1,21 +0,0 @@
using LANCommander.Data.Models;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace LANCommander.Models
{
public class GameViewModel
{
public long? IgdbId { get; set; }
public Game Game { get; set; }
public List<SelectListItem>? Genres { get; set; }
public List<SelectListItem>? Tags { get; set; }
public List<SelectListItem>? Categories { get; set; }
public List<SelectListItem>? Developers { get; set; }
public List<SelectListItem>? Publishers { get; set; }
public string[]? SelectedGenres { get; set; }
public string[]? SelectedTags { get; set; }
public string[]? SelectedCategories { get; set; }
public string[]? SelectedDevelopers { get; set; }
public string[]? SelectedPublishers { get; set; }
}
}

View file

@ -1,23 +0,0 @@
@page "/Games/{id:guid}/Browse"
@inject GameService GameService
<Card Title="Archive Browser" SubTitle="@Game.Title">
<Table>
<ArchiveBrowser ArchiveId="@Archive.Id" />
</Table>
</Card>
@code {
[Parameter] public Guid Id { get; set; }
private Game Game { get; set; }
private Archive Archive { get; set; }
protected override async Task OnInitializedAsync()
{
Game = await GameService.Get(Id);
if (Game.Archives != null && Game.Archives.Count > 0)
Archive = Game.Archives.OrderByDescending(a => a.CreatedOn).First();
}
}

View file

@ -29,5 +29,7 @@
<script src="~/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
<script src="~/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
<script src="~/js/site.js"></script>
<script src="~/js/Upload.js"></script>
<script>window.Uploader = new Uploader();</script>
</body>
</html>

View file

@ -1,127 +0,0 @@
@model LANCommander.Data.Models.Archive
@{
ViewData["Title"] = "Add Archive";
}
<div class="container-xl">
<!-- Page title -->
<div class="page-header d-print-none">
<div class="row align-items-center">
<div class="col">
<div class="page-pretitle">@Model.Game.Title</div>
<h2 class="page-title">
Add Archive
</h2>
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="row row-cards">
<div class="col-12">
<form asp-action="Add" enctype="multipart/form-data" class="card">
<fieldset>
<div class="card-body">
<div class="row">
<div class="col-12">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="mb-3">
<label asp-for="Version" class="control-label"></label>
<input asp-for="Version" class="form-control" />
<span asp-validation-for="Version" class="text-danger"></span>
@if (Model.LastVersion != null && !String.IsNullOrWhiteSpace(Model.LastVersion.Version))
{
<small class="form-hint">Last version: @Model.LastVersion.Version</small>
}
</div>
<div class="mb-3">
<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="mb-3">
<label for="File" class="control-label">File</label>
<input type="file" id="File" class="form-control" />
</div>
<div>
<div class="progress h-4">
<div class="progress-bar" role="progressbar" style="width: 0%"></div>
</div>
</div>
<input type="hidden" asp-for="GameId" />
<input type="hidden" asp-for="LastVersion.Id" />
<input type="hidden" asp-for="ObjectKey" />
</div>
</div>
</div>
<div class="card-footer">
<div class="d-flex">
<a asp-action="Edit" asp-controller="Games" asp-route-id="@Model.Game.Id" class="btn btn-ghost-primary">Cancel</a>
<button class="btn btn-primary ms-auto" id="UploadButton">Upload</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="~/js/Upload.js"></script>
<script>
var uploader = new Uploader();
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%');
};
uploader.OnComplete = (id, key) => {
$('#Id').val(id);
$('#ObjectKey').val(key);
$('.progress-bar')
.css('width', '100%')
.removeClass('progress-bar-striped')
.removeClass('progress-bar-animated')
.addClass('bg-success')
.text('Upload Complete!');
setTimeout(() => {
window.location.href = '@Url.Action("Edit", "Games", new { id = Model.Game.Id })';
}, 2000);
};
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('Upload Error!');
};
</script>
}

View file

@ -1,32 +0,0 @@
@using LANCommander.Components;
@model LANCommander.Data.Models.Archive
@{
ViewData["Title"] = "Browse Archive";
}
<div class="container-xl">
<!-- Page title -->
<div class="page-header d-print-none">
<div class="row align-items-center">
<div class="col">
<div class="page-pretitle">@Model.Game.Title</div>
<h2 class="page-title">
Browse Archive
</h2>
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="row row-cards">
<div class="col-12">
<div class="card">
<component type="typeof(ArchiveBrowser)" render-mode="Server" param-ArchiveId="Model.Id" />
</div>
</div>
</div>
</div>
</div>

View file

@ -1,43 +0,0 @@
@model LANCommander.Data.Models.Company
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Company</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="CreatedOn" class="control-label"></label>
<input asp-for="CreatedOn" class="form-control" />
<span asp-validation-for="CreatedOn" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="UpdatedOn" class="control-label"></label>
<input asp-for="UpdatedOn" class="form-control" />
<span asp-validation-for="UpdatedOn" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View file

@ -1,39 +0,0 @@
@model LANCommander.Data.Models.Company
@{
ViewData["Title"] = "Delete";
}
<h1>Delete</h1>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Company</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Name)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.CreatedOn)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.CreatedOn)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.UpdatedOn)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.UpdatedOn)
</dd>
</dl>
<form asp-action="Delete">
<input type="hidden" asp-for="Id" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-action="Index">Back to List</a>
</form>
</div>

View file

@ -1,36 +0,0 @@
@model LANCommander.Data.Models.Company
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Company</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Name)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.CreatedOn)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.CreatedOn)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.UpdatedOn)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.UpdatedOn)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model?.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>

View file

@ -1,44 +0,0 @@
@model LANCommander.Data.Models.Company
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Company</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="CreatedOn" class="control-label"></label>
<input asp-for="CreatedOn" class="form-control" />
<span asp-validation-for="CreatedOn" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="UpdatedOn" class="control-label"></label>
<input asp-for="UpdatedOn" class="form-control" />
<span asp-validation-for="UpdatedOn" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View file

@ -1,47 +0,0 @@
@model IEnumerable<LANCommander.Data.Models.Company>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.CreatedOn)
</th>
<th>
@Html.DisplayNameFor(model => model.UpdatedOn)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.CreatedOn)
</td>
<td>
@Html.DisplayFor(modelItem => item.UpdatedOn)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

View file

@ -1,125 +0,0 @@
@using LANCommander.Components
@model LANCommander.Models.GameViewModel
@{
ViewData["Title"] = "Add Game";
}
<div class="container-xl">
<!-- Page title -->
<div class="page-header d-print-none">
<div class="row align-items-center">
<div class="col">
<h2 class="page-title">
Add Game
</h2>
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="row row-cards">
<div class="col-12">
<form asp-action="Add" class="card">
<div class="card-body">
<div class="row">
<div class="col-12">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="mb-3">
<label asp-for="Game.Title" class="control-label"></label>
<div class="input-group">
<input asp-for="Game.Title" class="form-control" />
<button class="btn" type="submit" asp-action="Lookup">Lookup</button>
</div>
<span asp-validation-for="Game.Title" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="Game.SortTitle" class="control-label"></label>
<input asp-for="Game.SortTitle" class="form-control" />
<span asp-validation-for="Game.SortTitle" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="Game.Icon" class="control-label"></label>
<input asp-for="Game.Icon" class="form-control" />
<span asp-validation-for="Game.Icon" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="Game.Description" class="control-label"></label>
<textarea asp-for="Game.Description" class="form-control" data-bs-toggle="autosize"></textarea>
<span asp-validation-for="Game.Description" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="Game.ReleasedOn" class="control-label"></label>
<input asp-for="Game.ReleasedOn" class="form-control" />
<span asp-validation-for="Game.ReleasedOn" class="text-danger"></span>
</div>
<div class="mb-3">
<label class="form-check">
<input asp-for="Game.Singleplayer" type="checkbox" class="form-check-input" />
<span class="form-check-label">Singleplayer</span>
<span class="form-check-description">Game has a singleplayer mode</span>
</label>
</div>
<div class="mb-3">
<label asp-for="Developers" class="control-label"></label>
<input type="text" class="developer-select" />
<select asp-for="SelectedDevelopers" class="d-none"></select>
</div>
<div class="mb-3">
<label asp-for="Publishers" class="control-label"></label>
<input type="text" class="publisher-select" />
<select asp-for="SelectedPublishers" class="d-none"></select>
</div>
<div class="mb-3">
<label asp-for="Genres" class="control-label"></label>
<input type="text" class="genre-select" />
<select asp-for="SelectedGenres" class="d-none"></select>
</div>
<div class="mb-3">
<label asp-for="Tags" class="control-label"></label>
<input type="text" class="tag-select" />
<select asp-for="SelectedTags" class="d-none"></select>
</div>
</div>
</div>
</div>
<div class="card-header">
<h3 class="card-title">Actions</h3>
</div>
<component type="typeof(ActionEditor)" render-mode="Server" param-Actions="Model.Game.Actions.ToList()" param-GameId="Model.Game.Id" />
<div class="card-header">
<h3 class="card-title">Multiplayer Modes</h3>
</div>
<component type="typeof(MultiplayerModeEditor)" render-mode="Server" param-MultiplayerModes="Model.Game.MultiplayerModes" param-GameId="Model.Game.Id" />
<div class="card-footer">
<div class="d-flex">
<a asp-action="Index" class="btn btn-ghost-primary">Cancel</a>
<button type="submit" class="btn btn-primary ms-auto">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
<script>
new Select('.developer-select', @Html.Raw(Json.Serialize(Model.Developers)));
new Select('.publisher-select', @Html.Raw(Json.Serialize(Model.Publishers)));
new Select('.genre-select', @Html.Raw(Json.Serialize(Model.Genres)));
new Select('.tag-select', @Html.Raw(Json.Serialize(Model.Tags)));
</script>
}

View file

@ -1,65 +0,0 @@
@model LANCommander.Data.Models.Game
@{
ViewData["Title"] = "Delete";
}
<div class="container container-tight py-4">
<div class="page-header">
<div class="row align-items-center">
<div class="col">
<h2 class="page-title">Delete @Model.Title?</h2>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<p class="text-muted">Are you sure you want to delete this game?
@if (Model.Archives != null && Model.Archives.Count > 0)
{
<span>It will also delete the following archives:</span>
}
</p>
</div>
@if (Model.Archives != null && Model.Archives.Count > 0)
{
<div class="table-responsive">
<table class="table table-vcenter table-mobile-md card-table">
<thead>
<tr>
<th>Version</th>
<th>Uploaded By</th>
<th>Uploaded On</th>
<th>Size</th>
</tr>
</thead>
<tbody>
@foreach (var archive in Model.Archives.OrderByDescending(a => a.CreatedOn))
{
<tr>
<td>@Html.DisplayFor(m => archive.Version)</td>
<td>@Html.DisplayFor(m => archive.CreatedBy.UserName)</td>
<td>@Html.DisplayFor(m => archive.CreatedOn)</td>
<td>@ByteSizeLib.ByteSize.FromBytes(archive.CompressedSize)</td>
</tr>
}
</tbody>
</table>
</div>
}
<div class="card-footer">
<div class="d-flex justify-content-between">
<a asp-action="Index" class="btn btn-ghost-primary">Cancel</a>
<form asp-action="Delete">
<input type="hidden" asp-for="Id" />
<button type="submit" class="btn btn-danger ms-auto">Delete</button>
</form>
</div>
</div>
</div>
</div>

View file

@ -1,303 +0,0 @@
@using LANCommander.Components
@using LANCommander.Data.Models
@model LANCommander.Models.GameViewModel
@{
ViewData["Title"] = "Edit";
}
<div class="container-xl">
<!-- Page title -->
<div class="page-header d-print-none">
<div class="row align-items-center">
<div class="col">
<h2 class="page-title">
Edit Game
</h2>
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="row row-cards">
<div class="col-12">
<form asp-action="Edit" class="card">
<div class="card-body">
<div class="row">
<div class="col-12">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="mb-3">
<label asp-for="Game.Title" class="control-label"></label>
<div class="input-group">
<input asp-for="Game.Title" class="form-control" />
<button class="btn" type="submit" asp-action="Lookup">Lookup</button>
</div>
<span asp-validation-for="Game.Title" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="Game.SortTitle" class="control-label"></label>
<input asp-for="Game.SortTitle" class="form-control" />
<span asp-validation-for="Game.SortTitle" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="Game.Icon" class="control-label"></label>
<input asp-for="Game.Icon" class="form-control" />
<span asp-validation-for="Game.Icon" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="Game.Description" class="control-label"></label>
<textarea asp-for="Game.Description" class="form-control" data-bs-toggle="autosize"></textarea>
<span asp-validation-for="Game.Description" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="Game.ReleasedOn" class="control-label"></label>
<input asp-for="Game.ReleasedOn" class="form-control" />
<span asp-validation-for="Game.ReleasedOn" class="text-danger"></span>
</div>
<div class="mb-3">
<label class="form-check">
<input asp-for="Game.Singleplayer" type="checkbox" class="form-check-input" />
<span class="form-check-label">Singleplayer</span>
<span class="form-check-description">Game has a singleplayer mode</span>
</label>
</div>
<div class="mb-3">
<label asp-for="Developers" class="control-label"></label>
<input type="text" class="developer-select" />
<select asp-for="SelectedDevelopers" class="d-none"></select>
</div>
<div class="mb-3">
<label asp-for="Publishers" class="control-label"></label>
<input type="text" class="publisher-select" />
<select asp-for="SelectedPublishers" class="d-none"></select>
</div>
<div class="mb-3">
<label asp-for="Genres" class="control-label"></label>
<input type="text" class="genre-select" />
<select asp-for="SelectedGenres" class="d-none"></select>
</div>
<div class="mb-3">
<label asp-for="Tags" class="control-label"></label>
<input type="text" class="tag-select" />
<select asp-for="SelectedTags" class="d-none"></select>
</div>
<input type="hidden" asp-for="Game.Id" />
</div>
</div>
</div>
<div class="card-header">
<h3 class="card-title">Actions</h3>
</div>
<component type="typeof(ActionEditor)" render-mode="Server" param-Actions="Model.Game.Actions.ToList()" param-GameId="Model.Game.Id" />
<div class="card-header">
<h3 class="card-title">Multiplayer Modes</h3>
</div>
<component type="typeof(MultiplayerModeEditor)" render-mode="Server" param-MultiplayerModes="Model.Game.MultiplayerModes" param-GameId="Model.Game.Id" />
<div class="card-footer">
<div class="d-flex">
<a asp-action="Index" class="btn btn-ghost-primary">Cancel</a>
<button type="submit" class="btn btn-primary ms-auto">Save</button>
</div>
</div>
</form>
</div>
<div class="col-12">
<div class="card">
@if (Model.Game.Keys != null && Model.Game.Keys.Count > 0)
{
<div class="card-header">
<h3 class="card-title">Keys</h3>
<div class="card-actions">
<a asp-action="Details" asp-controller="Keys" asp-route-id="@Model.Game.Id" class="btn btn-ghost-primary">
Details
</a>
</div>
</div>
var keysAvailable = Model.Game.Keys.Count(k =>
{
return (k.AllocationMethod == KeyAllocationMethod.MacAddress && String.IsNullOrWhiteSpace(k.ClaimedByMacAddress)) ||
(k.AllocationMethod == KeyAllocationMethod.UserAccount && k.ClaimedByUser == null);
});
<div class="card-body">
<div class="datagrid text-center">
<div class="datagrid-item">
<div class="datagrid-title">Available</div>
<div class="datagrid-content">
@keysAvailable
</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">Claimed</div>
<div class="datagrid-content">
@(Model.Game.Keys.Count - keysAvailable)
</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">Total</div>
<div class="datagrid-content">
@Model.Game.Keys.Count
</div>
</div>
</div>
</div>
}
else
{
<div class="empty">
<p class="empty-title">No Keys</p>
<p class="empty-subtitle text-muted">There have been no keys added for this game.</p>
<div class="empty-action">
<a asp-action="Edit" asp-controller="Keys" asp-route-id="@Model.Game.Id" class="btn btn-primary">Edit Keys</a>
</div>
</div>
}
</div>
</div>
<div class="col-12">
<div class="card">
@if (Model.Game.Archives != null && Model.Game.Archives.Count > 0)
{
<div class="card-header">
<h3 class="card-title">Archives</h3>
<div class="card-actions">
<a asp-action="Add" asp-controller="Archives" asp-route-id="@Model.Game.Id" class="btn btn-primary">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
Add
</a>
</div>
</div>
<div class="table-responsive">
<table class="table table-vcenter table-mobile-md card-table">
<thead>
<tr>
<th>Version</th>
<th>Uploaded By</th>
<th>Uploaded On</th>
<th>Size</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var archive in Model.Game.Archives.OrderByDescending(a => a.CreatedOn))
{
<tr>
<td>@Html.DisplayFor(m => archive.Version)</td>
<td>@Html.DisplayFor(m => archive.CreatedBy.UserName)</td>
<td>@Html.DisplayFor(m => archive.CreatedOn)</td>
<td>@ByteSizeLib.ByteSize.FromBytes(new FileInfo(System.IO.Path.Combine("Upload", archive.ObjectKey)).Length)</td>
<td>
<div class="btn-list flex-nowrap justify-content-end">
<a asp-action="Download" asp-controller="Archives" asp-route-id="@archive.Id" class="btn">Download</a>
<a asp-action="Browse" asp-controller="Archives" asp-route-id="@archive.Id" class="btn">Browse</a>
<a asp-action="Delete" asp-controller="Archives" asp-route-id="@archive.Id" class="btn btn-danger">Delete</a>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
}
else
{
<div class="empty">
<p class="empty-title">No Archives</p>
<p class="empty-subtitle text-muted">There have been no archives uploaded for this game.</p>
<div class="empty-action">
<a asp-action="Add" asp-controller="Archives" asp-route-id="@Model.Game.Id" class="btn btn-primary">Upload Archive</a>
</div>
</div>
}
</div>
</div>
<div class="col-12">
<div class="card">
@if (Model.Game.Scripts != null && Model.Game.Scripts.Count > 0)
{
<div class="card-header">
<h3 class="card-title">Scripts</h3>
<div class="card-actions">
<a asp-action="Add" asp-controller="Scripts" asp-route-id="@Model.Game.Id" class="btn btn-primary">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
Add
</a>
</div>
</div>
<div class="table-responsive">
<table class="table table-vcenter table-mobile-md card-table">
<thead>
<tr>
<th>Type</th>
<th>Name</th>
<th>Created On</th>
<th>Created By</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var script in Model.Game.Scripts.OrderBy(s => s.Type).ThenByDescending(s => s.CreatedOn))
{
<tr>
<td>@Html.DisplayFor(m => script.Type)</td>
<td>@Html.DisplayFor(m => script.Name)</td>
<td>@Html.DisplayFor(m => script.CreatedOn)</td>
<td>@Html.DisplayFor(m => script.CreatedBy.UserName)</td>
<td>
<div class="btn-list flex-nowrap justify-content-end">
<a asp-action="Edit" asp-controller="Scripts" asp-route-id="@script.Id" class="btn">Edit</a>
<a asp-action="Delete" asp-controller="Scripts" asp-route-id="@script.Id" class="btn btn-danger">Delete</a>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
}
else
{
<div class="empty">
<p class="empty-title">No Scripts</p>
<p class="empty-subtitle text-muted">There have been no scripts added for this game.</p>
<div class="empty-action">
<a asp-action="Add" asp-controller="Scripts" asp-route-id="@Model.Game.Id" class="btn btn-primary">Add Script</a>
</div>
</div>
}
</div>
</div>
</div>
</div>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
<script>
new Select('.developer-select', @Html.Raw(Json.Serialize(Model.Developers)));
new Select('.publisher-select', @Html.Raw(Json.Serialize(Model.Publishers)));
new Select('.genre-select', @Html.Raw(Json.Serialize(Model.Genres)));
new Select('.tag-select', @Html.Raw(Json.Serialize(Model.Tags)));
</script>
}

View file

@ -1,110 +0,0 @@
@model IEnumerable<LANCommander.Data.Models.Game>
@{
ViewData["Title"] = "Games";
}
<div class="container-xl">
<div class="page-header d-print-none">
<div class="row align-items-center">
<div class="col">
<h2 class="page-title">
Games
</h2>
</div>
<div class="col-auto ms-auto">
<div class="btn-list">
<a asp-action="Add" class="btn btn-primary d-none d-sm-inline-block">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
Add
</a>
</div>
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="row row-cards">
<div class="col-12">
<div class="card">
<div class="table-responsive">
<table class="table table-vcenter table-mobile-md card-table">
<thead>
<tr>
<th></th>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.SortTitle)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleasedOn)
</th>
<th>
@Html.DisplayNameFor(model => model.CreatedOn)
</th>
<th>
@Html.DisplayNameFor(model => model.CreatedBy)
</th>
<th>
@Html.DisplayNameFor(model => model.UpdatedOn)
</th>
<th>
@Html.DisplayNameFor(model => model.UpdatedBy)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.OrderBy(g => !String.IsNullOrWhiteSpace(g.SortTitle) ? g.SortTitle : g.Title))
{
<tr>
<td>
@if (!String.IsNullOrWhiteSpace(item.Icon)) {
<img src="@Url.Action("GetIcon", "Games", new { id = item.Id })" />
}
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.SortTitle)
</td>
<td>
@if (item.ReleasedOn.HasValue)
{
@item.ReleasedOn.Value.ToString("MM/dd/yyyy")
}
</td>
<td>
@Html.DisplayFor(modelItem => item.CreatedOn)
</td>
<td>
@Html.DisplayFor(modelItem => item.CreatedBy.UserName)
</td>
<td>
@Html.DisplayFor(modelItem => item.UpdatedOn)
</td>
<td>
@Html.DisplayFor(modelItem => item.UpdatedBy.UserName)
</td>
<td>
<div class="btn-list flex-nowrap justify-content-end">
<a asp-action="Edit" asp-route-id="@item.Id" class="btn">Edit</a>
<a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-danger">Delete</a>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,99 +0,0 @@
@model LANCommander.Models.GameLookupResultsViewModel
@{
ViewData["Title"] = "Games";
}
<div class="container-xl">
<div class="page-header d-print-none">
<div class="row align-items-center">
<div class="col">
<div class="page-pretitle">@Model.Search</div>
<h2 class="page-title">
Game Lookup
</h2>
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="row row-cards">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Results</h3>
</div>
@if (Model.Results.Count() == 0)
{
<div class="card-body">
<p>No games could be found with the search "@Model.Search".</p>
</div>
}
else
{
<div class="card-body">
@if (Model.Results.Count() > 1)
{
<p>There was a total of @Model.Results.Count() games that matched the search "@Model.Search" in IGDB's database.</p>
}
else
{
<p>Only one game matched the search "@Model.Search" in IGDB's database.</p>
}
</div>
<form>
<div class="table-responsive">
<table class="table table-vcenter table-mobile-md card-table">
<thead>
<tr>
<th>
Title
</th>
<th>
Release Date
</th>
<th>
Developers
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Results)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleasedOn)
</td>
<td>
@String.Join(", ", item.Developers.Select(d => d.Name))
</td>
<td>
<div class="btn-list flex-nowrap justify-content-end">
<a asp-action="Add" asp-route-igdbid="@item.IGDBId" class="btn btn-ghost-primary">Select</a>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
</form>
}
<div class="card-footer">
<div class="d-flex">
<a class="btn btn-ghost-primary" asp-action="Add" asp-controller="Games">Go Back</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,106 +0,0 @@
@using LANCommander.Data.Models
@model LANCommander.Data.Models.Game
@{
ViewData["Title"] = "Keys | " + Model.Title;
}
<div class="container-xl">
<!-- Page title -->
<div class="page-header d-print-none">
<div class="row align-items-center">
<div class="col">
<div class="page-pretitle">@Model.Title</div>
<h2 class="page-title">
Keys
</h2>
</div>
<div class="col-auto ms-auto">
<div class="btn-list">
<a asp-action="Edit" asp-controller="Games" asp-route-id="@Model.Id" class="btn btn-ghost-primary">Back</a>
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-primary d-none d-sm-inline-block">Edit</a>
</div>
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="row row-cards">
<div class="col-12">
<form asp-action="Edit" class="card">
@if (Model.Keys != null && Model.Keys.Count > 0)
{
<div class="table-responsive">
<table class="table table-vcenter table-mobile-md card-table">
<thead>
<tr>
<th>Key</th>
<th>Allocation Method</th>
<th>Claimed By</th>
<th>Claimed On</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var key in Model.Keys.OrderByDescending(k => k.ClaimedOn))
{
<tr>
<td class="game-key">@Html.DisplayFor(m => key.Value)</td>
<td>@Html.DisplayFor(m => key.AllocationMethod)</td>
<td>
@switch (key.AllocationMethod)
{
case KeyAllocationMethod.MacAddress:
<text>@key.ClaimedByMacAddress</text>
break;
case KeyAllocationMethod.UserAccount:
<text>@key.ClaimedByUser?.UserName</text>
break;
}
</td>
<td>@key.ClaimedOn</td>
<td>
<div class="btn-list flex-nowrap justify-content-end">
@if ((key.AllocationMethod == KeyAllocationMethod.MacAddress && !String.IsNullOrWhiteSpace(key.ClaimedByMacAddress)) || (key.AllocationMethod == KeyAllocationMethod.UserAccount && key.ClaimedByUser != null))
{
<a asp-action="Release" asp-controller="Keys" asp-route-id="@key.Id" class="btn btn-sm btn-ghost-dark">Release</a>
}
<a asp-action="Delete" asp-controller="Keys" asp-route-id="@key.Id" class="btn btn-sm btn-ghost-danger">Delete</a>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
}
else
{
<div class="empty">
<p class="empty-title">No Key</p>
<p class="empty-subtitle text-muted">There have been no keys added for this game.</p>
<div class="empty-action">
<a asp-action="Edit" asp-controller="Keys" asp-route-id="@Model.Id" class="btn btn-primary">Edit Keys</a>
</div>
</div>
}
</form>
</div>
</div>
</div>
</div>
<style>
.game-key {
font-family: var(--tblr-font-monospace);
}
</style>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

View file

@ -1,73 +0,0 @@
@model LANCommander.Models.EditKeysViewModel
@{
ViewData["Title"] = "Edit Keys | " + Model.Game.Title;
}
<div class="container-xl">
<!-- Page title -->
<div class="page-header d-print-none">
<div class="row align-items-center">
<div class="col">
<div class="page-pretitle">@Model.Game.Title</div>
<h2 class="page-title">
Edit Keys
</h2>
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="row row-cards">
<div class="col-12">
<form asp-action="Edit" class="card">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Keys" />
<input type="hidden" asp-for="Game.Id" />
<div id="KeyEditor" style="height: 100%; min-height: 600px;"></div>
<div class="card-footer">
<div class="d-flex">
<a asp-action="Details" asp-route-id="@Model.Game.Id" class="btn btn-ghost-primary">Cancel</a>
<button type="submit" class="btn btn-primary ms-auto">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
<script src="~/lib/monaco-editor/min/vs/loader.js"></script>
<script>
require.config({ paths: { vs: '/lib/monaco-editor/min/vs' } });
require(['vs/editor/editor.main'], function () {
var editor = monaco.editor.create(document.getElementById('KeyEditor'), {
value: $('#Keys').val(),
readOnly: false,
theme: 'vs-dark',
automaticLayout: true
});
editor.onDidChangeModelContent(function (e) {
$('#Keys').val(editor.getModel().getValue());
});
});
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
$('form').submit();
}
});
</script>
}

View file

@ -1,125 +0,0 @@
@using LANCommander.Components;
@using LANCommander.Data.Enums
@model LANCommander.Data.Models.Script
@{
ViewData["Title"] = "Add Script | " + Model.Game.Title;
}
<div class="container-xl">
<!-- Page title -->
<div class="page-header d-print-none">
<div class="row align-items-center">
<div class="col">
<div class="page-pretitle">@Model.Game.Title</div>
<h2 class="page-title">
Add Script
</h2>
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="row row-cards">
<div class="col-12">
<form asp-action="Add" class="card">
<div class="card-body pb-0">
<div class="row">
<div class="col-12">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
</div>
</div>
<div class="row">
<div class="col-3">
<div class="mb-3">
<label asp-for="Type" class="control-label"></label>
<select asp-for="Type" class="form-control" asp-items="Html.GetEnumSelectList<ScriptType>()"></select>
<span asp-validation-for="Type" class="text-danger"></span>
</div>
</div>
<div class="col-9">
<div class="mb-3">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="mb-3">
<label asp-for="Description" class="control-label"></label>
<textarea asp-for="Description" class="form-control"></textarea>
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="mb-3">
<label class="form-check">
<input asp-for="RequiresAdmin" type="checkbox" class="form-check-input" />
<span class="form-check-label">Requires Admin Privileges</span>
<span class="form-check-description">Marks the script as needing admin privileges. Recommended for any changes to the system e.g. Windows Registry.</span>
</label>
</div>
<input type="hidden" asp-for="Contents" />
<input type="hidden" asp-for="GameId" />
</div>
</div>
<div class="row">
<div class="col btn-list mb-3">
<component type="typeof(SnippetBar)" render-mode="Server" />
</div>
</div>
</div>
<div id="ScriptEditor" style="height: 100%; min-height: 600px;"></div>
<div class="card-footer">
<div class="d-flex">
<a asp-action="Edit" asp-controller="Games" asp-route-id="@Model.Game.Id" class="btn btn-ghost-primary">Cancel</a>
<button type="submit" class="btn btn-primary ms-auto">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
<script src="~/lib/monaco-editor/min/vs/loader.js"></script>
<script>
window.Editor = {};
require.config({ paths: { vs: '/lib/monaco-editor/min/vs' } });
require(['vs/editor/editor.main'], function () {
window.Editor = monaco.editor.create(document.getElementById('ScriptEditor'), {
value: $('#Contents').val(),
language: 'powershell',
readOnly: false,
theme: 'vs-dark',
automaticLayout: true
});
window.Editor.onDidChangeModelContent(function (e) {
$('#Contents').val(window.Editor.getModel().getValue());
});
});
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
$('form').submit();
}
});
</script>
}

View file

@ -1,126 +0,0 @@
@using LANCommander.Components;
@using LANCommander.Data.Enums
@model LANCommander.Data.Models.Script
@{
ViewData["Title"] = "Edit Script | " + Model.Game.Title;
}
<div class="container-xl">
<!-- Page title -->
<div class="page-header d-print-none">
<div class="row align-items-center">
<div class="col">
<div class="page-pretitle">@Model.Game.Title</div>
<h2 class="page-title">
Edit Script
</h2>
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-xl">
<div class="row row-cards">
<div class="col-12">
<form asp-action="Edit" class="card">
<div class="card-body pb-0">
<div class="row">
<div class="col-12">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
</div>
</div>
<div class="row">
<div class="col-3">
<div class="mb-3">
<label asp-for="Type" class="control-label"></label>
<select asp-for="Type" class="form-control" asp-items="Html.GetEnumSelectList<ScriptType>()"></select>
<span asp-validation-for="Type" class="text-danger"></span>
</div>
</div>
<div class="col-9">
<div class="mb-3">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="mb-3">
<label asp-for="Description" class="control-label"></label>
<textarea asp-for="Description" class="form-control"></textarea>
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="mb-3">
<label class="form-check">
<input asp-for="RequiresAdmin" type="checkbox" class="form-check-input" />
<span class="form-check-label">Requires Admin Privileges</span>
<span class="form-check-description">Marks the script as needing admin privileges. Recommended for any changes to the system e.g. Windows Registry.</span>
</label>
</div>
<input type="hidden" asp-for="Contents" />
<input type="hidden" asp-for="GameId" />
<input type="hidden" asp-for="Id" />
</div>
</div>
<div class="row">
<div class="col btn-list mb-3">
<component type="typeof(SnippetBar)" render-mode="Server" />
</div>
</div>
</div>
<div id="ScriptEditor" style="height: 100%; min-height: 600px;"></div>
<div class="card-footer">
<div class="d-flex">
<a asp-action="Edit" asp-controller="Games" asp-route-id="@Model.Game.Id" class="btn btn-ghost-primary">Cancel</a>
<button type="submit" class="btn btn-primary ms-auto">Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
<script src="~/lib/monaco-editor/min/vs/loader.js"></script>
<script>
window.Editor = {};
require.config({ paths: { vs: '/lib/monaco-editor/min/vs' } });
require(['vs/editor/editor.main'], function () {
window.Editor = monaco.editor.create(document.getElementById('ScriptEditor'), {
value: $('#Contents').val(),
language: 'powershell',
readOnly: false,
theme: 'vs-dark',
automaticLayout: true
});
window.Editor.onDidChangeModelContent(function (e) {
$('#Contents').val(window.Editor.getModel().getValue());
});
});
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
$('form').submit();
}
});
</script>
}

View file

@ -1,43 +0,0 @@
@model LANCommander.Data.Models.Tag
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Tag</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="CreatedOn" class="control-label"></label>
<input asp-for="CreatedOn" class="form-control" />
<span asp-validation-for="CreatedOn" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="UpdatedOn" class="control-label"></label>
<input asp-for="UpdatedOn" class="form-control" />
<span asp-validation-for="UpdatedOn" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View file

@ -1,39 +0,0 @@
@model LANCommander.Data.Models.Tag
@{
ViewData["Title"] = "Delete";
}
<h1>Delete</h1>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Tag</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Name)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.CreatedOn)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.CreatedOn)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.UpdatedOn)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.UpdatedOn)
</dd>
</dl>
<form asp-action="Delete">
<input type="hidden" asp-for="Id" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-action="Index">Back to List</a>
</form>
</div>

View file

@ -1,36 +0,0 @@
@model LANCommander.Data.Models.Tag
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Tag</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Name)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.CreatedOn)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.CreatedOn)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.UpdatedOn)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.UpdatedOn)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model?.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>

View file

@ -1,44 +0,0 @@
@model LANCommander.Data.Models.Tag
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Tag</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="CreatedOn" class="control-label"></label>
<input asp-for="CreatedOn" class="form-control" />
<span asp-validation-for="CreatedOn" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="UpdatedOn" class="control-label"></label>
<input asp-for="UpdatedOn" class="form-control" />
<span asp-validation-for="UpdatedOn" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View file

@ -1,47 +0,0 @@
@model IEnumerable<LANCommander.Data.Models.Tag>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.CreatedOn)
</th>
<th>
@Html.DisplayNameFor(model => model.UpdatedOn)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.CreatedOn)
</td>
<td>
@Html.DisplayFor(modelItem => item.UpdatedOn)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

View file

@ -1,16 +0,0 @@
class Modal {
constructor(id) {
this.ElementId = id;
// @ts-ignore
this.Instance = new bootstrap.Modal(`#${this.ElementId}`, {
keyboard: false
});
}
Show(header, message) {
document.getElementById(`${this.ElementId}Header`).innerText = header;
document.getElementById(`${this.ElementId}Message`).innerText = message;
this.Instance.show();
}
}
const ErrorModal = new Modal('ErrorModal');
//# sourceMappingURL=Modal.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"Modal.js","sourceRoot":"","sources":["Modal.ts"],"names":[],"mappings":"AAAA,MAAM,KAAK;IAIP,YAAY,EAAU;QAClB,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;QAEpB,aAAa;QACb,IAAI,CAAC,QAAQ,GAAG,IAAI,SAAS,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE;YACtD,QAAQ,EAAE,KAAK;SAClB,CAAC,CAAC;IACP,CAAC;IAED,IAAI,CAAC,MAAc,EAAE,OAAe;QAChC,QAAQ,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC,SAAS,QAAQ,CAAC,CAAC,SAAS,GAAG,MAAM,CAAC;QACtE,QAAQ,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC,SAAS,SAAS,CAAC,CAAC,SAAS,GAAG,OAAO,CAAC;QAExE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;CACJ;AAED,MAAM,UAAU,GAAG,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC"}

View file

@ -1,22 +0,0 @@
class Modal {
Instance: any;
ElementId: string;
constructor(id: string) {
this.ElementId = id;
// @ts-ignore
this.Instance = new bootstrap.Modal(`#${this.ElementId}`, {
keyboard: false
});
}
Show(header: string, message: string) {
document.getElementById(`${this.ElementId}Header`).innerText = header;
document.getElementById(`${this.ElementId}Message`).innerText = message;
this.Instance.show();
}
}
const ErrorModal = new Modal('ErrorModal');

View file

@ -18,22 +18,43 @@ class Uploader {
constructor() {
this.InitRoute = "/Upload/Init";
this.ChunkRoute = "/Upload/Chunk";
this.ValidateRoute = "/Archives/Validate";
this.MaxChunkSize = 1024 * 1024 * 25;
}
Init(fileInputId, uploadButtonId) {
this.FileInput = document.getElementById("File");
this.UploadButton = document.getElementById("UploadButton");
this.VersionInput = document.getElementById("Version");
this.ChangelogTextArea = document.getElementById("Changelog");
this.LastVersionIdInput = document.getElementById("LastVersion_Id");
this.GameIdInput = document.getElementById("GameId");
this.ParentForm = this.FileInput.closest("form");
Init(fileInputId, uploadButtonId, objectKeyInputId) {
this.FileInput = document.getElementById(fileInputId);
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) {
return __awaiter(this, void 0, void 0, function* () {
this.FileInput = document.getElementById(fileInputId);
this.Chunks = [];
this.File = this.FileInput.files.item(0);
this.TotalChunks = Math.ceil(this.File.size / this.MaxChunkSize);
var response = yield fetch(this.InitRoute, {
method: "POST"
});
const data = yield response.json();
if (response.ok) {
this.Key = data.key;
this.GetChunks();
try {
for (let chunk of this.Chunks) {
yield this.UploadChunk(chunk);
}
}
catch (ex) {
this.OnError();
}
return this.Key;
}
return null;
});
}
OnUploadButtonClicked(e) {
return __awaiter(this, void 0, void 0, function* () {
e.preventDefault();
@ -51,11 +72,11 @@ class Uploader {
for (let chunk of this.Chunks) {
yield this.UploadChunk(chunk);
}
var isValid = yield this.Validate();
if (isValid)
this.OnComplete(this.Id, this.Key);
else
this.OnError();
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();
@ -78,31 +99,7 @@ class Uploader {
});
if (!chunkResponse)
throw `Error uploading chunk ${chunk.Index}/${this.TotalChunks}`;
this.OnProgress(chunk.Index / this.TotalChunks);
});
}
Validate() {
return __awaiter(this, void 0, void 0, function* () {
let formData = new FormData();
formData.append('Version', this.VersionInput.value);
formData.append('Changelog', this.ChangelogTextArea.value);
formData.append('GameId', this.GameIdInput.value);
formData.append('ObjectKey', this.Key);
let validationResponse = yield fetch(`${this.ValidateRoute}/${this.Key}`, {
method: "POST",
body: formData
});
if (!validationResponse.ok) {
ErrorModal.Show("Archive Invalid", yield validationResponse.text());
return false;
}
let data = yield validationResponse.json();
if (data == null || data.Id === "") {
ErrorModal.Show("Upload Error", "Something interfered with the upload. Try again.");
return false;
}
this.Id = data.Id;
return true;
//this.OnProgress(chunk.Index / this.TotalChunks);
});
}
GetChunks() {

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":";;;;;;;;;AAAA,MAAM,KAAK;IAKP,YAAY,KAAa,EAAE,GAAW,EAAE,KAAa;QACjD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;CACJ;AAED,MAAM,QAAQ;IAAd;QAOI,cAAS,GAAW,cAAc,CAAC;QACnC,eAAU,GAAW,eAAe,CAAC;QAErC,iBAAY,GAAW,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC;IAgI5C,CAAC;IAxHG,IAAI,CAAC,WAAmB,EAAE,cAAsB,EAAE,gBAAwB;QACtE,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC,cAAc,CAAC,WAAW,CAAqB,CAAC;QAC1E,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAC,cAAc,CAAC,cAAc,CAAsB,CAAC;QACjF,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAAqB,CAAC;QAEpF,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QAEjB,IAAI,CAAC,YAAY,CAAC,OAAO,GAAG,CAAO,CAAC,EAAE,EAAE;YACpC,MAAM,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACxC,CAAC,CAAA,CAAA;IACL,CAAC;IAEK,MAAM,CAAC,WAAmB;;YAC5B,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC,cAAc,CAAC,WAAW,CAAqB,CAAC;YAC1E,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;YAEjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACzC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC;YAEjE,IAAI,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE;gBACvC,MAAM,EAAE,MAAM;aACjB,CAAC,CAAC;YAEH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YAEnC,IAAI,QAAQ,CAAC,EAAE,EAAE;gBACb,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;gBAEpB,IAAI,CAAC,SAAS,EAAE,CAAC;gBAEjB,IAAI;oBACA,KAAK,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE;wBAC3B,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;qBACjC;iBACJ;gBACD,OAAO,EAAE,EAAE;oBACP,IAAI,CAAC,OAAO,EAAE,CAAC;iBAClB;gBAED,OAAO,IAAI,CAAC,GAAG,CAAC;aACnB;YAED,OAAO,IAAI,CAAC;QAChB,CAAC;KAAA;IAEK,qBAAqB,CAAC,CAAa;;YACrC,CAAC,CAAC,cAAc,EAAE,CAAC;YAEnB,IAAI,CAAC,OAAO,EAAE,CAAC;YAEf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACzC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC;YAEjE,IAAI,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE;gBACvC,MAAM,EAAE,MAAM;aACjB,CAAC,CAAC;YAEH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YAEnC,IAAI,QAAQ,CAAC,EAAE,EAAE;gBACb,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;gBAEpB,IAAI,CAAC,SAAS,EAAE,CAAC;gBAEjB,IAAI;oBACA,KAAK,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE;wBAC3B,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;qBACjC;oBAED,IAAI,CAAC,cAAc,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC;oBAErC,IAAI,KAAK,GAAG,QAAQ,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;oBAC/C,KAAK,CAAC,SAAS,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;oBACvC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;oBACzC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;iBACtC;gBACD,OAAO,EAAE,EAAE;oBACP,IAAI,CAAC,OAAO,EAAE,CAAC;iBAClB;aACJ;QACL,CAAC;KAAA;IAEK,WAAW,CAAC,KAAY;;YAC1B,IAAI,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;YAE9B,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YACrE,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;YACjD,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC7C,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;YACjC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YAEpD,OAAO,CAAC,IAAI,CAAC,mBAAmB,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,WAAW,KAAK,CAAC,CAAC;YAEtE,IAAI,aAAa,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE;gBAC7C,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,QAAQ;aACjB,CAAC,CAAC;YAEH,IAAI,CAAC,aAAa;gBACd,MAAM,yBAAyB,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YAErE,kDAAkD;QACtD,CAAC;KAAA;IAED,SAAS;QACL,KAAK,IAAI,YAAY,GAAG,CAAC,EAAE,YAAY,IAAI,IAAI,CAAC,WAAW,EAAE,YAAY,EAAE,EAAE;YACzE,IAAI,KAAK,GAAG,CAAC,YAAY,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC;YACnD,IAAI,GAAG,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;YAEjD,IAAI,YAAY,IAAI,IAAI,CAAC,WAAW;gBAChC,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YAEzB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC;SACzD;IACL,CAAC;CAMJ"}

View file

@ -11,21 +11,14 @@
}
class Uploader {
ParentForm: HTMLFormElement;
FileInput: HTMLInputElement;
UploadButton: HTMLButtonElement;
VersionInput: HTMLInputElement;
LastVersionIdInput: HTMLInputElement;
GameIdInput: HTMLInputElement;
ChangelogTextArea: HTMLTextAreaElement;
ObjectKeyInput: HTMLInputElement;
IdInput: HTMLInputElement;
File: File;
InitRoute: string = "/Upload/Init";
ChunkRoute: string = "/Upload/Chunk";
ValidateRoute: string = "/Archives/Validate";
MaxChunkSize: number = 1024 * 1024 * 25;
TotalChunks: number;
@ -35,14 +28,10 @@ class Uploader {
Key: string;
Id: string;
Init(fileInputId: string, uploadButtonId: string) {
this.FileInput = document.getElementById("File") as HTMLInputElement;
this.UploadButton = document.getElementById("UploadButton") as HTMLButtonElement;
this.VersionInput = document.getElementById("Version") as HTMLInputElement;
this.ChangelogTextArea = document.getElementById("Changelog") as HTMLTextAreaElement;
this.LastVersionIdInput = document.getElementById("LastVersion_Id") as HTMLInputElement;
this.GameIdInput = document.getElementById("GameId") as HTMLInputElement;
this.ParentForm = this.FileInput.closest("form");
Init(fileInputId: string, uploadButtonId: string, objectKeyInputId: string) {
this.FileInput = document.getElementById(fileInputId) as HTMLInputElement;
this.UploadButton = document.getElementById(uploadButtonId) as HTMLButtonElement;
this.ObjectKeyInput = document.getElementById(objectKeyInputId) as HTMLInputElement;
this.Chunks = [];
@ -51,6 +40,39 @@ class Uploader {
}
}
async Upload(fileInputId: string) {
this.FileInput = document.getElementById(fileInputId) as HTMLInputElement;
this.Chunks = [];
this.File = this.FileInput.files.item(0);
this.TotalChunks = Math.ceil(this.File.size / this.MaxChunkSize);
var response = await fetch(this.InitRoute, {
method: "POST"
});
const data = await response.json();
if (response.ok) {
this.Key = data.key;
this.GetChunks();
try {
for (let chunk of this.Chunks) {
await this.UploadChunk(chunk);
}
}
catch (ex) {
this.OnError();
}
return this.Key;
}
return null;
}
async OnUploadButtonClicked(e: MouseEvent) {
e.preventDefault();
@ -75,12 +97,12 @@ class Uploader {
await this.UploadChunk(chunk);
}
var isValid = await this.Validate();
this.ObjectKeyInput.value = this.Key;
if (isValid)
this.OnComplete(this.Id, this.Key);
else
this.OnError();
var event = document.createEvent('HTMLEvents');
event.initEvent('change', false, true);
this.ObjectKeyInput.dispatchEvent(event);
this.OnComplete(this.Id, this.Key);
}
catch (ex) {
this.OnError();
@ -107,39 +129,7 @@ class Uploader {
if (!chunkResponse)
throw `Error uploading chunk ${chunk.Index}/${this.TotalChunks}`;
this.OnProgress(chunk.Index / this.TotalChunks);
}
async Validate(): Promise<boolean> {
let formData = new FormData();
formData.append('Version', this.VersionInput.value);
formData.append('Changelog', this.ChangelogTextArea.value);
formData.append('GameId', this.GameIdInput.value);
formData.append('ObjectKey', this.Key);
let validationResponse = await fetch(`${this.ValidateRoute}/${this.Key}`, {
method: "POST",
body: formData
});
if (!validationResponse.ok) {
ErrorModal.Show("Archive Invalid", await validationResponse.text())
return false;
}
let data = await validationResponse.json();
if (data == null || data.Id === "") {
ErrorModal.Show("Upload Error", "Something interfered with the upload. Try again.");
return false;
}
this.Id = data.Id;
return true;
//this.OnProgress(chunk.Index / this.TotalChunks);
}
GetChunks() {