From f6f5e44ccf807120328e2db7f30448b81819870f Mon Sep 17 00:00:00 2001 From: Gabriel Sales Date: Sun, 2 Jun 2024 12:00:11 -0300 Subject: [PATCH 01/11] Add extension to create to paging --- .../Extensions/Queryable/PagedResponse.cs | 38 ++++++++++ .../Queryable/QueryableExtensions.cs | 76 +++++++++++++++++++ .../Extensions/Queryable/ResponsePage.cs | 18 +++++ 3 files changed, 132 insertions(+) create mode 100644 Spark.Library/Extensions/Queryable/PagedResponse.cs create mode 100644 Spark.Library/Extensions/Queryable/QueryableExtensions.cs create mode 100644 Spark.Library/Extensions/Queryable/ResponsePage.cs diff --git a/Spark.Library/Extensions/Queryable/PagedResponse.cs b/Spark.Library/Extensions/Queryable/PagedResponse.cs new file mode 100644 index 0000000..c044e7e --- /dev/null +++ b/Spark.Library/Extensions/Queryable/PagedResponse.cs @@ -0,0 +1,38 @@ + +namespace Spark.Library.Extensions.Queryable +{ + public class PagedResponse : ResponsePage + { + public PagedResponse(T data, int page, int limit) + { + Page = page; + Limit = limit; + Data = data; + } + + public int Page { get; set; } + public int Limit { get; set; } + public Uri? FirstPage { get; set; } + public Uri? LastPage { get; set; } + public int Total { get; set; } + public Uri? NextPage { get; set; } + public Uri? PreviousPage { get; set; } + + public string Links() + { + bool hasNext = NextPage is not null; + + bool hasPrevious = PreviousPage is not null && Page > 1; + + string style = ".pagination a {\r\n color: black;\r\n float: left;\r\n padding: 8px 16px;\r\n text-decoration: none;\r\n transition: background-color .3s;\r\n border: 1px solid #ddd;\r\n}\r\n\r\n.pagination a.active {\r\n background-color: #4CAF50;\r\n color: white;\r\n border: 1px solid #4CAF50;\r\n}\r\n\r\n.pagination a:hover:not(.active) {background-color: #ddd;} .isDisabled {\r\n color: currentColor;\r\n cursor: not-allowed;\r\n opacity: 0.5;\r\n text-decoration: none; pointer-events: none;\r\n cursor: default; \r\n }"; + + string html = $"" + + $""; + + return html; + } + } +} diff --git a/Spark.Library/Extensions/Queryable/QueryableExtensions.cs b/Spark.Library/Extensions/Queryable/QueryableExtensions.cs new file mode 100644 index 0000000..9093aa8 --- /dev/null +++ b/Spark.Library/Extensions/Queryable/QueryableExtensions.cs @@ -0,0 +1,76 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.WebUtilities; +using System.Linq.Expressions; + +namespace Spark.Library.Extensions.Queryable +{ + public static class QueryableExtensions + { + /// + /// The paginate method automatically takes care of setting the query's "limit" and "offset" based on the current page being viewed by the user. + /// By default, the current page is detected by the value of the page query string argument on the HTTP request. + /// This value is automatically detected by Laravel, and is also automatically inserted into links generated by the paginator. + /// Case if you not to use HttpRequest and IHttpContextAccessor you won't create links as + /// + /// + /// + /// + /// + /// + /// + /// PagedResponse + public static PagedResponse> Paginate(this IQueryable items, int page = 1, int limit = 15, HttpRequest? request = null, IHttpContextAccessor? httpContextAccessor = null) + { + page = page <= 1 ? 1 : page; + + var total = items + .ToList() + .Count(); + + var listPaginated = items + .Skip((page - 1) * limit) + .Take(limit); + + var respose = new PagedResponse>(listPaginated.ToList(), page, limit); + + var totalPages = total / (double)limit; + + int roundedTotalPages = Convert.ToInt32(Math.Ceiling(totalPages)); + + if (request is not null && httpContextAccessor is not null) + { + var route = request.Path.Value!; + + string url = string.Concat(httpContextAccessor?.HttpContext?.Request.Scheme, "://", httpContextAccessor?.HttpContext?.Request.Host.ToUriComponent()); + + // Uri uri = GetPageUri(page, limit, url, route); + + respose.NextPage = + page >= 1 && page < roundedTotalPages + ? GetPageUri(page + 1, limit, url, route) + : null; + + respose.PreviousPage = + page - 1 >= 1 && page <= roundedTotalPages + ? GetPageUri(page - 1, limit, url, route) + : null; + + respose.FirstPage = GetPageUri(1, limit, url, route); + + respose.LastPage = GetPageUri(roundedTotalPages, limit, url, route); + } + + respose.Total = total; + + return respose; + } + + private static Uri GetPageUri(int page = 1, int limit = 15, string baseUri = "https://localhost/", string route = "/not-found") + { + var _enpointUri = new Uri(string.Concat(baseUri, route)); + var modifiedUri = QueryHelpers.AddQueryString(_enpointUri.ToString(), "page", page.ToString()); + modifiedUri = QueryHelpers.AddQueryString(modifiedUri, "limit", limit.ToString()); + return new Uri(modifiedUri); + } + } +} \ No newline at end of file diff --git a/Spark.Library/Extensions/Queryable/ResponsePage.cs b/Spark.Library/Extensions/Queryable/ResponsePage.cs new file mode 100644 index 0000000..7c6f8f5 --- /dev/null +++ b/Spark.Library/Extensions/Queryable/ResponsePage.cs @@ -0,0 +1,18 @@ +namespace Spark.Library.Extensions.Queryable +{ + public class ResponsePage + { + + public ResponsePage() + { + + } + public ResponsePage(T? data) + { + Data = data; + } + + + public T? Data { get; set; } + } +} From 963074cbc34c6ae689faba241eda36fcf405db80 Mon Sep 17 00:00:00 2001 From: Gabriel Sales Date: Sun, 2 Jun 2024 12:00:49 -0300 Subject: [PATCH 02/11] Add extension new extension filter --- .../Queryable/QueryableExtensions.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Spark.Library/Extensions/Queryable/QueryableExtensions.cs b/Spark.Library/Extensions/Queryable/QueryableExtensions.cs index 9093aa8..4d6f6f4 100644 --- a/Spark.Library/Extensions/Queryable/QueryableExtensions.cs +++ b/Spark.Library/Extensions/Queryable/QueryableExtensions.cs @@ -65,6 +65,24 @@ public static PagedResponse> Paginate(this IQueryable items, int p return respose; } + /// + /// The when method will execute the given callback when the first argument given to the method evaluates to true. + /// + /// + /// + /// + /// + /// + public static IQueryable When(this IQueryable source, bool conditional, Expression> predicate) + { + if (conditional) + { + return source.Where(predicate); + } + return source; + } + + private static Uri GetPageUri(int page = 1, int limit = 15, string baseUri = "https://localhost/", string route = "/not-found") { var _enpointUri = new Uri(string.Concat(baseUri, route)); From c2461ee842c84f59597b24a13225efefb5c9ee86 Mon Sep 17 00:00:00 2001 From: Gabriel Sales Date: Sun, 2 Jun 2024 12:03:17 -0300 Subject: [PATCH 03/11] refact filters --- .../Extensions/Queryable/QueryableExtensions.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Spark.Library/Extensions/Queryable/QueryableExtensions.cs b/Spark.Library/Extensions/Queryable/QueryableExtensions.cs index 4d6f6f4..f8d0999 100644 --- a/Spark.Library/Extensions/Queryable/QueryableExtensions.cs +++ b/Spark.Library/Extensions/Queryable/QueryableExtensions.cs @@ -43,21 +43,20 @@ public static PagedResponse> Paginate(this IQueryable items, int p string url = string.Concat(httpContextAccessor?.HttpContext?.Request.Scheme, "://", httpContextAccessor?.HttpContext?.Request.Host.ToUriComponent()); - // Uri uri = GetPageUri(page, limit, url, route); respose.NextPage = page >= 1 && page < roundedTotalPages - ? GetPageUri(page + 1, limit, url, route) + ? GetPageUri(url,page + 1, limit, route) : null; respose.PreviousPage = page - 1 >= 1 && page <= roundedTotalPages - ? GetPageUri(page - 1, limit, url, route) + ? GetPageUri(url, page + 1, limit, route) : null; - respose.FirstPage = GetPageUri(1, limit, url, route); + respose.FirstPage = GetPageUri(url, 1, limit, route); - respose.LastPage = GetPageUri(roundedTotalPages, limit, url, route); + respose.LastPage = GetPageUri(url, roundedTotalPages, limit, route); } respose.Total = total; @@ -83,7 +82,7 @@ public static IQueryable When(this IQueryable source, bool conditional, } - private static Uri GetPageUri(int page = 1, int limit = 15, string baseUri = "https://localhost/", string route = "/not-found") + private static Uri GetPageUri(string baseUri, int page = 1, int limit = 15, string route = "/not-found") { var _enpointUri = new Uri(string.Concat(baseUri, route)); var modifiedUri = QueryHelpers.AddQueryString(_enpointUri.ToString(), "page", page.ToString()); From a6a8728846784cf6babe91cbac804e251d1add20 Mon Sep 17 00:00:00 2001 From: Gabriel Sales Date: Sun, 2 Jun 2024 12:05:31 -0300 Subject: [PATCH 04/11] add new string extensions --- Spark.Library/Extensions/StringExtensions.cs | 105 +++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/Spark.Library/Extensions/StringExtensions.cs b/Spark.Library/Extensions/StringExtensions.cs index 33dfb3c..04a3865 100644 --- a/Spark.Library/Extensions/StringExtensions.cs +++ b/Spark.Library/Extensions/StringExtensions.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Text; +using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -11,6 +12,9 @@ namespace Spark.Library.Extensions; public static class StringExtensions { + + private static Dictionary> _snakeCache = new Dictionary>(); + public static string Clamp(this string value, int maxChars) { return value.Length <= maxChars ? value : value.Substring(0, maxChars) + "..."; @@ -71,4 +75,105 @@ public static string ToUpperFirst(this string value) string restOfString = value[1..]; return firstChar + restOfString; } + + + /// + /// The mask method masks a portion of a string with a repeated character, and may be used to obfuscate segments of strings such as email addresses and phone numbers: + /// + /// + /// + /// + /// + /// + /// string + public static string Mask(this string str, string character, int index, int? length = null, string encoding = "UTF-8") + { + if (character == "") + { + return str; + } + + string segment = length != null ? str.Substring(index, length.Value) : str.Substring(index); + + if (segment == "") + { + return str; + } + + int strlen = Encoding.GetEncoding(encoding).GetByteCount(str); + int startIndex = index; + + if (index < 0) + { + startIndex = index < -strlen ? 0 : strlen + index; + } + + string start = str.Substring(0, startIndex); + int segmentLen = Encoding.GetEncoding(encoding).GetByteCount(segment); + string end = str.Substring(startIndex + segmentLen); + + return start + new string(character[0], segmentLen) + end; + } + + + /// + /// The snake method converts the given string to snake_case: + /// + /// + /// + /// string + public static string Snake(this string value, string delimiter = "_") + { + string key = value; + + if (_snakeCache.ContainsKey(key) && _snakeCache[key].ContainsKey(delimiter)) + { + return _snakeCache[key][delimiter]; + } + + if (!string.IsNullOrEmpty(value) && !value.All(char.IsLower)) + { + value = Regex.Replace(value, @"\s+", string.Empty); + value = Regex.Replace(value, @"(.)(?=[A-Z])", "$1" + delimiter); + value = value.ToLowerInvariant(); + } + + string result = value; + + if (_snakeCache.ContainsKey(key)) + { + _snakeCache[key].Add(delimiter, result); + } + else + { + _snakeCache.Add(key, new Dictionary { { delimiter, result } }); + } + + return result; + } + + + /// + /// The method determines if the given string is valid JSON: + /// + /// + /// bool + public static bool IsJson(this string value) + { + if (string.IsNullOrEmpty(value)) + { + return false; + } + + try + { + var result = JsonSerializer.Deserialize(value); + return true; + } + catch (JsonException) + { + return false; + } + } + } From 950011937003c71491eee91cae1e0fc1c9b53a27 Mon Sep 17 00:00:00 2001 From: Gabriel Sales Date: Sun, 2 Jun 2024 12:08:58 -0300 Subject: [PATCH 05/11] refact extensio clamp --- Spark.Library/Extensions/StringExtensions.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Spark.Library/Extensions/StringExtensions.cs b/Spark.Library/Extensions/StringExtensions.cs index 04a3865..ac8990c 100644 --- a/Spark.Library/Extensions/StringExtensions.cs +++ b/Spark.Library/Extensions/StringExtensions.cs @@ -1,4 +1,5 @@ using DotNetEnv; +using Spark.Library.Environment; using System; using System.Collections.Generic; using System.Globalization; @@ -15,9 +16,22 @@ public static class StringExtensions private static Dictionary> _snakeCache = new Dictionary>(); - public static string Clamp(this string value, int maxChars) + public static string Clamp(this string value, int maxChars,string end = "...") { - return value.Length <= maxChars ? value : value.Substring(0, maxChars) + "..."; + if (value != null && Encoding.UTF8.GetByteCount(value) <= maxChars) + { + return value; + } + + string truncated = value != null ? value.Substring(0, maxChars) : ""; + + if (truncated.Length > 0) + { + truncated = truncated.Remove(truncated.LastIndexOf(" ", StringComparison.Ordinal)); + } + + return $"{truncated}{end}"; + } public static string ToSlug(this string input) From 2abc0b88f5bb42e56bc3cf19ae840e39ce22e8d0 Mon Sep 17 00:00:00 2001 From: Gabriel Sales Date: Sun, 2 Jun 2024 12:10:17 -0300 Subject: [PATCH 06/11] add new list extension --- Spark.Library/Extensions/ListExtensions.cs | 57 ++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 Spark.Library/Extensions/ListExtensions.cs diff --git a/Spark.Library/Extensions/ListExtensions.cs b/Spark.Library/Extensions/ListExtensions.cs new file mode 100644 index 0000000..1a386db --- /dev/null +++ b/Spark.Library/Extensions/ListExtensions.cs @@ -0,0 +1,57 @@ +using System.Text.Json; + +namespace Spark.Library.Extensions +{ + public static class ListExtensions + { + /// + /// The method serializer object to json + /// + /// + /// + /// string + public static string ToJson(this List source) + { + if (source is null) + { + throw new ArgumentException("You need list"); + } + + return JsonSerializer.Serialize(source); + } + + /// + /// The collapse method collapses a collection of List into a single, flat collection + /// + /// + /// + /// List + public static List Collapse(this List> source) + { + if (source is null) + { + throw new ArgumentException("You need list"); + } + return source + .Aggregate(new List(), (x, y) => x.Concat(y) + .ToList()); + } + + /// + /// The diff method compares the List against another List. This method will return the values in the original List that are not present in the given List + /// + /// + /// + /// + /// List + public static List Diff(this List source, List anotherList) + { + if (source is null || anotherList is null) + { + throw new ArgumentException("You need list"); + } + + return source.Except(anotherList).ToList(); + } + } +} From 41c55b909fa73ff84467fac7c7eafdf83792df9a Mon Sep 17 00:00:00 2001 From: Gabriel Sales Date: Sun, 2 Jun 2024 12:16:53 -0300 Subject: [PATCH 07/11] refact clamp --- Spark.Library/Extensions/StringExtensions.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Spark.Library/Extensions/StringExtensions.cs b/Spark.Library/Extensions/StringExtensions.cs index ac8990c..8e8e980 100644 --- a/Spark.Library/Extensions/StringExtensions.cs +++ b/Spark.Library/Extensions/StringExtensions.cs @@ -1,13 +1,7 @@ -using DotNetEnv; -using Spark.Library.Environment; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; +using System.Globalization; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace Spark.Library.Extensions; @@ -23,6 +17,16 @@ public static string Clamp(this string value, int maxChars,string end = "...") return value; } + if (value.Length <= maxChars) + { + return value; + } + + if (value.Length >= maxChars) + { + return string.Concat(value.AsSpan(0, maxChars), end); + } + string truncated = value != null ? value.Substring(0, maxChars) : ""; if (truncated.Length > 0) From 012ef6bc7713fbe1475608f6fa8e66984f1c18f3 Mon Sep 17 00:00:00 2001 From: Gabriel Sales Date: Sun, 2 Jun 2024 12:17:21 -0300 Subject: [PATCH 08/11] remove coments unnecessary --- Spark.Tests/Extensions/ExtensionTests.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Spark.Tests/Extensions/ExtensionTests.cs b/Spark.Tests/Extensions/ExtensionTests.cs index 013759b..670733f 100644 --- a/Spark.Tests/Extensions/ExtensionTests.cs +++ b/Spark.Tests/Extensions/ExtensionTests.cs @@ -1,10 +1,5 @@ using Spark.Library.Extensions; -using Microsoft.AspNetCore.Components; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; + namespace BlazorSpark.Tests.Extensions { From e95728c284db7be99f6e5fa8d7f8d8cb1484b741 Mon Sep 17 00:00:00 2001 From: Gabriel Sales Date: Sun, 2 Jun 2024 12:18:28 -0300 Subject: [PATCH 09/11] add extension title --- Spark.Library/Extensions/StringExtensions.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Spark.Library/Extensions/StringExtensions.cs b/Spark.Library/Extensions/StringExtensions.cs index 8e8e980..5052593 100644 --- a/Spark.Library/Extensions/StringExtensions.cs +++ b/Spark.Library/Extensions/StringExtensions.cs @@ -194,4 +194,19 @@ public static bool IsJson(this string value) } } + + /// + /// the method converts the given string to Title Case + /// + /// + /// string + public static string Title(this string text) + { + string cultureName = CultureInfo.CurrentCulture.Name ?? "en-US"; + + var textinfo = new CultureInfo(cultureName, false).TextInfo; + + return textinfo.ToTitleCase(text); + } + } From 0630b77dbee10040c5552fb97fb3f33f87422553 Mon Sep 17 00:00:00 2001 From: Gabriel Sales Date: Sun, 2 Jun 2024 12:22:44 -0300 Subject: [PATCH 10/11] add test to string extension title --- Spark.Tests/Extensions/ExtensionTests.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Spark.Tests/Extensions/ExtensionTests.cs b/Spark.Tests/Extensions/ExtensionTests.cs index 670733f..65766ea 100644 --- a/Spark.Tests/Extensions/ExtensionTests.cs +++ b/Spark.Tests/Extensions/ExtensionTests.cs @@ -25,5 +25,17 @@ public void ShouldClampString() Assert.AreEqual("This...", clampedStr); } + + [TestMethod] + public void ShouldTitleString() + { + string input = "this is a string"; + + var title = input.Title(); + + string expectedTitle = "This Is A String"; + + Assert.AreEqual(expectedTitle, title); + } } } From c72931a4c810d6da07e66b88fa3a5faa959c1a63 Mon Sep 17 00:00:00 2001 From: Gabriel Sales Date: Sun, 2 Jun 2024 12:48:42 -0300 Subject: [PATCH 11/11] refact name method to create html paging page razor --- Spark.Library/Extensions/Queryable/PagedResponse.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Spark.Library/Extensions/Queryable/PagedResponse.cs b/Spark.Library/Extensions/Queryable/PagedResponse.cs index c044e7e..15bc756 100644 --- a/Spark.Library/Extensions/Queryable/PagedResponse.cs +++ b/Spark.Library/Extensions/Queryable/PagedResponse.cs @@ -18,7 +18,7 @@ public PagedResponse(T data, int page, int limit) public Uri? NextPage { get; set; } public Uri? PreviousPage { get; set; } - public string Links() + public string LinksRazor() { bool hasNext = NextPage is not null;