Merge branch 'media'
commit
14a92bdc3e
|
@ -352,3 +352,5 @@ Upload/
|
||||||
LANCommander/Icon/
|
LANCommander/Icon/
|
||||||
LANCommander/Settings.yml
|
LANCommander/Settings.yml
|
||||||
LANCommander/Saves/
|
LANCommander/Saves/
|
||||||
|
LANCommander/Media/
|
||||||
|
LANCommander/Uploads/
|
||||||
|
|
|
@ -235,6 +235,11 @@ namespace LANCommander.PlaynitePlugin
|
||||||
return response.Data;
|
return response.Data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetMediaUrl(Media media)
|
||||||
|
{
|
||||||
|
return (new Uri(Client.BaseUrl, $"/api/Media/{media.Id}/Download?fileId={media.FileId}").ToString());
|
||||||
|
}
|
||||||
|
|
||||||
public string GetKey(Guid id)
|
public string GetKey(Guid id)
|
||||||
{
|
{
|
||||||
Logger.Trace("Requesting key allocation...");
|
Logger.Trace("Requesting key allocation...");
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -183,6 +183,15 @@ namespace LANCommander.PlaynitePlugin
|
||||||
if (manifest.OnlineMultiplayer != null)
|
if (manifest.OnlineMultiplayer != null)
|
||||||
metadata.Features.Add(new MetadataNameProperty($"Online Multiplayer {manifest.OnlineMultiplayer.GetPlayerCount()}".Trim()));
|
metadata.Features.Add(new MetadataNameProperty($"Online Multiplayer {manifest.OnlineMultiplayer.GetPlayerCount()}".Trim()));
|
||||||
|
|
||||||
|
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Icon))
|
||||||
|
metadata.Icon = new MetadataFile(LANCommander.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Icon)));
|
||||||
|
|
||||||
|
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Cover))
|
||||||
|
metadata.CoverImage = new MetadataFile(LANCommander.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Cover)));
|
||||||
|
|
||||||
|
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Background))
|
||||||
|
metadata.BackgroundImage = new MetadataFile(LANCommander.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Background)));
|
||||||
|
|
||||||
gameMetadata.Add(metadata);
|
gameMetadata.Add(metadata);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LANCommander.SDK.Enums
|
||||||
|
{
|
||||||
|
public enum MediaType
|
||||||
|
{
|
||||||
|
Icon,
|
||||||
|
Cover,
|
||||||
|
Background
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ namespace LANCommander.SDK.Models
|
||||||
public virtual Company Developer { get; set; }
|
public virtual Company Developer { get; set; }
|
||||||
public virtual IEnumerable<Archive> Archives { get; set; }
|
public virtual IEnumerable<Archive> Archives { get; set; }
|
||||||
public virtual IEnumerable<Script> Scripts { get; set; }
|
public virtual IEnumerable<Script> Scripts { get; set; }
|
||||||
|
public virtual IEnumerable<Media> Media { get; set; }
|
||||||
public virtual IEnumerable<Redistributable> Redistributables { get; set; }
|
public virtual IEnumerable<Redistributable> Redistributables { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
using LANCommander.SDK.Enums;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LANCommander.SDK.Models
|
||||||
|
{
|
||||||
|
public class Media : BaseModel
|
||||||
|
{
|
||||||
|
public Guid FileId { get; set; }
|
||||||
|
public MediaType Type { get; set; }
|
||||||
|
public string SourceUrl { get; set; }
|
||||||
|
public string MimeType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; max-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,63 @@
|
||||||
|
@inherits FeedbackComponent<MediaGrabberOptions, MediaGrabberResult>
|
||||||
|
@using LANCommander.Data.Enums;
|
||||||
|
@using LANCommander.Models;
|
||||||
|
@inject IMediaGrabberService MediaGrabberService
|
||||||
|
|
||||||
|
<GridRow Justify="space-between">
|
||||||
|
<GridCol Span="6">
|
||||||
|
<Search @bind-Value="Search" OnSearch="(x) => GetResults(Type, x)" DefaultValue="@Search" />
|
||||||
|
</GridCol>
|
||||||
|
<GridCol Span="12"></GridCol>
|
||||||
|
<GridCol Span="6">
|
||||||
|
<Slider TValue="double" @bind-Value="Size" DefaultValue="200" Min="50" Max="400" />
|
||||||
|
</GridCol>
|
||||||
|
</GridRow>
|
||||||
|
|
||||||
|
@foreach (var group in Results)
|
||||||
|
{
|
||||||
|
<div class="media-grabber-group">
|
||||||
|
<h2>@group.First().Group</h2>
|
||||||
|
|
||||||
|
<ImagePicker Size="Size" Images="@group.ToDictionary(r => r.Id, r => r.ThumbnailUrl)" ValueChanged="OnImageSelected" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public string Search { get; set; }
|
||||||
|
[Parameter] public MediaType Type { get; set; }
|
||||||
|
|
||||||
|
MediaGrabberResult Media { get; set; }
|
||||||
|
|
||||||
|
double Size { get; set; } = 200;
|
||||||
|
|
||||||
|
IEnumerable<IEnumerable<MediaGrabberResult>> Results = new List<List<MediaGrabberResult>>();
|
||||||
|
Dictionary<string, string> Images { get; set; } = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
protected override async Task OnFirstAfterRenderAsync()
|
||||||
|
{
|
||||||
|
Type = Options.Type;
|
||||||
|
Search = Options.Search;
|
||||||
|
|
||||||
|
await GetResults(Type, Search);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetResults(MediaType type, string search)
|
||||||
|
{
|
||||||
|
if (!String.IsNullOrWhiteSpace(search))
|
||||||
|
{
|
||||||
|
Results = (await MediaGrabberService.SearchAsync(type, search)).GroupBy(r => r.Group);
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnImageSelected(string key)
|
||||||
|
{
|
||||||
|
Media = Results.SelectMany(g => g).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, media.MimeType);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,5 +48,11 @@ namespace LANCommander.Controllers.Api
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("Media")]
|
||||||
|
public async Task Media(IFormFile file)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,26 @@
|
||||||
|
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; }
|
||||||
|
|
||||||
|
[MaxLength(255)]
|
||||||
|
public string MimeType { 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" />
|
||||||
|
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,30 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace LANCommander.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddMediaMimeType : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "MimeType",
|
||||||
|
table: "Media",
|
||||||
|
type: "TEXT",
|
||||||
|
maxLength: 255,
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "MimeType",
|
||||||
|
table: "Media");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -474,6 +474,54 @@ 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>("MimeType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.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 +1350,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 +1591,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,14 @@
|
||||||
|
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; }
|
||||||
|
public string Group { get; set; }
|
||||||
|
public string MimeType { 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,13 @@
|
||||||
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 long MaxSize { get; set; } = 25;
|
||||||
|
}
|
||||||
|
|
||||||
public class LANCommanderIPXRelaySettings
|
public class LANCommanderIPXRelaySettings
|
||||||
{
|
{
|
||||||
public bool Enabled { get; set; } = false;
|
public bool Enabled { get; set; } = false;
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
@using LANCommander.Data.Enums;
|
||||||
|
@using LANCommander.Models;
|
||||||
|
@inject MediaService MediaService
|
||||||
|
@inject ModalService ModalService
|
||||||
|
@inject IMessageService MessageService
|
||||||
|
|
||||||
|
<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?fileId={context.FileId}")" />
|
||||||
|
}
|
||||||
|
</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>
|
||||||
|
@{
|
||||||
|
var fileInputId = Guid.NewGuid();
|
||||||
|
}
|
||||||
|
<InputFile id="@fileInputId" OnChange="(e) => UploadMedia(e, context)" hidden />
|
||||||
|
<label class="ant-btn ant-btn-text ant-btn-icon-only" for="@fileInputId">
|
||||||
|
<Icon Type="@IconType.Outline.Upload" />
|
||||||
|
</label>
|
||||||
|
</SpaceItem>
|
||||||
|
<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; }
|
||||||
|
|
||||||
|
LANCommanderSettings Settings = SettingService.GetSettings();
|
||||||
|
|
||||||
|
string[] ValidMimeTypes = new string[]
|
||||||
|
{
|
||||||
|
"image/png",
|
||||||
|
"image/jpeg"
|
||||||
|
};
|
||||||
|
|
||||||
|
private async Task AddMedia()
|
||||||
|
{
|
||||||
|
if (Values == null)
|
||||||
|
Values = new List<Media>();
|
||||||
|
|
||||||
|
Values.Add(new Media()
|
||||||
|
{
|
||||||
|
GameId = GameId,
|
||||||
|
Type = Enum.GetValues<MediaType>().ToList().FirstOrDefault(t => !Values.Any(v => v.Type == t))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SearchMedia(Media media)
|
||||||
|
{
|
||||||
|
var modalOptions = new ModalOptions()
|
||||||
|
{
|
||||||
|
Title = $"Download {media.Type}",
|
||||||
|
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.MimeType = result.MimeType;
|
||||||
|
|
||||||
|
if (media.Id == Guid.Empty)
|
||||||
|
{
|
||||||
|
media.FileId = await MediaService.DownloadMediaAsync(result.SourceUrl);
|
||||||
|
|
||||||
|
await MediaService.Add(media);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MediaService.DeleteLocalMediaFile(media.FileId);
|
||||||
|
media.FileId = await MediaService.DownloadMediaAsync(result.SourceUrl);
|
||||||
|
await MediaService.Update(media);
|
||||||
|
}
|
||||||
|
|
||||||
|
Values = MediaService.Get(m => m.GameId == media.GameId).ToList();
|
||||||
|
|
||||||
|
if (ValuesChanged.HasDelegate)
|
||||||
|
await ValuesChanged.InvokeAsync(Values);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UploadMedia(InputFileChangeEventArgs e, Media media)
|
||||||
|
{
|
||||||
|
if (!ValidMimeTypes.Contains(e.File.ContentType))
|
||||||
|
{
|
||||||
|
MessageService.Error("Unsupported file type");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((e.File.Size / 1024 / 1024) > Settings.Media.MaxSize)
|
||||||
|
{
|
||||||
|
MessageService.Error($"File size must be smaller than {Settings.Media.MaxSize}MB");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
media.SourceUrl = "";
|
||||||
|
media.MimeType = e.File.ContentType;
|
||||||
|
|
||||||
|
if (media.Id == Guid.Empty)
|
||||||
|
{
|
||||||
|
media.FileId = await MediaService.UploadMediaAsync(e.File);
|
||||||
|
|
||||||
|
await MediaService.Add(media);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MediaService.DeleteLocalMediaFile(media.FileId);
|
||||||
|
media.FileId = await MediaService.UploadMediaAsync(e.File);
|
||||||
|
await MediaService.Update(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,112 @@
|
||||||
|
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; }
|
||||||
|
|
||||||
|
private SteamGridDbFormats[] SupportedFormats = new SteamGridDbFormats[]
|
||||||
|
{
|
||||||
|
SteamGridDbFormats.Png,
|
||||||
|
SteamGridDbFormats.Jpeg,
|
||||||
|
SteamGridDbFormats.Webp
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
var results = new List<MediaGrabberResult>();
|
||||||
|
|
||||||
|
foreach (var game in games)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case MediaType.Icon:
|
||||||
|
results.AddRange(await GetIconsAsync(game));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MediaType.Cover:
|
||||||
|
results.AddRange(await GetCoversAsync(game));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MediaType.Background:
|
||||||
|
results.AddRange(await GetBackgroundsAsync(game));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<MediaGrabberResult>> GetIconsAsync(SteamGridDbGame game)
|
||||||
|
{
|
||||||
|
var icons = await SteamGridDb.GetIconsByGameIdAsync(game.Id);
|
||||||
|
|
||||||
|
return icons.Where(i => SupportedFormats.Contains(i.Format)).Select(i => new MediaGrabberResult()
|
||||||
|
{
|
||||||
|
Id = i.Id.ToString(),
|
||||||
|
Type = MediaType.Icon,
|
||||||
|
SourceUrl = i.FullImageUrl,
|
||||||
|
ThumbnailUrl = i.ThumbnailImageUrl,
|
||||||
|
Group = game.Name,
|
||||||
|
MimeType = GetMimeType(i.Format)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<MediaGrabberResult>> GetCoversAsync(SteamGridDbGame game)
|
||||||
|
{
|
||||||
|
var covers = await SteamGridDb.GetGridsByGameIdAsync(game.Id);
|
||||||
|
|
||||||
|
return covers.Where(c => SupportedFormats.Contains(c.Format)).Select(c => new MediaGrabberResult()
|
||||||
|
{
|
||||||
|
Id = c.Id.ToString(),
|
||||||
|
Type = MediaType.Cover,
|
||||||
|
SourceUrl = c.FullImageUrl,
|
||||||
|
ThumbnailUrl = c.ThumbnailImageUrl,
|
||||||
|
Group = game.Name,
|
||||||
|
MimeType = GetMimeType(c.Format)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<MediaGrabberResult>> GetBackgroundsAsync(SteamGridDbGame game)
|
||||||
|
{
|
||||||
|
var backgrounds = await SteamGridDb.GetHeroesByGameIdAsync(game.Id);
|
||||||
|
|
||||||
|
return backgrounds.Where(b => SupportedFormats.Contains(b.Format)).Select(b => new MediaGrabberResult()
|
||||||
|
{
|
||||||
|
Id = b.Id.ToString(),
|
||||||
|
Type = MediaType.Background,
|
||||||
|
SourceUrl = b.FullImageUrl,
|
||||||
|
ThumbnailUrl = b.ThumbnailImageUrl,
|
||||||
|
Group = game.Name,
|
||||||
|
MimeType = GetMimeType(b.Format)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetMimeType(SteamGridDbFormats format)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case SteamGridDbFormats.Png:
|
||||||
|
return "image/png";
|
||||||
|
case SteamGridDbFormats.Jpeg:
|
||||||
|
return "image/jpg";
|
||||||
|
case SteamGridDbFormats.Webp:
|
||||||
|
return "image/webp";
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException("The SteamGridDB grabber currently does not support this format");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
using LANCommander.Data;
|
||||||
|
using LANCommander.Data.Models;
|
||||||
|
using LANCommander.Helpers;
|
||||||
|
using LANCommander.Models;
|
||||||
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
|
|
||||||
|
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> UploadMediaAsync(IBrowserFile file)
|
||||||
|
{
|
||||||
|
var fileId = Guid.NewGuid();
|
||||||
|
|
||||||
|
var path = Path.Combine(Settings.Media.StoragePath, fileId.ToString());
|
||||||
|
|
||||||
|
using (var fs = new FileStream(path, FileMode.Create))
|
||||||
|
{
|
||||||
|
await file.OpenReadStream().CopyToAsync(fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteLocalMediaFile(Guid fileId)
|
||||||
|
{
|
||||||
|
var path = Path.Combine(Settings.Media.StoragePath, fileId.ToString());
|
||||||
|
|
||||||
|
if (File.Exists(path))
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,12 @@
|
||||||
background: #141414;
|
background: #141414;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label.ant-btn-icon-only {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-select-selector {
|
.ant-select-selector {
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
}
|
}
|
||||||
|
@ -173,6 +179,63 @@
|
||||||
background: rgba(255, 255, 255, 0.03);
|
background: rgba(255, 255, 255, 0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image-picker-images {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-image {
|
||||||
|
position: relative;
|
||||||
|
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;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-grabber-group {
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-grabber-group + .media-grabber-group {
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="Dark"] .media-grabber-group + .media-grabber-group {
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
@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