Most Changes for v0.1.0-beta #51
@@ -1,146 +1,279 @@
|
|||||||
@using CouchLog.Data
|
@using CouchLog.Data
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
@using CouchLog.Data
|
|
||||||
@using Microsoft.AspNetCore.Identity
|
@using Microsoft.AspNetCore.Identity
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using System.ComponentModel.DataAnnotations
|
||||||
|
|
||||||
@inject ApplicationDbContext CouchLogDB
|
@inject ApplicationDbContext CouchLogDB
|
||||||
@inject UserManager<ApplicationUser> UserManager
|
@inject UserManager<ApplicationUser> UserManager
|
||||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject ISnackbar Snackbar
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.drop-zone-hover-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity .2s ease;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.drop-zone-wrapper:hover .drop-zone-hover-overlay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
<TitleContent>
|
<TitleContent>
|
||||||
<MudText Typo="Typo.h6">
|
<MudText Typo="Typo.h6">Add Global Entity</MudText>
|
||||||
Add Global Entity
|
|
||||||
</MudText>
|
|
||||||
</TitleContent>
|
</TitleContent>
|
||||||
<DialogContent>
|
|
||||||
<EditForm Model="@newGlobalEntity" OnSubmit="CreateNewGlobalEntity">
|
|
||||||
<DataAnnotationsValidator />
|
|
||||||
<ValidationSummary />
|
|
||||||
|
|
||||||
<div class="mb-3">
|
<DialogContent>
|
||||||
<label for="Title" class="form-label">Title</label>
|
<EditForm EditContext="_editContext" OnSubmit="HandleSubmit">
|
||||||
<InputText id="Title" class="form-control" @bind-Value="newGlobalEntity.Title"></InputText>
|
<DataAnnotationsValidator />
|
||||||
<ValidationMessage For="@(() => newGlobalEntity.Title)"></ValidationMessage>
|
|
||||||
</div>
|
<MudGrid Style="min-height: 400px;">
|
||||||
<div class="mb-3">
|
<MudItem xs="5" Style="display: flex;">
|
||||||
<label for="Type" class="form-label">Type</label>
|
<MudFileUpload T="IBrowserFile" @ref="_fileUpload" FilesChanged="OnPictureUploaded" Accept="image/*" Style="width: 100%; display: flex;">
|
||||||
<select id="Type" class="form-select" @bind="SelectedMediaTypeId">
|
<ActivatorContent>
|
||||||
@foreach(MediaType Type in MediaTypes)
|
<div @onclick="OpenFilePicker"
|
||||||
{
|
@ondragenter="@(() => _isDragOver = true)"
|
||||||
<option value="@Type.Id">@Type.Name</option>
|
@ondragleave="@(() => _isDragOver = false)"
|
||||||
}
|
@ondragover:preventDefault="true"
|
||||||
</select>
|
@ondrop:preventDefault="true"
|
||||||
</div>
|
@ondrop="@(() => _isDragOver = false)"
|
||||||
@*<div class="mb-3">
|
style="@GetDropZoneStyle()"
|
||||||
<label for="Picture" class="form-label">Picture</label>
|
class="d-flex align-center justify-center cursor-pointer drop-zone-wrapper">
|
||||||
<InputFile OnChange="@LoadImage" accept="image/*" />
|
|
||||||
@if (!string.IsNullOrEmpty(ImageString))
|
@if (_imagePreviewUrl is not null)
|
||||||
{
|
{
|
||||||
<img src="@ImageString" style="max-width: 256px; max-height: 256px;" />
|
<div style="position: relative; width: 100%; height: 100%;">
|
||||||
}
|
<img src="@_imagePreviewUrl"
|
||||||
</div>*@
|
alt="Preview"
|
||||||
<div class="mb-3">
|
style="width: 100%; height: 100%; object-fit: cover; border-radius: 6px; display: block;" />
|
||||||
<label for="Genres" class="form-label">Genres</label>
|
<div class="drop-zone-hover-overlay">
|
||||||
@foreach(Genre Genre in Genres)
|
<MudStack AlignItems="AlignItems.Center" Spacing="1">
|
||||||
{
|
<MudIcon Icon="@Icons.Material.Filled.Edit"
|
||||||
<button class="btn-primary btn me-2" value="@Genre.Id" type="button" @onclick="() => AddGenreIdToGenreIdList(Genre.Id)">@Genre.Name</button>
|
Color="Color.Surface"
|
||||||
}
|
Size="Size.Large" />
|
||||||
</div>
|
<MudText Typo="Typo.caption" Style="color: white;">
|
||||||
<div class="mb-3">
|
Replace Image
|
||||||
<InputCheckbox DisplayName="isPrivate" @bind-Value="IsPrivate" ></InputCheckbox>
|
</MudText>
|
||||||
</div>
|
</MudStack>
|
||||||
<button type="submit" class="btn-success btn">Create new Global Entity</button>
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudStack AlignItems="AlignItems.Center" Spacing="2">
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.CloudUpload"
|
||||||
|
Size="Size.Large"
|
||||||
|
Color="@(_isDragOver ? Color.Primary : Color.Secondary)" />
|
||||||
|
<MudText Typo="Typo.body2"
|
||||||
|
Align="Align.Center"
|
||||||
|
Color="@(_isDragOver ? Color.Primary : Color.Secondary)">
|
||||||
|
Bild hierher ziehen<br />oder klicken zum Auswählen
|
||||||
|
</MudText>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</ActivatorContent>
|
||||||
|
</MudFileUpload>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<!-- ═══ Rechte Spalte: Formular ═══ -->
|
||||||
|
<MudItem xs="7">
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="_form.Title"
|
||||||
|
Label="Title"
|
||||||
|
For="@(() => _form.Title)"
|
||||||
|
Variant="Variant.Outlined" />
|
||||||
|
|
||||||
|
<MudSelect T="int"
|
||||||
|
@bind-Value="_selectedMediaTypeId"
|
||||||
|
Label="Type"
|
||||||
|
Variant="Variant.Outlined">
|
||||||
|
@foreach (var type in _mediaTypes)
|
||||||
|
{
|
||||||
|
<MudSelectItem T="int" Value="@type.Id">@type.Name</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
|
||||||
|
<MudSelect T="int"
|
||||||
|
Label="Genres"
|
||||||
|
MultiSelection="true"
|
||||||
|
@bind-SelectedValues="_selectedGenreIds"
|
||||||
|
Variant="Variant.Outlined">
|
||||||
|
@foreach (var genre in _genres)
|
||||||
|
{
|
||||||
|
<MudSelectItem T="int" Value="@genre.Id">@genre.Name</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
<MudCheckBox @bind-Value="_isPrivate" Label="Is Private" Color="Color.Primary" />
|
||||||
|
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Success" Disabled="_isSaving" Class="mt-3">
|
||||||
|
@if (_isSaving)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Indeterminate="true" Size="Size.Small" Class="mr-2" />
|
||||||
|
<span>Saving...</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>Create Global Entity</span>
|
||||||
|
}
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
</EditForm>
|
</EditForm>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
@*Dialog Footer*@
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
<MudButton OnClick="Cancel" Variant="Variant.Text" Disabled="_isSaving">Cancel</MudButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</MudDialog>
|
</MudDialog>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private ApplicationUser AppUser = null!;
|
|
||||||
|
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
private IMudDialogInstance MudDialog { get; set; }
|
private IMudDialogInstance MudDialog { get; set; } = null!;
|
||||||
private void Cancel() => MudDialog.Cancel();
|
|
||||||
|
|
||||||
|
private ApplicationUser? _appUser;
|
||||||
|
private EditContext _editContext = null!;
|
||||||
|
|
||||||
private List<MediaType> MediaTypes = new();
|
private List<MediaType> _mediaTypes = new();
|
||||||
private List<Genre> Genres = new();
|
private List<Genre> _genres = new();
|
||||||
|
|
||||||
#region ForNewGlobalEntity
|
private GlobalEntityFormModel _form = new();
|
||||||
private GlobalEntity newGlobalEntity = new();
|
private MudFileUpload<IBrowserFile>? _fileUpload;
|
||||||
private IBrowserFile? Picture;
|
private IBrowserFile? _picture;
|
||||||
private int SelectedMediaTypeId;
|
private string? _imagePreviewUrl;
|
||||||
private bool IsPrivate;
|
private bool _isDragOver;
|
||||||
private List<int> GenreIds = new ();
|
private int _selectedMediaTypeId;
|
||||||
#endregion
|
private bool _isPrivate;
|
||||||
|
private bool _isSaving;
|
||||||
|
private IEnumerable<int> _selectedGenreIds = new HashSet<int>();
|
||||||
|
|
||||||
|
private class GlobalEntityFormModel
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "Title is required.")]
|
||||||
|
[MaxLength(200, ErrorMessage = "Title may not exceed 200 characters.")]
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDropZoneStyle() =>
|
||||||
|
"width: 100%; height: 100%; min-height: 380px; border-radius: 6px; " +
|
||||||
|
$"border: 2px dashed {(_isDragOver ? "var(--mud-palette-primary)" : "var(--mud-palette-lines-default)")}; " +
|
||||||
|
$"background-color: {(_isDragOver ? "var(--mud-palette-primary-hover)" : "transparent")}; " +
|
||||||
|
"transition: border-color .2s ease, background-color .2s ease;";
|
||||||
|
|
||||||
|
private Task OpenFilePicker() =>
|
||||||
|
_fileUpload?.OpenFilePickerAsync() ?? Task.CompletedTask;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
var AuthState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
_editContext = new EditContext(_form);
|
||||||
var User = AuthState.User;
|
|
||||||
AppUser = (await UserManager.GetUserAsync(User))!;
|
|
||||||
|
|
||||||
if(AppUser == null)
|
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||||
|
_appUser = await UserManager.GetUserAsync(authState.User);
|
||||||
|
|
||||||
|
if (_appUser is null)
|
||||||
{
|
{
|
||||||
throw new Exception("Not Authenticated");
|
Snackbar.Add("Not authenticated.", Severity.Error);
|
||||||
|
MudDialog.Cancel();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaTypes = await CouchLogDB.MediaType.OrderBy(Type => Type.Id).ToListAsync();
|
_mediaTypes = await CouchLogDB.MediaType.OrderBy(t => t.Id).ToListAsync();
|
||||||
Genres = await CouchLogDB.Genres.OrderBy(Type => Type.Id).ToListAsync();
|
_genres = await CouchLogDB.Genres.OrderBy(t => t.Id).ToListAsync();
|
||||||
|
|
||||||
|
if (_mediaTypes.Count > 0)
|
||||||
|
_selectedMediaTypeId = _mediaTypes.First().Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CreateNewGlobalEntity()
|
private async Task OnPictureUploaded(IBrowserFile file)
|
||||||
{
|
{
|
||||||
if (Picture is null)
|
_picture = file;
|
||||||
{
|
|
||||||
throw new InvalidOperationException("No Picture selected.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AppUser is null)
|
// Base64-Vorschau generieren (max. 5 MB)
|
||||||
{
|
const long maxPreviewSize = 5 * 1024 * 1024;
|
||||||
throw new InvalidOperationException("User not loaded or not logged in.");
|
await using var stream = file.OpenReadStream(maxPreviewSize);
|
||||||
}
|
using var ms = new MemoryStream();
|
||||||
|
await stream.CopyToAsync(ms);
|
||||||
|
|
||||||
//Save Picture and Name it
|
var base64 = Convert.ToBase64String(ms.ToArray());
|
||||||
string NewFileName = $"{newGlobalEntity.Title.Replace(" ", "-")}-{Guid.NewGuid()}{Path.GetExtension(Picture.Name)}";
|
_imagePreviewUrl = $"data:{file.ContentType};base64,{base64}";
|
||||||
string PicturePath = Path.Combine("wwwroot", "Pictures", NewFileName);
|
|
||||||
using FileStream FileStream = File.Create(PicturePath);
|
|
||||||
await Picture.OpenReadStream().CopyToAsync(FileStream);
|
|
||||||
//await Picture.OpenReadStream(maxAllowedSize: 10_000_000).CopyToAsync(FileStream);
|
|
||||||
|
|
||||||
newGlobalEntity.PicturePath = $"Pictures/{NewFileName}";
|
|
||||||
newGlobalEntity.CreatorId = AppUser.Id;
|
|
||||||
newGlobalEntity.TypeId = SelectedMediaTypeId;
|
|
||||||
newGlobalEntity.CreationTime = DateTime.Now;
|
|
||||||
newGlobalEntity.IsPrivate = IsPrivate;
|
|
||||||
|
|
||||||
CouchLogDB.Add(newGlobalEntity);
|
|
||||||
await CouchLogDB.SaveChangesAsync();
|
|
||||||
|
|
||||||
foreach(int GenreId in GenreIds)
|
|
||||||
{
|
|
||||||
LinkTableGlobalGenre LinkTableGlobalGenre = new() { GenreId = GenreId, GlobalEntityId = newGlobalEntity.Id };
|
|
||||||
CouchLogDB.Add(LinkTableGlobalGenre);
|
|
||||||
}
|
|
||||||
|
|
||||||
await CouchLogDB.SaveChangesAsync();
|
|
||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddGenreIdToGenreIdList(int GenreId)
|
private void Cancel() => MudDialog.Cancel();
|
||||||
|
|
||||||
|
private async Task HandleSubmit(EditContext ctx)
|
||||||
{
|
{
|
||||||
if (!GenreIds.Contains(GenreId))
|
if (!ctx.Validate()) return;
|
||||||
|
|
||||||
|
if (_picture is null)
|
||||||
{
|
{
|
||||||
GenreIds.Add(GenreId);
|
Snackbar.Add("Please upload a picture.", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_selectedMediaTypeId == 0)
|
||||||
|
{
|
||||||
|
Snackbar.Add("Please select a media type.", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isSaving = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string safeTitle = _form.Title.Replace(" ", "-");
|
||||||
|
string fileName = $"{safeTitle}-{Guid.NewGuid()}{Path.GetExtension(_picture.Name)}";
|
||||||
|
string picturePath = Path.Combine("wwwroot", "Pictures", fileName);
|
||||||
|
|
||||||
|
await using (var fs = File.Create(picturePath))
|
||||||
|
{
|
||||||
|
await _picture.OpenReadStream(maxAllowedSize: 10_000_000).CopyToAsync(fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
var entity = new GlobalEntity
|
||||||
|
{
|
||||||
|
Title = _form.Title,
|
||||||
|
PicturePath = $"Pictures/{fileName}",
|
||||||
|
CreatorId = _appUser!.Id,
|
||||||
|
TypeId = _selectedMediaTypeId,
|
||||||
|
CreationTime = DateTime.UtcNow,
|
||||||
|
IsPrivate = _isPrivate
|
||||||
|
};
|
||||||
|
|
||||||
|
CouchLogDB.Add(entity);
|
||||||
|
await CouchLogDB.SaveChangesAsync();
|
||||||
|
|
||||||
|
foreach (int genreId in _selectedGenreIds)
|
||||||
|
{
|
||||||
|
CouchLogDB.Add(new LinkTableGlobalGenre
|
||||||
|
{
|
||||||
|
GenreId = genreId,
|
||||||
|
GlobalEntityId = entity.Id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await CouchLogDB.SaveChangesAsync();
|
||||||
|
|
||||||
|
Snackbar.Add("Global Entity created!", Severity.Success);
|
||||||
|
MudDialog.Close(DialogResult.Ok(entity.Id));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"Error: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isSaving = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user