Add the possibility to load layouts from the OSM-wiki. What could possibly go wrong?

This commit is contained in:
Pieter Vander Vennet 2020-09-07 02:25:45 +02:00
parent 115dc0249c
commit d9cae0fc46
7 changed files with 297 additions and 222 deletions

View file

@ -51,7 +51,7 @@ export class FromJSON {
const tr = FromJSON.Translation; const tr = FromJSON.Translation;
const layers = json.layers.map(FromJSON.Layer); const layers = json.layers.map(FromJSON.Layer);
const roaming: TagDependantUIElementConstructor[] = json.roamingRenderings?.map(FromJSON.TagRendering) ?? []; const roaming: TagDependantUIElementConstructor[] = json.roamingRenderings?.map((tr, i) => FromJSON.TagRendering(tr, "Roaming rendering "+i)) ?? [];
for (const layer of layers) { for (const layer of layers) {
layer.elementsToShow.push(...roaming); layer.elementsToShow.push(...roaming);
} }
@ -95,15 +95,15 @@ export class FromJSON {
return new Translation(tr); return new Translation(tr);
} }
public static TagRendering(json: TagRenderingConfigJson | string): TagDependantUIElementConstructor { public static TagRendering(json: TagRenderingConfigJson | string, propertyeName: string): TagDependantUIElementConstructor {
return FromJSON.TagRenderingWithDefault(json, "", undefined); return FromJSON.TagRenderingWithDefault(json, propertyeName, undefined);
} }
public static TagRenderingWithDefault(json: TagRenderingConfigJson | string, propertyName, defaultValue: string): TagDependantUIElementConstructor { public static TagRenderingWithDefault(json: TagRenderingConfigJson | string, propertyName, defaultValue: string): TagDependantUIElementConstructor {
if (json === undefined) { if (json === undefined) {
if(defaultValue !== undefined){ if(defaultValue !== undefined){
console.log(`Using default value ${defaultValue} for ${propertyName}`) console.log(`Using default value ${defaultValue} for ${propertyName}`)
return FromJSON.TagRendering(defaultValue); return FromJSON.TagRendering(defaultValue, propertyName);
} }
throw `Tagrendering ${propertyName} is undefined...` throw `Tagrendering ${propertyName} is undefined...`
} }
@ -145,7 +145,7 @@ export class FromJSON {
// Setup the freeform // Setup the freeform
if (template === undefined) { if (template === undefined) {
console.error("Freeform.key is defined, but render is not. This is not allowed.", json) console.error("Freeform.key is defined, but render is not. This is not allowed.", json)
throw "Freeform is defined, but render is not. This is not allowed." throw `Freeform is defined in tagrendering ${propertyName}, but render is not. This is not allowed.`
} }
freeform = { freeform = {
@ -175,7 +175,7 @@ export class FromJSON {
if(template === undefined && (mappings === undefined || mappings.length === 0)){ if(template === undefined && (mappings === undefined || mappings.length === 0)){
console.error("Empty tagrendering detected: no mappings nor template given", json) console.error("Empty tagrendering detected: no mappings nor template given", json)
throw "Empty tagrendering detected: no mappings nor template given" throw `Empty tagrendering ${propertyName} detected: no mappings nor template given`
} }
@ -247,27 +247,33 @@ export class FromJSON {
} }
public static Layer(json: LayerConfigJson | string): LayerDefinition { public static Layer(json: LayerConfigJson | string): LayerDefinition {
if (typeof (json) === "string") { if (typeof (json) === "string") {
const cached = FromJSON.sharedLayers.get(json); const cached = FromJSON.sharedLayers.get(json);
if (cached) { if (cached) {
return cached; return cached;
} }
throw `Layer ${json} not yet loaded...`
throw "Layer not yet loaded..."
} }
try {
return FromJSON.LayerUncaught(json);
} catch (e) {
throw `While parsing layer ${json.id}: ${e}`
}
}
private static LayerUncaught(json: LayerConfigJson): LayerDefinition {
console.log("Parsing layer", json) console.log("Parsing layer", json)
const tr = FromJSON.Translation; const tr = FromJSON.Translation;
const overpassTags = FromJSON.Tag(json.overpassTags); const overpassTags = FromJSON.Tag(json.overpassTags);
const icon = FromJSON.TagRenderingWithDefault(json.icon, "layericon", "./assets/bug.svg"); const icon = FromJSON.TagRenderingWithDefault(json.icon, "icon", "./assets/bug.svg");
const iconSize = FromJSON.TagRenderingWithDefault(json.iconSize, "iconSize", "40,40,center"); const iconSize = FromJSON.TagRenderingWithDefault(json.iconSize, "iconSize", "40,40,center");
const color = FromJSON.TagRenderingWithDefault(json.color, "layercolor", "#0000ff"); const color = FromJSON.TagRenderingWithDefault(json.color, "color", "#0000ff");
const width = FromJSON.TagRenderingWithDefault(json.width, "layerwidth", "10"); const width = FromJSON.TagRenderingWithDefault(json.width, "width", "10");
if(json.title === "Layer"){ if (json.title === "Layer") {
json.title = {}; json.title = {};
} }
let title= FromJSON.TagRendering(json.title); let title = FromJSON.TagRendering(json.title, "Popup title");
let tagRenderingDefs = json.tagRenderings ?? []; let tagRenderingDefs = json.tagRenderings ?? [];
@ -277,7 +283,7 @@ export class FromJSON {
continue; continue;
} }
let str = tagRenderingDef as string; let str = tagRenderingDef as string;
if(tagRenderingDef.indexOf("images") >= 0 || str.indexOf("pictures") >= 0){ if (tagRenderingDef.indexOf("images") >= 0 || str.indexOf("pictures") >= 0) {
hasImageElement = true; hasImageElement = true;
break; break;
} }
@ -285,7 +291,7 @@ export class FromJSON {
if (!hasImageElement) { if (!hasImageElement) {
tagRenderingDefs = ["images", ...tagRenderingDefs]; tagRenderingDefs = ["images", ...tagRenderingDefs];
} }
let tagRenderings = tagRenderingDefs.map(FromJSON.TagRendering); let tagRenderings = tagRenderingDefs.map((tr, i) => FromJSON.TagRendering(tr, "Tagrendering #"+i));
const renderTags = {"id": "node/-1"} const renderTags = {"id": "node/-1"}
@ -352,10 +358,7 @@ export class FromJSON {
} }
); );
console.log("Parsed layer is ", layer);
return layer; return layer;
} }
} }

