@stats-forge/github-stats-forge-core 0.0.1
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/LICENSE +21 -0
- package/README.md +148 -0
- package/build/api/api-result.d.ts +44 -0
- package/build/api/api-result.d.ts.map +1 -0
- package/build/api/api-result.js +33 -0
- package/build/api/gist.d.ts +29 -0
- package/build/api/gist.d.ts.map +1 -0
- package/build/api/gist.js +52 -0
- package/build/api/index.d.ts +13 -0
- package/build/api/index.d.ts.map +1 -0
- package/build/api/index.js +8 -0
- package/build/api/params.d.ts +93 -0
- package/build/api/params.d.ts.map +1 -0
- package/build/api/params.js +145 -0
- package/build/api/pin.d.ts +47 -0
- package/build/api/pin.d.ts.map +1 -0
- package/build/api/pin.js +87 -0
- package/build/api/stats.d.ts +42 -0
- package/build/api/stats.d.ts.map +1 -0
- package/build/api/stats.js +132 -0
- package/build/api/top-langs.d.ts +37 -0
- package/build/api/top-langs.d.ts.map +1 -0
- package/build/api/top-langs.js +101 -0
- package/build/api/wakatime.d.ts +34 -0
- package/build/api/wakatime.d.ts.map +1 -0
- package/build/api/wakatime.js +91 -0
- package/build/calculateRank.d.ts +29 -0
- package/build/calculateRank.d.ts.map +1 -0
- package/build/calculateRank.js +58 -0
- package/build/cards/gist.d.ts +16 -0
- package/build/cards/gist.d.ts.map +1 -0
- package/build/cards/gist.js +119 -0
- package/build/cards/index.d.ts +11 -0
- package/build/cards/index.d.ts.map +1 -0
- package/build/cards/index.js +7 -0
- package/build/cards/options.d.ts +19 -0
- package/build/cards/options.d.ts.map +1 -0
- package/build/cards/options.js +1 -0
- package/build/cards/repo.d.ts +25 -0
- package/build/cards/repo.d.ts.map +1 -0
- package/build/cards/repo.js +276 -0
- package/build/cards/stats.d.ts +37 -0
- package/build/cards/stats.d.ts.map +1 -0
- package/build/cards/stats.js +457 -0
- package/build/cards/top-languages.d.ts +153 -0
- package/build/cards/top-languages.d.ts.map +1 -0
- package/build/cards/top-languages.js +805 -0
- package/build/cards/wakatime.d.ts +31 -0
- package/build/cards/wakatime.d.ts.map +1 -0
- package/build/cards/wakatime.js +376 -0
- package/build/common/Card.d.ts +121 -0
- package/build/common/Card.d.ts.map +1 -0
- package/build/common/Card.js +305 -0
- package/build/common/I18n.d.ts +27 -0
- package/build/common/I18n.d.ts.map +1 -0
- package/build/common/I18n.js +35 -0
- package/build/common/color.d.ts +112 -0
- package/build/common/color.d.ts.map +1 -0
- package/build/common/color.js +188 -0
- package/build/common/config.d.ts +53 -0
- package/build/common/config.d.ts.map +1 -0
- package/build/common/config.js +87 -0
- package/build/common/constants.d.ts +7 -0
- package/build/common/constants.d.ts.map +1 -0
- package/build/common/constants.js +9 -0
- package/build/common/date.d.ts +29 -0
- package/build/common/date.d.ts.map +1 -0
- package/build/common/date.js +23 -0
- package/build/common/error.d.ts +87 -0
- package/build/common/error.d.ts.map +1 -0
- package/build/common/error.js +108 -0
- package/build/common/fmt.d.ts +28 -0
- package/build/common/fmt.d.ts.map +1 -0
- package/build/common/fmt.js +68 -0
- package/build/common/html.d.ts +8 -0
- package/build/common/html.d.ts.map +1 -0
- package/build/common/html.js +14 -0
- package/build/common/http.d.ts +53 -0
- package/build/common/http.d.ts.map +1 -0
- package/build/common/http.js +47 -0
- package/build/common/icons.d.ts +28 -0
- package/build/common/icons.d.ts.map +1 -0
- package/build/common/icons.js +77 -0
- package/build/common/languageColors.d.ts +11 -0
- package/build/common/languageColors.d.ts.map +1 -0
- package/build/common/languageColors.js +15 -0
- package/build/common/languageColors.json +678 -0
- package/build/common/log.d.ts +9 -0
- package/build/common/log.d.ts.map +1 -0
- package/build/common/log.js +8 -0
- package/build/common/ops.d.ts +59 -0
- package/build/common/ops.d.ts.map +1 -0
- package/build/common/ops.js +115 -0
- package/build/common/render.d.ts +196 -0
- package/build/common/render.d.ts.map +1 -0
- package/build/common/render.js +474 -0
- package/build/common/retryer.d.ts +36 -0
- package/build/common/retryer.d.ts.map +1 -0
- package/build/common/retryer.js +67 -0
- package/build/fetchers/gist.d.ts +15 -0
- package/build/fetchers/gist.d.ts.map +1 -0
- package/build/fetchers/gist.js +68 -0
- package/build/fetchers/index.d.ts +13 -0
- package/build/fetchers/index.d.ts.map +1 -0
- package/build/fetchers/index.js +10 -0
- package/build/fetchers/repo.d.ts +27 -0
- package/build/fetchers/repo.d.ts.map +1 -0
- package/build/fetchers/repo.js +100 -0
- package/build/fetchers/stats.d.ts +74 -0
- package/build/fetchers/stats.d.ts.map +1 -0
- package/build/fetchers/stats.js +498 -0
- package/build/fetchers/top-languages.d.ts +23 -0
- package/build/fetchers/top-languages.d.ts.map +1 -0
- package/build/fetchers/top-languages.js +89 -0
- package/build/fetchers/types.d.ts +130 -0
- package/build/fetchers/types.d.ts.map +1 -0
- package/build/fetchers/types.js +1 -0
- package/build/fetchers/wakatime.d.ts +19 -0
- package/build/fetchers/wakatime.d.ts.map +1 -0
- package/build/fetchers/wakatime.js +36 -0
- package/build/graphql/contributionsDocument.d.ts +18 -0
- package/build/graphql/contributionsDocument.d.ts.map +1 -0
- package/build/graphql/contributionsDocument.js +30 -0
- package/build/graphql/generated/common.d.ts +42 -0
- package/build/graphql/generated/common.d.ts.map +1 -0
- package/build/graphql/generated/common.js +2 -0
- package/build/graphql/generated/gist.d.ts +44 -0
- package/build/graphql/generated/gist.d.ts.map +1 -0
- package/build/graphql/generated/gist.js +27 -0
- package/build/graphql/generated/repo.d.ts +67 -0
- package/build/graphql/generated/repo.d.ts.map +1 -0
- package/build/graphql/generated/repo.js +30 -0
- package/build/graphql/generated/stats.d.ts +150 -0
- package/build/graphql/generated/stats.d.ts.map +1 -0
- package/build/graphql/generated/stats.js +95 -0
- package/build/graphql/generated/top-languages.d.ts +54 -0
- package/build/graphql/generated/top-languages.d.ts.map +1 -0
- package/build/graphql/generated/top-languages.js +27 -0
- package/build/graphql/graphqlDocument.d.ts +22 -0
- package/build/graphql/graphqlDocument.d.ts.map +1 -0
- package/build/graphql/graphqlDocument.js +8 -0
- package/build/graphql/reposContributedToDocument.d.ts +27 -0
- package/build/graphql/reposContributedToDocument.d.ts.map +1 -0
- package/build/graphql/reposContributedToDocument.js +59 -0
- package/build/index.d.ts +15 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +10 -0
- package/build/themes/index.d.ts +505 -0
- package/build/themes/index.d.ts.map +1 -0
- package/build/themes/index.js +505 -0
- package/build/translations.d.ts +1153 -0
- package/build/translations.d.ts.map +1 -0
- package/build/translations.js +1156 -0
- package/package.json +114 -0
- package/src/_emoji-name-map.d.ts +10 -0
- package/src/_github-username-regex.d.ts +4 -0
- package/src/api/api-result.ts +69 -0
- package/src/api/gist.ts +66 -0
- package/src/api/index.ts +16 -0
- package/src/api/params.ts +221 -0
- package/src/api/pin.ts +126 -0
- package/src/api/stats.ts +195 -0
- package/src/api/top-langs.ts +150 -0
- package/src/api/wakatime.ts +130 -0
- package/src/calculateRank.ts +91 -0
- package/src/cards/gist.ts +169 -0
- package/src/cards/index.ts +21 -0
- package/src/cards/options.ts +18 -0
- package/src/cards/repo.ts +376 -0
- package/src/cards/stats.ts +615 -0
- package/src/cards/top-languages.ts +1077 -0
- package/src/cards/wakatime.ts +501 -0
- package/src/common/Card.ts +364 -0
- package/src/common/I18n.ts +51 -0
- package/src/common/color.ts +296 -0
- package/src/common/config.ts +118 -0
- package/src/common/constants.ts +12 -0
- package/src/common/date.ts +34 -0
- package/src/common/error.ts +163 -0
- package/src/common/fmt.ts +86 -0
- package/src/common/html.ts +17 -0
- package/src/common/http.ts +98 -0
- package/src/common/icons.ts +80 -0
- package/src/common/languageColors.json +678 -0
- package/src/common/languageColors.ts +19 -0
- package/src/common/log.ts +12 -0
- package/src/common/ops.ts +145 -0
- package/src/common/render.ts +641 -0
- package/src/common/retryer.ts +111 -0
- package/src/fetchers/gist.ts +82 -0
- package/src/fetchers/index.ts +25 -0
- package/src/fetchers/repo.ts +140 -0
- package/src/fetchers/stats.ts +742 -0
- package/src/fetchers/top-languages.ts +134 -0
- package/src/fetchers/types.ts +136 -0
- package/src/fetchers/wakatime.ts +56 -0
- package/src/graphql/contributionsDocument.ts +44 -0
- package/src/graphql/generated/common.ts +26 -0
- package/src/graphql/generated/gist.ts +56 -0
- package/src/graphql/generated/repo.ts +79 -0
- package/src/graphql/generated/stats.ts +180 -0
- package/src/graphql/generated/top-languages.ts +66 -0
- package/src/graphql/graphqlDocument.ts +25 -0
- package/src/graphql/queries/gist.graphql +25 -0
- package/src/graphql/queries/repo.graphql +28 -0
- package/src/graphql/queries/stats.graphql +113 -0
- package/src/graphql/queries/top-languages.graphql +27 -0
- package/src/graphql/reposContributedToDocument.ts +83 -0
- package/src/index.ts +18 -0
- package/src/themes/index.ts +519 -0
- package/src/translations.ts +1169 -0
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
import { Card } from '../common/Card.js';
|
|
2
|
+
import { getLightDarkColors } from '../common/color.js';
|
|
3
|
+
import type { CardColors } from '../common/color.js';
|
|
4
|
+
import { CardError } from '../common/error.js';
|
|
5
|
+
import { I18n } from '../common/I18n.js';
|
|
6
|
+
import { icons, rankIcon } from '../common/icons.js';
|
|
7
|
+
import { buildSearchFilter, clampValue } from '../common/ops.js';
|
|
8
|
+
import { createTextNode, flexLayout, measureText } from '../common/render.js';
|
|
9
|
+
import type { StatsData } from '../fetchers/types.js';
|
|
10
|
+
import { statCardLocales, wakatimeCardLocales } from '../translations.js';
|
|
11
|
+
|
|
12
|
+
import type { CardOptions, CommonCardOptions } from './options.js';
|
|
13
|
+
|
|
14
|
+
const CARD_MIN_WIDTH = 287;
|
|
15
|
+
const CARD_DEFAULT_WIDTH = 287;
|
|
16
|
+
const RANK_CARD_MIN_WIDTH = 420;
|
|
17
|
+
const RANK_CARD_DEFAULT_WIDTH = 450;
|
|
18
|
+
const RANK_ONLY_CARD_MIN_WIDTH = 290;
|
|
19
|
+
const RANK_ONLY_CARD_DEFAULT_WIDTH = 290;
|
|
20
|
+
|
|
21
|
+
/** Rank indicators the card can draw; the api validates `rank_icon` against this. */
|
|
22
|
+
const RANK_ICONS = ['default', 'github', 'percentile'] as const;
|
|
23
|
+
type RankIcon = (typeof RANK_ICONS)[number];
|
|
24
|
+
|
|
25
|
+
interface StatCardOptions extends CommonCardOptions {
|
|
26
|
+
locale: string;
|
|
27
|
+
hide: Array<string>;
|
|
28
|
+
show_icons: boolean;
|
|
29
|
+
hide_title: boolean;
|
|
30
|
+
card_width: number;
|
|
31
|
+
hide_rank: boolean;
|
|
32
|
+
include_all_commits: boolean;
|
|
33
|
+
commits_year: number;
|
|
34
|
+
line_height: number | string;
|
|
35
|
+
custom_title: string;
|
|
36
|
+
disable_animations: boolean;
|
|
37
|
+
number_format: string;
|
|
38
|
+
number_precision: number;
|
|
39
|
+
ring_color: string;
|
|
40
|
+
text_bold: boolean;
|
|
41
|
+
rank_icon: RankIcon;
|
|
42
|
+
show: Array<string>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Meta data for a stat, used to build its text node and accessibility label. */
|
|
46
|
+
interface StatItem {
|
|
47
|
+
icon: string;
|
|
48
|
+
label: string;
|
|
49
|
+
value: number | string;
|
|
50
|
+
id: string;
|
|
51
|
+
unitSymbol?: string;
|
|
52
|
+
link?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Long locales that need more space for text. Keep sorted alphabetically. */
|
|
56
|
+
const LONG_LOCALES: Array<string> = [
|
|
57
|
+
'az',
|
|
58
|
+
'bg',
|
|
59
|
+
'cs',
|
|
60
|
+
'de',
|
|
61
|
+
'el',
|
|
62
|
+
'es',
|
|
63
|
+
'fil',
|
|
64
|
+
'fi',
|
|
65
|
+
'fr',
|
|
66
|
+
'hu',
|
|
67
|
+
'id',
|
|
68
|
+
'ja',
|
|
69
|
+
'ml',
|
|
70
|
+
'my',
|
|
71
|
+
'nl',
|
|
72
|
+
'pl',
|
|
73
|
+
'pt-br',
|
|
74
|
+
'pt-pt',
|
|
75
|
+
'ru',
|
|
76
|
+
'sr',
|
|
77
|
+
'sr-latn',
|
|
78
|
+
'sw',
|
|
79
|
+
'ta',
|
|
80
|
+
'uk-ua',
|
|
81
|
+
'uz',
|
|
82
|
+
'zh-tw',
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Calculates progress along the boundary of the circle, i.e. its circumference.
|
|
87
|
+
*
|
|
88
|
+
* @param value The rank value to calculate progress for.
|
|
89
|
+
* @returns Progress value.
|
|
90
|
+
*/
|
|
91
|
+
const calculateCircleProgress = (value: number): number => {
|
|
92
|
+
const radius = 40;
|
|
93
|
+
const c = Math.PI * (radius * 2);
|
|
94
|
+
|
|
95
|
+
if (value < 0) {
|
|
96
|
+
value = 0;
|
|
97
|
+
}
|
|
98
|
+
if (value > 100) {
|
|
99
|
+
value = 100;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return ((100 - value) / 100) * c;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Retrieves the animation to display progress along the circumference of circle
|
|
107
|
+
* from the beginning to the given value in a clockwise direction.
|
|
108
|
+
*
|
|
109
|
+
* @param props The props object.
|
|
110
|
+
* @param props.progress The progress value to animate to.
|
|
111
|
+
* @returns Progress animation css.
|
|
112
|
+
*/
|
|
113
|
+
const getProgressAnimation = ({ progress }: { progress: number }): string => {
|
|
114
|
+
return `
|
|
115
|
+
@keyframes rankAnimation {
|
|
116
|
+
from {
|
|
117
|
+
stroke-dashoffset: ${calculateCircleProgress(0)};
|
|
118
|
+
}
|
|
119
|
+
to {
|
|
120
|
+
stroke-dashoffset: ${calculateCircleProgress(progress)};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Retrieves CSS styles for a card.
|
|
128
|
+
*
|
|
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
|
+
* @returns Card CSS styles.
|
|
136
|
+
*/
|
|
137
|
+
const getStyles = ({
|
|
138
|
+
textColor,
|
|
139
|
+
iconColor,
|
|
140
|
+
ringColor,
|
|
141
|
+
show_icons,
|
|
142
|
+
progress,
|
|
143
|
+
}: {
|
|
144
|
+
textColor: string;
|
|
145
|
+
iconColor: string;
|
|
146
|
+
ringColor: string;
|
|
147
|
+
show_icons: boolean;
|
|
148
|
+
progress: number;
|
|
149
|
+
}): string => {
|
|
150
|
+
return `
|
|
151
|
+
.stat {
|
|
152
|
+
font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${textColor};
|
|
153
|
+
}
|
|
154
|
+
@supports(-moz-appearance: auto) {
|
|
155
|
+
/* Selector detects Firefox */
|
|
156
|
+
.stat { font-size:12px; }
|
|
157
|
+
}
|
|
158
|
+
.stagger {
|
|
159
|
+
opacity: 0;
|
|
160
|
+
animation: fadeInAnimation 0.3s ease-in-out forwards;
|
|
161
|
+
}
|
|
162
|
+
.rank-text {
|
|
163
|
+
font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor};
|
|
164
|
+
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
|
165
|
+
}
|
|
166
|
+
.rank-percentile-header {
|
|
167
|
+
font-size: 14px;
|
|
168
|
+
}
|
|
169
|
+
.rank-percentile-text {
|
|
170
|
+
font-size: 16px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.not_bold { font-weight: 400 }
|
|
174
|
+
.bold { font-weight: 700 }
|
|
175
|
+
.icon {
|
|
176
|
+
fill: ${iconColor};
|
|
177
|
+
display: ${show_icons ? 'block' : 'none'};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.rank-circle-rim {
|
|
181
|
+
stroke: ${ringColor};
|
|
182
|
+
fill: none;
|
|
183
|
+
stroke-width: 6;
|
|
184
|
+
opacity: 0.2;
|
|
185
|
+
}
|
|
186
|
+
.rank-circle {
|
|
187
|
+
stroke: ${ringColor};
|
|
188
|
+
stroke-dasharray: 250;
|
|
189
|
+
fill: none;
|
|
190
|
+
stroke-width: 6;
|
|
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
|
+
};
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Return the label for commits according to the selected options
|
|
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.
|
|
208
|
+
*/
|
|
209
|
+
const getTotalCommitsYearLabel = (
|
|
210
|
+
include_all_commits: boolean,
|
|
211
|
+
commits_year: number | undefined,
|
|
212
|
+
i18n: I18n,
|
|
213
|
+
): string =>
|
|
214
|
+
include_all_commits
|
|
215
|
+
? ''
|
|
216
|
+
: commits_year
|
|
217
|
+
? ` (${commits_year})`
|
|
218
|
+
: ` (${i18n.t('wakatimecard.lastyear')})`;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Renders the stats card.
|
|
222
|
+
*
|
|
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
|
+
* @returns The stats card SVG object.
|
|
229
|
+
*/
|
|
230
|
+
const renderStatsCard = (
|
|
231
|
+
stats: StatsData,
|
|
232
|
+
options: CardOptions<StatCardOptions> = {},
|
|
233
|
+
username?: string,
|
|
234
|
+
repo: Array<string> = [],
|
|
235
|
+
owner: Array<string> = [],
|
|
236
|
+
): string => {
|
|
237
|
+
const {
|
|
238
|
+
name,
|
|
239
|
+
totalStars,
|
|
240
|
+
totalCommits,
|
|
241
|
+
totalIssues,
|
|
242
|
+
totalPRs,
|
|
243
|
+
totalPRsMerged,
|
|
244
|
+
mergedPRsPercentage,
|
|
245
|
+
totalReviews,
|
|
246
|
+
totalDiscussionsStarted,
|
|
247
|
+
totalDiscussionsAnswered,
|
|
248
|
+
contributedTo,
|
|
249
|
+
allTimeContributedTo,
|
|
250
|
+
totalPRsAuthored,
|
|
251
|
+
totalPRsCommented,
|
|
252
|
+
totalPRsReviewed,
|
|
253
|
+
totalIssuesAuthored,
|
|
254
|
+
totalIssuesCommented,
|
|
255
|
+
totalContributions,
|
|
256
|
+
rank,
|
|
257
|
+
} = stats;
|
|
258
|
+
const {
|
|
259
|
+
hide = [],
|
|
260
|
+
show_icons = false,
|
|
261
|
+
hide_title = false,
|
|
262
|
+
hide_border = false,
|
|
263
|
+
card_width,
|
|
264
|
+
hide_rank = false,
|
|
265
|
+
include_all_commits = false,
|
|
266
|
+
commits_year,
|
|
267
|
+
line_height = 25,
|
|
268
|
+
text_bold = true,
|
|
269
|
+
custom_title,
|
|
270
|
+
border_radius,
|
|
271
|
+
number_format = 'short',
|
|
272
|
+
number_precision,
|
|
273
|
+
locale,
|
|
274
|
+
disable_animations = false,
|
|
275
|
+
rank_icon = 'default',
|
|
276
|
+
show = [],
|
|
277
|
+
} = options;
|
|
278
|
+
|
|
279
|
+
const lheight = parseInt(String(line_height), 10);
|
|
280
|
+
|
|
281
|
+
const { lightColors, darkColors } = getLightDarkColors(options);
|
|
282
|
+
|
|
283
|
+
const apostrophe = /s$/i.test(name.trim()) ? '' : 's';
|
|
284
|
+
const i18n = new I18n({
|
|
285
|
+
locale,
|
|
286
|
+
translations: {
|
|
287
|
+
...statCardLocales({ name, apostrophe }),
|
|
288
|
+
...wakatimeCardLocales,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Meta data for creating text nodes with createTextNode function
|
|
293
|
+
const STATS: Record<string, StatItem> = {};
|
|
294
|
+
|
|
295
|
+
STATS['stars'] = {
|
|
296
|
+
icon: icons.star,
|
|
297
|
+
label: i18n.t('statcard.totalstars'),
|
|
298
|
+
value: totalStars,
|
|
299
|
+
id: 'stars',
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
if (show.includes('contributions')) {
|
|
303
|
+
STATS['contributions'] = {
|
|
304
|
+
icon: icons.contributions,
|
|
305
|
+
label: i18n.t('statcard.contributions'),
|
|
306
|
+
value: totalContributions,
|
|
307
|
+
id: 'contributions',
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
STATS['commits'] = {
|
|
312
|
+
icon: icons.commits,
|
|
313
|
+
label: `${i18n.t('statcard.commits')}${getTotalCommitsYearLabel(
|
|
314
|
+
include_all_commits,
|
|
315
|
+
commits_year,
|
|
316
|
+
i18n,
|
|
317
|
+
)}`,
|
|
318
|
+
value: totalCommits,
|
|
319
|
+
id: 'commits',
|
|
320
|
+
};
|
|
321
|
+
STATS['prs'] = {
|
|
322
|
+
icon: icons.prs,
|
|
323
|
+
label: i18n.t('statcard.prs'),
|
|
324
|
+
value: totalPRs,
|
|
325
|
+
id: 'prs',
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
if (show.includes('prs_merged')) {
|
|
329
|
+
STATS['prs_merged'] = {
|
|
330
|
+
icon: icons.prs_merged,
|
|
331
|
+
label: i18n.t('statcard.prs-merged'),
|
|
332
|
+
value: totalPRsMerged,
|
|
333
|
+
id: 'prs_merged',
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (show.includes('prs_merged_percentage')) {
|
|
338
|
+
STATS['prs_merged_percentage'] = {
|
|
339
|
+
icon: icons.prs_merged_percentage,
|
|
340
|
+
label: i18n.t('statcard.prs-merged-percentage'),
|
|
341
|
+
value: mergedPRsPercentage.toFixed(
|
|
342
|
+
number_precision !== undefined && Number.isFinite(number_precision)
|
|
343
|
+
? clampValue(number_precision, 0, 2)
|
|
344
|
+
: 2,
|
|
345
|
+
),
|
|
346
|
+
id: 'prs_merged_percentage',
|
|
347
|
+
unitSymbol: '%',
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (show.includes('reviews')) {
|
|
352
|
+
STATS['reviews'] = {
|
|
353
|
+
icon: icons.reviews,
|
|
354
|
+
label: i18n.t('statcard.reviews'),
|
|
355
|
+
value: totalReviews,
|
|
356
|
+
id: 'reviews',
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
STATS['issues'] = {
|
|
361
|
+
icon: icons.issues,
|
|
362
|
+
label: i18n.t('statcard.issues'),
|
|
363
|
+
value: totalIssues,
|
|
364
|
+
id: 'issues',
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
if (show.includes('discussions_started')) {
|
|
368
|
+
STATS['discussions_started'] = {
|
|
369
|
+
icon: icons.discussions_started,
|
|
370
|
+
label: i18n.t('statcard.discussions-started'),
|
|
371
|
+
value: totalDiscussionsStarted,
|
|
372
|
+
id: 'discussions_started',
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
if (show.includes('discussions_answered')) {
|
|
376
|
+
STATS['discussions_answered'] = {
|
|
377
|
+
icon: icons.discussions_answered,
|
|
378
|
+
label: i18n.t('statcard.discussions-answered'),
|
|
379
|
+
value: totalDiscussionsAnswered,
|
|
380
|
+
id: 'discussions_answered',
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const repoFilter = encodeURIComponent(buildSearchFilter(repo, owner));
|
|
385
|
+
const encodedUsername = encodeURIComponent(username ?? '');
|
|
386
|
+
if (show.includes('prs_authored')) {
|
|
387
|
+
STATS['prs_authored'] = {
|
|
388
|
+
icon: icons.prs,
|
|
389
|
+
label: i18n.t('statcard.prs-authored'),
|
|
390
|
+
value: totalPRsAuthored,
|
|
391
|
+
id: 'prs_authored',
|
|
392
|
+
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=pullrequests`,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
if (show.includes('prs_commented')) {
|
|
396
|
+
STATS['prs_commented'] = {
|
|
397
|
+
icon: icons.comments,
|
|
398
|
+
label: i18n.t('statcard.prs-commented'),
|
|
399
|
+
value: totalPRsCommented,
|
|
400
|
+
id: 'prs_commented',
|
|
401
|
+
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
if (show.includes('prs_reviewed')) {
|
|
405
|
+
STATS['prs_reviewed'] = {
|
|
406
|
+
icon: icons.reviews,
|
|
407
|
+
label: i18n.t('statcard.prs-reviewed'),
|
|
408
|
+
value: totalPRsReviewed,
|
|
409
|
+
id: 'prs_reviewed',
|
|
410
|
+
link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
if (show.includes('issues_authored')) {
|
|
414
|
+
STATS['issues_authored'] = {
|
|
415
|
+
icon: icons.issues,
|
|
416
|
+
label: i18n.t('statcard.issues-authored'),
|
|
417
|
+
value: totalIssuesAuthored,
|
|
418
|
+
id: 'issues_authored',
|
|
419
|
+
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=issues`,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
if (show.includes('issues_commented')) {
|
|
423
|
+
STATS['issues_commented'] = {
|
|
424
|
+
icon: icons.discussions_started,
|
|
425
|
+
label: i18n.t('statcard.issues-commented'),
|
|
426
|
+
value: totalIssuesCommented,
|
|
427
|
+
id: 'issues_commented',
|
|
428
|
+
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&type=issues`,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
STATS['contribs'] = {
|
|
433
|
+
icon: icons.contribs,
|
|
434
|
+
label: i18n.t('statcard.contribs'),
|
|
435
|
+
value: contributedTo,
|
|
436
|
+
id: 'contribs',
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
if (show.includes('all_time_contribs')) {
|
|
440
|
+
STATS['all_time_contribs'] = {
|
|
441
|
+
icon: icons.contribs,
|
|
442
|
+
label: i18n.t('statcard.all-time-contribs'),
|
|
443
|
+
value: allTimeContributedTo,
|
|
444
|
+
id: 'all_time_contribs',
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const isLongLocale = locale ? LONG_LOCALES.includes(locale) : false;
|
|
449
|
+
|
|
450
|
+
// check if all used labels are short
|
|
451
|
+
const longLabels = Object.entries(STATS)
|
|
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
|
+
);
|
|
475
|
+
|
|
476
|
+
if (statItems.length === 0 && hide_rank) {
|
|
477
|
+
throw new CardError('Could not render stats card.', {
|
|
478
|
+
code: 'invalid_param',
|
|
479
|
+
secondaryMessage: 'Either stats or rank are required.',
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Calculate the card height depending on how many items there are
|
|
484
|
+
// but if rank circle is visible clamp the minimum height to `150`
|
|
485
|
+
const height = Math.max(
|
|
486
|
+
45 + (statItems.length + 1) * lheight,
|
|
487
|
+
hide_rank ? 0 : statItems.length ? 150 : 180,
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
// the lower the user's percentile the better
|
|
491
|
+
const progress = 100 - rank.percentile;
|
|
492
|
+
|
|
493
|
+
const calculateTextWidth = () => {
|
|
494
|
+
return measureText(
|
|
495
|
+
custom_title
|
|
496
|
+
? custom_title
|
|
497
|
+
: statItems.length
|
|
498
|
+
? i18n.t('statcard.title')
|
|
499
|
+
: i18n.t('statcard.ranktitle'),
|
|
500
|
+
);
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
/*
|
|
504
|
+
When hide_rank=true, the minimum card width is 270 px + the title length and padding.
|
|
505
|
+
When hide_rank=false, the minimum card_width is 340 px + the icon width (if show_icons=true).
|
|
506
|
+
Numbers are picked by looking at existing dimensions on production.
|
|
507
|
+
*/
|
|
508
|
+
const iconWidth = show_icons && statItems.length ? 16 + /* padding */ 1 : 0;
|
|
509
|
+
const minCardWidth =
|
|
510
|
+
(hide_rank
|
|
511
|
+
? clampValue(50 /* padding */ + calculateTextWidth() * 2, CARD_MIN_WIDTH, Infinity)
|
|
512
|
+
: statItems.length
|
|
513
|
+
? RANK_CARD_MIN_WIDTH
|
|
514
|
+
: RANK_ONLY_CARD_MIN_WIDTH) + iconWidth;
|
|
515
|
+
const defaultCardWidth =
|
|
516
|
+
(hide_rank
|
|
517
|
+
? CARD_DEFAULT_WIDTH
|
|
518
|
+
: statItems.length
|
|
519
|
+
? RANK_CARD_DEFAULT_WIDTH
|
|
520
|
+
: RANK_ONLY_CARD_DEFAULT_WIDTH) + iconWidth;
|
|
521
|
+
const width = card_width
|
|
522
|
+
? isNaN(card_width)
|
|
523
|
+
? Math.max(defaultCardWidth, minCardWidth)
|
|
524
|
+
: card_width
|
|
525
|
+
: Math.max(defaultCardWidth, minCardWidth);
|
|
526
|
+
|
|
527
|
+
const card = new Card({
|
|
528
|
+
customTitle: custom_title,
|
|
529
|
+
defaultTitle: statItems.length ? i18n.t('statcard.title') : i18n.t('statcard.ranktitle'),
|
|
530
|
+
width,
|
|
531
|
+
height,
|
|
532
|
+
border_radius,
|
|
533
|
+
colors: { light: lightColors, dark: darkColors },
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
card.setHideBorder(hide_border);
|
|
537
|
+
card.setHideTitle(hide_title);
|
|
538
|
+
const cardStyles = ({ ringColor, textColor, iconColor }: CardColors): string =>
|
|
539
|
+
getStyles({ ringColor, textColor, iconColor, show_icons, progress });
|
|
540
|
+
|
|
541
|
+
card.setCSS({ light: cardStyles, dark: cardStyles });
|
|
542
|
+
|
|
543
|
+
if (disable_animations) {
|
|
544
|
+
card.disableAnimations();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Calculates the right rank circle translation values such that the rank circle
|
|
549
|
+
* keeps respecting the following padding:
|
|
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.
|
|
554
|
+
*
|
|
555
|
+
* @returns Rank circle translation value.
|
|
556
|
+
*/
|
|
557
|
+
const calculateRankXTranslation = (): number => {
|
|
558
|
+
if (statItems.length) {
|
|
559
|
+
const minXTranslation = RANK_CARD_MIN_WIDTH + iconWidth - 70;
|
|
560
|
+
if (width > RANK_CARD_DEFAULT_WIDTH) {
|
|
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
|
+
};
|
|
570
|
+
|
|
571
|
+
// Conditionally rendered elements
|
|
572
|
+
const rankCircle = hide_rank
|
|
573
|
+
? ''
|
|
574
|
+
: `<g data-testid="rank-circle"
|
|
575
|
+
transform="translate(${calculateRankXTranslation()}, ${height / 2 - 50})">
|
|
576
|
+
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
|
577
|
+
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
|
578
|
+
<g class="rank-text">
|
|
579
|
+
${rankIcon(rank_icon, rank.level, rank.percentile)}
|
|
580
|
+
</g>
|
|
581
|
+
</g>`;
|
|
582
|
+
|
|
583
|
+
// Accessibility Labels
|
|
584
|
+
const labels = Object.entries(STATS)
|
|
585
|
+
.filter(([key]) => !hide.includes(key))
|
|
586
|
+
.map(([key, stat]) => {
|
|
587
|
+
if (key === 'commits') {
|
|
588
|
+
return `${i18n.t('statcard.commits')} ${getTotalCommitsYearLabel(
|
|
589
|
+
include_all_commits,
|
|
590
|
+
commits_year,
|
|
591
|
+
i18n,
|
|
592
|
+
)} : ${stat.value}`;
|
|
593
|
+
}
|
|
594
|
+
return `${stat.label}: ${stat.value}`;
|
|
595
|
+
})
|
|
596
|
+
.join(', ');
|
|
597
|
+
|
|
598
|
+
card.setAccessibilityLabel({
|
|
599
|
+
title: `${card.title}, Rank: ${rank.level}`,
|
|
600
|
+
desc: labels,
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
return card.render(`
|
|
604
|
+
${rankCircle}
|
|
605
|
+
<svg x="0" y="0">
|
|
606
|
+
${flexLayout({
|
|
607
|
+
items: statItems,
|
|
608
|
+
gap: lheight,
|
|
609
|
+
direction: 'column',
|
|
610
|
+
}).join('')}
|
|
611
|
+
</svg>
|
|
612
|
+
`);
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
export { RANK_ICONS, renderStatsCard };
|