From 1f39ba9ab50f63e04be89f676aa82c04d5bb2f94 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 3 May 2023 00:57:15 +0200 Subject: [PATCH] Fix: fix validation of question input; remove some obsolete logging; stabilize output of generate:translations wrt newlines --- .../Sources/TouchesBboxFeatureSource.ts | 1 - Models/ThemeConfig/TagRenderingConfig.ts | 53 +- Models/ThemeViewState.ts | 6 +- UI/InputElement/ValidatedInput.svelte | 22 +- UI/InputElement/Validator.ts | 3 +- UI/InputElement/Validators/EmailValidator.ts | 5 +- UI/InputElement/Validators/PhoneValidator.ts | 30 +- UI/OpeningHours/OpeningHours.ts | 2 +- UI/OpeningHours/OpeningHoursVisualization.ts | 8 +- UI/Popup/MoveWizard.ts | 1 + UI/Popup/QuestionBox.ts | 2 + UI/Popup/TagRendering/FreeformInput.svelte | 7 +- .../TagRendering/TagRenderingQuestion.svelte | 24 +- UI/Popup/TagRenderingQuestion.ts | 1 + assets/fonts/Ubuntu-L-normal.js | 7 + assets/fonts/Ubuntu-M-normal.js | 7 + assets/fonts/UbuntuMono-B-bold.js | 7 + langs/en.json | 3 +- .../templates/MapComplete-flyer.back.svg | 908 +++++++ public/assets/templates/MapComplete-flyer.svg | 1492 ++++++++++ .../templates/MapComplete-poster-a2.svg | 2353 ++++++++++++++++ .../templates/MapComplete-poster-a3.svg | 2398 +++++++++++++++++ public/assets/templates/Ubuntu-L-normal.js | 7 + public/assets/templates/Ubuntu-M-normal.js | 7 + public/assets/templates/UbuntuMono-B-bold.js | 7 + scripts/generateTranslations.ts | 38 +- 26 files changed, 7328 insertions(+), 71 deletions(-) create mode 100644 assets/fonts/Ubuntu-L-normal.js create mode 100644 assets/fonts/Ubuntu-M-normal.js create mode 100644 assets/fonts/UbuntuMono-B-bold.js create mode 100644 public/assets/templates/MapComplete-flyer.back.svg create mode 100644 public/assets/templates/MapComplete-flyer.svg create mode 100644 public/assets/templates/MapComplete-poster-a2.svg create mode 100644 public/assets/templates/MapComplete-poster-a3.svg create mode 100644 public/assets/templates/Ubuntu-L-normal.js create mode 100644 public/assets/templates/Ubuntu-M-normal.js create mode 100644 public/assets/templates/UbuntuMono-B-bold.js diff --git a/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts b/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts index 31613fee0..ac16be8e8 100644 --- a/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts +++ b/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts @@ -15,7 +15,6 @@ export default class BBoxFeatureSource extends StaticFeatureSource { if (mustTouch.data === undefined) { return features } - console.log("UPdating touching bbox") const box = mustTouch.data return features.filter((feature) => { if (feature.geometry.type === "Point") { diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index ed486cad5..1f9bca6f5 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -1,22 +1,20 @@ -import { Translation, TypedTranslation } from "../../UI/i18n/Translation" -import { TagsFilter } from "../../Logic/Tags/TagsFilter" +import {Translation, TypedTranslation} from "../../UI/i18n/Translation" +import {TagsFilter} from "../../Logic/Tags/TagsFilter" import Translations from "../../UI/i18n/Translations" -import { TagUtils, UploadableTag } from "../../Logic/Tags/TagUtils" -import { And } from "../../Logic/Tags/And" -import { Utils } from "../../Utils" -import { Tag } from "../../Logic/Tags/Tag" +import {TagUtils, UploadableTag} from "../../Logic/Tags/TagUtils" +import {And} from "../../Logic/Tags/And" +import {Utils} from "../../Utils" +import {Tag} from "../../Logic/Tags/Tag" import BaseUIElement from "../../UI/BaseUIElement" import Combine from "../../UI/Base/Combine" import Title from "../../UI/Base/Title" import Link from "../../UI/Base/Link" import List from "../../UI/Base/List" -import { - MappingConfigJson, - QuestionableTagRenderingConfigJson, -} from "./Json/QuestionableTagRenderingConfigJson" -import { FixedUiElement } from "../../UI/Base/FixedUiElement" -import { Paragraph } from "../../UI/Base/Paragraph" +import {MappingConfigJson, QuestionableTagRenderingConfigJson,} from "./Json/QuestionableTagRenderingConfigJson" +import {FixedUiElement} from "../../UI/Base/FixedUiElement" +import {Paragraph} from "../../UI/Base/Paragraph" import Svg from "../../Svg" +import Validators, {ValidatorType} from "../../UI/InputElement/Validators"; export interface Mapping { readonly if: UploadableTag @@ -623,13 +621,19 @@ export default class TagRenderingConfig { * * @param singleSelectedMapping (Only used if multiAnswer == false): the single mapping to apply. Use (mappings.length) for the freeform * @param multiSelectedMapping (Only used if multiAnswer == true): all the mappings that must be applied. Set multiSelectedMapping[mappings.length] to use the freeform as well + * @param currentProperties: The current properties of the object for which the question should be answered */ public constructChangeSpecification( freeformValue: string | undefined, singleSelectedMapping: number, - multiSelectedMapping: boolean[] | undefined + multiSelectedMapping: boolean[] | undefined, + currentProperties: Record ): UploadableTag { freeformValue = freeformValue?.trim() + const validator = Validators.get( this.freeform?.type) + if(validator && freeformValue){ + freeformValue = validator.reformat(freeformValue,() => currentProperties["_country"]) + } if (freeformValue === "") { freeformValue = undefined } @@ -666,7 +670,7 @@ export default class TagRenderingConfig { .filter((_, i) => !multiSelectedMapping[i]) .map((m) => m.ifnot) - if (multiSelectedMapping.at(-1)) { + if (multiSelectedMapping.at(-1) && this.freeform) { // The freeform value was selected as well selectedMappings.push( new And([ @@ -677,22 +681,29 @@ export default class TagRenderingConfig { } return TagUtils.FlattenMultiAnswer([...selectedMappings, ...unselectedMappings]) } else { - if (singleSelectedMapping === undefined) { - return undefined - } - if (singleSelectedMapping === this.mappings.length) { - if (freeformValue === undefined) { - return undefined + // Is at least one mapping shown in the answer? + const someMappingIsShown = this.mappings.some(m => { + if(typeof m.hideInAnswer === "boolean"){ + return !m.hideInAnswer } + const isHidden = m.hideInAnswer.matchesProperties(currentProperties) + return !isHidden + } ) + // If all mappings are hidden for the current tags, we can safely assume that we should use the freeform key + const useFreeform = freeformValue !== undefined && (singleSelectedMapping === this.mappings.length || !someMappingIsShown) + if (useFreeform) { return new And([ new Tag(this.freeform.key, freeformValue), ...(this.freeform.addExtraTags ?? []), ]) - } else { + } else if(singleSelectedMapping) { return new And([ this.mappings[singleSelectedMapping].if, ...(this.mappings[singleSelectedMapping].addExtraTags ?? []), ]) + }else{ + console.log("TagRenderingConfig.ConstructSpecification has a weird fallback for", {freeformValue, singleSelectedMapping, multiSelectedMapping, currentProperties}) + return undefined } } } diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index b739ebb03..f744e19fd 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -296,7 +296,8 @@ export default class ThemeViewState implements SpecialVisualizationState { ) this.initActors() - this.drawSpecialLayers(lastClick) + this.addLastClick(lastClick) + this.drawSpecialLayers() this.initHotkeys() this.miscSetup() console.log("State setup completed", this) @@ -424,10 +425,9 @@ export default class ThemeViewState implements SpecialVisualizationState { /** * Add the special layers to the map */ - private drawSpecialLayers(last_click: LastClickFeatureSource) { + private drawSpecialLayers() { type AddedByDefaultTypes = typeof Constants.added_by_default[number] const empty = [] - this.addLastClick(last_click) /** * A listing which maps the layerId onto the featureSource */ diff --git a/UI/InputElement/ValidatedInput.svelte b/UI/InputElement/ValidatedInput.svelte index 697e0f7da..69db3df57 100644 --- a/UI/InputElement/ValidatedInput.svelte +++ b/UI/InputElement/ValidatedInput.svelte @@ -6,29 +6,43 @@ import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"; import { Translation } from "../i18n/Translation"; import { createEventDispatcher, onDestroy } from "svelte"; + import {Validator} from "./Validator"; export let value: UIEventSource; // Internal state, only copied to 'value' so that no invalid values leak outside let _value = new UIEventSource(value.data ?? ""); onDestroy(value.addCallbackAndRunD(v => _value.setData(v ?? ""))); export let type: ValidatorType; - let validator = Validators.get(type); export let feedback: UIEventSource | undefined = undefined; + export let getCountry: () => string | undefined + let validator : Validator = Validators.get(type) + $: { + // The type changed -> reset some values + validator = Validators.get(type) + _value.setData("") + feedback = feedback?.setData(validator?.getFeedback(_value.data, getCountry)); + } + + onDestroy(value.addCallbackAndRun(v => { + if(v === undefined || v === ""){ + _value.setData("") + } + })) onDestroy(_value.addCallbackAndRun(v => { - if (validator.isValid(v)) { + if (validator.isValid(v, getCountry)) { feedback?.setData(undefined); value.setData(v); return; } value.setData(undefined); - feedback?.setData(validator.getFeedback(v)); + feedback?.setData(validator.getFeedback(v, getCountry)); })) if (validator === undefined) { throw "Not a valid type for a validator:" + type; } - const isValid = _value.map(v => validator.isValid(v)); + const isValid = _value.map(v => validator.isValid(v, getCountry)); let htmlElem: HTMLInputElement; diff --git a/UI/InputElement/Validator.ts b/UI/InputElement/Validator.ts index 5ad93b882..4736b28d3 100644 --- a/UI/InputElement/Validator.ts +++ b/UI/InputElement/Validator.ts @@ -44,9 +44,8 @@ export abstract class Validator { /** * Gets a piece of feedback. By default, validation. will be used, resulting in a generic 'not a valid '. * However, inheritors might overwrite this to give more specific feedback - * @param s */ - public getFeedback(s: string): Translation { + public getFeedback(s: string, requestCountry?: () => string): Translation { const tr = Translations.t.validation[this.name] if (tr !== undefined) { return tr["feedback"] diff --git a/UI/InputElement/Validators/EmailValidator.ts b/UI/InputElement/Validators/EmailValidator.ts index 04a4d82bd..066c73765 100644 --- a/UI/InputElement/Validators/EmailValidator.ts +++ b/UI/InputElement/Validators/EmailValidator.ts @@ -1,7 +1,8 @@ -import { Translation } from "../../i18n/Translation.js" +import {Translation} from "../../i18n/Translation.js" import Translations from "../../i18n/Translations.js" import * as emailValidatorLibrary from "email-validator" -import { Validator } from "../Validator" +import {Validator} from "../Validator" + export default class EmailValidator extends Validator { constructor() { super("email", "An email adress", "email") diff --git a/UI/InputElement/Validators/PhoneValidator.ts b/UI/InputElement/Validators/PhoneValidator.ts index 9ab3b183a..97aa0d758 100644 --- a/UI/InputElement/Validators/PhoneValidator.ts +++ b/UI/InputElement/Validators/PhoneValidator.ts @@ -1,12 +1,28 @@ -import { parsePhoneNumberFromString } from "libphonenumber-js" -import { Validator } from "../Validator" +import {parsePhoneNumberFromString} from "libphonenumber-js" +import {Validator} from "../Validator" +import {Translation} from "../../i18n/Translation"; +import Translations from "../../i18n/Translations"; export default class PhoneValidator extends Validator { constructor() { super("phone", "A phone number", "tel") } - isValid(str, country: () => string): boolean { + + getFeedback(s: string, requestCountry?: () => string): Translation { + const tr = Translations.t.validation.phone + const generic = tr.feedback + if(requestCountry){ + const country = requestCountry() + if(country){ + return tr.feedbackCountry.Subs({country}) + } + } + + return generic + } + + public isValid(str, country: () => string): boolean { if (str === undefined) { return false } @@ -20,13 +36,17 @@ export default class PhoneValidator extends Validator { return parsePhoneNumberFromString(str, countryCode)?.isValid() ?? false } - reformat = (str, country: () => string) => { + public reformat(str, country: () => string) { if (str.startsWith("tel:")) { str = str.substring("tel:".length) } + let countryCode = undefined + if(country){ + countryCode = country() + } return parsePhoneNumberFromString( str, - country()?.toUpperCase() as any + countryCode?.toUpperCase() as any )?.formatInternational() } } diff --git a/UI/OpeningHours/OpeningHours.ts b/UI/OpeningHours/OpeningHours.ts index a0ce4ec00..8bf3b195e 100644 --- a/UI/OpeningHours/OpeningHours.ts +++ b/UI/OpeningHours/OpeningHours.ts @@ -498,7 +498,7 @@ export class OH { lat: tags._lat, lon: tags._lon, address: { - country_code: tags._country.toLowerCase(), + country_code: tags._country?.toLowerCase(), state: undefined, }, }, diff --git a/UI/OpeningHours/OpeningHoursVisualization.ts b/UI/OpeningHours/OpeningHoursVisualization.ts index be9acd840..7a4273352 100644 --- a/UI/OpeningHours/OpeningHoursVisualization.ts +++ b/UI/OpeningHours/OpeningHoursVisualization.ts @@ -10,6 +10,7 @@ import { VariableUiElement } from "../Base/VariableUIElement" import Table from "../Base/Table" import { Translation } from "../i18n/Translation" import { OsmConnection } from "../../Logic/Osm/OsmConnection" +import Loading from "../Base/Loading"; export default class OpeningHoursVisualization extends Toggle { private static readonly weekdays: Translation[] = [ @@ -29,6 +30,7 @@ export default class OpeningHoursVisualization extends Toggle { prefix = "", postfix = "" ) { + const country = tags.map(tags => tags._country) const ohTable = new VariableUiElement( tags .map((tags) => { @@ -66,12 +68,12 @@ export default class OpeningHoursVisualization extends Toggle { ), ]) } - }) + }, [country]) ) super( ohTable, - Translations.t.general.opening_hours.loadingCountry.Clone(), + new Loading(Translations.t.general.opening_hours.loadingCountry), tags.map((tgs) => tgs._country !== undefined) ) } @@ -160,7 +162,7 @@ export default class OpeningHoursVisualization extends Toggle { const weekdayStyles = [] for (let i = 0; i < 7; i++) { const day = OpeningHoursVisualization.weekdays[i].Clone() - day.SetClass("w-full h-full block") + day.SetClass("w-full h-full flex") const rangesForDay = ranges[i].map((range) => OpeningHoursVisualization.CreateRangeElem( diff --git a/UI/Popup/MoveWizard.ts b/UI/Popup/MoveWizard.ts index 294529d7f..679fa1482 100644 --- a/UI/Popup/MoveWizard.ts +++ b/UI/Popup/MoveWizard.ts @@ -177,6 +177,7 @@ export default class MoveWizard extends Toggle { state.featureProperties.getStore(id).ping() currentStep.setData("moved") + state.mapProperties.location.setData(loc) }) const zoomInFurhter = t.zoomInFurther.SetClass("alert block m-6") return new Combine([ diff --git a/UI/Popup/QuestionBox.ts b/UI/Popup/QuestionBox.ts index 3118460cd..b41477a64 100644 --- a/UI/Popup/QuestionBox.ts +++ b/UI/Popup/QuestionBox.ts @@ -10,6 +10,8 @@ import Lazy from "../Base/Lazy" import { OsmServiceState } from "../../Logic/Osm/OsmConnection" /** + * @deprecated + * This element is getting stripped and is not used anymore * Generates all the questions, one by one */ export default class QuestionBox extends VariableUiElement { diff --git a/UI/Popup/TagRendering/FreeformInput.svelte b/UI/Popup/TagRendering/FreeformInput.svelte index 88df41b52..de0002c09 100644 --- a/UI/Popup/TagRendering/FreeformInput.svelte +++ b/UI/Popup/TagRendering/FreeformInput.svelte @@ -19,17 +19,20 @@ let dispatch = createEventDispatcher<{ "selected" }>(); onDestroy(value.addCallbackD(() => {dispatch("selected")})) + function getCountry() { + return tags.data["_country"] + }
{#if config.freeform.inline} - dispatch("selected")} + dispatch("selected")} type={config.freeform.type} {value}> {:else} - dispatch("selected")} + dispatch("selected")} type={config.freeform.type} {value}> {/if} diff --git a/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/UI/Popup/TagRendering/TagRenderingQuestion.svelte index d26468d66..a416a68e8 100644 --- a/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -16,6 +16,7 @@ import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"; import SpecialTranslation from "./SpecialTranslation.svelte"; import TagHint from "../TagHint.svelte"; + import Validators from "../../InputElement/Validators"; export let config: TagRenderingConfig; export let tags: UIEventSource>; @@ -34,9 +35,12 @@ let selectedMapping: number = undefined; let checkedMappings: boolean[]; $: { + // We received a new config -> reinit + console.log("Initing checkedMappings for", config) if (config.mappings?.length > 0 && (checkedMappings === undefined || checkedMappings?.length < config.mappings.length)) { checkedMappings = [...config.mappings.map(_ => false), false /*One element extra in case a freeform value is added*/]; } + freeformInput.setData(undefined) } let selectedTags: TagsFilter = undefined; @@ -54,9 +58,10 @@ $: { mappings = config.mappings?.filter(m => !mappingIsHidden(m)); try { - selectedTags = config?.constructChangeSpecification($freeformInput, selectedMapping, checkedMappings); + let freeformInputValue = $freeformInput + selectedTags = config?.constructChangeSpecification(freeformInputValue, selectedMapping, checkedMappings, tags.data); } catch (e) { - console.debug("Could not calculate changeSpecification:", e); + console.error("Could not calculate changeSpecification:", e); selectedTags = undefined; } } @@ -99,7 +104,7 @@ } ); freeformInput.setData(undefined); - selectedMapping = 0; + selectedMapping = undefined; selectedTags = undefined; change.CreateChangeDescriptions().then(changes => @@ -139,14 +144,14 @@ {#each config.mappings as mapping, i (mapping.then)} {#if !mappingIsHidden(mapping) } -