Merge pull request #1 from pietervdvn/master

Merge master branch to get up to date with the latest changes
This commit is contained in:
Win Olario 2021-04-11 21:53:59 +08:00 committed by GitHub
commit 620376446d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
172 changed files with 6042 additions and 749 deletions

17
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,17 @@
Opening a pull request on MapComplete
=====================================
Hey! Thanks for opening a pull request on Mapcomplete! This probably means you want to add a new theme - if so, please follow the checklist below.
If this pull request is for some other issue, please ignore the template.
Adding your new theme
---------------------
Thanks for taking the time to create your theme and to add it to the main repository!
To making merging smooth, please make sure that each of the following conditions is met:
- [ ] The codebase is GPL-licensed. By opening a pull request, the new theme will be GPL too
- [ ] All images are included in the pull request and no images are loaded from an external service (e.g. Wikipedia)
- [ ] The [guidelines on how to make your own theme](https://github.com/pietervdvn/MapComplete/blob/master/Docs/Making_Your_Own_Theme.md) are read and followed

View file

@ -0,0 +1,65 @@
name: Pull request check
on:
pull_request_target:
types: [opened, edited, synchronize, ready_for_review, review_requested]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v1.2.0
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
- name: install deps
run: npm install
- name: create generated dir
run: mkdir ./assets/generated
- name: create stub themes
run: "echo '{\"layers\": [], \"themes\": []}' > ./assets/generated/known_layers_and_themes.json"
- name: Compile license info
run: npm run generate:licenses
- name: Compile and validate themes and layers
run: npm run validate:layeroverview
- name: Validate license info
run: npm run validate:licenses
- name: Set failure key
run: |
ls
if [[ -f "layer_report.txt" || -f "missing_licenses.txt" ]]; then
echo "Found a report..."
echo "VALIDATION_FAILED=true" >> $GITHUB_ENV
else
echo "VALIDATION_FAILED=false" >> $GITHUB_ENV
fi
- name: Test variable
run: echo "${{ env.VALIDATION_FAILED }}"
- name: Archive reports
uses: actions/upload-artifact@v2
if: >-
env.VALIDATION_FAILED == 'true'
with:
name: reports
path: |
layer_report.txt
missing_licenses.txt
- name: Comment PR
uses: allthatjazzleo/actions-pull-request-add-comment@master
if: >-
env.VALIDATION_FAILED == 'true'
with:
message: "cat layer_report.txt missing_licenses.txt"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

32
.github/workflows/theme_validation.yml vendored Normal file
View file

@ -0,0 +1,32 @@
name: Theme Validation
on:
push:
branches:
- develop
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v1.2.0
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
- name: install deps
run: npm install
- name: create generated dir
run: mkdir ./assets/generated
- name: create stub themes
run: "echo '{\"layers\": [], \"themes\": []}' > ./assets/generated/known_layers_and_themes.json"
- name: Compile license info
run: npm run validate:licenses
- name: Compile themes and layers
run: npm run validate:layeroverview

File diff suppressed because one or more lines are too long

View file

@ -1,90 +1,35 @@
import * as drinkingWater from "../assets/layers/drinking_water/drinking_water.json";
import * as ghostbikes from "../assets/layers/ghost_bike/ghost_bike.json"
import * as viewpoint from "../assets/layers/viewpoint/viewpoint.json"
import * as bike_parking from "../assets/layers/bike_parking/bike_parking.json"
import * as bike_repair_station from "../assets/layers/bike_repair_station/bike_repair_station.json"
import * as birdhides from "../assets/layers/bird_hide/birdhides.json"
import * as nature_reserve from "../assets/layers/nature_reserve/nature_reserve.json"
import * as bike_cafes from "../assets/layers/bike_cafe/bike_cafes.json"
import * as bike_monitoring_station from "../assets/layers/bike_monitoring_station/bike_monitoring_station.json"
import * as cycling_themed_objects from "../assets/layers/cycling_themed_object/cycling_themed_objects.json"
import * as bike_shops from "../assets/layers/bike_shop/bike_shop.json"
import * as bike_cleaning from "../assets/layers/bike_cleaning/bike_cleaning.json"
import * as bicycle_library from "../assets/layers/bicycle_library/bicycle_library.json"
import * as bicycle_tube_vending_machine from "../assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json"
import * as maps from "../assets/layers/maps/maps.json"
import * as information_boards from "../assets/layers/information_board/information_board.json"
import * as direction from "../assets/layers/direction/direction.json"
import * as surveillance_camera from "../assets/layers/surveillance_cameras/surveillance_cameras.json"
import * as toilets from "../assets/layers/toilets/toilets.json"
import * as bookcases from "../assets/layers/public_bookcases/public_bookcases.json"
import * as tree_nodes from "../assets/layers/trees/tree_nodes.json"
import * as benches from "../assets/layers/benches/benches.json"
import * as benches_at_pt from "../assets/layers/benches/benches_at_pt.json"
import * as picnic_tables from "../assets/layers/benches/picnic_tables.json"
import * as play_forest from "../assets/layers/play_forest/play_forest.json"
import * as playground from "../assets/layers/playground/playground.json"
import * as sport_pitch from "../assets/layers/sport_pitch/sport_pitch.json"
import * as slow_roads from "../assets/layers/slow_roads/slow_roads.json"
import LayerConfig from "./JSON/LayerConfig"; import LayerConfig from "./JSON/LayerConfig";
import {LayerConfigJson} from "./JSON/LayerConfigJson"; import {LayerConfigJson} from "./JSON/LayerConfigJson";
import * as grass_in_parks from "../assets/layers/village_green/grass_in_parks.json" import * as known_layers from "../assets/generated/known_layers_and_themes.json"
import * as village_green from "../assets/layers/village_green/village_green.json" import {Utils} from "../Utils";
export default class AllKnownLayers { export default class AllKnownLayers {
private static sharedLayersListRaw : LayerConfigJson[] = [
drinkingWater,
ghostbikes,
viewpoint,
bike_parking,
bike_repair_station,
bike_monitoring_station,
birdhides,
nature_reserve,
bike_cafes,
bicycle_library,
cycling_themed_objects,
bike_shops,
bike_cleaning,
bicycle_tube_vending_machine,
maps,
direction,
information_boards,
toilets,
bookcases,
surveillance_camera,
tree_nodes,
benches,
benches_at_pt,
picnic_tables,
play_forest,
playground,
sport_pitch,
slow_roads,
grass_in_parks,
village_green
];
// Must be below the list... // Must be below the list...
public static sharedLayers: Map<string, LayerConfig> = AllKnownLayers.getSharedLayers(); public static sharedLayers: Map<string, LayerConfig> = AllKnownLayers.getSharedLayers();
public static sharedLayersJson: Map<string, any> = AllKnownLayers.getSharedLayersJson(); public static sharedLayersJson: Map<string, any> = AllKnownLayers.getSharedLayersJson();
private static sharedLayersListRaw: LayerConfigJson[] = known_layers.layers;
private static getSharedLayers(): Map<string, LayerConfig> { private static getSharedLayers(): Map<string, LayerConfig> {
const sharedLayers = new Map<string, LayerConfig>(); const sharedLayers = new Map<string, LayerConfig>();
for (const layer of AllKnownLayers.sharedLayersListRaw) { for (const layer of known_layers.layers) {
try {
const parsed = new LayerConfig(layer, "shared_layers") const parsed = new LayerConfig(layer, "shared_layers")
sharedLayers.set(layer.id, parsed); sharedLayers.set(layer.id, parsed);
sharedLayers[layer.id] = parsed; sharedLayers[layer.id] = parsed;
} catch (e) {
if (!Utils.runningFromConsole) {
console.error("CRITICAL: Could not parse a layer configuration!", layer.id, " due to", e)
}
}
} }
return sharedLayers; return sharedLayers;
} }
private static getSharedLayersJson(): Map<string, any> { private static getSharedLayersJson(): Map<string, any> {
const sharedLayers = new Map<string, any>(); const sharedLayers = new Map<string, any>();
for (const layer of AllKnownLayers.sharedLayersListRaw) { for (const layer of known_layers.layers) {
sharedLayers.set(layer.id, layer); sharedLayers.set(layer.id, layer);
sharedLayers[layer.id] = layer; sharedLayers[layer.id] = layer;
} }

View file

@ -1,74 +1,29 @@
import * as bookcases from "../assets/themes/bookcases/Bookcases.json";
import * as aed from "../assets/themes/aed/aed.json";
import * as toilets from "../assets/themes/toilets/toilets.json";
import * as artworks from "../assets/themes/artwork/artwork.json";
import * as cyclestreets from "../assets/themes/cyclestreets/cyclestreets.json";
import * as ghostbikes from "../assets/themes/ghostbikes/ghostbikes.json"
import * as cyclofix from "../assets/themes/cyclofix/cyclofix.json"
import * as buurtnatuur from "../assets/themes/buurtnatuur/buurtnatuur.json"
import * as nature from "../assets/themes/nature/nature.json"
import * as maps from "../assets/themes/maps/maps.json"
import * as shops from "../assets/themes/shops/shops.json"
import * as bike_monitoring_stations from "../assets/themes/bike_monitoring_station/bike_monitoring_stations.json"
import * as fritures from "../assets/themes/fritures/fritures.json"
import * as benches from "../assets/themes/benches/benches.json";
import * as charging_stations from "../assets/themes/charging_stations/charging_stations.json"
import * as widths from "../assets/themes/widths/width.json"
import * as drinking_water from "../assets/themes/drinking_water/drinking_water.json"
import * as climbing from "../assets/themes/climbing/climbing.json"
import * as surveillance_cameras from "../assets/themes/surveillance_cameras/surveillance_cameras.json"
import * as trees from "../assets/themes/trees/trees.json"
import * as personal from "../assets/themes/personalLayout/personalLayout.json"
import * as playgrounds from "../assets/themes/playgrounds/playgrounds.json"
import * as bicycle_lib from "../assets/themes/bicycle_library/bicycle_library.json"
import * as campersites from "../assets/themes/campersites/campersites.json"
import * as play_forests from "../assets/themes/play_forests/play_forests.json"
import * as speelplekken from "../assets/themes/speelplekken/speelplekken.json"
import * as sport_pitches from "../assets/themes/sport_pitches/sport_pitches.json"
import * as grb from "../assets/themes/grb.json"
import * as facadegardens from "../assets/themes/facadegardens/facadegardens.json"
import LayerConfig from "./JSON/LayerConfig";
import LayoutConfig from "./JSON/LayoutConfig"; import LayoutConfig from "./JSON/LayoutConfig";
import AllKnownLayers from "./AllKnownLayers"; import AllKnownLayers from "./AllKnownLayers";
import * as known_themes from "../assets/generated/known_layers_and_themes.json"
import {LayoutConfigJson} from "./JSON/LayoutConfigJson";
import * as all_layouts from "../assets/generated/known_layers_and_themes.json"
export class AllKnownLayouts { export class AllKnownLayouts {
public static allLayers: Map<string, LayerConfig> = undefined;
public static layoutsList: LayoutConfig[] = [
new LayoutConfig(personal),
AllKnownLayouts.GenerateCycloFix(),
new LayoutConfig(aed),
new LayoutConfig(bookcases),
new LayoutConfig(toilets),
new LayoutConfig(artworks),
new LayoutConfig(ghostbikes),
new LayoutConfig(shops),
new LayoutConfig(drinking_water),
new LayoutConfig(nature),
new LayoutConfig(cyclestreets),
new LayoutConfig(bicycle_lib),
new LayoutConfig(maps),
new LayoutConfig(fritures),
new LayoutConfig(benches),
new LayoutConfig(charging_stations),
new LayoutConfig(widths),
new LayoutConfig(buurtnatuur),
new LayoutConfig(bike_monitoring_stations),
new LayoutConfig(surveillance_cameras),
new LayoutConfig(climbing),
new LayoutConfig(playgrounds),
new LayoutConfig(trees),
new LayoutConfig(campersites),
new LayoutConfig(play_forests),
new LayoutConfig(speelplekken),
new LayoutConfig(sport_pitches),
new LayoutConfig(grb),
new LayoutConfig(facadegardens)
];
public static allSets: Map<string, LayoutConfig> = AllKnownLayouts.AllLayouts();
private static GenerateCycloFix(): LayoutConfig { public static allKnownLayouts: Map<string, LayoutConfig> = AllKnownLayouts.AllLayouts();
const layout = new LayoutConfig(cyclofix) public static layoutsList: LayoutConfig[] = AllKnownLayouts.GenerateOrderedList(AllKnownLayouts.allKnownLayouts);
private static GenerateOrderedList(allKnownLayouts: Map<string, LayoutConfig>): LayoutConfig[] {
const keys = ["personal", "cyclofix", "bookcases", "toilets", "aed"]
const list = []
for (const key of keys) {
list.push(allKnownLayouts.get(key))
}
allKnownLayouts.forEach((layout, key) => {
if (keys.indexOf(key) < 0) {
list.push(layout)
}
})
return list;
}
private static AddGhostBikes(layout: LayoutConfig): LayoutConfig {
const now = new Date(); const now = new Date();
const m = now.getMonth() + 1; const m = now.getMonth() + 1;
const day = new Date().getDate() + 1; const day = new Date().getDate() + 1;
@ -86,8 +41,15 @@ export class AllKnownLayouts {
} }
private static AllLayouts(): Map<string, LayoutConfig> { private static AllLayouts(): Map<string, LayoutConfig> {
this.allLayers = new Map<string, LayerConfig>(); const dict: Map<string, LayoutConfig> = new Map();
for (const layout of this.layoutsList) { for (const layoutConfigJson of known_themes.themes) {
const layout = new LayoutConfig(layoutConfigJson, true)
if (layout.id === "cyclofix") {
AllKnownLayouts.AddGhostBikes(layout)
}
dict.set(layout.id, layout)
for (let i = 0; i < layout.layers.length; i++) { for (let i = 0; i < layout.layers.length; i++) {
let layer = layout.layers[i]; let layer = layout.layers[i];
if (typeof (layer) === "string") { if (typeof (layer) === "string") {
@ -98,20 +60,9 @@ export class AllKnownLayouts {
} }
} }
if (this.allLayers[layer.id] !== undefined) {
continue;
}
this.allLayers[layer.id] = layer;
this.allLayers[layer.id.toLowerCase()] = layer;
} }
} }
return dict;
const allSets: Map<string, LayoutConfig> = new Map();
for (const layout of this.layoutsList) {
allSets[layout.id] = layout;
allSets[layout.id.toLowerCase()] = layout;
}
return allSets;
} }
} }

View file

@ -7,6 +7,7 @@ import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
import {Translation} from "../../UI/i18n/Translation"; import {Translation} from "../../UI/i18n/Translation";
import Img from "../../UI/Base/Img"; import Img from "../../UI/Base/Img";
import Svg from "../../Svg"; import Svg from "../../Svg";
import {Utils} from "../../Utils"; import {Utils} from "../../Utils";
import Combine from "../../UI/Base/Combine"; import Combine from "../../UI/Base/Combine";
import {VariableUiElement} from "../../UI/Base/VariableUIElement"; import {VariableUiElement} from "../../UI/Base/VariableUIElement";
@ -17,6 +18,7 @@ import {SubstitutedTranslation} from "../../UI/SubstitutedTranslation";
import SourceConfig from "./SourceConfig"; import SourceConfig from "./SourceConfig";
import {TagsFilter} from "../../Logic/Tags/TagsFilter"; import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import {Tag} from "../../Logic/Tags/Tag"; import {Tag} from "../../Logic/Tags/Tag";
import SubstitutingTag from "../../Logic/Tags/SubstitutingTag";
export default class LayerConfig { export default class LayerConfig {
@ -40,6 +42,7 @@ export default class LayerConfig {
icon: TagRenderingConfig; icon: TagRenderingConfig;
iconOverlays: { if: TagsFilter, then: TagRenderingConfig, badge: boolean }[] iconOverlays: { if: TagsFilter, then: TagRenderingConfig, badge: boolean }[]
iconSize: TagRenderingConfig; iconSize: TagRenderingConfig;
label: TagRenderingConfig;
rotation: TagRenderingConfig; rotation: TagRenderingConfig;
color: TagRenderingConfig; color: TagRenderingConfig;
width: TagRenderingConfig; width: TagRenderingConfig;
@ -212,14 +215,15 @@ export default class LayerConfig {
} }
this.isShown = tr("isShown", "yes"); this.isShown = tr("isShown", "yes");
this.iconSize = tr("iconSize", "40,40,center"); this.iconSize = tr("iconSize", "40,40,center");
this.label = tr("label", "")
this.color = tr("color", "#0000ff"); this.color = tr("color", "#0000ff");
this.width = tr("width", "7"); this.width = tr("width", "7");
this.rotation = tr("rotation", "0"); this.rotation = tr("rotation", "0");
this.dashArray = tr("dashArray", ""); this.dashArray = tr("dashArray", "");
if(json["showIf"] !== undefined){ if (json["showIf"] !== undefined) {
throw "Invalid key on layerconfig "+this.id+": showIf. Did you mean 'isShown' instead?"; throw "Invalid key on layerconfig " + this.id + ": showIf. Did you mean 'isShown' instead?";
} }
} }
@ -307,7 +311,7 @@ export default class LayerConfig {
function render(tr: TagRenderingConfig, deflt?: string) { function render(tr: TagRenderingConfig, deflt?: string) {
const str = (tr?.GetRenderValue(tags.data)?.txt ?? deflt); const str = (tr?.GetRenderValue(tags.data)?.txt ?? deflt);
return SubstitutedTranslation.SubstituteKeys(str, tags.data); return SubstitutedTranslation.SubstituteKeys(str, tags.data).replace(/{.*}/g, "");
} }
const iconSize = render(this.iconSize, "40,40,center").split(","); const iconSize = render(this.iconSize, "40,40,center").split(",");
@ -321,7 +325,7 @@ export default class LayerConfig {
const weight = rendernum(this.width, 5); const weight = rendernum(this.width, 5);
const iconW = num(iconSize[0]); const iconW = num(iconSize[0]);
const iconH = num(iconSize[1]); let iconH = num(iconSize[1]);
const mode = iconSize[2] ?? "center" const mode = iconSize[2] ?? "center"
let anchorW = iconW / 2; let anchorW = iconW / 2;
@ -343,15 +347,15 @@ export default class LayerConfig {
const iconUrlStatic = render(this.icon); const iconUrlStatic = render(this.icon);
const self = this; const self = this;
const mappedHtml = tags.map(tgs => { const mappedHtml = tags.map(tgs => {
// What do you mean, 'tgs' is never read?
// It is read implicitly in the 'render' method
const iconUrl = render(self.icon);
const rotation = render(self.rotation, "0deg");
let htmlParts: UIElement[] = [];
let sourceParts = iconUrl.split(";");
function genHtmlFromString(sourcePart: string): UIElement { function genHtmlFromString(sourcePart: string): UIElement {
if (sourcePart.indexOf("html:") == 0) {
// We use § as a replacement for ;
const html = sourcePart.substring("html:".length)
const inner = new FixedUiElement(SubstitutingTag.substituteString(html, tgs)).SetClass("block w-min text-center")
const outer = new Combine([inner]).SetClass("flex flex-col items-center")
return outer;
}
const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0`; const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0`;
let html: UIElement = new FixedUiElement(`<img src="${sourcePart}" style="${style}" />`); let html: UIElement = new FixedUiElement(`<img src="${sourcePart}" style="${style}" />`);
const match = sourcePart.match(/([a-zA-Z0-9_]*):([^;]*)/) const match = sourcePart.match(/([a-zA-Z0-9_]*):([^;]*)/)
@ -365,11 +369,17 @@ export default class LayerConfig {
} }
// What do you mean, 'tgs' is never read?
// It is read implicitly in the 'render' method
const iconUrl = render(self.icon);
const rotation = render(self.rotation, "0deg");
let htmlParts: UIElement[] = [];
let sourceParts = Utils.NoNull(iconUrl.split(";").filter(prt => prt != ""));
for (const sourcePart of sourceParts) { for (const sourcePart of sourceParts) {
htmlParts.push(genHtmlFromString(sourcePart)) htmlParts.push(genHtmlFromString(sourcePart))
} }
let badges = []; let badges = [];
for (const iconOverlay of self.iconOverlays) { for (const iconOverlay of self.iconOverlays) {
if (!iconOverlay.if.matchesProperties(tgs)) { if (!iconOverlay.if.matchesProperties(tgs)) {
@ -377,7 +387,7 @@ export default class LayerConfig {
} }
if (iconOverlay.badge) { if (iconOverlay.badge) {
const badgeParts: UIElement[] = []; const badgeParts: UIElement[] = [];
const partDefs = iconOverlay.then.GetRenderValue(tgs).txt.split(";"); const partDefs = iconOverlay.then.GetRenderValue(tgs).txt.split(";").filter(prt => prt != "");
for (const badgePartStr of partDefs) { for (const badgePartStr of partDefs) {
badgeParts.push(genHtmlFromString(badgePartStr)) badgeParts.push(genHtmlFromString(badgePartStr))
@ -399,6 +409,16 @@ export default class LayerConfig {
.SetStyle("display:flex;height:50%;width:100%;position:absolute;top:50%;left:50%;"); .SetStyle("display:flex;height:50%;width:100%;position:absolute;top:50%;left:50%;");
htmlParts.push(badgesComponent) htmlParts.push(badgesComponent)
} }
if(sourceParts.length ==0){iconH = 0}
const label = self.label.GetRenderValue(tgs)?.Subs(tgs)
.SetClass("block w-min text-center")
.SetStyle("margin-top: "+(iconH + 2) +"px")
console.log("Generating label gave ", label, " source: ", self.label, "tags: ", tgs)
if (label !== undefined) {
htmlParts.push(new Combine([label]).SetClass("flex flex-col items-center"))
}
return new Combine(htmlParts).Render(); return new Combine(htmlParts).Render();
}) })
@ -419,5 +439,23 @@ export default class LayerConfig {
}; };
} }
public ExtractImages(): Set<string> {
const parts: Set<string>[] = []
parts.push(...this.tagRenderings?.map(tr => tr.ExtractImages(false)))
parts.push(...this.titleIcons?.map(tr => tr.ExtractImages(true)))
parts.push(this.icon?.ExtractImages(true))
parts.push(...this.iconOverlays?.map(overlay => overlay.then.ExtractImages(true)))
for (const preset of this.presets) {
parts.push(new Set<string>(preset.description?.ExtractImages(false)))
}
const allIcons = new Set<string>();
for (const part of parts) {
part?.forEach(allIcons.add, allIcons)
}
return allIcons;
}
} }

View file

@ -105,6 +105,7 @@ export interface LayerConfigJson {
* As a result, on could use a generic pin, then overlay it with a specific icon. * As a result, on could use a generic pin, then overlay it with a specific icon.
* To make things even more practical, one can use all svgs from the folder "assets/svg" and _substitute the color_ in it. * To make things even more practical, one can use all svgs from the folder "assets/svg" and _substitute the color_ in it.
* E.g. to draw a red pin, use "pin:#f00", to have a green circle with your icon on top, use `circle:#0f0;<path to my icon.svg>` * E.g. to draw a red pin, use "pin:#f00", to have a green circle with your icon on top, use `circle:#0f0;<path to my icon.svg>`
*
*/ */
icon?: string | TagRenderingConfigJson; icon?: string | TagRenderingConfigJson;
@ -128,6 +129,11 @@ export interface LayerConfigJson {
* Usage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)`` * Usage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)``
*/ */
rotation?: string | TagRenderingConfigJson; rotation?: string | TagRenderingConfigJson;
/**
* A HTML-fragment that is shown at the center of the icon, for example:
* <div style="background: white; display: block">{name}</div>
*/
label?: string | TagRenderingConfigJson ;
/** /**
* The color for way-elements and SVG-elements. * The color for way-elements and SVG-elements.

View file

@ -9,6 +9,7 @@ import {Utils} from "../../Utils";
export default class LayoutConfig { export default class LayoutConfig {
public readonly id: string; public readonly id: string;
public readonly maintainer: string; public readonly maintainer: string;
public readonly credits?: string;
public readonly changesetmessage?: string; public readonly changesetmessage?: string;
public readonly version: string; public readonly version: string;
public readonly language: string[]; public readonly language: string[];
@ -48,6 +49,7 @@ export default class LayoutConfig {
this.id = json.id; this.id = json.id;
context = (context ?? "") + "." + this.id; context = (context ?? "") + "." + this.id;
this.maintainer = json.maintainer; this.maintainer = json.maintainer;
this.credits = json.credits;
this.changesetmessage = json.changesetmessage; this.changesetmessage = json.changesetmessage;
this.version = json.version; this.version = json.version;
this.language = []; this.language = [];
@ -182,4 +184,14 @@ export default class LayoutConfig {
custom.splice(0, 0, msg); custom.splice(0, 0, msg);
return custom; return custom;
} }
public ExtractImages() : Set<string>{
const icons = new Set<string>()
for (const layer of this.layers) {
layer.ExtractImages().forEach(icons.add, icons)
}
icons.add(this.icon)
icons.add(this.socialImage)
return icons
}
} }

View file

@ -24,6 +24,12 @@ export interface LayoutConfigJson {
* 'cyclestreets' which become 'cyclestreets.html' * 'cyclestreets' which become 'cyclestreets.html'
*/ */
id: string; id: string;
/**
* Who helped to create this theme and should be attributed?
*/
credits?: string;
/** /**
* Who does maintian this preset? * Who does maintian this preset?
*/ */

View file

@ -70,7 +70,7 @@ export default class TagRenderingConfig {
addExtraTags: json.freeform.addExtraTags?.map((tg, i) => addExtraTags: json.freeform.addExtraTags?.map((tg, i) =>
FromJSON.Tag(tg, `${context}.extratag[${i}]`)) ?? [] FromJSON.Tag(tg, `${context}.extratag[${i}]`)) ?? []
} }
if(json.freeform["extraTags"] !== undefined){ if (json.freeform["extraTags"] !== undefined) {
throw `Freeform.extraTags is defined. This should probably be 'freeform.addExtraTag' (at ${context})` throw `Freeform.extraTags is defined. This should probably be 'freeform.addExtraTag' (at ${context})`
} }
if (this.freeform.key === undefined || this.freeform.key === "") { if (this.freeform.key === undefined || this.freeform.key === "") {
@ -90,6 +90,9 @@ export default class TagRenderingConfig {
this.multiAnswer = json.multiAnswer ?? false this.multiAnswer = json.multiAnswer ?? false
if (json.mappings) { if (json.mappings) {
if(!Array.isArray(json.mappings)){
throw "Tagrendering has a 'mappings'-object, but expected a list ("+context+")"
}
this.mappings = json.mappings.map((mapping, i) => { this.mappings = json.mappings.map((mapping, i) => {
@ -254,5 +257,16 @@ export default class TagRenderingConfig {
return undefined; return undefined;
} }
public ExtractImages(isIcon: boolean): Set<string> {
const usedIcons = new Set<string>()
this.render?.ExtractImages(isIcon)?.forEach(usedIcons.add, usedIcons)
for (const mapping of this.mappings ?? []) {
mapping.then.ExtractImages(isIcon).forEach(usedIcons.add, usedIcons)
}
return usedIcons;
}
} }

View file

@ -1,11 +1,15 @@
import {AndOrTagConfigJson} from "./TagConfigJson"; import {AndOrTagConfigJson} from "./TagConfigJson";
/**
* A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.
* If the desired tags are missing and a question is defined, a question will be shown instead.
*/
export interface TagRenderingConfigJson { export interface TagRenderingConfigJson {
/** /**
* Renders this value. Note that "{key}"-parts are substituted by the corresponding values of the element. * Renders this value. Note that "{key}"-parts are substituted by the corresponding values of the element.
* If neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value. * If neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.
* *
* Note that this is a HTML-interpreted value, so you can add links as e.g. <a href='{website}'>{website}</a> * Note that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`
*/ */
render?: string | any, render?: string | any,

View file

@ -34,7 +34,7 @@ Adds the time that the data got loaded - pretty much the time of downloading fro
Calculating tags with Javascript Calculating tags with Javascript
-------------------------------- --------------------------------
In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. **\_lat**, **lon**, **\_country**), as detailed above. In some cases, it is useful to have some tags calculated based on other properties. Some useful tags are available by default (e.g. **lat**, **lon**, **\_country**), as detailed above.
It is also possible to calculate your own tags - but this requires some javascript knowledge. It is also possible to calculate your own tags - but this requires some javascript knowledge.
@ -46,7 +46,13 @@ Before proceeding, some warnings:
In the layer object, add a field **calculatedTags**, e.g.: In the layer object, add a field **calculatedTags**, e.g.:
"calculatedTags": { "\_someKey": "javascript-expression", "name": "feat.properties.name ?? feat.properties.ref ?? feat.properties.operator", "\_distanceCloserThen3Km": "feat.distanceTo( some\_lon, some\_lat) < 3 ? 'yes' : 'no'" } "calculatedTags": \[ "\_someKey=javascript-expression", "name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator", "\_distanceCloserThen3Km=feat.distanceTo( some\_lon, some\_lat) < 3 ? 'yes' : 'no'" \]
The above code will be executed for every feature in the layer. The feature is accessible as **feat** and is an amended geojson object: - **area** contains the surface area (in square meters) of the object - **lat** and **lon** contain the latitude and longitude Some advanced functions are available on **feat** as well:
* distanceTo
* overlapWith
* closest
### distanceTo ### distanceTo
@ -54,3 +60,15 @@ Calculates the distance between the feature and a specified point
* longitude * longitude
* latitude * latitude
### overlapWith
Gives a list of features from the specified layer which this feature overlaps with, the amount of overlap in m². The returned value is **{ feat: GeoJSONFeature, overlap: number}**
* ...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)
### closest
Given either a list of geojson features or a single layer name, gives the single object which is nearest to the feature. In the case of ways/polygons, only the centerpoint is considered.
* list of features

View file

@ -50,17 +50,19 @@ The preferred way to add your theme is via a Pull Request. A Pull Request is les
1) Fork this repository 1) Fork this repository
2) Go to `assets/themes` and create a new directory `yourtheme` 2) Go to `assets/themes` and create a new directory `yourtheme`
3) Create a new file `yourtheme.json`, paste the theme configuration in there. You can find your theme configuration in the customThemeBuilder (the tab with the *Floppy disk* icon) 3) Create a new file `yourtheme.json`, paste the theme configuration in there. You can find your theme configuration in the customThemeBuilder (the tab with the *Floppy disk* icon)
4) Copy all the images into this new directory: external assets can suddenly break and leak privacy 4) Copy all the images into this new directory. **No external sources are allowed!** External image sources leak privacy or can break.
- Make sure the license is suitable, preferable a Creative Commons license. Attribution can be added at the bottom of this document - Make sure the license is suitable, preferable a Creative Commons license or CC0-license.
- If an SVG version is available, use the SVG version - If an SVG version is available, use the SVG version
- Make sure all the links in `yourtheme.json` are updated. You can use `./assets/themes/yourtheme/yourimage.svg` instead of the HTML link - Make sure all the links in `yourtheme.json` are updated. You can use `./assets/themes/yourtheme/yourimage.svg` instead of the HTML link
- Create a file `license_info.json` in the theme directory, which contains metadata on every artwork source
5) Add your theme to the code base: 5) Add your theme to the code base:
- Open [AllKnownLayouts.ts](https://github.com/pietervdvn/MapComplete/blob/master/Customizations/AllKnownLayouts.ts) - Open [AllKnownLayouts.ts](https://github.com/pietervdvn/MapComplete/blob/master/Customizations/AllKnownLayouts.ts)
- Add an import statement, e.g. `import * as yourtheme from "../assets/themes/yourtheme/yourthemes.json";` - Add an import statement, e.g. `import * as yourtheme from "../assets/themes/yourtheme/yourthemes.json";`
- Add your theme to the `LayoutsList`, by adding a line `new LayoutConfig(yourtheme)` - Add your theme to the `LayoutsList`, by adding a line `new LayoutConfig(yourtheme)`
6) Test your theme: run the project as described [above](../README.md#Dev) 6) Add some finishing touches, such as a social image. See [this blog post](https://www.h3xed.com/web-and-internet/how-to-use-og-image-meta-tag-facebook-reddit) for some hints
7) Happy with your theme? Time to open a Pull Request! 7) Test your theme: run the project as described [above](../README.md#Dev)
8) Thanks a lot for improving MapComplete! 8) Happy with your theme? Time to open a Pull Request!
9) Thanks a lot for improving MapComplete!
The .JSON-format The .JSON-format
@ -84,6 +86,17 @@ Every field is documented in the source code itself - you can find them here:
There are few tags available that are calculated for convenience - e.g. the country an object is located at. [An overview of all these metatags is available here](Docs/CalculatedTags.md) There are few tags available that are calculated for convenience - e.g. the country an object is located at. [An overview of all these metatags is available here](Docs/CalculatedTags.md)
Some hints
------------
### Everything is HTML
All the texts are actually *HTML*-snippets, so you can use `<b>` to add bold, or `<img src=...>` to add images to mappings or tagrenderings.
Some remarks:
- links are disabled when answering a question (e.g. a link in a mapping) as it should trigger the answer - not trigger to open the link.
- If you include images, e.g. to clarify a type, make sure these are _icons_ or _diagrams_ - not actual pictures! If users see a picture, they think it is a picture of _that actual object_, not a type to clarify the type. An icon is however perceived as something more abstract.
Some pitfalls Some pitfalls
--------------- ---------------
@ -128,4 +141,6 @@ For example, in the [cyclofix-theme](https://mapcomplete.osm.org/cyclofix), ther
If all the layers are deselected except the bike wash layer, a shop having this tag will still match and will still show up as shop. If all the layers are deselected except the bike wash layer, a shop having this tag will still match and will still show up as shop.
### Not reading the .JSON-specs
There are a few advanced features to do fancy stuff available, which are documented only in the spec above - for example, reusing background images and substituting the colours or HTML-rendering. If you need advanced stuff, read it through!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 656 KiB

After

Width:  |  Height:  |  Size: 695 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 KiB

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 KiB

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 KiB

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 660 KiB

After

Width:  |  Height:  |  Size: 665 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 KiB

After

Width:  |  Height:  |  Size: 568 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 KiB

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 KiB

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 KiB

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 560 KiB

After

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 742 KiB

After

Width:  |  Height:  |  Size: 795 KiB

View file

@ -3239,3 +3239,81 @@
"2021-04-02", "Thierry1030", "nl", "cyclofix", "MapComplete 0.6.4a", 1, 2, "Adding data with #MapComplete for theme #cyclofix", "pietervdvn.github.io" "2021-04-02", "Thierry1030", "nl", "cyclofix", "MapComplete 0.6.4a", 1, 2, "Adding data with #MapComplete for theme #cyclofix", "pietervdvn.github.io"
"2021-04-02", "WandelenMetKinderen", "en", "campersite", "MapComplete 0.6.4b", 0, 5, "Adding data with #MapComplete for theme #campersite", "mapcomplete.osm.be" "2021-04-02", "WandelenMetKinderen", "en", "campersite", "MapComplete 0.6.4b", 0, 5, "Adding data with #MapComplete for theme #campersite", "mapcomplete.osm.be"
"2021-04-02", "WandelenMetKinderen", "nl", "playgrounds", "MapComplete 0.6.4b", 0, 2, "Adding data with #MapComplete for theme #playgrounds", "mapcomplete.osm.be" "2021-04-02", "WandelenMetKinderen", "nl", "playgrounds", "MapComplete 0.6.4b", 0, 2, "Adding data with #MapComplete for theme #playgrounds", "mapcomplete.osm.be"
"2021-04-03", "Awo", "en", "trees", "MapComplete 0.6.4a", 0, 2, "Adding data with #MapComplete for theme #trees", "pietervdvn.github.io"
"2021-04-03", "dkf2010", "en", "benches", "MapComplete 0.6.4b", 0, 6, "Adding data with #MapComplete for theme #benches", "mapcomplete.osm.be"
"2021-04-03", "dkf2010", "en", "charging_stations", "MapComplete 0.6.4b", 0, 2, "Adding data with #MapComplete for theme #charging_stations", "mapcomplete.osm.be"
"2021-04-03", "GOwin", "en", "HailHydrant", "MapComplete 0.6.4a", 0, 1, "Adding data with #MapComplete for theme #HailHydrant", "pietervdvn.github.io"
"2021-04-03", "GOwin", "en", "HailHydrant", "MapComplete 0.6.4a", 0, 4, "Adding data with #MapComplete for theme #HailHydrant", "pietervdvn.github.io"
"2021-04-03", "GOwin", "en", "HailHydrant", "MapComplete 0.6.4a", 1, 3, "Adding data with #MapComplete for theme #HailHydrant", "pietervdvn.github.io"
"2021-04-03", "hke2912", "en", "benches", "MapComplete 0.6.4a", 4, 9, "Adding data with #MapComplete for theme #benches", "pietervdvn.github.io"
"2021-04-03", "jhuanjho", "en", "drinking_water", "MapComplete 0.6.4b", 0, 2, "Adding data with #MapComplete for theme #drinking_water", "mapcomplete.osm.be"
"2021-04-03", "Kinjkajh", "nl", "surveillance", "MapComplete 0.6.4a", 4, 17, "Adding data with #MapComplete for theme #surveillance", "pietervdvn.github.io"
"2021-04-03", "Koen Rijnsent", "en", "personal", "MapComplete 0.6.4b", 8, 28, "Adding data with #MapComplete for theme #personal", "mapcomplete.osm.be"
"2021-04-03", "MAGONA", "en", "trees", "MapComplete 0.6.4a", 0, 1, "Adding data with #MapComplete for theme #trees", "pietervdvn.github.io"
"2021-04-03", "MAGONA", "en", "trees", "MapComplete 0.6.4a", 0, 1, "Adding data with #MapComplete for theme #trees", "pietervdvn.github.io"
"2021-04-03", "Peter Elderson", "en", "stolpersteine", "MapComplete 0.6.4b", 2, 20, "Adding data with #MapComplete for theme #stolpersteine", "mapcomplete.osm.be"
"2021-04-03", "Peter Elderson", "en", "stolpersteine", "MapComplete 0.6.4b", 37, 142, "Adding data with #MapComplete for theme #stolpersteine", "mapcomplete.osm.be"
"2021-04-03", "Peter Elderson", "en", "stolpersteine", "MapComplete 0.6.4b", 6, 61, "Adding data with #MapComplete for theme #stolpersteine", "mapcomplete.osm.be"
"2021-04-03", "Pieter Nuytinck", "en", "bookcases", "MapComplete 0.6.4a", 1, 5, "Adding data with #MapComplete for theme #bookcases", "pietervdvn.github.io"
"2021-04-03", "WinstonSmith", "en", "drinking_water", "MapComplete 0.6.4b", 1, 3, "Adding data with #MapComplete for theme #drinking_water", "mapcomplete.osm.be"
"2021-04-04", "dkf2010", "en", "benches", "MapComplete 0.6.4b", 0, 1, "Adding data with #MapComplete for theme #benches", "mapcomplete.osm.be"
"2021-04-04", "Fauranië", "en", "benches", "MapComplete 0.6.4b", 2, 2, "Adding data with #MapComplete for theme #benches", "mapcomplete.osm.be"
"2021-04-04", "Fauranië", "en", "ghostbikes", "MapComplete 0.6.4b", 1, 0, "Adding data with #MapComplete for theme #ghostbikes", "mapcomplete.osm.be"
"2021-04-04", "GOwin", "en", "HailHydrant", "MapComplete 0.6.4a", 0, 2, "Adding data with #MapComplete for theme #HailHydrant", "pietervdvn.github.io"
"2021-04-04", "JuanjoMC", "en", "cyclofix", "MapComplete 0.6.4b", 0, 4, "Adding data with #MapComplete for theme #cyclofix", "mapcomplete.osm.be"
"2021-04-04", "M!dgard", "nl", "fire", "MapComplete 0.6.4b", 1, 2, "Adding data with #MapComplete for theme #fire", "mapcomplete.braindeaddev.com"
"2021-04-04", "M!dgard", "nl", "fire", "MapComplete 0.6.4b", 2, 4, "Adding data with #MapComplete for theme #fire", "mapcomplete.braindeaddev.com"
"2021-04-04", "Peter Elderson", "en", "wandelknooppunten", "MapComplete 0.6.4b", 0, 1, "Adding data with #MapComplete for theme #wandelknooppunten", "mapcomplete.osm.be"
"2021-04-04", "Pieter Nuytinck", "en", "bookcases", "MapComplete 0.6.5", 1, 1, "Adding data with #MapComplete for theme #bookcases", "pietervdvn.github.io"
"2021-04-04", "Pieter Vander Vennet", "nl", "fritures", "MapComplete 0.6.4b", 1, 2, "Adding data with #MapComplete for theme #fritures", "mapcomplete.osm.be"
"2021-04-04", "Thierry1030", "nl", "cyclofix", "MapComplete 0.6.5", 1, 6, "Adding data with #MapComplete for theme #cyclofix", "pietervdvn.github.io"
"2021-04-04", "WinstonSmith", "en", "drinking_water", "MapComplete 0.6.4b", 1, 1, "Adding data with #MapComplete for theme #drinking_water", "mapcomplete.osm.be"
"2021-04-05", "antonbundle", "en", "bookcases", "MapComplete 0.6.4b", 1, 4, "Adding data with #MapComplete for theme #bookcases", "mapcomplete.osm.be"
"2021-04-05", "Awo", "en", "artworks", "MapComplete 0.6.5c", 0, 2, "Adding data with #MapComplete for theme #artworks", "pietervdvn.github.io"
"2021-04-05", "dentonny", "nl", "cyclofix", "MapComplete 0.6.5", 1, 2, "Adding data with #MapComplete for theme #cyclofix", "pietervdvn.github.io"
"2021-04-05", "dmlu", "en", "https://raw.githubusercontent.com/osmbe/play/master/mapcomplete/urban_fossils/urban_fossils.json", "MapComplete 0.6.4c", 1, 1, "Adding data with #MapComplete for theme #https://raw.githubusercontent.com/osmbe/play/master/mapcomplete/urban_fossils/urban_fossils.json", "mapcomplete.osm.be"
"2021-04-05", "Fauranië", "en", "benches", "MapComplete 0.6.4b", 3, 3, "Adding data with #MapComplete for theme #benches", "mapcomplete.osm.be"
"2021-04-05", "Fauranië", "en", "charging_stations", "MapComplete 0.6.4b", 2, 2, "Adding data with #MapComplete for theme #charging_stations", "mapcomplete.osm.be"
"2021-04-05", "Fauranië", "en", "cyclofix", "MapComplete 0.6.4b", 2, 2, "Adding data with #MapComplete for theme #cyclofix", "mapcomplete.osm.be"
"2021-04-05", "Fauranië", "en", "playgrounds", "MapComplete 0.6.4b", 2, 2, "Adding data with #MapComplete for theme #playgrounds", "mapcomplete.osm.be"
"2021-04-05", "Fauranië", "nl", "basketswings", "MapComplete 0.6.4b", 1, 0, "Adding data with #MapComplete for theme #basketswings", "mapcomplete.osm.be"
"2021-04-05", "lunaticstraydog", "en", "climbing", "MapComplete 0.6.4b", 1, 9, "Adding data with #MapComplete for theme #climbing", "mapcomplete.osm.be"
"2021-04-05", "M!dgard", "nl", "aed", "MapComplete 0.6.4b", 0, 3, "Adding data with #MapComplete for theme #aed", "mapcomplete.braindeaddev.com"
"2021-04-05", "Michel Stuyts", "en", "benches", "MapComplete 0.6.4b", 0, 2, "Adding data with #MapComplete for theme #benches", "mapcomplete.osm.be"
"2021-04-05", "Michel Stuyts", "en", "benches", "MapComplete 0.6.4b", 1, 2, "Adding data with #MapComplete for theme #benches", "mapcomplete.osm.be"
"2021-04-05", "Peter Elderson", "en", "stolpersteine", "MapComplete 0.6.4b", 0, 4, "Adding data with #MapComplete for theme #stolpersteine", "mapcomplete.osm.be"
"2021-04-05", "Pieter Vander Vennet", "nl", "grb", "MapComplete 0.6.4b", 0, 8, "Adding data with #MapComplete for theme #grb", "mapcomplete.osm.be"
"2021-04-05", "SuSanne Grittner", "de", "ghostbikes", "MapComplete 0.6.5c", 0, 1, "Adding data with #MapComplete for theme #ghostbikes", "pietervdvn.github.io"
"2021-04-05", "Technopolice_newBiE", "en", "surveillance", "MapComplete 0.6.4-unlocked", 92, 139, "Adding data with #MapComplete for theme #surveillance", "pietervdvn.github.io"
"2021-04-05", "Thierry1030", "nl", "cyclofix", "MapComplete 0.6.5", 1, 2, "Adding data with #MapComplete for theme #cyclofix", "pietervdvn.github.io"
"2021-04-05", "Tim Couwelier", "nl", "aed", "MapComplete 0.6.5", 1, 2, "Adding data with #MapComplete for theme #aed", "pietervdvn.github.io"
"2021-04-05", "WinstonSmith", "en", "drinking_water", "MapComplete 0.6.4b", 0, 1, "Adding data with #MapComplete for theme #drinking_water", "mapcomplete.osm.be"
"2021-04-06", "BS97n", "de", "cyclofix", "MapComplete 0.6.4d", 2, 2, "Adding data with #MapComplete for theme #cyclofix", "mapcomplete.osm.be"
"2021-04-06", "Pieter Vander Vennet", "en", "https://raw.githubusercontent.com/osmbe/play/master/mapcomplete/urban_fossils/urban_fossils.json", "MapComplete 0.6.4c", 1, 1, "Adding data with #MapComplete for theme #https://raw.githubusercontent.com/osmbe/play/master/mapcomplete/urban_fossils/urban_fossils.json", "mapcomplete.osm.be"
"2021-04-07", "bvrslypp", "en", "surveillance", "MapComplete 0.6.6a", 1, 4, "Adding data with #MapComplete for theme #surveillance", "mapcomplete.osm.be"
"2021-04-07", "dkf2010", "en", "benches", "MapComplete 0.6.6a", 1, 2, "Adding data with #MapComplete for theme #benches", "mapcomplete.osm.be"
"2021-04-07", "joost schouppe", "nl", "cyclofix", "MapComplete 0.6.6a", 0, 1, "Adding data with #MapComplete for theme #cyclofix", "mapcomplete.osm.be"
"2021-04-07", "joost schouppe", "nl", "personal", "MapComplete 0.6.6a", 0, 1, "Adding data with #MapComplete for theme #personal", "mapcomplete.osm.be"
"2021-04-07", "L'imaginaire", "en", "benches", "MapComplete 0.6.6a", 0, 2, "Adding data with #MapComplete for theme #benches", "mapcomplete.osm.be"
"2021-04-07", "L'imaginaire", "en", "playgrounds", "MapComplete 0.6.6a", 0, 2, "Adding data with #MapComplete for theme #playgrounds", "mapcomplete.osm.be"
"2021-04-07", "L'imaginaire", "nl", "cyclofix", "MapComplete 0.6.6a", 0, 2, "Adding data with #MapComplete for theme #cyclofix", "mapcomplete.osm.be"
"2021-04-07", "Pieter Vander Vennet", "en", "artworks", "MapComplete 0.6.6a", 1, 1, "Adding data with #MapComplete for theme #artworks", "mapcomplete.osm.be"
"2021-04-07", "Pieter Vander Vennet", "en", "benches", "MapComplete 0.6.6a", 1, 1, "Adding data with #MapComplete for theme #benches", "mapcomplete.osm.be"
"2021-04-07", "Pieter Vander Vennet", "en", "cyclofix", "MapComplete 0.6.6a", 1, 2, "Adding data with #MapComplete for theme #cyclofix", "mapcomplete.osm.be"
"2021-04-07", "ttt1234", "en", "surveillance", "MapComplete 0.6.6", 10, 31, "Adding data with #MapComplete for theme #surveillance", "pietervdvn.github.io"
"2021-04-07", "ttt1234", "nl", "fritures", "MapComplete 0.6.6", 1, 1, "Adding data with #MapComplete for theme #fritures", "pietervdvn.github.io"
"2021-04-07", "Wim L", "en", "bookcases", "MapComplete 0.6.6b", 1, 1, "Adding data with #MapComplete for theme #bookcases", "mapcomplete.osm.be"
"2021-04-08", "boute002", "nl", "drinking_water", "MapComplete 0.6.6b", 1, 0, "Adding data with #MapComplete for theme #drinking_water", "mapcomplete.osm.be"
"2021-04-08", "bvrslypp", "en", "surveillance", "MapComplete 0.6.6", 1, 1, "Adding data with #MapComplete for theme #surveillance", "pietervdvn.github.io"
"2021-04-08", "bvrslypp", "en", "surveillance", "MapComplete 0.6.6", 2, 3, "Adding data with #MapComplete for theme #surveillance", "pietervdvn.github.io"
"2021-04-08", "dkf2010", "en", "benches", "MapComplete 0.6.6b", 0, 2, "Adding data with #MapComplete for theme #benches", "mapcomplete.osm.be"
"2021-04-08", "Edlocks", "en", "cyclofix", "MapComplete 0.6.6", 1, 9, "Adding data with #MapComplete for theme #cyclofix", "pietervdvn.github.io"
"2021-04-08", "Erin76", "nl", "playgrounds", "MapComplete 0.6.4b", 0, 13, "Adding data with #MapComplete for theme #playgrounds", "mapcomplete.braindeaddev.com"
"2021-04-08", "joost schouppe", "nl", "personal", "MapComplete 0.6.6b", 1, 2, "Adding data with #MapComplete for theme #personal", "mapcomplete.osm.be"
"2021-04-08", "Koen Rijnsent", "en", "personal", "MapComplete 0.6.6b", 0, 4, "Adding data with #MapComplete for theme #personal", "mapcomplete.osm.be"
"2021-04-08", "Koen Rijnsent", "en", "personal", "MapComplete 0.6.6b", 7, 12, "Adding data with #MapComplete for theme #personal", "mapcomplete.osm.be"
"2021-04-08", "Pieter Vander Vennet", "nl", "nature", "MapComplete 0.6.6b", 1, 4, "Adding data with #MapComplete for theme #nature", "mapcomplete.osm.be"
"2021-04-08", "Pieter Vander Vennet", "nl", "nature", "MapComplete 0.6.6b", 2, 5, "Adding data with #MapComplete for theme #nature", "mapcomplete.osm.be"
"2021-04-08", "sjokomoeske", "en", "benches", "MapComplete 0.6.6b", 3, 4, "Adding data with #MapComplete for theme #benches", "mapcomplete.osm.be"
"2021-04-08", "sjokomoeske", "en", "playgrounds", "MapComplete 0.6.6b", 1, 2, "Adding data with #MapComplete for theme #playgrounds", "mapcomplete.osm.be"
"2021-04-08", "ttt1234", "en", "surveillance", "MapComplete 0.6.6", 3, 6, "Adding data with #MapComplete for theme #surveillance", "pietervdvn.github.io"

1 2020-05-27 Pieter Vander Vennet null null MapComplete 0.0.0 0 0 Testing Mapcomplete 0.0.0 null
3239 2021-04-02 Thierry1030 nl cyclofix MapComplete 0.6.4a 1 2 Adding data with #MapComplete for theme #cyclofix pietervdvn.github.io
3240 2021-04-02 WandelenMetKinderen en campersite MapComplete 0.6.4b 0 5 Adding data with #MapComplete for theme #campersite mapcomplete.osm.be
3241 2021-04-02 WandelenMetKinderen nl playgrounds MapComplete 0.6.4b 0 2 Adding data with #MapComplete for theme #playgrounds mapcomplete.osm.be
3242 2021-04-03 Awo en trees MapComplete 0.6.4a 0 2 Adding data with #MapComplete for theme #trees pietervdvn.github.io
3243 2021-04-03 dkf2010 en benches MapComplete 0.6.4b 0 6 Adding data with #MapComplete for theme #benches mapcomplete.osm.be
3244 2021-04-03 dkf2010 en charging_stations MapComplete 0.6.4b 0 2 Adding data with #MapComplete for theme #charging_stations mapcomplete.osm.be
3245 2021-04-03 GOwin en HailHydrant MapComplete 0.6.4a 0 1 Adding data with #MapComplete for theme #HailHydrant pietervdvn.github.io
3246 2021-04-03 GOwin en HailHydrant MapComplete 0.6.4a 0 4 Adding data with #MapComplete for theme #HailHydrant pietervdvn.github.io
3247 2021-04-03 GOwin en HailHydrant MapComplete 0.6.4a 1 3 Adding data with #MapComplete for theme #HailHydrant pietervdvn.github.io
3248 2021-04-03 hke2912 en benches MapComplete 0.6.4a 4 9 Adding data with #MapComplete for theme #benches pietervdvn.github.io
3249 2021-04-03 jhuanjho en drinking_water MapComplete 0.6.4b 0 2 Adding data with #MapComplete for theme #drinking_water mapcomplete.osm.be
3250 2021-04-03 Kinjkajh nl surveillance MapComplete 0.6.4a 4 17 Adding data with #MapComplete for theme #surveillance pietervdvn.github.io
3251 2021-04-03 Koen Rijnsent en personal MapComplete 0.6.4b 8 28 Adding data with #MapComplete for theme #personal mapcomplete.osm.be
3252 2021-04-03 MAGONA en trees MapComplete 0.6.4a 0 1 Adding data with #MapComplete for theme #trees pietervdvn.github.io
3253 2021-04-03 MAGONA en trees MapComplete 0.6.4a 0 1 Adding data with #MapComplete for theme #trees pietervdvn.github.io
3254 2021-04-03 Peter Elderson en stolpersteine MapComplete 0.6.4b 2 20 Adding data with #MapComplete for theme #stolpersteine mapcomplete.osm.be
3255 2021-04-03 Peter Elderson en stolpersteine MapComplete 0.6.4b 37 142 Adding data with #MapComplete for theme #stolpersteine mapcomplete.osm.be
3256 2021-04-03 Peter Elderson en stolpersteine MapComplete 0.6.4b 6 61 Adding data with #MapComplete for theme #stolpersteine mapcomplete.osm.be
3257 2021-04-03 Pieter Nuytinck en bookcases MapComplete 0.6.4a 1 5 Adding data with #MapComplete for theme #bookcases pietervdvn.github.io
3258 2021-04-03 WinstonSmith en drinking_water MapComplete 0.6.4b 1 3 Adding data with #MapComplete for theme #drinking_water mapcomplete.osm.be
3259 2021-04-04 dkf2010 en benches MapComplete 0.6.4b 0 1 Adding data with #MapComplete for theme #benches mapcomplete.osm.be
3260 2021-04-04 Fauranië en benches MapComplete 0.6.4b 2 2 Adding data with #MapComplete for theme #benches mapcomplete.osm.be
3261 2021-04-04 Fauranië en ghostbikes MapComplete 0.6.4b 1 0 Adding data with #MapComplete for theme #ghostbikes mapcomplete.osm.be
3262 2021-04-04 GOwin en HailHydrant MapComplete 0.6.4a 0 2 Adding data with #MapComplete for theme #HailHydrant pietervdvn.github.io
3263 2021-04-04 JuanjoMC en cyclofix MapComplete 0.6.4b 0 4 Adding data with #MapComplete for theme #cyclofix mapcomplete.osm.be
3264 2021-04-04 M!dgard nl fire MapComplete 0.6.4b 1 2 Adding data with #MapComplete for theme #fire mapcomplete.braindeaddev.com
3265 2021-04-04 M!dgard nl fire MapComplete 0.6.4b 2 4 Adding data with #MapComplete for theme #fire mapcomplete.braindeaddev.com
3266 2021-04-04 Peter Elderson en wandelknooppunten MapComplete 0.6.4b 0 1 Adding data with #MapComplete for theme #wandelknooppunten mapcomplete.osm.be
3267 2021-04-04 Pieter Nuytinck en bookcases MapComplete 0.6.5 1 1 Adding data with #MapComplete for theme #bookcases pietervdvn.github.io
3268 2021-04-04 Pieter Vander Vennet nl fritures MapComplete 0.6.4b 1 2 Adding data with #MapComplete for theme #fritures mapcomplete.osm.be
3269 2021-04-04 Thierry1030 nl cyclofix MapComplete 0.6.5 1 6 Adding data with #MapComplete for theme #cyclofix pietervdvn.github.io
3270 2021-04-04 WinstonSmith en drinking_water MapComplete 0.6.4b 1 1 Adding data with #MapComplete for theme #drinking_water mapcomplete.osm.be
3271 2021-04-05 antonbundle en bookcases MapComplete 0.6.4b 1 4 Adding data with #MapComplete for theme #bookcases mapcomplete.osm.be
3272 2021-04-05 Awo en artworks MapComplete 0.6.5c 0 2 Adding data with #MapComplete for theme #artworks pietervdvn.github.io
3273 2021-04-05 dentonny nl cyclofix MapComplete 0.6.5 1 2 Adding data with #MapComplete for theme #cyclofix pietervdvn.github.io
3274 2021-04-05 dmlu en https://raw.githubusercontent.com/osmbe/play/master/mapcomplete/urban_fossils/urban_fossils.json MapComplete 0.6.4c 1 1 Adding data with #MapComplete for theme #https://raw.githubusercontent.com/osmbe/play/master/mapcomplete/urban_fossils/urban_fossils.json mapcomplete.osm.be
3275 2021-04-05 Fauranië en benches MapComplete 0.6.4b 3 3 Adding data with #MapComplete for theme #benches mapcomplete.osm.be
3276 2021-04-05 Fauranië en charging_stations MapComplete 0.6.4b 2 2 Adding data with #MapComplete for theme #charging_stations mapcomplete.osm.be
3277 2021-04-05 Fauranië en cyclofix MapComplete 0.6.4b 2 2 Adding data with #MapComplete for theme #cyclofix mapcomplete.osm.be
3278 2021-04-05 Fauranië en playgrounds MapComplete 0.6.4b 2 2 Adding data with #MapComplete for theme #playgrounds mapcomplete.osm.be
3279 2021-04-05 Fauranië nl basketswings MapComplete 0.6.4b 1 0 Adding data with #MapComplete for theme #basketswings mapcomplete.osm.be
3280 2021-04-05 lunaticstraydog en climbing MapComplete 0.6.4b 1 9 Adding data with #MapComplete for theme #climbing mapcomplete.osm.be
3281 2021-04-05 M!dgard nl aed MapComplete 0.6.4b 0 3 Adding data with #MapComplete for theme #aed mapcomplete.braindeaddev.com
3282 2021-04-05 Michel Stuyts en benches MapComplete 0.6.4b 0 2 Adding data with #MapComplete for theme #benches mapcomplete.osm.be
3283 2021-04-05 Michel Stuyts en benches MapComplete 0.6.4b 1 2 Adding data with #MapComplete for theme #benches mapcomplete.osm.be
3284 2021-04-05 Peter Elderson en stolpersteine MapComplete 0.6.4b 0 4 Adding data with #MapComplete for theme #stolpersteine mapcomplete.osm.be
3285 2021-04-05 Pieter Vander Vennet nl grb MapComplete 0.6.4b 0 8 Adding data with #MapComplete for theme #grb mapcomplete.osm.be
3286 2021-04-05 SuSanne Grittner de ghostbikes MapComplete 0.6.5c 0 1 Adding data with #MapComplete for theme #ghostbikes pietervdvn.github.io
3287 2021-04-05 Technopolice_newBiE en surveillance MapComplete 0.6.4-unlocked 92 139 Adding data with #MapComplete for theme #surveillance pietervdvn.github.io
3288 2021-04-05 Thierry1030 nl cyclofix MapComplete 0.6.5 1 2 Adding data with #MapComplete for theme #cyclofix pietervdvn.github.io
3289 2021-04-05 Tim Couwelier nl aed MapComplete 0.6.5 1 2 Adding data with #MapComplete for theme #aed pietervdvn.github.io
3290 2021-04-05 WinstonSmith en drinking_water MapComplete 0.6.4b 0 1 Adding data with #MapComplete for theme #drinking_water mapcomplete.osm.be
3291 2021-04-06 BS97n de cyclofix MapComplete 0.6.4d 2 2 Adding data with #MapComplete for theme #cyclofix mapcomplete.osm.be
3292 2021-04-06 Pieter Vander Vennet en https://raw.githubusercontent.com/osmbe/play/master/mapcomplete/urban_fossils/urban_fossils.json MapComplete 0.6.4c 1 1 Adding data with #MapComplete for theme #https://raw.githubusercontent.com/osmbe/play/master/mapcomplete/urban_fossils/urban_fossils.json mapcomplete.osm.be
3293 2021-04-07 bvrslypp en surveillance MapComplete 0.6.6a 1 4 Adding data with #MapComplete for theme #surveillance mapcomplete.osm.be
3294 2021-04-07 dkf2010 en benches MapComplete 0.6.6a 1 2 Adding data with #MapComplete for theme #benches mapcomplete.osm.be
3295 2021-04-07 joost schouppe nl cyclofix MapComplete 0.6.6a 0 1 Adding data with #MapComplete for theme #cyclofix mapcomplete.osm.be
3296 2021-04-07 joost schouppe nl personal MapComplete 0.6.6a 0 1 Adding data with #MapComplete for theme #personal mapcomplete.osm.be
3297 2021-04-07 L'imaginaire en benches MapComplete 0.6.6a 0 2 Adding data with #MapComplete for theme #benches mapcomplete.osm.be
3298 2021-04-07 L'imaginaire en playgrounds MapComplete 0.6.6a 0 2 Adding data with #MapComplete for theme #playgrounds mapcomplete.osm.be
3299 2021-04-07 L'imaginaire nl cyclofix MapComplete 0.6.6a 0 2 Adding data with #MapComplete for theme #cyclofix mapcomplete.osm.be
3300 2021-04-07 Pieter Vander Vennet en artworks MapComplete 0.6.6a 1 1 Adding data with #MapComplete for theme #artworks mapcomplete.osm.be
3301 2021-04-07 Pieter Vander Vennet en benches MapComplete 0.6.6a 1 1 Adding data with #MapComplete for theme #benches mapcomplete.osm.be
3302 2021-04-07 Pieter Vander Vennet en cyclofix MapComplete 0.6.6a 1 2 Adding data with #MapComplete for theme #cyclofix mapcomplete.osm.be
3303 2021-04-07 ttt1234 en surveillance MapComplete 0.6.6 10 31 Adding data with #MapComplete for theme #surveillance pietervdvn.github.io
3304 2021-04-07 ttt1234 nl fritures MapComplete 0.6.6 1 1 Adding data with #MapComplete for theme #fritures pietervdvn.github.io
3305 2021-04-07 Wim L en bookcases MapComplete 0.6.6b 1 1 Adding data with #MapComplete for theme #bookcases mapcomplete.osm.be
3306 2021-04-08 boute002 nl drinking_water MapComplete 0.6.6b 1 0 Adding data with #MapComplete for theme #drinking_water mapcomplete.osm.be
3307 2021-04-08 bvrslypp en surveillance MapComplete 0.6.6 1 1 Adding data with #MapComplete for theme #surveillance pietervdvn.github.io
3308 2021-04-08 bvrslypp en surveillance MapComplete 0.6.6 2 3 Adding data with #MapComplete for theme #surveillance pietervdvn.github.io
3309 2021-04-08 dkf2010 en benches MapComplete 0.6.6b 0 2 Adding data with #MapComplete for theme #benches mapcomplete.osm.be
3310 2021-04-08 Edlocks en cyclofix MapComplete 0.6.6 1 9 Adding data with #MapComplete for theme #cyclofix pietervdvn.github.io
3311 2021-04-08 Erin76 nl playgrounds MapComplete 0.6.4b 0 13 Adding data with #MapComplete for theme #playgrounds mapcomplete.braindeaddev.com
3312 2021-04-08 joost schouppe nl personal MapComplete 0.6.6b 1 2 Adding data with #MapComplete for theme #personal mapcomplete.osm.be
3313 2021-04-08 Koen Rijnsent en personal MapComplete 0.6.6b 0 4 Adding data with #MapComplete for theme #personal mapcomplete.osm.be
3314 2021-04-08 Koen Rijnsent en personal MapComplete 0.6.6b 7 12 Adding data with #MapComplete for theme #personal mapcomplete.osm.be
3315 2021-04-08 Pieter Vander Vennet nl nature MapComplete 0.6.6b 1 4 Adding data with #MapComplete for theme #nature mapcomplete.osm.be
3316 2021-04-08 Pieter Vander Vennet nl nature MapComplete 0.6.6b 2 5 Adding data with #MapComplete for theme #nature mapcomplete.osm.be
3317 2021-04-08 sjokomoeske en benches MapComplete 0.6.6b 3 4 Adding data with #MapComplete for theme #benches mapcomplete.osm.be
3318 2021-04-08 sjokomoeske en playgrounds MapComplete 0.6.6b 1 2 Adding data with #MapComplete for theme #playgrounds mapcomplete.osm.be
3319 2021-04-08 ttt1234 en surveillance MapComplete 0.6.6 3 6 Adding data with #MapComplete for theme #surveillance pietervdvn.github.io

108
Docs/URL_Parameters.md Normal file
View file

@ -0,0 +1,108 @@
custom-css
------------
If specified, the custom css from the given link will be loaded additionaly
test
------
If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org
The default value is _false_
layout
--------
The layout to load into MapComplete
userlayout
------------
The default value is _false_
layer-control-toggle
----------------------
Whether or not the layer control is shown
The default value is _false_
tab
-----
The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >50 changesets)
The default value is _0_
z
---
The initial/current zoom level
The default value is set by the loaded theme
lat
-----
The initial/current latitude
The default value is set by the loaded theme
lon
-----
The initial/current longitude of the app
The default value is set by the loaded theme
fs-userbadge
--------------
Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode.
The default value is _true_
fs-search
-----------
Disables/Enables the search bar
The default value is _true_
fs-layers
-----------
Disables/Enables the layer control
The default value is _true_
fs-add-new
------------
Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)
The default value is _true_
fs-welcome-message
--------------------
Disables/enables the help menu or welcome message
The default value is _true_
fs-iframe
-----------
Disables/Enables the iframe-popup
The default value is _false_
fs-more-quests
----------------
Disables/Enables the 'More Quests'-tab in the welcome message
The default value is _true_
fs-share-screen
-----------------
Disables/Enables the 'Share-screen'-tab in the welcome message
The default value is _true_
fs-geolocation
----------------
Disables/Enables the geolocation button
The default value is _true_
debug
-------
If true, shows some extra debugging help such as all the available tags on every object
The default value is _false_
oauth_token
-------------
Used to complete the login
No default value set
background
------------
The id of the background layer to start with
The default value is set by the loaded theme
layer-<layer-id>
-----------------
Wether or not layer with _<layer-id>_ is shown
The default value is _true_

View file

@ -36,6 +36,9 @@ import Translations from "./UI/i18n/Translations";
import MapControlButton from "./UI/MapControlButton"; import MapControlButton from "./UI/MapControlButton";
import Combine from "./UI/Base/Combine"; import Combine from "./UI/Base/Combine";
import SelectedFeatureHandler from "./Logic/Actors/SelectedFeatureHandler"; import SelectedFeatureHandler from "./Logic/Actors/SelectedFeatureHandler";
import LZString from "lz-string";
import {LayoutConfigJson} from "./Customizations/JSON/LayoutConfigJson";
import AttributionPanel from "./UI/BigComponents/AttributionPanel";
export class InitUiElements { export class InitUiElements {
@ -209,7 +212,17 @@ export class InitUiElements {
hashFromLocalStorage.setData(hash); hashFromLocalStorage.setData(hash);
dedicatedHashFromLocalStorage.setData(hash); dedicatedHashFromLocalStorage.setData(hash);
} }
const layoutToUse = new LayoutConfig(JSON.parse(atob(hash)), false);
let json = {}
try{
json = JSON.parse(atob(hash));
} catch (e) {
// We try to decode with lz-string
json = JSON.parse( Utils.UnMinify(LZString.decompressFromBase64(hash)))
}
// @ts-ignore
const layoutToUse = new LayoutConfig(json, false);
userLayoutParam.setData(layoutToUse.id); userLayoutParam.setData(layoutToUse.id);
return layoutToUse; return layoutToUse;
} catch (e) { } catch (e) {
@ -275,11 +288,7 @@ export class InitUiElements {
const copyrightNotice = const copyrightNotice =
new ScrollableFullScreen( new ScrollableFullScreen(
() => Translations.t.general.attribution.attributionTitle.Clone(), () => Translations.t.general.attribution.attributionTitle.Clone(),
() => new Combine([ () => new AttributionPanel(State.state.layoutToUse),
Translations.t.general.attribution.attributionContent,
"<br/>",
new Attribution(undefined, undefined, State.state.layoutToUse, undefined)
]),
"copyright" "copyright"
) )
@ -288,7 +297,7 @@ export class InitUiElements {
copyrightNotice, copyrightNotice,
new MapControlButton(Svg.osm_copyright_svg()), new MapControlButton(Svg.osm_copyright_svg()),
copyrightNotice.isShown copyrightNotice.isShown
).SetClass("p-0.5 md:hidden") ).SetClass("p-0.5")
new Combine([copyrightButton, checkbox]) new Combine([copyrightButton, checkbox])
.AttachTo("bottom-left"); .AttachTo("bottom-left");
@ -416,11 +425,12 @@ export class InitUiElements {
} }
const newPointDialogIsShown = new UIEventSource<boolean>(false);
const addNewPoint = new ScrollableFullScreen( const addNewPoint = new ScrollableFullScreen(
() => Translations.t.general.add.title.Clone(), () => Translations.t.general.add.title.Clone(),
() => new SimpleAddUI(), () => new SimpleAddUI(newPointDialogIsShown),
"new"); "new",
newPointDialogIsShown)
addNewPoint.isShown.addCallback(isShown => { addNewPoint.isShown.addCallback(isShown => {
if (!isShown) { if (!isShown) {
State.state.LastClickLocation.setData(undefined) State.state.LastClickLocation.setData(undefined)
@ -436,5 +446,6 @@ export class InitUiElements {
); );
}); });
} }
} }

