diff --git a/dashboard/src/Index.vue b/dashboard/src/Index.vue index 9307fa30f..566f29c91 100644 --- a/dashboard/src/Index.vue +++ b/dashboard/src/Index.vue @@ -176,6 +176,10 @@ BrandingModel.onChange(BrandingModel.KEYS.AVATAR, (value) => { document.getElementById('favicon').href = value; }); +ProfileModel.onChange(ProfileModel.KEYS.AVATAR, (value) => { + profile.value.avatarUrl = value; +}); + onMounted(async () => { let [error, result] = await provisionModel.status(); if (error) return console.error(error); diff --git a/dashboard/src/models/ProfileModel.js b/dashboard/src/models/ProfileModel.js index 2c44f998f..01c12462f 100644 --- a/dashboard/src/models/ProfileModel.js +++ b/dashboard/src/models/ProfileModel.js @@ -2,9 +2,31 @@ import { ROLES, API_ORIGIN } from '../constants.js'; import { fetcher } from 'pankow'; +const changeHandlers = {}; +const KEYS = { + AVATAR: 'avatar', // only returns a URI with cachebusting +}; + +function notifyChange(key, value) { + const listener = changeHandlers[key] || []; + listener.forEach(h => h(value)); +} + +// key is one of KEYS +function onChange(key, handler) { + if (typeof handler !== 'function') return; + + if (!changeHandlers[key]) changeHandlers[key] = []; + + changeHandlers[key].push(handler); +} + function create() { const accessToken = localStorage.token; + // we use that currently only to generate the avatarUrl for the change handler + let profileCached = null; + return { name: 'ProfileModel', async logout() { @@ -37,6 +59,8 @@ function create() { result.body.backgroundImageUrl = result.body.hasBackgroundImage ? `${API_ORIGIN}/api/v1/profile/background_image?access_token=${accessToken}&bustcache=${Date.now()}` : ''; + profileCached = result.body; + return [null, result.body]; }, async setPassword(password, newPassword) { @@ -118,6 +142,8 @@ function create() { if (error) return error; if (result.status !== 202) return result; + notifyChange(KEYS.AVATAR, `${API_ORIGIN}/api/v1/profile/avatar/${profileCached.id}?ts=${Date.now()}`); + return null; }, async setBackgroundImage(file) { @@ -197,5 +223,7 @@ function create() { } export default { + KEYS, + onChange, create, }; diff --git a/dashboard/src/views/ProfileView.vue b/dashboard/src/views/ProfileView.vue index b57a017be..13ec4ad51 100644 --- a/dashboard/src/views/ProfileView.vue +++ b/dashboard/src/views/ProfileView.vue @@ -23,7 +23,7 @@ const profileModel = ProfileModel.create(); const cloudronModel = CloudronModel.create(); const tokensModel = TokensModel.create(); -const config = ref({}); // TODO what is this? +const config = ref({}); const user = ref({}); const inputDialog = useTemplateRef('inputDialog'); @@ -36,8 +36,6 @@ async function onSelectLanguage(lang) { const error = await profileModel.setLanguage(lang); if (error) console.error('Failed to set language', error); else window.location.reload(); - - // TODO dynamically change lange instead of reloading } async function refreshProfile() {