Added the ability to upload image as a setting value

This commit is contained in:
eugenijm 2020-02-21 01:05:59 +03:00
parent dd4b5b2f21
commit 10c436b24a
6 changed files with 243 additions and 2 deletions

View file

@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added ### Added
- Link settings that enable registrations and invites - Link settings that enable registrations and invites
- Ability to upload logo, background, default user avatar, instance thumbnail, and NSFW hiding images
### Changed ### Changed

19
src/api/mediaUpload.js Normal file
View file

@ -0,0 +1,19 @@
import { getToken } from '@/utils/auth'
import { baseName } from './utils'
const UPLOAD_URL = '/api/v1/media'
export function uploadMedia({ formData, authHost }) {
const url = baseName(authHost) + UPLOAD_URL
return fetch(url, {
body: formData,
method: 'POST',
headers: authHeaders()
})
.then((data) => data.json())
}
const authHeaders = () => {
return { 'Authorization': `Bearer ${getToken()}` }
}

View file

@ -388,7 +388,10 @@ export default {
instanceReboot: 'Reboot Instance', instanceReboot: 'Reboot Instance',
restartApp: 'You must restart the instance to apply settings', restartApp: 'You must restart the instance to apply settings',
restartSuccess: 'Instance rebooted successfully!', restartSuccess: 'Instance rebooted successfully!',
removeSettingConfirmation: 'Are you sure you want to remove this setting\'s value from the database?' removeSettingConfirmation: 'Are you sure you want to remove this setting\'s value from the database?',
changeImage: 'Change image',
uploadImage: 'Upload image',
remove: 'Remove'
}, },
invites: { invites: {
inviteTokens: 'Invite tokens', inviteTokens: 'Invite tokens',

View file

@ -33,8 +33,16 @@
</el-tooltip> </el-tooltip>
</span> </span>
<div class="input-row"> <div class="input-row">
<image-upload-input
v-if="isImageUrl"
:data="data"
:setting-group="settingGroup"
:setting="setting"
:input-value="inputValue"
@change="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"
/>
<el-input <el-input
v-if="setting.type === 'string' || (setting.type.includes('string') && setting.type.includes('atom'))" v-else-if="setting.type === 'string' || (setting.type.includes('string') && setting.type.includes('atom'))"
:value="inputValue" :value="inputValue"
:placeholder="setting.suggestions ? setting.suggestions[0] : null" :placeholder="setting.suggestions ? setting.suggestions[0] : null"
:data-search="setting.key || setting.group" :data-search="setting.key || setting.group"
@ -125,6 +133,7 @@ import {
CrontabInput, CrontabInput,
EditableKeywordInput, EditableKeywordInput,
IconsInput, IconsInput,
ImageUploadInput,
MascotsInput, MascotsInput,
ProxyUrlInput, ProxyUrlInput,
PruneInput, PruneInput,
@ -143,6 +152,7 @@ export default {
CrontabInput, CrontabInput,
EditableKeywordInput, EditableKeywordInput,
IconsInput, IconsInput,
ImageUploadInput,
MascotsInput, MascotsInput,
ProxyUrlInput, ProxyUrlInput,
PruneInput, PruneInput,
@ -273,6 +283,9 @@ export default {
}, },
updatedSettings() { updatedSettings() {
return this.$store.state.settings.updatedSettings return this.$store.state.settings.updatedSettings
},
isImageUrl() {
return [':background', ':logo', ':nsfwCensorImage', ':default_user_avatar', ':instance_thumbnail'].includes(this.setting.key)
} }
}, },
methods: { methods: {

View file

@ -0,0 +1,204 @@
<template>
<div class="image-upload-area">
<div class="input-row">
<div :style="dimensions" class="image-upload-wrapper">
<div :style="dimensions" class="image-upload-overlay">
<input
:aria-label="$t('settings.changeImage')"
class="input-file"
type="file"
accept=".jpg,.jpeg,.png"
@change="handleFiles" >
<div class="caption">
{{ $t('settings.changeImage') }}
</div>
<el-image
v-loading="loading"
:src="imageUrl(inputValue)"
:style="dimensions"
class="uploaded-image"
fit="cover" />
</div>
</div>
</div>
<div class="image-button-group">
<el-button class="upload-button" size="small">
{{ $t('settings.uploadImage') }}
<input
:aria-label="$t('settings.changeImage')"
class="input-file"
type="file"
accept=".jpg,.jpeg,.png"
@change="handleFiles">
</el-button>
<el-button v-if="!isDefault" type="danger" size="small" style="margin-left: 5px;" @click="removeFile()">
{{ $t('settings.remove') }}
</el-button>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import _ from 'lodash'
import { baseName } from '../../../../api/utils'
import { uploadMedia } from '../../../../api/mediaUpload'
export default {
name: 'ImageUploadInput',
props: {
inputValue: {
type: [String, Object],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
}
},
data() {
return {
loading: false
}
},
computed: {
...mapGetters([
'authHost'
]),
fullSize() {
if (_.includes([':background', ':nsfwCensorImage'], this.setting.key)) {
return true
}
return false
},
dimensions() {
return {
width: this.fullSize ? '100%' : '100px',
height: this.fullSize ? '250px' : '100px'
}
},
isDefault() {
return this.defaultImage === this.inputValue
},
defaultImage() {
return this.baseName + _.get(this.setting, 'suggestions[0]')
},
baseName() {
return baseName(this.authHost)
}
},
methods: {
imageUrl(url) {
if (_.isString(url)) {
const isUrl = url.startsWith('http') || url.startsWith('https')
return isUrl ? url : this.baseName + url
} else {
return this.defaultImage
}
},
handleFiles(event) {
const file = event.target.files[0]
if (!file) { return }
const reader = new FileReader()
reader.onload = ({ target }) => {
const formData = new FormData()
formData.append('file', file)
this.loading = true
uploadMedia({ formData, authHost: this.authHost }).then(response => {
this.loading = false
this.$emit('change', response.url)
})
}
reader.readAsDataURL(file)
},
removeFile() {
this.$emit('change', this.defaultImage)
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../styles/main';
@include settings;
.image-upload-area {
.input-row {
display: flex;
align-items: center;
}
.input-file {
z-index: 100;
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.image-button-group {
margin-top: 20px;
.upload-button {
position: relative;
}
}
.image-upload-wrapper {
position: relative;
.image-upload-overlay {
transition: box-shadow .1s;
border-radius: 5px;
.caption {
visibility: hidden;
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
font-weight: 700;
font-size: 10px;
text-transform: uppercase;;
color: #fff;
z-index: 9;
transition: box-shadow .1s;
}
.uploaded-image {
border-radius: 5px;
box-shadow: 0 2px 10px 0 rgba(0,0,0,.1);
}
&:hover {
visibility: visible;
cursor: pointer;
border-radius: 5px;
.el-image__error {
visibility: hidden;
}
.caption {
visibility: visible;
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.1), inset 0 0 120px 25px rgba(0, 0, 0, 0.8);
border-radius: 5px;
}
}
}
}
}
</style>

View file

@ -2,6 +2,7 @@ export { default as AutoLinkerInput } from './AutoLinkerInput'
export { default as EditableKeywordInput } from './EditableKeywordInput' export { default as EditableKeywordInput } from './EditableKeywordInput'
export { default as CrontabInput } from './CrontabInput' export { default as CrontabInput } from './CrontabInput'
export { default as IconsInput } from './IconsInput' export { default as IconsInput } from './IconsInput'
export { default as ImageUploadInput } from './ImageUploadInput'
export { default as MascotsInput } from './MascotsInput' export { default as MascotsInput } from './MascotsInput'
export { default as ProxyUrlInput } from './ProxyUrlInput' export { default as ProxyUrlInput } from './ProxyUrlInput'
export { default as PruneInput } from './PruneInput' export { default as PruneInput } from './PruneInput'