Compare commits

...

91 Commits

Author SHA1 Message Date
87b2abbd80 Merge branch 'eventlineup-development' 2024-05-29 16:55:07 -05:00
b0de3fb221 add buttons to move slots
build css
2024-05-29 16:48:03 -05:00
149f0a411b evensheet fix when no availabilities or positions 2024-05-29 16:44:46 -05:00
e6d1d5a697 Merge branch 'simplification' 2024-05-29 16:42:46 -05:00
bf9d0c1a78 simplifications
compile css
2024-05-29 16:39:36 -05:00
64fa16740b enabled 2024-05-27 15:17:27 -05:00
f273677ba7 Merge branch 'eventlineup-availability-reminders'
# Conflicts:
#	src/public/js/eventlineup.js
2024-05-27 15:11:39 -05:00
ca194d516d email fixes, tweaks 2024-05-27 15:10:17 -05:00
ed18389bb2 auto restart of debugger 2024-05-27 15:10:17 -05:00
b346710496 fix embed svg (additional classes) 2024-05-27 15:10:16 -05:00
6d1588e80f statusCode fix 2024-05-27 15:10:16 -05:00
858cb24e3f cleanup
oops missing bracket

cleanup
2024-05-27 15:10:16 -05:00
196eb5f51d fixed adjacent lineup 2024-05-27 15:10:15 -05:00
07446570c1 add clear lineup/availabilities, availability reminders 2024-05-27 15:04:34 -05:00
a5c47ff9a7 Merge branch 'main' into eventlineup-availability-reminders 2024-05-26 11:44:58 -05:00
aa2ebf0b2e Merge branch 'fix-adjacent-lineup' 2024-05-26 11:40:36 -05:00
5f02ea4d5c caddy_data is not external 2024-05-26 11:36:21 -05:00
39380eaf03 persist caddy data, move domain to .env 2024-05-26 11:34:35 -05:00
cb131353dc Merge branch 'docker' 2024-05-26 10:42:45 -05:00
e1c9a7b81b cleanup to allow for debugging 2024-05-26 10:42:21 -05:00
c495b265ee Merge branch 'main' into docker 2024-05-25 19:49:20 -05:00
49864874fc dev script updates for scss watch 2024-05-24 13:40:59 -05:00
e9fd60e619 404 fix 2024-05-24 13:40:42 -05:00
84da372330 Merge branch 'main' into docker 2024-05-24 10:42:19 -05:00
c696ffb4bc Merge branch 'eventsheet-development' 2024-05-24 10:42:06 -05:00
a06807b028 fixed adjacent lineup 2024-05-23 08:03:30 -05:00
07be459781 cleanup some styling 2024-05-22 16:47:25 -05:00
769fa60196 incorporating changes for updated svg 2024-05-22 16:09:33 -05:00
d377399c10 incorporating changes for updated svg 2024-05-22 16:07:08 -05:00
fa50ab93dc incorporate letter sized sheet
accessible when url parameter ?sheet_size=letter
2024-05-22 13:41:17 -05:00
73e3afcecc Merge branch 'main' into docker 2024-05-20 09:11:25 -05:00
af9fa3bd9b fix for if availabilities aren't available 2024-05-20 09:11:08 -05:00
7e803cc8e3 fix for if availabilities aren't available 2024-05-20 09:07:54 -05:00
339d4c7923 Merge branch 'duplicate-checking' 2024-05-20 09:07:30 -05:00
58825b5bcd reloads the page on lineup save
realized that the duplicates come from saving the lineup more than once. this occurs when initially no one has an eventlineupentry, it gets one on save, but the front end is not updated on save, so it keeps creating entrires on save. simple fix is to refresh the page on save. "better" solution would be to have the front end update on save, but that's longer to implement. i started this by having `postEventLineup` return the newly fetched lineupentries.
2024-05-20 09:06:53 -05:00
6b9e6734fe Merge branch 'main' into docker 2024-05-19 12:35:31 -05:00
03a9ac3aae Merge branch 'eventsheet-development' 2024-05-19 12:35:14 -05:00
da159f2b13 urlencode needs an explicit extended option 2024-05-19 12:34:27 -05:00
c2c192bdc6 add blank lineupsheet 2024-05-19 08:53:26 -05:00
c2b1898b91 styling updates 2024-05-19 08:52:48 -05:00
4ee466e7cb Merge branch 'main' into eventsheet-development 2024-05-14 19:09:33 -05:00
6592f2eeae Merge branch 'main' into docker 2024-05-10 17:55:15 -05:00
1f2ba45e54 bug fix for initSlots 2024-05-10 17:55:03 -05:00
588c23ec3f refactoring eventsheet 2024-05-10 17:54:45 -05:00
1c3cafdcda Merge branch 'main' into docker 2024-05-06 16:51:00 -05:00
fb0ca76c29 reimplement eventlineup slots, eventsheet refinements
fixed the loop in eventlineup slots so they will show duplicate eventlineupentries if they exist
refined some styling in eventsheet
2024-05-06 16:49:21 -05:00
bdb6a77371 begin adding availability reminders 2024-05-06 16:46:29 -05:00
e4b981d676 reimplement eventlineup slots, eventsheet refinements
fixed the loop in eventlineup slots so they will show duplicate eventlineupentries if they exist

refined some styling in eventsheet
2024-05-06 16:45:56 -05:00
c4c0d0fb7d move eventlineup helpers to separate file 2024-05-06 10:41:44 -05:00
3695cd8975 fix for grouping items with no type
Fixed an error that caused the app to crash when trying to group a bulk set of items when there are items with no "type" property. Items something like "delete request" items had no type.
2024-05-06 10:40:15 -05:00
bcade85182 eventsheet styling improvements 2024-05-06 10:38:52 -05:00
d50f94acc8 update better-sqlite
needed to update better-sqlite to work with node 22 (i think? this fixed it anyways)
2024-05-06 10:36:45 -05:00
5e1facf24a Merge branch 'main' into docker 2024-05-03 07:47:05 -05:00
cf01bf9fff styling fixes
Fixes for event sheet, added availability bar to event lineup
2024-05-03 07:46:05 -05:00
83e722cdb9 add availability bar to lineup view 2024-05-03 07:41:54 -05:00
dd48aeca8d eventsheet styling fixes 2024-05-03 07:41:29 -05:00
f421089eb9 Merge branch 'main' into docker 2024-03-22 16:05:34 -05:00
d484f8cfdf csrf fix when added lineup 2024-03-22 16:03:57 -05:00
87735f76b5 fix submit to actual submitted lineup 2024-03-22 16:03:46 -05:00
6d4600a858 fix submit animation when there's more than one lineup 2024-03-22 16:03:19 -05:00
5c19a16f8b flags fix 2024-03-22 15:50:33 -05:00
aea6a64b69 add session expiry date (24 hours) 2024-03-22 15:49:52 -05:00
b9ead6770a styling changes 2024-03-22 15:49:17 -05:00
84cc1f651c Merge branch 'main' into docker 2024-03-18 20:22:15 -05:00
a1cb6fcf0a eventlineup enhancments (availability notes, reminders, flags popup) 2024-03-18 20:20:00 -05:00
e459a0688a re-add padding on main content in layout 2024-03-18 20:19:08 -05:00
8e98286f7c eventsheet styling fixes 2024-03-18 20:18:33 -05:00
87fb835590 gamesheet hide logo if not exists 2024-03-18 20:17:59 -05:00
1f897d2b1e styling fix on game sheet (front cover) 2024-03-18 20:17:20 -05:00
8c1b325532 hide "members" section for now, not implemented yet 2024-03-18 20:15:33 -05:00
4b56259b98 fix change game card -> game sheet 2024-03-18 20:14:52 -05:00
16b1402c6f re-add margin for panels of panels 2024-03-18 20:14:24 -05:00
e4f6576847 more fixes for flag set vs. array 2024-03-18 20:13:40 -05:00
2df02e7452 auth fix 2024-03-18 20:12:41 -05:00
99d376af4c re-enable 404 catchall 2024-03-18 20:12:12 -05:00
2b497a0227 fix recent, upcoming date picking 2024-03-18 20:11:52 -05:00
00b270e0f6 fix event sheet (flags using set, not array) 2024-03-18 20:10:54 -05:00
2a2eb07823 cleanup some auth stuff 2024-03-17 10:46:18 -05:00
39e6c2b5af Merge branch 'main' into docker 2024-03-15 19:14:01 -05:00
832fb654ec visual refinements for multi lineups 2024-03-15 19:09:17 -05:00
9f9da4e191 Merge branch 'main' into docker 2024-03-15 14:20:38 -05:00
f2371c6b5a add insert lineup before and after 2024-03-15 14:19:45 -05:00
dc17ca76ba add script_tags helper 2024-03-15 14:19:45 -05:00
d24b2a121e respect environment is development on error message 2024-03-15 14:19:45 -05:00
7c5630c5ba Merge branch 'main' into docker 2024-03-15 08:41:20 -05:00
e4b4345cff refinement for final score table on gamesheet card 2024-03-15 08:41:02 -05:00
dfab474f42 add manifest for pwa 2024-03-15 08:40:42 -05:00
053f6038f6 refinements 2024-03-15 08:40:27 -05:00
cb69521875 init flag checkboxes 2024-03-15 08:39:56 -05:00
58c870ce7c navbar, login refinements 2024-03-15 08:39:22 -05:00
61b6dc8a35 add dr, dh flags 2024-03-15 08:38:15 -05:00
48 changed files with 2861 additions and 1769 deletions

20
.vscode/launch.json vendored
View File

@@ -4,10 +4,22 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
"name": "Attach",
"port": 9229,
"request": "attach",
"restart": true,
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"localRoot": "${workspaceFolder}/src",
"remoteRoot": "/home/node/app/src"
},
{ {
"console": "integratedTerminal", "console": "integratedTerminal",
"internalConsoleOptions": "neverOpen", "internalConsoleOptions": "neverOpen",
"name": "nodemon", "name": "nodemon (dev)",
"program": "dev", "program": "dev",
"request": "launch", "request": "launch",
"restart": true, "restart": true,
@@ -16,8 +28,8 @@
"<node_internals>/**" "<node_internals>/**"
], ],
"type": "node", "type": "node",
"env": {"NODE_ENV": "development"}, "env": {"NODE_ENV": "development", "DEBUG": "app"},
"preLaunchTask": "npm: scss" "preLaunchTask": "npm: build-css"
} }
] ]
} }

