From 22cd0af1bf92a4576316f5510c22f012e085a237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jovan=20Kruni=C4=87?= Date: Wed, 13 Jan 2021 18:21:14 +0100 Subject: [PATCH] feat: news module --- src/app/app-routing.module.ts | 2 +- src/app/app.module.ts | 2 + .../data/detail/data-detail.component.ts | 2 +- .../elements/skeleton-list-item.component.ts | 2 +- .../skeleton-simple-card.component.ts | 2 +- src/app/modules/news/news.module.ts | 52 +++++++++++++++ src/app/modules/news/news.provider.ts | 60 ++++++++++++++++++ .../modules/news/page/news-item.component.ts | 59 +++++++++++++++++ src/app/modules/news/page/news-item.html | 22 +++++++ src/app/modules/news/page/news-item.scss | 12 ++++ .../modules/news/page/news-page.component.ts | 41 ++++++++++++ src/app/modules/news/page/news-page.html | 25 ++++++++ .../news/page/skeleton-news-item.component.ts | 25 ++++++++ .../modules/news/page/skeleton-news-item.html | 12 ++++ src/assets/i18n/de.json | 3 + src/assets/i18n/en.json | 3 + src/assets/imgs/modules/news/placeholder.jpg | Bin 0 -> 13825 bytes 17 files changed, 320 insertions(+), 4 deletions(-) create mode 100644 src/app/modules/news/news.module.ts create mode 100644 src/app/modules/news/news.provider.ts create mode 100644 src/app/modules/news/page/news-item.component.ts create mode 100644 src/app/modules/news/page/news-item.html create mode 100644 src/app/modules/news/page/news-item.scss create mode 100644 src/app/modules/news/page/news-page.component.ts create mode 100644 src/app/modules/news/page/news-page.html create mode 100644 src/app/modules/news/page/skeleton-news-item.component.ts create mode 100644 src/app/modules/news/page/skeleton-news-item.html create mode 100644 src/assets/imgs/modules/news/placeholder.jpg diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 87e5343c..3e7bfee0 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -16,7 +16,7 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; const routes: Routes = [ - {path: '', redirectTo: '/search', pathMatch: 'full'}, + {path: '', redirectTo: '/news', pathMatch: 'full'}, ]; /** diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 24979cc4..4522e2f6 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -30,6 +30,7 @@ import {AppComponent} from './app.component'; import {ConfigModule} from './modules/config/config.module'; import {DataModule} from './modules/data/data.module'; import {MenuModule} from './modules/menu/menu.module'; +import {NewsModule} from './modules/news/news.module'; import {SettingsModule} from './modules/settings/settings.module'; import {StorageModule} from './modules/storage/storage.module'; import {fakeBackendProvider} from './_helpers/fake-backend.interceptor'; @@ -72,6 +73,7 @@ const providers : Provider[] = [ DataModule, IonicModule.forRoot(), MenuModule, + NewsModule, SettingsModule, StorageModule, TranslateModule.forRoot({ diff --git a/src/app/modules/data/detail/data-detail.component.ts b/src/app/modules/data/detail/data-detail.component.ts index 6905a0d0..79d728c6 100644 --- a/src/app/modules/data/detail/data-detail.component.ts +++ b/src/app/modules/data/detail/data-detail.component.ts @@ -42,7 +42,7 @@ export class DataDetailComponent { language: SCLanguageCode; /** - * + * * @param route TODO * @param dataProvider TODO * @param translateService TODO diff --git a/src/app/modules/data/elements/skeleton-list-item.component.ts b/src/app/modules/data/elements/skeleton-list-item.component.ts index 68f48e4b..cc65447d 100644 --- a/src/app/modules/data/elements/skeleton-list-item.component.ts +++ b/src/app/modules/data/elements/skeleton-list-item.component.ts @@ -15,7 +15,7 @@ import {Component} from '@angular/core'; /** - * TODO + * A placeholder to show when a list item is being loaded */ @Component({ selector: 'stapps-skeleton-list-item', diff --git a/src/app/modules/data/elements/skeleton-simple-card.component.ts b/src/app/modules/data/elements/skeleton-simple-card.component.ts index b653a595..71c0beb5 100644 --- a/src/app/modules/data/elements/skeleton-simple-card.component.ts +++ b/src/app/modules/data/elements/skeleton-simple-card.component.ts @@ -15,7 +15,7 @@ import {Component} from '@angular/core'; /** - * TODO + * A placeholder to show when a simple card is being loaded */ @Component({ selector: 'stapps-skeleton-simple-card', diff --git a/src/app/modules/news/news.module.ts b/src/app/modules/news/news.module.ts new file mode 100644 index 00000000..a24db78e --- /dev/null +++ b/src/app/modules/news/news.module.ts @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020-2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; +import {IonicModule} from '@ionic/angular'; +import {TranslateModule} from '@ngx-translate/core'; +import {MomentModule} from 'ngx-moment'; +import {DataModule} from '../data/data.module'; +import {SettingsProvider} from '../settings/settings.provider'; +import {NewsPageComponent} from './page/news-page.component'; +import {SkeletonNewsItem} from './page/skeleton-news-item.component'; +import {NewsItemComponent} from './page/news-item.component'; + +const newsRoutes: Routes = [ + {path: 'news', component: NewsPageComponent}, +]; + +/** + * News Module + */ +@NgModule({ + declarations: [ + NewsPageComponent, + SkeletonNewsItem, + NewsItemComponent, + ], + imports: [ + IonicModule.forRoot(), + TranslateModule.forChild(), + RouterModule.forChild(newsRoutes), + CommonModule, + MomentModule, + DataModule, + ], + providers: [ + SettingsProvider, + ], +}) +export class NewsModule {} diff --git a/src/app/modules/news/news.provider.ts b/src/app/modules/news/news.provider.ts new file mode 100644 index 00000000..1c4caa35 --- /dev/null +++ b/src/app/modules/news/news.provider.ts @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020-2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {Injectable} from '@angular/core'; +import {SCMessage} from '@openstapps/core'; +import {DataProvider} from '../data/data.provider'; +/** + * Service for providing news messages + */ +@Injectable({ + providedIn: 'root', +}) +export class NewsProvider { + constructor(private dataProvider: DataProvider) { + } + + /** + * Get news messages + * TODO: make dates sortable on the backend side and then adjust this method + * @param size How many messages/news to fetch + * @param sort If sort by date needs to be performed + */ + async getList(size: number, sort?: boolean): Promise { + const result = await this.dataProvider.search({ + filter: { + type: 'value', + arguments: { + field: 'type', + value: 'message', + }, + }, + size: size, + }); + + const news = result.data as SCMessage[]; + + if (sort) { + news.sort((a, b) => { + if (typeof a.datePublished !== 'undefined' && typeof b.datePublished !== 'undefined') { + return (a.datePublished > b.datePublished) ? -1 : ((a.datePublished < b.datePublished) ? 1 : 0); + } + + return 0; + }); + } + + return news; + } +} diff --git a/src/app/modules/news/page/news-item.component.ts b/src/app/modules/news/page/news-item.component.ts new file mode 100644 index 00000000..26087a66 --- /dev/null +++ b/src/app/modules/news/page/news-item.component.ts @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {Component, Input} from '@angular/core'; +import {LangChangeEvent, TranslateService} from '@ngx-translate/core'; +import {SCLanguageCode, SCMessage} from '@openstapps/core'; +import {Subscription} from 'rxjs'; +/** + * News page component + */ +@Component({ + selector: 'stapps-news-item', + templateUrl: 'news-item.html', + styleUrls: ['news-item.scss'], +}) +export class NewsItemComponent { + /** + * News (message) to show + */ + @Input() item: SCMessage; + /** + * Current language + */ + language: string; + /** + * Current language subscription + */ + languageSubscription: Subscription; + + constructor(private translateService: TranslateService) {} + + /** + * Remove language subscription on component destruction + */ + ngOnDestroy() { + this.languageSubscription?.unsubscribe(); + } + + /** + * Initialize the local variables on component initialization + */ + ngOnInit() { + this.language = this.translateService.currentLang as SCLanguageCode; + this.languageSubscription = this.translateService.onLangChange.subscribe((event: LangChangeEvent) => { + this.language = event.lang as SCLanguageCode; + }); + } +} diff --git a/src/app/modules/news/page/news-item.html b/src/app/modules/news/page/news-item.html new file mode 100644 index 00000000..be046afc --- /dev/null +++ b/src/app/modules/news/page/news-item.html @@ -0,0 +1,22 @@ + + + + + + + + + + + {{item.datePublished | amLocale: language | amDateFormat:'Do MMMM YYYY, HH:mm'}} + Uhr + + + {{item.name}} + {{item.name}} + + + + {{item.messageBody}} + + diff --git a/src/app/modules/news/page/news-item.scss b/src/app/modules/news/page/news-item.scss new file mode 100644 index 00000000..4b6f7978 --- /dev/null +++ b/src/app/modules/news/page/news-item.scss @@ -0,0 +1,12 @@ +ion-card-header a { + color: var(--ion-color-dark-tint); + text-decoration: none; + span.icon ion-icon { + vertical-align: text-top; + font-size: 60%; + padding-left: 4px; + } +} +ion-card img { + width: 100%; +} diff --git a/src/app/modules/news/page/news-page.component.ts b/src/app/modules/news/page/news-page.component.ts new file mode 100644 index 00000000..5aeca01b --- /dev/null +++ b/src/app/modules/news/page/news-page.component.ts @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020-2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {Component} from '@angular/core'; +import {SCMessage} from '@openstapps/core'; +import {NewsProvider} from '../news.provider'; +/** + * News page component + */ +@Component({ + selector: 'stapps-news-page', + templateUrl: 'news-page.html', +}) +export class NewsPageComponent { + /** + * News (messages) to show + */ + news: SCMessage[]; + + constructor(private newsProvider: NewsProvider) { + } + + /** + * Initialize the local variables on component initialization + */ + async ngOnInit() { + /* tslint:disable:no-magic-numbers */ + this.news = await this.newsProvider.getList(30, true); + } +} diff --git a/src/app/modules/news/page/news-page.html b/src/app/modules/news/page/news-page.html new file mode 100644 index 00000000..30585b05 --- /dev/null +++ b/src/app/modules/news/page/news-page.html @@ -0,0 +1,25 @@ + + + + + + + + {{'news.title' | translate}} + + + + + + + + + + + + + + + + + diff --git a/src/app/modules/news/page/skeleton-news-item.component.ts b/src/app/modules/news/page/skeleton-news-item.component.ts new file mode 100644 index 00000000..9e325cbb --- /dev/null +++ b/src/app/modules/news/page/skeleton-news-item.component.ts @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020-2021 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {Component} from '@angular/core'; + +/** + * A placeholder to show when a news item is being loaded + */ +@Component({ + selector: 'stapps-skeleton-news-item', + templateUrl: 'skeleton-news-item.html', +}) +export class SkeletonNewsItem { +} diff --git a/src/app/modules/news/page/skeleton-news-item.html b/src/app/modules/news/page/skeleton-news-item.html new file mode 100644 index 00000000..fa2ad103 --- /dev/null +++ b/src/app/modules/news/page/skeleton-news-item.html @@ -0,0 +1,12 @@ + + + + + + + +

+

+

+
+
diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index f3940e22..27b3e221 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -15,6 +15,9 @@ } } }, + "news": { + "title": "Aktuelles" + }, "menu": { "context": { "title": "Kontext MenĂ¼", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 47cc1222..0f7a9619 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -15,6 +15,9 @@ } } }, + "news": { + "title": "News" + }, "menu": { "context": { "title": "context menu", diff --git a/src/assets/imgs/modules/news/placeholder.jpg b/src/assets/imgs/modules/news/placeholder.jpg new file mode 100644 index 0000000000000000000000000000000000000000..94a31799a5afa1585d7db0b2804693df0d8c4615 GIT binary patch literal 13825 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 literal 0 HcmV?d00001