Initial commit - Common.RCL
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
@inject IJSRuntime JS
|
||||
|
||||
@* Hidden div to ensure Tailwind JIT generates classes used in carousal.js *@
|
||||
<div
|
||||
class="hidden scale-100 scale-125 duration-[4000ms] transition-all transition ease-in-out text-accent lg:text-5xl md:text-4xl">
|
||||
</div>
|
||||
|
||||
<div class="relative overflow-hidden w-full h-full bg-black">
|
||||
<img id="mainImage" src="" class="absolute inset-0 w-full h-full object-cover" />
|
||||
|
||||
<div class="absolute inset-0 flex items-center justify-center bg-black/20">
|
||||
<div class="ml14 text-center">
|
||||
<span class="text-wrapper relative inline-block p-4">
|
||||
<span class="line absolute bottom-0 left-0 w-full h-1 bg-accent origin-left"></span>
|
||||
<span id="mainText"
|
||||
class="letters block overflow-hidden lg:text-5xl md:text-4xl text-3xl font-bold text-white"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute inset-x-0 top-1/2 -translate-y-1/2 flex justify-between px-5 pointer-events-none">
|
||||
<button id="prev" class="btn btn-circle btn-ghost bg-black/20 text-white pointer-events-auto">❮</button>
|
||||
<button id="next" class="btn btn-circle btn-ghost bg-black/20 text-white pointer-events-auto">❯</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.ml14 .text-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ml14 .line {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
.ml14 .letter {
|
||||
display: inline-block;
|
||||
line-height: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public List<(string Text, string ImageUrl)>? Data { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<string>? Images { get; set; }
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
var dataToPass = new List<string[]>();
|
||||
|
||||
if (Data != null && Data.Any())
|
||||
{
|
||||
foreach (var item in Data)
|
||||
{
|
||||
dataToPass.Add(new[] { item.Text, item.ImageUrl });
|
||||
}
|
||||
}
|
||||
else if (Images != null && Images.Any())
|
||||
{
|
||||
foreach (var img in Images)
|
||||
{
|
||||
dataToPass.Add(new[] { "", img });
|
||||
}
|
||||
}
|
||||
|
||||
if (dataToPass.Any())
|
||||
{
|
||||
await JS.InvokeVoidAsync("animate", dataToPass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
@* @if(ShowDialog)
|
||||
{
|
||||
<div class="fixed inset-0 flex w-screen h-dvh bg-black bg-opacity-50 z-50">
|
||||
<div class="flex flex-col justify-between gap-10 bg-white w-1/2 h-1/2 opacity-100 mx-auto my-auto p-10">
|
||||
<h1>@TitleName</h1>
|
||||
<div class="overflow-y-auto">
|
||||
@Body
|
||||
</div>
|
||||
@if(WithButtons)
|
||||
{
|
||||
<div class="flex flex-row justify-end gap-5">
|
||||
<button @onclick="@(() => MakeAction(false))" class="btn btn-primary">Cancel</button>
|
||||
<button @onclick="@(() => MakeAction(true))" class="btn btn-primary">Ok</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@code{
|
||||
|
||||
[Parameter]
|
||||
public bool ShowDialog { get; set; } = false;
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment TitleName { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment Body { get; set; }
|
||||
|
||||
|
||||
[Parameter]
|
||||
public bool WithButtons { get; set; } = false;
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<bool> ActionMade { get; set; }
|
||||
|
||||
|
||||
[Parameter]
|
||||
public List<Product> Products { get; set; }
|
||||
|
||||
|
||||
async Task MakeAction(bool action)
|
||||
{
|
||||
await ActionMade.InvokeAsync(action);
|
||||
}
|
||||
|
||||
} *@
|
||||
@@ -0,0 +1,48 @@
|
||||
@inject IJSRuntime js
|
||||
|
||||
|
||||
<div class="flex p-64 h-screen w-screen">
|
||||
<div id="dropzone" name="file" class="w-full h-96 bg-gray-300 rounded-2xl " type="file"></div>
|
||||
<img id="cropped-image" class="w-full h-screen py-60" src="" alt="">
|
||||
|
||||
<div id="cropped" class="hidden">
|
||||
<img id="cropped-image" class="w-full h-96" src="" alt="">
|
||||
<div class="flex">
|
||||
<button type="button" @onclick="RemoveCropped" class="btn btn-primary">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public string UploadLink { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await js.InvokeVoidAsync("startCropper");
|
||||
}
|
||||
}
|
||||
|
||||
async Task RemoveCropped()
|
||||
{
|
||||
await js.InvokeVoidAsync("removeCropped");
|
||||
}
|
||||
|
||||
public async Task<string> UploadPhoto()
|
||||
{
|
||||
var photoLink = "";
|
||||
|
||||
return photoLink;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,142 @@
|
||||
@inject HttpClient httpClient
|
||||
|
||||
|
||||
|
||||
<h3>Multiple File Selection</h3>
|
||||
|
||||
<InputFile multiple OnChange="@HandleFilesSelected" accept="@AcceptedFileTypes" />
|
||||
|
||||
@if (ReadyToUpload.Any() || AlreadyUploaded.Any())
|
||||
{
|
||||
<div style="margin-top: 20px;">
|
||||
<h4>Selected Files:</h4>
|
||||
<ul>
|
||||
@foreach (var file in AlreadyUploaded)
|
||||
{
|
||||
<div class="flex flex-row">
|
||||
<li>@file</li>
|
||||
<button type="button" class="btn"
|
||||
@onclick="() => {ReadyToDeleteFromDB.Add(file); AlreadyUploaded.Remove(file);}">X</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@foreach (var file in ReadyToUpload)
|
||||
{
|
||||
<div class="flex flex-row">
|
||||
<li>@file.Name</li>
|
||||
<button type="button" class="btn" @onclick="() => ReadyToUpload.Remove(file)">X</button>
|
||||
</div>
|
||||
}
|
||||
</ul>
|
||||
@* <button class="btn btn-primary" @onclick="UploadFiles">Upload Selected Files</button> *@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
<p>@message</p>
|
||||
}
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public string UploadLink { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string DeleteLink { get; set; }
|
||||
|
||||
|
||||
[Parameter]
|
||||
public string AcceptedFileTypes { get; set; } = "*";
|
||||
|
||||
|
||||
|
||||
[Parameter]
|
||||
public List<string> AlreadyUploaded { get; set; }
|
||||
|
||||
List<string> ReadyToDeleteFromDB { get; set; }
|
||||
|
||||
private List<IBrowserFile> ReadyToUpload = new();
|
||||
|
||||
|
||||
private const long MaxFileSize = 1024 * 1024 * 10; // 10MB
|
||||
|
||||
private string message = string.Empty;
|
||||
|
||||
public async Task<List<string>> UpdateFiles()
|
||||
{
|
||||
var updatedImages = new List<string>();
|
||||
await DeleteFromServer();
|
||||
var uploadedImages = await UploadToServer();
|
||||
|
||||
|
||||
updatedImages.AddRange(uploadedImages);
|
||||
updatedImages.AddRange(AlreadyUploaded);
|
||||
return updatedImages;
|
||||
}
|
||||
|
||||
async Task<List<string>> UploadToServer()
|
||||
{
|
||||
var uploadedImages = new List<string>();
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var file in ReadyToUpload)
|
||||
{
|
||||
var content = new MultipartFormDataContent();
|
||||
var fileContent = new StreamContent(file.OpenReadStream(MaxFileSize));
|
||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(file.ContentType);
|
||||
content.Add(fileContent, "file", file.Name);
|
||||
|
||||
var response = await httpClient.PostAsync($"{UploadLink}", content);
|
||||
uploadedImages.Add(await response.Content.ReadAsStringAsync());
|
||||
}
|
||||
|
||||
message = $"Successfully processed {ReadyToUpload.Count} file(s)";
|
||||
return uploadedImages;
|
||||
// Optional: Clear selection after upload
|
||||
// selectedFiles.Clear();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
message = $"Error uploading files: {ex.Message}";
|
||||
return uploadedImages;
|
||||
}
|
||||
}
|
||||
|
||||
async Task DeleteFromServer()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var name in ReadyToDeleteFromDB)
|
||||
{
|
||||
await httpClient.DeleteAsync($"{name.Split("/")[-1]}");
|
||||
}
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleFilesSelected(InputFileChangeEventArgs e)
|
||||
{
|
||||
ReadyToUpload.Clear();
|
||||
|
||||
// Get all selected files
|
||||
foreach (var file in e.GetMultipleFiles())
|
||||
{
|
||||
// Optional: Add file size limit (e.g., 5MB)
|
||||
if (file.Size <= 5 * 1024 * 1024)
|
||||
{
|
||||
ReadyToUpload.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
message = $"Selected {ReadyToUpload.Count} file(s)";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
@* @using System.Reflection
|
||||
@typeparam TModel
|
||||
|
||||
|
||||
|
||||
|
||||
@foreach (var model in models)
|
||||
{
|
||||
@foreach (var property in Properties.Where(x => ))
|
||||
{
|
||||
property.GetValue(model);
|
||||
}
|
||||
|
||||
|
||||
@foreach (var member in model.GetProperties())
|
||||
{
|
||||
member.GetValue(memb);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
|
||||
[Parameter]
|
||||
public List<string> Exclude { get; set; }
|
||||
|
||||
[Parameter] public Func<Task<List<TModel>>> GetDataFunc { get; set; }
|
||||
|
||||
IEnumerable<TModel> models;
|
||||
|
||||
PropertyInfo[] Properties = typeof(TModel).GetProperties();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
models = await GetDataFunc();
|
||||
}
|
||||
|
||||
} *@
|
||||
@@ -0,0 +1,3 @@
|
||||
@* <div class="">
|
||||
|
||||
</div> *@
|
||||
@@ -0,0 +1,39 @@
|
||||
@* Components/CultureSwitcher.razor *@
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
<div class="culture-switcher">
|
||||
<select @bind="SelectedCulture" class="form-select">
|
||||
@foreach (var culture in SupportedCultures)
|
||||
{
|
||||
<option value="@culture">@culture</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string[] SupportedCultures { get; set; } = new[]
|
||||
{
|
||||
"en",
|
||||
"ar",
|
||||
"es",
|
||||
"de"
|
||||
};
|
||||
|
||||
private string SelectedCulture
|
||||
{
|
||||
get => CultureInfo.CurrentUICulture.Name;
|
||||
set
|
||||
{
|
||||
if (CultureInfo.CurrentUICulture.Name != value)
|
||||
{
|
||||
// Persist selection
|
||||
JSRuntime.InvokeVoidAsync("cultureInfo.set", value);
|
||||
|
||||
// Reload to apply new culture
|
||||
Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<div class="flex justify-center items-center my-6">
|
||||
<div class="join shadow-sm border border-base-300">
|
||||
<button disabled="@(PaginationModel.CurrentPage == 1)"
|
||||
class="join-item btn btn-sm md:btn-md @(PaginationModel.CurrentPage == 1 ? "btn-disabled opacity-50" : "btn-ghost text-primary")"
|
||||
@onclick="() => ChangePage((PaginationModel.CurrentPage ?? 1) - 1)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
@if (PaginationModel.TotalPages.HasValue)
|
||||
{
|
||||
var currentPage = PaginationModel.CurrentPage ?? 1;
|
||||
var totalPages = PaginationModel.TotalPages ?? 0;
|
||||
|
||||
@for (int i = Math.Max(1, currentPage - 2); i <= Math.Min(totalPages, currentPage + 2); i++)
|
||||
{
|
||||
var x = i;
|
||||
<button @onclick="() => ChangePage(x)"
|
||||
class="join-item btn btn-sm md:btn-md @(i == currentPage ? "btn-primary text-white" : "btn-ghost text-primary")">
|
||||
@i
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
||||
<button
|
||||
disabled="@(PaginationModel.CurrentPage == (PaginationModel.TotalPages ?? 0) || (PaginationModel.TotalPages ?? 0) == 0)"
|
||||
class="join-item btn btn-sm md:btn-md @(PaginationModel.CurrentPage == (PaginationModel.TotalPages ?? 0) || (PaginationModel.TotalPages ?? 0) == 0 ? "btn-disabled opacity-50" : "btn-ghost text-primary")"
|
||||
@onclick="() => ChangePage((PaginationModel.CurrentPage ?? 1) + 1)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public Pagination PaginationModel { get; set; } = new();
|
||||
[Parameter] public EventCallback<int> OnSearch { get; set; }
|
||||
|
||||
async Task ChangePage(int pageNumber)
|
||||
{
|
||||
if (pageNumber < 1 || pageNumber > (PaginationModel.TotalPages ?? 0)) return;
|
||||
await OnSearch.InvokeAsync(pageNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
@inject HttpClient Http
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
@if (!string.IsNullOrEmpty(Image))
|
||||
{
|
||||
<div>
|
||||
<img src="@Image" />
|
||||
</div>
|
||||
<button @onclick="RemovePhoto" class="btn btn-primary">Remove</button>
|
||||
|
||||
}
|
||||
|
||||
<InputFile OnChange="FileChanged" Accept="@Accept"></InputFile>
|
||||
</div>
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public string? Image { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? UploadLink { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Accept { get; set; }
|
||||
|
||||
|
||||
|
||||
IBrowserFile selectedFile;
|
||||
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void RemovePhoto()
|
||||
{
|
||||
Image = "";
|
||||
selectedFile = null;
|
||||
}
|
||||
|
||||
|
||||
async Task FileChanged(InputFileChangeEventArgs e)
|
||||
{
|
||||
selectedFile = e.File;
|
||||
await GenerateBase64Preview(selectedFile);
|
||||
}
|
||||
|
||||
|
||||
public async Task<string> UploadFile()
|
||||
{
|
||||
var multipartContent = new MultipartFormDataContent();
|
||||
if (selectedFile != null)
|
||||
{
|
||||
var fileContent = new StreamContent(selectedFile.OpenReadStream());
|
||||
|
||||
fileContent.Headers.ContentType = new MediaTypeHeaderValue(selectedFile.ContentType);
|
||||
|
||||
multipartContent.Add(fileContent, "file", selectedFile.Name);
|
||||
|
||||
var response = await Http.PostAsync(UploadLink, multipartContent);
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<string>();
|
||||
@* new ProgressMessageHandler(progress =>
|
||||
{
|
||||
_uploadProgress = progress;
|
||||
StateHasChanged();
|
||||
}) *@
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
private async Task GenerateBase64Preview(IBrowserFile file)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Resize image for preview (optional)
|
||||
var resizedImage = await file.RequestImageFileAsync(
|
||||
file.ContentType,
|
||||
maxWidth: 800,
|
||||
maxHeight: 800);
|
||||
|
||||
// Convert to base64
|
||||
using var stream = resizedImage.OpenReadStream(maxAllowedSize: file.Size);
|
||||
using var memoryStream = new MemoryStream();
|
||||
await stream.CopyToAsync(memoryStream);
|
||||
Image = $"data:{file.ContentType};base64,{Convert.ToBase64String(memoryStream.ToArray())}";
|
||||
@* ImageContentType = file.ContentType; *@
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error generating preview: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user