import Combine from "../Base/Combine" import { Store, UIEventSource } from "../../Logic/UIEventSource" import { BBox } from "../../Logic/BBox" import UserRelatedState from "../../Logic/State/UserRelatedState" import Translations from "../i18n/Translations" import { AllKnownLayouts } from "../../Customizations/AllKnownLayouts" import Constants from "../../Models/Constants" import { DropDown } from "../Input/DropDown" import { Utils } from "../../Utils" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import BaseLayer from "../../Models/BaseLayer" import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" import Loc from "../../Models/Loc" import Minimap from "../Base/Minimap" import Attribution from "../BigComponents/Attribution" import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer" import FilteredLayer, { FilterState } from "../../Models/FilteredLayer" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import Toggle from "../Input/Toggle" import { VariableUiElement } from "../Base/VariableUIElement" import { FixedUiElement } from "../Base/FixedUiElement" import { FlowStep } from "./FlowStep" import ScrollableFullScreen from "../Base/ScrollableFullScreen" import Title from "../Base/Title" import CheckBoxes from "../Input/Checkboxes" import { AllTagsPanel } from "../AllTagsPanel" import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch" class PreviewPanel extends ScrollableFullScreen { constructor(tags: UIEventSource) { super( (_) => new FixedUiElement("Element to import"), (_) => new Combine(["The tags are:", new AllTagsPanel(tags)]).SetClass("flex flex-col"), "element" ) } } /** * Shows the data to import on a map, asks for the correct layer to be selected */ export class MapPreview extends Combine implements FlowStep<{ bbox: BBox; layer: LayerConfig; features: any[] }> { public readonly IsValid: Store public readonly Value: Store<{ bbox: BBox; layer: LayerConfig; features: any[] }> constructor( state: UserRelatedState, geojson: { features: { properties: any; geometry: { coordinates: [number, number] } }[] } ) { const t = Translations.t.importHelper.mapPreview const propertyKeys = new Set() for (const f of geojson.features) { Object.keys(f.properties).forEach((key) => propertyKeys.add(key)) } const availableLayers = AllKnownLayouts.AllPublicLayers().filter( (l) => l.name !== undefined && Constants.priviliged_layers.indexOf(l.id) < 0 ) const layerPicker = new DropDown( t.selectLayer, [{ shown: t.selectLayer, value: undefined }].concat( availableLayers.map((l) => ({ shown: l.name, value: l, })) ) ) let autodetected = new UIEventSource(false) for (const layer of availableLayers) { const mismatched = geojson.features.some( (f) => !layer.source.osmTags.matchesProperties(f.properties) ) if (!mismatched) { console.log("Autodected layer", layer.id) layerPicker.GetValue().setData(layer) layerPicker.GetValue().addCallback((_) => autodetected.setData(false)) autodetected.setData(true) break } } const withId = geojson.features.map((f, i) => { const copy = Utils.Clone(f) copy.properties.id = "to-import/" + i return copy }) const matching: Store<{ properties: any; geometry: { coordinates: [number, number] } }[]> = layerPicker.GetValue().map((layer: LayerConfig) => { if (layer === undefined) { return [] } const matching: { properties: any; geometry: { coordinates: [number, number] } }[] = [] for (const feature of withId) { if (layer.source.osmTags.matchesProperties(feature.properties)) { matching.push(feature) } } return matching }) const background = new UIEventSource(AvailableBaseLayers.osmCarto) const location = new UIEventSource({ lat: 0, lon: 0, zoom: 1 }) const currentBounds = new UIEventSource(undefined) const map = Minimap.createMiniMap({ allowMoving: true, location, background, bounds: currentBounds, attribution: new Attribution( location, state.osmConnection.userDetails, undefined, currentBounds ), }) const layerControl = new BackgroundMapSwitch( { backgroundLayer: background, locationControl: location, }, background ) map.SetClass("w-full").SetStyle("height: 500px") new ShowDataMultiLayer({ layers: new UIEventSource( AllKnownLayouts.AllPublicLayers() .filter((l) => l.source.geojsonSource === undefined) .map((l) => ({ layerDef: l, isDisplayed: new UIEventSource(true), appliedFilters: new UIEventSource>(undefined), })) ), zoomToFeatures: true, features: StaticFeatureSource.fromDateless( matching.map((features) => features.map((feature) => ({ feature }))) ), leafletMap: map.leafletMap, popup: (tag) => new PreviewPanel(tag).SetClass("font-lg"), }) var bbox = matching.map((feats) => BBox.bboxAroundAll(feats.map((f) => new BBox([f.geometry.coordinates]))) ) const mismatchIndicator = new VariableUiElement( matching.map((matching) => { if (matching === undefined) { return undefined } const diff = geojson.features.length - matching.length if (diff === 0) { return undefined } const obligatory = layerPicker .GetValue() .data?.source?.osmTags?.asHumanString(false, false, {}) return t.mismatch.Subs({ count: diff, tags: obligatory }).SetClass("alert") }) ) const confirm = new CheckBoxes([t.confirm]) super([ new Title(t.title, 1), layerPicker, new Toggle(t.autodetected.SetClass("thank"), undefined, autodetected), mismatchIndicator, map, layerControl, confirm, ]) this.Value = bbox.map( (bbox) => ({ bbox, features: geojson.features, layer: layerPicker.GetValue().data, }), [layerPicker.GetValue()] ) this.IsValid = matching.map( (matching) => { if (matching === undefined) { return false } if (confirm.GetValue().data.length !== 1) { return false } const diff = geojson.features.length - matching.length return diff === 0 }, [confirm.GetValue()] ) } }