diff --git a/src/AwesomeGithubStats.Api/AwesomeGithubStats.Api.csproj b/src/AwesomeGithubStats.Api/AwesomeGithubStats.Api.csproj index a9faa49..8d006b0 100644 --- a/src/AwesomeGithubStats.Api/AwesomeGithubStats.Api.csproj +++ b/src/AwesomeGithubStats.Api/AwesomeGithubStats.Api.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 AwesomeGithubStats.Api 372edb10-8465-4af6-a84d-28d86be75fe3 Linux diff --git a/src/AwesomeGithubStats.Api/svgs/user-stats/github-card.svg b/src/AwesomeGithubStats.Api/svgs/user-stats/github-card.svg index c7c5397..9d0dd5f 100644 --- a/src/AwesomeGithubStats.Api/svgs/user-stats/github-card.svg +++ b/src/AwesomeGithubStats.Api/svgs/user-stats/github-card.svg @@ -1,7 +1,10 @@ - + {{Name}} diff --git a/src/AwesomeGithubStats.Api/svgs/user-stats/level-alt-card.svg b/src/AwesomeGithubStats.Api/svgs/user-stats/level-alt-card.svg index 49f47df..2afcae8 100644 --- a/src/AwesomeGithubStats.Api/svgs/user-stats/level-alt-card.svg +++ b/src/AwesomeGithubStats.Api/svgs/user-stats/level-alt-card.svg @@ -1,7 +1,10 @@ - + - + diff --git a/src/AwesomeGithubStats.Api/svgs/user-stats/level-card.svg b/src/AwesomeGithubStats.Api/svgs/user-stats/level-card.svg index 8436bd4..37a50a3 100644 --- a/src/AwesomeGithubStats.Api/svgs/user-stats/level-card.svg +++ b/src/AwesomeGithubStats.Api/svgs/user-stats/level-card.svg @@ -1,7 +1,10 @@ - + - + diff --git a/src/AwesomeGithubStats.Api/svgs/user-stats/octocat-card.svg b/src/AwesomeGithubStats.Api/svgs/user-stats/octocat-card.svg index 2e14a6f..06d6c43 100644 --- a/src/AwesomeGithubStats.Api/svgs/user-stats/octocat-card.svg +++ b/src/AwesomeGithubStats.Api/svgs/user-stats/octocat-card.svg @@ -1,7 +1,10 @@ - + {{Name}} diff --git a/src/AwesomeGithubStats.Api/wwwroot/assets/chevron-down-white.svg b/src/AwesomeGithubStats.Api/wwwroot/assets/chevron-down-white.svg new file mode 100644 index 0000000..408f9d3 --- /dev/null +++ b/src/AwesomeGithubStats.Api/wwwroot/assets/chevron-down-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/AwesomeGithubStats.Api/wwwroot/assets/chevron-down.svg b/src/AwesomeGithubStats.Api/wwwroot/assets/chevron-down.svg new file mode 100644 index 0000000..e947cd4 --- /dev/null +++ b/src/AwesomeGithubStats.Api/wwwroot/assets/chevron-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/AwesomeGithubStats.Api/wwwroot/css/style.css b/src/AwesomeGithubStats.Api/wwwroot/css/style.css index f5c76bb..389ff01 100644 --- a/src/AwesomeGithubStats.Api/wwwroot/css/style.css +++ b/src/AwesomeGithubStats.Api/wwwroot/css/style.css @@ -47,6 +47,7 @@ body { font-family: "Open Sans", Roboto, Arial, sans-serif; padding-top: 10px; color: var(--text); + transition: background 0.3s ease-in-out, color 0.3s ease-in-out; } .github { @@ -54,9 +55,9 @@ body { margin-bottom: 12px; } - .github a { - color: var(--text); - } +.github a { + color: var(--text); +} @media only screen and (max-width: 340px) { .github a { @@ -119,19 +120,19 @@ h2 { transition: 0.2s ease-in-out; } - .btn:focus { - outline: var(--button-outline) auto 2px; - } +.btn:focus { + outline: var(--button-outline) auto 2px; +} - .btn:hover { - background-color: var(--blue-dark); - box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); - } +.btn:hover { + background-color: var(--blue-dark); + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); +} - .btn:disabled { - background: var(--blue-transparent); - box-shadow: none; - } +.btn:disabled { + background: var(--blue-transparent); + box-shadow: none; +} .parameters { margin: auto; @@ -143,46 +144,39 @@ h2 { grid-gap: 8px; } - .parameters .btn { - margin-top: 8px; - } - - .parameters input[type="text"], - .parameters input.jscolor, - .parameters select { - padding: 10px 14px; - display: inline-block; - border: 1px solid var(--border); - border-radius: 6px; - box-sizing: border-box; - font-family: inherit; - background: var(--card-background); - width: 100%; - color: inherit; - } +.parameters .btn { + margin-top: 8px; +} - .parameters input.jscolor { - font-size: 12px; - max-width: 130px; - } +.parameters input[type="text"], +.parameters input.jscolor, +.parameters select { + padding: 10px 14px; + display: inline-block; + border: 1px solid var(--border); + border-radius: 6px; + box-sizing: border-box; + font-family: inherit; + background: var(--card-background); + width: 100%; + color: inherit; +} -@media only screen and (max-width: 1024px) { - .parameters input.jscolor { - max-width: none; - } +.parameters input.jscolor { + font-size: 12px; } .parameters select { -webkit-appearance: none; -moz-appearance: none; - background-image: url("data:image/svg+xml;utf8,"); + background-image: url("../assets/chevron-down.svg"); background-repeat: no-repeat; background-position-x: 100%; background-position-y: 5px; } [data-theme="dark"] .parameters select { - background-image: url("data:image/svg+xml;utf8,"); + background-image: url("../assets/chevron-down-white.svg"); } .parameters select option[disabled] { @@ -206,18 +200,18 @@ input:focus:invalid { grid-column-end: -1; } - .advanced summary { - padding: 6px; - } +.advanced summary { + padding: 6px; +} - .advanced .parameters { - margin-top: 8px; - grid-template-columns: auto 1fr auto; - } +.advanced .parameters { + margin-top: 8px; + grid-template-columns: auto 1fr auto; +} - .advanced .parameters label:first-of-type { - font-weight: bold; - } +.advanced .parameters label:first-of-type { + font-weight: bold; +} .plus.btn, .minus.btn { @@ -230,12 +224,12 @@ input:focus:invalid { width: 40px; } - .plus.btn:hover, - .minus.btn:hover { - background: var(--background); - } +.plus.btn:hover, +.minus.btn:hover { + background: var(--background); +} -.output img { +.output div { max-width: 100%; margin-left: 15px; } @@ -272,46 +266,46 @@ input:focus:invalid { align-items: center; } - /* tooltip bubble */ - .btn.tooltip:before { - content: attr(title); - position: absolute; - transform: translateY(-45px); - height: auto; - width: auto; - background: #4a4a4afa; - border-radius: 4px; - color: white; - line-height: 30px; - font-size: 1em; - padding: 0 12px; - pointer-events: none; - opacity: 0; - } +/* tooltip bubble */ +.btn.tooltip:before { + content: attr(title); + position: absolute; + transform: translateY(-45px); + height: auto; + width: auto; + background: #4a4a4afa; + border-radius: 4px; + color: white; + line-height: 30px; + font-size: 1em; + padding: 0 12px; + pointer-events: none; + opacity: 0; +} - /* tooltip bottom triangle */ - .btn.tooltip:after { - content: ""; - position: absolute; - transform: translateY(-27px); - border-style: solid; - border-color: #4a4a4afa transparent transparent transparent; - pointer-events: none; - opacity: 0; - } +/* tooltip bottom triangle */ +.btn.tooltip:after { + content: ""; + position: absolute; + transform: translateY(-27px); + border-style: solid; + border-color: #4a4a4afa transparent transparent transparent; + pointer-events: none; + opacity: 0; +} - /* show tooltip on hover */ - .btn.tooltip[title]:hover:before, - .btn.tooltip[title]:hover:after, - .btn.tooltip:disabled:hover:before, - .btn.tooltip:disabled:hover:after { - transition: 0.2s ease-in opacity; - opacity: 1; - } +/* show tooltip on hover */ +.btn.tooltip[title]:hover:before, +.btn.tooltip[title]:hover:after, +.btn.tooltip:disabled:hover:before, +.btn.tooltip:disabled:hover:after { + transition: 0.2s ease-in opacity; + opacity: 1; +} - .btn.tooltip:disabled:before { - content: "You must first input a valid username."; - } +.btn.tooltip:disabled:before { + content: "You must first input a valid username."; +} pre { white-space: pre-wrap; diff --git a/src/AwesomeGithubStats.Api/wwwroot/index.html b/src/AwesomeGithubStats.Api/wwwroot/index.html index ee157a1..00339c8 100644 --- a/src/AwesomeGithubStats.Api/wwwroot/index.html +++ b/src/AwesomeGithubStats.Api/wwwroot/index.html @@ -19,7 +19,8 @@ Awesome GitHub Stats Demo - + + @@ -82,7 +83,7 @@

Properties

- @@ -95,7 +96,7 @@

Properties

- @@ -112,6 +113,11 @@

Properties

+ + + @@ -146,7 +153,7 @@

Properties

Preview

- GitHub Readme Streak Stats +

Note: Wait until the stats load. Sometimes we've to wait too long for the GitHub GraphQL API. @@ -180,7 +187,7 @@

Usefull links

- + diff --git a/src/AwesomeGithubStats.Api/wwwroot/js/script.js b/src/AwesomeGithubStats.Api/wwwroot/js/script.js index 9a8915d..73fa7e7 100644 --- a/src/AwesomeGithubStats.Api/wwwroot/js/script.js +++ b/src/AwesomeGithubStats.Api/wwwroot/js/script.js @@ -53,21 +53,50 @@ let preview = { document.getElementById("rankLink").href = rank; // update stats link document.getElementById("statsLink").href = stats; + // update image preview - document.querySelector(".output img").src = demoImageURL; - // update markdown + const previewContainer = document.getElementById("preview-container"); + if(previewContainer) { + previewContainer.innerHTML = "Loading..."; // Optional loading text + + fetch(demoImageURL) + .then(response => { + if (!response.ok) throw new Error("Network response was not ok"); + return response.text(); + }) + .then(svgText => { + previewContainer.innerHTML = svgText; + }) + .catch(error => { + console.error("Error fetching SVG:", error); + previewContainer.innerHTML = "Error loading preview."; + }); + } document.querySelector(".md code").innerText = md; // disable copy button if username is invalid document.querySelectorAll(".copy-button").forEach(copyButton => { copyButton.disabled = !!document.querySelectorAll("#user:invalid").length; }); }, - addProperty: function (property, value = "#DD2727FF") { + addProperty: function (property, value) { const selectElement = document.querySelector("#properties"); // if no property passed, get the currently selected property if (!property) { property = selectElement.value; } + + // Handle specific logic for Border Radius vs Colors + let isColor = property !== "Border Radius"; + let inputId = property; + let defaultValue = value; + + if (property === "Border Radius") { + inputId = "borderRadius"; + if (!defaultValue) defaultValue = "4"; + } else { + if (!defaultValue) defaultValue = "#DD2727FF"; + } + if (!selectElement.disabled) { // disable option in menu Array.prototype.find.call( @@ -84,18 +113,30 @@ let preview = { } else { selectElement.disabled = true; } + // label const label = document.createElement("label"); label.innerText = property; label.setAttribute("data-property", property); - // color picker + + // Input setup const input = document.createElement("input"); - input.className = "param jscolor"; - input.id = property; - input.name = property; + input.id = inputId; + input.name = inputId; input.setAttribute("data-property", property); - input.setAttribute("data-jscolor", "{ format: 'hexa' }"); - input.value = value; + input.value = defaultValue; + + // Apply the 'param' class so the update() function finds it + input.className = "param"; + input.type = "text"; + + if (isColor) { + input.className += " jscolor"; // Add jscolor class for color picker + input.setAttribute("data-jscolor", "{ format: 'hexa' }"); + } else { + input.pattern = "[0-9]*"; + } + // removal button const minus = document.createElement("button"); minus.className = "minus btn"; @@ -105,14 +146,17 @@ let preview = { ); minus.innerText = "−"; minus.setAttribute("data-property", property); + // add elements const parent = document.querySelector(".advanced .parameters"); parent.appendChild(label); parent.appendChild(input); parent.appendChild(minus); - //initialise jscolor on element - jscolor.install(parent); + //initialise jscolor on element only if it is a color + if (isColor) { + jscolor.install(parent); + } // update and exit this.update(); @@ -199,6 +243,32 @@ document.addEventListener("click", () => preview.update(), false); window.addEventListener( "load", () => { + // We fetch the font list from Fontsource API + // to avoid Google Font API key + fetch('https://api.fontsource.org/v1/fonts') + .then(response => response.json()) + .then(data => { + const select = document.getElementById('fontFamily'); + if(!select) return; + + data.forEach(font => { + const option = document.createElement('option'); + option.value = font.family; + option.text = font.family; + select.appendChild(option); + }); + + const urlParams = new URLSearchParams(window.location.search); + const fontParam = urlParams.get('fontFamily'); + if (fontParam) { + select.value = fontParam; + preview.update(); + } + }) + .catch(err => { + console.error("Failed to fetch font list:", err); + }); + // set input boxes to match URL parameters new URLSearchParams(window.location.search).forEach((val, key) => { let paramInput = document.querySelector(`#${key}`); @@ -215,4 +285,4 @@ window.addEventListener( preview.update(); }, false -); +); \ No newline at end of file diff --git a/src/AwesomeGithubStats.Core/AwesomeGithubStats.Core.csproj b/src/AwesomeGithubStats.Core/AwesomeGithubStats.Core.csproj index 82af11c..f0edb1e 100644 --- a/src/AwesomeGithubStats.Core/AwesomeGithubStats.Core.csproj +++ b/src/AwesomeGithubStats.Core/AwesomeGithubStats.Core.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 diff --git a/src/AwesomeGithubStats.Core/Models/Options/CardStyles.cs b/src/AwesomeGithubStats.Core/Models/Options/CardStyles.cs index ffbfc56..028328a 100644 --- a/src/AwesomeGithubStats.Core/Models/Options/CardStyles.cs +++ b/src/AwesomeGithubStats.Core/Models/Options/CardStyles.cs @@ -1,4 +1,6 @@ -namespace AwesomeGithubStats.Core.Models.Options +using AwesomeGithubStats.Core.Models; + +namespace AwesomeGithubStats.Core.Models.Options { public record CardStyles { @@ -10,6 +12,7 @@ public record CardStyles public string Theme { get; set; } = "default"; public string BackgroundColor { get; set; } = "fffefe"; public string BorderColor { get; set; } = "e4e2e2"; + public int BorderRadius { get; set; } = 4; public void Apply(UserStatsOptions options) { @@ -20,6 +23,7 @@ public void Apply(UserStatsOptions options) BackgroundColor = options.Background ?? BackgroundColor; BorderColor = options.Border ?? BorderColor; RingColor = options.Ring ?? RingColor; + BorderRadius = options.BorderRadius ?? BorderRadius; } } -} +} \ No newline at end of file diff --git a/src/AwesomeGithubStats.Core/Models/Svgs/UserStatsCard.cs b/src/AwesomeGithubStats.Core/Models/Svgs/UserStatsCard.cs index fb0759c..b70d6a5 100644 --- a/src/AwesomeGithubStats.Core/Models/Svgs/UserStatsCard.cs +++ b/src/AwesomeGithubStats.Core/Models/Svgs/UserStatsCard.cs @@ -67,7 +67,19 @@ public Stream Svg(string file, UserRank rank, CardStyles cardStyles, CardTransla : rank.UserStats.Name.Truncate(25); CalculateProgressBar(rank); + + // Handle Custom Fonts from Google Fonts + var fontName = string.IsNullOrWhiteSpace(_options.FontFamily) ? "Roboto Slab" : _options.FontFamily; + var fontUrlName = fontName.Replace(" ", "+"); + + // Construct the @import URL for Google Fonts + var fontCss = $"@import url('https://fonts.googleapis.com/css2?family={fontUrlName}:wght@400;500;600;700;800;900&display=swap');"; + + var fontFamily = $"'{fontName}', serif"; + var svgFinal = file + .Replace("{{FontCSS}}", fontCss) + .Replace("{{FontFamily}}", fontFamily) .Replace("{{Name}}", displayName) .Replace("{{Stars}}", rank.UserStats.TotalStars()) .Replace("{{Commits}}", rank.UserStats.TotalCommits()) @@ -92,6 +104,7 @@ public Stream Svg(string file, UserRank rank, CardStyles cardStyles, CardTransla .Replace("{{ShowIcons}}", cardStyles.ShowIcons ? "block" : "none") .Replace("{{TextPosition}}", cardStyles.ShowIcons ? "25" : "0") .Replace("{{ProgressBarWidth}}", CalculateRectangleProgress(ProgressBar).ToString(CultureInfo.InvariantCulture)) + .Replace("{{BorderRadius}}", cardStyles.BorderRadius.ToString(CultureInfo.InvariantCulture)) ; @@ -131,4 +144,4 @@ public string GetCardName() }; } } -} +} \ No newline at end of file diff --git a/src/AwesomeGithubStats.Core/Models/UserStatsOptions.cs b/src/AwesomeGithubStats.Core/Models/UserStatsOptions.cs index 8724a6f..55c4afa 100644 --- a/src/AwesomeGithubStats.Core/Models/UserStatsOptions.cs +++ b/src/AwesomeGithubStats.Core/Models/UserStatsOptions.cs @@ -13,6 +13,8 @@ public class UserStatsOptions public string Ring { get; set; } public string CardType { get; set; } = "default"; public bool? PreferLogin { get; set; } + public int? BorderRadius { get; set; } + public string FontFamily { get; set; } } -} +} \ No newline at end of file