forked from MapComplete/MapComplete
Refactor OsmObject to use eventsources, add first version of the delete button
This commit is contained in:
parent
ec7833b2ee
commit
bbfcee686f
15 changed files with 553 additions and 229 deletions
|
@ -56,7 +56,7 @@ export default class UserBadge extends Toggle {
|
|||
let messageSpan =
|
||||
new Link(
|
||||
new Combine([Svg.envelope, "" + user.totalMessages]).SetClass(linkStyle),
|
||||
'https://www.openstreetmap.org/messages/inbox',
|
||||
`${user.backend}/messages/inbox`,
|
||||
true
|
||||
)
|
||||
|
||||
|
@ -64,14 +64,14 @@ export default class UserBadge extends Toggle {
|
|||
const csCount =
|
||||
new Link(
|
||||
new Combine([Svg.star, "" + user.csCount]).SetClass(linkStyle),
|
||||
`https://www.openstreetmap.org/user/${user.name}/history`,
|
||||
`${user.backend}/user/${user.name}/history`,
|
||||
true);
|
||||
|
||||
|
||||
if (user.unreadMessages > 0) {
|
||||
messageSpan = new Link(
|
||||
new Combine([Svg.envelope, "" + user.unreadMessages]),
|
||||
'https://www.openstreetmap.org/messages/inbox',
|
||||
'${user.backend}/messages/inbox',
|
||||
true
|
||||
).SetClass("alert")
|
||||
}
|
||||
|
@ -83,22 +83,22 @@ export default class UserBadge extends Toggle {
|
|||
|
||||
const settings =
|
||||
new Link(Svg.gear_svg(),
|
||||
`https://www.openstreetmap.org/user/${encodeURIComponent(user.name)}/account`,
|
||||
`${user.backend}/user/${encodeURIComponent(user.name)}/account`,
|
||||
true)
|
||||
|
||||
|
||||
const userIcon = new Link(
|
||||
new Img(user.img)
|
||||
user.img === undefined ? Svg.osm_logo_ui() : new Img(user.img)
|
||||
.SetClass("rounded-full opacity-0 m-0 p-0 duration-500 w-16 h16 float-left")
|
||||
,
|
||||
`https://www.openstreetmap.org/user/${encodeURIComponent(user.name)}`,
|
||||
`${user.backend}/user/${encodeURIComponent(user.name)}`,
|
||||
true
|
||||
);
|
||||
|
||||
|
||||
const userName = new Link(
|
||||
new FixedUiElement(user.name),
|
||||
`https://www.openstreetmap.org/user/${user.name}`,
|
||||
`${user.backend}/user/${user.name}`,
|
||||
true);
|
||||
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ export default class PublicHolidayInput extends InputElement<string> {
|
|||
this._value = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
GetValue(): UIEventSource<string> {
|
||||
return this._value;
|
||||
}
|
||||
|
@ -25,18 +25,56 @@ export default class PublicHolidayInput extends InputElement<string> {
|
|||
IsValid(t: string): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const dropdown = new DropDown(
|
||||
Translations.t.general.opening_hours.open_during_ph.Clone(),
|
||||
[
|
||||
{shown: Translations.t.general.opening_hours.ph_not_known.Clone(), value: ""},
|
||||
{shown: Translations.t.general.opening_hours.ph_closed.Clone(), value: "off"},
|
||||
{shown: Translations.t.general.opening_hours.ph_open_as_usual.Clone(), value: "open"},
|
||||
{shown: Translations.t.general.opening_hours.ph_open.Clone(), value: " "},
|
||||
]
|
||||
).SetClass("inline-block");
|
||||
/*
|
||||
* Either "" (unknown), " " (opened) or "off" (closed)
|
||||
* */
|
||||
const mode = dropdown.GetValue();
|
||||
|
||||
|
||||
const start = new TextField({
|
||||
placeholder: "starthour",
|
||||
htmlType: "time"
|
||||
}).SetClass("inline-block");
|
||||
const end = new TextField({
|
||||
placeholder: "starthour",
|
||||
htmlType: "time"
|
||||
}).SetClass("inline-block");
|
||||
|
||||
const askHours = new Toggle(
|
||||
new Combine([
|
||||
Translations.t.general.opening_hours.opensAt.Clone(),
|
||||
start,
|
||||
Translations.t.general.opening_hours.openTill.Clone(),
|
||||
end
|
||||
]),
|
||||
undefined,
|
||||
mode.map(mode => mode === " ")
|
||||
)
|
||||
|
||||
this.SetupDataSync(mode, start.GetValue(), end.GetValue())
|
||||
|
||||
return new Combine([
|
||||
dropdown,
|
||||
askHours
|
||||
]).ConstructElement()
|
||||
}
|
||||
|
||||
private SetupDataSync(mode: UIEventSource<string>, startTime: UIEventSource<string>, endTime: UIEventSource<string>) {
|
||||
|
||||
const value = this._value;
|
||||
value.addCallbackAndRun(ph => {
|
||||
if (ph === undefined) {
|
||||
return;
|
||||
}
|
||||
const parsed = OH.ParsePHRule(ph);
|
||||
if (parsed === null) {
|
||||
return;
|
||||
}
|
||||
value.map(ph => OH.ParsePHRule(ph))
|
||||
.addCallbackAndRunD(parsed => {
|
||||
mode.setData(parsed.mode)
|
||||
startTime.setData(parsed.start)
|
||||
endTime.setData(parsed.end)
|
||||
|
@ -72,50 +110,5 @@ export default class PublicHolidayInput extends InputElement<string> {
|
|||
}, [startTime, endTime]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const dropdown = new DropDown(
|
||||
Translations.t.general.opening_hours.open_during_ph.Clone(),
|
||||
[
|
||||
{shown: Translations.t.general.opening_hours.ph_not_known.Clone(), value: ""},
|
||||
{shown: Translations.t.general.opening_hours.ph_closed.Clone(), value: "off"},
|
||||
{shown:Translations.t.general.opening_hours.ph_open_as_usual.Clone(), value: "open"},
|
||||
{shown: Translations.t.general.opening_hours.ph_open.Clone(), value: " "},
|
||||
]
|
||||
).SetClass("inline-block");
|
||||
/*
|
||||
* Either "" (unknown), " " (opened) or "off" (closed)
|
||||
* */
|
||||
const mode = dropdown.GetValue();
|
||||
|
||||
|
||||
const start = new TextField({
|
||||
placeholder: "starthour",
|
||||
htmlType: "time"
|
||||
}).SetClass("inline-block");
|
||||
const end = new TextField({
|
||||
placeholder: "starthour",
|
||||
htmlType: "time"
|
||||
}).SetClass("inline-block");
|
||||
|
||||
const askHours = new Toggle(
|
||||
new Combine([
|
||||
Translations.t.general.opening_hours.opensAt.Clone(),
|
||||
start,
|
||||
Translations.t.general.opening_hours.openTill.Clone(),
|
||||
end
|
||||
]),
|
||||
undefined,
|
||||
mode.map(mode => mode === " ")
|
||||
)
|
||||
|
||||
this.SetupDataSync(mode, start.GetValue(), end.GetValue())
|
||||
|
||||
return new Combine([
|
||||
dropdown,
|
||||
askHours
|
||||
]).ConstructElement()
|
||||
}
|
||||
|
||||
}
|
|
@ -1,72 +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";
|
||||
import DeleteAction from "../../Logic/Osm/DeleteAction";
|
||||
import {Tag} from "../../Logic/Tags/Tag";
|
||||
import CheckBoxes from "../Input/Checkboxes";
|
||||
import {RadioButton} from "../Input/RadioButton";
|
||||
import {FixedInputElement} from "../Input/FixedInputElement";
|
||||
import {TextField} from "../Input/TextField";
|
||||
|
||||
|
||||
export default class DeleteButton extends Toggle {
|
||||
constructor(id: string) {
|
||||
export default class DeleteWizard extends Toggle {
|
||||
/**
|
||||
* The UI-element which triggers 'deletion' (either soft or hard).
|
||||
*
|
||||
* - A 'hard deletion' is if the point is actually deleted from the OSM database
|
||||
* - A 'soft deletion' is if the point is not deleted, but the tagging is modified which will result in the point not being picked up by the filters anymore.
|
||||
* Apart having needing theme-specific tags added (which must be supplied by the theme creator), fixme='marked for deletion' will be added too
|
||||
*
|
||||
* A deletion is only possible if the user is logged in.
|
||||
* A soft deletion is only possible if tags are provided
|
||||
* A hard deletion is only possible if the user has sufficient rigts
|
||||
*
|
||||
* If no deletion is possible at all, the delete button will not be shown - but a reason will be shown instead.
|
||||
*
|
||||
* @param id: The id of the element to remove
|
||||
* @param softDeletionTags: the tags to apply if the user doesn't have permission to delete, e.g. 'disused:amenity=public_bookcase', 'amenity='. After applying, the element should not be picked up on the map anymore. If undefined, the wizard will only show up if the point can be (hard) deleted
|
||||
*/
|
||||
constructor(id: string, softDeletionTags? : Tag[]) {
|
||||
const t = Translations.t.delete
|
||||
|
||||
const hasRelations: UIEventSource<boolean> = new UIEventSource<boolean>(null)
|
||||
OsmObject.DownloadReferencingRelations(id, (rels) => {
|
||||
hasRelations.setData(rels.length > 0)
|
||||
})
|
||||
const deleteAction = new DeleteAction(id);
|
||||
|
||||
const deleteReasons = new RadioButton<string>(
|
||||
[new FixedInputElement(
|
||||
t.reasons.test, "test"
|
||||
),
|
||||
new FixedInputElement(t.reasons.disused, "disused"),
|
||||
new FixedInputElement(t.reasons.notFound, "not found"),
|
||||
new TextField()]
|
||||
|
||||
)
|
||||
|
||||
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
|
||||
const deleteButton = new SubtleButton(
|
||||
Svg.delete_icon_svg(),
|
||||
t.delete.Clone()
|
||||
).onClick(() => deleteAction.DoDelete(deleteReasons.GetValue().data))
|
||||
|
||||
|
||||
|
||||
|
||||
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()
|
||||
}
|
||||
deleteButton,
|
||||
new VariableUiElement(deleteAction.canBeDeleted.map(cbd => cbd.reason.Clone())),
|
||||
deleteAction.canBeDeleted.map(cbd => cbd.canBeDeleted)
|
||||
),
|
||||
|
||||
|
||||
|
||||
t.loginToDelete.Clone().onClick(State.state.osmConnection.AttemptLogin),
|
||||
State.state.osmConnection.isLoggedIn
|
||||
)
|
||||
|
||||
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"))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -69,7 +69,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
|
|||
if (!hasMinimap) {
|
||||
renderings.push(new TagRenderingAnswer(tags, SharedTagRenderings.SharedTagRendering.get("minimap")))
|
||||
}
|
||||
|
||||
|
||||
renderings.push(
|
||||
new VariableUiElement(
|
||||
State.state.osmConnection.userDetails.map(userdetails => {
|
||||
|
|
|
@ -35,7 +35,7 @@ export default class TagRenderingQuestion extends Combine {
|
|||
configuration: TagRenderingConfig,
|
||||
units: Unit[],
|
||||
afterSave?: () => void,
|
||||
cancelButton?: BaseUIElement
|
||||
cancelButton?: BaseUIElement,
|
||||
) {
|
||||
if (configuration === undefined) {
|
||||
throw "A question is needed for a question visualization"
|
||||
|
|
|
@ -62,6 +62,9 @@ export default class ShowDataLayer {
|
|||
const allFeats = features.data.map(ff => ff.feature);
|
||||
geoLayer = self.CreateGeojsonLayer();
|
||||
for (const feat of allFeats) {
|
||||
if(feat === undefined){
|
||||
continue
|
||||
}
|
||||
// @ts-ignore
|
||||
geoLayer.addData(feat);
|
||||
}
|
||||
|
@ -76,7 +79,13 @@ export default class ShowDataLayer {
|
|||
}
|
||||
|
||||
if (zoomToFeatures) {
|
||||
try{
|
||||
|
||||
mp.fitBounds(geoLayer.getBounds())
|
||||
|
||||
}catch(e){
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -169,8 +178,8 @@ export default class ShowDataLayer {
|
|||
infobox.Activate();
|
||||
});
|
||||
const self = this;
|
||||
State.state.selectedElement.addCallbackAndRun(selected => {
|
||||
if (selected === undefined || self._leafletMap.data === undefined) {
|
||||
State.state.selectedElement.addCallbackAndRunD(selected => {
|
||||
if ( self._leafletMap.data === undefined) {
|
||||
return;
|
||||
}
|
||||
if (leafletLayer.getPopup().isOpen()) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue