Sitemap editor: Various fixes (#3194)
Relates to: https://github.com/openhab/openhab-webui/pull/3184#issuecomment-2886630684 - Fixes scroll through the three on iOS, immediately moves elements. - Fixes dirty warning when selecting an Item. - Fixes editor gets dirty when collapsing the tree. - Drag and drop also struggled when there were mutiple equal trees. When dragging one of these, it could not find the proper new position. This has been fixed as well. --------- Signed-off-by: Mark Herwege <mark.herwege@telenet.be>pull/3195/head
parent
68d47c5d22
commit
5d01ce3efc
|
@ -78,7 +78,7 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
allowedWidgetTypes (parentWidget, excludeWidgetIndex) {
|
||||
allowedWidgetTypes (parentWidget) {
|
||||
let types = this.WIDGET_TYPES.filter(w => w.type !== 'Sitemap')
|
||||
// Button only allowed inside Buttongrid
|
||||
if (parentWidget.component === 'Buttongrid') return types.filter(t => t.type === 'Button')
|
||||
|
@ -88,8 +88,7 @@ export default {
|
|||
// Linkable widget types only contain frames or none at all
|
||||
if (this.LINKABLE_WIDGET_TYPES.includes(parentWidget.component)) {
|
||||
if (parentWidget.slots?.widgets?.length > 0) {
|
||||
const widgetList = parentWidget.slots.widgets.filter((widget, index) => index !== excludeWidgetIndex)
|
||||
if (widgetList.find(w => w.component === 'Frame')) {
|
||||
if (parentWidget.slots.widgets.find(w => w.component === 'Frame')) {
|
||||
return types.filter(t => t.type === 'Frame')
|
||||
} else {
|
||||
return types.filter(t => t.type !== 'Frame')
|
||||
|
@ -98,6 +97,14 @@ export default {
|
|||
}
|
||||
return types
|
||||
},
|
||||
canAddChildren (widget) {
|
||||
if (!widget) return false
|
||||
if (widget.component === 'Buttongrid') {
|
||||
const buttons = widget.config.buttons
|
||||
if (Array.isArray(buttons) && buttons.length) return false
|
||||
}
|
||||
return this.LINKABLE_WIDGET_TYPES.includes(widget.component)
|
||||
},
|
||||
widgetTypeDef (component) {
|
||||
const componentType = component ?? this.widget.component
|
||||
return this.WIDGET_TYPES.find(w => w.type === componentType)
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
@treeview:open="setWidgetClosed(false)"
|
||||
@treeview:close="setWidgetClosed(true)"
|
||||
@click="select">
|
||||
<draggable :list="children" group="sitemap-treeview" animation="150" fallbackOnBody="true" swapThreshold="0.6"
|
||||
@start="onStart" @change="onChange" @end="onEnd">
|
||||
<draggable :disabled="!dropAllowed(widget)" :list="children" group="sitemap-treeview" animation="150" fallbackOnBody="true" swapThreshold="0.6" scrollSensitivity="200" delay="400" delayOnTouchOnly="true" touchStartThreshold="10"
|
||||
@start="onStart" @end="onEnd" :move="onMove">
|
||||
<sitemap-treeview-item class="sitemap-treeview-item" v-for="(childwidget, idx) in children"
|
||||
:key="idx"
|
||||
:includeItemName="includeItemName"
|
||||
|
@ -39,7 +39,6 @@
|
|||
<script>
|
||||
import SitemapMixin from '@/components/pagedesigner/sitemap/sitemap-mixin'
|
||||
import Draggable from 'vuedraggable'
|
||||
import fastDeepEqual from 'fast-deep-equal/es6'
|
||||
|
||||
export default {
|
||||
name: 'sitemap-treeview-item',
|
||||
|
@ -69,51 +68,32 @@ export default {
|
|||
console.debug('Drag start event:', event)
|
||||
this.$set(this.localMoveState, 'moving', true)
|
||||
this.$set(this.localMoveState, 'widget', this.widget.slots.widgets[event.oldIndex])
|
||||
this.$set(this.localMoveState, 'oldList', this.widget.slots.widgets)
|
||||
this.$set(this.localMoveState, 'oldIndex', event.oldIndex)
|
||||
this.$set(this.localMoveState, 'newParent', this.parentWidget)
|
||||
},
|
||||
onChange (event) {
|
||||
console.debug('Drag change event:', event)
|
||||
if (event.added) {
|
||||
this.$set(this.localMoveState, 'moving', false)
|
||||
this.validateMove(event.added.newIndex)
|
||||
onMove (event) {
|
||||
console.debug('Drag move event:', event)
|
||||
const newParent = event.relatedContext?.element?.parent
|
||||
if (newParent) {
|
||||
this.$set(this.localMoveState, 'newParent', newParent)
|
||||
}
|
||||
},
|
||||
onEnd (event) {
|
||||
console.debug('Drag end event:', event)
|
||||
this.$set(this.localMoveState, 'moving', false)
|
||||
this.validateMove(event.newIndex)
|
||||
},
|
||||
validateMove (newIndex) {
|
||||
const widget = this.localMoveState.widget
|
||||
const parentWidget = this.findParent(widget, this.localSitemap)
|
||||
const newList = parentWidget.slots.widgets
|
||||
console.debug('New parent:', parentWidget)
|
||||
if (!this.allowedWidgetTypes(parentWidget, newIndex).map(wt => wt.type).includes(widget.component)) {
|
||||
this.$f7.toast.create({
|
||||
text: 'Move invalid',
|
||||
destroyOnClose: true,
|
||||
closeTimeout: 2000
|
||||
}).open()
|
||||
console.debug('Move invalid, restore in original position:', this.localMoveState)
|
||||
newList.splice(newIndex, 1)
|
||||
this.localMoveState.oldList.splice(this.localMoveState.oldIndex, 0, widget)
|
||||
} else {
|
||||
console.debug('Move valid')
|
||||
const parentWidget = this.localMoveState.newParent
|
||||
if (widget && parentWidget) {
|
||||
this.$set(widget, 'parent', parentWidget)
|
||||
}
|
||||
this.$set(this.localMoveState, 'moving', false)
|
||||
this.$set(this.localMoveState, 'widget', null)
|
||||
this.$set(this.localMoveState, 'newParent', null)
|
||||
},
|
||||
findParent (widget, parentWidget) {
|
||||
if (parentWidget.slots?.widgets) {
|
||||
for (const w of parentWidget.slots.widgets) {
|
||||
if (fastDeepEqual(widget, w)) {
|
||||
return parentWidget
|
||||
} else {
|
||||
const parent = this.findParent(widget, w)
|
||||
if (parent != null) return parent
|
||||
}
|
||||
}
|
||||
dropAllowed (widget) {
|
||||
if (!this.canAddChildren(widget)) return false
|
||||
if (!this.localMoveState.widget || this.allowedWidgetTypes(widget).map(wt => wt.type).includes(this.localMoveState.widget.component)) {
|
||||
return true
|
||||
}
|
||||
return null
|
||||
return false
|
||||
},
|
||||
setWidgetClosed (closed) {
|
||||
this.$set(this.widget, 'closed', closed)
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
<div><f7-block-title>Icon Color</f7-block-title></div>
|
||||
<attribute-details :widget="selectedWidget" attribute="iconcolor" placeholder="item_name operator value = color" />
|
||||
</f7-block>
|
||||
<f7-block v-if="selectedWidget && canAddChildren && selectedWidget.component !== 'Buttongrid'">
|
||||
<f7-block v-if="selectedWidget && canAddChildren(selectedWidget) && selectedWidget.component !== 'Buttongrid'">
|
||||
<div><f7-block-title>Add Child Widget</f7-block-title></div>
|
||||
<f7-card>
|
||||
<f7-card-content>
|
||||
|
@ -96,7 +96,7 @@
|
|||
</f7-card-content>
|
||||
</f7-card>
|
||||
</f7-block>
|
||||
<f7-block v-if="selectedWidget && canAddChildren && selectedWidget.component === 'Buttongrid'">
|
||||
<f7-block v-if="selectedWidget && canAddChildren(selectedWidget) && selectedWidget.component === 'Buttongrid'">
|
||||
<div><f7-block-title>Add Button Widget</f7-block-title></div>
|
||||
<f7-card>
|
||||
<f7-card-content>
|
||||
|
@ -126,11 +126,11 @@
|
|||
</f7-tab>
|
||||
</f7-tabs>
|
||||
|
||||
<f7-fab class="add-to-sitemap-fab" v-if="canAddChildren && selectedWidget.component !== 'Buttongrid'" position="right-center" slot="fixed" color="blue" @click="$refs.widgetTypeSelection.open()">
|
||||
<f7-fab class="add-to-sitemap-fab" v-if="canAddChildren(selectedWidget) && selectedWidget.component !== 'Buttongrid'" position="right-center" slot="fixed" color="blue" @click="$refs.widgetTypeSelection.open()">
|
||||
<f7-icon ios="f7:plus" md="material:add" aurora="f7:plus" />
|
||||
<f7-icon ios="f7:multiply" md="material:close" aurora="f7:multiply" />
|
||||
</f7-fab>
|
||||
<f7-fab class="add-to-sitemap-fab" v-if="canAddChildren && selectedWidget.component === 'Buttongrid'" position="right-center" slot="fixed" color="blue" @click="addWidget('Button')">
|
||||
<f7-fab class="add-to-sitemap-fab" v-if="canAddChildren(selectedWidget) && selectedWidget.component === 'Buttongrid'" position="right-center" slot="fixed" color="blue" @click="addWidget('Button')">
|
||||
<f7-icon ios="f7:plus" md="material:add" aurora="f7:plus" />
|
||||
<f7-icon ios="f7:multiply" md="material:close" aurora="f7:multiply" />
|
||||
</f7-fab>
|
||||
|
@ -290,6 +290,7 @@ import AttributeDetails from '@/components/pagedesigner/sitemap/attribute-detail
|
|||
import SitemapTreeviewItem from '@/components/pagedesigner/sitemap/treeview-item.vue'
|
||||
import SitemapMixin from '@/components/pagedesigner/sitemap/sitemap-mixin'
|
||||
import DirtyMixin from '@/pages/settings/dirty-mixin'
|
||||
import fastDeepEqual from 'fast-deep-equal/es6'
|
||||
|
||||
export default {
|
||||
mixins: [DirtyMixin, SitemapMixin],
|
||||
|
@ -315,6 +316,7 @@ export default {
|
|||
tags: [],
|
||||
slots: { widgets: [] }
|
||||
},
|
||||
lastCleanSitemap: null,
|
||||
selectedWidget: null,
|
||||
selectedWidgetParent: null,
|
||||
previousSelection: null,
|
||||
|
@ -329,14 +331,6 @@ export default {
|
|||
if (!this.selectedWidget) return false
|
||||
return (Array.isArray(this.selectedWidget.slots?.widgets) && this.selectedWidget.slots.widgets.length)
|
||||
},
|
||||
canAddChildren () {
|
||||
if (!this.selectedWidget) return false
|
||||
if (this.selectedWidget.component === 'Buttongrid') {
|
||||
const buttons = this.selectedWidget.config.buttons
|
||||
if (Array.isArray(buttons) && buttons.length) return false
|
||||
}
|
||||
return this.LINKABLE_WIDGET_TYPES.includes(this.selectedWidget.component)
|
||||
},
|
||||
canShowValue () {
|
||||
if (!this.selectedWidget) return false
|
||||
return this.WIDGET_TYPES_SHOWING_VALUE.includes(this.selectedWidget.component)
|
||||
|
@ -348,9 +342,12 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
sitemap: {
|
||||
handler: function () {
|
||||
if (!this.loading) {
|
||||
handler (newVal) {
|
||||
if (this.loading) return
|
||||
if (!fastDeepEqual(this.stripClosed(newVal), this.lastCleanSitemap)) {
|
||||
this.dirty = true
|
||||
} else {
|
||||
this.dirty = false
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
|
@ -381,6 +378,29 @@ export default {
|
|||
ev.preventDefault()
|
||||
}
|
||||
},
|
||||
stripClosed (obj) {
|
||||
// Remove the closed field as it is only used for expanding the tree, and should not impact the dirty state
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(this.stripClosed)
|
||||
} else if (obj !== null && typeof obj === 'object') {
|
||||
// also exclude parent to avoid recursive copies
|
||||
const { parent, closed, ...rest } = obj
|
||||
const result = {}
|
||||
for (const key in rest) {
|
||||
result[key] = this.stripClosed(rest[key])
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
return obj
|
||||
}
|
||||
},
|
||||
setParents (widget) {
|
||||
// keep parents with widget for drag and drop
|
||||
widget.slots?.widgets?.forEach((w) => {
|
||||
this.$set(w, 'parent', widget)
|
||||
this.setParents(w)
|
||||
})
|
||||
},
|
||||
load () {
|
||||
if (this.loading) return
|
||||
this.loading = true
|
||||
|
@ -388,6 +408,7 @@ export default {
|
|||
if (this.ready && this.dirty) this.save(true, true)
|
||||
|
||||
if (this.createMode) {
|
||||
this.$set(this, 'lastCleanSitemap', this.stripClosed(this.sitemap))
|
||||
this.loading = false
|
||||
this.ready = true
|
||||
} else {
|
||||
|
@ -395,6 +416,8 @@ export default {
|
|||
const sitemap = this.preProcessSitemapLoad(data)
|
||||
this.$set(this, 'sitemap', sitemap)
|
||||
this.$nextTick(() => {
|
||||
this.$set(this, 'lastCleanSitemap', this.stripClosed(this.sitemap))
|
||||
this.setParents(this.sitemap)
|
||||
this.ready = true
|
||||
this.loading = false
|
||||
})
|
||||
|
@ -444,6 +467,8 @@ export default {
|
|||
destroyOnClose: true,
|
||||
closeTimeout: 2000
|
||||
}).open()
|
||||
this.$set(this, 'lastCleanSitemap', this.stripClosed(this.sitemap))
|
||||
this.setParents(sitemap)
|
||||
}
|
||||
this.$f7.emit('sidebarRefresh', null)
|
||||
// if (!stay) this.$f7router.back()
|
||||
|
|
Loading…
Reference in New Issue