@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/src/cards/stats.ts
CHANGED
|
@@ -1,27 +1,75 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
import type {
|
|
1
|
+
import {
|
|
2
|
+
CARD_ICON,
|
|
3
|
+
CARD_WIDTH,
|
|
4
|
+
FONT_SIZE,
|
|
5
|
+
FONT_WEIGHT,
|
|
6
|
+
firefoxFontSize,
|
|
7
|
+
font,
|
|
8
|
+
} from '../common/brand.ts';
|
|
9
|
+
import { Card } from '../common/Card.ts';
|
|
10
|
+
import { getLightDarkColors } from '../common/color.ts';
|
|
11
|
+
import type { CardColors } from '../common/color.ts';
|
|
12
|
+
import type { GitHubDateRange } from '../common/date.ts';
|
|
13
|
+
import { formatRange } from '../common/date.ts';
|
|
14
|
+
import { CardError } from '../common/error.ts';
|
|
15
|
+
import { I18n } from '../common/I18n.ts';
|
|
16
|
+
import { icons, rankIcon } from '../common/icons.ts';
|
|
17
|
+
import { buildSearchFilter, clampValue } from '../common/ops.ts';
|
|
18
|
+
import { NUMBER_FORMATS, createTextNode, flexLayout, measureText } from '../common/render.ts';
|
|
19
|
+
import type { StatsData } from '../fetchers/types.ts';
|
|
20
|
+
import type { Child, CssChild } from '../markup/index.ts';
|
|
21
|
+
import { atRule, cssComment, el, rule } from '../markup/index.ts';
|
|
22
|
+
import { statCardLocales, wakatimeCardLocales } from '../translations.ts';
|
|
23
|
+
|
|
24
|
+
import type { CardOptions, CommonCardOptions } from './options.ts';
|
|
13
25
|
|
|
14
26
|
const CARD_MIN_WIDTH = 287;
|
|
15
|
-
const CARD_DEFAULT_WIDTH =
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
const
|
|
27
|
+
const CARD_DEFAULT_WIDTH = CARD_WIDTH.compact;
|
|
28
|
+
const RANK_CARD_DEFAULT_WIDTH = CARD_WIDTH.wide;
|
|
29
|
+
const RANK_ONLY_CARD_DEFAULT_WIDTH = CARD_WIDTH.compact;
|
|
30
|
+
|
|
31
|
+
const STAT_FONT_SIZE = FONT_SIZE.body;
|
|
32
|
+
/** Padding the card keeps at its edges; matches `Card`'s own `paddingX`. */
|
|
33
|
+
const CARD_PADDING_X = 25;
|
|
34
|
+
/** How far a stat row is translated into the card; see `createTextNode`. */
|
|
35
|
+
const STAT_ROW_X = 25;
|
|
36
|
+
/** Room the rank ring needs at the right edge, so a value never runs into it. */
|
|
37
|
+
const RANK_GUTTER = 120;
|
|
38
|
+
/** The ring's own `cx`, which its group's translation has to make up for. */
|
|
39
|
+
const RANK_CIRCLE_CX_OFFSET = 10;
|
|
40
|
+
/** `createTextNode`'s own label offset, which it applies only when icons are shown. */
|
|
41
|
+
const LABEL_X_OFFSET = 25;
|
|
42
|
+
/** Smallest gap kept between the longest label and its value. */
|
|
43
|
+
const LABEL_VALUE_GAP = 16;
|
|
20
44
|
|
|
21
45
|
/** Rank indicators the card can draw; the api validates `rank_icon` against this. */
|
|
22
46
|
const RANK_ICONS = ['default', 'github', 'percentile'] as const;
|
|
23
47
|
type RankIcon = (typeof RANK_ICONS)[number];
|
|
24
48
|
|
|
49
|
+
/** Stats the card draws only when `show` names them. */
|
|
50
|
+
const SHOW_STATS = [
|
|
51
|
+
'contributions',
|
|
52
|
+
'prs_merged',
|
|
53
|
+
'prs_merged_percentage',
|
|
54
|
+
'reviews',
|
|
55
|
+
'discussions_started',
|
|
56
|
+
'discussions_answered',
|
|
57
|
+
'prs_authored',
|
|
58
|
+
'prs_commented',
|
|
59
|
+
'prs_reviewed',
|
|
60
|
+
'issues_authored',
|
|
61
|
+
'issues_commented',
|
|
62
|
+
'all_time_contribs',
|
|
63
|
+
] as const;
|
|
64
|
+
type ShowStat = (typeof SHOW_STATS)[number];
|
|
65
|
+
|
|
66
|
+
/** Stats the card always draws, and so the ones `hide` has anything to remove. */
|
|
67
|
+
const HIDE_STATS = ['stars', 'commits', 'prs', 'issues', 'contribs'] as const;
|
|
68
|
+
type HideStat = (typeof HIDE_STATS)[number];
|
|
69
|
+
|
|
70
|
+
/** Every stat the card can draw: the ones it always draws, plus the ones `show` adds. */
|
|
71
|
+
type StatId = HideStat | ShowStat;
|
|
72
|
+
|
|
25
73
|
interface StatCardOptions extends CommonCardOptions {
|
|
26
74
|
locale: string;
|
|
27
75
|
hide: Array<string>;
|
|
@@ -30,7 +78,6 @@ interface StatCardOptions extends CommonCardOptions {
|
|
|
30
78
|
card_width: number;
|
|
31
79
|
hide_rank: boolean;
|
|
32
80
|
include_all_commits: boolean;
|
|
33
|
-
commits_year: number;
|
|
34
81
|
line_height: number | string;
|
|
35
82
|
custom_title: string;
|
|
36
83
|
disable_animations: boolean;
|
|
@@ -44,7 +91,7 @@ interface StatCardOptions extends CommonCardOptions {
|
|
|
44
91
|
|
|
45
92
|
/** Meta data for a stat, used to build its text node and accessibility label. */
|
|
46
93
|
interface StatItem {
|
|
47
|
-
icon:
|
|
94
|
+
icon: Child;
|
|
48
95
|
label: string;
|
|
49
96
|
value: number | string;
|
|
50
97
|
id: string;
|
|
@@ -53,7 +100,7 @@ interface StatItem {
|
|
|
53
100
|
}
|
|
54
101
|
|
|
55
102
|
/** Long locales that need more space for text. Keep sorted alphabetically. */
|
|
56
|
-
const LONG_LOCALES:
|
|
103
|
+
const LONG_LOCALES: ReadonlySet<string> = new Set([
|
|
57
104
|
'az',
|
|
58
105
|
'bg',
|
|
59
106
|
'cs',
|
|
@@ -80,58 +127,37 @@ const LONG_LOCALES: Array<string> = [
|
|
|
80
127
|
'uk-ua',
|
|
81
128
|
'uz',
|
|
82
129
|
'zh-tw',
|
|
83
|
-
];
|
|
130
|
+
]);
|
|
84
131
|
|
|
85
132
|
/**
|
|
86
133
|
* Calculates progress along the boundary of the circle, i.e. its circumference.
|
|
87
134
|
*
|
|
88
|
-
* @param value The rank value to calculate progress for.
|
|
89
135
|
* @returns Progress value.
|
|
90
136
|
*/
|
|
91
137
|
const calculateCircleProgress = (value: number): number => {
|
|
92
138
|
const radius = 40;
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
if (value < 0) {
|
|
96
|
-
value = 0;
|
|
97
|
-
}
|
|
98
|
-
if (value > 100) {
|
|
99
|
-
value = 100;
|
|
100
|
-
}
|
|
139
|
+
const circumference = Math.PI * (radius * 2);
|
|
140
|
+
const clamped = Math.min(Math.max(value, 0), 100);
|
|
101
141
|
|
|
102
|
-
return ((100 -
|
|
142
|
+
return ((100 - clamped) / 100) * circumference;
|
|
103
143
|
};
|
|
104
144
|
|
|
105
145
|
/**
|
|
106
146
|
* Retrieves the animation to display progress along the circumference of circle
|
|
107
147
|
* from the beginning to the given value in a clockwise direction.
|
|
108
148
|
*
|
|
109
|
-
* @param props The props object.
|
|
110
|
-
* @param props.progress The progress value to animate to.
|
|
111
149
|
* @returns Progress animation css.
|
|
112
150
|
*/
|
|
113
|
-
const getProgressAnimation = ({ progress }: { progress: number }):
|
|
114
|
-
|
|
115
|
-
@keyframes rankAnimation
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
to {
|
|
120
|
-
stroke-dashoffset: ${calculateCircleProgress(progress)};
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
`;
|
|
124
|
-
};
|
|
151
|
+
const getProgressAnimation = ({ progress }: { progress: number }): CssChild =>
|
|
152
|
+
atRule(
|
|
153
|
+
'@keyframes rankAnimation',
|
|
154
|
+
rule('from', { 'stroke-dashoffset': calculateCircleProgress(0) }),
|
|
155
|
+
rule('to', { 'stroke-dashoffset': calculateCircleProgress(progress) }),
|
|
156
|
+
);
|
|
125
157
|
|
|
126
158
|
/**
|
|
127
159
|
* Retrieves CSS styles for a card.
|
|
128
160
|
*
|
|
129
|
-
* @param colors The colors to use for the card.
|
|
130
|
-
* @param colors.textColor The text color.
|
|
131
|
-
* @param colors.iconColor The icon color.
|
|
132
|
-
* @param colors.ringColor The ring color.
|
|
133
|
-
* @param colors.show_icons Whether to show icons.
|
|
134
|
-
* @param colors.progress The progress value to animate to.
|
|
135
161
|
* @returns Card CSS styles.
|
|
136
162
|
*/
|
|
137
163
|
const getStyles = ({
|
|
@@ -146,88 +172,69 @@ const getStyles = ({
|
|
|
146
172
|
ringColor: string;
|
|
147
173
|
show_icons: boolean;
|
|
148
174
|
progress: number;
|
|
149
|
-
}):
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
stroke-linecap: round;
|
|
192
|
-
opacity: 0.8;
|
|
193
|
-
transform-origin: -10px 8px;
|
|
194
|
-
transform: rotate(-90deg);
|
|
195
|
-
animation: rankAnimation 1s forwards ease-in-out;
|
|
196
|
-
}
|
|
197
|
-
${getProgressAnimation({ progress })}
|
|
198
|
-
`;
|
|
199
|
-
};
|
|
175
|
+
}): Array<CssChild> => [
|
|
176
|
+
rule('.stat', {
|
|
177
|
+
font: font('regular', 'body'),
|
|
178
|
+
fill: textColor,
|
|
179
|
+
'font-variant-numeric': 'tabular-nums',
|
|
180
|
+
}),
|
|
181
|
+
firefoxFontSize(['.stat'], 'small'),
|
|
182
|
+
rule('.stagger', { opacity: 0, animation: 'fadeInAnimation 0.3s ease-in-out forwards' }),
|
|
183
|
+
rule('.rank-text', {
|
|
184
|
+
font: font('bold', 'display'),
|
|
185
|
+
fill: textColor,
|
|
186
|
+
animation: 'scaleInAnimation 0.3s ease-in-out forwards',
|
|
187
|
+
}),
|
|
188
|
+
rule('.rank-percentile-header', { 'font-size': `${String(FONT_SIZE.body)}px` }),
|
|
189
|
+
rule('.rank-percentile-text', { 'font-size': `${String(FONT_SIZE.lead)}px` }),
|
|
190
|
+
cssComment('Labels recede so that the values read first.'),
|
|
191
|
+
rule('.not_bold', { 'font-weight': FONT_WEIGHT.regular, opacity: 0.75 }),
|
|
192
|
+
rule('.bold', { 'font-weight': FONT_WEIGHT.semibold }),
|
|
193
|
+
rule('.icon', {
|
|
194
|
+
fill: iconColor,
|
|
195
|
+
opacity: 0.75,
|
|
196
|
+
display: show_icons ? 'block' : 'none',
|
|
197
|
+
}),
|
|
198
|
+
rule('.rank-circle-rim', {
|
|
199
|
+
stroke: ringColor,
|
|
200
|
+
fill: 'none',
|
|
201
|
+
'stroke-width': 4,
|
|
202
|
+
opacity: 0.15,
|
|
203
|
+
}),
|
|
204
|
+
rule('.rank-circle', {
|
|
205
|
+
stroke: ringColor,
|
|
206
|
+
'stroke-dasharray': 250,
|
|
207
|
+
fill: 'none',
|
|
208
|
+
'stroke-width': 4,
|
|
209
|
+
'stroke-linecap': 'round',
|
|
210
|
+
opacity: 1,
|
|
211
|
+
'transform-origin': '-10px 8px',
|
|
212
|
+
transform: 'rotate(-90deg)',
|
|
213
|
+
animation: 'rankAnimation 0.8s forwards ease-in-out',
|
|
214
|
+
}),
|
|
215
|
+
getProgressAnimation({ progress }),
|
|
216
|
+
];
|
|
200
217
|
|
|
201
218
|
/**
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* @param include_all_commits Option to include all years
|
|
205
|
-
* @param commits_year Option to include only selected year
|
|
206
|
-
* @param i18n The I18n instance.
|
|
207
|
-
* @returns The label corresponding to the options.
|
|
219
|
+
* @returns What the commit count covers, for the stat's label.
|
|
208
220
|
*/
|
|
209
|
-
const
|
|
221
|
+
const getTotalCommitsRangeLabel = (
|
|
210
222
|
include_all_commits: boolean,
|
|
211
|
-
|
|
223
|
+
commitsRange: GitHubDateRange | undefined,
|
|
212
224
|
i18n: I18n,
|
|
213
225
|
): string =>
|
|
214
226
|
include_all_commits
|
|
215
227
|
? ''
|
|
216
|
-
:
|
|
217
|
-
? ` (${
|
|
228
|
+
: commitsRange
|
|
229
|
+
? ` (${formatRange(commitsRange)})`
|
|
218
230
|
: ` (${i18n.t('wakatimecard.lastyear')})`;
|
|
219
231
|
|
|
220
232
|
/**
|
|
221
233
|
* Renders the stats card.
|
|
222
234
|
*
|
|
223
|
-
* @param stats The stats data.
|
|
224
|
-
* @param options The card options.
|
|
225
|
-
* @param username GitHub username, used to build stat search links.
|
|
226
|
-
* @param repo Repositories to scope the search links to.
|
|
227
|
-
* @param owner Owners to scope the search links to.
|
|
228
235
|
* @returns The stats card SVG object.
|
|
229
236
|
*/
|
|
230
|
-
const
|
|
237
|
+
const renderCard = (
|
|
231
238
|
stats: StatsData,
|
|
232
239
|
options: CardOptions<StatCardOptions> = {},
|
|
233
240
|
username?: string,
|
|
@@ -238,6 +245,7 @@ const renderStatsCard = (
|
|
|
238
245
|
name,
|
|
239
246
|
totalStars,
|
|
240
247
|
totalCommits,
|
|
248
|
+
commitsRange,
|
|
241
249
|
totalIssues,
|
|
242
250
|
totalPRs,
|
|
243
251
|
totalPRsMerged,
|
|
@@ -263,7 +271,6 @@ const renderStatsCard = (
|
|
|
263
271
|
card_width,
|
|
264
272
|
hide_rank = false,
|
|
265
273
|
include_all_commits = false,
|
|
266
|
-
commits_year,
|
|
267
274
|
line_height = 25,
|
|
268
275
|
text_bold = true,
|
|
269
276
|
custom_title,
|
|
@@ -276,10 +283,13 @@ const renderStatsCard = (
|
|
|
276
283
|
show = [],
|
|
277
284
|
} = options;
|
|
278
285
|
|
|
279
|
-
const lheight = parseInt(String(line_height), 10);
|
|
286
|
+
const lheight = Number.parseInt(String(line_height), 10);
|
|
280
287
|
|
|
281
288
|
const { lightColors, darkColors } = getLightDarkColors(options);
|
|
282
289
|
|
|
290
|
+
// Typed against the exported list, so a stat drawn here cannot be missing from it.
|
|
291
|
+
const shows = (stat: ShowStat): boolean => show.includes(stat);
|
|
292
|
+
|
|
283
293
|
const apostrophe = /s$/i.test(name.trim()) ? '' : 's';
|
|
284
294
|
const i18n = new I18n({
|
|
285
295
|
locale,
|
|
@@ -290,16 +300,16 @@ const renderStatsCard = (
|
|
|
290
300
|
});
|
|
291
301
|
|
|
292
302
|
// Meta data for creating text nodes with createTextNode function
|
|
293
|
-
const STATS: Record<
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
303
|
+
const STATS: Partial<Record<StatId, StatItem>> = {
|
|
304
|
+
stars: {
|
|
305
|
+
icon: icons.star,
|
|
306
|
+
label: i18n.t('statcard.totalstars'),
|
|
307
|
+
value: totalStars,
|
|
308
|
+
id: 'stars',
|
|
309
|
+
},
|
|
300
310
|
};
|
|
301
311
|
|
|
302
|
-
if (
|
|
312
|
+
if (shows('contributions')) {
|
|
303
313
|
STATS['contributions'] = {
|
|
304
314
|
icon: icons.contributions,
|
|
305
315
|
label: i18n.t('statcard.contributions'),
|
|
@@ -310,9 +320,9 @@ const renderStatsCard = (
|
|
|
310
320
|
|
|
311
321
|
STATS['commits'] = {
|
|
312
322
|
icon: icons.commits,
|
|
313
|
-
label: `${i18n.t('statcard.commits')}${
|
|
323
|
+
label: `${i18n.t('statcard.commits')}${getTotalCommitsRangeLabel(
|
|
314
324
|
include_all_commits,
|
|
315
|
-
|
|
325
|
+
commitsRange,
|
|
316
326
|
i18n,
|
|
317
327
|
)}`,
|
|
318
328
|
value: totalCommits,
|
|
@@ -325,7 +335,7 @@ const renderStatsCard = (
|
|
|
325
335
|
id: 'prs',
|
|
326
336
|
};
|
|
327
337
|
|
|
328
|
-
if (
|
|
338
|
+
if (shows('prs_merged')) {
|
|
329
339
|
STATS['prs_merged'] = {
|
|
330
340
|
icon: icons.prs_merged,
|
|
331
341
|
label: i18n.t('statcard.prs-merged'),
|
|
@@ -334,7 +344,7 @@ const renderStatsCard = (
|
|
|
334
344
|
};
|
|
335
345
|
}
|
|
336
346
|
|
|
337
|
-
if (
|
|
347
|
+
if (shows('prs_merged_percentage')) {
|
|
338
348
|
STATS['prs_merged_percentage'] = {
|
|
339
349
|
icon: icons.prs_merged_percentage,
|
|
340
350
|
label: i18n.t('statcard.prs-merged-percentage'),
|
|
@@ -348,7 +358,7 @@ const renderStatsCard = (
|
|
|
348
358
|
};
|
|
349
359
|
}
|
|
350
360
|
|
|
351
|
-
if (
|
|
361
|
+
if (shows('reviews')) {
|
|
352
362
|
STATS['reviews'] = {
|
|
353
363
|
icon: icons.reviews,
|
|
354
364
|
label: i18n.t('statcard.reviews'),
|
|
@@ -364,7 +374,7 @@ const renderStatsCard = (
|
|
|
364
374
|
id: 'issues',
|
|
365
375
|
};
|
|
366
376
|
|
|
367
|
-
if (
|
|
377
|
+
if (shows('discussions_started')) {
|
|
368
378
|
STATS['discussions_started'] = {
|
|
369
379
|
icon: icons.discussions_started,
|
|
370
380
|
label: i18n.t('statcard.discussions-started'),
|
|
@@ -372,7 +382,7 @@ const renderStatsCard = (
|
|
|
372
382
|
id: 'discussions_started',
|
|
373
383
|
};
|
|
374
384
|
}
|
|
375
|
-
if (
|
|
385
|
+
if (shows('discussions_answered')) {
|
|
376
386
|
STATS['discussions_answered'] = {
|
|
377
387
|
icon: icons.discussions_answered,
|
|
378
388
|
label: i18n.t('statcard.discussions-answered'),
|
|
@@ -383,7 +393,7 @@ const renderStatsCard = (
|
|
|
383
393
|
|
|
384
394
|
const repoFilter = encodeURIComponent(buildSearchFilter(repo, owner));
|
|
385
395
|
const encodedUsername = encodeURIComponent(username ?? '');
|
|
386
|
-
if (
|
|
396
|
+
if (shows('prs_authored')) {
|
|
387
397
|
STATS['prs_authored'] = {
|
|
388
398
|
icon: icons.prs,
|
|
389
399
|
label: i18n.t('statcard.prs-authored'),
|
|
@@ -392,7 +402,7 @@ const renderStatsCard = (
|
|
|
392
402
|
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=pullrequests`,
|
|
393
403
|
};
|
|
394
404
|
}
|
|
395
|
-
if (
|
|
405
|
+
if (shows('prs_commented')) {
|
|
396
406
|
STATS['prs_commented'] = {
|
|
397
407
|
icon: icons.comments,
|
|
398
408
|
label: i18n.t('statcard.prs-commented'),
|
|
@@ -401,7 +411,7 @@ const renderStatsCard = (
|
|
|
401
411
|
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
402
412
|
};
|
|
403
413
|
}
|
|
404
|
-
if (
|
|
414
|
+
if (shows('prs_reviewed')) {
|
|
405
415
|
STATS['prs_reviewed'] = {
|
|
406
416
|
icon: icons.reviews,
|
|
407
417
|
label: i18n.t('statcard.prs-reviewed'),
|
|
@@ -410,7 +420,7 @@ const renderStatsCard = (
|
|
|
410
420
|
link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
411
421
|
};
|
|
412
422
|
}
|
|
413
|
-
if (
|
|
423
|
+
if (shows('issues_authored')) {
|
|
414
424
|
STATS['issues_authored'] = {
|
|
415
425
|
icon: icons.issues,
|
|
416
426
|
label: i18n.t('statcard.issues-authored'),
|
|
@@ -419,7 +429,7 @@ const renderStatsCard = (
|
|
|
419
429
|
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=issues`,
|
|
420
430
|
};
|
|
421
431
|
}
|
|
422
|
-
if (
|
|
432
|
+
if (shows('issues_commented')) {
|
|
423
433
|
STATS['issues_commented'] = {
|
|
424
434
|
icon: icons.discussions_started,
|
|
425
435
|
label: i18n.t('statcard.issues-commented'),
|
|
@@ -430,103 +440,107 @@ const renderStatsCard = (
|
|
|
430
440
|
}
|
|
431
441
|
|
|
432
442
|
STATS['contribs'] = {
|
|
433
|
-
icon: icons.
|
|
443
|
+
icon: icons.repo,
|
|
434
444
|
label: i18n.t('statcard.contribs'),
|
|
435
445
|
value: contributedTo,
|
|
436
446
|
id: 'contribs',
|
|
437
447
|
};
|
|
438
448
|
|
|
439
|
-
if (
|
|
449
|
+
if (shows('all_time_contribs')) {
|
|
440
450
|
STATS['all_time_contribs'] = {
|
|
441
|
-
icon: icons.
|
|
451
|
+
icon: icons.repo,
|
|
442
452
|
label: i18n.t('statcard.all-time-contribs'),
|
|
443
453
|
value: allTimeContributedTo,
|
|
444
454
|
id: 'all_time_contribs',
|
|
445
455
|
};
|
|
446
456
|
}
|
|
447
457
|
|
|
448
|
-
const isLongLocale = locale ? LONG_LOCALES.
|
|
458
|
+
const isLongLocale = locale ? LONG_LOCALES.has(locale) : false;
|
|
449
459
|
|
|
450
|
-
//
|
|
451
|
-
const
|
|
452
|
-
.filter(([key]) => !hide.includes(key))
|
|
453
|
-
.some(([, stat]) => stat.label.length > 18);
|
|
454
|
-
|
|
455
|
-
// filter out hidden stats defined by user & create the text nodes
|
|
456
|
-
const statItems = Object.entries(STATS)
|
|
457
|
-
.filter(([key]) => !hide.includes(key))
|
|
458
|
-
// pass index so that we can calculate the line spacing
|
|
459
|
-
.map(([, stat], index) =>
|
|
460
|
-
createTextNode({
|
|
461
|
-
icon: stat.icon,
|
|
462
|
-
label: stat.label,
|
|
463
|
-
value: stat.value,
|
|
464
|
-
id: stat.id,
|
|
465
|
-
unitSymbol: stat.unitSymbol,
|
|
466
|
-
index,
|
|
467
|
-
showIcons: show_icons,
|
|
468
|
-
shiftValuePos: 29.01 + (longLabels ? 50 : 0) + (isLongLocale ? 50 : 0),
|
|
469
|
-
bold: text_bold,
|
|
470
|
-
numberFormat: number_format,
|
|
471
|
-
numberPrecision: number_precision,
|
|
472
|
-
link: stat.link,
|
|
473
|
-
}),
|
|
474
|
-
);
|
|
460
|
+
// filter out hidden stats defined by user
|
|
461
|
+
const visibleStats = Object.entries(STATS).filter(([key]) => !hide.includes(key));
|
|
475
462
|
|
|
476
|
-
if (
|
|
463
|
+
if (visibleStats.length === 0 && hide_rank) {
|
|
477
464
|
throw new CardError('Could not render stats card.', {
|
|
478
465
|
code: 'invalid_param',
|
|
479
466
|
secondaryMessage: 'Either stats or rank are required.',
|
|
480
467
|
});
|
|
481
468
|
}
|
|
482
469
|
|
|
483
|
-
//
|
|
484
|
-
|
|
470
|
+
// check if all used labels are short
|
|
471
|
+
const longLabels = visibleStats.some(([, stat]) => stat.label.length > 18);
|
|
472
|
+
|
|
473
|
+
// The rank ring sets the floor: 150 beside the stats, 180 when it is the whole card.
|
|
485
474
|
const height = Math.max(
|
|
486
|
-
45 + (
|
|
487
|
-
hide_rank ? 0 :
|
|
475
|
+
45 + (visibleStats.length + 1) * lheight,
|
|
476
|
+
hide_rank ? 0 : visibleStats.length > 0 ? 150 : 180,
|
|
488
477
|
);
|
|
489
478
|
|
|
490
479
|
// the lower the user's percentile the better
|
|
491
480
|
const progress = 100 - rank.percentile;
|
|
492
481
|
|
|
493
|
-
const calculateTextWidth = () =>
|
|
494
|
-
|
|
495
|
-
custom_title
|
|
496
|
-
?
|
|
497
|
-
: statItems.length
|
|
498
|
-
? i18n.t('statcard.title')
|
|
499
|
-
: i18n.t('statcard.ranktitle'),
|
|
482
|
+
const calculateTextWidth = (): number =>
|
|
483
|
+
measureText(
|
|
484
|
+
custom_title ||
|
|
485
|
+
(visibleStats.length > 0 ? i18n.t('statcard.title') : i18n.t('statcard.ranktitle')),
|
|
500
486
|
);
|
|
501
|
-
};
|
|
502
487
|
|
|
503
|
-
/*
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
488
|
+
const iconWidth = show_icons && visibleStats.length > 0 ? 16 + /* padding */ 1 : 0;
|
|
489
|
+
// The icons ride inside the step's own slack, so a default card lands on the width grid;
|
|
490
|
+
// only the minimum, which has no slack, still has to make room for them.
|
|
491
|
+
const defaultCardWidth = hide_rank
|
|
492
|
+
? CARD_DEFAULT_WIDTH
|
|
493
|
+
: visibleStats.length > 0
|
|
494
|
+
? RANK_CARD_DEFAULT_WIDTH
|
|
495
|
+
: RANK_ONLY_CARD_DEFAULT_WIDTH;
|
|
496
|
+
// Only a card sized by its own title can outgrow its step: the ranked layouts are fixed,
|
|
497
|
+
// so their step is also their floor.
|
|
498
|
+
const fittedWidth = hide_rank
|
|
499
|
+
? Math.max(
|
|
500
|
+
defaultCardWidth,
|
|
501
|
+
clampValue(50 /* padding */ + calculateTextWidth() * 2, CARD_MIN_WIDTH, Infinity) +
|
|
502
|
+
iconWidth,
|
|
503
|
+
)
|
|
504
|
+
: defaultCardWidth;
|
|
505
|
+
const width = card_width ? (Number.isNaN(card_width) ? fittedWidth : card_width) : fittedWidth;
|
|
506
|
+
|
|
507
|
+
// A value ends at the card's inner edge, clear of the rank ring — or right after
|
|
508
|
+
// the longest label, on a card too narrow for that.
|
|
509
|
+
const widestLabel =
|
|
510
|
+
visibleStats.length > 0
|
|
511
|
+
? Math.max(...visibleStats.map(([, stat]) => measureText(`${stat.label}:`, STAT_FONT_SIZE)))
|
|
512
|
+
: 0;
|
|
513
|
+
const valueAnchorX = Math.round(
|
|
514
|
+
Math.max(
|
|
515
|
+
width - CARD_PADDING_X - STAT_ROW_X - (hide_rank ? 0 : RANK_GUTTER),
|
|
516
|
+
(show_icons ? LABEL_X_OFFSET : 0) + widestLabel + LABEL_VALUE_GAP,
|
|
517
|
+
),
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
// pass index so that we can calculate the line spacing
|
|
521
|
+
const statItems = visibleStats.map(([, stat], index) =>
|
|
522
|
+
createTextNode({
|
|
523
|
+
icon: stat.icon,
|
|
524
|
+
label: stat.label,
|
|
525
|
+
value: stat.value,
|
|
526
|
+
id: stat.id,
|
|
527
|
+
unitSymbol: stat.unitSymbol,
|
|
528
|
+
index,
|
|
529
|
+
showIcons: show_icons,
|
|
530
|
+
shiftValuePos: 29.01 + (longLabels ? 50 : 0) + (isLongLocale ? 50 : 0),
|
|
531
|
+
valueAnchorX,
|
|
532
|
+
bold: text_bold,
|
|
533
|
+
labelBold: false,
|
|
534
|
+
numberFormat: number_format,
|
|
535
|
+
numberPrecision: number_precision,
|
|
536
|
+
link: stat.link,
|
|
537
|
+
}),
|
|
538
|
+
);
|
|
526
539
|
|
|
527
540
|
const card = new Card({
|
|
528
541
|
customTitle: custom_title,
|
|
529
|
-
defaultTitle:
|
|
542
|
+
defaultTitle: visibleStats.length > 0 ? i18n.t('statcard.title') : i18n.t('statcard.ranktitle'),
|
|
543
|
+
titlePrefixIcon: CARD_ICON.stats,
|
|
530
544
|
width,
|
|
531
545
|
height,
|
|
532
546
|
border_radius,
|
|
@@ -535,7 +549,7 @@ const renderStatsCard = (
|
|
|
535
549
|
|
|
536
550
|
card.setHideBorder(hide_border);
|
|
537
551
|
card.setHideTitle(hide_title);
|
|
538
|
-
const cardStyles = ({ ringColor, textColor, iconColor }: CardColors):
|
|
552
|
+
const cardStyles = ({ ringColor, textColor, iconColor }: CardColors): Array<CssChild> =>
|
|
539
553
|
getStyles({ ringColor, textColor, iconColor, show_icons, progress });
|
|
540
554
|
|
|
541
555
|
card.setCSS({ light: cardStyles, dark: cardStyles });
|
|
@@ -545,49 +559,37 @@ const renderStatsCard = (
|
|
|
545
559
|
}
|
|
546
560
|
|
|
547
561
|
/**
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
*
|
|
551
|
-
* width > RANK_CARD_DEFAULT_WIDTH: The default right padding of 70 px will be used.
|
|
552
|
-
* width < RANK_CARD_DEFAULT_WIDTH: The left and right padding will be enlarged
|
|
553
|
-
* equally from a certain minimum at RANK_CARD_MIN_WIDTH.
|
|
562
|
+
* The ring sits centred in the same gutter the values are kept clear of, so the gap
|
|
563
|
+
* between them holds at every width; a rank-only card has no values, so it centres.
|
|
554
564
|
*
|
|
555
565
|
* @returns Rank circle translation value.
|
|
556
566
|
*/
|
|
557
|
-
const calculateRankXTranslation = (): number =>
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const xMaxExpansion = minXTranslation + (450 - minCardWidth) / 2;
|
|
562
|
-
return xMaxExpansion + width - RANK_CARD_DEFAULT_WIDTH;
|
|
563
|
-
} else {
|
|
564
|
-
return minXTranslation + (width - minCardWidth) / 2;
|
|
565
|
-
}
|
|
566
|
-
} else {
|
|
567
|
-
return width / 2 + 20 - 10;
|
|
568
|
-
}
|
|
569
|
-
};
|
|
567
|
+
const calculateRankXTranslation = (): number =>
|
|
568
|
+
visibleStats.length > 0
|
|
569
|
+
? width - CARD_PADDING_X - RANK_GUTTER / 2 + RANK_CIRCLE_CX_OFFSET
|
|
570
|
+
: width / 2 + 20 - 10;
|
|
570
571
|
|
|
571
572
|
// Conditionally rendered elements
|
|
572
|
-
const rankCircle =
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
573
|
+
const rankCircle =
|
|
574
|
+
!hide_rank &&
|
|
575
|
+
el(
|
|
576
|
+
'g',
|
|
577
|
+
{
|
|
578
|
+
'data-testid': 'rank-circle',
|
|
579
|
+
transform: `translate(${calculateRankXTranslation()}, ${height / 2 - 50})`,
|
|
580
|
+
},
|
|
581
|
+
el('circle', { class: 'rank-circle-rim', cx: -RANK_CIRCLE_CX_OFFSET, cy: 8, r: 40 }),
|
|
582
|
+
el('circle', { class: 'rank-circle', cx: -RANK_CIRCLE_CX_OFFSET, cy: 8, r: 40 }),
|
|
583
|
+
el('g', { class: 'rank-text' }, rankIcon(rank_icon, rank.level, rank.percentile)),
|
|
584
|
+
);
|
|
582
585
|
|
|
583
586
|
// Accessibility Labels
|
|
584
|
-
const labels =
|
|
585
|
-
.filter(([key]) => !hide.includes(key))
|
|
587
|
+
const labels = visibleStats
|
|
586
588
|
.map(([key, stat]) => {
|
|
587
589
|
if (key === 'commits') {
|
|
588
|
-
return `${i18n.t('statcard.commits')} ${
|
|
590
|
+
return `${i18n.t('statcard.commits')} ${getTotalCommitsRangeLabel(
|
|
589
591
|
include_all_commits,
|
|
590
|
-
|
|
592
|
+
commitsRange,
|
|
591
593
|
i18n,
|
|
592
594
|
)} : ${stat.value}`;
|
|
593
595
|
}
|
|
@@ -596,20 +598,28 @@ const renderStatsCard = (
|
|
|
596
598
|
.join(', ');
|
|
597
599
|
|
|
598
600
|
card.setAccessibilityLabel({
|
|
599
|
-
title: `${card.title},
|
|
601
|
+
title: `${card.title}, ${i18n.t('statcard.rank')}: ${rank.level}`,
|
|
600
602
|
desc: labels,
|
|
601
603
|
});
|
|
602
604
|
|
|
603
|
-
return card.render(
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
items: statItems,
|
|
608
|
-
gap: lheight,
|
|
609
|
-
direction: 'column',
|
|
610
|
-
}).join('')}
|
|
611
|
-
</svg>
|
|
612
|
-
`);
|
|
605
|
+
return card.render([
|
|
606
|
+
rankCircle,
|
|
607
|
+
el('svg', { x: 0, y: 0 }, flexLayout({ items: statItems, gap: lheight, direction: 'column' })),
|
|
608
|
+
]);
|
|
613
609
|
};
|
|
614
610
|
|
|
615
|
-
|
|
611
|
+
/**
|
|
612
|
+
* The card, and the values each of its options accepts, keyed by the option's own name.
|
|
613
|
+
* They ride on the renderer so a list cannot be found without what draws it;
|
|
614
|
+
* the api handler forwards them onto its own export, which is what a UI reads.
|
|
615
|
+
*/
|
|
616
|
+
const renderStatsCard = Object.assign(renderCard, {
|
|
617
|
+
OPTIONS: {
|
|
618
|
+
rank_icon: RANK_ICONS,
|
|
619
|
+
show: SHOW_STATS,
|
|
620
|
+
hide: HIDE_STATS,
|
|
621
|
+
number_format: NUMBER_FORMATS,
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
export { renderStatsCard };
|