Browse Source

Merge branch '3-permalink'

master
parent
commit
cde9a257ff
  1. 6
      README.md
  2. 2
      package.json
  3. 4
      src/CategoryBase.js
  4. 55
      src/CategoryIndex.js
  5. 30
      src/CategoryOverpass.js
  6. 14
      src/OpenStreetBrowserLoader.js
  7. 20
      src/categories.js
  8. 83
      src/index.js
  9. 2
      src/language.js
  10. 11
      src/location.js
  11. 166
      src/state.js

6
README.md

@ -110,3 +110,9 @@ The following values are possible for categories (the only mandatory value is qu
* const: an object variable which is available as prefix in twig functions.
All values in the "feature" section may use the [TwigJS language](doc/TwigJS.md) for evaluation.
### Hooks
With the function `register_hook` you can hook into several functions. The following hooks are available:
* `state-get`: modules can add values into the current state. Parameters: `state`: an object, which can be modified by modules.
* `state-apply`: when a state is applied to the app. Parameters: `state`: state which should be applied.

2
package.json

@ -7,6 +7,7 @@
"author": "Stephan Bösch-Plepelits <skunk@xover.mud.at>",
"license": "GPL-3.0",
"dependencies": {
"async": "^2.5.0",
"font-awesome": "^4.7.0",
"i18next-client": "^1.11.4",
"ip-location": "^1.0.1",
@ -18,6 +19,7 @@
"openstreetbrowser-categories-main": "https://github.com/plepe/openstreetbrowser-categories-main",
"openstreetmap-tag-translations": "https://github.com/plepe/openstreetmap-tag-translations",
"overpass-layer": "https://github.com/plepe/overpass-layer",
"query-string": "^5.0.0",
"sheet-router": "^4.2.3"
},
"scripts": {

4
src/CategoryBase.js

@ -87,8 +87,8 @@ CategoryBase.prototype.open = function () {
if (this.isOpen)
return
if (this.parent) {
this.parent.open()
if (this.parentCategory) {
this.parentCategory.open()
}
if (typeof this.parentDom === 'string') {

55
src/CategoryIndex.js

@ -1,3 +1,4 @@
var async = require('async')
var OpenStreetBrowserLoader = require('./OpenStreetBrowserLoader')
var CategoryBase = require('./CategoryBase')
@ -8,6 +9,10 @@ function CategoryIndex (id, data) {
this.childrenDoms = {}
this.childrenCategories = null
this._loadChildrenCategories(function (err) {
})
}
CategoryIndex.prototype.open = function () {
@ -20,24 +25,6 @@ CategoryIndex.prototype.open = function () {
this.isOpen = true
return
}
this.childrenCategories = {}
for (var i = 0; i < this.data.subCategories.length; i++) {
var data = this.data.subCategories[i]
var childDom = document.createElement('div')
childDom.className = 'categoryWrapper'
this.domContent.appendChild(childDom)
this.childrenDoms[data.id] = childDom
this.childrenCategories[data.id] = null
if ('type' in data) {
OpenStreetBrowserLoader.getCategoryFromData(data.id, data, this._loadChildCategory.bind(this))
} else {
OpenStreetBrowserLoader.getCategory(data.id, this._loadChildCategory.bind(this))
}
}
}
CategoryIndex.prototype.recalc = function () {
@ -48,15 +35,43 @@ CategoryIndex.prototype.recalc = function () {
}
}
CategoryIndex.prototype._loadChildCategory = function (err, category) {
CategoryIndex.prototype._loadChildrenCategories = function (callback) {
this.childrenCategories = {}
async.forEach(this.data.subCategories,
function (data, callback) {
var childDom = document.createElement('div')
childDom.className = 'categoryWrapper'
this.domContent.appendChild(childDom)
this.childrenDoms[data.id] = childDom
this.childrenCategories[data.id] = null
if ('type' in data) {
OpenStreetBrowserLoader.getCategoryFromData(data.id, data, this._loadChildCategory.bind(this, callback))
} else {
OpenStreetBrowserLoader.getCategory(data.id, this._loadChildCategory.bind(this, callback))
}
}.bind(this),
function (err) {
if (callback) {
callback(err)
}
}
)
}
CategoryIndex.prototype._loadChildCategory = function (callback, err, category) {
if (err) {
return
return callback(err)
}
this.childrenCategories[category.id] = category
category.setParent(this)
category.setParentDom(this.childrenDoms[category.id])
callback(err, category)
}
CategoryIndex.prototype.close = function () {

30
src/CategoryOverpass.js

@ -2,6 +2,7 @@ var OpenStreetBrowserLoader = require('./OpenStreetBrowserLoader')
var OverpassLayer = require('overpass-layer')
var OverpassLayerList = require('overpass-layer').List
var CategoryBase = require('./CategoryBase')
var state = require('./state')
var defaultValues = {
feature: {
title: "{{ localizedTag(tags, 'name') |default(localizedTag(tags, 'operator')) | default(localizedTag(tags, 'ref')) | default(trans('unnamed')) }}",
@ -107,6 +108,31 @@ function CategoryOverpass (id, data) {
this.domStatus.className = 'status'
this.dom.appendChild(this.domStatus)
register_hook('state-get', function (state) {
if (this.isOpen) {
if (state.categories) {
state.categories += ','
} else {
state.categories = ''
}
state.categories += this.id
}
}.bind(this))
register_hook('state-apply', function (state) {
if (!('categories' in state)) {
return
}
var list = state.categories.split(',')
if (list.indexOf(this.id) === -1) {
this.close()
}
// opening categories is handled by src/categories.js
}.bind(this))
}
CategoryOverpass.prototype.load = function (callback) {
@ -156,6 +182,8 @@ CategoryOverpass.prototype.open = function () {
}
this.isOpen = true
state.update()
}
CategoryOverpass.prototype.recalc = function () {
@ -170,6 +198,8 @@ CategoryOverpass.prototype.close = function () {
this.layer.remove()
this.list.remove()
state.update()
}
CategoryOverpass.prototype.get = function (id, callback) {

14
src/OpenStreetBrowserLoader.js

@ -4,6 +4,7 @@ function OpenStreetBrowserLoader () {
this.types = {}
this.categories = {}
this.templates = {}
this._loadClash = {} // if a category is being loaded multiple times, collect callbacks
}
OpenStreetBrowserLoader.prototype.setMap = function (map) {
@ -16,6 +17,13 @@ OpenStreetBrowserLoader.prototype.getCategory = function (id, callback) {
return
}
if (id in this._loadClash) {
this._loadClash[id].push(callback)
return
}
this._loadClash[id] = []
function reqListener (req) {
if (req.status !== 200) {
console.log(req)
@ -30,8 +38,12 @@ OpenStreetBrowserLoader.prototype.getCategory = function (id, callback) {
}
callback(err, category)
}.bind(this))
this._loadClash[id].forEach(function (c) {
c(err, category)
})
delete this._loadClash[id]
}.bind(this))
}
var req = new XMLHttpRequest()

20
src/categories.js

@ -0,0 +1,20 @@
var OpenStreetBrowserLoader = require('./OpenStreetBrowserLoader')
register_hook('state-apply', function (state) {
if (!('categories' in state)) {
return
}
var list = state.categories.split(',')
for (var i = 0; i < list.length; i++) {
OpenStreetBrowserLoader.getCategory(list[i], function (err, category) {
if (category) {
category.open()
if (!category.parentDom) {
category.setParentDom(document.getElementById('contentList'))
}
}
})
}
}.bind(this))

83
src/index.js

@ -4,7 +4,9 @@ var OverpassLayer = require('overpass-layer')
var OverpassLayerList = require('overpass-layer').List
var OverpassFrontend = require('overpass-frontend')
var OpenStreetBrowserLoader = require('./OpenStreetBrowserLoader')
var state = require('./state')
var hash = require('sheet-router/hash')
var queryString = require('query-string')
window.OpenStreetBrowserLoader = OpenStreetBrowserLoader
require('./CategoryIndex')
@ -14,6 +16,8 @@ global.map
window.baseCategory
window.overpassUrl
window.overpassFrontend
window.currentPath = null
var lastPopupClose = 0
// Optional modules
require('./options')
@ -23,15 +27,18 @@ require('./overpassChooser')
require('./fullscreen')
require('./mapLayers')
require('./twigFunctions')
require('./categories')
window.onload = function() {
var initState = {}
map = L.map('map')
call_hooks('init')
call_hooks_callback('init_callback', onload2)
call_hooks_callback('init_callback', initState, onload2.bind(this, initState))
}
function onload2 () {
function onload2 (initState) {
// Add Geo Search
var provider = new LeafletGeoSearch.OpenStreetMapProvider()
var searchControl = new LeafletGeoSearch.GeoSearchControl({
@ -63,6 +70,20 @@ function onload2 () {
OpenStreetBrowserLoader.setMap(map)
var newState
if (location.hash && location.hash.length > 1) {
newState = state.parse(location.hash.substr(1))
} else {
newState = initState
}
// make sure the map has an initial location
if (!('map' in newState)) {
newState.map = initState.map
}
state.apply(newState)
OpenStreetBrowserLoader.getCategory('index', function (err, category) {
if (err) {
alert(err)
@ -79,7 +100,10 @@ function onload2 () {
var url = e.popup.object.layer_id + '/' + e.popup.object.id
if (location.hash.substr(1) !== url && location.hash.substr(1, url.length + 1) !== url + '/' ) {
history.pushState(null, null, '#' + url)
currentPath = url
// only push state, when last popup close happened >1sec earlier
state.update(null, Date.now() - lastPopupClose > 1000)
}
OpenStreetBrowserLoader.getCategory(e.popup.object.layer_id, function (err, category) {
@ -89,44 +113,41 @@ function onload2 () {
}
})
map.on('popupclose', function (e) {
history.pushState(null, null, '#')
lastPopupClose = Date.now()
currentPath = null
state.update(null, true)
hide()
})
map.on('moveend', function (e) {
state.update()
})
if (location.hash && location.hash.length > 1) {
var url = location.hash.substr(1)
hash(function (loc) {
state.apply(state.parse(loc.substr(1)))
})
options = {
showDetails: !!location.hash.match(/\/details$/)
}
state.update()
}
show(url, options, function (err) {
if (err) {
alert(err)
return
}
window.setPath = function (path) {
currentPath = path
call_hooks('show', url, options)
})
if (!path) {
map.closePopup()
return
}
hash(function (loc) {
if (loc.length > 1) {
var url = loc.substr(1)
options = {
showDetails: !!loc.match(/\/details$/)
}
show(url, options, function (err) {
if (err) {
alert(err)
return
}
options = {
showDetails: !!path.match(/\/details$/)
}
call_hooks('show', url, options)
})
show(path, options, function (err) {
if (err) {
alert(err)
return
}
call_hooks('show', path, options)
})
}

2
src/language.js

@ -75,7 +75,7 @@ function langName (code) {
return ret
}
register_hook('init_callback', function (callback) {
register_hook('init_callback', function (initState, callback) {
if ('data_lang' in options) {
tagTranslations.setTagLanguage(options.data_lang)
} else {

11
src/location.js

@ -1,16 +1,17 @@
var ipLocation = require('./ip-location')
register_hook('init_callback', function (callback) {
register_hook('init_callback', function (initState, callback) {
initState.map = ('zoom' in config.defaultView ? config.defaultView.zoom : 14) + '/' + config.defaultView.lat + '/' + config.defaultView.lon
if ('checkIpLocation' in config && !config.checkIpLocation) {
map.setView(config.defaultView, 'zoom' in config.defaultView ? config.defaultView.zoom : 14)
return callback()
}
ipLocation('', function (err, ipLoc) {
var ret
if (typeof ipLoc === 'object' && 'latitude' in ipLoc) {
map.setView([ ipLoc.latitude, ipLoc.longitude ], 14)
} else {
map.setView(config.defaultView, 'zoom' in config.defaultView ? config.defaultView.zoom : 14)
initState.map = '14/' + ipLoc.latitude + '/' + ipLoc.longitude
}
callback()

166
src/state.js

@ -0,0 +1,166 @@
var queryString = require('query-string')
function get () {
var state = {}
// path
if (currentPath) {
state.path = currentPath
}
// location
if (typeof map.getZoom() === 'undefined') {
return
}
var center = map.getCenter()
var zoom = map.getZoom()
state.lat = center.lat
state.lon = center.lng
state.zoom = zoom
// other modules
call_hooks('state-get', state)
// done
return state
}
function apply (state) {
// path
setPath(state.path)
// location
if (state.lat && state.lon && state.zoom) {
if (typeof map.getZoom() === 'undefined') {
map.setView({ lat: state.lat, lng: state.lon }, state.zoom)
} else {
map.flyTo({ lat: state.lat, lng: state.lon }, state.zoom)
}
}
// other modules
call_hooks('state-apply', state)
}
function stringify (state) {
var link = ''
if (!state) {
state = get()
}
var tmpState = JSON.parse(JSON.stringify(state))
// path
if (state.path) {
link += state.path
delete tmpState.path
}
// location
var locPrecision = 5
if (state.zoom) {
locPrecision =
state.zoom > 16 ? 5 :
state.zoom > 8 ? 4 :
state.zoom > 4 ? 3 :
state.zoom > 2 ? 2 :
state.zoom > 1 ? 1 : 0
}
if (state.zoom && state.lat && state.lon) {
link += (link === '' ? '' : '&') + 'map=' +
parseFloat(state.zoom).toFixed(0) + '/' +
state.lat.toFixed(locPrecision) + '/' +
state.lon.toFixed(locPrecision)
delete tmpState.zoom
delete tmpState.lat
delete tmpState.lon
}
var newHash = queryString.stringify(tmpState)
// Characters we dont's want escaped
newHash = newHash.replace(/%2F/g, '/')
newHash = newHash.replace(/%2C/g, ',')
if (newHash !== '') {
link += (link === '' ? '' : '&') + newHash
}
return link
}
function parse (link) {
var firstEquals = link.search('=')
var firstAmp = link.search('&')
var urlNonPathPart = ''
var newState = {}
var newPath = ''
if (link === '') {
// nothing
} else if (firstEquals === -1) {
if (firstAmp === -1) {
newPath = link
} else {
newPath = link.substr(0, firstAmp)
}
} else {
if (firstAmp === -1) {
urlNonPathPart = link
} else if (firstAmp < firstEquals) {
newPath = link.substr(0, firstAmp)
urlNonPathPart = link.substr(firstAmp + 1)
} else {
urlNonPathPart = link
}
}
newState = queryString.parse(urlNonPathPart)
if (newPath !== '') {
newState.path = newPath
}
if ('map' in newState) {
var parts = newState.map.split('/')
newState.zoom = parts[0]
newState.lat = parts[1]
newState.lon = parts[2]
delete newState.map
}
return newState
}
function update (state, push) {
if (!state) {
state = get()
}
var newHash = '#' + stringify(state)
if (push) {
history.pushState(null, null, newHash)
console.log('push', newHash, state)
} else if (location.hash !== newHash && (location.hash !== '' || newHash !== '#')) {
history.replaceState(null, null, newHash)
console.log('replace', newHash, state)
} else {
console.log('ignore', newHash, state)
}
}
module.exports = {
get: get, // get the current app state
apply: apply, // apply a state to the current app
stringify: stringify, // create a link from a state (or the current state)
parse: parse, // parse a state from a link
update: update // update url (either replace or push)
}
Loading…
Cancel
Save