forked from MapComplete/MapComplete
Fix: fix validation of question input; remove some obsolete logging; stabilize output of generate:translations wrt newlines
This commit is contained in:
parent
755f905c36
commit
1f39ba9ab5
26 changed files with 7328 additions and 71 deletions
|
@ -15,7 +15,6 @@ export default class BBoxFeatureSource extends StaticFeatureSource {
|
||||||
if (mustTouch.data === undefined) {
|
if (mustTouch.data === undefined) {
|
||||||
return features
|
return features
|
||||||
}
|
}
|
||||||
console.log("UPdating touching bbox")
|
|
||||||
const box = mustTouch.data
|
const box = mustTouch.data
|
||||||
return features.filter((feature) => {
|
return features.filter((feature) => {
|
||||||
if (feature.geometry.type === "Point") {
|
if (feature.geometry.type === "Point") {
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
import { Translation, TypedTranslation } from "../../UI/i18n/Translation"
|
import {Translation, TypedTranslation} from "../../UI/i18n/Translation"
|
||||||
import { TagsFilter } from "../../Logic/Tags/TagsFilter"
|
import {TagsFilter} from "../../Logic/Tags/TagsFilter"
|
||||||
import Translations from "../../UI/i18n/Translations"
|
import Translations from "../../UI/i18n/Translations"
|
||||||
import { TagUtils, UploadableTag } from "../../Logic/Tags/TagUtils"
|
import {TagUtils, UploadableTag} from "../../Logic/Tags/TagUtils"
|
||||||
import { And } from "../../Logic/Tags/And"
|
import {And} from "../../Logic/Tags/And"
|
||||||
import { Utils } from "../../Utils"
|
import {Utils} from "../../Utils"
|
||||||
import { Tag } from "../../Logic/Tags/Tag"
|
import {Tag} from "../../Logic/Tags/Tag"
|
||||||
import BaseUIElement from "../../UI/BaseUIElement"
|
import BaseUIElement from "../../UI/BaseUIElement"
|
||||||
import Combine from "../../UI/Base/Combine"
|
import Combine from "../../UI/Base/Combine"
|
||||||
import Title from "../../UI/Base/Title"
|
import Title from "../../UI/Base/Title"
|
||||||
import Link from "../../UI/Base/Link"
|
import Link from "../../UI/Base/Link"
|
||||||
import List from "../../UI/Base/List"
|
import List from "../../UI/Base/List"
|
||||||
import {
|
import {MappingConfigJson, QuestionableTagRenderingConfigJson,} from "./Json/QuestionableTagRenderingConfigJson"
|
||||||
MappingConfigJson,
|
import {FixedUiElement} from "../../UI/Base/FixedUiElement"
|
||||||
QuestionableTagRenderingConfigJson,
|
import {Paragraph} from "../../UI/Base/Paragraph"
|
||||||
} from "./Json/QuestionableTagRenderingConfigJson"
|
|
||||||
import { FixedUiElement } from "../../UI/Base/FixedUiElement"
|
|
||||||
import { Paragraph } from "../../UI/Base/Paragraph"
|
|
||||||
import Svg from "../../Svg"
|
import Svg from "../../Svg"
|
||||||
|
import Validators, {ValidatorType} from "../../UI/InputElement/Validators";
|
||||||
|
|
||||||
export interface Mapping {
|
export interface Mapping {
|
||||||
readonly if: UploadableTag
|
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 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 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(
|
public constructChangeSpecification(
|
||||||
freeformValue: string | undefined,
|
freeformValue: string | undefined,
|
||||||
singleSelectedMapping: number,
|
singleSelectedMapping: number,
|
||||||
multiSelectedMapping: boolean[] | undefined
|
multiSelectedMapping: boolean[] | undefined,
|
||||||
|
currentProperties: Record<string, string>
|
||||||
): UploadableTag {
|
): UploadableTag {
|
||||||
freeformValue = freeformValue?.trim()
|
freeformValue = freeformValue?.trim()
|
||||||
|
const validator = Validators.get(<ValidatorType> this.freeform?.type)
|
||||||
|
if(validator && freeformValue){
|
||||||
|
freeformValue = validator.reformat(freeformValue,() => currentProperties["_country"])
|
||||||
|
}
|
||||||
if (freeformValue === "") {
|
if (freeformValue === "") {
|
||||||
freeformValue = undefined
|
freeformValue = undefined
|
||||||
}
|
}
|
||||||
|
@ -666,7 +670,7 @@ export default class TagRenderingConfig {
|
||||||
.filter((_, i) => !multiSelectedMapping[i])
|
.filter((_, i) => !multiSelectedMapping[i])
|
||||||
.map((m) => m.ifnot)
|
.map((m) => m.ifnot)
|
||||||
|
|
||||||
if (multiSelectedMapping.at(-1)) {
|
if (multiSelectedMapping.at(-1) && this.freeform) {
|
||||||
// The freeform value was selected as well
|
// The freeform value was selected as well
|
||||||
selectedMappings.push(
|
selectedMappings.push(
|
||||||
new And([
|
new And([
|
||||||
|
@ -677,22 +681,29 @@ export default class TagRenderingConfig {
|
||||||
}
|
}
|
||||||
return TagUtils.FlattenMultiAnswer([...selectedMappings, ...unselectedMappings])
|
return TagUtils.FlattenMultiAnswer([...selectedMappings, ...unselectedMappings])
|
||||||
} else {
|
} else {
|
||||||
if (singleSelectedMapping === undefined) {
|
// Is at least one mapping shown in the answer?
|
||||||
return undefined
|
const someMappingIsShown = this.mappings.some(m => {
|
||||||
}
|
if(typeof m.hideInAnswer === "boolean"){
|
||||||
if (singleSelectedMapping === this.mappings.length) {
|
return !m.hideInAnswer
|
||||||
if (freeformValue === undefined) {
|
|
||||||
return undefined
|
|
||||||
}
|
}
|
||||||
|
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([
|
return new And([
|
||||||
new Tag(this.freeform.key, freeformValue),
|
new Tag(this.freeform.key, freeformValue),
|
||||||
...(this.freeform.addExtraTags ?? []),
|
...(this.freeform.addExtraTags ?? []),
|
||||||
])
|
])
|
||||||
} else {
|
} else if(singleSelectedMapping) {
|
||||||
return new And([
|
return new And([
|
||||||
this.mappings[singleSelectedMapping].if,
|
this.mappings[singleSelectedMapping].if,
|
||||||
...(this.mappings[singleSelectedMapping].addExtraTags ?? []),
|
...(this.mappings[singleSelectedMapping].addExtraTags ?? []),
|
||||||
])
|
])
|
||||||
|
}else{
|
||||||
|
console.log("TagRenderingConfig.ConstructSpecification has a weird fallback for", {freeformValue, singleSelectedMapping, multiSelectedMapping, currentProperties})
|
||||||
|
return undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -296,7 +296,8 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
)
|
)
|
||||||
|
|
||||||
this.initActors()
|
this.initActors()
|
||||||
this.drawSpecialLayers(lastClick)
|
this.addLastClick(lastClick)
|
||||||
|
this.drawSpecialLayers()
|
||||||
this.initHotkeys()
|
this.initHotkeys()
|
||||||
this.miscSetup()
|
this.miscSetup()
|
||||||
console.log("State setup completed", this)
|
console.log("State setup completed", this)
|
||||||
|
@ -424,10 +425,9 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
/**
|
/**
|
||||||
* Add the special layers to the map
|
* Add the special layers to the map
|
||||||
*/
|
*/
|
||||||
private drawSpecialLayers(last_click: LastClickFeatureSource) {
|
private drawSpecialLayers() {
|
||||||
type AddedByDefaultTypes = typeof Constants.added_by_default[number]
|
type AddedByDefaultTypes = typeof Constants.added_by_default[number]
|
||||||
const empty = []
|
const empty = []
|
||||||
this.addLastClick(last_click)
|
|
||||||
/**
|
/**
|
||||||
* A listing which maps the layerId onto the featureSource
|
* A listing which maps the layerId onto the featureSource
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -6,29 +6,43 @@
|
||||||
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid";
|
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||||
import { Translation } from "../i18n/Translation";
|
import { Translation } from "../i18n/Translation";
|
||||||
import { createEventDispatcher, onDestroy } from "svelte";
|
import { createEventDispatcher, onDestroy } from "svelte";
|
||||||
|
import {Validator} from "./Validator";
|
||||||
|
|
||||||
export let value: UIEventSource<string>;
|
export let value: UIEventSource<string>;
|
||||||
// Internal state, only copied to 'value' so that no invalid values leak outside
|
// Internal state, only copied to 'value' so that no invalid values leak outside
|
||||||
let _value = new UIEventSource(value.data ?? "");
|
let _value = new UIEventSource(value.data ?? "");
|
||||||
onDestroy(value.addCallbackAndRunD(v => _value.setData(v ?? "")));
|
onDestroy(value.addCallbackAndRunD(v => _value.setData(v ?? "")));
|
||||||
export let type: ValidatorType;
|
export let type: ValidatorType;
|
||||||
let validator = Validators.get(type);
|
|
||||||
export let feedback: UIEventSource<Translation> | undefined = undefined;
|
export let feedback: UIEventSource<Translation> | 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 => {
|
onDestroy(_value.addCallbackAndRun(v => {
|
||||||
if (validator.isValid(v)) {
|
if (validator.isValid(v, getCountry)) {
|
||||||
feedback?.setData(undefined);
|
feedback?.setData(undefined);
|
||||||
value.setData(v);
|
value.setData(v);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
value.setData(undefined);
|
value.setData(undefined);
|
||||||
feedback?.setData(validator.getFeedback(v));
|
feedback?.setData(validator.getFeedback(v, getCountry));
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if (validator === undefined) {
|
if (validator === undefined) {
|
||||||
throw "Not a valid type for a validator:" + type;
|
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;
|
let htmlElem: HTMLInputElement;
|
||||||
|
|
||||||
|
|
|
@ -44,9 +44,8 @@ export abstract class Validator {
|
||||||
/**
|
/**
|
||||||
* Gets a piece of feedback. By default, validation.<type> will be used, resulting in a generic 'not a valid <type>'.
|
* Gets a piece of feedback. By default, validation.<type> will be used, resulting in a generic 'not a valid <type>'.
|
||||||
* However, inheritors might overwrite this to give more specific feedback
|
* 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]
|
const tr = Translations.t.validation[this.name]
|
||||||
if (tr !== undefined) {
|
if (tr !== undefined) {
|
||||||
return tr["feedback"]
|
return tr["feedback"]
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { Translation } from "../../i18n/Translation.js"
|
import {Translation} from "../../i18n/Translation.js"
|
||||||
import Translations from "../../i18n/Translations.js"
|
import Translations from "../../i18n/Translations.js"
|
||||||
import * as emailValidatorLibrary from "email-validator"
|
import * as emailValidatorLibrary from "email-validator"
|
||||||
import { Validator } from "../Validator"
|
import {Validator} from "../Validator"
|
||||||
|
|
||||||
export default class EmailValidator extends Validator {
|
export default class EmailValidator extends Validator {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("email", "An email adress", "email")
|
super("email", "An email adress", "email")
|
||||||
|
|
|
@ -1,12 +1,28 @@
|
||||||
import { parsePhoneNumberFromString } from "libphonenumber-js"
|
import {parsePhoneNumberFromString} from "libphonenumber-js"
|
||||||
import { Validator } from "../Validator"
|
import {Validator} from "../Validator"
|
||||||
|
import {Translation} from "../../i18n/Translation";
|
||||||
|
import Translations from "../../i18n/Translations";
|
||||||
|
|
||||||
export default class PhoneValidator extends Validator {
|
export default class PhoneValidator extends Validator {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("phone", "A phone number", "tel")
|
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) {
|
if (str === undefined) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -20,13 +36,17 @@ export default class PhoneValidator extends Validator {
|
||||||
return parsePhoneNumberFromString(str, countryCode)?.isValid() ?? false
|
return parsePhoneNumberFromString(str, countryCode)?.isValid() ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
reformat = (str, country: () => string) => {
|
public reformat(str, country: () => string) {
|
||||||
if (str.startsWith("tel:")) {
|
if (str.startsWith("tel:")) {
|
||||||
str = str.substring("tel:".length)
|
str = str.substring("tel:".length)
|
||||||
}
|
}
|
||||||
|
let countryCode = undefined
|
||||||
|
if(country){
|
||||||
|
countryCode = country()
|
||||||
|
}
|
||||||
return parsePhoneNumberFromString(
|
return parsePhoneNumberFromString(
|
||||||
str,
|
str,
|
||||||
country()?.toUpperCase() as any
|
countryCode?.toUpperCase() as any
|
||||||
)?.formatInternational()
|
)?.formatInternational()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -498,7 +498,7 @@ export class OH {
|
||||||
lat: tags._lat,
|
lat: tags._lat,
|
||||||
lon: tags._lon,
|
lon: tags._lon,
|
||||||
address: {
|
address: {
|
||||||
country_code: tags._country.toLowerCase(),
|
country_code: tags._country?.toLowerCase(),
|
||||||
state: undefined,
|
state: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { VariableUiElement } from "../Base/VariableUIElement"
|
||||||
import Table from "../Base/Table"
|
import Table from "../Base/Table"
|
||||||
import { Translation } from "../i18n/Translation"
|
import { Translation } from "../i18n/Translation"
|
||||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||||
|
import Loading from "../Base/Loading";
|
||||||
|
|
||||||
export default class OpeningHoursVisualization extends Toggle {
|
export default class OpeningHoursVisualization extends Toggle {
|
||||||
private static readonly weekdays: Translation[] = [
|
private static readonly weekdays: Translation[] = [
|
||||||
|
@ -29,6 +30,7 @@ export default class OpeningHoursVisualization extends Toggle {
|
||||||
prefix = "",
|
prefix = "",
|
||||||
postfix = ""
|
postfix = ""
|
||||||
) {
|
) {
|
||||||
|
const country = tags.map(tags => tags._country)
|
||||||
const ohTable = new VariableUiElement(
|
const ohTable = new VariableUiElement(
|
||||||
tags
|
tags
|
||||||
.map((tags) => {
|
.map((tags) => {
|
||||||
|
@ -66,12 +68,12 @@ export default class OpeningHoursVisualization extends Toggle {
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
})
|
}, [country])
|
||||||
)
|
)
|
||||||
|
|
||||||
super(
|
super(
|
||||||
ohTable,
|
ohTable,
|
||||||
Translations.t.general.opening_hours.loadingCountry.Clone(),
|
new Loading(Translations.t.general.opening_hours.loadingCountry),
|
||||||
tags.map((tgs) => tgs._country !== undefined)
|
tags.map((tgs) => tgs._country !== undefined)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -160,7 +162,7 @@ export default class OpeningHoursVisualization extends Toggle {
|
||||||
const weekdayStyles = []
|
const weekdayStyles = []
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
const day = OpeningHoursVisualization.weekdays[i].Clone()
|
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) =>
|
const rangesForDay = ranges[i].map((range) =>
|
||||||
OpeningHoursVisualization.CreateRangeElem(
|
OpeningHoursVisualization.CreateRangeElem(
|
||||||
|
|
|
@ -177,6 +177,7 @@ export default class MoveWizard extends Toggle {
|
||||||
|
|
||||||
state.featureProperties.getStore(id).ping()
|
state.featureProperties.getStore(id).ping()
|
||||||
currentStep.setData("moved")
|
currentStep.setData("moved")
|
||||||
|
state.mapProperties.location.setData(loc)
|
||||||
})
|
})
|
||||||
const zoomInFurhter = t.zoomInFurther.SetClass("alert block m-6")
|
const zoomInFurhter = t.zoomInFurther.SetClass("alert block m-6")
|
||||||
return new Combine([
|
return new Combine([
|
||||||
|
|
|
@ -10,6 +10,8 @@ import Lazy from "../Base/Lazy"
|
||||||
import { OsmServiceState } from "../../Logic/Osm/OsmConnection"
|
import { OsmServiceState } from "../../Logic/Osm/OsmConnection"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* This element is getting stripped and is not used anymore
|
||||||
* Generates all the questions, one by one
|
* Generates all the questions, one by one
|
||||||
*/
|
*/
|
||||||
export default class QuestionBox extends VariableUiElement {
|
export default class QuestionBox extends VariableUiElement {
|
||||||
|
|
|
@ -19,17 +19,20 @@
|
||||||
|
|
||||||
let dispatch = createEventDispatcher<{ "selected" }>();
|
let dispatch = createEventDispatcher<{ "selected" }>();
|
||||||
onDestroy(value.addCallbackD(() => {dispatch("selected")}))
|
onDestroy(value.addCallbackD(() => {dispatch("selected")}))
|
||||||
|
function getCountry() {
|
||||||
|
return tags.data["_country"]
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="inline-flex flex-col">
|
<div class="inline-flex flex-col">
|
||||||
|
|
||||||
{#if config.freeform.inline}
|
{#if config.freeform.inline}
|
||||||
<Inline key={config.freeform.key} {tags} template={config.render}>
|
<Inline key={config.freeform.key} {tags} template={config.render}>
|
||||||
<ValidatedInput {feedback} on:selected={() => dispatch("selected")}
|
<ValidatedInput {feedback} {getCountry} on:selected={() => dispatch("selected")}
|
||||||
type={config.freeform.type} {value}></ValidatedInput>
|
type={config.freeform.type} {value}></ValidatedInput>
|
||||||
</Inline>
|
</Inline>
|
||||||
{:else}
|
{:else}
|
||||||
<ValidatedInput {feedback} on:selected={() => dispatch("selected")}
|
<ValidatedInput {feedback} {getCountry} on:selected={() => dispatch("selected")}
|
||||||
type={config.freeform.type} {value}></ValidatedInput>
|
type={config.freeform.type} {value}></ValidatedInput>
|
||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid";
|
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||||
import SpecialTranslation from "./SpecialTranslation.svelte";
|
import SpecialTranslation from "./SpecialTranslation.svelte";
|
||||||
import TagHint from "../TagHint.svelte";
|
import TagHint from "../TagHint.svelte";
|
||||||
|
import Validators from "../../InputElement/Validators";
|
||||||
|
|
||||||
export let config: TagRenderingConfig;
|
export let config: TagRenderingConfig;
|
||||||
export let tags: UIEventSource<Record<string, string>>;
|
export let tags: UIEventSource<Record<string, string>>;
|
||||||
|
@ -34,9 +35,12 @@
|
||||||
let selectedMapping: number = undefined;
|
let selectedMapping: number = undefined;
|
||||||
let checkedMappings: boolean[];
|
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)) {
|
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*/];
|
checkedMappings = [...config.mappings.map(_ => false), false /*One element extra in case a freeform value is added*/];
|
||||||
}
|
}
|
||||||
|
freeformInput.setData(undefined)
|
||||||
}
|
}
|
||||||
let selectedTags: TagsFilter = undefined;
|
let selectedTags: TagsFilter = undefined;
|
||||||
|
|
||||||
|
@ -54,9 +58,10 @@
|
||||||
$: {
|
$: {
|
||||||
mappings = config.mappings?.filter(m => !mappingIsHidden(m));
|
mappings = config.mappings?.filter(m => !mappingIsHidden(m));
|
||||||
try {
|
try {
|
||||||
selectedTags = config?.constructChangeSpecification($freeformInput, selectedMapping, checkedMappings);
|
let freeformInputValue = $freeformInput
|
||||||
|
selectedTags = config?.constructChangeSpecification(freeformInputValue, selectedMapping, checkedMappings, tags.data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.debug("Could not calculate changeSpecification:", e);
|
console.error("Could not calculate changeSpecification:", e);
|
||||||
selectedTags = undefined;
|
selectedTags = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +104,7 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
freeformInput.setData(undefined);
|
freeformInput.setData(undefined);
|
||||||
selectedMapping = 0;
|
selectedMapping = undefined;
|
||||||
selectedTags = undefined;
|
selectedTags = undefined;
|
||||||
|
|
||||||
change.CreateChangeDescriptions().then(changes =>
|
change.CreateChangeDescriptions().then(changes =>
|
||||||
|
@ -139,14 +144,14 @@
|
||||||
{#each config.mappings as mapping, i (mapping.then)}
|
{#each config.mappings as mapping, i (mapping.then)}
|
||||||
<!-- Even though we have a list of 'mappings' already, we still iterate over the list as to keep the original indices-->
|
<!-- Even though we have a list of 'mappings' already, we still iterate over the list as to keep the original indices-->
|
||||||
{#if !mappingIsHidden(mapping) }
|
{#if !mappingIsHidden(mapping) }
|
||||||
<label>
|
<label class="flex">
|
||||||
<input type="radio" bind:group={selectedMapping} name={"mappings-radio-"+config.id} value={i}>
|
<input type="radio" bind:group={selectedMapping} name={"mappings-radio-"+config.id} value={i}>
|
||||||
<TagRenderingMapping {mapping} {tags} {state} {selectedElement} {layer}></TagRenderingMapping>
|
<TagRenderingMapping {mapping} {tags} {state} {selectedElement} {layer}></TagRenderingMapping>
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{#if config.freeform?.key}
|
{#if config.freeform?.key}
|
||||||
<label>
|
<label class="flex">
|
||||||
<input type="radio" bind:group={selectedMapping} name={"mappings-radio-"+config.id}
|
<input type="radio" bind:group={selectedMapping} name={"mappings-radio-"+config.id}
|
||||||
value={config.mappings.length}>
|
value={config.mappings.length}>
|
||||||
<FreeformInput {config} {tags} feature={selectedElement} value={freeformInput}
|
<FreeformInput {config} {tags} feature={selectedElement} value={freeformInput}
|
||||||
|
@ -159,13 +164,14 @@
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
{#each config.mappings as mapping, i (mapping.then)}
|
{#each config.mappings as mapping, i (mapping.then)}
|
||||||
{#if !mappingIsHidden(mapping)}
|
{#if !mappingIsHidden(mapping)}
|
||||||
<label>
|
<label class="flex">
|
||||||
<input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+i} bind:checked={checkedMappings[i]}>
|
<input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+i} bind:checked={checkedMappings[i]}>
|
||||||
<TagRenderingMapping {mapping} {tags} {state} {selectedElement}></TagRenderingMapping>
|
<TagRenderingMapping {mapping} {tags} {state} {selectedElement}></TagRenderingMapping>
|
||||||
</label>{/if}
|
</label>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{#if config.freeform?.key}
|
{#if config.freeform?.key}
|
||||||
<label>
|
<label class="flex">
|
||||||
<input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+config.mappings.length}
|
<input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+config.mappings.length}
|
||||||
bind:checked={checkedMappings[config.mappings.length]}>
|
bind:checked={checkedMappings[config.mappings.length]}>
|
||||||
<FreeformInput {config} {tags} feature={selectedElement} value={freeformInput}
|
<FreeformInput {config} {tags} feature={selectedElement} value={freeformInput}
|
||||||
|
@ -184,7 +190,7 @@
|
||||||
<Tr t={Translations.t.general.save}></Tr>
|
<Tr t={Translations.t.general.save}></Tr>
|
||||||
</button>
|
</button>
|
||||||
{:else }
|
{:else }
|
||||||
<div class="w-6 h-6">
|
<div class="inline-flex w-6 h-6">
|
||||||
<!-- Invalid value; show an inactive button or something like that-->
|
<!-- Invalid value; show an inactive button or something like that-->
|
||||||
<ExclamationIcon/>
|
<ExclamationIcon/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { SearchablePillsSelector } from "../Input/SearchableMappingsSelector"
|
||||||
import { OsmTags } from "../../Models/OsmFeature"
|
import { OsmTags } from "../../Models/OsmFeature"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated: getting stripped and getting ported
|
||||||
* Shows the question element.
|
* Shows the question element.
|
||||||
* Note that the value _migh_ already be known, e.g. when selected or when changing the value
|
* Note that the value _migh_ already be known, e.g. when selected or when changing the value
|
||||||
*/
|
*/
|
||||||
|
|
7
assets/fonts/Ubuntu-L-normal.js
Normal file
7
assets/fonts/Ubuntu-L-normal.js
Normal file
File diff suppressed because one or more lines are too long
7
assets/fonts/Ubuntu-M-normal.js
Normal file
7
assets/fonts/Ubuntu-M-normal.js
Normal file
File diff suppressed because one or more lines are too long
7
assets/fonts/UbuntuMono-B-bold.js
Normal file
7
assets/fonts/UbuntuMono-B-bold.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -1006,7 +1006,8 @@
|
||||||
},
|
},
|
||||||
"phone": {
|
"phone": {
|
||||||
"description": "a phone number",
|
"description": "a phone number",
|
||||||
"feedback": "This is not a valid phone number"
|
"feedback": "This is not a valid phone number",
|
||||||
|
"feedbackCountry": "This is not a valid phone number (for country {country})"
|
||||||
},
|
},
|
||||||
"pnat": {
|
"pnat": {
|
||||||
"description": "a positive, whole number",
|
"description": "a positive, whole number",
|
||||||
|
|
908
public/assets/templates/MapComplete-flyer.back.svg
Normal file
908
public/assets/templates/MapComplete-flyer.back.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 56 KiB |
1492
public/assets/templates/MapComplete-flyer.svg
Normal file
1492
public/assets/templates/MapComplete-flyer.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 1.6 MiB |
2353
public/assets/templates/MapComplete-poster-a2.svg
Normal file
2353
public/assets/templates/MapComplete-poster-a2.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 1.5 MiB |
2398
public/assets/templates/MapComplete-poster-a3.svg
Normal file
2398
public/assets/templates/MapComplete-poster-a3.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 1.5 MiB |
7
public/assets/templates/Ubuntu-L-normal.js
Normal file
7
public/assets/templates/Ubuntu-L-normal.js
Normal file
File diff suppressed because one or more lines are too long
7
public/assets/templates/Ubuntu-M-normal.js
Normal file
7
public/assets/templates/Ubuntu-M-normal.js
Normal file
File diff suppressed because one or more lines are too long
7
public/assets/templates/UbuntuMono-B-bold.js
Normal file
7
public/assets/templates/UbuntuMono-B-bold.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
||||||
import * as fs from "fs"
|
import * as fs from "fs"
|
||||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"
|
import {existsSync, mkdirSync, readFileSync, writeFileSync} from "fs"
|
||||||
import { Utils } from "../Utils"
|
import {Utils} from "../Utils"
|
||||||
import ScriptUtils from "./ScriptUtils"
|
import ScriptUtils from "./ScriptUtils"
|
||||||
|
|
||||||
const knownLanguages = ["en", "nl", "de", "fr", "es", "gl", "ca"]
|
const knownLanguages = ["en", "nl", "de", "fr", "es", "gl", "ca"]
|
||||||
|
@ -12,7 +12,7 @@ class TranslationPart {
|
||||||
const files = ScriptUtils.readDirRecSync(path, 1).filter((file) => file.endsWith(".json"))
|
const files = ScriptUtils.readDirRecSync(path, 1).filter((file) => file.endsWith(".json"))
|
||||||
const rootTranslation = new TranslationPart()
|
const rootTranslation = new TranslationPart()
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const content = JSON.parse(readFileSync(file, { encoding: "utf8" }))
|
const content = JSON.parse(readFileSync(file, {encoding: "utf8"}))
|
||||||
rootTranslation.addTranslation(file.substr(0, file.length - ".json".length), content)
|
rootTranslation.addTranslation(file.substr(0, file.length - ".json".length), content)
|
||||||
}
|
}
|
||||||
return rootTranslation
|
return rootTranslation
|
||||||
|
@ -52,10 +52,10 @@ class TranslationPart {
|
||||||
if (typeof v != "string") {
|
if (typeof v != "string") {
|
||||||
console.error(
|
console.error(
|
||||||
`Non-string object at ${context} in translation while trying to add the translation ` +
|
`Non-string object at ${context} in translation while trying to add the translation ` +
|
||||||
JSON.stringify(v) +
|
JSON.stringify(v) +
|
||||||
` to '` +
|
` to '` +
|
||||||
translationsKey +
|
translationsKey +
|
||||||
"'. The offending object which _should_ be a translation is: ",
|
"'. The offending object which _should_ be a translation is: ",
|
||||||
v,
|
v,
|
||||||
"\n\nThe current object is (only showing en):",
|
"\n\nThe current object is (only showing en):",
|
||||||
this.toJson(),
|
this.toJson(),
|
||||||
|
@ -94,9 +94,9 @@ class TranslationPart {
|
||||||
if (noTranslate !== undefined) {
|
if (noTranslate !== undefined) {
|
||||||
console.log(
|
console.log(
|
||||||
"Ignoring some translations for " +
|
"Ignoring some translations for " +
|
||||||
context +
|
context +
|
||||||
": " +
|
": " +
|
||||||
dontTranslateKeys.join(", ")
|
dontTranslateKeys.join(", ")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,14 +243,14 @@ class TranslationPart {
|
||||||
}
|
}
|
||||||
subparts = subparts.map((p) => p.split(/\(.*\)/)[0])
|
subparts = subparts.map((p) => p.split(/\(.*\)/)[0])
|
||||||
for (const subpart of subparts) {
|
for (const subpart of subparts) {
|
||||||
neededSubparts.add({ part: subpart, usedByLanguage: lang })
|
neededSubparts.add({part: subpart, usedByLanguage: lang})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Actually check for the needed sub-parts, e.g. that {key} isn't translated into {sleutel}
|
// Actually check for the needed sub-parts, e.g. that {key} isn't translated into {sleutel}
|
||||||
this.contents.forEach((value, key) => {
|
this.contents.forEach((value, key) => {
|
||||||
neededSubparts.forEach(({ part, usedByLanguage }) => {
|
neededSubparts.forEach(({part, usedByLanguage}) => {
|
||||||
if (typeof value !== "string") {
|
if (typeof value !== "string") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -444,6 +444,7 @@ function removeEmptyString(object: object) {
|
||||||
}
|
}
|
||||||
return object
|
return object
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats the specified file, helps to prevent merge conflicts
|
* Formats the specified file, helps to prevent merge conflicts
|
||||||
* */
|
* */
|
||||||
|
@ -659,7 +660,8 @@ function mergeLayerTranslations() {
|
||||||
const layerFiles = ScriptUtils.getLayerFiles()
|
const layerFiles = ScriptUtils.getLayerFiles()
|
||||||
for (const layerFile of layerFiles) {
|
for (const layerFile of layerFiles) {
|
||||||
mergeLayerTranslation(layerFile.parsed, layerFile.path, loadTranslationFilesFrom("layers"))
|
mergeLayerTranslation(layerFile.parsed, layerFile.path, loadTranslationFilesFrom("layers"))
|
||||||
writeFileSync(layerFile.path, JSON.stringify(layerFile.parsed, null, " ")) // layers use 2 spaces
|
const endsWithNewline = readFileSync(layerFile.path, {encoding: "utf8"})?.endsWith("\n") ?? true
|
||||||
|
writeFileSync(layerFile.path, JSON.stringify(layerFile.parsed, null, " ") + (endsWithNewline ? "\n" : "")) // layers use 2 spaces
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,7 +676,8 @@ function mergeThemeTranslations() {
|
||||||
|
|
||||||
const allTranslations = new TranslationPart()
|
const allTranslations = new TranslationPart()
|
||||||
allTranslations.recursiveAdd(config, themeFile.path)
|
allTranslations.recursiveAdd(config, themeFile.path)
|
||||||
writeFileSync(themeFile.path, JSON.stringify(config, null, " ")) // Themefiles use 2 spaces
|
const endsWithNewline = readFileSync(themeFile.path, {encoding: "utf8"})?.endsWith("\n") ?? true
|
||||||
|
writeFileSync(themeFile.path, JSON.stringify(config, null, " ") + (endsWithNewline ? "\n" : "")) // Themefiles use 2 spaces
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,7 +696,8 @@ if (!themeOverwritesWeblate) {
|
||||||
questionsPath,
|
questionsPath,
|
||||||
loadTranslationFilesFrom("shared-questions")
|
loadTranslationFilesFrom("shared-questions")
|
||||||
)
|
)
|
||||||
writeFileSync(questionsPath, JSON.stringify(questionsParsed, null, " "))
|
const endsWithNewline = readFileSync(questionsPath, {encoding: "utf8"}).endsWith("\n")
|
||||||
|
writeFileSync(questionsPath, JSON.stringify(questionsParsed, null, " ") + (endsWithNewline ? "\n" : ""))
|
||||||
} else {
|
} else {
|
||||||
console.log("Ignore weblate")
|
console.log("Ignore weblate")
|
||||||
}
|
}
|
||||||
|
@ -704,13 +708,13 @@ const l2 = generateTranslationsObjectFrom(
|
||||||
"themes"
|
"themes"
|
||||||
)
|
)
|
||||||
const l3 = generateTranslationsObjectFrom(
|
const l3 = generateTranslationsObjectFrom(
|
||||||
[{ path: questionsPath, parsed: questionsParsed }],
|
[{path: questionsPath, parsed: questionsParsed}],
|
||||||
"shared-questions"
|
"shared-questions"
|
||||||
)
|
)
|
||||||
|
|
||||||
const usedLanguages: string[] = Utils.Dedup(l1.concat(l2).concat(l3)).filter((v) => v !== "*")
|
const usedLanguages: string[] = Utils.Dedup(l1.concat(l2).concat(l3)).filter((v) => v !== "*")
|
||||||
usedLanguages.sort()
|
usedLanguages.sort()
|
||||||
fs.writeFileSync("./assets/used_languages.json", JSON.stringify({ languages: usedLanguages }))
|
fs.writeFileSync("./assets/used_languages.json", JSON.stringify({languages: usedLanguages}))
|
||||||
|
|
||||||
if (!themeOverwritesWeblate) {
|
if (!themeOverwritesWeblate) {
|
||||||
// Generates the core translations
|
// Generates the core translations
|
||||||
|
|
Loading…
Reference in a new issue