forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			228 lines
		
	
	
	
		
			9.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
	
		
			9.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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<OsmTags>, 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<string, BaseUIElement>();
 | |
|                     mapping.set("language", new Translation(all_languages[ln]))
 | |
|                     rendered = new SubstitutedTranslation(
 | |
|                         new Translation({"*": single_render}, undefined),
 | |
|                         tagSource, state, mapping
 | |
|                     )
 | |
|                 } else {
 | |
| 
 | |
|                     let mapping = new Map<string, BaseUIElement>();
 | |
|                     const languagesList = new List(
 | |
|                         foundLanguages.map(ln => {
 | |
|                             let mappingLn = new Map<string, BaseUIElement>();
 | |
|                             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]));
 | |
|     }
 | |
| }
 | |
| 
 |