Add logic for serving front-end from /app

This commit is contained in:
2020-11-04 20:47:58 -06:00
parent 9390f5b920
commit 31ff08e6d0
80 changed files with 64 additions and 12 deletions

View File

@@ -0,0 +1,25 @@
import TopLevelComponent from './components/TopLevel.component.js'
import MyTeamComponent from './components/pages/MyTeam.component.js'
import LinkComponent from './components/Link.component.js'
import AddPlayersComponent from './components/pages/AddPlayers.component.js'
import ScoresComponent from './components/pages/Scores.component.js'
import LeagueComponent from './components/pages/League.component.js'
import DraftBoardComponent from './components/pages/DraftBoard.component.js'
import GridComponent from './components/Grid.component.js'
import GridActionButtonComponent from './components/GridActionButton.component.js'
/*
* This is where various components we define should be registered.
* Once they are listed here, they will be automatically loaded by Vue.js.
*/
export default {
LinkComponent,
TopLevelComponent,
MyTeamComponent,
AddPlayersComponent,
ScoresComponent,
LeagueComponent,
DraftBoardComponent,
GridComponent,
GridActionButtonComponent,
}

View File

@@ -0,0 +1,72 @@
import {Component} from '../../lib/vues6.js'
/**
* An enum representing the different grid cell renderer types for the shared grid.
* @type {object}
*/
const GridCellRenderType = {
Simple: Symbol('simple'),
HTML: Symbol('html'),
Component: Symbol('component'),
}
export {GridCellRenderType}
const template = `
<div class="component-app-grid">
<table>
<tr>
<th v-if="show_row_numbers">#</th>
<th v-for="col of column_defs" :title="col.title || ''">{{ col.header || '' }}</th>
</tr>
<tr v-for="(row, idx) of data">
<td v-if="show_row_numbers">{{ idx + 1 }}</td>
<td v-for="col of column_defs">
<div v-if="!col.type || col.type === GridCellRenderType.Simple">{{ row[col.key] }}</div>
<div v-if="col.type === GridCellRenderType.HTML" v-html="col.renderer(row[col.key], row)"></div>
<div v-if="col.type === GridCellRenderType.Component">
<component :is="col.component" :row="row" :col="col" :idx="idx" @click="on_col_click(col, $event)"></component>
</div>
</td>
</tr>
</table>
</div>
`
/**
* Shared grid component used to show tables in various interfaces.
* @extends Component
*/
class GridComponent extends Component {
static get selector() { return 'app-grid' }
static get template() { return template }
static get props() {
return [
'show_row_numbers',
'column_defs',
'data',
]
}
GridCellRenderType = GridCellRenderType
/**
* Called when the component is instantiated.
* @return {Promise<void>}
*/
async vue_on_create() {
}
/**
* Called when the component renderer emits a click event, to pass it along to the column definition.
* @param {object} col - the column definition
* @param {object} row - the row clicked
* @param {object} passcol - the column emitted from the component
*/
on_col_click(col, [row, passcol]) {
col.on_click(row, passcol)
}
}
export default GridComponent

View File

@@ -0,0 +1,115 @@
import {Component} from '../../lib/vues6.js'
const template = `
<div class="component-action-button">
<button
v-if="!hidden"
v-bind:style="{border: '2px solid lightgrey', borderRadius: '3px', backgroundColor: color, color: 'white'}"
@click="on_click($event)"
>{{ text }}</button>
</div>
`
/**
* Component representing an action button that can be embedded in the shared grid.
* @extends Component
*/
class GridActionButtonComponent extends Component {
static get selector() { return 'app-action-button' }
static get template() { return template }
static get props() { return ['row', 'col'] }
/**
* The text shown on the action button.
* @type {string}
*/
text = ''
/**
* The CSS color of the action button.
* @type {string}
*/
color = 'white'
/**
* If true, the action button will be hidden.
* @type {boolean}
*/
hidden = false
/**
* Called when the component is instantiated. Updates the text, color, and hide status.
* @return {Promise<void>}
*/
async vue_on_create() {
this.update_text()
this.update_color()
this.update_hidden()
}
/**
* Called when the row value changes. Updates the text, color, and hide status.
*/
watch_row() {
this.update_text()
this.update_color()
this.update_hidden()
}
/**
* Called when the column value changes. Updates the text, color, and hide status.
*/
watch_col() {
this.update_text()
this.update_color()
this.update_hidden()
}
/**
* Determine the text to show on the button based on the column definition.
*/
update_text() {
if ( typeof this.col.button_text === 'function' ) {
this.text = this.col.button_text(this.row, this.col)
} else {
this.text = this.col.button_text || ''
}
}
/**
* Determine the color to show on the button based on the column definition.
*/
update_color() {
if ( typeof this.col.button_color === 'function' ) {
this.color = this.col.button_color(this.row, this.col)
} else {
this.color = this.col.button_color || 'white'
}
}
/**
* Determine whether the button should be shown or not, based on the column definition.
*/
update_hidden() {
if ( !('button_hidden' in this.col) ) {
this.hidden = false;
} else if ( typeof this.col.button_hidden === 'function' ) {
this.hidden = this.col.button_hidden(this.row, this.col)
} else {
this.hidden = this.col.button_hidden
}
}
/**
* Called when the button is clicked. Emits a click event and updates the text, color, and hide status.
* @param {MouseEvent} $event
*/
on_click($event) {
this.$emit('click', [this.row, this.col])
this.update_text()
this.update_color()
this.update_hidden()
}
}
export default GridActionButtonComponent

View File

@@ -0,0 +1,25 @@
import {Component} from '../../lib/vues6.js'
import {router} from '../module/routing.js'
const template = `
<a href="#" @click="on_click()">{{ text }}</a>
`
/**
* Component providing hyper-links that navigate to other pages in the SPA,
* without reloading the page.
*/
class LinkComponent extends Component {
static get selector() { return 'app-link' }
static get template() { return template }
static get props() { return ['href', 'args', 'text'] }
/**
* Called when the link is clicked. Navigates the router.
*/
on_click() {
router.navigate(this.href, this.args)
}
}
export default LinkComponent

View File

@@ -0,0 +1,99 @@
import {Component} from '../../lib/vues6.js'
import {router} from '../module/routing.js'
const template = `
<div class="top-level-container">
<div class="navbar-container">
<h1 class="title">Fantasy Football</h1>
<ul class="navbar">
<li class="navbar-item" v-for="item of navbar_items" :class="{ active: current_route === item.page }">
<app-link :href="item.page" :text="item.title"></app-link>
</li>
<li class="navbar-item">
<a href="#" @click="on_refresh($event)">Refresh</a>
</li>
</ul>
</div>
<div class="page-container">
<page-my-team v-if="current_route === 'my-team'"></page-my-team>
<page-add-players v-if="current_route === 'my-team/add-players'"></page-add-players>
<page-scores v-if="current_route === 'scores'"></page-scores>
<page-league v-if="current_route === 'league'"></page-league>
<page-draft-board v-if="current_route === 'draft-board'"></page-draft-board>
</div>
</div>
`
/**
* Top-level component which manages the display of the entire game.
* @extends Component
*/
class TopLevelComponent extends Component {
static get selector() { return 'app-top-level' }
static get template() { return template }
static get props() { return [] }
/**
* The currently loaded page route.
* @type {string}
*/
current_route = ''
/**
* Array of navigation bar items where "title" is the page name, and "page" is the page route.
* @type {Array<object>}
*/
navbar_items = [
{ title: 'My Team', page: 'my-team' },
{ title: 'Add Players', page: 'my-team/add-players' },
{ title: 'Scores', page: 'scores' },
{ title: 'League', page: 'league' },
{ title: 'Draft Board', page: 'draft-board' },
]
/**
* Called when the component is initialized.
* @return {Promise<void>}
*/
async vue_on_create() {
// Listen for navigation changes.
this.router_subscription = router.subscribe((path, args) => this.on_route_change(path, args))
const url_params = new URLSearchParams(window.location.search)
if ( url_params.has('then') ) {
const route = url_params.get('then')
router.navigate(route)
} else if ( !this.current_route ) {
router.navigate('my-team')
}
}
/**
* Called when the component is destroyed.
* @return {Promise<void>}
*/
async vue_on_destroy() {
// Stop listening for navigation changes.
this.router_subscription.unsubscribe()
}
/**
* Called when the navigation changes.
* @param {string} path
* @param {*} args
* @return {Promise<void>}
*/
async on_route_change(path, args) {
if ( path.startsWith('/') ) path = path.slice(1)
if ( path.endsWith('/') ) path = path.slice(0, -1)
this.current_route = path
}
on_refresh($event) {
window.location.href = `/?then=${this.current_route}`
}
}
export default TopLevelComponent

View File

@@ -0,0 +1,175 @@
import { Component } from '../../../lib/vues6.js'
import { fake_players } from '../../module/fake_data.js'
import { clone } from '../../module/util.js'
const template = `
<div class="page-add-players">
<div class="header">
<div class="left">
<h2>Add Players to Team</h2>
</div>
<div class="right">
<button :class="{ 'disable-click': my_team_only }" @click="to_my_team_only()">My Team</button><button :class="{ 'disable-click': !my_team_only }" @click="to_all_players()">All Players</button>
</div>
<div class="right">
<input type="text" placeholder="Quick filter..." v-model="quick_filter" @keyup="on_filter_change()">
</div>
</div>
<div class="item-grid">
<div class="item" v-for="player of filtered_players" @mouseover="on_photo_hover(player)"
@mouseleave="on_photo_leave(player)">
<div style="display: flex; flex-direction: column; height: 100%;">
<div class="item-icon" v-if="!player.showing_stats">
<img :src="player.image" :alt="player.name">
</div>
<div class="item-contents" v-if="!player.showing_stats">
<h1>{{ player.name }}</h1>
<p>#{{ player.number }} ({{ player.position }})</p>
</div>
<div class="item-contents" style="flex: 1;" v-else>
<div>
<p v-if="player.team_name"><b>Team: </b> {{ player.team_name }}</p>
<p><b>Position: </b> {{ player.position }}</p>
<p v-for="(value, stat) in player.stats"><b>{{ stat }}: </b> {{ value }}</p>
</div>
</div>
</div>
<div class="item-button">
<button
v-if="my_team.length < 15 && !my_team.includes(player)"
@click="add_to_team(player)"
class="add"
>Add to Team</button>
<button
v-if="my_team.includes(player)"
@click="remove_from_team(player)"
class="remove"
>Remove from Team</button>
</div>
</div>
</div>
</div>
`
/**
* A component which represents the "Add Players" page. Allows users to add/remove
* players from their team.
* @extends Component
*/
class AddPlayersComponent extends Component {
static get selector() { return 'page-add-players' }
static get props() { return [] }
static get template() { return template }
/**
* The current value of the quick filter for players. If empty string, no filter is applied.
* @type {string}
*/
quick_filter = ''
/**
* If true, then only the players on the user's team will be shown.
* @type {boolean}
*/
my_team_only = false
/**
* Array of players currently on the user's team.
* @type {object[]}
*/
my_team = []
/**
* Array of currently displayed players, after the filter has been applied.
* @type {object[]}
*/
filtered_players = []
/**
* Array of currently displayed players, before the filter has been applied.
* @type {object[]}
*/
possible_players = []
/**
* All available players, whether they are on the user's team or not.
* @type {object[]}
*/
all_players = clone(fake_players)
/**
* Called when the page is instantiated.
* @return {Promise<void>}
*/
async vue_on_create() {
this.possible_players = [...this.all_players];
this.filtered_players = [...this.possible_players];
}
/**
* Called when the quick-filter changes. Applies the filter to the displayed players.
*/
on_filter_change() {
const query = this.quick_filter.toLowerCase()
this.filtered_players = this.possible_players.filter(x => {
if (!query) return true;
return x.name.toLowerCase().includes(query) || x.position.toLowerCase().includes(query)
})
}
/**
* When called, change the display to show only the user's team.
*/
to_my_team_only() {
this.my_team_only = true;
this.possible_players = [...this.my_team]
this.on_filter_change()
}
/**
* When called, change the display to show all available players.
*/
to_all_players() {
this.my_team_only = false;
this.possible_players = [...this.all_players]
this.on_filter_change()
}
/**
* Add the given player to the user's team, if not already there.
* @param {object} player
*/
add_to_team(player) {
if (!this.my_team.includes(player)) {
this.my_team.push(player)
}
}
/**
* Remove the given player from the user's team, if there.
* @param {object} player
*/
remove_from_team(player) {
this.my_team = this.my_team.filter(x => x !== player)
player.showing_stats = false
if (this.my_team_only) this.to_my_team_only()
}
/**
* Called when the user hovers over a player. Toggles the stats to be shown.
* @param {object} player
*/
on_photo_hover(player) {
player.showing_stats = true
}
/**
* Called when the user un-hovers over a player. Toggles the stats to hide.
* @param {object} player
*/
on_photo_leave(player) {
player.showing_stats = false
}
}
export default AddPlayersComponent

