Files
snk/packages/svg-creator/index.ts
platane b40f17a02e ♻️ remove csso dependency
do a custom css optimization instead
2022-11-03 08:45:33 +01:00

163 lines
3.9 KiB
TypeScript

import {
copyGrid,
getColor,
isEmpty,
isInside,
setColorEmpty,
} from "@snk/types/grid";
import { getHeadX, getHeadY } from "@snk/types/snake";
import type { Snake } from "@snk/types/snake";
import type { Grid, Color, Empty } from "@snk/types/grid";
import type { Point } from "@snk/types/point";
import type { AnimationOptions } from "@snk/gif-creator";
import { createSnake } from "./snake";
import { createGrid } from "./grid";
import { createStack } from "./stack";
import { h } from "./xml-utils";
import { minifyCss } from "./css-utils";
export type DrawOptions = {
colorDots: Record<Color, string>;
colorEmpty: string;
colorDotBorder: string;
colorSnake: string;
sizeCell: number;
sizeDot: number;
sizeDotBorderRadius: number;
dark?: {
colorDots: Record<Color, string>;
colorEmpty: string;
colorDotBorder?: string;
colorSnake?: string;
};
};
const getCellsFromGrid = ({ width, height }: Grid) =>
Array.from({ length: width }, (_, x) =>
Array.from({ length: height }, (_, y) => ({ x, y }))
).flat();
const createLivingCells = (
grid0: Grid,
chain: Snake[],
cells: Point[] | null
) => {
const livingCells: (Point & {
t: number | null;
color: Color | Empty;
})[] = (cells ?? getCellsFromGrid(grid0)).map(({ x, y }) => ({
x,
y,
t: null,
color: getColor(grid0, x, y),
}));
const grid = copyGrid(grid0);
for (let i = 0; i < chain.length; i++) {
const snake = chain[i];
const x = getHeadX(snake);
const y = getHeadY(snake);
if (isInside(grid, x, y) && !isEmpty(getColor(grid, x, y))) {
setColorEmpty(grid, x, y);
const cell = livingCells.find((c) => c.x === x && c.y === y)!;
cell.t = i / chain.length;
}
}
return livingCells;
};
export const createSvg = (
grid: Grid,
cells: Point[] | null,
chain: Snake[],
drawOptions: DrawOptions,
animationOptions: Pick<AnimationOptions, "frameDuration">
) => {
const width = (grid.width + 2) * drawOptions.sizeCell;
const height = (grid.height + 5) * drawOptions.sizeCell;
const duration = animationOptions.frameDuration * chain.length;
const livingCells = createLivingCells(grid, chain, cells);
const elements = [
createGrid(livingCells, drawOptions, duration),
createStack(
livingCells,
drawOptions,
grid.width * drawOptions.sizeCell,
(grid.height + 2) * drawOptions.sizeCell,
duration
),
createSnake(chain, drawOptions, duration),
];
const viewBox = [
-drawOptions.sizeCell,
-drawOptions.sizeCell * 2,
width,
height,
].join(" ");
const style =
generateColorVar(drawOptions) +
elements
.map((e) => e.styles)
.flat()
.join("\n");
const svg = [
h("svg", {
viewBox,
width,
height,
xmlns: "http://www.w3.org/2000/svg",
}).replace("/>", ">"),
"<desc>",
"Generated with https://github.com/Platane/snk",
"</desc>",
"<style>",
optimizeCss(style),
"</style>",
...elements.map((e) => e.svgElements).flat(),
"</svg>",
].join("");
return optimizeSvg(svg);
};
const optimizeCss = (css: string) => minifyCss(css);
const optimizeSvg = (svg: string) => svg;
const generateColorVar = (drawOptions: DrawOptions) =>
`
:root {
--cb: ${drawOptions.colorDotBorder};
--cs: ${drawOptions.colorSnake};
--ce: ${drawOptions.colorEmpty};
${Object.entries(drawOptions.colorDots)
.map(([i, color]) => `--c${i}:${color};`)
.join("")}
}
` +
(drawOptions.dark
? `
@media (prefers-color-scheme: dark) {
:root {
--cb: ${drawOptions.dark.colorDotBorder || drawOptions.colorDotBorder};
--cs: ${drawOptions.dark.colorSnake || drawOptions.colorSnake};
--ce: ${drawOptions.dark.colorEmpty};
${Object.entries(drawOptions.dark.colorDots)
.map(([i, color]) => `--c${i}:${color};`)
.join("")}
}
}
`
: "");