Compare commits

8 Commits
login ... main

Author SHA1 Message Date
6326b5fef6 Merge branch 'auth' into 'main'
Auth

Added proper usage of JWT and communication with the Rona-Backend to the account service

See merge request daniel/rona-frontend!4
2020-07-30 17:14:46 +02:00
4f2c149f5a adjusted login and signup post operations to work with forms
added a jwt interceptor
2020-07-30 17:14:45 +02:00
48d620e878 Merge branch 'chat' into 'main'
- moved chat to a dedicated module
- text messages and dice rolls can be grouped into the same MessagesComponent
- now supports system messages
- fixed faulty timestamps displaying month instead of hours

See merge request daniel/rona-frontend!3
2020-07-27 17:09:18 +02:00
92644d9008 Chat upgraded for dice rolls and system messages 2020-07-27 17:09:18 +02:00
2598e5864e Merge branch 'game' into 'main'
Game

Created a GameModule that will contain all the functionality required to play. The routing and imports of AppModule are adjusted accordingly.

See merge request daniel/rona-frontend!2
2020-07-23 14:53:03 +02:00
8d2df73484 Added GameModule 2020-07-23 14:53:03 +02:00
44575a8a08 Merge branch 'login' into 'main'
See merge request daniel/rona-frontend!1
2020-07-21 15:34:53 +02:00
c82d784866 Initial implementation of login and registration with fake backend for future testing
Also introduced flex-layout
2020-07-21 15:34:52 +02:00
65 changed files with 1194 additions and 291 deletions

214
package-lock.json generated
View File

