Compare commits

...

2 Commits

Author SHA1 Message Date
release bot
e7aa7b7289 📦 2.0.0-rc.1 2022-04-11 21:23:16 +00:00
Platane
6b320a1ac4 change options, drop svg_out_path in favor of outputs list 2022-04-11 23:19:33 +02:00
26 changed files with 457 additions and 219 deletions

View File

@@ -45,14 +45,17 @@ jobs:
uses: ./
with:
github_user_name: platane
gif_out_path: dist/github-contribution-grid-snake.gif
svg_out_path: dist/github-contribution-grid-snake.svg
outputs: |
dist/github-contribution-grid-snake.svg
dist/github-contribution-grid-snake-dark.svg?palette=github-dark
dist/github-contribution-grid-snake.gif?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9
- name: ensure the generated file exists
run: |
ls dist
test -f ${{ steps.generate-snake.outputs.gif_out_path }}
test -f ${{ steps.generate-snake.outputs.svg_out_path }}
test -f dist/github-contribution-grid-snake.svg
test -f dist/github-contribution-grid-snake-dark.svg
test -f dist/github-contribution-grid-snake.gif
- uses: crazy-max/ghaction-github-pages@v2.5.0
with:
@@ -76,7 +79,7 @@ jobs:
GITHUB_USER_CONTRIBUTION_API_ENDPOINT: https://snk-one.vercel.app/api/github-user-contribution/
- uses: crazy-max/ghaction-github-pages@v2.6.0
if: success() && github.ref == 'refs/heads/master'
if: success() && github.ref.name == 'main'
with:
target_branch: gh-pages
build_dir: packages/demo/dist

View File

@@ -65,7 +65,7 @@ jobs:
git add package.json svg-only/dist action.yml
git commit -m "📦 $VERSION"
git tag v$VERSION
git push origin master --tags
git push origin main --tags
if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
git tag v$( echo $VERSION | cut -d. -f 1-1 )

View File

@@ -21,24 +21,23 @@ Available as github action. Automatically generate a new image at the end of the
**github action**
```yaml
- uses: Platane/snk@v1
- uses: Platane/snk@v2
with:
# github user name to read the contribution graph from (**required**)
# using action context var `github.repository_owner` or specified user
github_user_name: ${{ github.repository_owner }}
# path of the generated gif file
# If left empty, the gif file will not be generated
gif_out_path: dist/github-snake.gif
# path of the generated svg file
# If left empty, the svg file will not be generated
svg_out_path: dist/github-snake.svg
# list of files to generate.
# one file per line. Each output can be customized with options as query string.
outputs: |
dist/github-snake.svg
dist/github-snake.svg?palette=github-dark
dist/ocean.gif?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9
```
[example with cron job](https://github.com/Platane/Platane/blob/master/.github/workflows/main.yml#L24-L29)
If you are only interested in generating a svg, you can use this other faster action: `uses: Platane/snk/svg-only@v1`
If you are only interested in generating a svg, you can use this other faster action: `uses: Platane/snk/svg-only@v2`
**interactive demo**

View File

@@ -4,23 +4,28 @@ author: "platane"
runs:
using: docker
image: docker://platane/snk@sha256:74d02183a9a4adb8e00d9f50e6eb5035a5b6ef02644d848363ef3301235ebd1d
image: docker://platane/snk@sha256:300fb94d3b1214e6c229990b458286a8f1c4c68a178b1b59b670c9fcac7c80d1
inputs:
github_user_name:
description: "github user name"
required: true
gif_out_path:
description: "path of the generated gif file. If left empty, the gif file will not be generated."
required: false
default: null
svg_out_path:
description: "path of the generated svg file. If left empty, the svg file will not be generated."
outputs:
required: false
default: null
description: |
list of files to generate.
Generates one file per line. Each output can be customized with query string.
following this pattern: path/to/file.<gif or svg>?palette=<github or github-dark>&color_snake=<color>&color_dots=<color>,<color>,<color>,<color>,<color>
outputs:
gif_out_path:
description: "path of the generated gif"
svg_out_path:
description: "path of the generated svg"
supported query string options:
- palette: a preset of color, one of [github, github-dark, github-light]
- color_snake: color of the snake
- color_dots: coma separated list of dots color. The first one is the empty cell color, the second one is the lightest shade, the third one is the second lightest shade ect ...
example:
outputs: |
dark.svg?palette=github-dark&color_snake=blue
light.svg?color_snake=#7845ab
ocean.gif?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9

View File

@@ -1,7 +1,7 @@
{
"name": "snk",
"description": "Generates a snake game from a github user contributions grid",
"version": "1.1.3",
"version": "2.0.0-rc.1",
"private": true,
"repository": "github:platane/snk",
"devDependencies": {

View File

@@ -1,2 +1,3 @@
*
!.gitignore
!.gitignore
!*.snap

View File

@@ -0,0 +1,66 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should parse /out.svg?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9 1`] = `
Object {
"drawOptions": Object {
"colorDotBorder": "#1b1f230a",
"colorDots": Array [
"#bfd6f6",
"#8dbdff",
"#64a1f4",
"#4b91f1",
"#3c7dd9",
],
"colorEmpty": "#bfd6f6",
"colorSnake": "orange",
"dark": undefined,
"sizeCell": 16,
"sizeDot": 12,
"sizeDotBorderRadius": 2,
},
"filename": "/out.svg",
"format": "svg",
"gifOptions": Object {
"frameDuration": 100,
"step": 1,
},
}
`;
exports[`should parse path/to/out.gif 1`] = `
Object {
"drawOptions": Object {
"colorDotBorder": "#1b1f230a",
"colorDots": Array [
"#ebedf0",
"#9be9a8",
"#40c463",
"#30a14e",
"#216e39",
],
"colorEmpty": "#ebedf0",
"colorSnake": "purple",
"dark": Object {
"colorDotBorder": "#1b1f230a",
"colorDots": Array [
"#161b22",
"#01311f",
"#034525",
"#0f6d31",
"#00c647",
],
"colorEmpty": "#161b22",
"colorSnake": "purple",
},
"sizeCell": 16,
"sizeDot": 12,
"sizeDotBorderRadius": 2,
},
"filename": "path/to/out.gif",
"format": "gif",
"gifOptions": Object {
"frameDuration": 100,
"step": 1,
},
}
`;

View File

@@ -1,19 +0,0 @@
import * as fs from "fs";
import * as path from "path";
import { generateContributionSnake } from "../generateContributionSnake";
(async () => {
const outputSvg = path.join(__dirname, "__snapshots__/out.svg");
const outputGif = path.join(__dirname, "__snapshots__/out.gif");
const buffer = await generateContributionSnake("platane", {
svg: true,
gif: true,
});
console.log("💾 writing to", outputSvg);
fs.writeFileSync(outputSvg, buffer.svg);
console.log("💾 writing to", outputGif);
fs.writeFileSync(outputGif, buffer.gif);
})();

View File

@@ -1,6 +1,7 @@
import * as fs from "fs";
import * as path from "path";
import { generateContributionSnake } from "../generateContributionSnake";
import { parseOutputsOption } from "../outputsOptions";
jest.setTimeout(2 * 60 * 1000);
@@ -17,22 +18,26 @@ const silent = (handler: () => void | Promise<void>) => async () => {
it(
"should generate contribution snake",
silent(async () => {
const outputSvg = path.join(__dirname, "__snapshots__/out.svg");
const outputGif = path.join(__dirname, "__snapshots__/out.gif");
const entries = [
path.join(__dirname, "__snapshots__/out.svg"),
console.log = () => undefined;
const buffer = await generateContributionSnake("platane", {
svg: true,
gif: true,
});
path.join(__dirname, "__snapshots__/out-dark.svg") +
"?palette=github-dark&color_snake=orange",
expect(buffer.svg).toBeDefined();
expect(buffer.gif).toBeDefined();
path.join(__dirname, "__snapshots__/out.gif") +
"?color_snake=orange&color_dots=#d4e0f0,#8dbdff,#64a1f4,#4b91f1,#3c7dd9",
];
console.log("💾 writing to", outputSvg);
fs.writeFileSync(outputSvg, buffer.svg);
const outputs = parseOutputsOption(entries);
console.log("💾 writing to", outputGif);
fs.writeFileSync(outputGif, buffer.gif);
const results = await generateContributionSnake("platane", outputs);
expect(results[0]).toBeDefined();
expect(results[1]).toBeDefined();
expect(results[2]).toBeDefined();
fs.writeFileSync(outputs[0]!.filename, results[0]!);
fs.writeFileSync(outputs[1]!.filename, results[1]!);
fs.writeFileSync(outputs[2]!.filename, results[2]!);
})
);

View File

@@ -0,0 +1,11 @@
import { parseEntry } from "../outputsOptions";
[
"path/to/out.gif",
"/out.svg?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9",
].forEach((entry) =>
it(`should parse ${entry}`, () => {
expect(parseEntry(entry)).toMatchSnapshot();
})
);

View File

@@ -3,10 +3,18 @@ import { userContributionToGrid } from "./userContributionToGrid";
import { getBestRoute } from "@snk/solver/getBestRoute";
import { snake4 } from "@snk/types/__fixtures__/snake";
import { getPathToPose } from "@snk/solver/getPathToPose";
import { Options as DrawOptions } from "@snk/svg-creator";
export const generateContributionSnake = async (
userName: string,
format: { svg?: boolean; gif?: boolean }
outputs: ({
format: "svg" | "gif";
drawOptions: DrawOptions;
gifOptions: {
frameDuration: number;
step: number;
};
} | null)[]
) => {
console.log("🎣 fetching github user contribution");
const { cells, colorScheme } = await getGithubUserContribution(userName);
@@ -14,40 +22,26 @@ export const generateContributionSnake = async (
const grid = userContributionToGrid(cells, colorScheme);
const snake = snake4;
const drawOptions = {
sizeBorderRadius: 2,
sizeCell: 16,
sizeDot: 12,
colorBorder: "#1b1f230a",
colorDots: colorScheme as any,
colorEmpty: colorScheme[0],
colorSnake: "purple",
cells,
dark: {
colorEmpty: "#161b22",
colorDots: { 1: "#01311f", 2: "#034525", 3: "#0f6d31", 4: "#00c647" },
},
};
const gifOptions = { frameDuration: 100, step: 1 };
console.log("📡 computing best route");
const chain = getBestRoute(grid, snake)!;
chain.push(...getPathToPose(chain.slice(-1)[0], snake)!);
const output: Record<string, Buffer | string> = {};
if (format.gif) {
console.log("📹 creating gif");
const { createGif } = await import("@snk/gif-creator");
output.gif = await createGif(grid, chain, drawOptions, gifOptions);
}
if (format.svg) {
console.log("🖌 creating svg");
const { createSvg } = await import("@snk/svg-creator");
output.svg = createSvg(grid, chain, drawOptions, gifOptions);
}
return output;
return Promise.all(
outputs.map(async (out, i) => {
if (!out) return;
const { format, drawOptions, gifOptions } = out;
switch (format) {
case "svg": {
console.log(`🖌 creating svg (outputs[${i}])`);
const { createSvg } = await import("@snk/svg-creator");
return createSvg(grid, chain, drawOptions, gifOptions);
}
case "gif": {
console.log(`📹 creating gif (outputs[${i}])`);
const { createGif } = await import("@snk/gif-creator");
return await createGif(grid, chain, drawOptions, gifOptions);
}
}
})
);
};

View File

@@ -2,30 +2,28 @@ import * as fs from "fs";
import * as path from "path";
import * as core from "@actions/core";
import { generateContributionSnake } from "./generateContributionSnake";
import { parseOutputsOption } from "./outputsOptions";
(async () => {
try {
const userName = core.getInput("github_user_name");
const format = {
svg: core.getInput("svg_out_path"),
gif: core.getInput("gif_out_path"),
};
const { svg, gif } = await generateContributionSnake(
userName,
format as any
const outputs = parseOutputsOption(
core.getMultilineInput("outputs") ?? [
core.getInput("gif_out_path"),
core.getInput("svg_out_path"),
]
);
if (svg) {
fs.mkdirSync(path.dirname(format.svg), { recursive: true });
fs.writeFileSync(format.svg, svg);
core.setOutput("svg_out_path", format.svg);
}
if (gif) {
fs.mkdirSync(path.dirname(format.gif), { recursive: true });
fs.writeFileSync(format.gif, gif);
core.setOutput("gif_out_path", format.gif);
}
const results = await generateContributionSnake(userName, outputs);
outputs.forEach((out, i) => {
const result = results[i];
if (out?.filename && result) {
console.log(`💾 writing to ${out?.filename}`);
fs.mkdirSync(path.dirname(out?.filename), { recursive: true });
fs.writeFileSync(out?.filename, result);
}
});
} catch (e: any) {
core.setFailed(`Action failed with "${e.message}"`);
}

View File

@@ -0,0 +1,54 @@
import { Options as DrawOptions } from "@snk/svg-creator";
import { palettes } from "./palettes";
export const parseOutputsOption = (lines: string[]) => lines.map(parseEntry);
export const parseEntry = (entry: string) => {
const m = entry.trim().match(/^(.+\.(svg|gif))(\?.*)?$/);
if (!m) return null;
const [_, filename, format, query] = m;
const sp = new URLSearchParams(query || "");
const drawOptions: DrawOptions = {
sizeDotBorderRadius: 2,
sizeCell: 16,
sizeDot: 12,
...palettes["default"],
};
const gifOptions = { step: 1, frameDuration: 100 };
{
const palette = palettes[sp.get("palette")!];
if (palette) {
Object.assign(drawOptions, palette);
drawOptions.dark = palette.dark && { ...palette.dark };
}
}
if (sp.has("color_snake")) drawOptions.colorSnake = sp.get("color_snake")!;
if (sp.has("color_dots")) {
const colors = sp.get("color_dots")!.split(/[,;]/);
drawOptions.colorDots = colors;
drawOptions.colorEmpty = colors[0];
drawOptions.dark = undefined;
}
if (sp.has("color_dot_border"))
drawOptions.colorDotBorder = sp.get("color_dot_border")!;
if (sp.has("dark_color_dots")) {
const colors = sp.get("dark_color_dots")!.split(/[,;]/);
drawOptions.dark = {
...drawOptions.dark,
colorDots: colors,
colorEmpty: colors[0],
};
}
if (sp.has("dark_color_dot_border") && drawOptions.dark)
drawOptions.dark.colorDotBorder = sp.get("color_dot_border")!;
if (sp.has("dark_color_snake") && drawOptions.dark)
drawOptions.dark.colorSnake = sp.get("color_snake")!;
return { filename, format: format as "svg" | "gif", drawOptions, gifOptions };
};

View File

@@ -10,11 +10,9 @@
"@snk/types": "1.0.0"
},
"devDependencies": {
"@vercel/ncc": "0.24.1",
"ts-node": "10.7.0"
"@vercel/ncc": "0.24.1"
},
"scripts": {
"build": "ncc build --external canvas --external gifsicle --out dist ./index.ts",
"dev": "ts-node __tests__/dev.ts"
"build": "ncc build --external canvas --external gifsicle --out dist ./index.ts"
}
}

View File

@@ -0,0 +1,29 @@
import { Options as DrawOptions } from "@snk/svg-creator";
export const palettes: Record<
string,
Pick<
DrawOptions,
"colorDotBorder" | "colorEmpty" | "colorSnake" | "colorDots" | "dark"
>
> = {
"github-light": {
colorDotBorder: "#1b1f230a",
colorDots: ["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"],
colorEmpty: "#ebedf0",
colorSnake: "purple",
},
"github-dark": {
colorDotBorder: "#1b1f230a",
colorEmpty: "#161b22",
colorDots: ["#161b22", "#01311f", "#034525", "#0f6d31", "#00c647"],
colorSnake: "purple",
},
};
// aliases
palettes["github"] = {
...palettes["github-light"],
dark: { ...palettes["github-dark"] },
};
palettes["default"] = palettes["github"];

View File

@@ -1,12 +1,13 @@
import { Color, Grid } from "@snk/types/grid";
import { drawLerpWorld, drawWorld } from "@snk/draw/drawWorld";
import { Snake } from "@snk/types/snake";
import type { Options as DrawOptions } from "@snk/svg-creator";
export const drawOptions = {
sizeBorderRadius: 2,
export const drawOptions: DrawOptions = {
sizeDotBorderRadius: 2,
sizeCell: 16,
sizeDot: 12,
colorBorder: "#1b1f230a",
colorDotBorder: "#1b1f230a",
colorDots: {
1: "#9be9a8",
2: "#40c463",

View File

@@ -6,7 +6,7 @@ import type { Snake } from "@snk/types/snake";
import {
drawLerpWorld,
getCanvasWorldSize,
Options,
Options as DrawOptions,
} from "@snk/draw/drawWorld";
import { userContributionToGrid } from "@snk/action/userContributionToGrid";
import { createSvg } from "@snk/svg-creator";
@@ -120,7 +120,7 @@ const createViewer = ({
}: {
grid0: Grid;
chain: Snake[];
drawOptions: Options;
drawOptions: DrawOptions;
}) => {
//
// canvas
@@ -229,11 +229,11 @@ const onSubmit = async (userName: string) => {
);
const { cells, colorScheme } = (await res.json()) as Res;
const drawOptions = {
sizeBorderRadius: 2,
const drawOptions: DrawOptions = {
sizeDotBorderRadius: 2,
sizeCell: 16,
sizeDot: 12,
colorBorder: "#1b1f230a",
colorDotBorder: "#1b1f230a",
colorDots: colorScheme as any,
colorEmpty: colorScheme[0],
colorSnake: "purple",

View File

@@ -6,10 +6,10 @@ import type { Point } from "@snk/types/point";
type Options = {
colorDots: Record<Color, string>;
colorEmpty: string;
colorBorder: string;
colorDotBorder: string;
sizeCell: number;
sizeDot: number;
sizeBorderRadius: number;
sizeDotBorderRadius: number;
cells?: Point[];
};
@@ -31,11 +31,11 @@ export const drawGrid = (
);
ctx.fillStyle = color;
ctx.strokeStyle = o.colorBorder;
ctx.strokeStyle = o.colorDotBorder;
ctx.lineWidth = 1;
ctx.beginPath();
pathRoundedRect(ctx, o.sizeDot, o.sizeDot, o.sizeBorderRadius);
pathRoundedRect(ctx, o.sizeDot, o.sizeDot, o.sizeDotBorderRadius);
ctx.fill();
ctx.stroke();

View File

@@ -7,11 +7,11 @@ import type { Snake } from "@snk/types/snake";
export type Options = {
colorDots: Record<Color, string>;
colorEmpty: string;
colorBorder: string;
colorDotBorder: string;
colorSnake: string;
sizeCell: number;
sizeDot: number;
sizeBorderRadius: number;
sizeDotBorderRadius: number;
cells?: Point[];
};

View File

@@ -5,6 +5,7 @@ import { realistic as grid } from "@snk/types/__fixtures__/grid";
import { createGif } from "..";
import { getBestRoute } from "@snk/solver/getBestRoute";
import { getPathToPose } from "@snk/solver/getPathToPose";
import type { Options as DrawOptions } from "@snk/draw/drawWorld";
let snake = createSnakeFromCells(
Array.from({ length: 4 }, (_, i) => ({ x: i, y: -1 }))
@@ -24,11 +25,11 @@ let snake = createSnakeFromCells(
const chain = getBestRoute(grid, snake)!;
chain.push(...getPathToPose(chain.slice(-1)[0], snake)!);
const drawOptions = {
sizeBorderRadius: 2,
const drawOptions: DrawOptions = {
sizeDotBorderRadius: 2,
sizeCell: 16,
sizeDot: 12,
colorBorder: "#1b1f230a",
colorDotBorder: "#1b1f230a",
colorDots: { 1: "#9be9a8", 2: "#40c463", 3: "#30a14e", 4: "#216e39" },
colorEmpty: "#ebedf0",
colorSnake: "purple",

View File

@@ -5,15 +5,16 @@ import * as grids from "@snk/types/__fixtures__/grid";
import { snake3 as snake } from "@snk/types/__fixtures__/snake";
import { createSnakeFromCells, nextSnake } from "@snk/types/snake";
import { getBestRoute } from "@snk/solver/getBestRoute";
import type { Options as DrawOptions } from "@snk/draw/drawWorld";
jest.setTimeout(20 * 1000);
const upscale = 1;
const drawOptions = {
sizeBorderRadius: 2 * upscale,
const drawOptions: DrawOptions = {
sizeDotBorderRadius: 2 * upscale,
sizeCell: 16 * upscale,
sizeDot: 12 * upscale,
colorBorder: "#1b1f230a",
colorDotBorder: "#1b1f230a",
colorDots: { 1: "#9be9a8", 2: "#40c463", 3: "#30a14e", 4: "#216e39" },
colorEmpty: "#ebedf0",
colorSnake: "purple",

View File

@@ -1,15 +1,15 @@
import * as fs from "fs";
import * as path from "path";
import { createSvg } from "..";
import { createSvg, Options } from "..";
import * as grids from "@snk/types/__fixtures__/grid";
import { snake3 as snake } from "@snk/types/__fixtures__/snake";
import { getBestRoute } from "@snk/solver/getBestRoute";
const drawOptions = {
sizeBorderRadius: 2,
const drawOptions: Options = {
sizeDotBorderRadius: 2,
sizeCell: 16,
sizeDot: 12,
colorBorder: "#1b1f230a",
colorDotBorder: "#1b1f230a",
colorDots: { 1: "#9be9a8", 2: "#40c463", 3: "#30a14e", 4: "#216e39" },
colorEmpty: "#ebedf0",
colorSnake: "purple",

View File

@@ -5,17 +5,17 @@ import { h } from "./utils";
export type Options = {
colorDots: Record<Color, string>;
colorEmpty: string;
colorBorder: string;
colorDotBorder: string;
sizeCell: number;
sizeDot: number;
sizeBorderRadius: number;
sizeDotBorderRadius: number;
};
const percent = (x: number) => (x * 100).toFixed(2);
export const createGrid = (
cells: (Point & { t: number | null; color: Color | Empty })[],
{ sizeBorderRadius, sizeDot, sizeCell }: Options,
{ sizeDotBorderRadius, sizeDot, sizeCell }: Options,
duration: number
) => {
const svgElements: string[] = [];
@@ -54,8 +54,8 @@ export const createGrid = (
class: ["c", id].filter(Boolean).join(" "),
x: x * s + m,
y: y * s + m,
rx: sizeBorderRadius,
ry: sizeBorderRadius,
rx: sizeDotBorderRadius,
ry: sizeDotBorderRadius,
width: d,
height: d,
})

View File

@@ -18,16 +18,16 @@ import * as csso from "csso";
export type Options = {
colorDots: Record<Color, string>;
colorEmpty: string;
colorBorder: string;
colorDotBorder: string;
colorSnake: string;
sizeCell: number;
sizeDot: number;
sizeBorderRadius: number;
sizeDotBorderRadius: number;
cells?: Point[];
dark?: {
colorDots: Record<Color, string>;
colorEmpty: string;
colorBorder?: string;
colorDotBorder?: string;
colorSnake?: string;
};
};
@@ -137,7 +137,7 @@ const optimizeSvg = (svg: string) => svg;
const generateColorVar = (drawOptions: Options) =>
`
:root {
--cb: ${drawOptions.colorBorder};
--cb: ${drawOptions.colorDotBorder};
--cs: ${drawOptions.colorSnake};
--ce: ${drawOptions.colorEmpty};
${Object.entries(drawOptions.colorDots)
@@ -149,7 +149,7 @@ const generateColorVar = (drawOptions: Options) =>
? `
@media (prefers-color-scheme: dark) {
:root {
--cb: ${drawOptions.dark.colorBorder || drawOptions.colorBorder};
--cb: ${drawOptions.dark.colorDotBorder || drawOptions.colorDotBorder};
--cs: ${drawOptions.dark.colorSnake || drawOptions.colorSnake};
--ce: ${drawOptions.dark.colorEmpty};
${Object.entries(drawOptions.dark.colorDots)

View File

@@ -1,17 +1,12 @@
import { getSnakeLength, snakeToCells } from "@snk/types/snake";
import type { Snake } from "@snk/types/snake";
import type { Color } from "@snk/types/grid";
import type { Point } from "@snk/types/point";
import { h } from "./utils";
export type Options = {
colorDots: Record<Color, string>;
colorEmpty: string;
colorBorder: string;
colorSnake: string;
sizeCell: number;
sizeDot: number;
sizeBorderRadius: number;
};
const percent = (x: number) => (x * 100).toFixed(2);

224
svg-only/dist/index.js vendored
View File

@@ -32781,55 +32781,51 @@ var userContributionToGrid_1 = __webpack_require__(5740);
var getBestRoute_1 = __webpack_require__(2705);
var snake_1 = __webpack_require__(7087);
var getPathToPose_1 = __webpack_require__(6963);
var generateContributionSnake = function (userName, format) { return __awaiter(void 0, void 0, void 0, function () {
var _a, cells, colorScheme, grid, snake, drawOptions, gifOptions, chain, output, createGif, _b, createSvg;
return __generator(this, function (_c) {
switch (_c.label) {
var generateContributionSnake = function (userName, outputs) { return __awaiter(void 0, void 0, void 0, function () {
var _a, cells, colorScheme, grid, snake, chain;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
console.log("🎣 fetching github user contribution");
return [4 /*yield*/, (0, github_user_contribution_1.getGithubUserContribution)(userName)];
case 1:
_a = _c.sent(), cells = _a.cells, colorScheme = _a.colorScheme;
_a = _b.sent(), cells = _a.cells, colorScheme = _a.colorScheme;
grid = (0, userContributionToGrid_1.userContributionToGrid)(cells, colorScheme);
snake = snake_1.snake4;
drawOptions = {
sizeBorderRadius: 2,
sizeCell: 16,
sizeDot: 12,
colorBorder: "#1b1f230a",
colorDots: colorScheme,
colorEmpty: colorScheme[0],
colorSnake: "purple",
cells: cells,
dark: {
colorEmpty: "#161b22",
colorDots: { 1: "#01311f", 2: "#034525", 3: "#0f6d31", 4: "#00c647" }
}
};
gifOptions = { frameDuration: 100, step: 1 };
console.log("📡 computing best route");
chain = (0, getBestRoute_1.getBestRoute)(grid, snake);
chain.push.apply(chain, (0, getPathToPose_1.getPathToPose)(chain.slice(-1)[0], snake));
output = {};
if (!format.gif) return [3 /*break*/, 4];
console.log("📹 creating gif");
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(__webpack_require__(340)); })];
case 2:
createGif = (_c.sent()).createGif;
_b = output;
return [4 /*yield*/, createGif(grid, chain, drawOptions, gifOptions)];
case 3:
_b.gif = _c.sent();
_c.label = 4;
case 4:
if (!format.svg) return [3 /*break*/, 6];
console.log("🖌 creating svg");
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(__webpack_require__(7415)); })];
case 5:
createSvg = (_c.sent()).createSvg;
output.svg = createSvg(grid, chain, drawOptions, gifOptions);
_c.label = 6;
case 6: return [2 /*return*/, output];
return [2 /*return*/, Promise.all(outputs.map(function (out, i) { return __awaiter(void 0, void 0, void 0, function () {
var format, drawOptions, gifOptions, _a, createSvg, createGif;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (!out)
return [2 /*return*/];
format = out.format, drawOptions = out.drawOptions, gifOptions = out.gifOptions;
_a = format;
switch (_a) {
case "svg": return [3 /*break*/, 1];
case "gif": return [3 /*break*/, 3];
}
return [3 /*break*/, 6];
case 1:
console.log("\uD83D\uDD8C creating svg (outputs[".concat(i, "])"));
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(__webpack_require__(7415)); })];
case 2:
createSvg = (_b.sent()).createSvg;
return [2 /*return*/, createSvg(grid, chain, drawOptions, gifOptions)];
case 3:
console.log("\uD83D\uDCF9 creating gif (outputs[".concat(i, "])"));
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(__webpack_require__(340)); })];
case 4:
createGif = (_b.sent()).createGif;
return [4 /*yield*/, createGif(grid, chain, drawOptions, gifOptions)];
case 5: return [2 /*return*/, _b.sent()];
case 6: return [2 /*return*/];
}
});
}); }))];
}
});
}); };
@@ -32907,30 +32903,30 @@ var fs = __importStar(__webpack_require__(5747));
var path = __importStar(__webpack_require__(5622));
var core = __importStar(__webpack_require__(7117));
var generateContributionSnake_1 = __webpack_require__(8847);
var outputsOptions_1 = __webpack_require__(3379);
(function () { return __awaiter(void 0, void 0, void 0, function () {
var userName, format, _a, svg, gif, e_1;
var userName, outputs, results_1, e_1;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_b.trys.push([0, 2, , 3]);
userName = core.getInput("github_user_name");
format = {
svg: core.getInput("svg_out_path"),
gif: core.getInput("gif_out_path")
};
return [4 /*yield*/, (0, generateContributionSnake_1.generateContributionSnake)(userName, format)];
outputs = (0, outputsOptions_1.parseOutputsOption)((_a = core.getMultilineInput("outputs")) !== null && _a !== void 0 ? _a : [
core.getInput("gif_out_path"),
core.getInput("svg_out_path"),
]);
return [4 /*yield*/, (0, generateContributionSnake_1.generateContributionSnake)(userName, outputs)];
case 1:
_a = _b.sent(), svg = _a.svg, gif = _a.gif;
if (svg) {
fs.mkdirSync(path.dirname(format.svg), { recursive: true });
fs.writeFileSync(format.svg, svg);
core.setOutput("svg_out_path", format.svg);
}
if (gif) {
fs.mkdirSync(path.dirname(format.gif), { recursive: true });
fs.writeFileSync(format.gif, gif);
core.setOutput("gif_out_path", format.gif);
}
results_1 = _b.sent();
outputs.forEach(function (out, i) {
var result = results_1[i];
if ((out === null || out === void 0 ? void 0 : out.filename) && result) {
console.log("\uD83D\uDCBE writing to ".concat(out === null || out === void 0 ? void 0 : out.filename));
fs.mkdirSync(path.dirname(out === null || out === void 0 ? void 0 : out.filename), { recursive: true });
fs.writeFileSync(out === null || out === void 0 ? void 0 : out.filename, result);
}
});
return [3 /*break*/, 3];
case 2:
e_1 = _b.sent();
@@ -32942,6 +32938,106 @@ var generateContributionSnake_1 = __webpack_require__(8847);
}); })();
/***/ }),
/***/ 3379:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
exports.__esModule = true;
exports.parseEntry = exports.parseOutputsOption = void 0;
var palettes_1 = __webpack_require__(3848);
var parseOutputsOption = function (lines) { return lines.map(exports.parseEntry); };
exports.parseOutputsOption = parseOutputsOption;
var parseEntry = function (entry) {
var m = entry.trim().match(/^(.+\.(svg|gif))(\?.*)?$/);
if (!m)
return null;
var _ = m[0], filename = m[1], format = m[2], query = m[3];
var sp = new URLSearchParams(query || "");
var drawOptions = __assign({ sizeDotBorderRadius: 2, sizeCell: 16, sizeDot: 12 }, palettes_1.palettes["default"]);
var gifOptions = { step: 1, frameDuration: 100 };
{
var palette = palettes_1.palettes[sp.get("palette")];
if (palette) {
Object.assign(drawOptions, palette);
drawOptions.dark = palette.dark && __assign({}, palette.dark);
}
}
if (sp.has("color_snake"))
drawOptions.colorSnake = sp.get("color_snake");
if (sp.has("color_dots")) {
var colors = sp.get("color_dots").split(/[,;]/);
drawOptions.colorDots = colors;
drawOptions.colorEmpty = colors[0];
drawOptions.dark = undefined;
}
if (sp.has("color_dot_border"))
drawOptions.colorDotBorder = sp.get("color_dot_border");
if (sp.has("dark_color_dots")) {
var colors = sp.get("dark_color_dots").split(/[,;]/);
drawOptions.dark = __assign(__assign({}, drawOptions.dark), { colorDots: colors, colorEmpty: colors[0] });
}
if (sp.has("dark_color_dot_border") && drawOptions.dark)
drawOptions.dark.colorDotBorder = sp.get("color_dot_border");
if (sp.has("dark_color_snake") && drawOptions.dark)
drawOptions.dark.colorSnake = sp.get("color_snake");
return { filename: filename, format: format, drawOptions: drawOptions, gifOptions: gifOptions };
};
exports.parseEntry = parseEntry;
/***/ }),
/***/ 3848:
/***/ (function(__unused_webpack_module, exports) {
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
exports.__esModule = true;
exports.palettes = void 0;
exports.palettes = {
"github-light": {
colorDotBorder: "#1b1f230a",
colorDots: ["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"],
colorEmpty: "#ebedf0",
colorSnake: "purple"
},
"github-dark": {
colorDotBorder: "#1b1f230a",
colorEmpty: "#161b22",
colorDots: ["#161b22", "#01311f", "#034525", "#0f6d31", "#00c647"],
colorSnake: "purple"
}
};
// aliases
exports.palettes.github = __assign(__assign({}, exports.palettes["github-light"]), { dark: __assign({}, exports.palettes["github-dark"]) });
exports.palettes.default = exports.palettes["github"];
/***/ }),
/***/ 5740:
@@ -32999,10 +33095,10 @@ var drawGrid = function (ctx, grid, o) {
ctx.save();
ctx.translate(x * o.sizeCell + (o.sizeCell - o.sizeDot) / 2, y * o.sizeCell + (o.sizeCell - o.sizeDot) / 2);
ctx.fillStyle = color;
ctx.strokeStyle = o.colorBorder;
ctx.strokeStyle = o.colorDotBorder;
ctx.lineWidth = 1;
ctx.beginPath();
(0, pathRoundedRect_1.pathRoundedRect)(ctx, o.sizeDot, o.sizeDot, o.sizeBorderRadius);
(0, pathRoundedRect_1.pathRoundedRect)(ctx, o.sizeDot, o.sizeDot, o.sizeDotBorderRadius);
ctx.fill();
ctx.stroke();
ctx.closePath();
@@ -34284,7 +34380,7 @@ exports.createGrid = void 0;
var utils_1 = __webpack_require__(5913);
var percent = function (x) { return (x * 100).toFixed(2); };
var createGrid = function (cells, _a, duration) {
var sizeBorderRadius = _a.sizeBorderRadius, sizeDot = _a.sizeDot, sizeCell = _a.sizeCell;
var sizeDotBorderRadius = _a.sizeDotBorderRadius, sizeDot = _a.sizeDot, sizeCell = _a.sizeCell;
var svgElements = [];
var styles = [
".c{\n shape-rendering: geometricPrecision;\n fill: var(--ce);\n stroke-width: 1px;\n stroke: var(--cb);\n animation: none ".concat(duration, "ms linear infinite;\n }"),
@@ -34307,8 +34403,8 @@ var createGrid = function (cells, _a, duration) {
"class": ["c", id].filter(Boolean).join(" "),
x: x * s + m,
y: y * s + m,
rx: sizeBorderRadius,
ry: sizeBorderRadius,
rx: sizeDotBorderRadius,
ry: sizeDotBorderRadius,
width: d,
height: d
}));
@@ -34442,14 +34538,14 @@ exports.createSvg = createSvg;
var optimizeCss = function (css) { return csso.minify(css).css; };
var optimizeSvg = function (svg) { return svg; };
var generateColorVar = function (drawOptions) {
return "\n :root {\n --cb: ".concat(drawOptions.colorBorder, ";\n --cs: ").concat(drawOptions.colorSnake, ";\n --ce: ").concat(drawOptions.colorEmpty, ";\n ").concat(Object.entries(drawOptions.colorDots)
return "\n :root {\n --cb: ".concat(drawOptions.colorDotBorder, ";\n --cs: ").concat(drawOptions.colorSnake, ";\n --ce: ").concat(drawOptions.colorEmpty, ";\n ").concat(Object.entries(drawOptions.colorDots)
.map(function (_a) {
var i = _a[0], color = _a[1];
return "--c".concat(i, ":").concat(color, ";");
})
.join(""), "\n }\n ") +
(drawOptions.dark
? "\n @media (prefers-color-scheme: dark) {\n :root {\n --cb: ".concat(drawOptions.dark.colorBorder || drawOptions.colorBorder, ";\n --cs: ").concat(drawOptions.dark.colorSnake || drawOptions.colorSnake, ";\n --ce: ").concat(drawOptions.dark.colorEmpty, ";\n ").concat(Object.entries(drawOptions.dark.colorDots)
? "\n @media (prefers-color-scheme: dark) {\n :root {\n --cb: ".concat(drawOptions.dark.colorDotBorder || drawOptions.colorDotBorder, ";\n --cs: ").concat(drawOptions.dark.colorSnake || drawOptions.colorSnake, ";\n --ce: ").concat(drawOptions.dark.colorEmpty, ";\n ").concat(Object.entries(drawOptions.dark.colorDots)
.map(function (_a) {
var i = _a[0], color = _a[1];
return "--c".concat(i, ":").concat(color, ";");