Compare commits

...

84 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
45 changed files with 2681 additions and 1678 deletions

20
.vscode/launch.json vendored
View File

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

16
.vscode/tasks.json vendored
View File

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

21
bin/www
View File

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

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} {
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:
app:
app: &app
env_file:
- .env
build: .
networks:
- web
profiles:
- production
expose:
- 3000
app-dev:
<<: *app
ports:
- 9229:9229 #debugger
- 35729:35729 #livereload
profiles:
- development
command: npm run dev
volumes:
- ./src:/home/node/app/src
- ./package.json:/home/node/app/package.json
- ./package-lock.json:/home/node/app/package-lock.json
- ./certs:/home/node/app/certs
- ./bin/www:/home/node/app/bin/www
environment:
DEBUG: "app"
NODE_ENV: "development"
caddy:
image: caddy
@@ -17,10 +35,16 @@ services:
- 443:443
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
networks:
- web
env_file:
- .env
- .env
networks:
web:
web:
volumes:
caddy_data:
caddy_config:

71
package-lock.json generated
View File

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

View File

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

View File

@@ -43,6 +43,16 @@ hbs.registerHelper('section', (name, options) => {
this._sections[name] = options.fn(this);
return null;
})
hbs.registerHelper('script_tags', (scripts, options) => {
if(!scripts) {
return null;
}
var result = [];
scripts.forEach((script)=>{
result.push(`<script src="${script}"></script>`)
})
return result.join('\n');
})
hbs.registerHelper("embeddedSvgFromPath", require('./lib/utils').embeddedSvgFromPath)
hbs.registerHelper(require("./controllers/event").helpers)
hbs.registerHelper(require("./controllers/eventlineup").helpers)
@@ -52,12 +62,14 @@ app.set("view engine", "hbs");
app.locals.pluralize = require("pluralize");
if (process.env.NODE_ENV === "development") {
console.log('adding connectLiveReload')
var connectLiveReload = require("connect-livereload");
app.use(connectLiveReload());
app.use("/scss", express.static(path.join(__dirname, "scss")));
app.use(connectLiveReload({port: 35729, src:"http://localhost:35729/livereload.js?snipver=1"}));
}
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(bodyParser.urlencoded({extended: true }));
app.use(logger("dev"));
app.use(cors(corsOptions))
app.use(cookieParser());
@@ -108,8 +120,10 @@ app.use(
intervalMs: 900000 //ms = 15min
}
}),
cookie: { maxAge: 86400000 }, // value of maxAge is defined in milliseconds.
teamsnap_token: "",
current_team: "",
csrfToken:"",
secret: process.env['SECRET'],
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
@@ -142,7 +156,7 @@ app.use(require("./routes/eventsheet").router)
app.use(function (err, req, res, next) {
// set locals, only providing error in development
if (err) {
res.locals.message = err.message;
res.locals.message = req.app.get("env") === "development" ? err.message : "An error has occurred";
res.locals.error = req.app.get("env") === "development" ? err : {};
if (typeof err === 'string' || err instanceof String) {
err = {
@@ -159,9 +173,10 @@ app.use(function (err, req, res, next) {
});
// catch 404 and forward to error handler
// app.use(function (req, res, next) {
// next(createError(404));
// });
app.use(function (req, res, next) {
// next(createError(404));
res.status(404).send('not found')
});
app.set('trust proxy')

View File

@@ -21,6 +21,10 @@ exports.helpers = {
exports.partials = path.join(__dirname, "../views/event/partials")
exports.confirmModalAvailabilityReminders = async (req, res) => {
res.status(200).render("event/partials/modal_availability_reminders")
}
exports.getEvents = async (req, res, next) => {
const {user, team, layout} = req
const bulkLoadTypes = ["event", "availabilitySummary"]
@@ -67,3 +71,53 @@ exports.getEvent = async (req, res, next) => {
};
res.render("event/show", context);
};
exports.sendAvailabilityReminders = async (req,res,next) => {
await Promise.all(req.promises)
if (!req.body || ! (req.body.event_id && req.body.memberIds)) {
res.status(400).send('Malformed post')
}
if (req.params.event_id != req.body.eventId) {
// Load actual event. Do I want this to be an error? probably
res.status(400).send('Event ID parameter does not match the POST body')
return;
}
const {event} = req
const {eventId, memberIds} = req.body
const sendingMember = req.members.find(m=>m.userId==req.user.id)
try {
const promise = teamsnap.sendAvailabilityReminders(event, sendingMember, memberIds)
await promise
.then (res.status(200).send('OK'))
} catch (err) {
res.status(500).send()
}
return
}
exports.submitResetAvailabilities = async (req,res,next) => {
await Promise.all(req.promises)
if (!req.body || ! (req.body.event_id && req.body.memberIds)) {
res.status(400).send('Malformed post')
}
if (req.params.event_id != req.body.event_id) {
// Load actual event. Do I want this to be an error? probably
res.status(400).send('Event ID parameter does not match the POST body');
return
}
const {event_id, memberIds} = req.body
const reset_promises = []
const availabilities = await teamsnap.loadAvailabilities({eventId: event_id}, teamsnapCallback);
availabilities.filter(availability =>memberIds.includes(availability.memberId.toString())).forEach( availability => {
availability.statusCode = teamsnap.AVAILABILITIES.NONE
const promise = teamsnap.saveAvailability(availability, teamsnapCallback)
reset_promises.push(promise)
})
await Promise.all(reset_promises)
.then(res.status(200).send('OK'))
}

View File

@@ -1,107 +1,51 @@
const path = require('path')
const fs = require('fs')
const {embeddedSvgFromPath, parsePositionLabel, compilePositionLabel} = require("../lib/utils")
const {groupTeamsnapItems, parsePositionLabel, compilePositionLabel, teamsnapCallback} = require("../lib/utils")
const tsUtils = require('../lib/utils')
const { loadEventLineupEntries } = require('teamsnap.js')
exports.partials = path.join(__dirname, "../views/eventlineup/partials")
const statusCodeIcons = {
1: embeddedSvgFromPath("/teamsnap-ui/assets/icons/check.svg"),
0: embeddedSvgFromPath("/teamsnap-ui/assets/icons/dismiss.svg"),
2: embeddedSvgFromPath("/bootstrap-icons/question-lg.svg"),
null: embeddedSvgFromPath("/bootstrap-icons/question.svg"),
undefined: embeddedSvgFromPath("/bootstrap-icons/question-lg.svg")
}
exports.helpers = {
flagsString: (flags) => {
return flags != null ? Array.from(flags).join(",") : ''
},
plus1: (i) => Number(i)+1,
positions: () => ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "EH", "DH", "DR"],
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) => {
const {positionLabelWithoutFlags} = parsePositionLabel(label);
return positionLabelWithoutFlags
},
positionLabelWithoutPOFlag: (label) => {
const {positionLabelWithoutFlags, positionFlags} = parsePositionLabel(label);
positionFlags.delete('PO')
return compilePositionLabel(positionLabelWithoutFlags, positionFlags)
},
positionFlags: (label)=> {
const {positionFlags} = parsePositionLabel(label);
return `[${Array.from(positionFlags).join(",")}]`
},
hasPositionFlags: (label) => {
const {positionLabelWithoutFlags, positionFlags} = parsePositionLabel(label);
return positionFlags.size > 0;
},
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.has("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.has("PO"))
},
isInBench: (member) => {
if ((member.benchcoach.eventLineupEntry != null && member.benchcoach.eventLineupEntry.label != '') || member.isNonPlayer) return false;
return (member.benchcoach.availability?.statusCode != 0 && member.benchcoach.availability?.statusCode != null)
},
isInOut: (member) => {
if ((member.benchcoach.eventLineupEntry != null && member.benchcoach.eventLineupEntry.label != '') || member.isNonPlayer) return false;
return (member.benchcoach.availability?.statusCode == 0 || member.benchcoach.availability?.statusCode == null)
},
availabilityStatusShort: (availability) => {
const {YES, MAYBE, NO, NONE} = teamsnap.AVAILABILITIES
const statusShortLookup = {}
statusShortLookup[YES] = "YES"
statusShortLookup[MAYBE] = "MAY"
statusShortLookup[NO] = "NO"
statusShortLookup[NONE] = "UNK"
statusShortLookup[undefined] = "UNK"
return (statusShortLookup[availability?.statusCode])
}
}
exports.helpers = require('../helpers/eventlineup.js')
exports.getEventLineup = async (req, res)=>{
// res.send(req.event_lineup)
await Promise.all(req.promises)
const {user, team, members, event, layout, event_lineup, event_lineup_entries, availabilities, availabilitySummary, csrfToken} = req
attachBenchcoachPropertiesToMember(members, event_lineup_entries, availabilities)
members.sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName)
res.render("eventlineup/edit", {user, team, members, event, layout, event_lineup, event_lineup_entries, availabilitySummary, csrfToken})
const scripts = [
"https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js",
"/js/eventlineup.js",
"/js/tinymce.min.js"
]
res.render("eventlineup/edit", {user, team, members, event, availabilities, scripts, layout, event_lineup, event_lineup_entries, availabilitySummary, csrfToken})
}
exports.getAdjacentEventLineup = async (req, res) => {
await Promise.all(req.promises)
const index = Number(req.query.index)
const {user, team, members, csrfToken} = req
let event
if (index > 0) {
event = req.upcoming_events[index-1]
}
else if (index < 0){
event = req.recent_events[Math.abs(index)-1]
} else {
throw new Error('Index must be positive or negative number')
}
if (!event) {
res.status(500).send()
return
}
const availabilitySummary = event.availabilitySummary
const event_lineup = req.timeline.event_lineups?.find(i=>i.eventId==event.id)
const event_lineup_entries = req.timeline.event_lineup_entries?.filter(i=>i.eventId==event.id)
const availabilities = req.timeline.availabilities.filter(i=>i.eventId==event.id)
attachBenchcoachPropertiesToMember(members, event_lineup_entries, availabilities)
members.sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName)
console.log()
res.render("eventlineup/edit", {user, team, members, event, layout: null, event_lineup, event_lineup_entries, availabilitySummary, availabilities, csrfToken})
}
attachBenchcoachPropertiesToMember = (members, event_lineup_entries, availabilities) => {
@@ -114,7 +58,7 @@ attachBenchcoachPropertiesToMember = (members, event_lineup_entries, availabilit
// as far as I can tell, member_name should consistently be formulated from first and last name
// perhaps could have some edge cases if first or last names change, but this *should be* exceedingly rare.
const member_name = `${member.firstName} ${member.lastName}`
const event_lineup_entry = event_lineup_entries.find(e=> e.memberId == member.id || e.memberName == member_name)
const event_lineup_entry = event_lineup_entries?.find(e=> e.memberId == member.id || e.memberName == member_name)
const availability = availabilities.find(e=>e.memberId == member.id)
member.benchcoach.availability = availability
if (event_lineup_entry != null) {
@@ -141,7 +85,11 @@ exports.getEventLineupEmail = async (req, res)=>{
const {newEventLineupEntries} = processPostedEventLineupEntries(body, eventLineupEntries, event_lineup)
attachBenchcoachPropertiesToMember(members, newEventLineupEntries, availabilities)
members.sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName)
res.status(200).render("eventlineup/partials/email_table", {user, team, members, event, event_lineup, event_lineup_entries: newEventLineupEntries, availabilities, availabilitySummary})
res.status(200).render("eventlineup/partials/email_modal.hbs", {layout:null, user, team, members, event, event_lineup, event_lineup_entries: newEventLineupEntries, availabilities, availabilitySummary})
}
exports.getAvailabilityRemindersModal = (req, res) => {
res.status(200).render("eventlineup/partials/availability_reminder_modal.hbs")
}
exports.getEventLineupEntries = async (req, res)=>{
@@ -161,14 +109,20 @@ exports.postEventLineup = async (req,res) => {
const eventLineupEntries = req.event_lineup.eventLineupEntries
const {newEventLineupEntries, deleteEventLineupEntries} = processPostedEventLineupEntries(body, eventLineupEntries, req.event_lineup)
newEventLineupEntries.forEach(e=>{
teamsnap.saveEventLineupEntry(e)
teamsnap.saveEventLineupEntry(e, teamsnapCallback)
})
deleteEventLineupEntries.forEach(e=>{
teamsnap.deleteEventLineupEntry(e)
teamsnap.deleteEventLineupEntry(e, teamsnapCallback)
})
eventLineup = await teamsnap.loadEventLineups(req.params.event_id)
res.status(201).end()
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) => {
@@ -182,10 +136,14 @@ const processPostedEventLineupEntries = (body, eventLineupEntries, eventLineup)
const lineupEntryFlags = body.flags[i]
if (lineupEntryId != '' && lineupEntryLabel != '') {
// Update lineup entry
const eventLineupEntry = eventLineupEntries.find((e)=>e.id==Number(lineupEntryId))
eventLineupEntry.sequence = lineupEntrySequence
eventLineupEntry.label = compilePositionLabel(lineupEntryLabel, lineupEntryFlags)
newEventLineupEntries.push(eventLineupEntry)
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
@@ -206,4 +164,33 @@ const processPostedEventLineupEntries = (body, eventLineupEntries, eventLineup)
}
})
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)
const {sheet_size, sheet_layout} = req.query
const {user, team, team_preferences, members, event, event_lineup, event_lineup_entries, availabilities, availabilitySummary, timeline, recent_events, opponent_logo, upcoming_events} = req
res.render('eventsheet/sheet', {user, team, team_preferences, members, event, event_lineup, event_lineup_entries, availabilities, availabilitySummary, timeline, recent_events, opponent_logo,upcoming_events})
res.render('eventsheet/sheet', {sheet_size, sheet_layout, user, team, team_preferences, members, event, event_lineup, event_lineup_entries, availabilities, availabilitySummary, timeline, recent_events, opponent_logo,upcoming_events})
}
exports.getEventSheetBlank = (req,res) => {
res.render('eventsheet/sheet_blank')
}
exports.getLineupCard = (req, res, next) => {
@@ -82,7 +88,7 @@ exports.getLineupCard = (req, res, next) => {
availabilities: items.filter((i) => i.type == "availability").sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName),
all_lineup_entries: items.filter((i) => i.type == "eventLineupEntry"),
event_lineup_entries_offense: items
.filter((i) => i.type == "eventLineupEntry" && i.eventId == event_id && !i.label.includes("[PO]"))
.filter((i) => i.type == "eventLineupEntry" && i.eventId == event_id && !i.label.has("[PO]"))
.sort((a, b) => a.sequence - b.sequence),
event_lineup_entries: items
.filter((i) => i.type == "eventLineupEntry" && i.eventId == event_id)

View File

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

119
src/helpers/eventlineup.js Normal file
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 {attachBenchcoachPropertiesToMember} = require('../controllers/eventlineup')
const Handlebars = require("handlebars");
exports.offenseLineup = (number_of_slots, event_lineup_entries, members, options) => {
var results = ""
@@ -7,7 +8,7 @@ exports.offenseLineup = (number_of_slots, event_lineup_entries, members, options
for (let i = 0; i < number_of_slots; i++){
const event_lineup_entry = event_lineup_entries ? event_lineup_entries[i] : null
if (event_lineup_entry && !parsePositionLabel(event_lineup_entry.label).positionFlags.includes('PO')){
if (event_lineup_entry && !parsePositionLabel(event_lineup_entry.label).positionFlags.has('PO')){
results += options.fn({
sequence: event_lineup_entry.sequence,
member: members.find(member=> event_lineup_entry.memberId == member.id || event_lineup_entry.memberName == `${member.firstName} ${member.lastName}`),
@@ -60,7 +61,7 @@ exports.rosterHistory = (event, event_lineup_entries, members, availabilities, o
// const {event, event_lineup_entries, members, availabilities} = options.data.root
const players = members.filter(m=>!m.isNonPlayer)
attachBenchcoachPropertiesToMember(players, event_lineup_entries ? event_lineup_entries.filter(i=>i.eventId==event.id) : [], availabilities.filter(i=>i.eventId==event.id))
players.sort(teamsnapMembersSortAvailabilityLastName)
players.sort(teamsnapMembersSortLineupAvailabilityLastName)
players.forEach(member=>{
const {firstName, lastName, jerseyNumber, benchcoach, position, id} = member
@@ -87,6 +88,9 @@ const positionGroups = {
}
exports.positionCapabilityFor = (member, position, options) => {
if (!member.position) {
return ""
}
const member_positions = member.position.split(",").map(s=>s.trim())
const member_position_groups = new Set(member.position.split(",").map(s=>positionGroups[s.trim()]))
@@ -112,8 +116,15 @@ exports.repeat = (n, options) => {
exports.loopEvents = (events, options) => {
var results = "";
events.forEach(event => {
results += options.fn(event)
if (options.data) {
data = Handlebars.createFrame(options.data);
}
events.forEach((event,i) => {
if (data) {
data.index = i;
}
results += options.fn(event, {data: data })
}
)
return results;
@@ -121,6 +132,9 @@ exports.loopEvents = (events, options) => {
exports.timepointForMember = (member, timeline, event, options) => {
var results = ""
if (options.data) {
data = Handlebars.createFrame(options.data);
}
const availability = timeline.availabilities.find(a=>a.memberId==member.id && a.eventId==event.id)
const eventLineupEntry = timeline.event_lineup_entries.find(a=>(a.memberId==member.id || a.memberName == `${member.firstName} ${member.lastName}`) && a.eventId==event.id)
var value = ""
@@ -128,7 +142,15 @@ exports.timepointForMember = (member, timeline, event, options) => {
value = parsePositionLabel(eventLineupEntry.label).positionLabelWithoutFlags
}
else {
value = availability.status[0]
value = availability?.status[0]
}
return options.fn({availability: availability, eventLineupEntry: eventLineupEntry, value}, {data: data })
}
exports.ifEquals = (testValue, targetValue, options) => {
if (testValue === targetValue) {
return options.fn();
} else {
return '';
}
return options.fn({availability: availability, eventLineupEntry: eventLineupEntry, value})
}

View File

@@ -50,17 +50,6 @@ teamsnapMembersSortAvailabilityLastName = (a, b) => {
}
exports.teamsnapMembersSortAvailabilityLastName = teamsnapMembersSortAvailabilityLastName
exports.initTeamsnap = (req, res, next) => {
if (!teamsnap.isAuthed()) {
teamsnap.init(process.env["TEAMSNAP_CLIENT_ID"]);
teamsnap.auth(req.user.accessToken);
}
teamsnap.loadCollections((err) => {
teamsnap.enablePersistence();
next(req, res, next);
});
};
exports.teamsnapCallback = (err,result, d) => {
if (Array.isArray(result)){
types = new Set(result.map(i=>i.type))
@@ -95,6 +84,10 @@ const getPluralType = (type) =>{
// is not generated in the lookup. this is a
// kludge around that. (specifically availabilitySummary)
plural = teamsnap.getPluralType(type) || (function() {
if (type === undefined){
return type
}
switch (type.slice(-1)) {
case 'y':
return type.slice(0, -1) + 'ies';
@@ -120,7 +113,7 @@ exports.groupTeamsnapItems = (items, types = [], params = {}) => {
return result;
}
exports.embeddedSvgFromPath = (svg_path, additional_classes = "") => {
exports.embeddedSvgFromPath = (svg_path, additional_classes, options) => {
const iconStaticPaths = {
"/teamsnap-ui/assets":path.join(__dirname, "/../../node_modules/@teamsnap/teamsnap-ui/src/assets"),
"/bootstrap-icons":path.join(__dirname, "/../../node_modules/bootstrap-icons/icons"),
@@ -133,6 +126,8 @@ exports.embeddedSvgFromPath = (svg_path, additional_classes = "") => {
}
}
if (!options) {options=additional_classes; additional_classes=''}
const svg = fs.readFileSync(`${svg_path}`, 'utf8');
svgRegExWithClass = new RegExp(/<svg(.*)class="(.*?)"(.*)>/)
@@ -167,7 +162,19 @@ exports.compilePositionLabel = (label, flags) => {
return label
}
else {
const flags_set = new Set(flags.split(',').map(s=>s.trim()))
const flags_set = toFlagsSet(flags)
return `${label} [${Array.from(flags_set).sort().join(',')}]`
}
}
function toFlagsSet(flags) {
let flags_set
if (typeof(flags) == 'string'){
flags_set = new Set(flags.split(',').map(s=>s.trim()))
} else if (flags.constructor === Array){
flags_set = new Set(flags)
} else if (flags.constructor === Set){
flags_set = flags
}
return flags_set
}

View File

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

View File

@@ -1004,21 +1004,21 @@ h6 {
color: #ffffff;
}
.Button--blue {
.Button--blue, button:has(+ .position-label-flags :checked) {
background-color: #1A6BAF;
border-color: #15568c;
color: #ffffff;
}
.Button--blue:hover, .Button--blue:active, .Button--blue:focus {
.Button--blue:hover, button:hover:has(+ .position-label-flags :checked), .Button--blue:active, button:active:has(+ .position-label-flags :checked), .Button--blue:focus, button:focus:has(+ .position-label-flags :checked) {
background-color: #17609e;
border-color: #134d7e;
color: #ffffff;
}
.Button--blue.is-active {
.Button--blue.is-active, button.is-active:has(+ .position-label-flags :checked) {
background-color: #17609e;
color: #ffffff;
}
.Button--blue.is-disabled, .Button--blue.is-disabled:hover, .Button--blue.is-disabled:active, .Button--blue:disabled, .Button--blue:disabled:hover, .Button--blue:disabled:active {
.Button--blue.is-disabled, button.is-disabled:has(+ .position-label-flags :checked), .Button--blue.is-disabled:hover, button.is-disabled:hover:has(+ .position-label-flags :checked), .Button--blue.is-disabled:active, button.is-disabled:active:has(+ .position-label-flags :checked), .Button--blue:disabled, button:disabled:has(+ .position-label-flags :checked), .Button--blue:disabled:hover, button:disabled:hover:has(+ .position-label-flags :checked), .Button--blue:disabled:active, button:disabled:active:has(+ .position-label-flags :checked) {
background-color: #1A6BAF;
border-color: #15568c;
color: #ffffff;
@@ -2312,13 +2312,13 @@ input:-webkit-autofill:focus {
}
.Popup-container::before {
top: calc( 100% - 6px );
top: calc(100% - 6px);
border: solid #d6d6d6 1px;
box-shadow: 0 0 2px rgba(56, 56, 56, 0.15);
}
.Popup-container::after {
top: calc( 100% - 7px );
top: calc(100% - 7px);
}
.Popup-content {
@@ -2757,7 +2757,7 @@ input:-webkit-autofill:focus {
.StepNav-stepTitle {
width: 150px;
left: calc( 0px - 43px);
left: calc(0px - 43px);
padding-top: 4px;
}
@@ -2875,7 +2875,7 @@ input:-webkit-autofill:focus {
}
.StepNav--small .StepNav-stepTitle {
width: 150px;
left: calc( 0px - 53px);
left: calc(0px - 53px);
padding-top: 2px;
}
.StepNav--small .StepNav-stepIcon {
@@ -2910,7 +2910,7 @@ input:-webkit-autofill:focus {
}
.StepNav--xsmall .StepNav-stepTitle {
width: 150px;
left: calc( 0px - 62px);
left: calc(0px - 62px);
padding-top: 1px;
}
.StepNav--xsmall .StepNav-stepIcon {
@@ -7014,9 +7014,11 @@ a.Panel-row {
align-items: center;
}
div[id^=event-lineup] {
div.event-lineup {
max-width: 576px;
counter-reset: lineup-sequence-counter 0;
margin-left: 8px;
margin-right: 9px;
}
.lineup-slot {
@@ -7091,9 +7093,23 @@ li .availability-status-code- {
padding: 0;
}
div[id^=event-lineup] .Panel.position-only .Panel-cell:has(.sequence), div[id^=event-lineup] .Panel.bench .Panel-cell:has(.sequence), div[id^=event-lineup] .Panel.out .Panel-cell:has(.sequence) {
div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out .Panel-cell:has(.SelectBox),
div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out .Panel-cell:has(.drag-handle),
div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out button:has(+ .position-label-flags),
div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out button.addToStarting,
div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out button.addToBench {
display: none;
}
div.event-lineup .lineup-segment.bench .Panel-cell:has(.sequence), div.event-lineup .lineup-segment.position-only .Panel-cell:has(.sequence), div.event-lineup .lineup-segment.out .Panel-cell:has(.sequence) {
display: none;
}
div.event-lineup .lineup-segment.bench.bench button.addToBench, div.event-lineup .lineup-segment.position-only.bench button.addToBench, div.event-lineup .lineup-segment.out.bench button.addToBench {
display: none;
}
div.event-lineup .lineup-segment.starting button.addToStarting, div.event-lineup .lineup-segment.position-only button.addToStarting {
display: none;
}
.Tooltip:after {
padding: 2px !important;
font-size: inherit !important;
@@ -7102,8 +7118,6 @@ div[id^=event-lineup] .Panel.position-only .Panel-cell:has(.sequence), div[id^=e
@media (max-width: 480px) {
.Panel--full {
border-radius: 0;
margin-right: -16px;
margin-left: -16px;
border-right: none;
border-left: none;
}
@@ -7137,8 +7151,11 @@ div[id^=event-lineup] .Panel.position-only .Panel-cell:has(.sequence), div[id^=e
}
.Panel .Panel {
border: none;
margin: 0;
margin: 8px;
}
.scroll-horizontal {
overflow-x: scroll;
}
/*# sourceMappingURL=application.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

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

View File

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

View File

@@ -3,6 +3,8 @@ const eventsController = require("../controllers/event");
const router = express.Router();
const tsUtils = require("../lib/utils")
const {teamsnapCallback} = require("../lib/utils")
const multer = require("multer");
const upload = multer()
// Middleware
const loadEvent = (req,res,next) => {
@@ -23,12 +25,54 @@ const loadEvent = (req,res,next) => {
next();
}
// Middleware
const loadEvents = async (req,res,next) => {
const {team_id, event_id} = req.params
req.timeline = {}
await Promise.all(req.promises)
const {recent_events, upcoming_events} = req
const eventIds = [...recent_events.map(e=>e.id), event_id, ...upcoming_events.map(e=>e.id)]
// if (!req.event_lineup){
bulkLoadTypes = ['event','eventLineup', 'eventLineupEntry']
req.promises.push(
teamsnap.bulkLoad(
{teamId: team_id, types: bulkLoadTypes, scopeTo:'event', event__id:eventIds},
null,
(err, items) => {teamsnapCallback(err, items, {req, source:"loadEvents", method:'bulkLoad'})}
)
.then(items => tsUtils.groupTeamsnapItems(items, bulkLoadTypes))
.then(items => {
req.timeline.events = items.events;
req.timeline.event_lineups = items.eventLineups;
req.timeline.event_lineup_entries = items.eventLineupEntries;
})
)
req.promises.push(
teamsnap.loadAvailabilities(
{eventId: eventIds},
(err, items) => {teamsnapCallback(err, items, {req, source:"loadEvents", method:'loadAvailabilities'})}
).then(availabilities => {
req.timeline.availabilities = availabilities
}
)
)
// }
// else {
// // const {event_lineup} = req
// }
const {event_lineup} = req
next();
}
router.use("/:team_id([0-9]+)/event/:event_id([0-9]+)", loadEvent)
// Routes
router.get("/:team_id([0-9]+)/schedule", eventsController.getEvents);
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)", eventsController.getEvent);
// router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup", eventsController.getLineup);
// router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup_card", eventsController.getLineupCard);
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/availability_reminders", upload.none(), eventsController.sendAvailabilityReminders)
module.exports = {router, loadEvent}
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/modal-confirm-availability-reminders/", eventsController.confirmModalAvailabilityReminders)
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/reset_availabilities",upload.none(), eventsController.submitResetAvailabilities)
module.exports = {router, loadEvent, loadEvents}

View File

@@ -5,6 +5,8 @@ const tsUtils = require('../lib/utils')
const multer = require("multer");
const upload = multer()
const { doubleCsrfProtection } = require('../middlewares/csrf');
const {loadRecentAndUpcomingEvents} = require('../middlewares/bulkload')
const {loadEvents} = require('./event')
const {teamsnapCallback} = require("../lib/utils")
@@ -51,10 +53,11 @@ router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup", async (req,res) =
}
)
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/adjacent", doubleCsrfProtection, loadRecentAndUpcomingEvents, loadEvents, eventsLineupController.getAdjacentEventLineup);
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/email", upload.none(), doubleCsrfProtection, eventsLineupController.getEventLineupEmail )
router.get ("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)", upload.none(), doubleCsrfProtection, eventsLineupController.getEventLineup);
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)", upload.none(), doubleCsrfProtection, eventsLineupController.postEventLineup);
// router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup_card", eventsController.getLineupCard);
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/entries", eventsLineupController.getEventLineupEntries)
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/delete", upload.none(), eventsLineupController.submitDeleteEventLineupEntries);
module.exports = {router, loadEventLineup}

View File

@@ -1,7 +1,7 @@
const express = require("express");
const eventsSheetController = require("../controllers/eventsheet");
const {loadEventLineup} = require("./eventlineup");
const {loadEvent} = require("./event");
const {loadEvent, loadEvents} = require("./event");
const {loadRecentAndUpcomingEvents} = require("../middlewares/bulkload")
const router = express.Router();
const tsUtils = require('../lib/utils')
@@ -10,46 +10,6 @@ const multer = require("multer");
const upload = multer()
// Middleware
const loadEvents = async (req,res,next) => {
const {team_id, event_id} = req.params
req.timeline = {}
await Promise.all(req.promises)
const {recent_events, upcoming_events} = req
const eventIds = [...recent_events.map(e=>e.id), event_id, ...upcoming_events.map(e=>e.id)]
// if (!req.event_lineup){
bulkLoadTypes = ['event','eventLineup', 'eventLineupEntry']
req.promises.push(
teamsnap.bulkLoad(
{teamId: team_id, types: bulkLoadTypes, scopeTo:'event', event__id:eventIds},
null,
(err, items) => {teamsnapCallback(err, items, {req, source:"loadEvents", method:'bulkLoad'})}
)
.then(items => tsUtils.groupTeamsnapItems(items, bulkLoadTypes))
.then(items => {
req.timeline.events = items.events;
req.timeline.event_lineups = items.eventLineups;
req.timeline.event_lineup_entries = items.eventLineupEntries;
})
)
req.promises.push(
teamsnap.loadAvailabilities(
{eventId: eventIds},
(err, items) => {teamsnapCallback(err, items, {req, source:"loadEvents", method:'loadAvailabilities'})}
).then(availabilities => {
req.timeline.availabilities = availabilities
}
)
)
// }
// else {
// // const {event_lineup} = req
// }
const {event_lineup} = req
next();
}
const linksForEventSheet = async (req, res, next) => {
await Promise.all(req.promises)
const events = [...req.recent_events, req.event, ...req.upcoming_events]
@@ -70,6 +30,8 @@ router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/sheet", async (req,res) =>
}
)
router.get("/lineup/sheet/blank", eventsSheetController.getEventSheetBlank )
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/sheet", upload.none(), eventsSheetController.getEventSheet )
module.exports = {router}

View File

@@ -27,4 +27,10 @@ router.get("/", (req,res,next) => {
router.get("/:team_id([0-9]+)/members", membersController.getMembers);
router.get("/modal-confirm/", (req,res) => {
const {title, body} = req.query
res.render('modal_confirm', {title, body} )
}
)
module.exports = {router, partials};

View File

@@ -189,9 +189,11 @@ a.Panel-row {
align-items: center;
}
div[id^="event-lineup"] {
div.event-lineup {
max-width: 576px;
counter-reset: lineup-sequence-counter 0;
margin-left: 8px;
margin-right: 9px;
}
.lineup-slot {
@@ -287,17 +289,31 @@ li .availability-status-code- {
}
div[id^="event-lineup"] .Panel {
&.position-only .Panel-cell:has(.sequence), &.bench .Panel-cell:has(.sequence), &.out .Panel-cell:has(.sequence){
display: none;
}
&.out {
.Panel-cell {
&:has(.sequence), .drag-handle, .SelectBox {
// display: none;
div.event-lineup {
.lineup-segment {
&:has(input.Toggle-input:not(:checked)) {
&.out {
.Panel-cell:has(.SelectBox),
.Panel-cell:has(.drag-handle),
button:has(+.position-label-flags),
button.addToStarting,
button.addToBench
{
display: none;
}
}
}
&.bench, &.position-only, &.out {
.Panel-cell:has(.sequence) {
display: none;
}
&.bench button.addToBench {
display: none;
}
}
&.starting button.addToStarting, &.position-only button.addToStarting {
display: none;
}
}
}
@@ -309,8 +325,8 @@ div[id^="event-lineup"] .Panel {
@media (max-width: 480px){
.Panel--full {
border-radius: 0;
margin-right: -16px;
margin-left: -16px;
// margin-right: -16px;
// margin-left: -16px;
border-right: none;
border-left: none;
}}
@@ -352,8 +368,16 @@ div[id^="event-lineup"] .Panel {
.Panel .Panel{
// padding: 0;
// border-radius: 0;
border: none;
// border: none;
// border-top: 1px solid #d6d6d6;
// border-bottom: 1px solid #d6d6d6;
margin: 0;
margin: 8px;
}
.scroll-horizontal {
overflow-x: scroll;
}
button:has(+.position-label-flags :checked) {
@extend .Button--blue
}

File diff suppressed because it is too large Load Diff

View File

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

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

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">
<input type="hidden" name="label" value="{{member.benchcoach.eventLineupEntry.label}}">
<input type="hidden" name="flags" value="{{flagsString member.benchcoach.eventLineupEntry.flags}}">
<input type="hidden" name="sequence" value="{{member.benchcoach.eventLineupEntry.sequence}}">
<div class="Panel-expandableRow lineup-slot" data-initial-lineup-segment="{{initial_lineup_segment}}">
<input type="hidden" name="label" value="{{eventLineupEntry.label}}">
<input type="hidden" name="flags" value="{{flagsString eventLineupEntry.flags}}">
<input type="hidden" name="sequence" value="{{eventLineupEntry.sequence}}">
<input type="hidden" name="eventId" value="{{event.id}}">
<input type="hidden" name="eventLineupEntryId" value="{{member.benchcoach.eventLineupEntry.id}}">
<input type="hidden" name="availabilityStatusCode", value="{{member.benchcoach.availability?.statusCode}}">
<input type="hidden" name="eventLineupEntryId" value="{{eventLineupEntry.id}}">
<input type="hidden" name="availabilityStatusCode", value="{{#if availability}}{{availability.statusCode}}{{/if}}">
<input type="hidden" name="memberId" value="{{member.id}}">
<input type="hidden" name="lastName" value="{{member.lastName}}">
<input type="hidden" name="firstName" value="{{member.firstName}}">
@@ -17,13 +17,40 @@
</div>
<div class="Panel-cell u-padXs u-sizeFill u-flex">
<div
class="availability-status-code-{{
member.benchcoach.availability?.statusCode
class="Popup availability-status-code-{{
availability?.statusCode
}}"
>
{{#if member.benchcoach.availability}}{{{avail_status_code_icon member.benchcoach.availability.statusCode}}}{{/if}}
{{#if availability}}
{{#with availability}}
<div class="Popup">
<button class="Popup-toggle Button Button--smallSquare {{avail_status_code_class statusCode}}"
type="button"
data-control="popup"
data-open="availablility-popup-{{eventId}}-{{memberId}}"
>
{{#if notes}}{{{embeddedSvgFromPath "/bootstrap-icons/asterisk.svg"}}}{{else}}{{{avail_status_code_icon statusCode}}}{{/if}}
</button>
<div class="Popup-container Popup-container--left" data-popup="availablility-popup-{{eventId}}-{{memberId}}">
<div class="Popup-content u-padSm u-textCenter">
<h3 class="u-spaceBottomSm">Availability</h3>
{{#if notes}}
<p class="u-textLeft">“ <i>{{notes}}</i> ”</p>
{{else}}
<p class="u-textLeft">No notes.</p>
{{/if}}
<button type="button" class="Button u-spaceTopSm" onclick="sendAvailabilityReminder(this, {{eventId}}, ['{{memberId}}'], {{csrfToken}})">
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/send.svg"}}}
<span>Send Reminder</span>
</button>
</div>
</div>
</div>
{{/with}}
{{/if}}
</div>
<div class="u-fontSizeLg u-textNoWrap">
<div class="u-fontSizeLg u-textNoWrap">
<span class="lastname">
{{member.lastName}}
</span>
@@ -35,14 +62,27 @@
</span>
</div>
<div class="u-flexGrow1"></div>
<div class="position-label-flags">
<div class="Checkbox Checkbox--inline">
<input class="Checkbox-input" type="checkbox" name="flag-drd" id="flag-drd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}" onclick="refreshLineup()">
<label class="Checkbox-label" for="flag-drd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}">DR<small>d</small></label>
</div>
<div class="Checkbox Checkbox--inline">
<input class="Checkbox-input" type="checkbox" name="flag-dhd" id="flag-dhd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}" onclick="refreshLineup()">
<label class="Checkbox-label" for="flag-dhd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}">DH<small>d</small></label>
<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>
@@ -53,7 +93,7 @@
--
</option>
{{#each (positions)}}
<option value="{{this}}" {{#if (comparePositionWithFlags this ../member.benchcoach.eventLineupEntry)}}selected{{/if}}>
<option value="{{this}}" {{#if (comparePositionWithFlags this ../eventLineupEntry)}}selected{{/if}}>
{{this}}
</option>
{{/each}}

View File

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

View File

@@ -1,14 +1,16 @@
<table>
<colgroup><col span="3" class="player"></colgroup>
{{!-- <colgroup><col span="0" class="player-stats"></colgroup> --}}
<colgroup><col span="4" class="position-capability"></colgroup>
{{!-- <colgroup><col span="4" class="player"></colgroup> --}}
{{!-- <colgroup><col span="1" class="spacer"></colgroup> --}}
{{!-- <colgroup><col span="1" class="player-stats"></colgroup> --}}
{{!-- <colgroup><col span="4" class="position-capability"></colgroup>
<colgroup><col span="4" class="availability-on-day future"></colgroup>
<colgroup><col span="4" class="availability-on-day past"></colgroup>
<colgroup><col span="4" class="availability-on-day past"></colgroup> --}}
<thead>
<tr>
<th colspan="3" id="today-availability">
<th colspan="4" id="today-availability">
Available ({{availabilitySummary.playerGoingCount}}|{{availabilitySummary.playerMaybeCount}})
</th>
<th class="spacer first-of-group last-of-group"></th>
<th class="player-stats">
<span class="decimal-point">.</span>AVG
<span class="delimiter">/</span>
@@ -17,34 +19,38 @@
<span class="decimal-point">.</span>SLG
<span class="delimiter">:</span>PA
</th>
<th class="position-capability pitcher">P</th>
<th class="position-capability pitcher first-of-group">P</th>
<th class="position-capability catcher">C</th>
<th class="position-capability infield">I</th>
<th class="position-capability outfield">O</th>
<th class="position-capability outfield last-of-group">O</th>
{{!-- <% for timepoint, i in timeline.select{|tp| tp[:comparison_to_selected]>0}.sort{|tp| -tp[:comparison_to_selected]}.each_with_index do%> --}}
{{#loopEvents upcoming_events}}
<th class="availability-on-day avail-today-plus-{{@index}}" date="{{this.startDate}}"><div>{{dateFormat this.startDate "ddd" }}</div></th>
<th class="availability-on-day avail-today-plus-{{@index}} {{#ifEquals @index 0}}first-of-group{{/ifEquals}}{{#ifEquals @index 3}}last-of-group{{/ifEquals}}" date="{{this.startDate}}"><div>{{dateFormat this.startDate "ddd" }}</div></th>
{{/loopEvents}}
{{#loopEvents recent_events}}
<th class="availability-on-day avail-today-minus-{{@index}}" date="{{this.startDate}}"><div>{{dateFormat this.startDate "ddd" }}</div></th>
<th class="availability-on-day avail-today-minus-{{@index}} {{#ifEquals @index 0}}first-of-group{{/ifEquals}}{{#ifEquals @index 3}}last-of-group{{/ifEquals}}" date="{{this.startDate}}"><div>{{dateFormat this.startDate "ddd" }}</div></th>
{{/loopEvents}}
</tr>
</thead>
<tbody>
{{!-- <% by_member.select{|m,d| !m.is_non_player}.each_with_index do |(member, d), i|%> --}}
{{#rosterHistory event event_lineup_entries members availabilities}}
<tr id="roster-history-slot-<%= ::Temple::Utils.escape_html((i)) %>">
<td class="is-present-checkbox available-status-code-{{this.benchcoach.availability.statusCode}}">
<tr class="roster-history-slot{{#if (isStarting this)}} starting-today{{/if}}">
<td class="is-present-checkbox available-status-code-{{this.benchcoach.availability.statusCode}} first-of-group">
<span>■</span>
</td>
<td class="jersey-number available-status-code-{{this.benchcoach.availability.statusCode}}{{#if (isStarting this)}} starting{{/if}}">
<td class="jersey-number available-status-code-{{this.benchcoach.availability.statusCode}}">
{{this.jerseyNumber}}
</td>
<td class="player-name available-status-code-{{this.benchcoach.availability.statusCode}}{{#if (isStarting this)}} starting{{/if}}">
<td class="player-name available-status-code-{{this.benchcoach.availability.statusCode}}">
{{this.lastName}}
</td>
<td class="player-stats border-left border-right">
<td class="position available-status-code-{{this.benchcoach.availability.statusCode}} last-of-group">
<span>{{this.benchcoach.eventLineupEntry.label}}</span>
</td>
<td class="spacer"></td>
<td class="player-stats first-of-group last-of-group">
<span class="decimal-point">.</span>
<span class="avg">000</span>
<span class="delimiter">/</span>
@@ -56,20 +62,20 @@
<span class="delimiter">:</span>
<span class="pa">00</span>
</td>
<td class="position-capability pitcher">{{positionCapabilityFor this "P"}}</td>
<td class="position-capability pitcher first-of-group">{{positionCapabilityFor this "P"}}</td>
<td class="position-capability catcher">{{positionCapabilityFor this "C"}}</td>
<td class="position-capability infield">{{positionCapabilityFor this "IF"}}</td>
<td class="position-capability outfield">{{positionCapabilityFor this "OF"}}</td>
<td class="position-capability outfield last-of-group">{{positionCapabilityFor this "OF"}}</td>
{{#loopEvents ../upcoming_events}}
{{#timepointForMember ../this ../../timeline this}}
<td class="availability-on-day future available-status-code-{{this.availability.statusCode}}">
<td class="availability-on-day future available-status-code-{{this.availability.statusCode}} {{this.value}} {{#ifEquals @index 0}}first-of-group{{/ifEquals}}{{#ifEquals @index 3}}last-of-group{{/ifEquals}}">
{{this.value}}
</td>
{{/timepointForMember}}
{{/loopEvents}}
{{#loopEvents ../recent_events}}
{{#timepointForMember ../this ../../timeline this}}
<td class="availability-on-day past available-status-code-{{this.availability.statusCode}}">
<td class="availability-on-day past available-status-code-{{this.availability.statusCode}} {{this.value}} {{#ifEquals @index 0}}first-of-group{{/ifEquals}}{{#ifEquals @index 3}}last-of-group{{/ifEquals}}">
{{this.value}}
</td>
{{/timepointForMember}}

View File

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

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

@@ -21,14 +21,13 @@
</head>
<body>
<header>
<header class="u-spaceBottomMd">
{{> navbar }}
{{{_sections.header}}}
</header>
<div class="u-padSidesMd u-xs-padSidesLg">
<div class="u-max1200 u-flexExpandSides u-xs-size5of6 u-sm-size2of3 u-md-sizeFull u-padEndsLg u-sm-padEndsXl">
{{{ body }}}
</div>
</div>
<main class="u-xs-spaceSidesMd">
{{{ body }}}
</main>
</body>
{{{script_tags scripts}}}
</html>

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>
{{title}}
<hr class="Divider" />
</h1>
<div class="Panel">
<div class="Panel-body">

View File

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