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

@@ -1,7 +1,4 @@
<div class="site">
<header>
<app-header></app-header>
</header>
<main>
<app-news-dashboard></app-news-dashboard>
</main>

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

@@ -1,14 +1,13 @@
@import "../variables.scss";

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

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

footer {

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

@@ -1,5 +1,6 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@@ -12,6 +13,7 @@ import { AllNewsDashboardComponent } from './news/all-news-dashboard/all-news-da
import { TabsComponent } from './misc/tab/tabs/tabs.component';
import { TabComponent } from './misc/tab/tab/tab.component';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { NewsTeaserComponent } from './news/news-teaser/news-teaser.component';


@NgModule({
@@ -24,8 +26,10 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
AllNewsDashboardComponent,
TabsComponent,
TabComponent,
NewsTeaserComponent,
],
imports: [
FormsModule,
BrowserModule.withServerTransition({ appId: 'serverApp' }),
AppRoutingModule,
HttpClientModule,

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

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

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

@@ -1,4 +0,0 @@
<div class="header">
<fa-icon [icon]="faNewspaper"></fa-icon>
News Page
</div>

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

@@ -2,7 +2,7 @@

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

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

@@ -138,3 +138,8 @@
.inactive {
display: none;
}


.pane {
width: 100%;
}

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

@@ -1,5 +1,6 @@
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 { Template } from '@angular/compiler/src/render3/r3_ast';

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

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

@@ -1,15 +1,21 @@
<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"
(click)="selectTab(tab)"
[class.active]="tab?.active"
class="flex col tab-title"
class="tab-title"
>
<p>{{ tab?.title }}</p>
</div>
{{ tab?.title }}
</p>
</div>
<div class="tab-content flex row" >
<ng-content></ng-content>
<div class="tab-content flex flex-row">
<ng-content></ng-content>
</div>
</div>

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

@@ -1,35 +1,51 @@
$border-radius: 12.5px;
@import "../../../../variables.scss";
.tab-title {
min-width: 6em;
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;
transition: 0.3s;
padding: 1em;
}

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


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

}

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



.tab-content {
border: 5px solid $primary-color;
border-top-right-radius: 12.5px;
border-top-right-radius: 12.5px;
border-bottom-left-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

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

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

protected getTabAnimationDirection(tab: TabComponent): 'right' | 'left' | 'none' {
debugger;
const activeTab = this.tabs.toArray().filter(tab => tab.active)[0];
if (activeTab === undefined) {
return 'none';
@@ -37,6 +38,10 @@ export class TabsComponent implements OnInit, AfterContentInit {
return 'none';
}

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

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

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

@@ -1,10 +0,0 @@

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

@@ -0,0 +1,15 @@
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

@@ -0,0 +1,5 @@
export enum SortBy {
'Relevancy' = 'relevancy',
'Popularity' = 'popularity',
'PublishedAt' = 'publishedAt',
}

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

@@ -1 +1,5 @@
<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

@@ -1,4 +1,7 @@
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({
selector: 'app-all-news-dashboard',
@@ -7,9 +10,16 @@ import { Component, OnInit } from '@angular/core';
})
export class AllNewsDashboardComponent implements OnInit {

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

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

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

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

@@ -0,0 +1,9 @@
@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

@@ -0,0 +1,19 @@
<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

@@ -0,0 +1,86 @@
@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

@@ -0,0 +1,25 @@
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

@@ -0,0 +1,21 @@
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

@@ -1 +1,68 @@
<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

@@ -0,0 +1,31 @@
@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

@@ -1,8 +1,10 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, ContentChild } from '@angular/core';
import { Article } from '../../models/model/article';
import { NewsService } from '../../service/news.service';
import { apiKey } from '../../../environments/.api-key';
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({
selector: 'app-top-news-dashboard',
@@ -10,16 +12,39 @@ import { CountryCode } from '../../models/enum/country-codes';
styleUrls: ['./top-news-dashboard.component.scss']
})
export class TopNewsDashboardComponent implements OnInit {

news: Article[];
searchDto: TopHeadlineSearchDto = { apiKey };
selectedCountry: { key: string, value: CountryCode };
selectedCategory: { key: string, value: Category };
CountryCodes = CountryCode;
Category = Category;
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;
});
}

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

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

@@ -1,10 +1,10 @@
import { HttpClient } from '@angular/common/http';
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 { NewsSearchDto } from '../models/dto/news-search.dto';
import { NewsResponse } from '../models/response/news-response';
import { environment } from '../../environments/environment';
import { Observable, Subject } from 'rxjs';
import { map, tap } from 'rxjs/operators';

@Injectable({
providedIn: 'root'
@@ -13,9 +13,11 @@ export class NewsService {

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

@@ -1,20 +1,52 @@
@import "./variables.scss";
.flex {
display: flex;
.container {
> .container {
flex-direction: column;
}
.col {
> .col {
flex-direction: column;
}
.row {
flex-direction: row;
}
.content {
> .content {
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 {
margin: 10px;
padding: 20px;
@@ -27,8 +59,8 @@

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


+ 8
- 1
src/variables.scss View File

@@ -4,10 +4,17 @@ $primary-color: #2f85f6;
$secondary-color: #8c3cee;
$detail-color: #8c3cee;

$button-color: #8a8095;

$text-primary: white;

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



$footer-height: 1.2em;


$default-font-size: 15px;

$header-font-size: 25px;

Loading…
Cancel
Save