import { SpecialVisualization } from "../SpecialVisualization" import BaseUIElement from "../BaseUIElement" import { UIEventSource } from "../../Logic/UIEventSource" import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import { VariableUiElement } from "../Base/VariableUIElement" import { OsmTags } from "../../Models/OsmFeature" import all_languages from "../../assets/language_translations.json" import { Translation } from "../i18n/Translation" import Combine from "../Base/Combine" import Title from "../Base/Title" import Lazy from "../Base/Lazy" import { SubstitutedTranslation } from "../SubstitutedTranslation" import List from "../Base/List" import { AllLanguagesSelector } from "./AllLanguagesSelector" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import { And } from "../../Logic/Tags/And" import { Tag } from "../../Logic/Tags/Tag" import { EditButton, SaveButton } from "./SaveButton" import { FixedUiElement } from "../Base/FixedUiElement" import Translations from "../i18n/Translations" import Toggle from "../Input/Toggle" import { On } from "../../Models/ThemeConfig/Conversion/Conversion" export class LanguageElement implements SpecialVisualization { funcName: string = "language_chooser" docs: string | BaseUIElement = "The language element allows to show and pick all known (modern) languages. The key can be set" args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [ { name: "key", required: true, doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `language:nl=yes` if nl is picked ", }, { name: "question", required: true, doc: "What to ask if no questions are known", }, { name: "render_list_item", doc: "How a single language will be shown in the list of languages. Use `{language}` to indicate the language (which it must contain).", defaultValue: "{language()}", }, { name: "render_single_language", doc: "What will be shown if the feature only supports a single language", required: true, }, { name: "render_all", doc: "The full rendering. Use `{list}` to show where the list of languages must come. Optional if mode=single", defaultValue: "{list()}", }, { name: "no_known_languages", doc: "The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead", }, { name: "mode", doc: "If one or many languages can be selected. Should be 'multi' or 'single'", defaultValue: "multi", }, ] example: ` \`\`\`json {"special": "type": "language_chooser", "key": "school:language", "question": {"en": "What are the main (and administrative) languages spoken in this school?"}, "render_single_language": {"en": "{language()} is spoken on this school"}, "render_list_item": {"en": "{language()}"}, "render_all": {"en": "The following languages are spoken here:{list()}"} "mode":"multi" } \`\`\` ` constr( state: FeaturePipelineState, tagSource: UIEventSource, argument: string[] ): BaseUIElement { let [key, question, item_render, single_render, all_render, on_no_known_languages, mode] = argument if (mode === undefined || mode.length == 0) { mode = "multi" } if (item_render === undefined || item_render.trim() === "") { item_render = "{language()}" } if (all_render === undefined || all_render.length == 0) { all_render = "{list()}" } if (mode !== "single" && mode !== "multi") { throw ( "Error while calling language_chooser: mode must be either 'single' or 'multi' but it is " + mode ) } if (single_render.indexOf("{language()") < 0) { throw ( "Error while calling language_chooser: render_single_language must contain '{language()}' but it is " + single_render ) } if (item_render.indexOf("{language()") < 0) { throw ( "Error while calling language_chooser: render_list_item must contain '{language()}' but it is " + item_render ) } if (all_render.indexOf("{list()") < 0) { throw "Error while calling language_chooser: render_all must contain '{list()}'" } const prefix = key + ":" const foundLanguages = tagSource.map((tags) => { const foundLanguages: string[] = [] for (const k in tags) { const v = tags[k] if (v !== "yes") { continue } if (k.startsWith(prefix)) { foundLanguages.push(k.substring(prefix.length)) } } return foundLanguages }) const forceInputMode = new UIEventSource(false) const inputEl = new Lazy(() => { const selector = new AllLanguagesSelector({ mode: mode === "single" ? "select-one" : "select-many", currentCountry: tagSource.map((tgs) => tgs["_country"]), }) const cancelButton = Toggle.If(forceInputMode, () => Translations.t.general.cancel .Clone() .SetClass("btn btn-secondary") .onClick(() => forceInputMode.setData(false)) ) const saveButton = new SaveButton( selector.GetValue().map((lngs) => (lngs.length > 0 ? "true" : undefined)), state.osmConnection ).onClick(() => { const selectedLanguages = selector.GetValue().data const currentLanguages = foundLanguages.data const selection: Tag[] = selectedLanguages.map((ln) => new Tag(prefix + ln, "yes")) for (const currentLanguage of currentLanguages) { if (selectedLanguages.indexOf(currentLanguage) >= 0) { continue } // Erase language that is not spoken anymore selection.push(new Tag(prefix + currentLanguage, "")) } if (state.featureSwitchIsTesting.data) { for (const tag of selection) { tagSource.data[tag.key] = tag.value } tagSource.ping() } else { ;(state?.changes) .applyAction( new ChangeTagAction( tagSource.data.id, new And(selection), tagSource.data, { theme: state?.layoutToUse?.id ?? "unkown", changeType: "answer", } ) ) .then((_) => { console.log("Tagchanges applied") }) } forceInputMode.setData(false) }) return new Combine([ new Title(question), selector, new Combine([cancelButton, saveButton]).SetClass("flex justify-end"), ]).SetClass("flex flex-col question disable-links") }) const editButton = new EditButton(state.osmConnection, () => forceInputMode.setData(true)) return new VariableUiElement( foundLanguages.map( (foundLanguages) => { if (forceInputMode.data) { return inputEl } if (foundLanguages.length === 0) { // No languages found - we show the question and the input element if ( on_no_known_languages !== undefined && on_no_known_languages.length > 0 ) { return new Combine([on_no_known_languages, editButton]).SetClass( "flex justify-end" ) } return inputEl } let rendered: BaseUIElement if (foundLanguages.length === 1) { const ln = foundLanguages[0] let mapping = new Map() mapping.set("language", new Translation(all_languages[ln])) rendered = new SubstitutedTranslation( new Translation({ "*": single_render }, undefined), tagSource, state, mapping ) } else { let mapping = new Map() const languagesList = new List( foundLanguages.map((ln) => { let mappingLn = new Map() mappingLn.set("language", new Translation(all_languages[ln])) return new SubstitutedTranslation( new Translation({ "*": item_render }, undefined), tagSource, state, mappingLn ) }) ) mapping.set("list", languagesList) rendered = new SubstitutedTranslation( new Translation({ "*": all_render }, undefined), tagSource, state, mapping ) } return new Combine([rendered, editButton]).SetClass("flex justify-between") }, [forceInputMode] ) ) } }