321 lines
8.9 KiB
TypeScript
321 lines
8.9 KiB
TypeScript
import { MenuItem } from "./components/menu-item";
|
|
import { Item } from "./model/item";
|
|
import { ItemList } from "./model/item-list";
|
|
import { IdPool } from "./utils/item-id-pool";
|
|
import { LightDarkMode } from "./utils/light-dark-mode";
|
|
import { avaliableRGBs } from "./utils/utils";
|
|
|
|
const addButtonEl = document.getElementsByClassName(
|
|
"add-item-button"
|
|
)[0]! as HTMLButtonElement;
|
|
const inputEl = document.querySelector("input")! as HTMLInputElement;
|
|
const itemListEl = document.getElementsByClassName(
|
|
"menu-item-list"
|
|
)[0]! as HTMLUListElement;
|
|
const counterEl = document.getElementsByClassName(
|
|
"counter"
|
|
)[0]! as HTMLParagraphElement;
|
|
const removeAllItemsButtonEl = document.getElementsByClassName(
|
|
"remove-all-items-button"
|
|
)[0]! as HTMLButtonElement;
|
|
const lightDarkModeEl = document.getElementsByClassName(
|
|
"light-dark-mode"
|
|
)[0]! as HTMLDivElement;
|
|
const canvasEl = document.querySelector("canvas")! as HTMLCanvasElement;
|
|
const bodyEl = document.querySelector("body")! as HTMLBodyElement;
|
|
const modeEl = lightDarkModeEl.querySelector("img")! as HTMLImageElement;
|
|
const winnerEl = document.getElementsByClassName(
|
|
"winner"
|
|
)[0]! as HTMLDivElement;
|
|
const footerEl = document.querySelector("footer")!;
|
|
const authorsEl = document.getElementsByClassName(
|
|
"icons-authors"
|
|
)[0]! as HTMLDivElement;
|
|
|
|
const audio = new Audio("/static/roulette-wheel.mp3");
|
|
|
|
const ctx = canvasEl.getContext("2d")!;
|
|
|
|
const MAXIMUM_SIZE = 16;
|
|
const ROTATIONS = 20;
|
|
const ANIMATION_FPS = 60;
|
|
const WHEEL_OFFSET = 5;
|
|
const X_CENTER = canvasEl.width / 2;
|
|
const Y_CENTER = canvasEl.height / 2;
|
|
const RADIUS = canvasEl.height / 2 - WHEEL_OFFSET;
|
|
const TIME_TO_REDRAW = 1000 / ANIMATION_FPS;
|
|
|
|
const itemsList = new ItemList(MAXIMUM_SIZE);
|
|
const items: MenuItem[] = [];
|
|
|
|
let rotate_by = 50;
|
|
let totalTime = 0;
|
|
|
|
configure();
|
|
|
|
function configure() {
|
|
counterEl.textContent = `0/${MAXIMUM_SIZE}`;
|
|
audio.volume = 0.2;
|
|
|
|
LightDarkMode.currentMode = "dark";
|
|
IdPool.initializeIdsPool(MAXIMUM_SIZE);
|
|
addRemoveItem();
|
|
addItemByEnter();
|
|
removeAllItems();
|
|
lightDarkMode();
|
|
drawRouletteWheel(0);
|
|
|
|
canvasEl.addEventListener("click", startRoulette);
|
|
footerEl.addEventListener("click", () => {
|
|
authorsEl.hidden = !authorsEl.hidden;
|
|
});
|
|
}
|
|
|
|
function addRemoveItem() {
|
|
addButtonEl.addEventListener("click", () => {
|
|
if (!animationId) {
|
|
clearWinner();
|
|
const name = inputEl.value;
|
|
if (name) {
|
|
const id = IdPool.getAnId();
|
|
if (id) {
|
|
const color = avaliableRGBs[id % avaliableRGBs.length];
|
|
const item = new Item(id, name, color);
|
|
const menuItem = new MenuItem(id, name, color);
|
|
|
|
menuItem.deleteItem.el.addEventListener("click", () => {
|
|
if (!animationId) {
|
|
clearWinner();
|
|
menuItem.el.remove();
|
|
items.splice(items.indexOf(menuItem), 1);
|
|
itemsList.remove(item);
|
|
IdPool.addId(item.id);
|
|
updateCounter();
|
|
drawRouletteWheel(0);
|
|
}
|
|
});
|
|
|
|
registerChangeColorByClick(item, menuItem);
|
|
|
|
itemsList.add(item);
|
|
items.push(menuItem);
|
|
itemListEl.appendChild(menuItem.el);
|
|
updateCounter();
|
|
guessItemIndex = Math.floor(Math.random() * items.length);
|
|
segmentAngle = 360 / items.length;
|
|
maxAngle = 360 * ROTATIONS + segmentAngle * guessItemIndex;
|
|
drawRouletteWheel(0);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function addItemByEnter() {
|
|
bodyEl.addEventListener("keyup", (event: KeyboardEvent) => {
|
|
if (event.key !== "Enter") return;
|
|
addButtonEl.click();
|
|
});
|
|
}
|
|
|
|
function registerChangeColorByClick(item: Item, menuItem: MenuItem) {
|
|
menuItem.el.addEventListener("click", () => {
|
|
if (!animationId) {
|
|
const actualColorIndex = avaliableRGBs.indexOf(
|
|
menuItem.el.style.backgroundColor
|
|
);
|
|
if (actualColorIndex !== -1) {
|
|
const color =
|
|
avaliableRGBs[(actualColorIndex + 1) % avaliableRGBs.length];
|
|
menuItem.el.style.backgroundColor = color;
|
|
item.color = color;
|
|
drawRouletteWheel(0);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function removeAllItems() {
|
|
removeAllItemsButtonEl.addEventListener("click", () => {
|
|
if (!animationId) {
|
|
clearWinner();
|
|
if (items.length > 0) {
|
|
items.forEach((menuItem) => menuItem.el.remove());
|
|
items.length = 0;
|
|
itemsList.clear();
|
|
IdPool.initializeIdsPool(MAXIMUM_SIZE);
|
|
updateCounter();
|
|
drawRouletteWheel(0);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateCounter() {
|
|
counterEl.textContent = `${itemsList.items.length}/${MAXIMUM_SIZE}`;
|
|
animateCounter();
|
|
}
|
|
|
|
function animateCounter() {
|
|
counterEl.animate(
|
|
[{ transform: "scale(1.5)" }, { transform: "scale(1.00)" }],
|
|
{
|
|
duration: 500,
|
|
}
|
|
);
|
|
}
|
|
|
|
function lightDarkMode() {
|
|
lightDarkModeEl.addEventListener("click", () => {
|
|
modeEl.animate([{ transform: "rotate(360deg)" }], {
|
|
duration: 500,
|
|
});
|
|
|
|
if (LightDarkMode.currentMode === "dark") {
|
|
modeEl.src = "static/sun.png";
|
|
bodyEl.style.backgroundColor = "#FAF9F6";
|
|
bodyEl.style.color = "black";
|
|
LightDarkMode.currentMode = "light";
|
|
} else {
|
|
modeEl.src = "static/moon.png";
|
|
bodyEl.style.backgroundColor = "#121212";
|
|
bodyEl.style.color = "white";
|
|
LightDarkMode.currentMode = "dark";
|
|
}
|
|
});
|
|
}
|
|
|
|
function drawRouletteWheel(angle: number) {
|
|
const segmentWidth = 360 / items.length;
|
|
let endAngle = segmentWidth + angle;
|
|
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
|
|
ctx.beginPath();
|
|
ctx.save();
|
|
ctx.translate(X_CENTER + RADIUS + 50, Y_CENTER);
|
|
ctx.lineTo(0, -20);
|
|
ctx.lineTo(0, 20);
|
|
ctx.lineTo(-50, 0);
|
|
ctx.fillStyle = "#C4B454";
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
ctx.arc(X_CENTER, Y_CENTER, RADIUS, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
for (let i = 0; i < items.length; i++) {
|
|
ctx.beginPath();
|
|
ctx.lineTo(X_CENTER, Y_CENTER);
|
|
console.log(outerHeight);
|
|
console.log(outerWidth);
|
|
ctx.font = "bold 24px verdana, sans-serif";
|
|
ctx.arc(
|
|
X_CENTER,
|
|
Y_CENTER,
|
|
RADIUS,
|
|
(angle * Math.PI) / 180,
|
|
(endAngle * Math.PI) / 180
|
|
);
|
|
ctx.lineTo(X_CENTER, Y_CENTER);
|
|
ctx.fillStyle = itemsList.items[i].color;
|
|
ctx.fill();
|
|
ctx.save();
|
|
ctx.fillStyle = "black";
|
|
const text = itemsList.items[i].name;
|
|
if (items.length > 1) {
|
|
const angleVal = ((angle + segmentWidth / 2) * Math.PI) / 180;
|
|
const xT = X_CENTER + (Math.cos(angleVal) * RADIUS) / 2;
|
|
const xY = Y_CENTER + (Math.sin(angleVal) * RADIUS) / 2;
|
|
ctx.translate(xT, xY);
|
|
ctx.rotate(Math.PI / items.length + (Math.PI / 180) * angle);
|
|
ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
|
|
} else {
|
|
ctx.fillText(text, X_CENTER, Y_CENTER);
|
|
}
|
|
ctx.restore();
|
|
if (items.length !== 1) {
|
|
ctx.stroke();
|
|
}
|
|
angle += segmentWidth;
|
|
endAngle += segmentWidth;
|
|
}
|
|
}
|
|
|
|
let guessItemIndex = Math.floor(Math.random() * items.length);
|
|
let segmentAngle = 360 / items.length;
|
|
let maxAngle = 360 * ROTATIONS + segmentAngle * guessItemIndex;
|
|
let animationId: number | null;
|
|
let winner: string;
|
|
let endTime: number;
|
|
|
|
function startRoulette() {
|
|
if (items.length > 1) {
|
|
if (!animationId) {
|
|
const winnerCleared = clearWinner();
|
|
if (winnerCleared) {
|
|
drawRouletteWheel(0);
|
|
} else {
|
|
audio.play();
|
|
addButtonEl.style.background = "#C0C0C0";
|
|
removeAllItemsButtonEl.style.background = "#C0C0C0";
|
|
guessItemIndex = Math.floor(Math.random() * items.length);
|
|
winner = itemsList.items[guessItemIndex].name;
|
|
segmentAngle = 360 / items.length;
|
|
totalTime = 0;
|
|
maxAngle =
|
|
360 * ROTATIONS +
|
|
(items.length - 1 - guessItemIndex) * segmentAngle +
|
|
Math.random() * segmentAngle;
|
|
endTime = (maxAngle / rotate_by) * TIME_TO_REDRAW;
|
|
beginAnimateRoulette();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function beginAnimateRoulette() {
|
|
const angle = easeOut(totalTime, 0, maxAngle, endTime);
|
|
if (totalTime < endTime || maxAngle - angle > 0.1) {
|
|
setTimeout(() => {
|
|
drawRouletteWheel(angle);
|
|
totalTime += TIME_TO_REDRAW;
|
|
animationId = window.requestAnimationFrame(beginAnimateRoulette);
|
|
}, TIME_TO_REDRAW);
|
|
} else {
|
|
window.cancelAnimationFrame(animationId!);
|
|
animationId = null;
|
|
winnerEl.textContent = `Winner: ${winner}`;
|
|
addButtonEl.style.background = "#6082B6";
|
|
removeAllItemsButtonEl.style.background = "#6082B6";
|
|
animateWinner();
|
|
}
|
|
}
|
|
|
|
function easeOut(
|
|
time: number,
|
|
beginningVal: number,
|
|
toChange: number,
|
|
duration: number
|
|
) {
|
|
return time == duration
|
|
? beginningVal + toChange
|
|
: toChange * (-Math.pow(2, (-10 * time) / duration) + 1) + beginningVal;
|
|
}
|
|
|
|
function animateWinner() {
|
|
winnerEl.animate(
|
|
{
|
|
opacity: [0.5, 1],
|
|
easing: ["ease-in", "ease-out"],
|
|
},
|
|
1000
|
|
);
|
|
}
|
|
|
|
function clearWinner() {
|
|
if (winner) {
|
|
winner = "";
|
|
winnerEl.textContent = "";
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|