change options, drop svg_out_path in favor of outputs list

This commit is contained in:
Platane
2022-04-11 23:19:33 +02:00
committed by GitHub
parent 579bcf1afe
commit 6b320a1ac4
25 changed files with 296 additions and 154 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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