forked from platypush/platypush
[UI] Added general-purpose entity icon editor component.
This commit is contained in:
parent
1316af9553
commit
1133c6019a
2 changed files with 276 additions and 106 deletions
|
@ -0,0 +1,260 @@
|
|||
<template>
|
||||
<form class="icon-editor"
|
||||
@submit="onIconEdit(newIcon, $event)"
|
||||
@click.stop>
|
||||
<div class="row item">
|
||||
<div class="title">
|
||||
Icon
|
||||
<EditButton title="Edit Icon"
|
||||
@click.stop="editIcon = true"
|
||||
v-if="!editIcon" />
|
||||
</div>
|
||||
<div class="value icon-canvas">
|
||||
<span class="icon-editor" v-if="editIcon">
|
||||
<span class="icon-edit-form" v-if="newIcon != null">
|
||||
<span class="icon-container">
|
||||
<img :src="currentIcon.url" v-if="currentIcon?.url" />
|
||||
<i :class="currentIcon.class" :style="{color: currentIcon.color}" v-else />
|
||||
</span>
|
||||
|
||||
<NameEditor :value="currentIcon.url || currentIcon.class"
|
||||
:disabled="loading"
|
||||
@keyup="newIcon = $event.target.value?.trim()"
|
||||
@input="onIconEdit(newIcon)"
|
||||
@cancel="editIcon = false">
|
||||
<button type="button"
|
||||
title="Reset"
|
||||
@click.stop="onIconEdit(null, $event)">
|
||||
<i class="fas fa-rotate-left" />
|
||||
</button>
|
||||
</NameEditor>
|
||||
</span>
|
||||
|
||||
<span class="help">
|
||||
Supported: image URLs or
|
||||
<a href="https://fontawesome.com/icons" target="_blank">FontAwesome icon classes</a>.
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<Icon v-bind="currentIcon" v-else />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row item">
|
||||
<div class="title">
|
||||
Icon color
|
||||
</div>
|
||||
<div class="value icon-color-picker">
|
||||
<input type="color"
|
||||
:value="currentIcon.color"
|
||||
@input.stop
|
||||
@change.stop="onIconColorEdit">
|
||||
<button type="button"
|
||||
title="Reset"
|
||||
@click.stop="onIconColorEdit(null)">
|
||||
<i class="fas fa-rotate-left" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EditButton from "@/components/elements/EditButton";
|
||||
import Icon from "@/components/elements/Icon";
|
||||
import NameEditor from "@/components/elements/NameEditor";
|
||||
import Utils from "@/Utils";
|
||||
import meta from './meta.json';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditButton,
|
||||
Icon,
|
||||
NameEditor,
|
||||
},
|
||||
mixins: [Utils],
|
||||
emits: ['change', 'input'],
|
||||
props: {
|
||||
entity: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editIcon: false,
|
||||
loading: false,
|
||||
newIcon: null,
|
||||
newColor: null,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentIcon() {
|
||||
return {
|
||||
...((meta[this.entity.type] || {})?.icon || {}),
|
||||
...(this.entity.meta?.icon || {}),
|
||||
...(this.newIcon ? this.iconObj(this.newIcon) : {}),
|
||||
...(this.newColor?.length ? {color: this.newColor} : {}),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
async edit(icon, event) {
|
||||
const req = {
|
||||
[this.entity.id]: {icon},
|
||||
}
|
||||
this.loading = true
|
||||
|
||||
if (event)
|
||||
event.stopPropagation()
|
||||
|
||||
try {
|
||||
const entity = (await this.request('entities.set_meta', req))[0]
|
||||
icon = entity?.meta?.icon
|
||||
if (icon) {
|
||||
this.$emit('input', icon)
|
||||
}
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.editIcon = false
|
||||
}
|
||||
},
|
||||
|
||||
async onIconEdit(newIcon, event) {
|
||||
const icon = {
|
||||
...this.currentIcon,
|
||||
...(this.iconObj(newIcon) || {}),
|
||||
}
|
||||
|
||||
await this.edit(icon, event)
|
||||
},
|
||||
|
||||
async onIconColorEdit(event) {
|
||||
const color = event?.target?.value
|
||||
const icon = {
|
||||
...this.currentIcon,
|
||||
...(color?.length ? {color} : {color: null}),
|
||||
}
|
||||
|
||||
this.newColor = color
|
||||
await this.edit(icon, event)
|
||||
},
|
||||
|
||||
iconObj(iconStr) {
|
||||
if (!iconStr?.length)
|
||||
return {
|
||||
...this.currentIcon,
|
||||
url: this.entity.meta?.icon?.url,
|
||||
class: this.entity.meta?.icon?.class,
|
||||
}
|
||||
|
||||
if (iconStr.startsWith('http'))
|
||||
return {url: iconStr}
|
||||
|
||||
return {class: iconStr}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
editIcon() {
|
||||
this.newIcon = (this.entity.meta?.icon?.['class'] || this.entity.meta?.icon?.url)?.trim()
|
||||
},
|
||||
|
||||
newIcon() {
|
||||
this.$emit('change', this.currentIcon)
|
||||
},
|
||||
|
||||
newColor() {
|
||||
this.$emit('change', this.currentIcon)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "common";
|
||||
|
||||
.icon-editor {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 0.5em;
|
||||
|
||||
@include until($tablet) {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 10em;
|
||||
|
||||
@include until($tablet) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.value {
|
||||
display: inline-flex;
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
|
||||
@include until($tablet) {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-editor {
|
||||
align-items: flex-end;
|
||||
|
||||
@include until($tablet) {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 2em;
|
||||
max-height: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-canvas {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@include until($tablet) {
|
||||
.icon-container {
|
||||
justify-content: left;
|
||||
}
|
||||
}
|
||||
|
||||
@include from($tablet) {
|
||||
.icon-container {
|
||||
justify-content: right;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
.help {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -20,40 +20,7 @@
|
|||
</div>
|
||||
|
||||
<div class="table-row">
|
||||
<div class="title">
|
||||
Icon
|
||||
<EditButton @click="editIcon = true" v-if="!editIcon" />
|
||||
</div>
|
||||
<div class="value icon-canvas">
|
||||
<span class="icon-editor" v-if="editIcon">
|
||||
<NameEditor :value="entity.meta?.icon?.class || entity.meta?.icon?.url" @input="onIconEdit"
|
||||
@cancel="editIcon = false" :disabled="loading">
|
||||
<button type="button" title="Reset" @click="onIconEdit(null)"
|
||||
@touch="onIconEdit(null)">
|
||||
<i class="fas fa-rotate-left" />
|
||||
</button>
|
||||
</NameEditor>
|
||||
<span class="help">
|
||||
Supported: image URLs or
|
||||
<a href="https://fontawesome.com/icons" target="_blank">FontAwesome icon classes</a>.
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<Icon v-bind="entity?.meta?.icon || {}" v-else />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-row">
|
||||
<div class="title">
|
||||
Icon color
|
||||
</div>
|
||||
<div class="value icon-color-picker">
|
||||
<input type="color" :value="entity.meta?.icon?.color" @change="onIconColorEdit">
|
||||
<button type="button" title="Reset" @click="onIconColorEdit(null)"
|
||||
@touch="onIconColorEdit(null)">
|
||||
<i class="fas fa-rotate-left" />
|
||||
</button>
|
||||
</div>
|
||||
<IconEditor :entity="entity" @input="onIconEdit" />
|
||||
</div>
|
||||
|
||||
<div class="table-row">
|
||||
|
@ -206,14 +173,13 @@
|
|||
|
||||
<script>
|
||||
import Modal from "@/components/Modal";
|
||||
import Icon from "@/components/elements/Icon";
|
||||
import IconEditor from "./IconEditor";
|
||||
import ConfirmDialog from "@/components/elements/ConfirmDialog";
|
||||
import EditButton from "@/components/elements/EditButton";
|
||||
import EntityIcon from "./EntityIcon"
|
||||
import NameEditor from "@/components/elements/NameEditor";
|
||||
import Utils from "@/Utils";
|
||||
import Entity from "./Entity";
|
||||
import meta from './meta.json';
|
||||
|
||||
// These fields have a different rendering logic than the general-purpose one
|
||||
const specialFields = [
|
||||
|
@ -233,9 +199,14 @@ const specialFields = [
|
|||
]
|
||||
|
||||
export default {
|
||||
name: "EntityModal",
|
||||
components: {
|
||||
Entity, EntityIcon, Modal, EditButton, NameEditor, Icon, ConfirmDialog
|
||||
ConfirmDialog,
|
||||
EditButton,
|
||||
Entity,
|
||||
EntityIcon,
|
||||
IconEditor,
|
||||
Modal,
|
||||
NameEditor,
|
||||
},
|
||||
mixins: [Utils],
|
||||
emits: ['input', 'loading', 'entity-update'],
|
||||
|
@ -276,7 +247,6 @@ export default {
|
|||
return {
|
||||
loading: false,
|
||||
editName: false,
|
||||
editIcon: false,
|
||||
configCollapsed: true,
|
||||
childrenCollapsed: true,
|
||||
extraInfoCollapsed: true,
|
||||
|
@ -308,47 +278,14 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
async onIconEdit(newIcon) {
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
const icon = {url: null, class: null}
|
||||
if (newIcon?.length) {
|
||||
if (newIcon.startsWith('http'))
|
||||
icon.url = newIcon
|
||||
else
|
||||
icon.class = newIcon
|
||||
} else {
|
||||
icon.url = (meta[this.entity.type] || {})?.icon?.url
|
||||
icon.class = (meta[this.entity.type] || {})?.icon?.['class']
|
||||
}
|
||||
|
||||
const req = {}
|
||||
req[this.entity.id] = {icon: icon}
|
||||
await this.request('entities.set_meta', req)
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.editIcon = false
|
||||
}
|
||||
},
|
||||
|
||||
async onIconColorEdit(event) {
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
const icon = this.entity.meta?.icon || {}
|
||||
if (event)
|
||||
icon.color = event.target.value
|
||||
else
|
||||
icon.color = null
|
||||
|
||||
const req = {}
|
||||
req[this.entity.id] = {icon: icon}
|
||||
await this.request('entities.set_meta', req)
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.editIcon = false
|
||||
onIconEdit(icon) {
|
||||
this.$emit(
|
||||
'input',
|
||||
{
|
||||
...this.entity,
|
||||
meta: {...this.entity.meta, icon},
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
stringify(value) {
|
||||
|
@ -419,39 +356,12 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.icon-canvas {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@include until($tablet) {
|
||||
.icon-container {
|
||||
justify-content: left;
|
||||
}
|
||||
}
|
||||
|
||||
@include from($tablet) {
|
||||
.icon-container {
|
||||
justify-content: right;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
.help {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.delete-entity-container {
|
||||
color: $error-fg;
|
||||
cursor: pointer;
|
||||
|
|
Loading…
Reference in a new issue