mirror of
https://github.com/kevinveenbirkenbach/roulette-wheel.git
synced 2024-12-03 15:09:18 +01:00
Add/remove/clear items
This commit is contained in:
parent
078eb30b21
commit
656e157f14
62
app.css
Normal file
62
app.css
Normal file
@ -0,0 +1,62 @@
|
||||
html {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
|
||||
canvas {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
input {
|
||||
height: 1.5rem;
|
||||
width: 15rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.openCloseMenu {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.item-delete {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
float: right;
|
||||
}
|
||||
|
||||
#menu {
|
||||
height: 100vh;
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
#newItemButton {
|
||||
height: 2rem;
|
||||
width: 10rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
#menu-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#counter {
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
15
index.html
15
index.html
@ -5,8 +5,21 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Roulette wheel</title>
|
||||
<script type="module" src="dist/bundle.js"></script>
|
||||
<link rel="stylesheet" href="app.css">
|
||||
</head>
|
||||
<body>
|
||||
Test!
|
||||
<div id="menu">
|
||||
<img src="static/right-arrow.png" alt="open-close-arrow" class="openCloseMenu">
|
||||
<p id="counter">0/16</p>
|
||||
<div id="newItem">
|
||||
<label for="newItem">Item name</label>
|
||||
<input name="newItem" minlength="1" maxlength="32">
|
||||
<button id="newItemButton">Add</button>
|
||||
<button id="removeAllItemsButton">Remove all</button>
|
||||
</div>
|
||||
</div>
|
||||
<canvas></canvas>
|
||||
<footer><div>Icons made by <a href="https://www.flaticon.com/authors/roundicons" title="Roundicons">Roundicons</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div></footer>
|
||||
</body>
|
||||
</html>
|
99
src/app.ts
99
src/app.ts
@ -0,0 +1,99 @@
|
||||
import { ItemEl } from "./components/menu-item";
|
||||
import { ItemElList } from "./components/menu-item-list";
|
||||
|
||||
const canvas = document.querySelector("canvas")! as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
const centreX = canvas.width / 2;
|
||||
const centreY = canvas.height / 2;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(centreX, centreY);
|
||||
ctx.stroke();
|
||||
|
||||
console.log("Works!");
|
||||
|
||||
const maxItems = 16;
|
||||
const items: ItemElList = new ItemElList();
|
||||
|
||||
const menuEl = document.getElementById("menu")! as HTMLDivElement;
|
||||
const counterEl = document.getElementById("counter")!;
|
||||
const itemNameInput = document.querySelector("input")! as HTMLInputElement;
|
||||
const newItemButtonEl = document.getElementById(
|
||||
"newItemButton"
|
||||
)! as HTMLButtonElement;
|
||||
const removeAllItemsButtonEl = document.getElementById(
|
||||
"removeAllItemsButton"
|
||||
)! as HTMLButtonElement;
|
||||
|
||||
menuEl.appendChild(items.el);
|
||||
|
||||
const avaliableRGBs = ["255, 0, 0", "0, 255, 0", "0, 0, 255"];
|
||||
const bgOpacity = 0.5;
|
||||
let nextColor = avaliableRGBs[0];
|
||||
|
||||
let counter = 0;
|
||||
|
||||
const idsPool: number[] = [];
|
||||
initalizeIdsPool();
|
||||
|
||||
function addNewItem() {
|
||||
if (itemNameInput.value.length > 0 && itemNameInput.value.length <= 32) {
|
||||
if (counter < maxItems && idsPool.length > 0) {
|
||||
const item = new ItemEl(
|
||||
idsPool.pop()!,
|
||||
itemNameInput.value,
|
||||
`rgba(${nextColor}, ${bgOpacity})`
|
||||
);
|
||||
|
||||
item.deleteItem.el.addEventListener("click", () => {
|
||||
const itemElId = calculateItemElId(item);
|
||||
idsPool.push(itemElId);
|
||||
setNextColor();
|
||||
items.removeItem(item);
|
||||
counter--;
|
||||
updateCounter();
|
||||
});
|
||||
|
||||
items.addItem(item);
|
||||
counter++;
|
||||
updateCounter();
|
||||
setNextColor();
|
||||
} else {
|
||||
alert("Maximum number of items!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeAllItems() {
|
||||
if (items.itemsLength > 0) {
|
||||
items.clear();
|
||||
counter = 0;
|
||||
initalizeIdsPool();
|
||||
nextColor = avaliableRGBs[0];
|
||||
updateCounter();
|
||||
} else {
|
||||
alert("Nothing to clear!");
|
||||
}
|
||||
}
|
||||
|
||||
function updateCounter() {
|
||||
counterEl.textContent = `${counter}/${maxItems}`;
|
||||
}
|
||||
|
||||
function setNextColor() {
|
||||
nextColor = avaliableRGBs[counter % avaliableRGBs.length];
|
||||
}
|
||||
|
||||
function calculateItemElId(item: ItemEl) {
|
||||
return +item.el.id.slice(0, item.el.id.indexOf("-"));
|
||||
}
|
||||
|
||||
function initalizeIdsPool() {
|
||||
idsPool.length = 0;
|
||||
for (let i = 0; i < maxItems; i++) {
|
||||
idsPool.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
newItemButtonEl.addEventListener("click", addNewItem);
|
||||
removeAllItemsButtonEl.addEventListener("click", removeAllItems);
|
15
src/components/base-component.ts
Normal file
15
src/components/base-component.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export abstract class Component<T extends HTMLElement> {
|
||||
el: T;
|
||||
|
||||
constructor(el: T){
|
||||
this.el = el;
|
||||
}
|
||||
|
||||
removeChildren() {
|
||||
if(this.el.childNodes.length > 0){
|
||||
while(this.el.firstChild){
|
||||
this.el.removeChild(this.el.firstChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
src/components/menu-delete-item.ts
Normal file
17
src/components/menu-delete-item.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Component } from "./base-component";
|
||||
|
||||
export class DeleteItemEl extends Component<HTMLImageElement>{
|
||||
|
||||
constructor(id: number){
|
||||
const el = document.createElement('img');
|
||||
el.src = 'static/close.png';
|
||||
el.alt = 'delete';
|
||||
el.className = 'item-delete'
|
||||
el.id = `${id}-delete`;
|
||||
super(el);
|
||||
}
|
||||
|
||||
configure(){
|
||||
// TODO: make icon rotation after mouse over event
|
||||
}
|
||||
}
|
40
src/components/menu-item-list.ts
Normal file
40
src/components/menu-item-list.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { Component } from "./base-component";
|
||||
import { ItemEl } from "./menu-item";
|
||||
|
||||
export class ItemElList extends Component<HTMLUListElement> {
|
||||
private items: ItemEl[];
|
||||
|
||||
constructor(){
|
||||
const UList = document.createElement('ul');
|
||||
UList.className = 'item-list';
|
||||
super(UList);
|
||||
this.items = [];
|
||||
}
|
||||
|
||||
get itemsLength() {
|
||||
return this.items.length;
|
||||
}
|
||||
|
||||
addItem(item: ItemEl){
|
||||
this.items.push(item);
|
||||
this.el.appendChild(item.el);
|
||||
}
|
||||
|
||||
clear(){
|
||||
this.removeChildren();
|
||||
this.items = [];
|
||||
}
|
||||
|
||||
removeItem(item: ItemEl){
|
||||
item.el.remove();
|
||||
const indexToRemove = this.items.indexOf(item);
|
||||
this.items.splice(indexToRemove, 1);
|
||||
}
|
||||
|
||||
findLastItem(){
|
||||
if(this.items.length > 0){
|
||||
return this.items[this.items.length - 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
19
src/components/menu-item.ts
Normal file
19
src/components/menu-item.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Component } from "./base-component";
|
||||
import { DeleteItemEl } from "./menu-delete-item";
|
||||
|
||||
export class ItemEl extends Component<HTMLLIElement> {
|
||||
|
||||
deleteItem: DeleteItemEl;
|
||||
|
||||
constructor(id: number, value: string, color: string){
|
||||
const deleteItem = new DeleteItemEl(id);
|
||||
const el = document.createElement('li');
|
||||
el.className = 'menu-item';
|
||||
el.id = `${id}-item`;
|
||||
el.textContent = value;
|
||||
el.style.backgroundColor = color;
|
||||
el.appendChild(deleteItem.el);
|
||||
super(el);
|
||||
this.deleteItem = deleteItem;
|
||||
}
|
||||
}
|
BIN
static/close.png
Normal file
BIN
static/close.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
BIN
static/right-arrow.png
Normal file
BIN
static/right-arrow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
@ -1,18 +1,18 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
mode: 'development',
|
||||
entry: './src/app.ts',
|
||||
output: {
|
||||
path:path.resolve(__dirname, "dist"),
|
||||
filename: "bundle.js",
|
||||
publicPath: 'dist'
|
||||
path:path.resolve(__dirname, 'dist'),
|
||||
filename: 'bundle.js',
|
||||
publicPath: '/dist/'
|
||||
},
|
||||
devtool: 'inline-source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts?$/,
|
||||
test: /\.ts$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts?$/,
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user