forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			153 lines
		
	
	
	
		
			6.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			153 lines
		
	
	
	
		
			6.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
 | 
						|
import { UIEventSource } from "../../Logic/UIEventSource"
 | 
						|
import { DefaultGuiState } from "../DefaultGuiState"
 | 
						|
import BaseUIElement from "../BaseUIElement"
 | 
						|
import Translations from "../i18n/Translations"
 | 
						|
import { GeoOperations } from "../../Logic/GeoOperations"
 | 
						|
import NearbyImages, { NearbyImageOptions, P4CPicture, SelectOneNearbyImage } from "./NearbyImages"
 | 
						|
import { SubstitutedTranslation } from "../SubstitutedTranslation"
 | 
						|
import { Tag } from "../../Logic/Tags/Tag"
 | 
						|
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
 | 
						|
import { And } from "../../Logic/Tags/And"
 | 
						|
import { SaveButton } from "./SaveButton"
 | 
						|
import Lazy from "../Base/Lazy"
 | 
						|
import { CheckBox } from "../Input/Checkboxes"
 | 
						|
import Slider from "../Input/Slider"
 | 
						|
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
 | 
						|
import Combine from "../Base/Combine"
 | 
						|
import { VariableUiElement } from "../Base/VariableUIElement"
 | 
						|
import Toggle from "../Input/Toggle"
 | 
						|
import Title from "../Base/Title"
 | 
						|
import { MapillaryLinkVis } from "./MapillaryLinkVis"
 | 
						|
import { SpecialVisualization } from "../SpecialVisualization"
 | 
						|
 | 
						|
