2022-04-10 13:07:36 +02:00
|
|
|
<template>
|
2023-01-01 23:06:40 +01:00
|
|
|
<div class="entity-container-wrapper"
|
2023-01-13 02:58:47 +01:00
|
|
|
:class="{'with-children': hasChildren, collapsed: isCollapsed, hidden: !value?.name?.length}">
|
2023-01-01 23:06:40 +01:00
|
|
|
<div class="row item entity-container"
|
2023-01-21 14:55:06 +01:00
|
|
|
:class="{ 'with-children': hasChildren, collapsed: isCollapsed, blink: justUpdated }">
|
2023-01-01 23:06:40 +01:00
|
|
|
<div class="adjuster" :class="{'col-12': !hasChildren, 'col-11': hasChildren}">
|
2023-01-15 15:03:53 +01:00
|
|
|
<component
|
|
|
|
:is="component"
|
2023-01-01 23:06:40 +01:00
|
|
|
:value="value"
|
2023-03-19 12:50:45 +01:00
|
|
|
:parent="parent"
|
2023-03-20 01:27:21 +01:00
|
|
|
:children="computedChildren"
|
2023-01-01 23:06:40 +01:00
|
|
|
:loading="loading"
|
2023-01-15 15:03:53 +01:00
|
|
|
ref="instance"
|
2023-01-01 23:06:40 +01:00
|
|
|
:error="error || value?.reachable == false"
|
2023-01-15 15:03:53 +01:00
|
|
|
@click="onClick"
|
2023-01-01 23:06:40 +01:00
|
|
|
@input="$emit('input', $event)"
|
|
|
|
@loading="$emit('loading', $event)"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
2023-01-15 15:03:53 +01:00
|
|
|
<div class="col-1 collapse-toggler" @click.stop="toggleCollapsed" v-if="hasChildren">
|
2023-01-01 23:06:40 +01:00
|
|
|
<i class="fas"
|
|
|
|
:class="{'fa-chevron-down': isCollapsed, 'fa-chevron-up': !isCollapsed}" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2023-01-15 15:46:25 +01:00
|
|
|
<div class="children fade-in" v-if="!isCollapsed">
|
2023-01-01 23:06:40 +01:00
|
|
|
<div class="child" v-for="entity in computedChildren" :key="entity.id">
|
|
|
|
<Entity
|
|
|
|
:value="entity"
|
2023-03-19 12:50:45 +01:00
|
|
|
:parent="value"
|
2023-01-01 23:06:40 +01:00
|
|
|
:loading="loading"
|
|
|
|
:level="level + 1"
|
|
|
|
@input="$emit('input', entity)" />
|
|
|
|
</div>
|
|
|
|
</div>
|
2022-04-10 13:07:36 +02:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
2023-01-14 22:31:48 +01:00
|
|
|
import { defineAsyncComponent, shallowRef } from 'vue'
|
2022-05-01 15:34:15 +02:00
|
|
|
import EntityMixin from "./EntityMixin"
|
2023-01-21 16:58:28 +01:00
|
|
|
import { bus } from "@/bus";
|
2022-04-10 13:07:36 +02:00
|
|
|
|
|
|
|
export default {
|
|
|
|
name: "Entity",
|
2022-05-01 15:34:15 +02:00
|
|
|
mixins: [EntityMixin],
|
2023-01-21 16:58:28 +01:00
|
|
|
emits: ['input', 'loading', 'update'],
|
2022-04-10 13:07:36 +02:00
|
|
|
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
component: null,
|
2022-11-13 01:39:40 +01:00
|
|
|
justUpdated: false,
|
2022-04-10 13:07:36 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2023-01-01 23:06:40 +01:00
|
|
|
computed: {
|
|
|
|
computedChildren() {
|
|
|
|
return Object.values(this.children || {}).filter((child) => child)
|
|
|
|
},
|
|
|
|
|
|
|
|
hasChildren() {
|
|
|
|
return !!this.computedChildren.length
|
|
|
|
},
|
|
|
|
|
|
|
|
isCollapsed() {
|
|
|
|
if (!this.hasChildren)
|
|
|
|
return true
|
|
|
|
|
|
|
|
return this.collapsed
|
|
|
|
},
|
2023-01-15 15:03:53 +01:00
|
|
|
|
|
|
|
instance() {
|
|
|
|
return this.$refs.instance
|
|
|
|
},
|
2023-01-01 23:06:40 +01:00
|
|
|
},
|
|
|
|
|
2022-11-13 23:52:21 +01:00
|
|
|
methods: {
|
|
|
|
valuesEqual(a, b) {
|
|
|
|
a = {...a}
|
|
|
|
b = {...b}
|
2022-11-27 00:56:23 +01:00
|
|
|
for (const key of ['updated_at', 'data']) {
|
|
|
|
delete a[key]
|
|
|
|
delete b[key]
|
|
|
|
}
|
|
|
|
|
2022-11-13 23:52:21 +01:00
|
|
|
return this.objectsEqual(a, b)
|
|
|
|
},
|
2023-01-15 15:03:53 +01:00
|
|
|
|
|
|
|
onClick(event) {
|
2023-01-21 23:46:38 +01:00
|
|
|
if (
|
|
|
|
event.target.classList.contains('label') ||
|
|
|
|
event.target.classList.contains('head')
|
|
|
|
) {
|
2023-01-15 15:03:53 +01:00
|
|
|
event.stopPropagation()
|
|
|
|
this.toggleCollapsed()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2023-01-21 16:58:28 +01:00
|
|
|
onEntityUpdate(entity) {
|
|
|
|
// Check if any of the children have been updated
|
|
|
|
const entityId = entity?.id
|
2023-01-21 23:46:38 +01:00
|
|
|
const isChildUpdate = (
|
|
|
|
entityId != null &&
|
|
|
|
this.children &&
|
|
|
|
entityId in this.children
|
|
|
|
)
|
|
|
|
|
|
|
|
if (!isChildUpdate)
|
2023-01-21 16:58:28 +01:00
|
|
|
return
|
|
|
|
|
|
|
|
this.setJustUpdated()
|
|
|
|
},
|
|
|
|
|
2023-01-15 15:03:53 +01:00
|
|
|
toggleCollapsed() {
|
|
|
|
this.collapsed = !this.collapsed
|
|
|
|
// Propagate the collapsed state to the wrapped component if applicable
|
2023-01-15 15:29:26 +01:00
|
|
|
if (this.instance)
|
2023-01-15 15:03:53 +01:00
|
|
|
this.instance.collapsed = !this.instance.collapsed
|
2023-01-21 14:55:06 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
setJustUpdated() {
|
|
|
|
this.justUpdated = true
|
|
|
|
const self = this;
|
|
|
|
setTimeout(() => self.justUpdated = false, 1000)
|
|
|
|
},
|
2022-11-13 23:52:21 +01:00
|
|
|
},
|
|
|
|
|
2022-04-10 13:07:36 +02:00
|
|
|
mounted() {
|
2022-10-30 11:01:46 +01:00
|
|
|
if (this.type !== 'Entity') {
|
|
|
|
const type = this.type.split('_').map((t) =>
|
|
|
|
t[0].toUpperCase() + t.slice(1)
|
|
|
|
).join('')
|
|
|
|
|
2022-11-13 01:39:40 +01:00
|
|
|
this.$watch(
|
|
|
|
() => this.value,
|
2022-11-13 23:52:21 +01:00
|
|
|
(newValue, oldValue) => {
|
|
|
|
if (this.valuesEqual(oldValue, newValue))
|
|
|
|
return false
|
|
|
|
|
2023-01-21 14:55:06 +01:00
|
|
|
this.setJustUpdated()
|
|
|
|
this.$emit('update', {value: newValue})
|
2022-11-13 01:39:40 +01:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2023-01-14 22:31:48 +01:00
|
|
|
this.component = shallowRef(
|
|
|
|
defineAsyncComponent(
|
|
|
|
() => import(`@/components/panels/Entities/${type}`)
|
|
|
|
)
|
2022-04-10 13:07:36 +02:00
|
|
|
)
|
2022-10-30 11:01:46 +01:00
|
|
|
}
|
2023-01-21 16:58:28 +01:00
|
|
|
|
|
|
|
bus.onEntity(this.onEntityUpdate)
|
2022-04-10 13:07:36 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
2022-04-12 00:43:22 +02:00
|
|
|
<style lang="scss" scoped>
|
2022-05-01 15:34:15 +02:00
|
|
|
@import "common";
|
2022-04-10 13:07:36 +02:00
|
|
|
|
2023-01-01 23:06:40 +01:00
|
|
|
.entity-container-wrapper {
|
|
|
|
&.with-children:not(.collapsed) {
|
|
|
|
box-shadow: 0 3px 4px 0 $default-shadow-color;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-01 15:34:15 +02:00
|
|
|
.entity-container {
|
2022-04-10 13:07:36 +02:00
|
|
|
width: 100%;
|
2023-01-01 23:06:40 +01:00
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
2022-05-01 15:34:15 +02:00
|
|
|
position: relative;
|
|
|
|
padding: 0 !important;
|
2023-01-01 23:06:40 +01:00
|
|
|
border-bottom: $default-border-3;
|
|
|
|
|
|
|
|
&.with-children:not(.collapsed) {
|
|
|
|
background: $selected-bg;
|
|
|
|
font-weight: bold;
|
|
|
|
box-shadow: 0 0 3px 2px $default-shadow-color;
|
|
|
|
}
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
background: $hover-bg;
|
|
|
|
}
|
|
|
|
|
|
|
|
.collapse-toggler {
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
flex: 1;
|
|
|
|
min-height: 3em;
|
|
|
|
margin-left: 0;
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
color: $default-hover-fg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.adjuster {
|
|
|
|
cursor: pointer;
|
|
|
|
}
|
2022-04-10 13:07:36 +02:00
|
|
|
}
|
2022-11-13 01:39:40 +01:00
|
|
|
|
2023-01-15 15:03:53 +01:00
|
|
|
:deep(.entity-container) {
|
|
|
|
.head {
|
|
|
|
.name {
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
color: $hover-fg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-20 01:27:21 +01:00
|
|
|
.label {
|
|
|
|
margin-left: 0.5em;
|
|
|
|
}
|
|
|
|
|
2023-01-15 15:03:53 +01:00
|
|
|
.icon:hover {
|
|
|
|
color: $hover-fg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-13 01:39:40 +01:00
|
|
|
.blink {
|
|
|
|
animation: blink-animation 1s steps(20, start);
|
|
|
|
}
|
|
|
|
|
|
|
|
@keyframes blink-animation {
|
2022-11-13 23:52:21 +01:00
|
|
|
0% {
|
|
|
|
background: initial
|
|
|
|
}
|
|
|
|
|
|
|
|
50% {
|
2022-11-13 01:39:40 +01:00
|
|
|
background: $active-bg;
|
|
|
|
}
|
2022-11-13 23:52:21 +01:00
|
|
|
|
|
|
|
100% {
|
|
|
|
background: initial
|
|
|
|
}
|
2022-11-13 01:39:40 +01:00
|
|
|
}
|
2022-04-10 13:07:36 +02:00
|
|
|
</style>
|