import { MessageBus } from '@mbus' export class Application { constructor(records) {// {{{ window._mbus = new MessageBus() this.settings = new Settings() this.records = this.parseRecords(records) this.topFolder = new Folder(this, 'root') this.recordsTree = null this.settingsIcon = null this.renderFolders() this.render() }// }}} parseRecords(recordsData) {// {{{ const records = recordsData.map(d => new Record(d)) return records }// }}} // cleanFolders removes all records from all folders. // renderFolders can then put moved records in the correct // (or newly created) folders again when record names are updated. cleanFolders(folder) {// {{{ if (folder === undefined) folder = this.topFolder folder.records = [] folder.subfolders.forEach((folder, _label) => { this.cleanFolders(folder) }) }// }}} renderFolders() {// {{{ this.records.sort(this.sortRecords) // rec: for example www.google.com for (const rec of this.records) { // com.google (reverse and remove wwww) const labels = rec.labels().reverse().slice(0, -1) // Start each record from the top and iterate through all its labels // except the first one since that would be the actual record. let currFolder = this.topFolder let accFolderLabels = [] for (const i in labels) { const label = labels[i] // The accumulated name is used to create each folder as the record progresses. accFolderLabels.push(label) const accFolderName = accFolderLabels.map(v => v).reverse().join('.') // A new folder is created only when it doesn't exist // to be able to update them when necessary. let folder = currFolder.subfolders.get(label) if (folder === undefined) { folder = new Folder(this, accFolderName) currFolder.subfolders.set(label, folder) } currFolder = folder } // Add the record to the innermost folder currFolder.addRecord(rec) } }// }}} sortFolders(a, b) {// {{{ const aLabels = a.labels().reverse() const bLabels = b.labels().reverse() for (let i = 0; i < aLabels.length && i < bLabels.length; i++) { if (aLabels[i] < bLabels[i]) return -1 if (aLabels[i] > bLabels[i]) return 1 } if (a.length < b.length) return 1 if (a.length > b.length) return -1 return 0 }// }}} sortRecords(a, b) {// {{{ const aLabels = a.labels().reverse() const bLabels = b.labels().reverse() for (let i = 0; i < aLabels.length && i < bLabels.length; i++) { if (aLabels[i] < bLabels[i]) return -1 if (aLabels[i] > bLabels[i]) return 1 } return 0 }// }}} render() {// {{{ if (this.recordsTree == null) { this.recordsTree = document.createElement('div') this.recordsTree.id = 'records-tree' this.settingsIcon = document.createElement('img') this.settingsIcon.id = 'settings-icon' this.settingsIcon.src = `/images/${_VERSION}/icon_settings.svg` this.settingsIcon.addEventListener('click', () => new SettingsDialog(this).show()) document.body.appendChild(this.recordsTree) document.body.appendChild(this.settingsIcon) } //this.recordsTree.replaceChildren() // Top root folder doesn't have to be shown. const folders = Array.from(this.topFolder.subfolders.values()) folders.sort(this.sortFolders) for (const folder of folders) this.recordsTree.append(folder.toElement()) // Subscribe to settings update since the elements they will change // exists now. _mbus.subscribe('settings_updated', event => this.handlerSettingsUpdated(event.detail)) this.setBoxedFolders(this.settings.get('boxed_folders')) }// }}} handlerTop(event) {// {{{ const topEl = event.target.closest('.top') let records, types, values if (topEl.classList.contains('open')) { records = document.querySelectorAll(`[data-top$="${topEl.dataset.self}"]`) types = document.querySelectorAll(`[data-top$="${topEl.dataset.self}"] + .type`) values = document.querySelectorAll(`[data-top$="${topEl.dataset.self}"] + .type + .value`) for (const r of records) { r.classList.remove('show') r.classList.remove('open') } for (const r of types) r.classList.remove('show') for (const r of values) r.classList.remove('show') topEl.classList.remove('open') } else { if (event.shiftKey) { records = document.querySelectorAll(`[data-top$="${topEl.dataset.self}"]`) types = document.querySelectorAll(`[data-top$="${topEl.dataset.self}"] + .type`) values = document.querySelectorAll(`[data-top$="${topEl.dataset.self}"] + .type + .value`) } else { records = document.querySelectorAll(`[data-top="${topEl.dataset.self}"]`) types = document.querySelectorAll(`[data-top="${topEl.dataset.self}"] + .type`) values = document.querySelectorAll(`[data-top="${topEl.dataset.self}"] + .type + .value`) console.log(records) } for (const r of records) { r.classList.add('show') if (event.shiftKey && r.classList.contains('top')) r.classList.add('open') } for (const r of types) r.classList.add('show') for (const r of values) r.classList.add('show') topEl.classList.add('open') } }// }}} handlerSettingsUpdated({ key, value }) {// {{{ if (key == 'boxed_folders') { this.setBoxedFolders(value) } }// }}} setBoxedFolders(state) {// {{{ if (state) document.body.classList.add('boxed-folders') else document.body.classList.remove('boxed-folders') }// }}} } class Folder { constructor(app, name) {// {{{ this.application = app this.open = false this.folderName = name this.subfolders = new Map() this.records = [] this.div = null this.divSubfolders = null this.divRecords = null }// }}} name() {// {{{ return this.folderName.toLowerCase() }// }}} labels() {// {{{ return this.name().split('.') }// }}} addRecord(rec) {// {{{ this.records.push(rec) }// }}} openFolder(recursive) {// {{{ this.open = true this.div.classList.add('open') this.div.classList.remove('closed') if (recursive) this.subfolders.forEach(folder => folder.openFolder(recursive)) }// }}} closeFolder(recursive) {// {{{ this.open = false this.div.classList.remove('open') this.div.classList.add('closed') if (recursive) this.subfolders.forEach(folder => folder.closeFolder(recursive)) }// }}} toggleFolder(event) {// {{{ event.stopPropagation() if (this.open) this.closeFolder(event.shiftKey) else this.openFolder(event.shiftKey) }// }}} toElement() {// {{{ if (this.div === null) { this.div = document.createElement('div') this.div.classList.add('folder') this.div.classList.add(this.open ? 'open' : 'closed') if (this.labels().length == 1) this.div.classList.add('top-most') this.div.dataset.top = this.labels().slice(1).join('.') this.div.dataset.self = this.name() const firstLabel = this.labels()[0] const restLabels = this.labels().slice(1).join('.') this.div.innerHTML = `