replace the top menu component with a custom one
This commit is contained in:
parent
01de6d998f
commit
1a550f066f
314
css/sideMenu.css
314
css/sideMenu.css
|
@ -16,326 +16,334 @@
|
|||
*/
|
||||
|
||||
#side-menu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
background: linear-gradient(90deg, var(--side-menu-background-color, #333) 0%, var(--side-menu-background-color-to, #333) 100%);
|
||||
z-index: 3000;
|
||||
color: var(--side-menu-text-color, #fff);
|
||||
box-shadow: rgba(0, 0, 0, 0.22) 0px 25.6px 57.6px 0px, rgba(0, 0, 0, 0.18) 0px 4.8px 14.4px 0px;
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
background: linear-gradient(90deg, var(--side-menu-background-color, #333) 0%, var(--side-menu-background-color-to, #333) 100%);
|
||||
z-index: 3000;
|
||||
color: var(--side-menu-text-color, #fff);
|
||||
box-shadow: rgba(0, 0, 0, 0.22) 0px 25.6px 57.6px 0px, rgba(0, 0, 0, 0.18) 0px 4.8px 14.4px 0px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#side-menu a {
|
||||
transition: 0.2s;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
#side-menu.open {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#header .side-menu-opener {
|
||||
margin-left: 0px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.side-menu-settings {
|
||||
margin-right: 9px;
|
||||
margin-top: 2px;
|
||||
float: right;
|
||||
line-height: 34px;
|
||||
height: 28px;
|
||||
display: none;
|
||||
margin-right: 9px;
|
||||
margin-top: 2px;
|
||||
float: right;
|
||||
line-height: 34px;
|
||||
height: 28px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.side-menu-settings a {
|
||||
color: var(--side-menu-text-color, #fff);
|
||||
display: block;
|
||||
padding: 4px 7px;
|
||||
color: var(--side-menu-text-color, #fff);
|
||||
display: block;
|
||||
padding: 4px 7px;
|
||||
}
|
||||
|
||||
.side-menu-settings:hover a, .side-menu-settings a:active, .side-menu-settings a:focus {
|
||||
background: var(--side-menu-current-app-background-color, #444);
|
||||
background: var(--side-menu-current-app-background-color, #444);
|
||||
}
|
||||
|
||||
.side-menu-settings img {
|
||||
vertical-align: bottom;
|
||||
margin-left: 3px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
vertical-align: bottom;
|
||||
margin-left: 3px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
#side-menu.open .side-menu-settings {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.side-menu-opener {
|
||||
background: var(--side-menu-opener, url('../img/side-menu-opener.svg'));
|
||||
background-color: transparent !important;
|
||||
height: 40px !important;
|
||||
width: 40px !important;
|
||||
border-radius: 0 !important;
|
||||
border: 0 !important;
|
||||
padding-right: 12px !important;
|
||||
padding-left: 12px !important;
|
||||
margin-left: 5px !important;
|
||||
margin-left: 3px !important;
|
||||
background: var(--side-menu-opener, url('../img/side-menu-opener.svg'));
|
||||
background-color: transparent !important;
|
||||
height: 40px !important;
|
||||
width: 40px !important;
|
||||
border-radius: 0 !important;
|
||||
border: 0 !important;
|
||||
padding-right: 12px !important;
|
||||
padding-left: 12px !important;
|
||||
margin-left: 5px !important;
|
||||
margin-left: 3px !important;
|
||||
}
|
||||
|
||||
.side-menu-opener:active, .side-menu-opener:focus {
|
||||
background-color: var(--side-menu-current-app-background-color, #444) !important;
|
||||
background-color: var(--side-menu-current-app-background-color, #444) !important;
|
||||
}
|
||||
|
||||
.side-menu-closer {
|
||||
background: url('../img/side-menu-opener-closer.svg');
|
||||
display: none;
|
||||
background: url('../img/side-menu-opener-closer.svg');
|
||||
display: none;
|
||||
}
|
||||
|
||||
#side-menu.hide-opener .side-menu-opener, .side-menu-opener.hide, #side-menu.hide {
|
||||
display: none !important;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.side-menu-apps-list {
|
||||
height: calc(100vh - 150px);
|
||||
z-index: 2200;
|
||||
position: fixed;
|
||||
top: 150px;
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
overflow: auto;
|
||||
height: calc(100vh - 150px);
|
||||
z-index: 2200;
|
||||
position: fixed;
|
||||
top: 150px;
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.side-menu-app-icon {
|
||||
width: 20px;
|
||||
vertical-align: top;
|
||||
margin-right: 10px;
|
||||
filter: invert(var(--side-menu-icon-invert-filter, 0%));
|
||||
opacity: var(--side-menu-icon-opacity, 1);
|
||||
width: 20px;
|
||||
vertical-align: top;
|
||||
margin-right: 10px;
|
||||
filter: invert(var(--side-menu-icon-invert-filter, 0%));
|
||||
opacity: var(--side-menu-icon-opacity, 1);
|
||||
}
|
||||
|
||||
.side-menu-app-icon svg {
|
||||
vertical-align: middle;
|
||||
margin-top: -3px;
|
||||
vertical-align: middle;
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
.side-menu-app-icon .app-icon-notification {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.side-menu-app a {
|
||||
line-height: 30px;
|
||||
color: var(--side-menu-text-color, #fff);
|
||||
display: block;
|
||||
padding: 7px 0 5px 15px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 30px;
|
||||
color: var(--side-menu-text-color, #fff);
|
||||
display: block;
|
||||
padding: 7px 0 5px 15px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.side-menu-app a:hover, .side-menu-app.active, .side-menu-app a:focus {
|
||||
background: var(--side-menu-current-app-background-color, #444);
|
||||
background: var(--side-menu-current-app-background-color, #444);
|
||||
}
|
||||
|
||||
.side-menu-logo {
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.side-menu-logo img {
|
||||
max-width: 60%;
|
||||
max-height: 100px;
|
||||
max-width: 60%;
|
||||
max-height: 100px;
|
||||
}
|
||||
|
||||
.side-menu-header {
|
||||
height: 150px;
|
||||
width: 100%;
|
||||
z-index: 2300;
|
||||
max-width: 250px;
|
||||
position: fixed;
|
||||
padding-top: 2px;
|
||||
top: 0;
|
||||
height: 150px;
|
||||
width: 100%;
|
||||
z-index: 2300;
|
||||
max-width: 250px;
|
||||
position: fixed;
|
||||
padding-top: 2px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
#side-menu.side-menu-with-categories .side-menu-header {
|
||||
max-width: 295px;
|
||||
max-width: 295px;
|
||||
}
|
||||
|
||||
#side-menu.hide-opener .side-menu-logo {
|
||||
margin-top: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#side-menu-loader {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 3001;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 3001;
|
||||
}
|
||||
|
||||
#side-menu-loader-bar {
|
||||
height: 4px;
|
||||
background: var(--side-menu-loader-color, #0e75ac);
|
||||
width: 0;
|
||||
transition-property: width;
|
||||
height: 4px;
|
||||
background: var(--side-menu-loader-color, #0e75ac);
|
||||
width: 0;
|
||||
transition-property: width;
|
||||
}
|
||||
|
||||
#side-menu.side-menu-big, #side-menu.side-menu-with-categories {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.side-menu-big .side-menu-header, .side-menu-with-categories .side-menu-header {
|
||||
height: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.side-menu-big .side-menu-apps-list, .side-menu-with-categories .side-menu-apps-list {
|
||||
height: auto;
|
||||
position: static;
|
||||
max-width: 100vw;
|
||||
overflow: auto;
|
||||
height: auto;
|
||||
position: static;
|
||||
max-width: 100vw;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.side-menu-big .side-menu-app a, .side-menu-with-categories .side-menu-app a {
|
||||
padding: 7px 0 7px 7px;
|
||||
padding: 7px 0 7px 7px;
|
||||
}
|
||||
|
||||
.side-menu-categories-wrapper {
|
||||
padding-bottom: 70px;
|
||||
padding-bottom: 70px;
|
||||
}
|
||||
|
||||
.side-menu-categories {
|
||||
max-height: calc(100vh - 50px);
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
top: 50px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
padding: 0 10% 0 10%;
|
||||
max-height: calc(100vh - 50px);
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
top: 50px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
padding: 0 10% 0 10%;
|
||||
}
|
||||
|
||||
.side-menu-category {
|
||||
padding: 10px 20px;
|
||||
flex: 1 1 auto;
|
||||
padding: 10px 20px;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.side-menu-category-title {
|
||||
padding-left: 10px;
|
||||
color: var(--side-menu-text-color, #fff);
|
||||
padding-left: 10px;
|
||||
color: var(--side-menu-text-color, #fff);
|
||||
}
|
||||
|
||||
.side-menu-loader {
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.side-menu-loader svg {
|
||||
width: 38px;
|
||||
margin: auto;
|
||||
stroke: var(--side-menu-text-color, #fff);
|
||||
width: 38px;
|
||||
margin: auto;
|
||||
stroke: var(--side-menu-text-color, #fff);
|
||||
}
|
||||
|
||||
.side-menu-with-categories .side-menu-app-icon, .side-menu-big .side-menu-app-icon {
|
||||
vertical-align: middle;
|
||||
margin-top: -2px;
|
||||
vertical-align: middle;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.side-menu-always-displayed #header,
|
||||
.side-menu-always-displayed body {
|
||||
width: calc(100% - 50px) !important;
|
||||
width: calc(100% - 50px) !important;
|
||||
}
|
||||
|
||||
.side-menu-always-displayed body {
|
||||
position: absolute;
|
||||
left: 50px;
|
||||
position: absolute;
|
||||
left: 50px;
|
||||
}
|
||||
|
||||
.side-menu-always-displayed #side-menu {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.side-menu-always-displayed .side-menu-apps-list {
|
||||
height: calc(100vh - 49px);
|
||||
top: 49px;
|
||||
overflow: hidden;
|
||||
height: calc(100vh - 49px);
|
||||
top: 49px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.side-menu-always-displayed .side-menu-apps-list:hover {
|
||||
overflow: auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.side-menu-always-displayed #side-menu,
|
||||
.side-menu-always-displayed .side-menu-header,
|
||||
.side-menu-always-displayed .side-menu-apps-list {
|
||||
width: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.side-menu-always-displayed #side-menu .side-menu-app-text,
|
||||
.side-menu-always-displayed #header .side-menu-opener,
|
||||
.side-menu-always-displayed .side-menu-logo {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.side-menu-always-displayed #side-menu .side-menu-header {
|
||||
height: 49px;
|
||||
height: 49px;
|
||||
}
|
||||
|
||||
.side-menu-always-displayed #side-menu.open,
|
||||
.side-menu-always-displayed #side-menu.open .side-menu-apps-list,
|
||||
.side-menu-always-displayed #side-menu.open .side-menu-header {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.side-menu-always-displayed #side-menu.open .side-menu-app-text {
|
||||
display: inline;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.side-menu-always-displayed .app-navigation--close {
|
||||
transform: translateX(calc(-100% + 50px)) !important;
|
||||
transform: translateX(calc(-100% + 50px)) !important;
|
||||
}
|
||||
|
||||
#side-menu.side-menu-with-categories {
|
||||
max-width: 290px;
|
||||
height: 100vh;
|
||||
max-width: 290px;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.side-menu-with-categories .side-menu-categories {
|
||||
display: block;
|
||||
padding: 0;
|
||||
display: block;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.side-menu-with-categories .side-menu-category {
|
||||
padding: 10px 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.side-menu-always-displayed #body-settings, #body-settings.body-settings-side-menu {
|
||||
overflow-x: visible;
|
||||
overflow-x: visible;
|
||||
}
|
||||
|
||||
.app-menu {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.app-menu.show {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
#side-menu.side-menu-big {
|
||||
max-width: 290px;
|
||||
height: 100vh;
|
||||
}
|
||||
#side-menu.side-menu-big {
|
||||
max-width: 290px;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.side-menu-categories {
|
||||
display: block;
|
||||
padding: 0;
|
||||
}
|
||||
.side-menu-categories {
|
||||
display: block;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.side-menu-category {
|
||||
padding: 10px 0;
|
||||
}
|
||||
.side-menu-category {
|
||||
padding: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
.side-menu-closer {
|
||||
display: block;
|
||||
float: right;
|
||||
margin-right: 9px;
|
||||
}
|
||||
.side-menu-closer {
|
||||
display: block;
|
||||
float: right;
|
||||
margin-right: 9px;
|
||||
}
|
||||
|
||||
.side-menu-big .side-menu-header {
|
||||
max-width: 100%;
|
||||
}
|
||||
.side-menu-big .side-menu-header {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,6 @@
|
|||
"stylelint:fix": "stylelint src --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextcloud/axios": "^1.8.0",
|
||||
"@nextcloud/vue": "^1.5.0",
|
||||
"axios": "^0.24.0",
|
||||
"trim": "^1.0.1",
|
||||
"vue": "^2.6.11"
|
||||
|
@ -24,6 +22,10 @@
|
|||
"node": ">=16.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nextcloud/axios": "^1.8.0",
|
||||
"@nextcloud/initial-state": "^2.0.0",
|
||||
"@nextcloud/l10n": "^1.6.0",
|
||||
"@nextcloud/vue": "^1.5.0",
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/preset-env": "^7.9.0",
|
||||
|
|
297
src/AppMenu.vue
Normal file
297
src/AppMenu.vue
Normal file
|
@ -0,0 +1,297 @@
|
|||
|
||||
<!--
|
||||
- @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @author Julius Härtl <jus@bitgrid.net>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<nav class="app-menu show">
|
||||
<ul class="app-menu-main" v-if="apps">
|
||||
<li v-for="app in mainAppList()"
|
||||
:key="app.id"
|
||||
:data-app-id="app.id"
|
||||
class="app-menu-entry"
|
||||
:class="{ 'app-menu-entry__active': app.active }">
|
||||
<a :href="app.href"
|
||||
:class="{ 'has-unread': app.unread > 0 }"
|
||||
:aria-label="appLabel(app)"
|
||||
:aria-current="app.active ? 'page' : false">
|
||||
<img :src="app.icon" alt="">
|
||||
<div class="app-menu-entry--label">
|
||||
{{ app.name }}
|
||||
<span v-if="app.unread > 0" class="hidden-visually unread-counter">{{ app.unread }}</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<NcActions class="app-menu-more" :aria-label="t('core', 'More apps')" v-if="apps">
|
||||
<NcActionLink v-for="app in popoverAppList()"
|
||||
:key="app.id"
|
||||
:aria-label="appLabel(app)"
|
||||
:aria-current="app.active ? 'page' : false"
|
||||
:href="app.href"
|
||||
class="app-menu-popover-entry">
|
||||
<template #icon>
|
||||
<div class="app-icon" :class="{ 'has-unread': app.unread > 0 }">
|
||||
<img :src="app.icon" alt="">
|
||||
</div>
|
||||
</template>
|
||||
{{ app.name }}
|
||||
<span v-if="app.unread > 0" class="hidden-visually unread-counter">{{ app.unread }}</span>
|
||||
</NcActionLink>
|
||||
</NcActions>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { NcActions, NcActionLink } from '@nextcloud/vue'
|
||||
|
||||
export default {
|
||||
name: 'AppMenu',
|
||||
components: {
|
||||
NcActions, NcActionLink,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
apps: null,
|
||||
appLimit: 0,
|
||||
observer: null,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const ncApps = loadState('core', 'apps', {})
|
||||
const that = this
|
||||
that.apps = {}
|
||||
|
||||
Array.from(window.topMenuApps).forEach((id) => {
|
||||
if (ncApps.hasOwnProperty(id)) {
|
||||
that.apps[id] = ncApps[id]
|
||||
}
|
||||
})
|
||||
|
||||
this.observer = new ResizeObserver(this.resize)
|
||||
this.observer.observe(this.$el)
|
||||
this.resize()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.observer.disconnect()
|
||||
},
|
||||
methods: {
|
||||
appLabel() {
|
||||
return (app) => app.name
|
||||
+ (app.active ? ' (' + t('core', 'Currently open') + ')' : '')
|
||||
+ (app.unread > 0 ? ' (' + n('core', '{count} notification', '{count} notifications', app.unread, { count: app.unread }) + ')' : '')
|
||||
},
|
||||
appList() {
|
||||
return Object.values(this.apps)
|
||||
},
|
||||
mainAppList() {
|
||||
return this.appList().slice(0, this.appLimit)
|
||||
},
|
||||
popoverAppList() {
|
||||
return this.appList().slice(this.appLimit)
|
||||
},
|
||||
setNavigationCounter(id, counter) {
|
||||
this.$set(this.apps[id], 'unread', counter)
|
||||
},
|
||||
resize() {
|
||||
const availableWidth = this.$el.offsetWidth
|
||||
let appCount = Math.floor(availableWidth / 50) - 1
|
||||
const popoverAppCount = this.appList.length - appCount
|
||||
if (popoverAppCount === 1) {
|
||||
appCount--
|
||||
}
|
||||
if (appCount < 1) {
|
||||
appCount = 0
|
||||
}
|
||||
this.appLimit = appCount
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$header-icon-size: 20px;
|
||||
|
||||
.app-menu {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-shrink: 1;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.app-menu-main {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.app-menu-entry {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
opacity: .7;
|
||||
|
||||
&.app-menu-entry__active {
|
||||
opacity: 1;
|
||||
|
||||
&::before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border-bottom-color: var(--color-main-background);
|
||||
transform: translateX(-50%);
|
||||
width: 12px;
|
||||
height: 5px;
|
||||
border-radius: 3px;
|
||||
background-color: var(--color-primary-text);
|
||||
left: 50%;
|
||||
bottom: 6px;
|
||||
display: block;
|
||||
transition: all 0.1s ease-in-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.app-menu-entry--label {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
width: calc(100% - 4px);
|
||||
height: calc(100% - 4px);
|
||||
margin: 2px;
|
||||
color: var(--color-primary-text);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
img {
|
||||
transition: margin 0.1s ease-in-out;
|
||||
width: $header-icon-size;
|
||||
height: $header-icon-size;
|
||||
padding: calc((100% - $header-icon-size) / 2);
|
||||
filter: var(--primary-invert-if-bright);
|
||||
}
|
||||
|
||||
.app-menu-entry--label {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
color: var(--color-primary-text);
|
||||
text-align: center;
|
||||
bottom: -5px;
|
||||
left: 50%;
|
||||
display: block;
|
||||
min-width: 100%;
|
||||
transform: translateX(-50%);
|
||||
transition: all 0.1s ease-in-out;
|
||||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
opacity: 1;
|
||||
.app-menu-entry--label {
|
||||
opacity: 1;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Show labels
|
||||
&:hover,
|
||||
&:focus-within,
|
||||
.app-menu-entry:hover,
|
||||
.app-menu-entry:focus {
|
||||
opacity: 1;
|
||||
|
||||
img {
|
||||
margin-top: -6px;
|
||||
}
|
||||
|
||||
.app-menu-entry--label {
|
||||
opacity: 1;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&::before, .app-menu-entry::before {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .app-menu-more .button-vue--vue-tertiary {
|
||||
color: var(--color-primary-text);
|
||||
opacity: .7;
|
||||
margin: 3px;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
opacity: 1;
|
||||
background-color: transparent !important;
|
||||
border-radius: var(--border-radius);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px var(--color-primary-text);
|
||||
}
|
||||
}
|
||||
|
||||
.app-menu-popover-entry {
|
||||
.app-icon {
|
||||
position: relative;
|
||||
height: 44px;
|
||||
|
||||
&.has-unread::after {
|
||||
background-color: var(--color-main-text);
|
||||
}
|
||||
|
||||
img {
|
||||
filter: var(--background-invert-if-bright);
|
||||
width: $header-icon-size;
|
||||
height: $header-icon-size;
|
||||
padding: calc((50px - $header-icon-size) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.has-unread::after {
|
||||
content: "";
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: var(--color-primary-text);
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.unread-counter {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
|
@ -16,35 +16,51 @@
|
|||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import AppMenu from './AppMenu.vue'
|
||||
import SideMenu from './SideMenu.vue'
|
||||
import SideMenuBig from './SideMenuBig.vue'
|
||||
import SideMenuWithCategories from './SideMenuWithCategories.vue'
|
||||
|
||||
Vue.prototype.OC = OC
|
||||
Vue.prototype.t = OC.L10N.translate
|
||||
|
||||
const mountSideMenuComponent = () => {
|
||||
const sideMenuContainer = document.querySelector('#side-menu')
|
||||
const container = document.querySelector('#side-menu')
|
||||
|
||||
if (sideMenuContainer) {
|
||||
let component
|
||||
if (!container) {
|
||||
return window.setTimeout(mountSideMenuComponent, 50)
|
||||
}
|
||||
|
||||
if (sideMenuContainer.getAttribute('data-bigmenu')) {
|
||||
component = SideMenuBig
|
||||
} else if(sideMenuContainer.getAttribute('data-sidewithcategories')) {
|
||||
component = SideMenuWithCategories
|
||||
} else {
|
||||
component = SideMenu
|
||||
}
|
||||
|
||||
const View = Vue.extend(component)
|
||||
const sideMenu = new View({})
|
||||
|
||||
sideMenu.$mount('#side-menu')
|
||||
|
||||
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.ready'))
|
||||
const component = (() => {
|
||||
if (container.getAttribute('data-bigmenu')) {
|
||||
return SideMenuBig
|
||||
} else if(container.getAttribute('data-sidewithcategories')) {
|
||||
return SideMenuWithCategories
|
||||
} else {
|
||||
window.setTimeout(mountSideMenuComponent, 50)
|
||||
return SideMenu
|
||||
}
|
||||
})()
|
||||
|
||||
const View = Vue.extend(component)
|
||||
const App = new View({})
|
||||
|
||||
App.$mount('#side-menu')
|
||||
|
||||
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.ready'))
|
||||
}
|
||||
|
||||
const mountAppMenu = () => {
|
||||
const container = document.querySelector('#header .app-menu')
|
||||
|
||||
if (!container) {
|
||||
return window.setTimeout(mountAppMenu, 50)
|
||||
}
|
||||
|
||||
const View = Vue.extend(AppMenu)
|
||||
const App = new View({})
|
||||
|
||||
App.$mount('#header .app-menu')
|
||||
}
|
||||
|
||||
mountSideMenuComponent()
|
||||
mountAppMenu()
|
||||
|
|
|
@ -1,216 +0,0 @@
|
|||
let menuCache = null
|
||||
|
||||
const breakpointMobileWidth = 1024
|
||||
const usePercentualAppMenuLimit = 0.8
|
||||
const minAppsDesktop = 8
|
||||
|
||||
const handleMenuClick = (e, icon) => {
|
||||
let element = e.target
|
||||
|
||||
while (element.tagName !== 'LI') {
|
||||
element = element.parentNode
|
||||
}
|
||||
|
||||
const a = querySelector('a', element)
|
||||
|
||||
if (a.getAttribute('target') !== '_blank' && e.which === 1 && !e.ctrlKey && !e.metaKey) {
|
||||
for (let tag of ['svg', 'div']) {
|
||||
let el = querySelector(tag, element)
|
||||
|
||||
if (el) {
|
||||
el.remove()
|
||||
}
|
||||
}
|
||||
|
||||
const loader = createElement('div', {'class': icon})
|
||||
|
||||
a.insertBefore(loader, querySelector('span', a))
|
||||
}
|
||||
}
|
||||
|
||||
const updateTopMenu = function() {
|
||||
const isMobile = window.innerWidth < breakpointMobileWidth
|
||||
const menu = querySelector('#appmenu')
|
||||
const moreApps = querySelector('#more-apps')
|
||||
const navigation = querySelector('#navigation')
|
||||
const navigationApps = querySelector('#apps ul')
|
||||
|
||||
let apps = querySelectorAll('li', menu)
|
||||
let lastShownApp = null
|
||||
let appShown = []
|
||||
|
||||
if ((menu.innerHTML + menu.nextSibling.innerHTML) === menuCache) {
|
||||
return
|
||||
}
|
||||
|
||||
let navigationAppsHtml = ''
|
||||
|
||||
for (let app of apps) {
|
||||
const dataId = app.getAttribute('data-id')
|
||||
|
||||
if (dataId === null) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (topMenuApps.indexOf(dataId) === -1 && topSideMenuApps.indexOf(dataId) === -1) {
|
||||
app.classList.add('hidden')
|
||||
app.classList.add('app-hidden')
|
||||
} else {
|
||||
app.classList.remove('hidden')
|
||||
app.classList.add('app-external-site')
|
||||
|
||||
if (topSideMenuApps.indexOf(dataId) !== -1) {
|
||||
app.classList.add('app-top-side-menu')
|
||||
}
|
||||
|
||||
appShown.push(app)
|
||||
|
||||
navigationAppsHtml = navigationAppsHtml + app.outerHTML
|
||||
}
|
||||
|
||||
if (targetBlankApps.indexOf(dataId) !== -1) {
|
||||
querySelector('a', app).setAttribute('target', '_blank')
|
||||
}
|
||||
}
|
||||
|
||||
navigationApps.innerHTML = navigationAppsHtml
|
||||
|
||||
const rightHeaderWidth = querySelector('.header-right').offsetWidth
|
||||
const headerWidth = querySelector('header').offsetWidth
|
||||
|
||||
let availableWidth = headerWidth
|
||||
|
||||
availableWidth -= nextcloud.offsetWidth
|
||||
availableWidth -= querySelector('#header .side-menu-opener').offsetWidth
|
||||
availableWidth -= rightHeaderWidth > 230 ? rightHeaderWidth : 230
|
||||
availableWidth *= isMobile ? usePercentualAppMenuLimit : 1
|
||||
|
||||
let appCount = Math.floor(availableWidth / querySelector('#appmenu li:not(.hidden)').offsetWidth)
|
||||
|
||||
if (isMobile && appCount > minAppsDesktop) {
|
||||
appCount = minAppsDesktop
|
||||
} else if (!isMobile && appCount < minAppsDesktop) {
|
||||
appCount = minAppsDesktop
|
||||
}
|
||||
|
||||
menu.style.opacity = 1
|
||||
|
||||
if (appShown.length - 1 - appCount >= 1) {
|
||||
appCount--
|
||||
}
|
||||
|
||||
for (let item of querySelectorAll('a', moreApps)) {
|
||||
item.classList.remove('active')
|
||||
}
|
||||
|
||||
let k = 0
|
||||
let notInHeader = 0
|
||||
|
||||
for (let app of appShown) {
|
||||
const name = app.getAttribute('data-id')
|
||||
const li = querySelector('#apps li[data-id=' + name + '].app-external-site')
|
||||
|
||||
if (k < appCount && appCount > 0) {
|
||||
app.classList.remove('hidden')
|
||||
li.classList.add('in-header')
|
||||
|
||||
lastShownApp = app
|
||||
} else {
|
||||
app.classList.add('hidden')
|
||||
li.classList.remove('in-header')
|
||||
|
||||
notInHeader++
|
||||
|
||||
const a = querySelector('a', app)
|
||||
|
||||
if (appCount > 0 && a.classList.contains('active')) {
|
||||
lastShownApp.classList.add('hidden')
|
||||
app.classList.remove('hidden')
|
||||
notInHeader++
|
||||
|
||||
li.classList.add('in-header')
|
||||
}
|
||||
}
|
||||
|
||||
k++
|
||||
}
|
||||
|
||||
// Hack for:
|
||||
// - https://github.com/nextcloud/server/blob/master/core/src/components/MainMenu.js#L97-L119
|
||||
// - https://github.com/nextcloud/server/blob/master/core/src/components/MainMenu.js#L97-L119
|
||||
jQuery(menu).undelegate('li:not(#more-apps) > a', 'click')
|
||||
jQuery(navigation).undelegate('a', 'click')
|
||||
|
||||
const confs = [
|
||||
{
|
||||
items: querySelectorAll('#navigation li'),
|
||||
icon: 'icon-loading-small'
|
||||
},
|
||||
{
|
||||
items: querySelectorAll('li:not(#more-apps)', menu),
|
||||
icon: OCA.Theming && OCA.Theming.inverted ? 'icon-loading-small' : 'icon-loading-small-dark'
|
||||
},
|
||||
]
|
||||
|
||||
for (let conf of confs) {
|
||||
for (let item of conf.items) {
|
||||
item.addEventListener('click', (e) => {
|
||||
handleMenuClick(e, conf.icon)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (let app of querySelectorAll('#apps li.app-external-site')) {
|
||||
const appId = app.getAttribute('data-id')
|
||||
|
||||
if (app.classList.contains('in-header')) {
|
||||
for (let defs of querySelectorAll('svg defs', app)) {
|
||||
defs.remove()
|
||||
}
|
||||
} else {
|
||||
const svg = querySelector('svg', app)
|
||||
|
||||
if (querySelectorAll('svg defs', app).length > 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const defs = `
|
||||
<defs>
|
||||
<filter id="invertMenuMore-${appId}">
|
||||
<feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix>
|
||||
</filter>
|
||||
</defs>`
|
||||
|
||||
svg.innerHTML = defs + svg.innerHTML
|
||||
|
||||
for (let image of querySelectorAll('image', svg)) {
|
||||
image.setAttribute('filter', `url(#invertMenuMore-${appId})`)
|
||||
}
|
||||
|
||||
svg.innerHTML = svg.innerHTML.replace(/fecolormatrix/g, 'feColorMatrix')
|
||||
}
|
||||
}
|
||||
|
||||
if (notInHeader === 0) {
|
||||
moreApps.style.display = 'none'
|
||||
navigation.style.display = 'none'
|
||||
} else {
|
||||
moreApps.style.display = 'flex'
|
||||
}
|
||||
|
||||
menuCache = menu.innerHTML + menu.nextSibling.innerHTML
|
||||
}
|
||||
|
||||
for (let i = 0; i < 4000; i+= 100) {
|
||||
setTimeout(updateTopMenu, i)
|
||||
}
|
||||
|
||||
let resizeTimeout = null;
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
if (resizeTimeout !== null) {
|
||||
clearTimeout(resizeTimeout)
|
||||
}
|
||||
|
||||
resizeTimeout = setTimeout(updateTopMenu, 100)
|
||||
})
|
|
@ -203,12 +203,8 @@ if ($_['always-displayed']) {
|
|||
nextcloud.parentNode.insertBefore(sideMenuOpener, nextcloud.nextSibling)
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($_['top-menu-apps']) || !empty($_['top-side-menu-apps'])): ?>
|
||||
const topMenuApps = <?php echo json_encode($_['top-menu-apps']), "\n"; ?>
|
||||
const topSideMenuApps = <?php echo json_encode($_['top-side-menu-apps']); ?>
|
||||
|
||||
<?php require_once __DIR__.'/_topMenuApps.js'; ?>
|
||||
<?php endif; ?>
|
||||
window.topMenuApps = <?php echo json_encode($_['top-menu-apps']), "\n"; ?>
|
||||
window.topSideMenuApps = <?php echo json_encode($_['top-side-menu-apps']); ?>
|
||||
|
||||
<?php if ($display === 'always-displayed'): ?>
|
||||
<?php require_once __DIR__.'/_alwaysDisplayed.js'; ?>
|
||||
|
|
Loading…
Reference in a new issue