Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added option to set gradient backgrounds #481

Merged
merged 10 commits into from
Apr 1, 2023
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ If the `theme` parameter is specified, any color customizations specified will b
| `hide_border` | Make the border transparent (Default: `false`) | `true` or `false` |
| `border_radius` | Set the roundness of the edges (Default: `4.5`) | Number `0` (sharp corners) to `248` (ellipse) |
| `background` | Background color | **hex code** without `#` or **css color** |
| `gradientBg` | Background color | **rotation** and two colors **hex code** without `#` or **css color** |
AndroiableDroid marked this conversation as resolved.
Show resolved Hide resolved
| `border` | Border color | **hex code** without `#` or **css color** |
| `stroke` | Stroke line color between sections | **hex code** without `#` or **css color** |
| `ring` | Color of the ring around the current streak | **hex code** without `#` or **css color** |
Expand Down
12 changes: 11 additions & 1 deletion src/card.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ function generateCard(array $stats, array $params = null): string
// read border_radius parameter, default to 4.5 if not set
$borderRadius = $params["border_radius"] ?? "4.5";

// Set Background
$bg = $params['gradientBg'] ? 'url(#gradient)' : $theme["background"];
$gradient = explode(",",$params['gradientBg'] ?? "");

// total contributions
$totalContributions = $numFormatter->format($stats["totalContributions"]);
$firstContribution = formatDate($stats["firstContribution"], $dateFormat, $localeCode);
Expand Down Expand Up @@ -325,6 +329,12 @@ function generateCard(array $stats, array $params = null): string
100% { opacity: 1; }
}
</style>
<defs>
<linearGradient id='gradient' gradientTransform='rotate({$gradient[0]})'>
<stop offset='0%' stop-color='#{$gradient[1]}' />
<stop offset='100%' stop-color='#{$gradient[2]}' />
</linearGradient>
</defs>
AndroiableDroid marked this conversation as resolved.
Show resolved Hide resolved
<defs>
<clipPath id='outer_rectangle'>
<rect width='495' height='195' rx='{$borderRadius}'/>
Expand All @@ -336,7 +346,7 @@ function generateCard(array $stats, array $params = null): string
</defs>
<g clip-path='url(#outer_rectangle)'>
<g style='isolation: isolate'>
<rect stroke='{$theme["border"]}' fill='{$theme["background"]}' rx='{$borderRadius}' x='0.5' y='0.5' width='494' height='194'/>
<rect stroke='{$theme["border"]}' fill='{$bg}' rx='{$borderRadius}' x='0.5' y='0.5' width='494' height='194'/>
</g>
<g style='isolation: isolate'>
<line x1='330' y1='28' x2='330' y2='170' vector-effect='non-scaling-stroke' stroke-width='1' stroke='{$theme["stroke"]}' stroke-linejoin='miter' stroke-linecap='square' stroke-miterlimit='3'/>
Expand Down
5 changes: 5 additions & 0 deletions src/demo/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ input:focus:invalid {
grid-template-columns: auto 1fr auto;
}

.advanced .grid-middle {
display: grid;
grid-template-columns: 30% 35% 35%;
}

.advanced .color-properties label:first-of-type {
font-weight: bold;
}
Expand Down
3 changes: 2 additions & 1 deletion src/demo/index.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

