mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-09-04 10:14:13 +08:00
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:
parent
a12e8af652
commit
a46a30e7c7
10 changed files with 135 additions and 16 deletions
|
@ -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']
|
||||
|
|
|
@ -17,3 +17,4 @@ parameters:
|
|||
currency: true
|
||||
list_max_entries_per_page: true
|
||||
cache_reset_actions: true
|
||||
navigation_tab_limits: true
|
||||
|
|
7
config/services/themes/navigation_tab_limits.yaml
Normal file
7
config/services/themes/navigation_tab_limits.yaml
Normal file
|
@ -0,0 +1,7 @@
|
|||
parameters:
|
||||
themes.navigation_tab_limits:
|
||||
XSmall: 4
|
||||
Small: 4
|
||||
Medium: 6
|
||||
Large: 10
|
||||
XLarge: 12
|
|
@ -25,7 +25,8 @@ export interface NavbarModel {
|
|||
languages: LanguageStrings,
|
||||
userPreferences: UserPreferenceMap,
|
||||
currentUser: CurrentUserModel,
|
||||
appState: AppState
|
||||
appState: AppState,
|
||||
itemThreshold: number
|
||||
): void;
|
||||
|
||||
buildGroupTabMenu(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -46,6 +46,7 @@ export interface ModuleAction {
|
|||
url: string;
|
||||
params?: string;
|
||||
icon: string;
|
||||
module?: string;
|
||||
}
|
||||
|
||||
const initialState: Navigation = {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue