Add logic for serving front-end from /app
This commit is contained in:
25
frontend/src/components.js
Normal file
25
frontend/src/components.js
Normal 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,
|
||||
}
|
||||
72
frontend/src/components/Grid.component.js
Normal file
72
frontend/src/components/Grid.component.js
Normal 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
|
||||
115
frontend/src/components/GridActionButton.component.js
Normal file
115
frontend/src/components/GridActionButton.component.js
Normal 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
|
||||
25
frontend/src/components/Link.component.js
Normal file
25
frontend/src/components/Link.component.js
Normal 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
|
||||
99
frontend/src/components/TopLevel.component.js
Normal file
99
frontend/src/components/TopLevel.component.js
Normal 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
|
||||
175
frontend/src/components/pages/AddPlayers.component.js
Normal file
175
frontend/src/components/pages/AddPlayers.component.js
Normal 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
|
||||
125
frontend/src/components/pages/DraftBoard.component.js
Normal file
125
frontend/src/components/pages/DraftBoard.component.js
Normal 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
|
||||
180
frontend/src/components/pages/League.component.js
Normal file
180
frontend/src/components/pages/League.component.js
Normal file
File diff suppressed because one or more lines are too long
264
frontend/src/components/pages/MyTeam.component.js
Normal file
264
frontend/src/components/pages/MyTeam.component.js
Normal 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
|
||||
347
frontend/src/components/pages/Scores.component.js
Normal file
347
frontend/src/components/pages/Scores.component.js
Normal 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
|
||||
361
frontend/src/module/fake_data.js
Normal file
361
frontend/src/module/fake_data.js
Normal 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,
|
||||
},
|
||||
]
|
||||
94
frontend/src/module/routing.js
Normal file
94
frontend/src/module/routing.js
Normal 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 }
|
||||
22
frontend/src/module/start.js
Normal file
22
frontend/src/module/start.js
Normal 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 }
|
||||
41
frontend/src/module/util.js
Normal file
41
frontend/src/module/util.js
Normal 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)
|
||||
);
|
||||
}
|
||||
260
frontend/src/style/components.css
Normal file
260
frontend/src/style/components.css
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user