it works! (feature parity coming soon™️)
|
@ -1,36 +1,36 @@
|
|||
import asyncio, time, datetime, games, json, threading, jinja2, leagues
|
||||
from flask import Flask, url_for, Response, render_template, request, jsonify
|
||||
import asyncio, time, datetime, games, json, threading, jinja2, leagues, os
|
||||
from flask import Flask, url_for, Response, render_template, request, jsonify, send_from_directory
|
||||
from flask_socketio import SocketIO, emit
|
||||
|
||||
app = Flask("the-prestige")
|
||||
app = Flask("the-prestige", static_folder='simmadome/build')
|
||||
app.config['SECRET KEY'] = 'dev'
|
||||
#app.config['SERVER_NAME'] = '0.0.0.0:5000'
|
||||
socketio = SocketIO(app)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
if ('league' in request.args):
|
||||
return render_template("index.html", league=request.args['league'])
|
||||
return render_template("index.html")
|
||||
|
||||
@app.route('/game')
|
||||
def game_page():
|
||||
return render_template("game.html")
|
||||
# Serve React App
|
||||
@app.route('/', defaults={'path': ''})
|
||||
@app.route('/<path:path>')
|
||||
def serve(path):
|
||||
if path != "" and os.path.exists(app.static_folder + '/' + path):
|
||||
return send_from_directory(app.static_folder, path)
|
||||
else:
|
||||
return send_from_directory(app.static_folder, 'index.html')
|
||||
|
||||
|
||||
thread2 = threading.Thread(target=socketio.run,args=(app,'0.0.0.0'))
|
||||
thread2.start()
|
||||
|
||||
master_games_dic = {} #key timestamp : (game game, {} state)
|
||||
data_to_send = []
|
||||
game_states = []
|
||||
|
||||
@socketio.on("recieved")
|
||||
def handle_new_conn(data):
|
||||
socketio.emit("states_update", data_to_send, room=request.sid)
|
||||
socketio.emit("states_update", game_states, room=request.sid)
|
||||
|
||||
def update_loop():
|
||||
global game_states
|
||||
while True:
|
||||
game_states = {}
|
||||
game_states = []
|
||||
game_ids = iter(master_games_dic.copy().keys())
|
||||
for game_id in game_ids:
|
||||
this_game, state, discrim_string = master_games_dic[game_id]
|
||||
|
@ -125,7 +125,7 @@ def update_loop():
|
|||
|
||||
state["top_of_inning"] = this_game.top_of_inning
|
||||
|
||||
game_states[game_id] = state
|
||||
game_states.append([game_id, state])
|
||||
|
||||
if state["update_pause"] <= 1 and state["start_delay"] < 0:
|
||||
if this_game.over:
|
||||
|
@ -140,17 +140,5 @@ def update_loop():
|
|||
|
||||
state["update_pause"] -= 1
|
||||
|
||||
global data_to_send
|
||||
data_to_send = []
|
||||
template = jinja2.Environment(loader=jinja2.FileSystemLoader('templates')).get_template('game_box.html')
|
||||
|
||||
for id in game_states:
|
||||
data_to_send.append({
|
||||
'timestamp' : id,
|
||||
'league' : game_states[id]['leagueoruser'] if game_states[id]['is_league'] else '',
|
||||
'state' : game_states[id],
|
||||
'html' : template.render(state=game_states[id], timestamp=id)
|
||||
})
|
||||
|
||||
socketio.emit("states_update", data_to_send)
|
||||
socketio.emit("states_update", game_states)
|
||||
time.sleep(8)
|
||||
|
|
94
simmadome/package-lock.json
generated
|
@ -2178,6 +2178,11 @@
|
|||
"@babel/types": "^7.3.0"
|
||||
}
|
||||
},
|
||||
"@types/component-emitter": {
|
||||
"version": "1.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
|
||||
"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
|
||||
},
|
||||
"@types/eslint": {
|
||||
"version": "7.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.6.tgz",
|
||||
|
@ -2314,6 +2319,11 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/socket.io-client": {
|
||||
"version": "1.4.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.34.tgz",
|
||||
"integrity": "sha512-Lzia5OTQFJZJ5R4HsEEldywiiqT9+W2rDbyHJiiTGqOcju89sCsQ8aUXDljY6Ls33wKZZGC0bfMhr/VpOyjtXg=="
|
||||
},
|
||||
"@types/source-list-map": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
|
||||
|
@ -3417,6 +3427,11 @@
|
|||
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
|
||||
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ=="
|
||||
},
|
||||
"backo2": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
||||
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
|
@ -3472,6 +3487,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"base64-arraybuffer": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
|
||||
"integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
|
@ -5256,6 +5276,31 @@
|
|||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"engine.io-client": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.0.6.tgz",
|
||||
"integrity": "sha512-5lPh8rrhxIruo5ZlgFt31KM626o5OCXrCHBweieWWuVicDtnYdz/iR93k6N9k0Xs61WrYxZKIWXzeSaJF6fpNA==",
|
||||
"requires": {
|
||||
"base64-arraybuffer": "0.1.4",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~4.0.1",
|
||||
"has-cors": "1.1.0",
|
||||
"parseqs": "0.0.6",
|
||||
"parseuri": "0.0.6",
|
||||
"ws": "~7.4.2",
|
||||
"xmlhttprequest-ssl": "~1.5.4",
|
||||
"yeast": "0.1.2"
|
||||
}
|
||||
},
|
||||
"engine.io-parser": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz",
|
||||
"integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==",
|
||||
"requires": {
|
||||
"base64-arraybuffer": "0.1.4"
|
||||
}
|
||||
},
|
||||
"enhanced-resolve": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz",
|
||||
|
@ -6937,6 +6982,11 @@
|
|||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-cors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
|
||||
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
|
@ -10694,6 +10744,16 @@
|
|||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
|
||||
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="
|
||||
},
|
||||
"parseqs": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
|
||||
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w=="
|
||||
},
|
||||
"parseuri": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
|
||||
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
|
@ -13625,6 +13685,30 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"socket.io-client": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.0.5.tgz",
|
||||
"integrity": "sha512-NNnv3UH5h+aICeVDAdSHll3vSujp1OnzvDtuVz1ukUXliffr1+LTGc1W+qZAm3H7McapGsJhTI5nsBoY1r21dQ==",
|
||||
"requires": {
|
||||
"@types/component-emitter": "^1.2.10",
|
||||
"backo2": "~1.0.2",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-client": "~4.0.6",
|
||||
"parseuri": "0.0.6",
|
||||
"socket.io-parser": "~4.0.3"
|
||||
}
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.3.tgz",
|
||||
"integrity": "sha512-m4ybFiP4UYVORRt7jcdqf8UWx+ywVdAqqsJyruXxAdD3Sv6MDemijWij34mOWdMJ55bEdIb9jACBhxUgNK6sxw==",
|
||||
"requires": {
|
||||
"@types/component-emitter": "^1.2.10",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~4.3.1"
|
||||
}
|
||||
},
|
||||
"sockjs": {
|
||||
"version": "0.3.20",
|
||||
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz",
|
||||
|
@ -16401,6 +16485,11 @@
|
|||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
|
||||
},
|
||||
"xmlhttprequest-ssl": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
|
||||
"integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
@ -16455,6 +16544,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"yeast": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
|
||||
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
|
||||
},
|
||||
"yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
|
|
@ -11,9 +11,11 @@
|
|||
"@types/node": "^12.19.12",
|
||||
"@types/react": "^16.14.2",
|
||||
"@types/react-dom": "^16.9.10",
|
||||
"@types/socket.io-client": "^1.4.34",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-scripts": "4.0.1",
|
||||
"socket.io-client": "^3.0.5",
|
||||
"typescript": "^4.1.3",
|
||||
"web-vitals": "^0.2.4"
|
||||
},
|
||||
|
|
|
@ -1,43 +1,160 @@
|
|||
import React from 'react';
|
||||
import io from 'socket.io-client';
|
||||
import './App.css';
|
||||
import Game from './Game';
|
||||
|
||||
interface GameState {
|
||||
bases: (string | null)[];
|
||||
outs: number;
|
||||
display_top_of_inning: boolean
|
||||
display_inning: number
|
||||
max_innings: number
|
||||
title: string
|
||||
weather_emoji: string
|
||||
weather_text: string
|
||||
away_name: string
|
||||
away_score: number
|
||||
home_name: string
|
||||
home_score: number
|
||||
pitcher: string
|
||||
batter: string
|
||||
update_emoji: string
|
||||
update_text: string
|
||||
is_league: boolean
|
||||
leagueoruser: string
|
||||
}
|
||||
|
||||
type GameList = ([id: string, game: GameState] | null)[];
|
||||
|
||||
interface AppProps {
|
||||
filter: string | null
|
||||
gameId: string | null
|
||||
}
|
||||
|
||||
interface AppState {
|
||||
filter: string
|
||||
games: [string, GameState][]
|
||||
}
|
||||
|
||||
class App extends React.Component<AppProps, AppState> {
|
||||
gameList: GameList;
|
||||
|
||||
constructor(props: AppProps) {
|
||||
super(props);
|
||||
|
||||
let socket = io();
|
||||
socket.on('connect', () => socket.emit('recieved', {}));
|
||||
socket.on('states_update', (update: [string, GameState][]) => this.onStatesUpdate(update));
|
||||
|
||||
this.gameList = [];
|
||||
this.state = {
|
||||
filter: props.filter ?? "",
|
||||
games: []
|
||||
};
|
||||
this.onSelectNewFilter = this.onSelectNewFilter.bind(this);
|
||||
}
|
||||
|
||||
onStatesUpdate(newStates: [string, GameState][]) {
|
||||
console.log(newStates);
|
||||
this.setState({ games: newStates });
|
||||
}
|
||||
|
||||
onSelectNewFilter(newFilter: string) {
|
||||
this.setState({ filter: newFilter });
|
||||
this.gameList = [];
|
||||
}
|
||||
|
||||
updateGameList() {
|
||||
let filterGames = this.state.games.filter((game, i) => this.state.filter === "" || game[1].leagueoruser === this.state.filter)
|
||||
|
||||
//remove games no longer present, update games with new info
|
||||
for (let i = 0; i < this.gameList.length; i ++) {
|
||||
if (this.gameList[i] !== null) {
|
||||
let newState = filterGames.find((val) => val[0] === this.gameList[i]![0]);
|
||||
if (newState !== undefined) {
|
||||
this.gameList[i] = newState;
|
||||
} else {
|
||||
this.gameList[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add games not present
|
||||
for (let game of filterGames) {
|
||||
if (!this.gameList.find((val) => val !== null && val[0] === game[0])) {
|
||||
let firstEmpty = this.gameList.indexOf(null);
|
||||
if (firstEmpty < 0) {
|
||||
this.gameList.push(game)
|
||||
} else {
|
||||
this.gameList[firstEmpty] = game;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//remove trailing empty cells
|
||||
while (this.gameList[this.gameList.length-1] === null) {
|
||||
this.gameList.pop();
|
||||
}
|
||||
}
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
this.updateGameList();
|
||||
|
||||
let filters: string[] = [];
|
||||
this.state.games.forEach((game, id) => { if (game[1].is_league && !filters.includes(game[1].leagueoruser)) { filters.push(game[1].leagueoruser) }});
|
||||
return (
|
||||
<div className="App">
|
||||
<Filters />
|
||||
<Grid />
|
||||
<Footer />
|
||||
<Filters filterList={filters} selectedFilter={this.state.filter} onSelectNewFilter={this.onSelectNewFilter}/>
|
||||
<Grid gameList={this.gameList}/>
|
||||
<Footer has_games={this.gameList.length > 0}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Filters extends React.Component {
|
||||
render() {
|
||||
function Filters (props: {filterList: string[], selectedFilter: string, onSelectNewFilter: (newFilter: string) => void}) {
|
||||
let filters = props.filterList.map((filter: string) =>
|
||||
<button className="filter"
|
||||
id={filter === props.selectedFilter ? "selected_filter" : ""}
|
||||
onClick={() => props.onSelectNewFilter(filter)}>
|
||||
{filter}
|
||||
</button>
|
||||
)
|
||||
|
||||
return (
|
||||
<div id="filters">
|
||||
<div>Filter:</div>
|
||||
<button className="filter" id="selected_filter">All</button>
|
||||
<button className="filter"
|
||||
id={"" === props.selectedFilter ? "selected_filter" : ""}
|
||||
onClick={() => props.onSelectNewFilter("")}>
|
||||
All
|
||||
</button>
|
||||
{filters}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Grid() {
|
||||
|
||||
function Grid(props: { gameList: GameList }) {
|
||||
return (
|
||||
<section className="container" id="container">
|
||||
<div className="emptyslot" />
|
||||
{props.gameList.map((game) => {if (game) {
|
||||
return <Game gameId={game[0]} state={game[1]}/>
|
||||
} else {
|
||||
return <div className="emptyslot"/>
|
||||
}})}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Footer() {
|
||||
function Footer(props: { has_games: boolean }) {
|
||||
let text = props.has_games ? "" : "No games right now. Why not head over to Discord and start one?";
|
||||
return (
|
||||
<div id="footer">
|
||||
<div></div>
|
||||
<div>{text}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
export type { GameState };
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
:root {
|
||||
--background-main: #2f3136; /*discord dark theme background-secondary - the same color as the embeds*/
|
||||
--background-secondary: #4f545c; /*discord's background-tertiary*/
|
||||
|
@ -193,7 +192,7 @@
|
|||
height: 60px;
|
||||
}
|
||||
|
||||
.base_2 {
|
||||
.field > .base {
|
||||
margin-bottom: -25%
|
||||
}
|
||||
|
75
simmadome/src/Game.tsx
Normal file
|
@ -0,0 +1,75 @@
|
|||
import { GameState } from './App';
|
||||
import './Game.css';
|
||||
import base_filled from './img/base_filled.png';
|
||||
import base_empty from './img/base_empty.png';
|
||||
import out_filled from './img/out_out.png';
|
||||
import out_empty from './img/out_in.png';
|
||||
|
||||
function Game(props: {gameId: string, state : GameState}) {
|
||||
let state = props.state;
|
||||
return (
|
||||
<div className="game">
|
||||
<div className="header">
|
||||
<div className="inning">Inning: {state.display_top_of_inning ? "🔼" : "🔽"} {state.display_inning}/{state.max_innings}</div>
|
||||
<div className="title">{state.title}</div>
|
||||
<div className="weather">{state.weather_emoji} {state.weather_text}</div>
|
||||
</div>
|
||||
<div className="body">
|
||||
<div className="teams">
|
||||
<Team name={state.away_name} score={state.away_score}/>
|
||||
<Team name={state.home_name} score={state.home_score}/>
|
||||
</div>
|
||||
<div className="info">
|
||||
<div className="field">
|
||||
<Base name={state.bases[2]} />
|
||||
<div style={{display: "flex"}}>
|
||||
<Base name={state.bases[3]} />
|
||||
<Base name={state.bases[1]} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="outs">
|
||||
<div className="outs_title">OUTS</div>
|
||||
<div className="outs_count">
|
||||
{[1, 2].map((out) => <Out thisOut={out} totalOuts={state.outs} key={out} />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="players">
|
||||
<div className="player_type">PITCHER</div>
|
||||
<div className="player_name">{state.pitcher}</div>
|
||||
<div className="player_type">BATTER</div>
|
||||
<div className="player_name batter_name">{state.batter}</div>
|
||||
</div>
|
||||
<div className="update">
|
||||
<div className="update_emoji">{state.update_emoji}</div>
|
||||
<div className="update_text">{state.update_text}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer">
|
||||
<div className="batting">{state.display_top_of_inning ? state.away_name : state.home_name} batting.</div>
|
||||
<div className="leagueoruser">{state.leagueoruser} (<a href={"/game?id=" + props.gameId}>share</a>)</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Team(props: {name: string, score: number}) {
|
||||
return (
|
||||
<div className="team">
|
||||
<div className="team_name">{ props.name }</div>
|
||||
<div className="score">{ props.score }</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Base(props: {name: string | null}) {
|
||||
return (
|
||||
<img className="base" alt={ props.name ?? "" } src={ props.name ? base_filled : base_empty }/>
|
||||
);
|
||||
}
|
||||
|
||||
function Out(props: {thisOut: number, totalOuts: number}) {
|
||||
return <img className="out" alt="" src={props.thisOut <= props.totalOuts ? out_filled : out_empty}/>;
|
||||
}
|
||||
|
||||
export default Game;
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
@ -8,7 +8,7 @@ import reportWebVitals from './reportWebVitals';
|
|||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<Header />
|
||||
<App />
|
||||
<App filter={null} gameId={null}/>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
@ -22,7 +22,7 @@ function Header() {
|
|||
<a href="https://github.com/Sakimori/matteo-the-prestige" className="link" target="_blank" rel="noopener noreferrer">Github</a><br />
|
||||
<a href="https://twitter.com/intent/follow?screen_name=SIBR_XVI" className="link" target="_blank" rel="noopener noreferrer">Twitter</a>
|
||||
</div>
|
||||
<a href="/" className="page_header"><h2 className="page_header" style={{"font-size":"50px"} as React.CSSProperties}>THE SIMMADOME</h2></a>
|
||||
<a href="/" className="page_header"><h2 className="page_header" style={{fontSize:"50px"} as React.CSSProperties}>THE SIMMADOME</h2></a>
|
||||
<h2 className="page_header">Join SIBR on <a href="https://discord.gg/UhAajY2NCW" className="link"><img src={discordlogo} alt="" height="30"/></a> to start your own games!</h2>
|
||||
</div>
|
||||
);
|
||||
|
|
BIN
static/.DS_Store
vendored
|
@ -1,69 +0,0 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Alegreya&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Goldman:wght@700&display=swap');
|
||||
body {
|
||||
background-image: url("naturalblack.png");
|
||||
}
|
||||
/* Background pattern from Toptal Subtle Patterns */
|
||||
|
||||
div, button, h1, h2, a {
|
||||
font-family: 'Alegreya', serif;
|
||||
color: white;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.link{
|
||||
position: relative;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.h1 {
|
||||
margin: auto;
|
||||
width: 45%;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.page_header {
|
||||
color: white;
|
||||
font-family: 'Goldman', cursive;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#header {
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#header .page_header {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#link_div {
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
#link_div > a {
|
||||
background-color: transparent;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#link_div > a:link, #link_div > a:visited {
|
||||
color: lightblue;
|
||||
}
|
||||
|
||||
#link_div > a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
img.emoji {
|
||||
height: 14px;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
#game_container {
|
||||
margin-top: 3rem;
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(32rem, 1fr));
|
||||
grid-gap: 50px 30px; /*space between rows, then columns*/
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
|
||||
.emptyslot, .game {
|
||||
min-height: 18.75rem;
|
||||
justify-self: center;
|
||||
max-width: 44rem;
|
||||
}
|
||||
|
||||
#filters {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#filters > * {
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin: 0rem 0.5rem;
|
||||
font-size: 16pt;
|
||||
background: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
#filters > .filter {
|
||||
border-radius: 0.5rem;
|
||||
min-width: 6.25rem;
|
||||
text-align: center;
|
||||
border: none;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#selected_filter {
|
||||
background: rgb(113, 54, 138);
|
||||
}
|
||||
|
||||
#footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 4.5rem;
|
||||
}
|
||||
|
||||
#footer > div {
|
||||
text-align: center;
|
||||
font-size: 16pt;
|
||||
position: relative;
|
||||
top: 0.25rem;
|
||||
}
|
||||
|
||||
.emptyslot {
|
||||
border: 2px dashed white;
|
||||
border-radius: 15px;
|
||||
align-self: stretch;
|
||||
justify-self: stretch;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media only screen and (max-device-width: 800px) {
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, minmax(700px, 90%));
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
.emptyslot {
|
||||
border: none;
|
||||
min-height: 0px;
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 256 KiB |
Before Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 8.9 KiB |
BIN
static/js/.DS_Store
vendored
|
@ -1,25 +0,0 @@
|
|||
$(document).ready(function (){
|
||||
var socket = io.connect();
|
||||
|
||||
socket.on('connect', function () {
|
||||
console.log("connected")
|
||||
socket.emit('recieved', {});
|
||||
});
|
||||
|
||||
socket.on("states_update", function (json) { //json is an object containing all game updates
|
||||
var searchparams = new URLSearchParams(window.location.search);
|
||||
var exists = false;
|
||||
for (game of json) {
|
||||
if (searchparams.get('timestamp') == game.timestamp) {
|
||||
$('.game').html(game.html);
|
||||
exists = true;
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
$('game').remove()
|
||||
$('#game_container').text("The game you're looking for either doesn't exist or has already ended.")
|
||||
}
|
||||
|
||||
twemoji.parse(document.body);
|
||||
});
|
||||
});
|
|
@ -1,178 +0,0 @@
|
|||
var socket = io.connect();
|
||||
var lastupdate;
|
||||
var grid;
|
||||
|
||||
$(document).ready(function (){
|
||||
grid = document.getElementById("container")
|
||||
|
||||
socket.on('connect', function () {
|
||||
socket.emit('recieved', {});
|
||||
});
|
||||
|
||||
socket.on("states_update", function (json) { //json is an object containing all game updates
|
||||
lastupdate = json;
|
||||
updateGames(json, $('#selected_filter').text());
|
||||
updateLeagues(json);
|
||||
});
|
||||
});
|
||||
|
||||
const updateGames = (json, filter) => {
|
||||
|
||||
filterjson = [];
|
||||
for (var game of json) {
|
||||
if (game.league == filter || filter == "All") {
|
||||
filterjson.push(game);
|
||||
}
|
||||
}
|
||||
|
||||
if (filterjson.length == 0) {
|
||||
$('#footer div').html("No games right now. Why not head over to Discord and start one?");
|
||||
} else {
|
||||
$('#footer div').html("");
|
||||
}
|
||||
|
||||
var searchparams = new URLSearchParams(window.location.search);
|
||||
if (searchparams.has('game') && filterjson.some(x => x.timestamp == searchparams.get('game')) && grid.children[0].timestamp != searchparams.get('game')) {
|
||||
var game = filterjson.find(x => x.timestamp == searchparams.get('game'))
|
||||
var oldbox = Array.prototype.slice.call(grid.children).find(x => x.timestamp == game.timestamp)
|
||||
if (oldbox) { clearBox(oldbox) }
|
||||
insertGame(0, game)
|
||||
}
|
||||
|
||||
//replace games that have ended with empty slots
|
||||
for (var slotnum = 0; slotnum < grid.children.length; slotnum++) {
|
||||
if (grid.children[slotnum].className == "game" && !filterjson.some((x) => x.timestamp == grid.children[slotnum].timestamp)) {
|
||||
clearBox(grid.children[slotnum])
|
||||
}
|
||||
}
|
||||
|
||||
for (var game of filterjson) {
|
||||
//updates game in list
|
||||
for (var slotnum = 0; slotnum < grid.children.length; slotnum++) {
|
||||
if (grid.children[slotnum].timestamp == game.timestamp) {
|
||||
insertGame(slotnum, game);
|
||||
};
|
||||
};
|
||||
|
||||
//adds game to list if not there already
|
||||
if (!Array.prototype.slice.call(grid.children).some(x => x.timestamp == game.timestamp)) {
|
||||
for (var slotnum = 0; true; slotnum++) { //this is really a while loop but shh don't tell anyone
|
||||
if (slotnum >= grid.children.length) {
|
||||
insertEmpty(grid);
|
||||
}
|
||||
if (grid.children[slotnum].className == "emptyslot") {
|
||||
insertGame(slotnum, game);
|
||||
break;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
fillgrid(grid)
|
||||
};
|
||||
}
|
||||
|
||||
const insertEmpty = (grid) => {
|
||||
var newBox = document.createElement("DIV");
|
||||
newBox.className = "emptyslot";
|
||||
grid.appendChild(newBox);
|
||||
}
|
||||
|
||||
const insertGame = (gridboxnum, game) => {
|
||||
var thisBox = grid.children[gridboxnum];
|
||||
thisBox.className = "game";
|
||||
thisBox.timestamp = game.timestamp;
|
||||
thisBox.innerHTML = game.html;
|
||||
twemoji.parse(thisBox);
|
||||
};
|
||||
|
||||
const insertLeague = (league) => {
|
||||
var btn = document.createElement("BUTTON");
|
||||
btn.className = "filter";
|
||||
btn.innerHTML = league;
|
||||
$('#filters').append(btn);
|
||||
return btn;
|
||||
}
|
||||
|
||||
const clearBox = (box) => {
|
||||
box.className = "emptyslot";
|
||||
box.timestamp = null;
|
||||
box.innerHTML = "";
|
||||
}
|
||||
|
||||
const fillgrid = (grid) => {
|
||||
var gridwidth = window.getComputedStyle(grid).getPropertyValue('grid-template-columns').split(" ").length //hack to get number of grid columns
|
||||
|
||||
// add cells to fill last row
|
||||
while (grid.children.length % gridwidth != 0) {
|
||||
insertEmpty(grid)
|
||||
}
|
||||
|
||||
//remove last rows if not needed
|
||||
while (grid.children.length > gridwidth && Array.prototype.slice.call(grid.children).slice(grid.children.length - gridwidth).every( x => x.className == 'emptyslot')) {
|
||||
for (var i = 0; i < gridwidth; i++) {
|
||||
grid.removeChild(grid.children[grid.children.length-1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updateLeagues = (games) => {
|
||||
//get all leagues
|
||||
var leagues = []
|
||||
for (var game of games) {
|
||||
if (game.league != "" && !leagues.includes(game.league)) {
|
||||
leagues.push(game.league)
|
||||
}
|
||||
}
|
||||
|
||||
//remove leagues no longer present
|
||||
$('#filters .filter').each(function(index) {
|
||||
if (!leagues.includes($(this).text())) {
|
||||
if (this.id != 'selected_filter' && $(this).text() != "All") { //don't remove the currently selected filter or the "all" filter
|
||||
$(this).remove();
|
||||
}
|
||||
} else {
|
||||
leagues.splice(leagues.indexOf($(this).text()), 1);
|
||||
}
|
||||
})
|
||||
|
||||
// add leagues not already present
|
||||
for (var league of leagues) { // we removed the entries that are already there in the loop above
|
||||
insertLeague(league)
|
||||
}
|
||||
|
||||
//add click handlers to each filter
|
||||
$('#filters .filter').each(function(index) {
|
||||
this.onclick = function() {
|
||||
if ($('#filters #selected_filter').text() == 'All') {
|
||||
updateGames([], ""); // clear grid when switching off of All, to make games collapse to top
|
||||
}
|
||||
$('#filters #selected_filter').attr('id', '');
|
||||
this.id = 'selected_filter';
|
||||
|
||||
var search = new URLSearchParams();
|
||||
search.append('league', this.textContent);
|
||||
history.pushState({}, "", "/" + (this.textContent != 'All' ? "?" + search.toString() : ""));
|
||||
updateGames(lastupdate, this.textContent);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.onpopstate = function(e) {
|
||||
var searchparams = new URLSearchParams(window.location.search);
|
||||
updateLeagues(lastupdate);
|
||||
$('#filters #selected_filter').attr('id', '');
|
||||
if (searchparams.has('league')) {
|
||||
var filter_found = false
|
||||
$('#filters .filter').each(function(i) { if (this.textContent == searchparams.get('league')) { this.id = 'selected_filter'; filter_found = true }});
|
||||
if (!filter_found) { insertLeague(searchparams.get('league')).id = 'selected_filter' }
|
||||
|
||||
updateGames(lastupdate, searchparams.get('league'));
|
||||
} else {
|
||||
$('#filters .filter').each(function(i) { if (this.textContent == 'All') { this.id = 'selected_filter' }})
|
||||
updateGames(lastupdate, "All");
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', function(e) {
|
||||
fillgrid(grid)
|
||||
})
|
BIN
templates/.DS_Store
vendored
|
@ -1,23 +0,0 @@
|
|||
<html lang="en-US">
|
||||
<head>
|
||||
<script src="//code.jquery.com/jquery-3.5.1.min.js"></script>
|
||||
<script src="https://twemoji.maxcdn.com/v/latest/twemoji.min.js" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.4/socket.io.js"></script>
|
||||
<script type="text/javascript" async src="https://platform.twitter.com/widgets.js"></script>
|
||||
|
||||
<title>⚾ The Simmadome</title>
|
||||
|
||||
<meta property="og:title" content="Watch at the Simmadome" />
|
||||
<meta property="og:description" content="The Simsim: Your players, your teams, your games." />
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:site" content="@SIBR_XVI">
|
||||
|
||||
<link rel="stylesheet" href="/static/css/common.css">
|
||||
{% block head_tags %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
{% block body %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
|
@ -1,11 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% block head_tags %}
|
||||
<link rel="stylesheet" href="/static/css/game.css">
|
||||
<link rel="stylesheet" href="/static/css/game_page.css">
|
||||
<script type="text/javascript" src="static/js/game_loader.js"></script>
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<div id="game_container">
|
||||
<div class="game"></div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,53 +0,0 @@
|
|||
{% macro base(number) -%}
|
||||
src={% if state.bases[number] %}"/static/img/base_filled.png" alt="{{state.bases[number]}}"{% else %}"/static/img/base_empty.png"{% endif %}
|
||||
{%- endmacro %}
|
||||
{% macro out(number) -%}
|
||||
{% if number <= state.outs %}/static/img/out_out.png{% else %}/static/img/out_in.png{% endif %}
|
||||
{%- endmacro %}
|
||||
<div class="header">
|
||||
<div class="inning">Inning: {% if state.display_top_of_inning == true %}🔼{% else %}🔽{% endif %} {{ state.display_inning }}/{{ state.max_innings }}</div>
|
||||
<div class="title">{{ state.title }}</div>
|
||||
<div class="weather">{{ state.weather_emoji }} {{ state.weather_text }}</div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="teams">
|
||||
<div class="team">
|
||||
<div class="team_name">{{ state.away_name }}</div>
|
||||
<div class="score">{{ state.away_score }}</div>
|
||||
</div>
|
||||
<div class="team">
|
||||
<div class="team_name">{{ state.home_name }}</div>
|
||||
<div class="score">{{ state.home_score }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="field">
|
||||
<img class="base base_2" {{ base(2) }}/>
|
||||
<div style="display: flex;">
|
||||
<img class="base base_3" {{ base(3) }}/>
|
||||
<img class="base base_1" {{ base(1) }}/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="outs">
|
||||
<div class="outs_title">OUTS</div>
|
||||
<div class="outs_count">
|
||||
<img class="out" src="{{ out(1) }}"/>
|
||||
<img class="out" src="{{ out(2) }}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="players">
|
||||
<div class="player_type">PITCHER</div>
|
||||
<div class="player_name pitcher_name">{{ state.pitcher }}</div>
|
||||
<div class="player_type">BATTER</div>
|
||||
<div class="player_name batter_name">{{ state.batter }}</div>
|
||||
</div>
|
||||
<div class="update">
|
||||
<div class="update_emoji">{{ state.update_emoji }}</div>
|
||||
<div class="update_text">{{ state.update_text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="batting">{% if state.display_top_of_inning == true %}{{ state.away_name }}{% else %}{{ state.home_name }}{% endif %} batting.</div>
|
||||
<div class="leagueoruser">{{ state.leagueoruser }} (<a href="/game?timestamp={{ timestamp }}">share</a>)</div>
|
||||
</div>
|
|
@ -1,21 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% block head_tags %}
|
||||
<link rel="stylesheet" href="/static/css/games_page.css">
|
||||
<link rel="stylesheet" href="/static/css/game.css">
|
||||
<script type="text/javascript" src="static/js/grid_loader.js"></script>
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<div id="filters">
|
||||
<div>Filter:</div>
|
||||
<button class="filter" {% if not league %}id="selected_filter"{% endif %}>All</button>
|
||||
{% if league %}<button class="filter" id="selected_filter">{{ league }}</button>{% endif %}
|
||||
</div>
|
||||
<section class="container" id="container">
|
||||
<div class="emptyslot"></div>
|
||||
<div class="emptyslot"></div>
|
||||
<div class="emptyslot"></div>
|
||||
</section>
|
||||
<div id="footer">
|
||||
<div></div>
|
||||
</div>
|
||||
{% endblock %}
|