@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.
Files changed (249) hide show
  1. package/README.md +0 -10
  2. package/build/api/api-result.d.ts +6 -7
  3. package/build/api/api-result.d.ts.map +1 -1
  4. package/build/api/api-result.js +4 -5
  5. package/build/api/contributed-to.d.ts +23 -0
  6. package/build/api/contributed-to.d.ts.map +1 -0
  7. package/build/api/contributed-to.js +43 -0
  8. package/build/api/gist.d.ts +8 -22
  9. package/build/api/gist.d.ts.map +1 -1
  10. package/build/api/gist.js +14 -36
  11. package/build/api/handler.d.ts +16 -0
  12. package/build/api/handler.d.ts.map +1 -0
  13. package/build/api/handler.js +29 -0
  14. package/build/api/index.d.ts +13 -12
  15. package/build/api/index.d.ts.map +1 -1
  16. package/build/api/index.js +1 -0
  17. package/build/api/params.d.ts +21 -12
  18. package/build/api/params.d.ts.map +1 -1
  19. package/build/api/params.js +41 -19
  20. package/build/api/pin.d.ts +11 -31
  21. package/build/api/pin.d.ts.map +1 -1
  22. package/build/api/pin.js +35 -63
  23. package/build/api/stats.d.ts +15 -16
  24. package/build/api/stats.d.ts.map +1 -1
  25. package/build/api/stats.js +60 -99
  26. package/build/api/top-langs.d.ts +13 -18
  27. package/build/api/top-langs.d.ts.map +1 -1
  28. package/build/api/top-langs.js +35 -72
  29. package/build/api/wakatime.d.ts +11 -17
  30. package/build/api/wakatime.d.ts.map +1 -1
  31. package/build/api/wakatime.js +27 -64
  32. package/build/calculateRank.d.ts +0 -9
  33. package/build/calculateRank.d.ts.map +1 -1
  34. package/build/calculateRank.js +12 -17
  35. package/build/cards/contributed-to.d.ts +19 -0
  36. package/build/cards/contributed-to.d.ts.map +1 -0
  37. package/build/cards/contributed-to.js +242 -0
  38. package/build/cards/gist.d.ts +3 -4
  39. package/build/cards/gist.d.ts.map +1 -1
  40. package/build/cards/gist.js +38 -43
  41. package/build/cards/options.d.ts +1 -1
  42. package/build/cards/repo.d.ts +14 -5
  43. package/build/cards/repo.d.ts.map +1 -1
  44. package/build/cards/repo.js +93 -103
  45. package/build/cards/stats.d.ts +18 -11
  46. package/build/cards/stats.d.ts.map +1 -1
  47. package/build/cards/stats.js +191 -222
  48. package/build/cards/top-languages.d.ts +18 -33
  49. package/build/cards/top-languages.d.ts.map +1 -1
  50. package/build/cards/top-languages.js +196 -387
  51. package/build/cards/wakatime.d.ts +17 -8
  52. package/build/cards/wakatime.d.ts.map +1 -1
  53. package/build/cards/wakatime.js +133 -187
  54. package/build/common/Card.d.ts +20 -59
  55. package/build/common/Card.d.ts.map +1 -1
  56. package/build/common/Card.js +131 -198
  57. package/build/common/I18n.d.ts +1 -7
  58. package/build/common/I18n.d.ts.map +1 -1
  59. package/build/common/I18n.js +7 -8
  60. package/build/common/brand.d.ts +76 -0
  61. package/build/common/brand.d.ts.map +1 -0
  62. package/build/common/brand.js +73 -0
  63. package/build/common/color.d.ts +4 -17
  64. package/build/common/color.d.ts.map +1 -1
  65. package/build/common/color.js +67 -49
  66. package/build/common/config.d.ts +4 -14
  67. package/build/common/config.d.ts.map +1 -1
  68. package/build/common/config.js +3 -16
  69. package/build/common/constants.d.ts +6 -2
  70. package/build/common/constants.d.ts.map +1 -1
  71. package/build/common/constants.js +5 -1
  72. package/build/common/date.d.ts +50 -5
  73. package/build/common/date.d.ts.map +1 -1
  74. package/build/common/date.js +104 -4
  75. package/build/common/emojiMap.d.ts +8 -0
  76. package/build/common/emojiMap.d.ts.map +1 -0
  77. package/build/common/emojiMap.js +10 -0
  78. package/build/common/emojiMap.json +1915 -0
  79. package/build/common/error.d.ts +1 -10
  80. package/build/common/error.d.ts.map +1 -1
  81. package/build/common/error.js +1 -10
  82. package/build/common/fmt.d.ts +3 -15
  83. package/build/common/fmt.d.ts.map +1 -1
  84. package/build/common/fmt.js +7 -35
  85. package/build/common/http.d.ts +2 -7
  86. package/build/common/http.d.ts.map +1 -1
  87. package/build/common/http.js +8 -17
  88. package/build/common/icons.d.ts +20 -20
  89. package/build/common/icons.d.ts.map +1 -1
  90. package/build/common/icons.js +108 -67
  91. package/build/common/languageColors.d.ts +0 -1
  92. package/build/common/languageColors.d.ts.map +1 -1
  93. package/build/common/languageColors.js +1 -4
  94. package/build/common/languageColors.json +18 -2
  95. package/build/common/ops.d.ts +11 -17
  96. package/build/common/ops.d.ts.map +1 -1
  97. package/build/common/ops.js +22 -39
  98. package/build/common/render.d.ts +34 -82
  99. package/build/common/render.d.ts.map +1 -1
  100. package/build/common/render.js +225 -259
  101. package/build/common/retryer.d.ts +2 -7
  102. package/build/common/retryer.d.ts.map +1 -1
  103. package/build/common/retryer.js +4 -11
  104. package/build/fetchers/contributed-to.d.ts +62 -0
  105. package/build/fetchers/contributed-to.d.ts.map +1 -0
  106. package/build/fetchers/contributed-to.js +184 -0
  107. package/build/fetchers/gist.d.ts +2 -5
  108. package/build/fetchers/gist.d.ts.map +1 -1
  109. package/build/fetchers/gist.js +1 -5
  110. package/build/fetchers/graphql-error.d.ts +13 -0
  111. package/build/fetchers/graphql-error.d.ts.map +1 -0
  112. package/build/fetchers/graphql-error.js +29 -0
  113. package/build/fetchers/index.d.ts +13 -12
  114. package/build/fetchers/index.d.ts.map +1 -1
  115. package/build/fetchers/index.js +1 -0
  116. package/build/fetchers/repo.d.ts +2 -11
  117. package/build/fetchers/repo.d.ts.map +1 -1
  118. package/build/fetchers/repo.js +20 -33
  119. package/build/fetchers/stats.d.ts +16 -40
  120. package/build/fetchers/stats.d.ts.map +1 -1
  121. package/build/fetchers/stats.js +89 -271
  122. package/build/fetchers/top-languages.d.ts +2 -9
  123. package/build/fetchers/top-languages.d.ts.map +1 -1
  124. package/build/fetchers/top-languages.js +16 -47
  125. package/build/fetchers/types.d.ts +25 -1
  126. package/build/fetchers/types.d.ts.map +1 -1
  127. package/build/fetchers/wakatime.d.ts +2 -6
  128. package/build/fetchers/wakatime.d.ts.map +1 -1
  129. package/build/fetchers/wakatime.js +1 -5
  130. package/build/graphql/contributionsCollection.d.ts +9 -0
  131. package/build/graphql/contributionsCollection.d.ts.map +1 -0
  132. package/build/graphql/contributionsCollection.js +12 -0
  133. package/build/graphql/contributionsDocument.d.ts +6 -6
  134. package/build/graphql/contributionsDocument.d.ts.map +1 -1
  135. package/build/graphql/contributionsDocument.js +8 -16
  136. package/build/graphql/generated/contributed-to.d.ts +22 -0
  137. package/build/graphql/generated/contributed-to.d.ts.map +1 -0
  138. package/build/graphql/generated/contributed-to.js +11 -0
  139. package/build/graphql/generated/gist.d.ts +1 -1
  140. package/build/graphql/generated/repo.d.ts +1 -1
  141. package/build/graphql/generated/stats.d.ts +16 -4
  142. package/build/graphql/generated/stats.d.ts.map +1 -1
  143. package/build/graphql/generated/stats.js +2 -2
  144. package/build/graphql/generated/top-languages.d.ts +2 -2
  145. package/build/graphql/graphqlDocument.d.ts +1 -2
  146. package/build/graphql/graphqlDocument.d.ts.map +1 -1
  147. package/build/graphql/graphqlDocument.js +0 -1
  148. package/build/graphql/reposContributedToDocument.d.ts +4 -5
  149. package/build/graphql/reposContributedToDocument.d.ts.map +1 -1
  150. package/build/graphql/reposContributedToDocument.js +11 -7
  151. package/build/markup/css.d.ts +35 -0
  152. package/build/markup/css.d.ts.map +1 -0
  153. package/build/markup/css.js +64 -0
  154. package/build/markup/element.d.ts +41 -0
  155. package/build/markup/element.d.ts.map +1 -0
  156. package/build/markup/element.js +127 -0
  157. package/build/markup/html.d.ts +13 -0
  158. package/build/markup/html.d.ts.map +1 -0
  159. package/build/markup/html.js +27 -0
  160. package/build/markup/index.d.ts +9 -0
  161. package/build/markup/index.d.ts.map +1 -0
  162. package/build/markup/index.js +6 -0
  163. package/build/markup/serialize.d.ts +16 -0
  164. package/build/markup/serialize.d.ts.map +1 -0
  165. package/build/markup/serialize.js +17 -0
  166. package/build/themes/index.d.ts +332 -333
  167. package/build/themes/index.d.ts.map +1 -1
  168. package/build/themes/index.js +1 -1
  169. package/build/translations.d.ts +66 -7
  170. package/build/translations.d.ts.map +1 -1
  171. package/build/translations.js +686 -629
  172. package/package.json +17 -26
  173. package/src/api/api-result.ts +10 -9
  174. package/src/api/contributed-to.ts +84 -0
  175. package/src/api/gist.ts +22 -46
  176. package/src/api/handler.ts +42 -0
  177. package/src/api/index.ts +13 -12
  178. package/src/api/params.ts +74 -29
  179. package/src/api/pin.ts +38 -65
  180. package/src/api/stats.ts +81 -118
  181. package/src/api/top-langs.ts +38 -80
  182. package/src/api/wakatime.ts +35 -73
  183. package/src/calculateRank.ts +12 -23
  184. package/src/cards/contributed-to.ts +373 -0
  185. package/src/cards/gist.ts +55 -49
  186. package/src/cards/options.ts +1 -1
  187. package/src/cards/repo.ts +134 -120
  188. package/src/cards/stats.ts +262 -252
  189. package/src/cards/top-languages.ts +315 -422
  190. package/src/cards/wakatime.ts +207 -225
  191. package/src/common/Card.ts +207 -214
  192. package/src/common/I18n.ts +7 -8
  193. package/src/common/brand.ts +109 -0
  194. package/src/common/color.ts +80 -57
  195. package/src/common/config.ts +5 -18
  196. package/src/common/constants.ts +8 -2
  197. package/src/common/date.ts +135 -5
  198. package/src/common/emojiMap.json +1915 -0
  199. package/src/common/emojiMap.ts +13 -0
  200. package/src/common/error.ts +2 -11
  201. package/src/common/fmt.ts +7 -44
  202. package/src/common/http.ts +9 -15
  203. package/src/common/icons.ts +122 -42
  204. package/src/common/languageColors.json +18 -2
  205. package/src/common/languageColors.ts +1 -4
  206. package/src/common/ops.ts +31 -48
  207. package/src/common/render.ts +334 -284
  208. package/src/common/retryer.ts +8 -15
  209. package/src/fetchers/contributed-to.ts +271 -0
  210. package/src/fetchers/gist.ts +8 -12
  211. package/src/fetchers/graphql-error.ts +36 -0
  212. package/src/fetchers/index.ts +15 -12
  213. package/src/fetchers/repo.ts +27 -40
  214. package/src/fetchers/stats.ts +157 -366
  215. package/src/fetchers/top-languages.ts +27 -55
  216. package/src/fetchers/types.ts +27 -1
  217. package/src/fetchers/wakatime.ts +5 -9
  218. package/src/graphql/contributionsCollection.ts +18 -0
  219. package/src/graphql/contributionsDocument.ts +14 -19
  220. package/src/graphql/generated/contributed-to.ts +25 -0
  221. package/src/graphql/generated/gist.ts +1 -1
  222. package/src/graphql/generated/repo.ts +1 -1
  223. package/src/graphql/generated/stats.ts +21 -8
  224. package/src/graphql/generated/top-languages.ts +2 -2
  225. package/src/graphql/graphqlDocument.ts +1 -2
  226. package/src/graphql/queries/contributed-to.graphql +8 -0
  227. package/src/graphql/queries/stats.graphql +14 -2
  228. package/src/graphql/reposContributedToDocument.ts +16 -15
  229. package/src/markup/css.ts +118 -0
  230. package/src/markup/element.ts +246 -0
  231. package/src/markup/html.ts +34 -0
  232. package/src/markup/index.ts +9 -0
  233. package/src/markup/serialize.ts +27 -0
  234. package/src/themes/index.ts +1 -1
  235. package/src/translations.ts +699 -629
  236. package/build/cards/index.d.ts +0 -11
  237. package/build/cards/index.d.ts.map +0 -1
  238. package/build/cards/index.js +0 -7
  239. package/build/common/html.d.ts +0 -8
  240. package/build/common/html.d.ts.map +0 -1
  241. package/build/common/html.js +0 -14
  242. package/build/index.d.ts +0 -15
  243. package/build/index.d.ts.map +0 -1
  244. package/build/index.js +0 -10
  245. package/src/_emoji-name-map.d.ts +0 -10
  246. package/src/_github-username-regex.d.ts +0 -4
  247. package/src/cards/index.ts +0 -21
  248. package/src/common/html.ts +0 -17
  249. package/src/index.ts +0 -18
@@ -1,13 +1,14 @@
1
+ import { CARD_ICON, CARD_WIDTH, FONT_WEIGHT, firefoxFontSize, font } from '../common/brand.js';
1
2
  import { Card } from '../common/Card.js';
2
3
  import { getLightDarkColors, isPrefixedHexColor } from '../common/color.js';
3
4
  import { formatBytes } from '../common/fmt.js';
4
- import { encodeHTML } from '../common/html.js';
5
5
  import { I18n } from '../common/I18n.js';
6
6
  import { DEFAULT_LANG_COLOR } from '../common/languageColors.js';
7
7
  import { chunkArray, clampValue, lowercaseTrim } from '../common/ops.js';
8
8
  import { createProgressNode, flexLayout, measureText } from '../common/render.js';
9
+ import { atRule, el, rule } from '../markup/index.js';
9
10
  import { langCardLocales } from '../translations.js';
10
- const DEFAULT_CARD_WIDTH = 300;
11
+ const DEFAULT_CARD_WIDTH = CARD_WIDTH.compact;
11
12
  const MIN_CARD_WIDTH = 280;
12
13
  const CARD_PADDING = 25;
13
14
  const COMPACT_LAYOUT_BASE_HEIGHT = 90;
@@ -15,6 +16,8 @@ const MAXIMUM_LANGS_COUNT = 20;
15
16
  const NORMAL_LAYOUT_DEFAULT_LANGS_COUNT = 5;
