From fa166bc3773ee44152872462956a52edec78ec9d Mon Sep 17 00:00:00 2001 From: Eduardo Wesley Date: Sun, 26 Jul 2020 19:11:42 -0300 Subject: [PATCH 1/2] #22 convert ct client to use typescript --- client/@types/files.d.ts | 14 +++ client/@types/index.d.ts | 5 + client/package.json | 24 ++-- ...{AudioController.js => AudioController.ts} | 11 +- client/src/js/Game/{Board.js => Board.ts} | 63 ++++++---- client/src/js/Game/Card.js | 109 ------------------ client/src/js/Game/Card.ts | 102 ++++++++++++++++ client/src/js/Game/Scoreboard.js | 72 ------------ client/src/js/Game/Scoreboard.ts | 82 +++++++++++++ client/src/js/Modal.js | 92 --------------- client/src/js/Modal.ts | 100 ++++++++++++++++ client/src/js/{io.js => io.ts} | 2 +- client/src/js/{main.js => main.ts} | 18 +-- client/tsconfig.json | 16 +++ .../{webpack.config.js => webpack.config.ts} | 24 ++-- 15 files changed, 408 insertions(+), 326 deletions(-) create mode 100644 client/@types/files.d.ts create mode 100644 client/@types/index.d.ts rename client/src/js/Game/{AudioController.js => AudioController.ts} (60%) rename client/src/js/Game/{Board.js => Board.ts} (50%) delete mode 100644 client/src/js/Game/Card.js create mode 100644 client/src/js/Game/Card.ts delete mode 100644 client/src/js/Game/Scoreboard.js create mode 100644 client/src/js/Game/Scoreboard.ts delete mode 100644 client/src/js/Modal.js create mode 100644 client/src/js/Modal.ts rename client/src/js/{io.js => io.ts} (81%) rename client/src/js/{main.js => main.ts} (79%) create mode 100644 client/tsconfig.json rename client/{webpack.config.js => webpack.config.ts} (71%) diff --git a/client/@types/files.d.ts b/client/@types/files.d.ts new file mode 100644 index 0000000..ce417fc --- /dev/null +++ b/client/@types/files.d.ts @@ -0,0 +1,14 @@ +declare module '*.svg' { + const src : string; + export default src; +} + +declare module '*.mp3' { + const src : string; + export default src; +} + +declare module '*.wav' { + const src : string; + export default src; +} diff --git a/client/@types/index.d.ts b/client/@types/index.d.ts new file mode 100644 index 0000000..0df4950 --- /dev/null +++ b/client/@types/index.d.ts @@ -0,0 +1,5 @@ +export interface CardJSON { + id: string; + order: number; + icon: string; +} diff --git a/client/package.json b/client/package.json index bdd82ad..9a4109e 100644 --- a/client/package.json +++ b/client/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "start": "webpack-dev-server", + "dev": "webpack-dev-server", "build": "webpack", "deploy": "yarn build && ./node_modules/.bin/gh-pages -d dist" }, @@ -22,20 +22,28 @@ "@babel/preset-env": "^7.3.1", "@fortawesome/fontawesome-free": "^5.13.0", "@fortawesome/fontawesome-svg-core": "^1.2.28", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", "html-webpack-plugin": "^3.2.0", "socket.io-client": "^2.3.0", - "style-loader": "^0.23.1", - "svg-inline-loader": "^0.8.2", "webpack": "^4.29.3", - "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.1.14" + "webpack-cli": "^3.2.3" }, "devDependencies": { + "@types/html-webpack-plugin": "^3.2.3", + "@types/node": "^14.0.26", + "@types/socket.io-client": "^1.4.33", + "@types/webpack": "^4.41.21", + "@types/webpack-dev-server": "^3.11.0", "file-loader": "^6.0.0", "gh-pages": "^2.2.0", "node-sass": "^4.14.1", - "sass-loader": "^8.0.2" + "style-loader": "^0.23.1", + "svg-inline-loader": "^0.8.2", + "sass-loader": "^8.0.2", + "babel-loader": "^8.0.5", + "css-loader": "^2.1.0", + "ts-loader": "^8.0.1", + "ts-node": "^8.10.2", + "typescript": "^3.9.7", + "webpack-dev-server": "^3.1.14" } } diff --git a/client/src/js/Game/AudioController.js b/client/src/js/Game/AudioController.ts similarity index 60% rename from client/src/js/Game/AudioController.js rename to client/src/js/Game/AudioController.ts index e507970..c847f20 100644 --- a/client/src/js/Game/AudioController.js +++ b/client/src/js/Game/AudioController.ts @@ -2,18 +2,21 @@ import flipSound from '../../assets/audio/flip.wav'; import matchSound from '../../assets/audio/match.mp3'; class AudioController { - constructor() { + private flipSound : HTMLAudioElement; + private matchSound : HTMLAudioElement; + + public constructor() { this.flipSound = new Audio(flipSound); this.matchSound = new Audio(matchSound); } - flip() { + public flip() : void { this.flipSound.play(); } - match() { + public match() : void { this.matchSound.play(); } } -export default AudioController; \ No newline at end of file +export default AudioController; diff --git a/client/src/js/Game/Board.js b/client/src/js/Game/Board.ts similarity index 50% rename from client/src/js/Game/Board.js rename to client/src/js/Game/Board.ts index a6b79ab..abc0919 100644 --- a/client/src/js/Game/Board.js +++ b/client/src/js/Game/Board.ts @@ -1,12 +1,26 @@ import Card from './Card'; import Scoreboard from './Scoreboard'; import AudioController from './AudioController'; +import { CardJSON } from '../../../@types'; class Board { - #board; - - constructor(el, { elapsedTime, cards, players }, me) { - this.#board = el; + private board : HTMLElement; + private timer: HTMLDivElement; + private playerTurnEl: HTMLDivElement; + private cards : Card[]; + private me : string; + private players : string[]; + private elapsedTime : number; + private timerInterval : NodeJS.Timeout; + private AudioController : AudioController; + private scoreboard : Scoreboard; + + constructor( + el : HTMLElement, + { elapsedTime, cards, players } : { elapsedTime: number, cards: CardJSON[], players: string[] }, + me : string + ) { + this.board = el; this.me = me; this.elapsedTime = elapsedTime; this.players = players; @@ -19,66 +33,67 @@ class Board { this.playerTurnEl.classList.add('player-turn'); this.AudioController = new AudioController; + this.scoreboard = new Scoreboard; this.timer.classList.add('timer'); - document.querySelector('#game-info').append(this.timer, this.playerTurnEl); + document.querySelector('#game-info')?.append(this.timer, this.playerTurnEl); + this.timerInterval = this.startTimer(); + this.insertCardsInBoard(); this.initScoreBoard(); - this.startTimer(); } - initScoreBoard() { - this.scoreboard = new Scoreboard(); - document.querySelector('#game-info').appendChild(this.scoreboard.el); + private initScoreBoard() : void { + document.querySelector('#game-info')?.appendChild(this.scoreboard.el); } - startTimer() { - this.timerInterval = setInterval(() => { + private startTimer() : NodeJS.Timeout { + return setInterval(() => { this.elapsedTime++; this.timer.textContent = `Time ${this.elapsedTime}`; }, 1000); } - insertCardsInBoard() { - this.cards.forEach(card => this.#board.appendChild(card.toNode())); + private insertCardsInBoard() : void { + this.cards.forEach(card => this.board.appendChild(card.toNode())); } - removeCardsFromBoard() { - this.cards.forEach(card => this.#board.removeChild(card.toNode())); + private removeCardsFromBoard() : void { + this.cards.forEach(card => this.board.removeChild(card.toNode())); } - flip (id) { + public flip (id : string) : void { const card = this.cards.find(card => id === card.id); - card.flip(); + card?.flip(); } - unflip (ids) { + public unflip (ids : string[]) : void { const cards = this.cards.filter(card => ids.includes(card.id)); cards.forEach(card => card.unflip()); } - check(ids) { + public check(ids : string[]) : void { const cards = this.cards.filter(card => ids.includes(card.id)); cards.forEach(card => card.markAsMatched()); this.AudioController.match(); } - setScoreboard (score) { + public setScoreboard (score : Record) : void { this.players.forEach(player => { this.scoreboard[player === this.me ? 'myHits' : 'enemyHits'] = score[player] || 0; }) } - setPlayerTurn(playerOfTheTime) { + public setPlayerTurn(playerOfTheTime : string) : void { this.playerTurnEl.textContent = playerOfTheTime === this.me ? 'Your turn' : 'Enemy turn'; } - stopTimers() { + public stopTimers() : void { clearInterval(this.timerInterval); } - destroy () { + public destroy () : void { this.removeCardsFromBoard(); this.scoreboard.destroy(); this.stopTimers(); @@ -87,4 +102,4 @@ class Board { } } -export default Board; \ No newline at end of file +export default Board; diff --git a/client/src/js/Game/Card.js b/client/src/js/Game/Card.js deleted file mode 100644 index 2b88cb0..0000000 --- a/client/src/js/Game/Card.js +++ /dev/null @@ -1,109 +0,0 @@ -import { icon } from '@fortawesome/fontawesome-svg-core' -import AudioController from './AudioController'; -import socket from './../io'; - -class Card { - #fliped = false - #id; - #el; - #icon; - - constructor({ id, order, icon: iconNames }) { - this.#el = document.createElement('div'); - this.#id = id; - this.order = order; - this.AudioController = new AudioController(); - - const [prefix, iconName] = iconNames.split(' '); - this.#icon = icon({ prefix, iconName }).node[0]; - this.#icon.classList.add('main-icon'); - - this.effect = document.createElement('span'); - this.effect.innerHTML = ''; - this.effect.classList.add('match-effect'); - - this.initCard() - } - - set fliped (val) { - this.#fliped = val; - } - - get fliped () { - return this.#fliped; - } - - get id () { - return this.#id; - } - - set order (n) { - this.toNode().style.order = n; - } - - toNode() { - return this.#el; - } - - click() { - socket.emit('click', this.id); - } - - addClick() { - this.#el.addEventListener('click', this.click.bind(this)); - } - - removeClick() { - this.#el.removeEventListener('click', this.click); - } - - flip () { - clearTimeout(this.flipTimeout); - this.#el.querySelector('.face.front').appendChild(this.#icon); - this.AudioController.flip(); - this.#fliped = true; - this.#el.classList.add('active'); - } - - unflip () { - this.#fliped = false; - this.#el.classList.remove('active'); - this.#el.classList.remove('matched'); - this.flipTimeout = setTimeout(() => this.#icon.remove(), 800); - } - - markAsMatched () { - this.#el.classList.add('matched'); - - for(let i = 1; i <= 6; i++) { - const clone = this.effect.cloneNode(true); - this.#el.firstElementChild.appendChild(clone); - setTimeout(() => this.#el.firstElementChild.removeChild(clone), 1000); - } - } - - initCard() { - this.#el.classList.add('card'); - - this.#el.innerHTML = ` -
- -
- -
- - - - - - - -
- `; - - this.addClick(); - } - -} - -export default Card \ No newline at end of file diff --git a/client/src/js/Game/Card.ts b/client/src/js/Game/Card.ts new file mode 100644 index 0000000..9871c03 --- /dev/null +++ b/client/src/js/Game/Card.ts @@ -0,0 +1,102 @@ +import { icon, IconName, IconPrefix } from '@fortawesome/fontawesome-svg-core' +import AudioController from './AudioController'; +import socket from '../io'; +import { CardJSON } from '../../../@types'; + +class Card { + private el : HTMLDivElement; + private effect : HTMLSpanElement; + private icon : Element; + private hashId : string; + private flipTimeout : NodeJS.Timeout | null = null; + private AudioController : AudioController; + + constructor({ id, order, icon: iconNames } : CardJSON) { + this.el = document.createElement('div'); + this.hashId = id; + this.order = order; + this.AudioController = new AudioController(); + + const [prefix, iconName] = iconNames.split(' ') as [IconPrefix, IconName]; + this.icon = icon({ prefix, iconName }).node[0]; + this.icon.classList.add('main-icon'); + + this.effect = document.createElement('span'); + this.effect.innerHTML = ''; + this.effect.classList.add('match-effect'); + + this.initCard(); + } + + get id () { + return this.hashId; + } + + set order (n : number) { + this.toNode().style.order = (n).toString(); + } + + toNode() { + return this.el; + } + + click() : void { + socket.emit('click', this.hashId); + } + + addClick() : void { + this.el.addEventListener('click', this.click.bind(this)); + } + + removeClick() : void { + this.el.removeEventListener('click', this.click); + } + + flip () : void { + clearTimeout(this.flipTimeout as NodeJS.Timeout); + this.el.querySelector('.face.front')?.appendChild(this.icon); + this.AudioController.flip(); + this.el.classList.add('active'); + } + + unflip () : void { + this.el.classList.remove('active'); + this.el.classList.remove('matched'); + this.flipTimeout = setTimeout(() => this.icon.remove(), 800); + } + + markAsMatched () : void { + this.el.classList.add('matched'); + + for(let i = 1; i <= 6; i++) { + const clone = this.effect.cloneNode(true); + this.el.firstElementChild?.appendChild(clone); + setTimeout(() => this.el.firstElementChild?.removeChild(clone), 1000); + } + } + + initCard() : void { + this.el.classList.add('card'); + + this.el.innerHTML = ` +
+ +
+ +
+ + + + + + + +
+ `; + + this.addClick(); + } + +} + +export default Card \ No newline at end of file diff --git a/client/src/js/Game/Scoreboard.js b/client/src/js/Game/Scoreboard.js deleted file mode 100644 index 9362f3c..0000000 --- a/client/src/js/Game/Scoreboard.js +++ /dev/null @@ -1,72 +0,0 @@ -class Scoreboard { - #myHits; - #enemyHits; - #el; - - constructor (el) { - this.#el = { - scoreboard: el || document.createElement('div'), - myHits: document.createElement('div'), - enemyHits: document.createElement('div') - }; - - this.myHits = 0; - this.enemyHits = 0; - - this.#el.scoreboard.classList.add('scoreboard'); - - this.initValues(); - } - - get el () { - return this.#el.scoreboard; - } - - set el (value) { - this.#el = value; - } - - get myHits () { - return this.#myHits; - } - - set myHits (value) { - this.#myHits = value; - this.#el.myHits.textContent = `My Hits: ${value}`; - this.#el.myHits.classList.add('change'); - - setTimeout(() => this.#el.myHits.classList.remove('change'), 700); - } - - get enemyHits () { - return this.#enemyHits; - } - - set enemyHits (value) { - this.#enemyHits = value; - this.#el.enemyHits.textContent = `Enemy Hits: ${value}`; - this.#el.enemyHits.classList.add('change'); - - setTimeout(() => this.#el.enemyHits.classList.remove('change'), 700); - } - - initValues() { - const {scoreboard, myHits, enemyHits} = this.#el; - - myHits.classList.add('hits', 'my-hits'); - enemyHits.classList.add('hits', 'enemy-hits'); - - scoreboard.append(myHits, enemyHits); - } - - reset() { - this.myHits = 0; - this.enemyHits = 0; - } - - destroy () { - this.el.remove(); - } -} - -export default Scoreboard; \ No newline at end of file diff --git a/client/src/js/Game/Scoreboard.ts b/client/src/js/Game/Scoreboard.ts new file mode 100644 index 0000000..72b5d9d --- /dev/null +++ b/client/src/js/Game/Scoreboard.ts @@ -0,0 +1,82 @@ +interface ScoreBoardElements { + scoreboard : HTMLDivElement | HTMLElement; + myHits : HTMLDivElement; + enemyHits : HTMLDivElement; +} + +interface Hits { + my : number; + enemy : number; +} + +class Scoreboard { + private els : ScoreBoardElements; + private hits: Hits; + + constructor (el ?: HTMLElement) { + this.els = { + scoreboard: el || document.createElement('div'), + myHits: document.createElement('div'), + enemyHits: document.createElement('div') + }; + + this.hits = { + my: 0, + enemy: 0, + }; + + this.els.scoreboard.classList.add('scoreboard'); + + this.initValues(); + } + + public get el () { + return this.els.scoreboard; + } + + public get myHits () { + return this.hits.my; + } + + public set myHits (value : number) { + this.hits.my = value; + this.els.myHits.textContent = `My Hits: ${value}`; + this.els.myHits.classList.add('change'); + + setTimeout(() => this.els.myHits.classList.remove('change'), 700); + } + + public get enemyHits () { + return this.hits.enemy; + } + + public set enemyHits (value : number) { + this.hits.enemy = value; + this.els.enemyHits.textContent = `Enemy Hits: ${value}`; + this.els.enemyHits.classList.add('change'); + + setTimeout(() => this.els.enemyHits.classList.remove('change'), 700); + } + + public initValues() : void { + const { scoreboard, myHits, enemyHits } = this.els; + + myHits.classList.add('hits', 'my-hits'); + enemyHits.classList.add('hits', 'enemy-hits'); + + scoreboard.append(myHits, enemyHits); + } + + public reset() : void { + this.hits = { + my: 0, + enemy: 0, + }; + } + + public destroy () : void { + this.el.remove(); + } +} + +export default Scoreboard; diff --git a/client/src/js/Modal.js b/client/src/js/Modal.js deleted file mode 100644 index 32827b1..0000000 --- a/client/src/js/Modal.js +++ /dev/null @@ -1,92 +0,0 @@ -class Modal { - #el; - #modalNodes; - #overlay; - #visible = false; - #template = ` -

