Add cyclestreet theme, various bugfixes

This commit is contained in:
Pieter Vander Vennet 2020-08-27 18:44:16 +02:00
parent 72a744f60d
commit 60c15e9c8d
23 changed files with 412 additions and 211 deletions

View file

@ -15,6 +15,7 @@ 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 {PersonalLayout} from "../Logic/PersonalLayout";
export class AllKnownLayouts {
@ -31,6 +32,7 @@ export class AllKnownLayouts {
CustomLayoutFromJSON.LayoutFromJSON(aed),
CustomLayoutFromJSON.LayoutFromJSON(toilets),
CustomLayoutFromJSON.LayoutFromJSON(artworks),
CustomLayoutFromJSON.LayoutFromJSON(cyclestreets),
new MetaMap(),
new StreetWidth(),

View file

@ -135,6 +135,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
this._questionElement = this.InputElementFor(options);
const save = () => {
const selection = self._questionElement.GetValue().data;
console.log("Tagrendering: saving tags ", selection);
if (selection) {
State.state.changes.addTag(tags.data.id, selection);
}
@ -152,10 +153,10 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
return "";
}
if (csCount < State.userJourney.tagsVisibleAndWikiLinked) {
const tagsStr = tags.asHumanString(false);
const tagsStr = tags.asHumanString(false, true);
return new FixedUiElement(tagsStr).SetClass("subtle").Render();
}
return tags.asHumanString(true);
return tags.asHumanString(true, true);
}
)
);
@ -318,7 +319,6 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
return true;
}
}
return this._freeform !== undefined && this._source.data[this._freeform.key] !== undefined;
}

View file