16
17
  const COMPACT_LAYOUT_DEFAULT_LANGS_COUNT = 6;
17
18
  const DONUT_LAYOUT_DEFAULT_LANGS_COUNT = 5;
19
+ /** Room for the chart that sits beside the language list. */
20
+ const DONUT_EXTRA_WIDTH = 50;
18
21
  const PIE_LAYOUT_DEFAULT_LANGS_COUNT = 6;
19
22
  const DONUT_VERTICAL_LAYOUT_DEFAULT_LANGS_COUNT = 6;
20
23
  /** Layouts the card can draw; the api validates `layout` against this. */
@@ -24,31 +27,32 @@ const TOP_LANG_STATS_FORMATS = ['percentages', 'bytes'];
24
27
  /**
25
28
  * Retrieves the programming language whose name is the longest.
26
29
  *
27
- * @param arr Array of programming languages.
28
30
  * @returns Longest programming language object.
29
31
  */
30
- const getLongestLang = (arr) => arr.reduce((savedLang, lang) => (lang.name.length > savedLang.name.length ? lang : savedLang), { name: '', size: 0, color: '' });
32
+ const getLongestLang = (arr) => {
33
+ let longest = { name: '', size: 0, color: '' };
34
+ for (const lang of arr) {
35
+ if (lang.name.length > longest.name.length) {
36
+ longest = lang;
37
+ }
38
+ }
39
+ return longest;
40
+ };
31
41
  /**
32
42
  * Convert degrees to radians.
33
43
  *
34
- * @param angleInDegrees Angle in degrees.
35
44
  * @returns Angle in radians.
36
45
  */
37
- const degreesToRadians = (angleInDegrees) => angleInDegrees * (Math.PI / 180.0);
46
+ const degreesToRadians = (angleInDegrees) => angleInDegrees * (Math.PI / 180);
38
47
  /**
39
48
  * Convert radians to degrees.
40
49
  *
41
- * @param angleInRadians Angle in radians.
42
50
  * @returns Angle in degrees.
43
51
  */
44
- const radiansToDegrees = (angleInRadians) => angleInRadians / (Math.PI / 180.0);
52
+ const radiansToDegrees = (angleInRadians) => angleInRadians / (Math.PI / 180);
45
53
  /**
46
54
  * Convert polar coordinates to cartesian coordinates.
47
55
  *
48
- * @param centerX Center x coordinate.
49
- * @param centerY Center y coordinate.
50
- * @param radius Radius of the circle.
51
- * @param angleInDegrees Angle in degrees.
52
56
  * @returns Cartesian coordinates.
53
57
  */
