Move announcements module to store

This commit is contained in:
Sean King 2023-04-06 16:32:21 -06:00
parent e3ca5b0a32
commit f9254e5fb7
No known key found for this signature in database
GPG Key ID: 510C52BACD6E7257
13 changed files with 160 additions and 153 deletions

View File

@ -19,6 +19,7 @@ import FaviconService from '../services/favicon_service/favicon_service.js'
import { useI18nStore } from '../stores/i18n'
import { useInterfaceStore } from '../stores/interface'
import { useAnnouncementsStore } from '../stores/announcements'
let staticInitialResults = null
@ -389,7 +390,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
// Start fetching things that don't need to block the UI
store.dispatch('fetchMutes')
store.dispatch('startFetchingAnnouncements')
useAnnouncementsStore().startFetchingAnnouncements()
getTOS({ store })
getStickers({ store })

View File

@ -2,6 +2,7 @@ import { mapState } from 'vuex'
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
import RichContent from '../rich_content/rich_content.jsx'
import localeService from '../../services/locale/locale.service.js'
import { useAnnouncementsStore } from '../../stores/announcements'
const Announcement = {
components: {
@ -67,11 +68,11 @@ const Announcement = {
methods: {
markAsRead () {
if (!this.isRead) {
return this.$store.dispatch('markAnnouncementAsRead', this.announcement.id)
return useAnnouncementsStore().markAnnouncementAsRead(this.announcement.id)
}
},
deleteAnnouncement () {
return this.$store.dispatch('deleteAnnouncement', this.announcement.id)
return useAnnouncementsStore().deleteAnnouncement(this.announcement.id)
},
formatTimeOrDate (time, locale) {
const d = new Date(time)
@ -85,7 +86,7 @@ const Announcement = {
this.editing = true
},
submitEdit () {
this.$store.dispatch('editAnnouncement', {
useAnnouncementsStore().editAnnouncement({
id: this.announcement.id,
...this.editedAnnouncement
})

View File

@ -1,6 +1,7 @@
import { mapState } from 'vuex'
import Announcement from '../announcement/announcement.vue'
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
import { useAnnouncementsStore } from '../../stores/announcements'
const AnnouncementsPage = {
components: {
@ -20,14 +21,14 @@ const AnnouncementsPage = {
}
},
mounted () {
this.$store.dispatch('fetchAnnouncements')
useAnnouncementsStore().fetchAnnouncements()
},
computed: {
...mapState({
currentUser: state => state.users.currentUser
}),
announcements () {
return this.$store.state.announcements.announcements
return useAnnouncementsStore().announcements
},
canPostAnnouncement () {
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
@ -36,7 +37,7 @@ const AnnouncementsPage = {
methods: {
postAnnouncement () {
this.posting = true
this.$store.dispatch('postAnnouncement', this.newAnnouncement)
useAnnouncementsStore().postAnnouncement(this.newAnnouncement)
.then(() => {
this.newAnnouncement.content = ''
this.startsAt = undefined

View File

@ -5,6 +5,7 @@ import { unseenNotificationsFromStore } from '../../services/notification_utils/
import GestureService from '../../services/gesture_service/gesture_service'
import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import { mapGetters } from 'vuex'
import { mapState } from 'pinia'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes,
@ -13,6 +14,7 @@ import {
faArrowUp,
faMinus
} from '@fortawesome/free-solid-svg-icons'
import { useAnnouncementsStore } from '../../stores/announcements'
library.add(
faTimes,
@ -57,7 +59,8 @@ const MobileNav = {
isChat () {
return this.$route.name === 'chat'
},
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount']),
...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
...mapGetters(['unreadChatCount']),
chatsPinned () {
return new Set(this.$store.state.serverSideStorage.prefsStorage.collections.pinnedNavItems).has('chats')
},

View File

@ -1,5 +1,6 @@
import ListsMenuContent from 'src/components/lists_menu/lists_menu_content.vue'
import { mapState, mapGetters } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
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'
@ -21,6 +22,7 @@ import {
faList,
faBullhorn
} from '@fortawesome/free-solid-svg-icons'
import { useAnnouncementsStore } from '../../stores/announcements'
library.add(
faUsers,
@ -82,13 +84,16 @@ const NavPanel = {
}
},
computed: {
...mapPiniaState(useAnnouncementsStore, {
unreadAnnouncementCount: 'unreadAnnouncementCount',
supportsAnnouncements: store => store.supportsAnnouncements
}),
...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,
supportsAnnouncements: state => state.announcements.supportsAnnouncements,
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems),
collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav
}),
@ -120,7 +125,7 @@ const NavPanel = {
}
)
},
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
...mapGetters(['unreadChatCount'])
}
}

View File

@ -76,6 +76,7 @@ export const ROOT_ITEMS = {
route: 'announcements',
icon: 'bullhorn',
label: 'nav.announcements',
store: 'announcements',
badgeGetter: 'unreadAnnouncementCount',
criteria: ['announcements']
}

View File

@ -3,6 +3,8 @@ import { routeTo } from 'src/components/navigation/navigation.js'
import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faThumbtack } from '@fortawesome/free-solid-svg-icons'
import { mapStores } from 'pinia'
import { useAnnouncementsStore } from '../../stores/announcements'
library.add(faThumbtack)
@ -31,6 +33,7 @@ const NavigationEntry = {
getters () {
return this.$store.getters
},
...mapStores(useAnnouncementsStore),
...mapState({
currentUser: state => state.users.currentUser,
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)

View File

@ -39,6 +39,12 @@
>
{{ getters[item.badgeGetter] }}
</div>
<div
v-else-if="item.badgeGetter && item.store && this[`${item.store}Store`][item.badgeGetter]"
class="badge badge-notification"
>
{{ this[`${item.store}Store`][item.badgeGetter] }}
</div>
<button
v-if="showPin && currentUser"
type="button"

View File

@ -1,5 +1,6 @@
import { computed } from 'vue'
import { mapGetters } from 'vuex'
import { mapState } from 'pinia'
import Notification from '../notification/notification.vue'
import NotificationFilters from './notification_filters.vue'
import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
@ -12,6 +13,7 @@ import FaviconService from '../../services/favicon_service/favicon_service.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch, faArrowUp, faMinus } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from '../../stores/interface'
import { useAnnouncementsStore } from '../../stores/announcements'
library.add(
faCircleNotch,
@ -95,7 +97,8 @@ const Notifications = {
return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
},
noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
...mapGetters(['unreadChatCount'])
},
mounted () {
this.scrollerRef = this.$refs.root.closest('.column.-scrollable')

View File

@ -1,4 +1,5 @@
import { mapState, mapGetters } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import UserCard from '../user_card/user_card.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service'
@ -21,6 +22,7 @@ import {
} from '@fortawesome/free-solid-svg-icons'
import { useShoutStore } from '../../stores/shout'
import { useInterfaceStore } from '../../stores/interface'
import { useAnnouncementsStore } from '../../stores/announcements'
library.add(
faSignInAlt,
@ -96,11 +98,14 @@ const SideDrawer = {
return { name }
}
},
...mapState({
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
supportsAnnouncements: state => state.announcements.supportsAnnouncements
...mapPiniaState(useAnnouncementsStore, {
supportsAnnouncements: store => store.supportsAnnouncements,
unreadAnnouncementCount: 'unreadAnnouncementCount'
}),
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
...mapState({
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
}),
...mapGetters(['unreadChatCount'])
},
methods: {
toggleDrawer () {

View File

@ -18,7 +18,6 @@ import oauthTokensModule from './modules/oauth_tokens.js'
import reportsModule from './modules/reports.js'
import chatsModule from './modules/chats.js'
import announcementsModule from './modules/announcements.js'
import { createI18n } from 'vue-i18n'
@ -77,8 +76,7 @@ const persistedStateOptions = {
authFlow: authFlowModule,
oauthTokens: oauthTokensModule,
reports: reportsModule,
chats: chatsModule,
announcements: announcementsModule
chats: chatsModule
},
plugins,
strict: false // Socket modifies itself, let's ignore this for now.

View File

@ -1,135 +0,0 @@
const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5
export const defaultState = {
announcements: [],
supportsAnnouncements: true,
fetchAnnouncementsTimer: undefined
}
export const mutations = {
setAnnouncements (state, announcements) {
state.announcements = announcements
},
setAnnouncementRead (state, { id, read }) {
const index = state.announcements.findIndex(a => a.id === id)
if (index < 0) {
return
}
state.announcements[index].read = read
},
setFetchAnnouncementsTimer (state, timer) {
state.fetchAnnouncementsTimer = timer
},
setSupportsAnnouncements (state, supportsAnnouncements) {
state.supportsAnnouncements = supportsAnnouncements
}
}
export const getters = {
unreadAnnouncementCount (state, _getters, rootState) {
if (!rootState.users.currentUser) {
return 0
}
const unread = state.announcements.filter(announcement => !(announcement.inactive || announcement.read))
return unread.length
}
}
const announcements = {
state: defaultState,
mutations,
getters,
actions: {
fetchAnnouncements (store) {
if (!store.state.supportsAnnouncements) {
return Promise.resolve()
}
const currentUser = store.rootState.users.currentUser
const isAdmin = currentUser && currentUser.privileges.includes('announcements_manage_announcements')
const getAnnouncements = async () => {
if (!isAdmin) {
return store.rootState.api.backendInteractor.fetchAnnouncements()
}
const all = await store.rootState.api.backendInteractor.adminFetchAnnouncements()
const visible = await store.rootState.api.backendInteractor.fetchAnnouncements()
const visibleObject = visible.reduce((a, c) => {
a[c.id] = c
return a
}, {})
const getWithinVisible = announcement => visibleObject[announcement.id]
all.forEach(announcement => {
const visibleAnnouncement = getWithinVisible(announcement)
if (!visibleAnnouncement) {
announcement.inactive = true
} else {
announcement.read = visibleAnnouncement.read
}
})
return all
}
return getAnnouncements()
.then(announcements => {
store.commit('setAnnouncements', announcements)
})
.catch(error => {
// If and only if backend does not support announcements, it would return 404.
// In this case, silently ignores it.
if (error && error.statusCode === 404) {
store.commit('setSupportsAnnouncements', false)
} else {
throw error
}
})
},
markAnnouncementAsRead (store, id) {
return store.rootState.api.backendInteractor.dismissAnnouncement({ id })
.then(() => {
store.commit('setAnnouncementRead', { id, read: true })
})
},
startFetchingAnnouncements (store) {
if (store.state.fetchAnnouncementsTimer) {
return
}
const interval = setInterval(() => store.dispatch('fetchAnnouncements'), FETCH_ANNOUNCEMENT_INTERVAL_MS)
store.commit('setFetchAnnouncementsTimer', interval)
return store.dispatch('fetchAnnouncements')
},
stopFetchingAnnouncements (store) {
const interval = store.state.fetchAnnouncementsTimer
store.commit('setFetchAnnouncementsTimer', undefined)
clearInterval(interval)
},
postAnnouncement (store, { content, startsAt, endsAt, allDay }) {
return store.rootState.api.backendInteractor.postAnnouncement({ content, startsAt, endsAt, allDay })
.then(() => {
return store.dispatch('fetchAnnouncements')
})
},
editAnnouncement (store, { id, content, startsAt, endsAt, allDay }) {
return store.rootState.api.backendInteractor.editAnnouncement({ id, content, startsAt, endsAt, allDay })
.then(() => {
return store.dispatch('fetchAnnouncements')
})
},
deleteAnnouncement (store, id) {
return store.rootState.api.backendInteractor.deleteAnnouncement({ id })
.then(() => {
return store.dispatch('fetchAnnouncements')
})
}
}
}
export default announcements

115
src/stores/announcements.js Normal file
View File

@ -0,0 +1,115 @@
import { defineStore } from 'pinia'
const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5
export const useAnnouncementsStore = defineStore('announcements', {
state: () => ({
announcements: [],
supportsAnnouncements: true,
fetchAnnouncementsTimer: undefined
}),
getters: {
unreadAnnouncementCount () {
if (!window.vuex.state.users.currentUser) {
return 0
}
const unread = this.announcements.filter(announcement => !(announcement.inactive || announcement.read))
return unread.length
}
},
actions: {
fetchAnnouncements () {
if (!this.supportsAnnouncements) {
return Promise.resolve()
}
const currentUser = window.vuex.state.users.currentUser
const isAdmin = currentUser && currentUser.privileges.includes('announcements_manage_announcements')
const getAnnouncements = async () => {
if (!isAdmin) {
return window.vuex.state.api.backendInteractor.fetchAnnouncements()
}
const all = await window.vuex.state.api.backendInteractor.adminFetchAnnouncements()
const visible = await window.vuex.state.api.backendInteractor.fetchAnnouncements()
const visibleObject = visible.reduce((a, c) => {
a[c.id] = c
return a
}, {})
const getWithinVisible = announcement => visibleObject[announcement.id]
all.forEach(announcement => {
const visibleAnnouncement = getWithinVisible(announcement)
if (!visibleAnnouncement) {
announcement.inactive = true
} else {
announcement.read = visibleAnnouncement.read
}
})
return all
}
return getAnnouncements()
.then(announcements => {
this.announcements = announcements
})
.catch(error => {
// If and only if backend does not support announcements, it would return 404.
// In this case, silently ignores it.
if (error && error.statusCode === 404) {
this.supportsAnnouncements = false
} else {
throw error
}
})
},
markAnnouncementAsRead (id) {
return window.vuex.state.api.backendInteractor.dismissAnnouncement({ id })
.then(() => {
const index = this.announcements.findIndex(a => a.id === id)
if (index < 0) {
return
}
this.announcements[index].read = true
})
},
startFetchingAnnouncements () {
if (this.fetchAnnouncementsTimer) {
return
}
const interval = setInterval(() => this.fetchAnnouncements(), FETCH_ANNOUNCEMENT_INTERVAL_MS)
this.fetchAnnouncementsTimer = interval
return this.fetchAnnouncements()
},
stopFetchingAnnouncements () {
const interval = this.fetchAnnouncementsTimer
this.fetchAnnouncementsTimer = undefined
clearInterval(interval)
},
postAnnouncement ({ content, startsAt, endsAt, allDay }) {
return window.vuex.state.api.backendInteractor.postAnnouncement({ content, startsAt, endsAt, allDay })
.then(() => {
return this.fetchAnnouncements()
})
},
editAnnouncement ({ id, content, startsAt, endsAt, allDay }) {
return window.vuex.state.api.backendInteractor.editAnnouncement({ id, content, startsAt, endsAt, allDay })
.then(() => {
return this.fetchAnnouncements()
})
},
deleteAnnouncement (id) {
return window.vuex.state.api.backendInteractor.deleteAnnouncement({ id })
.then(() => {
return this.fetchAnnouncements()
})
}
}
})