const tabs = require('modulekit-tabs') const yaml = require('js-yaml') const md5 = require('md5') const OverpassLayer = require('overpass-layer') const jsonMultilineStrings = require('json-multiline-strings') const Window = require('./Window') const OpenStreetBrowserLoader = require('./OpenStreetBrowserLoader') const editors = [] class CustomCategoryRepository { constructor () { this.clearCache() } load (callback) { callback(null) } clearCache () { this.cache = {} } listCategories (options, callback) { fetch('customCategory.php?action=list') .then(res => res.json()) .then(result => callback(null, result)) } getCategory (id, options, callback) { if (id in this.cache) { return callback(null, yaml.load(this.cache[id]), this.cache[id]) } fetch('customCategory.php?id=' + id) .then(res => res.text()) .then(content => { let data this.cache[id] = content try { data = yaml.load(content) } catch (e) { return global.alert(e) } if (data && typeof data !== 'object') { callback(new Error('Data can not be parsed into an object')) } if (!data.name) { data.name = 'Custom ' + id.substr(0, 6) } callback(null, data, content) }) } saveCategory (body, options, callback) { const id = md5(body) this.cache[id] = body fetch('customCategory.php?action=save', { method: 'POST', body }) } getTemplate (id, options, callback) { callback(null, '') } } class CustomCategoryEditor { constructor (repository) { this.repository = repository editors.push(this) } load (id, callback) { this.repository.getCategory(id, {}, (err, category, content) => { this.content = content callback(err, content) }) } edit () { if (this.window) { this.window.focused = true return } this.window = new Window({ title: 'Custom Category' }) this.window.on('close', () => { this.window = null }) this.textarea = document.createElement('textarea') this.textarea.spellcheck = false this.window.content.appendChild(this.textarea) if (this.content !== undefined) { this.textarea.value = this.content } const controls = document.createElement('div') controls.className = 'controls' this.window.content.appendChild(controls) const input = document.createElement('input') input.type = 'submit' input.value = lang('apply') controls.appendChild(input) const tutorial = document.createElement('span') tutorial.className = 'tip-tutorial' let text = lang('tip-tutorial') text = text.replace('[', '') text = text.replace(']', '') tutorial.innerHTML = text controls.appendChild(tutorial) input.onclick = (e) => { const err = customCategoryTest(this.textarea.value) if (err) { return global.alert(err) } this.applyContent(this.textarea.value) e.preventDefault() } this.window.show() } applyContent (content) { this.content = content this.repository.saveCategory(this.content, {}, () => {}) if (this.textarea) { this.textarea.value = content } const id = md5(content) this.id = id if (this.category) { this.category.remove() this.category = null } OpenStreetBrowserLoader.getCategory('custom/' + id, {}, (err, category) => { if (err) { return global.alert(err) } this.category = category this.category.setParentDom(document.getElementById('contentListAddCategories')) this.category.open() }) } } function editCustomCategory (id, category) { let done = editors.filter(editor => { if (editor.id === id) { editor.edit() return true } }) if (!done.length) { const editor = new CustomCategoryEditor(repository) editor.load(id, (err) => { if (err) { return global.alert(err) } editor.category = category editor.edit() }) } } hooks.register('browser-more-categories', (browser, parameters) => { const content = browser.dom if (!Object.keys(parameters).length) { let block = document.createElement('div') block.setAttribute('weight', 0) content.appendChild(block) let header = document.createElement('h4') header.innerHTML = lang('customCategory:header') block.appendChild(header) let ul = document.createElement('ul') let li = document.createElement('li') let a = document.createElement('a') a.innerHTML = lang('customCategory:create') a.href = '#' a.onclick = (e) => { const editor = new CustomCategoryEditor(repository) editor.edit() browser.close() e.preventDefault() } li.appendChild(a) ul.appendChild(li) li = document.createElement('li') a = document.createElement('a') a.innerHTML = lang('customCategory:list') a.href = '#more-categories?custom=list' li.appendChild(a) ul.appendChild(li) block.appendChild(ul) browser.catchLinks() } else if (parameters.custom === 'list') { customCategoriesList(browser, parameters) } }) function customCategoriesList (browser, options) { browser.dom.innerHTML = ' ' + lang('loading') repository.listCategories({}, (err, result) => { browser.dom.innerHTML = '' const ul = document.createElement('ul') browser.dom.appendChild(ul) result.forEach(cat => { const li = document.createElement('li') const a = document.createElement('a') a.href = '#categories=custom/' + cat.id a.appendChild(document.createTextNode(cat.name)) li.appendChild(a) const edit = document.createElement('a') edit.onclick = (e) => { editCustomCategory(cat.id) e.preventDefault() } edit.innerHTML = ' ' li.appendChild(edit) ul.appendChild(li) }) browser.catchLinks() }) } const repository = new CustomCategoryRepository() hooks.register('init', () => { OpenStreetBrowserLoader.registerRepository('custom', repository) }) hooks.register('category-overpass-init', (category) => { const m = category.id.match(/^custom\/(.*)$/) if (m) { const id = m[1] if (category.tabEdit) { category.tools.remove(this.category.tabEdit) } category.tabEdit = new tabs.Tab({ id: 'edit', weight: 9 }) category.tools.add(category.tabEdit) category.tabEdit.header.innerHTML = '' category.tabEdit.header.title = lang('edit') category.tabEdit.on('select', () => { category.tabEdit.unselect() editCustomCategory(id, category) }) if (!category.tabShare) { const url = location.origin + location.pathname + '#categories=custom/' + id category.tabShare = new tabs.Tab({ id: 'share', weight: 10 }) category.tools.add(category.tabShare) category.shareLink = document.createElement('a') category.shareLink.href = url category.shareLink.innerHTML = '' category.tabShare.header.appendChild(category.shareLink) category.tabShare.header.className = 'share-button' category.tabShare.on('select', () => { category.tabShare.unselect() navigator.clipboard.writeText(url) const notify = document.createElement('div') notify.className = 'notify' notify.innerHTML = lang('copied-clipboard') category.tabShare.header.appendChild(notify) global.setTimeout(() => category.tabShare.header.removeChild(notify), 2000) }) } } else { if (category.tabClone) { category.tools.remove(this.category.tabClone) } category.tabClone = new tabs.Tab({ id: 'clone', weight: 9 }) category.tools.add(category.tabClone) category.tabClone.header.innerHTML = '' category.tabClone.header.title = lang('customCategory:clone') category.tabClone.on('select', () => { category.tabClone.unselect() const clone = new CustomCategoryEditor(repository) clone.edit() category.repository.file_get_contents(category.data.fileName, {}, (err, content) => { if (category.data.format === 'json') { content = JSON.parse(content) content = jsonMultilineStrings.join(content, { exclude: [ [ 'const' ], [ 'filter' ] ] }) content = yaml.dump(content, { lineWidth: 9999 }) } clone.applyContent(content) category.close() } ) }) } }) function customCategoryTest (value) { if (!value) { return new Error('Empty category') } let data try { data = yaml.load(value) } catch (e) { return e } if (!data || typeof data !== 'object') { return new Error('Data can not be parsed into an object') } const fields = ['feature', 'memberFeature'] for (let i1 = 0; i1 < fields.length; i1++) { const k1 = fields[i1] if (data[k1]) { for (k2 in data[k1]) { const err = customCategoryTestCompile(data[k1][k2]) if (err) { return new Error('Compiling /' + k1 + '/' + k2 + ': ' + err.message) } if (k2 === 'style' || k2.match(/^style:/)) { for (const k3 in data[k1][k2]) { const err = customCategoryTestCompile(data[k1][k2][k3]) if (err) { return new Error('Compiling /' + k1 + '/' + k2 + '/' + k3 + ': ' + err.message) } } } } } } } function customCategoryTestCompile (data) { if (typeof data !== 'string' || data.search('{') === -1) { return } try { OverpassLayer.twig.twig({ data }) } catch (e) { return e } }