54
58
  const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => {
@@ -61,14 +65,10 @@ const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => {
61
65
  /**
62
66
  * Convert cartesian coordinates to polar coordinates.
63
67
  *
64
- * @param centerX Center x coordinate.
65
- * @param centerY Center y coordinate.
66
- * @param x Point x coordinate.
67
- * @param y Point y coordinate.
68
68
  * @returns Polar coordinates.
69
69
  */
70
70
  const cartesianToPolar = (centerX, centerY, x, y) => {
71
- const radius = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
71
+ const radius = Math.hypot(x - centerX, y - centerY);
72
72
  let angleInDegrees = radiansToDegrees(Math.atan2(y - centerY, x - centerX));
73
73
  if (angleInDegrees < 0) {
74
74
  angleInDegrees += 360;
@@ -78,72 +78,48 @@ const cartesianToPolar = (centerX, centerY, x, y) => {
78
78
  /**
79
79
  * Calculates length of circle.
80
80
  *
81
- * @param radius Radius of the circle.
82
81
  * @returns The length of the circle.
83
82
  */
84
- const getCircleLength = (radius) => {
85
- return 2 * Math.PI * radius;
86
- };
83
+ const getCircleLength = (radius) => 2 * Math.PI * radius;
87
84
  /**
88
85
  * Calculates height for the compact layout.
89
86
  *
90
- * @param totalLangs Total number of languages.
91
87
  * @returns Card height.
92
88
  */
93
- const calculateCompactLayoutHeight = (totalLangs) => {
94
- return COMPACT_LAYOUT_BASE_HEIGHT + Math.round(totalLangs / 2) * 25;
95
- };
89
+ const calculateCompactLayoutHeight = (totalLangs) => COMPACT_LAYOUT_BASE_HEIGHT + Math.round(totalLangs / 2) * 25;
96
90
  /**
97
91
  * Calculates height for the normal layout.
98
92
  *
99
- * @param totalLangs Total number of languages.
100
93
  * @returns Card height.
101
94
  */
102
- const calculateNormalLayoutHeight = (totalLangs) => {
103
- return 45 + (totalLangs + 1) * 40;
104
- };
95
+ const calculateNormalLayoutHeight = (totalLangs) => 45 + (totalLangs + 1) * 40;
105
96
  /**
106
97
  * Calculates height for the donut layout.
107
98
  *
108
- * @param totalLangs Total number of languages.
109
99
  * @returns Card height.
110
100
  */
111
- const calculateDonutLayoutHeight = (totalLangs) => {
112
- return 215 + Math.max(totalLangs - 5, 0) * 32;
113
- };
101
+ const calculateDonutLayoutHeight = (totalLangs) => 215 + Math.max(totalLangs - 5, 0) * 32;
114
102
  /**
115
103
  * Calculates height for the donut vertical layout.
116
104
  *
117
- * @param totalLangs Total number of languages.
118
105
  * @returns Card height.
119
106
  */
120
- const calculateDonutVerticalLayoutHeight = (totalLangs) => {
121
- return 300 + Math.round(totalLangs / 2) * 25;
122
- };
107
+ const calculateDonutVerticalLayoutHeight = (totalLangs) => 300 + Math.round(totalLangs / 2) * 25;
123
108
  /**
124
109
  * Calculates height for the pie layout.
125
110
  *
126
- * @param totalLangs Total number of languages.
127
111
  * @returns Card height.
128
112
  */
129
- const calculatePieLayoutHeight = (totalLangs) => {
130
- return 300 + Math.round(totalLangs / 2) * 25;
131
- };
113
+ const calculatePieLayoutHeight = (totalLangs) => 300 + Math.round(totalLangs / 2) * 25;
132
114
  /**
133
115
  * Calculates the center translation needed to keep the donut chart centred.
134
116
  *
135
- * @param totalLangs Total number of languages.
136
117
  * @returns Donut center translation.
137
118
  */
138
- const donutCenterTranslation = (totalLangs) => {
139
- return -45 + Math.max(totalLangs - 5, 0) * 16;
140
- };
119
+ const donutCenterTranslation = (totalLangs) => -45 + Math.max(totalLangs - 5, 0) * 16;
141
120
  /**
142
121
  * Trim top languages to lang_count while also hiding certain languages.
143
122
  *
144
- * @param topLangs Top languages.
145
- * @param langs_count Number of languages to show.
146
- * @param hide Languages to hide.
147
123
  * @returns Trimmed top languages and total size.
148
124
  */
149
125
  const trimTopLanguages = (topLangs, langs_count, hide) => {
@@ -152,13 +128,13 @@ const trimTopLanguages = (topLangs, langs_count, hide) => {
152
128
  const langsCount = clampValue(langs_count, 1, MAXIMUM_LANGS_COUNT);
153
129
  // populate langsToHide map for quick lookup while filtering out
154
130
  if (hide) {
155
- hide.forEach((langName) => {
131
+ for (const langName of hide) {
156
132
  langsToHide[lowercaseTrim(langName)] = true;
157
- });
133
+ }
158
134
  }
159
135
  // filter out languages to be hidden
160
136
  langs = langs
161
- .sort((a, b) => b.size - a.size)
137
+ .toSorted((a, b) => b.size - a.size)
162
138
  .filter((lang) => !langsToHide[lowercaseTrim(lang.name)])
163
139
  .slice(0, langsCount);
164
140
  const totalLanguageSize = langs.reduce((acc, curr) => acc + curr.size, 0);
@@ -167,19 +143,13 @@ const trimTopLanguages = (topLangs, langs_count, hide) => {
167
143
  /**
168
144
  * Get display value corresponding to the format.
169
145
  *
170
- * @param size Bytes size.
171
- * @param percentages Percentage value.
172
- * @param format Format of the stats.
173
146
  * @returns Display value.
174
147
  */
175
- const getDisplayValue = (size, percentages, format) => {
176
- return format === 'bytes' ? formatBytes(size) : `${percentages.toFixed(2)}%`;
177
- };
148
+ const getDisplayValue = (size, percentages, format) => format === 'bytes' ? formatBytes(size) : `${percentages.toFixed(2)}%`;
178
149
  /**
179
150
  * Resolves a language's display color, falling back to the default, and
180
151
  * validates it is a prefixed hex color.
181
152
  *
182
- * @param lang Programming language object.
183
153
  * @returns Validated language color.
184
154
  */
185
155
  const resolveLangColor = (lang) => {
@@ -192,15 +162,6 @@ const resolveLangColor = (lang) => {
192
162
  /**
193
163
  * Create progress bar text item for a programming language.
194
164
  *
195
- * @param props Function properties.
196
- * @param props.width The card width
197
- * @param props.color Color of the programming language.
198
- * @param props.name Name of the programming language.
199
- * @param props.size Size of the programming language.
200
- * @param props.totalSize Total size of all languages.
201
- * @param props.statsFormat Stats format.
202
- * @param props.hideValues Whether to hide stats values.
203
- * @param props.index Index of the programming language.
204
165
  * @returns Programming language SVG node.
205
166
  */
206
167
  const createProgressTextNode = ({ width, color, name, size, totalSize, statsFormat, hideValues, index, }) => {
@@ -210,31 +171,18 @@ const createProgressTextNode = ({ width, color, name, size, totalSize, statsForm
210
171
  const progressWidth = width - paddingRight;
211
172
  const progress = (size / totalSize) * 100;
212
173
  const displayValue = getDisplayValue(size, progress, statsFormat);
213
- return `
214
- <g class="stagger" style="animation-delay: ${staggerDelay}ms">
215
- <text data-testid="lang-name" x="2" y="15" class="lang-name">${encodeHTML(name)}</text>
216
- ${hideValues ? '' : `<text x="${progressTextX}" y="34" class="lang-name">${encodeHTML(displayValue)}</text>`}
217
- ${createProgressNode({
174
+ return el('g', { class: 'stagger', style: `animation-delay: ${staggerDelay}ms` }, el('text', { 'data-testid': 'lang-name', x: 2, y: 15, class: 'lang-name' }, name), !hideValues && el('text', { x: progressTextX, y: 34, class: 'lang-name' }, displayValue), createProgressNode({
218
175
  x: 0,
219
176
  y: 25,
220
177
  color,
221
178
  width: progressWidth,
222
179
  progress,
223
180
  delay: staggerDelay + 300,
224
- })}
225
- </g>
226
- `;
181
+ }));
227
182
  };
228
183
  /**
229
184
  * Creates compact text item for a programming language.
230
185
  *
231
- * @param props Function properties.
232
- * @param props.lang Programming language object.
233
- * @param props.totalSize Total size of all languages.
234
- * @param props.hideProgress Whether to hide percentage.
235
- * @param props.hideValues Whether to hide stats values (percentages/bytes).
236
- * @param props.statsFormat Stats format
237
- * @param props.index Index of the programming language.
238
186
  * @returns Compact layout programming language SVG node.
239
187
  */
240
188
  const createCompactLangNode = ({ lang, totalSize, hideProgress, hideValues, statsFormat = 'percentages', index, }) => {
@@ -242,24 +190,12 @@ const createCompactLangNode = ({ lang, totalSize, hideProgress, hideValues, stat
242
190
  const displayValue = getDisplayValue(lang.size, percentages, statsFormat);
243
191
  const staggerDelay = (index + 3) * 150;
244
192
  const color = resolveLangColor(lang);
245
- return `
246
- <g class="stagger" style="animation-delay: ${staggerDelay}ms">
247
- <circle cx="5" cy="6" r="5" fill="${color}" />
248
- <text data-testid="lang-name" x="15" y="10" class='lang-name'>
249
- ${encodeHTML(lang.name)} ${hideProgress || hideValues ? '' : encodeHTML(displayValue)}
250
- </text>
251
- </g>
252
- `;
193
+ const label = hideProgress || hideValues ? lang.name : `${lang.name} ${displayValue}`;
194
+ return el('g', { class: 'stagger', style: `animation-delay: ${staggerDelay}ms` }, el('circle', { cx: 5, cy: 6, r: 5, fill: color }), el('text', { 'data-testid': 'lang-name', x: 15, y: 10, class: 'lang-name' }, label));
253
195
  };
254
196
  /**
255
197
  * Create compact languages text items for all programming languages.
256
198
  *
257
- * @param props Function properties.
258
- * @param props.langs Array of programming languages.
259
- * @param props.totalSize Total size of all languages.
260
- * @param props.hideProgress Whether to hide percentage.
261
- * @param props.hideValues Whether to hide stats values.
262
- * @param props.statsFormat Stats format
263
199
  * @returns Programming languages SVG node.
264
200
  */
265
201
  const createLanguageTextNode = ({ langs, totalSize, hideProgress, hideValues, statsFormat, }) => {
@@ -274,83 +210,52 @@ const createLanguageTextNode = ({ langs, totalSize, hideProgress, hideValues, st
274
210
  statsFormat,
275
211
  index,
276
212
  }));
277
- return flexLayout({
278
- items,
279
- gap: 25,
280
- direction: 'column',
281
- }).join('');
213
+ return flexLayout({ items, gap: 25, direction: 'column' });
282
214
  });
283
215
  const percent = ((longestLang.size / totalSize) * 100).toFixed(2);
284
216
  const minGap = 150;
285
217
  const maxGap = 20 + measureText(`${longestLang.name} ${percent}%`, 11);
286
- return flexLayout({
287
- items: layouts,
288
- gap: maxGap < minGap ? minGap : maxGap,
289
- }).join('');
218
+ return flexLayout({ items: layouts, gap: maxGap < minGap ? minGap : maxGap });
290
219
  };
291
220
  /**
292
221
  * Create donut languages text items for all programming languages.
293
222
  *
294
- * @param props Function properties.
295
- * @param props.langs Array of programming languages.
296
- * @param props.totalSize Total size of all languages.
297
- * @param props.hideValues Whether to hide stats values.
298
- * @param props.statsFormat Stats format
299
223
  * @returns Donut layout programming language SVG node.
300
224
  */
301
- const createDonutLanguagesNode = ({ langs, totalSize, hideValues, statsFormat, }) => {
302
- return flexLayout({
303
- items: langs.map((lang, index) => {
304
- return createCompactLangNode({
305
- lang,
306
- totalSize,
307
- hideProgress: false,
308
- hideValues,
309
- statsFormat,
310
- index,
311
- });
312
- }),
313
- gap: 32,
314
- direction: 'column',
315
- }).join('');
316
- };
225
+ const createDonutLanguagesNode = ({ langs, totalSize, hideValues, statsFormat, }) => flexLayout({
226
+ items: langs.map((lang, index) => createCompactLangNode({
227
+ lang,
228
+ totalSize,
229
+ hideProgress: false,
230
+ hideValues,
231
+ statsFormat,
232
+ index,
233
+ })),
234
+ gap: 32,
235
+ direction: 'column',
236
+ });
317
237
  /**
318
238
  * Renders the default language card layout.
319
239
  *
320
- * @param langs Array of programming languages.
321
- * @param width Card width.
322
- * @param totalLanguageSize Total size of all languages.
323
- * @param statsFormat Stats format.
324
- * @param hideValues Whether to hide stats values.
325
240
  * @returns Normal layout card SVG object.
326
241
  */
327
- const renderNormalLayout = (langs, width, totalLanguageSize, statsFormat, hideValues) => {
328
- return flexLayout({
329
- items: langs.map((lang, index) => {
330
- return createProgressTextNode({
331
- width,
332
- name: lang.name,
333
- color: lang.color || DEFAULT_LANG_COLOR,
334
- size: lang.size,
335
- totalSize: totalLanguageSize,
336
- statsFormat,
337
- hideValues,
338
- index,
339
- });
340
- }),
341
- gap: 40,
342
- direction: 'column',
343
- }).join('');
344
- };
242
+ const renderNormalLayout = (langs, width, totalLanguageSize, statsFormat, hideValues) => flexLayout({
243
+ items: langs.map((lang, index) => createProgressTextNode({
244
+ width,
245
+ name: lang.name,
246
+ color: lang.color || DEFAULT_LANG_COLOR,
247
+ size: lang.size,
248
+ totalSize: totalLanguageSize,
249
+ statsFormat,
250
+ hideValues,
251
+ index,
252
+ })),
253
+ gap: 40,
254
+ direction: 'column',
255
+ });
345
256
  /**
346
257
  * Renders the compact language card layout.
347
258
  *
348
- * @param langs Array of programming languages.
349
- * @param width Card width.
350
- * @param totalLanguageSize Total size of all languages.
351
- * @param hideProgress Whether to hide progress bar.
352
- * @param statsFormat Stats format.
353
- * @param hideValues Whether to hide stats values.
354
259
  * @returns Compact layout card SVG object.
355
260
  */
356
261
  const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress, statsFormat = 'percentages', hideValues) => {
@@ -359,53 +264,37 @@ const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress, stat
359
264
  // progressOffset holds the previous language's width and used to offset the next language
360
265
  // so that we can stack them one after another, like this: [--][----][---]
361
266
  let progressOffset = 0;
362
- const compactProgressBar = langs
363
- .map((lang) => {
364
- const langColor = resolveLangColor(lang);
365
- const percentage = parseFloat(((lang.size / totalLanguageSize) * offsetWidth).toFixed(2));
366
- const progress = percentage < 10 ? percentage + 10 : percentage;
367
- const output = `
368
- <rect
369
- mask="url(#rect-mask)"
370
- data-testid="lang-progress"
371
- x="${progressOffset}"
372
- y="0"
373
- width="${progress}"
374
- height="8"
375
- fill="${langColor}"
376
- />
377
- `;
267
+ const compactProgressBar = langs.map((lang) => {
268
+ const percentage = Number.parseFloat(((lang.size / totalLanguageSize) * offsetWidth).toFixed(2));
269
+ const x = progressOffset;
378
270
  progressOffset += percentage;
379
- return output;
380
- })
381
- .join('');
382
- return `
383
- ${hideProgress
384
- ? ''
385
- : `
386
- <mask id="rect-mask">
387
- <rect x="0" y="0" width="${offsetWidth}" height="8" fill="white" rx="5"/>
388
- </mask>
389
- ${compactProgressBar}
390
- `}
391
- <g transform="translate(0, ${hideProgress ? '0' : '25'})">
392
- ${createLanguageTextNode({
393
- langs,
394
- totalSize: totalLanguageSize,
395
- hideProgress,
396
- statsFormat,
397
- hideValues,
398
- })}
399
- </g>
400
- `;
271
+ return el('rect', {
272
+ mask: 'url(#rect-mask)',
273
+ 'data-testid': 'lang-progress',
274
+ x,
275
+ y: 0,
276
+ width: percentage < 10 ? percentage + 10 : percentage,
277
+ height: 8,
278
+ fill: resolveLangColor(lang),
279
+ });
280
+ });
281
+ return [
282
+ !hideProgress && [
283
+ el('mask', { id: 'rect-mask' }, el('rect', { x: 0, y: 0, width: offsetWidth, height: 8, fill: 'white', rx: 5 })),
284
+ compactProgressBar,
285
+ ],
286
+ el('g', { transform: `translate(0, ${hideProgress ? 0 : 25})` }, createLanguageTextNode({
287
+ langs,
288
+ totalSize: totalLanguageSize,
289
+ hideProgress,
290
+ statsFormat,
291
+ hideValues,
292
+ })),
293
+ ];
401
294
  };
402
295
  /**
403
296
  * Renders donut vertical layout to display user's most frequently used programming languages.
404
297
  *
405
- * @param langs Array of programming languages.
406
- * @param totalLanguageSize Total size of all languages.
407
- * @param statsFormat Stats format.
408
- * @param hideValues Whether to hide stats values.
409
298
  * @returns Compact layout card SVG object.
410
299
  */
411
300
  const renderDonutVerticalLayout = (langs, totalLanguageSize, statsFormat, hideValues) => {
@@ -424,55 +313,34 @@ const renderDonutVerticalLayout = (langs, totalLanguageSize, statsFormat, hideVa
424
313
  const percentage = (lang.size / totalLanguageSize) * 100;
425
314
  const circleLength = totalCircleLength * (percentage / 100);
426
315
  const delay = startDelayCoefficient * 100;
427
- circles.push(`
428
- <g class="stagger" style="animation-delay: ${delay}ms">
429
- <circle
430
- cx="150"
431
- cy="100"
432
- r="${radius}"
433
- fill="transparent"
434
- stroke="${langColor}"
435
- stroke-width="25"
436
- stroke-dasharray="${totalCircleLength}"
437
- stroke-dashoffset="${indent}"
438
- size="${percentage}"
439
- data-testid="lang-donut"
440
- />
441
- </g>
442
- `);
316
+ circles.push(el('g', { class: 'stagger', style: `animation-delay: ${delay}ms` }, el('circle', {
317
+ cx: 150,
318
+ cy: 100,
319
+ r: radius,
320
+ fill: 'transparent',
321
+ stroke: langColor,
322
+ 'stroke-width': 25,
323
+ 'stroke-dasharray': totalCircleLength,
324
+ 'stroke-dashoffset': indent,
325
+ size: percentage,
326
+ 'data-testid': 'lang-donut',
327
+ })));
443
328
  // Update the indent for the next part
444
329
  indent += circleLength;
445
330
  // Update the start delay coefficient for the next part
446
331
  startDelayCoefficient += 1;
447
332
  }
448
- return `
449
- <svg data-testid="lang-items">
450
- <g transform="translate(0, 0)">
451
- <svg data-testid="donut">
452
- ${circles.join('')}
453
- </svg>
454
- </g>
455
- <g transform="translate(0, 220)">
456
- <svg data-testid="lang-names" x="${CARD_PADDING}">
457
- ${createLanguageTextNode({
333
+ return el('svg', { 'data-testid': 'lang-items' }, el('svg', { 'data-testid': 'donut' }, circles), el('g', { transform: 'translate(0, 220)' }, el('svg', { 'data-testid': 'lang-names', x: CARD_PADDING }, createLanguageTextNode({
458
334
  langs,
459
335
  totalSize: totalLanguageSize,
460
336
  hideProgress: false,
461
337
  statsFormat,
462
338
  hideValues,
463
- })}
464
- </svg>
465
- </g>
466
- </svg>
467
- `;
339
+ }))));
468
340
  };
469
341
  /**
470
342
  * Renders pie layout to display user's most frequently used programming languages.
471
343
  *
472
- * @param langs Array of programming languages.
473
- * @param totalLanguageSize Total size of all languages.
474
- * @param statsFormat Stats format.
475
- * @param hideValues Whether to hide stats values.
476
344
  * @returns Compact layout card SVG object.
477
345
  */
478
346
  const renderPieLayout = (langs, totalLanguageSize, statsFormat, hideValues) => {
@@ -490,17 +358,15 @@ const renderPieLayout = (langs, totalLanguageSize, statsFormat, hideValues) => {
490
358
  for (const lang of langs) {
491
359
  const langColor = resolveLangColor(lang);
492
360
  if (langs.length === 1) {
493
- paths.push(`
494
- <circle
495
- cx="${centerX}"
496
- cy="${centerY}"
497
- r="${radius}"
498
- stroke="none"
499
- fill="${langColor}"
500
- data-testid="lang-pie"
501
- size="100"
502
- />
503
- `);
361
+ paths.push(el('circle', {
362
+ cx: centerX,
363
+ cy: centerY,
364
+ r: radius,
365
+ stroke: 'none',
366
+ fill: langColor,
367
+ 'data-testid': 'lang-pie',
368
+ size: 100,
369
+ }));
504
370
  break;
505
371
  }
506
372
  const langSizePart = lang.size / totalLanguageSize;
@@ -517,49 +383,28 @@ const renderPieLayout = (langs, totalLanguageSize, statsFormat, hideValues) => {
517
383
  // Calculate delay
518
384
  const delay = startDelayCoefficient * 100;
519
385
  // SVG arc markup
520
- paths.push(`
521
- <g class="stagger" style="animation-delay: ${delay}ms">
522
- <path
523
- data-testid="lang-pie"
524
- size="${percentage}"
525
- d="M ${centerX} ${centerY} L ${startPoint.x} ${startPoint.y} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endPoint.x} ${endPoint.y} Z"
526
- fill="${langColor}"
527
- />
528
- </g>
529
- `);
386
+ paths.push(el('g', { class: 'stagger', style: `animation-delay: ${delay}ms` }, el('path', {
387
+ 'data-testid': 'lang-pie',
388
+ size: percentage,
389
+ d: `M ${centerX} ${centerY} L ${startPoint.x} ${startPoint.y} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endPoint.x} ${endPoint.y} Z`,
390
+ fill: langColor,
391
+ })));
530
392
  // Update the start angle for the next part
531
393
  startAngle = endAngle;
532
394
  // Update the start delay coefficient for the next part
533
395
  startDelayCoefficient += 1;
534
396
  }
535
- return `
536
- <svg data-testid="lang-items">
537
- <g transform="translate(0, 0)">
538
- <svg data-testid="pie">
539
- ${paths.join('')}
540
- </svg>
541
- </g>
542
- <g transform="translate(0, 220)">
543
- <svg data-testid="lang-names" x="${CARD_PADDING}">
544
- ${createLanguageTextNode({
397
+ return el('svg', { 'data-testid': 'lang-items' }, el('svg', { 'data-testid': 'pie' }, paths), el('g', { transform: 'translate(0, 220)' }, el('svg', { 'data-testid': 'lang-names', x: CARD_PADDING }, createLanguageTextNode({
545
398
  langs,
546
399
  totalSize: totalLanguageSize,
547
400
  hideProgress: false,
548
401
  statsFormat,
549
402
  hideValues,
550
- })}
551
- </svg>
552
- </g>
553
- </svg>
554
- `;
403
+ }))));
555
404
  };
556
405
  /**
557
406
  * Creates the SVG paths for the language donut chart.
558
407
  *
559
- * @param cx Donut center x-position.
560
- * @param cy Donut center y-position.
561
- * @param radius Donut arc Radius.
562
- * @param percentages Array with donut section percentages.
563
408
  * @returns Array of svg path elements
564
409
  */
565
410
  const createDonutPaths = (cx, cy, radius, percentages) => {
@@ -567,7 +412,7 @@ const createDonutPaths = (cx, cy, radius, percentages) => {
567
412
  let startAngle = 0;
568
413
  const totalPercent = percentages.reduce((acc, curr) => acc + curr, 0);
569
414
  for (const rawPercent of percentages) {
570
- const percent = parseFloat(((rawPercent / totalPercent) * 100).toFixed(2));
415
+ const percent = Number.parseFloat(((rawPercent / totalPercent) * 100).toFixed(2));
571
416
  const endAngle = 3.6 * percent + startAngle;
572
417
  const startPoint = polarToCartesian(cx, cy, radius, endAngle - 90); // rotate donut 90 degrees counter-clockwise.
573
418
  const endPoint = polarToCartesian(cx, cy, radius, startAngle - 90); // rotate donut 90 degrees counter-clockwise.
@@ -583,104 +428,81 @@ const createDonutPaths = (cx, cy, radius, percentages) => {
583
428
  /**
584
429
  * Renders the donut language card layout.
585
430
  *
586
- * @param langs Array of programming languages.
587
- * @param width Card width.
588
- * @param totalLanguageSize Total size of all languages.
589
- * @param statsFormat Stats format.
590
- * @param hideValues Whether to hide stats values.
591
431
  * @returns Donut layout card SVG object.
592
432
  */
593
433
  const renderDonutLayout = (langs, width, totalLanguageSize, statsFormat, hideValues) => {
594
434
  if (!Number.isFinite(width)) {
595
- throw new Error(`Invalid width: "${width}"`);
435
+ throw new TypeError(`Invalid width: "${width}"`);
596
436
  }
597
437
  const centerX = width / 3;
598
438
  const centerY = width / 3;
599
439
  const radius = centerX - 60;
600
440
  const strokeWidth = 12;
601
- const colors = langs.map(resolveLangColor);
602
- const langsPercents = langs.map((lang) => parseFloat(((lang.size / totalLanguageSize) * 100).toFixed(2)));
441
+ const colors = langs.map((lang) => resolveLangColor(lang));
442
+ const langsPercents = langs.map((lang) => Number.parseFloat(((lang.size / totalLanguageSize) * 100).toFixed(2)));
603
443
  const langPaths = createDonutPaths(centerX, centerY, radius, langsPercents);
604
444
  const donutPaths = langs.length === 1
605
- ? `<circle cx="${centerX}" cy="${centerY}" r="${radius}" stroke="${colors[0] ?? DEFAULT_LANG_COLOR}" fill="none" stroke-width="${strokeWidth}" data-testid="lang-donut" size="100"/>`
606
- : langPaths
607
- .map((section, index) => {
608
- const staggerDelay = (index + 3) * 100;
609
- const delay = staggerDelay + 300;
610
- const output = `
611
- <g class="stagger" style="animation-delay: ${delay}ms">
612
- <path
613
- data-testid="lang-donut"
614
- size="${section.percent}"
615
- d="${section.d}"
616
- stroke="${colors[index] ?? DEFAULT_LANG_COLOR}"
617
- fill="none"
618
- stroke-width="${strokeWidth}">
619
- </path>
620
- </g>
621
- `;
622
- return output;
623
- })
624
- .join('');
625
- const donut = `<svg width="${width}" height="${width}">${donutPaths}</svg>`;
626
- return `
627
- <g transform="translate(0, 0)">
628
- <g transform="translate(0, 0)">
629
- ${createDonutLanguagesNode({ langs, totalSize: totalLanguageSize, hideValues, statsFormat })}
630
- </g>
631
-
632
- <g transform="translate(125, ${donutCenterTranslation(langs.length)})">
633
- ${donut}
634
- </g>
635
- </g>
636
- `;
445
+ ? [
446
+ el('circle', {
447
+ cx: centerX,
448
+ cy: centerY,
449
+ r: radius,
450
+ stroke: colors[0] ?? DEFAULT_LANG_COLOR,
451
+ fill: 'none',
452
+ 'stroke-width': strokeWidth,
453
+ 'data-testid': 'lang-donut',
454
+ size: 100,
455
+ }),
456
+ ]
457
+ : langPaths.map((section, index) => el('g', { class: 'stagger', style: `animation-delay: ${(index + 3) * 100 + 300}ms` }, el('path', {
458
+ 'data-testid': 'lang-donut',
459
+ size: section.percent,
460
+ d: section.d,
461
+ stroke: colors[index] ?? DEFAULT_LANG_COLOR,
462
+ fill: 'none',
463
+ 'stroke-width': strokeWidth,
464
+ })));
465
+ return [
466
+ createDonutLanguagesNode({ langs, totalSize: totalLanguageSize, hideValues, statsFormat }),
467
+ el('g', { transform: `translate(125, ${donutCenterTranslation(langs.length)})` }, el('svg', { width, height: width }, donutPaths)),
468
+ ];
637
469
  };
638
470
  /**
639
471
  * Creates the no languages data SVG node.
640
472
  *
641
- * @param props Object with function properties.
642
- * @param props.text No languages data translated text.
643
- * @param props.layout Card layout.
644
473
  * @returns No languages data SVG node string.
645
474
  */
646
- const noLanguagesDataNode = ({ text, layout, }) => {
647
- return `
648
- <text x="${layout === 'pie' || layout === 'donut-vertical' ? CARD_PADDING : 0}" y="11" class="stat bold">${encodeHTML(text)}</text>
649
- `;
650
- };
475
+ const noLanguagesDataNode = ({ text, layout, }) => el('text', {
476
+ x: layout === 'pie' || layout === 'donut-vertical' ? CARD_PADDING : 0,
477
+ y: 11,
478
+ class: 'stat bold',
479
+ }, text);
651
480
  /**
652
481
  * Get default languages count for provided card layout.
653
482
  *
654
- * @param props Function properties.
655
- * @param props.layout Input layout string.
656
- * @param props.hide_progress Input hide_progress parameter value.
657
483
  * @returns Default languages count for input layout.
658
484
  */
659
485
  const getDefaultLanguagesCountByLayout = ({ layout, hide_progress, }) => {
660
486
  if (layout === 'compact' || hide_progress === true) {
661
487
  return COMPACT_LAYOUT_DEFAULT_LANGS_COUNT;
662
488
  }
663
- else if (layout === 'donut') {
489
+ if (layout === 'donut') {
664
490
  return DONUT_LAYOUT_DEFAULT_LANGS_COUNT;
665
491
  }
666
- else if (layout === 'donut-vertical') {
492
+ if (layout === 'donut-vertical') {
667
493
  return DONUT_VERTICAL_LAYOUT_DEFAULT_LANGS_COUNT;
668
494
  }
669
- else if (layout === 'pie') {
495
+ if (layout === 'pie') {
670
496
  return PIE_LAYOUT_DEFAULT_LANGS_COUNT;
671
497
  }
672
- else {
673
- return NORMAL_LAYOUT_DEFAULT_LANGS_COUNT;
674
- }
498
+ return NORMAL_LAYOUT_DEFAULT_LANGS_COUNT;
675
499
  };
676
500
  /**
677
501
  * Renders card that display user's most frequently used programming languages.
678
502
  *
679
- * @param topLangs User's most frequently used programming languages.
680
- * @param options Card options.
681
503
  * @returns Language card SVG object.
682
504
  */
683
- const renderTopLanguages = (topLangs, options = {}) => {
505
+ const renderCard = (topLangs, options = {}) => {
684
506
  const { hide_title = false, hide_border = false, card_width, hide, hide_progress, hide_values, layout, custom_title, locale, langs_count = getDefaultLanguagesCountByLayout({ layout, hide_progress }), border_radius, disable_animations, stats_format = 'percentages', } = options;
685
507
  const i18n = new I18n({
686
508
  locale,
@@ -688,7 +510,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
688
510
  });
689
511
  const { langs, totalLanguageSize } = trimTopLanguages(topLangs, langs_count, hide);
690
512
  let width = card_width
691
- ? isNaN(card_width)
513
+ ? Number.isNaN(card_width)
692
514
  ? DEFAULT_CARD_WIDTH
693
515
  : card_width < MIN_CARD_WIDTH
694
516
  ? MIN_CARD_WIDTH
@@ -718,7 +540,9 @@ const renderTopLanguages = (topLangs, options = {}) => {
718
540
  }
719
541
  else if (layout === 'donut') {
720
542
  height = calculateDonutLayoutHeight(langs.length);
721
- width = width + 50; // padding
543
+ // A donut defaults a step wider so the chart has its room; a width asked for
544
+ // explicitly keeps the extra it has always been given instead of being snapped.
545
+ width = width === DEFAULT_CARD_WIDTH ? CARD_WIDTH.standard : width + DONUT_EXTRA_WIDTH;
722
546
  finalLayout = renderDonutLayout(langs, width, totalLanguageSize, stats_format, hide_values);
723
547
  }
724
548
  else {
@@ -727,6 +551,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
727
551
  const card = new Card({
728
552
  customTitle: custom_title,
729
553
  defaultTitle: i18n.t('langcard.title'),
554
+ titlePrefixIcon: CARD_ICON.topLanguages,
730
555
  width,
731
556
  height,
732
557
  border_radius,
@@ -738,52 +563,29 @@ const renderTopLanguages = (topLangs, options = {}) => {
738
563
  card.setHideBorder(hide_border);
739
564
  card.setHideTitle(hide_title);
740
565
  card.setCSS({
741
- light: ({ textColor, progBarBgColor }) => `
742
- @keyframes slideInAnimation {
743
- from {
744
- width: 0;
745
- }
746
- to {
747
- width: calc(100%-100px);
748
- }
749
- }
750
- @keyframes growWidthAnimation {
751
- from {
752
- width: 0;
753
- }
754
- to {
755
- width: 100%;
756
- }
757
- }
758
- .stat {
759
- font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${textColor};
760
- }
761
- @supports(-moz-appearance: auto) {
762
- /* Selector detects Firefox */
763
- .stat { font-size:12px; }
764
- }
765
- .bold { font-weight: 700 }
766
- .lang-name {
767
- font: 400 11px "Segoe UI", Ubuntu, Sans-Serif;
768
- fill: ${textColor};
769
- }
770
- .stagger {
771
- opacity: 0;
772
- animation: fadeInAnimation 0.3s ease-in-out forwards;
773
- }
774
- #rect-mask rect{
775
- animation: slideInAnimation 1s ease-in-out forwards;
776
- }
777
- .lang-progress{
778
- animation: growWidthAnimation 0.6s ease-in-out forwards;
779
- }
780
- .progress-background { fill: ${progBarBgColor}; }
781
- `,
782
- dark: ({ textColor, progBarBgColor }) => `
783
- .stat { fill: ${textColor}; }
784
- .lang-name { fill: ${textColor}; }
785
- .progress-background { fill: ${progBarBgColor}; }
786
- `,
566
+ light: ({ textColor, progBarBgColor }) => [
567
+ atRule('@keyframes slideInAnimation', rule('from', { width: 0 }),
568
+ // Invalid on purpose — `calc` needs spaces around its `-`. Browsers drop the
569
+ // declaration and animate to the rect's own width, which is the reveal the mask wants.
570
+ rule('to', { width: 'calc(100%-100px)' })),
571
+ atRule('@keyframes growWidthAnimation', rule('from', { width: 0 }), rule('to', { width: '100%' })),
572
+ rule('.stat', {
573
+ font: font('regular', 'body'),
574
+ fill: textColor,
575
+ }),
576
+ firefoxFontSize(['.stat'], 'small'),
577
+ rule('.bold', { 'font-weight': FONT_WEIGHT.semibold }),
578
+ rule('.lang-name', { font: font('regular', 'micro'), fill: textColor }),
579
+ rule('.stagger', { opacity: 0, animation: 'fadeInAnimation 0.3s ease-in-out forwards' }),
580
+ rule('#rect-mask rect', { animation: 'slideInAnimation 1s ease-in-out forwards' }),
581
+ rule('.lang-progress', { animation: 'growWidthAnimation 0.6s ease-in-out forwards' }),
582
+ rule('.progress-background', { fill: progBarBgColor }),
583
+ ],
584
+ dark: ({ textColor, progBarBgColor }) => [
585
+ rule('.stat', { fill: textColor }),
586
+ rule('.lang-name', { fill: textColor }),
587
+ rule('.progress-background', { fill: progBarBgColor }),
588
+ ],
787
589
  });
788
590
  // `role="img"` hides the inner text from assistive tech, so everything the card
789
591
  // shows has to be repeated here.
@@ -796,10 +598,17 @@ const renderTopLanguages = (topLangs, options = {}) => {
796
598
  if (layout === 'pie' || layout === 'donut-vertical') {
797
599
  return card.render(finalLayout);
798
600
  }
799
- return card.render(`
800
- <svg data-testid="lang-items" x="${CARD_PADDING}">
801
- ${finalLayout}
802
- </svg>
803
- `);
601
+ return card.render(el('svg', { 'data-testid': 'lang-items', x: CARD_PADDING }, finalLayout));
804
602
  };
805
- export { getLongestLang, degreesToRadians, radiansToDegrees, polarToCartesian, cartesianToPolar, getCircleLength, calculateCompactLayoutHeight, calculateNormalLayoutHeight, calculateDonutLayoutHeight, calculateDonutVerticalLayoutHeight, calculatePieLayoutHeight, donutCenterTranslation, trimTopLanguages, renderTopLanguages, MIN_CARD_WIDTH, getDefaultLanguagesCountByLayout, TOP_LANG_LAYOUTS, TOP_LANG_STATS_FORMATS, };
603
+ /**
604
+ * The card, and the values each of its options accepts, keyed by the option's own name.
605
+ * They ride on the renderer so a list cannot be found without what draws it;
606
+ * the api handler forwards them onto its own export, which is what a UI reads.
607
+ */
608
+ const renderTopLanguages = Object.assign(renderCard, {
609
+ OPTIONS: {
610
+ layout: TOP_LANG_LAYOUTS,
611
+ stats_format: TOP_LANG_STATS_FORMATS,
612
+ },
613
+ });
614
+ export { getLongestLang, degreesToRadians, radiansToDegrees, polarToCartesian, cartesianToPolar, getCircleLength, calculateCompactLayoutHeight, calculateNormalLayoutHeight, calculateDonutLayoutHeight, calculateDonutVerticalLayoutHeight, calculatePieLayoutHeight, donutCenterTranslation, trimTopLanguages, MIN_CARD_WIDTH, getDefaultLanguagesCountByLayout, renderTopLanguages, };