forked from MapComplete/MapComplete
Fix popups and core functionality
This commit is contained in:
parent
9cc721abad
commit
8ad9b816ac
13 changed files with 116 additions and 144 deletions
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>`);
|
||||
|
||||
const iframeCode = new VariableUiElement(
|
||||
url.map((url) => {
|
||||
return `<span class='literal-code iframe-code-block'>
|
||||
|
|
|
@ -31,7 +31,7 @@ export class FixedInputElement<T> extends InputElement<T> {
|
|||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
return undefined;
|
||||
return this._el;
|
||||
}
|
||||
|
||||
GetValue(): UIEventSource<T> {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;");
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
28
test.ts
|
@ -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
|
|
@ -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)
|
||||
|
||||
}
|
||||
], [
|
||||
|
|
|
@ -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);
|
||||
}]
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -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()]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue