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
Jump to file or symbol
Failed to load files and symbols.
+571 −221
Diff settings

Always

Just for now

View
@@ -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;
@@ -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;
View
@@ -20,7 +20,8 @@ import {
UserQueryEffects,
WallPaginationEffects,
MediaWallDirectUrlEffects,
SetTitleEffects
SetTitleEffects,
DisplayNewsEffects
} from './effects';
import { LoklakAppRoutingModule } from './app-routing.module';
@@ -105,7 +106,8 @@ import { SpeechComponent } from './speech/speech.component';
MediaWallQueryEffects,
WallPaginationEffects,
MediaWallDirectUrlEffects,
SetTitleEffects
SetTitleEffects,
DisplayNewsEffects
]),
/**
View
@@ -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';
@@ -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>
) { }
}
@@ -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">
@@ -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',
@@ -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,
@@ -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>
@@ -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%;
}
@@ -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();
});
});
@@ -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));
}
}
@@ -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>
@@ -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>
@@ -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 -->
@@ -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,
@@ -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();
}
@@ -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({
@@ -102,7 +103,8 @@ import { FeedAdvancedSearchComponent } from './feed-advanced-search/feed-advance
UserInfoBoxComponent,
FeedLightboxComponent,
FeedUserCardComponent,
FeedAdvancedSearchComponent
FeedAdvancedSearchComponent,
FeedNewsComponent
],
providers: [
SpeechService
View
@@ -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
@@ -83,6 +85,8 @@ export interface State {
mediaWallDirectUrl: fromMediaWallDirectUrl.State;
speech: fromSpeech.State;
title: fromTitle.State;
newsStatus: fromNews.State;
newsResponse: fromNewsResponse.State;
}
/**
@@ -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>[] =
@@ -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);
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.