forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			246 lines
		
	
	
		
			No EOL
		
	
	
		
			8.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			246 lines
		
	
	
		
			No EOL
		
	
	
		
			8.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import FeaturePipelineState from "../Logic/State/FeaturePipelineState";
 | 
						|
import State from "../State";
 | 
						|
import {Utils} from "../Utils";
 | 
						|
import {UIEventSource} from "../Logic/UIEventSource";
 | 
						|
import FullWelcomePaneWithTabs from "./BigComponents/FullWelcomePaneWithTabs";
 | 
						|
import MapControlButton from "./MapControlButton";
 | 
						|
import Svg from "../Svg";
 | 
						|
import Toggle from "./Input/Toggle";
 | 
						|
import UserBadge from "./BigComponents/UserBadge";
 | 
						|
import SearchAndGo from "./BigComponents/SearchAndGo";
 | 
						|
import BaseUIElement from "./BaseUIElement";
 | 
						|
import LeftControls from "./BigComponents/LeftControls";
 | 
						|
import RightControls from "./BigComponents/RightControls";
 | 
						|
import CenterMessageBox from "./CenterMessageBox";
 | 
						|
import ShowDataLayer from "./ShowDataLayer/ShowDataLayer";
 | 
						|
import ScrollableFullScreen from "./Base/ScrollableFullScreen";
 | 
						|
import Translations from "./i18n/Translations";
 | 
						|
import SimpleAddUI from "./BigComponents/SimpleAddUI";
 | 
						|
import StrayClickHandler from "../Logic/Actors/StrayClickHandler";
 | 
						|
import {DefaultGuiState} from "./DefaultGuiState";
 | 
						|
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
 | 
						|
import * as home_location_json from "../assets/layers/home_location/home_location.json";
 | 
						|
import NewNoteUi from "./Popup/NewNoteUi";
 | 
						|
import Combine from "./Base/Combine";
 | 
						|
import AddNewMarker from "./BigComponents/AddNewMarker";
 | 
						|
import FilteredLayer from "../Models/FilteredLayer";
 | 
						|
import ExtraLinkButton from "./BigComponents/ExtraLinkButton";
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * The default MapComplete GUI initializor
 | 
						|
 *
 | 
						|
 * Adds a welcome pane, contorl buttons, ... etc to index.html
 | 
						|
 */
 | 
						|
