Initial commit - Common.RCL

This commit is contained in:
2026-08-05 21:16:11 +03:00
commit c6b8aea543
150 changed files with 5817 additions and 0 deletions
+82
View File
@@ -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);
}
}
}
}
View File
+54
View File
@@ -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);
}
} *@
+48
View File
@@ -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;
}
}
+12
View File
@@ -0,0 +1,12 @@
<div>
<div>
</div>
<div>
</div>
</div>
+142
View File
@@ -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)";
}
}
+40
View File
@@ -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();
}
} *@
+3
View File
@@ -0,0 +1,3 @@
@* <div class="">
</div> *@
+39
View File
@@ -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);
}
}
}
}
+48
View File
@@ -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);
}
}
+104
View File
@@ -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}");
}
}
}