import { Editor } from '@editor' import { MessageBus } from '@mbus' export class App { constructor() {// {{{ window.mbus = new MessageBus() this.editor = null this.typesList = null this.currentNodeID = null const events = [ 'MENU_ITEM_SELECTED', 'NODE_SELECTED', 'EDITOR_NODE_SAVE', 'TYPES_LIST_FETCHED', ] for (const eventName of events) mbus.subscribe(eventName, event => this.eventHandler(event)) mbus.dispatch('MENU_ITEM_SELECTED', 'node') }// }}} eventHandler(event) {// {{{ switch (event.type) { case 'MENU_ITEM_SELECTED': const item = document.querySelector(`#menu [data-section="${event.detail}"]`) this.section(item, event.detail) break case 'NODE_SELECTED': this.currentNodeID = event.detail this.edit(this.currentNodeID) break case 'EDITOR_NODE_SAVE': this.nodeUpdate() break case 'TYPES_LIST_FETCHED': const types = document.getElementById('types') types.replaceChildren(this.typesList.render()) break default: console.log(event) } }// }}} section(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')) el.classList.remove('show') switch (name) { case 'node': document.getElementById('nodes').classList.add('show') document.getElementById('editor-node').classList.add('show') break case 'type': document.getElementById('types').classList.add('show') document.getElementById('editor-type-schema').classList.add('show') if (this.typesList === null) this.typesList = new TypesList() this.typesList.fetchTypes().then(() => { mbus.dispatch('TYPES_LIST_FETCHED') }) break } }// }}} edit(nodeID) {// {{{ console.log(nodeID) fetch(`/nodes/${nodeID}`) .then(data => data.json()) .then(json => { if (!json.OK) { showError(json.Error) return } const editorEl = document.querySelector('#editor-node .editor') this.editor = new Editor(json.Node.TypeSchema) editorEl.replaceChildren(this.editor.render(json.Node.Data)) }) }// }}} nodeUpdate() {// {{{ if (this.editor === null) return const btn = document.querySelector('#editor-node .controls button') btn.disabled = true const buttonPressed = Date.now() const nodeData = this.editor.data() fetch(`/nodes/update/${this.currentNodeID}`, { method: 'POST', body: JSON.stringify(nodeData), }) .then(data => data.json()) .then(json => { if (!json.OK) { showError(json.Error) return } const timePassed = Date.now() - buttonPressed if (timePassed < 250) setTimeout(()=>btn.disabled = false, 250 - timePassed) else btn.disabled = false }) }// }}} } export class TreeNode { constructor(parent, data) {// {{{ this.data = data this.parent = parent this.childrenFetched = false this.children = null this.sortChildren() }// }}} render() {// {{{ const nodeHTML = `
${this.name()}
` const tmpl = document.createElement('template') tmpl.innerHTML = nodeHTML this.children = tmpl.content.querySelector('.children') tmpl.content.querySelector('.name').addEventListener('click', () => mbus.dispatch('NODE_SELECTED', this.data.ID)) // data.NumChildren is set regardless of having fetched the children or not. if (this.hasChildren()) { const img = tmpl.content.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 tmpl.content.querySelector('.expand-status').classList.add('leaf') if (this.data.TypeIcon) { const img = tmpl.content.querySelector('.type-icon img') img.setAttribute('src', `/images/${_VERSION}/node_modules/@mdi/svg/svg/${this.data.TypeIcon}.svg`) } this.parent.appendChild(tmpl.content) for (const c of this.data.Children || []) { (new TreeNode(this.children, c)).render() } }// }}} name() {// {{{ if (this.data.TypeName === 'root_node') return 'Start' return this.data.Name }// }}} hasChildren() {// {{{ return this.data.NumChildren > 0 }// }}} sortChildren() {// {{{ this.data.Children.sort((a, b) => { if (a.TypeName < b.TypeName) return -1 if (a.TypeName > b.TypeName) return 1 if (a.Name < b.Name) return -1 if (a.Name > b.Name) return 1 return 0 }) }// }}} toggleExpand(event) {// {{{ const node = event.target.closest('.node') node?.classList.toggle('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`) if (!this.childrenFetched && this.data.NumChildren > 0 && this.data.Children.length == 0) { this.fetchChildren() .then(data => { this.childrenFetched = true this.data.Children = data.Children this.sortChildren() for (const nodeData of this.data.Children) { const node = new TreeNode(this.children, nodeData) node.render() } }) .catch(err => { alert(err) console.error(err) }) } }// }}} async fetchChildren() {// {{{ return new Promise((resolve, reject) => { fetch(`/nodes/tree/${this.data.ID}?depth=2`) .then(data => data.json()) .then(json => { if (json.OK) resolve(json.Nodes) else reject(json.Error) }) .catch(err => reject(err)) }) }// }}} } export class TypesList { constructur() {// {{{ this.types = [] }// }}} 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)) }) }// }}} 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 }) 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('div') group.classList.add('group') group.innerText = t.Schema['x-group'] div.appendChild(group) } const tDiv = document.createElement('div') tDiv.classList.add('type') tDiv.innerHTML = `
${t.Schema.title || t.Name}
` div.appendChild(tDiv) } return div }// }}} } // vim: foldmethod=marker