Implement basic ui concept

This commit is contained in:
Patrick Gebhardt 2020-06-18 00:10:30 +02:00
parent 410b363713
commit 25f653008a
24 changed files with 358 additions and 37 deletions

View File

@ -94,7 +94,6 @@
"src/assets" "src/assets"
], ],
"styles": [ "styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss" "src/styles.scss"
], ],
"scripts": [] "scripts": []
@ -129,4 +128,4 @@
} }
}, },
"defaultProject": "frontend" "defaultProject": "frontend"
} }

View File

@ -2,7 +2,7 @@
<button mat-icon-button class="menu-btn" (click)="drawer.toggle()"> <button mat-icon-button class="menu-btn" (click)="drawer.toggle()">
<mat-icon>menu</mat-icon> <mat-icon>menu</mat-icon>
</button> </button>
<h1>Travopti - Prototype</h1> <h1 class="title">Travopti - Prototype</h1>
</mat-toolbar> </mat-toolbar>
<mat-drawer-container class="content"> <mat-drawer-container class="content">
<mat-drawer #drawer class="drawer"> <mat-drawer #drawer class="drawer">

View File

@ -13,6 +13,10 @@
.menu-btn { .menu-btn {
margin-right: 1rem; margin-right: 1rem;
} }
.title {
flex: 1 1 auto
}
} }
.drawer { .drawer {

View File

@ -7,6 +7,7 @@ import {PresetService} from './services/preset.service';
styleUrls: ['./app.component.scss'] styleUrls: ['./app.component.scss']
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
constructor(private ps: PresetService) { constructor(private ps: PresetService) {
} }

View File

@ -21,8 +21,10 @@ import {TranslateModule, TranslateService} from '@ngx-translate/core';
// @ts-ignore // @ts-ignore
import * as enLang from '../assets/i18n/en.json'; import * as enLang from '../assets/i18n/en.json';
import {HttpClientModule} from '@angular/common/http'; import {HttpClientModule} from '@angular/common/http';
import {MatButtonToggleModule, MatCheckboxModule} from '@angular/material'; import {MatButtonToggleModule, MatCheckboxModule, MatDividerModule} from '@angular/material';
import {FormsModule} from '@angular/forms'; import {FormsModule} from '@angular/forms';
import {RegionComponent} from './components/region/region.component';
import {ResultComponent} from './components/result/result.component';
@NgModule({ @NgModule({
@ -31,7 +33,9 @@ import {FormsModule} from '@angular/forms';
HomeComponent, HomeComponent,
NotfoundComponent, NotfoundComponent,
SearchComponent, SearchComponent,
SearchInputComponent SearchInputComponent,
RegionComponent,
ResultComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -50,7 +54,8 @@ import {FormsModule} from '@angular/forms';
HttpClientModule, HttpClientModule,
MatCheckboxModule, MatCheckboxModule,
FormsModule, FormsModule,
MatButtonToggleModule MatButtonToggleModule,
MatDividerModule
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@ -0,0 +1,15 @@
<div class="region-mat-card">
<img class="region-img" src="https://travopti.de/api/v1/regions/{{region.region_id}}/image">
<div class="region-footer">
<div class="region-title">
<span class="region-name">{{region.name}}</span>
<span class="region-country">| {{region.country}}</span>
</div>
<button mat-icon-button>
<mat-icon>bookmark</mat-icon>
</button>
<button mat-icon-button>
<mat-icon>share</mat-icon>
</button>
</div>
</div>

View File

@ -0,0 +1,39 @@
.region-mat-card {
padding: 0;
display: flex;
flex-direction: column;
> .region-img {
flex: 0 0 auto;
width: 100%;
}
> .region-footer {
display: flex;
flex-direction: row;
> .region-title {
flex: 1 1 auto;
display: flex;
flex-direction: row;
margin: 0.25rem 0;
> .region-country {
text-transform: uppercase;
font-size: small;
align-self: center;
}
> .region-name {
font-weight: bold;
font-size: large;
align-self: center;
margin-right: 0.25rem;
}
}
}
}

View File

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

View File

@ -0,0 +1,20 @@
import {Component, Input, OnInit} from '@angular/core';
import {Region} from '../../interfaces/region.interface';
@Component({
selector: 'app-region',
templateUrl: './region.component.html',
styleUrls: ['./region.component.scss']
})
export class RegionComponent implements OnInit {
@Input()
region: Region;
constructor() {
}
ngOnInit() {
}
}

View File

@ -0,0 +1,15 @@
<div class="result-mat-card">
<img class="result-img" src="https://travopti.de/api/v1/regions/{{result.region_id}}/image">
<div class="result-footer">
<div class="result-title">
<span class="result-name">{{result.name}}</span>
<span class="result-country">| {{result.country}}</span>
</div>
<button mat-icon-button>
<mat-icon>bookmark</mat-icon>
</button>
<button mat-icon-button>
<mat-icon>share</mat-icon>
</button>
</div>
</div>

View File

@ -0,0 +1,37 @@
.result-mat-card {
padding: 0;
display: flex;
flex-direction: column;
> .result-img {
flex: 0 0 auto;
width: 100%;
}
> .result-footer {
display: flex;
flex-direction: row;
> .result-title {
flex: 1 1 auto;
display: flex;
flex-direction: row;
margin: 0.25rem 0;
> .result-country {
text-transform: uppercase;
font-size: small;
align-self: center;
}
> .result-name {
font-weight: bold;
font-size: large;
align-self: center;
margin-right: 0.25rem;
}
}
}
}

View File

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

View File

@ -0,0 +1,20 @@
import {Component, Input, OnInit} from '@angular/core';
import {Result} from '../../interfaces/result.interface';
@Component({
selector: 'app-result',
templateUrl: './result.component.html',
styleUrls: ['./result.component.scss']
})
export class ResultComponent implements OnInit {
@Input()
result: Result;
constructor() {
}
ngOnInit() {
}
}

View File

@ -14,7 +14,7 @@
<section> <section>
<h2>What would you prefer?</h2> <h2>What would you prefer?</h2>
<div *ngFor="let key of multiPresetsKeys" class="sub-group"> <div *ngFor="let key of multiPresetsKeys" class="sub-group">
<span class="lable">{{key|translate}}:</span><br> <span class="label">{{key|translate}}:</span><br>
<mat-button-toggle-group [(ngModel)]="multiPresetSelection[key]" [value]="undefined"> <mat-button-toggle-group [(ngModel)]="multiPresetSelection[key]" [value]="undefined">
<mat-button-toggle *ngFor="let preset of multiPresets.get(key)" <mat-button-toggle *ngFor="let preset of multiPresets.get(key)"
[value]="preset.preset_id">{{preset.tag_label|translate}}</mat-button-toggle> [value]="preset.preset_id">{{preset.tag_label|translate}}</mat-button-toggle>

View File

@ -18,7 +18,7 @@
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
> .label { > .label {
font-size: large; font-weight: bold;
} }
} }

View File

@ -1 +1,6 @@
<app-search-input></app-search-input> <app-search-input></app-search-input>
<h2>Explore the world</h2>
<div class="region-container">
<app-region *ngFor="let region of regions" [region]="region"></app-region>
</div>

View File

@ -1,4 +1,19 @@
:host { :host {
margin: 0; margin: 0;
box-sizing: border-box; box-sizing: border-box;
display: flex;
flex-direction: column;
}
app-search-input {
margin-bottom: 2rem;
}
.region-container {
display: flex;
flex-direction: column;
> app-region {
margin-bottom: 1rem;
}
} }

View File

@ -1,4 +1,6 @@
import { Component, OnInit } from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {Region} from '../../interfaces/region.interface';
import {DataService} from '../../services/data.service';
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
@ -7,9 +9,13 @@ import { Component, OnInit } from '@angular/core';
}) })
export class HomeComponent implements OnInit { export class HomeComponent implements OnInit {
constructor() { } regions: Region[];
ngOnInit() { constructor(private ds: DataService) {
}
async ngOnInit() {
this.regions = await this.ds.getAllRegions();
} }
} }

View File

@ -2,15 +2,10 @@
<span #result></span> <span #result></span>
<mat-card *ngIf="results"> <div *ngIf="results">
<h2 matCardTitle>Suchergebnisse ({{results.length}}):</h2> <h2>Results ({{results.length}}):</h2>
<mat-card *ngFor="let result of results" class="resultCard"> <app-result *ngFor="let result of results" [result]="result"></app-result>
<h2 matCardTitle>{{result.name}} ({{result.score}})</h2> </div>
<ul>
<li *ngFor="let score of result.scores">{{score.type|translate}}: {{score.value}}</li>
</ul>
</mat-card>
</mat-card>
<div *ngIf="!results" class="spinner"> <div *ngIf="!results" class="spinner">
<mat-spinner></mat-spinner> <mat-spinner></mat-spinner>

View File

@ -8,7 +8,7 @@ export interface Region {
country: string; country: string;
/** Short description of the region */ /** Short description of the region */
description: string; description: string;
/** Min temperature means per month */ /** Max temperature means per month */
temperature_mean_max: number[]; temperature_mean_max: number[];
/** Monthly precipitation */ /** Monthly precipitation */
precipitation: number[]; precipitation: number[];

View File

@ -3,14 +3,13 @@ import {HttpClient, HttpParams} from '@angular/common/http';
import {Preset} from '../interfaces/preset.interface'; import {Preset} from '../interfaces/preset.interface';
import {Result} from '../interfaces/result.interface'; import {Result} from '../interfaces/result.interface';
import {Region} from '../interfaces/region.interface'; import {Region} from '../interfaces/region.interface';
import {MOCK_PRESETS, MOCK_REGIONS, MOCK_RESULT} from '../mock/mock-data';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class DataService { export class DataService {
private readonly API_URL = 'https://example.com/api/v1'; private readonly API_URL = 'https://travopti.de/api/v1';
constructor(private http: HttpClient) { constructor(private http: HttpClient) {
} }
@ -19,24 +18,24 @@ export class DataService {
const params = new HttpParams(); const params = new HttpParams();
params.append('q', query); params.append('q', query);
// return this.http.get<Result[]>(this.API_URL + '/search', {params}).toPromise(); return this.http.get<Result[]>(this.API_URL + '/search', {params}).toPromise();
return new Promise<Result[]>(resolve => { // return new Promise<Result[]>(resolve => {
resolve(MOCK_RESULT); // resolve(MOCK_RESULT);
}); // });
} }
public getAllPresets(): Promise<Preset[]> { public getAllPresets(): Promise<Preset[]> {
// return this.http.get<Preset[]>(this.API_URL + '/search/presets').toPromise(); return this.http.get<Preset[]>(this.API_URL + '/search/presets').toPromise();
return new Promise<Preset[]>(resolve => { // return new Promise<Preset[]>(resolve => {
resolve(MOCK_PRESETS); // resolve(MOCK_PRESETS);
}); // });
} }
public getAllRegions(): Promise<Region[]> { public getAllRegions(): Promise<Region[]> {
// return this.http.get<Region[]>(this.API_URL + '/regions').toPromise(); return this.http.get<Region[]>(this.API_URL + '/regions').toPromise();
return new Promise<Region[]>(resolve => { // return new Promise<Region[]>(resolve => {
resolve(MOCK_REGIONS); // resolve(MOCK_REGIONS);
}); // });
} }
public getRegion(id: number): Promise<Region> { public getRegion(id: number): Promise<Region> {

View File

@ -10,7 +10,7 @@
"cheap_alcohol": "Cheap alcohol", "cheap_alcohol": "Cheap alcohol",
"cheap_food": "Cheap food", "cheap_food": "Cheap food",
"cheap_water": "Cheap water", "cheap_water": "Cheap water",
"cheap_transportations": "Cheap pubic transport", "cheap_transportations": "Cheap public transport",
"cheap_entertainment": "Cheap entertainment", "cheap_entertainment": "Cheap entertainment",
"warm": "warm", "warm": "warm",
"chilly": "cold", "chilly": "cold",

View File

@ -1,8 +1,13 @@
/* 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 "theme";
html, body { html, body {
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } body {
margin: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
}

91
frontend/src/theme.scss Normal file
View File

@ -0,0 +1,91 @@
@import '~@angular/material/theming';
// Plus imports for other components in your app.
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat-core();
$travopti-black: (
50: #AAAAAA,
100: #999999,
200: #777777,
300: #555555,
400: #333333,
500: #000000,
600: #000000,
700: #000000,
800: #000000,
900: #000000,
A100: #ffffff,
A200: #eeeeee,
A400: #bdbdbd,
A700: #616161,
contrast: (
50: $dark-primary-text,
100: $dark-primary-text,
200: $dark-primary-text,
300: $dark-primary-text,
400: $light-primary-text,
500: $light-primary-text,
600: $light-primary-text,
700: $light-primary-text,
800: $light-primary-text,
900: $light-primary-text,
A100: $dark-primary-text,
A200: $dark-primary-text,
A400: $dark-primary-text,
A700: $light-primary-text,
)
);
$travopti-green: (
50: #016500,
100: #006500,
200: #006500,
300: #005a00,
400: #008000,
500: #006000,
600: #005a00,
700: #005a00,
800: #2e7d32,
900: #1b5e20,
A100: #00ae00,
A200: #009000,
A400: #006400,
A700: #004d00,
contrast: (
50: $dark-primary-text,
100: $dark-primary-text,
200: $dark-primary-text,
300: $dark-primary-text,
400: $dark-primary-text,
500: $dark-primary-text,
600: $light-primary-text,
700: $light-primary-text,
800: $light-primary-text,
900: $light-primary-text,
A100: $dark-primary-text,
A200: $dark-primary-text,
A400: $dark-primary-text,
A700: $dark-primary-text,
)
);
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/
$travopti-app-primary: mat-palette($travopti-black);
$travopti-app-accent: mat-palette($travopti-green, A200, A100, A400);
// The warn palette is optional (defaults to red).
$travopti-app-warn: mat-palette($mat-red);
// Create the theme object (a Sass map containing all of the palettes).
$travopti-app-theme: mat-light-theme($travopti-app-primary, $travopti-app-accent, $travopti-app-warn);
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@include angular-material-theme($travopti-app-theme);