@gh-top-languages/lib 1.0.3 → 1.0.4
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 -21
- package/README.md +36 -33
- package/dist/charts/donut.d.ts +1 -1
- package/dist/charts/donut.d.ts.map +1 -1
- package/dist/charts/donut.js +5 -6
- package/dist/charts/donut.js.map +1 -1
- package/dist/charts/geometry.js +11 -11
- package/dist/charts/layout.d.ts +13 -2
- package/dist/charts/layout.d.ts.map +1 -1
- package/dist/charts/layout.js +27 -8
- package/dist/charts/layout.js.map +1 -1
- package/dist/charts/legend.d.ts +1 -1
- package/dist/charts/legend.d.ts.map +1 -1
- package/dist/charts/legend.js +20 -20
- package/dist/charts/legend.js.map +1 -1
- package/dist/charts/pie.d.ts +1 -1
- package/dist/charts/pie.d.ts.map +1 -1
- package/dist/charts/pie.js +5 -6
- package/dist/charts/pie.js.map +1 -1
- package/dist/constants/styles.d.ts +4 -2
- package/dist/constants/styles.d.ts.map +1 -1
- package/dist/constants/styles.js +18 -2
- package/dist/constants/styles.js.map +1 -1
- package/dist/render/chart.d.ts +1 -1
- package/dist/render/chart.d.ts.map +1 -1
- package/dist/render/chart.js +2 -2
- package/dist/render/chart.js.map +1 -1
- package/dist/render/error.js +7 -7
- package/dist/render/svg.d.ts +2 -1
- package/dist/render/svg.d.ts.map +1 -1
- package/dist/render/svg.js +21 -18
- package/dist/render/svg.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +48 -48
- package/src/charts/donut.ts +38 -22
- package/src/charts/geometry.ts +88 -88
- package/src/charts/layout.ts +50 -19
- package/src/charts/legend.ts +54 -53
- package/src/charts/pie.ts +41 -22
- package/src/constants/config.ts +11 -11
- package/src/constants/geometry.ts +9 -9
- package/src/constants/styles.ts +40 -23
- package/src/constants/themes.ts +68 -68
- package/src/constants/types.ts +4 -4
- package/src/render/chart.ts +22 -24
- package/src/render/error.ts +25 -25
- package/src/render/svg.ts +30 -31
- package/src/types.ts +30 -28
- package/src/utils/params.ts +47 -47
- package/src/utils/sanitize.ts +14 -14
package/src/constants/styles.ts
CHANGED
|
@@ -1,23 +1,40 @@
|
|
|
1
|
-
export const TITLE_STYLES = {
|
|
2
|
-
TEXT_Y: 30,
|
|
3
|
-
FONT_SIZE: 24
|
|
4
|
-
} as const;
|
|
5
|
-
|
|
6
|
-
export const LEGEND_STYLES = {
|
|
7
|
-
START_Y:
|
|
8
|
-
ROW_HEIGHT:
|
|
9
|
-
SQUARE_SIZE:
|
|
10
|
-
SQUARE_RADIUS:
|
|
11
|
-
FONT_SIZE:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} as const;
|
|
15
|
-
|
|
16
|
-
export const ERROR_STYLES = {
|
|
17
|
-
TEXT_Y: 100,
|
|
18
|
-
FONT_SIZE: 18,
|
|
19
|
-
COLOUR: "#ff6b6b"
|
|
20
|
-
} as const;
|
|
21
|
-
|
|
22
|
-
export const LEGEND_SHIFT_THRESHOLD = 8;
|
|
23
|
-
export const CHART_MARGIN_RIGHT = 20;
|
|
1
|
+
export const TITLE_STYLES = {
|
|
2
|
+
TEXT_Y: 30,
|
|
3
|
+
FONT_SIZE: 24
|
|
4
|
+
} as const;
|
|
5
|
+
|
|
6
|
+
export const LEGEND_STYLES = {
|
|
7
|
+
START_Y: 80,
|
|
8
|
+
ROW_HEIGHT: 25,
|
|
9
|
+
SQUARE_SIZE: 12,
|
|
10
|
+
SQUARE_RADIUS: 2,
|
|
11
|
+
FONT_SIZE: 11,
|
|
12
|
+
TEXT_GAP: 5,
|
|
13
|
+
COLUMN_GAP: 15,
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export const ERROR_STYLES = {
|
|
17
|
+
TEXT_Y: 100,
|
|
18
|
+
FONT_SIZE: 18,
|
|
19
|
+
COLOUR: "#ff6b6b"
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
export const LEGEND_SHIFT_THRESHOLD = 8;
|
|
23
|
+
export const CHART_MARGIN_RIGHT = 20;
|
|
24
|
+
|
|
25
|
+
export const ARIAL_CHAR_WIDTHS: Record<string, number> = {
|
|
26
|
+
" ": 278, "!": 278, "#": 556, "%": 889, "+": 584, ".": 278, "-": 333,
|
|
27
|
+
"0": 556, "1": 556, "2": 556, "3": 556, "4": 556,
|
|
28
|
+
"5": 556, "6": 556, "7": 556, "8": 556, "9": 556,
|
|
29
|
+
A: 667, B: 667, C: 722, D: 722, E: 667, F: 611,
|
|
30
|
+
G: 778, H: 722, I: 278, J: 500, K: 667, L: 556,
|
|
31
|
+
M: 833, N: 722, O: 778, P: 667, Q: 778, R: 722,
|
|
32
|
+
S: 667, T: 611, U: 722, V: 667, W: 944, X: 667,
|
|
33
|
+
Y: 667, Z: 611,
|
|
34
|
+
a: 556, b: 556, c: 500, d: 556, e: 556, f: 278,
|
|
35
|
+
g: 556, h: 556, i: 222, j: 222, k: 500, l: 222,
|
|
36
|
+
m: 833, n: 556, o: 556, p: 556, q: 556, r: 333,
|
|
37
|
+
s: 500, t: 278, u: 556, v: 500, w: 722, x: 500,
|
|
38
|
+
y: 500, z: 500
|
|
39
|
+
} as const;
|
|
40
|
+
export const DEFAULT_CHAR_WIDTH = 556;
|
package/src/constants/themes.ts
CHANGED
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
export const THEMES = {
|
|
2
|
-
default: {
|
|
3
|
-
bg: "#0d1117",
|
|
4
|
-
text: "#ffffff",
|
|
5
|
-
colours: [
|
|
6
|
-
"#A8D5Ba",
|
|
7
|
-
"#FFD6A5",
|
|
8
|
-
"#FFAAA6",
|
|
9
|
-
"#D0CFCF",
|
|
10
|
-
"#CBAACB",
|
|
11
|
-
"#FFE156",
|
|
12
|
-
"#96D5E9",
|
|
13
|
-
"#F3B0C3",
|
|
14
|
-
"#B4A7D6",
|
|
15
|
-
"#FFB6B9",
|
|
16
|
-
"#A3E4D7",
|
|
17
|
-
"#F8B88B",
|
|
18
|
-
"#C9E4CA",
|
|
19
|
-
"#FAD7A0",
|
|
20
|
-
"#AED6F1",
|
|
21
|
-
"#D7BDE2"
|
|
22
|
-
]
|
|
23
|
-
},
|
|
24
|
-
light: {
|
|
25
|
-
bg: "#ffffff",
|
|
26
|
-
text: "#2f2f2f",
|
|
27
|
-
colours: [
|
|
28
|
-
"#2ecc71",
|
|
29
|
-
"#3498db",
|
|
30
|
-
"#e74c3c",
|
|
31
|
-
"#f39c12",
|
|
32
|
-
"#9b59b6",
|
|
33
|
-
"#1abc9c",
|
|
34
|
-
"#e67e22",
|
|
35
|
-
"#34495e",
|
|
36
|
-
"#16a085",
|
|
37
|
-
"#c0392b",
|
|
38
|
-
"#8e44ad",
|
|
39
|
-
"#27ae60",
|
|
40
|
-
"#d35400",
|
|
41
|
-
"#2980b9",
|
|
42
|
-
"#7f8c8d",
|
|
43
|
-
"#f1c40f"
|
|
44
|
-
]
|
|
45
|
-
},
|
|
46
|
-
dark: {
|
|
47
|
-
bg: "#1a1a1a",
|
|
48
|
-
text: "#ccd6f6",
|
|
49
|
-
colours: [
|
|
50
|
-
"#ff6b6b",
|
|
51
|
-
"#4ecdc3",
|
|
52
|
-
"#45b7d1",
|
|
53
|
-
"#ffa07a",
|
|
54
|
-
"#98d8c8",
|
|
55
|
-
"#f7dc6f",
|
|
56
|
-
"#bb8fce",
|
|
57
|
-
"#64ffda",
|
|
58
|
-
"#85c1e2",
|
|
59
|
-
"#ff8a80",
|
|
60
|
-
"#a7ffeb",
|
|
61
|
-
"#ffd54f",
|
|
62
|
-
"#ea80fc",
|
|
63
|
-
"#80d8ff",
|
|
64
|
-
"#ffab91",
|
|
65
|
-
"#b9f6ca"
|
|
66
|
-
]
|
|
67
|
-
}
|
|
68
|
-
} as const;
|
|
1
|
+
export const THEMES = {
|
|
2
|
+
default: {
|
|
3
|
+
bg: "#0d1117",
|
|
4
|
+
text: "#ffffff",
|
|
5
|
+
colours: [
|
|
6
|
+
"#A8D5Ba",
|
|
7
|
+
"#FFD6A5",
|
|
8
|
+
"#FFAAA6",
|
|
9
|
+
"#D0CFCF",
|
|
10
|
+
"#CBAACB",
|
|
11
|
+
"#FFE156",
|
|
12
|
+
"#96D5E9",
|
|
13
|
+
"#F3B0C3",
|
|
14
|
+
"#B4A7D6",
|
|
15
|
+
"#FFB6B9",
|
|
16
|
+
"#A3E4D7",
|
|
17
|
+
"#F8B88B",
|
|
18
|
+
"#C9E4CA",
|
|
19
|
+
"#FAD7A0",
|
|
20
|
+
"#AED6F1",
|
|
21
|
+
"#D7BDE2"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
light: {
|
|
25
|
+
bg: "#ffffff",
|
|
26
|
+
text: "#2f2f2f",
|
|
27
|
+
colours: [
|
|
28
|
+
"#2ecc71",
|
|
29
|
+
"#3498db",
|
|
30
|
+
"#e74c3c",
|
|
31
|
+
"#f39c12",
|
|
32
|
+
"#9b59b6",
|
|
33
|
+
"#1abc9c",
|
|
34
|
+
"#e67e22",
|
|
35
|
+
"#34495e",
|
|
36
|
+
"#16a085",
|
|
37
|
+
"#c0392b",
|
|
38
|
+
"#8e44ad",
|
|
39
|
+
"#27ae60",
|
|
40
|
+
"#d35400",
|
|
41
|
+
"#2980b9",
|
|
42
|
+
"#7f8c8d",
|
|
43
|
+
"#f1c40f"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
dark: {
|
|
47
|
+
bg: "#1a1a1a",
|
|
48
|
+
text: "#ccd6f6",
|
|
49
|
+
colours: [
|
|
50
|
+
"#ff6b6b",
|
|
51
|
+
"#4ecdc3",
|
|
52
|
+
"#45b7d1",
|
|
53
|
+
"#ffa07a",
|
|
54
|
+
"#98d8c8",
|
|
55
|
+
"#f7dc6f",
|
|
56
|
+
"#bb8fce",
|
|
57
|
+
"#64ffda",
|
|
58
|
+
"#85c1e2",
|
|
59
|
+
"#ff8a80",
|
|
60
|
+
"#a7ffeb",
|
|
61
|
+
"#ffd54f",
|
|
62
|
+
"#ea80fc",
|
|
63
|
+
"#80d8ff",
|
|
64
|
+
"#ffab91",
|
|
65
|
+
"#b9f6ca"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
} as const;
|
package/src/constants/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const VALID_TYPES = [
|
|
2
|
-
"donut",
|
|
3
|
-
"pie"
|
|
4
|
-
] as const;
|
|
1
|
+
export const VALID_TYPES = [
|
|
2
|
+
"donut",
|
|
3
|
+
"pie"
|
|
4
|
+
] as const;
|
package/src/render/chart.ts
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
|
-
import type { Language, Theme, ChartType, ChartResult } from "../types.js";
|
|
2
|
-
import { generateDonutChart } from "../charts/donut.js";
|
|
3
|
-
import { generatePieChart } from "../charts/pie.js";
|
|
4
|
-
|
|
5
|
-
const CHART_GENERATORS: Record<ChartType, (
|
|
6
|
-
data: Language[],
|
|
7
|
-
theme: Theme,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return generator(data, theme, width, stroke);
|
|
24
|
-
}
|
|
1
|
+
import type { Language, Theme, ChartType, ChartResult } from "../types.js";
|
|
2
|
+
import { generateDonutChart } from "../charts/donut.js";
|
|
3
|
+
import { generatePieChart } from "../charts/pie.js";
|
|
4
|
+
|
|
5
|
+
const CHART_GENERATORS: Record<ChartType, (
|
|
6
|
+
data: Language[],
|
|
7
|
+
theme: Theme,
|
|
8
|
+
stroke: boolean
|
|
9
|
+
) => ChartResult> = {
|
|
10
|
+
donut: generateDonutChart,
|
|
11
|
+
pie: generatePieChart,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function generateChartData(
|
|
15
|
+
data: Language[],
|
|
16
|
+
theme: Theme,
|
|
17
|
+
chartType: ChartType,
|
|
18
|
+
stroke: boolean
|
|
19
|
+
): ChartResult {
|
|
20
|
+
const generator = CHART_GENERATORS[chartType] || CHART_GENERATORS.donut;
|
|
21
|
+
return generator(data, theme, stroke);
|
|
22
|
+
}
|
package/src/render/error.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import type { Theme } from "../types.js";
|
|
2
|
-
import { THEMES } from "../constants/themes.js";
|
|
3
|
-
import { ERROR_STYLES } from "../constants/styles.js"
|
|
4
|
-
import { sanitize } from "../utils/sanitize.js";
|
|
5
|
-
|
|
6
|
-
export function renderError(
|
|
7
|
-
message: string,
|
|
8
|
-
width: number,
|
|
9
|
-
height: number,
|
|
10
|
-
selectedTheme?: Theme
|
|
11
|
-
): string {
|
|
12
|
-
const background = selectedTheme?.bg || THEMES.default.bg;
|
|
13
|
-
const maxLen = 40;
|
|
14
|
-
const truncated = message.length > maxLen
|
|
15
|
-
? sanitize(message.slice(0, maxLen)) + "..."
|
|
16
|
-
: sanitize(message);
|
|
17
|
-
return `
|
|
18
|
-
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
|
|
19
|
-
<rect width="${width}" height="${height}" fill="${background}" rx="10"/>
|
|
20
|
-
<text x="${width/2}" y="${ERROR_STYLES.TEXT_Y}" text-anchor="middle" fill="${ERROR_STYLES.COLOUR}" font-family="Arial" font-size="${ERROR_STYLES.FONT_SIZE}">
|
|
21
|
-
Error: ${truncated}
|
|
22
|
-
</text>
|
|
23
|
-
</svg>
|
|
24
|
-
`.trim();
|
|
25
|
-
}
|
|
1
|
+
import type { Theme } from "../types.js";
|
|
2
|
+
import { THEMES } from "../constants/themes.js";
|
|
3
|
+
import { ERROR_STYLES } from "../constants/styles.js"
|
|
4
|
+
import { sanitize } from "../utils/sanitize.js";
|
|
5
|
+
|
|
6
|
+
export function renderError(
|
|
7
|
+
message: string,
|
|
8
|
+
width: number,
|
|
9
|
+
height: number,
|
|
10
|
+
selectedTheme?: Theme
|
|
11
|
+
): string {
|
|
12
|
+
const background = selectedTheme?.bg || THEMES.default.bg;
|
|
13
|
+
const maxLen = 40;
|
|
14
|
+
const truncated = message.length > maxLen
|
|
15
|
+
? sanitize(message.slice(0, maxLen)) + "..."
|
|
16
|
+
: sanitize(message);
|
|
17
|
+
return `
|
|
18
|
+
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
|
|
19
|
+
<rect width="${width}" height="${height}" fill="${background}" rx="10"/>
|
|
20
|
+
<text x="${width/2}" y="${ERROR_STYLES.TEXT_Y}" text-anchor="middle" fill="${ERROR_STYLES.COLOUR}" font-family="Arial" font-size="${ERROR_STYLES.FONT_SIZE}">
|
|
21
|
+
Error: ${truncated}
|
|
22
|
+
</text>
|
|
23
|
+
</svg>
|
|
24
|
+
`.trim();
|
|
25
|
+
}
|
package/src/render/svg.ts
CHANGED
|
@@ -1,31 +1,30 @@
|
|
|
1
|
-
import { TITLE_STYLES } from "../constants/styles.js"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
height:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
</svg
|
|
30
|
-
|
|
31
|
-
}
|
|
1
|
+
import { TITLE_STYLES } from "../constants/styles.js"
|
|
2
|
+
import type { ChartResult } from "../types.js";
|
|
3
|
+
|
|
4
|
+
export function renderSvg(
|
|
5
|
+
width: number, height: number, background: string,
|
|
6
|
+
chart: ChartResult, title: string | null, textColour: string
|
|
7
|
+
): string {
|
|
8
|
+
const titleElement = title ? `
|
|
9
|
+
<text
|
|
10
|
+
x="${chart.contentWidth / 2}"
|
|
11
|
+
y="${TITLE_STYLES.TEXT_Y}"
|
|
12
|
+
text-anchor="middle" fill="${textColour}"
|
|
13
|
+
font-family="Arial" font-size="${TITLE_STYLES.FONT_SIZE}"
|
|
14
|
+
>
|
|
15
|
+
${title}
|
|
16
|
+
</text>
|
|
17
|
+
` : '';
|
|
18
|
+
|
|
19
|
+
return `
|
|
20
|
+
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
|
|
21
|
+
<rect width="${width}" height="${height}" fill="${background}" rx="10"/>
|
|
22
|
+
<svg width="${width}" height="${height}"
|
|
23
|
+
viewBox="0 0 ${chart.contentWidth} ${chart.contentHeight}"
|
|
24
|
+
preserveAspectRatio="xMidYMid meet">
|
|
25
|
+
${titleElement}
|
|
26
|
+
${chart.segments}
|
|
27
|
+
${chart.legend}
|
|
28
|
+
</svg>
|
|
29
|
+
</svg>`.trim();
|
|
30
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,28 +1,30 @@
|
|
|
1
|
-
export type Point = {
|
|
2
|
-
x: number;
|
|
3
|
-
y: number;
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
export type Language = {
|
|
7
|
-
lang: string;
|
|
8
|
-
pct: number;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export type Geometry = {
|
|
12
|
-
CENTER_Y: number;
|
|
13
|
-
INNER_RADIUS: number;
|
|
14
|
-
OUTER_RADIUS: number;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export type ChartType = "donut" | "pie";
|
|
18
|
-
|
|
19
|
-
export type ChartResult = {
|
|
20
|
-
segments:
|
|
21
|
-
legend:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
readonly
|
|
28
|
-
|
|
1
|
+
export type Point = {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type Language = {
|
|
7
|
+
lang: string;
|
|
8
|
+
pct: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type Geometry = {
|
|
12
|
+
CENTER_Y: number;
|
|
13
|
+
INNER_RADIUS: number;
|
|
14
|
+
OUTER_RADIUS: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type ChartType = "donut" | "pie";
|
|
18
|
+
|
|
19
|
+
export type ChartResult = {
|
|
20
|
+
segments: string;
|
|
21
|
+
legend: string;
|
|
22
|
+
contentWidth: number;
|
|
23
|
+
contentHeight: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type Theme = {
|
|
27
|
+
readonly colours: readonly string[];
|
|
28
|
+
readonly text: string;
|
|
29
|
+
readonly bg: string;
|
|
30
|
+
};
|
package/src/utils/params.ts
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
import type { ChartType } from "../types.js";
|
|
2
|
-
import { sanitize } from "./sanitize.js";
|
|
3
|
-
import { VALID_TYPES } from "../constants/types.js";
|
|
4
|
-
import { DEFAULT_CONFIG } from "../constants/config.js";
|
|
5
|
-
import { THEMES } from "../constants/themes.js";
|
|
6
|
-
|
|
7
|
-
export type QueryParams = Record<string, string | undefined>;
|
|
8
|
-
|
|
9
|
-
const parseIntSafe = (
|
|
10
|
-
val: string | undefined,
|
|
11
|
-
fallback: number
|
|
12
|
-
): number => {
|
|
13
|
-
const parsed = Number.parseInt(val ?? '', 10);
|
|
14
|
-
return Number.isNaN(parsed) ? fallback : parsed;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const normalizeHex = (val: string) => `#${val.replace(/^#/, '')}`;
|
|
18
|
-
|
|
19
|
-
export function parseQueryParams(query: QueryParams) {
|
|
20
|
-
const baseTheme = THEMES[query["theme"] as keyof typeof THEMES] ?? THEMES.default;
|
|
21
|
-
const count = parseIntSafe(query["count"], DEFAULT_CONFIG.COUNT);
|
|
22
|
-
|
|
23
|
-
const customColours: string[] = [...baseTheme.colours];
|
|
24
|
-
for (let i = 1; i <= DEFAULT_CONFIG.MAX_COUNT; i++) {
|
|
25
|
-
const colourVal = query[`c${i}`];
|
|
26
|
-
if(colourVal) customColours[i - 1] = normalizeHex(colourVal);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const typeParam = query["type"] as ChartType | undefined;
|
|
30
|
-
const chartType: ChartType = VALID_TYPES.some(t => t === typeParam) ? typeParam! : "donut";
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
chartType,
|
|
34
|
-
chartTitle: query["hide_title"] === "true" ? '' : sanitize(query["title"] ?? DEFAULT_CONFIG.TITLE),
|
|
35
|
-
width: Math.max(parseIntSafe(query["width"], DEFAULT_CONFIG.WIDTH), DEFAULT_CONFIG.MIN_WIDTH ),
|
|
36
|
-
height: Math.max(parseIntSafe(query["height"], DEFAULT_CONFIG.HEIGHT), DEFAULT_CONFIG.MIN_HEIGHT),
|
|
37
|
-
count: Math.min(Math.max(count, 1), DEFAULT_CONFIG.MAX_COUNT),
|
|
38
|
-
selectedTheme: {
|
|
39
|
-
bg: THEMES[query["bg"] as keyof typeof THEMES]?.bg ?? (query["bg"] ? normalizeHex(query["bg"]) : baseTheme.bg),
|
|
40
|
-
text: query["text"] ? normalizeHex(query["text"]) : baseTheme.text,
|
|
41
|
-
colours: customColours
|
|
42
|
-
},
|
|
43
|
-
stroke: query["stroke"] === "true",
|
|
44
|
-
useTestData: query["test"] === "true",
|
|
45
|
-
errorTest: sanitize(query["error"] ?? '')
|
|
46
|
-
}
|
|
47
|
-
}
|
|
1
|
+
import type { ChartType } from "../types.js";
|
|
2
|
+
import { sanitize } from "./sanitize.js";
|
|
3
|
+
import { VALID_TYPES } from "../constants/types.js";
|
|
4
|
+
import { DEFAULT_CONFIG } from "../constants/config.js";
|
|
5
|
+
import { THEMES } from "../constants/themes.js";
|
|
6
|
+
|
|
7
|
+
export type QueryParams = Record<string, string | undefined>;
|
|
8
|
+
|
|
9
|
+
const parseIntSafe = (
|
|
10
|
+
val: string | undefined,
|
|
11
|
+
fallback: number
|
|
12
|
+
): number => {
|
|
13
|
+
const parsed = Number.parseInt(val ?? '', 10);
|
|
14
|
+
return Number.isNaN(parsed) ? fallback : parsed;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const normalizeHex = (val: string) => `#${val.replace(/^#/, '')}`;
|
|
18
|
+
|
|
19
|
+
export function parseQueryParams(query: QueryParams) {
|
|
20
|
+
const baseTheme = THEMES[query["theme"] as keyof typeof THEMES] ?? THEMES.default;
|
|
21
|
+
const count = parseIntSafe(query["count"], DEFAULT_CONFIG.COUNT);
|
|
22
|
+
|
|
23
|
+
const customColours: string[] = [...baseTheme.colours];
|
|
24
|
+
for (let i = 1; i <= DEFAULT_CONFIG.MAX_COUNT; i++) {
|
|
25
|
+
const colourVal = query[`c${i}`];
|
|
26
|
+
if(colourVal) customColours[i - 1] = normalizeHex(colourVal);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const typeParam = query["type"] as ChartType | undefined;
|
|
30
|
+
const chartType: ChartType = VALID_TYPES.some(t => t === typeParam) ? typeParam! : "donut";
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
chartType,
|
|
34
|
+
chartTitle: query["hide_title"] === "true" ? '' : sanitize(query["title"] ?? DEFAULT_CONFIG.TITLE),
|
|
35
|
+
width: Math.max(parseIntSafe(query["width"], DEFAULT_CONFIG.WIDTH), DEFAULT_CONFIG.MIN_WIDTH ),
|
|
36
|
+
height: Math.max(parseIntSafe(query["height"], DEFAULT_CONFIG.HEIGHT), DEFAULT_CONFIG.MIN_HEIGHT),
|
|
37
|
+
count: Math.min(Math.max(count, 1), DEFAULT_CONFIG.MAX_COUNT),
|
|
38
|
+
selectedTheme: {
|
|
39
|
+
bg: THEMES[query["bg"] as keyof typeof THEMES]?.bg ?? (query["bg"] ? normalizeHex(query["bg"]) : baseTheme.bg),
|
|
40
|
+
text: query["text"] ? normalizeHex(query["text"]) : baseTheme.text,
|
|
41
|
+
colours: customColours
|
|
42
|
+
},
|
|
43
|
+
stroke: query["stroke"] === "true",
|
|
44
|
+
useTestData: query["test"] === "true",
|
|
45
|
+
errorTest: sanitize(query["error"] ?? '')
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/utils/sanitize.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
const ESCAPE_MAP = {
|
|
2
|
-
'<': '<',
|
|
3
|
-
'>': '>',
|
|
4
|
-
'&': '&',
|
|
5
|
-
'"': '"',
|
|
6
|
-
"'": ''',
|
|
7
|
-
} as const;
|
|
8
|
-
|
|
9
|
-
export const sanitize = (str: unknown): string => {
|
|
10
|
-
if (typeof str !== "string") return '';
|
|
11
|
-
return str.replace(/[<>&"']/g, (m: string): string =>
|
|
12
|
-
ESCAPE_MAP[m as keyof typeof ESCAPE_MAP]
|
|
13
|
-
);
|
|
14
|
-
};
|
|
1
|
+
const ESCAPE_MAP = {
|
|
2
|
+
'<': '<',
|
|
3
|
+
'>': '>',
|
|
4
|
+
'&': '&',
|
|
5
|
+
'"': '"',
|
|
6
|
+
"'": ''',
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export const sanitize = (str: unknown): string => {
|
|
10
|
+
if (typeof str !== "string") return '';
|
|
11
|
+
return str.replace(/[<>&"']/g, (m: string): string =>
|
|
12
|
+
ESCAPE_MAP[m as keyof typeof ESCAPE_MAP]
|
|
13
|
+
);
|
|
14
|
+
};
|