<template lang="pug">
.layout-wrapper.layout
.layout(
:class="{ menu_collapsed: isMenuCollapsed, with_sidebar: isSidebarVisible }" ref="layout")
.minimum_width
b-alert(variant="warning", show, v-text="$t('error.responsive_minimum_width')")
//.hamburger__wrapper.fixed(
:class="{ extended: isSidebarExtended }",
v-show="isSidebarVisible"
)
em.fas.fa-bars.text-center.hamburger(
@click="toggleSidebarExtended(!isSidebarExtended)"
)
.sidebar(
ref="sidebar",
:class="{ extended: isSidebarExtended }",
v-show="isSidebarVisible"
)
.sidebar__overlay(@click="toggleSidebarExtended(false)")
.sidebar__inner
.hamburger__wrapper(
:class="{ extended: isSidebarExtended }",
v-show="isSidebarVisible"
@click="toggleSidebarExtended(!isSidebarExtended)"
)
SimplicitiLogo.text-center.hamburger()
slot(name="sidebar")
component(:is="getLayoutComponent(['sidebar', 'sidebarComponent'])")
.menu(
ref="menu",
v-show="isMenuVisible",
:class="{ full_collapse: willMenuCollapseCompletely }"
)
.menu_inner
slot(name="menu")
component(:is="getLayoutComponent(['menu', 'menuComponent'])")
.main(:style="mainStyle()", ref="main")
.toggle_menu(v-if="isMenuVisible&&isMenuToggleVisible")
button(@click="toggleMenuCollapsed(!isMenuCollapsed)")
em.fas.fa-chevron-right(v-show="isMenuCollapsed")
em.fas.fa-chevron-left(v-show="!isMenuCollapsed")
.floating_layout(
:class="{ has_inner: isRightMenuVisible || isWideMenuVisible }",
v-show="isSubMenuVisible||isRightMenuVisible||isWideMenuVisible"
)
.fl__inner(
v-show="isSubMenuVisible||isRightMenuVisible||isWideMenuVisible"
)
.sub_menu(v-if="isSubMenuVisible")
.sub_menu__header
button.close_btn(@click="toggleSubMenu(false)")
em.fas.fa-times
.sub_menu__content
slot(name="sub_menu")
component(
:is="getLayoutComponent(['sub_menu', 'subMenu', 'subMenuComponent'])"
)
.wide_menu_overlay(v-if="isWideMenuVisible" :style="mainStyle()")
.wide_menu
button.close_btn(@click="toggleWideMenu(false)")
em.fas.fa-times
.wide_menu__inner
slot(name="wide_menu")
component(
:is="getLayoutComponent(['wide_menu', 'wideMenuComponent'])"
)
.fl__right(v-if="isRightMenuVisible")
.fl__right__header(v-if="isRightMenuCloseButtonVisible")
button.close_btn(@click="toggleRightMenu(false)")
em.fas.fa-times
.fl__right__inner(
:class="{ has_bottom: isRightMenuBottomVisible }"
)
.top.right_menu__top
slot(name="right_menu")
component.right_menu__top__component(
:is="getLayoutComponent(['right_menu', 'right_menu_top', 'rightMenuTop', 'rightMenuTopComponent'])"
)
.bottom.right_menu__bottom(v-if="isRightMenuBottomVisible")
slot(name="right_menu_bottom")
component(
:is="getLayoutComponent(['right_menu_bottom', 'rightMenuBottom', 'rightMenuBottomComponent'])"
)
slot(name="main")
//Loading text
</template>
<script>
import Vue from 'vue'
import $ from 'jquery'
import { mapGetters } from 'vuex'
import SimplicitiLogo from '@/components/shared/SimplicitiLogo/SimplicitiLogo'
Vue.use({
install() {
Vue.prototype.$layout = Vue.$layout = {
components: {},
/**
* Clear a component from a layout section (sidebar, menu, etc)
*/
clearComponent(type) {
this.components[type] = null
this.vm && this.vm.$forceUpdate()
},
bind(vm) {
this.vm = vm
},
}
},
})
/**
* Used by MapToolbox to hide itself if Table+Carto (i.g Location module)
* (Module context pattern)
*/
export const layoutStaticContext = (() => {
const context = {
vm: null, //access to current layout vm
}
Object.defineProperty(context, 'isRightMenuVisible', {
enumerable: true,
get: () => context.vm.isRightMenuVisible,
})
Object.defineProperty(context, 'isRightMenuBottomVisible', {
enumerable: true,
get: () => context.vm.isRightMenuBottomVisible,
})
Object.defineProperty(context, '$data', {
enumerable: true,
get: () => context.vm.$data,
})
return context
})()
export default {
name: 'TLayout',
components: {
SimplicitiLogo,
},
provide() {
let self = this
return {
layoutData() {
return self.$data
},
layoutVM() {
return self
},
}
},
props: {
sidebar: {
type: Boolean,
default: false,
},
wideMenu: {
type: Boolean,
default: false,
},
menu: {
type: Boolean,
default: false,
},
menuCollapsed: {
type: Boolean,
default: false,
},
menuToggle: {
type: Boolean,
default: true,
},
menuFullCollapse: {
type: Boolean,
default: true,
},
rightMenu: {
type: Boolean,
default: false,
},
rightMenuBottom: {
type: Boolean,
default: false,
},
/** Dynamic layout, default */
syncWithVuex: {
type: Boolean,
default: false,
},
},
data() {
return {
/**
* Customize current layout name
* Used by V3 - Location Module - Table+Map mode -> To preserve the map if details windows is closed
*/
currentLayoutName: '',
mainOffsetLeft: 0,
windowWidth: 0,
isSidebarVisible: this.sidebar,
isSidebarExtended: false,
isMenuVisible: this.menu,
isMenuCollapsed: this.menuCollapsed,
isMenuToggleVisible: this.menuToggle,
willMenuCollapseCompletely: this.menuFullCollapse,
isWideMenuVisible: this.wideMenu,
isSubMenuVisible: false,
isRightMenuVisible: this.rightMenu,
isRightMenuBottomVisible: this.rightMenuBottom,
isRightMenuCloseButtonVisible: false,
//On Firefox, the main div needs to recalculate width manually
isFirefox: navigator.userAgent.toLowerCase().indexOf('firefox') > -1,
bodyOffsetWidth: document.body.offsetWidth,
layoutOffsetWidthExceptMainTotal: 0,
}
},
computed: {
...mapGetters({
layoutComponent: 'app/layoutComponent',
vuexIsSidebarExtended: 'app/isSidebarExtended',
}),
mainWidth() {
return this.windowWidth > 968
? `calc(100vw - ${this.mainOffsetLeft}px)`
: `calc(100vw)`
},
},
watch: {
menu() {
this.isMenuVisible = this.menu
},
menuCollapsed() {
this.isMenuCollapsed = this.menuCollapsed
},
menuToggle() {
this.isMenuToggleVisible = this.menuToggle
},
menuFullCollapse() {
this.willMenuCollapseCompletely = this.menuFullCollapse
},
rightMenu() {
this.isRightMenuVisible = this.rightMenu
},
rightMenuBottom() {
this.isRightMenuBottomVisible = this.rightMenuBottom
},
isSidebarVisible() {
this.recalculateMainOffsetLeft()
},
isMenuVisible() {
this.recalculateMainOffsetLeft()
},
isMenuCollapsed() {
this.recalculateMainOffsetLeft()
},
isSidebarExtended() {
this.recalculateMainOffsetLeft()
},
/**
* @todo Improve: This is a hotfix for: Expanded sidebar do not close itself after sidebar link click (alerts/identification)
*/
vuexIsSidebarExtended() {
this.isSidebarExtended = this.vuexIsSidebarExtended
},
},
created() {
layoutStaticContext.vm = this
//On Firefox, the main div needs to recalculate width manually
if (this.isFirefox) {
this.documentBodyOffsetWidthInterval = setInterval(() => {
this.bodyOffsetWidth = document.body.offsetWidth
if (this.$refs.layout) {
this.layoutOffsetWidthExceptMainTotal = Array.from(
this.$refs.layout.childNodes
).reduce(
(a, v) => (v.className.includes('main') ? a : a + v.offsetWidth),
0
)
}
}, 1000)
}
},
mounted() {
if (this.syncWithVuex) {
this.$layout.bind(this)
}
this.windowWidth = window.innerWidth
$(window).on(
'resize',
(this.onResize = () => {
this.windowWidth = window.innerWidth
this.recalculateMainOffsetLeft()
this.onChange(null, false)
})
)
this.$nextTick(() => this.recalculateMainOffsetLeft())
},
destroyed() {
$(window).off('resize', this.onResize)
if (this.isFirefox) {
clearInterval(this.documentBodyOffsetWidthInterval)
}
},
methods: {
mainStyle() {
let maxWidth = ''
//On Firefox, the main div needs to recalculate width manually
if (this.isFirefox && !!this.$refs.layout) {
maxWidth = this.bodyOffsetWidth - this.layoutOffsetWidthExceptMainTotal
}
if (maxWidth === 0) {
return ''
}
return `max-width:${maxWidth}px;`
},
getLayoutComponent(names) {
let n = names.find((n) => this.layoutComponent(n))
return n ? this.layoutComponent(n) : null
},
onChange(key, updateStore = true) {
this.recalculateMainOffsetLeft()
if (updateStore) {
this.$store.dispatch('app/updateLayout', {
...this.$data,
})
}
},
recalculateMainOffsetLeft() {
this.$nextTick(() => {
this.mainOffsetLeft = (this.$refs.main || {}).offsetLeft || 0
})
},
/**
*
* Called from app/index.js store
*
* */
configure(values = {}) {
if (!this.syncWithVuex) {
return
}
//this.$log.debug("configure", values);
let matchTable = {
sidebar: 'isSidebarVisible',
sidebar_extended: 'isSidebarExtended',
menu: 'toggleMenu',
menu_collapsed: 'toggleMenuCollapsed',
menu_full_collapse: 'willMenuCollapseCompletely',
right_menu: 'toggleRightMenu',
right_menu_bottom: 'toggleRightMenuBottom',
right_menu_close_btn: 'isRightMenuCloseButtonVisible',
sub_menu: 'toggleSubMenu',
wide_menu: 'toggleWideMenu',
menu_toggle: 'isMenuToggleVisible',
isMenuToggleVisible: 'isMenuToggleVisible',
isSidebarVisible: 'isSidebarVisible',
isSidebarExtended: 'isSidebarExtended',
isMenuVisible: 'toggleMenu',
isMenuCollapsed: 'toggleMenuCollapsed',
willMenuCollapseCompletely: 'willMenuCollapseCompletely',
isWideMenuVisible: 'toggleWideMenu',
isSubMenuVisible: 'toggleSubMenu',
isRightMenuVisible: 'toggleRightMenu',
isRightMenuBottomVisible: 'toggleRightMenuBottom',
}
Object.keys(values).forEach((key) => {
if (matchTable[key]) {
if (typeof this[matchTable[key]] === 'function') {
this[matchTable[key]](values[key], false)
} else {
this[matchTable[key]] = values[key]
this.onChange(key, false)
}
} else {
if (this.$data[key] !== undefined) {
this[key] = values[key]
}
}
})
//Update at end
this.$store.dispatch('app/updateLayout', {
...this.$data,
})
},
/**
* Internal
* */
toggleWideMenu(value = true, updateStore = true) {
this.isWideMenuVisible = value
this.$emit('toggleWideMenu')
this.onChange('wide_menu', updateStore)
},
/**
* Internal
* */
toggleSidebar(value = true, updateStore = true) {
this.isSidebarVisible = value
this.onChange('sidebar', updateStore)
},
/**
* Internal
* */
toggleSidebarExtended(value = true, updateStore = true) {
this.isSidebarExtended = value
this.onChange('sidebar_extended', updateStore)
this.$store.dispatch('sidebar/setIsExpanded', this.isSidebarExtended)
},
/**
* Internal
* */
toggleMenu(value = false, updateStore = true) {
this.isMenuVisible = value
this.onChange('menu', updateStore)
this.$emit('onMenuToggle', value)
},
/**
* Internal
* */
toggleMenuCollapsed(value = false, updateStore = true) {
this.isMenuCollapsed = value
this.onChange('menu_collapsed', updateStore)
if (this.willMenuCollapseCompletely) {
this.$emit('onMenuToggle', !value)
}
this.$emit('onMenuCollapsed', value)
},
/**
* Internal
* */
toggleSubMenu(value = true, updateStore = true) {
this.isSubMenuVisible = value
this.onChange('sub_menu', updateStore)
},
/**
* Internal
* */
toggleRightMenu(value = true, updateStore = true) {
this.isRightMenuVisible = value
this.onChange('right_menu', updateStore)
},
/**
* Internal
* */
toggleRightMenuBottom(value = true, updateStore = true) {
this.isRightMenuBottomVisible = value
this.onChange('right_menu_bottom', updateStore)
},
},
}
</script>
<style lang="scss" scoped>
.layout,
.layout > * {
box-sizing: border-box;
@media (max-width: 359px) {
display: none;
}
}
.layout .minimum_width {
display: none;
@media (max-width: 359px) {
position: fixed;
display: flex;
justify-content: center;
align-items: center;
min-width: calc(100vw);
align-self: center;
z-index: 10;
}
}
.layout {
display: flex;
height: calc(100vh);
width: 100%;
width: -moz-available; /* WebKit-based browsers will ignore this. */
width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
width: fill-available;
@media (max-width: 359px) {
align-items: center;
}
@media (max-width: 1400px) {
}
}
.sidebar {
min-width: 60px;
max-width: 60px;
z-index: 1000000;
}
.sidebar__inner {
padding-top: 0px;
box-shadow: 1px 0px 6px 0px #a9a9a991;
background-color: white;
height: calc(100vh);
overflow-y: auto;
scrollbar-color: white;
scrollbar-width: thin;
}
.sidebar__inner::-webkit-scrollbar {
width: 3px;
}
.sidebar__inner::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
}
.sidebar__inner::-webkit-scrollbar-thumb {
background-color: rgba(169, 169, 169, 0.267);
outline: 0.5px solid rgba(169, 169, 169, 0.267);
}
.sidebar.extended {
display: block;
position: fixed;
left: 0px;
top: 0px;
width: calc(100vw);
max-width: none;
z-index: 10000000;
& .sidebar__overlay {
display: block;
position: fixed;
left: 0px;
top: 0px;
width: calc(100vw);
height: calc(100vh);
background-color: rgba(47, 79, 79, 0.4);
z-index: -1;
}
& .sidebar__inner {
height: calc(100vh);
width: 300px;
}
}
.sidebar__hamburger {
padding: 10px;
}
.hamburger__wrapper.fixed {
position: fixed;
top: 0px;
left: 0px;
z-index: 10;
display: none;
}
.hamburger__wrapper {
background-color: #0a71ae;
height: 50px;
margin: 0 auto;
}
.hamburger__wrapper.extended {
box-shadow: none;
border-right: 0;
}
.hamburger {
cursor: pointer;
margin: 0 auto;
display: block;
font-size: 30px;
}
.main {
position: relative;
max-height: calc(100vh);
overflow-y: auto;
scrollbar-color: white;
scrollbar-width: thin;
width: calc(100%);
z-index: 1000000;
}
.main::-webkit-scrollbar {
width: 10px;
}
.main::-webkit-scrollbar-track {
background-color: white;
border-radius: 10px;
}
.main::-webkit-scrollbar-thumb {
background-color: var(--color-tundora);
height: 15px;
border-radius: 10px;
}
.menu {
flex-shrink: 0;
width: 468px;
max-width: 468px;
background-color: white;
box-shadow: 3px 4px 6px 0px #a9a9a991;
border-right: 1px solid #a9a9a991;
z-index: 2;
max-height: calc(100vh);
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: white;
@media (max-width: 968px) {
max-width: 350px;
}
@media (min-width: 969px) and (max-width: 1400px) {
max-width: 350px;
}
@media (min-width: 1401px) and (max-width: 1600px) {
max-width: 400px;
}
}
.menu::-webkit-scrollbar {
width: 5px;
}
.menu::-webkit-scrollbar-track {
background-color: white;
border-radius: 10px;
}
.menu::-webkit-scrollbar-thumb {
background-color: var(--color-tundora);
height: 15px;
border-radius: 10px;
}
.menu_collapsed .menu {
max-width: 110px !important;
}
.menu_collapsed .menu.full_collapse {
display: none;
}
.menu_inner {
position: relative;
}
.menu__hamburger {
display: none;
@media (max-width: 968px) {
display: flex;
margin: 0 auto;
}
}
.floating_layout {
position: absolute;
height: 100%;
z-index: 10000;
&.has_inner {
width: inherit;
}
}
.toggle_menu {
position: fixed;
top: 50%;
z-index: 100000;
}
.toggle_menu button {
height: 50px;
border: 0px;
border-radius: 0px 5px 5px 0px;
background-color: white;
border-right: 2px solid #00000069;
position: relative;
left: -1px;
}
.toggle_menu em {
color: var(--color-black);
}
.toggle_menu button:hover,
.toggle_menu button:focus,
.toggle_menu button:active {
outline: 0;
}
.sub_menu {
min-width: 400px;
max-width: 400px;
max-height: calc(100vh);
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: white;
background-color: var(--color-wild-sand);
position: relative;
@media (max-width: 968px) {
min-width: 350px;
max-width: 350px;
}
@media (min-width: 969px) and (max-width: 1400px) {
min-width: calc(25vw);
max-width: 350px;
}
@media (min-width: 1401px) and (max-width: 1600px) {
min-width: 350px;
max-width: 350px;
}
}
.sub_menu__header {
background-color: var(--color-dark-blue);
height: 30px;
right: 5px;
position: absolute;
top: 5px;
}
.sub_menu__header .close_btn {
background-color: transparent;
}
.sub_menu__header .close_btn em {
color: white;
}
.sub_menu__content {
margin: 0px;
}
.wide_menu_overlay {
background-color: rgba(255, 255, 255, 0.8);
position: absolute;
z-index: 10000;
height: inherit;
width: 100%;
}
.wide_menu {
height: 100%;
position: relative;
background-color: white;
width: 90%;
}
.wide_menu__inner {
height: 100%;
}
.fl__inner {
display: flex;
height: 100%;
}
.fl__right {
background-color: transparent;
height: 100%;
width: calc(100%);
}
.fl__right__header {
height: 26px;
margin-right: 20px;
position: absolute;
right: -10px;
z-index: 99999;
top: 0px;
}
.close_btn {
float: right;
border: 0px;
font-weight: 500;
font-size: 16px;
background-color: white;
border-radius: 5px;
}
.close_btn em {
color: var(--color-dark-blue);
}
.close_btn:hover,
.close_btn:active,
.close_btn:focus {
outline: 0;
}
.wide_menu .close_btn {
position: absolute;
z-index: 1;
right: 5px;
top: 5px;
}
.fl__right__inner {
display: grid;
grid-template-rows: 100%;
/*margin: 0px 10px 0px 22px;*/
height: 100%;
}
.fl__right__inner.has_bottom {
grid-template-rows: 1fr 400px;
}
.fl__right__inner .top {
/*border-radius: 5px;*/
background-color: white;
}
.fl__right__inner .bottom {
background-color: white;
max-width: 100%;
overflow-x: auto;
}
.right_menu__top {
overflow-x: auto;
scrollbar-color: white;
scrollbar-width: thin;
}
.right_menu__top::-webkit-scrollbar {
width: 10px;
height: 10px;
}
.right_menu__top::-webkit-scrollbar-track {
background-color: white;
border-radius: 10px;
}
.right_menu__top::-webkit-scrollbar-thumb {
background-color: var(--color-tundora);
height: 15px;
border-radius: 10px;
}
.right_menu__top__component {
height: calc(100%);
}
.right_menu__bottom,
.right_menu__top,
.sub_menu {
z-index: 1001;
}
.layout-simple {
width: 100%;
}
</style>
Source