@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,119 @@
|
|
|
1
|
+
import { default as Card } from '../common/Card.js';
|
|
2
|
+
import { getLightDarkColors } from '../common/color.js';
|
|
3
|
+
import { kFormatter, wrapTextMultiline } from '../common/fmt.js';
|
|
4
|
+
import { encodeHTML } from '../common/html.js';
|
|
5
|
+
import { icons } from '../common/icons.js';
|
|
6
|
+
import { getLanguageColor } from '../common/languageColors.js';
|
|
7
|
+
import { parseEmojis } from '../common/ops.js';
|
|
8
|
+
import { countWrappedLines, createLanguageNode, flexLayout, iconWithLabel, measureText, wrappedTextNode, wrappedTextStyles, } from '../common/render.js';
|
|
9
|
+
const ICON_SIZE = 16;
|
|
10
|
+
const CARD_DEFAULT_WIDTH = 400;
|
|
11
|
+
const X_OFFSET = 25;
|
|
12
|
+
const HEADER_MAX_LENGTH = 35;
|
|
13
|
+
const DESCRIPTION_BOX_WIDTH = CARD_DEFAULT_WIDTH - 2 * X_OFFSET;
|
|
14
|
+
const DESCRIPTION_FONT_SIZE = 13;
|
|
15
|
+
const DESCRIPTION_LINE_HEIGHT_PX = 16;
|
|
16
|
+
const DESCRIPTION_MAX_LINES = 10;
|
|
17
|
+
/**
|
|
18
|
+
* Render gist card.
|
|
19
|
+
*
|
|
20
|
+
* @param gistData Gist data.
|
|
21
|
+
* @param options Gist card options.
|
|
22
|
+
* @returns Gist card.
|
|
23
|
+
*/
|
|
24
|
+
const renderGistCard = (gistData, options = {}) => {
|
|
25
|
+
const { name, nameWithOwner, description, language, starsCount, forksCount } = gistData;
|
|
26
|
+
const { theme = 'default_repocard', border_radius, show_owner = false, browser_rendering = false, hide_border = false, } = options;
|
|
27
|
+
const { lightColors, darkColors } = getLightDarkColors({ ...options, theme });
|
|
28
|
+
const desc = parseEmojis(description || 'No description provided');
|
|
29
|
+
let descriptionLines;
|
|
30
|
+
let descriptionSvg;
|
|
31
|
+
if (browser_rendering) {
|
|
32
|
+
// The browser performs the actual text wrapping inside the foreignObject;
|
|
33
|
+
// we only estimate the line count server-side so the SVG can reserve enough
|
|
34
|
+
// height. The estimate uses measureText for font-aware widths instead of a
|
|
35
|
+
// fixed character count.
|
|
36
|
+
descriptionLines = countWrappedLines(desc, DESCRIPTION_FONT_SIZE, DESCRIPTION_BOX_WIDTH, DESCRIPTION_MAX_LINES);
|
|
37
|
+
descriptionSvg = wrappedTextNode({
|
|
38
|
+
text: desc,
|
|
39
|
+
x: X_OFFSET,
|
|
40
|
+
y: -3,
|
|
41
|
+
width: DESCRIPTION_BOX_WIDTH,
|
|
42
|
+
height: descriptionLines * DESCRIPTION_LINE_HEIGHT_PX + 10, // 10px extra for "descenders" like g, j, q, p, y
|
|
43
|
+
lineCount: descriptionLines,
|
|
44
|
+
className: 'description',
|
|
45
|
+
testId: 'description-text',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const linesLimit = 10;
|
|
50
|
+
const multiLineDescription = wrapTextMultiline(desc, DESCRIPTION_BOX_WIDTH, DESCRIPTION_FONT_SIZE, linesLimit);
|
|
51
|
+
descriptionLines = multiLineDescription.length;
|
|
52
|
+
descriptionSvg = multiLineDescription
|
|
53
|
+
.map((line) => `<tspan dy="1.2em" x="${X_OFFSET}">${encodeHTML(line)}</tspan>`)
|
|
54
|
+
.join('');
|
|
55
|
+
descriptionSvg = `<text class="description" x="${X_OFFSET}" y="-5">
|
|
56
|
+
${descriptionSvg}
|
|
57
|
+
</text>`;
|
|
58
|
+
}
|
|
59
|
+
const lineHeight = descriptionLines > 3 ? 12 : 10;
|
|
60
|
+
const height = (descriptionLines > 1 ? 120 : 110) + descriptionLines * lineHeight;
|
|
61
|
+
const totalStars = kFormatter(starsCount);
|
|
62
|
+
const totalForks = kFormatter(forksCount);
|
|
63
|
+
const svgStars = iconWithLabel(icons.star, totalStars, 'starsCount', ICON_SIZE);
|
|
64
|
+
const svgForks = iconWithLabel(icons.fork, totalForks, 'forksCount', ICON_SIZE);
|
|
65
|
+
const languageName = language || 'Unspecified';
|
|
66
|
+
const languageColor = getLanguageColor(languageName);
|
|
67
|
+
const svgLanguage = createLanguageNode(languageName, languageColor);
|
|
68
|
+
const starAndForkCount = flexLayout({
|
|
69
|
+
items: [svgLanguage, svgStars, svgForks],
|
|
70
|
+
sizes: [
|
|
71
|
+
measureText(languageName, 12),
|
|
72
|
+
ICON_SIZE + measureText(`${totalStars}`, 12),
|
|
73
|
+
ICON_SIZE + measureText(`${totalForks}`, 12),
|
|
74
|
+
],
|
|
75
|
+
gap: 25,
|
|
76
|
+
}).join('');
|
|
77
|
+
const header = show_owner ? nameWithOwner : name;
|
|
78
|
+
const card = new Card({
|
|
79
|
+
defaultTitle: header.length > HEADER_MAX_LENGTH ? `${header.slice(0, HEADER_MAX_LENGTH)}...` : header,
|
|
80
|
+
titlePrefixIcon: icons.gist,
|
|
81
|
+
width: CARD_DEFAULT_WIDTH,
|
|
82
|
+
height,
|
|
83
|
+
border_radius,
|
|
84
|
+
colors: { light: lightColors, dark: darkColors },
|
|
85
|
+
});
|
|
86
|
+
card.setCSS({
|
|
87
|
+
light: ({ textColor, iconColor }) => `
|
|
88
|
+
.description {
|
|
89
|
+
font: 400 ${DESCRIPTION_FONT_SIZE}px 'Segoe UI', Ubuntu, Sans-Serif;fill: ${textColor};
|
|
90
|
+
${browser_rendering ? wrappedTextStyles(textColor) : ''}
|
|
91
|
+
}
|
|
92
|
+
.gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
|
|
93
|
+
.icon { fill: ${iconColor} }
|
|
94
|
+
`,
|
|
95
|
+
dark: ({ textColor, iconColor }) => `
|
|
96
|
+
.description {
|
|
97
|
+
fill: ${textColor};
|
|
98
|
+
${browser_rendering ? wrappedTextStyles(textColor) : ''}
|
|
99
|
+
}
|
|
100
|
+
.gray { fill: ${textColor} }
|
|
101
|
+
.icon { fill: ${iconColor} }
|
|
102
|
+
`,
|
|
103
|
+
});
|
|
104
|
+
card.setHideBorder(hide_border);
|
|
105
|
+
// `role="img"` hides the inner text from assistive tech, so everything the card
|
|
106
|
+
// shows has to be repeated here.
|
|
107
|
+
card.setAccessibilityLabel({
|
|
108
|
+
title: card.title,
|
|
109
|
+
desc: `${desc}. Language: ${languageName}, Stars: ${totalStars}, Forks: ${totalForks}`,
|
|
110
|
+
});
|
|
111
|
+
return card.render(`
|
|
112
|
+
${descriptionSvg}
|
|
113
|
+
|
|
114
|
+
<g transform="translate(30, ${height - 75})">
|
|
115
|
+
${starAndForkCount}
|
|
116
|
+
</g>
|
|
117
|
+
`);
|
|
118
|
+
};
|
|
119
|
+
export { renderGistCard };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { renderGistCard } from './gist.js';
|
|
2
|
+
export { renderRepoCard } from './repo.js';
|
|
3
|
+
export { RANK_ICONS, renderStatsCard } from './stats.js';
|
|
4
|
+
export { TOP_LANG_LAYOUTS, TOP_LANG_STATS_FORMATS, renderTopLanguages } from './top-languages.js';
|
|
5
|
+
export { DISPLAY_FORMATS, WAKATIME_LAYOUTS, renderWakatimeCard } from './wakatime.js';
|
|
6
|
+
export type { CardOptions, CommonCardOptions } from './options.js';
|
|
7
|
+
export type { GistData, Lang, RepositoryData, StatsData, TopLangData, WakaTimeData, } from '../fetchers/types.js';
|
|
8
|
+
export { renderError } from '../common/render.js';
|
|
9
|
+
export { themes } from '../themes/index.js';
|
|
10
|
+
export type { ThemeName } from '../themes/index.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cards/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAClG,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEtF,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGnE,YAAY,EACV,QAAQ,EACR,IAAI,EACJ,cAAc,EACd,SAAS,EACT,WAAW,EACX,YAAY,GACb,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { renderGistCard } from './gist.js';
|
|
2
|
+
export { renderRepoCard } from './repo.js';
|
|
3
|
+
export { RANK_ICONS, renderStatsCard } from './stats.js';
|
|
4
|
+
export { TOP_LANG_LAYOUTS, TOP_LANG_STATS_FORMATS, renderTopLanguages } from './top-languages.js';
|
|
5
|
+
export { DISPLAY_FORMATS, WAKATIME_LAYOUTS, renderWakatimeCard } from './wakatime.js';
|
|
6
|
+
export { renderError } from '../common/render.js';
|
|
7
|
+
export { themes } from '../themes/index.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ColorParams } from '../common/color.js';
|
|
2
|
+
/**
|
|
3
|
+
* Options every card accepts.
|
|
4
|
+
* Cards spread these into `getLightDarkColors`, hence {@link ColorParams}.
|
|
5
|
+
*/
|
|
6
|
+
export interface CommonCardOptions extends ColorParams {
|
|
7
|
+
title_color: string;
|
|
8
|
+
icon_color: string;
|
|
9
|
+
text_color: string;
|
|
10
|
+
bg_color: string;
|
|
11
|
+
border_radius: number;
|
|
12
|
+
border_color: string;
|
|
13
|
+
hide_border: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** `Partial<T>` that also accepts an explicit `undefined`, as api handlers forward. */
|
|
16
|
+
export type CardOptions<T> = {
|
|
17
|
+
[K in keyof T]?: T[K] | undefined;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/cards/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;GAGG;AACH,MAAM,WAAW,iBAAkB,SAAQ,WAAW;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,uFAAuF;AACvF,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS;CAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { RepositoryData } from '../fetchers/types.js';
|
|
2
|
+
import type { CardOptions, CommonCardOptions } from './options.js';
|
|
3
|
+
interface RepoCardOptions extends CommonCardOptions {
|
|
4
|
+
locale: string;
|
|
5
|
+
show_owner: boolean;
|
|
6
|
+
browser_rendering: boolean;
|
|
7
|
+
description_lines_count: number;
|
|
8
|
+
card_width_input: number;
|
|
9
|
+
show: Array<string>;
|
|
10
|
+
show_icons: boolean;
|
|
11
|
+
number_format: string;
|
|
12
|
+
text_bold: boolean;
|
|
13
|
+
line_height: number | string;
|
|
14
|
+
username: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Renders repository card details.
|
|
18
|
+
*
|
|
19
|
+
* @param repo Repository data.
|
|
20
|
+
* @param options Card options.
|
|
21
|
+
* @returns Repository card SVG object.
|
|
22
|
+
*/
|
|
23
|
+
declare const renderRepoCard: (repo: RepositoryData, options?: CardOptions<RepoCardOptions>) => string;
|
|
24
|
+
export { renderRepoCard };
|
|
25
|
+
//# sourceMappingURL=repo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/cards/repo.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AASnE,UAAU,eAAgB,SAAQ,iBAAiB;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,uBAAuB,EAAE,MAAM,CAAC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAsCD;;;;;;GAMG;AACH,QAAA,MAAM,cAAc,GAClB,MAAM,cAAc,EACpB,UAAS,WAAW,CAAC,eAAe,CAAM,KACzC,MA4RF,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { Card } from '../common/Card.js';
|
|
2
|
+
import { getLightDarkColors } from '../common/color.js';
|
|
3
|
+
import { kFormatter, wrapTextMultiline } from '../common/fmt.js';
|
|
4
|
+
import { encodeHTML } from '../common/html.js';
|
|
5
|
+
import { I18n } from '../common/I18n.js';
|
|
6
|
+
import { icons } from '../common/icons.js';
|
|
7
|
+
import { buildSearchFilter, clampValue, parseEmojis } from '../common/ops.js';
|
|
8
|
+
import { countWrappedLines, createLanguageNode, createTextNode, flexLayout, iconWithLabel, measureText, wrappedTextNode, wrappedTextStyles, } from '../common/render.js';
|
|
9
|
+
import { repoCardLocales } from '../translations.js';
|
|
10
|
+
const ICON_SIZE = 16;
|
|
11
|
+
const CARD_DEFAULT_WIDTH = 400;
|
|
12
|
+
const X_OFFSET = 25;
|
|
13
|
+
const DESCRIPTION_FONT_SIZE = 13;
|
|
14
|
+
const DESCRIPTION_LINE_HEIGHT_PX = 16;
|
|
15
|
+
const DESCRIPTION_MAX_LINES = 3;
|
|
16
|
+
/**
|
|
17
|
+
* Retrieves the repository description and wraps it to fit the card width.
|
|
18
|
+
*
|
|
19
|
+
* @param label The repository description.
|
|
20
|
+
* @param xOffset Horizontal offset of the badge.
|
|
21
|
+
* @returns Wrapped repo description SVG object.
|
|
22
|
+
*/
|
|
23
|
+
const getBadgeSVG = (label, xOffset = 0) => {
|
|
24
|
+
if (!Number.isFinite(xOffset)) {
|
|
25
|
+
throw new Error(`Invalid xOffset: "${xOffset}"`);
|
|
26
|
+
}
|
|
27
|
+
return `
|
|
28
|
+
<g data-testid="badge" class="badge" transform="translate(${320 + xOffset}, -18)">
|
|
29
|
+
<rect stroke-width="1" width="70" height="20" x="-12" y="-14" ry="10" rx="10"></rect>
|
|
30
|
+
<text
|
|
31
|
+
x="23" y="-5"
|
|
32
|
+
alignment-baseline="central"
|
|
33
|
+
dominant-baseline="central"
|
|
34
|
+
text-anchor="middle"
|
|
35
|
+
>
|
|
36
|
+
${encodeHTML(label)}
|
|
37
|
+
</text>
|
|
38
|
+
</g>
|
|
39
|
+
`;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Renders repository card details.
|
|
43
|
+
*
|
|
44
|
+
* @param repo Repository data.
|
|
45
|
+
* @param options Card options.
|
|
46
|
+
* @returns Repository card SVG object.
|
|
47
|
+
*/
|
|
48
|
+
const renderRepoCard = (repo, options = {}) => {
|
|
49
|
+
const { name, nameWithOwner, description, primaryLanguage, isArchived, isTemplate, stargazerCount, forkCount, totalPRsAuthored, totalPRsCommented, totalPRsReviewed, totalIssuesAuthored, totalIssuesCommented, } = repo;
|
|
50
|
+
const { hide_border = false, card_width_input, show_owner = false, browser_rendering = false, show = [], show_icons = true, number_format = 'short', text_bold = false, line_height = 22, username, theme = 'default_repocard', border_radius, locale, description_lines_count, } = options;
|
|
51
|
+
const card_width = card_width_input && !isNaN(card_width_input)
|
|
52
|
+
? card_width_input
|
|
53
|
+
: show.length >= 2
|
|
54
|
+
? CARD_DEFAULT_WIDTH + 30
|
|
55
|
+
: CARD_DEFAULT_WIDTH;
|
|
56
|
+
const i18n = new I18n({
|
|
57
|
+
locale,
|
|
58
|
+
translations: repoCardLocales,
|
|
59
|
+
});
|
|
60
|
+
const repoFilter = encodeURIComponent(buildSearchFilter([nameWithOwner], []));
|
|
61
|
+
const encodedUsername = encodeURIComponent(username ?? '');
|
|
62
|
+
const STATS = {};
|
|
63
|
+
if (show.includes('prs_authored')) {
|
|
64
|
+
STATS['prs_authored'] = {
|
|
65
|
+
icon: icons.prs,
|
|
66
|
+
label: i18n.t('repocard.prs-authored'),
|
|
67
|
+
value: totalPRsAuthored,
|
|
68
|
+
id: 'prs_authored',
|
|
69
|
+
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=pullrequests`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (show.includes('prs_commented')) {
|
|
73
|
+
STATS['prs_commented'] = {
|
|
74
|
+
icon: icons.comments,
|
|
75
|
+
label: i18n.t('repocard.prs-commented'),
|
|
76
|
+
value: totalPRsCommented,
|
|
77
|
+
id: 'prs_commented',
|
|
78
|
+
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (show.includes('prs_reviewed')) {
|
|
82
|
+
STATS['prs_reviewed'] = {
|
|
83
|
+
icon: icons.reviews,
|
|
84
|
+
label: i18n.t('repocard.prs-reviewed'),
|
|
85
|
+
value: totalPRsReviewed,
|
|
86
|
+
id: 'prs_reviewed',
|
|
87
|
+
link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (show.includes('issues_authored')) {
|
|
91
|
+
STATS['issues_authored'] = {
|
|
92
|
+
icon: icons.issues,
|
|
93
|
+
label: i18n.t('repocard.issues-authored'),
|
|
94
|
+
value: totalIssuesAuthored,
|
|
95
|
+
id: 'issues_authored',
|
|
96
|
+
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=issues`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (show.includes('issues_commented')) {
|
|
100
|
+
STATS['issues_commented'] = {
|
|
101
|
+
icon: icons.discussions_started,
|
|
102
|
+
label: i18n.t('repocard.issues-commented'),
|
|
103
|
+
value: totalIssuesCommented,
|
|
104
|
+
id: 'issues_commented',
|
|
105
|
+
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&type=issues`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const statItems = Object.values(STATS).map((stat, index) =>
|
|
109
|
+
// create the text nodes, and pass index so that we can calculate the line spacing
|
|
110
|
+
createTextNode({
|
|
111
|
+
icon: stat.icon,
|
|
112
|
+
label: stat.label,
|
|
113
|
+
value: stat.value ?? 0,
|
|
114
|
+
id: stat.id,
|
|
115
|
+
unitSymbol: stat.unitSymbol,
|
|
116
|
+
index,
|
|
117
|
+
showIcons: show_icons,
|
|
118
|
+
shiftValuePos: 14.01,
|
|
119
|
+
bold: text_bold,
|
|
120
|
+
numberFormat: number_format,
|
|
121
|
+
link: stat.link,
|
|
122
|
+
labelXOffset: 23,
|
|
123
|
+
}));
|
|
124
|
+
const extraLHeight = parseInt(String(line_height), 10);
|
|
125
|
+
const lineHeight = 10;
|
|
126
|
+
const header = show_owner ? nameWithOwner : name;
|
|
127
|
+
const langName = (primaryLanguage && primaryLanguage.name) || 'Unspecified';
|
|
128
|
+
const langColor = (primaryLanguage && primaryLanguage.color) || '#333';
|
|
129
|
+
const desc = parseEmojis(description || 'No description provided');
|
|
130
|
+
const descriptionBoxWidth = card_width - 2 * X_OFFSET;
|
|
131
|
+
let descriptionLinesCount;
|
|
132
|
+
let descriptionSvg;
|
|
133
|
+
if (browser_rendering) {
|
|
134
|
+
// The browser performs the actual text wrapping inside the foreignObject;
|
|
135
|
+
// we only estimate the line count server-side so the SVG can reserve enough
|
|
136
|
+
// height. The estimate uses measureText for font-aware widths instead of a
|
|
137
|
+
// fixed character count.
|
|
138
|
+
descriptionLinesCount = description_lines_count
|
|
139
|
+
? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES)
|
|
140
|
+
: countWrappedLines(desc, DESCRIPTION_FONT_SIZE, descriptionBoxWidth, DESCRIPTION_MAX_LINES);
|
|
141
|
+
descriptionSvg = wrappedTextNode({
|
|
142
|
+
text: desc,
|
|
143
|
+
x: X_OFFSET,
|
|
144
|
+
y: -3,
|
|
145
|
+
width: descriptionBoxWidth,
|
|
146
|
+
height: descriptionLinesCount * DESCRIPTION_LINE_HEIGHT_PX + 10, // 10px extra for "descenders" like g, j, q, p, y
|
|
147
|
+
lineCount: descriptionLinesCount,
|
|
148
|
+
className: 'description',
|
|
149
|
+
testId: 'description-text',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
const descriptionMaxLines = description_lines_count
|
|
154
|
+
? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES)
|
|
155
|
+
: DESCRIPTION_MAX_LINES;
|
|
156
|
+
const multiLineDescription = wrapTextMultiline(desc, descriptionBoxWidth, DESCRIPTION_FONT_SIZE, descriptionMaxLines);
|
|
157
|
+
descriptionLinesCount = description_lines_count
|
|
158
|
+
? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES)
|
|
159
|
+
: multiLineDescription.length;
|
|
160
|
+
descriptionSvg = multiLineDescription
|
|
161
|
+
.map((line) => `<tspan dy="1.2em" x="${X_OFFSET}">${encodeHTML(line)}</tspan>`)
|
|
162
|
+
.join('');
|
|
163
|
+
descriptionSvg = `<text class="description" x="${X_OFFSET}" y="-5">
|
|
164
|
+
${descriptionSvg}
|
|
165
|
+
</text>`;
|
|
166
|
+
}
|
|
167
|
+
const extraHeight = Object.keys(STATS).length
|
|
168
|
+
? -7 + (Math.ceil(statItems.length / 2) + 1) * extraLHeight
|
|
169
|
+
: 0;
|
|
170
|
+
const height = (descriptionLinesCount > 1 ? 120 : 110) + descriptionLinesCount * lineHeight + extraHeight;
|
|
171
|
+
const { lightColors, darkColors } = getLightDarkColors({ ...options, theme });
|
|
172
|
+
const svgLanguage = primaryLanguage ? createLanguageNode(langName, langColor) : '';
|
|
173
|
+
const totalStars = kFormatter(stargazerCount);
|
|
174
|
+
const totalForks = kFormatter(forkCount);
|
|
175
|
+
const svgStars = iconWithLabel(icons.star, totalStars, 'stargazers', ICON_SIZE);
|
|
176
|
+
const svgForks = iconWithLabel(icons.fork, totalForks, 'forkcount', ICON_SIZE);
|
|
177
|
+
const starAndForkCount = flexLayout({
|
|
178
|
+
items: [svgLanguage, svgStars, svgForks],
|
|
179
|
+
sizes: [
|
|
180
|
+
measureText(langName, 12),
|
|
181
|
+
ICON_SIZE + measureText(`${totalStars}`, 12),
|
|
182
|
+
ICON_SIZE + measureText(`${totalForks}`, 12),
|
|
183
|
+
],
|
|
184
|
+
gap: 25,
|
|
185
|
+
}).join('');
|
|
186
|
+
const extraRows = [];
|
|
187
|
+
for (let i = 0; i < statItems.length; i += 2) {
|
|
188
|
+
extraRows.push(flexLayout({
|
|
189
|
+
items: statItems.slice(i, i + 2),
|
|
190
|
+
gap: 210,
|
|
191
|
+
direction: 'row',
|
|
192
|
+
}).join(''));
|
|
193
|
+
}
|
|
194
|
+
const extraItems = `
|
|
195
|
+
<svg x="0" y="0"><g transform="translate(-3, ${height - 52 - extraHeight})">
|
|
196
|
+
${flexLayout({
|
|
197
|
+
items: extraRows,
|
|
198
|
+
gap: extraLHeight,
|
|
199
|
+
direction: 'column',
|
|
200
|
+
}).join('')}
|
|
201
|
+
</g></svg>
|
|
202
|
+
`;
|
|
203
|
+
const card = new Card({
|
|
204
|
+
defaultTitle: header.length > 35 ? `${header.slice(0, 35)}...` : header,
|
|
205
|
+
titlePrefixIcon: icons.contribs,
|
|
206
|
+
width: card_width,
|
|
207
|
+
height,
|
|
208
|
+
border_radius,
|
|
209
|
+
colors: { light: lightColors, dark: darkColors },
|
|
210
|
+
});
|
|
211
|
+
card.disableAnimations();
|
|
212
|
+
card.setHideBorder(hide_border);
|
|
213
|
+
card.setHideTitle(false);
|
|
214
|
+
card.setCSS({
|
|
215
|
+
light: ({ textColor, iconColor }) => `
|
|
216
|
+
.description {
|
|
217
|
+
font: 400 ${DESCRIPTION_FONT_SIZE}px 'Segoe UI', Ubuntu, Sans-Serif;fill: ${textColor};
|
|
218
|
+
${browser_rendering ? wrappedTextStyles(textColor) : ''}
|
|
219
|
+
}
|
|
220
|
+
.gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
|
|
221
|
+
.badge { font: 600 11px 'Segoe UI', Ubuntu, Sans-Serif; }
|
|
222
|
+
.badge rect { opacity: 0.2; stroke: ${textColor} }
|
|
223
|
+
.badge text { fill: ${textColor} }
|
|
224
|
+
|
|
225
|
+
.stat { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
|
|
226
|
+
.stagger {
|
|
227
|
+
opacity: 0;
|
|
228
|
+
animation: fadeInAnimation 0.3s ease-in-out forwards;
|
|
229
|
+
}
|
|
230
|
+
.not_bold { font-weight: 400 }
|
|
231
|
+
.bold { font-weight: 700 }
|
|
232
|
+
.icon {
|
|
233
|
+
fill: ${iconColor};
|
|
234
|
+
display: block;
|
|
235
|
+
}
|
|
236
|
+
`,
|
|
237
|
+
dark: ({ textColor, iconColor }) => `
|
|
238
|
+
.description {
|
|
239
|
+
fill: ${textColor};
|
|
240
|
+
${browser_rendering ? wrappedTextStyles(textColor) : ''}
|
|
241
|
+
}
|
|
242
|
+
.gray { fill: ${textColor} }
|
|
243
|
+
.badge rect { stroke: ${textColor} }
|
|
244
|
+
.badge text { fill: ${textColor} }
|
|
245
|
+
.stat { fill: ${textColor} }
|
|
246
|
+
.icon { fill: ${iconColor}; }
|
|
247
|
+
`,
|
|
248
|
+
});
|
|
249
|
+
const extraStatLabels = Object.values(STATS)
|
|
250
|
+
.map((stat) => `${stat.label}: ${stat.value ?? 0}`)
|
|
251
|
+
.join(', ');
|
|
252
|
+
// `role="img"` hides the inner text from assistive tech, so everything the card
|
|
253
|
+
// shows has to be repeated here.
|
|
254
|
+
// Only translated or language-neutral text: the card honours `locale`, and there is no
|
|
255
|
+
// translated vocabulary for "stars" or "forks", so those counts stay out rather than
|
|
256
|
+
// announce themselves in English over a card rendered in another language.
|
|
257
|
+
card.setAccessibilityLabel({
|
|
258
|
+
title: card.title,
|
|
259
|
+
desc: [`${desc}.`, primaryLanguage ? langName : '', extraStatLabels].filter(Boolean).join(', '),
|
|
260
|
+
});
|
|
261
|
+
return card.render(`
|
|
262
|
+
${isTemplate
|
|
263
|
+
? getBadgeSVG(i18n.t('repocard.template'), card_width - CARD_DEFAULT_WIDTH)
|
|
264
|
+
: isArchived
|
|
265
|
+
? getBadgeSVG(i18n.t('repocard.archived'), card_width - CARD_DEFAULT_WIDTH)
|
|
266
|
+
: ''}
|
|
267
|
+
|
|
268
|
+
${descriptionSvg}
|
|
269
|
+
|
|
270
|
+
<g transform="translate(30, ${height - 75 - extraHeight})">
|
|
271
|
+
${starAndForkCount}
|
|
272
|
+
</g>
|
|
273
|
+
${extraItems}
|
|
274
|
+
`);
|
|
275
|
+
};
|
|
276
|
+
export { renderRepoCard };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { StatsData } from '../fetchers/types.js';
|
|
2
|
+
import type { CardOptions, CommonCardOptions } from './options.js';
|
|
3
|
+
/** Rank indicators the card can draw; the api validates `rank_icon` against this. */
|
|
4
|
+
declare const RANK_ICONS: readonly ["default", "github", "percentile"];
|
|
5
|
+
type RankIcon = (typeof RANK_ICONS)[number];
|
|
6
|
+
interface StatCardOptions extends CommonCardOptions {
|
|
7
|
+
locale: string;
|
|
8
|
+
hide: Array<string>;
|
|
9
|
+
show_icons: boolean;
|
|
10
|
+
hide_title: boolean;
|
|
11
|
+
card_width: number;
|
|
12
|
+
hide_rank: boolean;
|
|
13
|
+
include_all_commits: boolean;
|
|
14
|
+
commits_year: number;
|
|
15
|
+
line_height: number | string;
|
|
16
|
+
custom_title: string;
|
|
17
|
+
disable_animations: boolean;
|
|
18
|
+
number_format: string;
|
|
19
|
+
number_precision: number;
|
|
20
|
+
ring_color: string;
|
|
21
|
+
text_bold: boolean;
|
|
22
|
+
rank_icon: RankIcon;
|
|
23
|
+
show: Array<string>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Renders the stats card.
|
|
27
|
+
*
|
|
28
|
+
* @param stats The stats data.
|
|
29
|
+
* @param options The card options.
|
|
30
|
+
* @param username GitHub username, used to build stat search links.
|
|
31
|
+
* @param repo Repositories to scope the search links to.
|
|
32
|
+
* @param owner Owners to scope the search links to.
|
|
33
|
+
* @returns The stats card SVG object.
|
|
34
|
+
*/
|
|
35
|
+
declare const renderStatsCard: (stats: StatsData, options?: CardOptions<StatCardOptions>, username?: string, repo?: Array<string>, owner?: Array<string>) => string;
|
|
36
|
+
export { RANK_ICONS, renderStatsCard };
|
|
37
|
+
//# sourceMappingURL=stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/cards/stats.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AASnE,qFAAqF;AACrF,QAAA,MAAM,UAAU,8CAA+C,CAAC;AAChE,KAAK,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5C,UAAU,eAAgB,SAAQ,iBAAiB;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,QAAQ,CAAC;IACpB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACrB;AAiLD;;;;;;;;;GASG;AACH,QAAA,MAAM,eAAe,GACnB,OAAO,SAAS,EAChB,UAAS,WAAW,CAAC,eAAe,CAAM,EAC1C,WAAW,MAAM,EACjB,OAAM,KAAK,CAAC,MAAM,CAAM,EACxB,QAAO,KAAK,CAAC,MAAM,CAAM,KACxB,MAyXF,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC"}
|