diff options
Diffstat (limited to 'src/components/navigation')
| -rw-r--r-- | src/components/navigation/filter.js | 18 | ||||
| -rw-r--r-- | src/components/navigation/navigation.js | 75 | ||||
| -rw-r--r-- | src/components/navigation/navigation_entry.js | 47 | ||||
| -rw-r--r-- | src/components/navigation/navigation_entry.vue | 120 | ||||
| -rw-r--r-- | src/components/navigation/navigation_pins.js | 88 | ||||
| -rw-r--r-- | src/components/navigation/navigation_pins.vue | 76 |
6 files changed, 424 insertions, 0 deletions
diff --git a/src/components/navigation/filter.js b/src/components/navigation/filter.js new file mode 100644 index 00000000..31b55486 --- /dev/null +++ b/src/components/navigation/filter.js @@ -0,0 +1,18 @@ +export const filterNavigation = (list = [], { hasChats, isFederating, isPrivate, currentUser }) => { + return list.filter(({ criteria, anon, anonRoute }) => { + const set = new Set(criteria || []) + if (!isFederating && set.has('federating')) return false + if (isPrivate && set.has('!private')) return false + if (!currentUser && !(anon || anonRoute)) return false + if ((!currentUser || !currentUser.locked) && set.has('lockedUser')) return false + if (!hasChats && set.has('chats')) return false + return true + }) +} + +export const getListEntries = state => state.lists.allLists.map(list => ({ + name: 'list-' + list.id, + routeObject: { name: 'lists-timeline', params: { id: list.id } }, + labelRaw: list.title, + iconLetter: list.title[0] +})) diff --git a/src/components/navigation/navigation.js b/src/components/navigation/navigation.js new file mode 100644 index 00000000..f66dd981 --- /dev/null +++ b/src/components/navigation/navigation.js @@ -0,0 +1,75 @@ +export const USERNAME_ROUTES = new Set([ + 'bookmarks', + 'dms', + 'interactions', + 'notifications', + 'chat', + 'chats', + 'user-profile' +]) + +export const TIMELINES = { + home: { + route: 'friends', + icon: 'home', + label: 'nav.home_timeline', + criteria: ['!private'] + }, + public: { + route: 'public-timeline', + anon: true, + icon: 'users', + label: 'nav.public_tl', + criteria: ['!private'] + }, + twkn: { + route: 'public-external-timeline', + anon: true, + icon: 'globe', + label: 'nav.twkn', + criteria: ['!private', 'federating'] + }, + bookmarks: { + route: 'bookmarks', + icon: 'bookmark', + label: 'nav.bookmarks' + }, + favorites: { + routeObject: { name: 'user-profile', query: { tab: 'favorites' } }, + icon: 'star', + label: 'user_card.favorites' + }, + dms: { + route: 'dms', + icon: 'envelope', + label: 'nav.dms' + } +} + +export const ROOT_ITEMS = { + interactions: { + route: 'interactions', + icon: 'bell', + label: 'nav.interactions' + }, + chats: { + route: 'chats', + icon: 'comments', + label: 'nav.chats', + badgeGetter: 'unreadChatCount', + criteria: ['chats'] + }, + friendRequests: { + route: 'friend-requests', + icon: 'user-plus', + label: 'nav.friend_requests', + criteria: ['lockedUser'], + badgeGetter: 'followRequestCount' + }, + about: { + route: 'about', + anon: true, + icon: 'info-circle', + label: 'nav.about' + } +} diff --git a/src/components/navigation/navigation_entry.js b/src/components/navigation/navigation_entry.js new file mode 100644 index 00000000..fe3402fc --- /dev/null +++ b/src/components/navigation/navigation_entry.js @@ -0,0 +1,47 @@ +import { mapState } from 'vuex' +import { USERNAME_ROUTES } from 'src/components/navigation/navigation.js' +import { library } from '@fortawesome/fontawesome-svg-core' +import { faThumbtack } from '@fortawesome/free-solid-svg-icons' + +library.add(faThumbtack) + +const NavigationEntry = { + props: ['item', 'showPin'], + methods: { + isPinned (value) { + return this.pinnedItems.has(value) + }, + togglePin (value) { + if (this.isPinned(value)) { + this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedNavItems', value }) + } else { + this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value }) + } + this.$store.dispatch('pushServerSideStorage') + } + }, + computed: { + routeTo () { + if (!this.item.route && !this.item.routeObject) return null + let route + if (this.item.routeObject) { + route = this.item.routeObject + } else { + route = { name: (this.item.anon || this.currentUser) ? this.item.route : this.item.anonRoute } + } + if (USERNAME_ROUTES.has(route.name)) { + route.params = { username: this.currentUser.screen_name, name: this.currentUser.screen_name } + } + return route + }, + getters () { + return this.$store.getters + }, + ...mapState({ + currentUser: state => state.users.currentUser, + pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems) + }) + } +} + +export default NavigationEntry diff --git a/src/components/navigation/navigation_entry.vue b/src/components/navigation/navigation_entry.vue new file mode 100644 index 00000000..824c00a2 --- /dev/null +++ b/src/components/navigation/navigation_entry.vue @@ -0,0 +1,120 @@ +<template> + <li class="NavigationEntry"> + <component + :is="routeTo ? 'router-link' : 'button'" + class="menu-item button-unstyled" + :to="routeTo" + > + <span> + <FAIcon + v-if="item.icon" + fixed-width + class="fa-scale-110 menu-icon" + :icon="item.icon" + /> + </span> + <span + v-if="item.iconLetter" + class="icon iconLetter fa-scale-110 menu-icon" + >{{ item.iconLetter }} + </span> + <span class="label"> + {{ item.labelRaw || $t(item.label) }} + </span> + <slot /> + <div + v-if="item.badgeGetter && getters[item.badgeGetter]" + class="badge badge-notification" + > + {{ getters[item.badgeGetter] }} + </div> + <button + v-if="showPin && currentUser" + type="button" + class="button-unstyled extra-button" + :title="$t(isPinned ? 'general.unpin' : 'general.pin' )" + :aria-pressed="!!isPinned" + @click.stop.prevent="togglePin(item.name)" + > + <FAIcon + v-if="showPin && currentUser" + fixed-width + class="fa-scale-110" + :class="{ 'veryfaint': !isPinned(item.name) }" + :transform="!isPinned(item.name) ? 'rotate-45' : ''" + icon="thumbtack" + /> + </button> + </component> + </li> +</template> + +<script src="./navigation_entry.js"></script> + +<style lang="scss"> +@import '../../_variables.scss'; + +.NavigationEntry { + .label { + flex: 1; + } + + .menu-icon { + margin-right: 0.8em; + } + + .extra-button { + width: 3em; + text-align: center; + + &:last-child { + margin-right: -0.8em; + } + } + + .menu-item { + display: flex; + box-sizing: border-box; + align-items: baseline; + height: 3.5em; + line-height: 3.5em; + padding: 0 1em; + width: 100%; + color: $fallback--link; + color: var(--link, $fallback--link); + + &:hover { + background-color: $fallback--lightBg; + background-color: var(--selectedMenu, $fallback--lightBg); + color: $fallback--link; + color: var(--selectedMenuText, $fallback--link); + --faint: var(--selectedMenuFaintText, $fallback--faint); + --faintLink: var(--selectedMenuFaintLink, $fallback--faint); + --lightText: var(--selectedMenuLightText, $fallback--lightText); + + .menu-icon { + --icon: var(--text, $fallback--icon); + } + } + + &.router-link-active { + font-weight: bolder; + background-color: $fallback--lightBg; + background-color: var(--selectedMenu, $fallback--lightBg); + color: $fallback--text; + color: var(--selectedMenuText, $fallback--text); + --faint: var(--selectedMenuFaintText, $fallback--faint); + --faintLink: var(--selectedMenuFaintLink, $fallback--faint); + --lightText: var(--selectedMenuLightText, $fallback--lightText); + + .menu-icon { + --icon: var(--text, $fallback--icon); + } + + &:hover { + text-decoration: underline; + } + } + } +} +</style> diff --git a/src/components/navigation/navigation_pins.js b/src/components/navigation/navigation_pins.js new file mode 100644 index 00000000..57b8d589 --- /dev/null +++ b/src/components/navigation/navigation_pins.js @@ -0,0 +1,88 @@ +import { mapState } from 'vuex' +import { TIMELINES, ROOT_ITEMS, USERNAME_ROUTES } from 'src/components/navigation/navigation.js' +import { getListEntries, filterNavigation } from 'src/components/navigation/filter.js' + +import { library } from '@fortawesome/fontawesome-svg-core' +import { + faUsers, + faGlobe, + faBookmark, + faEnvelope, + faComments, + faBell, + faInfoCircle, + faStream, + faList +} from '@fortawesome/free-solid-svg-icons' + +library.add( + faUsers, + faGlobe, + faBookmark, + faEnvelope, + faComments, + faBell, + faInfoCircle, + faStream, + faList +) + +const NavPanel = { + props: ['limit'], + methods: { + getRouteTo (item) { + if (item.routeObject) { + return item.routeObject + } + const route = { name: (item.anon || this.currentUser) ? item.route : item.anonRoute } + if (USERNAME_ROUTES.has(route.name)) { + route.params = { username: this.currentUser.screen_name } + } + return route + } + }, + computed: { + getters () { + return this.$store.getters + }, + ...mapState({ + lists: getListEntries, + currentUser: state => state.users.currentUser, + followRequestCount: state => state.api.followRequests.length, + privateMode: state => state.instance.private, + federating: state => state.instance.federating, + pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, + pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems) + }), + pinnedList () { + if (!this.currentUser) { + return [ + { ...TIMELINES.public, name: 'public' }, + { ...TIMELINES.twkn, name: 'twkn' }, + { ...ROOT_ITEMS.about, name: 'about' } + ] + } + return filterNavigation( + [ + ...Object + .entries({ ...TIMELINES }) + .filter(([k]) => this.pinnedItems.has(k)) + .map(([k, v]) => ({ ...v, name: k })), + ...this.lists.filter((k) => this.pinnedItems.has(k.name)), + ...Object + .entries({ ...ROOT_ITEMS }) + .filter(([k]) => this.pinnedItems.has(k)) + .map(([k, v]) => ({ ...v, name: k })) + ], + { + hasChats: this.pleromaChatMessagesAvailable, + isFederating: this.federating, + isPrivate: this.privateMode, + currentUser: this.currentUser + } + ).slice(0, this.limit) + } + } +} + +export default NavPanel diff --git a/src/components/navigation/navigation_pins.vue b/src/components/navigation/navigation_pins.vue new file mode 100644 index 00000000..5b3fa6f4 --- /dev/null +++ b/src/components/navigation/navigation_pins.vue @@ -0,0 +1,76 @@ +<template> + <span class="NavigationPins"> + <router-link + v-for="item in pinnedList" + :key="item.name" + class="pinned-item" + :to="getRouteTo(item)" + :title="item.labelRaw || $t(item.label)" + > + <FAIcon + v-if="item.icon" + fixed-width + :icon="item.icon" + /> + <span + v-if="item.iconLetter" + class="iconLetter fa-scale-110 fa-old-padding" + >{{ item.iconLetter }}</span> + <div + v-if="item.badgeGetter && getters[item.badgeGetter]" + class="alert-dot" + /> + </router-link> + </span> +</template> + +<script src="./navigation_pins.js"></script> + +<style lang="scss"> +@import '../../_variables.scss'; +.NavigationPins { + display: flex; + flex-wrap: wrap; + overflow: hidden; + height: 100%; + + .alert-dot { + border-radius: 100%; + height: 0.5em; + width: 0.5em; + position: absolute; + right: calc(50% - 0.25em); + top: calc(50% - 0.25em); + margin-left: 6px; + margin-top: -6px; + background-color: $fallback--cRed; + background-color: var(--badgeNotification, $fallback--cRed); + } + + .pinned-item { + position: relative; + flex: 1 0 3em; + min-width: 2em; + text-align: center; + overflow: visible; + box-sizing: border-box; + height: 100%; + + & .svg-inline--fa, + & .iconLetter { + margin: 0; + } + + &.router-link-active { + color: $fallback--text; + color: var(--selectedMenuText, $fallback--text); + border-bottom: 4px solid; + + & .svg-inline--fa, + & .iconLetter { + color: inherit; + } + } + } +} +</style> |
