Compare commits

..

11 Commits

Author SHA1 Message Date
release bot
2e275adbb6 📦 2.0.0-rc.3 2022-04-12 21:06:46 +00:00
platane
66fef03781 👷 2022-04-12 22:04:20 +02:00
platane
5841a21a09 👷 remove benchmark test 2022-04-12 22:01:29 +02:00
platane
cce5c4514d ♻️ refacto: rename options 2022-04-12 22:01:29 +02:00
platane
fb82d42d53 🚀 Allow to pass option as Json 2022-04-12 21:34:25 +02:00
release bot
e3ad8b2caf 📦 2.0.0-rc.2 2022-04-11 22:00:35 +00:00
platane
c21e390ca9 🐛 fix svg-only action.yaml 2022-04-11 23:57:57 +02:00
platane
7077112ba4 📓 2022-04-11 23:48:03 +02:00
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
platane
579bcf1afe 📓 2022-04-09 01:25:34 +02:00
30 changed files with 674 additions and 303 deletions

View File

@@ -18,19 +18,6 @@ jobs:
- run: yarn lint - run: yarn lint
- run: yarn test --ci - run: yarn test --ci
test-benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
cache: yarn
node-version: 16
- run: yarn install --frozen-lockfile
- run: ( cd packages/gif-creator ; yarn benchmark )
test-action: test-action:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -45,14 +32,17 @@ jobs:
uses: ./ uses: ./
with: with:
github_user_name: platane github_user_name: platane
gif_out_path: dist/github-contribution-grid-snake.gif outputs: |
svg_out_path: dist/github-contribution-grid-snake.svg 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 - name: ensure the generated file exists
run: | run: |
ls dist ls dist
test -f ${{ steps.generate-snake.outputs.gif_out_path }} test -f dist/github-contribution-grid-snake.svg
test -f ${{ steps.generate-snake.outputs.svg_out_path }} 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 - uses: crazy-max/ghaction-github-pages@v2.5.0
with: with:
@@ -76,7 +66,7 @@ jobs:
GITHUB_USER_CONTRIBUTION_API_ENDPOINT: https://snk-one.vercel.app/api/github-user-contribution/ GITHUB_USER_CONTRIBUTION_API_ENDPOINT: https://snk-one.vercel.app/api/github-user-contribution/
- uses: crazy-max/ghaction-github-pages@v2.6.0 - uses: crazy-max/ghaction-github-pages@v2.6.0
if: success() && github.ref == 'refs/heads/master' if: success() && github.ref == 'refs/heads/main'
with: with:
target_branch: gh-pages target_branch: gh-pages
build_dir: packages/demo/dist build_dir: packages/demo/dist

View File

