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 * as 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 = "{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 || item_render.indexOf("{language()") < 0) { throw "Error while calling language_chooser: render_single_language and render_list_item must contain '{language()}'" } 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])); } }