mapcomplete/UI/DefaultGUI.ts
2022-06-02 02:59:34 +02:00

248 lines
No EOL
9.1 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)
}
Utils.downloadJson("./service-worker-version").then(data => console.log("Service worker", data)).catch(e => console.log("Service worker not active"))
}
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
)
}
}