import { h, Component, createRef } from 'preact' import { signal } from 'preact/signals' import htm from 'htm' import { Node, NodeUI } from 'node' import { ROOT_NODE } from 'node_store' const html = htm.bind(h) export class Notes2 extends Component { constructor() {//{{{ super() this.nodeUI = createRef() this.state = { startNode: null, } Sync.nodes().then(durationNodes => console.log(`Total time: ${Math.round(100 * durationNodes) / 100}s`) ) this.getStartNode() }//}}} render(_props, { startNode }) {//{{{ if (startNode === null) return return html` <${Tree} app=${this} startNode=${startNode} />
<${NodeUI} app=${this} ref=${this.nodeUI} startNode=${startNode} />
` }//}}} getStartNode() {//{{{ let nodeUUID = ROOT_NODE // Is a UUID provided on the URI as an anchor? const parts = document.URL.split('#') if (parts[1]?.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) nodeUUID = parts[1] nodeStore.get(nodeUUID).then(node => { this.setState({ startNode: node }) }) }//}}} async goToNode(nodeUUID, dontPush) {//{{{ // Don't switch notes until saved. if (this.nodeUI.current.nodeModified.value) { if (!confirm("Changes not saved. Do you want to discard changes?")) return } if (!dontPush) history.pushState({ nodeUUID }, '', `/notes2#${nodeUUID}`) // New node is fetched in order to retrieve content and files. // Such data is unnecessary to transfer for tree/navigational purposes. const node = nodeStore.node(nodeUUID) const ancestors = await nodeStore.getNodeAncestry(node) this.nodeUI.current.setNode(node) this.nodeUI.current.setCrumbs(ancestors) this.tree.setSelected(node) }//}}} logout() {//{{{ localStorage.removeItem('session.UUID') location.href = '/' }//}}} } class Tree extends Component { constructor(props) {//{{{ super(props) this.treeNodeComponents = {} this.treeTrunk = [] this.selectedNode = null this.expandedNodes = {} // keyed on UUID this.props.app.tree = this this.populateFirstLevel() }//}}} render({ app }) {//{{{ const renderedTreeTrunk = this.treeTrunk.map(node => { this.treeNodeComponents[node.UUID] = createRef() return html`<${TreeNode} key=${`treenode_${node.UUID}`} tree=${this} node=${node} ref=${this.treeNodeComponents[node.UUID]} selected=${node.UUID === app.state.startNode?.UUID} />` }) return html`
${renderedTreeTrunk}
` }//}}} populateFirstLevel(callback = null) {//{{{ nodeStore.getTreeNodes('', 0) .then(async res => { res.sort(Node.sort) this.treeNodeComponents = {} this.treeTrunk = [] for (const node of res) { // The root node isn't supposed to be shown in the tree. if (node.UUID === '00000000-0000-0000-0000-000000000000') continue if (node.ParentUUID === '') this.treeTrunk.push(node) } this.forceUpdate() if (callback) callback() }) .catch(e => { console.log(e); console.log(e.type, e.error); alert(e.error) }) }//}}} setSelected(node) {//{{{ // The previously selected node, if any, needs to be rerendered // to not retain its 'selected' class. const prevUUID = this.selectedNode?.UUID this.selectedNode = node if (prevUUID) this.treeNodeComponents[prevUUID]?.current.forceUpdate() // And now the newly selected node is rerendered. this.treeNodeComponents[node.UUID]?.current.forceUpdate() // Expanding selected nodes... I don't know... this.setNodeExpanded(node.UUID, true) }//}}} isSelected(node) {//{{{ return this.selectedNode?.UUID === node.UUID }//}}} async expandToTrunk(node) {//{{{ // Get all ancestors from a certain node up to the highest grandparent. const ancestry = await nodeStore.getNodeAncestry(node, []) for (const i in ancestry) { await nodeStore.node(ancestry[i].UUID).fetchChildren() this.setNodeExpanded(ancestry[i].UUID, true) } // Already a top node, no need to expand anything. if (ancestry.length === 0) return // Start the chain of by expanding the top node. this.setNodeExpanded(ancestry[ancestry.length-1].UUID, true) }//}}} getNodeExpanded(UUID) {//{{{ if (this.expandedNodes[UUID] === undefined) this.expandedNodes[UUID] = signal(false) return this.expandedNodes[UUID].value }//}}} setNodeExpanded(UUID, value) {//{{{ // Creating a default value if it doesn't exist already. this.getNodeExpanded(UUID) this.expandedNodes[UUID].value = value }//}}} } class TreeNode extends Component { constructor(props) {//{{{ super(props) this.children_populated = signal(false) if (this.props.node.Level === 0 || this.props.tree.getNodeExpanded(this.props.node.UUID)) this.fetchChildren() }//}}} render({ tree, node, parent }) {//{{{ // Fetch the next level of children if the parent tree node is expanded and our children thus will be visible. const selected = tree.isSelected(node) ? 'selected' : '' if (!this.children_populated.value && tree.getNodeExpanded(parent?.props.node.UUID)) this.fetchChildren() const children = node.Children.map(node => { tree.treeNodeComponents[node.UUID] = createRef() return html`<${TreeNode} key=${`treenode_${node.UUID}`} tree=${tree} node=${node} parent=${this} ref=${tree.treeNodeComponents[node.UUID]} selected=${node.UUID === tree.props.app.startNode?.UUID} />` }) let expandImg = '' if (node.Children.length === 0) expandImg = html`` else { if (tree.getNodeExpanded(node.UUID)) expandImg = html`` else expandImg = html`` } return html`
{ tree.setNodeExpanded(node.UUID, !tree.getNodeExpanded(node.UUID)) }}>${expandImg}
window._notes2.current.goToNode(node.UUID)}>${node.get('Name')}
${children}
` }//}}} async fetchChildren() {//{{{ await this.props.node.fetchChildren() this.children_populated.value = true }//}}} } // vim: foldmethod=marker