mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-03-14 02:32:50 +00:00
feat: apply new layout overhaul
This commit is contained in:
committed by
Rainer Killinger
parent
f16e5394cc
commit
7bbdba5c0b
@@ -0,0 +1,31 @@
|
||||
<stapps-section
|
||||
[title]="'dashboard.favorites.title' | translate"
|
||||
[isEditable]="true"
|
||||
(onEdit)="onSectionEdit()"
|
||||
>
|
||||
<div *ngIf="(items | async)?.length" class="container">
|
||||
<div
|
||||
*ngFor="let item of items | async"
|
||||
class="card clickable"
|
||||
(click)="notifySelect(item)"
|
||||
>
|
||||
<ion-thumbnail class="ion-margin-end">
|
||||
<ion-icon color="dark" [attr.name]="item.type | dataIcon"></ion-icon>
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
{{ 'name' | thingTranslate: item }}
|
||||
</ion-label>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="!(items | async)?.length">
|
||||
<div class="card">
|
||||
<ion-label>
|
||||
{{ 'dashboard.favorites.no_favorite_prefix' | translate }}
|
||||
<a (click)="onSectionEdit()">{{
|
||||
'dashboard.favorites.no_favorite_link' | translate
|
||||
}}</a>
|
||||
{{ 'dashboard.favorites.no_favorite_suffix' | translate }}
|
||||
</ion-label>
|
||||
</div>
|
||||
</ng-container>
|
||||
</stapps-section>
|
||||
@@ -0,0 +1,36 @@
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: var(--spacing-sm);
|
||||
--size: 60px;
|
||||
|
||||
& > * {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
min-height: var(--size);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
|
||||
ion-thumbnail {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
transform: translate(10%, 20%);
|
||||
margin: 0 auto var(--spacing-xs);
|
||||
--size: 60px;
|
||||
|
||||
ion-icon {
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
margin: auto;
|
||||
--ion-color-base: var(--ion-color-light-icon) !important;
|
||||
}
|
||||
}
|
||||
ion-label {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {AlertController} from '@ionic/angular';
|
||||
import {combineLatest} from 'rxjs';
|
||||
import {
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
startWith,
|
||||
take,
|
||||
} from 'rxjs/operators';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
import {SCThings} from '@openstapps/core';
|
||||
|
||||
import {DataProvider} from '../../../data/data.provider';
|
||||
import {DataRoutingService} from '../../../data/data-routing.service';
|
||||
import {SearchPageComponent} from '../../../data/list/search-page.component';
|
||||
import {PositionService} from '../../../map/position.service';
|
||||
import {SettingsProvider} from '../../../settings/settings.provider';
|
||||
import {FavoritesService} from '../../../favorites/favorites.service';
|
||||
import {ContextMenuService} from '../../../menu/context/context-menu.service';
|
||||
import {ConfigProvider} from '../../../config/config.provider';
|
||||
|
||||
/**
|
||||
* Shows a section with meals of the chosen mensa
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-favorites-section',
|
||||
templateUrl: 'favorites-section.component.html',
|
||||
styleUrls: ['favorites-section.component.scss'],
|
||||
})
|
||||
export class FavoritesSectionComponent
|
||||
extends SearchPageComponent
|
||||
implements OnInit
|
||||
{
|
||||
constructor(
|
||||
protected readonly alertController: AlertController,
|
||||
protected dataProvider: DataProvider,
|
||||
protected readonly contextMenuService: ContextMenuService,
|
||||
protected readonly settingsProvider: SettingsProvider,
|
||||
protected readonly logger: NGXLogger,
|
||||
protected dataRoutingService: DataRoutingService,
|
||||
protected router: Router,
|
||||
route: ActivatedRoute,
|
||||
positionService: PositionService,
|
||||
private favoritesService: FavoritesService,
|
||||
configProvider: ConfigProvider,
|
||||
) {
|
||||
super(
|
||||
alertController,
|
||||
dataProvider,
|
||||
contextMenuService,
|
||||
settingsProvider,
|
||||
logger,
|
||||
dataRoutingService,
|
||||
router,
|
||||
route,
|
||||
positionService,
|
||||
configProvider,
|
||||
);
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
this.subscriptions.push(
|
||||
combineLatest([
|
||||
this.queryTextChanged.pipe(
|
||||
debounceTime(this.searchQueryDueTime),
|
||||
distinctUntilChanged(),
|
||||
startWith(this.queryText),
|
||||
),
|
||||
this.favoritesService.favoritesChanged$,
|
||||
]).subscribe(async () => {
|
||||
await this.fetchAndUpdateItems();
|
||||
this.queryChanged.next();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches/updates the favorites (search page component's method override)
|
||||
*/
|
||||
async fetchAndUpdateItems() {
|
||||
this.favoritesService
|
||||
.search(this.queryText, this.filterQuery, this.sortQuery)
|
||||
.pipe(take(1))
|
||||
.subscribe(result => {
|
||||
this.items = new Promise(resolve => {
|
||||
resolve(
|
||||
result.data && result.data.filter(item => !this.isMensaThing(item)),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function as 'typeof' is not accessible in HTML
|
||||
*
|
||||
* @param item TODO
|
||||
*/
|
||||
isMensaThing(item: SCThings): boolean {
|
||||
return (
|
||||
this.hasCategories(item) &&
|
||||
((item.categories as string[]).includes('canteen') ||
|
||||
(item.categories as string[]).includes('cafe') ||
|
||||
(item.categories as string[]).includes('student canteen') ||
|
||||
(item.categories as string[]).includes('restaurant'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param item TODO
|
||||
*/
|
||||
hasCategories(item: SCThings): item is SCThings & {categories: string[]} {
|
||||
return typeof (item as {categories: string[]}).categories !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit event that an item was selected
|
||||
*/
|
||||
notifySelect(item: SCThings) {
|
||||
this.dataRoutingService.emitChildEvent(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action when user clicked edit to this section
|
||||
*/
|
||||
onSectionEdit() {
|
||||
void this.router.navigate(['/search']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<swiper
|
||||
[config]="sliderOptions"
|
||||
[navigation]="true"
|
||||
class="mensa-swiper card-swiper"
|
||||
*ngIf="dishes && dishes.length > 0"
|
||||
>
|
||||
<ng-template swiperSlide *ngFor="let dish of dishes">
|
||||
<a [routerLink]="'/data-detail/' + dish.uid" class="card">
|
||||
<ion-label>{{ 'name' | thingTranslate: dish }}</ion-label>
|
||||
</a>
|
||||
</ng-template>
|
||||
</swiper>
|
||||
<div class="card" *ngIf="!dishes || dishes.length === 0">
|
||||
<ion-label>
|
||||
{{ 'dashboard.canteens.no_dishes_available' | translate }}
|
||||
</ion-label>
|
||||
</div>
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import {SCDish, SCPlace, SCThings} from '@openstapps/core';
|
||||
import {PlaceMensaService} from '../../../data/types/place/special/mensa/place-mensa-service';
|
||||
|
||||
/**
|
||||
* Shows a section with meals of the chosen mensa
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-mensa-section-content',
|
||||
templateUrl: 'mensa-section-content.component.html',
|
||||
styleUrls: ['mensa-section.component.scss'],
|
||||
})
|
||||
export class MensaSectionContentComponent implements OnInit, OnChanges {
|
||||
/**
|
||||
* Slider options
|
||||
*/
|
||||
sliderOptions = {
|
||||
spaceBetween: 12,
|
||||
freeMode: {
|
||||
enabled: true,
|
||||
sticky: true,
|
||||
},
|
||||
width: 120,
|
||||
};
|
||||
|
||||
/**
|
||||
* Map of dishes for each day
|
||||
*/
|
||||
// eslint-disable-next-line unicorn/no-null
|
||||
dishes: SCDish[] | null = [];
|
||||
|
||||
@Input() items: SCThings[];
|
||||
|
||||
constructor(private readonly mensaService: PlaceMensaService) {}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.getDishes();
|
||||
}
|
||||
|
||||
async ngOnChanges(changes: SimpleChanges) {
|
||||
if (typeof changes.items !== 'undefined') {
|
||||
await this.getDishes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request dishes
|
||||
*/
|
||||
async getDishes() {
|
||||
if (this.items) {
|
||||
for (const item of this.items) {
|
||||
const dishes = await this.mensaService.getAllDishes(item as SCPlace, 1);
|
||||
this.dishes?.push(...dishes[Object.keys(dishes)[0]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<stapps-section
|
||||
[title]="'dashboard.canteens.title' | translate"
|
||||
[isEditable]="true"
|
||||
(onEdit)="onSectionEdit()"
|
||||
>
|
||||
<ng-container *ngIf="(items | async)?.length">
|
||||
<stapps-mensa-section-content
|
||||
[items]="items | async"
|
||||
></stapps-mensa-section-content>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!(items | async)?.length">
|
||||
<div class="card">
|
||||
<ion-label>
|
||||
{{ 'dashboard.canteens.no_favorite_prefix' | translate }}
|
||||
<a (click)="onSectionEdit()">{{
|
||||
'dashboard.canteens.no_favorite_link' | translate
|
||||
}}</a>
|
||||
{{ 'dashboard.canteens.no_favorite_suffix' | translate }}
|
||||
</ion-label>
|
||||
</div>
|
||||
</ng-container>
|
||||
</stapps-section>
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {AlertController, ModalController} from '@ionic/angular';
|
||||
import {combineLatest, Subscription} from 'rxjs';
|
||||
import {
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
startWith,
|
||||
take,
|
||||
} from 'rxjs/operators';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
import {SCThings} from '@openstapps/core';
|
||||
|
||||
import {DataProvider} from '../../../data/data.provider';
|
||||
import {DataRoutingService} from '../../../data/data-routing.service';
|
||||
import {FoodDataListComponent} from '../../../data/list/food-data-list.component';
|
||||
import {PositionService} from '../../../map/position.service';
|
||||
import {SettingsProvider} from '../../../settings/settings.provider';
|
||||
import {FavoritesService} from '../../../favorites/favorites.service';
|
||||
import {ContextMenuService} from '../../../menu/context/context-menu.service';
|
||||
import {ConfigProvider} from '../../../config/config.provider';
|
||||
|
||||
/**
|
||||
* Shows a section with meals of the chosen mensa
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-mensa-section',
|
||||
templateUrl: 'mensa-section.component.html',
|
||||
styleUrls: ['mensa-section.component.scss'],
|
||||
})
|
||||
export class MensaSectionComponent extends FoodDataListComponent {
|
||||
sub: Subscription;
|
||||
|
||||
constructor(
|
||||
protected readonly alertController: AlertController,
|
||||
protected dataProvider: DataProvider,
|
||||
protected readonly contextMenuService: ContextMenuService,
|
||||
protected readonly settingsProvider: SettingsProvider,
|
||||
protected readonly logger: NGXLogger,
|
||||
protected dataRoutingService: DataRoutingService,
|
||||
protected router: Router,
|
||||
route: ActivatedRoute,
|
||||
protected positionService: PositionService,
|
||||
public modalController: ModalController,
|
||||
protected favoritesService: FavoritesService,
|
||||
configProvider: ConfigProvider,
|
||||
) {
|
||||
super(
|
||||
alertController,
|
||||
dataProvider,
|
||||
contextMenuService,
|
||||
settingsProvider,
|
||||
logger,
|
||||
dataRoutingService,
|
||||
router,
|
||||
route,
|
||||
positionService,
|
||||
configProvider,
|
||||
);
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
super.initialize();
|
||||
|
||||
this.subscriptions.push(
|
||||
combineLatest([
|
||||
this.queryTextChanged.pipe(
|
||||
debounceTime(this.searchQueryDueTime),
|
||||
distinctUntilChanged(),
|
||||
startWith(this.queryText),
|
||||
),
|
||||
this.favoritesService.favoritesChanged$,
|
||||
]).subscribe(async query => {
|
||||
this.queryText = query[0];
|
||||
this.from = 0;
|
||||
if (
|
||||
typeof this.filterQuery !== 'undefined' ||
|
||||
this.queryText?.length > 0 ||
|
||||
this.showDefaultData
|
||||
) {
|
||||
await this.fetchAndUpdateItems();
|
||||
this.queryChanged.next();
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches/updates the favorites (search page component's method override)
|
||||
*/
|
||||
async fetchAndUpdateItems() {
|
||||
this.favoritesService
|
||||
.search(this.queryText, this.filterQuery, this.sortQuery)
|
||||
.pipe(take(1))
|
||||
.subscribe(result => {
|
||||
this.items = new Promise(resolve => {
|
||||
resolve(
|
||||
result.data && result.data.filter(item => this.isMensaThing(item)),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function as 'typeof' is not accessible in HTML
|
||||
*
|
||||
* @param item TODO
|
||||
*/
|
||||
isMensaThing(item: SCThings): boolean {
|
||||
return (
|
||||
this.hasCategories(item) &&
|
||||
((item.categories as string[]).includes('canteen') ||
|
||||
(item.categories as string[]).includes('cafe') ||
|
||||
(item.categories as string[]).includes('student canteen') ||
|
||||
(item.categories as string[]).includes('restaurant'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param item TODO
|
||||
*/
|
||||
hasCategories(item: SCThings): item is SCThings & {categories: string[]} {
|
||||
return typeof (item as {categories: string[]}).categories !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Action when user clicked edit to this section
|
||||
*/
|
||||
onSectionEdit() {
|
||||
void this.router.navigate(['/canteen']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface MenuItemInterface {
|
||||
icon: string;
|
||||
label: string;
|
||||
link: string;
|
||||
active: boolean;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import {MenuItemInterface} from './menu-item.interface';
|
||||
|
||||
export const MenuItems: MenuItemInterface[] = [
|
||||
{
|
||||
icon: 'book',
|
||||
label: 'dashboard.navigation.item.catalog',
|
||||
link: '/catalog',
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
icon: 'tools-kitchen',
|
||||
label: 'dashboard.navigation.item.canteen',
|
||||
link: '/canteen',
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
icon: 'map',
|
||||
label: 'dashboard.navigation.item.map',
|
||||
link: '/map',
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
icon: 'settings',
|
||||
label: 'dashboard.navigation.item.settings',
|
||||
link: '/settings',
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
icon: 'search',
|
||||
label: 'dashboard.navigation.item.search',
|
||||
link: '/search',
|
||||
active: false,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
<stapps-section
|
||||
[title]="'dashboard.navigation.title' | translate"
|
||||
[isEditable]="true"
|
||||
[isSectionExtended]="true"
|
||||
(onEdit)="onSectionEdit()"
|
||||
>
|
||||
<swiper
|
||||
[config]="sliderOptions"
|
||||
slidesPerView="auto"
|
||||
class="navigation-swiper card-swiper"
|
||||
>
|
||||
<ng-template swiperSlide *ngFor="let menuItem of activeMenuItems">
|
||||
<a [routerLink]="menuItem.link" class="card">
|
||||
<ion-icon [name]="menuItem.icon"></ion-icon>
|
||||
<ion-label>{{ menuItem.label | translate }}</ion-label>
|
||||
</a>
|
||||
</ng-template>
|
||||
</swiper>
|
||||
</stapps-section>
|
||||
@@ -0,0 +1,21 @@
|
||||
.navigation-swiper.swiper {
|
||||
|
||||
.swiper-slide {
|
||||
|
||||
a {
|
||||
font-family: var(--ion-font-family);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-semi-bold);
|
||||
text-align: center;
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
ion-icon {
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, OnInit, ViewEncapsulation} from '@angular/core';
|
||||
import {ModalController} from '@ionic/angular';
|
||||
import {EditModalComponent} from '../../edit-modal/edit-modal.component';
|
||||
import {MenuItems} from './menu-items.config';
|
||||
import {MenuItemInterface} from './menu-item.interface';
|
||||
import {EditModalTypeEnum} from '../../edit-modal/edit-modal-type.enum';
|
||||
import {StorageProvider} from '../../../storage/storage.provider';
|
||||
|
||||
const DASHBOARD_NAVIGATION = 'stapps.dashboard.navigation';
|
||||
|
||||
/**
|
||||
* Shows a horizontal list of navigation items
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-navigation-section',
|
||||
templateUrl: 'navigation-section.component.html',
|
||||
styleUrls: ['navigation-section.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class NavigationSectionComponent implements OnInit {
|
||||
/**
|
||||
* Slider options
|
||||
*/
|
||||
sliderOptions = {
|
||||
spaceBetween: 12,
|
||||
freeMode: {
|
||||
enabled: true,
|
||||
sticky: true,
|
||||
},
|
||||
width: 120,
|
||||
};
|
||||
|
||||
menuItems: MenuItemInterface[] = MenuItems;
|
||||
|
||||
activeMenuItems: MenuItemInterface[] = MenuItems;
|
||||
|
||||
constructor(
|
||||
public modalController: ModalController,
|
||||
private storageProvider: StorageProvider,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
void this.getItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current order of items
|
||||
*/
|
||||
async getItems() {
|
||||
if (await this.storageProvider.has(DASHBOARD_NAVIGATION)) {
|
||||
const storedMenuItems: string = await this.storageProvider.get(
|
||||
DASHBOARD_NAVIGATION,
|
||||
);
|
||||
if (storedMenuItems) {
|
||||
const parsedMenuItems = JSON.parse(storedMenuItems);
|
||||
if (Array.isArray(parsedMenuItems)) {
|
||||
this.menuItems = parsedMenuItems;
|
||||
this.activeMenuItems = parsedMenuItems.filter(item => item.active);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save updated order of items
|
||||
*
|
||||
* @param items List of items
|
||||
*/
|
||||
setItems(items: MenuItemInterface[]) {
|
||||
this.menuItems = items;
|
||||
this.activeMenuItems = items.filter(item => item.active);
|
||||
void this.storageProvider.put<string>(
|
||||
DASHBOARD_NAVIGATION,
|
||||
JSON.stringify(items),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action when user clicked edit to this section
|
||||
*/
|
||||
async onSectionEdit() {
|
||||
const modal = await this.modalController.create({
|
||||
component: EditModalComponent,
|
||||
componentProps: {
|
||||
items: this.menuItems,
|
||||
type: EditModalTypeEnum.CHECKBOXES,
|
||||
},
|
||||
});
|
||||
await modal.present();
|
||||
|
||||
modal.onDidDismiss().then(result => {
|
||||
if (result.data?.items) {
|
||||
this.setItems(result.data.items);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<stapps-section
|
||||
[title]="'dashboard.news.title' | translate"
|
||||
[isEditable]="false"
|
||||
[customIcon]="'news'"
|
||||
class="is-editable"
|
||||
(onEdit)="onMoreNewsClicked()"
|
||||
>
|
||||
<swiper
|
||||
[config]="sliderOptions"
|
||||
slidesPerView="auto"
|
||||
[navigation]="true"
|
||||
*ngIf="news.length > 0"
|
||||
class="news-swiper card-swiper"
|
||||
>
|
||||
<ng-template swiperSlide *ngFor="let newsItem of news">
|
||||
<a [routerLink]="['/data-detail', newsItem.uid]" class="card">
|
||||
<ion-img [src]="newsItem.image"></ion-img>
|
||||
<ion-label>{{ newsItem.name }}</ion-label>
|
||||
</a>
|
||||
</ng-template>
|
||||
<ng-template swiperSlide>
|
||||
<a [routerLink]="['/news']" class="card more-news">
|
||||
<ion-label>{{
|
||||
'dashboard.news.moreNews' | translate | titlecase
|
||||
}}</ion-label>
|
||||
<ion-thumbnail class="ion-margin-end">
|
||||
<ion-icon color="dark" name="news"></ion-icon>
|
||||
</ion-thumbnail>
|
||||
</a>
|
||||
</ng-template>
|
||||
</swiper>
|
||||
</stapps-section>
|
||||
@@ -0,0 +1,48 @@
|
||||
.news-swiper.swiper {
|
||||
|
||||
.swiper-slide {
|
||||
padding: 0;
|
||||
|
||||
.card {
|
||||
padding: 0;
|
||||
|
||||
ion-img {
|
||||
border-radius: var(--border-radius-default) var(--border-radius-default) 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ion-label {
|
||||
margin: var(--spacing-lg);
|
||||
text-align: left;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-bold);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.more-news {
|
||||
ion-label {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
ion-thumbnail {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
margin: 0 auto var(--spacing-xs);
|
||||
--size: 160px;
|
||||
|
||||
ion-icon {
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
margin: auto;
|
||||
--ion-color-base: var(--ion-color-light-icon) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, OnInit, ViewEncapsulation} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {NewsPageComponent} from '../../../news/page/news-page.component';
|
||||
import {NewsProvider} from '../../../news/news.provider';
|
||||
import {SettingsProvider} from '../../../settings/settings.provider';
|
||||
import {
|
||||
newsFilterSettingsFieldsMapping,
|
||||
NewsFilterSettingsNames,
|
||||
} from '../../../news/news-filter-settings';
|
||||
import {DataProvider} from '../../../data/data.provider';
|
||||
import {SCSearchValueFilter} from '@openstapps/core';
|
||||
|
||||
/**
|
||||
* Shows a section with news
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-news-section',
|
||||
templateUrl: 'news-section.component.html',
|
||||
styleUrls: ['news-section.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class NewsSectionComponent extends NewsPageComponent implements OnInit {
|
||||
/**
|
||||
* Slider options
|
||||
*/
|
||||
sliderOptions = {
|
||||
spaceBetween: 12,
|
||||
freeMode: {
|
||||
enabled: true,
|
||||
sticky: true,
|
||||
},
|
||||
width: 240,
|
||||
breakpoints: {
|
||||
768: {
|
||||
width: 280,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pageSize = 5;
|
||||
|
||||
/**
|
||||
* A map of the filters where the keys are settings names
|
||||
*/
|
||||
filtersMap = new Map<NewsFilterSettingsNames, SCSearchValueFilter>();
|
||||
|
||||
constructor(
|
||||
newsProvider: NewsProvider,
|
||||
settingsProvider: SettingsProvider,
|
||||
private router: Router,
|
||||
) {
|
||||
super(newsProvider, settingsProvider);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
for (const setting of this.settings) {
|
||||
this.filtersMap.set(
|
||||
setting.name as NewsFilterSettingsNames,
|
||||
DataProvider.createValueFilter(
|
||||
newsFilterSettingsFieldsMapping[
|
||||
setting.name as NewsFilterSettingsNames
|
||||
],
|
||||
setting.value as string,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
this.filters = [...this.filtersMap.values()];
|
||||
|
||||
try {
|
||||
await this.fetchNews();
|
||||
} catch {
|
||||
this.news = [];
|
||||
}
|
||||
}
|
||||
|
||||
onMoreNewsClicked() {
|
||||
void this.router.navigate(['/news']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<stapps-section
|
||||
title="{{ 'dashboard.navigation.item.search' | translate }}"
|
||||
[isEditable]="false"
|
||||
>
|
||||
<div class="searchbar">
|
||||
<ion-input
|
||||
type="search"
|
||||
placeholder="{{ 'search.search_bar.placeholder' | translate }}"
|
||||
(submit)="onSubmitSearch()"
|
||||
(keyup.enter)="onSubmitSearch()"
|
||||
[(ngModel)]="searchTerm"
|
||||
></ion-input>
|
||||
<ion-icon
|
||||
name="search"
|
||||
(click)="onSubmitSearch()"
|
||||
class="clickable"
|
||||
></ion-icon>
|
||||
</div>
|
||||
</stapps-section>
|
||||
@@ -0,0 +1,24 @@
|
||||
.searchbar {
|
||||
position: relative;
|
||||
max-width: 700px;
|
||||
|
||||
ion-input {
|
||||
background: var(--ion-color-field-bg);
|
||||
border-radius: var(--border-radius-default);
|
||||
--padding-start: var(--spacing-md);
|
||||
--padding-end: var(--spacing-md);
|
||||
--padding-top: var(--spacing-xl);
|
||||
--padding-bottom: var(--spacing-xl);
|
||||
font-size: var(--font-size-xs);
|
||||
box-shadow: var(--shadow-default);
|
||||
}
|
||||
ion-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: var(--spacing-md);
|
||||
transform: translateY(-50%);
|
||||
z-index: 2;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
|
||||
/**
|
||||
* Shows a search input field
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stapps-search-section',
|
||||
templateUrl: 'search-section.component.html',
|
||||
styleUrls: ['search-section.component.scss'],
|
||||
})
|
||||
export class SearchSectionComponent {
|
||||
searchTerm = '';
|
||||
|
||||
constructor(private router: Router) {}
|
||||
|
||||
/**
|
||||
* User submits search
|
||||
*/
|
||||
onSubmitSearch() {
|
||||
this.router.navigate(['/search', this.searchTerm]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user