Rudimentary creating of nodes

This commit is contained in:
Magnus Åhall 2025-07-03 21:53:09 +02:00
parent 08fd2cf4e9
commit c0255fadb8
7 changed files with 363 additions and 55 deletions

View file

@ -7,27 +7,39 @@ export class App {
this.editor = null
this.typesList = null
this.currentNodeID = null
this.types = []
this.currentPage = null
const events = [
'MENU_ITEM_SELECTED',
'NODE_SELECTED',
'EDITOR_NODE_SAVE',
'TYPES_LIST_FETCHED',
'NODE_CREATE_DIALOG',
'NODE_CREATE',
]
for (const eventName of events)
mbus.subscribe(eventName, event => this.eventHandler(event))
document.addEventListener('keydown', event => this.keyHandler(event))
mbus.dispatch('MENU_ITEM_SELECTED', 'node')
}// }}}
eventHandler(event) {// {{{
async eventHandler(event) {// {{{
switch (event.type) {
case 'MENU_ITEM_SELECTED':
const item = document.querySelector(`#menu [data-section="${event.detail}"]`)
this.section(item, event.detail)
this.page(item, event.detail)
break
case 'NODE_SELECTED':
for (const n of document.querySelectorAll('#nodes .node.selected'))
n.classList.remove('selected')
for (const n of document.querySelectorAll(`#nodes .node[data-node-id="${event.detail}"]`))
n.classList?.add('selected')
this.currentNodeID = event.detail
this.edit(this.currentNodeID)
break
@ -40,20 +52,49 @@ export class App {
const types = document.getElementById('types')
types.replaceChildren(this.typesList.render())
case 'NODE_CREATE_DIALOG':
if (this.currentPage !== 'node' || this.currentNodeID === null)
return
new NodeCreateDialog(this.currentNodeID)
break
case 'NODE_CREATE':
break
default:
console.log(event)
}
}// }}}
section(item, name) {// {{{
keyHandler(event) {// {{{
let handled = true
switch (event.key.toUpperCase()) {
case 'N':
if (!event.shiftKey || !event.altKey)
break
mbus.dispatch('NODE_CREATE_DIALOG')
break
default:
handled = false
}
if (handled) {
event.stopPropagation()
event.preventDefault()
}
}// }}}
page(item, name) {// {{{
for (const el of document.querySelectorAll('#menu .item'))
el.classList.remove('selected')
item.classList.add('selected')
for (const el of document.querySelectorAll('.section.show'))
for (const el of document.querySelectorAll('.page.show'))
el.classList.remove('show')
this.currentPage = name
switch (name) {
case 'node':
document.getElementById('nodes').classList.add('show')
@ -112,13 +153,126 @@ export class App {
const timePassed = Date.now() - buttonPressed
if (timePassed < 250)
setTimeout(()=>btn.disabled = false, 250 - timePassed)
setTimeout(() => btn.disabled = false, 250 - timePassed)
else
btn.disabled = false
})
}// }}}
}
class NodeCreateDialog {
constructor(parentNodeID) {// {{{
this.parentNodeID = parentNodeID
this.dialog = null
this.types = null
this.select = null
this.input = null
this.createElements()
this.fetchTypes()
.then(() => {
const st = new SelectType(this.types)
this.select.replaceChildren(st.render())
})
this.dialog.showModal()
this.select.focus()
}// }}}
createElements() {// {{{
this.dialog = document.createElement('dialog')
this.dialog.id = 'create-type'
this.dialog.innerHTML = `
<div style="padding: 16px">
<select></select>
<input type="text" placeholder="Name">
<button onclick="mbus.dispatch('NODE_CREATE', ()=>this.commit())">Create</button>
</div>
`
this.select = this.dialog.querySelector('select')
this.input = this.dialog.querySelector('input')
this.input.addEventListener('keydown', event => {
if (event.key === 'Enter')
this.commit()
})
document.body.appendChild(this.dialog)
}// }}}
commit() {// {{{
if (this.input.value.trim().length === 0) {
alert('Give a name.')
return
}
const req = {
ParentNodeID: this.parentNodeID,
TypeID: parseInt(this.select.value),
Name: this.input.value.trim(),
}
fetch('/nodes/create', {
method: 'POST',
body: JSON.stringify(req),
})
.then(data => data.json())
.then(json => {
if (!json.OK) {
showError(json.Error)
return
}
this.dialog.close()
})
.catch(err => showError(err))
}// }}}
async fetchTypes() {// {{{
return new Promise((resolve, reject) => {
fetch('/types/')
.then(data => data.json())
.then(json => {
if (!json.OK) {
showError(json.Error)
return
}
this.types = json.Types
resolve()
})
.catch(err => reject(err))
})
}// }}}
}
class SelectType {
constructor(types) {// {{{
this.types = types
}// }}}
render() {// {{{
const tmpl = document.createElement('template')
this.types.sort(typeSort)
let prevGroup = null
for (const t of this.types) {
if (t.Name == 'root_node')
continue
if (t.Schema['x-group'] != prevGroup) {
prevGroup = t.Schema['x-group']
const group = document.createElement('optgroup')
group.setAttribute('label', t.Schema['x-group'])
tmpl.content.appendChild(group)
}
const opt = document.createElement('option')
opt.setAttribute('value', t.ID)
opt.innerText = t.Schema.title || t.Name
tmpl.content.appendChild(opt)
}
return tmpl.content
}// }}}
}
export class TreeNode {
constructor(parent, data) {// {{{
this.data = data
@ -131,7 +285,7 @@ export class TreeNode {
render() {// {{{
const nodeHTML = `
<div class="node">
<div class="node" data-node-id="${this.data.ID}">
<div class="expand-status"><img /></div>
<div class="type-icon"><img /></div>
<div class="name">${this.name()}</div>
@ -246,21 +400,7 @@ export class TypesList {
render() {// {{{
const div = document.createElement('div')
this.types.sort((a,b)=> {
if (a.Schema['x-group'] === undefined)
a.Schema['x-group'] = 'No group'
if (b.Schema['x-group'] === undefined)
b.Schema['x-group'] = 'No group'
if (a.Schema['x-group'] < b.Schema['x-group']) return -1
if (a.Schema['x-group'] > b.Schema['x-group']) return 1
if ((a.Schema.title || a.Name) < (b.Schema.title || b.Name)) return -1
if ((a.Schema.title || a.Name) > (b.Schema.title || b.Name)) return 1
return 0
})
this.types.sort(typeSort)
let prevGroup = null
@ -290,4 +430,20 @@ export class TypesList {
}// }}}
}
function typeSort(a, b) {// {{{
if (a.Schema['x-group'] === undefined)
a.Schema['x-group'] = 'No group'
if (b.Schema['x-group'] === undefined)
b.Schema['x-group'] = 'No group'
if (a.Schema['x-group'] < b.Schema['x-group']) return -1
if (a.Schema['x-group'] > b.Schema['x-group']) return 1
if ((a.Schema.title || a.Name) < (b.Schema.title || b.Name)) return -1
if ((a.Schema.title || a.Name) > (b.Schema.title || b.Name)) return 1
return 0
}// }}}
// vim: foldmethod=marker