Browse Source

Merge branch 'repo2'

master
parent
commit
f36d65c58c
  1. 2
      README.md
  2. 25
      conf.php-dist
  3. 64
      doc/TwigJS.md
  4. 1
      lang/ast.json
  5. 1
      lang/ca.json
  6. 1
      lang/cs.json
  7. 1
      lang/da.json
  8. 3
      lang/de.json
  9. 1
      lang/el.json
  10. 3
      lang/en.json
  11. 1
      lang/es.json
  12. 1
      lang/et.json
  13. 1
      lang/fr.json
  14. 1
      lang/hu.json
  15. 1
      lang/it.json
  16. 1
      lang/ja.json
  17. 1
      lang/nl.json
  18. 1
      lang/pl.json
  19. 1
      lang/ro.json
  20. 1
      lang/ru.json
  21. 1
      lang/sr.json
  22. 1
      lang/template.json
  23. 1
      lang/uk.json
  24. 2
      lib/modulekit/form
  25. 2
      lib/modulekit/lang
  26. 4
      modulekit.php
  27. 3
      package.json
  28. 73
      repo.php
  29. 30
      src/CategoryBase.js
  30. 14
      src/CategoryIndex.js
  31. 20
      src/CategoryOverpass.js
  32. 207
      src/OpenStreetBrowserLoader.js
  33. 43
      src/RepositoryBase.php
  34. 52
      src/RepositoryDir.php
  35. 58
      src/RepositoryGit.php
  36. 3
      src/addCategories.css
  37. 140
      src/addCategories.js
  38. 12
      src/category.css
  39. 17
      src/index.js
  40. 12
      src/markers.js
  41. 70
      src/repositories.php
  42. 15
      style.css

2
README.md

