Compare commits
115 Commits
469c298753
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
87b2abbd80
|
|||
|
b0de3fb221
|
|||
|
149f0a411b
|
|||
|
e6d1d5a697
|
|||
|
bf9d0c1a78
|
|||
|
64fa16740b
|
|||
|
f273677ba7
|
|||
|
ca194d516d
|
|||
|
ed18389bb2
|
|||
|
b346710496
|
|||
|
6d1588e80f
|
|||
|
858cb24e3f
|
|||
|
196eb5f51d
|
|||
|
07446570c1
|
|||
|
a5c47ff9a7
|
|||
|
aa2ebf0b2e
|
|||
|
5f02ea4d5c
|
|||
|
39380eaf03
|
|||
|
cb131353dc
|
|||
|
e1c9a7b81b
|
|||
|
c495b265ee
|
|||
|
49864874fc
|
|||
|
e9fd60e619
|
|||
|
84da372330
|
|||
|
c696ffb4bc
|
|||
|
a06807b028
|
|||
|
07be459781
|
|||
|
769fa60196
|
|||
|
d377399c10
|
|||
|
fa50ab93dc
|
|||
|
73e3afcecc
|
|||
|
af9fa3bd9b
|
|||
|
7e803cc8e3
|
|||
|
339d4c7923
|
|||
|
58825b5bcd
|
|||
|
6b9e6734fe
|
|||
|
03a9ac3aae
|
|||
|
da159f2b13
|
|||
|
c2c192bdc6
|
|||
|
c2b1898b91
|
|||
|
4ee466e7cb
|
|||
|
6592f2eeae
|
|||
|
1f2ba45e54
|
|||
|
588c23ec3f
|
|||
|
1c3cafdcda
|
|||
|
fb0ca76c29
|
|||
|
bdb6a77371
|
|||
|
e4b981d676
|
|||
|
c4c0d0fb7d
|
|||
|
3695cd8975
|
|||
|
bcade85182
|
|||
|
d50f94acc8
|
|||
|
5e1facf24a
|
|||
|
cf01bf9fff
|
|||
|
83e722cdb9
|
|||
|
dd48aeca8d
|
|||
|
f421089eb9
|
|||
|
d484f8cfdf
|
|||
|
87735f76b5
|
|||
|
6d4600a858
|
|||
|
5c19a16f8b
|
|||
|
aea6a64b69
|
|||
|
b9ead6770a
|
|||
|
84cc1f651c
|
|||
|
a1cb6fcf0a
|
|||
|
e459a0688a
|
|||
|
8e98286f7c
|
|||
|
87fb835590
|
|||
|
1f897d2b1e
|
|||
|
8c1b325532
|
|||
|
4b56259b98
|
|||
|
16b1402c6f
|
|||
|
e4f6576847
|
|||
|
2df02e7452
|
|||
|
99d376af4c
|
|||
|
2b497a0227
|
|||
|
00b270e0f6
|
|||
|
2a2eb07823
|
|||
|
39e6c2b5af
|
|||
|
832fb654ec
|
|||
|
9f9da4e191
|
|||
|
f2371c6b5a
|
|||
|
dc17ca76ba
|
|||
|
d24b2a121e
|
|||
|
7c5630c5ba
|
|||
|
e4b4345cff
|
|||
|
dfab474f42
|
|||
|
053f6038f6
|
|||
|
cb69521875
|
|||
|
58c870ce7c
|
|||
|
61b6dc8a35
|
|||
|
d72ff726a5
|
|||
|
b53c8c532e
|
|||
|
4f30021a99
|
|||
|
05c948fd16
|
|||
|
7a1dfa3165
|
|||
|
fda4e1c3cc
|
|||
|
c8d0221247
|
|||
|
a9fa89107e
|
|||
|
7efb083e1d
|
|||
|
fb648d477f
|
|||
|
6076a2801e
|
|||
|
b2b2dba352
|
|||
|
b9f9c8455f
|
|||
|
54dac93da5
|
|||
|
3a95ca4b74
|
|||
|
450499d9aa
|
|||
|
39d1a37043
|
|||
|
35d6eba599
|
|||
|
2f2f33ce74
|
|||
|
66c18479b3
|
|||
|
a505747b06
|
|||
|
6576d17539
|
|||
|
70a7981ca5
|
|||
|
c9eaadf688
|
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,7 @@
|
||||
.env
|
||||
var
|
||||
src/public/media/
|
||||
|
||||
|
||||
#ide
|
||||
/.nova
|
||||
|
||||
35
.vscode/launch.json
vendored
Normal file
35
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Attach",
|
||||
"port": 9229,
|
||||
"request": "attach",
|
||||
"restart": true,
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node",
|
||||
"localRoot": "${workspaceFolder}/src",
|
||||
"remoteRoot": "/home/node/app/src"
|
||||
},
|
||||
{
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"name": "nodemon (dev)",
|
||||
"program": "dev",
|
||||
"request": "launch",
|
||||
"restart": true,
|
||||
"runtimeExecutable": "nodemon",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node",
|
||||
"env": {"NODE_ENV": "development", "DEBUG": "app"},
|
||||
"preLaunchTask": "npm: build-css"
|
||||
}
|
||||
]
|
||||
}
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
|
||||
}
|
||||
23
.vscode/tasks.json
vendored
Normal file
23
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "watch-scss",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: watch-scss",
|
||||
"detail": "npm run watch-css",
|
||||
"icon": {
|
||||
"id": "eye",
|
||||
"color": "terminal.ansiBlue"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "build-css",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: build-css",
|
||||
"detail": "npm build-css"
|
||||
}
|
||||
]
|
||||
}
|
||||
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM node:21
|
||||
|
||||
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
|
||||
RUN mkdir -p /home/node/app/var/db && chown -R node:node /home/node/app
|
||||
WORKDIR /home/node/app
|
||||
|
||||
USER node
|
||||
COPY --chown=node:node package*.json ./
|
||||
RUN npm install
|
||||
COPY --chown=node:node src src
|
||||
COPY --chown=node:node bin bin
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "npm", "start" ]
|
||||
86
app.js
86
app.js
@@ -1,86 +0,0 @@
|
||||
require("dotenv").config();
|
||||
|
||||
var createError = require("http-errors");
|
||||
var express = require("express");
|
||||
var path = require("path");
|
||||
var cookieParser = require("cookie-parser");
|
||||
var session = require("express-session");
|
||||
var csrf = require("csurf");
|
||||
var passport = require("passport");
|
||||
var logger = require("morgan");
|
||||
global.XMLHttpRequest = require("xhr2");
|
||||
var teamsnap = require("teamsnap.js");
|
||||
|
||||
var indexRouter = require("./routes/index");
|
||||
var authRouter = require("./routes/auth");
|
||||
|
||||
var app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set("views", path.join(__dirname, "views"));
|
||||
app.set("view engine", "pug");
|
||||
|
||||
app.locals.pluralize = require("pluralize");
|
||||
|
||||
app.use(logger("dev"));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
app.use(
|
||||
"/css",
|
||||
express.static(path.join(__dirname, "node_modules/bootstrap/dist/css"))
|
||||
);
|
||||
app.use(
|
||||
"/css",
|
||||
express.static(
|
||||
path.join(__dirname, "node_modules/@teamsnap/teamsnap-ui/dist/css")
|
||||
)
|
||||
);
|
||||
app.use(
|
||||
"/font",
|
||||
express.static(path.join(__dirname, "node_modules/bootstrap-icons/font"))
|
||||
);
|
||||
app.use(
|
||||
session({
|
||||
teamsnap_token: "",
|
||||
current_team: "",
|
||||
secret: "keyboard cat",
|
||||
resave: false, // don't save session if unmodified
|
||||
saveUninitialized: false, // don't create session until something stored
|
||||
})
|
||||
);
|
||||
app.use(csrf());
|
||||
app.use(passport.authenticate("session"));
|
||||
app.use(function (req, res, next) {
|
||||
var msgs = req.session.messages || [];
|
||||
res.locals.messages = msgs;
|
||||
res.locals.hasMessages = !!msgs.length;
|
||||
req.session.messages = [];
|
||||
next();
|
||||
});
|
||||
app.use(function (req, res, next) {
|
||||
res.locals.csrfToken = req.csrfToken();
|
||||
next();
|
||||
});
|
||||
|
||||
app.use("/", authRouter);
|
||||
app.use("/", indexRouter);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function (req, res, next) {
|
||||
next(createError(404));
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use(function (err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get("env") === "development" ? err : {};
|
||||
console.log("error:", err);
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.render("error", { message: err.message });
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
23
bin/www
23
bin/www
@@ -4,11 +4,13 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require("../app");
|
||||
var {app} = require("../src/app");
|
||||
var http = require("http");
|
||||
var https = require("https");
|
||||
var fs = require("fs");
|
||||
var debug = require("debug")("https");
|
||||
const path = require("path");
|
||||
var livereload = require("livereload");
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
@@ -16,16 +18,17 @@ var debug = require("debug")("https");
|
||||
|
||||
var port = normalizePort(process.env.PORT || "3000");
|
||||
app.set("port", port);
|
||||
var server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Create HTTPS server.
|
||||
*/
|
||||
const https_options = {
|
||||
key: fs.readFileSync("certs/key.pem"),
|
||||
cert: fs.readFileSync("certs/cert.pem"),
|
||||
};
|
||||
|
||||
var server = https.createServer(https_options, app);
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const liveReloadServer = livereload.createServer({port:35729});
|
||||
liveReloadServer.watch(path.join(__dirname, "../src/views"));
|
||||
liveReloadServer.server.once("connection", () => {
|
||||
setTimeout(() => {
|
||||
liveReloadServer.refresh("/");
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
|
||||
26
caddy/Caddyfile
Normal file
26
caddy/Caddyfile
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
{$LOG_LEVEL} # Set via environment variable
|
||||
}
|
||||
|
||||
localhost {
|
||||
# Development configuration
|
||||
@notProd {
|
||||
expression {env.ENVIRONMENT} == 'development'
|
||||
}
|
||||
handle @notProd {
|
||||
# Configuration that only applies when not in production
|
||||
reverse_proxy app-dev:3000
|
||||
}
|
||||
}
|
||||
|
||||
{$DOMAIN} {
|
||||
# Production configuration
|
||||
@prod {
|
||||
expression {env.ENVIRONMENT} == 'production'
|
||||
}
|
||||
handle @prod {
|
||||
# Configuration that only applies in production
|
||||
# header Strict-Transport-Security "max-age=31536000;"
|
||||
reverse_proxy app:3000
|
||||
}
|
||||
}
|
||||
50
docker-compose.yml
Normal file
50
docker-compose.yml
Normal file
@@ -0,0 +1,50 @@
|
||||
services:
|
||||
app: &app
|
||||
env_file:
|
||||
- .env
|
||||
build: .
|
||||
networks:
|
||||
- web
|
||||
profiles:
|
||||
- production
|
||||
expose:
|
||||
- 3000
|
||||
|
||||
app-dev:
|
||||
<<: *app
|
||||
ports:
|
||||
- 9229:9229 #debugger
|
||||
- 35729:35729 #livereload
|
||||
profiles:
|
||||
- development
|
||||
command: npm run dev
|
||||
volumes:
|
||||
- ./src:/home/node/app/src
|
||||
- ./package.json:/home/node/app/package.json
|
||||
- ./package-lock.json:/home/node/app/package-lock.json
|
||||
- ./certs:/home/node/app/certs
|
||||
- ./bin/www:/home/node/app/bin/www
|
||||
environment:
|
||||
DEBUG: "app"
|
||||
NODE_ENV: "development"
|
||||
|
||||
caddy:
|
||||
image: caddy
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
volumes:
|
||||
- ./caddy/Caddyfile:/etc/caddy/Caddyfile
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
networks:
|
||||
- web
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
networks:
|
||||
web:
|
||||
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
2226
package-lock.json
generated
2226
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@@ -23,28 +23,53 @@
|
||||
"url": "https://github.com/sponsors/anthonyscorrea"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./bin/www"
|
||||
"start": "node ./bin/www",
|
||||
"dev": "nodemon --inspect=0.0.0.0 ./bin/www",
|
||||
"build-css": "sass src/scss:src/public/css",
|
||||
"watch-scss": "nodemon -e scss -x \"npm run build-css\""
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ext": "js,hbs,scss",
|
||||
"watch": ["src"]
|
||||
},
|
||||
"dependencies": {
|
||||
"@teamsnap/teamsnap-ui": "^3.12.3",
|
||||
"better-sqlite3": "^9.6.0",
|
||||
"better-sqlite3-session-store": "^0.1.0",
|
||||
"bootstrap": "^5.3.1",
|
||||
"bootstrap-icons": "^1.10.5",
|
||||
"connect-ensure-login": "^0.1.1",
|
||||
"cookie-parser": "~1.4.4",
|
||||
"csurf": "^1.11.0",
|
||||
"cors": "^2.8.5",
|
||||
"cors-anywhere": "^0.4.4",
|
||||
"csrf-csrf": "^3.0.3",
|
||||
"debug": "~2.6.9",
|
||||
"dotenv": "^8.6.0",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.2",
|
||||
"handlebars-dateformat": "^1.1.3",
|
||||
"hbs": "^4.2.0",
|
||||
"http-errors": "~1.6.3",
|
||||
"mkdirp": "^1.0.4",
|
||||
"morgan": "~1.9.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"papaparse": "^5.4.1",
|
||||
"passport": "^0.6.0",
|
||||
"passport-teamsnap": "^1.1.1",
|
||||
"pluralize": "^8.0.0",
|
||||
"pug": "^3.0.2",
|
||||
"teamsnap.js": "^1.62.1",
|
||||
"sass": "^1.77.2",
|
||||
"sortablejs": "^1.15.0",
|
||||
"teamsnap.js": "github:anthonyscorrea/teamsnap-javascript-sdk#link-with-null-link",
|
||||
"tinymce": "^6.8.3",
|
||||
"underscore": "^1.13.6",
|
||||
"xhr2": "^0.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"connect-livereload": "^0.6.1",
|
||||
"install": "^0.13.0",
|
||||
"livereload": "^0.9.3",
|
||||
"nodemon": "^3.0.3",
|
||||
"pug2hbs": "^1.0.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,661 +0,0 @@
|
||||
@import url("../css/paper.css");
|
||||
@import url("../fonts/vera/bitstreamvera.css");
|
||||
@import url("../fonts/verdana/verdanapro.css");
|
||||
@import url("../fonts/m+1m/m+1m.css");
|
||||
@import url("../fonts/helvetica-now/stylesheet.css");
|
||||
@import url("../fonts/futura-now/stylesheet.css");
|
||||
@import url("../fonts/inconsolata/stylesheet.css");
|
||||
|
||||
:root {
|
||||
--color-success: #b7e1cd;
|
||||
--color-danger: #f4c7c3;
|
||||
--color-neutral: #acc9fe;
|
||||
--color-warning: rgb(249, 228, 180);
|
||||
--color-grey-100: #f8f9fa;
|
||||
--color-grey-200: #e9ecef;
|
||||
--color-grey-300: #dee2e6;
|
||||
--color-grey-400: #ced4da;
|
||||
--color-grey-500: #adb5bd;
|
||||
--color-grey-600: #6c757d;
|
||||
--color-grey-700: #495057;
|
||||
--color-grey-800: #343a40;
|
||||
--color-grey-900: #212529;
|
||||
--row-height: 14px;
|
||||
--monospace-font: "Inconsolata";
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Helvetica Now";
|
||||
position: relative;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
table {
|
||||
position: inherit;
|
||||
font-size: inherit;
|
||||
border-collapse: collapse;
|
||||
empty-cells: show;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
border: 0.5px solid black;
|
||||
display: inline-table;
|
||||
}
|
||||
|
||||
.bar-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.bar-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
/* vertical-align: middle; */
|
||||
/* line-height: 1.3em; */
|
||||
overflow: hidden;
|
||||
padding: 0 2px 0 2px; /* top right bottom left */
|
||||
}
|
||||
|
||||
tr {
|
||||
border-bottom-width: 0.5px;
|
||||
border-color: grey;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
tr:last-child {
|
||||
border-bottom-color: black;
|
||||
}
|
||||
|
||||
tr:first-child {
|
||||
border-top-color: black;
|
||||
}
|
||||
|
||||
tr:nth-child(odd) {
|
||||
background-color: rgb(242, 242, 242, 0.85);
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: rgb(256, 256, 256, 0.85);
|
||||
}
|
||||
|
||||
td:not(:first-child) {
|
||||
border-left: 0.5px solid grey;
|
||||
}
|
||||
|
||||
th {
|
||||
font-stretch: extra-condensed;
|
||||
width: 1em;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
td:empty::after,
|
||||
th:empty::after {
|
||||
content: "\00a0";
|
||||
}
|
||||
|
||||
td.player-name {
|
||||
text-transform: uppercase;
|
||||
font-stretch: 80%;
|
||||
/* font-family: var(--monospace-font); */
|
||||
}
|
||||
|
||||
td.position,
|
||||
td.jersey-number {
|
||||
font-family: var(--monospace-font);
|
||||
width: 2ch;
|
||||
text-align: right;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.index-card .gamecard {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.B5 > .gamecard {
|
||||
/* height: auto; */
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
/* padding: 0.15in; */
|
||||
/* grid-gap: 0.25in 0.15in; */
|
||||
|
||||
/* background-image: url("../2023-G08-0523.png"); */
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.gamecard > section {
|
||||
/* margin: 0.07in; */
|
||||
box-sizing: content-box;
|
||||
/* border: 4px solid var(--color-grey-200); */
|
||||
/* border-radius: 4px; */
|
||||
overflow: hidden;
|
||||
outline: 0.5px dashed lightgrey;
|
||||
/* border-right: 1px dotted black; */
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.gamecard > section > div {
|
||||
margin: 0.15in;
|
||||
outline: 0.5px solid black;
|
||||
display: flex;
|
||||
flex: 1; /* consumes all free space (taking full height) */
|
||||
align-items: stretch;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#lineup-card-dugout div.grid-container,
|
||||
#lineup-card-dugout-empty div.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: 60% auto;
|
||||
grid-template-rows: fit-content(16px) auto;
|
||||
grid-template-areas:
|
||||
"header header"
|
||||
"sarting-lineup-table substitution-table";
|
||||
}
|
||||
|
||||
#lineup-card-exchange div.grid-container,
|
||||
#lineup-card-exchange-empty div.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
grid-template-rows: fit-content(16px) auto;
|
||||
grid-template-areas:
|
||||
"header"
|
||||
"sarting-lineup-table";
|
||||
}
|
||||
|
||||
.lineup-card div.grid-container > .section-header {
|
||||
grid-area: "header";
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.lineup-card div.grid-container > .section-header:empty::after {
|
||||
content: "\00a0";
|
||||
}
|
||||
|
||||
div.grid-container > .starting-lineup-table {
|
||||
grid-area: "starting-lineup-table";
|
||||
}
|
||||
|
||||
div.grid-container > .substitution-table {
|
||||
grid-area: "substitution-table";
|
||||
}
|
||||
|
||||
.lineup-card thead th {
|
||||
color: var(--color-grey-600);
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
.lineup-card th.sequence {
|
||||
color: var(--color-grey-600);
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.lineup-card table {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.lineup-card td {
|
||||
height: 33.5px;
|
||||
}
|
||||
|
||||
#lineup-card-exchange tr,
|
||||
#lineup-card-exchange-empty tr,
|
||||
#lineup-card-dugout .starting-lineup-table tr,
|
||||
#lineup-card-dugout .substitution-table tr:nth-child(odd) {
|
||||
border-top: 1px solid black;
|
||||
}
|
||||
|
||||
#lineup-card-exchange tr,
|
||||
#lineup-card-exchange-empty tr,
|
||||
#lineup-card-dugout .starting-lineup-table tr,
|
||||
#lineup-card-dugout .substitution-table tr:nth-child(even) {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
#lineup-card-exchange td.player-name {
|
||||
font-stretch: 100%;
|
||||
}
|
||||
|
||||
#lineup-card-dugout td.player-name {
|
||||
width: 10ch;
|
||||
}
|
||||
|
||||
#lineup-card-dugout td.substitution,
|
||||
#lineup-card-dugout-empty td.substitution {
|
||||
font-size: 11px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.lineup-card .position,
|
||||
.lineup-card .jersey-number {
|
||||
width: 2ch;
|
||||
}
|
||||
|
||||
#lineup-card-dugout .position,
|
||||
#lineup-card-dugout .jersey-number {
|
||||
font-stretch: 75%;
|
||||
padding-left: 2.5px;
|
||||
padding-right: 2.5px;
|
||||
}
|
||||
|
||||
.lineup-card .section-header {
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
font-size: inherit;
|
||||
text-transform: uppercase;
|
||||
font-stretch: 85%;
|
||||
}
|
||||
|
||||
#todays-game > div {
|
||||
display: grid;
|
||||
/* gap: 0.5px; */
|
||||
grid-template-columns: 110px auto;
|
||||
grid-template-rows: calc(var(--row-height) * 1) auto auto;
|
||||
grid-template-areas:
|
||||
"header header"
|
||||
"offense defense"
|
||||
"footer footer";
|
||||
}
|
||||
|
||||
#defense-pane {
|
||||
position: relative;
|
||||
/* box-sizing: border-box; */
|
||||
padding: 4px 4px 0px 4px; /* top right bottom left */
|
||||
display: flex;
|
||||
grid-area: defense;
|
||||
border-left: 0.5px solid black;
|
||||
border-bottom: 0.5px solid black;
|
||||
}
|
||||
|
||||
#offense-pane {
|
||||
position: relative;
|
||||
/* box-sizing: border-box; */
|
||||
height: 100%;
|
||||
grid-area: offense;
|
||||
border-bottom: 0.5px solid black;
|
||||
/* outline: 0.5px solid black; */
|
||||
}
|
||||
|
||||
#offense-pane table {
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#defense-pane .container {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
|
||||
#defense-pane .pitching-container {
|
||||
margin: auto 0 0 0; /* top right bottom left */
|
||||
}
|
||||
|
||||
#defense-pane .field-container {
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
/* margin: top; */
|
||||
width: 100%;
|
||||
/* background: url("../baseball-diamond.svg"); */
|
||||
background-size: 100%;
|
||||
background-position: center 10px;
|
||||
background-repeat: no-repeat;
|
||||
gap: 6px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#defense-pane img {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.defense-slot-set {
|
||||
width: 77px;
|
||||
}
|
||||
|
||||
.index-card .defense-slot-set {
|
||||
width: 65px;
|
||||
}
|
||||
|
||||
.index-card .defense-slot-set .player-name {
|
||||
font-stretch: 70%;
|
||||
}
|
||||
|
||||
.pitching-container .defense-slot-set {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.container .row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
background-color: #cadcf9;
|
||||
font-size: 8.8px;
|
||||
/* outline: 1px solid black; */
|
||||
/* height: var(--row-height); */
|
||||
width: auto;
|
||||
grid-area: header;
|
||||
text-align: center;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border-bottom: 0.5px solid black;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.footer {
|
||||
/* height:var(--row-height); */
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
grid-area: footer;
|
||||
/* border: 1px solid black; */
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.footer table {
|
||||
height: 100%;
|
||||
outline: none;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.footer tr {
|
||||
background-color: white;
|
||||
outline: none;
|
||||
border-bottom: 0.5px solid var(--color-grey-500);
|
||||
}
|
||||
|
||||
.footer tr :last-child {
|
||||
background-color: white;
|
||||
outline: none;
|
||||
border-bottom-style: none;
|
||||
}
|
||||
.footer th {
|
||||
text-align: left;
|
||||
color: var(--color-grey-600);
|
||||
}
|
||||
.footer td {
|
||||
height: var(--row-height);
|
||||
|
||||
border: none;
|
||||
}
|
||||
|
||||
.footer td:empty::after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.cell-checkbox {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.in-starting-lineup {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.gametitle {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
font-stretch: semi-condensed;
|
||||
}
|
||||
|
||||
.homeaway {
|
||||
text-transform: uppercase;
|
||||
font-stretch: normal;
|
||||
font-weight: bolder;
|
||||
float: right;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.cell-smalltext {
|
||||
font-stretch: condensed;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.statscell {
|
||||
font-family: "m+1m";
|
||||
text-align: center;
|
||||
font-stretch: extra-condensed;
|
||||
font-size: 9px;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
{
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.condensedNameCell {
|
||||
width: 70px;
|
||||
text-transform: uppercase;
|
||||
font-stretch: condensed;
|
||||
}
|
||||
|
||||
.cell-square {
|
||||
height: var(--row-height);
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cell-square.narrow {
|
||||
width: 10px;
|
||||
}
|
||||
.cell-mono {
|
||||
font-family: "m+1m";
|
||||
}
|
||||
|
||||
.cell-condensed {
|
||||
font-stretch: condensed;
|
||||
}
|
||||
|
||||
.available-status-code-1 {
|
||||
color: rgb(0, 85, 0);
|
||||
background-color: #b7e1cd;
|
||||
}
|
||||
|
||||
.available-status-code-0 {
|
||||
color: rgb(170, 0, 0);
|
||||
background-color: #f4c7c3;
|
||||
}
|
||||
|
||||
.past.available-status-code-0,
|
||||
.past.available-status-code-null {
|
||||
color: var(--color-grey-600);
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.past.available-status-code-1 {
|
||||
color: inherit;
|
||||
background-color: var(--color-warning);
|
||||
}
|
||||
|
||||
.past.available-status-code-1.started {
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.available-status-code-2 {
|
||||
color: blue;
|
||||
background-color: #acc9fe;
|
||||
}
|
||||
|
||||
#roster-and-history .player-name,
|
||||
#roster-and-history .jersey-number {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#roster-and-history .player-name {
|
||||
font-stretch: normal;
|
||||
}
|
||||
|
||||
.starting {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#roster-and-history > div > table {
|
||||
/* font-size: 10.5px; */
|
||||
padding: 0;
|
||||
line-height: 1em;
|
||||
/* outline: 0.5px black; */
|
||||
}
|
||||
|
||||
#roster-and-history td,
|
||||
#roster-and-history th {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
padding: 0.2em 0.1em 0.2em 0.1em; /* top right bottom left */
|
||||
}
|
||||
|
||||
#roster-and-history td.player-name {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#roster-and-history th {
|
||||
background-color: #cadcf9;
|
||||
color: black;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#roster-and-history thead > tr,
|
||||
#roster-and-history tfoot > tr {
|
||||
border-bottom: solid black 1px;
|
||||
}
|
||||
|
||||
#roster-and-history tbody {
|
||||
border-bottom: solid black 1px;
|
||||
}
|
||||
|
||||
#roster-and-history td[id^="avail"][id$="today-plus-1"],
|
||||
#roster-and-history .pitcher,
|
||||
#roster-and-history .player-stats,
|
||||
#roster-and-history td[id^="avail"][id$="today-minus-1"] {
|
||||
border-left-width: 1px;
|
||||
border-left-style: solid;
|
||||
border-left-color: black;
|
||||
}
|
||||
|
||||
#roster-and-history td.jersey-number {
|
||||
border-left: 0.5px solid lightgrey;
|
||||
}
|
||||
|
||||
#roster-and-history td.today-minus-4 {
|
||||
border-right: 1px solid black;
|
||||
}
|
||||
|
||||
#roster-and-history tr.border-top {
|
||||
border-top: 1px solid black;
|
||||
}
|
||||
|
||||
#roster-and-history #today-availability {
|
||||
font-stretch: normal;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.player-stats {
|
||||
font-family: var(--monospace-font);
|
||||
font-size: 1em;
|
||||
font-stretch: 60%;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
#roster-and-history td.position-capability,
|
||||
th.position-capability {
|
||||
font-size: 8px;
|
||||
font-stretch: 50%;
|
||||
width: 5px;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#roster-and-history th.position-capability {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
td.position-capability:not(:empty) {
|
||||
color: var(--color-grey-700);
|
||||
background-color: var(--color-grey-200);
|
||||
}
|
||||
|
||||
td.is-present-checkbox {
|
||||
font-size: 0.5em;
|
||||
text-align: center;
|
||||
color: white;
|
||||
/* text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000,
|
||||
1px 1px 0 #000; */
|
||||
}
|
||||
|
||||
td.is-present-checkbox.available-status-code-0 > span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
td.is-present-checkbox.available-status-code-None > span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
td.availability {
|
||||
font-family: var(--monospace-font);
|
||||
font-stretch: condensed;
|
||||
text-align: center;
|
||||
max-width: 0.8em;
|
||||
min-width: 0.8em;
|
||||
}
|
||||
|
||||
.availability.future,
|
||||
.availability.past {
|
||||
font-family: var(--monospace-font);
|
||||
font-stretch: condensed;
|
||||
font-weight: normal;
|
||||
font-size: 0.8em;
|
||||
padding: 0.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#roster-test .player-name {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
grid-area: player-name;
|
||||
}
|
||||
|
||||
#roster-test .jersey-number {
|
||||
font-weight: bolder;
|
||||
font-stretch: extra-condensed;
|
||||
grid-area: jersey-number;
|
||||
}
|
||||
|
||||
#roster-test .player-stats {
|
||||
grid-area: player-stats;
|
||||
}
|
||||
|
||||
#roster-test .diamond {
|
||||
grid-area: diamond;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
height: 10px;
|
||||
/* height:10px; */
|
||||
}
|
||||
|
||||
.rotate {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
.delimiter,
|
||||
.decimal-point {
|
||||
font-family: Helvetica Now;
|
||||
font-stretch: expanded;
|
||||
color: var(--color-grey-500);
|
||||
}
|
||||
|
||||
th .decimal-point {
|
||||
color: rgb(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
th .delimiter {
|
||||
color: var(--color-grey-500);
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
event_id = "292333461";
|
||||
event_id_2 = "292333462";
|
||||
team_id = "6882652";
|
||||
|
||||
function format_stat(number) {
|
||||
const zeroPad = (num, places) => String(num).padStart(3, "0");
|
||||
return zeroPad(Math.round(number * 1000), 3);
|
||||
}
|
||||
|
||||
async function load_data_xxx() {
|
||||
const event_id = document.querySelector('input[name="event_id"]').value;
|
||||
const team_id = document.querySelector('input[name="team_id"]').value;
|
||||
update_card(team_id, event_id);
|
||||
}
|
||||
|
||||
async function update_card(team_id, event_id) {
|
||||
fetch(`/${team_id}/event/${event_id}/gamecard/data`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(function (items) {
|
||||
console.log(items);
|
||||
events = items.filter(function (item) {
|
||||
return item.type == "event";
|
||||
});
|
||||
event_index = events.findIndex(function (e) {
|
||||
return e.id == event_id;
|
||||
});
|
||||
event = events[event_index];
|
||||
document.title = event.formattedTitle;
|
||||
|
||||
document.querySelectorAll(".event-title").forEach(function (element) {
|
||||
element.innerText = event.formattedTitle;
|
||||
});
|
||||
|
||||
document.querySelectorAll(".event-label").forEach(function (element) {
|
||||
element.innerText = event.label;
|
||||
});
|
||||
|
||||
document
|
||||
.querySelectorAll(".event-location-name")
|
||||
.forEach(function (element) {
|
||||
element.innerText = event.locationName;
|
||||
});
|
||||
|
||||
document.querySelectorAll(".opponent").forEach(function (element) {
|
||||
element.innerText = event.opponentName;
|
||||
});
|
||||
|
||||
document.querySelectorAll(".homeaway").forEach(function (element) {
|
||||
element.innerText = event.gameType;
|
||||
});
|
||||
|
||||
document.querySelectorAll(".event-date").forEach(function (element) {
|
||||
element.innerText = new Date(event.startDate).toLocaleDateString(
|
||||
"en-us",
|
||||
{
|
||||
weekday: "short",
|
||||
day: "numeric",
|
||||
// year: "numeric",
|
||||
month: "short",
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
document.querySelectorAll(".event-time").forEach(function (element) {
|
||||
element.innerText = new Date(event.startDate).toLocaleTimeString(
|
||||
"en-us",
|
||||
{
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
document.getElementById("todays-game-header").innerText =
|
||||
event.formattedTitle +
|
||||
" - " +
|
||||
new Date(event.startDate).toLocaleDateString("en-us", {
|
||||
weekday: "short",
|
||||
day: "numeric",
|
||||
// year: "numeric",
|
||||
month: "short",
|
||||
}) +
|
||||
" " +
|
||||
new Date(event.startDate).toLocaleTimeString("en-us", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
});
|
||||
|
||||
for (let j = -4; j < 5; j++) {
|
||||
if (j < 0) {
|
||||
plus_minus = "minus";
|
||||
} else if (j > 0) {
|
||||
plus_minus = "plus";
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
document.querySelector(
|
||||
`th.today-${plus_minus}-${Math.abs(j)} div`
|
||||
).textContent = new Date(
|
||||
events[event_index + j].startDate
|
||||
).toLocaleDateString("en-us", {
|
||||
weekday: "short",
|
||||
});
|
||||
}
|
||||
console.log({
|
||||
0: events[event_index],
|
||||
1: events[event_index + 1],
|
||||
});
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 198 371" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-miterlimit:1.5;">
|
||||
<g transform="matrix(1.04762,0,0,1.04802,-58.5948,32.9153)">
|
||||
<path d="M90,150L60,119.396C60,119.396 77.426,47.232 150,47.383C220.563,47.53 240.863,120.923 240.863,120.923L210,150" style="fill:none;stroke:rgb(13,202,242);stroke-opacity:0.42;stroke-width:4.17px;"/>
|
||||
<g transform="matrix(1.97279,0,0,1.79916,-103.351,-30.936)">
|
||||
<path d="M128.423,67.218L158.837,100.567L128.423,133.916L98.009,100.567L128.423,67.218Z" style="fill:rgb(13,202,242);fill-opacity:0.1;stroke:rgb(13,202,242);stroke-opacity:0.42;stroke-width:2.21px;"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 17 KiB |
390
routes/index.js
390
routes/index.js
@@ -1,390 +0,0 @@
|
||||
var express = require("express");
|
||||
var ensureLogIn = require("connect-ensure-login").ensureLoggedIn;
|
||||
var papaparse = require("papaparse");
|
||||
|
||||
var ensureLoggedIn = ensureLogIn();
|
||||
|
||||
var router = express.Router();
|
||||
|
||||
function authTeamsnap(user) {
|
||||
if (!teamsnap.isAuthed()) {
|
||||
teamsnap.init(process.env["TEAMSNAP_CLIENT_ID"]);
|
||||
teamsnap.auth(user.accessToken);
|
||||
}
|
||||
}
|
||||
|
||||
function availabilitiesSort(a, b) {
|
||||
status_code_sort = [
|
||||
teamsnap.AVAILABILITIES.YES,
|
||||
teamsnap.AVAILABILITIES.MAYBE,
|
||||
teamsnap.AVAILABILITIES.NO,
|
||||
teamsnap.AVAILABILITIES.NONE,
|
||||
];
|
||||
a_sort = status_code_sort.indexOf(a.statusCode);
|
||||
b_sort = status_code_sort.indexOf(b.statusCode);
|
||||
if (a_sort > b_sort) {
|
||||
return 1;
|
||||
}
|
||||
if (a_sort < b_sort) {
|
||||
return -1;
|
||||
}
|
||||
if (a_sort == b_sort) {
|
||||
if (a.member.lastName < b.member.lastName) {
|
||||
return -1;
|
||||
}
|
||||
if (a.member.lastName > b.member.lastName) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetch_stats(resolve, reject) {
|
||||
url =
|
||||
"https://docs.google.com/spreadsheets/d/{sheet_id}/export?format=csv&gid={tab_id}";
|
||||
papaparse.Papa.parse(url, {
|
||||
download: true,
|
||||
complete: function (results) {
|
||||
results.data.forEach((row, i) => {
|
||||
if (i == 0 || row[2] == "Totals" || row[2] == "") {
|
||||
return;
|
||||
}
|
||||
d = {
|
||||
first_name: row[3],
|
||||
last_name: row[2],
|
||||
jersey_number: row[1],
|
||||
pa: row[5],
|
||||
ab: row[6],
|
||||
avg: row[20],
|
||||
obp: row[21],
|
||||
slg: row[22],
|
||||
};
|
||||
});
|
||||
resolve(d);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/* GET home page. */
|
||||
router.get("/", ensureLoggedIn, function (req, res, next) {
|
||||
if (req.user) {
|
||||
authTeamsnap(req.user);
|
||||
teamsnap.loadCollections(function (err) {
|
||||
if (err) {
|
||||
alert("Error loading TeamSnap SDK");
|
||||
return;
|
||||
}
|
||||
teamsnap.loadTeams(function onTeamsLoad(err, teams) {
|
||||
teams = teams.sort((a, b) => b.seasonName - a.seasonName);
|
||||
res.render("home", { req: req, teams: teams });
|
||||
});
|
||||
});
|
||||
} else {
|
||||
res.render("home", { req: req });
|
||||
}
|
||||
});
|
||||
|
||||
router.get(
|
||||
"/teams",
|
||||
ensureLoggedIn,
|
||||
function (req, res, next) {
|
||||
console.log("teamsnap authed?: ", teamsnap.isAuthed());
|
||||
console.log("user is", req.user);
|
||||
|
||||
authTeamsnap(req.user);
|
||||
teamsnap.loadCollections(function (err) {
|
||||
if (err) {
|
||||
alert("Error loading TeamSnap SDK");
|
||||
return;
|
||||
}
|
||||
teamsnap.loadTeams(function onTeamsLoad(err, teams) {
|
||||
teams = teams.sort((a, b) => b.seasonName - a.seasonName);
|
||||
res.render("teams", { teams: teams });
|
||||
});
|
||||
});
|
||||
|
||||
next();
|
||||
},
|
||||
function (req, res, next) {
|
||||
// res.send(`${me.firstName} ${me.lastName}`);
|
||||
}
|
||||
);
|
||||
|
||||
router.get("/:team_id([0-9]+)", ensureLoggedIn, function (req, res, next) {
|
||||
authTeamsnap(req.user);
|
||||
team_id = req.params.team_id;
|
||||
console.log("team_id", team_id);
|
||||
teamsnap.loadCollections(function (err) {
|
||||
if (err) {
|
||||
alert("Error loading TeamSnap SDK");
|
||||
return;
|
||||
}
|
||||
teamsnap.enablePersistence();
|
||||
|
||||
teamsnap.bulkLoad(
|
||||
team_id,
|
||||
["team", "member", "event", "opponent", "availability_summary"],
|
||||
function onBulkLoad(err, items) {
|
||||
team = items.find((i) => (i.type == "team") & (i.id == team_id));
|
||||
console.log(team);
|
||||
res.set("Content-Type", "text/html");
|
||||
res.render("team", { team: team });
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
router.get(
|
||||
"/:team_id/event/:event_id",
|
||||
ensureLoggedIn,
|
||||
function (req, res, next) {
|
||||
authTeamsnap(req.user);
|
||||
var team_id = req.params.team_id;
|
||||
var event_id = req.params.event_id;
|
||||
teamsnap.loadCollections(function (err) {
|
||||
console.log();
|
||||
teamsnap.enablePersistence();
|
||||
|
||||
teamsnap.bulkLoad(
|
||||
team_id,
|
||||
["team", "event", "availabilitySummary"],
|
||||
function (err, items) {
|
||||
if (err) {
|
||||
res.code = 500;
|
||||
res.send(err);
|
||||
}
|
||||
|
||||
availabilitySummaries = items.filter(
|
||||
(i) => i.type == "availabilitySummary" && i.id == event_id
|
||||
);
|
||||
events = items.filter((i) => i.type == "event" && i.id == event_id);
|
||||
|
||||
if (events) {
|
||||
event = events[0];
|
||||
availabilitySummary = availabilitySummaries[0];
|
||||
console.log("A_S", availabilitySummaries);
|
||||
res.render("event", {
|
||||
event: event,
|
||||
team_id: team_id,
|
||||
team: items.find((i) => i.type == "team" && i.id == team_id),
|
||||
availabilitySummary: availabilitySummary,
|
||||
});
|
||||
} else {
|
||||
res.code = 500;
|
||||
res.send("error");
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:team_id/event/:event_id/gamecard",
|
||||
ensureLoggedIn,
|
||||
function (req, res, next) {
|
||||
authTeamsnap(req.user);
|
||||
team_id = req.params.team_id;
|
||||
event_id = req.params.event_id;
|
||||
teamsnap.loadCollections((err) => {
|
||||
teamsnap.enablePersistence();
|
||||
var events;
|
||||
teamsnap
|
||||
.bulkLoad(team_id, [
|
||||
"team",
|
||||
"member",
|
||||
// "member_photos",
|
||||
"event",
|
||||
"opponent",
|
||||
"availability_summary",
|
||||
])
|
||||
.then((items) => {
|
||||
events = items
|
||||
.filter((i) => i.type == "event")
|
||||
.sort((a, b) => a.startDate - b.startDate);
|
||||
event = events.find((i) => i.id == event_id);
|
||||
events_past = events.slice(
|
||||
events.findIndex((e) => e == event) - 4,
|
||||
events.findIndex((e) => e == event)
|
||||
);
|
||||
events_future = events.slice(
|
||||
events.findIndex((e) => e == event) + 1,
|
||||
events.findIndex((e) => e == event) + 5
|
||||
);
|
||||
events = events_past.concat(event).concat(events_future);
|
||||
})
|
||||
.then((items) => {
|
||||
return teamsnap.loadAvailabilities({
|
||||
eventId: events.map((e) => e.id),
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return teamsnap.collections["eventLineups"]
|
||||
.queryItems("search", {
|
||||
eventId: events.map((e) => e.id),
|
||||
})
|
||||
.then((event_lineups) => {
|
||||
return Promise.all(
|
||||
event_lineups.map((elu) => elu.loadItem("eventLineupEntries"))
|
||||
);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
items = teamsnap.getAllItems();
|
||||
events = items.filter((i) => i.type == "event");
|
||||
current_event_index = events.findIndex((e) => e.id == event_id);
|
||||
|
||||
context = {
|
||||
team_id: req.params.team_id,
|
||||
event_id: req.params.event_id,
|
||||
current_event_index: current_event_index,
|
||||
events: items.filter((a) => a.type == "event"),
|
||||
availabilitySummaries: items.filter(
|
||||
(i) => i.type == "availabilitySummary"
|
||||
),
|
||||
event: items.find((e) => e.type == "event" && e.id == event_id),
|
||||
events_past: events_past,
|
||||
events_future: events_future,
|
||||
members: items.filter((a) => a.type == "member"),
|
||||
availabilities: items
|
||||
.filter((i) => i.type == "availability")
|
||||
.sort(availabilitiesSort),
|
||||
all_lineup_entries: items.filter(
|
||||
(i) => i.type == "eventLineupEntry"
|
||||
),
|
||||
event_lineup_entries_offense: items
|
||||
.filter(
|
||||
(i) =>
|
||||
i.type == "eventLineupEntry" &&
|
||||
i.eventId == event_id &&
|
||||
!i.label.includes("[PO]")
|
||||
)
|
||||
.sort((a, b) => a.sequence - b.sequence),
|
||||
event_lineup_entries: items
|
||||
.filter(
|
||||
(i) => i.type == "eventLineupEntry" && i.eventId == event_id
|
||||
)
|
||||
.sort((a, b) => a.sequence - b.sequence),
|
||||
};
|
||||
|
||||
res.render("gamecard", context);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:team_id/event/:event_id/lineup",
|
||||
ensureLoggedIn,
|
||||
function (req, res, next) {
|
||||
authTeamsnap(req.user);
|
||||
team_id = req.params.team_id;
|
||||
event_id = req.params.event_id;
|
||||
teamsnap.loadCollections((err) => {
|
||||
teamsnap.enablePersistence();
|
||||
var events;
|
||||
teamsnap
|
||||
.bulkLoad(team_id, [
|
||||
"team",
|
||||
"member",
|
||||
// "member_photos",
|
||||
"event",
|
||||
"opponent",
|
||||
"availability_summary",
|
||||
])
|
||||
.then((items) => {
|
||||
events = items
|
||||
.filter((i) => i.type == "event")
|
||||
.sort((a, b) => a.startDate - b.startDate);
|
||||
event = events.find((i) => i.id == event_id);
|
||||
events_past = events.slice(
|
||||
events.findIndex((e) => e == event) - 4,
|
||||
events.findIndex((e) => e == event)
|
||||
);
|
||||
events_future = events.slice(
|
||||
events.findIndex((e) => e == event) + 1,
|
||||
events.findIndex((e) => e == event) + 5
|
||||
);
|
||||
events = events_past.concat(event).concat(events_future);
|
||||
})
|
||||
.then((items) => {
|
||||
return teamsnap.loadAvailabilities({
|
||||
eventId: events.map((e) => e.id),
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return teamsnap.collections["eventLineups"]
|
||||
.queryItems("search", {
|
||||
eventId: events.map((e) => e.id),
|
||||
})
|
||||
.then((event_lineups) => {
|
||||
return Promise.all(
|
||||
event_lineups.map((elu) => elu.loadItem("eventLineupEntries"))
|
||||
);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
items = teamsnap.getAllItems();
|
||||
events = items.filter((i) => i.type == "event");
|
||||
current_event_index = events.findIndex((e) => e.id == event_id);
|
||||
|
||||
context = {
|
||||
team_id: req.params.team_id,
|
||||
event_id: req.params.event_id,
|
||||
current_event_index: current_event_index,
|
||||
events: items.filter((a) => a.type == "event"),
|
||||
availabilitySummaries: items.filter(
|
||||
(i) => i.type == "availabilitySummary"
|
||||
),
|
||||
event: items.find((e) => e.type == "event" && e.id == event_id),
|
||||
events_past: events_past,
|
||||
events_future: events_future,
|
||||
members: items.filter((a) => a.type == "member"),
|
||||
availabilities: items
|
||||
.filter((i) => i.type == "availability")
|
||||
.sort(availabilitiesSort),
|
||||
all_lineup_entries: items.filter(
|
||||
(i) => i.type == "eventLineupEntry"
|
||||
),
|
||||
event_lineup_entries_offense: items
|
||||
.filter(
|
||||
(i) =>
|
||||
i.type == "eventLineupEntry" &&
|
||||
i.eventId == event_id &&
|
||||
!i.label.includes("[PO]")
|
||||
)
|
||||
.sort((a, b) => a.sequence - b.sequence),
|
||||
event_lineup_entries: items
|
||||
.filter(
|
||||
(i) => i.type == "eventLineupEntry" && i.eventId == event_id
|
||||
)
|
||||
.sort((a, b) => a.sequence - b.sequence),
|
||||
};
|
||||
|
||||
res.render("lineup", context);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
router.get("/:team_id/events", ensureLoggedIn, function (req, res, next) {
|
||||
authTeamsnap(req.user);
|
||||
team_id = req.params.team_id;
|
||||
event_id = req.params.event_id;
|
||||
teamsnap.loadCollections(function (err) {
|
||||
teamsnap
|
||||
.bulkLoad(team_id, ["team", "event", "availability_summary"])
|
||||
.then((items) => {
|
||||
res.set("Content-Type", "text/html");
|
||||
res.render("events", {
|
||||
team: items.find((i) => i.type == "team" && i.id == team_id),
|
||||
events: items.filter((i) => i.type == "event"),
|
||||
availabilitySummaries: items.filter(
|
||||
(i) => i.type == "availabilitySummary"
|
||||
),
|
||||
team_id: team_id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
183
src/app.js
Normal file
183
src/app.js
Normal file
@@ -0,0 +1,183 @@
|
||||
require("dotenv").config();
|
||||
|
||||
const createError = require("http-errors");
|
||||
const express = require("express");
|
||||
const path = require("path");
|
||||
const cookieParser = require("cookie-parser");
|
||||
const session = require("express-session");
|
||||
const SqliteStore = require("better-sqlite3-session-store")(session)
|
||||
var sqlite = require("better-sqlite3");
|
||||
const { generateToken } = require('./middlewares/csrf');
|
||||
const passport = require("passport");
|
||||
const logger = require("morgan");
|
||||
const bodyParser = require("body-parser");
|
||||
global.XMLHttpRequest = require("xhr2");
|
||||
// const teamsnap = require("./public/js/teamsnap");
|
||||
const teamsnap = require("teamsnap.js/lib/teamsnap")
|
||||
// import {teamsnap} from "teamsnap.js"
|
||||
const indexRouter = require("./routes/index").router;
|
||||
const authRouter = require("./routes/auth").router;
|
||||
var hbs = require('hbs');
|
||||
const { embeddedSvgFromPath } = require("./lib/utils");
|
||||
const cors = require('cors');
|
||||
const corsOptions = {
|
||||
origin: false
|
||||
}
|
||||
|
||||
|
||||
// teamsnap.init(process.env["TEAMSNAP_CLIENT_ID"])
|
||||
// console.log(teamsnap)
|
||||
// teamsnap.teamsnap.init
|
||||
var app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set("views", path.join(__dirname, "views"));
|
||||
hbs.registerPartials(require("./routes/index").partials)
|
||||
hbs.registerPartials(require("./controllers/event").partials)
|
||||
hbs.registerPartials(require("./controllers/eventlineup").partials)
|
||||
hbs.registerPartials(require("./controllers/eventsheet").partials)
|
||||
|
||||
hbs.registerHelper('dateFormat', require('handlebars-dateformat'));
|
||||
hbs.registerHelper('section', (name, options) => {
|
||||
if(!this._sections) this._sections = {};
|
||||
this._sections[name] = options.fn(this);
|
||||
return null;
|
||||
})
|
||||
hbs.registerHelper('script_tags', (scripts, options) => {
|
||||
if(!scripts) {
|
||||
return null;
|
||||
}
|
||||
var result = [];
|
||||
scripts.forEach((script)=>{
|
||||
result.push(`<script src="${script}"></script>`)
|
||||
})
|
||||
return result.join('\n');
|
||||
})
|
||||
hbs.registerHelper("embeddedSvgFromPath", require('./lib/utils').embeddedSvgFromPath)
|
||||
hbs.registerHelper(require("./controllers/event").helpers)
|
||||
hbs.registerHelper(require("./controllers/eventlineup").helpers)
|
||||
hbs.registerHelper(require("./helpers/eventsheet"))
|
||||
|
||||
app.set("view engine", "hbs");
|
||||
app.locals.pluralize = require("pluralize");
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log('adding connectLiveReload')
|
||||
var connectLiveReload = require("connect-livereload");
|
||||
app.use("/scss", express.static(path.join(__dirname, "scss")));
|
||||
app.use(connectLiveReload({port: 35729, src:"http://localhost:35729/livereload.js?snipver=1"}));
|
||||
}
|
||||
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({extended: true }));
|
||||
app.use(logger("dev"));
|
||||
app.use(cors(corsOptions))
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
app.use(
|
||||
"/css",
|
||||
express.static(path.join(__dirname, "../node_modules/bootstrap/dist/css"))
|
||||
);
|
||||
app.use(
|
||||
"/css",
|
||||
express.static(
|
||||
path.join(__dirname, "../node_modules/@teamsnap/teamsnap-ui/dist/css")
|
||||
)
|
||||
);
|
||||
app.use(
|
||||
"/teamsnap-ui/assets",
|
||||
express.static(
|
||||
path.join(__dirname, "../node_modules/@teamsnap/teamsnap-ui/src/assets")
|
||||
)
|
||||
);
|
||||
app.use(
|
||||
"/font",
|
||||
express.static(path.join(__dirname, "../node_modules/bootstrap-icons/font"))
|
||||
);
|
||||
app.use(
|
||||
"/bootstrap-icons",
|
||||
express.static(path.join(__dirname, "../node_modules/bootstrap-icons/icons"))
|
||||
);
|
||||
app.use(
|
||||
"/js",
|
||||
express.static(path.join(__dirname, "../node_modules/sortablejs"))
|
||||
);
|
||||
app.use(
|
||||
"/js",
|
||||
express.static(path.join(__dirname, "../node_modules/tinymce"))
|
||||
);
|
||||
app.use(
|
||||
"/js",
|
||||
express.static(path.join(__dirname, "../node_modules/teamsnap.js/lib/"))
|
||||
);
|
||||
const db = new sqlite("./var/db/sessions.db");
|
||||
app.use(
|
||||
session({
|
||||
store: new SqliteStore({
|
||||
client: db,
|
||||
expired: {
|
||||
clear: true,
|
||||
intervalMs: 900000 //ms = 15min
|
||||
}
|
||||
}),
|
||||
cookie: { maxAge: 86400000 }, // value of maxAge is defined in milliseconds.
|
||||
teamsnap_token: "",
|
||||
current_team: "",
|
||||
csrfToken:"",
|
||||
secret: process.env['SECRET'],
|
||||
resave: false, // don't save session if unmodified
|
||||
saveUninitialized: false, // don't create session until something stored
|
||||
})
|
||||
);
|
||||
app.use(function (req, res, next) {
|
||||
res.locals.csrfToken = generateToken;
|
||||
next();
|
||||
});
|
||||
app.use(passport.authenticate("session", { failureRedirect: '/login', failureMessage: true }));
|
||||
app.use(function (req, res, next) {
|
||||
var msgs = req.session.messages || [];
|
||||
res.locals.messages = msgs;
|
||||
res.locals.hasMessages = !!msgs.length;
|
||||
req.session.messages = [];
|
||||
next();
|
||||
});
|
||||
|
||||
app.use('/', require('./routes/meta').router);
|
||||
app.use("/", authRouter);
|
||||
app.use("/", indexRouter);
|
||||
app.use(require("./routes/team").router)
|
||||
app.use(require("./routes/opponent").router)
|
||||
app.use(require("./routes/event").router)
|
||||
app.use(require("./routes/eventlineup").router)
|
||||
app.use(require("./routes/eventsheet").router)
|
||||
// app.use("/", indexRouter.team_router);
|
||||
|
||||
// error handler
|
||||
app.use(function (err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
if (err) {
|
||||
res.locals.message = req.app.get("env") === "development" ? err.message : "An error has occurred";
|
||||
res.locals.error = req.app.get("env") === "development" ? err : {};
|
||||
if (typeof err === 'string' || err instanceof String) {
|
||||
err = {
|
||||
message: err
|
||||
}
|
||||
}
|
||||
console.log("error:", err);
|
||||
// render the error page
|
||||
res.status(err.status || 500).render("error", { title:"Error", layout: req.layout, message: err.message });
|
||||
}
|
||||
else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function (req, res, next) {
|
||||
// next(createError(404));
|
||||
res.status(404).send('not found')
|
||||
});
|
||||
|
||||
app.set('trust proxy')
|
||||
|
||||
module.exports = {app};
|
||||
123
src/controllers/event.js
Normal file
123
src/controllers/event.js
Normal file
@@ -0,0 +1,123 @@
|
||||
tsUtils = require("../lib/utils");
|
||||
const path = require('path');
|
||||
const { teamsnapFailure, tsPromise, teamsnapCallback } = require("../lib/utils");
|
||||
const {promisify} = require('util')
|
||||
|
||||
|
||||
exports.helpers = {
|
||||
availability_percentage: (availabilitySummary, status) => {
|
||||
attribute = {
|
||||
going: "playerGoingCount",
|
||||
notgoing: "playerNotGoingCount",
|
||||
maybe: "playerMaybeCount",
|
||||
unknown: "playerUnknownCount"
|
||||
}[status.toLowerCase()]
|
||||
return (availabilitySummary[attribute]/availabilitySummary.team.playerMemberCount)*100.0
|
||||
},
|
||||
isAway: (event) => {
|
||||
return event.gameType == "Away";
|
||||
}
|
||||
}
|
||||
|
||||
exports.partials = path.join(__dirname, "../views/event/partials")
|
||||
|
||||
exports.confirmModalAvailabilityReminders = async (req, res) => {
|
||||
res.status(200).render("event/partials/modal_availability_reminders")
|
||||
}
|
||||
|
||||
exports.getEvents = async (req, res, next) => {
|
||||
const {user, team, layout} = req
|
||||
const bulkLoadTypes = ["event", "availabilitySummary"]
|
||||
// const tsPromiseBulkload = promisify(teamsnap.bulkLoad)
|
||||
const promise = teamsnap.bulkLoad(
|
||||
{teamId: team.id, types: bulkLoadTypes},
|
||||
undefined,
|
||||
(err,items) => {teamsnapCallback(err, items, {req, source: 'getEvents', method: 'bulkLoad'})}
|
||||
)
|
||||
.then(items=>tsUtils.groupTeamsnapItems(items))
|
||||
.then(items=>{
|
||||
items.events.forEach((event) => {
|
||||
event.link('availabilitySummary', items.availabilitySummaries.find(a=>a.eventId==event.id))
|
||||
}
|
||||
)
|
||||
req.events = items.events;
|
||||
}
|
||||
)
|
||||
|
||||
req.promises.push(promise)
|
||||
all = await Promise.all(req.promises)
|
||||
|
||||
try {
|
||||
const context = {
|
||||
title: "Events",
|
||||
user, team, layout,
|
||||
events: req.events,
|
||||
};
|
||||
res.render("event/list", context);
|
||||
} catch(e) {
|
||||
next(e)
|
||||
}
|
||||
};
|
||||
|
||||
exports.getEvent = async (req, res, next) => {
|
||||
await Promise.all(req.promises)
|
||||
const {user, team, event, layout} = req
|
||||
lineups = await req.event.loadItem('eventLineups')
|
||||
event.link('availabilitySummary', req.availabilitySummary)
|
||||
context = {
|
||||
title: "Event",
|
||||
user, team, event, layout,
|
||||
availabilitySummary: req.availabilitySummary,
|
||||
};
|
||||
res.render("event/show", context);
|
||||
};
|
||||
|
||||
exports.sendAvailabilityReminders = async (req,res,next) => {
|
||||
await Promise.all(req.promises)
|
||||
if (!req.body || ! (req.body.event_id && req.body.memberIds)) {
|
||||
res.status(400).send('Malformed post')
|
||||
}
|
||||
if (req.params.event_id != req.body.eventId) {
|
||||
// Load actual event. Do I want this to be an error? probably
|
||||
res.status(400).send('Event ID parameter does not match the POST body')
|
||||
return;
|
||||
}
|
||||
|
||||
const {event} = req
|
||||
const {eventId, memberIds} = req.body
|
||||
const sendingMember = req.members.find(m=>m.userId==req.user.id)
|
||||
try {
|
||||
const promise = teamsnap.sendAvailabilityReminders(event, sendingMember, memberIds)
|
||||
await promise
|
||||
.then (res.status(200).send('OK'))
|
||||
} catch (err) {
|
||||
res.status(500).send()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
exports.submitResetAvailabilities = async (req,res,next) => {
|
||||
await Promise.all(req.promises)
|
||||
if (!req.body || ! (req.body.event_id && req.body.memberIds)) {
|
||||
res.status(400).send('Malformed post')
|
||||
}
|
||||
|
||||
if (req.params.event_id != req.body.event_id) {
|
||||
// Load actual event. Do I want this to be an error? probably
|
||||
res.status(400).send('Event ID parameter does not match the POST body');
|
||||
return
|
||||
}
|
||||
|
||||
const {event_id, memberIds} = req.body
|
||||
const reset_promises = []
|
||||
|
||||
const availabilities = await teamsnap.loadAvailabilities({eventId: event_id}, teamsnapCallback);
|
||||
|
||||
availabilities.filter(availability =>memberIds.includes(availability.memberId.toString())).forEach( availability => {
|
||||
availability.statusCode = teamsnap.AVAILABILITIES.NONE
|
||||
const promise = teamsnap.saveAvailability(availability, teamsnapCallback)
|
||||
reset_promises.push(promise)
|
||||
})
|
||||
await Promise.all(reset_promises)
|
||||
.then(res.status(200).send('OK'))
|
||||
}
|
||||
196
src/controllers/eventlineup.js
Normal file
196
src/controllers/eventlineup.js
Normal file
@@ -0,0 +1,196 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const {groupTeamsnapItems, parsePositionLabel, compilePositionLabel, teamsnapCallback} = require("../lib/utils")
|
||||
const tsUtils = require('../lib/utils')
|
||||
const { loadEventLineupEntries } = require('teamsnap.js')
|
||||
|
||||
exports.partials = path.join(__dirname, "../views/eventlineup/partials")
|
||||
exports.helpers = require('../helpers/eventlineup.js')
|
||||
|
||||
exports.getEventLineup = async (req, res)=>{
|
||||
await Promise.all(req.promises)
|
||||
const {user, team, members, event, layout, event_lineup, event_lineup_entries, availabilities, availabilitySummary, csrfToken} = req
|
||||
attachBenchcoachPropertiesToMember(members, event_lineup_entries, availabilities)
|
||||
members.sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName)
|
||||
const scripts = [
|
||||
"https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js",
|
||||
"/js/eventlineup.js",
|
||||
"/js/tinymce.min.js"
|
||||
]
|
||||
res.render("eventlineup/edit", {user, team, members, event, availabilities, scripts, layout, event_lineup, event_lineup_entries, availabilitySummary, csrfToken})
|
||||
}
|
||||
|
||||
exports.getAdjacentEventLineup = async (req, res) => {
|
||||
await Promise.all(req.promises)
|
||||
const index = Number(req.query.index)
|
||||
const {user, team, members, csrfToken} = req
|
||||
let event
|
||||
if (index > 0) {
|
||||
event = req.upcoming_events[index-1]
|
||||
}
|
||||
else if (index < 0){
|
||||
event = req.recent_events[Math.abs(index)-1]
|
||||
} else {
|
||||
throw new Error('Index must be positive or negative number')
|
||||
}
|
||||
if (!event) {
|
||||
res.status(500).send()
|
||||
return
|
||||
}
|
||||
const availabilitySummary = event.availabilitySummary
|
||||
const event_lineup = req.timeline.event_lineups?.find(i=>i.eventId==event.id)
|
||||
const event_lineup_entries = req.timeline.event_lineup_entries?.filter(i=>i.eventId==event.id)
|
||||
const availabilities = req.timeline.availabilities.filter(i=>i.eventId==event.id)
|
||||
attachBenchcoachPropertiesToMember(members, event_lineup_entries, availabilities)
|
||||
members.sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName)
|
||||
console.log()
|
||||
|
||||
res.render("eventlineup/edit", {user, team, members, event, layout: null, event_lineup, event_lineup_entries, availabilitySummary, availabilities, csrfToken})
|
||||
}
|
||||
|
||||
attachBenchcoachPropertiesToMember = (members, event_lineup_entries, availabilities) => {
|
||||
members.forEach((member)=> {
|
||||
// I *think* this can be done with linking https://github.com/teamsnap/teamsnap-javascript-sdk/wiki/Persistence#linking
|
||||
// here's an example:
|
||||
// member.link('eventLineupEntry', event_lineup_entries.find(i=>i.id=members[1].id))
|
||||
member.benchcoach = {}
|
||||
// I don't really like this, but the member_id changes once a season is archived.
|
||||
// as far as I can tell, member_name should consistently be formulated from first and last name
|
||||
// perhaps could have some edge cases if first or last names change, but this *should be* exceedingly rare.
|
||||
const member_name = `${member.firstName} ${member.lastName}`
|
||||
const event_lineup_entry = event_lineup_entries?.find(e=> e.memberId == member.id || e.memberName == member_name)
|
||||
const availability = availabilities.find(e=>e.memberId == member.id)
|
||||
member.benchcoach.availability = availability
|
||||
if (event_lineup_entry != null) {
|
||||
// member.link('eventLineupEntry', event_lineup_entry)
|
||||
member.benchcoach.eventLineupEntry = event_lineup_entry
|
||||
const {positionLabelWithoutFlag, positionFlags} = parsePositionLabel(event_lineup_entry.label);
|
||||
member.benchcoach.eventLineupEntry.positionLabelWithoutFlag = positionLabelWithoutFlag
|
||||
member.benchcoach.eventLineupEntry.flags = positionFlags
|
||||
}
|
||||
else {
|
||||
member.benchcoach.eventLineupEntry = null
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
exports.attachBenchcoachPropertiesToMember = attachBenchcoachPropertiesToMember
|
||||
|
||||
exports.getEventLineupEmail = async (req, res)=>{
|
||||
const {body} = req
|
||||
if (body.memberId == null) {res.status(400).end();return}
|
||||
await Promise.all(req.promises)
|
||||
const {user, team, members, event, layout, event_lineup, event_lineup_entries, availabilities, availabilitySummary} = req
|
||||
const eventLineupEntries = req.event_lineup.eventLineupEntries
|
||||
const {newEventLineupEntries} = processPostedEventLineupEntries(body, eventLineupEntries, event_lineup)
|
||||
attachBenchcoachPropertiesToMember(members, newEventLineupEntries, availabilities)
|
||||
members.sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName)
|
||||
res.status(200).render("eventlineup/partials/email_modal.hbs", {layout:null, user, team, members, event, event_lineup, event_lineup_entries: newEventLineupEntries, availabilities, availabilitySummary})
|
||||
}
|
||||
|
||||
exports.getAvailabilityRemindersModal = (req, res) => {
|
||||
res.status(200).render("eventlineup/partials/availability_reminder_modal.hbs")
|
||||
}
|
||||
|
||||
exports.getEventLineupEntries = async (req, res)=>{
|
||||
const {event_lineup, event_lineup_entries} = req
|
||||
res.setHeader('Content-Type', 'application/json').send(JSON.stringify(req.event_lineup_entries))
|
||||
}
|
||||
|
||||
exports.getEventLineupEntriesData = async (req, res)=>{
|
||||
const {event_lineup, event_lineup_entries} = req
|
||||
res.setHeader('Content-Type', 'application/json').send(JSON.stringify(req.event_lineup_entries))
|
||||
}
|
||||
|
||||
exports.postEventLineup = async (req,res) => {
|
||||
const {body} = req
|
||||
if (body.memberId == null) {res.status(400).end();return}
|
||||
await Promise.all(req.promises);
|
||||
const eventLineupEntries = req.event_lineup.eventLineupEntries
|
||||
const {newEventLineupEntries, deleteEventLineupEntries} = processPostedEventLineupEntries(body, eventLineupEntries, req.event_lineup)
|
||||
newEventLineupEntries.forEach(e=>{
|
||||
teamsnap.saveEventLineupEntry(e, teamsnapCallback)
|
||||
})
|
||||
deleteEventLineupEntries.forEach(e=>{
|
||||
teamsnap.deleteEventLineupEntry(e, teamsnapCallback)
|
||||
})
|
||||
|
||||
const bulk_items = await teamsnap.bulkLoad(
|
||||
{teamId: req.params.team_id, types: ['eventLineup', 'eventLineupEntry'], scopeTo:'event', event__id:req.params.event_id,},
|
||||
null,
|
||||
(err, items) => {teamsnapCallback(err, items, {req, source:"postEventLineup", method:'bulkLoad'})}
|
||||
)
|
||||
groupedReturnedItems = groupTeamsnapItems(bulk_items)
|
||||
returnedEventLineupEntries = groupedReturnedItems.eventLineupEntries
|
||||
res.status(201).end(JSON.stringify(returnedEventLineupEntries))
|
||||
}
|
||||
|
||||
const processPostedEventLineupEntries = (body, eventLineupEntries, eventLineup) => {
|
||||
const newEventLineupEntries = []
|
||||
const deleteEventLineupEntries = []
|
||||
|
||||
body.memberId.forEach((memberId, i)=>{
|
||||
const lineupEntryId = body.eventLineupEntryId[i]
|
||||
const lineupEntryLabel = body.label[i]
|
||||
const lineupEntrySequence = body.sequence[i]
|
||||
const lineupEntryFlags = body.flags[i]
|
||||
if (lineupEntryId != '' && lineupEntryLabel != '') {
|
||||
// Update lineup entry
|
||||
try {
|
||||
const eventLineupEntry = eventLineupEntries.find((e)=>e.id==Number(lineupEntryId))
|
||||
eventLineupEntry.sequence = lineupEntrySequence
|
||||
eventLineupEntry.label = compilePositionLabel(lineupEntryLabel, lineupEntryFlags)
|
||||
newEventLineupEntries.push(eventLineupEntry)
|
||||
} catch {
|
||||
console.log
|
||||
}
|
||||
}
|
||||
else if (lineupEntryId != '') {
|
||||
// Delete lineup entry
|
||||
const eventLineupEntry = eventLineupEntries.find((e)=>e.id==Number(lineupEntryId))
|
||||
deleteEventLineupEntries.push(eventLineupEntry)
|
||||
}
|
||||
else if (lineupEntryLabel != '') {
|
||||
// Create lineup entry
|
||||
const eventLineupEntry = teamsnap.createEventLineupEntry()
|
||||
eventLineupEntry.eventLineupId = eventLineup.id
|
||||
eventLineupEntry.memberId = memberId
|
||||
eventLineupEntry.sequence = lineupEntrySequence
|
||||
eventLineupEntry.label = compilePositionLabel(lineupEntryLabel, lineupEntryFlags)
|
||||
newEventLineupEntries.push(eventLineupEntry)
|
||||
}
|
||||
else {
|
||||
// Skip lineup entry
|
||||
}
|
||||
})
|
||||
return {newEventLineupEntries, eventLineupEntries, deleteEventLineupEntries}
|
||||
}
|
||||
|
||||
exports.submitDeleteEventLineupEntries = async (req,res) => {
|
||||
await Promise.all(req.promises);
|
||||
const {event_lineup, event_lineup_entries} = req
|
||||
let event_id
|
||||
let memberIds
|
||||
|
||||
if (!req.body || ! (req.body.event_id && req.body.memberIds)) {
|
||||
res.status(400).send('Malformed post')
|
||||
} else if (req.params.event_id != req.body.event_id) {
|
||||
// Load actual event. Do I want this to be an error? probably
|
||||
res.status(400).send('Event ID parameter does not match the POST body');
|
||||
return
|
||||
} else {
|
||||
event_id = req.body.event_id
|
||||
memberIds = req.body.memberIds
|
||||
}
|
||||
|
||||
const deletion_promises = []
|
||||
|
||||
event_lineup_entries.filter(entry =>memberIds.includes(entry.memberId.toString())).forEach( entry => {
|
||||
const promise = teamsnap.deleteEventLineupEntry(entry, teamsnapCallback)
|
||||
deletion_promises.push(promise)
|
||||
})
|
||||
|
||||
await Promise.all(deletion_promises)
|
||||
.then(res.status(202).send('OK'))
|
||||
|
||||
}
|
||||
100
src/controllers/eventsheet.js
Normal file
100
src/controllers/eventsheet.js
Normal file
@@ -0,0 +1,100 @@
|
||||
const tsUtils = require('../lib/utils')
|
||||
const path = require('path')
|
||||
|
||||
exports.partials = path.join(__dirname, "../views/eventsheet/partials")
|
||||
|
||||
exports.getEventSheet = async (req,res) =>{
|
||||
req.promises.push(
|
||||
teamsnap.loadOpponents(
|
||||
req.team.id,
|
||||
(err, opponents)=>{
|
||||
if (err) console.log("error in route/opponent.js", err);
|
||||
}
|
||||
).then(opponents => {req.opponent=opponents.find(o=>o.id==req.event.opponentId);})
|
||||
)
|
||||
await Promise.all(req.promises)
|
||||
req.promises.push(
|
||||
teamsnap.loadTeamMedia(req.team.id, (err, team_media)=>{
|
||||
if (err) console.log("error in route/opponent.js", err);
|
||||
})
|
||||
.then(team_media => {
|
||||
req.opponent_logo = team_media.find(tm=>tm.description==`opponent-logo-${req.event.opponentId}.png`)
|
||||
}
|
||||
)
|
||||
)
|
||||
await Promise.all(req.promises)
|
||||
const {sheet_size, sheet_layout} = req.query
|
||||
|
||||
const {user, team, team_preferences, members, event, event_lineup, event_lineup_entries, availabilities, availabilitySummary, timeline, recent_events, opponent_logo, upcoming_events} = req
|
||||
res.render('eventsheet/sheet', {sheet_size, sheet_layout, user, team, team_preferences, members, event, event_lineup, event_lineup_entries, availabilities, availabilitySummary, timeline, recent_events, opponent_logo,upcoming_events})
|
||||
}
|
||||
|
||||
exports.getEventSheetBlank = (req,res) => {
|
||||
res.render('eventsheet/sheet_blank')
|
||||
}
|
||||
|
||||
exports.getLineupCard = (req, res, next) => {
|
||||
team_id = req.params.team_id;
|
||||
event_id = req.params.event_id;
|
||||
teamsnap
|
||||
.bulkLoad(team_id, [
|
||||
"team",
|
||||
"member",
|
||||
// "member_photos",
|
||||
"event",
|
||||
"opponent",
|
||||
"availabilitySummary",
|
||||
])
|
||||
.then((items) => {
|
||||
events = items.filter((i) => i.type == "event").sort((a, b) => a.startDate - b.startDate);
|
||||
event = events.find((i) => i.id == event_id);
|
||||
events_past = events.slice(
|
||||
events.findIndex((e) => e == event) - 4,
|
||||
events.findIndex((e) => e == event)
|
||||
);
|
||||
events_future = events.slice(events.findIndex((e) => e == event) + 1, events.findIndex((e) => e == event) + 5);
|
||||
events = events_past.concat(event).concat(events_future);
|
||||
})
|
||||
.then((items) => {
|
||||
return teamsnap.loadAvailabilities({
|
||||
eventId: events.map((e) => e.id),
|
||||
}).catch(error => console.log("error in event.js"));
|
||||
})
|
||||
.then(() => {
|
||||
return teamsnap.collections["eventLineups"]
|
||||
.queryItems("search", {
|
||||
eventId: events.map((e) => e.id),
|
||||
})
|
||||
.then((event_lineups) => {
|
||||
return Promise.all(event_lineups.map((elu) => elu.loadItem("eventLineupEntries")));
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
items = teamsnap.getAllItems();
|
||||
events = items.filter((i) => i.type == "event");
|
||||
current_event_index = events.findIndex((e) => e.id == event_id);
|
||||
|
||||
context = {
|
||||
title: "Gamecard",
|
||||
team_id: req.params.team_id,
|
||||
event_id: req.params.event_id,
|
||||
current_event_index: current_event_index,
|
||||
events: items.filter((a) => a.type == "event"),
|
||||
availabilitySummaries: items.filter((i) => i.type == "availabilitySummary"),
|
||||
event: items.find((e) => e.type == "event" && e.id == event_id),
|
||||
events_past: events_past,
|
||||
events_future: events_future,
|
||||
members: items.filter((a) => a.type == "member"),
|
||||
availabilities: items.filter((i) => i.type == "availability").sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName),
|
||||
all_lineup_entries: items.filter((i) => i.type == "eventLineupEntry"),
|
||||
event_lineup_entries_offense: items
|
||||
.filter((i) => i.type == "eventLineupEntry" && i.eventId == event_id && !i.label.has("[PO]"))
|
||||
.sort((a, b) => a.sequence - b.sequence),
|
||||
event_lineup_entries: items
|
||||
.filter((i) => i.type == "eventLineupEntry" && i.eventId == event_id)
|
||||
.sort((a, b) => a.sequence - b.sequence),
|
||||
};
|
||||
|
||||
res.render("event-lineupcard", context);
|
||||
});
|
||||
};
|
||||
9
src/controllers/member.js
Normal file
9
src/controllers/member.js
Normal file
@@ -0,0 +1,9 @@
|
||||
exports.getMembers = (req, res, next) => {
|
||||
const {members, team} = req
|
||||
context = {
|
||||
title: `Roster`,
|
||||
team_id: team.id,
|
||||
team, members,
|
||||
};
|
||||
res.render("members", context);
|
||||
};
|
||||
109
src/controllers/opponent.js
Normal file
109
src/controllers/opponent.js
Normal file
@@ -0,0 +1,109 @@
|
||||
|
||||
|
||||
exports.getOpponents = async (req, res, next) => {
|
||||
const {user, team, layout, csrfToken} = req
|
||||
opponents = await teamsnap.loadOpponents(team.id, ["event", "availabilitySummary"], (err, opponents)=>{
|
||||
if (err) console.log('err in controllers/opponent.js')
|
||||
|
||||
else return opponents;
|
||||
})
|
||||
context = {
|
||||
title: "Opponents",
|
||||
user, team, layout, csrfToken,
|
||||
opponents: opponents,
|
||||
};
|
||||
res.render("opponent/list", context);
|
||||
};
|
||||
|
||||
exports.uploadOpponentLogoForm = (req, res, next) => {
|
||||
opponent_id = req.params.opponent_id;
|
||||
team_id = req.params.team_id;
|
||||
res.set("Content-Type", "text/html");
|
||||
res.render("upload-logo", {
|
||||
title: "Upload Logo",
|
||||
csrf_token: req.csrfToken(),
|
||||
team_id: team_id,
|
||||
opponent_id: opponent_id,
|
||||
});
|
||||
};
|
||||
|
||||
exports.uploadOpponentLogo = (req, res, next) => {
|
||||
opponent_id = req.body.opponent_id;
|
||||
team_id = req.body.team_id;
|
||||
member_id = req.user.id;
|
||||
file = new File(req.file.buffer, `team-logo-${opponent_id}.png`, {
|
||||
type: "image/png",
|
||||
});
|
||||
authTeamsnap(req.user);
|
||||
teamsnap
|
||||
.loadCollections()
|
||||
.then(() => {
|
||||
return teamsnap.createTeamMedium({
|
||||
file: file,
|
||||
mediaFormat: "file",
|
||||
memberId: member_id,
|
||||
teamId: team_id,
|
||||
teamMediaGroupId: "4927028",
|
||||
description: `team-logo-${opponent_id}.png`,
|
||||
});
|
||||
})
|
||||
.then((item) => {
|
||||
return teamsnap.uploadTeamMedium(item);
|
||||
})
|
||||
.then((item) => {
|
||||
res.send("Data Received: " + JSON.stringify(item));
|
||||
})
|
||||
.fail((err) => console.log(err));
|
||||
};
|
||||
|
||||
exports.getOpponent = async (req, res) => {
|
||||
await Promise.all(req.promises)
|
||||
const {team, team_media_group, opponent, layout, opponent_logo, user, csrfToken} = req
|
||||
context = {
|
||||
team, team_media_group, opponent, layout, opponent_logo, user, csrfToken
|
||||
// opponent_logo: items.find(
|
||||
// (i) => i.type == "teamMedium" && i.description == `opponent-logo-${opponent_id}.png`
|
||||
// ),
|
||||
};
|
||||
res.set("Content-Type", "text/html");
|
||||
res.render("opponent/show", context);
|
||||
};
|
||||
|
||||
exports.postOpponentLogo = async (req, res, next) => {
|
||||
res.status('501').send('Not Implemented')
|
||||
// await Promise.all(req.promises)
|
||||
// const {team, opponent, user, body} = req
|
||||
// const filename = `team-logo-${opponent.id}.png`
|
||||
// file = new File(req.file.buffer, filename, {
|
||||
// type: "image/png",
|
||||
// });
|
||||
|
||||
// const team_medium = await teamsnap.createTeamMedium(
|
||||
// {
|
||||
// file: file,
|
||||
// memberId: user.id,
|
||||
// teamId: team.id,
|
||||
// teamMediaGroupId: body.teamMediaGroupId,
|
||||
// description: filename,
|
||||
// }
|
||||
// )
|
||||
// await teamsnap.saveTeamMedium(team_medium)
|
||||
// // await teamsnap.uploadTeamMedium(team_medium)
|
||||
|
||||
// const headers={'Authorization': `Bearer ${user.accessToken}`}
|
||||
// // const url = teamsnap.collections.teamMedia.commands.uploadTeamMedium.href
|
||||
// const url = teamsnap.collections.teamMedia.queries.search.href
|
||||
// const response = await fetch(url+`?team_id=${team.id}`, {
|
||||
// headers,
|
||||
// method: 'get',
|
||||
// // body:{team_id:team.id}
|
||||
// // body: {
|
||||
// // file: file,
|
||||
// // member_id: user.id,
|
||||
// // team_id: team.id,
|
||||
// // team_media_group_id: body.teamMediaGroupId,
|
||||
// // description: filename,
|
||||
// // }
|
||||
// })
|
||||
// // await teamsnap
|
||||
}
|
||||
42
src/controllers/team.js
Normal file
42
src/controllers/team.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const { teamsnapCallback } = require("../lib/utils");
|
||||
|
||||
utils = require("../lib/utils");
|
||||
|
||||
exports.getTeams = async (req, res, next) => {
|
||||
const {layout, user} = req
|
||||
const {user_id} = req.params
|
||||
req.session.current_team_id = null
|
||||
promise = teamsnap.loadTeams({'userId':user_id},
|
||||
(err, items) =>{
|
||||
teamsnapCallback(err,items);
|
||||
req.teams = items;
|
||||
})
|
||||
.fail(
|
||||
next
|
||||
)
|
||||
req.promises.push(promise)
|
||||
await Promise.all(req.promises)
|
||||
try {
|
||||
const context = { layout, title: "Teams", user, teams: req.teams.filter(t=>!t.isRetired) };
|
||||
res.render("team/list", context);
|
||||
} catch (e){
|
||||
next(e);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
exports.getTeamHome = async (req, res, next) => {
|
||||
await Promise.all(req.promises)
|
||||
const {user, team, team_preferences, upcoming_events, recent_events, layout} = req
|
||||
|
||||
try {
|
||||
context = {
|
||||
title: "Home",
|
||||
layout, team, user, team_preferences, upcoming_events, recent_events
|
||||
};
|
||||
res.render("team/home", context);
|
||||
} catch (e) {
|
||||
next (e);
|
||||
}
|
||||
|
||||
};
|
||||
119
src/helpers/eventlineup.js
Normal file
119
src/helpers/eventlineup.js
Normal file
@@ -0,0 +1,119 @@
|
||||
const {embeddedSvgFromPath, parsePositionLabel, compilePositionLabel} = require("../lib/utils")
|
||||
var hb = require('hbs').create();
|
||||
|
||||
const statusCodeIcons = {
|
||||
1: embeddedSvgFromPath("/teamsnap-ui/assets/icons/check.svg"),
|
||||
0: embeddedSvgFromPath("/teamsnap-ui/assets/icons/dismiss.svg"),
|
||||
2: embeddedSvgFromPath("/bootstrap-icons/question-lg.svg"),
|
||||
null: embeddedSvgFromPath("/bootstrap-icons/question.svg"),
|
||||
undefined: embeddedSvgFromPath("/bootstrap-icons/question-lg.svg")
|
||||
}
|
||||
|
||||
const statusCodeClasses = {
|
||||
1: "u-colorPositive",
|
||||
0: "u-colorNegative",
|
||||
2: "u-colorPrimary",
|
||||
null: "u-colorGrey",
|
||||
undefined: "u-colorGrey"
|
||||
}
|
||||
|
||||
const statusCodeButtonClasses = {
|
||||
1: "Button--yes",
|
||||
0: "Button--no",
|
||||
2: "Button--maybe",
|
||||
null: "",
|
||||
undefined: ""
|
||||
}
|
||||
exports.flagsString = (flags) => {
|
||||
return flags != null ? Array.from(flags).join(",") : ''
|
||||
};
|
||||
exports.plus1 = (i) => Number(i)+1;
|
||||
exports.positions = () => ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "EH", "DH", "DR"];
|
||||
exports.defense_positions = () => ["C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "P"];
|
||||
exports.avail_status_code_class = (status_code) => statusCodeButtonClasses[status_code];
|
||||
exports.avail_status_code_icon = (status_code) => statusCodeIcons[status_code];
|
||||
exports.positionLabelWithoutFlags = (label) => {
|
||||
const {positionLabelWithoutFlags} = parsePositionLabel(label);
|
||||
return positionLabelWithoutFlags
|
||||
};
|
||||
exports.positionLabelWithoutPOFlag = (label) => {
|
||||
const {positionLabelWithoutFlags, positionFlags} = parsePositionLabel(label);
|
||||
positionFlags.delete('PO')
|
||||
return compilePositionLabel(positionLabelWithoutFlags, positionFlags)
|
||||
};
|
||||
exports.positionFlags = (label)=> {
|
||||
const {positionFlags} = parsePositionLabel(label);
|
||||
return `[${Array.from(positionFlags).join(",")}]`
|
||||
};
|
||||
exports.hasPositionFlags = (label) => {
|
||||
const {positionLabelWithoutFlags, positionFlags} = parsePositionLabel(label);
|
||||
return positionFlags.size > 0;
|
||||
};
|
||||
exports.comparePositionWithFlags = (labelWithoutFlags, eventLineupEntry, options) => {
|
||||
labelWithFlags = eventLineupEntry?.label
|
||||
const {positionLabelWithoutFlags} = parsePositionLabel(labelWithFlags);
|
||||
return positionLabelWithoutFlags == labelWithoutFlags;
|
||||
};
|
||||
exports.isStarting = (member) => {
|
||||
return (member.benchcoach?.eventLineupEntry != null);
|
||||
};
|
||||
exports.isInStartingLineup = (member) => {
|
||||
if (member.benchcoach.eventLineupEntry == null || member.benchcoach.eventLineupEntry.label == '') return false;
|
||||
const {positionFlags} = parsePositionLabel(member.benchcoach.eventLineupEntry?.label);
|
||||
return (!positionFlags.has("PO"))
|
||||
};
|
||||
exports.isInPositionOnly = (member) => {
|
||||
if (!member.benchcoach || member.benchcoach.eventLineupEntry == null) return false;
|
||||
const {positionFlags} = parsePositionLabel(member.benchcoach.eventLineupEntry?.label);
|
||||
return (member.benchcoach.eventLineupEntry != null && positionFlags.has("PO"))
|
||||
};
|
||||
exports.isInBench = (member) => {
|
||||
if ((member.benchcoach.eventLineupEntry != null && member.benchcoach.eventLineupEntry.label != '') || member.isNonPlayer) return false;
|
||||
return (member.benchcoach.availability?.statusCode != 0 && member.benchcoach.availability?.statusCode != null)
|
||||
};
|
||||
exports. isInOut = (member) => {
|
||||
if ((member.benchcoach.eventLineupEntry != null && member.benchcoach.eventLineupEntry.label != '') || member.isNonPlayer) return false;
|
||||
return (member.benchcoach.availability?.statusCode == 0 || member.benchcoach.availability?.statusCode == null)
|
||||
};
|
||||
exports.availabilityStatusShort = (availability) => {
|
||||
const {YES, MAYBE, NO, NONE} = teamsnap.AVAILABILITIES
|
||||
const statusShortLookup = {}
|
||||
statusShortLookup[YES] = "YES"
|
||||
statusShortLookup[MAYBE] = "MAY"
|
||||
statusShortLookup[NO] = "NO"
|
||||
statusShortLookup[NONE] = "UNK"
|
||||
statusShortLookup[undefined] = "UNK"
|
||||
return (statusShortLookup[availability?.statusCode])
|
||||
};
|
||||
exports.filterNonPlayers = (members) => {
|
||||
return members.filter(m=>!m.isNonPlayer)
|
||||
};
|
||||
exports.joinMemberEmailAddresses = (members) => {
|
||||
return members.map(m=>m.emailAddresses.join(',')).join(',')
|
||||
}
|
||||
exports.loadSlots = (options) =>{
|
||||
var s = ""
|
||||
const {members, event_lineup, event_lineup_entries, event, availabilities} = options.data.root
|
||||
event_lineup_entries.forEach(eventLineupEntry =>{
|
||||
const availability = availabilities?.find(a=>a.memberId==eventLineupEntry.memberId)
|
||||
const member = members.find(m=>m.id==eventLineupEntry.memberId)
|
||||
const {positionFlags} = parsePositionLabel(eventLineupEntry.label)
|
||||
const initial_lineup_segment = `${positionFlags.has('PO') ? 'position-only' : 'starting'}`
|
||||
|
||||
s+=options.fn({eventLineupEntry, availability, member, event, initial_lineup_segment})
|
||||
})
|
||||
const players_without_lineup_entry = members.filter(
|
||||
member=>!event_lineup_entries.map(lue=>lue.memberId).includes(member.id) && !member.isNonPlayer
|
||||
)
|
||||
players_without_lineup_entry.forEach(member =>{
|
||||
const availability = availabilities?.find(a=>a.memberId==member.id)
|
||||
let initial_lineup_segment
|
||||
if (availability?.statusCode == 0 || availability?.statusCode == null) {
|
||||
initial_lineup_segment =`out`
|
||||
} else {
|
||||
initial_lineup_segment =`bench`
|
||||
}
|
||||
s+=options.fn({availability, member, event, initial_lineup_segment})
|
||||
})
|
||||
return s
|
||||
}
|
||||
156
src/helpers/eventsheet.js
Normal file
156
src/helpers/eventsheet.js
Normal file
@@ -0,0 +1,156 @@
|
||||
const { parsePositionLabel, teamsnapMembersSortLineupAvailabilityLastName, teamsnapMembersSortAvailabilityLastName } = require('../lib/utils')
|
||||
const {attachBenchcoachPropertiesToMember} = require('../controllers/eventlineup')
|
||||
const Handlebars = require("handlebars");
|
||||
|
||||
exports.offenseLineup = (number_of_slots, event_lineup_entries, members, options) => {
|
||||
var results = ""
|
||||
// const {event_lineup_entries, members} = options.data.root
|
||||
|
||||
for (let i = 0; i < number_of_slots; i++){
|
||||
const event_lineup_entry = event_lineup_entries ? event_lineup_entries[i] : null
|
||||
if (event_lineup_entry && !parsePositionLabel(event_lineup_entry.label).positionFlags.has('PO')){
|
||||
results += options.fn({
|
||||
sequence: event_lineup_entry.sequence,
|
||||
member: members.find(member=> event_lineup_entry.memberId == member.id || event_lineup_entry.memberName == `${member.firstName} ${member.lastName}`),
|
||||
label: event_lineup_entry.label
|
||||
})
|
||||
}
|
||||
else {
|
||||
results += options.fn({
|
||||
sequence: i,
|
||||
member: {},
|
||||
label: ""
|
||||
})
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
exports.defenseLineup = (event_lineup_entries, members, options) => {
|
||||
var results = ""
|
||||
// const {event_lineup_entries, members} = options.data.root
|
||||
const positions = ["C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "P"]
|
||||
positions.forEach(position=>{
|
||||
const event_lineup_entry = event_lineup_entries ? event_lineup_entries.find(e=>parsePositionLabel(e.label).positionLabelWithoutFlags == position) : null
|
||||
if (event_lineup_entry) {
|
||||
results += options.fn({position, eventLineupEntry:event_lineup_entry, member:members.find(member=> event_lineup_entry.memberId == member.id || event_lineup_entry.memberName == `${member.firstName} ${member.lastName}`)})
|
||||
}
|
||||
else {
|
||||
results += options.fn({position, member:{}})
|
||||
}
|
||||
})
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
exports.rosterHistoryHeader = (options) => {
|
||||
var results = ""
|
||||
events = ["+1", "+2", "+3", "+4","-1","-2","-3","-4"]
|
||||
|
||||
events.forEach(event => {
|
||||
const class_name = event.includes("+") ? "plus": "minus"
|
||||
const past_or_future = event.includes("+") ? "future": "past"
|
||||
const index = Number(event.replace("+","").replace("-",""))
|
||||
results += options.fn({class:`today-${class_name}-${index} ${past_or_future}`, event})
|
||||
})
|
||||
return results;
|
||||
}
|
||||
|
||||
exports.rosterHistory = (event, event_lineup_entries, members, availabilities, options) => {
|
||||
var results = ""
|
||||
// const {event, event_lineup_entries, members, availabilities} = options.data.root
|
||||
const players = members.filter(m=>!m.isNonPlayer)
|
||||
attachBenchcoachPropertiesToMember(players, event_lineup_entries ? event_lineup_entries.filter(i=>i.eventId==event.id) : [], availabilities.filter(i=>i.eventId==event.id))
|
||||
players.sort(teamsnapMembersSortLineupAvailabilityLastName)
|
||||
|
||||
players.forEach(member=>{
|
||||
const {firstName, lastName, jerseyNumber, benchcoach, position, id} = member
|
||||
results += options.fn({
|
||||
id, firstName, lastName, jerseyNumber, position, benchcoach
|
||||
})
|
||||
}
|
||||
)
|
||||
return results;
|
||||
}
|
||||
|
||||
const positionGroups = {
|
||||
"P":"P",
|
||||
"IF":"IF",
|
||||
"1B":"IF",
|
||||
"2B":"IF",
|
||||
"3B":"IF",
|
||||
"SS":"IF",
|
||||
"OF":"OF",
|
||||
"LF":"OF",
|
||||
"CF":"OF",
|
||||
"RF":"OF",
|
||||
"C":"C"
|
||||
}
|
||||
|
||||
exports.positionCapabilityFor = (member, position, options) => {
|
||||
if (!member.position) {
|
||||
return ""
|
||||
}
|
||||
const member_positions = member.position.split(",").map(s=>s.trim())
|
||||
const member_position_groups = new Set(member.position.split(",").map(s=>positionGroups[s.trim()]))
|
||||
|
||||
if (member_position_groups.has(position)){
|
||||
return "\u2713"
|
||||
}
|
||||
else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
exports.firstLetter = (s, options) => {
|
||||
return s[0];
|
||||
}
|
||||
|
||||
exports.repeat = (n, options) => {
|
||||
var results = "";
|
||||
[...Array(n).keys()].forEach(i => {
|
||||
results += options.fn({index: i})
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
exports.loopEvents = (events, options) => {
|
||||
var results = "";
|
||||
if (options.data) {
|
||||
data = Handlebars.createFrame(options.data);
|
||||
}
|
||||
|
||||
events.forEach((event,i) => {
|
||||
if (data) {
|
||||
data.index = i;
|
||||
}
|
||||
results += options.fn(event, {data: data })
|
||||
}
|
||||
)
|
||||
return results;
|
||||
}
|
||||
|
||||
exports.timepointForMember = (member, timeline, event, options) => {
|
||||
var results = ""
|
||||
if (options.data) {
|
||||
data = Handlebars.createFrame(options.data);
|
||||
}
|
||||
const availability = timeline.availabilities.find(a=>a.memberId==member.id && a.eventId==event.id)
|
||||
const eventLineupEntry = timeline.event_lineup_entries.find(a=>(a.memberId==member.id || a.memberName == `${member.firstName} ${member.lastName}`) && a.eventId==event.id)
|
||||
var value = ""
|
||||
if (eventLineupEntry){
|
||||
value = parsePositionLabel(eventLineupEntry.label).positionLabelWithoutFlags
|
||||
}
|
||||
else {
|
||||
value = availability?.status[0]
|
||||
}
|
||||
return options.fn({availability: availability, eventLineupEntry: eventLineupEntry, value}, {data: data })
|
||||
}
|
||||
|
||||
exports.ifEquals = (testValue, targetValue, options) => {
|
||||
if (testValue === targetValue) {
|
||||
return options.fn();
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
180
src/lib/utils.js
Normal file
180
src/lib/utils.js
Normal file
@@ -0,0 +1,180 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
exports.teamsnapMembersSortLineupAvailabilityLastName = (a, b) => {
|
||||
status_code_sort = [
|
||||
teamsnap.AVAILABILITIES.YES,
|
||||
teamsnap.AVAILABILITIES.MAYBE,
|
||||
teamsnap.AVAILABILITIES.NO,
|
||||
teamsnap.AVAILABILITIES.NONE,
|
||||
];
|
||||
|
||||
if (a.benchcoach.eventLineupEntry != null && b.benchcoach.eventLineupEntry != null){
|
||||
return a.benchcoach.eventLineupEntry.sequence - b.benchcoach.eventLineupEntry.sequence
|
||||
}
|
||||
else if (a.benchcoach.eventLineupEntry != null && b.benchcoach.eventLineupEntry == null){
|
||||
return -1
|
||||
}
|
||||
else if (a.benchcoach.eventLineupEntry == null && b.benchcoach.eventLineupEntry != null) {
|
||||
return 1
|
||||
}
|
||||
else {
|
||||
return teamsnapMembersSortAvailabilityLastName(a,b)
|
||||
}
|
||||
};
|
||||
|
||||
teamsnapMembersSortAvailabilityLastName = (a, b) => {
|
||||
status_code_sort = [
|
||||
teamsnap.AVAILABILITIES.YES,
|
||||
teamsnap.AVAILABILITIES.MAYBE,
|
||||
teamsnap.AVAILABILITIES.NO,
|
||||
teamsnap.AVAILABILITIES.NONE,
|
||||
];
|
||||
|
||||
a_sort = status_code_sort.indexOf(a.benchcoach.availability?.statusCode);
|
||||
b_sort = status_code_sort.indexOf(b.benchcoach.availability?.statusCode);
|
||||
if (a_sort > b_sort) {
|
||||
return 1;
|
||||
}
|
||||
if (a_sort < b_sort) {
|
||||
return -1;
|
||||
}
|
||||
if (a_sort == b_sort) {
|
||||
if (a.lastName < b.lastName) {
|
||||
return -1;
|
||||
}
|
||||
if (a.lastName > b.lastName) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.teamsnapMembersSortAvailabilityLastName = teamsnapMembersSortAvailabilityLastName
|
||||
|
||||
exports.teamsnapCallback = (err,result, d) => {
|
||||
if (Array.isArray(result)){
|
||||
types = new Set(result.map(i=>i.type))
|
||||
}
|
||||
else {
|
||||
types = [result?.type]
|
||||
}
|
||||
|
||||
if (d) {
|
||||
console.log(
|
||||
'\x1b[33mTeamSnap:\x1b[0m',
|
||||
`${d.source} using ${d.method ? "teamsnap."+d.method : "?"} \x1b[33m\[${Array.from(types).join(", ")}\]\x1b[0m`
|
||||
)
|
||||
}
|
||||
if (err) {
|
||||
console.log(err.message);
|
||||
throw new Error(err)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
exports.teamsnapFailure = (err, next) => {
|
||||
if (err) {
|
||||
console.log(err.message);
|
||||
}
|
||||
next(err);
|
||||
}
|
||||
|
||||
const getPluralType = (type) =>{
|
||||
// There are some instances where a type is not
|
||||
// in the list of teamsnap.types, so a plural
|
||||
// is not generated in the lookup. this is a
|
||||
// kludge around that. (specifically availabilitySummary)
|
||||
plural = teamsnap.getPluralType(type) || (function() {
|
||||
if (type === undefined){
|
||||
return type
|
||||
}
|
||||
|
||||
switch (type.slice(-1)) {
|
||||
case 'y':
|
||||
return type.slice(0, -1) + 'ies';
|
||||
case 's':
|
||||
return type + 'es';
|
||||
default:
|
||||
return type + 's';
|
||||
}
|
||||
})();
|
||||
return plural
|
||||
}
|
||||
|
||||
exports.groupTeamsnapItems = (items, types = [], params = {}) => {
|
||||
const result = {};
|
||||
items.forEach(item => {
|
||||
const type = item.type
|
||||
const type_plural = getPluralType(type)
|
||||
if ((types.length > 0 && types.includes(type)) || (types.length == 0)) {
|
||||
if (!result[type_plural]) result[type_plural] = []
|
||||
result[type_plural].push(item)
|
||||
}
|
||||
})
|
||||
return result;
|
||||
}
|
||||
|
||||
exports.embeddedSvgFromPath = (svg_path, additional_classes, options) => {
|
||||
const iconStaticPaths = {
|
||||
"/teamsnap-ui/assets":path.join(__dirname, "/../../node_modules/@teamsnap/teamsnap-ui/src/assets"),
|
||||
"/bootstrap-icons":path.join(__dirname, "/../../node_modules/bootstrap-icons/icons"),
|
||||
"/media":path.join(__dirname, "/../public/media")
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(iconStaticPaths)) {
|
||||
if (svg_path.startsWith(key)) {
|
||||
svg_path = svg_path.replace(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
if (!options) {options=additional_classes; additional_classes=''}
|
||||
|
||||
const svg = fs.readFileSync(`${svg_path}`, 'utf8');
|
||||
|
||||
svgRegExWithClass = new RegExp(/<svg(.*)class="(.*?)"(.*)>/)
|
||||
svgRegExWithoutClass = new RegExp(/<svg(.*?)>/)
|
||||
|
||||
if (svgRegExWithClass.test(svg)) {
|
||||
return svg.replace(svgRegExWithClass, `<svg$1 class="$2 Icon ${additional_classes}"$3>`)
|
||||
}
|
||||
else if (svgRegExWithoutClass.test(svg)) {
|
||||
return svg.replace(svgRegExWithoutClass, `<svg$1 class="Icon ${additional_classes}">`)
|
||||
}
|
||||
else return svg
|
||||
}
|
||||
|
||||
exports.parsePositionLabel = (label) => {
|
||||
if (label == null) {
|
||||
return {
|
||||
positionLabelWithoutFlags: null,
|
||||
positionFlags: null
|
||||
}
|
||||
}
|
||||
const pattern = /(?<pos>[A-Z0-9]+)(?:\s\[(?<flags>.[A-z,]+)\])?/g
|
||||
const {pos, flags} = pattern.exec(label)?.groups || {}
|
||||
const positionLabelWithoutFlags= pos
|
||||
const positionFlags = new Set(flags?.split(',').map(f=>f.trim()) || [])
|
||||
|
||||
return {positionLabelWithoutFlags, positionFlags}
|
||||
}
|
||||
|
||||
exports.compilePositionLabel = (label, flags) => {
|
||||
if (flags == null || flags == '' || flags.size == 0) {
|
||||
return label
|
||||
}
|
||||
else {
|
||||
const flags_set = toFlagsSet(flags)
|
||||
return `${label} [${Array.from(flags_set).sort().join(',')}]`
|
||||
}
|
||||
}
|
||||
|
||||
function toFlagsSet(flags) {
|
||||
let flags_set
|
||||
if (typeof(flags) == 'string'){
|
||||
flags_set = new Set(flags.split(',').map(s=>s.trim()))
|
||||
} else if (flags.constructor === Array){
|
||||
flags_set = new Set(flags)
|
||||
} else if (flags.constructor === Set){
|
||||
flags_set = flags
|
||||
}
|
||||
return flags_set
|
||||
}
|
||||
51
src/middlewares/bulkload.js
Normal file
51
src/middlewares/bulkload.js
Normal file
@@ -0,0 +1,51 @@
|
||||
exports.loadRecentAndUpcomingEvents = async (req, res, next) => {
|
||||
const {team_id, event_id} = req.params
|
||||
const page_size = req.query.page_size ? Number(req.query.page_size) : 4
|
||||
var subject_date
|
||||
if (event_id) {
|
||||
const event = await teamsnap.loadEvents({id: event_id}).pop()
|
||||
const new_date = new Date(event.startDate.getTime()+10000);
|
||||
subject_date = event.startDate
|
||||
}
|
||||
else {
|
||||
subject_date = new Date()
|
||||
}
|
||||
req.promises.push(
|
||||
teamsnap.bulkLoad({
|
||||
teamId: team_id,
|
||||
types: ["event", "availabilitySummary"],
|
||||
scopeTo: "event",
|
||||
event__startedAfter: new Date(subject_date.getTime()+10000),
|
||||
event__pageSize: page_size
|
||||
})
|
||||
.then(items => tsUtils.groupTeamsnapItems(items))
|
||||
.then((items)=>{
|
||||
req.upcoming_events=items.events ? items.events : [];
|
||||
const availabilitySummaries=items.availabilitySummaries;
|
||||
req.upcoming_events.forEach((event) => {
|
||||
event.link('availabilitySummary', availabilitySummaries.find(a=>a.eventId==event.id))
|
||||
})
|
||||
}
|
||||
).fail(utils.teamsnapFailure)
|
||||
)
|
||||
req.promises.push(
|
||||
teamsnap.bulkLoad({
|
||||
teamId: team_id,
|
||||
types: ["event", "availabilitySummary"],
|
||||
scopeTo: "event",
|
||||
event__startedBefore: new Date(subject_date.getTime()-10000),
|
||||
event__pageSize: page_size,
|
||||
event__sortStartDate: "desc"
|
||||
})
|
||||
.then(items => tsUtils.groupTeamsnapItems(items))
|
||||
.then((items)=>{
|
||||
req.recent_events=items.events || [];
|
||||
const availabilitySummaries=items.availabilitySummaries;
|
||||
req.recent_events.forEach((event) => {
|
||||
event.link('availabilitySummary', availabilitySummaries.find(a=>a.eventId==event.id))
|
||||
})
|
||||
}
|
||||
).fail(utils.teamsnapFailure)
|
||||
)
|
||||
next();
|
||||
}
|
||||
18
src/middlewares/csrf.js
Normal file
18
src/middlewares/csrf.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const { doubleCsrf } = require('csrf-csrf');
|
||||
|
||||
const csrf = doubleCsrf({
|
||||
getSecret: () => process.env.CSRF_SECRET,
|
||||
getTokenFromRequest: req => {
|
||||
return req.body.csrfToken
|
||||
},
|
||||
cookieName: process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'development' ? '__benchcoach.x-csrf-token' : '_csrf',
|
||||
cookieOptions: {
|
||||
secure: process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'development' // Enable for HTTPS in production
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = {
|
||||
doubleCsrfProtection: csrf.doubleCsrfProtection,
|
||||
generateToken: csrf.generateToken
|
||||
};
|
||||
7161
src/public/css/application.css
Normal file
7161
src/public/css/application.css
Normal file
File diff suppressed because it is too large
Load Diff
1
src/public/css/application.css.map
Normal file
1
src/public/css/application.css.map
Normal file
File diff suppressed because one or more lines are too long
908
src/public/css/eventsheet.css
Normal file
908
src/public/css/eventsheet.css
Normal file
@@ -0,0 +1,908 @@
|
||||
@charset "UTF-8";
|
||||
@import url("https://fonts.googleapis.com/css2?family=Pacifico");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Oswald");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Graduate");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inconsolata:wdth,wght@50..200,200..900&display=swap");
|
||||
@import url("/font/helvetica-now/stylesheet.css");
|
||||
@import url("/font/futura-now/stylesheet.css");
|
||||
:root {
|
||||
--color-success: #b7e1cd;
|
||||
--color-danger: #f4c7c3;
|
||||
--color-neutral: #acc9fe;
|
||||
--color-warning: rgb(249, 228, 180);
|
||||
--color-grey-100: #f8f9fa;
|
||||
--color-grey-200: #e9ecef;
|
||||
--color-grey-300: #dee2e6;
|
||||
--color-grey-400: #ced4da;
|
||||
--color-grey-500: #adb5bd;
|
||||
--color-grey-600: #6c757d;
|
||||
--color-grey-700: #495057;
|
||||
--color-grey-800: #343a40;
|
||||
--color-grey-900: #212529;
|
||||
--header-height: 17px;
|
||||
--row-height: 14px;
|
||||
--monospace-font: "Inconsolata", monospace;
|
||||
--section-border: 0.5px solid black;
|
||||
}
|
||||
|
||||
/** For Print **/
|
||||
@media print {
|
||||
:root {
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
body .sheet {
|
||||
padding: 0.175in;
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
/** For screen preview **/
|
||||
@media screen {
|
||||
body .sheet {
|
||||
padding: 0.175in;
|
||||
}
|
||||
body {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
.sheet {
|
||||
margin: auto;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 0.5mm 2mm rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
.sheet {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
page-break-after: always;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/** Paper sizes **/
|
||||
body.B5 .sheet {
|
||||
width: 176mm;
|
||||
height: 250mm;
|
||||
}
|
||||
|
||||
body.index-card .sheet {
|
||||
width: 3.5in;
|
||||
height: 5in;
|
||||
}
|
||||
|
||||
body.letter .sheet {
|
||||
width: 8.5in;
|
||||
height: 11in;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Helvetica Now", "Helvetica", sans-serif;
|
||||
position: relative;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
table, #roster-and-history table, .lineup-card table {
|
||||
font-size: inherit;
|
||||
border-collapse: collapse;
|
||||
empty-cells: show;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
table th, #roster-and-history table th, .lineup-card table th {
|
||||
color: var(--color-grey-700);
|
||||
}
|
||||
table th, #roster-and-history table th, .lineup-card table th, table td, table #roster-and-history .position, #roster-and-history table .position, #roster-and-history table td, .lineup-card table td {
|
||||
overflow: hidden;
|
||||
padding: 0 2px 0 2px;
|
||||
}
|
||||
table th:empty::after, #roster-and-history table th:empty::after, table td:empty::after, table #roster-and-history .position:empty::after, #roster-and-history table .position:empty::after, #roster-and-history table td:empty::after {
|
||||
content: " ";
|
||||
}
|
||||
table.striped tr:nth-child(odd) td, table.striped tr:nth-child(odd) #roster-and-history .position, #roster-and-history table tr:nth-child(odd) td, #roster-and-history table tr:nth-child(odd) .position, .lineup-card table tr:nth-child(odd) td, .lineup-card table tr:nth-child(odd) #roster-and-history .position, table.striped tr:nth-child(odd) th, #roster-and-history table tr:nth-child(odd) th, .lineup-card table tr:nth-child(odd) th {
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
table.striped tr:nth-child(even) td, table.striped tr:nth-child(even) #roster-and-history .position, #roster-and-history table tr:nth-child(even) td, #roster-and-history table tr:nth-child(even) .position, .lineup-card table tr:nth-child(even) td, .lineup-card table tr:nth-child(even) #roster-and-history .position, table.striped tr:nth-child(even) th, #roster-and-history table tr:nth-child(even) th, .lineup-card table tr:nth-child(even) th {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.float-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.eventsheet {
|
||||
--page-margin: 0.175in;
|
||||
}
|
||||
.eventsheet:has(section) {
|
||||
display: grid;
|
||||
}
|
||||
.eventsheet:has(section) section {
|
||||
--divider-border: lightgrey dashed 1px;
|
||||
box-sizing: content-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
outline-style: solid;
|
||||
outline-width: calc(var(--divider-border) / 4);
|
||||
outline-color: lightgray;
|
||||
}
|
||||
.eventsheet:has(section) section > div {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: stretch;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
.eventsheet:has(section) section.NW {
|
||||
grid-area: 1/1/2/2;
|
||||
}
|
||||
.eventsheet:has(section) section.NE {
|
||||
grid-area: 1/2/2/3;
|
||||
}
|
||||
.eventsheet:has(section) section.SW {
|
||||
grid-area: 2/1/3/2;
|
||||
}
|
||||
.eventsheet:has(section) section.SE {
|
||||
grid-area: 2/2/3/3;
|
||||
}
|
||||
.eventsheet:has(section) section.NW .divider, .eventsheet:has(section) section.SW .divider {
|
||||
border-right: var(--divider-border);
|
||||
}
|
||||
.eventsheet:has(section) section.NW .divider, .eventsheet:has(section) section.NE .divider {
|
||||
border-bottom: var(--divider-border);
|
||||
}
|
||||
|
||||
.eventsheet.quarters {
|
||||
--section-margin: calc(var(--page-margin)/2);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
column-gap: calc(var(--page-margin) * 2);
|
||||
row-gap: calc(var(--page-margin) * 2);
|
||||
outline-offset: var(--section-margin);
|
||||
}
|
||||
.eventsheet.quarters section {
|
||||
outline-offset: var(--page-margin);
|
||||
}
|
||||
|
||||
.letter .eventsheet.quarters {
|
||||
--header-height: 0.5in;
|
||||
}
|
||||
|
||||
.letter .eventsheet.index-cards-4x6 {
|
||||
--section-margin: calc(var(--page-margin)/2);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 125mm;
|
||||
column-gap: calc(var(--page-margin) * 2);
|
||||
row-gap: calc(var(--page-margin) * 2);
|
||||
}
|
||||
.letter .eventsheet.index-cards-4x6 section {
|
||||
outline-offset: var(--page-margin);
|
||||
}
|
||||
|
||||
.letter .eventsheet.index-cards-3x5 {
|
||||
--section-margin: calc(var(--page-margin)/2);
|
||||
grid-template-columns: 3in 3in;
|
||||
grid-template-rows: 5in 5in;
|
||||
column-gap: calc(var(--page-margin) * 2);
|
||||
row-gap: calc(var(--page-margin) * 2);
|
||||
}
|
||||
.letter .eventsheet.index-cards-3x5 section {
|
||||
outline-offset: var(--page-margin);
|
||||
}
|
||||
|
||||
.lineup-card {
|
||||
counter-reset: lineup-sequence-counter 0;
|
||||
--border: 0.5px solid grey;
|
||||
border: var(--section-border);
|
||||
}
|
||||
.lineup-card header {
|
||||
text-transform: uppercase;
|
||||
border-style: none;
|
||||
border-bottom: var(--border);
|
||||
height: var(--header-height);
|
||||
}
|
||||
.lineup-card header:empty::after {
|
||||
content: " ";
|
||||
}
|
||||
.lineup-card th {
|
||||
width: inherit;
|
||||
}
|
||||
.lineup-card th.sequence {
|
||||
counter-increment: lineup-sequence-counter 1;
|
||||
color: var(--color-grey-600);
|
||||
font-size: inherit;
|
||||
width: 2ch;
|
||||
font-stretch: 50%;
|
||||
border-right: var(--border);
|
||||
}
|
||||
.lineup-card th.sequence.counter::before {
|
||||
content: counter(lineup-sequence-counter);
|
||||
}
|
||||
.lineup-card thead th {
|
||||
color: var(--color-grey-600);
|
||||
font-size: 0.7em;
|
||||
border-bottom: var(--border);
|
||||
}
|
||||
.lineup-card table, .lineup-card #roster-and-history table, #roster-and-history .lineup-card table {
|
||||
font-size: 21px;
|
||||
}
|
||||
.lineup-card td, .lineup-card #roster-and-history .position, #roster-and-history .lineup-card .position {
|
||||
/* height: 34px; */
|
||||
}
|
||||
.lineup-card td.substitution, .lineup-card #roster-and-history .substitution.position, #roster-and-history .lineup-card .substitution.position {
|
||||
width: 8ch;
|
||||
}
|
||||
.lineup-card td.substitution::after, .lineup-card #roster-and-history .substitution.position::after, #roster-and-history .lineup-card .substitution.position::after {
|
||||
content: "";
|
||||
}
|
||||
.lineup-card td.position, .lineup-card #roster-and-history .position, #roster-and-history .lineup-card .position, .lineup-card td.jersey-number {
|
||||
width: 2ch;
|
||||
}
|
||||
.lineup-card td.position, .lineup-card #roster-and-history .position, #roster-and-history .lineup-card .position, .lineup-card td.jersey-number, .lineup-card td.substitution {
|
||||
font-family: var(--monospace-font);
|
||||
border-left: var(--border);
|
||||
text-align: right;
|
||||
padding-left: 2.5px;
|
||||
padding-right: 2.5px;
|
||||
}
|
||||
.lineup-card tr + tr td, .lineup-card tr + tr #roster-and-history .position, #roster-and-history .lineup-card tr + tr .position, .lineup-card tr + tr th {
|
||||
border-top: var(--border);
|
||||
}
|
||||
.lineup-card.dugout td.player-name, .lineup-card.dugout #roster-and-history .player-name.position, #roster-and-history .lineup-card.dugout .player-name.position {
|
||||
width: 10ch;
|
||||
font-stretch: 75%;
|
||||
}
|
||||
.lineup-card.dugout .position, .lineup-card.dugout .jersey-number, .lineup-card.dugout .substitution {
|
||||
font-stretch: 75%;
|
||||
}
|
||||
.lineup-card.exchange header {
|
||||
text-align: center;
|
||||
}
|
||||
.lineup-card.exchange header .float-left, .lineup-card.exchange header .float-right {
|
||||
float: none;
|
||||
}
|
||||
.lineup-card.exchange .player-name {
|
||||
font-stretch: 100%;
|
||||
}
|
||||
.lineup-card.exchange .homeaway, .lineup-card.exchange .substitution {
|
||||
display: none;
|
||||
}
|
||||
|
||||
section.blank svg, section.blank header {
|
||||
filter: grayscale(1) opacity(0.4);
|
||||
}
|
||||
section.blank > div {
|
||||
filter: opacity(0.4);
|
||||
}
|
||||
section.blank > div td.substitution, section.blank > div #roster-and-history .substitution.position, #roster-and-history section.blank > div .substitution.position {
|
||||
border-width: 0.5;
|
||||
}
|
||||
|
||||
#todays-game > div {
|
||||
display: grid;
|
||||
grid-template-columns: 110px auto;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-areas: "offense defense" "footer footer";
|
||||
}
|
||||
#todays-game #offense-pane {
|
||||
grid-area: offense;
|
||||
}
|
||||
#todays-game #defense-pane {
|
||||
grid-area: defense;
|
||||
}
|
||||
#todays-game .footer {
|
||||
/* height:var(--row-height); */
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
grid-area: footer;
|
||||
/* border: 1px solid black; */
|
||||
height: 100%;
|
||||
border-right: 0.5px solid grey;
|
||||
border-left: 0.5px solid grey;
|
||||
}
|
||||
#todays-game .footer table {
|
||||
height: 100%;
|
||||
outline: none;
|
||||
border-style: none;
|
||||
}
|
||||
#todays-game .footer table tr td, #todays-game .footer table tr #roster-and-history .position, #roster-and-history #todays-game .footer table tr .position, #todays-game .footer table tr th {
|
||||
background-color: white;
|
||||
outline: none;
|
||||
border-bottom: 0.5px solid var(--color-grey-500);
|
||||
}
|
||||
#todays-game .footer table tr :last-child td, #todays-game .footer table tr :last-child #roster-and-history .position, #roster-and-history #todays-game .footer table tr :last-child .position {
|
||||
background-color: white;
|
||||
outline: none;
|
||||
border-bottom-style: none;
|
||||
}
|
||||
#todays-game .footer table th {
|
||||
text-align: left;
|
||||
color: var(--color-grey-600);
|
||||
}
|
||||
#todays-game .footer table td, #todays-game .footer table #roster-and-history .position, #roster-and-history #todays-game .footer table .position {
|
||||
height: var(--row-height);
|
||||
border: none;
|
||||
}
|
||||
#todays-game .footer table tdempty::after {
|
||||
content: "";
|
||||
}
|
||||
#todays-game table.notes th {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
line-height: 1em;
|
||||
}
|
||||
#todays-game table.notes td:empty::after, #todays-game table.notes #roster-and-history .position:empty::after, #roster-and-history #todays-game table.notes .position:empty::after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
#defense-card #defense-pane {
|
||||
width: 100%;
|
||||
}
|
||||
#defense-card .footer {
|
||||
display: none;
|
||||
}
|
||||
#defense-card .slot-set table {
|
||||
font-size: 14px;
|
||||
width: 120px;
|
||||
}
|
||||
#defense-card .slot-set.pos-c {
|
||||
grid-area: 6/1/7/5 !important;
|
||||
}
|
||||
#defense-card .slot-set.pos-p {
|
||||
justify-content: center !important;
|
||||
align-items: center;
|
||||
margin-bottom: inherit !important;
|
||||
grid-area: 5/1/6/5 !important;
|
||||
}
|
||||
#defense-card .slot-set.pos-p table {
|
||||
width: 120px !important;
|
||||
}
|
||||
#defense-card .slot-set.pos-p table tbody > tr:last-child {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
#defense-card {
|
||||
border: var(--section-border);
|
||||
}
|
||||
|
||||
#defense-pane {
|
||||
position: relative;
|
||||
grid-area: defense;
|
||||
padding: 4px 4px 0px 4px; /* top right bottom left */
|
||||
display: flex;
|
||||
}
|
||||
#defense-pane .field-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-template-rows: repeat(6, 1fr);
|
||||
grid-column-gap: 4px;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
#defense-pane svg {
|
||||
position: absolute;
|
||||
stroke-linecap: round;
|
||||
stroke-miterlimit: 1.5;
|
||||
z-index: -1;
|
||||
opacity: 70%;
|
||||
}
|
||||
#defense-pane svg #outfield-path {
|
||||
stroke: #4AA1D5;
|
||||
fill: none;
|
||||
stroke-width: 4px;
|
||||
}
|
||||
#defense-pane svg #infield-path {
|
||||
stroke: #4AA1D5;
|
||||
fill: #D1E6F7;
|
||||
stroke-width: 4px;
|
||||
fill-opacity: 50%;
|
||||
}
|
||||
#defense-pane .slot-set {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
#defense-pane .slot-set .player-name {
|
||||
font-stretch: 80%;
|
||||
}
|
||||
#defense-pane .slot-set table {
|
||||
--border: grey solid 0.5px;
|
||||
border: var(--border);
|
||||
opacity: 85%;
|
||||
}
|
||||
#defense-pane .slot-set table tr:first-child th {
|
||||
border-bottom: var(--border);
|
||||
}
|
||||
#defense-pane .slot-set table tr + tr td, #defense-pane .slot-set table tr + tr #roster-and-history .position, #roster-and-history #defense-pane .slot-set table tr + tr .position, #defense-pane .slot-set table tr + tr th {
|
||||
border-top: var(--border);
|
||||
}
|
||||
#defense-pane .slot-set table tr th.position {
|
||||
font-family: var(--monospace-font);
|
||||
width: 2ch;
|
||||
text-align: right;
|
||||
}
|
||||
#defense-pane .slot-set.pos-cf tr:first-child th.position:empty {
|
||||
border-right: var(--border);
|
||||
}
|
||||
#defense-pane .slot-set.pos-cf tr:first-child th.position:empty::after {
|
||||
content: "cf";
|
||||
}
|
||||
#defense-pane .slot-set.pos-lf tr:first-child th.position:empty {
|
||||
border-right: var(--border);
|
||||
}
|
||||
#defense-pane .slot-set.pos-lf tr:first-child th.position:empty::after {
|
||||
content: "lf";
|
||||
}
|
||||
#defense-pane .slot-set.pos-rf tr:first-child th.position:empty {
|
||||
border-right: var(--border);
|
||||
}
|
||||
#defense-pane .slot-set.pos-rf tr:first-child th.position:empty::after {
|
||||
content: "rf";
|
||||
}
|
||||
#defense-pane .slot-set.pos-ss tr:first-child th.position:empty {
|
||||
border-right: var(--border);
|
||||
}
|
||||
#defense-pane .slot-set.pos-ss tr:first-child th.position:empty::after {
|
||||
content: "ss";
|
||||
}
|
||||
#defense-pane .slot-set.pos-2b tr:first-child th.position:empty {
|
||||
border-right: var(--border);
|
||||
}
|
||||
#defense-pane .slot-set.pos-2b tr:first-child th.position:empty::after {
|
||||
content: "2b";
|
||||
}
|
||||
#defense-pane .slot-set.pos-3b tr:first-child th.position:empty {
|
||||
border-right: var(--border);
|
||||
}
|
||||
#defense-pane .slot-set.pos-3b tr:first-child th.position:empty::after {
|
||||
content: "3b";
|
||||
}
|
||||
#defense-pane .slot-set.pos-1b tr:first-child th.position:empty {
|
||||
border-right: var(--border);
|
||||
}
|
||||
#defense-pane .slot-set.pos-1b tr:first-child th.position:empty::after {
|
||||
content: "1b";
|
||||
}
|
||||
#defense-pane .slot-set.pos-c tr:first-child th.position:empty {
|
||||
border-right: var(--border);
|
||||
}
|
||||
#defense-pane .slot-set.pos-c tr:first-child th.position:empty::after {
|
||||
content: "c";
|
||||
}
|
||||
#defense-pane .slot-set.pos-p tr:first-child th.position:empty {
|
||||
border-right: var(--border);
|
||||
}
|
||||
#defense-pane .slot-set.pos-p tr:first-child th.position:empty::after {
|
||||
content: "p";
|
||||
}
|
||||
#defense-pane .slot-set.pos-cf {
|
||||
justify-content: center;
|
||||
grid-area: 1/1/2/5;
|
||||
}
|
||||
#defense-pane .slot-set.pos-lf {
|
||||
justify-content: flex-start;
|
||||
grid-area: 2/1/3/3;
|
||||
}
|
||||
#defense-pane .slot-set.pos-rf {
|
||||
justify-content: flex-end;
|
||||
grid-area: 2/3/3/5;
|
||||
}
|
||||
#defense-pane .slot-set.pos-ss {
|
||||
justify-content: flex-end;
|
||||
grid-area: 3/1/4/3;
|
||||
}
|
||||
#defense-pane .slot-set.pos-2b {
|
||||
justify-content: flex-start;
|
||||
grid-area: 3/3/4/5;
|
||||
}
|
||||
#defense-pane .slot-set.pos-3b {
|
||||
justify-content: flex-start;
|
||||
grid-area: 4/1/5/3;
|
||||
}
|
||||
#defense-pane .slot-set.pos-1b {
|
||||
justify-content: flex-end;
|
||||
grid-area: 4/3/5/5;
|
||||
}
|
||||
#defense-pane .slot-set.pos-c {
|
||||
justify-content: center;
|
||||
grid-area: 5/1/6/5;
|
||||
}
|
||||
#defense-pane .slot-set.pos-p {
|
||||
align-items: end;
|
||||
margin-bottom: 4px;
|
||||
grid-area: 6/1/7/5;
|
||||
}
|
||||
#defense-pane .slot-set.pos-p table {
|
||||
width: 100%;
|
||||
}
|
||||
#defense-pane .slot-set.pos-p tr.substitute .position:empty {
|
||||
border-right: var(--border);
|
||||
}
|
||||
#defense-pane .slot-set.pos-p tr.substitute .position:empty::after {
|
||||
content: "RP";
|
||||
}
|
||||
|
||||
#offense-pane {
|
||||
position: relative;
|
||||
/* box-sizing: border-box; */
|
||||
height: 100%;
|
||||
border-bottom: 0.5px solid black;
|
||||
counter-reset: lineup-sequence-counter 0;
|
||||
/* outline: 0.5px solid black; */
|
||||
}
|
||||
#offense-pane table {
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
#offense-pane th.sequence {
|
||||
counter-increment: lineup-sequence-counter 1;
|
||||
}
|
||||
#offense-pane th.sequence.counter::before {
|
||||
content: counter(lineup-sequence-counter);
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #cadcf9;
|
||||
height: var(--header-height);
|
||||
width: auto;
|
||||
text-align: center;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border-bottom: var(--section-border);
|
||||
}
|
||||
|
||||
.cell-checkbox {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.in-starting-lineup {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
font-stretch: semi-condensed;
|
||||
}
|
||||
|
||||
.homeaway {
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.cell-smalltext {
|
||||
font-stretch: condensed;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.statscell {
|
||||
font-family: "m+1m";
|
||||
text-align: center;
|
||||
font-stretch: extra-condensed;
|
||||
font-size: 9px;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.condensedNameCell {
|
||||
width: 70px;
|
||||
text-transform: uppercase;
|
||||
font-stretch: condensed;
|
||||
}
|
||||
|
||||
.cell-square {
|
||||
height: var(--row-height);
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cell-square.narrow {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.cell-mono {
|
||||
font-family: "m+1m";
|
||||
}
|
||||
|
||||
.cell-condensed {
|
||||
font-stretch: condensed;
|
||||
}
|
||||
|
||||
#roster-and-history {
|
||||
--border: var(--section-border);
|
||||
}
|
||||
#roster-and-history table tr td.available-status-code-1, #roster-and-history table tr .available-status-code-1.position {
|
||||
color: rgb(0, 85, 0);
|
||||
background-color: #b7e1cd;
|
||||
}
|
||||
#roster-and-history table tr td.available-status-code-0, #roster-and-history table tr .available-status-code-0.position {
|
||||
color: rgb(170, 0, 0);
|
||||
background-color: #f4c7c3;
|
||||
}
|
||||
#roster-and-history table tr td.past.available-status-code-0, #roster-and-history table tr .past.available-status-code-0.position, #roster-and-history table tr td.past.available-status-code-null, #roster-and-history table tr .past.available-status-code-null.position {
|
||||
color: var(--color-grey-600);
|
||||
background-color: inherit;
|
||||
}
|
||||
#roster-and-history table tr td.past.available-status-code-1.Y, #roster-and-history table tr .past.available-status-code-1.Y.position {
|
||||
color: inherit;
|
||||
background-color: var(--color-warning);
|
||||
}
|
||||
#roster-and-history table tr td.available-status-code-2, #roster-and-history table tr .available-status-code-2.position {
|
||||
color: blue;
|
||||
background-color: #acc9fe;
|
||||
}
|
||||
#roster-and-history table thead tr {
|
||||
border: black solid 1px;
|
||||
height: var(--header-height);
|
||||
}
|
||||
#roster-and-history > div > table {
|
||||
/* font-size: 10.5px; */
|
||||
padding: 0;
|
||||
line-height: 1em;
|
||||
/* outline: 0.5px black; */
|
||||
}
|
||||
#roster-and-history tr.starting-today td.jersey-number, #roster-and-history tr.starting-today .jersey-number.position, #roster-and-history tr.starting-today td.player-name, #roster-and-history tr.starting-today .player-name.position {
|
||||
font-weight: bold;
|
||||
}
|
||||
#roster-and-history .player-name {
|
||||
font-stretch: 95%;
|
||||
}
|
||||
#roster-and-history .jersey-number {
|
||||
font-family: var(--monospace-font);
|
||||
width: 2ch;
|
||||
text-align: right;
|
||||
overflow: hidden;
|
||||
}
|
||||
#roster-and-history tr + tr {
|
||||
border-top: var(--border);
|
||||
}
|
||||
#roster-and-history td, #roster-and-history .position, #roster-and-history th {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
padding: 0.2em 0.1em 0.2em 0.1em; /* top right bottom left */
|
||||
}
|
||||
#roster-and-history td.availability-on-day, #roster-and-history .position, #roster-and-history th.availability-on-day {
|
||||
font-family: var(--monospace-font);
|
||||
font-stretch: 60%;
|
||||
text-align: center;
|
||||
max-width: 0.8em;
|
||||
min-width: 0.8em;
|
||||
}
|
||||
#roster-and-history td.availability-on-day.future, #roster-and-history .future.position, #roster-and-history td.availability-on-day.past, #roster-and-history .past.position, #roster-and-history th.availability-on-day.future, #roster-and-history th.availability-on-day.past {
|
||||
font-family: var(--monospace-font);
|
||||
font-stretch: condensed;
|
||||
font-weight: normal;
|
||||
font-size: 0.8em;
|
||||
padding: 0.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
#roster-and-history td.position-capability, #roster-and-history .position-capability.position, #roster-and-history th.position-capability {
|
||||
font-size: 8px;
|
||||
font-stretch: 50%;
|
||||
width: 5px;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
}
|
||||
#roster-and-history td.spacer, #roster-and-history .spacer.position, #roster-and-history th.spacer {
|
||||
display: none;
|
||||
}
|
||||
#roster-and-history td.spacer.first-of-group, #roster-and-history .spacer.first-of-group.position, #roster-and-history td.spacer.last-of-group, #roster-and-history .spacer.last-of-group.position, #roster-and-history th.spacer.first-of-group, #roster-and-history th.spacer.last-of-group {
|
||||
border: none;
|
||||
}
|
||||
#roster-and-history td.player-stats, #roster-and-history .player-stats.position, #roster-and-history th.player-stats {
|
||||
display: none;
|
||||
font-family: var(--monospace-font);
|
||||
font-size: 1em;
|
||||
font-stretch: 60%;
|
||||
font-weight: 300;
|
||||
}
|
||||
#roster-and-history td.player-stats .delimiter, #roster-and-history .player-stats.position .delimiter,
|
||||
#roster-and-history td.player-stats .decimal-point,
|
||||
#roster-and-history .player-stats.position .decimal-point, #roster-and-history th.player-stats .delimiter,
|
||||
#roster-and-history th.player-stats .decimal-point {
|
||||
font-family: Helvetica Now;
|
||||
font-stretch: expanded;
|
||||
color: var(--color-grey-500);
|
||||
}
|
||||
#roster-and-history td.player-stats .decimal-point, #roster-and-history .player-stats.position .decimal-point, #roster-and-history th.player-stats .decimal-point {
|
||||
color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
#roster-and-history td.player-stats .delimiter, #roster-and-history .player-stats.position .delimiter, #roster-and-history th.player-stats .delimiter {
|
||||
color: var(--color-grey-500);
|
||||
}
|
||||
#roster-and-history td.player-name, #roster-and-history .player-name.position {
|
||||
color: black !important;
|
||||
text-align: left;
|
||||
font-stretch: 95%;
|
||||
}
|
||||
#roster-and-history td.jersey-number, #roster-and-history .jersey-number.position {
|
||||
color: black !important;
|
||||
}
|
||||
#roster-and-history .first-of-group {
|
||||
border-left-width: 1px;
|
||||
border-left-style: solid;
|
||||
border-left-color: black;
|
||||
}
|
||||
#roster-and-history .last-of-group {
|
||||
border-right-width: 1px;
|
||||
border-right-style: solid;
|
||||
border-right-color: black;
|
||||
}
|
||||
#roster-and-history col.player-stats {
|
||||
border: inherit;
|
||||
}
|
||||
#roster-and-history table tr:nth-child(odd) th {
|
||||
background-color: #cadcf9;
|
||||
color: black;
|
||||
}
|
||||
#roster-and-history table tr:nth-child(odd) th.availability-on-day div, #roster-and-history table tr:nth-child(odd) th.position div {
|
||||
transform: rotate(270deg);
|
||||
/* font-stretch: 40%; */
|
||||
font-stretch: 75%;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
}
|
||||
#roster-and-history thead > tr, #roster-and-history tfoot > tr {
|
||||
border-bottom: solid black 1px;
|
||||
}
|
||||
#roster-and-history tbody {
|
||||
border-bottom: solid black 1px;
|
||||
}
|
||||
#roster-and-history tr.border-top td, #roster-and-history tr.border-top .position, #roster-and-history tr.border-top th {
|
||||
border-top: 1px solid black;
|
||||
}
|
||||
|
||||
.letter .eventsheet.quarters header {
|
||||
font-size: xx-large;
|
||||
}
|
||||
.letter .eventsheet.quarters .lineup-card table, .letter .eventsheet.quarters .lineup-card #roster-and-history table, #roster-and-history .letter .eventsheet.quarters .lineup-card table, .letter .eventsheet.quarters #roster-and-history .lineup-card table {
|
||||
font-size: 23;
|
||||
}
|
||||
.letter .eventsheet.quarters #defense-pane .slot-set.pos-p {
|
||||
align-items: start;
|
||||
}
|
||||
.letter .eventsheet.quarters #roster-and-history .spacer {
|
||||
display: table-cell;
|
||||
width: 30%;
|
||||
}
|
||||
.letter .eventsheet.quarters #roster-and-history td.position.last-of-group, .letter .eventsheet.quarters #roster-and-history .position.last-of-group {
|
||||
border-right: none;
|
||||
}
|
||||
.letter .eventsheet.quarters #roster-and-history .container {
|
||||
--padding: 2px;
|
||||
display: block;
|
||||
flex: none;
|
||||
transform: rotate(90deg) translateY(-100%);
|
||||
transform-origin: top left;
|
||||
height: calc(4.25in - 2 * var(--page-margin) - 2 * var(--padding));
|
||||
width: calc(5.5in - 2 * var(--page-margin) - 2 * var(--padding));
|
||||
padding: var(--padding);
|
||||
}
|
||||
.letter .eventsheet.quarters #roster-and-history table {
|
||||
font-size: 11;
|
||||
height: 100%;
|
||||
}
|
||||
.letter .eventsheet.quarters #roster-and-history table thead tr {
|
||||
height: inherit;
|
||||
}
|
||||
.letter .eventsheet.quarters #roster-and-history table thead tr th {
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
.letter .eventsheet.quarters #roster-and-history table th.availability-on-day div, .letter .eventsheet.quarters #roster-and-history table th.position div {
|
||||
transform: none;
|
||||
text-align: center;
|
||||
}
|
||||
.letter .eventsheet.quarters #roster-and-history #defense-pane .slot-set.pos-p {
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
table tr td.position-capability:not(:empty), #roster-and-history table tr td.position-capability:not(:empty), table tr #roster-and-history .position-capability.position:not(:empty), #roster-and-history table tr .position-capability.position:not(:empty) {
|
||||
color: var(--color-grey-700);
|
||||
background-color: var(--color-grey-200);
|
||||
}
|
||||
|
||||
table tr td.position-capability:empty, #roster-and-history table tr td.position-capability:empty, table tr #roster-and-history .position-capability.position:empty, #roster-and-history table tr .position-capability.position:empty {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
table tr td.is-present-checkbox, #roster-and-history table tr td.is-present-checkbox, table tr #roster-and-history .is-present-checkbox.position, #roster-and-history table tr .is-present-checkbox.position {
|
||||
font-size: 0.5em;
|
||||
text-align: center;
|
||||
color: white;
|
||||
/* text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000,
|
||||
1px 1px 0 #000; */
|
||||
}
|
||||
|
||||
td.is-present-checkbox.available-status-code-0 > span, #roster-and-history .is-present-checkbox.available-status-code-0.position > span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
td.is-present-checkbox.available-status-code-None > span, #roster-and-history .is-present-checkbox.available-status-code-None.position > span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#front-cover {
|
||||
border: solid 1px black;
|
||||
}
|
||||
#front-cover Header {
|
||||
font-family: "Helvetica Now";
|
||||
font-weight: 600;
|
||||
background-color: #323669;
|
||||
color: white;
|
||||
display: inline-flex;
|
||||
border: none;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
#front-cover Header .title {
|
||||
display: grid;
|
||||
font-family: "Futura Now";
|
||||
flex-grow: 1;
|
||||
align-content: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
#front-cover Header .homeaway {
|
||||
font-weight: 800;
|
||||
font-size: xx-large;
|
||||
}
|
||||
#front-cover Header .game-number, #front-cover Header .homeaway {
|
||||
display: grid;
|
||||
align-content: center;
|
||||
}
|
||||
#front-cover Header .game-number {
|
||||
font-size: large;
|
||||
font-stretch: extra-condensed;
|
||||
font-weight: 700;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
#front-cover > div {
|
||||
width: inherit;
|
||||
}
|
||||
#front-cover th {
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
#front-cover th, #front-cover td, #front-cover #roster-and-history .position, #roster-and-history #front-cover .position {
|
||||
font-family: "Futura Now";
|
||||
border: solid 0.5px grey;
|
||||
}
|
||||
#front-cover .conjuction {
|
||||
text-align: center;
|
||||
font-family: "Futura Now";
|
||||
text-transform: none;
|
||||
}
|
||||
#front-cover .head-to-head {
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#front-cover .opponent, #front-cover .team {
|
||||
text-align: center;
|
||||
font-weight: 800;
|
||||
font-size: xx-large;
|
||||
align-items: center;
|
||||
font-family: "Pacifico";
|
||||
text-transform: none;
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
}
|
||||
#front-cover .opponent img, #front-cover .team img {
|
||||
height: 115px;
|
||||
}
|
||||
#front-cover .opponent div:has(.name), #front-cover .team div:has(.name) {
|
||||
flex-grow: 1;
|
||||
}
|
||||
#front-cover .opponent name {
|
||||
text-align: left;
|
||||
}
|
||||
#front-cover .team name {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=eventsheet.css.map */
|
||||
1
src/public/css/eventsheet.css.map
Normal file
1
src/public/css/eventsheet.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["../../scss/eventsheet.scss"],"names":[],"mappings":";AAAQ;AACA;AACA;AACA;AACA;AACA;AAER;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIF;AACA;EACE;IACE;;EAEF;IACE;;EAEF;IACE;IACA;;;AAIJ;AACA;EACE;IACE;;EAEF;IACE;;EAEF;IACE;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;;AAGF;AACA;EAA+B;EAAc;;;AAC7C;EAA+B;EAAc;;;AAC7C;EAA+B;EAAc;;;AAE7C;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;;AAEF;EACE;EACA;;AAEA;EACE;;AAMkB;EAChB;;AAGiB;EACjB;;;AAOR;EACE;;;AAGF;EACE;;;AAGF;EACE;;AACA;EACE;;AACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAGF;EACE;;AAGF;EACE;;;AAMN;EACI;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;;;AAIN;EACE;;;AAGF;EACE;EACE;EACA;EACA;EACA;;AACA;EACE;;;AAIN;EACE;EACE;EACA;EACA;EACA;;AACA;EACE;;;AAIN;EACE;EAEA;EAEA;;AAEA;EACE;EACA;EACA;EACA;;AACA;EACE;;AAIJ;EACE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;;AAMN;EACE;EACA;EACA;;AAGF;EAEE;;AAGF;AACE;;AACA;EACE;;AACA;EACE;;AAGJ;EACE;;AAEF;EACE;EACA;EACA;EACA;EACA;;AAKF;EAEA;;AAIA;EACE;EACA;;AAEF;EACE;;AAKF;EACE;;AACA;EACE;;AAGJ;EACE;;AAGF;EACE;;;AAMJ;EACE;;AAEF;EACE;;AACA;EACE;;;AAOJ;EACE;EACA;EACA;EACA,qBACE;;AAIJ;EACE;;AAGF;EACE;;AAGF;AACE;EACA;EACA;EACA;AACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGE;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;;AACA;EACE;;AAQJ;EACE;EACA;EACA;;AAEF;EACE;;;AAON;EACE;;AAGF;EACE;;AAIA;EACE;EACA;;AAMF;EACE;;AAGF;EAOE;EACA;EACA;EACA;;AATA;EACE;;AACA;EACE;;;AAcV;EACE;;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;;AAIA;EACE;;AAGF;EACE;EACA;EACA;;AAEE;EACE;;AAEM;EACN;;AAEF;EACA;EACA;EACA;;AAQA;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AAoCZ;EACE;EAEA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EAIE;EACA;EACA;;AALA;EACE;;AAKF;EACE;;AACA;EACE;;;AASV;EACE;AACA;EACA;EAEA;EACA;AACA;;AAEA;EACE;EACA;;AAGF;EACE;;AACA;EACE;;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAEF;EACE;;;AAGF;EACE;;;AAKF;EACE;;AAME;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EAEE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;AAEF;AACE;EACA;EACA;AACA;;AAIA;EACA;;AAKF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAOF;EACE;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EAEE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACI;EACA;EACA;EACA;EACA;;AAEJ;EACE;;AACA;EACE;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EAEE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAMN;EACE;EACA;EACA;;AAEF;EACE;;AAIJ;EACE;EACA;EACA;;AAEF;EACE;EACA;EACA;;AAGF;EAEE;;AAGF;EACE;EACA;;AAEA;EACE;AACA;EACA;EACA;EACA;;AAQJ;EACE;;AAGF;EACE;;AAGa;EACb;;;AAMF;EACE;;AAEF;EACE;;AAEF;EACE;;AAIA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EAQE;EACA;;AARA;EACE;;AACA;EACE;EACA;;AAWJ;EACE;EACA;;AAQJ;EACE;;;AAKN;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;AACA;AAAA;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAIJ;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE","file":"eventsheet.css"}
|
||||
71
src/public/css/lineup.css
Normal file
71
src/public/css/lineup.css
Normal file
@@ -0,0 +1,71 @@
|
||||
@import url("/font/bootstrap-icons.css");
|
||||
|
||||
:root {
|
||||
--bc-text-muted: #6c757d;
|
||||
}
|
||||
|
||||
#label_13_5_0 span {
|
||||
display: none !important;
|
||||
}
|
||||
#label_13_5_0:after {
|
||||
font-family: FontAwesome;
|
||||
content: "\f005";
|
||||
}
|
||||
.lineup-slot .Panel-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.lineup-slot .Panel-cell:has(.drag-handle) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.lineup-slot [class*="availability-status-code"]::before {
|
||||
padding-right: 6px;
|
||||
font-family: "bootstrap-icons";
|
||||
}
|
||||
|
||||
.lineup-slot .availability-status-code-1::before {
|
||||
content: "\F26A";
|
||||
color: var(--bs-success);
|
||||
}
|
||||
|
||||
.lineup-slot .availability-status-code-2::before {
|
||||
content: "\F504";
|
||||
color: var(--bs-primary);
|
||||
}
|
||||
|
||||
.lineup-slot .availability-status-code-0::before {
|
||||
content: "\F622";
|
||||
color: var(--bs-danger);
|
||||
}
|
||||
|
||||
.lineup-slot .availability-status-code-null::before {
|
||||
content: "\F505";
|
||||
color: var(--bs-secondary);
|
||||
}
|
||||
|
||||
.lineup-slot .lastname {
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.lineup-slot .jerseynumber {
|
||||
text-transform: uppercase;
|
||||
font-weight: light;
|
||||
color: var(--bc-text-muted);
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.lineup-slot .jerseynumber::before {
|
||||
content: " - ";
|
||||
}
|
||||
|
||||
/* if lineup_entry.availabilityStatusCode == 2
|
||||
i.bi.bi-question-circle-fill.text-info.u-spaceRightXs
|
||||
else if lineup_entry.availabilityStatusCode == 1
|
||||
i.bi.bi-check-circle-fill.text-success.u-spaceRightXs
|
||||
else if lineup_entry.availabilityStatusCode == 0
|
||||
i.bi.bi-x-circle-fill.text-danger.u-spaceRightXs
|
||||
else
|
||||
i.bi.bi-question-circle.u-spaceRightXs */
|
||||
@@ -1,5 +1,31 @@
|
||||
/* These styles are generated from project.scss. */
|
||||
@import url("https://fonts.googleapis.com/css2?family=Open+Sans&display=swap");
|
||||
@import url("../fonts/helvetica-now/stylesheet.css");
|
||||
header.Header {
|
||||
background: #323669;
|
||||
padding: 8px 0;
|
||||
box-shadow: 0 4px 0 rgba(0, 0, 25, 0.1);
|
||||
border-bottom: 1px solid #d6d6d6;
|
||||
color: white;
|
||||
}
|
||||
.Header-bannerLogo, .Header-bannerTitle {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.Header-bannerLogo img {
|
||||
height: 36px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.Header-bannerTitle {
|
||||
font-family: "Helvetica", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
color: white;
|
||||
font-size: 28px;
|
||||
}
|
||||
.alert-debug {
|
||||
color: black;
|
||||
background-color: white;
|
||||
@@ -55,3 +81,17 @@
|
||||
border-color: #1b73bc;
|
||||
background-color: #1b73bc;
|
||||
}
|
||||
|
||||
.benchcoach-nav {
|
||||
background-color: #323669;
|
||||
margin-bottom: 2em;
|
||||
padding: 0.5em;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.benchcoach-nav h3 {
|
||||
font-family: "Helvetica Now";
|
||||
font-weight: bolder;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
612
src/public/js/eventlineup.js
Normal file
612
src/public/js/eventlineup.js
Normal file
@@ -0,0 +1,612 @@
|
||||
const lineupChangedEvent = new Event('bc:lineupChanged')
|
||||
|
||||
document.querySelectorAll('.event-lineup').forEach(lineup=>{
|
||||
lineup.addEventListener('bc:lineupChanged', (evt)=>{
|
||||
console.log(`lineup changed`, evt.target)
|
||||
const lineup = evt.target
|
||||
colorPositions(lineup)
|
||||
lineup.querySelectorAll(".lineup-slot").forEach((slot, i) => {
|
||||
const lineup_segment = determineLineupSegment(slot)
|
||||
if (lineup_segment != 'bench' && lineup_segment != 'out'){
|
||||
slot.querySelector("input[name=sequence]").value = i;
|
||||
} else {
|
||||
slot.querySelector("input[name=sequence]").value = null;
|
||||
}
|
||||
updateFlagInput(slot)
|
||||
updatePositionInput(slot)
|
||||
});
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
document.querySelectorAll('[data-control=popup]').forEach(popup_control=>{
|
||||
console.log(popup_control)
|
||||
popup_control.addEventListener('click', (evt)=>{
|
||||
const popup = evt.target.closest(".Popup")
|
||||
const to_open = popup.querySelector(".Popup-toggle").dataset.open
|
||||
popup.querySelectorAll(`[data-popup=${to_open}]`).forEach(popup_container => {
|
||||
console.log(evt, evt.target, popup, popup_container)
|
||||
popup_container.classList.toggle('is-open')
|
||||
evt.stopPropagation()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
document.querySelectorAll('.position-label-flags input[type="checkbox"]').forEach(flagCheckbox => {
|
||||
const lineup = flagCheckbox.closest('.event-lineup')
|
||||
flagCheckbox.addEventListener('click', ()=>{lineup.dispatchEvent(lineupChangedEvent)})
|
||||
})
|
||||
|
||||
function onPositionSelectChange(elem) {
|
||||
elem.querySelectorAll("option").forEach((option) => {
|
||||
if (option.innerText.trim() == elem.value) {
|
||||
option.setAttribute("selected", "selected");
|
||||
} else {
|
||||
option.removeAttribute("selected");
|
||||
}
|
||||
});
|
||||
const lineup = elem.closest('.event-lineup')
|
||||
lineup.dispatchEvent(lineupChangedEvent)
|
||||
elem
|
||||
}
|
||||
|
||||
function colorPositions(lineup) {
|
||||
const class_none = "u-colorNegative"
|
||||
const class_good = "u-colorPositive"
|
||||
const class_over = "u-colorHighlight"
|
||||
|
||||
lineup.querySelectorAll('.position-status').forEach(
|
||||
position_status=>{
|
||||
position_status.classList.remove(class_over, class_good, class_none);
|
||||
const occurences = lineup.querySelectorAll(`.position-select-box option:checked[value="${position_status.dataset.value}"]`)
|
||||
switch (occurences.length){
|
||||
case 0:
|
||||
position_status.classList.add(class_none)
|
||||
break;
|
||||
case 1:
|
||||
position_status.classList.add(class_good)
|
||||
break;
|
||||
default:
|
||||
position_status.classList.add(class_over)
|
||||
break;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function initFlagsCheckboxes(){
|
||||
document.querySelectorAll(".lineup-slot").forEach(lineup_slot=>{
|
||||
const possible_flags = ['DHd', 'DRd']
|
||||
const flags_string = lineup_slot.querySelector("input[name=flags]")?.value
|
||||
const flags = flagSetFromString(flags_string)
|
||||
|
||||
possible_flags.forEach(flag=>{
|
||||
if (flags.has(flag)){
|
||||
lineup_slot.querySelector(`input[type=checkbox][name=${flag}]`).checked = true
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
const flagSetFromString = (s) => {
|
||||
if (!s) {return new Set()}
|
||||
const array = s.split(',').map(item=>item.trim())
|
||||
return new Set(array)
|
||||
}
|
||||
|
||||
const flagSetFromSlot = (slot) => {
|
||||
const inputs = slot.querySelectorAll('.position-label-flags input[type=checkbox]:checked')
|
||||
const set = new Set()
|
||||
inputs.forEach(i=>set.add(i.name))
|
||||
return set
|
||||
}
|
||||
|
||||
const flagSetToString = (set) => {
|
||||
return Array.from(set).join(",");
|
||||
}
|
||||
|
||||
const updateFlagInput = (slot) => {
|
||||
const flags = flagSetFromSlot(slot)
|
||||
const lineup_segment = slot.closest('.lineup-segment')
|
||||
lineup_segment.classList.contains('position-only') ? flags.add('PO') : flags.delete('PO')
|
||||
slot.querySelector('input[name="flags"]').value = flagSetToString(flags);
|
||||
}
|
||||
|
||||
const updatePositionInput = (slot) => {
|
||||
const selected_position = slot.querySelector(".position-select-box option:checked");
|
||||
const lineup_segment = slot.closest('.lineup-segment')
|
||||
|
||||
if (selected_position && selected_position.text != "--" && !lineup_segment.classList.contains('bench')) {
|
||||
slot.querySelector("input[name=label]").value = selected_position.text;
|
||||
} else {
|
||||
slot.querySelector("input[name=label]").value = null;
|
||||
}
|
||||
}
|
||||
|
||||
const determineLineupSegment = (slot) => {
|
||||
const lineup_segments = ['starting', 'position-only', 'bench', 'out']
|
||||
const lineup_segment = slot.closest('.lineup-segment')
|
||||
const classList = Array.from(lineup_segment.classList)
|
||||
const segments = classList.filter(c=>lineup_segments.includes(c))
|
||||
|
||||
if (segments.length == 1) {
|
||||
return segments[0]
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function openAvailabilityReminderModal (el, team_id, event_id) {
|
||||
const url = `/${team_id}/event/${event_id}/modal-confirm-availability-reminders/`
|
||||
const form = el.closest('form')
|
||||
const form_data = new FormData (form)
|
||||
|
||||
fetch(url)
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.text();
|
||||
} else {
|
||||
return Promise.reject(response.text());
|
||||
}
|
||||
})
|
||||
.then((html) => {
|
||||
const parser = new DOMParser()
|
||||
const modal = parser.parseFromString(html, 'text/html')
|
||||
const modal_node = modal.firstElementChild.querySelector('#modal')
|
||||
modal_node.classList.add('is-open')
|
||||
const modal_node_accept = modal.querySelector('Button[data-confirm=yes]')
|
||||
const checked = Array.from(el.querySelectorAll('input:checked')).map
|
||||
const body = document.querySelector('body')
|
||||
body.appendChild(modal_node)
|
||||
modal_node_accept.addEventListener(
|
||||
"click", ()=>{
|
||||
// const memberIds = form_data.getAll('memberId')
|
||||
const csrf_token = form_data.get('csrfToken')
|
||||
const selected_status_codes = Array.from(document.querySelectorAll('input:checked')).map(e=>e.value)
|
||||
const slots = Array.from(document.querySelectorAll('.lineup-slot')).filter(
|
||||
slot =>{
|
||||
const slot_status_code = slot.querySelector('input[name=availabilityStatusCode]').value
|
||||
return selected_status_codes.includes(slot_status_code)
|
||||
}
|
||||
)
|
||||
const memberIds = slots.map(
|
||||
slot => slot.querySelector('input[name=memberId]').value
|
||||
)
|
||||
|
||||
console.log("sending reminders", el, event_id, memberIds, csrf_token)
|
||||
sendAvailabilityReminder(el, event_id, memberIds, csrf_token)
|
||||
body.removeChild(modal_node)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function confirmModal(el, prompt, fn, options) {
|
||||
const url = "/modal-confirm"
|
||||
const params = new URLSearchParams(prompt)
|
||||
url.search = params.toString()
|
||||
fetch(url+"?"+params.toString(), {method:"GET"})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.text();
|
||||
} else {
|
||||
return Promise.reject(response.text());
|
||||
}
|
||||
})
|
||||
.then((html) => {
|
||||
const parser = new DOMParser()
|
||||
const modal = parser.parseFromString(html, 'text/html')
|
||||
const modal_node = modal.firstElementChild.querySelector('#modal')
|
||||
modal_node.classList.add('is-open')
|
||||
const modal_node_accept = modal.querySelector('Button[data-confirm=yes]')
|
||||
const body = document.querySelector('body')
|
||||
body.appendChild(modal_node)
|
||||
modal_node_accept.addEventListener("click", ()=>{fn(modal_node, options)})
|
||||
})
|
||||
}
|
||||
|
||||
const toggleShowAndHideLoading = (el) => {
|
||||
console.log(el)
|
||||
el.querySelectorAll('.hideOnLoading').forEach((element)=>{
|
||||
element.classList.add('u-hidden')
|
||||
})
|
||||
el.querySelectorAll('.showOnLoading').forEach((element)=>{
|
||||
element.classList.remove('u-hidden')
|
||||
})
|
||||
}
|
||||
|
||||
const completeLoad = (el, success) => {
|
||||
el.querySelectorAll('.hideOnLoading, .showOnLoading, .showOnFailure, .showOnSuccess').forEach((element)=>{
|
||||
element.classList.add('u-hidden')
|
||||
})
|
||||
if (success) {
|
||||
el.querySelectorAll('.showOnSuccess').forEach((element) => {
|
||||
element.classList.remove('u-hidden')
|
||||
})
|
||||
} else {
|
||||
el.querySelectorAll('.showOnFailure').forEach((element) => {
|
||||
element.classList.remove('u-hidden')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function submitClearLineup(modal, options){
|
||||
console.log('clearing lineup...')
|
||||
toggleShowAndHideLoading(modal)
|
||||
const {team_id, event_id, event_lineup_id} = options
|
||||
const url = `/${team_id}/event/${event_id}/lineup/${event_lineup_id}/delete`
|
||||
const form = document.querySelector(`#event-lineup-${event_id} form`);
|
||||
const data = new FormData(form);
|
||||
const memberIds = data.getAll('memberId')
|
||||
console.log(url)
|
||||
fetch(url, {method:"POST", body: JSON.stringify({memberIds, event_id}), headers: {"Content-Type": "application/json"}})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
completeLoad(modal, true);
|
||||
return response.text();
|
||||
} else {
|
||||
completeLoad(modal, false);
|
||||
return Promise.reject(response.text());
|
||||
}
|
||||
})
|
||||
.finally(()=>{
|
||||
setTimeout(function (){
|
||||
location.reload()
|
||||
}, 500)
|
||||
});//refresh page
|
||||
}
|
||||
|
||||
function submitResetAvailabilities(modal, options){
|
||||
const {team_id, event_id} = options
|
||||
toggleShowAndHideLoading(modal)
|
||||
const url = `/${team_id}/event/${event_id}/reset_availabilities`
|
||||
const form = document.querySelector(`#event-lineup-${event_id} form`);
|
||||
const data = new FormData(form);
|
||||
const memberIds = data.getAll('memberId')
|
||||
|
||||
console.log('submitting...', url)
|
||||
fetch(url, {method:"POST", body: JSON.stringify({memberIds, event_id}), headers: {"Content-Type": "application/json"}})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
completeLoad(modal, true);
|
||||
return response.text();
|
||||
} else {
|
||||
completeLoad(modal, false);
|
||||
return Promise.reject(response.text());
|
||||
}
|
||||
})
|
||||
.finally(()=>{
|
||||
setTimeout(function (){
|
||||
location.reload()
|
||||
}, 500)
|
||||
});//refresh page
|
||||
}
|
||||
|
||||
function emailModal(el, url) {
|
||||
form = el.closest("form");
|
||||
console.log(form)
|
||||
data = new FormData(form);
|
||||
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
body: data,
|
||||
headers: {
|
||||
'CSRF-Token': data.get('_csrf')
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.text();
|
||||
} else {
|
||||
return Promise.reject(response.text());
|
||||
}
|
||||
})
|
||||
.then((html) => {
|
||||
const parser = new DOMParser()
|
||||
const email_modal = parser.parseFromString(html, 'text/html')
|
||||
const email_modal_node = email_modal.firstElementChild.querySelector('#modal')
|
||||
email_modal_node.setAttribute('id', `lineup-email-data-${data.get('event_lineup_id')}`)
|
||||
const body = document.querySelector('body')
|
||||
email_modal_node.classList.add('is-open')
|
||||
body.appendChild(email_modal_node)
|
||||
tinymce.init({
|
||||
selector:`textarea#email-editor`,
|
||||
content_css:"/css/application.css",
|
||||
plugins: 'image',
|
||||
menubar: false,
|
||||
toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | outdent indent | image',
|
||||
paste_data_images: true,
|
||||
statusbar:false})
|
||||
tinymce.remove();
|
||||
});
|
||||
}
|
||||
|
||||
async function submitEventLineup(form, event) {
|
||||
event.preventDefault();
|
||||
console.log(event)
|
||||
teamsnap_icon = form.querySelector("#teamsnap-icon");
|
||||
waiting_icon = form.querySelector("#waiting-icon");
|
||||
success_icon = form.querySelector("#success-icon");
|
||||
failure_icon = form.querySelector("#failure-icon");
|
||||
data = new FormData(form);
|
||||
console.log(form)
|
||||
url = form.attributes.action.textContent;
|
||||
toggleShowAndHideLoading(form)
|
||||
await fetch(url, {
|
||||
method: "POST",
|
||||
body: data,
|
||||
headers: {
|
||||
'CSRF-Token': data.get('_csrf')
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
|
||||
return response.text();
|
||||
} else {
|
||||
return Promise.reject(response.text());
|
||||
}
|
||||
})
|
||||
.then((text) => {
|
||||
event.submitter.blur()
|
||||
completeLoad(form, true)
|
||||
console.log(text);
|
||||
})
|
||||
.catch((error) => {
|
||||
event.submitter.blur()
|
||||
completeLoad(form, false)
|
||||
console.log(error);
|
||||
})
|
||||
.finally(()=>{location.reload()});//refresh page
|
||||
setTimeout(() => {
|
||||
[waiting_icon, success_icon, failure_icon].forEach(e=>e.classList.add('u-hidden'))
|
||||
teamsnap_icon.classList.remove('u-hidden')
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
async function copyEmailTable (element) {
|
||||
// range=document.createRange();
|
||||
// window.getSelection().removeAllRanges();
|
||||
// // range.selectNode(document.querySelector('.Modal').querySelector('.Modal-body'));
|
||||
// tinymce.activeEditor.selection.select(tinymce.activeEditor.getBody());
|
||||
// // window.getSelection().addRange(range);
|
||||
// document.execCommand('copy');
|
||||
// window.getSelection().removeAllRanges();
|
||||
const emailStyle = `
|
||||
<style>.lineup-email {
|
||||
font-family: "Helvetica", sans-serif;
|
||||
}
|
||||
.lineup-email .title-cell {
|
||||
font-weight: bold;
|
||||
background-color: #323669;
|
||||
color: #fff;
|
||||
padding: 2px 5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.lineup-email .title-cell.out {
|
||||
background-color: rgb(244, 199, 195);
|
||||
color: black;
|
||||
}
|
||||
.lineup-email .sequence-cell {
|
||||
font-weight: bold;
|
||||
padding: 1px 5px;
|
||||
text-align: left;
|
||||
}
|
||||
.lineup-email .name-cell {
|
||||
width: 200px;
|
||||
text-align: left;
|
||||
}
|
||||
.lineup-email .position-label-cell {
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.Panel .Panel {
|
||||
border: none;
|
||||
margin: 0;
|
||||
}</style>
|
||||
`
|
||||
// html_content = emailStyle+tinymce.activeEditor.getContent()
|
||||
// console.log(html_content)
|
||||
const table = element.closest('form').querySelector('.lineup-table table')
|
||||
|
||||
// navigator.clipboard.write(
|
||||
// [new ClipboardItem(
|
||||
// {
|
||||
// // 'text/plain': new Blob([tinymce.activeEditor.getContent({format: "text"})], {type: 'text/plain'}),
|
||||
// 'text/plain': new Blob([table.innerText], {type: 'text/plain'}),
|
||||
// 'text/html': new Blob([emailStyle+table.outerHTML], {type: 'text/html'})
|
||||
// })
|
||||
// ])
|
||||
|
||||
window.getSelection().removeAllRanges();
|
||||
var range = document.createRange();
|
||||
range.selectNode(table);
|
||||
window.getSelection().addRange(range);
|
||||
document.execCommand("copy");
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
|
||||
moveToLineupSegment = (slot, segment_name) => {
|
||||
if (!slot.classList.contains('lineup-slot')) {
|
||||
slot = slot.closest('.lineup-slot')
|
||||
if (!slot) {return}
|
||||
}
|
||||
|
||||
const current_lineup_segment = slot.closest('.lineup-segment')
|
||||
|
||||
if (current_lineup_segment.classList.contains(segment_name)) {
|
||||
return
|
||||
}
|
||||
|
||||
const lineup = slot.closest('.event-lineup')
|
||||
const newParent = lineup.querySelector(`.lineup-segment.${segment_name} .slot-set`)
|
||||
newParent.append(slot)
|
||||
}
|
||||
|
||||
function initSlots () {
|
||||
document.querySelectorAll('.lineup-slot').forEach(slot=>{
|
||||
if (slot.dataset.initialLineupSegment) {
|
||||
moveToLineupSegment(slot, slot.dataset.initialLineupSegment)
|
||||
slot.removeAttribute('data-initial-lineup-segment')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
addToStarting = (el) => {
|
||||
const slot = el.closest('.lineup-slot')
|
||||
|
||||
this.blur()
|
||||
}
|
||||
|
||||
removeToBench = (el) => {
|
||||
const slot = el.closest('.lineup-slot')
|
||||
|
||||
this.blue()
|
||||
}
|
||||
|
||||
function insertLineup(direction, teamId, eventId, element) {
|
||||
const currentUrl = window.location.href;
|
||||
let search_params
|
||||
if (Number(direction) > 0) {
|
||||
search_params = new URLSearchParams({
|
||||
page_size:1,
|
||||
index: 1
|
||||
})
|
||||
} else if (Number(direction) < 0) {
|
||||
search_params = new URLSearchParams({
|
||||
page_size:1,
|
||||
index: -1
|
||||
})
|
||||
} else {throw new Error("Needs to be a negative number or a positive number")}
|
||||
|
||||
fetch(`/${teamId}/event/${eventId}/lineup/adjacent?`+search_params, {
|
||||
method: "GET"
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.text();
|
||||
} else {
|
||||
return Promise.reject(response.text());
|
||||
}
|
||||
})
|
||||
.then((html) =>{
|
||||
const parser = new DOMParser();
|
||||
const new_lineup_doc = parser.parseFromString(html, 'text/html')
|
||||
const new_lineup_doc_node = new_lineup_doc.firstElementChild.querySelector('.event-lineup')
|
||||
const main = document.querySelector("main")
|
||||
const new_csrf_token = new_lineup_doc.querySelector('form input[name=csrfToken]').value
|
||||
|
||||
direction > 0 ? main.appendChild(new_lineup_doc_node) : main.insertBefore(new_lineup_doc_node, element.closest('[id*=event-lineup]'))
|
||||
|
||||
main.classList.remove(...main.classList)
|
||||
main.classList.add('scroll-horizontal', 'u-spaceSidesSm', 'u-flex')
|
||||
|
||||
Array.from(document.querySelectorAll(".event-lineup")).forEach((bcLineup) => {
|
||||
// main.classList.remove('.u-max1200', 'u-flexExpandSides')
|
||||
bcLineup.classList.remove('u-spaceSidesNone', 'u-sm-spaceSidesAuto')
|
||||
}
|
||||
)
|
||||
|
||||
Array.from(document.querySelectorAll(".event-lineup .Panel")).forEach((bcLineupPanel) => {
|
||||
bcLineupPanel.classList.remove('Panel--full')
|
||||
})
|
||||
for (input of document.querySelectorAll("form input[name=csrfToken]")){
|
||||
input.value = new_csrf_token
|
||||
}
|
||||
|
||||
initPage();
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function initPage (){
|
||||
initSlots();
|
||||
initFlagsCheckboxes();
|
||||
for (bcLineup of document.querySelectorAll(".event-lineup")) {
|
||||
bcLineup.dispatchEvent(lineupChangedEvent)
|
||||
options = {
|
||||
animation: 150,
|
||||
handle: ".Panel-cell:has(.drag-handle), .Panel-cell:has(.sequence)",
|
||||
ghostClass: "ghost",
|
||||
group: {
|
||||
name: bcLineup.id,
|
||||
put: [bcLineup.id],
|
||||
pull: [bcLineup.id],
|
||||
},
|
||||
onAdd: function (/**Event*/ evt) {
|
||||
bcLineup.dispatchEvent(lineupChangedEvent)
|
||||
},
|
||||
onUpdate: function (/**Event*/ evt) {
|
||||
bcLineup.dispatchEvent(lineupChangedEvent)
|
||||
},
|
||||
};
|
||||
new Sortable.create(bcLineup.querySelector(".lineup-segment.starting .slot-set"), options);
|
||||
new Sortable.create(bcLineup.querySelector(".lineup-segment.position-only .slot-set"), options);
|
||||
new Sortable.create(bcLineup.querySelector(".lineup-segment.bench .slot-set"), {...options, sort:false});
|
||||
new Sortable.create(bcLineup.querySelector(".lineup-segment.out .slot-set"), {...options, sort:false, group:{...options.group, put:[]}});
|
||||
}
|
||||
|
||||
// for (lineup_slot of document.querySelectorAll(".lineup-segment.out .lineup-slot")) {
|
||||
// const cells = lineup_slot.querySelectorAll('.Panel-cell:has(.sequence), .Panel-cell:has(.drag-handle), .Panel-cell:has(.position-select-box), button:has(+.position-label-flags)')
|
||||
// Array.from(cells).forEach(cell=>{
|
||||
// cell.classList.add('u-hidden')
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
function mailToLink(el, protocol) {
|
||||
const {to, bcc} = el.dataset
|
||||
const subject = document.getElementById('email-subject').value
|
||||
const email_body = document.getElementById('email-editor').value
|
||||
const url = `${protocol}://compose?recipient=${to}&bcc=${bcc}&subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(email_body)}`
|
||||
console.log(url)
|
||||
// location.href=`mailto:${to}${params}`
|
||||
const windowRef = window.open(url, '_blank');
|
||||
windowRef.focus();
|
||||
}
|
||||
|
||||
function sendAvailabilityReminder(element, eventId, memberIds, csrf_token) {
|
||||
const icon = element.querySelector('svg')
|
||||
const button_text = element.querySelector('span')
|
||||
icon.classList.toggle('u-hidden')
|
||||
button_text.classList.toggle('u-hidden')
|
||||
const loader = '<span class="PulseAnimation"><span class="PulseAnimation-dot"></span><span class="PulseAnimation-dot"></span><span class="PulseAnimation-dot"></span></span>'
|
||||
const loader_node = new DOMParser().parseFromString(loader, "text/html").firstChild.querySelector('span');
|
||||
element.appendChild(loader_node)
|
||||
element.blur();
|
||||
|
||||
const data = new FormData();
|
||||
const url = "../availability_reminders"
|
||||
data.append('eventId', eventId)
|
||||
for (var i = 0; i < memberIds.length; i++) {
|
||||
data.append('memberIds[]', memberIds[i]);
|
||||
}
|
||||
console.log(data)
|
||||
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
body: data,
|
||||
headers: {
|
||||
'CSRF-Token': csrf_token
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
console.log(response)
|
||||
return response.text();
|
||||
} else {
|
||||
return Promise.reject(response.text());
|
||||
}
|
||||
})
|
||||
.finally(()=>{
|
||||
loader_node.remove()
|
||||
icon.classList.toggle('u-hidden')
|
||||
button_text.classList.toggle('u-hidden')
|
||||
})
|
||||
|
||||
console.log(element, eventId, memberIds)
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initPage)
|
||||
31
src/public/js/opponent.js
Normal file
31
src/public/js/opponent.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// THIS DOESN'T WORK, CORS ERRORS!!
|
||||
// const form = document.querySelector("form[name=upload-opponent-logo]")
|
||||
|
||||
// form.querySelector("button").addEventListener('click', function() {
|
||||
// form.requestSubmit();
|
||||
// })
|
||||
|
||||
// form.addEventListener('submit', async function(e) {
|
||||
// e.preventDefault()
|
||||
// console.log(e.target)
|
||||
// data = new FormData(e.target)
|
||||
// // file = new File(data.file.buffer, data.filename, {
|
||||
// // type: "image/png",
|
||||
// // });
|
||||
// teamsnap.TeamSnap("http://localhost:8080/https://apiv3.teamsnap.com")
|
||||
// if (teamsnap.hasSession()) {
|
||||
// const token = sessionStorage.getItem('teamsnap.authToken')
|
||||
// teamsnap.auth(token);
|
||||
// teamsnap.loadCollections(async function(err) {
|
||||
// if (err) {
|
||||
// console.log(err)
|
||||
// alert('Error loading TeamSnap SDK');
|
||||
// return;
|
||||
// }
|
||||
// const team_medium = await teamsnap.createTeamMedium(data)
|
||||
// await teamsnap.uploadTeamMedium(team_medium)
|
||||
// console.log('Uploaded')
|
||||
// });
|
||||
// }
|
||||
|
||||
// })
|
||||
23
src/public/manifest.json
Normal file
23
src/public/manifest.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"short_name": "BenchCoach",
|
||||
"name": "BenchCoach: An assitant for TeamSnap",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/media/benchcoach.svg",
|
||||
"type": "image/svg+xml",
|
||||
"sizes": "800x800"
|
||||
},
|
||||
{
|
||||
"src": "/media/apple-touch-icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "120x120 180x180 167x167 152x152 80x80 120x120 58x58 87x87 76x76 114x114"
|
||||
}
|
||||
],
|
||||
"id": "/",
|
||||
"start_url": "/",
|
||||
"background_color": "#323669",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"theme_color": "#323669",
|
||||
"description": "An assitant for TeamSnap"
|
||||
}
|
||||
3
src/public/media/teamsnap_star.svg
Normal file
3
src/public/media/teamsnap_star.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="139" height="134" viewBox="0 0 139 134" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path id="Path" d="M 41.293747 133.837494 L 13.012501 112.212502 C 21.93251 102.073471 31.282745 92.321198 41.037498 82.982498 C 44.893745 79.208748 47.327499 76.815002 48.327499 75.802505 C 45.186249 75.316254 36.220001 73.280006 21.434998 69.702499 C 10.762501 67.118752 3.7425 65.207504 0.365002 63.978752 L 12.012501 30.691254 C 28.1175 37.353745 42.595001 44.679993 55.43 52.706245 C 52.435005 32.028748 50.945 15.15126 50.945 2.077515 L 84.220001 2.077515 C 84.220001 11.456238 82.541252 28.460007 79.172501 53.098747 C 81.66375 52.091248 87.087509 49.666245 95.464996 45.791245 C 106.912506 40.678757 117.471252 36.309998 127.139999 32.691254 L 136.790009 66.635002 C 122.783752 69.751251 106.574997 72.809998 88.154999 75.802505 L 110.755005 101.480003 C 115.267502 106.672501 118.842499 110.919998 121.485001 114.205002 L 92.537506 133.175003 L 67.400002 90.792496 C 59.805 104.262497 51.112495 118.615005 41.292503 133.837494"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,11 +1,12 @@
|
||||
var express = require("express");
|
||||
var passport = require("passport");
|
||||
var TeamsnapStrategy = require("passport-teamsnap");
|
||||
|
||||
const express = require("express");
|
||||
const passport = require("passport");
|
||||
const TeamsnapStrategy = require("passport-teamsnap");
|
||||
const {teamsnapCallback} = require('../lib/utils')
|
||||
// const {teamsnap} = require("../app");
|
||||
// Configure the TeamSnap strategy for use by Passport.
|
||||
//
|
||||
// OAuth 2.0-based strategies require a `verify` function which receives the
|
||||
// credential (`accessToken`) for accessing the Facebook API on the user's
|
||||
// credential (`accessToken`) for accessing the TeamSnap API on the user's
|
||||
// behalf, along with the user's profile. The function must invoke `cb`
|
||||
// with a user object, which will be set at `req.user` in route handlers after
|
||||
// authentication.
|
||||
@@ -17,25 +18,21 @@ passport.use(
|
||||
clientSecret: process.env["TEAMSNAP_CLIENT_SECRET"],
|
||||
callbackURL: "/auth/teamsnap/callback",
|
||||
passReqToCallback: true,
|
||||
scope: ["read", "write"],
|
||||
proxy: true
|
||||
},
|
||||
function (req, accessToken, refreshToken, profile, done) {
|
||||
json = JSON.parse(profile._raw);
|
||||
new_profile = { access_token: accessToken };
|
||||
new_profile["id"] = json.collection.items[0].data.filter(
|
||||
(e) => e.name == "id"
|
||||
)[0].value;
|
||||
new_profile["email"] = json.collection.items[0].data.filter(
|
||||
(e) => e.name == "email"
|
||||
)[0].value;
|
||||
new_profile["first_name"] = json.collection.items[0].data.filter(
|
||||
(e) => e.name == "first_name"
|
||||
)[0].value;
|
||||
console.log("LI#35 session is ", req.session);
|
||||
console.log("LI#35 session id is ", req.session.id);
|
||||
async function (req, accessToken, refreshToken, profile, done) {
|
||||
// json = JSON.parse(profile._raw);
|
||||
const new_profile = {
|
||||
access_token: accessToken,
|
||||
};
|
||||
['id', 'email', 'first_name', 'last_name', 'managed_team_ids'].forEach(
|
||||
k => {
|
||||
new_profile[k] = profile.data[0].get(k)
|
||||
})
|
||||
|
||||
req.session.teamsnap_access_token = accessToken;
|
||||
teamsnap.init(process.env["TEAMSNAP_CLIENT_ID"]);
|
||||
teamsnap.auth(accessToken);
|
||||
// teamsnap.enablePersistence();
|
||||
await initTeamsnap(accessToken)
|
||||
return done(null, new_profile);
|
||||
}
|
||||
)
|
||||
@@ -52,19 +49,28 @@ passport.use(
|
||||
// and deserialized.
|
||||
passport.serializeUser(function (user, cb) {
|
||||
process.nextTick(function () {
|
||||
console.log("L#51 serializing user id", user.id);
|
||||
console.log("L#56 serializing user id", user.id);
|
||||
cb(null, {
|
||||
id: user.id,
|
||||
username: user.email,
|
||||
name: user.firstName,
|
||||
email: user.email,
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
accessToken: user.access_token,
|
||||
managed_team_ids: user.managed_team_ids
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
passport.deserializeUser(function (user, cb) {
|
||||
process.nextTick(function () {
|
||||
return cb(null, user);
|
||||
process.nextTick(async function () {
|
||||
console.log("L#68 deserializing user id", user.id);
|
||||
try {
|
||||
await initTeamsnap(user.accessToken)
|
||||
return cb(null, user);
|
||||
} catch (err) {
|
||||
return cb(err)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,7 +85,13 @@ var router = express.Router();
|
||||
* will be sent to the `GET /login/federated/teamsnap` route.
|
||||
*/
|
||||
router.get("/login", function (req, res, next) {
|
||||
res.render("login");
|
||||
// https://stackoverflow.com/a/73056806/20522015
|
||||
returnTo = req.session.returnTo;
|
||||
if (req.user?.accessToken){
|
||||
res.redirect(returnTo || "/");
|
||||
} else {
|
||||
res.render("login", {layout:"layouts/main"});
|
||||
}
|
||||
});
|
||||
|
||||
/* GET /login/federated/teamsnap
|
||||
@@ -107,22 +119,43 @@ router.get(
|
||||
passport.authenticate("teamsnap", function (err, user, info, status) {})
|
||||
);
|
||||
|
||||
router.get("/auth/teamsnap/callback", function (req, res, next) {
|
||||
passport.authenticate("teamsnap", function (err, user, info, status) {
|
||||
if (err) {
|
||||
// do something with the error
|
||||
console.error("error: ", err);
|
||||
}
|
||||
// success
|
||||
console.log("L#105 user is ", user);
|
||||
req.logIn(user, function (err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
router.get(
|
||||
"/auth/teamsnap/callback",
|
||||
passport.authenticate("teamsnap", {
|
||||
successReturnToOrRedirect: "/",
|
||||
failureRedirect: "/login",
|
||||
keepSessionInfo: true,
|
||||
})
|
||||
);
|
||||
|
||||
return res.redirect("/");
|
||||
});
|
||||
})(req, res, next);
|
||||
const initTeamsnap = async (accessToken) => {
|
||||
await teamsnap.auth(accessToken);
|
||||
await teamsnap.loadCollections(teamsnapCallback);
|
||||
await teamsnap.enablePersistence();
|
||||
}
|
||||
|
||||
const ensureLoggedIn = (req, res, next) => {
|
||||
if (!req.isAuthenticated()){
|
||||
req.session.returnTo = req.originalUrl
|
||||
res.redirect("/login");
|
||||
// return next();
|
||||
}
|
||||
else {
|
||||
req.user = req.session.passport.user
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
router.get('/auth/teamsnap/session_storage', (req,res)=>{
|
||||
res.status(200).json({"teamsnap.authToken":req.user?.accessToken})
|
||||
}
|
||||
)
|
||||
|
||||
router.post('/logout', function(req, res, next){
|
||||
req.logout(function(err) {
|
||||
if (err) { return next(err); }
|
||||
res.redirect('/');
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = {router, ensureLoggedIn};
|
||||
78
src/routes/event.js
Normal file
78
src/routes/event.js
Normal file
@@ -0,0 +1,78 @@
|
||||
const express = require("express");
|
||||
const eventsController = require("../controllers/event");
|
||||
const router = express.Router();
|
||||
const tsUtils = require("../lib/utils")
|
||||
const {teamsnapCallback} = require("../lib/utils")
|
||||
const multer = require("multer");
|
||||
const upload = multer()
|
||||
|
||||
// Middleware
|
||||
const loadEvent = (req,res,next) => {
|
||||
const {team_id, event_id} = req.params;
|
||||
const bulkLoadTypes = ["event", "availabilitySummary"]
|
||||
req.promises.push(
|
||||
teamsnap.bulkLoad(
|
||||
{teamId: team_id, types: bulkLoadTypes, scopeTo:'event', event__id:event_id},
|
||||
null,
|
||||
(err, items) => {teamsnapCallback(err, items, {req, source:"loadEvent", method:'bulkLoad'})}
|
||||
)
|
||||
.then(bulkLoadItems=>{
|
||||
const items = tsUtils.groupTeamsnapItems(bulkLoadItems, bulkLoadTypes);
|
||||
req.availabilitySummary = items.availabilitySummaries.find(e=>e.eventId==event_id);
|
||||
req.event = items.events.find(e=>e.id==event_id);
|
||||
}
|
||||
))
|
||||
next();
|
||||
}
|
||||
|
||||
// Middleware
|
||||
const loadEvents = async (req,res,next) => {
|
||||
const {team_id, event_id} = req.params
|
||||
req.timeline = {}
|
||||
await Promise.all(req.promises)
|
||||
const {recent_events, upcoming_events} = req
|
||||
const eventIds = [...recent_events.map(e=>e.id), event_id, ...upcoming_events.map(e=>e.id)]
|
||||
// if (!req.event_lineup){
|
||||
bulkLoadTypes = ['event','eventLineup', 'eventLineupEntry']
|
||||
req.promises.push(
|
||||
teamsnap.bulkLoad(
|
||||
{teamId: team_id, types: bulkLoadTypes, scopeTo:'event', event__id:eventIds},
|
||||
null,
|
||||
(err, items) => {teamsnapCallback(err, items, {req, source:"loadEvents", method:'bulkLoad'})}
|
||||
)
|
||||
.then(items => tsUtils.groupTeamsnapItems(items, bulkLoadTypes))
|
||||
.then(items => {
|
||||
req.timeline.events = items.events;
|
||||
req.timeline.event_lineups = items.eventLineups;
|
||||
req.timeline.event_lineup_entries = items.eventLineupEntries;
|
||||
})
|
||||
)
|
||||
|
||||
req.promises.push(
|
||||
teamsnap.loadAvailabilities(
|
||||
{eventId: eventIds},
|
||||
(err, items) => {teamsnapCallback(err, items, {req, source:"loadEvents", method:'loadAvailabilities'})}
|
||||
).then(availabilities => {
|
||||
req.timeline.availabilities = availabilities
|
||||
}
|
||||
)
|
||||
)
|
||||
// }
|
||||
// else {
|
||||
// // const {event_lineup} = req
|
||||
// }
|
||||
const {event_lineup} = req
|
||||
next();
|
||||
}
|
||||
|
||||
router.use("/:team_id([0-9]+)/event/:event_id([0-9]+)", loadEvent)
|
||||
|
||||
// Routes
|
||||
router.get("/:team_id([0-9]+)/schedule", eventsController.getEvents);
|
||||
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)", eventsController.getEvent);
|
||||
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/availability_reminders", upload.none(), eventsController.sendAvailabilityReminders)
|
||||
|
||||
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/modal-confirm-availability-reminders/", eventsController.confirmModalAvailabilityReminders)
|
||||
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/reset_availabilities",upload.none(), eventsController.submitResetAvailabilities)
|
||||
|
||||
module.exports = {router, loadEvent, loadEvents}
|
||||
63
src/routes/eventlineup.js
Normal file
63
src/routes/eventlineup.js
Normal file
@@ -0,0 +1,63 @@
|
||||
const express = require("express");
|
||||
const eventsLineupController = require("../controllers/eventlineup");
|
||||
const router = express.Router();
|
||||
const tsUtils = require('../lib/utils')
|
||||
const multer = require("multer");
|
||||
const upload = multer()
|
||||
const { doubleCsrfProtection } = require('../middlewares/csrf');
|
||||
const {loadRecentAndUpcomingEvents} = require('../middlewares/bulkload')
|
||||
const {loadEvents} = require('./event')
|
||||
const {teamsnapCallback} = require("../lib/utils")
|
||||
|
||||
|
||||
// Middleware
|
||||
const loadEventLineup = (req,res,next) => {
|
||||
const {team_id, event_id} = req.params
|
||||
if (!req.event_lineup){
|
||||
bulkLoadTypes = ['eventLineup', 'eventLineupEntry']
|
||||
req.promises.push(
|
||||
teamsnap.bulkLoad(
|
||||
{teamId: team_id, types: bulkLoadTypes, scopeTo:'event', event__id:event_id},
|
||||
null,
|
||||
(err, items) => {teamsnapCallback(err, items, {req, source:"loadEventLineup", method:'bulkLoad'})}
|
||||
)
|
||||
.then(items => tsUtils.groupTeamsnapItems(items, bulkLoadTypes))
|
||||
.then(items => {
|
||||
req.event_lineup = items.eventLineups.pop();
|
||||
req.event_lineup_entries = items.eventLineupEntries?.sort((a,b)=>a.sequence-b.sequence) || [];
|
||||
})
|
||||
)
|
||||
req.promises.push(
|
||||
teamsnap.loadAvailabilities(
|
||||
{eventId: event_id},
|
||||
(err, items) => {teamsnapCallback(err, items, {req, source:"loadEventLineup", method:'loadAvailabilities'})}
|
||||
)
|
||||
.then(availabilities => req.availabilities = availabilities))
|
||||
}
|
||||
else {
|
||||
// const {event_lineup} = req
|
||||
}
|
||||
const {event_lineup} = req
|
||||
// req.availabilitySummary = items.find((i) => i.type == "availabilitySummary" && i.id == event.id),
|
||||
// req.event = items.find((i) => i.type == "event" && i.id == event_id)
|
||||
next();
|
||||
}
|
||||
|
||||
router.use("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup", loadEventLineup)
|
||||
|
||||
// Routes
|
||||
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup", async (req,res) => {
|
||||
await Promise.all(req.promises);
|
||||
const {event_lineup} = req
|
||||
res.redirect(`lineup/${event_lineup.id}`);
|
||||
}
|
||||
)
|
||||
|
||||
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/adjacent", doubleCsrfProtection, loadRecentAndUpcomingEvents, loadEvents, eventsLineupController.getAdjacentEventLineup);
|
||||
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/email", upload.none(), doubleCsrfProtection, eventsLineupController.getEventLineupEmail )
|
||||
router.get ("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)", upload.none(), doubleCsrfProtection, eventsLineupController.getEventLineup);
|
||||
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)", upload.none(), doubleCsrfProtection, eventsLineupController.postEventLineup);
|
||||
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/delete", upload.none(), eventsLineupController.submitDeleteEventLineupEntries);
|
||||
|
||||
|
||||
module.exports = {router, loadEventLineup}
|
||||
37
src/routes/eventsheet.js
Normal file
37
src/routes/eventsheet.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const express = require("express");
|
||||
const eventsSheetController = require("../controllers/eventsheet");
|
||||
const {loadEventLineup} = require("./eventlineup");
|
||||
const {loadEvent, loadEvents} = require("./event");
|
||||
const {loadRecentAndUpcomingEvents} = require("../middlewares/bulkload")
|
||||
const router = express.Router();
|
||||
const tsUtils = require('../lib/utils')
|
||||
const {teamsnapCallback} = require('../lib/utils')
|
||||
const multer = require("multer");
|
||||
const upload = multer()
|
||||
|
||||
|
||||
const linksForEventSheet = async (req, res, next) => {
|
||||
await Promise.all(req.promises)
|
||||
const events = [...req.recent_events, req.event, ...req.upcoming_events]
|
||||
events.forEach((event) => {
|
||||
console.log()
|
||||
})
|
||||
next();
|
||||
}
|
||||
|
||||
router.use("/:team_id([0-9]+)/event/:event_id([0-9]+)/sheet", loadEventLineup)
|
||||
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/sheet", loadRecentAndUpcomingEvents, loadEvents, eventsSheetController.getEventSheet)
|
||||
|
||||
// Routes
|
||||
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/sheet", async (req,res) => {
|
||||
await Promise.all(req.promises);
|
||||
const {event_lineup} = req
|
||||
res.redirect(`lineup/${event_lineup.id}/sheet`);
|
||||
}
|
||||
)
|
||||
|
||||
router.get("/lineup/sheet/blank", eventsSheetController.getEventSheetBlank )
|
||||
|
||||
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/sheet", upload.none(), eventsSheetController.getEventSheet )
|
||||
|
||||
module.exports = {router}
|
||||
36
src/routes/index.js
Normal file
36
src/routes/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const express = require("express");
|
||||
const {ensureLoggedIn} = require("./auth")
|
||||
|
||||
var router = express.Router();
|
||||
|
||||
var multer = require("multer");
|
||||
const storage = multer.memoryStorage();
|
||||
const upload = multer({ storage: storage });
|
||||
const path = require("path")
|
||||
|
||||
partials = path.join(__dirname, "../views/partials")
|
||||
|
||||
const membersController = require("../controllers/member");
|
||||
|
||||
router.use("/", ensureLoggedIn, (req,res,next) => {req.layout="layouts/main";req.promises=[];next();})
|
||||
|
||||
router.get("/", (req,res,next) => {
|
||||
if (!req.session.current_team_id){
|
||||
res.redirect(`/user/${req.session.passport.user.id}/teams`)
|
||||
next();
|
||||
}
|
||||
else {
|
||||
res.redirect(`/${req.session.current_team_id}/home`)
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/:team_id([0-9]+)/members", membersController.getMembers);
|
||||
|
||||
router.get("/modal-confirm/", (req,res) => {
|
||||
const {title, body} = req.query
|
||||
res.render('modal_confirm', {title, body} )
|
||||
}
|
||||
)
|
||||
|
||||
module.exports = {router, partials};
|
||||
6
src/routes/meta.js
Normal file
6
src/routes/meta.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/favicon.ico", (req, res, next) => res.status(204).end())
|
||||
|
||||
module.exports = {router}
|
||||
42
src/routes/opponent.js
Normal file
42
src/routes/opponent.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const express = require("express");
|
||||
const opponentsController = require("../controllers/opponent");
|
||||
const {loadTeam} = require("./team")
|
||||
var router = express.Router();
|
||||
const multer = require("multer");
|
||||
const upload = multer()
|
||||
const { doubleCsrfProtection } = require('../middlewares/csrf');
|
||||
const {teamsnapCallback} = require('../lib/utils')
|
||||
|
||||
// Middleware
|
||||
const loadOpponent = (req,res,next) => {
|
||||
const {opponent_id} = req.params;
|
||||
const {team} = req
|
||||
req.promises.push(
|
||||
teamsnap.loadOpponents(
|
||||
team.id,
|
||||
(err, opponents) => {teamsnapCallback(err, opponents, {req, source:"loadOpponent", method:'loadOpponent'})}
|
||||
)
|
||||
.then(opponents => {req.opponent=opponents.find(o=>o.id==opponent_id);})
|
||||
)
|
||||
|
||||
req.promises.push(
|
||||
teamsnap.loadTeamMedia(
|
||||
team.id,
|
||||
(err, opponents) => {teamsnapCallback(err, opponents, {req, source:"loadOpponent", method:'teamMedia'})}
|
||||
)
|
||||
.then(team_media => {
|
||||
req.opponent_logo = team_media.find(tm=>tm.description==`opponent-logo-${opponent_id}.png`)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
router.use("/:team_id([0-9]+)/opponent/:opponent_id([0-9]+)", loadOpponent)
|
||||
router.get("/:team_id([0-9]+)/opponents", opponentsController.getOpponents);
|
||||
router.get("/:team_id([0-9]+)/opponent/:opponent_id([0-9]+)", doubleCsrfProtection, opponentsController.getOpponent);
|
||||
router.post("/:team_id([0-9]+)/opponent/:opponent_id([0-9]+)/upload_logo", upload.single('file'), doubleCsrfProtection, opponentsController.postOpponentLogo);
|
||||
// router.get("/:team_id([0-9]+)/opponent/:opponent_id/logo", ensureLoggedIn, opponentsController.getOpponentLogo);
|
||||
|
||||
module.exports = {router}
|
||||
48
src/routes/team.js
Normal file
48
src/routes/team.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const express = require("express");
|
||||
const teamsController = require("../controllers/team");
|
||||
const {loadRecentAndUpcomingEvents} = require("../middlewares/bulkload")
|
||||
const { load } = require("dotenv");
|
||||
const router = express.Router();
|
||||
const tsUtils = require('../lib/utils')
|
||||
const {teamsnapCallback} = require('../lib/utils')
|
||||
|
||||
// Middleware
|
||||
const loadTeam = async (req,res,next) => {
|
||||
const {team_id} = req.params;
|
||||
req.team = await teamsnap.loadTeam(
|
||||
team_id,
|
||||
(err, result) => {teamsnapCallback(err, result, {req, source: 'loadTeam', method: 'loadTeam'})}
|
||||
)
|
||||
const bulkLoadTypes = ['teamMediaGroup', 'teamPreferences', 'member'];
|
||||
const items = tsUtils.groupTeamsnapItems(teamsnap.getAllItems(), bulkLoadTypes)
|
||||
if (req.session.current_team_id == null || req.session.current_team_id != team_id || bulkLoadTypes.filter(t=> !items[t] || items[t].length==0).length > 0){
|
||||
req.promises.push(teamsnap.bulkLoad(
|
||||
team_id,
|
||||
bulkLoadTypes,
|
||||
(err,items) => {teamsnapCallback(err, items, {req, source: 'loadTeam', method: 'bulkLoad'})}
|
||||
)
|
||||
.then(bulkLoadItems=>{
|
||||
const items = tsUtils.groupTeamsnapItems(bulkLoadItems, bulkLoadTypes)
|
||||
req.members = items.members;
|
||||
req.team_media_group = items.teamMediaGroups?.pop();
|
||||
req.team_preferences = items.teamsPreferences.pop();
|
||||
req.session.current_team_id = req.team.id;
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
else {
|
||||
req.members = items.member;
|
||||
req.team_media_group = items.teamMediaGroup.pop();
|
||||
req.team_preferences = items.teamPreferences.pop();
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
router.use("/:team_id([0-9]+)", loadTeam)
|
||||
|
||||
// Routes
|
||||
router.get('/user/:user_id/teams', teamsController.getTeams)
|
||||
router.get("/:team_id([0-9]+)/home", loadRecentAndUpcomingEvents, teamsController.getTeamHome);
|
||||
|
||||
module.exports = {router, loadTeam}
|
||||
383
src/scss/application.scss
Normal file
383
src/scss/application.scss
Normal file
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
||||
* listed below.
|
||||
*
|
||||
* Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
|
||||
* vendor/assets/stylesheets directory can be referenced here using a relative path.
|
||||
*
|
||||
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
||||
* compiled file so the styles you add here take precedence over styles defined in any other CSS
|
||||
* files in this directory. Styles in this file should be added after the last require_* statement.
|
||||
* It is generally better to create a new file per style scope.
|
||||
*
|
||||
*= require_tree .
|
||||
*= require_self
|
||||
*/
|
||||
|
||||
@import "../../node_modules/@teamsnap/teamsnap-ui/src/css/teamsnap-ui.scss";
|
||||
@import url('/font/helvetica-now/stylesheet.css');
|
||||
|
||||
|
||||
$color-success: #b7e1cd;
|
||||
$color-danger: #f4c7c3;
|
||||
$color-neutral: #acc9fe;
|
||||
$color-warning: rgb(249, 228, 180);
|
||||
$color-grey-100: #f8f9fa;
|
||||
$color-grey-200: #e9ecef;
|
||||
$color-grey-300: #dee2e6;
|
||||
$color-grey-400: #ced4da;
|
||||
$color-grey-500: #adb5bd;
|
||||
$color-grey-600: #6c757d;
|
||||
$color-grey-700: #495057;
|
||||
$color-grey-800: #343a40;
|
||||
$color-grey-900: #212529;
|
||||
$row-height: 14px;
|
||||
$monospace-font: "Inconsolata", monospace;
|
||||
|
||||
|
||||
// Components
|
||||
@import "components/progress";
|
||||
|
||||
:root {
|
||||
--bc-text-muted:#6c757d;
|
||||
--bc-avail-color-yes: $cu-positive;
|
||||
--bc-avail-color-no: $cu-negative;
|
||||
--bc-avail-color-maybe:#113b63;
|
||||
}
|
||||
|
||||
.availability-bar {
|
||||
&.going {
|
||||
background-color: $ts-green;
|
||||
}
|
||||
|
||||
&.not-going {
|
||||
background-color: $ts-red;
|
||||
}
|
||||
|
||||
&.maybe {
|
||||
background-color: $ts-blue;
|
||||
}
|
||||
|
||||
&.unknown {
|
||||
background-color: $ts-grey;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
background: #323669;
|
||||
padding: 8px 0;
|
||||
// margin: 0 0 16px 0;
|
||||
box-shadow: 0 4px 0 rgba(0, 0, 25, 0.1);
|
||||
border-bottom: 1px solid #d6d6d6;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
|
||||
.Header-banner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.filler {
|
||||
flex-grow:1,
|
||||
}
|
||||
|
||||
:has(>.Header-bannerLogo):has(>.Header-bannerTitle) {
|
||||
display: inline-flex
|
||||
}
|
||||
|
||||
.Header-bannerLogo, .Header-bannerTitle {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.Header-bannerLogo img {
|
||||
height: 36px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.Header-bannerTitle {
|
||||
font-family: "Helvetica", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
color: white;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.btn--Full {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: rgb(246, 246, 246);
|
||||
}
|
||||
|
||||
/* .u-padSm.u-border.u-borderRadiusLg.u-spaceEndsSm.u-maxWidthXs */
|
||||
|
||||
.event-card {
|
||||
font-family: "Open Sans", Helvetica, sans-serif;
|
||||
border: 1px solid #e6e6e6;
|
||||
border-radius: 8px;
|
||||
max-width: 480px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
background: white;
|
||||
|
||||
.title {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.event-card-body {
|
||||
padding: 8px 8px 8px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.event-card-body {
|
||||
> .availability-bar {
|
||||
margin: 4px;
|
||||
|
||||
&.fullwidth {
|
||||
margin-top: 4px;
|
||||
margin-bottom: -8px;
|
||||
margin-left: -8px;
|
||||
margin-right: -8px;
|
||||
}
|
||||
}
|
||||
|
||||
.availability-bar.fullwidth .progress {
|
||||
margin-left: -8px;
|
||||
margin-right: -8px;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.event-card {
|
||||
.date, .location {
|
||||
color: #7a7a7a;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.opponent {}
|
||||
|
||||
.Button span {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.event-card-footer {
|
||||
padding: 8px;
|
||||
border-radius: 0px 0px 8px 8px;
|
||||
background-color: rgb(251, 251, 251);
|
||||
border-top: solid 1px rgb(214, 214, 214);
|
||||
}
|
||||
}
|
||||
|
||||
.event-card-footer div {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a.Panel-row {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.lineup-slot .Panel-cell {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
div.event-lineup {
|
||||
max-width: 576px;
|
||||
counter-reset: lineup-sequence-counter 0;
|
||||
margin-left: 8px;
|
||||
margin-right: 9px;
|
||||
}
|
||||
|
||||
.lineup-slot {
|
||||
counter-increment: lineup-sequence-counter 1;
|
||||
|
||||
.Panel-cell {
|
||||
&.Panel-cell--header {
|
||||
background: rgba(256, 256, 256, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[class*="availability-status-code"]::before {
|
||||
padding-right: 6px;
|
||||
font-family: "bootstrap-icons";
|
||||
}
|
||||
}
|
||||
|
||||
.lineup-slot .availability-status-code-1 .icon {
|
||||
color: $ts-green;
|
||||
}
|
||||
|
||||
.lineup-slot .availability-status-code-2 .icon {
|
||||
color: $ts-blue;
|
||||
}
|
||||
|
||||
.lineup-slot .availability-status-code-0 .icon {
|
||||
color: $ts-red;
|
||||
}
|
||||
|
||||
.lineup-slot {
|
||||
.availability-status-code-nil .icon, .availability-status-code- .icon {
|
||||
color: $ts-grey;
|
||||
}
|
||||
}
|
||||
|
||||
li .availability-status-code- {
|
||||
content: "\F50B";
|
||||
color: var(--bs-secondary);
|
||||
}
|
||||
|
||||
.lineup-slot {
|
||||
line-height: 100%;
|
||||
vertical-align: middle;
|
||||
span {
|
||||
margin:auto;
|
||||
}
|
||||
.lastname {
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.jerseynumber {
|
||||
text-transform: uppercase;
|
||||
font-weight: light;
|
||||
color: var(--bc-text-muted);
|
||||
font-size: 0.8em;
|
||||
|
||||
// &::before {
|
||||
// content: "-";
|
||||
// margin-right: 4px;
|
||||
margin-left: 4px;
|
||||
// }
|
||||
}
|
||||
|
||||
button {
|
||||
margin-right: 0.5ch;
|
||||
}
|
||||
.sequence {
|
||||
width: 2.4ch;
|
||||
text-align: right;
|
||||
margin-right: 0.3ch;
|
||||
|
||||
&::before {
|
||||
content: counter(lineup-sequence-counter);
|
||||
}
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
width: 2ch;
|
||||
}
|
||||
|
||||
.position-select-box {
|
||||
width: 11ch;
|
||||
}
|
||||
|
||||
.Panel-cell {
|
||||
&:has(.sequence), &:has(.drag-handle), &:has(.position-select-box) {
|
||||
flex: 0 0 0% !important;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
div.event-lineup {
|
||||
.lineup-segment {
|
||||
&:has(input.Toggle-input:not(:checked)) {
|
||||
&.out {
|
||||
.Panel-cell:has(.SelectBox),
|
||||
.Panel-cell:has(.drag-handle),
|
||||
button:has(+.position-label-flags),
|
||||
button.addToStarting,
|
||||
button.addToBench
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.bench, &.position-only, &.out {
|
||||
.Panel-cell:has(.sequence) {
|
||||
display: none;
|
||||
}
|
||||
&.bench button.addToBench {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.starting button.addToStarting, &.position-only button.addToStarting {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Tooltip:after {
|
||||
padding: 2px !important;
|
||||
font-size: inherit !important;
|
||||
}
|
||||
|
||||
@media (max-width: 480px){
|
||||
.Panel--full {
|
||||
border-radius: 0;
|
||||
// margin-right: -16px;
|
||||
// margin-left: -16px;
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
}}
|
||||
|
||||
.lineup-email {
|
||||
font-family: "Helvetica", sans-serif;
|
||||
|
||||
.title-cell {
|
||||
font-weight:bold;
|
||||
background-color:#323669;
|
||||
color:#fff;
|
||||
padding:2px 5px;
|
||||
text-transform: uppercase;
|
||||
|
||||
&.out {
|
||||
background-color: rgb(244, 199, 195);
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
.sequence-cell {
|
||||
font-weight:bold;
|
||||
padding: 1px 5px;
|
||||
text-align: left
|
||||
}
|
||||
|
||||
.name-cell {
|
||||
width:200px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.position-label-cell {
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.Panel .Panel{
|
||||
// padding: 0;
|
||||
// border-radius: 0;
|
||||
// border: none;
|
||||
// border-top: 1px solid #d6d6d6;
|
||||
// border-bottom: 1px solid #d6d6d6;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.scroll-horizontal {
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
button:has(+.position-label-flags :checked) {
|
||||
@extend .Button--blue
|
||||
}
|
||||
78
src/scss/components/_border-radius.scss
Normal file
78
src/scss/components/_border-radius.scss
Normal file
@@ -0,0 +1,78 @@
|
||||
// stylelint-disable property-disallowed-list
|
||||
// Single side border-radius
|
||||
|
||||
// Helper function to replace negative values with 0
|
||||
@function valid-radius($radius) {
|
||||
$return: ();
|
||||
@each $value in $radius {
|
||||
@if type-of($value) == number {
|
||||
$return: append($return, max($value, 0));
|
||||
} @else {
|
||||
$return: append($return, $value);
|
||||
}
|
||||
}
|
||||
@return $return;
|
||||
}
|
||||
|
||||
// scss-docs-start border-radius-mixins
|
||||
@mixin border-radius($radius: $border-radius, $fallback-border-radius: false) {
|
||||
@if $enable-rounded {
|
||||
border-radius: valid-radius($radius);
|
||||
}
|
||||
@else if $fallback-border-radius != false {
|
||||
border-radius: $fallback-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin border-top-radius($radius: $border-radius) {
|
||||
@if $enable-rounded {
|
||||
border-top-left-radius: valid-radius($radius);
|
||||
border-top-right-radius: valid-radius($radius);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin border-end-radius($radius: $border-radius) {
|
||||
@if $enable-rounded {
|
||||
border-top-right-radius: valid-radius($radius);
|
||||
border-bottom-right-radius: valid-radius($radius);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin border-bottom-radius($radius: $border-radius) {
|
||||
@if $enable-rounded {
|
||||
border-bottom-right-radius: valid-radius($radius);
|
||||
border-bottom-left-radius: valid-radius($radius);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin border-start-radius($radius: $border-radius) {
|
||||
@if $enable-rounded {
|
||||
border-top-left-radius: valid-radius($radius);
|
||||
border-bottom-left-radius: valid-radius($radius);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin border-top-start-radius($radius: $border-radius) {
|
||||
@if $enable-rounded {
|
||||
border-top-left-radius: valid-radius($radius);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin border-top-end-radius($radius: $border-radius) {
|
||||
@if $enable-rounded {
|
||||
border-top-right-radius: valid-radius($radius);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin border-bottom-end-radius($radius: $border-radius) {
|
||||
@if $enable-rounded {
|
||||
border-bottom-right-radius: valid-radius($radius);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin border-bottom-start-radius($radius: $border-radius) {
|
||||
@if $enable-rounded {
|
||||
border-bottom-left-radius: valid-radius($radius);
|
||||
}
|
||||
}
|
||||
// scss-docs-end border-radius-mixins
|
||||
18
src/scss/components/_box-shadow.scss
Normal file
18
src/scss/components/_box-shadow.scss
Normal file
@@ -0,0 +1,18 @@
|
||||
@mixin box-shadow($shadow...) {
|
||||
@if $enable-shadows {
|
||||
$result: ();
|
||||
|
||||
@each $value in $shadow {
|
||||
@if $value != null {
|
||||
$result: append($result, $value, "comma");
|
||||
}
|
||||
@if $value == none and length($shadow) > 1 {
|
||||
@warn "The keyword 'none' must be used as a single argument.";
|
||||
}
|
||||
}
|
||||
|
||||
@if (length($result) > 0) {
|
||||
box-shadow: $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/scss/components/_progress.scss
Normal file
72
src/scss/components/_progress.scss
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
|
||||
$progress-height: 1rem;
|
||||
$progress-font-size: $tu-base-fontSize * .75;
|
||||
$progress-bg: $color-grey-200;
|
||||
$progress-border-radius: $border-radius-small;
|
||||
$progress-box-shadow: $inset-box-shadow-small;
|
||||
$progress-bar-color: $ts-white;
|
||||
$progress-bar-bg: $ts-green;
|
||||
$progress-bar-animation-timing: 1s linear infinite;
|
||||
$progress-bar-transition: width .6s ease;
|
||||
$prefix: "";
|
||||
|
||||
// Disable animation if transitions are disabled
|
||||
|
||||
// scss-docs-start progress-keyframes
|
||||
// @if $enable-transitions {
|
||||
// @keyframes progress-bar-stripes {
|
||||
// 0% { background-position-x: $progress-height; }
|
||||
// }
|
||||
// }
|
||||
// scss-docs-end progress-keyframes
|
||||
|
||||
.progress {
|
||||
// scss-docs-start progress-css-vars
|
||||
--#{$prefix}progress-height: #{$progress-height};
|
||||
// @include rfs($progress-font-size, --#{$prefix}progress-font-size);
|
||||
--#{$prefix}progress-bg: #{$progress-bg};
|
||||
--#{$prefix}progress-border-radius: #{$progress-border-radius};
|
||||
--#{$prefix}progress-box-shadow: #{$progress-box-shadow};
|
||||
--#{$prefix}progress-bar-color: #{$progress-bar-color};
|
||||
--#{$prefix}progress-bar-bg: #{$progress-bar-bg};
|
||||
--#{$prefix}progress-bar-transition: #{$progress-bar-transition};
|
||||
// scss-docs-end progress-css-vars
|
||||
|
||||
display: flex;
|
||||
height: var(--#{$prefix}progress-height);
|
||||
overflow: hidden; // force rounded corners by cropping it
|
||||
// @include font-size(var(--#{$prefix}progress-font-size));
|
||||
background-color: var(--#{$prefix}progress-bg);
|
||||
// @include border-radius(var(--#{$prefix}progress-border-radius));
|
||||
// @include box-shadow(var(--#{$prefix}progress-box-shadow));
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
color: var(--#{$prefix}progress-bar-color);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
background-color: var(--#{$prefix}progress-bar-bg);
|
||||
// @include transition(var(--#{$prefix}progress-bar-transition));
|
||||
}
|
||||
|
||||
.progress-bar-striped {
|
||||
// @include gradient-striped();
|
||||
background-size: var(--#{$prefix}progress-height) var(--#{$prefix}progress-height);
|
||||
}
|
||||
|
||||
// @if $enable-transitions {
|
||||
// .progress-bar-animated {
|
||||
// animation: $progress-bar-animation-timing progress-bar-stripes;
|
||||
|
||||
// @if $enable-reduced-motion {
|
||||
// @media (prefers-reduced-motion: reduce) {
|
||||
// animation: none;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
1027
src/scss/eventsheet.scss
Normal file
1027
src/scss/eventsheet.scss
Normal file
File diff suppressed because it is too large
Load Diff
2
src/views/error.hbs
Normal file
2
src/views/error.hbs
Normal file
@@ -0,0 +1,2 @@
|
||||
<h1>Oops ...</h1>
|
||||
<code>{{message}}</code>
|
||||
120
src/views/event/list.hbs
Normal file
120
src/views/event/list.hbs
Normal file
@@ -0,0 +1,120 @@
|
||||
<h1>
|
||||
Schedule
|
||||
</h1>
|
||||
<div class="Panel">
|
||||
<div class="Panel-body">
|
||||
<div class="Panel-row Panel-row--withCells u-textDecorationNone">
|
||||
<div class="Panel-cell u-size7of24 u-textLeft">
|
||||
<h4 class="Panel-title">
|
||||
Title
|
||||
</h4>
|
||||
</div>
|
||||
<div class="Panel-cell u-size3of24 u-hidden u-xs-block">
|
||||
<h4 class="Panel-title">
|
||||
Date
|
||||
</h4>
|
||||
</div>
|
||||
<div class="Panel-cell u-size3of24 u-hidden u-xs-block">
|
||||
<h4 class="Panel-title">
|
||||
Time
|
||||
</h4>
|
||||
</div>
|
||||
<div class="Panel-cell u-size3of24 u-xs-hidden">
|
||||
<h4 class="Panel-title">
|
||||
When/Where
|
||||
</h4>
|
||||
</div>
|
||||
<div class="Panel-cell u-size7of24 u-hidden u-xs-block">
|
||||
<h4 class="Panel-title">
|
||||
Location
|
||||
</h4>
|
||||
</div>
|
||||
<div class="Panel-cell u-size3of24 u-borderLeft">
|
||||
<h4 class="Panel-title">
|
||||
...
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{{#each events as |event|}}
|
||||
<div class="Panel-row Panel-row--withCells">
|
||||
<div class="Panel-cell u-size7of24 u-textLeft">
|
||||
<a href="event/{{this.id}}">
|
||||
{{event.formattedTitle}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="Panel-cell u-size3of24 u-textLeft u-hidden u-xs-block">
|
||||
{{dateFormat event.startDate "ddd MMM D"}}
|
||||
</div>
|
||||
<div class="Panel-cell u-size3of24 u-hidden u-xs-block">
|
||||
{{dateFormat event.startDate "h:mm A"}}
|
||||
</div>
|
||||
<div class="Panel-cell u-size3of24 u-xs-hidden">
|
||||
{{dateFormat this.startDate "ddd MMM D, h:mm A"}} {{
|
||||
event.locationName
|
||||
}}
|
||||
</div>
|
||||
<div class="Panel-cell u-size7of24 u-hidden u-xs-block">
|
||||
{{event.locationName}}
|
||||
</div>
|
||||
<div class="Panel-cell u-size3of24 u-borderLeft">
|
||||
<button
|
||||
class="Button Button--small Button--default Popup"
|
||||
onclick='console.log(this);this.querySelector(".Popup-container").classList.toggle("is-open")'
|
||||
;
|
||||
>
|
||||
...
|
||||
<div
|
||||
class="Popup-container Popup-container--up Popup-container--right"
|
||||
>
|
||||
<div class="Popup-content u-textDecorationNone">
|
||||
<div class="Grid Grid--fit u-spaceXs">
|
||||
<div class="Grid-cell u-sizeFit u-spaceXs">
|
||||
<a href="url_for(event_path :team_id=>team.id, :event_id=>event.id)">
|
||||
<span>{{{embeddedSvgFromPath "/bootstrap-icons/calendar.svg"}}}</span>
|
||||
<span class="u-hidden u-xs-inline">Details</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="Grid-cell u-sizeFit u-spaceXs">
|
||||
<a href="/{{team.id}}/event/{{event.id}}/lineup">
|
||||
<span>{{{embeddedSvgFromPath "/bootstrap-icons/clipboard.svg"}}}</span>
|
||||
<span class="u-hidden u-xs-inline">Lineup</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="Grid-cell u-sizeFit u-spaceXs">
|
||||
<a href="/{{team.id}}/event/{{event.id}}/sheet">
|
||||
<span>{{{embeddedSvgFromPath "/bootstrap-icons/file-earmark.svg"}}}</span>
|
||||
<span class="u-hidden u-xs-inline">Sheet</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="Grid-cell u-sizeFit u-spaceXs">
|
||||
<a href="https://go.teamsnap.com/{{../team.id}}/schedule/view_game/{{event.id}}">
|
||||
<span>{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}</span>
|
||||
<span class="u-hidden u-xs-inline">TeamSnap</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{!--
|
||||
|
||||
a.Button href=url_for(event_path :team_id=>team.id, :event_id=>event.id)
|
||||
=embedded_svg "teamsnap-ui/assets/icons/schedule svg", class:"Icon"
|
||||
| Details
|
||||
.Grid-cell.u-sizeFit.u-spaceXs
|
||||
a.Button href=url_for(event_lineup_path :team_id => team.id, :event_id => event.id)
|
||||
=embedded_svg "bootstrap-icons/clipboard svg", class:"Icon"
|
||||
| Lineup
|
||||
.Grid-cell.u-sizeFit.u-spaceXs
|
||||
a.Button href=url_for(event_lineup_card_path :team_id => team.id, :event_id => event.id)
|
||||
=embedded_svg "bootstrap-icons/book svg", class:"Icon"
|
||||
| Card
|
||||
.Grid-cell.u-sizeFit.u-spaceXs
|
||||
a.Button href="https://go teamsnap com/#{team id}/schedule/view_game/#{event id}"
|
||||
=embedded_svg "bootstrap-icons/asterisk svg", class:"Icon"
|
||||
| TeamSnap }} --}}
|
||||
</div>
|
||||
</div>
|
||||
16
src/views/event/partials/availability_bar.hbs
Normal file
16
src/views/event/partials/availability_bar.hbs
Normal file
@@ -0,0 +1,16 @@
|
||||
<div class="progress">
|
||||
{{#if availabilitySummary}}
|
||||
<div class="progress-bar availability-bar going" role="progressbar" style="width:{{availability_percentage availabilitySummary "going"}}%">
|
||||
{{availabilitySummary.playerGoingCount}}
|
||||
</div>
|
||||
<div class="progress-bar availability-bar maybe" role="progressbar" style="width:{{availability_percentage availabilitySummary "maybe"}}%">
|
||||
{{availabilitySummary.playerMaybeCount}}
|
||||
</div>
|
||||
<div class="progress-bar availability-bar not-going" role="progressbar" style="width:{{availability_percentage availabilitySummary "notgoing"}}%">
|
||||
{{availabilitySummary.playerNotGoingCount}}
|
||||
</div>
|
||||
<div class="progress-bar availability-bar unknown" role="progressbar" style="width:{{availability_percentage availabilitySummary "unknown"}}%">
|
||||
{{availabilitySummary.playerUnknownCount}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
34
src/views/event/partials/event_panel.hbs
Normal file
34
src/views/event/partials/event_panel.hbs
Normal file
@@ -0,0 +1,34 @@
|
||||
<div class="Panel">
|
||||
<div class="Panel-header u-padEndsSm">
|
||||
<h3><a href="/{{team.id}}/event/{{event.id}}">{{event.formattedTitle}}</a></h3>
|
||||
</div>
|
||||
<div class=" Panel-body u-padEndsSm">
|
||||
<div class=" u-padSidesSm">
|
||||
<div class="date" >{{dateFormat event.startDate "ddd, MMM D h:mm A" }}</div>
|
||||
<div class="location">{{event.locationName}}</div>
|
||||
</div>
|
||||
<div class=" availability-bar fullwidth">
|
||||
{{> availability_bar availabilitySummary=event.availabilitySummary}}
|
||||
</div>
|
||||
</div>
|
||||
<div class=" Panel-footer u-flex u-flexJustifyAround u-padSm">
|
||||
<div class="u-maxWidthXs">
|
||||
<a class="Button" href="/{{team.id}}/event/{{event.id}}">
|
||||
<span>{{{embeddedSvgFromPath "/bootstrap-icons/calendar.svg"}}}</span>
|
||||
<span class="u-hidden">Details</span>
|
||||
</a>
|
||||
<a class="Button" href="/{{team.id}}/event/{{event.id}}/lineup">
|
||||
<span>{{{embeddedSvgFromPath "/bootstrap-icons/clipboard.svg"}}}</span>
|
||||
<span class="u-hidden">Lineup</span>
|
||||
</a>
|
||||
<a class="Button" href="/{{team.id}}/event/{{event.id}}/sheet">
|
||||
<span>{{{embeddedSvgFromPath "/bootstrap-icons/file-earmark.svg"}}}</span>
|
||||
<span class="u-hidden">Sheet</span>
|
||||
</a>
|
||||
<a class="Button" href="https://go.teamsnap.com/{{team.id}}/schedule/view_game/{{event.id}}">
|
||||
<span>{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}</span>
|
||||
<span class="u-hidden">TeamSnap</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
38
src/views/event/partials/modal_availability_reminders.hbs
Normal file
38
src/views/event/partials/modal_availability_reminders.hbs
Normal file
@@ -0,0 +1,38 @@
|
||||
<div id="modal" class="Modal Modal--clickableBg">
|
||||
<div class="Modal-content">
|
||||
<div onclick="javascript:this.closest('.Modal').remove();">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg" "Modal-iconDismiss"}}}</div>
|
||||
<div class="Modal-header">
|
||||
<div class="Modal-title">Send Reminders</div>
|
||||
</div>
|
||||
<div class="Modal-body">
|
||||
<div class="u-padSidesMd">
|
||||
<strong>Send to players who have selected:</strong>
|
||||
<div class="u-spaceTopSm"><div class="Checkbox">
|
||||
<input class="Checkbox-input" type="checkbox" name="undecidedCheckBox" id="undecidedCheckBox" checked="" value>
|
||||
<label class="Checkbox-label" for="undecidedCheckBox">Undecided</label>
|
||||
</div>
|
||||
<div class="Checkbox">
|
||||
<input class="Checkbox-input" type="checkbox" name="maybeCheckbox" id="maybeCheckbox" value="2">
|
||||
<label class="Checkbox-label" for="maybeCheckbox">Maybe</label>
|
||||
</div>
|
||||
<div class="Checkbox">
|
||||
<input class="Checkbox-input" type="checkbox" name="attendingCheckbox" id="attendingCheckbox" value="1">
|
||||
<label class="Checkbox-label" for="attendingCheckbox">Attending</label>
|
||||
</div>
|
||||
<div class="Checkbox u-padBottomNone">
|
||||
<input class="Checkbox-input" type="checkbox" name="notAttendingCheckbox" id="notAttendingCheckbox" value="0">
|
||||
<label class="Checkbox-label" for="notAttendingCheckbox">Not Attending</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Modal-footer">
|
||||
<button class="Button Button--negative" role="button" type="button" onclick="javascript:this.closest('.Modal').remove();" data-confirm="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="Button Button--primary" role="button" type="button" data-confirm="yes">
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
1
src/views/event/show.hbs
Normal file
1
src/views/event/show.hbs
Normal file
@@ -0,0 +1 @@
|
||||
{{> event_panel event=event}}
|
||||
0
src/views/event/stats-importer.hbs
Normal file
0
src/views/event/stats-importer.hbs
Normal file
132
src/views/eventlineup/edit.hbs
Normal file
132
src/views/eventlineup/edit.hbs
Normal file
@@ -0,0 +1,132 @@
|
||||
<div class="u-spaceSidesNone u-sm-spaceSidesAuto event-lineup" id="event-lineup-{{event.id}}" data-event-lineup-id="{{event_lineup.id}}" data-event-id="{{event.id}}">
|
||||
<form onsubmit="submitEventLineup(this,event)" action="/{{team.id}}/event/{{event.id}}/lineup/{{event_lineup.id}}">
|
||||
<input type="hidden" name="event_lineup_id" value="{{event_lineup.id}}">
|
||||
{{!-- <input type="hidden" name="_csrf" value="{{csrfToken}}"> --}}
|
||||
<input type="hidden" name="csrfToken" value="{{csrfToken}}">
|
||||
<div class="Panel Panel--full">
|
||||
<div class="Panel-header u-padEndsSm">
|
||||
<h3 style="flex: 1 1 0%;">{{event.formattedTitle}}</h3>
|
||||
<div class="Popup">
|
||||
<div class="ButtonGroup">
|
||||
<button class="Button Button--orange" type="submit" formmethod="post">
|
||||
<div>
|
||||
<span id="teamsnap-icon" class="hideOnLoading">{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}</span>
|
||||
<span id="waiting-icon" class="u-hidden showOnLoading">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/loader.svg" "Icon--loader"}}}</span>
|
||||
<span id="success-icon" class="u-hidden showOnSuccess">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/check.svg"}}}</span>
|
||||
<span id="failure-icon" class="u-hidden showOnFailure">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg"}}}</span>
|
||||
Save
|
||||
</div>
|
||||
</button>
|
||||
<div class="Button Button--orange .u-padSidesXs Popup-toggle" data-control="popup" data-open="event-lineup-more-actions-{{event_lineup.id}}">
|
||||
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/caret-down.svg"}}}
|
||||
<div class="Popup-container Popup-container--down Popup-container--right" style="width: 200px" data-popup="event-lineup-more-actions-{{event_lineup.id}}">
|
||||
<div class="Popup-content u-textDecorationNone">
|
||||
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="emailModal(this, '{{event_lineup.id}}/email')">
|
||||
{{{embeddedSvgFromPath "/bootstrap-icons/envelope.svg"}}}
|
||||
<span>Generate Email</span>
|
||||
</a>
|
||||
<hr class="Divider u-spaceEndsNone">
|
||||
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="/{{team.id}}/event/{{event.id}}/sheet">
|
||||
<span>{{{embeddedSvgFromPath "/bootstrap-icons/file-earmark.svg"}}}</span>
|
||||
<span class="u-hidden u-xs-inline">Game Sheet</span>
|
||||
</a>
|
||||
<hr class="Divider u-spaceEndsNone">
|
||||
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="insertLineup(1, {{team.id}}, {{event.id}}, this)">
|
||||
{{{embeddedSvgFromPath "/bootstrap-icons/caret-right.svg"}}}
|
||||
<span>Insert next lineup</span>
|
||||
</a>
|
||||
<hr class="Divider u-spaceEndsNone">
|
||||
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="insertLineup(-1, {{team.id}}, {{event.id}}, this)">
|
||||
{{{embeddedSvgFromPath "/bootstrap-icons/caret-left.svg"}}}
|
||||
<span>Insert previous lineup</span>
|
||||
</a>
|
||||
<hr class="Divider u-spaceEndsNone">
|
||||
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="openAvailabilityReminderModal(this, {{team.id}}, {{event.id}})">
|
||||
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/send.svg"}}}
|
||||
<span>Availability Reminders</span>
|
||||
</a>
|
||||
<hr class="Divider u-spaceEndsNone">
|
||||
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="confirmModal(this, {title:'Reset Availabilities',body:'Are sure you want to reset availabilities?'}, submitResetAvailabilities, {team_id:{{team.id}}, event_id:{{event.id}} })";>
|
||||
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/refresh.svg"}}}
|
||||
<span>Reset All Availabilities</span>
|
||||
</a>
|
||||
<hr class="Divider u-spaceEndsNone">
|
||||
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="confirmModal(this, {title:'Clear Lineup',body:'Are sure you want to clear lineup?'}, submitClearLineup, {team_id:{{team.id}}, event_id:{{event.id}}, event_lineup_id:{{event_lineup.id}} })">
|
||||
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/trash.svg"}}}
|
||||
<span>Clear Lineup</span>
|
||||
</a>
|
||||
<div class="u-hidden">
|
||||
<hr class="Divider u-spaceEndsNone">
|
||||
<span class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="console.log('not implemented yet')">
|
||||
<span>Publish</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class=" Panel-body u-padEndsSm">
|
||||
<div class=" u-padSidesSm">
|
||||
<div class="date">{{dateFormat event.startDate "ddd, MMM D h:mm A" }}</div>
|
||||
<div class="location">{{event.locationName}}</div>
|
||||
</div>
|
||||
<div class="availability-bar fullwidth">
|
||||
{{> availability_bar availabilitySummary=availabilitySummary}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Panel u-maxWidthSm lineup-segment starting Panel--fullWidthMobile Panel--full">
|
||||
<div class="Panel-body">
|
||||
<div class="Panel-row Panel-title u-padXs">
|
||||
<i>{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-check.svg"}}}</i>
|
||||
<span>Starting Lineup</span>
|
||||
</div>
|
||||
<div class=" Panel-row Grid Grid--fit u-textBold u-textCenter u-padXs">
|
||||
{{#each (positions)}}
|
||||
<div class="Grid-cell position-status" data-value="{{this}}">{{this}}</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="slot-set">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Panel u-maxWidthSm lineup-segment position-only Panel--full">
|
||||
<div class="Panel-row Panel-title u-padXs">
|
||||
{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-check.svg"}}}
|
||||
<span>Position Only</span>
|
||||
</div>
|
||||
<div class="slot-set">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="Panel u-maxWidthSm lineup-segment bench Panel--full">
|
||||
<div class="Panel-row Panel-title u-padXs">
|
||||
{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-minus.svg"}}}
|
||||
<span>Bench</span>
|
||||
</div>
|
||||
<div class="slot-set">
|
||||
{{#loadSlots}}
|
||||
{{>slot member=member event_lineup=event_event_lineup availablity=availability}}
|
||||
{{/loadSlots}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="Panel u-maxWidthSm lineup-segment out Panel--full">
|
||||
<div class="Panel-row Panel-title u-padXs u-flex">
|
||||
<div><span style="flex: 1 1 0%;">{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-x.svg"}}}Out</span></div>
|
||||
<div class="u-flexGrow1"></div>
|
||||
<div class="Toggle">
|
||||
<input class="Toggle-input" type="checkbox" id="enable-slots">
|
||||
<label class="Toggle-label" for="enable-slots"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slot-set">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
70
src/views/eventlineup/email.hbs
Normal file
70
src/views/eventlineup/email.hbs
Normal file
@@ -0,0 +1,70 @@
|
||||
<div class="lineup-email">
|
||||
<div>
|
||||
<p>Team,</p>
|
||||
<p></p>
|
||||
</div>
|
||||
<div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="title-cell" colSpan=3>
|
||||
STARTING LINEUP
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each members}}
|
||||
{{#if (isInStartingLineup this)}}
|
||||
<tr>
|
||||
<td class="sequence-cell">
|
||||
{{plus1 this.benchcoach.eventLineupEntry.sequence}}
|
||||
</td>
|
||||
<td class="name-cell">{{this.lastName}}, {{this.firstName}} – #{{this.jerseyNumber}}</td>
|
||||
<td class="position-label-cell">{{this.benchcoach.eventLineupEntry.label}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<tr>
|
||||
<th class="title-cell" colSpan=3>Starting (Pos. Only)</th>
|
||||
</tr>
|
||||
{{#each members}}
|
||||
{{#if (isInPositionOnly this)}}
|
||||
<tr>
|
||||
<td class="sequence-cell"></td>
|
||||
<td class="name-cell">{{this.lastName}}, {{this.firstName}} – #{{this.jerseyNumber}}</td>
|
||||
<td class="position-label-cell">{{this.benchcoach.eventLineupEntry.label}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<tr>
|
||||
<th class="title-cell" colSpan=3>Subs</th>
|
||||
</tr>
|
||||
{{#each members}}
|
||||
{{#if (isInBench this)}}
|
||||
<tr>
|
||||
<td class="sequence-cell">
|
||||
{{availabilityStatusShort this.benchcoach.availability}}
|
||||
</td>
|
||||
<td class="name-cell">{{this.lastName}}, {{this.firstName}} – #{{this.jerseyNumber}}</td>
|
||||
<td class="position-label-cell">{{this.benchcoach.eventLineupEntry.label}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<tr>
|
||||
<th class="title-cell out" colSpan=3>Out</th>
|
||||
</tr>
|
||||
{{#each members}}
|
||||
{{#if (isInOut this)}}
|
||||
<tr>
|
||||
<td class="sequence-cell">
|
||||
{{availabilityStatusShort this.benchcoach.availability}}
|
||||
</td>
|
||||
<td class="name-cell">{{this.lastName}}, {{this.firstName}} – #{{this.jerseyNumber}}</td>
|
||||
<td class="position-label-cell">{{this.benchcoach.eventLineupEntry.label}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div id="availability-reminder-modal-{{event.id}}" class="Modal Modal--clickableBg">
|
||||
<div class="Modal-content">
|
||||
<div onclick="javascript:this.closest('.Modal').remove();">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg" "Modal-iconDismiss"}}}</div>
|
||||
<div class="Modal-header">
|
||||
<div class="Modal-title">Send Reminders</div>
|
||||
</div>
|
||||
<div class="Modal-body">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
44
src/views/eventlineup/partials/email_modal.hbs
Normal file
44
src/views/eventlineup/partials/email_modal.hbs
Normal file
@@ -0,0 +1,44 @@
|
||||
<div id="modal" class="Modal Modal--clickableBg">
|
||||
<div class="Modal-content">
|
||||
<div onclick="javascript:this.closest('.Modal').remove();tinymce.remove();">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg" "Modal-iconDismiss"}}}</div>
|
||||
<div class="Modal-header">
|
||||
<div class="Modal-title">Email</div>
|
||||
</div>
|
||||
<div class="Modal-body">
|
||||
<form>
|
||||
<div class="FieldGroup">
|
||||
<label class="FieldGroup-label">Subject</label>
|
||||
<input class="Input" id="email-subject" type="text" value="{{dateFormat event.startDate "ddd, MMM D, YYYY h:mm A" }}, {{ event.locationName }}, ({{#if (isAway event) }}@{{/if}}{{ event.opponentName }})">
|
||||
</div>
|
||||
<div class="FieldGroup">
|
||||
<label class="FieldGroup-label">Body</label>
|
||||
<textarea id="email-editor" class="Input"></textarea>
|
||||
</div>
|
||||
<div class="FieldGroup">
|
||||
<label class="FieldGroup-label">
|
||||
Lineup
|
||||
<button class="Button Button--smallSquare" role="button" type="button" onclick="copyEmailTable(this)">
|
||||
{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-fill.svg"}}}
|
||||
</button>
|
||||
</label>
|
||||
<div class="lineup-email lineup-table">{{>email_table}}</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="Modal-footer">
|
||||
<button class="Button" role="button" type="button" onclick="mailToLink(this, 'readdle-spark');",
|
||||
data-to="{{user.email}}"
|
||||
data-bcc="{{joinMemberEmailAddresses (filterNonPlayers members)}}">
|
||||
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/mail.svg"}}}
|
||||
Spark Mail
|
||||
</button>
|
||||
<button class="Button" role="button" type="button" onclick="mailToLink(this, 'mailto');",
|
||||
data-to="{{user.email}}"
|
||||
data-bcc="{{joinMemberEmailAddresses (filterNonPlayers members)}}">
|
||||
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/mail.svg"}}}
|
||||
Mail
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
62
src/views/eventlineup/partials/email_table.hbs
Normal file
62
src/views/eventlineup/partials/email_table.hbs
Normal file
@@ -0,0 +1,62 @@
|
||||
<table class="lineup-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="title-cell" colSpan=3>
|
||||
STARTING LINEUP
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each members}}
|
||||
{{#if (isInStartingLineup this)}}
|
||||
<tr>
|
||||
<td class="sequence-cell">
|
||||
{{plus1 this.benchcoach.eventLineupEntry.sequence}}{{#if (hasPositionFlags this.benchcoach.eventLineupEntry.label)}} {{positionFlags this.benchcoach.eventLineupEntry.label}}{{/if}}
|
||||
</td>
|
||||
<td class="name-cell">{{this.lastName}}, {{this.firstName}} – #{{this.jerseyNumber}}</td>
|
||||
<td class="position-label-cell">{{positionLabelWithoutFlags this.benchcoach.eventLineupEntry.label}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<tr>
|
||||
<th class="title-cell" colSpan=3>Starting (Pos. Only)</th>
|
||||
</tr>
|
||||
{{#each members}}
|
||||
{{#if (isInPositionOnly this)}}
|
||||
<tr>
|
||||
<td class="sequence-cell"></td>
|
||||
<td class="name-cell">{{this.lastName}}, {{this.firstName}} – #{{this.jerseyNumber}}</td>
|
||||
<td class="position-label-cell">{{positionLabelWithoutPOFlag this.benchcoach.eventLineupEntry.label}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<tr>
|
||||
<th class="title-cell" colSpan=3>Subs</th>
|
||||
</tr>
|
||||
{{#each members}}
|
||||
{{#if (isInBench this)}}
|
||||
<tr>
|
||||
<td class="sequence-cell">
|
||||
{{availabilityStatusShort this.benchcoach.availability}}
|
||||
</td>
|
||||
<td class="name-cell">{{this.lastName}}, {{this.firstName}} – #{{this.jerseyNumber}}</td>
|
||||
<td class="position-label-cell">{{this.benchcoach.eventLineupEntry.label}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<tr>
|
||||
<th class="title-cell out" colSpan=3>Out</th>
|
||||
</tr>
|
||||
{{#each members}}
|
||||
{{#if (isInOut this)}}
|
||||
<tr>
|
||||
<td class="sequence-cell">
|
||||
{{availabilityStatusShort this.benchcoach.availability}}
|
||||
</td>
|
||||
<td class="name-cell">{{this.lastName}}, {{this.firstName}} – #{{this.jerseyNumber}}</td>
|
||||
<td class="position-label-cell">{{this.benchcoach.eventLineupEntry.label}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
109
src/views/eventlineup/partials/slot.hbs
Normal file
109
src/views/eventlineup/partials/slot.hbs
Normal file
@@ -0,0 +1,109 @@
|
||||
<div class="Panel-expandableRow lineup-slot" data-initial-lineup-segment="{{initial_lineup_segment}}">
|
||||
<input type="hidden" name="label" value="{{eventLineupEntry.label}}">
|
||||
<input type="hidden" name="flags" value="{{flagsString eventLineupEntry.flags}}">
|
||||
<input type="hidden" name="sequence" value="{{eventLineupEntry.sequence}}">
|
||||
<input type="hidden" name="eventId" value="{{event.id}}">
|
||||
<input type="hidden" name="eventLineupEntryId" value="{{eventLineupEntry.id}}">
|
||||
<input type="hidden" name="availabilityStatusCode", value="{{#if availability}}{{availability.statusCode}}{{/if}}">
|
||||
<input type="hidden" name="memberId" value="{{member.id}}">
|
||||
<input type="hidden" name="lastName" value="{{member.lastName}}">
|
||||
<input type="hidden" name="firstName" value="{{member.firstName}}">
|
||||
<input type="hidden" name="jerseyNumber" value="{{member.jerseyNumber}}">
|
||||
<input type="hidden" name="emailAddresses" value="{{member.emailAddresses}}">
|
||||
<div class="Panel-row Panel-row--withCells Panel-row--parent">
|
||||
<div
|
||||
class="Panel-cell Panel-cell--header">
|
||||
<div class="sequence u-textNoWrap u-fontSizeLg"></div>
|
||||
</div>
|
||||
<div class="Panel-cell u-padXs u-sizeFill u-flex">
|
||||
<div
|
||||
class="Popup availability-status-code-{{
|
||||
availability?.statusCode
|
||||
}}"
|
||||
>
|
||||
{{#if availability}}
|
||||
{{#with availability}}
|
||||
<div class="Popup">
|
||||
<button class="Popup-toggle Button Button--smallSquare {{avail_status_code_class statusCode}}"
|
||||
type="button"
|
||||
data-control="popup"
|
||||
data-open="availablility-popup-{{eventId}}-{{memberId}}"
|
||||
>
|
||||
{{#if notes}}{{{embeddedSvgFromPath "/bootstrap-icons/asterisk.svg"}}}{{else}}{{{avail_status_code_icon statusCode}}}{{/if}}
|
||||
</button>
|
||||
<div class="Popup-container Popup-container--left" data-popup="availablility-popup-{{eventId}}-{{memberId}}">
|
||||
<div class="Popup-content u-padSm u-textCenter">
|
||||
<h3 class="u-spaceBottomSm">Availability</h3>
|
||||
{{#if notes}}
|
||||
<p class="u-textLeft">“ <i>{{notes}}</i> ”</p>
|
||||
{{else}}
|
||||
<p class="u-textLeft">No notes.</p>
|
||||
{{/if}}
|
||||
<button type="button" class="Button u-spaceTopSm" onclick="sendAvailabilityReminder(this, {{eventId}}, ['{{memberId}}'], {{csrfToken}})">
|
||||
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/send.svg"}}}
|
||||
<span>Send Reminder</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/with}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="u-fontSizeLg u-textNoWrap">
|
||||
<span class="lastname">
|
||||
{{member.lastName}}
|
||||
</span>
|
||||
<span class="lastname u-hidden u-sm-inline">
|
||||
, {{member.firstName}}
|
||||
</span>
|
||||
<span class="jerseynumber u-hidden u-sm-inline u-fontSizeSm">
|
||||
#{{member.jerseyNumber}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="u-flexGrow1"></div>
|
||||
<button type="button" class="Button Button--smallSquare addToBench" onclick="moveToLineupSegment(this, 'bench');this.blur()">
|
||||
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg"}}}
|
||||
</button>
|
||||
<button type="button" class="Button Button--smallSquare addToStarting" onclick="moveToLineupSegment(this, 'starting');this.blur()">
|
||||
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/plus.svg"}}}
|
||||
</button>
|
||||
<div class="Popup">
|
||||
<button type="button" class="Popup-toggle Button Button--smallSquare" onclick="this.closest('div').querySelector('.Popup-container').classList.toggle('is-open');this.blur();" href="javascript:void(0)">
|
||||
{{{embeddedSvgFromPath "/bootstrap-icons/three-dots.svg"}}}
|
||||
</button>
|
||||
<div class="Popup-container Popup-container--rightHang position-label-flags">
|
||||
<div class="Popup-content u-padSm u-textCenter">
|
||||
<div class="Checkbox Checkbox--inline">
|
||||
<input class="Checkbox-input" type="checkbox" name="DRd" id="flag-drd-{{member.id}}-{{eventLineupEntry.id}}">
|
||||
<label class="Checkbox-label" for="flag-drd-{{member.id}}-{{eventLineupEntry.id}}">DR<small>d</small></label>
|
||||
</div>
|
||||
<div class="Checkbox Checkbox--inline">
|
||||
<input class="Checkbox-input" type="checkbox" name="DHd" id="flag-dhd-{{member.id}}-{{eventLineupEntry.id}}">
|
||||
<label class="Checkbox-label" for="flag-dhd-{{member.id}}-{{eventLineupEntry.id}}">DH<small>d</small></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Panel-cell u-padXs u-sizeFit">
|
||||
<div class="SelectBox position-selection">
|
||||
<select name="positionLabelSelectBox" class="position-select-box SelectBox-options" onchange="onPositionSelectChange(this)" >
|
||||
<option value="--">
|
||||
--
|
||||
</option>
|
||||
{{#each (positions)}}
|
||||
<option value="{{this}}" {{#if (comparePositionWithFlags this ../eventLineupEntry)}}selected{{/if}}>
|
||||
{{this}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Panel-cell u-padSidesMd u-sizeFit">
|
||||
<div class="drag-handle">
|
||||
{{{embeddedSvgFromPath "/bootstrap-icons/grip-vertical.svg"}}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
0
src/views/eventlineup/show.hbs
Normal file
0
src/views/eventlineup/show.hbs
Normal file
22
src/views/eventsheet/partials/defense_pane.hbs
Normal file
22
src/views/eventsheet/partials/defense_pane.hbs
Normal file
@@ -0,0 +1,22 @@
|
||||
<div class="field-container">
|
||||
{{{embeddedSvgFromPath "/media/baseball-diamond.svg" 'baseball-diamond'}}}
|
||||
{{#defenseLineup event_lineup_entries members}}
|
||||
<div class="slot-set pos-{{this.position}}">
|
||||
<table class="striped">
|
||||
<tbody>
|
||||
<tr class="slot">
|
||||
<th class="position"></th>
|
||||
<td class="player-name">{{this.member.lastName}}</td>
|
||||
</tr>
|
||||
<tr class="slot substitute">
|
||||
<th class="position"></th>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="slot substitute">
|
||||
<th class="position"></th><td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{/defenseLineup}}
|
||||
</div>
|
||||
20
src/views/eventsheet/partials/offense_pane.hbs
Normal file
20
src/views/eventsheet/partials/offense_pane.hbs
Normal file
@@ -0,0 +1,20 @@
|
||||
<table>
|
||||
<tbody>
|
||||
<div class="slot-set">
|
||||
{{!-- <% offensive_lineup_entries = by_member.select{|m,d| d[:event_lineup_entry] and d[:event_lineup_entry].label.exclude?("[PO]")}.sort_by{|m,d| d[:event_lineup_entry].sequence}.each_with_index do |(member, d), i| if i < 11%> --}}
|
||||
{{#offenseLineup 11 event_lineup_entries members}}
|
||||
<tr class="slot">
|
||||
<th class="sequence counter" rowspan="2"></th>
|
||||
<td class="player-name">{{this.member.lastName}}</td>
|
||||
<td class="jersey-number">{{this.member.jerseyNumber}}</td>
|
||||
<td class="position">{{positionLabelWithoutFlags this.label}}</td>
|
||||
</tr>
|
||||
<tr class="slot substitute">
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{{/offenseLineup}}
|
||||
</div>
|
||||
</tbody>
|
||||
</table>
|
||||
86
src/views/eventsheet/partials/roster_and_history.hbs
Normal file
86
src/views/eventsheet/partials/roster_and_history.hbs
Normal file
@@ -0,0 +1,86 @@
|
||||
<table>
|
||||
{{!-- <colgroup><col span="4" class="player"></colgroup> --}}
|
||||
{{!-- <colgroup><col span="1" class="spacer"></colgroup> --}}
|
||||
{{!-- <colgroup><col span="1" class="player-stats"></colgroup> --}}
|
||||
{{!-- <colgroup><col span="4" class="position-capability"></colgroup>
|
||||
<colgroup><col span="4" class="availability-on-day future"></colgroup>
|
||||
<colgroup><col span="4" class="availability-on-day past"></colgroup> --}}
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4" id="today-availability">
|
||||
Available ({{availabilitySummary.playerGoingCount}}|{{availabilitySummary.playerMaybeCount}})
|
||||
</th>
|
||||
<th class="spacer first-of-group last-of-group"></th>
|
||||
<th class="player-stats">
|
||||
<span class="decimal-point">.</span>AVG
|
||||
<span class="delimiter">/</span>
|
||||
<span class="decimal-point">.</span>OBP
|
||||
<span class="delimiter">/</span>
|
||||
<span class="decimal-point">.</span>SLG
|
||||
<span class="delimiter">:</span>PA
|
||||
</th>
|
||||
<th class="position-capability pitcher first-of-group">P</th>
|
||||
<th class="position-capability catcher">C</th>
|
||||
<th class="position-capability infield">I</th>
|
||||
<th class="position-capability outfield last-of-group">O</th>
|
||||
{{!-- <% for timepoint, i in timeline.select{|tp| tp[:comparison_to_selected]>0}.sort{|tp| -tp[:comparison_to_selected]}.each_with_index do%> --}}
|
||||
|
||||
{{#loopEvents upcoming_events}}
|
||||
<th class="availability-on-day avail-today-plus-{{@index}} {{#ifEquals @index 0}}first-of-group{{/ifEquals}}{{#ifEquals @index 3}}last-of-group{{/ifEquals}}" date="{{this.startDate}}"><div>{{dateFormat this.startDate "ddd" }}</div></th>
|
||||
{{/loopEvents}}
|
||||
{{#loopEvents recent_events}}
|
||||
<th class="availability-on-day avail-today-minus-{{@index}} {{#ifEquals @index 0}}first-of-group{{/ifEquals}}{{#ifEquals @index 3}}last-of-group{{/ifEquals}}" date="{{this.startDate}}"><div>{{dateFormat this.startDate "ddd" }}</div></th>
|
||||
{{/loopEvents}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{!-- <% by_member.select{|m,d| !m.is_non_player}.each_with_index do |(member, d), i|%> --}}
|
||||
{{#rosterHistory event event_lineup_entries members availabilities}}
|
||||
<tr class="roster-history-slot{{#if (isStarting this)}} starting-today{{/if}}">
|
||||
<td class="is-present-checkbox available-status-code-{{this.benchcoach.availability.statusCode}} first-of-group">
|
||||
<span>■</span>
|
||||
</td>
|
||||
<td class="jersey-number available-status-code-{{this.benchcoach.availability.statusCode}}">
|
||||
{{this.jerseyNumber}}
|
||||
</td>
|
||||
<td class="player-name available-status-code-{{this.benchcoach.availability.statusCode}}">
|
||||
{{this.lastName}}
|
||||
</td>
|
||||
<td class="position available-status-code-{{this.benchcoach.availability.statusCode}} last-of-group">
|
||||
<span>{{this.benchcoach.eventLineupEntry.label}}</span>
|
||||
</td>
|
||||
<td class="spacer"></td>
|
||||
<td class="player-stats first-of-group last-of-group">
|
||||
<span class="decimal-point">.</span>
|
||||
<span class="avg">000</span>
|
||||
<span class="delimiter">/</span>
|
||||
<span class="decimal-point">.</span>
|
||||
<span class="obp">000</span>
|
||||
<span class="delimiter">/</span>
|
||||
<span class="decimal-point">.</span>
|
||||
<span class="slg">000</span>
|
||||
<span class="delimiter">:</span>
|
||||
<span class="pa">00</span>
|
||||
</td>
|
||||
<td class="position-capability pitcher first-of-group">{{positionCapabilityFor this "P"}}</td>
|
||||
<td class="position-capability catcher">{{positionCapabilityFor this "C"}}</td>
|
||||
<td class="position-capability infield">{{positionCapabilityFor this "IF"}}</td>
|
||||
<td class="position-capability outfield last-of-group">{{positionCapabilityFor this "OF"}}</td>
|
||||
{{#loopEvents ../upcoming_events}}
|
||||
{{#timepointForMember ../this ../../timeline this}}
|
||||
<td class="availability-on-day future available-status-code-{{this.availability.statusCode}} {{this.value}} {{#ifEquals @index 0}}first-of-group{{/ifEquals}}{{#ifEquals @index 3}}last-of-group{{/ifEquals}}">
|
||||
{{this.value}}
|
||||
</td>
|
||||
{{/timepointForMember}}
|
||||
{{/loopEvents}}
|
||||
{{#loopEvents ../recent_events}}
|
||||
{{#timepointForMember ../this ../../timeline this}}
|
||||
<td class="availability-on-day past available-status-code-{{this.availability.statusCode}} {{this.value}} {{#ifEquals @index 0}}first-of-group{{/ifEquals}}{{#ifEquals @index 3}}last-of-group{{/ifEquals}}">
|
||||
{{this.value}}
|
||||
</td>
|
||||
{{/timepointForMember}}
|
||||
{{/loopEvents}}
|
||||
</tr>
|
||||
{{/rosterHistory}}
|
||||
</tbody>
|
||||
</table>
|
||||
277
src/views/eventsheet/sheet.hbs
Normal file
277
src/views/eventsheet/sheet.hbs
Normal file
@@ -0,0 +1,277 @@
|
||||
<link rel="stylesheet" href="/css/eventsheet.css">
|
||||
|
||||
<body class="{{#if sheet_size}}{{sheet_size}}{{else}}B5{{/if}}">
|
||||
<div class="sheet eventsheet {{#if sheet_layout}}{{sheet_layout}}{{else}}quarters{{/if}}" id="page-1">
|
||||
<section class="NE" id="defense-card">
|
||||
<header>
|
||||
<div class="event-title float-left">
|
||||
{{event.formattedTitle}}
|
||||
</div>
|
||||
<div class="homeaway float-right">
|
||||
{{event.gameType}}
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<div id="defense-pane">
|
||||
{{> defense_pane event_lineup_entries=event_lineup_entries members=members}}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<table class="notes">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
{{#repeat 3}}
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
{{/repeat}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="SW" id="roster-and-history">
|
||||
<div class="container">
|
||||
{{> roster_and_history
|
||||
event=event
|
||||
event_lineup_entries=event_lineup_entries
|
||||
members=members availabilities=availabilities
|
||||
recent_events=recent_events
|
||||
upcoming_events=upcoming_events
|
||||
}}
|
||||
</div>
|
||||
</section>
|
||||
<section class="NW lineup-card dugout" id="lineup-card-dugout">
|
||||
<header>
|
||||
<div class="float-left event-title">{{event.formattedTitle}}</div>
|
||||
<div class="float-right homeaway">{{event.gameType}}</div>
|
||||
</header>
|
||||
<div class="starting-lineup-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4">Starting</th>
|
||||
<th class="substitution">Substitution</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#offenseLineup 11 event_lineup_entries members}}
|
||||
<tr class="slot">
|
||||
<th class="sequence{{#if this.member.lastName}} counter{{/if}}"></th>
|
||||
<td class="player-name">{{this.member.lastName}}</td>
|
||||
<td class="jersey-number">{{this.member.jerseyNumber}}</td>
|
||||
<td class="position">{{this.label}}</td>
|
||||
<td class="substitution"></td>
|
||||
</tr>
|
||||
{{/offenseLineup }}
|
||||
{{#defenseLineup event_lineup_entries members}}
|
||||
<tr class="slot">
|
||||
{{#if (isInPositionOnly this.member)}}{{#if (comparePositionWithFlags "P" this.eventLineupEntry)}}
|
||||
<th class="sequence">PO</th>
|
||||
<td class="player-name">{{this.member.lastName}}</td>
|
||||
<td class="jersey-number">{{this.member.jerseyNumber}}</td>
|
||||
<td class="position">{{positionLabelWithoutFlags this.eventLineupEntry.label}}</td>
|
||||
<td class="substitution"></td>
|
||||
{{/if}}{{/if}}
|
||||
</tr>
|
||||
{{/defenseLineup}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<section class="SE lineup-card exchange" id="lineup-card-exchange">
|
||||
<header>
|
||||
<div class="float-left event-title">{{event.formattedTitleForMultiTeam}}</div>
|
||||
<div class="float-right homeaway">{{event.gameType}}</div>
|
||||
</header>
|
||||
<div class="starting-lineup-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4">Starting</th>
|
||||
<th class="substitution">Substitution</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#offenseLineup 11 event_lineup_entries members}}
|
||||
<tr class="slot">
|
||||
<th class="sequence {{#if this.member.lastName}}counter{{/if}}"></th>
|
||||
<td class="player-name">{{this.member.lastName}}</td>
|
||||
<td class="jersey-number">{{this.member.jerseyNumber}}</td>
|
||||
<td class="position">{{this.label}}</td>
|
||||
<td class="substitution"></td>
|
||||
</tr>
|
||||
{{/offenseLineup}}
|
||||
{{#defenseLineup event_lineup_entries members}}
|
||||
<tr class="slot">
|
||||
{{#if (isInPositionOnly this.member)}}{{#if (comparePositionWithFlags "P" this.eventLineupEntry)}}
|
||||
<th class="sequence">PO</th>
|
||||
<td class="player-name">{{this.member.lastName}}</td>
|
||||
<td class="jersey-number">{{this.member.jerseyNumber}}</td>
|
||||
<td class="position">{{positionLabelWithoutFlags this.eventLineupEntry.label}}</td>
|
||||
<td class="substitution"></td>
|
||||
{{/if}}{{/if}}
|
||||
</tr>
|
||||
{{/defenseLineup}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="sheet eventsheet {{#if sheet_layout}}{{sheet_layout}}{{else}}quarters{{/if}}" id="page-2">
|
||||
<section class="SE" id="front-cover">
|
||||
<header>
|
||||
<div class="game-number">
|
||||
{{event.label}}
|
||||
</div>
|
||||
<div class="title">
|
||||
<span class="date-time">{{dateFormat event.startDate "ddd, MMM D h:mm A" }}</span>
|
||||
<span class="location">{{event.locationName}}</span>
|
||||
</div>
|
||||
<div class="homeaway">
|
||||
<span>{{firstLetter event.gameType}}</span>
|
||||
</div>
|
||||
</header>
|
||||
<div style="display:block;max-height: 1em;background-color: lightgray;border-bottom: solid 2px black;">
|
||||
</div>
|
||||
<div class="head-to-head">
|
||||
<div class="team">
|
||||
<img src="{{team_preferences.links.teamLogo.href}}">
|
||||
<div>
|
||||
<span class="name">{{team.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{# if event.opponentName}}
|
||||
<div class="conjuction">
|
||||
<span>vs</span>
|
||||
</div>
|
||||
<div class="opponent">
|
||||
<div>
|
||||
<span class="name">{{event.opponentName}}</span>
|
||||
</div>
|
||||
{{#if opponent_logo.mediumUrl }}
|
||||
<img src="{{opponent_logo.mediumUrl}}">
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Final Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{{team.name}}</th>
|
||||
<th>{{event.opponentName}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="height:5em;width: 50%;"></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</section>
|
||||
<section class="NW blank" id="defense-card">
|
||||
<header>
|
||||
<div class="event-title float-left">
|
||||
|
||||
</div>
|
||||
<div class="homeaway float-right">
|
||||
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<div id="defense-pane">
|
||||
{{> defense_pane event_lineup_entries=null members=null}}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<table class="notes">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
{{#repeat 3}}
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
{{/repeat}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="SW lineup-card exchange blank" id="lineup-card-exchange-blank">
|
||||
<header></header>
|
||||
<div class="starting-lineup-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4">
|
||||
Starting
|
||||
</th>
|
||||
<th class="substitution">
|
||||
Substitution
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#repeat 12}}
|
||||
<tr class="slot">
|
||||
<th class="sequence">
|
||||
</th>
|
||||
<td class="player-name">
|
||||
</td>
|
||||
<td class="jersey-number">
|
||||
</td>
|
||||
<td class="position">
|
||||
</td>
|
||||
<td class="substitution">
|
||||
</td>
|
||||
</tr>
|
||||
{{/repeat}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<section class="NE lineup-card dugout blank" id="lineup-card-dugout-blank">
|
||||
<header></header>
|
||||
<div class="starting-lineup-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4">
|
||||
Starting
|
||||
</th>
|
||||
<th class="substitution">
|
||||
Substitution
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{!-- <% for i in (0...12) do%> --}}
|
||||
{{#repeat 12}}
|
||||
<tr class="slot">
|
||||
<th class="sequence">
|
||||
</th>
|
||||
<td class="player-name">
|
||||
</td>
|
||||
<td class="jersey-number">
|
||||
</td>
|
||||
<td class="position">
|
||||
</td>
|
||||
<td class="substitution">
|
||||
</td>
|
||||
</tr>
|
||||
{{/repeat}}
|
||||
{{!-- <% end %> --}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
248
src/views/eventsheet/sheet_blank.hbs
Normal file
248
src/views/eventsheet/sheet_blank.hbs
Normal file
@@ -0,0 +1,248 @@
|
||||
<link rel="stylesheet" href="/css/eventsheet.css">
|
||||
|
||||
<body class="{{page_size}} ">
|
||||
<div class="sheet eventsheet {{layout}}" id="page-1">
|
||||
<section class="NW" id="roster-and-history">
|
||||
<div class="roster-and-history">
|
||||
{{> roster_and_history
|
||||
event=event
|
||||
event_lineup_entries=event_lineup_entries
|
||||
members=members availabilities=availabilities
|
||||
recent_events=recent_events
|
||||
upcoming_events=upcoming_events
|
||||
}}
|
||||
</div>
|
||||
</section>
|
||||
<section class="NE blank" id="defense-card">
|
||||
<header>
|
||||
<div class="event-title float-left">
|
||||
</div>
|
||||
<div class="homeaway float-right">
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<div id="defense-pane">
|
||||
{{> defense_pane event_lineup_entries=event_lineup_entries members=members}}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<table class="notes">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
{{#repeat 3}}
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
{{/repeat}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="SW lineup-card dugout blank" id="lineup-card-dugout">
|
||||
<header>
|
||||
<div class="float-left event-title">{{event.formattedTitle}}</div>
|
||||
<div class="float-right homeaway">{{event.gameType}}</div>
|
||||
</header>
|
||||
<div class="starting-lineup-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4">Starting</th>
|
||||
<th class="substitution">Substitution</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#offenseLineup 11 event_lineup_entries members}}
|
||||
<tr class="slot">
|
||||
<th class="sequence{{#if this.member.lastName}} counter{{/if}}"></th>
|
||||
<td class="player-name">{{this.member.lastName}}</td>
|
||||
<td class="jersey-number">{{this.member.jerseyNumber}}</td>
|
||||
<td class="position">{{this.label}}</td>
|
||||
<td class="substitution"></td>
|
||||
</tr>
|
||||
{{/offenseLineup }}
|
||||
{{#defenseLineup event_lineup_entries members}}
|
||||
<tr class="slot">
|
||||
{{#if (isInPositionOnly this.member)}}{{#if (comparePositionWithFlags "P" this.eventLineupEntry)}}
|
||||
<th class="sequence">PO</th>
|
||||
<td class="player-name">{{this.member.lastName}}</td>
|
||||
<td class="jersey-number">{{this.member.jerseyNumber}}</td>
|
||||
<td class="position">{{positionLabelWithoutFlags this.eventLineupEntry.label}}</td>
|
||||
<td class="substitution"></td>
|
||||
{{/if}}{{/if}}
|
||||
</tr>
|
||||
{{/defenseLineup}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<section class="SE lineup-card exchange blank" id="lineup-card-exchange">
|
||||
<header>
|
||||
<div class="float-left event-title">{{event.formattedTitle}}</div>
|
||||
<div class="float-right homeaway">{{event.gameType}}</div>
|
||||
</header>
|
||||
<div class="starting-lineup-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4">Starting</th>
|
||||
<th class="substitution">Substitution</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#offenseLineup 11 event_lineup_entries members}}
|
||||
<tr class="slot">
|
||||
<th class="sequence {{#if this.member.lastName}}counter{{/if}}"></th>
|
||||
<td class="player-name">{{this.member.lastName}}</td>
|
||||
<td class="jersey-number">{{this.member.jerseyNumber}}</td>
|
||||
<td class="position">{{this.label}}</td>
|
||||
<td class="substitution"></td>
|
||||
</tr>
|
||||
{{/offenseLineup}}
|
||||
{{#defenseLineup event_lineup_entries members}}
|
||||
<tr class="slot">
|
||||
{{#if (isInPositionOnly this.member)}}{{#if (comparePositionWithFlags "P" this.eventLineupEntry)}}
|
||||
<th class="sequence">PO</th>
|
||||
<td class="player-name">{{this.member.lastName}}</td>
|
||||
<td class="jersey-number">{{this.member.jerseyNumber}}</td>
|
||||
<td class="position">{{positionLabelWithoutFlags this.eventLineupEntry.label}}</td>
|
||||
<td class="substitution"></td>
|
||||
{{/if}}{{/if}}
|
||||
</tr>
|
||||
{{/defenseLineup}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="sheet eventsheet {{layout}}" id="page-2">
|
||||
<section class="NW blank" id="defense-card">
|
||||
<header>
|
||||
<div class="event-title float-left">
|
||||
|
||||
</div>
|
||||
<div class="homeaway float-right">
|
||||
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<div id="defense-pane">
|
||||
{{> defense_pane event_lineup_entries=null members=null}}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<table class="notes">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
{{#repeat 3}}
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
{{/repeat}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="NE blank" id="defense-card">
|
||||
<header>
|
||||
<div class="event-title float-left">
|
||||
|
||||
</div>
|
||||
<div class="homeaway float-right">
|
||||
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<div id="defense-pane">
|
||||
{{> defense_pane event_lineup_entries=null members=null}}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<table class="notes">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
{{#repeat 3}}
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
{{/repeat}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="SW lineup-card exchange blank" id="lineup-card-exchange-blank">
|
||||
<header></header>
|
||||
<div class="starting-lineup-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4">
|
||||
Starting
|
||||
</th>
|
||||
<th class="substitution">
|
||||
Substitution
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#repeat 12}}
|
||||
<tr class="slot">
|
||||
<th class="sequence">
|
||||
</th>
|
||||
<td class="player-name">
|
||||
</td>
|
||||
<td class="jersey-number">
|
||||
</td>
|
||||
<td class="position">
|
||||
</td>
|
||||
<td class="substitution">
|
||||
</td>
|
||||
</tr>
|
||||
{{/repeat}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<section class="SE lineup-card dugout blank" id="lineup-card-dugout-blank">
|
||||
<header></header>
|
||||
<div class="starting-lineup-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4">
|
||||
Starting
|
||||
</th>
|
||||
<th class="substitution">
|
||||
Substitution
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{!-- <% for i in (0...12) do%> --}}
|
||||
{{#repeat 12}}
|
||||
<tr class="slot">
|
||||
<th class="sequence">
|
||||
</th>
|
||||
<td class="player-name">
|
||||
</td>
|
||||
<td class="jersey-number">
|
||||
</td>
|
||||
<td class="position">
|
||||
</td>
|
||||
<td class="substitution">
|
||||
</td>
|
||||
</tr>
|
||||
{{/repeat}}
|
||||
{{!-- <% end %> --}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
33
src/views/layouts/main.hbs
Normal file
33
src/views/layouts/main.hbs
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="stylesheet" href="/css/application.css">
|
||||
{{#if style}}<link rel="stylesheet" href="/css/{{style}}">{{/if}}
|
||||
<title>{{#if title}}{{title}}{{else}}BenchCoach{{/if}}</title>
|
||||
<script>
|
||||
async function setSession() {
|
||||
const response = await fetch("/auth/teamsnap/session_storage")
|
||||
const session_storage = await response.json();
|
||||
for (const [key, value] of Object.entries(session_storage)) {
|
||||
window.sessionStorage.setItem(key,value)
|
||||
}
|
||||
}
|
||||
setSession();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="u-spaceBottomMd">
|
||||
{{> navbar }}
|
||||
{{{_sections.header}}}
|
||||
</header>
|
||||
<main class="u-xs-spaceSidesMd">
|
||||
{{{ body }}}
|
||||
</main>
|
||||
</body>
|
||||
{{{script_tags scripts}}}
|
||||
</html>
|
||||
10
src/views/login.hbs
Normal file
10
src/views/login.hbs
Normal file
@@ -0,0 +1,10 @@
|
||||
<div class="Panel u-maxWidthXs u-padLg u-spaceSidesAuto">
|
||||
<h1 class="u-textCenter">Sign in</h1>
|
||||
<p class="u-spaceEndsMd">Sign into BenchCoach using your TeamSnap account</p>
|
||||
<a class="Button Button--large Button--orange u-spaceSidesAuto btn--Full" href="/login/federated/teamsnap">
|
||||
{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}
|
||||
<span>TeamSnap</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
26
src/views/modal_confirm.hbs
Normal file
26
src/views/modal_confirm.hbs
Normal file
@@ -0,0 +1,26 @@
|
||||
<div id="modal" class="Modal Modal--clickableBg">
|
||||
<div class="Modal-content">
|
||||
<div onclick="javascript:this.closest('.Modal').remove();">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg" "Modal-iconDismiss"}}}</div>
|
||||
<div class="Modal-header">
|
||||
<div class="Modal-title">{{title}}</div>
|
||||
</div>
|
||||
<div class="Modal-body">
|
||||
{{body}}
|
||||
</div>
|
||||
<div class="Modal-footer">
|
||||
<button class="Button Button--negative" role="button" type="button" onclick="javascript:this.closest('.Modal').remove();" data-confirm="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="Button Button--primary" role="button" type="button" data-confirm="yes">
|
||||
<span class="hideOnLoading">Yes</span>
|
||||
<span id="success-icon" class="u-hidden showOnSuccess">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/check.svg"}}}</span>
|
||||
<span id="failure-icon" class="u-hidden showOnFailure">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg"}}}</span>
|
||||
<span class="PulseAnimation showOnLoading u-hidden">
|
||||
<span class="PulseAnimation-dot"></span>
|
||||
<span class="PulseAnimation-dot"></span>
|
||||
<span class="PulseAnimation-dot"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
15
src/views/opponent/list.hbs
Normal file
15
src/views/opponent/list.hbs
Normal file
@@ -0,0 +1,15 @@
|
||||
<h1>
|
||||
{{title}}
|
||||
<hr class="Divider" />
|
||||
</h1>
|
||||
<div class="Panel">
|
||||
<div class="Panel-body">
|
||||
{{#each opponents}}
|
||||
<div class="Panel-row Panel-row--withCells u-textDecorationNone">
|
||||
<a class="Panel-cell" href="opponent/{{this.id}}">
|
||||
{{this.name}}
|
||||
</a>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
79
src/views/opponent/show.hbs
Normal file
79
src/views/opponent/show.hbs
Normal file
@@ -0,0 +1,79 @@
|
||||
<div class="Panel">
|
||||
<div class="Panel-header">
|
||||
<div class="Panel-title">
|
||||
{{opponent.name}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="Panel-body">
|
||||
<div class="Panel-row Panel-row--withCells">
|
||||
<div class="Panel-cell Panel-cell--header">
|
||||
ID
|
||||
</div>
|
||||
<div class="Panel-cell">
|
||||
{{opponent.id}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="Panel-row Panel-row--withCells">
|
||||
<div class="Panel-cell Panel-cell--header">
|
||||
Contact Name
|
||||
</div>
|
||||
<div class="Panel-cell">
|
||||
{{opponent.contactsName}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="Panel-row Panel-row--withCells">
|
||||
<div class="Panel-cell Panel-cell--header">
|
||||
Contact Phone
|
||||
</div>
|
||||
<div class="Panel-cell">
|
||||
{{opponent.contactsPhone}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="Panel-row Panel-row--withCells">
|
||||
<div class="Panel-cell Panel-cell--header">
|
||||
Contact Email
|
||||
</div>
|
||||
<div class="Panel-cell">
|
||||
{{opponent.contactsEmail}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="Panel-row Panel-row--withCells">
|
||||
<div class="Panel-cell Panel-cell--header">
|
||||
Logo
|
||||
</div>
|
||||
<div class="Panel-cell">
|
||||
{{#if opponent_logo}}
|
||||
<img src="{{opponent_logo.mediumUrl}}" width="64" height="64" />
|
||||
{{else}}
|
||||
<form name="upload-opponent-logo" action="{{opponent.id}}/upload_logo" method="post" enctype="multipart/form-data" hidden>
|
||||
{{!-- THIS DOESN'T WORK XHR2 ERRORS (FORMDATA) --}}
|
||||
<input type="hidden" name="_csrf" value="{{csrfToken}}">
|
||||
<input type="hidden" name="csrfToken" value="{{csrfToken}}">
|
||||
<input type="hidden" name="teamMediaGroupId" value="{{team_media_group.id}}">
|
||||
<input type="hidden" name="memberId" value="{{member.id}}">
|
||||
<input type="hidden" name="teamId" value="{{team.id}}">
|
||||
<input type="hidden" name="description" value="team-logo-{{opponent.id}}.png">
|
||||
<div class="FieldGroup">
|
||||
<label for='file' class="FieldGroup-label">Select files</label>
|
||||
<input id='file' name='file' type="file">
|
||||
<button class="submit-btn Button" type="submit">Upload</button>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
class="Button"
|
||||
onclick="navigator.clipboard.writeText('opponent-logo-{{
|
||||
opponent.id
|
||||
}}.png');window.open('https://go.teamsnap.com/{{team.id}}/files/list/{{team_media_group.id}}');"
|
||||
>
|
||||
<img class="Icon" src="/bootstrap-icons/clipboard.svg" />
|
||||
<span>
|
||||
Copy Filename and Go to Teamsnap
|
||||
</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/js/teamsnap.min.js"></script>
|
||||
<script src="/js/opponent.js"></script>
|
||||
39
src/views/partials/navbar.hbs
Normal file
39
src/views/partials/navbar.hbs
Normal file
@@ -0,0 +1,39 @@
|
||||
<div class="Header-banner">
|
||||
<a href="/" class="">
|
||||
<div class="Header-bannerLogo">
|
||||
<img class="logo" src="/media/benchcoach.svg" alt="BenchCoach Logo">
|
||||
</div>
|
||||
<div class="Header-bannerTitle">
|
||||
BenchCoach
|
||||
</div>
|
||||
</a>
|
||||
{{#if user}}
|
||||
<div class="filler"></div>
|
||||
<div class="u-padSidesSm u-spaceAuto">
|
||||
<div class="Popup">
|
||||
<div class="Button Button--small Popup-toggle" onclick="this.closest('.Popup').querySelector('.Popup-container').classList.toggle('is-open')">
|
||||
Account
|
||||
</div>
|
||||
<div class="Popup-container Popup-container--down Popup-container--right u-sizeFit">
|
||||
<div class="Popup-content u-padXs u-sizeFit u-fontSizeSm">
|
||||
<h6 class="h6 title u-textNoWrap u-fontSizeSm u-textSemiBold">{{user.first_name}} {{user.last_name}}</h6>
|
||||
<div class="u-textNoWrap u-fontSizeSm">{{user.email}}</div>
|
||||
<hr class="Divider u-spaceEndsNone">
|
||||
<div class="u-padBottomSm u-padTopSm">
|
||||
<a href="/user/{{user.id}}/teams" class="u-spaceBottomSm Button Button--small">
|
||||
<span>{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/team.svg"}}}</span>
|
||||
Teams
|
||||
</a>
|
||||
<form method="post" action="/logout">
|
||||
<button type="submit" name="logout" class="u-spaceBottomSm Button Button--small">
|
||||
Logout
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
51
src/views/team/home.hbs
Normal file
51
src/views/team/home.hbs
Normal file
@@ -0,0 +1,51 @@
|
||||
<div class=" Grid u-spaceBottomSm">
|
||||
<div class=" Grid-cell u-spaceNone u-sizeFit">
|
||||
<img src="{{team_preferences.links.teamLogo.href}}", style="height: 64px;">
|
||||
</div>
|
||||
<div class=" Grid-cell u-sizeFill u-spaceNone">
|
||||
<h2 class=" u-spaceNone">{{team.name}}</h2>
|
||||
<p class=" u-spaceNone">{{team.season_name}}</p>
|
||||
<small class=" u-spaceNone"><a href="{{team.leagueUrl}}">{{team.leagueName}}</a></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class=" Grid-cell u-sizeFit u-padTopSm u-padBottomSm">
|
||||
<div class=" u-flex u-flexAlignItemsCenter u-flexJustifyEnd">
|
||||
<div class="ButtonGroup">
|
||||
<a class="Button" href="schedule">
|
||||
<img src ="/teamsnap-ui/assets/icons/schedule.svg" class="Icon">
|
||||
<span class="span u-hidden u-xs-inline">Schedule</span>
|
||||
</a>
|
||||
<a class="Button u-hidden" href="members">
|
||||
<img src ="/teamsnap-ui/assets/icons/roster.svg" class="Icon">
|
||||
<span class="span u-hidden u-xs-inline">Roster</span>
|
||||
</a>
|
||||
<a class="Button" href="opponents">
|
||||
<img src ="/teamsnap-ui/assets/icons/team.svg" class="Icon">
|
||||
<span class="span u-hidden u-xs-inline">Opponents</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<h2 class=" ">Upcoming Events</h2>
|
||||
<hr class="Divider" />
|
||||
<div class="Grid Grid--withGutter">
|
||||
{{#each upcoming_events}}
|
||||
<div class="Grid-cell u-xs-size1of2 u-sm-size1of3">
|
||||
{{>event_panel event=this}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<h2 class="">Recent Events</h2>
|
||||
<hr class="Divider" />
|
||||
<div class="Grid Grid--withGutter">
|
||||
{{#each recent_events}}
|
||||
<div class="Grid-cell u-xs-size1of2 u-sm-size1of3">
|
||||
{{>event_panel event=this}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
49
src/views/team/list.hbs
Normal file
49
src/views/team/list.hbs
Normal file
@@ -0,0 +1,49 @@
|
||||
<div class=" Panel u-sm-size1of2 u-spaceSidesAuto u-spaceSm u-maxWidthSm">
|
||||
<div class="Panel-header">
|
||||
<h3 class="Panel-title">
|
||||
My Teams
|
||||
</h3>
|
||||
</div>
|
||||
<div class="Panel-body">
|
||||
{{#each teams}}
|
||||
{{#unless this.isArchivedSeason}}
|
||||
<a
|
||||
class="Panel-row Panel-row--withCells u-textDecorationNone"
|
||||
href="/{{this.id}}/home"
|
||||
>
|
||||
<div class=" Panel-cell u-flexGrow1 u-textNoWrap">
|
||||
<b>
|
||||
{{this.name}}
|
||||
</b>
|
||||
</div>
|
||||
<div class=" Panel-cell u-fontSizeSm u-size1of3 u-textRight">
|
||||
{{this.seasonName}}
|
||||
</div>
|
||||
</a>
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
<div class="Panel-header">
|
||||
<h3 class="Panel-title">
|
||||
Archived Seasons
|
||||
</h3>
|
||||
</div>
|
||||
{{#each teams}}
|
||||
{{#if this.isArchivedSeason}}
|
||||
<a
|
||||
class="Panel-row Panel-row--withCells u-textDecorationNone"
|
||||
href="/{{this.id}}/home"
|
||||
>
|
||||
<div class=" Panel-cell u-flexGrow1 u-textNoWrap">
|
||||
<b>
|
||||
{{this.name}}
|
||||
</b>
|
||||
</div>
|
||||
<div class=" Panel-cell u-fontSizeSm u-size1of3 u-textRight">
|
||||
{{this.seasonName}}
|
||||
</div>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,8 +0,0 @@
|
||||
html
|
||||
head
|
||||
body
|
||||
h1 error
|
||||
h2
|
||||
error.status
|
||||
pre
|
||||
message #{message}
|
||||
@@ -1,46 +0,0 @@
|
||||
html
|
||||
head
|
||||
meta(charset='utf-8')
|
||||
meta(name='viewport' content='width=device-width, initial-scale=1')
|
||||
title #{event.formattedTitle}
|
||||
link(rel='stylesheet' href='/css/bootstrap.min.css')
|
||||
link(rel='stylesheet' href='/font/bootstrap-icons.min.css')
|
||||
link(rel='stylesheet' href='/css/teamsnap-ui.css')
|
||||
body
|
||||
.container
|
||||
.Panel
|
||||
.Panel-header
|
||||
h3.Panel-title #{event.formattedTitle}
|
||||
.Panel-body
|
||||
.Panel-row
|
||||
h6.card-text.text-muted.mb-2
|
||||
|#{event.startDate}
|
||||
br
|
||||
|#{event.locationName}
|
||||
.Panel-row
|
||||
h4 Availability
|
||||
.progress
|
||||
div(class="progress-bar bg-success fw-bold" role="progressbar" style=`
|
||||
width: ${((availabilitySummary.playerGoingCount/team.playerMemberCount)*100).toString() + "%"}`)
|
||||
|#{availabilitySummary.playerGoingCount}
|
||||
div(class="progress-bar bg-info fw-bold" role="progressbar" style=`
|
||||
width: ${((availabilitySummary.playerMaybeCount/team.playerMemberCount)*100).toString() + "%"}`)
|
||||
|#{availabilitySummary.playerMaybeCount}
|
||||
div(class="progress-bar bg-danger fw-bold" role="progressbar" style=`
|
||||
width: ${((availabilitySummary.playerNotGoingCount/team.playerMemberCount)*100).toString() + "%"}`)
|
||||
|#{availabilitySummary.playerNotGoingCount}
|
||||
div(class="progress-bar text-secondary fw-bold" role="progressbar" style=`
|
||||
width: ${((availabilitySummary.playerUnknownCount/team.playerMemberCount)*100).toString() + "%"};
|
||||
background-color: var(--bs-gray-200)`)
|
||||
|#{availabilitySummary.playerUnknownCount}
|
||||
hr
|
||||
div.d-flex
|
||||
a(class="Button m-auto" href=`/${team_id}/event/${event.id}/lineup`)
|
||||
i(class="bi bi-clipboard")
|
||||
span.mx-1 Lineup
|
||||
a(class="Button m-auto" href=`/${team_id}/event/${event.id}/gamecard`)
|
||||
i(class="bi bi-book")
|
||||
span.mx-1 Game Card
|
||||
a(class="Button m-auto" href=`https://go.teamsnap.com/${team_id}/schedule/view_game/${event.id}`)
|
||||
i(class="bi bi-asterisk")
|
||||
span.mx-1 TeamSnap
|
||||
@@ -1,37 +0,0 @@
|
||||
html
|
||||
head
|
||||
meta(charset='utf-8')
|
||||
meta(name='viewport' content='width=device-width, initial-scale=1')
|
||||
title BenchCoach - Teams
|
||||
link(rel='stylesheet' href='/css/bootstrap.min.css')
|
||||
link(rel='stylesheet' href='/css/teamsnap-ui.css')
|
||||
body
|
||||
.container
|
||||
.Panel
|
||||
.Panel-header
|
||||
.Panel-title Schedule
|
||||
.Panel-body
|
||||
each event in events
|
||||
- var availabilitySummary = availabilitySummaries.find((a)=>a.eventId==event.id)
|
||||
.Panel-row
|
||||
a(class="event list-group-item" href=`/${team_id}/event/${event.id}`)
|
||||
h4 #{event.formattedTitle}
|
||||
p.small
|
||||
| #{event.startDate.toLocaleDateString("en-us",{weekday: "short", day: "numeric",month: "short"})}
|
||||
| #{event.startDate.toLocaleTimeString("en-us",{hour: "numeric", minute: "2-digit"})}
|
||||
p #{event.locationName}
|
||||
.progress
|
||||
div(class="progress-bar bg-success fw-bold" role="progressbar" style=`
|
||||
width: ${((availabilitySummary.playerGoingCount/team.playerMemberCount)*100).toString() + "%"}`)
|
||||
|#{availabilitySummary.playerGoingCount}
|
||||
div(class="progress-bar bg-info fw-bold" role="progressbar" style=`
|
||||
width: ${((availabilitySummary.playerMaybeCount/team.playerMemberCount)*100).toString() + "%"}`)
|
||||
|#{availabilitySummary.playerMaybeCount}
|
||||
div(class="progress-bar bg-danger fw-bold" role="progressbar" style=`
|
||||
width: ${((availabilitySummary.playerNotGoingCount/team.playerMemberCount)*100).toString() + "%"}`)
|
||||
|#{availabilitySummary.playerNotGoingCount}
|
||||
div(class="progress-bar text-secondary fw-bold" role="progressbar" style=`
|
||||
width: ${((availabilitySummary.playerUnknownCount/team.playerMemberCount)*100).toString() + "%"};
|
||||
background-color: var(--bs-gray-200)`)
|
||||
|#{availabilitySummary.playerUnknownCount}
|
||||
|
||||
@@ -1,409 +0,0 @@
|
||||
html
|
||||
head
|
||||
meta(charset='utf-8')
|
||||
title #{event.formattedTitle}
|
||||
link(rel='stylesheet' href='/css/gamecard.css')
|
||||
|
||||
body(class="B5")
|
||||
input(name="team_id", type="hidden" value=`${team_id}`)
|
||||
input(name="event_id", type="hidden" value=`${event_id}`)
|
||||
#page-1.sheet.gamecard
|
||||
section#todays-game
|
||||
.grid-container
|
||||
.section-header
|
||||
#todays-game-header.bar-left.event-title
|
||||
| #{event.formattedTitle}
|
||||
| #{event.startDate.toLocaleDateString("en-us",{weekday: "short", day: "numeric",month: "short"})}
|
||||
| #{event.startDate.toLocaleTimeString("en-us",{hour: "numeric", minute: "2-digit"})}
|
||||
.bar-right.homeaway #{event.gameType}
|
||||
.bar-span.gametitle
|
||||
#offense-pane.left
|
||||
table#starting-lineup-offense
|
||||
tbody
|
||||
each _, i in Array(11)
|
||||
- if (typeof(event_lineup_entries_offense[i]) !== 'undefined'){
|
||||
tr
|
||||
th(rowspan='2') #{i+1}
|
||||
td(id=`offense-slot-${i}-name` class="player-name") #{event_lineup_entries_offense[i].member.lastName}
|
||||
td(id=`offense-slot-${i}-jersey-number` class="jersey-number") #{event_lineup_entries_offense[i].member.jerseyNumber}
|
||||
td(id=`offense-slot-${i}-position` class="position") #{event_lineup_entries_offense[i].label}
|
||||
tr.substitute
|
||||
td
|
||||
td
|
||||
td
|
||||
- } else {
|
||||
tr
|
||||
th(rowspan='2')
|
||||
td(id=`offense-slot-${i}-name` class="player-name")
|
||||
td(id=`offense-slot-${i}-jersey-number` class="jersey-number")
|
||||
td(id=`offense-slot-${i}-position` class="position")
|
||||
tr.substitute
|
||||
td
|
||||
td
|
||||
td
|
||||
- }
|
||||
|
||||
#defense-pane.right
|
||||
.container
|
||||
.field-container
|
||||
image(src='/media/baseball-diamond.svg')
|
||||
.row(style='justify-content: center')
|
||||
.defense-slot-set
|
||||
table
|
||||
tr
|
||||
th.position CF
|
||||
td#defense-slot-CF-name.player-name
|
||||
| #{(event_lineup_entries.find((lue)=>lue.label.startsWith("CF")) || {"member":{}}).member.lastName}
|
||||
tr
|
||||
td(colspan='2')
|
||||
tr
|
||||
td(colspan='2')
|
||||
.row(style='justify-content: space-between')
|
||||
.defense-slot-set
|
||||
table
|
||||
tr
|
||||
th.position LF
|
||||
td#defense-slot-LF-name.player-name
|
||||
| #{(event_lineup_entries.find((lue)=>lue.label.startsWith("LF")) || {"member":{}}).member.lastName}
|
||||
tr
|
||||
td(colspan='2')
|
||||
tr
|
||||
td(colspan='2')
|
||||
.defense-slot-set
|
||||
table
|
||||
tr
|
||||
th.position RF
|
||||
td#defense-slot-RF-name.player-name
|
||||
| #{(event_lineup_entries.find((lue)=>lue.label.startsWith("RF")) || {"member":{}}).member.lastName}
|
||||
tr
|
||||
td(colspan='2')
|
||||
tr
|
||||
td(colspan='2')
|
||||
.row(style='justify-content: space-around')
|
||||
.defense-slot-set
|
||||
table
|
||||
tr
|
||||
th.position SS
|
||||
td#defense-slot-SS-name.player-name
|
||||
| #{(event_lineup_entries.find((lue)=>lue.label.startsWith("SS")) || {"member":{}}).member.lastName}
|
||||
tr
|
||||
td(colspan='2')
|
||||
tr
|
||||
td(colspan='2')
|
||||
.defense-slot-set
|
||||
table
|
||||
tr
|
||||
th.position 2B
|
||||
td#defense-slot-2B-name.player-name
|
||||
| #{(event_lineup_entries.find((lue)=>lue.label.startsWith("2B")) || {"member":{}}).member.lastName}
|
||||
tr
|
||||
td(colspan='2')
|
||||
tr
|
||||
td(colspan='2')
|
||||
.row(style='justify-content: space-between')
|
||||
.defense-slot-set
|
||||
table
|
||||
tr
|
||||
th.position 3B
|
||||
td#defense-slot-3B-name.player-name
|
||||
| #{(event_lineup_entries.find((lue)=>lue.label.startsWith("3B")) || {"member":{}}).member.lastName}
|
||||
tr
|
||||
td(colspan='2')
|
||||
tr
|
||||
td(colspan='2')
|
||||
.defense-slot-set
|
||||
table
|
||||
tr
|
||||
th.position 1B
|
||||
td#defense-slot-1B-name.player-name
|
||||
| #{(event_lineup_entries.find((lue)=>lue.label.startsWith("1B")) || {"member":{}}).member.lastName}
|
||||
tr
|
||||
td(colspan='2')
|
||||
tr
|
||||
td(colspan='2')
|
||||
.row(style='justify-content: center')
|
||||
.defense-slot-set
|
||||
table
|
||||
tr
|
||||
th.position C
|
||||
td#defense-slot-C-name.player-name
|
||||
| #{(event_lineup_entries.find((lue)=>lue.label.startsWith("C") && !lue.label.startsWith("CF") ) || {"member":{}}).member.lastName}
|
||||
tr
|
||||
td(colspan='2')
|
||||
tr
|
||||
td(colspan='2')
|
||||
.pitching-container
|
||||
.defense-slot-set
|
||||
table
|
||||
tr
|
||||
th.position P
|
||||
td#defense-slot-P-name.player-name
|
||||
| #{(event_lineup_entries.find((lue)=>lue.label.startsWith("P")) || {"member":{}}).member.lastName}
|
||||
td.jersey-number
|
||||
| #{(event_lineup_entries.find((lue)=>lue.label.startsWith("P")) || {"member":{}}).member.jerseyNumber}
|
||||
td.position
|
||||
tr
|
||||
th.position RP
|
||||
td#defense-slot-RP1-name.player-name
|
||||
td
|
||||
td
|
||||
tr
|
||||
th.position RP
|
||||
td#defense-slot-RP2-name.player-name
|
||||
td
|
||||
td
|
||||
.footer
|
||||
table
|
||||
tr
|
||||
th Notes
|
||||
td
|
||||
tr
|
||||
td
|
||||
tr
|
||||
td
|
||||
section#roster-and-history
|
||||
div
|
||||
table
|
||||
thead
|
||||
tr
|
||||
th#today-availability(colspan='3') Available (
|
||||
| #{availabilitySummaries.find((e)=>e.id==event_id).playerGoingCount}|
|
||||
| #{availabilitySummaries.find((e)=>e.id==event_id).playerMaybeCount}
|
||||
| )
|
||||
th.player-stats
|
||||
span.decimal-point .
|
||||
| AVG
|
||||
span.delimiter /
|
||||
span.decimal-point .
|
||||
| OBP
|
||||
span.delimiter /
|
||||
span.decimal-point .
|
||||
| SLG
|
||||
span.delimiter :
|
||||
| PA
|
||||
th.position-capability.pitcher P
|
||||
th.position-capability.catcher C
|
||||
th.position-capability.infield I
|
||||
th.position-capability.outfield O
|
||||
each event_future, i in events_future
|
||||
th(id=`avail-header-today-plus-${i+1}` class="availability future")
|
||||
.rotate #{event_future.startDate.toLocaleDateString("en-us", {weekday: "short"})}
|
||||
each event_past, i in events_past
|
||||
th(id=`avail-header-today-minus-${i+1}` class="availability past")
|
||||
.rotate #{event_past.startDate.toLocaleDateString("en-us", {weekday: "short"})}
|
||||
tbody
|
||||
each row, index in availabilities.filter((e)=>e.event.id==event_id && !e.member.isNonPlayer)
|
||||
tr(id=`roster-history-slot-${index+1}` class=``)
|
||||
td(class=`is-present-checkbox available-status-code-${row.statusCode}`)
|
||||
span ■
|
||||
td(
|
||||
class=`
|
||||
jersey-number
|
||||
border-left
|
||||
available-status-code-${row.statusCode}
|
||||
${event_lineup_entries.find((lue)=>lue.member.id==row.member.id) !== undefined ? "starting" : ""}
|
||||
`)
|
||||
| #{row.member.jerseyNumber}
|
||||
td(
|
||||
class=`
|
||||
player-name
|
||||
available-status-code-${row.statusCode}
|
||||
${event_lineup_entries.find((lue)=>lue.member.id==row.member.id) !== undefined ? "starting" : ""}
|
||||
`)
|
||||
| #{row.member.lastName}
|
||||
td.player-stats.border-left.border-right
|
||||
span.decimal-point .
|
||||
span.avg 000
|
||||
span.delimiter /
|
||||
span.decimal-point .
|
||||
span.obp 000
|
||||
span.delimiter /
|
||||
span.decimal-point .
|
||||
span.slg 000
|
||||
span.delimiter :
|
||||
span.pa 00
|
||||
td.position-capability.pitcher #{row.member.position.includes("P") ? "\u2713" : ""}
|
||||
td.position-capability.catcher #{row.member.position.includes("C") ? "\u2713" : ""}
|
||||
td.position-capability.infield #{row.member.position.includes("IF") ? "\u2713" : ""}
|
||||
td.position-capability.outfield #{row.member.position.includes("OF") ? "\u2713" : ""}
|
||||
- var future_availability
|
||||
- var future_lineupEntry
|
||||
each future_event, i in events_future
|
||||
- future_availability = availabilities.find((el)=>el.eventId ==future_event.id && el.memberId==row.member.id)
|
||||
- future_lineupEntry = all_lineup_entries.find((el)=>el.eventId ==future_event.id && el.member.id==row.member.id)
|
||||
- console.log(future_availability)
|
||||
td(id=`avail-${row.member}-today-plus-${i+1}` class=`
|
||||
row
|
||||
future
|
||||
availability
|
||||
available-status-code-${future_availability.statusCode}
|
||||
`)
|
||||
if future_lineupEntry
|
||||
|#{future_lineupEntry.label.slice(0,2)}
|
||||
else
|
||||
|#{future_availability.status[0]}
|
||||
- var past_availability
|
||||
- var past_lineupEntry
|
||||
each past_event, i in events_past
|
||||
- past_availability = availabilities.find((el)=>el.eventId==past_event.id && el.memberId==row.memberId)
|
||||
- past_lineupEntry = all_lineup_entries.find((el)=>el.event.id==past_event.id && el.member.id==row.member.id)
|
||||
td(id=`avail-${row.member}-today-minus-${i+1}` class=`
|
||||
row
|
||||
past
|
||||
availability
|
||||
available-status-code-${past_availability.statusCode}
|
||||
${past_lineupEntry ? "started" : ""}
|
||||
`)
|
||||
if past_lineupEntry
|
||||
|#{past_lineupEntry.label.slice(0,2)}
|
||||
else
|
||||
|#{past_availability.status[0]}
|
||||
tfoot
|
||||
tr
|
||||
th(colspan='3')
|
||||
th
|
||||
th(colspan='4')
|
||||
each event_future, i in events_future
|
||||
th(class=`availability future`)
|
||||
.rotate #{availabilitySummaries.find((el)=>el.eventId == event_future.id).playerGoingCount}
|
||||
th.today-minus-1
|
||||
.rotate
|
||||
th.today-minus-2
|
||||
.rotate
|
||||
th.today-minus-3
|
||||
.rotate
|
||||
th.today-minus-4
|
||||
.rotate
|
||||
section#lineup-card-dugout.lineup-card
|
||||
.grid-container
|
||||
.section-header
|
||||
.bar-left.event-title
|
||||
| #{event.formattedTitle}
|
||||
.bar-right.homeaway #{event.gameType}
|
||||
.starting-lineup-table
|
||||
table
|
||||
thead
|
||||
tr
|
||||
th(colspan='4') Starting
|
||||
tbody
|
||||
each i in [0,1,2,3,4,5,6,7,8,9,10]
|
||||
- if (typeof(event_lineup_entries_offense[i]) !== 'undefined'){
|
||||
tr
|
||||
th.sequence.label #{event_lineup_entries_offense[i].sequence +1}
|
||||
td.player-name #{event_lineup_entries_offense[i].member.lastName}
|
||||
td.jersey-number #{event_lineup_entries_offense[i].member.jerseyNumber}
|
||||
td.position #{event_lineup_entries_offense[i].label}
|
||||
- } else {
|
||||
tr
|
||||
th.sequence.label
|
||||
td.player-name
|
||||
td.jersey-number
|
||||
td.position
|
||||
tr
|
||||
td
|
||||
td
|
||||
td
|
||||
td
|
||||
- }
|
||||
|
||||
.substitution-table
|
||||
table(style='width: 100%')
|
||||
thead
|
||||
tr
|
||||
th Substitution
|
||||
tbody
|
||||
each i in [0,1,2,3,4,5,6,7,8,9,10,11]
|
||||
tr
|
||||
td.substitution
|
||||
tr
|
||||
td.substitution
|
||||
section#lineup-card-exchange.lineup-card
|
||||
.grid-container
|
||||
.section-header.event-title #{event.formattedTitleForMultiTeam}
|
||||
.starting-lineup-table
|
||||
table.starting-lineup-table
|
||||
thead
|
||||
tr
|
||||
th
|
||||
th.player-name Name
|
||||
th.jersey-number Num
|
||||
th.position Pos
|
||||
tbody
|
||||
each _,i in Array(10)
|
||||
- if (typeof(event_lineup_entries_offense[i]) !== 'undefined'){
|
||||
tr
|
||||
th.sequence.label #{event_lineup_entries_offense[i].sequence+1}
|
||||
td.player-name #{event_lineup_entries_offense[i].member.lastName}
|
||||
td.jersey-number #{event_lineup_entries_offense[i].member.jerseyNumber}
|
||||
td.position #{event_lineup_entries_offense[i].label}
|
||||
- } else {
|
||||
tr
|
||||
th.sequence.label
|
||||
td.player-name
|
||||
td.jersey-number
|
||||
td.position
|
||||
tr
|
||||
td
|
||||
td
|
||||
td
|
||||
td
|
||||
- }
|
||||
#page-2.sheet.gamecard
|
||||
section#back-cover
|
||||
section#front-cover
|
||||
div.grid-container
|
||||
.section-header
|
||||
.bar-right.homeaway #{event.gameType}
|
||||
.event-title
|
||||
| #{event.startDate.toLocaleDateString("en-us",{weekday: "long", day: "numeric",month: "short"})},
|
||||
| #{event.startDate.toLocaleTimeString("en-us",{hour: "numeric", minute: "2-digit"})}
|
||||
br
|
||||
| #{event.locationName}
|
||||
div.team
|
||||
|#{event.team.name}
|
||||
div.opponent
|
||||
|#{event.opponent.name}
|
||||
section#lineup-card-dugout-empty.lineup-card
|
||||
.grid-container
|
||||
.section-header
|
||||
.starting-lineup-table
|
||||
table
|
||||
thead
|
||||
tr
|
||||
th(colspan='4') Starting
|
||||
tbody
|
||||
each _ in Array(12)
|
||||
tr
|
||||
th.sequence.label
|
||||
td.player-name
|
||||
td.jersey-number
|
||||
td.position
|
||||
.substitution-table
|
||||
table(style='width: 100%')
|
||||
thead
|
||||
tr
|
||||
th Substitution
|
||||
tbody
|
||||
each _ in Array(11)
|
||||
tr
|
||||
td.substitution
|
||||
tr
|
||||
td.substitution
|
||||
section#lineup-card-exchange-empty.lineup-card
|
||||
.grid-container
|
||||
.section-header
|
||||
.starting-lineup-table
|
||||
table.starting-lineup-table
|
||||
thead
|
||||
tr
|
||||
th
|
||||
th.player-name Name
|
||||
th.jersey-number Num
|
||||
th.position Pos
|
||||
tbody
|
||||
each _ in Array(12)
|
||||
tr
|
||||
th.sequence.label
|
||||
td.player-name
|
||||
td.jersey-number
|
||||
td.position
|
||||
@@ -1,29 +0,0 @@
|
||||
head
|
||||
meta(charset='utf-8')
|
||||
meta(name='viewport' content='width=device-width, initial-scale=1')
|
||||
title BenchCoach - Home
|
||||
link(rel='stylesheet' href='/css/bootstrap.min.css')
|
||||
link(rel='stylesheet' href='/css/project.css')
|
||||
body.bg-light
|
||||
.container
|
||||
.row
|
||||
.text-center.my-2
|
||||
.row
|
||||
h1
|
||||
img.mx-auto(src="media/benchcoach.svg" style="width: 2.5em;")
|
||||
.row
|
||||
h1
|
||||
strong
|
||||
| Welcome to
|
||||
span.text-nowrap BenchCoach
|
||||
.text-center.lead.fst-italic.fw-light
|
||||
| An assistant coach for TeamSnap
|
||||
.row
|
||||
.col.text-center
|
||||
if req.user
|
||||
ul.list-group
|
||||
each team in teams
|
||||
a(class='team list-group-item' href=`/${team.id}`) #{team.name} [#{team.seasonName}]
|
||||
else
|
||||
a.btn.btn-outline-primary(href="login")
|
||||
| Login
|
||||
139
views/lineup.pug
139
views/lineup.pug
@@ -1,139 +0,0 @@
|
||||
html
|
||||
head
|
||||
meta(charset='utf-8')
|
||||
title #{event.formattedTitle}
|
||||
link(rel='stylesheet' href='/css/bootstrap.min.css')
|
||||
link(rel='stylesheet' href='/font/bootstrap-icons.min.css')
|
||||
link(rel='stylesheet' href='/css/teamsnap-ui.css')
|
||||
|
||||
body.bg-light
|
||||
.container
|
||||
div(style="max-width: 455px")
|
||||
.Panel
|
||||
.panel-header
|
||||
.Panel-title #{event.formattedTitle}
|
||||
.Panel-body
|
||||
.Panel-row
|
||||
p.text-muted.mb-2 #{event.startDate}
|
||||
p #{event.locationName}
|
||||
.Panel
|
||||
.Panel-body
|
||||
.Panel-row.Panel-title.u-padXs
|
||||
i.bi.bi-clipboard-check.me-1
|
||||
span Starting Lineup
|
||||
.Panel-row.Grid.Grid--fit.fw-bold.text-center.u-padXs
|
||||
each pos in ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "EH", "DH"]
|
||||
if event_lineup_entries.map((lue)=>lue.label).includes(pos)
|
||||
.Grid-cell.text-success
|
||||
|#{pos}
|
||||
else
|
||||
.Grid-cell.text-danger
|
||||
|#{pos}
|
||||
each lineup_entry, i in event_lineup_entries_offense
|
||||
.Panel-row.Panel-row--withCells
|
||||
.Panel-cell.Panel-cell--header.u-padXs.u-size1of12
|
||||
.u-flexAlignSelfCenter
|
||||
|#{i+1}
|
||||
.Panel-cell.u-padXs.u-size8of12.fw-bold.text-uppercase
|
||||
if lineup_entry.availabilityStatusCode == 2
|
||||
i.bi.bi-question-circle-fill.text-info.u-spaceRightXs
|
||||
else if lineup_entry.availabilityStatusCode == 1
|
||||
i.bi.bi-check-circle-fill.text-success.u-spaceRightXs
|
||||
else if lineup_entry.availabilityStatusCode == 0
|
||||
i.bi.bi-x-circle-fill.text-danger.u-spaceRightXs
|
||||
else
|
||||
i.bi.bi-question-circle.u-spaceRightXs
|
||||
|#{lineup_entry.member.lastName}
|
||||
.Panel-cell.u-padXs.u-size2of12
|
||||
.SelectBox
|
||||
select.SelectBox-options
|
||||
each pos in ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "EH", "DH"]
|
||||
option(selected=lineup_entry.label==pos) #{pos}
|
||||
.Panel-cell.u-padXs.u-flexAlignSelfCenter.u-size1of12
|
||||
.drag-handle
|
||||
i.bi.bi-grip-vertical.text-secondary
|
||||
.Panel
|
||||
.Panel-body
|
||||
.Panel-row.Panel-title.u-padXs
|
||||
i.bi.bi-clipboard-minus.me-1
|
||||
span Starting Lineup (Position Only)
|
||||
each lineup_entry, i in event_lineup_entries
|
||||
if lineup_entry.label.includes("[PO]")
|
||||
.Panel-row.Panel-row--withCells
|
||||
.Panel-cell.Panel-cell--header.u-padXs.u-size1of12
|
||||
.u-flexAlignSelfCenter
|
||||
|#{i+1}
|
||||
.Panel-cell.u-padXs.u-size8of12.fw-bold.text-uppercase
|
||||
if lineup_entry.availabilityStatusCode == 2
|
||||
i.bi.bi-question-circle-fill.text-info.u-spaceRightXs
|
||||
else if lineup_entry.availabilityStatusCode == 1
|
||||
i.bi.bi-check-circle-fill.text-success.u-spaceRightXs
|
||||
else if lineup_entry.availabilityStatusCode == 0
|
||||
i.bi.bi-x-circle-fill.text-danger.u-spaceRightXs
|
||||
else
|
||||
i.bi.bi-question-circle.u-spaceRightXs
|
||||
|#{lineup_entry.member.lastName}
|
||||
.Panel-cell.u-padXs.u-size2of12
|
||||
.SelectBox
|
||||
select.SelectBox-options
|
||||
each pos in ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "EH", "DH"]
|
||||
option(selected=lineup_entry.label==pos) #{pos}
|
||||
.Panel-cell.u-padXs.u-flexAlignSelfCenter.u-size1of12
|
||||
.drag-handle
|
||||
i.bi.bi-grip-vertical.text-secondary
|
||||
.Panel
|
||||
.Panel-body
|
||||
.Panel-row.Panel-title.u-padXs
|
||||
i.bi.bi-clipboard.me-1
|
||||
span Bench
|
||||
each availability, i in availabilities.filter((a)=>a.eventId==event_id && !context.event_lineup_entries.map((lue)=>lue.memberId).includes(a.memberId) && !a.member.isNonPlayer && a.statusCode!=0 && a.statusCode!==null)
|
||||
.Panel-row.Panel-row--withCells
|
||||
.Panel-cell.Panel-cell--header.u-padXs.u-size1of12
|
||||
.u-flexAlignSelfCenter
|
||||
.Panel-cell.u-padXs.u-size8of12.fw-bold.text-uppercase
|
||||
if availability.statusCode == 2
|
||||
i.bi.bi-question-circle-fill.text-info.u-spaceRightXs
|
||||
else if availability.statusCode == 1
|
||||
i.bi.bi-check-circle-fill.text-success.u-spaceRightXs
|
||||
else if availability.statusCode == 0
|
||||
i.bi.bi-x-circle-fill.text-danger.u-spaceRightXs
|
||||
else
|
||||
i.bi.bi-question-circle.u-spaceRightXs
|
||||
|#{availability.member.lastName}
|
||||
.Panel-cell.u-padXs.u-size2of12
|
||||
.SelectBox
|
||||
select.SelectBox-options
|
||||
each pos in ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "EH", "DH"]
|
||||
option #{pos}
|
||||
.Panel-cell.u-padXs.u-flexAlignSelfCenter.u-size1of12
|
||||
.drag-handle
|
||||
i.bi.bi-grip-vertical.text-secondary
|
||||
|
||||
.Panel
|
||||
.Panel-body
|
||||
.Panel-row.Panel-title.u-padXs
|
||||
i.bi.bi-clipboard-x.me-1
|
||||
span Out
|
||||
each availability, i in availabilities.filter((a)=>a.eventId==event_id && !context.event_lineup_entries.map((lue)=>lue.memberId).includes(a.memberId) && !a.member.isNonPlayer && (a.statusCode==0 || a.statusCode===null))
|
||||
.Panel-row.Panel-row--withCells
|
||||
.Panel-cell.Panel-cell--header.u-padXs.u-size1of12
|
||||
.u-flexAlignSelfCenter
|
||||
.Panel-cell.u-padXs.u-size8of12.fw-bold.text-uppercase
|
||||
if availability.statusCode == 2
|
||||
i.bi.bi-question-circle-fill.text-info.u-spaceRightXs
|
||||
else if availability.statusCode == 1
|
||||
i.bi.bi-check-circle-fill.text-success.u-spaceRightXs
|
||||
else if availability.statusCode == 0
|
||||
i.bi.bi-x-circle-fill.text-danger.u-spaceRightXs
|
||||
else
|
||||
i.bi.bi-question-circle.u-spaceRightXs
|
||||
|#{availability.member.lastName}
|
||||
.Panel-cell.u-padXs.u-size2of12
|
||||
.SelectBox
|
||||
select.SelectBox-options
|
||||
each pos in ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "EH", "DH"]
|
||||
option #{pos}
|
||||
.Panel-cell.u-padXs.u-flexAlignSelfCenter.u-size1of12
|
||||
.drag-handle
|
||||
i.bi.bi-grip-vertical.text-secondary
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
html
|
||||
head
|
||||
meta(charset='utf-8')
|
||||
meta(name='viewport' content='width=device-width, initial-scale=1')
|
||||
title BenchCoach - Login
|
||||
link(rel='stylesheet' href='/css/bootstrap.min.css')
|
||||
link(rel='stylesheet' href='/font/bootstrap-icons.min.css')
|
||||
link(rel='stylesheet' href='/css/teamsnap-ui.css')
|
||||
body
|
||||
.u-padSidesMd.u-xs-padSidesLg
|
||||
.Panel.u-padLg
|
||||
h3 BenchCoach
|
||||
p Sign in
|
||||
|
||||
a(class="Button Button--large Button--orange" href="/login/federated/teamsnap")
|
||||
i(class="bi bi-asterisk")/
|
||||
span Sign in with TeamSnap
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
html
|
||||
head
|
||||
meta(charset='utf-8')
|
||||
meta(name='viewport' content='width=device-width, initial-scale=1')
|
||||
title BenchCoach - #{team.name}
|
||||
link(rel='stylesheet' href='/css/bootstrap.min.css')
|
||||
link(rel='stylesheet' href='/font/bootstrap-icons.min.css')
|
||||
link(rel='stylesheet' href='/css/teamsnap-ui.css')
|
||||
|
||||
body
|
||||
.container
|
||||
h2 #{team.name}
|
||||
p #{team.seasonName}
|
||||
hr
|
||||
ul.list-group
|
||||
a(class="list-group-item" href=`${team.id}/events`) Events
|
||||
a(class="list-group-item" href=`${team.id}/roster`) Roster
|
||||
@@ -1,14 +0,0 @@
|
||||
html
|
||||
head
|
||||
meta(charset='utf-8')
|
||||
meta(name='viewport' content='width=device-width, initial-scale=1')
|
||||
title BenchCoach - Teams
|
||||
link(rel='stylesheet' href='/css/bootstrap.min.css')
|
||||
link(rel='stylesheet' href='/font/bootstrap-icons.min.css')
|
||||
link(rel='stylesheet' href='/css/teamsnap-ui.css')
|
||||
|
||||
body
|
||||
ul
|
||||
each team in teams
|
||||
li
|
||||
a(class='team' href=`/${team.id}`) #{team.name} [#{team.seasonName}]
|
||||
Reference in New Issue
Block a user