First steps for a delete button
This commit is contained in:
parent
b7798a470c
commit
985e97d43b
10 changed files with 246 additions and 75 deletions
|
@ -13,6 +13,7 @@ export default class UserDetails {
|
|||
|
||||
public loggedIn = false;
|
||||
public name = "Not logged in";
|
||||
public uid: number;
|
||||
public csCount = 0;
|
||||
public img: string;
|
||||
public unreadMessages = 0;
|
||||
|
@ -167,6 +168,7 @@ export class OsmConnection {
|
|||
data.loggedIn = true;
|
||||
console.log("Login completed, userinfo is ", userInfo);
|
||||
data.name = userInfo.getAttribute('display_name');
|
||||
data.uid= Number(userInfo.getAttribute("id"))
|
||||
data.csCount = userInfo.getElementsByTagName("changesets")[0].getAttribute("count");
|
||||
|
||||
data.img = undefined;
|
||||
|
|
|
@ -43,11 +43,47 @@ export abstract class OsmObject {
|
|||
}
|
||||
}
|
||||
|
||||
public static DownloadHistory(id: string, continuation: (versions: OsmObject[]) => void): void {
|
||||
/**
|
||||
* Downloads the ways that are using this node.
|
||||
* Beware: their geometry will be incomplete!
|
||||
* @param id
|
||||
* @param continuation
|
||||
* @constructor
|
||||
*/
|
||||
public static DownloadReferencingWays(id: string, continuation: (referencingWays: OsmWay[]) => void){
|
||||
Utils.downloadJson(`https://www.openStreetMap.org/api/0.6/${id}/ways`)
|
||||
.then(data => {
|
||||
const ways = data.elements.map(wayInfo => {
|
||||
const way = new OsmWay(wayInfo.id)
|
||||
way.LoadData(wayInfo)
|
||||
return way
|
||||
})
|
||||
continuation(ways)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Downloads the relations that are using this feature.
|
||||
* Beware: their geometry will be incomplete!
|
||||
* @param id
|
||||
* @param continuation
|
||||
* @constructor
|
||||
*/
|
||||
public static DownloadReferencingRelations(id: string, continuation: (referencingRelations: OsmRelation[]) => void){
|
||||
Utils.downloadJson(`https://www.openStreetMap.org/api/0.6/${id}/relations`)
|
||||
.then(data => {
|
||||
const rels = data.elements.map(wayInfo => {
|
||||
const rel = new OsmRelation(wayInfo.id)
|
||||
rel.LoadData(wayInfo)
|
||||
return rel
|
||||
})
|
||||
continuation(rels)
|
||||
})
|
||||
}
|
||||
public static DownloadHistory(id: string, continuation: (versions: OsmObject[]) => void): void{
|
||||
const splitted = id.split("/");
|
||||
const type = splitted[0];
|
||||
const idN = splitted[1];
|
||||
$.getJSON("https://openStreetMap.org/api/0.6/" + type + "/" + idN + "/history", data => {
|
||||
$.getJSON("https://www.openStreetMap.org/api/0.6/" + type + "/" + idN + "/history", data => {
|
||||
const elements: any[] = data.elements;
|
||||
const osmObjects: OsmObject[] = []
|
||||
for (const element of elements) {
|
||||
|
|
|
@ -7,11 +7,13 @@ export default class Constants {
|
|||
// The user journey states thresholds when a new feature gets unlocked
|
||||
public static userJourney = {
|
||||
moreScreenUnlock: 1,
|
||||
personalLayoutUnlock: 15,
|
||||
historyLinkVisible: 20,
|
||||
personalLayoutUnlock: 5,
|
||||
historyLinkVisible: 10,
|
||||
deletePointsOfOthersUnlock: 15,
|
||||
tagsVisibleAt: 25,
|
||||
mapCompleteHelpUnlock: 50,
|
||||
tagsVisibleAndWikiLinked: 30,
|
||||
|
||||
mapCompleteHelpUnlock: 50,
|
||||
themeGeneratorReadOnlyUnlock: 50,
|
||||
themeGeneratorFullUnlock: 500,
|
||||
addNewPointWithUnreadMessagesUnlock: 500,
|
||||
|
|
7
UI/Base/Loading.ts
Normal file
7
UI/Base/Loading.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import {FixedUiElement} from "./FixedUiElement";
|
||||
|
||||
export default class Loading extends FixedUiElement {
|
||||
constructor() {
|
||||
super("Loading..."); // TODO to be improved
|
||||
}
|
||||
}
|
72
UI/Popup/DeleteButton.ts
Normal file
72
UI/Popup/DeleteButton.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import {OsmObject} from "../../Logic/Osm/OsmObject";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {Translation} from "../i18n/Translation";
|
||||
import State from "../../State";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Loading from "../Base/Loading";
|
||||
import UserDetails from "../../Logic/Osm/OsmConnection";
|
||||
import Constants from "../../Models/Constants";
|
||||
import {SubtleButton} from "../Base/SubtleButton";
|
||||
import Svg from "../../Svg";
|
||||
import {Utils} from "../../Utils";
|
||||
|
||||
|
||||
export default class DeleteButton extends Toggle {
|
||||
constructor(id: string) {
|
||||
|
||||
const hasRelations: UIEventSource<boolean> = new UIEventSource<boolean>(null)
|
||||
OsmObject.DownloadReferencingRelations(id, (rels) => {
|
||||
hasRelations.setData(rels.length > 0)
|
||||
})
|
||||
|
||||
const hasWays: UIEventSource<boolean> = new UIEventSource<boolean>(null)
|
||||
OsmObject.DownloadReferencingWays(id, (ways) => {
|
||||
hasWays.setData(ways.length > 0)
|
||||
})
|
||||
|
||||
const previousEditors = new UIEventSource<number[]>(null)
|
||||
OsmObject.DownloadHistory(id, versions => {
|
||||
const uids = versions.map(version => version.tags["_last_edit:contributor:uid"])
|
||||
previousEditors.setData(uids)
|
||||
})
|
||||
const allByMyself = previousEditors.map(previous => {
|
||||
if (previous === null) {
|
||||
return null;
|
||||
}
|
||||
const userId = State.state.osmConnection.userDetails.data.uid;
|
||||
return !previous.some(editor => editor !== userId)
|
||||
}, [State.state.osmConnection.userDetails])
|
||||
|
||||
const t = Translations.t.deleteButton
|
||||
|
||||
super(
|
||||
new Toggle(
|
||||
new VariableUiElement(
|
||||
hasRelations.map(hasRelations => {
|
||||
if (hasRelations === null || hasWays.data === null) {
|
||||
return new Loading()
|
||||
}
|
||||
if (hasWays.data || hasRelations) {
|
||||
return t.partOfOthers.Clone()
|
||||
}
|
||||
|
||||
return new Toggle(
|
||||
new SubtleButton(Svg.delete_icon_svg(), t.delete.Clone()),
|
||||
t.notEnoughExperience.Clone(),
|
||||
State.state.osmConnection.userDetails.map(userinfo =>
|
||||
allByMyself.data ||
|
||||
userinfo.csCount >= Constants.userJourney.deletePointsOfOthersUnlock,
|
||||
[allByMyself])
|
||||
)
|
||||
|
||||
}, [hasWays])
|
||||
),
|
||||
t.onlyEditedByLoggedInUser.Clone().onClick(State.state.osmConnection.AttemptLogin),
|
||||
State.state.osmConnection.isLoggedIn),
|
||||
t.isntAPoint,
|
||||
new UIEventSource<boolean>(id.startsWith("node"))
|
||||
);
|
||||
}
|
||||
}
|
1
Utils.ts
1
Utils.ts
|
@ -343,6 +343,7 @@ export class Utils {
|
|||
}
|
||||
};
|
||||
xhr.open('GET', url);
|
||||
xhr.setRequestHeader("accept","application/json")
|
||||
xhr.send();
|
||||
}catch(e){
|
||||
reject(e)
|
||||
|
|
|
@ -27,6 +27,15 @@
|
|||
"intro": "MapComplete is an OpenStreetMap-viewer and editor, which shows you information about a specific theme.",
|
||||
"pickTheme": "Pick a theme below to get started."
|
||||
},
|
||||
"deleteButton": {
|
||||
"delete": "Delete",
|
||||
"loginToDelete": "You must be logged in to delete a point",
|
||||
"checkingDeletability": "Inspecting properties to check if this feature can be deleted",
|
||||
"isntAPoint": "Only points can be deleted",
|
||||
"onlyEditedByLoggedInUser": "This point has only be edited by yourself, you can safely delete it",
|
||||
"notEnoughExperience": "You don't have enough experience to delete points made by other people. Make more edits to improve your skills",
|
||||
"partOfOthers": "This point is part of some way or relation, so you can not delete it"
|
||||
},
|
||||
"general": {
|
||||
"loginWithOpenStreetMap": "Login with OpenStreetMap",
|
||||
"welcomeBack": "You are logged in, welcome back!",
|
||||
|
|
138
test.ts
138
test.ts
|
@ -1,4 +1,8 @@
|
|||
import ValidatedTextField from "./UI/Input/ValidatedTextField";
|
||||
import {OsmObject} from "./Logic/Osm/OsmObject";
|
||||
import DeleteButton from "./UI/Popup/DeleteButton";
|
||||
import Combine from "./UI/Base/Combine";
|
||||
import State from "./State";
|
||||
/*import ValidatedTextField from "./UI/Input/ValidatedTextField";
|
||||
import Combine from "./UI/Base/Combine";
|
||||
import {VariableUiElement} from "./UI/Base/VariableUIElement";
|
||||
import {UIEventSource} from "./Logic/UIEventSource";
|
||||
|
@ -69,75 +73,77 @@ function TestAllInputMethods() {
|
|||
})).AttachTo("maindiv")
|
||||
}
|
||||
|
||||
function TestMiniMap() {
|
||||
|
||||
const location = new UIEventSource<Loc>({
|
||||
lon: 4.84771728515625,
|
||||
lat: 51.17920846421931,
|
||||
zoom: 14
|
||||
})
|
||||
const map0 = new Minimap({
|
||||
location: location,
|
||||
allowMoving: true,
|
||||
background: new AvailableBaseLayers(location).availableEditorLayers.map(layers => layers[2])
|
||||
})
|
||||
map0.SetStyle("width: 500px; height: 250px; overflow: hidden; border: 2px solid red")
|
||||
.AttachTo("maindiv")
|
||||
|
||||
const layout = AllKnownLayouts.layoutsList[1]
|
||||
State.state = new State(layout)
|
||||
console.log("LAYOUT is", layout.id)
|
||||
|
||||
const feature = {
|
||||
"type": "Feature",
|
||||
_matching_layer_id: "bike_repair_station",
|
||||
"properties": {
|
||||
id: "node/-1",
|
||||
"amenity": "bicycle_repair_station"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
4.84771728515625,
|
||||
51.17920846421931
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
State.state.allElements.addOrGetElement(feature)
|
||||
|
||||
const featureSource = new UIEventSource([{
|
||||
freshness: new Date(),
|
||||
feature: feature
|
||||
}])
|
||||
|
||||
new ShowDataLayer(
|
||||
featureSource,
|
||||
map0.leafletMap,
|
||||
new UIEventSource<LayoutConfig>(layout)
|
||||
)
|
||||
|
||||
const map1 = new Minimap({
|
||||
const location = new UIEventSource<Loc>({
|
||||
lon: 4.84771728515625,
|
||||
lat: 51.17920846421931,
|
||||
zoom: 14
|
||||
})
|
||||
const map0 = new Minimap({
|
||||
location: location,
|
||||
allowMoving: true,
|
||||
background: new AvailableBaseLayers(location).availableEditorLayers.map(layers => layers[5])
|
||||
},
|
||||
)
|
||||
background: new AvailableBaseLayers(location).availableEditorLayers.map(layers => layers[2])
|
||||
})
|
||||
map0.SetStyle("width: 500px; height: 250px; overflow: hidden; border: 2px solid red")
|
||||
.AttachTo("maindiv")
|
||||
|
||||
map1.SetStyle("width: 500px; height: 250px; overflow: hidden; border : 2px solid black")
|
||||
.AttachTo("extradiv")
|
||||
const layout = AllKnownLayouts.layoutsList[1]
|
||||
State.state = new State(layout)
|
||||
console.log("LAYOUT is", layout.id)
|
||||
|
||||
const feature = {
|
||||
"type": "Feature",
|
||||
_matching_layer_id: "bike_repair_station",
|
||||
"properties": {
|
||||
id: "node/-1",
|
||||
"amenity": "bicycle_repair_station"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
4.84771728515625,
|
||||
51.17920846421931
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
State.state.allElements.addOrGetElement(feature)
|
||||
|
||||
const featureSource = new UIEventSource([{
|
||||
freshness: new Date(),
|
||||
feature: feature
|
||||
}])
|
||||
|
||||
new ShowDataLayer(
|
||||
featureSource,
|
||||
map0.leafletMap,
|
||||
new UIEventSource<LayoutConfig>(layout)
|
||||
)
|
||||
|
||||
const map1 = new Minimap({
|
||||
location: location,
|
||||
allowMoving: true,
|
||||
background: new AvailableBaseLayers(location).availableEditorLayers.map(layers => layers[5])
|
||||
},
|
||||
)
|
||||
|
||||
map1.SetStyle("width: 500px; height: 250px; overflow: hidden; border : 2px solid black")
|
||||
.AttachTo("extradiv")
|
||||
|
||||
|
||||
new ShowDataLayer(
|
||||
featureSource,
|
||||
map1.leafletMap,
|
||||
new UIEventSource<LayoutConfig>(layout)
|
||||
)
|
||||
|
||||
|
||||
|
||||
new ShowDataLayer(
|
||||
featureSource,
|
||||
map1.leafletMap,
|
||||
new UIEventSource<LayoutConfig>(layout)
|
||||
)
|
||||
|
||||
featureSource.ping()
|
||||
|
||||
// */
|
||||
featureSource.ping()
|
||||
}
|
||||
//*/
|
||||
State.state= new State(undefined)
|
||||
new Combine([
|
||||
new DeleteButton("node/8598664388"),
|
||||
]).AttachTo("maindiv")
|
||||
|
|
32
test/OsmObject.spec.ts
Normal file
32
test/OsmObject.spec.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import T from "./TestHelper";
|
||||
import {OsmObject} from "../Logic/Osm/OsmObject";
|
||||
import ScriptUtils from "../scripts/ScriptUtils";
|
||||
|
||||
export default class OsmObjectSpec extends T {
|
||||
constructor() {
|
||||
super("OsmObject", [
|
||||
[
|
||||
"Download referencing ways",
|
||||
() => {
|
||||
let downloaded = false;
|
||||
OsmObject.DownloadReferencingWays("node/1124134958", ways => {
|
||||
downloaded = true;
|
||||
console.log(ways)
|
||||
})
|
||||
let timeout = 10
|
||||
while (!downloaded && timeout >= 0) {
|
||||
ScriptUtils.sleep(1000)
|
||||
|
||||
timeout--;
|
||||
}
|
||||
if(!downloaded){
|
||||
throw "Timeout: referencing ways not found"
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import {Utils} from "../Utils";
|
||||
Utils.runningFromConsole = true;
|
||||
import {Utils} from "../Utils";Utils.runningFromConsole = true;
|
||||
import TagSpec from "./Tag.spec";
|
||||
import ImageAttributionSpec from "./ImageAttribution.spec";
|
||||
import GeoOperationsSpec from "./GeoOperations.spec";
|
||||
|
@ -10,6 +9,10 @@ import OsmConnectionSpec from "./OsmConnection.spec";
|
|||
import T from "./TestHelper";
|
||||
import {FixedUiElement} from "../UI/Base/FixedUiElement";
|
||||
import Combine from "../UI/Base/Combine";
|
||||
import OsmObjectSpec from "./OsmObject.spec";
|
||||
import ScriptUtils from "../scripts/ScriptUtils";
|
||||
|
||||
|
||||
|
||||
export default class TestAll {
|
||||
private needsBrowserTests: T[] = [new OsmConnectionSpec()]
|
||||
|
@ -26,8 +29,9 @@ export default class TestAll {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScriptUtils.fixUtils()
|
||||
const allTests = [
|
||||
new OsmObjectSpec(),
|
||||
new TagSpec(),
|
||||
new ImageAttributionSpec(),
|
||||
new GeoOperationsSpec(),
|
||||
|
@ -39,6 +43,6 @@ const allTests = [
|
|||
|
||||
for (const test of allTests) {
|
||||
if (test.failures.length > 0) {
|
||||
throw "Some test failed"
|
||||
throw "Some test failed: "+test.failures.join(", ")
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue