Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7a9c1e353 | ||
|
|
a0e08722d9 | ||
|
|
29c7ee48ec | ||
|
|
21655d1bda | ||
|
|
b895ed2e0f | ||
|
|
96773d2b2e | ||
|
|
79ae29668c | ||
|
|
62f6ff3091 | ||
|
|
4a03759871 | ||
|
|
463b90d43c | ||
|
|
b40f17a02e | ||
|
|
f83b9ab0c3 | ||
|
|
fb80d60b23 |
40
.github/workflows/main.yml
vendored
40
.github/workflows/main.yml
vendored
@@ -44,13 +44,51 @@ jobs:
|
||||
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@v3.1.0
|
||||
with:
|
||||
target_branch: output
|
||||
build_dir: dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
test-action-svg-only:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: yarn
|
||||
node-version: 16
|
||||
- run: yarn install --frozen-lockfile
|
||||
|
||||
- name: build svg-only action
|
||||
run: |
|
||||
yarn build:action
|
||||
rm -r svg-only/dist
|
||||
mv packages/action/dist svg-only/dist
|
||||
|
||||
- name: generate-snake-game-from-github-contribution-grid
|
||||
id: generate-snake
|
||||
uses: ./svg-only
|
||||
with:
|
||||
github_user_name: platane
|
||||
outputs: |
|
||||
dist/github-contribution-grid-snake.svg
|
||||
dist/github-contribution-grid-snake-dark.svg?palette=github-dark
|
||||
|
||||
- name: ensure the generated file exists
|
||||
run: |
|
||||
ls dist
|
||||
test -f dist/github-contribution-grid-snake.svg
|
||||
test -f dist/github-contribution-grid-snake-dark.svg
|
||||
|
||||
- uses: crazy-max/ghaction-github-pages@v3.1.0
|
||||
with:
|
||||
target_branch: output-svg-only
|
||||
build_dir: dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
deploy-ghpages:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
15
.github/workflows/release.yml
vendored
15
.github/workflows/release.yml
vendored
@@ -4,7 +4,10 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version"
|
||||
description: |
|
||||
New version for the release
|
||||
If the version is in format <major>.<minor>.<patch> a new release is emitted.
|
||||
Otherwise for other format ( for example <major>.<minor>.<patch>-beta.1 ), a prerelease is emitted.
|
||||
default: "0.0.1"
|
||||
required: true
|
||||
type: string
|
||||
@@ -15,6 +18,8 @@ on:
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -71,15 +76,15 @@ jobs:
|
||||
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
|
||||
echo "prerelease=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo ::set-output name=prerelease::true
|
||||
echo "prerelease=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- uses: actions/create-release@v1
|
||||
- uses: ncipollo/release-action@v1.11.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: v${{ github.event.inputs.version }}
|
||||
tag: v${{ github.event.inputs.version }}
|
||||
body: ${{ github.event.inputs.description }}
|
||||
prerelease: ${{ steps.push-tags.outputs.prerelease }}
|
||||
|
||||
@@ -23,7 +23,7 @@ FROM node:16-slim
|
||||
WORKDIR /action-release
|
||||
|
||||
RUN export YARN_CACHE_FOLDER="$(mktemp -d)" \
|
||||
&& yarn add canvas@2.9.1 gifsicle@5.3.0 --no-lockfile \
|
||||
&& yarn add canvas@2.10.2 gifsicle@5.3.0 --no-lockfile \
|
||||
&& rm -r "$YARN_CACHE_FOLDER"
|
||||
|
||||
COPY --from=builder /app/packages/action/dist/ /action-release/
|
||||
|
||||
17
README.md
17
README.md
@@ -14,14 +14,14 @@ Make it a snake Game, generate a snake path where the cells get eaten in an orde
|
||||
|
||||
Generate a [gif](https://github.com/Platane/snk/raw/output/github-contribution-grid-snake.gif) or [svg](https://github.com/Platane/snk/raw/output/github-contribution-grid-snake.svg) image.
|
||||
|
||||
Available as github action. Automatically generate a new image at the end of the day. Which makes for great [github profile readme](https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-your-github-profile/managing-your-profile-readme)
|
||||
Available as github action. It can automatically generate a new image each day. Which makes for great [github profile readme](https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-your-github-profile/managing-your-profile-readme)
|
||||
|
||||
## Usage
|
||||
|
||||
**github action**
|
||||
|
||||
```yaml
|
||||
- uses: Platane/snk@v2.0.0-rc.3
|
||||
- uses: Platane/snk@v2
|
||||
with:
|
||||
# github user name to read the contribution graph from (**required**)
|
||||
# using action context var `github.repository_owner` or specified user
|
||||
@@ -38,13 +38,22 @@ Available as github action. Automatically generate a new image at the end of the
|
||||
# Exactly 5 colors are expected.
|
||||
outputs: |
|
||||
dist/github-snake.svg
|
||||
dist/github-snake.svg?palette=github-dark
|
||||
dist/github-snake-dark.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@v2.0.0-rc.3`
|
||||
If you are only interested in generating a svg, consider using this faster action: `uses: Platane/snk/svg-only@v2`
|
||||
|
||||
**dark mode**
|
||||
|
||||
For **dark mode** support on github, use this [special syntax](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#specifying-the-theme-an-image-is-shown-to=) in your readme.
|
||||
|
||||
```md
|
||||

|
||||

|
||||
```
|
||||
|
||||
**interactive demo**
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ author: "platane"
|
||||
|
||||
runs:
|
||||
using: docker
|
||||
image: docker://platane/snk@sha256:3169a2e4a5b5181c203ae18e7075d0d92be88a0cdeabce6c3221288ef6a17675
|
||||
image: docker://platane/snk@sha256:dcb351bdad223f2a2161fa5d6e3c9102e6ebe9fbde99a10fa3bf443d69f61a0f
|
||||
|
||||
inputs:
|
||||
github_user_name:
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
testMatch: ["**/__tests__/**/?(*.)+(spec|test).ts"],
|
||||
};
|
||||
22
package.json
22
package.json
@@ -1,20 +1,30 @@
|
||||
{
|
||||
"name": "snk",
|
||||
"description": "Generates a snake game from a github user contributions grid",
|
||||
"version": "2.0.0",
|
||||
"version": "2.2.0",
|
||||
"private": true,
|
||||
"repository": "github:platane/snk",
|
||||
"devDependencies": {
|
||||
"@types/jest": "27.4.1",
|
||||
"@sucrase/jest-plugin": "3.0.0",
|
||||
"@types/jest": "29.2.1",
|
||||
"@types/node": "16.11.7",
|
||||
"jest": "27.5.1",
|
||||
"prettier": "2.6.2",
|
||||
"ts-jest": "27.1.4",
|
||||
"typescript": "4.6.3"
|
||||
"jest": "29.2.2",
|
||||
"prettier": "2.7.1",
|
||||
"sucrase": "3.28.0",
|
||||
"typescript": "4.8.4"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/**"
|
||||
],
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"testMatch": [
|
||||
"**/__tests__/**/?(*.)+(spec|test).ts"
|
||||
],
|
||||
"transform": {
|
||||
"\\.(ts|tsx)$": "@sucrase/jest-plugin"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"type": "tsc --noEmit",
|
||||
"lint": "yarn prettier -c '**/*.{ts,js,json,md,yml,yaml}' '!packages/*/dist/**' '!svg-only/dist/**'",
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should parse /out.svg {"color_snake":"yellow"} 1`] = `
|
||||
Object {
|
||||
"animationOptions": Object {
|
||||
{
|
||||
"animationOptions": {
|
||||
"frameDuration": 100,
|
||||
"step": 1,
|
||||
},
|
||||
"drawOptions": Object {
|
||||
"drawOptions": {
|
||||
"colorDotBorder": "#1b1f230a",
|
||||
"colorDots": Array [
|
||||
"colorDots": [
|
||||
"#ebedf0",
|
||||
"#9be9a8",
|
||||
"#40c463",
|
||||
@@ -17,9 +17,9 @@ Object {
|
||||
],
|
||||
"colorEmpty": "#ebedf0",
|
||||
"colorSnake": "yellow",
|
||||
"dark": Object {
|
||||
"dark": {
|
||||
"colorDotBorder": "#1b1f230a",
|
||||
"colorDots": Array [
|
||||
"colorDots": [
|
||||
"#161b22",
|
||||
"#01311f",
|
||||
"#034525",
|
||||
@@ -39,14 +39,14 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`should parse /out.svg?.gif.svg?color_snake=orange 1`] = `
|
||||
Object {
|
||||
"animationOptions": Object {
|
||||
{
|
||||
"animationOptions": {
|
||||
"frameDuration": 100,
|
||||
"step": 1,
|
||||
},
|
||||
"drawOptions": Object {
|
||||
"drawOptions": {
|
||||
"colorDotBorder": "#1b1f230a",
|
||||
"colorDots": Array [
|
||||
"colorDots": [
|
||||
"#ebedf0",
|
||||
"#9be9a8",
|
||||
"#40c463",
|
||||
@@ -55,9 +55,9 @@ Object {
|
||||
],
|
||||
"colorEmpty": "#ebedf0",
|
||||
"colorSnake": "orange",
|
||||
"dark": Object {
|
||||
"dark": {
|
||||
"colorDotBorder": "#1b1f230a",
|
||||
"colorDots": Array [
|
||||
"colorDots": [
|
||||
"#161b22",
|
||||
"#01311f",
|
||||
"#034525",
|
||||
@@ -77,14 +77,14 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`should parse /out.svg?{"color_snake":"yellow","color_dots":["#000","#111","#222","#333","#444"]} 1`] = `
|
||||
Object {
|
||||
"animationOptions": Object {
|
||||
{
|
||||
"animationOptions": {
|
||||
"frameDuration": 100,
|
||||
"step": 1,
|
||||
},
|
||||
"drawOptions": Object {
|
||||
"drawOptions": {
|
||||
"colorDotBorder": "#1b1f230a",
|
||||
"colorDots": Array [
|
||||
"colorDots": [
|
||||
"#000",
|
||||
"#111",
|
||||
"#222",
|
||||
@@ -104,14 +104,14 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`should parse /out.svg?color_snake=orange&color_dots=#000,#111,#222,#333,#444 1`] = `
|
||||
Object {
|
||||
"animationOptions": Object {
|
||||
{
|
||||
"animationOptions": {
|
||||
"frameDuration": 100,
|
||||
"step": 1,
|
||||
},
|
||||
"drawOptions": Object {
|
||||
"drawOptions": {
|
||||
"colorDotBorder": "#1b1f230a",
|
||||
"colorDots": Array [
|
||||
"colorDots": [
|
||||
"#000",
|
||||
"#111",
|
||||
"#222",
|
||||
@@ -131,14 +131,14 @@ 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 {
|
||||
{
|
||||
"animationOptions": {
|
||||
"frameDuration": 100,
|
||||
"step": 1,
|
||||
},
|
||||
"drawOptions": Object {
|
||||
"drawOptions": {
|
||||
"colorDotBorder": "#1b1f230a",
|
||||
"colorDots": Array [
|
||||
"colorDots": [
|
||||
"#000",
|
||||
"#111",
|
||||
"#222",
|
||||
@@ -147,8 +147,8 @@ Object {
|
||||
],
|
||||
"colorEmpty": "#000",
|
||||
"colorSnake": "orange",
|
||||
"dark": Object {
|
||||
"colorDots": Array [
|
||||
"dark": {
|
||||
"colorDots": [
|
||||
"#a00",
|
||||
"#a11",
|
||||
"#a22",
|
||||
@@ -167,14 +167,14 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`should parse path/to/out.gif 1`] = `
|
||||
Object {
|
||||
"animationOptions": Object {
|
||||
{
|
||||
"animationOptions": {
|
||||
"frameDuration": 100,
|
||||
"step": 1,
|
||||
},
|
||||
"drawOptions": Object {
|
||||
"drawOptions": {
|
||||
"colorDotBorder": "#1b1f230a",
|
||||
"colorDots": Array [
|
||||
"colorDots": [
|
||||
"#ebedf0",
|
||||
"#9be9a8",
|
||||
"#40c463",
|
||||
@@ -183,9 +183,9 @@ Object {
|
||||
],
|
||||
"colorEmpty": "#ebedf0",
|
||||
"colorSnake": "purple",
|
||||
"dark": Object {
|
||||
"dark": {
|
||||
"colorDotBorder": "#1b1f230a",
|
||||
"colorDots": Array [
|
||||
"colorDots": [
|
||||
"#161b22",
|
||||
"#01311f",
|
||||
"#034525",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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 () => {
|
||||
@@ -14,6 +13,9 @@ import { parseOutputsOption } from "./outputsOptions";
|
||||
]
|
||||
);
|
||||
|
||||
const { generateContributionSnake } = await import(
|
||||
"./generateContributionSnake"
|
||||
);
|
||||
const results = await generateContributionSnake(userName, outputs);
|
||||
|
||||
outputs.forEach((out, i) => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@snk/action",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@actions/core": "1.6.0",
|
||||
"@actions/core": "1.10.0",
|
||||
"@snk/gif-creator": "1.0.0",
|
||||
"@snk/github-user-contribution": "1.0.0",
|
||||
"@snk/solver": "1.0.0",
|
||||
@@ -10,9 +10,10 @@
|
||||
"@snk/types": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vercel/ncc": "0.24.1"
|
||||
"@vercel/ncc": "0.34.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "ncc build --external canvas --external gifsicle --out dist ./index.ts"
|
||||
"build": "ncc build --external canvas --external gifsicle --out dist ./index.ts",
|
||||
"run:build": "INPUT_GITHUB_USER_NAME=platane INPUT_OUTPUTS='dist/out.svg' node dist/index.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
"@types/dat.gui": "0.7.7",
|
||||
"dat.gui": "0.7.9",
|
||||
"html-webpack-plugin": "5.5.0",
|
||||
"ts-loader": "9.2.8",
|
||||
"ts-node": "10.7.0",
|
||||
"webpack": "5.72.0",
|
||||
"webpack-cli": "4.9.2",
|
||||
"webpack-dev-server": "4.8.1"
|
||||
"ts-loader": "9.4.1",
|
||||
"ts-node": "10.9.1",
|
||||
"webpack": "5.74.0",
|
||||
"webpack-cli": "4.10.0",
|
||||
"webpack-dev-server": "4.11.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"dependencies": {
|
||||
"@snk/draw": "1.0.0",
|
||||
"@snk/solver": "1.0.0",
|
||||
"canvas": "2.9.1",
|
||||
"canvas": "2.10.2",
|
||||
"gif-encoder-2": "1.0.5",
|
||||
"gifsicle": "5.3.0",
|
||||
"tmp": "0.2.1"
|
||||
@@ -12,7 +12,7 @@
|
||||
"devDependencies": {
|
||||
"@types/gifsicle": "5.2.0",
|
||||
"@types/tmp": "0.2.3",
|
||||
"@vercel/ncc": "0.24.1"
|
||||
"@vercel/ncc": "0.34.0"
|
||||
},
|
||||
"scripts": {
|
||||
"benchmark": "ncc run __tests__/benchmark.ts --quiet"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getGithubUserContribution } from "@snk/github-user-contribution";
|
||||
import { NowRequest, NowResponse } from "@vercel/node";
|
||||
import { VercelRequest, VercelResponse } from "@vercel/node";
|
||||
|
||||
export default async (req: NowRequest, res: NowResponse) => {
|
||||
export default async (req: VercelRequest, res: VercelResponse) => {
|
||||
const { userName } = req.query;
|
||||
|
||||
try {
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@snk/github-user-contribution": "1.0.0",
|
||||
"@vercel/node": "1.14.0"
|
||||
"@vercel/node": "2.6.1"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,7 @@ describe("getGithubUserContribution", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should match snapshot for year=2019", async () => {
|
||||
xit("should match snapshot for year=2019", async () => {
|
||||
expect(
|
||||
await getGithubUserContribution("platane", { year: 2019 })
|
||||
).toMatchSnapshot();
|
||||
|
||||
@@ -47,18 +47,22 @@ const parseUserPage = (content: string) => {
|
||||
let x = 0;
|
||||
let lastYAttribute = 0;
|
||||
|
||||
const rects = Array.from(block.matchAll(/<rect[^>]*>/g)).map(([m]) => {
|
||||
const date = m.match(/data-date="([^"]+)"/)![1];
|
||||
const count = +m.match(/data-count="([^"]+)"/)![1];
|
||||
const level = +m.match(/data-level="([^"]+)"/)![1];
|
||||
const yAttribute = +m.match(/y="([^"]+)"/)![1];
|
||||
const rects = Array.from(block.matchAll(/<rect[^>]*>[^<]*<\/rect>/g)).map(
|
||||
([m]) => {
|
||||
const date = m.match(/data-date="([^"]+)"/)![1];
|
||||
const level = +m.match(/data-level="([^"]+)"/)![1];
|
||||
const yAttribute = +m.match(/y="([^"]+)"/)![1];
|
||||
|
||||
if (lastYAttribute > yAttribute) x++;
|
||||
const literalCount = m.match(/(No|\d+) contributions? on/)![1];
|
||||
const count = literalCount === "No" ? 0 : +literalCount;
|
||||
|
||||
lastYAttribute = yAttribute;
|
||||
if (lastYAttribute > yAttribute) x++;
|
||||
|
||||
return { date, count, level, x, yAttribute };
|
||||
});
|
||||
lastYAttribute = yAttribute;
|
||||
|
||||
return { date, count, level, x, yAttribute };
|
||||
}
|
||||
);
|
||||
|
||||
const yAttributes = Array.from(
|
||||
new Set(rects.map((c) => c.yAttribute)).keys()
|
||||
|
||||
26
packages/svg-creator/__tests__/minifyCss.spec.ts
Normal file
26
packages/svg-creator/__tests__/minifyCss.spec.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { minifyCss } from "../css-utils";
|
||||
|
||||
it("should minify css", () => {
|
||||
expect(
|
||||
minifyCss(`
|
||||
.c {
|
||||
color : red ;
|
||||
}
|
||||
|
||||
`)
|
||||
).toBe(".c{color:red}");
|
||||
|
||||
expect(
|
||||
minifyCss(`
|
||||
.c {
|
||||
top : 0;
|
||||
color : red ;
|
||||
}
|
||||
|
||||
# {
|
||||
animation: linear 10;
|
||||
}
|
||||
|
||||
`)
|
||||
).toBe(".c{top:0;color:red}#{animation:linear 10}");
|
||||
});
|
||||
38
packages/svg-creator/css-utils.ts
Normal file
38
packages/svg-creator/css-utils.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
const percent = (x: number) =>
|
||||
parseFloat((x * 100).toFixed(2)).toString() + "%";
|
||||
|
||||
const mergeKeyFrames = (keyframes: { t: number; style: string }[]) => {
|
||||
const s = new Map<string, number[]>();
|
||||
for (const { t, style } of keyframes) {
|
||||
s.set(style, [...(s.get(style) ?? []), t]);
|
||||
}
|
||||
return Array.from(s.entries())
|
||||
.map(([style, ts]) => ({ style, ts }))
|
||||
.sort((a, b) => a.ts[0] - b.ts[0]);
|
||||
};
|
||||
|
||||
/**
|
||||
* generate the keyframe animation from a list of keyframe
|
||||
*/
|
||||
export const createAnimation = (
|
||||
name: string,
|
||||
keyframes: { t: number; style: string }[]
|
||||
) =>
|
||||
`@keyframes ${name}{` +
|
||||
mergeKeyFrames(keyframes)
|
||||
.map(({ style, ts }) => ts.map(percent).join(",") + `{${style}}`)
|
||||
.join("") +
|
||||
"}";
|
||||
|
||||
/**
|
||||
* remove white spaces
|
||||
*/
|
||||
export const minifyCss = (css: string) =>
|
||||
css
|
||||
.replace(/\s+/g, " ")
|
||||
.replace(/.\s+[,;:{}()]/g, (a) => a.replace(/\s+/g, ""))
|
||||
.replace(/[,;:{}()]\s+./g, (a) => a.replace(/\s+/g, ""))
|
||||
.replace(/.\s+[,;:{}()]/g, (a) => a.replace(/\s+/g, ""))
|
||||
.replace(/[,;:{}()]\s+./g, (a) => a.replace(/\s+/g, ""))
|
||||
.replace(/\;\s*\}/g, "}")
|
||||
.trim();
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Color, Empty } from "@snk/types/grid";
|
||||
import type { Point } from "@snk/types/point";
|
||||
import { h } from "./utils";
|
||||
import { createAnimation } from "./css-utils";
|
||||
import { h } from "./xml-utils";
|
||||
|
||||
export type Options = {
|
||||
colorDots: Record<Color, string>;
|
||||
@@ -11,8 +12,6 @@ export type Options = {
|
||||
sizeDotBorderRadius: number;
|
||||
};
|
||||
|
||||
const percent = (x: number) => (x * 100).toFixed(2);
|
||||
|
||||
export const createGrid = (
|
||||
cells: (Point & { t: number | null; color: Color | Empty })[],
|
||||
{ sizeDotBorderRadius, sizeDot, sizeCell }: Options,
|
||||
@@ -26,38 +25,40 @@ export const createGrid = (
|
||||
stroke-width: 1px;
|
||||
stroke: var(--cb);
|
||||
animation: none ${duration}ms linear infinite;
|
||||
width: ${sizeDot}px;
|
||||
height: ${sizeDot}px;
|
||||
}`,
|
||||
];
|
||||
|
||||
let i = 0;
|
||||
for (const { x, y, color, t } of cells) {
|
||||
const id = t && "c" + (i++).toString(36);
|
||||
const s = sizeCell;
|
||||
const d = sizeDot;
|
||||
const m = (s - d) / 2;
|
||||
const m = (sizeCell - sizeDot) / 2;
|
||||
|
||||
if (t !== null) {
|
||||
if (t !== null && id) {
|
||||
const animationName = id;
|
||||
|
||||
styles.push(
|
||||
`@keyframes ${animationName} {` +
|
||||
`${percent(t - 0.0001)}%{fill:var(--c${color})}` +
|
||||
`${percent(t + 0.0001)}%,100%{fill:var(--ce)}` +
|
||||
"}",
|
||||
createAnimation(animationName, [
|
||||
{ t: t - 0.0001, style: `fill:var(--c${color})` },
|
||||
{ t: t + 0.0001, style: `fill:var(--ce)` },
|
||||
{ t: 1, style: `fill:var(--ce)` },
|
||||
]),
|
||||
|
||||
`.c.${id}{fill: var(--c${color}); animation-name: ${animationName}}`
|
||||
`.c.${id}{
|
||||
fill: var(--c${color});
|
||||
animation-name: ${animationName}
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
svgElements.push(
|
||||
h("rect", {
|
||||
class: ["c", id].filter(Boolean).join(" "),
|
||||
x: x * s + m,
|
||||
y: y * s + m,
|
||||
x: x * sizeCell + m,
|
||||
y: y * sizeCell + m,
|
||||
rx: sizeDotBorderRadius,
|
||||
ry: sizeDotBorderRadius,
|
||||
width: d,
|
||||
height: d,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ 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 "./utils";
|
||||
import * as csso from "csso";
|
||||
import { AnimationOptions } from "@snk/gif-creator";
|
||||
import { h } from "./xml-utils";
|
||||
import { minifyCss } from "./css-utils";
|
||||
|
||||
export type DrawOptions = {
|
||||
colorDots: Record<Color, string>;
|
||||
@@ -132,7 +132,7 @@ export const createSvg = (
|
||||
return optimizeSvg(svg);
|
||||
};
|
||||
|
||||
const optimizeCss = (css: string) => csso.minify(css).css;
|
||||
const optimizeCss = (css: string) => minifyCss(css);
|
||||
const optimizeSvg = (svg: string) => svg;
|
||||
|
||||
const generateColorVar = (drawOptions: DrawOptions) =>
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
"name": "@snk/svg-creator",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@snk/solver": "1.0.0",
|
||||
"csso": "5.0.3"
|
||||
"@snk/solver": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/csso": "5.0.0"
|
||||
}
|
||||
"devDependencies": {}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { getSnakeLength, snakeToCells } from "@snk/types/snake";
|
||||
import type { Snake } from "@snk/types/snake";
|
||||
import type { Point } from "@snk/types/point";
|
||||
import { h } from "./utils";
|
||||
import { h } from "./xml-utils";
|
||||
import { createAnimation } from "./css-utils";
|
||||
|
||||
export type Options = {
|
||||
colorSnake: string;
|
||||
@@ -9,8 +10,6 @@ export type Options = {
|
||||
sizeDot: number;
|
||||
};
|
||||
|
||||
const percent = (x: number) => (x * 100).toFixed(2);
|
||||
|
||||
const lerp = (k: number, a: number, b: number) => (1 - k) * a + k * b;
|
||||
|
||||
export const createSnake = (
|
||||
@@ -55,8 +54,8 @@ export const createSnake = (
|
||||
|
||||
const styles = [
|
||||
`.s{
|
||||
shape-rendering:geometricPrecision;
|
||||
fill:var(--cs);
|
||||
shape-rendering: geometricPrecision;
|
||||
fill: var(--cs);
|
||||
animation: none linear ${duration}ms infinite
|
||||
}`,
|
||||
|
||||
@@ -64,16 +63,17 @@ export const createSnake = (
|
||||
const id = `s${i}`;
|
||||
const animationName = id;
|
||||
|
||||
return [
|
||||
`@keyframes ${animationName} {` +
|
||||
removeInterpolatedPositions(
|
||||
positions.map((tr, i, { length }) => ({ ...tr, t: i / length }))
|
||||
)
|
||||
.map((p) => `${percent(p.t)}%{${transform(p)}}`)
|
||||
.join("") +
|
||||
"}",
|
||||
const keyframes = removeInterpolatedPositions(
|
||||
positions.map((tr, i, { length }) => ({ ...tr, t: i / length }))
|
||||
).map(({ t, ...p }) => ({ t, style: transform(p) }));
|
||||
|
||||
`.s.${id}{${transform(positions[0])};animation-name: ${animationName}}`,
|
||||
return [
|
||||
createAnimation(animationName, keyframes),
|
||||
|
||||
`.s.${id}{
|
||||
${transform(positions[0])};
|
||||
animation-name: ${animationName}
|
||||
}`,
|
||||
];
|
||||
}),
|
||||
].flat();
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import type { Color, Empty } from "@snk/types/grid";
|
||||
import { h } from "./utils";
|
||||
import { createAnimation } from "./css-utils";
|
||||
import { h } from "./xml-utils";
|
||||
|
||||
export type Options = {
|
||||
sizeDot: number;
|
||||
};
|
||||
|
||||
const percent = (x: number) => (x * 100).toFixed(2);
|
||||
|
||||
export const createStack = (
|
||||
cells: { t: number | null; color: Color | Empty }[],
|
||||
{ sizeDot }: Options,
|
||||
@@ -56,23 +55,28 @@ export const createStack = (
|
||||
);
|
||||
|
||||
styles.push(
|
||||
`@keyframes ${animationName} {` +
|
||||
createAnimation(
|
||||
animationName,
|
||||
[
|
||||
...ts.map((t, i, { length }) => [
|
||||
{ scale: i / length, t: t - 0.0001 },
|
||||
{ scale: (i + 1) / length, t: t + 0.0001 },
|
||||
]),
|
||||
[{ scale: 1, t: 1 }],
|
||||
]
|
||||
.flat()
|
||||
.map(
|
||||
({ scale, t }) =>
|
||||
`${percent(t)}%{transform:scale(${scale.toFixed(2)},1)}`
|
||||
)
|
||||
.join("\n") +
|
||||
"}",
|
||||
...ts
|
||||
.map((t, i, { length }) => [
|
||||
{ scale: i / length, t: t - 0.0001 },
|
||||
{ scale: (i + 1) / length, t: t + 0.0001 },
|
||||
])
|
||||
.flat(),
|
||||
{ scale: 1, t: 1 },
|
||||
].map(({ scale, t }) => ({
|
||||
t,
|
||||
style: `transform:scale(${scale.toFixed(3)},1)`,
|
||||
}))
|
||||
),
|
||||
|
||||
`.u.${id}{fill:var(--c${color});animation-name:${animationName};transform-origin:${x}px 0}`
|
||||
`.u.${id} {
|
||||
fill: var(--c${color});
|
||||
animation-name: ${animationName};
|
||||
transform-origin: ${x}px 0
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
233
svg-only/dist/142.index.js
vendored
Normal file
233
svg-only/dist/142.index.js
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
"use strict";
|
||||
exports.id = 142;
|
||||
exports.ids = [142];
|
||||
exports.modules = {
|
||||
|
||||
/***/ 7142:
|
||||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
// ESM COMPAT FLAG
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
|
||||
// EXPORTS
|
||||
__webpack_require__.d(__webpack_exports__, {
|
||||
"createGif": () => (/* binding */ createGif)
|
||||
});
|
||||
|
||||
// EXTERNAL MODULE: external "fs"
|
||||
var external_fs_ = __webpack_require__(7147);
|
||||
var external_fs_default = /*#__PURE__*/__webpack_require__.n(external_fs_);
|
||||
// EXTERNAL MODULE: external "path"
|
||||
var external_path_ = __webpack_require__(1017);
|
||||
var external_path_default = /*#__PURE__*/__webpack_require__.n(external_path_);
|
||||
// EXTERNAL MODULE: external "child_process"
|
||||
var external_child_process_ = __webpack_require__(2081);
|
||||
// EXTERNAL MODULE: external "canvas"
|
||||
var external_canvas_ = __webpack_require__(1576);
|
||||
// EXTERNAL MODULE: ../types/grid.ts
|
||||
var types_grid = __webpack_require__(2881);
|
||||
;// CONCATENATED MODULE: ../draw/pathRoundedRect.ts
|
||||
const pathRoundedRect_pathRoundedRect = (ctx, width, height, borderRadius) => {
|
||||
ctx.moveTo(borderRadius, 0);
|
||||
ctx.arcTo(width, 0, width, height, borderRadius);
|
||||
ctx.arcTo(width, height, 0, height, borderRadius);
|
||||
ctx.arcTo(0, height, 0, 0, borderRadius);
|
||||
ctx.arcTo(0, 0, width, 0, borderRadius);
|
||||
};
|
||||
|
||||
;// CONCATENATED MODULE: ../draw/drawGrid.ts
|
||||
|
||||
|
||||
const drawGrid_drawGrid = (ctx, grid, cells, o) => {
|
||||
for (let x = grid.width; x--;)
|
||||
for (let y = grid.height; y--;) {
|
||||
if (!cells || cells.some((c) => c.x === x && c.y === y)) {
|
||||
const c = (0,types_grid/* getColor */.Lq)(grid, x, y);
|
||||
// @ts-ignore
|
||||
const color = !c ? o.colorEmpty : o.colorDots[c];
|
||||
ctx.save();
|
||||
ctx.translate(x * o.sizeCell + (o.sizeCell - o.sizeDot) / 2, y * o.sizeCell + (o.sizeCell - o.sizeDot) / 2);
|
||||
ctx.fillStyle = color;
|
||||
ctx.strokeStyle = o.colorDotBorder;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
pathRoundedRect_pathRoundedRect(ctx, o.sizeDot, o.sizeDot, o.sizeDotBorderRadius);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
;// CONCATENATED MODULE: ../draw/drawSnake.ts
|
||||
|
||||
|
||||
const drawSnake_drawSnake = (ctx, snake, o) => {
|
||||
const cells = snakeToCells(snake);
|
||||
for (let i = 0; i < cells.length; i++) {
|
||||
const u = (i + 1) * 0.6;
|
||||
ctx.save();
|
||||
ctx.fillStyle = o.colorSnake;
|
||||
ctx.translate(cells[i].x * o.sizeCell + u, cells[i].y * o.sizeCell + u);
|
||||
ctx.beginPath();
|
||||
pathRoundedRect(ctx, o.sizeCell - u * 2, o.sizeCell - u * 2, (o.sizeCell - u * 2) * 0.25);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
const lerp = (k, a, b) => (1 - k) * a + k * b;
|
||||
const clamp = (x, a, b) => Math.max(a, Math.min(b, x));
|
||||
const drawSnakeLerp = (ctx, snake0, snake1, k, o) => {
|
||||
const m = 0.8;
|
||||
const n = snake0.length / 2;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const u = (i + 1) * 0.6 * (o.sizeCell / 16);
|
||||
const a = (1 - m) * (i / Math.max(n - 1, 1));
|
||||
const ki = clamp((k - a) / m, 0, 1);
|
||||
const x = lerp(ki, snake0[i * 2 + 0], snake1[i * 2 + 0]) - 2;
|
||||
const y = lerp(ki, snake0[i * 2 + 1], snake1[i * 2 + 1]) - 2;
|
||||
ctx.save();
|
||||
ctx.fillStyle = o.colorSnake;
|
||||
ctx.translate(x * o.sizeCell + u, y * o.sizeCell + u);
|
||||
ctx.beginPath();
|
||||
pathRoundedRect_pathRoundedRect(ctx, o.sizeCell - u * 2, o.sizeCell - u * 2, (o.sizeCell - u * 2) * 0.25);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
|
||||
;// CONCATENATED MODULE: ../draw/drawWorld.ts
|
||||
|
||||
|
||||
const drawStack = (ctx, stack, max, width, o) => {
|
||||
ctx.save();
|
||||
const m = width / max;
|
||||
for (let i = 0; i < stack.length; i++) {
|
||||
// @ts-ignore
|
||||
ctx.fillStyle = o.colorDots[stack[i]];
|
||||
ctx.fillRect(i * m, 0, m + width * 0.005, 10);
|
||||
}
|
||||
ctx.restore();
|
||||
};
|
||||
const drawWorld = (ctx, grid, cells, snake, stack, o) => {
|
||||
ctx.save();
|
||||
ctx.translate(1 * o.sizeCell, 2 * o.sizeCell);
|
||||
drawGrid(ctx, grid, cells, o);
|
||||
drawSnake(ctx, snake, o);
|
||||
ctx.restore();
|
||||
ctx.save();
|
||||
ctx.translate(o.sizeCell, (grid.height + 4) * o.sizeCell);
|
||||
const max = grid.data.reduce((sum, x) => sum + +!!x, stack.length);
|
||||
drawStack(ctx, stack, max, grid.width * o.sizeCell, o);
|
||||
ctx.restore();
|
||||
// ctx.save();
|
||||
// ctx.translate(o.sizeCell + 100, (grid.height + 4) * o.sizeCell + 100);
|
||||
// ctx.scale(0.6, 0.6);
|
||||
// drawCircleStack(ctx, stack, o);
|
||||
// ctx.restore();
|
||||
};
|
||||
const drawLerpWorld = (ctx, grid, cells, snake0, snake1, stack, k, o) => {
|
||||
ctx.save();
|
||||
ctx.translate(1 * o.sizeCell, 2 * o.sizeCell);
|
||||
drawGrid_drawGrid(ctx, grid, cells, o);
|
||||
drawSnakeLerp(ctx, snake0, snake1, k, o);
|
||||
ctx.translate(0, (grid.height + 2) * o.sizeCell);
|
||||
const max = grid.data.reduce((sum, x) => sum + +!!x, stack.length);
|
||||
drawStack(ctx, stack, max, grid.width * o.sizeCell, o);
|
||||
ctx.restore();
|
||||
};
|
||||
const getCanvasWorldSize = (grid, o) => {
|
||||
const width = o.sizeCell * (grid.width + 2);
|
||||
const height = o.sizeCell * (grid.height + 4) + 30;
|
||||
return { width, height };
|
||||
};
|
||||
|
||||
// EXTERNAL MODULE: ../types/snake.ts
|
||||
var types_snake = __webpack_require__(9347);
|
||||
;// CONCATENATED MODULE: ../solver/step.ts
|
||||
|
||||
|
||||
const step = (grid, stack, snake) => {
|
||||
const x = (0,types_snake/* getHeadX */.If)(snake);
|
||||
const y = (0,types_snake/* getHeadY */.IP)(snake);
|
||||
const color = (0,types_grid/* getColor */.Lq)(grid, x, y);
|
||||
if ((0,types_grid/* isInside */.V0)(grid, x, y) && !(0,types_grid/* isEmpty */.xb)(color)) {
|
||||
stack.push(color);
|
||||
(0,types_grid/* setColorEmpty */.Dy)(grid, x, y);
|
||||
}
|
||||
};
|
||||
|
||||
// EXTERNAL MODULE: ../../node_modules/tmp/lib/tmp.js
|
||||
var tmp = __webpack_require__(6382);
|
||||
// EXTERNAL MODULE: external "gifsicle"
|
||||
var external_gifsicle_ = __webpack_require__(542);
|
||||
var external_gifsicle_default = /*#__PURE__*/__webpack_require__.n(external_gifsicle_);
|
||||
// EXTERNAL MODULE: ../../node_modules/gif-encoder-2/index.js
|
||||
var gif_encoder_2 = __webpack_require__(3561);
|
||||
var gif_encoder_2_default = /*#__PURE__*/__webpack_require__.n(gif_encoder_2);
|
||||
;// CONCATENATED MODULE: ../gif-creator/index.ts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
const withTmpDir = async (handler) => {
|
||||
const { name: dir, removeCallback: cleanUp } = tmp.dirSync({
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
try {
|
||||
return await handler(dir);
|
||||
}
|
||||
finally {
|
||||
cleanUp();
|
||||
}
|
||||
};
|
||||
const createGif = async (grid0, cells, chain, drawOptions, animationOptions) => withTmpDir(async (dir) => {
|
||||
const { width, height } = getCanvasWorldSize(grid0, drawOptions);
|
||||
const canvas = (0,external_canvas_.createCanvas)(width, height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
const grid = (0,types_grid/* copyGrid */.VJ)(grid0);
|
||||
const stack = [];
|
||||
const encoder = new (gif_encoder_2_default())(width, height, "neuquant", true);
|
||||
encoder.setRepeat(0);
|
||||
encoder.setDelay(animationOptions.frameDuration);
|
||||
encoder.start();
|
||||
for (let i = 0; i < chain.length; i += 1) {
|
||||
const snake0 = chain[i];
|
||||
const snake1 = chain[Math.min(chain.length - 1, i + 1)];
|
||||
step(grid, stack, snake0);
|
||||
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 / animationOptions.step, drawOptions);
|
||||
encoder.addFrame(ctx);
|
||||
}
|
||||
}
|
||||
const outFileName = external_path_default().join(dir, "out.gif");
|
||||
const optimizedFileName = external_path_default().join(dir, "out.optimized.gif");
|
||||
encoder.finish();
|
||||
external_fs_default().writeFileSync(outFileName, encoder.out.getData());
|
||||
(0,external_child_process_.execFileSync)((external_gifsicle_default()), [
|
||||
//
|
||||
"--optimize=3",
|
||||
"--color-method=diversity",
|
||||
"--colors=18",
|
||||
outFileName,
|
||||
["--output", optimizedFileName],
|
||||
].flat());
|
||||
return external_fs_default().readFileSync(optimizedFileName);
|
||||
});
|
||||
|
||||
|
||||
/***/ })
|
||||
|
||||
};
|
||||
;
|
||||
3889
svg-only/dist/197.index.js
vendored
Normal file
3889
svg-only/dist/197.index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
795
svg-only/dist/317.index.js
vendored
Normal file
795
svg-only/dist/317.index.js
vendored
Normal file
@@ -0,0 +1,795 @@
|
||||
"use strict";
|
||||
exports.id = 317;
|
||||
exports.ids = [317];
|
||||
exports.modules = {
|
||||
|
||||
/***/ 5317:
|
||||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
// ESM COMPAT FLAG
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
|
||||
// EXPORTS
|
||||
__webpack_require__.d(__webpack_exports__, {
|
||||
"generateContributionSnake": () => (/* binding */ generateContributionSnake)
|
||||
});
|
||||
|
||||
// EXTERNAL MODULE: ../../node_modules/node-fetch/lib/index.js
|
||||
var lib = __webpack_require__(2197);
|
||||
var lib_default = /*#__PURE__*/__webpack_require__.n(lib);
|
||||
;// CONCATENATED MODULE: ../github-user-contribution/formatParams.ts
|
||||
const formatParams = (options = {}) => {
|
||||
const sp = new URLSearchParams();
|
||||
const o = { ...options };
|
||||
if ("year" in options) {
|
||||
o.from = `${options.year}-01-01`;
|
||||
o.to = `${options.year}-12-31`;
|
||||
}
|
||||
for (const s of ["from", "to"])
|
||||
if (o[s]) {
|
||||
const value = o[s];
|
||||
if (value >= formatDate(new Date()))
|
||||
throw new Error("Cannot get contribution for a date in the future.\nPlease limit your range to the current UTC day.");
|
||||
sp.set(s, value);
|
||||
}
|
||||
return sp.toString();
|
||||
};
|
||||
const formatDate = (d) => {
|
||||
const year = d.getUTCFullYear();
|
||||
const month = d.getUTCMonth() + 1;
|
||||
const date = d.getUTCDate();
|
||||
return [
|
||||
year,
|
||||
month.toString().padStart(2, "0"),
|
||||
date.toString().padStart(2, "0"),
|
||||
].join("-");
|
||||
};
|
||||
|
||||
;// CONCATENATED MODULE: ../github-user-contribution/index.ts
|
||||
|
||||
|
||||
/**
|
||||
* get the contribution grid from a github user page
|
||||
*
|
||||
* use options.from=YYYY-MM-DD options.to=YYYY-MM-DD to get the contribution grid for a specific time range
|
||||
* or year=2019 as an alias for from=2019-01-01 to=2019-12-31
|
||||
*
|
||||
* otherwise return use the time range from today minus one year to today ( as seen in github profile page )
|
||||
*
|
||||
* @param userName github user name
|
||||
* @param options
|
||||
*
|
||||
* @example
|
||||
* getGithubUserContribution("platane", { from: "2019-01-01", to: "2019-12-31" })
|
||||
* getGithubUserContribution("platane", { year: 2019 })
|
||||
*
|
||||
*/
|
||||
const getGithubUserContribution = async (userName, options = {}) => {
|
||||
// either use github.com/users/xxxx/contributions for previous years
|
||||
// or github.com/xxxx ( which gives the latest update to today result )
|
||||
const url = "year" in options || "from" in options || "to" in options
|
||||
? `https://github.com/users/${userName}/contributions?` +
|
||||
formatParams(options)
|
||||
: `https://github.com/${userName}`;
|
||||
const res = await lib_default()(url);
|
||||
if (!res.ok)
|
||||
throw new Error(res.statusText);
|
||||
const resText = await res.text();
|
||||
return parseUserPage(resText);
|
||||
};
|
||||
const parseUserPage = (content) => {
|
||||
// take roughly the svg block
|
||||
const block = content
|
||||
.split(`class="js-calendar-graph-svg"`)[1]
|
||||
.split("</svg>")[0];
|
||||
let x = 0;
|
||||
let lastYAttribute = 0;
|
||||
const rects = Array.from(block.matchAll(/<rect[^>]*>[^<]*<\/rect>/g)).map(([m]) => {
|
||||
const date = m.match(/data-date="([^"]+)"/)[1];
|
||||
const level = +m.match(/data-level="([^"]+)"/)[1];
|
||||
const yAttribute = +m.match(/y="([^"]+)"/)[1];
|
||||
const literalCount = m.match(/(No|\d+) contributions? on/)[1];
|
||||
const count = literalCount === "No" ? 0 : +literalCount;
|
||||
if (lastYAttribute > yAttribute)
|
||||
x++;
|
||||
lastYAttribute = yAttribute;
|
||||
return { date, count, level, x, yAttribute };
|
||||
});
|
||||
const yAttributes = Array.from(new Set(rects.map((c) => c.yAttribute)).keys()).sort();
|
||||
const cells = rects.map(({ yAttribute, ...c }) => ({
|
||||
y: yAttributes.indexOf(yAttribute),
|
||||
...c,
|
||||
}));
|
||||
return cells;
|
||||
};
|
||||
|
||||
// EXTERNAL MODULE: ../types/grid.ts
|
||||
var types_grid = __webpack_require__(2881);
|
||||
;// CONCATENATED MODULE: ./userContributionToGrid.ts
|
||||
|
||||
const userContributionToGrid = (cells) => {
|
||||
const width = Math.max(0, ...cells.map((c) => c.x)) + 1;
|
||||
const height = Math.max(0, ...cells.map((c) => c.y)) + 1;
|
||||
const grid = (0,types_grid/* createEmptyGrid */.u1)(width, height);
|
||||
for (const c of cells) {
|
||||
if (c.level > 0)
|
||||
(0,types_grid/* setColor */.vk)(grid, c.x, c.y, c.level);
|
||||
else
|
||||
(0,types_grid/* setColorEmpty */.Dy)(grid, c.x, c.y);
|
||||
}
|
||||
return grid;
|
||||
};
|
||||
|
||||
;// CONCATENATED MODULE: ../types/point.ts
|
||||
const around4 = [
|
||||
{ x: 1, y: 0 },
|
||||
{ x: 0, y: -1 },
|
||||
{ x: -1, y: 0 },
|
||||
{ x: 0, y: 1 },
|
||||
];
|
||||
const pointEquals = (a, b) => a.x === b.x && a.y === b.y;
|
||||
|
||||
;// CONCATENATED MODULE: ../solver/outside.ts
|
||||
|
||||
|
||||
const createOutside = (grid, color = 0) => {
|
||||
const outside = (0,types_grid/* createEmptyGrid */.u1)(grid.width, grid.height);
|
||||
for (let x = outside.width; x--;)
|
||||
for (let y = outside.height; y--;)
|
||||
(0,types_grid/* setColor */.vk)(outside, x, y, 1);
|
||||
fillOutside(outside, grid, color);
|
||||
return outside;
|
||||
};
|
||||
const fillOutside = (outside, grid, color = 0) => {
|
||||
let changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (let x = outside.width; x--;)
|
||||
for (let y = outside.height; y--;)
|
||||
if ((0,types_grid/* getColor */.Lq)(grid, x, y) <= color &&
|
||||
!isOutside(outside, x, y) &&
|
||||
around4.some((a) => isOutside(outside, x + a.x, y + a.y))) {
|
||||
changed = true;
|
||||
(0,types_grid/* setColorEmpty */.Dy)(outside, x, y);
|
||||
}
|
||||
}
|
||||
return outside;
|
||||
};
|
||||
const isOutside = (outside, x, y) => !(0,types_grid/* isInside */.V0)(outside, x, y) || (0,types_grid/* isEmpty */.xb)((0,types_grid/* getColor */.Lq)(outside, x, y));
|
||||
|
||||
// EXTERNAL MODULE: ../types/snake.ts
|
||||
var types_snake = __webpack_require__(9347);
|
||||
;// CONCATENATED MODULE: ../solver/utils/sortPush.ts
|
||||
const sortPush = (arr, x, sortFn) => {
|
||||
let a = 0;
|
||||
let b = arr.length;
|
||||
if (arr.length === 0 || sortFn(x, arr[a]) <= 0) {
|
||||
arr.unshift(x);
|
||||
return;
|
||||
}
|
||||
while (b - a > 1) {
|
||||
const e = Math.ceil((a + b) / 2);
|
||||
const s = sortFn(x, arr[e]);
|
||||
if (s === 0)
|
||||
a = b = e;
|
||||
else if (s > 0)
|
||||
a = e;
|
||||
else
|
||||
b = e;
|
||||
}
|
||||
const e = Math.ceil((a + b) / 2);
|
||||
arr.splice(e, 0, x);
|
||||
};
|
||||
|
||||
;// CONCATENATED MODULE: ../solver/tunnel.ts
|
||||
|
||||
|
||||
/**
|
||||
* get the sequence of snake to cross the tunnel
|
||||
*/
|
||||
const getTunnelPath = (snake0, tunnel) => {
|
||||
const chain = [];
|
||||
let snake = snake0;
|
||||
for (let i = 1; i < tunnel.length; i++) {
|
||||
const dx = tunnel[i].x - (0,types_snake/* getHeadX */.If)(snake);
|
||||
const dy = tunnel[i].y - (0,types_snake/* getHeadY */.IP)(snake);
|
||||
snake = (0,types_snake/* nextSnake */.kv)(snake, dx, dy);
|
||||
chain.unshift(snake);
|
||||
}
|
||||
return chain;
|
||||
};
|
||||
/**
|
||||
* assuming the grid change and the colors got deleted, update the tunnel
|
||||
*/
|
||||
const updateTunnel = (grid, tunnel, toDelete) => {
|
||||
while (tunnel.length) {
|
||||
const { x, y } = tunnel[0];
|
||||
if (isEmptySafe(grid, x, y) ||
|
||||
toDelete.some((p) => p.x === x && p.y === y)) {
|
||||
tunnel.shift();
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
while (tunnel.length) {
|
||||
const { x, y } = tunnel[tunnel.length - 1];
|
||||
if (isEmptySafe(grid, x, y) ||
|
||||
toDelete.some((p) => p.x === x && p.y === y)) {
|
||||
tunnel.pop();
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
};
|
||||
const isEmptySafe = (grid, x, y) => !(0,types_grid/* isInside */.V0)(grid, x, y) || (0,types_grid/* isEmpty */.xb)((0,types_grid/* getColor */.Lq)(grid, x, y));
|
||||
/**
|
||||
* remove empty cell from start
|
||||
*/
|
||||
const trimTunnelStart = (grid, tunnel) => {
|
||||
while (tunnel.length) {
|
||||
const { x, y } = tunnel[0];
|
||||
if (isEmptySafe(grid, x, y))
|
||||
tunnel.shift();
|
||||
else
|
||||
break;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* remove empty cell from end
|
||||
*/
|
||||
const trimTunnelEnd = (grid, tunnel) => {
|
||||
while (tunnel.length) {
|
||||
const i = tunnel.length - 1;
|
||||
const { x, y } = tunnel[i];
|
||||
if (isEmptySafe(grid, x, y) ||
|
||||
tunnel.findIndex((p) => p.x === x && p.y === y) < i)
|
||||
tunnel.pop();
|
||||
else
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
;// CONCATENATED MODULE: ../solver/getBestTunnel.ts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const getColorSafe = (grid, x, y) => (0,types_grid/* isInside */.V0)(grid, x, y) ? (0,types_grid/* getColor */.Lq)(grid, x, y) : 0;
|
||||
const setEmptySafe = (grid, x, y) => {
|
||||
if ((0,types_grid/* isInside */.V0)(grid, x, y))
|
||||
(0,types_grid/* setColorEmpty */.Dy)(grid, x, y);
|
||||
};
|
||||
const unwrap = (m) => !m
|
||||
? []
|
||||
: [...unwrap(m.parent), { x: (0,types_snake/* getHeadX */.If)(m.snake), y: (0,types_snake/* getHeadY */.IP)(m.snake) }];
|
||||
/**
|
||||
* returns the path to reach the outside which contains the least color cell
|
||||
*/
|
||||
const getSnakeEscapePath = (grid, outside, snake0, color) => {
|
||||
const openList = [{ snake: snake0, w: 0 }];
|
||||
const closeList = [];
|
||||
while (openList[0]) {
|
||||
const o = openList.shift();
|
||||
const x = (0,types_snake/* getHeadX */.If)(o.snake);
|
||||
const y = (0,types_snake/* getHeadY */.IP)(o.snake);
|
||||
if (isOutside(outside, x, y))
|
||||
return unwrap(o);
|
||||
for (const a of around4) {
|
||||
const c = getColorSafe(grid, x + a.x, y + a.y);
|
||||
if (c <= color && !(0,types_snake/* snakeWillSelfCollide */.nJ)(o.snake, a.x, a.y)) {
|
||||
const snake = (0,types_snake/* nextSnake */.kv)(o.snake, a.x, a.y);
|
||||
if (!closeList.some((s0) => (0,types_snake/* snakeEquals */.kE)(s0, snake))) {
|
||||
const w = o.w + 1 + +(c === color) * 1000;
|
||||
sortPush(openList, { snake, w, parent: o }, (a, b) => a.w - b.w);
|
||||
closeList.push(snake);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
/**
|
||||
* compute the best tunnel to get to the cell and back to the outside ( best = less usage of <color> )
|
||||
*
|
||||
* notice that it's one of the best tunnels, more with the same score could exist
|
||||
*/
|
||||
const getBestTunnel = (grid, outside, x, y, color, snakeN) => {
|
||||
const c = { x, y };
|
||||
const snake0 = (0,types_snake/* createSnakeFromCells */.xG)(Array.from({ length: snakeN }, () => c));
|
||||
const one = getSnakeEscapePath(grid, outside, snake0, color);
|
||||
if (!one)
|
||||
return null;
|
||||
// get the position of the snake if it was going to leave the x,y cell
|
||||
const snakeICells = one.slice(0, snakeN);
|
||||
while (snakeICells.length < snakeN)
|
||||
snakeICells.push(snakeICells[snakeICells.length - 1]);
|
||||
const snakeI = (0,types_snake/* createSnakeFromCells */.xG)(snakeICells);
|
||||
// remove from the grid the colors that one eat
|
||||
const gridI = (0,types_grid/* copyGrid */.VJ)(grid);
|
||||
for (const { x, y } of one)
|
||||
setEmptySafe(gridI, x, y);
|
||||
const two = getSnakeEscapePath(gridI, outside, snakeI, color);
|
||||
if (!two)
|
||||
return null;
|
||||
one.shift();
|
||||
one.reverse();
|
||||
one.push(...two);
|
||||
trimTunnelStart(grid, one);
|
||||
trimTunnelEnd(grid, one);
|
||||
return one;
|
||||
};
|
||||
|
||||
;// CONCATENATED MODULE: ../solver/getPathTo.ts
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* starting from snake0, get to the cell x,y
|
||||
* return the snake chain (reversed)
|
||||
*/
|
||||
const getPathTo = (grid, snake0, x, y) => {
|
||||
const openList = [{ snake: snake0, w: 0 }];
|
||||
const closeList = [];
|
||||
while (openList.length) {
|
||||
const c = openList.shift();
|
||||
const cx = (0,types_snake/* getHeadX */.If)(c.snake);
|
||||
const cy = (0,types_snake/* getHeadY */.IP)(c.snake);
|
||||
for (let i = 0; i < around4.length; i++) {
|
||||
const { x: dx, y: dy } = around4[i];
|
||||
const nx = cx + dx;
|
||||
const ny = cy + dy;
|
||||
if (nx === x && ny === y) {
|
||||
// unwrap
|
||||
const path = [(0,types_snake/* nextSnake */.kv)(c.snake, dx, dy)];
|
||||
let e = c;
|
||||
while (e.parent) {
|
||||
path.push(e.snake);
|
||||
e = e.parent;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
if ((0,types_grid/* isInsideLarge */.HJ)(grid, 2, nx, ny) &&
|
||||
!(0,types_snake/* snakeWillSelfCollide */.nJ)(c.snake, dx, dy) &&
|
||||
(!(0,types_grid/* isInside */.V0)(grid, nx, ny) || (0,types_grid/* isEmpty */.xb)((0,types_grid/* getColor */.Lq)(grid, nx, ny)))) {
|
||||
const nsnake = (0,types_snake/* nextSnake */.kv)(c.snake, dx, dy);
|
||||
if (!closeList.some((s) => (0,types_snake/* snakeEquals */.kE)(nsnake, s))) {
|
||||
const w = c.w + 1;
|
||||
const h = Math.abs(nx - x) + Math.abs(ny - y);
|
||||
const f = w + h;
|
||||
const o = { snake: nsnake, parent: c, w, h, f };
|
||||
sortPush(openList, o, (a, b) => a.f - b.f);
|
||||
closeList.push(nsnake);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
;// CONCATENATED MODULE: ../solver/clearResidualColoredLayer.ts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const clearResidualColoredLayer = (grid, outside, snake0, color) => {
|
||||
const snakeN = (0,types_snake/* getSnakeLength */.JJ)(snake0);
|
||||
const tunnels = getTunnellablePoints(grid, outside, snakeN, color);
|
||||
// sort
|
||||
tunnels.sort((a, b) => b.priority - a.priority);
|
||||
const chain = [snake0];
|
||||
while (tunnels.length) {
|
||||
// get the best next tunnel
|
||||
let t = getNextTunnel(tunnels, chain[0]);
|
||||
// goes to the start of the tunnel
|
||||
chain.unshift(...getPathTo(grid, chain[0], t[0].x, t[0].y));
|
||||
// goes to the end of the tunnel
|
||||
chain.unshift(...getTunnelPath(chain[0], t));
|
||||
// update grid
|
||||
for (const { x, y } of t)
|
||||
clearResidualColoredLayer_setEmptySafe(grid, x, y);
|
||||
// update outside
|
||||
fillOutside(outside, grid);
|
||||
// update tunnels
|
||||
for (let i = tunnels.length; i--;)
|
||||
if ((0,types_grid/* isEmpty */.xb)((0,types_grid/* getColor */.Lq)(grid, tunnels[i].x, tunnels[i].y)))
|
||||
tunnels.splice(i, 1);
|
||||
else {
|
||||
const t = tunnels[i];
|
||||
const tunnel = getBestTunnel(grid, outside, t.x, t.y, color, snakeN);
|
||||
if (!tunnel)
|
||||
tunnels.splice(i, 1);
|
||||
else {
|
||||
t.tunnel = tunnel;
|
||||
t.priority = getPriority(grid, color, tunnel);
|
||||
}
|
||||
}
|
||||
// re-sort
|
||||
tunnels.sort((a, b) => b.priority - a.priority);
|
||||
}
|
||||
chain.pop();
|
||||
return chain;
|
||||
};
|
||||
const getNextTunnel = (ts, snake) => {
|
||||
let minDistance = Infinity;
|
||||
let closestTunnel = null;
|
||||
const x = (0,types_snake/* getHeadX */.If)(snake);
|
||||
const y = (0,types_snake/* getHeadY */.IP)(snake);
|
||||
const priority = ts[0].priority;
|
||||
for (let i = 0; ts[i] && ts[i].priority === priority; i++) {
|
||||
const t = ts[i].tunnel;
|
||||
const d = distanceSq(t[0].x, t[0].y, x, y);
|
||||
if (d < minDistance) {
|
||||
minDistance = d;
|
||||
closestTunnel = t;
|
||||
}
|
||||
}
|
||||
return closestTunnel;
|
||||
};
|
||||
/**
|
||||
* get all the tunnels for all the cells accessible
|
||||
*/
|
||||
const getTunnellablePoints = (grid, outside, snakeN, color) => {
|
||||
const points = [];
|
||||
for (let x = grid.width; x--;)
|
||||
for (let y = grid.height; y--;) {
|
||||
const c = (0,types_grid/* getColor */.Lq)(grid, x, y);
|
||||
if (!(0,types_grid/* isEmpty */.xb)(c) && c < color) {
|
||||
const tunnel = getBestTunnel(grid, outside, x, y, color, snakeN);
|
||||
if (tunnel) {
|
||||
const priority = getPriority(grid, color, tunnel);
|
||||
points.push({ x, y, priority, tunnel });
|
||||
}
|
||||
}
|
||||
}
|
||||
return points;
|
||||
};
|
||||
/**
|
||||
* get the score of the tunnel
|
||||
* prioritize tunnel with maximum color smaller than <color> and with minimum <color>
|
||||
* with some tweaks
|
||||
*/
|
||||
const getPriority = (grid, color, tunnel) => {
|
||||
let nColor = 0;
|
||||
let nLess = 0;
|
||||
for (let i = 0; i < tunnel.length; i++) {
|
||||
const { x, y } = tunnel[i];
|
||||
const c = clearResidualColoredLayer_getColorSafe(grid, x, y);
|
||||
if (!(0,types_grid/* isEmpty */.xb)(c) && i === tunnel.findIndex((p) => p.x === x && p.y === y)) {
|
||||
if (c === color)
|
||||
nColor += 1;
|
||||
else
|
||||
nLess += color - c;
|
||||
}
|
||||
}
|
||||
if (nColor === 0)
|
||||
return 99999;
|
||||
return nLess / nColor;
|
||||
};
|
||||
const distanceSq = (ax, ay, bx, by) => (ax - bx) ** 2 + (ay - by) ** 2;
|
||||
const clearResidualColoredLayer_getColorSafe = (grid, x, y) => (0,types_grid/* isInside */.V0)(grid, x, y) ? (0,types_grid/* getColor */.Lq)(grid, x, y) : 0;
|
||||
const clearResidualColoredLayer_setEmptySafe = (grid, x, y) => {
|
||||
if ((0,types_grid/* isInside */.V0)(grid, x, y))
|
||||
(0,types_grid/* setColorEmpty */.Dy)(grid, x, y);
|
||||
};
|
||||
|
||||
;// CONCATENATED MODULE: ../solver/clearCleanColoredLayer.ts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const clearCleanColoredLayer = (grid, outside, snake0, color) => {
|
||||
const snakeN = (0,types_snake/* getSnakeLength */.JJ)(snake0);
|
||||
const points = clearCleanColoredLayer_getTunnellablePoints(grid, outside, snakeN, color);
|
||||
const chain = [snake0];
|
||||
while (points.length) {
|
||||
const path = getPathToNextPoint(grid, chain[0], color, points);
|
||||
path.pop();
|
||||
for (const snake of path)
|
||||
clearCleanColoredLayer_setEmptySafe(grid, (0,types_snake/* getHeadX */.If)(snake), (0,types_snake/* getHeadY */.IP)(snake));
|
||||
chain.unshift(...path);
|
||||
}
|
||||
fillOutside(outside, grid);
|
||||
chain.pop();
|
||||
return chain;
|
||||
};
|
||||
const clearCleanColoredLayer_unwrap = (m) => !m ? [] : [m.snake, ...clearCleanColoredLayer_unwrap(m.parent)];
|
||||
const getPathToNextPoint = (grid, snake0, color, points) => {
|
||||
const closeList = [];
|
||||
const openList = [{ snake: snake0 }];
|
||||
while (openList.length) {
|
||||
const o = openList.shift();
|
||||
const x = (0,types_snake/* getHeadX */.If)(o.snake);
|
||||
const y = (0,types_snake/* getHeadY */.IP)(o.snake);
|
||||
const i = points.findIndex((p) => p.x === x && p.y === y);
|
||||
if (i >= 0) {
|
||||
points.splice(i, 1);
|
||||
return clearCleanColoredLayer_unwrap(o);
|
||||
}
|
||||
for (const { x: dx, y: dy } of around4) {
|
||||
if ((0,types_grid/* isInsideLarge */.HJ)(grid, 2, x + dx, y + dy) &&
|
||||
!(0,types_snake/* snakeWillSelfCollide */.nJ)(o.snake, dx, dy) &&
|
||||
clearCleanColoredLayer_getColorSafe(grid, x + dx, y + dy) <= color) {
|
||||
const snake = (0,types_snake/* nextSnake */.kv)(o.snake, dx, dy);
|
||||
if (!closeList.some((s0) => (0,types_snake/* snakeEquals */.kE)(s0, snake))) {
|
||||
closeList.push(snake);
|
||||
openList.push({ snake, parent: o });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* get all cells that are tunnellable
|
||||
*/
|
||||
const clearCleanColoredLayer_getTunnellablePoints = (grid, outside, snakeN, color) => {
|
||||
const points = [];
|
||||
for (let x = grid.width; x--;)
|
||||
for (let y = grid.height; y--;) {
|
||||
const c = (0,types_grid/* getColor */.Lq)(grid, x, y);
|
||||
if (!(0,types_grid/* isEmpty */.xb)(c) &&
|
||||
c <= color &&
|
||||
!points.some((p) => p.x === x && p.y === y)) {
|
||||
const tunnel = getBestTunnel(grid, outside, x, y, color, snakeN);
|
||||
if (tunnel)
|
||||
for (const p of tunnel)
|
||||
if (!clearCleanColoredLayer_isEmptySafe(grid, p.x, p.y))
|
||||
points.push(p);
|
||||
}
|
||||
}
|
||||
return points;
|
||||
};
|
||||
const clearCleanColoredLayer_getColorSafe = (grid, x, y) => (0,types_grid/* isInside */.V0)(grid, x, y) ? (0,types_grid/* getColor */.Lq)(grid, x, y) : 0;
|
||||
const clearCleanColoredLayer_setEmptySafe = (grid, x, y) => {
|
||||
if ((0,types_grid/* isInside */.V0)(grid, x, y))
|
||||
(0,types_grid/* setColorEmpty */.Dy)(grid, x, y);
|
||||
};
|
||||
const clearCleanColoredLayer_isEmptySafe = (grid, x, y) => !(0,types_grid/* isInside */.V0)(grid, x, y) && (0,types_grid/* isEmpty */.xb)((0,types_grid/* getColor */.Lq)(grid, x, y));
|
||||
|
||||
;// CONCATENATED MODULE: ../solver/getBestRoute.ts
|
||||
|
||||
|
||||
|
||||
|
||||
const getBestRoute = (grid0, snake0) => {
|
||||
const grid = (0,types_grid/* copyGrid */.VJ)(grid0);
|
||||
const outside = createOutside(grid);
|
||||
const chain = [snake0];
|
||||
for (const color of extractColors(grid)) {
|
||||
if (color > 1)
|
||||
chain.unshift(...clearResidualColoredLayer(grid, outside, chain[0], color));
|
||||
chain.unshift(...clearCleanColoredLayer(grid, outside, chain[0], color));
|
||||
}
|
||||
return chain.reverse();
|
||||
};
|
||||
const extractColors = (grid) => {
|
||||
// @ts-ignore
|
||||
let maxColor = Math.max(...grid.data);
|
||||
return Array.from({ length: maxColor }, (_, i) => (i + 1));
|
||||
};
|
||||
|
||||
;// CONCATENATED MODULE: ../types/__fixtures__/snake.ts
|
||||
|
||||
const create = (length) => (0,types_snake/* createSnakeFromCells */.xG)(Array.from({ length }, (_, i) => ({ x: i, y: -1 })));
|
||||
const snake1 = create(1);
|
||||
const snake3 = create(3);
|
||||
const snake4 = create(4);
|
||||
const snake5 = create(5);
|
||||
const snake9 = create(9);
|
||||
|
||||
;// CONCATENATED MODULE: ../solver/getPathToPose.ts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const getPathToPose_isEmptySafe = (grid, x, y) => !(0,types_grid/* isInside */.V0)(grid, x, y) || (0,types_grid/* isEmpty */.xb)((0,types_grid/* getColor */.Lq)(grid, x, y));
|
||||
const getPathToPose = (snake0, target, grid) => {
|
||||
if ((0,types_snake/* snakeEquals */.kE)(snake0, target))
|
||||
return [];
|
||||
const targetCells = (0,types_snake/* snakeToCells */.Ks)(target).reverse();
|
||||
const snakeN = (0,types_snake/* getSnakeLength */.JJ)(snake0);
|
||||
const box = {
|
||||
min: {
|
||||
x: Math.min((0,types_snake/* getHeadX */.If)(snake0), (0,types_snake/* getHeadX */.If)(target)) - snakeN - 1,
|
||||
y: Math.min((0,types_snake/* getHeadY */.IP)(snake0), (0,types_snake/* getHeadY */.IP)(target)) - snakeN - 1,
|
||||
},
|
||||
max: {
|
||||
x: Math.max((0,types_snake/* getHeadX */.If)(snake0), (0,types_snake/* getHeadX */.If)(target)) + snakeN + 1,
|
||||
y: Math.max((0,types_snake/* getHeadY */.IP)(snake0), (0,types_snake/* getHeadY */.IP)(target)) + snakeN + 1,
|
||||
},
|
||||
};
|
||||
const [t0, ...forbidden] = targetCells;
|
||||
forbidden.slice(0, 3);
|
||||
const openList = [{ snake: snake0, w: 0 }];
|
||||
const closeList = [];
|
||||
while (openList.length) {
|
||||
const o = openList.shift();
|
||||
const x = (0,types_snake/* getHeadX */.If)(o.snake);
|
||||
const y = (0,types_snake/* getHeadY */.IP)(o.snake);
|
||||
if (x === t0.x && y === t0.y) {
|
||||
const path = [];
|
||||
let e = o;
|
||||
while (e) {
|
||||
path.push(e.snake);
|
||||
e = e.parent;
|
||||
}
|
||||
path.unshift(...getTunnelPath(path[0], targetCells));
|
||||
path.pop();
|
||||
path.reverse();
|
||||
return path;
|
||||
}
|
||||
for (let i = 0; i < around4.length; i++) {
|
||||
const { x: dx, y: dy } = around4[i];
|
||||
const nx = x + dx;
|
||||
const ny = y + dy;
|
||||
if (!(0,types_snake/* snakeWillSelfCollide */.nJ)(o.snake, dx, dy) &&
|
||||
(!grid || getPathToPose_isEmptySafe(grid, nx, ny)) &&
|
||||
(grid
|
||||
? (0,types_grid/* isInsideLarge */.HJ)(grid, 2, nx, ny)
|
||||
: box.min.x <= nx &&
|
||||
nx <= box.max.x &&
|
||||
box.min.y <= ny &&
|
||||
ny <= box.max.y) &&
|
||||
!forbidden.some((p) => p.x === nx && p.y === ny)) {
|
||||
const snake = (0,types_snake/* nextSnake */.kv)(o.snake, dx, dy);
|
||||
if (!closeList.some((s) => (0,types_snake/* snakeEquals */.kE)(snake, s))) {
|
||||
const w = o.w + 1;
|
||||
const h = Math.abs(nx - x) + Math.abs(ny - y);
|
||||
const f = w + h;
|
||||
sortPush(openList, { f, w, snake, parent: o }, (a, b) => a.f - b.f);
|
||||
closeList.push(snake);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
;// CONCATENATED MODULE: ./generateContributionSnake.ts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const generateContributionSnake = async (userName, outputs) => {
|
||||
console.log("🎣 fetching github user contribution");
|
||||
const cells = await getGithubUserContribution(userName);
|
||||
const grid = userContributionToGrid(cells);
|
||||
const snake = snake4;
|
||||
console.log("📡 computing best route");
|
||||
const chain = getBestRoute(grid, snake);
|
||||
chain.push(...getPathToPose(chain.slice(-1)[0], snake));
|
||||
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 __webpack_require__.e(/* import() */ 340).then(__webpack_require__.bind(__webpack_require__, 8340));
|
||||
return createSvg(grid, cells, chain, drawOptions, animationOptions);
|
||||
}
|
||||
case "gif": {
|
||||
console.log(`📹 creating gif (outputs[${i}])`);
|
||||
const { createGif } = await Promise.all(/* import() */[__webpack_require__.e(371), __webpack_require__.e(142)]).then(__webpack_require__.bind(__webpack_require__, 7142));
|
||||
return await createGif(grid, cells, chain, drawOptions, animationOptions);
|
||||
}
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 2881:
|
||||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
||||
/* harmony export */ "V0": () => (/* binding */ isInside),
|
||||
/* harmony export */ "HJ": () => (/* binding */ isInsideLarge),
|
||||
/* harmony export */ "VJ": () => (/* binding */ copyGrid),
|
||||
/* harmony export */ "Lq": () => (/* binding */ getColor),
|
||||
/* harmony export */ "xb": () => (/* binding */ isEmpty),
|
||||
/* harmony export */ "vk": () => (/* binding */ setColor),
|
||||
/* harmony export */ "Dy": () => (/* binding */ setColorEmpty),
|
||||
/* harmony export */ "u1": () => (/* binding */ createEmptyGrid)
|
||||
/* harmony export */ });
|
||||
/* unused harmony exports isGridEmpty, gridEquals */
|
||||
const isInside = (grid, x, y) => x >= 0 && y >= 0 && x < grid.width && y < grid.height;
|
||||
const isInsideLarge = (grid, m, x, y) => x >= -m && y >= -m && x < grid.width + m && y < grid.height + m;
|
||||
const copyGrid = ({ width, height, data }) => ({
|
||||
width,
|
||||
height,
|
||||
data: Uint8Array.from(data),
|
||||
});
|
||||
const getIndex = (grid, x, y) => x * grid.height + y;
|
||||
const getColor = (grid, x, y) => grid.data[getIndex(grid, x, y)];
|
||||
const isEmpty = (color) => color === 0;
|
||||
const setColor = (grid, x, y, color) => {
|
||||
grid.data[getIndex(grid, x, y)] = color || 0;
|
||||
};
|
||||
const setColorEmpty = (grid, x, y) => {
|
||||
setColor(grid, x, y, 0);
|
||||
};
|
||||
/**
|
||||
* return true if the grid is empty
|
||||
*/
|
||||
const isGridEmpty = (grid) => grid.data.every((x) => x === 0);
|
||||
const gridEquals = (a, b) => a.data.every((_, i) => a.data[i] === b.data[i]);
|
||||
const createEmptyGrid = (width, height) => ({
|
||||
width,
|
||||
height,
|
||||
data: new Uint8Array(width * height),
|
||||
});
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 9347:
|
||||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
||||
/* harmony export */ "If": () => (/* binding */ getHeadX),
|
||||
/* harmony export */ "IP": () => (/* binding */ getHeadY),
|
||||
/* harmony export */ "JJ": () => (/* binding */ getSnakeLength),
|
||||
/* harmony export */ "kE": () => (/* binding */ snakeEquals),
|
||||
/* harmony export */ "kv": () => (/* binding */ nextSnake),
|
||||
/* harmony export */ "nJ": () => (/* binding */ snakeWillSelfCollide),
|
||||
/* harmony export */ "Ks": () => (/* binding */ snakeToCells),
|
||||
/* harmony export */ "xG": () => (/* binding */ createSnakeFromCells)
|
||||
/* harmony export */ });
|
||||
/* unused harmony export copySnake */
|
||||
const getHeadX = (snake) => snake[0] - 2;
|
||||
const getHeadY = (snake) => snake[1] - 2;
|
||||
const getSnakeLength = (snake) => snake.length / 2;
|
||||
const copySnake = (snake) => snake.slice();
|
||||
const snakeEquals = (a, b) => {
|
||||
for (let i = 0; i < a.length; i++)
|
||||
if (a[i] !== b[i])
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
* return a copy of the next snake, considering that dx, dy is the direction
|
||||
*/
|
||||
const nextSnake = (snake, dx, dy) => {
|
||||
const copy = new Uint8Array(snake.length);
|
||||
for (let i = 2; i < snake.length; i++)
|
||||
copy[i] = snake[i - 2];
|
||||
copy[0] = snake[0] + dx;
|
||||
copy[1] = snake[1] + dy;
|
||||
return copy;
|
||||
};
|
||||
/**
|
||||
* return true if the next snake will collide with itself
|
||||
*/
|
||||
const snakeWillSelfCollide = (snake, dx, dy) => {
|
||||
const nx = snake[0] + dx;
|
||||
const ny = snake[1] + dy;
|
||||
for (let i = 2; i < snake.length - 2; i += 2)
|
||||
if (snake[i + 0] === nx && snake[i + 1] === ny)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
const snakeToCells = (snake) => Array.from({ length: snake.length / 2 }, (_, i) => ({
|
||||
x: snake[i * 2 + 0] - 2,
|
||||
y: snake[i * 2 + 1] - 2,
|
||||
}));
|
||||
const createSnakeFromCells = (points) => {
|
||||
const snake = new Uint8Array(points.length * 2);
|
||||
for (let i = points.length; i--;) {
|
||||
snake[i * 2 + 0] = points[i].x + 2;
|
||||
snake[i * 2 + 1] = points[i].y + 2;
|
||||
}
|
||||
return snake;
|
||||
};
|
||||
|
||||
|
||||
/***/ })
|
||||
|
||||
};
|
||||
;
|
||||
325
svg-only/dist/340.index.js
vendored
Normal file
325
svg-only/dist/340.index.js
vendored
Normal file
@@ -0,0 +1,325 @@
|
||||
"use strict";
|
||||
exports.id = 340;
|
||||
exports.ids = [340];
|
||||
exports.modules = {
|
||||
|
||||
/***/ 8340:
|
||||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
// ESM COMPAT FLAG
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
|
||||
// EXPORTS
|
||||
__webpack_require__.d(__webpack_exports__, {
|
||||
"createSvg": () => (/* binding */ createSvg)
|
||||
});
|
||||
|
||||
// EXTERNAL MODULE: ../types/grid.ts
|
||||
var types_grid = __webpack_require__(2881);
|
||||
// EXTERNAL MODULE: ../types/snake.ts
|
||||
var types_snake = __webpack_require__(9347);
|
||||
;// CONCATENATED MODULE: ../svg-creator/xml-utils.ts
|
||||
const h = (element, attributes) => `<${element} ${toAttribute(attributes)}/>`;
|
||||
const toAttribute = (o) => Object.entries(o)
|
||||
.filter(([, value]) => value !== null)
|
||||
.map(([name, value]) => `${name}="${value}"`)
|
||||
.join(" ");
|
||||
|
||||
;// CONCATENATED MODULE: ../svg-creator/css-utils.ts
|
||||
const percent = (x) => parseFloat((x * 100).toFixed(2)).toString() + "%";
|
||||
const mergeKeyFrames = (keyframes) => {
|
||||
const s = new Map();
|
||||
for (const { t, style } of keyframes) {
|
||||
s.set(style, [...(s.get(style) ?? []), t]);
|
||||
}
|
||||
return Array.from(s.entries())
|
||||
.map(([style, ts]) => ({ style, ts }))
|
||||
.sort((a, b) => a.ts[0] - b.ts[0]);
|
||||
};
|
||||
/**
|
||||
* generate the keyframe animation from a list of keyframe
|
||||
*/
|
||||
const createAnimation = (name, keyframes) => `@keyframes ${name}{` +
|
||||
mergeKeyFrames(keyframes)
|
||||
.map(({ style, ts }) => ts.map(percent).join(",") + `{${style}}`)
|
||||
.join("") +
|
||||
"}";
|
||||
/**
|
||||
* remove white spaces
|
||||
*/
|
||||
const minifyCss = (css) => css
|
||||
.replace(/\s+/g, " ")
|
||||
.replace(/.\s+[,;:{}()]/g, (a) => a.replace(/\s+/g, ""))
|
||||
.replace(/[,;:{}()]\s+./g, (a) => a.replace(/\s+/g, ""))
|
||||
.replace(/.\s+[,;:{}()]/g, (a) => a.replace(/\s+/g, ""))
|
||||
.replace(/[,;:{}()]\s+./g, (a) => a.replace(/\s+/g, ""))
|
||||
.replace(/\;\s*\}/g, "}")
|
||||
.trim();
|
||||
|
||||
;// CONCATENATED MODULE: ../svg-creator/snake.ts
|
||||
|
||||
|
||||
|
||||
const lerp = (k, a, b) => (1 - k) * a + k * b;
|
||||
const createSnake = (chain, { sizeCell, sizeDot }, duration) => {
|
||||
const snakeN = chain[0] ? (0,types_snake/* getSnakeLength */.JJ)(chain[0]) : 0;
|
||||
const snakeParts = Array.from({ length: snakeN }, () => []);
|
||||
for (const snake of chain) {
|
||||
const cells = (0,types_snake/* snakeToCells */.Ks)(snake);
|
||||
for (let i = cells.length; i--;)
|
||||
snakeParts[i].push(cells[i]);
|
||||
}
|
||||
const svgElements = snakeParts.map((_, i, { length }) => {
|
||||
// compute snake part size
|
||||
const dMin = sizeDot * 0.8;
|
||||
const dMax = sizeCell * 0.9;
|
||||
const iMax = Math.min(4, length);
|
||||
const u = (1 - Math.min(i, iMax) / iMax) ** 2;
|
||||
const s = lerp(u, dMin, dMax);
|
||||
const m = (sizeCell - s) / 2;
|
||||
const r = Math.min(4.5, (4 * s) / sizeDot);
|
||||
return h("rect", {
|
||||
class: `s s${i}`,
|
||||
x: m.toFixed(1),
|
||||
y: m.toFixed(1),
|
||||
width: s.toFixed(1),
|
||||
height: s.toFixed(1),
|
||||
rx: r.toFixed(1),
|
||||
ry: r.toFixed(1),
|
||||
});
|
||||
});
|
||||
const transform = ({ x, y }) => `transform:translate(${x * sizeCell}px,${y * sizeCell}px)`;
|
||||
const styles = [
|
||||
`.s{
|
||||
shape-rendering: geometricPrecision;
|
||||
fill: var(--cs);
|
||||
animation: none linear ${duration}ms infinite
|
||||
}`,
|
||||
...snakeParts.map((positions, i) => {
|
||||
const id = `s${i}`;
|
||||
const animationName = id;
|
||||
const keyframes = removeInterpolatedPositions(positions.map((tr, i, { length }) => ({ ...tr, t: i / length }))).map(({ t, ...p }) => ({ t, style: transform(p) }));
|
||||
return [
|
||||
createAnimation(animationName, keyframes),
|
||||
`.s.${id}{
|
||||
${transform(positions[0])};
|
||||
animation-name: ${animationName}
|
||||
}`,
|
||||
];
|
||||
}),
|
||||
].flat();
|
||||
return { svgElements, styles };
|
||||
};
|
||||
const removeInterpolatedPositions = (arr) => arr.filter((u, i, arr) => {
|
||||
if (i - 1 < 0 || i + 1 >= arr.length)
|
||||
return true;
|
||||
const a = arr[i - 1];
|
||||
const b = arr[i + 1];
|
||||
const ex = (a.x + b.x) / 2;
|
||||
const ey = (a.y + b.y) / 2;
|
||||
// return true;
|
||||
return !(Math.abs(ex - u.x) < 0.01 && Math.abs(ey - u.y) < 0.01);
|
||||
});
|
||||
|
||||
;// CONCATENATED MODULE: ../svg-creator/grid.ts
|
||||
|
||||
|
||||
const createGrid = (cells, { sizeDotBorderRadius, sizeDot, sizeCell }, duration) => {
|
||||
const svgElements = [];
|
||||
const styles = [
|
||||
`.c{
|
||||
shape-rendering: geometricPrecision;
|
||||
fill: var(--ce);
|
||||
stroke-width: 1px;
|
||||
stroke: var(--cb);
|
||||
animation: none ${duration}ms linear infinite;
|
||||
width: ${sizeDot}px;
|
||||
height: ${sizeDot}px;
|
||||
}`,
|
||||
];
|
||||
let i = 0;
|
||||
for (const { x, y, color, t } of cells) {
|
||||
const id = t && "c" + (i++).toString(36);
|
||||
const m = (sizeCell - sizeDot) / 2;
|
||||
if (t !== null && id) {
|
||||
const animationName = id;
|
||||
styles.push(createAnimation(animationName, [
|
||||
{ t: t - 0.0001, style: `fill:var(--c${color})` },
|
||||
{ t: t + 0.0001, style: `fill:var(--ce)` },
|
||||
{ t: 1, style: `fill:var(--ce)` },
|
||||
]), `.c.${id}{
|
||||
fill: var(--c${color});
|
||||
animation-name: ${animationName}
|
||||
}`);
|
||||
}
|
||||
svgElements.push(h("rect", {
|
||||
class: ["c", id].filter(Boolean).join(" "),
|
||||
x: x * sizeCell + m,
|
||||
y: y * sizeCell + m,
|
||||
rx: sizeDotBorderRadius,
|
||||
ry: sizeDotBorderRadius,
|
||||
}));
|
||||
}
|
||||
return { svgElements, styles };
|
||||
};
|
||||
|
||||
;// CONCATENATED MODULE: ../svg-creator/stack.ts
|
||||
|
||||
|
||||
const createStack = (cells, { sizeDot }, width, y, duration) => {
|
||||
const svgElements = [];
|
||||
const styles = [
|
||||
`.u{
|
||||
transform-origin: 0 0;
|
||||
transform: scale(0,1);
|
||||
animation: none linear ${duration}ms infinite;
|
||||
}`,
|
||||
];
|
||||
const stack = cells
|
||||
.slice()
|
||||
.filter((a) => a.t !== null)
|
||||
.sort((a, b) => a.t - b.t);
|
||||
const blocks = [];
|
||||
stack.forEach(({ color, t }) => {
|
||||
const latest = blocks[blocks.length - 1];
|
||||
if (latest?.color === color)
|
||||
latest.ts.push(t);
|
||||
else
|
||||
blocks.push({ color, ts: [t] });
|
||||
});
|
||||
const m = width / stack.length;
|
||||
let i = 0;
|
||||
let nx = 0;
|
||||
for (const { color, ts } of blocks) {
|
||||
const id = "u" + (i++).toString(36);
|
||||
const animationName = id;
|
||||
const x = (nx * m).toFixed(1);
|
||||
nx += ts.length;
|
||||
svgElements.push(h("rect", {
|
||||
class: `u ${id}`,
|
||||
height: sizeDot,
|
||||
width: (ts.length * m + 0.6).toFixed(1),
|
||||
x,
|
||||
y,
|
||||
}));
|
||||
styles.push(createAnimation(animationName, [
|
||||
...ts
|
||||
.map((t, i, { length }) => [
|
||||
{ scale: i / length, t: t - 0.0001 },
|
||||
{ scale: (i + 1) / length, t: t + 0.0001 },
|
||||
])
|
||||
.flat(),
|
||||
{ scale: 1, t: 1 },
|
||||
].map(({ scale, t }) => ({
|
||||
t,
|
||||
style: `transform:scale(${scale.toFixed(3)},1)`,
|
||||
}))), `.u.${id} {
|
||||
fill: var(--c${color});
|
||||
animation-name: ${animationName};
|
||||
transform-origin: ${x}px 0
|
||||
}
|
||||
`);
|
||||
}
|
||||
return { svgElements, styles };
|
||||
};
|
||||
|
||||
;// CONCATENATED MODULE: ../svg-creator/index.ts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const getCellsFromGrid = ({ width, height }) => Array.from({ length: width }, (_, x) => Array.from({ length: height }, (_, y) => ({ x, y }))).flat();
|
||||
const createLivingCells = (grid0, chain, cells) => {
|
||||
const livingCells = (cells ?? getCellsFromGrid(grid0)).map(({ x, y }) => ({
|
||||
x,
|
||||
y,
|
||||
t: null,
|
||||
color: (0,types_grid/* getColor */.Lq)(grid0, x, y),
|
||||
}));
|
||||
const grid = (0,types_grid/* copyGrid */.VJ)(grid0);
|
||||
for (let i = 0; i < chain.length; i++) {
|
||||
const snake = chain[i];
|
||||
const x = (0,types_snake/* getHeadX */.If)(snake);
|
||||
const y = (0,types_snake/* getHeadY */.IP)(snake);
|
||||
if ((0,types_grid/* isInside */.V0)(grid, x, y) && !(0,types_grid/* isEmpty */.xb)((0,types_grid/* getColor */.Lq)(grid, x, y))) {
|
||||
(0,types_grid/* setColorEmpty */.Dy)(grid, x, y);
|
||||
const cell = livingCells.find((c) => c.x === x && c.y === y);
|
||||
cell.t = i / chain.length;
|
||||
}
|
||||
}
|
||||
return livingCells;
|
||||
};
|
||||
const createSvg = (grid, cells, chain, drawOptions, animationOptions) => {
|
||||
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) => minifyCss(css);
|
||||
const optimizeSvg = (svg) => svg;
|
||||
const generateColorVar = (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("")}
|
||||
}
|
||||
}
|
||||
`
|
||||
: "");
|
||||
|
||||
|
||||
/***/ })
|
||||
|
||||
};
|
||||
;
|
||||
5865
svg-only/dist/371.index.js
vendored
Normal file
5865
svg-only/dist/371.index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
30640
svg-only/dist/index.js
vendored
30640
svg-only/dist/index.js
vendored
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user