diff --git a/api/games/games.js b/api/games/games.js deleted file mode 100644 index 8fe11b68a24f4e6c2b4def58882eda3410be2b00..0000000000000000000000000000000000000000 --- a/api/games/games.js +++ /dev/null @@ -1,180 +0,0 @@ -import { Mongo } from 'meteor/mongo' -import SimpleSchema from 'simpl-schema' -import { ValidatedMethod } from 'meteor/mdg:validated-method' -import { loggedInMixin, schemaMixin } from '../mixins' -import { Meteor } from 'meteor/meteor' -import { Card } from './cards' -import { Bet, GameSystem } from './logic' - -export const Games = new Mongo.Collection('games') - -export const notInGameMixin = method => { - let run = method.run - method.run = function () { - if (Games.find({'players.id': this.userId}).count() > 0) throw new Meteor.Error('already_in_game') - return run.call(this, ...arguments) - } - return method -} - -export const inGameMixin = method => { - let run = method.run - method.run = function () { - this.game = Games.findOne({'players.id': this.userId}) - if (this.game == null) throw new Meteor.Error('not_in_game') - this.game.sys = GameSystem.fromObject(this.game.sys) - return run.call(this, ...arguments) - } - return method -} - -const PlayerSchema = new SimpleSchema({ - id: String, - position: {type: Number, allowedValues: [0, 1, 2, 3]} -}) - -const GameSchema = new SimpleSchema({ - name: String, - players: {type: Array, defaultValue: [], optional: true}, - 'players.$': PlayerSchema, - sys: {type: GameSystem, defaultValue: new GameSystem(), optional: true} -}) - -export const newGame = new ValidatedMethod({ - name: 'Game.new', - mixins: [schemaMixin, loggedInMixin, notInGameMixin], - validate: new SimpleSchema({ - name: String - }), - run (game) { - if (Games.find(game).count() === 0) { - game = GameSchema.clean(game) - game.sys = game.sys.toObject() - return Games.insert(game) - } else { - throw new Meteor.Error('name_taken') - } - } -}) - -export const joinGame = new ValidatedMethod({ - name: 'Game.join', - mixins: [schemaMixin, loggedInMixin, notInGameMixin], - validate: new SimpleSchema({ - _id: String - }), - run ({_id}) { - let game = Games.findOne({_id}) - if (game == null) throw new Meteor.Error('game_not_found') - if (game.players.length === 4) throw new Meteor.Error('game_full') - game.players.push({id: this.userId, position: game.players.length}) - if (game.players.length === 4) { - game.sys = GameSystem.fromObject(game.sys) - game.sys.newRound() - game.sys = game.sys.toObject() - } - Games.update({_id}, {$set: game}) - } -}) - -export const nextCards = new ValidatedMethod({ - name: 'Game.nextCards', - mixins: [schemaMixin, loggedInMixin, inGameMixin], - validate: new SimpleSchema({}), - run () { - let player = this.game.players.findIndex(p => p.id === this.userId) - if (!this.game.sys.nextCards(player)) { - throw new Meteor.Error('next_cards') - } - this.game.sys = this.game.sys.toObject() - Games.update({_id: this.game._id}, {$set: this.game}) - } -}) - -export const giveCards = new ValidatedMethod({ - name: 'Game.giveCards', - mixins: [schemaMixin, loggedInMixin, inGameMixin], - validate: new SimpleSchema({ - cards: {type: Array, minCount: 3, maxCount: 3}, - 'cards.$': Card - }), - run ({cards}) { - let player = this.game.players.findIndex(p => p.id === this.userId) - if (!this.game.sys.giveCards(player, cards)) { - throw new Meteor.Error('give_cards') - } - this.game.sys = this.game.sys.toObject() - Games.update({_id: this.game._id}, {$set: this.game}) - } -}) - -export const playCards = new ValidatedMethod({ - name: 'Game.playCards', - mixins: [schemaMixin, loggedInMixin, inGameMixin], - validate: new SimpleSchema({ - cards: [Card] - }), - run ({cards}) { - let player = this.game.players.findIndex(p => p.id === this.userId) - - if (!this.game.sys.playCards(player, cards)) { - throw new Meteor.Error('play_cards') - } - - let winner = this.game.sys.winner() - - if (winner >= 0) { - Games.remove({_id: this.game._id}) - return winner - } else { - this.game.sys = this.game.sys.toObject() - Games.update({_id: this.game._id}, {$set: this.game}) - } - } -}) - -export const skipTurn = new ValidatedMethod({ - name: 'Game.skipTurn', - mixins: [schemaMixin, loggedInMixin, inGameMixin], - validate: new SimpleSchema({}), - run () { - let player = this.game.players.findIndex(p => p.id === this.userId) - if (!this.game.sys.skipTurn(player)) { - throw new Meteor.Error('skip_turn') - } - this.game.sys = this.game.sys.toObject() - Games.update({_id: this.game._id}, {$set: this.game}) - } -}) - -export const giveDragon = new ValidatedMethod({ - name: 'game.giveDragon', - mixins: [schemaMixin, loggedInMixin, inGameMixin], - validate: new SimpleSchema({ - to: {type: Number, allowedValues: [0, 1]} - }), - run ({to}) { - let player = this.game.players.findIndex(p => p.id === this.userId) - if (!this.game.sys.giveDragon(player, to)) { - throw new Meteor.Error('give_dragon') - } - this.game.sys = this.game.sys.toObject() - Games.update({_id: this.game._id}, {$set: this.game}) - } -}) - -export const makeCall = new ValidatedMethod({ - name: 'Game.makeCall', - mixins: [schemaMixin, loggedInMixin, inGameMixin], - validate: new SimpleSchema({ - bet: {type: Number, allowedValues: [Bet.TICHU, Bet.GRAND]} - }), - run ({bet}) { - let player = this.game.players.findIndex(p => p.id === this.userId) - if (!this.game.sys.makeCall(player, bet)) { - throw new Meteor.Error('make_call') - } - this.game.sys = this.game.sys.toObject() - Games.update({_id: this.game._id}, {$set: this.game}) - } -}) diff --git a/package-lock.json b/package-lock.json index d33ca0c14d7b685f0a360a27c5f5e7e22ce5b62a..a4841c2748a3c78fcccac0fd883cd1994adabba6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -187,6 +187,16 @@ "@types/react": "*" } }, + "@types/react-router-dom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-4.3.1.tgz", + "integrity": "sha512-GbztJAScOmQ/7RsQfO4cd55RuH1W4g6V1gDW3j4riLlt+8yxYLqqsiMzmyuXBLzdFmDtX/uU2Bpcm0cmudv44A==", + "requires": { + "@types/history": "*", + "@types/react": "*", + "@types/react-router": "*" + } + }, "@types/underscore": { "version": "1.8.9", "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.8.9.tgz", diff --git a/package.json b/package.json index 03c97a29023458b87118ba2f9b91d9b66600c771..37d7a18a863ba82c75de58d2eeb502fc948ede78 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@types/meteor": "^1.4.22", "@types/react-dom": "^16.0.9", "@types/react-router": "^4.0.32", + "@types/react-router-dom": "^4.3.1", "bcrypt": "^3.0.1", "chai": "^4.2.0", "collections": "^5.1.5", diff --git a/startup/routes.tsx b/startup/routes.tsx index e9b5d06c89b0cf9a3b3ac3e178a7d3e8f025b3a7..f1f8e016490188a9b02c090e7623cc6c0a34099c 100644 --- a/startup/routes.tsx +++ b/startup/routes.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { Route, Router, Switch } from 'react-router' import createBrowserHistory from 'history/createBrowserHistory' -import GameListPage from '../ui/pages/game_list.jsx' +import GameListPage from '../ui/pages/game_list' import GamePage from '../ui/pages/game.jsx' import IndexPage from '../ui/pages/index' diff --git a/tsconfig.json b/tsconfig.json index f09efd40e3f318645ae8e46dc276d0059c35cdc9..80d7d97d5404f03461a434225a2e02a60b7a96e6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,6 @@ { "compilerOptions": { "target": "es5", - "module": "commonjs", "removeComments": true, "sourceMap": true, "downlevelIteration": true, @@ -23,7 +22,8 @@ "server/*", "api/**/*", "startup/**/*", - "ui/**/*" + "ui/**/*", + "typings/*" ], "exclude": [ "node_modules", diff --git a/typings/globals.d.ts b/typings/globals.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..ed1c1bb3b0dfa48a566379ea9cb83c4dbe826b50 --- /dev/null +++ b/typings/globals.d.ts @@ -0,0 +1,7 @@ +interface Function { + name: string +} + +interface Array<T> { + findIndex (predicate: (value: T, index: number, obj: Array<T>) => boolean, thisArg?: any): number +} diff --git a/typings/mdg_validated-method.d.ts b/typings/mdg_validated-method.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..c3cbe6d1506689f283acade3cb50688a3cab108a --- /dev/null +++ b/typings/mdg_validated-method.d.ts @@ -0,0 +1,22 @@ +declare module 'meteor/mdg:validated-method' { + class ValidatedMethod<A, R> extends MeteorValidatedMethod.ValidatedMethod<A, R> {} + + module MeteorValidatedMethod { + export class ValidatedMethod<A, R> { + constructor (options: ValidatedMethodOptions<A, R>); + + call (options?: A, cb?: (err: Error, res: R) => void): void; + + run (this: any, opts: A): R; + } + + interface ValidatedMethodOptions<A, R> { + name: string; + mixins?: ((method: ValidatedMethod<A, R>) => ValidatedMethod<A, R>)[]; + validate: any; + applyOptions?: any; + + run (this: any, opts: A): R; + } + } +} \ No newline at end of file diff --git a/typings/react-meteor-data.d.ts b/typings/react-meteor-data.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..ef495556f398c215b519fdad87f1e23cd4aa3c28 --- /dev/null +++ b/typings/react-meteor-data.d.ts @@ -0,0 +1,5 @@ +declare module 'meteor/react-meteor-data' { + function withTracker<T> ( + getProps: () => T, + ): (component: React.ComponentClass<T> | React.StatelessComponent<T>) => React.ComponentClass<T>; +} diff --git a/typings/simpl-schema.d.ts b/typings/simpl-schema.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..2895b48d15dca896a5b776b59e113cc81311492e --- /dev/null +++ b/typings/simpl-schema.d.ts @@ -0,0 +1,166 @@ +declare module 'simpl-schema' { + /* + class ValidationContext { + constructor (ss: any); + + addValidationErrors (errors: any): void; + + clean (...args: any[]): any; + + getErrorForKey (key: any, ...args: any[]): any; + + isValid (): any; + + keyErrorMessage (key: any, ...args: any[]): any; + + keyIsInvalid (key: any, ...args: any[]): any; + + reset (): void; + + setValidationErrors (errors: any): void; + + validate (obj: any, ...args: any[]): any; + + validationErrors (): any; + } + // */ + interface SchemaDefinition { + type: any; + label?: string | Function; + optional?: boolean | Function; + min?: number | boolean | Date | Function; + max?: number | boolean | Date | Function; + minCount?: number | Function; + maxCount?: number | Function; + allowedValues?: any[] | Function; + decimal?: boolean; + exclusiveMax?: boolean; + exclusiveMin?: boolean; + regEx?: RegExp | RegExp[]; + custom?: Function; + blackbox?: boolean; + autoValue?: Function; + defaultValue?: any; + trim?: boolean; + } + + interface CleanOption { + filter?: boolean; + autoConvert?: boolean; + removeEmptyStrings?: boolean; + trimStrings?: boolean; + getAutoValues?: boolean; + isModifier?: boolean; + extendAutoValueContext?: boolean; + } + + class SimpleSchema { + constructor (schema: { [key: string]: SchemaDefinition | any } | any[]); + + debug: boolean + + namedContext (name?: string): SimpleSchemaValidationContextStatic; + + addValidator (validator: Function): any; + + pick (...fields: string[]): SimpleSchema; + + omit (...fields: string[]): SimpleSchema; + + clean (doc: any, options?: CleanOption): any; + + schema (key?: string): SchemaDefinition | SchemaDefinition[]; + + getDefinition (key: string, propList?: any, functionContext?: any): any; + + keyIsInBlackBox (key: string): boolean; + + labels (labels: { [key: string]: string }): void; + + label (key: any): any; + + Integer: RegExp + + messages (messages: any): void; + + messageForError (type: any, key: any, def: any, value: any): string; + + allowsKey (key: any): string; + + newContext (): SimpleSchemaValidationContextStatic; + + objectKeys (keyPrefix: any): any[]; + + validate (obj: any, options?: ValidationOption): void; + + validator (options?: ValidationOption): Function; + + RegEx: { + Email: RegExp; + EmailWithTLD: RegExp; + Domain: RegExp; + WeakDomain: RegExp; + IP: RegExp; + IPv4: RegExp; + IPv6: RegExp; + Url: RegExp; + Id: RegExp; + ZipCode: RegExp; + Phone: RegExp; + } + } + + interface ValidationOption { + modifier?: boolean; + upsert?: boolean; + clean?: boolean; + filter?: boolean; + upsertextendedCustomContext?: boolean; + } + + interface SimpleSchemaValidationContextStatic { + validate (obj: any, options?: ValidationOption): boolean; + + validateOne (doc: any, keyName: string, options?: ValidationOption): boolean; + + resetValidation (): void; + + isValid (): boolean; + + invalidKeys (): { name: string; type: string; value?: any; }[]; + + addInvalidKeys (errors: { name: string, type: string; }[]): void; + + keyIsInvalid (name: any): boolean; + + keyErrorMessage (name: any): string; + + getErrorObject (): any; + } + + /* + interface SimpleSchema { + debug: boolean; + + addValidator (validator: Function): any; + + extendOptions (options: { [key: string]: any }): void; + + messages (messages: any): void; + + RegEx: { + Email: RegExp; + Domain: RegExp; + WeakDomain: RegExp; + IP: RegExp; + IPv4: RegExp; + IPv6: RegExp; + Url: RegExp; + Id: RegExp; + ZipCode: RegExp; + Phone: RegExp; + }; + } +// */ + export default SimpleSchema +} \ No newline at end of file diff --git a/ui/components/game_entry.jsx b/ui/components/game_entry.tsx similarity index 62% rename from ui/components/game_entry.jsx rename to ui/components/game_entry.tsx index c5ac5c7535dd32bb89582301966990a94fc15f91..6311e9f7dfec03e7da03c64e1a38976c4ba78d42 100644 --- a/ui/components/game_entry.jsx +++ b/ui/components/game_entry.tsx @@ -1,7 +1,7 @@ -import React from 'react' +import * as React from 'react' import { Link } from 'react-router-dom' -export default class GameEntry extends React.Component { +export default class GameEntry extends React.Component<{ game: {} }> { render () { return ( <li><Link to={'/games/' + this.props.game._id}>{this.props.game.name}</Link></li> diff --git a/ui/pages/game.jsx b/ui/pages/game.jsx index 762a95e7d4f310518a2b8597f76aa962e56de90e..409233a5fe196a7c7909d04155d44b017e5b01e3 100644 --- a/ui/pages/game.jsx +++ b/ui/pages/game.jsx @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor' import React from 'react' import { withTracker } from 'meteor/react-meteor-data' -import { Games, joinGame } from '../../api/games/games.js' +import { Games, joinGame } from '../../api/games/games.ts' import PlayerEntry from '../components/player_entry.jsx' import TichuGame from '../tichu_game' import { GameSystem } from '../../api/games/logic.js' diff --git a/ui/pages/game_list.jsx b/ui/pages/game_list.tsx similarity index 54% rename from ui/pages/game_list.jsx rename to ui/pages/game_list.tsx index 7185e4baecec926a67c89a75d0aff462f5b38c3f..55877468f65d6cb9f11637731bf9e90ca5eebee8 100644 --- a/ui/pages/game_list.jsx +++ b/ui/pages/game_list.tsx @@ -1,14 +1,18 @@ -import React from 'react' +import * as React from 'react' import { withTracker } from 'meteor/react-meteor-data' -import GameEntry from '../components/game_entry.jsx' -import { Games, newGame } from '../../api/games/games.js' +import GameEntry from '../components/game_entry' +import { Games, newGame } from '../../api/games/games' -class GameList extends React.Component { - onSubmit (event) { +interface SubmitEvent extends Event { + target: HTMLFormElement +} + +class GameList extends React.Component<{ games: Array<{}> }> { + onSubmit (event: SubmitEvent) { event.preventDefault() - let name = event.target.name.value - newGame.call({name}, (err, res) => { + let name = event.target.tableName.value + newGame.call({name}, (err: Error) => { if (err) console.dir(err) }) } @@ -17,7 +21,7 @@ class GameList extends React.Component { return ( <div> <form onSubmit={this.onSubmit.bind(this)}> - <input type="text" name="name" placeholder="Game name"/> + <input type="text" name="tableName" placeholder="Game name"/> <input type="submit" value="Create a new game"/> </form> <ul>