feat: wakatime card width customization (#4458)

* add card_width to wakatime card

* better handling of card_width in wakatime card

* move default card_width for wakatime card, add to readme

* review

* review

---------

Co-authored-by: Alexandr <qwerty541zxc@gmail.com>
This commit is contained in:
martin-mfg
2025-10-05 23:03:25 +03:00
committed by GitHub
co-authored by Alexandr
parent 361e8279cc
commit 39b93b57c8
4 changed files with 48 additions and 17 deletions
+2
View File
@@ -16,6 +16,7 @@ export default async (req, res) => {
title_color,
icon_color,
hide_border,
card_width,
line_height,
text_color,
bg_color,
@@ -90,6 +91,7 @@ export default async (req, res) => {
custom_title,
hide_title: parseBoolean(hide_title),
hide_border: parseBoolean(hide_border),
card_width: parseInt(card_width, 10),
hide: parseArray(hide),
line_height,
title_color,
+1
View File
@@ -648,6 +648,7 @@ You can customize the appearance and behavior of the WakaTime stats card using t
| --- | --- | --- | --- |
| `hide` | Hides the languages specified from the card. | string (comma-separated values) | `null` |
| `hide_title` | Hides the title of your card. | boolean | `false` |
| `card_width` | Sets the card's width manually. | number | `495` |
| `line_height` | Sets the line height between text. | integer | `25` |
| `hide_progress` | Hides the progress bar and percentage. | boolean | `false` |
| `custom_title` | Sets a custom title for the card. | string | `WakaTime Stats` |
+1
View File
@@ -51,6 +51,7 @@ export type TopLangOptions = CommonOptions & {
export type WakaTimeOptions = CommonOptions & {
hide_title: boolean;
hide: string[];
card_width: number;
line_height: string;
hide_progress: boolean;
custom_title: string;
+44 -17
View File
@@ -1,4 +1,5 @@
// @ts-check
import { Card } from "../common/Card.js";
import { createProgressNode } from "../common/createProgressNode.js";
import { I18n } from "../common/I18n.js";
@@ -21,6 +22,10 @@ import { createRequire } from "module";
const require = createRequire(import.meta.url);
const languageColors = require("../common/languageColors.json"); // now works
const DEFAULT_CARD_WIDTH = 495;
const MIN_CARD_WIDTH = 250;
const COMPACT_LAYOUT_MIN_WIDTH = 400;
/**
* Creates the no coding activity SVG node.
*
@@ -84,22 +89,21 @@ const createCompactLangNode = ({ lang, x, y, display_format }) => {
* @param {WakaTimeLang[]} args.langs The language objects.
* @param {number} args.y The y position of the language node.
* @param {"time" | "percent"} args.display_format The display format of the language node.
* @param {number} args.card_width Width in px of the card.
* @returns {string[]} The language text node items.
*/
const createLanguageTextNode = ({ langs, y, display_format }) => {
const createLanguageTextNode = ({ langs, y, display_format, card_width }) => {
const LEFT_X = 25;
const RIGHT_X_BASE = 230;
const rightOffset = (card_width - DEFAULT_CARD_WIDTH) / 2;
const RIGHT_X = RIGHT_X_BASE + rightOffset;
return langs.map((lang, index) => {
if (index % 2 === 0) {
return createCompactLangNode({
lang,
x: 25,
y: 12.5 * index + y,
display_format,
});
}
const isLeft = index % 2 === 0;
return createCompactLangNode({
lang,
x: 230,
y: 12.5 + 12.5 * index,
x: isLeft ? LEFT_X : RIGHT_X,
y: isLeft ? 12.5 * index + y : 12.5 + 12.5 * index,
display_format,
});
});
@@ -117,6 +121,7 @@ const createLanguageTextNode = ({ langs, y, display_format }) => {
* @param {boolean=} args.hideProgress Whether to hide the progress bar.
* @param {string} args.progressBarColor The color of the progress bar.
* @param {string} args.progressBarBackgroundColor The color of the progress bar background.
* @param {number} args.progressBarWidth The width of the progress bar.
* @returns {string} The text SVG node.
*/
const createTextNode = ({
@@ -128,6 +133,7 @@ const createTextNode = ({
hideProgress,
progressBarColor,
progressBarBackgroundColor,
progressBarWidth,
}) => {
const staggerDelay = (index + 3) * 150;
@@ -138,7 +144,7 @@ const createTextNode = ({
y: 4,
progress: percent,
color: progressBarColor,
width: 220,
width: progressBarWidth,
// @ts-ignore
name: label,
progressBarBackgroundColor,
@@ -150,7 +156,7 @@ const createTextNode = ({
<text class="stat bold" y="12.5" data-testid="${id}">${label}:</text>
<text
class="stat"
x="${hideProgress ? 170 : 350}"
x="${hideProgress ? 170 : 130 + progressBarWidth}"
y="12.5"
>${value}</text>
${cardProgress}
@@ -206,6 +212,24 @@ const getStyles = ({
`;
};
/**
* Normalize incoming width (string or number) and clamp to minimum.
*
* @param {Object} args The function arguments.
* @param {WakaTimeOptions["layout"] | undefined} args.layout The incoming layout value.
* @param {number|undefined} args.value The incoming width value.
* @returns {number} The normalized width value.
*/
const normalizeCardWidth = ({ value, layout }) => {
if (value === undefined || value === null || isNaN(value)) {
return DEFAULT_CARD_WIDTH;
}
return Math.max(
layout === "compact" ? COMPACT_LAYOUT_MIN_WIDTH : MIN_CARD_WIDTH,
value,
);
};
/**
* @typedef {import('../fetchers/types').WakaTimeData} WakaTimeData
* @typedef {import('./types').WakaTimeOptions} WakaTimeOptions
@@ -223,6 +247,7 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
const {
hide_title = false,
hide_border = false,
card_width,
hide,
line_height = 25,
title_color,
@@ -241,6 +266,8 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
disable_animations,
} = options;
const normalizedWidth = normalizeCardWidth({ value: card_width, layout });
const shouldHideLangs = Array.isArray(hide) && hide.length > 0;
if (shouldHideLangs) {
const languagesToHide = new Set(hide.map((lang) => lowercaseTrim(lang)));
@@ -289,11 +316,9 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
let finalLayout = "";
let width = 440;
// RENDER COMPACT LAYOUT
if (layout === "compact") {
width = width + 50;
let width = normalizedWidth - 5;
height = 90 + Math.round(filteredLanguages.length / 2) * 25;
// progressOffset holds the previous language's width and used to offset the next language
@@ -333,6 +358,7 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
y: 25,
langs: filteredLanguages,
display_format,
card_width: normalizedWidth,
}).join("")
: noCodingActivityNode({
// @ts-ignore
@@ -360,6 +386,7 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
// @ts-ignore
progressBarBackgroundColor: textColor,
hideProgress: hide_progress,
progressBarWidth: normalizedWidth - 275,
});
})
: [
@@ -392,7 +419,7 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
const card = new Card({
customTitle: custom_title,
defaultTitle: titleText,
width: 495,
width: normalizedWidth,
height,
border_radius,
colors: {