diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts
index be2adc52df..fa615d7a63 100644
--- a/Customizations/JSON/LayerConfig.ts
+++ b/Customizations/JSON/LayerConfig.ts
@@ -325,7 +325,7 @@ export default class LayerConfig {
function render(tr: TagRenderingConfig, deflt?: string) {
const str = (tr?.GetRenderValue(tags.data)?.txt ?? deflt);
- return SubstitutedTranslation.SubstituteKeys(str, tags.data).replace(/{.*}/g, "");
+ return Utils.SubstituteKeys(str, tags.data).replace(/{.*}/g, "");
}
const iconSize = render(this.iconSize, "40,40,center").split(",");
diff --git a/Customizations/JSON/TagRenderingConfig.ts b/Customizations/JSON/TagRenderingConfig.ts
index 157bc2d1bf..80e51c603b 100644
--- a/Customizations/JSON/TagRenderingConfig.ts
+++ b/Customizations/JSON/TagRenderingConfig.ts
@@ -7,6 +7,8 @@ import {Utils} from "../../Utils";
import {TagUtils} from "../../Logic/Tags/TagUtils";
import {And} from "../../Logic/Tags/And";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
+import {UIElement} from "../../UI/UIElement";
+import {SubstitutedTranslation} from "../../UI/SubstitutedTranslation";
/***
* The parsed version of TagRenderingConfigJSON
@@ -240,6 +242,46 @@ export default class TagRenderingConfig {
return this.question === null && this.condition === null;
}
+ /**
+ * Gets all the render values. Will return multiple render values if 'multianswer' is enabled.
+ * The result will equal [GetRenderValue] if not 'multiAnswer'
+ * @param tags
+ * @constructor
+ */
+ public GetRenderValues(tags: any): Translation[]{
+ if(!this.multiAnswer){
+ return [this.GetRenderValue(tags)]
+ }
+
+ // A flag to check that the freeform key isn't matched multiple times
+ // If it is undefined, it is "used" already, or at least we don't have to check for it anymore
+ let freeformKeyUsed = this.freeform?.key === undefined;
+ // We run over all the mappings first, to check if the mapping matches
+ const applicableMappings: Translation[] = Utils.NoNull((this.mappings ?? [])?.map(mapping => {
+ if (mapping.if === undefined) {
+ return mapping.then;
+ }
+ if (TagUtils.MatchesMultiAnswer(mapping.if, tags)) {
+ if(!freeformKeyUsed){
+ if(mapping.if.usedKeys().indexOf(this.freeform.key) >= 0){
+ // This mapping matches the freeform key - we mark the freeform key to be ignored!
+ freeformKeyUsed = true;
+ }
+ }
+ return mapping.then;
+ }
+ return undefined;
+ }))
+
+
+
+ if (!freeformKeyUsed
+ && tags[this.freeform.key] !== undefined) {
+ applicableMappings.push(this.render)
+ }
+ return applicableMappings
+ }
+
/**
* Gets the correct rendering value (or undefined if not known)
* @constructor
diff --git a/InitUiElements.ts b/InitUiElements.ts
index 8fd6f88be9..7858dba2a8 100644
--- a/InitUiElements.ts
+++ b/InitUiElements.ts
@@ -269,11 +269,10 @@ export class InitUiElements {
// ?-Button on Desktop, opens panel with close-X.
const help = new MapControlButton(Svg.help_svg());
+ help.onClick(() => isOpened.setData(true))
new Toggle(
fullOptions
- .SetClass("welcomeMessage")
- .onClick(() => {/*Catch the click*/
- }),
+ .SetClass("welcomeMessage"),
help
, isOpened
).AttachTo("messagesbox");
@@ -308,7 +307,8 @@ export class InitUiElements {
copyrightNotice,
new MapControlButton(Svg.osm_copyright_svg()),
copyrightNotice.isShown
- ).SetClass("p-0.5")
+ ).ToggleOnClick()
+ .SetClass("p-0.5")
const layerControlPanel = new LayerControlPanel(
State.state.layerControlIsOpened)
@@ -317,7 +317,7 @@ export class InitUiElements {
layerControlPanel,
new MapControlButton(Svg.layers_svg()),
State.state.layerControlIsOpened
- )
+ ).ToggleOnClick()
const layerControl = new Toggle(
layerControlButton,
diff --git a/Logic/Actors/OverpassFeatureSource.ts b/Logic/Actors/OverpassFeatureSource.ts
index b9b7866d99..d76ef12064 100644
--- a/Logic/Actors/OverpassFeatureSource.ts
+++ b/Logic/Actors/OverpassFeatureSource.ts
@@ -53,7 +53,6 @@ export default class OverpassFeatureSource implements FeatureSource {
return false;
}
let minzoom = Math.min(...layoutToUse.data.layers.map(layer => layer.minzoom ?? 18));
- console.debug("overpass source: minzoom is ", minzoom)
return location.zoom >= minzoom;
}, [layoutToUse]
);
diff --git a/Logic/Actors/StrayClickHandler.ts b/Logic/Actors/StrayClickHandler.ts
index 064da814c5..b4d6300700 100644
--- a/Logic/Actors/StrayClickHandler.ts
+++ b/Logic/Actors/StrayClickHandler.ts
@@ -47,13 +47,13 @@ export default class StrayClickHandler {
popupAnchor: [0, -45]
})
});
- const popup = L.popup().setContent(uiToShow.Render());
+ const popup = L.popup().setContent("
");
self._lastMarker.addTo(leafletMap.data);
self._lastMarker.bindPopup(popup);
self._lastMarker.on("click", () => {
+ uiToShow.AttachTo("strayclick")
uiToShow.Activate();
- uiToShow.Update();
});
});
diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts
index f140f2fd2a..004cefa2a2 100644
--- a/Logic/Osm/OsmConnection.ts
+++ b/Logic/Osm/OsmConnection.ts
@@ -65,7 +65,14 @@ export class OsmConnection {
this.userDetails = new UIEventSource(new UserDetails(), "userDetails");
this.userDetails.data.dryRun = dryRun;
- this.isLoggedIn = this.userDetails.map(user => user.loggedIn)
+ const self =this;
+ this.isLoggedIn = this.userDetails.map(user => user.loggedIn).addCallback(isLoggedIn => {
+ if(self.userDetails.data.loggedIn == false){
+ // We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do
+ // This means someone attempted to toggle this; so we attempt to login!
+ self.AttemptLogin()
+ }
+ });
this._dryRun = dryRun;
this.updateAuthObject();
@@ -217,14 +224,15 @@ export class OsmConnection {
});
}
- private CheckForMessagesContinuously() {
- const self = this;
- window.setTimeout(() => {
- if (self.userDetails.data.loggedIn) {
- console.log("Checking for messages")
- this.AttemptLogin();
- }
- }, 5 * 60 * 1000);
+ private CheckForMessagesContinuously(){
+ const self =this;
+ UIEventSource.Chronic(5 * 60 * 1000).addCallback(_ => {
+ if (self.isLoggedIn .data) {
+ console.log("Checking for messages")
+ self.AttemptLogin();
+ }
+ });
+
}
diff --git a/Models/Constants.ts b/Models/Constants.ts
index a350603c75..e7ec48ba98 100644
--- a/Models/Constants.ts
+++ b/Models/Constants.ts
@@ -6,7 +6,6 @@ export default class Constants {
// The user journey states thresholds when a new feature gets unlocked
public static userJourney = {
- addNewPointsUnlock: 0,
moreScreenUnlock: 1,
personalLayoutUnlock: 15,
historyLinkVisible: 20,
diff --git a/UI/Base/FixedUiElement.ts b/UI/Base/FixedUiElement.ts
index c89927f3f0..a65b80e6f1 100644
--- a/UI/Base/FixedUiElement.ts
+++ b/UI/Base/FixedUiElement.ts
@@ -1,4 +1,3 @@
-import {UIElement} from "../UIElement";
import BaseUIElement from "../BaseUIElement";
export class FixedUiElement extends BaseUIElement {
diff --git a/UI/Base/List.ts b/UI/Base/List.ts
new file mode 100644
index 0000000000..d7b45a399a
--- /dev/null
+++ b/UI/Base/List.ts
@@ -0,0 +1,34 @@
+import {Utils} from "../../Utils";
+import BaseUIElement from "../BaseUIElement";
+import Translations from "../i18n/Translations";
+
+export default class List extends BaseUIElement {
+ private readonly uiElements: BaseUIElement[];
+ private readonly _ordered: boolean;
+
+ constructor(uiElements: (string | BaseUIElement)[], ordered = false) {
+ super();
+ this._ordered = ordered;
+ this.uiElements = Utils.NoNull(uiElements)
+ .map(Translations.W);
+ }
+
+ protected InnerConstructElement(): HTMLElement {
+ const el = document.createElement(this._ordered ? "ol" : "ul")
+
+ for (const subEl of this.uiElements) {
+ if(subEl === undefined || subEl === null){
+ continue;
+ }
+ const subHtml = subEl.ConstructElement()
+ if(subHtml !== undefined){
+ const item = document.createElement("li")
+ item.appendChild(subHtml)
+ el.appendChild(item)
+ }
+ }
+
+ return el;
+ }
+
+}
\ No newline at end of file
diff --git a/UI/Base/SubtleButton.ts b/UI/Base/SubtleButton.ts
index ec737381ac..c87e121583 100644
--- a/UI/Base/SubtleButton.ts
+++ b/UI/Base/SubtleButton.ts
@@ -35,8 +35,8 @@ export class SubtleButton extends UIElement {
if (linkTo == undefined) {
return new Combine([
image,
- message,
- ]);
+ message?.SetClass("blcok ml-4 overflow-ellipsis"),
+ ]).SetClass("flex group");
}
diff --git a/UI/Base/VariableUIElement.ts b/UI/Base/VariableUIElement.ts
index ca38f64d91..8d82858733 100644
--- a/UI/Base/VariableUIElement.ts
+++ b/UI/Base/VariableUIElement.ts
@@ -24,10 +24,17 @@ export class VariableUiElement extends BaseUIElement {
el.innerHTML = contents
} else if (contents instanceof Array) {
for (const content of contents) {
- el.appendChild(content.ConstructElement())
+ const c = content.ConstructElement();
+ if (c !== undefined && c !== null) {
+ el.appendChild(c)
+ }
+
+ }
+ } else {
+ const c = contents.ConstructElement();
+ if (c !== undefined && c !== null) {
+ el.appendChild(c)
}
- }else{
- el.appendChild(contents.ConstructElement())
}
})
}
diff --git a/UI/BaseUIElement.ts b/UI/BaseUIElement.ts
index 456f9139c1..840814530e 100644
--- a/UI/BaseUIElement.ts
+++ b/UI/BaseUIElement.ts
@@ -104,6 +104,9 @@ export default abstract class BaseUIElement {
return this._constructedHtmlElement
}
+ if(this.InnerConstructElement === undefined){
+ throw "ERROR! This is not a correct baseUIElement: "+this.constructor.name
+ }
const el = this.InnerConstructElement();
diff --git a/UI/BigComponents/FullWelcomePaneWithTabs.ts b/UI/BigComponents/FullWelcomePaneWithTabs.ts
index 754d82b84f..63f513488e 100644
--- a/UI/BigComponents/FullWelcomePaneWithTabs.ts
+++ b/UI/BigComponents/FullWelcomePaneWithTabs.ts
@@ -65,8 +65,8 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
);
return new Toggle(
- new TabbedComponent(tabs, State.state.welcomeMessageOpenedTab),
new TabbedComponent(tabsWithAboutMc, State.state.welcomeMessageOpenedTab),
+ new TabbedComponent(tabs, State.state.welcomeMessageOpenedTab),
userDetails.map(userdetails =>
userdetails.csCount < Constants.userJourney.mapCompleteHelpUnlock)
)
diff --git a/UI/BigComponents/LayerSelection.ts b/UI/BigComponents/LayerSelection.ts
index 08a8f68a56..7810c7b10f 100644
--- a/UI/BigComponents/LayerSelection.ts
+++ b/UI/BigComponents/LayerSelection.ts
@@ -56,7 +56,7 @@ export default class LayerSelection extends Combine {
.SetStyle(styleWhole),
new Combine([new Combine([iconUnselected, "", name, ""]).SetStyle(style), zoomStatus])
.SetStyle(styleWhole),
- layer.isDisplayed)
+ layer.isDisplayed).ToggleOnClick()
.SetStyle("margin:0.3em;")
);
}
diff --git a/UI/BigComponents/PersonalLayersPanel.ts b/UI/BigComponents/PersonalLayersPanel.ts
index d6a27e49d4..2bf2f800e1 100644
--- a/UI/BigComponents/PersonalLayersPanel.ts
+++ b/UI/BigComponents/PersonalLayersPanel.ts
@@ -91,7 +91,7 @@ export default class PersonalLayersPanel extends UIElement {
""
])),
controls[layer.id] ?? (favs.indexOf(layer.id) >= 0)
- );
+ ).ToggleOnClick();
cb.SetClass("custom-layer-checkbox");
controls[layer.id] = cb.isEnabled;
diff --git a/UI/BigComponents/ShareScreen.ts b/UI/BigComponents/ShareScreen.ts
index 70a9ab3c54..7f32802f53 100644
--- a/UI/BigComponents/ShareScreen.ts
+++ b/UI/BigComponents/ShareScreen.ts
@@ -36,7 +36,7 @@ export default class ShareScreen extends Combine {
new Combine([check(), tr.fsIncludeCurrentLocation.Clone()]),
new Combine([nocheck(), tr.fsIncludeCurrentLocation.Clone()]),
new UIEventSource(true)
- )
+ ).ToggleOnClick()
optionCheckboxes.push(includeLocation);
const currentLocation = State.state?.locationControl;
@@ -71,7 +71,7 @@ export default class ShareScreen extends Combine {
new Combine([check(), currentBackground]),
new Combine([nocheck(), currentBackground]),
new UIEventSource(true)
- )
+ ).ToggleOnClick()
optionCheckboxes.push(includeCurrentBackground);
optionParts.push(includeCurrentBackground.isEnabled.map((includeBG) => {
if (includeBG) {
@@ -86,7 +86,7 @@ export default class ShareScreen extends Combine {
new Combine([check(), tr.fsIncludeCurrentLayers.Clone()]),
new Combine([nocheck(), tr.fsIncludeCurrentLayers.Clone()]),
new UIEventSource(true)
- )
+ ).ToggleOnClick()
optionCheckboxes.push(includeLayerChoices);
optionParts.push(includeLayerChoices.isEnabled.map((includeLayerSelection) => {
@@ -116,7 +116,7 @@ export default class ShareScreen extends Combine {
new Combine([check(), Translations.W(swtch.human.Clone())]),
new Combine([nocheck(), Translations.W(swtch.human.Clone())]),
new UIEventSource(!swtch.reverse)
- );
+ ).ToggleOnClick();
optionCheckboxes.push(checkbox);
optionParts.push(checkbox.isEnabled.map((isEn) => {
if (isEn) {
diff --git a/UI/BigComponents/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts
index c6248681e3..78730096f5 100644
--- a/UI/BigComponents/SimpleAddUI.ts
+++ b/UI/BigComponents/SimpleAddUI.ts
@@ -1,9 +1,7 @@
/**
* Asks to add a feature at the last clicked location, at least if zoom is sufficient
*/
-import Locale from "../i18n/Locale";
import {UIEventSource} from "../../Logic/UIEventSource";
-import {UIElement} from "../UIElement";
import Svg from "../../Svg";
import {SubtleButton} from "../Base/SubtleButton";
import State from "../../State";
@@ -14,118 +12,163 @@ import LayerConfig from "../../Customizations/JSON/LayerConfig";
import {Tag} from "../../Logic/Tags/Tag";
import {TagUtils} from "../../Logic/Tags/TagUtils";
import BaseUIElement from "../BaseUIElement";
+import {VariableUiElement} from "../Base/VariableUIElement";
+import Toggle from "../Input/Toggle";
+import UserDetails from "../../Logic/Osm/OsmConnection";
+import {Translation} from "../i18n/Translation";
-export default class SimpleAddUI extends UIElement {
- private readonly _loginButton: BaseUIElement;
+/*
+* The SimpleAddUI is a single panel, which can have multiple states:
+* - A list of presets which can be added by the user
+* - A 'confirm-selection' button (or alternatively: please enable the layer)
+* - A 'something is wrong - please soom in further'
+* - A 'read your unread messages before adding a point'
+ */
- private readonly _confirmPreset: UIEventSource<{
- description: string | BaseUIElement,
- name: string | BaseUIElement,
- icon: BaseUIElement,
- tags: Tag[],
- layerToAddTo: {
- layerDef: LayerConfig,
- isDisplayed: UIEventSource
- }
- }>
- = new UIEventSource(undefined);
+interface PresetInfo {
+ description: string | Translation,
+ name: string | BaseUIElement,
+ icon: BaseUIElement,
+ tags: Tag[],
+ layerToAddTo: {
+ layerDef: LayerConfig,
+ isDisplayed: UIEventSource
+ }
+}
- private _component:BaseUIElement;
-
- private readonly openLayerControl: BaseUIElement;
- private readonly cancelButton: BaseUIElement;
- private readonly goToInboxButton: BaseUIElement = new SubtleButton(Svg.envelope_ui(),
- Translations.t.general.goToInbox, {url: "https://www.openstreetmap.org/messages/inbox", newTab: false});
+export default class SimpleAddUI extends Toggle {
constructor(isShown: UIEventSource) {
- super(State.state.locationControl.map(loc => loc.zoom));
- const self = this;
- this.ListenTo(Locale.language);
- this.ListenTo(State.state.osmConnection.userDetails);
- this.ListenTo(State.state.layerUpdater.runningQuery);
- this.ListenTo(this._confirmPreset);
- this.ListenTo(State.state.locationControl);
- State.state.filteredLayers.data?.map(layer => {
- self.ListenTo(layer.isDisplayed)
- })
- this._loginButton = Translations.t.general.add.pleaseLogin.Clone().onClick(() => State.state.osmConnection.AttemptLogin());
+
+ const loginButton = Translations.t.general.add.pleaseLogin.Clone().onClick(State.state.osmConnection.AttemptLogin);
+ const readYourMessages = new Combine([
+ Translations.t.general.readYourMessages.Clone().SetClass("alert"),
+ new SubtleButton(Svg.envelope_ui(),
+ Translations.t.general.goToInbox, {url: "https://www.openstreetmap.org/messages/inbox", newTab: false})
+ ]);
+
+
+
+ const selectedPreset = new UIEventSource(undefined);
+ isShown.addCallback(_ => selectedPreset.setData(undefined)) // Clear preset selection when the UI is closed/opened
+
+ function createNewPoint(tags: any[]){
+ const loc = State.state.LastClickLocation.data;
+ let feature = State.state.changes.createElement(tags, loc.lat, loc.lon);
+ State.state.selectedElement.setData(feature);
+ }
+
+ const presetsOverview = SimpleAddUI.CreateAllPresetsPanel(selectedPreset)
+
+ const addUi = new VariableUiElement(
+ selectedPreset.map(preset => {
+ if (preset === undefined) {
+ return presetsOverview
+ }
+ return SimpleAddUI.CreateConfirmButton(preset,
+ tags => {
+ createNewPoint(tags)
+ selectedPreset.setData(undefined)
+ }, () => {
+ selectedPreset.setData(undefined)
+ })
+ }
+ ))
+
+
+ super(
+ new Toggle(
+ new Toggle(
+ new Toggle(
+ Translations.t.general.add.stillLoading.Clone().SetClass("alert"),
+ addUi,
+ State.state.layerUpdater.runningQuery
+ ),
+ Translations.t.general.add.zoomInFurther.Clone().SetClass("alert") ,
+ State.state.locationControl.map(loc => loc.zoom >= Constants.userJourney.minZoomLevelToAddNewPoints)
+ ),
+ readYourMessages,
+ State.state.osmConnection.userDetails.map((userdetails: UserDetails) =>
+ userdetails.csCount >= Constants.userJourney.addNewPointWithUnreadMessagesUnlock ||
+ userdetails.unreadMessages == 0)
+ ),
+ loginButton,
+ State.state.osmConnection.isLoggedIn
+ )
+
this.SetStyle("font-size:large");
- this.cancelButton = new SubtleButton(Svg.close_ui(),
- Translations.t.general.cancel
- ).onClick(() => {
- self._confirmPreset.setData(undefined);
- })
+ }
- this.openLayerControl = new SubtleButton(Svg.layers_ui(),
- Translations.t.general.add.openLayerControl
- ).onClick(() => {
- State.state.layerControlIsOpened.setData(true);
- })
+
+
+ private static CreateConfirmButton(preset: PresetInfo,
+ confirm: (tags: any[]) => void,
+ cancel: () => void): BaseUIElement {
+
+
+ const confirmButton = new SubtleButton(preset.icon,
+ new Combine([
+ Translations.t.general.add.addNew.Subs({category: preset.name}),
+ Translations.t.general.add.warnVisibleForEveryone.Clone().SetClass("alert")
+ ]).SetClass("flex flex-col")
+ ).SetClass("font-bold break-words")
+ .onClick(() => confirm(preset.tags));
+
+
+ const openLayerControl =
+ new SubtleButton(
+ Svg.layers_ui(),
+ new Combine([
+ Translations.t.general.add.layerNotEnabled
+ .Subs({layer: preset.layerToAddTo.layerDef.name})
+ .SetClass("alert"),
+ Translations.t.general.add.openLayerControl
+ ])
+ )
+
+ .onClick(() => State.state.layerControlIsOpened.setData(true))
- // IS shown is the state of the dialog - we reset the choice if the dialog dissappears
- isShown.addCallback(isShown =>
- {
- if(!isShown){
- self._confirmPreset.setData(undefined)
- }
- })
- // If the click location changes, we reset the dialog as well
- State.state.LastClickLocation.addCallback(() => {
- self._confirmPreset.setData(undefined)
- })
- this._component = this.CreateContent();
- }
+ const openLayerOrConfirm = new Toggle(
+ confirmButton,
+ openLayerControl,
+ preset.layerToAddTo.isDisplayed
+ )
+ const tagInfo = SimpleAddUI.CreateTagInfoFor(preset);
- InnerRender(): BaseUIElement {
- return this._component;
+ const cancelButton = new SubtleButton(Svg.close_ui(),
+ Translations.t.general.cancel
+ ).onClick(cancel )
+
+ return new Combine([
+ Translations.t.general.add.confirmIntro.Subs({title: preset.name}),
+ State.state.osmConnection.userDetails.data.dryRun ?
+ Translations.t.general.testing.Clone().SetClass("alert") : undefined ,
+ openLayerOrConfirm,
+ cancelButton,
+ preset.description,
+ tagInfo
+
+ ]).SetClass("flex flex-col")
}
- private CreatePresetsPanel(): BaseUIElement {
- const userDetails = State.state.osmConnection.userDetails;
- if (userDetails === undefined) {
- return undefined;
- }
+ private static CreateTagInfoFor(preset: PresetInfo, optionallyLinkToWiki = true) {
+ const csCount = State.state.osmConnection.userDetails.data.csCount;
+ return new Toggle(
+ Translations.t.general.presetInfo.Subs({
+ tags: preset.tags.map(t => t.asHumanString(optionallyLinkToWiki && csCount > Constants.userJourney.tagsVisibleAndWikiLinked, true)).join("&"),
- if (!userDetails.data.loggedIn) {
- return this._loginButton;
- }
+ }),
- if (userDetails.data.unreadMessages > 0 && userDetails.data.csCount < Constants.userJourney.addNewPointWithUnreadMessagesUnlock) {
- return new Combine([
- Translations.t.general.readYourMessages.Clone().SetClass("alert"),
- this.goToInboxButton
- ]);
-
- }
-
- if (userDetails.data.csCount < Constants.userJourney.addNewPointsUnlock) {
- return new Combine(["",
- Translations.t.general.fewChangesBefore,
- " "]);
- }
-
- if (State.state.locationControl.data.zoom < Constants.userJourney.minZoomLevelToAddNewPoints) {
- return Translations.t.general.add.zoomInFurther.SetClass("alert")
- }
-
- if (State.state.layerUpdater.runningQuery.data) {
- return Translations.t.general.add.stillLoading
- }
-
- const presetButtons = this.CreatePresetButtons()
- return new Combine(presetButtons)
+ undefined,
+ State.state.osmConnection.userDetails.map(userdetails => userdetails.csCount >= Constants.userJourney.tagsVisibleAt)
+ );
}
-
- private CreateContent(): BaseUIElement {
- const confirmPanel = this.CreateConfirmPanel();
- if (confirmPanel !== undefined) {
- return confirmPanel;
- }
-
+ private static CreateAllPresetsPanel(selectedPreset: UIEventSource): BaseUIElement {
+ const presetButtons = SimpleAddUI.CreatePresetButtons(selectedPreset)
let intro: BaseUIElement = Translations.t.general.add.intro;
let testMode: BaseUIElement = undefined;
@@ -133,113 +176,58 @@ export default class SimpleAddUI extends UIElement {
testMode = Translations.t.general.testing.Clone().SetClass("alert")
}
- let presets = this.CreatePresetsPanel();
- return new Combine([intro, testMode, presets])
-
+ return new Combine([intro, testMode, presetButtons]).SetClass("flex flex-col")
}
- private CreateConfirmPanel(): BaseUIElement {
- const preset = this._confirmPreset.data;
- if (preset === undefined) {
- return undefined;
- }
+ private static CreatePresetSelectButton(preset: PresetInfo){
- const confirmButton = new SubtleButton(preset.icon,
+ const tagInfo =SimpleAddUI.CreateTagInfoFor(preset, false);
+ return new SubtleButton(
+ preset.icon,
new Combine([
- "",
- Translations.t.general.add.confirmButton.Subs({category: preset.name}),
- " "])).SetClass("break-words");
- confirmButton.onClick(
- this.CreatePoint(preset.tags)
- );
-
- if (!this._confirmPreset.data.layerToAddTo.isDisplayed.data) {
- return new Combine([
- Translations.t.general.add.layerNotEnabled.Subs({layer: this._confirmPreset.data.layerToAddTo.layerDef.name})
- .SetClass("alert"),
- this.openLayerControl,
-
- this.cancelButton
- ]);
- }
-
- let tagInfo = "";
- const csCount = State.state.osmConnection.userDetails.data.csCount;
- if (csCount > Constants.userJourney.tagsVisibleAt) {
- tagInfo = this._confirmPreset.data.tags.map(t => t.asHumanString(csCount > Constants.userJourney.tagsVisibleAndWikiLinked, true)).join("&");
- tagInfo = ` More information about the preset: ${tagInfo}`
- }
-
- return new Combine([
- Translations.t.general.add.confirmIntro.Subs({title: this._confirmPreset.data.name}),
- State.state.osmConnection.userDetails.data.dryRun ? "TESTING - changes won't be saved " : "",
- confirmButton,
- this.cancelButton,
- preset.description,
- tagInfo
-
- ])
-
+ Translations.t.general.add.addNew.Subs({
+ category: preset.name
+ }).SetClass("font-bold"),
+ Translations.WT(preset.description)?.FirstSentence(),
+ tagInfo?.SetClass("subtle")
+ ]).SetClass("flex flex-col")
+ )
}
-
- private CreatePresetButtons() {
+
+/*
+* Generates the list with all the buttons.*/
+ private static CreatePresetButtons(selectedPreset: UIEventSource): BaseUIElement {
const allButtons = [];
- const self = this;
for (const layer of State.state.filteredLayers.data) {
+
+ if(layer.isDisplayed.data === false && State.state.featureSwitchLayers){
+ continue;
+ }
+
const presets = layer.layerDef.presets;
for (const preset of presets) {
- const tags = TagUtils.KVtoProperties(preset.tags ?? []);
- let icon: BaseUIElement = layer.layerDef.GenerateLeafletStyle(new UIEventSource(tags), false).icon.html.SetClass("simple-add-ui-icon");
- const csCount = State.state.osmConnection.userDetails.data.csCount;
- let tagInfo = undefined;
- if (csCount > Constants.userJourney.tagsVisibleAt) {
- const presets = preset.tags.map(t => new Combine([t.asHumanString(false, true), " "]).SetClass("subtle break-words"))
- tagInfo = new Combine(presets)
+ const tags = TagUtils.KVtoProperties(preset.tags ?? []);
+ let icon: BaseUIElement = layer.layerDef.GenerateLeafletStyle(new UIEventSource(tags), false).icon.html
+ .SetClass("w-12 h-12 block relative");
+ const presetInfo: PresetInfo = {
+ tags: preset.tags,
+ layerToAddTo: layer,
+ name: preset.title,
+ description: preset.description,
+ icon: icon
}
- const button: UIElement =
- new SubtleButton(
- icon,
- new Combine([
- "",
- preset.title,
- " ",
- preset.description !== undefined ? new Combine([" ", preset.description.FirstSentence()]) : "",
- " ",
- tagInfo
- ])
- ).onClick(
- () => {
- self._confirmPreset.setData({
- tags: preset.tags,
- layerToAddTo: layer,
- name: preset.title,
- description: preset.description,
- icon: icon
- });
- self.Update();
- }
- )
+
+ const button = SimpleAddUI.CreatePresetSelectButton(presetInfo);
+ button.onClick(() => {
+ selectedPreset.setData(presetInfo)
+ })
allButtons.push(button);
}
}
- return allButtons;
+ return new Combine(allButtons).SetClass("flex flex-col");
}
- private CreatePoint(tags: Tag[]) {
- return () => {
- console.log("Create Point Triggered")
- const loc = State.state.LastClickLocation.data;
- let feature = State.state.changes.createElement(tags, loc.lat, loc.lon);
- State.state.selectedElement.setData(feature);
- this._confirmPreset.setData(undefined);
- }
- }
-
- public OnClose(){
- console.log("On close triggered")
- this._confirmPreset.setData(undefined)
- }
}
\ No newline at end of file
diff --git a/UI/Image/DeleteImage.ts b/UI/Image/DeleteImage.ts
index df6de5e2ea..656b9160d2 100644
--- a/UI/Image/DeleteImage.ts
+++ b/UI/Image/DeleteImage.ts
@@ -37,7 +37,7 @@ export default class DeleteImage extends UIElement {
cancelButton
]).SetClass("flex flex-col background-black"),
Svg.delete_icon_svg().SetStyle("width: 2em; height: 2em; display:block;")
- )
+ ).ToggleOnClick()
}
diff --git a/UI/Input/FixedInputElement.ts b/UI/Input/FixedInputElement.ts
index bc2424939d..de0f2b593e 100644
--- a/UI/Input/FixedInputElement.ts
+++ b/UI/Input/FixedInputElement.ts
@@ -1,44 +1,46 @@
import {InputElement} from "./InputElement";
-import {UIElement} from "../UIElement";
-import {FixedUiElement} from "../Base/FixedUiElement";
import {UIEventSource} from "../../Logic/UIEventSource";
+import Translations from "../i18n/Translations";
+import BaseUIElement from "../BaseUIElement";
export class FixedInputElement extends InputElement {
- private readonly rendering: UIElement;
private readonly value: UIEventSource;
public readonly IsSelected : UIEventSource = new UIEventSource(false);
private readonly _comparator: (t0: T, t1: T) => boolean;
- constructor(rendering: UIElement | string,
+ private readonly _el : HTMLElement;
+
+ constructor(rendering: BaseUIElement | string,
value: T,
comparator: ((t0: T, t1: T) => boolean ) = undefined) {
- super(undefined);
+ super();
this._comparator = comparator ?? ((t0, t1) => t0 == t1);
this.value = new UIEventSource(value);
- this.rendering = typeof (rendering) === 'string' ? new FixedUiElement(rendering) : rendering;
- const self = this;
+
+ const selected = this.IsSelected;
+ this._el = document.createElement("span")
+ this._el.addEventListener("mouseout", () => selected.setData(false))
+ const e = Translations.W(rendering)?.ConstructElement()
+ if(e){
+ this._el.appendChild( e)
+ }
+
this.onClick(() => {
- self.IsSelected.setData(true)
+ selected.setData(true)
})
}
+ protected InnerConstructElement(): HTMLElement {
+ return undefined;
+ }
+
GetValue(): UIEventSource {
return this.value;
}
- InnerRender(): string {
- return this.rendering.Render();
- }
IsValid(t: T): boolean {
return this._comparator(t, this.value.data);
}
- protected InnerUpdate(htmlElement: HTMLElement) {
- super.InnerUpdate(htmlElement);
- const self = this;
- htmlElement.addEventListener("mouseout", () => self.IsSelected.setData(false))
-
- }
-
}
\ No newline at end of file
diff --git a/UI/Input/RadioButton.ts b/UI/Input/RadioButton.ts
index 3ead32abf5..ac0828f75b 100644
--- a/UI/Input/RadioButton.ts
+++ b/UI/Input/RadioButton.ts
@@ -3,24 +3,19 @@ import {UIEventSource} from "../../Logic/UIEventSource";
import {Utils} from "../../Utils";
export class RadioButton extends InputElement {
+ private static _nextId = 0;
IsSelected: UIEventSource = new UIEventSource(false);
-
- private readonly _selectedElementIndex: UIEventSource
- = new UIEventSource(null);
-
private readonly value: UIEventSource;
- private readonly _elements: InputElement[]
- private readonly _selectFirstAsDefault: boolean;
+ private _elements: InputElement[];
+ private readonly _element: HTMLElement;
constructor(elements: InputElement[],
selectFirstAsDefault = true) {
- super(undefined);
- this._elements = Utils.NoNull(elements);
- this._selectFirstAsDefault = selectFirstAsDefault;
- const self = this;
-
- this.value =
- UIEventSource.flatten(this._selectedElementIndex.map(
+ super()
+ elements = Utils.NoNull(elements);
+ const selectedElementIndex: UIEventSource = new UIEventSource(null);
+ const value =
+ UIEventSource.flatten(selectedElementIndex.map(
(selectedIndex) => {
if (selectedIndex !== undefined && selectedIndex !== null) {
return elements[selectedIndex].GetValue()
@@ -28,26 +23,63 @@ export class RadioButton extends InputElement {
}
), elements.map(e => e?.GetValue()));
- this.value.addCallback((t) => {
- self?.ShowValue(t);
- })
+
+ /*
+ value.addCallback((t) => {
+ self?.ShowValue(t);
+ })*/
for (let i = 0; i < elements.length; i++) {
// If an element is clicked, the radio button corresponding with it should be selected as well
elements[i]?.onClick(() => {
- self._selectedElementIndex.setData(i);
+ selectedElementIndex.setData(i);
});
elements[i].IsSelected.addCallback(isSelected => {
if (isSelected) {
- self._selectedElementIndex.setData(i);
+ selectedElementIndex.setData(i);
}
})
elements[i].GetValue().addCallback(() => {
- self._selectedElementIndex.setData(i);
+ selectedElementIndex.setData(i);
})
}
- this.dumbMode = false;
+
+
+ const groupId = "radiogroup" + RadioButton._nextId
+ RadioButton._nextId++
+
+ const form = document.createElement("form")
+ this._element = form;
+ for (let i1 = 0; i1 < elements.length; i1++) {
+ let element = elements[i1];
+ const labelHtml = element.ConstructElement();
+ if (labelHtml === undefined) {
+ continue;
+ }
+
+ const input = document.createElement("input")
+ input.id = "radio" + groupId + "-" + i1;
+ input.name = groupId;
+ input.type = "radio"
+
+
+ const label = document.createElement("label")
+ label.appendChild(labelHtml)
+ label.htmlFor = input.id;
+ input.appendChild(label)
+
+ form.appendChild(input)
+
+ form.addEventListener("change", () => {
+ // TODO FIXME
+ }
+ );
+ }
+
+
+ this.value = value;
+ this._elements = elements;
}
@@ -65,25 +97,11 @@ export class RadioButton extends InputElement {
}
- private IdFor(i) {
- return 'radio-' + this.id + '-' + i;
- }
-
- InnerRender(): string {
- let body = "";
- for (let i = 0; i < this._elements.length; i++){
- const el = this._elements[i];
- const htmlElement =
- `` +
- ` ` +
- el.Render() +
- ` `;
- body += htmlElement;
- }
-
- return ``;
+ protected InnerConstructElement(): HTMLElement {
+ return this._element;
}
+ /*
public ShowValue(t: T): boolean {
if (t === undefined) {
return false;
@@ -104,48 +122,7 @@ export class RadioButton extends InputElement {
}
}
- }
-
- InnerUpdate(htmlElement: HTMLElement) {
- const self = this;
-
- function checkButtons() {
- for (let i = 0; i < self._elements.length; i++) {
- const el = document.getElementById(self.IdFor(i));
- // @ts-ignore
- if (el.checked) {
- self._selectedElementIndex.setData(i);
- }
- }
- }
-
- const el = document.getElementById(this.id);
- el.addEventListener("change",
- function () {
- checkButtons();
- }
- );
- if (this._selectedElementIndex.data !== null) {
- const el = document.getElementById(this.IdFor(this._selectedElementIndex.data));
- if (el) {
- // @ts-ignore
- el.checked = true;
- checkButtons();
- }
- } else if (this._selectFirstAsDefault) {
- this.ShowValue(this.value.data);
- if (this._selectedElementIndex.data === null || this._selectedElementIndex.data === undefined) {
- const el = document.getElementById(this.IdFor(0));
- if (el) {
- // @ts-ignore
- el.checked = true;
- checkButtons();
- }
- }
- }
-
-
- };
+ }*/
}
\ No newline at end of file
diff --git a/UI/Input/Toggle.ts b/UI/Input/Toggle.ts
index f3a4eb74f7..ca9fd6fbab 100644
--- a/UI/Input/Toggle.ts
+++ b/UI/Input/Toggle.ts
@@ -15,9 +15,13 @@ export default class Toggle extends VariableUiElement{
isEnabled.map(isEnabled => isEnabled ? showEnabled : showDisabled)
);
this.isEnabled = isEnabled
+ }
+
+ public ToggleOnClick(): Toggle{
+ const self = this;
this.onClick(() => {
- isEnabled.setData(!isEnabled.data);
+ self. isEnabled.setData(!self.isEnabled.data);
})
-
+ return this;
}
}
\ No newline at end of file
diff --git a/UI/Popup/EditableTagRendering.ts b/UI/Popup/EditableTagRendering.ts
index 0019910a5a..db89a4fd5b 100644
--- a/UI/Popup/EditableTagRendering.ts
+++ b/UI/Popup/EditableTagRendering.ts
@@ -1,4 +1,3 @@
-import {UIElement} from "../UIElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
import TagRenderingQuestion from "./TagRenderingQuestion";
@@ -7,80 +6,65 @@ import Combine from "../Base/Combine";
import TagRenderingAnswer from "./TagRenderingAnswer";
import State from "../../State";
import Svg from "../../Svg";
+import Toggle from "../Input/Toggle";
+import BaseUIElement from "../BaseUIElement";
-export default class EditableTagRendering extends UIElement {
- private readonly _tags: UIEventSource;
- private readonly _configuration: TagRenderingConfig;
-
- private _editMode: UIEventSource = new UIEventSource(false);
- private _editButton: UIElement;
-
- private _question: UIElement;
- private _answer: UIElement;
+export default class EditableTagRendering extends Toggle {
constructor(tags: UIEventSource,
configuration: TagRenderingConfig) {
- super(tags);
- this._tags = tags;
- this._configuration = configuration;
- this.ListenTo(this._editMode);
- this.ListenTo(State.state?.osmConnection?.userDetails)
+ const editMode = new UIEventSource(false);
- this._answer = new TagRenderingAnswer(tags, configuration);
- this._answer.SetClass("w-full")
- this._question = this.GenerateQuestion();
- this.dumbMode = false;
+ const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration)
+ let rendering = answer;
- if (this._configuration.question !== undefined) {
- if (State.state?.featureSwitchUserbadge?.data) {
- // 2.3em total width
- const self = this;
- this._editButton =
- Svg.pencil_svg().SetClass("edit-button")
- .onClick(() => {
- self._editMode.setData(true);
- });
- }
- }
- }
+ if (configuration.question !== undefined && State.state?.featureSwitchUserbadge?.data) {
+ // We have a question and editing is enabled
+ const editButton =
+ new Combine([Svg.pencil_ui()]).SetClass("block relative h-10 w-10 p-2 float-right").SetStyle("border: 1px solid black; border-radius: 0.7em")
+ .onClick(() => {
+ editMode.setData(true);
+ });
- InnerRender(): string {
- if (!this._configuration?.condition?.matchesProperties(this._tags.data)) {
- return "";
- }
- if (this._editMode.data) {
- return this._question.Render();
- }
- if(!this._configuration.IsKnown(this._tags.data)){
- // Even though it is not known, we hide the question here
- // It is the questionbox's task to show the question in edit mode
- return "";
- }
- return new Combine([this._answer,
- (State.state?.osmConnection?.userDetails?.data?.loggedIn ?? true) ? this._editButton : undefined
- ]).SetClass("flex w-full break-word justify-between text-default landscape:w-1/2 landscape:p-2 pb-2 border-b border-gray-300 mb-2")
- .Render();
- }
+ const answerWithEditButton = new Combine([answer,
+ new Toggle(editButton, undefined, State.state.osmConnection.isLoggedIn)]).SetClass("w-full")
+
- private GenerateQuestion() {
- const self = this;
- if (this._configuration.question !== undefined) {
- // And at last, set up the skip button
const cancelbutton =
Translations.t.general.cancel.Clone()
.SetClass("btn btn-secondary mr-3")
.onClick(() => {
- self._editMode.setData(false)
+ editMode.setData(false)
});
- return new TagRenderingQuestion(this._tags, this._configuration,
+ const question = new TagRenderingQuestion(tags, configuration,
() => {
- self._editMode.setData(false)
+ editMode.setData(false)
},
cancelbutton)
+
+
+ rendering = new Toggle(
+ question,
+ answerWithEditButton,
+ editMode
+ )
}
+ answer.SetClass("flex w-full break-word justify-between text-default landscape:w-1/2 landscape:p-2 pb-2 border-b border-gray-300 mb-2")
+ rendering.SetClass("flex m-1 p-1 border-b border-gray-300 mb-2 pb-2")
+ // The tagrendering is hidden if:
+ // The answer is unknown. The questionbox will then show the question
+ // There is a condition hiding the answer
+ const renderingIsShown = tags.map(tags =>
+ !configuration.IsKnown(tags) &&
+ (configuration?.condition?.matchesProperties(tags) ?? true))
+ super(
+ rendering,
+ undefined,
+ renderingIsShown
+ )
}
}
\ No newline at end of file
diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts
index 14025b9054..64ffe59a2c 100644
--- a/UI/Popup/FeatureInfoBox.ts
+++ b/UI/Popup/FeatureInfoBox.ts
@@ -11,10 +11,11 @@ import ScrollableFullScreen from "../Base/ScrollableFullScreen";
import {Tag} from "../../Logic/Tags/Tag";
import Constants from "../../Models/Constants";
import SharedTagRenderings from "../../Customizations/SharedTagRenderings";
+import BaseUIElement from "../BaseUIElement";
export default class FeatureInfoBox extends ScrollableFullScreen {
- private constructor(
+ public constructor(
tags: UIEventSource,
layerConfig: LayerConfig
) {
@@ -28,12 +29,8 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
}
- static construct(tags: UIEventSource, layer: LayerConfig): FeatureInfoBox {
- return new FeatureInfoBox(tags, layer)
- }
-
private static GenerateTitleBar(tags: UIEventSource,
- layerConfig: LayerConfig): UIElement {
+ layerConfig: LayerConfig): BaseUIElement {
const title = new TagRenderingAnswer(tags, layerConfig.title ?? new TagRenderingConfig("POI", undefined))
.SetClass("break-words font-bold sm:p-0.5 md:p-1 sm:p-1.5 md:p-2");
const titleIcons = new Combine(
@@ -48,7 +45,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
}
private static GenerateContent(tags: UIEventSource,
- layerConfig: LayerConfig): UIElement {
+ layerConfig: LayerConfig): BaseUIElement {
let questionBox: UIElement = undefined;
if (State.state.featureSwitchUserbadge.data) {
diff --git a/UI/Popup/SaveButton.ts b/UI/Popup/SaveButton.ts
index 89d7aa6db8..415b30da56 100644
--- a/UI/Popup/SaveButton.ts
+++ b/UI/Popup/SaveButton.ts
@@ -1,17 +1,11 @@
import {UIEventSource} from "../../Logic/UIEventSource";
-import {UIElement} from "../UIElement";
import Translations from "../i18n/Translations";
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
-import BaseUIElement from "../BaseUIElement";
import Toggle from "../Input/Toggle";
-export class SaveButton extends UIElement {
-
-
- private readonly _element: BaseUIElement;
+export class SaveButton extends Toggle {
constructor(value: UIEventSource, osmConnection: OsmConnection) {
- super(value);
if (value === undefined) {
throw "No event source for savebutton, something is wrong"
}
@@ -31,7 +25,7 @@ export class SaveButton extends UIElement {
saveDisabled,
isSaveable
)
- this._element = new Toggle(
+ super(
save
, pleaseLogin,
osmConnection?.userDetails?.map(userDetails => userDetails.loggedIn) ?? new UIEventSource(false)
@@ -39,9 +33,4 @@ export class SaveButton extends UIElement {
}
- InnerRender(): BaseUIElement {
- return this._element
-
- }
-
}
\ No newline at end of file
diff --git a/UI/Popup/TagRenderingAnswer.ts b/UI/Popup/TagRenderingAnswer.ts
index 1b716208c7..8000361515 100644
--- a/UI/Popup/TagRenderingAnswer.ts
+++ b/UI/Popup/TagRenderingAnswer.ts
@@ -1,98 +1,43 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
-import {UIElement} from "../UIElement";
import {Utils} from "../../Utils";
-import Combine from "../Base/Combine";
import {SubstitutedTranslation} from "../SubstitutedTranslation";
-import {Translation} from "../i18n/Translation";
-import {TagUtils} from "../../Logic/Tags/TagUtils";
import BaseUIElement from "../BaseUIElement";
+import {VariableUiElement} from "../Base/VariableUIElement";
+import List from "../Base/List";
+import {FixedUiElement} from "../Base/FixedUiElement";
/***
* Displays the correct value for a known tagrendering
*/
-export default class TagRenderingAnswer extends UIElement {
- private readonly _tags: UIEventSource;
- private _configuration: TagRenderingConfig;
- private _content: BaseUIElement;
- private readonly _contentClass: string;
- private _contentStyle: string;
+export default class TagRenderingAnswer extends VariableUiElement {
- constructor(tags: UIEventSource, configuration: TagRenderingConfig, contentClasses: string = "", contentStyle: string = "") {
- super();
- this._tags = tags;
- this.ListenTo(tags)
- this._configuration = configuration;
- this._contentClass = contentClasses;
- this._contentStyle = contentStyle;
+ constructor(tagsSource: UIEventSource, configuration: TagRenderingConfig, contentClasses: string = "", contentStyle: string = "") {
if (configuration === undefined) {
throw "Trying to generate a tagRenderingAnswer without configuration..."
}
+ super(tagsSource.map(tags => {
+ if(tags === undefined){
+ return undefined;
+ }
+ const trs = Utils.NoNull(configuration.GetRenderValues(tags));
+ if(trs.length === 0){
+ return undefined;
+ }
+ trs.forEach(tr => console.log("Rendering ", tr))
+ const valuesToRender: BaseUIElement[] = trs.map(tr => new SubstitutedTranslation(tr, tagsSource))
+
+ if(valuesToRender.length === 1){
+ return valuesToRender[0];
+ }else if(valuesToRender.length > 1){
+ return new List(valuesToRender)
+ }
+ return undefined;
+ }).map(innerComponent => innerComponent?.SetClass(contentClasses)?.SetStyle(contentStyle))
+ )
+
this.SetClass("flex items-center flex-row text-lg link-underline")
this.SetStyle("word-wrap: anywhere;");
}
- InnerRender(): string | BaseUIElement{
- if (this._configuration.condition !== undefined) {
- if (!this._configuration.condition.matchesProperties(this._tags.data)) {
- return "";
- }
- }
-
- const tags = this._tags.data;
- if (tags === undefined) {
- return "";
- }
-
- // The render value doesn't work well with multi-answers (checkboxes), so we have to check for them manually
- if (this._configuration.multiAnswer) {
-
- let freeformKeyUsed = this._configuration.freeform?.key === undefined; // If it is undefined, it is "used" already, or at least we don't have to check for it anymore
- const applicableThens: Translation[] = Utils.NoNull(this._configuration.mappings?.map(mapping => {
- if (mapping.if === undefined) {
- return mapping.then;
- }
- if (TagUtils.MatchesMultiAnswer(mapping.if, tags)) {
- if(!freeformKeyUsed){
- if(mapping.if.usedKeys().indexOf(this._configuration.freeform.key) >= 0){
- freeformKeyUsed = true;
- }
- }
- return mapping.then;
- }
- return undefined;
- }) ?? [])
-
- if (!freeformKeyUsed
- && tags[this._configuration.freeform.key] !== undefined) {
- applicableThens.push(this._configuration.render)
- }
-
- const self = this
- const valuesToRender: UIElement[] = applicableThens.map(tr => SubstitutedTranslation.construct(tr, self._tags))
-
- if (valuesToRender.length >= 0) {
- if (valuesToRender.length === 1) {
- this._content = valuesToRender[0];
- } else {
- this._content = new Combine(["",
- ...valuesToRender.map(tr => new Combine(["", tr, " "])) ,
- " "
- ])
-
- }
- return this._content.SetClass(this._contentClass).SetStyle(this._contentStyle);
- }
- }
-
- const tr = this._configuration.GetRenderValue(tags);
- if (tr !== undefined) {
- this._content = SubstitutedTranslation.construct(tr, this._tags);
- return this._content.SetClass(this._contentClass).SetStyle(this._contentStyle);
- }
-
- return "";
-
- }
-
}
\ No newline at end of file
diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts
index cdf197c77d..292b530212 100644
--- a/UI/Popup/TagRenderingQuestion.ts
+++ b/UI/Popup/TagRenderingQuestion.ts
@@ -32,23 +32,23 @@ export default class TagRenderingQuestion extends UIElement {
private readonly _tags: UIEventSource;
private _configuration: TagRenderingConfig;
- private _saveButton: UIElement;
+ private _saveButton: BaseUIElement;
private _inputElement: InputElement;
- private _cancelButton: UIElement;
+ private _cancelButton: BaseUIElement;
private _appliedTags: BaseUIElement;
- private _question: UIElement;
+ private _question: BaseUIElement;
constructor(tags: UIEventSource,
configuration: TagRenderingConfig,
afterSave?: () => void,
- cancelButton?: UIElement
+ cancelButton?: BaseUIElement
) {
super(tags);
this._tags = tags;
this._configuration = configuration;
this._cancelButton = cancelButton;
- this._question = SubstitutedTranslation.construct(this._configuration.question, tags)
+ this._question = new SubstitutedTranslation(this._configuration.question, tags)
.SetClass("question-text");
if (configuration === undefined) {
throw "A question is needed for a question visualization"
@@ -242,7 +242,7 @@ export default class TagRenderingQuestion extends UIElement {
return undefined;
}
return new FixedInputElement(
- SubstitutedTranslation.construct(mapping.then, this._tags),
+ new SubstitutedTranslation(mapping.then, this._tags),
mapping.if,
(t0, t1) => t1.isEquivalent(t0));
}
diff --git a/UI/Reviews/ReviewForm.ts b/UI/Reviews/ReviewForm.ts
index ce2ac3423e..fb0ecc32f4 100644
--- a/UI/Reviews/ReviewForm.ts
+++ b/UI/Reviews/ReviewForm.ts
@@ -102,8 +102,8 @@ export default class ReviewForm extends InputElement {
.SetClass("review-form")
- return new Toggle(form, Translations.t.reviews.plz_login,
- this.userDetails.map(userdetails => userdetails.loggedIn))
+ return new Toggle(form, Translations.t.reviews.plz_login.Clone(),
+ this.userDetails.map(userdetails => userdetails.loggedIn)).ToggleOnClick()
.ConstructElement()
}
diff --git a/UI/ShowDataLayer.ts b/UI/ShowDataLayer.ts
index bb15b6dca7..70560ad7e8 100644
--- a/UI/ShowDataLayer.ts
+++ b/UI/ShowDataLayer.ts
@@ -87,10 +87,10 @@ export default class ShowDataLayer {
}
marker.openPopup();
- const popup = marker.getPopup();
+
const tags = State.state.allElements.getEventSourceById(selected.properties.id);
const layer: LayerConfig = this._layerDict[selected._matching_layer_id];
- const infoBox = FeatureInfoBox.construct(tags, layer);
+ const infoBox = new FeatureInfoBox(tags, layer);
infoBox.isShown.addCallback(isShown => {
if (!isShown) {
@@ -98,9 +98,8 @@ export default class ShowDataLayer {
}
});
- popup.setContent(infoBox.Render());
+ infoBox.AttachTo(`popup-${selected.properties.id}`)
infoBox.Activate();
- infoBox.Update();
})
}
@@ -156,11 +155,13 @@ export default class ShowDataLayer {
}, leafletLayer);
// By setting 50vh, leaflet will attempt to fit the entire screen and move the feature down
- popup.setContent("Rendering
");
+ popup.setContent(``);
leafletLayer.bindPopup(popup);
+
leafletLayer.on("popupopen", () => {
State.state.selectedElement.setData(feature)
+ // The feature info box is bound via the selected element callback, as there are multiple ways to open the popup (e.g. a trigger via the URL°
});
this._popups.set(feature, leafletLayer);
diff --git a/UI/SubstitutedTranslation.ts b/UI/SubstitutedTranslation.ts
index e63a7f8e3d..bd43e127f4 100644
--- a/UI/SubstitutedTranslation.ts
+++ b/UI/SubstitutedTranslation.ts
@@ -1,71 +1,37 @@
-import {UIElement} from "./UIElement";
import {UIEventSource} from "../Logic/UIEventSource";
import {Translation} from "./i18n/Translation";
import Locale from "./i18n/Locale";
-import Combine from "./Base/Combine";
import State from "../State";
import {FixedUiElement} from "./Base/FixedUiElement";
import SpecialVisualizations from "./SpecialVisualizations";
import BaseUIElement from "./BaseUIElement";
+import {Utils} from "../Utils";
+import {VariableUiElement} from "./Base/VariableUIElement";
+import Combine from "./Base/Combine";
-export class SubstitutedTranslation extends UIElement {
- private readonly tags: UIEventSource;
- private readonly translation: Translation;
- private content: BaseUIElement[];
+export class SubstitutedTranslation extends VariableUiElement {
- private constructor(
+ public constructor(
translation: Translation,
tags: UIEventSource) {
- super(tags);
- this.translation = translation;
- this.tags = tags;
- const self = this;
- tags.addCallbackAndRun(() => {
- self.content = self.CreateContent();
- self.Update();
- });
-
- Locale.language.addCallback(() => {
- self.content = self.CreateContent();
- self.Update();
- });
+ super(
+ tags.map(tags => {
+ const txt = Utils.SubstituteKeys(translation.txt, tags)
+ if (txt === undefined) {
+ return "no tags subs tr"
+ }
+ const contents = SubstitutedTranslation.EvaluateSpecialComponents(txt, tags)
+ console.log("Substr has contents", contents)
+ return new Combine(contents)
+ }, [Locale.language])
+ )
+
+
this.SetClass("w-full")
}
- public static construct(
- translation: Translation,
- tags: UIEventSource): SubstitutedTranslation {
- return new SubstitutedTranslation(translation, tags);
- }
- public static SubstituteKeys(txt: string, tags: any) {
- for (const key in tags) {
- if(!tags.hasOwnProperty(key)) {
- continue
- }
- txt = txt.replace(new RegExp("{" + key + "}", "g"), tags[key])
- }
- return txt;
- }
-
- InnerRender() {
- if (this.content.length == 1) {
- return this.content[0];
- }
- return new Combine(this.content);
- }
-
- private CreateContent(): BaseUIElement[] {
- let txt = this.translation?.txt;
- if (txt === undefined) {
- return []
- }
- const tags = this.tags.data;
- txt = SubstitutedTranslation.SubstituteKeys(txt, tags);
- return this.EvaluateSpecialComponents(txt);
- }
-
- private EvaluateSpecialComponents(template: string): BaseUIElement[] {
+ private static EvaluateSpecialComponents(template: string, tags: UIEventSource): BaseUIElement[] {
for (const knownSpecial of SpecialVisualizations.specialVisualizations) {
@@ -74,9 +40,9 @@ export class SubstitutedTranslation extends UIElement {
if (matched != null) {
// We found a special component that should be brought to live
- const partBefore = this.EvaluateSpecialComponents(matched[1]);
+ const partBefore = SubstitutedTranslation.EvaluateSpecialComponents(matched[1], tags);
const argument = matched[2].trim();
- const partAfter = this.EvaluateSpecialComponents(matched[3]);
+ const partAfter = SubstitutedTranslation.EvaluateSpecialComponents(matched[3], tags);
try {
const args = knownSpecial.args.map(arg => arg.defaultValue ?? "");
if (argument.length > 0) {
@@ -91,7 +57,13 @@ export class SubstitutedTranslation extends UIElement {
}
- const element = knownSpecial.constr(State.state, this.tags, args);
+ let element: BaseUIElement = new FixedUiElement(`Constructing ${knownSpecial}(${args.join(", ")})`)
+ try{
+ element = knownSpecial.constr(State.state, tags, args);
+ }catch(e){
+ element = new FixedUiElement(`Could not generate special renering for ${knownSpecial}(${args.join(", ")}) ${e}`).SetClass("alert")
+ }
+
return [...partBefore, element, ...partAfter]
} catch (e) {
console.error(e);
diff --git a/UI/UIElement.ts b/UI/UIElement.ts
index e228d70d3c..83b5f67526 100644
--- a/UI/UIElement.ts
+++ b/UI/UIElement.ts
@@ -21,7 +21,7 @@ export abstract class UIElement extends BaseUIElement{
if (source === undefined) {
return this;
}
- console.trace("Got a listenTo in ", this.constructor.name)
+ //console.trace("Got a listenTo in ", this.constructor.name)
const self = this;
source.addCallback(() => {
self.lastInnerRender = undefined;
diff --git a/UI/i18n/Translation.ts b/UI/i18n/Translation.ts
index ab13e4b403..508d9c0261 100644
--- a/UI/i18n/Translation.ts
+++ b/UI/i18n/Translation.ts
@@ -32,10 +32,14 @@ export class Translation extends BaseUIElement {
}
get txt(): string {
+ return this.textFor(Translation.forcedLanguage ?? Locale.language.data)
+ }
+
+ public textFor(language: string): string{
if (this.translations["*"]) {
return this.translations["*"];
}
- const txt = this.translations[Translation.forcedLanguage ?? Locale.language.data];
+ const txt = this.translations[language];
if (txt !== undefined) {
return txt;
}
@@ -52,7 +56,7 @@ export class Translation extends BaseUIElement {
console.error("Missing language ", Locale.language.data, "for", this.translations)
return "";
}
-
+
InnerConstructElement(): HTMLElement {
const el = document.createElement("span")
Locale.language.addCallbackAndRun(_ => {
@@ -106,12 +110,12 @@ export class Translation extends BaseUIElement {
// @ts-ignore
const date: Date = el;
rtext = date.toLocaleString();
- } else if (el.InnerRenderAsString === undefined) {
+ } else if (el.ConstructElement() === undefined) {
console.error("InnerREnder is not defined", el);
throw "Hmmm, el.InnerRender is not defined?"
} else {
Translation.forcedLanguage = lang; // This is a very dirty hack - it'll bite me one day
- rtext = el.InnerRenderAsString();
+ rtext = el.ConstructElement().innerHTML;
}
for (let i = 0; i < parts.length - 1; i++) {
diff --git a/Utils.ts b/Utils.ts
index 9b41a260de..b8ae81e7e8 100644
--- a/Utils.ts
+++ b/Utils.ts
@@ -149,6 +149,16 @@ export class Utils {
return [a.substr(0, index), a.substr(index + sep.length)];
}
+ public static SubstituteKeys(txt: string, tags: any) {
+ for (const key in tags) {
+ if(!tags.hasOwnProperty(key)) {
+ continue
+ }
+ txt = txt.replace(new RegExp("{" + key + "}", "g"), tags[key])
+ }
+ return txt;
+ }
+
// Date will be undefined on failure
public static LoadCustomCss(location: string) {
const head = document.getElementsByTagName('head')[0];
@@ -251,6 +261,10 @@ export class Utils {
public static UnMinify(minified: string): string {
+ if(minified === undefined || minified === null){
+ return undefined;
+ }
+
const parts = minified.split("|");
let result = parts.shift();
const keys = Utils.knownKeys.concat(Utils.extraKeys);
diff --git a/css/tagrendering.css b/css/tagrendering.css
index 5c73f84ede..a87f67a471 100644
--- a/css/tagrendering.css
+++ b/css/tagrendering.css
@@ -68,57 +68,4 @@ input:checked + label .question-option-with-border {
width: 100%;
}
-.edit-button img {
- width: 1.3em;
- height: 1.3em;
- padding: 0.5em;
- border-radius: 0.65em;
- border: solid var(--popup-border) 1px;
- font-size: medium;
- float: right;
-}
-.edit-button svg {
- width: 1.3em;
- height: 1.3em;
- padding: 0.5em;
- border-radius: 0.65em;
- border: solid var(--foreground-color) 1px;
- stroke: var(--foreground-color) !important;
- fill: var(--foreground-color) !important;
- font-size: medium;
- float: right;
-}
-
-.edit-button svg path {
- stroke: var(--foreground-color) !important;
- fill: var(--foreground-color) !important;
-}
-
-
-
-.to-the-map span {
- font-size: xx-large;
-}
-
-.to-the-map {
- background: var(--catch-detail-color);
- height: var(--return-to-the-map-height);
- color: var(--catch-detail-color-contrast);
- font-weight: bold;
- pointer-events: all;
- cursor: pointer;
- padding-top: 0.4em;
- text-align: center;
- box-sizing: border-box;
- display: block;
- max-height: var(--return-to-the-map-height);
- position: fixed;
- width: 100vw;
- bottom: 0;
- z-index: 100000;
-}
-
-.to-the-map-inner{
- font-size: xx-large;
-}
diff --git a/index.css b/index.css
index c6b0dabbb7..10c8df5ee7 100644
--- a/index.css
+++ b/index.css
@@ -222,23 +222,6 @@ li::marker {
max-width: 2em !important;
}
-.simple-add-ui-icon {
- position: relative;
- display: block;
- width: 4em;
- height: 3.5em;
-}
-
-.simple-add-ui-icon img {
- max-height: 3.5em !important;
- max-width: 3.5em !important;
-}
-
-.simple-add-ui-icon svg {
- max-height: 3.5em !important;
- max-width: 3.5em !important;
-}
-
/**************** GENERIC ****************/
@@ -292,14 +275,10 @@ li::marker {
}
.link-underline .subtle a {
- color: var(--foreground-color);
text-decoration: underline 1px #7193bb88;
color: #7193bb;
}
-.bold {
- font-weight: bold;
-}
.thanks {
background-color: #43d904;
@@ -318,11 +297,6 @@ li::marker {
pointer-events: none !important;
}
-.page-split {
- display: flex;
- height: 100%;
-}
-
/**************************************/
diff --git a/langs/en.json b/langs/en.json
index 287f85dc3a..b92fc6e2b6 100644
--- a/langs/en.json
+++ b/langs/en.json
@@ -54,7 +54,7 @@
"zoomInFurther": "Zoom in further to add a point.",
"stillLoading": "The data is still loading. Please wait a bit before you add a new point.",
"confirmIntro": "Add a {title} here? The point you create here will be visible for everyone . Please, only add things on to the map if they truly exist. A lot of applications use this data.",
- "confirmButton": "Add a {category} here.Your addition is visible for everyone
",
+ "warnVisibleForEveryone": "Your addition will be visible for everyone",
"openLayerControl": "Open the layer control box",
"layerNotEnabled": "The layer {layer} is not enabled. Enable this layer to add a point"
},
@@ -108,6 +108,7 @@
"createYourOwnTheme": "Create your own MapComplete theme from scratch"
},
"readYourMessages": "Please, read all your OpenStreetMap-messages before adding a new point.",
+ "presetInfo": "The new POI will have {tags}",
"fewChangesBefore": "Please, answer a few questions of existing points before adding a new point.",
"goToInbox": "Open inbox",
"getStartedLogin": "Login with OpenStreetMap to get started",
diff --git a/test/Tag.spec.ts b/test/Tag.spec.ts
index 0c1938a79c..b4d07702c9 100644
--- a/test/Tag.spec.ts
+++ b/test/Tag.spec.ts
@@ -144,8 +144,6 @@ export default class TagSpec extends T{
equal(undefined, tr.GetRenderValue({"foo": "bar"}));
equal("Has no name", tr.GetRenderValue({"noname": "yes"})?.txt);
equal("Ook een {name}", tr.GetRenderValue({"name": "xyz"})?.txt);
- equal("Ook een xyz", SubstitutedTranslation.construct(tr.GetRenderValue({"name": "xyz"}),
- new UIEventSource({"name": "xyz"})).InnerRenderAsString());
equal(undefined, tr.GetRenderValue({"foo": "bar"}));
})],
@@ -196,7 +194,7 @@ export default class TagSpec extends T{
const uiEl = new EditableTagRendering(new UIEventSource(
{leisure: "park", "access": "no"}), constr
);
- const rendered = uiEl.InnerRenderAsString();
+ const rendered = uiEl.ConstructElement().innerHTML;
equal(true, rendered.indexOf("Niet toegankelijk") > 0)
}
diff --git a/test/TagQuestion.spec.ts b/test/TagQuestion.spec.ts
index f0415ad008..b094986df5 100644
--- a/test/TagQuestion.spec.ts
+++ b/test/TagQuestion.spec.ts
@@ -5,7 +5,6 @@ Utils.runningFromConsole = true;
import TagRenderingQuestion from "../UI/Popup/TagRenderingQuestion";
import {UIEventSource} from "../Logic/UIEventSource";
import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig";
-import EditableTagRendering from "../UI/Popup/EditableTagRendering";
export default class TagQuestionSpec extends T {
constructor() {