View file

@ -22,10 +22,214 @@ import {QueryParameters} from "./Logic/Web/QueryParameters";
import {PersonalLayout} from "./Logic/PersonalLayout"; import {PersonalLayout} from "./Logic/PersonalLayout";
import {PersonalLayersPanel} from "./Logic/PersonalLayersPanel"; import {PersonalLayersPanel} from "./Logic/PersonalLayersPanel";
import Locale from "./UI/i18n/Locale"; import Locale from "./UI/i18n/Locale";
import {StrayClickHandler} from "./Logic/Leaflet/StrayClickHandler";
import {SimpleAddUI} from "./UI/SimpleAddUI";
import {CenterMessageBox} from "./UI/CenterMessageBox";
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
import {TagUtils} from "./Logic/Tags";
import {UserBadge} from "./UI/UserBadge";
import {SearchAndGo} from "./UI/SearchAndGo";
import {FullScreenMessageBox} from "./UI/FullScreenMessageBoxHandler";
import {GeoLocationHandler} from "./Logic/Leaflet/GeoLocationHandler";
import {Layout} from "./Customizations/Layout";
import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
import {FromJSON} from "./Customizations/JSON/FromJSON";
export class InitUiElements { export class InitUiElements {
static InitAll(layoutToUse: Layout, layoutFromBase64: string, testing: UIEventSource<string>, defaultLayout1: string ) {
if (layoutToUse === undefined) {
console.log("Incorrect layout")
new FixedUiElement("Error: incorrect layout <i>" + defaultLayout1 + "</i><br/><a href='https://pietervdvn.github.io/MapComplete/index.html'>Go back</a>").AttachTo("centermessage").onClick(() => {
});
throw "Incorrect layout"
}
console.log("Using layout: ", layoutToUse.id);
State.state = new State(layoutToUse);
if (layoutToUse.hideFromOverview) {
State.state.osmConnection.GetPreference("hidden-theme-" + layoutToUse.id + "-enabled").setData("true");
}
if (layoutFromBase64 !== "false") {
State.state.layoutDefinition = location.hash.substr(1);
if (testing.data !== "true") {
State.state.osmConnection.OnLoggedIn(() => {
State.state.osmConnection.GetLongPreference("installed-theme-" + layoutToUse.id).setData(State.state.layoutDefinition);
})
} else {
console.warn("NOT saving custom layout to OSM as we are tesing -> probably in an iFrame")
}
}
InitUiElements.InitBaseMap();
new FixedUiElement("").AttachTo("decoration-desktop"); // Remove the decoration
function setupAllLayerElements() {
// ------------- Setup the layers -------------------------------
InitUiElements.InitLayers();
InitUiElements.InitLayerSelection();
// ------------------ Setup various other UI elements ------------
InitUiElements.OnlyIf(State.state.featureSwitchAddNew, () => {
let presetCount = 0;
for (const layer of State.state.filteredLayers.data) {
for (const preset of layer.layerDef.presets) {
presetCount++;
}
}
if (presetCount == 0) {
return;
}
new StrayClickHandler(() => {
return new SimpleAddUI();
}
);
});
new CenterMessageBox().AttachTo("centermessage");
}
setupAllLayerElements();
function updateFavs() {
const favs = State.state.favouriteLayers.data ?? [];
layoutToUse.layers = [];
for (const fav of favs) {
const layer = AllKnownLayouts.allLayers[fav];
if (!!layer) {
layoutToUse.layers.push(layer);
}
for (const layouts of State.state.installedThemes.data) {
for (const layer of layouts.layout.layers) {
if (typeof layer === "string") {
continue;
}
if (layer.id === fav) {
layoutToUse.layers.push(layer);
}
}
}
}
setupAllLayerElements();
State.state.layerUpdater.ForceRefresh();
State.state.locationControl.ping();
}
if (layoutToUse === AllKnownLayouts.allSets[PersonalLayout.NAME]) {
State.state.favouriteLayers.addCallback(updateFavs);
State.state.installedThemes.addCallback(updateFavs);
}
/**
* Show the questions and information for the selected element
* This is given to the div which renders fullscreen on mobile devices
*/
State.state.selectedElement.addCallback((feature) => {
if (feature?.feature?.properties === undefined) {
return;
}
const data = feature.feature.properties;
// Which is the applicable set?
for (const layer of layoutToUse.layers) {
if (typeof layer === "string") {
continue;
}
const applicable = layer.overpassFilter.matches(TagUtils.proprtiesToKV(data));
if (applicable) {
// This layer is the layer that gives the questions
const featureBox = new FeatureInfoBox(
feature.feature,
State.state.allElements.getElement(data.id),
layer.title,
layer.elementsToShow,
);
State.state.fullScreenMessage.setData(featureBox);
break;
}
}
}
);
InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => {
new UserBadge().AttachTo('userbadge');
});
InitUiElements.OnlyIf((State.state.featureSwitchSearch), () => {
new SearchAndGo().AttachTo("searchbox");
});
new FullScreenMessageBox(() => {
State.state.selectedElement.setData(undefined)
}).AttachTo("messagesboxmobile");
InitUiElements.OnlyIf(State.state.featureSwitchWelcomeMessage, () => {
InitUiElements.InitWelcomeMessage()
});
if ((window != window.top && !State.state.featureSwitchWelcomeMessage.data) || State.state.featureSwitchIframe.data) {
const currentLocation = State.state.locationControl;
const url = `${window.location.origin}${window.location.pathname}?z=${currentLocation.data.zoom}&lat=${currentLocation.data.lat}&lon=${currentLocation.data.lon}`;
const content = `<a href='${url}' target='_blank'><span class='iframe-escape'><img src='assets/pop-out.svg'></span></a>`;
new FixedUiElement(content).AttachTo("messagesbox");
new FixedUiElement(content).AttachTo("help-button-mobile")
}
new GeoLocationHandler().AttachTo("geolocate-button");
State.state.locationControl.ping();
}
static LoadLayoutFromHash(userLayoutParam: UIEventSource<string>) {
try {
let hash = location.hash.substr(1);
const layoutFromBase64 = userLayoutParam.data;
// layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter
const dedicatedHashFromLocalStorage = LocalStorageSource.Get("user-layout-" + layoutFromBase64.replace(" ", "_"));
if (dedicatedHashFromLocalStorage.data?.length < 10) {
dedicatedHashFromLocalStorage.setData(undefined);
}
const hashFromLocalStorage = LocalStorageSource.Get("last-loaded-user-layout");
if (hash.length < 10) {
hash = dedicatedHashFromLocalStorage.data ?? hashFromLocalStorage.data;
} else {
console.log("Saving hash to local storage")
hashFromLocalStorage.setData(hash);
dedicatedHashFromLocalStorage.setData(hash);
}
const layoutToUse = FromJSON.FromBase64(hash.substr(1));
userLayoutParam.setData(layoutToUse.id);
return layoutToUse;
} catch (e) {
new FixedUiElement("Error: could not parse the custom layout:<br/> " + e).AttachTo("centermessage");
throw e;
}
}
static OnlyIf(featureSwitch: UIEventSource<boolean>, callback: () => void) { static OnlyIf(featureSwitch: UIEventSource<boolean>, callback: () => void) {
featureSwitch.addCallback(() => { featureSwitch.addCallback(() => {

View file

@ -86,7 +86,7 @@ export default class CustomGeneratorPanel extends UIElement {
}, },
{ {
header: "<img src='./assets/share.svg'>", header: "<img src='./assets/share.svg'>",
content: new SharePanel(es, liveUrl) content: new SharePanel(es, liveUrl, userDetails)
} }
]) ])
} }

