Refactor desktop navbar into a component, change layout to grid for

better compatibility with search field and simpler CSS
This commit is contained in:
Henry Jameson 2020-10-29 21:13:31 +02:00
parent 0f8a7037ea
commit 633349ddff
11 changed files with 468 additions and 408 deletions

View file

@ -1,7 +1,6 @@
import UserPanel from './components/user_panel/user_panel.vue' import UserPanel from './components/user_panel/user_panel.vue'
import NavPanel from './components/nav_panel/nav_panel.vue' import NavPanel from './components/nav_panel/nav_panel.vue'
import Notifications from './components/notifications/notifications.vue' import Notifications from './components/notifications/notifications.vue'
import SearchBar from './components/search_bar/search_bar.vue'
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue' import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
import FeaturesPanel from './components/features_panel/features_panel.vue' import FeaturesPanel from './components/features_panel/features_panel.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue' import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
@ -11,6 +10,7 @@ import MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue' import SideDrawer from './components/side_drawer/side_drawer.vue'
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue' import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
import MobileNav from './components/mobile_nav/mobile_nav.vue' import MobileNav from './components/mobile_nav/mobile_nav.vue'
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue' import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
import PostStatusModal from './components/post_status_modal/post_status_modal.vue' import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue' import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
@ -22,7 +22,6 @@ export default {
UserPanel, UserPanel,
NavPanel, NavPanel,
Notifications, Notifications,
SearchBar,
InstanceSpecificPanel, InstanceSpecificPanel,
FeaturesPanel, FeaturesPanel,
WhoToFollowPanel, WhoToFollowPanel,
@ -31,6 +30,7 @@ export default {
SideDrawer, SideDrawer,
MobilePostStatusButton, MobilePostStatusButton,
MobileNav, MobileNav,
DesktopNav,
SettingsModal, SettingsModal,
UserReportingModal, UserReportingModal,
PostStatusModal, PostStatusModal,
@ -38,14 +38,6 @@ export default {
}, },
data: () => ({ data: () => ({
mobileActivePanel: 'timeline', mobileActivePanel: 'timeline',
searchBarHidden: true,
supportsMask: window.CSS && window.CSS.supports && (
window.CSS.supports('mask-size', 'contain') ||
window.CSS.supports('-webkit-mask-size', 'contain') ||
window.CSS.supports('-moz-mask-size', 'contain') ||
window.CSS.supports('-ms-mask-size', 'contain') ||
window.CSS.supports('-o-mask-size', 'contain')
)
}), }),
created () { created () {
// Load the locale from the storage // Load the locale from the storage
@ -61,28 +53,6 @@ export default {
background () { background () {
return this.currentUser.background_image || this.$store.state.instance.background return this.currentUser.background_image || this.$store.state.instance.background
}, },
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
logoStyle () {
return {
'visibility': this.enableMask ? 'hidden' : 'visible'
}
},
logoMaskStyle () {
return this.enableMask ? {
'mask-image': `url(${this.$store.state.instance.logo})`
} : {
'background-color': this.enableMask ? '' : 'transparent'
}
},
logoBgStyle () {
return Object.assign({
'margin': `${this.$store.state.instance.logoMargin} 0`,
opacity: this.searchBarHidden ? 1 : 0
}, this.enableMask ? {} : {
'background-color': this.enableMask ? '' : 'transparent'
})
},
logo () { return this.$store.state.instance.logo },
bgStyle () { bgStyle () {
return { return {
'background-image': `url(${this.background})` 'background-image': `url(${this.background})`
@ -93,9 +63,7 @@ export default {
'--body-background-image': `url(${this.background})` '--body-background-image': `url(${this.background})`
} }
}, },
sitename () { return this.$store.state.instance.name },
chat () { return this.$store.state.chat.channel.state === 'joined' }, chat () { return this.$store.state.chat.channel.state === 'joined' },
hideSitename () { return this.$store.state.instance.hideSitename },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () { showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel && return this.$store.state.instance.showInstanceSpecificPanel &&
@ -112,19 +80,6 @@ export default {
} }
}, },
methods: { methods: {
scrollToTop () {
window.scrollTo(0, 0)
},
logout () {
this.$router.replace('/main/public')
this.$store.dispatch('logout')
},
onSearchBarToggled (hidden) {
this.searchBarHidden = hidden
},
openSettingsModal () {
this.$store.dispatch('openSettingsModal')
},
updateMobileState () { updateMobileState () {
const mobileLayout = windowWidth() <= 800 const mobileLayout = windowWidth() <= 800
const layoutHeight = windowHeight() const layoutHeight = windowHeight()

View file

@ -359,119 +359,10 @@ i[class*=icon-], .svg-inline--fa {
padding: 0 10px 0 10px; padding: 0 10px 0 10px;
} }
.item {
flex: 1;
line-height: 50px;
height: 50px;
overflow: hidden;
display: flex;
flex-wrap: wrap;
.nav-icon {
margin-left: 0.2em;
width: 2em;
text-align: center;
}
&.right {
justify-content: flex-end;
}
}
.auto-size { .auto-size {
flex: 1 flex: 1
} }
.nav-bar {
padding: 0;
width: 100%;
align-items: center;
position: fixed;
height: 50px;
box-sizing: border-box;
button {
&, i[class*=icon-], svg {
color: $fallback--text;
color: var(--btnTopBarText, $fallback--text);
}
&:active {
background-color: $fallback--fg;
background-color: var(--btnPressedTopBar, $fallback--fg);
color: $fallback--text;
color: var(--btnPressedTopBarText, $fallback--text);
}
&:disabled {
color: $fallback--text;
color: var(--btnDisabledTopBarText, $fallback--text);
}
&.toggled {
color: $fallback--text;
color: var(--btnToggledTopBarText, $fallback--text);
background-color: $fallback--fg;
background-color: var(--btnToggledTopBar, $fallback--fg)
}
}
.logo {
display: flex;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
align-items: stretch;
justify-content: center;
flex: 0 0 auto;
z-index: -1;
transition: opacity;
transition-timing-function: ease-out;
transition-duration: 100ms;
.mask {
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
background-color: $fallback--fg;
background-color: var(--topBarText, $fallback--fg);
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
img {
height: 100%;
object-fit: contain;
display: block;
flex: 0;
}
}
.inner-nav {
position: relative;
margin: auto;
box-sizing: border-box;
padding-left: 10px;
padding-right: 10px;
display: flex;
align-items: center;
flex-basis: 970px;
height: 50px;
a, a i, a svg {
color: $fallback--link;
color: var(--topBarLink, $fallback--link);
}
}
}
main-router { main-router {
flex: 1; flex: 1;
} }
@ -781,16 +672,6 @@ nav {
} }
} }
@media all and (min-width: 800px) {
.logo {
opacity: 1 !important;
}
}
.item.right {
text-align: right;
}
.visibility-notice { .visibility-notice {
padding: .5em; padding: .5em;
border: 1px solid $fallback--faint; border: 1px solid $fallback--faint;

View file

@ -9,80 +9,7 @@
:style="bgStyle" :style="bgStyle"
/> />
<MobileNav v-if="isMobileLayout" /> <MobileNav v-if="isMobileLayout" />
<nav <DesktopNav v-else />
v-else
id="nav"
class="nav-bar container"
@click="scrollToTop()"
>
<div class="inner-nav">
<div
class="logo"
:style="logoBgStyle"
>
<div
class="mask"
:style="logoMaskStyle"
/>
<img
:src="logo"
:style="logoStyle"
>
</div>
<div class="item">
<router-link
v-if="!hideSitename"
class="site-name"
:to="{ name: 'root' }"
active-class="home"
>
{{ sitename }}
</router-link>
</div>
<div class="item right">
<search-bar
v-if="currentUser || !privateMode"
class="mobile-hidden"
@toggled="onSearchBarToggled"
@click.stop.native
/>
<a
href="#"
class="mobile-hidden nav-icon"
@click.stop="openSettingsModal"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="cog"
:title="$t('nav.preferences')"
/>
</a>
<a
v-if="currentUser && currentUser.role === 'admin'"
href="/pleroma/admin/#/login-pleroma"
class="mobile-hidden nav-icon"
target="_blank"
><FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="tachometer-alt"
:title="$t('nav.administration')"
/></a>
<a
v-if="currentUser"
href="#"
class="mobile-hidden nav-icon"
@click.prevent="logout"
><FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="sign-out-alt"
:title="$t('login.logout')"
/></a>
</div>
</div>
</nav>
<div class="app-bg-wrapper app-container-wrapper" /> <div class="app-bg-wrapper app-container-wrapper" />
<div <div
id="content" id="content"

View file

@ -130,6 +130,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
? 0 ? 0
: config.logoMargin : config.logoMargin
}) })
copyInstanceOption('logoLeft')
store.commit('authFlow/setInitialStrategy', config.loginMethod) store.commit('authFlow/setInitialStrategy', config.loginMethod)
copyInstanceOption('redirectRootNoLogin') copyInstanceOption('redirectRootNoLogin')

View file

@ -0,0 +1,89 @@
import SearchBar from 'components/search_bar/search_bar.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faSignInAlt,
faSignOutAlt,
faHome,
faComments,
faBell,
faUserPlus,
faBullhorn,
faSearch,
faTachometerAlt,
faCog,
faInfoCircle
} from '@fortawesome/free-solid-svg-icons'
library.add(
faSignInAlt,
faSignOutAlt,
faHome,
faComments,
faBell,
faUserPlus,
faBullhorn,
faSearch,
faTachometerAlt,
faCog,
faInfoCircle
)
export default {
components: {
SearchBar
},
data: () => ({
searchBarHidden: true,
supportsMask: window.CSS && window.CSS.supports && (
window.CSS.supports('mask-size', 'contain') ||
window.CSS.supports('-webkit-mask-size', 'contain') ||
window.CSS.supports('-moz-mask-size', 'contain') ||
window.CSS.supports('-ms-mask-size', 'contain') ||
window.CSS.supports('-o-mask-size', 'contain')
)
}),
computed: {
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
logoStyle () {
return {
'visibility': this.enableMask ? 'hidden' : 'visible'
}
},
logoMaskStyle () {
return this.enableMask ? {
'mask-image': `url(${this.$store.state.instance.logo})`
} : {
'background-color': this.enableMask ? '' : 'transparent'
}
},
logoBgStyle () {
return Object.assign({
'margin': `${this.$store.state.instance.logoMargin} 0`,
opacity: this.searchBarHidden ? 1 : 0
}, this.enableMask ? {} : {
'background-color': this.enableMask ? '' : 'transparent'
})
},
logo () { return this.$store.state.instance.logo },
sitename () { return this.$store.state.instance.name },
hideSitename () { return this.$store.state.instance.hideSitename },
logoLeft () { return this.$store.state.instance.logoLeft },
currentUser () { return this.$store.state.users.currentUser },
privateMode () { return this.$store.state.instance.private },
},
methods: {
scrollToTop () {
window.scrollTo(0, 0)
},
logout () {
this.$router.replace('/main/public')
this.$store.dispatch('logout')
},
onSearchBarToggled (hidden) {
this.searchBarHidden = hidden
},
openSettingsModal () {
this.$store.dispatch('openSettingsModal')
},
}
}

View file

@ -0,0 +1,112 @@
@import '../../_variables.scss';
.DesktopNav {
height: 50px;
width: 100%;
position: fixed;
.inner-nav {
display: grid;
grid-template-rows: 50px;
grid-template-columns: 2fr auto 2fr;
grid-template-areas: "sitename logo actions";
box-sizing: border-box;
padding: 0 1.2em;
margin: auto;
max-width: 980px;
}
&.-logoLeft {
grid-template-columns: auto 2fr 2fr;
grid-template-areas: "logo sitename actions";
}
button {
&, svg {
color: $fallback--text;
color: var(--btnTopBarText, $fallback--text);
}
&:active {
background-color: $fallback--fg;
background-color: var(--btnPressedTopBar, $fallback--fg);
color: $fallback--text;
color: var(--btnPressedTopBarText, $fallback--text);
}
&:disabled {
color: $fallback--text;
color: var(--btnDisabledTopBarText, $fallback--text);
}
&.toggled {
color: $fallback--text;
color: var(--btnToggledTopBarText, $fallback--text);
background-color: $fallback--fg;
background-color: var(--btnToggledTopBar, $fallback--fg)
}
}
.logo {
grid-area: logo;
position: relative;
transition: opacity;
transition-timing-function: ease-out;
transition-duration: 100ms;
@media all and (min-width: 800px) {
opacity: 1 !important;
}
.mask {
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
background-color: $fallback--fg;
background-color: var(--topBarText, $fallback--fg);
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
img {
display: inline-block;
height: 50px;
}
}
.nav-icon {
margin-left: 0.2em;
width: 2em;
text-align: center;
}
a, a svg {
color: $fallback--link;
color: var(--topBarLink, $fallback--link);
}
.sitename {
grid-area: sitename;
}
.actions {
grid-area: actions;
}
.item {
flex: 1;
line-height: 50px;
height: 50px;
overflow: hidden;
display: flex;
flex-wrap: wrap;
&.right {
justify-content: flex-end;
text-align: right;
}
}
}

View file

@ -0,0 +1,79 @@
<template>
<nav
id="nav"
class="DesktopNav"
:class="{ '-logoLeft': logoLeft }"
@click="scrollToTop()"
>
<div class="inner-nav">
<div class="item sitename">
<router-link
v-if="!hideSitename"
class="site-name"
:to="{ name: 'root' }"
active-class="home"
>
{{ sitename }}
</router-link>
</div>
<router-link
class="logo"
:to="{ name: 'root' }"
:style="logoBgStyle"
>
<div
class="mask"
:style="logoMaskStyle"
/>
<img
:src="logo"
:style="logoStyle"
>
</router-link>
<div class="item right actions">
<search-bar
v-if="currentUser || !privateMode"
@toggled="onSearchBarToggled"
@click.stop.native
/>
<a
href="#"
class="nav-icon"
@click.stop="openSettingsModal"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="cog"
:title="$t('nav.preferences')"
/>
</a>
<a
v-if="currentUser && currentUser.role === 'admin'"
href="/pleroma/admin/#/login-pleroma"
class="nav-icon"
target="_blank"
><FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="tachometer-alt"
:title="$t('nav.administration')"
/></a>
<a
v-if="currentUser"
href="#"
class="nav-icon"
@click.prevent="logout"
><FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="sign-out-alt"
:title="$t('login.logout')"
/></a>
</div>
</div>
</nav>
</template>
<script src="./desktop_nav.js"></script>
<style src="./desktop_nav.scss" lang="scss"></style>

View file

@ -1,12 +1,11 @@
<template> <template>
<div> <div
class="MobileNav"
>
<nav <nav
id="nav" id="nav"
class="nav-bar container" class="mobile-nav"
:class="{ 'mobile-hidden': isChat }" :class="{ 'mobile-hidden': isChat }"
>
<div
class="mobile-inner-nav"
@click="scrollToTop()" @click="scrollToTop()"
> >
<div class="item"> <div class="item">
@ -50,7 +49,6 @@
/> />
</a> </a>
</div> </div>
</div>
</nav> </nav>
<div <div
v-if="currentUser" v-if="currentUser"
@ -93,6 +91,18 @@
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.MobileNav {
.mobile-nav {
display: grid;
line-height: 50px;
height: 50px;
grid-template-rows: 50px;
grid-template-columns: 2fr auto;
width: 100%;
position: fixed;
box-sizing: border-box;
}
.mobile-inner-nav { .mobile-inner-nav {
width: 100%; width: 100%;
display: flex; display: flex;
@ -190,5 +200,6 @@
} }
} }
} }
}
</style> </style>

View file

@ -1,6 +1,8 @@
<template> <template>
<div> <div
<div class="search-bar-container"> class="SearchBar"
:class="{ '-expanded': !hidden }"
>
<a <a
v-if="hidden" v-if="hidden"
href="#" href="#"
@ -41,7 +43,6 @@
</span> </span>
</template> </template>
</div> </div>
</div>
</template> </template>
<script src="./search_bar.js"></script> <script src="./search_bar.js"></script>
@ -49,21 +50,23 @@
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.search-bar-container { .SearchBar {
max-width: 100%;
display: inline-flex; display: inline-flex;
align-items: baseline; align-items: baseline;
vertical-align: baseline; vertical-align: baseline;
justify-content: flex-end; justify-content: flex-end;
&.-expanded {
width: 100%;
}
.search-bar-input, .search-bar-input,
.search-button { .search-button {
height: 29px; height: 29px;
} }
.search-bar-input { .search-bar-input {
// TODO: do this properly without a rough guesstimate of 2 icons + paddings flex: 1 0 auto;
max-width: calc(100% - 30px - 30px - 20px);
} }
.cancel-icon { .cancel-icon {

View file

@ -30,6 +30,7 @@ const defaultState = {
logo: '/static/logo.png', logo: '/static/logo.png',
logoMargin: '.2em', logoMargin: '.2em',
logoMask: true, logoMask: true,
logoLeft: false,
minimalScopesMode: false, minimalScopesMode: false,
nsfwCensorImage: undefined, nsfwCensorImage: undefined,
postContentType: 'text/plain', postContentType: 'text/plain',

View file

@ -13,6 +13,7 @@
"logo": "/static/logo.png", "logo": "/static/logo.png",
"logoMargin": ".1em", "logoMargin": ".1em",
"logoMask": true, "logoMask": true,
"logoLeft": false,
"minimalScopesMode": false, "minimalScopesMode": false,
"nsfwCensorImage": "", "nsfwCensorImage": "",
"postContentType": "text/plain", "postContentType": "text/plain",