Simplify priority behaviour of PillsSelector, create LanguageChooser-element, update usages
This commit is contained in:
parent
efb54782ca
commit
84eee064b2
7 changed files with 385 additions and 162 deletions
45
UI/Popup/AllLanguagesSelector.ts
Normal file
45
UI/Popup/AllLanguagesSelector.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import {SearchablePillsSelector} from "../Input/SearchableMappingsSelector";
|
||||
import {Store} from "../../Logic/UIEventSource";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import * as all_languages from "../../assets/language_translations.json";
|
||||
import {Translation} from "../i18n/Translation";
|
||||
|
||||
export class AllLanguagesSelector extends SearchablePillsSelector <string> {
|
||||
|
||||
constructor(options?: {
|
||||
mode?: "select-many" | "select-one"
|
||||
currentCountry?: Store<string>,
|
||||
supportedLanguages?: Record<string, string> & { _meta?: { countries?: string[] } }
|
||||
}) {
|
||||
|
||||
const possibleValues: {
|
||||
show: BaseUIElement
|
||||
value: string
|
||||
mainTerm: Record<string, string>
|
||||
searchTerms?: Record<string, string[]>,
|
||||
hasPriority?: Store<boolean>
|
||||
}[] = []
|
||||
|
||||
const langs = options?.supportedLanguages ?? all_languages["default"] ?? all_languages
|
||||
for (const ln in langs) {
|
||||
let languageInfo: Record<string, string> & { _meta?: { countries: string[] } } = all_languages[ln]
|
||||
const countries = languageInfo._meta?.countries?.map(c => c.toLowerCase())
|
||||
languageInfo = {...languageInfo}
|
||||
delete languageInfo._meta
|
||||
const term = {
|
||||
show: new Translation(languageInfo),
|
||||
value: ln,
|
||||
mainTerm: languageInfo,
|
||||
searchTerms: {"*": [ln]},
|
||||
hasPriority: countries === undefined ? undefined : options?.currentCountry?.map(country => countries?.indexOf(country.toLowerCase()) >= 0)
|
||||
}
|
||||
possibleValues.push(term)
|
||||
|
||||
}
|
||||
super(possibleValues,
|
||||
{
|
||||
mode: options?.mode ?? 'select-many'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -10,6 +10,7 @@ import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
|||
import { Unit } from "../../Models/Unit"
|
||||
import Lazy from "../Base/Lazy"
|
||||
import { FixedUiElement } from "../Base/FixedUiElement"
|
||||
import {EditButton} from "./SaveButton";
|
||||
|
||||
export default class EditableTagRendering extends Toggle {
|
||||
constructor(
|
||||
|
@ -70,16 +71,9 @@ export default class EditableTagRendering extends Toggle {
|
|||
// We have a question and editing is enabled
|
||||
const answerWithEditButton = new Combine([
|
||||
answer,
|
||||
new Toggle(
|
||||
new Combine([Svg.pencil_ui()])
|
||||
.SetClass("block relative h-10 w-10 p-2 float-right")
|
||||
.SetStyle("border: 1px solid black; border-radius: 0.7em")
|
||||
.onClick(() => {
|
||||
editMode.setData(true)
|
||||
}),
|
||||
undefined,
|
||||
state.osmConnection.isLoggedIn
|
||||
),
|
||||
new EditButton(state.osmConnection,() => {
|
||||
editMode.setData(true)
|
||||
}),
|
||||
]).SetClass("flex justify-between w-full")
|
||||
|
||||
const question = new Lazy(
|
||||
|
|
228
UI/Popup/LanguageElement.ts
Normal file
228
UI/Popup/LanguageElement.ts
Normal file
|
@ -0,0 +1,228 @@
|
|||
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]));
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,21 @@ import Translations from "../i18n/Translations"
|
|||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import Toggle from "../Input/Toggle"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import Combine from "../Base/Combine";
|
||||
import Svg from "../../Svg";
|
||||
|
||||
export class EditButton extends Toggle {
|
||||
constructor(osmConnection: OsmConnection, onClick: () => void) {
|
||||
super(
|
||||
new Combine([Svg.pencil_ui()])
|
||||
.SetClass("block relative h-10 w-10 p-2 float-right")
|
||||
.SetStyle("border: 1px solid black; border-radius: 0.7em")
|
||||
.onClick(onClick),
|
||||
undefined,
|
||||
osmConnection.isLoggedIn
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class SaveButton extends Toggle {
|
||||
constructor(
|
||||
|
|
|
@ -105,7 +105,7 @@ export default class TagRenderingQuestion extends Combine {
|
|||
TagUtils.FlattenAnd(inputElement.GetValue().data, tags.data)
|
||||
)
|
||||
if (selection) {
|
||||
;(state?.changes)
|
||||
(state?.changes)
|
||||
.applyAction(
|
||||
new ChangeTagAction(tags.data.id, selection, tags.data, {
|
||||
theme: state?.layoutToUse?.id ?? "unkown",
|
||||
|
@ -288,14 +288,16 @@ export default class TagRenderingQuestion extends Combine {
|
|||
value: number
|
||||
mainTerm: Record<string, string>
|
||||
searchTerms?: Record<string, string[]>
|
||||
original: Mapping
|
||||
original: Mapping,
|
||||
hasPriority?: Store<boolean>
|
||||
}[] {
|
||||
const values: {
|
||||
show: BaseUIElement
|
||||
value: number
|
||||
mainTerm: Record<string, string>
|
||||
searchTerms?: Record<string, string[]>
|
||||
original: Mapping
|
||||
original: Mapping,
|
||||
hasPriority?: Store<boolean>
|
||||
}[] = []
|
||||
const addIcons = applicableMappings.some((m) => m.icon !== undefined)
|
||||
for (let i = 0; i < applicableMappings.length; i++) {
|
||||
|
@ -317,6 +319,7 @@ export default class TagRenderingQuestion extends Combine {
|
|||
mainTerm: tr.translations,
|
||||
searchTerms: mapping.searchTerms,
|
||||
original: mapping,
|
||||
hasPriority: tagsSource.map(tags => mapping.priorityIf?.matchesProperties(tags))
|
||||
})
|
||||
}
|
||||
return values
|
||||
|
@ -397,7 +400,7 @@ export default class TagRenderingQuestion extends Combine {
|
|||
const values = TagRenderingQuestion.MappingToPillValue(
|
||||
applicableMappings,
|
||||
tagsSource,
|
||||
state
|
||||
state,
|
||||
)
|
||||
|
||||
const searchValue: UIEventSource<string> =
|
||||
|
@ -411,47 +414,12 @@ export default class TagRenderingQuestion extends Combine {
|
|||
}
|
||||
const mode = configuration.multiAnswer ? "select-many" : "select-one"
|
||||
|
||||
const tooMuchElementsValue = new UIEventSource<number[]>([])
|
||||
|
||||
let priorityPresets: BaseUIElement = undefined
|
||||
const classes = "h-64 overflow-scroll"
|
||||
|
||||
if (applicableMappings.some((m) => m.priorityIf !== undefined)) {
|
||||
const priorityValues = tagsSource.map((tags) =>
|
||||
TagRenderingQuestion.MappingToPillValue(
|
||||
applicableMappings,
|
||||
tagsSource,
|
||||
state
|
||||
).filter((v) => v.original.priorityIf?.matchesProperties(tags))
|
||||
)
|
||||
priorityPresets = new VariableUiElement(
|
||||
priorityValues.map((priority) => {
|
||||
if (priority.length === 0) {
|
||||
return Translations.t.general.useSearch
|
||||
}
|
||||
return new Combine([
|
||||
Translations.t.general.useSearchForMore.Subs({
|
||||
total: applicableMappings.length,
|
||||
}),
|
||||
new SearchablePillsSelector(priority, {
|
||||
selectedElements: tooMuchElementsValue,
|
||||
hideSearchBar: true,
|
||||
mode,
|
||||
}),
|
||||
])
|
||||
.SetClass("flex flex-col items-center ")
|
||||
.SetClass(classes)
|
||||
})
|
||||
)
|
||||
}
|
||||
const presetSearch = new SearchablePillsSelector<number>(values, {
|
||||
selectIfSingle: true,
|
||||
mode,
|
||||
searchValue,
|
||||
onNoMatches: onEmpty?.SetClass(classes).SetClass("flex justify-center items-center"),
|
||||
searchAreaClass: classes,
|
||||
onManyElementsValue: tooMuchElementsValue,
|
||||
onManyElements: priorityPresets,
|
||||
searchAreaClass: classes
|
||||
})
|
||||
const fallbackTag = searchValue.map((s) => {
|
||||
if (s === undefined || ff?.key === undefined) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue