Compare commits
91 Commits
d72ff726a5
...
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
|
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
@@ -4,10 +4,22 @@
|
||||
// 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",
|
||||
"name": "nodemon (dev)",
|
||||
"program": "dev",
|
||||
"request": "launch",
|
||||
"restart": true,
|
||||
@@ -16,8 +28,8 @@
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node",
|
||||
"env": {"NODE_ENV": "development"},
|
||||
"preLaunchTask": "npm: scss"
|
||||
"env": {"NODE_ENV": "development", "DEBUG": "app"},
|
||||
"preLaunchTask": "npm: build-css"
|
||||
}
|
||||
]
|
||||
}
|
||||
16
.vscode/tasks.json
vendored
16
.vscode/tasks.json
vendored
@@ -3,17 +3,21 @@
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "scss",
|
||||
"script": "watch-scss",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: scss watch",
|
||||
"detail": "sass --watch src/scss/application.scss public/css/application.css src/scss/eventsheet.scss:public/css/eventsheet.css"
|
||||
"label": "npm: watch-scss",
|
||||
"detail": "npm run watch-css",
|
||||
"icon": {
|
||||
"id": "eye",
|
||||
"color": "terminal.ansiBlue"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "scss",
|
||||
"script": "build-css",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: scss",
|
||||
"detail": "sass src/scss/application.scss:public/css/application.css src/scss/eventsheet.scss:public/css/eventsheet.css"
|
||||
"label": "npm: build-css",
|
||||
"detail": "npm build-css"
|
||||
}
|
||||
]
|
||||
}
|
||||
21
bin/www
21
bin/www
@@ -10,8 +10,7 @@ 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.
|
||||
@@ -19,30 +18,16 @@ const path = require("path");
|
||||
|
||||
var port = normalizePort(process.env.PORT || "3000");
|
||||
app.set("port", port);
|
||||
|
||||
var server = http.createServer(app);
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
/**
|
||||
* Create HTTPS server.
|
||||
*/
|
||||
const https_options = {
|
||||
key: fs.readFileSync("certs/key.pem"),
|
||||
cert: fs.readFileSync("certs/cert.pem"),
|
||||
};
|
||||
var livereload = require("livereload");
|
||||
|
||||
const liveReloadServer = livereload.createServer({https: https_options});
|
||||
const liveReloadServer = livereload.createServer({port:35729});
|
||||
liveReloadServer.watch(path.join(__dirname, "../src/views"));
|
||||
liveReloadServer.server.once("connection", () => {
|
||||
setTimeout(() => {
|
||||
liveReloadServer.refresh("/");
|
||||
}, 100);
|
||||
});
|
||||
|
||||
var server = https.createServer(https_options, app);
|
||||
}
|
||||
else {
|
||||
var server = http.createServer(app);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
{$DOMAIN} {
|
||||
reverse_proxy app:3000
|
||||
{
|
||||
{$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
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,33 @@
|
||||
version: "3.3"
|
||||
|
||||
services:
|
||||
app:
|
||||
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:
|
||||
@@ -17,6 +35,8 @@ services:
|
||||
- 443:443
|
||||
volumes:
|
||||
- ./caddy/Caddyfile:/etc/caddy/Caddyfile
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
networks:
|
||||
- web
|
||||
env_file:
|
||||
@@ -24,3 +44,7 @@ services:
|
||||
|
||||
networks:
|
||||
web:
|
||||
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
71
package-lock.json
generated
71
package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@teamsnap/teamsnap-ui": "^3.12.3",
|
||||
"better-sqlite3": "^9.4.1",
|
||||
"better-sqlite3": "^9.6.0",
|
||||
"better-sqlite3-session-store": "^0.1.0",
|
||||
"bootstrap": "^5.3.1",
|
||||
"bootstrap-icons": "^1.10.5",
|
||||
@@ -33,6 +33,7 @@
|
||||
"passport-teamsnap": "^1.1.1",
|
||||
"pluralize": "^8.0.0",
|
||||
"pug": "^3.0.2",
|
||||
"sass": "^1.77.2",
|
||||
"sortablejs": "^1.15.0",
|
||||
"teamsnap.js": "github:anthonyscorrea/teamsnap-javascript-sdk#link-with-null-link",
|
||||
"tinymce": "^6.8.3",
|
||||
@@ -663,7 +664,6 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
@@ -813,9 +813,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/better-sqlite3": {
|
||||
"version": "9.4.1",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.4.1.tgz",
|
||||
"integrity": "sha512-QpqiQeMI4WkE+dQ68zTMX5OzlPGc7lXIDP1iKUt4Omt9PdaVgzKYxHIJRIzt1E+RUBQoFmkip/IbvzyrxehAIg==",
|
||||
"version": "9.6.0",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.6.0.tgz",
|
||||
"integrity": "sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
@@ -834,7 +834,6 @@
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -997,7 +996,6 @@
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
},
|
||||
@@ -1169,7 +1167,6 @@
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
@@ -1618,9 +1615,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
|
||||
"integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -2040,7 +2037,6 @@
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@@ -2195,7 +2191,6 @@
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2304,7 +2299,6 @@
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
@@ -2564,6 +2558,11 @@
|
||||
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz",
|
||||
"integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ=="
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
@@ -2632,7 +2631,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
},
|
||||
@@ -2704,7 +2702,6 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -2722,7 +2719,6 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
@@ -2745,7 +2741,6 @@
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
@@ -3270,9 +3265,9 @@
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.54.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.54.0.tgz",
|
||||
"integrity": "sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==",
|
||||
"version": "3.62.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz",
|
||||
"integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
@@ -3373,7 +3368,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -3667,7 +3661,6 @@
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
@@ -3719,9 +3712,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
|
||||
"integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==",
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz",
|
||||
"integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
@@ -4078,7 +4071,6 @@
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
@@ -4228,6 +4220,22 @@
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.77.2",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.77.2.tgz",
|
||||
"integrity": "sha512-eb4GZt1C3avsX3heBNlrc7I09nyT00IUuo4eFhAbeXWU2fvA7oXI53SxODVAA+zgZCk9aunAZgO+losjR3fAwA==",
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||
@@ -4466,6 +4474,14 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
@@ -4735,7 +4751,6 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
|
||||
13
package.json
13
package.json
@@ -24,13 +24,17 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./bin/www",
|
||||
"dev": "nodemon . & npm run scss",
|
||||
"scss": "sass src/scss/application.scss:src/public/css/application.css src/scss/eventsheet.scss:src/public/css/eventsheet.css",
|
||||
"scss watch": "sass --watch src/scss/application.scss:src/public/css/application.css src/scss/eventsheet.scss:src/public/css/eventsheet.css"
|
||||
"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.4.1",
|
||||
"better-sqlite3": "^9.6.0",
|
||||
"better-sqlite3-session-store": "^0.1.0",
|
||||
"bootstrap": "^5.3.1",
|
||||
"bootstrap-icons": "^1.10.5",
|
||||
@@ -54,6 +58,7 @@
|
||||
"passport-teamsnap": "^1.1.1",
|
||||
"pluralize": "^8.0.0",
|
||||
"pug": "^3.0.2",
|
||||
"sass": "^1.77.2",
|
||||
"sortablejs": "^1.15.0",
|
||||
"teamsnap.js": "github:anthonyscorrea/teamsnap-javascript-sdk#link-with-null-link",
|
||||
"tinymce": "^6.8.3",
|
||||
|
||||
27
src/app.js
27
src/app.js
@@ -43,6 +43,16 @@ hbs.registerHelper('section', (name, options) => {
|
||||
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)
|
||||
@@ -52,12 +62,14 @@ 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(connectLiveReload());
|
||||
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());
|
||||
app.use(bodyParser.urlencoded({extended: true }));
|
||||
app.use(logger("dev"));
|
||||
app.use(cors(corsOptions))
|
||||
app.use(cookieParser());
|
||||
@@ -108,8 +120,10 @@ app.use(
|
||||
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
|
||||
@@ -142,7 +156,7 @@ app.use(require("./routes/eventsheet").router)
|
||||
app.use(function (err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
if (err) {
|
||||
res.locals.message = err.message;
|
||||
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 = {
|
||||
@@ -159,9 +173,10 @@ app.use(function (err, req, res, next) {
|
||||
});
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
// app.use(function (req, res, next) {
|
||||
// next(createError(404));
|
||||
// });
|
||||
app.use(function (req, res, next) {
|
||||
// next(createError(404));
|
||||
res.status(404).send('not found')
|
||||
});
|
||||
|
||||
app.set('trust proxy')
|
||||
|
||||
|
||||
@@ -21,6 +21,10 @@ exports.helpers = {
|
||||
|
||||
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"]
|
||||
@@ -67,3 +71,53 @@ exports.getEvent = async (req, res, next) => {
|
||||
};
|
||||
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'))
|
||||
}
|
||||
@@ -1,91 +1,51 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const {embeddedSvgFromPath, parsePositionLabel, compilePositionLabel} = require("../lib/utils")
|
||||
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")
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
exports.helpers = {
|
||||
flagsString: (flags) => flags?.join(","),
|
||||
plus1: (i) => Number(i)+1,
|
||||
positions: () => ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "EH", "DH"],
|
||||
defense_positions: () => ["C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "P"],
|
||||
avail_status_code_icon: (status_code) => {
|
||||
const icon_classes = {
|
||||
1: "u-colorPositive",
|
||||
0: "u-colorNegative",
|
||||
2: "u-colorPrimary",
|
||||
null: "u-colorGrey",
|
||||
undefined: "u-colorGrey"
|
||||
}
|
||||
|
||||
const button_classes = {
|
||||
1: "Button--yes",
|
||||
0: "Button--no",
|
||||
2: "Button--maybe",
|
||||
null: "",
|
||||
undefined: ""
|
||||
}
|
||||
|
||||
return `<button class="Button Button--smallSquare ${button_classes[status_code]}" type="button"><span class="">${statusCodeIcons[status_code]}</span></button>`
|
||||
},
|
||||
positionLabelWithoutFlags: (label) => {
|
||||
return label.replace(/(.*?)\s\[(.*?)\]/, "$1");
|
||||
},
|
||||
comparePositionWithFlags: (labelWithoutFlags, eventLineupEntry, options) => {
|
||||
labelWithFlags = eventLineupEntry?.label
|
||||
const {positionLabelWithoutFlags} = parsePositionLabel(labelWithFlags);
|
||||
return positionLabelWithoutFlags == labelWithoutFlags;
|
||||
},
|
||||
isStarting: (member) => {
|
||||
return (member.benchcoach?.eventLineupEntry != null);
|
||||
},
|
||||
isInStartingLineup: (member) => {
|
||||
if (member.benchcoach.eventLineupEntry == null || member.benchcoach.eventLineupEntry.label == '') return false;
|
||||
const {positionFlags} = parsePositionLabel(member.benchcoach.eventLineupEntry?.label);
|
||||
return (positionFlags != "PO")
|
||||
},
|
||||
isInPositionOnly: (member) => {
|
||||
if (!member.benchcoach || member.benchcoach.eventLineupEntry == null) return false;
|
||||
const {positionFlags} = parsePositionLabel(member.benchcoach.eventLineupEntry?.label);
|
||||
return (member.benchcoach.eventLineupEntry != null && positionFlags == "PO")
|
||||
},
|
||||
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)
|
||||
},
|
||||
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)
|
||||
},
|
||||
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.helpers = require('../helpers/eventlineup.js')
|
||||
|
||||
exports.getEventLineup = async (req, res)=>{
|
||||
// res.send(req.event_lineup)
|
||||
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)
|
||||
res.render("eventlineup/edit", {user, team, members, event, layout, event_lineup, event_lineup_entries, availabilitySummary, csrfToken})
|
||||
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) => {
|
||||
@@ -98,7 +58,7 @@ attachBenchcoachPropertiesToMember = (members, event_lineup_entries, availabilit
|
||||
// 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 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) {
|
||||
@@ -125,7 +85,11 @@ exports.getEventLineupEmail = async (req, res)=>{
|
||||
const {newEventLineupEntries} = processPostedEventLineupEntries(body, eventLineupEntries, event_lineup)
|
||||
attachBenchcoachPropertiesToMember(members, newEventLineupEntries, availabilities)
|
||||
members.sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName)
|
||||
res.status(200).render("eventlineup/partials/email_table", {user, team, members, event, event_lineup, event_lineup_entries: newEventLineupEntries, availabilities, availabilitySummary})
|
||||
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)=>{
|
||||
@@ -143,28 +107,48 @@ exports.postEventLineup = async (req,res) => {
|
||||
if (body.memberId == null) {res.status(400).end();return}
|
||||
await Promise.all(req.promises);
|
||||
const eventLineupEntries = req.event_lineup.eventLineupEntries
|
||||
const {newEventLineupEntries} = processPostedEventLineupEntries(body, eventLineupEntries, req.event_lineup)
|
||||
const {newEventLineupEntries, deleteEventLineupEntries} = processPostedEventLineupEntries(body, eventLineupEntries, req.event_lineup)
|
||||
newEventLineupEntries.forEach(e=>{
|
||||
teamsnap.saveEventLineupEntry(e)
|
||||
teamsnap.saveEventLineupEntry(e, teamsnapCallback)
|
||||
})
|
||||
eventLineup = await teamsnap.loadEventLineups(req.params.event_id)
|
||||
res.status(201).end()
|
||||
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 != '') {
|
||||
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
|
||||
@@ -179,5 +163,34 @@ const processPostedEventLineupEntries = (body, eventLineupEntries, eventLineup)
|
||||
// Skip lineup entry
|
||||
}
|
||||
})
|
||||
return {newEventLineupEntries, eventLineupEntries}
|
||||
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'))
|
||||
|
||||
}
|
||||
@@ -23,8 +23,14 @@ exports.getEventSheet = async (req,res) =>{
|
||||
)
|
||||
)
|
||||
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', {user, team, team_preferences, members, event, event_lineup, event_lineup_entries, availabilities, availabilitySummary, timeline, recent_events, opponent_logo,upcoming_events})
|
||||
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) => {
|
||||
@@ -82,7 +88,7 @@ exports.getLineupCard = (req, res, next) => {
|
||||
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.includes("[PO]"))
|
||||
.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)
|
||||
|
||||
@@ -3,7 +3,7 @@ const { teamsnapCallback } = require("../lib/utils");
|
||||
utils = require("../lib/utils");
|
||||
|
||||
exports.getTeams = async (req, res, next) => {
|
||||
const {layout} = req
|
||||
const {layout, user} = req
|
||||
const {user_id} = req.params
|
||||
req.session.current_team_id = null
|
||||
promise = teamsnap.loadTeams({'userId':user_id},
|
||||
@@ -17,7 +17,7 @@ exports.getTeams = async (req, res, next) => {
|
||||
req.promises.push(promise)
|
||||
await Promise.all(req.promises)
|
||||
try {
|
||||
const context = { layout, title: "Teams", teams: req.teams.filter(t=>!t.isRetired) };
|
||||
const context = { layout, title: "Teams", user, teams: req.teams.filter(t=>!t.isRetired) };
|
||||
res.render("team/list", 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
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
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 = ""
|
||||
@@ -7,7 +8,7 @@ exports.offenseLineup = (number_of_slots, event_lineup_entries, members, options
|
||||
|
||||
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.includes('PO')){
|
||||
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}`),
|
||||
@@ -60,7 +61,7 @@ exports.rosterHistory = (event, event_lineup_entries, members, availabilities, o
|
||||
// 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(teamsnapMembersSortAvailabilityLastName)
|
||||
players.sort(teamsnapMembersSortLineupAvailabilityLastName)
|
||||
|
||||
players.forEach(member=>{
|
||||
const {firstName, lastName, jerseyNumber, benchcoach, position, id} = member
|
||||
@@ -87,6 +88,9 @@ const positionGroups = {
|
||||
}
|
||||
|
||||
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()]))
|
||||
|
||||
@@ -112,8 +116,15 @@ exports.repeat = (n, options) => {
|
||||
|
||||
exports.loopEvents = (events, options) => {
|
||||
var results = "";
|
||||
events.forEach(event => {
|
||||
results += options.fn(event)
|
||||
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;
|
||||
@@ -121,6 +132,9 @@ exports.loopEvents = (events, options) => {
|
||||
|
||||
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 = ""
|
||||
@@ -128,7 +142,15 @@ exports.timepointForMember = (member, timeline, event, options) => {
|
||||
value = parsePositionLabel(eventLineupEntry.label).positionLabelWithoutFlags
|
||||
}
|
||||
else {
|
||||
value = availability.status[0]
|
||||
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 '';
|
||||
}
|
||||
return options.fn({availability: availability, eventLineupEntry: eventLineupEntry, value})
|
||||
}
|
||||
@@ -50,17 +50,6 @@ teamsnapMembersSortAvailabilityLastName = (a, b) => {
|
||||
}
|
||||
exports.teamsnapMembersSortAvailabilityLastName = teamsnapMembersSortAvailabilityLastName
|
||||
|
||||
exports.initTeamsnap = (req, res, next) => {
|
||||
if (!teamsnap.isAuthed()) {
|
||||
teamsnap.init(process.env["TEAMSNAP_CLIENT_ID"]);
|
||||
teamsnap.auth(req.user.accessToken);
|
||||
}
|
||||
teamsnap.loadCollections((err) => {
|
||||
teamsnap.enablePersistence();
|
||||
next(req, res, next);
|
||||
});
|
||||
};
|
||||
|
||||
exports.teamsnapCallback = (err,result, d) => {
|
||||
if (Array.isArray(result)){
|
||||
types = new Set(result.map(i=>i.type))
|
||||
@@ -95,6 +84,10 @@ const getPluralType = (type) =>{
|
||||
// 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';
|
||||
@@ -120,7 +113,7 @@ exports.groupTeamsnapItems = (items, types = [], params = {}) => {
|
||||
return result;
|
||||
}
|
||||
|
||||
exports.embeddedSvgFromPath = (svg_path, additional_classes = "") => {
|
||||
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"),
|
||||
@@ -133,6 +126,8 @@ exports.embeddedSvgFromPath = (svg_path, additional_classes = "") => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!options) {options=additional_classes; additional_classes=''}
|
||||
|
||||
const svg = fs.readFileSync(`${svg_path}`, 'utf8');
|
||||
|
||||
svgRegExWithClass = new RegExp(/<svg(.*)class="(.*?)"(.*)>/)
|
||||
@@ -157,16 +152,29 @@ exports.parsePositionLabel = (label) => {
|
||||
const pattern = /(?<pos>[A-Z0-9]+)(?:\s\[(?<flags>.[A-z,]+)\])?/g
|
||||
const {pos, flags} = pattern.exec(label)?.groups || {}
|
||||
const positionLabelWithoutFlags= pos
|
||||
const positionFlags = flags?.split(',').map(f=>f.trim()) || []
|
||||
const positionFlags = new Set(flags?.split(',').map(f=>f.trim()) || [])
|
||||
|
||||
return {positionLabelWithoutFlags, positionFlags}
|
||||
}
|
||||
|
||||
exports.compilePositionLabel = (label, flags) => {
|
||||
if (flags == null || flags == '' || flags.lengh == 0) {
|
||||
if (flags == null || flags == '' || flags.size == 0) {
|
||||
return label
|
||||
}
|
||||
else {
|
||||
const flags_set = new Set(flags.split(',').map(s=>s.trim()))
|
||||
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
|
||||
}
|
||||
@@ -1,24 +1,26 @@
|
||||
exports.loadRecentAndUpcomingEvents = async (req, res, next) => {
|
||||
const {team_id, event_id} = req.params
|
||||
var subject_date = ""
|
||||
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()
|
||||
subject_date = event.startDate.toISOString().slice(0,10)
|
||||
const new_date = new Date(event.startDate.getTime()+10000);
|
||||
subject_date = event.startDate
|
||||
}
|
||||
else {
|
||||
subject_date = new Date().toISOString().slice(0,10)
|
||||
subject_date = new Date()
|
||||
}
|
||||
req.promises.push(
|
||||
teamsnap.bulkLoad({
|
||||
teamId: team_id,
|
||||
types: ["event", "availabilitySummary"],
|
||||
scopeTo: "event",
|
||||
event__startedAfter: subject_date,
|
||||
event__pageSize: 4
|
||||
event__startedAfter: new Date(subject_date.getTime()+10000),
|
||||
event__pageSize: page_size
|
||||
})
|
||||
.then(items => tsUtils.groupTeamsnapItems(items))
|
||||
.then((items)=>{
|
||||
req.upcoming_events=items.events || [];
|
||||
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))
|
||||
@@ -31,8 +33,8 @@ exports.loadRecentAndUpcomingEvents = async (req, res, next) => {
|
||||
teamId: team_id,
|
||||
types: ["event", "availabilitySummary"],
|
||||
scopeTo: "event",
|
||||
event__startedBefore: subject_date,
|
||||
event__pageSize: 4,
|
||||
event__startedBefore: new Date(subject_date.getTime()-10000),
|
||||
event__pageSize: page_size,
|
||||
event__sortStartDate: "desc"
|
||||
})
|
||||
.then(items => tsUtils.groupTeamsnapItems(items))
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
*= require_tree .
|
||||
*= require_self
|
||||
*/
|
||||
@import url("/font/helvetica-now/stylesheet.css");
|
||||
@font-face {
|
||||
font-family: "MuseoSansRounded100Regular";
|
||||
src: url("https://teamsnap-ui.teamsnap.com/assets/fonts/museo/MuseoSansRounded-100-webfont.eot");
|
||||
@@ -1003,21 +1004,21 @@ h6 {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.Button--blue {
|
||||
.Button--blue, button:has(+ .position-label-flags :checked) {
|
||||
background-color: #1A6BAF;
|
||||
border-color: #15568c;
|
||||
color: #ffffff;
|
||||
}
|
||||
.Button--blue:hover, .Button--blue:active, .Button--blue:focus {
|
||||
.Button--blue:hover, button:hover:has(+ .position-label-flags :checked), .Button--blue:active, button:active:has(+ .position-label-flags :checked), .Button--blue:focus, button:focus:has(+ .position-label-flags :checked) {
|
||||
background-color: #17609e;
|
||||
border-color: #134d7e;
|
||||
color: #ffffff;
|
||||
}
|
||||
.Button--blue.is-active {
|
||||
.Button--blue.is-active, button.is-active:has(+ .position-label-flags :checked) {
|
||||
background-color: #17609e;
|
||||
color: #ffffff;
|
||||
}
|
||||
.Button--blue.is-disabled, .Button--blue.is-disabled:hover, .Button--blue.is-disabled:active, .Button--blue:disabled, .Button--blue:disabled:hover, .Button--blue:disabled:active {
|
||||
.Button--blue.is-disabled, button.is-disabled:has(+ .position-label-flags :checked), .Button--blue.is-disabled:hover, button.is-disabled:hover:has(+ .position-label-flags :checked), .Button--blue.is-disabled:active, button.is-disabled:active:has(+ .position-label-flags :checked), .Button--blue:disabled, button:disabled:has(+ .position-label-flags :checked), .Button--blue:disabled:hover, button:disabled:hover:has(+ .position-label-flags :checked), .Button--blue:disabled:active, button:disabled:active:has(+ .position-label-flags :checked) {
|
||||
background-color: #1A6BAF;
|
||||
border-color: #15568c;
|
||||
color: #ffffff;
|
||||
@@ -2311,13 +2312,13 @@ input:-webkit-autofill:focus {
|
||||
}
|
||||
|
||||
.Popup-container::before {
|
||||
top: calc( 100% - 6px );
|
||||
top: calc(100% - 6px);
|
||||
border: solid #d6d6d6 1px;
|
||||
box-shadow: 0 0 2px rgba(56, 56, 56, 0.15);
|
||||
}
|
||||
|
||||
.Popup-container::after {
|
||||
top: calc( 100% - 7px );
|
||||
top: calc(100% - 7px);
|
||||
}
|
||||
|
||||
.Popup-content {
|
||||
@@ -2756,7 +2757,7 @@ input:-webkit-autofill:focus {
|
||||
|
||||
.StepNav-stepTitle {
|
||||
width: 150px;
|
||||
left: calc( 0px - 43px);
|
||||
left: calc(0px - 43px);
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
@@ -2874,7 +2875,7 @@ input:-webkit-autofill:focus {
|
||||
}
|
||||
.StepNav--small .StepNav-stepTitle {
|
||||
width: 150px;
|
||||
left: calc( 0px - 53px);
|
||||
left: calc(0px - 53px);
|
||||
padding-top: 2px;
|
||||
}
|
||||
.StepNav--small .StepNav-stepIcon {
|
||||
@@ -2909,7 +2910,7 @@ input:-webkit-autofill:focus {
|
||||
}
|
||||
.StepNav--xsmall .StepNav-stepTitle {
|
||||
width: 150px;
|
||||
left: calc( 0px - 62px);
|
||||
left: calc(0px - 62px);
|
||||
padding-top: 1px;
|
||||
}
|
||||
.StepNav--xsmall .StepNav-stepIcon {
|
||||
@@ -6908,12 +6909,46 @@ input:-webkit-autofill:focus {
|
||||
background-color: #d6d6d6;
|
||||
}
|
||||
|
||||
header.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;
|
||||
text-decoration: none;
|
||||
}
|
||||
header .Header-banner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
header .filler {
|
||||
flex-grow: 1;
|
||||
}
|
||||
header :has(> .Header-bannerLogo):has(> .Header-bannerTitle) {
|
||||
display: inline-flex;
|
||||
}
|
||||
header .Header-bannerLogo, header .Header-bannerTitle {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
header .Header-bannerLogo img {
|
||||
height: 36px;
|
||||
width: auto;
|
||||
}
|
||||
header .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 {
|
||||
@@ -6970,52 +7005,20 @@ body {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.benchcoach-nav {
|
||||
background-color: #323669;
|
||||
margin-bottom: 2em;
|
||||
padding: 0.5em;
|
||||
color: white;
|
||||
}
|
||||
|
||||
a.Panel-row {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.benchcoach-nav h3 {
|
||||
font-family: "Helvetica", sans-serif;
|
||||
font-weight: bolder;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.lineup-slot .Panel-cell {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
div[id^=event-lineup] {
|
||||
div.event-lineup {
|
||||
max-width: 576px;
|
||||
counter-reset: lineup-sequence-counter 0;
|
||||
margin-left: 8px;
|
||||
margin-right: 9px;
|
||||
}
|
||||
|
||||
.lineup-slot {
|
||||
@@ -7090,9 +7093,23 @@ li .availability-status-code- {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div[id^=event-lineup] .Panel.position-only .Panel-cell:has(.sequence), div[id^=event-lineup] .Panel.bench .Panel-cell:has(.sequence), div[id^=event-lineup] .Panel.out .Panel-cell:has(.sequence) {
|
||||
div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out .Panel-cell:has(.SelectBox),
|
||||
div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out .Panel-cell:has(.drag-handle),
|
||||
div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out button:has(+ .position-label-flags),
|
||||
div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out button.addToStarting,
|
||||
div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out button.addToBench {
|
||||
display: none;
|
||||
}
|
||||
div.event-lineup .lineup-segment.bench .Panel-cell:has(.sequence), div.event-lineup .lineup-segment.position-only .Panel-cell:has(.sequence), div.event-lineup .lineup-segment.out .Panel-cell:has(.sequence) {
|
||||
display: none;
|
||||
}
|
||||
div.event-lineup .lineup-segment.bench.bench button.addToBench, div.event-lineup .lineup-segment.position-only.bench button.addToBench, div.event-lineup .lineup-segment.out.bench button.addToBench {
|
||||
display: none;
|
||||
}
|
||||
div.event-lineup .lineup-segment.starting button.addToStarting, div.event-lineup .lineup-segment.position-only button.addToStarting {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.Tooltip:after {
|
||||
padding: 2px !important;
|
||||
font-size: inherit !important;
|
||||
@@ -7101,8 +7118,6 @@ div[id^=event-lineup] .Panel.position-only .Panel-cell:has(.sequence), div[id^=e
|
||||
@media (max-width: 480px) {
|
||||
.Panel--full {
|
||||
border-radius: 0;
|
||||
margin-right: -16px;
|
||||
margin-left: -16px;
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
}
|
||||
@@ -7136,8 +7151,11 @@ div[id^=event-lineup] .Panel.position-only .Panel-cell:has(.sequence), div[id^=e
|
||||
}
|
||||
|
||||
.Panel .Panel {
|
||||
border: none;
|
||||
margin: 0;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.scroll-horizontal {
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=application.css.map */
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["../../scss/eventsheet.scss"],"names":[],"mappings":";AAAQ;AACA;AACA;AACA;AACA;AAER;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;AACA;EACE;IACE;IACA;;;AAIJ;AACA;EACE;IACE;;EAEF;IACE;;EAEF;IACE;IACA;;;AAIJ;EACE;EACA;EACA;EACA;;;AAGF;AACA;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAKA;EACE;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAQR;EACE;;;AAGF;EACE;;;AAOF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;AACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;AAAA;EAEE;EACA;EACA;EACA,qBACE;;;AAIJ;AAAA;EAEE;EACA;EACA;EACA,qBACE;;;AAIJ;AAAA;AAAA;AAAA;EAIE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAKF;EACE;;AAEA;EACE;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAQN;EACE;;;AAGF;AACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;AAAA;AAAA;AAAA;AAAA;EAKE;;;AAGF;AAAA;AAAA;AAAA;AAAA;EAKE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;AAAA;EAEE;;;AAGF;AAAA;EAEE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAKA;EACE;EACA;EACA;EACA,qBACE;;AAIJ;EACE;;AAGF;EACE;;AAGF;AACE;EACA;EACA;EACA;AACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;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;;;AAeV;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAMI;EACE,SALM;;AAIR;EACE,SALM;;AAIR;EACE,SALM;;AAIR;EACE,SALM;;AAIR;EACE,SALM;;AAIR;EACE,SALM;;AAIR;EACE,SALM;;AAIR;EACE,SALM;;AAIR;EACE,SALM;;AAUZ;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;EACI;;;AASV;EACE;AACA;EACA;EAEA;EACA;AACA;;AAEA;EACE;EACA;;AAGF;EACE;;AACA;EACE;;;AAON;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;AACA;AACA;AACA;EACA;EAEA;EACA;EACA;EACA;EACA;;;AAKF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAEF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;AAAA;EAEE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AASA;AACE;EACA;EACA;AACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EAEE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACI;EACA;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EAEE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAMN;EACE;EACA;EACA;;AAEF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EAEE;;AAGF;EACE;EACA;EACA;;AAEA;EACE;;AAQJ;EACE;;AAGF;EACE;;AAGF;EACE;;;AAOJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;AACA;AAAA;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACI;EACA;EACA;EACA;;AAGJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE","file":"eventsheet.css"}
|
||||
{"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"}
|
||||
File diff suppressed because it is too large
Load Diff
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"
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
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.
|
||||
//
|
||||
@@ -21,20 +22,17 @@ passport.use(
|
||||
proxy: true
|
||||
},
|
||||
async function (req, accessToken, refreshToken, profile, done) {
|
||||
json = JSON.parse(profile._raw);
|
||||
field_from_collection = (field_name) => {
|
||||
return json.collection.items[0].data.filter(
|
||||
(e) => e.name == field_name
|
||||
)[0].value;
|
||||
}
|
||||
const new_profile = { access_token: accessToken };
|
||||
new_profile["id"] = field_from_collection("id")
|
||||
new_profile["email"] = field_from_collection("email")
|
||||
new_profile["first_name"] = field_from_collection("first_name")
|
||||
new_profile["last_name"] = field_from_collection("last_name")
|
||||
// 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;
|
||||
await initTeamsnap(process.env["TEAMSNAP_CLIENT_ID"], accessToken)
|
||||
await initTeamsnap(accessToken)
|
||||
return done(null, new_profile);
|
||||
}
|
||||
)
|
||||
@@ -59,6 +57,7 @@ passport.serializeUser(function (user, cb) {
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
accessToken: user.access_token,
|
||||
managed_team_ids: user.managed_team_ids
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -66,10 +65,12 @@ passport.serializeUser(function (user, cb) {
|
||||
passport.deserializeUser(function (user, cb) {
|
||||
process.nextTick(async function () {
|
||||
console.log("L#68 deserializing user id", user.id);
|
||||
if (!teamsnap.isAuthed()){
|
||||
await initTeamsnap(process.env["TEAMSNAP_CLIENT_ID"], user.accessToken)
|
||||
}
|
||||
try {
|
||||
await initTeamsnap(user.accessToken)
|
||||
return cb(null, user);
|
||||
} catch (err) {
|
||||
return cb(err)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -86,9 +87,11 @@ var router = express.Router();
|
||||
router.get("/login", function (req, res, next) {
|
||||
// https://stackoverflow.com/a/73056806/20522015
|
||||
returnTo = req.session.returnTo;
|
||||
// req.session.regenerate(); // this is not working right as of now...
|
||||
req.session.returnTo = returnTo;
|
||||
if (req.user?.accessToken){
|
||||
res.redirect(returnTo || "/");
|
||||
} else {
|
||||
res.render("login", {layout:"layouts/main"});
|
||||
}
|
||||
});
|
||||
|
||||
/* GET /login/federated/teamsnap
|
||||
@@ -125,10 +128,9 @@ router.get(
|
||||
})
|
||||
);
|
||||
|
||||
const initTeamsnap = async (clientID, accessToken) => {
|
||||
teamsnap.init(clientID);
|
||||
teamsnap.auth(accessToken);
|
||||
await teamsnap.loadCollections();
|
||||
const initTeamsnap = async (accessToken) => {
|
||||
await teamsnap.auth(accessToken);
|
||||
await teamsnap.loadCollections(teamsnapCallback);
|
||||
await teamsnap.enablePersistence();
|
||||
}
|
||||
|
||||
@@ -138,7 +140,7 @@ const ensureLoggedIn = (req, res, next) => {
|
||||
res.redirect("/login");
|
||||
// return next();
|
||||
}
|
||||
else{
|
||||
else {
|
||||
req.user = req.session.passport.user
|
||||
next();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ 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) => {
|
||||
@@ -23,12 +25,54 @@ const loadEvent = (req,res,next) => {
|
||||
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.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup", eventsController.getLineup);
|
||||
// router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup_card", eventsController.getLineupCard);
|
||||
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/availability_reminders", upload.none(), eventsController.sendAvailabilityReminders)
|
||||
|
||||
module.exports = {router, loadEvent}
|
||||
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}
|
||||
@@ -5,6 +5,8 @@ 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")
|
||||
|
||||
|
||||
@@ -51,10 +53,11 @@ router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup", async (req,res) =
|
||||
}
|
||||
)
|
||||
|
||||
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.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup_card", eventsController.getLineupCard);
|
||||
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/entries", eventsLineupController.getEventLineupEntries)
|
||||
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}
|
||||
@@ -1,7 +1,7 @@
|
||||
const express = require("express");
|
||||
const eventsSheetController = require("../controllers/eventsheet");
|
||||
const {loadEventLineup} = require("./eventlineup");
|
||||
const {loadEvent} = require("./event");
|
||||
const {loadEvent, loadEvents} = require("./event");
|
||||
const {loadRecentAndUpcomingEvents} = require("../middlewares/bulkload")
|
||||
const router = express.Router();
|
||||
const tsUtils = require('../lib/utils')
|
||||
@@ -10,46 +10,6 @@ const multer = require("multer");
|
||||
const upload = multer()
|
||||
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
const linksForEventSheet = async (req, res, next) => {
|
||||
await Promise.all(req.promises)
|
||||
const events = [...req.recent_events, req.event, ...req.upcoming_events]
|
||||
@@ -70,6 +30,8 @@ router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/sheet", async (req,res) =>
|
||||
}
|
||||
)
|
||||
|
||||
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}
|
||||
@@ -27,4 +27,10 @@ router.get("/", (req,res,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};
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
@import "../../node_modules/@teamsnap/teamsnap-ui/src/css/teamsnap-ui.scss";
|
||||
@import url('/font/helvetica-now/stylesheet.css');
|
||||
|
||||
|
||||
$color-success: #b7e1cd;
|
||||
@@ -62,12 +63,54 @@ $monospace-font: "Inconsolata", monospace;
|
||||
}
|
||||
}
|
||||
|
||||
header.Header {
|
||||
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 {
|
||||
@@ -137,52 +180,20 @@ body {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.benchcoach-nav {
|
||||
background-color: #323669;
|
||||
margin-bottom: 2em;
|
||||
padding: 0.5em;
|
||||
color: white;
|
||||
}
|
||||
|
||||
a.Panel-row {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.benchcoach-nav h3 {
|
||||
font-family: "Helvetica", sans-serif;
|
||||
font-weight: bolder;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.lineup-slot .Panel-cell {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
div[id^="event-lineup"] {
|
||||
div.event-lineup {
|
||||
max-width: 576px;
|
||||
counter-reset: lineup-sequence-counter 0;
|
||||
margin-left: 8px;
|
||||
margin-right: 9px;
|
||||
}
|
||||
|
||||
.lineup-slot {
|
||||
@@ -278,17 +289,31 @@ li .availability-status-code- {
|
||||
|
||||
}
|
||||
|
||||
div[id^="event-lineup"] .Panel {
|
||||
&.position-only .Panel-cell:has(.sequence), &.bench .Panel-cell:has(.sequence), &.out .Panel-cell:has(.sequence){
|
||||
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;
|
||||
}
|
||||
|
||||
&.out {
|
||||
.Panel-cell {
|
||||
&:has(.sequence), .drag-handle, .SelectBox {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,8 +325,8 @@ div[id^="event-lineup"] .Panel {
|
||||
@media (max-width: 480px){
|
||||
.Panel--full {
|
||||
border-radius: 0;
|
||||
margin-right: -16px;
|
||||
margin-left: -16px;
|
||||
// margin-right: -16px;
|
||||
// margin-left: -16px;
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
}}
|
||||
@@ -343,8 +368,16 @@ div[id^="event-lineup"] .Panel {
|
||||
.Panel .Panel{
|
||||
// padding: 0;
|
||||
// border-radius: 0;
|
||||
border: none;
|
||||
// border: none;
|
||||
// border-top: 1px solid #d6d6d6;
|
||||
// border-bottom: 1px solid #d6d6d6;
|
||||
margin: 0;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.scroll-horizontal {
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
button:has(+.position-label-flags :checked) {
|
||||
@extend .Button--blue
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,21 +12,23 @@
|
||||
</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 u-xs-inline">Details</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 u-xs-inline">Lineup</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 u-xs-inline">Sheet</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 u-xs-inline">TeamSnap</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>
|
||||
0
src/views/event/stats-importer.hbs
Normal file
0
src/views/event/stats-importer.hbs
Normal file
@@ -1,35 +1,67 @@
|
||||
{{>emailmodal}}
|
||||
<div id="event-lineup-{{event.id}}" data-event-lineup-id="{{event_lineup.id}}" data-event-id="{{event.id}}">
|
||||
<form onsubmit="onSubmit(this,event)" action="#">
|
||||
<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">{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}</span>
|
||||
<span id="waiting-icon" class="u-hidden">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/loader.svg" "Icon--loader"}}}</span>
|
||||
<span id="success-icon" class="u-hidden">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/check.svg"}}}</span>
|
||||
<span id="failure-icon" class="u-hidden">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg"}}}</span>
|
||||
<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" onclick="togglePopup(this)">
|
||||
<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">
|
||||
<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="../sheet">
|
||||
{{{embeddedSvgFromPath "/bootstrap-icons/book.svg"}}}
|
||||
<span>Lineup Card</span>
|
||||
<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>
|
||||
@@ -40,12 +72,12 @@
|
||||
<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">
|
||||
|
||||
<div class="availability-bar fullwidth">
|
||||
{{> availability_bar availabilitySummary=availabilitySummary}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="lineup-starting-{{event.id}}" class="Panel u-maxWidthSm starting Panel--fullWidthMobile Panel--full">
|
||||
<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>
|
||||
@@ -53,77 +85,48 @@
|
||||
</div>
|
||||
<div class=" Panel-row Grid Grid--fit u-textBold u-textCenter u-padXs">
|
||||
{{#each (positions)}}
|
||||
<div class="Grid-cell position-status">{{this}}</div>
|
||||
<div class="Grid-cell position-status" data-value="{{this}}">{{this}}</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="slot-set">
|
||||
{{#each members}}
|
||||
{{#if (isInStartingLineup this)}}
|
||||
{{> slot member=this}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="lineup-positiononly-{{event.id}}" class="Panel u-maxWidthSm position-only Panel--full">
|
||||
<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">
|
||||
{{#each members}}
|
||||
{{#if (isInPositionOnly this)}}
|
||||
{{> slot member=this}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="lineup-bench-{{event.id}}" class="Panel u-maxWidthSm bench Panel--full">
|
||||
<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">
|
||||
{{#each members}}
|
||||
{{#if (isInBench this)}}
|
||||
{{> slot member=this event=../event}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{#loadSlots}}
|
||||
{{>slot member=member event_lineup=event_event_lineup availablity=availability}}
|
||||
{{/loadSlots}}
|
||||
</div>
|
||||
</div>
|
||||
<div id="lineup-out-{{event.id}}" class="Panel u-maxWidthSm out Panel--full">
|
||||
<div class="Panel-row Panel-title u-padXs">
|
||||
<span style="flex: 1 1 0%;">{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-x.svg"}}}Out</span>
|
||||
<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" onclick="toggleChildSlots(this);">
|
||||
<label class="Toggle-label" for="availability-tab"></label>
|
||||
<input class="Toggle-input" type="checkbox" id="enable-slots">
|
||||
<label class="Toggle-label" for="enable-slots"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slot-set">
|
||||
{{#each members}}
|
||||
{{#if (isInOut this)}}
|
||||
{{> slot member=this}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
|
||||
<script src="/js/eventlineup.js"></script>
|
||||
<script src="/js/tinymce.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
colorPositions();
|
||||
refreshLineup();
|
||||
tinymce.init({
|
||||
selector:"#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})
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -1,4 +1,4 @@
|
||||
<table>
|
||||
<table class="lineup-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="title-cell" colSpan=3>
|
||||
@@ -11,10 +11,10 @@
|
||||
{{#if (isInStartingLineup this)}}
|
||||
<tr>
|
||||
<td class="sequence-cell">
|
||||
{{plus1 this.benchcoach.eventLineupEntry.sequence}}
|
||||
{{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">{{this.benchcoach.eventLineupEntry.label}}</td>
|
||||
<td class="position-label-cell">{{positionLabelWithoutFlags this.benchcoach.eventLineupEntry.label}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
@@ -26,7 +26,7 @@
|
||||
<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>
|
||||
<td class="position-label-cell">{{positionLabelWithoutPOFlag this.benchcoach.eventLineupEntry.label}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<div id="modal" class="Modal Modal--clickableBg">
|
||||
<div class="Modal-bgDismiss" onclick="javascript:this.closest('.Modal').classList.toggle('is-open')"></div>
|
||||
<div class="Modal-content">
|
||||
<div onclick="javascript:this.closest('.Modal').classList.toggle('is-open')">{{{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" 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</label>
|
||||
<div class="lineup-email"></div>
|
||||
</div>
|
||||
<div class="FieldGroup">
|
||||
<button class="Button" role="button" onclick="submitEmail();">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,10 +1,10 @@
|
||||
<div class="Panel-expandableRow lineup-slot">
|
||||
<input type="hidden" name="label" value="{{member.benchcoach.eventLineupEntry.label}}">
|
||||
<input type="hidden" name="flags" value="{{flagsString member.benchcoach.eventLineupEntry.flags}}">
|
||||
<input type="hidden" name="sequence" value="{{member.benchcoach.eventLineupEntry.sequence}}">
|
||||
<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="{{member.benchcoach.eventLineupEntry.id}}">
|
||||
<input type="hidden" name="availabilityStatusCode", value="{{member.benchcoach.availability?.statusCode}}">
|
||||
<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}}">
|
||||
@@ -15,14 +15,42 @@
|
||||
class="Panel-cell Panel-cell--header">
|
||||
<div class="sequence u-textNoWrap u-fontSizeLg"></div>
|
||||
</div>
|
||||
<div class="Panel-cell u-padXs u-sizeFill">
|
||||
<div class="Panel-cell u-padXs u-sizeFill u-flex">
|
||||
<div
|
||||
class="d-flex availability-status-code-{{
|
||||
member.benchcoach.availability?.statusCode
|
||||
class="Popup availability-status-code-{{
|
||||
availability?.statusCode
|
||||
}}"
|
||||
>
|
||||
<div class="u-flexInline u-fontSizeLg u-textNoWrap">
|
||||
{{#if member.benchcoach.availability}}{{{avail_status_code_icon member.benchcoach.availability.statusCode}}}{{/if}}
|
||||
{{#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>
|
||||
@@ -33,6 +61,29 @@
|
||||
#{{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">
|
||||
@@ -42,7 +93,7 @@
|
||||
--
|
||||
</option>
|
||||
{{#each (positions)}}
|
||||
<option value="{{this}}" {{#if (comparePositionWithFlags this ../member.benchcoach.eventLineupEntry)}}selected{{/if}}>
|
||||
<option value="{{this}}" {{#if (comparePositionWithFlags this ../eventLineupEntry)}}selected{{/if}}>
|
||||
{{this}}
|
||||
</option>
|
||||
{{/each}}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<div class="field-container">
|
||||
<img src="/media/baseball-diamond.svg" />
|
||||
{{{embeddedSvgFromPath "/media/baseball-diamond.svg" 'baseball-diamond'}}}
|
||||
{{#defenseLineup event_lineup_entries members}}
|
||||
<div class="slot-set pos-{{this.position}}">
|
||||
<table>
|
||||
<table class="striped">
|
||||
<tbody>
|
||||
<tr class="slot">
|
||||
<th class="position"></th>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<table>
|
||||
<colgroup><col span="3" class="player"></colgroup>
|
||||
{{!-- <colgroup><col span="0" class="player-stats"></colgroup> --}}
|
||||
<colgroup><col span="4" class="position-capability"></colgroup>
|
||||
{{!-- <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>
|
||||
<colgroup><col span="4" class="availability-on-day past"></colgroup> --}}
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="3" id="today-availability">
|
||||
<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>
|
||||
@@ -17,34 +19,38 @@
|
||||
<span class="decimal-point">.</span>SLG
|
||||
<span class="delimiter">:</span>PA
|
||||
</th>
|
||||
<th class="position-capability pitcher">P</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">O</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}}" date="{{this.startDate}}"><div>{{dateFormat this.startDate "ddd" }}</div></th>
|
||||
<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}}" date="{{this.startDate}}"><div>{{dateFormat this.startDate "ddd" }}</div></th>
|
||||
<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 id="roster-history-slot-<%= ::Temple::Utils.escape_html((i)) %>">
|
||||
<td class="is-present-checkbox available-status-code-{{this.benchcoach.availability.statusCode}}">
|
||||
<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}}{{#if (isStarting this)}} starting{{/if}}">
|
||||
<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}}{{#if (isStarting this)}} starting{{/if}}">
|
||||
<td class="player-name available-status-code-{{this.benchcoach.availability.statusCode}}">
|
||||
{{this.lastName}}
|
||||
</td>
|
||||
<td class="player-stats border-left border-right">
|
||||
<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>
|
||||
@@ -56,20 +62,20 @@
|
||||
<span class="delimiter">:</span>
|
||||
<span class="pa">00</span>
|
||||
</td>
|
||||
<td class="position-capability pitcher">{{positionCapabilityFor this "P"}}</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">{{positionCapabilityFor this "OF"}}</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}}">
|
||||
<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}}">
|
||||
<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}}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<link rel="stylesheet" href="/css/eventsheet.css">
|
||||
|
||||
<body class="B5">
|
||||
<div class="sheet eventsheet" id="page-1">
|
||||
<section id="defense-card">
|
||||
<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}} – {{dateFormat event.startDate "ddd, MMM D h:mm A" }}
|
||||
{{event.formattedTitle}}
|
||||
</div>
|
||||
<div class="homeaway float-right">
|
||||
{{event.gameType}}
|
||||
@@ -31,8 +31,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="roster-and-history">
|
||||
<div class="roster-and-history">
|
||||
<section class="SW" id="roster-and-history">
|
||||
<div class="container">
|
||||
{{> roster_and_history
|
||||
event=event
|
||||
event_lineup_entries=event_lineup_entries
|
||||
@@ -42,7 +42,7 @@
|
||||
}}
|
||||
</div>
|
||||
</section>
|
||||
<section class="lineup-card" id="lineup-card-dugout">
|
||||
<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>
|
||||
@@ -80,9 +80,9 @@
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<section class="lineup-card" id="lineup-card-exchange">
|
||||
<section class="SE lineup-card exchange" id="lineup-card-exchange">
|
||||
<header>
|
||||
<div class="float-left event-title">{{event.formattedTitle}}</div>
|
||||
<div class="float-left event-title">{{event.formattedTitleForMultiTeam}}</div>
|
||||
<div class="float-right homeaway">{{event.gameType}}</div>
|
||||
</header>
|
||||
<div class="starting-lineup-table">
|
||||
@@ -119,24 +119,18 @@
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="sheet eventsheet" id="page-2">
|
||||
<section id="defense-card" style="border:solid black;">
|
||||
<div id="defense-pane">
|
||||
{{> defense_pane}}
|
||||
</div>
|
||||
</section>
|
||||
<section id="front-cover">
|
||||
<div class="sheet eventsheet {{#if sheet_layout}}{{sheet_layout}}{{else}}quarters{{/if}}" id="page-2">
|
||||
<section class="SE" id="front-cover">
|
||||
<header>
|
||||
<div class="homeaway">
|
||||
<span>{{firstLetter event.gameType}}</span>
|
||||
<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="game-number">
|
||||
<span class="label">Game #</span>
|
||||
<span class="value">XX</span>
|
||||
<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;">
|
||||
@@ -149,17 +143,16 @@
|
||||
</div>
|
||||
</div>
|
||||
{{# if event.opponentName}}
|
||||
<div>
|
||||
<div style="text-align: center;font-family: 'Pacifico';">
|
||||
<div class="conjuction">
|
||||
<span>vs</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="opponent">
|
||||
<div>
|
||||
<span class="name">{{event.opponentName}}</span>
|
||||
</div>
|
||||
{{#if opponent_logo.mediumUrl }}
|
||||
<img src="{{opponent_logo.mediumUrl}}">
|
||||
{{!-- <%= ::Temple::Utils.escape_html((image_tag opponent_logos[event.opponent.id].medium_url)) %> --}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="">
|
||||
<table>
|
||||
@@ -174,7 +167,7 @@
|
||||
<th>{{event.opponentName}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="height:5em;"></td>
|
||||
<td style="height:5em;width: 50%;"></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -183,7 +176,70 @@
|
||||
{{/if}}
|
||||
</div>
|
||||
</section>
|
||||
<section class="lineup-card" id="lineup-card-dugout-blank">
|
||||
<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>
|
||||
@@ -217,38 +273,5 @@
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<section class="lineup-card" id="lineup-card-exchange-blank">
|
||||
<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>
|
||||
|
||||
</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>
|
||||
@@ -4,6 +4,7 @@
|
||||
<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>
|
||||
@@ -19,15 +20,14 @@
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="bg-light">
|
||||
<header class="Header">
|
||||
<body>
|
||||
<header class="u-spaceBottomMd">
|
||||
{{> navbar }}
|
||||
{{{_sections.header}}}
|
||||
</header>
|
||||
<div class="u-padSidesMd u-xs-padSidesLg">
|
||||
<div class="u-max1200 u-flexExpandSides u-xs-size5of6 u-sm-size2of3 u-md-sizeFull u-padBottomMd u-xs-padEndsLg u-sm-padEndsXl">
|
||||
<main class="u-xs-spaceSidesMd">
|
||||
{{{ body }}}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
{{{script_tags scripts}}}
|
||||
</html>
|
||||
|
||||
@@ -1,27 +1,10 @@
|
||||
<div class="Grid Grid--fit Grid--withGutter u-max1200 u-flexExpandSides u-xs-size5of6 u-sm-size2of3 u-md-sizeFull u-padBottomMd u-xs-padEndsLg u-sm-padEndsXl">
|
||||
<div class="Grid-cell u-size5of12">
|
||||
<div class="Panel u-padLg u-spaceSidesAuto">
|
||||
<h1 class="u-spaceSidesAuto u-spaceBottomLg">Sign in</h1>
|
||||
<div>
|
||||
<a class="Button Button--large Button--orange u-spaceSidesAuto" href="/login/federated/teamsnap">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Grid-cell u-size7of12 u-textCenter">
|
||||
<h1>
|
||||
<img src="/media/benchcoach.svg" style="width: 2.5em;">
|
||||
</h1>
|
||||
<h1>
|
||||
<strong>
|
||||
Welcome to
|
||||
<span class="text-nowrap">BenchCoach</span>
|
||||
</strong>
|
||||
</h1>
|
||||
<div class="lead fst-italic fw-light">
|
||||
An assistant coach for TeamSnap
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
@@ -1,5 +1,6 @@
|
||||
<h1>
|
||||
{{title}}
|
||||
<hr class="Divider" />
|
||||
</h1>
|
||||
<div class="Panel">
|
||||
<div class="Panel-body">
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<div class="Header-container Grid u-flexAlignItemsCenter">
|
||||
<div class="Grid-cell u-sizeFill">
|
||||
<div class="Header-banner Grid u-flexAlignItemsCenter">
|
||||
<a href="/" class="Grid-cell u-sizeFit u-flexInline u-flexAlignItemsCenter u-textDecorationNone">
|
||||
<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>
|
||||
<div class="Grid-cell u-flexInline u-flexJustifyEnd u-sizeFill u-padSidesSm">
|
||||
{{#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
|
||||
@@ -31,10 +33,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -17,7 +17,7 @@
|
||||
<img src ="/teamsnap-ui/assets/icons/schedule.svg" class="Icon">
|
||||
<span class="span u-hidden u-xs-inline">Schedule</span>
|
||||
</a>
|
||||
<a class="Button" href="members">
|
||||
<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>
|
||||
@@ -29,24 +29,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" Panel Panel--full">
|
||||
<div class=" Panel-header">
|
||||
<h2 class=" Panel-title">Upcoming Events</h2>
|
||||
</div>
|
||||
<div class=" Panel-body">
|
||||
|
||||
<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}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<div class=" Panel Panel--full">
|
||||
<div class=" Panel-header">
|
||||
<h2 class=" Panel-title">Recent Events</h2>
|
||||
</div>
|
||||
<div class=" Panel-body">
|
||||
<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}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
Reference in New Issue
Block a user