@ -90,7 +90,7 @@ The following values are possible for categories (the only mandatory value is qu
* feature: an object describing how the feature will be formated resp. styled.
* style: a Leaflet style.
* stroke: Whether to draw stroke along the path. Set it to false or empty string to disable borders on polygons or circles. (boolean, true)
* weight: Stroke width in pixels (number, 3)
* width: Stroke width in pixels (number, 3)
* color: Stroke color (string, '#3388ff')
* opacity: Stroke opacity (number, 1.0)
* lineCap: shape at end of the stroke (string, 'round')

25
conf.php-dist

@ -1,10 +1,31 @@
<?php
// Directory from which to read the categories.
$config['categoriesDir'] = 'node_modules/openstreetbrowser-categories-main';
// Repositories from which to read the categories.
// repositoryUrl and categoryUrl are twig templates, which take the following input values:
// {{ repositoryId }} id of the repository
// {{ categoryId }} id of the category (not for repositoryUrl)
$repositories = array(
'default' => array(
'path' => 'node_modules/openstreetbrowser-categories-main',
'type' => 'dir',
// public URL of repository
'repositoryUrl' => 'https://github.com/example/categories',
// public URL of source of a category in repository
'categoryUrl' => 'https://github.com/example/categories/tree/master/{{ categoryId }}.json',
),
);
// Repositories which should be included from gitea
$repositoriesGitea = array(
'path' => "/home/gitea/gitea-repositories",
'url' => "https://www.openstreetbrowser.org/dev",
);
// Set to true to reload categories on every page visit.
$config['categoriesAlwaysReload'] = true;
// (optional) URL, which points to the OpenStreetBrowser Editor
#$config['urlCategoriesEditor'] = 'editor/';
// URL of the Overpass API
$config['overpassUrl'] = array(
'//overpass-api.de/api/interpreter',

64
doc/TwigJS.md

@ -1,17 +1,55 @@
#### Examples
Twig resp. TwigJS is a template language. Example:
```twig
Value of property "test": {{ test }}.
```
If-condition:
```twig
{% if test == "foo" %}
It's foo!
{% elseif test == "bar" %}
It's bar!
{% else %}
Other value: {{ test }}
{% endif %}
```
For-loop:
```twig
{% for k, v in tags %}
Tag {{ k }} has value {{ v }}
{% endfor %}
```
Assign value to variable:
```twig
{% set k = "foo" %}
```
For more information, please visit:
* [https://twig.symfony.com/](Page of the original Twig template language)
* [https://github.com/twigjs/twig.js/wiki](Wiki of the TwigJS template language which is almost identical to Twig)
#### TwigJS templates
The following properties are available:
* id (the id of the object is always available, prefixed 'n' for nodes, 'w' for ways and 'r' for relations; e.g. 'n1234')
* osm_id (the numerical id of the object)
* layer_id (the id of the category)
* type ('node', 'way' or 'relation')
* tags.* (all tags are available with the prefix tags., e.g. tags.amenity)
* meta.timestamp (timestamp of last modification)
* meta.version (version of the object)
* meta.changeset (ID of the changeset, the object was last modified in)
* meta.user (Username of the user, who changed the object last)
* meta.uid (UID of the user, who changed the object last)
* map.zoom (Current zoom level)
* const.* (Values from the 'const' option)
When rendering map features, the following properties are available:
* `id` (the id of the object is always available, prefixed 'n' for nodes, 'w' for ways and 'r' for relations; e.g. 'n1234')
* `osm_id` (the numerical id of the object)
* `layer_id` (the id of the category)
* `type` ('node', 'way' or 'relation')
* `tags.*` (all tags are available with the prefix tags., e.g. tags.amenity)
* `meta.timestamp` (timestamp of last modification)
* `meta.version` (version of the object)
* `meta.changeset` (ID of the changeset, the object was last modified in)
* `meta.user` (Username of the user, who changed the object last)
* `meta.uid` (UID of the user, who changed the object last)
* `map.zoom` (Current zoom level)
* `const.*` (Values from the 'const' option)
For the info-section of a category the following properties are available:
* `layer_id` (the id of the category)
* `map.zoom` (Current zoom level)
* `const.*` (Values from the 'const' option)
There are several extra functions defined for the TwigJS language:
* function `keyTrans`: return the translation of the given key. Parameters: key (required, e.g. 'amenity').

1
lang/ast.json

@ -1,6 +1,7 @@
{
"main:options": "Opciones",
"more": "más",
"more_categories": "Más categoríes",
"options:data_lang": "Llingua de los datos",
"options:data_lang:local": "Llingua llocal",
"options:ui_lang": "Llingua de la interfaz",

1
lang/ca.json

@ -1,5 +1,6 @@
{
"main:options": "Opcions",
"more": "més",
"more_categories": "Més categories",
"save": "Guardar"
}

1
lang/cs.json

@ -1,6 +1,7 @@
{
"main:options": "Nastavení",
"more": "více",
"more_categories": "Více kategorií",
"options:data_lang": "Jazyk dat",
"options:data_lang:local": "Místní jazyk",
"options:ui_lang": "Jazyk rozhraní",

1
lang/da.json

@ -1,6 +1,7 @@
{
"main:options": "Indstillinger",
"more": "mere",
"more_categories": "Flere kategorier",
"options:data_lang": "Data sprog",
"options:data_lang:local": "Lokalt sprog",
"options:ui_lang": "Brugerfladesprog",

3
lang/de.json

@ -1,4 +1,5 @@
{
"back": "zurück",
"category-info-tooltip": "Info & Legende",
"closed": "geschlossen",
"default": "Standard",
@ -10,6 +11,8 @@
"images": "Bilder",
"main:options": "Optionen",
"more": "mehr",
"more_categories": "Mehr Kategorien",
"more_categories_gitea": "Erstelle und verbessere Kategorien hier!",
"open": "geöffnet",
"options:data_lang": "Datensprache",
"options:data_lang:local": "Lokale Sprache",

1
lang/el.json

@ -1,6 +1,7 @@
{
"main:options": "Επιλογές",
"more": "περισσότερα",
"more_categories": "Περισσότερες κατηγορίες",
"options:data_lang": "Γλωσσα δεδομένων",
"options:data_lang:local": "Τοπική γλώσσα",
"options:ui_lang": "Γλώσσα διεπαφής",

3
lang/en.json

@ -1,4 +1,5 @@
{
"back": "back",
"category-info-tooltip": "Info & Map key",
"closed": "closed",
"default": "default",
@ -10,6 +11,8 @@
"images": "Images",
"main:options": "Options",
"more": "more",
"more_categories": "More categories",
"more_categories_gitea": "Create &amp; improve categories yourself!",
"open": "open",
"options:data_lang": "Data language",
"options:data_lang:desc": "Many map features have their name (and other tags) translated to different languages (e.g. with 'name:en', 'name:de'). Specify which language should be used for displaying, or 'Local language' so that always the untranslated value (e.g. 'name') will be used",

1
lang/es.json

@ -1,6 +1,7 @@
{
"main:options": "Opciones",
"more": "más",
"more_categories": "Más categorías",
"options:data_lang": "Idioma de datos",
"options:data_lang:local": "Idioma local",
"options:ui_lang": "Idioma de interfaz",

1
lang/et.json

@ -1,6 +1,7 @@
{
"main:options": "Valikud",
"more": "lisaks",
"more_categories": "Rohkem kategooriaid",
"options:data_lang": "Andmete keel",
"options:data_lang:local": "Kohalik keel",
"options:ui_lang": "Kasutajaliidese keel",

1
lang/fr.json

@ -1,6 +1,7 @@
{
"main:options": "Options",
"more": "plus",
"more_categories": "Plus de catégories",
"options:data_lang": "Langue des données",
"options:data_lang:local": "Langue locale",
"options:ui_lang": "Langue de l'interface",

1
lang/hu.json

@ -1,6 +1,7 @@
{
"main:options": "Beállítások",
"more": "több",
"more_categories": "Több kategória",
"options:data_lang": "Adatnyelv",
"options:data_lang:local": "Helyi nyelv",
"options:ui_lang": "Menünyelv",

1
lang/it.json

@ -1,6 +1,7 @@
{
"main:options": "Opzioni",
"more": "altri",
"more_categories": "Altre categorie",
"options:data_lang": "Lingua dei dati",
"options:data_lang:local": "Lingua del tuo browser",
"options:ui_lang": "Lingua dell'interfaccia",

1
lang/ja.json

@ -1,6 +1,7 @@
{
"main:options": "オプション設定",
"more": "もっと",
"more_categories": "カテゴリを一覧から追加",
"options:data_lang": "データ表示",
"options:data_lang:local": "ブラウザの設定言語",
"options:ui_lang": "インタフェース表示",

1
lang/nl.json

@ -1,6 +1,7 @@
{
"main:options": "Opties",
"more": "meer",
"more_categories": "Meer categorieën",
"options:data_lang": "Taal voor data",
"options:data_lang:local": "Lokale taal",
"options:ui_lang": "Interfacetaal",

1
lang/pl.json

@ -1,6 +1,7 @@
{
"main:options": "Opcje",
"more": "więcej",
"more_categories": "Więcej kategorii",
"options:data_lang": "Język danych",
"options:data_lang:local": "Język lokalny",
"options:ui_lang": "Język interfejsu",

1
lang/ro.json

@ -1,6 +1,7 @@
{
"main:options": "Optiuni",
"more": "Mai mult",
"more_categories": "Mai multe categorii",
"options:data_lang": "Limba date",
"options:data_lang:local": "Limba locala",
"options:ui_lang": "Limba interfata",

1
lang/ru.json

@ -1,6 +1,7 @@
{
"main:options": "Настройки",
"more": "Ещё",
"more_categories": "Больше категорий",
"options:data_lang": "Язык информации на карте",
"options:data_lang:local": "Определить язык автоматически",
"options:ui_lang": "Язык интерфейса",

1
lang/sr.json

@ -1,6 +1,7 @@
{
"main:options": "Опције",
"more": "још",
"more_categories": "Више категорија",
"options:data_lang": "Језик подетака",
"options:data_lang:local": "Локални језик",
"options:ui_lang": "Језик интерфејса",

1
lang/template.json

@ -2,6 +2,7 @@
"default": "",
"main:options": "",
"more": "",
"more_categories": "",
"options:data_lang": "",
"options:data_lang:desc": "",
"options:data_lang:local": "",

1
lang/uk.json

@ -1,6 +1,7 @@
{
"main:options": "Налаштування",
"more": "Ще",
"more_categories": "Більше категорій",
"options:data_lang": "Мова мапи",
"options:data_lang:local": "Місцева мова",
"options:ui_lang": "Мова інтерфейсу",

2
lib/modulekit/form

@ -1 +1 @@
Subproject commit 819380c621e2ec79d5ca22db6af44d0eaf1c158c
Subproject commit 4a94f64c11d3f16b01a5aec6afd5cfb4b7257572

2
lib/modulekit/lang

@ -1 +1 @@
Subproject commit 1013930aafa47f214f6e9d4b68c684acf4922efc
Subproject commit 80118dbcaafa9ab95298be95548126071efc069f

4
modulekit.php

@ -16,6 +16,10 @@ $include = array(
'src/ip-location.php',
'src/wikipedia.php',
'src/ImageLoader.php',
'src/RepositoryBase.php',
'src/RepositoryDir.php',
'src/RepositoryGit.php',
'src/repositories.php',
),
'css' => array(
'style.css',

3
package.json

@ -26,7 +26,8 @@
"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"
"sheet-router": "^4.2.3",
"weight-sort": "^1.3.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",

73
repo.php

@ -0,0 +1,73 @@
<?php include "conf.php"; /* load a local configuration */ ?>
<?php session_start(); ?>
<?php require 'vendor/autoload.php'; /* composer includes */ ?>
<?php include "modulekit/loader.php"; /* loads all php-includes */ ?>
<?php include "node_modules/json-multiline-strings/src/json-multiline-strings.php"; ?>
<?php call_hooks("init"); /* initialize submodules */ ?>
<?php
$allRepositories = getRepositories();
if (!isset($_REQUEST['repo'])) {
Header("Content-Type: application/json; charset=utf-8");
print '{';
$c = 0;
foreach ($allRepositories as $repoId => $repoData) {
$repo = getRepo($repoId, $repoData);
print $c++ ? ',' : '';
print json_encode($repoId, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) . ':';
$info = $repo->info();
if (isset($repoData['repositoryUrl'])) {
$info['repositoryUrl'] = $repoData['repositoryUrl'];
}
if (isset($repoData['categoryUrl'])) {
$info['categoryUrl'] = $repoData['categoryUrl'];
}
print json_encode($info, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_FORCE_OBJECT);
}
print '}';
exit(0);
}
$repoId = $_REQUEST['repo'];
if (!array_key_exists($repoId, $allRepositories)) {
Header("HTTP/1.1 404 Repository not found");
exit(0);
}
$repoData = $allRepositories[$repoId];
$repo = getRepo($repoId, $repoData);
$cacheDir = null;
$ts = $repo->timestamp($path);
if (isset($config['cache'])) {
$cacheDir = "{$config['cache']}/repo";
@mkdir($cacheDir);
$cacheTs = filemtime("{$cacheDir}/{$repoId}.json");
if ($cacheTs === $ts) {
Header("Content-Type: application/json; charset=utf-8");
readfile("{$cacheDir}/{$repoId}.json");
exit(0);
}
}
$data = $repo->data();
if (isset($repoData['repositoryUrl'])) {
$data['repositoryUrl'] = $repoData['repositoryUrl'];
}
if (isset($repoData['categoryUrl'])) {
$data['categoryUrl'] = $repoData['categoryUrl'];
}
$ret = json_encode($data, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
Header("Content-Type: application/json; charset=utf-8");
print $ret;
file_put_contents("{$cacheDir}/{$repoId}.json", $ret);
touch("{$cacheDir}/{$repoId}.json", $ts);

30
src/CategoryBase.js

@ -2,8 +2,15 @@
var OpenStreetBrowserLoader = require('./OpenStreetBrowserLoader')
var tabs = require('modulekit-tabs')
function CategoryBase (id, data) {
this.id = id
function CategoryBase (options, data) {
if (typeof options === 'string') {
this.id = options
this.options = {}
}
else {
this.id = options.id
this.options = options
}
this.parentCategory = null
this.childrenLoadingCount = 0
this.data = data
@ -35,7 +42,14 @@ function CategoryBase (id, data) {
a.onclick = this.toggle.bind(this)
domHeader.appendChild(a)
if (options.debug) {
if (this.options.repositoryId && this.options.repositoryId !== 'default') {
a = document.createElement('span')
a.className = 'repoId'
a.appendChild(document.createTextNode(this.options.repositoryId))
domHeader.appendChild(a)
}
if (this.shallShowReload()) {
a = document.createElement('a')
a.appendChild(document.createTextNode('⟳'))
a.title = lang('reload')
@ -66,6 +80,14 @@ function CategoryBase (id, data) {
this.dom.appendChild(this.domContent)
}
CategoryBase.prototype.load = function (callback) {
callback()
}
CategoryBase.prototype.shallShowReload = function () {
return options.debug
}
CategoryBase.prototype.setMap = function (map) {
this.map = map
}
@ -137,7 +159,7 @@ CategoryBase.prototype.reload = function (callback) {
OpenStreetBrowserLoader.forget(this.id)
OpenStreetBrowserLoader.getCategory(this.id, function (err, category) {
OpenStreetBrowserLoader.getCategory(this.id, { force: true }, function (err, category) {
if (err) {
return callback(err)
}

14
src/CategoryIndex.js

@ -5,8 +5,8 @@ var CategoryBase = require('./CategoryBase')
CategoryIndex.prototype = Object.create(CategoryBase.prototype)
CategoryIndex.prototype.constructor = CategoryIndex
function CategoryIndex (id, data) {
CategoryBase.call(this, id, data)
function CategoryIndex (options, data) {
CategoryBase.call(this, options, data)
this.childrenDoms = {}
this.childrenCategories = null
@ -51,9 +51,9 @@ CategoryIndex.prototype._loadChildrenCategories = function (callback) {
this.childrenCategories[data.id] = null
if ('type' in data) {
OpenStreetBrowserLoader.getCategoryFromData(data.id, data, this._loadChildCategory.bind(this, callback))
OpenStreetBrowserLoader.getCategoryFromData(data.id, this.options, data, this._loadChildCategory.bind(this, data.id, callback))
} else {
OpenStreetBrowserLoader.getCategory(data.id, this._loadChildCategory.bind(this, callback))
OpenStreetBrowserLoader.getCategory(data.id, this.options, this._loadChildCategory.bind(this, data.id, callback))
}
}.bind(this),
function (err) {
@ -64,15 +64,15 @@ CategoryIndex.prototype._loadChildrenCategories = function (callback) {
)
}
CategoryIndex.prototype._loadChildCategory = function (callback, err, category) {
CategoryIndex.prototype._loadChildCategory = function (id, callback, err, category) {
if (err) {
return callback(err)
}
this.childrenCategories[category.id] = category
this.childrenCategories[id] = category
category.setParent(this)
category.setParentDom(this.childrenDoms[category.id])
category.setParentDom(this.childrenDoms[id])
callback(err, category)
}

20
src/CategoryOverpass.js

@ -11,7 +11,7 @@ var defaultValues = {
markerSign: '',
'style:hover': {
color: 'black',
weight: 3,
width: 3,
opacity: 1,
radius: 12,
fill: false
@ -26,10 +26,10 @@ var defaultValues = {
CategoryOverpass.prototype = Object.create(CategoryBase.prototype)
CategoryOverpass.prototype.constructor = CategoryOverpass
function CategoryOverpass (id, data) {
function CategoryOverpass (options, data) {
var p
CategoryBase.call(this, id, data)
CategoryBase.call(this, options, data)
data.id = this.id
@ -63,6 +63,7 @@ function CategoryOverpass (id, data) {
data.feature.appUrl = '#' + this.id + '/{{ id }}'
data.styleNoBindPopup = [ 'hover' ]
data.stylesNoAutoShow = [ 'hover' ]
this.layer = new OverpassLayer(data)
@ -155,7 +156,7 @@ function CategoryOverpass (id, data) {
}
CategoryOverpass.prototype.load = function (callback) {
OpenStreetBrowserLoader.getTemplate('popupBody', function (err, template) {
OpenStreetBrowserLoader.getTemplate('popupBody', this.options, function (err, template) {
if (err) {
console.log("can't load popupBody.html")
} else {
@ -255,8 +256,13 @@ CategoryOverpass.prototype.updateInfo = function () {
}
global.currentCategory = this
var data = JSON.parse(JSON.stringify(this.data))
data.map = { zoom: map.getZoom() }
var data = {
layer_id: this.id,
'const': this.data.const,
}
if (this.map) {
data.map = { zoom: map.getZoom() }
}
this.domInfo.innerHTML = this.templateInfo.render(data)
global.currentCategory = null
}
@ -323,7 +329,7 @@ CategoryOverpass.prototype.updatePopupContent = function (object, popup) {
}
CategoryOverpass.prototype.renderTemplate = function (object, templateId, callback) {
OpenStreetBrowserLoader.getTemplate(templateId, function (err, template) {
OpenStreetBrowserLoader.getTemplate(templateId, this.options, function (err, template) {
if (err) {
err = "can't load " + templateId + ': ' + err
return callback(err, null)

207
src/OpenStreetBrowserLoader.js

@ -4,6 +4,7 @@ var jsonMultilineStrings = require('json-multiline-strings')
function OpenStreetBrowserLoader () {
this.types = {}
this.categories = {}
this.repoCache = {}
this.templates = {}
this._loadClash = {} // if a category is being loaded multiple times, collect callbacks
}
@ -12,83 +13,168 @@ OpenStreetBrowserLoader.prototype.setMap = function (map) {
this.map = map
}
OpenStreetBrowserLoader.prototype.getCategory = function (id, callback) {
if (id in this.categories) {
callback(null, this.categories[id])
return
/**
* @param string id ID of the category
* @param [object] options Options.
* @waram {boolean} [options.force=false] Whether repository should be reload or not.
* @param function callback Callback which will be called with (err, category)
*/
OpenStreetBrowserLoader.prototype.getCategory = function (id, options, callback) {
if (typeof options === 'function') {
callback = options
options = {}
}
if (id in this._loadClash) {
this._loadClash[id].push(callback)
return
var ids = this.getFullId(id, options)
if (ids === null) {
return callback('invalid id', null)
}
this._loadClash[id] = []
if (options.force) {
delete this.categories[ids.fullId]
}
function reqListener (req) {
if (req.status !== 200) {
console.log(req)
return callback(req.statusText, null)
if (ids.fullId in this.categories) {
return callback(null, this.categories[ids.fullId])
}
var opt = JSON.parse(JSON.stringify(options))
opt.categoryId = ids.entityId
opt.repositoryId = ids.repositoryId
this.getRepo(ids.repositoryId, opt, function (err, repoData) {
// maybe loaded in the meantime?
if (ids.fullId in this.categories) {
return callback(null, this.categories[ids.fullId])
}
if (err) {
return callback(err, null)
}
var data = JSON.parse(req.responseText)
data = jsonMultilineStrings.join(data, { exclude: [ [ 'const' ] ] })
if (!(ids.entityId in repoData.categories)) {
return callback(new Error('category not defined'), null)
}
this.getCategoryFromData(id, data, function (err, category) {
this.getCategoryFromData(ids.id, opt, repoData.categories[ids.entityId], function (err, category) {
if (category) {
category.setMap(this.map)
}
callback(err, category)
this._loadClash[id].forEach(function (c) {
c(err, category)
})
delete this._loadClash[id]
}.bind(this))
}
var req = new XMLHttpRequest()
req.addEventListener('load', reqListener.bind(this, req))
req.open('GET', config.categoriesDir + '/' + id + '.json?' + config.categoriesRev)
req.send()
}.bind(this))
}
OpenStreetBrowserLoader.prototype.getTemplate = function (id, callback) {
if (id in this.templates) {
callback.apply(this, this.templates[id])
return
/**
* @param string repo ID of the repository
* @parapm [object] options Options.
* @waram {boolean} [options.force=false] Whether repository should be reload or not.
* @param function callback Callback which will be called with (err, repoData)
*/
OpenStreetBrowserLoader.prototype.getRepo = function (repo, options, callback) {
if (options.force) {
delete this.repoCache[repo]
}
if (id in this._loadClash) {
this._loadClash[id].push(callback)
if (repo in this.repoCache) {
return callback.apply(this, this.repoCache[repo])
}
if (repo in this._loadClash) {
this._loadClash[repo].push(callback)
return
}
this._loadClash[id] = []
this._loadClash[repo] = [ callback ]
function reqListener (req) {
if (req.status !== 200) {
console.log(req)
this.templates[id] = [ req.statusText, null ]
console.log('http error when loading repository', req)
this.repoCache[repo] = [ req.statusText, null ]
} else {
this.templates[id] = [ null, OverpassLayer.twig.twig({ data: req.responseText, autoescape: true }) ]
try {
this.repoCache[repo] = [ null, JSON.parse(req.responseText) ]
} catch (err) {
console.log('couldn\'t parse repository', req.responseText)
this.repoCache[repo] = [ 'couldn\t parse repository', null ]
}
}
callback.apply(this, this.templates[id])
var todo = this._loadClash[repo]
delete this._loadClash[repo]
this._loadClash[id].forEach(function (c) {
c.apply(this, this.templates[id])
todo.forEach(function (callback) {
callback.apply(this, this.repoCache[repo])
}.bind(this))
}
var param = []
if (repo) {
param.push('repo=' + encodeURIComponent(repo))
}
param.push(config.categoriesRev)
param = param.length ? '?' + param.join('&') : ''
var req = new XMLHttpRequest()
req.addEventListener('load', reqListener.bind(this, req))
req.open('GET', config.categoriesDir + '/' + id + '.html?' + config.categoriesRev)
req.open('GET', 'repo.php' + param)
req.send()
}
OpenStreetBrowserLoader.prototype.getCategoryFromData = function (id, data, callback) {
/**
* @param string id ID of the template
* @parapm [object] options Options.
* @waram {boolean} [options.force=false] Whether repository should be reload or not.
* @param function callback Callback which will be called with (err, template)
*/
OpenStreetBrowserLoader.prototype.getTemplate = function (id, options, callback) {
if (typeof options === 'function') {
callback = options
options = {}
}
var ids = this.getFullId(id, options)
if (options.force) {
delete this.templates[ids.fullId]
}
if (ids.fullId in this.templates) {
return callback(null, this.templates[ids.fullId])
}
var opt = JSON.parse(JSON.stringify(options))
opt.templateId = ids.entityId
opt.repositoryId = ids.repositoryId
this.getRepo(ids.repositoryId, opt, function (err, repoData) {
// maybe loaded in the meantime?
if (ids.fullId in this.templates) {
return callback(null, this.templates[ids.fullId])
}
if (err) {
return callback(err, null)
}
if (!repoData.templates || !(ids.entityId in repoData.templates)) {
return callback(new Error('template not defined'), null)
}
this.templates[ids.fullId] = OverpassLayer.twig.twig({ data: repoData.templates[ids.entityId], autoescape: true })
callback(null, this.templates[ids.fullId])
}.bind(this))
}
OpenStreetBrowserLoader.prototype.getCategoryFromData = function (id, options, data, callback) {
var ids = this.getFullId(id, options)
if (ids.fullId in this.categories) {
return callback(null, this.categories[ids.fullId])
}
if (!data.type) {
return callback(new Error('no type defined'), null)
}
@ -97,11 +183,13 @@ OpenStreetBrowserLoader.prototype.getCategoryFromData = function (id, data, call
return callback(new Error('unknown type'), null)
}
var layer = new this.types[data.type](id, data)
var opt = JSON.parse(JSON.stringify(options))
opt.id = ids.id
var layer = new this.types[data.type](opt, data)
layer.setMap(this.map)
this.categories[id] = layer
this.categories[ids.fullId] = layer
if ('load' in layer) {
layer.load(function (err) {
@ -112,9 +200,38 @@ OpenStreetBrowserLoader.prototype.getCategoryFromData = function (id, data, call
}
}
OpenStreetBrowserLoader.prototype.getFullId = function (id, options) {
var result = {}
if (!id) {
return null
}
var m
if (m = id.match(/^(.*)\/([^\/]*)/)) {
result.id = id
result.repositoryId = m[1]
result.entityId = m[2]
} else if (options.repositoryId && options.repositoryId !== 'default') {
result.repositoryId = options.repositoryId
result.entityId = id
result.id = result.repositoryId + '/' + id
} else {
result.id = id
result.repositoryId = 'default'
result.entityId = id
}
result.fullId = result.repositoryId + '/' + result.entityId
return result
}
OpenStreetBrowserLoader.prototype.forget = function (id) {
this.categories[id].remove()
delete this.categories[id]
var ids = this.getFullId(id, options)
this.categories[ids.fullId].remove()
delete this.categories[ids.fullId]
}
OpenStreetBrowserLoader.prototype.registerType = function (type, classObject) {

43
src/RepositoryBase.php

@ -0,0 +1,43 @@
<?php
class RepositoryBase {
function __construct ($id, $def) {
$this->def = $def;
$this->path = $def['path'];
}
function timestamp () {
return null;
}
function info () {
$ret = array();
foreach (array('name') as $k) {
if (array_key_exists($k, $this->def)) {
$ret[$k] = $this->def[$k];
}
}
$ret['timestamp'] = Date(DATE_ISO8601, $this->timestamp());
return $ret;
}
function data () {
$data = array(
'categories' => array(),
'templates' => array(),
'timestamp' => Date(DATE_ISO8601, $this->timestamp()),
);
return $data;
}
function isCategory ($data) {
if (!array_key_exists('type', $data)) {
return false;
}
return in_array($data['type'], array('index', 'overpass'));
}
}

52
src/RepositoryDir.php

@ -0,0 +1,52 @@
<?php
class RepositoryDir extends RepositoryBase {
function timestamp () {
$ts = 0;
$d = opendir($this->path);
while ($f = readdir($d)) {
$t = filemtime("{$this->path}/{$f}");
if ($t > $ts) {
$ts = $t;
}
}
closedir($d);
return $ts;
}
function data () {
$data = parent::data();
$d = opendir($this->path);
while ($f = readdir($d)) {
if (preg_match("/^([0-9a-zA-Z_\-]+)\.json$/", $f, $m) && $f !== 'package.json') {
$d1 = json_decode(file_get_contents("{$this->path}/{$f}"), true);
if (!$this->isCategory($d1)) {
continue;
}
$data['categories'][$m[1]] = jsonMultilineStringsJoin($d1, array('exclude' => array(array('const'))));
}
if (preg_match("/^(detailsBody|popupBody).html$/", $f, $m)) {
$data['templates'][$m[1]] = file_get_contents("{$this->path}/{$f}");
}
}
closedir($d);
return $data;
}
function scandir($path="") {
return scandir("{$this->path}/{$path}");
}
function file_get_contents ($file) {
return file_get_contents("{$this->path}/{$file}");
}
function file_put_contents ($file, $content) {
return file_put_contents("{$this->path}/{$file}", $content);
}
}

58
src/RepositoryGit.php

@ -0,0 +1,58 @@
<?php
class RepositoryGit extends RepositoryBase {
function timestamp () {
$ts = (int)shell_exec("cd " . escapeShellArg($this->path) . "; git log -1 --pretty=format:%ct");
return $ts;
}
function data () {
$data = parent::data();
$d = popen("cd " . escapeShellArg($this->path) . "; git ls-tree HEAD", "r");
while ($r = fgets($d)) {
if (preg_match("/^[0-9]{6} blob [0-9a-f]{40}\t(([0-9a-zA-Z_\-]+)\.json)$/", $r, $m)) {
$f = $m[1];
$id = $m[2];
if ($f === 'package.json') {
continue;
}
$d1 = json_decode(shell_exec("cd " . escapeShellArg($this->path) . "; git show HEAD:" . escapeShellArg($f)), true);
if (!$this->isCategory($d1)) {
continue;
}
$data['categories'][$id] = jsonMultilineStringsJoin($d1, array('exclude' => array(array('const'))));
}
if (preg_match("/^[0-9]{6} blob [0-9a-f]{40}\t((detailsBody|popupBody)\.html)$/", $r, $m)) {
$data['templates'][$m[2]] = shell_exec("cd " . escapeShellArg($this->path) . "; git show HEAD:" . escapeShellArg($m[1]));
}
}
pclose($d);
return $data;
}
function scandir($path="") {
if ($path !== '' && substr($path, -1) !== '/') {
$path .= '/';
}
$d = popen("cd " . escapeShellArg($this->path) . "; git ls-tree HEAD " . escapeShellArg($path), "r");
$ret = array();
while ($r = fgets($d)) {
$ret[] = substr($r, 53);
}
pclose($d);
return $ret;
}
function file_get_contents ($file) {
return shell_exec("cd " . escapeShellArg($this->path) . "; git show HEAD:" . escapeShellArg($file));
}
}

3
src/addCategories.css

@ -0,0 +1,3 @@
#content.addCategories > #contentAddCategories {
display: block;
}

140
src/addCategories.js

@ -0,0 +1,140 @@
var OpenStreetBrowserLoader = require('./OpenStreetBrowserLoader')
require('./addCategories.css')
var weightSort = require('weight-sort')
var content
function addCategoriesShow (repo) {
if (!content) {
content = document.createElement('div')
content.id = 'contentAddCategories'
document.getElementById('content').appendChild(content)
}
content.innerHTML = 'Loading ...'
document.getElementById('content').className = 'addCategories'
OpenStreetBrowserLoader.getRepo(repo, {}, function (err, repoData) {
while(content.firstChild)
content.removeChild(content.firstChild)
var backLink = document.createElement('a')
backLink.className = 'back'
backLink.href = '#'
backLink.innerHTML = '<i class="fa fa-chevron-circle-left" aria-hidden="true"></i> '
backLink.appendChild(document.createTextNode(lang('back')))
var categoryUrl = null
if (repoData.categoryUrl) {
categoryUrl = OverpassLayer.twig.twig({ data: repoData.categoryUrl, autoescape: true })
}
var list = {}
if (repo) {
backLink.onclick = function () {
addCategoriesShow()
return false
}
content.appendChild(backLink)
var h = document.createElement('h2')
h.appendChild(document.createTextNode(repo))
content.appendChild(h)
list = repoData.categories
} else {
backLink.onclick = function () {
addCategoriesHide()
return false
}
content.appendChild(backLink)
var h = document.createElement('h2')
h.innerHTML = lang('more_categories')
content.appendChild(h)
if (typeof repositoriesGitea === 'object' && repositoriesGitea.url) {
var a = document.createElement('a')
a.href = repositoriesGitea.url
a.target = '_blank'
a.innerHTML = lang('more_categories_gitea')
content.appendChild(a)
}
list = weightSort(repoData, {
key: 'timestamp',
reverse: true
})
}
var ul = document.createElement('ul')
for (var id in list) {
var data = list[id]
var repositoryUrl = null
if (data.repositoryUrl) {
repositoryUrl = OverpassLayer.twig.twig({ data: data.repositoryUrl, autoescape: true })
}
var li = document.createElement('li')
var a = document.createElement('a')
if (repo) {
a.href = '#categories=' + repo + '/' + id
a.onclick = function () {
addCategoriesHide()
}
} else {
a.href = '#'
a.onclick = function (id) {
addCategoriesShow(id)
return false
}.bind(this, id)
}
li.appendChild(a)
a.appendChild(document.createTextNode('name' in data ? lang(data.name) : id))
var editLink = null
if (repo && categoryUrl) {
editLink = document.createElement('a')
editLink.href = categoryUrl.render({ repositoryId: repo, categoryId: id })
}
if (!repo && repositoryUrl) {
editLink = document.createElement('a')
editLink.href = repositoryUrl.render({ repositoryId: id })
}
if (editLink) {
editLink.className = 'source-code'
editLink.title = 'Show source code'
editLink.target = '_blank'
editLink.innerHTML = '<i class="fa fa-file-code-o" aria-hidden="true"></i>'
li.appendChild(document.createTextNode(' '))
li.appendChild(editLink)
}
ul.appendChild(li)
}
content.appendChild(ul)
})
}
function addCategoriesHide () {
document.getElementById('content').className = 'list'
}
register_hook('init', function (callback) {
var link = document.createElement('a')
link.className = 'addCategories'
link.href = '#'
link.onclick = function () {
addCategoriesShow()
return false
}
link.innerHTML = '<i class="fa fa-chevron-circle-down" aria-hidden="true"></i> ' + lang('more_categories')
document.getElementById('contentList').appendChild(link)
})

12
src/category.css

@ -69,13 +69,11 @@
user-select: none;
font-size: 15px;
}
.category header > a {
text-decoration: none;
color: black;
}
.category header > a:active,
.category header > a:hover {
text-decoration: underline;
.category header > span.repoId {
margin-left: 0.2em;
font-size: 10px;
line-height: 10px;
color: #7f7f7f;
}
.category header > a.reload {
float: right;

17
src/index.js

@ -30,6 +30,7 @@ require('./markers')
require('./categories')
require('./wikipedia')
require('./image')
require('./addCategories')
window.onload = function () {
initState = config.defaultView
@ -172,15 +173,17 @@ function show (id, options, callback) {
document.getElementById('contentDetails').innerHTML = 'Loading ...'
}
id = id.split('/')
if (id.length < 2) {
var m = id.match(/^(.*)\/([nwr]\d+)(\/details)?$/)
if (!m) {
return callback(new Error('unknown request'))
}
OpenStreetBrowserLoader.getCategory(id[0], function (err, category) {
var categoryId = m[1]
var featureId = m[2]
OpenStreetBrowserLoader.getCategory(categoryId, function (err, category) {
if (err) {
return callback(new Error('error loading category "' + id[0] + '": ' + err))
return callback(new Error('error loading category "' + categoryId + '": ' + err))
}
if (!category.parentDom) {
@ -188,12 +191,12 @@ function show (id, options, callback) {
}
category.show(
id[1],
featureId,
{
},
function (err, data) {
if (err) {
return callback(new Error('error loading object "' + id[0] + '/' + id[1] + '": ' + err))
return callback(new Error('error loading object "' + categoryId + '/' + featureId + '": ' + err))
}
if (!map._popup || map._popup !== data.popup) {

12
src/markers.js

@ -5,9 +5,7 @@ function cssStyle (style) {
if ('color' in style) {
ret += 'stroke: ' + style.color + ';'
}
if ('weight' in style) {
ret += 'stroke-width: ' + style.weight + ';'
}
ret += 'stroke-width: ' + ('width' in style ? style.width : '3') + ';'
if ('dashArray' in style) {
ret += 'stroke-dasharray: ' + style.dashArray + ';'
}
@ -80,17 +78,17 @@ function markerPolygon (data) {
function markerCircle (style) {
var fillColor = 'fillColor' in style ? style.fillColor : '#f2756a'
var color = 'color' in style ? style.color : '#000000'
var weight = 'weight' in style ? style.weight : 1
var width = 'width' in style ? style.width : 1
return '<svg anchorX="13" anchorY="13" width="25" height="25"><circle cx="12.5" cy="12.5" r="12" style="stroke: ' + color + '; stroke-width: ' + weight + '; fill: ' + fillColor + ';"/></svg>'
return '<svg anchorX="13" anchorY="13" width="25" height="25"><circle cx="12.5" cy="12.5" r="12" style="stroke: ' + color + '; stroke-width: ' + width + '; fill: ' + fillColor + ';"/></svg>'
}
function markerPointer (style) {
var fillColor = 'fillColor' in style ? style.fillColor : '#f2756a'
var color = 'color' in style ? style.color : '#000000'
var weight = 'weight' in style ? style.weight : 1
var width = 'width' in style ? style.width : 1
return '<svg anchorX="13" anchorY="45" width="25" height="45"><path d="M0.5,12.5 A 12,12 0 0 1 24.5,12.5 C 24.5,23 13,30 12.5,44.5 C 12,30 0.5,23 0.5,12.5" style="stroke: ' + color + '; stroke-width: ' + weight + '; fill: ' + fillColor + ';"/></svg>'
return '<svg anchorX="13" anchorY="45" width="25" height="45"><path d="M0.5,12.5 A 12,12 0 0 1 24.5,12.5 C 24.5,23 13,30 12.5,44.5 C 12,30 0.5,23 0.5,12.5" style="stroke: ' + color + '; stroke-width: ' + width + '; fill: ' + fillColor + ';"/></svg>'
}
OverpassLayer.twig.extendFunction('markerLine', markerLine)

70
src/repositories.php

@ -0,0 +1,70 @@
<?php
function getRepositories () {
global $repositories;
global $repositoriesGitea;
$result = array();
if (isset($repositories)) {
$result = $repositories;
}
else {
$result = array(
'default' => array(
'path' => $config['categoriesDir'],
),
);
}
if (isset($repositoriesGitea)) {
$d1 = opendir($repositoriesGitea['path']);
while ($f1 = readdir($d1)) {
if (substr($f1, 0, 1) !== '.') {
$d2 = opendir("{$repositoriesGitea['path']}/{$f1}");
while ($f2 = readdir($d2)) {
if (substr($f2, 0, 1) !== '.') {
$f2id = substr($f2, 0, -4);
$r = array(
'path' => "{$repositoriesGitea['path']}/{$f1}/{$f2}",
'type' => 'git',
);
if (array_key_exists('url', $repositoriesGitea)) {
$r['repositoryUrl'] = "{$repositoriesGitea['url']}/{{ repositoryId }}";
$r['categoryUrl'] = "{$repositoriesGitea['url']}/{{ repositoryId }}/src/{{ categoryId }}.json";
}
$result["{$f1}/{$f2id}"] = $r;
}
}
closedir($d2);
}
}
closedir($d1);
}
return $result;
}
function getRepo ($repoId, $repoData) {
switch (array_key_exists('type', $repoData) ? $repoData['type'] : 'dir') {
case 'git':
$repo = new RepositoryGit($repoId, $repoData);
break;
default:
$repo = new RepositoryDir($repoId, $repoData);
}
return $repo;
}
register_hook('init', function () {
global $repositoriesGitea;
if (isset($repositoriesGitea) && array_key_exists('url', $repositoriesGitea)) {
$d = array('repositoriesGitea' => array(
'url' => $repositoriesGitea['url'],
));
html_export_var($d);
}
});

15
style.css

@ -8,6 +8,14 @@ body {
font-size: 11px;
color:#333;
}
a {
text-decoration: none;
color: black;
}
a:hover,
a:active {
text-decoration: underline;
}
#sidebar {
top: 0px;
@ -116,13 +124,6 @@ a.showDetails {
#menu li {
display: inline-block;
}
#menu a {
text-decoration: none;
color: black;
}
#menu a:hover {
text-decoration: underline;
}
#menu li::after {
content: ' |';
}

Loading…
Cancel
Save