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%'
|
$frontendExcludedModules: '%legacy.frontend_excluded_modules%'
|
||||||
$datetimeFormatMap: '%legacy.datetime_format_map%'
|
$datetimeFormatMap: '%legacy.datetime_format_map%'
|
||||||
$cacheResetActions: '%legacy.cache_reset_actions%'
|
$cacheResetActions: '%legacy.cache_reset_actions%'
|
||||||
|
$navigationTabLimits: '%themes.navigation_tab_limits%'
|
||||||
_instanceof:
|
_instanceof:
|
||||||
App\Service\ProcessHandlerInterface:
|
App\Service\ProcessHandlerInterface:
|
||||||
tags: ['app.process.handler']
|
tags: ['app.process.handler']
|
||||||
|
|
|
@ -17,3 +17,4 @@ parameters:
|
||||||
currency: true
|
currency: true
|
||||||
list_max_entries_per_page: true
|
list_max_entries_per_page: true
|
||||||
cache_reset_actions: 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,
|
languages: LanguageStrings,
|
||||||
userPreferences: UserPreferenceMap,
|
userPreferences: UserPreferenceMap,
|
||||||
currentUser: CurrentUserModel,
|
currentUser: CurrentUserModel,
|
||||||
appState: AppState
|
appState: AppState,
|
||||||
|
itemThreshold: number
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
buildGroupTabMenu(
|
buildGroupTabMenu(
|
||||||
|
|
|
@ -129,13 +129,15 @@ export class NavbarAbstract implements NavbarModel {
|
||||||
* @param {object} userPreferences info
|
* @param {object} userPreferences info
|
||||||
* @param {object} currentUser info
|
* @param {object} currentUser info
|
||||||
* @param {object} appState info
|
* @param {object} appState info
|
||||||
|
* @param {number} maxTabs to display
|
||||||
*/
|
*/
|
||||||
public build(
|
public build(
|
||||||
navigation: Navigation,
|
navigation: Navigation,
|
||||||
language: LanguageStrings,
|
language: LanguageStrings,
|
||||||
userPreferences: UserPreferenceMap,
|
userPreferences: UserPreferenceMap,
|
||||||
currentUser: CurrentUserModel,
|
currentUser: CurrentUserModel,
|
||||||
appState: AppState
|
appState: AppState,
|
||||||
|
maxTabs: number
|
||||||
): void {
|
): void {
|
||||||
|
|
||||||
this.resetMenu();
|
this.resetMenu();
|
||||||
|
@ -150,12 +152,12 @@ export class NavbarAbstract implements NavbarModel {
|
||||||
const sort = userPreferences.sort_modules_by_name.toString() === 'on';
|
const sort = userPreferences.sort_modules_by_name.toString() === 'on';
|
||||||
|
|
||||||
if (navigationParadigm === 'm') {
|
if (navigationParadigm === 'm') {
|
||||||
this.buildModuleNavigation(navigation, language, appState, sort);
|
this.buildModuleNavigation(navigation, language, appState, maxTabs, sort);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navigationParadigm === 'gm') {
|
if (navigationParadigm === 'gm') {
|
||||||
this.buildGroupedNavigation(navigation, language, appState, sort);
|
this.buildGroupedNavigation(navigation, language, appState, maxTabs, sort);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,12 +226,14 @@ export class NavbarAbstract implements NavbarModel {
|
||||||
* @param {object} navigation info
|
* @param {object} navigation info
|
||||||
* @param {object} languages map
|
* @param {object} languages map
|
||||||
* @param {object} appState info
|
* @param {object} appState info
|
||||||
|
* @param {number} maxTabs to use
|
||||||
* @param {boolean} sort flag
|
* @param {boolean} sort flag
|
||||||
*/
|
*/
|
||||||
protected buildModuleNavigation(
|
protected buildModuleNavigation(
|
||||||
navigation: Navigation,
|
navigation: Navigation,
|
||||||
languages: LanguageStrings,
|
languages: LanguageStrings,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
maxTabs: number,
|
||||||
sort: boolean
|
sort: boolean
|
||||||
): void {
|
): void {
|
||||||
|
|
||||||
|
@ -237,7 +241,7 @@ export class NavbarAbstract implements NavbarModel {
|
||||||
return;
|
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);
|
this.buildSelectedModule(navigation, languages, appState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,12 +251,14 @@ export class NavbarAbstract implements NavbarModel {
|
||||||
* @param {object} navigation info
|
* @param {object} navigation info
|
||||||
* @param {object} languages map
|
* @param {object} languages map
|
||||||
* @param {object} appState info
|
* @param {object} appState info
|
||||||
|
* @param {number} maxTabs to use
|
||||||
* @param {boolean} sort flag
|
* @param {boolean} sort flag
|
||||||
*/
|
*/
|
||||||
protected buildGroupedNavigation(
|
protected buildGroupedNavigation(
|
||||||
navigation: Navigation,
|
navigation: Navigation,
|
||||||
languages: LanguageStrings,
|
languages: LanguageStrings,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
maxTabs: number,
|
||||||
sort: boolean
|
sort: boolean
|
||||||
): void {
|
): void {
|
||||||
|
|
||||||
|
@ -260,7 +266,7 @@ export class NavbarAbstract implements NavbarModel {
|
||||||
return;
|
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);
|
this.buildSelectedModule(navigation, languages, appState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {ApiService} from '@services/api/api.service';
|
||||||
import {NavbarModel} from './navbar-model';
|
import {NavbarModel} from './navbar-model';
|
||||||
import {NavbarAbstract} from './navbar.abstract';
|
import {NavbarAbstract} from './navbar.abstract';
|
||||||
import {Navigation, NavigationStore} from '@store/navigation/navigation.store';
|
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 {AuthService} from '@services/auth/auth.service';
|
||||||
import {SystemConfigStore} from '@store/system-config/system-config.store';
|
import {SystemConfigStore} from '@store/system-config/system-config.store';
|
||||||
import {AppState, AppStateStore} from '@store/app-state/app-state.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 {ModuleNameMapper} from '@services/navigation/module-name-mapper/module-name-mapper.service';
|
||||||
import {ActionNameMapper} from '@services/navigation/action-name-mapper/action-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 {ModuleNavigation} from '@services/navigation/module-navigation/module-navigation.service';
|
||||||
|
import {ScreenSize, ScreenSizeObserverService} from '@services/ui/screen-size-observer/screen-size-observer.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'scrm-navbar-ui',
|
selector: 'scrm-navbar-ui',
|
||||||
|
@ -35,10 +36,12 @@ export class NavbarUiComponent implements OnInit, OnDestroy {
|
||||||
backLink = false;
|
backLink = false;
|
||||||
mainNavLink = true;
|
mainNavLink = true;
|
||||||
submenu: any = [];
|
submenu: any = [];
|
||||||
moduleNameMapper = new ModuleNameMapper(this.systemConfigStore)
|
moduleNameMapper = new ModuleNameMapper(this.systemConfigStore);
|
||||||
actionNameMapper = new ActionNameMapper(this.systemConfigStore)
|
actionNameMapper = new ActionNameMapper(this.systemConfigStore);
|
||||||
routeConverter = new RouteConverter(this.moduleNameMapper, this.actionNameMapper)
|
routeConverter = new RouteConverter(this.moduleNameMapper, this.actionNameMapper);
|
||||||
navbar: NavbarModel = new NavbarAbstract(this.routeConverter, this.moduleNavigation);
|
navbar: NavbarModel = new NavbarAbstract(this.routeConverter, this.moduleNavigation);
|
||||||
|
maxTabs = 8;
|
||||||
|
screen: ScreenSize = ScreenSize.Medium;
|
||||||
|
|
||||||
languages$: Observable<LanguageStrings> = this.languageStore.vm$;
|
languages$: Observable<LanguageStrings> = this.languageStore.vm$;
|
||||||
userPreferences$: Observable<UserPreferenceMap> = this.userPreferenceStore.userPreferences$;
|
userPreferences$: Observable<UserPreferenceMap> = this.userPreferenceStore.userPreferences$;
|
||||||
|
@ -51,16 +54,24 @@ export class NavbarUiComponent implements OnInit, OnDestroy {
|
||||||
this.languages$,
|
this.languages$,
|
||||||
this.userPreferences$,
|
this.userPreferences$,
|
||||||
this.currentUser$,
|
this.currentUser$,
|
||||||
this.appState$
|
this.appState$,
|
||||||
|
this.screenSize.screenSize$
|
||||||
]).pipe(
|
]).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(
|
this.navbar.build(
|
||||||
navigation,
|
navigation,
|
||||||
languages,
|
languages,
|
||||||
userPreferences,
|
userPreferences,
|
||||||
currentUser,
|
currentUser,
|
||||||
appState
|
appState,
|
||||||
|
this.maxTabs
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -76,7 +87,8 @@ export class NavbarUiComponent implements OnInit, OnDestroy {
|
||||||
protected systemConfigStore: SystemConfigStore,
|
protected systemConfigStore: SystemConfigStore,
|
||||||
protected appState: AppStateStore,
|
protected appState: AppStateStore,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
protected moduleNavigation: ModuleNavigation
|
protected moduleNavigation: ModuleNavigation,
|
||||||
|
protected screenSize: ScreenSizeObserverService
|
||||||
) {
|
) {
|
||||||
const navbar = new NavbarAbstract(this.routeConverter, this.moduleNavigation);
|
const navbar = new NavbarAbstract(this.routeConverter, this.moduleNavigation);
|
||||||
this.setNavbar(navbar);
|
this.setNavbar(navbar);
|
||||||
|
@ -171,4 +183,17 @@ export class NavbarUiComponent implements OnInit, OnDestroy {
|
||||||
protected isLoaded(): boolean {
|
protected isLoaded(): boolean {
|
||||||
return this.loaded;
|
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;
|
url: string;
|
||||||
params?: string;
|
params?: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
|
module?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: Navigation = {
|
const initialState: Navigation = {
|
||||||
|
|
|
@ -48,6 +48,7 @@ class SystemConfigHandler extends LegacyHandler implements SystemConfigProviderI
|
||||||
* @param SystemConfigMappers $mappers
|
* @param SystemConfigMappers $mappers
|
||||||
* @param array $systemConfigKeyMap
|
* @param array $systemConfigKeyMap
|
||||||
* @param array $cacheResetActions
|
* @param array $cacheResetActions
|
||||||
|
* @param array $navigationTabLimits
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
string $projectDir,
|
string $projectDir,
|
||||||
|
@ -61,7 +62,8 @@ class SystemConfigHandler extends LegacyHandler implements SystemConfigProviderI
|
||||||
ClassicViewRoutingExclusionsHandler $exclusionsManager,
|
ClassicViewRoutingExclusionsHandler $exclusionsManager,
|
||||||
SystemConfigMappers $mappers,
|
SystemConfigMappers $mappers,
|
||||||
array $systemConfigKeyMap,
|
array $systemConfigKeyMap,
|
||||||
array $cacheResetActions
|
array $cacheResetActions,
|
||||||
|
array $navigationTabLimits
|
||||||
) {
|
) {
|
||||||
parent::__construct($projectDir, $legacyDir, $legacySessionName, $defaultSessionName, $legacyScopeState);
|
parent::__construct($projectDir, $legacyDir, $legacySessionName, $defaultSessionName, $legacyScopeState);
|
||||||
$this->exposedSystemConfigs = $exposedSystemConfigs;
|
$this->exposedSystemConfigs = $exposedSystemConfigs;
|
||||||
|
@ -70,6 +72,7 @@ class SystemConfigHandler extends LegacyHandler implements SystemConfigProviderI
|
||||||
$this->injectedSystemConfigs['action_name_map'] = $actionNameMapper->getMap();
|
$this->injectedSystemConfigs['action_name_map'] = $actionNameMapper->getMap();
|
||||||
$this->injectedSystemConfigs['classicview_routing_exclusions'] = $exclusionsManager->get();
|
$this->injectedSystemConfigs['classicview_routing_exclusions'] = $exclusionsManager->get();
|
||||||
$this->injectedSystemConfigs['cache_reset_actions'] = $cacheResetActions;
|
$this->injectedSystemConfigs['cache_reset_actions'] = $cacheResetActions;
|
||||||
|
$this->injectedSystemConfigs['navigation_tab_limits'] = $navigationTabLimits;
|
||||||
$this->mappers = $mappers;
|
$this->mappers = $mappers;
|
||||||
$this->systemConfigKeyMap = $systemConfigKeyMap;
|
$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(
|
$this->handler = new SystemConfigHandler(
|
||||||
$projectDir,
|
$projectDir,
|
||||||
$legacyDir,
|
$legacyDir,
|
||||||
|
@ -149,7 +158,8 @@ class SystemConfigHandlerTest extends Unit
|
||||||
$classicViewExclusionHandler,
|
$classicViewExclusionHandler,
|
||||||
$mappers,
|
$mappers,
|
||||||
$systemConfigKeyMap,
|
$systemConfigKeyMap,
|
||||||
$cacheResetActions
|
$cacheResetActions,
|
||||||
|
$navigationTabLimits
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue