Compare commits

...

19 commits

Author SHA1 Message Date
Henry Jameson
2d26737bab Merge branch 'migrate/vuex-to-pinia' into shigusegubu-themes3 2025-02-03 00:15:01 +02:00
Henry Jameson
7d19cc2d53 cleanup and fixes 2025-02-03 00:14:44 +02:00
Henry Jameson
c926ed7ac1 pleroma-fe boots once again 2025-01-30 21:56:07 +02:00
Henry Jameson
58e18d48df Merge remote-tracking branch 'origin/develop' into migrate/vuex-to-pinia 2025-01-30 18:08:05 +02:00
Sean King
5515f53794
Fix lint in lists store 2023-04-06 22:19:36 -06:00
Sean King
963e163858
Slightly refactor lists store 2023-04-06 22:17:03 -06:00
Sean King
8eff081468
Migrates lists module to store 2023-04-06 22:13:30 -06:00
Sean King
ad7d47f440
Move reports module to store 2023-04-06 17:59:12 -06:00
Sean King
f9254e5fb7
Move announcements module to store 2023-04-06 16:32:21 -06:00
Sean King
e3ca5b0a32
Move polls module to store 2023-04-05 22:30:20 -06:00
Sean King
22ab848f6b
Remove old interface module 2023-04-05 21:50:17 -06:00
Sean King
b1dcea0199
Migrate interface module to store 2023-04-05 21:06:37 -06:00
Sean King
872569ae8e
Move statusHistory module to store 2023-04-05 14:13:28 -06:00
Sean King
c25cfe540b
Move media_viewer module to store 2023-04-05 13:55:38 -06:00
Sean King
3430604dda
Move editStatus module to store 2023-04-05 13:23:25 -06:00
Sean King
27e36dbc2e
Move postStatus module to store 2023-04-05 13:01:37 -06:00
Sean King
aa6c13f9e6
Move shout module to store 2023-04-04 21:17:54 -06:00
Sean King
aa98e83ff0
Move i18n to new store 2023-04-04 14:40:12 -06:00
Sean King
edfaf5e80c
Add Pinia as dependency 2023-04-04 14:38:19 -06:00
97 changed files with 2309 additions and 3238 deletions

View file

@ -38,6 +38,7 @@
"pako": "^2.1.0", "pako": "^2.1.0",
"parse-link-header": "2.0.0", "parse-link-header": "2.0.0",
"phoenix": "1.7.18", "phoenix": "1.7.18",
"pinia": "^2.0.33",
"punycode.js": "2.3.1", "punycode.js": "2.3.1",
"qrcode": "1.5.4", "qrcode": "1.5.4",
"querystring-es3": "0.2.1", "querystring-es3": "0.2.1",

View file

@ -17,6 +17,8 @@ import GlobalNoticeList from './components/global_notice_list/global_notice_list
import { windowWidth, windowHeight } from './services/window_utils/window_utils' import { windowWidth, windowHeight } from './services/window_utils/window_utils'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
import { useShoutStore } from './stores/shout'
import { useInterfaceStore } from './stores/interface'
export default { export default {
name: 'app', name: 'app',
@ -60,7 +62,7 @@ export default {
document.getElementById('modal').classList = ['-' + this.layoutType] document.getElementById('modal').classList = ['-' + this.layoutType]
}, },
mounted () { mounted () {
if (this.$store.state.interface.themeApplied) { if (useInterfaceStore().themeApplied) {
this.removeSplash() this.removeSplash()
} }
}, },
@ -69,7 +71,7 @@ export default {
}, },
computed: { computed: {
themeApplied () { themeApplied () {
return this.$store.state.interface.themeApplied return useInterfaceStore().themeApplied
}, },
layoutModalClass () { layoutModalClass () {
return '-' + this.layoutType return '-' + this.layoutType
@ -106,7 +108,7 @@ export default {
} }
} }
}, },
shout () { return this.$store.state.shout.joined }, shout () { return useShoutStore().joined },
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 &&
@ -132,7 +134,7 @@ export default {
hideShoutbox () { hideShoutbox () {
return this.$store.getters.mergedConfig.hideShoutbox return this.$store.getters.mergedConfig.hideShoutbox
}, },
layoutType () { return this.$store.state.interface.layoutType }, layoutType () { return useInterfaceStore().layoutType },
privateMode () { return this.$store.state.instance.private }, privateMode () { return this.$store.state.instance.private },
reverseLayout () { reverseLayout () {
const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig
@ -148,8 +150,8 @@ export default {
}, },
methods: { methods: {
updateMobileState () { updateMobileState () {
this.$store.dispatch('setLayoutWidth', windowWidth()) useInterfaceStore().setLayoutWidth(windowWidth())
this.$store.dispatch('setLayoutHeight', windowHeight()) useInterfaceStore().setLayoutHeight(windowHeight())
}, },
removeSplash () { removeSplash () {
document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4)) document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4))

View file

@ -1,6 +1,6 @@
<template> <template>
<div <div
v-show="$store.state.interface.themeApplied" v-show="themeApplied"
id="app-loaded" id="app-loaded"
:style="bgStyle" :style="bgStyle"
> >

View file

@ -17,6 +17,10 @@ import { applyConfig } from '../services/style_setter/style_setter.js'
import FaviconService from '../services/favicon_service/favicon_service.js' import FaviconService from '../services/favicon_service/favicon_service.js'
import { initServiceWorker, updateFocus } from '../services/sw/sw.js' import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
import { useI18nStore } from '../stores/i18n'
import { useInterfaceStore } from '../stores/interface'
import { useAnnouncementsStore } from '../stores/announcements'
let staticInitialResults = null let staticInitialResults = null
const parsedInitialResults = () => { const parsedInitialResults = () => {
@ -333,9 +337,16 @@ const checkOAuthToken = async ({ store }) => {
return Promise.resolve() return Promise.resolve()
} }
const afterStoreSetup = async ({ store, i18n }) => { const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
store.dispatch('setLayoutWidth', windowWidth()) const app = createApp(App)
store.dispatch('setLayoutHeight', windowHeight()) app.use(pinia)
if (storageError) {
useInterfaceStore().pushGlobalNotice({ messageKey: 'errors.storage_unavailable', level: 'error' })
}
useInterfaceStore().setLayoutWidth(windowWidth())
useInterfaceStore().setLayoutHeight(windowHeight())
FaviconService.initFaviconService() FaviconService.initFaviconService()
initServiceWorker(store) initServiceWorker(store)
@ -348,7 +359,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
await setConfig({ store }) await setConfig({ store })
try { try {
await store.dispatch('applyTheme').catch((e) => { console.error('Error setting theme', e) }) await useInterfaceStore().applyTheme().catch((e) => { console.error('Error setting theme', e) })
} catch (e) { } catch (e) {
window.splashError(e) window.splashError(e)
return Promise.reject(e) return Promise.reject(e)
@ -369,7 +380,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
// Start fetching things that don't need to block the UI // Start fetching things that don't need to block the UI
store.dispatch('fetchMutes') store.dispatch('fetchMutes')
store.dispatch('startFetchingAnnouncements') useAnnouncementsStore().startFetchingAnnouncements()
getTOS({ store }) getTOS({ store })
getStickers({ store }) getStickers({ store })
@ -384,7 +395,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
} }
}) })
const app = createApp(App) useI18nStore().setI18n(i18n)
app.use(router) app.use(router)
app.use(store) app.use(store)
@ -392,9 +403,9 @@ const afterStoreSetup = async ({ store, i18n }) => {
// Little thing to get out of invalid theme state // Little thing to get out of invalid theme state
window.resetThemes = () => { window.resetThemes = () => {
store.dispatch('resetThemeV3') useInterfaceStore().resetThemeV3()
store.dispatch('resetThemeV3Palette') useInterfaceStore().resetThemeV3Palette()
store.dispatch('resetThemeV2') useInterfaceStore().resetThemeV2()
} }
app.use(vClickOutside) app.use(vClickOutside)

View file

@ -7,6 +7,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faEllipsisV faEllipsisV
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useReportsStore } from '../../stores/reports'
library.add( library.add(
faEllipsisV faEllipsisV
@ -73,7 +74,7 @@ const AccountActions = {
this.hideConfirmRemoveUserFromFollowers() this.hideConfirmRemoveUserFromFollowers()
}, },
reportUser () { reportUser () {
this.$store.dispatch('openUserReportingModal', { userId: this.user.id }) useReportsStore().openUserReportingModal({ userId: this.user.id })
}, },
openChat () { openChat () {
this.$router.push({ this.$router.push({

View file

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

View file

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

View file

@ -18,6 +18,7 @@ import {
faPencilAlt, faPencilAlt,
faAlignRight faAlignRight
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useMediaViewerStore } from '../../stores/media_viewer'
library.add( library.add(
faFile, faFile,
@ -147,14 +148,14 @@ const Attachment = {
openModal (event) { openModal (event) {
if (this.useModal) { if (this.useModal) {
this.$emit('setMedia') this.$emit('setMedia')
this.$store.dispatch('setCurrentMedia', this.attachment) useMediaViewerStore().setCurrentMedia(this.attachment)
} else if (this.type === 'unknown') { } else if (this.type === 'unknown') {
window.open(this.attachment.url) window.open(this.attachment.url)
} }
}, },
openModalForce (event) { openModalForce (event) {
this.$emit('setMedia') this.$emit('setMedia')
this.$store.dispatch('setCurrentMedia', this.attachment) useMediaViewerStore().setCurrentMedia(this.attachment)
}, },
onEdit (event) { onEdit (event) {
this.edit && this.edit(this.attachment, event) this.edit && this.edit(this.attachment, event)

View file

@ -63,7 +63,7 @@ const BookmarkFolderEdit = {
this.$router.push({ name: 'bookmark-folders' }) this.$router.push({ name: 'bookmark-folders' })
}) })
.catch((e) => { .catch((e) => {
this.$store.dispatch('pushGlobalNotice', { this.$store.useInterfaceStore().pushGlobalNotice({
messageKey: 'bookmark_folders.error', messageKey: 'bookmark_folders.error',
messageArgs: [e.message], messageArgs: [e.message],
level: 'error' level: 'error'

View file

@ -1,6 +1,7 @@
import _ from 'lodash' import _ from 'lodash'
import { WSConnectionStatus } from '../../services/api/api.service.js' import { WSConnectionStatus } from '../../services/api/api.service.js'
import { mapGetters, mapState } from 'vuex' import { mapGetters, mapState } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import ChatMessage from '../chat_message/chat_message.vue' import ChatMessage from '../chat_message/chat_message.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue' import PostStatusForm from '../post_status_form/post_status_form.vue'
import ChatTitle from '../chat_title/chat_title.vue' import ChatTitle from '../chat_title/chat_title.vue'
@ -13,6 +14,7 @@ import {
faChevronLeft faChevronLeft
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js' import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js'
import { useInterfaceStore } from '../../stores/interface.js'
library.add( library.add(
faChevronDown, faChevronDown,
@ -90,10 +92,12 @@ const Chat = {
'findOpenedChatByRecipientId', 'findOpenedChatByRecipientId',
'mergedConfig' 'mergedConfig'
]), ]),
...mapPiniaState(useInterfaceStore, {
mobileLayout: store => store.layoutType === 'mobile'
}),
...mapState({ ...mapState({
backendInteractor: state => state.api.backendInteractor, backendInteractor: state => state.api.backendInteractor,
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus, mastoUserSocketStatus: state => state.api.mastoUserSocketStatus,
mobileLayout: state => state.interface.layoutType === 'mobile',
currentUser: state => state.users.currentUser currentUser: state => state.users.currentUser
}) })
}, },

View file

@ -1,4 +1,5 @@
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import Popover from '../popover/popover.vue' import Popover from '../popover/popover.vue'
import Attachment from '../attachment/attachment.vue' import Attachment from '../attachment/attachment.vue'
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
@ -12,6 +13,7 @@ import {
faTimes, faTimes,
faEllipsisH faEllipsisH
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from '../../stores/interface'
library.add( library.add(
faTimes, faTimes,
@ -65,6 +67,9 @@ const ChatMessage = {
hasAttachment () { hasAttachment () {
return this.message.attachments.length > 0 return this.message.attachments.length > 0
}, },
...mapPiniaState(useInterfaceStore, {
betterShadow: store => store.browserSupport.cssFilter
}),
...mapState({ ...mapState({
currentUser: state => state.users.currentUser, currentUser: state => state.users.currentUser,
restrictedNicknames: state => state.instance.restrictedNicknames restrictedNicknames: state => state.instance.restrictedNicknames

View file

@ -3,8 +3,10 @@ import Status from '../status/status.vue'
import ThreadTree from '../thread_tree/thread_tree.vue' import ThreadTree from '../thread_tree/thread_tree.vue'
import { WSConnectionStatus } from '../../services/api/api.service.js' import { WSConnectionStatus } from '../../services/api/api.service.js'
import { mapGetters, mapState } from 'vuex' import { mapGetters, mapState } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import QuickFilterSettings from '../quick_filter_settings/quick_filter_settings.vue' import QuickFilterSettings from '../quick_filter_settings/quick_filter_settings.vue'
import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue' import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue'
import { useInterfaceStore } from 'src/stores/interface'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -350,8 +352,10 @@ const conversation = {
}, },
...mapGetters(['mergedConfig']), ...mapGetters(['mergedConfig']),
...mapState({ ...mapState({
mobileLayout: state => state.interface.layoutType === 'mobile',
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus mastoUserSocketStatus: state => state.api.mastoUserSocketStatus
}),
...mapPiniaState(useInterfaceStore, {
mobileLayout: store => store.layoutType === 'mobile'
}) })
}, },
components: { components: {

View file

@ -14,6 +14,7 @@ import {
faCog, faCog,
faInfoCircle faInfoCircle
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from '../../stores/interface'
library.add( library.add(
faSignInAlt, faSignInAlt,
@ -107,10 +108,10 @@ export default {
this.searchBarHidden = hidden this.searchBarHidden = hidden
}, },
openSettingsModal () { openSettingsModal () {
this.$store.dispatch('openSettingsModal', 'user') useInterfaceStore().openSettingsModal('user')
}, },
openAdminModal () { openAdminModal () {
this.$store.dispatch('openSettingsModal', 'admin') useInterfaceStore().openSettingsModal('admin')
} }
} }
} }

View file

@ -1,6 +1,7 @@
import EditStatusForm from '../edit_status_form/edit_status_form.vue' import EditStatusForm from '../edit_status_form/edit_status_form.vue'
import Modal from '../modal/modal.vue' import Modal from '../modal/modal.vue'
import get from 'lodash/get' import get from 'lodash/get'
import { useEditStatusStore } from '../../stores/editStatus'
const EditStatusModal = { const EditStatusModal = {
components: { components: {
@ -17,13 +18,13 @@ const EditStatusModal = {
return !!this.$store.state.users.currentUser return !!this.$store.state.users.currentUser
}, },
modalActivated () { modalActivated () {
return this.$store.state.editStatus.modalActivated return useEditStatusStore().modalActivated
}, },
isFormVisible () { isFormVisible () {
return this.isLoggedIn && !this.resettingForm && this.modalActivated return this.isLoggedIn && !this.resettingForm && this.modalActivated
}, },
params () { params () {
return this.$store.state.editStatus.params || {} return useEditStatusStore().params || {}
} }
}, },
watch: { watch: {
@ -46,7 +47,7 @@ const EditStatusModal = {
this.$refs.editStatusForm.requestClose() this.$refs.editStatusForm.requestClose()
}, },
doCloseModal () { doCloseModal () {
this.$store.dispatch('closeEditStatusModal') useEditStatusStore().closeEditStatusModal()
} }
} }
} }

View file

@ -1,4 +1,6 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import { useAnnouncementsStore } from '../../stores/announcements'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -7,6 +9,8 @@ import {
faBullhorn faBullhorn
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from '../../stores/interface'
library.add( library.add(
faUserPlus, faUserPlus,
faComments, faComments,
@ -33,11 +37,14 @@ const ExtraNotifications = {
currentUser () { currentUser () {
return this.$store.state.users.currentUser return this.$store.state.users.currentUser
}, },
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount', 'followRequestCount', 'mergedConfig']) ...mapGetters(['unreadChatCount', 'followRequestCount', 'mergedConfig']),
...mapPiniaState(useAnnouncementsStore, {
unreadAnnouncementCount: 'unreadAnnouncementCount'
})
}, },
methods: { methods: {
openNotificationSettings () { openNotificationSettings () {
return this.$store.dispatch('openSettingsModalTab', 'notifications') return useInterfaceStore().openSettingsModalTab('notifications')
}, },
dismissConfigurationTip () { dismissConfigurationTip () {
return this.$store.dispatch('setOption', { name: 'showExtraNotificationsTip', value: false }) return this.$store.dispatch('setOption', { name: 'showExtraNotificationsTip', value: false })

View file

@ -1,6 +1,7 @@
import Select from '../select/select.vue' import Select from '../select/select.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
import Popover from 'src/components/popover/popover.vue' import Popover from 'src/components/popover/popover.vue'
import { useInterfaceStore } from 'src/stores/interface'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -25,7 +26,7 @@ export default {
'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit' 'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'
], ],
mounted () { mounted () {
this.$store.dispatch('queryLocalFonts') useInterfaceStore().queryLocalFonts()
}, },
emits: ['update:modelValue'], emits: ['update:modelValue'],
data () { data () {
@ -50,10 +51,10 @@ export default {
return typeof this.modelValue !== 'undefined' return typeof this.modelValue !== 'undefined'
}, },
localFontsList () { localFontsList () {
return this.$store.state.interface.localFonts return useInterfaceStore().localFonts
}, },
localFontsSize () { localFontsSize () {
return this.$store.state.interface.localFonts?.length return useInterfaceStore().localFonts?.length
} }
} }
} }

View file

@ -1,3 +1,4 @@
import { useMediaViewerStore } from '../../stores/media_viewer'
import Attachment from '../attachment/attachment.vue' import Attachment from '../attachment/attachment.vue'
import { sumBy, set } from 'lodash' import { sumBy, set } from 'lodash'
@ -107,11 +108,11 @@ const Gallery = {
this.hidingLong = event this.hidingLong = event
}, },
openGallery () { openGallery () {
this.$store.dispatch('setMedia', this.attachments) useMediaViewerStore().setMedia(this.attachments)
this.$store.dispatch('setCurrentMedia', this.attachments[0]) useMediaViewerStore().setCurrentMedia(this.attachments[0])
}, },
onMedia () { onMedia () {
this.$store.dispatch('setMedia', this.attachments) useMediaViewerStore().setMedia(this.attachments)
} }
} }
} }

View file

@ -2,6 +2,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faTimes faTimes
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from '../../stores/interface'
library.add( library.add(
faTimes faTimes
@ -10,12 +11,12 @@ library.add(
const GlobalNoticeList = { const GlobalNoticeList = {
computed: { computed: {
notices () { notices () {
return this.$store.state.interface.globalNotices return useInterfaceStore().globalNotices
} }
}, },
methods: { methods: {
closeNotice (notice) { closeNotice (notice) {
this.$store.dispatch('removeGlobalNotice', notice) useInterfaceStore().removeGlobalNotice(notice)
} }
} }
} }

View file

@ -1,3 +1,4 @@
import { useListsStore } from '../../stores/lists'
import ListsCard from '../lists_card/lists_card.vue' import ListsCard from '../lists_card/lists_card.vue'
const Lists = { const Lists = {
@ -11,7 +12,7 @@ const Lists = {
}, },
computed: { computed: {
lists () { lists () {
return this.$store.state.lists.allLists return useListsStore().allLists
} }
}, },
methods: { methods: {

View file

@ -1,4 +1,5 @@
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import BasicUserCard from '../basic_user_card/basic_user_card.vue' import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import ListsUserSearch from '../lists_user_search/lists_user_search.vue' import ListsUserSearch from '../lists_user_search/lists_user_search.vue'
import PanelLoading from 'src/components/panel_loading/panel_loading.vue' import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
@ -9,6 +10,8 @@ import {
faSearch, faSearch,
faChevronLeft faChevronLeft
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from '../../stores/interface'
import { useListsStore } from '../../stores/lists'
library.add( library.add(
faSearch, faSearch,
@ -37,12 +40,12 @@ const ListsNew = {
}, },
created () { created () {
if (!this.id) return if (!this.id) return
this.$store.dispatch('fetchList', { listId: this.id }) useListsStore().fetchList({ listId: this.id })
.then(() => { .then(() => {
this.title = this.findListTitle(this.id) this.title = this.findListTitle(this.id)
this.titleDraft = this.title this.titleDraft = this.title
}) })
this.$store.dispatch('fetchListAccounts', { listId: this.id }) useListsStore().fetchListAccounts({ listId: this.id })
.then(() => { .then(() => {
this.membersUserIds = this.findListAccounts(this.id) this.membersUserIds = this.findListAccounts(this.id)
this.membersUserIds.forEach(userId => { this.membersUserIds.forEach(userId => {
@ -64,7 +67,8 @@ const ListsNew = {
...mapState({ ...mapState({
currentUser: state => state.users.currentUser currentUser: state => state.users.currentUser
}), }),
...mapGetters(['findUser', 'findListTitle', 'findListAccounts']) ...mapPiniaState(useListsStore, ['findListTitle', 'findListAccounts']),
...mapGetters(['findUser'])
}, },
methods: { methods: {
onInput () { onInput () {
@ -95,10 +99,10 @@ const ListsNew = {
return this.addedUserIds.has(user.id) return this.addedUserIds.has(user.id)
}, },
addUser (user) { addUser (user) {
this.$store.dispatch('addListAccount', { accountId: user.id, listId: this.id }) useListsStore().addListAccount({ accountId: user.id, listId: this.id })
}, },
removeUser (userId) { removeUser (userId) {
this.$store.dispatch('removeListAccount', { accountId: userId, listId: this.id }) useListsStore().removeListAccount({ accountId: userId, listId: this.id })
}, },
onSearchLoading (results) { onSearchLoading (results) {
this.searchLoading = true this.searchLoading = true
@ -111,24 +115,23 @@ const ListsNew = {
this.searchUserIds = results this.searchUserIds = results
}, },
updateListTitle () { updateListTitle () {
this.$store.dispatch('setList', { listId: this.id, title: this.titleDraft }) useListsStore().setList({ listId: this.id, title: this.titleDraft })
.then(() => { .then(() => {
this.title = this.findListTitle(this.id) this.title = this.findListTitle(this.id)
}) })
}, },
createList () { createList () {
this.$store.dispatch('createList', { title: this.titleDraft }) useListsStore().createList({ title: this.titleDraft })
.then((list) => { .then((list) => {
return this return useListsStore()
.$store .setListAccounts({ listId: list.id, accountIds: [...this.addedUserIds] })
.dispatch('setListAccounts', { listId: list.id, accountIds: [...this.addedUserIds] })
.then(() => list.id) .then(() => list.id)
}) })
.then((listId) => { .then((listId) => {
this.$router.push({ name: 'lists-timeline', params: { id: listId } }) this.$router.push({ name: 'lists-timeline', params: { id: listId } })
}) })
.catch((e) => { .catch((e) => {
this.$store.dispatch('pushGlobalNotice', { useInterfaceStore().pushGlobalNotice({
messageKey: 'lists.error', messageKey: 'lists.error',
messageArgs: [e.message], messageArgs: [e.message],
level: 'error' level: 'error'
@ -136,7 +139,7 @@ const ListsNew = {
}) })
}, },
deleteList () { deleteList () {
this.$store.dispatch('deleteList', { listId: this.id }) useListsStore().deleteList({ listId: this.id })
this.$router.push({ name: 'lists' }) this.$router.push({ name: 'lists' })
} }
} }

View file

@ -1,6 +1,8 @@
import { mapState } from 'vuex' import { mapState } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import { getListEntries } from 'src/components/navigation/filter.js' import { getListEntries } from 'src/components/navigation/filter.js'
import { useListsStore } from '../../stores/lists'
export const ListsMenuContent = { export const ListsMenuContent = {
props: [ props: [
@ -10,8 +12,10 @@ export const ListsMenuContent = {
NavigationEntry NavigationEntry
}, },
computed: { computed: {
...mapPiniaState(useListsStore, {
lists: getListEntries
}),
...mapState({ ...mapState({
lists: getListEntries,
currentUser: state => state.users.currentUser, currentUser: state => state.users.currentUser,
privateMode: state => state.instance.private, privateMode: state => state.instance.private,
federating: state => state.instance.federating federating: state => state.instance.federating

View file

@ -1,3 +1,4 @@
import { useListsStore } from '../../stores/lists'
import Timeline from '../timeline/timeline.vue' import Timeline from '../timeline/timeline.vue'
const ListsTimeline = { const ListsTimeline = {
data () { data () {
@ -17,14 +18,14 @@ const ListsTimeline = {
this.listId = route.params.id this.listId = route.params.id
this.$store.dispatch('stopFetchingTimeline', 'list') this.$store.dispatch('stopFetchingTimeline', 'list')
this.$store.commit('clearTimeline', { timeline: 'list' }) this.$store.commit('clearTimeline', { timeline: 'list' })
this.$store.dispatch('fetchList', { listId: this.listId }) useListsStore().fetchList({ listId: this.listId })
this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId }) this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
} }
} }
}, },
created () { created () {
this.listId = this.$route.params.id this.listId = this.$route.params.id
this.$store.dispatch('fetchList', { listId: this.listId }) useListsStore().fetchList({ listId: this.listId })
this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId }) this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
}, },
unmounted () { unmounted () {

View file

@ -13,6 +13,7 @@ import {
faCircleNotch, faCircleNotch,
faTimes faTimes
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useMediaViewerStore } from '../../stores/media_viewer'
library.add( library.add(
faChevronLeft, faChevronLeft,
@ -44,16 +45,16 @@ const MediaModal = {
}, },
computed: { computed: {
showing () { showing () {
return this.$store.state.mediaViewer.activated return useMediaViewerStore().activated
}, },
media () { media () {
return this.$store.state.mediaViewer.media return useMediaViewerStore().media
}, },
description () { description () {
return this.currentMedia.description return this.currentMedia.description
}, },
currentIndex () { currentIndex () {
return this.$store.state.mediaViewer.currentIndex return useMediaViewerStore().currentIndex
}, },
currentMedia () { currentMedia () {
return this.media[this.currentIndex] return this.media[this.currentIndex]
@ -79,7 +80,7 @@ const MediaModal = {
// to be processed on the content below the overlay // to be processed on the content below the overlay
const transitionTime = 100 // ms const transitionTime = 100 // ms
setTimeout(() => { setTimeout(() => {
this.$store.dispatch('closeMediaViewer') useMediaViewerStore().closeMediaViewer()
}, transitionTime) }, transitionTime)
}, },
hideIfNotSwiped (event) { hideIfNotSwiped (event) {
@ -98,7 +99,7 @@ const MediaModal = {
if (this.getType(newMedia) === 'image') { if (this.getType(newMedia) === 'image') {
this.loading = true this.loading = true
} }
this.$store.dispatch('setCurrentMedia', newMedia) useMediaViewerStore().setCurrentMedia(newMedia)
} }
}, },
goNext () { goNext () {
@ -108,7 +109,7 @@ const MediaModal = {
if (this.getType(newMedia) === 'image') { if (this.getType(newMedia) === 'image') {
this.loading = true this.loading = true
} }
this.$store.dispatch('setCurrentMedia', newMedia) useMediaViewerStore().setCurrentMedia(newMedia)
} }
}, },
onImageLoaded () { onImageLoaded () {

View file

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

View file

@ -3,6 +3,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faPen faPen
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { usePostStatusStore } from '../../stores/postStatus'
library.add( library.add(
faPen faPen
@ -71,7 +72,7 @@ const MobilePostStatusButton = {
window.removeEventListener('scroll', this.handleScrollEnd) window.removeEventListener('scroll', this.handleScrollEnd)
}, },
openPostForm () { openPostForm () {
this.$store.dispatch('openPostStatusModal') usePostStatusStore().openPostStatusModal()
}, },
handleOSK () { handleOSK () {
// This is a big hack: we're guessing from changed window sizes if the // This is a big hack: we're guessing from changed window sizes if the

View file

@ -1,11 +1,13 @@
import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue' import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue'
import ListsMenuContent from 'src/components/lists_menu/lists_menu_content.vue' import ListsMenuContent from 'src/components/lists_menu/lists_menu_content.vue'
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import { TIMELINES, ROOT_ITEMS } from 'src/components/navigation/navigation.js' import { TIMELINES, ROOT_ITEMS } from 'src/components/navigation/navigation.js'
import { filterNavigation } from 'src/components/navigation/filter.js' import { filterNavigation } from 'src/components/navigation/filter.js'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import NavigationPins from 'src/components/navigation/navigation_pins.vue' import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
import { useAnnouncementsStore } from 'src/stores/announcements'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -90,13 +92,16 @@ const NavPanel = {
} }
}, },
computed: { computed: {
...mapPiniaState(useAnnouncementsStore, {
unreadAnnouncementCount: 'unreadAnnouncementCount',
supportsAnnouncements: store => store.supportsAnnouncements
}),
...mapState({ ...mapState({
currentUser: state => state.users.currentUser, currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length, followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private, privateMode: state => state.instance.private,
federating: state => state.instance.federating, federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
supportsAnnouncements: state => state.announcements.supportsAnnouncements,
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, collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav,
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable
@ -131,7 +136,7 @@ const NavPanel = {
} }
) )
}, },
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount']) ...mapGetters(['unreadChatCount'])
} }
} }

View file

@ -12,7 +12,7 @@ export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFede
}) })
} }
export const getListEntries = state => state.lists.allLists.map(list => ({ export const getListEntries = store => store.allLists.map(list => ({
name: 'list-' + list.id, name: 'list-' + list.id,
routeObject: { name: 'lists-timeline', params: { id: list.id } }, routeObject: { name: 'lists-timeline', params: { id: list.id } },
labelRaw: list.title, labelRaw: list.title,

View file

@ -84,6 +84,7 @@ export const ROOT_ITEMS = {
route: 'announcements', route: 'announcements',
icon: 'bullhorn', icon: 'bullhorn',
label: 'nav.announcements', label: 'nav.announcements',
store: 'announcements',
badgeStyle: 'notification', badgeStyle: 'notification',
badgeGetter: 'unreadAnnouncementCount', badgeGetter: 'unreadAnnouncementCount',
criteria: ['announcements'] 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 OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faThumbtack } from '@fortawesome/free-solid-svg-icons' import { faThumbtack } from '@fortawesome/free-solid-svg-icons'
import { mapStores } from 'pinia'
import { useAnnouncementsStore } from '../../stores/announcements'
library.add(faThumbtack) library.add(faThumbtack)
@ -31,6 +33,7 @@ const NavigationEntry = {
getters () { getters () {
return this.$store.getters return this.$store.getters
}, },
...mapStores(useAnnouncementsStore),
...mapState({ ...mapState({
currentUser: state => state.users.currentUser, currentUser: state => state.users.currentUser,
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems) pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)

View file

@ -53,6 +53,12 @@
> >
{{ getters[item.badgeGetter] }} {{ getters[item.badgeGetter] }}
</div> </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 <button
v-if="showPin && currentUser" v-if="showPin && currentUser"
type="button" type="button"

View file

@ -1,4 +1,5 @@
import { mapState } from 'vuex' import { mapState } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import { TIMELINES, ROOT_ITEMS, routeTo } from 'src/components/navigation/navigation.js' import { TIMELINES, ROOT_ITEMS, routeTo } from 'src/components/navigation/navigation.js'
import { getBookmarkFolderEntries, getListEntries, filterNavigation } from 'src/components/navigation/filter.js' import { getBookmarkFolderEntries, getListEntries, filterNavigation } from 'src/components/navigation/filter.js'
@ -16,6 +17,8 @@ import {
faStream, faStream,
faList faList
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useListsStore } from 'src/stores/lists'
import { useAnnouncementsStore } from 'src/stores/announcements'
library.add( library.add(
faUsers, faUsers,
@ -43,15 +46,19 @@ const NavPanel = {
getters () { getters () {
return this.$store.getters return this.$store.getters
}, },
...mapPiniaState(useListsStore, {
lists: getListEntries
}),
...mapPiniaState(useAnnouncementsStore, {
supportsAnnouncements: store => store.supportsAnnouncements
}),
...mapState({ ...mapState({
lists: getListEntries,
bookmarks: getBookmarkFolderEntries, bookmarks: getBookmarkFolderEntries,
currentUser: state => state.users.currentUser, currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length, followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private, privateMode: state => state.instance.private,
federating: state => state.instance.federating, federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
supportsAnnouncements: state => state.announcements.supportsAnnouncements,
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems) pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)
}), }),
pinnedList () { pinnedList () {

View file

@ -1,5 +1,6 @@
import { computed } from 'vue' import { computed } from 'vue'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { mapState } from 'pinia'
import Notification from '../notification/notification.vue' import Notification from '../notification/notification.vue'
import ExtraNotifications from '../extra_notifications/extra_notifications.vue' import ExtraNotifications from '../extra_notifications/extra_notifications.vue'
import NotificationFilters from './notification_filters.vue' import NotificationFilters from './notification_filters.vue'
@ -14,6 +15,8 @@ import {
import FaviconService from '../../services/favicon_service/favicon_service.js' import FaviconService from '../../services/favicon_service/favicon_service.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch, faArrowUp, faMinus } from '@fortawesome/free-solid-svg-icons' import { faCircleNotch, faArrowUp, faMinus } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from '../../stores/interface'
import { useAnnouncementsStore } from '../../stores/announcements'
library.add( library.add(
faCircleNotch, faCircleNotch,
@ -98,11 +101,11 @@ const Notifications = {
return this.$store.state.notifications.loading return this.$store.state.notifications.loading
}, },
noHeading () { noHeading () {
const { layoutType } = this.$store.state.interface const { layoutType } = useInterfaceStore()
return this.minimalMode || layoutType === 'mobile' return this.minimalMode || layoutType === 'mobile'
}, },
teleportTarget () { teleportTarget () {
const { layoutType } = this.$store.state.interface const { layoutType } = useInterfaceStore()
const map = { const map = {
wide: '#notifs-column', wide: '#notifs-column',
mobile: '#mobile-notifications' mobile: '#mobile-notifications'
@ -110,7 +113,7 @@ const Notifications = {
return map[layoutType] || '#notifs-sidebar' return map[layoutType] || '#notifs-sidebar'
}, },
popoversZLayer () { popoversZLayer () {
const { layoutType } = this.$store.state.interface const { layoutType } = useInterfaceStore()
return layoutType === 'mobile' ? 'navbar' : null return layoutType === 'mobile' ? 'navbar' : null
}, },
notificationsToDisplay () { notificationsToDisplay () {
@ -121,7 +124,8 @@ const Notifications = {
showExtraNotifications () { showExtraNotifications () {
return !this.noExtra return !this.noExtra
}, },
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount']) ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
...mapGetters(['unreadChatCount'])
}, },
mounted () { mounted () {
this.scrollerRef = this.$refs.root.closest('.column.-scrollable') this.scrollerRef = this.$refs.root.closest('.column.-scrollable')
@ -141,10 +145,10 @@ const Notifications = {
unseenCountTitle (count) { unseenCountTitle (count) {
if (count > 0) { if (count > 0) {
FaviconService.drawFaviconBadge() FaviconService.drawFaviconBadge()
this.$store.dispatch('setPageTitle', `(${count})`) useInterfaceStore().setPageTitle(`(${count})`)
} else { } else {
FaviconService.clearFaviconBadge() FaviconService.clearFaviconBadge()
this.$store.dispatch('setPageTitle', '') useInterfaceStore().setPageTitle('')
} }
}, },
teleportTarget () { teleportTarget () {

View file

@ -2,6 +2,7 @@ import Timeago from 'components/timeago/timeago.vue'
import genRandomSeed from '../../services/random_seed/random_seed.service.js' import genRandomSeed from '../../services/random_seed/random_seed.service.js'
import RichContent from 'components/rich_content/rich_content.jsx' import RichContent from 'components/rich_content/rich_content.jsx'
import { forEach, map } from 'lodash' import { forEach, map } from 'lodash'
import { usePollsStore } from '../../stores/polls'
export default { export default {
name: 'Poll', name: 'Poll',
@ -18,20 +19,20 @@ export default {
} }
}, },
created () { created () {
if (!this.$store.state.polls.pollsObject[this.pollId]) { if (!usePollsStore().pollsObject[this.pollId]) {
this.$store.dispatch('mergeOrAddPoll', this.basePoll) usePollsStore().mergeOrAddPoll(this.basePoll)
} }
this.$store.dispatch('trackPoll', this.pollId) usePollsStore().trackPoll(this.pollId)
}, },
unmounted () { unmounted () {
this.$store.dispatch('untrackPoll', this.pollId) usePollsStore().untrackPoll(this.pollId)
}, },
computed: { computed: {
pollId () { pollId () {
return this.basePoll.id return this.basePoll.id
}, },
poll () { poll () {
const storePoll = this.$store.state.polls.pollsObject[this.pollId] const storePoll = usePollsStore().pollsObject[this.pollId]
return storePoll || {} return storePoll || {}
}, },
options () { options () {
@ -77,9 +78,6 @@ export default {
resultTitle (option) { resultTitle (option) {
return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}` return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}`
}, },
fetchPoll () {
this.$store.dispatch('refreshPoll', { id: this.statusId, pollId: this.poll.id })
},
activateOption (index) { activateOption (index) {
// forgive me father: doing checking the radio/checkboxes // forgive me father: doing checking the radio/checkboxes
// in code because of customized input elements need either // in code because of customized input elements need either
@ -107,8 +105,7 @@ export default {
vote () { vote () {
if (this.choiceIndices.length === 0) return if (this.choiceIndices.length === 0) return
this.loading = true this.loading = true
this.$store.dispatch( usePollsStore().votePoll(
'votePoll',
{ id: this.statusId, pollId: this.poll.id, choices: this.choiceIndices } { id: this.statusId, pollId: this.poll.id, choices: this.choiceIndices }
).then(poll => { ).then(poll => {
this.loading = false this.loading = false

View file

@ -14,7 +14,8 @@ import { propsToNative } from '../../services/attributes_helper/attributes_helpe
import { pollFormToMasto } from 'src/services/poll/poll.service.js' import { pollFormToMasto } from 'src/services/poll/poll.service.js'
import { reject, map, uniqBy, debounce } from 'lodash' import { reject, map, uniqBy, debounce } from 'lodash'
import suggestor from '../emoji_input/suggestor.js' import suggestor from '../emoji_input/suggestor.js'
import { mapGetters, mapState } from 'vuex' import { mapGetters } from 'vuex'
import { mapState, mapActions } from 'pinia'
import Checkbox from '../checkbox/checkbox.vue' import Checkbox from '../checkbox/checkbox.vue'
import Select from '../select/select.vue' import Select from '../select/select.vue'
import DraftCloser from 'src/components/draft_closer/draft_closer.vue' import DraftCloser from 'src/components/draft_closer/draft_closer.vue'
@ -32,6 +33,9 @@ import {
faChevronRight faChevronRight
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useMediaViewerStore } from 'src/stores/media_viewer.js'
library.add( library.add(
faSmileBeam, faSmileBeam,
faPollH, faPollH,
@ -367,8 +371,8 @@ const PostStatusForm = {
) && this.saveable ) && this.saveable
}, },
...mapGetters(['mergedConfig']), ...mapGetters(['mergedConfig']),
...mapState({ ...mapState(useInterfaceStore, {
mobileLayout: state => state.interface.mobileLayout mobileLayout: store => store.mobileLayout
}) })
}, },
watch: { watch: {
@ -393,6 +397,7 @@ const PostStatusForm = {
this.removeBeforeUnloadListener() this.removeBeforeUnloadListener()
}, },
methods: { methods: {
...mapActions(useMediaViewerStore, ['increment']),
statusChanged () { statusChanged () {
this.autoPreview() this.autoPreview()
this.updateIdempotencyKey() this.updateIdempotencyKey()
@ -753,7 +758,7 @@ const PostStatusForm = {
this.idempotencyKey = Date.now().toString() this.idempotencyKey = Date.now().toString()
}, },
openProfileTab () { openProfileTab () {
this.$store.dispatch('openSettingsModalTab', 'profile') useInterfaceStore().openSettingsModalTab('profile')
}, },
propsToNative (props) { propsToNative (props) {
return propsToNative(props) return propsToNative(props)

View file

@ -386,7 +386,7 @@
:nsfw="false" :nsfw="false"
:attachments="newStatus.files" :attachments="newStatus.files"
:descriptions="newStatus.mediaDescriptions" :descriptions="newStatus.mediaDescriptions"
:set-media="() => $store.dispatch('setMedia', newStatus.files)" :set-media="() => setMedia()"
:editable="true" :editable="true"
:edit-attachment="editAttachment" :edit-attachment="editAttachment"
:remove-attachment="removeMediaFile" :remove-attachment="removeMediaFile"

View file

@ -1,6 +1,7 @@
import PostStatusForm from '../post_status_form/post_status_form.vue' import PostStatusForm from '../post_status_form/post_status_form.vue'
import Modal from '../modal/modal.vue' import Modal from '../modal/modal.vue'
import get from 'lodash/get' import get from 'lodash/get'
import { usePostStatusStore } from '../../stores/postStatus'
const PostStatusModal = { const PostStatusModal = {
components: { components: {
@ -17,13 +18,13 @@ const PostStatusModal = {
return !!this.$store.state.users.currentUser return !!this.$store.state.users.currentUser
}, },
modalActivated () { modalActivated () {
return this.$store.state.postStatus.modalActivated return usePostStatusStore().modalActivated
}, },
isFormVisible () { isFormVisible () {
return this.isLoggedIn && !this.resettingForm && this.modalActivated return this.isLoggedIn && !this.resettingForm && this.modalActivated
}, },
params () { params () {
return this.$store.state.postStatus.params || {} return usePostStatusStore().params || {}
} }
}, },
watch: { watch: {
@ -43,11 +44,11 @@ const PostStatusModal = {
}, },
methods: { methods: {
closeModal () { closeModal () {
this.$store.dispatch('closePostStatusModal') usePostStatusStore().closePostStatusModal()
}, },
resetAndClose () { resetAndClose () {
this.$store.dispatch('resetPostStatusModal') usePostStatusStore().resetPostStatusModal()
this.$store.dispatch('closePostStatusModal') usePostStatusStore().closePostStatusModal()
} }
} }
} }

View file

@ -1,7 +1,9 @@
import Popover from '../popover/popover.vue' import Popover from '../popover/popover.vue'
import { mapGetters, mapState } from 'vuex' import { mapGetters } from 'vuex'
import { mapState } from 'pinia'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons' import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from '../../stores/interface'
library.add( library.add(
faFilter, faFilter,
@ -23,13 +25,13 @@ const QuickFilterSettings = {
this.$store.dispatch('queueFlushAll') this.$store.dispatch('queueFlushAll')
}, },
openTab (tab) { openTab (tab) {
this.$store.dispatch('openSettingsModalTab', tab) useInterfaceStore().openSettingsModalTab(tab)
} }
}, },
computed: { computed: {
...mapGetters(['mergedConfig']), ...mapGetters(['mergedConfig']),
...mapState({ ...mapState(useInterfaceStore, {
mobileLayout: state => state.interface.layoutType === 'mobile' mobileLayout: state => state.layoutType === 'mobile'
}), }),
triggerAttrs () { triggerAttrs () {
if (this.mobileLayout) { if (this.mobileLayout) {

View file

@ -1,8 +1,10 @@
import Popover from 'src/components/popover/popover.vue' import Popover from 'src/components/popover/popover.vue'
import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filter_settings.vue' import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filter_settings.vue'
import { mapGetters, mapState } from 'vuex' import { mapGetters } from 'vuex'
import { mapState } from 'pinia'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faList, faFolderTree, faBars, faWrench } from '@fortawesome/free-solid-svg-icons' import { faList, faFolderTree, faBars, faWrench } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from '../../stores/interface'
library.add( library.add(
faList, faList,
@ -24,13 +26,13 @@ const QuickViewSettings = {
this.$store.dispatch('setOption', { name: 'conversationDisplay', value: visibility }) this.$store.dispatch('setOption', { name: 'conversationDisplay', value: visibility })
}, },
openTab (tab) { openTab (tab) {
this.$store.dispatch('openSettingsModalTab', tab) useInterfaceStore().openSettingsModalTab(tab)
} }
}, },
computed: { computed: {
...mapGetters(['mergedConfig']), ...mapGetters(['mergedConfig']),
...mapState({ ...mapState(useInterfaceStore, {
mobileLayout: state => state.interface.layoutType === 'mobile' mobileLayout: state => state.layoutType === 'mobile'
}), }),
loggedIn () { loggedIn () {
return !!this.$store.state.users.currentUser return !!this.$store.state.users.currentUser

View file

@ -1,3 +1,4 @@
import { useReportsStore } from '../../stores/reports'
import Select from '../select/select.vue' import Select from '../select/select.vue'
import StatusContent from '../status_content/status_content.vue' import StatusContent from '../status_content/status_content.vue'
import Timeago from '../timeago/timeago.vue' import Timeago from '../timeago/timeago.vue'
@ -16,7 +17,7 @@ const Report = {
}, },
computed: { computed: {
report () { report () {
return this.$store.state.reports.reports[this.reportId] || {} return useReportsStore().reports[this.reportId] || {}
}, },
state: { state: {
get: function () { return this.report.state }, get: function () { return this.report.state },
@ -28,7 +29,7 @@ const Report = {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames) return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
}, },
setReportState (state) { setReportState (state) {
return this.$store.dispatch('setReportState', { id: this.report.id, state }) return useReportsStore().setReportState({ id: this.report.id, state })
} }
} }
} }

View file

@ -232,7 +232,7 @@ const EmojiTab = {
}) })
}, },
displayError (msg) { displayError (msg) {
this.$store.dispatch('pushGlobalNotice', { this.$store.useInterfaceStore().pushGlobalNotice({
messageKey: 'admin_dash.emoji.error', messageKey: 'admin_dash.emoji.error',
messageArgs: [msg], messageArgs: [msg],
level: 'error' level: 'error'

View file

@ -80,7 +80,7 @@ const FrontendsTab = {
this.$store.dispatch('loadFrontendsStuff') this.$store.dispatch('loadFrontendsStuff')
if (response.error) { if (response.error) {
const reason = await response.error.json() const reason = await response.error.json()
this.$store.dispatch('pushGlobalNotice', { this.$store.useInterfaceStore().pushGlobalNotice({
level: 'error', level: 'error',
messageKey: 'admin_dash.frontend.failure_installing_frontend', messageKey: 'admin_dash.frontend.failure_installing_frontend',
messageArgs: { messageArgs: {
@ -90,7 +90,7 @@ const FrontendsTab = {
timeout: 5000 timeout: 5000
}) })
} else { } else {
this.$store.dispatch('pushGlobalNotice', { this.$store.useInterfaceStore().pushGlobalNotice({
level: 'success', level: 'success',
messageKey: 'admin_dash.frontend.success_installing_frontend', messageKey: 'admin_dash.frontend.success_installing_frontend',
messageArgs: { messageArgs: {

View file

@ -7,6 +7,7 @@ import Checkbox from 'src/components/checkbox/checkbox.vue'
import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue' import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { cloneDeep, isEqual } from 'lodash' import { cloneDeep, isEqual } from 'lodash'
import { mapState as mapPiniaState } from 'pinia'
import { import {
newImporter, newImporter,
newExporter newExporter
@ -20,6 +21,7 @@ import {
import { import {
faWindowMinimize faWindowMinimize
} from '@fortawesome/free-regular-svg-icons' } from '@fortawesome/free-regular-svg-icons'
import { useInterfaceStore } from '../../stores/interface'
const PLEROMAFE_SETTINGS_MAJOR_VERSION = 1 const PLEROMAFE_SETTINGS_MAJOR_VERSION = 1
const PLEROMAFE_SETTINGS_MINOR_VERSION = 0 const PLEROMAFE_SETTINGS_MINOR_VERSION = 0
@ -74,10 +76,10 @@ const SettingsModal = {
}, },
methods: { methods: {
closeModal () { closeModal () {
this.$store.dispatch('closeSettingsModal') useInterfaceStore().closeSettingsModal()
}, },
peekModal () { peekModal () {
this.$store.dispatch('togglePeekSettingsModal') useInterfaceStore().togglePeekSettingsModal()
}, },
importValidator (data) { importValidator (data) {
if (!Array.isArray(data._pleroma_settings_version)) { if (!Array.isArray(data._pleroma_settings_version)) {
@ -109,7 +111,7 @@ const SettingsModal = {
} }
if (minor > PLEROMAFE_SETTINGS_MINOR_VERSION) { if (minor > PLEROMAFE_SETTINGS_MINOR_VERSION) {
this.$store.dispatch('pushGlobalNotice', { useInterfaceStore().pushGlobalNotice({
level: 'warning', level: 'warning',
messageKey: 'settings.file_export_import.errors.file_slightly_new' messageKey: 'settings.file_export_import.errors.file_slightly_new'
}) })
@ -119,9 +121,9 @@ const SettingsModal = {
}, },
onImportFailure (result) { onImportFailure (result) {
if (result.error) { if (result.error) {
this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_settings_imported', level: 'error' }) useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_settings_imported', level: 'error' })
} else { } else {
this.$store.dispatch('pushGlobalNotice', { ...result.validationResult, level: 'error' }) useInterfaceStore().pushGlobalNotice({ ...result.validationResult, level: 'error' })
} }
}, },
onImport (data) { onImport (data) {
@ -166,24 +168,15 @@ const SettingsModal = {
} }
}, },
computed: { computed: {
currentSaveStateNotice () { ...mapPiniaState(useInterfaceStore, {
return this.$store.state.interface.settings.currentSaveStateNotice temporaryChangesTimeoutId: store => store.layoutType === 'mobile',
}, currentSaveStateNotice: store => store.settings.currentSaveStateNotice,
modalActivated () { modalActivated: store => store.settingsModalState !== 'hidden',
return this.$store.state.interface.settingsModalState !== 'hidden' modalMode: store => store.settingsModalMode,
}, modalOpenedOnceUser: store => store.settingsModalLoadedUser,
modalMode () { modalOpenedOnceAdmin: store => store.settingsModalLoadedAdmin,
return this.$store.state.interface.settingsModalMode modalPeeked: store => store.settingsModalState === 'minimized'
}, }),
modalOpenedOnceUser () {
return this.$store.state.interface.settingsModalLoadedUser
},
modalOpenedOnceAdmin () {
return this.$store.state.interface.settingsModalLoadedAdmin
},
modalPeeked () {
return this.$store.state.interface.settingsModalState === 'minimized'
},
expertLevel: { expertLevel: {
get () { get () {
return this.$store.state.config.expertLevel > 0 return this.$store.state.config.expertLevel > 0

View file

@ -158,7 +158,7 @@
</div> </div>
<teleport to="#modal"> <teleport to="#modal">
<ConfirmModal <ConfirmModal
v-if="$store.state.interface.temporaryChangesTimeoutId" v-if="temporaryChangesTimeoutId"
:title="$t('settings.confirm_new_setting')" :title="$t('settings.confirm_new_setting')"
:cancel-text="$t('settings.revert')" :cancel-text="$t('settings.revert')"
:confirm-text="$t('settings.confirm')" :confirm-text="$t('settings.confirm')"

View file

@ -4,6 +4,7 @@ import InstanceTab from './admin_tabs/instance_tab.vue'
import LimitsTab from './admin_tabs/limits_tab.vue' import LimitsTab from './admin_tabs/limits_tab.vue'
import FrontendsTab from './admin_tabs/frontends_tab.vue' import FrontendsTab from './admin_tabs/frontends_tab.vue'
import EmojiTab from './admin_tabs/emoji_tab.vue' import EmojiTab from './admin_tabs/emoji_tab.vue'
import { useInterfaceStore } from 'src/stores/interface'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -45,10 +46,10 @@ const SettingsModalAdminContent = {
return !!this.$store.state.users.currentUser return !!this.$store.state.users.currentUser
}, },
open () { open () {
return this.$store.state.interface.settingsModalState !== 'hidden' return useInterfaceStore().settingsModalState !== 'hidden'
}, },
bodyLock () { bodyLock () {
return this.$store.state.interface.settingsModalState === 'visible' return useInterfaceStore().settingsModalState === 'visible'
}, },
adminDbLoaded () { adminDbLoaded () {
return this.$store.state.adminSettings.loaded return this.$store.state.adminSettings.loaded
@ -67,7 +68,7 @@ const SettingsModalAdminContent = {
}, },
methods: { methods: {
onOpen () { onOpen () {
const targetTab = this.$store.state.interface.settingsModalTargetTab const targetTab = useInterfaceStore().settingsModalTargetTab
// We're being told to open in specific tab // We're being told to open in specific tab
if (targetTab) { if (targetTab) {
const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => { const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => {
@ -79,7 +80,7 @@ const SettingsModalAdminContent = {
} }
// Clear the state of target tab, so that next time settings is opened // Clear the state of target tab, so that next time settings is opened
// it doesn't force it. // it doesn't force it.
this.$store.dispatch('clearSettingsModalTargetTab') useInterfaceStore().clearSettingsModalTargetTab()
} }
}, },
mounted () { mounted () {

View file

@ -25,6 +25,7 @@ import {
faInfo, faInfo,
faWindowRestore faWindowRestore
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from 'src/stores/interface'
library.add( library.add(
faWrench, faWrench,
@ -60,21 +61,21 @@ const SettingsModalContent = {
return !!this.$store.state.users.currentUser return !!this.$store.state.users.currentUser
}, },
open () { open () {
return this.$store.state.interface.settingsModalState !== 'hidden' return useInterfaceStore().settingsModalState !== 'hidden'
}, },
bodyLock () { bodyLock () {
return this.$store.state.interface.settingsModalState === 'visible' return useInterfaceStore().settingsModalState === 'visible'
}, },
expertLevel () { expertLevel () {
return this.$store.state.config.expertLevel return this.$store.state.config.expertLevel
}, },
isMobileLayout () { isMobileLayout () {
return this.$store.state.interface.layoutType === 'mobile' return useInterfaceStore().layoutType === 'mobile'
} }
}, },
methods: { methods: {
onOpen () { onOpen () {
const targetTab = this.$store.state.interface.settingsModalTargetTab const targetTab = useInterfaceStore().settingsModalTargetTab
// We're being told to open in specific tab // We're being told to open in specific tab
if (targetTab) { if (targetTab) {
const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => { const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => {
@ -86,7 +87,7 @@ const SettingsModalContent = {
} }
// Clear the state of target tab, so that next time settings is opened // Clear the state of target tab, so that next time settings is opened
// it doesn't force it. // it doesn't force it.
this.$store.dispatch('clearSettingsModalTargetTab') useInterfaceStore().clearSettingsModalTargetTab()
} }
}, },
mounted () { mounted () {

View file

@ -4,11 +4,9 @@ import IntegerSetting from '../helpers/integer_setting.vue'
import FloatSetting from '../helpers/float_setting.vue' import FloatSetting from '../helpers/float_setting.vue'
import UnitSetting, { defaultHorizontalUnits } from '../helpers/unit_setting.vue' import UnitSetting, { defaultHorizontalUnits } from '../helpers/unit_setting.vue'
import PaletteEditor from 'src/components/palette_editor/palette_editor.vue' import PaletteEditor from 'src/components/palette_editor/palette_editor.vue'
import Preview from './theme_tab/theme_preview.vue'
import FontControl from 'src/components/font_control/font_control.vue' import FontControl from 'src/components/font_control/font_control.vue'
import { normalizeThemeData } from 'src/modules/interface'
import { newImporter } from 'src/services/export_import/export_import.js' import { newImporter } from 'src/services/export_import/export_import.js'
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js' import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
import { init } from 'src/services/theme_data/theme_data_3.service.js' import { init } from 'src/services/theme_data/theme_data_3.service.js'
@ -20,17 +18,15 @@ import { deserialize } from 'src/services/theme_data/iss_deserializer.js'
import SharedComputedObject from '../helpers/shared_computed_object.js' import SharedComputedObject from '../helpers/shared_computed_object.js'
import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue' import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
import { mapActions } from 'pinia'
import { useInterfaceStore, normalizeThemeData } from 'src/stores/interface'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faGlobe faGlobe
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import Preview from './theme_tab/theme_preview.vue'
// helper for debugging
// eslint-disable-next-line no-unused-vars
const toValue = (x) => JSON.parse(JSON.stringify(x === undefined ? 'null' : x))
library.add( library.add(
faGlobe faGlobe
) )
@ -90,7 +86,7 @@ const AppearanceTab = {
PaletteEditor PaletteEditor
}, },
mounted () { mounted () {
this.$store.dispatch('getThemeData') useInterfaceStore().getThemeData()
const updateIndex = (resource) => { const updateIndex = (resource) => {
const capitalizedResource = resource[0].toUpperCase() + resource.slice(1) const capitalizedResource = resource[0].toUpperCase() + resource.slice(1)
@ -100,7 +96,7 @@ const AppearanceTab = {
if (currentIndex) { if (currentIndex) {
promise = Promise.resolve(currentIndex) promise = Promise.resolve(currentIndex)
} else { } else {
promise = this.$store.dispatch(`fetch${capitalizedResource}sIndex`) promise = useInterfaceStore()[`fetch${capitalizedResource}sIndex`]()
} }
return promise.then(index => { return promise.then(index => {
@ -131,7 +127,7 @@ const AppearanceTab = {
})) }))
}) })
this.userPalette = this.$store.state.interface.paletteDataUsed || {} this.userPalette = useInterfaceStore().paletteDataUsed || {}
updateIndex('palette').then(bundledPalettes => { updateIndex('palette').then(bundledPalettes => {
bundledPalettes.forEach(([key, palettePromise]) => palettePromise.then(v => { bundledPalettes.forEach(([key, palettePromise]) => palettePromise.then(v => {
@ -187,10 +183,10 @@ const AppearanceTab = {
}, },
computed: { computed: {
switchInProgress () { switchInProgress () {
return this.$store.state.interface.themeChangeInProgress return useInterfaceStore().themeChangeInProgress
}, },
paletteDataUsed () { paletteDataUsed () {
return this.$store.state.interface.paletteDataUsed return useInterfaceStore().paletteDataUsed
}, },
availableStyles () { availableStyles () {
return [ return [
@ -205,7 +201,7 @@ const AppearanceTab = {
] ]
}, },
stylePalettes () { stylePalettes () {
const ruleset = this.$store.state.interface.styleDataUsed || [] const ruleset = useInterfaceStore().styleDataUsed || []
if (!ruleset && ruleset.length === 0) return if (!ruleset && ruleset.length === 0) return
const meta = ruleset.find(x => x.component === '@meta') const meta = ruleset.find(x => x.component === '@meta')
const result = ruleset.filter(x => x.component.startsWith('@palette')) const result = ruleset.filter(x => x.component.startsWith('@palette'))
@ -273,7 +269,7 @@ const AppearanceTab = {
} }
}, },
customThemeVersion () { customThemeVersion () {
const { themeVersion } = this.$store.state.interface const { themeVersion } = useInterfaceStore()
return themeVersion return themeVersion
}, },
isCustomThemeUsed () { isCustomThemeUsed () {
@ -311,14 +307,14 @@ const AppearanceTab = {
}, },
onImport (parsed, filename) { onImport (parsed, filename) {
if (filename.endsWith('.json')) { if (filename.endsWith('.json')) {
this.$store.dispatch('setThemeCustom', parsed.source || parsed.theme) useInterfaceStore().setThemeCustom(parsed.source || parsed.theme)
} else if (filename.endsWith('.iss')) { } else if (filename.endsWith('.iss')) {
this.$store.dispatch('setStyleCustom', parsed) useInterfaceStore().setStyleCustom(parsed)
} }
}, },
onImportFailure (result) { onImportFailure (result) {
console.error('Failure importing theme:', result) console.error('Failure importing theme:', result)
this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_theme_imported', level: 'error' }) this.$store.useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_theme_imported', level: 'error' })
}, },
importValidator (parsed, filename) { importValidator (parsed, filename) {
if (filename.endsWith('.json')) { if (filename.endsWith('.json')) {
@ -340,22 +336,20 @@ const AppearanceTab = {
isPaletteActive (key) { isPaletteActive (key) {
return key === (this.mergedConfig.palette || this.$store.state.instance.palette) return key === (this.mergedConfig.palette || this.$store.state.instance.palette)
}, },
setStyle (name) { ...mapActions(useInterfaceStore, [
this.$store.dispatch('setStyle', name) 'setStyle',
}, 'setTheme'
setTheme (name) { ]),
this.$store.dispatch('setTheme', name)
},
setPalette (name, data) { setPalette (name, data) {
this.$store.dispatch('setPalette', name) useInterfaceStore().setPalette(name)
this.userPalette = data this.userPalette = data
}, },
setPaletteCustom (data) { setPaletteCustom (data) {
this.$store.dispatch('setPaletteCustom', data) useInterfaceStore().setPaletteCustom(data)
this.userPalette = data this.userPalette = data
}, },
resetTheming (name) { resetTheming (name) {
this.$store.dispatch('setStyle', 'stock') useInterfaceStore().setStyle('stock')
}, },
previewTheme (key, version, input) { previewTheme (key, version, input) {
let theme3 let theme3

View file

@ -21,6 +21,7 @@ import {
faPlus, faPlus,
faCircleNotch faCircleNotch
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from '../../../stores/interface'
library.add( library.add(
faTimes, faTimes,
@ -175,7 +176,7 @@ const ProfileTab = {
if (file.size > this.$store.state.instance[slot + 'limit']) { if (file.size > this.$store.state.instance[slot + 'limit']) {
const filesize = fileSizeFormatService.fileSizeFormat(file.size) const filesize = fileSizeFormatService.fileSizeFormat(file.size)
const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit']) const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit'])
this.$store.dispatch('pushGlobalNotice', { useInterfaceStore().pushGlobalNotice({
messageKey: 'upload.error.message', messageKey: 'upload.error.message',
messageArgs: [ messageArgs: [
this.$t('upload.error.file_too_big', { this.$t('upload.error.file_too_big', {
@ -266,7 +267,7 @@ const ProfileTab = {
.finally(() => { this.backgroundUploading = false }) .finally(() => { this.backgroundUploading = false })
}, },
displayUploadError (error) { displayUploadError (error) {
this.$store.dispatch('pushGlobalNotice', { useInterfaceStore().pushGlobalNotice({
messageKey: 'upload.error.message', messageKey: 'upload.error.message',
messageArgs: [error.message], messageArgs: [error.message],
level: 'error' level: 'error'

View file

@ -1,5 +1,5 @@
import { ref, reactive, computed, watch, watchEffect, provide, getCurrentInstance } from 'vue' import { ref, reactive, computed, watch, watchEffect, provide, getCurrentInstance } from 'vue'
import { useStore } from 'vuex' import { useInterfaceStore } from 'src/stores/interface'
import { get, set, unset, throttle } from 'lodash' import { get, set, unset, throttle } from 'lodash'
import Select from 'src/components/select/select.vue' import Select from 'src/components/select/select.vue'
@ -80,13 +80,13 @@ export default {
}, },
setup (props, context) { setup (props, context) {
const exports = {} const exports = {}
const store = useStore() const interfaceStore = useInterfaceStore()
// All rules that are made by editor // All rules that are made by editor
const allEditedRules = ref(store.state.interface.styleDataUsed || {}) const allEditedRules = ref(interfaceStore.styleDataUsed || {})
const styleDataUsed = computed(() => store.state.interface.styleDataUsed) const styleDataUsed = computed(() => interfaceStore.styleDataUsed)
watch([styleDataUsed], (value) => { watch([styleDataUsed], (value) => {
onImport(store.state.interface.styleDataUsed) onImport(interfaceStore.styleDataUsed)
}, { once: true }) }, { once: true })
exports.isActive = computed(() => { exports.isActive = computed(() => {
@ -640,7 +640,7 @@ export default {
parser (string) { return deserialize(string) }, parser (string) { return deserialize(string) },
onImportFailure (result) { onImportFailure (result) {
console.error('Failure importing style:', result) console.error('Failure importing style:', result)
this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_theme_imported', level: 'error' }) this.$store.useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_theme_imported', level: 'error' })
}, },
onImport onImport
}) })
@ -664,7 +664,7 @@ export default {
}) })
exports.clearStyle = () => { exports.clearStyle = () => {
onImport(store.state.interface.styleDataUsed) onImport(interfaceStore().styleDataUsed)
} }
exports.exportStyle = () => { exports.exportStyle = () => {
@ -676,7 +676,7 @@ export default {
} }
exports.applyStyle = () => { exports.applyStyle = () => {
store.dispatch('setStyleCustom', exportRules.value) useInterfaceStore().setStyleCustom(exportRules.value)
} }
const overallPreviewRules = ref([]) const overallPreviewRules = ref([])

View file

@ -43,6 +43,7 @@ import Checkbox from 'src/components/checkbox/checkbox.vue'
import Select from 'src/components/select/select.vue' import Select from 'src/components/select/select.vue'
import Preview from './theme_preview.vue' import Preview from './theme_preview.vue'
import { useInterfaceStore } from '../../../../stores/interface'
// List of color values used in v1 // List of color values used in v1
const v1OnlyNames = [ const v1OnlyNames = [
@ -126,7 +127,7 @@ export default {
if (currentIndex) { if (currentIndex) {
promise = Promise.resolve(currentIndex) promise = Promise.resolve(currentIndex)
} else { } else {
promise = this.$store.dispatch('fetchThemesIndex') promise = useInterfaceStore().fetchThemesIndex()
} }
promise.then(themesIndex => { promise.then(themesIndex => {
@ -296,7 +297,7 @@ export default {
} }
}, },
themeDataUsed () { themeDataUsed () {
return this.$store.state.interface.themeDataUsed return useInterfaceStore().themeDataUsed
}, },
shadowsAvailable () { shadowsAvailable () {
return Object.keys(DEFAULT_SHADOWS).sort() return Object.keys(DEFAULT_SHADOWS).sort()
@ -492,7 +493,7 @@ export default {
} }
}, },
setCustomTheme () { setCustomTheme () {
this.$store.dispatch('setThemeV2', { useInterfaceStore().setThemeV2({
customTheme: { customTheme: {
ignore: true, ignore: true,
themeFileVersion: this.selectedVersion, themeFileVersion: this.selectedVersion,
@ -536,7 +537,7 @@ export default {
this.loadTheme(parsed, 'file', forceSource) this.loadTheme(parsed, 'file', forceSource)
}, },
onImportFailure (result) { onImportFailure (result) {
this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_theme_imported', level: 'error' }) useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_theme_imported', level: 'error' })
}, },
importValidator (parsed) { importValidator (parsed) {
const version = parsed._pleroma_theme_version const version = parsed._pleroma_theme_version

View file

@ -4,6 +4,7 @@ import {
faBullhorn, faBullhorn,
faTimes faTimes
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useShoutStore } from '../../stores/shout'
library.add( library.add(
faBullhorn, faBullhorn,
@ -21,12 +22,12 @@ const shoutPanel = {
}, },
computed: { computed: {
messages () { messages () {
return this.$store.state.shout.messages return useShoutStore().messages
} }
}, },
methods: { methods: {
submit (message) { submit (message) {
this.$store.state.shout.channel.push('new_msg', { text: message }, 10000) useShoutStore().channel.push('new_msg', { text: message }, 10000)
this.currentMessage = '' this.currentMessage = ''
}, },
togglePanel () { togglePanel () {

View file

@ -1,4 +1,5 @@
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import UserCard from '../user_card/user_card.vue' import UserCard from '../user_card/user_card.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils' import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service' import GestureService from '../../services/gesture_service/gesture_service'
@ -20,6 +21,9 @@ import {
faList, faList,
faFilePen faFilePen
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useShoutStore } from '../../stores/shout'
import { useInterfaceStore } from '../../stores/interface'
import { useAnnouncementsStore } from '../../stores/announcements'
library.add( library.add(
faSignInAlt, faSignInAlt,
@ -56,7 +60,7 @@ const SideDrawer = {
currentUser () { currentUser () {
return this.$store.state.users.currentUser return this.$store.state.users.currentUser
}, },
shout () { return this.$store.state.shout.joined }, shout () { return useShoutStore().joined },
unseenNotifications () { unseenNotifications () {
return unseenNotificationsFromStore(this.$store) return unseenNotificationsFromStore(this.$store)
}, },
@ -86,8 +90,8 @@ const SideDrawer = {
}, },
timelinesRoute () { timelinesRoute () {
let name let name
if (this.$store.state.interface.lastTimeline) { if (useInterfaceStore().lastTimeline) {
name = this.$store.state.interface.lastTimeline name = useInterfaceStore().lastTimeline
} }
name = this.currentUser ? 'friends' : 'public-timeline' name = this.currentUser ? 'friends' : 'public-timeline'
if (USERNAME_ROUTES.has(name)) { if (USERNAME_ROUTES.has(name)) {
@ -96,11 +100,14 @@ const SideDrawer = {
return { name } return { name }
} }
}, },
...mapState({ ...mapPiniaState(useAnnouncementsStore, {
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, supportsAnnouncements: store => store.supportsAnnouncements,
supportsAnnouncements: state => state.announcements.supportsAnnouncements unreadAnnouncementCount: 'unreadAnnouncementCount'
}), }),
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount', 'draftCount']) ...mapState({
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
}),
...mapGetters(['unreadChatCount', 'draftCount'])
}, },
methods: { methods: {
toggleDrawer () { toggleDrawer () {
@ -117,10 +124,10 @@ const SideDrawer = {
GestureService.updateSwipe(e, this.closeGesture) GestureService.updateSwipe(e, this.closeGesture)
}, },
openSettingsModal () { openSettingsModal () {
this.$store.dispatch('openSettingsModal', 'user') useInterfaceStore().openSettingsModal('user')
}, },
openAdminModal () { openAdminModal () {
this.$store.dispatch('openSettingsModal', 'admin') useInterfaceStore().openSettingsModal('admin')
} }
} }
} }

View file

@ -13,6 +13,7 @@ import {
faLink, faLink,
faPollH faPollH
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useMediaViewerStore } from '../../stores/media_viewer'
library.add( library.add(
faCircleNotch, faCircleNotch,
@ -130,7 +131,7 @@ const StatusContent = {
}, },
setMedia () { setMedia () {
const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments
return () => this.$store.dispatch('setMedia', attachments) return () => useMediaViewerStore().setMedia(attachments)
} }
} }
} }

View file

@ -1,6 +1,7 @@
import { get } from 'lodash' import { get } from 'lodash'
import Modal from '../modal/modal.vue' import Modal from '../modal/modal.vue'
import Status from '../status/status.vue' import Status from '../status/status.vue'
import { useStatusHistoryStore } from '../../stores/statusHistory'
const StatusHistoryModal = { const StatusHistoryModal = {
components: { components: {
@ -14,10 +15,10 @@ const StatusHistoryModal = {
}, },
computed: { computed: {
modalActivated () { modalActivated () {
return this.$store.state.statusHistory.modalActivated return useStatusHistoryStore().modalActivated
}, },
params () { params () {
return this.$store.state.statusHistory.params return useStatusHistoryStore().params
}, },
statusId () { statusId () {
return this.params.id return this.params.id
@ -52,7 +53,7 @@ const StatusHistoryModal = {
}) })
}, },
closeModal () { closeModal () {
this.$store.dispatch('closeStatusHistoryModal') useStatusHistoryStore().closeStatusHistoryModal()
} }
} }
} }

View file

@ -1,9 +1,10 @@
// eslint-disable-next-line no-unused // eslint-disable-next-line no-unused
import { h, Fragment } from 'vue' import { h, Fragment } from 'vue'
import { mapState } from 'vuex' import { mapState } from 'pinia'
import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome' import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
import './tab_switcher.scss' import './tab_switcher.scss'
import { useInterfaceStore } from '../../stores/interface'
const findFirstUsable = (slots) => slots.findIndex(_ => _.props) const findFirstUsable = (slots) => slots.findIndex(_ => _.props)

View file

@ -1,5 +1,5 @@
import Status from '../status/status.vue' import Status from '../status/status.vue'
import { mapState } from 'vuex' import { mapState } from 'pinia'
import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js' import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js'
import Conversation from '../conversation/conversation.vue' import Conversation from '../conversation/conversation.vue'
import TimelineMenu from '../timeline_menu/timeline_menu.vue' import TimelineMenu from '../timeline_menu/timeline_menu.vue'
@ -8,6 +8,7 @@ import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue'
import { debounce, throttle, keyBy } from 'lodash' import { debounce, throttle, keyBy } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch, faCirclePlus, faCog, faMinus, faArrowUp, faCheck } from '@fortawesome/free-solid-svg-icons' import { faCircleNotch, faCirclePlus, faCog, faMinus, faArrowUp, faCheck } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from '../../stores/interface'
library.add( library.add(
faCircleNotch, faCircleNotch,
@ -103,8 +104,8 @@ const Timeline = {
virtualScrollingEnabled () { virtualScrollingEnabled () {
return this.$store.getters.mergedConfig.virtualScrolling return this.$store.getters.mergedConfig.virtualScrolling
}, },
...mapState({ ...mapState(useInterfaceStore, {
mobileLayout: state => state.interface.layoutType === 'mobile' mobileLayout: store => store.layoutType === 'mobile'
}) })
}, },
created () { created () {

View file

@ -9,6 +9,8 @@ import { filterNavigation } from 'src/components/navigation/filter.js'
import { import {
faChevronDown faChevronDown
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from '../../stores/interface'
import { useListsStore } from '../../stores/lists'
library.add(faChevronDown) library.add(faChevronDown)
@ -39,7 +41,7 @@ const TimelineMenu = {
}, },
created () { created () {
if (timelineNames(this.bookmarkFolders)[this.$route.name]) { if (timelineNames(this.bookmarkFolders)[this.$route.name]) {
this.$store.dispatch('setLastTimeline', this.$route.name) useInterfaceStore().setLastTimeline(this.$route.name)
} }
}, },
computed: { computed: {
@ -95,7 +97,7 @@ const TimelineMenu = {
return '#' + this.$route.params.tag return '#' + this.$route.params.tag
} }
if (route === 'lists-timeline') { if (route === 'lists-timeline') {
return this.$store.getters.findListTitle(this.$route.params.id) return useListsStore().findListTitle(this.$route.params.id)
} }
if (route === 'bookmark-folder') { if (route === 'bookmark-folder') {
return this.$store.getters.findBookmarkFolderName(this.$route.params.id) return this.$store.getters.findBookmarkFolderName(this.$route.params.id)

View file

@ -1,4 +1,5 @@
import StillImage from '../still-image/still-image.vue' import StillImage from '../still-image/still-image.vue'
import { useInterfaceStore } from 'src/stores/interface'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
@ -22,7 +23,7 @@ const UserAvatar = {
return { return {
showPlaceholder: false, showPlaceholder: false,
defaultAvatar: `${this.$store.state.instance.server + this.$store.state.instance.defaultAvatar}`, defaultAvatar: `${this.$store.state.instance.server + this.$store.state.instance.defaultAvatar}`,
betterShadow: this.$store.state.interface.browserSupport.cssFilter betterShadow: useInterfaceStore().browserSupport.cssFilter
} }
}, },
components: { components: {

View file

@ -11,6 +11,7 @@ import RichContent from 'src/components/rich_content/rich_content.jsx'
import MuteConfirm from '../confirm_modal/mute_confirm.vue' import MuteConfirm from '../confirm_modal/mute_confirm.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { usePostStatusStore } from '../../stores/postStatus'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faBell, faBell,
@ -21,6 +22,8 @@ import {
faTimes, faTimes,
faExpandAlt faExpandAlt
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useMediaViewerStore } from '../../stores/media_viewer'
import { useInterfaceStore } from '../../stores/interface'
library.add( library.add(
faRss, faRss,
@ -188,18 +191,18 @@ export default {
) )
}, },
openProfileTab () { openProfileTab () {
this.$store.dispatch('openSettingsModalTab', 'profile') useInterfaceStore().openSettingsModalTab('profile')
}, },
zoomAvatar () { zoomAvatar () {
const attachment = { const attachment = {
url: this.user.profile_image_url_original, url: this.user.profile_image_url_original,
mimetype: 'image' mimetype: 'image'
} }
this.$store.dispatch('setMedia', [attachment]) useMediaViewerStore().setMedia([attachment])
this.$store.dispatch('setCurrentMedia', attachment) useMediaViewerStore().setCurrentMedia(attachment)
}, },
mentionUser () { mentionUser () {
this.$store.dispatch('openPostStatusModal', { profileMention: true, repliedUser: this.user }) usePostStatusStore().openPostStatusModal({ profileMention: true, repliedUser: this.user })
}, },
onAvatarClickHandler (e) { onAvatarClickHandler (e) {
if (this.onAvatarClick) { if (this.onAvatarClick) {

View file

@ -1,9 +1,10 @@
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faChevronRight } from '@fortawesome/free-solid-svg-icons' import { faChevronRight } from '@fortawesome/free-solid-svg-icons'
import { mapState } from 'vuex' import { mapState } from 'pinia'
import DialogModal from '../dialog_modal/dialog_modal.vue' import DialogModal from '../dialog_modal/dialog_modal.vue'
import Popover from '../popover/popover.vue' import Popover from '../popover/popover.vue'
import { useListsStore } from '../../stores/lists'
library.add(faChevronRight) library.add(faChevronRight)
@ -22,8 +23,8 @@ const UserListMenu = {
this.$store.dispatch('fetchUserInLists', this.user.id) this.$store.dispatch('fetchUserInLists', this.user.id)
}, },
computed: { computed: {
...mapState({ ...mapState(useListsStore, {
allLists: state => state.lists.allLists allLists: store => store.allLists
}), }),
inListsSet () { inListsSet () {
return new Set(this.user.inLists.map(x => x.id)) return new Set(this.user.inLists.map(x => x.id))
@ -44,12 +45,12 @@ const UserListMenu = {
methods: { methods: {
toggleList (listId) { toggleList (listId) {
if (this.inListsSet.has(listId)) { if (this.inListsSet.has(listId)) {
this.$store.dispatch('removeListAccount', { accountId: this.user.id, listId }).then((response) => { useListsStore().removeListAccount({ accountId: this.user.id, listId }).then((response) => {
if (!response.ok) { return } if (!response.ok) { return }
this.$store.dispatch('fetchUserInLists', this.user.id) this.$store.dispatch('fetchUserInLists', this.user.id)
}) })
} else { } else {
this.$store.dispatch('addListAccount', { accountId: this.user.id, listId }).then((response) => { useListsStore().addListAccount({ accountId: this.user.id, listId }).then((response) => {
if (!response.ok) { return } if (!response.ok) { return }
this.$store.dispatch('fetchUserInLists', this.user.id) this.$store.dispatch('fetchUserInLists', this.user.id)
}) })

View file

@ -3,6 +3,7 @@ import List from '../list/list.vue'
import Checkbox from '../checkbox/checkbox.vue' import Checkbox from '../checkbox/checkbox.vue'
import Modal from '../modal/modal.vue' import Modal from '../modal/modal.vue'
import UserLink from '../user_link/user_link.vue' import UserLink from '../user_link/user_link.vue'
import { useReportsStore } from '../../stores/reports'
const UserReportingModal = { const UserReportingModal = {
components: { components: {
@ -23,7 +24,7 @@ const UserReportingModal = {
}, },
computed: { computed: {
reportModal () { reportModal () {
return this.$store.state.reports.reportModal return useReportsStore().reportModal
}, },
isLoggedIn () { isLoggedIn () {
return !!this.$store.state.users.currentUser return !!this.$store.state.users.currentUser
@ -63,7 +64,7 @@ const UserReportingModal = {
this.error = false this.error = false
}, },
closeModal () { closeModal () {
this.$store.dispatch('closeUserReportingModal') useReportsStore().closeUserReportingModal()
}, },
reportUser () { reportUser () {
this.processing = true this.processing = true

View file

@ -1,5 +1,6 @@
import merge from 'lodash.merge' import merge from 'lodash.merge'
import { each, get, set, cloneDeep } from 'lodash' import { each, get, set, cloneDeep } from 'lodash'
import { useInterfaceStore } from '../stores/interface'
import { storage } from './storage.js' import { storage } from './storage.js'
let loaded = false let loaded = false
@ -76,12 +77,12 @@ export default function createPersistedState ({
.then(success => { .then(success => {
if (typeof success !== 'undefined') { if (typeof success !== 'undefined') {
if (mutation.type === 'setOption' || mutation.type === 'setCurrentUser') { if (mutation.type === 'setOption' || mutation.type === 'setCurrentUser') {
store.dispatch('settingsSaved', { success }) useInterfaceStore().settingsSaved({ success })
} }
} }
}, error => { }, error => {
if (mutation.type === 'setOption' || mutation.type === 'setCurrentUser') { if (mutation.type === 'setOption' || mutation.type === 'setCurrentUser') {
store.dispatch('settingsSaved', { error }) useInterfaceStore().settingsSaved({ error })
} }
}) })
} }

View file

@ -1,8 +1,10 @@
import { useInterfaceStore } from '../stores/interface'
export default (store) => { export default (store) => {
store.subscribe((mutation, state) => { store.subscribe((mutation, state) => {
const vapidPublicKey = state.instance.vapidPublicKey const vapidPublicKey = state.instance.vapidPublicKey
const webPushNotification = state.config.webPushNotifications const webPushNotification = state.config.webPushNotifications
const permission = state.interface.notificationPermission === 'granted' const permission = useInterfaceStore().notificationPermission === 'granted'
const user = state.users.currentUser const user = state.users.currentUser
const isUserMutation = mutation.type === 'setCurrentUser' const isUserMutation = mutation.type === 'setCurrentUser'

View file

@ -1,9 +1,9 @@
import { createStore } from 'vuex' import { createStore } from 'vuex'
import { createPinia } from 'pinia'
import 'custom-event-polyfill' import 'custom-event-polyfill'
import './lib/event_target_polyfill.js' import './lib/event_target_polyfill.js'
import interfaceModule from './modules/interface.js'
import instanceModule from './modules/instance.js' import instanceModule from './modules/instance.js'
import statusesModule from './modules/statuses.js' import statusesModule from './modules/statuses.js'
import notificationsModule from './modules/notifications.js' import notificationsModule from './modules/notifications.js'
@ -14,19 +14,11 @@ import configModule from './modules/config.js'
import profileConfigModule from './modules/profileConfig.js' import profileConfigModule from './modules/profileConfig.js'
import serverSideStorageModule from './modules/serverSideStorage.js' import serverSideStorageModule from './modules/serverSideStorage.js'
import adminSettingsModule from './modules/adminSettings.js' import adminSettingsModule from './modules/adminSettings.js'
import shoutModule from './modules/shout.js'
import oauthModule from './modules/oauth.js' import oauthModule from './modules/oauth.js'
import authFlowModule from './modules/auth_flow.js' import authFlowModule from './modules/auth_flow.js'
import mediaViewerModule from './modules/media_viewer.js'
import oauthTokensModule from './modules/oauth_tokens.js' import oauthTokensModule from './modules/oauth_tokens.js'
import reportsModule from './modules/reports.js'
import pollsModule from './modules/polls.js'
import postStatusModule from './modules/postStatus.js'
import editStatusModule from './modules/editStatus.js'
import statusHistoryModule from './modules/statusHistory.js'
import draftsModule from './modules/drafts.js' import draftsModule from './modules/drafts.js'
import chatsModule from './modules/chats.js' import chatsModule from './modules/chats.js'
import announcementsModule from './modules/announcements.js'
import bookmarkFoldersModule from './modules/bookmark_folders.js' import bookmarkFoldersModule from './modules/bookmark_folders.js'
import { createI18n } from 'vue-i18n' import { createI18n } from 'vue-i18n'
@ -85,6 +77,7 @@ const persistedStateOptions = {
try { try {
let storageError let storageError
const plugins = [pushNotifications] const plugins = [pushNotifications]
const pinia = createPinia()
try { try {
const persistedState = await createPersistedState(persistedStateOptions) const persistedState = await createPersistedState(persistedStateOptions)
plugins.push(persistedState) plugins.push(persistedState)
@ -98,36 +91,22 @@ const persistedStateOptions = {
document.querySelector('#splash-credit').textContent = i18n.global.t('update.art_by', { linkToArtist: 'pipivovott' }) document.querySelector('#splash-credit').textContent = i18n.global.t('update.art_by', { linkToArtist: 'pipivovott' })
const store = createStore({ const store = createStore({
modules: { modules: {
i18n: {
getters: {
i18n: () => i18n.global
}
},
interface: interfaceModule,
instance: instanceModule, instance: instanceModule,
// TODO refactor users/statuses modules, they depend on each other // TODO refactor users/statuses modules, they depend on each other
users: usersModule, users: usersModule,
lists: listsModule,
statuses: statusesModule, statuses: statusesModule,
notifications: notificationsModule, notifications: notificationsModule,
lists: listsModule,
api: apiModule, api: apiModule,
config: configModule, config: configModule,
profileConfig: profileConfigModule, profileConfig: profileConfigModule,
serverSideStorage: serverSideStorageModule, serverSideStorage: serverSideStorageModule,
adminSettings: adminSettingsModule, adminSettings: adminSettingsModule,
shout: shoutModule,
oauth: oauthModule, oauth: oauthModule,
authFlow: authFlowModule, authFlow: authFlowModule,
mediaViewer: mediaViewerModule,
oauthTokens: oauthTokensModule, oauthTokens: oauthTokensModule,
reports: reportsModule,
polls: pollsModule,
postStatus: postStatusModule,
editStatus: editStatusModule,
statusHistory: statusHistoryModule,
drafts: draftsModule, drafts: draftsModule,
chats: chatsModule, chats: chatsModule,
announcements: announcementsModule,
bookmarkFolders: bookmarkFoldersModule bookmarkFolders: bookmarkFoldersModule
}, },
plugins, plugins,
@ -137,10 +116,9 @@ const persistedStateOptions = {
strict: false // Socket modifies itself, let's ignore this for now. strict: false // Socket modifies itself, let's ignore this for now.
// strict: process.env.NODE_ENV !== 'production' // strict: process.env.NODE_ENV !== 'production'
}) })
if (storageError) { window.vuex = store
store.dispatch('pushGlobalNotice', { messageKey: 'errors.storage_unavailable', level: 'error' }) // Temporarily passing pinia and vuex stores along with storageError result until migration is fully complete.
} return await afterStoreSetup({ pinia, store, storageError, i18n })
return await afterStoreSetup({ store, i18n })
} catch (e) { } catch (e) {
splashError(i18n, e) splashError(i18n, e)
} }

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

View file

@ -2,6 +2,8 @@ import backendInteractorService from '../services/backend_interactor_service/bac
import { WSConnectionStatus } from '../services/api/api.service.js' import { WSConnectionStatus } from '../services/api/api.service.js'
import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js'
import { Socket } from 'phoenix' import { Socket } from 'phoenix'
import { useShoutStore } from '../stores/shout.js'
import { useInterfaceStore } from '../stores/interface.js'
const retryTimeout = (multiplier) => 1000 * multiplier const retryTimeout = (multiplier) => 1000 * multiplier
@ -134,7 +136,7 @@ const api = {
state.mastoUserSocket.addEventListener('open', () => { state.mastoUserSocket.addEventListener('open', () => {
// Do not show notification when we just opened up the page // Do not show notification when we just opened up the page
if (state.mastoUserSocketStatus !== WSConnectionStatus.STARTING_INITIAL) { if (state.mastoUserSocketStatus !== WSConnectionStatus.STARTING_INITIAL) {
dispatch('pushGlobalNotice', { useInterfaceStore().pushGlobalNotice({
level: 'success', level: 'success',
messageKey: 'timeline.socket_reconnected', messageKey: 'timeline.socket_reconnected',
timeout: 5000 timeout: 5000
@ -176,7 +178,7 @@ const api = {
dispatch('startFetchingTimeline', { timeline: 'friends' }) dispatch('startFetchingTimeline', { timeline: 'friends' })
dispatch('startFetchingNotifications') dispatch('startFetchingNotifications')
dispatch('startFetchingChats') dispatch('startFetchingChats')
dispatch('pushGlobalNotice', { useInterfaceStore().pushGlobalNotice({
level: 'error', level: 'error',
messageKey: 'timeline.socket_broke', messageKey: 'timeline.socket_broke',
messageArgs: [code], messageArgs: [code],
@ -300,7 +302,7 @@ const api = {
socket.connect() socket.connect()
commit('setSocket', socket) commit('setSocket', socket)
dispatch('initializeShout', socket) useShoutStore().initializeShout(socket)
} }
}, },
disconnectFromSocket ({ commit, state }) { disconnectFromSocket ({ commit, state }) {

View file

@ -3,6 +3,10 @@ import { applyConfig } from '../services/style_setter/style_setter.js'
import messages from '../i18n/messages' import messages from '../i18n/messages'
import { set } from 'lodash' import { set } from 'lodash'
import localeService from '../services/locale/locale.service.js' import localeService from '../services/locale/locale.service.js'
import { useI18nStore } from '../stores/i18n.js'
import { useInterfaceStore } from '../stores/interface.js'
import { defaultState } from './default_config_state.js'
const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage' const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
const APPEARANCE_SETTINGS_KEYS = new Set([ const APPEARANCE_SETTINGS_KEYS = new Set([
@ -17,8 +21,6 @@ const APPEARANCE_SETTINGS_KEYS = new Set([
'emojiReactionsScale' 'emojiReactionsScale'
]) ])
const browserLocale = (window.navigator.language || 'en').split('-')[0]
/* TODO this is a bit messy. /* TODO this is a bit messy.
* We need to declare settings with their types and also deal with * We need to declare settings with their types and also deal with
* instance-default settings in some way, hopefully try to avoid copy-pasta * instance-default settings in some way, hopefully try to avoid copy-pasta
@ -34,168 +36,7 @@ export const multiChoiceProperties = [
'unsavedPostAction' // save | discard | confirm 'unsavedPostAction' // save | discard | confirm
] ]
export const defaultState = { console.log('TEST', defaultState)
expertLevel: 0, // used to track which settings to show and hide
// Theme stuff
theme: undefined, // Very old theme store, stores preset name, still in use
// V1
colors: {}, // VERY old theme store, just colors of V1, probably not even used anymore
// V2
customTheme: undefined, // "snapshot", previously was used as actual theme store for V2 so it's still used in case of PleromaFE downgrade event.
customThemeSource: undefined, // "source", stores original theme data
// V3
style: null,
styleCustomData: null,
palette: null,
paletteCustomData: null,
themeDebug: false, // debug mode that uses computed backgrounds instead of real ones to debug contrast functions
forceThemeRecompilation: false, // flag that forces recompilation on boot even if cache exists
theme3hacks: { // Hacks, user overrides that are independent of theme used
underlay: 'none',
fonts: {
interface: undefined,
input: undefined,
post: undefined,
monospace: undefined
}
},
hideISP: false,
hideInstanceWallpaper: false,
hideShoutbox: false,
// bad name: actually hides posts of muted USERS
hideMutedPosts: undefined, // instance default
hideMutedThreads: undefined, // instance default
hideWordFilteredPosts: undefined, // instance default
muteBotStatuses: undefined, // instance default
muteSensitiveStatuses: undefined, // instance default
collapseMessageWithSubject: undefined, // instance default
padEmoji: true,
hideAttachments: false,
hideAttachmentsInConv: false,
hideScrobbles: false,
hideScrobblesAfter: '2d',
maxThumbnails: 16,
hideNsfw: true,
preloadImage: true,
loopVideo: true,
loopVideoSilentOnly: true,
streaming: false,
emojiReactionsOnTimeline: true,
alwaysShowNewPostButton: false,
autohideFloatingPostButton: false,
pauseOnUnfocused: true,
stopGifs: true,
replyVisibility: 'all',
thirdColumnMode: 'notifications',
notificationVisibility: {
follows: true,
mentions: true,
statuses: true,
likes: true,
repeats: true,
moves: true,
emojiReactions: true,
followRequest: true,
reports: true,
chatMention: true,
polls: true
},
notificationNative: {
follows: true,
mentions: true,
statuses: true,
likes: false,
repeats: false,
moves: false,
emojiReactions: false,
followRequest: true,
reports: true,
chatMention: true,
polls: true
},
webPushNotifications: false,
webPushAlwaysShowNotifications: false,
muteWords: [],
highlight: {},
interfaceLanguage: browserLocale,
hideScopeNotice: false,
useStreamingApi: false,
sidebarRight: undefined, // instance default
scopeCopy: undefined, // instance default
subjectLineBehavior: undefined, // instance default
alwaysShowSubjectInput: undefined, // instance default
postContentType: undefined, // instance default
minimalScopesMode: undefined, // instance default
// This hides statuses filtered via a word filter
hideFilteredStatuses: undefined, // instance default
modalOnRepeat: undefined, // instance default
modalOnUnfollow: undefined, // instance default
modalOnBlock: undefined, // instance default
modalOnMute: undefined, // instance default
modalOnMuteConversation: undefined, // instance default
modalOnMuteDomain: undefined, // instance default
modalOnDelete: undefined, // instance default
modalOnLogout: undefined, // instance default
modalOnApproveFollow: undefined, // instance default
modalOnDenyFollow: undefined, // instance default
modalOnRemoveUserFromFollowers: undefined, // instance default
modalMobileCenter: undefined,
playVideosInModal: false,
useOneClickNsfw: false,
useContainFit: true,
disableStickyHeaders: false,
showScrollbars: false,
userPopoverAvatarAction: 'open',
userPopoverOverlay: false,
sidebarColumnWidth: '25rem',
contentColumnWidth: '45rem',
notifsColumnWidth: '25rem',
emojiReactionsScale: undefined,
textSize: undefined, // instance default
emojiSize: undefined, // instance default
navbarSize: undefined, // instance default
panelHeaderSize: undefined, // instance default
forcedRoundness: undefined, // instance default
navbarColumnStretch: false,
greentext: undefined, // instance default
useAtIcon: undefined, // instance default
mentionLinkDisplay: undefined, // instance default
mentionLinkShowTooltip: undefined, // instance default
mentionLinkShowAvatar: undefined, // instance default
mentionLinkFadeDomain: undefined, // instance default
mentionLinkShowYous: undefined, // instance default
mentionLinkBoldenYou: undefined, // instance default
hidePostStats: undefined, // instance default
hideBotIndication: undefined, // instance default
hideUserStats: undefined, // instance default
virtualScrolling: undefined, // instance default
sensitiveByDefault: undefined, // instance default
conversationDisplay: undefined, // instance default
conversationTreeAdvanced: undefined, // instance default
conversationOtherRepliesButton: undefined, // instance default
conversationTreeFadeAncestors: undefined, // instance default
showExtraNotifications: undefined, // instance default
showExtraNotificationsTip: undefined, // instance default
showChatsInExtraNotifications: undefined, // instance default
showAnnouncementsInExtraNotifications: undefined, // instance default
showFollowRequestsInExtraNotifications: undefined, // instance default
maxDepthInThread: undefined, // instance default
autocompleteSelect: undefined, // instance default
closingDrawerMarksAsSeen: undefined, // instance default
unseenAtTop: undefined, // instance default
ignoreInactionableSeen: undefined, // instance default
unsavedPostAction: undefined, // instance default
autoSaveDraft: undefined, // instance default
useAbsoluteTimeFormat: undefined, // instance default
absoluteTimeFormatMinAge: undefined, // instance default
absoluteTime12h: undefined, // instance default
imageCompression: true
}
// caching the instance default properties // caching the instance default properties
export const instanceDefaultProperties = Object.entries(defaultState) export const instanceDefaultProperties = Object.entries(defaultState)
@ -259,7 +100,7 @@ const config = {
commit('setHighlight', { user, color, type }) commit('setHighlight', { user, color, type })
}, },
setOptionTemporarily ({ commit, dispatch, state, rootState }, { name, value }) { setOptionTemporarily ({ commit, dispatch, state, rootState }, { name, value }) {
if (rootState.interface.temporaryChangesTimeoutId !== null) { if (useInterfaceStore().temporaryChangesTimeoutId !== null) {
console.warn('Can\'t track more than one temporary change') console.warn('Can\'t track more than one temporary change')
return return
} }
@ -327,7 +168,7 @@ const config = {
break break
} }
case 'interfaceLanguage': case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value) messages.setLanguage(useI18nStore().i18n, value)
dispatch('loadUnicodeEmojiData', value) dispatch('loadUnicodeEmojiData', value)
Cookies.set( Cookies.set(
BACKEND_LANGUAGE_COOKIE_NAME, BACKEND_LANGUAGE_COOKIE_NAME,
@ -335,7 +176,7 @@ const config = {
) )
break break
case 'thirdColumnMode': case 'thirdColumnMode':
dispatch('setLayoutWidth', undefined) useInterfaceStore().setLayoutWidth(undefined)
break break
} }
} }

View file

@ -0,0 +1,164 @@
const browserLocale = (window.navigator.language || 'en').split('-')[0]
export const defaultState = {
expertLevel: 0, // used to track which settings to show and hide
// Theme stuff
theme: undefined, // Very old theme store, stores preset name, still in use
// V1
colors: {}, // VERY old theme store, just colors of V1, probably not even used anymore
// V2
customTheme: undefined, // "snapshot", previously was used as actual theme store for V2 so it's still used in case of PleromaFE downgrade event.
customThemeSource: undefined, // "source", stores original theme data
// V3
style: null,
styleCustomData: null,
palette: null,
paletteCustomData: null,
themeDebug: false, // debug mode that uses computed backgrounds instead of real ones to debug contrast functions
forceThemeRecompilation: false, // flag that forces recompilation on boot even if cache exists
theme3hacks: { // Hacks, user overrides that are independent of theme used
underlay: 'none',
fonts: {
interface: undefined,
input: undefined,
post: undefined,
monospace: undefined
}
},
hideISP: false,
hideInstanceWallpaper: false,
hideShoutbox: false,
// bad name: actually hides posts of muted USERS
hideMutedPosts: undefined, // instance default
hideMutedThreads: undefined, // instance default
hideWordFilteredPosts: undefined, // instance default
muteBotStatuses: undefined, // instance default
muteSensitiveStatuses: undefined, // instance default
collapseMessageWithSubject: undefined, // instance default
padEmoji: true,
hideAttachments: false,
hideAttachmentsInConv: false,
hideScrobbles: false,
hideScrobblesAfter: '2d',
maxThumbnails: 16,
hideNsfw: true,
preloadImage: true,
loopVideo: true,
loopVideoSilentOnly: true,
streaming: false,
emojiReactionsOnTimeline: true,
alwaysShowNewPostButton: false,
autohideFloatingPostButton: false,
pauseOnUnfocused: true,
stopGifs: true,
replyVisibility: 'all',
thirdColumnMode: 'notifications',
notificationVisibility: {
follows: true,
mentions: true,
statuses: true,
likes: true,
repeats: true,
moves: true,
emojiReactions: true,
followRequest: true,
reports: true,
chatMention: true,
polls: true
},
notificationNative: {
follows: true,
mentions: true,
statuses: true,
likes: false,
repeats: false,
moves: false,
emojiReactions: false,
followRequest: true,
reports: true,
chatMention: true,
polls: true
},
webPushNotifications: false,
webPushAlwaysShowNotifications: false,
muteWords: [],
highlight: {},
interfaceLanguage: browserLocale,
hideScopeNotice: false,
useStreamingApi: false,
sidebarRight: undefined, // instance default
scopeCopy: undefined, // instance default
subjectLineBehavior: undefined, // instance default
alwaysShowSubjectInput: undefined, // instance default
postContentType: undefined, // instance default
minimalScopesMode: undefined, // instance default
// This hides statuses filtered via a word filter
hideFilteredStatuses: undefined, // instance default
modalOnRepeat: undefined, // instance default
modalOnUnfollow: undefined, // instance default
modalOnBlock: undefined, // instance default
modalOnMute: undefined, // instance default
modalOnMuteConversation: undefined, // instance default
modalOnMuteDomain: undefined, // instance default
modalOnDelete: undefined, // instance default
modalOnLogout: undefined, // instance default
modalOnApproveFollow: undefined, // instance default
modalOnDenyFollow: undefined, // instance default
modalOnRemoveUserFromFollowers: undefined, // instance default
modalMobileCenter: undefined,
playVideosInModal: false,
useOneClickNsfw: false,
useContainFit: true,
disableStickyHeaders: false,
showScrollbars: false,
userPopoverAvatarAction: 'open',
userPopoverOverlay: false,
sidebarColumnWidth: '25rem',
contentColumnWidth: '45rem',
notifsColumnWidth: '25rem',
emojiReactionsScale: undefined,
textSize: undefined, // instance default
emojiSize: undefined, // instance default
navbarSize: undefined, // instance default
panelHeaderSize: undefined, // instance default
forcedRoundness: undefined, // instance default
navbarColumnStretch: false,
greentext: undefined, // instance default
useAtIcon: undefined, // instance default
mentionLinkDisplay: undefined, // instance default
mentionLinkShowTooltip: undefined, // instance default
mentionLinkShowAvatar: undefined, // instance default
mentionLinkFadeDomain: undefined, // instance default
mentionLinkShowYous: undefined, // instance default
mentionLinkBoldenYou: undefined, // instance default
hidePostStats: undefined, // instance default
hideBotIndication: undefined, // instance default
hideUserStats: undefined, // instance default
virtualScrolling: undefined, // instance default
sensitiveByDefault: undefined, // instance default
conversationDisplay: undefined, // instance default
conversationTreeAdvanced: undefined, // instance default
conversationOtherRepliesButton: undefined, // instance default
conversationTreeFadeAncestors: undefined, // instance default
showExtraNotifications: undefined, // instance default
showExtraNotificationsTip: undefined, // instance default
showChatsInExtraNotifications: undefined, // instance default
showAnnouncementsInExtraNotifications: undefined, // instance default
showFollowRequestsInExtraNotifications: undefined, // instance default
maxDepthInThread: undefined, // instance default
autocompleteSelect: undefined, // instance default
closingDrawerMarksAsSeen: undefined, // instance default
unseenAtTop: undefined, // instance default
ignoreInactionableSeen: undefined, // instance default
unsavedPostAction: undefined, // instance default
autoSaveDraft: undefined, // instance default
useAbsoluteTimeFormat: undefined, // instance default
absoluteTimeFormatMinAge: undefined, // instance default
absoluteTime12h: undefined, // instance default
imageCompression: true
}

View file

@ -1,25 +0,0 @@
const editStatus = {
state: {
params: null,
modalActivated: false
},
mutations: {
openEditStatusModal (state, params) {
state.params = params
state.modalActivated = true
},
closeEditStatusModal (state) {
state.modalActivated = false
}
},
actions: {
openEditStatusModal ({ commit }, params) {
commit('openEditStatusModal', params)
},
closeEditStatusModal ({ commit }) {
commit('closeEditStatusModal')
}
}
}
export default editStatus

View file

@ -1,6 +1,7 @@
import apiService from '../services/api/api.service.js' import apiService from '../services/api/api.service.js'
import { instanceDefaultProperties } from './config.js' import { instanceDefaultProperties } from './config.js'
import { langCodeToCldrName, ensureFinalFallback } from '../i18n/languages.js' import { langCodeToCldrName, ensureFinalFallback } from '../i18n/languages.js'
import { useInterfaceStore } from '../stores/interface.js'
const SORTED_EMOJI_GROUP_IDS = [ const SORTED_EMOJI_GROUP_IDS = [
'smileys-and-emotion', 'smileys-and-emotion',
@ -292,7 +293,7 @@ const instance = {
commit('setInstanceOption', { name, value }) commit('setInstanceOption', { name, value })
switch (name) { switch (name) {
case 'name': case 'name':
dispatch('setPageTitle') useInterfaceStore().setPageTitle()
break break
case 'shoutAvailable': case 'shoutAvailable':
if (value) { if (value) {

View file

@ -1,40 +0,0 @@
import fileTypeService from '../services/file_type/file_type.service.js'
const supportedTypes = new Set(['image', 'video', 'audio', 'flash'])
const mediaViewer = {
state: {
media: [],
currentIndex: 0,
activated: false
},
mutations: {
setMedia (state, media) {
state.media = media
},
setCurrentMedia (state, index) {
state.activated = true
state.currentIndex = index
},
close (state) {
state.activated = false
}
},
actions: {
setMedia ({ commit }, attachments) {
const media = attachments.filter(attachment => {
const type = fileTypeService.fileType(attachment.mimetype)
return supportedTypes.has(type)
})
commit('setMedia', media)
},
setCurrentMedia ({ commit, state }, current) {
const index = state.media.indexOf(current)
commit('setCurrentMedia', index || 0)
},
closeMediaViewer ({ commit }) {
commit('close')
}
}
}
export default mediaViewer

View file

@ -11,6 +11,8 @@ import {
closeAllDesktopNotifications closeAllDesktopNotifications
} from '../services/desktop_notification_utils/desktop_notification_utils.js' } from '../services/desktop_notification_utils/desktop_notification_utils.js'
import { useReportsStore } from '../stores/reports.js'
const emptyNotifications = () => ({ const emptyNotifications = () => ({
desktopNotificationSilence: true, desktopNotificationSilence: true,
maxId: 0, maxId: 0,
@ -94,7 +96,7 @@ export const notifications = {
validNotifications.forEach(notification => { validNotifications.forEach(notification => {
if (notification.type === 'pleroma:report') { if (notification.type === 'pleroma:report') {
dispatch('addReport', notification.report) useReportsStore().addReport(notification.report)
} }
if (notification.type === 'pleroma:emoji_reaction') { if (notification.type === 'pleroma:emoji_reaction') {

View file

@ -1,69 +0,0 @@
import { merge } from 'lodash'
const polls = {
state: {
// Contains key = id, value = number of trackers for this poll
trackedPolls: {},
pollsObject: {}
},
mutations: {
mergeOrAddPoll (state, poll) {
const existingPoll = state.pollsObject[poll.id]
// Make expired-state change trigger re-renders properly
poll.expired = Date.now() > Date.parse(poll.expires_at)
if (existingPoll) {
state.pollsObject[poll.id] = merge(existingPoll, poll)
} else {
state.pollsObject[poll.id] = poll
}
},
trackPoll (state, pollId) {
const currentValue = state.trackedPolls[pollId]
if (currentValue) {
state.trackedPolls[pollId] = currentValue + 1
} else {
state.trackedPolls[pollId] = 1
}
},
untrackPoll (state, pollId) {
const currentValue = state.trackedPolls[pollId]
if (currentValue) {
state.trackedPolls[pollId] = currentValue - 1
} else {
state.trackedPolls[pollId] = 0
}
}
},
actions: {
mergeOrAddPoll ({ commit }, poll) {
commit('mergeOrAddPoll', poll)
},
updateTrackedPoll ({ rootState, dispatch, commit }, pollId) {
rootState.api.backendInteractor.fetchPoll({ pollId }).then(poll => {
setTimeout(() => {
if (rootState.polls.trackedPolls[pollId]) {
dispatch('updateTrackedPoll', pollId)
}
}, 30 * 1000)
commit('mergeOrAddPoll', poll)
})
},
trackPoll ({ rootState, commit, dispatch }, pollId) {
if (!rootState.polls.trackedPolls[pollId]) {
setTimeout(() => dispatch('updateTrackedPoll', pollId), 30 * 1000)
}
commit('trackPoll', pollId)
},
untrackPoll ({ commit }, pollId) {
commit('untrackPoll', pollId)
},
votePoll ({ rootState, commit }, { id, pollId, choices }) {
return rootState.api.backendInteractor.vote({ pollId, choices }).then(poll => {
commit('mergeOrAddPoll', poll)
return poll
})
}
}
}
export default polls

View file

@ -1,31 +0,0 @@
const postStatus = {
state: {
params: null,
modalActivated: false
},
mutations: {
openPostStatusModal (state, params) {
state.params = params
state.modalActivated = true
},
closePostStatusModal (state) {
state.modalActivated = false
},
resetPostStatusModal (state) {
state.params = null
}
},
actions: {
openPostStatusModal ({ commit }, params) {
commit('openPostStatusModal', params)
},
closePostStatusModal ({ commit }) {
commit('closePostStatusModal')
},
resetPostStatusModal ({ commit }) {
commit('resetPostStatusModal')
}
}
}
export default postStatus

View file

@ -1,64 +0,0 @@
import filter from 'lodash/filter'
const reports = {
state: {
reportModal: {
userId: null,
statuses: [],
preTickedIds: [],
activated: false
},
reports: {}
},
mutations: {
openUserReportingModal (state, { userId, statuses, preTickedIds }) {
state.reportModal.userId = userId
state.reportModal.statuses = statuses
state.reportModal.preTickedIds = preTickedIds
state.reportModal.activated = true
},
closeUserReportingModal (state) {
state.reportModal.activated = false
},
setReportState (reportsState, { id, state }) {
reportsState.reports[id].state = state
},
addReport (state, report) {
state.reports[report.id] = report
}
},
actions: {
openUserReportingModal ({ rootState, commit }, { userId, statusIds = [] }) {
const preTickedStatuses = statusIds.map(id => rootState.statuses.allStatusesObject[id])
const preTickedIds = statusIds
const statuses = preTickedStatuses.concat(
filter(rootState.statuses.allStatuses,
status => status.user.id === userId && !preTickedIds.includes(status.id)
)
)
commit('openUserReportingModal', { userId, statuses, preTickedIds })
},
closeUserReportingModal ({ commit }) {
commit('closeUserReportingModal')
},
setReportState ({ commit, dispatch, rootState }, { id, state }) {
const oldState = rootState.reports.reports[id].state
commit('setReportState', { id, state })
rootState.api.backendInteractor.setReportState({ id, state }).catch(e => {
console.error('Failed to set report state', e)
dispatch('pushGlobalNotice', {
level: 'error',
messageKey: 'general.generic_error_message',
messageArgs: [e.message],
timeout: 5000
})
commit('setReportState', { id, state: oldState })
})
},
addReport ({ commit }, report) {
commit('addReport', report)
}
}
}
export default reports

View file

@ -1,46 +0,0 @@
const shout = {
state: {
messages: [],
channel: { state: '' },
joined: false
},
mutations: {
setChannel (state, channel) {
state.channel = channel
},
addMessage (state, message) {
state.messages.push(message)
state.messages = state.messages.slice(-19, 20)
},
setMessages (state, messages) {
state.messages = messages.slice(-19, 20)
},
setJoined (state, joined) {
state.joined = joined
}
},
actions: {
initializeShout (store, socket) {
const channel = socket.channel('chat:public')
channel.joinPush.receive('ok', () => {
store.commit('setJoined', true)
})
channel.onClose(() => {
store.commit('setJoined', false)
})
channel.onError(() => {
store.commit('setJoined', false)
})
channel.on('new_msg', (msg) => {
store.commit('addMessage', msg)
})
channel.on('messages', ({ messages }) => {
store.commit('setMessages', messages)
})
channel.join()
store.commit('setChannel', channel)
}
}
}
export default shout

View file

@ -1,25 +0,0 @@
const statusHistory = {
state: {
params: {},
modalActivated: false
},
mutations: {
openStatusHistoryModal (state, params) {
state.params = params
state.modalActivated = true
},
closeStatusHistoryModal (state) {
state.modalActivated = false
}
},
actions: {
openStatusHistoryModal ({ commit }, params) {
commit('openStatusHistoryModal', params)
},
closeStatusHistoryModal ({ commit }) {
commit('closeStatusHistoryModal')
}
}
}
export default statusHistory

View file

@ -13,6 +13,7 @@ import {
omitBy omitBy
} from 'lodash' } from 'lodash'
import apiService from '../services/api/api.service.js' import apiService from '../services/api/api.service.js'
import { useInterfaceStore } from 'src/stores/interface'
const emptyTl = (userId = 0) => ({ const emptyTl = (userId = 0) => ({
statuses: [], statuses: [],
@ -510,7 +511,7 @@ const statuses = {
commit('setDeleted', { status }) commit('setDeleted', { status })
}) })
.catch((e) => { .catch((e) => {
dispatch('pushGlobalNotice', { useInterfaceStore().pushGlobalNotice({
level: 'error', level: 'error',
messageKey: 'status.delete_error', messageKey: 'status.delete_error',
messageArgs: [e.message], messageArgs: [e.message],

View file

@ -3,6 +3,7 @@ import { windowWidth, windowHeight } from '../services/window_utils/window_utils
import oauthApi from '../services/new_api/oauth.js' import oauthApi from '../services/new_api/oauth.js'
import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash' import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash'
import { registerPushNotifications, unregisterPushNotifications } from '../services/sw/sw.js' import { registerPushNotifications, unregisterPushNotifications } from '../services/sw/sw.js'
import { useInterfaceStore } from '../stores/interface.js'
// TODO: Unify with mergeOrAdd in statuses.js // TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => { export const mergeOrAdd = (arr, obj, item) => {
@ -584,9 +585,9 @@ const users = {
store.commit('clearNotifications') store.commit('clearNotifications')
store.commit('resetStatuses') store.commit('resetStatuses')
store.dispatch('resetChats') store.dispatch('resetChats')
store.dispatch('setLastTimeline', 'public-timeline') useInterfaceStore().setLastTimeline('public-timeline')
store.dispatch('setLayoutWidth', windowWidth()) useInterfaceStore().setLayoutWidth(windowWidth())
store.dispatch('setLayoutHeight', windowHeight()) useInterfaceStore().setLayoutHeight(windowHeight())
store.commit('clearServerSideStorage') store.commit('clearServerSideStorage')
}) })
}, },
@ -611,7 +612,7 @@ const users = {
dispatch('fetchEmoji') dispatch('fetchEmoji')
getNotificationPermission() getNotificationPermission()
.then(permission => commit('setNotificationPermission', permission)) .then(permission => useInterfaceStore().setNotificationPermission(permission))
// Set our new backend interactor // Set our new backend interactor
commit('setBackendInteractor', backendInteractorService(accessToken)) commit('setBackendInteractor', backendInteractorService(accessToken))
@ -658,8 +659,8 @@ const users = {
// Get user mutes // Get user mutes
dispatch('fetchMutes') dispatch('fetchMutes')
dispatch('setLayoutWidth', windowWidth()) useInterfaceStore().setLayoutWidth(windowWidth())
dispatch('setLayoutHeight', windowHeight()) useInterfaceStore().setLayoutHeight(windowHeight())
// Fetch our friends // Fetch our friends
store.rootState.api.backendInteractor.fetchFriends({ id: user.id }) store.rootState.api.backendInteractor.fetchFriends({ id: user.id })

View file

@ -1,10 +1,11 @@
import { useListsStore } from '../../stores/lists.js'
import apiService from '../api/api.service.js' import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js' import { promiseInterval } from '../promise_interval/promise_interval.js'
const fetchAndUpdate = ({ store, credentials }) => { const fetchAndUpdate = ({ store, credentials }) => {
return apiService.fetchLists({ credentials }) return apiService.fetchLists({ credentials })
.then(lists => { .then(lists => {
store.commit('setLists', lists) useListsStore().setLists(lists)
}, () => {}) }, () => {})
.catch(() => {}) .catch(() => {})
} }

View file

@ -1,5 +1,7 @@
import { muteWordHits } from '../status_parser/status_parser.js' import { muteWordHits } from '../status_parser/status_parser.js'
import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js' import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js'
import { useI18nStore } from '../../stores/i18n.js'
import { useAnnouncementsStore } from 'src/stores/announcements'
import FaviconService from 'src/services/favicon_service/favicon_service.js' import FaviconService from 'src/services/favicon_service/favicon_service.js'
@ -64,13 +66,12 @@ const isMutedNotification = (store, notification) => {
export const maybeShowNotification = (store, notification) => { export const maybeShowNotification = (store, notification) => {
const rootState = store.rootState || store.state const rootState = store.rootState || store.state
const rootGetters = store.rootGetters || store.getters
if (notification.seen) return if (notification.seen) return
if (!visibleTypes(store).includes(notification.type)) return if (!visibleTypes(store).includes(notification.type)) return
if (notification.type === 'mention' && isMutedNotification(store, notification)) return if (notification.type === 'mention' && isMutedNotification(store, notification)) return
const notificationObject = prepareNotificationObject(notification, rootGetters.i18n) const notificationObject = prepareNotificationObject(notification, useI18nStore().i18n)
showDesktopNotification(rootState, notificationObject) showDesktopNotification(rootState, notificationObject)
} }
@ -169,7 +170,7 @@ export const countExtraNotifications = (store) => {
return [ return [
mergedConfig.showChatsInExtraNotifications ? rootGetters.unreadChatCount : 0, mergedConfig.showChatsInExtraNotifications ? rootGetters.unreadChatCount : 0,
mergedConfig.showAnnouncementsInExtraNotifications ? rootGetters.unreadAnnouncementCount : 0, mergedConfig.showAnnouncementsInExtraNotifications ? useAnnouncementsStore().unreadAnnouncementCount : 0,
mergedConfig.showFollowRequestsInExtraNotifications ? rootGetters.followRequestCount : 0 mergedConfig.showFollowRequestsInExtraNotifications ? rootGetters.followRequestCount : 0
].reduce((a, c) => a + c, 0) ].reduce((a, c) => a + c, 0)
} }

View file

@ -1,3 +1,4 @@
import { useInterfaceStore } from '../../stores/interface.js'
import apiService from '../api/api.service.js' import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js' import { promiseInterval } from '../promise_interval/promise_interval.js'
@ -78,7 +79,7 @@ const fetchNotifications = ({ store, args, older }) => {
return notifications return notifications
}) })
.catch((error) => { .catch((error) => {
store.dispatch('pushGlobalNotice', { useInterfaceStore().pushGlobalNotice({
level: 'error', level: 'error',
messageKey: 'notifications.error', messageKey: 'notifications.error',
messageArgs: [error.message], messageArgs: [error.message],

View file

@ -1,10 +1,12 @@
import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js' import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js'
import { getCssRules } from '../theme_data/css_utils.js' import { getCssRules } from '../theme_data/css_utils.js'
import { defaultState } from '../../modules/config.js' import { defaultState } from 'src/modules/default_config_state.js'
import { chunk } from 'lodash' import { chunk } from 'lodash'
import pako from 'pako' import pako from 'pako'
import localforage from 'localforage' import localforage from 'localforage'
console.log('CONFIG', defaultState)
// On platforms where this is not supported, it will return undefined // On platforms where this is not supported, it will return undefined
// Otherwise it will return an array // Otherwise it will return an array
const supportsAdoptedStyleSheets = !!document.adoptedStyleSheets const supportsAdoptedStyleSheets = !!document.adoptedStyleSheets
@ -212,6 +214,7 @@ const extractStyleConfig = ({
return result return result
} }
console.log(defaultState)
const defaultStyleConfig = extractStyleConfig(defaultState) const defaultStyleConfig = extractStyleConfig(defaultState)
export const applyConfig = (input, i18n) => { export const applyConfig = (input, i18n) => {

View file

@ -2,6 +2,7 @@ import { camelCase } from 'lodash'
import apiService from '../api/api.service.js' import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js' import { promiseInterval } from '../promise_interval/promise_interval.js'
import { useInterfaceStore } from '../../stores/interface.js'
const update = ({ store, statuses, timeline, showImmediately, userId, listId, pagination }) => { const update = ({ store, statuses, timeline, showImmediately, userId, listId, pagination }) => {
const ccTimeline = camelCase(timeline) const ccTimeline = camelCase(timeline)
@ -73,7 +74,7 @@ const fetchAndUpdate = ({
return { statuses, pagination } return { statuses, pagination }
}) })
.catch((error) => { .catch((error) => {
store.dispatch('pushGlobalNotice', { useInterfaceStore().pushGlobalNotice({
level: 'error', level: 'error',
messageKey: 'timeline.error', messageKey: 'timeline.error',
messageArgs: [error.message], messageArgs: [error.message],

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()
})
}
}
})

17
src/stores/editStatus.js Normal file
View file

@ -0,0 +1,17 @@
import { defineStore } from 'pinia'
export const useEditStatusStore = defineStore('editStatus', {
state: () => ({
params: null,
modalActivated: false
}),
actions: {
openEditStatusModal (params) {
this.params = params
this.modalActivated = true
},
closeEditStatusModal () {
this.modalActivated = false
}
}
})

14
src/stores/i18n.js Normal file
View file

@ -0,0 +1,14 @@
import { defineStore } from 'pinia'
export const useI18nStore = defineStore('i18n', {
state: () => ({
i18n: null
}),
actions: {
setI18n (newI18n) {
this.$patch({
i18n: newI18n.global
})
}
}
})

View file

@ -1,163 +1,109 @@
import { getResourcesIndex, applyTheme, tryLoadCache } from '../services/style_setter/style_setter.js' import { defineStore } from 'pinia'
import { CURRENT_VERSION, generatePreset } from 'src/services/theme_data/theme_data.service.js' import { CURRENT_VERSION, generatePreset } from 'src/services/theme_data/theme_data.service.js'
import { getResourcesIndex, applyTheme, tryLoadCache } from '../services/style_setter/style_setter.js'
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js' import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
import { deserialize } from '../services/theme_data/iss_deserializer.js' import { deserialize } from '../services/theme_data/iss_deserializer.js'
// helper for debugging export const useInterfaceStore = defineStore('interface', {
// eslint-disable-next-line no-unused-vars state: () => ({
const toValue = (x) => JSON.parse(JSON.stringify(x === undefined ? 'null' : x)) localFonts: null,
themeApplied: false,
const defaultState = { themeChangeInProgress: false,
localFonts: null, themeVersion: 'v3',
themeApplied: false, styleNameUsed: null,
themeChangeInProgress: false, styleDataUsed: null,
themeVersion: 'v3', useStylePalette: false, // hack for applying styles from appearance tab
styleNameUsed: null, paletteNameUsed: null,
styleDataUsed: null, paletteDataUsed: null,
useStylePalette: false, // hack for applying styles from appearance tab themeNameUsed: null,
paletteNameUsed: null, themeDataUsed: null,
paletteDataUsed: null, temporaryChangesTimeoutId: null, // used for temporary options that revert after a timeout
themeNameUsed: null, temporaryChangesConfirm: () => {}, // used for applying temporary options
themeDataUsed: null, temporaryChangesRevert: () => {}, // used for reverting temporary options
temporaryChangesTimeoutId: null, // used for temporary options that revert after a timeout settingsModalState: 'hidden',
temporaryChangesConfirm: () => {}, // used for applying temporary options settingsModalLoadedUser: false,
temporaryChangesRevert: () => {}, // used for reverting temporary options settingsModalLoadedAdmin: false,
settingsModalState: 'hidden', settingsModalTargetTab: null,
settingsModalLoadedUser: false, settingsModalMode: 'user',
settingsModalLoadedAdmin: false, settings: {
settingsModalTargetTab: null, currentSaveStateNotice: null,
settingsModalMode: 'user', noticeClearTimeout: null,
settings: { notificationPermission: null
currentSaveStateNotice: null, },
noticeClearTimeout: null, browserSupport: {
notificationPermission: null cssFilter: window.CSS && window.CSS.supports && (
}, window.CSS.supports('filter', 'drop-shadow(0 0)') ||
browserSupport: { window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
cssFilter: window.CSS && window.CSS.supports && ( ),
window.CSS.supports('filter', 'drop-shadow(0 0)') || localFonts: typeof window.queryLocalFonts === 'function'
window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)') },
), layoutType: 'normal',
localFonts: typeof window.queryLocalFonts === 'function' globalNotices: [],
}, layoutHeight: 0,
layoutType: 'normal', lastTimeline: null
globalNotices: [], }),
layoutHeight: 0, actions: {
lastTimeline: null setPageTitle (option = '') {
} try {
document.title = `${option} ${window.vuex.state.instance.name}`
const interfaceMod = { } catch (error) {
state: defaultState, console.error(`${error}`)
mutations: {
settingsSaved (state, { success, error }) {
if (success) {
if (state.noticeClearTimeout) {
clearTimeout(state.noticeClearTimeout)
}
state.settings.currentSaveStateNotice = { error: false, data: success }
state.settings.noticeClearTimeout = setTimeout(() => delete state.settings.currentSaveStateNotice, 2000)
} else {
state.settings.currentSaveStateNotice = { error: true, errorData: error }
} }
}, },
setTemporaryChanges (state, { timeoutId, confirm, revert }) { settingsSaved ({ success, error }) {
state.temporaryChangesTimeoutId = timeoutId if (success) {
state.temporaryChangesConfirm = confirm if (this.noticeClearTimeout) {
state.temporaryChangesRevert = revert clearTimeout(this.noticeClearTimeout)
}
this.settings.currentSaveStateNotice = { error: false, data: success }
this.settings.noticeClearTimeout = setTimeout(() => delete this.settings.currentSaveStateNotice, 2000)
} else {
this.settings.currentSaveStateNotice = { error: true, errorData: error }
}
}, },
clearTemporaryChanges (state) { setNotificationPermission (permission) {
clearTimeout(state.temporaryChangesTimeoutId) this.notificationPermission = permission
state.temporaryChangesTimeoutId = null
state.temporaryChangesConfirm = () => {}
state.temporaryChangesRevert = () => {}
}, },
setThemeApplied (state) { closeSettingsModal () {
state.themeApplied = true this.settingsModalState = 'hidden'
}, },
setNotificationPermission (state, permission) { openSettingsModal (value) {
state.notificationPermission = permission this.settingsModalMode = value
this.settingsModalState = 'visible'
if (value === 'user') {
if (!this.settingsModalLoadedUser) {
this.settingsModalLoadedUser = true
}
} else if (value === 'admin') {
if (!this.settingsModalLoadedAdmin) {
this.settingsModalLoadedAdmin = true
}
}
}, },
setLayoutType (state, value) { togglePeekSettingsModal () {
state.layoutType = value switch (this.settingsModalState) {
},
closeSettingsModal (state) {
state.settingsModalState = 'hidden'
},
togglePeekSettingsModal (state) {
switch (state.settingsModalState) {
case 'minimized': case 'minimized':
state.settingsModalState = 'visible' this.settingsModalState = 'visible'
return return
case 'visible': case 'visible':
state.settingsModalState = 'minimized' this.settingsModalState = 'minimized'
return return
default: default:
throw new Error('Illegal minimization state of settings modal') throw new Error('Illegal minimization state of settings modal')
} }
}, },
openSettingsModal (state, value) { clearSettingsModalTargetTab () {
state.settingsModalMode = value this.settingsModalTargetTab = null
state.settingsModalState = 'visible'
if (value === 'user') {
if (!state.settingsModalLoadedUser) {
state.settingsModalLoadedUser = true
}
} else if (value === 'admin') {
if (!state.settingsModalLoadedAdmin) {
state.settingsModalLoadedAdmin = true
}
}
}, },
setSettingsModalTargetTab (state, value) { openSettingsModalTab (value, mode = 'user') {
state.settingsModalTargetTab = value this.settingsModalTargetTab = value
this.openSettingsModal(mode)
}, },
pushGlobalNotice (state, notice) { removeGlobalNotice (notice) {
state.globalNotices.push(notice) this.globalNotices = this.globalNotices.filter(n => n !== notice)
},
removeGlobalNotice (state, notice) {
state.globalNotices = state.globalNotices.filter(n => n !== notice)
},
setLayoutHeight (state, value) {
state.layoutHeight = value
},
setLayoutWidth (state, value) {
state.layoutWidth = value
},
setLastTimeline (state, value) {
state.lastTimeline = value
},
setFontsList (state, value) {
// Set is used here so that we filter out duplicate fonts (possibly same font but with different weight)
state.localFonts = [...(new Set(value.map(font => font.family))).values()]
}
},
actions: {
setPageTitle ({ rootState }, option = '') {
document.title = `${option} ${rootState.instance.name}`
},
settingsSaved ({ commit, dispatch }, { success, error }) {
commit('settingsSaved', { success, error })
},
setNotificationPermission ({ commit }, permission) {
commit('setNotificationPermission', permission)
},
closeSettingsModal ({ commit }) {
commit('closeSettingsModal')
},
openSettingsModal ({ commit }, value = 'user') {
commit('openSettingsModal', value)
},
togglePeekSettingsModal ({ commit }) {
commit('togglePeekSettingsModal')
},
clearSettingsModalTargetTab ({ commit }) {
commit('setSettingsModalTargetTab', null)
},
openSettingsModalTab ({ commit }, value) {
commit('setSettingsModalTargetTab', value)
commit('openSettingsModal', 'user')
}, },
pushGlobalNotice ( pushGlobalNotice (
{ commit, dispatch, state },
{ {
messageKey, messageKey,
messageArgs = {}, messageArgs = {},
@ -169,52 +115,56 @@ const interfaceMod = {
messageArgs, messageArgs,
level level
} }
commit('pushGlobalNotice', notice)
this.globalNotices.push(notice)
// Adding a new element to array wraps it in a Proxy, which breaks the comparison // Adding a new element to array wraps it in a Proxy, which breaks the comparison
// TODO: Generate UUID or something instead or relying on !== operator? // TODO: Generate UUID or something instead or relying on !== operator?
const newNotice = state.globalNotices[state.globalNotices.length - 1] const newNotice = this.globalNotices[this.globalNotices.length - 1]
if (timeout) { if (timeout) {
setTimeout(() => dispatch('removeGlobalNotice', newNotice), timeout) setTimeout(() => this.removeGlobalNotice(newNotice), timeout)
} }
return newNotice return newNotice
}, },
removeGlobalNotice ({ commit }, notice) { setLayoutHeight (value) {
commit('removeGlobalNotice', notice) this.layoutHeight = value
}, },
setLayoutHeight ({ commit }, value) { setLayoutWidth (value) {
commit('setLayoutHeight', value)
},
// value is optional, assuming it was cached prior
setLayoutWidth ({ commit, state, rootGetters, rootState }, value) {
let width = value let width = value
if (value !== undefined) { if (value !== undefined) {
commit('setLayoutWidth', value) this.layoutWidth = value
} else { } else {
width = state.layoutWidth width = this.layoutWidth
} }
const mobileLayout = width <= 800 const mobileLayout = width <= 800
const normalOrMobile = mobileLayout ? 'mobile' : 'normal' const normalOrMobile = mobileLayout ? 'mobile' : 'normal'
const { thirdColumnMode } = rootGetters.mergedConfig const { thirdColumnMode } = window.vuex.getters.mergedConfig
if (thirdColumnMode === 'none' || !rootState.users.currentUser) { if (thirdColumnMode === 'none' || !window.vuex.state.users.currentUser) {
commit('setLayoutType', normalOrMobile) this.layoutType = normalOrMobile
} else { } else {
const wideLayout = width >= 1300 const wideLayout = width >= 1300
commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile) this.layoutType = wideLayout ? 'wide' : normalOrMobile
} }
}, },
queryLocalFonts ({ commit, dispatch, state }) { setFontsList (value) {
if (state.localFonts !== null) return this.localFonts = [...(new Set(value.map(font => font.family))).values()]
commit('setFontsList', []) },
if (!state.browserSupport.localFonts) { queryLocalFonts () {
if (this.localFonts !== null) return
this.setFontsList([])
if (!this.browserSupport.localFonts) {
return return
} }
window window
.queryLocalFonts() .queryLocalFonts()
.then((fonts) => { .then((fonts) => {
commit('setFontsList', fonts) this.setFontsList(fonts)
}) })
.catch((e) => { .catch((e) => {
dispatch('pushGlobalNotice', { this.pushGlobalNotice({
messageKey: 'settings.style.themes3.font.font_list_unavailable', messageKey: 'settings.style.themes3.font.font_list_unavailable',
messageArgs: { messageArgs: {
error: e error: e
@ -223,118 +173,118 @@ const interfaceMod = {
}) })
}) })
}, },
setLastTimeline ({ commit }, value) { setLastTimeline (value) {
commit('setLastTimeline', value) this.lastTimeline = value
}, },
async fetchPalettesIndex ({ commit, state }) { async fetchPalettesIndex () {
try { try {
const value = await getResourcesIndex('/static/palettes/index.json') const value = await getResourcesIndex('/static/palettes/index.json')
commit('setInstanceOption', { name: 'palettesIndex', value }) window.vuex.commit('setInstanceOption', { name: 'palettesIndex', value })
return value return value
} catch (e) { } catch (e) {
console.error('Could not fetch palettes index', e) console.error('Could not fetch palettes index', e)
commit('setInstanceOption', { name: 'palettesIndex', value: { _error: e } }) window.vuex.commit('setInstanceOption', { name: 'palettesIndex', value: { _error: e } })
return Promise.resolve({}) return Promise.resolve({})
} }
}, },
setPalette ({ dispatch, commit }, value) { setPalette (value) {
dispatch('resetThemeV3Palette') this.resetThemeV3Palette()
dispatch('resetThemeV2') this.resetThemeV2()
commit('setOption', { name: 'palette', value }) window.vuex.commit('setOption', { name: 'palette', value })
dispatch('applyTheme', { recompile: true }) this.applyTheme({ recompile: true })
}, },
setPaletteCustom ({ dispatch, commit }, value) { setPaletteCustom (value) {
dispatch('resetThemeV3Palette') this.resetThemeV3Palette()
dispatch('resetThemeV2') this.resetThemeV2()
commit('setOption', { name: 'paletteCustomData', value }) window.vuex.commit('setOption', { name: 'paletteCustomData', value })
dispatch('applyTheme', { recompile: true }) this.applyTheme({ recompile: true })
}, },
async fetchStylesIndex ({ commit, state }) { async fetchStylesIndex () {
try { try {
const value = await getResourcesIndex( const value = await getResourcesIndex(
'/static/styles/index.json', '/static/styles/index.json',
deserialize deserialize
) )
commit('setInstanceOption', { name: 'stylesIndex', value }) window.vuex.commit('setInstanceOption', { name: 'stylesIndex', value })
return value return value
} catch (e) { } catch (e) {
console.error('Could not fetch styles index', e) console.error('Could not fetch styles index', e)
commit('setInstanceOption', { name: 'stylesIndex', value: { _error: e } }) window.vuex.commit('setInstanceOption', { name: 'stylesIndex', value: { _error: e } })
return Promise.resolve({}) return Promise.resolve({})
} }
}, },
setStyle ({ dispatch, commit, state }, value) { setStyle (value) {
dispatch('resetThemeV3') this.resetThemeV3()
dispatch('resetThemeV2') this.resetThemeV2()
dispatch('resetThemeV3Palette') this.resetThemeV3Palette()
commit('setOption', { name: 'style', value }) window.vuex.commit('setOption', { name: 'style', value })
state.useStylePalette = true this.useStylePalette = true
dispatch('applyTheme', { recompile: true }).then(() => { this.applyTheme({ recompile: true }).then(() => {
state.useStylePalette = false this.useStylePalette = false
}) })
}, },
setStyleCustom ({ dispatch, commit, state }, value) { setStyleCustom (value) {
dispatch('resetThemeV3') this.resetThemeV3()
dispatch('resetThemeV2') this.resetThemeV2()
dispatch('resetThemeV3Palette') this.resetThemeV3Palette()
commit('setOption', { name: 'styleCustomData', value }) window.vuex.commit('setOption', { name: 'styleCustomData', value })
state.useStylePalette = true this.useStylePalette = true
dispatch('applyTheme', { recompile: true }).then(() => { this.applyTheme({ recompile: true }).then(() => {
state.useStylePalette = false this.useStylePalette = false
}) })
}, },
async fetchThemesIndex ({ commit, state }) { async fetchThemesIndex () {
try { try {
const value = await getResourcesIndex('/static/styles.json') const value = await getResourcesIndex('/static/styles.json')
commit('setInstanceOption', { name: 'themesIndex', value }) window.vuex.commit('setInstanceOption', { name: 'themesIndex', value })
return value return value
} catch (e) { } catch (e) {
console.error('Could not fetch themes index', e) console.error('Could not fetch themes index', e)
commit('setInstanceOption', { name: 'themesIndex', value: { _error: e } }) window.vuex.commit('setInstanceOption', { name: 'themesIndex', value: { _error: e } })
return Promise.resolve({}) return Promise.resolve({})
} }
}, },
setTheme ({ dispatch, commit }, value) { setTheme (value) {
dispatch('resetThemeV3') this.resetThemeV3()
dispatch('resetThemeV3Palette') this.resetThemeV3Palette()
dispatch('resetThemeV2') this.resetThemeV2()
commit('setOption', { name: 'theme', value }) window.vuex.commit('setOption', { name: 'theme', value })
dispatch('applyTheme', { recompile: true }) this.applyTheme({ recompile: true })
}, },
setThemeCustom ({ dispatch, commit }, value) { setThemeCustom (value) {
dispatch('resetThemeV3') this.resetThemeV3()
dispatch('resetThemeV3Palette') this.resetThemeV3Palette()
dispatch('resetThemeV2') this.resetThemeV2()
commit('setOption', { name: 'customTheme', value }) window.vuex.commit('setOption', { name: 'customTheme', value })
commit('setOption', { name: 'customThemeSource', value }) window.vuex.commit('setOption', { name: 'customThemeSource', value })
dispatch('applyTheme', { recompile: true }) this.applyTheme({ recompile: true })
}, },
resetThemeV3 ({ dispatch, commit }) { resetThemeV3 () {
commit('setOption', { name: 'style', value: null }) window.vuex.commit('setOption', { name: 'style', value: null })
commit('setOption', { name: 'styleCustomData', value: null }) window.vuex.commit('setOption', { name: 'styleCustomData', value: null })
}, },
resetThemeV3Palette ({ dispatch, commit }) { resetThemeV3Palette () {
commit('setOption', { name: 'palette', value: null }) window.vuex.commit('setOption', { name: 'palette', value: null })
commit('setOption', { name: 'paletteCustomData', value: null }) window.vuex.commit('setOption', { name: 'paletteCustomData', value: null })
}, },
resetThemeV2 ({ dispatch, commit }) { resetThemeV2 () {
commit('setOption', { name: 'theme', value: null }) window.vuex.commit('setOption', { name: 'theme', value: null })
commit('setOption', { name: 'customTheme', value: null }) window.vuex.commit('setOption', { name: 'customTheme', value: null })
commit('setOption', { name: 'customThemeSource', value: null }) window.vuex.commit('setOption', { name: 'customThemeSource', value: null })
}, },
async getThemeData ({ dispatch, commit, rootState, state }) { async getThemeData () {
const getData = async (resource, index, customData, name) => { const getData = async (resource, index, customData, name) => {
const capitalizedResource = resource[0].toUpperCase() + resource.slice(1) const capitalizedResource = resource[0].toUpperCase() + resource.slice(1)
const result = {} const result = {}
@ -358,7 +308,7 @@ const interfaceMod = {
} }
const newName = Object.keys(index)[0] const newName = Object.keys(index)[0]
fetchFunc = index[newName] fetchFunc = index[newName]
console.warn(`${capitalizedResource} with id '${state.styleNameUsed}' not found, trying back to '${newName}'`) console.warn(`${capitalizedResource} with id '${this.styleNameUsed}' not found, trying back to '${newName}'`)
if (!fetchFunc) { if (!fetchFunc) {
console.warn(`${capitalizedResource} doesn't have a fallback, defaulting to stock.`) console.warn(`${capitalizedResource} doesn't have a fallback, defaulting to stock.`)
fetchFunc = () => Promise.resolve(null) fetchFunc = () => Promise.resolve(null)
@ -372,27 +322,27 @@ const interfaceMod = {
const { const {
style: instanceStyleName, style: instanceStyleName,
palette: instancePaletteName palette: instancePaletteName
} = rootState.instance } = window.vuex.state.instance
let { let {
theme: instanceThemeV2Name, theme: instanceThemeV2Name,
themesIndex, themesIndex,
stylesIndex, stylesIndex,
palettesIndex palettesIndex
} = rootState.instance } = window.vuex.state.instance
const { const {
style: userStyleName, style: userStyleName,
styleCustomData: userStyleCustomData, styleCustomData: userStyleCustomData,
palette: userPaletteName, palette: userPaletteName,
paletteCustomData: userPaletteCustomData paletteCustomData: userPaletteCustomData
} = rootState.config } = window.vuex.state.config
let { let {
theme: userThemeV2Name, theme: userThemeV2Name,
customTheme: userThemeV2Snapshot, customTheme: userThemeV2Snapshot,
customThemeSource: userThemeV2Source customThemeSource: userThemeV2Source
} = rootState.config } = window.vuex.state.config
let majorVersionUsed let majorVersionUsed
@ -437,8 +387,8 @@ const interfaceMod = {
if (majorVersionUsed === 'v3') { if (majorVersionUsed === 'v3') {
const result = await Promise.all([ const result = await Promise.all([
dispatch('fetchPalettesIndex'), this.fetchPalettesIndex(),
dispatch('fetchStylesIndex') this.fetchStylesIndex()
]) ])
palettesIndex = result[0] palettesIndex = result[0]
@ -446,19 +396,19 @@ const interfaceMod = {
} else { } else {
// Promise.all just to be uniform with v3 // Promise.all just to be uniform with v3
const result = await Promise.all([ const result = await Promise.all([
dispatch('fetchThemesIndex') this.fetchThemesIndex()
]) ])
themesIndex = result[0] themesIndex = result[0]
} }
state.themeVersion = majorVersionUsed this.themeVersion = majorVersionUsed
console.debug('Version used', majorVersionUsed) console.debug('Version used', majorVersionUsed)
if (majorVersionUsed === 'v3') { if (majorVersionUsed === 'v3') {
state.themeDataUsed = null this.themeDataUsed = null
state.themeNameUsed = null this.themeNameUsed = null
const style = await getData( const style = await getData(
'style', 'style',
@ -466,8 +416,8 @@ const interfaceMod = {
userStyleCustomData, userStyleCustomData,
userStyleName || instanceStyleName userStyleName || instanceStyleName
) )
state.styleNameUsed = style.nameUsed this.styleNameUsed = style.nameUsed
state.styleDataUsed = style.dataUsed this.styleDataUsed = style.dataUsed
let firstStylePaletteName = null let firstStylePaletteName = null
style style
@ -492,21 +442,21 @@ const interfaceMod = {
'palette', 'palette',
palettesIndex, palettesIndex,
userPaletteCustomData, userPaletteCustomData,
state.useStylePalette ? firstStylePaletteName : (userPaletteName || instancePaletteName) this.useStylePalette ? firstStylePaletteName : (userPaletteName || instancePaletteName)
) )
if (state.useStylePalette) { if (this.useStylePalette) {
commit('setOption', { name: 'palette', value: firstStylePaletteName }) window.vuex.commit('setOption', { name: 'palette', value: firstStylePaletteName })
} }
state.paletteNameUsed = palette.nameUsed this.paletteNameUsed = palette.nameUsed
state.paletteDataUsed = palette.dataUsed this.paletteDataUsed = palette.dataUsed
if (state.paletteDataUsed) { if (this.paletteDataUsed) {
state.paletteDataUsed.link = state.paletteDataUsed.link || state.paletteDataUsed.accent this.paletteDataUsed.link = this.paletteDataUsed.link || this.paletteDataUsed.accent
state.paletteDataUsed.accent = state.paletteDataUsed.accent || state.paletteDataUsed.link this.paletteDataUsed.accent = this.paletteDataUsed.accent || this.paletteDataUsed.link
} }
if (Array.isArray(state.paletteDataUsed)) { if (Array.isArray(this.paletteDataUsed)) {
const [ const [
name, name,
bg, bg,
@ -518,7 +468,7 @@ const interfaceMod = {
cBlue = '#0000FF', cBlue = '#0000FF',
cOrange = '#E3FF00' cOrange = '#E3FF00'
] = palette.dataUsed ] = palette.dataUsed
state.paletteDataUsed = { this.paletteDataUsed = {
name, name,
bg, bg,
fg, fg,
@ -533,10 +483,10 @@ const interfaceMod = {
} }
console.debug('Palette data used', palette.dataUsed) console.debug('Palette data used', palette.dataUsed)
} else { } else {
state.styleNameUsed = null this.styleNameUsed = null
state.styleDataUsed = null this.styleDataUsed = null
state.paletteNameUsed = null this.paletteNameUsed = null
state.paletteDataUsed = null this.paletteDataUsed = null
const theme = await getData( const theme = await getData(
'theme', 'theme',
@ -544,41 +494,43 @@ const interfaceMod = {
userThemeV2Source || userThemeV2Snapshot, userThemeV2Source || userThemeV2Snapshot,
userThemeV2Name || instanceThemeV2Name userThemeV2Name || instanceThemeV2Name
) )
state.themeNameUsed = theme.nameUsed this.themeNameUsed = theme.nameUsed
state.themeDataUsed = theme.dataUsed this.themeDataUsed = theme.dataUsed
} }
}, },
async setThemeApplied () {
this.themeApplied = true
},
async applyTheme ( async applyTheme (
{ dispatch, commit, rootState, state },
{ recompile = false } = {} { recompile = false } = {}
) { ) {
const { const {
forceThemeRecompilation, forceThemeRecompilation,
themeDebug, themeDebug,
theme3hacks theme3hacks
} = rootState.config } = window.vuex.state.config
state.themeChangeInProgress = true this.themeChangeInProgress = true
// If we're not not forced to recompile try using // If we're not not forced to recompile try using
// cache (tryLoadCache return true if load successful) // cache (tryLoadCache return true if load successful)
const forceRecompile = forceThemeRecompilation || recompile const forceRecompile = forceThemeRecompilation || recompile
if (!forceRecompile && !themeDebug && await tryLoadCache()) { if (!forceRecompile && !themeDebug && await tryLoadCache()) {
state.themeChangeInProgress = false this.themeChangeInProgress = false
return commit('setThemeApplied') return this.setThemeApplied()
} }
window.splashUpdate('splash.theme') window.splashUpdate('splash.theme')
await dispatch('getThemeData') await this.getThemeData()
try { try {
const paletteIss = (() => { const paletteIss = (() => {
if (!state.paletteDataUsed) return null if (!this.paletteDataUsed) return null
const result = { const result = {
component: 'Root', component: 'Root',
directives: {} directives: {}
} }
Object Object
.entries(state.paletteDataUsed) .entries(this.paletteDataUsed)
.filter(([k]) => k !== 'name') .filter(([k]) => k !== 'name')
.forEach(([k, v]) => { .forEach(([k, v]) => {
let issRootDirectiveName let issRootDirectiveName
@ -597,7 +549,7 @@ const interfaceMod = {
return result return result
})() })()
const theme2ruleset = state.themeDataUsed && convertTheme2To3(normalizeThemeData(state.themeDataUsed)) const theme2ruleset = this.themeDataUsed && convertTheme2To3(normalizeThemeData(this.themeDataUsed))
const hacks = [] const hacks = []
Object.entries(theme3hacks).forEach(([key, value]) => { Object.entries(theme3hacks).forEach(([key, value]) => {
@ -664,16 +616,16 @@ const interfaceMod = {
const rulesetArray = [ const rulesetArray = [
theme2ruleset, theme2ruleset,
state.styleDataUsed, this.styleDataUsed,
paletteIss, paletteIss,
hacks hacks
].filter(x => x) ].filter(x => x)
return applyTheme( return applyTheme(
rulesetArray.flat(), rulesetArray.flat(),
() => commit('setThemeApplied'), () => this.setThemeApplied(),
() => { () => {
state.themeChangeInProgress = false this.themeChangeInProgress = false
}, },
themeDebug themeDebug
) )
@ -682,9 +634,7 @@ const interfaceMod = {
} }
} }
} }
} })
export default interfaceMod
export const normalizeThemeData = (input) => { export const normalizeThemeData = (input) => {
let themeData, themeSource let themeData, themeSource

110
src/stores/lists.js Normal file
View file

@ -0,0 +1,110 @@
import { defineStore } from 'pinia'
import { remove, find } from 'lodash'
export const useListsStore = defineStore('lists', {
state: () => ({
allLists: [],
allListsObject: {}
}),
getters: {
findListTitle (state) {
return (id) => {
if (!this.allListsObject[id]) return
return this.allListsObject[id].title
}
},
findListAccounts (state) {
return (id) => [...this.allListsObject[id].accountIds]
}
},
actions: {
setLists (value) {
this.allLists = value
},
createList ({ title }) {
return window.vuex.state.api.backendInteractor.createList({ title })
.then((list) => {
this.setList({ listId: list.id, title })
return list
})
},
fetchList ({ listId }) {
return window.vuex.state.api.backendInteractor.getList({ listId })
.then((list) => this.setList({ listId: list.id, title: list.title }))
},
fetchListAccounts ({ listId }) {
return window.vuex.state.api.backendInteractor.getListAccounts({ listId })
.then((accountIds) => {
if (!this.allListsObject[listId]) {
this.allListsObject[listId] = { accountIds: [] }
}
this.allListsObject[listId].accountIds = accountIds
})
},
setList ({ listId, title }) {
if (!this.allListsObject[listId]) {
this.allListsObject[listId] = { accountIds: [] }
}
this.allListsObject[listId].title = title
const entry = find(this.allLists, { id: listId })
if (!entry) {
this.allLists.push({ id: listId, title })
} else {
entry.title = title
}
},
setListAccounts ({ listId, accountIds }) {
const saved = this.allListsObject[listId].accountIds || []
const added = accountIds.filter(id => !saved.includes(id))
const removed = saved.filter(id => !accountIds.includes(id))
if (!this.allListsObject[listId]) {
this.allListsObject[listId] = { accountIds: [] }
}
this.allListsObject[listId].accountIds = accountIds
if (added.length > 0) {
window.vuex.state.api.backendInteractor.addAccountsToList({ listId, accountIds: added })
}
if (removed.length > 0) {
window.vuex.state.api.backendInteractor.removeAccountsFromList({ listId, accountIds: removed })
}
},
addListAccount ({ listId, accountId }) {
return window.vuex.state
.api
.backendInteractor
.addAccountsToList({ listId, accountIds: [accountId] })
.then((result) => {
if (!this.allListsObject[listId]) {
this.allListsObject[listId] = { accountIds: [] }
}
this.allListsObject[listId].accountIds.push(accountId)
return result
})
},
removeListAccount ({ listId, accountId }) {
return window.vuex.state
.api
.backendInteractor
.removeAccountsFromList({ listId, accountIds: [accountId] })
.then((result) => {
if (!this.allListsObject[listId]) {
this.allListsObject[listId] = { accountIds: [] }
}
const { accountIds } = this.allListsObject[listId]
const set = new Set(accountIds)
set.delete(accountId)
this.allListsObject[listId].accountIds = [...set]
return result
})
},
deleteList ({ listId }) {
window.vuex.state.api.backendInteractor.deleteList({ listId })
delete this.allListsObject[listId]
remove(this.allLists, list => list.id === listId)
}
}
})

View file

@ -0,0 +1,30 @@
import { defineStore } from 'pinia'
import fileTypeService from '../services/file_type/file_type.service.js'
const supportedTypes = new Set(['image', 'video', 'audio', 'flash'])
export const useMediaViewerStore = defineStore('mediaViewer', {
state: () => ({
media: [],
currentIndex: 0,
activated: false
}),
actions: {
setMedia (attachments) {
const media = attachments.filter(attachment => {
const type = fileTypeService.fileType(attachment.mimetype)
return supportedTypes.has(type)
})
this.media = media
},
setCurrentMedia (current) {
const index = this.media.indexOf(current)
this.activated = true
this.currentIndex = index
},
closeMediaViewer () {
this.activated = false
}
}
})

57
src/stores/polls.js Normal file
View file

@ -0,0 +1,57 @@
import { merge } from 'lodash'
import { defineStore } from 'pinia'
export const usePollsStore = defineStore('polls', {
state: () => ({
// Contains key = id, value = number of trackers for this poll
trackedPolls: {},
pollsObject: {}
}),
actions: {
mergeOrAddPoll (poll) {
const existingPoll = this.pollsObject[poll.id]
// Make expired-state change trigger re-renders properly
poll.expired = Date.now() > Date.parse(poll.expires_at)
if (existingPoll) {
this.pollsObject[poll.id] = merge(existingPoll, poll)
} else {
this.pollsObject[poll.id] = poll
}
},
updateTrackedPoll (pollId) {
window.vuex.state.api.backendInteractor.fetchPoll({ pollId }).then(poll => {
setTimeout(() => {
if (this.trackedPolls[pollId]) {
this.updateTrackedPoll(pollId)
}
}, 30 * 1000)
this.mergeOrAddPoll(poll)
})
},
trackPoll (pollId) {
if (!this.trackedPolls[pollId]) {
setTimeout(() => this.updateTrackedPoll(pollId), 30 * 1000)
}
const currentValue = this.trackedPolls[pollId]
if (currentValue) {
this.trackedPolls[pollId] = currentValue + 1
} else {
this.trackedPolls[pollId] = 1
}
},
untrackPoll (pollId) {
const currentValue = this.trackedPolls[pollId]
if (currentValue) {
this.trackedPolls[pollId] = currentValue - 1
} else {
this.trackedPolls[pollId] = 0
}
},
votePoll ({ id, pollId, choices }) {
return window.vuex.state.api.backendInteractor.vote({ pollId, choices }).then(poll => {
this.mergeOrAddPoll(poll)
return poll
})
}
}
})

17
src/stores/postStatus.js Normal file
View file

@ -0,0 +1,17 @@
import { defineStore } from 'pinia'
export const usePostStatusStore = defineStore('postStatus', {
state: () => ({
params: null,
modalActivated: false
}),
actions: {
openPostStatusModal (params) {
this.params = params
this.modalActivated = true
},
closePostStatusModal () {
this.modalActivated = false
}
}
})

52
src/stores/reports.js Normal file
View file

@ -0,0 +1,52 @@
import { defineStore } from 'pinia'
import filter from 'lodash/filter'
import { useInterfaceStore } from '../stores/interface'
export const useReportsStore = defineStore('reports', {
state: () => ({
reportModal: {
userId: null,
statuses: [],
preTickedIds: [],
activated: false
},
reports: {}
}),
actions: {
openUserReportingModal ({ userId, statusIds = [] }) {
const preTickedStatuses = statusIds.map(id => window.vuex.state.statuses.allStatusesObject[id])
const preTickedIds = statusIds
const statuses = preTickedStatuses.concat(
filter(window.vuex.state.statuses.allStatuses,
status => status.user.id === userId && !preTickedIds.includes(status.id)
)
)
this.reportModal.userId = userId
this.reportModal.statuses = statuses
this.reportModal.preTickedIds = preTickedIds
this.reportModal.activated = true
},
closeUserReportingModal () {
this.reportModal.activated = false
},
setReportState ({ id, state }) {
const oldState = window.vuex.state.reports.reports[id].state
this.reports[id].state = state
window.vuex.state.api.backendInteractor.setReportState({ id, state }).catch(e => {
console.error('Failed to set report state', e)
useInterfaceStore().pushGlobalNotice({
level: 'error',
messageKey: 'general.generic_error_message',
messageArgs: [e.message],
timeout: 5000
})
this.reports[id].state = oldState
})
},
addReport (report) {
this.reports[report.id] = report
}
}
})

32
src/stores/shout.js Normal file
View file

@ -0,0 +1,32 @@
import { defineStore } from 'pinia'
export const useShoutStore = defineStore('shout', {
state: () => ({
messages: [],
channel: { state: '' },
joined: false
}),
actions: {
initializeShout (socket) {
const channel = socket.channel('chat:public')
channel.joinPush.receive('ok', () => {
this.joined = true
})
channel.onClose(() => {
this.joined = false
})
channel.onError(() => {
this.joined = false
})
channel.on('new_msg', (msg) => {
this.messages.push(msg)
this.messages = this.messages.slice(-19, 20)
})
channel.on('messages', ({ messages }) => {
this.messages = messages.slice(-19, 20)
})
channel.join()
this.channel = channel
}
}
})

View file

@ -0,0 +1,17 @@
import { defineStore } from 'pinia'
export const useStatusHistoryStore = defineStore('statusHistory', {
state: () => ({
params: {},
modalActivated: false
}),
actions: {
openStatusHistoryModal (params) {
this.params = params
this.modalActivated = true
},
closeStatusHistoryModal () {
this.modalActivated = false
}
}
})

View file

@ -1,83 +0,0 @@
import { cloneDeep } from 'lodash'
import { defaultState, mutations, getters } from '../../../../src/modules/lists.js'
describe('The lists module', () => {
describe('mutations', () => {
it('updates array of all lists', () => {
const state = cloneDeep(defaultState)
const list = { id: '1', title: 'testList' }
mutations.setLists(state, [list])
expect(state.allLists).to.have.length(1)
expect(state.allLists).to.eql([list])
})
it('adds a new list with a title, updating the title for existing lists', () => {
const state = cloneDeep(defaultState)
const list = { id: '1', title: 'testList' }
const modList = { id: '1', title: 'anotherTestTitle' }
mutations.setList(state, { listId: list.id, title: list.title })
expect(state.allListsObject[list.id]).to.eql({ title: list.title, accountIds: [] })
expect(state.allLists).to.have.length(1)
expect(state.allLists[0]).to.eql(list)
mutations.setList(state, { listId: modList.id, title: modList.title })
expect(state.allListsObject[modList.id]).to.eql({ title: modList.title, accountIds: [] })
expect(state.allLists).to.have.length(1)
expect(state.allLists[0]).to.eql(modList)
})
it('adds a new list with an array of IDs, updating the IDs for existing lists', () => {
const state = cloneDeep(defaultState)
const list = { id: '1', accountIds: ['1', '2', '3'] }
const modList = { id: '1', accountIds: ['3', '4', '5'] }
mutations.setListAccounts(state, { listId: list.id, accountIds: list.accountIds })
expect(state.allListsObject[list.id]).to.eql({ accountIds: list.accountIds })
mutations.setListAccounts(state, { listId: modList.id, accountIds: modList.accountIds })
expect(state.allListsObject[modList.id]).to.eql({ accountIds: modList.accountIds })
})
it('deletes a list', () => {
const state = {
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
}
const listId = '1'
mutations.deleteList(state, { listId })
expect(state.allLists).to.have.length(0)
expect(state.allListsObject).to.eql({})
})
})
describe('getters', () => {
it('returns list title', () => {
const state = {
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
}
const id = '1'
expect(getters.findListTitle(state)(id)).to.eql('testList')
})
it('returns list accounts', () => {
const state = {
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
}
const id = '1'
expect(getters.findListAccounts(state)(id)).to.eql(['1', '2', '3'])
})
})
})

View file

@ -0,0 +1,93 @@
import { createPinia, setActivePinia } from 'pinia'
import { useListsStore } from '../../../../src/stores/lists.js'
import { createStore } from 'vuex'
import apiModule from '../../../../src/modules/api.js'
setActivePinia(createPinia())
const store = useListsStore()
window.vuex = createStore({
modules: {
api: apiModule
}
})
describe('The lists store', () => {
describe('actions', () => {
it('updates array of all lists', () => {
store.$reset()
const list = { id: '1', title: 'testList' }
store.setLists([list])
expect(store.allLists).to.have.length(1)
expect(store.allLists).to.eql([list])
})
it('adds a new list with a title, updating the title for existing lists', () => {
store.$reset()
const list = { id: '1', title: 'testList' }
const modList = { id: '1', title: 'anotherTestTitle' }
store.setList({ listId: list.id, title: list.title })
expect(store.allListsObject[list.id]).to.eql({ title: list.title, accountIds: [] })
expect(store.allLists).to.have.length(1)
expect(store.allLists[0]).to.eql(list)
store.setList({ listId: modList.id, title: modList.title })
expect(store.allListsObject[modList.id]).to.eql({ title: modList.title, accountIds: [] })
expect(store.allLists).to.have.length(1)
expect(store.allLists[0]).to.eql(modList)
})
it('adds a new list with an array of IDs, updating the IDs for existing lists', () => {
store.$reset()
const list = { id: '1', accountIds: ['1', '2', '3'] }
const modList = { id: '1', accountIds: ['3', '4', '5'] }
store.setListAccounts({ listId: list.id, accountIds: list.accountIds })
expect(store.allListsObject[list.id].accountIds).to.eql(list.accountIds)
store.setListAccounts({ listId: modList.id, accountIds: modList.accountIds })
expect(store.allListsObject[modList.id].accountIds).to.eql(modList.accountIds)
})
it('deletes a list', () => {
store.$patch({
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
})
const listId = '1'
store.deleteList({ listId })
expect(store.allLists).to.have.length(0)
expect(store.allListsObject).to.eql({})
})
})
describe('getters', () => {
it('returns list title', () => {
store.$patch({
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
})
const id = '1'
expect(store.findListTitle(id)).to.eql('testList')
})
it('returns list accounts', () => {
store.$patch({
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
})
const id = '1'
expect(store.findListAccounts(id)).to.eql(['1', '2', '3'])
})
})
})

3021
yarn.lock

File diff suppressed because it is too large Load diff