forked from MapComplete/MapComplete
Add import button
This commit is contained in:
parent
2676b0e439
commit
706c5e3d53
3 changed files with 134 additions and 7 deletions
59
UI/BigComponents/ImportButton.ts
Normal file
59
UI/BigComponents/ImportButton.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import {SubtleButton} from "../Base/SubtleButton";
|
||||||
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
import Combine from "../Base/Combine";
|
||||||
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
|
import Translations from "../i18n/Translations";
|
||||||
|
import State from "../../State";
|
||||||
|
import Constants from "../../Models/Constants";
|
||||||
|
import Toggle from "../Input/Toggle";
|
||||||
|
import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction";
|
||||||
|
import {Tag} from "../../Logic/Tags/Tag";
|
||||||
|
|
||||||
|
export default class ImportButton extends Toggle {
|
||||||
|
constructor(imageUrl: string | BaseUIElement, message: string | BaseUIElement,
|
||||||
|
originalTags: UIEventSource<any>,
|
||||||
|
newTags: UIEventSource<Tag[]>, lat: number, lon: number) {
|
||||||
|
const t = Translations.t.general.add;
|
||||||
|
const isImported = originalTags.map(tags => tags._imported === "yes")
|
||||||
|
const appliedTags = new Toggle(
|
||||||
|
new VariableUiElement(
|
||||||
|
newTags.map(tgs => {
|
||||||
|
const parts = []
|
||||||
|
for (const tag of tgs) {
|
||||||
|
parts.push(tag.key + "=" + tag.value)
|
||||||
|
}
|
||||||
|
const txt = parts.join(" & ")
|
||||||
|
return t.presetInfo.Subs({tags: txt}).SetClass("subtle")
|
||||||
|
})), undefined,
|
||||||
|
State.state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.tagsVisibleAt)
|
||||||
|
)
|
||||||
|
const button = new SubtleButton(imageUrl, message)
|
||||||
|
|
||||||
|
|
||||||
|
button.onClick(() => {
|
||||||
|
if (isImported.data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
originalTags.data["_imported"] = "yes"
|
||||||
|
originalTags.ping() // will set isImported as per its definition
|
||||||
|
const newElementAction = new CreateNewNodeAction(newTags.data, lat, lon)
|
||||||
|
State.state.changes.applyAction(newElementAction)
|
||||||
|
State.state.selectedElement.setData(State.state.allElements.ContainingFeatures.get(
|
||||||
|
newElementAction.newElementId
|
||||||
|
))
|
||||||
|
console.log("Did set selected element to", State.state.allElements.ContainingFeatures.get(
|
||||||
|
newElementAction.newElementId
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
const withLoadingCheck = new Toggle(
|
||||||
|
t.stillLoading,
|
||||||
|
new Combine([button, appliedTags]).SetClass("flex flex-col"),
|
||||||
|
State.state.layerUpdater.runningQuery
|
||||||
|
)
|
||||||
|
super(t.hasBeenImported, withLoadingCheck, isImported)
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,8 @@ import Loc from "../Models/Loc";
|
||||||
import {Utils} from "../Utils";
|
import {Utils} from "../Utils";
|
||||||
import BaseLayer from "../Models/BaseLayer";
|
import BaseLayer from "../Models/BaseLayer";
|
||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
||||||
|
import ImportButton from "./BigComponents/ImportButton";
|
||||||
|
import {Tag} from "../Logic/Tags/Tag";
|
||||||
|
|
||||||
export interface SpecialVisualization {
|
export interface SpecialVisualization {
|
||||||
funcName: string,
|
funcName: string,
|
||||||
|
@ -65,7 +67,6 @@ export default class SpecialVisualizations {
|
||||||
})).SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;")
|
})).SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
funcName: "image_carousel",
|
funcName: "image_carousel",
|
||||||
docs: "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)",
|
docs: "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)",
|
||||||
|
@ -87,7 +88,6 @@ export default class SpecialVisualizations {
|
||||||
return new ImageCarousel(searcher, tags);
|
return new ImageCarousel(searcher, tags);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
funcName: "image_upload",
|
funcName: "image_upload",
|
||||||
docs: "Creates a button where a user can upload an image to IMGUR",
|
docs: "Creates a button where a user can upload an image to IMGUR",
|
||||||
|
@ -185,7 +185,7 @@ export default class SpecialVisualizations {
|
||||||
{
|
{
|
||||||
funcName: "reviews",
|
funcName: "reviews",
|
||||||
docs: "Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten",
|
docs: "Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten",
|
||||||
example: "<b>{reviews()}<b> for a vanilla review, <b>{reviews(name, play_forest)}</b> to review a play forest. If a name is known, the name will be used as identifier, otherwise 'play_forest' is used",
|
example: "`{reviews()}` for a vanilla review, `{reviews(name, play_forest)}` to review a play forest. If a name is known, the name will be used as identifier, otherwise 'play_forest' is used",
|
||||||
args: [{
|
args: [{
|
||||||
name: "subjectKey",
|
name: "subjectKey",
|
||||||
defaultValue: "name",
|
defaultValue: "name",
|
||||||
|
@ -222,7 +222,6 @@ export default class SpecialVisualizations {
|
||||||
return new OpeningHoursVisualization(tagSource, args[0])
|
return new OpeningHoursVisualization(tagSource, args[0])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
funcName: "live",
|
funcName: "live",
|
||||||
docs: "Downloads a JSON from the given URL, e.g. '{live(example.org/data.json, shorthand:x.y.z, other:a.b.c, shorthand)}' will download the given file, will create an object {shorthand: json[x][y][z], other: json[a][b][c] out of it and will return 'other' or 'json[a][b][c]. This is made to use in combination with tags, e.g. {live({url}, {url:format}, needed_value)}",
|
docs: "Downloads a JSON from the given URL, e.g. '{live(example.org/data.json, shorthand:x.y.z, other:a.b.c, shorthand)}' will download the given file, will create an object {shorthand: json[x][y][z], other: json[a][b][c] out of it and will return 'other' or 'json[a][b][c]. This is made to use in combination with tags, e.g. {live({url}, {url:format}, needed_value)}",
|
||||||
|
@ -243,7 +242,6 @@ export default class SpecialVisualizations {
|
||||||
return new VariableUiElement(source.map(data => data[neededValue] ?? "Loading..."));
|
return new VariableUiElement(source.map(data => data[neededValue] ?? "Loading..."));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
funcName: "histogram",
|
funcName: "histogram",
|
||||||
docs: "Create a histogram for a list of given values, read from the properties.",
|
docs: "Create a histogram for a list of given values, read from the properties.",
|
||||||
|
@ -381,6 +379,75 @@ export default class SpecialVisualizations {
|
||||||
[state.layoutToUse])
|
[state.layoutToUse])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
funcName: "import_button",
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: "tags",
|
||||||
|
doc: "Tags to copy-specification. This contains one or more pairs (seperated by a `;`), e.g. `amenity=fast_food; addr:housenumber=$number`. This new point will then have the tags `amenity=fast_food` and `addr:housenumber` with the value that was saved in `number` in the original feature. (Hint: prepare these values, e.g. with calculatedTags)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "text",
|
||||||
|
doc: "The text to show on the button",
|
||||||
|
defaultValue: "Import this data into OpenStreetMap"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "icon",
|
||||||
|
doc: "A nice icon to show in the button",
|
||||||
|
defaultValue: "./assets/svg/addSmall.svg"
|
||||||
|
}],
|
||||||
|
docs: `This button will copy the data from an external dataset into OpenStreetMap. It is only functional in official themes but can be tested in unofficial themes.
|
||||||
|
|
||||||
|
If you want to import a dataset, make sure that:
|
||||||
|
|
||||||
|
1. The dataset to import has a suitable license
|
||||||
|
2. The community has been informed of the import
|
||||||
|
3. All other requirements of the [import guidelines](https://wiki.openstreetmap.org/wiki/Import/Guidelines) have been followed
|
||||||
|
|
||||||
|
There are also some technicalities in your theme to keep in mind:
|
||||||
|
|
||||||
|
1. The new point will be added and will flow through the program as any other new point as if it came from OSM.
|
||||||
|
This means that there should be a layer which will match the new tags and which will display it.
|
||||||
|
2. The original point from your geojson layer will gain the tag '_imported=yes'.
|
||||||
|
This should be used to change the appearance or even to hide it (eg by changing the icon size to zero)
|
||||||
|
3. There should be a way for the theme to detect previously imported points, even after reloading.
|
||||||
|
A reference number to the original dataset is an excellen way to do this
|
||||||
|
`,
|
||||||
|
constr: (state, tagSource, args) => {
|
||||||
|
if (!state.layoutToUse.data.official && !state.featureSwitchIsTesting.data) {
|
||||||
|
return new Combine([new FixedUiElement("The import button is disabled for unofficial themes to prevent accidents.").SetClass("alert"),
|
||||||
|
new FixedUiElement("To test, add 'test=true' to the URL. The changeset will be printed in the console. Please open a PR to officialize this theme to actually enable the import button.")])
|
||||||
|
}
|
||||||
|
const tgsSpec = args[0].split(",").map(spec => {
|
||||||
|
const kv = spec.split("=").map(s => s.trim());
|
||||||
|
if (kv.length != 2) {
|
||||||
|
throw "Invalid key spec: multiple '=' found in " + spec
|
||||||
|
}
|
||||||
|
return kv
|
||||||
|
})
|
||||||
|
const rewrittenTags : UIEventSource<Tag[]> = tagSource.map(tags => {
|
||||||
|
const newTags : Tag [] = []
|
||||||
|
for (const [key, value] of tgsSpec) {
|
||||||
|
if (value.startsWith('$')) {
|
||||||
|
const origKey = value.substring(1)
|
||||||
|
newTags.push(new Tag(key, tags[origKey]))
|
||||||
|
} else {
|
||||||
|
newTags.push(new Tag(key, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newTags
|
||||||
|
})
|
||||||
|
const id = tagSource.data.id;
|
||||||
|
const feature = State.state.allElements.ContainingFeatures.get(id)
|
||||||
|
if (feature.geometry.type !== "Point") {
|
||||||
|
return new FixedUiElement("Error: can only import point objects").SetClass("alert")
|
||||||
|
}
|
||||||
|
const [lon, lat] = feature.geometry.coordinates;
|
||||||
|
return new ImportButton(
|
||||||
|
args[2], args[1], tagSource, rewrittenTags, lat, lon
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -399,7 +466,7 @@ export default class SpecialVisualizations {
|
||||||
),
|
),
|
||||||
new Title("Example usage", 4),
|
new Title("Example usage", 4),
|
||||||
new FixedUiElement(
|
new FixedUiElement(
|
||||||
viz.example ?? "{" + viz.funcName + "(" + viz.args.map(arg => arg.defaultValue).join(",") + ")}"
|
viz.example ?? "`{" + viz.funcName + "(" + viz.args.map(arg => arg.defaultValue).join(",") + ")}`"
|
||||||
).SetClass("literal-code"),
|
).SetClass("literal-code"),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -100,7 +100,8 @@
|
||||||
"confirmIntro": "<h3>Add a {title} here?</h3>The point you create here will be <b>visible for everyone</b>. Please, only add things on to the map if they truly exist. A lot of applications use this data.",
|
"confirmIntro": "<h3>Add a {title} here?</h3>The point you create here will be <b>visible for everyone</b>. Please, only add things on to the map if they truly exist. A lot of applications use this data.",
|
||||||
"confirmButton": "Add a {category} here.<br/><div class='alert'>Your addition is visible for everyone</div>",
|
"confirmButton": "Add a {category} here.<br/><div class='alert'>Your addition is visible for everyone</div>",
|
||||||
"openLayerControl": "Open the layer control box",
|
"openLayerControl": "Open the layer control box",
|
||||||
"layerNotEnabled": "The layer {layer} is not enabled. Enable this layer to add a point"
|
"layerNotEnabled": "The layer {layer} is not enabled. Enable this layer to add a point",
|
||||||
|
"hasBeenImported": "This point has already been imported"
|
||||||
},
|
},
|
||||||
"pickLanguage": "Choose a language: ",
|
"pickLanguage": "Choose a language: ",
|
||||||
"about": "Easily edit and add OpenStreetMap for a certain theme",
|
"about": "Easily edit and add OpenStreetMap for a certain theme",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue