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"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss"
],
"scripts": []
@ -129,4 +128,4 @@
}
},
"defaultProject": "frontend"
}
}

View File

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

View File

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

View File

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

View File

@ -21,8 +21,10 @@ import {TranslateModule, TranslateService} from '@ngx-translate/core';
// @ts-ignore
import * as enLang from '../assets/i18n/en.json';
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 {RegionComponent} from './components/region/region.component';
import {ResultComponent} from './components/result/result.component';
@NgModule({
@ -31,7 +33,9 @@ import {FormsModule} from '@angular/forms';
HomeComponent,
NotfoundComponent,
SearchComponent,
SearchInputComponent
SearchInputComponent,
RegionComponent,
ResultComponent
],
imports: [
BrowserModule,
@ -50,7 +54,8 @@ import {FormsModule} from '@angular/forms';
HttpClientModule,
MatCheckboxModule,
FormsModule,
MatButtonToggleModule
MatButtonToggleModule,
MatDividerModule
],
providers: [],
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>
<h2>What would you prefer?</h2>
<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 *ngFor="let preset of multiPresets.get(key)"
[value]="preset.preset_id">{{preset.tag_label|translate}}</mat-button-toggle>

View File

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

View File

@ -1 +1,6 @@
<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 {
margin: 0;
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({
selector: 'app-home',
@ -7,9 +9,13 @@ import { Component, OnInit } from '@angular/core';
})
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>
<mat-card *ngIf="results">
<h2 matCardTitle>Suchergebnisse ({{results.length}}):</h2>
<mat-card *ngFor="let result of results" class="resultCard">
<h2 matCardTitle>{{result.name}} ({{result.score}})</h2>
<ul>
<li *ngFor="let score of result.scores">{{score.type|translate}}: {{score.value}}</li>
</ul>
</mat-card>
</mat-card>
<div *ngIf="results">
<h2>Results ({{results.length}}):</h2>
<app-result *ngFor="let result of results" [result]="result"></app-result>
</div>
<div *ngIf="!results" class="spinner">
<mat-spinner></mat-spinner>

View File

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

View File

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

View File

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

View File

@ -1,8 +1,13 @@
/* You can add global styles to this file, and also import other style files */
@import "theme";
html, body {
height: 100%;
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);