Add option to show text field inline with the rendering; add option to fill out a default value
This commit is contained in:
parent
29ea0ac925
commit
6e3c39e475
10 changed files with 123 additions and 23 deletions
|
@ -26,6 +26,8 @@ export default class TagRenderingConfig {
|
|||
readonly key: string,
|
||||
readonly type: string,
|
||||
readonly addExtraTags: TagsFilter[];
|
||||
readonly inline: boolean,
|
||||
readonly default?: string
|
||||
};
|
||||
|
||||
readonly multiAnswer: boolean;
|
||||
|
@ -73,6 +75,8 @@ export default class TagRenderingConfig {
|
|||
type: json.freeform.type ?? "string",
|
||||
addExtraTags: json.freeform.addExtraTags?.map((tg, i) =>
|
||||
FromJSON.Tag(tg, `${context}.extratag[${i}]`)) ?? [],
|
||||
inline: json.freeform.inline ?? false,
|
||||
default: json.freeform.default
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -46,7 +46,19 @@ export interface TagRenderingConfigJson {
|
|||
**/
|
||||
addExtraTags?: string[];
|
||||
|
||||
|
||||
/**
|
||||
* When set, influences the way a question is asked.
|
||||
* Instead of showing a full-widht text field, the text field will be shown within the rendering of the question.
|
||||
*
|
||||
* This combines badly with special input elements, as it'll distort the layout.
|
||||
*/
|
||||
inline?: boolean
|
||||
|
||||
/**
|
||||
* default value to enter if no previous tagging is present.
|
||||
* Normally undefined (aka do not enter anything)
|
||||
*/
|
||||
default?: string
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
35
UI/Input/InputElementWrapper.ts
Normal file
35
UI/Input/InputElementWrapper.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import {InputElement} from "./InputElement";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import {Translation} from "../i18n/Translation";
|
||||
import {SubstitutedTranslation} from "../SubstitutedTranslation";
|
||||
|
||||
export default class InputElementWrapper<T> extends InputElement<T> {
|
||||
public readonly IsSelected: UIEventSource<boolean>;
|
||||
private readonly _inputElement: InputElement<T>;
|
||||
private readonly _renderElement: BaseUIElement
|
||||
|
||||
constructor(inputElement: InputElement<T>, translation: Translation, key: string, tags: UIEventSource<any>) {
|
||||
super()
|
||||
this._inputElement = inputElement;
|
||||
this.IsSelected = inputElement.IsSelected
|
||||
const mapping = new Map<string, BaseUIElement>()
|
||||
|
||||
mapping.set(key, inputElement)
|
||||
|
||||
this._renderElement = new SubstitutedTranslation(translation, tags, mapping)
|
||||
}
|
||||
|
||||
GetValue(): UIEventSource<T> {
|
||||
return this._inputElement.GetValue();
|
||||
}
|
||||
|
||||
IsValid(t: T): boolean {
|
||||
return this._inputElement.IsValid(t);
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
return this._renderElement.ConstructElement();
|
||||
}
|
||||
|
||||
}
|
|
@ -36,11 +36,11 @@ export class TextField extends InputElement<string> {
|
|||
this.SetClass("form-text-field")
|
||||
let inputEl: HTMLElement
|
||||
if (options.htmlType === "area") {
|
||||
this.SetClass("w-full box-border max-w-full")
|
||||
const el = document.createElement("textarea")
|
||||
el.placeholder = placeholder
|
||||
el.rows = options.textAreaRows
|
||||
el.cols = 50
|
||||
el.style.cssText = "max-width: 100%; width: 100%; box-sizing: border-box"
|
||||
inputEl = el;
|
||||
} else {
|
||||
const el = document.createElement("input")
|
||||
|
|
|
@ -282,7 +282,7 @@ export default class ValidatedTextField {
|
|||
})
|
||||
)
|
||||
unitDropDown.GetValue().setData(unit.defaultDenom)
|
||||
unitDropDown.SetStyle("width: min-content")
|
||||
unitDropDown.SetClass("w-min")
|
||||
|
||||
input = new CombinedInputElement(
|
||||
input,
|
||||
|
|
|
@ -24,6 +24,7 @@ import {TagUtils} from "../../Logic/Tags/TagUtils";
|
|||
import BaseUIElement from "../BaseUIElement";
|
||||
import {DropDown} from "../Input/DropDown";
|
||||
import {Unit} from "../../Customizations/JSON/Denomination";
|
||||
import InputElementWrapper from "../Input/InputElementWrapper";
|
||||
|
||||
/**
|
||||
* Shows the question element.
|
||||
|
@ -128,7 +129,7 @@ export default class TagRenderingQuestion extends Combine {
|
|||
}
|
||||
return Utils.NoNull(configuration.mappings?.map((m,i) => excludeIndex === i ? undefined: m.ifnot))
|
||||
}
|
||||
const ff = TagRenderingQuestion.GenerateFreeform(configuration, applicableUnit, tagsSource.data);
|
||||
const ff = TagRenderingQuestion.GenerateFreeform(configuration, applicableUnit, tagsSource);
|
||||
const hasImages = mappings.filter(mapping => mapping.then.ExtractImages().length > 0).length > 0
|
||||
|
||||
if (mappings.length < 8 || configuration.multiAnswer || hasImages) {
|
||||
|
@ -289,7 +290,7 @@ export default class TagRenderingQuestion extends Combine {
|
|||
(t0, t1) => t1.isEquivalent(t0));
|
||||
}
|
||||
|
||||
private static GenerateFreeform(configuration: TagRenderingConfig, applicableUnit: Unit, tagsData: any): InputElement<TagsFilter> {
|
||||
private static GenerateFreeform(configuration: TagRenderingConfig, applicableUnit: Unit, tags: UIEventSource<any>): InputElement<TagsFilter> {
|
||||
const freeform = configuration.freeform;
|
||||
if (freeform === undefined) {
|
||||
return undefined;
|
||||
|
@ -328,7 +329,8 @@ export default class TagRenderingQuestion extends Combine {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
let input: InputElement<string> = ValidatedTextField.InputForType(configuration.freeform.type, {
|
||||
const tagsData = tags.data;
|
||||
const input: InputElement<string> = ValidatedTextField.InputForType(configuration.freeform.type, {
|
||||
isValid: (str) => (str.length <= 255),
|
||||
country: () => tagsData._country,
|
||||
location: [tagsData._lat, tagsData._lon],
|
||||
|
@ -336,12 +338,22 @@ export default class TagRenderingQuestion extends Combine {
|
|||
unit: applicableUnit
|
||||
});
|
||||
|
||||
input.GetValue().setData(tagsData[configuration.freeform.key]);
|
||||
input.GetValue().setData(tagsData[freeform.key] ?? freeform.default);
|
||||
|
||||
return new InputElementMap(
|
||||
let inputTagsFilter : InputElement<TagsFilter> = new InputElementMap(
|
||||
input, (a, b) => a === b || (a?.isEquivalent(b) ?? false),
|
||||
pickString, toString
|
||||
);
|
||||
|
||||
if(freeform.inline){
|
||||
|
||||
inputTagsFilter.SetClass("w-16-imp")
|
||||
inputTagsFilter = new InputElementWrapper(inputTagsFilter, configuration.render, freeform.key, tags)
|
||||
inputTagsFilter.SetClass("block")
|
||||
|
||||
}
|
||||
|
||||
return inputTagsFilter;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -379,6 +379,7 @@ export default class SpecialVisualizations {
|
|||
}
|
||||
|
||||
]
|
||||
|
||||
static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage();
|
||||
private static GenHelpMessage() {
|
||||
|
||||
|
|
|
@ -7,19 +7,43 @@ import SpecialVisualizations, {SpecialVisualization} from "./SpecialVisualizatio
|
|||
import {Utils} from "../Utils";
|
||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||
import Combine from "./Base/Combine";
|
||||
import BaseUIElement from "./BaseUIElement";
|
||||
|
||||
export class SubstitutedTranslation extends VariableUiElement {
|
||||
|
||||
public constructor(
|
||||
translation: Translation,
|
||||
tagsSource: UIEventSource<any>) {
|
||||
tagsSource: UIEventSource<any>,
|
||||
mapping: Map<string, BaseUIElement> = undefined) {
|
||||
|
||||
const extraMappings: SpecialVisualization[] = [];
|
||||
|
||||
mapping?.forEach((value, key) => {
|
||||
console.log("KV:", key, value)
|
||||
extraMappings.push(
|
||||
{
|
||||
funcName: key,
|
||||
constr: (() => {
|
||||
return value
|
||||
}),
|
||||
docs: "Dynamically injected input element",
|
||||
args: [],
|
||||
example: ""
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
super(
|
||||
Locale.language.map(language => {
|
||||
const txt = translation.textFor(language)
|
||||
let txt = translation.textFor(language);
|
||||
if (txt === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt).map(
|
||||
mapping?.forEach((_, key) => {
|
||||
txt = txt.replace(new RegExp(`{${key}}`, "g"), `{${key}()}`)
|
||||
})
|
||||
|
||||
return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt, extraMappings).map(
|
||||
proto => {
|
||||
if (proto.fixed !== undefined) {
|
||||
return new VariableUiElement(tagsSource.map(tags => Utils.SubstituteKeys(proto.fixed, tags)));
|
||||
|
@ -36,30 +60,35 @@ export class SubstitutedTranslation extends VariableUiElement {
|
|||
})
|
||||
)
|
||||
|
||||
|
||||
this.SetClass("w-full")
|
||||
}
|
||||
|
||||
|
||||
public static ExtractSpecialComponents(template: string): {
|
||||
fixed?: string, special?: {
|
||||
public static ExtractSpecialComponents(template: string, extraMappings: SpecialVisualization[] = []): {
|
||||
fixed?: string,
|
||||
special?: {
|
||||
func: SpecialVisualization,
|
||||
args: string[],
|
||||
style: string
|
||||
}
|
||||
}[] {
|
||||
|
||||
for (const knownSpecial of SpecialVisualizations.specialVisualizations) {
|
||||
if (extraMappings.length > 0) {
|
||||
|
||||
console.log("Extra mappings are", extraMappings)
|
||||
}
|
||||
|
||||
for (const knownSpecial of SpecialVisualizations.specialVisualizations.concat(extraMappings)) {
|
||||
|
||||
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
|
||||
const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`);
|
||||
if (matched != null) {
|
||||
|
||||
// We found a special component that should be brought to live
|
||||
const partBefore = SubstitutedTranslation.ExtractSpecialComponents(matched[1]);
|
||||
const partBefore = SubstitutedTranslation.ExtractSpecialComponents(matched[1], extraMappings);
|
||||
const argument = matched[2].trim();
|
||||
const style = matched[3]?.substring(1) ?? ""
|
||||
const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4]);
|
||||
const partAfter = SubstitutedTranslation.ExtractSpecialComponents(matched[4], extraMappings);
|
||||
const args = knownSpecial.args.map(arg => arg.defaultValue ?? "");
|
||||
if (argument.length > 0) {
|
||||
const realArgs = argument.split(",").map(str => str.trim());
|
||||
|
@ -73,11 +102,13 @@ export class SubstitutedTranslation extends VariableUiElement {
|
|||
}
|
||||
|
||||
let element;
|
||||
element = {special:{
|
||||
args: args,
|
||||
style: style,
|
||||
func: knownSpecial
|
||||
}}
|
||||
element = {
|
||||
special: {
|
||||
args: args,
|
||||
style: style,
|
||||
func: knownSpecial
|
||||
}
|
||||
}
|
||||
return [...partBefore, element, ...partAfter]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,7 +137,8 @@
|
|||
},
|
||||
"freeform": {
|
||||
"key": "capacity",
|
||||
"type": "nat"
|
||||
"type": "nat",
|
||||
"inline": true
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -105,6 +105,10 @@ a {
|
|||
width: min-content;
|
||||
}
|
||||
|
||||
.w-16-imp {
|
||||
width: 4rem !important;
|
||||
}
|
||||
|
||||
.space-between{
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue