Further work, add checkboxes as option
This commit is contained in:
parent
8bb9b1228b
commit
c944156d87
28 changed files with 364 additions and 149 deletions
|
@ -9,8 +9,6 @@ import {LayerConfigJson} from "./LayerConfigJson";
|
|||
import {LayerDefinition, Preset} from "../LayerDefinition";
|
||||
import {TagDependantUIElementConstructor} from "../UIElementConstructor";
|
||||
import Combine from "../../UI/Base/Combine";
|
||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import {ImageCarouselConstructor} from "../../UI/Image/ImageCarousel";
|
||||
import * as drinkingWater from "../../assets/layers/drinking_water/drinking_water.json";
|
||||
import * as ghostbikes from "../../assets/layers/ghost_bike/ghost_bike.json"
|
||||
import * as viewpoint from "../../assets/layers/viewpoint/viewpoint.json"
|
||||
|
@ -20,12 +18,19 @@ import * as birdhides from "../../assets/layers/bird_hide/birdhides.json"
|
|||
import * as nature_reserve from "../../assets/layers/nature_reserve/nature_reserve.json"
|
||||
|
||||
import {Utils} from "../../Utils";
|
||||
import ImageCarouselWithUploadConstructor from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import {ImageCarouselConstructor} from "../../UI/Image/ImageCarousel";
|
||||
import {State} from "../../State";
|
||||
|
||||
export class FromJSON {
|
||||
|
||||
public static sharedLayers: Map<string, LayerDefinition> = FromJSON.getSharedLayers();
|
||||
|
||||
private static getSharedLayers() {
|
||||
|
||||
// We inject a function into state while we are busy
|
||||
State.FromBase64 = FromJSON.FromBase64;
|
||||
|
||||
const sharedLayers = new Map<string, LayerDefinition>();
|
||||
|
||||
const sharedLayersList = [
|
||||
|
@ -50,6 +55,7 @@ export class FromJSON {
|
|||
}
|
||||
|
||||
public static LayoutFromJSON(json: LayoutConfigJson): Layout {
|
||||
console.log(json)
|
||||
const tr = FromJSON.Translation;
|
||||
|
||||
const layers = json.layers.map(FromJSON.Layer);
|
||||
|
@ -111,7 +117,6 @@ export class FromJSON {
|
|||
|
||||
|
||||
if (typeof json === "string") {
|
||||
|
||||
switch (json) {
|
||||
case "picture": {
|
||||
return new ImageCarouselWithUploadConstructor()
|
||||
|
@ -123,7 +128,7 @@ export class FromJSON {
|
|||
return new ImageCarouselWithUploadConstructor()
|
||||
}
|
||||
case "images": {
|
||||
return new ImageCarouselWithUploadConstructor()
|
||||
return new ImageCarouselWithUploadConstructor()
|
||||
}
|
||||
case "picturesNoUpload": {
|
||||
return new ImageCarouselConstructor()
|
||||
|
@ -193,11 +198,13 @@ export class FromJSON {
|
|||
let rendering = new TagRenderingOptions({
|
||||
question: question,
|
||||
freeform: freeform,
|
||||
mappings: mappings
|
||||
mappings: mappings,
|
||||
multiAnswer: json.multiAnswer
|
||||
});
|
||||
|
||||
|
||||
if (json.condition) {
|
||||
return rendering.OnlyShowIf(FromJSON.Tag(json.condition, `In tagrendering ${propertyName}.condition`));
|
||||
const condition = FromJSON.Tag(json.condition, `In tagrendering ${propertyName}.condition`);
|
||||
return rendering.OnlyShowIf(condition);
|
||||
}
|
||||
|
||||
return rendering;
|
||||
|
|
|
@ -38,7 +38,12 @@ export interface TagRenderingConfigJson {
|
|||
* Usefull to add a 'fixme=freeform textfield used - to be checked'
|
||||
**/
|
||||
addExtraTags?: string[];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* If true, use checkboxes instead of radio buttons when asking the question
|
||||
*/
|
||||
multiAnswer?: boolean,
|
||||
|
||||
/**
|
||||
* Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {LayerDefinition} from "../LayerDefinition";
|
||||
import FixedText from "../Questions/FixedText";
|
||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import ImageCarouselWithUploadConstructor from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import Translations from "../../UI/i18n/Translations";
|
||||
import CafeName from "../Questions/bike/CafeName";
|
||||
import {And, Or, RegexTag, Tag} from "../../Logic/Tags";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {LayerDefinition} from "../LayerDefinition";
|
||||
import Translations from "../../UI/i18n/Translations";
|
||||
import {And, RegexTag, Tag} from "../../Logic/Tags";
|
||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import ImageCarouselWithUploadConstructor from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import ShopRetail from "../Questions/bike/ShopRetail";
|
||||
import ShopPump from "../Questions/bike/ShopPump";
|
||||
import ShopRental from "../Questions/bike/ShopRental";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {LayerDefinition} from "../LayerDefinition";
|
||||
import Translations from "../../UI/i18n/Translations";
|
||||
import {And, Tag} from "../../Logic/Tags";
|
||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import ImageCarouselWithUploadConstructor from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import ShopRetail from "../Questions/bike/ShopRetail";
|
||||
import ShopPump from "../Questions/bike/ShopPump";
|
||||
import ShopRental from "../Questions/bike/ShopRental";
|
||||
|
|
|
@ -5,7 +5,7 @@ import {OperatorTag} from "../Questions/OperatorTag";
|
|||
import {NameQuestion} from "../Questions/NameQuestion";
|
||||
import {NameInline} from "../Questions/NameInline";
|
||||
import {DescriptionQuestion} from "../Questions/DescriptionQuestion";
|
||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import ImageCarouselWithUploadConstructor from "../../UI/Image/ImageCarouselWithUpload";
|
||||
|
||||
export class Bos extends LayerDefinition {
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import {LayerDefinition} from "../LayerDefinition";
|
||||
import FixedText from "../Questions/FixedText";
|
||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import {And, Tag} from "../../Logic/Tags";
|
||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||
import ImageCarouselWithUploadConstructor from "../../UI/Image/ImageCarouselWithUpload";
|
||||
|
||||
export class InformationBoard extends LayerDefinition {
|
||||
constructor() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {LayerDefinition} from "../LayerDefinition";
|
||||
import FixedText from "../Questions/FixedText";
|
||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import ImageCarouselWithUploadConstructor from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import {Tag} from "../../Logic/Tags";
|
||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import {OperatorTag} from "../Questions/OperatorTag";
|
|||
import {NameQuestion} from "../Questions/NameQuestion";
|
||||
import {NameInline} from "../Questions/NameInline";
|
||||
import {DescriptionQuestion} from "../Questions/DescriptionQuestion";
|
||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import ImageCarouselWithUploadConstructor from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||
|
||||
export class NatureReserves extends LayerDefinition {
|
||||
|
|
|
@ -3,7 +3,7 @@ import {Or, Tag} from "../../Logic/Tags";
|
|||
import {NameQuestion} from "../Questions/NameQuestion";
|
||||
import {NameInline} from "../Questions/NameInline";
|
||||
import {DescriptionQuestion} from "../Questions/DescriptionQuestion";
|
||||
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import ImageCarouselWithUploadConstructor from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import {TagRenderingOptions} from "../TagRenderingOptions";
|
||||
|
||||
export class Park extends LayerDefinition {
|
||||
|
|
|
@ -21,6 +21,7 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
|
|||
placeholder?: string | Translation;
|
||||
extraTags?: TagsFilter
|
||||
};
|
||||
multiAnswer?: boolean,
|
||||
mappings?: { k: TagsFilter; txt: string | Translation; priority?: number, substitute?: boolean, hideInAnwser?: boolean }[]
|
||||
};
|
||||
|
||||
|
@ -54,8 +55,12 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
|
|||
*
|
||||
*
|
||||
*/
|
||||
mappings?: { k: TagsFilter, txt: Translation | string, priority?: number, substitute?: boolean , hideInAnswer?:boolean}[],
|
||||
mappings?: { k: TagsFilter, txt: Translation | string, priority?: number, substitute?: boolean, hideInAnswer?: boolean }[],
|
||||
|
||||
/**
|
||||
* If true, use checkboxes to answer instead of radiobuttons
|
||||
*/
|
||||
multiAnswer?: boolean,
|
||||
|
||||
/**
|
||||
* If one wants to render a freeform tag (thus no predefined key/values) or if there are a few well-known tags with a freeform object,
|
||||
|
@ -113,12 +118,25 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
|
|||
return template.Subs(tags);
|
||||
}
|
||||
|
||||
console.warn("No content defined for",tags," with mapping",this);
|
||||
console.warn("No content defined for", tags, " with mapping", this);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
public static tagRendering: (tags: UIEventSource<any>, options: { priority?: number; question?: string | Translation; freeform?: { key: string; tagsPreprocessor?: (tags: any) => any; template: string | Translation; renderTemplate: string | Translation; placeholder?: string | Translation; extraTags?: TagsFilter }; mappings?: { k: TagsFilter; txt: string | Translation; priority?: number; substitute?: boolean, hideInAnswer?: boolean }[] }) => TagDependantUIElement;
|
||||
public static tagRendering: (tags: UIEventSource<any>,
|
||||
options: {
|
||||
priority?: number;
|
||||
question?: string | Translation;
|
||||
freeform?: {
|
||||
key: string;
|
||||
tagsPreprocessor?: (tags: any) => any;
|
||||
template: string | Translation;
|
||||
renderTemplate: string | Translation;
|
||||
placeholder?: string | Translation; extraTags?: TagsFilter
|
||||
},
|
||||
multiAnswer?: boolean,
|
||||
mappings?: { k: TagsFilter; txt: string | Translation; priority?: number; substitute?: boolean, hideInAnswer?: boolean }[]
|
||||
}) => TagDependantUIElement;
|
||||
|
||||
construct(dependencies: Dependencies): TagDependantUIElement {
|
||||
return TagRenderingOptions.tagRendering(dependencies.tags, this.options);
|
||||
|
|
|
@ -16,6 +16,7 @@ export class PersonalLayersPanel extends UIElement {
|
|||
super(State.state.favouriteLayers);
|
||||
|
||||
this.ListenTo(State.state.osmConnection.userDetails);
|
||||
this.ListenTo(State.state.favouriteLayers);
|
||||
|
||||
this.UpdateView([]);
|
||||
const self = this;
|
||||
|
@ -53,6 +54,9 @@ export class PersonalLayersPanel extends UIElement {
|
|||
this.checkboxes.push(header);
|
||||
|
||||
for (const layer of layout.layers) {
|
||||
if(typeof layer === "string"){
|
||||
continue;
|
||||
}
|
||||
let icon = layer.icon;
|
||||
if (icon !== undefined && typeof (icon) !== "string") {
|
||||
icon = icon.GetContent({"id": "node/-1"}).txt ?? "./assets/bug.svg";
|
||||
|
|
130
Logic/Tags.ts
130
Logic/Tags.ts
|
@ -344,5 +344,133 @@ export class TagUtils {
|
|||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given multiple tagsfilters which can be used as answer, will take the tags with the same keys together as set.
|
||||
* E.g:
|
||||
*
|
||||
* FlattenMultiAnswer([and: [ "x=a", "y=0;1"], and: ["x=b", "y=2"], and: ["x=", "y=3"]])
|
||||
* will result in
|
||||
* ["x=a;b", "y=0;1;2;3"]
|
||||
*
|
||||
* @param tagsFilters
|
||||
* @constructor
|
||||
*/
|
||||
static FlattenMultiAnswer(tagsFilters: TagsFilter[]): And {
|
||||
if (tagsFilters === undefined) {
|
||||
return new And([]);
|
||||
}
|
||||
const keyValues = {} // Map string -> string[]
|
||||
tagsFilters = [...tagsFilters]
|
||||
while (tagsFilters.length > 0) {
|
||||
const tagsFilter = tagsFilters.pop();
|
||||
|
||||
if (tagsFilter === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tagsFilter instanceof And) {
|
||||
tagsFilters.push(...tagsFilter.and);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tagsFilter instanceof Tag) {
|
||||
if (keyValues[tagsFilter.key] === undefined) {
|
||||
keyValues[tagsFilter.key] = [];
|
||||
}
|
||||
keyValues[tagsFilter.key].push(...tagsFilter.value.split(";"));
|
||||
continue;
|
||||
}
|
||||
|
||||
console.error("Invalid type to flatten the multiAnswer", tagsFilter);
|
||||
throw "Invalid type to FlattenMultiAnswer"
|
||||
}
|
||||
|
||||
const and: TagsFilter[] = []
|
||||
for (const key in keyValues) {
|
||||
and.push(new Tag(key, Utils.Dedup(keyValues[key]).join(";")));
|
||||
}
|
||||
|
||||
return new And(and);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the actualTags onto a list of which the values are the same as the tagsFilters.
|
||||
* Leftovers are returned in the list too if there is an 'undefined' value
|
||||
*/
|
||||
static SplitMultiAnswer(actualTags: TagsFilter, possibleTags: TagsFilter[], freeformKey: string, freeformExtraTags: TagsFilter): TagsFilter[] {
|
||||
|
||||
const queue: TagsFilter[] = [actualTags]
|
||||
|
||||
const keyValues = {} // key ==> value[]
|
||||
|
||||
while (queue.length > 0) {
|
||||
const tf = queue.pop();
|
||||
if (tf instanceof And) {
|
||||
queue.push(...tf.and);
|
||||
continue;
|
||||
}
|
||||
if (tf instanceof Tag) {
|
||||
if (keyValues[tf.key] === undefined) {
|
||||
keyValues[tf.key] = []
|
||||
}
|
||||
keyValues[tf.key].push(...tf.value.split(";"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tf === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw "Invalid tagfilter: " + JSON.stringify(tf)
|
||||
}
|
||||
|
||||
const foundValues = [];
|
||||
for (const possibleTag of possibleTags) {
|
||||
if (possibleTag === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (possibleTag instanceof Tag) {
|
||||
const key = possibleTag.key;
|
||||
const actualValues: string[] = keyValues[key] ?? [];
|
||||
const possibleValues = possibleTag.value.split(";");
|
||||
|
||||
let allPossibleValuesFound = true;
|
||||
for (const possibleValue of possibleValues) {
|
||||
if (actualValues.indexOf(possibleValue) < 0) {
|
||||
allPossibleValuesFound = false;
|
||||
}
|
||||
}
|
||||
if (!allPossibleValuesFound) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// At this point, we know that 'possibleTag' is completely present in the tagset
|
||||
// we add the possibleTag to the found values
|
||||
foundValues.push(possibleTag);
|
||||
|
||||
for (const possibleValue of possibleValues) {
|
||||
actualValues.splice(actualValues.indexOf(possibleValue), 1);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
throw "Unsupported possibletag: " + JSON.stringify(possibleTag);
|
||||
}
|
||||
|
||||
let leftoverTag = undefined;
|
||||
if (keyValues[freeformKey] !== undefined && keyValues[freeformKey].length !== 0) {
|
||||
leftoverTag = new Tag(freeformKey, keyValues[freeformKey].join(";"));
|
||||
if (freeformExtraTags !== undefined) {
|
||||
leftoverTag = new And([
|
||||
leftoverTag,
|
||||
freeformExtraTags
|
||||
])
|
||||
}
|
||||
foundValues.push(leftoverTag);
|
||||
}
|
||||
|
||||
return foundValues;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ export class UIEventSource<T>{
|
|||
|
||||
if(g !== undefined) {
|
||||
newSource.addCallback((latest) => {
|
||||
self.setData((g(latest)));
|
||||
self.setData(g(latest));
|
||||
})
|
||||
}
|
||||
|
||||
|
|
4
State.ts
4
State.ts
|
@ -193,7 +193,7 @@ export class State {
|
|||
continue;
|
||||
}
|
||||
try {
|
||||
const layout = FromJSON.FromBase64(customLayout.data);
|
||||
const layout = State.FromBase64(customLayout.data);
|
||||
if(layout.id === undefined){
|
||||
// This is an old style theme
|
||||
// We remove it
|
||||
|
@ -252,4 +252,6 @@ export class State {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
public static FromBase64 : (data: string) => Layout = undefined;
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ export default class TagRenderingPreview extends UIElement {
|
|||
try {
|
||||
rendering =
|
||||
new VariableUiElement(es.map(tagRenderingConfig => {
|
||||
const tr = FromJSON.TagRendering(tagRenderingConfig)
|
||||
const tr = FromJSON.TagRendering(tagRenderingConfig, "preview")
|
||||
.construct({tags: this.previewTagValue});
|
||||
return tr.Render();
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@ import {OsmImageUploadHandler} from "../../Logic/Osm/OsmImageUploadHandler";
|
|||
import {State} from "../../State";
|
||||
import Translation from "../i18n/Translation";
|
||||
|
||||
export class ImageCarouselWithUploadConstructor implements TagDependantUIElementConstructor{
|
||||
export default class ImageCarouselWithUploadConstructor implements TagDependantUIElementConstructor{
|
||||
|
||||
IsKnown(properties: any): boolean {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ export class ImgurImage extends UIElement {
|
|||
* Dictionary from url to alreayd known license info
|
||||
*/
|
||||
static allLicenseInfos: any = {};
|
||||
private _imageMeta: UIEventSource<LicenseInfo>;
|
||||
private _imageLocation: string;
|
||||
private readonly _imageMeta: UIEventSource<LicenseInfo>;
|
||||
private readonly _imageLocation: string;
|
||||
|
||||
constructor(source: string) {
|
||||
super(undefined)
|
||||
|
|
|
@ -7,8 +7,8 @@ export class WikimediaImage extends UIElement {
|
|||
|
||||
|
||||
static allLicenseInfos: any = {};
|
||||
private _imageMeta: UIEventSource<LicenseInfo>;
|
||||
private _imageLocation : string;
|
||||
private readonly _imageMeta: UIEventSource<LicenseInfo>;
|
||||
private readonly _imageLocation : string;
|
||||
|
||||
constructor(source: string) {
|
||||
super(undefined)
|
||||
|
|
|
@ -13,17 +13,16 @@ export class CheckBoxes<T> extends InputElement<T[]> {
|
|||
|
||||
private readonly value: UIEventSource<T[]>;
|
||||
private readonly _elements: InputElement<T>[]
|
||||
private readonly _selectFirstAsDefault: boolean;
|
||||
|
||||
|
||||
constructor(elements: InputElement<T>[],
|
||||
selectFirstAsDefault = true) {
|
||||
constructor(elements: InputElement<T>[]) {
|
||||
super(undefined);
|
||||
this._elements = Utils.NoNull(elements);
|
||||
this._selectFirstAsDefault = selectFirstAsDefault;
|
||||
this.dumbMode = false;
|
||||
|
||||
this.value = new UIEventSource<T[]>([])
|
||||
this.ListenTo(this.value);
|
||||
this.value.addCallback(latest => console.log("Latest is ", latest))
|
||||
|
||||
}
|
||||
|
||||
|
@ -35,6 +34,7 @@ export class CheckBoxes<T> extends InputElement<T[]> {
|
|||
let matchFound = false;
|
||||
for (const element of this._elements) {
|
||||
if (element.IsValid(t)) {
|
||||
element.GetValue().setData(t);
|
||||
matchFound = true;
|
||||
break
|
||||
}
|
||||
|
@ -56,7 +56,6 @@ export class CheckBoxes<T> extends InputElement<T[]> {
|
|||
}
|
||||
|
||||
InnerRender(): string {
|
||||
|
||||
let body = "";
|
||||
for (let i = 0; i < this._elements.length; i++) {
|
||||
let el = this._elements[i];
|
||||
|
@ -66,27 +65,30 @@ export class CheckBoxes<T> extends InputElement<T[]> {
|
|||
|
||||
}
|
||||
|
||||
return `<form id='${this.id}-form'>${body}</form>`;
|
||||
return `<form id='${this.id}'>${body}</form>`;
|
||||
}
|
||||
|
||||
protected InnerUpdate(htmlElement: HTMLElement) {
|
||||
super.InnerUpdate(htmlElement);
|
||||
const self = this;
|
||||
|
||||
|
||||
for (let i = 0; i < this._elements.length; i++) {
|
||||
const el = document.getElementById(this.IdFor(i));
|
||||
const inputEl = this._elements[i];
|
||||
{
|
||||
|
||||
const v = inputEl.GetValue().data;
|
||||
const index = self.value.data.indexOf(v);
|
||||
if(index >= 0){
|
||||
// @ts-ignore
|
||||
el.checked = true;
|
||||
for (const t of this.value.data ?? []) {
|
||||
if(t === undefined){
|
||||
continue;
|
||||
}
|
||||
let isValid = inputEl.IsValid(t);
|
||||
// @ts-ignore
|
||||
el.checked = isValid;
|
||||
if(isValid){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
el.onchange = e => {
|
||||
const v = inputEl.GetValue().data;
|
||||
const index = self.value.data.indexOf(v);
|
||||
|
|
53
UI/Input/InputElementMap.ts
Normal file
53
UI/Input/InputElementMap.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import {InputElement} from "./InputElement";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
|
||||
|
||||
export default class InputElementMap<T, X> extends InputElement<X> {
|
||||
|
||||
private readonly _inputElement: InputElement<T>;
|
||||
private isSame: (x0: X, x1: X) => boolean;
|
||||
private readonly fromX: (x: X) => T;
|
||||
private readonly toX: (t: T) => X;
|
||||
private readonly _value: UIEventSource<X>;
|
||||
|
||||
constructor(inputElement: InputElement<T>,
|
||||
isSame: (x0: X, x1: X) => boolean,
|
||||
toX: (t: T) => X,
|
||||
fromX: (x: X) => T
|
||||
) {
|
||||
super();
|
||||
this.isSame = isSame;
|
||||
this.fromX = fromX;
|
||||
this.toX = toX;
|
||||
this._inputElement = inputElement;
|
||||
this.IsSelected = inputElement.IsSelected;
|
||||
const self = this;
|
||||
this._value = inputElement.GetValue().map(
|
||||
(t => {
|
||||
const currentX = self.GetValue()?.data;
|
||||
const newX = toX(t);
|
||||
if (isSame(currentX, newX)) {
|
||||
return currentX;
|
||||
}
|
||||
return newX;
|
||||
}), [], x => {
|
||||
const newT = fromX(x);
|
||||
return newT;
|
||||
});
|
||||
}
|
||||
|
||||
GetValue(): UIEventSource<X> {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
return this._inputElement.InnerRender();
|
||||
}
|
||||
|
||||
IsSelected: UIEventSource<boolean>;
|
||||
|
||||
IsValid(x: X): boolean {
|
||||
return this._inputElement.IsValid(this.fromX(x));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,22 +1,23 @@
|
|||
import {UIElement} from "../UI/UIElement";
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import {And, Tag, TagsFilter, TagUtils} from "../Logic/Tags";
|
||||
import {FixedUiElement} from "../UI/Base/FixedUiElement";
|
||||
import {SaveButton} from "../UI/SaveButton";
|
||||
import {VariableUiElement} from "../UI/Base/VariableUIElement";
|
||||
import {TagDependantUIElement} from "./UIElementConstructor";
|
||||
import {TextField, ValidatedTextField} from "../UI/Input/TextField";
|
||||
import {InputElement} from "../UI/Input/InputElement";
|
||||
import {InputElementWrapper} from "../UI/Input/InputElementWrapper";
|
||||
import {FixedInputElement} from "../UI/Input/FixedInputElement";
|
||||
import {RadioButton} from "../UI/Input/RadioButton";
|
||||
import Translations from "../UI/i18n/Translations";
|
||||
import Locale from "../UI/i18n/Locale";
|
||||
import {State} from "../State";
|
||||
import {TagRenderingOptions} from "./TagRenderingOptions";
|
||||
import Translation from "../UI/i18n/Translation";
|
||||
import Combine from "../UI/Base/Combine";
|
||||
|
||||
import {TagDependantUIElement} from "../Customizations/UIElementConstructor";
|
||||
import {UIElement} from "./UIElement";
|
||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||
import InputElementMap from "./Input/InputElementMap";
|
||||
import {CheckBoxes} from "./Input/Checkboxes";
|
||||
import {InputElement} from "./Input/InputElement";
|
||||
import {SaveButton} from "./SaveButton";
|
||||
import {RadioButton} from "./Input/RadioButton";
|
||||
import {InputElementWrapper} from "./Input/InputElementWrapper";
|
||||
import {FixedInputElement} from "./Input/FixedInputElement";
|
||||
import {TextField, ValidatedTextField} from "./Input/TextField";
|
||||
import {TagRenderingOptions} from "../Customizations/TagRenderingOptions";
|
||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||
|
||||
export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||
|
||||
|
@ -61,7 +62,6 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
priority?: number
|
||||
|
||||
question?: string | Translation,
|
||||
|
||||
freeform?: {
|
||||
key: string,
|
||||
template: string | Translation,
|
||||
|
@ -70,6 +70,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
extraTags?: TagsFilter,
|
||||
},
|
||||
tagsPreprocessor?: ((tags: any) => any),
|
||||
multiAnswer?: boolean,
|
||||
mappings?: { k: TagsFilter, txt: string | Translation, priority?: number, substitute?: boolean, hideInAnswer?: boolean }[]
|
||||
}) {
|
||||
super(tags);
|
||||
|
@ -122,11 +123,10 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
// Prepare the actual input element -> pick an appropriate implementation
|
||||
|
||||
this._questionElement = this.InputElementFor(options) ??
|
||||
new FixedInputElement<TagsFilter>("<span class='alert'>No input possible</span>", new Tag("a","b"));
|
||||
new FixedInputElement<TagsFilter>("<span class='alert'>No input possible</span>", new Tag("a", "b"));
|
||||
const save = () => {
|
||||
const selection = self._questionElement.GetValue().data;
|
||||
console.log("Tagrendering: saving tags ", selection);
|
||||
|
@ -193,59 +193,93 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
|
||||
private InputElementFor(options: {
|
||||
freeform?: {
|
||||
key: string,
|
||||
key: string,
|
||||
template: string | Translation,
|
||||
renderTemplate: string | Translation,
|
||||
placeholder?: string | Translation,
|
||||
extraTags?: TagsFilter,
|
||||
},
|
||||
multiAnswer?: boolean,
|
||||
mappings?: { k: TagsFilter, txt: string | Translation, priority?: number, substitute?: boolean, hideInAnswer?: boolean }[]
|
||||
}):
|
||||
InputElement<TagsFilter> {
|
||||
|
||||
const elements = [];
|
||||
|
||||
let freeformElement = undefined;
|
||||
|
||||
let freeformElement = undefined;
|
||||
if (options.freeform !== undefined) {
|
||||
freeformElement = this.InputForFreeForm(options.freeform);
|
||||
}
|
||||
|
||||
if (options.mappings !== undefined) {
|
||||
|
||||
const previousTexts= [];
|
||||
for (const mapping of options.mappings) {
|
||||
if(mapping.k === null){
|
||||
continue;
|
||||
}
|
||||
if(mapping.hideInAnswer){
|
||||
continue;
|
||||
}
|
||||
previousTexts.push(this.ApplyTemplate(mapping.txt));
|
||||
|
||||
elements.push(this.InputElementForMapping(mapping, mapping.substitute));
|
||||
}
|
||||
|
||||
if (options.mappings === undefined || options.mappings.length === 0) {
|
||||
return freeformElement;
|
||||
}
|
||||
|
||||
if(freeformElement !== undefined) {
|
||||
|
||||
|
||||
const elements: InputElement<TagsFilter>[] = [];
|
||||
|
||||
for (const mapping of options.mappings) {
|
||||
if (mapping.k === null) {
|
||||
continue;
|
||||
}
|
||||
if (mapping.hideInAnswer) {
|
||||
continue;
|
||||
}
|
||||
elements.push(this.InputElementForMapping(mapping, mapping.substitute));
|
||||
}
|
||||
|
||||
if (freeformElement !== undefined) {
|
||||
elements.push(freeformElement);
|
||||
}
|
||||
|
||||
|
||||
if (options.multiAnswer) {
|
||||
const possibleTags = elements.map(el => el.GetValue().data);
|
||||
const checkBoxes = new CheckBoxes(elements);
|
||||
|
||||
// In order to let this work, we are cheating a lot here
|
||||
// First of all, it is very tricky to let the mapping stabilize
|
||||
// The selection gets added as list and needs to be flattened into a new 'and'
|
||||
// This new and causes the mapping to have a 'set value', which is unpacked to update the UI
|
||||
// AFter which the UI reupdates and reapplies the value
|
||||
// So, instead we opt to _always return the 'value' below which is statefully updated
|
||||
// But, then we still have to figure out when to update...
|
||||
// For this, we use the original inputElement
|
||||
// This is very dirty code, I know
|
||||
const value = new And([]);
|
||||
const inputElement = new InputElementMap(checkBoxes,
|
||||
(t0: And, t1: And) => {
|
||||
return t0?.isEquivalent(t1) ?? t0 === t1
|
||||
},
|
||||
(fromUI) => {
|
||||
if (fromUI === undefined) {
|
||||
value.and = [];
|
||||
return value;
|
||||
}
|
||||
const flattened = TagUtils.FlattenMultiAnswer(fromUI);
|
||||
value.and = flattened.and;
|
||||
return value;
|
||||
},
|
||||
(fromTags) => {
|
||||
return TagUtils.SplitMultiAnswer(fromTags, possibleTags, this._freeform?.key, this._freeform?.extraTags);
|
||||
}
|
||||
);
|
||||
|
||||
let previousSelectionCount = -1;
|
||||
checkBoxes.GetValue().addCallback(selected => {
|
||||
const newSelectionCount = selected.length;
|
||||
if (newSelectionCount != previousSelectionCount) {
|
||||
previousSelectionCount = newSelectionCount;
|
||||
inputElement.GetValue().ping();
|
||||
}
|
||||
});
|
||||
|
||||
return inputElement;
|
||||
|
||||
if (elements.length == 0) {
|
||||
return new FixedInputElement("This should not happen: no tag renderings defined", undefined);
|
||||
}
|
||||
if (elements.length == 1) {
|
||||
return elements[0];
|
||||
}
|
||||
|
||||
return new RadioButton(elements, false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private InputElementForMapping(mapping: { k: TagsFilter, txt: (string | Translation) }, substituteValues: boolean) {
|
||||
private InputElementForMapping(mapping: { k: TagsFilter, txt: (string | Translation) }, substituteValues: boolean): FixedInputElement<TagsFilter> {
|
||||
if (substituteValues) {
|
||||
|
||||
return new FixedInputElement(this.ApplyTemplate(mapping.txt),
|
||||
|
@ -253,7 +287,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
(t0, t1) => t0.isEquivalent(t1)
|
||||
);
|
||||
}
|
||||
return new FixedInputElement(this.ApplyTemplate(mapping.txt),mapping.k,
|
||||
return new FixedInputElement(this.ApplyTemplate(mapping.txt), mapping.k,
|
||||
(t0, t1) => t0.isEquivalent(t1));
|
||||
}
|
||||
|
||||
|
@ -308,6 +342,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
|
||||
const toString =
|
||||
(tag) => {
|
||||
console.log("Decoding ", tag, "in freeform text element")
|
||||
if (tag instanceof And) {
|
||||
for (const subtag of tag.and) {
|
||||
if(subtag instanceof Tag && subtag.key === freeform.key){
|
||||
|
@ -497,7 +532,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
return tr.InnerRender();
|
||||
}
|
||||
return tr.Subs(tags).InnerRender()
|
||||
}));
|
||||
})).ListenTo(Locale.language);
|
||||
}
|
||||
|
||||
|
|
@ -238,46 +238,38 @@
|
|||
"gl": "Esta bomba de ar admite as seguintes válvulas: {valves}"
|
||||
},
|
||||
"freeform": {
|
||||
"addExtraTags": [
|
||||
"#addExtraTags": [
|
||||
"fixme=Freeform 'valves'-tag used: possibly a wrong value"
|
||||
],
|
||||
"key": "valves"
|
||||
},
|
||||
"multiAnswer": true,
|
||||
"mappings": [
|
||||
{
|
||||
"if": "valves=sclaverand;schrader;dunlop",
|
||||
"then": {
|
||||
"en": "There is a default head, so Dunlop, Sclaverand and auto",
|
||||
"nl": "Er is een standaard aansluiting, die dus voor Dunlop, Sclaverand en auto's",
|
||||
"fr": "Il y a une valve par défaut, fonctionnant sur les valves Dunlop, Sclaverand et les valves de voitures",
|
||||
"gl": "Hai un cabezal predeterminado que é compatíbel con Dunlop, Sclaverand e automóbil"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "valves=sclaverand",
|
||||
"then": {
|
||||
"en": "Only Sclaverand (also known as Presta)",
|
||||
"nl": "Enkel Sclaverand (ook gekend als Presta)",
|
||||
"fr": "Seulement Sclaverand (aussi appelé Presta)",
|
||||
"gl": "Só Sclaverand (tamén coñecido como Presta)"
|
||||
"en": "Sclaverand (also known as Presta)",
|
||||
"nl": "Sclaverand (ook gekend als Presta)",
|
||||
"fr": "Sclaverand (aussi appelé Presta)",
|
||||
"gl": "Sclaverand (tamén coñecido como Presta)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "valves=dunlop",
|
||||
"then": {
|
||||
"en": "Only Dunlop",
|
||||
"nl": "Enkel Dunlop",
|
||||
"fr": "Seulement Dunlop",
|
||||
"gl": "Só Dunlop"
|
||||
"en": "Dunlop",
|
||||
"nl": "Dunlop",
|
||||
"fr": "Dunlop",
|
||||
"gl": "Dunlop"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": "valves=schrader",
|
||||
"then": {
|
||||
"en": "Only for cars",
|
||||
"nl": "Enkel voor auto's",
|
||||
"fr": "Seuelement les valves de voitures",
|
||||
"gl": "Só para automóbiles"
|
||||
"en": "Schrader (cars)",
|
||||
"nl": "Schrader (auto's)",
|
||||
"fr": "Schrader (les valves de voitures)",
|
||||
"gl": "Schrader (para automóbiles)"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -159,21 +159,13 @@
|
|||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": {
|
||||
"and": [
|
||||
"operator=Natuurpunt"
|
||||
]
|
||||
},
|
||||
"if": "operator=Natuurpunt",
|
||||
"then": {
|
||||
"nl": "Beheer door Natuurpunt"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"and": [
|
||||
"operator=Agentschap Natuur en Bos"
|
||||
]
|
||||
},
|
||||
"if": "operator=Agentschap Natuur en Bos",
|
||||
"then": {
|
||||
"nl": "Beheer door het Agentschap Natuur en Bos "
|
||||
}
|
||||
|
|
|
@ -2,14 +2,15 @@ import {UIElement} from "./UI/UIElement";
|
|||
// We HAVE to mark this while importing
|
||||
UIElement.runningFromConsole = true;
|
||||
|
||||
import {TagRendering} from "./Customizations/TagRendering";
|
||||
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
|
||||
import {FromJSON} from "./Customizations/JSON/FromJSON";
|
||||
import {Layout} from "./Customizations/Layout";
|
||||
import {readFileSync, writeFile, writeFileSync} from "fs";
|
||||
import Locale from "./UI/i18n/Locale";
|
||||
import svg2img from 'promise-svg2img';
|
||||
import Translation from "./UI/i18n/Translation";
|
||||
import Translations from "./UI/i18n/Translations";
|
||||
import {TagRendering} from "./UI/TagRendering";
|
||||
|
||||
TagRendering.injectFunction();
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {UIEventSource} from "./Logic/UIEventSource";
|
||||
import {GenerateEmpty} from "./UI/CustomGenerator/GenerateEmpty";
|
||||
import {TagRendering} from "./Customizations/TagRendering";
|
||||
import {TagRendering} from "./UI/TagRendering";
|
||||
import {LayoutConfigJson} from "./Customizations/JSON/LayoutConfigJson";
|
||||
import {OsmConnection} from "./Logic/Osm/OsmConnection";
|
||||
import CustomGeneratorPanel from "./UI/CustomGenerator/CustomGeneratorPanel";
|
||||
|
|
25
deploy.sh
25
deploy.sh
|
@ -20,27 +20,4 @@ fi
|
|||
|
||||
git add . && git commit -m "New mapcomplete version" && git push
|
||||
cd -
|
||||
# clean up the mess we made
|
||||
# rm *.js
|
||||
# rm Logic/*.js
|
||||
# rm Logic/*.js
|
||||
# rm Logic/*/*.js
|
||||
# rm Logic/*/*/*.js
|
||||
# rm UI/*.js
|
||||
# rm UI/*/*.js
|
||||
# rm UI/*/*/*.js
|
||||
# rm Customizations/*.js
|
||||
# rm Customizations/*/*.js
|
||||
# rm Customizations/*/*/*.js
|
||||
|
||||
rm *.webmanifest
|
||||
rm assets/generated/*
|
||||
|
||||
for f in ./*.html; do
|
||||
if [[ "$f" == "./index.html" ]] || [[ "$f" == "./land.html" ]] || [[ "$f" == "./test.html" ]] || [[ "$f" == "./preferences.html" ]] || [[ "$f" == "./customGenerator.html" ]]
|
||||
then
|
||||
echo "Not removing $f"
|
||||
else
|
||||
rm $f
|
||||
fi
|
||||
done
|
||||
./clean.sh
|
||||
|
|
3
index.ts
3
index.ts
|
@ -1,4 +1,3 @@
|
|||
import {TagRendering} from "./Customizations/TagRendering";
|
||||
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
|
||||
import {Layout} from "./Customizations/Layout";
|
||||
import {FixedUiElement} from "./UI/Base/FixedUiElement";
|
||||
|
@ -7,6 +6,7 @@ import {QueryParameters} from "./Logic/Web/QueryParameters";
|
|||
import {UIEventSource} from "./Logic/UIEventSource";
|
||||
import * as $ from "jquery";
|
||||
import {FromJSON} from "./Customizations/JSON/FromJSON";
|
||||
import {TagRendering} from "./UI/TagRendering";
|
||||
|
||||
TagRendering.injectFunction();
|
||||
|
||||
|
@ -63,7 +63,6 @@ let layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout.toLowerCase()] ?
|
|||
|
||||
const userLayoutParam = QueryParameters.GetQueryParameter("userlayout", "false");
|
||||
const layoutFromBase64 = decodeURIComponent(userLayoutParam.data);
|
||||
console.log(layoutFromBase64);
|
||||
if (layoutFromBase64.startsWith("wiki:")) {
|
||||
console.log("Downloading map theme from the wiki");
|
||||
const themeName = layoutFromBase64.substr("wiki:".length);
|
||||
|
|
Loading…
Reference in a new issue