Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #46: Implement news sub tab with results from news organizations #770

Merged
merged 1 commit into from
Jun 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/app/actions/news.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Action } from '@ngrx/store';

/**
* For each action type in an action group, make a simple
* enum object for all of this group's action types.
*
* The 'actionTypeCheck' utility function coerces strings into string
* literal types and runs a simple check to guarantee all
* action types in the application are unique.
*/
export const ActionTypes = {
NEWS_STATUS: '[NEWS] NEWS STATUS'
};

/**
* Every action is comprised of at least a type and an optional
* payload. Expressing actions as classes enables powerful
* type checking in reducer functions.
*
* See Discriminated Unions: https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions
*/

export class NewsStatusAction implements Action {
type = ActionTypes.NEWS_STATUS;

constructor (public payload: boolean) { }
}

export type Actions
= NewsStatusAction;
31 changes: 31 additions & 0 deletions src/app/actions/newsSuccess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Action } from '@ngrx/store';
import { ApiResponse } from '../models/api-response';

/**
* For each action type in an action group, make a simple
* enum object for all of this group's action types.
*
* The 'actionTypeCheck' utility function coerces strings into string
* literal types and runs a simple check to guarantee all
* action types in the application are unique.
*/
export const ActionTypes = {
NEWS_SEARCH_SUCCESS: '[NEWS] SEARCH SUCCESS'
};

/**
* Every action is comprised of at least a type and an optional
* payload. Expressing actions as classes enables powerful
* type checking in reducer functions.
*
* See Discriminated Unions: https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions
*/

export class NewsSearchSuccessAction implements Action {
type = ActionTypes.NEWS_SEARCH_SUCCESS;

constructor (public payload: ApiResponse) { }
}

export type Actions
= NewsSearchSuccessAction;
6 changes: 4 additions & 2 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
UserQueryEffects,
WallPaginationEffects,
MediaWallDirectUrlEffects,
SetTitleEffects
SetTitleEffects,
DisplayNewsEffects
} from './effects';

import { LoklakAppRoutingModule } from './app-routing.module';
Expand Down Expand Up @@ -105,7 +106,8 @@ import { SpeechComponent } from './speech/speech.component';
MediaWallQueryEffects,
WallPaginationEffects,
MediaWallDirectUrlEffects,
SetTitleEffects
SetTitleEffects,
DisplayNewsEffects
]),

/**
Expand Down
1 change: 1 addition & 0 deletions src/app/effects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './query.effects';
export * from './user-query.effects';
export * from './media-wall-direct-url.effects';
export * from './title.effects';
export * from './news.effects';
40 changes: 40 additions & 0 deletions src/app/effects/news.effects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Injectable } from '@angular/core';
import { Effect, Actions, ofType } from '@ngrx/effects';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import * as newsAction from '../actions/news';
import * as newsStatusAction from '../actions/newsSuccess';
import * as fromRoot from '../reducers';
import { Store } from '@ngrx/store';
import { newsOrgs } from '../shared/news-org';
import { SearchService, SearchServiceConfig } from '../services';

@Injectable()
export class DisplayNewsEffects {

@Effect({ dispatch: false })
searchNews$: Observable<void> = this.actions$
.pipe(
ofType(
newsAction.ActionTypes.NEWS_STATUS
),
map((action) => {
if (action['payload']) {
const orgs = newsOrgs;
orgs.forEach((org) => {
const searchServiceConfig: SearchServiceConfig = new SearchServiceConfig();
this.apiSearchService
.fetchQuery('from:' + org, searchServiceConfig).subscribe(response =>
this.store$.dispatch(new newsStatusAction.NewsSearchSuccessAction(response)));
});
}
})
);

constructor(
private actions$: Actions,
private apiSearchService: SearchService,
private store$: Store<fromRoot.State>
) { }

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
(click)="getFilterResults($event.currentTarget.value)">
Videos
</button>
<button [class.selected]="selectedTab === 'news'" class="tab" value="news"
(click)="getFilterResults($event.currentTarget.value)">
News
</button>
</div>

<div class="tools">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Store } from '@ngrx/store';
import * as fromRoot from '../../reducers';
import * as queryAction from '../../actions/query';
import { Query, FilterList, TimeBound } from '../../models';
import * as newsAction from '../../actions/news';

@Component({
selector: 'feed-advanced-search',
Expand Down Expand Up @@ -66,24 +67,31 @@ export class FeedAdvancedSearchComponent implements OnInit {

public getFilterResults(value: string) {
if (value === 'all') {
this.store.dispatch(new newsAction.NewsStatusAction(false));
this.selectedTab = 'all';
this.filterList = {
image: false,
video: false
};
} else if (value === 'image') {
this.store.dispatch(new newsAction.NewsStatusAction(false));
this.selectedTab = 'image';
this.filterList = {
image: true,
video: false
};
} else if (value === 'video') {
this.store.dispatch(new newsAction.NewsStatusAction(false));
this.selectedTab = 'video';
this.filterList = {
image: false,
video: true
};
} else if (value === 'news') {
this.store.dispatch(new newsAction.NewsStatusAction(true));
this.selectedTab = 'news';
} else {
this.store.dispatch(new newsAction.NewsStatusAction(false));
this.selectedTab = 'all';
this.filterList = {
image: false,
Expand Down
15 changes: 15 additions & 0 deletions src/app/feed/feed-news/feed-news.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div *ngIf="newsResponse.length === 0">
<div class="searching">
<!-- Hook Up the Material Design Progress Bar. -->
</div>
<div class="searching-message">
Searching top news of last 30 days.
</div>
</div>
<div class="feed-wrapper">
<div class="wrapper feed-results">
<div *ngFor="let item of newsResponse.slice(0,30); let i = index ">
<feed-card [feedItem]="item" [feedIndex]="i"></feed-card>
</div>
</div>
</div>
20 changes: 20 additions & 0 deletions src/app/feed/feed-news/feed-news.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@import "../feed.component.scss";

.searching {
border-radius: 50%;
margin: 13% 72.5% auto;
width: 100px;
height: 100px;
border: 5px solid var(--accent-color, $accent-color);
border-top-color: rgba(216,97,96,0.2);
animation: spin 1s infinite linear;
margin-bottom: 20px;
}

.searching-message {
word-break: break-word;
font-size: 1.5em;
text-align: center;
padding: 10px;
margin: 6% -25.5% 23% 38%;
}
34 changes: 34 additions & 0 deletions src/app/feed/feed-news/feed-news.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Component, Input, Output, EventEmitter } from '@angular/core';

import { FeedNewsComponent } from './feed-news.component';

@Component({
selector: 'feed-card',
template: ''
})
class FeedCardStubComponent {
@Input() feedItem;
@Input() feedIndex;
@Output() showLightBox: EventEmitter<any>;
}

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

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

beforeEach(() => {
fixture = TestBed.createComponent(FeedNewsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
});
31 changes: 31 additions & 0 deletions src/app/feed/feed-news/feed-news.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ApiResponseResult } from './../../models/api-response';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Store } from '@ngrx/store';
import * as fromRoot from '../../reducers';
import * as newsAction from '../../actions/news';

@Component({
selector: 'app-feed-news',
templateUrl: './feed-news.component.html',
styleUrls: ['./feed-news.component.scss']
})

export class FeedNewsComponent implements OnInit, OnDestroy {
public newsResponse: ApiResponseResult[] = [];
constructor(
private store: Store<fromRoot.State>
) { }

ngOnInit() {
this.store.select(fromRoot.getNewsResponse).subscribe(v => {
for ( let i = 0; i < v.length; i++ ) {
this.newsResponse.push(v[i]);
}
});
}

ngOnDestroy() {
this.store.dispatch(new newsAction.NewsStatusAction(false));
}

}
9 changes: 7 additions & 2 deletions src/app/feed/feed.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[doCloseSuggestBox$]="doCloseSuggestBox$()"
(searchEvent)="search($event)"
(relocateEvent)="relocateURL($event)"></feed-header>
<div *ngIf="(isSearching$ | async)">
<div *ngIf="(isSearching$ | async) && !newsStatus">
<div class="searching">
<!-- Hook Up the Material Design Progress Bar. -->
</div>
Expand All @@ -14,7 +14,7 @@
</div>
</div>
<br>
<div *ngIf="!(isSearching$ | async)" class="feed-wrapper">
<div *ngIf="!(isSearching$ | async) && !newsStatus" class="feed-wrapper">
<div *ngIf="(areResultsAvailable$ | async)" class="wrapper feed-results">
<div *ngFor="let item of (apiResponseResults$ | async); let i = index ">
<feed-card [feedItem]="item" [feedIndex]="i"></feed-card>
Expand Down Expand Up @@ -46,6 +46,11 @@
<feed-not-found [query]="(query$ | async)"></feed-not-found>
</div>
</div>
<div *ngIf="newsStatus" class="feed-wrapper">
<div class="wrapper feed-results">
<app-feed-news></app-feed-news>
</div>
</div>
</div>

<!-- Peek the footer when Results are not found -->
Expand Down
2 changes: 2 additions & 0 deletions src/app/feed/feed.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy {
public suggestQuery$: Observable<SuggestQuery>;
public isSuggestLoading$: Observable<boolean>;
public suggestResponse$: Observable<SuggestResults[]>;
public newsStatus = false;

constructor(
private route: ActivatedRoute,
Expand All @@ -68,6 +69,7 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy {
) { }

ngOnInit() {
this.store.select(fromRoot.getNewsStatus).subscribe(status => this.newsStatus = status);
this.queryFromURL();
this.getDataFromStore();
}
Expand Down
4 changes: 3 additions & 1 deletion src/app/feed/feed.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { FeedLightboxComponent } from './feed-lightbox/feed-lightbox.component';
import { FeedUserCardComponent } from './feed-user-card/feed-user-card.component';
import { InViewportDirective } from '../shared/in-viewport.directive';
import { FeedAdvancedSearchComponent } from './feed-advanced-search/feed-advanced-search.component';
import { FeedNewsComponent } from './feed-news/feed-news.component';


@NgModule({
Expand Down Expand Up @@ -102,7 +103,8 @@ import { FeedAdvancedSearchComponent } from './feed-advanced-search/feed-advance
UserInfoBoxComponent,
FeedLightboxComponent,
FeedUserCardComponent,
FeedAdvancedSearchComponent
FeedAdvancedSearchComponent,
FeedNewsComponent
],
providers: [
SpeechService
Expand Down
24 changes: 23 additions & 1 deletion src/app/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ import * as fromMediaWallPagination from './media-wall-pagination';
import * as fromMediaWallDesign from './media-wall-design';
import * as fromMediaWallDirectUrl from './media-wall-direct-url';
import * as fromSpeech from './speech';
import * as fromNews from './news-status';
import * as fromNewsResponse from './news-response';

/**
* As mentioned, we treat each reducer like a table in a database. This means
Expand All @@ -83,6 +85,8 @@ export interface State {
mediaWallDirectUrl: fromMediaWallDirectUrl.State;
speech: fromSpeech.State;
title: fromTitle.State;
newsStatus: fromNews.State;
newsResponse: fromNewsResponse.State;
}

/**
Expand Down Expand Up @@ -112,7 +116,9 @@ export const reducers: ActionReducerMap<State> = {
mediaWallPagination: fromMediaWallPagination.reducer,
mediaWallDesign: fromMediaWallDesign.reducer,
mediaWallDirectUrl: fromMediaWallDirectUrl.reducer,
speech: fromSpeech.reducer
speech: fromSpeech.reducer,
newsStatus: fromNews.reducer,
newsResponse: fromNewsResponse.reducer
};

export const metaReducers: MetaReducer<State>[] =
Expand Down Expand Up @@ -345,3 +351,19 @@ export const getIsUserSearchSuccess =
export const getSpeechState = (state: State) => state.speech;

export const getspeechStatus = createSelector(getSpeechState, fromSpeech.getspeechStatus);

/**
* Selector for News Status
*/

export const getNewsState = (state: State) => state.newsStatus;

export const getNewsStatus = createSelector(getNewsState, fromNews.getNewsStatus);

/**
* Selector for News Response
*/

export const getNewsResponseState = (state: State) => state.newsResponse;

export const getNewsResponse = createSelector(getNewsResponseState, fromNewsResponse.getNewsResponse);
Loading