201 changed files with 42589 additions and 12322 deletions
@ -1,49 +1 @@ |
|||||
# See http://help.github.com/ignore-files/ for more about ignoring files. |
node_modules/ |
||||
|
|
||||
# Compiled output |
|
||||
/dist |
|
||||
/tmp |
|
||||
/out-tsc |
|
||||
/bazel-out |
|
||||
|
|
||||
# Node |
|
||||
/node_modules |
|
||||
npm-debug.log |
|
||||
yarn-error.log |
|
||||
|
|
||||
# IDEs and editors |
|
||||
.idea/ |
|
||||
.project |
|
||||
.classpath |
|
||||
.c9/ |
|
||||
*.launch |
|
||||
.settings/ |
|
||||
*.sublime-workspace |
|
||||
|
|
||||
# Visual Studio Code |
|
||||
.vscode/* |
|
||||
!.vscode/settings.json |
|
||||
!.vscode/tasks.json |
|
||||
!.vscode/launch.json |
|
||||
!.vscode/extensions.json |
|
||||
.history/* |
|
||||
|
|
||||
# Miscellaneous |
|
||||
/.angular/cache |
|
||||
.sass-cache/ |
|
||||
/connect.lock |
|
||||
/coverage |
|
||||
/libpeerconnection.log |
|
||||
testem.log |
|
||||
/typings |
|
||||
|
|
||||
# System files |
|
||||
.DS_Store |
|
||||
Thumbs.db |
|
||||
|
|
||||
# Uploads |
|
||||
/uploads |
|
||||
nedb-database/raceresults.db |
|
||||
nedb-database/racers.db |
|
||||
nedb-database/races.db |
|
||||
nedb-database/seasons.db |
|
||||
@ -1,20 +1,16 @@ |
|||||
{ |
{ |
||||
|
// Use IntelliSense to learn about possible attributes. |
||||
|
// Hover to view descriptions of existing attributes. |
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 |
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 |
||||
"version": "0.2.0", |
"version": "0.2.0", |
||||
"configurations": [ |
"configurations": [ |
||||
{ |
{ |
||||
|
"cwd": "${workspaceFolder}/pakcages/bridge-ui/", |
||||
"name": "ng serve", |
"name": "ng serve", |
||||
"type": "chrome", |
"type": "chrome", |
||||
"request": "launch", |
"request": "launch", |
||||
"preLaunchTask": "npm: start", |
"preLaunchTask": "npm: start", |
||||
"url": "http://localhost:4200/" |
"url": "http://localhost:4200/" |
||||
}, |
|
||||
{ |
|
||||
"name": "ng test", |
|
||||
"type": "chrome", |
|
||||
"request": "launch", |
|
||||
"preLaunchTask": "npm: test", |
|
||||
"url": "http://localhost:9876/debug.html" |
|
||||
} |
} |
||||
] |
] |
||||
} |
} |
||||
@ -1,15 +0,0 @@ |
|||||
const multer = require('multer'); |
|
||||
|
|
||||
const storage = multer.diskStorage({ |
|
||||
destination: (req, file, cb) => { |
|
||||
cb(null, 'uploads/'); |
|
||||
}, |
|
||||
filename: (req, file, cb) => { |
|
||||
cb(null, Date.now() + '-' + file.originalname); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
// Create the multer instance
|
|
||||
const upload = multer({ storage: storage}); |
|
||||
|
|
||||
module.exports = upload; |
|
||||
@ -1,78 +0,0 @@ |
|||||
class gbxHeader { |
|
||||
|
|
||||
has_magic = true |
|
||||
version = -1 |
|
||||
format = -1 |
|
||||
compressionofRefTable = -1 |
|
||||
compressionOfrefBofy = -1 |
|
||||
compressionTextFlag = '' |
|
||||
id = -1 |
|
||||
userData = [] |
|
||||
numNodes = 0 |
|
||||
|
|
||||
is_vaild = false |
|
||||
|
|
||||
parse(buff) |
|
||||
{ |
|
||||
let header_magic = buff.readString(3); |
|
||||
|
|
||||
if (header_magic != 'GBX') |
|
||||
{ |
|
||||
console.log("Header Magic Mismatch: " + header_magic); |
|
||||
return |
|
||||
} |
|
||||
this.has_magic = true |
|
||||
|
|
||||
this.version = buff.readUInt16(); |
|
||||
console.log(this.version) |
|
||||
|
|
||||
if (this.version < 5 || this.version >7 ) |
|
||||
{ |
|
||||
console.log("Unsupported version") |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
this.format = buff.readInt8(); |
|
||||
if (this.format != 66) |
|
||||
{ |
|
||||
console.log("Unsupported format") |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
this.compressionofRefTable = buff.readInt8(); |
|
||||
|
|
||||
if (this.compressionofRefTable != 67 && this.compressionofRefTable != 85) |
|
||||
{ |
|
||||
console.log("Unsupported compression format, Ref") |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
this.compressionofRefBody = buff.readInt8(); |
|
||||
if (this.compressionofRefBody != 67 && this.compressionofRefBody != 85) |
|
||||
{ |
|
||||
console.log("Unsupported compression format, Body") |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
this.compressionTextFlag = ''; |
|
||||
if (this.version >= 4) |
|
||||
{ |
|
||||
this.compressionTextFlag = buff.readString(1); |
|
||||
} |
|
||||
|
|
||||
this.id = buff.readUInt32(); |
|
||||
console.log(this.id) |
|
||||
|
|
||||
if (this.version >=6) |
|
||||
{ |
|
||||
this.userData = buff.readBytes(); |
|
||||
} |
|
||||
|
|
||||
this.numNodes = buff.readUInt32(); |
|
||||
|
|
||||
this.is_vaild = true |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
module.exports = gbxHeader; |
|
||||
@ -1,377 +0,0 @@ |
|||||
class gbxReplay { |
|
||||
|
|
||||
CHUNKS_OFFSET = 17; |
|
||||
HEADER_OFFSET = 21; |
|
||||
UNASSIGNED = 0xFFFFFFFF; |
|
||||
|
|
||||
mapUID = ""; |
|
||||
environment = ""; |
|
||||
author = ""; |
|
||||
bestTime = this.UNASSIGNED; |
|
||||
gamerHandle = "" |
|
||||
login = "" |
|
||||
titleId = "" |
|
||||
|
|
||||
calcChunkOffset(num) { |
|
||||
return (((num) * 8) + this.HEADER_OFFSET) |
|
||||
} |
|
||||
|
|
||||
isNumber(id) { return (((id) & 0xC0000000) == 0)} |
|
||||
isString(id) { return (((id) & 0xC0000000) != 0)} |
|
||||
isUnassigned(id) {return ((id) == this.UNASSIGNED)} |
|
||||
getIndex(id) {return ((id) & 0x3FFFFFFF)} |
|
||||
|
|
||||
parseReplayChunk(buff, chunkInfo) { |
|
||||
buff.seek(0+chunkInfo.dwOffset); |
|
||||
let version = buff.readUInt32(); |
|
||||
let isVSK = version >= 9999 ? true : false; |
|
||||
|
|
||||
let idList = { version: 0, index: 0, list:[]} |
|
||||
|
|
||||
if( chunkInfo.dwSize <= 4) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
if((!isVSK && version >= 3) || (isVSK && version >= 10000)) |
|
||||
{ |
|
||||
let result = this.readIdentifier(buff, idList); |
|
||||
if(result.read > 0) |
|
||||
{ |
|
||||
this.mapUID = result.str; |
|
||||
console.log("MapUID:\t " + this.mapUID) |
|
||||
} |
|
||||
|
|
||||
result = this.readIdentifier(buff, idList); |
|
||||
if(result.read > 0) |
|
||||
{ |
|
||||
this.environment = result.str; |
|
||||
console.log("Environ:\t " + this.environment) |
|
||||
} |
|
||||
|
|
||||
result = this.readIdentifier(buff, idList) |
|
||||
if (result.read > 0) |
|
||||
{ |
|
||||
this.author = result.str; |
|
||||
console.log("Author:\t " + this.author) |
|
||||
} |
|
||||
|
|
||||
this.bestTime = buff.readUInt32(); |
|
||||
console.log("BestTime:\t " + this.bestTime); |
|
||||
|
|
||||
result = buff.readNadeoString(); |
|
||||
if (result.read > 0) |
|
||||
{ |
|
||||
this.gamerHandle = result.str; |
|
||||
console.log("Gamer Handle:\t " + this.gamerHandle) |
|
||||
} |
|
||||
|
|
||||
if((!isVSK && version >= 6) || (isVSK && version >= 10000)) |
|
||||
{ |
|
||||
result = buff.readNadeoString(); |
|
||||
if (result.read > 0) |
|
||||
{ |
|
||||
this.login = result.str; |
|
||||
console.log("Login:\t " + this.login) |
|
||||
} |
|
||||
|
|
||||
if((!isVSK && version >= 8) || (isVSK && version >= 10000)) |
|
||||
{ |
|
||||
buff.seek(buff.readPos+1); |
|
||||
result = this.readIdentifier(buff, idList) |
|
||||
if (result.read > 0) |
|
||||
{ |
|
||||
this.titleId = result.str; |
|
||||
console.log("titleId:\t " + this.titleId) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
parseAuthorChunk(buff, chunkInfo) { |
|
||||
|
|
||||
} |
|
||||
|
|
||||
parseCommunityChunk(buff, chunkInfo) { |
|
||||
|
|
||||
} |
|
||||
|
|
||||
parse(buff) |
|
||||
{ |
|
||||
buff.seek(0+this.CHUNKS_OFFSET); |
|
||||
let numChunks = buff.readUInt32(); |
|
||||
|
|
||||
if(numChunks == 0) { |
|
||||
return true; |
|
||||
} |
|
||||
else if (numChunks > 0xff) { |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
let chunkId, chunkSize = 0; |
|
||||
let chunkOffset = this.calcChunkOffset(numChunks) |
|
||||
let chunkVersion = {} |
|
||||
let chunkCommunity = {} |
|
||||
let chunkAuthor = {} |
|
||||
|
|
||||
for (let counter = 1; counter <= numChunks; counter++) { |
|
||||
chunkId = buff.readUInt32(); |
|
||||
chunkSize = buff.readUInt32(); |
|
||||
|
|
||||
chunkSize &= 0x7FFFFFFF; |
|
||||
|
|
||||
switch (chunkId){ |
|
||||
case 0x03093000: // (TM)
|
|
||||
case 0x2403F000: // (VSK, TM)
|
|
||||
chunkVersion.dwId = chunkId; |
|
||||
chunkVersion.dwSize = chunkSize; |
|
||||
chunkVersion.dwOffset = chunkOffset; |
|
||||
chunkOffset += chunkSize; |
|
||||
break; |
|
||||
case 0x03093001: // (TM)
|
|
||||
case 0x2403F001: // (VSK, TM)
|
|
||||
chunkCommunity.dwId = chunkId; |
|
||||
chunkCommunity.dwSize = chunkSize; |
|
||||
chunkCommunity.dwOffset = chunkOffset; |
|
||||
chunkOffset += chunkSize; |
|
||||
//OutputTextFmt(hwndCtl, szOutput, _countof(szOutput), g_szChunk, dwCouter, dwChunkId, dwChunkSize);
|
|
||||
break; |
|
||||
|
|
||||
case 0x03093002: // (MP)
|
|
||||
chunkAuthor.dwId = chunkId; |
|
||||
chunkAuthor.dwSize = chunkSize; |
|
||||
chunkAuthor.dwOffset = chunkOffset; |
|
||||
chunkOffset += chunkSize; |
|
||||
//OutputTextFmt(hwndCtl, szOutput, _countof(szOutput), g_szChunk, dwCouter, dwChunkId, dwChunkSize);
|
|
||||
break; |
|
||||
|
|
||||
default: |
|
||||
chunkOffset += chunkSize; |
|
||||
//OutputTextFmt(hwndCtl, szOutput, _countof(szOutput), g_szChunk, dwCouter, dwChunkId, dwChunkSize);
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (chunkVersion.dwSize > 0) { |
|
||||
this.parseReplayChunk(buff, chunkVersion); |
|
||||
} |
|
||||
|
|
||||
if (chunkCommunity.dwSize > 0) { |
|
||||
this.parseCommunityChunk(buff, chunkCommunity); |
|
||||
} |
|
||||
|
|
||||
if (chunkAuthor.dwSize > 0) { |
|
||||
this.parseAuthorChunk(buff, chunkAuthor); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
readIdentifier(buff, idList) |
|
||||
{ |
|
||||
if ( idList.version < 3 ) |
|
||||
{ |
|
||||
idList.version = buff.readUInt32(); |
|
||||
|
|
||||
if (idList.version < 2) |
|
||||
{ |
|
||||
return {read:-1, str:""}; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
let id = buff.readUInt32(); |
|
||||
if(this.isUnassigned(id)) |
|
||||
{ |
|
||||
return {read:0, str:""}; |
|
||||
} |
|
||||
|
|
||||
if(this.isNumber(id)) |
|
||||
{ |
|
||||
return this.getCollectionString(id); |
|
||||
} |
|
||||
|
|
||||
if(idList.version == 2) |
|
||||
{ |
|
||||
return buff.readNadeoString(); |
|
||||
} |
|
||||
|
|
||||
if(this.isString(id) && this.getIndex(id) == 0) |
|
||||
{ |
|
||||
let result = buff.readNadeoString(); |
|
||||
|
|
||||
if (result.read > 0 && idList.index < 8) |
|
||||
{ |
|
||||
idList.list[idList.index] = result.str; |
|
||||
idList.index++; |
|
||||
} |
|
||||
|
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
let index = this.getIndex(id); |
|
||||
if( index == 0 || index > 8) |
|
||||
{ |
|
||||
return {read:0, str:""}; |
|
||||
} |
|
||||
|
|
||||
let latest = idList.list[idList.index-1]; |
|
||||
return {read:latest.length, str:latest}; |
|
||||
} |
|
||||
|
|
||||
getCollectionString(id) |
|
||||
{ |
|
||||
let outString = "" |
|
||||
switch (id) |
|
||||
{ |
|
||||
case 0: // Speed
|
|
||||
outString = "Desert"; |
|
||||
break; |
|
||||
case 1: // Alpine
|
|
||||
outString = "Snow"; |
|
||||
break; |
|
||||
case 2: // Rally
|
|
||||
outString = "Rally"; |
|
||||
break; |
|
||||
case 3: // Island
|
|
||||
outString = "Island"; |
|
||||
break; |
|
||||
case 4: // Bay
|
|
||||
outString = "Bay"; |
|
||||
break; |
|
||||
case 5: // Coast
|
|
||||
outString = "Coast"; |
|
||||
break; |
|
||||
case 6: // StadiumMP4
|
|
||||
outString = "Stadium"; |
|
||||
break; |
|
||||
case 7: // Basic
|
|
||||
outString = "Basic"; |
|
||||
break; |
|
||||
case 8: // Plain
|
|
||||
outString = "Plain"; |
|
||||
break; |
|
||||
case 9: // Moon
|
|
||||
outString = "Moon"; |
|
||||
break; |
|
||||
case 10: // Toy
|
|
||||
outString = "Toy"; |
|
||||
break; |
|
||||
case 11: // Valley
|
|
||||
outString = "Valley"; |
|
||||
break; |
|
||||
case 12: // Canyon
|
|
||||
outString = "Canyon"; |
|
||||
break; |
|
||||
case 13: // Lagoon
|
|
||||
outString = "Lagoon"; |
|
||||
break; |
|
||||
case 14: // Deprecated_Arena
|
|
||||
outString = "Arena"; |
|
||||
break; |
|
||||
case 15: // TMTest8
|
|
||||
outString = "TMTest8"; |
|
||||
break; |
|
||||
case 16: // TMTest9
|
|
||||
outString = "TMTest9"; |
|
||||
break; |
|
||||
case 17: // TMCommon
|
|
||||
outString = "TMCommon"; |
|
||||
break; |
|
||||
case 18: // Canyon4
|
|
||||
outString = "Canyon4"; |
|
||||
break; |
|
||||
case 19: // Canyon256
|
|
||||
outString = "Canyon256"; |
|
||||
break; |
|
||||
case 20: // Valley4
|
|
||||
outString = "Valley4"; |
|
||||
break; |
|
||||
case 21: // Valley256
|
|
||||
outString = "Valley256"; |
|
||||
break; |
|
||||
case 22: // Lagoon4
|
|
||||
outString = "Lagoon4"; |
|
||||
break; |
|
||||
case 23: // Lagoon256
|
|
||||
outString = "Lagoon256"; |
|
||||
break; |
|
||||
case 24: // Stadium4
|
|
||||
outString = "Stadium4"; |
|
||||
break; |
|
||||
case 25: // Stadium256
|
|
||||
outString = "Stadium256"; |
|
||||
break; |
|
||||
case 26: // Stadium
|
|
||||
outString = "Stadium"; |
|
||||
break; |
|
||||
case 27: // Voxel
|
|
||||
outString = "Voxel"; |
|
||||
break; |
|
||||
case 100: // History
|
|
||||
outString = "History"; |
|
||||
break; |
|
||||
case 101: // Society
|
|
||||
outString = "Society"; |
|
||||
break; |
|
||||
case 102: // Galaxy
|
|
||||
outString = "Galaxy"; |
|
||||
break; |
|
||||
case 103: // QMTest1
|
|
||||
outString = "QMTest1"; |
|
||||
break; |
|
||||
case 104: // QMTest2
|
|
||||
outString = "QMTest2"; |
|
||||
break; |
|
||||
case 105: // QMTest3
|
|
||||
outString = "QMTest3"; |
|
||||
break; |
|
||||
case 200: // Gothic
|
|
||||
outString = "Gothic"; |
|
||||
break; |
|
||||
case 201: // Paris
|
|
||||
outString = "Paris"; |
|
||||
break; |
|
||||
case 202: // Storm
|
|
||||
outString = "Storm"; |
|
||||
break; |
|
||||
case 203: // Cryo
|
|
||||
outString = "Cryo"; |
|
||||
break; |
|
||||
case 204: // Meteor
|
|
||||
outString = "Meteor"; |
|
||||
break; |
|
||||
case 205: // Meteor4
|
|
||||
outString = "Meteor4"; |
|
||||
break; |
|
||||
case 206: // Meteor256
|
|
||||
outString = "Meteor256"; |
|
||||
break; |
|
||||
case 207: // SMTest3
|
|
||||
outString = "SMTest3"; |
|
||||
break; |
|
||||
case 299: // SMCommon
|
|
||||
outString = "SMCommon"; |
|
||||
break; |
|
||||
case 10000: // Vehicles
|
|
||||
outString = "Vehicles"; |
|
||||
break; |
|
||||
case 10001: // Orbital
|
|
||||
outString = "Orbital"; |
|
||||
break; |
|
||||
case 10002: // Actors
|
|
||||
outString = "Actors"; |
|
||||
break; |
|
||||
case 10003: // Common
|
|
||||
outString = "Common"; |
|
||||
break; |
|
||||
case UNASSIGNED: |
|
||||
outString = "_Unassigned"; |
|
||||
break; |
|
||||
default: |
|
||||
{ |
|
||||
return {read:0, str:""}; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return {read:outString.length, str:outString} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
module.exports = gbxReplay; |
|
||||
@ -1,70 +0,0 @@ |
|||||
class Advancablebuffer { |
|
||||
readPos = 0; |
|
||||
internalBuffer; |
|
||||
littleEndian = true |
|
||||
|
|
||||
constructor(buffer, useBigEndian) |
|
||||
{ |
|
||||
this.internalBuffer = buffer; |
|
||||
this.readPos = 0; |
|
||||
this.littleEndian = !useBigEndian; |
|
||||
} |
|
||||
|
|
||||
seek(seekPos) |
|
||||
{ |
|
||||
this.readPos = seekPos |
|
||||
} |
|
||||
|
|
||||
readString(numBytes) |
|
||||
{ |
|
||||
let t = this.internalBuffer.subarray(this.readPos, this.readPos+=numBytes); |
|
||||
return t.toString(); |
|
||||
} |
|
||||
|
|
||||
readInt8() |
|
||||
{ |
|
||||
let value = this.internalBuffer.readInt8(this.readPos); |
|
||||
this.readPos += 1; |
|
||||
return value; |
|
||||
} |
|
||||
|
|
||||
readUInt16() |
|
||||
{ |
|
||||
let value = this.littleEndian ? this.internalBuffer.readUInt16LE(this.readPos) : this.internalBuffer.readUInt16BE(this.readPos); |
|
||||
this.readPos += 2; |
|
||||
return value; |
|
||||
} |
|
||||
|
|
||||
readUInt32() |
|
||||
{ |
|
||||
let value = this.littleEndian ? this.internalBuffer.readUInt32LE(this.readPos) : this.internalBuffer.readUInt32BE(this.readPos); |
|
||||
this.readPos += 4; |
|
||||
return value; |
|
||||
} |
|
||||
|
|
||||
readBytes() |
|
||||
{ |
|
||||
let length = this.readUInt32() |
|
||||
let sub = this.internalBuffer.subarray(this.readPos, this.readPos+=length); |
|
||||
return sub; |
|
||||
} |
|
||||
|
|
||||
readNadeoString() |
|
||||
{ |
|
||||
let length = this.readUInt32() |
|
||||
if(length >= 0xFFFF) |
|
||||
{ |
|
||||
return {read:-1, str:""}; |
|
||||
} |
|
||||
|
|
||||
if (length == 0) |
|
||||
{ |
|
||||
return {read:0, str:""}; |
|
||||
} |
|
||||
|
|
||||
let outString = this.readString(length); |
|
||||
return {read:outString.length, str:outString}; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
module.exports = Advancablebuffer; |
|
||||
@ -1,6 +0,0 @@ |
|||||
function readStringAndAdvance(buffer, start, numBytes) |
|
||||
{ |
|
||||
let pos = start |
|
||||
let string = buffer.subarray(pos, pos+=numBytes); |
|
||||
return string.toString(), pos; |
|
||||
} |
|
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json", |
||||
|
"version": "0.0.0" |
||||
|
} |
||||
File diff suppressed because it is too large
@ -1,59 +1,20 @@ |
|||||
{ |
{ |
||||
"name": "bridge", |
"name": "bridge", |
||||
"version": "0.0.0", |
"version": "1.0.0", |
||||
|
"description": "", |
||||
|
"main": "index.js", |
||||
"scripts": { |
"scripts": { |
||||
"ng": "ng", |
"test": "echo \"Error: no test specified\" && exit 1" |
||||
"start": "ng serve", |
|
||||
"build": "ng build", |
|
||||
"watch": "ng build --watch --configuration development", |
|
||||
"test": "ng test", |
|
||||
"start:server": "nodemon server.js" |
|
||||
}, |
|
||||
"private": true, |
|
||||
"dependencies": { |
|
||||
"@angular/animations": "^16.2.0", |
|
||||
"@angular/cdk": "^16.2.11", |
|
||||
"@angular/common": "^16.2.0", |
|
||||
"@angular/compiler": "^16.2.0", |
|
||||
"@angular/core": "^16.2.0", |
|
||||
"@angular/forms": "^16.2.0", |
|
||||
"@angular/material": "^16.2.11", |
|
||||
"@angular/platform-browser": "^16.2.0", |
|
||||
"@angular/platform-browser-dynamic": "^16.2.0", |
|
||||
"@angular/router": "^16.2.0", |
|
||||
"@fortawesome/fontawesome-free": "^6.4.2", |
|
||||
"@types/jwt-decode": "^3.1.0", |
|
||||
"axios": "^1.6.2", |
|
||||
"better-sqlite3": "^9.1.1", |
|
||||
"camo": "^0.12.4", |
|
||||
"cookie-parser": "^1.4.6", |
|
||||
"cors": "^2.8.5", |
|
||||
"express": "^4.18.2", |
|
||||
"jsonwebtoken": "^9.0.2", |
|
||||
"jwt-decode": "^4.0.0", |
|
||||
"mdb-angular-ui-kit": "^5.1.0", |
|
||||
"moment": "^2.29.4", |
|
||||
"multer": "^1.4.5-lts.1", |
|
||||
"nedb": "^1.8.0", |
|
||||
"node-sass": "^9.0.0", |
|
||||
"nodemon": "^3.0.1", |
|
||||
"rxjs": "~7.8.0", |
|
||||
"sass-loader": "^13.3.2", |
|
||||
"trackmania.io": "^3.2.2", |
|
||||
"tslib": "^2.3.0", |
|
||||
"zone.js": "~0.13.0" |
|
||||
}, |
}, |
||||
|
"keywords": [], |
||||
|
"author": "", |
||||
|
"license": "ISC", |
||||
"devDependencies": { |
"devDependencies": { |
||||
"@angular-devkit/build-angular": "^16.2.9", |
"lerna": "^7.4.2" |
||||
"@angular/cli": "^16.2.9", |
}, |
||||
"@angular/compiler-cli": "^16.2.0", |
"workspaces": [ |
||||
"@types/jasmine": "~4.3.0", |
"packages/bridge-server", |
||||
"jasmine-core": "~4.6.0", |
"packages/bridge-ui", |
||||
"karma": "~6.4.0", |
"packages/bridge-shared" |
||||
"karma-chrome-launcher": "~3.2.0", |
] |
||||
"karma-coverage": "~2.2.0", |
|
||||
"karma-jasmine": "~5.1.0", |
|
||||
"karma-jasmine-html-reporter": "~2.1.0", |
|
||||
"typescript": "~5.1.3" |
|
||||
} |
|
||||
} |
} |
||||
|
|||||
@ -0,0 +1,25 @@ |
|||||
|
module.exports = { |
||||
|
parser: '@typescript-eslint/parser', |
||||
|
parserOptions: { |
||||
|
project: 'tsconfig.json', |
||||
|
tsconfigRootDir: __dirname, |
||||
|
sourceType: 'module', |
||||
|
}, |
||||
|
plugins: ['@typescript-eslint/eslint-plugin'], |
||||
|
extends: [ |
||||
|
'plugin:@typescript-eslint/recommended', |
||||
|
'plugin:prettier/recommended', |
||||
|
], |
||||
|
root: true, |
||||
|
env: { |
||||
|
node: true, |
||||
|
jest: true, |
||||
|
}, |
||||
|
ignorePatterns: ['.eslintrc.js'], |
||||
|
rules: { |
||||
|
'@typescript-eslint/interface-name-prefix': 'off', |
||||
|
'@typescript-eslint/explicit-function-return-type': 'off', |
||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off', |
||||
|
'@typescript-eslint/no-explicit-any': 'off', |
||||
|
}, |
||||
|
}; |
||||
@ -0,0 +1,35 @@ |
|||||
|
# compiled output |
||||
|
/dist |
||||
|
/node_modules |
||||
|
|
||||
|
# Logs |
||||
|
logs |
||||
|
*.log |
||||
|
npm-debug.log* |
||||
|
pnpm-debug.log* |
||||
|
yarn-debug.log* |
||||
|
yarn-error.log* |
||||
|
lerna-debug.log* |
||||
|
|
||||
|
# OS |
||||
|
.DS_Store |
||||
|
|
||||
|
# Tests |
||||
|
/coverage |
||||
|
/.nyc_output |
||||
|
|
||||
|
# IDEs and editors |
||||
|
/.idea |
||||
|
.project |
||||
|
.classpath |
||||
|
.c9/ |
||||
|
*.launch |
||||
|
.settings/ |
||||
|
*.sublime-workspace |
||||
|
|
||||
|
# IDE - VSCode |
||||
|
.vscode/* |
||||
|
!.vscode/settings.json |
||||
|
!.vscode/tasks.json |
||||
|
!.vscode/launch.json |
||||
|
!.vscode/extensions.json |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"singleQuote": true, |
||||
|
"trailingComma": "all" |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
<p align="center"> |
||||
|
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a> |
||||
|
</p> |
||||
|
|
||||
|
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 |
||||
|
[circleci-url]: https://circleci.com/gh/nestjs/nest |
||||
|
|
||||
|
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p> |
||||
|
<p align="center"> |
||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a> |
||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a> |
||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a> |
||||
|
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a> |
||||
|
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a> |
||||
|
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a> |
||||
|
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a> |
||||
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a> |
||||
|
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a> |
||||
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a> |
||||
|
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a> |
||||
|
</p> |
||||
|
<!--[](https://opencollective.com/nest#backer) |
||||
|
[](https://opencollective.com/nest#sponsor)--> |
||||
|
|
||||
|
## Description |
||||
|
|
||||
|
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. |
||||
|
|
||||
|
## Installation |
||||
|
|
||||
|
```bash |
||||
|
$ npm install |
||||
|
``` |
||||
|
|
||||
|
## Running the app |
||||
|
|
||||
|
```bash |
||||
|
# development |
||||
|
$ npm run start |
||||
|
|
||||
|
# watch mode |
||||
|
$ npm run start:dev |
||||
|
|
||||
|
# production mode |
||||
|
$ npm run start:prod |
||||
|
``` |
||||
|
|
||||
|
## Test |
||||
|
|
||||
|
```bash |
||||
|
# unit tests |
||||
|
$ npm run test |
||||
|
|
||||
|
# e2e tests |
||||
|
$ npm run test:e2e |
||||
|
|
||||
|
# test coverage |
||||
|
$ npm run test:cov |
||||
|
``` |
||||
|
|
||||
|
## Support |
||||
|
|
||||
|
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). |
||||
|
|
||||
|
## Stay in touch |
||||
|
|
||||
|
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) |
||||
|
- Website - [https://nestjs.com](https://nestjs.com/) |
||||
|
- Twitter - [@nestframework](https://twitter.com/nestframework) |
||||
|
|
||||
|
## License |
||||
|
|
||||
|
Nest is [MIT licensed](LICENSE). |
||||
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"$schema": "https://json.schemastore.org/nest-cli", |
||||
|
"collection": "@nestjs/schematics", |
||||
|
"sourceRoot": "src", |
||||
|
"compilerOptions": { |
||||
|
"deleteOutDir": true |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,76 @@ |
|||||
|
{ |
||||
|
"name": "bridge-server", |
||||
|
"version": "0.0.1", |
||||
|
"description": "", |
||||
|
"author": "", |
||||
|
"private": true, |
||||
|
"license": "UNLICENSED", |
||||
|
"scripts": { |
||||
|
"build": "nest build", |
||||
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", |
||||
|
"start": "nest start", |
||||
|
"start:dev": "nest start --watch", |
||||
|
"start:debug": "nest start --debug --watch", |
||||
|
"start:prod": "node dist/main", |
||||
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", |
||||
|
"test": "jest", |
||||
|
"test:watch": "jest --watch", |
||||
|
"test:cov": "jest --coverage", |
||||
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", |
||||
|
"test:e2e": "jest --config ./test/jest-e2e.json" |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"@nestjs/common": "^10.0.0", |
||||
|
"@nestjs/core": "^10.0.0", |
||||
|
"@nestjs/jwt": "^10.2.0", |
||||
|
"@nestjs/platform-express": "^10.0.0", |
||||
|
"@nestjs/sequelize": "^10.0.0", |
||||
|
"bridge-shared": "^1.0.0", |
||||
|
"reflect-metadata": "^0.1.13", |
||||
|
"rxjs": "^7.8.1", |
||||
|
"sequelize": "^6.35.1", |
||||
|
"sequelize-typescript": "^2.1.5", |
||||
|
"sqlite3": "^5.1.6" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@nestjs/cli": "^10.0.0", |
||||
|
"@nestjs/schematics": "^10.0.0", |
||||
|
"@nestjs/testing": "^10.0.0", |
||||
|
"@types/express": "^4.17.17", |
||||
|
"@types/jest": "^29.5.2", |
||||
|
"@types/node": "^20.3.1", |
||||
|
"@types/sequelize": "^4.28.18", |
||||
|
"@types/supertest": "^2.0.12", |
||||
|
"@typescript-eslint/eslint-plugin": "^6.0.0", |
||||
|
"@typescript-eslint/parser": "^6.0.0", |
||||
|
"eslint": "^8.42.0", |
||||
|
"eslint-config-prettier": "^9.0.0", |
||||
|
"eslint-plugin-prettier": "^5.0.0", |
||||
|
"jest": "^29.5.0", |
||||
|
"prettier": "^3.0.0", |
||||
|
"source-map-support": "^0.5.21", |
||||
|
"supertest": "^6.3.3", |
||||
|
"ts-jest": "^29.1.0", |
||||
|
"ts-loader": "^9.4.3", |
||||
|
"ts-node": "^10.9.1", |
||||
|
"tsconfig-paths": "^4.2.0", |
||||
|
"typescript": "^5.1.3" |
||||
|
}, |
||||
|
"jest": { |
||||
|
"moduleFileExtensions": [ |
||||
|
"js", |
||||
|
"json", |
||||
|
"ts" |
||||
|
], |
||||
|
"rootDir": "src", |
||||
|
"testRegex": ".*\\.spec\\.ts$", |
||||
|
"transform": { |
||||
|
"^.+\\.(t|j)s$": "ts-jest" |
||||
|
}, |
||||
|
"collectCoverageFrom": [ |
||||
|
"**/*.(t|j)s" |
||||
|
], |
||||
|
"coverageDirectory": "../coverage", |
||||
|
"testEnvironment": "node" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
import { Test, TestingModule } from '@nestjs/testing'; |
||||
|
import { AppController } from './app.controller'; |
||||
|
import { AppService } from './app.service'; |
||||
|
|
||||
|
describe('AppController', () => { |
||||
|
let appController: AppController; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
const app: TestingModule = await Test.createTestingModule({ |
||||
|
controllers: [AppController], |
||||
|
providers: [AppService], |
||||
|
}).compile(); |
||||
|
|
||||
|
appController = app.get<AppController>(AppController); |
||||
|
}); |
||||
|
|
||||
|
describe('root', () => { |
||||
|
it('should return "Hello World!"', () => { |
||||
|
expect(appController.getHello()).toBe('Hello World!'); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,12 @@ |
|||||
|
import { Controller, Get } from '@nestjs/common'; |
||||
|
import { AppService } from './app.service'; |
||||
|
|
||||
|
@Controller() |
||||
|
export class AppController { |
||||
|
constructor(private readonly appService: AppService) {} |
||||
|
|
||||
|
@Get() |
||||
|
getHello(): string { |
||||
|
return this.appService.getHello(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common'; |
||||
|
import { AppController } from './app.controller'; |
||||
|
import { AppService } from './app.service'; |
||||
|
|
||||
|
import { SequelizeModule } from '@nestjs/sequelize'; |
||||
|
import { AuthModule } from './auth/auth.module'; |
||||
|
import { UsersService } from './users/users.service'; |
||||
|
import { UsersModule } from './users/users.module'; |
||||
|
|
||||
|
import { LoggerMiddleware } from './auth/logger.middleware'; |
||||
|
import { SeasonsController } from './seasons/seasons.controller'; |
||||
|
|
||||
|
@Module({ |
||||
|
imports: [ |
||||
|
SequelizeModule.forRoot({ |
||||
|
dialect: 'sqlite', |
||||
|
storage: 'data/data.db', |
||||
|
autoLoadModels: true, |
||||
|
synchronize: true, |
||||
|
}), |
||||
|
AuthModule, |
||||
|
UsersModule, |
||||
|
], |
||||
|
controllers: [AppController, SeasonsController], |
||||
|
providers: [AppService, UsersService], |
||||
|
}) |
||||
|
export class AppModule { |
||||
|
configure(consumer: MiddlewareConsumer) { |
||||
|
consumer |
||||
|
.apply(LoggerMiddleware) |
||||
|
.forRoutes({ path: '*', method: RequestMethod.ALL }); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
import { Injectable } from '@nestjs/common'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class AppService { |
||||
|
getHello(): string { |
||||
|
return 'Hello World!'; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
import { Test, TestingModule } from '@nestjs/testing'; |
||||
|
import { AuthController } from './auth.controller'; |
||||
|
|
||||
|
describe('AuthController', () => { |
||||
|
let controller: AuthController; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
const module: TestingModule = await Test.createTestingModule({ |
||||
|
controllers: [AuthController], |
||||
|
}).compile(); |
||||
|
|
||||
|
controller = module.get<AuthController>(AuthController); |
||||
|
}); |
||||
|
|
||||
|
it('should be defined', () => { |
||||
|
expect(controller).toBeDefined(); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,30 @@ |
|||||
|
import { |
||||
|
Body, |
||||
|
Controller, |
||||
|
Get, |
||||
|
HttpCode, |
||||
|
HttpStatus, |
||||
|
Post, |
||||
|
Request, |
||||
|
UseGuards |
||||
|
} from '@nestjs/common'; |
||||
|
import { AuthGuard } from './auth.guard'; |
||||
|
import { AuthService } from './auth.service'; |
||||
|
|
||||
|
@Controller('auth') |
||||
|
export class AuthController { |
||||
|
constructor(private authService: AuthService) {} |
||||
|
|
||||
|
@HttpCode(HttpStatus.OK) |
||||
|
@Post('login') |
||||
|
signIn(@Body() signInDto: Record<string, any>) { |
||||
|
return this.authService.signIn(signInDto.username, signInDto.password); |
||||
|
} |
||||
|
|
||||
|
@UseGuards(AuthGuard) |
||||
|
@Get('profile') |
||||
|
getProfile(@Request() req) { |
||||
|
console.log(req.user); |
||||
|
return req.user; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
import { |
||||
|
CanActivate, |
||||
|
ExecutionContext, |
||||
|
Injectable, |
||||
|
UnauthorizedException, |
||||
|
} from '@nestjs/common'; |
||||
|
import { JwtService } from '@nestjs/jwt'; |
||||
|
import { jwtConstants } from './constants'; |
||||
|
import { Request } from 'express'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class AuthGuard implements CanActivate { |
||||
|
constructor(private jwtService: JwtService) {} |
||||
|
|
||||
|
async canActivate(context: ExecutionContext): Promise<boolean> { |
||||
|
const request = context.switchToHttp().getRequest(); |
||||
|
const token = this.extractTokenFromHeader(request); |
||||
|
if (!token) { |
||||
|
throw new UnauthorizedException(); |
||||
|
} |
||||
|
try { |
||||
|
const payload = await this.jwtService.verifyAsync( |
||||
|
token, |
||||
|
{ |
||||
|
secret: jwtConstants.secret |
||||
|
} |
||||
|
); |
||||
|
// 💡 We're assigning the payload to the request object here
|
||||
|
// so that we can access it in our route handlers
|
||||
|
request['user'] = payload; |
||||
|
} catch { |
||||
|
throw new UnauthorizedException(); |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private extractTokenFromHeader(request: Request): string | undefined { |
||||
|
const [type, token] = request.headers.authorization?.split(' ') ?? []; |
||||
|
return type === 'Bearer' ? token : undefined; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
import { Module } from '@nestjs/common'; |
||||
|
import { JwtModule } from '@nestjs/jwt'; |
||||
|
import { AuthController } from './auth.controller'; |
||||
|
import { AuthService } from './auth.service'; |
||||
|
import { UsersModule } from '../users/users.module'; |
||||
|
import { jwtConstants } from './constants'; |
||||
|
|
||||
|
@Module({ |
||||
|
imports: [ |
||||
|
UsersModule, |
||||
|
JwtModule.register({ |
||||
|
global: true, |
||||
|
secret: jwtConstants.secret, |
||||
|
signOptions: { expiresIn: '1h' }, |
||||
|
}), |
||||
|
], |
||||
|
controllers: [AuthController], |
||||
|
providers: [AuthService] |
||||
|
}) |
||||
|
export class AuthModule {} |
||||
@ -0,0 +1,18 @@ |
|||||
|
import { Test, TestingModule } from '@nestjs/testing'; |
||||
|
import { AuthService } from './auth.service'; |
||||
|
|
||||
|
describe('AuthService', () => { |
||||
|
let service: AuthService; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
const module: TestingModule = await Test.createTestingModule({ |
||||
|
providers: [AuthService], |
||||
|
}).compile(); |
||||
|
|
||||
|
service = module.get<AuthService>(AuthService); |
||||
|
}); |
||||
|
|
||||
|
it('should be defined', () => { |
||||
|
expect(service).toBeDefined(); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,24 @@ |
|||||
|
import { Injectable, UnauthorizedException } from '@nestjs/common'; |
||||
|
import { JwtService } from '@nestjs/jwt'; |
||||
|
|
||||
|
import { UsersService } from '../users/users.service'; |
||||
|
|
||||
|
|
||||
|
@Injectable() |
||||
|
export class AuthService { |
||||
|
constructor( |
||||
|
private usersService: UsersService, |
||||
|
private jwtService: JwtService |
||||
|
) {} |
||||
|
|
||||
|
async signIn(username: string, pass: string): Promise<any> { |
||||
|
const user = await this.usersService.findOne(username); |
||||
|
if (user?.password !== pass) { |
||||
|
throw new UnauthorizedException(); |
||||
|
} |
||||
|
const payload = { sub: user.userId, username: user.username }; |
||||
|
return { |
||||
|
access_token: await this.jwtService.signAsync(payload), |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
export const jwtConstants = { |
||||
|
secret: 'ThisLittlePiggy', |
||||
|
}; |
||||
@ -0,0 +1,19 @@ |
|||||
|
import { Injectable, NestMiddleware, RequestMethod } from '@nestjs/common'; |
||||
|
import { Request, Response, NextFunction } from 'express'; |
||||
|
import { RouteInfo } from '@nestjs/common/interfaces'; |
||||
|
import { request } from 'http'; |
||||
|
@Injectable() |
||||
|
export class LoggerMiddleware implements NestMiddleware { |
||||
|
use(req: Request, res: Response, next: NextFunction) { |
||||
|
// Gets the request log
|
||||
|
console.log(`req:`, { |
||||
|
headers: req.headers, |
||||
|
body: req.body, |
||||
|
originalUrl: req.originalUrl, |
||||
|
}); |
||||
|
// Ends middleware function execution, hence allowing to move on
|
||||
|
if (next) { |
||||
|
next(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
import { NestFactory } from '@nestjs/core'; |
||||
|
import { AppModule } from './app.module'; |
||||
|
|
||||
|
async function bootstrap() { |
||||
|
const app = await NestFactory.create(AppModule); |
||||
|
app.enableCors(); |
||||
|
await app.listen(3000); |
||||
|
} |
||||
|
bootstrap(); |
||||
@ -0,0 +1,18 @@ |
|||||
|
import { Test, TestingModule } from '@nestjs/testing'; |
||||
|
import { SeasonsController } from './seasons.controller'; |
||||
|
|
||||
|
describe('SeasonsController', () => { |
||||
|
let controller: SeasonsController; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
const module: TestingModule = await Test.createTestingModule({ |
||||
|
controllers: [SeasonsController], |
||||
|
}).compile(); |
||||
|
|
||||
|
controller = module.get<SeasonsController>(SeasonsController); |
||||
|
}); |
||||
|
|
||||
|
it('should be defined', () => { |
||||
|
expect(controller).toBeDefined(); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,9 @@ |
|||||
|
import { Controller, Get } from '@nestjs/common'; |
||||
|
|
||||
|
@Controller('seasons') |
||||
|
export class SeasonsController { |
||||
|
@Get() |
||||
|
findAll(): string { |
||||
|
return '' |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
import { Module } from '@nestjs/common'; |
||||
|
import { UsersService } from './users.service'; |
||||
|
|
||||
|
@Module({ |
||||
|
providers: [UsersService], |
||||
|
exports: [UsersService], |
||||
|
}) |
||||
|
export class UsersModule {} |
||||
@ -0,0 +1,18 @@ |
|||||
|
import { Test, TestingModule } from '@nestjs/testing'; |
||||
|
import { UsersService } from './users.service'; |
||||
|
|
||||
|
describe('UsersService', () => { |
||||
|
let service: UsersService; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
const module: TestingModule = await Test.createTestingModule({ |
||||
|
providers: [UsersService], |
||||
|
}).compile(); |
||||
|
|
||||
|
service = module.get<UsersService>(UsersService); |
||||
|
}); |
||||
|
|
||||
|
it('should be defined', () => { |
||||
|
expect(service).toBeDefined(); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,24 @@ |
|||||
|
import { Injectable } from '@nestjs/common'; |
||||
|
|
||||
|
// This should be a real class/interface representing a user entity
|
||||
|
export type User = any; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class UsersService { |
||||
|
private readonly users = [ |
||||
|
{ |
||||
|
userId: 1, |
||||
|
username: 'john', |
||||
|
password: 'changeme', |
||||
|
}, |
||||
|
{ |
||||
|
userId: 2, |
||||
|
username: 'maria', |
||||
|
password: 'guess', |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
async findOne(username: string): Promise<User | undefined> { |
||||
|
return this.users.find(user => user.username === username); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
import { Test, TestingModule } from '@nestjs/testing'; |
||||
|
import { INestApplication } from '@nestjs/common'; |
||||
|
import * as request from 'supertest'; |
||||
|
import { AppModule } from './../src/app.module'; |
||||
|
|
||||
|
describe('AppController (e2e)', () => { |
||||
|
let app: INestApplication; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
const moduleFixture: TestingModule = await Test.createTestingModule({ |
||||
|
imports: [AppModule], |
||||
|
}).compile(); |
||||
|
|
||||
|
app = moduleFixture.createNestApplication(); |
||||
|
await app.init(); |
||||
|
}); |
||||
|
|
||||
|
it('/ (GET)', () => { |
||||
|
return request(app.getHttpServer()) |
||||
|
.get('/') |
||||
|
.expect(200) |
||||
|
.expect('Hello World!'); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,9 @@ |
|||||
|
{ |
||||
|
"moduleFileExtensions": ["js", "json", "ts"], |
||||
|
"rootDir": ".", |
||||
|
"testEnvironment": "node", |
||||
|
"testRegex": ".e2e-spec.ts$", |
||||
|
"transform": { |
||||
|
"^.+\\.(t|j)s$": "ts-jest" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"extends": "./tsconfig.json", |
||||
|
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"] |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
{ |
||||
|
"compilerOptions": { |
||||
|
"module": "commonjs", |
||||
|
"declaration": true, |
||||
|
"removeComments": true, |
||||
|
"emitDecoratorMetadata": true, |
||||
|
"experimentalDecorators": true, |
||||
|
"allowSyntheticDefaultImports": true, |
||||
|
"target": "ES2021", |
||||
|
"sourceMap": true, |
||||
|
"outDir": "./dist", |
||||
|
"baseUrl": "./", |
||||
|
"incremental": true, |
||||
|
"skipLibCheck": true, |
||||
|
"strictNullChecks": false, |
||||
|
"noImplicitAny": false, |
||||
|
"strictBindCallApply": false, |
||||
|
"forceConsistentCasingInFileNames": false, |
||||
|
"noFallthroughCasesInSwitch": false |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"name": "bridge-shared", |
||||
|
"version": "1.0.0", |
||||
|
"description": "", |
||||
|
"main": "index.js", |
||||
|
"devDependencies": {}, |
||||
|
"scripts": { |
||||
|
"test": "echo \"Error: no test specified\" && exit 1" |
||||
|
}, |
||||
|
"author": "", |
||||
|
"license": "ISC" |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files. |
||||
|
|
||||
|
# Compiled output |
||||
|
/dist |
||||
|
/tmp |
||||
|
/out-tsc |
||||
|
/bazel-out |
||||
|
|
||||
|
# Node |
||||
|
/node_modules |
||||
|
npm-debug.log |
||||
|
yarn-error.log |
||||
|
|
||||
|
# IDEs and editors |
||||
|
.idea/ |
||||
|
.project |
||||
|
.classpath |
||||
|
.c9/ |
||||
|
*.launch |
||||
|
.settings/ |
||||
|
*.sublime-workspace |
||||
|
|
||||
|
# Visual Studio Code |
||||
|
.vscode/* |
||||
|
!.vscode/settings.json |
||||
|
!.vscode/tasks.json |
||||
|
!.vscode/launch.json |
||||
|
!.vscode/extensions.json |
||||
|
.history/* |
||||
|
|
||||
|
# Miscellaneous |
||||
|
/.angular/cache |
||||
|
.sass-cache/ |
||||
|
/connect.lock |
||||
|
/coverage |
||||
|
/libpeerconnection.log |
||||
|
testem.log |
||||
|
/typings |
||||
|
|
||||
|
# System files |
||||
|
.DS_Store |
||||
|
Thumbs.db |
||||
@ -0,0 +1,20 @@ |
|||||
|
{ |
||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 |
||||
|
"version": "0.2.0", |
||||
|
"configurations": [ |
||||
|
{ |
||||
|
"name": "ng serve", |
||||
|
"type": "chrome", |
||||
|
"request": "launch", |
||||
|
"preLaunchTask": "npm: start", |
||||
|
"url": "http://localhost:4200/" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "ng test", |
||||
|
"type": "chrome", |
||||
|
"request": "launch", |
||||
|
"preLaunchTask": "npm: test", |
||||
|
"url": "http://localhost:9876/debug.html" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
@ -1,6 +1,6 @@ |
|||||
# Bridge |
# BridgeUi |
||||
|
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.2.9. |
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.1. |
||||
|
|
||||
## Development server |
## Development server |
||||
|
|
||||
@ -0,0 +1,42 @@ |
|||||
|
{ |
||||
|
"name": "bridge-ui", |
||||
|
"version": "0.0.0", |
||||
|
"scripts": { |
||||
|
"ng": "ng", |
||||
|
"start": "ng serve", |
||||
|
"build": "ng build", |
||||
|
"watch": "ng build --watch --configuration development", |
||||
|
"test": "ng test" |
||||
|
}, |
||||
|
"private": true, |
||||
|
"dependencies": { |
||||
|
"@angular/animations": "^17.0.0", |
||||
|
"@angular/cdk": "^17.0.1", |
||||
|
"@angular/common": "^17.0.0", |
||||
|
"@angular/compiler": "^17.0.0", |
||||
|
"@angular/core": "^17.0.0", |
||||
|
"@angular/forms": "^17.0.0", |
||||
|
"@angular/material": "^17.0.1", |
||||
|
"@angular/platform-browser": "^17.0.0", |
||||
|
"@angular/platform-browser-dynamic": "^17.0.0", |
||||
|
"@angular/router": "^17.0.0", |
||||
|
"bridge-shared": "^1.0.0", |
||||
|
"jwt-decode": "^4.0.0", |
||||
|
"rxjs": "~7.8.0", |
||||
|
"tslib": "^2.3.0", |
||||
|
"zone.js": "~0.14.2" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@angular-devkit/build-angular": "^17.0.1", |
||||
|
"@angular/cli": "^17.0.1", |
||||
|
"@angular/compiler-cli": "^17.0.0", |
||||
|
"@types/jasmine": "~5.1.0", |
||||
|
"jasmine-core": "~5.1.0", |
||||
|
"karma": "~6.4.0", |
||||
|
"karma-chrome-launcher": "~3.2.0", |
||||
|
"karma-coverage": "~2.2.0", |
||||
|
"karma-jasmine": "~5.1.0", |
||||
|
"karma-jasmine-html-reporter": "~2.1.0", |
||||
|
"typescript": "~5.2.2" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
import { Component, OnInit } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { RouterOutlet } from '@angular/router'; |
||||
|
import { TopBarComponent } from './components/top-bar/top-bar.component'; |
||||
|
|
||||
|
import { ThemeService } from './services/theme.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-root', |
||||
|
standalone: true, |
||||
|
imports: [CommonModule, RouterOutlet, TopBarComponent], |
||||
|
templateUrl: './app.component.html', |
||||
|
styleUrl: './app.component.scss' |
||||
|
}) |
||||
|
export class AppComponent { |
||||
|
title = 'bridge-ui'; |
||||
|
defaultTheme = 'purple-green' |
||||
|
|
||||
|
constructor (private themeService: ThemeService) {} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.themeService.setTheme(this.defaultTheme); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
import { ApplicationConfig } from '@angular/core'; |
||||
|
import { provideRouter } from '@angular/router'; |
||||
|
|
||||
|
import { routes } from './app.routes'; |
||||
|
import { provideAnimations } from '@angular/platform-browser/animations'; |
||||
|
|
||||
|
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; |
||||
|
import { AuthInterceptor } from './interceptors/auth.interceptor'; |
||||
|
|
||||
|
export const appConfig: ApplicationConfig = { |
||||
|
providers: [ |
||||
|
provideRouter(routes), |
||||
|
provideAnimations(), |
||||
|
provideHttpClient( |
||||
|
withInterceptorsFromDi() |
||||
|
), |
||||
|
{provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true}, |
||||
|
] |
||||
|
}; |
||||
@ -0,0 +1,11 @@ |
|||||
|
import { Routes } from '@angular/router'; |
||||
|
import { SeasonsComponent } from './pages/seasons/seasons.component'; |
||||
|
import { SeasonDetailsComponent } from './pages/season-details/season-details.component'; |
||||
|
import { GettingStartedComponent } from './pages/getting-started/getting-started.component'; |
||||
|
|
||||
|
export const routes: Routes = [ |
||||
|
{ path: '', redirectTo: '/seasons', pathMatch:'full' }, |
||||
|
{ path: 'seasons', component: SeasonsComponent }, |
||||
|
{ path: 'seasons/:id', component: SeasonDetailsComponent }, |
||||
|
{ path: 'getting-started', component: GettingStartedComponent }, |
||||
|
]; |
||||
@ -0,0 +1 @@ |
|||||
|
<p>bottom-bar works!</p> |
||||
@ -0,0 +1,23 @@ |
|||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing'; |
||||
|
|
||||
|
import { BottomBarComponent } from './bottom-bar.component'; |
||||
|
|
||||
|
describe('BottomBarComponent', () => { |
||||
|
let component: BottomBarComponent; |
||||
|
let fixture: ComponentFixture<BottomBarComponent>; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
await TestBed.configureTestingModule({ |
||||
|
imports: [BottomBarComponent] |
||||
|
}) |
||||
|
.compileComponents(); |
||||
|
|
||||
|
fixture = TestBed.createComponent(BottomBarComponent); |
||||
|
component = fixture.componentInstance; |
||||
|
fixture.detectChanges(); |
||||
|
}); |
||||
|
|
||||
|
it('should create', () => { |
||||
|
expect(component).toBeTruthy(); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,13 @@ |
|||||
|
import { Component } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-bottom-bar', |
||||
|
standalone: true, |
||||
|
imports: [CommonModule], |
||||
|
templateUrl: './bottom-bar.component.html', |
||||
|
styleUrl: './bottom-bar.component.scss' |
||||
|
}) |
||||
|
export class BottomBarComponent { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
<div class="signInForm"> |
||||
|
<h1 mat-dialog-title>Login</h1> |
||||
|
<div mat-dialog-content> |
||||
|
<mat-form-field class="full-width"> |
||||
|
<mat-label>Username</mat-label> |
||||
|
<input matInput type="text" id="username" [formControl]="username" name="username" required> |
||||
|
</mat-form-field> |
||||
|
<br/> |
||||
|
<mat-form-field class="full-width"> |
||||
|
<mat-label>Password</mat-label> |
||||
|
<input matInput type="password" id="password" [formControl]="password" name="password" required> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<mat-dialog-actions align="end"> |
||||
|
<button mat-button mat-dialog-close>Cancel</button> |
||||
|
<button mat-button mat-dialog-close (click)="login()">Login</button> |
||||
|
</mat-dialog-actions> |
||||
|
</div> |
||||
@ -0,0 +1,12 @@ |
|||||
|
.signInForm { |
||||
|
width: 400px; |
||||
|
padding: 12px 24px 24px; |
||||
|
|
||||
|
igx-input-group + igx-input-group { |
||||
|
margin-top: 24px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.full-width { |
||||
|
width: 100%; |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing'; |
||||
|
|
||||
|
import { LoginDialogComponent } from './login-dialog.component'; |
||||
|
|
||||
|
describe('LoginDialogComponent', () => { |
||||
|
let component: LoginDialogComponent; |
||||
|
let fixture: ComponentFixture<LoginDialogComponent>; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
await TestBed.configureTestingModule({ |
||||
|
imports: [LoginDialogComponent] |
||||
|
}) |
||||
|
.compileComponents(); |
||||
|
|
||||
|
fixture = TestBed.createComponent(LoginDialogComponent); |
||||
|
component = fixture.componentInstance; |
||||
|
fixture.detectChanges(); |
||||
|
}); |
||||
|
|
||||
|
it('should create', () => { |
||||
|
expect(component).toBeTruthy(); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,60 @@ |
|||||
|
import { Component } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { ReactiveFormsModule, FormControl } from '@angular/forms'; |
||||
|
import { FormsModule } from '@angular/forms'; |
||||
|
|
||||
|
import { MatButtonModule } from '@angular/material/button'; |
||||
|
import { MatDialogModule } from '@angular/material/dialog'; |
||||
|
import { MatInputModule } from '@angular/material/input'; |
||||
|
import { MatFormFieldModule } from '@angular/material/form-field'; |
||||
|
|
||||
|
import { |
||||
|
MatDialog, |
||||
|
MAT_DIALOG_DATA, |
||||
|
MatDialogRef, |
||||
|
MatDialogTitle, |
||||
|
MatDialogContent, |
||||
|
MatDialogActions, |
||||
|
MatDialogClose, |
||||
|
} from '@angular/material/dialog'; |
||||
|
|
||||
|
import { AuthService } from '../../services/auth.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-login-dialog', |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
ReactiveFormsModule, |
||||
|
MatButtonModule, |
||||
|
MatDialogModule, |
||||
|
MatInputModule, |
||||
|
MatFormFieldModule, |
||||
|
FormsModule, |
||||
|
MatDialogTitle, |
||||
|
MatDialogContent, |
||||
|
MatDialogActions, |
||||
|
MatDialogClose, |
||||
|
], |
||||
|
templateUrl: './login-dialog.component.html', |
||||
|
styleUrl: './login-dialog.component.scss' |
||||
|
}) |
||||
|
export class LoginDialogComponent { |
||||
|
username = new FormControl(''); |
||||
|
password = new FormControl(''); |
||||
|
|
||||
|
constructor(private authService: AuthService) {} |
||||
|
|
||||
|
login(): void { |
||||
|
let username = this.username.value != undefined ? this.username.value : ""; |
||||
|
let password = this.password.value != undefined ? this.password.value : ""; |
||||
|
|
||||
|
this.authService.login(username, password).subscribe(data => { |
||||
|
console.log(data) |
||||
|
if (data.access_token) { |
||||
|
localStorage.setItem('token', data.access_token); |
||||
|
window.location.reload(); |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
@if( race != undefined ) { |
||||
|
<mat-grid-list cols="4" rowHeight="100px"> |
||||
|
<mat-grid-tile [colspan]="3" [rowspan]="1"><h1>{{race.mapName}}</h1></mat-grid-tile> |
||||
|
<mat-grid-tile [colspan]="1" [rowspan]="2"><img |
||||
|
src={{race.mapImgUrl}} |
||||
|
class="img-thumbnail shadow-2-strong" |
||||
|
/></mat-grid-tile> |
||||
|
<mat-grid-tile [colspan]="3" [rowspan]="1">Three</mat-grid-tile> |
||||
|
</mat-grid-list> |
||||
|
<br/> |
||||
|
<div> |
||||
|
<table mat-table [dataSource]="sortedResults" class="mat-elevation-z8"> |
||||
|
<ng-container matColumnDef="position"> |
||||
|
<th mat-header-cell *matHeaderCellDef></th> |
||||
|
<td mat-cell *matCellDef="let element; let i = index">{{i + 1}}</td> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Name Column --> |
||||
|
<ng-container matColumnDef="name"> |
||||
|
<th mat-header-cell *matHeaderCellDef>Name </th> |
||||
|
<td mat-cell *matCellDef="let element"> {{getRacerName(element.racer_id)}} </td> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Weight Column --> |
||||
|
<ng-container matColumnDef="runTime"> |
||||
|
<th mat-header-cell *matHeaderCellDef>Time </th> |
||||
|
<td mat-cell *matCellDef="let element"> {{formatMilliseconds(element.time)}} </td> |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-container matColumnDef="ghost"> |
||||
|
<th mat-header-cell *matHeaderCellDef>Ghost</th> |
||||
|
<td mat-cell *matCellDef="let element"> <button mat-raised-button color="accent"><mat-icon aria-hidden="false" aria-label="Example home icon" fontIcon="download"></mat-icon> Ghost</button> </td> |
||||
|
</ng-container> |
||||
|
|
||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> |
||||
|
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> |
||||
|
</table> |
||||
|
</div> |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
import { Component, Input, OnInit } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
|
||||
|
import { MatButtonModule } from '@angular/material/button'; |
||||
|
import { MatGridListModule } from '@angular/material/grid-list'; |
||||
|
import { MatIconModule } from '@angular/material/icon'; |
||||
|
import { MatTableModule } from '@angular/material/table'; |
||||
|
|
||||
|
import { RacersService } from '../../services/racers.service'; |
||||
|
import { RaceResultService } from '../../services/race-result.service'; |
||||
|
import { Race } from '../../models/race.model'; |
||||
|
import { Racer } from '../../models/racer.model'; |
||||
|
import { RaceEntry } from '../../models/raceEntry.model'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-race-details', |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
MatGridListModule, |
||||
|
MatIconModule, |
||||
|
MatTableModule, |
||||
|
MatButtonModule |
||||
|
], |
||||
|
templateUrl: './race-details.component.html', |
||||
|
styleUrl: './race-details.component.scss' |
||||
|
}) |
||||
|
|
||||
|
export class RaceDetailsComponent { |
||||
|
@Input() race?: Race; |
||||
|
racers: Map<string, Racer> = new Map<string, Racer>(); |
||||
|
raceResults: Map<string, RaceEntry[]> = new Map<string, RaceEntry[]>; |
||||
|
|
||||
|
displayedColumns: string[] = ['position', 'name', 'runTime', 'ghost']; |
||||
|
sortedResults: RaceEntry[] = []; |
||||
|
|
||||
|
constructor( |
||||
|
private racersService: RacersService, |
||||
|
private raceResultService: RaceResultService, |
||||
|
) {} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
if( this.race == undefined) { |
||||
|
return; |
||||
|
} |
||||
|
for( let racer_id of this.race?.racers ) { |
||||
|
this.racersService.getRacer(racer_id).subscribe( data => { |
||||
|
if(data != undefined) { |
||||
|
this.racers.set(racer_id, data); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
this.raceResultService.getRaceResultsForRacerInRace(racer_id, this.race.id).subscribe( data => { |
||||
|
if(data != undefined) { |
||||
|
this.raceResults.set(racer_id, data); |
||||
|
let result = this.getBestTimeForRacer(racer_id); |
||||
|
if (result != undefined) { |
||||
|
this.sortedResults.push(result) |
||||
|
this.sortedResults.sort((a, b) => { |
||||
|
if( a.time == b.time ) { return 0 }; |
||||
|
if( a.time < b.time ) { return -1}; |
||||
|
return 1; |
||||
|
}) |
||||
|
this.sortedResults = [...this.sortedResults]; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
getBestTimeForRacer(racer_id: string): RaceEntry | undefined { |
||||
|
let results = this.raceResults.get(racer_id) |
||||
|
if (results == undefined ){ |
||||
|
return undefined |
||||
|
} |
||||
|
|
||||
|
let bestTime: number = 0xFFFFFFFF; |
||||
|
let bestResult = undefined |
||||
|
for(let result of results){ |
||||
|
if(result.time < bestTime) { |
||||
|
bestTime = result.time |
||||
|
bestResult = result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return bestResult |
||||
|
} |
||||
|
|
||||
|
formatMilliseconds(milliseconds: number) |
||||
|
{ |
||||
|
const minutes = Math.floor(milliseconds / (1000 * 60)); |
||||
|
const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000); |
||||
|
const remainingMilliseconds = milliseconds % 1000; |
||||
|
|
||||
|
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}:${String(remainingMilliseconds).padStart(3, '0')}`; |
||||
|
} |
||||
|
|
||||
|
getRacerName(racerId: string): string { |
||||
|
let racer = this.racers.get(racerId); |
||||
|
if( racer != undefined) { |
||||
|
return racer.name + " (" + racer.gameHandle + ")" |
||||
|
} |
||||
|
|
||||
|
return "" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
<a class="docs-guide-item ng-star-inserted" (click)="openNewSeasonDialog()"> |
||||
|
<mat-card class="mat-mdc-card mdc-card docs-guide-card"> |
||||
|
<mat-card-title class="mat-mdc-card-title">New Season</mat-card-title> |
||||
|
<mat-card-subtitle>Make it!</mat-card-subtitle> |
||||
|
<div class="docs-guide-card-divider"></div> |
||||
|
<mat-card-content class="mat-mdc-card-content docs-guide-card-summary"> |
||||
|
<mat-icon>add</mat-icon> |
||||
|
</mat-card-content> |
||||
|
</mat-card> |
||||
|
</a> |
||||
@ -0,0 +1,32 @@ |
|||||
|
.docs-guide-card.mat-mdc-card { |
||||
|
text-align: center; |
||||
|
width: 240px; |
||||
|
height: 240px; |
||||
|
padding: 16px 0; |
||||
|
} |
||||
|
|
||||
|
.docs-guide-item { |
||||
|
text-decoration: none; |
||||
|
display: flex; |
||||
|
margin: 15px; |
||||
|
} |
||||
|
|
||||
|
.docs-guide-card .mat-mdc-card-title { |
||||
|
height: 33%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
font-size: 22px; |
||||
|
} |
||||
|
|
||||
|
.docs-guide-card-divider { |
||||
|
width: 40%; |
||||
|
height: 7px; |
||||
|
margin: 15px auto; |
||||
|
} |
||||
|
|
||||
|
mat-icon{ |
||||
|
width: 50px; |
||||
|
height: 50px; |
||||
|
font-size: xxx-large; |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing'; |
||||
|
|
||||
|
import { SeasonCardNewComponent } from './season-card-new.component'; |
||||
|
|
||||
|
describe('SeasonCardNewComponent', () => { |
||||
|
let component: SeasonCardNewComponent; |
||||
|
let fixture: ComponentFixture<SeasonCardNewComponent>; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
await TestBed.configureTestingModule({ |
||||
|
imports: [SeasonCardNewComponent] |
||||
|
}) |
||||
|
.compileComponents(); |
||||
|
|
||||
|
fixture = TestBed.createComponent(SeasonCardNewComponent); |
||||
|
component = fixture.componentInstance; |
||||
|
fixture.detectChanges(); |
||||
|
}); |
||||
|
|
||||
|
it('should create', () => { |
||||
|
expect(component).toBeTruthy(); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,23 @@ |
|||||
|
import { Component } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
|
||||
|
import { MatCardModule } from '@angular/material/card'; |
||||
|
import { MatIconModule } from '@angular/material/icon'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-season-card-new', |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
MatCardModule, |
||||
|
MatIconModule, |
||||
|
], |
||||
|
templateUrl: './season-card-new.component.html', |
||||
|
styleUrl: './season-card-new.component.scss' |
||||
|
}) |
||||
|
export class SeasonCardNewComponent { |
||||
|
|
||||
|
openNewSeasonDialog() { |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
<a class="docs-guide-item ng-star-inserted" href="/seasons/{{season?.id}}"> |
||||
|
<mat-card class="mat-mdc-card mdc-card docs-guide-card"> |
||||
|
@if (season != undefined) { |
||||
|
<mat-card-title class="mat-mdc-card-title">{{season.title}}</mat-card-title> |
||||
|
<mat-card-subtitle>{{season.subTitle}}</mat-card-subtitle> |
||||
|
<div class="docs-guide-card-divider"></div> |
||||
|
<mat-card-content class="mat-mdc-card-content docs-guide-card-summary"> |
||||
|
<p>Start Date: {{getStartingDate()}}</p> |
||||
|
<p>Number of Races: {{season.races.length}}</p> |
||||
|
</mat-card-content> |
||||
|
} |
||||
|
</mat-card> |
||||
|
</a> |
||||
@ -0,0 +1,26 @@ |
|||||
|
.docs-guide-card.mat-mdc-card { |
||||
|
text-align: center; |
||||
|
width: 240px; |
||||
|
height: 240px; |
||||
|
padding: 16px 0; |
||||
|
} |
||||
|
|
||||
|
.docs-guide-item { |
||||
|
text-decoration: none; |
||||
|
display: flex; |
||||
|
margin: 15px; |
||||
|
} |
||||
|
|
||||
|
.docs-guide-card .mat-mdc-card-title { |
||||
|
height: 33%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
font-size: 22px; |
||||
|
} |
||||
|
|
||||
|
.docs-guide-card-divider { |
||||
|
width: 40%; |
||||
|
height: 7px; |
||||
|
margin: 15px auto; |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
import { Component, Input } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
|
||||
|
import { MatCardModule } from '@angular/material/card'; |
||||
|
|
||||
|
import { Season } from '../../models/season.model'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-season-card', |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
MatCardModule |
||||
|
], |
||||
|
templateUrl: './season-card.component.html', |
||||
|
styleUrl: './season-card.component.scss' |
||||
|
}) |
||||
|
|
||||
|
export class SeasonCardComponent { |
||||
|
|
||||
|
@Input() season?: Season; |
||||
|
|
||||
|
getStartingDate() { |
||||
|
if(this.season != undefined){ |
||||
|
let date = new Date(0); |
||||
|
date.setUTCSeconds(Number(this.season.startingDate)); |
||||
|
return date.toUTCString(); |
||||
|
} |
||||
|
|
||||
|
return ""; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
<div> |
||||
|
<table mat-table [dataSource]="sortedStandings" class="mat-elevation-z8"> |
||||
|
<ng-container matColumnDef="position"> |
||||
|
<th mat-header-cell *matHeaderCellDef></th> |
||||
|
<td mat-cell *matCellDef="let element; let i = index">{{i + 1}}</td> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Name Column --> |
||||
|
<ng-container matColumnDef="name"> |
||||
|
<th mat-header-cell *matHeaderCellDef>Name </th> |
||||
|
<td mat-cell *matCellDef="let element"> {{getRacerName(element.id)}} </td> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Weight Column --> |
||||
|
<ng-container matColumnDef="points"> |
||||
|
<th mat-header-cell *matHeaderCellDef>Points </th> |
||||
|
<td mat-cell *matCellDef="let element"> {{element.points}} </td> |
||||
|
</ng-container> |
||||
|
|
||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> |
||||
|
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> |
||||
|
</table> |
||||
|
</div> |
||||
@ -0,0 +1,23 @@ |
|||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing'; |
||||
|
|
||||
|
import { SeasonStandingsComponent } from './season-standings.component'; |
||||
|
|
||||
|
describe('SeasonStandingsComponent', () => { |
||||
|
let component: SeasonStandingsComponent; |
||||
|
let fixture: ComponentFixture<SeasonStandingsComponent>; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
await TestBed.configureTestingModule({ |
||||
|
imports: [SeasonStandingsComponent] |
||||
|
}) |
||||
|
.compileComponents(); |
||||
|
|
||||
|
fixture = TestBed.createComponent(SeasonStandingsComponent); |
||||
|
component = fixture.componentInstance; |
||||
|
fixture.detectChanges(); |
||||
|
}); |
||||
|
|
||||
|
it('should create', () => { |
||||
|
expect(component).toBeTruthy(); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,155 @@ |
|||||
|
import { Component, Input, OnInit } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
|
||||
|
import { MatTableModule } from '@angular/material/table'; |
||||
|
|
||||
|
import { RacersService } from '../../services/racers.service'; |
||||
|
import { RaceResultService } from '../../services/race-result.service'; |
||||
|
import { Race } from '../../models/race.model'; |
||||
|
import { Racer } from '../../models/racer.model'; |
||||
|
import { RaceEntry } from '../../models/raceEntry.model'; |
||||
|
import { Season } from '../../models/season.model'; |
||||
|
import { RacesService } from '../../services/races.service'; |
||||
|
|
||||
|
class SeasonStanding { |
||||
|
id: string = ""; |
||||
|
points: number = 0; |
||||
|
constructor(_id: string) { this.id = _id;} |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-season-standings', |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
MatTableModule, |
||||
|
], |
||||
|
templateUrl: './season-standings.component.html', |
||||
|
styleUrl: './season-standings.component.scss' |
||||
|
}) |
||||
|
|
||||
|
export class SeasonStandingsComponent { |
||||
|
|
||||
|
@Input() season?: Season |
||||
|
races: Race[] = []; |
||||
|
|
||||
|
seasonRacers: Map<string, Racer> = new Map<string, Racer>(); |
||||
|
seasonRaceResults: Map<string, RaceEntry[]> = new Map<string, RaceEntry[]>; |
||||
|
|
||||
|
seasonPoints: Map<string, SeasonStanding> = new Map<string, SeasonStanding>; |
||||
|
|
||||
|
displayedColumns: string[] = ['position', 'name', 'points']; |
||||
|
sortedStandings!: SeasonStanding[]; |
||||
|
|
||||
|
constructor( |
||||
|
private racersService: RacersService, |
||||
|
private raceResultService: RaceResultService, |
||||
|
private racesService: RacesService, |
||||
|
) {} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
if(this.season == undefined){ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if(this.races == undefined) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
for (let race_id of this.season.races) { |
||||
|
this.racesService.getRace(race_id).subscribe( race => { |
||||
|
if( race != undefined ){ |
||||
|
this.races.push(race); |
||||
|
|
||||
|
this.seasonRaceResults.set(race.id, []); |
||||
|
for( let racer_id of race.racers ) { |
||||
|
if( this.seasonRacers.has(racer_id) == false ) { |
||||
|
this.racersService.getRacer(racer_id).subscribe( data => { |
||||
|
if(data != undefined) { |
||||
|
this.seasonRacers.set(racer_id, data); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
let thisRaceResults = this.seasonRaceResults.get(race.id); |
||||
|
this.raceResultService.getRaceResultsForRacerInRace(racer_id, race.id).subscribe( data => { |
||||
|
if(data != undefined) { |
||||
|
let bestResult = this.getBestTime(data); |
||||
|
|
||||
|
if(thisRaceResults != undefined) { |
||||
|
thisRaceResults.push(bestResult); |
||||
|
} |
||||
|
|
||||
|
this.calculateSeasonPoints(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
getRacerName(racerId: string): string { |
||||
|
let racer = this.seasonRacers.get(racerId); |
||||
|
if( racer != undefined) { |
||||
|
return racer.name + " (" + racer.gameHandle + ")" |
||||
|
} |
||||
|
|
||||
|
return "" |
||||
|
} |
||||
|
|
||||
|
getBestTime(results: RaceEntry[]): RaceEntry { |
||||
|
let bestTime: number = 0xFFFFFFFF; |
||||
|
let bestResult = new RaceEntry(); |
||||
|
for(let result of results){ |
||||
|
if(result.time < bestTime) { |
||||
|
bestTime = result.time |
||||
|
bestResult = result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return bestResult |
||||
|
} |
||||
|
|
||||
|
calculateSeasonPoints() { |
||||
|
|
||||
|
if(this.races == undefined) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
let maxRacePoints = this.seasonRacers.size; |
||||
|
|
||||
|
this.seasonPoints = new Map<string, SeasonStanding>(); |
||||
|
|
||||
|
for( let race of this.races) { |
||||
|
let availablePoints = maxRacePoints; |
||||
|
let thisRaceResults = this.seasonRaceResults.get(race.id); |
||||
|
if(thisRaceResults == undefined) { |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
thisRaceResults.sort((a,b) =>{ |
||||
|
return a.time - b.time; |
||||
|
}) |
||||
|
|
||||
|
for (let result of thisRaceResults) { |
||||
|
if (this.seasonPoints.has(result.racer_id) == false) { |
||||
|
this.seasonPoints.set(result.racer_id, new SeasonStanding(result.racer_id)); |
||||
|
} |
||||
|
|
||||
|
let currentPoints = this.seasonPoints.get(result.racer_id); |
||||
|
if( currentPoints == undefined ) { |
||||
|
continue; |
||||
|
} |
||||
|
currentPoints.points+=availablePoints; |
||||
|
availablePoints -=1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.sortedStandings = Array.from(this.seasonPoints.values()).sort((a, b) => { |
||||
|
return b.points - a.points; |
||||
|
}) |
||||
|
|
||||
|
this.sortedStandings = [...this.sortedStandings] |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
<button mat-icon-button class="example-icon" aria-label="Example icon-button with share icon" [matMenuTriggerFor]="menu"> |
||||
|
<mat-icon class="icon">palette</mat-icon> |
||||
|
<mat-menu #menu="matMenu"> |
||||
|
<button |
||||
|
*ngFor="let option of options" |
||||
|
mat-menu-item |
||||
|
(click)="changeTheme(option.value)"> |
||||
|
<mat-icon |
||||
|
role="img" |
||||
|
svgicon="theme-example" |
||||
|
aria-hidden="true"> |
||||
|
<svg |
||||
|
xmlns="http://www.w3.org/2000/svg" |
||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" |
||||
|
width="100%" |
||||
|
height="100%" |
||||
|
viewBox="0 0 80 80" |
||||
|
fit="" |
||||
|
preserveAspectRatio="xMidYMid meet" |
||||
|
focusable="false"> |
||||
|
<defs> |
||||
|
<path |
||||
|
d="M77.87 0C79.05 0 80 .95 80 2.13v75.74c0 1.17-.95 2.13-2.13 2.13H2.13C.96 80 0 79.04 0 77.87V2.13C0 .95.96 0 2.13 0h75.74z" |
||||
|
id="a"> |
||||
|
</path> |
||||
|
<path |
||||
|
d="M54 40c3.32 0 6 2.69 6 6 0 1.2 0-1.2 0 0 0 3.31-2.68 6-6 6H26c-3.31 0-6-2.69-6-6 0-1.2 0 1.2 0 0 0-3.31 2.69-6 6-6h28z" |
||||
|
id="b"> |
||||
|
</path> |
||||
|
<path d="M0 0h80v17.24H0V0z" id="c"></path> |
||||
|
</defs> |
||||
|
<use xlink:href="#a" [attr.fill]="option.backgroundColor"></use> |
||||
|
<use xlink:href="#b" [attr.fill]="option.buttonColor"></use> |
||||
|
<use xlink:href="#c" [attr.fill]="option.headingColor"></use> |
||||
|
</svg> |
||||
|
</mat-icon> |
||||
|
<span>{{ option.label }}</span> |
||||
|
</button> |
||||
|
</mat-menu> |
||||
|
</button> |
||||
@ -0,0 +1,23 @@ |
|||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing'; |
||||
|
|
||||
|
import { ThemeMenuComponent } from './theme-menu.component'; |
||||
|
|
||||
|
describe('ThemeMenuComponent', () => { |
||||
|
let component: ThemeMenuComponent; |
||||
|
let fixture: ComponentFixture<ThemeMenuComponent>; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
await TestBed.configureTestingModule({ |
||||
|
imports: [ThemeMenuComponent] |
||||
|
}) |
||||
|
.compileComponents(); |
||||
|
|
||||
|
fixture = TestBed.createComponent(ThemeMenuComponent); |
||||
|
component = fixture.componentInstance; |
||||
|
fixture.detectChanges(); |
||||
|
}); |
||||
|
|
||||
|
it('should create', () => { |
||||
|
expect(component).toBeTruthy(); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,33 @@ |
|||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
|
||||
|
import { MatIconModule } from '@angular/material/icon'; |
||||
|
import { MatMenuModule } from '@angular/material/menu'; |
||||
|
import { MatButtonModule } from '@angular/material/button'; |
||||
|
|
||||
|
import { ThemeOption } from '../../models/theme-option.model'; |
||||
|
import { ThemeService } from '../../services/theme.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-theme-menu', |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
MatIconModule, |
||||
|
MatMenuModule, |
||||
|
MatButtonModule |
||||
|
], |
||||
|
templateUrl: './theme-menu.component.html', |
||||
|
styleUrl: './theme-menu.component.scss' |
||||
|
}) |
||||
|
|
||||
|
export class ThemeMenuComponent { |
||||
|
constructor(private themeService: ThemeService) {} |
||||
|
|
||||
|
@Input() options: Array<ThemeOption> = []; |
||||
|
@Output() themeChange: EventEmitter<string> = new EventEmitter<string>(); |
||||
|
|
||||
|
changeTheme(selectedTheme: string) { |
||||
|
this.themeChange.emit(selectedTheme); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
<mat-toolbar color="primary"> |
||||
|
<button mat-flat-button color="primary" [routerLink]="['/seasons']"> |
||||
|
<mat-icon>sports_esports</mat-icon> |
||||
|
Seasons |
||||
|
</button> |
||||
|
<button mat-flat-button color="primary" [routerLink]="['/getting-started']"> |
||||
|
<mat-icon>help_center</mat-icon> |
||||
|
Getting Started |
||||
|
</button> |
||||
|
<span class="example-spacer"></span> |
||||
|
<app-theme-menu |
||||
|
[options] = "options" |
||||
|
(themeChange)="themeChangeHandler($event)"> |
||||
|
</app-theme-menu> |
||||
|
@if(isAuthed() == false) { |
||||
|
<button mat-icon-button class="example-icon" aria-label="Example icon-button with share icon" (click)="onLoginClick()"> |
||||
|
<mat-icon>login</mat-icon> |
||||
|
</button> |
||||
|
} |
||||
|
@if(isAuthed()) { |
||||
|
<button mat-icon-button class="example-icon" aria-label="Example icon-button with share icon" (click)="onProfile()"> |
||||
|
<mat-icon>person_pin</mat-icon> |
||||
|
</button> |
||||
|
} |
||||
|
</mat-toolbar> |
||||
@ -1,3 +1,5 @@ |
|||||
|
@use '@angular/material' as mat; |
||||
|
|
||||
.example-spacer { |
.example-spacer { |
||||
flex: 1 1 auto; |
flex: 1 1 auto; |
||||
} |
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
import { Component, OnInit } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
import { RouterModule } from '@angular/router'; |
||||
|
|
||||
|
import { MatIconModule } from '@angular/material/icon'; |
||||
|
import { MatButtonModule } from '@angular/material/button'; |
||||
|
import { MatToolbarModule } from '@angular/material/toolbar'; |
||||
|
|
||||
|
import { MatDialog } from '@angular/material/dialog'; |
||||
|
|
||||
|
import { ThemeMenuComponent } from '../theme-menu/theme-menu.component'; |
||||
|
import { ThemeService } from '../../services/theme.service'; |
||||
|
import { ThemeOption } from '../../models/theme-option.model'; |
||||
|
|
||||
|
import { LoginDialogComponent } from '../login-dialog/login-dialog.component'; |
||||
|
import { AuthService } from '../../services/auth.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-top-bar', |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
RouterModule, |
||||
|
MatToolbarModule, |
||||
|
MatButtonModule, |
||||
|
MatIconModule, |
||||
|
ThemeMenuComponent |
||||
|
], |
||||
|
templateUrl: './top-bar.component.html', |
||||
|
styleUrl: './top-bar.component.scss' |
||||
|
}) |
||||
|
|
||||
|
export class TopBarComponent { |
||||
|
constructor( |
||||
|
private readonly themeService: ThemeService, |
||||
|
private authService: AuthService, |
||||
|
public dialog: MatDialog |
||||
|
) {} |
||||
|
|
||||
|
options: Array<ThemeOption> = []; |
||||
|
|
||||
|
async ngOnInit() { |
||||
|
this.themeService.getThemeOptions().subscribe(data => { |
||||
|
this.options = data; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
themeChangeHandler(seletedTheme: string) { |
||||
|
this.themeService.setTheme(seletedTheme) |
||||
|
} |
||||
|
|
||||
|
onLoginClick() { |
||||
|
this.dialog.open(LoginDialogComponent); |
||||
|
} |
||||
|
|
||||
|
isAuthed() { |
||||
|
return this.authService.isAuthenticated(); |
||||
|
} |
||||
|
|
||||
|
onProfile() { |
||||
|
this.authService.testProfile().subscribe(data => { |
||||
|
console.log(data) |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
export class Race{ |
||||
|
id: string = ""; |
||||
|
mapName: string = ""; |
||||
|
mapUrl: string = ""; |
||||
|
mapUID: string = ""; |
||||
|
mapImgUrl: string = ""; |
||||
|
startDate: Date = new Date(0); |
||||
|
endDate: Date = new Date(0); |
||||
|
racers: string[] = []; |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
export class RaceEntry { |
||||
|
id: string = ""; |
||||
|
race_id: string = ""; |
||||
|
racer_id: string = ""; |
||||
|
replayPath: string = ""; |
||||
|
time: number = 0; |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
export class Racer { |
||||
|
id: string = ""; |
||||
|
name: string = ""; |
||||
|
gameHandle: string = ""; |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
export class Season { |
||||
|
id: string = "0"; |
||||
|
title: string = ""; |
||||
|
subTitle: string = ""; |
||||
|
startingDate: Date = new Date(Date.now()); |
||||
|
races: string[] = []; |
||||
|
racers: string[] = []; |
||||
|
standings: string[] = []; |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
export interface ThemeOption { |
||||
|
backgroundColor: string; |
||||
|
buttonColor: string; |
||||
|
headingColor: string; |
||||
|
label: string; |
||||
|
value: string; |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
<div class="docs-guide-wrapper"> |
||||
|
<div class="docs-markdown"> |
||||
|
<h1>Getting Started</h1> |
||||
|
|
||||
|
<p>In order to take part in a trackmanai tournament you are going to need a few things.</p> |
||||
|
<p>Trackmanaia being the primary thing. You can get it from <a href="https://store.steampowered.com/app/2225070/Trackmania/">Steam</a>, <a href="https://store.epicgames.com/en-US/p/trackmania">Epic Store</a> or <a href="https://store.ubisoft.com/uk/trackmania-starter-access/5e8b58345cdf9a12c868c878.html">Ubisoft</a></p> |
||||
|
<p>You will need to purchase standard access to the game, which you can do <a href="https://store.ubisoft.com/uk/trackmania-standard-access-1-year/5e14dc8a5cdf9a1ec45ad81f.html">here</a> or <a href="https://store.epicgames.com/en-US/p/trackmania--standard-access-one-year">here</a>. It does go on sale now and again so keep an eye out for other offers.</p> |
||||
|
|
||||
|
<br/> |
||||
|
|
||||
|
<h1>Plugins</h1> |
||||
|
|
||||
|
<p>You are going to need to install some plugins for the game to help facilitate the tournaments.</p> |
||||
|
<p>First you will need to heave ofer to <a href="OpenPlanet.dev">OpenPlanet.dev</a> and download the latest version of <a href="https://openplanet.dev/download/next">OpenPlanet for Trackmania</a>. Thats 1.26.5 at the time of writing.</p> |
||||
|
<p>Once that is installed you can boot the game and hit f3 to bring up the plugins bar.</p> |
||||
|
<p>From there open up the plugins manager and search fro "replay". You will need to install Autosave Ghosts and MLHook. This will auto save a replay for each successful race you complete automatically.</p> |
||||
|
<p>These replay will be used to record your best times during the tournament.</p> |
||||
|
|
||||
|
<br/> |
||||
|
|
||||
|
<h1>Replays</h1> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,18 @@ |
|||||
|
mat-drawer-container, |
||||
|
mat-drawer-content, |
||||
|
mat-drawer { |
||||
|
height: calc(100vh - 56px); |
||||
|
} |
||||
|
|
||||
|
mat-drawer { |
||||
|
width: 200px; |
||||
|
} |
||||
|
|
||||
|
.docs-guide-wrapper{ |
||||
|
padding: 20px 40px 0; |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
h1{ |
||||
|
text-align: left; |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing'; |
||||
|
|
||||
|
import { GettingStartedComponent } from './getting-started.component'; |
||||
|
|
||||
|
describe('GettingStartedComponent', () => { |
||||
|
let component: GettingStartedComponent; |
||||
|
let fixture: ComponentFixture<GettingStartedComponent>; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
await TestBed.configureTestingModule({ |
||||
|
imports: [GettingStartedComponent] |
||||
|
}) |
||||
|
.compileComponents(); |
||||
|
|
||||
|
fixture = TestBed.createComponent(GettingStartedComponent); |
||||
|
component = fixture.componentInstance; |
||||
|
fixture.detectChanges(); |
||||
|
}); |
||||
|
|
||||
|
it('should create', () => { |
||||
|
expect(component).toBeTruthy(); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,22 @@ |
|||||
|
import { Component } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { RouterModule } from '@angular/router'; |
||||
|
|
||||
|
import { MatSidenavModule } from '@angular/material/sidenav'; |
||||
|
import { MatListModule } from '@angular/material/list'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-getting-started', |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
RouterModule, |
||||
|
MatSidenavModule, |
||||
|
MatListModule, |
||||
|
], |
||||
|
templateUrl: './getting-started.component.html', |
||||
|
styleUrl: './getting-started.component.scss' |
||||
|
}) |
||||
|
export class GettingStartedComponent { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
<p>home works!</p> |
||||
@ -0,0 +1,23 @@ |
|||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing'; |
||||
|
|
||||
|
import { HomeComponent } from './home.component'; |
||||
|
|
||||
|
describe('HomeComponent', () => { |
||||
|
let component: HomeComponent; |
||||
|
let fixture: ComponentFixture<HomeComponent>; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
await TestBed.configureTestingModule({ |
||||
|
imports: [HomeComponent] |
||||
|
}) |
||||
|
.compileComponents(); |
||||
|
|
||||
|
fixture = TestBed.createComponent(HomeComponent); |
||||
|
component = fixture.componentInstance; |
||||
|
fixture.detectChanges(); |
||||
|
}); |
||||
|
|
||||
|
it('should create', () => { |
||||
|
expect(component).toBeTruthy(); |
||||
|
}); |
||||
|
}); |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue