mapcomplete/UI/BigComponents/SimpleAddUI.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

312 lines
12 KiB
TypeScript
Raw Normal View History

2020-06-29 03:12:44 +02:00
/**
* Asks to add a feature at the last clicked location, at least if zoom is sufficient
*/
2023-01-06 03:37:22 +01:00
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Svg from "../../Svg"
import { SubtleButton } from "../Base/SubtleButton"
import Combine from "../Base/Combine"
import Translations from "../i18n/Translations"
import Constants from "../../Models/Constants"
import { TagUtils } from "../../Logic/Tags/TagUtils"
2021-06-12 02:58:32 +02:00
import BaseUIElement from "../BaseUIElement"
2021-06-14 02:39:23 +02:00
import { VariableUiElement } from "../Base/VariableUIElement"
import Toggle from "../Input/Toggle"
import UserDetails, { OsmConnection } from "../../Logic/Osm/OsmConnection"
import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction"
import { OsmObject, OsmWay } from "../../Logic/Osm/OsmObject"
import PresetConfig from "../../Models/ThemeConfig/PresetConfig"
import FilteredLayer from "../../Models/FilteredLayer"
import Loc from "../../Models/Loc"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { Changes } from "../../Logic/Osm/Changes"
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"
import { ElementStorage } from "../../Logic/ElementStorage"
import ConfirmLocationOfPoint from "../NewPoint/ConfirmLocationOfPoint"
2021-11-30 19:19:53 +01:00
import BaseLayer from "../../Models/BaseLayer"
2022-01-25 21:55:51 +01:00
import Loading from "../Base/Loading"
2022-03-23 12:42:47 +01:00
import Hash from "../../Logic/Web/Hash"
import { GlobalFilter } from "../../Logic/State/MapState"
2022-10-27 01:50:41 +02:00
import { WayId } from "../../Models/OsmFeature"
2022-11-08 14:37:48 +01:00
import { Tag } from "../../Logic/Tags/Tag"
2023-01-06 03:37:22 +01:00
import { LoginToggle } from "../Popup/LoginButton"
2021-06-14 02:39:23 +02:00
/*
* The SimpleAddUI is a single panel, which can have multiple states:
* - A list of presets which can be added by the user
* - A 'confirm-selection' button (or alternatively: please enable the layer)
* - A 'something is wrong - please soom in further'
* - A 'read your unread messages before adding a point'
*/
export interface PresetInfo extends PresetConfig {
2021-06-14 02:39:23 +02:00
name: string | BaseUIElement
icon: () => BaseUIElement
2022-01-25 21:55:51 +01:00
layerToAddTo: FilteredLayer
boundsFactor?: 0.25 | number
2021-06-14 02:39:23 +02:00
}
2021-01-08 16:49:42 +01:00
2023-01-06 03:37:22 +01:00
export default class SimpleAddUI extends LoginToggle {
2022-07-18 00:28:26 +02:00
/**
*
* @param isShown
* @param resetScrollSignal
* @param filterViewIsOpened
* @param state
* @param takeLocationFrom: defaults to state.lastClickLocation. Take this location to add the new point around
*/
constructor(
isShown: UIEventSource<boolean>,
resetScrollSignal: UIEventSource<void>,
filterViewIsOpened: UIEventSource<boolean>,
state: {
featureSwitchIsTesting: UIEventSource<boolean>
2023-01-06 03:37:22 +01:00
featureSwitchUserbadge: Store<boolean>
layoutToUse: LayoutConfig
osmConnection: OsmConnection
changes: Changes
allElements: ElementStorage
LastClickLocation: UIEventSource<{ lat: number; lon: number }>
featurePipeline: FeaturePipeline
selectedElement: UIEventSource<any>
locationControl: UIEventSource<Loc>
filteredLayers: UIEventSource<FilteredLayer[]>
featureSwitchFilter: UIEventSource<boolean>
backgroundLayer: UIEventSource<BaseLayer>
globalFilters: UIEventSource<GlobalFilter[]>
2022-07-18 00:28:26 +02:00
},
takeLocationFrom?: UIEventSource<{ lat: number; lon: number }>
) {
2021-06-14 02:39:23 +02:00
const readYourMessages = new Combine([
Translations.t.general.readYourMessages.Clone().SetClass("alert"),
new SubtleButton(Svg.envelope_ui(), Translations.t.general.goToInbox, {
url: "https://www.openstreetmap.org/messages/inbox",
newTab: false,
}),
])
2022-07-18 00:28:26 +02:00
takeLocationFrom = takeLocationFrom ?? state.LastClickLocation
2021-06-14 02:39:23 +02:00
const selectedPreset = new UIEventSource<PresetInfo>(undefined)
selectedPreset.addCallback((_) => {
resetScrollSignal.ping()
})
2021-06-14 02:39:23 +02:00
isShown.addCallback((_) => selectedPreset.setData(undefined)) // Clear preset selection when the UI is closed/opened
takeLocationFrom.addCallback((_) => selectedPreset.setData(undefined))
const presetsOverview = SimpleAddUI.CreateAllPresetsPanel(selectedPreset, state)
2022-06-13 01:35:50 +02:00
async function createNewPoint(
tags: Tag[],
2022-06-13 01:35:50 +02:00
location: { lat: number; lon: number },
snapOntoWay?: OsmWay
): Promise<void> {
tags.push(new Tag(Tag.newlyCreated.key, new Date().toISOString()))
2023-01-29 13:31:49 +01:00
if (snapOntoWay) {
tags.push(new Tag("_referencing_ways", "way/" + snapOntoWay.id))
}
const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, {
theme: state.layoutToUse?.id ?? "unkown",
changeType: "create",
snapOnto: snapOntoWay,
})
await state.changes.applyAction(newElementAction)
selectedPreset.setData(undefined)
isShown.setData(false)
2022-11-08 14:37:48 +01:00
const selectedFeature = state.allElements.ContainingFeatures.get(
newElementAction.newElementId
)
state.selectedElement.setData(selectedFeature)
2022-03-23 12:42:47 +01:00
Hash.hash.setData(newElementAction.newElementId)
}
2021-06-14 02:39:23 +02:00
const addUi = new VariableUiElement(
selectedPreset.map((preset) => {
if (preset === undefined) {
return presetsOverview
}
function confirm(
tags: any[],
location: { lat: number; lon: number },
2022-09-21 02:21:20 +02:00
snapOntoWayId?: WayId
) {
if (snapOntoWayId === undefined) {
createNewPoint(tags, location, undefined)
} else {
OsmObject.DownloadObject(snapOntoWayId).addCallbackAndRunD((way) => {
2022-09-21 02:21:20 +02:00
createNewPoint(tags, location, way)
return true
2022-09-08 21:40:48 +02:00
})
}
2022-09-08 21:40:48 +02:00
}
function cancel() {
selectedPreset.setData(undefined)
2021-06-14 02:39:23 +02:00
}
2022-04-01 12:51:55 +02:00
const message = Translations.t.general.add.addNew.Subs(
{ category: preset.name },
preset.name["context"]
2022-09-08 21:40:48 +02:00
)
return new ConfirmLocationOfPoint(
2022-09-08 21:40:48 +02:00
state,
filterViewIsOpened,
2022-09-08 21:40:48 +02:00
preset,
message,
2022-07-18 00:28:26 +02:00
takeLocationFrom.data,
2022-09-08 21:40:48 +02:00
confirm,
cancel,
() => {
2022-01-08 04:22:50 +01:00
isShown.setData(false)
},
{
cancelIcon: Svg.back_svg(),
2022-11-14 01:29:22 +01:00
cancelText: Translations.t.general.add.backToSelect,
2022-09-08 21:40:48 +02:00
}
)
})
)
2021-06-14 02:39:23 +02:00
super(
new Toggle(
new Toggle(
new Toggle(
2022-01-25 21:55:51 +01:00
new Loading(Translations.t.general.add.stillLoading).SetClass("alert"),
2021-10-20 19:12:28 +02:00
addUi,
state.featurePipeline.runningQuery
2021-06-14 02:39:23 +02:00
),
Translations.t.general.add.zoomInFurther.Clone().SetClass("alert"),
state.locationControl.map(
(loc) => loc.zoom >= Constants.userJourney.minZoomLevelToAddNewPoints
2022-09-08 21:40:48 +02:00
)
2021-06-14 02:39:23 +02:00
),
readYourMessages,
state.osmConnection.userDetails.map(
(userdetails: UserDetails) =>
2021-06-14 02:39:23 +02:00
userdetails.csCount >=
Constants.userJourney.addNewPointWithUnreadMessagesUnlock ||
userdetails.unreadMessages == 0
)
),
2023-01-06 03:37:22 +01:00
Translations.t.general.add.pleaseLogin,
state
2021-06-14 02:39:23 +02:00
)
2021-01-08 16:49:42 +01:00
}
public static CreateTagInfoFor(
preset: PresetInfo,
osmConnection: OsmConnection,
optionallyLinkToWiki = true
) {
const csCount = osmConnection.userDetails.data.csCount
2021-06-14 02:39:23 +02:00
return new Toggle(
2021-06-21 03:12:43 +02:00
Translations.t.general.add.presetInfo
.Subs({
2021-06-14 02:39:23 +02:00
tags: preset.tags
.map((t) =>
t.asHumanString(
optionallyLinkToWiki &&
csCount > Constants.userJourney.tagsVisibleAndWikiLinked,
true
2022-09-08 21:40:48 +02:00
)
)
2021-06-14 02:39:23 +02:00
.join("&"),
})
.SetStyle("word-break: break-all"),
2021-06-14 02:39:23 +02:00
undefined,
osmConnection.userDetails.map(
(userdetails) => userdetails.csCount >= Constants.userJourney.tagsVisibleAt
2021-06-14 02:39:23 +02:00
)
2022-09-08 21:40:48 +02:00
)
2021-06-14 02:39:23 +02:00
}
2021-01-08 16:49:42 +01:00
private static CreateAllPresetsPanel(
selectedPreset: UIEventSource<PresetInfo>,
state: {
featureSwitchIsTesting: UIEventSource<boolean>
filteredLayers: UIEventSource<FilteredLayer[]>
featureSwitchFilter: UIEventSource<boolean>
osmConnection: OsmConnection
}
): BaseUIElement {
const presetButtons = SimpleAddUI.CreatePresetButtons(state, selectedPreset)
2021-06-12 02:58:32 +02:00
let intro: BaseUIElement = Translations.t.general.add.intro
2021-01-08 16:49:42 +01:00
let testMode: BaseUIElement = new Toggle(
Translations.t.general.testing.SetClass("alert"),
undefined,
state.featureSwitchIsTesting
2022-09-08 21:40:48 +02:00
)
2021-01-08 16:49:42 +01:00
2021-06-14 02:39:23 +02:00
return new Combine([intro, testMode, presetButtons]).SetClass("flex flex-col")
2021-01-08 16:49:42 +01:00
}
private static CreatePresetSelectButton(preset: PresetInfo) {
2022-04-01 12:51:55 +02:00
const title = Translations.t.general.add.addNew.Subs(
{
category: preset.name,
},
preset.name["context"]
)
2021-06-14 02:39:23 +02:00
return new SubtleButton(
preset.icon(),
2021-01-08 16:49:42 +01:00
new Combine([
2022-04-01 12:51:55 +02:00
title.SetClass("font-bold"),
preset.description?.FirstSentence(),
2021-06-14 02:39:23 +02:00
]).SetClass("flex flex-col")
)
2021-01-08 16:49:42 +01:00
}
/*
* Generates the list with all the buttons.*/
private static CreatePresetButtons(
state: {
filteredLayers: UIEventSource<FilteredLayer[]>
featureSwitchFilter: UIEventSource<boolean>
osmConnection: OsmConnection
},
selectedPreset: UIEventSource<PresetInfo>
): BaseUIElement {
2021-01-08 16:49:42 +01:00
const allButtons = []
for (const layer of state.filteredLayers.data) {
if (layer.isDisplayed.data === false) {
// The layer is not displayed...
if (!state.featureSwitchFilter.data) {
// ...and we cannot enable the layer control -> we skip, as these presets can never be shown anyway
continue
}
if (layer.layerDef.name === undefined) {
// this layer can never be toggled on in any case, so we skip the presets
continue
}
2021-06-14 02:39:23 +02:00
}
const presets = layer.layerDef.presets
for (const preset of presets) {
2021-06-14 02:39:23 +02:00
const tags = TagUtils.KVtoProperties(preset.tags ?? [])
let icon: () => BaseUIElement = () =>
layer.layerDef.mapRendering[0]
.GenerateLeafletStyle(new UIEventSource<any>(tags), false)
2021-06-14 02:39:23 +02:00
.html.SetClass("w-12 h-12 block relative")
const presetInfo: PresetInfo = {
layerToAddTo: layer,
name: preset.title,
title: preset.title,
icon: icon,
preciseInput: preset.preciseInput,
...preset,
}
2021-06-14 02:39:23 +02:00
const button = SimpleAddUI.CreatePresetSelectButton(presetInfo)
2021-06-14 02:39:23 +02:00
button.onClick(() => {
selectedPreset.setData(presetInfo)
})
2021-01-08 16:49:42 +01:00
allButtons.push(button)
2020-07-31 16:17:16 +02:00
}
2020-06-29 03:12:44 +02:00
}
2021-06-14 02:39:23 +02:00
return new Combine(allButtons).SetClass("flex flex-col")
2020-06-29 03:12:44 +02:00
}
}