333 lines
15 KiB
Vue
333 lines
15 KiB
Vue
|
<template>
|
||
|
<transition name="layout-menu-container">
|
||
|
<div class="layout-menu-container" @click="onMenuClick" v-show="isMenuVisible">
|
||
|
<TTMenuSearch v-if="layoutMode === 'static'" :model="menu_model"></TTMenuSearch>
|
||
|
<div ref="layout-menu-content" class="layout-menu-content" @mouseover="onMenuContentMouseOver" @mouseleave="onMenuContentMouseLeave">
|
||
|
<!-- <div class="layout-menu-title">MENU</div>-->
|
||
|
<TTAppMenu :key="'appmsenu'"
|
||
|
:model="menu_model"
|
||
|
:layoutMode="layoutMode"
|
||
|
:active="menuActive"
|
||
|
:mobileMenuActive="staticMenuMobileActive"
|
||
|
@menuitem-click="onMenuItemClick" @root-menuitem-click="onRootMenuItemClick"></TTAppMenu>
|
||
|
<div class="layout-menu-footer">
|
||
|
<!-- <div class="layout-menu-footer-title"></div>-->
|
||
|
<div class="layout-menu-footer-content">
|
||
|
<a id="copy_right_logo_link" class="copy-right-logo-link" target="_blank"><img id="copy_right_logo" class="copy-right-logo"></a>
|
||
|
<div class="signal-copyright-container">
|
||
|
<div><ul class="signal-strength">
|
||
|
<li class="signal-strength-very-weak">
|
||
|
<div></div>
|
||
|
</li>
|
||
|
<li class="signal-strength-weak">
|
||
|
<div></div>
|
||
|
</li>
|
||
|
<li class="signal-strength-strong">
|
||
|
<div></div>
|
||
|
</li>
|
||
|
<li class="signal-strength-pretty-strong">
|
||
|
<div></div>
|
||
|
</li>
|
||
|
</ul></div>
|
||
|
<div class="copyright-container">
|
||
|
<!-- REMOVING OR CHANGING THIS COPYRIGHT NOTICE IS IN STRICT VIOLATION OF THE LICENSE AND COPYRIGHT AGREEMENT -->
|
||
|
<span v-html="copyright_notice" id="copy_right_info_1" class="copy-right-info" style="display: none"></span>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div v-if="disable_feedback === false" id="feedbackLinkContainer" class="feedback-link-container">
|
||
|
<span id="feedback-link">Send feedback to {{ application_name }}</span>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</transition>
|
||
|
</template>
|
||
|
<script>
|
||
|
import TTAppMenu from './main_menu/TTAppMenu';
|
||
|
import TTMenuSearch from './main_menu/TTMenuSearch';
|
||
|
import ProgressBar from 'primevue/progressbar';
|
||
|
import { MenuManager } from './main_menu/MenuManager';
|
||
|
|
||
|
export default {
|
||
|
beforeCreate() {
|
||
|
this.main_menu = new MenuManager();
|
||
|
// Set a promise to wait for login to complete
|
||
|
TTPromise.add( 'VueMenu', 'waitOnLoginSuccess' );
|
||
|
TTPromise.add( 'VueMenu', 'waitOnTopBarCreated' );
|
||
|
TTPromise.wait( 'VueMenu', null, () => {
|
||
|
// We wait for login, as initDefaultMenuItems() within MenuManager needs permission data from login. If run too soon, no icons are shown, as permission_results will all return false.
|
||
|
// Also needs to wait for TTTopbar to be created first so we do not run into race condition problems.
|
||
|
this.main_menu.initDefaultMenuItems();
|
||
|
this.menu_model = this.main_menu.getMenu(); // adding to [0] as there is a hidden top level menu in the PrimeVue menu. Can't replace whole menu as JS reference to menu object will be lost.
|
||
|
} );
|
||
|
},
|
||
|
created() {
|
||
|
this.event_bus = new TTEventBus( {
|
||
|
component_id: this.component_id,
|
||
|
} );
|
||
|
this.event_bus.on( this.component_id, 'rebuild_menu', () => {
|
||
|
this.rebuildMenu();
|
||
|
} );
|
||
|
this.event_bus.on( this.component_id,'reset_slim_offsets', () => {
|
||
|
this.resetSlimSubmenuOffsets();
|
||
|
} );
|
||
|
this.event_bus.on( 'global','reset_vue_data', () => {
|
||
|
// Reset the Vue data when the user logs out.
|
||
|
this.resetData();
|
||
|
} );
|
||
|
},
|
||
|
data() {
|
||
|
return {
|
||
|
component_id: 'tt_left_container',
|
||
|
menuActive: false,
|
||
|
application_name: window.APPLICATION_NAME,
|
||
|
copyright_notice: APIGlobal.pre_login_data.copyright_notice,
|
||
|
menu_model: [ // While we wait for login, initiatize the base menu model.
|
||
|
{
|
||
|
label: 'Navigation',
|
||
|
items: [{ // The items array here is what we will be dynamically updating via updateMenu(). If auto update through TTPromise fails, menu can be updated using this menu option.
|
||
|
label: 'Update Menu',
|
||
|
icon: 'pi pi-fw pi-refresh',
|
||
|
command: this.rebuildMenu
|
||
|
}]
|
||
|
}
|
||
|
],
|
||
|
disable_feedback: APIGlobal.pre_login_data.disable_feedback
|
||
|
};
|
||
|
},
|
||
|
props: {
|
||
|
isMenuVisible: Boolean,
|
||
|
layoutMode: String,
|
||
|
staticMenuMobileActive: Boolean,
|
||
|
},
|
||
|
methods: {
|
||
|
resetData() {
|
||
|
Object.assign( this.$data, this.$options.data() );
|
||
|
},
|
||
|
rebuildMenu() {
|
||
|
var items = this.main_menu.rebuildMenu();
|
||
|
this.menu_model = items;
|
||
|
},
|
||
|
onMenuClick( event ) {
|
||
|
this.$emit( 'menu-click', event );
|
||
|
},
|
||
|
onMenuItemClick( event ) {
|
||
|
this.$emit( 'menuitem-click', event );
|
||
|
//A slight delay is required before calculating if the scroll bar should be shown as the dropdown does not expand instantly.
|
||
|
setTimeout( () => {
|
||
|
if ( ( this.layoutMode === 'static' || this.layoutMode === 'overlay' ) && Global.isVerticalScrollBarRequired( this.$refs['layout-menu-content'] ) ) {
|
||
|
this.toggleScrollBar( true );
|
||
|
} else {
|
||
|
this.toggleScrollBar( false );
|
||
|
}
|
||
|
}, 250 );
|
||
|
if ( this.layoutMode === 'slim' && !event.item.parent_id ) {
|
||
|
this.repositionSubMenu( event.item );
|
||
|
}
|
||
|
},
|
||
|
onRootMenuItemClick( event ) {
|
||
|
this.$emit( 'root-menuitem-click', event );
|
||
|
},
|
||
|
onTopbarItemClick( event ) {
|
||
|
this.$emit( 'topbar-item-click', event );
|
||
|
},
|
||
|
onMenuContentMouseOver() {
|
||
|
if ( Global.isVerticalScrollBarRequired( this.$refs['layout-menu-content'] ) ) {
|
||
|
this.toggleScrollBar( true );
|
||
|
} else {
|
||
|
this.toggleScrollBar( false );
|
||
|
}
|
||
|
},
|
||
|
onMenuContentMouseLeave() {
|
||
|
this.toggleScrollBar( false );
|
||
|
},
|
||
|
toggleScrollBar( show ) {
|
||
|
if ( show == true || Global.UNIT_TEST_MODE == true ) {
|
||
|
this.$refs['layout-menu-content'].style.maskPosition = 'left top';
|
||
|
this.$refs['layout-menu-content'].style.webkitMaskPosition = 'left top';
|
||
|
} else {
|
||
|
this.$refs['layout-menu-content'].style.maskPosition = 'left bottom';
|
||
|
this.$refs['layout-menu-content'].style.webkitMaskPosition = 'left bottom';
|
||
|
}
|
||
|
},
|
||
|
repositionSubMenu( menu_item ) {
|
||
|
let menu = document.querySelector( '.layout-main-menu' );
|
||
|
let submenu = null;
|
||
|
|
||
|
//Find the dom sub menu to check if we need to reposition it.
|
||
|
for ( let i = 0; i < menu.childNodes.length; i++ ) {
|
||
|
if ( menu.children[i] && menu.children[i].children[1] ) {
|
||
|
submenu = menu.children[i].children[1];
|
||
|
submenu.style.top = '';
|
||
|
submenu.style.overflowY = '';
|
||
|
if ( menu.children[i].children[0].children[1].textContent === menu_item.label ) {
|
||
|
//Sub menu needs to be visible to calculate dimensions and position.
|
||
|
submenu.style.display = 'block';
|
||
|
//Check if sub menu goes below the bottom of screen.
|
||
|
if ( submenu.getBoundingClientRect().bottom > window.innerHeight ) {
|
||
|
//Raise the sub menu up by the amount it is hidden. And an adjustment of 10px to make the menu mot flush with the bottom of the screen.
|
||
|
submenu.style.top = '-' + ( submenu.getBoundingClientRect().bottom - ( window.innerHeight - 10 ) ) + 'px';
|
||
|
}
|
||
|
if ( menu_item.items.some( item => item.items ) ) {
|
||
|
//If the menu has sub menus then we need to show the scroll bar and set a max height
|
||
|
//so that further submenu expansion do not go off screen. (Report menu has multiple sections)
|
||
|
submenu.style.maxHeight = submenu.offsetHeight + 'px';
|
||
|
submenu.style.overflowY = 'scroll'; //Scroll instead of auto so that contents do not shift when expanding.
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
resetSlimSubmenuOffsets() {
|
||
|
let menu = document.querySelector( '.layout-main-menu' );
|
||
|
let submenu = null;
|
||
|
|
||
|
//In slim mode we offset submenus to prevent the menu from going off screen.
|
||
|
//This needs to be reset when switching from slim so that the changes do not carry over to other layout modes.
|
||
|
for ( let i = 0; i < menu.childNodes.length; i++ ) {
|
||
|
if ( menu.children[i] && menu.children[i].children[1] ) {
|
||
|
submenu = menu.children[i].children[1];
|
||
|
submenu.style.top = '';
|
||
|
submenu.style.maxHeight = '';
|
||
|
submenu.style.overflowY = '';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
components: {
|
||
|
TTAppMenu,
|
||
|
TTMenuSearch,
|
||
|
ProgressBar
|
||
|
}
|
||
|
};
|
||
|
</script>
|
||
|
<style>
|
||
|
/* Warning: These styles get applied globally. They are not scoped. */
|
||
|
|
||
|
/* TODO: Future refactor: Worth moving a lot of these menu styles over to the actual components/main_menu/TTAppMenu.vue file as some are more directly relevant there to the menu rather than the left container. */
|
||
|
|
||
|
/* Overrides for left menu up/down in/out transitions */
|
||
|
.layout-wrapper .layout-menu-container .layout-menu li ul.layout-submenu-container-leave-active {
|
||
|
transition-duration: 0s; /* Overrides Apollo default: 0.45s */
|
||
|
}
|
||
|
|
||
|
.layout-wrapper .layout-menu-container .layout-menu li ul.layout-submenu-container-enter-active {
|
||
|
/*transition: max-height 1s ease-in-out;*/
|
||
|
transition-duration: 0.2s; /* Overrides Apollo default: 1s */
|
||
|
}
|
||
|
|
||
|
.layout-wrapper .layout-menu-container.layout-menu-container-enter-to {
|
||
|
transition-duration: 0.2s!important; /* Overrides Apollo default: 1s */
|
||
|
}
|
||
|
|
||
|
/* Hide left main menu scrollbar with a mask until user mouseovers the menu and the menu is large enough to warrant a scrollbar. Mouseover and size check done in JavaScript */
|
||
|
.layout-static .layout-menu-content, .layout-overlay .layout-menu-content {
|
||
|
overflow-y: scroll;
|
||
|
mask-image: linear-gradient(0deg, transparent, #000000), linear-gradient(270deg, transparent 17px, #000000 0);
|
||
|
mask-size: 100% 20000px;
|
||
|
mask-position: left bottom;
|
||
|
-webkit-mask-image: linear-gradient(0deg, transparent, #000000), linear-gradient(270deg, transparent 17px, #000000 0);
|
||
|
-webkit-mask-size: 100% 20000px;
|
||
|
-webkit-mask-position: left bottom;
|
||
|
transition: mask-position 0.3s, -webkit-mask-position 0.3s;
|
||
|
}
|
||
|
|
||
|
/*.layout-wrapper.layout-overlay .layout-menu-container,
|
||
|
.layout-wrapper.layout-static .layout-menu-container {
|
||
|
overflow-y: scroll; !* Force permanent vertical scrollbar space so that the content like menu arrows dont flash/move around when toggling between open/closed menu groups *!
|
||
|
}*/
|
||
|
|
||
|
/* --- Animation and style settings for menu mode toggle from static to slim and back --- */
|
||
|
|
||
|
/* Do not animate width change from slim to static, no matter now fast, text still gets squashed/wrapped. */
|
||
|
/*.layout-wrapper.layout-static .layout-menu-container {*/
|
||
|
/* transition: width 0.01s; !* From static to slim, do it fast, so the text is not squashed/wrapped during animation. *!*/
|
||
|
/*}*/
|
||
|
|
||
|
.layout-wrapper.layout-slim .layout-menu-container {
|
||
|
transition: width 0.2s; /* From static to slim, can be slow, but not too slow, because the text dissappears before animation finishes. */
|
||
|
}
|
||
|
|
||
|
.layout-wrapper.layout-slim .layout-menu-container .layout-menu>li>a {
|
||
|
text-align: left; /* Without this, text jumps left/right during menu state change width animation */
|
||
|
padding-left: 12px; /* With the text align left above, this ensures icon is still centered in slim mode during and after animation. Trial & error value. */
|
||
|
}
|
||
|
.layout-wrapper .layout-menu-container .layout-menu li>a i {
|
||
|
line-height: 22px; /* Sets icon line height equal to .layout-menuitem-toggler so that the icons dont move up/down during transition to/from slim/static mode */
|
||
|
}
|
||
|
|
||
|
.layout-wrapper.layout-slim .layout-menu-container {
|
||
|
width: 42px; /* Sets width of slim menu via a trial & error value that ensures icon stays in same place between slim/static mode toggle. */
|
||
|
}
|
||
|
|
||
|
.layout-wrapper.layout-slim .layout-menu-container .layout-menu>li>a i:first-child {
|
||
|
font-size: 14px; /* Matches font size for static mode, so icon size stays consistent during mode toggle */
|
||
|
}
|
||
|
|
||
|
.layout-wrapper.layout-overlay .layout-menu-container .layout-menu-title, .layout-wrapper.layout-static .layout-menu-container .layout-menu-title {
|
||
|
padding: 8px; /* To bring the menu header border-bottom line more inline with the bottom of the context menu */
|
||
|
}
|
||
|
|
||
|
.layout-wrapper.layout-static .layout-menu-container .layout-menu-footer {
|
||
|
padding-bottom: 0;
|
||
|
}
|
||
|
.feedback-link-container {
|
||
|
margin-top: 10px;
|
||
|
}
|
||
|
.layout-menu-footer-content {
|
||
|
padding: 5px 8px;
|
||
|
}
|
||
|
.layout-menu-footer-content,
|
||
|
.layout-wrapper.layout-overlay .layout-menu-container .layout-menu-footer .layout-menu-footer-content,
|
||
|
.layout-wrapper.layout-static .layout-menu-container .layout-menu-footer .layout-menu-footer-content {
|
||
|
/* Override the base primevue styles */
|
||
|
border-top: 1px solid #dee2e6;
|
||
|
text-align: center;
|
||
|
}
|
||
|
.signal-copyright-container {
|
||
|
margin-top: 5px;
|
||
|
display: flex;
|
||
|
align-items: center;
|
||
|
justify-content: center;
|
||
|
}
|
||
|
.copy-right-info {
|
||
|
margin-top: 2px;
|
||
|
font-size: 10px;
|
||
|
padding-top: 5px;
|
||
|
}
|
||
|
.layout-wrapper .layout-menu-container .layout-menu-content .layout-menu li>a {
|
||
|
font-size: 13px;
|
||
|
color: #3b3b3b;
|
||
|
}
|
||
|
|
||
|
.layout-static .layout-main-menu > li.active-menuitem {
|
||
|
border: 1px solid #dbdee1;
|
||
|
border-radius: 2px;
|
||
|
}
|
||
|
|
||
|
.layout-static .layout-main-menu > li {
|
||
|
margin-left: 5px;
|
||
|
margin-right: 5px;
|
||
|
border: 1px solid transparent;
|
||
|
}
|
||
|
|
||
|
#tt_main_ui .layout-wrapper.layout-static .layout-menu-container .layout-menu li.p-menu-separator {
|
||
|
margin-left: 5px;
|
||
|
margin-right: 5px;
|
||
|
}
|
||
|
|
||
|
.layout-wrapper.layout-horizontal .layout-menu-container .layout-menu > li > a {
|
||
|
padding: 9px; /* Centers the vertical alignment of the horizontal menu items */
|
||
|
}
|
||
|
|
||
|
.layout-menu .active-menuitem>ul {
|
||
|
z-index: 10; /* #3065 Ensures the active menu items are shown properly and not transparently mixing in with the main menu items behind it, making the active menu items unreadable. Especially in horizontal menu mode. */
|
||
|
}
|
||
|
|
||
|
.layout-wrapper.layout-static .layout-menu-container .layout-menu-content {
|
||
|
height: calc(100% - 37px); /*Adjustment required to take into account the search bar height*/
|
||
|
}
|
||
|
|
||
|
</style>
|