Add ability to add media to games. Search media from steamgriddb.com
parent
8c61a7e3b5
commit
499b0c910a
|
@ -113,7 +113,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
var games = LANCommander
|
var games = LANCommander
|
||||||
.GetGames()
|
.GetGames()
|
||||||
.Where(g => g.Archives != null && g.Archives.Count() > 0);
|
.Where(g => g != null && g.Archives != null && g.Archives.Count() > 0);
|
||||||
|
|
||||||
foreach (var game in games)
|
foreach (var game in games)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<div class="image-picker">
|
||||||
|
<div class="image-picker-images">
|
||||||
|
@foreach (var image in Images)
|
||||||
|
{
|
||||||
|
<div class="image-picker-image" style="width: @(Size)px; height: @(Size)px">
|
||||||
|
<input type="radio" id="image-picker-image-@image.Key" checked="@(Value == image.Key)" name="SelectedResult" @onchange="@(() => SelectionChanged(image.Key))" />
|
||||||
|
<label for="image-picker-image-@image.Key"></label>
|
||||||
|
<img src="@image.Value" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public double Size { get; set; }
|
||||||
|
[Parameter] public string Value { get; set; }
|
||||||
|
[Parameter] public EventCallback<string> ValueChanged { get; set; }
|
||||||
|
[Parameter] public Dictionary<string, string> Images { get; set; }
|
||||||
|
|
||||||
|
async Task SelectionChanged(string key)
|
||||||
|
{
|
||||||
|
Value = key;
|
||||||
|
|
||||||
|
if (ValueChanged.HasDelegate)
|
||||||
|
await ValueChanged.InvokeAsync(key);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
@inherits FeedbackComponent<MediaGrabberOptions, MediaGrabberResult>
|
||||||
|
@using LANCommander.Data.Enums;
|
||||||
|
@using LANCommander.Models;
|
||||||
|
@inject IMediaGrabberService MediaGrabberService
|
||||||
|
|
||||||
|
<Slider TValue="double" @bind-Value="Size" DefaultValue="200" Min="50" Max="400" />
|
||||||
|
|
||||||
|
<ImagePicker Size="Size" Images="Images" ValueChanged="OnImageSelected" />
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public string Search { get; set; }
|
||||||
|
[Parameter] public MediaType Type { get; set; }
|
||||||
|
|
||||||
|
MediaGrabberResult Media { get; set; }
|
||||||
|
|
||||||
|
double Size { get; set; } = 200;
|
||||||
|
|
||||||
|
IEnumerable<MediaGrabberResult> Results = new List<MediaGrabberResult>();
|
||||||
|
Dictionary<string, string> Images { get; set; } = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
Results = await MediaGrabberService.SearchAsync(Options.Type, Options.Search);
|
||||||
|
|
||||||
|
Images = Results.ToDictionary(r => r.Id, r => r.ThumbnailUrl);
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnImageSelected(string key)
|
||||||
|
{
|
||||||
|
Media = Results.FirstOrDefault(r => r.Id == key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnFeedbackOkAsync(ModalClosingEventArgs args)
|
||||||
|
{
|
||||||
|
await base.OkCancelRefWithResult!.OnOk(Media);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
namespace LANCommander.Controllers.Api
|
||||||
|
{
|
||||||
|
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class MediaController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly MediaService MediaService;
|
||||||
|
private readonly LANCommanderSettings Settings = SettingService.GetSettings();
|
||||||
|
|
||||||
|
public MediaController(MediaService mediaService)
|
||||||
|
{
|
||||||
|
|
||||||
|
MediaService = mediaService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IEnumerable<Media>> Get()
|
||||||
|
{
|
||||||
|
return await MediaService.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<Media> Get(Guid id)
|
||||||
|
{
|
||||||
|
return await MediaService.Get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpGet("{id}/Download")]
|
||||||
|
public async Task<IActionResult> Download(Guid id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var media = await MediaService.Get(id);
|
||||||
|
|
||||||
|
var fs = System.IO.File.OpenRead(MediaService.GetImagePath(media));
|
||||||
|
|
||||||
|
return File(fs, "image/png");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -90,6 +90,11 @@ namespace LANCommander.Data
|
||||||
gr => gr.HasOne<Game>().WithMany().HasForeignKey("GameId")
|
gr => gr.HasOne<Game>().WithMany().HasForeignKey("GameId")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
builder.Entity<Game>()
|
||||||
|
.HasMany(g => g.Media)
|
||||||
|
.WithOne(m => m.Game)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
builder.Entity<User>()
|
builder.Entity<User>()
|
||||||
.HasMany(u => u.GameSaves)
|
.HasMany(u => u.GameSaves)
|
||||||
.WithOne(gs => gs.User)
|
.WithOne(gs => gs.User)
|
||||||
|
@ -146,5 +151,7 @@ namespace LANCommander.Data
|
||||||
public DbSet<ServerConsole>? ServerConsoles { get; set; }
|
public DbSet<ServerConsole>? ServerConsoles { get; set; }
|
||||||
|
|
||||||
public DbSet<Redistributable>? Redistributables { get; set; }
|
public DbSet<Redistributable>? Redistributables { get; set; }
|
||||||
|
|
||||||
|
public DbSet<Media>? Media { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace LANCommander.Data.Enums
|
||||||
|
{
|
||||||
|
public enum MediaType
|
||||||
|
{
|
||||||
|
Icon,
|
||||||
|
Cover,
|
||||||
|
Background
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,7 @@ namespace LANCommander.Data.Models
|
||||||
public virtual ICollection<SavePath>? SavePaths { get; set; }
|
public virtual ICollection<SavePath>? SavePaths { get; set; }
|
||||||
public virtual ICollection<Server>? Servers { get; set; }
|
public virtual ICollection<Server>? Servers { get; set; }
|
||||||
public virtual ICollection<Redistributable>? Redistributables { get; set; }
|
public virtual ICollection<Redistributable>? Redistributables { get; set; }
|
||||||
|
public virtual ICollection<Media>? Media { get; set; }
|
||||||
|
|
||||||
public string? ValidKeyRegex { get; set; }
|
public string? ValidKeyRegex { get; set; }
|
||||||
public virtual ICollection<Key>? Keys { get; set; }
|
public virtual ICollection<Key>? Keys { get; set; }
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
using LANCommander.Data.Enums;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace LANCommander.Data.Models
|
||||||
|
{
|
||||||
|
[Table("Media")]
|
||||||
|
public class Media : BaseModel
|
||||||
|
{
|
||||||
|
public Guid FileId { get; set; }
|
||||||
|
public MediaType Type { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(2048)]
|
||||||
|
public string SourceUrl { get; set; }
|
||||||
|
|
||||||
|
public Guid GameId { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
[ForeignKey(nameof(GameId))]
|
||||||
|
[InverseProperty("Media")]
|
||||||
|
public virtual Game? Game { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,7 @@
|
||||||
<PackageReference Include="BlazorMonaco" Version="3.1.0" />
|
<PackageReference Include="BlazorMonaco" Version="3.1.0" />
|
||||||
<PackageReference Include="ByteSize" Version="2.1.1" />
|
<PackageReference Include="ByteSize" Version="2.1.1" />
|
||||||
<PackageReference Include="CoreRCON" Version="5.0.5" />
|
<PackageReference Include="CoreRCON" Version="5.0.5" />
|
||||||
|
<PackageReference Include="craftersmine.SteamGridDB.Net" Version="1.1.5" />
|
||||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.5" />
|
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.5" />
|
||||||
<PackageReference Include="Hangfire.Core" Version="1.8.5" />
|
<PackageReference Include="Hangfire.Core" Version="1.8.5" />
|
||||||
<PackageReference Include="Hangfire.InMemory" Version="0.5.1" />
|
<PackageReference Include="Hangfire.InMemory" Version="0.5.1" />
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 405 KiB |
Binary file not shown.
After Width: | Height: | Size: 876 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.4 MiB |
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,72 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace LANCommander.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddMediaEntity : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Media",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
FileId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
Type = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
SourceUrl = table.Column<string>(type: "TEXT", maxLength: 2048, nullable: false),
|
||||||
|
GameId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
CreatedOn = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
CreatedById = table.Column<Guid>(type: "TEXT", nullable: true),
|
||||||
|
UpdatedOn = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
UpdatedById = table.Column<Guid>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Media", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Media_AspNetUsers_CreatedById",
|
||||||
|
column: x => x.CreatedById,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Media_AspNetUsers_UpdatedById",
|
||||||
|
column: x => x.UpdatedById,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Media_Games_GameId",
|
||||||
|
column: x => x.GameId,
|
||||||
|
principalTable: "Games",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Media_CreatedById",
|
||||||
|
table: "Media",
|
||||||
|
column: "CreatedById");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Media_GameId",
|
||||||
|
table: "Media",
|
||||||
|
column: "GameId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Media_UpdatedById",
|
||||||
|
table: "Media",
|
||||||
|
column: "UpdatedById");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Media");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -474,6 +474,49 @@ namespace LANCommander.Migrations
|
||||||
b.ToTable("Keys");
|
b.ToTable("Keys");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LANCommander.Data.Models.Media", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatedById")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedOn")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("FileId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("GameId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceUrl")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UpdatedById")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedOn")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CreatedById");
|
||||||
|
|
||||||
|
b.HasIndex("GameId");
|
||||||
|
|
||||||
|
b.HasIndex("UpdatedById");
|
||||||
|
|
||||||
|
b.ToTable("Media");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.MultiplayerMode", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.MultiplayerMode", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
@ -1302,6 +1345,29 @@ namespace LANCommander.Migrations
|
||||||
b.Navigation("UpdatedBy");
|
b.Navigation("UpdatedBy");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LANCommander.Data.Models.Media", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CreatedById");
|
||||||
|
|
||||||
|
b.HasOne("LANCommander.Data.Models.Game", "Game")
|
||||||
|
.WithMany("Media")
|
||||||
|
.HasForeignKey("GameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UpdatedById");
|
||||||
|
|
||||||
|
b.Navigation("CreatedBy");
|
||||||
|
|
||||||
|
b.Navigation("Game");
|
||||||
|
|
||||||
|
b.Navigation("UpdatedBy");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.MultiplayerMode", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.MultiplayerMode", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
|
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
|
||||||
|
@ -1520,6 +1586,8 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.Navigation("Keys");
|
b.Navigation("Keys");
|
||||||
|
|
||||||
|
b.Navigation("Media");
|
||||||
|
|
||||||
b.Navigation("MultiplayerModes");
|
b.Navigation("MultiplayerModes");
|
||||||
|
|
||||||
b.Navigation("SavePaths");
|
b.Navigation("SavePaths");
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
using LANCommander.Components.FileManagerComponents;
|
||||||
|
using LANCommander.Data.Enums;
|
||||||
|
|
||||||
|
namespace LANCommander.Models
|
||||||
|
{
|
||||||
|
public class MediaGrabberOptions
|
||||||
|
{
|
||||||
|
public MediaType Type { get; set; }
|
||||||
|
public string Search { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
using LANCommander.Data.Enums;
|
||||||
|
|
||||||
|
namespace LANCommander.Models
|
||||||
|
{
|
||||||
|
public class MediaGrabberResult
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public MediaType Type { get; set; }
|
||||||
|
public string SourceUrl { get; set; }
|
||||||
|
public string ThumbnailUrl { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@
|
||||||
public LANCommanderAuthenticationSettings Authentication { get; set; } = new LANCommanderAuthenticationSettings();
|
public LANCommanderAuthenticationSettings Authentication { get; set; } = new LANCommanderAuthenticationSettings();
|
||||||
public LANCommanderUserSaveSettings UserSaves { get; set; } = new LANCommanderUserSaveSettings();
|
public LANCommanderUserSaveSettings UserSaves { get; set; } = new LANCommanderUserSaveSettings();
|
||||||
public LANCommanderArchiveSettings Archives { get; set; } = new LANCommanderArchiveSettings();
|
public LANCommanderArchiveSettings Archives { get; set; } = new LANCommanderArchiveSettings();
|
||||||
|
public LANCommanderMediaSettings Media { get; set; } = new LANCommanderMediaSettings();
|
||||||
public LANCommanderIPXRelaySettings IPXRelay { get; set; } = new LANCommanderIPXRelaySettings();
|
public LANCommanderIPXRelaySettings IPXRelay { get; set; } = new LANCommanderIPXRelaySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +52,12 @@
|
||||||
public string StoragePath { get; set; } = "Uploads";
|
public string StoragePath { get; set; } = "Uploads";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class LANCommanderMediaSettings
|
||||||
|
{
|
||||||
|
public string SteamGridDbApiKey { get; set; } = "";
|
||||||
|
public string StoragePath { get; set; } = "Media";
|
||||||
|
}
|
||||||
|
|
||||||
public class LANCommanderIPXRelaySettings
|
public class LANCommanderIPXRelaySettings
|
||||||
{
|
{
|
||||||
public bool Enabled { get; set; } = false;
|
public bool Enabled { get; set; } = false;
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
@using LANCommander.Data.Enums;
|
||||||
|
@using LANCommander.Models;
|
||||||
|
@inject MediaService MediaService
|
||||||
|
@inject ModalService ModalService
|
||||||
|
|
||||||
|
<Space Direction="DirectionVHType.Vertical" Size="@("large")" Style="width: 100%" Class="media-editor">
|
||||||
|
<SpaceItem>
|
||||||
|
<Table TItem="Media" DataSource="@Values" HidePagination="true" Responsive>
|
||||||
|
<PropertyColumn Property="p => p.Id" Title="Preview" Width="100px" Align="ColumnAlign.Center">
|
||||||
|
@if (MediaService.FileExists(context))
|
||||||
|
{
|
||||||
|
<Image Width="100px" Src="@($"/api/Media/{context.Id}/Download")" />
|
||||||
|
}
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="p => p.Type">
|
||||||
|
<Select @bind-Value="context.Type" TItem="MediaType" TItemValue="MediaType" DataSource="Enum.GetValues<MediaType>()" />
|
||||||
|
</PropertyColumn>
|
||||||
|
<ActionColumn>
|
||||||
|
<Space Style="display: flex; justify-content: end">
|
||||||
|
<SpaceItem>
|
||||||
|
<Button OnClick="() => SearchMedia(context)" Type="@ButtonType.Text" Icon="@IconType.Outline.Search" />
|
||||||
|
</SpaceItem>
|
||||||
|
<SpaceItem>
|
||||||
|
<Popconfirm OnConfirm="() => RemoveMedia(context)" Title="Are you sure you want to delete this media?">
|
||||||
|
<Button Type="@ButtonType.Text" Danger Icon="@IconType.Outline.Close" />
|
||||||
|
</Popconfirm>
|
||||||
|
</SpaceItem>
|
||||||
|
</Space>
|
||||||
|
</ActionColumn>
|
||||||
|
</Table>
|
||||||
|
</SpaceItem>
|
||||||
|
|
||||||
|
<SpaceItem>
|
||||||
|
<GridRow Justify="end">
|
||||||
|
<GridCol>
|
||||||
|
<Button OnClick="AddMedia" Type="@ButtonType.Primary">Add Media</Button>
|
||||||
|
</GridCol>
|
||||||
|
</GridRow>
|
||||||
|
</SpaceItem>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public ICollection<Media> Values { get; set; } = new List<Media>();
|
||||||
|
[Parameter] public EventCallback<ICollection<Media>> ValuesChanged { get; set; }
|
||||||
|
|
||||||
|
[Parameter] public Guid GameId { get; set; }
|
||||||
|
[Parameter] public string GameTitle { get; set; }
|
||||||
|
|
||||||
|
private async Task AddMedia()
|
||||||
|
{
|
||||||
|
if (Values == null)
|
||||||
|
Values = new List<Media>();
|
||||||
|
|
||||||
|
Values.Add(new Media()
|
||||||
|
{
|
||||||
|
GameId = GameId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SearchMedia(Media media)
|
||||||
|
{
|
||||||
|
var modalOptions = new ModalOptions()
|
||||||
|
{
|
||||||
|
Title = "Search Media",
|
||||||
|
Maximizable = false,
|
||||||
|
DefaultMaximized = true,
|
||||||
|
Closable = true,
|
||||||
|
OkText = "Select",
|
||||||
|
};
|
||||||
|
|
||||||
|
var grabberOptions = new MediaGrabberOptions()
|
||||||
|
{
|
||||||
|
Type = media.Type,
|
||||||
|
Search = GameTitle
|
||||||
|
};
|
||||||
|
|
||||||
|
var modalRef = await ModalService.CreateModalAsync<MediaGrabberDialog, MediaGrabberOptions, MediaGrabberResult>(modalOptions, grabberOptions);
|
||||||
|
|
||||||
|
modalRef.OnOk = async (result) =>
|
||||||
|
{
|
||||||
|
modalRef.Config.ConfirmLoading = true;
|
||||||
|
|
||||||
|
media.SourceUrl = result.SourceUrl;
|
||||||
|
media.FileId = await MediaService.DownloadMediaAsync(result.SourceUrl);
|
||||||
|
|
||||||
|
await MediaService.Add(media);
|
||||||
|
|
||||||
|
Values = MediaService.Get(m => m.GameId == media.GameId).ToList();
|
||||||
|
|
||||||
|
if (ValuesChanged.HasDelegate)
|
||||||
|
await ValuesChanged.InvokeAsync(Values);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemoveMedia(Media media)
|
||||||
|
{
|
||||||
|
Values.Remove(media);
|
||||||
|
|
||||||
|
await MediaService.Delete(media);
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
@if (Game != null && Game.Id != Guid.Empty)
|
@if (Game != null && Game.Id != Guid.Empty)
|
||||||
{
|
{
|
||||||
|
<MenuItem RouterLink="@($"/Games/{Game.Id}/Media")">Media</MenuItem>
|
||||||
<MenuItem RouterLink="@($"/Games/{Game.Id}/Actions")">Actions</MenuItem>
|
<MenuItem RouterLink="@($"/Games/{Game.Id}/Actions")">Actions</MenuItem>
|
||||||
<MenuItem RouterLink="@($"/Games/{Game.Id}/Multiplayer")">Multiplayer</MenuItem>
|
<MenuItem RouterLink="@($"/Games/{Game.Id}/Multiplayer")">Multiplayer</MenuItem>
|
||||||
<MenuItem RouterLink="@($"/Games/{Game.Id}/SavePaths")">Save Paths</MenuItem>
|
<MenuItem RouterLink="@($"/Games/{Game.Id}/SavePaths")">Save Paths</MenuItem>
|
||||||
|
@ -107,6 +108,10 @@
|
||||||
|
|
||||||
@if (Game != null && Game.Id != Guid.Empty)
|
@if (Game != null && Game.Id != Guid.Empty)
|
||||||
{
|
{
|
||||||
|
<div data-panel="Media">
|
||||||
|
<MediaEditor @bind-Values="Game.Media" GameId="Game.Id" GameTitle="@Game.Title" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div data-panel="Actions">
|
<div data-panel="Actions">
|
||||||
<ActionEditor @bind-Actions="Game.Actions" GameId="Game.Id" ArchiveId="@LatestArchiveId" />
|
<ActionEditor @bind-Actions="Game.Actions" GameId="Game.Id" ArchiveId="@LatestArchiveId" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,6 +10,7 @@ using NLog.Web;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using LANCommander.Services.MediaGrabbers;
|
||||||
|
|
||||||
namespace LANCommander
|
namespace LANCommander
|
||||||
{
|
{
|
||||||
|
@ -141,7 +142,9 @@ namespace LANCommander
|
||||||
builder.Services.AddScoped<ServerService>();
|
builder.Services.AddScoped<ServerService>();
|
||||||
builder.Services.AddScoped<ServerConsoleService>();
|
builder.Services.AddScoped<ServerConsoleService>();
|
||||||
builder.Services.AddScoped<GameSaveService>();
|
builder.Services.AddScoped<GameSaveService>();
|
||||||
|
builder.Services.AddScoped<MediaService>();
|
||||||
builder.Services.AddScoped<RedistributableService>();
|
builder.Services.AddScoped<RedistributableService>();
|
||||||
|
builder.Services.AddScoped<IMediaGrabberService, SteamGridDBMediaGrabber>();
|
||||||
|
|
||||||
builder.Services.AddSingleton<ServerProcessService>();
|
builder.Services.AddSingleton<ServerProcessService>();
|
||||||
builder.Services.AddSingleton<IPXRelayService>();
|
builder.Services.AddSingleton<IPXRelayService>();
|
||||||
|
@ -211,6 +214,9 @@ namespace LANCommander
|
||||||
if (!Directory.Exists(settings.UserSaves.StoragePath))
|
if (!Directory.Exists(settings.UserSaves.StoragePath))
|
||||||
Directory.CreateDirectory(settings.UserSaves.StoragePath);
|
Directory.CreateDirectory(settings.UserSaves.StoragePath);
|
||||||
|
|
||||||
|
if (!Directory.Exists(settings.Media.StoragePath))
|
||||||
|
Directory.CreateDirectory(settings.Media.StoragePath);
|
||||||
|
|
||||||
if (!Directory.Exists("Snippets"))
|
if (!Directory.Exists("Snippets"))
|
||||||
Directory.CreateDirectory("Snippets");
|
Directory.CreateDirectory("Snippets");
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
using LANCommander.Data.Enums;
|
||||||
|
using LANCommander.Models;
|
||||||
|
|
||||||
|
namespace LANCommander.Services
|
||||||
|
{
|
||||||
|
public interface IMediaGrabberService
|
||||||
|
{
|
||||||
|
Task<IEnumerable<MediaGrabberResult>> SearchAsync(MediaType type, string keywords);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
using craftersmine.SteamGridDBNet;
|
||||||
|
using LANCommander.Data.Enums;
|
||||||
|
using LANCommander.Models;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
|
namespace LANCommander.Services.MediaGrabbers
|
||||||
|
{
|
||||||
|
public class SteamGridDBMediaGrabber : IMediaGrabberService
|
||||||
|
{
|
||||||
|
SteamGridDb SteamGridDb { get; set; }
|
||||||
|
public SteamGridDBMediaGrabber()
|
||||||
|
{
|
||||||
|
var settings = SettingService.GetSettings();
|
||||||
|
|
||||||
|
SteamGridDb = new SteamGridDb(settings.Media.SteamGridDbApiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<MediaGrabberResult>> SearchAsync(MediaType type, string keywords)
|
||||||
|
{
|
||||||
|
var games = await SteamGridDb.SearchForGamesAsync(keywords);
|
||||||
|
|
||||||
|
if (games.Length > 0)
|
||||||
|
{
|
||||||
|
var game = games.FirstOrDefault();
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case MediaType.Icon:
|
||||||
|
return await GetIconsAsync(game.Id);
|
||||||
|
|
||||||
|
case MediaType.Cover:
|
||||||
|
return await GetCoversAsync(game.Id);
|
||||||
|
|
||||||
|
case MediaType.Background:
|
||||||
|
return await GetBackgroundsAsync(game.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new List<MediaGrabberResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<MediaGrabberResult>> GetIconsAsync(int gameId)
|
||||||
|
{
|
||||||
|
var icons = await SteamGridDb.GetIconsByGameIdAsync(gameId);
|
||||||
|
|
||||||
|
return icons.Select(i => new MediaGrabberResult()
|
||||||
|
{
|
||||||
|
Id = i.Id.ToString(),
|
||||||
|
Type = MediaType.Icon,
|
||||||
|
SourceUrl = i.FullImageUrl,
|
||||||
|
ThumbnailUrl = i.ThumbnailImageUrl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<MediaGrabberResult>> GetCoversAsync(int gameId)
|
||||||
|
{
|
||||||
|
var covers = await SteamGridDb.GetGridsByGameIdAsync(gameId);
|
||||||
|
|
||||||
|
return covers.Select(c => new MediaGrabberResult()
|
||||||
|
{
|
||||||
|
Id = c.Id.ToString(),
|
||||||
|
Type = MediaType.Cover,
|
||||||
|
SourceUrl = c.FullImageUrl,
|
||||||
|
ThumbnailUrl = c.ThumbnailImageUrl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<MediaGrabberResult>> GetBackgroundsAsync(int gameId)
|
||||||
|
{
|
||||||
|
var backgrounds = await SteamGridDb.GetHeroesByGameIdAsync(gameId);
|
||||||
|
|
||||||
|
return backgrounds.Select(b => new MediaGrabberResult()
|
||||||
|
{
|
||||||
|
Id = b.Id.ToString(),
|
||||||
|
Type = MediaType.Background,
|
||||||
|
SourceUrl = b.FullImageUrl,
|
||||||
|
ThumbnailUrl = b.ThumbnailImageUrl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
using LANCommander.Data;
|
||||||
|
using LANCommander.Data.Models;
|
||||||
|
using LANCommander.Helpers;
|
||||||
|
using LANCommander.Models;
|
||||||
|
|
||||||
|
namespace LANCommander.Services
|
||||||
|
{
|
||||||
|
public class MediaService : BaseDatabaseService<Media>
|
||||||
|
{
|
||||||
|
private readonly LANCommanderSettings Settings;
|
||||||
|
|
||||||
|
public MediaService(DatabaseContext dbContext, IHttpContextAccessor httpContextAccessor) : base(dbContext, httpContextAccessor)
|
||||||
|
{
|
||||||
|
Settings = SettingService.GetSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task Delete(Media entity)
|
||||||
|
{
|
||||||
|
FileHelpers.DeleteIfExists(GetImagePath(entity));
|
||||||
|
|
||||||
|
return base.Delete(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FileExists(Media entity)
|
||||||
|
{
|
||||||
|
var path = GetImagePath(entity);
|
||||||
|
|
||||||
|
return File.Exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> FileExists(Guid id)
|
||||||
|
{
|
||||||
|
var path = await GetImagePath(id);
|
||||||
|
|
||||||
|
return File.Exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetImagePath(Guid id)
|
||||||
|
{
|
||||||
|
var entity = await Get(id);
|
||||||
|
|
||||||
|
return GetImagePath(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetImagePath(Media entity)
|
||||||
|
{
|
||||||
|
return Path.Combine(Settings.Media.StoragePath, entity.FileId.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Guid> DownloadMediaAsync(string sourceUrl)
|
||||||
|
{
|
||||||
|
var fileId = Guid.NewGuid();
|
||||||
|
var path = Path.Combine(Settings.Media.StoragePath, fileId.ToString());
|
||||||
|
|
||||||
|
using (var http = new HttpClient())
|
||||||
|
using (var fs = new FileStream(path, FileMode.Create))
|
||||||
|
{
|
||||||
|
var response = await http.GetStreamAsync(sourceUrl);
|
||||||
|
|
||||||
|
await response.CopyToAsync(fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -173,6 +173,51 @@
|
||||||
background: rgba(255, 255, 255, 0.03);
|
background: rgba(255, 255, 255, 0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image-picker-images {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-image {
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-image img {
|
||||||
|
border: 4px solid transparent;
|
||||||
|
border-radius: 2px;
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-image input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-image input + label {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-image input:checked ~ img {
|
||||||
|
border-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-editor img {
|
||||||
|
max-width: 100px;
|
||||||
|
max-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.mobile-menu {
|
.mobile-menu {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
Loading…
Reference in New Issue