From 60c15e9c8db024eaff705e000475ac184ab4d77d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 27 Aug 2020 18:44:16 +0200 Subject: [PATCH] Add cyclestreet theme, various bugfixes --- Customizations/AllKnownLayouts.ts | 2 + Customizations/TagRendering.ts | 6 +- Logic/LayerUpdater.ts | 3 +- Logic/Osm/Changes.ts | 245 ++++++++++--------- Logic/Osm/ChangesetHandler.ts | 26 +- Logic/Osm/OsmConnection.ts | 11 +- Logic/Osm/OsmObject.ts | 26 ++ Logic/TagsFilter.ts | 33 +-- README.md | 10 +- State.ts | 1 + UI/Base/TabbedComponent.ts | 8 +- UI/CustomThemeGenerator/Preview.ts | 2 +- UI/CustomThemeGenerator/ThemeGenerator.ts | 16 +- UI/Input/TextField.ts | 2 +- UI/MoreScreen.ts | 6 +- UI/UIElement.ts | 9 +- UI/UserBadge.ts | 6 +- UI/WelcomeMessage.ts | 36 ++- assets/themes/cyclestreets/cyclestreets.json | 132 +++++++++- customGenerator.ts | 4 +- index.css | 26 +- index.ts | 7 +- tsconfig.json | 6 + 23 files changed, 412 insertions(+), 211 deletions(-) diff --git a/Customizations/AllKnownLayouts.ts b/Customizations/AllKnownLayouts.ts index 28bf5900e..8d428470b 100644 --- a/Customizations/AllKnownLayouts.ts +++ b/Customizations/AllKnownLayouts.ts @@ -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(), diff --git a/Customizations/TagRendering.ts b/Customizations/TagRendering.ts index 273eb97f7..1e635d218 100644 --- a/Customizations/TagRendering.ts +++ b/Customizations/TagRendering.ts @@ -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; } diff --git a/Logic/LayerUpdater.ts b/Logic/LayerUpdater.ts index 6472e51ad..e3c3f09ed 100644 --- a/Logic/LayerUpdater.ts +++ b/Logic/LayerUpdater.ts @@ -11,12 +11,11 @@ export class LayerUpdater { public readonly sufficentlyZoomed: UIEventSource = new UIEventSource(false); public readonly runningQuery: UIEventSource = new UIEventSource(false); public readonly retries: UIEventSource = new UIEventSource(0); - /** + /** * The previous bounds for which the query has been run */ private previousBounds: Bounds; - /** * The most important layer should go first, as that one gets first pick for the questions * @param map diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index 65e5e1c67..2a41386f7 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -14,56 +14,75 @@ 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; } - - if(tagsFilter instanceof And){ + 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); + } + + + 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"; } - + /** * Adds a change to the pending changes * @param elementId * @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,37 +128,90 @@ export class Changes { return geojson; } + + 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) { + // This is a new element - we should apply this on one of the new elements + for (const newElement of newElements) { + if (newElement.type + "/" + newElement.id === change.elementId) { + newElement.addTag(change.key, change.value); + } + } + + } else { + knownElements[change.elementId].addTag(change.key, change.value); + } + } + + // Small sanity check for duplicate information + let changedElements = []; + for (const elementId in knownElements) { + const element = knownElements[elementId]; + if (element.changed) { + changedElements.push(element); + } + } + if (changedElements.length == 0 && newElements.length == 0) { + console.log("No changes in any object"); + return; + } + + console.log("Beginning upload..."); + // At last, we build the changeset and upload + State.state.osmConnection.UploadChangeset( + function (csId) { + + let modifications = ""; + for (const element of changedElements) { + if (!element.changed) { + continue; + } + modifications += element.ChangesetXML(csId) + "\n"; + } + + + let creations = ""; + for (const newElement of newElements) { + creations += newElement.ChangesetXML(csId); + } + + + let changes = ``; + + if (creations.length > 0) { + changes += + "" + + creations + + ""; + } + + if (modifications.length > 0) { + + changes += + "" + + modifications + + ""; + } + + changes += ""; + + return changes; + }); + }; + + 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 = []; + let neededIds: string[] = []; for (const change of pending) { const id = change.elementId; if (parseFloat(id.split("/")[1]) < 0) { @@ -149,80 +221,9 @@ export class Changes { } } - - DownloadAndContinue(neededIds, function () { - // Here, inside the continuation, we know that all 'neededIds' are loaded in 'knownElements' - // We apply the changes on them - for (const change of pending) { - if (parseInt(change.elementId.split("/")[1]) < 0) { - // This is a new element - we should apply this on one of the new elements - for (const newElement of newElements) { - if (newElement.type + "/" + newElement.id === change.elementId) { - newElement.addTag(change.key, change.value); - } - } - - } else { - knownElements[change.elementId].addTag(change.key, change.value); - } - } - - // Small sanity check for duplicate information - let changedElements = []; - for (const elementId in knownElements) { - const element = knownElements[elementId]; - if (element.changed) { - changedElements.push(element); - } - } - if (changedElements.length == 0 && newElements.length == 0) { - console.log("No changes in any object"); - return; - } - - console.log("Beginning upload..."); - // At last, we build the changeset and upload - State.state.osmConnection.UploadChangeset( - function (csId) { - - let modifications = ""; - for (const element of changedElements) { - if (!element.changed) { - continue; - } - modifications += element.ChangesetXML(csId) + "\n"; - } - - - let creations = ""; - for (const newElement of newElements) { - creations += newElement.ChangesetXML(csId); - } - - - let changes = ``; - - if (creations.length > 0) { - changes += - "" + - creations + - ""; - } - - if (modifications.length > 0) { - - changes += - "" + - modifications + - ""; - } - - changes += ""; - - return changes; - }, - () => { - }); + neededIds = Utils.Dedup(neededIds); + OsmObject.DownloadAll(neededIds, {}, (knownElements) => { + self.uploadChangesWithLatestVersions(knownElements, newElements, pending) }); } diff --git a/Logic/Osm/ChangesetHandler.ts b/Logic/Osm/ChangesetHandler.ts index 7cbfe0593..a235d607e 100644 --- a/Logic/Osm/ChangesetHandler.ts +++ b/Logic/Osm/ChangesetHandler.ts @@ -10,11 +10,11 @@ export class ChangesetHandler { public currentChangeset: UIEventSource; - 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(); } diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 877c701e7..fa2a5e436 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -29,7 +29,11 @@ export class OsmConnection { private _onLoggedIn : ((userDetails: UserDetails) => void)[] = []; - constructor(dryRun: boolean, oauth_token: UIEventSource, singlePage: boolean = true, useDevServer:boolean = false) { + constructor(dryRun: boolean, oauth_token: UIEventSource, + // 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' diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index 9e20a679e..2b2147cf6 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -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); + } + ); + } } diff --git a/Logic/TagsFilter.ts b/Logic/TagsFilter.ts index 57da3b5f9..6d4124ad2 100644 --- a/Logic/TagsFilter.ts +++ b/Logic/TagsFilter.ts @@ -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 === "") { @@ -109,10 +114,6 @@ export class Tag extends TagsFilter { return Tag.regexOrStrMatches(this.value, tag.v) !== this.invertValue } } - - 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{ + v = this.value; + } else { // value is a regex v = this.value.source; } - v = Utils.EllipsesAfter(v, 25); + if (shorten) { + v = Utils.EllipsesAfter(v, 25); + } if (linkToWiki) { return `${this.key}` + `=` + @@ -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); } } diff --git a/README.md b/README.md index c92e5eeaa..0649f85fb 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/State.ts b/State.ts index d5b014edb..b19c6dd62 100644 --- a/State.ts +++ b/State.ts @@ -175,6 +175,7 @@ export class State { this.osmConnection = new OsmConnection( testParam === "true", QueryParameters.GetQueryParameter("oauth_token", undefined), + layoutToUse.name, true, testParam === "dev" ); diff --git a/UI/Base/TabbedComponent.ts b/UI/Base/TabbedComponent.ts index b79f28e12..a37fa2d0c 100644 --- a/UI/Base/TabbedComponent.ts +++ b/UI/Base/TabbedComponent.ts @@ -35,9 +35,13 @@ export class TabbedComponent extends UIElement { headerBar = "
" + headerBar + "
" - const content = this.content[this._source.data].Render(); + const content = this.content[this._source.data]; + return headerBar + "
" + content.Render() + "
"; + } - return headerBar + "
" + content + "
"; + protected InnerUpdate(htmlElement: HTMLElement) { + super.InnerUpdate(htmlElement); + this.content[this._source.data].Update(); } } \ No newline at end of file diff --git a/UI/CustomThemeGenerator/Preview.ts b/UI/CustomThemeGenerator/Preview.ts index 2643ab863..3cd9016b3 100644 --- a/UI/CustomThemeGenerator/Preview.ts +++ b/UI/CustomThemeGenerator/Preview.ts @@ -13,7 +13,7 @@ export class Preview extends UIElement { private reloadButton: Button; private otherPreviews: VariableUiElement; - constructor(url: UIEventSource, config: UIEventSource) { + constructor(url: UIEventSource, testurl: UIEventSource, config: UIEventSource) { super(undefined); this.config = config; this.url = url; diff --git a/UI/CustomThemeGenerator/ThemeGenerator.ts b/UI/CustomThemeGenerator/ThemeGenerator.ts index f6146c477..fea4db1bf 100644 --- a/UI/CustomThemeGenerator/ThemeGenerator.ts +++ b/UI/CustomThemeGenerator/ThemeGenerator.ts @@ -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: "", 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; private readonly allQuestionFields: UIElement[]; public url: UIEventSource; + public testurl: UIEventSource; 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()}; diff --git a/UI/Input/TextField.ts b/UI/Input/TextField.ts index a9060bc39..b6242252c 100644 --- a/UI/Input/TextField.ts +++ b/UI/Input/TextField.ts @@ -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 diff --git a/UI/MoreScreen.ts b/UI/MoreScreen.ts index 27a3bc016..6548ae141 100644 --- a/UI/MoreScreen.ts +++ b/UI/MoreScreen.ts @@ -23,8 +23,10 @@ export class MoreScreen extends UIElement { } private createLinkButton(layout: Layout, customThemeDefinition: string = undefined) { - if (layout.hideFromOverview && State.state.osmConnection.userDetails.data.name !== "Pieter Vander Vennet") { - return undefined; + 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; diff --git a/UI/UIElement.ts b/UI/UIElement.ts index f096a0f2a..83a6ab6e4 100644 --- a/UI/UIElement.ts +++ b/UI/UIElement.ts @@ -6,7 +6,7 @@ export abstract class UIElement extends UIEventSource{ public readonly id: string; public readonly _source: UIEventSource; - public clss : string = "" + private clss: string[] = [] private _hideIfEmpty = false; @@ -41,6 +41,7 @@ export abstract class UIElement extends UIEventSource{ public onClick(f: (() => void)) { this._onClick = f; + this.SetClass("clickable") this.Update(); return this; } @@ -107,7 +108,7 @@ export abstract class UIElement extends UIEventSource{ } Render(): string { - return `${this.InnerRender()}` + return `${this.InnerRender()}` } AttachTo(divId: string) { @@ -142,7 +143,9 @@ export abstract class UIElement extends UIEventSource{ } public SetClass(clss: string): UIElement { - this.clss = clss; + if (this.clss.indexOf(clss) < 0) { + this.clss.push(clss); + } return this; } diff --git a/UI/UserBadge.ts b/UI/UserBadge.ts index 243a4d03f..a30a55df4 100644 --- a/UI/UserBadge.ts +++ b/UI/UserBadge.ts @@ -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("logout") .onClick(() => { State.state.osmConnection.LogOut(); diff --git a/UI/WelcomeMessage.ts b/UI/WelcomeMessage.ts index 02282f5fe..93a132aee 100644 --- a/UI/WelcomeMessage.ts +++ b/UI/WelcomeMessage.ts @@ -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); } + protected InnerUpdate(htmlElement: HTMLElement) { + super.InnerUpdate(htmlElement); + console.log("Innerupdating welcome message") + this.plzLogIn.Update(); + } + + InnerRender(): string { - let loginStatus = ""; + let loginStatus = undefined; if (State.state.featureSwitchUserbadge.data) { - loginStatus = (State.state.osmConnection.userDetails.data.loggedIn ? this.welcomeBack : this.plzLogIn).Render(); - loginStatus = loginStatus + "
" + loginStatus = (State.state.osmConnection.userDetails.data.loggedIn ? this.welcomeBack : + this.plzLogIn); } - return "" + - this.description.Render() + - "

" + - loginStatus + - this.tail.Render() + - "
" + - this.languagePicker.Render() + - "
"; + return new Combine([ + this.description, + "

", + // TODO this button is broken - figure out why loginStatus, + this.tail, + "
", + this.languagePicker + ]).Render() } diff --git a/assets/themes/cyclestreets/cyclestreets.json b/assets/themes/cyclestreets/cyclestreets.json index 859f84d9a..6ab6ef750 100644 --- a/assets/themes/cyclestreets/cyclestreets.json +++ b/assets/themes/cyclestreets/cyclestreets.json @@ -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.

Op deze open kaart kan je alle gekende fietsstraten zien en kan je ontbrekende fietsstraten aanduiden.", + "widenFactor": 0.03 } \ No newline at end of file diff --git a/customGenerator.ts b/customGenerator.ts index 2ab840fc5..4207f1e36 100644 --- a/customGenerator.ts +++ b/customGenerator.ts @@ -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(undefined), false); +const connection = new OsmConnection(true, new UIEventSource(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, "Loading from the text field will erase the current theme", diff --git a/index.css b/index.css index f2abaecc5..033543378 100644 --- a/index.css +++ b/index.css @@ -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; diff --git a/index.ts b/index.ts index f91fad67b..4e7dc993c 100644 --- a/index.ts +++ b/index.ts @@ -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(); diff --git a/tsconfig.json b/tsconfig.json index 66d328895..16522ca96 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,12 @@ "target": "es5", "sourceMap": true, "resolveJsonModule": true, + "lib": [ + "dom", + "es5", + "scripthost", + "es2015.collection" + ] }, "exclude": [ "node_modules"