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:
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