Compare commits
5 Commits
v2.0.0-rc.
...
v2.0.0-rc.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e275adbb6 | ||
|
|
66fef03781 | ||
|
|
5841a21a09 | ||
|
|
cce5c4514d | ||
|
|
fb82d42d53 |
15
.github/workflows/main.yml
vendored
15
.github/workflows/main.yml
vendored
@@ -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:
|
||||
@@ -79,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.name == 'main'
|
||||
if: success() && github.ref == 'refs/heads/main'
|
||||
with:
|
||||
target_branch: gh-pages
|
||||
build_dir: packages/demo/dist
|
||||
|
||||
@@ -4,7 +4,7 @@ author: "platane"
|
||||
|
||||
runs:
|
||||
using: docker
|
||||
image: docker://platane/snk@sha256:339ea30dd50fc9566cd9b7244729b5ea12f14afea44585770212b96d3389748b
|
||||
image: docker://platane/snk@sha256:e40bb02de6ed0f164eca8586b3f6c32109b2bcb426cd57c6882764825b40fe0d
|
||||
|
||||
inputs:
|
||||
github_user_name:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "snk",
|
||||
"description": "Generates a snake game from a github user contributions grid",
|
||||
"version": "2.0.0-rc.2",
|
||||
"version": "2.0.0-rc.3",
|
||||
"private": true,
|
||||
"repository": "github:platane/snk",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,17 +1,48 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should parse /out.svg?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9 1`] = `
|
||||
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 [
|
||||
"#bfd6f6",
|
||||
"#8dbdff",
|
||||
"#64a1f4",
|
||||
"#4b91f1",
|
||||
"#3c7dd9",
|
||||
"#000",
|
||||
"#111",
|
||||
"#222",
|
||||
"#333",
|
||||
"#444",
|
||||
],
|
||||
"colorEmpty": "#bfd6f6",
|
||||
"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,
|
||||
@@ -20,15 +51,51 @@ Object {
|
||||
},
|
||||
"filename": "/out.svg",
|
||||
"format": "svg",
|
||||
"gifOptions": Object {
|
||||
}
|
||||
`;
|
||||
|
||||
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 [
|
||||
@@ -58,9 +125,5 @@ Object {
|
||||
},
|
||||
"filename": "path/to/out.gif",
|
||||
"format": "gif",
|
||||
"gifOptions": Object {
|
||||
"frameDuration": 100,
|
||||
"step": 1,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -3,7 +3,11 @@ import { parseEntry } from "../outputsOptions";
|
||||
[
|
||||
"path/to/out.gif",
|
||||
|
||||
"/out.svg?color_snake=orange&color_dots=#bfd6f6,#8dbdff,#64a1f4,#4b91f1,#3c7dd9",
|
||||
"/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();
|
||||
|
||||
@@ -3,17 +3,15 @@ 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";
|
||||
import type { DrawOptions as DrawOptions } from "@snk/svg-creator";
|
||||
import type { AnimationOptions } from "@snk/gif-creator";
|
||||
|
||||
export const generateContributionSnake = async (
|
||||
userName: string,
|
||||
outputs: ({
|
||||
format: "svg" | "gif";
|
||||
drawOptions: DrawOptions;
|
||||
gifOptions: {
|
||||
frameDuration: number;
|
||||
step: number;
|
||||
};
|
||||
animationOptions: AnimationOptions;
|
||||
} | null)[]
|
||||
) => {
|
||||
console.log("🎣 fetching github user contribution");
|
||||
@@ -29,17 +27,23 @@ export const generateContributionSnake = async (
|
||||
return Promise.all(
|
||||
outputs.map(async (out, i) => {
|
||||
if (!out) return;
|
||||
const { format, drawOptions, gifOptions } = out;
|
||||
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, chain, drawOptions, gifOptions);
|
||||
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, chain, drawOptions, gifOptions);
|
||||
return await createGif(
|
||||
grid,
|
||||
cells,
|
||||
chain,
|
||||
drawOptions,
|
||||
animationOptions
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
import { Options as DrawOptions } from "@snk/svg-creator";
|
||||
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))(\?.*)?$/);
|
||||
const m = entry.trim().match(/^(.+\.(svg|gif))(\?(.*))?$/);
|
||||
if (!m) return null;
|
||||
|
||||
const [_, filename, format, query] = m;
|
||||
const [, filename, format, , query] = m;
|
||||
|
||||
const sp = new URLSearchParams(query || "");
|
||||
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,
|
||||
@@ -17,7 +30,7 @@ export const parseEntry = (entry: string) => {
|
||||
sizeDot: 12,
|
||||
...palettes["default"],
|
||||
};
|
||||
const gifOptions = { step: 1, frameDuration: 100 };
|
||||
const animationOptions: AnimationOptions = { step: 1, frameDuration: 100 };
|
||||
|
||||
{
|
||||
const palette = palettes[sp.get("palette")!];
|
||||
@@ -50,5 +63,10 @@ export const parseEntry = (entry: string) => {
|
||||
if (sp.has("dark_color_snake") && drawOptions.dark)
|
||||
drawOptions.dark.colorSnake = sp.get("color_snake")!;
|
||||
|
||||
return { filename, format: format as "svg" | "gif", drawOptions, gifOptions };
|
||||
return {
|
||||
filename,
|
||||
format: format as "svg" | "gif",
|
||||
drawOptions,
|
||||
animationOptions,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Options as DrawOptions } from "@snk/svg-creator";
|
||||
import { DrawOptions as DrawOptions } from "@snk/svg-creator";
|
||||
|
||||
export const palettes: Record<
|
||||
string,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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";
|
||||
import type { DrawOptions as DrawOptions } from "@snk/svg-creator";
|
||||
|
||||
export const drawOptions: DrawOptions = {
|
||||
sizeDotBorderRadius: 2,
|
||||
@@ -68,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 = (
|
||||
@@ -79,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") => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { step } from "@snk/solver/step";
|
||||
import { isStableAndBound, stepSpring } from "./springUtils";
|
||||
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,
|
||||
@@ -12,6 +13,7 @@ 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,
|
||||
@@ -116,10 +118,12 @@ const createGithubProfile = () => {
|
||||
const createViewer = ({
|
||||
grid0,
|
||||
chain,
|
||||
cells,
|
||||
drawOptions,
|
||||
}: {
|
||||
grid0: Grid;
|
||||
chain: Snake[];
|
||||
cells: Point[];
|
||||
drawOptions: DrawOptions;
|
||||
}) => {
|
||||
//
|
||||
@@ -159,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);
|
||||
};
|
||||
@@ -189,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";
|
||||
@@ -237,7 +241,6 @@ const onSubmit = async (userName: string) => {
|
||||
colorDots: colorScheme as any,
|
||||
colorEmpty: colorScheme[0],
|
||||
colorSnake: "purple",
|
||||
cells,
|
||||
};
|
||||
|
||||
const grid = userContributionToGrid(cells, colorScheme);
|
||||
@@ -246,7 +249,7 @@ const onSubmit = async (userName: string) => {
|
||||
|
||||
dispose();
|
||||
|
||||
createViewer({ grid0: grid, chain, drawOptions });
|
||||
createViewer({ grid0: grid, chain, cells, drawOptions });
|
||||
};
|
||||
|
||||
const worker = new Worker(
|
||||
|
||||
@@ -4,12 +4,15 @@ 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;
|
||||
|
||||
@@ -10,17 +10,17 @@ type Options = {
|
||||
sizeCell: number;
|
||||
sizeDot: number;
|
||||
sizeDotBorderRadius: number;
|
||||
cells?: Point[];
|
||||
};
|
||||
|
||||
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];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
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>;
|
||||
@@ -12,7 +12,6 @@ export type Options = {
|
||||
sizeCell: number;
|
||||
sizeDot: number;
|
||||
sizeDotBorderRadius: number;
|
||||
cells?: Point[];
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
@@ -2,7 +2,7 @@ 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";
|
||||
@@ -35,7 +35,7 @@ const drawOptions: DrawOptions = {
|
||||
colorSnake: "purple",
|
||||
};
|
||||
|
||||
const gifOptions = { frameDuration: 100, step: 1 };
|
||||
const animationOptions: AnimationOptions = { frameDuration: 100, step: 1 };
|
||||
|
||||
(async () => {
|
||||
for (
|
||||
@@ -50,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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";
|
||||
@@ -20,7 +20,7 @@ const drawOptions: DrawOptions = {
|
||||
colorSnake: "purple",
|
||||
};
|
||||
|
||||
const gifOptions = { frameDuration: 200, step: 1 };
|
||||
const animationOptions: AnimationOptions = { frameDuration: 200, step: 1 };
|
||||
|
||||
const dir = path.resolve(__dirname, "__snapshots__");
|
||||
|
||||
@@ -40,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();
|
||||
|
||||
@@ -64,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();
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { createSvg, Options } 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: Options = {
|
||||
const drawOptions: DrawOptions = {
|
||||
sizeDotBorderRadius: 2,
|
||||
sizeCell: 16,
|
||||
sizeDot: 12,
|
||||
@@ -19,7 +20,7 @@ const drawOptions: Options = {
|
||||
},
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
|
||||
@@ -14,8 +14,9 @@ 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;
|
||||
colorDotBorder: string;
|
||||
@@ -23,7 +24,6 @@ export type Options = {
|
||||
sizeCell: number;
|
||||
sizeDot: number;
|
||||
sizeDotBorderRadius: number;
|
||||
cells?: Point[];
|
||||
dark?: {
|
||||
colorDots: Record<Color, string>;
|
||||
colorEmpty: 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,
|
||||
@@ -134,7 +135,7 @@ 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.colorDotBorder};
|
||||
|
||||
72
svg-only/dist/index.js
vendored
72
svg-only/dist/index.js
vendored
@@ -32796,13 +32796,13 @@ var generateContributionSnake = function (userName, outputs) { return __awaiter(
|
||||
chain = (0, getBestRoute_1.getBestRoute)(grid, snake);
|
||||
chain.push.apply(chain, (0, getPathToPose_1.getPathToPose)(chain.slice(-1)[0], snake));
|
||||
return [2 /*return*/, Promise.all(outputs.map(function (out, i) { return __awaiter(void 0, void 0, void 0, function () {
|
||||
var format, drawOptions, gifOptions, _a, createSvg, createGif;
|
||||
var format, drawOptions, animationOptions, _a, createSvg, createGif;
|
||||
return __generator(this, function (_b) {
|
||||
switch (_b.label) {
|
||||
case 0:
|
||||
if (!out)
|
||||
return [2 /*return*/];
|
||||
format = out.format, drawOptions = out.drawOptions, gifOptions = out.gifOptions;
|
||||
format = out.format, drawOptions = out.drawOptions, animationOptions = out.animationOptions;
|
||||
_a = format;
|
||||
switch (_a) {
|
||||
case "svg": return [3 /*break*/, 1];
|
||||
@@ -32814,13 +32814,13 @@ var generateContributionSnake = function (userName, outputs) { return __awaiter(
|
||||
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(__webpack_require__(7415)); })];
|
||||
case 2:
|
||||
createSvg = (_b.sent()).createSvg;
|
||||
return [2 /*return*/, createSvg(grid, chain, drawOptions, gifOptions)];
|
||||
return [2 /*return*/, createSvg(grid, cells, chain, drawOptions, animationOptions)];
|
||||
case 3:
|
||||
console.log("\uD83D\uDCF9 creating gif (outputs[".concat(i, "])"));
|
||||
return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(__webpack_require__(340)); })];
|
||||
case 4:
|
||||
createGif = (_b.sent()).createGif;
|
||||
return [4 /*yield*/, createGif(grid, chain, drawOptions, gifOptions)];
|
||||
return [4 /*yield*/, createGif(grid, cells, chain, drawOptions, animationOptions)];
|
||||
case 5: return [2 /*return*/, _b.sent()];
|
||||
case 6: return [2 /*return*/];
|
||||
}
|
||||
@@ -32962,13 +32962,25 @@ var palettes_1 = __webpack_require__(3848);
|
||||
var parseOutputsOption = function (lines) { return lines.map(exports.parseEntry); };
|
||||
exports.parseOutputsOption = parseOutputsOption;
|
||||
var parseEntry = function (entry) {
|
||||
var m = entry.trim().match(/^(.+\.(svg|gif))(\?.*)?$/);
|
||||
var m = entry.trim().match(/^(.+\.(svg|gif))(\?(.*))?$/);
|
||||
if (!m)
|
||||
return null;
|
||||
var _ = m[0], filename = m[1], format = m[2], query = m[3];
|
||||
var filename = m[1], format = m[2], query = m[4];
|
||||
var sp = new URLSearchParams(query || "");
|
||||
try {
|
||||
var o = JSON.parse(query);
|
||||
if (Array.isArray(o.color_dots))
|
||||
o.color_dots = o.color_dots.join(",");
|
||||
if (Array.isArray(o.dark_color_dots))
|
||||
o.dark_color_dots = o.dark_color_dots.join(",");
|
||||
sp = new URLSearchParams(o);
|
||||
}
|
||||
catch (err) {
|
||||
if (!(err instanceof SyntaxError))
|
||||
throw err;
|
||||
}
|
||||
var drawOptions = __assign({ sizeDotBorderRadius: 2, sizeCell: 16, sizeDot: 12 }, palettes_1.palettes["default"]);
|
||||
var gifOptions = { step: 1, frameDuration: 100 };
|
||||
var animationOptions = { step: 1, frameDuration: 100 };
|
||||
{
|
||||
var palette = palettes_1.palettes[sp.get("palette")];
|
||||
if (palette) {
|
||||
@@ -32994,7 +33006,12 @@ var parseEntry = function (entry) {
|
||||
drawOptions.dark.colorDotBorder = sp.get("color_dot_border");
|
||||
if (sp.has("dark_color_snake") && drawOptions.dark)
|
||||
drawOptions.dark.colorSnake = sp.get("color_snake");
|
||||
return { filename: filename, format: format, drawOptions: drawOptions, gifOptions: gifOptions };
|
||||
return {
|
||||
filename: filename,
|
||||
format: format,
|
||||
drawOptions: drawOptions,
|
||||
animationOptions: animationOptions
|
||||
};
|
||||
};
|
||||
exports.parseEntry = parseEntry;
|
||||
|
||||
@@ -33085,10 +33102,10 @@ exports.__esModule = true;
|
||||
exports.drawGrid = void 0;
|
||||
var grid_1 = __webpack_require__(2881);
|
||||
var pathRoundedRect_1 = __webpack_require__(2356);
|
||||
var drawGrid = function (ctx, grid, o) {
|
||||
var drawGrid = function (ctx, grid, cells, o) {
|
||||
var _loop_1 = function (x) {
|
||||
var _loop_2 = function (y) {
|
||||
if (!o.cells || o.cells.some(function (c) { return c.x === x && c.y === y; })) {
|
||||
if (!cells || cells.some(function (c) { return c.x === x && c.y === y; })) {
|
||||
var c = (0, grid_1.getColor)(grid, x, y);
|
||||
// @ts-ignore
|
||||
var color = !c ? o.colorEmpty : o.colorDots[c];
|
||||
@@ -33186,10 +33203,10 @@ var drawStack = function (ctx, stack, max, width, o) {
|
||||
ctx.restore();
|
||||
};
|
||||
exports.drawStack = drawStack;
|
||||
var drawWorld = function (ctx, grid, snake, stack, o) {
|
||||
var drawWorld = function (ctx, grid, cells, snake, stack, o) {
|
||||
ctx.save();
|
||||
ctx.translate(1 * o.sizeCell, 2 * o.sizeCell);
|
||||
(0, drawGrid_1.drawGrid)(ctx, grid, o);
|
||||
(0, drawGrid_1.drawGrid)(ctx, grid, cells, o);
|
||||
(0, drawSnake_1.drawSnake)(ctx, snake, o);
|
||||
ctx.restore();
|
||||
ctx.save();
|
||||
@@ -33204,10 +33221,10 @@ var drawWorld = function (ctx, grid, snake, stack, o) {
|
||||
// ctx.restore();
|
||||
};
|
||||
exports.drawWorld = drawWorld;
|
||||
var drawLerpWorld = function (ctx, grid, snake0, snake1, stack, k, o) {
|
||||
var drawLerpWorld = function (ctx, grid, cells, snake0, snake1, stack, k, o) {
|
||||
ctx.save();
|
||||
ctx.translate(1 * o.sizeCell, 2 * o.sizeCell);
|
||||
(0, drawGrid_1.drawGrid)(ctx, grid, o);
|
||||
(0, drawGrid_1.drawGrid)(ctx, grid, cells, o);
|
||||
(0, drawSnake_1.drawSnakeLerp)(ctx, snake0, snake1, k, o);
|
||||
ctx.translate(0, (grid.height + 2) * o.sizeCell);
|
||||
var max = grid.data.reduce(function (sum, x) { return sum + +!!x; }, stack.length);
|
||||
@@ -33321,7 +33338,7 @@ var withTmpDir = function (handler) { return __awaiter(void 0, void 0, void 0, f
|
||||
}
|
||||
});
|
||||
}); };
|
||||
var createGif = function (grid0, chain, drawOptions, gifOptions) { return __awaiter(void 0, void 0, void 0, function () {
|
||||
var createGif = function (grid0, cells, chain, drawOptions, animationOptions) { return __awaiter(void 0, void 0, void 0, function () {
|
||||
return __generator(this, function (_a) {
|
||||
return [2 /*return*/, withTmpDir(function (dir) { return __awaiter(void 0, void 0, void 0, function () {
|
||||
var _a, width, height, canvas, ctx, grid, stack, encoder, i, snake0, snake1, k, outFileName, optimizedFileName;
|
||||
@@ -33333,17 +33350,17 @@ var createGif = function (grid0, chain, drawOptions, gifOptions) { return __awai
|
||||
stack = [];
|
||||
encoder = new gif_encoder_2_1["default"](width, height, "neuquant", true);
|
||||
encoder.setRepeat(0);
|
||||
encoder.setDelay(gifOptions.frameDuration);
|
||||
encoder.setDelay(animationOptions.frameDuration);
|
||||
encoder.start();
|
||||
for (i = 0; i < chain.length; i += 1) {
|
||||
snake0 = chain[i];
|
||||
snake1 = chain[Math.min(chain.length - 1, i + 1)];
|
||||
(0, step_1.step)(grid, stack, snake0);
|
||||
for (k = 0; k < gifOptions.step; k++) {
|
||||
for (k = 0; k < animationOptions.step; k++) {
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
(0, drawWorld_1.drawLerpWorld)(ctx, grid, snake0, snake1, stack, k / gifOptions.step, drawOptions);
|
||||
(0, drawWorld_1.drawLerpWorld)(ctx, grid, cells, snake0, snake1, stack, k / animationOptions.step, drawOptions);
|
||||
encoder.addFrame(ctx);
|
||||
}
|
||||
}
|
||||
@@ -34468,9 +34485,8 @@ var getCellsFromGrid = function (_a) {
|
||||
return Array.from({ length: height }, function (_, y) { return ({ x: x, y: y }); });
|
||||
}).flat();
|
||||
};
|
||||
var createLivingCells = function (grid0, chain, drawOptions) {
|
||||
var _a;
|
||||
var cells = ((_a = drawOptions.cells) !== null && _a !== void 0 ? _a : getCellsFromGrid(grid0)).map(function (_a) {
|
||||
var createLivingCells = function (grid0, chain, cells) {
|
||||
var livingCells = (cells !== null && cells !== void 0 ? cells : getCellsFromGrid(grid0)).map(function (_a) {
|
||||
var x = _a.x, y = _a.y;
|
||||
return ({
|
||||
x: x,
|
||||
@@ -34486,23 +34502,23 @@ var createLivingCells = function (grid0, chain, drawOptions) {
|
||||
var y = (0, snake_1.getHeadY)(snake);
|
||||
if ((0, grid_1.isInside)(grid, x, y) && !(0, grid_1.isEmpty)((0, grid_1.getColor)(grid, x, y))) {
|
||||
(0, grid_1.setColorEmpty)(grid, x, y);
|
||||
var cell = cells.find(function (c) { return c.x === x && c.y === y; });
|
||||
var cell = livingCells.find(function (c) { return c.x === x && c.y === y; });
|
||||
cell.t = i / chain.length;
|
||||
}
|
||||
};
|
||||
for (var i = 0; i < chain.length; i++) {
|
||||
_loop_1(i);
|
||||
}
|
||||
return cells;
|
||||
return livingCells;
|
||||
};
|
||||
var createSvg = function (grid, chain, drawOptions, gifOptions) {
|
||||
var createSvg = function (grid, cells, chain, drawOptions, animationOptions) {
|
||||
var width = (grid.width + 2) * drawOptions.sizeCell;
|
||||
var height = (grid.height + 5) * drawOptions.sizeCell;
|
||||
var duration = gifOptions.frameDuration * chain.length;
|
||||
var cells = createLivingCells(grid, chain, drawOptions);
|
||||
var duration = animationOptions.frameDuration * chain.length;
|
||||
var livingCells = createLivingCells(grid, chain, cells);
|
||||
var elements = [
|
||||
(0, grid_2.createGrid)(cells, drawOptions, duration),
|
||||
(0, stack_1.createStack)(cells, drawOptions, grid.width * drawOptions.sizeCell, (grid.height + 2) * drawOptions.sizeCell, duration),
|
||||
(0, grid_2.createGrid)(livingCells, drawOptions, duration),
|
||||
(0, stack_1.createStack)(livingCells, drawOptions, grid.width * drawOptions.sizeCell, (grid.height + 2) * drawOptions.sizeCell, duration),
|
||||
(0, snake_2.createSnake)(chain, drawOptions, duration),
|
||||
];
|
||||
var viewBox = [
|
||||
|
||||
Reference in New Issue
Block a user