First steps for a delete button

This commit is contained in:
pietervdvn 2021-06-28 18:06:54 +02:00
parent b7798a470c
commit 985e97d43b
10 changed files with 246 additions and 75 deletions

View file

@ -13,6 +13,7 @@ export default class UserDetails {
public loggedIn = false; public loggedIn = false;
public name = "Not logged in"; public name = "Not logged in";
public uid: number;
public csCount = 0; public csCount = 0;
public img: string; public img: string;
public unreadMessages = 0; public unreadMessages = 0;
@ -167,6 +168,7 @@ export class OsmConnection {
data.loggedIn = true; data.loggedIn = true;
console.log("Login completed, userinfo is ", userInfo); console.log("Login completed, userinfo is ", userInfo);
data.name = userInfo.getAttribute('display_name'); data.name = userInfo.getAttribute('display_name');
data.uid= Number(userInfo.getAttribute("id"))
data.csCount = userInfo.getElementsByTagName("changesets")[0].getAttribute("count"); data.csCount = userInfo.getElementsByTagName("changesets")[0].getAttribute("count");
data.img = undefined; data.img = undefined;

View file

@ -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 splitted = id.split("/");
const type = splitted[0]; const type = splitted[0];
const idN = splitted[1]; 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 elements: any[] = data.elements;
const osmObjects: OsmObject[] = [] const osmObjects: OsmObject[] = []
for (const element of elements) { for (const element of elements) {

View file

@ -7,11 +7,13 @@ export default class Constants {
// The user journey states thresholds when a new feature gets unlocked // The user journey states thresholds when a new feature gets unlocked
public static userJourney = { public static userJourney = {
moreScreenUnlock: 1, moreScreenUnlock: 1,
personalLayoutUnlock: 15, personalLayoutUnlock: 5,
historyLinkVisible: 20, historyLinkVisible: 10,
deletePointsOfOthersUnlock: 15,
tagsVisibleAt: 25, tagsVisibleAt: 25,
mapCompleteHelpUnlock: 50,
tagsVisibleAndWikiLinked: 30, tagsVisibleAndWikiLinked: 30,
mapCompleteHelpUnlock: 50,
themeGeneratorReadOnlyUnlock: 50, themeGeneratorReadOnlyUnlock: 50,
themeGeneratorFullUnlock: 500, themeGeneratorFullUnlock: 500,
addNewPointWithUnreadMessagesUnlock: 500, addNewPointWithUnreadMessagesUnlock: 500,

7
UI/Base/Loading.ts Normal file
View 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
View 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"))
);
}
}

View file

@ -343,6 +343,7 @@ export class Utils {
} }
}; };
xhr.open('GET', url); xhr.open('GET', url);
xhr.setRequestHeader("accept","application/json")
xhr.send(); xhr.send();
}catch(e){ }catch(e){
reject(e) reject(e)

View file

@ -27,6 +27,15 @@
"intro": "MapComplete is an OpenStreetMap-viewer and editor, which shows you information about a specific theme.", "intro": "MapComplete is an OpenStreetMap-viewer and editor, which shows you information about a specific theme.",
"pickTheme": "Pick a theme below to get started." "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": { "general": {
"loginWithOpenStreetMap": "Login with OpenStreetMap", "loginWithOpenStreetMap": "Login with OpenStreetMap",
"welcomeBack": "You are logged in, welcome back!", "welcomeBack": "You are logged in, welcome back!",

138
test.ts
View file

@ -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 Combine from "./UI/Base/Combine";
import {VariableUiElement} from "./UI/Base/VariableUIElement"; import {VariableUiElement} from "./UI/Base/VariableUIElement";
import {UIEventSource} from "./Logic/UIEventSource"; import {UIEventSource} from "./Logic/UIEventSource";
@ -69,75 +73,77 @@ function TestAllInputMethods() {
})).AttachTo("maindiv") })).AttachTo("maindiv")
} }
function TestMiniMap() {
const location = new UIEventSource<Loc>({ const location = new UIEventSource<Loc>({
lon: 4.84771728515625, lon: 4.84771728515625,
lat: 51.17920846421931, lat: 51.17920846421931,
zoom: 14 zoom: 14
}) })
const map0 = new Minimap({ 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({
location: location, location: location,
allowMoving: true, 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") const layout = AllKnownLayouts.layoutsList[1]
.AttachTo("extradiv") 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)
)
featureSource.ping()
}
new ShowDataLayer( //*/
featureSource, State.state= new State(undefined)
map1.leafletMap, new Combine([
new UIEventSource<LayoutConfig>(layout) new DeleteButton("node/8598664388"),
) ]).AttachTo("maindiv")
featureSource.ping()
// */

32
test/OsmObject.spec.ts Normal file
View 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"
}
}
]
]);
}
}

View file

@ -1,5 +1,4 @@
import {Utils} from "../Utils"; import {Utils} from "../Utils";Utils.runningFromConsole = true;
Utils.runningFromConsole = true;
import TagSpec from "./Tag.spec"; import TagSpec from "./Tag.spec";
import ImageAttributionSpec from "./ImageAttribution.spec"; import ImageAttributionSpec from "./ImageAttribution.spec";
import GeoOperationsSpec from "./GeoOperations.spec"; import GeoOperationsSpec from "./GeoOperations.spec";
@ -10,6 +9,10 @@ import OsmConnectionSpec from "./OsmConnection.spec";
import T from "./TestHelper"; import T from "./TestHelper";
import {FixedUiElement} from "../UI/Base/FixedUiElement"; import {FixedUiElement} from "../UI/Base/FixedUiElement";
import Combine from "../UI/Base/Combine"; import Combine from "../UI/Base/Combine";
import OsmObjectSpec from "./OsmObject.spec";
import ScriptUtils from "../scripts/ScriptUtils";
export default class TestAll { export default class TestAll {
private needsBrowserTests: T[] = [new OsmConnectionSpec()] private needsBrowserTests: T[] = [new OsmConnectionSpec()]
@ -26,8 +29,9 @@ export default class TestAll {
} }
} }
} }
ScriptUtils.fixUtils()
const allTests = [ const allTests = [
new OsmObjectSpec(),
new TagSpec(), new TagSpec(),
new ImageAttributionSpec(), new ImageAttributionSpec(),
new GeoOperationsSpec(), new GeoOperationsSpec(),
@ -39,6 +43,6 @@ const allTests = [
for (const test of allTests) { for (const test of allTests) {
if (test.failures.length > 0) { if (test.failures.length > 0) {
throw "Some test failed" throw "Some test failed: "+test.failures.join(", ")
} }
} }