forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			258 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			258 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { UIEventSource } from "../../Logic/UIEventSource"
 | |
| import { OsmConnection } from "../../Logic/Osm/OsmConnection"
 | |
| import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"
 | |
| import BaseUIElement from "../BaseUIElement"
 | |
| import LocationInput from "../Input/LocationInput"
 | |
| import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"
 | |
| import { BBox } from "../../Logic/BBox"
 | |
| import { TagUtils } from "../../Logic/Tags/TagUtils"
 | |
| import { SubtleButton } from "../Base/SubtleButton"
 | |
| import Combine from "../Base/Combine"
 | |
| import Translations from "../i18n/Translations"
 | |
| import Svg from "../../Svg"
 | |
| import Toggle from "../Input/Toggle"
 | |
| import SimpleAddUI, { PresetInfo } from "../BigComponents/SimpleAddUI"
 | |
| import BaseLayer from "../../Models/BaseLayer"
 | |
| import Img from "../Base/Img"
 | |
| import Title from "../Base/Title"
 | |
| import { GlobalFilter } from "../../Logic/State/MapState"
 | |
| import { VariableUiElement } from "../Base/VariableUIElement"
 | |
| import { Tag } from "../../Logic/Tags/Tag"
 | |
| import { WayId } from "../../Models/OsmFeature"
 | |
| 
 | |
