Compare commits

...

15 Commits

Author SHA1 Message Date
Paddy 7cb3c12351 Comments and docs
Jenkins Production Deployment
2021-10-03 12:10:54 +02:00
Paddy 84aaa9ed99 Removing unused component, UI optimization
Jenkins Production Deployment
2021-10-03 11:34:54 +02:00
Paddy 7224a732e2 Saving week and other preferences in localstorage so they are persistent
Jenkins Production Deployment
2021-10-03 10:28:36 +02:00
Paddy d7c9fc9265 Sorting events on a day by their starting time
Jenkins Production Deployment
2021-10-02 13:13:27 +02:00
Paddy 347875390f Better displaying of dates and times
Jenkins Production Deployment
2021-10-02 12:50:11 +02:00
Paddy 6b55c438f6 Adding ability to hide deleted events
Jenkins Production Deployment
2021-10-02 12:13:18 +02:00
Paddy 59682c3091 First Alpha release candidate
Jenkins Production Deployment
2021-10-01 19:25:39 +02:00
Paddy 8e1c071645 Adding some fancy buttons to the datepicker
Jenkins Production Deployment
2021-09-30 20:56:50 +02:00
Paddy 5c3870585e Basic UI for events in a week 2021-09-30 20:43:01 +02:00
Paddy fd27e3ff39 Adding week and day components - POC release
Jenkins Production Deployment
2021-09-30 15:24:49 +02:00
Paddy e97cafd1ac Writing notifying framework to pass selected date to parent component 2021-09-30 13:41:46 +02:00
Paddy 7a2f03c241 Reformatting 2021-09-30 13:26:06 +02:00
Paddy 1ce1f1875a Adding datepicker component 2021-09-30 13:18:20 +02:00
Paddy cb27348020 Adding basic components and API service 2021-09-29 13:04:04 +02:00
Paddy b15a755e12 Adding components, reformatting and stuff 2021-09-19 22:29:59 +02:00
50 changed files with 1737 additions and 922 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ root = true
[*] [*]
charset = utf-8 charset = utf-8
indent_style = space indent_style = space
indent_size = 2 indent_size = 4
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
+333 -315
View File
File diff suppressed because it is too large Load Diff
+40 -37
View File
@@ -1,39 +1,42 @@
{ {
"name": "com.p4ddy.rapla", "name": "com.p4ddy.rapla",
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
"build": "ng build", "build": "ng build",
"watch": "ng build --watch --configuration development", "watch": "ng build --watch --configuration development",
"test": "ng test" "test": "ng test"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "~12.2.0", "@angular/animations": "~12.2.0",
"@angular/common": "~12.2.0", "@angular/common": "~12.2.0",
"@angular/compiler": "~12.2.0", "@angular/compiler": "~12.2.0",
"@angular/core": "~12.2.0", "@angular/core": "~12.2.0",
"@angular/forms": "~12.2.0", "@angular/forms": "~12.2.0",
"@angular/platform-browser": "~12.2.0", "@angular/material": "^12.2.8",
"@angular/platform-browser-dynamic": "~12.2.0", "@angular/platform-browser": "~12.2.0",
"@angular/router": "~12.2.0", "@angular/platform-browser-dynamic": "~12.2.0",
"rxjs": "~6.6.0", "@angular/router": "~12.2.0",
"tslib": "^2.3.0", "@ng-bootstrap/ng-bootstrap": "^10.0.0",
"zone.js": "~0.11.4" "bootstrap": "^5.1.1",
}, "rxjs": "~6.6.0",
"devDependencies": { "tslib": "^2.3.0",
"@angular-devkit/build-angular": "~12.2.6", "zone.js": "~0.11.4"
"@angular/cli": "~12.2.6", },
"@angular/compiler-cli": "~12.2.0", "devDependencies": {
"@types/jasmine": "~3.8.0", "@angular-devkit/build-angular": "~12.2.6",
"@types/node": "^12.11.1", "@angular/cli": "~12.2.6",
"jasmine-core": "~3.8.0", "@angular/compiler-cli": "~12.2.0",
"karma": "~6.3.0", "@types/jasmine": "~3.8.0",
"karma-chrome-launcher": "~3.1.0", "@types/node": "^12.20.27",
"karma-coverage": "~2.0.3", "jasmine-core": "~3.8.0",
"karma-jasmine": "~4.0.0", "karma": "~6.3.0",
"karma-jasmine-html-reporter": "~1.7.0", "karma-chrome-launcher": "~3.1.0",
"typescript": "~4.3.5" "karma-coverage": "~2.0.3",
} "karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"typescript": "~4.3.5"
}
} }
+1 -500
View File
@@ -1,500 +1 @@
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * --> <router-outlet></router-outlet>
<!-- * * * * * * * * * * * The content below * * * * * * * * * * * -->
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * Delete the template below * * * * * * * * * * -->
<!-- * * * * * * * to get started with your project! * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<style>
:host {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 14px;
color: #333;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 8px 0;
}
p {
margin: 0;
}
.spacer {
flex: 1;
}
.toolbar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 60px;
display: flex;
align-items: center;
background-color: #1976d2;
color: white;
font-weight: 600;
}
.toolbar img {
margin: 0 16px;
}
.toolbar #twitter-logo {
height: 40px;
margin: 0 8px;
}
.toolbar #youtube-logo {
height: 40px;
margin: 0 16px;
}
.toolbar #twitter-logo:hover,
.toolbar #youtube-logo:hover {
opacity: 0.8;
}
.content {
display: flex;
margin: 82px auto 32px;
padding: 0 16px;
max-width: 960px;
flex-direction: column;
align-items: center;
}
svg.material-icons {
height: 24px;
width: auto;
}
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 {
all: unset;
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;
}
svg#rocket {
width: 80px;
position: absolute;
left: -10px;
top: -24px;
}
svg#rocket-smoke {
height: calc(100vh - 95px);
position: absolute;
top: 10px;
right: 180px;
z-index: -10;
}
a,
a:visited,
a:hover {
color: #1976d2;
text-decoration: none;
}
a:hover {
color: #125699;
}
.terminal {
position: relative;
width: 80%;
max-width: 600px;
border-radius: 6px;
padding-top: 45px;
margin-top: 8px;
overflow: hidden;
background-color: rgb(15, 15, 16);
}
.terminal::before {
content: "\2022 \2022 \2022";
position: absolute;
top: 0;
left: 0;
height: 4px;
background: rgb(58, 58, 58);
color: #c2c3c4;
width: 100%;
font-size: 2rem;
line-height: 0;
padding: 14px 0;
text-indent: 4px;
}
.terminal pre {
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
color: white;
padding: 0 1rem 1rem;
margin: 0;
}
.circle-link {
height: 40px;
width: 40px;
border-radius: 40px;
margin: 8px;
background-color: white;
border: 1px solid #eeeeee;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
transition: 1s ease-out;
}
.circle-link:hover {
transform: translateY(-0.25rem);
box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.2);
}
footer {
margin-top: 8px;
display: flex;
align-items: center;
line-height: 20px;
}
footer a {
display: flex;
align-items: center;
}
.github-star-badge {
color: #24292e;
display: flex;
align-items: center;
font-size: 12px;
padding: 3px 10px;
border: 1px solid rgba(27,31,35,.2);
border-radius: 3px;
background-image: linear-gradient(-180deg,#fafbfc,#eff3f6 90%);
margin-left: 4px;
font-weight: 600;
}
.github-star-badge:hover {
background-image: linear-gradient(-180deg,#f0f3f6,#e6ebf1 90%);
border-color: rgba(27,31,35,.35);
background-position: -.5em;
}
.github-star-badge .material-icons {
height: 16px;
width: 16px;
margin-right: 4px;
}
svg#clouds {
position: fixed;
bottom: -160px;
left: -230px;
z-index: -10;
width: 1920px;
}
/* Responsive Styles */
@media screen and (max-width: 767px) {
.card-container > *:not(.circle-link) ,
.terminal {
width: 100%;
}
.card:not(.highlight-card) {
height: 16px;
margin: 8px 0;
}
.card.highlight-card span {
margin-left: 72px;
}
svg#rocket-smoke {
right: 120px;
transform: rotate(-5deg);
}
}
@media screen and (max-width: 575px) {
svg#rocket-smoke {
display: none;
visibility: hidden;
}
}
</style>
<!-- Toolbar -->
<div class="toolbar" role="banner">
<img
width="40"
alt="Angular Logo"
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="
/>
<span>Welcome</span>
<div class="spacer"></div>
<a aria-label="Angular on twitter" target="_blank" rel="noopener" href="https://twitter.com/angular" title="Twitter">
<svg id="twitter-logo" height="24" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
<rect width="400" height="400" fill="none"/>
<path d="M153.62,301.59c94.34,0,145.94-78.16,145.94-145.94,0-2.22,0-4.43-.15-6.63A104.36,104.36,0,0,0,325,122.47a102.38,102.38,0,0,1-29.46,8.07,51.47,51.47,0,0,0,22.55-28.37,102.79,102.79,0,0,1-32.57,12.45,51.34,51.34,0,0,0-87.41,46.78A145.62,145.62,0,0,1,92.4,107.81a51.33,51.33,0,0,0,15.88,68.47A50.91,50.91,0,0,1,85,169.86c0,.21,0,.43,0,.65a51.31,51.31,0,0,0,41.15,50.28,51.21,51.21,0,0,1-23.16.88,51.35,51.35,0,0,0,47.92,35.62,102.92,102.92,0,0,1-63.7,22A104.41,104.41,0,0,1,75,278.55a145.21,145.21,0,0,0,78.62,23" fill="#fff"/>
</svg>
</a>
<a aria-label="Angular on YouTube" target="_blank" rel="noopener" href="https://youtube.com/angular" title="YouTube">
<svg id="youtube-logo" height="24" width="24" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#fff">
<path d="M0 0h24v24H0V0z" fill="none"/>
<path d="M21.58 7.19c-.23-.86-.91-1.54-1.77-1.77C18.25 5 12 5 12 5s-6.25 0-7.81.42c-.86.23-1.54.91-1.77 1.77C2 8.75 2 12 2 12s0 3.25.42 4.81c.23.86.91 1.54 1.77 1.77C5.75 19 12 19 12 19s6.25 0 7.81-.42c.86-.23 1.54-.91 1.77-1.77C22 15.25 22 12 22 12s0-3.25-.42-4.81zM10 15V9l5.2 3-5.2 3z"/>
</svg>
</a>
</div>
<div class="content" role="main">
<!-- Highlight Card -->
<div class="card highlight-card card-small">
<svg id="rocket" xmlns="http://www.w3.org/2000/svg" width="101.678" height="101.678" viewBox="0 0 101.678 101.678">
<title>Rocket Ship</title>
<g id="Group_83" data-name="Group 83" transform="translate(-141 -696)">
<circle id="Ellipse_8" data-name="Ellipse 8" cx="50.839" cy="50.839" r="50.839" transform="translate(141 696)" fill="#dd0031"/>
<g id="Group_47" data-name="Group 47" transform="translate(165.185 720.185)">
<path id="Path_33" data-name="Path 33" d="M3.4,42.615a3.084,3.084,0,0,0,3.553,3.553,21.419,21.419,0,0,0,12.215-6.107L9.511,30.4A21.419,21.419,0,0,0,3.4,42.615Z" transform="translate(0.371 3.363)" fill="#fff"/>
<path id="Path_34" data-name="Path 34" d="M53.3,3.221A3.09,3.09,0,0,0,50.081,0,48.227,48.227,0,0,0,18.322,13.437c-6-1.666-14.991-1.221-18.322,7.218A33.892,33.892,0,0,1,9.439,25.1l-.333.666a3.013,3.013,0,0,0,.555,3.553L23.985,43.641a2.9,2.9,0,0,0,3.553.555l.666-.333A33.892,33.892,0,0,1,32.647,53.3c8.55-3.664,8.884-12.326,7.218-18.322A48.227,48.227,0,0,0,53.3,3.221ZM34.424,9.772a6.439,6.439,0,1,1,9.106,9.106,6.368,6.368,0,0,1-9.106,0A6.467,6.467,0,0,1,34.424,9.772Z" transform="translate(0 0.005)" fill="#fff"/>
</g>
</g>
</svg>
<span>{{ title }} app is running!</span>
<svg id="rocket-smoke" xmlns="http://www.w3.org/2000/svg" width="516.119" height="1083.632" viewBox="0 0 516.119 1083.632">
<title>Rocket Ship Smoke</title>
<path id="Path_40" data-name="Path 40" d="M644.6,141S143.02,215.537,147.049,870.207s342.774,201.755,342.774,201.755S404.659,847.213,388.815,762.2c-27.116-145.51-11.551-384.124,271.9-609.1C671.15,139.365,644.6,141,644.6,141Z" transform="translate(-147.025 -140.939)" fill="#f5f5f5"/>
</svg>
</div>
<!-- Resources -->
<h2>Resources</h2>
<p>Here are some links to help you get started:</p>
<div class="card-container">
<a class="card" target="_blank" rel="noopener" href="https://angular.io/tutorial">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M5 13.18v4L12 21l7-3.82v-4L12 17l-7-3.82zM12 3L1 9l11 6 9-4.91V17h2V9L12 3z"/></svg>
<span>Learn Angular</span>
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg> </a>
<a class="card" target="_blank" rel="noopener" href="https://angular.io/cli">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
<span>CLI Documentation</span>
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</a>
<a class="card" target="_blank" rel="noopener" href="https://blog.angular.io/">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M13.5.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z"/></svg>
<span>Angular Blog</span>
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</a>
<a class="card" target="_blank" rel="noopener" href="https://angular.io/devtools/">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M14.73,13.31C15.52,12.24,16,10.93,16,9.5C16,5.91,13.09,3,9.5,3S3,5.91,3,9.5C3,13.09,5.91,16,9.5,16 c1.43,0,2.74-0.48,3.81-1.27L19.59,21L21,19.59L14.73,13.31z M9.5,14C7.01,14,5,11.99,5,9.5S7.01,5,9.5,5S14,7.01,14,9.5 S11.99,14,9.5,14z"/><polygon points="10.29,8.44 9.5,6 8.71,8.44 6.25,8.44 8.26,10.03 7.49,12.5 9.5,10.97 11.51,12.5 10.74,10.03 12.75,8.44"/></g></g></svg>
<span>Angular DevTools</span>
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</a>
</div>
<!-- Next Steps -->
<h2>Next Steps</h2>
<p>What do you want to do next with your app?</p>
<input type="hidden" #selection>
<div class="card-container">
<button class="card card-small" (click)="selection.value = 'component'" 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>New Component</span>
</button>
<button class="card card-small" (click)="selection.value = 'material'" 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>Angular Material</span>
</button>
<button class="card card-small" (click)="selection.value = 'pwa'" 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>Add PWA Support</span>
</button>
<button class="card card-small" (click)="selection.value = 'dependency'" 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>Add Dependency</span>
</button>
<button class="card card-small" (click)="selection.value = 'test'" 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>Run and Watch Tests</span>
</button>
<button class="card card-small" (click)="selection.value = 'build'" 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>Build for Production</span>
</button>
</div>
<!-- Terminal -->
<div class="terminal" [ngSwitch]="selection.value">
<pre *ngSwitchDefault>ng generate component xyz</pre>
<pre *ngSwitchCase="'material'">ng add @angular/material</pre>
<pre *ngSwitchCase="'pwa'">ng add @angular/pwa</pre>
<pre *ngSwitchCase="'dependency'">ng add _____</pre>
<pre *ngSwitchCase="'test'">ng test</pre>
<pre *ngSwitchCase="'build'">ng build</pre>
</div>
<!-- Links -->
<div class="card-container">
<a class="circle-link" title="Animations" href="https://angular.io/guide/animations" target="_blank" rel="noopener">
<svg id="Group_20" data-name="Group 20" xmlns="http://www.w3.org/2000/svg" width="21.813" height="23.453" viewBox="0 0 21.813 23.453">
<path id="Path_15" data-name="Path 15" d="M4099.584,972.736h0l-10.882,3.9,1.637,14.4,9.245,5.153,9.245-5.153,1.686-14.4Z" transform="translate(-4088.702 -972.736)" fill="#ffa726"/>
<path id="Path_16" data-name="Path 16" d="M4181.516,972.736v23.453l9.245-5.153,1.686-14.4Z" transform="translate(-4170.633 -972.736)" fill="#fb8c00"/>
<path id="Path_17" data-name="Path 17" d="M4137.529,1076.127l-7.7-3.723,4.417-2.721,7.753,3.723Z" transform="translate(-4125.003 -1058.315)" fill="#ffe0b2"/>
<path id="Path_18" data-name="Path 18" d="M4137.529,1051.705l-7.7-3.723,4.417-2.721,7.753,3.723Z" transform="translate(-4125.003 -1036.757)" fill="#fff3e0"/>
<path id="Path_19" data-name="Path 19" d="M4137.529,1027.283l-7.7-3.723,4.417-2.721,7.753,3.723Z" transform="translate(-4125.003 -1015.199)" fill="#fff"/>
</svg>
</a>
<a class="circle-link" title="CLI" href="https://cli.angular.io/" target="_blank" rel="noopener">
<svg xmlns="http://www.w3.org/2000/svg" width="21.762" height="23.447" viewBox="0 0 21.762 23.447">
<title>Angular CLI Logo</title>
<g id="Group_21" data-name="Group 21" transform="translate(0)">
<path id="Path_20" data-name="Path 20" d="M2660.313,313.618h0l-10.833,3.9,1.637,14.4,9.2,5.152,9.244-5.152,1.685-14.4Z" transform="translate(-2649.48 -313.618)" fill="#37474f"/>
<path id="Path_21" data-name="Path 21" d="M2741.883,313.618v23.447l9.244-5.152,1.685-14.4Z" transform="translate(-2731.05 -313.618)" fill="#263238"/>
<path id="Path_22" data-name="Path 22" d="M2692.293,379.169h11.724V368.618h-11.724Zm11.159-.6h-10.608v-9.345h10.621v9.345Z" transform="translate(-2687.274 -362.17)" fill="#fff"/>
<path id="Path_23" data-name="Path 23" d="M2709.331,393.688l.4.416,2.265-2.28-2.294-2.294-.4.4,1.893,1.893Z" transform="translate(-2702.289 -380.631)" fill="#fff"/>
<rect id="Rectangle_12" data-name="Rectangle 12" width="3.517" height="0.469" transform="translate(9.709 13.744)" fill="#fff"/>
</g>
</svg>
</a>
<a class="circle-link" title="Find a Local Meetup" href="https://www.meetup.com/find/?keywords=angular" target="_blank" rel="noopener">
<svg xmlns="http://www.w3.org/2000/svg" width="24.607" height="23.447" viewBox="0 0 24.607 23.447">
<title>Meetup Logo</title>
<path id="logo--mSwarm" d="M21.221,14.95A4.393,4.393,0,0,1,17.6,19.281a4.452,4.452,0,0,1-.8.069c-.09,0-.125.035-.154.117a2.939,2.939,0,0,1-2.506,2.091,2.868,2.868,0,0,1-2.248-.624.168.168,0,0,0-.245-.005,3.926,3.926,0,0,1-2.589.741,4.015,4.015,0,0,1-3.7-3.347,2.7,2.7,0,0,1-.043-.38c0-.106-.042-.146-.143-.166a3.524,3.524,0,0,1-1.516-.69A3.623,3.623,0,0,1,2.23,14.557a3.66,3.66,0,0,1,1.077-3.085.138.138,0,0,0,.026-.2,3.348,3.348,0,0,1-.451-1.821,3.46,3.46,0,0,1,2.749-3.28.44.44,0,0,0,.355-.281,5.072,5.072,0,0,1,3.863-3,5.028,5.028,0,0,1,3.555.666.31.31,0,0,0,.271.03A4.5,4.5,0,0,1,18.3,4.7a4.4,4.4,0,0,1,1.334,2.751,3.658,3.658,0,0,1,.022.706.131.131,0,0,0,.1.157,2.432,2.432,0,0,1,1.574,1.645,2.464,2.464,0,0,1-.7,2.616c-.065.064-.051.1-.014.166A4.321,4.321,0,0,1,21.221,14.95ZM13.4,14.607a2.09,2.09,0,0,0,1.409,1.982,4.7,4.7,0,0,0,1.275.221,1.807,1.807,0,0,0,.9-.151.542.542,0,0,0,.321-.545.558.558,0,0,0-.359-.534,1.2,1.2,0,0,0-.254-.078c-.262-.047-.526-.086-.787-.138a.674.674,0,0,1-.617-.75,3.394,3.394,0,0,1,.218-1.109c.217-.658.509-1.286.79-1.918a15.609,15.609,0,0,0,.745-1.86,1.95,1.95,0,0,0,.06-1.073,1.286,1.286,0,0,0-1.051-1.033,1.977,1.977,0,0,0-1.521.2.339.339,0,0,1-.446-.042c-.1-.092-.2-.189-.307-.284a1.214,1.214,0,0,0-1.643-.061,7.563,7.563,0,0,1-.614.512A.588.588,0,0,1,10.883,8c-.215-.115-.437-.215-.659-.316a2.153,2.153,0,0,0-.695-.248A2.091,2.091,0,0,0,7.541,8.562a9.915,9.915,0,0,0-.405.986c-.559,1.545-1.015,3.123-1.487,4.7a1.528,1.528,0,0,0,.634,1.777,1.755,1.755,0,0,0,1.5.211,1.35,1.35,0,0,0,.824-.858c.543-1.281,1.032-2.584,1.55-3.875.142-.355.28-.712.432-1.064a.548.548,0,0,1,.851-.24.622.622,0,0,1,.185.539,2.161,2.161,0,0,1-.181.621c-.337.852-.68,1.7-1.018,2.552a2.564,2.564,0,0,0-.173.528.624.624,0,0,0,.333.71,1.073,1.073,0,0,0,.814.034,1.22,1.22,0,0,0,.657-.655q.758-1.488,1.511-2.978.35-.687.709-1.37a1.073,1.073,0,0,1,.357-.434.43.43,0,0,1,.463-.016.373.373,0,0,1,.153.387.7.7,0,0,1-.057.236c-.065.157-.127.316-.2.469-.42.883-.846,1.763-1.262,2.648A2.463,2.463,0,0,0,13.4,14.607Zm5.888,6.508a1.09,1.09,0,0,0-2.179.006,1.09,1.09,0,0,0,2.179-.006ZM1.028,12.139a1.038,1.038,0,1,0,.01-2.075,1.038,1.038,0,0,0-.01,2.075ZM13.782.528a1.027,1.027,0,1,0-.011,2.055A1.027,1.027,0,0,0,13.782.528ZM22.21,6.95a.882.882,0,0,0-1.763.011A.882.882,0,0,0,22.21,6.95ZM4.153,4.439a.785.785,0,1,0,.787-.78A.766.766,0,0,0,4.153,4.439Zm8.221,18.22a.676.676,0,1,0-.677.666A.671.671,0,0,0,12.374,22.658ZM22.872,12.2a.674.674,0,0,0-.665.665.656.656,0,0,0,.655.643.634.634,0,0,0,.655-.644A.654.654,0,0,0,22.872,12.2ZM7.171-.123A.546.546,0,0,0,6.613.43a.553.553,0,1,0,1.106,0A.539.539,0,0,0,7.171-.123ZM24.119,9.234a.507.507,0,0,0-.493.488.494.494,0,0,0,.494.494.48.48,0,0,0,.487-.483A.491.491,0,0,0,24.119,9.234Zm-19.454,9.7a.5.5,0,0,0-.488-.488.491.491,0,0,0-.487.5.483.483,0,0,0,.491.479A.49.49,0,0,0,4.665,18.936Z" transform="translate(0 0.123)" fill="#f64060"/>
</svg>
</a>
<a class="circle-link" title="Join the Conversation on Discord" href="https://discord.gg/angular" target="_blank" rel="noopener">
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 245 240">
<title>Discord Logo</title>
<path d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/>
<path d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/>
</svg>
</a>
</div>
<!-- Footer -->
<footer>
Love Angular?&nbsp;
<a href="https://github.com/angular/angular" target="_blank" rel="noopener"> Give our repo a star.
<div class="github-star-badge">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
Star
</div>
</a>
<a href="https://github.com/angular/angular" target="_blank" rel="noopener">
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" fill="#1976d2"/><path d="M0 0h24v24H0z" fill="none"/></svg>
</a>
</footer>
<svg id="clouds" xmlns="http://www.w3.org/2000/svg" width="2611.084" height="485.677" viewBox="0 0 2611.084 485.677">
<title>Gray Clouds Background</title>
<path id="Path_39" data-name="Path 39" d="M2379.709,863.793c10-93-77-171-168-149-52-114-225-105-264,15-75,3-140,59-152,133-30,2.83-66.725,9.829-93.5,26.25-26.771-16.421-63.5-23.42-93.5-26.25-12-74-77-130-152-133-39-120-212-129-264-15-54.084-13.075-106.753,9.173-138.488,48.9-31.734-39.726-84.4-61.974-138.487-48.9-52-114-225-105-264,15a162.027,162.027,0,0,0-103.147,43.044c-30.633-45.365-87.1-72.091-145.206-58.044-52-114-225-105-264,15-75,3-140,59-152,133-53,5-127,23-130,83-2,42,35,72,70,86,49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33,61.112,8.015,113.854-5.72,150.492-29.764a165.62,165.62,0,0,0,110.861-3.236c47,94,178,113,251,33,31.385,4.116,60.563,2.495,86.487-3.311,25.924,5.806,55.1,7.427,86.488,3.311,73,80,204,61,251-33a165.625,165.625,0,0,0,120,0c51,13,108,15,157-5a147.188,147.188,0,0,0,33.5-18.694,147.217,147.217,0,0,0,33.5,18.694c49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33C2446.709,1093.793,2554.709,922.793,2379.709,863.793Z" transform="translate(142.69 -634.312)" fill="#eee"/>
</svg>
</div>
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * -->
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
+25 -25
View File
@@ -1,31 +1,31 @@
import { TestBed } from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import { AppComponent } from './app.component'; import {AppComponent} from './app.component';
describe('AppComponent', () => { describe('AppComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ declarations: [
AppComponent AppComponent
], ]
}).compileComponents(); }).compileComponents();
}); });
it('should create the app', () => { it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance; const app = fixture.componentInstance;
expect(app).toBeTruthy(); expect(app).toBeTruthy();
}); });
it(`should have as title 'Frontend'`, () => { it(`should have as title 'Frontend'`, () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance; const app = fixture.componentInstance;
expect(app.title).toEqual('Frontend'); expect(app.title).toEqual('Frontend');
}); });
it('should render title', () => { it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges(); fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement; const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent).toContain('Frontend app is running!'); expect(compiled.querySelector('.content span')?.textContent).toContain('Frontend app is running!');
}); });
}); });
+5 -5
View File
@@ -1,10 +1,10 @@
import { Component } from '@angular/core'; import {Component} from '@angular/core';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.css'] styleUrls: ['./app.component.css']
}) })
export class AppComponent { export class AppComponent {
title = 'TINF19B4 RaPla Changes'; title = 'TINF19B4 RaPla Changes';
} }
+41 -12
View File
@@ -1,16 +1,45 @@
import { NgModule } from '@angular/core'; import {NgModule} from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
import { AppComponent } from './app.component'; import {AppComponent} from './app.component';
import {AppRouting} from './app.routing';
import {LandingpageComponent} from './pages/landingpage/landingpage.component';
import {PageNotFoundComponent} from './pages/page-not-found/page-not-found.component';
import {WeekComponent} from './components/week/week.component';
import {DayComponent} from './components/day/day.component';
import {EventComponent} from './components/event/event.component';
import {DatepickerComponent} from './components/datepicker/datepicker.component';
import {EventDetailComponent} from './components/event-detail/event-detail.component';
import {HttpClientModule} from '@angular/common/http';
import {FormsModule} from '@angular/forms';
import {ApiService} from './services/api/api.service';
import {MatTooltipModule} from '@angular/material/tooltip';
import {UtilitiesService} from './services/utils/utilities.service';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent AppComponent,
], LandingpageComponent,
imports: [ PageNotFoundComponent,
BrowserModule WeekComponent,
], DayComponent,
providers: [], EventComponent,
bootstrap: [AppComponent] DatepickerComponent,
EventDetailComponent
],
imports: [
BrowserModule,
AppRouting,
HttpClientModule,
NgbModule,
FormsModule,
MatTooltipModule,
BrowserAnimationsModule
],
providers: [ApiService, UtilitiesService],
bootstrap: [AppComponent]
}) })
export class AppModule { } export class AppModule {
}
+23
View File
@@ -0,0 +1,23 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import { EventDetailComponent } from './components/event-detail/event-detail.component';
import {LandingpageComponent} from './pages/landingpage/landingpage.component';
import {PageNotFoundComponent} from './pages/page-not-found/page-not-found.component';
const routes: Routes = [
{path: '', component: LandingpageComponent, pathMatch: 'full'},
{path: 'eventDetails/:id', component: EventDetailComponent},
{path: '**', component: PageNotFoundComponent}
];
@NgModule({
declarations: [],
imports: [
RouterModule.forRoot(routes)
],
exports: [
RouterModule
]
})
export class AppRouting {
}
@@ -0,0 +1,41 @@
.datepicker {
text-align: center;
}
.switchWeekButton {
background-color: dimgrey;
border: none;
color: #E0E5E9;
padding: .15em .5em;
text-align: center;
display: inline-block;
border-radius: .5em;
margin: .5em;
}
.showDeletedBtn {
border: none;
color: #E0E5E9;
padding: .15em .5em;
text-align: center;
display: inline-block;
border-radius: .5em;
margin: .5em;
}
.show {
background-color: green;
}
.hide {
background-color: darkred;
}
.datePickerDropdown {
background-color: dimgrey;
border: none;
color: #E0E5E9;
padding: .15em .5em;
border-radius: .5em;
margin: .1em;
}
@@ -0,0 +1,22 @@
<div class="datepicker">
<p *ngIf="itIsWednesday">It is Wednesday, my dudes!</p>
<button (click)="switchToPreviousWeek()" class="switchWeekButton">&lt;&lt; Previous week</button>
<select name="year" (change)="handleYearChange()" [(ngModel)]="selectedYear" class="datePickerDropdown">
<option *ngFor="let year of years">
{{year}}
</option>
</select>
<select name="month" (change)="handleMonthChange()" [(ngModel)]="selectedMonth" class="datePickerDropdown">
<option *ngFor="let month of months">
{{month}}
</option>
</select>
<select name="day" (change)="handleDayChange()" [(ngModel)]="selectedDay" class="datePickerDropdown">
<option *ngFor="let day of days">
{{day}}
</option>
</select>
<button (click)="switchToToday()" class="switchWeekButton">Today</button>
<button (click)="switchShowDeletedEvents()" class="showDeletedBtn {{switchDeletedBtnClass}}">{{showingDeletedBtnText}}</button>
<button (click)="switchToNextWeek()" class="switchWeekButton">Next week &gt;&gt;</button>
</div>
@@ -0,0 +1,25 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {DatepickerComponent} from './datepicker.component';
describe('DatepickerComponent', () => {
let component: DatepickerComponent;
let fixture: ComponentFixture<DatepickerComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DatepickerComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DatepickerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,266 @@
import {Component, EventEmitter, OnInit, Output} from '@angular/core';
@Component({
selector: 'app-datepicker',
templateUrl: './datepicker.component.html',
styleUrls: ['./datepicker.component.css']
})
export class DatepickerComponent implements OnInit {
selectedYear: string = '';
selectedMonth: string = '';
selectedDay: string = '';
years: string[] = [];
months: string[] = [];
days: string[] = [];
itIsWednesday: boolean = false;
internalShowDeletedEvents: boolean = true;
@Output() showDeletedEvents = new EventEmitter<boolean>();
switchDeletedBtnClass: string = 'show';
showingDeletedBtnText: string = 'Showing deleted events';
@Output() selectedMonday = new EventEmitter<string>();
constructor() {
}
ngOnInit(): void {
this.fillDatePickers();
this.getDetailsFromLocalStorage();
this.handleDateChange();
}
/**
* Fills all the date pickers and sets the itIsWednesday variable
*/
fillDatePickers(): void {
let currentDate = new Date();
if (currentDate.getDay() === 3) {
this.itIsWednesday = true;
}
this.fillYearPicker();
this.fillMonthPicker();
this.fillDayPicker();
}
/**
* Fills the "year" picker with the appropriate values and pre-selects the current year
*/
fillYearPicker(): void {
this.years = [];
let currentDate = new Date();
let currentYear = currentDate.getFullYear().toString();
this.years = [
'2020',
'2021',
'2022'
];
this.selectedYear = currentYear;
}
/**
* Fills the "month" picker with the appropriate values and pre-selects the current month
*/
fillMonthPicker(): void {
this.months = [];
let currentDate = new Date();
let currentMonth = (currentDate.getMonth() + 1).toString().padStart(2, '0');
for (let month = 1; month <= 12; month++) {
let monthString = month.toString().padStart(2, '0');
this.months.push(monthString);
}
this.selectedMonth = currentMonth;
}
/**
* Fills the "day" picker with the appropriate values and pre-selects the current day if possible
*/
fillDayPicker(): void {
this.days = [];
let currentDate = new Date();
let currentDay = currentDate.getDate().toString().padStart(2, '0');
let maxDays = 31;
if (this.selectedMonth !== '') {
maxDays = this.getDaysInGivenMonth(parseInt(this.selectedYear), parseInt(this.selectedMonth));
} else {
maxDays = 31;
}
for (let day = 1; day <= maxDays; day++) {
let dayString = day.toString().padStart(2, '0');
this.days.push(dayString);
}
this.selectedDay = parseInt(currentDay) < maxDays ? currentDay : maxDays.toString().padStart(2, '0');
}
/**
* Handles the change of the "year" picker
*/
handleYearChange(): void {
this.fillDayPicker();
this.handleDateChange();
}
/**
* Handles the change of the "month" picker
*/
handleMonthChange(): void {
this.fillDayPicker();
this.handleDateChange();
}
/**
* Handles a change of the "day" picker
*/
handleDayChange(): void {
this.handleDateChange();
}
/**
* Handles changed date selections and fills the selectedMonday variable
*/
handleDateChange(): void {
let mondayDate = this.findMondayInWeek(this.selectedYear, this.selectedMonth, this.selectedDay);
this.selectedMonday.emit(mondayDate);
this.saveDetailsToLocalStorage();
}
/**
* Calculates the date of the monday in the week for any given date.
* If you e.g. enter 2021, 09, 30 the result will be 2021-09-27
* @param year The year
* @param month The month
* @param day The day
*/
findMondayInWeek(year: string, month: string, day: string): string {
let selectedDate = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
let mondayInWeekDate = selectedDate;
let selectedDayInWeek = selectedDate.getDay();
// Sunday is 0 and Monday is 1, hence we want to substract until we land at 1.
// Sunday is the exeption as we need to go to 1 in the "previous" week
if (selectedDayInWeek === 0) {
mondayInWeekDate.setDate(selectedDate.getDate() - 6);
} else {
let difference = selectedDayInWeek - 1;
mondayInWeekDate.setDate(selectedDate.getDate() - difference);
}
let yearString = mondayInWeekDate.getFullYear().toString();
let monthString = (mondayInWeekDate.getMonth() + 1).toString().padStart(2, '0');
let dayString = mondayInWeekDate.getDate().toString().padStart(2, '0');
return yearString + '-' + monthString + '-' + dayString;
}
/**
* Returns the number of days in the given month
* @param year The year
* @param month The month for which to return the number of days for
*/
getDaysInGivenMonth(year: number, month: number) {
return new Date(year, month, 0).getDate();
}
/**
* Switches the currently selected date to the next week
*/
switchToNextWeek() {
let currentDate = new Date(parseInt(this.selectedYear), parseInt(this.selectedMonth)-1, parseInt(this.selectedDay));
let newDate = currentDate;
newDate.setDate(currentDate.getDate() + 7);
this.selectedYear = newDate.getFullYear().toString();
this.selectedMonth = (newDate.getMonth()+1).toString().padStart(2, '0');
this.selectedDay = newDate.getDate().toString().padStart(2, '0');
this.handleDateChange();
}
/**
* Switches the currently selected date to the previous week
*/
switchToPreviousWeek() {
let currentDate = new Date(parseInt(this.selectedYear), parseInt(this.selectedMonth)-1, parseInt(this.selectedDay));
let newDate = currentDate;
newDate.setDate(currentDate.getDate() - 7);
this.selectedYear = newDate.getFullYear().toString();
this.selectedMonth = (newDate.getMonth()+1).toString().padStart(2, '0');
this.selectedDay = newDate.getDate().toString().padStart(2, '0');
this.handleDateChange();
}
/**
* Switches the currently selected date to today
*/
switchToToday() {
let currentDate = new Date();
this.selectedYear = currentDate.getFullYear().toString();
this.selectedMonth = (currentDate.getMonth()+1).toString().padStart(2, '0');
this.selectedDay = currentDate.getDate().toString().padStart(2, '0');
this.handleDateChange();
}
/**
* Switches whether to show deleted events in the overview
*/
switchShowDeletedEvents() {
if(this.internalShowDeletedEvents) {
this.internalShowDeletedEvents = false;
this.showDeletedEvents.emit(false);
this.switchDeletedBtnClass = 'hide';
this.showingDeletedBtnText = 'Deleted events hidden';
} else {
this.internalShowDeletedEvents = true;
this.showDeletedEvents.emit(true);
this.switchDeletedBtnClass = 'show';
this.showingDeletedBtnText = 'Showing deleted events';
}
this.saveDetailsToLocalStorage();
}
/**
* Saves preferences like the selected week to the local storage
*/
saveDetailsToLocalStorage() {
localStorage.setItem('has_saved_details', 'true');
localStorage.setItem('selected_year', this.selectedYear);
localStorage.setItem('selected_month', this.selectedMonth);
localStorage.setItem('selected_day', this.selectedDay);
localStorage.setItem('show_deleted', this.internalShowDeletedEvents.toString());
}
/**
* Gets saved preferences from the local storage if there are any
*/
getDetailsFromLocalStorage() {
if((localStorage.getItem('has_saved_details') ?? 'false') === 'true') {
this.selectedYear = localStorage.getItem('selected_year')!;
this.selectedMonth = localStorage.getItem('selected_month')!;
this.selectedDay = localStorage.getItem('selected_day')!;
this.internalShowDeletedEvents = localStorage.getItem('show_deleted')! === 'true';
this.showDeletedEvents.emit(this.internalShowDeletedEvents);
this.checkShowDeletedEventsBtn();
}
}
/**
* Checks if the "show deleted events" button has the correct style
*/
checkShowDeletedEventsBtn() {
if(this.internalShowDeletedEvents) {
this.switchDeletedBtnClass = 'show';
this.showingDeletedBtnText = 'Showing deleted events';
} else {
this.switchDeletedBtnClass = 'hide';
this.showingDeletedBtnText = 'Deleted events hidden';
}
}
}
@@ -0,0 +1,4 @@
<h1>{{weekDayName}}</h1>
<div *ngFor="let event of events">
<app-event [event]="event" *ngIf="!(!showDeletedEvents && event.changes[event.changes.length-1].is_deleted)"></app-event>
</div>
@@ -0,0 +1,25 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {DayComponent} from './day.component';
describe('DayComponent', () => {
let component: DayComponent;
let fixture: ComponentFixture<DayComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DayComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DayComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,26 @@
import {Component, Input, OnInit} from '@angular/core';
import {Event} from '../../models/event';
@Component({
selector: 'app-day',
templateUrl: './day.component.html',
styleUrls: ['./day.component.css']
})
export class DayComponent implements OnInit {
@Input() events: Event[] = [];
@Input() dayOfWeek: number = 0;
@Input() showDeletedEvents: boolean = true;
weekDayName: string = '';
constructor() {
}
ngOnInit(): void {
this.weekDayName = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'][this.dayOfWeek];
this.events = this.events.sort((a, b) => {
return new Date(a.latest_start_date.toString()).getTime() - new Date(b.latest_start_date.toString()).getTime()
});
}
}
@@ -0,0 +1,41 @@
.title {
text-align: center;
font-size: 2em;
font-weight: bold;
}
footer {
text-align: center;
}
.backBtn {
background-color: dimgrey; /* Green */
border: none;
color: #E0E5E9;
padding: .15em .5em;
text-align: center;
display: inline-block;
border-radius: .5em;
margin: .5em;
}
.info-table {
padding: .5em;
}
.event-current-status, .event-changes {
display: flex;
justify-content: center;
}
.changes-table th, .changes-table td {
padding-inline: .5em;
}
.change-old-version {
color: red;
}
.change-new-version {
color: green;
}
@@ -0,0 +1,70 @@
<header>
<button (click)="closeTab()" class="backBtn">&lt;&lt; Back</button>
<p class="title">Event: {{event.latest_event_summary}}</p>
</header>
<div class="event-current-status">
<table class="info-table">
<tr>
<th>Most recent details</th>
</tr>
<tr>
<td>Description:</td>
<td>&nbsp;{{latestFullChange.new_description}}</td>
</tr>
<tr>
<td>Created:</td>
<td>&nbsp;{{getDateTime(latestFullChange.new_created.toString())}}</td>
</tr>
<tr>
<td>Start:</td>
<td>&nbsp;{{getDateTime(latestFullChange.new_start.toString())}}</td>
</tr>
<tr>
<td>End:</td>
<td>&nbsp;{{getDateTime(latestFullChange.new_end.toString())}}</td>
</tr>
<tr>
<td>Last modified:</td>
<td>&nbsp;{{getDateTime(latestFullChange.new_last_modified.toString())}}</td>
</tr>
<tr>
<td>Location:</td>
<td>&nbsp;{{latestFullChange.new_location}}</td>
</tr>
<tr>
<td>Organizer:</td>
<td>&nbsp;{{latestFullChange.new_organizer}}</td>
</tr>
<tr>
<td>Type:</td>
<td>&nbsp;{{latestFullChange.new_categories}}</td>
</tr>
<tr>
<td>Is deleted:</td>
<td>&nbsp;{{isDeleted}}</td>
</tr>
</table>
</div><br>
<div class="event-changes">
<table class="changes-table">
<tr>
<th>Changes</th>
</tr>
<tbody *ngFor="let change of changeDetails">
<tr>
<td class="change-timestamp">{{getDateTime(change.timestamp.toString())}}</td>
<td class="change-summary">{{change.summary}}</td>
</tr>
<tr *ngIf="change.hasDetails">
<td></td>
<td class="change-old-version">{{change.oldVersion}}</td>
<td>--></td>
<td class="change-new-version">{{change.newVersion}}</td>
</tr>
</tbody>
</table>
</div>
<footer class="fixed-bottom start-50 translate-middle">
Copyright &#169; 2021 - Patrick Müller
</footer>
@@ -0,0 +1,25 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {EventDetailComponent} from './event-detail.component';
describe('EventDetailComponent', () => {
let component: EventDetailComponent;
let fixture: ComponentFixture<EventDetailComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [EventDetailComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(EventDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,146 @@
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Event} from '../../models/event';
import {ApiService} from '../../services/api/api.service';
import {Change} from '../../models/change';
import {UtilitiesService} from '../../services/utils/utilities.service';
@Component({
selector: 'app-event-detail',
templateUrl: './event-detail.component.html',
styleUrls: ['./event-detail.component.css']
})
export class EventDetailComponent implements OnInit {
id: string = '';
event: Event = {} as Event;
latestFullChange: Change = {} as Change;
isDeleted: boolean = false;
changeDetails: ChangeDetail[] = [];
constructor(
private route: ActivatedRoute,
private apiService: ApiService,
private utilities: UtilitiesService
) {
}
ngOnInit(): void {
this.id = this.route.snapshot.paramMap.get('id') as string;
console.log(this.id);
this.apiService.getEventById(this.id).subscribe(event => {
this.event = event;
// Set latest full change
this.latestFullChange = this.event.changes[this.event.changes.length - 1];
if (this.latestFullChange.is_deleted) {
this.isDeleted = true;
this.latestFullChange = this.event.changes[this.event.changes.length - 2];
}
this.generateChangeDetails();
});
}
/**
* Checks which details have changed in the changes and formats them properly to be displayed in the component
*/
generateChangeDetails() {
let changes = this.event.changes;
if (changes.length < 1) {
return;
}
let firstChange = changes.shift();
this.changeDetails.push({
timestamp: firstChange!.change_timestamp,
summary: 'Created',
hasDetails: false
});
let lastChange = firstChange!;
for (let change of changes) {
let cTs = change.change_timestamp;
let cSummary = '';
let cOld = '';
let cNew = '';
let cDetails = false;
if (change.is_deleted) {
cSummary = 'Deleted';
} else if (change.new_summary !== lastChange.new_summary) {
cSummary = 'Name changed';
cOld = lastChange.new_summary;
cNew = change.new_summary;
cDetails = true;
} else if (change.new_description !== lastChange.new_description) {
cSummary = 'Description changed';
cOld = lastChange.new_description;
cNew = change.new_description;
cDetails = true;
} else if (change.new_start !== lastChange.new_start) {
cSummary = 'Start changed';
cOld = lastChange.new_start.toString();
cNew = change.new_start.toString();
cDetails = true;
} else if (change.new_end !== lastChange.new_end) {
cSummary = 'End changed';
cOld = lastChange.new_end.toString();
cNew = change.new_end.toString();
cDetails = true;
} else if (change.new_location !== lastChange.new_location) {
cSummary = 'Location changed';
cOld = lastChange.new_location;
cNew = change.new_location;
cDetails = true;
} else if (change.new_organizer !== lastChange.new_organizer) {
cSummary = 'Organizer changed';
cOld = lastChange.new_organizer;
cNew = change.new_organizer;
cDetails = true;
} else if (change.new_categories !== lastChange.new_categories) {
cSummary = 'Type changed';
cOld = lastChange.new_categories;
cNew = change.new_categories;
cDetails = true;
} else {
cSummary = 'Internal change';
}
this.changeDetails.push({
timestamp: cTs,
summary: cSummary,
oldVersion: cOld,
newVersion: cNew,
hasDetails: cDetails
});
lastChange = change;
}
}
/**
* Closes the current tab so the user gets back to the event overview
*/
closeTab(): void {
window.close();
}
/**
* Returns the formatted date for the given date
* @param date The date as a string to format
*/
getDateTime(date: string): string {
return this.utilities.getDateTimeFromString(date);
}
}
export interface ChangeDetail {
timestamp: Date;
summary: string;
hasDetails: boolean;
oldVersion?: string;
newVersion?: string;
}
@@ -0,0 +1,26 @@
.event-card {
border-radius: .5em;
padding: .25em;
margin: .5em;
}
.event-card:hover {
cursor: pointer;
}
.event-card-lecture {
background-color: #0A62D0;
}
.event-card-exam {
background-color: darkred;
}
.event-card-blocker {
background-color: dimgrey;
}
.click-hint {
font-size: 80%;
color: black;
}
@@ -0,0 +1,6 @@
<div class="event-card {{eventTypeClass}}" (click)="openEventDetails()">
{{event.latest_event_summary}}<br>
Start: {{getTime()}}<br>
Deleted: {{event.changes[event.changes.length-1].is_deleted}}<br>
<div class="click-hint">Click for change details</div>
</div>
@@ -0,0 +1,25 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {EventComponent} from './event.component';
describe('EventComponent', () => {
let component: EventComponent;
let fixture: ComponentFixture<EventComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [EventComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(EventComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,58 @@
import {Component, Input, OnInit} from '@angular/core';
import {Event} from '../../models/event';
import {Change} from '../../models/change';
import {Router} from '@angular/router';
import {UtilitiesService} from '../../services/utils/utilities.service';
@Component({
selector: 'app-event',
templateUrl: './event.component.html',
styleUrls: ['./event.component.css']
})
export class EventComponent implements OnInit {
@Input() event: Event = {} as Event;
eventTypeClass: string = '';
latestFullChange: Change = {} as Change;
constructor(
private router: Router,
private utilities: UtilitiesService
) {
}
ngOnInit(): void {
this.latestFullChange = this.event.changes[this.event.changes.length - 1];
if (this.latestFullChange.is_deleted) {
this.latestFullChange = this.event.changes[this.event.changes.length - 2];
}
switch (this.latestFullChange.new_categories) {
case 'Prüfung':
this.eventTypeClass = 'event-card-exam';
break;
case 'Lehrveranstaltung':
this.eventTypeClass = 'event-card-lecture';
break;
case 'Sonstige':
this.eventTypeClass = 'event-card-blocker';
break;
default:
this.eventTypeClass = 'event-card-blocker';
}
}
/**
* Opens a new tab with the event details
*/
openEventDetails() {
this.router.navigate([]).then(result => { window.open('/eventDetails/'+this.event.event_uid, '_blank'); });
}
/**
* Formats the starting time of the event
*/
getTime(): string {
return this.utilities.getTimeFromString(this.latestFullChange.new_start.toString());
}
}
@@ -0,0 +1,5 @@
<div id="week" class="container-fluid">
<div class="row">
<app-day *ngFor="let day of eventsPerDay" [events]="day" [dayOfWeek]="eventsPerDay.indexOf(day)" [showDeletedEvents]="showDeletedEvents" class="col"></app-day>
</div>
</div>
@@ -0,0 +1,25 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {WeekComponent} from './week.component';
describe('WeekComponent', () => {
let component: WeekComponent;
let fixture: ComponentFixture<WeekComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [WeekComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(WeekComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,53 @@
import {Component, Input, OnInit, SimpleChanges} from '@angular/core';
import {Event} from '../../models/event';
@Component({
selector: 'app-week',
templateUrl: './week.component.html',
styleUrls: ['./week.component.css']
})
export class WeekComponent implements OnInit {
@Input() events: Event[] = [];
@Input() showDeletedEvents: boolean = true;
eventsPerDay: Event[][] = [];
constructor() {
}
ngOnInit(): void {
this.splitEventsIntoDays();
}
ngOnChanges(changes: SimpleChanges): void {
this.splitEventsIntoDays();
}
/**
* Splits the given events into their days so we have a list of events for every day of the week
* to pass into the day components
*/
splitEventsIntoDays() {
// Pre-fill list with 7 empty days
this.eventsPerDay = [];
for(let day = 0; day < 7; day++) {
this.eventsPerDay.push([]);
}
this.events.forEach((event) => {
let startDate = new Date(event.latest_start_date);
let year = startDate.getFullYear().toString();
let month = startDate.getMonth().toString().padStart(2, '0');
let day = startDate.getDate().toString().padStart(2, '0');
let dayIndex = startDate.getDay();
// Because these start with fucking sunday
if(dayIndex === 0) {
dayIndex = 6;
} else {
dayIndex--;
}
this.eventsPerDay[dayIndex].push(event);
});
}
}
+37
View File
@@ -0,0 +1,37 @@
import {Observable} from 'rxjs';
/**
* Mock class for unit testing
*/
export abstract class AbstractMockObservableService {
protected _observable: Observable<any> | undefined;
protected _fakeContent: any;
protected _fakeError: any;
set error(err: any) {
this._fakeError = err;
}
set content(data: any) {
this._fakeContent = data;
}
get subscription(): Observable<any> | undefined {
return this._observable;
}
subscribe(next: Function, error?: Function, complete?: Function): Observable<any> {
this._observable = new Observable();
if (next && this._fakeContent && !this._fakeError) {
next(this._fakeContent);
}
if (error && this._fakeError) {
error(this._fakeError);
}
if (complete) {
complete();
}
return this._observable;
}
}
+16
View File
@@ -0,0 +1,16 @@
export interface Change {
change_id: string;
event_id: string;
change_timestamp: Date;
is_deleted: boolean;
new_summary: string;
new_description: string;
new_start: Date;
new_end: Date;
new_last_modified: Date;
new_created: Date;
new_location: string;
new_organizer: string;
new_categories: string;
new_recurring: string;
}
+9
View File
@@ -0,0 +1,9 @@
import {Change} from './change';
export interface Event {
event_id: string;
event_uid: string;
latest_event_summary: string;
latest_start_date: Date;
changes: Change[];
}
@@ -0,0 +1,9 @@
header p {
text-align: center;
font-size: 2em;
font-weight: bold;
}
footer {
text-align: center;
}
@@ -0,0 +1,12 @@
<header>
<p>TINF19B4 RaPla Changes</p>
<app-datepicker (selectedMonday)="dateChangedFromPicker($event)" (showDeletedEvents)="showDeletedChangedFromPicker($event)"></app-datepicker>
</header>
<div id="content">
<app-week [events]="events" [showDeletedEvents]="showDeletedEvents"></app-week>
</div>
<footer class="fixed-bottom start-50 translate-middle">
Copyright &#169; 2021 - Patrick Müller
</footer>
@@ -0,0 +1,25 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {LandingpageComponent} from './landingpage.component';
describe('LandingpageComponent', () => {
let component: LandingpageComponent;
let fixture: ComponentFixture<LandingpageComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [LandingpageComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LandingpageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,53 @@
import {Component, OnInit} from '@angular/core';
import {ApiService} from '../../services/api/api.service';
import {Event} from '../../models/event';
@Component({
selector: 'app-landingpage',
templateUrl: './landingpage.component.html',
styleUrls: ['./landingpage.component.css']
})
export class LandingpageComponent implements OnInit {
events: Event[] = [];
eventCount: number = 0;
selectedDate: string = '';
showDeletedEvents: boolean = true;
constructor(
private apiService: ApiService
) {
}
ngOnInit(): void {
}
/**
* Fetches the events for the currently selected date from the API
*/
getEvents() {
this.apiService.getEvents(this.selectedDate).subscribe(events => {
this.events = events;
this.eventCount = events.length;
});
}
/**
* Called when the selected date is changed in the datepicker component.
* Fetches the events for the new date
* @param newDate The new date to get the events for
*/
dateChangedFromPicker(newDate: string) {
this.selectedDate = newDate;
this.getEvents();
}
/**
* Called when the state of "show deleted events" is changed in the datepicker component.
* Passes the new value to the underlying components
* @param newShowDeleted The new state of "show deleted events"
*/
showDeletedChangedFromPicker(newShowDeleted: boolean) {
this.showDeletedEvents = newShowDeleted;
}
}
@@ -0,0 +1,3 @@
<h1>404</h1>
<p>Page not found!</p>
<p routerLink="">Zurück</p>
@@ -0,0 +1,25 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {PageNotFoundComponent} from './page-not-found.component';
describe('PageNotFoundComponent', () => {
let component: PageNotFoundComponent;
let fixture: ComponentFixture<PageNotFoundComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [PageNotFoundComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PageNotFoundComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,16 @@
import {Component, OnInit} from '@angular/core';
@Component({
selector: 'app-page-not-found',
templateUrl: './page-not-found.component.html',
styleUrls: ['./page-not-found.component.css']
})
export class PageNotFoundComponent implements OnInit {
constructor() {
}
ngOnInit(): void {
}
}
@@ -0,0 +1,16 @@
import {TestBed} from '@angular/core/testing';
import {ApiService} from './api.service';
describe('ApiService', () => {
let service: ApiService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ApiService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,56 @@
import {Injectable} from '@angular/core';
import {HttpClient, HttpParams} from '@angular/common/http';
import {Observable} from 'rxjs';
import {Event} from '../../models/event';
@Injectable({
providedIn: 'root'
})
export class ApiService {
apiUrl = 'https://api.plutodev.de/raplachanges/';
constructor(
private http: HttpClient
) {
}
/*
______ __ ________
/ ____/__ / /_ / ____/ /_ ____ _____ ____ ____ _____
/ / __/ _ \/ __/ / / / __ \/ __ `/ __ \/ __ `/ _ \/ ___/
/ /_/ / __/ /_ / /___/ / / / /_/ / / / / /_/ / __(__ )
\____/\___/\__/ \____/_/ /_/\__,_/_/ /_/\__, /\___/____/
/____/
*/
/**
* Gets all events and their corresponding changes for the given week.
* Best to supply only dates which are a monday, although other days are possible as well
* @param week The week to fetch the events for
* @return Observable<Event[]> A list of events
*/
getEvents(week: string): Observable<Event[]> {
try {
let params = new HttpParams();
params = params.append('week', week);
return this.http.get<Event[]>((this.apiUrl), {params});
} catch (exception) {
console.log('Error fetching events from API');
}
return new Observable<Event[]>();
}
/**
* Gets the event details for the specified event
* @param id The UID of the event as saved in SQL
* @return Observable<Event> The single event
*/
getEventById(id: string): Observable<Event> {
try {
return this.http.get<Event>((this.apiUrl + '/' + id));
} catch (exception) {
console.log('Error fetching events from API');
}
return new Observable<Event>();
}
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { UtilitiesService } from './utilities.service';
describe('UtilitiesService', () => {
let service: UtilitiesService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(UtilitiesService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,54 @@
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class UtilitiesService {
constructor() {
}
/**
* Get the formatted time for the given date
* @param date The date to format the time for
* @return string the formatted time in the format HH:MM (24h format)
*/
getTimeFromDate(date: Date): string {
return date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0');
}
/**
* Get the formatted time for the given date
* @param dateString The date as a string to format the time for
* @return string the formatted time in the format HH:MM (24h format)
*/
getTimeFromString(dateString: string): string {
let date = new Date(dateString);
return this.getTimeFromDate(date);
}
/**
* Formats the given date
* @param date The date to format
* @return string the formatted date in the format DD.mm.YY, HH:MM (24h format)
*/
getDateTimeFromDate(date: Date): string {
let year = date.getFullYear().toString();
let month = date.getMonth().toString().padStart(2, '0');
let day = date.getDate().toString().padStart(2, '0');
let hour = date.getHours().toString().padStart(2, '0');
let minute = date.getMinutes().toString().padStart(2, '0');
return day + '.' + month + '.' + year + ', ' + hour + ':' + minute;
}
/**
* Formats the given date
* @param dateString The date as a string to format
* @return string the formatted date in the format DD.mm.YY, HH:MM (24h format)
*/
getDateTimeFromString(dateString: string): string {
let date = new Date(dateString);
return this.getDateTimeFromDate(date);
}
}
@@ -1,3 +1,3 @@
export const environment = { export const environment = {
production: true production: true
}; };
+1 -1
View File
@@ -3,7 +3,7 @@
// The list of file replacements can be found in `angular.json`. // The list of file replacements can be found in `angular.json`.
export const environment = { export const environment = {
production: false production: false
}; };
/* /*
+6 -6
View File
@@ -1,13 +1,13 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>TINF19B4 RaPla Changes</title> <title>TINF19B4 RaPla Changes</title>
<base href="/"> <base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
</head> </head>
<body> <body>
<app-root></app-root> <app-root></app-root>
</body> </body>
</html> </html>
+6 -6
View File
@@ -1,12 +1,12 @@
import { enableProdMode } from '@angular/core'; import {enableProdMode} from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module'; import {AppModule} from './app/app.module';
import { environment } from './environments/environment'; import {environment} from './environments/environment';
if (environment.production) { if (environment.production) {
enableProdMode(); enableProdMode();
} }
platformBrowserDynamic().bootstrapModule(AppModule) platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err)); .catch(err => console.error(err));
+1 -1
View File
@@ -57,7 +57,7 @@
/*************************************************************************************************** /***************************************************************************************************
* Zone JS is required by default for Angular itself. * Zone JS is required by default for Angular itself.
*/ */
import 'zone.js'; // Included with Angular CLI. import 'zone.js'; // Included with Angular CLI.
/*************************************************************************************************** /***************************************************************************************************
+8
View File
@@ -1 +1,9 @@
/* You can add global styles to this file, and also import other style files */ /* You can add global styles to this file, and also import other style files */
@import '~bootstrap/dist/css/bootstrap.css';
html,
body {
height: 100%;
background-color: #191718;
color: #E0E5E9;
}
+9 -12
View File
@@ -1,24 +1,21 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files // This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing'; import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing'; import {getTestBed} from '@angular/core/testing';
import { import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: { declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): { context(path: string, deep?: boolean, filter?: RegExp): {
keys(): string[]; keys(): string[];
<T>(id: string): T; <T>(id: string): T;
}; };
}; };
// First, initialize the Angular testing environment. // First, initialize the Angular testing environment.
getTestBed().initTestEnvironment( getTestBed().initTestEnvironment(
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting(), platformBrowserDynamicTesting(),
{ teardown: { destroyAfterEach: true }}, {teardown: {destroyAfterEach: true}}
); );
// Then we find all the tests. // Then we find all the tests.