@@ -5,25 +5,25 @@
"requires": true,
"dependencies": {
"@angular-devkit/architect": {
"version": "0.1000.2",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1000.2.tgz",
"integrity": "sha512-n1e/ZdE70C6hDauTWLAiofKDI6m88nhb91Ddqum0eeUh3HL3Ppv/sT9O+UYsab3gIdIOFJwHBpZ96SM/2Ja5NA==",
"version": "0.1000.3",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1000.3.tgz",
"integrity": "sha512-8ZszTAkRvGGMXERFvyLT6SJPfJXjNNfHamA76uDPTBXy+EijJ1XVTUr1+SYEe73E4ovtxqxAnsApEFxS7/Ni5w==",
"dev": true,
"requires": {
"@angular-devkit/core": "10.0.2",
"@angular-devkit/core": "10.0.3",
"rxjs": "6.5.5"
}
},
"@angular-devkit/build-angular": {
"version": "0.1000.2",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.1000.2.tgz",
"integrity": "sha512-Cl7JaXkE1OMpagMwPPNGq7IGy50p2F3R0iZFi38imq661YqH/FFv0SdbMqmpAHaIvvr6E1HJt5ltoLNERQWFjg==",
"version": "0.1000.3",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.1000.3.tgz",
"integrity": "sha512-r3KJj39AwkYYzbixSM095l4fOGvhyByr0XvmAEu0l5dGGdL4tNXywvgXkNhEVRDo0jZYpTMegiTqzOik/9YCDw==",
"dev": true,
"requires": {
"@angular-devkit/architect": "0.1000.2",
"@angular-devkit/build-optimizer": "0.1000.2",
"@angular-devkit/build-webpack": "0.1000.2",
"@angular-devkit/core": "10.0.2",
"@angular-devkit/architect": "0.1000.3",
"@angular-devkit/build-optimizer": "0.1000.3",
"@angular-devkit/build-webpack": "0.1000.3",
"@angular-devkit/core": "10.0.3",
"@babel/core": "7.9.6",
"@babel/generator": "7.9.6",
"@babel/plugin-transform-runtime": "7.9.6",
@@ -31,7 +31,7 @@
"@babel/runtime": "7.9.6",
"@babel/template": "7.8.6",
"@jsdevtools/coverage-istanbul-loader": "3.0.3",
"@ngtools/webpack": "10.0.2",
"@ngtools/webpack": "10.0.3",
"ajv": "6.12.2",
"autoprefixer": "9.8.0",
"babel-loader": "8.1.0",
@@ -88,9 +88,9 @@
}
},
"@angular-devkit/build-optimizer": {
"version": "0.1000.2",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.1000.2.tgz",
"integrity": "sha512-wbrgJQw92+A7kFaG7U0F9MMzhVI32tcIdr26+SFXWGAeBaWIkBfMs/jfGLlEYESLqQQF5oMn7LJBwXu+nkPHvw==",
"version": "0.1000.3",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.1000.3.tgz",
"integrity": "sha512-6mFoubg08UCWC0fE2mGoawEt2R1VlGStvUNAP2PRCjoj1ZySa1NnVYoKk65cyAAA3K2o7vSoDZesNq1uABjZbg==",
"dev": true,
"requires": {
"loader-utils": "2.0.0",
@@ -100,20 +100,20 @@
}
},
"@angular-devkit/build-webpack": {
"version": "0.1000.2",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1000.2.tgz",
"integrity": "sha512-x1fHnZFTwvAE3lB6XnlJmf0KNiiAsZKGdUuTXqzgsgh34A/aFOWtu0EB6cw6lvifMj1ioDT8Zjp8N89Lh5AtEA==",
"version": "0.1000.3",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1000.3.tgz",
"integrity": "sha512-+vmn9d9THFubSWS28K1+nElUfOrhT576ptVZMd0a5S24momV8loW3J8iBOBfnGal/P86ZCAyP46kSirlAzH9Jg==",
"dev": true,
"requires": {
"@angular-devkit/architect": "0.1000.2",
"@angular-devkit/core": "10.0.2",
"@angular-devkit/architect": "0.1000.3",
"@angular-devkit/core": "10.0.3",
"rxjs": "6.5.5"
}
},
"@angular-devkit/core": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-10.0.2.tgz",
"integrity": "sha512-gD8iUP28GscsHqGKKu+NtX97bt7aXDZmIYqWTv4SThrcsPYY2A4tBw+NBbNAwjHkNfn4jflpqTmcN9gBAoLoSg==",
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-10.0.3.tgz",
"integrity": "sha512-m27ogjq44j80x64RnEswSvy8UewUqeCVJBbEuY6fzrWoaiCf12sgPlrSCwjwfhtQrLgl1e/i9zYA7U6ulGRXyg==",
"dev": true,
"requires": {
"ajv": "6.12.2",
@@ -124,28 +124,28 @@
}
},
"@angular-devkit/schematics": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-10.0.2.tgz",
"integrity": "sha512-BUUWAreHoxk4Z91Xhr+wtFUhK6+7AOczsIxlZmJnQwSUhN2ZdiDjOXeO3NFkDdL8CtNOz5khGu2iNBqn08FpUw==",
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-10.0.3.tgz",
"integrity": "sha512-TjA2ZSPCgUK9l4FiRTIQY7DceXMAvNzOMWffy9o3kv2HPtxG9kuBrQXk++Z99zpylK0cAsugV7t/5ANpUkrIiA==",
"dev": true,
"requires": {
"@angular-devkit/core": "10.0.2",
"@angular-devkit/core": "10.0.3",
"ora": "4.0.4",
"rxjs": "6.5.5"
}
},
"@angular/animations": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-10.0.3.tgz",
"integrity": "sha512-s7hob0TmSRqsgEWdB0EYsKY8eF8iD6Sj44XntecAhEc/IxVQGIIZiNwGvW43Lb8iEdbvdF+GJfgxu5I8ZWMX3Q==",
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-10.0.4.tgz",
"integrity": "sha512-UzQiWhDHY6wixS1Nh+Jwpzq1weiLGXJPt3Pa4pETpt3Hg7MIUu62dik6OFWuGYQPbn9DJYH+CH+sRxN1GCVjww==",
"requires": {
"tslib": "^2.0.0"
}
},
"@angular/cdk": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-10.0.2.tgz",
"integrity": "sha512-ZvZkeh7qXllAGSOFvWSYurCslflTmB0JD3gonmUKOBl/O/AcOTPntP+iOrUaC3+eR3ohsfL5SswxChW0NF+oHQ==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-10.1.0.tgz",
"integrity": "sha512-zSZcpsfhRWdNAzNXnKZIlaX1uAWY+8W2zV7ktQNJoNypo9X02rY0YtmPBzlPjT0ITjM4EqohZ07nfd+5bLUw4A==",
"requires": {
"parse5": "^5.0.0",
"tslib": "^2.0.0"
@@ -160,16 +160,16 @@
}
},
"@angular/cli": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-10.0.2.tgz",
"integrity": "sha512-L/uLUrZNIwbYzIeU9R3SC2hblDgtxP57msmRjoOQBpSzwlOME+z0wlCXPv+h9NOzNPvVVbEtLtjBgZxUw0IHzg==",
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-10.0.3.tgz",
"integrity": "sha512-ONK8YG20KuakQetY0lPKDAOA3uBoLurdpSfFspFkcECyDimwJYSEydi3FUnCxEexeoKvrQWcol+q+u9YPoHCyg==",
"dev": true,
"requires": {
"@angular-devkit/architect": "0.1000.2",
"@angular-devkit/core": "10.0.2",
"@angular-devkit/schematics": "10.0.2",
"@schematics/angular": "10.0.2",
"@schematics/update": "0.1000.2",
"@angular-devkit/architect": "0.1000.3",
"@angular-devkit/core": "10.0.3",
"@angular-devkit/schematics": "10.0.3",
"@schematics/angular": "10.0.3",
"@schematics/update": "0.1000.3",
"@yarnpkg/lockfile": "1.1.0",
"ansi-colors": "4.1.1",
"debug": "4.1.1",
@@ -202,25 +202,25 @@
}
},
"@angular/common": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-10.0.3.tgz",
"integrity": "sha512-R2q/Vt07PHgcmvZBVGcYjf6K2xERCHWUYQYN1HsgjVQUu5ypvE7Kqs+6s0BfIoBKc+ejKmjMHHumnm+89O+gXg==",
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-10.0.4.tgz",
"integrity": "sha512-9DJMD8GgHz7i2fMz0f1IHZlDSIz83uE6fcH8SIq1iCSWT2dubRRpCX000VpIyhAgfkCgdNCYXQ7VGNsZceoagQ==",
"requires": {
"tslib": "^2.0.0"
}
},
"@angular/compiler": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-10.0.3.tgz",
"integrity": "sha512-SmzMYxBprs8bVS9WctiCDj6KVp3jZFEaxGTbC/FwtdvesMIdOktpHggUeJW+OzUTwTnYVmKrm6d8rdg3QSaXFQ==",
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-10.0.4.tgz",
"integrity": "sha512-1rnEmSHJtrKC1QD+PyF36xwMnSqG4Slgi5+PYk3BxIa5vbWBibrYijtd/uCNhscfPSpfb06MVM2mRsrc+BmbQg==",
"requires": {
"tslib": "^2.0.0"
}
},
"@angular/compiler-cli": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-10.0.3.tgz",
"integrity": "sha512-XK2xQX0RLr7QMkEseDhcdqZ2hMM5n1Q6LFvOJfxQXXBpa7DBC9mB6HLsn0vrLgwaAfz+SEQ7pLDgXQSy2tmJUg==",
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-10.0.4.tgz",
"integrity": "sha512-uZKk6Ab4Pw8qcaXhpORuEoCbPSHWx3TWcs9OXIKIhzOOoMMe9OSt2SzOkHCrySSaik1IhQODnHww7sGRw5mxwQ==",
"dev": true,
"requires": {
"canonical-path": "1.0.0",
@@ -401,49 +401,57 @@
}
},
"@angular/core": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-10.0.3.tgz",
"integrity": "sha512-EfWAz5StlPYo2ZtvVzeoNlGrFAXRncwGd/CExbLFOZx4HcDXVkATw5d4vnKHmmKacDqnbuvMD2M0Tl0EJi5q4g==",
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-10.0.4.tgz",
"integrity": "sha512-lA8RDagJ/O0gUX95h00+nnUZs/1QmmhAVWVtHcuI12ueC836tJhLtPGnEx9ib9NXrgRyNwb8lO1xJPmmuQgdQQ==",
"requires": {
"tslib": "^2.0.0"
}
},
"@angular/flex-layout": {
"version": "10.0.0-beta.32",
"resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-10.0.0-beta.32.tgz",
"integrity": "sha512-JvuY4dUoy5jyCTIrFiq7n30Znakh1pD3nbg0h0hs2r3t1OiDQb0ZSI1wcumosG/vYHsuJQTuNhbfaIZzA1x8nA==",
"requires": {
"tslib": "^2.0.0"
}
},
"@angular/forms": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-10.0.3.tgz",
"integrity": "sha512-08XlERBGVnzea4sVY7J1JsU6zX/ENGZGB9V4K1jpX6gNjlAk2AbskC76+ngdsgdzv0neNEqPoLdMAZerNECnUg==",
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-10.0.4.tgz",
"integrity": "sha512-SJSYZCfHua9fi/dks1q+ad9OGBblRUn3Q1V4B7r99fJYr39qRiIHcegikhY4h8H3Wk1bJRGJG7iXmxJhjWXK3Q==",
"requires": {
"tslib": "^2.0.0"
}
},
"@angular/material": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-10.0.2.tgz",
"integrity": "sha512-xG8lCG+QS+r61aDoBauH6RyNuOHai4yxhn3e8cb2ua86liarJmF9jqlW0tB49tE1ZLz9U/+ybJKkos2kX1eF1Q==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-10.1.0.tgz",
"integrity": "sha512-zHJxMHYAyfJbhXGhsWUFTABBBQVzWNexuJGWh19MQx0jQn7aLek5nYQ0oLG00+ArISVAg/XMOtnbdRaemFWVzw==",
"requires": {
"tslib": "^2.0.0"
}
},
"@angular/platform-browser": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-10.0.3.tgz",
"integrity": "sha512-f6JsFMunJNn7fPtwwWbZsXZ/JFA5RlwbyHKKkLAPMLLJwabOcSh/bUpPWKoRMHpUxr53PGEX4XY6QNPJXrUaag==",
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-10.0.4.tgz",
"integrity": "sha512-iaZ8pFS5XUgPCO6/C47TzSFPzlzgleayq099cVOOx9z0t/SwUCSKt4AdAVhyQ8RTnx6l1JmmwBgRaXpScZlqzg==",
"requires": {
"tslib": "^2.0.0"
}
},
"@angular/platform-browser-dynamic": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-10.0.3.tgz",
"integrity": "sha512-ASZWymDVCgmDe0XE1djGKrfJnUR65PdfoZ51CQ7xSVJTnXp6kCAXzFeG2IyqU+O7IXvQRgvb4GOA4Y6GScNVKw==",
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-10.0.4.tgz",
"integrity": "sha512-RoUMqYhUwF6+Mvk/aH0IZQ1D0SDEi9k5EZlx9CJ3RvNuKygk7to+S4vMWVpGxFQlwdS3bytRLKi+Kki6f4nLkg==",
"requires": {
"tslib": "^2.0.0"
}
},
"@angular/router": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-10.0.3.tgz",
"integrity": "sha512-WMNJ4yS3xvgOPa73hKQNHoTCT2l+bvompxdqZSqN7dh92QtPJBXCm+qBC2jRDj4wgP7Eq5dgymh7a/dZqseFEQ==",
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-10.0.4.tgz",
"integrity": "sha512-iDLWdmltU5pZ6M/fBKC5Kg2o9Aqb1YJ+oHXFu186BQAl2RNeNCmMQ0VaCxjpMgD/MoSxpuRuGQ6rRrCSFCxtcQ==",
"requires": {
"tslib": "^2.0.0"
}
@@ -1556,12 +1564,12 @@
}
},
"@ngtools/webpack": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-10.0.2.tgz",
"integrity": "sha512-Py8jkc6UIHtp5TKKAMkNiKhx0goL+d7RkQEBWIvO+9e5fBGIt0Npy3dBoJ9gRldaGIjLZWlHhGsgeaYbq5dlvA==",
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-10.0.3.tgz",
"integrity": "sha512-0TuvYMCLtsApLtCHXeDBYGEoAQXzsRLpgFxPM5W7CGcj0ecthZO4NYrMAt+J8ky//KmbxqQSFHWmss2cbirIPA==",
"dev": true,
"requires": {
"@angular-devkit/core": "10.0.2",
"@angular-devkit/core": "10.0.3",
"enhanced-resolve": "4.1.1",
"rxjs": "6.5.5",
"webpack-sources": "1.4.3"
@@ -1611,23 +1619,23 @@
}
},
"@schematics/angular": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-10.0.2.tgz",
"integrity": "sha512-viSf1HQH2vxZAonkfdAjJ/0Z8VgKptsi4UYtJDwabKRbbyp2EQ/vhEPI/ddV4QgrQqR8mkOl7nu8JqL2M0+v6A==",
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-10.0.3.tgz",
"integrity": "sha512-Or2pCqjpPbAvmbxtfMosGwQbNbSL4xodK5Key7678ZAPGB+rcxrVkBI9yxEJ/qzF/LrmMoKqy0JCmVLK7Grpog==",
"dev": true,
"requires": {
"@angular-devkit/core": "10.0.2",
"@angular-devkit/schematics": "10.0.2"
"@angular-devkit/core": "10.0.3",
"@angular-devkit/schematics": "10.0.3"
}
},
"@schematics/update": {
"version": "0.1000.2",
"resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.1000.2.tgz",
"integrity": "sha512-6PSJ+WexBqY0ESo0Kp6fa9wPWAXKE9oyLNjISJ1S9QKCVowoHa8FV+7BalVOUYwUhnUzwPCF6n7PqcJiZR632g==",
"version": "0.1000.3",
"resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.1000.3.tgz",
"integrity": "sha512-Nncdklmzi1tyzkoAh7GlSslxriRhftlmfqPVmFHrrPRttYACtT/QH5qcWsrPgTPpHGINYEHrPjpeljsMoMchBQ==",
"dev": true,
"requires": {
"@angular-devkit/core": "10.0.2",
"@angular-devkit/schematics": "10.0.2",
"@angular-devkit/core": "10.0.3",
"@angular-devkit/schematics": "10.0.3",
"@yarnpkg/lockfile": "1.1.0",
"ini": "1.3.5",
"npm-package-arg": "^8.0.0",
@@ -2899,9 +2907,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001100",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001100.tgz",
"integrity": "sha512-0eYdp1+wFCnMlCj2oudciuQn2B9xAFq3WpgpcBIZTxk/1HNA/O2YA7rpeYhnOqsqAJq1AHUgx6i1jtafg7m2zA==",
"version": "1.0.30001104",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001104.tgz",
"integrity": "sha512-pkpCg7dmI/a7WcqM2yfdOiT4Xx5tzyoHAXWsX5/HxZ3TemwDZs0QXdqbE0UPLPVy/7BeK7693YfzfRYfu1YVpg==",
"dev": true
},
"canonical-path": {
@@ -3027,9 +3035,9 @@
}
},
"cli-spinners": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.3.0.tgz",
"integrity": "sha512-Xs2Hf2nzrvJMFKimOR7YR0QwZ8fc0u98kdtwN1eNAZzNQgH3vK2pXzff6GJtKh7S5hoJ87ECiAiZFS2fb5Ii2w==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.4.0.tgz",
"integrity": "sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA==",
"dev": true
},
"cli-width": {
@@ -4324,9 +4332,9 @@
"dev": true
},
"electron-to-chromium": {
"version": "1.3.498",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.498.tgz",
"integrity": "sha512-W1hGwaQEU8j9su2jeAr3aabkPuuXw+j8t73eajGAkEJWbfWiwbxBwQN/8Qmv2qCy3uCDm2rOAaZneYQM8VGC4w==",
"version": "1.3.501",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.501.tgz",
"integrity": "sha512-tyzuKaV2POw2mtqBBzQGNBojMZzH0MRu8bT8T/50x+hWeucyG/9pkgAATy+PcM2ySNM9+8eG2VllY9c6j4i+bg==",
"dev": true
},
"elliptic": {
@@ -6990,9 +6998,9 @@
"dev": true
},
"less": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/less/-/less-3.12.0.tgz",
"integrity": "sha512-3mmSHFRP9hGxxQgAKgChfau1LO3ksV/zyZf1qd2ENyBV778NA9Ids99wFRA20jE+5prT7oScKod8PoGlxSe1gg==",
"version": "3.12.2",
"resolved": "https://registry.npmjs.org/less/-/less-3.12.2.tgz",
"integrity": "sha512-+1V2PCMFkL+OIj2/HrtrvZw0BC0sYLMICJfbQjuj/K8CEnlrFX6R5cKKgzzttsZDHyxQNL1jqMREjKN3ja/E3Q==",
"dev": true,
"requires": {
"errno": "^0.1.1",
@@ -7687,9 +7695,9 @@
}
},
"native-request": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/native-request/-/native-request-1.0.5.tgz",
"integrity": "sha512-7wU3DvBGAJQxWuMR3F62zrhB7hxNj2DdlC/eBVrCgavc6+ZpFZOqS/PsR7QyUPLMkFk0GvvzoeeOAZGLLnObnA==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/native-request/-/native-request-1.0.7.tgz",
"integrity": "sha512-9nRjinI9bmz+S7dgNtf4A70+/vPhnd+2krGpy4SUlADuOuSa24IDkNaZ+R/QT1wQ6S8jBdi6wE7fLekFZNfUpQ==",
"dev": true,
"optional": true
},
@@ -8692,9 +8700,9 @@
}
},
"portfinder": {
"version": "1.0.26",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
"integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==",
"version": "1.0.27",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.27.tgz",
"integrity": "sha512-bJ3U3MThKnyJ9Dx1Idtm5pQmxXqw08+XOHhi/Lie8OF1OlhVaBFhsntAIhkZYjfDcCzszSr0w1yCbccThhzgxQ==",
"dev": true,
"requires": {
"async": "^2.6.2",
@@ -12018,9 +12026,9 @@
"dev": true
},
"typescript": {
"version": "3.9.6",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.6.tgz",
"integrity": "sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==",
"version": "3.9.7",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
"dev": true
},
"ua-parser-js": {

View File

@@ -11,28 +11,29 @@
},
"private": true,
"dependencies": {
"@angular/animations": "~10.0.3",
"@angular/cdk": "^10.0.2",
"@angular/common": "~10.0.3",
"@angular/compiler": "~10.0.3",
"@angular/core": "~10.0.3",
"@angular/forms": "~10.0.3",
"@angular/material": "^10.0.2",
"@angular/platform-browser": "~10.0.3",
"@angular/platform-browser-dynamic": "~10.0.3",
"@angular/router": "~10.0.3",
"@angular/animations": "^10.0.4",
"@angular/cdk": "^10.1.0",
"@angular/common": "^10.0.4",
"@angular/compiler": "^10.0.4",
"@angular/core": "^10.0.4",
"@angular/flex-layout": "^10.0.0-beta.32",
"@angular/forms": "^10.0.4",
"@angular/material": "^10.1.0",
"@angular/platform-browser": "^10.0.4",
"@angular/platform-browser-dynamic": "^10.0.4",
"@angular/router": "^10.0.4",
"rxjs": "~6.5.5",
"socket.io": "^2.3.0",
"tslib": "^2.0.0",
"zone.js": "~0.10.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1000.2",
"@angular/cli": "~10.0.2",
"@angular/compiler-cli": "~10.0.3",
"@types/node": "^12.11.1",
"@angular-devkit/build-angular": "^0.1000.3",
"@angular/cli": "^10.0.3",
"@angular/compiler-cli": "^10.0.4",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~5.0.0",
@@ -44,6 +45,6 @@
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~3.9.5"
"typescript": "^3.9.7"
}
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { AccountService } from './account.service';
describe('AccountService', () => {
let service: AccountService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(AccountService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,45 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { environment } from '../../environments/environment';
import { User } from './user';
@Injectable({
providedIn: 'root'
})
export class AccountService {
private userSubject: BehaviorSubject<User>;
public user: Observable<User>;
constructor(private httpClient: HttpClient) {
this.userSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('user')));
this.user = this.userSubject.asObservable();
}
public get userValue() {
return this.userSubject.value;
}
login(username, password) {
const body = new HttpParams()
.set('username', username)
.set('password', password);
return this.httpClient.post<User>(environment.apiUrl + '/login', body)
.pipe((map(user => {
localStorage.setItem('user', JSON.stringify(user));
this.userSubject.next(user);
return user;
})))
}
register(user) {
const body = new HttpParams()
.set('username', user.username)
.set('password', user.password)
.set('email', user.email);
return this.httpClient.post(environment.apiUrl + '/signup', body);
}
}

View File

@@ -0,0 +1,7 @@
import { AuthGuard } from './auth.guard';
describe('Auth.Guard', () => {
it('should create an instance', () => {
expect(new AuthGuard()).toBeTruthy();
});
});

View File

@@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AccountService } from './account.service';
@Injectable({providedIn: 'root'})
export class AuthGuard implements CanActivate {
constructor(private router: Router,
private accountService: AccountService) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const user = this.accountService.userValue;
if (user) {
return true;
}
this.router.navigate(['/login'], {queryParams: {returnURL: state.url}});
return false;
}
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { JwtInterceptor } from './jwt.interceptor';
describe('JwtInterceptor', () => {
beforeEach(() => TestBed.configureTestingModule({
providers: [
JwtInterceptor
]
}));
it('should be created', () => {
const interceptor: JwtInterceptor = TestBed.inject(JwtInterceptor);
expect(interceptor).toBeTruthy();
});
});

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { AccountService } from './account.service';
@Injectable()
export class JwtInterceptor implements HttpInterceptor {
constructor(private accountService: AccountService) {}
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
const user = this.accountService.userValue;
const isLoggedIn = user && user.token;
const isApiUrl = request.url.startsWith(environment.apiUrl);
if (isLoggedIn && isApiUrl) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${user.token}`
}
});
}
return next.handle(request);
}
}

View File

@@ -0,0 +1,22 @@
.login-card {
width: 400px;
}
.login-container {
width: 100%;
height: 100%;
background-color: #333333;
}
.login-form {
padding: 10px 40px;
}
.mat-button {
width: 80px;
}
.mat-form-field {
width: 100%;
padding-top: 15px;
}

View File

@@ -0,0 +1,30 @@
<div class="login-container" fxLayout="row" fxLayoutAlign="center center">
<mat-card class="login-card" fxLayout="column" fxLayoutAlign="center center">
<mat-card-title>Login</mat-card-title>
<mat-card-content>
<form class="login-form" [formGroup]="form" (ngSubmit)="onLogin()">
<mat-form-field appearance="fill">
<mat-label>User name</mat-label>
<label>
<input matInput formControlName="username" required>
</label>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Password</mat-label>
<label>
<input matInput formControlName="password" type="password" required minlength="15">
</label>
</mat-form-field>
<div fxLayout="row" fxLayoutAlign="space-evenly center">
<button mat-flat-button color="primary" [disabled]="loading">
<span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
Login
</button>
<a mat-button routerLink="/signup">Sign up</a>
</div>
</form>
</mat-card-content>
</mat-card>
</div>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,43 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, Validators} from '@angular/forms';
import { AccountService } from '../account.service';
import { ActivatedRoute, Router } from '@angular/router';
import { first } from 'rxjs/operators';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
form = this.formBuilder.group({
username: ['', Validators.required],
password: ['', [Validators.required, Validators.minLength(15)]],
});
loading = false;
returnUrl = this.activatedRoute.snapshot.queryParams['returnUrl'] || '/';
onLogin() {
this.loading = true;
this.accountService.login(this.form.get('username').value, this.form.get('password').value)
.pipe(first())
.subscribe(data => {
this.router.navigate([this.returnUrl]);
},
error => {
// TODO error handling
console.log(error);
this.loading = false;
});
}
constructor(private accountService: AccountService,
private activatedRoute: ActivatedRoute,
private formBuilder: FormBuilder,
private router: Router,
) { }
ngOnInit(): void {
}
}

View File

@@ -0,0 +1,7 @@
import { PasswordErrorStateMatcher } from './password-error-state-matcher';
describe('PasswordErrorStateMatcher', () => {
it('should create an instance', () => {
expect(new PasswordErrorStateMatcher()).toBeTruthy();
});
});

View File

@@ -0,0 +1,11 @@
import { ErrorStateMatcher } from '@angular/material/core';
import { FormControl, FormGroupDirective, NgForm } from '@angular/forms';
export class PasswordErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const invalidControl = !!(control && control.invalid && control.parent.touched);
const invalidParent = !!(control && control.parent && control.parent.invalid && control.parent.touched);
return invalidControl || invalidParent;
}
}

View File

@@ -0,0 +1,22 @@
.register-card {
width: 400px;
}
.register-container {
width: 100%;
height: 100%;
background-color: #333333;
}
.register-form {
padding: 10px 40px;
}
.mat-button {
width: 80px;
}
.mat-form-field {
width: 100%;
padding-top: 15px;
}

View File

@@ -0,0 +1,48 @@
<div class="register-container" fxLayout="row" fxLayoutAlign="center center">
<mat-card class="register-card" fxLayout="column" fxLayoutAlign="center center">
<mat-card-title>Sign up</mat-card-title>
<mat-card-content>
<form class="register-form" [formGroup]="form" (ngSubmit)="onRegister()">
<mat-form-field appearance="fill">
<mat-label>User name</mat-label>
<label>
<input matInput formControlName="username" required>
</label>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Email</mat-label>
<label>
<input matInput formControlName="email" required email>
</label>
</mat-form-field>
<div formGroupName="password">
<mat-form-field appearance="fill" hintLabel="At least 15 characters.">
<mat-label>Password</mat-label>
<label>
<input matInput formControlName="first" type="password" required minlength="15">
</label>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Confirm password</mat-label>
<label>
<input matInput formControlName="second" type="password" required minlength="15"
[errorStateMatcher]="passwordErrorStateMatcher">
</label>
<mat-error *ngIf="form.get('password').hasError('mismatch')">Passwords don't match.</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutAlign="space-evenly center">
<button mat-flat-button color="primary" [disabled]="loading">
<span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
Sign up
</button>
<a mat-button routerLink="/login">Login</a>
</div>
</form>
</mat-card-content>
</mat-card>
</div>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SignupComponent } from './signup.component';
describe('RegisterComponent', () => {
let component: SignupComponent;
let fixture: ComponentFixture<SignupComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SignupComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SignupComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,65 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { first } from 'rxjs/operators';
import { AccountService } from '../account.service';
import { PasswordErrorStateMatcher } from './password-error-state-matcher';
@Component({
selector: 'app-register',
templateUrl: './signup.component.html',
styleUrls: ['./signup.component.css']
})
export class SignupComponent implements OnInit {
form = this.formBuilder.group({
username: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
password: this.formBuilder.group({
first: ['', [Validators.required, Validators.minLength(15)]],
second: ['', [Validators.required, Validators.minLength(15)]],
},
{ validators: this.passwordsEqual }),
});
loading = false;
passwordErrorStateMatcher = new PasswordErrorStateMatcher();
constructor(private accountService: AccountService,
private formBuilder: FormBuilder,
private router: Router,
) { }
passwordsEqual(passwords: FormGroup): ValidationErrors | null {
const password1 = passwords.get('first');
const password2 = passwords.get('second');
return password1 && password2 && password1.value === password2.value ? null : { mismatch: true };
}
readForm() {
return {
username: this.form.get('username').value,
email: this.form.get('email').value,
password: this.form.get('password').get('first').value,
}
}
onRegister() {
if (this.form.invalid) {
return;
}
this.loading = true;
this.accountService.register(this.readForm())
.pipe(first())
.subscribe(data => {
this.router.navigate(['/login']);
},
error => {
// TODO error handling
console.log(error);
this.loading = false;
});
}
ngOnInit(): void { }
}

View File

@@ -0,0 +1,7 @@
import { User } from './user';
describe('User', () => {
it('should create an instance', () => {
expect(new User()).toBeTruthy();
});
});

7
src/app/account/user.ts Normal file
View File

@@ -0,0 +1,7 @@
export class User {
id: number;
character: string;
username: string;
email: string;
token: string;
}

View File

@@ -1,8 +1,22 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { AuthGuard } from './account/auth.guard';
import { LoginComponent } from './account/login/login.component';
import { SignupComponent } from './account/signup/signup.component';
const routes: Routes = [];
const gameModule = () => import('./game/game.module').then(x => x.GameModule);
const routes: Routes = [
{ path: '', component: AppComponent, canActivate: [AuthGuard] },
// { path: '', redirectTo: '/game', pathMatch: 'prefix', canActivate: [AuthGuard] },
{ path: 'login', component: LoginComponent },
{ path: 'signup', component: SignupComponent },
{ path: 'game', loadChildren: gameModule, canActivate: [AuthGuard] },
{ path: '**', redirectTo: '' },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],

View File

@@ -16,69 +16,6 @@ svg.material-icons:not(:last-child) {
margin-right: 8px;
}
.card svg.material-icons path {
fill: #888;
}
.card-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
/* margin-top: 16px; */
}
.card {
border-radius: 4px;
border: 1px solid #eee;
background-color: #fafafa;
height: 40px;
width: 200px;
margin: 0 8px 16px;
padding: 16px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
transition: all 0.2s ease-in-out;
line-height: 24px;
}
.card-container .card:not(:last-child) {
margin-right: 0;
}
.card.card-small {
height: 16px;
width: 168px;
}
.card-container .card:not(.highlight-card) {
cursor: pointer;
}
.card-container .card:not(.highlight-card):hover {
transform: translateY(-3px);
box-shadow: 0 4px 17px rgba(0, 0, 0, 0.35);
}
.card-container .card:not(.highlight-card):hover .material-icons path {
fill: rgb(105, 103, 103);
}
.card.highlight-card {
background-color: #1976d2;
color: white;
font-weight: 600;
border: none;
width: auto;
min-width: 30%;
position: relative;
}
.card.card.highlight-card span {
margin-left: 60px;
}
/* Responsive Styles */
@media screen and (max-width: 767px) {
@@ -93,6 +30,10 @@ svg.material-icons:not(:last-child) {
}
.router {
height: 100%;
}
.chat {
float: right;
height: 100%;

View File

@@ -1,20 +1,3 @@
<div class="chat">
<app-chat></app-chat>
<div class="router">
<router-outlet></router-outlet>
</div>
<div>
<div class="card-container">
<div class="card card-small" (click)="onClickSocket()" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Send test message</span>
</div>
<div class="card card-small" (click)="onClickApi()" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Call rest api</span>
</div>
</div>
</div>
<router-outlet></router-outlet>

View File

@@ -1,6 +1,6 @@
import { Component } from '@angular/core';
import { SocketService } from './socket/socket.service';
import { HttpClient } from '@angular/common/http';
import { AccountService } from './account/account.service';
@Component({
selector: 'app-root',
@@ -10,22 +10,14 @@ import { HttpClient } from '@angular/common/http';
export class AppComponent {
title = 'rona-frontend';
onClickSocket() {
this.socketService.send('test', {'user': 'USERNAME', 'payload': 'PAYLOAD TEST'})
}
onClickApi() {
this.httpService.get('http://localhost:5005/').subscribe(response => {
console.log('REST API call returned: ', response);
});
}
/**
*
* @param accountService
* @param socketService
* @param httpService
*/
constructor(private socketService: SocketService,
private httpService: HttpClient) { }
constructor(private accountService: AccountService,
private socketService: SocketService,
) { }
}

View File

@@ -1,32 +1,47 @@
import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ChatComponent } from './chat/chat.component';
import { EntryComponent } from './chat/entry/entry.component';
import { InputComponent } from './chat/input/input.component';
import {MatCardModule} from '@angular/material/card';
import {MatInputModule} from '@angular/material/input';
import { LoginComponent } from './account/login/login.component';
import { SignupComponent } from './account/signup/signup.component';
import { GameModule } from './game/game.module';
import { JwtInterceptor } from './account/jwt.interceptor';
@NgModule({
declarations: [
AppComponent,
ChatComponent,
EntryComponent,
InputComponent
LoginComponent,
SignupComponent,
],
imports: [
HttpClientModule,
BrowserModule,
BrowserAnimationsModule,
FlexLayoutModule,
MatButtonModule,
MatCardModule,
MatIconModule,
MatInputModule,
ReactiveFormsModule,
AppRoutingModule,
GameModule,
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
// fakeBackendProvider,
],
exports: [
],
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule,
BrowserAnimationsModule,
MatCardModule,
MatInputModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@@ -1,4 +1,5 @@
#chat-log {
height: calc(99% - 34px);
overflow-y: scroll;
background-color: goldenrod;
}

View File

@@ -1,7 +1,10 @@
<div id="chat-log">
<app-entry *ngFor="let entry of entries"
[entry]=entry>
</app-entry>
<div *ngFor="let entry of entries">
<div [ngSwitch]="entry.type">
<app-entry *ngSwitchCase="'messages'" [entry]="entry"></app-entry>
<app-system-entry *ngSwitchCase="'system'" [entry]="entry"></app-system-entry>
</div>
</div>
</div>
<app-input></app-input>

View File

@@ -1,7 +1,9 @@
import { Component, OnInit } from '@angular/core';
import { Entry } from './entry/entry';
import { Message } from './message';
import {SocketService} from '../socket/socket.service';
import { SystemMessage } from './entry/entry';
import { Messages } from './entry/messages/messages';
import { SocketService } from '../socket/socket.service';
@Component({
selector: 'app-chat',
@@ -10,28 +12,39 @@ import {SocketService} from '../socket/socket.service';
})
export class ChatComponent implements OnInit {
entries = new Array<Entry>();
entries = new Array<Messages|SystemMessage>();
public addMessage(message: Message): void {
if ((this.entries.length > 0)
&& (this.entries[this.entries.length - 1].character == message.sender)) {
this.entries[this.entries.length - 1].messages.push(message.message);
&& (this.entries[this.entries.length - 1].type === 'messages')) {
let entry = this.entries[this.entries.length - 1] as Messages;
if (entry.character == message.character) {
entry.messages.push(message);
} else {
this.entries.push(new Messages(message));
}
} else {
this.entries.push(new Entry(message.sender, 'Aangular User', message.message));
this.entries.push(new Messages(message));
}
window.setTimeout(ChatComponent.scrollToBottom, 5);
}
public addSystemMessage(message: SystemMessage): void {
this.entries.push(message);
}
static scrollToBottom() {
const chatLog = document.getElementById('chat-log');
chatLog.scrollTop = chatLog.scrollHeight;
}
constructor(private socketService: SocketService) {
socketService.onTestMessage().subscribe((message: Message) => {
console.log(message);
socketService.onPublicMessage().subscribe((message: Message) => {
this.addMessage(message);
});
socketService.onSystemMessage().subscribe((message: SystemMessage) => {
this.addSystemMessage(message);
})
}
ngOnInit(): void {

View File

@@ -0,0 +1,29 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatInputModule } from '@angular/material/input';
import { ChatComponent } from './chat.component';
import { SystemMessageComponent } from './entry/system-message/system-message.component';
import { MessagesComponent } from './entry/messages/messages.component';
import { InputComponent } from './input/input.component';
@NgModule({
declarations: [
ChatComponent,
InputComponent,
MessagesComponent,
SystemMessageComponent
],
exports: [
ChatComponent,
],
imports: [
CommonModule,
MatCardModule,
MatInputModule
]
})
export class ChatModule { }

View File

@@ -1,18 +0,0 @@
import { Component, Input, OnInit } from '@angular/core';
import { Entry } from './entry';
@Component({
selector: 'app-entry',
templateUrl: './entry.component.html',
styleUrls: ['./entry.component.css']
})
export class EntryComponent implements OnInit {
@Input() entry: Entry;
constructor() { }
ngOnInit(): void {
}
}

View File

@@ -1,7 +1,7 @@
import { Entry } from './entry';
import { Messages } from './entry';
describe('Entry', () => {
it('should create an instance', () => {
expect(new Entry()).toBeTruthy();
expect(new Messages()).toBeTruthy();
});
});

View File

@@ -1,10 +1,25 @@
export class Entry {
export abstract class Entry {
public timestamp;
public messages: Array<string> = new Array<string>();
constructor(public character: string,
public user: string,
message: string) {
this.messages.push(message);
protected constructor() {
this.timestamp = new Date();
}
}
export class SystemMessage extends Entry {
constructor(public message: string,
public severity: SeverityEnum) {
super();
}
public get type(): string {
return 'system'
}
}
export enum SeverityEnum {
info = 'info',
warning = 'warning',
error = 'error',
}

View File

@@ -0,0 +1,6 @@
.eye {
margin-right: 4px;
padding-left: 2px;
padding-right: 2px;
border: solid 1px;
}

View File

@@ -3,13 +3,14 @@
<div mat-card-avatar class="avatar"></div>
<mat-card-title class="character">
{{entry.character}}
<div class="timestamp">23:31</div>
<div class="timestamp">{{entry.timestamp | date:'HH:mm' }}</div>
</mat-card-title>
<mat-card-subtitle class="user">played by {{entry.user}}</mat-card-subtitle>
<mat-card-subtitle class="user">{{entry.user}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div class="messages" *ngFor="let message of entry.messages">
{{message}}
<div>{{message.message}}</div>
<span *ngFor="let eye of message.eyes" class="eye">{{eye}}</span><span *ngIf="message.result">&rarr; {{message.result}}</span>
</div>
</mat-card-content>
</mat-card>

View File

@@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { EntryComponent } from './entry.component';
import { MessagesComponent } from './messages.component';
describe('EntryComponent', () => {
let component: EntryComponent;
let fixture: ComponentFixture<EntryComponent>;
let component: MessagesComponent;
let fixture: ComponentFixture<MessagesComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ EntryComponent ]
declarations: [ MessagesComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EntryComponent);
fixture = TestBed.createComponent(MessagesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@@ -0,0 +1,18 @@
import { Component, Input, OnInit } from '@angular/core';
import { Messages } from './messages';
@Component({
selector: 'app-entry',
templateUrl: './messages.component.html',
styleUrls: ['../entry.component.css', './messages.component.css']
})
export class MessagesComponent implements OnInit {
@Input() entry: Messages;
constructor() { }
ngOnInit(): void {
}
}

View File

@@ -0,0 +1,7 @@
import { Messages } from './messages';
describe('Messages', () => {
it('should create an instance', () => {
expect(new Messages()).toBeTruthy();
});
});

View File

@@ -0,0 +1,23 @@
import { Entry } from '../entry';
import { Message } from '../../message';
export class Messages extends Entry {
public messages: Array<Message> = new Array<Message>();
constructor(message: Message) {
super();
this.messages.push(message);
}
public get character(): string {
return this.messages[0].character;
}
public get type(): string {
return 'messages'
}
public get user(): string {
return this.messages[0].user;
}
}

View File

@@ -0,0 +1,3 @@
.system-avatar {
background-size: cover;
}

View File

@@ -0,0 +1,15 @@
<mat-card>
<mat-card-header class="header">
<div mat-card-avatar class="system-avatar">
<img [alt]="entry.severity" src="../../../../assets/build_circle-24px.svg">
</div>
<mat-card-title class="character">
{{entry.severity | titlecase}}
<div class="timestamp">{{entry.timestamp | date:'HH:mm' }}</div>
</mat-card-title>
<mat-card-subtitle class="user">System</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
{{entry.message}}
</mat-card-content>
</mat-card>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SystemMessageComponent } from './system-message.component';
describe('SystemEntryComponent', () => {
let component: SystemMessageComponent;
let fixture: ComponentFixture<SystemMessageComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SystemMessageComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SystemMessageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,18 @@
import { Component, Input, OnInit } from '@angular/core';
import { SystemMessage } from '../entry';
@Component({
selector: 'app-system-entry',
templateUrl: './system-message.component.html',
styleUrls: ['../entry.component.css']
})
export class SystemMessageComponent implements OnInit {
@Input() entry: SystemMessage;
constructor() { }
ngOnInit(): void {
}
}

View File

@@ -1,7 +1,9 @@
import { Component, OnInit } from '@angular/core';
import {SocketService} from '../../socket/socket.service';
import {Message} from '../message';
import {Events} from '../../socket/events-enum';
import { Message } from '../message';
import { Events } from '../../socket/events-enum';
import { SocketService } from '../../socket/socket.service';
import { AccountService } from '../../account/account.service';
@Component({
selector: 'app-input',
@@ -11,11 +13,15 @@ import {Events} from '../../socket/events-enum';
export class InputComponent implements OnInit {
onEnter(value: string): void {
const message = new Message('Aangular Frontend', value);
this.socketService.send(Events.publicMessage, message);
if (value.length > 0) {
const user = this.accountService.userValue;
const message = new Message(user.character, user.username, value);
this.socketService.send(Events.publicMessage, message);
}
}
constructor(private socketService: SocketService) { }
constructor(private accountService: AccountService,
private socketService: SocketService) { }
ngOnInit(): void {
}

View File

@@ -1,6 +1,11 @@
export class Message {
constructor(public sender: string,
public eyes?: Array<number>;
public result?: number;
constructor(public character: string,
public user: string,
public message: string) {
}

View File

@@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { GameComponent } from './game.component';
import { TestComponent } from './test/test.component';
const routes: Routes = [
{ path: '', component: GameComponent,
children: [
{ path: '', redirectTo: 'test', pathMatch: 'prefix' },
{ path: 'test', component: TestComponent },
]}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class GameRoutingModule { }

View File

@@ -0,0 +1,7 @@
#game-items {
height: 100%;
}
#navbar {
background-color: #333333;
}

View File

@@ -0,0 +1,11 @@
<div fxLayout="row" id="game-items">
<div fxFlex="48px" id="navbar">
<app-navbar></app-navbar>
</div>
<div fxFlex>
<router-outlet></router-outlet>
</div>
<div fxFlex="20%">
<app-chat></app-chat>
</div>
</div>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { GameComponent } from './game.component';
describe('GameComponent', () => {
let component: GameComponent;
let fixture: ComponentFixture<GameComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ GameComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(GameComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-game',
templateUrl: './game.component.html',
styleUrls: ['./game.component.css']
})
export class GameComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@@ -0,0 +1,31 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FlexModule } from '@angular/flex-layout';
import { MatCardModule } from '@angular/material/card';
import { GameRoutingModule } from './game-routing.module';
import { GameComponent } from './game.component';
import { NavbarComponent } from './navbar/navbar.component';
import { TestComponent } from './test/test.component';
import { ChatModule } from '../chat/chat.module';
@NgModule({
declarations: [
GameComponent,
NavbarComponent,
TestComponent,
],
exports: [
NavbarComponent
],
imports: [
CommonModule,
GameRoutingModule,
FlexModule,
MatCardModule,
ChatModule,
]
})
export class GameModule { }

View File

@@ -0,0 +1,8 @@
.nav-img {
width: 40px;
height: 40px;
}
.nav-item {
margin: 2px 4px;
}

View File

@@ -0,0 +1,11 @@
<div fxLayout="column" fxLayoutAlign=" center">
<a routerLink="test">
<img class="nav-img" src="assets/build_circle-24px.svg" alt="TODO">
</a>
<a routerLink="test">
<img class="nav-img" src="assets/build_circle-24px.svg" alt="TODO">
</a>
<a routerLink="test">
<img class="nav-img" src="assets/build_circle-24px.svg" alt="TODO">
</a>
</div>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NavbarComponent } from './navbar.component';
describe('NavbarComponent', () => {
let component: NavbarComponent;
let fixture: ComponentFixture<NavbarComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ NavbarComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NavbarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

View File

@@ -0,0 +1,15 @@
<div>
<div class="card-container">
<div class="card card-small" (click)="onClickSocket()" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Send test message</span>
</div>
<div class="card card-small" (click)="onClickApi()" tabindex="0">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Call rest api</span>
</div>
</div>
</div>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TestComponent } from './test.component';
describe('TestComponent', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TestComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,29 @@
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {SocketService} from '../../socket/socket.service';
@Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit {
onClickSocket() {
this.socketService.send('test', {'user': 'USERNAME', 'payload': 'PAYLOAD TEST'})
}
onClickApi() {
this.httpService.get('http://localhost:5005/').subscribe(response => {
console.log('REST API call returned: ', response);
});
}
constructor(private httpService: HttpClient,
private socketService: SocketService,
) { }
ngOnInit(): void {
}
}

View File

@@ -1,3 +1,4 @@
export enum Events {
publicMessage = 'public message'
publicMessage = 'public message',
systemMessage = 'system message',
}

View File

@@ -3,6 +3,8 @@ import { Observable } from 'rxjs';
import * as socketIo from 'socket.io-client';
import { Events } from './events-enum';
import { SystemMessage } from '../chat/entry/entry';
import { Message } from '../chat/message';
const SERVER_URL = 'http://localhost:5005'
@@ -26,12 +28,21 @@ export class SocketService implements OnInit {
this.socket.emit(event, message);
}
public onTestMessage(): Observable<any> {
return new Observable<any>(observer => {
public onPublicMessage(): Observable<Message> {
return new Observable<Message>(observer => {
this.socket.on(Events.publicMessage, (data) => observer.next(data));
});
}
public onSystemMessage(): Observable<SystemMessage> {
return new Observable<SystemMessage>(observer => {
this.socket.on(Events.systemMessage, (data: SystemMessage) => {
data = Object.assign(SystemMessage, data);
observer.next(new SystemMessage(data.message, data.severity));
});
});
}
constructor() {
this.initSocket();
}

View File

@@ -0,0 +1,7 @@
import { FakeBackend } from './fake-backend';
describe('FakeBackend', () => {
it('should create an instance', () => {
expect(new FakeBackend()).toBeTruthy();
});
});

View File

@@ -0,0 +1,75 @@
import { Injectable } from '@angular/core';
import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HTTP_INTERCEPTORS } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators';
let users = JSON.parse(localStorage.getItem('users')) || [];
@Injectable()
export class FakeBackend implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const { url, method, headers, body } = request;
return of(null)
.pipe(mergeMap(handleRoute))
.pipe(materialize())
.pipe(delay(500))
.pipe(dematerialize());
function handleRoute() {
switch (true) {
case url.endsWith('fake_login') && method === 'POST':
return authenticate();
case url.endsWith('fake_registration') && method === 'POST':
return register();
default:
return next.handle(request);
}
}
function authenticate() {
const { username, password } = body;
const user = users.find(x => x.username === username.value && x.password === password.value);
if (!user) {
console.log(password);
return error('Username or password is incorrect.');
}
return ok({
id: user.id,
username: user.username,
character: user.character,
token: 'fake-jwt-token',
});
}
function register() {
const user = body;
if (users.find(x => x.username === user.username)) {
return error('Username ' + user.username + ' is already taken.');
}
user.id = users.length ? Math.max(...users.map(x => x.id)) + 1 : 1;
user.character = 'placeholder';
users.push(user);
localStorage.setItem('users', JSON.stringify(users));
console.log('Register user: ' + user);
return ok();
}
function ok(body?) {
return of(new HttpResponse({ status: 200, body }));
}
function error(message) {
return throwError({ error: { message } });
}
}
}
export const fakeBackendProvider = {
provide: HTTP_INTERCEPTORS,
useClass: FakeBackend,
multi: true,
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24"><g><rect fill="none" height="24" width="24"/><rect fill="none" height="24" width="24"/></g><g><g><path d="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10s10-4.48,10-10 C22,6.48,17.52,2,12,2z M16.54,15.85l-0.69,0.69c-0.39,0.39-1.02,0.39-1.41,0l-3.05-3.05c-1.22,0.43-2.64,0.17-3.62-0.81 c-1.11-1.11-1.3-2.79-0.59-4.1l2.35,2.35l1.41-1.41L8.58,7.17c1.32-0.71,2.99-0.52,4.1,0.59c0.98,0.98,1.24,2.4,0.81,3.62 l3.05,3.05C16.93,14.82,16.93,15.46,16.54,15.85z" fill-rule="evenodd"/></g></g></svg>

After

Width:  |  Height:  |  Size: 602 B

View File

@@ -3,7 +3,8 @@
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
production: false,
apiUrl: 'http://localhost:5005',
};
/*

View File

@@ -10,6 +10,6 @@
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<app-root></app-root>
<app-root></app-root>
</body>
</html>