Compare commits

...

22 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
release bot
1018f7a937 📦 1.1.3 2022-04-08 23:18:20 +00:00
platane
4edf90f41b 👷 2022-04-09 01:16:14 +02:00
platane
faf76e6eb6 👷 2022-04-09 00:23:17 +02:00
platane
5bede02e06 ⬆️ bump tooling dependencies 2022-04-09 00:03:30 +02:00
platane
4f7ff9bc90 📓 2022-04-08 23:25:09 +02:00
platane
b0d592375a 👷 2022-04-08 23:25:09 +02:00
platane
672fe6bf0e ⬆️ bump node-fetch 2022-04-08 23:02:26 +02:00
platane
829a59da98 🚀 demo page workerize load 2022-03-25 10:37:49 +01:00
platane
58176f658e ♻️ use fancy new typescript utils 2022-03-25 10:32:08 +01:00
platane
9c881735b7 🚀 add <desc> metadata to svg 2022-03-25 08:56:05 +01:00
platane
3c697c687e ♻️ clean up 2022-03-24 14:54:28 +01:00
38 changed files with 42598 additions and 40498 deletions

View File

@@ -18,19 +18,6 @@ jobs:
- run: yarn lint
- 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:
runs-on: ubuntu-latest
steps:
@@ -45,14 +32,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 +66,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 == 'refs/heads/main'
with:
target_branch: gh-pages
build_dir: packages/demo/dist

View File

@@ -11,11 +11,6 @@ on:
description:
description: "Version description"
type: string
prerelease:
description: "Prerelease"
default: false
required: true
type: boolean
jobs:
release:
@@ -60,12 +55,26 @@ jobs:
- name: bump package version
run: yarn version --no-git-tag-version --new-version ${{ github.event.inputs.version }}
- name: push new commit
uses: EndBug/add-and-commit@v7
with:
add: package.json svg-only/dist action.yml
message: 📦 ${{ github.event.inputs.version }}
tag: v${{ github.event.inputs.version }}
- name: push new build, tag version and push
id: push-tags
run: |
VERSION=${{ github.event.inputs.version }}
git config --global user.email "bot@platane.me"
git config --global user.name "release bot"
git add package.json svg-only/dist action.yml
git commit -m "📦 $VERSION"
git tag v$VERSION
git push origin main --tags
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-2 )
git push origin --tags --force
echo ::set-output name=prerelease::false
else
echo ::set-output name=prerelease::true
fi
- uses: actions/create-release@v1
env:
@@ -73,4 +82,4 @@ jobs:
with:
tag_name: v${{ github.event.inputs.version }}
body: ${{ github.event.inputs.description }}
prerelease: ${{ github.event.inputs.prerelease }}
prerelease: ${{ steps.push-tags.outputs.prerelease }}

View File

