Polishing and translations for the import helper
This commit is contained in:
parent
f7844d8b2b
commit
8e2e227563
7 changed files with 161 additions and 87 deletions
|
@ -4,8 +4,12 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
|||
import ValidatedTextField from "../Input/ValidatedTextField";
|
||||
import {LocalStorageSource} from "../../Logic/Web/LocalStorageSource";
|
||||
import Title from "../Base/Title";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import Translations from "../i18n/Translations";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import {SubtleButton} from "../Base/SubtleButton";
|
||||
import Svg from "../../Svg";
|
||||
import {Utils} from "../../Utils";
|
||||
|
||||
export class AskMetadata extends Combine implements FlowStep<{
|
||||
features: any[],
|
||||
|
@ -25,7 +29,7 @@ export class AskMetadata extends Combine implements FlowStep<{
|
|||
public readonly IsValid: UIEventSource<boolean>;
|
||||
|
||||
constructor(params: ({ features: any[], theme: string })) {
|
||||
|
||||
const t = Translations.t.importHelper.askMetadata
|
||||
const introduction = ValidatedTextField.ForType("text").ConstructInputElement({
|
||||
value: LocalStorageSource.Get("import-helper-introduction-text"),
|
||||
inputStyle: "width: 100%"
|
||||
|
@ -42,28 +46,39 @@ export class AskMetadata extends Combine implements FlowStep<{
|
|||
})
|
||||
|
||||
super([
|
||||
new Title("Set metadata"),
|
||||
"Before adding " + params.features.length + " notes, please provide some extra information.",
|
||||
"Please, write an introduction for someone who sees the note",
|
||||
new Title(t.title),
|
||||
t.intro.Subs({count: params.features.length}),
|
||||
t.giveDescription,
|
||||
introduction.SetClass("w-full border border-black"),
|
||||
"What is the source of this data? If 'source' is set in the feature, this value will be ignored",
|
||||
source.SetClass("w-full border border-black"),
|
||||
"On what wikipage can one find more information about this import?",
|
||||
t.giveSource,
|
||||
source.SetClass("w-full border border-black"),
|
||||
t.giveWikilink ,
|
||||
wikilink.SetClass("w-full border border-black"),
|
||||
new VariableUiElement(wikilink.GetValue().map(wikilink => {
|
||||
try{
|
||||
const url = new URL(wikilink)
|
||||
if(url.hostname.toLowerCase() !== "wiki.openstreetmap.org"){
|
||||
return new FixedUiElement("Expected a link to wiki.openstreetmap.org").SetClass("alert");
|
||||
return t.shouldBeOsmWikilink.SetClass("alert");
|
||||
}
|
||||
|
||||
if(url.pathname.toLowerCase() === "/wiki/main_page"){
|
||||
return new FixedUiElement("Nope, the home page isn't allowed either. Enter the URL of a proper wikipage documenting your import").SetClass("alert");
|
||||
return t.shouldNotBeHomepage.SetClass("alert");
|
||||
}
|
||||
}catch(e){
|
||||
return new FixedUiElement("Not a valid URL").SetClass("alert")
|
||||
return t.shouldBeUrl.SetClass("alert")
|
||||
}
|
||||
}))
|
||||
})),
|
||||
t.orDownload,
|
||||
new SubtleButton(Svg.download_svg(), t.downloadGeojson).OnClickWithLoading("Preparing your download",
|
||||
async ( ) => {
|
||||
const geojson = {
|
||||
type:"FeatureCollection",
|
||||
features: params.features
|
||||
}
|
||||
Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson), "prepared_import_"+params.theme+".geojson",{
|
||||
mimetype: "application/vnd.geo+json"
|
||||
})
|
||||
})
|
||||
]);
|
||||
this.SetClass("flex flex-col")
|
||||
|
||||
|
|
|
@ -2,45 +2,30 @@ import Combine from "../Base/Combine";
|
|||
import {FlowStep} from "./FlowStep";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import Link from "../Base/Link";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import CheckBoxes from "../Input/Checkboxes";
|
||||
import Title from "../Base/Title";
|
||||
import {SubtleButton} from "../Base/SubtleButton";
|
||||
import Svg from "../../Svg";
|
||||
import {Utils} from "../../Utils";
|
||||
import Translations from "../i18n/Translations";
|
||||
|
||||
export class ConfirmProcess extends Combine implements FlowStep<{ features: any[], theme: string }> {
|
||||
|
||||
public IsValid: UIEventSource<boolean>
|
||||
public Value: UIEventSource<{ features: any[],theme: string }>
|
||||
|
||||
constructor(v: { features: any[], theme: string }) {
|
||||
public Value: UIEventSource<{ features: any[], theme: string }>
|
||||
|
||||
constructor(v: { features: any[], theme: string }) {
|
||||
const t = Translations.t.importHelper.confirmProcess;
|
||||
const toConfirm = [
|
||||
new Combine(["I have read the ", new Link("import guidelines on the OSM wiki", "https://wiki.openstreetmap.org/wiki/Import_guidelines", true)]),
|
||||
new FixedUiElement("I did contact the (local) community about this import"),
|
||||
new FixedUiElement("The license of the data to import allows it to be imported into OSM. They are allowed to be redistributed commercially, with only minimal attribution"),
|
||||
new FixedUiElement("The process is documented on the OSM-wiki (you'll need this link later)")
|
||||
new Link(t.readImportGuidelines, "https://wiki.openstreetmap.org/wiki/Import_guidelines", true),
|
||||
t.contactedCommunity,
|
||||
t.licenseIsCompatible,
|
||||
t.wikipageIsMade
|
||||
];
|
||||
|
||||
const licenseClear = new CheckBoxes(toConfirm)
|
||||
super([
|
||||
new Title("Did you go through the import process?"),
|
||||
licenseClear,
|
||||
new FixedUiElement("Alternatively, you can download the dataset to import directly"),
|
||||
new SubtleButton(Svg.download_svg(), "Download geojson").OnClickWithLoading("Preparing your download",
|
||||
async ( ) => {
|
||||
const geojson = {
|
||||
type:"FeatureCollection",
|
||||
features: v.features
|
||||
}
|
||||
Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson), "prepared_import_"+v.theme+".geojson",{
|
||||
mimetype: "application/vnd.geo+json"
|
||||
})
|
||||
})
|
||||
new Title(t.titleLong),
|
||||
new CheckBoxes(toConfirm),
|
||||
]);
|
||||
this.SetClass("link-underline")
|
||||
this.IsValid = licenseClear.GetValue().map(selected => toConfirm.length == selected.length)
|
||||
this.IsValid = new CheckBoxes(toConfirm).GetValue().map(selected => toConfirm.length == selected.length)
|
||||
this.Value = new UIEventSource<{ features: any[], theme: string }>(v)
|
||||
}
|
||||
}
|
|
@ -8,47 +8,58 @@ import {VariableUiElement} from "../Base/VariableUIElement";
|
|||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import {SubtleButton} from "../Base/SubtleButton";
|
||||
import Svg from "../../Svg";
|
||||
import Translations from "../i18n/Translations";
|
||||
|
||||
export class CreateNotes extends Combine {
|
||||
|
||||
public static createNoteContents(feature: {properties: any, geometry: {coordinates: [number,number]}},
|
||||
options: {wikilink: string; intro: string; source: string, theme: string }
|
||||
): string[]{
|
||||
const src = feature.properties["source"] ?? feature.properties["src"] ?? options.source
|
||||
delete feature.properties["source"]
|
||||
delete feature.properties["src"]
|
||||
let extraNote = ""
|
||||
if (feature.properties["note"]) {
|
||||
extraNote = feature.properties["note"] + "\n"
|
||||
delete feature.properties["note"]
|
||||
}
|
||||
|
||||
const tags: string [] = []
|
||||
for (const key in feature.properties) {
|
||||
if (feature.properties[key] === null || feature.properties[key] === undefined) {
|
||||
console.warn("Null or undefined key for ", feature.properties)
|
||||
continue
|
||||
}
|
||||
if (feature.properties[key] === "") {
|
||||
continue
|
||||
}
|
||||
tags.push(key + "=" + (feature.properties[key]+"").replace(/=/, "\\=").replace(/;/g, "\\;").replace(/\n/g, "\\n"))
|
||||
}
|
||||
const lat = feature.geometry.coordinates[1]
|
||||
const lon = feature.geometry.coordinates[0]
|
||||
const note = Translations.t.importHelper.noteParts
|
||||
return [
|
||||
options.intro,
|
||||
extraNote,
|
||||
note.datasource.Subs({source: src}).txt,
|
||||
note.wikilink.Subs(options).txt,
|
||||
'',
|
||||
note.importEasily.txt,
|
||||
`https://mapcomplete.osm.be/${options.theme}.html?z=18&lat=${lat}&lon=${lon}#import`,
|
||||
...tags]
|
||||
}
|
||||
|
||||
constructor(state: { osmConnection: OsmConnection }, v: { features: any[]; wikilink: string; intro: string; source: string, theme: string }) {
|
||||
|
||||
const t = Translations.t.importHelper.createNotes;
|
||||
const createdNotes: UIEventSource<number[]> = new UIEventSource<number[]>([])
|
||||
const failed = new UIEventSource<string[]>([])
|
||||
const currentNote = createdNotes.map(n => n.length)
|
||||
|
||||
for (const f of v.features) {
|
||||
|
||||
const src = f.properties["source"] ?? f.properties["src"] ?? v.source
|
||||
delete f.properties["source"]
|
||||
delete f.properties["src"]
|
||||
let extraNote = ""
|
||||
if (f.properties["note"]) {
|
||||
extraNote = f.properties["note"] + "\n"
|
||||
delete f.properties["note"]
|
||||
}
|
||||
|
||||
const tags: string [] = []
|
||||
for (const key in f.properties) {
|
||||
if (f.properties[key] === null || f.properties[key] === undefined) {
|
||||
console.warn("Null or undefined key for ", f.properties)
|
||||
continue
|
||||
}
|
||||
if (f.properties[key] === "") {
|
||||
continue
|
||||
}
|
||||
tags.push(key + "=" + (f.properties[key]+"").replace(/=/, "\\=").replace(/;/g, "\\;").replace(/\n/g, "\\n"))
|
||||
}
|
||||
|
||||
const lat = f.geometry.coordinates[1]
|
||||
const lon = f.geometry.coordinates[0]
|
||||
const text = [v.intro,
|
||||
extraNote,
|
||||
"Source: " + src,
|
||||
'More information at ' + v.wikilink,
|
||||
'',
|
||||
'Import this point easily with',
|
||||
`https://mapcomplete.osm.be/${v.theme}.html?z=18&lat=${lat}&lon=${lon}#import`,
|
||||
...tags].join("\n")
|
||||
const text = CreateNotes.createNoteContents(f, v).join("\n")
|
||||
|
||||
state.osmConnection.openNote(
|
||||
lat, lon, text)
|
||||
|
@ -62,13 +73,19 @@ export class CreateNotes extends Combine {
|
|||
}
|
||||
|
||||
super([
|
||||
new Title("Creating notes"),
|
||||
"Hang on while we are importing...",
|
||||
new Title(t.title),
|
||||
t.loading ,
|
||||
new Toggle(
|
||||
new Loading(new VariableUiElement(currentNote.map(count => new FixedUiElement("Imported <b>" + count + "</b> out of " + v.features.length + " notes")))),
|
||||
new Loading(new VariableUiElement(currentNote.map(count => t.creating.Subs({
|
||||
count, total: v.features.length
|
||||
}
|
||||
|
||||
)))),
|
||||
new Combine([
|
||||
new FixedUiElement("All done!").SetClass("thanks"),
|
||||
new SubtleButton(Svg.note_svg(), "Inspect the progress of your notes in the 'import_viewer'", {
|
||||
Svg.party_svg().SetClass("w-24"),
|
||||
t.done.Subs(v.features.length).SetClass("thanks"),
|
||||
new SubtleButton(Svg.note_svg(),
|
||||
t.openImportViewer , {
|
||||
url: "import_viewer.html"
|
||||
})
|
||||
]
|
||||
|
|
|
@ -120,11 +120,11 @@ export class FlowPanel<T> extends Toggle {
|
|||
isError.setData(true)
|
||||
}
|
||||
}),
|
||||
"Select a valid value to continue",
|
||||
new SubtleButton(Svg.invalid_svg(), t.notValid),
|
||||
initial.IsValid
|
||||
),
|
||||
new Toggle(
|
||||
new FixedUiElement("Something went wrong...").SetClass("alert"),
|
||||
t.error.SetClass("alert"),
|
||||
undefined,
|
||||
isError),
|
||||
]).SetClass("flex w-full justify-end space-x-2"),
|
||||
|
|
|
@ -37,9 +37,9 @@ export default class ImportHelperGui extends LeftIndex {
|
|||
.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))
|
||||
.finish("Note creation", v => new CreateNotes(state, v));
|
||||
.then(t.confirmProcess, v => new ConfirmProcess(v))
|
||||
.then(t.askMetadata, (v) => new AskMetadata(v))
|
||||
.finish(t.createNotes.title, v => new CreateNotes(state, v));
|
||||
|
||||
const toc = new List(
|
||||
titles.map((title, i) => new VariableUiElement(furthestStep.map(currentStep => {
|
||||
|
@ -58,11 +58,11 @@ export default class ImportHelperGui extends LeftIndex {
|
|||
, true)
|
||||
|
||||
const leftContents: BaseUIElement[] = [
|
||||
new SubtleButton(undefined, "Inspect your preview imports", {
|
||||
new SubtleButton(undefined, t.gotoImportViewer, {
|
||||
url: "import_viewer.html"
|
||||
}),
|
||||
toc,
|
||||
new Toggle(new FixedUiElement("Testmode - won't actually import notes").SetClass("alert"), undefined, state.featureSwitchIsTesting),
|
||||
new Toggle(t.testMode.SetClass("block alert"), undefined, state.featureSwitchIsTesting),
|
||||
LanguagePicker.CreateLanguagePicker(Translations.t.importHelper.title.SupportedLanguages())?.SetClass("mt-4 self-end flex-col"),
|
||||
].map(el => el?.SetClass("pl-4"))
|
||||
|
||||
|
|
|
@ -3,18 +3,42 @@ import {FlowStep} from "./FlowStep";
|
|||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Title from "../Base/Title";
|
||||
import {CreateNotes} from "./CreateNotes";
|
||||
|
||||
export default class Introdution extends Combine implements FlowStep<void> {
|
||||
readonly IsValid: UIEventSource<boolean> = new UIEventSource<boolean>(true);
|
||||
readonly Value: UIEventSource<void> = new UIEventSource<void>(undefined);
|
||||
readonly IsValid: UIEventSource<boolean>;
|
||||
readonly Value: UIEventSource<void>;
|
||||
|
||||
constructor() {
|
||||
const example = CreateNotes.createNoteContents({
|
||||
properties:{
|
||||
"some_key":"some_value",
|
||||
"note":"a note in the original dataset"
|
||||
},
|
||||
geometry:{
|
||||
coordinates: [3.4,51.2]
|
||||
}
|
||||
}, {
|
||||
wikilink: "https://wiki.openstreetmap.org/wiki/Imports/<documentation of your import>",
|
||||
intro: "There might be an XYZ here",
|
||||
theme: "theme",
|
||||
source: "source of the data"
|
||||
})
|
||||
|
||||
super([
|
||||
new Title(Translations.t.importHelper.introduction.title),
|
||||
Translations.t.importHelper.introduction.description,
|
||||
Translations.t.importHelper.introduction.importFormat,
|
||||
new Combine(
|
||||
[new Combine(
|
||||
example
|
||||
).SetClass("flex flex-col")
|
||||
] ).SetClass("literal-code")
|
||||
]);
|
||||
this.SetClass("flex flex-col")
|
||||
this. IsValid= new UIEventSource<boolean>(true);
|
||||
this. Value = new UIEventSource<void>(undefined);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -119,6 +119,7 @@
|
|||
"title": "Download visible data",
|
||||
"uploadGpx": "Upload your track to OpenStreetMap"
|
||||
},
|
||||
"error": "Something went wrong...",
|
||||
"example": "Example",
|
||||
"examples": "Examples",
|
||||
"fewChangesBefore": "Please, answer a few questions of existing points before adding a new point.",
|
||||
|
@ -151,6 +152,7 @@
|
|||
"next": "Next",
|
||||
"noNameCategory": "{category} without a name",
|
||||
"noTagsSelected": "No tags selected",
|
||||
"notValid": "Select a valid value to continue",
|
||||
"number": "number",
|
||||
"oneSkippedQuestion": "One question is skipped",
|
||||
"openStreetMapIntro": "<h3>An Open Map</h3><p>One that everyone can use and edit freely. A single place to store all geo-info. Different, small, incompatible and outdated maps are not needed anywhere.</p><p><b><a href='https://OpenStreetMap.org' target='_blank'>OpenStreetMap</a></b> is not the enemy map. The map data can be used freely (with <a href='https://osm.org/copyright' target='_blank'>attribution and publication of changes to that data</a>). Everyone can add new data and fix errors. This website uses OpenStreetMap. All the data is from there, and your answers and corrections are used all over.</p><p>Many people and apps already use OpenStreetMap: <a href='https://organicmaps.app/' target='_blank'>Organic Maps</a>, <a href='https://osmAnd.net' target='_blank'>OsmAnd</a>, but also the maps at Facebook, Instagram, Apple-maps and Bing-maps are (partly) powered by OpenStreetMap.</p>",
|
||||
|
@ -276,6 +278,18 @@
|
|||
"willBePublished": "Your picture will be published "
|
||||
},
|
||||
"importHelper": {
|
||||
"askMetadata": {
|
||||
"downloadGeojson": "Download geojson",
|
||||
"giveDescription": "Please, write a small description for someone who sees the note. A good note describes what the contributor has to do, e.g; <i>There might be a bench here. If you are around, could you please check and indicate if the bench exists or not?</i> (A link to MapComplete will be added automatically)",
|
||||
"giveSource": "What is the source of this data? If 'source' is set in the feature, this value will be ignored",
|
||||
"giveWikilink": "On what wikipage can one find more information about this import?",
|
||||
"intro": "Before adding {count} notes, please provide some extra information.",
|
||||
"orDownload": "Alternatively, you can download the dataset to import directly",
|
||||
"shouldBeOsmWikilink": "Expected a link to a page on wiki.openstreetmap.org",
|
||||
"shouldBeUrl": "Not a valid URL",
|
||||
"shouldNotBeHomepage": "Nope, the home page isn't allowed either. Enter the URL of a proper wikipage documenting your import",
|
||||
"title": "Set metadata"
|
||||
},
|
||||
"compareToAlreadyExistingNotes": {
|
||||
"completelyImported": "All of the proposed points have (or had) an import note already",
|
||||
"loading": "Fetching notes from OSM",
|
||||
|
@ -288,13 +302,27 @@
|
|||
"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",
|
||||
"confirmProcess": {
|
||||
"contactedCommunity": "I did contact the (local) community about this import",
|
||||
"licenseIsCompatible": "The license of the data to import allows it to be imported into OSM. They are allowed to be redistributed commercially, with only minimal attribution",
|
||||
"readImportGuidelines": "I have read the import guidelines on the OSM wiki",
|
||||
"title": "License and community",
|
||||
"titleLong": "Did you go through the import process?",
|
||||
"wikipageIsMade": "The process is documented on the OSM-wiki (you'll need this link later)"
|
||||
},
|
||||
"createNotes": {
|
||||
"creating": "Created <b>{count}</b> notes out of {total}",
|
||||
"done": "All {count} notes have been created!",
|
||||
"loading": "Hang on while we are loading...",
|
||||
"openImportViewer": "Inspect the progress of your notes in the 'import_viewer'",
|
||||
"title": "Note creation"
|
||||
},
|
||||
"gotoImportViewer": "Inspect your previous imports",
|
||||
"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>",
|
||||
"importFormat": "A text in a note should have the following format in order to be picked up",
|
||||
"title": "Introduction"
|
||||
},
|
||||
"locked": "You need at least {importHelperUnlock} to use the import helper",
|
||||
"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",
|
||||
|
@ -310,6 +338,11 @@
|
|||
"selectLayer": "Which layer does this import match with?",
|
||||
"title": "Map preview"
|
||||
},
|
||||
"noteParts": {
|
||||
"datasource": "Original data from {source}",
|
||||
"importEasily": "Add this point easily with MapComplete:",
|
||||
"wikilink": "More information about this import can be found at {wikilink}"
|
||||
},
|
||||
"previewAttributes": {
|
||||
"allAttributesSame": "All features to import have this tag",
|
||||
"inspectDataTitle": "Inspect data of {count} features to import",
|
||||
|
@ -342,8 +375,8 @@
|
|||
"title": "Select a theme",
|
||||
"unmatchedTitle": "The following elements don't match any of the presets"
|
||||
},
|
||||
"title": "Import helper",
|
||||
"validateDataTitle": "Validate data"
|
||||
"testMode": "Testmode - won't actually import notes",
|
||||
"title": "Import helper"
|
||||
},
|
||||
"importInspector": {
|
||||
"title": "Inspect and manage import notes"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue