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 () => {