@stats-forge/github-stats-forge-core 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -10
- package/build/api/api-result.d.ts +6 -7
- package/build/api/api-result.d.ts.map +1 -1
- package/build/api/api-result.js +4 -5
- package/build/api/contributed-to.d.ts +23 -0
- package/build/api/contributed-to.d.ts.map +1 -0
- package/build/api/contributed-to.js +43 -0
- package/build/api/gist.d.ts +8 -22
- package/build/api/gist.d.ts.map +1 -1
- package/build/api/gist.js +14 -36
- package/build/api/handler.d.ts +16 -0
- package/build/api/handler.d.ts.map +1 -0
- package/build/api/handler.js +29 -0
- package/build/api/index.d.ts +13 -12
- package/build/api/index.d.ts.map +1 -1
- package/build/api/index.js +1 -0
- package/build/api/params.d.ts +21 -12
- package/build/api/params.d.ts.map +1 -1
- package/build/api/params.js +41 -19
- package/build/api/pin.d.ts +11 -31
- package/build/api/pin.d.ts.map +1 -1
- package/build/api/pin.js +35 -63
- package/build/api/stats.d.ts +15 -16
- package/build/api/stats.d.ts.map +1 -1
- package/build/api/stats.js +60 -99
- package/build/api/top-langs.d.ts +13 -18
- package/build/api/top-langs.d.ts.map +1 -1
- package/build/api/top-langs.js +35 -72
- package/build/api/wakatime.d.ts +11 -17
- package/build/api/wakatime.d.ts.map +1 -1
- package/build/api/wakatime.js +27 -64
- package/build/calculateRank.d.ts +0 -9
- package/build/calculateRank.d.ts.map +1 -1
- package/build/calculateRank.js +12 -17
- package/build/cards/contributed-to.d.ts +19 -0
- package/build/cards/contributed-to.d.ts.map +1 -0
- package/build/cards/contributed-to.js +242 -0
- package/build/cards/gist.d.ts +3 -4
- package/build/cards/gist.d.ts.map +1 -1
- package/build/cards/gist.js +38 -43
- package/build/cards/options.d.ts +1 -1
- package/build/cards/repo.d.ts +14 -5
- package/build/cards/repo.d.ts.map +1 -1
- package/build/cards/repo.js +93 -103
- package/build/cards/stats.d.ts +18 -11
- package/build/cards/stats.d.ts.map +1 -1
- package/build/cards/stats.js +191 -222
- package/build/cards/top-languages.d.ts +18 -33
- package/build/cards/top-languages.d.ts.map +1 -1
- package/build/cards/top-languages.js +196 -387
- package/build/cards/wakatime.d.ts +17 -8
- package/build/cards/wakatime.d.ts.map +1 -1
- package/build/cards/wakatime.js +133 -187
- package/build/common/Card.d.ts +20 -59
- package/build/common/Card.d.ts.map +1 -1
- package/build/common/Card.js +131 -198
- package/build/common/I18n.d.ts +1 -7
- package/build/common/I18n.d.ts.map +1 -1
- package/build/common/I18n.js +7 -8
- package/build/common/brand.d.ts +76 -0
- package/build/common/brand.d.ts.map +1 -0
- package/build/common/brand.js +73 -0
- package/build/common/color.d.ts +4 -17
- package/build/common/color.d.ts.map +1 -1
- package/build/common/color.js +67 -49
- package/build/common/config.d.ts +4 -14
- package/build/common/config.d.ts.map +1 -1
- package/build/common/config.js +3 -16
- package/build/common/constants.d.ts +6 -2
- package/build/common/constants.d.ts.map +1 -1
- package/build/common/constants.js +5 -1
- package/build/common/date.d.ts +50 -5
- package/build/common/date.d.ts.map +1 -1
- package/build/common/date.js +104 -4
- package/build/common/emojiMap.d.ts +8 -0
- package/build/common/emojiMap.d.ts.map +1 -0
- package/build/common/emojiMap.js +10 -0
- package/build/common/emojiMap.json +1915 -0
- package/build/common/error.d.ts +1 -10
- package/build/common/error.d.ts.map +1 -1
- package/build/common/error.js +1 -10
- package/build/common/fmt.d.ts +3 -15
- package/build/common/fmt.d.ts.map +1 -1
- package/build/common/fmt.js +7 -35
- package/build/common/http.d.ts +2 -7
- package/build/common/http.d.ts.map +1 -1
- package/build/common/http.js +8 -17
- package/build/common/icons.d.ts +20 -20
- package/build/common/icons.d.ts.map +1 -1
- package/build/common/icons.js +108 -67
- package/build/common/languageColors.d.ts +0 -1
- package/build/common/languageColors.d.ts.map +1 -1
- package/build/common/languageColors.js +1 -4
- package/build/common/languageColors.json +18 -2
- package/build/common/ops.d.ts +11 -17
- package/build/common/ops.d.ts.map +1 -1
- package/build/common/ops.js +22 -39
- package/build/common/render.d.ts +34 -82
- package/build/common/render.d.ts.map +1 -1
- package/build/common/render.js +225 -259
- package/build/common/retryer.d.ts +2 -7
- package/build/common/retryer.d.ts.map +1 -1
- package/build/common/retryer.js +4 -11
- package/build/fetchers/contributed-to.d.ts +62 -0
- package/build/fetchers/contributed-to.d.ts.map +1 -0
- package/build/fetchers/contributed-to.js +184 -0
- package/build/fetchers/gist.d.ts +2 -5
- package/build/fetchers/gist.d.ts.map +1 -1
- package/build/fetchers/gist.js +1 -5
- package/build/fetchers/graphql-error.d.ts +13 -0
- package/build/fetchers/graphql-error.d.ts.map +1 -0
- package/build/fetchers/graphql-error.js +29 -0
- package/build/fetchers/index.d.ts +13 -12
- package/build/fetchers/index.d.ts.map +1 -1
- package/build/fetchers/index.js +1 -0
- package/build/fetchers/repo.d.ts +2 -11
- package/build/fetchers/repo.d.ts.map +1 -1
- package/build/fetchers/repo.js +20 -33
- package/build/fetchers/stats.d.ts +16 -40
- package/build/fetchers/stats.d.ts.map +1 -1
- package/build/fetchers/stats.js +89 -271
- package/build/fetchers/top-languages.d.ts +2 -9
- package/build/fetchers/top-languages.d.ts.map +1 -1
- package/build/fetchers/top-languages.js +16 -47
- package/build/fetchers/types.d.ts +25 -1
- package/build/fetchers/types.d.ts.map +1 -1
- package/build/fetchers/wakatime.d.ts +2 -6
- package/build/fetchers/wakatime.d.ts.map +1 -1
- package/build/fetchers/wakatime.js +1 -5
- package/build/graphql/contributionsCollection.d.ts +9 -0
- package/build/graphql/contributionsCollection.d.ts.map +1 -0
- package/build/graphql/contributionsCollection.js +12 -0
- package/build/graphql/contributionsDocument.d.ts +6 -6
- package/build/graphql/contributionsDocument.d.ts.map +1 -1
- package/build/graphql/contributionsDocument.js +8 -16
- package/build/graphql/generated/contributed-to.d.ts +22 -0
- package/build/graphql/generated/contributed-to.d.ts.map +1 -0
- package/build/graphql/generated/contributed-to.js +11 -0
- package/build/graphql/generated/gist.d.ts +1 -1
- package/build/graphql/generated/repo.d.ts +1 -1
- package/build/graphql/generated/stats.d.ts +16 -4
- package/build/graphql/generated/stats.d.ts.map +1 -1
- package/build/graphql/generated/stats.js +2 -2
- package/build/graphql/generated/top-languages.d.ts +2 -2
- package/build/graphql/graphqlDocument.d.ts +1 -2
- package/build/graphql/graphqlDocument.d.ts.map +1 -1
- package/build/graphql/graphqlDocument.js +0 -1
- package/build/graphql/reposContributedToDocument.d.ts +4 -5
- package/build/graphql/reposContributedToDocument.d.ts.map +1 -1
- package/build/graphql/reposContributedToDocument.js +11 -7
- package/build/markup/css.d.ts +35 -0
- package/build/markup/css.d.ts.map +1 -0
- package/build/markup/css.js +64 -0
- package/build/markup/element.d.ts +41 -0
- package/build/markup/element.d.ts.map +1 -0
- package/build/markup/element.js +127 -0
- package/build/markup/html.d.ts +13 -0
- package/build/markup/html.d.ts.map +1 -0
- package/build/markup/html.js +27 -0
- package/build/markup/index.d.ts +9 -0
- package/build/markup/index.d.ts.map +1 -0
- package/build/markup/index.js +6 -0
- package/build/markup/serialize.d.ts +16 -0
- package/build/markup/serialize.d.ts.map +1 -0
- package/build/markup/serialize.js +17 -0
- package/build/themes/index.d.ts +332 -333
- package/build/themes/index.d.ts.map +1 -1
- package/build/themes/index.js +1 -1
- package/build/translations.d.ts +66 -7
- package/build/translations.d.ts.map +1 -1
- package/build/translations.js +686 -629
- package/package.json +17 -26
- package/src/api/api-result.ts +10 -9
- package/src/api/contributed-to.ts +84 -0
- package/src/api/gist.ts +22 -46
- package/src/api/handler.ts +42 -0
- package/src/api/index.ts +13 -12
- package/src/api/params.ts +74 -29
- package/src/api/pin.ts +38 -65
- package/src/api/stats.ts +81 -118
- package/src/api/top-langs.ts +38 -80
- package/src/api/wakatime.ts +35 -73
- package/src/calculateRank.ts +12 -23
- package/src/cards/contributed-to.ts +373 -0
- package/src/cards/gist.ts +55 -49
- package/src/cards/options.ts +1 -1
- package/src/cards/repo.ts +134 -120
- package/src/cards/stats.ts +262 -252
- package/src/cards/top-languages.ts +315 -422
- package/src/cards/wakatime.ts +207 -225
- package/src/common/Card.ts +207 -214
- package/src/common/I18n.ts +7 -8
- package/src/common/brand.ts +109 -0
- package/src/common/color.ts +80 -57
- package/src/common/config.ts +5 -18
- package/src/common/constants.ts +8 -2
- package/src/common/date.ts +135 -5
- package/src/common/emojiMap.json +1915 -0
- package/src/common/emojiMap.ts +13 -0
- package/src/common/error.ts +2 -11
- package/src/common/fmt.ts +7 -44
- package/src/common/http.ts +9 -15
- package/src/common/icons.ts +122 -42
- package/src/common/languageColors.json +18 -2
- package/src/common/languageColors.ts +1 -4
- package/src/common/ops.ts +31 -48
- package/src/common/render.ts +334 -284
- package/src/common/retryer.ts +8 -15
- package/src/fetchers/contributed-to.ts +271 -0
- package/src/fetchers/gist.ts +8 -12
- package/src/fetchers/graphql-error.ts +36 -0
- package/src/fetchers/index.ts +15 -12
- package/src/fetchers/repo.ts +27 -40
- package/src/fetchers/stats.ts +157 -366
- package/src/fetchers/top-languages.ts +27 -55
- package/src/fetchers/types.ts +27 -1
- package/src/fetchers/wakatime.ts +5 -9
- package/src/graphql/contributionsCollection.ts +18 -0
- package/src/graphql/contributionsDocument.ts +14 -19
- package/src/graphql/generated/contributed-to.ts +25 -0
- package/src/graphql/generated/gist.ts +1 -1
- package/src/graphql/generated/repo.ts +1 -1
- package/src/graphql/generated/stats.ts +21 -8
- package/src/graphql/generated/top-languages.ts +2 -2
- package/src/graphql/graphqlDocument.ts +1 -2
- package/src/graphql/queries/contributed-to.graphql +8 -0
- package/src/graphql/queries/stats.graphql +14 -2
- package/src/graphql/reposContributedToDocument.ts +16 -15
- package/src/markup/css.ts +118 -0
- package/src/markup/element.ts +246 -0
- package/src/markup/html.ts +34 -0
- package/src/markup/index.ts +9 -0
- package/src/markup/serialize.ts +27 -0
- package/src/themes/index.ts +1 -1
- package/src/translations.ts +699 -629
- package/build/cards/index.d.ts +0 -11
- package/build/cards/index.d.ts.map +0 -1
- package/build/cards/index.js +0 -7
- package/build/common/html.d.ts +0 -8
- package/build/common/html.d.ts.map +0 -1
- package/build/common/html.js +0 -14
- package/build/index.d.ts +0 -15
- package/build/index.d.ts.map +0 -1
- package/build/index.js +0 -10
- package/src/_emoji-name-map.d.ts +0 -10
- package/src/_github-username-regex.d.ts +0 -4
- package/src/cards/index.ts +0 -21
- package/src/common/html.ts +0 -17
- package/src/index.ts +0 -18
package/build/cards/stats.js
CHANGED
|
@@ -1,21 +1,52 @@
|
|
|
1
|
+
import { CARD_ICON, CARD_WIDTH, FONT_SIZE, FONT_WEIGHT, firefoxFontSize, font, } from '../common/brand.js';
|
|
1
2
|
import { Card } from '../common/Card.js';
|
|
2
3
|
import { getLightDarkColors } from '../common/color.js';
|
|
4
|
+
import { formatRange } from '../common/date.js';
|
|
3
5
|
import { CardError } from '../common/error.js';
|
|
4
6
|
import { I18n } from '../common/I18n.js';
|
|
5
7
|
import { icons, rankIcon } from '../common/icons.js';
|
|
6
8
|
import { buildSearchFilter, clampValue } from '../common/ops.js';
|
|
7
|
-
import { createTextNode, flexLayout, measureText } from '../common/render.js';
|
|
9
|
+
import { NUMBER_FORMATS, createTextNode, flexLayout, measureText } from '../common/render.js';
|
|
10
|
+
import { atRule, cssComment, el, rule } from '../markup/index.js';
|
|
8
11
|
import { statCardLocales, wakatimeCardLocales } from '../translations.js';
|
|
9
12
|
const CARD_MIN_WIDTH = 287;
|
|
10
|
-
const CARD_DEFAULT_WIDTH =
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
13
|
+
const CARD_DEFAULT_WIDTH = CARD_WIDTH.compact;
|
|
14
|
+
const RANK_CARD_DEFAULT_WIDTH = CARD_WIDTH.wide;
|
|
15
|
+
const RANK_ONLY_CARD_DEFAULT_WIDTH = CARD_WIDTH.compact;
|
|
16
|
+
const STAT_FONT_SIZE = FONT_SIZE.body;
|
|
17
|
+
/** Padding the card keeps at its edges; matches `Card`'s own `paddingX`. */
|
|
18
|
+
const CARD_PADDING_X = 25;
|
|
19
|
+
/** How far a stat row is translated into the card; see `createTextNode`. */
|
|
20
|
+
const STAT_ROW_X = 25;
|
|
21
|
+
/** Room the rank ring needs at the right edge, so a value never runs into it. */
|
|
22
|
+
const RANK_GUTTER = 120;
|
|
23
|
+
/** The ring's own `cx`, which its group's translation has to make up for. */
|
|
24
|
+
const RANK_CIRCLE_CX_OFFSET = 10;
|
|
25
|
+
/** `createTextNode`'s own label offset, which it applies only when icons are shown. */
|
|
26
|
+
const LABEL_X_OFFSET = 25;
|
|
27
|
+
/** Smallest gap kept between the longest label and its value. */
|
|
28
|
+
const LABEL_VALUE_GAP = 16;
|
|
15
29
|
/** Rank indicators the card can draw; the api validates `rank_icon` against this. */
|
|
16
30
|
const RANK_ICONS = ['default', 'github', 'percentile'];
|
|
31
|
+
/** Stats the card draws only when `show` names them. */
|
|
32
|
+
const SHOW_STATS = [
|
|
33
|
+
'contributions',
|
|
34
|
+
'prs_merged',
|
|
35
|
+
'prs_merged_percentage',
|
|
36
|
+
'reviews',
|
|
37
|
+
'discussions_started',
|
|
38
|
+
'discussions_answered',
|
|
39
|
+
'prs_authored',
|
|
40
|
+
'prs_commented',
|
|
41
|
+
'prs_reviewed',
|
|
42
|
+
'issues_authored',
|
|
43
|
+
'issues_commented',
|
|
44
|
+
'all_time_contribs',
|
|
45
|
+
];
|
|
46
|
+
/** Stats the card always draws, and so the ones `hide` has anything to remove. */
|
|
47
|
+
const HIDE_STATS = ['stars', 'commits', 'prs', 'issues', 'contribs'];
|
|
17
48
|
/** Long locales that need more space for text. Keep sorted alphabetically. */
|
|
18
|
-
const LONG_LOCALES = [
|
|
49
|
+
const LONG_LOCALES = new Set([
|
|
19
50
|
'az',
|
|
20
51
|
'bg',
|
|
21
52
|
'cs',
|
|
@@ -42,134 +73,92 @@ const LONG_LOCALES = [
|
|
|
42
73
|
'uk-ua',
|
|
43
74
|
'uz',
|
|
44
75
|
'zh-tw',
|
|
45
|
-
];
|
|
76
|
+
]);
|
|
46
77
|
/**
|
|
47
78
|
* Calculates progress along the boundary of the circle, i.e. its circumference.
|
|
48
79
|
*
|
|
49
|
-
* @param value The rank value to calculate progress for.
|
|
50
80
|
* @returns Progress value.
|
|
51
81
|
*/
|
|
52
82
|
const calculateCircleProgress = (value) => {
|
|
53
83
|
const radius = 40;
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
if (value > 100) {
|
|
59
|
-
value = 100;
|
|
60
|
-
}
|
|
61
|
-
return ((100 - value) / 100) * c;
|
|
84
|
+
const circumference = Math.PI * (radius * 2);
|
|
85
|
+
const clamped = Math.min(Math.max(value, 0), 100);
|
|
86
|
+
return ((100 - clamped) / 100) * circumference;
|
|
62
87
|
};
|
|
63
88
|
/**
|
|
64
89
|
* Retrieves the animation to display progress along the circumference of circle
|
|
65
90
|
* from the beginning to the given value in a clockwise direction.
|
|
66
91
|
*
|
|
67
|
-
* @param props The props object.
|
|
68
|
-
* @param props.progress The progress value to animate to.
|
|
69
92
|
* @returns Progress animation css.
|
|
70
93
|
*/
|
|
71
|
-
const getProgressAnimation = ({ progress }) => {
|
|
72
|
-
return `
|
|
73
|
-
@keyframes rankAnimation {
|
|
74
|
-
from {
|
|
75
|
-
stroke-dashoffset: ${calculateCircleProgress(0)};
|
|
76
|
-
}
|
|
77
|
-
to {
|
|
78
|
-
stroke-dashoffset: ${calculateCircleProgress(progress)};
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
`;
|
|
82
|
-
};
|
|
94
|
+
const getProgressAnimation = ({ progress }) => atRule('@keyframes rankAnimation', rule('from', { 'stroke-dashoffset': calculateCircleProgress(0) }), rule('to', { 'stroke-dashoffset': calculateCircleProgress(progress) }));
|
|
83
95
|
/**
|
|
84
96
|
* Retrieves CSS styles for a card.
|
|
85
97
|
*
|
|
86
|
-
* @param colors The colors to use for the card.
|
|
87
|
-
* @param colors.textColor The text color.
|
|
88
|
-
* @param colors.iconColor The icon color.
|
|
89
|
-
* @param colors.ringColor The ring color.
|
|
90
|
-
* @param colors.show_icons Whether to show icons.
|
|
91
|
-
* @param colors.progress The progress value to animate to.
|
|
92
98
|
* @returns Card CSS styles.
|
|
93
99
|
*/
|
|
94
|
-
const getStyles = ({ textColor, iconColor, ringColor, show_icons, progress, }) =>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
.rank-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
stroke-linecap: round;
|
|
137
|
-
opacity: 0.8;
|
|
138
|
-
transform-origin: -10px 8px;
|
|
139
|
-
transform: rotate(-90deg);
|
|
140
|
-
animation: rankAnimation 1s forwards ease-in-out;
|
|
141
|
-
}
|
|
142
|
-
${getProgressAnimation({ progress })}
|
|
143
|
-
`;
|
|
144
|
-
};
|
|
100
|
+
const getStyles = ({ textColor, iconColor, ringColor, show_icons, progress, }) => [
|
|
101
|
+
rule('.stat', {
|
|
102
|
+
font: font('regular', 'body'),
|
|
103
|
+
fill: textColor,
|
|
104
|
+
'font-variant-numeric': 'tabular-nums',
|
|
105
|
+
}),
|
|
106
|
+
firefoxFontSize(['.stat'], 'small'),
|
|
107
|
+
rule('.stagger', { opacity: 0, animation: 'fadeInAnimation 0.3s ease-in-out forwards' }),
|
|
108
|
+
rule('.rank-text', {
|
|
109
|
+
font: font('bold', 'display'),
|
|
110
|
+
fill: textColor,
|
|
111
|
+
animation: 'scaleInAnimation 0.3s ease-in-out forwards',
|
|
112
|
+
}),
|
|
113
|
+
rule('.rank-percentile-header', { 'font-size': `${String(FONT_SIZE.body)}px` }),
|
|
114
|
+
rule('.rank-percentile-text', { 'font-size': `${String(FONT_SIZE.lead)}px` }),
|
|
115
|
+
cssComment('Labels recede so that the values read first.'),
|
|
116
|
+
rule('.not_bold', { 'font-weight': FONT_WEIGHT.regular, opacity: 0.75 }),
|
|
117
|
+
rule('.bold', { 'font-weight': FONT_WEIGHT.semibold }),
|
|
118
|
+
rule('.icon', {
|
|
119
|
+
fill: iconColor,
|
|
120
|
+
opacity: 0.75,
|
|
121
|
+
display: show_icons ? 'block' : 'none',
|
|
122
|
+
}),
|
|
123
|
+
rule('.rank-circle-rim', {
|
|
124
|
+
stroke: ringColor,
|
|
125
|
+
fill: 'none',
|
|
126
|
+
'stroke-width': 4,
|
|
127
|
+
opacity: 0.15,
|
|
128
|
+
}),
|
|
129
|
+
rule('.rank-circle', {
|
|
130
|
+
stroke: ringColor,
|
|
131
|
+
'stroke-dasharray': 250,
|
|
132
|
+
fill: 'none',
|
|
133
|
+
'stroke-width': 4,
|
|
134
|
+
'stroke-linecap': 'round',
|
|
135
|
+
opacity: 1,
|
|
136
|
+
'transform-origin': '-10px 8px',
|
|
137
|
+
transform: 'rotate(-90deg)',
|
|
138
|
+
animation: 'rankAnimation 0.8s forwards ease-in-out',
|
|
139
|
+
}),
|
|
140
|
+
getProgressAnimation({ progress }),
|
|
141
|
+
];
|
|
145
142
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
* @param include_all_commits Option to include all years
|
|
149
|
-
* @param commits_year Option to include only selected year
|
|
150
|
-
* @param i18n The I18n instance.
|
|
151
|
-
* @returns The label corresponding to the options.
|
|
143
|
+
* @returns What the commit count covers, for the stat's label.
|
|
152
144
|
*/
|
|
153
|
-
const
|
|
145
|
+
const getTotalCommitsRangeLabel = (include_all_commits, commitsRange, i18n) => include_all_commits
|
|
154
146
|
? ''
|
|
155
|
-
:
|
|
156
|
-
? ` (${
|
|
147
|
+
: commitsRange
|
|
148
|
+
? ` (${formatRange(commitsRange)})`
|
|
157
149
|
: ` (${i18n.t('wakatimecard.lastyear')})`;
|
|
158
150
|
/**
|
|
159
151
|
* Renders the stats card.
|
|
160
152
|
*
|
|
161
|
-
* @param stats The stats data.
|
|
162
|
-
* @param options The card options.
|
|
163
|
-
* @param username GitHub username, used to build stat search links.
|
|
164
|
-
* @param repo Repositories to scope the search links to.
|
|
165
|
-
* @param owner Owners to scope the search links to.
|
|
166
153
|
* @returns The stats card SVG object.
|
|
167
154
|
*/
|
|
168
|
-
const
|
|
169
|
-
const { name, totalStars, totalCommits, totalIssues, totalPRs, totalPRsMerged, mergedPRsPercentage, totalReviews, totalDiscussionsStarted, totalDiscussionsAnswered, contributedTo, allTimeContributedTo, totalPRsAuthored, totalPRsCommented, totalPRsReviewed, totalIssuesAuthored, totalIssuesCommented, totalContributions, rank, } = stats;
|
|
170
|
-
const { hide = [], show_icons = false, hide_title = false, hide_border = false, card_width, hide_rank = false, include_all_commits = false,
|
|
171
|
-
const lheight = parseInt(String(line_height), 10);
|
|
155
|
+
const renderCard = (stats, options = {}, username, repo = [], owner = []) => {
|
|
156
|
+
const { name, totalStars, totalCommits, commitsRange, totalIssues, totalPRs, totalPRsMerged, mergedPRsPercentage, totalReviews, totalDiscussionsStarted, totalDiscussionsAnswered, contributedTo, allTimeContributedTo, totalPRsAuthored, totalPRsCommented, totalPRsReviewed, totalIssuesAuthored, totalIssuesCommented, totalContributions, rank, } = stats;
|
|
157
|
+
const { hide = [], show_icons = false, hide_title = false, hide_border = false, card_width, hide_rank = false, include_all_commits = false, line_height = 25, text_bold = true, custom_title, border_radius, number_format = 'short', number_precision, locale, disable_animations = false, rank_icon = 'default', show = [], } = options;
|
|
158
|
+
const lheight = Number.parseInt(String(line_height), 10);
|
|
172
159
|
const { lightColors, darkColors } = getLightDarkColors(options);
|
|
160
|
+
// Typed against the exported list, so a stat drawn here cannot be missing from it.
|
|
161
|
+
const shows = (stat) => show.includes(stat);
|
|
173
162
|
const apostrophe = /s$/i.test(name.trim()) ? '' : 's';
|
|
174
163
|
const i18n = new I18n({
|
|
175
164
|
locale,
|
|
@@ -179,14 +168,15 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
179
168
|
},
|
|
180
169
|
});
|
|
181
170
|
// Meta data for creating text nodes with createTextNode function
|
|
182
|
-
const STATS = {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
171
|
+
const STATS = {
|
|
172
|
+
stars: {
|
|
173
|
+
icon: icons.star,
|
|
174
|
+
label: i18n.t('statcard.totalstars'),
|
|
175
|
+
value: totalStars,
|
|
176
|
+
id: 'stars',
|
|
177
|
+
},
|
|
188
178
|
};
|
|
189
|
-
if (
|
|
179
|
+
if (shows('contributions')) {
|
|
190
180
|
STATS['contributions'] = {
|
|
191
181
|
icon: icons.contributions,
|
|
192
182
|
label: i18n.t('statcard.contributions'),
|
|
@@ -196,7 +186,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
196
186
|
}
|
|
197
187
|
STATS['commits'] = {
|
|
198
188
|
icon: icons.commits,
|
|
199
|
-
label: `${i18n.t('statcard.commits')}${
|
|
189
|
+
label: `${i18n.t('statcard.commits')}${getTotalCommitsRangeLabel(include_all_commits, commitsRange, i18n)}`,
|
|
200
190
|
value: totalCommits,
|
|
201
191
|
id: 'commits',
|
|
202
192
|
};
|
|
@@ -206,7 +196,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
206
196
|
value: totalPRs,
|
|
207
197
|
id: 'prs',
|
|
208
198
|
};
|
|
209
|
-
if (
|
|
199
|
+
if (shows('prs_merged')) {
|
|
210
200
|
STATS['prs_merged'] = {
|
|
211
201
|
icon: icons.prs_merged,
|
|
212
202
|
label: i18n.t('statcard.prs-merged'),
|
|
@@ -214,7 +204,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
214
204
|
id: 'prs_merged',
|
|
215
205
|
};
|
|
216
206
|
}
|
|
217
|
-
if (
|
|
207
|
+
if (shows('prs_merged_percentage')) {
|
|
218
208
|
STATS['prs_merged_percentage'] = {
|
|
219
209
|
icon: icons.prs_merged_percentage,
|
|
220
210
|
label: i18n.t('statcard.prs-merged-percentage'),
|
|
@@ -225,7 +215,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
225
215
|
unitSymbol: '%',
|
|
226
216
|
};
|
|
227
217
|
}
|
|
228
|
-
if (
|
|
218
|
+
if (shows('reviews')) {
|
|
229
219
|
STATS['reviews'] = {
|
|
230
220
|
icon: icons.reviews,
|
|
231
221
|
label: i18n.t('statcard.reviews'),
|
|
@@ -239,7 +229,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
239
229
|
value: totalIssues,
|
|
240
230
|
id: 'issues',
|
|
241
231
|
};
|
|
242
|
-
if (
|
|
232
|
+
if (shows('discussions_started')) {
|
|
243
233
|
STATS['discussions_started'] = {
|
|
244
234
|
icon: icons.discussions_started,
|
|
245
235
|
label: i18n.t('statcard.discussions-started'),
|
|
@@ -247,7 +237,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
247
237
|
id: 'discussions_started',
|
|
248
238
|
};
|
|
249
239
|
}
|
|
250
|
-
if (
|
|
240
|
+
if (shows('discussions_answered')) {
|
|
251
241
|
STATS['discussions_answered'] = {
|
|
252
242
|
icon: icons.discussions_answered,
|
|
253
243
|
label: i18n.t('statcard.discussions-answered'),
|
|
@@ -257,7 +247,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
257
247
|
}
|
|
258
248
|
const repoFilter = encodeURIComponent(buildSearchFilter(repo, owner));
|
|
259
249
|
const encodedUsername = encodeURIComponent(username ?? '');
|
|
260
|
-
if (
|
|
250
|
+
if (shows('prs_authored')) {
|
|
261
251
|
STATS['prs_authored'] = {
|
|
262
252
|
icon: icons.prs,
|
|
263
253
|
label: i18n.t('statcard.prs-authored'),
|
|
@@ -266,7 +256,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
266
256
|
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=pullrequests`,
|
|
267
257
|
};
|
|
268
258
|
}
|
|
269
|
-
if (
|
|
259
|
+
if (shows('prs_commented')) {
|
|
270
260
|
STATS['prs_commented'] = {
|
|
271
261
|
icon: icons.comments,
|
|
272
262
|
label: i18n.t('statcard.prs-commented'),
|
|
@@ -275,7 +265,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
275
265
|
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
276
266
|
};
|
|
277
267
|
}
|
|
278
|
-
if (
|
|
268
|
+
if (shows('prs_reviewed')) {
|
|
279
269
|
STATS['prs_reviewed'] = {
|
|
280
270
|
icon: icons.reviews,
|
|
281
271
|
label: i18n.t('statcard.prs-reviewed'),
|
|
@@ -284,7 +274,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
284
274
|
link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
285
275
|
};
|
|
286
276
|
}
|
|
287
|
-
if (
|
|
277
|
+
if (shows('issues_authored')) {
|
|
288
278
|
STATS['issues_authored'] = {
|
|
289
279
|
icon: icons.issues,
|
|
290
280
|
label: i18n.t('statcard.issues-authored'),
|
|
@@ -293,7 +283,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
293
283
|
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=issues`,
|
|
294
284
|
};
|
|
295
285
|
}
|
|
296
|
-
if (
|
|
286
|
+
if (shows('issues_commented')) {
|
|
297
287
|
STATS['issues_commented'] = {
|
|
298
288
|
icon: icons.discussions_started,
|
|
299
289
|
label: i18n.t('statcard.issues-commented'),
|
|
@@ -303,29 +293,59 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
303
293
|
};
|
|
304
294
|
}
|
|
305
295
|
STATS['contribs'] = {
|
|
306
|
-
icon: icons.
|
|
296
|
+
icon: icons.repo,
|
|
307
297
|
label: i18n.t('statcard.contribs'),
|
|
308
298
|
value: contributedTo,
|
|
309
299
|
id: 'contribs',
|
|
310
300
|
};
|
|
311
|
-
if (
|
|
301
|
+
if (shows('all_time_contribs')) {
|
|
312
302
|
STATS['all_time_contribs'] = {
|
|
313
|
-
icon: icons.
|
|
303
|
+
icon: icons.repo,
|
|
314
304
|
label: i18n.t('statcard.all-time-contribs'),
|
|
315
305
|
value: allTimeContributedTo,
|
|
316
306
|
id: 'all_time_contribs',
|
|
317
307
|
};
|
|
318
308
|
}
|
|
319
|
-
const isLongLocale = locale ? LONG_LOCALES.
|
|
309
|
+
const isLongLocale = locale ? LONG_LOCALES.has(locale) : false;
|
|
310
|
+
// filter out hidden stats defined by user
|
|
311
|
+
const visibleStats = Object.entries(STATS).filter(([key]) => !hide.includes(key));
|
|
312
|
+
if (visibleStats.length === 0 && hide_rank) {
|
|
313
|
+
throw new CardError('Could not render stats card.', {
|
|
314
|
+
code: 'invalid_param',
|
|
315
|
+
secondaryMessage: 'Either stats or rank are required.',
|
|
316
|
+
});
|
|
317
|
+
}
|
|
320
318
|
// check if all used labels are short
|
|
321
|
-
const longLabels =
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
//
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
319
|
+
const longLabels = visibleStats.some(([, stat]) => stat.label.length > 18);
|
|
320
|
+
// The rank ring sets the floor: 150 beside the stats, 180 when it is the whole card.
|
|
321
|
+
const height = Math.max(45 + (visibleStats.length + 1) * lheight, hide_rank ? 0 : visibleStats.length > 0 ? 150 : 180);
|
|
322
|
+
// the lower the user's percentile the better
|
|
323
|
+
const progress = 100 - rank.percentile;
|
|
324
|
+
const calculateTextWidth = () => measureText(custom_title ||
|
|
325
|
+
(visibleStats.length > 0 ? i18n.t('statcard.title') : i18n.t('statcard.ranktitle')));
|
|
326
|
+
const iconWidth = show_icons && visibleStats.length > 0 ? 16 + /* padding */ 1 : 0;
|
|
327
|
+
// The icons ride inside the step's own slack, so a default card lands on the width grid;
|
|
328
|
+
// only the minimum, which has no slack, still has to make room for them.
|
|
329
|
+
const defaultCardWidth = hide_rank
|
|
330
|
+
? CARD_DEFAULT_WIDTH
|
|
331
|
+
: visibleStats.length > 0
|
|
332
|
+
? RANK_CARD_DEFAULT_WIDTH
|
|
333
|
+
: RANK_ONLY_CARD_DEFAULT_WIDTH;
|
|
334
|
+
// Only a card sized by its own title can outgrow its step: the ranked layouts are fixed,
|
|
335
|
+
// so their step is also their floor.
|
|
336
|
+
const fittedWidth = hide_rank
|
|
337
|
+
? Math.max(defaultCardWidth, clampValue(50 /* padding */ + calculateTextWidth() * 2, CARD_MIN_WIDTH, Infinity) +
|
|
338
|
+
iconWidth)
|
|
339
|
+
: defaultCardWidth;
|
|
340
|
+
const width = card_width ? (Number.isNaN(card_width) ? fittedWidth : card_width) : fittedWidth;
|
|
341
|
+
// A value ends at the card's inner edge, clear of the rank ring — or right after
|
|
342
|
+
// the longest label, on a card too narrow for that.
|
|
343
|
+
const widestLabel = visibleStats.length > 0
|
|
344
|
+
? Math.max(...visibleStats.map(([, stat]) => measureText(`${stat.label}:`, STAT_FONT_SIZE)))
|
|
345
|
+
: 0;
|
|
346
|
+
const valueAnchorX = Math.round(Math.max(width - CARD_PADDING_X - STAT_ROW_X - (hide_rank ? 0 : RANK_GUTTER), (show_icons ? LABEL_X_OFFSET : 0) + widestLabel + LABEL_VALUE_GAP));
|
|
347
|
+
// pass index so that we can calculate the line spacing
|
|
348
|
+
const statItems = visibleStats.map(([, stat], index) => createTextNode({
|
|
329
349
|
icon: stat.icon,
|
|
330
350
|
label: stat.label,
|
|
331
351
|
value: stat.value,
|
|
@@ -334,53 +354,17 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
334
354
|
index,
|
|
335
355
|
showIcons: show_icons,
|
|
336
356
|
shiftValuePos: 29.01 + (longLabels ? 50 : 0) + (isLongLocale ? 50 : 0),
|
|
357
|
+
valueAnchorX,
|
|
337
358
|
bold: text_bold,
|
|
359
|
+
labelBold: false,
|
|
338
360
|
numberFormat: number_format,
|
|
339
361
|
numberPrecision: number_precision,
|
|
340
362
|
link: stat.link,
|
|
341
363
|
}));
|
|
342
|
-
if (statItems.length === 0 && hide_rank) {
|
|
343
|
-
throw new CardError('Could not render stats card.', {
|
|
344
|
-
code: 'invalid_param',
|
|
345
|
-
secondaryMessage: 'Either stats or rank are required.',
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
// Calculate the card height depending on how many items there are
|
|
349
|
-
// but if rank circle is visible clamp the minimum height to `150`
|
|
350
|
-
const height = Math.max(45 + (statItems.length + 1) * lheight, hide_rank ? 0 : statItems.length ? 150 : 180);
|
|
351
|
-
// the lower the user's percentile the better
|
|
352
|
-
const progress = 100 - rank.percentile;
|
|
353
|
-
const calculateTextWidth = () => {
|
|
354
|
-
return measureText(custom_title
|
|
355
|
-
? custom_title
|
|
356
|
-
: statItems.length
|
|
357
|
-
? i18n.t('statcard.title')
|
|
358
|
-
: i18n.t('statcard.ranktitle'));
|
|
359
|
-
};
|
|
360
|
-
/*
|
|
361
|
-
When hide_rank=true, the minimum card width is 270 px + the title length and padding.
|
|
362
|
-
When hide_rank=false, the minimum card_width is 340 px + the icon width (if show_icons=true).
|
|
363
|
-
Numbers are picked by looking at existing dimensions on production.
|
|
364
|
-
*/
|
|
365
|
-
const iconWidth = show_icons && statItems.length ? 16 + /* padding */ 1 : 0;
|
|
366
|
-
const minCardWidth = (hide_rank
|
|
367
|
-
? clampValue(50 /* padding */ + calculateTextWidth() * 2, CARD_MIN_WIDTH, Infinity)
|
|
368
|
-
: statItems.length
|
|
369
|
-
? RANK_CARD_MIN_WIDTH
|
|
370
|
-
: RANK_ONLY_CARD_MIN_WIDTH) + iconWidth;
|
|
371
|
-
const defaultCardWidth = (hide_rank
|
|
372
|
-
? CARD_DEFAULT_WIDTH
|
|
373
|
-
: statItems.length
|
|
374
|
-
? RANK_CARD_DEFAULT_WIDTH
|
|
375
|
-
: RANK_ONLY_CARD_DEFAULT_WIDTH) + iconWidth;
|
|
376
|
-
const width = card_width
|
|
377
|
-
? isNaN(card_width)
|
|
378
|
-
? Math.max(defaultCardWidth, minCardWidth)
|
|
379
|
-
: card_width
|
|
380
|
-
: Math.max(defaultCardWidth, minCardWidth);
|
|
381
364
|
const card = new Card({
|
|
382
365
|
customTitle: custom_title,
|
|
383
|
-
defaultTitle:
|
|
366
|
+
defaultTitle: visibleStats.length > 0 ? i18n.t('statcard.title') : i18n.t('statcard.ranktitle'),
|
|
367
|
+
titlePrefixIcon: CARD_ICON.stats,
|
|
384
368
|
width,
|
|
385
369
|
height,
|
|
386
370
|
border_radius,
|
|
@@ -394,64 +378,49 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
|
|
|
394
378
|
card.disableAnimations();
|
|
395
379
|
}
|
|
396
380
|
/**
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
* width > RANK_CARD_DEFAULT_WIDTH: The default right padding of 70 px will be used.
|
|
401
|
-
* width < RANK_CARD_DEFAULT_WIDTH: The left and right padding will be enlarged
|
|
402
|
-
* equally from a certain minimum at RANK_CARD_MIN_WIDTH.
|
|
381
|
+
* The ring sits centred in the same gutter the values are kept clear of, so the gap
|
|
382
|
+
* between them holds at every width; a rank-only card has no values, so it centres.
|
|
403
383
|
*
|
|
404
384
|
* @returns Rank circle translation value.
|
|
405
385
|
*/
|
|
406
|
-
const calculateRankXTranslation = () =>
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (width > RANK_CARD_DEFAULT_WIDTH) {
|
|
410
|
-
const xMaxExpansion = minXTranslation + (450 - minCardWidth) / 2;
|
|
411
|
-
return xMaxExpansion + width - RANK_CARD_DEFAULT_WIDTH;
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
return minXTranslation + (width - minCardWidth) / 2;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
else {
|
|
418
|
-
return width / 2 + 20 - 10;
|
|
419
|
-
}
|
|
420
|
-
};
|
|
386
|
+
const calculateRankXTranslation = () => visibleStats.length > 0
|
|
387
|
+
? width - CARD_PADDING_X - RANK_GUTTER / 2 + RANK_CIRCLE_CX_OFFSET
|
|
388
|
+
: width / 2 + 20 - 10;
|
|
421
389
|
// Conditionally rendered elements
|
|
422
|
-
const rankCircle = hide_rank
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
|
428
|
-
<g class="rank-text">
|
|
429
|
-
${rankIcon(rank_icon, rank.level, rank.percentile)}
|
|
430
|
-
</g>
|
|
431
|
-
</g>`;
|
|
390
|
+
const rankCircle = !hide_rank &&
|
|
391
|
+
el('g', {
|
|
392
|
+
'data-testid': 'rank-circle',
|
|
393
|
+
transform: `translate(${calculateRankXTranslation()}, ${height / 2 - 50})`,
|
|
394
|
+
}, el('circle', { class: 'rank-circle-rim', cx: -RANK_CIRCLE_CX_OFFSET, cy: 8, r: 40 }), el('circle', { class: 'rank-circle', cx: -RANK_CIRCLE_CX_OFFSET, cy: 8, r: 40 }), el('g', { class: 'rank-text' }, rankIcon(rank_icon, rank.level, rank.percentile)));
|
|
432
395
|
// Accessibility Labels
|
|
433
|
-
const labels =
|
|
434
|
-
.filter(([key]) => !hide.includes(key))
|
|
396
|
+
const labels = visibleStats
|
|
435
397
|
.map(([key, stat]) => {
|
|
436
398
|
if (key === 'commits') {
|
|
437
|
-
return `${i18n.t('statcard.commits')} ${
|
|
399
|
+
return `${i18n.t('statcard.commits')} ${getTotalCommitsRangeLabel(include_all_commits, commitsRange, i18n)} : ${stat.value}`;
|
|
438
400
|
}
|
|
439
401
|
return `${stat.label}: ${stat.value}`;
|
|
440
402
|
})
|
|
441
403
|
.join(', ');
|
|
442
404
|
card.setAccessibilityLabel({
|
|
443
|
-
title: `${card.title},
|
|
405
|
+
title: `${card.title}, ${i18n.t('statcard.rank')}: ${rank.level}`,
|
|
444
406
|
desc: labels,
|
|
445
407
|
});
|
|
446
|
-
return card.render(
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
items: statItems,
|
|
451
|
-
gap: lheight,
|
|
452
|
-
direction: 'column',
|
|
453
|
-
}).join('')}
|
|
454
|
-
</svg>
|
|
455
|
-
`);
|
|
408
|
+
return card.render([
|
|
409
|
+
rankCircle,
|
|
410
|
+
el('svg', { x: 0, y: 0 }, flexLayout({ items: statItems, gap: lheight, direction: 'column' })),
|
|
411
|
+
]);
|
|
456
412
|
};
|
|
457
|
-
|
|
413
|
+
/**
|
|
414
|
+
* The card, and the values each of its options accepts, keyed by the option's own name.
|
|
415
|
+
* They ride on the renderer so a list cannot be found without what draws it;
|
|
416
|
+
* the api handler forwards them onto its own export, which is what a UI reads.
|
|
417
|
+
*/
|
|
418
|
+
const renderStatsCard = Object.assign(renderCard, {
|
|
419
|
+
OPTIONS: {
|
|
420
|
+
rank_icon: RANK_ICONS,
|
|
421
|
+
show: SHOW_STATS,
|
|
422
|
+
hide: HIDE_STATS,
|
|
423
|
+
number_format: NUMBER_FORMATS,
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
export { renderStatsCard };
|