export class NearbyImageVis implements SpecialVisualization {
 | 
						|
    args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [
 | 
						|
        {
 | 
						|
            name: "mode",
 | 
						|
            defaultValue: "expandable",
 | 
						|
            doc: "Indicates how this component is initialized. Options are: \n\n- `open`: always show and load the pictures\n- `collapsable`: show the pictures, but a user can collapse them\n- `expandable`: shown by default; but a user can collapse them.",
 | 
						|
        },
 | 
						|
        {
 | 
						|
            name: "mapillary",
 | 
						|
            defaultValue: "true",
 | 
						|
            doc: "If 'true', includes a link to mapillary on this location.",
 | 
						|
        },
 | 
						|
    ]
 | 
						|
    docs =
 | 
						|
        "A component showing nearby images loaded from various online services such as Mapillary. In edit mode and when used on a feature, the user can select an image to add to the feature"
 | 
						|
    funcName = "nearby_images"
 | 
						|
 | 
						|
    constr(
 | 
						|
        state: FeaturePipelineState,
 | 
						|
        tagSource: UIEventSource<any>,
 | 
						|
        args: string[],
 | 
						|
        guistate: DefaultGuiState
 | 
						|
    ): BaseUIElement {
 | 
						|
        const t = Translations.t.image.nearbyPictures
 | 
						|
        const mode: "open" | "expandable" | "collapsable" = <any>args[0]
 | 
						|
        const feature = state.allElements.ContainingFeatures.get(tagSource.data.id)
 | 
						|
        const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
 | 
						|
        const id: string = tagSource.data["id"]
 | 
						|
        const canBeEdited: boolean = !!id?.match("(node|way|relation)/-?[0-9]+")
 | 
						|
        const selectedImage = new UIEventSource<P4CPicture>(undefined)
 | 
						|
 | 
						|
        let saveButton: BaseUIElement = undefined
 | 
						|
        if (canBeEdited) {
 | 
						|
            const confirmText: BaseUIElement = new SubstitutedTranslation(
 | 
						|
                t.confirm,
 | 
						|
                tagSource,
 | 
						|
                state
 | 
						|
            )
 | 
						|
 | 
						|
            const onSave = async () => {
 | 
						|
                console.log("Selected a picture...", selectedImage.data)
 | 
						|
                const osmTags = selectedImage.data.osmTags
 | 
						|
                const tags: Tag[] = []
 | 
						|
                for (const key in osmTags) {
 | 
						|
                    tags.push(new Tag(key, osmTags[key]))
 | 
						|
                }
 | 
						|
                await state?.changes?.applyAction(
 | 
						|
                    new ChangeTagAction(id, new And(tags), tagSource.data, {
 | 
						|
                        theme: state?.layoutToUse.id,
 | 
						|
                        changeType: "link-image",
 | 
						|
                    })
 | 
						|
                )
 | 
						|
            }
 | 
						|
            saveButton = new SaveButton(
 | 
						|
                selectedImage,
 | 
						|
                state.osmConnection,
 | 
						|
                confirmText,
 | 
						|
                t.noImageSelected
 | 
						|
            )
 | 
						|
                .onClick(onSave)
 | 
						|
                .SetClass("flex justify-end")
 | 
						|
        }
 | 
						|
 | 
						|
        const nearby = new Lazy(() => {
 | 
						|
            const towardsCenter = new CheckBox(t.onlyTowards, false)
 | 
						|
 | 
						|
            const maxSearchRadius = 100
 | 
						|
            const stepSize = 10
 | 
						|
            const defaultValue = Math.floor(maxSearchRadius / (2 * stepSize)) * stepSize
 | 
						|
            const fromOsmPreferences = state?.osmConnection
 | 
						|
                ?.GetPreference("nearby-images-radius", "" + defaultValue)
 | 
						|
                .sync(
 | 
						|
                    (s) => Number(s),
 | 
						|
                    [],
 | 
						|
                    (i) => "" + i
 | 
						|
                )
 | 
						|
            const radiusValue = new UIEventSource(fromOsmPreferences.data)
 | 
						|
            radiusValue.addCallbackAndRunD((v) => fromOsmPreferences.setData(v))
 | 
						|
 | 
						|
            const radius = new Slider(stepSize, maxSearchRadius, {
 | 
						|
                value: radiusValue,
 | 
						|
                step: 10,
 | 
						|
            })
 | 
						|
            const alreadyInTheImage = AllImageProviders.LoadImagesFor(tagSource)
 | 
						|
            const options: NearbyImageOptions & { value } = {
 | 
						|
                lon,
 | 
						|
                lat,
 | 
						|
                searchRadius: maxSearchRadius,
 | 
						|
                shownRadius: radius.GetValue(),
 | 
						|
                value: selectedImage,
 | 
						|
                blacklist: alreadyInTheImage,
 | 
						|
                towardscenter: towardsCenter.GetValue(),
 | 
						|
                maxDaysOld: 365 * 3,
 | 
						|
            }
 | 
						|
            const slideshow = canBeEdited
 | 
						|
                ? new SelectOneNearbyImage(options, state)
 | 
						|
                : new NearbyImages(options, state)
 | 
						|
            const controls = new Combine([
 | 
						|
                towardsCenter,
 | 
						|
                new Combine([
 | 
						|
                    new VariableUiElement(
 | 
						|
                        radius.GetValue().map((radius) => t.withinRadius.Subs({ radius }))
 | 
						|
                    ),
 | 
						|
                    radius,
 | 
						|
                ]).SetClass("flex justify-between"),
 | 
						|
            ]).SetClass("flex flex-col")
 | 
						|
            return new Combine([
 | 
						|
                slideshow,
 | 
						|
                controls,
 | 
						|
                saveButton,
 | 
						|
                new MapillaryLinkVis().constr(state, tagSource, []).SetClass("mt-6"),
 | 
						|
            ])
 | 
						|
        })
 | 
						|
 | 
						|
        let withEdit: BaseUIElement = nearby
 | 
						|
        if (canBeEdited) {
 | 
						|
            withEdit = new Combine([t.hasMatchingPicture, nearby]).SetClass("flex flex-col")
 | 
						|
        }
 | 
						|
 | 
						|
        if (mode === "open") {
 | 
						|
            return withEdit
 | 
						|
        }
 | 
						|
        const toggleState = new UIEventSource<boolean>(mode === "collapsable")
 | 
						|
        return new Toggle(
 | 
						|
            new Combine([new Title(t.title), withEdit]),
 | 
						|
            new Title(t.browseNearby).onClick(() => toggleState.setData(true)),
 | 
						|
            toggleState
 | 
						|
        )
 | 
						|
    }
 | 
						|
}
 |