export default class DefaultGUI {
 | 
						|
    private readonly guiState: DefaultGuiState;
 | 
						|
    private readonly state: FeaturePipelineState;
 | 
						|
 | 
						|
 | 
						|
    constructor(state: FeaturePipelineState, guiState: DefaultGuiState) {
 | 
						|
        this.state = state;
 | 
						|
        this.guiState = guiState;
 | 
						|
 | 
						|
        }
 | 
						|
 | 
						|
    public setup(){
 | 
						|
        if (this.state.layoutToUse.customCss !== undefined) {
 | 
						|
            Utils.LoadCustomCss(this.state.layoutToUse.customCss);
 | 
						|
        }
 | 
						|
 | 
						|
        this.SetupUIElements();
 | 
						|
        this.SetupMap()
 | 
						|
 | 
						|
 | 
						|
        if (this.state.layoutToUse.customCss !== undefined && window.location.pathname.indexOf("index") >= 0) {
 | 
						|
            Utils.LoadCustomCss(this.state.layoutToUse.customCss)
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    public setupClickDialogOnMap(filterViewIsOpened: UIEventSource<boolean>, state: FeaturePipelineState) {
 | 
						|
 | 
						|
        const hasPresets = state.layoutToUse.layers.some(layer => layer.presets.length > 0);
 | 
						|
        const noteLayer: FilteredLayer = state.filteredLayers.data.filter(l => l.layerDef.id === "note")[0]
 | 
						|
        let addNewNoteDialog: (isShown: UIEventSource<boolean>) => BaseUIElement = undefined;
 | 
						|
        if (noteLayer !== undefined) {
 | 
						|
            addNewNoteDialog = (isShown) => new NewNoteUi(noteLayer, isShown, state)
 | 
						|
        }
 | 
						|
 | 
						|
        function setup() {
 | 
						|
            if (!hasPresets && addNewNoteDialog === undefined) {
 | 
						|
                return; // nothing to do
 | 
						|
            }
 | 
						|
            const newPointDialogIsShown = new UIEventSource<boolean>(false);
 | 
						|
            const addNewPoint = new ScrollableFullScreen(
 | 
						|
                () => hasPresets ? Translations.t.general.add.title : Translations.t.notes.createNoteTitle,
 | 
						|
                ({resetScrollSignal}) => {
 | 
						|
                    let addNew = undefined;
 | 
						|
                    if (hasPresets) {
 | 
						|
                        addNew = new SimpleAddUI(newPointDialogIsShown, resetScrollSignal, filterViewIsOpened, state);
 | 
						|
                    }
 | 
						|
                    let addNote = undefined;
 | 
						|
                    if (noteLayer !== undefined) {
 | 
						|
                        addNote = addNewNoteDialog(newPointDialogIsShown)
 | 
						|
                    }
 | 
						|
                    return new Combine([addNew, addNote]).SetClass("flex flex-col font-lg text-lg")
 | 
						|
                },
 | 
						|
                "new",
 | 
						|
                newPointDialogIsShown
 | 
						|
            );
 | 
						|
 | 
						|
            addNewPoint.isShown.addCallback((isShown) => {
 | 
						|
                if (!isShown) {
 | 
						|
                    // Clear the 'last-click'-location when the dialog is closed - this causes the popup and the marker to be removed
 | 
						|
                    state.LastClickLocation.setData(undefined);
 | 
						|
                }
 | 
						|
            });
 | 
						|
 | 
						|
            let noteMarker = undefined;
 | 
						|
            if (!hasPresets && addNewNoteDialog !== undefined) {
 | 
						|
                noteMarker = new Combine(
 | 
						|
                    [Svg.note_svg().SetClass("absolute bottom-0").SetStyle("height: 40px"),
 | 
						|
                        Svg.addSmall_svg().SetClass("absolute w-6 animate-pulse")
 | 
						|
                            .SetStyle("right: 10px; bottom: -8px;")
 | 
						|
                    ])
 | 
						|
                    .SetClass("block relative h-full")
 | 
						|
                    .SetStyle("left: calc( 50% - 15px )") // This is a bit hacky, yes I know!
 | 
						|
            }
 | 
						|
 | 
						|
            new StrayClickHandler(
 | 
						|
                state,
 | 
						|
                addNewPoint,
 | 
						|
                hasPresets ? new AddNewMarker(state.filteredLayers) : noteMarker
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        if (noteLayer !== undefined) {
 | 
						|
            setup()
 | 
						|
        } else {
 | 
						|
            state.featureSwitchAddNew.addCallbackAndRunD(addNewAllowed => {
 | 
						|
                if (addNewAllowed) {
 | 
						|
                    setup()
 | 
						|
                    return true;
 | 
						|
                }
 | 
						|
            })
 | 
						|
        }
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    private SetupMap() {
 | 
						|
        const state = this.state;
 | 
						|
        const guiState = this.guiState;
 | 
						|
 | 
						|
        // Attach the map
 | 
						|
        state.mainMapObject.SetClass("w-full h-full")
 | 
						|
            .AttachTo("leafletDiv")
 | 
						|
 | 
						|
        this.setupClickDialogOnMap(
 | 
						|
            guiState.filterViewIsOpened,
 | 
						|
            state
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
        new ShowDataLayer({
 | 
						|
            leafletMap: state.leafletMap,
 | 
						|
            layerToShow: new LayerConfig(home_location_json, "all_known_layers", true),
 | 
						|
            features: state.homeLocation,
 | 
						|
            state
 | 
						|
        })
 | 
						|
 | 
						|
        state.leafletMap.addCallbackAndRunD(_ => {
 | 
						|
            // Lets assume that all showDataLayers are initialized at this point
 | 
						|
            state.selectedElement.ping()
 | 
						|
            State.state.locationControl.ping();
 | 
						|
            return true;
 | 
						|
        })
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    private SetupUIElements() {
 | 
						|
        const state = this.state;
 | 
						|
        const guiState = this.guiState;
 | 
						|
 | 
						|
        const self = this
 | 
						|
        new Combine([
 | 
						|
            Toggle.If(state.featureSwitchUserbadge,
 | 
						|
                () => new UserBadge(state)
 | 
						|
            ),
 | 
						|
            Toggle.If(state.featureSwitchExtraLinkEnabled,
 | 
						|
                () => new ExtraLinkButton(state, state.layoutToUse.extraLink)
 | 
						|
            )
 | 
						|
        ]).SetClass("flex flex-col")
 | 
						|
            .AttachTo("userbadge")
 | 
						|
 | 
						|
        new Combine([
 | 
						|
            new ExtraLinkButton(state, {...state.layoutToUse.extraLink, newTab: true, requirements: new Set<"iframe" | "no-iframe" | "welcome-message" | "no-welcome-message">( ) })
 | 
						|
        ]).SetClass("flex items-center justify-center normal-background h-full")
 | 
						|
            .AttachTo("on-small-screen")
 | 
						|
 | 
						|
        Toggle.If(state.featureSwitchSearch,
 | 
						|
            () => new SearchAndGo(state))
 | 
						|
            .AttachTo("searchbox");
 | 
						|
 | 
						|
 | 
						|
        Toggle.If(
 | 
						|
            state.featureSwitchWelcomeMessage,
 | 
						|
            () => self.InitWelcomeMessage()
 | 
						|
        )
 | 
						|
            .AttachTo("messagesbox");
 | 
						|
 | 
						|
 | 
						|
        new LeftControls(state, guiState).AttachTo("bottom-left");
 | 
						|
        new RightControls(state).AttachTo("bottom-right");
 | 
						|
 | 
						|
        new CenterMessageBox(state).AttachTo("centermessage");
 | 
						|
        document
 | 
						|
            .getElementById("centermessage")
 | 
						|
            .classList.add("pointer-events-none");
 | 
						|
 | 
						|
        // We have to ping the welcomeMessageIsOpened and other isOpened-stuff to activate the FullScreenMessage if needed
 | 
						|
        for (const state of guiState.allFullScreenStates) {
 | 
						|
            if (state.data) {
 | 
						|
                state.ping()
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * At last, if the map moves or an element is selected, we close all the panels just as well
 | 
						|
         */
 | 
						|
 | 
						|
        state.selectedElement.addCallbackAndRunD((_) => {
 | 
						|
            guiState.allFullScreenStates.forEach(s => s.setData(false))
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    private InitWelcomeMessage(): BaseUIElement {
 | 
						|
        const isOpened = this.guiState.welcomeMessageIsOpened
 | 
						|
        const fullOptions = new FullWelcomePaneWithTabs(isOpened, this.guiState.welcomeMessageOpenedTab, this.state);
 | 
						|
 | 
						|
        // ?-Button on Desktop, opens panel with close-X.
 | 
						|
        const help = new MapControlButton(Svg.help_svg());
 | 
						|
        help.onClick(() => isOpened.setData(true));
 | 
						|
 | 
						|
 | 
						|
        const openedTime = new Date().getTime();
 | 
						|
        this.state.locationControl.addCallback(() => {
 | 
						|
            if (new Date().getTime() - openedTime < 15 * 1000) {
 | 
						|
                // Don't autoclose the first 15 secs when the map is moving
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            isOpened.setData(false);
 | 
						|
            return true; // Unregister this caller - we only autoclose once
 | 
						|
        });
 | 
						|
 | 
						|
        this.state.selectedElement.addCallbackAndRunD((_) => {
 | 
						|
            isOpened.setData(false);
 | 
						|
        });
 | 
						|
 | 
						|
        return new Toggle(
 | 
						|
            fullOptions.SetClass("welcomeMessage pointer-events-auto"),
 | 
						|
            help.SetClass("pointer-events-auto"),
 | 
						|
            isOpened
 | 
						|
        )
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
} |