You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
511 lines
12 KiB
511 lines
12 KiB
const tabs = require('modulekit-tabs')
|
|
const yaml = require('js-yaml')
|
|
const md5 = require('md5')
|
|
const OverpassLayer = require('overpass-layer')
|
|
const OverpassFrontendFilter = require('overpass-frontend/src/Filter')
|
|
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) {
|
|
const data = this.parseCategory(id, this.cache[id])
|
|
return callback(null, data, this.cache[id])
|
|
}
|
|
|
|
fetch('customCategory.php?id=' + id)
|
|
.then(res => res.text())
|
|
.then(content => {
|
|
this.cache[id] = content
|
|
|
|
const data = this.parseCategory(id, content)
|
|
|
|
callback(null, data, content)
|
|
})
|
|
}
|
|
|
|
parseCategory (id, content) {
|
|
let data
|
|
|
|
try {
|
|
data = yaml.load(content)
|
|
} catch (e) {
|
|
return global.alert(e)
|
|
}
|
|
|
|
if (data && typeof data !== 'object') {
|
|
return new Error('Data can not be parsed into an object')
|
|
}
|
|
|
|
if (!data.name) {
|
|
data.name = 'Custom ' + id.substr(0, 6)
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
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-keep')
|
|
controls.appendChild(input)
|
|
|
|
const inputClose = document.createElement('input')
|
|
inputClose.type = 'button'
|
|
inputClose.value = lang('apply-close')
|
|
inputClose.onclick = () => {
|
|
if (this.submit()) {
|
|
this.window.close()
|
|
}
|
|
}
|
|
controls.appendChild(inputClose)
|
|
|
|
const icons = document.createElement('div')
|
|
icons.className = 'actions'
|
|
controls.appendChild(icons)
|
|
|
|
this.inputDownload = document.createElement('a')
|
|
this.textarea.onchange = () => this.updateDownload()
|
|
this.updateDownload()
|
|
this.inputDownload.title = lang('download')
|
|
this.inputDownload.innerHTML = '<i class="fas fa-download"></i>'
|
|
icons.appendChild(this.inputDownload)
|
|
|
|
const tutorial = document.createElement('span')
|
|
tutorial.className = 'tip-tutorial'
|
|
let text = lang('tip-tutorial')
|
|
text = text.replace('[', '<a target="_blank" href="https://github.com/plepe/OpenStreetBrowser/blob/master/doc/Tutorial.md">')
|
|
text = text.replace(']', '</a>')
|
|
tutorial.innerHTML = text
|
|
controls.appendChild(tutorial)
|
|
|
|
input.onclick = (e) => {
|
|
this.submit()
|
|
e.preventDefault()
|
|
}
|
|
|
|
this.window.show()
|
|
}
|
|
|
|
submit () {
|
|
const err = customCategoryTest(this.textarea.value)
|
|
if (err) {
|
|
global.alert(err)
|
|
return false
|
|
}
|
|
|
|
this.applyContent(this.textarea.value)
|
|
return true
|
|
}
|
|
|
|
applyContent (content) {
|
|
this.content = content
|
|
this.repository.saveCategory(this.content, {}, () => {})
|
|
|
|
if (this.textarea) {
|
|
this.textarea.value = content
|
|
this.updateDownload()
|
|
}
|
|
|
|
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()
|
|
})
|
|
}
|
|
|
|
updateDownload () {
|
|
const file = new Blob([this.textarea.value], { type: 'application/yaml' })
|
|
this.inputDownload.href = URL.createObjectURL(file)
|
|
this.inputDownload.download = md5(this.textarea.value) + '.yaml'
|
|
}
|
|
}
|
|
|
|
function editCustomCategory (id, category) {
|
|
const 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) {
|
|
const block = document.createElement('div')
|
|
block.setAttribute('weight', 0)
|
|
content.appendChild(block)
|
|
|
|
const header = document.createElement('h4')
|
|
header.innerHTML = lang('customCategory:header')
|
|
block.appendChild(header)
|
|
|
|
const 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 = '<i class="fa fa-spinner fa-pulse fa-fw"></i> ' + 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 = ' <i class="fa fa-pen"></i>'
|
|
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 = '<i class="fa fa-pen"></i>'
|
|
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 = '<i class="fa fa-share-alt"></i>'
|
|
|
|
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 = '<i class="fa fa-clone"></i>'
|
|
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 (err) {
|
|
console.error(err)
|
|
return global.alert(err)
|
|
}
|
|
|
|
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')
|
|
}
|
|
|
|
if (!('query' in data)) {
|
|
return new Error('No "query" defined!')
|
|
}
|
|
|
|
if (typeof data.query === 'string') {
|
|
const r = customCategoryTestQuery(data.query)
|
|
if (r) { return r }
|
|
} else if (data.query === null) {
|
|
return new Error('No "query" defined!')
|
|
} else if (Object.values(data.query).length) {
|
|
for (const z in data.query) {
|
|
const r = customCategoryTestQuery(data.query[z])
|
|
if (r) { return new Error('Query z' + z + ': ' + r) }
|
|
}
|
|
} else {
|
|
return new Error('"query" can not be parsed!')
|
|
}
|
|
|
|
const testPaths = [
|
|
[ /^(memberF|f)eature$/, /./ ],
|
|
[ /^(memberF|f)eature$/, /^style(:.*)$/, /./ ],
|
|
[ 'filter', /./, /(name|query|values|placeholder|valueName)/ ],
|
|
[ 'filter', /./, 'values', /./ , 'name'],
|
|
[ 'info' ]
|
|
]
|
|
|
|
try {
|
|
testPaths.forEach(path => customCategoryTestCompilePath(data, path, ''))
|
|
}
|
|
catch (err) {
|
|
return err
|
|
}
|
|
}
|
|
|
|
function customCategoryTestCompilePath (data, subPaths, path) {
|
|
if (subPaths.length) {
|
|
const p = subPaths[0]
|
|
|
|
for (const k in data) {
|
|
if (typeof p === 'string' ? k === p : k.match(p)) {
|
|
customCategoryTestCompilePath(data[k], subPaths.slice(1), path + '/' + k)
|
|
}
|
|
}
|
|
}
|
|
|
|
const err = customCategoryTestCompile(data)
|
|
if (err) {
|
|
throw new Error('Compiling ' + path + ': ' + err.message)
|
|
}
|
|
}
|
|
|
|
function customCategoryTestCompile (data) {
|
|
if (typeof data !== 'string' || data.search('{') === -1) {
|
|
return
|
|
}
|
|
|
|
let template
|
|
try {
|
|
template = OverpassLayer.twig.twig({ data, rethrow: true })
|
|
} catch (e) {
|
|
return e
|
|
}
|
|
|
|
const fakeOb = {
|
|
id: 'n1',
|
|
sublayer_id: 'main',
|
|
osm_id: 1,
|
|
type: 'node',
|
|
tags: {
|
|
foo: 'bar'
|
|
},
|
|
map: {
|
|
zoom: 15,
|
|
metersPerPixel: 0.8
|
|
}
|
|
}
|
|
|
|
try {
|
|
template.render(fakeOb)
|
|
} catch (e) {
|
|
return e
|
|
}
|
|
}
|
|
|
|
function customCategoryTestQuery (str) {
|
|
if (typeof str !== 'string') {
|
|
return 'Query is not a string!'
|
|
}
|
|
|
|
// make sure the request ends with ';'
|
|
if (!str.match(/;\s*$/)) {
|
|
str += ';'
|
|
}
|
|
|
|
try {
|
|
new OverpassFrontendFilter(str)
|
|
} catch (e) {
|
|
return e
|
|
}
|
|
}
|