mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-03-12 01:32:12 +00:00
feat: apply new layout overhaul
This commit is contained in:
committed by
Rainer Killinger
parent
f16e5394cc
commit
7bbdba5c0b
@@ -4,9 +4,11 @@
|
||||
contentId="{{ contentId }}"
|
||||
side="end"
|
||||
>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-label class="ion-padding-horizontal">
|
||||
<h1>{{ 'menu.context.title' | translate | titlecase }}</h1>
|
||||
<h1 class="ion-padding-horizontal">
|
||||
{{ 'menu.context.title' | translate | titlecase }}
|
||||
</h1>
|
||||
</ion-label>
|
||||
</ion-toolbar>
|
||||
<ion-content>
|
||||
@@ -14,7 +16,7 @@
|
||||
<ion-list>
|
||||
<ion-radio-group class="context-sort" *ngIf="sortOption" [value]="0">
|
||||
<ion-list-header>
|
||||
<ion-icon name="swap-vertical-outline"></ion-icon>
|
||||
<ion-icon name="arrows-down-up"></ion-icon>
|
||||
<ion-title>{{
|
||||
'menu.context.sort.title' | translate | titlecase
|
||||
}}</ion-title>
|
||||
@@ -29,12 +31,9 @@
|
||||
<span *ngIf="sortOption.value === value.value && value.reversible">
|
||||
<ion-icon
|
||||
*ngIf="sortOption.reversed"
|
||||
name="arrow-down-outline"
|
||||
></ion-icon>
|
||||
<ion-icon
|
||||
*ngIf="!sortOption.reversed"
|
||||
name="arrow-up-outline"
|
||||
name="arrow-down"
|
||||
></ion-icon>
|
||||
<ion-icon *ngIf="!sortOption.reversed" name="arrow-up"></ion-icon>
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-radio slot="end" [value]="i"> </ion-radio>
|
||||
@@ -44,7 +43,7 @@
|
||||
<!-- Filter Context -->
|
||||
<div class="context-filter" *ngIf="filterOption">
|
||||
<ion-list-header>
|
||||
<ion-icon name="filter-outline"></ion-icon>
|
||||
<ion-icon name="filter"></ion-icon>
|
||||
<ion-title>{{
|
||||
'menu.context.filter.title' | translate | titlecase
|
||||
}}</ion-title>
|
||||
|
||||
@@ -16,12 +16,14 @@ import {CommonModule} from '@angular/common';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {LayoutModule} from '@angular/cdk/layout';
|
||||
import {IonicModule} from '@ionic/angular';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {SettingsModule} from '../settings/settings.module';
|
||||
import {ContextMenuComponent} from './context/context-menu.component';
|
||||
import {ContextMenuService} from './context/context-menu.service';
|
||||
import {NavigationComponent} from './navigation/navigation.component';
|
||||
import {TabsModule} from './tabs/tabs.module';
|
||||
|
||||
/**
|
||||
* Menu module
|
||||
@@ -36,6 +38,8 @@ import {NavigationComponent} from './navigation/navigation.component';
|
||||
RouterModule,
|
||||
SettingsModule,
|
||||
TranslateModule.forChild(),
|
||||
TabsModule,
|
||||
LayoutModule,
|
||||
],
|
||||
providers: [ContextMenuService],
|
||||
})
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import {NavigationService} from './navigation.service';
|
||||
import config from 'capacitor.config';
|
||||
import {SettingsProvider} from '../../settings/settings.provider';
|
||||
import {BreakpointObserver} from '@angular/cdk/layout';
|
||||
|
||||
/**
|
||||
* Generated class for the MenuPage page.
|
||||
@@ -36,6 +37,8 @@ import {SettingsProvider} from '../../settings/settings.provider';
|
||||
templateUrl: 'navigation.html',
|
||||
})
|
||||
export class NavigationComponent implements OnInit {
|
||||
showTabbar = true;
|
||||
|
||||
/**
|
||||
* Name of the app
|
||||
*/
|
||||
@@ -60,11 +63,16 @@ export class NavigationComponent implements OnInit {
|
||||
public translateService: TranslateService,
|
||||
private navigationService: NavigationService,
|
||||
private settingsProvider: SettingsProvider,
|
||||
private responsive: BreakpointObserver,
|
||||
) {
|
||||
translateService.onLangChange.subscribe((event: LangChangeEvent) => {
|
||||
this.language = event.lang as keyof SCTranslations<SCLanguage>;
|
||||
this.translator = new SCThingTranslator(this.language);
|
||||
});
|
||||
|
||||
this.responsive.observe(['(min-width: 768px)']).subscribe(result => {
|
||||
this.showTabbar = !result.matches;
|
||||
});
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
<ion-split-pane contentId="main">
|
||||
<ion-menu menuId="main" contentId="main" type="overlay" side="start">
|
||||
<ion-split-pane contentId="main" when="md">
|
||||
<ion-menu
|
||||
menuId="main"
|
||||
contentId="main"
|
||||
type="overlay"
|
||||
side="start"
|
||||
swipe-gesture="false"
|
||||
>
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>
|
||||
<img src="assets/imgs/logo.png" alt="" />
|
||||
<span class="text">{{ appName }}</span>
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
<ion-buttons slot="start"></ion-buttons>
|
||||
<ion-title
|
||||
class="clickable"
|
||||
[routerLink]="['/']"
|
||||
[routerDirection]="'root'"
|
||||
>
|
||||
<ion-img src="assets/imgs/logo.png" class="logo"></ion-img>
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
@@ -19,7 +26,10 @@
|
||||
</ion-label>
|
||||
</ion-list-header>
|
||||
<ion-menu-toggle auto-hide="false" *ngFor="let item of category.items">
|
||||
<ion-item [routerDirection]="'root'" [routerLink]="[item.route]">
|
||||
<ion-item
|
||||
[routerDirection]="'root'"
|
||||
[routerLink]="['/' + item.route]"
|
||||
>
|
||||
<ion-icon slot="end" [name]="item.icon"></ion-icon>
|
||||
<ion-label>
|
||||
{{ item.translations[language].title | titlecase }}
|
||||
@@ -31,3 +41,4 @@
|
||||
</ion-menu>
|
||||
<ion-router-outlet id="main"></ion-router-outlet>
|
||||
</ion-split-pane>
|
||||
<stapps-navigation-tabs *ngIf="showTabbar"></stapps-navigation-tabs>
|
||||
|
||||
@@ -1,11 +1,34 @@
|
||||
ion-title {
|
||||
span.text {
|
||||
vertical-align: middle;
|
||||
font-size: 20px;
|
||||
padding-left: 5px;
|
||||
@import '../../../../theme/util/mixins';
|
||||
|
||||
:host {
|
||||
|
||||
ion-split-pane {
|
||||
margin-bottom: calc(var(--ion-tabbar-height) + env(safe-area-inset-bottom));
|
||||
|
||||
@include phoneLandscape {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@include ion-md-up {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
img {
|
||||
height: 25px;
|
||||
vertical-align: middle
|
||||
|
||||
ion-toolbar.in-toolbar {
|
||||
padding-bottom: 0;
|
||||
|
||||
ion-title {
|
||||
position: relative;
|
||||
padding: var(--spacing-xl) var(--spacing-md);
|
||||
|
||||
.logo {
|
||||
object-position: left;
|
||||
height: 80px;
|
||||
|
||||
@include ion-md-up {
|
||||
height: var(--tablet-top-bar-height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
src/app/modules/menu/tabs/tabs-routing.module.ts
Normal file
16
src/app/modules/menu/tabs/tabs-routing.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: '/dashboard',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class TabsRoutingModule {}
|
||||
39
src/app/modules/menu/tabs/tabs.component.scss
Normal file
39
src/app/modules/menu/tabs/tabs.component.scss
Normal file
@@ -0,0 +1,39 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background: var(--ion-color-primary-contrast);
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-top: 1px solid var(--ion-color-medium);
|
||||
max-height: calc(var(--ion-tabbar-height) + env(safe-area-inset-bottom));
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 25%;
|
||||
align-items: center;
|
||||
padding: var(--spacing-xs);
|
||||
|
||||
color: var(--ion-color-medium);
|
||||
background: var(--ion-color-primary-contrast);
|
||||
text-decoration: none;
|
||||
|
||||
&:focus,
|
||||
&.active{
|
||||
color: var(--ion-color-medium-contrast);
|
||||
}
|
||||
|
||||
ion-icon {
|
||||
font-size: 28px;
|
||||
}
|
||||
ion-label {
|
||||
text-transform: uppercase;
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-semi-bold);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/app/modules/menu/tabs/tabs.component.ts
Normal file
63
src/app/modules/menu/tabs/tabs.component.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {
|
||||
SCAppConfigurationMenuCategory,
|
||||
SCLanguage,
|
||||
SCThingTranslator,
|
||||
SCTranslations,
|
||||
} from '@openstapps/core';
|
||||
import {ConfigProvider} from '../../config/config.provider';
|
||||
import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
|
||||
@Component({
|
||||
selector: 'stapps-navigation-tabs',
|
||||
templateUrl: 'tabs.template.html',
|
||||
styleUrls: ['./tabs.component.scss'],
|
||||
})
|
||||
export class TabsComponent {
|
||||
/**
|
||||
* Possible languages to be used for translation
|
||||
*/
|
||||
language: keyof SCTranslations<SCLanguage> = 'en';
|
||||
|
||||
/**
|
||||
* Menu entries from config module
|
||||
*/
|
||||
menu: SCAppConfigurationMenuCategory;
|
||||
|
||||
/**
|
||||
* Core translator
|
||||
*/
|
||||
translator: SCThingTranslator;
|
||||
|
||||
constructor(
|
||||
private readonly configProvider: ConfigProvider,
|
||||
public translateService: TranslateService,
|
||||
private readonly logger: NGXLogger,
|
||||
) {
|
||||
void this.loadMenuEntries();
|
||||
translateService.onLangChange?.subscribe((event: LangChangeEvent) => {
|
||||
this.language = event.lang as keyof SCTranslations<SCLanguage>;
|
||||
this.translator = new SCThingTranslator(this.language);
|
||||
});
|
||||
this.translator = new SCThingTranslator('en');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads menu entries from configProvider
|
||||
*/
|
||||
async loadMenuEntries() {
|
||||
try {
|
||||
const menus = (await this.configProvider.getValue(
|
||||
'menus',
|
||||
)) as SCAppConfigurationMenuCategory[];
|
||||
|
||||
const menu = menus.find(menu => menu.id === 'main');
|
||||
if (menu) {
|
||||
this.menu = menu;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`error from loading menu entries: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/app/modules/menu/tabs/tabs.module.ts
Normal file
15
src/app/modules/menu/tabs/tabs.module.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {IonicModule} from '@ionic/angular';
|
||||
|
||||
import {TabsComponent} from './tabs.component';
|
||||
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, IonicModule, TranslateModule, RouterModule],
|
||||
declarations: [TabsComponent],
|
||||
exports: [TabsComponent],
|
||||
})
|
||||
export class TabsModule {}
|
||||
101
src/app/modules/menu/tabs/tabs.spec.ts
Normal file
101
src/app/modules/menu/tabs/tabs.spec.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/*
|
||||
* Copyright (C) 2018, 2019 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 {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {TestBed, waitForAsync} from '@angular/core/testing';
|
||||
|
||||
import {TabsComponent} from './tabs.component';
|
||||
import {ConfigProvider} from '../../config/config.provider';
|
||||
import {sampleAuthConfiguration} from '../../../_helpers/data/sample-configuration';
|
||||
import {TranslateModule, TranslateService} from '@ngx-translate/core';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
import {Platform} from '@ionic/angular';
|
||||
import {ThingTranslateService} from '../../../translation/thing-translate.service';
|
||||
import {SettingsProvider} from '../../settings/settings.provider';
|
||||
import {ScheduleSyncService} from '../../background/schedule/schedule-sync.service';
|
||||
import {StorageProvider} from '../../storage/storage.provider';
|
||||
|
||||
describe('Tabs', () => {
|
||||
let platformReadySpy: any;
|
||||
let platformSpy: jasmine.SpyObj<Platform>;
|
||||
let translateServiceSpy: jasmine.SpyObj<TranslateService>;
|
||||
let thingTranslateServiceSpy: jasmine.SpyObj<ThingTranslateService>;
|
||||
let settingsProvider: jasmine.SpyObj<SettingsProvider>;
|
||||
let configProvider: jasmine.SpyObj<ConfigProvider>;
|
||||
let ngxLogger: jasmine.SpyObj<NGXLogger>;
|
||||
let scheduleSyncServiceSpy: jasmine.SpyObj<ScheduleSyncService>;
|
||||
let platformIsSpy;
|
||||
let storageProvider: jasmine.SpyObj<StorageProvider>;
|
||||
beforeEach(waitForAsync(() => {
|
||||
platformReadySpy = Promise.resolve();
|
||||
platformIsSpy = Promise.resolve();
|
||||
platformSpy = jasmine.createSpyObj('Platform', {
|
||||
ready: platformReadySpy,
|
||||
is: platformIsSpy,
|
||||
});
|
||||
translateServiceSpy = jasmine.createSpyObj('TranslateService', [
|
||||
'setDefaultLang',
|
||||
'use',
|
||||
]);
|
||||
thingTranslateServiceSpy = jasmine.createSpyObj('ThingTranslateService', [
|
||||
'init',
|
||||
]);
|
||||
settingsProvider = jasmine.createSpyObj('SettingsProvider', [
|
||||
'getSettingValue',
|
||||
'provideSetting',
|
||||
'setCategoriesOrder',
|
||||
]);
|
||||
scheduleSyncServiceSpy = jasmine.createSpyObj('ScheduleSyncService', [
|
||||
'getDifferences',
|
||||
'postDifferencesNotification',
|
||||
]);
|
||||
configProvider = jasmine.createSpyObj('ConfigProvider', [
|
||||
'init',
|
||||
'getAnyValue',
|
||||
]);
|
||||
configProvider.getAnyValue = jasmine.createSpy().and.callFake(function () {
|
||||
return sampleAuthConfiguration;
|
||||
});
|
||||
ngxLogger = jasmine.createSpyObj('NGXLogger', ['log', 'error', 'warn']);
|
||||
storageProvider = jasmine.createSpyObj('StorageProvider', [
|
||||
'init',
|
||||
'get',
|
||||
'has',
|
||||
'put',
|
||||
]);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TabsComponent],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
imports: [TranslateModule.forRoot()],
|
||||
providers: [
|
||||
{provide: Platform, useValue: platformSpy},
|
||||
{provide: TranslateService, useValue: translateServiceSpy},
|
||||
{provide: ThingTranslateService, useValue: thingTranslateServiceSpy},
|
||||
{provide: ScheduleSyncService, useValue: scheduleSyncServiceSpy},
|
||||
{provide: SettingsProvider, useValue: settingsProvider},
|
||||
{provide: ConfigProvider, useValue: configProvider},
|
||||
{provide: NGXLogger, useValue: ngxLogger},
|
||||
{provide: StorageProvider, useValue: storageProvider},
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the tabs page', () => {
|
||||
const fixture = TestBed.createComponent(TabsComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
});
|
||||
36
src/app/modules/menu/tabs/tabs.template.html
Normal file
36
src/app/modules/menu/tabs/tabs.template.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<a
|
||||
[routerDirection]="'root'"
|
||||
[routerLink]="['/dashboard']"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<ion-icon [name]="'smart-home'"></ion-icon>
|
||||
<ion-label>{{ 'tabs.home' | translate }}</ion-label>
|
||||
</a>
|
||||
<a
|
||||
[routerDirection]="'root'"
|
||||
[routerLink]="['/canteen']"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<ion-icon [name]="'tools-kitchen-2'"></ion-icon>
|
||||
<ion-label>{{ 'tabs.canteens' | translate }}</ion-label>
|
||||
</a>
|
||||
<a
|
||||
[routerDirection]="'root'"
|
||||
[routerLink]="['/schedule']"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<ion-icon [name]="'school'"></ion-icon>
|
||||
<ion-label>{{ 'tabs.schedule' | translate }}</ion-label>
|
||||
</a>
|
||||
<a [routerDirection]="'root'" [routerLink]="['/map']" routerLinkActive="active">
|
||||
<ion-icon [name]="'map-search'"></ion-icon>
|
||||
<ion-label>{{ 'tabs.map' | translate }}</ion-label>
|
||||
</a>
|
||||
<a
|
||||
[routerDirection]="'root'"
|
||||
[routerLink]="['/profile']"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
<ion-icon [name]="'user'"></ion-icon>
|
||||
<ion-label>{{ 'tabs.profile' | translate }}</ion-label>
|
||||
</a>
|
||||
Reference in New Issue
Block a user