diff --git a/.travis.yml b/.travis.yml
index b8a4fc7..4138fe1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,7 @@ language: csharp
mono: none
sudo: required
dist: xenial
-dotnet: 2.2
+dotnet: 3.1
script:
- dotnet restore
diff --git a/pokeR/ClientApp/package-lock.json b/pokeR/ClientApp/package-lock.json
index 5d9c2a5..4493fc9 100644
--- a/pokeR/ClientApp/package-lock.json
+++ b/pokeR/ClientApp/package-lock.json
@@ -78,6 +78,60 @@
"webpack-subresource-integrity": "1.1.0-rc.6"
},
"dependencies": {
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true,
+ "optional": true
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ }
+ },
+ "nan": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
+ "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
+ "dev": true,
+ "optional": true
+ },
+ "node-sass": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz",
+ "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "async-foreach": "^0.1.3",
+ "chalk": "^1.1.1",
+ "cross-spawn": "^3.0.0",
+ "gaze": "^1.0.0",
+ "get-stdin": "^4.0.1",
+ "glob": "^7.0.3",
+ "in-publish": "^2.0.0",
+ "lodash": "^4.17.11",
+ "meow": "^3.7.0",
+ "mkdirp": "^0.5.1",
+ "nan": "^2.13.2",
+ "node-gyp": "^3.8.0",
+ "npmlog": "^4.0.0",
+ "request": "^2.88.0",
+ "sass-graph": "^2.2.4",
+ "stdout-stream": "^1.4.0",
+ "true-case-path": "^1.0.2"
+ }
+ },
"rxjs": {
"version": "6.3.3",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz",
@@ -108,6 +162,13 @@
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true,
+ "optional": true
}
}
},
@@ -1097,26 +1158,6 @@
"tslib": "^1.9.0"
}
},
- "@aspnet/signalr": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@aspnet/signalr/-/signalr-1.1.0.tgz",
- "integrity": "sha512-UwWXpfVyB7pfod/GmntaYWMdmmqlfuX7hoig7wMs01xxp53FIBrWGFStUKC3y3S4yu83i38NkzBndBIfOHo5Dw==",
- "requires": {
- "eventsource": "^1.0.7",
- "request": "^2.88.0",
- "ws": "^6.0.0"
- },
- "dependencies": {
- "ws": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz",
- "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==",
- "requires": {
- "async-limiter": "~1.0.0"
- }
- }
- }
- },
"@babel/code-frame": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
@@ -1201,12 +1242,24 @@
}
}
},
- "@ng-bootstrap/ng-bootstrap": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-4.0.1.tgz",
- "integrity": "sha512-COQ6MgZ+HD27pGz2sVPB2ttCZozrjHPs0sayuZkleMvzTllYX/eQEPAOiS+yRsXNkDApi5/XGlIFVWBjxTtwoA==",
+ "@microsoft/signalr": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-3.1.5.tgz",
+ "integrity": "sha512-S2Y7HhnClipySDwnUVTIKAP5dwMsJrk2fbTVedLn88wcag4q6kb4yzUH44qejJcl6xFmbzU++3KHaH6rvblIrQ==",
"requires": {
- "tslib": "^1.9.0"
+ "eventsource": "^1.0.7",
+ "request": "^2.88.0",
+ "ws": "^6.0.0"
+ },
+ "dependencies": {
+ "ws": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
+ "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
+ "requires": {
+ "async-limiter": "~1.0.0"
+ }
+ }
}
},
"@ngtools/webpack": {
@@ -1292,15 +1345,6 @@
"@types/jasmine": "*"
}
},
- "@types/jquery": {
- "version": "3.3.29",
- "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.29.tgz",
- "integrity": "sha512-FhJvBninYD36v3k6c+bVk1DSZwh7B5Dpb/Pyk3HKVsiohn0nhbefZZ+3JXbWQhFyt0MxSl2jRDdGQPHeOHFXrQ==",
- "dev": true,
- "requires": {
- "@types/sizzle": "*"
- }
- },
"@types/node": {
"version": "8.9.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz",
@@ -1319,21 +1363,6 @@
"integrity": "sha512-4GbNCDs98uHCT/OMv40qQC/OpoPbYn9XdXeTiFwHBBFO6eJhYEPUu2zDKirXSbHlvDV8oZ9l8EQ+HrEx/YS9DQ==",
"dev": true
},
- "@types/signalr": {
- "version": "2.2.35",
- "resolved": "https://registry.npmjs.org/@types/signalr/-/signalr-2.2.35.tgz",
- "integrity": "sha512-YVIqZzmiNA8E1maMM57fqz44xJFTWxoNeuw3NfYv/j22lVhVXZeef1rOFo96ENpnrpEa+5ktKpDsGpozfKdTCw==",
- "dev": true,
- "requires": {
- "@types/jquery": "*"
- }
- },
- "@types/sizzle": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
- "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==",
- "dev": true
- },
"@types/source-list-map": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
@@ -1563,8 +1592,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
- "dev": true,
- "optional": true
+ "dev": true
},
"accepts": {
"version": "1.3.5",
@@ -1753,8 +1781,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
"integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
- "dev": true,
- "optional": true
+ "dev": true
},
"array-flatten": {
"version": "2.1.2",
@@ -1878,8 +1905,7 @@
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
"integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=",
- "dev": true,
- "optional": true
+ "dev": true
},
"async-limiter": {
"version": "1.0.0",
@@ -2181,7 +2207,6 @@
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
"integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
"dev": true,
- "optional": true,
"requires": {
"inherits": "~2.0.0"
}
@@ -2506,15 +2531,13 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
"integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
- "dev": true,
- "optional": true
+ "dev": true
},
"camelcase-keys": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
"dev": true,
- "optional": true,
"requires": {
"camelcase": "^2.0.0",
"map-obj": "^1.0.0"
@@ -3067,7 +3090,6 @@
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
"integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
"dev": true,
- "optional": true,
"requires": {
"lru-cache": "^4.0.1",
"which": "^1.2.9"
@@ -3129,7 +3151,6 @@
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
"integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
"dev": true,
- "optional": true,
"requires": {
"array-find-index": "^1.0.1"
}
@@ -4231,7 +4252,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -4252,12 +4274,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -4272,17 +4296,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -4399,7 +4426,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -4411,6 +4439,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -4425,6 +4454,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -4432,12 +4462,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -4456,6 +4488,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -4536,7 +4569,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -4548,6 +4582,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -4633,7 +4668,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -4669,6 +4705,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -4688,6 +4725,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -4731,19 +4769,21 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
}
}
},
"fstream": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
- "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
+ "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
@@ -4773,7 +4813,6 @@
"resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
"integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
"dev": true,
- "optional": true,
"requires": {
"globule": "^1.0.0"
}
@@ -4872,15 +4911,22 @@
}
},
"globule": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
- "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz",
+ "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==",
"dev": true,
- "optional": true,
"requires": {
"glob": "~7.1.1",
- "lodash": "~4.17.10",
+ "lodash": "~4.17.12",
"minimatch": "~3.0.2"
+ },
+ "dependencies": {
+ "lodash": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+ "dev": true
+ }
}
},
"graceful-fs": {
@@ -5337,18 +5383,16 @@
"dev": true
},
"in-publish": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
- "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=",
- "dev": true,
- "optional": true
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz",
+ "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==",
+ "dev": true
},
"indent-string": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
"integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
"dev": true,
- "optional": true,
"requires": {
"repeating": "^2.0.0"
}
@@ -6178,11 +6222,10 @@
"dev": true
},
"js-base64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz",
- "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==",
- "dev": true,
- "optional": true
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz",
+ "integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==",
+ "dev": true
},
"js-tokens": {
"version": "3.0.2",
@@ -6626,7 +6669,6 @@
"resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
"integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
"dev": true,
- "optional": true,
"requires": {
"currently-unhandled": "^0.4.1",
"signal-exit": "^3.0.0"
@@ -6844,7 +6886,6 @@
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
"dev": true,
- "optional": true,
"requires": {
"camelcase-keys": "^2.0.0",
"decamelize": "^1.1.2",
@@ -6859,11 +6900,10 @@
},
"dependencies": {
"minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
- "dev": true,
- "optional": true
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
}
}
},
@@ -7147,6 +7187,14 @@
"integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==",
"dev": true
},
+ "ng2-tooltip-directive": {
+ "version": "2.9.20",
+ "resolved": "https://registry.npmjs.org/ng2-tooltip-directive/-/ng2-tooltip-directive-2.9.20.tgz",
+ "integrity": "sha512-mZzZnBy9ugQgK548n3btp2rBsPIX1YTjVAvmbfKam21El5SnMrure0H0cFa4P34KE+cDaOSAU/FgZOGWXVcF6w==",
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@@ -7175,7 +7223,6 @@
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
"integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
"dev": true,
- "optional": true,
"requires": {
"fstream": "^1.0.0",
"glob": "^7.0.3",
@@ -7195,8 +7242,7 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
- "dev": true,
- "optional": true
+ "dev": true
}
}
},
@@ -7249,11 +7295,10 @@
}
},
"node-sass": {
- "version": "4.12.0",
- "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz",
- "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==",
+ "version": "4.13.1",
+ "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.1.tgz",
+ "integrity": "sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw==",
"dev": true,
- "optional": true,
"requires": {
"async-foreach": "^0.1.3",
"chalk": "^1.1.1",
@@ -7262,7 +7307,7 @@
"get-stdin": "^4.0.1",
"glob": "^7.0.3",
"in-publish": "^2.0.0",
- "lodash": "^4.17.11",
+ "lodash": "^4.17.15",
"meow": "^3.7.0",
"mkdirp": "^0.5.1",
"nan": "^2.13.2",
@@ -7278,15 +7323,13 @@
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
- "dev": true,
- "optional": true
+ "dev": true
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
- "optional": true,
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
@@ -7295,19 +7338,23 @@
"supports-color": "^2.0.0"
}
},
+ "lodash": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+ "dev": true
+ },
"nan": {
- "version": "2.13.2",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
- "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==",
- "dev": true,
- "optional": true
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
+ "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
+ "dev": true
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
- "dev": true,
- "optional": true
+ "dev": true
}
}
},
@@ -7316,7 +7363,6 @@
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
"dev": true,
- "optional": true,
"requires": {
"abbrev": "1"
}
@@ -7606,7 +7652,6 @@
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
"dev": true,
- "optional": true,
"requires": {
"lcid": "^1.0.0"
}
@@ -8528,7 +8573,6 @@
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
"integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
"dev": true,
- "optional": true,
"requires": {
"indent-string": "^2.1.0",
"strip-indent": "^1.0.1"
@@ -8800,7 +8844,6 @@
"resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
"integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=",
"dev": true,
- "optional": true,
"requires": {
"glob": "^7.0.0",
"lodash": "^4.0.0",
@@ -8853,7 +8896,6 @@
"resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
"integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
"dev": true,
- "optional": true,
"requires": {
"js-base64": "^2.1.8",
"source-map": "^0.4.2"
@@ -8864,7 +8906,6 @@
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
"dev": true,
- "optional": true,
"requires": {
"amdefine": ">=0.0.4"
}
@@ -9643,7 +9684,6 @@
"resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz",
"integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==",
"dev": true,
- "optional": true,
"requires": {
"readable-stream": "^2.0.1"
}
@@ -9766,7 +9806,6 @@
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
"integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
"dev": true,
- "optional": true,
"requires": {
"get-stdin": "^4.0.1"
}
@@ -9853,14 +9892,13 @@
"dev": true
},
"tar": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
- "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
+ "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
"dev": true,
- "optional": true,
"requires": {
"block-stream": "*",
- "fstream": "^1.0.2",
+ "fstream": "^1.0.12",
"inherits": "2"
}
},
@@ -10208,8 +10246,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
"integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
- "dev": true,
- "optional": true
+ "dev": true
},
"trim-right": {
"version": "1.0.1",
@@ -10222,7 +10259,6 @@
"resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz",
"integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==",
"dev": true,
- "optional": true,
"requires": {
"glob": "^7.1.2"
}
@@ -11109,8 +11145,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
"integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=",
- "dev": true,
- "optional": true
+ "dev": true
},
"wide-align": {
"version": "1.1.3",
@@ -11216,7 +11251,6 @@
"resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
"integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=",
"dev": true,
- "optional": true,
"requires": {
"camelcase": "^3.0.0",
"cliui": "^3.2.0",
@@ -11237,15 +11271,13 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
- "dev": true,
- "optional": true
+ "dev": true
},
"y18n": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
- "dev": true,
- "optional": true
+ "dev": true
}
}
},
@@ -11254,7 +11286,6 @@
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
"integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
"dev": true,
- "optional": true,
"requires": {
"camelcase": "^3.0.0"
},
@@ -11263,8 +11294,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
- "dev": true,
- "optional": true
+ "dev": true
}
}
},
diff --git a/pokeR/ClientApp/package.json b/pokeR/ClientApp/package.json
index 7d7c17b..d5bf3d2 100644
--- a/pokeR/ClientApp/package.json
+++ b/pokeR/ClientApp/package.json
@@ -1,10 +1,10 @@
{
- "name": "client-app",
- "version": "0.0.0",
+ "name": "poker-web",
+ "version": "2.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
- "build": "npm rebuild node-sass && ng build",
+ "build": "ng build --prod",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
@@ -20,10 +20,10 @@
"@angular/platform-browser": "~7.2.11",
"@angular/platform-browser-dynamic": "~7.2.11",
"@angular/router": "~7.2.11",
- "@aspnet/signalr": "^1.1.0",
- "@ng-bootstrap/ng-bootstrap": "^4.0.1",
+ "@microsoft/signalr": "^3.1.5",
"canvas-confetti": "^0.2.0",
"core-js": "^2.5.4",
+ "ng2-tooltip-directive": "^2.9.20",
"rxjs": "~6.4.0",
"tslib": "^1.9.0",
"zone.js": "^0.8.29"
@@ -36,7 +36,6 @@
"@types/jasmine": "^2.8.15",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~8.9.4",
- "@types/signalr": "^2.2.35",
"codelyzer": "~4.5.0",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1",
@@ -45,6 +44,7 @@
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~1.1.2",
"karma-jasmine-html-reporter": "^0.2.2",
+ "node-sass": "^4.13.1",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.11.0",
diff --git a/pokeR/ClientApp/src/app/app.component.html b/pokeR/ClientApp/src/app/app.component.html
index b3d7414..abf69c9 100644
--- a/pokeR/ClientApp/src/app/app.component.html
+++ b/pokeR/ClientApp/src/app/app.component.html
@@ -1,2 +1,3 @@
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/pokeR/ClientApp/src/app/app.component.scss b/pokeR/ClientApp/src/app/app.component.scss
index e69de29..650a65c 100644
--- a/pokeR/ClientApp/src/app/app.component.scss
+++ b/pokeR/ClientApp/src/app/app.component.scss
@@ -0,0 +1,8 @@
+@import '../theme/variables';
+
+.corner-button {
+ position: fixed;
+ top: $mid;
+ right: $mid;
+ z-index: 30;
+}
\ No newline at end of file
diff --git a/pokeR/ClientApp/src/app/app.module.ts b/pokeR/ClientApp/src/app/app.module.ts
index c55aae0..0db2a46 100644
--- a/pokeR/ClientApp/src/app/app.module.ts
+++ b/pokeR/ClientApp/src/app/app.module.ts
@@ -6,10 +6,10 @@ import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
import { PagesModule } from './pages/pages.module';
-import { NotificationComponent } from './shared-components/notification/notification.component';
-import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { SharedComponentsModule } from './shared-components/shared-components.module';
+import { TooltipModule } from 'ng2-tooltip-directive';
+
@NgModule({
declarations: [
AppComponent
@@ -20,13 +20,11 @@ import { SharedComponentsModule } from './shared-components/shared-components.mo
HttpClientModule,
PagesModule,
FormsModule,
- NgbModule,
+ TooltipModule,
SharedComponentsModule
],
providers: [],
bootstrap: [AppComponent],
- entryComponents: [
- NotificationComponent
- ]
+ entryComponents: []
})
export class AppModule { }
diff --git a/pokeR/ClientApp/src/app/models/theme-mode.ts b/pokeR/ClientApp/src/app/models/theme-mode.ts
new file mode 100644
index 0000000..07f9e4e
--- /dev/null
+++ b/pokeR/ClientApp/src/app/models/theme-mode.ts
@@ -0,0 +1,5 @@
+export enum ThemeMode {
+ light,
+ dark,
+ automatic
+}
diff --git a/pokeR/ClientApp/src/app/pages/ingame/ingame.component.html b/pokeR/ClientApp/src/app/pages/ingame/ingame.component.html
index 9c80653..ca3ee33 100644
--- a/pokeR/ClientApp/src/app/pages/ingame/ingame.component.html
+++ b/pokeR/ClientApp/src/app/pages/ingame/ingame.component.html
@@ -1,20 +1,28 @@
-
-
-
-
Room not found 😢
-
+
+
Room not found 😢
+
+
+
+
-
\ No newline at end of file
+
diff --git a/pokeR/ClientApp/src/app/pages/ingame/ingame.component.scss b/pokeR/ClientApp/src/app/pages/ingame/ingame.component.scss
index e69de29..7a75df3 100644
--- a/pokeR/ClientApp/src/app/pages/ingame/ingame.component.scss
+++ b/pokeR/ClientApp/src/app/pages/ingame/ingame.component.scss
@@ -0,0 +1,54 @@
+@import "../../../theme/variables";
+
+.enter-form {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ animation: fan-fade-in 1s 1s ease forwards;
+ opacity: 0;
+}
+
+.bg-underlay {
+ z-index: -1;
+}
+
+.contained {
+ max-width: 800px;
+}
+
+.bg-feather-mask {
+ box-shadow: 0 0 $thicc $thicc var(--primary-color);
+ padding: $some;
+ background-color: var(--primary-color);
+ border-radius: 40%;
+}
+
+@keyframes fan-fade-in {
+ from {
+ opacity: 0;
+ transform: scale(0.9);
+ }
+
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+.invalid-container {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+
+ h4 {
+ font-size: 2.3rem;
+ font-weight: lighter;
+ }
+
+ .btn {
+ margin: $some;
+ }
+}
diff --git a/pokeR/ClientApp/src/app/pages/ingame/ingame.component.ts b/pokeR/ClientApp/src/app/pages/ingame/ingame.component.ts
index a03e871..3a4d3b6 100644
--- a/pokeR/ClientApp/src/app/pages/ingame/ingame.component.ts
+++ b/pokeR/ClientApp/src/app/pages/ingame/ingame.component.ts
@@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router';
import { Room } from 'src/app/models/entities/room';
import { Observable } from 'rxjs';
import { PokerService } from 'src/app/services/poker.service';
-import { map, flatMap } from 'rxjs/operators';
+import { map, flatMap, tap } from 'rxjs/operators';
@Component({
selector: 'app-ingame',
@@ -21,16 +21,21 @@ export class IngameComponent implements OnInit {
ngOnInit() {
this.loadRoom().subscribe();
- this.route.params.subscribe(p => {
- this.roomId = p['id'];
- });
}
onRoomError = (): boolean => this.isInvalid = true;
onJoined = (): boolean => this.isInGame = true;
- loadRoom = (): Observable
=> this.route.params
- .pipe(flatMap(p => this.service.getRoom(p['id'])))
- .pipe(map(r => this.room = r))
+ loadRoom = (): Observable => this.route.params.pipe(
+ map(params => params['id']),
+ tap(id => this.roomId = id),
+ flatMap(id => this.service.getRoom(id)),
+ map(room => this.room = room)
+ )
+
+ reload = () => {
+ this.service.getRoom(this.roomId).pipe(map(room => this.room = room)).subscribe();
+ this.isInvalid = false;
+ }
}
diff --git a/pokeR/ClientApp/src/app/pages/landing/landing.component.html b/pokeR/ClientApp/src/app/pages/landing/landing.component.html
index c7a769f..21cb94b 100644
--- a/pokeR/ClientApp/src/app/pages/landing/landing.component.html
+++ b/pokeR/ClientApp/src/app/pages/landing/landing.component.html
@@ -1,15 +1,19 @@
-
-
-
-
-
Planning PokeR
-
- v1.2
-
+
\ No newline at end of file
diff --git a/pokeR/ClientApp/src/app/pages/landing/landing.component.scss b/pokeR/ClientApp/src/app/pages/landing/landing.component.scss
index e69de29..90f1cdb 100644
--- a/pokeR/ClientApp/src/app/pages/landing/landing.component.scss
+++ b/pokeR/ClientApp/src/app/pages/landing/landing.component.scss
@@ -0,0 +1,134 @@
+@import '../../../theme/variables';
+
+.landing-container {
+ width: 100%;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+ overflow: hidden;
+ flex-wrap: wrap;
+
+ .promo-pane {
+ background-color: var(--accent-color);
+ padding: $thicc;
+ flex-grow: 1;
+ position: relative;
+ overflow: hidden;
+ z-index: 0;
+
+ .cards {
+ position: absolute;
+ pointer-events: none;
+ transform: rotate(-36deg);
+ width: 100%;
+ height: 100%;
+ position: absolute;
+
+ .card {
+ width: 340px;
+ height: 410px;
+ border-radius: 48px;
+ display: block;
+ background: linear-gradient(0deg, transparent, var(--primary-color));
+ opacity: 0;
+ position: absolute;
+ animation-name: slide-up;
+ animation-timing-function: ease;
+ animation-duration: .8s;
+ animation-fill-mode: forwards;
+
+ &.pos-a {
+ top: 12%;
+ left: 47%;
+ animation-delay: 0.8s;
+ }
+
+ &.pos-b {
+ top: 18%;
+ left: 15%;
+ animation-delay: 1.1s;
+ }
+
+ &.pos-c {
+ top: 41%;
+ left: 32%;
+ animation-delay: 1.24s;
+ }
+ }
+ }
+
+ .promo-text {
+ z-index: 4;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+ height: 100%;
+
+ h1 {
+ font-weight: normal;
+ font-size: 48px;
+ margin: 0;
+ animation: slide-left 0.8s .5s ease forwards;
+ opacity: 0;
+ }
+
+ p {
+ font-size: 20px;
+ animation: slide-left 0.8s .7s ease forwards;
+ opacity: 0;
+ }
+ }
+ }
+
+ .content-pane {
+ flex-grow: 2;
+ background-color: var(--primary-color);
+ padding: $thicc;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: stretch;
+ box-shadow: 0 0 0 0 var(--primary-color);
+ z-index: 1;
+ }
+
+ &.exiting {
+ pointer-events: none;
+
+ .content-pane {
+ &>* {
+ transition: opacity .5s ease;
+ opacity: 0;
+ }
+
+ transition: box-shadow .5s ease;
+ box-shadow: 0 0 0 100vw var(--primary-color);
+ }
+ }
+}
+
+@keyframes slide-up {
+ from {
+ opacity: 0;
+ transform: translateY(168px);
+ }
+
+ to {
+ opacity: 0.35;
+ transform: translateY(0);
+ }
+}
+
+@keyframes slide-left {
+ from {
+ opacity: 0;
+ transform: translateX(-168px);
+ }
+
+ to {
+ opacity: 2;
+ transform: translateX(0);
+ }
+}
\ No newline at end of file
diff --git a/pokeR/ClientApp/src/app/pages/landing/landing.component.ts b/pokeR/ClientApp/src/app/pages/landing/landing.component.ts
index 0a884c5..ccc42ef 100644
--- a/pokeR/ClientApp/src/app/pages/landing/landing.component.ts
+++ b/pokeR/ClientApp/src/app/pages/landing/landing.component.ts
@@ -1,15 +1,23 @@
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
+import { Subscription, of } from 'rxjs';
+import { delay, tap } from 'rxjs/operators';
+import { Router } from '@angular/router';
@Component({
selector: 'app-landing',
templateUrl: './landing.component.html',
styleUrls: ['./landing.component.scss']
})
-export class LandingComponent implements OnInit {
+export class LandingComponent {
+ public exiting = false;
- constructor() { }
-
- ngOnInit() {
- }
+ constructor(
+ private router: Router
+ ) { }
+ onExit = (route: any[]): Subscription => of(null).pipe(
+ tap(() => this.exiting = true),
+ delay(550),
+ tap(() => this.router.navigate(route))
+ ).subscribe()
}
diff --git a/pokeR/ClientApp/src/app/services/poker.service.ts b/pokeR/ClientApp/src/app/services/poker.service.ts
index 9585078..8354c7a 100644
--- a/pokeR/ClientApp/src/app/services/poker.service.ts
+++ b/pokeR/ClientApp/src/app/services/poker.service.ts
@@ -2,10 +2,10 @@ import { Injectable, EventEmitter } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { CreateRoomRequest } from '../models/create-room-request';
import { Deck } from '../models/entities/deck';
-import { of, Observable, from, Subject, forkJoin } from 'rxjs';
+import { of, Observable, from, forkJoin } from 'rxjs';
import { map, flatMap } from 'rxjs/operators';
import { Room } from '../models/entities/room';
-import { HubConnection, HubConnectionBuilder, JsonHubProtocol } from '@aspnet/signalr';
+import { HubConnection, HubConnectionBuilder, JsonHubProtocol } from '@microsoft/signalr';
import { JoinRoomRequest } from '../models/join-room-request';
import { User } from '../models/entities/user';
import { ListChange } from '../models/list-change';
@@ -44,32 +44,32 @@ export class PokerService {
this.initializeHubWatches().subscribe();
}
- public getEmblemUrl = (id: number): string => `api/emblems/${id}/image`;
+ public getEmblemUrl = (id: number): string => `/api/emblems/${id}/image`;
public reset = (): void => this.room = null;
public createRoom = (request: CreateRoomRequest): Observable
=>
- this.http.post('api/rooms', request)
+ this.http.post('/api/rooms', request)
public getDecks = (): Observable =>
this.decks.length ? of(this.decks) :
- this.http.get('api/decks').pipe(map(d => this.decks = d))
+ this.http.get('/api/decks').pipe(map(d => this.decks = d))
public getEmblems = (): Observable =>
this.emblems.length ? of(this.emblems) :
- this.http.get('api/emblems').pipe(map(e => this.emblems = e))
+ this.http.get('/api/emblems').pipe(map(e => this.emblems = e))
public getRoom = (roomId: string): Observable =>
- this.http.get(`api/rooms/${roomId}`).pipe(map(r => this.room = r))
+ this.http.get(`/api/rooms/${roomId}`).pipe(map(r => this.room = r))
public getPlayers = (): Observable> =>
this.room
- ? this.http.get(`api/rooms/${this.room.id}`).pipe(map(r => r.users))
+ ? this.http.get(`/api/rooms/${this.room.id}`).pipe(map(r => r.users))
: of(new Array())
public getTagline = (): Observable =>
this.room
- ? this.http.get(`api/rooms/${this.room.id}`).pipe(map(r => r.tagLine))
+ ? this.http.get(`/api/rooms/${this.room.id}`).pipe(map(r => r.tagLine))
: of('')
public joinRoom = (request: JoinRoomRequest): Observable =>
@@ -100,7 +100,7 @@ export class PokerService {
this.getHub().pipe(flatMap((hub: HubConnection) => from(hub.invoke('switchHost', newHostId))))
public checkAvailability = (id: string): Observable =>
- this.http.get(`api/rooms/available/${id}`, { observe: 'response', responseType: 'text' as 'json' })
+ this.http.get(`/api/rooms/available/${id}`, { observe: 'response', responseType: 'text' as 'json' })
.pipe(map(r => r.body === 'true'))
public getCards = (deckId: number): Observable =>
@@ -118,6 +118,7 @@ export class PokerService {
private prepareHub = (): Promise => {
this.hub = new HubConnectionBuilder()
.withUrl('/notify/room')
+ .withAutomaticReconnect([0, 1000, 2000, 3000, 5000, 10000, 12000, 15000, 30000, null])
.withHubProtocol(new JsonHubProtocol())
.build();
return this.hub.start().catch(console.error).then(() => {
diff --git a/pokeR/ClientApp/src/app/services/theme.service.spec.ts b/pokeR/ClientApp/src/app/services/theme.service.spec.ts
new file mode 100644
index 0000000..a223a14
--- /dev/null
+++ b/pokeR/ClientApp/src/app/services/theme.service.spec.ts
@@ -0,0 +1,12 @@
+import { TestBed } from '@angular/core/testing';
+
+import { ThemeService } from './theme.service';
+
+describe('ThemeService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: ThemeService = TestBed.get(ThemeService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/pokeR/ClientApp/src/app/services/theme.service.ts b/pokeR/ClientApp/src/app/services/theme.service.ts
new file mode 100644
index 0000000..971551f
--- /dev/null
+++ b/pokeR/ClientApp/src/app/services/theme.service.ts
@@ -0,0 +1,54 @@
+import { Injectable } from '@angular/core';
+import { ThemeMode } from '../models/theme-mode';
+
+const themes = {
+ dark: {
+ '--primary-color': '#353749',
+ '--accent-color': '#53577D',
+ '--contrast-color': 'white',
+ '--hover-color': 'rgba(255,255,255,0.1)',
+ '--active-color': 'rgba(255,255,255,0.2)',
+ '--font-family': '\'Open Sans\',sans-serif',
+ '--valid-color': 'green',
+ '--invalid-color': '#ff5c5c'
+ }, light: {
+ '--primary-color': '#E9E9E9',
+ '--accent-color': '#9FA2C2',
+ '--contrast-color': '#353749',
+ '--hover-color': 'rgba(0,0,0,0.1)',
+ '--active-color': 'rgba(0,0,0,0.2)',
+ '--font-family': '\'Open Sans\',sans-serif',
+ '--valid-color': 'green',
+ '--invalid-color': 'red'
+ }
+};
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ThemeService {
+
+ constructor() { }
+
+ public applyTheme = (m: ThemeMode): void => {
+ switch (m) {
+ case ThemeMode.automatic:
+ if (this.isInDarkMode()) {
+ this.setVariables(themes.dark);
+ } else {
+ this.setVariables(themes.light);
+ } return;
+ case ThemeMode.dark:
+ this.setVariables(themes.dark);
+ return;
+ case ThemeMode.light:
+ this.setVariables(themes.light);
+ return;
+ }
+ }
+
+ private isInDarkMode = (): boolean => window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
+
+ private setVariables = (map: any) => Object.keys(map)
+ .forEach(k => document.documentElement.style.setProperty(k, map[k]))
+}
diff --git a/pokeR/ClientApp/src/app/shared-components/background-cards/background-cards.component.html b/pokeR/ClientApp/src/app/shared-components/background-cards/background-cards.component.html
new file mode 100644
index 0000000..83ba3fe
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/background-cards/background-cards.component.html
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/pokeR/ClientApp/src/app/shared-components/background-cards/background-cards.component.scss b/pokeR/ClientApp/src/app/shared-components/background-cards/background-cards.component.scss
new file mode 100644
index 0000000..4effdb4
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/background-cards/background-cards.component.scss
@@ -0,0 +1,52 @@
+@import '../../../theme/variables';
+
+.bg-cards {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ perspective: 900px;
+ pointer-events: none;
+ overflow: hidden;
+
+ .perspective-container {
+ width: 100%;
+ height: 100%;
+ animation: pan-in 20s ease 0s forwards;
+ transform-style: preserve-3d;
+
+ .bg-card {
+ position: absolute;
+ border: $slim solid var(--accent-color);
+ border-radius: $slim;
+ width: 4.5rem;
+ height: 6rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: .5;
+ transform-style: preserve-3d;
+ background-color: var(--primary-color);
+
+ .card-label {
+ color: var(--accent-color);
+ font-size: 56px;
+ }
+ }
+ }
+}
+
+@keyframes pan-in {
+ 0% {
+ transform: translateZ(-10000px);
+ }
+
+ 5% {
+ transform: translateZ(-240px);
+ }
+
+ 100% {
+ transform: translateZ(0);
+ }
+}
\ No newline at end of file
diff --git a/pokeR/ClientApp/src/app/shared-components/background-cards/background-cards.component.spec.ts b/pokeR/ClientApp/src/app/shared-components/background-cards/background-cards.component.spec.ts
new file mode 100644
index 0000000..e5f6263
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/background-cards/background-cards.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { BackgroundCardsComponent } from './background-cards.component';
+
+describe('BackgroundCardsComponent', () => {
+ let component: BackgroundCardsComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ BackgroundCardsComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(BackgroundCardsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/pokeR/ClientApp/src/app/shared-components/background-cards/background-cards.component.ts b/pokeR/ClientApp/src/app/shared-components/background-cards/background-cards.component.ts
new file mode 100644
index 0000000..5c8aa1a
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/background-cards/background-cards.component.ts
@@ -0,0 +1,43 @@
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { Card } from 'src/app/models/entities/card';
+
+@Component({
+ selector: 'app-background-cards',
+ templateUrl: './background-cards.component.html',
+ styleUrls: ['./background-cards.component.scss']
+})
+export class BackgroundCardsComponent implements OnChanges {
+ @Input() cards: Array = [];
+ cardViews: any[] = [];
+ constructor() { }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ const newCards: Array = changes.cards.currentValue;
+ this.cardViews = this.duplicate(newCards, 2).map(this.getView);
+ }
+
+ private getView = (c: Card): any => ({
+ px: Math.random() * 100,
+ py: Math.random() * 100,
+ zBase: Math.random(),
+ ox: Math.random() * 180 - 90,
+ oy: Math.random() * 180 - 90,
+ oz: Math.random() * 180 - 90,
+ text: c.name
+ })
+
+ getStyle = (v: any): any => ({
+ 'transform': `translateZ(${v.zBase * 1200 - 600}px) rotateX(${v.ox}deg) rotateY(${v.oy}deg) rotateZ(${v.oz}deg)`,
+ 'left': `${v.px}%`,
+ 'top': `${v.py}%`,
+ 'opacity': v.zBase * 0.7 + 0.3
+ })
+
+ private duplicate = (source: Array, count: number): Array => {
+ let result = source;
+ for (let i = 0; i < count; i++) {
+ result = result.concat(result);
+ }
+ return result;
+ }
+}
diff --git a/pokeR/ClientApp/src/app/shared-components/card-list/card-list.component.html b/pokeR/ClientApp/src/app/shared-components/card-list/card-list.component.html
index 1d09c07..58c136e 100644
--- a/pokeR/ClientApp/src/app/shared-components/card-list/card-list.component.html
+++ b/pokeR/ClientApp/src/app/shared-components/card-list/card-list.component.html
@@ -1,7 +1,9 @@
-
-
- {{c.name}}
+
\ No newline at end of file
+
diff --git a/pokeR/ClientApp/src/app/shared-components/card-list/card-list.component.scss b/pokeR/ClientApp/src/app/shared-components/card-list/card-list.component.scss
index 9a4e4ef..a1d2373 100644
--- a/pokeR/ClientApp/src/app/shared-components/card-list/card-list.component.scss
+++ b/pokeR/ClientApp/src/app/shared-components/card-list/card-list.component.scss
@@ -1,17 +1,51 @@
-.cardTray {
- font-size: 1.4rem;
- min-width: 2em;
- padding-top: .5em;
- padding-bottom: .5em;
- text-align: center;
- transition: font-size .2s ease;
-}
+@import "../../../theme/variables";
+
+.card-list {
+ left: 50%;
+ transform: translateX(-50%);
+ animation: slide-up forwards 1s ease;
+ width: 100%;
+ overflow-x: auto;
+
+ & > .card-list__inner {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: flex-end;
+
+ @media screen and (max-width: 768px) {
+ justify-content: flex-start;
+ }
-.cardTray.selected {
- font-size: 2rem;
+ & > .card {
+ font-size: 1.4rem;
+ min-width: 2em;
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ text-align: center;
+ transition: font-size 0.2s ease;
+ border: $thinn solid var(--accent-color);
+ border-radius: $slim;
+ cursor: pointer;
+ margin: $mid;
+
+ &.selected {
+ font-size: 2rem;
+ }
+ }
+ }
}
.fixed-bottom {
- position: fixed;
- bottom: 0;
-}
\ No newline at end of file
+ position: fixed;
+ bottom: 0;
+}
+
+@keyframes slide-up {
+ 0% {
+ transform: translateY(100%) translateX(-50%);
+ }
+ 100% {
+ transform: translateY(0) translateX(-50%);
+ }
+}
diff --git a/pokeR/ClientApp/src/app/shared-components/create-room/create-room.component.html b/pokeR/ClientApp/src/app/shared-components/create-room/create-room.component.html
index d542f60..4a1a860 100644
--- a/pokeR/ClientApp/src/app/shared-components/create-room/create-room.component.html
+++ b/pokeR/ClientApp/src/app/shared-components/create-room/create-room.component.html
@@ -2,67 +2,59 @@
-
-
- This room is available.
-
-
- This room is taken.
-
Join it
-
-
-
- Room ID is required.
+
+
+
+ This room is available.
+
+
+ This room is taken.
+
Join it
+
+
+
+ Room Number is required.
+
-
-
- Name is required.
+
+
+
+ Room Name is required.
+
-
-
- Deck is required.
+
+
+
+ Deck is required.
+
@@ -79,8 +71,7 @@
-
diff --git a/pokeR/ClientApp/src/app/shared-components/create-room/create-room.component.ts b/pokeR/ClientApp/src/app/shared-components/create-room/create-room.component.ts
index dbbbc9b..ac66714 100644
--- a/pokeR/ClientApp/src/app/shared-components/create-room/create-room.component.ts
+++ b/pokeR/ClientApp/src/app/shared-components/create-room/create-room.component.ts
@@ -1,9 +1,8 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, EventEmitter, Output } from '@angular/core';
import { CreateRoomRequest } from 'src/app/models/create-room-request';
import { PokerService } from 'src/app/services/poker.service';
import { Deck } from 'src/app/models/entities/deck';
import { map, debounceTime, flatMap } from 'rxjs/operators';
-import { Router } from '@angular/router';
import { Observable } from 'rxjs';
import { FormGroup, FormControl, Validators } from '@angular/forms';
@@ -15,6 +14,7 @@ import { FormGroup, FormControl, Validators } from '@angular/forms';
export class CreateRoomComponent implements OnInit {
decks: Array = new Array();
isAvailable: boolean;
+ @Output() exit: EventEmitter = new EventEmitter();
submitAttempted = false;
@@ -25,13 +25,15 @@ export class CreateRoomComponent implements OnInit {
deck: new FormControl('', Validators.required)
});
- constructor(private service: PokerService, private router: Router) { }
+ constructor(private service: PokerService) { }
ngOnInit() {
this.service.getDecks().subscribe(d => this.decks = d);
this.watchIdChanges().subscribe();
}
+ join = (): void => this.exit.emit(['/room', this.form.get('roomId').value]);
+
// tslint:disable-next-line:triple-equals
getSelectedDeck = (): Deck => this.decks.find(d => d.id == this.form.get('deck').value);
@@ -40,7 +42,7 @@ export class CreateRoomComponent implements OnInit {
if (this.form.valid && this.isAvailable) {
const snapshot = this.getModel();
this.service.createRoom(snapshot).pipe(map(() => {
- this.router.navigate(['/room', snapshot.id]);
+ this.exit.emit(['/room', snapshot.id]);
})).subscribe();
}
}
@@ -56,7 +58,7 @@ export class CreateRoomComponent implements OnInit {
debounceTime(300),
flatMap(this.service.checkAvailability),
map(r => this.isAvailable = r)
- );
+ )
getDelayStyle = (i: number) => {
'animation-delay': `${i * 60}ms`
diff --git a/pokeR/ClientApp/src/app/shared-components/entryway/entryway.component.html b/pokeR/ClientApp/src/app/shared-components/entryway/entryway.component.html
new file mode 100644
index 0000000..78dd6e8
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/entryway/entryway.component.html
@@ -0,0 +1,20 @@
+
+
+
+ Join an existing room
+
+
+
+
+
+
+
+ Create a new room
+
+
+
+
\ No newline at end of file
diff --git a/pokeR/ClientApp/src/app/shared-components/entryway/entryway.component.scss b/pokeR/ClientApp/src/app/shared-components/entryway/entryway.component.scss
new file mode 100644
index 0000000..2d9daed
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/entryway/entryway.component.scss
@@ -0,0 +1,104 @@
+@import '../../../theme/variables';
+
+.actionable {
+ overflow-y: hidden;
+ max-height: 100vh;
+ transition: max-height 1s ease;
+ margin-top: $slim;
+ margin-bottom: $slim;
+ border-radius: $slim;
+
+ .selector-bar {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ font-size: 24px;
+ margin-top: $mid;
+ margin-bottom: $mid;
+ padding: $some $slim $some $slim;
+ border-radius: $slim;
+ cursor: pointer;
+ position: relative;
+ transition: background-color .2s ease;
+
+ .forward-icon,
+ .back-icon {
+ overflow: hidden;
+ }
+
+ .back-icon {
+ max-width: 0;
+ transition: .5s;
+ margin-right: $slim;
+ }
+
+ .forward-icon {
+ transition: .5s;
+ max-width: 24px;
+ margin-left: $slim;
+ }
+
+ .label {
+ flex-grow: 1;
+ }
+
+ &::after {
+ content: '';
+ display: block;
+ position: absolute;
+ height: $slim;
+ background-color: var(--accent-color);
+ width: calc(100% - #{$slim});
+ transform-origin: left;
+ top: 100%;
+ transform: scaleX(0);
+ transition: transform 1.4s ease;
+ }
+ }
+
+ .content {
+ max-height: 0;
+ overflow-y: hidden;
+ pointer-events: none;
+ transition: 1.2s;
+ padding-top: $some;
+ opacity: 0;
+ }
+
+ &.active {
+ .selector-bar {
+ cursor: inherit;
+
+ .back-icon {
+ max-width: 24px;
+ cursor: pointer;
+ }
+
+ .forward-icon {
+ max-width: 0;
+ }
+
+ &::after {
+ transform: scaleX(1);
+ }
+ }
+
+ .content {
+ pointer-events: initial;
+ max-height: 100vh;
+ opacity: 1;
+ }
+ }
+
+ &:not(.active) {
+ &:hover {
+ .selector-bar {
+ background-color: var(--hover-color);
+ }
+ }
+ }
+
+ &.inactive {
+ max-height: 0;
+ }
+}
\ No newline at end of file
diff --git a/pokeR/ClientApp/src/app/shared-components/entryway/entryway.component.spec.ts b/pokeR/ClientApp/src/app/shared-components/entryway/entryway.component.spec.ts
new file mode 100644
index 0000000..26de2d7
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/entryway/entryway.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { EntrywayComponent } from './entryway.component';
+
+describe('EntrywayComponent', () => {
+ let component: EntrywayComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ EntrywayComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(EntrywayComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/pokeR/ClientApp/src/app/shared-components/entryway/entryway.component.ts b/pokeR/ClientApp/src/app/shared-components/entryway/entryway.component.ts
new file mode 100644
index 0000000..04fb56f
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/entryway/entryway.component.ts
@@ -0,0 +1,14 @@
+import { Component, EventEmitter, Output } from '@angular/core';
+
+@Component({
+ selector: 'app-entryway',
+ templateUrl: './entryway.component.html',
+ styleUrls: ['./entryway.component.scss']
+})
+export class EntrywayComponent {
+ @Output() exit: EventEmitter = new EventEmitter();
+ public action: string = null;
+ constructor() { }
+
+ onExit = (route: any[]): void => this.exit.emit(route);
+}
diff --git a/pokeR/ClientApp/src/app/shared-components/find-room/find-room.component.html b/pokeR/ClientApp/src/app/shared-components/find-room/find-room.component.html
new file mode 100644
index 0000000..679c3b4
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/find-room/find-room.component.html
@@ -0,0 +1,29 @@
+
\ No newline at end of file
diff --git a/pokeR/ClientApp/src/app/shared-components/find-room/find-room.component.scss b/pokeR/ClientApp/src/app/shared-components/find-room/find-room.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/pokeR/ClientApp/src/app/shared-components/find-room/find-room.component.spec.ts b/pokeR/ClientApp/src/app/shared-components/find-room/find-room.component.spec.ts
new file mode 100644
index 0000000..d2a1185
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/find-room/find-room.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FindRoomComponent } from './find-room.component';
+
+describe('FindRoomComponent', () => {
+ let component: FindRoomComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ FindRoomComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(FindRoomComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/pokeR/ClientApp/src/app/shared-components/find-room/find-room.component.ts b/pokeR/ClientApp/src/app/shared-components/find-room/find-room.component.ts
new file mode 100644
index 0000000..4a8ed7c
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/find-room/find-room.component.ts
@@ -0,0 +1,39 @@
+import { Component, OnInit, EventEmitter, Output } from '@angular/core';
+import { PokerService } from 'src/app/services/poker.service';
+import { Observable } from 'rxjs';
+import { debounceTime, flatMap, map } from 'rxjs/operators';
+import { FormGroup, FormControl, Validators } from '@angular/forms';
+
+@Component({
+ selector: 'app-find-room',
+ templateUrl: './find-room.component.html',
+ styleUrls: ['./find-room.component.scss']
+})
+export class FindRoomComponent implements OnInit {
+ @Output() exit: EventEmitter = new EventEmitter();
+ public exists: boolean = null;
+ form: FormGroup = new FormGroup({
+ roomId: new FormControl('', Validators.required)
+ });
+
+ constructor(
+ private service: PokerService
+ ) { }
+
+ ngOnInit() {
+ this.watchIdChanges().subscribe();
+ }
+
+ onSubmit = (): void => {
+ if (this.form.valid && this.exists) {
+ const id = this.form.get('roomId').value;
+ this.exit.emit(['/room', id]);
+ }
+ }
+
+ watchIdChanges = (): Observable => this.form.get('roomId').valueChanges.pipe(
+ debounceTime(300),
+ flatMap(this.service.checkAvailability),
+ map(r => this.exists = !r)
+ )
+}
diff --git a/pokeR/ClientApp/src/app/shared-components/host-controls/host-controls.component.html b/pokeR/ClientApp/src/app/shared-components/host-controls/host-controls.component.html
new file mode 100644
index 0000000..f49a7cb
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/host-controls/host-controls.component.html
@@ -0,0 +1,31 @@
+
+
+
+
{{currentTagline}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{getTimeText()}}
+
+
+
+
diff --git a/pokeR/ClientApp/src/app/shared-components/host-controls/host-controls.component.scss b/pokeR/ClientApp/src/app/shared-components/host-controls/host-controls.component.scss
new file mode 100644
index 0000000..23b93ad
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/host-controls/host-controls.component.scss
@@ -0,0 +1,71 @@
+@import "../../../theme/variables";
+
+.host-controls {
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 28;
+ animation: slide-down forwards 1s ease;
+
+ .current-tagline {
+ text-align: center;
+ font-size: 1.4rem;
+ margin-top: 0.3rem;
+ }
+
+ .control-bar {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+
+ & > .input-group {
+ margin-bottom: $some;
+ margin-top: $some;
+ margin-right: $some;
+ }
+
+ & > .subject--input {
+ flex-grow: 10;
+ }
+
+ & > .countdown--input {
+ min-width: 120px;
+ flex-grow: 1;
+ }
+
+ & > .btn {
+ margin-bottom: $some;
+ margin-top: $some;
+ $size: 46px;
+ height: $size;
+ width: $size;
+ margin-right: $some;
+ padding: $some;
+
+ & > i::before {
+ transform: scale(1.3) translateY(-3px);
+ }
+ }
+ }
+}
+
+@keyframes slide-down {
+ 0% {
+ transform: translateY(-100%);
+ opacity: 0;
+ }
+ 100% {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
+
+.countdown {
+ width: 100%;
+ & > .bar {
+ border-radius: $mid;
+ color: var(--contrast-color);
+ background-color: var(--accent-color);
+ text-align: center;
+ }
+}
diff --git a/pokeR/ClientApp/src/app/shared-components/host-controls/host-controls.component.spec.ts b/pokeR/ClientApp/src/app/shared-components/host-controls/host-controls.component.spec.ts
new file mode 100644
index 0000000..67447c1
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/host-controls/host-controls.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HostControlsComponent } from './host-controls.component';
+
+describe('HostControlsComponent', () => {
+ let component: HostControlsComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ HostControlsComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(HostControlsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/pokeR/ClientApp/src/app/shared-components/host-controls/host-controls.component.ts b/pokeR/ClientApp/src/app/shared-components/host-controls/host-controls.component.ts
new file mode 100644
index 0000000..0e1dc9d
--- /dev/null
+++ b/pokeR/ClientApp/src/app/shared-components/host-controls/host-controls.component.ts
@@ -0,0 +1,130 @@
+import { Component, OnInit } from '@angular/core';
+import { PokerService } from 'src/app/services/poker.service';
+import { Observable, Subscription, forkJoin } from 'rxjs';
+import { map, debounceTime, flatMap } from 'rxjs/operators';
+import { User } from 'src/app/models/entities/user';
+import { FormGroup, FormControl } from '@angular/forms';
+
+@Component({
+ selector: 'app-host-controls',
+ templateUrl: './host-controls.component.html',
+ styleUrls: ['./host-controls.component.scss']
+})
+export class HostControlsComponent implements OnInit {
+ currentTagline: string;
+ countdownIsActive = false;
+ remainingTime: number;
+ maxTime: number;
+ deadline: Date;
+ timer: number;
+ player: User;
+ formGroup: FormGroup = new FormGroup({
+ subject: new FormControl(''),
+ countdown: new FormControl('')
+ });
+
+ constructor(private service: PokerService) { }
+
+ ngOnInit() {
+ this.player = this.service.player;
+ this.initialize().subscribe();
+ this.monitorGameState().subscribe();
+ }
+
+ initialize = (): Observable => this.service.getTagline().pipe(map(t => this.currentTagline = t));
+
+ monitorGameState = (): Observable =>
+ forkJoin(
+ this.watchInputChange(),
+ this.watchTaglineChanges(),
+ this.watchRoundEnd(),
+ this.watchCountdownStart(),
+ this.watchHostChanges()
+ )
+
+ getRemainingTime = (): number => this.deadline ? this.deadline.getTime() - Date.now() : 0;
+
+ updateRemainingTime = (): void => {
+ const time = this.getRemainingTime();
+ if (time > 0) {
+ this.remainingTime = time;
+ this.timer = window.requestAnimationFrame(this.updateRemainingTime);
+ } else {
+ window.cancelAnimationFrame(this.timer);
+ }
+ }
+
+ getCountdownValue = (): number => {
+ const time = this.remainingTime;
+ if (time > 0 && this.maxTime) {
+ return time * 100 / this.maxTime;
+ }
+ return 0;
+ }
+
+ getTimeText = (): string => {
+ let result = '';
+ const value = this.remainingTime / 1000;
+ if (value >= 60) {
+ result += `${Math.floor(value / 60)}m `;
+ }
+ result += `${Math.floor(value % 60)}s`;
+ return result;
+ }
+
+ startCountdown = (): void => {
+ const countdownLength = Number(this.formGroup.get('countdown').value);
+ if (countdownLength) {
+ this.service.startTimer(1000 * countdownLength).subscribe();
+ this.countdownIsActive = true;
+ window.setTimeout(() => {
+ this.service.endRound().subscribe();
+ }, 1000 * countdownLength);
+ }
+ }
+
+ startNewRound = (): Subscription => this.service.startRound().subscribe();
+
+ watchInputChange = (): Observable => {
+ const source = this.formGroup.get('subject').valueChanges;
+ return forkJoin(
+ source.pipe(debounceTime(500), flatMap(this.service.storeTagline)),
+ source.pipe(flatMap(this.service.updateTagline))
+ );
+ };
+
+ watchTaglineChanges = (): Observable =>
+ this.service.taglineUpdated.pipe(map(t => {
+ if (!this.player.isHost) {
+ this.currentTagline = t;
+ }
+ }))
+
+ watchRoundEnd = (): Observable =>
+ this.service.roundEnds.pipe(map(() => {
+ this.countdownIsActive = false;
+ this.remainingTime = null;
+ this.maxTime = null;
+ this.formGroup.get('countdown').setValue('');
+ window.cancelAnimationFrame(this.timer);
+ }))
+
+ watchCountdownStart = (): Observable =>
+ this.service.timerStarts.pipe(map(t => {
+ if (t) {
+ this.maxTime = t;
+ this.remainingTime = t;
+ this.deadline = new Date(Date.now() + t);
+ this.countdownIsActive = true;
+ this.timer = window.requestAnimationFrame(this.updateRemainingTime);
+ }
+ }))
+
+ watchHostChanges = (): Observable =>
+ this.service.hostChanges.pipe(map(d => {
+ const playerMatch = d.collection.find(c => c.id === this.player.id);
+ if (playerMatch) {
+ this.player = playerMatch;
+ }
+ }))
+}
diff --git a/pokeR/ClientApp/src/app/shared-components/join-room/join-room.component.html b/pokeR/ClientApp/src/app/shared-components/join-room/join-room.component.html
index 06603df..13c9cc5 100644
--- a/pokeR/ClientApp/src/app/shared-components/join-room/join-room.component.html
+++ b/pokeR/ClientApp/src/app/shared-components/join-room/join-room.component.html
@@ -1,35 +1,29 @@
-