Add translations to more parts of the import helper GUI

This commit is contained in:
pietervdvn 2022-04-14 01:32:04 +02:00
parent 8db80d879a
commit f7844d8b2b
8 changed files with 115 additions and 82 deletions

View file

@ -21,6 +21,7 @@ import {FixedUiElement} from "../Base/FixedUiElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import * as known_layers from "../../assets/generated/known_layers.json"
import {LayerConfigJson} from "../../Models/ThemeConfig/Json/LayerConfigJson";
import Translations from "../i18n/Translations";
/**
* Filters out points for which the import-note already exists, to prevent duplicates
@ -28,11 +29,11 @@ import {LayerConfigJson} from "../../Models/ThemeConfig/Json/LayerConfigJson";
export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, features: any[], theme: string }> {
public IsValid: UIEventSource<boolean>
public Value: UIEventSource<{ bbox: BBox, layer: LayerConfig, features: any[] , theme: string}>
public Value: UIEventSource<{ bbox: BBox, layer: LayerConfig, features: any[], theme: string }>
constructor(state, params: { bbox: BBox, layer: LayerConfig, features: any[], theme: string }) {
constructor(state, params: { bbox: BBox, layer: LayerConfig, features: any[], theme: string }) {
const t = Translations.t.importHelper.compareToAlreadyExistingNotes
const layerConfig = known_layers.layers.filter(l => l.id === params.layer.id)[0]
if (layerConfig === undefined) {
console.error("WEIRD: layer not found in the builtin layer overview")
@ -45,7 +46,7 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
layerDef: importLayer
}
const allNotesWithinBbox = new GeoJsonSource(flayer, params.bbox.padAbsolute(0.0001))
allNotesWithinBbox.features.map(f => MetaTagging.addMetatags(
f,
{
@ -63,7 +64,6 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
)
)
const alreadyOpenImportNotes = new FilteringFeatureSource(state, undefined, allNotesWithinBbox)
alreadyOpenImportNotes.features.addCallbackD(features => console.log("Loaded and filtered features are", features))
const map = Minimap.createMiniMap()
map.SetClass("w-full").SetStyle("height: 500px")
@ -99,43 +99,46 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
})
super([
new Title("Compare with already existing 'to-import'-notes"),
new Title(t.titleLong),
new VariableUiElement(
alreadyOpenImportNotes.features.map(notesWithImport => {
if(allNotesWithinBbox.state.data !== undefined && allNotesWithinBbox.state.data["error"] !== undefined){
return new FixedUiElement("Loading notes failed: "+allNotesWithinBbox.state.data["error"] )
if (allNotesWithinBbox.state.data !== undefined && allNotesWithinBbox.state.data["error"] !== undefined) {
t.loadingFailed.Subs(allNotesWithinBbox.state.data)
}
if (allNotesWithinBbox.features.data === undefined || allNotesWithinBbox.features.data.length === 0) {
return new Loading("Fetching notes from OSM")
return new Loading(t.loading)
}
if (notesWithImport.length === 0) {
return new FixedUiElement("No previous import notes found").SetClass("thanks")
return t.noPreviousNotesFound.SetClass("thanks")
}
return new Combine([
"The red elements on the next map are all the data points from your dataset. There are <b>"+params.features.length+"</b> elements in your dataset.",
t.mapExplanation.Subs(params.features),
map,
new VariableUiElement( partitionedImportPoints.map(({noNearby, hasNearby}) => {
if(noNearby.length === 0){
// Nothing can be imported
return new FixedUiElement("All of the proposed points have (or had) an import note already").SetClass("alert w-full block").SetStyle("padding: 0.5rem")
}
if(hasNearby.length === 0){
// All points can be imported
return new FixedUiElement("All of the proposed points have don't have a previous import note nearby").SetClass("thanks w-full block").SetStyle("padding: 0.5rem")
}
return new Combine([
new FixedUiElement(hasNearby.length+" points do have an already existing import note within "+maxDistance.data+" meter.").SetClass("alert"),
"These data points will <i>not</i> be imported and are shown as red dots on the map below",
comparisonMap.SetClass("w-full")
]).SetClass("w-full")
new VariableUiElement(partitionedImportPoints.map(({noNearby, hasNearby}) => {
if (noNearby.length === 0) {
// Nothing can be imported
return t.completelyImported.SetClass("alert w-full block").SetStyle("padding: 0.5rem")
}
if (hasNearby.length === 0) {
// All points can be imported
return t.nothingNearby.SetClass("thanks w-full block").SetStyle("padding: 0.5rem")
}
return new Combine([
t.someNearby.Subs({
hasNearby: hasNearby.length,
distance: maxDistance.data
}).SetClass("alert"),
t.wontBeImported,
comparisonMap.SetClass("w-full")
]).SetClass("w-full")
}))
]).SetClass("flex flex-col")
}, [allNotesWithinBbox.features, allNotesWithinBbox.state])

View file

@ -25,15 +25,15 @@ export class FlowPanelFactory<T> {
this._stepNames = stepNames;
}
public static start<TOut>(name: string | BaseUIElement, step: FlowStep<TOut>): FlowPanelFactory<TOut> {
return new FlowPanelFactory(step, [], [name])
public static start<TOut>(name:{title: BaseUIElement}, step: FlowStep<TOut>): FlowPanelFactory<TOut> {
return new FlowPanelFactory(step, [], [name.title])
}
public then<TOut>(name: string | BaseUIElement, construct: ((t: T) => FlowStep<TOut>)): FlowPanelFactory<TOut> {
public then<TOut>(name: string | {title: BaseUIElement}, construct: ((t: T) => FlowStep<TOut>)): FlowPanelFactory<TOut> {
return new FlowPanelFactory<TOut>(
this._initial,
this._steps.concat([construct]),
this._stepNames.concat([name])
this._stepNames.concat([name["title"] ?? name])
)
}

View file

@ -7,7 +7,7 @@ import MinimapImplementation from "../Base/MinimapImplementation";
import Translations from "../i18n/Translations";
import {FlowPanelFactory} from "./FlowStep";
import {RequestFile} from "./RequestFile";
import {PreviewPanel} from "./PreviewPanel";
import {PreviewAttributesPanel} from "./PreviewPanel";
import ConflationChecker from "./ConflationChecker";
import {AskMetadata} from "./AskMetadata";
import {ConfirmProcess} from "./ConfirmProcess";
@ -26,16 +26,16 @@ import SelectTheme from "./SelectTheme";
export default class ImportHelperGui extends LeftIndex {
constructor() {
const state = new UserRelatedState(undefined)
const t = Translations.t.importHelper;
const {flow, furthestStep, titles} =
FlowPanelFactory
.start("Introduction", new Introdution())
.then("Login", _ => new LoginToImport(state))
.then("Select file", _ => new RequestFile())
.then("Inspect attributes", geojson => new PreviewPanel(state, geojson))
.then("Inspect data", geojson => new MapPreview(state, geojson))
.then("Select theme", v => new SelectTheme(v))
.then("Compare with open notes", v => new CompareToAlreadyExistingNotes(state, v))
.start(t.introduction, new Introdution())
.then(t.login, _ => new LoginToImport(state))
.then(t.selectFile, _ => new RequestFile())
.then(t.previewAttributes, geojson => new PreviewAttributesPanel(state, geojson))
.then(t.mapPreview, geojson => new MapPreview(state, geojson))
.then(t.selectTheme, v => new SelectTheme(v))
.then(t.compareToAlreadyExistingNotes, v => new CompareToAlreadyExistingNotes(state, v))
.then("Compare with existing data", v => new ConflationChecker(state, v))
.then("License and community check", v => new ConfirmProcess(v))
.then("Metadata", (v) => new AskMetadata(v))

View file

@ -10,9 +10,9 @@ export default class Introdution extends Combine implements FlowStep<void> {
constructor() {
super([
new Title(Translations.t.importHelper.title),
Translations.t.importHelper.description,
Translations.t.importHelper.importFormat,
new Title(Translations.t.importHelper.introduction.title),
Translations.t.importHelper.introduction.description,
Translations.t.importHelper.introduction.importFormat,
]);
this.SetClass("flex flex-col")
}

View file

@ -21,7 +21,7 @@ export default class LoginToImport extends Combine implements FlowStep<UserRelat
private static readonly whitelist = [15015689];
constructor(state: UserRelatedState) {
const t = Translations.t.importHelper
const t = Translations.t.importHelper.login
const check = new CheckBoxes([new VariableUiElement(state.osmConnection.userDetails.map(ud => t.loginIsCorrect.Subs(ud)))])
const isValid = state.osmConnection.userDetails.map(ud =>
LoginToImport.whitelist.indexOf(ud.uid) >= 0 || ud.csCount >= Constants.userJourney.importHelperUnlock)

View file

@ -12,16 +12,16 @@ import List from "../Base/List";
import CheckBoxes from "../Input/Checkboxes";
/**
* Shows the data to import on a map, asks for the correct layer to be selected
* Shows the attributes by value, requests to check them of
*/
export class PreviewPanel extends Combine implements FlowStep<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }> {
export class PreviewAttributesPanel extends Combine implements FlowStep<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }> {
public readonly IsValid: UIEventSource<boolean>;
public readonly Value: UIEventSource<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }>
constructor(
state: UserRelatedState,
geojson: { features: { properties: any, geometry: { coordinates: [number, number] } }[] }) {
const t = Translations.t.importHelper;
const t = Translations.t.importHelper.previewAttributes;
const propertyKeys = new Set<string>()
for (const f of geojson.features) {

View file

@ -10,12 +10,12 @@ import Title from "../Base/Title";
import {RadioButton} from "../Input/RadioButton";
import {And} from "../../Logic/Tags/And";
import {VariableUiElement} from "../Base/VariableUIElement";
import {FixedUiElement} from "../Base/FixedUiElement";
import Toggleable from "../Base/Toggleable";
import {BBox} from "../../Logic/BBox";
import BaseUIElement from "../BaseUIElement";
import PresetConfig from "../../Models/ThemeConfig/PresetConfig";
import List from "../Base/List";
import Translations from "../i18n/Translations";
export default class SelectTheme extends Combine implements FlowStep<{
features: any[],
@ -33,7 +33,7 @@ export default class SelectTheme extends Combine implements FlowStep<{
public readonly IsValid: UIEventSource<boolean>;
constructor(params: ({ features: any[], layer: LayerConfig, bbox: BBox, })) {
const t = Translations.t.importHelper.selectTheme
let options: InputElement<string>[] = AllKnownLayouts.layoutsList
.filter(th => th.layers.some(l => l.id === params.layer.id))
.filter(th => th.id !== "personal")
@ -69,15 +69,15 @@ export default class SelectTheme extends Combine implements FlowStep<{
})
super([
new Title("Select a theme"),
"All of the following themes will show the import notes. However, the note on OpenStreetMap can link to only one single theme. Choose which theme that the created notes will link to",
new Title(t.title),
t.intro,
themeRadios,
new VariableUiElement(applicablePresets.map(applicablePresets => {
if (themeRadios.GetValue().data === undefined) {
return undefined
}
if (applicablePresets === undefined || applicablePresets.length === 0) {
return new FixedUiElement("This theme has no presets loaded. As a result, imports won't work here").SetClass("alert")
return t.noMatchingPresets.SetClass("alert")
}
}, [themeRadios.GetValue()])),
@ -115,11 +115,14 @@ export default class SelectTheme extends Combine implements FlowStep<{
if (unmatched === undefined || unmatched.length === 0) {
return
}
const applicablePresetsOverview = applicablePresets.map(preset => new Combine([
preset.title.txt, "needs tags",
new FixedUiElement(preset.tags.map(t => t.asHumanString()).join(" & ")).SetClass("thanks")
]))
const t = Translations.t.importHelper.selectTheme
const applicablePresetsOverview = applicablePresets.map(preset =>
t.needsTags.Subs(
{title: preset.title,
tags:preset.tags.map(t => t.asHumanString()).join(" & ") })
.SetClass("thanks")
);
const unmatchedPanels: BaseUIElement[] = []
for (const feat of unmatched) {
@ -133,20 +136,16 @@ export default class SelectTheme extends Combine implements FlowStep<{
const missing = []
for (const {k, v} of tags) {
if (preset[k] === undefined) {
missing.push(
`Expected ${k}=${v}, but it is completely missing`
)
missing.push(t.missing.Subs({k,v}))
} else if (feat.properties[k] !== v) {
missing.push(
`Property with key ${k} does not have expected value ${v}; instead it is ${feat.properties}`
)
missing.push(t.misMatch.Subs({k, v, properties: feat.properties}))
}
}
if (missing.length > 0) {
parts.push(
new Combine([
new FixedUiElement(`Preset ${preset.title.txt} is not applicable:`),
t.notApplicable.Subs(preset),
new List(missing)
]).SetClass("flex flex-col alert")
)
@ -158,9 +157,9 @@ export default class SelectTheme extends Combine implements FlowStep<{
}
return new Combine([
new FixedUiElement(unmatched.length + " objects dont match any presets").SetClass("alert"),
t.displayNonMatchingCount.Subs(unmatched).SetClass("alert"),
...applicablePresetsOverview,
new Toggleable(new Title("The following elements don't match any of the presets"),
new Toggleable(new Title(t.unmatchedTitle),
new Combine(unmatchedPanels))
]).SetClass("flex flex-col")

View file

@ -276,17 +276,33 @@
"willBePublished": "Your picture will be published "
},
"importHelper": {
"allAttributesSame": "All features to import have this tag",
"description": "The import helper converts an external dataset to notes. The external dataset must match one of the existing MapComplete layers. For every item you put in the importer, a single note will be created. These notes will be shown together with the relevant features in these maps to easily add them.",
"importFormat": "A text in a note should have the following format in order to be picked up: <br/><div class='literal-code'>[A bit of introduction]<br/>https://mapcomplete.osm.be/[themename].html?[parameters such as lat and lon]#import<br/>[all tags of the feature] </div>",
"inspectDataTitle": "Inspect data of {count} features to import",
"compareToAlreadyExistingNotes": {
"completelyImported": "All of the proposed points have (or had) an import note already",
"loading": "Fetching notes from OSM",
"loadingFailed": "Loading notes failed due to {error}",
"mapExplanation": "The red elements on the next map are all the data points from your dataset. There are <b>{length}</b> elements in your dataset.",
"noPreviousNotesFound": "No previous import notes found",
"nothingNearby": "All of the proposed points have don't have a previous import note nearby",
"someNearby": "{hasNearby} points do have an already existing import note within {distance} meter",
"title": "Compare with existing notes",
"titleLong": "Compare with already existing 'to-import'-notes",
"wontBeImported": "These data points will <i>not</i> be imported and are shown as red dots on the map below"
},
"inspectDidAutoDected": "Layer was chosen automatically",
"inspectLooksCorrect": "These values look correct",
"lockNotice": "This page is locked. You need {importHelperUnlock} changesets before you can access here.",
"introduction": {
"description": "The import helper converts an external dataset to notes. The external dataset must match one of the existing MapComplete layers. For every item you put in the importer, a single note will be created. These notes will be shown together with the relevant features in these maps to easily add them.",
"importFormat": "A text in a note should have the following format in order to be picked up: <br/><div class='literal-code'>[A bit of introduction]<br/>https://mapcomplete.osm.be/[themename].html?[parameters such as lat and lon]#import<br/>[all tags of the feature] </div>",
"title": "Introduction"
},
"locked": "You need at least {importHelperUnlock} to use the import helper",
"loggedInWith": "You are currently logged in as <b>{name}</b> and have made {csCount} changesets",
"loginIsCorrect": "<b>{name}</b> is the correct account to create the import notes with.",
"loginRequired": "You have to be logged in to continue",
"login": {
"lockNotice": "This page is locked. You need {importHelperUnlock} changesets before you can access here.",
"loggedInWith": "You are currently logged in as <b>{name}</b> and have made {csCount} changesets",
"loginIsCorrect": "<b>{name}</b> is the correct account to create the import notes with.",
"loginRequired": "You have to be logged in to continue",
"title": "Login",
"userAccountTitle": "Select user account"
},
"mapPreview": {
"autodetected": "The layer was automatically deducted based on the properties",
"confirm": "The features are on the right location on the map",
@ -294,6 +310,13 @@
"selectLayer": "Which layer does this import match with?",
"title": "Map preview"
},
"previewAttributes": {
"allAttributesSame": "All features to import have this tag",
"inspectDataTitle": "Inspect data of {count} features to import",
"inspectLooksCorrect": "These values look correct",
"someHaveSame": "{count} features to import have this tag, this is {percentage}% of the total",
"title": "Inspect attributes"
},
"selectFile": {
"description": "Select a .csv or .geojson file to get started",
"errDuplicate": "Some columns have the same name",
@ -308,10 +331,18 @@
"noFilesLoaded": "No file is currently loaded",
"title": "Select file"
},
"selectLayer": "Select a layer...",
"someHaveSame": "{count} features to import have this tag, this is {percentage}% of the total",
"selectTheme": {
"displayNonMatchingCount": "{length} objects dont match any presets",
"intro": "All of the following themes will show the import notes. However, the note on OpenStreetMap can link to only one single theme. Choose which theme that the created notes will link to",
"misMatch": "Property with key {k} does not have expected value {v}; instead it is {properties}",
"missing": "Expected $k}={v}, but it is completely missing",
"needsTags": "{title} needs tags {tags}",
"noMatchingPresets": "This theme has no presets loaded. As a result, imports won't work here",
"notApplicable": "Preset {title} is not applicable:",
"title": "Select a theme",
"unmatchedTitle": "The following elements don't match any of the presets"
},
"title": "Import helper",
"userAccountTitle": "Select user account",
"validateDataTitle": "Validate data"
},
"importInspector": {