@@ -65,7 +65,7 @@ jobs:
git add package.json svg-only/dist action.yml git add package.json svg-only/dist action.yml
git commit -m "📦 $VERSION" git commit -m "📦 $VERSION"
git tag v$VERSION git tag v$VERSION
git push origin master --tags git push origin main --tags
if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
git tag v$( echo $VERSION | cut -d. -f 1-1 ) 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** **github action**
```yaml ```yaml
- uses: Platane/snk@v1.1.0 - uses: Platane/snk@v2.0.0-rc.1
with: with:
# github user name to read the contribution graph from (**required**) # github user name to read the contribution graph from (**required**)
# using action context var `github.repository_owner` or specified user # using action context var `github.repository_owner` or specified user
github_user_name: ${{ github.repository_owner }} github_user_name: ${{ github.repository_owner }}
# path of the generated gif file # list of files to generate.
# If left empty, the gif file will not be generated # one file per line. Each output can be customized with options as query string.
gif_out_path: dist/github-snake.gif outputs: |
dist/github-snake.svg
# path of the generated svg file dist/github-snake.svg?palette=github-dark
# If left empty, the svg file will not be generated dist/ocean.gif?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9
svg_out_path: dist/github-snake.svg
``` ```
[example with cron job](https://github.com/Platane/Platane/blob/master/.github/workflows/main.yml#L24-L29) [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.1.0` If you are only interested in generating a svg, you can use this other faster action: `uses: Platane/snk/svg-only@v2.0.0-rc.1`
**interactive demo** **interactive demo**

View File

@@ -4,23 +4,28 @@ author: "platane"
runs: runs:
using: docker using: docker
image: docker://platane/snk@sha256:74d02183a9a4adb8e00d9f50e6eb5035a5b6ef02644d848363ef3301235ebd1d image: docker://platane/snk@sha256:e40bb02de6ed0f164eca8586b3f6c32109b2bcb426cd57c6882764825b40fe0d
inputs: inputs:
github_user_name: github_user_name:
description: "github user name" description: "github user name"
required: true required: true
gif_out_path: outputs:
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."
required: false required: false
default: null 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: supported query string options:
gif_out_path:
description: "path of the generated gif" - palette: a preset of color, one of [github, github-dark, github-light]
svg_out_path: - color_snake: color of the snake
description: "path of the generated svg" - 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", "name": "snk",
"description": "Generates a snake game from a github user contributions grid", "description": "Generates a snake game from a github user contributions grid",
"version": "1.1.3", "version": "2.0.0-rc.3",
"private": true, "private": true,
"repository": "github:platane/snk", "repository": "github:platane/snk",
"devDependencies": { "devDependencies": {

View File

@@ -8,8 +8,6 @@ Contains the github action code.
Because the gif generation requires some native libs, we cannot use a node.js action. Because the gif generation requires some native libs, we cannot use a node.js action.
Use a docker action instead, the image is created from the [Dockerfile](./Dockerfile). Use a docker action instead, the image is created from the [Dockerfile](../../Dockerfile).
It's published to [dockerhub](https://hub.docker.com/r/platane/snk) which makes for faster build ( compare to building the image when the action runs ) It's published to [dockerhub](https://hub.docker.com/r/platane/snk) which makes for faster build ( compare to building the image when the action runs )
Notice that the [action.yml](../../action.yml) point to the latest version of the image. Which makes releasing sematic versioning of the action pointless. Which is probably fine for a wacky project like this one.

View File

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

View File

@@ -0,0 +1,129 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should parse /out.svg?{"color_snake":"yellow","color_dots":["#000","#111","#222","#333","#444"]} 1`] = `
Object {
"animationOptions": Object {
"frameDuration": 100,
"step": 1,
},
"drawOptions": Object {
"colorDotBorder": "#1b1f230a",
"colorDots": Array [
"#000",
"#111",
"#222",
"#333",
"#444",
],
"colorEmpty": "#000",
"colorSnake": "yellow",
"dark": undefined,
"sizeCell": 16,
"sizeDot": 12,
"sizeDotBorderRadius": 2,
},
"filename": "/out.svg",
"format": "svg",
}
`;
exports[`should parse /out.svg?color_snake=orange&color_dots=#000,#111,#222,#333,#444 1`] = `
Object {
"animationOptions": Object {
"frameDuration": 100,
"step": 1,
},
"drawOptions": Object {
"colorDotBorder": "#1b1f230a",
"colorDots": Array [
"#000",
"#111",
"#222",
"#333",
"#444",
],
"colorEmpty": "#000",
"colorSnake": "orange",
"dark": undefined,
"sizeCell": 16,
"sizeDot": 12,
"sizeDotBorderRadius": 2,
},
"filename": "/out.svg",
"format": "svg",
}
`;
exports[`should parse /out.svg?color_snake=orange&color_dots=#000,#111,#222,#333,#444&dark_color_dots=#a00,#a11,#a22,#a33,#a44 1`] = `
Object {
"animationOptions": Object {
"frameDuration": 100,
"step": 1,
},
"drawOptions": Object {
"colorDotBorder": "#1b1f230a",
"colorDots": Array [
"#000",
"#111",
"#222",
"#333",
"#444",
],
"colorEmpty": "#000",
"colorSnake": "orange",
"dark": Object {
"colorDots": Array [
"#a00",
"#a11",
"#a22",
"#a33",
"#a44",
],
"colorEmpty": "#a00",
},
"sizeCell": 16,
"sizeDot": 12,
"sizeDotBorderRadius": 2,
},
"filename": "/out.svg",
"format": "svg",
}
`;
exports[`should parse path/to/out.gif 1`] = `
Object {
"animationOptions": Object {
"frameDuration": 100,
"step": 1,
},
"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",
}
`;

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 fs from "fs";
import * as path from "path"; import * as path from "path";
import { generateContributionSnake } from "../generateContributionSnake"; import { generateContributionSnake } from "../generateContributionSnake";
import { parseOutputsOption } from "../outputsOptions";
jest.setTimeout(2 * 60 * 1000); jest.setTimeout(2 * 60 * 1000);
@@ -17,22 +18,26 @@ const silent = (handler: () => void | Promise<void>) => async () => {
it( it(
"should generate contribution snake", "should generate contribution snake",
silent(async () => { silent(async () => {
const outputSvg = path.join(__dirname, "__snapshots__/out.svg"); const entries = [
const outputGif = path.join(__dirname, "__snapshots__/out.gif"); path.join(__dirname, "__snapshots__/out.svg"),
console.log = () => undefined; path.join(__dirname, "__snapshots__/out-dark.svg") +
const buffer = await generateContributionSnake("platane", { "?palette=github-dark&color_snake=orange",
svg: true,
gif: true,
});
expect(buffer.svg).toBeDefined(); path.join(__dirname, "__snapshots__/out.gif") +
expect(buffer.gif).toBeDefined(); "?color_snake=orange&color_dots=#d4e0f0,#8dbdff,#64a1f4,#4b91f1,#3c7dd9",
];
console.log("💾 writing to", outputSvg); const outputs = parseOutputsOption(entries);
fs.writeFileSync(outputSvg, buffer.svg);
console.log("💾 writing to", outputGif); const results = await generateContributionSnake("platane", outputs);
fs.writeFileSync(outputGif, buffer.gif);
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,15 @@
import { parseEntry } from "../outputsOptions";
[
"path/to/out.gif",
"/out.svg?color_snake=orange&color_dots=#000,#111,#222,#333,#444",
`/out.svg?{"color_snake":"yellow","color_dots":["#000","#111","#222","#333","#444"]}`,
"/out.svg?color_snake=orange&color_dots=#000,#111,#222,#333,#444&dark_color_dots=#a00,#a11,#a22,#a33,#a44",
].forEach((entry) =>
it(`should parse ${entry}`, () => {
expect(parseEntry(entry)).toMatchSnapshot();
})
);

View File

@@ -3,10 +3,16 @@ import { userContributionToGrid } from "./userContributionToGrid";
import { getBestRoute } from "@snk/solver/getBestRoute"; import { getBestRoute } from "@snk/solver/getBestRoute";
import { snake4 } from "@snk/types/__fixtures__/snake"; import { snake4 } from "@snk/types/__fixtures__/snake";
import { getPathToPose } from "@snk/solver/getPathToPose"; import { getPathToPose } from "@snk/solver/getPathToPose";
import type { DrawOptions as DrawOptions } from "@snk/svg-creator";
import type { AnimationOptions } from "@snk/gif-creator";
export const generateContributionSnake = async ( export const generateContributionSnake = async (
userName: string, userName: string,
format: { svg?: boolean; gif?: boolean } outputs: ({
format: "svg" | "gif";
drawOptions: DrawOptions;
animationOptions: AnimationOptions;
} | null)[]
) => { ) => {
console.log("🎣 fetching github user contribution"); console.log("🎣 fetching github user contribution");
const { cells, colorScheme } = await getGithubUserContribution(userName); const { cells, colorScheme } = await getGithubUserContribution(userName);
@@ -14,40 +20,32 @@ export const generateContributionSnake = async (
const grid = userContributionToGrid(cells, colorScheme); const grid = userContributionToGrid(cells, colorScheme);
const snake = snake4; 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"); console.log("📡 computing best route");
const chain = getBestRoute(grid, snake)!; const chain = getBestRoute(grid, snake)!;
chain.push(...getPathToPose(chain.slice(-1)[0], snake)!); chain.push(...getPathToPose(chain.slice(-1)[0], snake)!);
const output: Record<string, Buffer | string> = {}; return Promise.all(
outputs.map(async (out, i) => {
if (format.gif) { if (!out) return;
console.log("📹 creating gif"); const { format, drawOptions, animationOptions } = out;
const { createGif } = await import("@snk/gif-creator"); switch (format) {
output.gif = await createGif(grid, chain, drawOptions, gifOptions); case "svg": {
} console.log(`🖌 creating svg (outputs[${i}])`);
const { createSvg } = await import("@snk/svg-creator");
if (format.svg) { return createSvg(grid, cells, chain, drawOptions, animationOptions);
console.log("🖌 creating svg"); }
const { createSvg } = await import("@snk/svg-creator"); case "gif": {
output.svg = createSvg(grid, chain, drawOptions, gifOptions); console.log(`📹 creating gif (outputs[${i}])`);
} const { createGif } = await import("@snk/gif-creator");
return await createGif(
return output; grid,
cells,
chain,
drawOptions,
animationOptions
);
}
}
})
);
}; };

View File

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

View File

@@ -0,0 +1,72 @@
import type { AnimationOptions } from "@snk/gif-creator";
import type { DrawOptions 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;
let sp = new URLSearchParams(query || "");
try {
const o = JSON.parse(query);
if (Array.isArray(o.color_dots)) o.color_dots = o.color_dots.join(",");
if (Array.isArray(o.dark_color_dots))
o.dark_color_dots = o.dark_color_dots.join(",");
sp = new URLSearchParams(o);
} catch (err) {
if (!(err instanceof SyntaxError)) throw err;
}
const drawOptions: DrawOptions = {
sizeDotBorderRadius: 2,
sizeCell: 16,
sizeDot: 12,
...palettes["default"],
};
const animationOptions: AnimationOptions = { 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,
animationOptions,
};
};

View File

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

View File

@@ -0,0 +1,29 @@
import { DrawOptions 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 { Color, Grid } from "@snk/types/grid";
import { drawLerpWorld, drawWorld } from "@snk/draw/drawWorld"; import { drawLerpWorld, drawWorld } from "@snk/draw/drawWorld";
import { Snake } from "@snk/types/snake"; import { Snake } from "@snk/types/snake";
import type { DrawOptions as DrawOptions } from "@snk/svg-creator";
export const drawOptions = { export const drawOptions: DrawOptions = {
sizeBorderRadius: 2, sizeDotBorderRadius: 2,
sizeCell: 16, sizeCell: 16,
sizeDot: 12, sizeDot: 12,
colorBorder: "#1b1f230a", colorDotBorder: "#1b1f230a",
colorDots: { colorDots: {
1: "#9be9a8", 1: "#9be9a8",
2: "#40c463", 2: "#40c463",
@@ -67,7 +68,7 @@ export const createCanvas = ({
const draw = (grid: Grid, snake: Snake, stack: Color[]) => { const draw = (grid: Grid, snake: Snake, stack: Color[]) => {
ctx.clearRect(0, 0, 9999, 9999); ctx.clearRect(0, 0, 9999, 9999);
drawWorld(ctx, grid, snake, stack, drawOptions); drawWorld(ctx, grid, null, snake, stack, drawOptions);
}; };
const drawLerp = ( const drawLerp = (
@@ -78,7 +79,7 @@ export const createCanvas = ({
k: number k: number
) => { ) => {
ctx.clearRect(0, 0, 9999, 9999); ctx.clearRect(0, 0, 9999, 9999);
drawLerpWorld(ctx, grid, snake0, snake1, stack, k, drawOptions); drawLerpWorld(ctx, grid, null, snake0, snake1, stack, k, drawOptions);
}; };
const highlightCell = (x: number, y: number, color = "orange") => { const highlightCell = (x: number, y: number, color = "orange") => {

View File

@@ -3,15 +3,17 @@ import { step } from "@snk/solver/step";
import { isStableAndBound, stepSpring } from "./springUtils"; import { isStableAndBound, stepSpring } from "./springUtils";
import type { Res } from "@snk/github-user-contribution"; import type { Res } from "@snk/github-user-contribution";
import type { Snake } from "@snk/types/snake"; import type { Snake } from "@snk/types/snake";
import type { Point } from "@snk/types/point";
import { import {
drawLerpWorld, drawLerpWorld,
getCanvasWorldSize, getCanvasWorldSize,
Options, Options as DrawOptions,
} from "@snk/draw/drawWorld"; } from "@snk/draw/drawWorld";
import { userContributionToGrid } from "@snk/action/userContributionToGrid"; import { userContributionToGrid } from "@snk/action/userContributionToGrid";
import { createSvg } from "@snk/svg-creator"; import { createSvg } from "@snk/svg-creator";
import { createRpcClient } from "./worker-utils"; import { createRpcClient } from "./worker-utils";
import type { API as WorkerAPI } from "./demo.interactive.worker"; import type { API as WorkerAPI } from "./demo.interactive.worker";
import { AnimationOptions } from "@snk/gif-creator";
const createForm = ({ const createForm = ({
onSubmit, onSubmit,
@@ -116,11 +118,13 @@ const createGithubProfile = () => {
const createViewer = ({ const createViewer = ({
grid0, grid0,
chain, chain,
cells,
drawOptions, drawOptions,
}: { }: {
grid0: Grid; grid0: Grid;
chain: Snake[]; chain: Snake[];
drawOptions: Options; cells: Point[];
drawOptions: DrawOptions;
}) => { }) => {
// //
// canvas // canvas
@@ -159,7 +163,7 @@ const createViewer = ({
const k = spring.x % 1; const k = spring.x % 1;
ctx.clearRect(0, 0, 9999, 9999); ctx.clearRect(0, 0, 9999, 9999);
drawLerpWorld(ctx, grid, snake0, snake1, stack, k, drawOptions); drawLerpWorld(ctx, grid, null, snake0, snake1, stack, k, drawOptions);
if (!stable) animationFrame = requestAnimationFrame(loop); if (!stable) animationFrame = requestAnimationFrame(loop);
}; };
@@ -189,9 +193,9 @@ const createViewer = ({
// //
// svg // svg
const svgLink = document.createElement("a"); const svgLink = document.createElement("a");
const svgString = createSvg(grid0, chain, drawOptions, { const svgString = createSvg(grid0, cells, chain, drawOptions, {
frameDuration: 100, frameDuration: 100,
}); } as AnimationOptions);
const svgImageUri = `data:image/*;charset=utf-8;base64,${btoa(svgString)}`; const svgImageUri = `data:image/*;charset=utf-8;base64,${btoa(svgString)}`;
svgLink.href = svgImageUri; svgLink.href = svgImageUri;
svgLink.innerText = "github-user-contribution.svg"; svgLink.innerText = "github-user-contribution.svg";
@@ -229,15 +233,14 @@ const onSubmit = async (userName: string) => {
); );
const { cells, colorScheme } = (await res.json()) as Res; const { cells, colorScheme } = (await res.json()) as Res;
const drawOptions = { const drawOptions: DrawOptions = {
sizeBorderRadius: 2, sizeDotBorderRadius: 2,
sizeCell: 16, sizeCell: 16,
sizeDot: 12, sizeDot: 12,
colorBorder: "#1b1f230a", colorDotBorder: "#1b1f230a",
colorDots: colorScheme as any, colorDots: colorScheme as any,
colorEmpty: colorScheme[0], colorEmpty: colorScheme[0],
colorSnake: "purple", colorSnake: "purple",
cells,
}; };
const grid = userContributionToGrid(cells, colorScheme); const grid = userContributionToGrid(cells, colorScheme);
@@ -246,7 +249,7 @@ const onSubmit = async (userName: string) => {
dispose(); dispose();
createViewer({ grid0: grid, chain, drawOptions }); createViewer({ grid0: grid, chain, cells, drawOptions });
}; };
const worker = new Worker( const worker = new Worker(

View File

@@ -4,12 +4,15 @@ import { createSvg } from "@snk/svg-creator";
import { grid, snake } from "./sample"; import { grid, snake } from "./sample";
import { drawOptions } from "./canvas"; import { drawOptions } from "./canvas";
import { getPathToPose } from "@snk/solver/getPathToPose"; import { getPathToPose } from "@snk/solver/getPathToPose";
import type { AnimationOptions } from "@snk/gif-creator";
const chain = getBestRoute(grid, snake); const chain = getBestRoute(grid, snake);
chain.push(...getPathToPose(chain.slice(-1)[0], snake)!); chain.push(...getPathToPose(chain.slice(-1)[0], snake)!);
(async () => { (async () => {
const svg = await createSvg(grid, chain, drawOptions, { frameDuration: 200 }); const svg = await createSvg(grid, null, chain, drawOptions, {
frameDuration: 200,
} as AnimationOptions);
const container = document.createElement("div"); const container = document.createElement("div");
container.innerHTML = svg; container.innerHTML = svg;

View File

@@ -6,21 +6,21 @@ import type { Point } from "@snk/types/point";
type Options = { type Options = {
colorDots: Record<Color, string>; colorDots: Record<Color, string>;
colorEmpty: string; colorEmpty: string;
colorBorder: string; colorDotBorder: string;
sizeCell: number; sizeCell: number;
sizeDot: number; sizeDot: number;
sizeBorderRadius: number; sizeDotBorderRadius: number;
cells?: Point[];
}; };
export const drawGrid = ( export const drawGrid = (
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
grid: Grid, grid: Grid,
cells: Point[] | null,
o: Options o: Options
) => { ) => {
for (let x = grid.width; x--; ) for (let x = grid.width; x--; )
for (let y = grid.height; y--; ) { for (let y = grid.height; y--; ) {
if (!o.cells || o.cells.some((c) => c.x === x && c.y === y)) { if (!cells || cells.some((c) => c.x === x && c.y === y)) {
const c = getColor(grid, x, y); const c = getColor(grid, x, y);
// @ts-ignore // @ts-ignore
const color = !c ? o.colorEmpty : o.colorDots[c]; const color = !c ? o.colorEmpty : o.colorDots[c];
@@ -31,11 +31,11 @@ export const drawGrid = (
); );
ctx.fillStyle = color; ctx.fillStyle = color;
ctx.strokeStyle = o.colorBorder; ctx.strokeStyle = o.colorDotBorder;
ctx.lineWidth = 1; ctx.lineWidth = 1;
ctx.beginPath(); ctx.beginPath();
pathRoundedRect(ctx, o.sizeDot, o.sizeDot, o.sizeBorderRadius); pathRoundedRect(ctx, o.sizeDot, o.sizeDot, o.sizeDotBorderRadius);
ctx.fill(); ctx.fill();
ctx.stroke(); ctx.stroke();

View File

@@ -1,18 +1,17 @@
import { drawGrid } from "./drawGrid"; import { drawGrid } from "./drawGrid";
import { drawSnake, drawSnakeLerp } from "./drawSnake"; import { drawSnake, drawSnakeLerp } from "./drawSnake";
import type { Grid, Color } from "@snk/types/grid"; import type { Grid, Color } from "@snk/types/grid";
import type { Point } from "@snk/types/point";
import type { Snake } from "@snk/types/snake"; import type { Snake } from "@snk/types/snake";
import type { Point } from "@snk/types/point";
export type Options = { export type Options = {
colorDots: Record<Color, string>; colorDots: Record<Color, string>;
colorEmpty: string; colorEmpty: string;
colorBorder: string; colorDotBorder: string;
colorSnake: string; colorSnake: string;
sizeCell: number; sizeCell: number;
sizeDot: number; sizeDot: number;
sizeBorderRadius: number; sizeDotBorderRadius: number;
cells?: Point[];
}; };
export const drawStack = ( export const drawStack = (
@@ -37,6 +36,7 @@ export const drawStack = (
export const drawWorld = ( export const drawWorld = (
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
grid: Grid, grid: Grid,
cells: Point[] | null,
snake: Snake, snake: Snake,
stack: Color[], stack: Color[],
o: Options o: Options
@@ -44,7 +44,7 @@ export const drawWorld = (
ctx.save(); ctx.save();
ctx.translate(1 * o.sizeCell, 2 * o.sizeCell); ctx.translate(1 * o.sizeCell, 2 * o.sizeCell);
drawGrid(ctx, grid, o); drawGrid(ctx, grid, cells, o);
drawSnake(ctx, snake, o); drawSnake(ctx, snake, o);
ctx.restore(); ctx.restore();
@@ -68,6 +68,7 @@ export const drawWorld = (
export const drawLerpWorld = ( export const drawLerpWorld = (
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
grid: Grid, grid: Grid,
cells: Point[] | null,
snake0: Snake, snake0: Snake,
snake1: Snake, snake1: Snake,
stack: Color[], stack: Color[],
@@ -77,7 +78,7 @@ export const drawLerpWorld = (
ctx.save(); ctx.save();
ctx.translate(1 * o.sizeCell, 2 * o.sizeCell); ctx.translate(1 * o.sizeCell, 2 * o.sizeCell);
drawGrid(ctx, grid, o); drawGrid(ctx, grid, cells, o);
drawSnakeLerp(ctx, snake0, snake1, k, o); drawSnakeLerp(ctx, snake0, snake1, k, o);
ctx.translate(0, (grid.height + 2) * o.sizeCell); ctx.translate(0, (grid.height + 2) * o.sizeCell);

View File

@@ -2,9 +2,10 @@ import * as fs from "fs";
import { performance } from "perf_hooks"; import { performance } from "perf_hooks";
import { createSnakeFromCells } from "@snk/types/snake"; import { createSnakeFromCells } from "@snk/types/snake";
import { realistic as grid } from "@snk/types/__fixtures__/grid"; import { realistic as grid } from "@snk/types/__fixtures__/grid";
import { createGif } from ".."; import { AnimationOptions, createGif } from "..";
import { getBestRoute } from "@snk/solver/getBestRoute"; import { getBestRoute } from "@snk/solver/getBestRoute";
import { getPathToPose } from "@snk/solver/getPathToPose"; import { getPathToPose } from "@snk/solver/getPathToPose";
import type { Options as DrawOptions } from "@snk/draw/drawWorld";
let snake = createSnakeFromCells( let snake = createSnakeFromCells(
Array.from({ length: 4 }, (_, i) => ({ x: i, y: -1 })) Array.from({ length: 4 }, (_, i) => ({ x: i, y: -1 }))
@@ -24,17 +25,17 @@ let snake = createSnakeFromCells(
const chain = getBestRoute(grid, snake)!; const chain = getBestRoute(grid, snake)!;
chain.push(...getPathToPose(chain.slice(-1)[0], snake)!); chain.push(...getPathToPose(chain.slice(-1)[0], snake)!);
const drawOptions = { const drawOptions: DrawOptions = {
sizeBorderRadius: 2, sizeDotBorderRadius: 2,
sizeCell: 16, sizeCell: 16,
sizeDot: 12, sizeDot: 12,
colorBorder: "#1b1f230a", colorDotBorder: "#1b1f230a",
colorDots: { 1: "#9be9a8", 2: "#40c463", 3: "#30a14e", 4: "#216e39" }, colorDots: { 1: "#9be9a8", 2: "#40c463", 3: "#30a14e", 4: "#216e39" },
colorEmpty: "#ebedf0", colorEmpty: "#ebedf0",
colorSnake: "purple", colorSnake: "purple",
}; };
const gifOptions = { frameDuration: 100, step: 1 }; const animationOptions: AnimationOptions = { frameDuration: 100, step: 1 };
(async () => { (async () => {
for ( for (
@@ -49,7 +50,13 @@ const gifOptions = { frameDuration: 100, step: 1 };
const chainL = chain.slice(0, length); const chainL = chain.slice(0, length);
for (let k = 0; k < 10 && (Date.now() - start < 10 * 1000 || k < 2); k++) { for (let k = 0; k < 10 && (Date.now() - start < 10 * 1000 || k < 2); k++) {
const s = performance.now(); const s = performance.now();
buffer = await createGif(grid, chainL, drawOptions, gifOptions); buffer = await createGif(
grid,
null,
chainL,
drawOptions,
animationOptions
);
stats.push(performance.now() - s); stats.push(performance.now() - s);
} }

View File

@@ -1,25 +1,26 @@
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import { createGif } from ".."; import { AnimationOptions, createGif } from "..";
import * as grids from "@snk/types/__fixtures__/grid"; import * as grids from "@snk/types/__fixtures__/grid";
import { snake3 as snake } from "@snk/types/__fixtures__/snake"; import { snake3 as snake } from "@snk/types/__fixtures__/snake";
import { createSnakeFromCells, nextSnake } from "@snk/types/snake"; import { createSnakeFromCells, nextSnake } from "@snk/types/snake";
import { getBestRoute } from "@snk/solver/getBestRoute"; import { getBestRoute } from "@snk/solver/getBestRoute";
import type { Options as DrawOptions } from "@snk/draw/drawWorld";
jest.setTimeout(20 * 1000); jest.setTimeout(20 * 1000);
const upscale = 1; const upscale = 1;
const drawOptions = { const drawOptions: DrawOptions = {
sizeBorderRadius: 2 * upscale, sizeDotBorderRadius: 2 * upscale,
sizeCell: 16 * upscale, sizeCell: 16 * upscale,
sizeDot: 12 * upscale, sizeDot: 12 * upscale,
colorBorder: "#1b1f230a", colorDotBorder: "#1b1f230a",
colorDots: { 1: "#9be9a8", 2: "#40c463", 3: "#30a14e", 4: "#216e39" }, colorDots: { 1: "#9be9a8", 2: "#40c463", 3: "#30a14e", 4: "#216e39" },
colorEmpty: "#ebedf0", colorEmpty: "#ebedf0",
colorSnake: "purple", colorSnake: "purple",
}; };
const gifOptions = { frameDuration: 200, step: 1 }; const animationOptions: AnimationOptions = { frameDuration: 200, step: 1 };
const dir = path.resolve(__dirname, "__snapshots__"); const dir = path.resolve(__dirname, "__snapshots__");
@@ -39,7 +40,13 @@ for (const key of [
const chain = [snake, ...getBestRoute(grid, snake)!]; const chain = [snake, ...getBestRoute(grid, snake)!];
const gif = await createGif(grid, chain, drawOptions, gifOptions); const gif = await createGif(
grid,
null,
chain,
drawOptions,
animationOptions
);
expect(gif).toBeDefined(); expect(gif).toBeDefined();
@@ -63,7 +70,7 @@ it(`should generate swipper`, async () => {
} }
} }
const gif = await createGif(grid, chain, drawOptions, gifOptions); const gif = await createGif(grid, null, chain, drawOptions, animationOptions);
expect(gif).toBeDefined(); expect(gif).toBeDefined();

View File

@@ -5,10 +5,11 @@ import { createCanvas } from "canvas";
import { Grid, copyGrid, Color } from "@snk/types/grid"; import { Grid, copyGrid, Color } from "@snk/types/grid";
import { Snake } from "@snk/types/snake"; import { Snake } from "@snk/types/snake";
import { import {
Options, Options as DrawOptions,
drawLerpWorld, drawLerpWorld,
getCanvasWorldSize, getCanvasWorldSize,
} from "@snk/draw/drawWorld"; } from "@snk/draw/drawWorld";
import type { Point } from "@snk/types/point";
import { step } from "@snk/solver/step"; import { step } from "@snk/solver/step";
import tmp from "tmp"; import tmp from "tmp";
import gifsicle from "gifsicle"; import gifsicle from "gifsicle";
@@ -29,11 +30,14 @@ const withTmpDir = async <T>(
} }
}; };
export type AnimationOptions = { frameDuration: number; step: number };
export const createGif = async ( export const createGif = async (
grid0: Grid, grid0: Grid,
cells: Point[] | null,
chain: Snake[], chain: Snake[],
drawOptions: Options, drawOptions: DrawOptions,
gifOptions: { frameDuration: number; step: number } animationOptions: AnimationOptions
) => ) =>
withTmpDir(async (dir) => { withTmpDir(async (dir) => {
const { width, height } = getCanvasWorldSize(grid0, drawOptions); const { width, height } = getCanvasWorldSize(grid0, drawOptions);
@@ -46,7 +50,7 @@ export const createGif = async (
const encoder = new GIFEncoder(width, height, "neuquant", true); const encoder = new GIFEncoder(width, height, "neuquant", true);
encoder.setRepeat(0); encoder.setRepeat(0);
encoder.setDelay(gifOptions.frameDuration); encoder.setDelay(animationOptions.frameDuration);
encoder.start(); encoder.start();
for (let i = 0; i < chain.length; i += 1) { for (let i = 0; i < chain.length; i += 1) {
@@ -54,17 +58,18 @@ export const createGif = async (
const snake1 = chain[Math.min(chain.length - 1, i + 1)]; const snake1 = chain[Math.min(chain.length - 1, i + 1)];
step(grid, stack, snake0); step(grid, stack, snake0);
for (let k = 0; k < gifOptions.step; k++) { for (let k = 0; k < animationOptions.step; k++) {
ctx.clearRect(0, 0, width, height); ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "#fff"; ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, width, height); ctx.fillRect(0, 0, width, height);
drawLerpWorld( drawLerpWorld(
ctx, ctx,
grid, grid,
cells,
snake0, snake0,
snake1, snake1,
stack, stack,
k / gifOptions.step, k / animationOptions.step,
drawOptions drawOptions
); );

View File

@@ -1,15 +1,16 @@
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import { createSvg } from ".."; import { createSvg, DrawOptions as DrawOptions } from "..";
import * as grids from "@snk/types/__fixtures__/grid"; import * as grids from "@snk/types/__fixtures__/grid";
import { snake3 as snake } from "@snk/types/__fixtures__/snake"; import { snake3 as snake } from "@snk/types/__fixtures__/snake";
import { getBestRoute } from "@snk/solver/getBestRoute"; import { getBestRoute } from "@snk/solver/getBestRoute";
import { AnimationOptions } from "@snk/gif-creator";
const drawOptions = { const drawOptions: DrawOptions = {
sizeBorderRadius: 2, sizeDotBorderRadius: 2,
sizeCell: 16, sizeCell: 16,
sizeDot: 12, sizeDot: 12,
colorBorder: "#1b1f230a", colorDotBorder: "#1b1f230a",
colorDots: { 1: "#9be9a8", 2: "#40c463", 3: "#30a14e", 4: "#216e39" }, colorDots: { 1: "#9be9a8", 2: "#40c463", 3: "#30a14e", 4: "#216e39" },
colorEmpty: "#ebedf0", colorEmpty: "#ebedf0",
colorSnake: "purple", colorSnake: "purple",
@@ -19,7 +20,7 @@ const drawOptions = {
}, },
}; };
const gifOptions = { frameDuration: 100, step: 1 }; const animationOptions: AnimationOptions = { frameDuration: 100, step: 1 };
const dir = path.resolve(__dirname, "__snapshots__"); const dir = path.resolve(__dirname, "__snapshots__");
@@ -31,7 +32,13 @@ for (const [key, grid] of Object.entries(grids))
it(`should generate ${key} svg`, async () => { it(`should generate ${key} svg`, async () => {
const chain = [snake, ...getBestRoute(grid, snake)!]; const chain = [snake, ...getBestRoute(grid, snake)!];
const svg = await createSvg(grid, chain, drawOptions, gifOptions); const svg = await createSvg(
grid,
null,
chain,
drawOptions,
animationOptions
);
expect(svg).toBeDefined(); expect(svg).toBeDefined();

View File

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

View File

@@ -14,20 +14,20 @@ import { createGrid } from "./grid";
import { createStack } from "./stack"; import { createStack } from "./stack";
import { h } from "./utils"; import { h } from "./utils";
import * as csso from "csso"; import * as csso from "csso";
import { AnimationOptions } from "@snk/gif-creator";
export type Options = { export type DrawOptions = {
colorDots: Record<Color, string>; colorDots: Record<Color, string>;
colorEmpty: string; colorEmpty: string;
colorBorder: string; colorDotBorder: string;
colorSnake: string; colorSnake: string;
sizeCell: number; sizeCell: number;
sizeDot: number; sizeDot: number;
sizeBorderRadius: number; sizeDotBorderRadius: number;
cells?: Point[];
dark?: { dark?: {
colorDots: Record<Color, string>; colorDots: Record<Color, string>;
colorEmpty: string; colorEmpty: string;
colorBorder?: string; colorDotBorder?: string;
colorSnake?: string; colorSnake?: string;
}; };
}; };
@@ -40,12 +40,12 @@ const getCellsFromGrid = ({ width, height }: Grid) =>
const createLivingCells = ( const createLivingCells = (
grid0: Grid, grid0: Grid,
chain: Snake[], chain: Snake[],
drawOptions: Options cells: Point[] | null
) => { ) => {
const cells: (Point & { const livingCells: (Point & {
t: number | null; t: number | null;
color: Color | Empty; color: Color | Empty;
})[] = (drawOptions.cells ?? getCellsFromGrid(grid0)).map(({ x, y }) => ({ })[] = (cells ?? getCellsFromGrid(grid0)).map(({ x, y }) => ({
x, x,
y, y,
t: null, t: null,
@@ -60,31 +60,32 @@ const createLivingCells = (
if (isInside(grid, x, y) && !isEmpty(getColor(grid, x, y))) { if (isInside(grid, x, y) && !isEmpty(getColor(grid, x, y))) {
setColorEmpty(grid, x, y); setColorEmpty(grid, x, y);
const cell = cells.find((c) => c.x === x && c.y === y)!; const cell = livingCells.find((c) => c.x === x && c.y === y)!;
cell.t = i / chain.length; cell.t = i / chain.length;
} }
} }
return cells; return livingCells;
}; };
export const createSvg = ( export const createSvg = (
grid: Grid, grid: Grid,
cells: Point[] | null,
chain: Snake[], chain: Snake[],
drawOptions: Options, drawOptions: DrawOptions,
gifOptions: { frameDuration: number } animationOptions: Pick<AnimationOptions, "frameDuration">
) => { ) => {
const width = (grid.width + 2) * drawOptions.sizeCell; const width = (grid.width + 2) * drawOptions.sizeCell;
const height = (grid.height + 5) * drawOptions.sizeCell; const height = (grid.height + 5) * drawOptions.sizeCell;
const duration = gifOptions.frameDuration * chain.length; const duration = animationOptions.frameDuration * chain.length;
const cells = createLivingCells(grid, chain, drawOptions); const livingCells = createLivingCells(grid, chain, cells);
const elements = [ const elements = [
createGrid(cells, drawOptions, duration), createGrid(livingCells, drawOptions, duration),
createStack( createStack(
cells, livingCells,
drawOptions, drawOptions,
grid.width * drawOptions.sizeCell, grid.width * drawOptions.sizeCell,
(grid.height + 2) * drawOptions.sizeCell, (grid.height + 2) * drawOptions.sizeCell,
@@ -134,10 +135,10 @@ export const createSvg = (
const optimizeCss = (css: string) => csso.minify(css).css; const optimizeCss = (css: string) => csso.minify(css).css;
const optimizeSvg = (svg: string) => svg; const optimizeSvg = (svg: string) => svg;
const generateColorVar = (drawOptions: Options) => const generateColorVar = (drawOptions: DrawOptions) =>
` `
:root { :root {
--cb: ${drawOptions.colorBorder}; --cb: ${drawOptions.colorDotBorder};
--cs: ${drawOptions.colorSnake}; --cs: ${drawOptions.colorSnake};
--ce: ${drawOptions.colorEmpty}; --ce: ${drawOptions.colorEmpty};
${Object.entries(drawOptions.colorDots) ${Object.entries(drawOptions.colorDots)
@@ -149,7 +150,7 @@ const generateColorVar = (drawOptions: Options) =>
? ` ? `
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--cb: ${drawOptions.dark.colorBorder || drawOptions.colorBorder}; --cb: ${drawOptions.dark.colorDotBorder || drawOptions.colorDotBorder};
--cs: ${drawOptions.dark.colorSnake || drawOptions.colorSnake}; --cs: ${drawOptions.dark.colorSnake || drawOptions.colorSnake};
--ce: ${drawOptions.dark.colorEmpty}; --ce: ${drawOptions.dark.colorEmpty};
${Object.entries(drawOptions.dark.colorDots) ${Object.entries(drawOptions.dark.colorDots)

View File

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

View File

@@ -10,11 +10,22 @@ inputs:
github_user_name: github_user_name:
description: "github user name" description: "github user name"
required: true required: true
svg_out_path: outputs:
description: "path of the generated svg file. If left empty, the svg file will not be generated."
required: false required: false
default: null 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.svg?palette=<github or github-dark>&color_snake=<color>&color_dots=<color>,<color>,<color>,<color>,<color>
outputs: supported query string options:
svg_out_path:
description: "path of the generated svg" - 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.svg?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9

280
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 getBestRoute_1 = __webpack_require__(2705);
var snake_1 = __webpack_require__(7087); var snake_1 = __webpack_require__(7087);
var getPathToPose_1 = __webpack_require__(6963); var getPathToPose_1 = __webpack_require__(6963);
var generateContributionSnake = function (userName, format) { return __awaiter(void 0, void 0, void 0, function () { var generateContributionSnake = function (userName, outputs) { return __awaiter(void 0, void 0, void 0, function () {
var _a, cells, colorScheme, grid, snake, drawOptions, gifOptions, chain, output, createGif, _b, createSvg; var _a, cells, colorScheme, grid, snake, chain;
return __generator(this, function (_c) { return __generator(this, function (_b) {
switch (_c.label) { switch (_b.label) {
case 0: case 0:
console.log("🎣 fetching github user contribution"); console.log("🎣 fetching github user contribution");
return [4 /*yield*/, (0, github_user_contribution_1.getGithubUserContribution)(userName)]; return [4 /*yield*/, (0, github_user_contribution_1.getGithubUserContribution)(userName)];
case 1: 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); grid = (0, userContributionToGrid_1.userContributionToGrid)(cells, colorScheme);
snake = snake_1.snake4; 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"); console.log("📡 computing best route");
chain = (0, getBestRoute_1.getBestRoute)(grid, snake); chain = (0, getBestRoute_1.getBestRoute)(grid, snake);
chain.push.apply(chain, (0, getPathToPose_1.getPathToPose)(chain.slice(-1)[0], snake)); chain.push.apply(chain, (0, getPathToPose_1.getPathToPose)(chain.slice(-1)[0], snake));
output = {}; return [2 /*return*/, Promise.all(outputs.map(function (out, i) { return __awaiter(void 0, void 0, void 0, function () {
if (!format.gif) return [3 /*break*/, 4]; var format, drawOptions, animationOptions, _a, createSvg, createGif;
console.log("📹 creating gif"); return __generator(this, function (_b) {
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(__webpack_require__(340)); })]; switch (_b.label) {
case 2: case 0:
createGif = (_c.sent()).createGif; if (!out)
_b = output; return [2 /*return*/];
return [4 /*yield*/, createGif(grid, chain, drawOptions, gifOptions)]; format = out.format, drawOptions = out.drawOptions, animationOptions = out.animationOptions;
case 3: _a = format;
_b.gif = _c.sent(); switch (_a) {
_c.label = 4; case "svg": return [3 /*break*/, 1];
case 4: case "gif": return [3 /*break*/, 3];
if (!format.svg) return [3 /*break*/, 6]; }
console.log("🖌 creating svg"); return [3 /*break*/, 6];
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(__webpack_require__(7415)); })]; case 1:
case 5: console.log("\uD83D\uDD8C creating svg (outputs[".concat(i, "])"));
createSvg = (_c.sent()).createSvg; return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(__webpack_require__(7415)); })];
output.svg = createSvg(grid, chain, drawOptions, gifOptions); case 2:
_c.label = 6; createSvg = (_b.sent()).createSvg;
case 6: return [2 /*return*/, output]; return [2 /*return*/, createSvg(grid, cells, chain, drawOptions, animationOptions)];
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, cells, chain, drawOptions, animationOptions)];
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 path = __importStar(__webpack_require__(5622));
var core = __importStar(__webpack_require__(7117)); var core = __importStar(__webpack_require__(7117));
var generateContributionSnake_1 = __webpack_require__(8847); var generateContributionSnake_1 = __webpack_require__(8847);
var outputsOptions_1 = __webpack_require__(3379);
(function () { return __awaiter(void 0, void 0, void 0, function () { (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) { return __generator(this, function (_b) {
switch (_b.label) { switch (_b.label) {
case 0: case 0:
_b.trys.push([0, 2, , 3]); _b.trys.push([0, 2, , 3]);
userName = core.getInput("github_user_name"); userName = core.getInput("github_user_name");
format = { outputs = (0, outputsOptions_1.parseOutputsOption)((_a = core.getMultilineInput("outputs")) !== null && _a !== void 0 ? _a : [
svg: core.getInput("svg_out_path"), core.getInput("gif_out_path"),
gif: core.getInput("gif_out_path") core.getInput("svg_out_path"),
}; ]);
return [4 /*yield*/, (0, generateContributionSnake_1.generateContributionSnake)(userName, format)]; return [4 /*yield*/, (0, generateContributionSnake_1.generateContributionSnake)(userName, outputs)];
case 1: case 1:
_a = _b.sent(), svg = _a.svg, gif = _a.gif; results_1 = _b.sent();
if (svg) { outputs.forEach(function (out, i) {
fs.mkdirSync(path.dirname(format.svg), { recursive: true }); var result = results_1[i];
fs.writeFileSync(format.svg, svg); if ((out === null || out === void 0 ? void 0 : out.filename) && result) {
core.setOutput("svg_out_path", format.svg); 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 });
if (gif) { fs.writeFileSync(out === null || out === void 0 ? void 0 : out.filename, result);
fs.mkdirSync(path.dirname(format.gif), { recursive: true }); }
fs.writeFileSync(format.gif, gif); });
core.setOutput("gif_out_path", format.gif);
}
return [3 /*break*/, 3]; return [3 /*break*/, 3];
case 2: case 2:
e_1 = _b.sent(); e_1 = _b.sent();
@@ -32942,6 +32938,123 @@ 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 filename = m[1], format = m[2], query = m[4];
var sp = new URLSearchParams(query || "");
try {
var o = JSON.parse(query);
if (Array.isArray(o.color_dots))
o.color_dots = o.color_dots.join(",");
if (Array.isArray(o.dark_color_dots))
o.dark_color_dots = o.dark_color_dots.join(",");
sp = new URLSearchParams(o);
}
catch (err) {
if (!(err instanceof SyntaxError))
throw err;
}
var drawOptions = __assign({ sizeDotBorderRadius: 2, sizeCell: 16, sizeDot: 12 }, palettes_1.palettes["default"]);
var animationOptions = { 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,
animationOptions: animationOptions
};
};
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: /***/ 5740:
@@ -32989,20 +33102,20 @@ exports.__esModule = true;
exports.drawGrid = void 0; exports.drawGrid = void 0;
var grid_1 = __webpack_require__(2881); var grid_1 = __webpack_require__(2881);
var pathRoundedRect_1 = __webpack_require__(2356); var pathRoundedRect_1 = __webpack_require__(2356);
var drawGrid = function (ctx, grid, o) { var drawGrid = function (ctx, grid, cells, o) {
var _loop_1 = function (x) { var _loop_1 = function (x) {
var _loop_2 = function (y) { var _loop_2 = function (y) {
if (!o.cells || o.cells.some(function (c) { return c.x === x && c.y === y; })) { if (!cells || cells.some(function (c) { return c.x === x && c.y === y; })) {
var c = (0, grid_1.getColor)(grid, x, y); var c = (0, grid_1.getColor)(grid, x, y);
// @ts-ignore // @ts-ignore
var color = !c ? o.colorEmpty : o.colorDots[c]; var color = !c ? o.colorEmpty : o.colorDots[c];
ctx.save(); ctx.save();
ctx.translate(x * o.sizeCell + (o.sizeCell - o.sizeDot) / 2, y * o.sizeCell + (o.sizeCell - o.sizeDot) / 2); ctx.translate(x * o.sizeCell + (o.sizeCell - o.sizeDot) / 2, y * o.sizeCell + (o.sizeCell - o.sizeDot) / 2);
ctx.fillStyle = color; ctx.fillStyle = color;
ctx.strokeStyle = o.colorBorder; ctx.strokeStyle = o.colorDotBorder;
ctx.lineWidth = 1; ctx.lineWidth = 1;
ctx.beginPath(); 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.fill();
ctx.stroke(); ctx.stroke();
ctx.closePath(); ctx.closePath();
@@ -33090,10 +33203,10 @@ var drawStack = function (ctx, stack, max, width, o) {
ctx.restore(); ctx.restore();
}; };
exports.drawStack = drawStack; exports.drawStack = drawStack;
var drawWorld = function (ctx, grid, snake, stack, o) { var drawWorld = function (ctx, grid, cells, snake, stack, o) {
ctx.save(); ctx.save();
ctx.translate(1 * o.sizeCell, 2 * o.sizeCell); ctx.translate(1 * o.sizeCell, 2 * o.sizeCell);
(0, drawGrid_1.drawGrid)(ctx, grid, o); (0, drawGrid_1.drawGrid)(ctx, grid, cells, o);
(0, drawSnake_1.drawSnake)(ctx, snake, o); (0, drawSnake_1.drawSnake)(ctx, snake, o);
ctx.restore(); ctx.restore();
ctx.save(); ctx.save();
@@ -33108,10 +33221,10 @@ var drawWorld = function (ctx, grid, snake, stack, o) {
// ctx.restore(); // ctx.restore();
}; };
exports.drawWorld = drawWorld; exports.drawWorld = drawWorld;
var drawLerpWorld = function (ctx, grid, snake0, snake1, stack, k, o) { var drawLerpWorld = function (ctx, grid, cells, snake0, snake1, stack, k, o) {
ctx.save(); ctx.save();
ctx.translate(1 * o.sizeCell, 2 * o.sizeCell); ctx.translate(1 * o.sizeCell, 2 * o.sizeCell);
(0, drawGrid_1.drawGrid)(ctx, grid, o); (0, drawGrid_1.drawGrid)(ctx, grid, cells, o);
(0, drawSnake_1.drawSnakeLerp)(ctx, snake0, snake1, k, o); (0, drawSnake_1.drawSnakeLerp)(ctx, snake0, snake1, k, o);
ctx.translate(0, (grid.height + 2) * o.sizeCell); ctx.translate(0, (grid.height + 2) * o.sizeCell);
var max = grid.data.reduce(function (sum, x) { return sum + +!!x; }, stack.length); var max = grid.data.reduce(function (sum, x) { return sum + +!!x; }, stack.length);
@@ -33225,7 +33338,7 @@ var withTmpDir = function (handler) { return __awaiter(void 0, void 0, void 0, f
} }
}); });
}); }; }); };
var createGif = function (grid0, chain, drawOptions, gifOptions) { return __awaiter(void 0, void 0, void 0, function () { var createGif = function (grid0, cells, chain, drawOptions, animationOptions) { return __awaiter(void 0, void 0, void 0, function () {
return __generator(this, function (_a) { return __generator(this, function (_a) {
return [2 /*return*/, withTmpDir(function (dir) { return __awaiter(void 0, void 0, void 0, function () { return [2 /*return*/, withTmpDir(function (dir) { return __awaiter(void 0, void 0, void 0, function () {
var _a, width, height, canvas, ctx, grid, stack, encoder, i, snake0, snake1, k, outFileName, optimizedFileName; var _a, width, height, canvas, ctx, grid, stack, encoder, i, snake0, snake1, k, outFileName, optimizedFileName;
@@ -33237,17 +33350,17 @@ var createGif = function (grid0, chain, drawOptions, gifOptions) { return __awai
stack = []; stack = [];
encoder = new gif_encoder_2_1["default"](width, height, "neuquant", true); encoder = new gif_encoder_2_1["default"](width, height, "neuquant", true);
encoder.setRepeat(0); encoder.setRepeat(0);
encoder.setDelay(gifOptions.frameDuration); encoder.setDelay(animationOptions.frameDuration);
encoder.start(); encoder.start();
for (i = 0; i < chain.length; i += 1) { for (i = 0; i < chain.length; i += 1) {
snake0 = chain[i]; snake0 = chain[i];
snake1 = chain[Math.min(chain.length - 1, i + 1)]; snake1 = chain[Math.min(chain.length - 1, i + 1)];
(0, step_1.step)(grid, stack, snake0); (0, step_1.step)(grid, stack, snake0);
for (k = 0; k < gifOptions.step; k++) { for (k = 0; k < animationOptions.step; k++) {
ctx.clearRect(0, 0, width, height); ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "#fff"; ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, width, height); ctx.fillRect(0, 0, width, height);
(0, drawWorld_1.drawLerpWorld)(ctx, grid, snake0, snake1, stack, k / gifOptions.step, drawOptions); (0, drawWorld_1.drawLerpWorld)(ctx, grid, cells, snake0, snake1, stack, k / animationOptions.step, drawOptions);
encoder.addFrame(ctx); encoder.addFrame(ctx);
} }
} }
@@ -34284,7 +34397,7 @@ exports.createGrid = void 0;
var utils_1 = __webpack_require__(5913); var utils_1 = __webpack_require__(5913);
var percent = function (x) { return (x * 100).toFixed(2); }; var percent = function (x) { return (x * 100).toFixed(2); };
var createGrid = function (cells, _a, duration) { 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 svgElements = [];
var styles = [ 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 }"), ".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 +34420,8 @@ var createGrid = function (cells, _a, duration) {
"class": ["c", id].filter(Boolean).join(" "), "class": ["c", id].filter(Boolean).join(" "),
x: x * s + m, x: x * s + m,
y: y * s + m, y: y * s + m,
rx: sizeBorderRadius, rx: sizeDotBorderRadius,
ry: sizeBorderRadius, ry: sizeDotBorderRadius,
width: d, width: d,
height: d height: d
})); }));
@@ -34372,9 +34485,8 @@ var getCellsFromGrid = function (_a) {
return Array.from({ length: height }, function (_, y) { return ({ x: x, y: y }); }); return Array.from({ length: height }, function (_, y) { return ({ x: x, y: y }); });
}).flat(); }).flat();
}; };
var createLivingCells = function (grid0, chain, drawOptions) { var createLivingCells = function (grid0, chain, cells) {
var _a; var livingCells = (cells !== null && cells !== void 0 ? cells : getCellsFromGrid(grid0)).map(function (_a) {
var cells = ((_a = drawOptions.cells) !== null && _a !== void 0 ? _a : getCellsFromGrid(grid0)).map(function (_a) {
var x = _a.x, y = _a.y; var x = _a.x, y = _a.y;
return ({ return ({
x: x, x: x,
@@ -34390,23 +34502,23 @@ var createLivingCells = function (grid0, chain, drawOptions) {
var y = (0, snake_1.getHeadY)(snake); var y = (0, snake_1.getHeadY)(snake);
if ((0, grid_1.isInside)(grid, x, y) && !(0, grid_1.isEmpty)((0, grid_1.getColor)(grid, x, y))) { if ((0, grid_1.isInside)(grid, x, y) && !(0, grid_1.isEmpty)((0, grid_1.getColor)(grid, x, y))) {
(0, grid_1.setColorEmpty)(grid, x, y); (0, grid_1.setColorEmpty)(grid, x, y);
var cell = cells.find(function (c) { return c.x === x && c.y === y; }); var cell = livingCells.find(function (c) { return c.x === x && c.y === y; });
cell.t = i / chain.length; cell.t = i / chain.length;
} }
}; };
for (var i = 0; i < chain.length; i++) { for (var i = 0; i < chain.length; i++) {
_loop_1(i); _loop_1(i);
} }
return cells; return livingCells;
}; };
var createSvg = function (grid, chain, drawOptions, gifOptions) { var createSvg = function (grid, cells, chain, drawOptions, animationOptions) {
var width = (grid.width + 2) * drawOptions.sizeCell; var width = (grid.width + 2) * drawOptions.sizeCell;
var height = (grid.height + 5) * drawOptions.sizeCell; var height = (grid.height + 5) * drawOptions.sizeCell;
var duration = gifOptions.frameDuration * chain.length; var duration = animationOptions.frameDuration * chain.length;
var cells = createLivingCells(grid, chain, drawOptions); var livingCells = createLivingCells(grid, chain, cells);
var elements = [ var elements = [
(0, grid_2.createGrid)(cells, drawOptions, duration), (0, grid_2.createGrid)(livingCells, drawOptions, duration),
(0, stack_1.createStack)(cells, drawOptions, grid.width * drawOptions.sizeCell, (grid.height + 2) * drawOptions.sizeCell, duration), (0, stack_1.createStack)(livingCells, drawOptions, grid.width * drawOptions.sizeCell, (grid.height + 2) * drawOptions.sizeCell, duration),
(0, snake_2.createSnake)(chain, drawOptions, duration), (0, snake_2.createSnake)(chain, drawOptions, duration),
]; ];
var viewBox = [ var viewBox = [
@@ -34442,14 +34554,14 @@ exports.createSvg = createSvg;
var optimizeCss = function (css) { return csso.minify(css).css; }; var optimizeCss = function (css) { return csso.minify(css).css; };
var optimizeSvg = function (svg) { return svg; }; var optimizeSvg = function (svg) { return svg; };
var generateColorVar = function (drawOptions) { 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) { .map(function (_a) {
var i = _a[0], color = _a[1]; var i = _a[0], color = _a[1];
return "--c".concat(i, ":").concat(color, ";"); return "--c".concat(i, ":").concat(color, ";");
}) })
.join(""), "\n }\n ") + .join(""), "\n }\n ") +
(drawOptions.dark (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) { .map(function (_a) {
var i = _a[0], color = _a[1]; var i = _a[0], color = _a[1];
return "--c".concat(i, ":").concat(color, ";"); return "--c".concat(i, ":").concat(color, ";");