diff --git a/Customizations/JSON/TagRenderingConfig.ts b/Customizations/JSON/TagRenderingConfig.ts index d7e55ed8d..d3d440493 100644 --- a/Customizations/JSON/TagRenderingConfig.ts +++ b/Customizations/JSON/TagRenderingConfig.ts @@ -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 } diff --git a/Customizations/JSON/TagRenderingConfigJson.ts b/Customizations/JSON/TagRenderingConfigJson.ts index 7dfaae82b..89871ec74 100644 --- a/Customizations/JSON/TagRenderingConfigJson.ts +++ b/Customizations/JSON/TagRenderingConfigJson.ts @@ -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 }, /** diff --git a/UI/Input/InputElementWrapper.ts b/UI/Input/InputElementWrapper.ts new file mode 100644 index 000000000..765a0d3b4 --- /dev/null +++ b/UI/Input/InputElementWrapper.ts @@ -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 extends InputElement { + public readonly IsSelected: UIEventSource; + private readonly _inputElement: InputElement; + private readonly _renderElement: BaseUIElement + + constructor(inputElement: InputElement, translation: Translation, key: string, tags: UIEventSource) { + super() + this._inputElement = inputElement; + this.IsSelected = inputElement.IsSelected + const mapping = new Map() + + mapping.set(key, inputElement) + + this._renderElement = new SubstitutedTranslation(translation, tags, mapping) + } + + GetValue(): UIEventSource { + return this._inputElement.GetValue(); + } + + IsValid(t: T): boolean { + return this._inputElement.IsValid(t); + } + + protected InnerConstructElement(): HTMLElement { + return this._renderElement.ConstructElement(); + } + +} \ No newline at end of file diff --git a/UI/Input/TextField.ts b/UI/Input/TextField.ts index 8f7d6ac44..da3073323 100644 --- a/UI/Input/TextField.ts +++ b/UI/Input/TextField.ts @@ -36,11 +36,11 @@ export class TextField extends InputElement { 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") diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index 8ea3fb948..2eeff8a54 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -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, diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index 52b2962d8..20c0b00d2 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -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 { + private static GenerateFreeform(configuration: TagRenderingConfig, applicableUnit: Unit, tags: UIEventSource): InputElement { const freeform = configuration.freeform; if (freeform === undefined) { return undefined; @@ -328,7 +329,8 @@ export default class TagRenderingQuestion extends Combine { return undefined; } - let input: InputElement = ValidatedTextField.InputForType(configuration.freeform.type, { + const tagsData = tags.data; + const input: InputElement = 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 = 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; } diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 346e71e47..2bbcbbb34 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -379,6 +379,7 @@ export default class SpecialVisualizations { } ] + static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage(); private static GenHelpMessage() { diff --git a/UI/SubstitutedTranslation.ts b/UI/SubstitutedTranslation.ts index 03c7eb074..43352aa5b 100644 --- a/UI/SubstitutedTranslation.ts +++ b/UI/SubstitutedTranslation.ts @@ -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) { + tagsSource: UIEventSource, + mapping: Map = 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] } } diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json index 896f97cea..496b36ae1 100644 --- a/assets/layers/public_bookcase/public_bookcase.json +++ b/assets/layers/public_bookcase/public_bookcase.json @@ -137,7 +137,8 @@ }, "freeform": { "key": "capacity", - "type": "nat" + "type": "nat", + "inline": true } }, { diff --git a/index.css b/index.css index 347c27a5d..0bd790f55 100644 --- a/index.css +++ b/index.css @@ -105,6 +105,10 @@ a { width: min-content; } +.w-16-imp { + width: 4rem !important; +} + .space-between{ justify-content: space-between; }