Wip
This commit is contained in:
parent
a6bb845c9d
commit
cfd5bfd719
9 changed files with 213 additions and 111 deletions
|
|
@ -13,12 +13,14 @@ export class App {
|
|||
this.tree = new Tree(document.getElementById('nodes'))
|
||||
|
||||
const events = [
|
||||
'MENU_ITEM_SELECTED',
|
||||
'NODE_SELECTED',
|
||||
'EDITOR_NODE_SAVE',
|
||||
'TYPES_LIST_FETCHED',
|
||||
'MENU_ITEM_SELECTED',
|
||||
'NODE_CREATE_DIALOG',
|
||||
'NODE_DELETE',
|
||||
'NODE_EDIT_NAME',
|
||||
'NODE_SELECTED',
|
||||
'TREE_RELOAD_NODE',
|
||||
'TYPES_LIST_FETCHED',
|
||||
]
|
||||
for (const eventName of events)
|
||||
mbus.subscribe(eventName, event => this.eventHandler(event))
|
||||
|
|
@ -45,6 +47,12 @@ export class App {
|
|||
this.edit(event.detail)
|
||||
break
|
||||
|
||||
case 'NODE_DELETE':
|
||||
if (!confirm('Are you sure you want to delete this node?'))
|
||||
return
|
||||
this.nodeDelete(this.currentNode.ID)
|
||||
break
|
||||
|
||||
case 'EDITOR_NODE_SAVE':
|
||||
this.nodeUpdate()
|
||||
break
|
||||
|
|
@ -65,7 +73,18 @@ export class App {
|
|||
const newName = prompt('Rename node', this.currentNode.Name)
|
||||
if (newName === null)
|
||||
return
|
||||
|
||||
this.nodeRename(this.currentNode.ID, newName)
|
||||
.then(() => mbus.dispatch('TREE_RELOAD_NODE', { parentNodeID: this.currentNode.ParentID }))
|
||||
break
|
||||
|
||||
case 'TREE_RELOAD_NODE':
|
||||
this.tree.updateNode(event.detail.parentNodeID)
|
||||
.then(() => {
|
||||
if (event.detail.callback)
|
||||
event.detail.callback()
|
||||
.catch(err => showError(err))
|
||||
})
|
||||
break
|
||||
|
||||
default:
|
||||
|
|
@ -77,6 +96,10 @@ export class App {
|
|||
let handled = true
|
||||
|
||||
switch (event.key.toUpperCase()) {
|
||||
case 'D':
|
||||
mbus.dispatch('NODE_DELETE')
|
||||
break
|
||||
|
||||
case 'N':
|
||||
if (!event.shiftKey || !event.altKey)
|
||||
return
|
||||
|
|
@ -145,30 +168,38 @@ export class App {
|
|||
// Name is separate from the JSON node.
|
||||
const name = document.getElementById('editor-node-name')
|
||||
name.innerText = json.Node.Name
|
||||
|
||||
// The editor-node div is hidden from the start as a lot of the elements
|
||||
// doesn't make any sense before a node is selected.
|
||||
document.getElementById('editor-node').style.display = 'grid'
|
||||
})
|
||||
}// }}}
|
||||
nodeRename(nodeID, name) {// {{{
|
||||
name = name.trim()
|
||||
if (name.length === 0) {
|
||||
alert('A name must be provided.')
|
||||
return
|
||||
}
|
||||
async nodeRename(nodeID, name) {// {{{
|
||||
return new Promise((resolve, reject) => {
|
||||
name = name.trim()
|
||||
if (name.length === 0) {
|
||||
alert('A name must be provided.')
|
||||
return
|
||||
}
|
||||
|
||||
fetch(`/nodes/rename/${nodeID}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
Name: name,
|
||||
}),
|
||||
})
|
||||
.then(data => data.json())
|
||||
.then(json => {
|
||||
if (!json.OK) {
|
||||
showError(json.Error)
|
||||
return
|
||||
}
|
||||
|
||||
this.edit(nodeID)
|
||||
fetch(`/nodes/rename/${nodeID}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
Name: name,
|
||||
}),
|
||||
})
|
||||
.then(data => data.json())
|
||||
.then(json => {
|
||||
if (!json.OK) {
|
||||
showError(json.Error)
|
||||
return
|
||||
}
|
||||
|
||||
this.edit(nodeID)
|
||||
resolve()
|
||||
})
|
||||
.catch(err => reject(err))
|
||||
})
|
||||
}// }}}
|
||||
nodeUpdate() {// {{{
|
||||
if (this.editor === null)
|
||||
|
|
@ -198,6 +229,17 @@ export class App {
|
|||
btn.disabled = false
|
||||
})
|
||||
}// }}}
|
||||
nodeDelete(nodeID) {// {{{
|
||||
fetch(`/nodes/delete/${nodeID}`)
|
||||
.then(data => data.json())
|
||||
.then(json => {
|
||||
if (!json.OK) {
|
||||
showError(json.Error)
|
||||
return
|
||||
}
|
||||
})
|
||||
.catch(err => showError(err))
|
||||
}// }}}
|
||||
}
|
||||
|
||||
class NodeCreateDialog {
|
||||
|
|
@ -226,10 +268,11 @@ class NodeCreateDialog {
|
|||
<div style="padding: 16px">
|
||||
<select></select>
|
||||
<input type="text" placeholder="Name">
|
||||
<button onclick="this.commit()">Create</button>
|
||||
<button>Create</button>
|
||||
</div>
|
||||
`
|
||||
|
||||
this.dialog.querySelector('button').addEventListener('click', () => this.commit())
|
||||
this.select = this.dialog.querySelector('select')
|
||||
this.input = this.dialog.querySelector('input')
|
||||
this.input.addEventListener('keydown', event => {
|
||||
|
|
@ -261,6 +304,13 @@ class NodeCreateDialog {
|
|||
showError(json.Error)
|
||||
return
|
||||
}
|
||||
mbus.dispatch('TREE_RELOAD_NODE', {
|
||||
parentNodeID: this.parentNodeID,
|
||||
callback: () => {
|
||||
console.log('hum foo')
|
||||
mbus.dispatch('NODE_SELECTED', json.NodeID)
|
||||
},
|
||||
})
|
||||
this.dialog.close()
|
||||
})
|
||||
.catch(err => showError(err))
|
||||
|
|
@ -359,34 +409,41 @@ export class Tree {
|
|||
})
|
||||
}// }}}
|
||||
updateNode(nodeID) {// {{{
|
||||
// updateNode retrieves a node and its' immediate children.
|
||||
// Node and each child is found in the treeNodes map and the names are updated.
|
||||
// If not found, created and added.
|
||||
//
|
||||
// Newly created nodes are found and added, existing but renamed nodes are modified, and unchanged are left as is.
|
||||
this.fetchNodes(nodeID)
|
||||
.then(node => {
|
||||
const thisTreeNode = this.treeNodes.get(nodeID)
|
||||
thisTreeNode.childrenFetched = true
|
||||
return new Promise((resolve, reject) => {
|
||||
// updateNode retrieves a node and its' immediate children.
|
||||
// Node and each child is found in the treeNodes map and the names are updated.
|
||||
// If not found, created and added.
|
||||
//
|
||||
// Newly created nodes are found and added, existing but renamed nodes are modified, and unchanged are left as is.
|
||||
this.fetchNodes(nodeID)
|
||||
.then(node => {
|
||||
const thisTreeNode = this.treeNodes.get(nodeID)
|
||||
thisTreeNode.childrenFetched = true
|
||||
thisTreeNode.node = node
|
||||
thisTreeNode.updateExpandImages()
|
||||
thisTreeNode.toggleExpand(true)
|
||||
|
||||
// Children are sorted according to type and name.
|
||||
this.sortChildren(node.Children)
|
||||
// Children are sorted according to type and name.
|
||||
this.sortChildren(node.Children)
|
||||
|
||||
// Update or add children
|
||||
for (const n of node.Children) {
|
||||
if (this.treeNodes.has(n.ID)) {
|
||||
const treenode = this.treeNodes.get(n.ID)
|
||||
treenode.node = n
|
||||
treenode.element.querySelector('.name').innerText = n.Name
|
||||
} else {
|
||||
const treenode = new TreeNode(n)
|
||||
this.treeNodes.set(n.ID, treenode)
|
||||
thisTreeNode.children.appendChild(treenode.render())
|
||||
// Update or add children
|
||||
for (const n of node.Children) {
|
||||
if (this.treeNodes.has(n.ID)) {
|
||||
const treenode = this.treeNodes.get(n.ID)
|
||||
treenode.node = n
|
||||
treenode.element.querySelector('.name').innerText = n.Name
|
||||
treenode.updateExpandImages()
|
||||
} else {
|
||||
const treenode = new TreeNode(n)
|
||||
this.treeNodes.set(n.ID, treenode)
|
||||
thisTreeNode.children.appendChild(treenode.render())
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve()
|
||||
|
||||
})
|
||||
.catch(err => showError(err))
|
||||
})
|
||||
.catch(err => reject(err))
|
||||
})
|
||||
}// }}}
|
||||
sortChildren(children) {// {{{
|
||||
children.sort((a, b) => {
|
||||
|
|
@ -407,6 +464,7 @@ export class TreeNode {
|
|||
this.childrenFetched = false
|
||||
this.element = null
|
||||
this.children = null
|
||||
this.expandEventListenerAdded = false
|
||||
}// }}}
|
||||
|
||||
render() {// {{{
|
||||
|
|
@ -423,16 +481,13 @@ export class TreeNode {
|
|||
div.innerHTML = nodeHTML
|
||||
|
||||
this.children = div.querySelector('.children')
|
||||
this.expandImg = div.querySelector('.expand-status img')
|
||||
|
||||
div.querySelector('.name').addEventListener('click', () => mbus.dispatch('NODE_SELECTED', this.node.ID))
|
||||
|
||||
// data.NumChildren is set regardless of having fetched the children or not.
|
||||
if (this.hasChildren()) {
|
||||
const img = div.querySelector('.expand-status img')
|
||||
img.setAttribute('src', `/images/${_VERSION}/node_modules/@mdi/svg/svg/plus-box-outline.svg`)
|
||||
img.addEventListener('click', event => this.toggleExpand(event))
|
||||
} else
|
||||
div.querySelector('.expand-status').classList.add('leaf')
|
||||
this.expandStatus = div.querySelector('.expand-status img')
|
||||
this.updateExpandImages()
|
||||
|
||||
if (this.node.TypeIcon) {
|
||||
const img = div.querySelector('.type-icon img')
|
||||
|
|
@ -450,15 +505,32 @@ export class TreeNode {
|
|||
hasChildren() {// {{{
|
||||
return this.node.NumChildren > 0
|
||||
}// }}}
|
||||
toggleExpand(event) {// {{{
|
||||
const node = event.target.closest('.node')
|
||||
node?.classList.toggle('expanded')
|
||||
updateExpandImages() {// {{{
|
||||
if (this.hasChildren()) {
|
||||
this.expandStatus.setAttribute('src', `/images/${_VERSION}/node_modules/@mdi/svg/svg/plus-box-outline.svg`)
|
||||
|
||||
if (!this.expandEventListenerAdded) {
|
||||
this.expandStatus.addEventListener('click', () => this.toggleExpand())
|
||||
this.expandEventListenerAdded = true
|
||||
}
|
||||
} else {
|
||||
this.expandStatus.setAttribute('src', `/images/${_VERSION}/node_modules/@mdi/svg/svg/circle-medium.svg`)
|
||||
}
|
||||
}// }}}
|
||||
toggleExpand(expanded) {// {{{
|
||||
const node = this.element
|
||||
|
||||
if (expanded === undefined)
|
||||
node?.classList.toggle('expanded')
|
||||
else if (expanded === true)
|
||||
node?.classList.add('expanded')
|
||||
else
|
||||
node?.classList.remove('expanded')
|
||||
|
||||
const img = node?.classList.contains('expanded') ? 'minus-box-outline' : 'plus-box-outline'
|
||||
event.target.setAttribute('src', `/images/${_VERSION}/node_modules/@mdi/svg/svg/${img}.svg`)
|
||||
this.expandStatus.setAttribute('src', `/images/${_VERSION}/node_modules/@mdi/svg/svg/${img}.svg`)
|
||||
|
||||
if (!this.childrenFetched && this.node.NumChildren > 0 && this.node.Children.length == 0) {
|
||||
console.log(`fetching for ${this.node.Name}`)
|
||||
mbus.dispatch('NODE_EXPAND', this)
|
||||
}
|
||||
}// }}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue