diff --git a/api/wakatime.js b/api/wakatime.js
index 69f0ce7d..4b9ce1f8 100644
--- a/api/wakatime.js
+++ b/api/wakatime.js
@@ -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,
diff --git a/readme.md b/readme.md
index d1348777..dab957dd 100644
--- a/readme.md
+++ b/readme.md
@@ -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` |
diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts
index 94f36adc..c7d8c60f 100644
--- a/src/cards/types.d.ts
+++ b/src/cards/types.d.ts
@@ -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;
diff --git a/src/cards/wakatime.js b/src/cards/wakatime.js
index 65e1d54d..64ded83d 100644
--- a/src/cards/wakatime.js
+++ b/src/cards/wakatime.js
@@ -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 = ({
${label}:
${value}
${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: {