forked from MapComplete/MapComplete
Fix dynamism in questions with new VariableInputElement
This commit is contained in:
parent
b6b20ed3ca
commit
abae813606
6 changed files with 98 additions and 61 deletions
|
@ -7,7 +7,6 @@ export class VariableUiElement extends BaseUIElement {
|
||||||
constructor(contents: UIEventSource<string | BaseUIElement | BaseUIElement[]>) {
|
constructor(contents: UIEventSource<string | BaseUIElement | BaseUIElement[]>) {
|
||||||
super();
|
super();
|
||||||
this._contents = contents;
|
this._contents = contents;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InnerConstructElement(): HTMLElement {
|
protected InnerConstructElement(): HTMLElement {
|
||||||
|
|
35
UI/Input/VariableInputElement.ts
Normal file
35
UI/Input/VariableInputElement.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import {InputElement} from "./InputElement";
|
||||||
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
|
|
||||||
|
export default class VariableInputElement<T> extends InputElement<T> {
|
||||||
|
|
||||||
|
private readonly value: UIEventSource<T>;
|
||||||
|
private readonly element: BaseUIElement
|
||||||
|
public readonly IsSelected: UIEventSource<boolean>;
|
||||||
|
private readonly upstream: UIEventSource<InputElement<T>>;
|
||||||
|
|
||||||
|
constructor(upstream: UIEventSource<InputElement<T>>) {
|
||||||
|
|
||||||
|
super()
|
||||||
|
this.upstream = upstream;
|
||||||
|
this.value = upstream.bind(v => v.GetValue())
|
||||||
|
this.element = new VariableUiElement(upstream)
|
||||||
|
this.IsSelected = upstream.bind(v => v.IsSelected)
|
||||||
|
}
|
||||||
|
|
||||||
|
GetValue(): UIEventSource<T> {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected InnerConstructElement(): HTMLElement {
|
||||||
|
return this.element.ConstructElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IsValid(t: T): boolean {
|
||||||
|
return this.upstream.data.IsValid(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ export default class EditableTagRendering extends Toggle {
|
||||||
const renderingIsShown = tags.map(tags =>
|
const renderingIsShown = tags.map(tags =>
|
||||||
configuration.IsKnown(tags) &&
|
configuration.IsKnown(tags) &&
|
||||||
(configuration?.condition?.matchesProperties(tags) ?? true))
|
(configuration?.condition?.matchesProperties(tags) ?? true))
|
||||||
|
|
||||||
super(
|
super(
|
||||||
new Lazy(() => EditableTagRendering.CreateRendering(tags, configuration, units, editMode)),
|
new Lazy(() => EditableTagRendering.CreateRendering(tags, configuration, units, editMode)),
|
||||||
undefined,
|
undefined,
|
||||||
|
@ -49,8 +50,8 @@ export default class EditableTagRendering extends Toggle {
|
||||||
]).SetClass("flex justify-between w-full")
|
]).SetClass("flex justify-between w-full")
|
||||||
|
|
||||||
|
|
||||||
const question = new Lazy(() => {
|
const question = new Lazy(() =>
|
||||||
return new TagRenderingQuestion(tags, configuration,
|
new TagRenderingQuestion(tags, configuration,
|
||||||
{
|
{
|
||||||
units: units,
|
units: units,
|
||||||
cancelButton: Translations.t.general.cancel.Clone()
|
cancelButton: Translations.t.general.cancel.Clone()
|
||||||
|
@ -61,10 +62,7 @@ export default class EditableTagRendering extends Toggle {
|
||||||
afterSave: () => {
|
afterSave: () => {
|
||||||
editMode.setData(false)
|
editMode.setData(false)
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
rendering = new Toggle(
|
rendering = new Toggle(
|
||||||
|
|
|
@ -7,6 +7,7 @@ import BaseUIElement from "../BaseUIElement";
|
||||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||||
import {Unit} from "../../Models/Unit";
|
import {Unit} from "../../Models/Unit";
|
||||||
|
import Lazy from "../Base/Lazy";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,7 +28,8 @@ export default class QuestionBox extends VariableUiElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagRenderingQuestions = tagRenderings
|
const tagRenderingQuestions = tagRenderings
|
||||||
.map((tagRendering, i) => new TagRenderingQuestion(tagsSource, tagRendering,
|
.map((tagRendering, i) =>
|
||||||
|
new Lazy(() => new TagRenderingQuestion(tagsSource, tagRendering,
|
||||||
{
|
{
|
||||||
units: units,
|
units: units,
|
||||||
afterSave: () => {
|
afterSave: () => {
|
||||||
|
@ -41,7 +43,7 @@ export default class QuestionBox extends VariableUiElement {
|
||||||
skippedQuestions.ping();
|
skippedQuestions.ping();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
));
|
)));
|
||||||
|
|
||||||
const skippedQuestionsButton = Translations.t.general.skippedQuestions.Clone()
|
const skippedQuestionsButton = Translations.t.general.skippedQuestions.Clone()
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import InputElementWrapper from "../Input/InputElementWrapper";
|
||||||
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
|
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
|
||||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||||
import {Unit} from "../../Models/Unit";
|
import {Unit} from "../../Models/Unit";
|
||||||
|
import VariableInputElement from "../Input/VariableInputElement";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the question element.
|
* Shows the question element.
|
||||||
|
@ -45,9 +46,9 @@ export default class TagRenderingQuestion extends Combine {
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
||||||
/* const applicableMappings =
|
const applicableMappingsSrc =
|
||||||
UIEventSource.ListStabilized(tags.map(tags => {
|
UIEventSource.ListStabilized(tags.map(tags => {
|
||||||
const applicableMappings : {if: TagsFilter, then: any, ifnot?: TagsFilter}[] = []
|
const applicableMappings: { if: TagsFilter, then: any, ifnot?: TagsFilter }[] = []
|
||||||
for (const mapping of configuration.mappings ?? []) {
|
for (const mapping of configuration.mappings ?? []) {
|
||||||
if (mapping.hideInAnswer === true) {
|
if (mapping.hideInAnswer === true) {
|
||||||
continue
|
continue
|
||||||
|
@ -56,37 +57,18 @@ export default class TagRenderingQuestion extends Combine {
|
||||||
applicableMappings.push(mapping)
|
applicableMappings.push(mapping)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const condition = <TagsFilter> mapping.hideInAnswer;
|
const condition = <TagsFilter>mapping.hideInAnswer;
|
||||||
const isShown = !condition.matchesProperties(tags)
|
const isShown = !condition.matchesProperties(tags)
|
||||||
if(isShown){
|
if (isShown) {
|
||||||
applicableMappings.push(mapping)
|
applicableMappings.push(mapping)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return applicableMappings
|
return applicableMappings
|
||||||
}));
|
}));
|
||||||
|
|
||||||
super(
|
applicableMappingsSrc.addCallbackAndRun(appl => console.log("Currently applicable renderings are:", appl.map(m => m.then.txt).join(", ")))
|
||||||
applicableMappings.map(applicableMappings => {
|
|
||||||
return TagRenderingQuestion.GenerateFullQuestion(tags, applicableMappings, configuration, options)
|
|
||||||
})
|
|
||||||
)*/
|
|
||||||
|
|
||||||
const applicableMappings = Utils.NoNull((configuration.mappings??[]).filter(mapping => mapping.hideInAnswer !== undefined))
|
|
||||||
|
|
||||||
super([TagRenderingQuestion.GenerateFullQuestion(tags, applicableMappings, configuration, options)])
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GenerateFullQuestion(tags: UIEventSource<any>,
|
|
||||||
applicableMappings: {if: TagsFilter, then: any, ifnot?: TagsFilter}[],
|
|
||||||
configuration: TagRenderingConfig,
|
|
||||||
options?: {
|
|
||||||
units?: Unit[],
|
|
||||||
afterSave?: () => void,
|
|
||||||
cancelButton?: BaseUIElement,
|
|
||||||
saveButtonConstr?: (src: UIEventSource<TagsFilter>) => BaseUIElement,
|
|
||||||
bottomText?: (src: UIEventSource<TagsFilter>) => BaseUIElement
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
if (configuration === undefined) {
|
if (configuration === undefined) {
|
||||||
throw "A question is needed for a question visualization"
|
throw "A question is needed for a question visualization"
|
||||||
}
|
}
|
||||||
|
@ -96,13 +78,14 @@ export default class TagRenderingQuestion extends Combine {
|
||||||
.SetClass("question-text");
|
.SetClass("question-text");
|
||||||
|
|
||||||
|
|
||||||
const inputElement: InputElement<TagsFilter> = TagRenderingQuestion.GenerateInputElement(configuration, applicableMappings, applicableUnit, tags)
|
const inputElement: InputElement<TagsFilter> =
|
||||||
|
new VariableInputElement(applicableMappingsSrc.map(applicableMappings =>
|
||||||
|
TagRenderingQuestion.GenerateInputElement(configuration, applicableMappings, applicableUnit, tags)
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
// inputElement.GetValue().addCallbackAndRun(s => console.trace(configuration.question.txt, "Current selection is ", s))
|
||||||
|
|
||||||
if (inputElement === undefined) {
|
|
||||||
console.error("MultiAnswer failed - probably not a single option was possible", configuration)
|
|
||||||
throw "MultiAnswer failed - probably not a single option was possible"
|
|
||||||
}
|
|
||||||
inputElement.GetValue().addCallbackAndRun(s => console.trace("Current selection is ", s))
|
|
||||||
const save = () => {
|
const save = () => {
|
||||||
console.log("OnSaveTriggered", inputElement)
|
console.log("OnSaveTriggered", inputElement)
|
||||||
const selection = inputElement.GetValue().data;
|
const selection = inputElement.GetValue().data;
|
||||||
|
@ -151,18 +134,18 @@ export default class TagRenderingQuestion extends Combine {
|
||||||
)
|
)
|
||||||
).SetClass("block break-all")
|
).SetClass("block break-all")
|
||||||
}
|
}
|
||||||
return new Combine([
|
super([
|
||||||
question,
|
question,
|
||||||
inputElement,
|
inputElement,
|
||||||
options.cancelButton,
|
options.cancelButton,
|
||||||
saveButton,
|
saveButton,
|
||||||
bottomTags]
|
bottomTags])
|
||||||
).SetClass("question")
|
this.SetClass("question")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static GenerateInputElement(configuration: TagRenderingConfig,
|
private static GenerateInputElement(configuration: TagRenderingConfig,
|
||||||
applicableMappings: {if: TagsFilter, then: any, ifnot?: TagsFilter}[],
|
applicableMappings: { if: TagsFilter, then: any, ifnot?: TagsFilter }[],
|
||||||
applicableUnit: Unit,
|
applicableUnit: Unit,
|
||||||
tagsSource: UIEventSource<any>)
|
tagsSource: UIEventSource<any>)
|
||||||
: InputElement<TagsFilter> {
|
: InputElement<TagsFilter> {
|
||||||
|
@ -171,7 +154,6 @@ export default class TagRenderingQuestion extends Combine {
|
||||||
const ff = TagRenderingQuestion.GenerateFreeform(configuration, applicableUnit, tagsSource);
|
const ff = TagRenderingQuestion.GenerateFreeform(configuration, applicableUnit, tagsSource);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const hasImages = applicableMappings.filter(mapping => mapping.then.ExtractImages().length > 0).length > 0
|
const hasImages = applicableMappings.filter(mapping => mapping.then.ExtractImages().length > 0).length > 0
|
||||||
let inputEls: InputElement<TagsFilter>[];
|
let inputEls: InputElement<TagsFilter>[];
|
||||||
|
|
||||||
|
@ -226,6 +208,9 @@ export default class TagRenderingQuestion extends Combine {
|
||||||
|
|
||||||
|
|
||||||
if (inputEls.length == 0) {
|
if (inputEls.length == 0) {
|
||||||
|
if(ff === undefined){
|
||||||
|
throw "Error: could not generate a question: freeform and all mappings are undefined"
|
||||||
|
}
|
||||||
return ff;
|
return ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
32
test.ts
32
test.ts
|
@ -1,8 +1,26 @@
|
||||||
import Wikidata from "./Logic/Web/Wikidata";
|
import FeatureInfoBox from "./UI/Popup/FeatureInfoBox";
|
||||||
import WikipediaBox from "./UI/WikipediaBox";
|
import {UIEventSource} from "./Logic/UIEventSource";
|
||||||
import Locale from "./UI/i18n/Locale";
|
import AllKnownLayers from "./Customizations/AllKnownLayers";
|
||||||
import LanguagePicker from "./UI/LanguagePicker";
|
import State from "./State";
|
||||||
|
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
|
||||||
|
|
||||||
new WikipediaBox("Q177").SetStyle("max-height: 25rem")
|
State.state = new State(AllKnownLayouts.allKnownLayouts.get("charging_stations"))
|
||||||
.AttachTo("maindiv")
|
State.state.changes.pendingChanges.setData([])
|
||||||
LanguagePicker.CreateLanguagePicker(["en","nl","fr","de"]).AttachTo("extradiv")
|
const geojson = {
|
||||||
|
type: "Feature",
|
||||||
|
geometry: {
|
||||||
|
type: "Point",
|
||||||
|
coordinates: [51.0, 4]
|
||||||
|
},
|
||||||
|
properties:
|
||||||
|
{
|
||||||
|
id: "node/42",
|
||||||
|
amenity: "charging_station",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State.state.allElements.addOrGetElement(geojson)
|
||||||
|
const tags = State.state.allElements.getEventSourceById("node/42")
|
||||||
|
new FeatureInfoBox(
|
||||||
|
tags,
|
||||||
|
AllKnownLayers.sharedLayers.get("charging_station")
|
||||||
|
).AttachTo("maindiv")
|
Loading…
Reference in a new issue