- -

- -
- `; - - constructor(el) { - const overlay = document.createElement('div'); - - el = document.querySelector(el); - - overlay.classList.add('modal-overlay'); - - el.classList.add('modal'); - el.innerHTML = this.#template; - el.parentNode.appendChild(overlay); - overlay.appendChild(el); - - this.#el = el; - this.#overlay = overlay; - - this.initNodes(); - this.hide(); - } - - show(bg = 'rgba(0,0,0,0.7)') { - this.#visible = true; - this.#overlay.style.display = 'flex'; - this.#overlay.style.backgroundColor = bg - } - - hide() { - this.#visible = false; - this.#overlay.style.display = 'none'; - } - - toggle() { - this.#overlay.style.display = this.#visible ? 'none' : 'block'; - } - - initNodes() { - const el = this.#el; - - this.#modalNodes = { - title: el.querySelector('.title'), - content: el.querySelector('.content'), - actions: el.querySelector('.actions'), - } - } - - setAction(text, callback, classes = '', tooltip = '') { - const button = document.createElement('button'); - - classes = classes ? `${classes} btn` : 'btn'; - - button.classList.add(...classes.split(' ')); - button.innerHTML = text; - button.addEventListener('click', callback); - - if (tooltip) { - button.dataset.tooltip = tooltip; - button.classList.add('tooltip'); - } - - this.#modalNodes.actions.appendChild(button); - } - - disableAction(id) { - this.#modalNodes.actions.children[id].style.display = 'none'; - } - - enableAction(id) { - this.#modalNodes.actions.children[id].style.display = 'block'; - } - - setTitle(text) { - this.#modalNodes.title.textContent = text; - } - - setContent(html) { - this.#modalNodes.content.innerHTML = html; - } -} - -export default Modal; \ No newline at end of file diff --git a/client/src/js/Modal.ts b/client/src/js/Modal.ts new file mode 100644 index 0000000..35420d9 --- /dev/null +++ b/client/src/js/Modal.ts @@ -0,0 +1,100 @@ +declare interface ModalNodes { + title : HTMLElement, + content : HTMLParagraphElement, + actions : HTMLDivElement, +} + +class Modal { + private el : HTMLElement; + private modalNodes : ModalNodes; + private overlay : HTMLDivElement; + private visible : boolean = false; + private template : string = ` +

+ +

+ +
+ `; + + public constructor(el : string | HTMLElement) { + const overlay = document.createElement('div'); + + el = document.querySelector(el as string) as HTMLElement; + + overlay.classList.add('modal-overlay'); + + el.classList.add('modal'); + el.innerHTML = this.template; + el.parentNode?.appendChild(overlay); + overlay.appendChild(el); + + this.el = el; + this.overlay = overlay; + this.modalNodes = this.initNodes(); + + this.hide(); + } + + public show(bg = 'rgba(0,0,0,0.7)') : void { + this.visible = true; + this.overlay.style.display = 'flex'; + this.overlay.style.backgroundColor = bg + } + + public hide() : void { + this.visible = false; + this.overlay.style.display = 'none'; + } + + public toggle() : void { + this.overlay.style.display = this.visible ? 'none' : 'block'; + } + + public initNodes() : ModalNodes { + const el = this.el; + + return { + title: el.querySelector('.title') as HTMLElement, + content: el.querySelector('.content') as HTMLParagraphElement, + actions: el.querySelector('.actions') as HTMLDivElement, + } + } + + public setAction(text : string, callback : () => void, classes : string = '', tooltip : string = '') : void { + const button = document.createElement('button'); + + classes = classes ? `${classes} btn` : 'btn'; + + button.classList.add(...classes.split(' ')); + button.innerHTML = text; + button.addEventListener('click', callback); + + if (tooltip) { + button.dataset.tooltip = tooltip; + button.classList.add('tooltip'); + } + + this.modalNodes.actions.appendChild(button); + } + + public disableAction(id : number) : void { + const action = this.modalNodes.actions.children[id] as HTMLElement; + action.style.display = 'none'; + } + + public enableAction(id : number) : void { + const action = this.modalNodes.actions.children[id] as HTMLElement; + action.style.display = 'block'; + } + + public setTitle(text : string) : void { + this.modalNodes.title.textContent = text; + } + + public setContent(html : string) : void { + this.modalNodes.content.innerHTML = html; + } +} + +export default Modal; diff --git a/client/src/js/io.js b/client/src/js/io.ts similarity index 81% rename from client/src/js/io.js rename to client/src/js/io.ts index 783dcae..b9530e2 100644 --- a/client/src/js/io.js +++ b/client/src/js/io.ts @@ -2,4 +2,4 @@ import io from 'socket.io-client'; const socket = io('https://edumudu-memory-game.herokuapp.com/'); -export default socket; \ No newline at end of file +export default socket; diff --git a/client/src/js/main.js b/client/src/js/main.ts similarity index 79% rename from client/src/js/main.js rename to client/src/js/main.ts index 3bfee91..6a7d2d7 100644 --- a/client/src/js/main.js +++ b/client/src/js/main.ts @@ -5,12 +5,12 @@ import Board from './Game/Board'; import Modal from './Modal'; import socket from './io'; -const el = document.querySelector('#board'); +const el = document.querySelector('#board') as HTMLElement; const modal = new Modal('#modal'); const menu = new Modal('#menu'); -let game; -let room; +let game : Board; +let room : string; menu.setAction('', () => { socket.emit('room'); @@ -36,7 +36,7 @@ modal.setAction('', () => { game.destroy(); }, '', 'New game'); -socket.on('start-game', (board, playerRoom) => { +socket.on('start-game', (board : any, playerRoom : string) => { room = playerRoom; game = new Board(el, board, socket.id); game.setPlayerTurn(board.playerOfTheTime); @@ -51,19 +51,19 @@ socket.on('start-game', (board, playerRoom) => { menu.show('black'); }) - socket.on('check', ids => { + socket.on('check', (ids : string[]) => { game.check(ids); }) - socket.on('flip', id => { + socket.on('flip', (id : string) => { game.flip(id); }); - socket.on('unflip', ids => { + socket.on('unflip', (ids : string[]) => { setTimeout(() => game.unflip(ids), 800) }); - socket.on('hits', scoreboard => { + socket.on('hits', (scoreboard : Record) => { game.setScoreboard(scoreboard); }); @@ -79,7 +79,7 @@ socket.on('start-game', (board, playerRoom) => { modal.show(); }); - socket.on('toggle-player', player => { + socket.on('toggle-player', (player : string) => { game.setPlayerTurn(player); }) }) diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 0000000..7770ae8 --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "outDir": "./dist/", + "noImplicitAny": true, + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + "removeComments": true, /* Do not emit comments to output. */ + "allowJs": true, + + "strict": true, /* Enable all strict type-checking options. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} diff --git a/client/webpack.config.js b/client/webpack.config.ts similarity index 71% rename from client/webpack.config.js rename to client/webpack.config.ts index 84a9113..b454325 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.ts @@ -1,19 +1,24 @@ -const HTMLWebpackPlugin = require('html-webpack-plugin') -const path = require('path') -const webpack = require('webpack') +import HTMLWebpackPlugin from 'html-webpack-plugin'; +import path from 'path'; +import webpack from 'webpack'; -const PUBLIC_DIR = 'public' +const PUBLIC_DIR : string = 'public' -module.exports = { +const config : webpack.Configuration = { devServer: { contentBase: path.join(__dirname, PUBLIC_DIR), hot: true, port: 3030 }, - entry: path.resolve(__dirname, 'src', 'js','main.js'), + entry: path.resolve(__dirname, 'src', 'js','main.ts'), mode: 'development', module: { rules: [ + { + exclude: /node_modules/, + loader: 'ts-loader', + test: /\.ts$/, + }, { exclude: /node_modules/, loader: 'babel-loader', @@ -51,5 +56,10 @@ module.exports = { }), new webpack.HotModuleReplacementPlugin(), ], - target: 'web' + target: 'web', + resolve: { + extensions: ['.ts', '.js', '.json'] + } } + +export default config; From cf154d463e87612b1263f5827fd6ec53dfca56d9 Mon Sep 17 00:00:00 2001 From: Eduardo Wesley Date: Sun, 26 Jul 2020 22:57:57 -0300 Subject: [PATCH 2/2] #22 convert server side to typescript --- client/src/js/Game/Scoreboard.ts | 4 + server/@types/index.d.ts | 9 ++ server/{app.js => app.ts} | 23 +++-- server/package.json | 9 +- server/src/{Board.js => Board.ts} | 46 ++++++--- server/src/Card.js | 0 server/src/data/{cards.js => cards.ts} | 4 +- server/tsconfig.json | 13 +++ server/yarn.lock | 131 +++++++++++++++++++++++++ 9 files changed, 211 insertions(+), 28 deletions(-) create mode 100644 server/@types/index.d.ts rename server/{app.js => app.ts} (82%) rename server/src/{Board.js => Board.ts} (60%) delete mode 100644 server/src/Card.js rename server/src/data/{cards.js => cards.ts} (93%) create mode 100644 server/tsconfig.json diff --git a/client/src/js/Game/Scoreboard.ts b/client/src/js/Game/Scoreboard.ts index 72b5d9d..5a3332e 100644 --- a/client/src/js/Game/Scoreboard.ts +++ b/client/src/js/Game/Scoreboard.ts @@ -60,9 +60,13 @@ class Scoreboard { public initValues() : void { const { scoreboard, myHits, enemyHits } = this.els; + const { enemy, my } = this.hits; myHits.classList.add('hits', 'my-hits'); enemyHits.classList.add('hits', 'enemy-hits'); + + this.myHits = my; + this.enemyHits = enemy; scoreboard.append(myHits, enemyHits); } diff --git a/server/@types/index.d.ts b/server/@types/index.d.ts new file mode 100644 index 0000000..6f39780 --- /dev/null +++ b/server/@types/index.d.ts @@ -0,0 +1,9 @@ +export interface CardJSON { + id: string; + order: number; + icon: string; +} + +export interface CardAPI { + icon: string; +} diff --git a/server/app.js b/server/app.ts similarity index 82% rename from server/app.js rename to server/app.ts index 082c79b..e3e35d5 100644 --- a/server/app.js +++ b/server/app.ts @@ -1,14 +1,17 @@ -const Express = require('express')(); -const Http = require('http').Server(Express); -const io = require('socket.io')(Http); +import Express from 'express'; +import Http from 'http'; +import socket, { Packet } from 'socket.io'; -const cards = require('./src/data/cards'); -const Board = require('./src/Board'); +const app = new Http.Server(Express()); +const io = socket(app); -const games = {}; +import cards from './src/data/cards'; +import Board from './src/Board'; + +const games : Record = {}; io.on('connection', socket => { - let userRoom; + let userRoom : string; socket.on('room', room => { const rooms = io.sockets.adapter.rooms; @@ -60,7 +63,7 @@ io.on('connection', socket => { const winner = game.checkIfFinish(); if(winner) { - io.in(room).clients((error, clients) => { + io.in(room).clients((error : Packet, clients : string[]) => { if(error) throw error; clients.map(socketId => io.sockets.sockets[socketId]) @@ -83,7 +86,7 @@ io.on('connection', socket => { io.sockets.in(userRoom).emit('enemy-left'); delete games[userRoom]; - io.in(userRoom).clients((error, clients) => { + io.in(userRoom).clients((error : Packet, clients : string[]) => { if(error) throw error; clients.forEach(socketId => io.sockets.sockets[socketId].leave(userRoom)); @@ -91,4 +94,4 @@ io.on('connection', socket => { } }); -Http.listen(process.env.PORT || 3000); \ No newline at end of file +app.listen(process.env.PORT || 3000); diff --git a/server/package.json b/server/package.json index c50b734..25438ea 100644 --- a/server/package.json +++ b/server/package.json @@ -4,7 +4,7 @@ "main": "index.js", "license": "MIT", "scripts": { - "dev": "nodemon app.js", + "dev": "nodemon --watch '*.ts' --exec 'ts-node' app.ts", "start": "node app.js" }, "dependencies": { @@ -12,6 +12,11 @@ "socket.io": "^2.3.0" }, "devDependencies": { - "nodemon": "^2.0.4" + "@types/express": "^4.17.7", + "@types/node": "^14.0.26", + "@types/socket.io": "^2.1.10", + "nodemon": "^2.0.4", + "ts-node": "^8.10.2", + "typescript": "^3.9.7" } } diff --git a/server/src/Board.js b/server/src/Board.ts similarity index 60% rename from server/src/Board.js rename to server/src/Board.ts index e9314e4..0b8fbc1 100644 --- a/server/src/Board.js +++ b/server/src/Board.ts @@ -1,5 +1,20 @@ +import { CardJSON, CardAPI } from "../@types"; + +interface ScoreBoard extends Record{ + total : number; +} + class Board { - constructor (cards, players) { + private elapsedTime : number; + private cards : CardJSON[]; + private players : string[]; + private activesCards : CardJSON[]; + private matches : CardJSON[]; + public scoreboard : ScoreBoard; + public rematchRequests : number; + public playerOfTheTime : string; + + constructor (cards : CardAPI[], players : string[]) { this.elapsedTime = 0; this.cards = this.shuffle([...cards, ...cards].map(card => ({ ...card }))); this.players = players; @@ -11,17 +26,20 @@ class Board { this.playerOfTheTime = players[Math.floor(Math.random() * 2)]; } - shuffle (cards) { - return cards.map(card => { + private shuffle (cards : CardAPI[]) : CardJSON[] { + return cards.map(({ icon }) => { const randomNumber = Math.floor(Math.random() * cards.length); - card.order = randomNumber; - card.id = `_${Math.random().toString(36).substr(2, 9)}`; - + const card : CardJSON = { + icon, + order: randomNumber, + id: `_${Math.random().toString(36).substr(2, 9)}` + }; + return card; }); } - click (id) { + public click (id : string) : CardJSON[] { const card = this.cards.find(card => card.id === id); if(!card || !this.checkIfCanFlip(id) || this.activesCards.length >= 2) return this.activesCards; @@ -31,7 +49,7 @@ class Board { return this.activesCards; } - checkIfMatch () { + public checkIfMatch () : boolean { const [first, second] = this.activesCards; const isMatch = first.icon === second.icon; @@ -41,14 +59,14 @@ class Board { return isMatch; } - checkIfCanFlip (id) { + public checkIfCanFlip (id : string) : boolean { const containInActives = this.activesCards.some(card => card.id === id); const containInMatches = this.matches.some(card => card.id === id); return !(containInActives || containInMatches) } - checkIfFinish () { + public checkIfFinish () : boolean | string { if(this.scoreboard.total === this.cards.length / 2) { return this.players.reduce((winner, client) => this.scoreboard[client] > this.scoreboard[winner] ? client : winner); } @@ -56,15 +74,15 @@ class Board { return false; } - incrementHits () { + public incrementHits () : void { const score = this.scoreboard[this.playerOfTheTime] this.scoreboard[this.playerOfTheTime] = (score || 0) + 1; this.scoreboard.total++; } - togglePlayer () { - this.playerOfTheTime = this.players.find(player => player !== this.playerOfTheTime); + public togglePlayer () : void { + this.playerOfTheTime = this.players.find(player => player !== this.playerOfTheTime) || ''; } } -module.exports = Board; \ No newline at end of file +export default Board; diff --git a/server/src/Card.js b/server/src/Card.js deleted file mode 100644 index e69de29..0000000 diff --git a/server/src/data/cards.js b/server/src/data/cards.ts similarity index 93% rename from server/src/data/cards.js rename to server/src/data/cards.ts index 91c5960..47963b0 100644 --- a/server/src/data/cards.js +++ b/server/src/data/cards.ts @@ -1,6 +1,6 @@ // Simulate a API request -module.exports = [ +export default [ { icon: 'fas spider' }, { icon: 'fas otter' }, { icon: 'fas hippo' }, @@ -12,4 +12,4 @@ module.exports = [ { icon: 'fas dove' }, { icon: 'fas cat' }, { icon: 'fas crow' }, -] \ No newline at end of file +]; diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..c1df235 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + + "strict": true, /* Enable all strict type-checking options. */ + + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} diff --git a/server/yarn.lock b/server/yarn.lock index a997b9f..3f06c0a 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -14,11 +14,88 @@ dependencies: defer-to-connect "^1.0.1" +"@types/body-parser@*": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" + integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== + dependencies: + "@types/connect" "*" + "@types/node" "*" + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== +"@types/connect@*": + version "3.4.33" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546" + integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A== + dependencies: + "@types/node" "*" + +"@types/engine.io@*": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@types/engine.io/-/engine.io-3.1.4.tgz#3d9472711d179daa7c95c051e50ad411e18a9bdc" + integrity sha512-98rXVukLD6/ozrQ2O80NAlWDGA4INg+tqsEReWJldqyi2fulC9V7Use/n28SWgROXKm6003ycWV4gZHoF8GA6w== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@*": + version "4.17.9" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.9.tgz#2d7b34dcfd25ec663c25c85d76608f8b249667f1" + integrity sha512-DG0BYg6yO+ePW+XoDENYz8zhNGC3jDDEpComMYn7WJc4mY1Us8Rw9ax2YhJXxpyk2SF47PQAoQ0YyVT1a0bEkA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@^4.17.7": + version "4.17.7" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.7.tgz#42045be6475636d9801369cd4418ef65cdb0dd59" + integrity sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "*" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/mime@*": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" + integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q== + +"@types/node@*", "@types/node@^14.0.26": + version "14.0.26" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.26.tgz#22a3b8a46510da8944b67bfc27df02c34a35331c" + integrity sha512-W+fpe5s91FBGE0pEa0lnqGLL4USgpLgs4nokw16SrBBco/gQxuua7KnArSEOd5iaMqbbSHV10vUDkJYJJqpXKA== + +"@types/qs@*": + version "6.9.4" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a" + integrity sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ== + +"@types/range-parser@*": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" + integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== + +"@types/serve-static@*": + version "1.13.4" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.4.tgz#6662a93583e5a6cabca1b23592eb91e12fa80e7c" + integrity sha512-jTDt0o/YbpNwZbQmE/+2e+lfjJEJJR0I3OFaKQKPWkASkCoW3i6fsUnqudSMcNAfbtmADGu8f4MV4q+GqULmug== + dependencies: + "@types/express-serve-static-core" "*" + "@types/mime" "*" + +"@types/socket.io@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@types/socket.io/-/socket.io-2.1.10.tgz#cb00583313cc40a65d5201e897d7b8c95cfd8c73" + integrity sha512-1fQMaDU/x2LPljEI/QI5IKl8sBYHM/zv32YYKvNrVEor7/1+MLqMqmWt8Bb8Vpf+PlIPBiTTC0BnrRx7ju3xOw== + dependencies: + "@types/engine.io" "*" + "@types/node" "*" + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -70,6 +147,11 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -167,6 +249,11 @@ braces@~3.0.2: dependencies: fill-range "^7.0.1" +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -371,6 +458,11 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + dot-prop@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" @@ -802,6 +894,11 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -1179,6 +1276,19 @@ socket.io@^2.3.0: socket.io-client "2.3.0" socket.io-parser "~3.4.0" +source-map-support@^0.5.17: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -1269,6 +1379,17 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" +ts-node@^8.10.2: + version "8.10.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" + integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" @@ -1289,6 +1410,11 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typescript@^3.9.7: + version "3.9.7" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" + integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== + undefsafe@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" @@ -1392,3 +1518,8 @@ yeast@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==