$THEMES = include "../themes.php";
$PROPERTIES = include "../properties.php";
$TRANSLATIONS = include "../translations.php";
// Get the keys of the first value in the translations array
// and filter to only include locales that have an array as the value
Expand Down Expand Up @@ -149,7 +150,7 @@ function gtag() {
<div class="content color-properties parameters">
<label for="theme">Add Property</label>
<select id="properties" name="properties">
<?php foreach ($THEMES["default"] as $option => $color): ?>
<?php foreach ($PROPERTIES as $option): ?>
<option><?php echo $option; ?></option>
<?php endforeach; ?>
</select>
Expand Down
121 changes: 95 additions & 26 deletions src/demo/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ const preview = {
// convert parameters to query string
const query = Object.keys(params)
.filter((key) => params[key] !== this.defaults[key])
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.map((key) => {
if (key === 'gradientBg') {
return `${encodeURIComponent(key)}=${encodeURIComponent(params[key][0])},${encodeURIComponent(params[key][1])},${encodeURIComponent(params[key][2])}`
}
return `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`
})
.join("&");
// generate links and markdown
const imageURL = `${window.location.origin}?${query}`;
Expand Down Expand Up @@ -61,18 +66,12 @@ const preview = {
*/
addProperty(property, value = "#EB5454FF") {
const selectElement = document.querySelector("#properties");
Array.prototype.find.call(selectElement.options, (o) => o.value === property);
// if no property passed, get the currently selected property
const propertyName = property || selectElement.value;
if (!selectElement.disabled) {
// disable option in menu
Array.prototype.find.call(selectElement.options, (o) => o.value === propertyName).disabled = true;
// select first unselected option
const firstAvailable = Array.prototype.find.call(selectElement.options, (o) => !o.disabled);
if (firstAvailable) {
firstAvailable.selected = true;
} else {
selectElement.disabled = true;
}
// label
const label = document.createElement("label");
label.innerText = propertyName;
Expand All @@ -83,26 +82,80 @@ const preview = {
onChange: `preview.pickerChange(this, '${propertyName}')`,
onInput: `preview.pickerChange(this, '${propertyName}')`,
};
const input = document.createElement("input");
input.className = "param jscolor";
input.id = propertyName;
input.name = propertyName;
input.setAttribute("data-property", propertyName);
input.setAttribute("data-jscolor", JSON.stringify(jscolorConfig));
input.value = value;

const parent = document.querySelector(".advanced .color-properties");
if (propertyName === "gradientBg") {
Array.prototype.find.call(selectElement.options, (o) => o.value === 'background').disabled = true;

const input = document.createElement("span");
input.className = "grid-middle";
input.setAttribute("data-property", propertyName);

const rotate = document.createElement("input");
rotate.className = "param";
rotate.type = "text";
rotate.id = "rotate";
rotate.placeholder = "30deg";
rotate.value = "30deg";
rotate.pattern = "^-[0-9]+deg|^[0-9]+[deg]+"

const color1 = document.createElement("input");
color1.className = "param jscolor";
color1.id = "color1";
color1.setAttribute("data-jscolor", JSON.stringify({
format: "hexa",
onChange: `preview.pickerChange(this, '${color1.id}')`,
onInput: `preview.pickerChange(this, '${color1.id}')`,
}));
const color2 = document.createElement("input");
color2.className = "param jscolor";
color2.id = "color2";
color2.setAttribute("data-jscolor", JSON.stringify({
format: "hexa",
onChange: `preview.pickerChange(this, '${color2.id}')`,
onInput: `preview.pickerChange(this, '${color2.id}')`,
}));
rotate.name = color1.name = color2.name = propertyName;
color1.value = color2.value = value;
// add elements
parent.appendChild(label);
input.appendChild(rotate);
input.appendChild(color1);
input.appendChild(color2);
parent.appendChild(input);
} else {
const input = document.createElement("input");
input.className = "param jscolor";
input.id = propertyName;
input.name = propertyName;
input.setAttribute("data-property", propertyName);
input.setAttribute("data-jscolor", JSON.stringify(jscolorConfig));
input.value = value;
// add elements
parent.appendChild(label);
parent.appendChild(input);
}

if (propertyName === 'background') {
Array.prototype.find.call(selectElement.options, (o) => o.value === 'gradientBg').disabled = true;
}

// select first unselected option
const firstAvailable = Array.prototype.find.call(selectElement.options, (o) => !o.disabled);
if (firstAvailable) {
firstAvailable.selected = true;
} else {
selectElement.disabled = true;
}
// removal button
const minus = document.createElement("button");
minus.className = "minus btn";
minus.setAttribute("onclick", "return preview.removeProperty(this.getAttribute('data-property'));");
minus.setAttribute("type", "button");
minus.innerText = "−";
minus.setAttribute("data-property", propertyName);
// add elements
const parent = document.querySelector(".advanced .color-properties");
parent.appendChild(label);
parent.appendChild(input);
parent.appendChild(minus);

// initialise jscolor on element
jscolor.install(parent);

Expand All @@ -127,6 +180,11 @@ const preview = {
const option = Array.prototype.find.call(selectElement.options, (o) => o.value === property);
selectElement.disabled = false;
option.disabled = false;
if (property === 'gradientBg') {
Array.prototype.find.call(selectElement.options, (o) => o.value === 'background').disabled = false;
} else if (property === 'background') {
Array.prototype.find.call(selectElement.options, (o) => o.value === 'gradientBg').disabled = false;
}
// update and exit
this.update();
},
Expand All @@ -151,8 +209,12 @@ const preview = {
* @returns {Object} the key-value mapping
*/
objectFromElements(elements) {
let mCount = 0;
return Array.from(elements).reduce((acc, next) => {
const obj = { ...acc };
if (obj.gradientBg !== undefined) {
mCount++;
} else if (mCount >= 3) mCount = 0;
let value = next.value;
if (value.indexOf("#") >= 0) {
// if the value is colour, remove the hash sign
Expand All @@ -161,8 +223,12 @@ const preview = {
// if the value is in hexa and opacity is 1, remove FF
value = value.replace(/[Ff]{2}$/, "");
}
} else if (value.indexOf("deg") >= 0) {
value = value.replace(/deg/g, "");
}
obj[next.name] = value;
if (mCount <= 0)
obj[next.name] = [];
obj[next.name].push(value);
return obj;
}, {});
},
Expand Down Expand Up @@ -194,12 +260,15 @@ const preview = {
* Remove "FF" from a hex color if opacity is 1
* @param {string} color - the hex color
* @param {string} input - the property name, or id of the element to update
* @param {boolean} setColor - if true set the color to the input else update original value
*/
checkColor(color, input) {
checkColor(color, input, setColor = false) {
// if color has hex alpha value -> remove it
if (color.length === 9 && color.slice(-2) === "FF") {
// if color has hex alpha value -> remove it
document.querySelector(`[name="${input}"]`).value = color.slice(0, -2);
}
for (const el of document.querySelectorAll(`[name="${input}"]`))
if (el.value.length === 9 && color.slice(-2) === "FF")
el.value = setColor ? color.slice(0, -2) : el.value.slice(0, -2);
}
},

/**
Expand All @@ -209,7 +278,7 @@ const preview = {
*/
pickerChange(picker, input) {
// color was changed by picker - check it
this.checkColor(picker.toHEXAString(), input);
this.checkColor(picker.toHEXAString(), input, true);
// update preview
this.update();
},
Expand Down
15 changes: 15 additions & 0 deletions src/properties.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
return [
"background",
"gradientBg",
"border",
"stroke",
"ring",
"fire",
"currStreakNum",
"sideNums",
"currStreakLabel",
"sideLabels",
"dates"
];
?>
6 changes: 6 additions & 0 deletions tests/expected/test_card.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.