@@ -1,6 +1,7 @@
# snk
[![GitHub marketplace](https://img.shields.io/github/v/tag/platane/snk?label=marketplace&logo=github&sort=semver&style=flat-square)](https://github.com/marketplace/actions/generate-snake-game-from-github-contribution-grid)
[![GitHub release](https://img.shields.io/github/release/platane/snk.svg?style=flat-square)](https://github.com/platane/snk/releases/latest)
[![GitHub marketplace](https://img.shields.io/badge/marketplace-snake-blue?logo=github&style=flat-square)](https://github.com/marketplace/actions/generate-snake-game-from-github-contribution-grid)
![type definitions](https://img.shields.io/npm/types/typescript?style=flat-square)
![code style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)
@@ -20,24 +21,23 @@ Available as github action. Automatically generate a new image at the end of the
**github action**
```yaml
- uses: Platane/snk@v1.1.0
- uses: Platane/snk@v2.0.0-rc.1
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.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**

View File

@@ -4,23 +4,28 @@ author: "platane"
runs:
using: docker
image: docker://platane/snk@sha256:9c3604282bdf9cf367dc955545fff9e4a893e5716ee88701d8924fd2377dbc72
image: docker://platane/snk@sha256:e40bb02de6ed0f164eca8586b3f6c32109b2bcb426cd57c6882764825b40fe0d
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,16 +1,16 @@
{
"name": "snk",
"description": "Generates a snake game from a github user contributions grid",
"version": "1.1.2",
"version": "2.0.0-rc.3",
"private": true,
"repository": "github:platane/snk",
"devDependencies": {
"@types/jest": "27.4.1",
"@types/node": "16.11.7",
"jest": "27.5.1",
"prettier": "2.6.0",
"ts-jest": "27.1.3",
"typescript": "4.6.2"
"prettier": "2.6.2",
"ts-jest": "27.1.4",
"typescript": "4.6.3"
},
"workspaces": [
"packages/**"

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.
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 )
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 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,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 { snake4 } from "@snk/types/__fixtures__/snake";
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 (
userName: string,
format: { svg?: boolean; gif?: boolean }
outputs: ({
format: "svg" | "gif";
drawOptions: DrawOptions;
animationOptions: AnimationOptions;
} | null)[]
) => {
console.log("🎣 fetching github user contribution");
const { cells, colorScheme } = await getGithubUserContribution(userName);
@@ -14,40 +20,32 @@ 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, animationOptions } = out;
switch (format) {
case "svg": {
console.log(`🖌 creating svg (outputs[${i}])`);
const { createSvg } = await import("@snk/svg-creator");
return createSvg(grid, cells, chain, drawOptions, animationOptions);
}
case "gif": {
console.log(`📹 creating gif (outputs[${i}])`);
const { createGif } = await import("@snk/gif-creator");
return await createGif(
grid,
cells,
chain,
drawOptions,
animationOptions
);
}
}
})
);
};

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,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"
},
"devDependencies": {
"@zeit/ncc": "0.22.3",
"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 { 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 { drawLerpWorld, drawWorld } from "@snk/draw/drawWorld";
import { Snake } from "@snk/types/snake";
import type { DrawOptions 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",
@@ -67,7 +68,7 @@ export const createCanvas = ({
const draw = (grid: Grid, snake: Snake, stack: Color[]) => {
ctx.clearRect(0, 0, 9999, 9999);
drawWorld(ctx, grid, snake, stack, drawOptions);
drawWorld(ctx, grid, null, snake, stack, drawOptions);
};
const drawLerp = (
@@ -78,7 +79,7 @@ export const createCanvas = ({
k: number
) => {
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") => {

View File

@@ -1,18 +1,19 @@
import { getBestRoute } from "@snk/solver/getBestRoute";
import { Color, copyGrid, Grid } from "@snk/types/grid";
import { step } from "@snk/solver/step";
import { isStableAndBound, stepSpring } from "./springUtils";
import { Res } from "@snk/github-user-contribution";
import { Snake } from "@snk/types/snake";
import type { Res } from "@snk/github-user-contribution";
import type { Snake } from "@snk/types/snake";
import type { Point } from "@snk/types/point";
import {
drawLerpWorld,
getCanvasWorldSize,
Options,
Options as DrawOptions,
} from "@snk/draw/drawWorld";
import { userContributionToGrid } from "../action/userContributionToGrid";
import { snake4 as snake } from "@snk/types/__fixtures__/snake";
import { getPathToPose } from "@snk/solver/getPathToPose";
import { createSvg } from "../svg-creator";
import { userContributionToGrid } from "@snk/action/userContributionToGrid";
import { createSvg } from "@snk/svg-creator";
import { createRpcClient } from "./worker-utils";
import type { API as WorkerAPI } from "./demo.interactive.worker";
import { AnimationOptions } from "@snk/gif-creator";
const createForm = ({
onSubmit,
@@ -47,15 +48,24 @@ const createForm = ({
form.addEventListener("submit", (event) => {
event.preventDefault();
onSubmit(input.value).catch((err) => {
label.innerText = "error :(";
throw err;
});
onSubmit(input.value)
.finally(() => {
clearTimeout(timeout);
})
.catch((err) => {
label.innerText = "error :(";
throw err;
});
input.disabled = true;
submit.disabled = true;
form.appendChild(label);
label.innerText = "loading ...";
const timeout = setTimeout(() => {
label.innerText = "loading ( it might take a while ) ... ";
}, 5000);
});
//
@@ -75,6 +85,7 @@ const createGithubProfile = () => {
container.style.opacity = "0";
container.style.display = "flex";
container.style.flexDirection = "column";
container.style.height = "120px";
container.style.alignItems = "flex-start";
const image = document.createElement("img");
image.style.width = "100px";
@@ -107,11 +118,13 @@ const createGithubProfile = () => {
const createViewer = ({
grid0,
chain,
cells,
drawOptions,
}: {
grid0: Grid;
chain: Snake[];
drawOptions: Options;
cells: Point[];
drawOptions: DrawOptions;
}) => {
//
// canvas
@@ -150,7 +163,7 @@ const createViewer = ({
const k = spring.x % 1;
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);
};
@@ -180,9 +193,9 @@ const createViewer = ({
//
// svg
const svgLink = document.createElement("a");
const svgString = createSvg(grid0, chain, drawOptions, {
const svgString = createSvg(grid0, cells, chain, drawOptions, {
frameDuration: 100,
});
} as AnimationOptions);
const svgImageUri = `data:image/*;charset=utf-8;base64,${btoa(svgString)}`;
svgLink.href = svgImageUri;
svgLink.innerText = "github-user-contribution.svg";
@@ -220,25 +233,35 @@ 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",
cells,
};
const grid = userContributionToGrid(cells, colorScheme);
const chain = getBestRoute(grid, snake)!;
chain.push(...getPathToPose(chain.slice(-1)[0], snake)!);
const chain = await getChain(grid);
dispose();
createViewer({ grid0: grid, chain, drawOptions });
createViewer({ grid0: grid, chain, cells, drawOptions });
};
const worker = new Worker(
new URL(
"./demo.interactive.worker.ts",
// @ts-ignore
import.meta.url
)
);
const { getChain } = createRpcClient<WorkerAPI>(worker);
const profile = createGithubProfile();
const { dispose } = createForm({
onSubmit,

View File

@@ -0,0 +1,17 @@
import { getBestRoute } from "@snk/solver/getBestRoute";
import { getPathToPose } from "@snk/solver/getPathToPose";
import { snake4 as snake } from "@snk/types/__fixtures__/snake";
import type { Grid } from "@snk/types/grid";
import { createRpcServer } from "./worker-utils";
const getChain = (grid: Grid) => {
const chain = getBestRoute(grid, snake)!;
chain.push(...getPathToPose(chain.slice(-1)[0], snake)!);
return chain;
};
const api = { getChain };
export type API = typeof api;
createRpcServer(api);

View File

@@ -1,15 +1,18 @@
import "./menu";
import { getBestRoute } from "@snk/solver/getBestRoute";
import { createSvg } from "../svg-creator";
import { createSvg } from "@snk/svg-creator";
import { grid, snake } from "./sample";
import { drawOptions } from "./canvas";
import { getPathToPose } from "@snk/solver/getPathToPose";
import type { AnimationOptions } from "@snk/gif-creator";
const chain = getBestRoute(grid, snake);
chain.push(...getPathToPose(chain.slice(-1)[0], snake)!);
(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");
container.innerHTML = svg;

View File

@@ -2,20 +2,22 @@
"name": "@snk/demo",
"version": "1.0.0",
"dependencies": {
"@snk/action": "1.0.0",
"@snk/draw": "1.0.0",
"@snk/github-user-contribution": "1.0.0",
"@snk/solver": "1.0.0",
"canvas": "2.9.1",
"gifsicle": "5.3.0"
"@snk/svg-creator": "1.0.0",
"@snk/types": "1.0.0"
},
"devDependencies": {
"@types/dat.gui": "0.7.7",
"dat.gui": "0.7.7",
"dat.gui": "0.7.9",
"html-webpack-plugin": "5.5.0",
"ts-loader": "9.2.6",
"ts-loader": "9.2.8",
"ts-node": "10.7.0",
"webpack": "5.70.0",
"webpack": "5.72.0",
"webpack-cli": "4.9.2",
"webpack-dev-server": "4.7.4"
"webpack-dev-server": "4.8.1"
},
"scripts": {
"build": "webpack",

View File

@@ -0,0 +1,59 @@
type API = Record<string, (...args: any[]) => any>;
const symbol = "worker-rpc__";
export const createRpcServer = (api: API) =>
self.addEventListener("message", async (event) => {
if (event.data?.symbol === symbol) {
try {
const res = await api[event.data.methodName](...event.data.args);
self.postMessage({ symbol, key: event.data.key, res });
} catch (error: any) {
postMessage({ symbol, key: event.data.key, error: error.message });
}
}
});
export const createRpcClient = <API_ extends API>(worker: Worker) => {
const originalTerminate = worker.terminate;
worker.terminate = () => {
worker.dispatchEvent(new Event("terminate"));
originalTerminate.call(worker);
};
return new Proxy(
{} as {
[K in keyof API_]: (
...args: Parameters<API_[K]>
) => Promise<Awaited<ReturnType<API_[K]>>>;
},
{
get:
(_, methodName) =>
(...args: any[]) =>
new Promise((resolve, reject) => {
const key = Math.random().toString();
const onTerminate = () => {
worker.removeEventListener("terminate", onTerminate);
worker.removeEventListener("message", onMessageHandler);
reject(new Error("worker terminated"));
};
const onMessageHandler = (event: MessageEvent) => {
if (event.data?.symbol === symbol && event.data.key === key) {
if (event.data.error) reject(event.data.error);
else if (event.data.res) resolve(event.data.res);
worker.removeEventListener("terminate", onTerminate);
worker.removeEventListener("message", onMessageHandler);
}
};
worker.addEventListener("message", onMessageHandler);
worker.addEventListener("terminate", onTerminate);
worker.postMessage({ symbol, key, methodName, args });
}),
}
);
};

View File

@@ -6,21 +6,21 @@ 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;
cells?: Point[];
sizeDotBorderRadius: number;
};
export const drawGrid = (
ctx: CanvasRenderingContext2D,
grid: Grid,
cells: Point[] | null,
o: Options
) => {
for (let x = grid.width; x--; )
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);
// @ts-ignore
const color = !c ? o.colorEmpty : o.colorDots[c];
@@ -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

@@ -1,18 +1,17 @@
import { drawGrid } from "./drawGrid";
import { drawSnake, drawSnakeLerp } from "./drawSnake";
import type { Grid, Color } from "@snk/types/grid";
import type { Point } from "@snk/types/point";
import type { Snake } from "@snk/types/snake";
import type { Point } from "@snk/types/point";
export type Options = {
colorDots: Record<Color, string>;
colorEmpty: string;
colorBorder: string;
colorDotBorder: string;
colorSnake: string;
sizeCell: number;
sizeDot: number;
sizeBorderRadius: number;
cells?: Point[];
sizeDotBorderRadius: number;
};
export const drawStack = (
@@ -37,6 +36,7 @@ export const drawStack = (
export const drawWorld = (
ctx: CanvasRenderingContext2D,
grid: Grid,
cells: Point[] | null,
snake: Snake,
stack: Color[],
o: Options
@@ -44,7 +44,7 @@ export const drawWorld = (
ctx.save();
ctx.translate(1 * o.sizeCell, 2 * o.sizeCell);
drawGrid(ctx, grid, o);
drawGrid(ctx, grid, cells, o);
drawSnake(ctx, snake, o);
ctx.restore();
@@ -68,6 +68,7 @@ export const drawWorld = (
export const drawLerpWorld = (
ctx: CanvasRenderingContext2D,
grid: Grid,
cells: Point[] | null,
snake0: Snake,
snake1: Snake,
stack: Color[],
@@ -77,7 +78,7 @@ export const drawLerpWorld = (
ctx.save();
ctx.translate(1 * o.sizeCell, 2 * o.sizeCell);
drawGrid(ctx, grid, o);
drawGrid(ctx, grid, cells, o);
drawSnakeLerp(ctx, snake0, snake1, k, o);
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 { createSnakeFromCells } from "@snk/types/snake";
import { realistic as grid } from "@snk/types/__fixtures__/grid";
import { createGif } from "..";
import { AnimationOptions, 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,17 +25,17 @@ 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",
};
const gifOptions = { frameDuration: 100, step: 1 };
const animationOptions: AnimationOptions = { frameDuration: 100, step: 1 };
(async () => {
for (
@@ -49,7 +50,13 @@ const gifOptions = { frameDuration: 100, step: 1 };
const chainL = chain.slice(0, length);
for (let k = 0; k < 10 && (Date.now() - start < 10 * 1000 || k < 2); k++) {
const s = performance.now();
buffer = await createGif(grid, chainL, drawOptions, gifOptions);
buffer = await createGif(
grid,
null,
chainL,
drawOptions,
animationOptions
);
stats.push(performance.now() - s);
}

View File

@@ -1,25 +1,26 @@
import * as fs from "fs";
import * as path from "path";
import { createGif } from "..";
import { AnimationOptions, createGif } from "..";
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",
};
const gifOptions = { frameDuration: 200, step: 1 };
const animationOptions: AnimationOptions = { frameDuration: 200, step: 1 };
const dir = path.resolve(__dirname, "__snapshots__");
@@ -39,7 +40,13 @@ for (const key of [
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();
@@ -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();

View File

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

View File

@@ -12,7 +12,7 @@
"devDependencies": {
"@types/gifsicle": "5.2.0",
"@types/tmp": "0.2.3",
"@zeit/ncc": "0.22.3"
"@vercel/ncc": "0.24.1"
},
"scripts": {
"benchmark": "ncc run __tests__/benchmark.ts --quiet"

View File

@@ -124,8 +124,6 @@ const getSvgPosition = (
return p;
};
type ThenArg<T> = T extends PromiseLike<infer U> ? U : T;
export type Res = ThenArg<ReturnType<typeof getGithubUserContribution>>;
export type Res = Awaited<ReturnType<typeof getGithubUserContribution>>;
export type Cell = Res["cells"][number];

View File

@@ -3,7 +3,7 @@
"version": "1.0.0",
"dependencies": {
"cheerio": "1.0.0-rc.10",
"node-fetch": "2.6.1"
"node-fetch": "2.6.7"
},
"devDependencies": {
"@types/node-fetch": "2.6.1"

View File

@@ -1,15 +1,16 @@
import * as fs from "fs";
import * as path from "path";
import { createSvg } from "..";
import { createSvg, DrawOptions as DrawOptions } from "..";
import * as grids from "@snk/types/__fixtures__/grid";
import { snake3 as snake } from "@snk/types/__fixtures__/snake";
import { getBestRoute } from "@snk/solver/getBestRoute";
import { AnimationOptions } from "@snk/gif-creator";
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",
@@ -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__");
@@ -31,7 +32,13 @@ for (const [key, grid] of Object.entries(grids))
it(`should generate ${key} svg`, async () => {
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();

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

@@ -14,20 +14,20 @@ import { createGrid } from "./grid";
import { createStack } from "./stack";
import { h } from "./utils";
import * as csso from "csso";
import { AnimationOptions } from "@snk/gif-creator";
export type Options = {
export type DrawOptions = {
colorDots: Record<Color, string>;
colorEmpty: string;
colorBorder: string;
colorDotBorder: string;
colorSnake: string;
sizeCell: number;
sizeDot: number;
sizeBorderRadius: number;
cells?: Point[];
sizeDotBorderRadius: number;
dark?: {
colorDots: Record<Color, string>;
colorEmpty: string;
colorBorder?: string;
colorDotBorder?: string;
colorSnake?: string;
};
};
@@ -40,12 +40,12 @@ const getCellsFromGrid = ({ width, height }: Grid) =>
const createLivingCells = (
grid0: Grid,
chain: Snake[],
drawOptions: Options
cells: Point[] | null
) => {
const cells: (Point & {
const livingCells: (Point & {
t: number | null;
color: Color | Empty;
})[] = (drawOptions.cells ?? getCellsFromGrid(grid0)).map(({ x, y }) => ({
})[] = (cells ?? getCellsFromGrid(grid0)).map(({ x, y }) => ({
x,
y,
t: null,
@@ -60,31 +60,32 @@ const createLivingCells = (
if (isInside(grid, x, y) && !isEmpty(getColor(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;
}
}
return cells;
return livingCells;
};
export const createSvg = (
grid: Grid,
cells: Point[] | null,
chain: Snake[],
drawOptions: Options,
gifOptions: { frameDuration: number }
drawOptions: DrawOptions,
animationOptions: Pick<AnimationOptions, "frameDuration">
) => {
const width = (grid.width + 2) * 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 = [
createGrid(cells, drawOptions, duration),
createGrid(livingCells, drawOptions, duration),
createStack(
cells,
livingCells,
drawOptions,
grid.width * drawOptions.sizeCell,
(grid.height + 2) * drawOptions.sizeCell,
@@ -115,6 +116,10 @@ export const createSvg = (
xmlns: "http://www.w3.org/2000/svg",
}).replace("/>", ">"),
"<desc>",
"Generated with https://github.com/Platane/snk",
"</desc>",
"<style>",
optimizeCss(style),
"</style>",
@@ -130,10 +135,10 @@ export const createSvg = (
const optimizeCss = (css: string) => csso.minify(css).css;
const optimizeSvg = (svg: string) => svg;
const generateColorVar = (drawOptions: Options) =>
const generateColorVar = (drawOptions: DrawOptions) =>
`
:root {
--cb: ${drawOptions.colorBorder};
--cb: ${drawOptions.colorDotBorder};
--cs: ${drawOptions.colorSnake};
--ce: ${drawOptions.colorEmpty};
${Object.entries(drawOptions.colorDots)
@@ -145,7 +150,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);

View File

@@ -10,11 +10,22 @@ inputs:
github_user_name:
description: "github user name"
required: true
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.svg?palette=<github or github-dark>&color_snake=<color>&color_dots=<color>,<color>,<color>,<color>,<color>
outputs:
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.svg?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9

81450
svg-only/dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,8 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true
"esModuleInterop": true,
"moduleResolution": "node"
},
"exclude": ["node_modules"]
}

756
yarn.lock

File diff suppressed because it is too large Load Diff