Browse Source

Add Search Filter, Styling

Add Seach
 * Category
 * Country
 * q
master
Christian Ziermann 4 years ago
parent
commit
f95ef091fb
28 changed files with 454 additions and 77 deletions
  1. +0
    -3
      src/app/app.component.html
  2. +2
    -3
      src/app/app.component.scss
  3. +4
    -0
      src/app/app.module.ts
  4. +1
    -1
      src/app/misc/footer/footer.component.scss
  5. +0
    -4
      src/app/misc/header/header.component.html
  6. +1
    -1
      src/app/misc/header/header.component.scss
  7. +5
    -0
      src/app/misc/tab/tab/tab.component.scss
  8. +2
    -1
      src/app/misc/tab/tab/tab.component.ts
  9. +13
    -7
      src/app/misc/tab/tabs/tabs.component.html
  10. +32
    -16
      src/app/misc/tab/tabs/tabs.component.scss
  11. +7
    -2
      src/app/misc/tab/tabs/tabs.component.ts
  12. +0
    -10
      src/app/models/dto/news-search.dto copy.ts
  13. +15
    -0
      src/app/models/dto/news-search.dto.ts
  14. +5
    -0
      src/app/models/enum/sort-by.ts
  15. +5
    -1
      src/app/news/all-news-dashboard/all-news-dashboard.component.html
  16. +11
    -1
      src/app/news/all-news-dashboard/all-news-dashboard.component.ts
  17. +2
    -2
      src/app/news/news-dashboard/news-dashboard.component.html
  18. +9
    -0
      src/app/news/news-dashboard/news-dashboard.component.scss
  19. +19
    -0
      src/app/news/news-teaser/news-teaser.component.html
  20. +86
    -0
      src/app/news/news-teaser/news-teaser.component.scss
  21. +25
    -0
      src/app/news/news-teaser/news-teaser.component.spec.ts
  22. +21
    -0
      src/app/news/news-teaser/news-teaser.component.ts
  23. +68
    -1
      src/app/news/top-news-dashboard/top-news-dashboard.component.html
  24. +31
    -0
      src/app/news/top-news-dashboard/top-news-dashboard.component.scss
  25. +32
    -7
      src/app/news/top-news-dashboard/top-news-dashboard.component.ts
  26. +10
    -8
      src/app/service/news.service.ts
  27. +40
    -8
      src/styles.scss
  28. +8
    -1
      src/variables.scss

+ 0
- 3
src/app/app.component.html View File

<div class="site"> <div class="site">
<header>
<app-header></app-header>
</header>
<main> <main>
<app-news-dashboard></app-news-dashboard> <app-news-dashboard></app-news-dashboard>
</main> </main>

+ 2
- 3
src/app/app.component.scss View File

@import "../variables.scss"; @import "../variables.scss";


main { main {
padding-left: $footer-height;
padding-top: $footer-height;
padding: 0px;
margin: 0px;
padding-bottom: $footer-height; padding-bottom: $footer-height;
} }


.site { .site {
height: auto; height: auto;
min-height: 100%;
} }


footer { footer {

+ 4
- 0
src/app/app.module.ts View File

import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';


import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { TabsComponent } from './misc/tab/tabs/tabs.component'; import { TabsComponent } from './misc/tab/tabs/tabs.component';
import { TabComponent } from './misc/tab/tab/tab.component'; import { TabComponent } from './misc/tab/tab/tab.component';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { NewsTeaserComponent } from './news/news-teaser/news-teaser.component';




@NgModule({ @NgModule({
AllNewsDashboardComponent, AllNewsDashboardComponent,
TabsComponent, TabsComponent,
TabComponent, TabComponent,
NewsTeaserComponent,
], ],
imports: [ imports: [
FormsModule,
BrowserModule.withServerTransition({ appId: 'serverApp' }), BrowserModule.withServerTransition({ appId: 'serverApp' }),
AppRoutingModule, AppRoutingModule,
HttpClientModule, HttpClientModule,

+ 1
- 1
src/app/misc/footer/footer.component.scss View File

padding-left: 0.5%; padding-left: 0.5%;
padding-top: 0.5%; padding-top: 0.5%;
padding-bottom: 0.5%; padding-bottom: 0.5%;
background-color: $primary-color;
background: transparent;
margin: 0px; margin: 0px;
font-size: 20px; font-size: 20px;
text-align: left; text-align: left;

+ 0
- 4
src/app/misc/header/header.component.html View File

<div class="header">
<fa-icon [icon]="faNewspaper"></fa-icon>
News Page
</div>

+ 1
- 1
src/app/misc/header/header.component.scss View File



.header { .header {
color: white; color: white;
background: linear-gradient(48deg, $detail-color, $secondary-color, $primary-color);
background: transparent;
margin: 0px; margin: 0px;
font-size: 30px; font-size: 30px;
padding: 1em 1em; padding: 1em 1em;

+ 5
- 0
src/app/misc/tab/tab/tab.component.scss View File

.inactive { .inactive {
display: none; display: none;
} }


.pane {
width: 100%;
}

+ 2
- 1
src/app/misc/tab/tab/tab.component.ts View File

import { Component, OnInit, ContentChildren, Input } from '@angular/core';
import { Component, OnInit, ContentChildren, Input, ViewChildren, TemplateRef } from '@angular/core';
import { TabsComponent } from '../tabs/tabs.component'; import { TabsComponent } from '../tabs/tabs.component';
import { Template } from '@angular/compiler/src/render3/r3_ast';


@Component({ @Component({
templateUrl: 'tab.component.html', templateUrl: 'tab.component.html',

+ 13
- 7
src/app/misc/tab/tabs/tabs.component.html View File

<div class="tab-container"> <div class="tab-container">
<div class="flex row">
<div
<div class="flex-container header">
<p class="header-icon">
<fa-icon [icon]="faNewspaper"></fa-icon>
</p>
<p class="title">News Page</p>
<p class="spacer"></p>
<p class="spacer"></p>
<p
*ngFor="let tab of tabs" *ngFor="let tab of tabs"
(click)="selectTab(tab)" (click)="selectTab(tab)"
[class.active]="tab?.active" [class.active]="tab?.active"
class="flex col tab-title"
class="tab-title"
> >
<p>{{ tab?.title }}</p>
</div>
{{ tab?.title }}
</p>
</div> </div>
<div class="tab-content flex row" >
<ng-content></ng-content>
<div class="tab-content flex flex-row">
<ng-content></ng-content>
</div> </div>
</div> </div>

+ 32
- 16
src/app/misc/tab/tabs/tabs.component.scss View File

$border-radius: 12.5px;
@import "../../../../variables.scss"; @import "../../../../variables.scss";
.tab-title { .tab-title {
min-width: 6em;
cursor: pointer; cursor: pointer;
padding-left: 5px;
padding-right: 5px;
border-top: solid $border-color 1px;
margin-bottom: 0px;
border-top-left-radius: 12.5px;
border-top-right-radius: 12.5px;
background-color: $primary-color;
opacity: 0.4; opacity: 0.4;
transition: 0.3s; transition: 0.3s;
padding: 1em;
} }


.title {
justify-self: flex-start;
align-items: self-start;
}



.tab-title:active, .active {
.tab-title:active,
.active {
opacity: 1; opacity: 1;

} }


.tab-title:hover { .tab-title:hover {
opacity: 0.8
opacity: 0.8;
} }




.tab-content { .tab-content {
border: 5px solid $primary-color;
border-top-right-radius: 12.5px;
border-top-right-radius: 12.5px; border-top-right-radius: 12.5px;
border-bottom-left-radius: 12.5px; border-bottom-left-radius: 12.5px;
border-bottom-right-radius: 12.5px; border-bottom-right-radius: 12.5px;
padding-left: 1em;
height: 100%;
}


.tab-container {
border-radius: $border-radius;
background: white;
}

.header-icon {
padding: 1em;
}

.header {
justify-content: space-between;
align-items: spac;
height: 3em;
flex-direction: row nowrap;
font-size: $header-font-size;
color: white;
background: linear-gradient(48deg, $detail-color, $secondary-color, $primary-color);
} }

+ 7
- 2
src/app/misc/tab/tabs/tabs.component.ts View File

import { Component, OnInit, ContentChildren, QueryList, AfterContentInit } from '@angular/core'; import { Component, OnInit, ContentChildren, QueryList, AfterContentInit } from '@angular/core';
import { TabComponent } from '../tab/tab.component'; import { TabComponent } from '../tab/tab.component';
import { faNewspaper, faFilter } from '@fortawesome/free-solid-svg-icons';
import { from } from 'rxjs';


@Component({ @Component({
selector: 'app-tabs', selector: 'app-tabs',
styleUrls: ['./tabs.component.scss'] styleUrls: ['./tabs.component.scss']
}) })
export class TabsComponent implements OnInit, AfterContentInit { export class TabsComponent implements OnInit, AfterContentInit {
faNewspaper = faNewspaper;
@ContentChildren(TabComponent) tabs: QueryList<TabComponent>; @ContentChildren(TabComponent) tabs: QueryList<TabComponent>;
@ContentChildren(TabComponent) activeTab: TabComponent; @ContentChildren(TabComponent) activeTab: TabComponent;
public selectTab(tab: TabComponent): void { public selectTab(tab: TabComponent): void {
} }


protected getTabAnimationDirection(tab: TabComponent): 'right' | 'left' | 'none' { protected getTabAnimationDirection(tab: TabComponent): 'right' | 'left' | 'none' {
debugger;
const activeTab = this.tabs.toArray().filter(tab => tab.active)[0]; const activeTab = this.tabs.toArray().filter(tab => tab.active)[0];
if (activeTab === undefined) { if (activeTab === undefined) {
return 'none'; return 'none';
return 'none'; return 'none';
} }


getActiveTab(): TabComponent {
return this.tabs.toArray().filter(tab => tab.active)[0];
}

ngAfterContentInit(): void { ngAfterContentInit(): void {
// get all active tabs // get all active tabs
this.tabs.toArray().forEach((tab: TabComponent, index) => tab.tabIndex = index); this.tabs.toArray().forEach((tab: TabComponent, index) => tab.tabIndex = index);

+ 0
- 10
src/app/models/dto/news-search.dto copy.ts View File


export class TopHeadlineSearchDto {
sources: string;
q: string;
qInTitle: string;
domains: string;
excludeDomains: string;
from: Date;
to: Date;
}

+ 15
- 0
src/app/models/dto/news-search.dto.ts View File

import { SortBy } from '../enum/sort-by';

export class NewsSearchDto {
sources?: string;
q?: string;
qInTitle?: string;
domains?: string;
excludeDomains?: string;
from?: Date;
to?: Date;
sortBy?: SortBy;
pageSize?: number;
page?: number;
apiKey: string;
}

+ 5
- 0
src/app/models/enum/sort-by.ts View File

export enum SortBy {
'Relevancy' = 'relevancy',
'Popularity' = 'popularity',
'PublishedAt' = 'publishedAt',
}

+ 5
- 1
src/app/news/all-news-dashboard/all-news-dashboard.component.html View File

<p>all-news-dashboard works!</p>
<div class="flex-container wrap">
<div class="flex-item" *ngFor="let article of news">
<app-news-teaser [article]="article"></app-news-teaser>
</div>
</div>

+ 11
- 1
src/app/news/all-news-dashboard/all-news-dashboard.component.ts View File

import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { NewsService } from '../../service/news.service';
import { Article } from '../../models/model/article';
import { apiKey } from '../../../environments/.api-key';


@Component({ @Component({
selector: 'app-all-news-dashboard', selector: 'app-all-news-dashboard',
}) })
export class AllNewsDashboardComponent implements OnInit { export class AllNewsDashboardComponent implements OnInit {


constructor() { }
news: Article[];
constructor(protected newsService: NewsService) { }


ngOnInit(): void { ngOnInit(): void {
// this.newsService.searchAllNews({
// apiKey
// }).subscribe(response => {
// this.news = response.articles;
// });
} }



} }

+ 2
- 2
src/app/news/news-dashboard/news-dashboard.component.html View File

<app-tabs> <app-tabs>
<app-tab title="Top News">
<app-tab style="width: auto" title="Top News">
<app-top-news-dashboard></app-top-news-dashboard> <app-top-news-dashboard></app-top-news-dashboard>
</app-tab> </app-tab>
<app-tab title="All News">
<app-tab style="width: auto" title="All News">
<app-all-news-dashboard></app-all-news-dashboard> <app-all-news-dashboard></app-all-news-dashboard>
</app-tab> </app-tab>
</app-tabs> </app-tabs>

+ 9
- 0
src/app/news/news-dashboard/news-dashboard.component.scss View File

@import "../../../variables.scss";

$border-radius: 12.5px;

.dashboard {
padding: 0px;
background: white;
}


+ 19
- 0
src/app/news/news-teaser/news-teaser.component.html View File

<div
(click)="open()"
class="teaser"
*ngIf="article.urlToImage && article.content"
>
<div class="teaser-header">
<div class="teaser-title">{{ article.title }}</div>
<div class="teaser-img">
<img
[src]="article.urlToImage"
*ngIf="article.urlToImage"
alt="image of article"
/>
</div>
</div>
<div class="teaser-content" *ngIf="article.description">
{{ article.description }} ... <a [href]="article.url"> Read</a>
</div>
</div>

+ 86
- 0
src/app/news/news-teaser/news-teaser.component.scss View File

@import "../../../variables.scss";

$teaser-border-radius: 12.5px;
$teaser-background: linear-gradient(48deg, rgb(223, 217, 217), white);

.teaser-header {
min-height: 10em;
flex-direction: column;
> div.teaser-img {
border-top-left-radius: $teaser-border-radius;
border-top-right-radius: $teaser-border-radius;
background: $teaser-background;
display: flex;
justify-content: center;
align-items: center;
> img {
padding: 5px;
max-width: 40em;
max-height: 20em;
}
}
> div.teaser-title {
padding: 2em;
background: transparent;
max-width: 40em;
text-align: center;
font-size: $header-font-size;
}
> div.teaser-title::after {
content: "";
background-color: $detail-color;
display: block;
margin: 2rem 0;
height: 2px;
width: 3.75rem;
}
}

.teaser {
cursor: pointer;
border: 1px solid $border-color;
width: 45em;
min-height: 20em;
border-radius: $teaser-border-radius;
}

@-webkit-keyframes hover {
100% {
border: 1px white;
}
}
@keyframes hover {
100% {
border: 1px solid white;
}
}
.teaser:hover {
-webkit-animation: hover 0.3s cubic-bezier(0.39, 0.575, 0.565, 1) both;
animation: hover 0.3s cubic-bezier(0.39, 0.575, 0.565, 1) both;
}



.teaser-content {
background: $teaser-background;
border-bottom-left-radius: $teaser-border-radius;
border-bottom-right-radius: $teaser-border-radius;
min-width: 10em;
padding: 5px;
}

.teaser-footer {
height: 2em;
padding: 5px;
}
.btn {
border-radius: 2.5px;
background-color: $button-color;
color: white;
padding: 5px;
}

a {
color: $detail-color;
text-decoration: none;
}

+ 25
- 0
src/app/news/news-teaser/news-teaser.component.spec.ts View File

import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { NewsTeaserComponent } from './news-teaser.component';

describe('NewsTeaserComponent', () => {
let component: NewsTeaserComponent;
let fixture: ComponentFixture<NewsTeaserComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ NewsTeaserComponent ]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(NewsTeaserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 21
- 0
src/app/news/news-teaser/news-teaser.component.ts View File

import { Component, OnInit, Input } from '@angular/core';
import { Article } from '../../models/model/article';

@Component({
selector: 'app-news-teaser',
templateUrl: './news-teaser.component.html',
styleUrls: ['./news-teaser.component.scss']
})
export class NewsTeaserComponent implements OnInit {
@Input() article: Article;
constructor() { }

ngOnInit(): void {
}

open(): void {
window.open(this.article.url);

}

}

+ 68
- 1
src/app/news/top-news-dashboard/top-news-dashboard.component.html View File

<p>top-news-dashboard works!</p>
<div class="title flex-container">
Top
<ng-container *ngIf="selectedCategory">
{{ selectedCategory.key }}
</ng-container>
News
<ng-container *ngIf="selectedCountry"
>in {{ selectedCountry.key }}
<img
class="flag"
src="https://www.countryflags.io/{{
selectedCountry.value
}}/shiny/64.png"
/></ng-container>
</div>

<div class="flex-container filter-bar" *ngIf="news">
<div class="filter">
<label for="search">Search: </label>
<input
(change)="upateDto()"
name="search"
id="search"
[(ngModel)]="searchDto.q"
/>
</div>
<div class="filter">
<label for="country">Country: </label>
<select
(change)="upateDto()"
name="country"
id="country"
[(ngModel)]="selectedCountry"
>
<option
*ngFor="let country of CountryCodes | keyvalue"
[ngValue]="country"
>
{{ country.key }}
</option>
</select>
</div>
<div class="filter">
<label for="category">Category: </label>
<select
(change)="upateDto()"
name="category"
id="category"
[(ngModel)]="selectedCategory"
>
<option *ngFor="let category of Category | keyvalue" [ngValue]="category">
{{ category.key }}
</option>
</select>
</div>
</div>

<div class="flex-container wrap" *ngIf="news?.length; else notingFound">
<div class="flex-item" *ngFor="let article of news">
<app-news-teaser [article]="article"></app-news-teaser>
</div>
</div>

<ng-template #notingFound>
<div class="flex-container not-found">
noting found
</div>
</ng-template>

+ 31
- 0
src/app/news/top-news-dashboard/top-news-dashboard.component.scss View File

@import "../../../variables.scss";

.filter-bar {
height: 5em;
margin: 2em;
display: flex;
flex-flow: row;
justify-content: space-evenly;
align-items: center;
border: 1px solid $border-color;
}

.filter {
height: 2em;
}

.title {
background: transparent;
font-size: $header-font-size;
}

.flag {
height: 1em;
padding-left: 0.5em;
align-self: center;
}

.not-found {
width: 160em;
height: 10em;
}

+ 32
- 7
src/app/news/top-news-dashboard/top-news-dashboard.component.ts View File

import { Component, OnInit } from '@angular/core';
import { Component, OnInit, ContentChild } from '@angular/core';
import { Article } from '../../models/model/article'; import { Article } from '../../models/model/article';
import { NewsService } from '../../service/news.service'; import { NewsService } from '../../service/news.service';
import { apiKey } from '../../../environments/.api-key'; import { apiKey } from '../../../environments/.api-key';
import { CountryCode } from '../../models/enum/country-codes'; import { CountryCode } from '../../models/enum/country-codes';
import { TopHeadlineSearchDto } from '../../models/dto/top-headline-search.dto';
import { Category } from 'src/app/models/enum/category';


@Component({ @Component({
selector: 'app-top-news-dashboard', selector: 'app-top-news-dashboard',
styleUrls: ['./top-news-dashboard.component.scss'] styleUrls: ['./top-news-dashboard.component.scss']
}) })
export class TopNewsDashboardComponent implements OnInit { export class TopNewsDashboardComponent implements OnInit {

news: Article[]; news: Article[];
searchDto: TopHeadlineSearchDto = { apiKey };
selectedCountry: { key: string, value: CountryCode };
selectedCategory: { key: string, value: Category };
CountryCodes = CountryCode;
Category = Category;
constructor(protected newsService: NewsService) { } constructor(protected newsService: NewsService) { }


ngOnInit(): void {
this.newsService.getTopNews({
apiKey,
country: CountryCode.Germany
}).subscribe(response => {
upateDto(): void {
if (this.selectedCountry?.value) {
this.searchDto.country = this.selectedCountry.value;
} else {
delete this.searchDto.country;
}
if (this.selectedCategory?.value) {
this.searchDto.category = this.selectedCategory.value;
} else {
delete this.searchDto.category;
}
this.search();
}


search(): void {
this.newsService.searchTopNews(
this.searchDto
).subscribe(response => {
this.news = response.articles; this.news = response.articles;
}); });
} }

ngOnInit(): void {
this.selectedCountry = { key: 'Germany', value: CountryCode.Germany };
this.upateDto();
}
} }

+ 10
- 8
src/app/service/news.service.ts View File

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpResponse, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { TopHeadlineSearchDto } from '../models/dto/top-headline-search.dto'; import { TopHeadlineSearchDto } from '../models/dto/top-headline-search.dto';
import { NewsSearchDto } from '../models/dto/news-search.dto';
import { NewsResponse } from '../models/response/news-response'; import { NewsResponse } from '../models/response/news-response';
import { environment } from '../../environments/environment';
import { Observable, Subject } from 'rxjs';
import { map, tap } from 'rxjs/operators';


@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'


constructor(protected http: HttpClient) { } constructor(protected http: HttpClient) { }


getTopNews(searchDto: TopHeadlineSearchDto): Observable<NewsResponse> | null {
return new Subject<NewsResponse>();
const headers = new HttpHeaders();
return this.http.get<NewsResponse>(`${environment.apiUrl}/top-headlines`, { params: searchDto as any, headers });
searchTopNews(searchDto: TopHeadlineSearchDto): Observable<NewsResponse> | null {
return this.http.get<NewsResponse>(`${environment.apiUrl}/top-headlines`, { params: searchDto as any });
}

searchAllNews(searchDto: NewsSearchDto): Observable<NewsResponse> | null {
return this.http.get<NewsResponse>(`${environment.apiUrl}/everything`, { params: searchDto as any });
} }
} }

+ 40
- 8
src/styles.scss View File

@import "./variables.scss"; @import "./variables.scss";
.flex { .flex {
display: flex; display: flex;
.container {
> .container {
flex-direction: column; flex-direction: column;
} }
.col {
> .col {
flex-direction: column; flex-direction: column;
} }
.row {
flex-direction: row;
}
.content {
> .content {
flex: 1; flex: 1;
} }
} }


.flex-row {
flex-direction: row;
}

.flex-container {
padding: 0;
margin: 0;
list-style: none;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -moz-flex;
display: -webkit-flex;
display: flex;
justify-content: center;
align-items: center;
}

.nowrap {
-webkit-flex-wrap: nowrap;
flex-wrap: nowrap;
}

.spacer {
flex-grow: 1;
}
.wrap {
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
}

.flex-item {
padding: 5px;
}

.col > div { .col > div {
margin: 10px; margin: 10px;
padding: 20px; padding: 20px;


body, body,
html { html {
background-color: $base-color;
font-size: $default-font-size;
background: linear-gradient(48deg, $detail-color, $secondary-color, $primary-color);
height: 100%; height: 100%;
margin: 0px; margin: 0px;
} }


+ 8
- 1
src/variables.scss View File

$secondary-color: #8c3cee; $secondary-color: #8c3cee;
$detail-color: #8c3cee; $detail-color: #8c3cee;


$button-color: #8a8095;

$text-primary: white; $text-primary: white;


$border-color: white;
$border-color: #d8d8d8;






$footer-height: 1.2em; $footer-height: 1.2em;


$default-font-size: 15px;

$header-font-size: 25px;

Loading…
Cancel
Save