Most Changes for v0.1.0-beta #51
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v0.1.0-beta
|
||||
|
||||
jobs:
|
||||
build-docker-linux-arm64:
|
||||
@@ -14,23 +15,70 @@ jobs:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Get Repository Name
|
||||
id: get_repo
|
||||
run: |
|
||||
echo "REPO_LC=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
||||
|
||||
REPO_LC="${GITHUB_REPOSITORY,,}"
|
||||
echo "REPO_LC=$REPO_LC" >> $GITHUB_ENV
|
||||
echo "Repo: $REPO_LC"
|
||||
|
||||
- name: Build version
|
||||
run: |
|
||||
git fetch --force --tags
|
||||
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 --match "v*")
|
||||
DISTANCE=$(git rev-list "${LAST_TAG}"..HEAD --count 2>/dev/null || echo "0")
|
||||
|
||||
BASE_VERSION="${LAST_TAG#v}"
|
||||
|
||||
# Pre-release-Suffix extrahieren (beta, alpha, etc.)
|
||||
if [[ "$BASE_VERSION" == *-* ]]; then
|
||||
PRE_RELEASE=$(echo "$BASE_VERSION" | sed 's/.*-/-/')
|
||||
else
|
||||
PRE_RELEASE=""
|
||||
fi
|
||||
|
||||
# Nightly-Version bauen
|
||||
if [[ "$PRE_RELEASE" == "" ]]; then
|
||||
# Stabiles Release: v0.1.0 → v0.1.0-nightly.10
|
||||
VERSION="${BASE_VERSION}-nightly.${DISTANCE}"
|
||||
else
|
||||
# Beta/Alpha: v0.1.0-beta → v0.1.0-beta.10
|
||||
VERSION="${BASE_VERSION}.${DISTANCE}"
|
||||
fi
|
||||
|
||||
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
|
||||
echo "ASSEMBLY_VERSION=${VERSION}.0" >> "$GITHUB_ENV"
|
||||
echo "FILE_VERSION=${VERSION}.0" >> "$GITHUB_ENV"
|
||||
|
||||
echo "Last tag: $LAST_TAG"
|
||||
echo "Distance: $DISTANCE"
|
||||
echo "Pre-release: $PRE_RELEASE"
|
||||
echo "Final version: $VERSION"
|
||||
|
||||
- name: Build Linux ARM64 Docker Image
|
||||
run: |
|
||||
docker build \
|
||||
-t gitea.penry.de/${{ env.REPO_LC }}:latest \
|
||||
-t gitea.penry.de/${{ env.REPO_LC }}:nightly \
|
||||
.
|
||||
|
||||
- name: Login to own image registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: gitea.penry.de
|
||||
username: ${{ secrets.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Push Images
|
||||
run: |
|
||||
docker push gitea.penry.de/${{ env.REPO_LC }}:nightly
|
||||
|
||||
- name: Save Docker Image to Tar
|
||||
run: docker save -o CouchLog-Linux-ARM64-Docker-Image.tar gitea.penry.de/${{ env.REPO_LC }}:latest
|
||||
run: docker save -o CouchLog-Linux-ARM64-Docker-Image.tar gitea.penry.de/${{ env.REPO_LC }}:nightly
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"terminal.explorerKind": "integrated"
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
@page "/Account/Login"
|
||||
@layout Layout.IdentityLayout
|
||||
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@using Microsoft.AspNetCore.Authentication
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@using CouchLog.Data
|
||||
@using MudBlazor
|
||||
|
||||
@inject SignInManager<ApplicationUser> SignInManager
|
||||
@inject ILogger<Login> Logger
|
||||
@@ -12,57 +14,59 @@
|
||||
|
||||
<PageTitle>Log in</PageTitle>
|
||||
|
||||
<h1>Log in</h1>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<section>
|
||||
<MudGrid Justify="Justify.Center" Class="align-center">
|
||||
@* Linke Spalte: Login-Formular *@
|
||||
<MudItem xs="12" md="7">
|
||||
<MudPaper Elevation="3" Class="pa-6">
|
||||
<StatusMessage Message="@errorMessage" />
|
||||
|
||||
<EditForm Model="Input" method="post" OnValidSubmit="LoginUser" FormName="login">
|
||||
<DataAnnotationsValidator />
|
||||
<h2>Use a local account to log in.</h2>
|
||||
<hr />
|
||||
|
||||
<MudText Typo="Typo.h5" GutterBottom="true" Align="Align.Center">Login</MudText>
|
||||
<MudDivider Class="mb-6" />
|
||||
|
||||
<ValidationSummary class="text-danger" role="alert" />
|
||||
<div class="form-floating mb-3">
|
||||
<InputText @bind-Value="Input.Username" id="Input.Username" class="form-control" autocomplete="username" aria-required="true" placeholder="Username" />
|
||||
<label for="Input.Username" class="form-label">Username</label>
|
||||
<ValidationMessage For="() => Input.Username" class="text-danger" />
|
||||
|
||||
@* MudTextFields zwingend mit Name-Attribut für SSR *@
|
||||
<MudTextField @bind-Value="Input.Username"
|
||||
For="@(() => Input.Username)"
|
||||
Name="Input.Username"
|
||||
Label="Username"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
ShrinkLabel="true"
|
||||
Class="mb-4" />
|
||||
|
||||
<MudTextField @bind-Value="Input.Password"
|
||||
For="@(() => Input.Password)"
|
||||
Name="Input.Password"
|
||||
Label="Password"
|
||||
InputType="InputType.Password"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
ShrinkLabel="true"
|
||||
Class="mb-4" />
|
||||
|
||||
@* Standard-Checkbox mit MudBlazor-Typografie *@
|
||||
<div class="d-flex align-center mb-6">
|
||||
<InputCheckbox @bind-Value="Input.RememberMe" id="Input.RememberMe" style="width: 1.2rem; height: 1.2rem; accent-color: var(--mud-palette-primary);" />
|
||||
<label for="Input.RememberMe" class="ml-2 mud-typography mud-typography-body1">Remember me</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<InputText type="password" @bind-Value="Input.Password" id="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" placeholder="Password" />
|
||||
<label for="Input.Password" class="form-label">Password</label>
|
||||
<ValidationMessage For="() => Input.Password" class="text-danger" />
|
||||
</div>
|
||||
<div class="checkbox mb-3">
|
||||
<label class="form-label">
|
||||
<InputCheckbox @bind-Value="Input.RememberMe" class="darker-border-checkbox form-check-input" />
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<a href="Account/ForgotPassword">Forgot your password?</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="@(NavigationManager.GetUriWithQueryParameters("Account/Register", new Dictionary<string, object?> { ["ReturnUrl"] = ReturnUrl }))">Register as a new user</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="Account/ResendEmailConfirmation">Resend email confirmation</a>
|
||||
</p>
|
||||
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Size="Size.Large" FullWidth="true" Class="mb-4">
|
||||
Log in
|
||||
</MudButton>
|
||||
|
||||
<div class="d-flex flex-column gap-2 mt-4">
|
||||
<MudLink Href="Account/ForgotPassword" Typo="Typo.body2">Forgot your password?</MudLink>
|
||||
<MudLink Href="@(NavigationManager.GetUriWithQueryParameters("Account/Register", new Dictionary<string, object?> { ["ReturnUrl"] = ReturnUrl }))" Typo="Typo.body2">Register as a new user</MudLink>
|
||||
<MudLink Href="Account/ResendEmailConfirmation" Typo="Typo.body2">Resend email confirmation</MudLink>
|
||||
</div>
|
||||
</EditForm>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-lg-4 col-lg-offset-2">
|
||||
<section>
|
||||
<h3>Use another service to log in.</h3>
|
||||
<hr />
|
||||
<ExternalLoginPicker />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@code {
|
||||
private string? errorMessage;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/Account/Register"
|
||||
@layout Layout.IdentityLayout
|
||||
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@using System.Text
|
||||
|
||||
@@ -1,51 +1,38 @@
|
||||
@page "/AdminSettings/CouchLogSettings"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@using System.Reflection
|
||||
@using CouchLog.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
|
||||
@inject ApplicationDbContext CouchLogDB
|
||||
@inject ApplicationDbContext CouchLogDb
|
||||
|
||||
<h3>CouchLog Settings</h3>
|
||||
<MudText Typo="Typo.h3">CouchLog Settings</MudText>
|
||||
|
||||
@if (accountsSettings is null)
|
||||
{
|
||||
<p>Lade Einstellungen…</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<EditForm Model="accountsSettings">
|
||||
<div class="form-check form-switch">
|
||||
<InputCheckbox class="form-check-input"
|
||||
role="switch"
|
||||
id="IsRegistrationallowedInput"
|
||||
Value="accountsSettings.IsRegistrationAllowed"
|
||||
ValueChanged="OnRegistrationChanged"
|
||||
ValueExpression="() => accountsSettings.IsRegistrationAllowed" />
|
||||
<EditForm Model="_accountsSettings">
|
||||
<MudSwitch T="bool"
|
||||
Label="Is Registration allowed"
|
||||
Value="_accountsSettings.IsRegistrationAllowed"
|
||||
ValueChanged="OnRegistrationChanged"
|
||||
Color="Color.Primary" />
|
||||
</EditForm>
|
||||
|
||||
|
||||
|
||||
<label class="form-check-label" for="IsRegistrationallowedInput">
|
||||
Is Registration allowed
|
||||
</label>
|
||||
</div>
|
||||
</EditForm>
|
||||
}
|
||||
<MudText>Version: @_version</MudText>
|
||||
|
||||
@code {
|
||||
private AccountsSettings? accountsSettings;
|
||||
private AccountsSettings? _accountsSettings;
|
||||
private readonly string _version = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion?.Split('+')[0];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
accountsSettings = await CouchLogDB.AccountsSettings.FirstAsync();
|
||||
_accountsSettings = await CouchLogDb.AccountsSettings.FirstAsync();
|
||||
}
|
||||
|
||||
private async Task OnRegistrationChanged(bool value)
|
||||
{
|
||||
accountsSettings!.IsRegistrationAllowed = value;
|
||||
_accountsSettings!.IsRegistrationAllowed = value;
|
||||
|
||||
CouchLogDB.AccountsSettings.Update(accountsSettings);
|
||||
await CouchLogDB.SaveChangesAsync();
|
||||
CouchLogDb.AccountsSettings.Update(_accountsSettings);
|
||||
await CouchLogDb.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
@using CouchLog.Data
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">Create New User</MudText>
|
||||
</TitleContent>
|
||||
|
||||
<DialogContent>
|
||||
<MudInput @bind-Value="@_newUsername"></MudInput>
|
||||
<MudSelect @bind-Value="_newUserRole">
|
||||
@foreach (var role in Roles)
|
||||
{
|
||||
<MudSelectItem Value="@role.Name">@role.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<MudButton OnClick="CreateNewUser" Variant="Variant.Filled" Color="Color.Success">Create user</MudButton>
|
||||
<MudButton OnClick="Cancel" Variant="Variant.Filled" Color="Color.Error">Cancel</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public required List<IdentityRole> Roles { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public IMudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
private string _newUsername = string.Empty;
|
||||
private string _newUserRole = string.Empty;
|
||||
|
||||
private async Task CreateNewUser()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_newUsername) || string.IsNullOrWhiteSpace(_newUserRole))
|
||||
{
|
||||
Snackbar.Add("Add Username and Role" , Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationUser newUser = new ApplicationUser
|
||||
{
|
||||
UserName = _newUsername,
|
||||
EmailConfirmed = true
|
||||
};
|
||||
|
||||
var result = await UserManager.CreateAsync(newUser, "NewPassword123!");
|
||||
|
||||
if(result.Succeeded)
|
||||
{
|
||||
await UserManager.AddToRoleAsync(newUser, _newUserRole);
|
||||
MudDialog.Close(DialogResult.Ok(newUser));
|
||||
return;
|
||||
}
|
||||
|
||||
//Snackbar.Add("User could not be created", Severity.Error);
|
||||
}
|
||||
|
||||
private async Task Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,15 @@
|
||||
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@attribute [Authorize(Roles = "Admin")]
|
||||
|
||||
<h3>Index</h3>
|
||||
|
||||
<MudLink Href="AdminSettings/CouchLogSettings">CouchLog Settings</MudLink>
|
||||
<MudLink Href="AdminSettings/UserManagement">User Management</MudLink>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,106 +1,84 @@
|
||||
@page "/AdminSettings/UserManagement"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@using CouchLog.Components.AdminSettings.Pages.Dialogs
|
||||
@using CouchLog.Data
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Microsoft.AspNetCore.Components.QuickGrid
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
|
||||
@inject ApplicationDbContext CouchLogDB
|
||||
@inject ApplicationDbContext CouchLogDb
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IDialogService DialogService
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
@attribute [Authorize(Roles = "Admin")]
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<h3 class="mb-0">UserManagement</h3>
|
||||
<MudStack Row="true">
|
||||
<MudText Typo="Typo.h3">UserManagement</MudText>
|
||||
<MudSpacer/>
|
||||
<MudFab StartIcon="@Icons.Material.Filled.Add" Color="Color.Primary" OnClick="OpenCreateUserDialog"/>
|
||||
</MudStack>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#exampleModal">
|
||||
Add User
|
||||
</button>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
<div>
|
||||
<QuickGrid Items="@gridUsers.AsQueryable()">
|
||||
<PropertyColumn Title="#" Property="@(u => u.Index)" />
|
||||
<PropertyColumn Title="Username" Property="@(u => u.UserName)" />
|
||||
<PropertyColumn Title="Role" Property="@(u => u.Role)" />
|
||||
|
||||
<TemplateColumn Title="">
|
||||
@if(context.UserId != currentUserId)
|
||||
{
|
||||
<button class="btn btn-danger btn-sm" @onclick="() => DeleteUser(context)">Delete</button>
|
||||
}
|
||||
<MudDataGrid Items="_gridUsers" Filterable="false" SortMode="@SortMode.Single" Groupable="false">
|
||||
<Columns>
|
||||
<PropertyColumn Property="arg => arg.Index " Title="Index"/>
|
||||
<PropertyColumn Property="arg => arg.UserId" Title="User Id"/>
|
||||
<PropertyColumn Property="arg => arg.UserName" Title="User Name"/>
|
||||
<PropertyColumn Property="arg => arg.Role" Title="Role"/>
|
||||
<TemplateColumn>
|
||||
<CellTemplate>
|
||||
@if (context.Item.UserId != _currentUserId)
|
||||
{
|
||||
<MudFab Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" OnClick="@(() => DeleteUser(context.Item))"/>
|
||||
}
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</QuickGrid>
|
||||
</div>
|
||||
<!-- #region UserCreate Modal -->
|
||||
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true" style="color: black">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" style="color: black" id="exampleModalLabel">Create User</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- 1. Username Input mit Binding und Bootstrap Klasse -->
|
||||
<div class="mb-3">
|
||||
<label for="newUsernameInput" class="form-label">Username:</label>
|
||||
<input type="text" class="form-control" id="newUsernameInput" @bind="newUsername" />
|
||||
</div>
|
||||
|
||||
<!-- 2. Select mit Binding -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Role:</label>
|
||||
<select class="form-select" @bind="newUserRoleId">
|
||||
<!-- WICHTIG: Eine leere Option als Standard, damit man weiß, ob etwas gewählt wurde -->
|
||||
<option value="" selected disabled>Bitte Rolle wählen...</option>
|
||||
|
||||
@foreach (var role in roles)
|
||||
{
|
||||
<option value="@role.Id">@role.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" @onclick="CreateUserAsync">Create User</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
</Columns>
|
||||
</MudDataGrid>
|
||||
|
||||
@code {
|
||||
|
||||
string newUsername = "";
|
||||
string newUserRoleId = "";
|
||||
List<UserGridItem> gridUsers = new();
|
||||
List<IdentityRole> roles = new();
|
||||
string? currentUserId;
|
||||
private string? _currentUserId;
|
||||
private List<ApplicationUser> _users = new();
|
||||
private List<IdentityRole> _roles = new();
|
||||
private List<UserGridItem> _gridUsers = new();
|
||||
|
||||
public class UserGridItem
|
||||
{
|
||||
public int Index { get; set; }
|
||||
public string UserId { get; set; } = "";
|
||||
public string UserName { get; set; } = "";
|
||||
public string Role { get; set; } = "";
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
currentUserId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
_currentUserId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
var users = await CouchLogDB.Users.ToListAsync();
|
||||
roles = await CouchLogDB.Roles.ToListAsync();
|
||||
var users = await CouchLogDb.Users.ToListAsync();
|
||||
_roles = await CouchLogDb.Roles.ToListAsync();
|
||||
|
||||
await CreateGridUserObjects();
|
||||
}
|
||||
|
||||
private async Task CreateGridUserObjects()
|
||||
{
|
||||
_gridUsers.Clear();
|
||||
|
||||
var users = await CouchLogDb.Users.ToListAsync();
|
||||
int index = 1;
|
||||
|
||||
gridUsers.Clear();
|
||||
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
|
||||
var role = await UserManager.GetRolesAsync(user);
|
||||
|
||||
gridUsers.Add(new UserGridItem
|
||||
_gridUsers.Add(new UserGridItem
|
||||
{
|
||||
Index = index++,
|
||||
UserId = user.Id,
|
||||
@@ -110,17 +88,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
public class UserGridItem
|
||||
private async Task OpenCreateUserDialog()
|
||||
{
|
||||
public int Index { get; set; }
|
||||
public string UserId { get; set; } = "";
|
||||
public string UserName { get; set; } = "";
|
||||
public string Role { get; set; } = "";
|
||||
var parameters = new DialogParameters<CreateUserDialog>
|
||||
{
|
||||
{ x => x.Roles, _roles }
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<CreateUserDialog>("Create User", parameters);
|
||||
var result = await dialog.Result;
|
||||
if (!result!.Canceled && result.Data is ApplicationUser newUser)
|
||||
{
|
||||
Snackbar.Add("User created", Severity.Success);
|
||||
await CreateGridUserObjects();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
async Task DeleteUser(UserGridItem user)
|
||||
{
|
||||
if (user.UserId == currentUserId)
|
||||
if (user.UserId == _currentUserId)
|
||||
return;
|
||||
|
||||
var identityUser = await UserManager.FindByIdAsync(user.UserId);
|
||||
@@ -132,48 +119,12 @@
|
||||
|
||||
if (result.Succeeded)
|
||||
{
|
||||
gridUsers.Remove(user);
|
||||
_gridUsers.Remove(user);
|
||||
StateHasChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Optional: Fehler anzeigen
|
||||
Snackbar.Add(result.Errors.ToString()!, Severity.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateUserAsync()
|
||||
{
|
||||
// Validierung
|
||||
if (string.IsNullOrWhiteSpace(newUsername) || string.IsNullOrWhiteSpace(newUserRoleId))
|
||||
{
|
||||
// Fehlermeldung anzeigen oder abbrechen
|
||||
Console.WriteLine("Bitte Benutzername und Rolle angeben.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Hier deine Logik zum Erstellen des Users
|
||||
var roleIdToUse = newUserRoleId;
|
||||
var usernameToUse = newUsername;
|
||||
|
||||
ApplicationUser newUser = new ApplicationUser
|
||||
{
|
||||
UserName = newUsername,
|
||||
EmailConfirmed = true
|
||||
};
|
||||
|
||||
var result = await UserManager.CreateAsync(newUser, "NewPassword123!");
|
||||
|
||||
IdentityRole roleName = roles.FirstOrDefault(r => r.Id == newUserRoleId);
|
||||
|
||||
if(result.Succeeded)
|
||||
{
|
||||
await UserManager.AddToRoleAsync(newUser, roleName.Name);
|
||||
}
|
||||
|
||||
// Modal schließen oder Formular zurücksetzen
|
||||
newUsername = "";
|
||||
newUserRoleId = "";
|
||||
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,27 @@
|
||||
|
||||
@attribute [Authorize(Roles = "Admin")]
|
||||
|
||||
<h1>Manage CouchLog</h1>
|
||||
<MudText Typo="Typo.h1">Manage CouchLog</MudText>
|
||||
|
||||
<div>
|
||||
<h2></h2>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col-lg-3">
|
||||
<AdminSettingsNavMenu />
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="pa-4">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="3" lg="2">
|
||||
<MudNavMenu>
|
||||
<MudNavLink Href="/AdminSettings/CouchLogSettings"
|
||||
Icon="@Icons.Material.Filled.Settings">
|
||||
CouchLog Settings
|
||||
</MudNavLink>
|
||||
|
||||
<MudNavLink Href="/AdminSettings/UserManagement"
|
||||
Match="NavLinkMatch.All"
|
||||
Icon="@Icons.Material.Filled.People">
|
||||
User Management
|
||||
</MudNavLink>
|
||||
</MudNavMenu>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" md="9" lg="10">
|
||||
@Body
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
@@ -1,18 +0,0 @@
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@using CouchLog.Data
|
||||
|
||||
@inject SignInManager<ApplicationUser> SignInManager
|
||||
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="/AdminSettings/CouchLogSettings">CouchLog Settings</NavLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="/AdminSettings/UserManagement" Match="NavLinkMatch.All">User Management</NavLink>
|
||||
</li>
|
||||
<!--
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href=""></NavLink>
|
||||
</li>
|
||||
-->
|
||||
</ul>
|
||||
@@ -1,23 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="@Assets["CouchLog.styles.css"]" />
|
||||
<ImportMap />
|
||||
|
||||
@* Rendermode get's hand over dynamic *@
|
||||
<HeadOutlet @rendermode="RenderModeForPage" />
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" />
|
||||
<link rel="stylesheet" href="@Assets["app.css"]" />
|
||||
<link rel="stylesheet" href="@Assets["CouchLog.styles.css"]" />
|
||||
<ImportMap />
|
||||
<HeadOutlet />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes />
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<script src="lib/bootstrap/dist/js/bootstrap.js"></script>
|
||||
<script src="lib/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<script src="lib/bootstrap/dist/js/site.js"></script>
|
||||
</body>
|
||||
<!-- #MudBlazor -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
||||
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||
<!-- #endregion -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@* Rendermode get's hand over dynamic *@
|
||||
<Routes @rendermode="RenderModeForPage" />
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private HttpContext HttpContext { get; set; } = default!;
|
||||
|
||||
private Microsoft.AspNetCore.Components.IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account") ? null : Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
@inherits LayoutComponentBase
|
||||
@using MudBlazor
|
||||
|
||||
<MudThemeProvider IsDarkMode="true" />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
<MudLayout>
|
||||
<MudMainContent>
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-8">
|
||||
@Body
|
||||
</MudContainer>
|
||||
</MudMainContent>
|
||||
</MudLayout>
|
||||
@@ -1,19 +1,40 @@
|
||||
@inherits LayoutComponentBase
|
||||
@using MudBlazor
|
||||
|
||||
<div class="page">
|
||||
<div class="sidebar">
|
||||
<MudThemeProvider @bind-IsDarkMode="@_isDarkMode" />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
<MudPopoverProvider />
|
||||
|
||||
<MudLayout>
|
||||
<MudAppBar Color="Color.Primary" Fixed="true">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" />
|
||||
<MudText Typo="Typo.h6">CouchLog</MudText>
|
||||
</MudAppBar>
|
||||
|
||||
<MudDrawer @bind-Open="@_drawerOpen" ClipMode="DrawerClipMode.Always" Elevation="1">
|
||||
<NavMenu />
|
||||
</div>
|
||||
</MudDrawer>
|
||||
|
||||
<main>
|
||||
<article class="content px-4">
|
||||
<MudMainContent>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
|
||||
@Body
|
||||
</article>
|
||||
</main>
|
||||
</MudContainer>
|
||||
</MudMainContent>
|
||||
</MudLayout>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui" data-nosnippet>
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
@code {
|
||||
bool _drawerOpen = true;
|
||||
bool _isDarkMode = true;
|
||||
|
||||
void DrawerToggle()
|
||||
{
|
||||
_drawerOpen = !_drawerOpen;
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
justify-content: flex-end;
|
||||
height: 3.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
white-space: nowrap;
|
||||
margin-left: 1.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.top-row ::deep a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 640.98px) {
|
||||
.top-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.top-row.auth ::deep a:first-child {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.top-row, article {
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
color-scheme: light only;
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
@@ -7,83 +7,68 @@
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject ApplicationDbContext CouchLogDB
|
||||
|
||||
<div class="top-row ps-3 navbar navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="">CouchLog</a>
|
||||
</div>
|
||||
</div>
|
||||
<MudNavMenu>
|
||||
<MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home">
|
||||
Home
|
||||
</MudNavLink>
|
||||
|
||||
<input type="checkbox" title="Navigation menu" class="navbar-toggler" />
|
||||
<MudNavLink Href="GlobalList" Icon="@Icons.Material.Filled.List">
|
||||
Globallist
|
||||
</MudNavLink>
|
||||
|
||||
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
|
||||
<nav class="nav flex-column">
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="GlobalList">
|
||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Global List
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="PrivateList">
|
||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Private List
|
||||
</NavLink>
|
||||
</div>
|
||||
<MudNavLink Href="PrivateList" Icon="@Icons.Material.Filled.Lock">
|
||||
Privatelist
|
||||
</MudNavLink>
|
||||
|
||||
<AuthorizeView Roles="Admin">
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="AdminSettings">
|
||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Admin Settings
|
||||
</NavLink>
|
||||
</div>
|
||||
</AuthorizeView>
|
||||
<AuthorizeView Roles="Admin">
|
||||
<MudNavLink Href="AdminSettings" Icon="@Icons.Material.Filled.AdminPanelSettings">
|
||||
Admin Settings
|
||||
</MudNavLink>
|
||||
</AuthorizeView>
|
||||
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="Account/Manage">
|
||||
<span class="bi bi-person-fill-nav-menu" aria-hidden="true"></span> @context.User.Identity?.Name
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-3">
|
||||
<form action="Account/Logout" method="post">
|
||||
<AntiforgeryToken />
|
||||
<input type="hidden" name="ReturnUrl" value="@currentUrl" />
|
||||
<button type="submit" class="nav-link">
|
||||
<span class="bi bi-arrow-bar-left-nav-menu" aria-hidden="true"></span> Logout
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
@if(CouchLogDB.AccountsSettings.First().IsRegistrationAllowed)
|
||||
{
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="Account/Register">
|
||||
<span class="bi bi-person-nav-menu" aria-hidden="true"></span> Register
|
||||
</NavLink>
|
||||
</div>
|
||||
}
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="Account/Login">
|
||||
<span class="bi bi-person-badge-nav-menu" aria-hidden="true"></span> Login
|
||||
</NavLink>
|
||||
</div>
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
</nav>
|
||||
</div>
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudNavLink Href="Account/Manage" Icon="@Icons.Material.Filled.Person">
|
||||
@context.User.Identity?.Name
|
||||
</MudNavLink>
|
||||
<form action="Account/Logout" method="post">
|
||||
<AntiforgeryToken />
|
||||
<input type="hidden" name="ReturnUrl" value="@currentUrl" />
|
||||
<MudNavLink Icon="@Icons.Material.Filled.Logout"
|
||||
OnClick="@(() => { })"
|
||||
Href="javascript:void(0)"
|
||||
UserAttributes="@(new Dictionary<string, object>
|
||||
{ {"onclick", "this.closest('form').submit()"} })">
|
||||
Logout
|
||||
</MudNavLink>
|
||||
</form>
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
<MudDivider Class="my-2" />
|
||||
@if (_isRegistrationAllowed)
|
||||
{
|
||||
<MudNavLink Href="Account/Register" Icon="@Icons.Material.Filled.PersonAdd">
|
||||
Register
|
||||
</MudNavLink>
|
||||
}
|
||||
<MudNavLink Href="Account/Login" Icon="@Icons.Material.Filled.Badge">
|
||||
Login
|
||||
</MudNavLink>
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
|
||||
</MudNavMenu>
|
||||
|
||||
@code {
|
||||
private string? currentUrl;
|
||||
private bool _isRegistrationAllowed;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
currentUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
|
||||
NavigationManager.LocationChanged += OnLocationChanged;
|
||||
_isRegistrationAllowed = CouchLogDB.AccountsSettings.First().IsRegistrationAllowed;
|
||||
}
|
||||
|
||||
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
|
||||
@@ -96,5 +81,4 @@
|
||||
{
|
||||
NavigationManager.LocationChanged -= OnLocationChanged;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
.navbar-toggler {
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
width: 3.5rem;
|
||||
height: 2.5rem;
|
||||
color: white;
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.navbar-toggler:checked {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
min-height: 3.5rem;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.bi {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
margin-right: 0.75rem;
|
||||
top: -1px;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.bi-house-door-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-plus-square-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-list-nested-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-lock-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath d='M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2zM5 8h6a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-person-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person' viewBox='0 0 16 16'%3E%3Cpath d='M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0Zm4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4Zm-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664h10Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-person-badge-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person-badge' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-3zM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0z'/%3E%3Cpath d='M4.5 0A2.5 2.5 0 0 0 2 2.5V14a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2.5A2.5 2.5 0 0 0 11.5 0h-7zM3 2.5A1.5 1.5 0 0 1 4.5 1h7A1.5 1.5 0 0 1 13 2.5v10.795a4.2 4.2 0 0 0-.776-.492C11.392 12.387 10.063 12 8 12s-3.392.387-4.224.803a4.2 4.2 0 0 0-.776.492V2.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-person-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person-fill' viewBox='0 0 16 16'%3E%3Cpath d='M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3Zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-arrow-bar-left-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-arrow-bar-left' viewBox='0 0 16 16'%3E%3Cpath d='M12.5 15a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5ZM10 8a.5.5 0 0 1-.5.5H3.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L3.707 7.5H9.5a.5.5 0 0 1 .5.5Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: 0.9rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-item:first-of-type {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.nav-item:last-of-type {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep .nav-link {
|
||||
color: #d7d7d7;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active {
|
||||
background-color: rgba(255,255,255,0.37);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item ::deep .nav-link:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-toggler:checked ~ .nav-scrollable {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.navbar-toggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
/* Never collapse the sidebar for wide screens */
|
||||
display: block;
|
||||
|
||||
/* Allow sidebar to scroll for tall menus */
|
||||
height: calc(100vh - 3.5rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
@@ -33,4 +33,4 @@
|
||||
|
||||
protected override void OnInitialized() =>
|
||||
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||
}
|
||||
}
|
||||
@@ -1,335 +0,0 @@
|
||||
@page "/GlobalList"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using CouchLog.Data
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@inject ApplicationDbContext CouchLogDB
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@attribute [Authorize]
|
||||
|
||||
<PageTitle>GlobalList</PageTitle>
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<!-- #region CreateEntity -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Global List</h2>
|
||||
<button class="btn btn-info" type="button" @onclick="ToogleCollapseNewGlobalEntity">@(isCollapseNewGlobalEntityOpen ? "X" : "Add Entity")</button>
|
||||
</div>
|
||||
<div class="collapse @(isCollapseNewGlobalEntityOpen ? "show" : "")" id="CollapseCreateNewGlobalEntity">
|
||||
<div class="mb-4 align-items-center CreateNewGlobalEntity-Container">
|
||||
<EditForm Model="@GlobalEntity" OnSubmit="CreateNewGlobalEntity" FormName="CreateNewGlobalEntityForm">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="Title" class="form-label">Title</label>
|
||||
<InputText id="Title" class="form-control" @bind-Value="GlobalEntity.Title"></InputText>
|
||||
<ValidationMessage For="@(() => GlobalEntity.Title)"></ValidationMessage>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="Type" class="form-label">Type</label>
|
||||
<select id="Type" class="form-select" @bind="SelectedMediaTypeId">
|
||||
@foreach(MediaType Type in MediaTypes)
|
||||
{
|
||||
<option value="@Type.Id">@Type.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="Picture" class="form-label">Picture</label>
|
||||
<InputFile OnChange="@LoadImage" accept="image/*" />
|
||||
@if (!string.IsNullOrEmpty(ImageString))
|
||||
{
|
||||
<img src="@ImageString" style="max-width: 256px; max-height: 256px;" />
|
||||
}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="Genres" class="form-label">Genres</label>
|
||||
@foreach(Genre Genre in Genres)
|
||||
{
|
||||
<button class="btn-primary btn me-2" value="@Genre.Id" type="button" @onclick="() => AddGenreToList(Genre.Id)">@Genre.Name</button>
|
||||
}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<InputCheckbox DisplayName="isPrivate" @bind-Value="isPrivate" ></InputCheckbox>
|
||||
</div>
|
||||
<button type="submit" class="btn-success btn">Create new Global Entity</button>
|
||||
</EditForm>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
|
||||
<!-- #region Show Entitys -->
|
||||
<div class="row g-4">
|
||||
@foreach (var Entity in GlobalEntities)
|
||||
{
|
||||
if (!Entity.IsPrivate || (Entity.IsPrivate && (Entity.CreatorId == AppUser.Id)))
|
||||
{
|
||||
<div name="Enity-Container" class="col-6 col-md-4 col-lg-2 col-xl-2 mb-4 Entity-Container">
|
||||
<div name="Entity-Container-Card" class="Entity-Container-Card">
|
||||
|
||||
<div class="Entity-Container-Menu-Button dropdown ms-1">
|
||||
<button type="button" class="Entity-Container-Menu-Button btn menu-btn" data-bs-toggle="modal" data-bs-target="#modal-@Entity.Id">⋮</button>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="@Entity.Id" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="staticBackdropLabel">Modal title</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
This is a vertically centered modal.
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary">Understood</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div name="Enity-Container-Image" class="">
|
||||
<a href="javascript:void(0)" class="Entity-Container-Image">
|
||||
<img src="/@Entity.PicturePath" alt="" class="Entity-Container-Image" />
|
||||
</a>
|
||||
</div>
|
||||
<div name="Entity-Container-Data" class="">
|
||||
<h3 class="">@Entity.Title</h3>
|
||||
</div>
|
||||
<div name="Entity-Container-Button" class="d-flex Entity-Container-Button" style="gap: 10px;">
|
||||
<button class="btn btn-info" type="button" @onclick="() => AddToPrivateList(Entity)">
|
||||
@(IsInPrivateList(Entity.Id) ? "Added" : "Add to Private List")
|
||||
</button>
|
||||
<button class="btn btn-info" type="button" @onclick="() => throw new NotImplementedException()">Add to Shared List</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modal-@Entity.Id" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Optionen</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<button class="btn btn-danger" type="button" @onclick="() => DeleteEntity(Entity)">Delete Entity</button>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
private List<MediaType> MediaTypes = new List<MediaType>();
|
||||
private List<Genre> Genres = new List<Genre>();
|
||||
private List<GlobalEntity> GlobalEntities = new List<GlobalEntity>();
|
||||
private List<int> GenreIds = new List<int>();
|
||||
private HashSet<int> UserPrivateEntityIds = new();
|
||||
private IBrowserFile? Picture;
|
||||
System.Security.Claims.ClaimsPrincipal User = new();
|
||||
ApplicationUser AppUser = null!;
|
||||
private GlobalEntity GlobalEntity = new();
|
||||
private bool isCollapseNewGlobalEntityOpen = false;
|
||||
private int SelectedMediaTypeId;
|
||||
private bool isPrivate = false;
|
||||
private string ImageString = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
GlobalEntities = await CouchLogDB.GlobalEntities.OrderByDescending(Entity => Entity.CreationTime).ToListAsync();
|
||||
MediaTypes = await CouchLogDB.MediaType.OrderBy(Type => Type.Id).ToListAsync();
|
||||
Genres = await CouchLogDB.Genres.OrderBy(Genre => Genre.Name).ToListAsync();
|
||||
|
||||
var AuthState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
User = AuthState.User;
|
||||
|
||||
AppUser = (await UserManager.GetUserAsync(User))!;
|
||||
|
||||
if (AppUser == null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
var TempUserPrivateEntityIds = await CouchLogDB.PrivateEntities
|
||||
.Where(p => p.UserId == AppUser.Id)
|
||||
.Select(p => p.GlobalEntityId)
|
||||
.ToListAsync();
|
||||
|
||||
UserPrivateEntityIds = TempUserPrivateEntityIds.ToHashSet();
|
||||
}
|
||||
|
||||
private async Task LoadImage(InputFileChangeEventArgs e)
|
||||
{
|
||||
Picture = e.File;
|
||||
|
||||
foreach(var file in e.GetMultipleFiles())
|
||||
{
|
||||
using var stream = file.OpenReadStream();
|
||||
using var ms = new MemoryStream();
|
||||
await stream.CopyToAsync(ms);
|
||||
ImageString = $"data:{file.ContentType};base64,{Convert.ToBase64String(ms.ToArray())}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <returns></returns>
|
||||
private async Task LoadFiles(InputFileChangeEventArgs eventArgs)
|
||||
{
|
||||
Picture = eventArgs.File;
|
||||
|
||||
//Byte[] Buffer = new byte[Picture.Size];
|
||||
//await Picture.OpenReadStream().ReadAsync(Buffer);
|
||||
//await Picture.OpenReadStream(maxAllowedSize: 10_000_000).ReadAsync(Buffer);
|
||||
|
||||
//string Base64 = Convert.ToBase64String(Buffer);
|
||||
//ImagePreviewUrl = $"data:{Picture.ContentType};base64,{Base64}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private void ToogleCollapseNewGlobalEntity()
|
||||
{
|
||||
isCollapseNewGlobalEntityOpen = !isCollapseNewGlobalEntityOpen;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task CreateNewGlobalEntity()
|
||||
{
|
||||
if (Picture is null)
|
||||
{
|
||||
throw new InvalidOperationException("No Picture selected.");
|
||||
}
|
||||
|
||||
if (AppUser is null)
|
||||
{
|
||||
throw new InvalidOperationException("User not loaded or not logged in.");
|
||||
}
|
||||
|
||||
//Save Picture and Name it
|
||||
string NewFileName = $"{GlobalEntity.Title.Replace(" ", "-")}-{Guid.NewGuid()}{Path.GetExtension(Picture.Name)}";
|
||||
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);
|
||||
|
||||
GlobalEntity.PicturePath = $"Pictures/{NewFileName}";
|
||||
GlobalEntity.CreatorId = AppUser.Id;
|
||||
GlobalEntity.TypeId = SelectedMediaTypeId;
|
||||
GlobalEntity.CreationTime = DateTime.Now;
|
||||
GlobalEntity.IsPrivate = isPrivate;
|
||||
|
||||
CouchLogDB.Add(GlobalEntity);
|
||||
await CouchLogDB.SaveChangesAsync();
|
||||
|
||||
foreach(int GenreId in GenreIds)
|
||||
{
|
||||
LinkTableGlobalGenre LinkTableGlobalGenre = new() { GenreId = GenreId, GlobalEntityId = GlobalEntity.Id };
|
||||
CouchLogDB.Add(LinkTableGlobalGenre);
|
||||
}
|
||||
|
||||
await CouchLogDB.SaveChangesAsync();
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="GolbalEntityId"></param>
|
||||
/// <returns></returns>
|
||||
private bool IsInPrivateList(int GolbalEntityId)
|
||||
{
|
||||
return UserPrivateEntityIds.Contains(GolbalEntityId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="GlobalEntity"></param>
|
||||
/// <returns></returns>
|
||||
private async Task AddToPrivateList(GlobalEntity GlobalEntity)
|
||||
{
|
||||
if(User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
if(!IsInPrivateList(GlobalEntity.Id))
|
||||
{
|
||||
if (AppUser is null)
|
||||
{
|
||||
throw new InvalidOperationException("User not loaded or not logged in.");
|
||||
}
|
||||
|
||||
PrivateEntity PrivateEntity = new()
|
||||
{
|
||||
UserId = AppUser.Id,
|
||||
CreationTime = DateTime.Now,
|
||||
GlobalEntityId = GlobalEntity.Id,
|
||||
UserWatchStatusId = 1,
|
||||
};
|
||||
|
||||
CouchLogDB.PrivateEntities.Add(PrivateEntity);
|
||||
await CouchLogDB.SaveChangesAsync();
|
||||
|
||||
UserPrivateEntityIds.Add(GlobalEntity.Id);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddGenreToList(int GenreId)
|
||||
{
|
||||
if (!GenreIds.Contains(GenreId))
|
||||
{
|
||||
GenreIds.Add(GenreId);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteEntity(GlobalEntity entity)
|
||||
{
|
||||
if(entity.PicturePath != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(Path.Combine("wwwroot", entity.PicturePath));
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
CouchLogDB.GlobalEntities.Remove(entity);
|
||||
|
||||
await CouchLogDB.SaveChangesAsync();
|
||||
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*For Development*/
|
||||
/** {
|
||||
outline: 1px solid red !important;
|
||||
}*/
|
||||
|
||||
.Entity-Container {
|
||||
color:aliceblue
|
||||
}
|
||||
|
||||
.Entity-Container:hover {
|
||||
color: rgba(0,255,255,0.5);
|
||||
}
|
||||
|
||||
.Entity-Container-Card {
|
||||
position: relative;
|
||||
max-width: 275px;
|
||||
background: #111;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
border-radius: 15px;
|
||||
transition: all 0.5s ease;
|
||||
text-align: center;
|
||||
gap: 0.75rem;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
|
||||
.Entity-Container-Card h2 {
|
||||
color: #0ff;
|
||||
font-size: 2rem;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.Entity-Container-Card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient( 0deg, transparent, transparent 30%, rgba(0,255,255,0.3) );
|
||||
transform: rotate(-45deg);
|
||||
transition: all 0.5s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.Entity-Container-Card:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 0 20px rgba(0,255,255,0.5);
|
||||
}
|
||||
|
||||
.Entity-Container-Card:hover::before {
|
||||
opacity: 1;
|
||||
transform: rotate(-45deg) translateY(100%);
|
||||
}
|
||||
|
||||
.Entity-Container-Image {
|
||||
margin: 4px;
|
||||
margin-top: 6px;
|
||||
max-width: 256px;
|
||||
max-height: 256px;
|
||||
}
|
||||
|
||||
.Entity-Container-Button {
|
||||
display: flex;
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.CreateNewGlobalEntity-Container {
|
||||
background: #111;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.Entity-Container-Menu-Button {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 7.5px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #0dcaf0;
|
||||
font-size: 2.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
@using CouchLog.Data
|
||||
|
||||
<MudCard>
|
||||
<MudPaper Elevation="0">
|
||||
<MudCardMedia Image="@GlobalEntity.PicturePath" Height="400" Style="object-fit: contain;"/>
|
||||
</MudPaper>
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.h5">@GlobalEntity.Title</MudText>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudTooltip Text="Add to shared List">
|
||||
<MudIconButton Icon="@Icons.Material.Rounded.People" Color="Color.Primary" OnClick="@(() => OnAddToShared.InvokeAsync(GlobalEntity))" />
|
||||
</MudTooltip>
|
||||
<MudSpacer/>
|
||||
@if (UserPrivateEntityIds.Contains(GlobalEntity.Id))
|
||||
{
|
||||
<MudTooltip Text="Already added to Private List">
|
||||
<MudIconButton Icon="@Icons.Material.Rounded.Check" Color="Color.Primary"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTooltip Text="Add to private List">
|
||||
<MudIconButton Icon="@Icons.Material.Rounded.Lock" Color="Color.Primary" OnClick="@(() => OnAddToPrivate.InvokeAsync(GlobalEntity))" />
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public GlobalEntity GlobalEntity { get; set; } = null!;
|
||||
[Parameter] public HashSet<int> UserPrivateEntityIds { get; set; } = null!;
|
||||
|
||||
[Parameter] public EventCallback<GlobalEntity> OnAddToPrivate { get; set; }
|
||||
[Parameter] public EventCallback<GlobalEntity> OnAddToShared { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
@using CouchLog.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
|
||||
@inject ApplicationDbContext CouchLogDB
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<style>
|
||||
.mud-file-upload-dragarea {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">Add Global Entity</MudText>
|
||||
</TitleContent>
|
||||
|
||||
<DialogContent>
|
||||
<EditForm EditContext="_editContext" OnSubmit="HandleSubmit">
|
||||
<DataAnnotationsValidator />
|
||||
|
||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Start" Style="min-height: 400px;">
|
||||
<!--<editor-fold desc="Picture-Upload">-->
|
||||
<MudPaper Class="pa-0" Elevation="0" Width="280px" MinWidth="280px">
|
||||
@if (!string.IsNullOrWhiteSpace(_imagePreview))
|
||||
{
|
||||
<MudImage Src="@_imagePreview"
|
||||
ObjectFit="ObjectFit.Contain"
|
||||
Class="rounded"
|
||||
Style="width: 280px; height: 400px;"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudFileUpload @bind-Files="_pictureFile"
|
||||
DragAndDrop="true"
|
||||
OnFilesChanged="OnPictureUploaded"
|
||||
Accept="image/*"
|
||||
InputClass="null"
|
||||
Class="mud-file-upload-dragarea">
|
||||
<SelectedTemplate Context="SelectedTemplateContext"/>
|
||||
</MudFileUpload>
|
||||
}
|
||||
</MudPaper>
|
||||
<!--</editor-fold>-->
|
||||
|
||||
<MudPaper Class="flex-grow-1" Elevation="0">
|
||||
<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>
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
</EditForm>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel" Variant="Variant.Text" Disabled="_isSaving">Cancel</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private IMudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
private ApplicationUser? _appUser;
|
||||
private EditContext _editContext = null!;
|
||||
|
||||
private List<MediaType> _mediaTypes = new();
|
||||
private List<Genre> _genres = new();
|
||||
|
||||
private GlobalEntityFormModel _form = new();
|
||||
private string? _imagePreview;
|
||||
private int _selectedMediaTypeId;
|
||||
private bool _isPrivate;
|
||||
private bool _isSaving;
|
||||
private IReadOnlyCollection<int> _selectedGenreIds = Array.Empty<int>();
|
||||
|
||||
//Picture
|
||||
private IBrowserFile? _pictureFile;
|
||||
|
||||
private class GlobalEntityFormModel
|
||||
{
|
||||
[Required(ErrorMessage = "Title is required.")]
|
||||
[MaxLength(200, ErrorMessage = "Title may not exceed 200 characters.")]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_editContext = new EditContext(_form);
|
||||
|
||||
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
_appUser = await UserManager.GetUserAsync(authState.User);
|
||||
|
||||
if (_appUser is null)
|
||||
{
|
||||
Snackbar.Add("Not authenticated.", Severity.Error);
|
||||
MudDialog.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
_mediaTypes = await CouchLogDB.MediaType.OrderBy(t => t.Id).ToListAsync();
|
||||
_genres = await CouchLogDB.Genres.OrderBy(t => t.Id).ToListAsync();
|
||||
|
||||
if (_mediaTypes.Count > 0)
|
||||
_selectedMediaTypeId = _mediaTypes.First().Id;
|
||||
}
|
||||
|
||||
private async Task OnPictureUploaded()
|
||||
{
|
||||
if (_pictureFile == null)
|
||||
{
|
||||
Snackbar.Add("No file selected!", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
if (_pictureFile.Size > 5242880)
|
||||
{
|
||||
Snackbar.Add("File size exceeds 5MB!", Severity.Error);
|
||||
return;
|
||||
}
|
||||
await using var stream = _pictureFile.OpenReadStream(5242880);
|
||||
using var ms = new MemoryStream();
|
||||
await stream.CopyToAsync(ms);
|
||||
|
||||
var base64 = Convert.ToBase64String(ms.ToArray());
|
||||
_imagePreview = $"data:{_pictureFile.ContentType};base64,{base64}";
|
||||
}
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private async Task HandleSubmit(EditContext ctx)
|
||||
{
|
||||
if (!ctx.Validate()) return;
|
||||
|
||||
if (_pictureFile is null)
|
||||
{
|
||||
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 fileName = $"{Guid.NewGuid()}{Path.GetExtension(_pictureFile.Name)}";
|
||||
string picturePath = Path.Combine("wwwroot", "Pictures", fileName);
|
||||
|
||||
await using (var fs = File.Create(picturePath))
|
||||
{
|
||||
await _pictureFile.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));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSaving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
@page "/GlobalList"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using CouchLog.Data
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using CouchLog.Components.Pages.GlobalList.Dialogs
|
||||
@using CouchLog.Components.Pages.GlobalList.Components
|
||||
|
||||
@inject ApplicationDbContext CouchLogDb
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject IDialogService DialogService
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
@attribute [Authorize]
|
||||
|
||||
<PageTitle>GlobalList</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False">
|
||||
@*Top of the Page*@
|
||||
<MudContainer>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
@*Page Title*@
|
||||
<MudText Typo="Typo.h3" Style="font-weight: bold;">Global List</MudText>
|
||||
|
||||
@*Spacer*@
|
||||
<MudSpacer />
|
||||
|
||||
@*Button for adding GlobalEntity*@
|
||||
<MudTooltip Text="Add GlobalEntity">
|
||||
<MudFab StartIcon="@Icons.Material.Filled.Add" Color="Color.Primary" OnClick="OpenAddNewGlobalEntityDialog"/>
|
||||
</MudTooltip>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
|
||||
<br/>
|
||||
|
||||
<MudContainer>
|
||||
<MudPaper Class="pa-2" Elevation="1">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
@*Search bar*@
|
||||
<MudTextField @bind-Value="_searchString"
|
||||
Placeholder="Search..."
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
Immediate="true"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Style="max-width: 300px;"/>
|
||||
@*Refresh button*@
|
||||
@*<MudTooltip Text="Refresh Entities">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Refresh" OnClick="RefreshPage"/>
|
||||
</MudTooltip>*@
|
||||
@*More Filters button*@
|
||||
<MudTooltip Text="More filters">
|
||||
<MudIconButton Icon="@(_showFilters
|
||||
? Icons.Material.Filled.ExpandLess
|
||||
: Icons.Material.Filled.FilterList)"
|
||||
OnClick="@(() => _showFilters = !_showFilters)"
|
||||
Color="Color.Default"/>
|
||||
</MudTooltip>
|
||||
</MudStack>
|
||||
<MudCollapse Expanded="_showFilters">
|
||||
@*Entity allready added*@
|
||||
<MudSwitch @bind-Value="_showAlreadyAddedEntities" Color="Color.Primary">
|
||||
Show already added Entities
|
||||
</MudSwitch>
|
||||
@*To be exstendet*@
|
||||
</MudCollapse>
|
||||
</MudPaper>
|
||||
</MudContainer>
|
||||
|
||||
<br/>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False">
|
||||
<MudGrid Spacing="6" Justify="Justify.Center">
|
||||
@foreach (var globalEntity in VisibleGlobalEntities)
|
||||
{
|
||||
<MudItem xs="12" sm="6" md="4" lg="3" xl="2">
|
||||
<GlobalEntityCard GlobalEntity="globalEntity"
|
||||
UserPrivateEntityIds="_userPrivateEntityIds"
|
||||
OnAddToPrivate="AddGlobalEntityToPrivateList"
|
||||
OnAddToShared="AddGlobalEntityToSharedList"/>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
|
||||
<br/>
|
||||
|
||||
<MudContainer>
|
||||
<MudStack AlignItems="AlignItems.Center">
|
||||
<MudPagination Count="@TotalPages" @bind-Selected="_currentPage"/>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
</MudContainer>
|
||||
|
||||
@code
|
||||
{
|
||||
//Filter
|
||||
private string? _searchString;
|
||||
private bool _showFilters;
|
||||
private bool _showAlreadyAddedEntities = false;
|
||||
private IEnumerable<GlobalEntity> FiltertGlobalEntities => _globalEntities.Where(entity =>
|
||||
(string.IsNullOrWhiteSpace(_searchString) || entity.Title.Contains(_searchString, StringComparison.OrdinalIgnoreCase)) && //Search bar
|
||||
(_showAlreadyAddedEntities || !_userPrivateEntityIds.Contains(entity.Id)) //Filter for already added Entitys
|
||||
);
|
||||
|
||||
//Paging
|
||||
private int _currentPage = 1;
|
||||
private int _pageSize = 12;
|
||||
private int TotalPages => (int)Math.Ceiling(FiltertGlobalEntities.Count() / (double)_pageSize);
|
||||
private IEnumerable<GlobalEntity> VisibleGlobalEntities => FiltertGlobalEntities.Skip((_currentPage - 1) * _pageSize).Take(_pageSize);
|
||||
|
||||
//User
|
||||
private ApplicationUser? _appUser;
|
||||
|
||||
//Core
|
||||
private List<GlobalEntity> _globalEntities = [];
|
||||
private HashSet<int> _userPrivateEntityIds = [];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
_appUser = await UserManager.GetUserAsync(authState.User);
|
||||
|
||||
if (_appUser is null)
|
||||
{
|
||||
Snackbar.Add("Not authenticated.", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
_globalEntities = await CouchLogDb.GlobalEntities
|
||||
.OrderByDescending(entity => entity.CreationTime)
|
||||
.Where(entity => !entity.IsPrivate || (entity.IsPrivate && entity.CreatorId == _appUser.Id))
|
||||
.ToListAsync();
|
||||
|
||||
_userPrivateEntityIds = await CouchLogDb.PrivateEntities
|
||||
.Where(entity => entity.UserId == _appUser.Id)
|
||||
.Select(p => p.GlobalEntityId)
|
||||
.ToHashSetAsync();
|
||||
}
|
||||
|
||||
private async Task RefreshPage()
|
||||
{
|
||||
_currentPage = 1;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task OnNewEntity(GlobalEntity entity)
|
||||
{
|
||||
_globalEntities.Add(entity);
|
||||
await RefreshPage();
|
||||
}
|
||||
|
||||
private async Task OpenAddNewGlobalEntityDialog()
|
||||
{
|
||||
var dialog = await DialogService.ShowAsync<AddNewGlobalEntityDialog>("Add new Global Entity");
|
||||
var result = await dialog.Result;
|
||||
if (!result!.Canceled)
|
||||
{
|
||||
var entity = (GlobalEntity)result.Data!;
|
||||
await OnNewEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddGlobalEntityToPrivateList(GlobalEntity globalEntity)
|
||||
{
|
||||
if(_appUser is null) { Snackbar.Add("Not logged in!", Severity.Error); return;}
|
||||
|
||||
if (!await IsInPrivateList(globalEntity.Id))
|
||||
{
|
||||
PrivateEntity privateEntity = new()
|
||||
{
|
||||
UserId = _appUser.Id,
|
||||
CreationTime = DateTime.Now,
|
||||
GlobalEntityId = globalEntity.Id,
|
||||
UserWatchStatusId = 1,
|
||||
//Season = 0,
|
||||
//Episode = 0
|
||||
};
|
||||
|
||||
CouchLogDb.PrivateEntities.Add(privateEntity);
|
||||
await CouchLogDb.SaveChangesAsync();
|
||||
|
||||
_userPrivateEntityIds.Add(globalEntity.Id);
|
||||
StateHasChanged();
|
||||
Snackbar.Add("Added Entity to private List", Severity.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add("Entity already in private List!", Severity.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> IsInPrivateList(int globalEntityId)
|
||||
{
|
||||
if (_appUser is null) return false;
|
||||
|
||||
return await CouchLogDb.PrivateEntities.Where(entity => entity.UserId == _appUser.Id).AnyAsync(entity => entity.GlobalEntityId == globalEntityId);
|
||||
}
|
||||
|
||||
private void AddGlobalEntityToSharedList()
|
||||
{
|
||||
Snackbar.Add("Feature not implemented yet!", Severity.Error);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
@page "/"
|
||||
|
||||
@using CouchLog.Components.Layout
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
|
||||
@attribute [Authorize]
|
||||
@@ -8,4 +9,4 @@
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
Welcome to your new app.
|
||||
@@ -1,232 +0,0 @@
|
||||
@page "/PrivateList"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using CouchLog.Data
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@inject ApplicationDbContext CouchLogDB
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@attribute [Authorize]
|
||||
|
||||
<PageTitle>Private List</PageTitle>
|
||||
|
||||
<div class="container-fluid mt-4 px-4">
|
||||
|
||||
<!-- Active Watching Section -->
|
||||
@if (ActiveWatchingEntities.Any())
|
||||
{
|
||||
<div class="mb-5">
|
||||
<h2 class="mb-4">Aktiv am Schauen</h2>
|
||||
<div class="row g-4">
|
||||
@foreach (var entity in ActiveWatchingEntities)
|
||||
{
|
||||
@RenderEntityCard(entity)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Library / Completed Section -->
|
||||
@if (LibraryEntities.Any())
|
||||
{
|
||||
<div class="mb-5">
|
||||
<h2 class="mb-4">Bibliothek</h2>
|
||||
<div class="row g-4">
|
||||
@foreach (var entity in LibraryEntities)
|
||||
{
|
||||
@RenderEntityCard(entity)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!PrivateEntities.Any())
|
||||
{
|
||||
<div class="text-center text-muted py-5">
|
||||
<p>Keine Einträge vorhanden.</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
private List<PrivateEntity> PrivateEntities = new List<PrivateEntity>();
|
||||
private List<UserWatchStatus> userWatchStatuses = new List<UserWatchStatus>();
|
||||
ApplicationUser? AppUser = new();
|
||||
|
||||
private List<PrivateEntity> ActiveWatchingEntities => PrivateEntities.Where(x => x.UserWatchStatus?.Name == "Watching").ToList();
|
||||
private List<PrivateEntity> LibraryEntities => PrivateEntities.Where(x => x.UserWatchStatus?.Name != "Watching").ToList();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var AuthState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
AppUser = await UserManager.GetUserAsync(AuthState.User);
|
||||
|
||||
if (AppUser != null)
|
||||
{
|
||||
PrivateEntities = await CouchLogDB.PrivateEntities
|
||||
.Where(x => x.UserId == AppUser.Id)
|
||||
.Include(x => x.GlobalEntity)
|
||||
.Include(x => x.UserWatchStatus)
|
||||
.Include(x => x.GlobalEntity.MediaType)
|
||||
.OrderByDescending(Entity => Entity.LastChange ?? Entity.CreationTime)
|
||||
.ToListAsync();
|
||||
|
||||
userWatchStatuses = await CouchLogDB.UserWatchStatuses.OrderByDescending(Entity => Entity.Id).ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private RenderFragment RenderEntityCard(PrivateEntity Entity) => __builder =>
|
||||
{
|
||||
<div class="col-12 col-lg-6 col-xxl-4">
|
||||
<div class="card shadow-sm border-0 overflow-hidden private-entity-card">
|
||||
<div class="row g-0 h-100">
|
||||
|
||||
<div class="col-auto" style="flex: 0 0 auto; min-width: 120px; max-width: 150px;">
|
||||
@if (!string.IsNullOrEmpty(Entity.GlobalEntity?.PicturePath))
|
||||
{
|
||||
<img src="/@Entity.GlobalEntity.PicturePath"
|
||||
class="entity-img w-100 h-100"
|
||||
style="object-fit: cover;"
|
||||
alt="@Entity.GlobalEntity.Title">
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="d-flex align-items-center justify-content-center h-100 bg-light text-muted small">
|
||||
Kein Bild
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="col" style="min-width: 0;">
|
||||
<div class="card-body d-flex flex-column h-100 py-2 px-3">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<div style="min-width: 0; flex: 1;">
|
||||
<h5 class="card-title fw-bold mb-0" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
@Entity.GlobalEntity?.Title
|
||||
</h5>
|
||||
<small class="text-muted meta-text">
|
||||
@Entity.CreationTime.ToShortDateString()
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="dropdown ms-2" style="flex-shrink: 0;">
|
||||
<button class="btn btn-link menu-btn" type="button" data-bs-toggle="modal" data-bs-target="#modal-@Entity.Id">
|
||||
⋮
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Watch Status Dropdown -->
|
||||
<select class="form-select"
|
||||
value="@Entity.UserWatchStatusId"
|
||||
@onchange="@(e => UpdateWatchStatus(Entity, e.Value))">
|
||||
@foreach (var status in userWatchStatuses)
|
||||
{
|
||||
<option value="@status.Id">@status.Name</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
@if (Entity.GlobalEntity?.MediaType.Name == "Series" || Entity.GlobalEntity?.MediaType.Name == "Anime")
|
||||
{
|
||||
<select class="form-select" value="@Entity.Season" @onchange="@(e => UpdateSeason(Entity, e.Value))">
|
||||
@{
|
||||
int currentSeason = Entity.Season ?? 1;
|
||||
int startOffsetSeason = currentSeason > 1 ? -1 : 0;
|
||||
}
|
||||
@for (int i = startOffsetSeason; i <= 5; i++)
|
||||
{
|
||||
int season = currentSeason + i;
|
||||
<option value="@season">Staffel @season</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
<select class="form-select" value="@Entity.Episode" @onchange="@(e => UpdateEpisode(Entity, e.Value))">
|
||||
@{
|
||||
int currentEpisode = Entity.Episode ?? 1;
|
||||
int startOffsetEpisode = currentEpisode > 1 ? -1 : 0;
|
||||
}
|
||||
@for (int i = startOffsetEpisode; i <= 5; i++)
|
||||
{
|
||||
int episode = currentEpisode + i;
|
||||
<option value="@episode">Episode @episode</option>
|
||||
}
|
||||
</select>
|
||||
}
|
||||
|
||||
<!-- Footer Button -->
|
||||
<div class="mt-auto d-flex justify-content-end">
|
||||
<button class="btn btn-outline-secondary btn-sm btn-details" type="button">Details</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="modal-@Entity.Id" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Optionen</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<button type="button" class="btn btn-danger" @onclick="@(e => RemoveEntityFromPrivateList(Entity))" data-bs-dismiss="modal">Aus PrivateList entfernen</button>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
|
||||
private async Task RemoveEntityFromPrivateList(PrivateEntity entity)
|
||||
{
|
||||
//Delete DB
|
||||
CouchLogDB.PrivateEntities.Remove(entity);
|
||||
await CouchLogDB.SaveChangesAsync();
|
||||
|
||||
//Delete Memory List
|
||||
PrivateEntities.Remove(entity);
|
||||
}
|
||||
|
||||
private async Task UpdateWatchStatus(PrivateEntity entity, object? newValue)
|
||||
{
|
||||
if (int.TryParse(newValue?.ToString(), out int newId))
|
||||
{
|
||||
entity.UserWatchStatusId = newId;
|
||||
entity.UserWatchStatus = userWatchStatuses.FirstOrDefault(s => s.Id == newId);
|
||||
await CouchLogDB.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateSeason(PrivateEntity entity, object? newSeasonValue)
|
||||
{
|
||||
if (int.TryParse(newSeasonValue?.ToString(), out int newSeason))
|
||||
{
|
||||
entity.Season = newSeason;
|
||||
entity.Episode = 1;
|
||||
await CouchLogDB.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateEpisode(PrivateEntity entity, object? newEpisodeValue)
|
||||
{
|
||||
if (int.TryParse(newEpisodeValue?.ToString(), out int newEpisode))
|
||||
{
|
||||
entity.Episode = newEpisode;
|
||||
await CouchLogDB.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/* PrivateList.razor.css */
|
||||
|
||||
.private-entity-card {
|
||||
height: 220px;
|
||||
background-color: #fff;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.private-entity-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
/*
|
||||
NEU: Feste Breite für das Bild!
|
||||
150px Breite bei 220px Höhe passt perfekt für Poster.
|
||||
*/
|
||||
.img-wrapper {
|
||||
width: 150px;
|
||||
background-color: #f0f2f5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Bild füllt den Wrapper komplett aus */
|
||||
.entity-img {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* Positioniert das Bild oben, falls das Format doch leicht abweicht (Köpfe/Titel sind meist oben) */
|
||||
object-position: top center;
|
||||
}
|
||||
|
||||
.text-truncate-multiline {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.meta-text {
|
||||
font-size: 0.75rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
color: #212529;
|
||||
text-decoration: none;
|
||||
font-size: 1.2rem;
|
||||
line-height: 1;
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
.menu-btn:hover {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.btn-details {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.2rem 0.6rem;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
@using CouchLog.Data
|
||||
|
||||
<MudCard Elevation="4">
|
||||
<MudCardContent Class="pa-0">
|
||||
<MudGrid Spacing="0">
|
||||
<!--<editor-fold desc="Poster-Image">-->
|
||||
<MudItem xs="4">
|
||||
<MudImage
|
||||
Src="@PrivateEntity.GlobalEntity.PicturePath"
|
||||
Alt=""
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
Style="width: 100%; height: 100%; min-height: 180px; border-radius: 4px 0 0 4px;" />
|
||||
</MudItem>
|
||||
<!--</editor-fold>-->
|
||||
|
||||
<MudItem xs="8">
|
||||
<MudStack Class="pa-3" Spacing="2">
|
||||
<!--<editor-fold desc="Options">-->
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
|
||||
<MudText Typo="Typo.h6">@PrivateEntity.GlobalEntity.Title</MudText>
|
||||
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Color="Color.Default" Dense="true">
|
||||
<MudMenuItem>Edit</MudMenuItem>
|
||||
<MudMenuItem OnClick="@(() => OnRemove.InvokeAsync())">Remove</MudMenuItem>
|
||||
</MudMenu>
|
||||
</MudStack>
|
||||
<!--</editor-fold>-->
|
||||
|
||||
<!--<editor-fold desc="Dropdown-States">-->
|
||||
<MudSelect
|
||||
T="int"
|
||||
Value="PrivateEntity.UserWatchStatus!.Id"
|
||||
ValueChanged="@(newStatusId => OnWatchStatusChange.InvokeAsync(newStatusId))"
|
||||
Dense="true"
|
||||
Variant="Variant.Outlined"
|
||||
Label="">
|
||||
@foreach (var status in UserWatchStatuses)
|
||||
{
|
||||
<MudSelectItem Value="status.Id">@status.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<!--</editor-fold>-->
|
||||
|
||||
<!--<editor-fold desc="Dropdown-Season">-->
|
||||
<MudSelect
|
||||
T="int?"
|
||||
Value="PrivateEntity.Season"
|
||||
ValueChanged="@(newSeason => OnSeasonChange.InvokeAsync(newSeason))"
|
||||
Dense="true"
|
||||
Variant="Variant.Outlined"
|
||||
Label="">
|
||||
@{
|
||||
int? currentSeason = PrivateEntity.Season ?? 1;
|
||||
int? startOffsetSeason = currentSeason > 1 ? -1 : 0;
|
||||
}
|
||||
@for (int? i = startOffsetSeason; i <= 5; i++)
|
||||
{
|
||||
int? season = currentSeason + i;
|
||||
<MudSelectItem Value="@season">Staffel @season</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<!--</editor-fold>-->
|
||||
|
||||
<!--<editor-fold desc="Dropdown-Episode">-->
|
||||
<MudSelect
|
||||
T="int?"
|
||||
Value="PrivateEntity.Episode"
|
||||
ValueChanged="@(newEpisode => OnEpisodeChange.InvokeAsync(newEpisode))"
|
||||
Dense="true"
|
||||
Variant="Variant.Outlined"
|
||||
Label="">
|
||||
@{
|
||||
int? currentEpisode = PrivateEntity.Episode ?? 1;
|
||||
int? startOffsetEpisode = currentEpisode > 1 ? -1 : 0;
|
||||
}
|
||||
@for (int? i = startOffsetEpisode; i <= 5; i++)
|
||||
{
|
||||
int? episode = currentEpisode + i;
|
||||
<MudSelectItem Value="@episode">Episode @episode</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<!--</editor-fold>-->
|
||||
|
||||
<!--<editor-fold desc="Details Button">-->
|
||||
<MudStack Row="true" Justify="Justify.FlexEnd">
|
||||
<MudButton
|
||||
Variant="Variant.Outlined"
|
||||
Size="Size.Small">
|
||||
Details
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
<!--</editor-fold>-->
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public PrivateEntity PrivateEntity { get; set; } = null!;
|
||||
[Parameter] public List<UserWatchStatus> UserWatchStatuses { get; set; } = null!;
|
||||
|
||||
[Parameter] public EventCallback<int> OnWatchStatusChange { get; set; }
|
||||
[Parameter] public EventCallback<int?> OnSeasonChange { get; set; }
|
||||
[Parameter] public EventCallback<int?> OnEpisodeChange { get; set; }
|
||||
|
||||
[Parameter] public EventCallback OnRemove { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
@page "/PrivateList"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@using CouchLog.Components.Pages.PrivateList.Components
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using CouchLog.Data
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@inject ApplicationDbContext CouchLogDb
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@attribute [Authorize]
|
||||
|
||||
<PageTitle>Private List</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False">
|
||||
@*Top of the Page*@
|
||||
<MudContainer>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
@*Page Title*@
|
||||
<MudText Typo="Typo.h3" Style="font-weight: bold;">Private List</MudText>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
|
||||
<br/>
|
||||
|
||||
<MudContainer>
|
||||
<MudPaper Class="pa-2" Elevation="1">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
@*Search bar*@
|
||||
<MudTextField @bind-Value="_searchString"
|
||||
Placeholder="Search..."
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
Immediate="true"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense"
|
||||
Style="max-width: 300px;"/>
|
||||
@*Refresh button*@
|
||||
@*<MudTooltip Text="Refresh Entities">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Refresh" OnClick="RefreshPage"/>
|
||||
</MudTooltip>*@
|
||||
@*More Filters button*@
|
||||
<MudTooltip Text="More filters">
|
||||
<MudIconButton Icon="@(_showFilters
|
||||
? Icons.Material.Filled.ExpandLess
|
||||
: Icons.Material.Filled.FilterList)"
|
||||
OnClick="@(() => _showFilters = !_showFilters)"
|
||||
Color="Color.Default"/>
|
||||
</MudTooltip>
|
||||
</MudStack>
|
||||
<MudCollapse Expanded="_showFilters">
|
||||
No Filters avalible yet!
|
||||
<!--@*Entity allready added*@
|
||||
<MudSwitch @bind-Value="_showAlreadyAddedEntities" Color="Color.Primary">
|
||||
Show already added Entities
|
||||
</MudSwitch>-->
|
||||
@*To be exstendet*@
|
||||
</MudCollapse>
|
||||
</MudPaper>
|
||||
</MudContainer>
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
<!--<editor-fold desc="Active Watching">-->
|
||||
<MudContainer MaxWidth="MaxWidth.False">
|
||||
<MudText Typo="Typo.h4">Active Watching</MudText>
|
||||
<br/>
|
||||
<MudGrid Spacing="6" Justify="Justify.Center">
|
||||
@foreach (var privateEntity in VisibleActiveWatchingEntities)
|
||||
{
|
||||
<MudItem xs="12" sm="8" md="6" lg="4">
|
||||
<PrivateEntityCard
|
||||
PrivateEntity="privateEntity"
|
||||
UserWatchStatuses="_userWatchStatuses"
|
||||
OnWatchStatusChange="@(newStatusId => ChangeWatchStatus(privateEntity, newStatusId))"
|
||||
OnSeasonChange="@(newSeason => ChangeSeason(privateEntity, newSeason))"
|
||||
OnEpisodeChange="@(newEpisode => ChangeEpisode(privateEntity, newEpisode))"
|
||||
OnRemove="() => RemovePrivateEntity(privateEntity)"
|
||||
/>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
<br/>
|
||||
|
||||
<MudContainer>
|
||||
<MudStack AlignItems="AlignItems.Center">
|
||||
<MudPagination Count="@TotalPagesActiveWatching" @bind-Selected="_currentActiveWatchingPage"/>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
<!--</editor-fold>-->
|
||||
|
||||
<br/>
|
||||
|
||||
<!--<editor-fold desc="Library">-->
|
||||
<MudContainer MaxWidth="MaxWidth.False">
|
||||
<MudText Typo="Typo.h4">Library</MudText>
|
||||
<MudGrid Spacing="6" Justify="Justify.Center">
|
||||
@foreach (var privateEntity in VisibleLibraryEntities)
|
||||
{
|
||||
<MudItem xs="12" sm="8" md="6" lg="4">
|
||||
<PrivateEntityCard
|
||||
PrivateEntity="privateEntity"
|
||||
UserWatchStatuses="_userWatchStatuses"
|
||||
OnWatchStatusChange="@(newStatusId => ChangeWatchStatus(privateEntity, newStatusId))"
|
||||
OnSeasonChange="@(newSeason => ChangeSeason(privateEntity, newSeason))"
|
||||
OnEpisodeChange="@(newEpisode => ChangeEpisode(privateEntity, newEpisode))"
|
||||
OnRemove="() => RemovePrivateEntity(privateEntity)"
|
||||
/>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
|
||||
<br/>
|
||||
|
||||
<MudContainer>
|
||||
<MudStack AlignItems="AlignItems.Center">
|
||||
<MudPagination Count="@TotalPagesLibrary" @bind-Selected="_currentLibraryPage"/>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
<!--</editor-fold>-->
|
||||
</MudContainer>
|
||||
|
||||
@code
|
||||
{
|
||||
//Filter
|
||||
private string? _searchString;
|
||||
private bool _showFilters;
|
||||
private bool _showAlreadyAddedEntities = false;
|
||||
private IEnumerable<PrivateEntity> FiltertGlobalEntities => _privateEntities.Where(entity =>
|
||||
(string.IsNullOrWhiteSpace(_searchString) || entity.GlobalEntity.Title.Contains(_searchString, StringComparison.OrdinalIgnoreCase)) //Search bar
|
||||
);
|
||||
|
||||
//Paging Active Watching
|
||||
private int _currentActiveWatchingPage = 1;
|
||||
private readonly int _pageSizeActiveWatching = 12;
|
||||
private int TotalPagesActiveWatching => (int)Math.Ceiling(ActiveWatchingEntities.Count() / (double)_pageSizeActiveWatching);
|
||||
private IEnumerable<PrivateEntity> VisibleActiveWatchingEntities => ActiveWatchingEntities.Skip((_currentActiveWatchingPage - 1) * _pageSizeActiveWatching).Take(_pageSizeActiveWatching);
|
||||
|
||||
//Paging Library
|
||||
private int _currentLibraryPage = 1;
|
||||
private readonly int _pageSizeLibrary = 12;
|
||||
private int TotalPagesLibrary => (int)Math.Ceiling(LibraryEntities.Count() / (double)_pageSizeLibrary);
|
||||
private IEnumerable<PrivateEntity> VisibleLibraryEntities => LibraryEntities.Skip((_currentLibraryPage - 1) * _pageSizeLibrary).Take(_pageSizeLibrary);
|
||||
|
||||
//User
|
||||
private ApplicationUser? _appUser;
|
||||
|
||||
//Entities
|
||||
private List<PrivateEntity> _privateEntities = [];
|
||||
private List<UserWatchStatus> _userWatchStatuses = [];
|
||||
|
||||
private IEnumerable<PrivateEntity> ActiveWatchingEntities => FiltertGlobalEntities.Where(x => x.UserWatchStatus?.Name == "Watching").ToList();
|
||||
private IEnumerable<PrivateEntity> LibraryEntities => FiltertGlobalEntities.Where(x => x.UserWatchStatus?.Name != "Watching").ToList();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await GetPrivateEntities();
|
||||
|
||||
_userWatchStatuses = await CouchLogDb.UserWatchStatuses.OrderBy(entity => entity.Id).ToListAsync();
|
||||
}
|
||||
|
||||
private async Task GetPrivateEntities()
|
||||
{
|
||||
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
_appUser = await UserManager.GetUserAsync(authState.User);
|
||||
|
||||
if (_appUser != null)
|
||||
{
|
||||
_privateEntities = await CouchLogDb.PrivateEntities
|
||||
.Where(x => x.UserId == _appUser.Id)
|
||||
.Include(x => x.GlobalEntity)
|
||||
.Include(x => x.UserWatchStatus)
|
||||
.Include(x => x.GlobalEntity.MediaType)
|
||||
.OrderByDescending(entity => entity.LastChange ?? entity.CreationTime)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshPages()
|
||||
{
|
||||
_currentLibraryPage = 1;
|
||||
_currentActiveWatchingPage = 1;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task ChangeWatchStatus(PrivateEntity privateEntity, int newUserWatchStatusId)
|
||||
{
|
||||
privateEntity.UserWatchStatus = _userWatchStatuses.FirstOrDefault(s => s.Id == newUserWatchStatusId);
|
||||
await CouchLogDb.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task ChangeSeason(PrivateEntity privateEntity, int? newSeason)
|
||||
{
|
||||
privateEntity.Season = newSeason;
|
||||
await CouchLogDb.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task ChangeEpisode(PrivateEntity privateEntity, int? newEpisode)
|
||||
{
|
||||
privateEntity.Episode = newEpisode;
|
||||
await CouchLogDb.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task RemovePrivateEntity(PrivateEntity privateEntity)
|
||||
{
|
||||
CouchLogDb.PrivateEntities.Remove(privateEntity);
|
||||
await CouchLogDb.SaveChangesAsync();
|
||||
await GetPrivateEntities();
|
||||
}
|
||||
}
|
||||
@@ -8,4 +8,4 @@
|
||||
</AuthorizeRouteView>
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
</Router>
|
||||
</Router>
|
||||
@@ -9,3 +9,4 @@
|
||||
@using Microsoft.JSInterop
|
||||
@using CouchLog
|
||||
@using CouchLog.Components
|
||||
@using MudBlazor
|
||||
@@ -7,6 +7,8 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<PackAsTool>True</PackAsTool>
|
||||
<Version>0.1.0-nightly.10</Version>
|
||||
<Authors>Penry</Authors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -77,6 +79,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MudBlazor" Version="9.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace CouchLog
|
||||
new() { Name = "Not watched", CreationTime = DateTime.Now },
|
||||
new() { Name = "Watching", CreationTime = DateTime.Now },
|
||||
new() { Name = "Finished", CreationTime = DateTime.Now },
|
||||
new() { Name = "Paused", CreationTime = DateTime.Now },
|
||||
new() { Name = "Waiting for next Season", CreationTime = DateTime.Now },
|
||||
new() { Name = "Aborted", CreationTime= DateTime.Now },
|
||||
];
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ using CouchLog.Components;
|
||||
using CouchLog.Components.Account;
|
||||
using CouchLog.Data;
|
||||
using CouchLog;
|
||||
using MudBlazor;
|
||||
using MudBlazor.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -35,6 +37,10 @@ builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.Requ
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
|
||||
builder.Services.AddMudServices(config =>
|
||||
{
|
||||
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomCenter;
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
@@ -6,6 +6,13 @@
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
},
|
||||
"Console": {
|
||||
"FormatterName": "simple",
|
||||
"FormatterOptions": {
|
||||
"TimestampFormat": "[HH:mm:ss]",
|
||||
"SingleLine": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
html, body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
background: #2d2d2d;
|
||||
color: white
|
||||
}
|
||||
|
||||
a, .btn-link {
|
||||
color: #006bb7;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 1.1rem;
|
||||
}
|
||||
|
||||
h1:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.valid.modified:not([type=checkbox]) {
|
||||
outline: 1px solid #26b050;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
outline: 1px solid #e50000;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
color: #e50000;
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::after {
|
||||
content: "An error has occurred."
|
||||
}
|
||||
|
||||
.darker-border-checkbox.form-check-input {
|
||||
border-color: #929292;
|
||||
}
|
||||
|
||||
.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
|
||||
text-align: start;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
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 it is too large
Load Diff
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
@@ -1,597 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* rtl:raw:
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
*/
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=bootstrap-reboot.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
@@ -1,594 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: right;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.rtl.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 it is too large
Load Diff
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 it is too large
Load Diff
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
-12057
File diff suppressed because it is too large
Load Diff
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
-12030
File diff suppressed because it is too large
Load Diff
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 it is too large
Load Diff
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
-4447
File diff suppressed because it is too large
Load Diff
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
-4494
File diff suppressed because it is too large
Load Diff
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
@@ -1,8 +0,0 @@
|
||||
window.initializeBootstrapCollapse = () => {
|
||||
var collapseElementList = [].slice.call(document.querySelectorAll('[data-bs-toggle="collapse"]'))
|
||||
var collapseList = collapseElementList.map(function (collapseEl) {
|
||||
return new bootstrap.Collapse(document.querySelector(collapseEl.dataset.bsTarget), {
|
||||
toggle: false
|
||||
})
|
||||
})
|
||||
};
|
||||
@@ -1,4 +1,8 @@
|
||||
# Watchlog
|
||||
## Work in Progress
|
||||
### Information
|
||||
- This Repo has a Mirror on Codeberg, but the Releases a only available on [Gitea](https://gitea.penry.de/Penry/CouchLog) for now
|
||||
## Information
|
||||
- This Repo has a Mirror on Codeberg, but the Releases a only available on [Gitea](https://gitea.penry.de/Penry/CouchLog) for now
|
||||
|
||||
## About docker image Releases
|
||||
There are two options:
|
||||
- latest: Is the latest release Version
|
||||
- nightly: Is the latest commit on the main branch
|
||||
Reference in New Issue
Block a user