Fixing too many bugs, cleaning up some old parts of the code

This commit is contained in:
Pieter Vander Vennet 2020-09-03 16:44:48 +02:00
parent 3d05999f85
commit 00a6611e1f
21 changed files with 706 additions and 436 deletions

View file

@ -259,7 +259,13 @@ export class FromJSON {
const iconSize = FromJSON.TagRenderingWithDefault(json.iconSize, "iconSize", "40,40,center");
const color = FromJSON.TagRenderingWithDefault(json.color, "layercolor", "#0000ff");
const width = FromJSON.TagRenderingWithDefault(json.width, "layerwidth", "10");
const tagRenderings = json.tagRenderings?.map(FromJSON.TagRendering) ?? [];
let tagRenderingDefs = json.tagRenderings ?? [];
if (tagRenderingDefs.indexOf("images") < 0) {
tagRenderingDefs = ["images", ...tagRenderingDefs];
}
let tagRenderings = tagRenderingDefs.map(FromJSON.TagRendering);
const renderTags = {"id": "node/-1"}
const presets: Preset[] = json?.presets?.map(preset => {
@ -294,7 +300,7 @@ export class FromJSON {
// the anchor is always set from the center of the point
// x, y with x going right and y going down if the values are bigger
const popupAnchor = [0, -iconAnchor[1]+3];
const popupAnchor = [0, 3 - iconAnchor[1]];
return {
color: color.GetContent(tags).txt,

View file

@ -105,7 +105,16 @@ export class UIEventSource<T>{
});
return newSource;
}
public static Chronic(millis: number):UIEventSource<Date>{
const source = new UIEventSource<Date>(undefined);
function run() {
source.setData(new Date());
window.setTimeout(run, millis);
}
run();
return source;
}

View file

@ -23,16 +23,15 @@ export class State {
// The singleton of the global state
public static state: State;
public static vNumber = "0.0.7d Refactored";
public static vNumber = "0.0.7e Fixing all the bugs";
// The user journey states thresholds when a new feature gets unlocked
public static userJourney = {
customLayoutUnlock: 50,
themeGeneratorUnlock: 500,
tagsVisibleAt: 200,
tagsVisibleAndWikiLinked: 250
personalLayoutUnlock: 20,
tagsVisibleAt: 100,
tagsVisibleAndWikiLinked: 150,
themeGeneratorReadOnlyUnlock: 200,
themeGeneratorFullUnlock: 500,
};
public static runningFromConsole: boolean = false;
@ -68,13 +67,6 @@ export class State {
* The message that should be shown at the center of the screen
*/
public readonly centerMessage = new UIEventSource<string>("");
/**
* The countdown: if set to e.g. ten, it'll start counting down. When reaching zero, changes will be saved. NB: this is implemented later, not in the eventSource
*/
public readonly secondsTillChangesAreSaved = new UIEventSource<number>(0);
/**
This message is shown full screen on mobile devices
*/
@ -116,10 +108,6 @@ export class State {
latlng: number,
accuracy: number
}> = new UIEventSource<{ latlng: number, accuracy: number }>(undefined);
/** After this many milliseconds without changes, saves are sent of to OSM
*/
public readonly saveTimeout = new UIEventSource<number>(30 * 1000);
public layoutDefinition: string;
public installedThemes: UIEventSource<{ layout: Layout; definition: string }[]>;
@ -259,16 +247,5 @@ export class State {
return;
}
}
public GetFilteredLayerFor(id: string) : FilteredLayer{
for (const flayer of this.filteredLayers.data) {
if(flayer.layerDef.id === id){
return flayer;
}
}
return undefined;
}
}

View file

@ -6,6 +6,7 @@ import {LayoutConfigJson} from "../../Customizations/JSON/LayoutConfigJson";
import Combine from "../Base/Combine";
import {GenerateEmpty} from "./GenerateEmpty";
import LayerPanelWithPreview from "./LayerPanelWithPreview";
import {UserDetails} from "../../Logic/Osm/OsmConnection";
export default class AllLayersPanel extends UIElement {
@ -15,19 +16,19 @@ export default class AllLayersPanel extends UIElement {
private readonly languages: UIEventSource<string[]>;
constructor(config: UIEventSource<LayoutConfigJson>,
languages: UIEventSource<any>) {
languages: UIEventSource<any>, userDetails: UserDetails) {
super(undefined);
this._config = config;
this.languages = languages;
this.createPanels();
this.createPanels(userDetails);
const self = this;
config.map<number>(config => config.layers.length).addCallback(() => self.createPanels());
config.map<number>(config => config.layers.length).addCallback(() => self.createPanels(userDetails));
}
private createPanels() {
private createPanels(userDetails: UserDetails) {
const self = this;
const tabs = [];
@ -36,7 +37,8 @@ export default class AllLayersPanel extends UIElement {
tabs.push({
header: "<img src='./assets/bug.svg'>",
content: new LayerPanelWithPreview(this._config, this.languages, i)});
content: new LayerPanelWithPreview(this._config, this.languages, i, userDetails)
});
}
tabs.push({
header: "<img src='./assets/layersAdd.svg'>",

View file

@ -0,0 +1,115 @@
import {UIElement} from "../UIElement";
import {OsmConnection, UserDetails} from "../../Logic/Osm/OsmConnection";
import {UIEventSource} from "../../Logic/UIEventSource";
import SingleSetting from "./SingleSetting";
import GeneralSettings from "./GeneralSettings";
import Combine from "../Base/Combine";
import {VariableUiElement} from "../Base/VariableUIElement";
import {TabbedComponent} from "../Base/TabbedComponent";
import PageSplit from "../Base/PageSplit";
import HelpText from "../../Customizations/HelpText";
import AllLayersPanel from "./AllLayersPanel";
import SharePanel from "./SharePanel";
import {LayoutConfigJson} from "../../Customizations/JSON/LayoutConfigJson";
import {SubtleButton} from "../Base/SubtleButton";
import {State} from "../../State";
import {FixedUiElement} from "../Base/FixedUiElement";
import SavePanel from "./SavePanel";
import {LocalStorageSource} from "../../Logic/Web/LocalStorageSource";
export default class CustomGeneratorPanel extends UIElement {
private mainPanel: UIElement;
private loginButton: UIElement;
private connection: OsmConnection;
constructor(connection: OsmConnection, layout: LayoutConfigJson) {
super(connection.userDetails);
this.connection = connection;
this.SetClass("main-tabs");
this.loginButton = new SubtleButton("", "Login to create a custom theme").onClick(() => connection.AttemptLogin())
const self = this;
self.mainPanel = new FixedUiElement("Attempting to log in...");
connection.OnLoggedIn(userDetails => {
self.InitMainPanel(layout, userDetails, connection);
self.Update();
})
}
private InitMainPanel(layout: LayoutConfigJson, userDetails: UserDetails, connection: OsmConnection) {
const es = new UIEventSource(layout);
const encoded = es.map(config => btoa(JSON.stringify(config)));
encoded.addCallback(encoded => LocalStorageSource.Get("\"last-custom-theme\""))
const liveUrl = encoded.map(encoded => `./index.html?userlayout=${es.data.id}#${encoded}`)
const iframe = liveUrl.map(url => `<iframe src='${url}' width='100%' height='99%' style="box-sizing: border-box" title='Theme Preview'></iframe>`);
const currentSetting = new UIEventSource<SingleSetting<any>>(undefined)
const generalSettings = new GeneralSettings(es, currentSetting);
const languages = generalSettings.languages;
const chronic = UIEventSource.Chronic(120 * 1000)
.map(date => {
if (es.data.id == undefined) {
return undefined
}
if (es.data.id === "") {
return undefined;
}
const pref = connection.GetLongPreference("installed-theme-" + es.data.id);
pref.setData(encoded.data);
return date;
});
const preview = new Combine([
new VariableUiElement(iframe.stabilized(2500))
]).SetClass("preview")
this.mainPanel = new TabbedComponent([
{
header: "<img src='./assets/gear.svg'>",
content:
new PageSplit(
generalSettings.SetStyle("width: 50vw;"),
new Combine([
new HelpText(currentSetting).SetStyle("height:calc(100% - 65vh); width: 100%; display:block; overflow-y: auto"),
preview.SetStyle("height:65vh; width:100%; display:block")
]).SetStyle("position:relative; width: 50%;")
)
},
{
header: "<img src='./assets/layers.svg'>",
content: new AllLayersPanel(es, languages, userDetails)
},
{
header: "<img src='./assets/floppy.svg'>",
content: new SavePanel(this.connection, es, chronic)
},
{
header: "<img src='./assets/share.svg'>",
content: new SharePanel(es, liveUrl)
}
])
}
InnerRender(): string {
const ud = this.connection.userDetails.data;
if (!ud.loggedIn) {
return new Combine([
"<h3>Not Logged in</h3>",
"You need to be logged in in order to create a custom theme",
this.loginButton
]).Render();
}
if (ud.csCount <= State.userJourney.themeGeneratorReadOnlyUnlock) {
return new Combine([
"<h3>Too little experience/h3>",
`Creating your own (readonly) themes can only be done if you have more then <b>${State.userJourney.themeGeneratorReadOnlyUnlock}</b> changesets made`,
`Making a theme including survey options can be done at <b>${State.userJourney.themeGeneratorFullUnlock}</b> changesets`,
this.loginButton
]).Render();
}
return this.mainPanel.Render()
}
}

View file

@ -16,6 +16,9 @@ import {TagRenderingConfigJson} from "../../Customizations/JSON/TagRenderingConf
import {MultiInput} from "../Input/MultiInput";
import {LayerConfigJson} from "../../Customizations/JSON/LayerConfigJson";
import PresetInputPanel from "./PresetInputPanel";
import {UserDetails} from "../../Logic/Osm/OsmConnection";
import {State} from "../../State";
import {FixedUiElement} from "../Base/FixedUiElement";
/**
* Shows the configuration for a single layer
@ -38,10 +41,11 @@ export default class LayerPanel extends UIElement {
constructor(config: UIEventSource<LayoutConfigJson>,
languages: UIEventSource<string[]>,
index: number,
currentlySelected: UIEventSource<SingleSetting<any>>) {
currentlySelected: UIEventSource<SingleSetting<any>>,
userDetails: UserDetails) {
super();
this._config = config;
this.mapRendering = this.setupRenderOptions(config, languages, index, currentlySelected);
this.mapRendering = this.setupRenderOptions(config, languages, index, currentlySelected, userDetails);
const actualDeleteButton = new SubtleButton(
"./assets/delete.svg",
@ -100,7 +104,7 @@ export default class LayerPanel extends UIElement {
currentlySelected);
const self = this;
const popupTitleRendering = new TagRenderingPanel(languages, currentlySelected, {
const popupTitleRendering = new TagRenderingPanel(languages, currentlySelected, userDetails, {
title: "Popup title",
description: "This is the rendering shown as title in the popup for this element",
disableQuestions: true
@ -113,7 +117,7 @@ export default class LayerPanel extends UIElement {
const tagRenderings = new MultiInput<TagRenderingConfigJson>("Add a tag rendering/question",
() => ({}),
() => {
const tagPanel = new TagRenderingPanel(languages, currentlySelected)
const tagPanel = new TagRenderingPanel(languages, currentlySelected, userDetails)
self.registerTagRendering(tagPanel);
return tagPanel;
});
@ -124,11 +128,16 @@ export default class LayerPanel extends UIElement {
}
)
const presetPanel = new MultiInput("Add a preset",
() => ({tags: [], title: {}}),
() => new PresetInputPanel(currentlySelected, languages));
this.presetsPanel = presetPanel;
new SingleSetting(config, presetPanel, ["layers", index, "presets"], "Presets", "")
if (userDetails.csCount >= State.userJourney.themeGeneratorFullUnlock) {
const presetPanel = new MultiInput("Add a preset",
() => ({tags: [], title: {}}),
() => new PresetInputPanel(currentlySelected, languages));
new SingleSetting(config, presetPanel, ["layers", index, "presets"], "Presets", "")
this.presetsPanel = presetPanel;
} else {
this.presetsPanel = new FixedUiElement(`Creating a custom theme which also edits OSM is only unlocked after ${State.userJourney.themeGeneratorFullUnlock} changesets`).SetClass("alert");
}
function loadTagRenderings() {
const values = (config.data.layers[index] as LayerConfigJson).tagRenderings;
@ -152,27 +161,29 @@ export default class LayerPanel extends UIElement {
private setupRenderOptions(config: UIEventSource<LayoutConfigJson>,
languages: UIEventSource<string[]>,
index: number,
currentlySelected: UIEventSource<SingleSetting<any>>): UIElement {
currentlySelected: UIEventSource<SingleSetting<any>>,
userDetails: UserDetails
): UIElement {
const iconSelect = new TagRenderingPanel(
languages, currentlySelected,
languages, currentlySelected, userDetails,
{
title: "Icon",
description: "A visual representation for this layer and for the points on the map.",
disableQuestions: true
});
const size = new TagRenderingPanel(languages, currentlySelected,
const size = new TagRenderingPanel(languages, currentlySelected, userDetails,
{
title: "Icon Size",
description: "The size of the icons on the map in pixels. Can vary based on the tagging",
disableQuestions: true
});
const color = new TagRenderingPanel(languages, currentlySelected,
const color = new TagRenderingPanel(languages, currentlySelected, userDetails,
{
title: "Way and area color",
description: "The color or a shown way or area. Can vary based on the tagging",
disableQuestions: true
});
const stroke = new TagRenderingPanel(languages, currentlySelected,
const stroke = new TagRenderingPanel(languages, currentlySelected, userDetails,
{
title: "Stroke width",
description: "The width of lines representing ways and the outline of areas. Can vary based on the tags",

View file

@ -8,16 +8,17 @@ import {FromJSON} from "../../Customizations/JSON/FromJSON";
import Combine from "../Base/Combine";
import PageSplit from "../Base/PageSplit";
import TagRenderingPreview from "./TagRenderingPreview";
import {UserDetails} from "../../Logic/Osm/OsmConnection";
export default class LayerPanelWithPreview extends UIElement{
private panel: UIElement;
constructor(config: UIEventSource<any>, languages: UIEventSource<string[]>, index: number) {
constructor(config: UIEventSource<any>, languages: UIEventSource<string[]>, index: number, userDetails: UserDetails) {
super();
const currentlySelected = new UIEventSource<(SingleSetting<any>)>(undefined);
const layer = new LayerPanel(config, languages, index, currentlySelected);
const layer = new LayerPanel(config, languages, index, currentlySelected, userDetails);
const helpText = new HelpText(currentlySelected);
const previewTagInput = new MultiTagInput();

View file

@ -0,0 +1,49 @@
import {UIElement} from "../UIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import {LayoutConfigJson} from "../../Customizations/JSON/LayoutConfigJson";
import Combine from "../Base/Combine";
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
import {FixedUiElement} from "../Base/FixedUiElement";
export default class SavePanel extends UIElement {
private json: UIElement;
private lastSaveEl: UIElement;
constructor(
connection: OsmConnection,
config: UIEventSource<LayoutConfigJson>,
chronic: UIEventSource<Date>) {
super();
this.lastSaveEl = new VariableUiElement(chronic
.map(date => {
if (date === undefined) {
return new FixedUiElement("Your theme will be saved automatically within two minutes... Click here to force saving").SetClass("alert").Render()
}
return "Your theme was last saved at " + date.toISOString()
})).onClick(() => chronic.setData(new Date()));
this.json = new VariableUiElement(config.map(config => {
return JSON.stringify(config, null, 2)
.replace(/\n/g, "<br/>")
.replace(/ /g, "&nbsp;");
}))
.SetClass("literal-code");
}
InnerRender(): string {
return new Combine([
"<h3>Saving</h3>",
this.lastSaveEl,
"<h3>JSON configuration</h3>",
"The url hash is actually no more then a BASE64-encoding of the below JSON. This json contains the full configuration of the theme.<br/>" +
"This configuration is mainly useful for debugging",
this.json
]).SetClass("scrollable")
.Render();
}
}

View file

@ -12,6 +12,8 @@ import {MultiInput} from "../Input/MultiInput";
import MappingInput from "./MappingInput";
import {AndOrTagConfigJson} from "../../Customizations/JSON/TagConfigJson";
import {TagRenderingConfigJson} from "../../Customizations/JSON/TagRenderingConfigJson";
import {UserDetails} from "../../Logic/Osm/OsmConnection";
import {State} from "../../State";
export default class TagRenderingPanel extends InputElement<TagRenderingConfigJson> {
@ -24,6 +26,7 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
constructor(languages: UIEventSource<string[]>,
currentlySelected: UIEventSource<SingleSetting<any>>,
userDetails: UserDetails,
options?: {
title?: string,
description?: string,
@ -36,6 +39,10 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
this.SetClass("min-height");
this.options = options ?? {};
const questionsNotUnlocked = userDetails.csCount < State.userJourney.themeGeneratorFullUnlock;
this.options.disableQuestions =
(this.options.disableQuestions ?? false) &&
questionsNotUnlocked;
this.intro = new Combine(["<h3>", options?.title ?? "TagRendering", "</h3>", options?.description ?? ""])
this.IsImage = options?.isImage ?? false;
@ -47,9 +54,9 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
return new SingleSetting<any>(value, input, id, name, description);
}
const questionSettings = [
setting(new MultiLingualTextFields(languages), "question", "Question", "If the key or mapping doesn't match, this question is asked"),
setting(new AndOrTagInput(), "condition", "Condition",
@ -70,6 +77,8 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
const settings: (string | SingleSetting<any>)[] = [
setting(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."),
questionsNotUnlocked ? `You need at least ${State.userJourney.themeGeneratorFullUnlock} changesets to unlock the 'question'-field and to use your theme to edit OSM data`: "",
...(options?.disableQuestions ? [] : questionSettings),
"<h3>Mappings</h3>",

View file

@ -26,10 +26,10 @@ export class FeatureInfoBox extends UIElement {
private readonly _title: UIElement;
private readonly _osmLink: UIElement;
private readonly _wikipedialink: UIElement;
private _infoboxes: TagDependantUIElement[];
private readonly _infoboxes: TagDependantUIElement[];
private _oneSkipped = Translations.t.general.oneSkippedQuestion.Clone();
private _someSkipped = Translations.t.general.skippedQuestions.Clone();
private readonly _oneSkipped = Translations.t.general.oneSkippedQuestion.Clone();
private readonly _someSkipped = Translations.t.general.skippedQuestions.Clone();
constructor(
feature: any,
@ -53,7 +53,7 @@ export class FeatureInfoBox extends UIElement {
tagRenderingOption.construct(deps));
}
function initTags() {
self._infoboxes = []
self._infoboxes.splice(0, self._infoboxes.length);
for (const tagRenderingOption of elementsToShow) {
self._infoboxes.push(
tagRenderingOption.construct(deps));
@ -101,10 +101,10 @@ export class FeatureInfoBox extends UIElement {
}
let questionsHtml = "";
let questionElement: UIElement;
if (!State.state.osmConnection.userDetails.data.loggedIn) {
let mostImportantQuestion;
let mostImportantQuestion ;
let score = -1000;
for (const question of questions) {
@ -114,7 +114,7 @@ export class FeatureInfoBox extends UIElement {
}
}
questionsHtml = mostImportantQuestion?.Render() ?? "";
questionElement = mostImportantQuestion;
} else if (questions.length > 0) {
// We select the most important question and render that one
let mostImportantQuestion;
@ -127,11 +127,11 @@ export class FeatureInfoBox extends UIElement {
}
}
questionsHtml = mostImportantQuestion?.Render() ?? "";
questionElement = mostImportantQuestion;
} else if (skippedQuestions == 1) {
questionsHtml = this._oneSkipped.Render();
questionElement = this._oneSkipped;
} else if (skippedQuestions > 0) {
questionsHtml = this._someSkipped.Render();
questionElement = this._someSkipped;
}
const title = new Combine([
@ -140,12 +140,16 @@ export class FeatureInfoBox extends UIElement {
this._osmLink]);
const infoboxcontents = new Combine(
[ new VerticalCombine(info, "infobox-information "), questionsHtml]);
[ new VerticalCombine(info, "infobox-information "), questionElement ?? ""]);
return "<div class='featureinfobox'>" +
new Combine([
"<div class='featureinfoboxtitle'>" + title.Render() + "</div>",
"<div class='infoboxcontents'>" + infoboxcontents.Render() + "</div>"]).Render() + "</div>";
"<div class='featureinfoboxtitle'>",
title,
"</div>",
"<div class='infoboxcontents'>",
infoboxcontents,
"</div>"]).Render() + "</div>";
}

View file

@ -1,24 +1,36 @@
import {UIElement} from "./UIElement";
import {VariableUiElement} from "./Base/VariableUIElement";
import Translations from "./i18n/Translations";
import {State} from "../State";
import {UIEventSource} from "../Logic/UIEventSource";
import Combine from "./Base/Combine";
/**
* Handles the full screen popup on mobile
*/
export class FullScreenMessageBoxHandler {
export class FullScreenMessageBox extends UIElement {
private _uielement: UIEventSource<UIElement>;
private _uielement: UIElement;
private returnToTheMap: UIElement;
constructor(onClear: (() => void)) {
this._uielement = State.state.fullScreenMessage;
const self = this;
this._uielement.addCallback(function () {
self.update();
});
super(undefined);
this.update();
const self = this;
State.state.fullScreenMessage.addCallback(uielement => {
return self._uielement = uielement?.SetClass("messagesboxmobile-scroll")?.Activate();
});
this._uielement = State.state.fullScreenMessage.data;
this.ListenTo(State.state.fullScreenMessage);
this.HideOnEmpty(true);
State.state.fullScreenMessage.addCallback(latestData => {
if (latestData === undefined) {
location.hash = "";
} else {
location.hash = "#element";
}
this.Update();
})
if (window !== undefined) {
window.onhashchange = function () {
@ -30,36 +42,24 @@ export class FullScreenMessageBoxHandler {
}
}
Translations.t.general.returnToTheMap
this.returnToTheMap = Translations.t.general.returnToTheMap.Clone()
.SetClass("to-the-map")
.onClick(() => {
self._uielement.setData(undefined);
console.log("Returning...")
State.state.fullScreenMessage.setData(undefined);
onClear();
})
.AttachTo("to-the-map");
self.Update();
});
}
update() {
const wrapper = document.getElementById("messagesboxmobilewrapper");
const gen = this._uielement.data;
if (gen === undefined) {
wrapper.classList.add("hidden")
if (location.hash !== "") {
location.hash = ""
}
return;
InnerRender(): string {
if (this._uielement === undefined) {
return "";
}
location.hash = "#element"
wrapper.classList.remove("hidden");
gen
?.HideOnEmpty(true)
?.AttachTo("messagesboxmobile")
?.Activate();
return new Combine([this._uielement, this.returnToTheMap]).SetStyle("").Render();
}
}

View file

@ -72,7 +72,7 @@ export class MoreScreen extends UIElement {
els.push(new VariableUiElement(
State.state.osmConnection.userDetails.map(userDetails => {
if (userDetails.csCount < State.userJourney.themeGeneratorUnlock) {
if (userDetails.csCount < State.userJourney.themeGeneratorReadOnlyUnlock) {
return tr.requestATheme.Render();
}
return new SubtleButton("./assets/pencil.svg", tr.createYourOwnTheme, {
@ -86,7 +86,7 @@ export class MoreScreen extends UIElement {
for (const k in AllKnownLayouts.allSets) {
const layout : Layout = AllKnownLayouts.allSets[k];
if (k === PersonalLayout.NAME) {
if (State.state.osmConnection.userDetails.data.csCount < State.userJourney.customLayoutUnlock) {
if (State.state.osmConnection.userDetails.data.csCount < State.userJourney.personalLayoutUnlock) {
continue;
}
}
@ -99,7 +99,6 @@ export class MoreScreen extends UIElement {
const customThemesNames = State.state.installedThemes.data ?? [];
if (customThemesNames.length > 0) {
console.log(customThemesNames)
els.push(Translations.t.general.customThemeIntro)
for (const installed of State.state.installedThemes.data) {

View file

@ -99,7 +99,7 @@ export abstract class UIElement extends UIEventSource<string> {
if (element.innerHTML === "") {
element.parentElement.style.display = "none";
} else {
element.parentElement.style.display = undefined;
element.parentElement.style.display = "block";
}
}
@ -174,7 +174,7 @@ export abstract class UIElement extends UIEventSource<string> {
public abstract InnerRender(): string;
public Activate(): void {
public Activate(): UIElement {
for (const i in this) {
const child = this[i];
if (child instanceof UIElement) {
@ -187,6 +187,7 @@ export abstract class UIElement extends UIEventSource<string> {
}
}
}
return this;
};
public IsEmpty(): boolean {

View file

@ -1,7 +1,6 @@
import {UIElement} from "./UIElement";
import Locale from "../UI/i18n/Locale";
import {State} from "../State";
import {Layout} from "../Customizations/Layout";
import Translations from "./i18n/Translations";
import Combine from "./Base/Combine";
import {InitUiElements} from "../InitUiElements";
@ -20,27 +19,18 @@ export class WelcomeMessage extends UIElement {
super(State.state.osmConnection.userDetails);
this.ListenTo(Locale.language);
this.languagePicker = InitUiElements.CreateLanguagePicker(Translations.t.general.pickLanguage);
const layout = State.state.layoutToUse.data;
function fromLayout(f: (layout: Layout) => (string | UIElement)): UIElement {
return Translations.W(f(State.state.layoutToUse.data));
}
this.description = fromLayout((layout) => layout.welcomeMessage);
this.description =Translations.W(layout.welcomeMessage);
this.plzLogIn =
fromLayout((layout) => layout.gettingStartedPlzLogin
.onClick(() => {State.state.osmConnection.AttemptLogin()})
);
this.welcomeBack = fromLayout((layout) => layout.welcomeBackMessage);
this.tail = fromLayout((layout) => layout.welcomeTail);
Translations.W(layout.gettingStartedPlzLogin)
.onClick(() => {
State.state.osmConnection.AttemptLogin()
});
this.welcomeBack = Translations.W(layout.welcomeBackMessage);
this.tail = Translations.W(layout.welcomeTail);
}
protected InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement);
console.log("Innerupdating welcome message")
this.plzLogIn.Update();
}
InnerRender(): string {
let loginStatus = undefined;
@ -52,7 +42,8 @@ export class WelcomeMessage extends UIElement {
return new Combine([
this.description,
"<br/></br>",
// TODO this button is broken - figure out why loginStatus,
// TODO this button is broken - figure out why loginStatus,
loginStatus,
this.tail,
"<br/>",
this.languagePicker

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100"
height="100"
viewBox="0 0 26.458333 26.458334"
version="1.1"
id="svg8"
sodipodi:docname="arrow-left-smooth.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="19.262262"
inkscape:cy="36.323203"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<sodipodi:guide
position="13.229167,23.859748"
orientation="1,0"
id="guide815"
inkscape:locked="false" />
<sodipodi:guide
position="14.944824,13.229167"
orientation="0,1"
id="guide817"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-270.54165)">
<path
style="fill:none;stroke:#ffffff;stroke-width:3.59588718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 20.139011,294.16029 c 0,0 -13.7995299,-7.53922 -13.8484369,-10.36091 -0.04891,-2.82169 13.8484369,-10.38607 13.8484369,-10.38607"
id="path821"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100"
height="100"
viewBox="0 0 26.458333 26.458334"
version="1.1"
id="svg8"
sodipodi:docname="arrow-right-smooth.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="-22.237738"
inkscape:cy="36.323203"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<sodipodi:guide
position="13.229167,23.859748"
orientation="1,0"
id="guide815"
inkscape:locked="false" />
<sodipodi:guide
position="14.944824,13.229167"
orientation="0,1"
id="guide817"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-270.54165)">
<path
style="fill:none;stroke:#ffffff;stroke-width:3.59588718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 6.3128214,273.41335 c 0,0 13.7995296,7.53922 13.8484366,10.36091 0.04891,2.82169 -13.8484366,10.38607 -13.8484366,10.38607"
id="path821"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -12,7 +12,11 @@
"amenity=bicycle_parking"
]
},
"icon": "./assets/layers/bike_parking/parking.svg",
"icon": {
"render": {
"en": "./assets/layers/bike_parking/parking.svg"
}
},
"size": {
"render": {
"en": "50,50,bottom"

View file

@ -1,70 +1,25 @@
import {UIEventSource} from "./Logic/UIEventSource";
import SingleSetting from "./UI/CustomGenerator/SingleSetting";
import Combine from "./UI/Base/Combine";
import {VariableUiElement} from "./UI/Base/VariableUIElement";
import GeneralSettings from "./UI/CustomGenerator/GeneralSettings";
import {TabbedComponent} from "./UI/Base/TabbedComponent";
import AllLayersPanel from "./UI/CustomGenerator/AllLayersPanel";
import SharePanel from "./UI/CustomGenerator/SharePanel";
import {GenerateEmpty} from "./UI/CustomGenerator/GenerateEmpty";
import PageSplit from "./UI/Base/PageSplit";
import HelpText from "./Customizations/HelpText";
import {TagRendering} from "./Customizations/TagRendering";
import {LayoutConfigJson} from "./Customizations/JSON/LayoutConfigJson";
import {OsmConnection} from "./Logic/Osm/OsmConnection";
import CustomGeneratorPanel from "./UI/CustomGenerator/CustomGeneratorPanel";
import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
let layout = GenerateEmpty.createEmptyLayout();
if(window.location.hash.length > 10){
if (window.location.hash.length > 10) {
layout = JSON.parse(atob(window.location.hash.substr(1))) as LayoutConfigJson;
} else {
const hash = LocalStorageSource.Get("last-custom-theme").data
if (hash !== undefined) {
layout = JSON.parse(atob(hash)) as LayoutConfigJson;
}
}
const es = new UIEventSource(layout);
const encoded = es.map(config => btoa(JSON.stringify(config)));
const liveUrl = encoded.map(encoded => `./index.html?userlayout=${es.data.id}#${encoded}`)
const iframe = liveUrl.map(url => `<iframe src='${url}' width='100%' height='99%' style="box-sizing: border-box" title='Theme Preview'></iframe>`);
TagRendering.injectFunction();
const currentSetting = new UIEventSource<SingleSetting<any>>(undefined)
const connection = new OsmConnection(false, new UIEventSource<string>(undefined), "customGenerator", false);
const generalSettings = new GeneralSettings(es, currentSetting);
const languages = generalSettings.languages;
// The preview
const preview = new Combine([
new VariableUiElement(iframe.stabilized(2500))
]).SetClass("preview")
new TabbedComponent([
{
header: "<img src='./assets/gear.svg'>",
content:
new PageSplit(
generalSettings.SetStyle("width: 50vw;"),
new Combine([
new HelpText(currentSetting).SetStyle("height:calc(100% - 65vh); width: 100%; display:block; overflow-y: auto"),
preview.SetStyle("height:65vh; width:100%; display:block")
]).SetStyle("position:relative; width: 50%;")
)
},
{
header: "<img src='./assets/layers.svg'>",
content: new AllLayersPanel(es, languages)
},
{
header: "<img src='./assets/floppy.svg'>",
content: new VariableUiElement(es.map(config => {
return JSON.stringify(config, null, 2)
.replace(/\n/g, "<br/>")
.replace(/ /g, "&nbsp;");
}))
},
{
header: "<img src='./assets/share.svg'>",
content: new SharePanel(es, liveUrl)
}
]).SetClass("main-tabs")
new CustomGeneratorPanel(connection, layout)
.AttachTo("maindiv");

441
index.css
View file

@ -284,6 +284,9 @@
padding-top: 0.2em;
padding-bottom: 0.2em;
}
#hidden-on-mobile {
display: none; /*Only shown on small screens*/
}
@media only screen and (max-height: 600px) and (not (max-width:700px)) {
@ -351,9 +354,6 @@
padding-right: 10px; /* Shadow offset */
}
#messagesboxmobilewrapper {
display: none; /*Only shown on small screens*/
}
.collapse-button {
position: absolute;
@ -556,6 +556,10 @@
@media only screen and (max-width: 600px), only screen and (max-height: 600px) {
#hidden-on-mobile {
display: block;
}
#messagesbox-wrapper {
display: none;
}
@ -590,33 +594,20 @@
}
#messagesboxmobilewrapper {
position: absolute;
padding: 0;
margin: 0;
z-index: 5050;
transition: all 500ms linear;
overflow: hidden;
border-radius: 0;
width: 100%;
height: 100%;
display: block;
background-color: white;
}
#messagesboxmobile-scroll {
.messagesboxmobile-scroll {
display: block;
width: 100vw;
overflow-y: auto;
padding: 0;
box-sizing: border-box;
overflow-y: scroll;
padding: 1em;
margin: 0;
height: calc(100% - 5em); /*Height of to-the-map is 5em*/
height: calc(100vh - 5em); /*Height of to-the-map is 5em*/
}
#messagesboxmobile {
padding: 1em;
padding-bottom: 2em;
border-radius: 1em;
display: block;
position: absolute;
z-index: 10000;
background-color: white;
}
@ -639,72 +630,72 @@
}
#to-the-map {
position: relative;
}
#to-the-map > span{
position: absolute;
box-sizing: border-box;
height: 3em;
padding: 0.5em;
margin: 0;
padding-top: 0.75em;
text-align: center;
width: 100%;
color: white;
background-color: #7ebc6f;
cursor: pointer;
font-size: xx-large;
font-weight: bold;
}
@media only screen and (max-height: 400px) {
/* Landscape: small 'to the map' */
#to-the-map {
position: relative;
height: 100%;
width: 100%
}
#to-the-map span {
width: auto;
border-top-left-radius: 1.5em;
position: absolute;
z-index: 5;
right: 0;
bottom: 0;
height: auto;
margin:0;
padding: 1em;
padding-bottom: 0.75em;
height: 3em;
font-size: x-large;
}
#messagesboxmobile {
padding-bottom: 5em;
}
#messagesboxmobile-scroll {
position: absolute;
z-index: 2;
width: 100vw;
height: 100vh;
.to-the-map {
display: block;
box-sizing: border-box;
height: 2.5em;
margin: 0;
padding: 0.5em;
padding-top: 0.75em;
text-align: center;
color: white;
background-color: #7ebc6f;
cursor: pointer;
font-size: xx-large;
font-weight: bold;
border-top-left-radius: 0.5em;
border-top-right-radius: 0.5em;
}
#welcomeMessage{
box-shadow: unset;
max-height: 100vh;
}
}
@media only screen and (max-height: 400px) {
/* Landscape: small 'to the map' */
#hidden-on-mobile {
display: unset;
}
.to-the-map {
position: relative;
height: 100%;
width: 100%
}
.to-the-map span {
width: auto;
border-top-left-radius: 1.5em;
position: absolute;
z-index: 5;
right: 0;
bottom: 0;
height: auto;
margin: 0;
padding: 1em;
padding-bottom: 0.75em;
height: 3em;
font-size: x-large;
}
#messagesboxmobile {
position: absolute;
display: block;
z-index: 10000;
padding-bottom: 5em;
}
.messagesboxmobile-scroll {
display: block;
width: 100vw;
height: calc(100vh - 5em);
box-sizing: border-box;
overflow-y: scroll;
}
#welcomeMessage {
box-shadow: unset;
max-height: 100vh;
}
}
.logo {
float:right;
@ -841,170 +832,173 @@
}
.attribution-author {
display: inline-block;
}
.attribution-author {
display: inline-block;
}
.license {
font-size: small;
font-weight: lighter;
}
.license {
font-size: small;
font-weight: lighter;
}
.attribution a {
color: white;
}
.attribution a {
color: white;
}
/**************** Image upload flow ***************/
/**************** Image upload flow ***************/
.imageflow {
margin-top: 1em;
margin-bottom: 2em;
text-align: center;
.imageflow {
margin-top: 1em;
margin-bottom: 2em;
text-align: center;
}
}
.imageflow-file-input-wrapper {
display: flex;
flex-wrap: wrap;
padding: 0.5em;
border-radius: 1em;
border: 3px solid black;
}
.imageflow-file-input-wrapper {
display: flex;
flex-wrap: wrap;
padding: 0.5em;
border-radius: 1em;
border: 3px solid black;
}
.imageflow-add-picture {
font-size: 28px;
font-weight: bold;
float: left;
margin-top: 4px;
padding-top: 4px;
padding-bottom: 4px;
padding-left: 13px;
}
.imageflow-add-picture {
font-size: 28px;
font-weight: bold;
float: left;
margin-top: 4px;
padding-top: 4px;
padding-bottom: 4px;
padding-left: 13px;
}
.imageflow-file-input-wrapper img {
width: 36px;
height: 36px;
padding: 0.1em;
margin-top: 5px;
border-radius: 0;
float: left;
}
.imageflow-file-input-wrapper img {
width: 36px;
height: 36px;
padding: 0.1em;
margin-top: 5px;
border-radius: 0;
float: left;
}
.license-picker {
float: left;
}
.license-picker {
float: left;
}
.imageflow > input {
display: none;
}
.imageflow > input {
display: none;
}
/***************** Info box (box containing features and questions ******************/
/***************** Info box (box containing features and questions ******************/
.leaflet-popup-content {
width: 40em !important;
}
#messagesboxmobile .featureinfobox {
max-height: unset;
overflow-y: unset;
}
#messagesboxmobile .featureinfobox > div {
width: 100%;
max-width: unset;
padding-left: unset;
}
.featureinfobox {
max-height: 80vh;
overflow-y: auto;
}
.featureinfobox > div {
width: calc(100% - 2em);
padding-left: 1em;
}
.leaflet-popup-content {
width: 40em !important;
}
.featureinfoboxtitle {
position: relative;
}
.question .form-text-field > input{
width: 100%;
box-sizing: border-box;
}
.osmlink {
position: absolute;
right: 0;
}
.osm-logo path {
fill: #7ebc6f;
}
.featureinfoboxtitle .answer {
display: inline;
margin-right: 3em;
}
.featureinfoboxtitle .answer-text {
display: inline;
}
.featureinfoboxtitle .editbutton {
float: none;
width: 0.8em;
height: 0.8em;
padding: 0.3em;
border-radius: 0.35em;
border: solid black 1px;
margin-left: 0.5em;
top: 0.2em;
vertical-align: middle;
}
.editbutton {
width: 1.3em;
height: 1.3em;
padding: 0.5em;
border-radius: 0.65em;
border: solid black 1px;
#messagesboxmobile .featureinfobox {
max-height: unset;
overflow-y: unset;
}
font-size: medium;
float: right;
#messagesboxmobile .featureinfobox > div {
width: 100%;
max-width: unset;
padding-left: unset;
}
.featureinfobox {
max-height: 80vh;
overflow-y: auto;
}
.featureinfobox > div {
width: calc(100% - 2em);
padding-left: 1em;
}
}
.featureinfoboxtitle {
position: relative;
}
.wikipedialink {
position: absolute;
right: 24px;
width: 24px;
height: 24px;
padding-right: 12px;
}
.question .form-text-field > input {
width: 100%;
box-sizing: border-box;
}
.wikipedialink img {
width: 24px;
height: 24px;
}
.osmlink {
position: absolute;
right: 0;
}
.osm-logo path {
fill: #7ebc6f;
}
.featureinfoboxtitle .answer {
display: inline;
margin-right: 3em;
}
.featureinfoboxtitle .answer-text {
display: inline;
}
.featureinfoboxtitle .editbutton {
float: none;
width: 0.8em;
height: 0.8em;
padding: 0.3em;
border-radius: 0.35em;
border: solid black 1px;
margin-left: 0.5em;
top: 0.2em;
vertical-align: middle;
}
.featureinfoboxtitle span {
font-weight: bold;
font-size: x-large;
}
.editbutton {
width: 1.3em;
height: 1.3em;
padding: 0.5em;
border-radius: 0.65em;
border: solid black 1px;
.featureinfoboxtitle a {
float: right;
margin-left: 1em;
font-size: medium;
float: right;
}
.wikipedialink {
position: absolute;
right: 24px;
width: 24px;
height: 24px;
padding-right: 12px;
}
.wikipedialink img {
width: 24px;
height: 24px;
}
.featureinfoboxtitle span {
font-weight: bold;
font-size: x-large;
}
.featureinfoboxtitle a {
float: right;
margin-left: 1em;
}
@ -1273,6 +1267,7 @@
padding: 0.5em;
word-break: break-all;
color: black;
box-sizing: border-box;
}
.iframe-code-block {

View file

@ -14,7 +14,7 @@
<!-- $$$OG-META -->
<style>
.decoration-desktop img {
#decoration-desktop img {
width: 100%;
height: 100%;
}
@ -23,33 +23,16 @@
</head>
<body>
<div style="position: absolute; left: 1em; top: 1em; width:35vh; height:35vh;" class="decoration-desktop">
<div style="position: absolute; left: 1em; bottom: 1em; width:35vh; height:35vh;"
id="decoration-desktop">
<!-- A nice decoration while loading or on errors -->
<!-- DECORATION 0 START -->
<img src="./assets/add.svg"/>
<!-- DECORATION 0 END -->
</div>
<div id="messagesboxmobilewrapper">
<div id="decoration" style="position: absolute; left: 1em; top: 1em; z-index: 1; width: 15em; height: 15em">
<!-- A nice decoration while loading or on errors -->
<!-- DECORATION 1 START -->
<img src="./assets/add.svg"/>
<!-- DECORATION 1 END -->
</div>
<div id="messagesboxmobile-scroll">
<div id="messagesboxmobile">
<div style="position: absolute; top: 50vh; left: 25vw; width:50vw; box-sizing: border-box;font-size: x-large">
Loading MapComplete
</div>
</div>
</div>
<div id="to-the-map">
If this message persists:
<ul style="margin:0">
<li>Is javascript enabled?</li>
<li>Is a scriptblocker (such as UMatrix or Brave) active?</li>
</ul>
<div id="hidden-on-mobile">
<div id="messagesboxmobile">
</div>
</div>

View file

@ -2,7 +2,6 @@ import {TagRendering} from "./Customizations/TagRendering";
import {UserBadge} from "./UI/UserBadge";
import {CenterMessageBox} from "./UI/CenterMessageBox";
import {TagUtils} from "./Logic/Tags";
import {FullScreenMessageBoxHandler} from "./UI/FullScreenMessageBoxHandler";
import {FeatureInfoBox} from "./UI/FeatureInfoBox";
import {SimpleAddUI} from "./UI/SimpleAddUI";
import {SearchAndGo} from "./UI/SearchAndGo";
@ -17,6 +16,7 @@ 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";
TagRendering.injectFunction();
@ -28,9 +28,9 @@ if (location.href.startsWith("http://buurtnatuur.be")) {
window.location.replace("https://buurtnatuur.be");
}
const testing = QueryParameters.GetQueryParameter("test", "true");
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
// Set to true if testing and changes should NOT be saved
const testing = QueryParameters.GetQueryParameter("test", "true");
testing.setData(testing.data ?? "true")
// If you have a testfile somewhere, enable this to spoof overpass
// This should be hosted independantly, e.g. with `cd assets; webfsd -p 8080` + a CORS plugin to disable cors rules
@ -113,13 +113,17 @@ if (layoutToUse.hideFromOverview) {
if (layoutFromBase64 !== "false") {
State.state.layoutDefinition = hash.substr(1);
State.state.osmConnection.OnLoggedIn(() => {
State.state.osmConnection.GetLongPreference("installed-theme-" + layoutToUse.id).setData(State.state.layoutDefinition);
})
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"); // Remove the decoration
new FixedUiElement("").AttachTo("decoration-desktop"); // Remove the decoration
function setupAllLayerElements() {
@ -232,9 +236,12 @@ InitUiElements.OnlyIf((State.state.featureSwitchSearch), () => {
new SearchAndGo().AttachTo("searchbox");
});
new FullScreenMessageBoxHandler(() => {
new FullScreenMessageBox(() => {
State.state.selectedElement.setData(undefined)
}).update();
}).AttachTo("messagesboxmobile");
InitUiElements.OnlyIf(State.state.featureSwitchWelcomeMessage, () => {
InitUiElements.InitWelcomeMessage()