diff --git a/sample-components-tictactoe/package.json b/sample-components-tictactoe/package.json index 7b27cfe..dd98aaf 100644 --- a/sample-components-tictactoe/package.json +++ b/sample-components-tictactoe/package.json @@ -6,26 +6,30 @@ "private": true, "devDependencies": { "@babel/core": "^7.5.5", - "@babel/plugin-transform-react-jsx": "^7.3.0", "@babel/plugin-proposal-class-properties": "^7.5.5", + "@babel/plugin-transform-react-jsx": "^7.3.0", + "@types/react": "^16.9.11", "eslint-config-semistandard": "^13.0.0", "eslint-config-standard": "^12.0.0", "eslint-plugin-import": "^2.18.2", "eslint-plugin-node": "^8.0.1", "eslint-plugin-promise": "^4.0.1", "eslint-plugin-standard": "^4.0.0", - "magic-script-typings": "1.4.0", + "magic-script-typings": "~1.5.0", "rollup": "^1.1.2", "rollup-plugin-babel": "^4.3.2", "rollup-plugin-commonjs": "https://github.com/creationix/rollup-plugin-commonjs.git", "rollup-plugin-node-resolve": "^4.0.0", - "semistandard": "^13.0.1" + "rollup-plugin-typescript": "^1.0.1", + "semistandard": "^13.0.1", + "tslib": "^1.10.0", + "typescript": "^3.6.4" }, "dependencies": { "eslint-plugin-react": "^7.15.1", "firebase": "^7.0.0", - "magic-script-components": "2.0.0", - "magic-script-components-lumin": "1.0.0", + "magic-script-components": "^2.0.0", + "magic-script-components-lumin": "^1.0.7", "magic-script-polyfills": "^2.4.3" } } diff --git a/sample-components-tictactoe/rollup.config.js b/sample-components-tictactoe/rollup.config.js index 0734e48..50f3aeb 100644 --- a/sample-components-tictactoe/rollup.config.js +++ b/sample-components-tictactoe/rollup.config.js @@ -1,20 +1,41 @@ -// Rollup config for consuming some npm modules in MagicScript - import babel from 'rollup-plugin-babel'; import resolve from 'rollup-plugin-node-resolve'; import commonjs from 'rollup-plugin-commonjs'; +import typescript from 'rollup-plugin-typescript'; -export default { - external: ['uv', 'lumin', 'ssl'], - input: 'src/main.js', - preserveModules: true, - output: { - dir: 'bin', - format: 'es' - }, +const common = { plugins: [ - babel({ exclude: "node_modules/**" }), + babel({ exclude: 'node_modules/**' }), resolve(), - commonjs() + commonjs(), + typescript() ] }; + +export default [ + // Build for MagicScript on LuminOS + { + ...common, + external: ['uv', 'lumin', 'ssl', 'jpeg', 'png', 'gl'], + input: 'src/main.tsx', + preserveModules: true, + output: { + dir: 'bin', + format: 'es' + } + }, + // Build for MagicScript on Magicverse (iOS, Android) + { + ...common, + input: 'src/app.tsx', + external: ['react'], + output: { + globals: { + 'react': 'React' + }, + file: 'bin/bundle.js', + format: 'iife', + name: '_' + } + } +]; diff --git a/sample-components-tictactoe/src/app.js b/sample-components-tictactoe/src/app.js deleted file mode 100644 index 0c67a4b..0000000 --- a/sample-components-tictactoe/src/app.js +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import firebase from 'firebase/app'; - -import { View } from 'magic-script-components'; -import { PlayerChooser, Game } from './components/index.js'; - -// Top-level application component -- renders either a player chooser or the game -export class TicTacToeApp extends React.Component { - constructor (props) { - super(props); - - this.state = { player: '?' }; - this.onPlayerChosen = this.onPlayerChosen.bind(this); - } - - useFirebase () { - return this.props.firebaseConfig && this.props.firebaseConfig.hasOwnProperty('apiKey'); - } - - componentDidMount () { - if (this.useFirebase()) { - firebase.initializeApp(this.props.firebaseConfig); - } - } - - onPlayerChosen (player) { - this.setState({ player: player }); - } - - render () { - return ( - - {this.state.player === '?' - ? - : ( - this.onPlayerChosen('?')} - enableFirebase={this.useFirebase()} - /> - )} - - ); - } -} - -TicTacToeApp.propTypes = { - firebaseConfig: PropTypes.object -}; diff --git a/sample-components-tictactoe/src/app.tsx b/sample-components-tictactoe/src/app.tsx new file mode 100644 index 0000000..40bf563 --- /dev/null +++ b/sample-components-tictactoe/src/app.tsx @@ -0,0 +1,53 @@ +import React from 'react'; + +import firebase from 'firebase/app'; + +import { View, AppProps } from 'magic-script-components'; +import { PlayerChooser, Game } from './components/index.js'; +import { Player } from './components/square.js'; + +interface Props extends AppProps { + firebaseConfig?: object; +} + +interface State { + // Use null to indicate that no player has been chosen yet + // (Player.None is used to play for both X and O) + player: Player | null; +} + +// Top-level application component -- renders either a player chooser or the game +export class TicTacToeApp extends React.Component { + state: State = { player: null }; + + useFirebase (): boolean { + return this.props.firebaseConfig !== undefined && this.props.firebaseConfig !== null + && this.props.firebaseConfig.hasOwnProperty('apiKey'); + } + + componentDidMount () { + if (this.useFirebase()) { + firebase.initializeApp(this.props.firebaseConfig!); + } + } + + onPlayerChosen = (player: Player | null) => { + this.setState({ player }); + } + + render () { + return ( + + {this.state.player === null + ? + : ( + this.onPlayerChosen(null)} + enableFirebase={this.useFirebase()} + /> + )} + + ); + } +} diff --git a/sample-components-tictactoe/src/components/board.js b/sample-components-tictactoe/src/components/board.tsx similarity index 66% rename from sample-components-tictactoe/src/components/board.js rename to sample-components-tictactoe/src/components/board.tsx index 3e4d923..61df618 100644 --- a/sample-components-tictactoe/src/components/board.js +++ b/sample-components-tictactoe/src/components/board.tsx @@ -1,12 +1,16 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { GridLayout } from 'magic-script-components'; -import { Square } from './index.js'; +import Square, { Player } from './square'; + +interface Props { + squares: Player[]; + onClick: (i: number) => void; +} // Component that renders the full Tic Tac Toe board -export default function Board (props) { - const renderSquare = (props, i) => { +export default function Board (props: Props) { + const renderSquare = (props: Props, i: number) => { return ( {items}; } - -Board.propTypes = { - squares: PropTypes.arrayOf(PropTypes.string), - onClick: PropTypes.func -}; diff --git a/sample-components-tictactoe/src/components/calculate-winner.js b/sample-components-tictactoe/src/components/calculate-winner.ts similarity index 72% rename from sample-components-tictactoe/src/components/calculate-winner.js rename to sample-components-tictactoe/src/components/calculate-winner.ts index c0a5797..2e65dca 100644 --- a/sample-components-tictactoe/src/components/calculate-winner.js +++ b/sample-components-tictactoe/src/components/calculate-winner.ts @@ -1,4 +1,6 @@ -export function calculateWinner (squares) { +import { Player } from "./square"; + +export default function calculateWinner (squares: Player[]): Player { const lines = [ [0, 1, 2], [3, 4, 5], @@ -15,5 +17,5 @@ export function calculateWinner (squares) { return squares[a]; } } - return null; + return Player.None; } diff --git a/sample-components-tictactoe/src/components/game-info.js b/sample-components-tictactoe/src/components/game-info.tsx similarity index 67% rename from sample-components-tictactoe/src/components/game-info.js rename to sample-components-tictactoe/src/components/game-info.tsx index 39e0749..3a8030d 100644 --- a/sample-components-tictactoe/src/components/game-info.js +++ b/sample-components-tictactoe/src/components/game-info.tsx @@ -1,18 +1,21 @@ +import { ListView, ListViewItem, Text, View, Button } from 'magic-script-components'; import React from 'react'; -import PropTypes from 'prop-types'; +import calculateWinner from './calculate-winner'; +import { HistoryItem } from './game'; +import { Player } from './square'; -import { - Button, - ListView, - ListViewItem, - Text, - View -} from 'magic-script-components'; -import { calculateWinner } from './calculate-winner.js'; +interface Props { + history: HistoryItem[]; + onHistoryClick: (move: number) => void; + player: Player; + onChoosePlayer: () => void; + stepNumber: number; + xIsNext: boolean; +} // Component that renders current game status and history of moves -export default function GameInfo (props) { +export default function GameInfo (props: Props) { const current = props.history[props.stepNumber]; const winner = calculateWinner(current.squares); @@ -39,16 +42,16 @@ export default function GameInfo (props) { ); let status; - if (winner) { - if (props.player === ' ') { + if (winner != Player.None) { + if (props.player === Player.None) { status = winner + ' Wins!'; } else { status = (winner === props.player) ? 'You Win!' : 'You Lose!'; } } else { - const nextPlayer = props.xIsNext ? 'X' : 'O'; + const nextPlayer = props.xIsNext ? Player.X : Player.O; status = 'Next player: ' + nextPlayer; - if (props.player !== ' ') { + if (props.player !== Player.None) { status += (nextPlayer === props.player) ? ' (You)' : ' (Opponent)'; } } @@ -60,12 +63,3 @@ export default function GameInfo (props) { ); } - -GameInfo.propTypes = { - history: PropTypes.array, - onHistoryClick: PropTypes.func, - player: PropTypes.string, - onChoosePlayer: PropTypes.func, - stepNumber: PropTypes.number, - xIsNext: PropTypes.bool -}; diff --git a/sample-components-tictactoe/src/components/game.js b/sample-components-tictactoe/src/components/game.tsx similarity index 67% rename from sample-components-tictactoe/src/components/game.js rename to sample-components-tictactoe/src/components/game.tsx index fe17b8c..3379d08 100644 --- a/sample-components-tictactoe/src/components/game.js +++ b/sample-components-tictactoe/src/components/game.tsx @@ -1,27 +1,39 @@ import React from 'react'; -import PropTypes from 'prop-types'; import firebase from 'firebase/app'; import 'firebase/database'; import { View } from 'magic-script-components'; -import { Board, GameInfo } from './index.js'; -import { calculateWinner } from './calculate-winner.js'; +import calculateWinner from './calculate-winner.js'; +import Board from './board'; +import GameInfo from './game-info'; +import { Player } from './square.js'; + +export interface HistoryItem { + squares: Player[] +} + +interface Props { + enableFirebase: boolean; + player: Player; + onChoosePlayer: () => void; +} + +const initialState = { + history: [ + { + squares: Array(9).fill(Player.None) + } + ], + stepNumber: 0, + xIsNext: true +}; + +type State = Readonly; // The main gameplay component that renders the Tic Tac Toe board -export default class Game extends React.Component { - constructor (props) { - super(props); - this.state = { - history: [ - { - squares: Array(9).fill('') - } - ], - stepNumber: 0, - xIsNext: true - }; - } +export default class Game extends React.Component { + state: State = initialState; componentDidMount () { if (this.props.enableFirebase) { @@ -41,11 +53,11 @@ export default class Game extends React.Component { return firebase.database().ref('tic-tac-toe'); } - updateState (partialNewState) { + updateState (partialNewState: Pick) { this.setState(partialNewState, () => this.syncStateChange(this.state)); } - syncStateChange (state) { + syncStateChange (state: State) { if (this.props.enableFirebase) { this.dbref().set(state, (error) => { console.error('Error writing state update', error); @@ -53,15 +65,15 @@ export default class Game extends React.Component { } } - handleClick (i) { + handleClick (i: number) { const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); - const nextPlayer = this.state.xIsNext ? 'X' : 'O'; - if (this.props.player !== ' ' && this.props.player !== nextPlayer) { + const nextPlayer = this.state.xIsNext ? Player.X : Player.O + if (this.props.player !== Player.None && this.props.player !== nextPlayer) { return; } - if (calculateWinner(squares) || squares[i]) { + if (calculateWinner(squares) !== Player.None || squares[i] !== Player.None) { return; } squares[i] = nextPlayer; @@ -76,7 +88,7 @@ export default class Game extends React.Component { }); } - jumpTo (step) { + jumpTo (step: number) { this.updateState({ stepNumber: step, xIsNext: (step % 2) === 0 @@ -109,9 +121,3 @@ export default class Game extends React.Component { ); } } - -Game.propTypes = { - enableFirebase: PropTypes.bool, - player: PropTypes.string, - onChoosePlayer: PropTypes.func -}; diff --git a/sample-components-tictactoe/src/components/index.js b/sample-components-tictactoe/src/components/index.js deleted file mode 100644 index 0637ec7..0000000 --- a/sample-components-tictactoe/src/components/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export { default as Board } from './board.js'; -export { default as Game } from './game.js'; -export { default as GameInfo } from './game-info.js'; -export { default as PlayerChooser } from './player-chooser.js'; -export { default as Square } from './square.js'; diff --git a/sample-components-tictactoe/src/components/index.ts b/sample-components-tictactoe/src/components/index.ts new file mode 100644 index 0000000..809efce --- /dev/null +++ b/sample-components-tictactoe/src/components/index.ts @@ -0,0 +1,5 @@ +export { default as Board } from './board'; +export { default as Game } from './game'; +export { default as GameInfo } from './game-info'; +export { default as PlayerChooser } from './player-chooser'; +export { default as Square } from './square'; diff --git a/sample-components-tictactoe/src/components/player-chooser.js b/sample-components-tictactoe/src/components/player-chooser.tsx similarity index 50% rename from sample-components-tictactoe/src/components/player-chooser.js rename to sample-components-tictactoe/src/components/player-chooser.tsx index 674be09..3b202d6 100644 --- a/sample-components-tictactoe/src/components/player-chooser.js +++ b/sample-components-tictactoe/src/components/player-chooser.tsx @@ -1,32 +1,31 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { LinearLayout, Text } from 'magic-script-components'; -import { Square } from './index.js'; +import Square, { Player } from './square'; -export default function PlayerChooser (props) { +interface Props { + onPlayerChosen: (player: Player) => void; +} + +export default function PlayerChooser (props: Props) { return ( Choose a player: props.onPlayerChosen('X')} + onClick={event => props.onPlayerChosen(Player.X)} /> props.onPlayerChosen('O')} + onClick={event => props.onPlayerChosen(Player.O)} /> props.onPlayerChosen(' ')} + onClick={event => props.onPlayerChosen(Player.None)} /> ); } - -PlayerChooser.propTypes = { - onPlayerChosen: PropTypes.func -}; diff --git a/sample-components-tictactoe/src/components/square.js b/sample-components-tictactoe/src/components/square.js deleted file mode 100644 index 73a0d3a..0000000 --- a/sample-components-tictactoe/src/components/square.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { Button } from 'magic-script-components'; - -// Component that renders a single square in the Tic Tac Toe board -export default function Square (props) { - const color = props.value === 'X' ? [1, 0.1, 0.1, 1] : [0.1, 0.1, 1, 1]; - return ( - - ); -} - -Square.propTypes = { - name: PropTypes.string, - onClick: PropTypes.func, - value: PropTypes.string -}; diff --git a/sample-components-tictactoe/src/components/square.tsx b/sample-components-tictactoe/src/components/square.tsx new file mode 100644 index 0000000..49bb86b --- /dev/null +++ b/sample-components-tictactoe/src/components/square.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +import { Button, vec4 } from 'magic-script-components'; + +export enum Player { + X = 'X', + O = 'O', + None = ' ' +} + +interface Props { + name: string; + value: Player; + onClick: (event: any) => void; +} + +// Component that renders a single square in the Tic Tac Toe board +export default function Square (props: Props) { + const color: vec4 = props.value === Player.X ? [1, 0.1, 0.1, 1] : [0.1, 0.1, 1, 1]; + return ( + + ); +} diff --git a/sample-components-tictactoe/src/main.js b/sample-components-tictactoe/src/main.tsx similarity index 85% rename from sample-components-tictactoe/src/main.js rename to sample-components-tictactoe/src/main.tsx index 247d80a..c0ae1b2 100644 --- a/sample-components-tictactoe/src/main.js +++ b/sample-components-tictactoe/src/main.tsx @@ -2,12 +2,15 @@ // Simply importing this sets all these as global definitions. // They are declared in the .eslintrc so your editor won't complain. import 'magic-script-polyfills'; -import process from './global-scope.js'; // eslint-disable-line no-unused-vars +import process from './global-scope'; import React from 'react'; import mxs from 'magic-script-components-lumin'; +// Reference process so it isn't stripped +typeof process; + // Load main app logic from the app class. -import { TicTacToeApp } from './app.js'; +import { TicTacToeApp } from './app'; // To enable firebase, copy firebaseConfig from your Firebase project console diff --git a/sample-components-tictactoe/tsconfig.json b/sample-components-tictactoe/tsconfig.json index 41eb6d1..2459787 100644 --- a/sample-components-tictactoe/tsconfig.json +++ b/sample-components-tictactoe/tsconfig.json @@ -1,8 +1,12 @@ { "compilerOptions": { - "allowJs": true, - "noEmit": true, - "typeRoots" : ["./node_modules/magic-script-typings", "./node_modules/@types"] - } + "allowJs": true, + "noEmit": true, + "strict": true, + "target": "esnext", + "jsx": "react", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true + } } -