View file

@ -7,7 +7,6 @@ import {OsmConnection} from "../../Logic/Osm/OsmConnection";
import {FixedUiElement} from "../Base/FixedUiElement"; import {FixedUiElement} from "../Base/FixedUiElement";
import {TextField} from "../Input/TextField"; import {TextField} from "../Input/TextField";
import {SubtleButton} from "../Base/SubtleButton"; import {SubtleButton} from "../Base/SubtleButton";
import {FromJSON} from "../../Customizations/JSON/FromJSON";
export default class SavePanel extends UIElement { export default class SavePanel extends UIElement {
private json: UIElement; private json: UIElement;

View file

@ -3,22 +3,50 @@ import {UIEventSource} from "../../Logic/UIEventSource";
import {LayoutConfigJson} from "../../Customizations/JSON/LayoutConfigJson"; import {LayoutConfigJson} from "../../Customizations/JSON/LayoutConfigJson";
import Combine from "../Base/Combine"; import Combine from "../Base/Combine";
import {VariableUiElement} from "../Base/VariableUIElement"; import {VariableUiElement} from "../Base/VariableUIElement";
import {UserDetails} from "../../Logic/Osm/OsmConnection";
export default class SharePanel extends UIElement { export default class SharePanel extends UIElement {
private _config: UIEventSource<LayoutConfigJson>; private _config: UIEventSource<LayoutConfigJson>;
private _panel: UIElement; private _panel: UIElement;
constructor(config: UIEventSource<LayoutConfigJson>, liveUrl: UIEventSource<string>) { constructor(config: UIEventSource<LayoutConfigJson>, liveUrl: UIEventSource<string>, userDetails: UserDetails) {
super(undefined); super(undefined);
this._config = config; this._config = config;
const proposedName = `User:${userDetails.name}/${config.data.id}`
const proposedNameEnc = encodeURIComponent(`wiki:User:${userDetails.name}/${config.data.id}`)
this._panel = new Combine([ this._panel = new Combine([
"<h2>share</h2>", "<h2>share</h2>",
"Share the following link with friends:<br/>", "Share the following link with friends:<br/>",
new VariableUiElement(liveUrl.map(url => `<a href='${url}' target="_blank">${url}</a>`)), new VariableUiElement(liveUrl.map(url => `<a href='${url}' target="_blank">${url}</a>`)),
"<h2>Publish on OSM Wiki</h2>",
"In the list of 'hacks upon hacks to make MapComplete work', it is now possible to put the JSON-file onto a Wikipage*.<br/>" +
"This is a <i>very stable</i> and <i>very well-tested</i> solution. Using wikipages as source control! What could possibly go wrong???? /s<br/><br/>",
"Why to publish the layout as a wikipage?",
"<ul>",
...["If something breaks, it can be fixed centrally",
"If someone has a remark about your preset, the talk page can be used to point this out and discuss the preset",
"In case of a grave error, everyone can step in to fix the prest"].map(li => `<li>${li}</li>`),
"</ul>",
"In order to make this work:",
"<ol>",
...[`Create a new page on the OSM-wiki, e.g. <a href='https://wiki.osm.org/wiki/${proposedName}' target="_blank">${proposedName}</a>`,
"Click 'create page'",
"Type <span class='literal-code'>&lt;nowiki&gt;</span>, a few newlines and <span class='literal-code'>&lt;/nowiki&gt;</span>",
"Copy the json configuration from the 'save-tab', paste it between the 'nowiki'-tags in the Wiki",
"Click 'save' to save the wiki page",
"Share the link with the url parameter <span class='literal-code'>userlayout=wiki:YOURWIKIPAGE</span>, e.g. " +
`<a href='./index.html?userlayout=${proposedNameEnc}' target='_blank'>https://pietervdvn.github.io/MapComplete/index.html?userlayout=${proposedNameEnc}</a>`
].map(li => `<li>${li}</li>`),
"</ol>",
"(* This has made a lot of people very angry and been widely regarded as a bad move.)",
"</div>" "</div>"
]); ]);
} }

View file

@ -88,7 +88,8 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
"<h3>Mappings</h3>", "<h3>Mappings</h3>",
setting(new MultiInput<{ if: AndOrTagConfigJson, then: (string | any), hideInAnswer?: boolean }>("Add a mapping", setting(new MultiInput<{ if: AndOrTagConfigJson, then: (string | any), hideInAnswer?: boolean }>("Add a mapping",
() => ({if: undefined, then: undefined}), () => ({if: undefined, then: undefined}),
() => new MappingInput(languages, options?.disableQuestions ?? false)), "mappings", () => new MappingInput(languages, options?.disableQuestions ?? false),
undefined, {allowMovement: true}), "mappings",
"If a tag matches, then show the first respective text", "") "If a tag matches, then show the first respective text", "")
]; ];

234
index.ts
View file

@ -1,23 +1,12 @@
import {TagRendering} from "./Customizations/TagRendering"; import {TagRendering} from "./Customizations/TagRendering";
import {UserBadge} from "./UI/UserBadge";
import {CenterMessageBox} from "./UI/CenterMessageBox";
import {TagUtils} from "./Logic/Tags";
import {FeatureInfoBox} from "./UI/FeatureInfoBox";
import {SimpleAddUI} from "./UI/SimpleAddUI";
import {SearchAndGo} from "./UI/SearchAndGo";
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
import {Layout} from "./Customizations/Layout"; import {Layout} from "./Customizations/Layout";
import {FixedUiElement} from "./UI/Base/FixedUiElement"; import {FixedUiElement} from "./UI/Base/FixedUiElement";
import {InitUiElements} from "./InitUiElements"; import {InitUiElements} from "./InitUiElements";
import {StrayClickHandler} from "./Logic/Leaflet/StrayClickHandler";
import {GeoLocationHandler} from "./Logic/Leaflet/GeoLocationHandler";
import {State} from "./State";
import {QueryParameters} from "./Logic/Web/QueryParameters"; import {QueryParameters} from "./Logic/Web/QueryParameters";
import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
import {PersonalLayout} from "./Logic/PersonalLayout";
import {FromJSON} from "./Customizations/JSON/FromJSON";
import {FullScreenMessageBox} from "./UI/FullScreenMessageBoxHandler";
import {UIEventSource} from "./Logic/UIEventSource"; import {UIEventSource} from "./Logic/UIEventSource";
import * as $ from "jquery";
import {FromJSON} from "./Customizations/JSON/FromJSON";
TagRendering.injectFunction(); TagRendering.injectFunction();
@ -73,193 +62,44 @@ let layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout.toLowerCase()] ?
const userLayoutParam = QueryParameters.GetQueryParameter("userlayout", "false"); const userLayoutParam = QueryParameters.GetQueryParameter("userlayout", "false");
const layoutFromBase64 = userLayoutParam.data; const layoutFromBase64 = decodeURIComponent(userLayoutParam.data);
if (layoutFromBase64 !== "false") { console.log(layoutFromBase64);
try { if (layoutFromBase64.startsWith("wiki:")) {
// layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter console.log("Downloading map theme from the wiki");
const themeName = layoutFromBase64.substr("wiki:".length);
new FixedUiElement(`Downloading ${themeName} from the wiki...`)
.AttachTo("centermessage");
const cleanUrl = `https://wiki.openstreetmap.org/wiki/${themeName}`;
const url = `https://cors-anywhere.herokuapp.com/` + cleanUrl; // VERY SAFE AND HACKER-PROOF!
const dedicatedHashFromLocalStorage = LocalStorageSource.Get("user-layout-" + layoutFromBase64.replace(" ", "_")); $.ajax({
if(dedicatedHashFromLocalStorage.data?.length < 10){ url: url,
dedicatedHashFromLocalStorage.setData(undefined); dataType: 'xml',
} success: function (data) {
const layoutJson = data.querySelector('[id="bodyContent"]')
const hashFromLocalStorage = LocalStorageSource.Get("last-loaded-user-layout"); .querySelector('[class="mw-parser-output"]')
if (hash.length < 10) { .children[0]
hash = dedicatedHashFromLocalStorage.data ?? hashFromLocalStorage.data; .firstChild.textContent;
} else { try {
console.log("Saving hash to local storage") console.log("DOWNLOADED:",layoutJson);
hashFromLocalStorage.setData(hash); const layout = FromJSON.LayoutFromJSON(JSON.parse(layoutJson));
dedicatedHashFromLocalStorage.setData(hash); InitUiElements.InitAll(layout, layoutFromBase64, testing, layoutFromBase64);
} } catch (e) {
layoutToUse = FromJSON.FromBase64(hash.substr(1)); new FixedUiElement(`<a href="${cleanUrl}">${themeName}</a> is invalid:<br/>${e}`)
userLayoutParam.setData(layoutToUse.id); .SetClass("clickable")
} catch (e) { .AttachTo("centermessage");
new FixedUiElement("Error: could not parse the custom layout:<br/> "+e).AttachTo("centermessage"); throw e;
throw e;
}
}
if (layoutToUse === undefined) {
console.log("Incorrect layout")
new FixedUiElement("Error: incorrect layout <i>" + defaultLayout + "</i><br/><a href='https://pietervdvn.github.io/MapComplete/index.html'>Go back</a>").AttachTo("centermessage").onClick(() => {
});
throw "Incorrect layout"
}
console.log("Using layout: ", layoutToUse.id);
State.state = new State(layoutToUse);
if (layoutToUse.hideFromOverview) {
State.state.osmConnection.GetPreference("hidden-theme-" + layoutToUse.id + "-enabled").setData("true");
}
if (layoutFromBase64 !== "false") {
State.state.layoutDefinition = hash.substr(1);
if (!testing) {
State.state.osmConnection.OnLoggedIn(() => {
State.state.osmConnection.GetLongPreference("installed-theme-" + layoutToUse.id).setData(State.state.layoutDefinition);
})
}else{
console.warn("NOT saving custom layout to OSM as we are tesing -> probably in an iFrame")
}
}
InitUiElements.InitBaseMap();
new FixedUiElement("").AttachTo("decoration-desktop"); // Remove the decoration
function setupAllLayerElements() {
// ------------- Setup the layers -------------------------------
InitUiElements.InitLayers();
InitUiElements.InitLayerSelection();
// ------------------ Setup various other UI elements ------------
InitUiElements.OnlyIf(State.state.featureSwitchAddNew, () => {
let presetCount = 0;
for (const layer of State.state.filteredLayers.data) {
for (const preset of layer.layerDef.presets) {
presetCount++;
} }
} },
if (presetCount == 0) { }).fail(e => {
return; new FixedUiElement(`<a href="${cleanUrl}">${themeName}</a> is invalid:<br/>Could not download - wrong URL?`)
} .SetClass("clickable")
.AttachTo("centermessage");
new StrayClickHandler(() => {
return new SimpleAddUI();
}
);
}); });
new CenterMessageBox().AttachTo("centermessage"); } else {
if (layoutFromBase64 !== "false") {
} layoutToUse = InitUiElements.LoadLayoutFromHash(userLayoutParam);
setupAllLayerElements();
function updateFavs() {
const favs = State.state.favouriteLayers.data ?? [];
layoutToUse.layers = [];
for (const fav of favs) {
const layer = AllKnownLayouts.allLayers[fav];
if (!!layer) {
layoutToUse.layers.push(layer);
}
for (const layouts of State.state.installedThemes.data) {
for (const layer of layouts.layout.layers) {
if (typeof layer === "string") {
continue;
}
if (layer.id === fav) {
layoutToUse.layers.push(layer);
}
}
}
} }
InitUiElements.InitAll(layoutToUse, layoutFromBase64, testing, defaultLayout);
setupAllLayerElements();
State.state.layerUpdater.ForceRefresh();
State.state.locationControl.ping();
} }
if (layoutToUse === AllKnownLayouts.allSets[PersonalLayout.NAME]) {
State.state.favouriteLayers.addCallback(updateFavs);
State.state.installedThemes.addCallback(updateFavs);
}
/**
* Show the questions and information for the selected element
* This is given to the div which renders fullscreen on mobile devices
*/
State.state.selectedElement.addCallback((feature) => {
if (feature?.feature?.properties === undefined) {
return;
}
const data = feature.feature.properties;
// Which is the applicable set?
for (const layer of layoutToUse.layers) {
if (typeof layer === "string") {
continue;
}
const applicable = layer.overpassFilter.matches(TagUtils.proprtiesToKV(data));
if (applicable) {
// This layer is the layer that gives the questions
const featureBox = new FeatureInfoBox(
feature.feature,
State.state.allElements.getElement(data.id),
layer.title,
layer.elementsToShow,
);
State.state.fullScreenMessage.setData(featureBox);
break;
}
}
}
);
InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => {
new UserBadge().AttachTo('userbadge');
});
InitUiElements.OnlyIf((State.state.featureSwitchSearch), () => {
new SearchAndGo().AttachTo("searchbox");
});
new FullScreenMessageBox(() => {
State.state.selectedElement.setData(undefined)
}).AttachTo("messagesboxmobile");
InitUiElements.OnlyIf(State.state.featureSwitchWelcomeMessage, () => {
InitUiElements.InitWelcomeMessage()
});
if ((window != window.top && !State.state.featureSwitchWelcomeMessage.data) || State.state.featureSwitchIframe.data) {
const currentLocation = State.state.locationControl;
const url = `${window.location.origin}${window.location.pathname}?z=${currentLocation.data.zoom}&lat=${currentLocation.data.lat}&lon=${currentLocation.data.lon}`;
const content = `<a href='${url}' target='_blank'><span class='iframe-escape'><img src='assets/pop-out.svg'></span></a>`;
new FixedUiElement(content).AttachTo("messagesbox");
new FixedUiElement(content).AttachTo("help-button-mobile")
}
new GeoLocationHandler().AttachTo("geolocate-button");
State.state.locationControl.ping();