16
.vscode/tasks.json vendored
View File

@@ -3,17 +3,21 @@
"tasks": [ "tasks": [
{ {
"type": "npm", "type": "npm",
"script": "scss", "script": "watch-scss",
"problemMatcher": [], "problemMatcher": [],
"label": "npm: scss watch", "label": "npm: watch-scss",
"detail": "sass --watch src/scss/application.scss public/css/application.css src/scss/eventsheet.scss:public/css/eventsheet.css" "detail": "npm run watch-css",
"icon": {
"id": "eye",
"color": "terminal.ansiBlue"
}
}, },
{ {
"type": "npm", "type": "npm",
"script": "scss", "script": "build-css",
"problemMatcher": [], "problemMatcher": [],
"label": "npm: scss", "label": "npm: build-css",
"detail": "sass src/scss/application.scss:public/css/application.css src/scss/eventsheet.scss:public/css/eventsheet.css" "detail": "npm build-css"
} }
] ]
} }

21
bin/www
View File

@@ -10,8 +10,7 @@ var https = require("https");
var fs = require("fs"); var fs = require("fs");
var debug = require("debug")("https"); var debug = require("debug")("https");
const path = require("path"); const path = require("path");
var livereload = require("livereload");
/** /**
* Get port from environment and store in Express. * Get port from environment and store in Express.
@@ -19,30 +18,16 @@ const path = require("path");
var port = normalizePort(process.env.PORT || "3000"); var port = normalizePort(process.env.PORT || "3000");
app.set("port", port); app.set("port", port);
var server = http.createServer(app);
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
/** const liveReloadServer = livereload.createServer({port:35729});
* 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});
liveReloadServer.watch(path.join(__dirname, "../src/views")); liveReloadServer.watch(path.join(__dirname, "../src/views"));
liveReloadServer.server.once("connection", () => { liveReloadServer.server.once("connection", () => {
setTimeout(() => { setTimeout(() => {
liveReloadServer.refresh("/"); liveReloadServer.refresh("/");
}, 100); }, 100);
}); });
var server = https.createServer(https_options, app);
}
else {
var server = http.createServer(app);
} }
/** /**

View File

@@ -1,3 +1,26 @@
{
{$LOG_LEVEL} # Set via environment variable
}
localhost {
# Development configuration
@notProd {
expression {env.ENVIRONMENT} == 'development'
}
handle @notProd {
# Configuration that only applies when not in production
reverse_proxy app-dev:3000
}
}
{$DOMAIN} { {$DOMAIN} {
reverse_proxy app:3000 # 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
}
} }

View File

@@ -1,14 +1,32 @@
version: "3.3"
services: services:
app: app: &app
env_file: env_file:
- .env - .env
build: . build: .
networks: networks:
- web - web
profiles:
- production
expose: expose:
- 3000 - 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: caddy:
image: caddy image: caddy
@@ -17,10 +35,16 @@ services:
- 443:443 - 443:443
volumes: volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile - ./caddy/Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
networks: networks:
- web - web
env_file: env_file:
- .env - .env
networks: networks:
web: web:
volumes:
caddy_data:
caddy_config:

71
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@teamsnap/teamsnap-ui": "^3.12.3", "@teamsnap/teamsnap-ui": "^3.12.3",
"better-sqlite3": "^9.4.1", "better-sqlite3": "^9.6.0",
"better-sqlite3-session-store": "^0.1.0", "better-sqlite3-session-store": "^0.1.0",
"bootstrap": "^5.3.1", "bootstrap": "^5.3.1",
"bootstrap-icons": "^1.10.5", "bootstrap-icons": "^1.10.5",
@@ -33,6 +33,7 @@
"passport-teamsnap": "^1.1.1", "passport-teamsnap": "^1.1.1",
"pluralize": "^8.0.0", "pluralize": "^8.0.0",
"pug": "^3.0.2", "pug": "^3.0.2",
"sass": "^1.77.2",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"teamsnap.js": "github:anthonyscorrea/teamsnap-javascript-sdk#link-with-null-link", "teamsnap.js": "github:anthonyscorrea/teamsnap-javascript-sdk#link-with-null-link",
"tinymce": "^6.8.3", "tinymce": "^6.8.3",
@@ -663,7 +664,6 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"dependencies": { "dependencies": {
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"picomatch": "^2.0.4" "picomatch": "^2.0.4"
@@ -813,9 +813,9 @@
} }
}, },
"node_modules/better-sqlite3": { "node_modules/better-sqlite3": {
"version": "9.4.1", "version": "9.6.0",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.4.1.tgz", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.6.0.tgz",
"integrity": "sha512-QpqiQeMI4WkE+dQ68zTMX5OzlPGc7lXIDP1iKUt4Omt9PdaVgzKYxHIJRIzt1E+RUBQoFmkip/IbvzyrxehAIg==", "integrity": "sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"bindings": "^1.5.0", "bindings": "^1.5.0",
@@ -834,7 +834,6 @@
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -997,7 +996,6 @@
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.0.1"
}, },
@@ -1169,7 +1167,6 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"dependencies": { "dependencies": {
"anymatch": "~3.1.2", "anymatch": "~3.1.2",
"braces": "~3.0.2", "braces": "~3.0.2",
@@ -1618,9 +1615,9 @@
} }
}, },
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.0.2", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
"integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -2040,7 +2037,6 @@
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@@ -2195,7 +2191,6 @@
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"optional": true, "optional": true,
"os": [ "os": [
@@ -2304,7 +2299,6 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": { "dependencies": {
"is-glob": "^4.0.1" "is-glob": "^4.0.1"
}, },
@@ -2564,6 +2558,11 @@
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
"dev": true "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": { "node_modules/inherits": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
@@ -2632,7 +2631,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"dependencies": { "dependencies": {
"binary-extensions": "^2.0.0" "binary-extensions": "^2.0.0"
}, },
@@ -2704,7 +2702,6 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -2722,7 +2719,6 @@
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"dependencies": { "dependencies": {
"is-extglob": "^2.1.1" "is-extglob": "^2.1.1"
}, },
@@ -2745,7 +2741,6 @@
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": { "engines": {
"node": ">=0.12.0" "node": ">=0.12.0"
} }
@@ -3270,9 +3265,9 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
}, },
"node_modules/node-abi": { "node_modules/node-abi": {
"version": "3.54.0", "version": "3.62.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.54.0.tgz", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz",
"integrity": "sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==", "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==",
"dependencies": { "dependencies": {
"semver": "^7.3.5" "semver": "^7.3.5"
}, },
@@ -3373,7 +3368,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -3667,7 +3661,6 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
}, },
@@ -3719,9 +3712,9 @@
} }
}, },
"node_modules/prebuild-install": { "node_modules/prebuild-install": {
"version": "7.1.1", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz",
"integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==",
"dependencies": { "dependencies": {
"detect-libc": "^2.0.0", "detect-libc": "^2.0.0",
"expand-template": "^2.0.3", "expand-template": "^2.0.3",
@@ -4078,7 +4071,6 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"dependencies": { "dependencies": {
"picomatch": "^2.2.1" "picomatch": "^2.2.1"
}, },
@@ -4228,6 +4220,22 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "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": { "node_modules/semver": {
"version": "7.6.0", "version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
@@ -4466,6 +4474,14 @@
"node": ">=0.10.0" "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": { "node_modules/source-map-support": {
"version": "0.5.21", "version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@@ -4735,7 +4751,6 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": { "dependencies": {
"is-number": "^7.0.0" "is-number": "^7.0.0"
}, },

View File

@@ -24,13 +24,17 @@
}, },
"scripts": { "scripts": {
"start": "node ./bin/www", "start": "node ./bin/www",
"dev": "nodemon . & npm run scss", "dev": "nodemon --inspect=0.0.0.0 ./bin/www",
"scss": "sass src/scss/application.scss:src/public/css/application.css src/scss/eventsheet.scss:src/public/css/eventsheet.css", "build-css": "sass src/scss:src/public/css",
"scss watch": "sass --watch src/scss/application.scss:src/public/css/application.css src/scss/eventsheet.scss:src/public/css/eventsheet.css" "watch-scss": "nodemon -e scss -x \"npm run build-css\""
},
"nodemonConfig": {
"ext": "js,hbs,scss",
"watch": ["src"]
}, },
"dependencies": { "dependencies": {
"@teamsnap/teamsnap-ui": "^3.12.3", "@teamsnap/teamsnap-ui": "^3.12.3",
"better-sqlite3": "^9.4.1", "better-sqlite3": "^9.6.0",
"better-sqlite3-session-store": "^0.1.0", "better-sqlite3-session-store": "^0.1.0",
"bootstrap": "^5.3.1", "bootstrap": "^5.3.1",
"bootstrap-icons": "^1.10.5", "bootstrap-icons": "^1.10.5",
@@ -54,6 +58,7 @@
"passport-teamsnap": "^1.1.1", "passport-teamsnap": "^1.1.1",
"pluralize": "^8.0.0", "pluralize": "^8.0.0",
"pug": "^3.0.2", "pug": "^3.0.2",
"sass": "^1.77.2",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"teamsnap.js": "github:anthonyscorrea/teamsnap-javascript-sdk#link-with-null-link", "teamsnap.js": "github:anthonyscorrea/teamsnap-javascript-sdk#link-with-null-link",
"tinymce": "^6.8.3", "tinymce": "^6.8.3",

View File

@@ -43,6 +43,16 @@ hbs.registerHelper('section', (name, options) => {
this._sections[name] = options.fn(this); this._sections[name] = options.fn(this);
return null; 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("embeddedSvgFromPath", require('./lib/utils').embeddedSvgFromPath)
hbs.registerHelper(require("./controllers/event").helpers) hbs.registerHelper(require("./controllers/event").helpers)
hbs.registerHelper(require("./controllers/eventlineup").helpers) hbs.registerHelper(require("./controllers/eventlineup").helpers)
@@ -52,12 +62,14 @@ app.set("view engine", "hbs");
app.locals.pluralize = require("pluralize"); app.locals.pluralize = require("pluralize");
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
console.log('adding connectLiveReload')
var connectLiveReload = require("connect-livereload"); 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.json());
app.use(bodyParser.urlencoded()); app.use(bodyParser.urlencoded({extended: true }));
app.use(logger("dev")); app.use(logger("dev"));
app.use(cors(corsOptions)) app.use(cors(corsOptions))
app.use(cookieParser()); app.use(cookieParser());
@@ -108,8 +120,10 @@ app.use(
intervalMs: 900000 //ms = 15min intervalMs: 900000 //ms = 15min
} }
}), }),
cookie: { maxAge: 86400000 }, // value of maxAge is defined in milliseconds.
teamsnap_token: "", teamsnap_token: "",
current_team: "", current_team: "",
csrfToken:"",
secret: process.env['SECRET'], secret: process.env['SECRET'],
resave: false, // don't save session if unmodified resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored 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) { app.use(function (err, req, res, next) {
// set locals, only providing error in development // set locals, only providing error in development
if (err) { 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 : {}; res.locals.error = req.app.get("env") === "development" ? err : {};
if (typeof err === 'string' || err instanceof String) { if (typeof err === 'string' || err instanceof String) {
err = { err = {
@@ -159,9 +173,10 @@ app.use(function (err, req, res, next) {
}); });
// catch 404 and forward to error handler // catch 404 and forward to error handler
// app.use(function (req, res, next) { app.use(function (req, res, next) {
// next(createError(404)); // next(createError(404));
// }); res.status(404).send('not found')
});
app.set('trust proxy') app.set('trust proxy')

View File

@@ -21,6 +21,10 @@ exports.helpers = {
exports.partials = path.join(__dirname, "../views/event/partials") 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) => { exports.getEvents = async (req, res, next) => {
const {user, team, layout} = req const {user, team, layout} = req
const bulkLoadTypes = ["event", "availabilitySummary"] const bulkLoadTypes = ["event", "availabilitySummary"]
@@ -67,3 +71,53 @@ exports.getEvent = async (req, res, next) => {
}; };
res.render("event/show", context); 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'))
}

View File

@@ -1,91 +1,51 @@
const path = require('path') const path = require('path')
const fs = require('fs') 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 tsUtils = require('../lib/utils')
const { loadEventLineupEntries } = require('teamsnap.js') const { loadEventLineupEntries } = require('teamsnap.js')
exports.partials = path.join(__dirname, "../views/eventlineup/partials") exports.partials = path.join(__dirname, "../views/eventlineup/partials")
exports.helpers = require('../helpers/eventlineup.js')
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.getEventLineup = async (req, res)=>{ exports.getEventLineup = async (req, res)=>{
// res.send(req.event_lineup)
await Promise.all(req.promises) await Promise.all(req.promises)
const {user, team, members, event, layout, event_lineup, event_lineup_entries, availabilities, availabilitySummary, csrfToken} = req const {user, team, members, event, layout, event_lineup, event_lineup_entries, availabilities, availabilitySummary, csrfToken} = req
attachBenchcoachPropertiesToMember(members, event_lineup_entries, availabilities) attachBenchcoachPropertiesToMember(members, event_lineup_entries, availabilities)
members.sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName) 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) => { 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 // 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. // 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 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) const availability = availabilities.find(e=>e.memberId == member.id)
member.benchcoach.availability = availability member.benchcoach.availability = availability
if (event_lineup_entry != null) { if (event_lineup_entry != null) {
@@ -125,7 +85,11 @@ exports.getEventLineupEmail = async (req, res)=>{
const {newEventLineupEntries} = processPostedEventLineupEntries(body, eventLineupEntries, event_lineup) const {newEventLineupEntries} = processPostedEventLineupEntries(body, eventLineupEntries, event_lineup)
attachBenchcoachPropertiesToMember(members, newEventLineupEntries, availabilities) attachBenchcoachPropertiesToMember(members, newEventLineupEntries, availabilities)
members.sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName) 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)=>{ exports.getEventLineupEntries = async (req, res)=>{
@@ -143,28 +107,48 @@ exports.postEventLineup = async (req,res) => {
if (body.memberId == null) {res.status(400).end();return} if (body.memberId == null) {res.status(400).end();return}
await Promise.all(req.promises); await Promise.all(req.promises);
const eventLineupEntries = req.event_lineup.eventLineupEntries 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=>{ newEventLineupEntries.forEach(e=>{
teamsnap.saveEventLineupEntry(e) teamsnap.saveEventLineupEntry(e, teamsnapCallback)
}) })
eventLineup = await teamsnap.loadEventLineups(req.params.event_id) deleteEventLineupEntries.forEach(e=>{
res.status(201).end() 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 processPostedEventLineupEntries = (body, eventLineupEntries, eventLineup) => {
const newEventLineupEntries = [] const newEventLineupEntries = []
const deleteEventLineupEntries = []
body.memberId.forEach((memberId, i)=>{ body.memberId.forEach((memberId, i)=>{
const lineupEntryId = body.eventLineupEntryId[i] const lineupEntryId = body.eventLineupEntryId[i]
const lineupEntryLabel = body.label[i] const lineupEntryLabel = body.label[i]
const lineupEntrySequence = body.sequence[i] const lineupEntrySequence = body.sequence[i]
const lineupEntryFlags = body.flags[i] const lineupEntryFlags = body.flags[i]
if (lineupEntryId != '') { if (lineupEntryId != '' && lineupEntryLabel != '') {
// Update lineup entry // 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)) const eventLineupEntry = eventLineupEntries.find((e)=>e.id==Number(lineupEntryId))
eventLineupEntry.sequence = lineupEntrySequence deleteEventLineupEntries.push(eventLineupEntry)
eventLineupEntry.label = compilePositionLabel(lineupEntryLabel, lineupEntryFlags)
newEventLineupEntries.push(eventLineupEntry)
} }
else if (lineupEntryLabel != '') { else if (lineupEntryLabel != '') {
// Create lineup entry // Create lineup entry
@@ -179,5 +163,34 @@ const processPostedEventLineupEntries = (body, eventLineupEntries, eventLineup)
// Skip lineup entry // 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'))
} }

View File

@@ -23,8 +23,14 @@ exports.getEventSheet = async (req,res) =>{
) )
) )
await Promise.all(req.promises) 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 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) => { exports.getLineupCard = (req, res, next) => {
@@ -82,7 +88,7 @@ exports.getLineupCard = (req, res, next) => {
availabilities: items.filter((i) => i.type == "availability").sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName), availabilities: items.filter((i) => i.type == "availability").sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName),
all_lineup_entries: items.filter((i) => i.type == "eventLineupEntry"), all_lineup_entries: items.filter((i) => i.type == "eventLineupEntry"),
event_lineup_entries_offense: items 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), .sort((a, b) => a.sequence - b.sequence),
event_lineup_entries: items event_lineup_entries: items
.filter((i) => i.type == "eventLineupEntry" && i.eventId == event_id) .filter((i) => i.type == "eventLineupEntry" && i.eventId == event_id)

View File

@@ -3,7 +3,7 @@ const { teamsnapCallback } = require("../lib/utils");
utils = require("../lib/utils"); utils = require("../lib/utils");
exports.getTeams = async (req, res, next) => { exports.getTeams = async (req, res, next) => {
const {layout} = req const {layout, user} = req
const {user_id} = req.params const {user_id} = req.params
req.session.current_team_id = null req.session.current_team_id = null
promise = teamsnap.loadTeams({'userId':user_id}, promise = teamsnap.loadTeams({'userId':user_id},
@@ -17,7 +17,7 @@ exports.getTeams = async (req, res, next) => {
req.promises.push(promise) req.promises.push(promise)
await Promise.all(req.promises) await Promise.all(req.promises)
try { 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); res.render("team/list", context);
} catch (e){ } catch (e){
next(e); next(e);

119
src/helpers/eventlineup.js Normal file
View 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
}

View File

@@ -1,5 +1,6 @@
const { parsePositionLabel, teamsnapMembersSortLineupAvailabilityLastName, teamsnapMembersSortAvailabilityLastName } = require('../lib/utils') const { parsePositionLabel, teamsnapMembersSortLineupAvailabilityLastName, teamsnapMembersSortAvailabilityLastName } = require('../lib/utils')
const {attachBenchcoachPropertiesToMember} = require('../controllers/eventlineup') const {attachBenchcoachPropertiesToMember} = require('../controllers/eventlineup')
const Handlebars = require("handlebars");
exports.offenseLineup = (number_of_slots, event_lineup_entries, members, options) => { exports.offenseLineup = (number_of_slots, event_lineup_entries, members, options) => {
var results = "" 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++){ for (let i = 0; i < number_of_slots; i++){
const event_lineup_entry = event_lineup_entries ? event_lineup_entries[i] : null 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({ results += options.fn({
sequence: event_lineup_entry.sequence, sequence: event_lineup_entry.sequence,
member: members.find(member=> event_lineup_entry.memberId == member.id || event_lineup_entry.memberName == `${member.firstName} ${member.lastName}`), 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 {event, event_lineup_entries, members, availabilities} = options.data.root
const players = members.filter(m=>!m.isNonPlayer) 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)) 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=>{ players.forEach(member=>{
const {firstName, lastName, jerseyNumber, benchcoach, position, id} = member const {firstName, lastName, jerseyNumber, benchcoach, position, id} = member
@@ -87,6 +88,9 @@ const positionGroups = {
} }
exports.positionCapabilityFor = (member, position, options) => { exports.positionCapabilityFor = (member, position, options) => {
if (!member.position) {
return ""
}
const member_positions = member.position.split(",").map(s=>s.trim()) const member_positions = member.position.split(",").map(s=>s.trim())
const member_position_groups = new Set(member.position.split(",").map(s=>positionGroups[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) => { exports.loopEvents = (events, options) => {
var results = ""; var results = "";
events.forEach(event => { if (options.data) {
results += options.fn(event) data = Handlebars.createFrame(options.data);
}
events.forEach((event,i) => {
if (data) {
data.index = i;
}
results += options.fn(event, {data: data })
} }
) )
return results; return results;
@@ -121,6 +132,9 @@ exports.loopEvents = (events, options) => {
exports.timepointForMember = (member, timeline, event, options) => { exports.timepointForMember = (member, timeline, event, options) => {
var results = "" 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 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) const eventLineupEntry = timeline.event_lineup_entries.find(a=>(a.memberId==member.id || a.memberName == `${member.firstName} ${member.lastName}`) && a.eventId==event.id)
var value = "" var value = ""
@@ -128,7 +142,15 @@ exports.timepointForMember = (member, timeline, event, options) => {
value = parsePositionLabel(eventLineupEntry.label).positionLabelWithoutFlags value = parsePositionLabel(eventLineupEntry.label).positionLabelWithoutFlags
} }
else { 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})
} }

View File

@@ -50,17 +50,6 @@ teamsnapMembersSortAvailabilityLastName = (a, b) => {
} }
exports.teamsnapMembersSortAvailabilityLastName = teamsnapMembersSortAvailabilityLastName 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) => { exports.teamsnapCallback = (err,result, d) => {
if (Array.isArray(result)){ if (Array.isArray(result)){
types = new Set(result.map(i=>i.type)) types = new Set(result.map(i=>i.type))
@@ -95,6 +84,10 @@ const getPluralType = (type) =>{
// is not generated in the lookup. this is a // is not generated in the lookup. this is a
// kludge around that. (specifically availabilitySummary) // kludge around that. (specifically availabilitySummary)
plural = teamsnap.getPluralType(type) || (function() { plural = teamsnap.getPluralType(type) || (function() {
if (type === undefined){
return type
}
switch (type.slice(-1)) { switch (type.slice(-1)) {
case 'y': case 'y':
return type.slice(0, -1) + 'ies'; return type.slice(0, -1) + 'ies';
@@ -120,7 +113,7 @@ exports.groupTeamsnapItems = (items, types = [], params = {}) => {
return result; return result;
} }
exports.embeddedSvgFromPath = (svg_path, additional_classes = "") => { exports.embeddedSvgFromPath = (svg_path, additional_classes, options) => {
const iconStaticPaths = { const iconStaticPaths = {
"/teamsnap-ui/assets":path.join(__dirname, "/../../node_modules/@teamsnap/teamsnap-ui/src/assets"), "/teamsnap-ui/assets":path.join(__dirname, "/../../node_modules/@teamsnap/teamsnap-ui/src/assets"),
"/bootstrap-icons":path.join(__dirname, "/../../node_modules/bootstrap-icons/icons"), "/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'); const svg = fs.readFileSync(`${svg_path}`, 'utf8');
svgRegExWithClass = new RegExp(/<svg(.*)class="(.*?)"(.*)>/) svgRegExWithClass = new RegExp(/<svg(.*)class="(.*?)"(.*)>/)
@@ -157,16 +152,29 @@ exports.parsePositionLabel = (label) => {
const pattern = /(?<pos>[A-Z0-9]+)(?:\s\[(?<flags>.[A-z,]+)\])?/g const pattern = /(?<pos>[A-Z0-9]+)(?:\s\[(?<flags>.[A-z,]+)\])?/g
const {pos, flags} = pattern.exec(label)?.groups || {} const {pos, flags} = pattern.exec(label)?.groups || {}
const positionLabelWithoutFlags= pos const positionLabelWithoutFlags= pos
const positionFlags = flags?.split(',').map(f=>f.trim()) || [] const positionFlags = new Set(flags?.split(',').map(f=>f.trim()) || [])
return {positionLabelWithoutFlags, positionFlags} return {positionLabelWithoutFlags, positionFlags}
} }
exports.compilePositionLabel = (label, flags) => { exports.compilePositionLabel = (label, flags) => {
if (flags == null || flags == '' || flags.lengh == 0) { if (flags == null || flags == '' || flags.size == 0) {
return label return label
} }
else { 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(',')}]` 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
} }

View File

@@ -1,24 +1,26 @@
exports.loadRecentAndUpcomingEvents = async (req, res, next) => { exports.loadRecentAndUpcomingEvents = async (req, res, next) => {
const {team_id, event_id} = req.params 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) { if (event_id) {
const event = await teamsnap.loadEvents({id: event_id}).pop() 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 { else {
subject_date = new Date().toISOString().slice(0,10) subject_date = new Date()
} }
req.promises.push( req.promises.push(
teamsnap.bulkLoad({ teamsnap.bulkLoad({
teamId: team_id, teamId: team_id,
types: ["event", "availabilitySummary"], types: ["event", "availabilitySummary"],
scopeTo: "event", scopeTo: "event",
event__startedAfter: subject_date, event__startedAfter: new Date(subject_date.getTime()+10000),
event__pageSize: 4 event__pageSize: page_size
}) })
.then(items => tsUtils.groupTeamsnapItems(items)) .then(items => tsUtils.groupTeamsnapItems(items))
.then((items)=>{ .then((items)=>{
req.upcoming_events=items.events || []; req.upcoming_events=items.events ? items.events : [];
const availabilitySummaries=items.availabilitySummaries; const availabilitySummaries=items.availabilitySummaries;
req.upcoming_events.forEach((event) => { req.upcoming_events.forEach((event) => {
event.link('availabilitySummary', availabilitySummaries.find(a=>a.eventId==event.id)) event.link('availabilitySummary', availabilitySummaries.find(a=>a.eventId==event.id))
@@ -31,8 +33,8 @@ exports.loadRecentAndUpcomingEvents = async (req, res, next) => {
teamId: team_id, teamId: team_id,
types: ["event", "availabilitySummary"], types: ["event", "availabilitySummary"],
scopeTo: "event", scopeTo: "event",
event__startedBefore: subject_date, event__startedBefore: new Date(subject_date.getTime()-10000),
event__pageSize: 4, event__pageSize: page_size,
event__sortStartDate: "desc" event__sortStartDate: "desc"
}) })
.then(items => tsUtils.groupTeamsnapItems(items)) .then(items => tsUtils.groupTeamsnapItems(items))

View File

@@ -14,6 +14,7 @@
*= require_tree . *= require_tree .
*= require_self *= require_self
*/ */
@import url("/font/helvetica-now/stylesheet.css");
@font-face { @font-face {
font-family: "MuseoSansRounded100Regular"; font-family: "MuseoSansRounded100Regular";
src: url("https://teamsnap-ui.teamsnap.com/assets/fonts/museo/MuseoSansRounded-100-webfont.eot"); src: url("https://teamsnap-ui.teamsnap.com/assets/fonts/museo/MuseoSansRounded-100-webfont.eot");
@@ -1003,21 +1004,21 @@ h6 {
color: #ffffff; color: #ffffff;
} }
.Button--blue { .Button--blue, button:has(+ .position-label-flags :checked) {
background-color: #1A6BAF; background-color: #1A6BAF;
border-color: #15568c; border-color: #15568c;
color: #ffffff; 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; background-color: #17609e;
border-color: #134d7e; border-color: #134d7e;
color: #ffffff; color: #ffffff;
} }
.Button--blue.is-active { .Button--blue.is-active, button.is-active:has(+ .position-label-flags :checked) {
background-color: #17609e; background-color: #17609e;
color: #ffffff; 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; background-color: #1A6BAF;
border-color: #15568c; border-color: #15568c;
color: #ffffff; color: #ffffff;
@@ -2311,13 +2312,13 @@ input:-webkit-autofill:focus {
} }
.Popup-container::before { .Popup-container::before {
top: calc( 100% - 6px ); top: calc(100% - 6px);
border: solid #d6d6d6 1px; border: solid #d6d6d6 1px;
box-shadow: 0 0 2px rgba(56, 56, 56, 0.15); box-shadow: 0 0 2px rgba(56, 56, 56, 0.15);
} }
.Popup-container::after { .Popup-container::after {
top: calc( 100% - 7px ); top: calc(100% - 7px);
} }
.Popup-content { .Popup-content {
@@ -2756,7 +2757,7 @@ input:-webkit-autofill:focus {
.StepNav-stepTitle { .StepNav-stepTitle {
width: 150px; width: 150px;
left: calc( 0px - 43px); left: calc(0px - 43px);
padding-top: 4px; padding-top: 4px;
} }
@@ -2874,7 +2875,7 @@ input:-webkit-autofill:focus {
} }
.StepNav--small .StepNav-stepTitle { .StepNav--small .StepNav-stepTitle {
width: 150px; width: 150px;
left: calc( 0px - 53px); left: calc(0px - 53px);
padding-top: 2px; padding-top: 2px;
} }
.StepNav--small .StepNav-stepIcon { .StepNav--small .StepNav-stepIcon {
@@ -2909,7 +2910,7 @@ input:-webkit-autofill:focus {
} }
.StepNav--xsmall .StepNav-stepTitle { .StepNav--xsmall .StepNav-stepTitle {
width: 150px; width: 150px;
left: calc( 0px - 62px); left: calc(0px - 62px);
padding-top: 1px; padding-top: 1px;
} }
.StepNav--xsmall .StepNav-stepIcon { .StepNav--xsmall .StepNav-stepIcon {
@@ -6908,12 +6909,46 @@ input:-webkit-autofill:focus {
background-color: #d6d6d6; background-color: #d6d6d6;
} }
header.Header { header {
background: #323669; background: #323669;
padding: 8px 0; padding: 8px 0;
box-shadow: 0 4px 0 rgba(0, 0, 25, 0.1); box-shadow: 0 4px 0 rgba(0, 0, 25, 0.1);
border-bottom: 1px solid #d6d6d6; border-bottom: 1px solid #d6d6d6;
color: white; 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 { body {
@@ -6970,52 +7005,20 @@ body {
text-align: center; 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 { a.Panel-row {
color: inherit; color: inherit;
} }
.benchcoach-nav h3 {
font-family: "Helvetica", sans-serif;
font-weight: bolder;
color: white;
text-transform: uppercase;
}
.lineup-slot .Panel-cell { .lineup-slot .Panel-cell {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
} }
div[id^=event-lineup] { div.event-lineup {
max-width: 576px; max-width: 576px;
counter-reset: lineup-sequence-counter 0; counter-reset: lineup-sequence-counter 0;
margin-left: 8px;
margin-right: 9px;
} }
.lineup-slot { .lineup-slot {
@@ -7090,9 +7093,23 @@ li .availability-status-code- {
padding: 0; 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; 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 { .Tooltip:after {
padding: 2px !important; padding: 2px !important;
font-size: inherit !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) { @media (max-width: 480px) {
.Panel--full { .Panel--full {
border-radius: 0; border-radius: 0;
margin-right: -16px;
margin-left: -16px;
border-right: none; border-right: none;
border-left: none; border-left: none;
} }
@@ -7136,8 +7151,11 @@ div[id^=event-lineup] .Panel.position-only .Panel-cell:has(.sequence), div[id^=e
} }
.Panel .Panel { .Panel .Panel {
border: none; margin: 8px;
margin: 0; }
.scroll-horizontal {
overflow-x: scroll;
} }
/*# sourceMappingURL=application.css.map */ /*# 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

View File

@@ -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
View 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"
}

View File

@@ -1,6 +1,7 @@
var express = require("express"); const express = require("express");
var passport = require("passport"); const passport = require("passport");
var TeamsnapStrategy = require("passport-teamsnap"); const TeamsnapStrategy = require("passport-teamsnap");
const {teamsnapCallback} = require('../lib/utils')
// const {teamsnap} = require("../app"); // const {teamsnap} = require("../app");
// Configure the TeamSnap strategy for use by Passport. // Configure the TeamSnap strategy for use by Passport.
// //
@@ -21,20 +22,17 @@ passport.use(
proxy: true proxy: true
}, },
async function (req, accessToken, refreshToken, profile, done) { async function (req, accessToken, refreshToken, profile, done) {
json = JSON.parse(profile._raw); // json = JSON.parse(profile._raw);
field_from_collection = (field_name) => { const new_profile = {
return json.collection.items[0].data.filter( access_token: accessToken,
(e) => e.name == field_name };
)[0].value; ['id', 'email', 'first_name', 'last_name', 'managed_team_ids'].forEach(
} k => {
const new_profile = { access_token: accessToken }; new_profile[k] = profile.data[0].get(k)
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")
req.session.teamsnap_access_token = accessToken; req.session.teamsnap_access_token = accessToken;
await initTeamsnap(process.env["TEAMSNAP_CLIENT_ID"], accessToken) await initTeamsnap(accessToken)
return done(null, new_profile); return done(null, new_profile);
} }
) )
@@ -59,6 +57,7 @@ passport.serializeUser(function (user, cb) {
first_name: user.first_name, first_name: user.first_name,
last_name: user.last_name, last_name: user.last_name,
accessToken: user.access_token, 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) { passport.deserializeUser(function (user, cb) {
process.nextTick(async function () { process.nextTick(async function () {
console.log("L#68 deserializing user id", user.id); console.log("L#68 deserializing user id", user.id);
if (!teamsnap.isAuthed()){ try {
await initTeamsnap(process.env["TEAMSNAP_CLIENT_ID"], user.accessToken) await initTeamsnap(user.accessToken)
return cb(null, user);
} catch (err) {
return cb(err)
} }
return cb(null, user);
}); });
}); });
@@ -86,9 +87,11 @@ var router = express.Router();
router.get("/login", function (req, res, next) { router.get("/login", function (req, res, next) {
// https://stackoverflow.com/a/73056806/20522015 // https://stackoverflow.com/a/73056806/20522015
returnTo = req.session.returnTo; returnTo = req.session.returnTo;
// req.session.regenerate(); // this is not working right as of now... if (req.user?.accessToken){
req.session.returnTo = returnTo; res.redirect(returnTo || "/");
res.render("login", {layout:"layouts/main"}); } else {
res.render("login", {layout:"layouts/main"});
}
}); });
/* GET /login/federated/teamsnap /* GET /login/federated/teamsnap
@@ -125,10 +128,9 @@ router.get(
}) })
); );
const initTeamsnap = async (clientID, accessToken) => { const initTeamsnap = async (accessToken) => {
teamsnap.init(clientID); await teamsnap.auth(accessToken);
teamsnap.auth(accessToken); await teamsnap.loadCollections(teamsnapCallback);
await teamsnap.loadCollections();
await teamsnap.enablePersistence(); await teamsnap.enablePersistence();
} }
@@ -138,7 +140,7 @@ const ensureLoggedIn = (req, res, next) => {
res.redirect("/login"); res.redirect("/login");
// return next(); // return next();
} }
else{ else {
req.user = req.session.passport.user req.user = req.session.passport.user
next(); next();
} }

View File

@@ -3,6 +3,8 @@ const eventsController = require("../controllers/event");
const router = express.Router(); const router = express.Router();
const tsUtils = require("../lib/utils") const tsUtils = require("../lib/utils")
const {teamsnapCallback} = require("../lib/utils") const {teamsnapCallback} = require("../lib/utils")
const multer = require("multer");
const upload = multer()
// Middleware // Middleware
const loadEvent = (req,res,next) => { const loadEvent = (req,res,next) => {
@@ -23,12 +25,54 @@ const loadEvent = (req,res,next) => {
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) router.use("/:team_id([0-9]+)/event/:event_id([0-9]+)", loadEvent)
// Routes // Routes
router.get("/:team_id([0-9]+)/schedule", eventsController.getEvents); 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]+)", eventsController.getEvent);
// router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup", eventsController.getLineup); router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/availability_reminders", upload.none(), eventsController.sendAvailabilityReminders)
// router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup_card", eventsController.getLineupCard);
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}

View File

@@ -5,6 +5,8 @@ const tsUtils = require('../lib/utils')
const multer = require("multer"); const multer = require("multer");
const upload = multer() const upload = multer()
const { doubleCsrfProtection } = require('../middlewares/csrf'); const { doubleCsrfProtection } = require('../middlewares/csrf');
const {loadRecentAndUpcomingEvents} = require('../middlewares/bulkload')
const {loadEvents} = require('./event')
const {teamsnapCallback} = require("../lib/utils") 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.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.get ("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)", upload.none(), doubleCsrfProtection, eventsLineupController.getEventLineup);
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)", upload.none(), doubleCsrfProtection, eventsLineupController.postEventLineup); router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)", upload.none(), doubleCsrfProtection, eventsLineupController.postEventLineup);
// 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]+)/lineup/:event_lineup_id([0-9]+)/delete", upload.none(), eventsLineupController.submitDeleteEventLineupEntries);
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/entries", eventsLineupController.getEventLineupEntries)
module.exports = {router, loadEventLineup} module.exports = {router, loadEventLineup}

View File

@@ -1,7 +1,7 @@
const express = require("express"); const express = require("express");
const eventsSheetController = require("../controllers/eventsheet"); const eventsSheetController = require("../controllers/eventsheet");
const {loadEventLineup} = require("./eventlineup"); const {loadEventLineup} = require("./eventlineup");
const {loadEvent} = require("./event"); const {loadEvent, loadEvents} = require("./event");
const {loadRecentAndUpcomingEvents} = require("../middlewares/bulkload") const {loadRecentAndUpcomingEvents} = require("../middlewares/bulkload")
const router = express.Router(); const router = express.Router();
const tsUtils = require('../lib/utils') const tsUtils = require('../lib/utils')
@@ -10,46 +10,6 @@ const multer = require("multer");
const upload = 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) => { const linksForEventSheet = async (req, res, next) => {
await Promise.all(req.promises) await Promise.all(req.promises)
const events = [...req.recent_events, req.event, ...req.upcoming_events] 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 ) 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} module.exports = {router}

View File

@@ -27,4 +27,10 @@ router.get("/", (req,res,next) => {
router.get("/:team_id([0-9]+)/members", membersController.getMembers); 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}; module.exports = {router, partials};

View File

@@ -15,6 +15,7 @@
*/ */
@import "../../node_modules/@teamsnap/teamsnap-ui/src/css/teamsnap-ui.scss"; @import "../../node_modules/@teamsnap/teamsnap-ui/src/css/teamsnap-ui.scss";
@import url('/font/helvetica-now/stylesheet.css');
$color-success: #b7e1cd; $color-success: #b7e1cd;
@@ -62,12 +63,54 @@ $monospace-font: "Inconsolata", monospace;
} }
} }
header.Header { header {
background: #323669; background: #323669;
padding: 8px 0; padding: 8px 0;
// margin: 0 0 16px 0;
box-shadow: 0 4px 0 rgba(0, 0, 25, 0.1); box-shadow: 0 4px 0 rgba(0, 0, 25, 0.1);
border-bottom: 1px solid #d6d6d6; border-bottom: 1px solid #d6d6d6;
color: white; 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 { body {
@@ -137,52 +180,20 @@ body {
text-align: center; 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 { a.Panel-row {
color: inherit; color: inherit;
} }
.benchcoach-nav h3 {
font-family: "Helvetica", sans-serif;
font-weight: bolder;
color: white;
text-transform: uppercase;
}
.lineup-slot .Panel-cell { .lineup-slot .Panel-cell {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
} }
div[id^="event-lineup"] { div.event-lineup {
max-width: 576px; max-width: 576px;
counter-reset: lineup-sequence-counter 0; counter-reset: lineup-sequence-counter 0;
margin-left: 8px;
margin-right: 9px;
} }
.lineup-slot { .lineup-slot {
@@ -278,17 +289,31 @@ li .availability-status-code- {
} }
div[id^="event-lineup"] .Panel { div.event-lineup {
&.position-only .Panel-cell:has(.sequence), &.bench .Panel-cell:has(.sequence), &.out .Panel-cell:has(.sequence){ .lineup-segment {
display: none; &:has(input.Toggle-input:not(:checked)) {
} &.out {
.Panel-cell:has(.SelectBox),
&.out { .Panel-cell:has(.drag-handle),
.Panel-cell { button:has(+.position-label-flags),
&:has(.sequence), .drag-handle, .SelectBox { button.addToStarting,
// display: none; button.addToBench
{
display: none;
}
} }
} }
&.bench, &.position-only, &.out {
.Panel-cell:has(.sequence) {
display: none;
}
&.bench button.addToBench {
display: none;
}
}
&.starting button.addToStarting, &.position-only button.addToStarting {
display: none;
}
} }
} }
@@ -300,8 +325,8 @@ div[id^="event-lineup"] .Panel {
@media (max-width: 480px){ @media (max-width: 480px){
.Panel--full { .Panel--full {
border-radius: 0; border-radius: 0;
margin-right: -16px; // margin-right: -16px;
margin-left: -16px; // margin-left: -16px;
border-right: none; border-right: none;
border-left: none; border-left: none;
}} }}
@@ -343,8 +368,16 @@ div[id^="event-lineup"] .Panel {
.Panel .Panel{ .Panel .Panel{
// padding: 0; // padding: 0;
// border-radius: 0; // border-radius: 0;
border: none; // border: none;
// border-top: 1px solid #d6d6d6; // border-top: 1px solid #d6d6d6;
// border-bottom: 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

View File

@@ -12,21 +12,23 @@
</div> </div>
</div> </div>
<div class=" Panel-footer u-flex u-flexJustifyAround u-padSm"> <div class=" Panel-footer u-flex u-flexJustifyAround u-padSm">
<a class="Button" href="/{{team.id}}/event/{{event.id}}"> <div class="u-maxWidthXs">
<span>{{{embeddedSvgFromPath "/bootstrap-icons/calendar.svg"}}}</span> <a class="Button" href="/{{team.id}}/event/{{event.id}}">
<span class="u-hidden u-xs-inline">Details</span> <span>{{{embeddedSvgFromPath "/bootstrap-icons/calendar.svg"}}}</span>
</a> <span class="u-hidden">Details</span>
<a class="Button" href="/{{team.id}}/event/{{event.id}}/lineup"> </a>
<span>{{{embeddedSvgFromPath "/bootstrap-icons/clipboard.svg"}}}</span> <a class="Button" href="/{{team.id}}/event/{{event.id}}/lineup">
<span class="u-hidden u-xs-inline">Lineup</span> <span>{{{embeddedSvgFromPath "/bootstrap-icons/clipboard.svg"}}}</span>
</a> <span class="u-hidden">Lineup</span>
<a class="Button" href="/{{team.id}}/event/{{event.id}}/sheet"> </a>
<span>{{{embeddedSvgFromPath "/bootstrap-icons/file-earmark.svg"}}}</span> <a class="Button" href="/{{team.id}}/event/{{event.id}}/sheet">
<span class="u-hidden u-xs-inline">Sheet</span> <span>{{{embeddedSvgFromPath "/bootstrap-icons/file-earmark.svg"}}}</span>
</a> <span class="u-hidden">Sheet</span>
<a class="Button" href="https://go.teamsnap.com/{{team.id}}/schedule/view_game/{{event.id}}"> </a>
<span>{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}</span> <a class="Button" href="https://go.teamsnap.com/{{team.id}}/schedule/view_game/{{event.id}}">
<span class="u-hidden u-xs-inline">TeamSnap</span> <span>{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}</span>
</a> <span class="u-hidden">TeamSnap</span>
</a>
</div>
</div> </div>
</div> </div>

View 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>

View File

View File

@@ -1,38 +1,70 @@
{{>emailmodal}} <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}}">
<div 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}}">
<form onsubmit="onSubmit(this,event)" action="#">
<input type="hidden" name="event_lineup_id" value="{{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="_csrf" value="{{csrfToken}}"> --}}
<input type="hidden" name="csrfToken" value="{{csrfToken}}"> <input type="hidden" name="csrfToken" value="{{csrfToken}}">
<div class="Panel Panel--full"> <div class="Panel Panel--full">
<div class="Panel-header u-padEndsSm"> <div class="Panel-header u-padEndsSm">
<h3 style="flex: 1 1 0%;">{{event.formattedTitle}}</h3> <h3 style="flex: 1 1 0%;">{{event.formattedTitle}}</h3>
<div class="ButtonGroup"> <div class="Popup">
<button class="Button Button--orange" type="submit" formmethod="post"> <div class="ButtonGroup">
<div> <button class="Button Button--orange" type="submit" formmethod="post">
<span id="teamsnap-icon">{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}</span> <div>
<span id="waiting-icon" class="u-hidden">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/loader.svg" "Icon--loader"}}}</span> <span id="teamsnap-icon" class="hideOnLoading">{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}</span>
<span id="success-icon" class="u-hidden">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/check.svg"}}}</span> <span id="waiting-icon" class="u-hidden showOnLoading">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/loader.svg" "Icon--loader"}}}</span>
<span id="failure-icon" class="u-hidden">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg"}}}</span> <span id="success-icon" class="u-hidden showOnSuccess">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/check.svg"}}}</span>
Save <span id="failure-icon" class="u-hidden showOnFailure">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg"}}}</span>
</div> Save
</button> </div>
<div class="Button Button--orange .u-padSidesXs Popup" onclick="togglePopup(this)"> </button>
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/caret-down.svg"}}} <div class="Button Button--orange .u-padSidesXs Popup-toggle" data-control="popup" data-open="event-lineup-more-actions-{{event_lineup.id}}">
<div class="Popup-container Popup-container--down Popup-container--right" style="width: 200px"> {{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/caret-down.svg"}}}
<div class="Popup-content u-textDecorationNone"> <div class="Popup-container Popup-container--down Popup-container--right" style="width: 200px" data-popup="event-lineup-more-actions-{{event_lineup.id}}">
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="emailModal(this, '{{event_lineup.id}}/email')"> <div class="Popup-content u-textDecorationNone">
{{{embeddedSvgFromPath "/bootstrap-icons/envelope.svg"}}} <a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="emailModal(this, '{{event_lineup.id}}/email')">
<span>Generate Email</span> {{{embeddedSvgFromPath "/bootstrap-icons/envelope.svg"}}}
</a> <span>Generate Email</span>
<hr class="Divider u-spaceEndsNone"> </a>
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="../sheet"> <hr class="Divider u-spaceEndsNone">
{{{embeddedSvgFromPath "/bootstrap-icons/book.svg"}}} <a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="/{{team.id}}/event/{{event.id}}/sheet">
<span>Lineup Card</span> <span>{{{embeddedSvgFromPath "/bootstrap-icons/file-earmark.svg"}}}</span>
</a> <span class="u-hidden u-xs-inline">Game Sheet</span>
</a>
<hr class="Divider u-spaceEndsNone">
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="insertLineup(1, {{team.id}}, {{event.id}}, this)">
{{{embeddedSvgFromPath "/bootstrap-icons/caret-right.svg"}}}
<span>Insert next lineup</span>
</a>
<hr class="Divider u-spaceEndsNone">
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="insertLineup(-1, {{team.id}}, {{event.id}}, this)">
{{{embeddedSvgFromPath "/bootstrap-icons/caret-left.svg"}}}
<span>Insert previous lineup</span>
</a>
<hr class="Divider u-spaceEndsNone">
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="openAvailabilityReminderModal(this, {{team.id}}, {{event.id}})">
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/send.svg"}}}
<span>Availability Reminders</span>
</a>
<hr class="Divider u-spaceEndsNone">
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="confirmModal(this, {title:'Reset Availabilities',body:'Are sure you want to reset availabilities?'}, submitResetAvailabilities, {team_id:{{team.id}}, event_id:{{event.id}} })";>
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/refresh.svg"}}}
<span>Reset All Availabilities</span>
</a>
<hr class="Divider u-spaceEndsNone">
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="confirmModal(this, {title:'Clear Lineup',body:'Are sure you want to clear lineup?'}, submitClearLineup, {team_id:{{team.id}}, event_id:{{event.id}}, event_lineup_id:{{event_lineup.id}} })">
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/trash.svg"}}}
<span>Clear Lineup</span>
</a>
<div class="u-hidden">
<hr class="Divider u-spaceEndsNone">
<span class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="console.log('not implemented yet')">
<span>Publish</span>
</span>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class=" Panel-body u-padEndsSm"> <div class=" Panel-body u-padEndsSm">
@@ -40,12 +72,12 @@
<div class="date">{{dateFormat event.startDate "ddd, MMM D h:mm A" }}</div> <div class="date">{{dateFormat event.startDate "ddd, MMM D h:mm A" }}</div>
<div class="location">{{event.locationName}}</div> <div class="location">{{event.locationName}}</div>
</div> </div>
<div class=" availability-bar fullwidth"> <div class="availability-bar fullwidth">
{{> availability_bar availabilitySummary=availabilitySummary}}
</div> </div>
</div> </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-body">
<div class="Panel-row Panel-title u-padXs"> <div class="Panel-row Panel-title u-padXs">
<i>{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-check.svg"}}}</i> <i>{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-check.svg"}}}</i>
@@ -53,77 +85,48 @@
</div> </div>
<div class=" Panel-row Grid Grid--fit u-textBold u-textCenter u-padXs"> <div class=" Panel-row Grid Grid--fit u-textBold u-textCenter u-padXs">
{{#each (positions)}} {{#each (positions)}}
<div class="Grid-cell position-status">{{this}}</div> <div class="Grid-cell position-status" data-value="{{this}}">{{this}}</div>
{{/each}} {{/each}}
</div> </div>
<div class="slot-set"> <div class="slot-set">
{{#each members}}
{{#if (isInStartingLineup this)}}
{{> slot member=this}}
{{/if}}
{{/each}}
</div> </div>
</div> </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"> <div class="Panel-row Panel-title u-padXs">
{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-check.svg"}}} {{{embeddedSvgFromPath "/bootstrap-icons/clipboard-check.svg"}}}
<span>Position Only</span> <span>Position Only</span>
</div> </div>
<div class="slot-set"> <div class="slot-set">
{{#each members}}
{{#if (isInPositionOnly this)}}
{{> slot member=this}}
{{/if}}
{{/each}}
</div> </div>
</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"> <div class="Panel-row Panel-title u-padXs">
{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-minus.svg"}}} {{{embeddedSvgFromPath "/bootstrap-icons/clipboard-minus.svg"}}}
<span>Bench</span> <span>Bench</span>
</div> </div>
<div class="slot-set"> <div class="slot-set">
{{#each members}} {{#loadSlots}}
{{#if (isInBench this)}} {{>slot member=member event_lineup=event_event_lineup availablity=availability}}
{{> slot member=this event=../event}} {{/loadSlots}}
{{/if}}
{{/each}}
</div> </div>
</div> </div>
<div id="lineup-out-{{event.id}}" class="Panel u-maxWidthSm out Panel--full"> <div class="Panel u-maxWidthSm lineup-segment out Panel--full">
<div class="Panel-row Panel-title u-padXs"> <div class="Panel-row Panel-title u-padXs u-flex">
<span style="flex: 1 1 0%;">{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-x.svg"}}}Out</span> <div><span style="flex: 1 1 0%;">{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-x.svg"}}}Out</span></div>
<div class="u-flexGrow1"></div>
<div class="Toggle"> <div class="Toggle">
<input class="Toggle-input" type="checkbox" id="enable-slots" onclick="toggleChildSlots(this);"> <input class="Toggle-input" type="checkbox" id="enable-slots">
<label class="Toggle-label" for="availability-tab"></label> <label class="Toggle-label" for="enable-slots"></label>
</div> </div>
</div> </div>
<div class="slot-set"> <div class="slot-set">
{{#each members}}
{{#if (isInOut this)}}
{{> slot member=this}}
{{/if}}
{{/each}}
</div> </div>
</div> </div>
</form> </form>
</div> </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>

View File

@@ -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>

View 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>

View File

@@ -1,4 +1,4 @@
<table> <table class="lineup-table">
<thead> <thead>
<tr> <tr>
<th class="title-cell" colSpan=3> <th class="title-cell" colSpan=3>
@@ -11,10 +11,10 @@
{{#if (isInStartingLineup this)}} {{#if (isInStartingLineup this)}}
<tr> <tr>
<td class="sequence-cell"> <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>
<td class="name-cell">{{this.lastName}}, {{this.firstName}} #{{this.jerseyNumber}}</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> </tr>
{{/if}} {{/if}}
{{/each}} {{/each}}
@@ -26,7 +26,7 @@
<tr> <tr>
<td class="sequence-cell"></td> <td class="sequence-cell"></td>
<td class="name-cell">{{this.lastName}}, {{this.firstName}} #{{this.jerseyNumber}}</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> </tr>
{{/if}} {{/if}}
{{/each}} {{/each}}

View File

@@ -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>

View File

@@ -1,10 +1,10 @@
<div class="Panel-expandableRow lineup-slot"> <div class="Panel-expandableRow lineup-slot" data-initial-lineup-segment="{{initial_lineup_segment}}">
<input type="hidden" name="label" value="{{member.benchcoach.eventLineupEntry.label}}"> <input type="hidden" name="label" value="{{eventLineupEntry.label}}">
<input type="hidden" name="flags" value="{{flagsString member.benchcoach.eventLineupEntry.flags}}"> <input type="hidden" name="flags" value="{{flagsString eventLineupEntry.flags}}">
<input type="hidden" name="sequence" value="{{member.benchcoach.eventLineupEntry.sequence}}"> <input type="hidden" name="sequence" value="{{eventLineupEntry.sequence}}">
<input type="hidden" name="eventId" value="{{event.id}}"> <input type="hidden" name="eventId" value="{{event.id}}">
<input type="hidden" name="eventLineupEntryId" value="{{member.benchcoach.eventLineupEntry.id}}"> <input type="hidden" name="eventLineupEntryId" value="{{eventLineupEntry.id}}">
<input type="hidden" name="availabilityStatusCode", value="{{member.benchcoach.availability?.statusCode}}"> <input type="hidden" name="availabilityStatusCode", value="{{#if availability}}{{availability.statusCode}}{{/if}}">
<input type="hidden" name="memberId" value="{{member.id}}"> <input type="hidden" name="memberId" value="{{member.id}}">
<input type="hidden" name="lastName" value="{{member.lastName}}"> <input type="hidden" name="lastName" value="{{member.lastName}}">
<input type="hidden" name="firstName" value="{{member.firstName}}"> <input type="hidden" name="firstName" value="{{member.firstName}}">
@@ -15,14 +15,42 @@
class="Panel-cell Panel-cell--header"> class="Panel-cell Panel-cell--header">
<div class="sequence u-textNoWrap u-fontSizeLg"></div> <div class="sequence u-textNoWrap u-fontSizeLg"></div>
</div> </div>
<div class="Panel-cell u-padXs u-sizeFill"> <div class="Panel-cell u-padXs u-sizeFill u-flex">
<div <div
class="d-flex availability-status-code-{{ class="Popup availability-status-code-{{
member.benchcoach.availability?.statusCode availability?.statusCode
}}" }}"
> >
<div class="u-flexInline u-fontSizeLg u-textNoWrap"> {{#if availability}}
{{#if member.benchcoach.availability}}{{{avail_status_code_icon member.benchcoach.availability.statusCode}}}{{/if}} {{#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"> <span class="lastname">
{{member.lastName}} {{member.lastName}}
</span> </span>
@@ -32,6 +60,29 @@
<span class="jerseynumber u-hidden u-sm-inline u-fontSizeSm"> <span class="jerseynumber u-hidden u-sm-inline u-fontSizeSm">
#{{member.jerseyNumber}} #{{member.jerseyNumber}}
</span> </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>
</div> </div>
@@ -42,7 +93,7 @@
-- --
</option> </option>
{{#each (positions)}} {{#each (positions)}}
<option value="{{this}}" {{#if (comparePositionWithFlags this ../member.benchcoach.eventLineupEntry)}}selected{{/if}}> <option value="{{this}}" {{#if (comparePositionWithFlags this ../eventLineupEntry)}}selected{{/if}}>
{{this}} {{this}}
</option> </option>
{{/each}} {{/each}}

View File

@@ -1,8 +1,8 @@
<div class="field-container"> <div class="field-container">
<img src="/media/baseball-diamond.svg" /> {{{embeddedSvgFromPath "/media/baseball-diamond.svg" 'baseball-diamond'}}}
{{#defenseLineup event_lineup_entries members}} {{#defenseLineup event_lineup_entries members}}
<div class="slot-set pos-{{this.position}}"> <div class="slot-set pos-{{this.position}}">
<table> <table class="striped">
<tbody> <tbody>
<tr class="slot"> <tr class="slot">
<th class="position"></th> <th class="position"></th>

View File

@@ -1,14 +1,16 @@
<table> <table>
<colgroup><col span="3" class="player"></colgroup> {{!-- <colgroup><col span="4" class="player"></colgroup> --}}
{{!-- <colgroup><col span="0" class="player-stats"></colgroup> --}} {{!-- <colgroup><col span="1" class="spacer"></colgroup> --}}
<colgroup><col span="4" class="position-capability"></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 future"></colgroup>
<colgroup><col span="4" class="availability-on-day past"></colgroup> <colgroup><col span="4" class="availability-on-day past"></colgroup> --}}
<thead> <thead>
<tr> <tr>
<th colspan="3" id="today-availability"> <th colspan="4" id="today-availability">
Available ({{availabilitySummary.playerGoingCount}}|{{availabilitySummary.playerMaybeCount}}) Available ({{availabilitySummary.playerGoingCount}}|{{availabilitySummary.playerMaybeCount}})
</th> </th>
<th class="spacer first-of-group last-of-group"></th>
<th class="player-stats"> <th class="player-stats">
<span class="decimal-point">.</span>AVG <span class="decimal-point">.</span>AVG
<span class="delimiter">/</span> <span class="delimiter">/</span>
@@ -17,34 +19,38 @@
<span class="decimal-point">.</span>SLG <span class="decimal-point">.</span>SLG
<span class="delimiter">:</span>PA <span class="delimiter">:</span>PA
</th> </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 catcher">C</th>
<th class="position-capability infield">I</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%> --}} {{!-- <% 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}} {{#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}}
{{#loopEvents recent_events}} {{#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}} {{/loopEvents}}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{!-- <% by_member.select{|m,d| !m.is_non_player}.each_with_index do |(member, d), i|%> --}} {{!-- <% by_member.select{|m,d| !m.is_non_player}.each_with_index do |(member, d), i|%> --}}
{{#rosterHistory event event_lineup_entries members availabilities}} {{#rosterHistory event event_lineup_entries members availabilities}}
<tr id="roster-history-slot-<%= ::Temple::Utils.escape_html((i)) %>"> <tr class="roster-history-slot{{#if (isStarting this)}} starting-today{{/if}}">
<td class="is-present-checkbox available-status-code-{{this.benchcoach.availability.statusCode}}"> <td class="is-present-checkbox available-status-code-{{this.benchcoach.availability.statusCode}} first-of-group">
<span>■</span> <span>■</span>
</td> </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}} {{this.jerseyNumber}}
</td> </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}} {{this.lastName}}
</td> </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="decimal-point">.</span>
<span class="avg">000</span> <span class="avg">000</span>
<span class="delimiter">/</span> <span class="delimiter">/</span>
@@ -56,20 +62,20 @@
<span class="delimiter">:</span> <span class="delimiter">:</span>
<span class="pa">00</span> <span class="pa">00</span>
</td> </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 catcher">{{positionCapabilityFor this "C"}}</td>
<td class="position-capability infield">{{positionCapabilityFor this "IF"}}</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}} {{#loopEvents ../upcoming_events}}
{{#timepointForMember ../this ../../timeline this}} {{#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}} {{this.value}}
</td> </td>
{{/timepointForMember}} {{/timepointForMember}}
{{/loopEvents}} {{/loopEvents}}
{{#loopEvents ../recent_events}} {{#loopEvents ../recent_events}}
{{#timepointForMember ../this ../../timeline this}} {{#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}} {{this.value}}
</td> </td>
{{/timepointForMember}} {{/timepointForMember}}

View File

@@ -1,11 +1,11 @@
<link rel="stylesheet" href="/css/eventsheet.css"> <link rel="stylesheet" href="/css/eventsheet.css">
<body class="B5"> <body class="{{#if sheet_size}}{{sheet_size}}{{else}}B5{{/if}}">
<div class="sheet eventsheet" id="page-1"> <div class="sheet eventsheet {{#if sheet_layout}}{{sheet_layout}}{{else}}quarters{{/if}}" id="page-1">
<section id="defense-card"> <section class="NE" id="defense-card">
<header> <header>
<div class="event-title float-left"> <div class="event-title float-left">
{{event.formattedTitle}} {{dateFormat event.startDate "ddd, MMM D h:mm A" }} {{event.formattedTitle}}
</div> </div>
<div class="homeaway float-right"> <div class="homeaway float-right">
{{event.gameType}} {{event.gameType}}
@@ -31,18 +31,18 @@
</div> </div>
</div> </div>
</section> </section>
<section id="roster-and-history"> <section class="SW" id="roster-and-history">
<div class="roster-and-history"> <div class="container">
{{> roster_and_history {{> roster_and_history
event=event event=event
event_lineup_entries=event_lineup_entries event_lineup_entries=event_lineup_entries
members=members availabilities=availabilities members=members availabilities=availabilities
recent_events=recent_events recent_events=recent_events
upcoming_events=upcoming_events upcoming_events=upcoming_events
}} }}
</div> </div>
</section> </section>
<section class="lineup-card" id="lineup-card-dugout"> <section class="NW lineup-card dugout" id="lineup-card-dugout">
<header> <header>
<div class="float-left event-title">{{event.formattedTitle}}</div> <div class="float-left event-title">{{event.formattedTitle}}</div>
<div class="float-right homeaway">{{event.gameType}}</div> <div class="float-right homeaway">{{event.gameType}}</div>
@@ -80,9 +80,9 @@
</table> </table>
</div> </div>
</section> </section>
<section class="lineup-card" id="lineup-card-exchange"> <section class="SE lineup-card exchange" id="lineup-card-exchange">
<header> <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> <div class="float-right homeaway">{{event.gameType}}</div>
</header> </header>
<div class="starting-lineup-table"> <div class="starting-lineup-table">
@@ -119,24 +119,18 @@
</div> </div>
</section> </section>
</div> </div>
<div class="sheet eventsheet" id="page-2"> <div class="sheet eventsheet {{#if sheet_layout}}{{sheet_layout}}{{else}}quarters{{/if}}" id="page-2">
<section id="defense-card" style="border:solid black;"> <section class="SE" id="front-cover">
<div id="defense-pane">
{{> defense_pane}}
</div>
</section>
<section id="front-cover">
<header> <header>
<div class="homeaway"> <div class="game-number">
<span>{{firstLetter event.gameType}}</span> {{event.label}}
</div> </div>
<div class="title"> <div class="title">
<span class="date-time">{{dateFormat event.startDate "ddd, MMM D h:mm A" }}</span> <span class="date-time">{{dateFormat event.startDate "ddd, MMM D h:mm A" }}</span>
<span class="location">{{event.locationName}}</span> <span class="location">{{event.locationName}}</span>
</div> </div>
<div class="game-number"> <div class="homeaway">
<span class="label">Game #</span> <span>{{firstLetter event.gameType}}</span>
<span class="value">XX</span>
</div> </div>
</header> </header>
<div style="display:block;max-height: 1em;background-color: lightgray;border-bottom: solid 2px black;"> <div style="display:block;max-height: 1em;background-color: lightgray;border-bottom: solid 2px black;">
@@ -149,17 +143,16 @@
</div> </div>
</div> </div>
{{# if event.opponentName}} {{# if event.opponentName}}
<div> <div class="conjuction">
<div style="text-align: center;font-family: &#39;Pacifico&#39;;">
<span>vs</span> <span>vs</span>
</div>
</div> </div>
<div class="opponent"> <div class="opponent">
<div> <div>
<span class="name">{{event.opponentName}}</span> <span class="name">{{event.opponentName}}</span>
</div> </div>
{{#if opponent_logo.mediumUrl }}
<img src="{{opponent_logo.mediumUrl}}"> <img src="{{opponent_logo.mediumUrl}}">
{{!-- <%= ::Temple::Utils.escape_html((image_tag opponent_logos[event.opponent.id].medium_url)) %> --}} {{/if}}
</div> </div>
<div class=""> <div class="">
<table> <table>
@@ -174,7 +167,7 @@
<th>{{event.opponentName}}</th> <th>{{event.opponentName}}</th>
</tr> </tr>
<tr> <tr>
<td style="height:5em;"></td> <td style="height:5em;width: 50%;"></td>
<td></td> <td></td>
</tr> </tr>
</tbody> </tbody>
@@ -183,7 +176,70 @@
{{/if}} {{/if}}
</div> </div>
</section> </section>
<section class="lineup-card" id="lineup-card-dugout-blank"> <section class="NW blank" id="defense-card">
<header>
<div class="event-title float-left">
&nbsp;
</div>
<div class="homeaway float-right">
&nbsp;
</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"> <div class="starting-lineup-table">
<table> <table>
<thead> <thead>
@@ -217,38 +273,5 @@
</table> </table>
</div> </div>
</section> </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> </div>
</body> </body>

View 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">
&nbsp;
</div>
<div class="homeaway float-right">
&nbsp;
</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">
&nbsp;
</div>
<div class="homeaway float-right">
&nbsp;
</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>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="manifest" href="/manifest.json">
<link rel="stylesheet" href="/css/application.css"> <link rel="stylesheet" href="/css/application.css">
{{#if style}}<link rel="stylesheet" href="/css/{{style}}">{{/if}} {{#if style}}<link rel="stylesheet" href="/css/{{style}}">{{/if}}
<title>{{#if title}}{{title}}{{else}}BenchCoach{{/if}}</title> <title>{{#if title}}{{title}}{{else}}BenchCoach{{/if}}</title>
@@ -19,15 +20,14 @@
</script> </script>
</head> </head>
<body class="bg-light"> <body>
<header class="Header"> <header class="u-spaceBottomMd">
{{> navbar }} {{> navbar }}
{{{_sections.header}}} {{{_sections.header}}}
</header> </header>
<div class="u-padSidesMd u-xs-padSidesLg"> <main class="u-xs-spaceSidesMd">
<div class="u-max1200 u-flexExpandSides u-xs-size5of6 u-sm-size2of3 u-md-sizeFull u-padBottomMd u-xs-padEndsLg u-sm-padEndsXl"> {{{ body }}}
{{{ body }}} </main>
</div>
</div>
</body> </body>
{{{script_tags scripts}}}
</html> </html>

View File

@@ -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="Panel u-maxWidthXs u-padLg u-spaceSidesAuto">
<div class="Grid-cell u-size5of12"> <h1 class="u-textCenter">Sign in</h1>
<div class="Panel u-padLg u-spaceSidesAuto"> <p class="u-spaceEndsMd">Sign into BenchCoach using your TeamSnap account</p>
<h1 class="u-spaceSidesAuto u-spaceBottomLg">Sign in</h1> <a class="Button Button--large Button--orange u-spaceSidesAuto btn--Full" href="/login/federated/teamsnap">
<div> {{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}
<a class="Button Button--large Button--orange u-spaceSidesAuto" href="/login/federated/teamsnap"> <span>TeamSnap</span>
{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}} </a>
<span>TeamSnap</span> </div>
</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>

View 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>

View File

@@ -1,5 +1,6 @@
<h1> <h1>
{{title}} {{title}}
<hr class="Divider" />
</h1> </h1>
<div class="Panel"> <div class="Panel">
<div class="Panel-body"> <div class="Panel-body">

View File

@@ -1,40 +1,39 @@
<div class="Header-container Grid u-flexAlignItemsCenter"> <div class="Header-banner">
<div class="Grid-cell u-sizeFill"> <a href="/" class="">
<div class="Header-banner Grid u-flexAlignItemsCenter"> <div class="Header-bannerLogo">
<a href="/" class="Grid-cell u-sizeFit u-flexInline u-flexAlignItemsCenter u-textDecorationNone"> <img class="logo" src="/media/benchcoach.svg" alt="BenchCoach Logo">
<div class="Header-bannerLogo"> </div>
<img class="logo" src="/media/benchcoach.svg" alt="BenchCoach Logo"> <div class="Header-bannerTitle">
</div> BenchCoach
</a> </div>
<div class="Grid-cell u-flexInline u-flexJustifyEnd u-sizeFill u-padSidesSm"> </a>
{{#if user}} {{#if user}}
<div class="Popup"> <div class="filler"></div>
<div class="Button Button--small Popup-toggle" onclick="this.closest('.Popup').querySelector('.Popup-container').classList.toggle('is-open')"> <div class="u-padSidesSm u-spaceAuto">
Account <div class="Popup">
</div> <div class="Button Button--small Popup-toggle" onclick="this.closest('.Popup').querySelector('.Popup-container').classList.toggle('is-open')">
<div class="Popup-container Popup-container--down Popup-container--right u-sizeFit"> Account
<div class="Popup-content u-padXs u-sizeFit u-fontSizeSm"> </div>
<h6 class="h6 title u-textNoWrap u-fontSizeSm u-textSemiBold">{{user.first_name}} {{user.last_name}}</h6> <div class="Popup-container Popup-container--down Popup-container--right u-sizeFit">
<div class="u-textNoWrap u-fontSizeSm">{{user.email}}</div> <div class="Popup-content u-padXs u-sizeFit u-fontSizeSm">
<hr class="Divider u-spaceEndsNone"> <h6 class="h6 title u-textNoWrap u-fontSizeSm u-textSemiBold">{{user.first_name}} {{user.last_name}}</h6>
<div class="u-padBottomSm u-padTopSm"> <div class="u-textNoWrap u-fontSizeSm">{{user.email}}</div>
<a href="/user/{{user.id}}/teams" class="u-spaceBottomSm Button Button--small"> <hr class="Divider u-spaceEndsNone">
<span>{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/team.svg"}}}</span> <div class="u-padBottomSm u-padTopSm">
Teams <a href="/user/{{user.id}}/teams" class="u-spaceBottomSm Button Button--small">
</a> <span>{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/team.svg"}}}</span>
<form method="post" action="/logout"> Teams
<button type="submit" name="logout" class="u-spaceBottomSm Button Button--small"> </a>
Logout <form method="post" action="/logout">
</button> <button type="submit" name="logout" class="u-spaceBottomSm Button Button--small">
</form> Logout
</div> </button>
</div> </form>
</div> </div>
</div> </div>
{{/if}}
</div> </div>
</div> </div>
</div> </div>
</div> {{/if}}
</div>

View File

@@ -17,7 +17,7 @@
<img src ="/teamsnap-ui/assets/icons/schedule.svg" class="Icon"> <img src ="/teamsnap-ui/assets/icons/schedule.svg" class="Icon">
<span class="span u-hidden u-xs-inline">Schedule</span> <span class="span u-hidden u-xs-inline">Schedule</span>
</a> </a>
<a class="Button" href="members"> <a class="Button u-hidden" href="members">
<img src ="/teamsnap-ui/assets/icons/roster.svg" class="Icon"> <img src ="/teamsnap-ui/assets/icons/roster.svg" class="Icon">
<span class="span u-hidden u-xs-inline">Roster</span> <span class="span u-hidden u-xs-inline">Roster</span>
</a> </a>
@@ -29,24 +29,23 @@
</div> </div>
</div> </div>
<div class=" Panel Panel--full">
<div class=" Panel-header"> <h2 class=" ">Upcoming Events</h2>
<h2 class=" Panel-title">Upcoming Events</h2> <hr class="Divider" />
</div> <div class="Grid Grid--withGutter">
<div class=" Panel-body">
{{#each upcoming_events}} {{#each upcoming_events}}
<div class="Grid-cell u-xs-size1of2 u-sm-size1of3">
{{>event_panel event=this}} {{>event_panel event=this}}
{{/each}}
</div> </div>
{{/each}}
</div> </div>
<div class=" Panel Panel--full"> <h2 class="">Recent Events</h2>
<div class=" Panel-header"> <hr class="Divider" />
<h2 class=" Panel-title">Recent Events</h2> <div class="Grid Grid--withGutter">
</div>
<div class=" Panel-body">
{{#each recent_events}} {{#each recent_events}}
<div class="Grid-cell u-xs-size1of2 u-sm-size1of3">
{{>event_panel event=this}} {{>event_panel event=this}}
{{/each}}
</div> </div>
{{/each}}
</div> </div>