Fix popups and core functionality

This commit is contained in:
Pieter Vander Vennet 2021-06-14 17:28:11 +02:00
parent 9cc721abad
commit 8ad9b816ac
13 changed files with 116 additions and 144 deletions

View file

@ -26,7 +26,7 @@ export class VariableUiElement extends BaseUIElement {
for (const content of contents) {
const c = content.ConstructElement();
if (c !== undefined && c !== null) {
el.appendChild(c)
el.appendChild(c)
}
}

View file

@ -43,6 +43,7 @@ export default abstract class BaseUIElement {
throw "SEVERE: could not attach UIElement to " + divId;
}
console.log("Attaching to ", element)
while (element.firstChild) {
//The list is LIVE so it will re-index each call
element.removeChild(element.firstChild);

View file

@ -46,7 +46,10 @@ export default class ShareScreen extends Combine {
return null;
}
if (includeL) {
return `z=${currentLocation.data.zoom}&lat=${currentLocation.data.lat}&lon=${currentLocation.data.lon}`
return [["z", currentLocation.data?.zoom], ["lat", currentLocation.data?.lat], ["lon", currentLocation.data?.lon]]
.filter(p => p[1] !== undefined)
.map(p => p[0]+"="+p[1])
.join("&")
} else {
return null;
}
@ -166,8 +169,6 @@ export default class ShareScreen extends Combine {
}, optionParts);
const iframe = url.map(url => `<iframe src="${url}" width="100%" height="100%" title="${layout?.title?.txt ?? "MapComplete"} with MapComplete"></iframe&gt`);
const iframeCode = new VariableUiElement(
url.map((url) => {
return `<span class='literal-code iframe-code-block'>

View file

@ -31,7 +31,7 @@ export class FixedInputElement<T> extends InputElement<T> {
}
protected InnerConstructElement(): HTMLElement {
return undefined;
return this._el;
}
GetValue(): UIEventSource<T> {

View file

@ -22,13 +22,24 @@ export class RadioButton<T> extends InputElement<T> {
}
}
), elements.map(e => e?.GetValue()));
if(selectFirstAsDefault){
value.addCallbackAndRun(selected =>{
if(selected === undefined){
for (const element of elements) {
const v = element.GetValue().data;
if(v !== undefined){
value.setData(v)
break;
}
}
}
})
/*
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
@ -63,14 +74,25 @@ export class RadioButton<T> extends InputElement<T> {
input.name = groupId;
input.type = "radio"
input.onchange = () => {
if(input.checked){
selectedElementIndex.setData(i1)
}
}
value.addCallbackAndRun(
selected => input.checked = element.IsValid(selected)
)
const label = document.createElement("label")
label.appendChild(labelHtml)
label.htmlFor = input.id;
input.appendChild(label)
form.appendChild(input)
const block = document.createElement("div")
block.appendChild(input)
block.appendChild(label)
form.appendChild(block)
form.addEventListener("change", () => {
// TODO FIXME
}
@ -81,6 +103,7 @@ export class RadioButton<T> extends InputElement<T> {
this.value = value;
this._elements = elements;
this.SetClass("flex flex-col")
}
IsValid(t: T): boolean {

View file

@ -8,6 +8,7 @@ import State from "../../State";
import Svg from "../../Svg";
import Toggle from "../Input/Toggle";
import BaseUIElement from "../BaseUIElement";
import {FixedUiElement} from "../Base/FixedUiElement";
export default class EditableTagRendering extends Toggle {
@ -28,8 +29,10 @@ export default class EditableTagRendering extends Toggle {
});
const answerWithEditButton = new Combine([answer,
new Toggle(editButton, undefined, State.state.osmConnection.isLoggedIn)]).SetClass("w-full")
new Toggle(editButton, undefined, State.state.osmConnection.isLoggedIn)])
.SetClass("flex justify-between w-full")
const cancelbutton =
@ -52,13 +55,12 @@ export default class EditableTagRendering extends Toggle {
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")
rendering.SetClass("block w-full break-word text-default m-1 p-1 border-b border-gray-200 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.IsKnown(tags) &&
(configuration?.condition?.matchesProperties(tags) ?? true))
super(
rendering,

View file

@ -1,11 +1,10 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig";
import {Utils} from "../../Utils";
import {SubstitutedTranslation} from "../SubstitutedTranslation";
import BaseUIElement from "../BaseUIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import List from "../Base/List";
import {FixedUiElement} from "../Base/FixedUiElement";
import {SubstitutedTranslation} from "../SubstitutedTranslation";
/***
* Displays the correct value for a known tagrendering
@ -24,19 +23,17 @@ export default class TagRenderingAnswer extends VariableUiElement {
if(trs.length === 0){
return undefined;
}
trs.forEach(tr => console.log("Rendering ", tr))
const valuesToRender: BaseUIElement[] = trs.map(tr => new SubstitutedTranslation(tr, tagsSource))
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((element : BaseUIElement) => element?.SetClass(contentClasses)?.SetStyle(contentStyle)))
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.SetClass("flex items-center flex-row text-lg link-underline tag-renering-answer")
this.SetStyle("word-wrap: anywhere;");
}

View file

@ -14,8 +14,7 @@ export default class ShowDataLayer {
private _layerDict;
private readonly _leafletMap: UIEventSource<L.Map>;
private readonly _popups = new Map<any, L.Layer>();
private _cleanCount = 0;
constructor(features: UIEventSource<{ feature: any, freshness: Date }[]>,
leafletMap: UIEventSource<L.Map>,
@ -44,6 +43,7 @@ export default class ShowDataLayer {
return;
}
self._cleanCount++
// clean all the old stuff away, if any
if (geoLayer !== undefined) {
mp.removeLayer(geoLayer);
@ -74,34 +74,6 @@ export default class ShowDataLayer {
features.addCallback(() => update());
leafletMap.addCallback(() => update());
update();
State.state.selectedElement.addCallbackAndRun(selected => {
if (selected === undefined) {
mp.closePopup();
return;
}
const marker = self._popups.get(selected);
if (marker === undefined) {
return;
}
marker.openPopup();
const tags = State.state.allElements.getEventSourceById(selected.properties.id);
const layer: LayerConfig = this._layerDict[selected._matching_layer_id];
const infoBox = new FeatureInfoBox(tags, layer);
infoBox.isShown.addCallback(isShown => {
if (!isShown) {
State.state.selectedElement.setData(undefined);
}
});
infoBox.AttachTo(`popup-${selected.properties.id}`)
infoBox.Activate();
})
}
@ -154,17 +126,43 @@ export default class ShowDataLayer {
closeButton: false
}, leafletLayer);
// By setting 50vh, leaflet will attempt to fit the entire screen and move the feature down
popup.setContent(`<div style='height: 50vh' id='popup-${feature.properties.id}'>Rendering</div>`);
leafletLayer.bindPopup(popup);
leafletLayer.bindPopup(popup);
let infobox : FeatureInfoBox = undefined;
const id = `popup-${feature.properties.id}-${this._cleanCount}`
popup.setContent(`<div style='height: 50vh' id='${id}'>Rendering</div>`)
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°
if (infobox === undefined) {
const tags = State.state.allElements.getEventSourceById(feature.properties.id);
infobox = new FeatureInfoBox(tags, layer);
infobox.isShown.addCallback(isShown => {
if (!isShown) {
State.state.selectedElement.setData(undefined);
}
});
}
infobox.AttachTo(id)
infobox.Activate();
});
const self = this;
State.state.selectedElement.addCallbackAndRun(selected => {
if (selected === undefined || self._leafletMap.data === undefined) {
return;
}
if (popup.isOpen()) {
return;
}
if (selected.properties.id === feature.properties.id) {
leafletLayer.openPopup()
}
})
this._popups.set(feature, leafletLayer);
}
private CreateGeojsonLayer(): L.Layer {

View file

@ -20,9 +20,7 @@ export class SubstitutedTranslation extends VariableUiElement {
if (txt === undefined) {
return "no tags subs tr"
}
const contents = SubstitutedTranslation.EvaluateSpecialComponents(txt, tags)
console.log("Substr has contents", contents)
return new Combine(contents)
return new Combine(SubstitutedTranslation.EvaluateSpecialComponents(txt, tags))
}, [Locale.language])
)

28
test.ts
View file

@ -1,3 +1,27 @@
import TestAll from "./test/TestAll";
import {RadioButton} from "./UI/Input/RadioButton";
import {FixedInputElement} from "./UI/Input/FixedInputElement";
import {SubstitutedTranslation} from "./UI/SubstitutedTranslation";
import {UIEventSource} from "./Logic/UIEventSource";
import {Translation} from "./UI/i18n/Translation";
import TagRenderingAnswer from "./UI/Popup/TagRenderingAnswer";
import TagRenderingConfig from "./Customizations/JSON/TagRenderingConfig";
import EditableTagRendering from "./UI/Popup/EditableTagRendering";
new TestAll().testAll();
const tagsSource = new UIEventSource({
id:'id',
name:'name',
surface:'asphalt'
})
const config = new TagRenderingConfig({
render: "Rendering {name} {id} {surface}"
}, null, "test")
new EditableTagRendering(
tagsSource,
config
).AttachTo("extradiv")
window.v = tagsSource

View file

@ -190,12 +190,6 @@ export default class TagSpec extends T{
]
};
const constr = new TagRenderingConfig(def, undefined, "test");
const uiEl = new EditableTagRendering(new UIEventSource<any>(
{leisure: "park", "access": "no"}), constr
);
const rendered = uiEl.ConstructElement().innerHTML;
equal(true, rendered.indexOf("Niet toegankelijk") > 0)
}
], [

View file

@ -1,62 +0,0 @@
import T from "./TestHelper";
import {Utils} from "../Utils";
Utils.runningFromConsole = true;
import TagRenderingQuestion from "../UI/Popup/TagRenderingQuestion";
import {UIEventSource} from "../Logic/UIEventSource";
import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig";
export default class TagQuestionSpec extends T {
constructor() {
super("TagQuestionElement",
[
["Freeform has textfield", () => {
const tags = new UIEventSource({
id: "way/123",
amenity: 'public_bookcases'
});
const config = new TagRenderingConfig(
{
render: "The name is {name}",
question: "What is the name of this bookcase?",
freeform: {
key: "name",
type: "string"
}
}, undefined, "Testing tag"
);
const questionElement = new TagRenderingQuestion(tags, config);
const html = questionElement.InnerRenderAsString();
T.assertContains("What is the name of this bookcase?", html);
T.assertContains("<input type='text'", html);
}],
["TagsQuestion with Freeform and mappings has textfield", () => {
const tags = new UIEventSource({
id: "way/123",
amenity: 'public_bookcases'
});
const config = new TagRenderingConfig(
{
render: "The name is {name}",
question: "What is the name of this bookcase?",
freeform: {
key: "name",
type: "string"
},
mappings: [
{
"if": "noname=yes",
"then": "This bookcase has no name"
}
]
}, undefined, "Testing tag"
);
const questionElement = new TagRenderingQuestion(tags, config);
const html = questionElement.InnerRenderAsString();
T.assertContains("What is the name of this bookcase?", html);
T.assertContains("This bookcase has no name", html);
T.assertContains("<input type='text'", html);
}]
]);
}
}

View file

@ -1,11 +1,8 @@
import {Utils} from "../Utils";
Utils.runningFromConsole = true;
import TagSpec from "./Tag.spec";
import ImageAttributionSpec from "./ImageAttribution.spec";
import GeoOperationsSpec from "./GeoOperations.spec";
import TagQuestionSpec from "./TagQuestion.spec";
import ImageSearcherSpec from "./ImageSearcher.spec";
import ThemeSpec from "./Theme.spec";
import UtilsSpec from "./Utils.spec";
@ -34,7 +31,6 @@ const allTests = [
new TagSpec(),
new ImageAttributionSpec(),
new GeoOperationsSpec(),
new TagQuestionSpec(),
new ImageSearcherSpec(),
new ThemeSpec(),
new UtilsSpec()]