Add responsive navbar layout

- Add screensize observer to check size changes
- Add navbar item limit configuration per screen size
-- exposed through system configs
- Re-adjust navbar upon screen size changes
This commit is contained in:
Clemente Raposo 2020-06-26 16:52:52 +01:00 committed by Dillon-Brown
parent a12e8af652
commit a46a30e7c7
10 changed files with 135 additions and 16 deletions

View file

@ -34,6 +34,7 @@ services:
$frontendExcludedModules: '%legacy.frontend_excluded_modules%'
$datetimeFormatMap: '%legacy.datetime_format_map%'
$cacheResetActions: '%legacy.cache_reset_actions%'
$navigationTabLimits: '%themes.navigation_tab_limits%'
_instanceof:
App\Service\ProcessHandlerInterface:
tags: ['app.process.handler']

View file

@ -17,3 +17,4 @@ parameters:
currency: true
list_max_entries_per_page: true
cache_reset_actions: true
navigation_tab_limits: true

View file

@ -0,0 +1,7 @@
parameters:
themes.navigation_tab_limits:
XSmall: 4
Small: 4
Medium: 6
Large: 10
XLarge: 12

View file

@ -25,7 +25,8 @@ export interface NavbarModel {
languages: LanguageStrings,
userPreferences: UserPreferenceMap,
currentUser: CurrentUserModel,
appState: AppState
appState: AppState,
itemThreshold: number
): void;
buildGroupTabMenu(

View file

@ -129,13 +129,15 @@ export class NavbarAbstract implements NavbarModel {
* @param {object} userPreferences info
* @param {object} currentUser info
* @param {object} appState info
* @param {number} maxTabs to display
*/
public build(
navigation: Navigation,
language: LanguageStrings,
userPreferences: UserPreferenceMap,
currentUser: CurrentUserModel,
appState: AppState
appState: AppState,
maxTabs: number
): void {
this.resetMenu();
@ -150,12 +152,12 @@ export class NavbarAbstract implements NavbarModel {
const sort = userPreferences.sort_modules_by_name.toString() === 'on';
if (navigationParadigm === 'm') {
this.buildModuleNavigation(navigation, language, appState, sort);
this.buildModuleNavigation(navigation, language, appState, maxTabs, sort);
return;
}
if (navigationParadigm === 'gm') {
this.buildGroupedNavigation(navigation, language, appState, sort);
this.buildGroupedNavigation(navigation, language, appState, maxTabs, sort);
return;
}
}
@ -224,12 +226,14 @@ export class NavbarAbstract implements NavbarModel {
* @param {object} navigation info
* @param {object} languages map
* @param {object} appState info
* @param {number} maxTabs to use
* @param {boolean} sort flag
*/
protected buildModuleNavigation(
navigation: Navigation,
languages: LanguageStrings,
appState: AppState,
maxTabs: number,
sort: boolean
): void {
@ -237,7 +241,7 @@ export class NavbarAbstract implements NavbarModel {
return;
}
this.buildTabMenu(navigation.tabs, navigation.modules, languages, navigation.maxTabs, appState, sort);
this.buildTabMenu(navigation.tabs, navigation.modules, languages, maxTabs, appState, sort);
this.buildSelectedModule(navigation, languages, appState);
}
@ -247,12 +251,14 @@ export class NavbarAbstract implements NavbarModel {
* @param {object} navigation info
* @param {object} languages map
* @param {object} appState info
* @param {number} maxTabs to use
* @param {boolean} sort flag
*/
protected buildGroupedNavigation(
navigation: Navigation,
languages: LanguageStrings,
appState: AppState,
maxTabs: number,
sort: boolean
): void {
@ -260,7 +266,7 @@ export class NavbarAbstract implements NavbarModel {
return;
}
this.buildGroupTabMenu(navigation.tabs, navigation.modules, languages, navigation.maxTabs, navigation.groupedTabs, sort);
this.buildGroupTabMenu(navigation.tabs, navigation.modules, languages, maxTabs, navigation.groupedTabs, sort);
this.buildSelectedModule(navigation, languages, appState);
}

View file

@ -6,7 +6,7 @@ import {ApiService} from '@services/api/api.service';
import {NavbarModel} from './navbar-model';
import {NavbarAbstract} from './navbar.abstract';
import {Navigation, NavigationStore} from '@store/navigation/navigation.store';
import {UserPreferenceStore, UserPreferenceMap} from '@store/user-preference/user-preference.store';
import {UserPreferenceMap, UserPreferenceStore} from '@store/user-preference/user-preference.store';
import {AuthService} from '@services/auth/auth.service';
import {SystemConfigStore} from '@store/system-config/system-config.store';
import {AppState, AppStateStore} from '@store/app-state/app-state.store';
@ -15,6 +15,7 @@ import {RouteConverter} from '@services/navigation/route-converter/route-convert
import {ModuleNameMapper} from '@services/navigation/module-name-mapper/module-name-mapper.service';
import {ActionNameMapper} from '@services/navigation/action-name-mapper/action-name-mapper.service';
import {ModuleNavigation} from '@services/navigation/module-navigation/module-navigation.service';
import {ScreenSize, ScreenSizeObserverService} from '@services/ui/screen-size-observer/screen-size-observer.service';
@Component({
selector: 'scrm-navbar-ui',
@ -35,10 +36,12 @@ export class NavbarUiComponent implements OnInit, OnDestroy {
backLink = false;
mainNavLink = true;
submenu: any = [];
moduleNameMapper = new ModuleNameMapper(this.systemConfigStore)
actionNameMapper = new ActionNameMapper(this.systemConfigStore)
routeConverter = new RouteConverter(this.moduleNameMapper, this.actionNameMapper)
moduleNameMapper = new ModuleNameMapper(this.systemConfigStore);
actionNameMapper = new ActionNameMapper(this.systemConfigStore);
routeConverter = new RouteConverter(this.moduleNameMapper, this.actionNameMapper);
navbar: NavbarModel = new NavbarAbstract(this.routeConverter, this.moduleNavigation);
maxTabs = 8;
screen: ScreenSize = ScreenSize.Medium;
languages$: Observable<LanguageStrings> = this.languageStore.vm$;
userPreferences$: Observable<UserPreferenceMap> = this.userPreferenceStore.userPreferences$;
@ -51,16 +54,24 @@ export class NavbarUiComponent implements OnInit, OnDestroy {
this.languages$,
this.userPreferences$,
this.currentUser$,
this.appState$
this.appState$,
this.screenSize.screenSize$
]).pipe(
map(([navigation, languages, userPreferences, currentUser, appState]) => {
map(([navigation, languages, userPreferences, currentUser, appState, screenSize]) => {
if (screenSize) {
this.screen = screenSize;
}
this.calculateMaxTabs(navigation);
this.navbar.build(
navigation,
languages,
userPreferences,
currentUser,
appState
appState,
this.maxTabs
);
return {
@ -76,7 +87,8 @@ export class NavbarUiComponent implements OnInit, OnDestroy {
protected systemConfigStore: SystemConfigStore,
protected appState: AppStateStore,
private authService: AuthService,
protected moduleNavigation: ModuleNavigation
protected moduleNavigation: ModuleNavigation,
protected screenSize: ScreenSizeObserverService
) {
const navbar = new NavbarAbstract(this.routeConverter, this.moduleNavigation);
this.setNavbar(navbar);
@ -171,4 +183,17 @@ export class NavbarUiComponent implements OnInit, OnDestroy {
protected isLoaded(): boolean {
return this.loaded;
}
protected calculateMaxTabs(navigation: Navigation): void {
const sizeMap = this.systemConfigStore.getConfigValue('navigation_tab_limits');
if (this.screen && sizeMap) {
let maxTabs = sizeMap[this.screen];
if (!maxTabs || navigation.maxTabs && navigation.maxTabs < maxTabs) {
maxTabs = navigation.maxTabs;
}
this.maxTabs = maxTabs;
}
}
}

View file

@ -0,0 +1,64 @@
import {Injectable} from '@angular/core';
import {BreakpointObserver, Breakpoints, BreakpointState} from '@angular/cdk/layout';
import {merge, Observable} from 'rxjs';
import {map} from 'rxjs/operators';
export enum ScreenSize {
XSmall = 'XSmall',
Small = 'Small',
Medium = 'Medium',
Large = 'Large',
XLarge = 'XLarge'
}
@Injectable({
providedIn: 'root'
})
export class ScreenSizeObserverService {
screenSize$: Observable<ScreenSize>;
constructor(protected breakpointObserver: BreakpointObserver) {
this.initScreenSizeObservable();
}
protected initScreenSizeObservable(): void {
this.screenSize$ = merge(
this.breakpointObserver.observe([
Breakpoints.XSmall,
]).pipe(map((result: BreakpointState) => {
if (result.matches) {
return ScreenSize.XSmall;
}
})),
this.breakpointObserver.observe([
Breakpoints.Small,
]).pipe(map((result: BreakpointState) => {
if (result.matches) {
return ScreenSize.Small;
}
})),
this.breakpointObserver.observe([
Breakpoints.Medium,
]).pipe(map((result: BreakpointState) => {
if (result.matches) {
return ScreenSize.Medium;
}
})),
this.breakpointObserver.observe([
Breakpoints.Large,
]).pipe(map((result: BreakpointState) => {
if (result.matches) {
return ScreenSize.Large;
}
})),
this.breakpointObserver.observe([
Breakpoints.XLarge,
]).pipe(map((result: BreakpointState) => {
if (result.matches) {
return ScreenSize.XLarge;
}
}))
);
}
}

View file

@ -46,6 +46,7 @@ export interface ModuleAction {
url: string;
params?: string;
icon: string;
module?: string;
}
const initialState: Navigation = {

View file

@ -48,6 +48,7 @@ class SystemConfigHandler extends LegacyHandler implements SystemConfigProviderI
* @param SystemConfigMappers $mappers
* @param array $systemConfigKeyMap
* @param array $cacheResetActions
* @param array $navigationTabLimits
*/
public function __construct(
string $projectDir,
@ -61,7 +62,8 @@ class SystemConfigHandler extends LegacyHandler implements SystemConfigProviderI
ClassicViewRoutingExclusionsHandler $exclusionsManager,
SystemConfigMappers $mappers,
array $systemConfigKeyMap,
array $cacheResetActions
array $cacheResetActions,
array $navigationTabLimits
) {
parent::__construct($projectDir, $legacyDir, $legacySessionName, $defaultSessionName, $legacyScopeState);
$this->exposedSystemConfigs = $exposedSystemConfigs;
@ -70,6 +72,7 @@ class SystemConfigHandler extends LegacyHandler implements SystemConfigProviderI
$this->injectedSystemConfigs['action_name_map'] = $actionNameMapper->getMap();
$this->injectedSystemConfigs['classicview_routing_exclusions'] = $exclusionsManager->get();
$this->injectedSystemConfigs['cache_reset_actions'] = $cacheResetActions;
$this->injectedSystemConfigs['navigation_tab_limits'] = $navigationTabLimits;
$this->mappers = $mappers;
$this->systemConfigKeyMap = $systemConfigKeyMap;
}

View file

@ -137,6 +137,15 @@ class SystemConfigHandlerTest extends Unit
]
];
$navigationTabLimits = [
'XSmall' => 4,
'Small' => 4,
'Medium' => 6,
'Large' => 10,
'XLarge' => 12
];
$this->handler = new SystemConfigHandler(
$projectDir,
$legacyDir,
@ -149,7 +158,8 @@ class SystemConfigHandlerTest extends Unit
$classicViewExclusionHandler,
$mappers,
$systemConfigKeyMap,
$cacheResetActions
$cacheResetActions,
$navigationTabLimits
);
}