View File

@@ -0,0 +1,125 @@
import {Component} from '../../../lib/vues6.js'
import {fake_players} from '../../module/fake_data.js'
import {GridCellRenderType} from '../Grid.component.js'
import {clone} from '../../module/util.js'
const template = `
<div class="page-draft-board">
<div class="header">
<div class="left">
<h2>Draft Board</h2>
</div>
</div>
<div class="body" style="display: flex; flex-direction: row">
<div class="picks" style="margin-right: 20px;">
<app-grid
:column_defs="top_picks_column_defs"
:data="top_picks"
:show_row_numbers="true"
></app-grid>
</div>
<app-grid
style="flex: 1"
:column_defs="column_defs"
:data="data"
:show_row_numbers="false"
></app-grid>
</div>
</div>
`
/**
* Component representing the draft board page.
* @extends Component
*/
class DraftBoardComponent extends Component {
static get selector() { return 'page-draft-board' }
static get template() { return template }
static get props() { return [] }
top_picks_column_defs = [
{
header: 'Player',
key: 'name',
type: GridCellRenderType.HTML,
renderer: (_, data) => `
<div class="center">
<img src="${data.image}" alt="${data.name}" height="50" style="border-radius: 50%">
<span>${data.name}</span>
</div>
`,
}
]
top_picks = []
column_defs = [
{
header: 'Name',
key: 'name',
type: GridCellRenderType.HTML,
renderer: (_, data) => `
<div class="center">
<img src="${data.image}" alt="${data.name}" height="50" style="border-radius: 50%">
<span>${data.name}</span>
</div>
`,
},
{
header: 'Team',
key: 'team_name',
},
{
header: 'Position',
key: 'position',
},
{
header: 'Points',
key: 'points',
},
{
header: 'Stats',
key: 'stats',
type: GridCellRenderType.HTML,
renderer: (value, row) => {
const stats = []
for ( const stat in value ) {
if ( !value.hasOwnProperty(stat) ) continue; // Prototypical member
stats.push(`
<div class="stat">
<div class="title">${stat}</div>
<div>${value[stat]}</div>
</div>
`)
}
return `
<div class="stats">
${stats.join('\n')}
</div>
`
},
},
{
header: '',
key: 'stats',
type: GridCellRenderType.Component,
component: Vue.component('app-action-button'),
button_color: (row, col) => '#CC5746',
button_text: (row, col) => 'Draft',
button_hidden: (row, col) => this.top_picks.includes(row),
on_click: (row, col) => {
this.top_picks.push(row);
},
},
]
data = clone(fake_players)
async vue_on_create() {
}
}
export default DraftBoardComponent

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,264 @@
import {Component} from '../../../lib/vues6.js'
import {GridCellRenderType} from '../Grid.component.js'
const template = `
<div class="page-my-team">
<div class="header">
<div class="left team-name">
<h2>My Team - </h2><input placeholder="Click to edit team name..." type="text" v-model="team_name">
</div>
</div>
<div class="body" style="display: flex; flex-direction: row; margin-left: 10px; padding-bottom: 50px;" v-if="show_body">
<app-grid
:column_defs="overall_column_defs"
:data="overall_data"
:show_row_numbers="true"
style="flex: 1;"
></app-grid>
<div class="lineup-grids" style="margin-left: 30px; margin-right: 10px; flex: 1;">
<h3>Starting Lineup</h3>
<app-grid
:column_defs="lineup_column_defs"
:data="starting_players"
:show_row_numbers="false"
></app-grid>
<h3>Bench</h3>
<app-grid
:column_defs="lineup_column_defs"
:data="bench_players"
:show_row_numbers="false"
></app-grid>
</div>
</div>
</div>
`
/**
* Component representing the my-team page.
* @extends Component
*/
class MyTeamComponent extends Component {
static get selector() { return 'page-my-team' }
static get template() { return template }
static get props() { return [] }
/**
* The team name.
* @type {string}
*/
team_name = ''
/**
* If true, the body of the page will be shown. Otherwise, hidden.
* This is used to refresh the entire component at once.
* @type {boolean}
*/
show_body = true
/**
* The player currently being moved. If none, then will be set to undefined.
* @type {undefined}
*/
moving_player = undefined
/**
* Array of players filling starting line up positions. If no player is in
* a position, then only the "postition" key will be set.
* @type {object[]}
*/
starting_players = [
{
position: 'QB',
},
{
position: 'RB',
},
{
position: 'RB',
},
{
position: 'WR',
},
{
position: 'WR',
},
{
position: 'TE',
},
{
position: 'FLX',
},
{
position: 'DST',
},
]
/**
* Players on the bench.
* @type {object[]}
*/
bench_players = []
/**
* Column definitions for the starting/bench lineup grids.
* @type {object[]}
*/
lineup_column_defs = [
{
header: 'POS',
key: 'position',
},
{
header: 'Player',
key: 'player_name',
type: GridCellRenderType.HTML,
renderer: (_, data) => {
if ( !data.player_name ) {
return `<i style="color: darkgrey">none</i>`
} else {
return `
<div class="center">
<img src="${data.image}" alt="${data.player_name}" height="50" style="border-radius: 50%">
<span>${data.player_name}</span>
</div>
`
}
},
},
{
header: '',
key: 'player_name',
type: GridCellRenderType.Component,
component: Vue.component('app-action-button'),
button_color: (row, col) => this.moving_player ? '#CC5746' : '#0582CA',
button_text: (row, col) => {
return this.moving_player ? 'Here' : 'Move'
},
button_hidden: (row, col) => {
if ( this.moving_player && this.moving_player.player_name !== row.player_name ) return false;
if ( !row.player_name ) return true;
return this.moving_player && this.moving_player.player_name === row.player_name;
},
on_click: (row, col) => {
if ( !this.moving_player ) {
this.moving_player = row;
} else {
const old_row = {...row};
row.player_name = this.moving_player.player_name;
row.ecr = this.moving_player.ecr;
row.image = this.moving_player.image;
this.moving_player.player_name = old_row.player_name;
this.moving_player.ecr = old_row.ecr;
this.moving_player.image = old_row.image;
this.moving_player = undefined;
console.log(this.moving_player, row);
}
this.$_vue_inst.update(); // $_vue_inst refers to the Vue.component instance, not the data class.
},
},
]
/**
* Column definitions for the overall team grid.
* @type {object[]}
*/
overall_column_defs = [
{
header: 'Name',
key: 'player_name',
type: GridCellRenderType.HTML,
renderer: (_, data) => `
<div class="center">
<img src="${data.image}" alt="${data.player_name}" height="50" style="border-radius: 50%">
<span>${data.player_name}</span>
</div>
`,
},
{
header: 'POS',
key: 'position',
},
{
header: 'ECR',
title: 'Expected Coverage Rating',
key: 'ecr',
},
]
/**
* Data for the overall team grid (list of user's team players).
* @type {object[]}
*/
overall_data = [
{
player_name: 'Christian McCaffrey',
position: 'RB1',
ecr: '0.0',
"image": "https://images.generated.photos/eGoWRgqxtahGFDAD81-l8CNxdz1oe-huz3CQ7m3v0VI/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAzNDA0NDlfMDgz/MDY1Nl8wMTk4NTI4/LmpwZw.jpg",
},
{
player_name: 'Ezekiel Elliott',
position: 'RB3',
ecr: '1.0',
"image": "https://images.generated.photos/fd8kkioB4vLw_5MGwQXdDt9Q7Ley2_Ia8Cu390zaNVM/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzA0Nzg2ODEuanBn.jpg",
},
{
player_name: 'Dalvin Cook',
position: 'RB5',
ecr: '0.0',
"image": "https://images.generated.photos/PEBx5b8_iPHU_nJpJbh3geUN8cBFglHVAAR9NktzXsk/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAxODI1NzlfMDgx/MTA0OV8wNDQzOTM5/LmpwZw.jpg",
},
{
player_name: 'Alvin Kamara',
position: 'RB6',
ecr: '-1.0',
"image": "https://images.generated.photos/cb3jAo-GBziFLxs85KJGt7a8bJdhz4sSy76PYAXkeg4/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzA1ODU4MDBfMDMy/NjY2OF8wODEwNTA2/LmpwZw.jpg",
},
{
player_name: 'Michael Thomas',
position: 'WR1',
ecr: '3.0',
"image": "https://images.generated.photos/LLiy3FypH5A1suda78U82t_Kcn9AlJwZt1g3w1p5DwE/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAzODc0NjlfMDUy/MDc0NF8wNzc3NzQ5/LmpwZw.jpg",
},
{
player_name: 'Davante Adams',
position: 'WR2',
ecr: '4.0',
"image": "https://images.generated.photos/dW84LNLE4Kzp73NTTnL68U--dYuq8CCzD-dGTs76U38/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAyNjE1NjZfMDEz/NDM1NF8wMTg5MjI0/LmpwZw.jpg",
},
{
player_name: 'Travis Kelce',
position: 'TE1',
ecr: '-4.0',
"image": "https://images.generated.photos/erudOopARQnXWNaLqkIPRLLMLAVBr8m70aFC_dtYu1Y/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAzODA5MTVfMDkx/MzIzN18wNDQxMTk4/LmpwZw.jpg",
},
]
/**
* Called when the component is instantiated. Initializes the bench players data.
* @return {Promise<void>}
*/
async vue_on_create() {
this.bench_players = this.overall_data.map(x => { x = {...x, position: 'B'}; return x })
setTimeout(() => {
this.update();
}, 500);
}
/**
* Force re-render the entire component by briefly hiding it.
*/
update() {
this.show_body = false;
this.$nextTick(() => {
this.show_body = true;
});
}
}
export default MyTeamComponent

View File

@@ -0,0 +1,347 @@
import {Component} from '../../../lib/vues6.js'
import {GridCellRenderType} from '../Grid.component.js'
const template = `
<div class="page-scores">
<div class="header">
<div class="left">
<h2>Matchups & Scores - <small>Week {{ current_week }}</small></h2>
</div>
<div class="right">
<button :class="{ 'disable-click': current_week === max_week }" @click="to_next_week()">Next Week</button><button :class="{ 'disable-click': current_week === min_week }" @click="to_previous_week()">Previous Week</button>
</div>
</div>
<app-grid
:column_defs="column_defs"
:data="data"
:show_row_numbers="false"
></app-grid>
</div>
`
/**
* Component representing the scores & match-ups page.
* @extends Component
*/
class ScoresComponent extends Component {
static get selector() { return 'page-scores' }
static get template() { return template }
static get props() { return [] }
/**
* The number of the current week shown in the interface
* @type {number}
*/
current_week = 6
/**
* Most recent week number.
* @type {number}
*/
max_week = 6
/**
* Least recent week number.
* @type {number}
*/
min_week = 1
/**
* Array of arrays of data for each week with first item being week 1, second being week 2, &c.
* @type {object[][]}
*/
week_x_data = [
// Week 1 Data
[
{
"date": "11/2/2020",
"team_1": "Team 1",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 50,
"team_2": "Team 6",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 73
},
{
"date": "10/23/2020",
"team_1": "Team 2",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 66,
"team_2": "Team 5",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 71,
"winner": "Team 5",
"winner_score": "84",
"loser_score": "41",
},
{
"date": "10/31/2020",
"team_1": "Team 3",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 85,
"team_2": "Team 4",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 67
},
],
// Week 2 Data
[
{
"date": "11/2/2020",
"team_1": "Team 1",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 58,
"team_2": "Team 6",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 34
},
{
"date": "10/23/2020",
"team_1": "Team 2",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 57,
"team_2": "Team 5",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 27,
"winner": "Team 5",
"winner_score": "84",
"loser_score": "41",
},
{
"date": "10/31/2020",
"team_1": "Team 3",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 48,
"team_2": "Team 4",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 49
},
],
// Week 3 Data
[
{
"date": "11/2/2020",
"team_1": "Team 1",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 67,
"team_2": "Team 6",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 47
},
{
"date": "10/23/2020",
"team_1": "Team 2",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 83,
"team_2": "Team 5",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 62,
"winner": "Team 5",
"winner_score": "84",
"loser_score": "41",
},
{
"date": "10/31/2020",
"team_1": "Team 3",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 48,
"team_2": "Team 4",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 17
},
],
// Week 4 Data
[
{
"date": "11/2/2020",
"team_1": "Team 1",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 30,
"team_2": "Team 6",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 41
},
{
"date": "10/23/2020",
"team_1": "Team 2",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 65,
"team_2": "Team 5",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 27,
"winner": "Team 5",
"winner_score": "84",
"loser_score": "41",
},
{
"date": "10/31/2020",
"team_1": "Team 3",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 48,
"team_2": "Team 4",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 24
},
],
// Week 5 Data
[
{
"date": "11/2/2020",
"team_1": "Team 1",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 43,
"team_2": "Team 6",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 48
},
{
"date": "10/23/2020",
"team_1": "Team 2",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 57,
"team_2": "Team 5",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 61,
"winner": "Team 5",
"winner_score": "84",
"loser_score": "41",
},
{
"date": "10/31/2020",
"team_1": "Team 3",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 48,
"team_2": "Team 4",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 91
},
],
// Week 6 Data
[
{
"date": "11/2/2020",
"team_1": "Team 1",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 50,
"team_2": "Team 6",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 37
},
{
"date": "10/23/2020",
"team_1": "Team 2",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 36,
"team_2": "Team 5",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 71,
"winner": "Team 5",
"winner_score": "84",
"loser_score": "41",
},
{
"date": "10/31/2020",
"team_1": "Team 3",
"team_1_logo": "https://via.placeholder.com/150x100",
"team_1_projection": 48,
"team_2": "Team 4",
"team_2_logo": "https://via.placeholder.com/150x100",
"team_2_projection": 1
},
]
]
/**
* Column definitions for the matchups grid.
* @type {object[]}
*/
column_defs = [
{
header: 'Date',
type: GridCellRenderType.HTML,
key: 'date',
renderer: (_, data) => {
return `${data.date} @ ${data.team_1}`
}
},
{
header: 'Team 1',
type: GridCellRenderType.HTML,
key: 'team_1',
renderer: (_, data) => `
<div style="display: flex; flex-direction: row;">
<img src="${data.team_1_logo}" alt="${data.team_1}">
<div style="margin-left: 20px">
<b>${data.team_1}</b>
<p>Projection: ${data.team_1_projection}</p>
</div>
</div>
`
},
{
header: 'Team 2',
type: GridCellRenderType.HTML,
key: 'team_2',
renderer: (_, data) => `
<div style="display: flex; flex-direction: row;">
<img src="${data.team_2_logo}" alt="${data.team_2}">
<div style="margin-left: 20px">
<b>${data.team_2}</b>
<p>Projection: ${data.team_2_projection}</p>
</div>
</div>
`
},
{
header: 'Outcome',
type: GridCellRenderType.HTML,
key: 'winner',
renderer: (_, data) => {
if ( data?.winner ) {
return `
<div><b>Winner:</b> ${data.winner}</div>
<div><b>Score: </b> ${data.winner_score} / ${data.loser_score}</div>
`
} else {
return `N/A`
}
},
}
]
/**
* The currently shown week's data.
* @type {object[]}
*/
data = []
/**
* Called when the component is instantiated. Initializes the current week to the most recent week.
* @return {Promise<void>}
*/
async vue_on_create() {
this.data = this.week_x_data[this.max_week - 1];
}
/**
* When called, advances the data to the next-most recent week, if one exists.
*/
to_next_week() {
if ( this.current_week < this.max_week ) {
this.current_week += 1;
this.data = this.week_x_data[this.current_week - 1];
}
}
/**
* When called, advances the data to the next-least recent week, if one exists.
*/
to_previous_week() {
if ( this.current_week > this.min_week ) {
this.current_week -= 1;
this.data = this.week_x_data[this.current_week - 1];
}
}
}
export default ScoresComponent

