diff --git a/dashboard/src/Index.vue b/dashboard/src/Index.vue index e63ba25cc..7962649ae 100644 --- a/dashboard/src/Index.vue +++ b/dashboard/src/Index.vue @@ -186,6 +186,11 @@ onMounted(async () => { window.document.title = result.cloudronName; document.getElementById('favicon').href = `${API_ORIGIN}/api/v1/cloudron/avatar?ts=${Date.now()}`; + if (profile.value.hasBackgroundImage) { + window.document.body.style.backgroundImage = `url('${profile.value.backgroundImageUrl}')`; + window.document.body.classList.add('has-background'); + } + ready.value = true; }); diff --git a/dashboard/src/components/ImagePicker.vue b/dashboard/src/components/ImagePicker.vue index 3b7671655..6d2bf2438 100644 --- a/dashboard/src/components/ImagePicker.vue +++ b/dashboard/src/components/ImagePicker.vue @@ -4,7 +4,7 @@ import { useTemplateRef, ref } from 'vue'; const fileInput = useTemplateRef('fileInput'); -const props = defineProps(['src', 'fallbackSrc', 'size', 'maxSize', 'displayHeight']); +const props = defineProps(['src', 'fallbackSrc', 'size', 'maxSize', 'displayHeight', 'displayWidth']); const emits = defineEmits(['changed']); defineExpose({ clear(originalSrc = '') { @@ -117,7 +117,7 @@ function onError() {
- +
@@ -134,7 +134,7 @@ function onError() { .image-picker > img { display: block; - height: 320px; +/* height: 320px;*/ border-radius: 10px; } diff --git a/dashboard/src/components/Section.vue b/dashboard/src/components/Section.vue index be075bef0..86e72dee9 100644 --- a/dashboard/src/components/Section.vue +++ b/dashboard/src/components/Section.vue @@ -33,6 +33,12 @@ export default { margin-bottom: 50px; } +body.has-background .section { + background-color: rgba(255,255,255,0.2); + backdrop-filter: blur(10px); + border-radius: 10px; +} + .section-header { display: flex; flex-wrap: wrap; diff --git a/dashboard/src/models/ProfileModel.js b/dashboard/src/models/ProfileModel.js index 76e7d16b7..5dd435eac 100644 --- a/dashboard/src/models/ProfileModel.js +++ b/dashboard/src/models/ProfileModel.js @@ -35,6 +35,8 @@ function create() { result.body.isAtLeastAdmin = [ ROLES.OWNER, ROLES.ADMIN ].indexOf(result.body.role) !== -1; result.body.isAtLeastOwner = [ ROLES.OWNER ].indexOf(result.body.role) !== -1; + result.body.backgroundImageUrl = result.body.hasBackgroundImage ? `${API_ORIGIN}/api/v1/profile/background_image?access_token=${accessToken}&bustcache=${Date.now()}` : ''; + return [null, result.body]; }, async setPassword(password, newPassword) { @@ -118,6 +120,22 @@ function create() { return null; }, + async setBackgroundImage(file) { + const fd = new FormData(); + if (file) fd.append('backgroundImage', file); + + let error, result; + try { + result = await fetcher.post(`${API_ORIGIN}/api/v1/profile/background_image`, fd, { access_token: accessToken }); + } catch (e) { + error = e; + } + + if (error) return error; + if (result.status !== 202) return result; + + return null; + }, async sendPasswordReset(identifier) { let error, result; try { diff --git a/dashboard/src/style.css b/dashboard/src/style.css index bf0dfad13..577adcea5 100644 --- a/dashboard/src/style.css +++ b/dashboard/src/style.css @@ -27,8 +27,10 @@ html, body { width: 100%; padding: 0; margin: 0; - background-color: white; color: var(--pankow-text-color); + background-color: white; + background-position: center; + background-size: cover; } @media (prefers-color-scheme: dark) { diff --git a/dashboard/src/views/ProfileView.vue b/dashboard/src/views/ProfileView.vue index da02ef914..de63ce9ba 100644 --- a/dashboard/src/views/ProfileView.vue +++ b/dashboard/src/views/ProfileView.vue @@ -117,6 +117,18 @@ async function onAvatarChanged(file) { user.value.avatarUrl = u.toString(); } +async function onBackgroundChanged(file) { + await profileModel.setBackgroundImage(file); + await refreshProfile(); + + if (file) { + window.document.body.style.backgroundImage = `url('${user.value.backgroundImageUrl}')`; + window.document.body.classList.add('has-background'); + } else { + window.document.body.style.backgroundImage = 'None'; + window.document.body.classList.remove('has-background'); + } +} // Password changes async function onPasswordChange() { @@ -268,8 +280,12 @@ onMounted(async () => {
-
- +
+ +
+ +
Clear
+