@ -16,7 +16,6 @@ export class LayerUpdater {
*/
private previousBounds: Bounds;
/**
* The most important layer should go first, as that one gets first pick for the questions
* @param map

View file

@ -14,22 +14,46 @@ export class Changes {
private static _nextId = -1; // New assined ID's are negative
addTag(elementId: string, tagsFilter : TagsFilter){
if(tagsFilter instanceof Tag){
const tag = tagsFilter as Tag;
if(typeof tag.value !== "string"){
throw "Invalid value"
}
this.addChange(elementId, tag.key, tag.value);
addTag(elementId: string, tagsFilter: TagsFilter) {
const changes = this.tagToChange(tagsFilter);
if (changes.length == 0) {
return;
}
const eventSource = State.state.allElements.getElement(elementId);
const elementTags = eventSource.data;
const pending : {elementId:string, key: string, value: string}[] = [];
for (const change of changes) {
if (elementTags[change.k] !== change.v) {
elementTags[change.k] = change.v;
pending.push({elementId: elementTags.id, key: change.k, value: change.v});
}
}
if(pending.length === 0){
return;
}
eventSource.ping();
this.uploadAll([], pending);
}
if(tagsFilter instanceof And){
private tagToChange(tagsFilter: TagsFilter) {
let changes: { k: string, v: string }[] = [];
if (tagsFilter instanceof Tag) {
const tag = tagsFilter as Tag;
if (typeof tag.value !== "string") {
throw "Invalid value"
}
return [this.checkChange(tag.key, tag.value)];
}
if (tagsFilter instanceof And) {
const and = tagsFilter as And;
for (const tag of and.and) {
this.addTag(elementId, tag);
changes = changes.concat(this.tagToChange(tag));
}
return;
return changes;
}
console.log("Unsupported tagsfilter element to addTag", tagsFilter);
throw "Unsupported tagsFilter element";
@ -41,29 +65,24 @@ export class Changes {
* @param key
* @param value
*/
addChange(elementId: string, key: string, value: string) {
private checkChange(key: string, value: string): { k: string, v: string } {
if (key === undefined || key === null) {
console.log("Invalid key");
return;
return undefined;
}
if (value === undefined || value === null) {
console.log("Invalid value for ",key);
return;
console.log("Invalid value for ", key);
return undefined;
}
if(key.startsWith(" ") || value.startsWith(" ") || value.endsWith(" ") || key.endsWith(" ")){
if (key.startsWith(" ") || value.startsWith(" ") || value.endsWith(" ") || key.endsWith(" ")) {
console.warn("Tag starts with or ends with a space - trimming anyway")
}
key = key.trim();
value = value.trim();
const eventSource = State.state.allElements.getElement(elementId);
eventSource.data[key] = value;
eventSource.ping();
this.uploadAll([], [{elementId: eventSource.data.id, key: key, value: value}]);
return {k: key, v: value};
}
/**
@ -109,49 +128,10 @@ export class Changes {
return geojson;
}
private uploadAll(
newElements: OsmObject[],
pending: { elementId: string; key: string; value: string }[]
) {
const self = this;
const knownElements = {}; // maps string --> OsmObject
function DownloadAndContinue(neededIds, continuation: (() => void)) {
// local function which downloads all the objects one by one
// this is one big loop, running one download, then rerunning the entire function
if (neededIds.length == 0) {
continuation();
return;
}
const neededId = neededIds.pop();
if (neededId in knownElements) {
DownloadAndContinue(neededIds, continuation);
return;
}
console.log("Downloading ", neededId);
OsmObject.DownloadObject(neededId,
function (element) {
knownElements[neededId] = element; // assign the element for later, continue downloading the next element
DownloadAndContinue(neededIds, continuation);
}
);
}
const neededIds = [];
for (const change of pending) {
const id = change.elementId;
if (parseFloat(id.split("/")[1]) < 0) {
// New element - we don't have to download this
} else {
neededIds.push(id);
}
}
DownloadAndContinue(neededIds, function () {
// Here, inside the continuation, we know that all 'neededIds' are loaded in 'knownElements'
private uploadChangesWithLatestVersions(
knownElements, newElements: OsmObject[], pending: { elementId: string; key: string; value: string }[]) {
// Here, inside the continuation, we know that all 'neededIds' are loaded in 'knownElements', which maps the ids onto the elements
// We apply the changes on them
for (const change of pending) {
if (parseInt(change.elementId.split("/")[1]) < 0) {
@ -220,9 +200,30 @@ export class Changes {
changes += "</osmChange>";
return changes;
},
() => {
});
};
private uploadAll(
newElements: OsmObject[],
pending: { elementId: string; key: string; value: string }[]
) {
const self = this;
let neededIds: string[] = [];
for (const change of pending) {
const id = change.elementId;
if (parseFloat(id.split("/")[1]) < 0) {
// New element - we don't have to download this
} else {
neededIds.push(id);
}
}
neededIds = Utils.Dedup(neededIds);
OsmObject.DownloadAll(neededIds, {}, (knownElements) => {
self.uploadChangesWithLatestVersions(knownElements, newElements, pending)
});
}

View file

@ -10,11 +10,11 @@ export class ChangesetHandler {
public currentChangeset: UIEventSource<string>;
constructor(dryRun: boolean, osmConnection: OsmConnection, auth) {
constructor(layoutName: string, dryRun: boolean, osmConnection: OsmConnection, auth) {
this._dryRun = dryRun;
this.userDetails = osmConnection.userDetails;
this.auth = auth;
this.currentChangeset = osmConnection.GetPreference("current-open-changeset");
this.currentChangeset = osmConnection.GetPreference("current-open-changeset-" + layoutName);
if (dryRun) {
console.log("DRYRUN ENABLED");
@ -26,7 +26,6 @@ export class ChangesetHandler {
continuation: () => void) {
if (this._dryRun) {
console.log("NOT UPLOADING as dryrun is true");
var changesetXML = generateChangeXML("123456");
console.log(changesetXML);
continuation();
@ -40,7 +39,9 @@ export class ChangesetHandler {
// We have to open a new changeset
this.OpenChangeset((csId) => {
this.currentChangeset.setData(csId);
self.AddChange(csId, generateChangeXML(csId),
const changeset = generateChangeXML(csId);
console.log(changeset);
self.AddChange(csId, changeset,
() => {
},
(e) => {
@ -67,23 +68,6 @@ export class ChangesetHandler {
)
}
/*
this.OpenChangeset(
function (csId) {
var changesetXML = generateChangeXML(csId);
self.AddChange(csId, changesetXML,
function (csId, mapping) {
self.CloseChangeset(csId, continuation);
handleMapping(mapping);
}
);
}
);*/
this.userDetails.data.csCount++;
this.userDetails.ping();
}

View file

@ -29,7 +29,11 @@ export class OsmConnection {
private _onLoggedIn : ((userDetails: UserDetails) => void)[] = [];
constructor(dryRun: boolean, oauth_token: UIEventSource<string>, singlePage: boolean = true, useDevServer:boolean = false) {
constructor(dryRun: boolean, oauth_token: UIEventSource<string>,
// Used to keep multiple changesets open and to write to the correct changeset
layoutName: string,
singlePage: boolean = true,
useDevServer:boolean = false) {
let pwaStandAloneMode = false;
try {
@ -72,7 +76,7 @@ export class OsmConnection {
this.preferencesHandler = new OsmPreferences(this.auth, this);
this.changesetHandler = new ChangesetHandler(dryRun, this, this.auth);
this.changesetHandler = new ChangesetHandler(layoutName, dryRun, this, this.auth);
if (oauth_token.data !== undefined) {
console.log(oauth_token.data)
const self = this;
@ -94,7 +98,7 @@ export class OsmConnection {
public UploadChangeset(generateChangeXML: (csid: string) => string,
continuation: () => void) {
continuation: () => void = () => {}) {
this.changesetHandler.UploadChangeset(generateChangeXML, continuation);
}
@ -119,6 +123,7 @@ export class OsmConnection {
public AttemptLogin() {
const self = this;
console.log("Trying to log in...");
this.auth.xhr({
method: 'GET',
path: '/api/0.6/user/details'

View file

@ -60,6 +60,7 @@ export abstract class OsmObject {
return tags;
}
Download(continuation: ((element: OsmObject) => void)) {
const self = this;
$.getJSON("https://www.openstreetmap.org/api/0.6/" + this.type + "/" + this.id,
@ -96,6 +97,31 @@ export abstract class OsmObject {
return 'version="'+this.version+'"';
}
abstract ChangesetXML(changesetId: string): string;
public static DownloadAll(neededIds, knownElements: any = {}, continuation: ((knownObjects : any) => void)) {
// local function which downloads all the objects one by one
// this is one big loop, running one download, then rerunning the entire function
if (neededIds.length == 0) {
continuation(knownElements);
return;
}
const neededId = neededIds.pop();
if (neededId in knownElements) {
OsmObject.DownloadAll(neededIds, knownElements, continuation);
return;
}
console.log("Downloading ", neededId);
OsmObject.DownloadObject(neededId,
function (element) {
knownElements[neededId] = element; // assign the element for later, continue downloading the next element
OsmObject.DownloadAll(neededIds,knownElements, continuation);
}
);
}
}

View file

@ -9,7 +9,7 @@ export abstract class TagsFilter {
return this.matches(TagUtils.proprtiesToKV(properties));
}
abstract asHumanString(linkToWiki: boolean);
abstract asHumanString(linkToWiki: boolean, shorten: boolean);
}
@ -90,7 +90,12 @@ export class Tag extends TagsFilter {
}
matches(tags: { k: string; v: string }[]): boolean {
if (this.value === "") {
return true
}
for (const tag of tags) {
if (Tag.regexOrStrMatches(this.key, tag.k)) {
if (tag.v === "") {
@ -110,10 +115,6 @@ export class Tag extends TagsFilter {
}
}
if (this.value === "") {
return true
}
return this.invertValue
}
@ -150,15 +151,17 @@ export class Tag extends TagsFilter {
return new Tag(this.key, TagUtils.ApplyTemplate(this.value as string, tags));
}
asHumanString(linkToWiki: boolean) {
asHumanString(linkToWiki: boolean, shorten: boolean) {
let v = ""
if (typeof (this.value) === "string") {
v = this.value;
}else{
} else {
// value is a regex
v = this.value.source;
}
if (shorten) {
v = Utils.EllipsesAfter(v, 25);
}
if (linkToWiki) {
return `<a href='https://wiki.openstreetmap.org/wiki/Key:${this.key}' target='_blank'>${this.key}</a>` +
`=` +
@ -221,8 +224,8 @@ export class Or extends TagsFilter {
return new Or(newChoices);
}
asHumanString(linkToWiki: boolean) {
return this.or.map(t => t.asHumanString(linkToWiki)).join("|");
asHumanString(linkToWiki: boolean, shorten: boolean) {
return this.or.map(t => t.asHumanString(linkToWiki, shorten)).join("|");
}
}
@ -282,8 +285,8 @@ export class And extends TagsFilter {
return new And(newChoices);
}
asHumanString(linkToWiki: boolean) {
return this.and.map(t => t.asHumanString(linkToWiki)).join("&");
asHumanString(linkToWiki: boolean, shorten: boolean) {
return this.and.map(t => t.asHumanString(linkToWiki, shorten)).join("&");
}
}
@ -308,8 +311,8 @@ export class Not extends TagsFilter{
return new Not(this.not.substituteValues(tags));
}
asHumanString(linkToWiki: boolean) {
return "!" + this.not.asHumanString(linkToWiki);
asHumanString(linkToWiki: boolean, shorten: boolean) {
return "!" + this.not.asHumanString(linkToWiki, shorten);
}
}

View file

@ -107,7 +107,7 @@ A theme has translations into the preset.json (`assets/themes/themename/themenam
### High-level overview
The website is purely static. This means that there is no database here, nor one is needed as all the data is kept in OpenStreetMap or Wikimedia (for images).
The website is purely static. This means that there is no database here, nor one is needed as all the data is kept in OpenStreetMap, Wikimedia (for images), IMGUR. Settings are saved in the preferences-space of the OSM-website, amended by some local-storage if the user is not logged-in.
When viewing, the data is loaded from overpass. The data is then converted (in the browser) to geojson, which is rendered by Leaflet.
@ -130,7 +130,13 @@ Images are fetched from:
Images are uplaoded to imgur, as their API was way easier to handle. The URL is written into the changes
The idea is that one in a while, the images are transfered to wikipedia
The idea is that once in a while, the images are transfered to wikipedia or that we hook up wikimedia directly (but I need some help in getting their API working).
### Uploading changes
In order to avoid lots of small changesets, a changeset is opened and kept open. The changeset number is saved into the users preferences on OSM.
Whenever a change is made -even adding a single tag- the change is uploaded into this changeset. If that fails, the changeset is probably closed and we open a new changeset.
# Privacy

View file

@ -175,6 +175,7 @@ export class State {
this.osmConnection = new OsmConnection(
testParam === "true",
QueryParameters.GetQueryParameter("oauth_token", undefined),
layoutToUse.name,
true,
testParam === "dev"
);

View file

@ -35,9 +35,13 @@ export class TabbedComponent extends UIElement {
headerBar = "<div class='tabs-header-bar'>" + headerBar + "</div>"
const content = this.content[this._source.data].Render();
const content = this.content[this._source.data];
return headerBar + "<div class='tab-content'>" + content.Render() + "</div>";
}
return headerBar + "<div class='tab-content'>" + content + "</div>";
protected InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement);
this.content[this._source.data].Update();
}
}

View file

@ -13,7 +13,7 @@ export class Preview extends UIElement {
private reloadButton: Button;
private otherPreviews: VariableUiElement;
constructor(url: UIEventSource<string>, config: UIEventSource<LayoutConfigJson>) {
constructor(url: UIEventSource<string>, testurl: UIEventSource<string>, config: UIEventSource<LayoutConfigJson>) {
super(undefined);
this.config = config;
this.url = url;

View file

@ -85,8 +85,7 @@ class MappingGenerator extends UIElement {
}
InnerRender(): string {
const combine = new VerticalCombine(this.elements);
combine.clss = "bordered";
const combine = new VerticalCombine(this.elements).SetClass("bordered");
return combine.Render();
}
}
@ -186,8 +185,7 @@ class TagRenderingGenerator
}
InnerRender(): string {
const combine = new VerticalCombine(this.elements);
combine.clss = "bordered";
const combine = new VerticalCombine(this.elements).SetClass("bordered");
return combine.Render();
}
}
@ -235,8 +233,7 @@ class PresetGenerator extends UIElement {
}
InnerRender(): string {
const combine = new VerticalCombine(this.elements);
combine.clss = "bordered";
const combine = new VerticalCombine(this.elements).SetClass("bordered");
return combine.Render();
}
@ -460,7 +457,7 @@ class AllLayerComponent extends UIElement {
header: "<img src='./assets/add.svg'>",
content: new Button("Add a new layer", () => {
config.data.layers.push({
id: "",
name: "",
title: {
key: "*",
render: "Title"
@ -506,6 +503,7 @@ export class ThemeGenerator extends UIElement {
public readonly themeObject: UIEventSource<LayoutConfigJson>;
private readonly allQuestionFields: UIElement[];
public url: UIEventSource<string>;
public testurl: UIEventSource<string>;
private loginButton: Button
@ -535,7 +533,9 @@ export class ThemeGenerator extends UIElement {
if (window.location.hostname === "127.0.0.1") {
baseUrl = "http://127.0.0.1:1234";
}
this.url = base64.map((data) => `${baseUrl}/index.html?test=true&userlayout=${this.themeObject.data.name}#${data}`);
this.url = base64.map((data) => `${baseUrl}/index.html?userlayout=${this.themeObject.data.name}#${data}`);
this.testurl = base64.map((data) => `${baseUrl}/index.html?test=true&userlayout=${this.themeObject.data.name}#${data}`);
const self = this;
pingThemeObject = () => {self.themeObject.ping()};

View file

@ -57,7 +57,7 @@ export class ValidatedTextField {
return undefined;
}
}
return new And(tags).asHumanString(false);
return new And(tags).asHumanString(false, false);
},
value: value,
startValidated: true

View file

@ -23,9 +23,11 @@ export class MoreScreen extends UIElement {
}
private createLinkButton(layout: Layout, customThemeDefinition: string = undefined) {
if (layout.hideFromOverview && State.state.osmConnection.userDetails.data.name !== "Pieter Vander Vennet") {
if (layout.hideFromOverview) {
if (State.state.osmConnection.GetPreference("hidden-theme-" + layout.name + "-enabled").data !== "true") {
return undefined;
}
}
if (layout.name === State.state.layoutToUse.data.name) {
return undefined;
}

View file

@ -6,7 +6,7 @@ export abstract class UIElement extends UIEventSource<string>{
public readonly id: string;
public readonly _source: UIEventSource<any>;
public clss : string = ""
private clss: string[] = []
private _hideIfEmpty = false;
@ -41,6 +41,7 @@ export abstract class UIElement extends UIEventSource<string>{
public onClick(f: (() => void)) {
this._onClick = f;
this.SetClass("clickable")
this.Update();
return this;
}
@ -107,7 +108,7 @@ export abstract class UIElement extends UIEventSource<string>{
}
Render(): string {
return `<span class='uielement ${this.clss}' id='${this.id}'>${this.InnerRender()}</span>`
return `<span class='uielement ${this.clss.join(" ")}' id='${this.id}'>${this.InnerRender()}</span>`
}
AttachTo(divId: string) {
@ -142,7 +143,9 @@ export abstract class UIElement extends UIEventSource<string>{
}
public SetClass(clss: string): UIElement {
this.clss = clss;
if (this.clss.indexOf(clss) < 0) {
this.clss.push(clss);
}
return this;
}

View file

@ -7,6 +7,7 @@ import {UserDetails} from "../Logic/Osm/OsmConnection";
import {State} from "../State";
import {Utils} from "../Utils";
import {UIEventSource} from "../Logic/UIEventSource";
import {SubtleButton} from "./Base/SubtleButton";
/**
* Handles and updates the user badge
@ -23,7 +24,10 @@ export class UserBadge extends UIElement {
super(State.state.osmConnection.userDetails);
this._userDetails = State.state.osmConnection.userDetails;
this._languagePicker = Utils.CreateLanguagePicker();
this._loginButton = Translations.t.general.loginWithOpenStreetMap.Clone().onClick(() => State.state.osmConnection.AttemptLogin());
this._loginButton = Translations.t.general.loginWithOpenStreetMap
.Clone()
.SetClass("userbadge-login")
.onClick(() => State.state.osmConnection.AttemptLogin());
this._logout = new FixedUiElement("<img src='assets/logout.svg' class='small-userbadge-icon' alt='logout'>")
.onClick(() => {
State.state.osmConnection.LogOut();

View file

@ -7,6 +7,7 @@ import Translations from "./i18n/Translations";
import {VariableUiElement} from "./Base/VariableUIElement";
import {Utils} from "../Utils";
import {UIEventSource} from "../Logic/UIEventSource";
import Combine from "./Base/Combine";
export class WelcomeMessage extends UIElement {
@ -30,28 +31,37 @@ export class WelcomeMessage extends UIElement {
}
this.description = fromLayout((layout) => layout.welcomeMessage);
this.plzLogIn = fromLayout((layout) => layout.gettingStartedPlzLogin);
this.plzLogIn.onClick(()=> State.state.osmConnection.AttemptLogin());
this.plzLogIn =
fromLayout((layout) => layout.gettingStartedPlzLogin
.onClick(() => {State.state.osmConnection.AttemptLogin()})
);
this.welcomeBack = fromLayout((layout) => layout.welcomeBackMessage);
this.tail = fromLayout((layout) => layout.welcomeTail);
}
InnerRender(): string {
let loginStatus = "";
if (State.state.featureSwitchUserbadge.data) {
loginStatus = (State.state.osmConnection.userDetails.data.loggedIn ? this.welcomeBack : this.plzLogIn).Render();
loginStatus = loginStatus + "<br/>"
protected InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement);
console.log("Innerupdating welcome message")
this.plzLogIn.Update();
}
return "<span>" +
this.description.Render() +
"<br/></br>" +
loginStatus +
this.tail.Render() +
"<br/>" +
this.languagePicker.Render() +
"</span>";
InnerRender(): string {
let loginStatus = undefined;
if (State.state.featureSwitchUserbadge.data) {
loginStatus = (State.state.osmConnection.userDetails.data.loggedIn ? this.welcomeBack :
this.plzLogIn);
}
return new Combine([
this.description,
"<br/></br>",
// TODO this button is broken - figure out why loginStatus,
this.tail,
"<br/>",
this.languagePicker
]).Render()
}

View file

@ -4,20 +4,135 @@
"id": "Fietsstraat",
"title": {
"render": "{name}",
"key": "name"
"key": "*"
},
"icon": {
"key": ""
"key": "*",
"render": "./assets/themes/cyclestreets/F111.svg"
},
"color": {
"key": "",
"key": "*",
"render": "#0000ff"
},
"description": "Een fietsstraat is een straat",
"minzoom": "13",
"description": "Een fietsstraat is een straat waar gemotoriseerd verkeer een fietser niet mag inhalen.",
"minzoom": "16",
"presets": [],
"tagRenderings": [],
"overpassTags": "cyclestreet=yes"
"overpassTags": "cyclestreet=yes",
"width": {
"key": "*",
"addExtraTags": "",
"mappings": [],
"question": "",
"render": "10",
"type": "nat"
},
"name": "Fietsstraat"
},
{
"id": "",
"title": {
"key": "*",
"render": "Toekomstige fietsstraat",
"mappings": [
{
"then": "{name} wordt fietsstraat",
"if": "name=*"
}
]
},
"icon": {
"key": "*",
"render": "https://upload.wikimedia.org/wikipedia/commons/6/65/Belgian_road_sign_F113.svg"
},
"color": {
"key": "*",
"render": "#09f9dd"
},
"width": {
"key": "*",
"render": "5"
},
"description": "Deze straat wordt binnenkort een fietsstraat",
"minzoom": "16",
"wayHandling": 0,
"presets": [],
"tagRenderings": [{
"key": "cyclestreet:start_date",
"render": "Deze straat wordt fietsstraat op {cyclestreet:start_date}",
"type": "date",
"question": "Wanneer wordt deze straat een fietsstraat?"
}],
"name": "Toekomstige fietsstraat",
"overpassTags": "proposed:cyclestreet=yes"
},
{
"name": "Alle straten",
"title": {
"key": "*",
"render": "Straat",
"mappings": [
{
"then": "{name}",
"if": "name=*"
}
]
},
"icon": {
"key": "*",
"render": "./assets/pencil.svg"
},
"color": {
"key": "*",
"render": "#aaaaaa",
"mappings": [
{
"then": "#0000ff",
"if": "cyclestreet=yes"
},
{
"then": "#09f9dd",
"if": "proposed:cyclestreet=yes"
}
]
},
"width": {
"key": "*",
"render": "5"
},
"description": "Laag waar je een straat als fietsstraat kan markeren",
"wayHandling": 0,
"presets": [],
"tagRenderings": [
{
"mappings": [
{
"then": "Deze straat is een fietsstraat",
"if": "cyclestreet=yes&proposed:cyclestreet="
},
{
"then": "Deze straat wordt binnenkort een fietsstraat",
"if": "proposed:cyclestreet=yes&cyclestreet="
},
{
"if": "cyclestreet=&proposed:cyclestreet=",
"then": "Deze straat is geen fietsstraat"
}
],
"type": "text",
"question": "Is deze straat een fietsstraat?",
},
{
"key": "cyclestreet:start_date",
"render": "Deze straat wordt fietsstraat op {cyclestreet:start_date}",
"type": "date",
"question": "Wanneer wordt deze straat een fietsstraat?",
"condition": "proposed:cyclestreet=yes"
}
],
"overpassTags": "highway~=residential|tertiary|unclassified",
"minzoom": "13"
}
],
"language": "nl",
@ -27,6 +142,7 @@
"name": "Fietsstraten",
"title": "Fietsstraten",
"startLon": "3.2228",
"icon": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6c/Belgian_road_sign_F111.svg/400px-Belgian_road_sign_F111.svg.png",
"description": "Een fietsstraat is een straat waar automobilisten geen fietsers mogen inhalen en waar een maximumsnelheid van 30km/h geldt. "
"icon": "./assets/themes/cyclestreets/F111.svg",
"description": "Een fietsstraat is een straat waar automobilisten geen fietsers mogen inhalen en waar een maximumsnelheid van 30km/h geldt.<br/><br/>Op deze open kaart kan je alle gekende fietsstraten zien en kan je ontbrekende fietsstraten aanduiden.",
"widenFactor": 0.03
}

View file

@ -11,7 +11,7 @@ import {FixedUiElement} from "./UI/Base/FixedUiElement";
import {State} from "./State";
import {TextField} from "./UI/Input/TextField";
const connection = new OsmConnection(true, new UIEventSource<string>(undefined), false);
const connection = new OsmConnection(true, new UIEventSource<string>(undefined), "customThemeGenerator", false);
connection.AttemptLogin();
let hash = window.location.hash?.substr(1);
@ -67,7 +67,7 @@ const loadFromTextField = new Button("Load", () => {
});
new Combine([
new Preview(themeGenerator.url, themeGenerator.themeObject),
new Preview(themeGenerator.url, themeGenerator.testurl, themeGenerator.themeObject),
loadFrom,
loadFromTextField,
"<span class='alert'>Loading from the text field will erase the current theme</span>",

View file

@ -109,6 +109,10 @@ form {
padding-bottom: 0.15em;
}
.clickable {
pointer-events: all;
}
.activate-osm-authentication {
cursor: pointer;
@ -126,6 +130,8 @@ form {
border-radius: 0;
}
#home {
cursor: pointer;
}
@ -181,7 +187,6 @@ form {
border-radius: 2em;
border-bottom-right-radius: 1.5em;
border-top-right-radius: 1.5em;
transition: all 500ms linear;
margin: 0;
margin-bottom: 0.5em;
@ -189,6 +194,25 @@ form {
pointer-events: all;
}
.userbadge-login {
font-weight: bold;
font-size: large;
background-color: #e5f5ff !important;
height:3em;
display: inline-block;
text-align: center;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
border-bottom-right-radius: 1.5em;
border-top-right-radius: 1.5em;
margin: 0;
min-width: 20em;
pointer-events: all;
}
#userbadge-and-search {
display: inline-block;
width: min-content;

View file

@ -107,10 +107,15 @@ if (layoutToUse === undefined) {
console.log("Using layout: ", layoutToUse.name);
State.state = new State(layoutToUse);
if (layoutToUse.hideFromOverview) {
State.state.osmConnection.GetPreference("hidden-theme-" + layoutToUse.name + "-enabled").setData("true");
}
if (layoutFromBase64 !== "false") {
State.state.layoutDefinition = hash.substr(1);
State.state.osmConnection.OnLoggedIn(() => {
State.state.osmConnection.GetLongPreference("installed-theme-"+layoutToUse.name).setData(State.state.layoutDefinition);
State.state.osmConnection.GetLongPreference("installed-theme-" + layoutToUse.name).setData(State.state.layoutDefinition);
})
}
InitUiElements.InitBaseMap();

View file

@ -4,6 +4,12 @@
"target": "es5",
"sourceMap": true,
"resolveJsonModule": true,
"lib": [
"dom",
"es5",
"scripthost",
"es2015.collection"
]
},
"exclude": [
"node_modules"