server-side storage for flags

This commit is contained in:
Henry Jameson 2022-08-04 01:56:52 +03:00
parent 5b7c653874
commit dbfca224d8
8 changed files with 326 additions and 34 deletions

View File

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

View File

@ -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;
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;
}
}
}

View File

@ -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">
<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>
<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>

View File

@ -17,6 +17,7 @@ const saveImmedeatelyActions = [
'markNotificationsAsSeen',
'clearCurrentUser',
'setCurrentUser',
'setServerSideStorage',
'setHighlight',
'setOption',
'setClientData',

View File

@ -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,

View 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

View File

@ -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)

View File

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