logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://hacktivis.me/git/pleroma-fe.git
commit: dbfca224d812c2ba80a48852ba047bb65c4c6dd9
parent 5b7c6538745083dfd48771392ab22de557a7a344
Author: Henry Jameson <me@hjkos.com>
Date:   Thu,  4 Aug 2022 01:56:52 +0300

server-side storage for flags

Diffstat:

Msrc/components/update_notification/update_notification.js40+++++++++++++++++++++++++++++++++++++---
Msrc/components/update_notification/update_notification.scss95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/components/update_notification/update_notification.vue52++++++++++++++++++++++++++++++++++++----------------
Msrc/lib/persisted_state.js1+
Msrc/main.js3+++
Asrc/modules/serverSideStorage.js158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/users.js2++
Msrc/services/entity_normalizer/entity_normalizer.service.js3+++
8 files changed, 323 insertions(+), 31 deletions(-)

diff --git a/src/components/update_notification/update_notification.js b/src/components/update_notification/update_notification.js @@ -10,15 +10,49 @@ library.add( faTimes ) -const SettingsModal = { +const CURRENT_UPDATE_COUNTER = 1 + +const UpdateNotification = { data () { return { - pleromaTanVariant: Math.random() > 0.5 ? pleromaTan : pleromaTanFox + pleromaTanVariant: Math.random() > 0.5 ? pleromaTan : pleromaTanFox, + showingMore: true, + contentHeight: 0 } }, components: { Modal + }, + computed: { + pleromaTanStyles () { + return { + 'shape-outside': 'url(' + this.pleromaTanVariant + ')' + } + }, + shouldShow () { + return this.$store.state.serverSideStorage.flagStorage.updateCounter < CURRENT_UPDATE_COUNTER && + !this.$store.state.serverSideStorage.flagStorage.dontShowUpdateNotifs + } + }, + methods: { + toggleShow () { + this.showingMore = !this.showingMore + }, + neverShowAgain () { + this.$store.commit('setFlag', { flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER }) + this.$store.commit('setFlag', { flag: 'dontShowUpdateNotifs', value: 1 }) + this.$store.dispatch('pushServerSideStorage') + }, + dismiss () { + this.$store.commit('setFlag', { flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER }) + this.$store.dispatch('pushServerSideStorage') + } + }, + mounted () { + setTimeout(() => { + this.contentHeight = this.$refs.content.offsetHeight + }, 10) } } -export default SettingsModal +export default UpdateNotification diff --git a/src/components/update_notification/update_notification.scss b/src/components/update_notification/update_notification.scss @@ -1,5 +1,13 @@ @import 'src/_variables.scss'; +.UpdateNotification { + overflow: hidden; +} .UpdateNotificationModal { + --__top-fringe: 18em; // how much pleroma-tan should stick her head above + --__bottom-fringe: 80em; // just reserving as much as we can, number is mostly irrelevant + --__right-fringe: 8em; + + font-size: 15px; /* Explanation: * Modal is positioned vertically centered. * 100vh - 100% = Distance between modal's top+bottom boundaries and screen @@ -8,27 +16,90 @@ * bottom of the screen * - 50px - leaving tiny amount of space so that titlebar + tiny amount of modal is visible */ - transform: translateY(calc(((100vh - 100%) / 2 + 5%))); - max-width: 90vh; - width: 30em; position: relative; + transition: transform; + transition-timing-function: ease-in-out; + transition-duration: 500ms; + + .text { + width: 40em; + padding-left: 1em; + } @media all and (max-width: 800px) { /* For mobile, the modal takes 100% of the available screen. This ensures the minimized modal is always 50px above the browser bottom bar regardless of whether or not it is visible. */ - transform: translateY(calc(100% - 50px)); + width: 100vw; + } + + @media all and (max-height: 600px) { + display: none; } - .panel-body > p { - width: calc(100% - 10em) + + .content { + overflow: hidden; + margin-top: calc(-1 * var(--__top-fringe)); + margin-bottom: calc(-1 * var(--__bottom-fringe)); + margin-right: calc(-1 * var(--__right-fringe)); + } + + .panel-body { + border-width: 0 0 1px 0; + border-style: solid; + border-color: var(--border, $fallback--border); + } + + .panel-footer { + z-index: 22; + position: relative; + border-width: 0; + grid-template-columns: auto; } .pleroma-tan { - max-width: 20em; - max-height: 40em; - position: absolute; - right: -5em; - top: -10em; - z-index: 10; + object-fit: cover; + object-position: top; + transition: position, left, right, top, bottom, max-width, max-height; + transition-timing-function: ease-in-out; + transition-duration: 500ms; + width: 25em; + float: right; + z-index: 20; + position: relative; + shape-margin: 0.5em; + } + + .spacer-top { + min-height: var(--__top-fringe); + } + + .spacer-bottom { + min-height: var(--__bottom-fringe); + } + + .extra-info { + transition: max-height, padding, height; + transition-timing-function: ease-in-out; + transition-duration: 500ms; + max-height: auto; + height: auto; + } + + &.-peek { + transform: translateY(calc(((100vh - 100%) / 2))); + + .pleroma-tan { + float: right; + z-index: 10; + shape-image-threshold: 0.7; + } + + .extra-info { + max-height: 0; + height: 0; + display: none; + overflow: hidden; + } } } diff --git a/src/components/update_notification/update_notification.vue b/src/components/update_notification/update_notification.vue @@ -1,36 +1,56 @@ <template> <Modal - :is-open="true" + :is-open="shouldShow" class="UpdateNotification" - :class="{ peek: modalPeeked }" :no-background="true" > - <div class="UpdateNotificationModal panel"> + <div + class="UpdateNotificationModal panel" + :class="{ '-peek': !showingMore }" + > <div class="panel-heading"> <span class="title"> {{ $t('update.big_update_title') }} </span> </div> <div class="panel-body"> - <p> - {{ $t('update.big_update_content') }} - </p> - <p> + <div class="content" ref="content"> + <img class="pleroma-tan" :src="pleromaTanVariant" :style="pleromaTanStyles"/> + <div class="spacer-top"/> + <div class="text"> + <p> + {{ $t('update.big_update_content') }} + </p> + <p class="extra-info"> + {{ $t('update.update_bugs') }} + </p> + <p class="extra-info"> + {{ $t('update.update_changelog') }} + </p> + </div> + <div class="spacer-bottom"/> + </div> + </div> + <div class="panel-footer"> + <button + class="button-default" + @click.prevent="neverShowAgain" + > + {{ $t("general.never_show_again") }} + </button> <button - class="button-unstyled -link tall-status-hider" + class="button-default" @click.prevent="toggleShowMore" - > + v-if="!showingMore" + > {{ $t("general.show_more") }} </button> - {{ ' ' }} <button - class="button-unstyled -link tall-status-hider" - @click.prevent="toggleShowMore" - > - {{ $t("general.never_show_again") }} + class="button-default" + @click.prevent="dismiss" + > + {{ $t("general.dismiss") }} </button> - </p> - <img class="pleroma-tan" :src="pleromaTanVariant"/> </div> </div> </Modal> diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js @@ -17,6 +17,7 @@ const saveImmedeatelyActions = [ 'markNotificationsAsSeen', 'clearCurrentUser', 'setCurrentUser', + 'setServerSideStorage', 'setHighlight', 'setOption', 'setClientData', diff --git a/src/main.js b/src/main.js @@ -10,6 +10,7 @@ import usersModule from './modules/users.js' 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' @@ -42,6 +43,7 @@ messages.setLanguage(i18n, currentLocale) const persistedStateOptions = { paths: [ + 'serverSideStorage.cache', 'config', 'users.lastLoginName', 'oauth' @@ -73,6 +75,7 @@ const persistedStateOptions = { api: apiModule, config: configModule, serverSideConfig: serverSideConfigModule, + serverSideStorage: serverSideStorageModule, shout: shoutModule, oauth: oauthModule, authFlow: authFlowModule, diff --git a/src/modules/serverSideStorage.js b/src/modules/serverSideStorage.js @@ -0,0 +1,158 @@ +import { toRaw } from 'vue' + +const VERSION = 1 +const NEW_USER_DATE = new Date('04-08-2022') // date of writing this, basically + +const COMMAND_TRIM_FLAGS = 1000 +const COMMAND_TRIM_FLAGS_AND_RESET = 1001 + +const defaultState = { + // last timestamp + timestamp: 0, + // need to update server + dirty: false, + // storage of flags - stuff that can only be set and incremented + flagStorage: { + updateCounter: 0, // Counter for most recent update notification seen + // TODO move to prefsStorage when that becomes a thing since only way + // this can be reset is by complete reset of all flags + dontShowUpdateNotifs: 0, // if user chose to not show update notifications ever again + reset: 0 // special flag that can be used to force-reset all flags, debug purposes only + // special reset codes: + // 1000: trim keys to those known by currently running FE + // 1001: same as above + reset everything to 0 + }, + // raw data + raw: null, + // local cache + cache: null +} + +const newUserFlags = { + ...defaultState.flagStorage, + updateCounter: 1 // new users don't need to see update notification +} + +const serverSideStorage = { + state: { + ...defaultState + }, + mutations: { + setServerSideStorage (state, userData) { + const live = userData.storage + const userNew = userData.created_at > NEW_USER_DATE + const flagsTemplate = userNew ? newUserFlags : defaultState.defaultState + state.raw = live + console.log(1111, live._timestamp) + let recent = null + const cache = state.cache || {} + const cacheValid = cache._timestamp > 0 && cache._version > 0 + const liveValid = live._timestamp > 0 && live._version > 0 + if (!liveValid) { + state.dirty = true + console.debug('Nothing valid stored on server, assuming cache to be source of truth') + if (cacheValid) { + recent = cache + } else { + console.debug(`Local cache is empty, initializing for ${userNew ? 'new' : 'existing'} user`) + + recent = { + _timestamp: Date.now(), + _version: VERSION, + flagStorage: { ...flagsTemplate } + } + } + } else if (!cacheValid) { + console.debug('Valid storage on server found, no local cache found, using live as source of truth') + recent = live + } else { + console.debug('Both sources have valid data, figuring things out...') + console.log(live._timestamp, cache._timestamp) + if (live._timestamp === cache._timestamp && live._version === cache._version) { + console.debug('Same version/timestamp on both source, source of truth irrelevant') + recent = cache + } else { + state.dirty = true + console.debug('Different timestamp, figuring out which one is more recent') + let stale + if (live._timestamp < cache._timestamp) { + recent = cache + stale = live + } else { + recent = live + stale = cache + } + + // Merge the flags + console.debug('Merging the flags...') + recent.flagStorage = recent.flagStorage || { ...flagsTemplate } + stale.flagStorage = stale.flagStorage || { ...flagsTemplate } + const allFlags = Array.from(new Set([ + ...Object.keys(toRaw(recent.flagStorage)), + ...Object.keys(toRaw(stale.flagStorage)) + ])) + + const totalFlags = Object.fromEntries(allFlags.map(flag => { + const recentFlag = recent.flagStorage[flag] + const staleFlag = stale.flagStorage[flag] + // use flag that is of higher value + return [flag, recentFlag > staleFlag ? recentFlag : staleFlag] + })) + + console.debug('AAA', totalFlags) + // flag reset functionality + if (totalFlags.reset >= COMMAND_TRIM_FLAGS && totalFlags.reset <= COMMAND_TRIM_FLAGS_AND_RESET) { + console.debug('Received command to trim the flags') + const knownKeys = new Set(Object.keys(defaultState.flagStorage)) + allFlags.forEach(flag => { + if (!knownKeys.has(flag)) { + delete totalFlags[flag] + } + }) + if (totalFlags.reset === COMMAND_TRIM_FLAGS_AND_RESET) { + // 1001 - and reset everything to 0 + console.debug('Received command to reset the flags') + allFlags.forEach(flag => { totalFlags[flag] = 0 }) + } else { + // reset the reset 0 + totalFlags.reset = 0 + } + } else if (totalFlags.reset > 0 && totalFlags.reset < 9000) { + console.debug('Received command to reset the flags') + allFlags.forEach(flag => { totalFlags[flag] = 0 }) + // for good luck + totalFlags.reset = 0 + } + console.log('AAAA', totalFlags) + state.cache.flagStorage = totalFlags + } + } + state.cache = recent + state.flagStorage = state.cache.flagStorage + }, + setFlag (state, { flag, value }) { + state.flagStorage[flag] = value + state.dirty = true + } + }, + actions: { + pushServerSideStorage ({ state, rootState, commit }, { force = false } = {}) { + console.log('PUSH') + const needPush = state.dirty || force + if (!needPush) return + state.cache = { + _timestamp: Date.now(), + _version: VERSION, + flagStorage: toRaw(state.flagStorage) + } + console.log('YES') + const params = { pleroma_settings_store: { 'pleroma-fe': state.cache } } + rootState.api.backendInteractor + .updateProfile({ params }) + .then((user) => commit('setServerSideStorage', user)) + state.dirty = false + } + } +} + +export default serverSideStorage diff --git a/src/modules/users.js b/src/modules/users.js @@ -525,6 +525,7 @@ const users = { user.muteIds = [] user.domainMutes = [] commit('setCurrentUser', user) + commit('setServerSideStorage', user) commit('addNewUsers', [user]) store.dispatch('fetchEmoji') @@ -534,6 +535,7 @@ const users = { // Set our new backend interactor commit('setBackendInteractor', backendInteractorService(accessToken)) + store.dispatch('pushServerSideStorage') if (user.token) { store.dispatch('setWsToken', user.token) diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js @@ -90,6 +90,9 @@ export const parseUser = (data) => { output.bot = data.bot if (data.pleroma) { + if (data.pleroma.settings_store) { + output.storage = data.pleroma.settings_store['pleroma-fe'] + } const relationship = data.pleroma.relationship output.background_image = data.pleroma.background_image