From 6df99133548fb209bf365b77665931be477f0a30 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 11 Aug 2022 14:30:58 +0300 Subject: ability to pin items in navigation menu, initial draft version --- src/components/nav_panel/nav_panel.js | 111 ++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 5 deletions(-) (limited to 'src/components/nav_panel/nav_panel.js') diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index abeff6bf..758f9af4 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -31,6 +31,66 @@ library.add( faList ) +export const TIMELINES = { + home: { + route: 'friends', + anonRoute: 'public-timeline', + 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' + }, + 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' + }, + 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' + } +} + const NavPanel = { created () { if (this.currentUser && this.currentUser.locked) { @@ -43,8 +103,11 @@ const NavPanel = { }, data () { return { + collapsed: false, showTimelines: false, - showLists: false + showLists: false, + timelinesList: Object.entries(TIMELINES).map(([k, v]) => ({ ...v, name: k })), + rootList: Object.entries(ROOT_ITEMS).map(([k, v]) => ({ ...v, name: k })) } }, methods: { @@ -53,19 +116,57 @@ const NavPanel = { }, toggleLists () { this.showLists = !this.showLists + }, + toggleCollapse () { + this.collapsed = !this.collapsed + }, + isPinned (item) { + return this.pinnedItems.has(item) + }, + togglePin (item) { + if (this.isPinned(item)) { + this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedNavItems', value: item }) + } else { + this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value: item }) + } } }, computed: { - listsNavigation () { - return this.$store.getters.mergedConfig.listsNavigation - }, ...mapState({ 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 + pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, + pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems) }), + rootItems () { + return Object + .entries({ ...ROOT_ITEMS }) + .map(([k, v]) => ({ ...v, name: k })) + .filter(({ criteria, anon, anonRoute }) => { + const set = new Set(criteria || []) + if (!this.federating && set.has('federating')) return false + if (this.private && set.has('!private')) return false + if (!this.currentUser && !(anon || anonRoute)) return false + if ((!this.currentUser || !this.currentUser.locked) && set.has('lockedUser')) return false + return true + }) + }, + pinnedList () { + return Object + .entries({ ...TIMELINES, ...ROOT_ITEMS }) + .filter(([k]) => this.pinnedItems.has(k)) + .map(([k, v]) => ({ ...v, name: k })) + .filter(({ criteria, anon, anonRoute }) => { + const set = new Set(criteria || []) + if (!this.federating && set.has('federating')) return false + if (this.private && set.has('!private')) return false + if (!this.currentUser && !(anon || anonRoute)) return false + if (this.currentUser && !this.currentUser.locked && set.has('locked')) return false + return true + }) + }, ...mapGetters(['unreadChatCount']) } } -- cgit v1.2.3-70-g09d2 From 04f8c2d29d0e9c5e0341b067e5e783b90c95064b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 11 Aug 2022 18:06:28 +0300 Subject: it works more or less well now --- src/components/nav_panel/nav_panel.js | 8 ++++-- src/modules/serverSideStorage.js | 54 +++++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 15 deletions(-) (limited to 'src/components/nav_panel/nav_panel.js') diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index 758f9af4..7cc3122d 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -103,7 +103,6 @@ const NavPanel = { }, data () { return { - collapsed: false, showTimelines: false, showLists: false, timelinesList: Object.entries(TIMELINES).map(([k, v]) => ({ ...v, name: k })), @@ -118,7 +117,8 @@ const NavPanel = { this.showLists = !this.showLists }, toggleCollapse () { - this.collapsed = !this.collapsed + this.$store.commit('setPreference', { path: 'simple.collapseNav', value: !this.collapsed }) + this.$store.dispatch('pushServerSideStorage') }, isPinned (item) { return this.pinnedItems.has(item) @@ -129,6 +129,7 @@ const NavPanel = { } else { this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value: item }) } + this.$store.dispatch('pushServerSideStorage') } }, computed: { @@ -138,7 +139,8 @@ const NavPanel = { privateMode: state => state.instance.private, federating: state => state.instance.federating, pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, - pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems) + pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems), + collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav }), rootItems () { return Object diff --git a/src/modules/serverSideStorage.js b/src/modules/serverSideStorage.js index c4c5ebba..d95fbb8a 100644 --- a/src/modules/serverSideStorage.js +++ b/src/modules/serverSideStorage.js @@ -22,7 +22,8 @@ export const defaultState = { prefsStorage: { _journal: [], simple: { - dontShowUpdateNotifs: false + dontShowUpdateNotifs: false, + collapseNav: false }, collections: { pinnedNavItems: ['home', 'dms', 'chats', 'about'] @@ -58,6 +59,23 @@ const _wrapData = (data) => ({ const _checkValidity = (data) => data._timestamp > 0 && data._version > 0 +const _verifyPrefs = (state) => { + state.prefsStorage = state.prefsStorage || { + simple: {}, + collections: {} + } + Object.entries(defaultState.prefsStorage.simple).forEach(([k, v]) => { + if (typeof v === 'number' || typeof v === 'boolean') return + console.warn(`Preference simple.${k} as invalid type, reinitializing`) + set(state.prefsStorage.simple, k, defaultState.prefsStorage.simple[k]) + }) + Object.entries(defaultState.prefsStorage.collections).forEach(([k, v]) => { + if (Array.isArray(v)) return + console.warn(`Preference collections.${k} as invalid type, reinitializing`) + set(state.prefsStorage.collections, k, defaultState.prefsStorage.collections[k]) + }) +} + export const _getRecentData = (cache, live) => { const result = { recent: null, stale: null, needUpload: false } const cacheValid = _checkValidity(cache || {}) @@ -149,7 +167,8 @@ export const _mergePrefs = (recent, stale, allFlagKeys) => { */ const resultOutput = { ...recentData } const totalJournal = _mergeJournal(staleJournal, recentJournal) - totalJournal.forEach(({ path, timestamp, operation, args }) => { + totalJournal.forEach(({ path, timestamp, operation, command, args }) => { + operation = operation || command if (path.startsWith('_')) { console.error(`journal contains entry to edit internal (starts with _) field '${path}', something is incorrect here, ignoring.`) return @@ -161,9 +180,12 @@ export const _mergePrefs = (recent, stale, allFlagKeys) => { case 'addToCollection': set(resultOutput, path, Array.from(new Set(get(resultOutput, path)).add(args[0]))) break - case 'removeFromCollection': - set(resultOutput, path, Array.from(new Set(get(resultOutput, path)).remove(args[0]))) + case 'removeFromCollection': { + const newSet = new Set(get(resultOutput, path)) + newSet.delete(args[0]) + set(resultOutput, path, Array.from(newSet)) break + } case 'reorderCollection': { const [value, movement] = args set(resultOutput, path, _moveItemInArray(get(resultOutput, path), value, movement)) @@ -269,6 +291,8 @@ export const mutations = { // Merge the flags console.debug('Merging the data...') totalFlags = _mergeFlags(recent, stale, allFlagKeys) + _verifyPrefs(recent) + _verifyPrefs(stale) totalPrefs = _mergePrefs(recent.prefsStorage, stale.prefsStorage) } else { totalFlags = recent.flagStorage @@ -301,8 +325,9 @@ export const mutations = { set(state.prefsStorage, path, value) state.prefsStorage._journal = [ ...state.prefsStorage._journal, - { command: 'set', path, args: [value], timestamp: Date.now() } + { operation: 'set', path, args: [value], timestamp: Date.now() } ] + state.dirty = true }, addCollectionPreference (state, { path, value }) { if (path.startsWith('_')) { @@ -311,11 +336,12 @@ export const mutations = { } const collection = new Set(get(state.prefsStorage, path)) collection.add(value) - set(state.prefsStorage, path, collection) + set(state.prefsStorage, path, [...collection]) state.prefsStorage._journal = [ ...state.prefsStorage._journal, - { command: 'addToCollection', path, args: [value], timestamp: Date.now() } + { operation: 'addToCollection', path, args: [value], timestamp: Date.now() } ] + state.dirty = true }, removeCollectionPreference (state, { path, value }) { if (path.startsWith('_')) { @@ -324,11 +350,12 @@ export const mutations = { } const collection = new Set(get(state.prefsStorage, path)) collection.delete(value) - set(state.prefsStorage, path, collection) + set(state.prefsStorage, path, [...collection]) state.prefsStorage._journal = [ ...state.prefsStorage._journal, - { command: 'removeFromCollection', path, args: [value], timestamp: Date.now() } + { operation: 'removeFromCollection', path, args: [value], timestamp: Date.now() } ] + state.dirty = true }, reorderCollectionPreference (state, { path, value, movement }) { if (path.startsWith('_')) { @@ -340,8 +367,9 @@ export const mutations = { set(state.prefsStorage, path, newCollection) state.prefsStorage._journal = [ ...state.prefsStorage._journal, - { command: 'arrangeCollection', path, args: [value], timestamp: Date.now() } + { operation: 'arrangeCollection', path, args: [value], timestamp: Date.now() } ] + state.dirty = true }, updateCache (state) { state.prefsStorage._journal = _mergeJournal(state.prefsStorage._journal) @@ -365,8 +393,10 @@ const serverSideStorage = { const params = { pleroma_settings_store: { 'pleroma-fe': state.cache } } rootState.api.backendInteractor .updateProfile({ params }) - .then((user) => commit('setServerSideStorage', user)) - state.dirty = false + .then((user) => { + commit('setServerSideStorage', user) + state.dirty = false + }) } } } -- cgit v1.2.3-70-g09d2 From 77127e2a588abb5bf329506ff7e006021b086e90 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 11 Aug 2022 21:00:27 +0300 Subject: you can now pin lists --- src/App.scss | 15 ++- src/components/lists_menu/lists_menu_content.js | 32 +++--- src/components/lists_menu/lists_menu_content.vue | 16 +-- src/components/nav_panel/nav_panel.js | 123 ++++++--------------- src/components/nav_panel/nav_panel.vue | 56 +++------- src/components/timeline/timeline.js | 1 + src/components/timeline/timeline.vue | 2 +- src/components/timeline_menu/timeline_menu.js | 16 ++- src/components/timeline_menu/timeline_menu.vue | 8 +- .../timeline_menu/timeline_menu_content.js | 52 --------- .../timeline_menu/timeline_menu_content.vue | 31 ------ 11 files changed, 103 insertions(+), 249 deletions(-) delete mode 100644 src/components/timeline_menu/timeline_menu_content.js delete mode 100644 src/components/timeline_menu/timeline_menu_content.vue (limited to 'src/components/nav_panel/nav_panel.js') diff --git a/src/App.scss b/src/App.scss index c75c990a..4c9a8884 100644 --- a/src/App.scss +++ b/src/App.scss @@ -117,8 +117,15 @@ h4 { margin: 0; } +.iconLetter { + display: inline-block; + text-align: center; + font-weight: 1000; +} + i[class*=icon-], -.svg-inline--fa { +.svg-inline--fa, +.iconLetter { color: $fallback--icon; color: var(--icon, $fallback--icon); } @@ -746,13 +753,15 @@ option { } .fa-scale-110 { - &.svg-inline--fa { + &.svg-inline--fa, + &.iconLetter { font-size: 1.1em; } } .fa-old-padding { - &.svg-inline--fa { + &.svg-inline--fa, + &.iconLetter { padding: 0 0.3em; } } diff --git a/src/components/lists_menu/lists_menu_content.js b/src/components/lists_menu/lists_menu_content.js index 37e7868c..99fea0f0 100644 --- a/src/components/lists_menu/lists_menu_content.js +++ b/src/components/lists_menu/lists_menu_content.js @@ -1,28 +1,26 @@ import { mapState } from 'vuex' -import { library } from '@fortawesome/fontawesome-svg-core' -import { - faUsers, - faGlobe, - faBookmark, - faEnvelope, - faHome -} from '@fortawesome/free-solid-svg-icons' +import NavigationEntry from 'src/components/navigation/navigation_entry.vue' -library.add( - faUsers, - faGlobe, - faBookmark, - faEnvelope, - faHome -) +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] +})) -const ListsMenuContent = { +export const ListsMenuContent = { + props: [ + 'showPin' + ], created () { this.$store.dispatch('startFetchingLists') }, + components: { + NavigationEntry + }, computed: { ...mapState({ - lists: state => state.lists.allLists, + lists: getListEntries, currentUser: state => state.users.currentUser, privateMode: state => state.instance.private, federating: state => state.instance.federating diff --git a/src/components/lists_menu/lists_menu_content.vue b/src/components/lists_menu/lists_menu_content.vue index e910d6eb..9c58b092 100644 --- a/src/components/lists_menu/lists_menu_content.vue +++ b/src/components/lists_menu/lists_menu_content.vue @@ -1,17 +1,7 @@ diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index 7cc3122d..10998113 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -1,6 +1,8 @@ -import TimelineMenuContent from '../timeline_menu/timeline_menu_content.vue' -import ListsMenuContent from '../lists_menu/lists_menu_content.vue' +import { getListEntries, ListsMenuContent } from '../lists_menu/lists_menu_content.vue' import { mapState, mapGetters } from 'vuex' +import { TIMELINES, ROOT_ITEMS } from 'src/components/navigation/navigation.js' +import { filterNavigation } from 'src/components/navigation/filter.js' +import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -30,67 +32,6 @@ library.add( faStream, faList ) - -export const TIMELINES = { - home: { - route: 'friends', - anonRoute: 'public-timeline', - 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' - }, - 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' - }, - 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' - } -} - const NavPanel = { created () { if (this.currentUser && this.currentUser.locked) { @@ -98,8 +39,8 @@ const NavPanel = { } }, components: { - TimelineMenuContent, - ListsMenuContent + ListsMenuContent, + NavigationEntry }, data () { return { @@ -134,6 +75,7 @@ const NavPanel = { }, computed: { ...mapState({ + lists: getListEntries, currentUser: state => state.users.currentUser, followRequestCount: state => state.api.followRequests.length, privateMode: state => state.instance.private, @@ -143,31 +85,36 @@ const NavPanel = { collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav }), rootItems () { - return Object - .entries({ ...ROOT_ITEMS }) - .map(([k, v]) => ({ ...v, name: k })) - .filter(({ criteria, anon, anonRoute }) => { - const set = new Set(criteria || []) - if (!this.federating && set.has('federating')) return false - if (this.private && set.has('!private')) return false - if (!this.currentUser && !(anon || anonRoute)) return false - if ((!this.currentUser || !this.currentUser.locked) && set.has('lockedUser')) return false - return true - }) + return filterNavigation( + Object + .entries({ ...ROOT_ITEMS }) + .map(([k, v]) => ({ ...v, name: k })), + { + isFederating: this.federating, + isPrivate: this.private, + currentUser: this.currentUser + } + ) }, pinnedList () { - return Object - .entries({ ...TIMELINES, ...ROOT_ITEMS }) - .filter(([k]) => this.pinnedItems.has(k)) - .map(([k, v]) => ({ ...v, name: k })) - .filter(({ criteria, anon, anonRoute }) => { - const set = new Set(criteria || []) - if (!this.federating && set.has('federating')) return false - if (this.private && set.has('!private')) return false - if (!this.currentUser && !(anon || anonRoute)) return false - if (this.currentUser && !this.currentUser.locked && set.has('locked')) return false - return true - }) + return filterNavigation( + [ + ...Object + .entries({ + ...TIMELINES, + ...ROOT_ITEMS + }) + .filter(([k]) => this.pinnedItems.has(k)) + .map(([k, v]) => ({ ...v, name: k })), + ...this.lists.filter((k) => this.pinnedItems.has(k.name)) + + ], + { + isFederating: this.federating, + isPrivate: this.private, + currentUser: this.currentUser + } + ) }, ...mapGetters(['unreadChatCount']) } diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue index 99a4571e..767ba6ec 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -5,13 +5,18 @@ + {{ item.iconLetter }} @@ -48,7 +53,9 @@ v-show="showTimelines" class="timelines-background" > - +
    + +
  • @@ -81,34 +88,10 @@ v-show="showLists" class="timelines-background" > - +
  • -
  • - - {{ $t(item.label) }} - - -
  • + @@ -220,16 +203,13 @@ margin-right: 0.8em; } - .badge { - position: absolute; - right: 0.6rem; - top: 1.25em; - } - .pinned-item { - .router-link-exact-active .svg-inline--fa { - color: $fallback--text; - color: var(--selectedMenuText, $fallback--text); + .router-link-active { + & .svg-inline--fa, + & .iconLetter { + color: $fallback--text; + color: var(--selectedMenuText, $fallback--text); + } } } } diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 8f6cae66..ee590c4b 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -28,6 +28,7 @@ const Timeline = { 'footerSlipgate' // reference to an element where we should put our footer ], data () { + console.log(this.timelineName) return { paused: false, unfocused: false, diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 627cafbb..fad3ff13 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -1,7 +1,7 @@