From 93a87b56460393033ee672d31e8da12d57739395 Mon Sep 17 00:00:00 2001 From: newt Date: Tue, 22 Feb 2022 13:27:12 +0000 Subject: [PATCH 0001/1277] Add status check. --- src/services/entity_normalizer/entity_normalizer.service.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index f219c161f..bd7f7ea5c 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -380,7 +380,9 @@ export const parseNotification = (data) => { if (masto) { output.type = mastoDict[data.type] || data.type output.seen = data.pleroma.is_seen - output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null + // TODO: null check should be a temporary fix, I guess. + // Investigate why backend does this. + output.status = isStatusNotification(output.type) && data.status !== null ? parseStatus(data.status) : null output.action = output.status // TODO: Refactor, this is unneeded output.target = output.type !== 'move' ? null From 1bf256b34b020622718d4dcdcae4153a6f75e060 Mon Sep 17 00:00:00 2001 From: Xnuk Shuman Date: Tue, 20 Dec 2022 02:03:35 +0900 Subject: [PATCH 0002/1277] use normal checkbox component label in announcement --- src/components/announcement_editor/announcement_editor.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/announcement_editor/announcement_editor.vue b/src/components/announcement_editor/announcement_editor.vue index 0f29f9f70..32b05bb20 100644 --- a/src/components/announcement_editor/announcement_editor.vue +++ b/src/components/announcement_editor/announcement_editor.vue @@ -32,8 +32,9 @@ id="announcement-all-day" v-model="announcement.allDay" :disabled="disabled" - /> - + > + {{ $t('announcements.all_day_prompt') }} + From f8a0cd2dd3e298b2a771d786033a7c29df8dcbfc Mon Sep 17 00:00:00 2001 From: Xnuk Shuman Date: Tue, 20 Dec 2022 02:34:11 +0900 Subject: [PATCH 0003/1277] vertical centering the checkbox --- src/components/checkbox/checkbox.vue | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue index b6768d67f..d7839e822 100644 --- a/src/components/checkbox/checkbox.vue +++ b/src/components/checkbox/checkbox.vue @@ -39,20 +39,23 @@ export default { display: inline-block; min-height: 1.2em; + & > * { + vertical-align: middle; + } + &-indicator { + display: inline-block; position: relative; - padding-left: 1.2em; + width: 1.2em; + height: 1.2em; } &-indicator::before { position: absolute; - right: 0; - top: 0; + inset: 0; display: block; content: '✓'; transition: color 200ms; - width: 1.1em; - height: 1.1em; border-radius: $fallback--checkboxRadius; border-radius: var(--checkboxRadius, $fallback--checkboxRadius); box-shadow: 0px 0px 2px black inset; From 7d90c594fe9530e8f483c6912ffcb80319934a8c Mon Sep 17 00:00:00 2001 From: tusooa Date: Fri, 6 Jan 2023 13:52:49 -0500 Subject: [PATCH 0004/1277] Make in-reply-to i18n-friendly --- src/components/status/status.vue | 83 ++++++++++++++++++-------------- src/i18n/en.json | 2 + 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 82eb7ac61..2a3946101 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -257,44 +257,57 @@ v-if="isReply" class="glued-label reply-glued-label" > - - - + + - - {{ $t('status.reply_to') }} - - + + {{ $t('status.reply_to') }} + + + + diff --git a/src/i18n/en.json b/src/i18n/en.json index 1ee1147ad..e98b1d4c5 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -853,6 +853,8 @@ "unbookmark": "Unbookmark", "delete_confirm": "Do you really want to delete this status?", "reply_to": "Reply to", + "reply_to_with_icon": "{icon} {replyTo}", + "reply_to_with_arg": "{replyToWithIcon} {user}", "mentions": "Mentions", "replies_list": "Replies:", "replies_list_with_others": "Replies (+{numReplies} other): | Replies (+{numReplies} others):", From 1506d2421d9a16867c5d3f39fbbc650952c2849a Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 28 Jan 2023 21:44:24 -0500 Subject: [PATCH 0005/1277] Get rid of * --- src/components/checkbox/checkbox.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue index cbebe5789..6c23d9e48 100644 --- a/src/components/checkbox/checkbox.vue +++ b/src/components/checkbox/checkbox.vue @@ -39,7 +39,7 @@ export default { display: inline-block; min-height: 1.2em; - & > * { + .checkbox-indicator, .label { vertical-align: middle; } From f7daaead6f16d87aef5a6a26fa8f8cfe7f7caf10 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 28 Jan 2023 21:54:08 -0500 Subject: [PATCH 0006/1277] Fix stylelint --- src/components/checkbox/checkbox.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue index 6c23d9e48..32c8f79cd 100644 --- a/src/components/checkbox/checkbox.vue +++ b/src/components/checkbox/checkbox.vue @@ -39,7 +39,8 @@ export default { display: inline-block; min-height: 1.2em; - .checkbox-indicator, .label { + &-indicator, + & .label { vertical-align: middle; } From edfaf5e80cb232ba6452d07614895c08c2e3978f Mon Sep 17 00:00:00 2001 From: Sean King Date: Tue, 4 Apr 2023 14:38:19 -0600 Subject: [PATCH 0007/1277] Add Pinia as dependency --- package.json | 1 + yarn.lock | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 721400ffa..0cba43903 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "localforage": "1.10.0", "parse-link-header": "2.0.0", "phoenix": "1.6.2", + "pinia": "^2.0.33", "punycode.js": "2.3.0", "qrcode": "1.5.0", "querystring-es3": "0.2.1", diff --git a/yarn.lock b/yarn.lock index 34e9664db..03c711d0b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2220,6 +2220,11 @@ resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.4.5.tgz#d54e844c1adbb1e677c81c665ecef1a2b4bb8380" integrity sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ== +"@vue/devtools-api@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07" + integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q== + "@vue/reactivity-transform@3.2.45": version "3.2.45" resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz#07ac83b8138550c83dfb50db43cde1e0e5e8124d" @@ -7075,6 +7080,14 @@ pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" +pinia@^2.0.33: + version "2.0.33" + resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.0.33.tgz#b70065be697874d5824e9792f59bd5d87ddb5e7d" + integrity sha512-HOj1yVV2itw6rNIrR2f7+MirGNxhORjrULL8GWgRwXsGSvEqIQ+SE0MYt6cwtpegzCda3i+rVTZM+AM7CG+kRg== + dependencies: + "@vue/devtools-api" "^6.5.0" + vue-demi "*" + pirates@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" @@ -8855,7 +8868,7 @@ void-elements@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" -vue-demi@^0.13.11: +vue-demi@*, vue-demi@^0.13.11: version "0.13.11" resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99" integrity sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A== From aa98e83ff003df834e50665f7ac5f265751784ed Mon Sep 17 00:00:00 2001 From: Sean King Date: Tue, 4 Apr 2023 14:40:12 -0600 Subject: [PATCH 0008/1277] Move i18n to new store --- src/boot/after_store.js | 8 ++++++++ src/main.js | 6 +----- src/modules/config.js | 3 ++- .../notification_utils/notification_utils.js | 3 ++- src/stores/i18n.js | 14 ++++++++++++++ 5 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 src/stores/i18n.js diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 9c1f007bd..c4da8b4f7 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -1,4 +1,5 @@ import { createApp } from 'vue' +import { createPinia } from 'pinia' import { createRouter, createWebHistory } from 'vue-router' import vClickOutside from 'click-outside-vue3' import VueVirtualScroller from 'vue-virtual-scroller' @@ -17,6 +18,8 @@ import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { applyTheme, applyConfig } from '../services/style_setter/style_setter.js' import FaviconService from '../services/favicon_service/favicon_service.js' +import { useI18nStore } from '../stores/i18n' + let staticInitialResults = null const parsedInitialResults = () => { @@ -395,6 +398,11 @@ const afterStoreSetup = async ({ store, i18n }) => { }) const app = createApp(App) + const pinia = createPinia() + + app.use(pinia) + + useI18nStore().setI18n(i18n) app.use(router) app.use(store) diff --git a/src/main.js b/src/main.js index d3e60a0fa..43869b01d 100644 --- a/src/main.js +++ b/src/main.js @@ -67,11 +67,6 @@ const persistedStateOptions = { } const store = createStore({ modules: { - i18n: { - getters: { - i18n: () => i18n.global - } - }, interface: interfaceModule, instance: instanceModule, // TODO refactor users/statuses modules, they depend on each other @@ -99,6 +94,7 @@ const persistedStateOptions = { strict: false // Socket modifies itself, let's ignore this for now. // strict: process.env.NODE_ENV !== 'production' }) + if (storageError) { store.dispatch('pushGlobalNotice', { messageKey: 'errors.storage_unavailable', level: 'error' }) } diff --git a/src/modules/config.js b/src/modules/config.js index 7597886e0..c60ff11eb 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -2,6 +2,7 @@ import Cookies from 'js-cookie' import { setPreset, applyTheme, applyConfig } from '../services/style_setter/style_setter.js' import messages from '../i18n/messages' import localeService from '../services/locale/locale.service.js' +import { useI18nStore } from '../stores/i18n.js' const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage' @@ -194,7 +195,7 @@ const config = { applyTheme(value) break case 'interfaceLanguage': - messages.setLanguage(this.getters.i18n, value) + messages.setLanguage(useI18nStore().i18n, value) dispatch('loadUnicodeEmojiData', value) Cookies.set( BACKEND_LANGUAGE_COOKIE_NAME, diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index 0f8b9b029..aec753c46 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -1,6 +1,7 @@ import { filter, sortBy, includes } from 'lodash' import { muteWordHits } from '../status_parser/status_parser.js' import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js' +import { useI18nStore } from '../../stores/i18n.js' export const notificationsFromStore = store => store.state.statuses.notifications.data @@ -59,7 +60,7 @@ export const maybeShowNotification = (store, notification) => { if (!visibleTypes(store).includes(notification.type)) return if (notification.type === 'mention' && isMutedNotification(store, notification)) return - const notificationObject = prepareNotificationObject(notification, store.rootGetters.i18n) + const notificationObject = prepareNotificationObject(notification, useI18nStore().i18n) showDesktopNotification(rootState, notificationObject) } diff --git a/src/stores/i18n.js b/src/stores/i18n.js new file mode 100644 index 000000000..d038b30a4 --- /dev/null +++ b/src/stores/i18n.js @@ -0,0 +1,14 @@ +import { defineStore } from 'pinia' + +export const useI18nStore = defineStore('i18n', { + state: () => ({ + i18n: null + }), + actions: { + setI18n (newI18n) { + this.$patch({ + i18n: newI18n.global + }) + } + } +}) From aa6c13f9e60913639ff27d40009b045e7feb17ca Mon Sep 17 00:00:00 2001 From: Sean King Date: Tue, 4 Apr 2023 21:17:54 -0600 Subject: [PATCH 0009/1277] Move shout module to store --- src/App.js | 3 +- src/boot/after_store.js | 11 ++---- src/components/shout_panel/shout_panel.js | 5 ++- src/components/side_drawer/side_drawer.js | 3 +- src/main.js | 8 ++-- src/modules/api.js | 3 +- src/modules/shout.js | 46 ----------------------- src/stores/shout.js | 32 ++++++++++++++++ 8 files changed, 50 insertions(+), 61 deletions(-) delete mode 100644 src/modules/shout.js create mode 100644 src/stores/shout.js diff --git a/src/App.js b/src/App.js index b7eb2f72e..ac6885f34 100644 --- a/src/App.js +++ b/src/App.js @@ -17,6 +17,7 @@ import GlobalNoticeList from './components/global_notice_list/global_notice_list import { windowWidth, windowHeight } from './services/window_utils/window_utils' import { mapGetters } from 'vuex' import { defineAsyncComponent } from 'vue' +import { useShoutStore } from './stores/shout' export default { name: 'app', @@ -86,7 +87,7 @@ export default { } } }, - shout () { return this.$store.state.shout.joined }, + shout () { return useShoutStore().joined }, suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, showInstanceSpecificPanel () { return this.$store.state.instance.showInstanceSpecificPanel && diff --git a/src/boot/after_store.js b/src/boot/after_store.js index c4da8b4f7..bc9b9996d 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -1,5 +1,4 @@ import { createApp } from 'vue' -import { createPinia } from 'pinia' import { createRouter, createWebHistory } from 'vue-router' import vClickOutside from 'click-outside-vue3' import VueVirtualScroller from 'vue-virtual-scroller' @@ -341,7 +340,10 @@ const checkOAuthToken = async ({ store }) => { }) } -const afterStoreSetup = async ({ store, i18n }) => { +const afterStoreSetup = async ({ pinia, store, i18n }) => { + const app = createApp(App) + app.use(pinia) + store.dispatch('setLayoutWidth', windowWidth()) store.dispatch('setLayoutHeight', windowHeight()) @@ -397,11 +399,6 @@ const afterStoreSetup = async ({ store, i18n }) => { } }) - const app = createApp(App) - const pinia = createPinia() - - app.use(pinia) - useI18nStore().setI18n(i18n) app.use(router) diff --git a/src/components/shout_panel/shout_panel.js b/src/components/shout_panel/shout_panel.js index fb0c5aa21..4b8e8c8d7 100644 --- a/src/components/shout_panel/shout_panel.js +++ b/src/components/shout_panel/shout_panel.js @@ -4,6 +4,7 @@ import { faBullhorn, faTimes } from '@fortawesome/free-solid-svg-icons' +import { useShoutStore } from '../../stores/shout' library.add( faBullhorn, @@ -21,12 +22,12 @@ const shoutPanel = { }, computed: { messages () { - return this.$store.state.shout.messages + return useShoutStore().messages } }, methods: { submit (message) { - this.$store.state.shout.channel.push('new_msg', { text: message }, 10000) + useShoutStore().channel.push('new_msg', { text: message }, 10000) this.currentMessage = '' }, togglePanel () { diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js index 270195775..a0ab0ccbd 100644 --- a/src/components/side_drawer/side_drawer.js +++ b/src/components/side_drawer/side_drawer.js @@ -19,6 +19,7 @@ import { faCompass, faList } from '@fortawesome/free-solid-svg-icons' +import { useShoutStore } from '../../stores/shout' library.add( faSignInAlt, @@ -54,7 +55,7 @@ const SideDrawer = { currentUser () { return this.$store.state.users.currentUser }, - shout () { return this.$store.state.shout.joined }, + shout () { return useShoutStore().joined }, unseenNotifications () { return unseenNotificationsFromStore(this.$store) }, diff --git a/src/main.js b/src/main.js index 43869b01d..ee0adcb61 100644 --- a/src/main.js +++ b/src/main.js @@ -1,4 +1,5 @@ import { createStore } from 'vuex' +import { createPinia } from 'pinia' import 'custom-event-polyfill' import './lib/event_target_polyfill.js' @@ -12,7 +13,6 @@ import apiModule from './modules/api.js' import configModule from './modules/config.js' import serverSideConfigModule from './modules/serverSideConfig.js' import serverSideStorageModule from './modules/serverSideStorage.js' -import shoutModule from './modules/shout.js' import oauthModule from './modules/oauth.js' import authFlowModule from './modules/auth_flow.js' import mediaViewerModule from './modules/media_viewer.js' @@ -58,6 +58,7 @@ const persistedStateOptions = { (async () => { let storageError = false const plugins = [pushNotifications] + const pinia = createPinia() try { const persistedState = await createPersistedState(persistedStateOptions) plugins.push(persistedState) @@ -77,7 +78,6 @@ const persistedStateOptions = { config: configModule, serverSideConfig: serverSideConfigModule, serverSideStorage: serverSideStorageModule, - shout: shoutModule, oauth: oauthModule, authFlow: authFlowModule, mediaViewer: mediaViewerModule, @@ -98,7 +98,9 @@ const persistedStateOptions = { if (storageError) { store.dispatch('pushGlobalNotice', { messageKey: 'errors.storage_unavailable', level: 'error' }) } - afterStoreSetup({ store, i18n }) + + // Temporarily passing both vuex and pinia stores until migration is fully complete. + afterStoreSetup({ pinia, store, i18n }) })() // These are inlined by webpack's DefinePlugin diff --git a/src/modules/api.js b/src/modules/api.js index fee584e84..d6cef55f9 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -2,6 +2,7 @@ import backendInteractorService from '../services/backend_interactor_service/bac import { WSConnectionStatus } from '../services/api/api.service.js' import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' import { Socket } from 'phoenix' +import { useShoutStore } from '../stores/shout.js' const retryTimeout = (multiplier) => 1000 * multiplier @@ -283,7 +284,7 @@ const api = { socket.connect() commit('setSocket', socket) - dispatch('initializeShout', socket) + useShoutStore().initializeShout(socket) } }, disconnectFromSocket ({ commit, state }) { diff --git a/src/modules/shout.js b/src/modules/shout.js deleted file mode 100644 index 88aefbfea..000000000 --- a/src/modules/shout.js +++ /dev/null @@ -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 diff --git a/src/stores/shout.js b/src/stores/shout.js new file mode 100644 index 000000000..105e80e6f --- /dev/null +++ b/src/stores/shout.js @@ -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 + } + } +}) From 27e36dbc2ef9664d0acccb47dee36fe94a9dd4b8 Mon Sep 17 00:00:00 2001 From: Sean King Date: Wed, 5 Apr 2023 13:01:37 -0600 Subject: [PATCH 0010/1277] Move postStatus module to store --- .../mobile_post_status_button.js | 3 ++- .../post_status_modal/post_status_modal.js | 7 +++--- src/components/user_card/user_card.js | 3 ++- src/main.js | 2 -- src/modules/postStatus.js | 25 ------------------- src/stores/postStatus.js | 17 +++++++++++++ 6 files changed, 25 insertions(+), 32 deletions(-) delete mode 100644 src/modules/postStatus.js create mode 100644 src/stores/postStatus.js diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.js b/src/components/mobile_post_status_button/mobile_post_status_button.js index f7f96cd67..9a8af4551 100644 --- a/src/components/mobile_post_status_button/mobile_post_status_button.js +++ b/src/components/mobile_post_status_button/mobile_post_status_button.js @@ -3,6 +3,7 @@ import { library } from '@fortawesome/fontawesome-svg-core' import { faPen } from '@fortawesome/free-solid-svg-icons' +import { usePostStatusStore } from '../../stores/postStatus' library.add( faPen @@ -71,7 +72,7 @@ const MobilePostStatusButton = { window.removeEventListener('scroll', this.handleScrollEnd) }, openPostForm () { - this.$store.dispatch('openPostStatusModal') + usePostStatusStore().openPostStatusModal() }, handleOSK () { // This is a big hack: we're guessing from changed window sizes if the diff --git a/src/components/post_status_modal/post_status_modal.js b/src/components/post_status_modal/post_status_modal.js index b44354db1..fb7436ab7 100644 --- a/src/components/post_status_modal/post_status_modal.js +++ b/src/components/post_status_modal/post_status_modal.js @@ -1,6 +1,7 @@ import PostStatusForm from '../post_status_form/post_status_form.vue' import Modal from '../modal/modal.vue' import get from 'lodash/get' +import { usePostStatusStore } from '../../stores/postStatus' const PostStatusModal = { components: { @@ -17,13 +18,13 @@ const PostStatusModal = { return !!this.$store.state.users.currentUser }, modalActivated () { - return this.$store.state.postStatus.modalActivated + return usePostStatusStore().modalActivated }, isFormVisible () { return this.isLoggedIn && !this.resettingForm && this.modalActivated }, params () { - return this.$store.state.postStatus.params || {} + return usePostStatusStore().params || {} } }, watch: { @@ -43,7 +44,7 @@ const PostStatusModal = { }, methods: { closeModal () { - this.$store.dispatch('closePostStatusModal') + usePostStatusStore().closePostStatusModal() } } } diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index e17bf8eb2..ccbe9ce71 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -12,6 +12,7 @@ import RichContent from 'src/components/rich_content/rich_content.jsx' import ConfirmModal from '../confirm_modal/confirm_modal.vue' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import { mapGetters } from 'vuex' +import { usePostStatusStore } from '../../stores/postStatus' import { library } from '@fortawesome/fontawesome-svg-core' import { faBell, @@ -225,7 +226,7 @@ export default { this.$store.dispatch('setCurrentMedia', attachment) }, mentionUser () { - this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user }) + usePostStatusStore().openPostStatusModal({ replyTo: true, repliedUser: this.user }) }, onAvatarClickHandler (e) { if (this.onAvatarClick) { diff --git a/src/main.js b/src/main.js index ee0adcb61..4ba689292 100644 --- a/src/main.js +++ b/src/main.js @@ -19,7 +19,6 @@ import mediaViewerModule from './modules/media_viewer.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' @@ -84,7 +83,6 @@ const persistedStateOptions = { oauthTokens: oauthTokensModule, reports: reportsModule, polls: pollsModule, - postStatus: postStatusModule, editStatus: editStatusModule, statusHistory: statusHistoryModule, chats: chatsModule, diff --git a/src/modules/postStatus.js b/src/modules/postStatus.js deleted file mode 100644 index 638c1fb20..000000000 --- a/src/modules/postStatus.js +++ /dev/null @@ -1,25 +0,0 @@ -const postStatus = { - state: { - params: null, - modalActivated: false - }, - mutations: { - openPostStatusModal (state, params) { - state.params = params - state.modalActivated = true - }, - closePostStatusModal (state) { - state.modalActivated = false - } - }, - actions: { - openPostStatusModal ({ commit }, params) { - commit('openPostStatusModal', params) - }, - closePostStatusModal ({ commit }) { - commit('closePostStatusModal') - } - } -} - -export default postStatus diff --git a/src/stores/postStatus.js b/src/stores/postStatus.js new file mode 100644 index 000000000..b9fe96d3c --- /dev/null +++ b/src/stores/postStatus.js @@ -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 + } + } +}) From 3430604ddae7ef9af666d673d9a4e627b482f226 Mon Sep 17 00:00:00 2001 From: Sean King Date: Wed, 5 Apr 2023 13:23:25 -0600 Subject: [PATCH 0011/1277] Move editStatus module to store --- .../edit_status_modal/edit_status_modal.js | 9 ++++--- src/components/extra_buttons/extra_buttons.js | 3 ++- src/main.js | 2 -- src/modules/editStatus.js | 25 ------------------- src/stores/editStatus.js | 17 +++++++++++++ 5 files changed, 24 insertions(+), 32 deletions(-) delete mode 100644 src/modules/editStatus.js create mode 100644 src/stores/editStatus.js diff --git a/src/components/edit_status_modal/edit_status_modal.js b/src/components/edit_status_modal/edit_status_modal.js index 75adfea75..c39211510 100644 --- a/src/components/edit_status_modal/edit_status_modal.js +++ b/src/components/edit_status_modal/edit_status_modal.js @@ -2,6 +2,7 @@ import PostStatusForm from '../post_status_form/post_status_form.vue' import Modal from '../modal/modal.vue' import statusPosterService from '../../services/status_poster/status_poster.service.js' import get from 'lodash/get' +import { useEditStatusStore } from '../../stores/editStatus' const EditStatusModal = { components: { @@ -18,13 +19,13 @@ const EditStatusModal = { return !!this.$store.state.users.currentUser }, modalActivated () { - return this.$store.state.editStatus.modalActivated + return useEditStatusStore().modalActivated }, isFormVisible () { return this.isLoggedIn && !this.resettingForm && this.modalActivated }, params () { - return this.$store.state.editStatus.params || {} + return useEditStatusStore().params || {} } }, watch: { @@ -46,7 +47,7 @@ const EditStatusModal = { doEditStatus ({ status, spoilerText, sensitive, media, contentType, poll }) { const params = { store: this.$store, - statusId: this.$store.state.editStatus.params.statusId, + statusId: useEditStatusStore().params.statusId, status, spoilerText, sensitive, @@ -67,7 +68,7 @@ const EditStatusModal = { }) }, closeModal () { - this.$store.dispatch('closeEditStatusModal') + useEditStatusStore().closeEditStatusModal() } } } diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js index 48b960b2b..4f3c98e13 100644 --- a/src/components/extra_buttons/extra_buttons.js +++ b/src/components/extra_buttons/extra_buttons.js @@ -16,6 +16,7 @@ import { faBookmark as faBookmarkReg, faFlag } from '@fortawesome/free-regular-svg-icons' +import { useEditStatusStore } from '../../stores/editStatus' library.add( faEllipsisH, @@ -107,7 +108,7 @@ const ExtraButtons = { }, editStatus () { this.$store.dispatch('fetchStatusSource', { id: this.status.id }) - .then(data => this.$store.dispatch('openEditStatusModal', { + .then(data => useEditStatusStore().openEditStatusModal({ statusId: this.status.id, subject: data.spoiler_text, statusText: data.text, diff --git a/src/main.js b/src/main.js index 4ba689292..622c9a71f 100644 --- a/src/main.js +++ b/src/main.js @@ -19,7 +19,6 @@ import mediaViewerModule from './modules/media_viewer.js' import oauthTokensModule from './modules/oauth_tokens.js' import reportsModule from './modules/reports.js' import pollsModule from './modules/polls.js' -import editStatusModule from './modules/editStatus.js' import statusHistoryModule from './modules/statusHistory.js' import chatsModule from './modules/chats.js' @@ -83,7 +82,6 @@ const persistedStateOptions = { oauthTokens: oauthTokensModule, reports: reportsModule, polls: pollsModule, - editStatus: editStatusModule, statusHistory: statusHistoryModule, chats: chatsModule, announcements: announcementsModule diff --git a/src/modules/editStatus.js b/src/modules/editStatus.js deleted file mode 100644 index fd3165191..000000000 --- a/src/modules/editStatus.js +++ /dev/null @@ -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 diff --git a/src/stores/editStatus.js b/src/stores/editStatus.js new file mode 100644 index 000000000..9ead99192 --- /dev/null +++ b/src/stores/editStatus.js @@ -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 + } + } +}) From c25cfe540b8b879c8ffafaa2cd99f49723cfb703 Mon Sep 17 00:00:00 2001 From: Sean King Date: Wed, 5 Apr 2023 13:55:38 -0600 Subject: [PATCH 0012/1277] Move media_viewer module to store --- src/components/attachment/attachment.js | 5 ++- src/components/gallery/gallery.js | 7 ++-- src/components/media_modal/media_modal.js | 13 +++--- .../status_content/status_content.js | 3 +- src/components/user_card/user_card.js | 5 ++- src/main.js | 2 - src/modules/media_viewer.js | 40 ------------------- src/stores/media_viewer.js | 30 ++++++++++++++ 8 files changed, 49 insertions(+), 56 deletions(-) delete mode 100644 src/modules/media_viewer.js create mode 100644 src/stores/media_viewer.js diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index 6e14b24d7..deffe22cd 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -18,6 +18,7 @@ import { faPencilAlt, faAlignRight } from '@fortawesome/free-solid-svg-icons' +import { useMediaViewerStore } from '../../stores/media_viewer' library.add( faFile, @@ -147,14 +148,14 @@ const Attachment = { openModal (event) { if (this.useModal) { this.$emit('setMedia') - this.$store.dispatch('setCurrentMedia', this.attachment) + useMediaViewerStore().setCurrentMedia(this.attachment) } else if (this.type === 'unknown') { window.open(this.attachment.url) } }, openModalForce (event) { this.$emit('setMedia') - this.$store.dispatch('setCurrentMedia', this.attachment) + useMediaViewerStore().setCurrentMedia(this.attachment) }, onEdit (event) { this.edit && this.edit(this.attachment, event) diff --git a/src/components/gallery/gallery.js b/src/components/gallery/gallery.js index e86a3eeaf..ab1adcf2f 100644 --- a/src/components/gallery/gallery.js +++ b/src/components/gallery/gallery.js @@ -1,3 +1,4 @@ +import { useMediaViewerStore } from '../../stores/media_viewer' import Attachment from '../attachment/attachment.vue' import { sumBy, set } from 'lodash' @@ -107,11 +108,11 @@ const Gallery = { this.hidingLong = event }, openGallery () { - this.$store.dispatch('setMedia', this.attachments) - this.$store.dispatch('setCurrentMedia', this.attachments[0]) + useMediaViewerStore().setMedia(this.attachments) + useMediaViewerStore().setCurrentMedia(this.attachments[0]) }, onMedia () { - this.$store.dispatch('setMedia', this.attachments) + useMediaViewerStore().setMedia(this.attachments) } } } diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 05ef9fbe1..1c19afcad 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -13,6 +13,7 @@ import { faCircleNotch, faTimes } from '@fortawesome/free-solid-svg-icons' +import { useMediaViewerStore } from '../../stores/media_viewer' library.add( faChevronLeft, @@ -44,16 +45,16 @@ const MediaModal = { }, computed: { showing () { - return this.$store.state.mediaViewer.activated + return useMediaViewerStore().activated }, media () { - return this.$store.state.mediaViewer.media + return useMediaViewerStore().media }, description () { return this.currentMedia.description }, currentIndex () { - return this.$store.state.mediaViewer.currentIndex + return useMediaViewerStore().currentIndex }, currentMedia () { return this.media[this.currentIndex] @@ -79,7 +80,7 @@ const MediaModal = { // to be processed on the content below the overlay const transitionTime = 100 // ms setTimeout(() => { - this.$store.dispatch('closeMediaViewer') + useMediaViewerStore().closeMediaViewer() }, transitionTime) }, hideIfNotSwiped (event) { @@ -98,7 +99,7 @@ const MediaModal = { if (this.getType(newMedia) === 'image') { this.loading = true } - this.$store.dispatch('setCurrentMedia', newMedia) + useMediaViewerStore().setCurrentMedia(newMedia) } }, goNext () { @@ -108,7 +109,7 @@ const MediaModal = { if (this.getType(newMedia) === 'image') { this.loading = true } - this.$store.dispatch('setCurrentMedia', newMedia) + useMediaViewerStore().setCurrentMedia(newMedia) } }, onImageLoaded () { diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js index 89f0aa517..af62f9e14 100644 --- a/src/components/status_content/status_content.js +++ b/src/components/status_content/status_content.js @@ -13,6 +13,7 @@ import { faLink, faPollH } from '@fortawesome/free-solid-svg-icons' +import { useMediaViewerStore } from '../../stores/media_viewer' library.add( faCircleNotch, @@ -123,7 +124,7 @@ const StatusContent = { }, setMedia () { const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments - return () => this.$store.dispatch('setMedia', attachments) + return () => useMediaViewerStore().setMedia(attachments) } } } diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index ccbe9ce71..c2db91040 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -23,6 +23,7 @@ import { faTimes, faExpandAlt } from '@fortawesome/free-solid-svg-icons' +import { useMediaViewerStore } from '../../stores/media_viewer' library.add( faRss, @@ -222,8 +223,8 @@ export default { url: this.user.profile_image_url_original, mimetype: 'image' } - this.$store.dispatch('setMedia', [attachment]) - this.$store.dispatch('setCurrentMedia', attachment) + useMediaViewerStore().setMedia([attachment]) + useMediaViewerStore().setCurrentMedia(attachment) }, mentionUser () { usePostStatusStore().openPostStatusModal({ replyTo: true, repliedUser: this.user }) diff --git a/src/main.js b/src/main.js index 622c9a71f..503467c4a 100644 --- a/src/main.js +++ b/src/main.js @@ -15,7 +15,6 @@ import serverSideConfigModule from './modules/serverSideConfig.js' import serverSideStorageModule from './modules/serverSideStorage.js' import oauthModule from './modules/oauth.js' import authFlowModule from './modules/auth_flow.js' -import mediaViewerModule from './modules/media_viewer.js' import oauthTokensModule from './modules/oauth_tokens.js' import reportsModule from './modules/reports.js' import pollsModule from './modules/polls.js' @@ -78,7 +77,6 @@ const persistedStateOptions = { serverSideStorage: serverSideStorageModule, oauth: oauthModule, authFlow: authFlowModule, - mediaViewer: mediaViewerModule, oauthTokens: oauthTokensModule, reports: reportsModule, polls: pollsModule, diff --git a/src/modules/media_viewer.js b/src/modules/media_viewer.js deleted file mode 100644 index ebcba01d4..000000000 --- a/src/modules/media_viewer.js +++ /dev/null @@ -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 diff --git a/src/stores/media_viewer.js b/src/stores/media_viewer.js new file mode 100644 index 000000000..0e0e18289 --- /dev/null +++ b/src/stores/media_viewer.js @@ -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 + } + } +}) From 872569ae8e7c593f31a5d98356d89fdb7f024548 Mon Sep 17 00:00:00 2001 From: Sean King Date: Wed, 5 Apr 2023 14:13:28 -0600 Subject: [PATCH 0013/1277] Move statusHistory module to store --- src/components/extra_buttons/extra_buttons.js | 3 ++- .../status_history_modal.js | 7 +++--- src/main.js | 2 -- src/modules/statusHistory.js | 25 ------------------- src/stores/statusHistory.js | 17 +++++++++++++ 5 files changed, 23 insertions(+), 31 deletions(-) delete mode 100644 src/modules/statusHistory.js create mode 100644 src/stores/statusHistory.js diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js index 4f3c98e13..045d8a5fa 100644 --- a/src/components/extra_buttons/extra_buttons.js +++ b/src/components/extra_buttons/extra_buttons.js @@ -17,6 +17,7 @@ import { faFlag } from '@fortawesome/free-regular-svg-icons' import { useEditStatusStore } from '../../stores/editStatus' +import { useStatusHistoryStore } from '../../stores/statusHistory' library.add( faEllipsisH, @@ -123,7 +124,7 @@ const ExtraButtons = { const originalStatus = { ...this.status } const stripFieldsList = ['attachments', 'created_at', 'emojis', 'text', 'raw_html', 'nsfw', 'poll', 'summary', 'summary_raw_html'] stripFieldsList.forEach(p => delete originalStatus[p]) - this.$store.dispatch('openStatusHistoryModal', originalStatus) + useStatusHistoryStore().openStatusHistoryModal(originalStatus) } }, computed: { diff --git a/src/components/status_history_modal/status_history_modal.js b/src/components/status_history_modal/status_history_modal.js index 3941a56f8..752a7c2e9 100644 --- a/src/components/status_history_modal/status_history_modal.js +++ b/src/components/status_history_modal/status_history_modal.js @@ -1,6 +1,7 @@ import { get } from 'lodash' import Modal from '../modal/modal.vue' import Status from '../status/status.vue' +import { useStatusHistoryStore } from '../../stores/statusHistory' const StatusHistoryModal = { components: { @@ -14,10 +15,10 @@ const StatusHistoryModal = { }, computed: { modalActivated () { - return this.$store.state.statusHistory.modalActivated + return useStatusHistoryStore().modalActivated }, params () { - return this.$store.state.statusHistory.params + return useStatusHistoryStore().params }, statusId () { return this.params.id @@ -52,7 +53,7 @@ const StatusHistoryModal = { }) }, closeModal () { - this.$store.dispatch('closeStatusHistoryModal') + useStatusHistoryStore().closeStatusHistoryModal() } } } diff --git a/src/main.js b/src/main.js index 503467c4a..8c291f547 100644 --- a/src/main.js +++ b/src/main.js @@ -18,7 +18,6 @@ import authFlowModule from './modules/auth_flow.js' import oauthTokensModule from './modules/oauth_tokens.js' import reportsModule from './modules/reports.js' import pollsModule from './modules/polls.js' -import statusHistoryModule from './modules/statusHistory.js' import chatsModule from './modules/chats.js' import announcementsModule from './modules/announcements.js' @@ -80,7 +79,6 @@ const persistedStateOptions = { oauthTokens: oauthTokensModule, reports: reportsModule, polls: pollsModule, - statusHistory: statusHistoryModule, chats: chatsModule, announcements: announcementsModule }, diff --git a/src/modules/statusHistory.js b/src/modules/statusHistory.js deleted file mode 100644 index db3d6d4be..000000000 --- a/src/modules/statusHistory.js +++ /dev/null @@ -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 diff --git a/src/stores/statusHistory.js b/src/stores/statusHistory.js new file mode 100644 index 000000000..0d3c54c89 --- /dev/null +++ b/src/stores/statusHistory.js @@ -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 + } + } +}) From b1dcea01995f7da4ccb1d293969734d81ee71a5e Mon Sep 17 00:00:00 2001 From: Sean King Date: Wed, 5 Apr 2023 21:06:37 -0600 Subject: [PATCH 0014/1277] Migrate interface module to store --- src/App.js | 7 +- src/boot/after_store.js | 11 +- src/components/chat/chat.js | 6 +- src/components/chat_message/chat_message.js | 6 +- src/components/desktop_nav/desktop_nav.js | 3 +- .../global_notice_list/global_notice_list.js | 5 +- src/components/lists_edit/lists_edit.js | 3 +- src/components/notification/notification.js | 3 +- src/components/notifications/notifications.js | 11 +- .../post_status_form/post_status_form.js | 10 +- .../quick_filter_settings.js | 3 +- .../quick_view_settings.js | 3 +- .../settings_modal/settings_modal.js | 19 +-- .../settings_modal/settings_modal_content.js | 9 +- .../settings_modal/tabs/profile_tab.js | 5 +- .../tabs/theme_tab/theme_tab.js | 3 +- src/components/side_drawer/side_drawer.js | 7 +- src/components/status/status.js | 3 +- src/components/tab_switcher/tab_switcher.jsx | 7 +- src/components/timeline/timeline.js | 7 +- src/components/timeline_menu/timeline_menu.js | 3 +- src/components/user_card/user_card.js | 5 +- src/lib/persisted_state.js | 5 +- src/lib/push_notifications_plugin.js | 4 +- src/main.js | 14 +- src/modules/api.js | 5 +- src/modules/config.js | 3 +- src/modules/instance.js | 3 +- src/modules/reports.js | 3 +- src/modules/users.js | 13 +- .../notifications_fetcher.service.js | 3 +- .../timeline_fetcher.service.js | 3 +- src/stores/interface.js | 126 ++++++++++++++++++ 33 files changed, 244 insertions(+), 77 deletions(-) create mode 100644 src/stores/interface.js diff --git a/src/App.js b/src/App.js index ac6885f34..f283b3f14 100644 --- a/src/App.js +++ b/src/App.js @@ -18,6 +18,7 @@ import { windowWidth, windowHeight } from './services/window_utils/window_utils' import { mapGetters } from 'vuex' import { defineAsyncComponent } from 'vue' import { useShoutStore } from './stores/shout' +import { useInterfaceStore } from './stores/interface' export default { name: 'app', @@ -113,7 +114,7 @@ export default { 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 }, reverseLayout () { const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig @@ -129,8 +130,8 @@ export default { }, methods: { updateMobileState () { - this.$store.dispatch('setLayoutWidth', windowWidth()) - this.$store.dispatch('setLayoutHeight', windowHeight()) + useInterfaceStore().setLayoutWidth(windowWidth()) + useInterfaceStore().setLayoutHeight(windowHeight()) } } } diff --git a/src/boot/after_store.js b/src/boot/after_store.js index bc9b9996d..6a722aff7 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -18,6 +18,7 @@ import { applyTheme, applyConfig } from '../services/style_setter/style_setter.j import FaviconService from '../services/favicon_service/favicon_service.js' import { useI18nStore } from '../stores/i18n' +import { useInterfaceStore } from '../stores/interface' let staticInitialResults = null @@ -340,12 +341,16 @@ const checkOAuthToken = async ({ store }) => { }) } -const afterStoreSetup = async ({ pinia, store, i18n }) => { +const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { const app = createApp(App) app.use(pinia) - store.dispatch('setLayoutWidth', windowWidth()) - store.dispatch('setLayoutHeight', windowHeight()) + if (storageError) { + useInterfaceStore().pushGlobalNotice({ messageKey: 'errors.storage_unavailable', level: 'error' }) + } + + useInterfaceStore().setLayoutWidth(windowWidth()) + useInterfaceStore().setLayoutHeight(windowHeight()) FaviconService.initFaviconService() diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index 79f24771a..28c2be890 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -1,6 +1,7 @@ import _ from 'lodash' import { WSConnectionStatus } from '../../services/api/api.service.js' import { mapGetters, mapState } from 'vuex' +import { mapState as mapPiniaState } from 'pinia' import ChatMessage from '../chat_message/chat_message.vue' import PostStatusForm from '../post_status_form/post_status_form.vue' import ChatTitle from '../chat_title/chat_title.vue' @@ -13,6 +14,7 @@ import { faChevronLeft } from '@fortawesome/free-solid-svg-icons' import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js' +import { useInterfaceStore } from '../../stores/interface.js' library.add( faChevronDown, @@ -90,10 +92,12 @@ const Chat = { 'findOpenedChatByRecipientId', 'mergedConfig' ]), + ...mapPiniaState(useInterfaceStore, { + mobileLayout: store => store.layoutType === 'mobile' + }), ...mapState({ backendInteractor: state => state.api.backendInteractor, mastoUserSocketStatus: state => state.api.mastoUserSocketStatus, - mobileLayout: state => state.interface.layoutType === 'mobile', currentUser: state => state.users.currentUser }) }, diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js index ebe09814c..9d84811d0 100644 --- a/src/components/chat_message/chat_message.js +++ b/src/components/chat_message/chat_message.js @@ -1,4 +1,5 @@ import { mapState, mapGetters } from 'vuex' +import { mapState as mapPiniaState } from 'pinia' import Popover from '../popover/popover.vue' import Attachment from '../attachment/attachment.vue' import UserAvatar from '../user_avatar/user_avatar.vue' @@ -12,6 +13,7 @@ import { faTimes, faEllipsisH } from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from '../../stores/interface' library.add( faTimes, @@ -65,8 +67,10 @@ const ChatMessage = { hasAttachment () { return this.message.attachments.length > 0 }, + ...mapPiniaState(useInterfaceStore, { + betterShadow: store => store.browserSupport.cssFilter + }), ...mapState({ - betterShadow: state => state.interface.browserSupport.cssFilter, currentUser: state => state.users.currentUser, restrictedNicknames: state => state.instance.restrictedNicknames }), diff --git a/src/components/desktop_nav/desktop_nav.js b/src/components/desktop_nav/desktop_nav.js index 745b1a815..69ac7b73f 100644 --- a/src/components/desktop_nav/desktop_nav.js +++ b/src/components/desktop_nav/desktop_nav.js @@ -14,6 +14,7 @@ import { faCog, faInfoCircle } from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from '../../stores/interface' library.add( faSignInAlt, @@ -107,7 +108,7 @@ export default { this.searchBarHidden = hidden }, openSettingsModal () { - this.$store.dispatch('openSettingsModal') + useInterfaceStore().openSettingsModal() } } } diff --git a/src/components/global_notice_list/global_notice_list.js b/src/components/global_notice_list/global_notice_list.js index e93fba752..0d67eef79 100644 --- a/src/components/global_notice_list/global_notice_list.js +++ b/src/components/global_notice_list/global_notice_list.js @@ -2,6 +2,7 @@ import { library } from '@fortawesome/fontawesome-svg-core' import { faTimes } from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from '../../stores/interface' library.add( faTimes @@ -10,12 +11,12 @@ library.add( const GlobalNoticeList = { computed: { notices () { - return this.$store.state.interface.globalNotices + return useInterfaceStore().globalNotices } }, methods: { closeNotice (notice) { - this.$store.dispatch('removeGlobalNotice', notice) + useInterfaceStore().removeGlobalNotice(notice) } } } diff --git a/src/components/lists_edit/lists_edit.js b/src/components/lists_edit/lists_edit.js index c33659dfc..d929dbdfe 100644 --- a/src/components/lists_edit/lists_edit.js +++ b/src/components/lists_edit/lists_edit.js @@ -9,6 +9,7 @@ import { faSearch, faChevronLeft } from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from '../../stores/interface' library.add( faSearch, @@ -128,7 +129,7 @@ const ListsNew = { this.$router.push({ name: 'lists-timeline', params: { id: listId } }) }) .catch((e) => { - this.$store.dispatch('pushGlobalNotice', { + useInterfaceStore().pushGlobalNotice({ messageKey: 'lists.error', messageArgs: [e.message], level: 'error' diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 420db4f0f..d5b7fd266 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -25,6 +25,7 @@ import { faExpandAlt, faCompressAlt } from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from '../../stores/interface' library.add( faCheck, @@ -43,7 +44,7 @@ const Notification = { data () { return { statusExpanded: false, - betterShadow: this.$store.state.interface.browserSupport.cssFilter, + betterShadow: useInterfaceStore().browserSupport.cssFilter, unmuted: false, showingApproveConfirmDialog: false, showingDenyConfirmDialog: false diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index d499d3d62..e334d5178 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -11,6 +11,7 @@ import { import FaviconService from '../../services/favicon_service/favicon_service.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch, faArrowUp, faMinus } from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from '../../stores/interface' library.add( faCircleNotch, @@ -75,11 +76,11 @@ const Notifications = { return this.$store.state.statuses.notifications.loading }, noHeading () { - const { layoutType } = this.$store.state.interface + const { layoutType } = useInterfaceStore() return this.minimalMode || layoutType === 'mobile' }, teleportTarget () { - const { layoutType } = this.$store.state.interface + const { layoutType } = useInterfaceStore() const map = { wide: '#notifs-column', mobile: '#mobile-notifications' @@ -87,7 +88,7 @@ const Notifications = { return map[layoutType] || '#notifs-sidebar' }, popoversZLayer () { - const { layoutType } = this.$store.state.interface + const { layoutType } = useInterfaceStore() return layoutType === 'mobile' ? 'navbar' : null }, notificationsToDisplay () { @@ -114,10 +115,10 @@ const Notifications = { unseenCountTitle (count) { if (count > 0) { FaviconService.drawFaviconBadge() - this.$store.dispatch('setPageTitle', `(${count})`) + useInterfaceStore().setPageTitle(`(${count})`) } else { FaviconService.clearFaviconBadge() - this.$store.dispatch('setPageTitle', '') + useInterfaceStore().setPageTitle('') } }, teleportTarget () { diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index b75fee691..69eb21aff 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -11,7 +11,8 @@ import { findOffset } from '../../services/offset_finder/offset_finder.service.j import { propsToNative } from '../../services/attributes_helper/attributes_helper.service.js' import { reject, map, uniqBy, debounce } from 'lodash' import suggestor from '../emoji_input/suggestor.js' -import { mapGetters, mapState } from 'vuex' +import { mapGetters } from 'vuex' +import { mapState } from 'pinia' import Checkbox from '../checkbox/checkbox.vue' import Select from '../select/select.vue' @@ -24,6 +25,7 @@ import { faTimes, faCircleNotch } from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from '../../stores/interface.js' library.add( faSmileBeam, @@ -266,8 +268,8 @@ const PostStatusForm = { return typeof this.statusId !== 'undefined' && this.statusId.trim() !== '' }, ...mapGetters(['mergedConfig']), - ...mapState({ - mobileLayout: state => state.interface.mobileLayout + ...mapState(useInterfaceStore, { + mobileLayout: store => store.mobileLayout }) }, watch: { @@ -629,7 +631,7 @@ const PostStatusForm = { this.idempotencyKey = Date.now().toString() }, openProfileTab () { - this.$store.dispatch('openSettingsModalTab', 'profile') + useInterfaceStore().openSettingsModalTab('profile') }, propsToNative (props) { return propsToNative(props) diff --git a/src/components/quick_filter_settings/quick_filter_settings.js b/src/components/quick_filter_settings/quick_filter_settings.js index e67e3a4b1..6986ee555 100644 --- a/src/components/quick_filter_settings/quick_filter_settings.js +++ b/src/components/quick_filter_settings/quick_filter_settings.js @@ -2,6 +2,7 @@ import Popover from '../popover/popover.vue' import { mapGetters } from 'vuex' import { library } from '@fortawesome/fontawesome-svg-core' import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from '../../stores/interface' library.add( faFilter, @@ -22,7 +23,7 @@ const QuickFilterSettings = { this.$store.dispatch('queueFlushAll') }, openTab (tab) { - this.$store.dispatch('openSettingsModalTab', tab) + useInterfaceStore().openSettingsModalTab(tab) } }, computed: { diff --git a/src/components/quick_view_settings/quick_view_settings.js b/src/components/quick_view_settings/quick_view_settings.js index 2798f37ac..e1054dca0 100644 --- a/src/components/quick_view_settings/quick_view_settings.js +++ b/src/components/quick_view_settings/quick_view_settings.js @@ -2,6 +2,7 @@ import Popover from '../popover/popover.vue' import { mapGetters } from 'vuex' import { library } from '@fortawesome/fontawesome-svg-core' import { faList, faFolderTree, faBars, faWrench } from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from '../../stores/interface' library.add( faList, @@ -22,7 +23,7 @@ const QuickViewSettings = { this.$store.dispatch('setOption', { name: 'conversationDisplay', value: visibility }) }, openTab (tab) { - this.$store.dispatch('openSettingsModalTab', tab) + useInterfaceStore().openSettingsModalTab(tab) } }, computed: { diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js index 0a72dca1e..95edfeb72 100644 --- a/src/components/settings_modal/settings_modal.js +++ b/src/components/settings_modal/settings_modal.js @@ -19,6 +19,7 @@ import { import { faWindowMinimize } from '@fortawesome/free-regular-svg-icons' +import { useInterfaceStore } from '../../stores/interface' const PLEROMAFE_SETTINGS_MAJOR_VERSION = 1 const PLEROMAFE_SETTINGS_MINOR_VERSION = 0 @@ -64,10 +65,10 @@ const SettingsModal = { }, methods: { closeModal () { - this.$store.dispatch('closeSettingsModal') + useInterfaceStore().closeSettingsModal() }, peekModal () { - this.$store.dispatch('togglePeekSettingsModal') + useInterfaceStore().togglePeekSettingsModal() }, importValidator (data) { if (!Array.isArray(data._pleroma_settings_version)) { @@ -99,7 +100,7 @@ const SettingsModal = { } if (minor > PLEROMAFE_SETTINGS_MINOR_VERSION) { - this.$store.dispatch('pushGlobalNotice', { + useInterfaceStore().pushGlobalNotice({ level: 'warning', messageKey: 'settings.file_export_import.errors.file_slightly_new' }) @@ -109,9 +110,9 @@ const SettingsModal = { }, onImportFailure (result) { if (result.error) { - this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_settings_imported', level: 'error' }) + useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_settings_imported', level: 'error' }) } else { - this.$store.dispatch('pushGlobalNotice', { ...result.validationResult, level: 'error' }) + useInterfaceStore().pushGlobalNotice({ ...result.validationResult, level: 'error' }) } }, onImport (data) { @@ -151,16 +152,16 @@ const SettingsModal = { }, computed: { currentSaveStateNotice () { - return this.$store.state.interface.settings.currentSaveStateNotice + return useInterfaceStore().settings.currentSaveStateNotice }, modalActivated () { - return this.$store.state.interface.settingsModalState !== 'hidden' + return useInterfaceStore().settingsModalState !== 'hidden' }, modalOpenedOnce () { - return this.$store.state.interface.settingsModalLoaded + return useInterfaceStore().settingsModalLoaded }, modalPeeked () { - return this.$store.state.interface.settingsModalState === 'minimized' + return useInterfaceStore().settingsModalState === 'minimized' }, expertLevel: { get () { diff --git a/src/components/settings_modal/settings_modal_content.js b/src/components/settings_modal/settings_modal_content.js index 9ac0301f6..ca933f6a2 100644 --- a/src/components/settings_modal/settings_modal_content.js +++ b/src/components/settings_modal/settings_modal_content.js @@ -21,6 +21,7 @@ import { faEyeSlash, faInfo } from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from '../../stores/interface' library.add( faWrench, @@ -52,15 +53,15 @@ const SettingsModalContent = { return !!this.$store.state.users.currentUser }, open () { - return this.$store.state.interface.settingsModalState !== 'hidden' + return useInterfaceStore().settingsModalState !== 'hidden' }, bodyLock () { - return this.$store.state.interface.settingsModalState === 'visible' + return useInterfaceStore().settingsModalState === 'visible' } }, methods: { onOpen () { - const targetTab = this.$store.state.interface.settingsModalTargetTab + const targetTab = useInterfaceStore().settingsModalTargetTab // We're being told to open in specific tab if (targetTab) { const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => { @@ -72,7 +73,7 @@ const SettingsModalContent = { } // Clear the state of target tab, so that next time settings is opened // it doesn't force it. - this.$store.dispatch('clearSettingsModalTargetTab') + useInterfaceStore().clearSettingsModalTargetTab() } }, mounted () { diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index eeacad489..35b78d71f 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -20,6 +20,7 @@ import { faPlus, faCircleNotch } from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from '../../../stores/interface' library.add( faTimes, @@ -166,7 +167,7 @@ const ProfileTab = { if (file.size > this.$store.state.instance[slot + 'limit']) { const filesize = fileSizeFormatService.fileSizeFormat(file.size) const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit']) - this.$store.dispatch('pushGlobalNotice', { + useInterfaceStore().pushGlobalNotice({ messageKey: 'upload.error.message', messageArgs: [ this.$t('upload.error.file_too_big', { @@ -257,7 +258,7 @@ const ProfileTab = { .finally(() => { this.backgroundUploading = false }) }, displayUploadError (error) { - this.$store.dispatch('pushGlobalNotice', { + useInterfaceStore().pushGlobalNotice({ messageKey: 'upload.error.message', messageArgs: [error.message], level: 'error' diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js index 4a739f737..47f6417cb 100644 --- a/src/components/settings_modal/tabs/theme_tab/theme_tab.js +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.js @@ -38,6 +38,7 @@ import Checkbox from 'src/components/checkbox/checkbox.vue' import Select from 'src/components/select/select.vue' import Preview from './preview.vue' +import { useInterfaceStore } from '../../../../stores/interface' // List of color values used in v1 const v1OnlyNames = [ @@ -548,7 +549,7 @@ export default { this.loadTheme(parsed, 'file', forceSource) }, onImportFailure (result) { - this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_theme_imported', level: 'error' }) + useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_theme_imported', level: 'error' }) }, importValidator (parsed) { const version = parsed._pleroma_theme_version diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js index a0ab0ccbd..5c50eef92 100644 --- a/src/components/side_drawer/side_drawer.js +++ b/src/components/side_drawer/side_drawer.js @@ -20,6 +20,7 @@ import { faList } from '@fortawesome/free-solid-svg-icons' import { useShoutStore } from '../../stores/shout' +import { useInterfaceStore } from '../../stores/interface' library.add( faSignInAlt, @@ -85,8 +86,8 @@ const SideDrawer = { }, timelinesRoute () { let name - if (this.$store.state.interface.lastTimeline) { - name = this.$store.state.interface.lastTimeline + if (useInterfaceStore().lastTimeline) { + name = useInterfaceStore().lastTimeline } name = this.currentUser ? 'friends' : 'public-timeline' if (USERNAME_ROUTES.has(name)) { @@ -116,7 +117,7 @@ const SideDrawer = { GestureService.updateSwipe(e, this.closeGesture) }, openSettingsModal () { - this.$store.dispatch('openSettingsModal') + useInterfaceStore().openSettingsModal() } } } diff --git a/src/components/status/status.js b/src/components/status/status.js index 9a9bca7aa..bb86e8c9a 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -20,6 +20,7 @@ import generateProfileLink from 'src/services/user_profile_link_generator/user_p import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import { muteWordHits } from '../../services/status_parser/status_parser.js' import { unescape, uniqBy } from 'lodash' +import { useInterfaceStore } from '../../stores/interface' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -379,7 +380,7 @@ const Status = { return this.$store.state.users.currentUser }, betterShadow () { - return this.$store.state.interface.browserSupport.cssFilter + return useInterfaceStore().browserSupport.cssFilter }, mergedConfig () { return this.$store.getters.mergedConfig diff --git a/src/components/tab_switcher/tab_switcher.jsx b/src/components/tab_switcher/tab_switcher.jsx index a7ef8560a..cbc7809aa 100644 --- a/src/components/tab_switcher/tab_switcher.jsx +++ b/src/components/tab_switcher/tab_switcher.jsx @@ -1,9 +1,10 @@ // eslint-disable-next-line no-unused import { h, Fragment } from 'vue' -import { mapState } from 'vuex' +import { mapState } from 'pinia' import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome' import './tab_switcher.scss' +import { useInterfaceStore } from '../../stores/interface' const findFirstUsable = (slots) => slots.findIndex(_ => _.props) @@ -64,8 +65,8 @@ export default { settingsModalVisible () { return this.settingsModalState === 'visible' }, - ...mapState({ - settingsModalState: state => state.interface.settingsModalState + ...mapState(useInterfaceStore, { + settingsModalState: store => store.settingsModalState }) }, beforeUpdate () { diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index b74146109..e2f4d0336 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -1,5 +1,5 @@ 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 Conversation from '../conversation/conversation.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 { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch, faCirclePlus, faCog, faMinus, faArrowUp, faCheck } from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from '../../stores/interface' library.add( faCircleNotch, @@ -101,8 +102,8 @@ const Timeline = { virtualScrollingEnabled () { return this.$store.getters.mergedConfig.virtualScrolling }, - ...mapState({ - mobileLayout: state => state.interface.layoutType === 'mobile' + ...mapState(useInterfaceStore, { + mobileLayout: store => store.layoutType === 'mobile' }) }, created () { diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js index 5a2a86c2f..a9e7893cf 100644 --- a/src/components/timeline_menu/timeline_menu.js +++ b/src/components/timeline_menu/timeline_menu.js @@ -8,6 +8,7 @@ import { filterNavigation } from 'src/components/navigation/filter.js' import { faChevronDown } from '@fortawesome/free-solid-svg-icons' +import { useInterfaceStore } from '../../stores/interface' library.add(faChevronDown) @@ -36,7 +37,7 @@ const TimelineMenu = { }, created () { if (timelineNames()[this.$route.name]) { - this.$store.dispatch('setLastTimeline', this.$route.name) + useInterfaceStore().setLastTimeline(this.$route.name) } }, computed: { diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index c2db91040..1bc653378 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -24,6 +24,7 @@ import { faExpandAlt } from '@fortawesome/free-solid-svg-icons' import { useMediaViewerStore } from '../../stores/media_viewer' +import { useInterfaceStore } from '../../stores/interface' library.add( faRss, @@ -50,7 +51,7 @@ export default { data () { return { followRequestInProgress: false, - betterShadow: this.$store.state.interface.browserSupport.cssFilter, + betterShadow: useInterfaceStore().browserSupport.cssFilter, showingConfirmMute: false, muteExpiryAmount: 0, muteExpiryUnit: 'minutes' @@ -216,7 +217,7 @@ export default { ) }, openProfileTab () { - this.$store.dispatch('openSettingsModalTab', 'profile') + useInterfaceStore().openSettingsModalTab('profile') }, zoomAvatar () { const attachment = { diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js index 6d59c5955..ab9a79b39 100644 --- a/src/lib/persisted_state.js +++ b/src/lib/persisted_state.js @@ -1,6 +1,7 @@ import merge from 'lodash.merge' import localforage from 'localforage' import { each, get, set, cloneDeep } from 'lodash' +import { useInterfaceStore } from '../stores/interface' let loaded = false @@ -76,12 +77,12 @@ export default function createPersistedState ({ .then(success => { if (typeof success !== 'undefined') { if (mutation.type === 'setOption' || mutation.type === 'setCurrentUser') { - store.dispatch('settingsSaved', { success }) + useInterfaceStore().settingsSaved({ success }) } } }, error => { if (mutation.type === 'setOption' || mutation.type === 'setCurrentUser') { - store.dispatch('settingsSaved', { error }) + useInterfaceStore().settingsSaved({ error }) } }) } diff --git a/src/lib/push_notifications_plugin.js b/src/lib/push_notifications_plugin.js index f75bb8230..57258b4e8 100644 --- a/src/lib/push_notifications_plugin.js +++ b/src/lib/push_notifications_plugin.js @@ -1,8 +1,10 @@ +import { useInterfaceStore } from '../stores/interface' + export default (store) => { store.subscribe((mutation, state) => { const vapidPublicKey = state.instance.vapidPublicKey const webPushNotification = state.config.webPushNotifications - const permission = state.interface.notificationPermission === 'granted' + const permission = useInterfaceStore().notificationPermission === 'granted' const user = state.users.currentUser const isUserMutation = mutation.type === 'setCurrentUser' diff --git a/src/main.js b/src/main.js index 8c291f547..030d70e65 100644 --- a/src/main.js +++ b/src/main.js @@ -4,7 +4,6 @@ import { createPinia } from 'pinia' import 'custom-event-polyfill' import './lib/event_target_polyfill.js' -import interfaceModule from './modules/interface.js' import instanceModule from './modules/instance.js' import statusesModule from './modules/statuses.js' import listsModule from './modules/lists.js' @@ -62,9 +61,10 @@ const persistedStateOptions = { console.error(e) storageError = true } - const store = createStore({ + + // Temporarily storing as a global variable while we migrate to Pinia + window.vuex = createStore({ modules: { - interface: interfaceModule, instance: instanceModule, // TODO refactor users/statuses modules, they depend on each other users: usersModule, @@ -87,12 +87,10 @@ const persistedStateOptions = { // strict: process.env.NODE_ENV !== 'production' }) - if (storageError) { - store.dispatch('pushGlobalNotice', { messageKey: 'errors.storage_unavailable', level: 'error' }) - } + const store = window.vuex - // Temporarily passing both vuex and pinia stores until migration is fully complete. - afterStoreSetup({ pinia, store, i18n }) + // Temporarily passing pinia and vuex stores along with storageError result until migration is fully complete. + afterStoreSetup({ pinia, store, storageError, i18n }) })() // These are inlined by webpack's DefinePlugin diff --git a/src/modules/api.js b/src/modules/api.js index d6cef55f9..0000ddc8e 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -3,6 +3,7 @@ import { WSConnectionStatus } from '../services/api/api.service.js' import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' import { Socket } from 'phoenix' import { useShoutStore } from '../stores/shout.js' +import { useInterfaceStore } from '../stores/interface.js' const retryTimeout = (multiplier) => 1000 * multiplier @@ -132,7 +133,7 @@ const api = { state.mastoUserSocket.addEventListener('open', () => { // Do not show notification when we just opened up the page if (state.mastoUserSocketStatus !== WSConnectionStatus.STARTING_INITIAL) { - dispatch('pushGlobalNotice', { + useInterfaceStore().pushGlobalNotice({ level: 'success', messageKey: 'timeline.socket_reconnected', timeout: 5000 @@ -174,7 +175,7 @@ const api = { dispatch('startFetchingTimeline', { timeline: 'friends' }) dispatch('startFetchingNotifications') dispatch('startFetchingChats') - dispatch('pushGlobalNotice', { + useInterfaceStore().pushGlobalNotice({ level: 'error', messageKey: 'timeline.socket_broke', messageArgs: [code], diff --git a/src/modules/config.js b/src/modules/config.js index c60ff11eb..bbabebad6 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -3,6 +3,7 @@ import { setPreset, applyTheme, applyConfig } from '../services/style_setter/sty import messages from '../i18n/messages' import localeService from '../services/locale/locale.service.js' import { useI18nStore } from '../stores/i18n.js' +import { useInterfaceStore } from '../stores/interface.js' const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage' @@ -203,7 +204,7 @@ const config = { ) break case 'thirdColumnMode': - dispatch('setLayoutWidth', undefined) + useInterfaceStore().setLayoutWidth(undefined) break } } diff --git a/src/modules/instance.js b/src/modules/instance.js index bb0292da0..4a417d831 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -3,6 +3,7 @@ import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import apiService from '../services/api/api.service.js' import { instanceDefaultProperties } from './config.js' import { langCodeToCldrName, ensureFinalFallback } from '../i18n/languages.js' +import { useInterfaceStore } from '../stores/interface.js' const SORTED_EMOJI_GROUP_IDS = [ 'smileys-and-emotion', @@ -261,7 +262,7 @@ const instance = { commit('setInstanceOption', { name, value }) switch (name) { case 'name': - dispatch('setPageTitle') + useInterfaceStore().setPageTitle() break case 'shoutAvailable': if (value) { diff --git a/src/modules/reports.js b/src/modules/reports.js index 925792c03..c75377cd3 100644 --- a/src/modules/reports.js +++ b/src/modules/reports.js @@ -1,4 +1,5 @@ import filter from 'lodash/filter' +import { useInterfaceStore } from '../stores/interface' const reports = { state: { @@ -46,7 +47,7 @@ const reports = { commit('setReportState', { id, state }) rootState.api.backendInteractor.setReportState({ id, state }).catch(e => { console.error('Failed to set report state', e) - dispatch('pushGlobalNotice', { + useInterfaceStore().pushGlobalNotice({ level: 'error', messageKey: 'general.generic_error_message', messageArgs: [e.message], diff --git a/src/modules/users.js b/src/modules/users.js index a1316ba25..e2204bb19 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -3,6 +3,7 @@ import { windowWidth, windowHeight } from '../services/window_utils/window_utils import oauthApi from '../services/new_api/oauth.js' import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash' import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js' +import { useInterfaceStore } from '../stores/interface.js' // TODO: Unify with mergeOrAdd in statuses.js export const mergeOrAdd = (arr, obj, item) => { @@ -542,9 +543,9 @@ const users = { store.commit('clearNotifications') store.commit('resetStatuses') store.dispatch('resetChats') - store.dispatch('setLastTimeline', 'public-timeline') - store.dispatch('setLayoutWidth', windowWidth()) - store.dispatch('setLayoutHeight', windowHeight()) + useInterfaceStore().setLastTimeline('public-timeline') + useInterfaceStore().setLayoutWidth(windowWidth()) + useInterfaceStore().setLayoutHeight(windowHeight()) store.commit('clearServerSideStorage') }) }, @@ -568,7 +569,7 @@ const users = { store.dispatch('fetchEmoji') getNotificationPermission() - .then(permission => commit('setNotificationPermission', permission)) + .then(permission => useInterfaceStore().setNotificationPermission(permission)) // Set our new backend interactor commit('setBackendInteractor', backendInteractorService(accessToken)) @@ -614,8 +615,8 @@ const users = { // Get user mutes store.dispatch('fetchMutes') - store.dispatch('setLayoutWidth', windowWidth()) - store.dispatch('setLayoutHeight', windowHeight()) + useInterfaceStore().setLayoutWidth(windowWidth()) + useInterfaceStore().setLayoutHeight(windowHeight()) // Fetch our friends store.rootState.api.backendInteractor.fetchFriends({ id: user.id }) diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index 6c2472107..3b4f9f7b1 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -1,3 +1,4 @@ +import { useInterfaceStore } from '../../stores/interface.js' import apiService from '../api/api.service.js' import { promiseInterval } from '../promise_interval/promise_interval.js' @@ -70,7 +71,7 @@ const fetchNotifications = ({ store, args, older }) => { return notifications }) .catch((error) => { - store.dispatch('pushGlobalNotice', { + useInterfaceStore().pushGlobalNotice({ level: 'error', messageKey: 'notifications.error', messageArgs: [error.message], diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 8501907e4..c79590dff 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -2,6 +2,7 @@ import { camelCase } from 'lodash' import apiService from '../api/api.service.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 ccTimeline = camelCase(timeline) @@ -69,7 +70,7 @@ const fetchAndUpdate = ({ return { statuses, pagination } }) .catch((error) => { - store.dispatch('pushGlobalNotice', { + useInterfaceStore().pushGlobalNotice({ level: 'error', messageKey: 'timeline.error', messageArgs: [error.message], diff --git a/src/stores/interface.js b/src/stores/interface.js new file mode 100644 index 000000000..7a7195d39 --- /dev/null +++ b/src/stores/interface.js @@ -0,0 +1,126 @@ +import { defineStore } from 'pinia' + +export const useInterfaceStore = defineStore('interface', { + state: () => ({ + settingsModalState: 'hidden', + settingsModalLoaded: false, + settingsModalTargetTab: null, + settings: { + currentSaveStateNotice: null, + noticeClearTimeout: null, + notificationPermission: null + }, + browserSupport: { + cssFilter: window.CSS && window.CSS.supports && ( + window.CSS.supports('filter', 'drop-shadow(0 0)') || + window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)') + ) + }, + layoutType: 'normal', + globalNotices: [], + layoutHeight: 0, + lastTimeline: null + }), + actions: { + setPageTitle (option = '') { + try { + document.title = `${option} ${window.vuex.state.instance.name}` + } catch (error) { + console.error(`${error}`) + } + }, + settingsSaved ({ success, error }) { + if (success) { + if (this.noticeClearTimeout) { + 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 } + } + }, + setNotificationPermission (permission) { + this.notificationPermission = permission + }, + closeSettingsModal () { + this.settingsModalState = 'hidden' + }, + openSettingsModal () { + this.settingsModalState = 'visible' + if (!this.settingsModalLoaded) { + this.settingsModalLoaded = true + } + }, + togglePeekSettingsModal () { + switch (this.settingsModalState) { + case 'minimized': + this.settingsModalState = 'visible' + return + case 'visible': + this.settingsModalState = 'minimized' + return + default: + throw new Error('Illegal minimization state of settings modal') + } + }, + clearSettingsModalTargetTab () { + this.settingsModalTargetTab = null + }, + openSettingsModalTab (value) { + this.settingsModalTargetTab = value + this.openSettingsModal() + }, + removeGlobalNotice (notice) { + this.globalNotices = this.globalNotices.filter(n => n !== notice) + }, + pushGlobalNotice ( + { + messageKey, + messageArgs = {}, + level = 'error', + timeout = 0 + }) { + const notice = { + messageKey, + messageArgs, + level + } + + this.globalNotices.push(notice) + + // 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? + const newNotice = this.globalNotices[this.globalNotices.length - 1] + if (timeout) { + setTimeout(() => this.removeGlobalNotice(newNotice), timeout) + } + + return newNotice + }, + setLayoutHeight (value) { + this.layoutHeight = value + }, + setLayoutWidth (value) { + let width = value + if (value !== undefined) { + this.layoutWidth = value + } else { + width = this.layoutWidth + } + + const mobileLayout = width <= 800 + const normalOrMobile = mobileLayout ? 'mobile' : 'normal' + const { thirdColumnMode } = window.vuex.getters.mergedConfig + if (thirdColumnMode === 'none' || !window.vuex.state.users.currentUser) { + this.layoutType = normalOrMobile + } else { + const wideLayout = width >= 1300 + this.layoutType = wideLayout ? 'wide' : normalOrMobile + } + }, + setLastTimeline (value) { + this.lastTimeline = value + } + } +}) From 22ab848f6ba7f734499011e54952c31634d216d5 Mon Sep 17 00:00:00 2001 From: Sean King Date: Wed, 5 Apr 2023 21:50:17 -0600 Subject: [PATCH 0015/1277] Remove old interface module --- src/modules/interface.js | 160 --------------------------------------- 1 file changed, 160 deletions(-) delete mode 100644 src/modules/interface.js diff --git a/src/modules/interface.js b/src/modules/interface.js deleted file mode 100644 index a86193eaf..000000000 --- a/src/modules/interface.js +++ /dev/null @@ -1,160 +0,0 @@ -const defaultState = { - settingsModalState: 'hidden', - settingsModalLoaded: false, - settingsModalTargetTab: null, - settings: { - currentSaveStateNotice: null, - noticeClearTimeout: null, - notificationPermission: null - }, - browserSupport: { - cssFilter: window.CSS && window.CSS.supports && ( - window.CSS.supports('filter', 'drop-shadow(0 0)') || - window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)') - ) - }, - layoutType: 'normal', - globalNotices: [], - layoutHeight: 0, - lastTimeline: null -} - -const interfaceMod = { - state: defaultState, - 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 } - } - }, - setNotificationPermission (state, permission) { - state.notificationPermission = permission - }, - setLayoutType (state, value) { - state.layoutType = value - }, - closeSettingsModal (state) { - state.settingsModalState = 'hidden' - }, - togglePeekSettingsModal (state) { - switch (state.settingsModalState) { - case 'minimized': - state.settingsModalState = 'visible' - return - case 'visible': - state.settingsModalState = 'minimized' - return - default: - throw new Error('Illegal minimization state of settings modal') - } - }, - openSettingsModal (state) { - state.settingsModalState = 'visible' - if (!state.settingsModalLoaded) { - state.settingsModalLoaded = true - } - }, - setSettingsModalTargetTab (state, value) { - state.settingsModalTargetTab = value - }, - pushGlobalNotice (state, notice) { - state.globalNotices.push(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 - } - }, - 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 }) { - commit('openSettingsModal') - }, - togglePeekSettingsModal ({ commit }) { - commit('togglePeekSettingsModal') - }, - clearSettingsModalTargetTab ({ commit }) { - commit('setSettingsModalTargetTab', null) - }, - openSettingsModalTab ({ commit }, value) { - commit('setSettingsModalTargetTab', value) - commit('openSettingsModal') - }, - pushGlobalNotice ( - { commit, dispatch, state }, - { - messageKey, - messageArgs = {}, - level = 'error', - timeout = 0 - }) { - const notice = { - messageKey, - messageArgs, - level - } - commit('pushGlobalNotice', notice) - // 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? - const newNotice = state.globalNotices[state.globalNotices.length - 1] - if (timeout) { - setTimeout(() => dispatch('removeGlobalNotice', newNotice), timeout) - } - return newNotice - }, - removeGlobalNotice ({ commit }, notice) { - commit('removeGlobalNotice', notice) - }, - setLayoutHeight ({ commit }, value) { - commit('setLayoutHeight', value) - }, - // value is optional, assuming it was cached prior - setLayoutWidth ({ commit, state, rootGetters, rootState }, value) { - let width = value - if (value !== undefined) { - commit('setLayoutWidth', value) - } else { - width = state.layoutWidth - } - const mobileLayout = width <= 800 - const normalOrMobile = mobileLayout ? 'mobile' : 'normal' - const { thirdColumnMode } = rootGetters.mergedConfig - if (thirdColumnMode === 'none' || !rootState.users.currentUser) { - commit('setLayoutType', normalOrMobile) - } else { - const wideLayout = width >= 1300 - commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile) - } - }, - setLastTimeline ({ commit }, value) { - commit('setLastTimeline', value) - } - } -} - -export default interfaceMod From e3ca5b0a324d2c627d90f002e39e549caf93dce7 Mon Sep 17 00:00:00 2001 From: Sean King Date: Wed, 5 Apr 2023 22:30:20 -0600 Subject: [PATCH 0016/1277] Move polls module to store --- src/components/poll/poll.js | 17 ++++----- src/main.js | 2 -- src/modules/polls.js | 69 ------------------------------------- src/stores/polls.js | 57 ++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 81 deletions(-) delete mode 100644 src/modules/polls.js create mode 100644 src/stores/polls.js diff --git a/src/components/poll/poll.js b/src/components/poll/poll.js index e4d6869a4..79f90e480 100644 --- a/src/components/poll/poll.js +++ b/src/components/poll/poll.js @@ -1,6 +1,7 @@ import Timeago from 'components/timeago/timeago.vue' import RichContent from 'components/rich_content/rich_content.jsx' import { forEach, map } from 'lodash' +import { usePollsStore } from '../../stores/polls' export default { name: 'Poll', @@ -17,20 +18,20 @@ export default { } }, created () { - if (!this.$store.state.polls.pollsObject[this.pollId]) { - this.$store.dispatch('mergeOrAddPoll', this.basePoll) + if (!usePollsStore().pollsObject[this.pollId]) { + usePollsStore().mergeOrAddPoll(this.basePoll) } - this.$store.dispatch('trackPoll', this.pollId) + usePollsStore().trackPoll(this.pollId) }, unmounted () { - this.$store.dispatch('untrackPoll', this.pollId) + usePollsStore().untrackPoll(this.pollId) }, computed: { pollId () { return this.basePoll.id }, poll () { - const storePoll = this.$store.state.polls.pollsObject[this.pollId] + const storePoll = usePollsStore().pollsObject[this.pollId] return storePoll || {} }, options () { @@ -76,9 +77,6 @@ export default { resultTitle (option) { return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}` }, - fetchPoll () { - this.$store.dispatch('refreshPoll', { id: this.statusId, pollId: this.poll.id }) - }, activateOption (index) { // forgive me father: doing checking the radio/checkboxes // in code because of customized input elements need either @@ -106,8 +104,7 @@ export default { vote () { if (this.choiceIndices.length === 0) return this.loading = true - this.$store.dispatch( - 'votePoll', + usePollsStore().votePoll( { id: this.statusId, pollId: this.poll.id, choices: this.choiceIndices } ).then(poll => { this.loading = false diff --git a/src/main.js b/src/main.js index 030d70e65..695179aae 100644 --- a/src/main.js +++ b/src/main.js @@ -16,7 +16,6 @@ import oauthModule from './modules/oauth.js' import authFlowModule from './modules/auth_flow.js' import oauthTokensModule from './modules/oauth_tokens.js' import reportsModule from './modules/reports.js' -import pollsModule from './modules/polls.js' import chatsModule from './modules/chats.js' import announcementsModule from './modules/announcements.js' @@ -78,7 +77,6 @@ const persistedStateOptions = { authFlow: authFlowModule, oauthTokens: oauthTokensModule, reports: reportsModule, - polls: pollsModule, chats: chatsModule, announcements: announcementsModule }, diff --git a/src/modules/polls.js b/src/modules/polls.js deleted file mode 100644 index 1c4f98a49..000000000 --- a/src/modules/polls.js +++ /dev/null @@ -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 diff --git a/src/stores/polls.js b/src/stores/polls.js new file mode 100644 index 000000000..ab24a16df --- /dev/null +++ b/src/stores/polls.js @@ -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 + }) + } + } +}) From f9254e5fb77f5dfb3bbc9021618cfeeb09248fa1 Mon Sep 17 00:00:00 2001 From: Sean King Date: Thu, 6 Apr 2023 16:32:21 -0600 Subject: [PATCH 0017/1277] Move announcements module to store --- src/boot/after_store.js | 3 +- src/components/announcement/announcement.js | 7 +- .../announcements_page/announcements_page.js | 7 +- src/components/mobile_nav/mobile_nav.js | 5 +- src/components/nav_panel/nav_panel.js | 9 +- src/components/navigation/navigation.js | 1 + src/components/navigation/navigation_entry.js | 3 + .../navigation/navigation_entry.vue | 6 + src/components/notifications/notifications.js | 5 +- src/components/side_drawer/side_drawer.js | 13 +- src/main.js | 4 +- src/modules/announcements.js | 135 ------------------ src/stores/announcements.js | 115 +++++++++++++++ 13 files changed, 160 insertions(+), 153 deletions(-) delete mode 100644 src/modules/announcements.js create mode 100644 src/stores/announcements.js diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 6a722aff7..0f86aff5b 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -19,6 +19,7 @@ import FaviconService from '../services/favicon_service/favicon_service.js' import { useI18nStore } from '../stores/i18n' import { useInterfaceStore } from '../stores/interface' +import { useAnnouncementsStore } from '../stores/announcements' let staticInitialResults = null @@ -389,7 +390,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { // Start fetching things that don't need to block the UI store.dispatch('fetchMutes') - store.dispatch('startFetchingAnnouncements') + useAnnouncementsStore().startFetchingAnnouncements() getTOS({ store }) getStickers({ store }) diff --git a/src/components/announcement/announcement.js b/src/components/announcement/announcement.js index 30254926a..ab781a42b 100644 --- a/src/components/announcement/announcement.js +++ b/src/components/announcement/announcement.js @@ -2,6 +2,7 @@ import { mapState } from 'vuex' import AnnouncementEditor from '../announcement_editor/announcement_editor.vue' import RichContent from '../rich_content/rich_content.jsx' import localeService from '../../services/locale/locale.service.js' +import { useAnnouncementsStore } from '../../stores/announcements' const Announcement = { components: { @@ -67,11 +68,11 @@ const Announcement = { methods: { markAsRead () { if (!this.isRead) { - return this.$store.dispatch('markAnnouncementAsRead', this.announcement.id) + return useAnnouncementsStore().markAnnouncementAsRead(this.announcement.id) } }, deleteAnnouncement () { - return this.$store.dispatch('deleteAnnouncement', this.announcement.id) + return useAnnouncementsStore().deleteAnnouncement(this.announcement.id) }, formatTimeOrDate (time, locale) { const d = new Date(time) @@ -85,7 +86,7 @@ const Announcement = { this.editing = true }, submitEdit () { - this.$store.dispatch('editAnnouncement', { + useAnnouncementsStore().editAnnouncement({ id: this.announcement.id, ...this.editedAnnouncement }) diff --git a/src/components/announcements_page/announcements_page.js b/src/components/announcements_page/announcements_page.js index 8d1204d4c..2b8a85cf9 100644 --- a/src/components/announcements_page/announcements_page.js +++ b/src/components/announcements_page/announcements_page.js @@ -1,6 +1,7 @@ import { mapState } from 'vuex' import Announcement from '../announcement/announcement.vue' import AnnouncementEditor from '../announcement_editor/announcement_editor.vue' +import { useAnnouncementsStore } from '../../stores/announcements' const AnnouncementsPage = { components: { @@ -20,14 +21,14 @@ const AnnouncementsPage = { } }, mounted () { - this.$store.dispatch('fetchAnnouncements') + useAnnouncementsStore().fetchAnnouncements() }, computed: { ...mapState({ currentUser: state => state.users.currentUser }), announcements () { - return this.$store.state.announcements.announcements + return useAnnouncementsStore().announcements }, canPostAnnouncement () { return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements') @@ -36,7 +37,7 @@ const AnnouncementsPage = { methods: { postAnnouncement () { this.posting = true - this.$store.dispatch('postAnnouncement', this.newAnnouncement) + useAnnouncementsStore().postAnnouncement(this.newAnnouncement) .then(() => { this.newAnnouncement.content = '' this.startsAt = undefined diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js index dad1f6aa3..1c47fdf01 100644 --- a/src/components/mobile_nav/mobile_nav.js +++ b/src/components/mobile_nav/mobile_nav.js @@ -5,6 +5,7 @@ import { unseenNotificationsFromStore } from '../../services/notification_utils/ import GestureService from '../../services/gesture_service/gesture_service' import NavigationPins from 'src/components/navigation/navigation_pins.vue' import { mapGetters } from 'vuex' +import { mapState } from 'pinia' import { library } from '@fortawesome/fontawesome-svg-core' import { faTimes, @@ -13,6 +14,7 @@ import { faArrowUp, faMinus } from '@fortawesome/free-solid-svg-icons' +import { useAnnouncementsStore } from '../../stores/announcements' library.add( faTimes, @@ -57,7 +59,8 @@ const MobileNav = { isChat () { return this.$route.name === 'chat' }, - ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount']), + ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), + ...mapGetters(['unreadChatCount']), chatsPinned () { return new Set(this.$store.state.serverSideStorage.prefsStorage.collections.pinnedNavItems).has('chats') }, diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index 8c9c3b11f..429fcbce9 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -1,5 +1,6 @@ import ListsMenuContent from 'src/components/lists_menu/lists_menu_content.vue' import { mapState, mapGetters } from 'vuex' +import { mapState as mapPiniaState } from 'pinia' import { TIMELINES, ROOT_ITEMS } from 'src/components/navigation/navigation.js' import { filterNavigation } from 'src/components/navigation/filter.js' import NavigationEntry from 'src/components/navigation/navigation_entry.vue' @@ -21,6 +22,7 @@ import { faList, faBullhorn } from '@fortawesome/free-solid-svg-icons' +import { useAnnouncementsStore } from '../../stores/announcements' library.add( faUsers, @@ -82,13 +84,16 @@ const NavPanel = { } }, computed: { + ...mapPiniaState(useAnnouncementsStore, { + unreadAnnouncementCount: 'unreadAnnouncementCount', + supportsAnnouncements: store => store.supportsAnnouncements + }), ...mapState({ currentUser: state => state.users.currentUser, followRequestCount: state => state.api.followRequests.length, privateMode: state => state.instance.private, federating: state => state.instance.federating, pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, - supportsAnnouncements: state => state.announcements.supportsAnnouncements, pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems), collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav }), @@ -120,7 +125,7 @@ const NavPanel = { } ) }, - ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount']) + ...mapGetters(['unreadChatCount']) } } diff --git a/src/components/navigation/navigation.js b/src/components/navigation/navigation.js index face430ed..7018a5d0c 100644 --- a/src/components/navigation/navigation.js +++ b/src/components/navigation/navigation.js @@ -76,6 +76,7 @@ export const ROOT_ITEMS = { route: 'announcements', icon: 'bullhorn', label: 'nav.announcements', + store: 'announcements', badgeGetter: 'unreadAnnouncementCount', criteria: ['announcements'] } diff --git a/src/components/navigation/navigation_entry.js b/src/components/navigation/navigation_entry.js index 22ed77d9d..00376a03c 100644 --- a/src/components/navigation/navigation_entry.js +++ b/src/components/navigation/navigation_entry.js @@ -3,6 +3,8 @@ import { routeTo } from 'src/components/navigation/navigation.js' import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faThumbtack } from '@fortawesome/free-solid-svg-icons' +import { mapStores } from 'pinia' +import { useAnnouncementsStore } from '../../stores/announcements' library.add(faThumbtack) @@ -31,6 +33,7 @@ const NavigationEntry = { getters () { return this.$store.getters }, + ...mapStores(useAnnouncementsStore), ...mapState({ currentUser: state => state.users.currentUser, pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems) diff --git a/src/components/navigation/navigation_entry.vue b/src/components/navigation/navigation_entry.vue index 411ca5366..dd02e48b8 100644 --- a/src/components/navigation/navigation_entry.vue +++ b/src/components/navigation/navigation_entry.vue @@ -39,6 +39,12 @@ > {{ getters[item.badgeGetter] }} +
+ {{ this[`${item.store}Store`][item.badgeGetter] }} +
diff --git a/src/components/settings_modal/tabs/appearance_tab.vue b/src/components/settings_modal/tabs/appearance_tab.vue index 164247fcb..9c08fb542 100644 --- a/src/components/settings_modal/tabs/appearance_tab.vue +++ b/src/components/settings_modal/tabs/appearance_tab.vue @@ -1,16 +1,19 @@ From 13838a75a9b5b0f2b59fa5b10675952e1b7cae11 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 3 Oct 2024 02:16:55 +0300 Subject: [PATCH 0131/1277] import of v2 on appearance tab works now --- .../settings_modal/tabs/appearance_tab.js | 31 ++++++++++++++++++- .../settings_modal/tabs/appearance_tab.scss | 15 +++++++++ .../settings_modal/tabs/appearance_tab.vue | 11 ++++++- src/modules/interface.js | 31 +++++++------------ src/services/export_import/export_import.js | 8 +++-- 5 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js index 247a800e5..1e48c7e88 100644 --- a/src/components/settings_modal/tabs/appearance_tab.js +++ b/src/components/settings_modal/tabs/appearance_tab.js @@ -8,6 +8,7 @@ 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 { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js' import { init } from 'src/services/theme_data/theme_data_3.service.js' import { @@ -33,6 +34,12 @@ const AppearanceTab = { return { availableStyles: [], availablePalettes: [], + themeImporter: newImporter({ + accept: '.json, .piss', + validator: this.importValidator, + onImport: this.onImport, + onImportFailure: this.onImportFailure + }), palettesKeys: [ 'background', 'foreground', @@ -184,7 +191,6 @@ const AppearanceTab = { }, methods: { updateFont (key, value) { - console.log(key, value) this.$store.dispatch('setOption', { name: 'theme3hacks', value: { @@ -196,6 +202,26 @@ const AppearanceTab = { } }) }, + importTheme () { + this.themeImporter.importData() + }, + onImport (parsed, filename) { + if (filename.endsWith('.json')) { + this.$store.dispatch('setThemeCustom', parsed.source || parsed.theme) + this.$store.dispatch('applyTheme') + } + + // this.loadTheme(parsed, 'file', forceSource) + }, + onImportFailure (result) { + this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_theme_imported', level: 'error' }) + }, + importValidator (parsed, filename) { + if (filename.endsWith('.json')) { + const version = parsed._pleroma_theme_version + return version >= 1 || version <= 2 + } + }, isThemeActive (key) { const { theme } = this.mergedConfig return key === theme @@ -207,6 +233,9 @@ const AppearanceTab = { isPaletteActive (key) { const { palette } = this.mergedConfig return key === palette + }, + importStyle () { + }, setTheme (name) { this.$store.dispatch('setTheme', name) diff --git a/src/components/settings_modal/tabs/appearance_tab.scss b/src/components/settings_modal/tabs/appearance_tab.scss index 7d5467410..77d668ecb 100644 --- a/src/components/settings_modal/tabs/appearance_tab.scss +++ b/src/components/settings_modal/tabs/appearance_tab.scss @@ -5,6 +5,21 @@ margin: 1em; } + .setting-item { + padding-bottom: 0; + + &.heading { + display: grid; + align-items: baseline; + grid-template-columns: 1fr auto auto auto; + grid-gap: 0.5em; + + h2 { + flex: 1 0 auto; + } + } + } + .palettes { display: grid; grid-template-columns: 1fr 1fr; diff --git a/src/components/settings_modal/tabs/appearance_tab.vue b/src/components/settings_modal/tabs/appearance_tab.vue index 9c08fb542..d27f43fe1 100644 --- a/src/components/settings_modal/tabs/appearance_tab.vue +++ b/src/components/settings_modal/tabs/appearance_tab.vue @@ -3,8 +3,17 @@ class="appearance-tab" :label="$t('settings.general')" > -
+

{{ $t('settings.theme') }}

+ +
+
    true @@ -27,18 +28,19 @@ export const newImporter = ({ importData () { const filePicker = document.createElement('input') filePicker.setAttribute('type', 'file') - filePicker.setAttribute('accept', '.json') + filePicker.setAttribute('accept', accept) filePicker.addEventListener('change', event => { if (event.target.files[0]) { + const filename = event.target.files[0].name // eslint-disable-next-line no-undef const reader = new FileReader() reader.onload = ({ target }) => { try { const parsed = JSON.parse(target.result) - const validationResult = validator(parsed) + const validationResult = validator(parsed, filename) if (validationResult === true) { - onImport(parsed) + onImport(parsed, filename) } else { onImportFailure({ validationResult }) } From 81d9537f9d00ab18cab4305344d7563fb6418ddd Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 3 Oct 2024 23:03:33 +0300 Subject: [PATCH 0132/1277] show warning about palettes being unsupported when using v2 theme --- .../settings_modal/tabs/appearance_tab.js | 18 +++++--- .../settings_modal/tabs/appearance_tab.scss | 4 ++ .../settings_modal/tabs/appearance_tab.vue | 45 +++++++++++-------- src/i18n/en.json | 3 +- src/modules/interface.js | 5 ++- 5 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js index 1e48c7e88..bba4647c6 100644 --- a/src/components/settings_modal/tabs/appearance_tab.js +++ b/src/components/settings_modal/tabs/appearance_tab.js @@ -34,7 +34,7 @@ const AppearanceTab = { return { availableStyles: [], availablePalettes: [], - themeImporter: newImporter({ + fileImporter: newImporter({ accept: '.json, .piss', validator: this.importValidator, onImport: this.onImport, @@ -179,6 +179,10 @@ const AppearanceTab = { this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) } }, + customThemeVersion () { + const { themeVersion } = this.$store.state.interface + return themeVersion + }, isCustomThemeUsed () { const { theme } = this.mergedConfig return theme === 'custom' @@ -202,8 +206,8 @@ const AppearanceTab = { } }) }, - importTheme () { - this.themeImporter.importData() + importFile () { + this.fileImporter.importData() }, onImport (parsed, filename) { if (filename.endsWith('.json')) { @@ -234,14 +238,18 @@ const AppearanceTab = { const { palette } = this.mergedConfig return key === palette }, - importStyle () { - + setStyle (name) { + this.$store.dispatch('resetThemeV2') + this.$store.dispatch('setTheme', name) + this.$store.dispatch('applyTheme') }, setTheme (name) { + this.$store.dispatch('resetThemeV3') this.$store.dispatch('setTheme', name) this.$store.dispatch('applyTheme') }, setPalette (name) { + this.$store.dispatch('resetThemeV2') this.$store.dispatch('setPalette', name) this.$store.dispatch('applyTheme') }, diff --git a/src/components/settings_modal/tabs/appearance_tab.scss b/src/components/settings_modal/tabs/appearance_tab.scss index 77d668ecb..b95ef07db 100644 --- a/src/components/settings_modal/tabs/appearance_tab.scss +++ b/src/components/settings_modal/tabs/appearance_tab.scss @@ -24,6 +24,10 @@ display: grid; grid-template-columns: 1fr 1fr; grid-gap: 0.5em; + + .unsupported-theme-v2 { + grid-column: 1 / span 2; + } } .palette-entry { diff --git a/src/components/settings_modal/tabs/appearance_tab.vue b/src/components/settings_modal/tabs/appearance_tab.vue index d27f43fe1..a65c52310 100644 --- a/src/components/settings_modal/tabs/appearance_tab.vue +++ b/src/components/settings_modal/tabs/appearance_tab.vue @@ -7,7 +7,7 @@

    {{ $t('settings.theme') }}

{{ $t('settings.style.themes3.palette.label') }}

- + +
diff --git a/src/i18n/en.json b/src/i18n/en.json index 0da8d4f00..603bc4910 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -771,7 +771,8 @@ "cOrange": "Orange color", "extra1": "Extra 1", "extra2": "Extra 2", - "extra3": "Extra 3" + "extra3": "Extra 3", + "v2_unsupported": "Older v2 themes don't support palettes. Switch to v3 theme to make use of palettes", }, "editor": { "title": "Style", diff --git a/src/modules/interface.js b/src/modules/interface.js index 96d23cbcd..6daa4a1b2 100644 --- a/src/modules/interface.js +++ b/src/modules/interface.js @@ -5,6 +5,7 @@ import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js' const defaultState = { localFonts: null, themeApplied: false, + themeVersion: 'v3', temporaryChangesTimeoutId: null, // used for temporary options that revert after a timeout temporaryChangesConfirm: () => {}, // used for applying temporary options temporaryChangesRevert: () => {}, // used for reverting temporary options @@ -307,7 +308,7 @@ const interfaceMod = { commit('setOption', { name: 'customThemeSource', value: null }) }, async applyTheme ( - { dispatch, commit, rootState }, + { dispatch, commit, rootState, state }, { recompile = true } = {} ) { // If we're not not forced to recompile try using @@ -398,6 +399,8 @@ const interfaceMod = { majorVersionUsed = 'v3' } + state.themeVersion = majorVersionUsed + let styleDataUsed = null let styleNameUsed = null let paletteDataUsed = null From 9e3e4ed429e11594ffeccbe8000afeaebe066cb0 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 3 Oct 2024 23:03:56 +0300 Subject: [PATCH 0133/1277] rearrange palettes so that Pleroma-dark is default (first) and bird is next to Pleroma-light in UI --- static/palettes/index.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/palettes/index.json b/static/palettes/index.json index acb4e4d98..63fbad5a5 100644 --- a/static/palettes/index.json +++ b/static/palettes/index.json @@ -1,6 +1,6 @@ { - "pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], "pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], + "pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], "classic-dark": { "name": "Classic Dark", "background": "#161c20", @@ -12,6 +12,7 @@ "cBlue": "#0095ff", "cOrange": "#ffa500" }, + "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"], "pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"], "tomorrow-night": { "name": "Tomorrow Night", @@ -26,7 +27,6 @@ "_cYellow": "#f0c674", "_cPurple": "#b294bb" }, - "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"], "ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ], "monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ] } From c937736feab589fb123b8208792d5f12e3a056de Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 4 Oct 2024 00:27:53 +0300 Subject: [PATCH 0134/1277] shadow editor now can handle expressions (functions and variables) --- .../tabs/style_tab/style_tab.js | 37 +- .../tabs/style_tab/style_tab.vue | 2 +- .../shadow_control/shadow_control.js | 80 ++-- .../shadow_control/shadow_control.scss | 10 +- .../shadow_control/shadow_control.vue | 353 ++++++++++-------- src/i18n/en.json | 3 + .../theme_data/theme_data_3.service.js | 7 +- 7 files changed, 285 insertions(+), 207 deletions(-) diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.js b/src/components/settings_modal/tabs/style_tab/style_tab.js index ecddf9d56..cbd25dfc2 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.js +++ b/src/components/settings_modal/tabs/style_tab/style_tab.js @@ -219,9 +219,13 @@ export default { return selectors.map(x => x.substring(1)).join('') }) const previewCss = computed(() => { - const scoped = getCssRules(previewRules) - .map(simulatePseudoSelectors) - return scoped.join('\n') + try { + const scoped = getCssRules(previewRules).map(simulatePseudoSelectors) + return scoped.join('\n') + } catch (e) { + console.error('Invalid ruleset', e) + return null + } }) // ### Rules stuff aka meat and potatoes @@ -415,17 +419,22 @@ export default { }) const updatePreview = () => { - previewRules.splice(0, previewRules.length) - previewRules.push(...init({ - inputRuleset: editorFriendlyToOriginal.value, - initialStaticVars: { - ...palette.value - }, - ultimateBackgroundColor: '#000000', - rootComponentName: selectedComponentName.value, - editMode: true, - debug: true - }).eager) + try { + const rules = init({ + inputRuleset: editorFriendlyToOriginal.value, + initialStaticVars: { + ...palette.value + }, + ultimateBackgroundColor: '#000000', + rootComponentName: selectedComponentName.value, + editMode: true, + debug: true + }).eager + previewRules.splice(0, previewRules.length) + previewRules.push(...rules) + } catch (e) { + console.error('Could not compile preview theme', e) + } } const updateSelectedComponent = () => { diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.vue b/src/components/settings_modal/tabs/style_tab/style_tab.vue index cd9d3e474..590624ffd 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.vue +++ b/src/components/settings_modal/tabs/style_tab/style_tab.vue @@ -125,7 +125,7 @@ :shadow-control="isShadowTabOpen" :preview-class="previewClass" :preview-style="editorHintStyle" - :disabled="!editedSubShadow" + :disabled="!editedSubShadow && typeof editedShadow !== 'string'" :shadow="editedSubShadow" @update:shadow="({ axis, value }) => updateSubShadow(axis, value)" /> diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js index 4521305ea..2befb8ba2 100644 --- a/src/components/shadow_control/shadow_control.js +++ b/src/components/shadow_control/shadow_control.js @@ -21,16 +21,22 @@ library.add( faPlus ) -const toModel = (object = {}) => ({ - x: 0, - y: 0, - blur: 0, - spread: 0, - inset: false, - color: '#000000', - alpha: 1, - ...object -}) +const toModel = (input) => { + if (typeof input === 'object') { + return { + x: 0, + y: 0, + blur: 0, + spread: 0, + inset: false, + color: '#000000', + alpha: 1, + ...input + } + } else if (typeof input === 'string') { + return input + } +} export default { props: [ @@ -56,12 +62,29 @@ export default { this.cValue = (this.modelValue ?? this.fallback ?? []).map(toModel) }, computed: { - selected () { - const selected = this.cValue[this.selectedId] - if (selected) { - return { ...selected } + selectedType: { + get () { + return typeof this.selected + }, + set (newType) { + this.selected = toModel(newType === 'object' ? {} : '') + } + }, + selected: { + get () { + const selected = this.cValue[this.selectedId] + console.log('SELECTED', selected) + if (selected && typeof selected === 'object') { + return { ...selected } + } else if (typeof selected === 'string') { + return selected + } + return null + }, + set (value) { + this.cValue[this.selectedId] = toModel(value) + this.$emit('update:modelValue', this.cValue) } - return null }, present () { return this.selected != null && !this.usingFallback @@ -82,14 +105,20 @@ export default { return this.modelValue == null }, style () { - if (this.separateInset) { - return { - filter: getCssShadowFilter(this.cValue), - boxShadow: getCssShadow(this.cValue, true) + try { + if (this.separateInset) { + return { + filter: getCssShadowFilter(this.cValue), + boxShadow: getCssShadow(this.cValue, true) + } + } + return { + boxShadow: getCssShadow(this.cValue) + } + } catch (e) { + return { + border: '1px solid red' } - } - return { - boxShadow: getCssShadow(this.cValue) } } }, @@ -99,6 +128,13 @@ export default { } }, methods: { + getSubshadowLabel (shadow, index) { + if (typeof shadow === 'object') { + return shadow?.name ?? this.$t('settings.style.shadows.shadow_id', { value: index }) + } else if (typeof shadow === 'string') { + return shadow || this.$t('settings.style.shadows.empty_expression') + } + }, updateProperty: throttle(function (prop, value) { this.cValue[this.selectedId][prop] = value if (prop === 'inset' && value === false && this.separateInset) { diff --git a/src/components/shadow_control/shadow_control.scss b/src/components/shadow_control/shadow_control.scss index dd0490235..067895eee 100644 --- a/src/components/shadow_control/shadow_control.scss +++ b/src/components/shadow_control/shadow_control.scss @@ -4,6 +4,7 @@ justify-content: stretch; grid-gap: 0.25em; margin-bottom: 1em; + width: 100%; .shadow-switcher { order: 1; @@ -37,6 +38,9 @@ min-width: 10em; margin-left: 0.125em; margin-right: 0.125em; + display: grid; + grid-template-rows: auto 1fr; + grid-gap: 0.25em; /* hack */ .input-boolean { @@ -52,6 +56,11 @@ flex: 1 0 5em; } + .shadow-expression { + width: 100%; + height: 100%; + } + .id-control { align-items: stretch; @@ -100,6 +109,5 @@ } .inset-tooltip { - padding: 0.5em; max-width: 30em; } diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue index e1d201914..669de36e1 100644 --- a/src/components/shadow_control/shadow_control.vue +++ b/src/components/shadow_control/shadow_control.vue @@ -8,7 +8,6 @@ class="shadow-preview" :shadow-control="true" :shadow="selected" - :preview-style="style" :disabled="disabled || !present" @update:shadow="({ axis, value }) => updateProperty(axis, value)" /> @@ -18,7 +17,7 @@ v-model="selectedId" class="shadow-list" size="10" - :disabled="shadowsAreNull" + :disabled="disabled || shadowsAreNull" >
-
-
diff --git a/src/i18n/en.json b/src/i18n/en.json index 64d6201df..df0f8d6db 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -966,6 +966,9 @@ "blur": "Blur", "spread": "Spread", "inset": "Inset", + "raw": "Plain shadow", + "expression": "Expression (advanced)", + "empty_expression": "Empty expression", "hintV3": "For shadows you can also use the {0} notation to use other color slot.", "filter_hint": { "always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.", diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index 4765a7733..e45d2cefd 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -44,8 +44,8 @@ const findShadow = (shadows, { dynamicVars, staticVars }) => { if (shadow.startsWith('$')) { targetShadow = process(shadow, shadowFunctions, { findColor, findShadow }, { dynamicVars, staticVars }) } else if (shadow.startsWith('--')) { - const [variable] = shadow.split(/,/g).map(str => str.trim()) // discarding modifier since it's not supported - const variableSlot = variable.substring(2) + // modifiers are completely unsupported here + const variableSlot = shadow.substring(2) return findShadow(staticVars[variableSlot], { dynamicVars, staticVars }) } else { targetShadow = parseShadow(shadow) @@ -66,6 +66,7 @@ const findColor = (color, { dynamicVars, staticVars }) => { if (typeof color !== 'string' || (!color.startsWith('--') && !color.startsWith('$'))) return color let targetColor = null if (color.startsWith('--')) { + // Modifier support is pretty much for v2 themes only const [variable, modifier] = color.split(/,/g).map(str => str.trim()) const variableSlot = variable.substring(2) if (variableSlot === 'stack') { @@ -421,7 +422,7 @@ export const init = ({ break } case 'shadow': { - const shadow = value.split(/,/g).map(s => s.trim()) + const shadow = value.split(/,/g).map(s => s.trim()).filter(x => x) dynamicVars[k] = shadow if (combination.component === rootComponentName) { staticVars[k.substring(2)] = shadow From 3d77860e57892569cfe98bccfdc65d3a9c95dbf6 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 4 Oct 2024 02:49:20 +0300 Subject: [PATCH 0135/1277] moved the select motion stuff into its own component --- src/components/select/select_motion.vue | 115 +++++ .../tabs/style_tab/style_tab.vue | 432 +++++++++--------- .../shadow_control/shadow_control.js | 45 +- .../shadow_control/shadow_control.vue | 55 +-- 4 files changed, 356 insertions(+), 291 deletions(-) create mode 100644 src/components/select/select_motion.vue diff --git a/src/components/select/select_motion.vue b/src/components/select/select_motion.vue new file mode 100644 index 000000000..1b9f4041a --- /dev/null +++ b/src/components/select/select_motion.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.vue b/src/components/settings_modal/tabs/style_tab/style_tab.vue index 590624ffd..7026f7afb 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.vue +++ b/src/components/settings_modal/tabs/style_tab/style_tab.vue @@ -51,231 +51,241 @@
-
-
- - -
+
- - - {{ fallbackI18n($t(getVariantPath(selectedComponentName, variant)), variant) }} - - -
-
- -
    + {{ fallbackI18n($t(getFriendlyNamePath(componentsMap.get(key).name)), componentsMap.get(key).name) }} + + +
+
-
  • + {{ $t('settings.style.themes3.editor.variant_selector') }} + + +
  • +
    + +
      +
    • + + {{ fallbackI18n($t(getStatePath(selectedComponentName, state)), state) }} + +
    • +
    +
    +
    + + + + +
    + +
    + + + + + + + + + + + + +
    + + +
    + + + +
    + +
    +
    + +
    + + + + + + + + +
    +
    - {{ fallbackI18n($t(getStatePath(selectedComponentName, state)), state) }} - - - + {{ $t('settings.style.themes3.editor.include_in_rule') }} + + +
    +
    -
    - - - - -
    - -
    - - - - - - - - - - + + + - - - -
    - - - -
    - -
    -
    - -
    - - - - - - - - + {{ $t('settings.style.themes3.palette.light') }} + +
    -
    - - {{ $t('settings.style.themes3.editor.include_in_rule') }} - - -
    - - -
    -
    - - +
    - -
    + diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js index 2befb8ba2..fc227b5be 100644 --- a/src/components/shadow_control/shadow_control.js +++ b/src/components/shadow_control/shadow_control.js @@ -1,6 +1,7 @@ import ColorInput from 'src/components/color_input/color_input.vue' import OpacityInput from 'src/components/opacity_input/opacity_input.vue' import Select from 'src/components/select/select.vue' +import SelectMotion from 'src/components/select/select_motion.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' import Popover from 'src/components/popover/popover.vue' import ComponentPreview from 'src/components/component_preview/component_preview.vue' @@ -54,13 +55,11 @@ export default { ColorInput, OpacityInput, Select, + SelectMotion, Checkbox, Popover, ComponentPreview }, - beforeUpdate () { - this.cValue = (this.modelValue ?? this.fallback ?? []).map(toModel) - }, computed: { selectedType: { get () { @@ -73,7 +72,6 @@ export default { selected: { get () { const selected = this.cValue[this.selectedId] - console.log('SELECTED', selected) if (selected && typeof selected === 'object') { return { ...selected } } else if (typeof selected === 'string') { @@ -95,12 +93,6 @@ export default { currentFallback () { return this.fallback?.[this.selectedId] }, - moveUpValid () { - return this.selectedId > 0 - }, - moveDnValid () { - return this.selectedId < this.cValue.length - 1 - }, usingFallback () { return this.modelValue == null }, @@ -123,11 +115,20 @@ export default { } }, watch: { + modelValue (value) { + if (!value) this.cValue = (this.modelValue ?? this.fallback ?? []).map(toModel) + }, selected (value) { this.$emit('subShadowSelected', this.selectedId) } }, methods: { + getNewSubshadow () { + return toModel(this.selected) + }, + onSelectChange (id) { + this.selectedId = id + }, getSubshadowLabel (shadow, index) { if (typeof shadow === 'object') { return shadow?.name ?? this.$t('settings.style.shadows.shadow_id', { value: index }) @@ -141,28 +142,6 @@ export default { this.cValue[this.selectedId].spread = 0 } this.$emit('update:modelValue', this.cValue) - }, 100), - add () { - this.cValue.push(toModel(this.selected)) - this.selectedId = Math.max(this.cValue.length - 1, 0) - this.$emit('update:modelValue', this.cValue) - }, - del () { - this.cValue.splice(this.selectedId, 1) - this.selectedId = this.cValue.length === 0 ? undefined : Math.max(this.selectedId - 1, 0) - this.$emit('update:modelValue', this.cValue) - }, - moveUp () { - const movable = this.cValue.splice(this.selectedId, 1)[0] - this.cValue.splice(this.selectedId - 1, 0, movable) - this.selectedId -= 1 - this.$emit('update:modelValue', this.cValue) - }, - moveDn () { - const movable = this.cValue.splice(this.selectedId, 1)[0] - this.cValue.splice(this.selectedId + 1, 0, movable) - this.selectedId += 1 - this.$emit('update:modelValue', this.cValue) - } + }, 100) } } diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue index 669de36e1..6a5cd1861 100644 --- a/src/components/shadow_control/shadow_control.vue +++ b/src/components/shadow_control/shadow_control.vue @@ -28,53 +28,14 @@ {{ getSubshadowLabel(shadow, index) }} -
    - - - - -
    +
    - + /> diff --git a/src/services/export_import/export_import.js b/src/services/export_import/export_import.js index e1ac6a425..b8f84351b 100644 --- a/src/services/export_import/export_import.js +++ b/src/services/export_import/export_import.js @@ -2,15 +2,22 @@ import utf8 from 'utf8' export const newExporter = ({ filename = 'data', + mime = 'application/json', + extension = '.json', getExportedObject }) => ({ exportData () { - const stringified = utf8.encode(JSON.stringify(getExportedObject(), null, 2)) // Pretty-print and indent with 2 spaces + let stringified + if (mime === 'application/json') { + stringified = utf8.encode(JSON.stringify(getExportedObject(), null, 2)) // Pretty-print and indent with 2 spaces + } else { + stringified = utf8.encode(getExportedObject()) // Pretty-print and indent with 2 spaces + } // Create an invisible link with a data url and simulate a click const e = document.createElement('a') - e.setAttribute('download', `${filename}.json`) - e.setAttribute('href', 'data:application/json;base64,' + window.btoa(stringified)) + e.setAttribute('download', `${filename}.${extension}`) + e.setAttribute('href', `data:${mime};base64, ${window.btoa(stringified)}`) e.style.display = 'none' document.body.appendChild(e) From 756ea63b6709e3daf35d738b0a1f3a5e9439c4d9 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 6 Oct 2024 20:21:13 +0300 Subject: [PATCH 0155/1277] variables stuff seem to be at least somewhat working --- .../palette_editor/palette_editor.vue | 2 +- .../tabs/style_tab/style_tab.js | 36 +++++++++++++++---- .../tabs/style_tab/style_tab.scss | 8 ++--- .../tabs/style_tab/style_tab.vue | 9 +++-- .../shadow_control/shadow_control.js | 4 +++ src/i18n/en.json | 6 +++- 6 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/components/palette_editor/palette_editor.vue b/src/components/palette_editor/palette_editor.vue index cebe73ee1..16148262c 100644 --- a/src/components/palette_editor/palette_editor.vue +++ b/src/components/palette_editor/palette_editor.vue @@ -107,7 +107,7 @@ const updatePalette = (paletteKey, value) => { grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(3, 1fr) auto; grid-gap: 0.5em; - align-items: space-between; + align-items: baseline; .palette-import-button { grid-column: 1 / span 2; diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.js b/src/components/settings_modal/tabs/style_tab/style_tab.js index 4e9a39060..c75e6b6b4 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.js +++ b/src/components/settings_modal/tabs/style_tab/style_tab.js @@ -17,7 +17,7 @@ import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue' import { init } from 'src/services/theme_data/theme_data_3.service.js' import { getCssRules } from 'src/services/theme_data/css_utils.js' import { serialize } from 'src/services/theme_data/iss_serializer.js' -// import { deserialize } from 'src/services/theme_data/iss_deserializer.js' +import { parseShadow /* , deserialize */ } from 'src/services/theme_data/iss_deserializer.js' import { // rgb2hex, hex2rgb, @@ -259,7 +259,7 @@ export default { } }) - const getEditedElement = (component, directive) => computed({ + const getEditedElement = (component, directive, postProcess = x => x) => computed({ get () { let usedRule const fallback = editorFriendlyFallbackStructure.value @@ -271,7 +271,11 @@ export default { usedRule = get(fallback, path) } - return usedRule + if (directive === 'shadow') { + console.log('EDITED', usedRule) + console.log('PP', postProcess(usedRule)) + } + return postProcess(usedRule) }, set (value) { set(allEditedRules, getPath(component, directive), value) @@ -316,9 +320,22 @@ export default { } } + const normalizeShadows = (shadows) => { + console.log('NORMALIZE') + return shadows?.map(shadow => { + if (typeof shadow === 'object') { + return shadow + } + if (typeof shadow === 'string') { + return parseShadow(shadow) + } + return null + }) + } + // Shadow is partially edited outside the ShadowControl // for better space utilization - const editedShadow = getEditedElement(null, 'shadow') + const editedShadow = getEditedElement(null, 'shadow', normalizeShadows) const editedSubShadowId = ref(null) const editedSubShadow = computed(() => { if (editedShadow.value == null || editedSubShadowId.value == null) return null @@ -511,8 +528,15 @@ export default { const selectedVirtualDirectiveParsed = computed({ get () { switch (selectedVirtualDirective.value.valType) { - case 'shadow': - return {} + case 'shadow': { + const directiveValue = selectedVirtualDirective.value.value + if (Array.isArray(directiveValue)) { + return normalizeShadows(directiveValue) + } else { + const splitShadow = directiveValue.split(/,/g).map(x => x.trim()) + return normalizeShadows(splitShadow) + } + } default: return null } diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.scss b/src/components/settings_modal/tabs/style_tab/style_tab.scss index 0f8540f9a..0384f7a48 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.scss +++ b/src/components/settings_modal/tabs/style_tab/style_tab.scss @@ -93,7 +93,7 @@ "label editor" "selector editor" "movement editor"; - grid-template-columns: auto 1fr; + grid-template-columns: 10em 1fr; grid-template-rows: auto 1fr auto; grid-gap: 0.5em; @@ -124,9 +124,9 @@ grid-template-rows: auto auto 1fr; grid-gap: 0.5em; grid-template-areas: - "component component variant" - "state state state" - "preview settings settings"; + "component component variant" + "state state state" + "preview settings settings"; .component-selector { grid-area: component; diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.vue b/src/components/settings_modal/tabs/style_tab/style_tab.vue index 8aaa67130..430b7c614 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.vue +++ b/src/components/settings_modal/tabs/style_tab/style_tab.vue @@ -245,6 +245,7 @@ > {{ $t('settings.style.themes3.editor.include_in_rule') }} + {{ editedShadow }}
    + - {{ selectedVirtualDirective.valType }}
    diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js index fc227b5be..3fe1aa299 100644 --- a/src/components/shadow_control/shadow_control.js +++ b/src/components/shadow_control/shadow_control.js @@ -45,6 +45,7 @@ export default { ], emits: ['update:modelValue', 'subShadowSelected'], data () { + console.log('MODEL VALUE', this.modelValue, this.fallback) return { selectedId: 0, // TODO there are some bugs regarding display of array (it's not getting updated when deleting for some reason) @@ -60,6 +61,9 @@ export default { Popover, ComponentPreview }, + beforeUpdate () { + this.cValue = (this.modelValue ?? this.fallback ?? []).map(toModel) + }, computed: { selectedType: { get () { diff --git a/src/i18n/en.json b/src/i18n/en.json index 3694a3908..bad4ddb03 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -774,6 +774,9 @@ "extra3": "Extra 3", "v2_unsupported": "Older v2 themes don't support palettes. Switch to v3 theme to make use of palettes" }, + "variables": { + "label": "Variables" + }, "editor": { "title": "Style", "new_style": "New", @@ -800,7 +803,8 @@ "no-auto": "Disabled" }, "component_tab": "Components style", - "palette_tab": "Color presets" + "palette_tab": "Color presets", + "variables_tab": "Variables (Advanced)" }, "hacks": { "underlay_overrides": "Change underlay", From cfe52185f74684ebb599754ebb5b888302498231 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 7 Oct 2024 00:57:54 +0300 Subject: [PATCH 0156/1277] neat-looking variables tab (sans shadow editor) --- .../tabs/style_tab/style_tab.scss | 16 ++++ .../tabs/style_tab/style_tab.vue | 90 ++++++++++++------- src/i18n/en.json | 4 +- 3 files changed, 75 insertions(+), 35 deletions(-) diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.scss b/src/components/settings_modal/tabs/style_tab/style_tab.scss index 0384f7a48..69349f785 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.scss +++ b/src/components/settings_modal/tabs/style_tab/style_tab.scss @@ -109,6 +109,7 @@ font-weight: bold; grid-area: label; margin: 0; + align-self: baseline; } &-movement { @@ -118,6 +119,21 @@ } } + .variables-editor { + .variable-selector { + display: grid; + grid-template-columns: auto 1fr auto 10em; + grid-template-rows: subgrid; + align-items: baseline; + grid-gap: 0 0.5em; + } + + .list-edit-area { + display: grid; + grid-template-rows: subgrid; + } + } + .component-editor { display: grid; grid-template-columns: 6fr 3fr 4fr; diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.vue b/src/components/settings_modal/tabs/style_tab/style_tab.vue index 430b7c614..9b5a4f9e6 100644 --- a/src/components/settings_modal/tabs/style_tab/style_tab.vue +++ b/src/components/settings_modal/tabs/style_tab/style_tab.vue @@ -259,7 +259,7 @@
    diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js index 3fe1aa299..8eab5c917 100644 --- a/src/components/shadow_control/shadow_control.js +++ b/src/components/shadow_control/shadow_control.js @@ -41,7 +41,12 @@ const toModel = (input) => { export default { props: [ - 'modelValue', 'fallback', 'separateInset', 'noPreview', 'disabled' + 'modelValue', + 'fallback', + 'separateInset', + 'noPreview', + 'disabled', + 'compact' ], emits: ['update:modelValue', 'subShadowSelected'], data () { diff --git a/src/components/shadow_control/shadow_control.scss b/src/components/shadow_control/shadow_control.scss index de4159c11..b0cf70148 100644 --- a/src/components/shadow_control/shadow_control.scss +++ b/src/components/shadow_control/shadow_control.scss @@ -1,12 +1,32 @@ -.settings-modal .settings-modal-panel .shadow-control { - display: flex; - flex-wrap: wrap; +.ShadowControl { + display: grid; + grid-template-columns: 10em 1fr 1fr; + grid-template-rows: 1fr; + grid-template-areas: "selector preview tweak"; + grid-gap: 0.5em; justify-content: stretch; - grid-gap: 0.25em; margin-bottom: 1em; width: 100%; + &.-compact { + grid-template-columns: 1fr; + grid-template-rows: 10em auto auto; + grid-template-areas: + "selector" + "preview" + "tweak"; + + &.-no-preview { + grid-template-columns: 1fr; + grid-template-rows: 10em 1fr; + grid-template-areas: + "selector" + "tweak"; + } + } + .shadow-switcher { + grid-area: selector; order: 1; flex: 1 0 6em; min-width: 6em; @@ -20,6 +40,7 @@ } .shadow-tweak { + grid-area: tweak; order: 3; flex: 2 0 10em; min-width: 10em; @@ -65,6 +86,10 @@ } &.-no-preview { + grid-template-columns: 10em 1fr; + grid-template-rows: 1fr; + grid-template-areas: "selector tweak"; + .shadow-tweak { order: 0; flex: 2 0 8em; @@ -87,8 +112,7 @@ } .shadow-preview { - order: 2; - flex: 3 3 15em; + grid-area: preview; min-width: 10em; margin-left: 0.125em; align-self: start; diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue index 29adfff4a..4f0906c70 100644 --- a/src/components/shadow_control/shadow_control.vue +++ b/src/components/shadow_control/shadow_control.vue @@ -1,7 +1,7 @@