| export default class ConfirmLocationOfPoint extends Combine {
 | |
|     constructor(
 | |
|         state: {
 | |
|             globalFilters: UIEventSource<GlobalFilter[]>
 | |
|             featureSwitchIsTesting: UIEventSource<boolean>
 | |
|             osmConnection: OsmConnection
 | |
|             featurePipeline: FeaturePipeline
 | |
|             backgroundLayer?: UIEventSource<BaseLayer>
 | |
|         },
 | |
|         filterViewIsOpened: UIEventSource<boolean>,
 | |
|         preset: PresetInfo,
 | |
|         confirmText: BaseUIElement,
 | |
|         loc: { lon: number; lat: number },
 | |
|         confirm: (
 | |
|             tags: any[],
 | |
|             location: { lat: number; lon: number },
 | |
|             snapOntoWayId: WayId | undefined
 | |
|         ) => void,
 | |
|         cancel: () => void,
 | |
|         closePopup: () => void
 | |
|     ) {
 | |
|         let preciseInput: LocationInput = undefined
 | |
|         if (preset.preciseInput !== undefined) {
 | |
|             // Create location input
 | |
| 
 | |
|             // We uncouple the event source
 | |
|             const zloc = { ...loc, zoom: 19 }
 | |
|             const locationSrc = new UIEventSource(zloc)
 | |
| 
 | |
|             let backgroundLayer = new UIEventSource(
 | |
|                 state?.backgroundLayer?.data ?? AvailableBaseLayers.osmCarto
 | |
|             )
 | |
|             if (preset.preciseInput.preferredBackground) {
 | |
|                 const defaultBackground = AvailableBaseLayers.SelectBestLayerAccordingTo(
 | |
|                     locationSrc,
 | |
|                     new UIEventSource<string | string[]>(preset.preciseInput.preferredBackground)
 | |
|                 )
 | |
|                 // Note that we _break the link_ here, as the minimap will take care of the switching!
 | |
|                 backgroundLayer.setData(defaultBackground.data)
 | |
|             }
 | |
| 
 | |
|             let snapToFeatures: UIEventSource<{ feature: any }[]> = undefined
 | |
|             let mapBounds: UIEventSource<BBox> = undefined
 | |
|             if (preset.preciseInput.snapToLayers && preset.preciseInput.snapToLayers.length > 0) {
 | |
|                 snapToFeatures = new UIEventSource<{ feature: any }[]>([])
 | |
|                 mapBounds = new UIEventSource<BBox>(undefined)
 | |
|             }
 | |
| 
 | |
|             const tags = TagUtils.KVtoProperties(preset.tags ?? [])
 | |
|             preciseInput = new LocationInput({
 | |
|                 mapBackground: backgroundLayer,
 | |
|                 centerLocation: locationSrc,
 | |
|                 snapTo: snapToFeatures,
 | |
|                 snappedPointTags: tags,
 | |
|                 maxSnapDistance: preset.preciseInput.maxSnapDistance,
 | |
|                 bounds: mapBounds,
 | |
|                 state: <any>state,
 | |
|             })
 | |
|             preciseInput.installBounds(preset.boundsFactor ?? 0.25, true)
 | |
|             preciseInput
 | |
|                 .SetClass("rounded-xl overflow-hidden border border-gray")
 | |
|                 .SetStyle("height: 18rem; max-height: 50vh")
 | |
| 
 | |
|             if (preset.preciseInput.snapToLayers && preset.preciseInput.snapToLayers.length > 0) {
 | |
|                 // We have to snap to certain layers.
 | |
|                 // Lets fetch them
 | |
| 
 | |
|                 let loadedBbox: BBox = undefined
 | |
|                 mapBounds?.addCallbackAndRunD((bbox) => {
 | |
|                     if (loadedBbox !== undefined && bbox.isContainedIn(loadedBbox)) {
 | |
|                         // All is already there
 | |
|                         // return;
 | |
|                     }
 | |
| 
 | |
|                     bbox = bbox.pad(
 | |
|                         Math.max(preset.boundsFactor ?? 0.25, 2),
 | |
|                         Math.max(preset.boundsFactor ?? 0.25, 2)
 | |
|                     )
 | |
|                     loadedBbox = bbox
 | |
|                     const allFeatures: { feature: any }[] = []
 | |
|                     preset.preciseInput.snapToLayers.forEach((layerId) => {
 | |
|                         console.log("Snapping to", layerId)
 | |
|                         state.featurePipeline
 | |
|                             .GetFeaturesWithin(layerId, bbox)
 | |
|                             ?.forEach((feats) =>
 | |
|                                 allFeatures.push(...feats.map((f) => ({ feature: f })))
 | |
|                             )
 | |
|                     })
 | |
|                     console.log("Snapping to", allFeatures)
 | |
|                     snapToFeatures.setData(allFeatures)
 | |
|                 })
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         let confirmButton: BaseUIElement = new SubtleButton(
 | |
|             preset.icon(),
 | |
|             new Combine([
 | |
|                 confirmText,
 | |
|                 Translations.t.general.add.warnVisibleForEveryone.Clone().SetClass("alert"),
 | |
|             ]).SetClass("flex flex-col")
 | |
|         )
 | |
|             .SetClass("font-bold break-words")
 | |
|             .onClick(() => {
 | |
|                 console.log(
 | |
|                     "The confirmLocationPanel - precise input yielded ",
 | |
|                     preciseInput?.GetValue()?.data
 | |
|                 )
 | |
|                 const globalFilterTagsToAdd: Tag[][] = state.globalFilters.data
 | |
|                     .filter((gf) => gf.onNewPoint !== undefined)
 | |
|                     .map((gf) => gf.onNewPoint.tags)
 | |
|                 const globalTags: Tag[] = [].concat(...globalFilterTagsToAdd)
 | |
|                 console.log("Global tags to add are: ", globalTags)
 | |
|                 confirm(
 | |
|                     [...preset.tags, ...globalTags],
 | |
|                     preciseInput?.GetValue()?.data ?? loc,
 | |
|                     preciseInput?.snappedOnto?.data?.properties?.id
 | |
|                 )
 | |
|             })
 | |
| 
 | |
|         if (preciseInput !== undefined) {
 | |
|             confirmButton = new Combine([preciseInput, confirmButton])
 | |
|         }
 | |
| 
 | |
|         const openLayerControl = new SubtleButton(
 | |
|             Svg.layers_ui(),
 | |
|             new Combine([
 | |
|                 Translations.t.general.add.layerNotEnabled
 | |
|                     .Subs({ layer: preset.layerToAddTo.layerDef.name })
 | |
|                     .SetClass("alert"),
 | |
|                 Translations.t.general.add.openLayerControl,
 | |
|             ])
 | |
|         ).onClick(() => filterViewIsOpened.setData(true))
 | |
| 
 | |
|         let openLayerOrConfirm = new Toggle(
 | |
|             confirmButton,
 | |
|             openLayerControl,
 | |
|             preset.layerToAddTo.isDisplayed
 | |
|         )
 | |
| 
 | |
|         const disableFilter = new SubtleButton(
 | |
|             new Combine([
 | |
|                 Svg.filter_ui().SetClass("absolute w-full"),
 | |
|                 Svg.cross_bottom_right_svg().SetClass("absolute red-svg"),
 | |
|             ]).SetClass("relative"),
 | |
|             new Combine([
 | |
|                 Translations.t.general.add.disableFiltersExplanation.Clone(),
 | |
|                 Translations.t.general.add.disableFilters.Clone().SetClass("text-xl"),
 | |
|             ]).SetClass("flex flex-col")
 | |
|         ).onClick(() => {
 | |
|             const appliedFilters = preset.layerToAddTo.appliedFilters
 | |
|             appliedFilters.data.forEach((_, k) => appliedFilters.data.set(k, undefined))
 | |
|             appliedFilters.ping()
 | |
|             cancel()
 | |
|             closePopup()
 | |
|         })
 | |
| 
 | |
|         // We assume the number of global filters won't change during the run of the program
 | |
|         for (let i = 0; i < state.globalFilters.data.length; i++) {
 | |
|             const hasBeenCheckedOf = new UIEventSource(false)
 | |
| 
 | |
|             const filterConfirmPanel = new VariableUiElement(
 | |
|                 state.globalFilters.map((gfs) => {
 | |
|                     const gf = gfs[i]
 | |
|                     const confirm = gf.onNewPoint?.confirmAddNew?.Subs({ preset: preset.title })
 | |
|                     return new Combine([
 | |
|                         gf.onNewPoint?.safetyCheck,
 | |
|                         new SubtleButton(Svg.confirm_svg(), confirm).onClick(() =>
 | |
|                             hasBeenCheckedOf.setData(true)
 | |
|                         ),
 | |
|                     ])
 | |
|                 })
 | |
|             )
 | |
| 
 | |
|             openLayerOrConfirm = new Toggle(
 | |
|                 openLayerOrConfirm,
 | |
|                 filterConfirmPanel,
 | |
|                 state.globalFilters.map(
 | |
|                     (f) => hasBeenCheckedOf.data || f[i]?.onNewPoint === undefined,
 | |
|                     [hasBeenCheckedOf]
 | |
|                 )
 | |
|             )
 | |
|         }
 | |
| 
 | |
|         const hasActiveFilter = preset.layerToAddTo.appliedFilters.map((appliedFilters) => {
 | |
|             const activeFilters = Array.from(appliedFilters.values()).filter(
 | |
|                 (f) => f?.currentFilter !== undefined
 | |
|             )
 | |
|             return activeFilters.length === 0
 | |
|         })
 | |
| 
 | |
|         // If at least one filter is active which _might_ hide a newly added item, this blocks the preset and requests the filter to be disabled
 | |
|         const disableFiltersOrConfirm = new Toggle(
 | |
|             openLayerOrConfirm,
 | |
|             disableFilter,
 | |
|             hasActiveFilter
 | |
|         )
 | |
| 
 | |
|         const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, state.osmConnection)
 | |
| 
 | |
|         const cancelButton = new SubtleButton(
 | |
|             Svg.close_ui(),
 | |
|             Translations.t.general.cancel
 | |
|         ).onClick(cancel)
 | |
| 
 | |
|         let examples: BaseUIElement = undefined
 | |
|         if (preset.exampleImages !== undefined && preset.exampleImages.length > 0) {
 | |
|             examples = new Combine([
 | |
|                 new Title(
 | |
|                     preset.exampleImages.length == 1
 | |
|                         ? Translations.t.general.example
 | |
|                         : Translations.t.general.examples
 | |
|                 ),
 | |
|                 new Combine(
 | |
|                     preset.exampleImages.map((img) =>
 | |
|                         new Img(img).SetClass("h-64 m-1 w-auto rounded-lg")
 | |
|                     )
 | |
|                 ).SetClass("flex flex-wrap items-stretch"),
 | |
|             ])
 | |
|         }
 | |
| 
 | |
|         super([
 | |
|             new Toggle(
 | |
|                 Translations.t.general.testing.SetClass("alert"),
 | |
|                 undefined,
 | |
|                 state.featureSwitchIsTesting
 | |
|             ),
 | |
|             disableFiltersOrConfirm,
 | |
|             cancelButton,
 | |
|             preset.description,
 | |
|             examples,
 | |
|             tagInfo,
 | |
|         ])
 | |
| 
 | |
|         this.SetClass("flex flex-col")
 | |
|     }
 | |
| }
 |