server-side storage for flags
This commit is contained in:
parent
5b7c653874
commit
dbfca224d8
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
.panel-body > p {
|
||||
width: calc(100% - 10em)
|
||||
|
||||
@media all and (max-height: 600px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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-unstyled -link tall-status-hider"
|
||||
@click.prevent="toggleShowMore"
|
||||
>
|
||||
{{ $t("general.show_more") }}
|
||||
</button>
|
||||
{{ ' ' }}
|
||||
<button
|
||||
class="button-unstyled -link tall-status-hider"
|
||||
@click.prevent="toggleShowMore"
|
||||
>
|
||||
class="button-default"
|
||||
@click.prevent="neverShowAgain"
|
||||
>
|
||||
{{ $t("general.never_show_again") }}
|
||||
</button>
|
||||
</p>
|
||||
<img class="pleroma-tan" :src="pleromaTanVariant"/>
|
||||
<button
|
||||
class="button-default"
|
||||
@click.prevent="toggleShowMore"
|
||||
v-if="!showingMore"
|
||||
>
|
||||
{{ $t("general.show_more") }}
|
||||
</button>
|
||||
<button
|
||||
class="button-default"
|
||||
@click.prevent="dismiss"
|
||||
>
|
||||
{{ $t("general.dismiss") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
@ -17,6 +17,7 @@ const saveImmedeatelyActions = [
|
||||
'markNotificationsAsSeen',
|
||||
'clearCurrentUser',
|
||||
'setCurrentUser',
|
||||
'setServerSideStorage',
|
||||
'setHighlight',
|
||||
'setOption',
|
||||
'setClientData',
|
||||
|
@ -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,
|
||||
|
158
src/modules/serverSideStorage.js
Normal file
158
src/modules/serverSideStorage.js
Normal file
@ -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
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user