Use SettingsItem in branding view

This commit is contained in:
Johannes Zellner
2025-04-07 17:05:57 +02:00
parent 71fc7c4ab6
commit 73a5fb1648
3 changed files with 142 additions and 57 deletions
+129 -56
View File
@@ -4,6 +4,7 @@ import { ref, onMounted } from 'vue';
import { Button, FormGroup, TextInput } from 'pankow';
import { API_ORIGIN } from '../constants.js';
import Section from '../components/Section.vue';
import SettingsItem from '../components/SettingsItem.vue';
import ImagePicker from '../components/ImagePicker.vue';
import BrandingModel from '../models/BrandingModel.js';
import DashboardModel from '../models/DashboardModel.js';
@@ -11,49 +12,95 @@ import DashboardModel from '../models/DashboardModel.js';
const brandingModel = BrandingModel.create();
const dashboardModel = DashboardModel.create();
let newBackground = null;
let newAvatar = null;
const busy = ref(false);
const name = ref('');
const footer = ref('');
const avatarUrl = ref(`${API_ORIGIN}/api/v1/cloudron/avatar?${String(Math.random()).slice(2)}`);
const backgroundUrl = ref(`${API_ORIGIN}/api/v1/cloudron/background?${String(Math.random()).slice(2)}`);
const backgroundError = ref('');
async function onSubmit() {
busy.value = true;
backgroundError.value = '';
let [error] = await brandingModel.setName(name.value);
if (error) return console.error(error);
// Cloudron name
const editingName = ref(false);
const editName = ref('');
const editNameBusy = ref(false);
[error] = await brandingModel.setFooter(footer.value);
if (error) return console.error(error);
if (newBackground) {
const [error] = await brandingModel.setBackground(newBackground);
if (error) {
backgroundError.value = error.body ? error.body.message : 'Internal error';
busy.value = false;
return;
}
}
if (newAvatar) {
const [error] = await brandingModel.setAvatar(newAvatar);
if (error) return console.error(error);
}
busy.value = false;
function onChangeName(currentName) {
editNameBusy.value = false;
editingName.value = true;
editName.value = currentName;
}
async function onChangeNameSubmit() {
editNameBusy.value = true;
const [error] = await brandingModel.setName(editName.value);
if (error) return console.error(error);
name.value = editName.value;
editingName.value = false;
editNameBusy.value = false;
editName.value = '';
}
// Avatar
const avatarBusy = ref(false);
const avatarNew = ref(null);
function onAvatarChanged(file) {
newAvatar = file;
avatarNew.value = file;
}
async function onAvatarSubmit() {
if (!avatarNew.value) return;
avatarBusy.value = true;
const [error] = await brandingModel.setAvatar(avatarNew.value);
if (error) return console.error(error);
avatarNew.value = null;
avatarBusy.value = false;
}
// Background
const backgroundBusy = ref(false);
const backgroundNew = ref(null);
function onBackgroundChanged(file) {
newBackground = file;
backgroundNew.value = file;
}
async function onBackgroundSubmit() {
if (!backgroundNew.value) return;
backgroundBusy.value = true;
const [error] = await brandingModel.setBackground(backgroundNew.value);
if (error) return console.error(error);
backgroundNew.value = null;
backgroundBusy.value = false;
}
// Footer
const editingFooter = ref(false);
const editFooter = ref('');
const editFooterBusy = ref(false);
function onChangeFooter(currentFooter) {
editFooterBusy.value = false;
editingFooter.value = true;
editFooter.value = currentFooter;
}
async function onChangeFooterSubmit() {
editFooterBusy.value = true;
const [error] = await brandingModel.setFooter(editFooter.value);
if (error) return console.error(error);
footer.value = editFooter.value;
editingFooter.value = false;
editFooterBusy.value = false;
editFooter.value = '';
}
onMounted(async () => {
@@ -71,35 +118,61 @@ onMounted(async () => {
<template>
<div class="content">
<Section :title="$t('branding.title')">
<form @submit.prevent="onSubmit()">
<fieldset :disabled="busy">
<input type="submit" style="display: none;" />
<SettingsItem>
<FormGroup>
<label>{{ $t('branding.cloudronName') }}</label>
<div>{{ name }}</div>
</FormGroup>
<Transition name="slide-up" mode="out-in">
<div v-if="editingName" style="display: flex; position: relative; align-items: center; gap: 6px">
<TextInput v-model="editName" @keydown.enter="onChangeNameSubmit()" :disabled="editNameBusy"/>
<Button tool @click="onChangeNameSubmit()" :disabled="editNameBusy" :loading="editNameBusy">{{ $t('main.dialog.save') }}</Button>
<Button tool plain secondary @click="editingName = false" :disabled="editNameBusy">{{ $t('main.dialog.cancel') }}</Button>
</div>
<div v-else style="display: flex; position: relative; align-items: center">
<Button tool plain @click="onChangeName(name)">{{ $t('main.dialog.edit') }}</Button>
</div>
</Transition>
</SettingsItem>
<FormGroup>
<label for="nameInput">{{ $t('branding.cloudronName') }}</label>
<TextInput id="nameInput" v-model="name" minlength="1" maxlength="64" required/>
</FormGroup>
<SettingsItem>
<FormGroup>
<label>{{ $t('branding.logo') }}</label>
<ImagePicker :src="avatarUrl" @changed="onAvatarChanged" :size="512" display-height="60px"/>
</FormGroup>
<div v-show="avatarNew !== null" style="display: flex; position: relative; align-items: center; gap: 6px">
<Button tool @click="onAvatarSubmit()" :disabled="!avatarNew || avatarBusy" :loading="avatarBusy">{{ $t('main.dialog.save') }}</Button>
<Button tool plain secondary @click="avatarNew = null" :disabled="avatarBusy">{{ $t('main.dialog.cancel') }}</Button>
</div>
</SettingsItem>
<FormGroup>
<label>{{ $t('branding.logo') }}</label>
<ImagePicker :src="avatarUrl" @changed="onAvatarChanged" :size="512" display-height="100px"/>
</FormGroup>
<SettingsItem>
<FormGroup>
<label>{{ $t('branding.backgroundImage') }}</label>
<ImagePicker :src="backgroundUrl" @changed="onBackgroundChanged" fallback-src="/img/background-image-placeholder.svg" display-height="200px" :max-size="1280"/>
</FormGroup>
<div v-show="backgroundNew !== null" style="display: flex; position: relative; align-items: center gap: 6px">
<Button tool @click="onBackgroundSubmit()" :disabled="!backgroundNew || backgroundBusy" :loading="backgroundBusy">{{ $t('main.dialog.save') }}</Button>
<Button tool plain secondary @click="backgroundNew = null" :disabled="backgroundBusy">{{ $t('main.dialog.cancel') }}</Button>
</div>
</SettingsItem>
<FormGroup>
<label class="control-label">{{ $t('branding.backgroundImage') }}</label>
<ImagePicker :src="backgroundUrl" @changed="onBackgroundChanged" fallback-src="/img/background-image-placeholder.svg" display-height="200px" :max-size="1280"/>
<div class="has-error" v-if="backgroundError">{{ backgroundError }}</div>
</FormGroup>
<FormGroup>
<label for="footerInput">{{ $t('branding.footer.title') }} </label>
<p>{{ $t('branding.footer.description') }} <sup><a href="https://docs.cloudron.io/branding/#footer" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></p>
<textarea id="footerInput" v-model="footer" :disabled="busy" style="display: block; width: 100%" rows="1"></textarea>
</FormGroup>
<Button @click="onSubmit()" :loading="busy" :disabled="busy">{{ $t('main.dialog.save') }}</Button>
</fieldset>
</form>
<SettingsItem>
<FormGroup>
<label>{{ $t('branding.footer.title') }}</label>
<div>{{ $t('branding.footer.description') }} <sup><a href="https://docs.cloudron.io/branding/#footer" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></div>
</FormGroup>
<Transition name="slide-up" mode="out-in">
<div v-if="editingFooter" style="display: flex; position: relative; align-items: center; gap: 6px">
<TextInput v-model="editFooter" @keydown.enter="onChangeFooterSubmit()" :disabled="editFooterBusy"/>
<Button tool @click="onChangeFooterSubmit()" :disabled="editFooterBusy" :loading="editFooterBusy">{{ $t('main.dialog.save') }}</Button>
<Button tool plain secondary @click="editingFooter = false" :disabled="editFooterBusy">{{ $t('main.dialog.cancel') }}</Button>
</div>
<div v-else style="display: flex; position: relative; align-items: center">
<Button tool plain @click="onChangeFooter(footer)">{{ $t('main.dialog.edit') }}</Button>
</div>
</Transition>
</SettingsItem>
</Section>
</div>
</template>