Use ImagePicker component in branding page
This commit is contained in:
@@ -1,27 +1,24 @@
|
||||
<script setup>
|
||||
|
||||
import { ref, onMounted, useTemplateRef } from 'vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { Button, FormGroup, TextInput } from 'pankow';
|
||||
import { API_ORIGIN } from '../constants.js';
|
||||
import Section from '../components/Section.vue';
|
||||
import ImagePicker from '../components/ImagePicker.vue';
|
||||
import BrandingModel from '../models/BrandingModel.js';
|
||||
import DashboardModel from '../models/DashboardModel.js';
|
||||
|
||||
const brandingModel = BrandingModel.create();
|
||||
const dashboardModel = DashboardModel.create();
|
||||
|
||||
let backgroundChanged = false;
|
||||
let newBackground = null;
|
||||
let avatarChanged = false;
|
||||
let newAvatar = null;
|
||||
let newBackgroundDataUrl = null;
|
||||
let newAvatarDataUrl = 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 avatarFileInput = useTemplateRef('avatarFileInput');
|
||||
const backgroundUrl = ref(`${API_ORIGIN}/api/v1/cloudron/background?${String(Math.random()).slice(2)}`);
|
||||
const backgroundFileInput = useTemplateRef('backgroundFileInput');
|
||||
|
||||
async function onSubmit() {
|
||||
busy.value = true;
|
||||
@@ -32,115 +29,25 @@ async function onSubmit() {
|
||||
[error] = await brandingModel.setFooter(footer.value);
|
||||
if (error) return console.error(error);
|
||||
|
||||
if (backgroundChanged) {
|
||||
const [error] = await brandingModel.setBackground(newBackground);
|
||||
if (newBackgroundDataUrl) {
|
||||
const [error] = await brandingModel.setBackground(newBackgroundDataUrl);
|
||||
if (error) return console.error(error);
|
||||
}
|
||||
|
||||
if (avatarChanged) {
|
||||
const [error] = await brandingModel.setAvatar(newAvatar);
|
||||
if (newAvatarDataUrl) {
|
||||
const [error] = await brandingModel.setAvatar(newAvatarDataUrl);
|
||||
if (error) return console.error(error);
|
||||
}
|
||||
|
||||
backgroundChanged = false;
|
||||
avatarChanged = false;
|
||||
|
||||
busy.value = false;
|
||||
}
|
||||
|
||||
function onChangeAvatar() {
|
||||
avatarFileInput.value.click();
|
||||
function onAvatarChanged(dataUrl) {
|
||||
newAvatarDataUrl = dataUrl;
|
||||
}
|
||||
|
||||
function onAvatarChanged(event) {
|
||||
const fr = new FileReader();
|
||||
fr.onload = function () {
|
||||
const image = new Image();
|
||||
image.onload = function () {
|
||||
const size = 512;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
|
||||
const imageDimensionRatio = image.width / image.height;
|
||||
const canvasDimensionRatio = canvas.width / canvas.height;
|
||||
let renderableHeight, renderableWidth, xStart, yStart;
|
||||
|
||||
if (imageDimensionRatio > canvasDimensionRatio) {
|
||||
renderableHeight = canvas.height;
|
||||
renderableWidth = image.width * (renderableHeight / image.height);
|
||||
xStart = (canvas.width - renderableWidth) / 2;
|
||||
yStart = 0;
|
||||
} else if (imageDimensionRatio < canvasDimensionRatio) {
|
||||
renderableWidth = canvas.width;
|
||||
renderableHeight = image.height * (renderableWidth / image.width);
|
||||
xStart = 0;
|
||||
yStart = (canvas.height - renderableHeight) / 2;
|
||||
} else {
|
||||
renderableHeight = canvas.height;
|
||||
renderableWidth = canvas.width;
|
||||
xStart = 0;
|
||||
yStart = 0;
|
||||
}
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(image, xStart, yStart, renderableWidth, renderableHeight);
|
||||
|
||||
canvas.toBlob((blob) => {
|
||||
newAvatar = new File([blob], 'avatar.png', { type: blob.type });
|
||||
|
||||
avatarUrl.value = URL.createObjectURL(newAvatar);
|
||||
avatarChanged = true;
|
||||
}, 'image/png');
|
||||
};
|
||||
|
||||
image.src = fr.result;
|
||||
};
|
||||
fr.readAsDataURL(event.target.files[0]);
|
||||
}
|
||||
|
||||
function onChangeBackgroud() {
|
||||
backgroundFileInput.value.click();
|
||||
}
|
||||
|
||||
function onBackgroundChanged(event) {
|
||||
const fr = new FileReader();
|
||||
fr.onload = function () {
|
||||
const image = new Image();
|
||||
image.onload = function () {
|
||||
// convert and scale to webp max 4k
|
||||
const maxWidth = 4096;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
|
||||
if (image.naturalWidth > maxWidth) {
|
||||
canvas.width = maxWidth;
|
||||
canvas.height = (image.naturalHeight / image.naturalWidth) * maxWidth;
|
||||
} else {
|
||||
canvas.width = image.naturalWidth;
|
||||
canvas.height = image.naturalHeight;
|
||||
}
|
||||
|
||||
canvas.getContext('2d').drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||
canvas.toBlob((blob) => {
|
||||
newBackground = new File([blob], 'background.webp', { type: blob.type });
|
||||
|
||||
backgroundUrl.value = URL.createObjectURL(newBackground);
|
||||
backgroundChanged = true;
|
||||
}, 'image/webp');
|
||||
};
|
||||
|
||||
image.src = fr.result;
|
||||
};
|
||||
fr.readAsDataURL(event.target.files[0]);
|
||||
}
|
||||
|
||||
function onBackgroundClear() {
|
||||
backgroundUrl.value = `${API_ORIGIN}/img/background-image-placeholder.svg`;
|
||||
newBackground = null;
|
||||
backgroundChanged = true;
|
||||
function onBackgroundChanged(dataUrl) {
|
||||
newBackgroundDataUrl = dataUrl;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -169,21 +76,12 @@ onMounted(async () => {
|
||||
|
||||
<FormGroup>
|
||||
<label>{{ $t('branding.logo') }}</label>
|
||||
<div class="branding-avatar" @click="onChangeAvatar()">
|
||||
<img :src="avatarUrl"/>
|
||||
<i class="picture-edit-indicator fa fa-pencil-alt"></i>
|
||||
</div>
|
||||
<input @change="onAvatarChanged($event)" type="file" ref="avatarFileInput" style="display: none" accept="image/*"/>
|
||||
<ImagePicker :src="avatarUrl" @changed="onAvatarChanged" :size="512" display-height="100px"/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label class="control-label">{{ $t('branding.backgroundImage') }}</label>
|
||||
<div class="branding-background" @click="onChangeBackgroud()">
|
||||
<img :src="backgroundUrl" onerror="this.src = '/img/background-image-placeholder.svg'"/>
|
||||
<i class="picture-edit-indicator fa fa-pencil-alt"></i>
|
||||
</div>
|
||||
<div v-show="backgroundUrl" class="actionable" @click="onBackgroundClear()">{{ $t('branding.clearBackgroundImage') }}</div>
|
||||
<input @change="onBackgroundChanged($event)" type="file" ref="backgroundFileInput" style="display: none" accept="image/*"/>
|
||||
<ImagePicker :src="backgroundUrl" @changed="onBackgroundChanged" fallback-src="/img/background-image-placeholder.svg" :max-size="4096"/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
@@ -192,11 +90,80 @@ onMounted(async () => {
|
||||
<textarea id="footerInput" v-model="footer" :disabled="busy" style="display: block; width: 100%" rows="1"></textarea>
|
||||
</FormGroup>
|
||||
|
||||
<br/>
|
||||
|
||||
<Button @click="onSubmit()" :loading="busy" :disabled="busy">{{ $t('main.dialog.save') }}</Button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.cloudron-logo {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
background-position: center;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
border: 1px solid gray;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.cloudron-logo-edit-indicator {
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
right: -4px;
|
||||
border-radius: 20px;
|
||||
padding: 5px;
|
||||
color: var(--pankow-text-color);
|
||||
background-color: var(--pankow-input-background-color);
|
||||
transition: all 250ms;
|
||||
}
|
||||
|
||||
.cloudron-logo:hover .cloudron-logo-edit-indicator {
|
||||
color: white;
|
||||
background: var(--pankow-color-primary);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.cloudron-background {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
/* width: 1280px;*/
|
||||
width: auto;
|
||||
height: 256px;
|
||||
margin-bottom: 5px;
|
||||
background-position: center;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
border: 1px solid gray;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.cloudron-background > img {
|
||||
display: block;
|
||||
/* width: 100%;*/
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.cloudron-background-edit-indicator {
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
right: -4px;
|
||||
border-radius: 20px;
|
||||
padding: 5px;
|
||||
color: var(--pankow-text-color);
|
||||
background-color: var(--pankow-input-background-color);
|
||||
transition: all 250ms;
|
||||
}
|
||||
|
||||
.cloudron-background:hover > .cloudron-background-edit-indicator {
|
||||
color: white;
|
||||
background: var(--pankow-color-primary);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user