View file

@ -81,7 +81,7 @@ export class ImageSearcher extends UIEventSource<{ key: string, url: string }[]>
let mapillary = tags.mapillary; let mapillary = tags.mapillary;
const prefix = "https://www.mapillary.com/map/im/"; const prefix = "https://www.mapillary.com/map/im/";
let regex = /https?:\/\/www.mapillary.com\/app\/.*&pKey=([^&]*)/ let regex = /https?:\/\/www.mapillary.com\/app\/.*pKey=([^&]*).*/
let match = mapillary.match(regex); let match = mapillary.match(regex);
if (match) { if (match) {
mapillary = match[1]; mapillary = match[1];

View file

@ -27,6 +27,10 @@ export default class SelectedFeatureHandler {
featureSource.features.addCallback(_ => self.selectFeature()); featureSource.features.addCallback(_ => self.selectFeature());
selectedFeature.addCallback(feature => { selectedFeature.addCallback(feature => {
if(feature === undefined){
hash.setData("")
}
const h = feature?.properties?.id; const h = feature?.properties?.id;
if(h !== undefined){ if(h !== undefined){
hash.setData(h) hash.setData(h)

View file

@ -18,11 +18,11 @@ export default class SubstitutingTag implements TagsFilter {
this._value = value; this._value = value;
} }
private static substituteString(template: string, dict: any): string { public static substituteString(template: string, dict: any): string {
for (const k in dict) { for (const k in dict) {
template = template.replace(new RegExp("\\{" + k + "\\}", 'g'), dict[k]) template = template.replace(new RegExp("\\{" + k + "\\}", 'g'), dict[k])
} }
return template; return template.replace(/{.*}/g, "");
} }
asHumanString(linkToWiki: boolean, shorten: boolean, properties) { asHumanString(linkToWiki: boolean, shorten: boolean, properties) {

View file

@ -38,6 +38,10 @@ export class QueryParameters {
QueryParameters.knownSources[key] = source; QueryParameters.knownSources[key] = source;
} }
} }
window["mapcomplete_query_parameter_overview"] = () => {
console.log(QueryParameters.GenerateQueryParameterDocs())
}
} }
private static Serialize() { private static Serialize() {
@ -84,7 +88,13 @@ export class QueryParameters {
public static GenerateQueryParameterDocs(): string { public static GenerateQueryParameterDocs(): string {
const docs = []; const docs = [];
for (const key in QueryParameters.documentation) { for (const key in QueryParameters.documentation) {
docs.push("**" + key + "**: " + QueryParameters.documentation[key] + " (default value: _" + QueryParameters.defaults[key] + "_)") docs.push([
" "+key+" ",
"-".repeat(key.length + 2),
QueryParameters.documentation[key],
QueryParameters.defaults[key] === undefined ? "No default value set" : `The default value is _${QueryParameters.defaults[key]}_`
].join("\n"))
} }
return docs.join("\n\n"); return docs.join("\n\n");
} }

View file

@ -2,7 +2,7 @@ import { Utils } from "../Utils";
export default class Constants { export default class Constants {
public static vNumber = "0.6.4b"; public static vNumber = "0.6.8";
// The user journey states thresholds when a new feature gets unlocked // The user journey states thresholds when a new feature gets unlocked
public static userJourney = { public static userJourney = {

6
Models/smallLicense.ts Normal file
View file

@ -0,0 +1,6 @@
export default interface SmallLicense {
path: string,
authors: string[],
license: string,
sources: string[]
}

View file

@ -142,49 +142,9 @@ Whenever a change is made -even adding a single tag- the change is uploaded into
Note that changesets are closed automatically after one hour of inactivity, so we don't have to worry about closing them. Note that changesets are closed automatically after one hour of inactivity, so we don't have to worry about closing them.
### Query parameters # Documentation
By adding extra query parameters, more options are available to influence: All documentation can be found in [here](Docs/)
**test**: If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org (default value: _false_)
**layout**: The layout to load into MapComplete (default value: _bookcases_)
**userlayout**: undefined (default value: _false_)
**layer-control-toggle**: Wether or not the layer control is shown (default value: _false_)
**tab**: The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >200 changesets) (default value: _0_)
**z**: The initial/current zoom level (default value: _1_)
**lat**: The initial/current latitude (default value: _0_)
**lon**: The initial/current longitude of the app (default value: _0_)
**fs-userbadge**: Disables/Enables the userbadge (and thus disables login capabilities) (default value: _true_)
**fs-search**: Disables/Enables the search bar (default value: _true_)
**fs-layers**: Disables/Enables the layer control (default value: _true_)
**fs-add-new**: Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place) (default value: _true_)
**fs-welcome-message**: undefined (default value: _true_)
**fs-iframe**: Disables/Enables the iframe-popup (default value: _false_)
**fs-more-quests**: Disables/Enables the 'More Quests'-tab in the welcome message (default value: _true_)
**fs-share-screen**: Disables/Enables the 'Share-screen'-tab in the welcome message (default value: _true_)
**fs-geolocation**: Disables/Enables the geolocation button (default value: _true_)
**oauth_token**: Used to complete the login (default value: _undefined_)
**background**: The id of the background layer to start with (default value: _undefined_)
**layer-bookcases**: Wehter or not layer bookcases is shown (default value: _true_) index.ts:104:8
# Privacy # Privacy
@ -195,53 +155,12 @@ Geolocation is available on mobile only throught hte device's GPS location (so n
TODO: erase cookies of third party websites and API's TODO: erase cookies of third party websites and API's
# Attributions # Attribution
Data from OpenStreetMap Data from OpenStreetMap
Background layer selection: curated by https://github.com/osmlab/editor-layer-index Background layer selection: curated by https://github.com/osmlab/editor-layer-index
Images from Wikipedia/Wikimedia Icons are attributed in various 'license_info.json'-files and can be found in the app.
https://commons.wikimedia.org/wiki/File:Camera_font_awesome.svg
Camera Icon, Dave Gandy, CC-BY-SA 3.0
https://commons.wikimedia.org/wiki/File:OOjs_UI_indicator_search-rtl.svg
Search Icon, MIT
https://commons.wikimedia.org/wiki/File:Trash_font_awesome.svg
Trash icon by Dave Gandy, CC-BY-SA
https://commons.wikimedia.org/wiki/File:Home-icon.svg
Home icon by Timothy Miller, CC-BY-SA 3.0
https://commons.wikimedia.org/wiki/File:Map_icons_by_Scott_de_Jonge_-_bicycle-store.svg
Bicycle logo, Scott de Jonge
Nature Reserve icon via http://www.onlinewebfonts.com/icon/389579, CC BY 3.0 (@ Эдуард Черных)
Park icon via http://www.onlinewebfonts.com/icon/425974, CC BY 3.0 (@sterankofrank)
Forest icon via https://www.onlinewebfonts.com/icon/498112, CC BY
Statistics icon via https://www.onlinewebfonts.com/icon/197818
Chronometer (on monitoring_station.svg): ANTU chronometer
https://commons.wikimedia.org/w/index.php?title=Antu_chronometer
Fries icon:
https://www.flaticon.com/free-icon/french-fries_1144288
Shower icon (used in 'bike_cleaning.svg'):
https://commons.wikimedia.org/wiki/File:Shower_symbol.svg
Bench icons from StreetComplete: https://github.com/westnordost/StreetComplete/tree/v25.0-beta1/res/graphics/quest%20icons, GPLv3.0
Urinal icon: https://thenounproject.com/term/urinal/1307984/
24/7 icon: https://www.vecteezy.com/vector-art/1394992-24-7-service-and-support-icon-set
Translation-icon: https://commons.wikimedia.org/wiki/File:OOjs_UI_icon_language-ltr.svg
PingPong-table icon: Font Awesome Free 5.2.0 by @fontawesome - https://fontawesome.com

7
Svg.ts

File diff suppressed because one or more lines are too long

View file

@ -10,6 +10,9 @@ import Loc from "../../Models/Loc";
import LeafletMap from "../../Models/LeafletMap"; import LeafletMap from "../../Models/LeafletMap";
import * as L from "leaflet" import * as L from "leaflet"
/**
* The bottom right attribution panel in the leaflet map
*/
export default class Attribution extends UIElement { export default class Attribution extends UIElement {
private readonly _location: UIEventSource<Loc>; private readonly _location: UIEventSource<Loc>;

View file

@ -0,0 +1,69 @@
import {UIElement} from "../UIElement";
import Combine from "../Base/Combine";
import Translations from "../i18n/Translations";
import Attribution from "./Attribution";
import State from "../../State";
import {UIEventSource} from "../../Logic/UIEventSource";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import {FixedUiElement} from "../Base/FixedUiElement";
import * as licenses from "../../assets/generated/license_info.json"
import SmallLicense from "../../Models/smallLicense";
import {Icon} from "leaflet";
import Img from "../Base/Img";
import {Utils} from "../../Utils";
/**
* The attribution panel shown on mobile
*/
export default class AttributionPanel extends Combine {
private static LicenseObject = AttributionPanel.GenerateLicenses();
constructor(layoutToUse: UIEventSource<LayoutConfig>) {
super([
Translations.t.general.attribution.attributionContent,
((layoutToUse.data.maintainer ?? "") == "") ? "" : Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.data.maintainer}),
layoutToUse.data.credits ,
"<br/>",
new Attribution(undefined, undefined, State.state.layoutToUse, undefined),
"<br/>",
Translations.t.general.attribution.iconAttribution.title.Clone().SetClass("font-bold pt-12 pb-3"),
...Utils.NoNull(Array.from(layoutToUse.data.ExtractImages()))
.map(AttributionPanel.IconAttribution)
]);
this.SetClass("flex flex-col")
}
private static IconAttribution(iconPath: string) {
if (iconPath.startsWith("http")) {
iconPath = "." + new URL(iconPath).pathname;
}
const license: SmallLicense = AttributionPanel.LicenseObject[iconPath]
if (license == undefined) {
return undefined;
}
if(license.license.indexOf("trivial")>=0){
return undefined;
}
return new Combine([
`<img src='${iconPath}' style="width: 50px; height: 50px; margin-right: 0.5em;">`,
new Combine([
new FixedUiElement(license.authors.join("; ")).SetClass("font-bold"),
new Combine([license.license, license.sources.length > 0 ? " - " : "",
...license.sources.map(link => `<a href='${link}' target="_blank">${new URL(link).hostname}</a> `)]).SetClass("block")
]).SetClass("flex flex-col")
]).SetClass("flex")
}
private static GenerateLicenses() {
const allLicenses = {}
for (const key in licenses) {
const license: SmallLicense = licenses[key];
allLicenses[license.path] = license
}
return allLicenses;
}
}

View file

@ -85,16 +85,12 @@ export default class MoreScreen extends UIElement {
const linkButton: UIElement[] = [] const linkButton: UIElement[] = []
for (const k in AllKnownLayouts.allSets) { for (const layout of AllKnownLayouts.layoutsList) {
const layout: LayoutConfig = AllKnownLayouts.allSets[k]; if (layout.id === personal.id) {
if (k === personal.id) {
if (State.state.osmConnection.userDetails.data.csCount < Constants.userJourney.personalLayoutUnlock) { if (State.state.osmConnection.userDetails.data.csCount < Constants.userJourney.personalLayoutUnlock) {
continue; continue;
} }
} }
if (layout.id !== k) {
continue; // This layout was added multiple time due to an uppercase
}
linkButton.push(this.createLinkButton(layout)); linkButton.push(this.createLinkButton(layout));
} }

View file

@ -37,7 +37,7 @@ export default class SimpleAddUI extends UIElement {
private readonly goToInboxButton: UIElement = new SubtleButton(Svg.envelope_ui(), private readonly goToInboxButton: UIElement = new SubtleButton(Svg.envelope_ui(),
Translations.t.general.goToInbox, {url: "https://www.openstreetmap.org/messages/inbox", newTab: false}); Translations.t.general.goToInbox, {url: "https://www.openstreetmap.org/messages/inbox", newTab: false});
constructor() { constructor(isShown: UIEventSource<boolean>) {
super(State.state.locationControl.map(loc => loc.zoom)); super(State.state.locationControl.map(loc => loc.zoom));
const self = this; const self = this;
this.ListenTo(Locale.language); this.ListenTo(Locale.language);
@ -64,6 +64,18 @@ export default class SimpleAddUI extends UIElement {
State.state.layerControlIsOpened.setData(true); State.state.layerControlIsOpened.setData(true);
}) })
// IS shown is the state of the dialog - we reset the choice if the dialog dissappears
isShown.addCallback(isShown =>
{
if(!isShown){
self._confirmPreset.setData(undefined)
}
})
// If the click location changes, we reset the dialog as well
State.state.LastClickLocation.addCallback(() => {
self._confirmPreset.setData(undefined)
})
} }
InnerRender(): string { InnerRender(): string {
@ -143,7 +155,9 @@ export default class SimpleAddUI extends UIElement {
"<b>", "<b>",
Translations.t.general.add.confirmButton.Subs({category: preset.name}), Translations.t.general.add.confirmButton.Subs({category: preset.name}),
"</b>"])).SetClass("break-words"); "</b>"])).SetClass("break-words");
confirmButton.onClick(this.CreatePoint(preset.tags)); confirmButton.onClick(
this.CreatePoint(preset.tags)
);
if (!this._confirmPreset.data.layerToAddTo.isDisplayed.data) { if (!this._confirmPreset.data.layerToAddTo.isDisplayed.data) {
return new Combine([ return new Combine([
@ -158,7 +172,7 @@ export default class SimpleAddUI extends UIElement {
let tagInfo = ""; let tagInfo = "";
const csCount = State.state.osmConnection.userDetails.data.csCount; const csCount = State.state.osmConnection.userDetails.data.csCount;
if (csCount > Constants.userJourney.tagsVisibleAt) { if (csCount > Constants.userJourney.tagsVisibleAt) {
tagInfo = this._confirmPreset.data.tags.map(t => t.asHumanString(csCount > Constants.userJourney.tagsVisibleAndWikiLinked, true, {})).join("&"); tagInfo = this._confirmPreset.data.tags.map(t => t.asHumanString(csCount > Constants.userJourney.tagsVisibleAndWikiLinked, true)).join("&");
tagInfo = `<br/>More information about the preset: ${tagInfo}` tagInfo = `<br/>More information about the preset: ${tagInfo}`
} }
@ -186,7 +200,7 @@ export default class SimpleAddUI extends UIElement {
const csCount = State.state.osmConnection.userDetails.data.csCount; const csCount = State.state.osmConnection.userDetails.data.csCount;
let tagInfo = undefined; let tagInfo = undefined;
if (csCount > Constants.userJourney.tagsVisibleAt) { if (csCount > Constants.userJourney.tagsVisibleAt) {
const presets = preset.tags.map(t => new Combine ([t.asHumanString(false, true, {}), " "]).SetClass("subtle break-words") ) const presets = preset.tags.map(t => new Combine([t.asHumanString(false, true), " "]).SetClass("subtle break-words"))
tagInfo = new Combine(presets) tagInfo = new Combine(presets)
} }
const button: UIElement = const button: UIElement =
@ -220,11 +234,17 @@ export default class SimpleAddUI extends UIElement {
private CreatePoint(tags: Tag[]) { private CreatePoint(tags: Tag[]) {
return () => { return () => {
console.log("Create Point Triggered")
const loc = State.state.LastClickLocation.data; const loc = State.state.LastClickLocation.data;
let feature = State.state.changes.createElement(tags, loc.lat, loc.lon); let feature = State.state.changes.createElement(tags, loc.lat, loc.lon);
State.state.selectedElement.setData(feature); State.state.selectedElement.setData(feature);
this._confirmPreset.setData(undefined);
} }
} }
public OnClose(){
console.log("On close triggered")
this._confirmPreset.setData(undefined)
}
} }

View file

@ -17,7 +17,8 @@ import {LocalStorageSource} from "../../Logic/Web/LocalStorageSource";
import HelpText from "./HelpText"; import HelpText from "./HelpText";
import Svg from "../../Svg"; import Svg from "../../Svg";
import Constants from "../../Models/Constants"; import Constants from "../../Models/Constants";
import LZString from "lz-string";
import {Utils} from "../../Utils";
export default class CustomGeneratorPanel extends UIElement { export default class CustomGeneratorPanel extends UIElement {
private mainPanel: UIElement; private mainPanel: UIElement;
@ -40,7 +41,7 @@ export default class CustomGeneratorPanel extends UIElement {
private InitMainPanel(layout: LayoutConfigJson, userDetails: UserDetails, connection: OsmConnection) { private InitMainPanel(layout: LayoutConfigJson, userDetails: UserDetails, connection: OsmConnection) {
const es = new UIEventSource(layout); const es = new UIEventSource(layout);
const encoded = es.map(config => btoa(JSON.stringify(config))); const encoded = es.map(config => LZString.compressToBase64(Utils.MinifyJSON(JSON.stringify(config, null, 0))));
encoded.addCallback(encoded => LocalStorageSource.Get("last-custom-theme")) encoded.addCallback(encoded => LocalStorageSource.Get("last-custom-theme"))
const liveUrl = encoded.map(encoded => `./index.html?userlayout=${es.data.id}#${encoded}`) const liveUrl = encoded.map(encoded => `./index.html?userlayout=${es.data.id}#${encoded}`)
const testUrl = encoded.map(encoded => `./index.html?test=true&userlayout=${es.data.id}#${encoded}`) const testUrl = encoded.map(encoded => `./index.html?test=true&userlayout=${es.data.id}#${encoded}`)

View file

@ -21,14 +21,13 @@ import Constants from "../../Models/Constants";
export default class TagRenderingPanel extends InputElement<TagRenderingConfigJson> { export default class TagRenderingPanel extends InputElement<TagRenderingConfigJson> {
public IsImage = false;
public options: { title?: string; description?: string; disableQuestions?: boolean; isImage?: boolean; };
public readonly validText: UIElement;
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private intro: UIElement; private intro: UIElement;
private settingsTable: UIElement; private settingsTable: UIElement;
public IsImage = false;
private readonly _value: UIEventSource<TagRenderingConfigJson>; private readonly _value: UIEventSource<TagRenderingConfigJson>;
public options: { title?: string; description?: string; disableQuestions?: boolean; isImage?: boolean; };
public readonly validText : UIElement;
constructor(languages: UIEventSource<string[]>, constructor(languages: UIEventSource<string[]>,
currentlySelected: UIEventSource<SingleSetting<any>>, currentlySelected: UIEventSource<SingleSetting<any>>,
@ -63,8 +62,18 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
} }
this._value.addCallback(value => { this._value.addCallback(value => {
if(value?.freeform?.key == ""){ let doPing = false;
if (value?.freeform?.key == "") {
value.freeform = undefined; value.freeform = undefined;
doPing = true;
}
if (value?.render == "") {
value.render = undefined;
doPing = true;
}
if (doPing) {
this._value.ping(); this._value.ping();
} }
}) })
@ -72,7 +81,7 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
const questionSettings = [ const questionSettings = [
setting(options?.noLanguage ? new TextField({placeholder:"question"}) : new MultiLingualTextFields(languages) setting(options?.noLanguage ? new TextField({placeholder: "question"}) : new MultiLingualTextFields(languages)
, "question", "Question", "If the key or mapping doesn't match, this question is asked"), , "question", "Question", "If the key or mapping doesn't match, this question is asked"),
"<h3>Freeform key</h3>", "<h3>Freeform key</h3>",
@ -90,11 +99,11 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
const settings: (string | SingleSetting<any>)[] = [ const settings: (string | SingleSetting<any>)[] = [
setting( setting(
options?.noLanguage ? new TextField({placeholder:"Rendering"}) : options?.noLanguage ? new TextField({placeholder: "Rendering"}) :
new MultiLingualTextFields(languages), "render", "Value to show", new MultiLingualTextFields(languages), "render", "Value to show",
"Renders this value. Note that <span class='literal-code'>{key}</span>-parts are substituted by the corresponding values of the element. If neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value." + "Renders this value. Note that <span class='literal-code'>{key}</span>-parts are substituted by the corresponding values of the element. If neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value." +
"<br/><br/>" + "<br/><br/>" +
"Furhtermore, some special functions are supported:"+SpecialVisualizations.HelpMessage.Render()), "Furhtermore, some special functions are supported:" + SpecialVisualizations.HelpMessage.Render()),
questionsNotUnlocked ? `You need at least ${Constants.userJourney.themeGeneratorFullUnlock} changesets to unlock the 'question'-field and to use your theme to edit OSM data` : "", questionsNotUnlocked ? `You need at least ${Constants.userJourney.themeGeneratorFullUnlock} changesets to unlock the 'question'-field and to use your theme to edit OSM data` : "",
...(options?.disableQuestions ? [] : questionSettings), ...(options?.disableQuestions ? [] : questionSettings),
@ -117,11 +126,11 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
this.validText = new VariableUiElement(value.map((json: TagRenderingConfigJson) => { this.validText = new VariableUiElement(value.map((json: TagRenderingConfigJson) => {
try{ try {
new TagRenderingConfig(json,undefined, options?.title ?? ""); new TagRenderingConfig(json, undefined, options?.title ?? "");
return ""; return "";
}catch(e){ } catch (e) {
return "<span class='alert'>"+e+"</span>" return "<span class='alert'>" + e + "</span>"
} }
})); }));
@ -138,8 +147,6 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
return this._value; return this._value;
} }
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
IsValid(t: TagRenderingConfigJson): boolean { IsValid(t: TagRenderingConfigJson): boolean {
return false; return false;
} }

View file

@ -34,13 +34,6 @@ export class SubstitutedTranslation extends UIElement {
this.SetClass("w-full") this.SetClass("w-full")
} }
private static GenerateMap(){
return new Map<UIEventSource<any>, SubstitutedTranslation>()
}
private static GenerateSubCache(){
return new Map<Translation, Map<UIEventSource<any>, SubstitutedTranslation>>();
}
public static construct( public static construct(
translation: Translation, translation: Translation,
tags: UIEventSource<any>): SubstitutedTranslation { tags: UIEventSource<any>): SubstitutedTranslation {
@ -59,14 +52,21 @@ export class SubstitutedTranslation extends UIElement {
public static SubstituteKeys(txt: string, tags: any) { public static SubstituteKeys(txt: string, tags: any) {
for (const key in tags) { for (const key in tags) {
// Poor mans replace all txt = txt.replace(new RegExp("{" + key + "}", "g"), tags[key])
txt = txt.split("{" + key + "}").join(tags[key]);
} }
return txt; return txt;
} }
private static GenerateMap() {
return new Map<UIEventSource<any>, SubstitutedTranslation>()
}
private static GenerateSubCache() {
return new Map<Translation, Map<UIEventSource<any>, SubstitutedTranslation>>();
}
InnerRender(): string { InnerRender(): string {
if(this.content.length == 1){ if (this.content.length == 1) {
return this.content[0].Render(); return this.content[0].Render();
} }
return new Combine(this.content).Render(); return new Combine(this.content).Render();
@ -117,8 +117,8 @@ export class SubstitutedTranslation extends UIElement {
} }
} }
// IF we end up here, no changes have to be made // IF we end up here, no changes have to be made - except to remove any resting {}
return [new FixedUiElement(template)]; return [new FixedUiElement(template.replace(/{.*}/g, ""))];
} }
} }

View file

@ -8,6 +8,8 @@ export class Translation extends UIElement {
public static forcedLanguage = undefined; public static forcedLanguage = undefined;
public readonly translations: object public readonly translations: object
return
allIcons;
constructor(translations: object, context?: string) { constructor(translations: object, context?: string) {
super(Locale.language) super(Locale.language)
@ -17,6 +19,9 @@ export class Translation extends UIElement {
let count = 0; let count = 0;
for (const translationsKey in translations) { for (const translationsKey in translations) {
count++; count++;
if(typeof(translations[translationsKey]) != "string"){
throw "Error in an object depicting a translation: a non-string object was found. ("+context+")\n You probably put some other section accidentally in the translation"
}
} }
this.translations = translations; this.translations = translations;
if (count === 0) { if (count === 0) {
@ -46,7 +51,7 @@ export class Translation extends UIElement {
public SupportedLanguages(): string[] { public SupportedLanguages(): string[] {
const langs = [] const langs = []
for (const translationsKey in this.translations) { for (const translationsKey in this.translations) {
if(translationsKey === "#"){ if (translationsKey === "#") {
continue; continue;
} }
langs.push(translationsKey) langs.push(translationsKey)
@ -102,7 +107,6 @@ export class Translation extends UIElement {
return new Translation(this.translations) return new Translation(this.translations)
} }
FirstSentence() { FirstSentence() {
const tr = {}; const tr = {};
@ -115,4 +119,38 @@ export class Translation extends UIElement {
return new Translation(tr); return new Translation(tr);
} }
public ExtractImages(isIcon = false): string[] {
const allIcons: string[] = []
for (const key in this.translations) {
const render = this.translations[key]
if (isIcon) {
const icons = render.split(";").filter(part => part.match(/(\.svg|\.png|\.jpg)$/) != null)
allIcons.push(...icons)
} else if (!Utils.runningFromConsole) {
// This might be a tagrendering containing some img as html
const htmlElement = document.createElement("div")
htmlElement.innerHTML = render
const images = Array.from(htmlElement.getElementsByTagName("img")).map(img => img.src)
allIcons.push(...images)
} else {
// We are running this in ts-node (~= nodejs), and can not access document
// So, we fallback to simple regex
try {
const matches = render.match(/<img[^>]+>/g)
if (matches != null) {
const sources = matches.map(img => img.match(/src=("[^"]+"|'[^']+'|[^/ ]+)/))
.filter(match => match != null)
.map(match => match[1].trim().replace(/^['"]/, '').replace(/['"]$/, ''));
allIcons.push(...sources)
}
}catch(e){
console.error("Could not search for images: ", render, this.txt)
throw e
}
}
}
return allIcons.filter(icon => icon != undefined)
}
} }

View file

@ -10,6 +10,8 @@ export class Utils {
public static runningFromConsole = false; public static runningFromConsole = false;
public static readonly assets_path = "./assets/svg/"; public static readonly assets_path = "./assets/svg/";
private static knownKeys = ["addExtraTags", "and", "calculatedTags", "changesetmessage", "clustering", "color", "condition", "customCss", "dashArray", "defaultBackgroundId", "description", "descriptionTail", "doNotDownload", "enableAddNewPoints", "enableBackgroundLayerSelection", "enableGeolocation", "enableLayers", "enableMoreQuests", "enableSearch", "enableShareScreen", "enableUserBadge", "freeform", "hideFromOverview", "hideInAnswer", "icon", "iconOverlays", "iconSize", "id", "if", "ifnot", "isShown", "key", "language", "layers", "lockLocation", "maintainer", "mappings", "maxzoom", "maxZoom", "minNeededElements", "minzoom", "multiAnswer", "name", "or", "osmTags", "passAllFeatures", "presets", "question", "render", "roaming", "roamingRenderings", "rotation", "shortDescription", "socialImage", "source", "startLat", "startLon", "startZoom", "tagRenderings", "tags", "then", "title", "titleIcons", "type", "version", "wayHandling", "widenFactor", "width"]
private static extraKeys = ["nl", "en", "fr", "de", "pt", "es", "name", "phone", "email", "amenity", "leisure", "highway", "building", "yes", "no", "true", "false"]
static EncodeXmlValue(str) { static EncodeXmlValue(str) {
return str.replace(/&/g, '&amp;') return str.replace(/&/g, '&amp;')
@ -202,6 +204,42 @@ export class Utils {
return {x: Utils.lon2tile(lon, z), y: Utils.lat2tile(lat, z), z: z} return {x: Utils.lon2tile(lon, z), y: Utils.lat2tile(lat, z), z: z}
} }
public static MinifyJSON(stringified: string): string {
stringified = stringified.replace(/\|/g, "||");
const keys = Utils.knownKeys.concat(Utils.extraKeys);
for (let i = 0; i < keys.length; i++) {
const knownKey = keys[i];
let code = i;
if (i >= 124) {
code += 1; // Character 127 is our 'escape' character |
}
let replacement = "|" + String.fromCharCode(code)
stringified = stringified.replace(new RegExp(`\"${knownKey}\":`, "g"), replacement);
}
return stringified;
}
public static UnMinify(minified: string): string {
const parts = minified.split("|");
let result = parts.shift();
const keys = Utils.knownKeys.concat(Utils.extraKeys);
for (const part of parts) {
if (part == "") {
// Empty string => this was a || originally
result += "|"
continue
}
const i = part.charCodeAt(0);
result += "\"" + keys[i] + "\":" + part.substring(1)
}
return result;
}
private static tile2long(x, z) { private static tile2long(x, z) {
return (x / Math.pow(2, z) * 360 - 180); return (x / Math.pow(2, z) * 360 - 180);
} }

BIN
assets/SocialImage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

View file

@ -0,0 +1,10 @@
[
{
"authors": [
"Pieter Vander Vennet"
],
"path": "picnic_table.svg",
"license": "CC0",
"sources": []
}
]

View file

@ -0,0 +1,10 @@
[
{
"authors": [
"Pieter Vander Vennet"
],
"path": "bicycle_library.svg",
"license": "CC0",
"sources": []
}
]

View file

@ -0,0 +1,18 @@
[
{
"authors": [
"Pieter Vander Vennet"
],
"path": "pinIcon.svg",
"license": "CC0",
"sources": []
},
{
"authors": [
"Pieter Vander Vennet"
],
"path": "tube.svg",
"license": "CC0",
"sources": []
}
]

View file

@ -0,0 +1,16 @@
[
{
"authors": [
"Pieter Fiers",
"Thibault Declercq",
"Pierre Barban",
"Joost Schouppe",
"Pieter Vander Vennet"
],
"path": "bike_cafe.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/cyclofix"
]
}
]

View file

@ -0,0 +1,18 @@
[
{
"authors": [
"Pieter Vander Vennet"
],
"path": "bike_cleaning.svg",
"license": "CC0",
"sources": []
},
{
"authors": [
"Pieter Vander Vennet"
],
"path": "bike_cleaning_icon.svg",
"license": "CC0",
"sources": []
}
]

View file

@ -0,0 +1,12 @@
[
{
"authors": [
"Fabián Alexis"
],
"path": "monitoring_station.svg",
"license": "CC-BY-SA 3.0",
"sources": [
"https://commons.wikimedia.org/wiki/File:Antu_chronometer-reset.svg"
]
}
]

View file

@ -293,7 +293,6 @@
"question": { "question": {
"en": "Does this bicycle parking have spots for cargo bikes?", "en": "Does this bicycle parking have spots for cargo bikes?",
"nl": "Heeft deze fietsparking plaats voor bakfietsen?", "nl": "Heeft deze fietsparking plaats voor bakfietsen?",
"fr": "TODO: fr",
"gl": "Este aparcadoiro de bicicletas ten espazo para bicicletas de carga?", "gl": "Este aparcadoiro de bicicletas ten espazo para bicicletas de carga?",
"de": "Gibt es auf diesem Fahrrad-Parkplatz Plätze für Lastenfahrräder?" "de": "Gibt es auf diesem Fahrrad-Parkplatz Plätze für Lastenfahrräder?"
}, },
@ -303,7 +302,6 @@
"then": { "then": {
"en": "This parking has room for cargo bikes", "en": "This parking has room for cargo bikes",
"nl": "Deze parking heeft plaats voor bakfietsen", "nl": "Deze parking heeft plaats voor bakfietsen",
"fr": "TODO: fr",
"gl": "Este aparcadoiro ten espazo para bicicletas de carga.", "gl": "Este aparcadoiro ten espazo para bicicletas de carga.",
"de": "Dieser Parkplatz bietet Platz für Lastenfahrräder" "de": "Dieser Parkplatz bietet Platz für Lastenfahrräder"
} }
@ -313,7 +311,6 @@
"then": { "then": {
"en": "This parking has designated (official) spots for cargo bikes.", "en": "This parking has designated (official) spots for cargo bikes.",
"nl": "Er zijn speciale plaatsen voorzien voor bakfietsen", "nl": "Er zijn speciale plaatsen voorzien voor bakfietsen",
"fr": "TODO: fr",
"gl": "Este aparcadoiro ten espazos designados (oficiais) para bicicletas de carga.", "gl": "Este aparcadoiro ten espazos designados (oficiais) para bicicletas de carga.",
"de": "Dieser Parkplatz verfügt über ausgewiesene (offizielle) Plätze für Lastenfahrräder." "de": "Dieser Parkplatz verfügt über ausgewiesene (offizielle) Plätze für Lastenfahrräder."
} }
@ -323,7 +320,6 @@
"then": { "then": {
"en": "You're not allowed to park cargo bikes", "en": "You're not allowed to park cargo bikes",
"nl": "Je mag hier geen bakfietsen parkeren", "nl": "Je mag hier geen bakfietsen parkeren",
"fr": "TODO: fr",
"gl": "Non está permitido aparcar bicicletas de carga", "gl": "Non está permitido aparcar bicicletas de carga",
"de": "Es ist nicht erlaubt, Lastenfahrräder zu parken" "de": "Es ist nicht erlaubt, Lastenfahrräder zu parken"
} }

View file

@ -0,0 +1,100 @@
[
{
"authors": [
"Gitte Vande Graveele"
],
"path": "bollard.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/walk-by-brussels"
]
},
{
"authors": [
"Gitte Vande Graveele"
],
"path": "handlebar_holder.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/walk-by-brussels"
]
},
{
"authors": [
"Pieter Fiers",
"Thibault Declercq",
"Pierre Barban",
"Joost Schouppe",
"Pieter Vander Vennet"
],
"path": "parking.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/cyclofix"
]
},
{
"authors": [
"Pieter Fiers",
"Thibault Declercq",
"Pierre Barban",
"Joost Schouppe",
"Pieter Vander Vennet"
],
"path": "parking_old.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/cyclofix"
]
},
{
"authors": [
"Gitte Vande Graveele"
],
"path": "rack.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/walk-by-brussels"
]
},
{
"authors": [
"Gitte Vande Graveele"
],
"path": "shed.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/walk-by-brussels"
]
},
{
"authors": [
"Gitte Vande Graveele"
],
"path": "staple.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/walk-by-brussels"
]
},
{
"authors": [
"Gitte Vande Graveele"
],
"path": "two_tier.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/walk-by-brussels"
]
},
{
"authors": [
"Gitte Vande Graveele"
],
"path": "wall_loops.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/walk-by-brussels"
]
}
]

View file

@ -533,8 +533,8 @@
"service:bicycle:pump=yes" "service:bicycle:pump=yes"
], ],
"description": { "description": {
"en": "A device to inflate your tires on a fixed location in the public space.<h3>Examples of bicycle pumps</h3><img src='./assets/layers/bike_repair_station/pump_example_manual.jpg' height='200'/><img src='./assets/layers/bike_repair_station/pump_example.png' height='200'/><img src='./assets/layers/bike_repair_station/pump_example_round.jpg' height='200'/>", "en": "A device to inflate your tires on a fixed location in the public space.<h3>Examples of bicycle pumps</h3><div style='width: 100%; display: flex; align-items: stretch;'><img src='./assets/layers/bike_repair_station/pump_example_manual.jpg' style='height: 200px; width: auto;'/><img src='./assets/layers/bike_repair_station/pump_example.png' style='height: 200px; width: auto;'/><img src='./assets/layers/bike_repair_station/pump_example_round.jpg' style='height: 200px; width: auto;'/></div>",
"nl": "Een apparaat waar je je fietsbanden kan oppompen, beschikbaar in de publieke ruimte. De fietspomp in je kelder telt dus niet.<h3>Voorbeelden</h3><img src='./assets/layers/bike_repair_station/pump_example_manual.jpg' height='200'/><img src='./assets/layers/bike_repair_station/pump_example.png' height='200'/><img src='./assets/layers/bike_repair_station/pump_example_round.jpg' height='200'/>" "nl": "Een apparaat waar je je fietsbanden kan oppompen, beschikbaar in de publieke ruimte. De fietspomp in je kelder telt dus niet.<h3>Voorbeelden</h3><h3>Examples of bicycle pumps</h3><div style='width: 100%; display: flex; align-items: stretch;'><img src='./assets/layers/bike_repair_station/pump_example_manual.jpg' style='height: 200px; width: auto;'/><img src='./assets/layers/bike_repair_station/pump_example.png' style='height: 200px; width: auto;'/><img src='./assets/layers/bike_repair_station/pump_example_round.jpg' style='height: 200px; width: auto;'/></div>"
} }
}, },
{ {

View file

@ -0,0 +1,126 @@
[
{
"authors": [
"Pieter Fiers",
"Thibault Declercq",
"Pierre Barban",
"Joost Schouppe",
"Pieter Vander Vennet"
],
"path": "bike_pump.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/cyclofix"
]
},
{
"authors": [
"Pieter Fiers",
"Thibault Declercq",
"Pierre Barban",
"Joost Schouppe",
"Pieter Vander Vennet"
],
"path": "broken_pump.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/cyclofix"
]
},
{
"authors": [
"Pieter Fiers",
"Thibault Declercq",
"Pierre Barban",
"Joost Schouppe",
"Pieter Vander Vennet"
],
"path": "broken_pump_2.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/cyclofix"
]
},
{
"authors": [
"Pieter Fiers",
"Thibault Declercq",
"Pierre Barban",
"Joost Schouppe",
"Pieter Vander Vennet"
],
"path": "pump.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/cyclofix"
]
},
{
"authors": [
"Turvec Solutions"
],
"path": "pump_example.png",
"license": "Used with permission; all rights reserved",
"note": "Used with permission after email conversation, can be assumed to be CC-BY",
"sources": [
"https://turvec.com/product/public-bike-pump/"
]
},
{
"authors": [
"Pieter Vander Vennet"
],
"path": "pump_example_manual.jpg",
"license": "CC0",
"sources": []
},
{
"authors": [
"©Altinnova"
],
"path": "pump_example_round.jpg",
"license": "Used with permission; all rights reserved",
"sources": [
"https://www.altinnova.com",
"https://www.teeken.de/produkte/stadtmobiliar/green-air1/12?lang=3"
]
},
{
"authors": [
"Pieter Fiers",
"Thibault Declercq",
"Pierre Barban",
"Joost Schouppe",
"Pieter Vander Vennet"
],
"path": "repair_station.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/cyclofix"
]
},
{
"authors": [
"Polarbear24"
],
"path": "repair_station_example.jpg",
"license": "CC-BY-SA 4.0",
"sources": [
"https://wiki.openstreetmap.org/wiki/File:Public_Bike_Repair_Station.jpg"
]
},
{
"authors": [
"Pieter Fiers",
"Thibault Declercq",
"Pierre Barban",
"Joost Schouppe",
"Pieter Vander Vennet"
],
"path": "repair_station_pump.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/cyclofix"
]
}
]

View file

@ -58,8 +58,7 @@
{ {
"if": { "if": {
"and": [ "and": [
"shop=sports", "shop=sports"
"name~*"
] ]
}, },
"then": { "then": {
@ -68,18 +67,10 @@
"fr": "Magasin de sport <i>{name}</i>" "fr": "Magasin de sport <i>{name}</i>"
} }
}, },
{
"if": "shop=sports",
"then": {
"en": "Sport gear shop",
"nl": "Sportwinkel",
"fr": "Magasin de sport"
}
},
{ {
"if": { "if": {
"and": [ "and": [
"shop!~bicycle", "shop!~.*bicycle.*",
"shop~*" "shop~*"
] ]
}, },
@ -88,9 +79,24 @@
{ {
"if": { "if": {
"and": [ "and": [
"name~*", {
"or": [
"service:bicycle:rental=yes",
"amenity=bicycle_rental"
]
}
]
},
"then": {
"nl": "Fietsverhuur <i>{name}</i>",
"en": "Bicycle rental <i>{name}</i>"
}
},
{
"if": {
"and": [
"service:bicycle:retail!~yes", "service:bicycle:retail!~yes",
"service:bicycle:repair!~no" "service:bicycle:repair=yes"
] ]
}, },
"then": { "then": {
@ -104,22 +110,6 @@
{ {
"if": { "if": {
"and": [ "and": [
"service:bicycle:retail!~yes",
"service:bicycle:repair!~no"
]
},
"then": {
"en": "Bike repair",
"nl": "Fietsenmaker",
"fr": "Réparateur de vélo",
"gl": "Arranxo de bicicletas",
"de": "Fahrradwerkstatt"
}
},
{
"if": {
"and": [
"name~*",
"service:bicycle:repair!~yes" "service:bicycle:repair!~yes"
] ]
}, },
@ -131,33 +121,6 @@
"de": "Fahrradgeschäft <i>{name}</i>" "de": "Fahrradgeschäft <i>{name}</i>"
} }
}, },
{
"if": "service:bicycle:repair!~yes",
"then": {
"en": "Bike shop",
"nl": "Fietswinkel",
"fr": "Magasin de vélo",
"gl": "Tenda de bicicletas",
"de": "Fahrradgeschäft"
}
},
{
"if": {
"and": [
"name~*",
{
"or": [
"service:bicycle:rental=yes",
"amenity=bicycle_rental"
]
}
]
},
"then": {
"nl": "Fietsverhuur <i>{name}</i>",
"en": "Bicycle rental <i>{name}</i>"
}
},
{ {
"if": "name~*", "if": "name~*",
"then": { "then": {

View file

@ -0,0 +1,58 @@
[
{
"authors": [
"Pieter Fiers",
"Thibault Declercq",
"Pierre Barban",
"Joost Schouppe",
"Pieter Vander Vennet"
],
"path": "pump.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/cyclofix"
]
},
{
"authors": [
"Pieter Fiers",
"Thibault Declercq",
"Pierre Barban",
"Joost Schouppe",
"Pieter Vander Vennet"
],
"path": "repair_shop.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/cyclofix"
]
},
{
"authors": [
"Pieter Fiers",
"Thibault Declercq",
"Pierre Barban",
"Joost Schouppe",
"Pieter Vander Vennet"
],
"path": "shop.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/cyclofix"
]
},
{
"authors": [
"Pieter Fiers",
"Thibault Declercq",
"Pierre Barban",
"Joost Schouppe",
"Pieter Vander Vennet"
],
"path": "tools.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/cyclofix"
]
}
]

View file

@ -0,0 +1,22 @@
[
{
"authors": [
"Font Awesome"
],
"path": "birdhide.svg",
"license": "CC-BY 4.0",
"sources": [
"https://fontawesome.com"
]
},
{
"authors": [
"Font Awesome Free 5.2.0 by @fontawesome"
],
"path": "birdshelter.svg",
"license": "CC-BY-SA 4.0",
"sources": [
"https://fontawesome.com\r"
]
}
]

View file

@ -0,0 +1,10 @@
[
{
"authors": [
"Pieter Vander Vennet"
],
"path": "other_services.svg",
"license": "CC0",
"sources": []
}
]

View file

@ -82,21 +82,21 @@
"if": "operational_status=", "if": "operational_status=",
"then": { "then": {
"en": "This drinking water works", "en": "This drinking water works",
"nl": "Deze drinkwaterfonteint werkt" "nl": "Deze drinkwaterfontein werkt"
} }
}, },
{ {
"if": "operational_status=broken", "if": "operational_status=broken",
"then": { "then": {
"en": "This drinking water is broken", "en": "This drinking water is broken",
"nl": "Deze drinkwaterfonteint is kapot" "nl": "Deze drinkwaterfontein is kapot"
} }
}, },
{ {
"if": "operational_status=closed", "if": "operational_status=closed",
"then": { "then": {
"en": "This drinking water is closed", "en": "This drinking water is closed",
"nl": "Deze drinkwaterfonteint is afgesloten" "nl": "Deze drinkwaterfontein is afgesloten"
} }
} }
] ]

View file

@ -0,0 +1,16 @@
[
{
"authors": [
"Pieter Fiers",
"Thibault Declercq",
"Pierre Barban",
"Joost Schouppe",
"Pieter Vander Vennet"
],
"path": "drips.svg",
"license": "CC-BY-SA",
"sources": [
"https://osoc.be/editions/2020/cyclofix"
]
}
]

Some files were not shown because too many files have changed in this diff Show more