move dashboard script block content into own files with new classes to update, render to have a better code overview
Build and Push Docker Container / build-and-push (push) Successful in 3m55s
Build and Push Docker Container / build-and-push (push) Successful in 3m55s
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
class SnakeUtils {
|
||||
static snakeColor(index) {
|
||||
return `var(--snake-${((index % 10) + 1)})`;
|
||||
}
|
||||
|
||||
static stableColorIndexFromId(snakeId) {
|
||||
const raw = String(snakeId || "");
|
||||
let hash = 0;
|
||||
for (let i = 0; i < raw.length; i += 1) {
|
||||
hash = ((hash * 31) + raw.charCodeAt(i)) >>> 0;
|
||||
}
|
||||
return hash % 10;
|
||||
}
|
||||
|
||||
static resolveSnakeColor(snakeId, isYou, colorById) {
|
||||
if (snakeId && colorById && colorById.has(snakeId)) {
|
||||
return colorById.get(snakeId);
|
||||
}
|
||||
if (isYou) return "var(--you)";
|
||||
return SnakeUtils.snakeColor(SnakeUtils.stableColorIndexFromId(snakeId));
|
||||
}
|
||||
|
||||
static extractSnakeColor(rawSnake) {
|
||||
if (!rawSnake || typeof rawSnake !== "object") return null;
|
||||
const direct = rawSnake.color;
|
||||
const custom = rawSnake.customizations && rawSnake.customizations.color;
|
||||
const appearance = rawSnake.appearance && rawSnake.appearance.color;
|
||||
const color = direct || custom || appearance;
|
||||
if (!color) return null;
|
||||
return String(color).trim();
|
||||
}
|
||||
|
||||
static buildSnakeColorById(turnData, replay) {
|
||||
const colorById = new Map();
|
||||
const replayTurns = replay && Array.isArray(replay.turns) ? replay.turns : [];
|
||||
const boardSnakes = turnData && turnData.board && Array.isArray(turnData.board.snakes)
|
||||
? turnData.board.snakes
|
||||
: [];
|
||||
const replaySnakes = Array.isArray(turnData && turnData.snakes) ? turnData.snakes : [];
|
||||
|
||||
const historicalSnakes = [];
|
||||
for (const replayTurn of replayTurns) {
|
||||
if (replayTurn && Array.isArray(replayTurn.snakes)) {
|
||||
historicalSnakes.push(...replayTurn.snakes);
|
||||
}
|
||||
if (replayTurn && replayTurn.board && Array.isArray(replayTurn.board.snakes)) {
|
||||
historicalSnakes.push(...replayTurn.board.snakes);
|
||||
}
|
||||
}
|
||||
|
||||
for (const snake of [...historicalSnakes, ...boardSnakes, ...replaySnakes]) {
|
||||
if (!snake) continue;
|
||||
const snakeId = snake.id || snake.snake_id;
|
||||
if (!snakeId) continue;
|
||||
const color = SnakeUtils.extractSnakeColor(snake);
|
||||
if (color) colorById.set(snakeId, color);
|
||||
}
|
||||
return colorById;
|
||||
}
|
||||
|
||||
static buildSnakeCustomizationById(turnData, replay) {
|
||||
const customById = new Map();
|
||||
const replayTurns = replay && Array.isArray(replay.turns) ? replay.turns : [];
|
||||
const sources = [];
|
||||
|
||||
for (const replayTurn of replayTurns) {
|
||||
if (replayTurn && replayTurn.board && Array.isArray(replayTurn.board.snakes)) {
|
||||
sources.push(...replayTurn.board.snakes);
|
||||
}
|
||||
}
|
||||
|
||||
if (turnData && turnData.board && Array.isArray(turnData.board.snakes)) {
|
||||
sources.push(...turnData.board.snakes);
|
||||
}
|
||||
|
||||
for (const snake of sources) {
|
||||
if (!snake) continue;
|
||||
const snakeId = snake.id || snake.snake_id;
|
||||
if (!snakeId) continue;
|
||||
const custom = snake.customizations || {};
|
||||
const head = custom.head || null;
|
||||
const tail = custom.tail || null;
|
||||
if (head || tail) {
|
||||
customById.set(snakeId, { head, tail });
|
||||
}
|
||||
}
|
||||
|
||||
return customById;
|
||||
}
|
||||
|
||||
static buildCustomizationIconUrl(kind, value) {
|
||||
const raw = String(value || "").trim().toLowerCase();
|
||||
if (!raw) return null;
|
||||
if (!/^[a-z0-9-]+$/.test(raw)) return null;
|
||||
return `/dashboard/customizations/${kind}/${raw}.svg`;
|
||||
}
|
||||
|
||||
static parseSnakeColor(color) {
|
||||
if (!color) return null;
|
||||
const value = String(color).trim();
|
||||
if (value.startsWith("#")) {
|
||||
const hex = value.slice(1);
|
||||
if (hex.length === 3) {
|
||||
const r = parseInt(hex[0] + hex[0], 16);
|
||||
const g = parseInt(hex[1] + hex[1], 16);
|
||||
const b = parseInt(hex[2] + hex[2], 16);
|
||||
return Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b) ? null : { r, g, b };
|
||||
}
|
||||
if (hex.length === 6) {
|
||||
const r = parseInt(hex.slice(0, 2), 16);
|
||||
const g = parseInt(hex.slice(2, 4), 16);
|
||||
const b = parseInt(hex.slice(4, 6), 16);
|
||||
return Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b) ? null : { r, g, b };
|
||||
}
|
||||
}
|
||||
const rgb = value.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
|
||||
if (rgb) {
|
||||
return {
|
||||
r: Math.max(0, Math.min(255, Number(rgb[1]))),
|
||||
g: Math.max(0, Math.min(255, Number(rgb[2]))),
|
||||
b: Math.max(0, Math.min(255, Number(rgb[3]))),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static snakeRowBackground(color) {
|
||||
const parsed = SnakeUtils.parseSnakeColor(color);
|
||||
if (!parsed) return "transparent";
|
||||
const isDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
const alpha = isDark ? 0.26 : 0.16;
|
||||
return `rgba(${parsed.r}, ${parsed.g}, ${parsed.b}, ${alpha})`;
|
||||
}
|
||||
|
||||
static inferHeadDirection(snake) {
|
||||
const body = Array.isArray(snake && snake.body) ? snake.body : [];
|
||||
if (body.length >= 2) {
|
||||
const head = body[0];
|
||||
const neck = body[1];
|
||||
if (head && neck) {
|
||||
const dx = Number(head.x) - Number(neck.x);
|
||||
const dy = Number(head.y) - Number(neck.y);
|
||||
if (dx > 0) return "right";
|
||||
if (dx < 0) return "left";
|
||||
if (dy > 0) return "up";
|
||||
if (dy < 0) return "down";
|
||||
}
|
||||
}
|
||||
|
||||
const inferred = String(snake && snake.inferred_move ? snake.inferred_move : "").toLowerCase();
|
||||
if (["up", "down", "left", "right"].includes(inferred)) return inferred;
|
||||
|
||||
if (body.length < 2) return "right";
|
||||
const head = body[0];
|
||||
const neck = body[1];
|
||||
if (!head || !neck) return "right";
|
||||
const dx = Number(head.x) - Number(neck.x);
|
||||
const dy = Number(head.y) - Number(neck.y);
|
||||
if (dx > 0) return "right";
|
||||
if (dx < 0) return "left";
|
||||
if (dy > 0) return "up";
|
||||
if (dy < 0) return "down";
|
||||
return "right";
|
||||
}
|
||||
|
||||
static inferTailDirection(snake) {
|
||||
const body = Array.isArray(snake && snake.body) ? snake.body : [];
|
||||
if (body.length < 2) return "right";
|
||||
const tail = body[body.length - 1];
|
||||
if (!tail) return "right";
|
||||
|
||||
let beforeTail = null;
|
||||
for (let idx = body.length - 2; idx >= 0; idx -= 1) {
|
||||
const candidate = body[idx];
|
||||
if (!candidate) continue;
|
||||
if (Number(candidate.x) !== Number(tail.x) || Number(candidate.y) !== Number(tail.y)) {
|
||||
beforeTail = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!beforeTail) {
|
||||
const inferred = String(snake && snake.inferred_move ? snake.inferred_move : "").toLowerCase();
|
||||
if (["up", "down", "left", "right"].includes(inferred)) return inferred;
|
||||
return "right";
|
||||
}
|
||||
|
||||
const dx = Number(beforeTail.x) - Number(tail.x);
|
||||
const dy = Number(beforeTail.y) - Number(tail.y);
|
||||
if (dx > 0) return "right";
|
||||
if (dx < 0) return "left";
|
||||
if (dy > 0) return "up";
|
||||
if (dy < 0) return "down";
|
||||
return "right";
|
||||
}
|
||||
|
||||
static directionToHeadTransform(direction) {
|
||||
if (direction === "left") return "scaleX(-1)";
|
||||
if (direction === "up") return "rotate(270deg)";
|
||||
if (direction === "down") return "rotate(90deg)";
|
||||
return "rotate(0deg)";
|
||||
}
|
||||
|
||||
static directionToTailTransform(direction) {
|
||||
if (direction === "right") return "scaleX(-1)";
|
||||
if (direction === "left") return "rotate(0deg)";
|
||||
if (direction === "up") return "rotate(270deg) scaleX(-1)";
|
||||
if (direction === "down") return "rotate(90deg) scaleX(-1)";
|
||||
return "scaleX(-1)";
|
||||
}
|
||||
|
||||
static stableVariantFromString(value) {
|
||||
const raw = String(value || "");
|
||||
if (!raw) return 1;
|
||||
let hash = 0;
|
||||
for (let i = 0; i < raw.length; i += 1) {
|
||||
hash = ((hash * 33) + raw.charCodeAt(i)) >>> 0;
|
||||
}
|
||||
return (hash % 5) + 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user