server-side storage for flags
This commit is contained in:
parent
5b7c653874
commit
dbfca224d8
@ -10,15 +10,49 @@ library.add(
|
|||||||
faTimes
|
faTimes
|
||||||
)
|
)
|
||||||
|
|
||||||
const SettingsModal = {
|
const CURRENT_UPDATE_COUNTER = 1
|
||||||
|
|
||||||
|
const UpdateNotification = {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
pleromaTanVariant: Math.random() > 0.5 ? pleromaTan : pleromaTanFox
|
pleromaTanVariant: Math.random() > 0.5 ? pleromaTan : pleromaTanFox,
|
||||||
|
showingMore: true,
|
||||||
|
contentHeight: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Modal
|
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';
|
@import 'src/_variables.scss';
|
||||||
|
.UpdateNotification {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
.UpdateNotificationModal {
|
.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:
|
/* Explanation:
|
||||||
* Modal is positioned vertically centered.
|
* Modal is positioned vertically centered.
|
||||||
* 100vh - 100% = Distance between modal's top+bottom boundaries and screen
|
* 100vh - 100% = Distance between modal's top+bottom boundaries and screen
|
||||||
@ -8,27 +16,90 @@
|
|||||||
* bottom of the screen
|
* bottom of the screen
|
||||||
* - 50px - leaving tiny amount of space so that titlebar + tiny amount of modal is visible
|
* - 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;
|
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) {
|
@media all and (max-width: 800px) {
|
||||||
/* For mobile, the modal takes 100% of the available screen.
|
/* 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.
|
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 {
|
.pleroma-tan {
|
||||||
max-width: 20em;
|
object-fit: cover;
|
||||||
max-height: 40em;
|
object-position: top;
|
||||||
position: absolute;
|
transition: position, left, right, top, bottom, max-width, max-height;
|
||||||
right: -5em;
|
transition-timing-function: ease-in-out;
|
||||||
top: -10em;
|
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;
|
z-index: 10;
|
||||||
|
shape-image-threshold: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-info {
|
||||||
|
max-height: 0;
|
||||||
|
height: 0;
|
||||||
|
display: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,56 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal
|
<Modal
|
||||||
:is-open="true"
|
:is-open="shouldShow"
|
||||||
class="UpdateNotification"
|
class="UpdateNotification"
|
||||||
:class="{ peek: modalPeeked }"
|
|
||||||
:no-background="true"
|
:no-background="true"
|
||||||
>
|
>
|
||||||
<div class="UpdateNotificationModal panel">
|
<div
|
||||||
|
class="UpdateNotificationModal panel"
|
||||||
|
:class="{ '-peek': !showingMore }"
|
||||||
|
>
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<span class="title">
|
<span class="title">
|
||||||
{{ $t('update.big_update_title') }}
|
{{ $t('update.big_update_title') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
<div class="content" ref="content">
|
||||||
|
<img class="pleroma-tan" :src="pleromaTanVariant" :style="pleromaTanStyles"/>
|
||||||
|
<div class="spacer-top"/>
|
||||||
|
<div class="text">
|
||||||
<p>
|
<p>
|
||||||
{{ $t('update.big_update_content') }}
|
{{ $t('update.big_update_content') }}
|
||||||
</p>
|
</p>
|
||||||
<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
|
<button
|
||||||
class="button-unstyled -link tall-status-hider"
|
class="button-default"
|
||||||
@click.prevent="toggleShowMore"
|
@click.prevent="neverShowAgain"
|
||||||
>
|
|
||||||
{{ $t("general.show_more") }}
|
|
||||||
</button>
|
|
||||||
{{ ' ' }}
|
|
||||||
<button
|
|
||||||
class="button-unstyled -link tall-status-hider"
|
|
||||||
@click.prevent="toggleShowMore"
|
|
||||||
>
|
>
|
||||||
{{ $t("general.never_show_again") }}
|
{{ $t("general.never_show_again") }}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
<button
|
||||||
<img class="pleroma-tan" :src="pleromaTanVariant"/>
|
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>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -17,6 +17,7 @@ const saveImmedeatelyActions = [
|
|||||||
'markNotificationsAsSeen',
|
'markNotificationsAsSeen',
|
||||||
'clearCurrentUser',
|
'clearCurrentUser',
|
||||||
'setCurrentUser',
|
'setCurrentUser',
|
||||||
|
'setServerSideStorage',
|
||||||
'setHighlight',
|
'setHighlight',
|
||||||
'setOption',
|
'setOption',
|
||||||
'setClientData',
|
'setClientData',
|
||||||
|
@ -10,6 +10,7 @@ import usersModule from './modules/users.js'
|
|||||||
import apiModule from './modules/api.js'
|
import apiModule from './modules/api.js'
|
||||||
import configModule from './modules/config.js'
|
import configModule from './modules/config.js'
|
||||||
import serverSideConfigModule from './modules/serverSideConfig.js'
|
import serverSideConfigModule from './modules/serverSideConfig.js'
|
||||||
|
import serverSideStorageModule from './modules/serverSideStorage.js'
|
||||||
import shoutModule from './modules/shout.js'
|
import shoutModule from './modules/shout.js'
|
||||||
import oauthModule from './modules/oauth.js'
|
import oauthModule from './modules/oauth.js'
|
||||||
import authFlowModule from './modules/auth_flow.js'
|
import authFlowModule from './modules/auth_flow.js'
|
||||||
@ -42,6 +43,7 @@ messages.setLanguage(i18n, currentLocale)
|
|||||||
|
|
||||||
const persistedStateOptions = {
|
const persistedStateOptions = {
|
||||||
paths: [
|
paths: [
|
||||||
|
'serverSideStorage.cache',
|
||||||
'config',
|
'config',
|
||||||
'users.lastLoginName',
|
'users.lastLoginName',
|
||||||
'oauth'
|
'oauth'
|
||||||
@ -73,6 +75,7 @@ const persistedStateOptions = {
|
|||||||
api: apiModule,
|
api: apiModule,
|
||||||
config: configModule,
|
config: configModule,
|
||||||
serverSideConfig: serverSideConfigModule,
|
serverSideConfig: serverSideConfigModule,
|
||||||
|
serverSideStorage: serverSideStorageModule,
|
||||||
shout: shoutModule,
|
shout: shoutModule,
|
||||||
oauth: oauthModule,
|
oauth: oauthModule,
|
||||||
authFlow: authFlowModule,
|
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.muteIds = []
|
||||||
user.domainMutes = []
|
user.domainMutes = []
|
||||||
commit('setCurrentUser', user)
|
commit('setCurrentUser', user)
|
||||||
|
commit('setServerSideStorage', user)
|
||||||
commit('addNewUsers', [user])
|
commit('addNewUsers', [user])
|
||||||
|
|
||||||
store.dispatch('fetchEmoji')
|
store.dispatch('fetchEmoji')
|
||||||
@ -534,6 +535,7 @@ const users = {
|
|||||||
|
|
||||||
// Set our new backend interactor
|
// Set our new backend interactor
|
||||||
commit('setBackendInteractor', backendInteractorService(accessToken))
|
commit('setBackendInteractor', backendInteractorService(accessToken))
|
||||||
|
store.dispatch('pushServerSideStorage')
|
||||||
|
|
||||||
if (user.token) {
|
if (user.token) {
|
||||||
store.dispatch('setWsToken', user.token)
|
store.dispatch('setWsToken', user.token)
|
||||||
|
@ -90,6 +90,9 @@ export const parseUser = (data) => {
|
|||||||
output.bot = data.bot
|
output.bot = data.bot
|
||||||
|
|
||||||
if (data.pleroma) {
|
if (data.pleroma) {
|
||||||
|
if (data.pleroma.settings_store) {
|
||||||
|
output.storage = data.pleroma.settings_store['pleroma-fe']
|
||||||
|
}
|
||||||
const relationship = data.pleroma.relationship
|
const relationship = data.pleroma.relationship
|
||||||
|
|
||||||
output.background_image = data.pleroma.background_image
|
output.background_image = data.pleroma.background_image
|
||||||
|
Loading…
Reference in New Issue
Block a user