Added metadata pulling from IGDB. Added select inputs that works like tags and allow freeform submission.
parent
f64d7a8126
commit
ddc68277b1
|
@ -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" />
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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")
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue