Prerel 1.2.0

This commit is contained in:
Chris Collins
2024-12-11 21:37:24 +00:00
parent 69a802af55
commit c02eb1d02c
23 changed files with 1079 additions and 282 deletions

View File

@@ -2,17 +2,18 @@
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using WeddingShare.Enums;
using WeddingShare.Helpers;
using WeddingShare.Helpers.Database;
using WeddingShare.Models.Database;
namespace WeddingShare.Helpers
namespace WeddingShare.BackgroundWorkers
{
public sealed class DirectoryScanner(IWebHostEnvironment hostingEnvironment, IConfigHelper configHelper, IDatabaseHelper databaseHelper, IImageHelper imageHelper, ILogger<DirectoryScanner> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var cron = configHelper.GetOrDefault("BackgroundServices", "Directory_Scanner_Interval", "*/30 * * * *");
var schedule = CrontabSchedule.Parse(cron);
var schedule = CrontabSchedule.Parse(cron, new CrontabSchedule.ParseOptions() { IncludingSeconds = cron.Split(new[] { ' ' }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Length == 6 });
await Task.Delay((int)TimeSpan.FromSeconds(10).TotalMilliseconds, stoppingToken);
await ScanForFiles();
@@ -27,7 +28,7 @@ namespace WeddingShare.Helpers
await ScanForFiles();
}
}
private async Task ScanForFiles()
{
await Task.Run(async () =>
@@ -66,7 +67,7 @@ namespace WeddingShare.Helpers
var galleryItems = await databaseHelper.GetAllGalleryItems(galleryItem.Id);
if (Path.Exists(gallery))
{
{
var approvedFiles = Directory.GetFiles(gallery, "*.*", SearchOption.TopDirectoryOnly).Where(x => allowedFileTypes.Any(y => string.Equals(Path.GetExtension(x).Trim('.'), y.Trim('.'), StringComparison.OrdinalIgnoreCase)));
if (approvedFiles != null)
{
@@ -113,7 +114,7 @@ namespace WeddingShare.Helpers
}
if (Path.Exists(Path.Combine(gallery, "Pending")))
{
{
var pendingFiles = Directory.GetFiles(Path.Combine(gallery, "Pending"), "*.*", SearchOption.TopDirectoryOnly).Where(x => allowedFileTypes.Any(y => string.Equals(Path.GetExtension(x).Trim('.'), y.Trim('.'), StringComparison.OrdinalIgnoreCase)));
if (pendingFiles != null)
{

View File

@@ -1,3 +1,4 @@
using System.Composition;
using System.IO.Compression;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
@@ -257,6 +258,81 @@ namespace WeddingShare.Controllers
return Json(new { success = false });
}
[HttpDelete]
public async Task<IActionResult> WipeGallery(int id)
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
try
{
var gallery = await _database.GetGallery(id);
if (gallery != null)
{
var galleryDir = Path.Combine(UploadsDirectory, gallery.Name);
if (Directory.Exists(galleryDir))
{
foreach (var photo in Directory.GetFiles(galleryDir, "*.*", SearchOption.AllDirectories))
{
var thumbnail = Path.Combine(ThumbnailsDirectory, $"{Path.GetFileNameWithoutExtension(photo)}.webp");
if (System.IO.File.Exists(thumbnail))
{
System.IO.File.Delete(thumbnail);
}
}
Directory.Delete(galleryDir, true);
Directory.CreateDirectory(galleryDir);
}
return Json(new { success = await _database.WipeGallery(gallery) });
}
else
{
return Json(new { success = false, message = _localizer["Failed_Wipe_Gallery"].Value });
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Failed_Wipe_Gallery"].Value} - {ex?.Message}");
}
}
return Json(new { success = false });
}
[HttpDelete]
public async Task<IActionResult> WipeAllGalleries()
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
try
{
if (Directory.Exists(UploadsDirectory))
{
foreach (var gallery in Directory.GetDirectories(UploadsDirectory, "*", SearchOption.TopDirectoryOnly))
{
Directory.Delete(gallery, true);
}
foreach (var thumbnail in Directory.GetFiles(ThumbnailsDirectory, "*.*", SearchOption.AllDirectories))
{
System.IO.File.Delete(thumbnail);
}
Directory.CreateDirectory(Path.Combine(UploadsDirectory, "default"));
}
return Json(new { success = await _database.WipeAllGalleries() });
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Failed_Wipe_Galleries"].Value} - {ex?.Message}");
}
}
return Json(new { success = false });
}
[HttpDelete]
public async Task<IActionResult> DeleteGallery(int id)
{
@@ -289,6 +365,42 @@ namespace WeddingShare.Controllers
return Json(new { success = false });
}
[HttpDelete]
public async Task<IActionResult> DeletePhoto(int id)
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
try
{
var photo = await _database.GetGalleryItem(id);
if (photo != null)
{
var gallery = await _database.GetGallery(photo.GalleryId);
if (gallery != null)
{
var photoPath = Path.Combine(UploadsDirectory, gallery.Name, photo.Title);
if (System.IO.File.Exists(photoPath))
{
System.IO.File.Delete(photoPath);
}
return Json(new { success = await _database.DeleteGalleryItem(photo) });
}
}
else
{
return Json(new { success = false, message = _localizer["Failed_Delete_Gallery"].Value });
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Failed_Delete_Gallery"].Value} - {ex?.Message}");
}
}
return Json(new { success = false });
}
[HttpPost]
public async Task<IActionResult> DownloadGallery(int id)
{
@@ -330,5 +442,139 @@ namespace WeddingShare.Controllers
return Json(new { success = false });
}
[HttpGet]
public async Task<IActionResult> ExportBackup()
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var tempDir = $"Temp";
var exportDir = Path.Combine(tempDir, "Export");
try
{
if (Directory.Exists(UploadsDirectory))
{
if (!Directory.Exists(tempDir))
{
Directory.CreateDirectory(tempDir);
}
if (Directory.Exists(exportDir))
{
Directory.Delete(exportDir, true);
}
Directory.CreateDirectory(exportDir);
var dbExport = Path.Combine(exportDir, $"WeddingShare.bak");
var exported = await _database.Export($"Data Source={dbExport}");
if (exported)
{
var uploadsZip = Path.Combine(exportDir, $"Uploads.bak");
ZipFile.CreateFromDirectory(UploadsDirectory, uploadsZip, CompressionLevel.Optimal, false);
var thumbnailsZip = Path.Combine(exportDir, $"Thumbnails.bak");
ZipFile.CreateFromDirectory(ThumbnailsDirectory, thumbnailsZip, CompressionLevel.Optimal, false);
var exportZipFile = Path.Combine(tempDir, $"WeddingShare-{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.zip");
if (System.IO.File.Exists(exportZipFile))
{
System.IO.File.Delete(exportZipFile);
}
ZipFile.CreateFromDirectory(exportDir, exportZipFile, CompressionLevel.Optimal, false);
System.IO.File.Delete(dbExport);
System.IO.File.Delete(uploadsZip);
System.IO.File.Delete(thumbnailsZip);
byte[] bytes = System.IO.File.ReadAllBytes(exportZipFile);
System.IO.File.Delete(exportZipFile);
return Json(new { success = true, filename = Path.GetFileName(exportZipFile), content = Convert.ToBase64String(bytes, 0, bytes.Length) });
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Failed_Export"].Value} - {ex?.Message}");
}
finally
{
Directory.Delete(exportDir, true);
}
}
return Json(new { success = false });
}
[HttpPost]
public async Task<IActionResult> ImportBackup()
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var tempDir = $"Temp";
var importDir = Path.Combine(tempDir, "Import");
try
{
var files = Request?.Form?.Files;
if (files != null && files.Count > 0)
{
foreach (IFormFile file in files)
{
var extension = Path.GetExtension(file.FileName)?.Trim('.');
if (string.Equals("zip", extension, StringComparison.OrdinalIgnoreCase))
{
if (!Directory.Exists(tempDir))
{
Directory.CreateDirectory(tempDir);
}
var filePath = Path.Combine(tempDir, "Import.zip");
if (!string.IsNullOrWhiteSpace(filePath))
{
using (var fs = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(fs);
}
if (Directory.Exists(importDir))
{
Directory.Delete(importDir, true);
}
Directory.CreateDirectory(importDir);
ZipFile.ExtractToDirectory(filePath, importDir, true);
System.IO.File.Delete(filePath);
var uploadsZip = Path.Combine(importDir, "Uploads.bak");
ZipFile.ExtractToDirectory(uploadsZip, UploadsDirectory, true);
var thumbnailsZip = Path.Combine(importDir, "Thumbnails.bak");
ZipFile.ExtractToDirectory(thumbnailsZip, ThumbnailsDirectory, true);
var dbImport = Path.Combine(importDir, "WeddingShare.bak");
var imported = await _database.Import($"Data Source={dbImport}");
return Json(new { success = imported });
}
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Import_Failed"].Value} - {ex?.Message}");
}
finally
{
Directory.Delete(importDir, true);
}
}
return Json(new { success = false });
}
}
}

View File

@@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Localization;
using WeddingShare.Attributes;
using WeddingShare.Enums;
@@ -20,19 +19,21 @@ namespace WeddingShare.Controllers
private readonly IDatabaseHelper _database;
private readonly ISecretKeyHelper _secretKey;
private readonly IDeviceDetector _deviceDetector;
private readonly IImageHelper _imageHelper;
private readonly ILogger _logger;
private readonly IStringLocalizer<GalleryController> _localizer;
private readonly string UploadsDirectory;
private readonly string ThumbnailsDirectory;
public GalleryController(IWebHostEnvironment hostingEnvironment, IConfigHelper config, IDatabaseHelper database, ISecretKeyHelper secretKey, IDeviceDetector deviceDetector, ILogger<GalleryController> logger, IStringLocalizer<GalleryController> localizer)
public GalleryController(IWebHostEnvironment hostingEnvironment, IConfigHelper config, IDatabaseHelper database, ISecretKeyHelper secretKey, IDeviceDetector deviceDetector, IImageHelper imageHelper, ILogger<GalleryController> logger, IStringLocalizer<GalleryController> localizer)
{
_hostingEnvironment = hostingEnvironment;
_config = config;
_database = database;
_secretKey = secretKey;
_deviceDetector = deviceDetector;
_imageHelper = imageHelper;
_logger = logger;
_localizer = localizer;
@@ -43,7 +44,7 @@ namespace WeddingShare.Controllers
[HttpGet]
[RequiresSecretKey]
[AllowGuestCreate]
public async Task<IActionResult> Index(string id = "default", string? key = null)
public async Task<IActionResult> Index(string id = "default", string? key = null, GalleryOrder order = GalleryOrder.None)
{
id = id.ToLower();
if (string.IsNullOrWhiteSpace(id) || _config.GetOrDefault("Settings", "Single_Gallery_Mode", false))
@@ -80,18 +81,38 @@ namespace WeddingShare.Controllers
if (gallery != null)
{
var allowedFileTypes = _config.GetOrDefault("Settings", "Allowed_File_Types", ".jpg,.jpeg,.png").Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
var images = new PhotoGallery(_config.GetOrDefault("Settings", "Gallery_Columns", 4))
var allowedFileTypes = _config.GetOrDefault("Settings", "Allowed_File_Types", ".jpg,.jpeg,.png").Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
var images = (await _database.GetAllGalleryItems(gallery.Id, GalleryItemState.Approved))?.Where(x => allowedFileTypes.Any(y => string.Equals(Path.GetExtension(x.Title).Trim('.'), y.Trim('.'), StringComparison.OrdinalIgnoreCase)));
switch (order)
{
case GalleryOrder.UploadedAsc:
images = images?.OrderBy(x => x.Id);
break;
case GalleryOrder.UploadedDesc:
images = images?.OrderByDescending(x => x.Id);
break;
case GalleryOrder.NameAsc:
images = images?.OrderByDescending(x => x.Title);
break;
case GalleryOrder.NameDesc:
images = images?.OrderBy(x => x.Title);
break;
default:
break;
}
var model = new PhotoGallery(_config.GetOrDefault("Settings", "Gallery_Columns", 4))
{
GalleryId = id,
GalleryPath = $"/{galleryPath.Remove(_hostingEnvironment.WebRootPath).Replace('\\', '/').TrimStart('/')}",
ThumbnailsPath = $"/{ThumbnailsDirectory.Remove(_hostingEnvironment.WebRootPath).Replace('\\', '/').TrimStart('/')}",
Images = (await _database.GetAllGalleryItems(gallery.Id, GalleryItemState.Approved))?.Where(x => allowedFileTypes.Any(y => string.Equals(Path.GetExtension(x.Title).Trim('.'), y.Trim('.'), StringComparison.OrdinalIgnoreCase)))?.Select(x => Path.GetFileName(x.Title))?.ToList(),
Images = images?.Select(x => new PhotoGalleryImage() { Id = x.Id, Name = Path.GetFileName(x.Title), Path = x.Title })?.ToList(),
PendingCount = gallery?.PendingItems ?? 0,
FileUploader = !_config.GetOrDefault("Settings", "Disable_Upload", false) || (User?.Identity != null && User.Identity.IsAuthenticated) ? new FileUploader(id, "/Gallery/UploadImage") : null
};
return View(images);
return View(model);
}
return View(new PhotoGallery());
@@ -146,6 +167,11 @@ namespace WeddingShare.Controllers
{
var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
var galleryPath = requiresReview ? Path.Combine(UploadsDirectory, gallery.Name, "Pending") : Path.Combine(UploadsDirectory, gallery.Name);
if (!Directory.Exists(galleryPath))
{
Directory.CreateDirectory(galleryPath);
}
var filePath = Path.Combine(galleryPath, fileName);
if (!string.IsNullOrWhiteSpace(filePath))
{
@@ -154,6 +180,16 @@ namespace WeddingShare.Controllers
await file.CopyToAsync(fs);
}
if (!requiresReview)
{
if (!Directory.Exists(ThumbnailsDirectory))
{
Directory.CreateDirectory(ThumbnailsDirectory);
}
await _imageHelper.GenerateThumbnail(filePath, Path.Combine(ThumbnailsDirectory, $"{Path.GetFileNameWithoutExtension(filePath)}.webp"), _config.GetOrDefault("Settings", "Thumbnail_Size", 720));
}
var item = await _database.AddGalleryItem(new GalleryItemModel()
{
GalleryId = gallery.Id,

View File

@@ -0,0 +1,11 @@
namespace WeddingShare.Enums
{
public enum GalleryOrder
{
None,
UploadedAsc,
UploadedDesc,
NameAsc,
NameDesc
}
}

View File

@@ -10,8 +10,9 @@ namespace WeddingShare.Helpers.Database
Task<GalleryModel?> GetGallery(string name);
Task<GalleryModel?> AddGallery(GalleryModel model);
Task<GalleryModel?> EditGallery(GalleryModel model);
Task<bool> WipeGallery(GalleryModel model);
Task<bool> WipeAllGalleries();
Task<bool> DeleteGallery(GalleryModel model);
Task<List<GalleryItemModel>> GetAllGalleryItems(int galleryId, GalleryItemState state = GalleryItemState.All);
Task<int> GetPendingGalleryItemCount(int? galleryId = null);
Task<List<PendingGalleryItemModel>> GetPendingGalleryItems(int? galleryId = null);
@@ -20,5 +21,7 @@ namespace WeddingShare.Helpers.Database
Task<GalleryItemModel?> AddGalleryItem(GalleryItemModel model);
Task<GalleryItemModel?> EditGalleryItem(GalleryItemModel model);
Task<bool> DeleteGalleryItem(GalleryItemModel model);
Task<bool> Import(string path);
Task<bool> Export(string path);
}
}

View File

@@ -141,6 +141,63 @@ namespace WeddingShare.Helpers.Database
return result;
}
public async Task<bool> WipeGallery(GalleryModel model)
{
bool result = false;
using (var conn = new SqliteConnection(_connString))
{
var cmd = new SqliteCommand($"DELETE FROM `gallery_items` WHERE `gallery_id`=@Id;", conn);
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("Id", model.Id);
await conn.OpenAsync();
var tran = await conn.BeginTransactionAsync();
try
{
cmd.Transaction = (SqliteTransaction)tran;
result = (await cmd.ExecuteNonQueryAsync()) > 0;
await tran.CommitAsync();
}
catch
{
await tran.RollbackAsync();
}
await conn.CloseAsync();
}
return result;
}
public async Task<bool> WipeAllGalleries()
{
bool result = false;
using (var conn = new SqliteConnection(_connString))
{
var cmd = new SqliteCommand($"DELETE FROM `gallery_items`; DELETE FROM `galleries` WHERE `id` > 1;", conn);
cmd.CommandType = CommandType.Text;
await conn.OpenAsync();
var tran = await conn.BeginTransactionAsync();
try
{
cmd.Transaction = (SqliteTransaction)tran;
result = (await cmd.ExecuteNonQueryAsync()) > 0;
await tran.CommitAsync();
}
catch
{
await tran.RollbackAsync();
}
await conn.CloseAsync();
}
return result;
}
public async Task<bool> DeleteGallery(GalleryModel model)
{
bool result = false;
@@ -358,6 +415,62 @@ namespace WeddingShare.Helpers.Database
}
#endregion
#region Backups
public async Task<bool> Import(string path)
{
bool result = false;
try
{
using (var backup = new SqliteConnection(path))
using (var conn = new SqliteConnection(_connString))
{
await backup.OpenAsync();
await conn.OpenAsync();
backup.BackupDatabase(conn);
await conn.CloseAsync();
await backup.CloseAsync();
SqliteConnection.ClearPool(backup);
}
result = true;
}
catch { }
return result;
}
public async Task<bool> Export(string path)
{
bool result = false;
try
{
using (var conn = new SqliteConnection(_connString))
using (var backup = new SqliteConnection(path))
{
await conn.OpenAsync();
await backup.OpenAsync();
conn.BackupDatabase(backup);
await backup.CloseAsync();
await conn.CloseAsync();
SqliteConnection.ClearPool(backup);
}
result = true;
}
catch { }
return result;
}
#endregion
#region Data Parsers
private async Task<List<GalleryModel>> ReadGalleries(SqliteDataReader? reader)
{

View File

@@ -8,11 +8,11 @@
}
public PhotoGallery(int columnCount)
: this("default", columnCount, string.Empty, string.Empty, new List<string>())
: this("default", columnCount, string.Empty, string.Empty, new List<PhotoGalleryImage>())
{
}
public PhotoGallery(string id, int columnCount, string galleryPath, string thumbnailPath, List<string> images)
public PhotoGallery(string id, int columnCount, string galleryPath, string thumbnailPath, List<PhotoGalleryImage> images)
{
this.GalleryId = id;
this.GalleryPath = galleryPath;
@@ -42,7 +42,18 @@
return this.ApprovedCount + this.PendingCount;
}
}
public List<string>? Images { get; set; }
public List<PhotoGalleryImage>? Images { get; set; }
public FileUploader? FileUploader { get; set; }
}
public class PhotoGalleryImage
{
public PhotoGalleryImage()
{
}
public int Id { get; set; }
public string? Path { get; set; }
public string? Name { get; set; }
}
}

View File

@@ -129,12 +129,24 @@
<data name="Failed_Edit_Gallery" xml:space="preserve">
<value>Failed to update gallery</value>
</data>
<data name="Failed_Export" xml:space="preserve">
<value>Failed to export data</value>
</data>
<data name="Failed_Finding_File" xml:space="preserve">
<value>Failed to find file</value>
</data>
<data name="Failed_Import" xml:space="preserve">
<value>Failed to import data</value>
</data>
<data name="Failed_Reviewing_Photo" xml:space="preserve">
<value>Failed to review photo</value>
</data>
<data name="Failed_Wipe_Galleries" xml:space="preserve">
<value>Failed to wipe galleries</value>
</data>
<data name="Failed_Wipe_Gallery" xml:space="preserve">
<value>Failed to wipe gallery</value>
</data>
<data name="Gallery_Name_Already_Exists" xml:space="preserve">
<value>Gallery name already exists</value>
</data>

View File

@@ -129,12 +129,24 @@
<data name="Failed_Edit_Gallery" xml:space="preserve">
<value>Échec de la mise à jour de la galerie</value>
</data>
<data name="Failed_Export" xml:space="preserve">
<value>Échec de l'exportation des données</value>
</data>
<data name="Failed_Finding_File" xml:space="preserve">
<value>Impossible de trouver le fichier</value>
</data>
<data name="Failed_Import" xml:space="preserve">
<value>Échec de l'importation des données</value>
</data>
<data name="Failed_Reviewing_Photo" xml:space="preserve">
<value>Impossible de vérifier la photo</value>
</data>
<data name="Failed_Wipe_Galleries" xml:space="preserve">
<value>Impossible d'effacer la galerie</value>
</data>
<data name="Failed_Wipe_Gallery" xml:space="preserve">
<value>Impossible d'effacer la galerie</value>
</data>
<data name="Gallery_Name_Already_Exists" xml:space="preserve">
<value>Le nom de la galerie existe déjà</value>
</data>

View File

@@ -117,6 +117,18 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Clean" xml:space="preserve">
<value>Clean</value>
</data>
<data name="Clean_Confirmation" xml:space="preserve">
<value>Are you sure you want to clean this gallery?</value>
</data>
<data name="Wipe" xml:space="preserve">
<value>Wipe</value>
</data>
<data name="Wipe_Confirmation" xml:space="preserve">
<value>Are you sure you want to wipe this gallery?</value>
</data>
<data name="Close" xml:space="preserve">
<value>Close</value>
</data>
@@ -141,4 +153,7 @@
<data name="Update" xml:space="preserve">
<value>Update</value>
</data>
<data name="Wipe_All_Confirmation" xml:space="preserve">
<value>Are you sure you want to wipe all galleries?</value>
</data>
</root>

View File

@@ -117,6 +117,18 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Clean" xml:space="preserve">
<value>Faire le ménage</value>
</data>
<data name="Clean_Confirmation" xml:space="preserve">
<value>Etes-vous sûr de vouloir nettoyer cette galerie ?</value>
</data>
<data name="Wipe" xml:space="preserve">
<value>Essuyer</value>
</data>
<data name="Wipe_Confirmation" xml:space="preserve">
<value>Etes-vous sûr de vouloir effacer cette galerie ?</value>
</data>
<data name="Close" xml:space="preserve">
<value>Fermer</value>
</data>
@@ -141,4 +153,7 @@
<data name="Update" xml:space="preserve">
<value>Mise à jour</value>
</data>
<data name="Wipe_All_Confirmation" xml:space="preserve">
<value>Êtes-vous sûr de vouloir effacer toutes les galeries ?</value>
</data>
</root>

View File

@@ -1,7 +1,9 @@
using System.Globalization;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using WeddingShare.BackgroundWorkers;
using WeddingShare.Helpers;
using WeddingShare.Helpers.Database;
using WeddingShare.Helpers.Dbup;
@@ -52,6 +54,16 @@ namespace WeddingShare
options.Limits.MaxRequestBodySize = int.MaxValue;
});
services.Configure<FormOptions>(x =>
{
x.MultipartHeadersLengthLimit = Int32.MaxValue;
x.MultipartBoundaryLengthLimit = Int32.MaxValue;
x.MultipartBodyLengthLimit = Int64.MaxValue;
x.ValueLengthLimit = Int32.MaxValue;
x.BufferBodyLengthLimit = Int64.MaxValue;
x.MemoryBufferThreshold = Int32.MaxValue;
});
services.Configure<RequestLocalizationOptions>(options => {
var supportedCultures = new[]
{

View File

@@ -11,13 +11,34 @@
var ctx = Context.Request;
var link = $"{(_config.GetOrDefault("Settings", "Force_Https", false) ? "https" : ctx.Scheme)}://{ctx.Host}/Gallery";
<h1 class="display-6">
<span class="float-none">@_localizer["Available_Galleries"].Value: </span>
@if (!_config.GetOrDefault("Settings", "Single_Gallery_Mode", false))
{
<span class="float-end"><i class="btnAddGallery fa-solid fa-calendar-plus fa-2x text-success pointer px-2" alt="Create"></i></span>
}
</h1>
<div class="row pb-2 pb-lg-4">
<div class="col-12 text-center">
@if (!_config.GetOrDefault("Settings", "Single_Gallery_Mode", false))
{
<div class="mx-0 mx-sm-5 px-2 px-xl-5 text-success d-inline-block">
<i class="btnAddGallery fa-solid fa-calendar-plus fa-2x pointer" alt="Create"></i>
<h6>Create</h6>
</div>
}
<div class="mx-0 mx-sm-5 px-2 px-xl-5 text-primary d-inline-block">
<i class="btnImport fa-solid fa-upload fa-2x pointer" alt="Import"></i>
<h6>Import</h6>
</div>
<div class="mx-0 mx-sm-5 px-2 px-xl-5 text-primary d-inline-block">
<i class="btnExport fa-solid fa-download fa-2x pointer" alt="Export"></i>
<h6>Export</h6>
</div>
<div class="mx-0 mx-sm-5 px-2 px-xl-5 text-danger d-inline-block">
<i class="btnWipeAllGalleries fa-solid fa-broom fa-2x pointer" alt="Wipe"></i>
<h6>Wipe</h6>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<h1 class="display-6">@_localizer["Available_Galleries"].Value: </h1>
</div>
</div>
<table class="table table-bordered">
<tr>
<th class="col-6 col-md-4">@_localizer["Name"].Value</th>
@@ -43,17 +64,9 @@
<td>
<i class="btnOpenGallery btn btn-outline-primary fa-solid fa-up-right-from-square" data-url="@galleryLink" alt="Open"></i>
<i class="btnDownloadGallery btn @(gallery.TotalItems > 0 ? "btn-outline-primary" : "btn-outline-disabled") fa-solid fa-download" alt="Download" @(gallery.TotalItems == 0 ? "disabled=disabled" : string.Empty)></i>
@if (gallery.Id > 1)
{
<i class="btnEditGallery btn btn-outline-success fa-solid fa-pen-to-square" alt="Rename"></i>
<i class="btnDeleteGallery btn btn-outline-danger fa-solid fa-trash-can" alt="Delete"></i>
}
else
{
<i class="btn btn-outline-disabled fa-solid fa-pen-to-square" alt="Rename"></i>
<i class="btn btn-outline-disabled fa-solid fa-trash-can" alt="Delete"></i>
}
<i class="btn @(gallery.Id > 1 ? "btnEditGallery btn-outline-success" : "btn-outline-disabled") fa-solid fa-pen-to-square" alt="Edit"></i>
<i class="btn @(gallery.TotalItems > 0 ? "btnWipeGallery btn-outline-danger" : "btn-outline-disabled") fa-solid fa-broom" alt="Wipe" @(gallery.TotalItems == 0 ? "disabled=disabled" : string.Empty)></i>
<i class="btn @(gallery.Id > 1 ? "btnDeleteGallery btn-outline-danger" : "btn-outline-disabled") fa-solid fa-trash-can" alt="Delete"></i>
</td>
</tr>
}

View File

@@ -1,4 +1,5 @@
@using System.Text
@using WeddingShare.Enums
@using WeddingShare.Views.Gallery
@inject Microsoft.Extensions.Localization.IStringLocalizer<IndexModel> _localizer
@inject WeddingShare.Helpers.IConfigHelper _config
@@ -7,21 +8,21 @@
@{
var ctx = Context.Request;
var qrCodeLink = $"{(_config.GetOrDefault("Settings", "Force_Https", false) ? "https" : ctx.Scheme)}://{ctx.Host}{ctx.Path}";
if (_config.GetOrDefault("Settings", "Hide_Key_From_QR_Code", false))
var baseLink = $"{(_config.GetOrDefault("Settings", "Force_Https", false) ? "https" : ctx.Scheme)}://{ctx.Host}{ctx.Path}";
var queryString = new StringBuilder();
foreach (var q in ctx.Query.Where(x => !string.Equals("order", x.Key, StringComparison.OrdinalIgnoreCase)))
{
var queryString = new StringBuilder();
foreach (var q in ctx.Query.Where(x => !string.Equals("key", x.Key, StringComparison.OrdinalIgnoreCase)))
if (string.Equals("key", q.Key, StringComparison.OrdinalIgnoreCase) && _config.GetOrDefault("Settings", "Hide_Key_From_QR_Code", false))
{
queryString.Append($"&{q.Key}={q.Value}");
continue;
}
qrCodeLink = $"{qrCodeLink}?{queryString.ToString().TrimStart('&')}";
}
else
{
qrCodeLink = $"{qrCodeLink}?{ctx.QueryString.ToString().TrimStart('?')}";
queryString.Append($"&{q.Key}={q.Value}");
}
baseLink = $"{baseLink}?{queryString.ToString().TrimStart('&')}".TrimEnd(new char[] { '?', '&' });
var qrCodeLink = $"{baseLink}{(ctx.Query.ContainsKey("order") ? $"&order={ctx.Query["order"]}" : string.Empty)}";
}
@if (Model?.FileUploader != null)
@@ -53,6 +54,20 @@
@if (!_config.GetOrDefault("Settings", "Disable_QR_Code", false))
{
<div class="qrcode-container d-none d-lg-block">
<div class="btn-group w-100 mb-5">
<button class="btn btn-primary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Sort
</button>
<div class="dropdown-menu">
@foreach (GalleryOrder order in Enum.GetValues(typeof(GalleryOrder)))
{
if (order != GalleryOrder.None)
{
<a class="dropdown-item" href="@($"{baseLink}&order={(int)order}")">@order</a>
}
}
</div>
</div>
<div id="qrcode"></div>
<span>@_localizer["Share_Code"].Value</span>
</div>

View File

@@ -25,7 +25,6 @@
<link rel="stylesheet" href="~/css/fontawesome.solid.min.css" asp-append-version="true" />
<link rel="stylesheet" href="~/css/fontawesome.brands.min.css" asp-append-version="true" />
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/lib/jquery-lightbox/lightbox-plus-jquery.min.js"></script>
@@ -35,6 +34,7 @@
<script src="~/js/fontawesome.solid.min.js"></script>
<script src="~/js/fontawesome.brands.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
<script src="~/js/popup.js" asp-append-version="true"></script>
</head>
<body>
<!-- Navigation -->

View File

@@ -14,119 +14,4 @@
</div>
</div>
</div>
</div>
<div id="add-gallery-modal" class="modal pt-lg-4" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@_localizer["Create"].Value</h5>
</div>
<div class="modal-body">
<div class="row pb-4">
<div class="col-12">
<label for="gallery-name">@_localizer["Gallery_Name"].Value</label>
<input type="text" id="gallery-name" class="form-control" aria-describedby="gallery-name-help" placeholder="" aria-label="@_localizer["Gallery_Name"].Value" />
<div id="gallery-key-help" class="form-text">
@_localizer["Gallery_Name_Help"].Value
</div>
</div>
</div>
<div class="row pb-4">
<div class="col-12">
<label for="gallery-name">@_localizer["Gallery_Key"].Value</label>
<input type="text" id="gallery-key" class="form-control" aria-describedby="gallery-key-help" placeholder="" aria-label="@_localizer["Gallery_Key"].Value" />
<div id="gallery-key-help" class="form-text">
@_localizer["Gallery_Key_Help"].Value
</div>
</div>
</div>
<div class="row pt-3">
<div class="col-6">
<button type="button" class="btn btn-success col-12 btnCreate" data-dismiss="modal">@_localizer["Create"].Value</button>
</div>
<div class="col-6">
<button type="button" class="btn btn-secondary col-12 btnDismissPopup" data-dismiss="modal">@_localizer["Cancel"].Value</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="edit-gallery-modal" class="modal pt-lg-4" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@_localizer["Update"].Value</h5>
</div>
<div class="modal-body">
<input type="hidden" id="gallery-id" />
<div class="row pb-4">
<div class="col-12">
<label for="gallery-name">@_localizer["Gallery_Name"].Value</label>
<input type="text" id="gallery-name" class="form-control" aria-describedby="gallery-name-help" placeholder="" aria-label="@_localizer["Gallery_Name"].Value" />
<div id="gallery-key-help" class="form-text">
@_localizer["Gallery_Name_Help"].Value
</div>
</div>
</div>
<div class="row pb-4">
<div class="col-12">
<label for="gallery-name">@_localizer["Gallery_Key"].Value</label>
<input type="text" id="gallery-key" class="form-control" aria-describedby="gallery-key-help" placeholder="" aria-label="@_localizer["Gallery_Key"].Value" />
<div id="gallery-key-help" class="form-text">
@_localizer["Gallery_Key_Help"].Value
</div>
</div>
</div>
<div class="row pt-3">
<div class="col-6">
<button type="button" class="btn btn-success col-12 btnUpdate" data-dismiss="modal">@_localizer["Update"].Value</button>
</div>
<div class="col-6">
<button type="button" class="btn btn-secondary col-12 btnDismissPopup" data-dismiss="modal">@_localizer["Cancel"].Value</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="delete-gallery-modal" class="modal pt-lg-4" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@_localizer["Delete"].Value</h5>
</div>
<div class="modal-body">
<input type="hidden" id="gallery-id" />
<div class="row pb-4">
<div class="col-12">
<label for="gallery-name">@_localizer["Gallery_Name"].Value</label>
<input type="text" id="gallery-name" class="form-control" aria-describedby="gallery-name-help" placeholder="" aria-label="@_localizer["Gallery_Name"].Value" disabled="disabled" />
</div>
</div>
<div class="row pb-4">
<div class="col-12">@_localizer["Delete_Confirmation"].Value</div>
</div>
<div class="row pt-3">
<div class="col-6">
<button type="button" class="btn btn-danger col-12 btnDelete" data-dismiss="modal">@_localizer["Delete"].Value</button>
</div>
<div class="col-6">
<button type="button" class="btn btn-secondary col-12 btnDismissPopup" data-dismiss="modal">@_localizer["Cancel"].Value</button>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -33,9 +33,15 @@
var index = columnIndex;
while (index < Model.Images.Count())
{
<a href="@Model.GalleryPath/@Model.Images[index]" data-lightbox="@Model.GalleryId">
<img src="@Model.ThumbnailsPath/@(System.IO.Path.GetFileNameWithoutExtension(Model.Images[index].TrimStart('/'))).webp" class="w-100 shadow-1-strong rounded mb-4" loading="lazy" />
</a>
<div class="rounded mb-4 image-tile">
<a href="@Model.GalleryPath/@Model.Images[index]?.Name?.TrimStart('/')" data-lightbox="@Model.GalleryId">
<img src="@Model.ThumbnailsPath/@(System.IO.Path.GetFileNameWithoutExtension(Model.Images[index]?.Name?.TrimStart('/'))).webp" class="w-100 shadow-1-strong" loading="lazy" />
</a>
@if (User?.Identity != null && User.Identity.IsAuthenticated)
{
<button type="button" class="btn btn-danger btnDeletePhoto w-100 m-0" data-photo-id="@Model.Images[index]?.Id" data-photo-name="@Model.Images[index]?.Name">@_localizer["Delete"].Value</button>
}
</div>
index += columnCount;
}
}

View File

@@ -34,7 +34,7 @@
"Directory_Scanner_Interval": "*/30 * * * *"
},
"Release": {
"Version": "1.1.2"
"Version": "1.2.0"
},
"AllowedHosts": "*"
}

View File

@@ -90,4 +90,17 @@ tr td i {
.pending-approval .btn-group .btn:last-child {
border-radius: 0px 0px 5px 0px;
}
}
.image-tile {
overflow: hidden;
}
.image-tile .btn {
border-radius: 0px;
display: none;
}
.image-tile:hover .btn {
display: block;
}

View File

@@ -51,11 +51,224 @@
$(document).off('click', 'i.btnAddGallery').on('click', 'i.btnAddGallery', function (e) {
preventDefaults(e);
displayPopup('add-gallery-modal');
if ($(this).attr('disabled') == 'disabled') {
return;
}
displayPopup({
Title: 'Create Gallery',
Fields: [{
Id: 'gallery-name',
Name: 'Gallery Name',
Hint: 'Please enter a new gallery name'
}, {
Id: 'gallery-key',
Name: 'Secret Key',
Hint: 'Please enter a new secret key'
}],
Buttons: [{
Text: 'Create',
Class: 'btn-success',
Callback: function () {
displayLoader('Loading...');
let name = $('#popup-modal-field-gallery-name').val();
if (name == undefined || name.length == 0) {
displayMessage(`Create Gallery`, `Gallery name cannot be empty`);
return;
}
let key = $('#popup-modal-field-gallery-key').val();
$.ajax({
url: '/Admin/AddGallery',
method: 'POST',
data: { Id: 0, Name: name, SecretKey: key }
})
.done(data => {
if (data.success === true) {
displayMessage(`Create Gallery`, `Successfully created gallery`);
} else if (data.message) {
displayMessage(`Create Gallery`, `Create failed`, [data.message]);
} else {
displayMessage(`Create Gallery`, `Failed to create gallery`);
}
})
.fail((xhr, error) => {
displayMessage(`Create Gallery`, `Create failed`, [error]);
});
}
}, {
Text: 'Close'
}]
});
});
$(document).off('click', 'i.btnImport').on('click', 'i.btnImport', function (e) {
preventDefaults(e);
if ($(this).attr('disabled') == 'disabled') {
return;
}
displayPopup({
Title: 'Import Data',
Fields: [{
Id: 'import-file',
Name: 'Backup File',
Type: 'File',
Hint: 'Please select a WeddingShare backup archive',
Accept: '.zip'
}],
Buttons: [{
Text: 'Import',
Class: 'btn-success',
Callback: function () {
displayLoader('Loading...');
var files = $('#popup-modal-field-import-file')[0].files;
if (files == undefined || files.length == 0) {
displayMessage(`Import Data`, `Please select an import file`);
return;
}
var data = new FormData();
data.append('file-0', files[0]);
$.ajax({
url: '/Admin/ImportBackup',
method: 'POST',
data: data,
contentType: false,
processData: false
})
.done(data => {
if (data.success === true) {
displayMessage(`Import Data`, `Successfully imported data`);
window.location.reload();
} else if (data.message) {
displayMessage(`Import Data`, `Import failed`, [data.message]);
} else {
displayMessage(`Import Data`, `Failed to import data`);
}
})
.fail((xhr, error) => {
displayMessage(`Import Data`, `Import failed`, [error]);
});
}
}, {
Text: 'Close'
}]
});
});
$(document).off('click', 'i.btnExport').on('click', 'i.btnExport', function (e) {
preventDefaults(e);
if ($(this).attr('disabled') == 'disabled') {
return;
}
displayPopup({
Title: 'Export Data',
Message: 'Are you sure you want to continue?',
Buttons: [{
Text: 'Export',
Class: 'btn-success',
Callback: function () {
displayLoader('Loading...');
$.ajax({
url: '/Admin/ExportBackup',
method: 'GET'
})
.done(data => {
hideLoader();
if (data.success === true) {
var s = window.atob(data.content);
var bytes = new Uint8Array(s.length);
for (var i = 0; i < s.length; i++) {
bytes[i] = s.charCodeAt(i);
}
var blob = new Blob([bytes], { type: "application/octetstream" });
var isIE = false || !!document.documentMode;
if (isIE) {
window.navigator.msSaveBlob(blob, data.filename);
} else {
var url = window.URL || window.webkitURL;
link = url.createObjectURL(blob);
var a = $("<a />");
a.attr("download", data.filename);
a.attr("href", link);
$("body").append(a);
a[0].click();
$("body").remove(a);
}
} else if (data.message) {
displayMessage(`Export Data`, `Export failed`, [data.message]);
} else {
displayMessage(`Export Data`, `Failed to export data`);
}
})
.fail((xhr, error) => {
displayMessage(`Export Data`, `Export failed`, [error]);
});
}
}, {
Text: 'Close'
}]
});
});
$(document).off('click', 'i.btnWipeAllGalleries').on('click', 'i.btnWipeAllGalleries', function (e) {
preventDefaults(e);
if ($(this).attr('disabled') == 'disabled') {
return;
}
displayPopup({
Title: 'Wipe Data',
Message: 'Are you sure you want to wipe all data?',
Buttons: [{
Text: 'Wipe',
Class: 'btn-danger',
Callback: function () {
displayLoader('Loading...');
$.ajax({
url: '/Admin/WipeAllGalleries',
method: 'DELETE'
})
.done(data => {
if (data.success === true) {
displayMessage(`Wipe Data`, `Successfully wiped data`);
} else if (data.message) {
displayMessage(`Wipe Data`, `Wipe failed`, [data.message]);
} else {
displayMessage(`Wipe Data`, `Failed to wipe data`);
}
})
.fail((xhr, error) => {
displayMessage(`Wipe Data`, `Wipe failed`, [error]);
});
}
}, {
Text: 'Close'
}]
});
});
$(document).off('click', 'i.btnOpenGallery').on('click', 'i.btnOpenGallery', function (e) {
preventDefaults(e);
if ($(this).attr('disabled') == 'disabled') {
return;
}
window.open($(this).data('url'), '_blank');
});
@@ -116,119 +329,177 @@
$(document).off('click', 'i.btnEditGallery').on('click', 'i.btnEditGallery', function (e) {
preventDefaults(e);
if ($(this).attr('disabled') == 'disabled') {
return;
}
let row = $(this).closest('tr');
displayPopup({
Title: 'Edit Gallery',
Fields: [{
Id: 'gallery-id',
Value: row.data('gallery-id'),
Type: 'hidden'
}, {
Id: 'gallery-name',
Name: 'Gallery Name',
Value: row.data('gallery-name'),
Hint: 'Please enter a new gallery name'
}, {
Id: 'gallery-key',
Name: 'Secret Key',
Value: row.data('gallery-key'),
Hint: 'Please enter a new secret key'
}],
Buttons: [{
Text: 'Update',
Class: 'btn-success',
Callback: function () {
displayLoader('Loading...');
$('#edit-gallery-modal #gallery-id').val(row.data('gallery-id'));
$('#edit-gallery-modal #gallery-name').val(row.data('gallery-name'));
$('#edit-gallery-modal #gallery-key').val(row.data('gallery-key'));
let id = $('#popup-modal-field-gallery-id').val();
if (id == undefined || id.length == 0) {
displayMessage(`Edit Gallery`, `Gallery id cannot be empty`);
return;
}
displayPopup('edit-gallery-modal');
let name = $('#popup-modal-field-gallery-name').val();
if (name == undefined || name.length == 0) {
displayMessage(`Edit Gallery`, `Gallery name cannot be empty`);
return;
}
let key = $('#popup-modal-field-gallery-key').val();
$.ajax({
url: '/Admin/EditGallery',
method: 'PUT',
data: { Id: id, Name: name, SecretKey: key }
})
.done(data => {
if (data.success === true) {
$(`tr[data-gallery-id=${id}] .gallery-name`).text(name);
displayMessage(`Edit Gallery`, `Successfully updated gallery`);
} else if (data.message) {
displayMessage(`Edit Gallery`, `Update failed`, [data.message]);
} else {
displayMessage(`Edit Gallery`, `Failed to update gallery`);
}
})
.fail((xhr, error) => {
displayMessage(`Edit Gallery`, `Update failed`, [error]);
});
}
}, {
Text: 'Close'
}]
});
});
$(document).off('click', 'i.btnWipeGallery').on('click', 'i.btnWipeGallery', function (e) {
preventDefaults(e);
if ($(this).attr('disabled') == 'disabled') {
return;
}
let row = $(this).closest('tr');
displayPopup({
Title: 'Wipe Gallery',
Message: `Are you sure you want to wipe gallery '${row.data('gallery-name') }'?`,
Fields: [{
Id: 'gallery-id',
Value: row.data('gallery-id'),
Type: 'hidden'
}],
Buttons: [{
Text: 'Wipe',
Class: 'btn-danger',
Callback: function () {
displayLoader('Loading...');
let id = $('#popup-modal-field-gallery-id').val();
if (id == undefined || id.length == 0) {
displayMessage(`Wipe Gallery`, `Gallery id cannot be empty`);
return;
}
$.ajax({
url: '/Admin/WipeGallery',
method: 'DELETE',
data: { id }
})
.done(data => {
if (data.success === true) {
$(`tr[data-gallery-id=${id}] .gallery-name`).text(name);
displayMessage(`Wipe Gallery`, `Successfully wiped gallery`);
} else if (data.message) {
displayMessage(`Wipe Gallery`, `Wipe failed`, [data.message]);
} else {
displayMessage(`Wipe Gallery`, `Failed to wipe gallery`);
}
})
.fail((xhr, error) => {
displayMessage(`Wipe Gallery`, `Wipe failed`, [error]);
});
}
}, {
Text: 'Close'
}]
});
});
$(document).off('click', 'i.btnDeleteGallery').on('click', 'i.btnDeleteGallery', function (e) {
preventDefaults(e);
if ($(this).attr('disabled') == 'disabled') {
return;
}
let row = $(this).closest('tr');
displayPopup({
Title: 'Delete Gallery',
Message: `Are you sure you want to delete gallery '${row.data('gallery-name')}'?`,
Fields: [{
Id: 'gallery-id',
Value: row.data('gallery-id'),
Type: 'hidden'
}],
Buttons: [{
Text: 'Delete',
Class: 'btn-danger',
Callback: function () {
displayLoader('Loading...');
$('#delete-gallery-modal #gallery-id').val(row.data('gallery-id'));
$('#delete-gallery-modal #gallery-name').val(row.data('gallery-name'));
let id = $('#popup-modal-field-gallery-id').val();
if (id == undefined || id.length == 0) {
displayMessage(`Delete Gallery`, `Gallery id cannot be empty`);
return;
}
displayPopup('delete-gallery-modal');
});
$(document).off('click', '#add-gallery-modal .btnCreate').on('click', '#add-gallery-modal .btnCreate', function (e) {
preventDefaults(e);
hidePopup('add-gallery-modal');
displayLoader('Loading...');
let name = $('#add-gallery-modal #gallery-name').val();
let key = $('#add-gallery-modal #gallery-key').val();
$.ajax({
url: '/Admin/AddGallery',
method: 'POST',
data: { Id: 0, Name: name, SecretKey: key }
})
.done(data => {
hideLoader();
if (data.success === true) {
displayMessage(`Create`, `Successfully created gallery`);
} else if (data.message) {
displayMessage(`Create`, `Create failed`, [data.message]);
} else {
displayMessage(`Create`, `Failed to create gallery`);
$.ajax({
url: '/Admin/DeleteGallery',
method: 'DELETE',
data: { id }
})
.done(data => {
if (data.success === true) {
$(`tr[data-gallery-id=${id}]`).remove();
displayMessage(`Delete Gallery`, `Successfully deleted gallery`);
} else if (data.message) {
displayMessage(`Delete Gallery`, `Delete failed`, [data.message]);
} else {
displayMessage(`Delete Gallery`, `Failed to delete gallery`);
}
})
.fail((xhr, error) => {
displayMessage(`Delete Gallery`, `Delete failed`, [error]);
});
}
})
.fail((xhr, error) => {
hideLoader();
displayMessage(`Create`, `Create failed`, [error]);
});
});
$(document).off('click', '#edit-gallery-modal .btnUpdate').on('click', '#edit-gallery-modal .btnUpdate', function (e) {
preventDefaults(e);
hidePopup('edit-gallery-modal');
displayLoader('Loading...');
let id = $('#edit-gallery-modal #gallery-id').val();
let name = $('#edit-gallery-modal #gallery-name').val();
let key = $('#edit-gallery-modal #gallery-key').val();
$.ajax({
url: '/Admin/EditGallery',
method: 'PUT',
data: { Id: id, Name: name, SecretKey: key }
})
.done(data => {
hideLoader();
if (data.success === true) {
$(`tr[data-gallery-id=${id}] .gallery-name`).text(name);
displayMessage(`Update`, `Successfully updated gallery`);
} else if (data.message) {
displayMessage(`Update`, `Update failed`, [data.message]);
} else {
displayMessage(`Update`, `Failed to update gallery`);
}
})
.fail((xhr, error) => {
hideLoader();
displayMessage(`Update`, `Update failed`, [error]);
});
});
$(document).off('click', '#delete-gallery-modal .btnDelete').on('click', '#delete-gallery-modal .btnDelete', function (e) {
preventDefaults(e);
hidePopup('delete-gallery-modal');
displayLoader('Loading...');
let id = $('#delete-gallery-modal #gallery-id').val();
$.ajax({
url: '/Admin/DeleteGallery',
method: 'DELETE',
data: { id }
})
.done(data => {
hideLoader();
if (data.success === true) {
$(`tr[data-gallery-id=${id}]`).remove();
displayMessage(`Delete`, `Successfully deleted gallery`);
} else if (data.message) {
displayMessage(`Delete`, `Delete failed`, [data.message]);
} else {
displayMessage(`Delete`, `Failed to delete gallery`);
}
})
.fail((xhr, error) => {
hideLoader();
displayMessage(`Delete`, `Delete failed`, [error]);
});
}, {
Text: 'Close'
}]
});
});
});

View File

@@ -148,5 +148,60 @@
imageUpload(dataRefs);
}
$(document).off('click', 'button.btnDeletePhoto').on('click', 'button.btnDeletePhoto', function (e) {
preventDefaults(e);
if ($(this).attr('disabled') == 'disabled') {
return;
}
var id = $(this).data('photo-id');
var name = $(this).data('photo-name');
displayPopup({
Title: 'Delete Photo',
Message: `Are you sure you want to delete photo '${name}'?`,
Fields: [{
Id: 'photo-id',
Value: id,
Type: 'hidden'
}],
Buttons: [{
Text: 'Delete',
Class: 'btn-danger',
Callback: function () {
displayLoader('Loading...');
let id = $('#popup-modal-field-photo-id').val();
if (id == undefined || id.length == 0) {
displayMessage(`Delete Photo`, `Photo id cannot be empty`);
return;
}
$.ajax({
url: '/Admin/DeletePhoto',
method: 'DELETE',
data: { id }
})
.done(data => {
if (data.success === true) {
$(`tr[data-gallery-id=${id}]`).remove();
displayMessage(`Delete Photo`, `Successfully deleted photo`);
} else if (data.message) {
displayMessage(`Delete Photo`, `Delete failed`, [data.message]);
} else {
displayMessage(`Delete Photo`, `Failed to delete photo`);
}
})
.fail((xhr, error) => {
displayMessage(`Delete Photo`, `Delete failed`, [error]);
});
}
}, {
Text: 'Close'
}]
});
});
});
})();

View File

@@ -0,0 +1,65 @@
function displayPopup(options) {
let fields = '';
(options?.Fields ?? [])?.forEach((field, index) => {
fields += `<div class="row pb-3 ${field?.Type?.toLowerCase() == 'hidden' ? "d-none" : ""}">
<div class="col-12">
<label>${field?.Name}</label>
<input type="${field?.Type?.toLowerCase() ?? "text"}" id="popup-modal-field-${field?.Id}" class="form-control" aria-describedby="${field?.DescribedBy ?? ""}" placeholder="${field?.Placeholder ?? ""}" value="${field?.Value ?? ""}" aria-label="${field?.Name}" ${field?.Disabled ? "disabled=\"disabled\"" : ""} ${field?.Accept ? "accept=\"" + field?.Accept + "\"" : ""} />
<div class="form-text">${field?.Hint ?? ""}</div>
</div>
</div>`;
});
let message = '';
if (options?.Message != undefined && options?.Message.length > 0) {
message = `<div class="row pb-3">
<div class="col-12">${options?.Message ?? ""}</div>
</div>`;
}
let buttons = '';
(options?.Buttons ?? [{ Text: 'Close' }])?.forEach((button, index) => {
buttons += `<div class="col-${12 / options?.Buttons?.length ?? 1}">
<button type="button" id="popup-modal-button-${index}" class="btn ${button?.Class ?? "btn-secondary"} col-12">${button?.Text ?? "Close"}</button>
</div>`;
$(document).off('click', `#popup-modal-button-${index}`).on('click', `#popup-modal-button-${index}`, function () {
hidePopup();
if (button?.Callback) {
button?.Callback();
}
});
});
$('body').loading({
theme: 'dark',
message: '',
stoppable: false,
start: true
});
$('.popup-modal').remove();
$('body').append(`<div class="modal popup-modal pt-lg-4" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${options.Title}</h5>
</div>
<div class="modal-body">
${fields}
${message}
<div class="row ${(options?.Fields?.length ?? 0) > 0 ? "pt-3" : ""}">
${buttons}
</div>
</div>
</div>
</div>
</div>`);
$('.popup-modal').show();
}
function hidePopup() {
$('body').loading('stop');
$('.popup-modal').hide();
}

View File

@@ -16,21 +16,6 @@ function hideLoader() {
$('body').loading('stop');
}
function displayPopup(id) {
$('body').loading({
theme: 'dark',
message: '',
stoppable: false,
start: true
});
$(`#${id}`).show();
}
function hidePopup(id) {
$('body').loading('stop');
$(`#${id}`).hide();
}
function uuidv4() {
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
(+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
@@ -38,6 +23,8 @@ function uuidv4() {
}
function displayMessage(title, message, errors) {
hideLoader();
$('#alert-message-modal .modal-title').text(title);
$('#alert-message-modal .modal-message').html(message);