View File

@@ -0,0 +1,361 @@
/** @module fake_data */
/**
* Array of players with stats info.
* @type {object[]}
*/
export const fake_players = [
{
"number": 14,
"name": "Andy Dalton",
"position": "Quarterback",
"stats":
{
"Passing Yards": "1" ,
"Rushing Yards": "551 YDS",
"completions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/-K9iBY4oOkLsqQfoTA1R8X0EKvR_BCbMXk0KNX4EIIs/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzA0NzY1MjkuanBn.jpg",
"showing_stats": false,
"points": 13,
},
{
"number": 7,
"name": "Ben DiNucci",
"position": "Quarterback",
"stats":
{
"Passing Yards": "1" ,
"Rushing Yards": "551 YDS",
"completions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/EUa6Hmnt6682dl03Q5FPIeMqLnS833rfzOJaJXlYxqI/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAxNTk3OTFfMDU1/Nzg5OF8wMzgzMzIw/LmpwZw.jpg",
"showing_stats": false,
"points": 0,
},
{
"number": 3,
"name": "Garrett Gilbert",
"position": "Quarterback",
"stats":
{
"Passing Yards": "1" ,
"Rushing Yards": "551 YDS",
"completions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/eGoWRgqxtahGFDAD81-l8CNxdz1oe-huz3CQ7m3v0VI/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAzNDA0NDlfMDgz/MDY1Nl8wMTk4NTI4/LmpwZw.jpg",
"showing_stats": false,
"points": 0,
"speed": 0,
"price": "$0.00",
},
{
"number": 34,
"name": "Rico Dowdle",
"position": "Running back",
"stats":
{
"Rushing Yards": "1" ,
"Receiving Yards": "551 YDS",
"Receptions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/fd8kkioB4vLw_5MGwQXdDt9Q7Ley2_Ia8Cu390zaNVM/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzA0Nzg2ODEuanBn.jpg",
"showing_stats": false,
"points": 0,
"speed": 0,
"price": "$0.00",
},
{
"number": 21,
"name": "Ezekiel Elliott",
"position": "Running back",
"stats":
{
"Rushing Yards": "1" ,
"Receiving Yards": "551 YDS",
"Receptions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/PEBx5b8_iPHU_nJpJbh3geUN8cBFglHVAAR9NktzXsk/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAxODI1NzlfMDgx/MTA0OV8wNDQzOTM5/LmpwZw.jpg",
"showing_stats": false,
"points": 18,
"speed": 0,
"price": "$0.00",
},
{
"number": 20,
"name": "Tony Pollard",
"position": "Running back",
"stats":
{
"Rushing Yards": "1" ,
"Receiving Yards": "551 YDS",
"Receptions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/cb3jAo-GBziFLxs85KJGt7a8bJdhz4sSy76PYAXkeg4/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzA1ODU4MDBfMDMy/NjY2OF8wODEwNTA2/LmpwZw.jpg",
"showing_stats": false,
"points": 6,
"speed": 0,
"price": "$0.00",
},
{
"number": 85,
"name": "Noah Brown",
"position": "Wide receiver",
"stats":
{
"Rushing Yards": "1" ,
"Receiving Yards": "551 YDS",
"Receptions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/LLiy3FypH5A1suda78U82t_Kcn9AlJwZt1g3w1p5DwE/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAzODc0NjlfMDUy/MDc0NF8wNzc3NzQ5/LmpwZw.jpg",
"showing_stats": false,
"points": 0,
"speed": 0,
"price": "$0.00",
},
{
"number": 19,
"name": "Amari Cooper",
"position": "Wide receiver",
"stats":
{
"Rushing Yards": "1" ,
"Receiving Yards": "551 YDS",
"Receptions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/dW84LNLE4Kzp73NTTnL68U--dYuq8CCzD-dGTs76U38/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAyNjE1NjZfMDEz/NDM1NF8wMTg5MjI0/LmpwZw.jpg",
"showing_stats": false,
"points": 19,
"speed": 0,
"price": "$0.00",
},
{
"number": 13,
"name": "Michael Gallup",
"position": "Wide receiver",
"stats":
{
"Rushing Yards": "1" ,
"Receiving Yards": "551 YDS",
"Receptions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/erudOopARQnXWNaLqkIPRLLMLAVBr8m70aFC_dtYu1Y/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAzODA5MTVfMDkx/MzIzN18wNDQxMTk4/LmpwZw.jpg",
"showing_stats": false,
"points": 14,
},
{
"number": 88,
"name": "CeeDee Lamb",
"position": "Wide receiver",
"stats":
{
"Rushing Yards": "1" ,
"Receiving Yards": "551 YDS",
"Receptions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/WFV4nHHq5ZaBb1rdmFL5WEZTOanckWHEfkmDA1fOVfw/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAxNTYzNzNfMDI4/Nzc2N18wNzYxNDY3/LmpwZw.jpg",
"showing_stats": false,
"points": 11,
},
{
"number": 17,
"name": "Malik Turner",
"position": "Wide receiver",
"stats":
{
"Rushing Yards": "1" ,
"Receiving Yards": "551 YDS",
"Receptions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/PEBx5b8_iPHU_nJpJbh3geUN8cBFglHVAAR9NktzXsk/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAxODI1NzlfMDgx/MTA0OV8wNDQzOTM5/LmpwZw.jpg",
"showing_stats": false,
"points": 3,
},
{
"number": 11,
"name": "Cedrick Wilson Jr.",
"position": "Wide receiver",
"stats":
{
"Rushing Yards": "1" ,
"Receiving Yards": "551 YDS",
"Receptions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/fd8kkioB4vLw_5MGwQXdDt9Q7Ley2_Ia8Cu390zaNVM/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzA0Nzg2ODEuanBn.jpg",
"showing_stats": false,
"points": 8,
},
{
"number": 80,
"name": "Blake Bell",
"position": "Tight end",
"stats":
{
"Rushing Yards": "1" ,
"Receiving Yards": "551 YDS",
"Receptions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/eGoWRgqxtahGFDAD81-l8CNxdz1oe-huz3CQ7m3v0VI/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAzNDA0NDlfMDgz/MDY1Nl8wMTk4NTI4/LmpwZw.jpg",
"showing_stats": false,
"points": 9,
},
{
"number": 84,
"name": "Sean McKeon",
"position": "Tight end",
"stats":
{
"Rushing Yards": "1" ,
"Receiving Yards": "551 YDS",
"Receptions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/EUa6Hmnt6682dl03Q5FPIeMqLnS833rfzOJaJXlYxqI/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzAxNTk3OTFfMDU1/Nzg5OF8wMzgzMzIw/LmpwZw.jpg",
"showing_stats": false,
"points": 0,
},
{
"number": 86,
"name": "Dalton Schultz",
"position": "Tight end",
"stats":
{
"Rushing Yards": "1" ,
"Receiving Yards": "551 YDS",
"Receptions": "1",
"TD": "1",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/-K9iBY4oOkLsqQfoTA1R8X0EKvR_BCbMXk0KNX4EIIs/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzA0NzY1MjkuanBn.jpg",
"showing_stats": false,
"points": 13,
},
{
"number": 23,
"name": "Darian Thompson RB",
"position": "Running back",
"stats":
{
"Rushing Yards": "1" ,
"Receiving Yards": "551 YDS",
"Receptions": "1",
"TD": "1",
},
"team_name": "Kansas City Chiefs",
"image": "https://images.generated.photos/cb3jAo-GBziFLxs85KJGt7a8bJdhz4sSy76PYAXkeg4/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzA1ODU4MDBfMDMy/NjY2OF8wODEwNTA2/LmpwZw.jpg",
"showing_stats": false,
"points": 4,
},
{
"number": 2,
"name": "Greg Zuerlein K",
"position": "Special team",
"stats":
{
"XP": "1" ,
"FG": "551 YDS",
},
"team_name": "Dallas Cowboys",
"image": "https://images.generated.photos/fd8kkioB4vLw_5MGwQXdDt9Q7Ley2_Ia8Cu390zaNVM/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzA0Nzg2ODEuanBn.jpg",
"showing_stats": false,
"points": 11,
},
{
"number": "N/A",
"name": "Arizona Cardinals",
"position": "Defense",
"stats":
{
"Fumble": "1" ,
"YDS_Allowed": "551 YDS",
"Sacks": "1",
"INT": "1",
"TD": "0",
},
"team_name": "Arizona Cardinals",
"image": "https://images.generated.photos/fd8kkioB4vLw_5MGwQXdDt9Q7Ley2_Ia8Cu390zaNVM/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzA0Nzg2ODEuanBn.jpg",
"showing_stats": false,
"points": 11,
},
{
"number": "N/A",
"name": "Kansas City Chiefs",
"position": "Defense",
"stats":
{
"Fumble": "1" ,
"YDS_Allowed": "551 YDS",
"Sacks": "1",
"INT": "1",
"TD": "0",
},
"team_name": "Kansas City Chiefs",
"image": "https://images.generated.photos/fd8kkioB4vLw_5MGwQXdDt9Q7Ley2_Ia8Cu390zaNVM/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzA0Nzg2ODEuanBn.jpg",
"showing_stats": false,
"points": 11,
},
{
"number": "N/A",
"name": "Atlanta Falcons",
"position": "Defense",
"stats":
{
"Fumble": "1" ,
"YDS_Allowed": "551 YDS",
"Sacks": "1",
"INT": "1",
"TD": "0",
},
"team_name": "Atlanta Falcons",
"image": "https://images.generated.photos/fd8kkioB4vLw_5MGwQXdDt9Q7Ley2_Ia8Cu390zaNVM/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzA0Nzg2ODEuanBn.jpg",
"showing_stats": false,
"points": 11,
},
{
"number": "N/A",
"name": "Baltimore Ravens",
"position": "Defense",
"stats":
{
"Fumble": "1" ,
"YDS_Allowed": "551 YDS",
"Sacks": "1",
"INT": "1",
"TD": "0",
},
"team_name": "Baltimore Ravens",
"image": "https://images.generated.photos/fd8kkioB4vLw_5MGwQXdDt9Q7Ley2_Ia8Cu390zaNVM/rs:fit:128:128/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Yz/XzA0Nzg2ODEuanBn.jpg",
"showing_stats": false,
"points": 11,
},
]

View File

@@ -0,0 +1,94 @@
/** @module routing */
/**
* A bare-bones history-api based SPA router.
*/
class Router {
/**
* Arguments for the current route.
* @type {undefined|*}
*/
route_args = undefined
/**
* List of callback functions listening for route changes.
* @type {function[]}
*/
subscribers = []
/**
* Array of router history records.
* @type {object[]}
*/
history = []
/**
* Returns the APP_BASE_PATH of the application.
* @return {string}
*/
get base_path() {
return APP_BASE_PATH
}
/**
* Navigate the app to the given path with the given args.
* @param {string} path
* @param {*} args
*/
navigate(path, args) {
this.route_args = args
this.history.push({path, args})
window.history.pushState({}, path, this.build_url(path))
this.subscribers.forEach(sub => sub(path, args))
}
/**
* Navigate back one route.
*/
back() {
window.history.back()
if ( this.history.length < 2 ) return;
this.history.pop()
const { path, args } = this.history[this.history.length - 1]
this.subscribers.forEach(sub => sub(path, args))
}
/**
* Subscribe to listen for route changes. Returns an object with an unsubscribe() property.
* @param {function} handler - callback called when the route changes
* @return {object} - subscription manager
*/
subscribe(handler) {
if ( !this.subscribers.includes(handler) ) {
this.subscribers.push(handler)
}
return {
unsubscribe: () => {
this.subscribers = this.subscribers.filter(handler)
}
}
}
/**
* Given an array of route parts, build a joined URL route.
* @param {...string} parts
* @return {string}
*/
build_url(...parts) {
parts = [this.base_path, ...parts].map(part => {
if ( part.endsWith('/') ) part = part.slice(0, -1)
if ( part.startsWith('/') ) part = part.slice(1)
return part
})
return parts.join('/')
}
}
/**
* Global router instance.
* @type {Router}
*/
const router = new Router()
export { router }

View File

@@ -0,0 +1,22 @@
import components from '../components.js'
import VuES6Loader from '../../lib/vues6.js'
/*
* This is a little script to load the components into Vue in a nice way.
*/
const loader = new VuES6Loader(components)
loader.load()
/*
* This is the Vue app itself.
*/
const app = new Vue({
el: '#wrapper',
data: {},
})
/*
* In case either needs to be accessed, they can with:
* import { app, loader } from './start.js'
*/
export { app, loader }

View File

@@ -0,0 +1,41 @@
/** @module util */
/**
* Makes a deep copy of the value passed in.
* @param {*} obj
* @return {*}
*/
export function clone(obj) {
// If it's just a value, return it.
if ( typeof obj !== 'object' || obj === null ) return obj
// If it's an array, copy its values.
if ( Array.isArray(obj) ) return obj.map(x => clone(x))
// If it's an object, copy its properties.
const copy = {}
for ( const prop in obj ) {
copy[prop] = clone(obj[prop])
}
return copy
}
/**
* Generate an absolute URL to a file w/in the project directory.
* @param {string} path
* @return {string}
*/
export function appUrl(path) {
if ( path.startsWith('/') ) path = path.slice(1)
return `${APP_BASE_PATH}${path}`
}
/**
* Generates a UUIDv4. Taken from: https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid
* @return {string}
*/
export function uuid_v4() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}

View File

@@ -0,0 +1,260 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;400;700&display=swap');
body {
font-family: 'Roboto Condensed', sans-serif;
}
:root {
--color-text-dark: #022D50;
--color-text-light: #FDFFFC;
--color-accent-1: #0582CA;
--color-accent-2: #00A6FB;
--color-accent-3: #CC5746;
}
#wrapper {
height: 100%;
width: 100%;
position: fixed;
top: 0;
left: 0;
overflow-y: scroll;
}
.top-level-container {
flex: 1;
display: flex;
flex-direction: column;
}
/****** NAVIGATION BAR STYLES ******/
.navbar-container {
background: var(--color-text-light);
color: var(--color-text-dark);
position: fixed;
width: 100%;
}
.navbar-container .title {
margin-left: 20px;
}
.navbar {
list-style-type: none;
margin: 0;
padding: 0;
color: var(--color-text-light);
background: var(--color-text-dark);
overflow: hidden;
}
.navbar .navbar-item {
display: inline;
float: left;
transition: all linear 150ms;
}
.navbar .navbar-item:hover, .navbar .navbar-item.active {
background: var(--color-text-light);
color: var(--color-text-dark);
}
.navbar .navbar-item a {
display: block;
text-align: center;
text-decoration: none;
padding: 15px;
}
.page-container {
margin-top: 130px;
}
/******** SHARED GRID STYLES ********/
.component-app-grid table {
width: 100%;
border: 1px solid var(--color-text-dark);
border-collapse: collapse;
}
.component-app-grid th, .component-app-grid td {
padding: 10px;
border: 1px solid var(--color-text-dark);
border-collapse: collapse;
}
div.center {
display: table;
align-items: center;
text-align: center;
}
div.center img {
vertical-align: middle;
display: table-cell;
}
div.center span {
padding: 10px;
vertical-align: middle;
display: table-cell;
}
#ranking{
text-align: center;
}
#record{
text-align: center;
}
.header {
padding-left: 15px;
display: flex;
flex-direction: row;
}
.header .left {
flex: 1;
}
.header .right {
margin-right: 20px;
}
.header .right button {
margin: 0;
margin-top: 25px;
border: 1px solid lightgray;
padding: 5px 20px;
}
.header .right button:hover:not(.disable-click) {
background: lightgrey;
cursor: pointer;
}
.header .right button.disable-click {
background: darkgrey;
}
/********* ADD PLAYERS PAGE **********/
.page-add-players {
display: flex;
flex-direction: column;
}
.page-add-players .header input {
border: 1px solid lightgray;
padding: 5px;
border-radius: 3px;
margin-top: 24px;
}
.page-add-players .item-grid {
flex: 1;
display: flex;
flex-direction: row;
height: 270px;
flex-wrap: wrap;
}
.page-add-players .item-grid .item {
margin: 15px;
border: 1px solid darkgrey;
border-radius: 5px;
padding: 10px;
min-width: 180px;
min-height: 270px;
display: flex;
flex-direction: column;
}
.page-add-players .item-grid .item .item-icon {
text-align: center;
}
.page-add-players .item-grid .item .item-icon img {
height: 100px;
border-radius: 50%;
}
.page-add-players .item-grid .item .item-contents h1 {
font-size: 1.5em;
}
.page-add-players .item-grid .item .item-button button {
width: 100%;
color: white;
border-radius: 5px;
}
.page-add-players .item-grid .item .item-button button.add {
border: 1px solid #009900;
background: darkgreen;
}
.page-add-players .item-grid .item .item-button button.add:hover {
background: #009900;
cursor: pointer;
}
.page-add-players .item-grid .item .item-button button.remove {
border: 1px solid #ff1a1a;
background: #cc0000;
}
.page-add-players .item-grid .item .item-button button.remove:hover {
background: #ff1a1a;
cursor: pointer;
}
/************ MY TEAM PAGE **************/
.page-my-team .header .team-name {
display: flex;
flex-direction: row;
}
.page-my-team .header .team-name input {
margin-left: 5px;
border: none;
font-size: 15pt;
font-family: 'Roboto Condensed', sans-serif;
flex: 1;
}
/************ League PAGE **************/
#ranking{
text-align: center;
}
#record{
text-align: center;
}
.page-league .stats {
display: flex;
flex-direction: row;
}
.page-league .stats .stat {
margin-right: 10px;
}
.page-league .stats .stat .title {
font-weight: bold;
}
/************ Draft Board PAGE **************/
.page-draft-board .stats {
display: flex;
flex-direction: row;
}
.page-draft-board .stats .stat {
margin-right: 10px;
}
.page-draft-board .stats .stat .title {
font-weight: bold;
}