change options, drop svg_out_path in favor of outputs list
This commit is contained in:
13
.github/workflows/main.yml
vendored
13
.github/workflows/main.yml
vendored
@@ -45,14 +45,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 +79,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.name == 'main'
|
||||||
with:
|
with:
|
||||||
target_branch: gh-pages
|
target_branch: gh-pages
|
||||||
build_dir: packages/demo/dist
|
build_dir: packages/demo/dist
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -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 )
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -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
|
- uses: Platane/snk@v2
|
||||||
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`
|
If you are only interested in generating a svg, you can use this other faster action: `uses: Platane/snk/svg-only@v2`
|
||||||
|
|
||||||
**interactive demo**
|
**interactive demo**
|
||||||
|
|
||||||
|
|||||||
27
action.yml
27
action.yml
@@ -10,17 +10,22 @@ 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
|
||||||
|
|||||||
@@ -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",
|
||||||
"private": true,
|
"private": true,
|
||||||
"repository": "github:platane/snk",
|
"repository": "github:platane/snk",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
*
|
*
|
||||||
!.gitignore
|
!.gitignore
|
||||||
|
!*.snap
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`should parse /out.svg?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9 1`] = `
|
||||||
|
Object {
|
||||||
|
"drawOptions": Object {
|
||||||
|
"colorDotBorder": "#1b1f230a",
|
||||||
|
"colorDots": Array [
|
||||||
|
"#bfd6f6",
|
||||||
|
"#8dbdff",
|
||||||
|
"#64a1f4",
|
||||||
|
"#4b91f1",
|
||||||
|
"#3c7dd9",
|
||||||
|
],
|
||||||
|
"colorEmpty": "#bfd6f6",
|
||||||
|
"colorSnake": "orange",
|
||||||
|
"dark": undefined,
|
||||||
|
"sizeCell": 16,
|
||||||
|
"sizeDot": 12,
|
||||||
|
"sizeDotBorderRadius": 2,
|
||||||
|
},
|
||||||
|
"filename": "/out.svg",
|
||||||
|
"format": "svg",
|
||||||
|
"gifOptions": Object {
|
||||||
|
"frameDuration": 100,
|
||||||
|
"step": 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`should parse path/to/out.gif 1`] = `
|
||||||
|
Object {
|
||||||
|
"drawOptions": Object {
|
||||||
|
"colorDotBorder": "#1b1f230a",
|
||||||
|
"colorDots": Array [
|
||||||
|
"#ebedf0",
|
||||||
|
"#9be9a8",
|
||||||
|
"#40c463",
|
||||||
|
"#30a14e",
|
||||||
|
"#216e39",
|
||||||
|
],
|
||||||
|
"colorEmpty": "#ebedf0",
|
||||||
|
"colorSnake": "purple",
|
||||||
|
"dark": Object {
|
||||||
|
"colorDotBorder": "#1b1f230a",
|
||||||
|
"colorDots": Array [
|
||||||
|
"#161b22",
|
||||||
|
"#01311f",
|
||||||
|
"#034525",
|
||||||
|
"#0f6d31",
|
||||||
|
"#00c647",
|
||||||
|
],
|
||||||
|
"colorEmpty": "#161b22",
|
||||||
|
"colorSnake": "purple",
|
||||||
|
},
|
||||||
|
"sizeCell": 16,
|
||||||
|
"sizeDot": 12,
|
||||||
|
"sizeDotBorderRadius": 2,
|
||||||
|
},
|
||||||
|
"filename": "path/to/out.gif",
|
||||||
|
"format": "gif",
|
||||||
|
"gifOptions": Object {
|
||||||
|
"frameDuration": 100,
|
||||||
|
"step": 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -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);
|
|
||||||
})();
|
|
||||||
@@ -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]!);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
11
packages/action/__tests__/outputsOptions.spec.ts
Normal file
11
packages/action/__tests__/outputsOptions.spec.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { parseEntry } from "../outputsOptions";
|
||||||
|
|
||||||
|
[
|
||||||
|
"path/to/out.gif",
|
||||||
|
|
||||||
|
"/out.svg?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9",
|
||||||
|
].forEach((entry) =>
|
||||||
|
it(`should parse ${entry}`, () => {
|
||||||
|
expect(parseEntry(entry)).toMatchSnapshot();
|
||||||
|
})
|
||||||
|
);
|
||||||
@@ -3,10 +3,18 @@ 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 { Options as DrawOptions } from "@snk/svg-creator";
|
||||||
|
|
||||||
export const generateContributionSnake = async (
|
export const generateContributionSnake = async (
|
||||||
userName: string,
|
userName: string,
|
||||||
format: { svg?: boolean; gif?: boolean }
|
outputs: ({
|
||||||
|
format: "svg" | "gif";
|
||||||
|
drawOptions: DrawOptions;
|
||||||
|
gifOptions: {
|
||||||
|
frameDuration: number;
|
||||||
|
step: number;
|
||||||
|
};
|
||||||
|
} | null)[]
|
||||||
) => {
|
) => {
|
||||||
console.log("🎣 fetching github user contribution");
|
console.log("🎣 fetching github user contribution");
|
||||||
const { cells, colorScheme } = await getGithubUserContribution(userName);
|
const { cells, colorScheme } = await getGithubUserContribution(userName);
|
||||||
@@ -14,40 +22,26 @@ 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, gifOptions } = 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, chain, drawOptions, gifOptions);
|
||||||
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(grid, chain, drawOptions, gifOptions);
|
||||||
return output;
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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}"`);
|
||||||
}
|
}
|
||||||
|
|||||||
54
packages/action/outputsOptions.ts
Normal file
54
packages/action/outputsOptions.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { Options as DrawOptions } from "@snk/svg-creator";
|
||||||
|
import { palettes } from "./palettes";
|
||||||
|
|
||||||
|
export const parseOutputsOption = (lines: string[]) => lines.map(parseEntry);
|
||||||
|
|
||||||
|
export const parseEntry = (entry: string) => {
|
||||||
|
const m = entry.trim().match(/^(.+\.(svg|gif))(\?.*)?$/);
|
||||||
|
if (!m) return null;
|
||||||
|
|
||||||
|
const [_, filename, format, query] = m;
|
||||||
|
|
||||||
|
const sp = new URLSearchParams(query || "");
|
||||||
|
|
||||||
|
const drawOptions: DrawOptions = {
|
||||||
|
sizeDotBorderRadius: 2,
|
||||||
|
sizeCell: 16,
|
||||||
|
sizeDot: 12,
|
||||||
|
...palettes["default"],
|
||||||
|
};
|
||||||
|
const gifOptions = { step: 1, frameDuration: 100 };
|
||||||
|
|
||||||
|
{
|
||||||
|
const palette = palettes[sp.get("palette")!];
|
||||||
|
if (palette) {
|
||||||
|
Object.assign(drawOptions, palette);
|
||||||
|
drawOptions.dark = palette.dark && { ...palette.dark };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sp.has("color_snake")) drawOptions.colorSnake = sp.get("color_snake")!;
|
||||||
|
if (sp.has("color_dots")) {
|
||||||
|
const colors = sp.get("color_dots")!.split(/[,;]/);
|
||||||
|
drawOptions.colorDots = colors;
|
||||||
|
drawOptions.colorEmpty = colors[0];
|
||||||
|
drawOptions.dark = undefined;
|
||||||
|
}
|
||||||
|
if (sp.has("color_dot_border"))
|
||||||
|
drawOptions.colorDotBorder = sp.get("color_dot_border")!;
|
||||||
|
|
||||||
|
if (sp.has("dark_color_dots")) {
|
||||||
|
const colors = sp.get("dark_color_dots")!.split(/[,;]/);
|
||||||
|
drawOptions.dark = {
|
||||||
|
...drawOptions.dark,
|
||||||
|
colorDots: colors,
|
||||||
|
colorEmpty: colors[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (sp.has("dark_color_dot_border") && drawOptions.dark)
|
||||||
|
drawOptions.dark.colorDotBorder = sp.get("color_dot_border")!;
|
||||||
|
if (sp.has("dark_color_snake") && drawOptions.dark)
|
||||||
|
drawOptions.dark.colorSnake = sp.get("color_snake")!;
|
||||||
|
|
||||||
|
return { filename, format: format as "svg" | "gif", drawOptions, gifOptions };
|
||||||
|
};
|
||||||
@@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
packages/action/palettes.ts
Normal file
29
packages/action/palettes.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Options as DrawOptions } from "@snk/svg-creator";
|
||||||
|
|
||||||
|
export const palettes: Record<
|
||||||
|
string,
|
||||||
|
Pick<
|
||||||
|
DrawOptions,
|
||||||
|
"colorDotBorder" | "colorEmpty" | "colorSnake" | "colorDots" | "dark"
|
||||||
|
>
|
||||||
|
> = {
|
||||||
|
"github-light": {
|
||||||
|
colorDotBorder: "#1b1f230a",
|
||||||
|
colorDots: ["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"],
|
||||||
|
colorEmpty: "#ebedf0",
|
||||||
|
colorSnake: "purple",
|
||||||
|
},
|
||||||
|
"github-dark": {
|
||||||
|
colorDotBorder: "#1b1f230a",
|
||||||
|
colorEmpty: "#161b22",
|
||||||
|
colorDots: ["#161b22", "#01311f", "#034525", "#0f6d31", "#00c647"],
|
||||||
|
colorSnake: "purple",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// aliases
|
||||||
|
palettes["github"] = {
|
||||||
|
...palettes["github-light"],
|
||||||
|
dark: { ...palettes["github-dark"] },
|
||||||
|
};
|
||||||
|
palettes["default"] = palettes["github"];
|
||||||
@@ -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 { Options 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",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { Snake } from "@snk/types/snake";
|
|||||||
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";
|
||||||
@@ -120,7 +120,7 @@ const createViewer = ({
|
|||||||
}: {
|
}: {
|
||||||
grid0: Grid;
|
grid0: Grid;
|
||||||
chain: Snake[];
|
chain: Snake[];
|
||||||
drawOptions: Options;
|
drawOptions: DrawOptions;
|
||||||
}) => {
|
}) => {
|
||||||
//
|
//
|
||||||
// canvas
|
// canvas
|
||||||
@@ -229,11 +229,11 @@ 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",
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ 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[];
|
cells?: Point[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import type { Snake } from "@snk/types/snake";
|
|||||||
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[];
|
cells?: Point[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { realistic as grid } from "@snk/types/__fixtures__/grid";
|
|||||||
import { createGif } from "..";
|
import { 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,11 +25,11 @@ 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",
|
||||||
|
|||||||
@@ -5,15 +5,16 @@ 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",
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
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, Options } 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";
|
||||||
|
|
||||||
const drawOptions = {
|
const drawOptions: Options = {
|
||||||
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",
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,16 +18,16 @@ import * as csso from "csso";
|
|||||||
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[];
|
cells?: Point[];
|
||||||
dark?: {
|
dark?: {
|
||||||
colorDots: Record<Color, string>;
|
colorDots: Record<Color, string>;
|
||||||
colorEmpty: string;
|
colorEmpty: string;
|
||||||
colorBorder?: string;
|
colorDotBorder?: string;
|
||||||
colorSnake?: string;
|
colorSnake?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -137,7 +137,7 @@ const optimizeSvg = (svg: string) => svg;
|
|||||||
const generateColorVar = (drawOptions: Options) =>
|
const generateColorVar = (drawOptions: Options) =>
|
||||||
`
|
`
|
||||||
: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 +149,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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user