🚀 interactive demo
This commit is contained in:
@@ -4,7 +4,9 @@ const container = document.createElement("div");
|
||||
container.style.fontFamily = "helvetica";
|
||||
document.body.appendChild(container);
|
||||
|
||||
for (const demo of require("./demo.json").filter((x: any) => x !== "index")) {
|
||||
for (const demo of require("./demo.json").filter(
|
||||
(x: any) => !["index", "interactive"].includes(x)
|
||||
)) {
|
||||
const title = document.createElement("h1");
|
||||
title.innerText = demo;
|
||||
|
||||
|
||||
188
packages/demo/demo.interactive.ts
Normal file
188
packages/demo/demo.interactive.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import { getBestRoute } from "@snk/compute/getBestRoute";
|
||||
import { Color, copyGrid, Grid } from "@snk/types/grid";
|
||||
import { step } from "@snk/compute/step";
|
||||
import { isStableAndBound, stepSpring } from "./springUtils";
|
||||
import { Res } from "@snk/github-user-contribution";
|
||||
import { Snake } from "@snk/types/snake";
|
||||
import {
|
||||
drawLerpWorld,
|
||||
getCanvasWorldSize,
|
||||
Options,
|
||||
} from "@snk/draw/drawWorld";
|
||||
import { userContributionToGrid } from "../action/userContributionToGrid";
|
||||
import { snake3 } from "@snk/types/__fixtures__/snake";
|
||||
|
||||
const createForm = ({ onSubmit }: { onSubmit: (s: string) => void }) => {
|
||||
const form = document.createElement("form");
|
||||
form.style.position = "relative";
|
||||
form.style.display = "flex";
|
||||
form.style.flexDirection = "row";
|
||||
const input = document.createElement("input");
|
||||
input.style.padding = "16px";
|
||||
input.placeholder = "github user";
|
||||
const submit = document.createElement("button");
|
||||
submit.style.padding = "16px";
|
||||
submit.type = "submit";
|
||||
submit.innerText = "ok";
|
||||
|
||||
const label = document.createElement("label");
|
||||
label.innerText = "loading ...";
|
||||
label.style.position = "absolute";
|
||||
label.style.textAlign = "center";
|
||||
label.style.top = "60px";
|
||||
label.style.left = "0";
|
||||
label.style.right = "0";
|
||||
|
||||
form.appendChild(input);
|
||||
form.appendChild(submit);
|
||||
document.body.appendChild(form);
|
||||
|
||||
form.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
onSubmit(input.value);
|
||||
|
||||
input.disabled = true;
|
||||
submit.disabled = true;
|
||||
form.appendChild(label);
|
||||
|
||||
const sp = new URLSearchParams(window.location.search);
|
||||
sp.set("userName", input.value);
|
||||
window.location.replace(window.location.pathname + "?" + sp.toString());
|
||||
});
|
||||
|
||||
//
|
||||
// dispose
|
||||
const dispose = () => {
|
||||
document.body.removeChild(form);
|
||||
};
|
||||
|
||||
//
|
||||
// bypass when userName in url
|
||||
const u = new URLSearchParams(window.location.search).get("userName");
|
||||
if (u) {
|
||||
input.value = u;
|
||||
onSubmit(u);
|
||||
|
||||
input.disabled = true;
|
||||
submit.disabled = true;
|
||||
form.appendChild(label);
|
||||
}
|
||||
|
||||
return { dispose };
|
||||
};
|
||||
|
||||
const clamp = (x: number, a: number, b: number) => Math.max(a, Math.min(b, x));
|
||||
|
||||
const createViewer = ({
|
||||
grid0,
|
||||
chain,
|
||||
drawOptions,
|
||||
}: {
|
||||
grid0: Grid;
|
||||
chain: Snake[];
|
||||
drawOptions: Options;
|
||||
}) => {
|
||||
//
|
||||
// canvas
|
||||
const canvas = document.createElement("canvas");
|
||||
const { width, height } = getCanvasWorldSize(grid0, drawOptions);
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
const w = Math.min(width, window.innerWidth);
|
||||
const h = (height / width) * w;
|
||||
canvas.style.width = w + "px";
|
||||
canvas.style.height = h + "px";
|
||||
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
//
|
||||
// draw
|
||||
let animationFrame: number;
|
||||
const spring = { x: 0, v: 0, target: 0 };
|
||||
const springParams = { tension: 120, friction: 20, maxVelocity: 50 };
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
const loop = () => {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
|
||||
stepSpring(spring, springParams, spring.target);
|
||||
const stable = isStableAndBound(spring, spring.target);
|
||||
|
||||
const grid = copyGrid(grid0);
|
||||
const stack: Color[] = [];
|
||||
for (let i = 0; i < Math.min(chain.length, spring.x); i++)
|
||||
step(grid, stack, chain[i]);
|
||||
|
||||
const snake0 = chain[clamp(Math.floor(spring.x), 0, chain.length - 1)];
|
||||
const snake1 = chain[clamp(Math.ceil(spring.x), 0, chain.length - 1)];
|
||||
const k = spring.x % 1;
|
||||
|
||||
ctx.clearRect(0, 0, 9999, 9999);
|
||||
drawLerpWorld(ctx, grid, snake0, snake1, stack, k, drawOptions);
|
||||
|
||||
if (!stable) animationFrame = requestAnimationFrame(loop);
|
||||
};
|
||||
loop();
|
||||
|
||||
//
|
||||
// controls
|
||||
const input = document.createElement("input") as any;
|
||||
input.type = "range";
|
||||
input.value = 0;
|
||||
input.step = 1;
|
||||
input.min = 0;
|
||||
input.max = chain.length;
|
||||
input.style.width = "calc( 100% - 20px )";
|
||||
input.addEventListener("input", () => {
|
||||
spring.target = +input.value;
|
||||
cancelAnimationFrame(animationFrame);
|
||||
animationFrame = requestAnimationFrame(loop);
|
||||
});
|
||||
document.body.addEventListener("click", () => input.focus());
|
||||
document.body.append(input);
|
||||
|
||||
//
|
||||
// dispose
|
||||
const dispose = () => {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
document.body.removeChild(canvas);
|
||||
document.body.removeChild(input);
|
||||
};
|
||||
|
||||
return { dispose };
|
||||
};
|
||||
|
||||
const onSubmit = async (userName: string) => {
|
||||
const res = await fetch(
|
||||
`https://snk-one.vercel.app/api/github-user-contribution/${userName}`
|
||||
);
|
||||
const { cells, colorScheme } = (await res.json()) as Res;
|
||||
|
||||
const drawOptions = {
|
||||
sizeBorderRadius: 2,
|
||||
sizeCell: 16,
|
||||
sizeDot: 12,
|
||||
colorBorder: "#1b1f230a",
|
||||
colorDots: colorScheme as any,
|
||||
colorEmpty: colorScheme[0],
|
||||
colorSnake: "purple",
|
||||
cells,
|
||||
};
|
||||
|
||||
const snake = snake3;
|
||||
const grid = userContributionToGrid(cells);
|
||||
const chain = [snake, ...getBestRoute(grid, snake)!];
|
||||
dispose();
|
||||
|
||||
createViewer({ grid0: grid, chain, drawOptions });
|
||||
};
|
||||
const { dispose } = createForm({ onSubmit });
|
||||
|
||||
document.body.style.margin = "0";
|
||||
document.body.style.display = "flex";
|
||||
document.body.style.flexDirection = "column";
|
||||
document.body.style.alignItems = "center";
|
||||
document.body.style.justifyContent = "center";
|
||||
document.body.style.height = "100%";
|
||||
document.body.style.width = "100%";
|
||||
document.body.style.position = "absolute";
|
||||
@@ -1 +1 @@
|
||||
["index", "getAvailableRoutes", "getBestRoute", "pruneLayer"]
|
||||
["index", "getAvailableRoutes", "pruneLayer", "getBestRoute", "interactive"]
|
||||
|
||||
1
packages/github-user-contribution-service/.gitignore
vendored
Normal file
1
packages/github-user-contribution-service/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
public
|
||||
@@ -2,6 +2,11 @@
|
||||
"name": "@snk/github-user-contribution-service",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@snk/github-user-contribution": "1.0.0",
|
||||
"@snk/demo": "1.0.0",
|
||||
"@vercel/node": "1.8.4"
|
||||
},
|
||||
"scripts": {
|
||||
"vercel-build": "( cd ../demo ; yarn build ) && mv ../demo/dist public"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user