Added metadata pulling from IGDB. Added select inputs that works like tags and allow freeform submission.
This commit is contained in:
parent
f64d7a8126
commit
ddc68277b1
27 changed files with 2797 additions and 35 deletions
|
@ -12,27 +12,30 @@ using Microsoft.AspNetCore.Authorization;
|
|||
using LANCommander.Services;
|
||||
using System.Drawing;
|
||||
using LANCommander.Models;
|
||||
using LANCommander.Data.Enums;
|
||||
|
||||
namespace LANCommander.Controllers
|
||||
{
|
||||
[Authorize(Roles = "Administrator")]
|
||||
public class GamesController : Controller
|
||||
{
|
||||
private readonly DatabaseContext Context;
|
||||
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;
|
||||
|
||||
public GamesController(DatabaseContext context, GameService gameService, ArchiveService archiveService, CategoryService categoryService, TagService tagService, GenreService genreService)
|
||||
public GamesController(GameService gameService, ArchiveService archiveService, CategoryService categoryService, TagService tagService, GenreService genreService, CompanyService companyService, IGDBService igdbService)
|
||||
{
|
||||
Context = context;
|
||||
GameService = gameService;
|
||||
ArchiveService = archiveService;
|
||||
CategoryService = categoryService;
|
||||
TagService = tagService;
|
||||
GenreService = genreService;
|
||||
CompanyService = companyService;
|
||||
IGDBService = igdbService;
|
||||
}
|
||||
|
||||
// GET: Games
|
||||
|
@ -41,10 +44,193 @@ namespace LANCommander.Controllers
|
|||
return View(GameService.Get());
|
||||
}
|
||||
|
||||
// GET: Games/Create
|
||||
public IActionResult Add()
|
||||
public async Task<IActionResult> Add(long? igdbid)
|
||||
{
|
||||
return View();
|
||||
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();
|
||||
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,
|
||||
MultiplayerModes = new List<MultiplayerMode>(),
|
||||
};
|
||||
|
||||
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
|
||||
|
@ -52,16 +238,16 @@ namespace LANCommander.Controllers
|
|||
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Add([Bind("Title,SortTitle,Description,ReleasedOn,Id,CreatedOn,CreatedById,UpdatedOn,UpdatedById")] Game game)
|
||||
public async Task<IActionResult> Add(GameViewModel viewModel)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
await GameService.Add(game);
|
||||
await GameService.Add(viewModel.Game);
|
||||
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
return View(game);
|
||||
return View(viewModel.Game);
|
||||
}
|
||||
|
||||
// GET: Games/Edit/5
|
||||
|
@ -113,11 +299,6 @@ namespace LANCommander.Controllers
|
|||
// GET: Games/Delete/5
|
||||
public async Task<IActionResult> Delete(Guid? id)
|
||||
{
|
||||
if (id == null || Context.Games == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var game = await GameService.Get(id.GetValueOrDefault());
|
||||
|
||||
if (game == null)
|
||||
|
@ -143,6 +324,51 @@ namespace LANCommander.Controllers
|
|||
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 =>
|
||||
{
|
||||
return new Game()
|
||||
{
|
||||
IGDBId = r.Id.GetValueOrDefault(),
|
||||
Title = r.Name,
|
||||
ReleasedOn = r.FirstReleaseDate.GetValueOrDefault().UtcDateTime,
|
||||
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 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
|
||||
|
|
|
@ -6,18 +6,19 @@ namespace LANCommander.Data.Models
|
|||
[Table("Games")]
|
||||
public class Game : BaseModel
|
||||
{
|
||||
public long? IGDBId { get; set; }
|
||||
public string Title { get; set; }
|
||||
[Display(Name = "Sort Title")]
|
||||
public string? SortTitle { get; set; }
|
||||
[Display(Name = "Directory Name")]
|
||||
public string? DirectoryName { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string? Description { get; set; }
|
||||
[Display(Name = "Released On")]
|
||||
public DateTime ReleasedOn { get; set; }
|
||||
public DateTime? ReleasedOn { get; set; }
|
||||
|
||||
public virtual ICollection<Action> Actions { get; set; }
|
||||
|
||||
public bool Singleplayer { get; set; }
|
||||
public bool Singleplayer { get; set; } = false;
|
||||
|
||||
public virtual ICollection<MultiplayerMode> MultiplayerModes { get; set; }
|
||||
public virtual ICollection<Genre> Genres { get; set; }
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ByteSize" Version="2.1.1" />
|
||||
<PackageReference Include="IGDB" Version="2.3.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.12" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.8" />
|
||||
|
|
1025
LANCommander/Migrations/20230110023006_AddGameIGDBId.Designer.cs
generated
Normal file
1025
LANCommander/Migrations/20230110023006_AddGameIGDBId.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
25
LANCommander/Migrations/20230110023006_AddGameIGDBId.cs
Normal file
25
LANCommander/Migrations/20230110023006_AddGameIGDBId.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LANCommander.Migrations
|
||||
{
|
||||
public partial class AddGameIGDBId : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "IGDBId",
|
||||
table: "Games",
|
||||
type: "INTEGER",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IGDBId",
|
||||
table: "Games");
|
||||
}
|
||||
}
|
||||
}
|
1024
LANCommander/Migrations/20230110023249_ChangeGameRequiredFields.Designer.cs
generated
Normal file
1024
LANCommander/Migrations/20230110023249_ChangeGameRequiredFields.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LANCommander.Migrations
|
||||
{
|
||||
public partial class ChangeGameRequiredFields : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "ReleasedOn",
|
||||
table: "Games",
|
||||
type: "TEXT",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "TEXT");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Description",
|
||||
table: "Games",
|
||||
type: "TEXT",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "TEXT");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "ReleasedOn",
|
||||
table: "Games",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "TEXT",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Description",
|
||||
table: "Games",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: "",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "TEXT",
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -277,13 +277,15 @@ namespace LANCommander.Migrations
|
|||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DirectoryName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ReleasedOn")
|
||||
b.Property<long?>("IGDBId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("ReleasedOn")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Singleplayer")
|
||||
|
|
10
LANCommander/Models/GameLookupResultsViewModel.cs
Normal file
10
LANCommander/Models/GameLookupResultsViewModel.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using LANCommander.Data.Models;
|
||||
|
||||
namespace LANCommander.Models
|
||||
{
|
||||
public class GameLookupResultsViewModel
|
||||
{
|
||||
public string Search { get; set; }
|
||||
public IEnumerable<Game> Results { get; set; }
|
||||
}
|
||||
}
|
16
LANCommander/Models/GameViewModel.cs
Normal file
16
LANCommander/Models/GameViewModel.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
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; }
|
||||
}
|
||||
}
|
|
@ -58,12 +58,15 @@ builder.Services.AddControllersWithViews().AddJsonOptions(x =>
|
|||
x.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles;
|
||||
});
|
||||
|
||||
builder.Services.AddScoped<SettingService>();
|
||||
builder.Services.AddScoped<ArchiveService>();
|
||||
builder.Services.AddScoped<CategoryService>();
|
||||
builder.Services.AddScoped<GameService>();
|
||||
builder.Services.AddScoped<GenreService>();
|
||||
builder.Services.AddScoped<KeyService>();
|
||||
builder.Services.AddScoped<TagService>();
|
||||
builder.Services.AddScoped<CompanyService>();
|
||||
builder.Services.AddScoped<IGDBService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
|
12
LANCommander/Services/CompanyService.cs
Normal file
12
LANCommander/Services/CompanyService.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using LANCommander.Data;
|
||||
using LANCommander.Data.Models;
|
||||
|
||||
namespace LANCommander.Services
|
||||
{
|
||||
public class CompanyService : BaseDatabaseService<Company>
|
||||
{
|
||||
public CompanyService(DatabaseContext dbContext, IHttpContextAccessor httpContextAccessor) : base(dbContext, httpContextAccessor)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
46
LANCommander/Services/IGDBService.cs
Normal file
46
LANCommander/Services/IGDBService.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using IGDB;
|
||||
using IGDB.Models;
|
||||
|
||||
namespace LANCommander.Services
|
||||
{
|
||||
public class IGDBService
|
||||
{
|
||||
private readonly IGDBClient Client;
|
||||
private readonly SettingService SettingService;
|
||||
private const string DefaultFields = "*";
|
||||
|
||||
public IGDBService(SettingService settingService)
|
||||
{
|
||||
SettingService = settingService;
|
||||
|
||||
var settings = SettingService.GetSettings();
|
||||
|
||||
Client = new IGDBClient(settings.IGDBClientId, settings.IGDBClientSecret);
|
||||
}
|
||||
|
||||
public async Task<Game> Get(long id, params string[] additionalFields)
|
||||
{
|
||||
var fields = DefaultFields.Split(',').ToList();
|
||||
|
||||
fields.AddRange(additionalFields);
|
||||
|
||||
var games = await Client.QueryAsync<Game>(IGDBClient.Endpoints.Games, $"fields {String.Join(',', fields)}; where id = {id};");
|
||||
|
||||
if (games == null)
|
||||
return null;
|
||||
|
||||
return games.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Game>> Search(string query, params string[] additionalFields)
|
||||
{
|
||||
var fields = DefaultFields.Split(',').ToList();
|
||||
|
||||
fields.AddRange(additionalFields);
|
||||
|
||||
var games = await Client.QueryAsync<Game>(IGDBClient.Endpoints.Games, $"search \"{query}\"; fields {String.Join(',', fields)};");
|
||||
|
||||
return games.AsEnumerable();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
@model LANCommander.Data.Models.Game
|
||||
@model LANCommander.Models.GameViewModel
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Add Game";
|
||||
|
@ -28,24 +28,49 @@
|
|||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Title" class="control-label"></label>
|
||||
<input asp-for="Title" class="form-control" />
|
||||
<span asp-validation-for="Title" class="text-danger"></span>
|
||||
<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="SortTitle" class="control-label"></label>
|
||||
<input asp-for="SortTitle" class="form-control" />
|
||||
<span asp-validation-for="SortTitle" class="text-danger"></span>
|
||||
<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="Description" class="control-label"></label>
|
||||
<input asp-for="Description" class="form-control" />
|
||||
<span asp-validation-for="Description" class="text-danger"></span>
|
||||
<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="ReleasedOn" class="control-label"></label>
|
||||
<input asp-for="ReleasedOn" class="form-control" />
|
||||
<span asp-validation-for="ReleasedOn" class="text-danger"></span>
|
||||
<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 asp-for="Developers" class="control-label"></label>
|
||||
<input type="text" class="developer-select" />
|
||||
<select asp-for="Developers" asp-items="Model.Developers" 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="Publishers" asp-items="Model.Publishers" 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="Genres" asp-items="Model.Genres" 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="Tags" asp-items="Model.Tags" class="d-none"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -65,4 +90,61 @@
|
|||
|
||||
@section Scripts {
|
||||
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
|
||||
|
||||
<script>
|
||||
var developers = @Html.Raw(Json.Serialize(Model.Developers));
|
||||
var publishers = @Html.Raw(Json.Serialize(Model.Publishers));
|
||||
var genres = @Html.Raw(Json.Serialize(Model.Genres));
|
||||
var tags = @Html.Raw(Json.Serialize(Model.Tags));
|
||||
|
||||
function selectize(selector, options) {
|
||||
var selected = [];
|
||||
|
||||
for (let option of options) {
|
||||
if (option.selected)
|
||||
selected.push(option.value);
|
||||
}
|
||||
|
||||
$(selector).val(selected.join('|'));
|
||||
|
||||
$(selector).selectize({
|
||||
delimiter: '|',
|
||||
plugins: ['remove_button'],
|
||||
create: true,
|
||||
valueField: 'value',
|
||||
labelField: 'text',
|
||||
searchField: 'text',
|
||||
options: options,
|
||||
onItemAdd: function(value) {
|
||||
for (let option of Object.values(this.options)) {
|
||||
if (option.value == value)
|
||||
{
|
||||
this.$input.siblings('select').append(`<option value="${option.value}" selected>${option.text}</option>`);
|
||||
}
|
||||
}
|
||||
},
|
||||
onItemRemove: function(value) {
|
||||
for (let option of Object.values(this.options)) {
|
||||
if (option.value == value)
|
||||
{
|
||||
this.$input.siblings('select').find(`option[value="${option.value}"]`).remove();
|
||||
}
|
||||
}
|
||||
},
|
||||
onInitialize: function() {
|
||||
for (let option of Object.values(this.options)) {
|
||||
if (option.selected)
|
||||
{
|
||||
this.$input.siblings('select').append(`<option value="${option.value}" selected>${option.text}</option>`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
selectize('.developer-select', developers);
|
||||
selectize('.publisher-select', publishers);
|
||||
selectize('.genre-select', genres);
|
||||
selectize('.tag-select', tags);
|
||||
</script>
|
||||
}
|
||||
|
|
99
LANCommander/Views/Games/Lookup.cshtml
Normal file
99
LANCommander/Views/Games/Lookup.cshtml
Normal file
|
@ -0,0 +1,99 @@
|
|||
@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>
|
|
@ -5,6 +5,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - LANCommander</title>
|
||||
<link href="~/css/tabler.min.css" rel="stylesheet" />
|
||||
<link href="~/lib/selectize.js/css/selectize.bootstrap5.min.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="navbar navbar-expand-md navbar-light">
|
||||
|
@ -87,7 +88,8 @@
|
|||
</div>
|
||||
|
||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||
<script src="~/js/tabler.min.js"></script>
|
||||
<script src="~/lib/selectize.js/js/selectize.min.js"></script>
|
||||
<script src="~/lib/tabler/core/dist/js/tabler.min.js"></script>
|
||||
<script src="~/js/Modal.js"></script>
|
||||
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - LANCommander</title>
|
||||
<link href="~/css/tabler.min.css" rel="stylesheet" />
|
||||
<link href="~/lib/tabler/core/dist/css/tabler.min.css" type="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
@RenderBody()
|
||||
|
||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||
<script src="~/js/tabler.min.js"></script>
|
||||
<script src="~/lib/tabler/core/dist/js/tabler.min.js"></script>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
||||
|
|
45
LANCommander/libman.json
Normal file
45
LANCommander/libman.json
Normal file
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"version": "1.0",
|
||||
"defaultProvider": "cdnjs",
|
||||
"libraries": [
|
||||
{
|
||||
"provider": "cdnjs",
|
||||
"library": "selectize.js@0.15.2",
|
||||
"destination": "wwwroot/lib/selectize.js/",
|
||||
"files": [
|
||||
"js/selectize.min.js",
|
||||
"css/selectize.bootstrap5.min.css",
|
||||
"css/selectize.min.css",
|
||||
"css/selectize.default.min.css"
|
||||
]
|
||||
},
|
||||
{
|
||||
"provider": "jsdelivr",
|
||||
"library": "@tabler/core@1.0.0-beta16",
|
||||
"destination": "wwwroot/lib/tabler/core/",
|
||||
"files": [
|
||||
"dist/js/tabler.min.js",
|
||||
"dist/css/tabler-vendors.min.css",
|
||||
"dist/css/tabler.min.css",
|
||||
"dist/css/tabler-payments.min.css",
|
||||
"dist/css/tabler-flags.min.css"
|
||||
]
|
||||
}
|
||||
,
|
||||
{
|
||||
"provider": "cdnjs",
|
||||
"library": "tom-select@2.2.2",
|
||||
"destination": "wwwroot/lib/tom-select/",
|
||||
"files": [
|
||||
"js/tom-select.complete.min.js",
|
||||
"js/tom-select.complete.min.js.map",
|
||||
"css/tom-select.bootstrap5.min.css",
|
||||
"css/tom-select.bootstrap5.min.css.map",
|
||||
"css/tom-select.default.min.css",
|
||||
"css/tom-select.default.min.css.map",
|
||||
"css/tom-select.min.css",
|
||||
"css/tom-select.min.css.map"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
1
LANCommander/wwwroot/lib/selectize.js/css/selectize.bootstrap5.min.css
vendored
Normal file
1
LANCommander/wwwroot/lib/selectize.js/css/selectize.bootstrap5.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
LANCommander/wwwroot/lib/selectize.js/css/selectize.default.min.css
vendored
Normal file
1
LANCommander/wwwroot/lib/selectize.js/css/selectize.default.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
LANCommander/wwwroot/lib/selectize.js/css/selectize.min.css
vendored
Normal file
1
LANCommander/wwwroot/lib/selectize.js/css/selectize.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
32
LANCommander/wwwroot/lib/selectize.js/js/selectize.min.js
vendored
Normal file
32
LANCommander/wwwroot/lib/selectize.js/js/selectize.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
9
LANCommander/wwwroot/lib/tabler/core/dist/css/tabler-flags.min.css
vendored
Normal file
9
LANCommander/wwwroot/lib/tabler/core/dist/css/tabler-flags.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
9
LANCommander/wwwroot/lib/tabler/core/dist/css/tabler-payments.min.css
vendored
Normal file
9
LANCommander/wwwroot/lib/tabler/core/dist/css/tabler-payments.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
9
LANCommander/wwwroot/lib/tabler/core/dist/css/tabler-vendors.min.css
vendored
Normal file
9
LANCommander/wwwroot/lib/tabler/core/dist/css/tabler-vendors.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
14
LANCommander/wwwroot/lib/tabler/core/dist/css/tabler.min.css
vendored
Normal file
14
LANCommander/wwwroot/lib/tabler/core/dist/css/tabler.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
15
LANCommander/wwwroot/lib/tabler/core/dist/js/tabler.min.js
vendored
Normal file
15
LANCommander/wwwroot/lib/tabler/core/dist/js/tabler.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue