Initial use of simple message bus

This commit is contained in:
Magnus Åhall 2025-06-15 12:13:00 +02:00
parent b36ca0d635
commit 23307d7967
7 changed files with 189 additions and 6 deletions

View file

@ -233,3 +233,32 @@ html {
grid-area: blank; grid-area: blank;
height: 32px; height: 32px;
} }
dialog.op::backdrop {
background: rgba(0, 0, 0, 0.5);
}
dialog.op .header {
font-weight: bold;
margin-top: 16px;
}
dialog.op .header:first-child {
margin-top: 0px;
}
#op-search .results {
display: grid;
grid-template-columns: min-content min-content;
grid-gap: 6px 16px;
}
#op-search .results div {
white-space: nowrap;
}
#op-search .results .ancestors {
display: flex;
}
#op-search .results .ancestors .ancestor::after {
content: ">";
margin: 0px 8px;
color: #a00;
}
#op-search .results .ancestors .ancestor:last-child::after {
content: "";
}

View file

@ -1 +0,0 @@
foo

17
static/js/mbus.mjs Normal file
View file

@ -0,0 +1,17 @@
export class MessageBus {
constructor() {
this.bus = new EventTarget()
}
subscribe(eventName, fn) {
this.bus.addEventListener(eventName, fn)
}
unsubscribe(eventName, fn) {
this.bus.removeEventListener(eventName, fn)
}
dispatch(eventName, data) {
this.bus.dispatchEvent(new CustomEvent(eventName, { detail: data }))
}
}

View file

@ -215,6 +215,10 @@ export class NodeUI extends Component {
else else
document.getElementById('tree').focus() document.getElementById('tree').focus()
break break
case 'F':
_mbus.dispatch('op-search')
break
/* /*
case 'C': case 'C':
this.showPage('node') this.showPage('node')

View file

@ -13,10 +13,13 @@ export class Notes2 extends Component {
this.state = { this.state = {
startNode: null, startNode: null,
} }
this.op = signal('')
window._sync = new Sync() window._sync = new Sync()
window._sync.run() window._sync.run()
new OpSearch()
this.getStartNode() this.getStartNode()
}//}}} }//}}}
render(_props, { startNode }) {//{{{ render(_props, { startNode }) {//{{{
@ -27,6 +30,15 @@ export class Notes2 extends Component {
if (startNode === null) if (startNode === null)
return return
/*
let op = null
switch(this.op.value) {
case 'search':
op = html`<${OpSearch} />`
break
}
*/
return html` return html`
<${Tree} app=${this} key=${treeKey} startNode=${startNode} /> <${Tree} app=${this} key=${treeKey} startNode=${startNode} />
<${NodeUI} app=${this} ref=${this.nodeUI} startNode=${startNode} /> <${NodeUI} app=${this} ref=${this.nodeUI} startNode=${startNode} />
@ -99,7 +111,7 @@ class Tree extends Component {
<div id="tree" ref=${this.treeDiv} tabindex="0"> <div id="tree" ref=${this.treeDiv} tabindex="0">
<div id="logo" onclick=${() => _notes2.current.goToNode(ROOT_NODE)}><img src="/images/${_VERSION}/logo.svg" /></div> <div id="logo" onclick=${() => _notes2.current.goToNode(ROOT_NODE)}><img src="/images/${_VERSION}/logo.svg" /></div>
<div class="icons"> <div class="icons">
<img src="/images/${_VERSION}/icon_search.svg" style="height: 22px" onclick=${()=>_sync.run()} /> <img src="/images/${_VERSION}/icon_search.svg" style="height: 22px" onclick=${() => _mbus.dispatch('op-search')} />
<img src="/images/${_VERSION}/icon_refresh.svg" onclick=${() => _sync.run()} /> <img src="/images/${_VERSION}/icon_refresh.svg" onclick=${() => _sync.run()} />
</div> </div>
${renderedTreeTrunk} ${renderedTreeTrunk}
@ -468,4 +480,79 @@ class TreeNode extends Component {
}//}}} }//}}}
} }
class Op {
constructor(id) {
this.id = id
_mbus.subscribe(this.id, p => this.render(p))
}
render(html) {
const op = document.getElementById('op')
const t = document.createElement('template')
t.innerHTML = `<dialog id="${this.id}" class="op">${html}</dialog>`
op.replaceChildren(t.content)
document.getElementById(this.id).showModal()
}
get(selector) {
return document.querySelector(`#${this.id} ${selector}`)
}
bind(selector, event, fn) {
this.get(selector).addEventListener(event, evt => fn(evt))
}
}
function tmpl(html) {
const el = document.createElement('template')
el.innerHTML = html
return el.content.children
}
class OpSearch extends Op {
constructor() {
super('op-search')
}
render() {
super.render(`
<div class="header">Search</div>
<div>
<input type="text" />
</div>
<div class="header">Results</div>
<div class="results"></div>
`)
this.bind('input[type="text"]', 'keydown', evt => this.search(evt))
}
search(event) {
if (event.key !== 'Enter')
return
const searchFor = document.querySelector('#op-search input').value
nodeStore.search(searchFor, ROOT_NODE)
.then(res => this.displayResults(res))
}
displayResults(results) {
const rs = []
for (const r of results) {
const ancestors = r.ancestry.reverse().map(a => {
const div = tmpl(`<div class="ancestor">${a.data.Name}</div>`)
div[0].addEventListener('click', ()=>_notes2.current.goToNode(a.UUID))
return div[0]
})
const div = tmpl(`<div>${r.name}</div>`)
div[0].addEventListener('click', ()=>_notes2.current.goToNode(r.uuid))
rs.push(...div)
const ancDev = tmpl('<div class="ancestors"></div>')
ancDev[0].append(...ancestors)
rs.push(ancDev[0])
}
this.get('.results').replaceChildren(...rs)
}
}
// vim: foldmethod=marker // vim: foldmethod=marker

View file

@ -305,3 +305,46 @@ html {
grid-area: blank; grid-area: blank;
height: 32px; height: 32px;
} }
dialog.op {
&::backdrop {
background: rgba(0, 0, 0, 0.5);
}
.header {
font-weight: bold;
margin-top: 16px;
&:first-child {
margin-top: 0px;
}
}
}
#op-search {
.results {
display: grid;
grid-template-columns: min-content min-content;
grid-gap: 6px 16px;
div {
white-space: nowrap;
}
.ancestors {
display: flex;
.ancestor::after {
content: ">";
margin: 0px 8px;
color: #a00;
}
.ancestor:last-child::after {
content: "";
}
}
}
}

View file

@ -10,6 +10,10 @@
if (navigator.serviceWorker) if (navigator.serviceWorker)
navigator.serviceWorker.register('/service_worker.js') navigator.serviceWorker.register('/service_worker.js')
</script> </script>
<script type="module" defer>
import { MessageBus } from '/js/{{ .VERSION }}/mbus.mjs'
window._mbus = new MessageBus()
</script>
<script type="importmap"> <script type="importmap">
{ {
"imports": { "imports": {
@ -42,7 +46,7 @@
<script type="text/javascript" src="/js/{{ .VERSION }}/lib/fullcalendar.min.js"></script> <script type="text/javascript" src="/js/{{ .VERSION }}/lib/fullcalendar.min.js"></script>
</head> </head>
<body> <body>
<div id="app">{{ block "page" . }}{{ end }}</div> <div id="app">{{ block "page" . }}{{ end }}</div>
<div id="op"></div>
</body> </body>
</html> </html>