From dab51e44a5ffbdaf675d6c4abe4fa37930167bc1 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Mon, 3 Jul 2023 18:44:17 +0200 Subject: [PATCH 1/2] install and initialize pinia Signed-off-by: Maksim Sukharev --- package-lock.json | 78 +++++++++++++++++++++++++++++++ package.json | 1 + src/main.js | 5 ++ src/mainFilesSidebar.js | 6 +++ src/mainPublicShareAuthSidebar.js | 5 ++ src/mainPublicShareSidebar.js | 5 ++ src/mainRecording.js | 5 ++ 7 files changed, 105 insertions(+) diff --git a/package-lock.json b/package-lock.json index c4e81aa385a..13846eda566 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "lodash": "^4.17.21", "mockconsole": "0.0.1", "nextcloud-vue-collections": "^0.11.1", + "pinia": "^2.1.4", "ua-parser-js": "^1.0.35", "util": "^0.12.5", "vue": "^2.7.14", @@ -4292,6 +4293,11 @@ "url": "https://opencollective.com/postcss/" } }, + "node_modules/@vue/devtools-api": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", + "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" + }, "node_modules/@vue/eslint-config-typescript": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.2.tgz", @@ -14123,6 +14129,56 @@ "node": ">=6" } }, + "node_modules/pinia": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.4.tgz", + "integrity": "sha512-vYlnDu+Y/FXxv1ABo1vhjC+IbqvzUdiUC3sfDRrRyY2CQSrqqaa+iiHmqtARFxJVqWQMCJfXx1PBvFs9aJVLXQ==", + "dependencies": { + "@vue/devtools-api": "^6.5.0", + "vue-demi": ">=0.14.5" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@vue/composition-api": "^1.4.0", + "typescript": ">=4.4.4", + "vue": "^2.6.14 || ^3.3.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -21393,6 +21449,11 @@ } } }, + "@vue/devtools-api": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", + "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" + }, "@vue/eslint-config-typescript": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.2.tgz", @@ -28706,6 +28767,23 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, + "pinia": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.4.tgz", + "integrity": "sha512-vYlnDu+Y/FXxv1ABo1vhjC+IbqvzUdiUC3sfDRrRyY2CQSrqqaa+iiHmqtARFxJVqWQMCJfXx1PBvFs9aJVLXQ==", + "requires": { + "@vue/devtools-api": "^6.5.0", + "vue-demi": ">=0.14.5" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "requires": {} + } + } + }, "pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", diff --git a/package.json b/package.json index b02ea5da537..1d4e5b40f47 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "lodash": "^4.17.21", "mockconsole": "0.0.1", "nextcloud-vue-collections": "^0.11.1", + "pinia": "^2.1.4", "ua-parser-js": "^1.0.35", "util": "^0.12.5", "vue": "^2.7.14", diff --git a/src/main.js b/src/main.js index 99ffb8d1b88..3ddbe49610b 100644 --- a/src/main.js +++ b/src/main.js @@ -24,6 +24,7 @@ * */ +import { createPinia, PiniaVuePlugin } from 'pinia' import Vue from 'vue' import VueObserveVisibility from 'vue-observe-visibility' import vOutsideEvents from 'vue-outside-events' @@ -70,18 +71,22 @@ Vue.prototype.n = translatePlural Vue.prototype.OC = OC Vue.prototype.OCA = OCA +Vue.use(PiniaVuePlugin) Vue.use(Vuex) Vue.use(VueRouter) Vue.use(VueObserveVisibility) Vue.use(VueShortKey, { prevent: ['input', 'textarea', 'div'] }) Vue.use(vOutsideEvents) +const pinia = createPinia() + TooltipOptions.container = '#content-vue' store.dispatch('setMainContainerSelector', '#content-vue') const instance = new Vue({ el: '#content', store, + pinia, router, propsData: { fileInfo: null, diff --git a/src/mainFilesSidebar.js b/src/mainFilesSidebar.js index fef66ddbbf5..b2676ed523c 100644 --- a/src/mainFilesSidebar.js +++ b/src/mainFilesSidebar.js @@ -24,6 +24,7 @@ * */ +import { createPinia, PiniaVuePlugin } from 'pinia' import Vue from 'vue' import VueObserveVisibility from 'vue-observe-visibility' import vOutsideEvents from 'vue-outside-events' @@ -64,20 +65,25 @@ Vue.prototype.n = translatePlural Vue.prototype.OC = OC Vue.prototype.OCA = OCA +Vue.use(PiniaVuePlugin) Vue.use(Vuex) Vue.use(VueShortKey, { prevent: ['input', 'textarea', 'div'] }) Vue.use(vOutsideEvents) Vue.use(VueObserveVisibility) +const pinia = createPinia() + store.dispatch('setMainContainerSelector', '.talkChatTab') const newCallView = () => new Vue({ store, + pinia, render: h => h(FilesSidebarCallViewApp), }) const newTab = () => new Vue({ store, + pinia, id: 'talk-chat-tab', render: h => h(FilesSidebarTabApp), }) diff --git a/src/mainPublicShareAuthSidebar.js b/src/mainPublicShareAuthSidebar.js index f33ee28e083..d4e6ee74d82 100644 --- a/src/mainPublicShareAuthSidebar.js +++ b/src/mainPublicShareAuthSidebar.js @@ -18,6 +18,7 @@ * */ +import { createPinia, PiniaVuePlugin } from 'pinia' import Vue from 'vue' import VueObserveVisibility from 'vue-observe-visibility' import vOutsideEvents from 'vue-outside-events' @@ -58,11 +59,13 @@ Vue.prototype.n = translatePlural Vue.prototype.OC = OC Vue.prototype.OCA = OCA +Vue.use(PiniaVuePlugin) Vue.use(Vuex) Vue.use(VueShortKey, { prevent: ['input', 'textarea', 'div'] }) Vue.use(vOutsideEvents) Vue.use(VueObserveVisibility) +const pinia = createPinia() store.dispatch('setMainContainerSelector', '#talk-sidebar') /** @@ -127,6 +130,7 @@ function getShareToken() { const requestPasswordVm = new Vue({ store, + pinia, id: 'talk-video-verification', propsData: { shareToken: getShareToken(), @@ -137,6 +141,7 @@ requestPasswordVm.$mount('#request-password') const talkSidebarVm = new Vue({ store, + pinia, ...PublicShareAuthSidebar, }) talkSidebarVm.$mount(document.querySelector('#talk-sidebar')) diff --git a/src/mainPublicShareSidebar.js b/src/mainPublicShareSidebar.js index 6db0c16e963..572f8767a4c 100644 --- a/src/mainPublicShareSidebar.js +++ b/src/mainPublicShareSidebar.js @@ -18,6 +18,7 @@ * */ +import { createPinia, PiniaVuePlugin } from 'pinia' import Vue from 'vue' import VueObserveVisibility from 'vue-observe-visibility' import vOutsideEvents from 'vue-outside-events' @@ -58,11 +59,14 @@ Vue.prototype.n = translatePlural Vue.prototype.OC = OC Vue.prototype.OCA = OCA +Vue.use(PiniaVuePlugin) Vue.use(Vuex) Vue.use(VueShortKey, { prevent: ['input', 'textarea', 'div'] }) Vue.use(vOutsideEvents) Vue.use(VueObserveVisibility) +const pinia = createPinia() + store.dispatch('setMainContainerSelector', '#talk-sidebar') /** @@ -132,6 +136,7 @@ function getShareToken() { const talkSidebarVm = new Vue({ store, + pinia, id: 'talk-chat-tab', propsData: { shareToken: getShareToken(), diff --git a/src/mainRecording.js b/src/mainRecording.js index 5a323a91776..d274177c243 100644 --- a/src/mainRecording.js +++ b/src/mainRecording.js @@ -25,6 +25,7 @@ * */ +import { createPinia, PiniaVuePlugin } from 'pinia' import Vue from 'vue' import VueObserveVisibility from 'vue-observe-visibility' import vOutsideEvents from 'vue-outside-events' @@ -72,12 +73,15 @@ Vue.prototype.n = translatePlural Vue.prototype.OC = OC Vue.prototype.OCA = OCA +Vue.use(PiniaVuePlugin) Vue.use(Vuex) Vue.use(VueRouter) Vue.use(VueObserveVisibility) Vue.use(VueShortKey, { prevent: ['input', 'textarea', 'div'] }) Vue.use(vOutsideEvents) +const pinia = createPinia() + TooltipOptions.container = '#call-container' store.dispatch('setMainContainerSelector', '#call-container') @@ -90,6 +94,7 @@ if (!window.OCA.Talk) { const instance = new Vue({ el: '#content', store, + pinia, router, render: h => h(Recording), }) From ec5dd4240268b7cc361b1311240b1880ce5641c2 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Mon, 17 Jul 2023 15:00:39 +0200 Subject: [PATCH 2/2] replace Vuex settingsStore.js to Pinia settings.js Signed-off-by: Maksim Sukharev --- src/components/NewMessage/NewMessage.vue | 6 +- .../SettingsDialog/SettingsDialog.vue | 24 +++--- src/store/settingsStore.js | 80 ------------------- src/store/storeConfig.js | 2 - src/stores/__tests__/settings.spec.js | 43 ++++++++++ src/stores/settings.js | 57 +++++++++++++ 6 files changed, 114 insertions(+), 98 deletions(-) delete mode 100644 src/store/settingsStore.js create mode 100644 src/stores/__tests__/settings.spec.js create mode 100644 src/stores/settings.js diff --git a/src/components/NewMessage/NewMessage.vue b/src/components/NewMessage/NewMessage.vue index 303de1c292a..a7e6b24cfe4 100644 --- a/src/components/NewMessage/NewMessage.vue +++ b/src/components/NewMessage/NewMessage.vue @@ -183,6 +183,7 @@ import { CONVERSATION, PARTICIPANT, PRIVACY } from '../../constants.js' import { EventBus } from '../../services/EventBus.js' import { shareFile } from '../../services/filesSharingServices.js' import { searchPossibleMentions } from '../../services/mentionsService.js' +import { useSettingsStore } from '../../stores/settings.js' import { fetchClipboardContent } from '../../utils/clipboard.js' import { isDarkTheme } from '../../utils/isDarkTheme.js' @@ -261,8 +262,11 @@ export default { setup() { const { openViewer } = useViewer() + const settingsStore = useSettingsStore() + return { openViewer, + settingsStore, supportTypingStatus, } }, @@ -371,7 +375,7 @@ export default { }, showTypingStatus() { return this.hasTypingIndicator && this.supportTypingStatus - && this.$store.getters.getTypingStatusPrivacy() === PRIVACY.PUBLIC + && this.settingsStore.typingStatusPrivacy === PRIVACY.PUBLIC }, }, diff --git a/src/components/SettingsDialog/SettingsDialog.vue b/src/components/SettingsDialog/SettingsDialog.vue index e83fc111876..02fd9732efe 100644 --- a/src/components/SettingsDialog/SettingsDialog.vue +++ b/src/components/SettingsDialog/SettingsDialog.vue @@ -168,6 +168,7 @@ import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' import MediaDevicesPreview from '../MediaDevicesPreview.vue' import { PRIVACY } from '../../constants.js' +import { useSettingsStore } from '../../stores/settings.js' const supportTypingStatus = getCapabilities()?.spreed?.config?.chat?.['typing-privacy'] !== undefined @@ -184,7 +185,10 @@ export default { }, setup() { + const settingsStore = useSettingsStore() + return { + settingsStore, supportTypingStatus, } }, @@ -220,19 +224,11 @@ export default { }, readStatusPrivacyIsPublic() { - return this.readStatusPrivacy === PRIVACY.PUBLIC - }, - - readStatusPrivacy() { - return this.$store.getters.getReadStatusPrivacy() + return this.settingsStore.readStatusPrivacy === PRIVACY.PUBLIC }, typingStatusPrivacyIsPublic() { - return this.typingStatusPrivacy === PRIVACY.PUBLIC - }, - - typingStatusPrivacy() { - return this.$store.getters.getTypingStatusPrivacy() + return this.settingsStore.typingStatusPrivacy === PRIVACY.PUBLIC }, settingsUrl() { @@ -280,9 +276,8 @@ export default { async toggleReadStatusPrivacy() { this.privacyLoading = true try { - await this.$store.dispatch( - 'updateReadStatusPrivacy', - this.readStatusPrivacyIsPublic ? PRIVACY.PRIVATE : PRIVACY.PUBLIC, + await this.settingsStore.updateReadStatusPrivacy( + this.readStatusPrivacyIsPublic ? PRIVACY.PRIVATE : PRIVACY.PUBLIC ) showSuccess(t('spreed', 'Your privacy setting has been saved')) } catch (exception) { @@ -294,8 +289,7 @@ export default { async toggleTypingStatusPrivacy() { this.privacyLoading = true try { - await this.$store.dispatch( - 'updateTypingStatusPrivacy', + await this.settingsStore.updateTypingStatusPrivacy( this.typingStatusPrivacyIsPublic ? PRIVACY.PRIVATE : PRIVACY.PUBLIC ) showSuccess(t('spreed', 'Your privacy setting has been saved')) diff --git a/src/store/settingsStore.js b/src/store/settingsStore.js deleted file mode 100644 index 20a761d8fb8..00000000000 --- a/src/store/settingsStore.js +++ /dev/null @@ -1,80 +0,0 @@ -/** - * @copyright Copyright (c) 2020 Joas Schilling - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -import { loadState } from '@nextcloud/initial-state' - -import { PRIVACY } from '../constants.js' -import { setReadStatusPrivacy, setTypingStatusPrivacy } from '../services/settingsService.js' - -const state = { - readStatusPrivacy: loadState('spreed', 'read_status_privacy', PRIVACY.PRIVATE), - typingStatusPrivacy: loadState('spreed', 'typing_privacy', PRIVACY.PRIVATE), -} - -const getters = { - getReadStatusPrivacy: (state) => () => { - return state.readStatusPrivacy - }, - getTypingStatusPrivacy: (state) => () => { - return state.typingStatusPrivacy - }, -} - -const mutations = { - /** - * Updates the token - * - * @param {object} state current store state; - * @param {string} privacy The token of the active conversation - */ - updateReadStatusPrivacy(state, privacy) { - state.readStatusPrivacy = privacy - }, - updateTypingStatusPrivacy(state, privacy) { - state.typingStatusPrivacy = privacy - }, -} - -const actions = { - - /** - * Update the read status privacy for the user - * - * @param {object} context default store context; - * @param {number} privacy The new selected privacy - */ - async updateReadStatusPrivacy(context, privacy) { - await setReadStatusPrivacy(privacy) - context.commit('updateReadStatusPrivacy', privacy) - }, - - /** - * Update the typing status privacy for the user - * - * @param {object} context default store context; - * @param {number} privacy The new selected privacy - */ - async updateTypingStatusPrivacy(context, privacy) { - await setTypingStatusPrivacy(privacy) - context.commit('updateTypingStatusPrivacy', privacy) - }, -} - -export default { state, mutations, getters, actions } diff --git a/src/store/storeConfig.js b/src/store/storeConfig.js index 1bf21065856..ad9c53628a1 100644 --- a/src/store/storeConfig.js +++ b/src/store/storeConfig.js @@ -34,7 +34,6 @@ import participantsStore from './participantsStore.js' import pollStore from './pollStore.js' import quoteReplyStore from './quoteReplyStore.js' import reactionsStore from './reactionsStore.js' -import settingsStore from './settingsStore.js' import sharedItemStore from './sharedItemsStore.js' import sidebarStore from './sidebarStore.js' import soundsStore from './soundsStore.js' @@ -55,7 +54,6 @@ export default { newGroupConversationStore, participantsStore, quoteReplyStore, - settingsStore, sidebarStore, soundsStore, talkHashStore, diff --git a/src/stores/__tests__/settings.spec.js b/src/stores/__tests__/settings.spec.js new file mode 100644 index 00000000000..9b7eccb8b48 --- /dev/null +++ b/src/stores/__tests__/settings.spec.js @@ -0,0 +1,43 @@ +import { setActivePinia, createPinia } from 'pinia' + +import { PRIVACY } from '../../constants.js' +import { useSettingsStore } from '../settings.js' + +jest.mock('@nextcloud/initial-state', + () => ({ + loadState: jest.fn().mockReturnValue(0), + })) + +jest.mock('../../services/settingsService', + () => ({ + setReadStatusPrivacy: jest.fn().mockReturnValue('success'), + setTypingStatusPrivacy: jest.fn().mockReturnValue('success'), + })) + +describe('settingsStore', () => { + beforeEach(() => { + // creates a fresh pinia and make it active, so it's automatically picked + // up by any useStore() call without having to pass it to it: + // `useStore(pinia)` + setActivePinia(createPinia()) + }) + + it('shows correct loaded values for statuses', () => { + const settingsStore = useSettingsStore() + + expect(settingsStore.readStatusPrivacy).toBe(PRIVACY.PUBLIC) + expect(settingsStore.typingStatusPrivacy).toBe(PRIVACY.PUBLIC) + }) + + it('toggles statuses correctly', async () => { + const settingsStore = useSettingsStore() + + expect(settingsStore.readStatusPrivacy).toBe(PRIVACY.PUBLIC) + await settingsStore.updateReadStatusPrivacy(PRIVACY.PRIVATE) + expect(settingsStore.readStatusPrivacy).toBe(PRIVACY.PRIVATE) + + expect(settingsStore.typingStatusPrivacy).toBe(PRIVACY.PUBLIC) + await settingsStore.updateTypingStatusPrivacy(PRIVACY.PRIVATE) + expect(settingsStore.typingStatusPrivacy).toBe(PRIVACY.PRIVATE) + }) +}) diff --git a/src/stores/settings.js b/src/stores/settings.js new file mode 100644 index 00000000000..64bb5c582e9 --- /dev/null +++ b/src/stores/settings.js @@ -0,0 +1,57 @@ +/** + * @copyright Copyright (c) 2020 Joas Schilling + * + * @author Maksim Sukharev + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +import { defineStore } from 'pinia' + +import { loadState } from '@nextcloud/initial-state' + +import { PRIVACY } from '../constants.js' +import { setReadStatusPrivacy, setTypingStatusPrivacy } from '../services/settingsService.js' + +export const useSettingsStore = defineStore('settings', { + state: () => ({ + readStatusPrivacy: loadState('spreed', 'read_status_privacy', PRIVACY.PRIVATE), + typingStatusPrivacy: loadState('spreed', 'typing_privacy', PRIVACY.PRIVATE), + }), + + actions: { + /** + * Update the read status privacy for the user + * + * @param {number} privacy The new selected privacy + */ + async updateReadStatusPrivacy(privacy) { + await setReadStatusPrivacy(privacy) + this.readStatusPrivacy = privacy + }, + + /** + * Update the typing status privacy for the user + * + * @param {number} privacy The new selected privacy + */ + async updateTypingStatusPrivacy(privacy) { + await setTypingStatusPrivacy(privacy) + this.typingStatusPrivacy = privacy + }, + }, +})