mapcomplete/UI/DefaultGUI.ts

345 lines
13 KiB
TypeScript
Raw Normal View History

2022-09-08 21:40:48 +02:00
import FeaturePipelineState from "../Logic/State/FeaturePipelineState"
import State from "../State"
2022-12-16 13:44:25 +01:00
import { Utils } from "../Utils"
import { UIEventSource } from "../Logic/UIEventSource"
2022-09-08 21:40:48 +02:00
import FullWelcomePaneWithTabs from "./BigComponents/FullWelcomePaneWithTabs"
import MapControlButton from "./MapControlButton"
import Svg from "../Svg"
import Toggle from "./Input/Toggle"
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"
2022-12-16 13:44:25 +01:00
import { DefaultGuiState } from "./DefaultGuiState"
2022-09-08 21:40:48 +02:00
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import home_location_json from "../assets/layers/home_location/home_location.json"
2022-09-08 21:40:48 +02:00
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"
2022-12-16 13:44:25 +01:00
import { VariableUiElement } from "./Base/VariableUIElement"
import Img from "./Base/Img"
import UserInformationPanel from "./BigComponents/UserInformation"
import { LoginToggle } from "./Popup/LoginButton"
import { FixedUiElement } from "./Base/FixedUiElement"
import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
import { GeoLocationState } from "../Logic/State/GeoLocationState"
import Hotkeys from "./Base/Hotkeys"
import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers"
import CopyrightPanel from "./BigComponents/CopyrightPanel"
import SvelteUIElement from "./Base/SvelteUIElement"
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
/**
2022-12-08 02:56:49 +01:00
* The default MapComplete GUI initializer
*
2022-12-08 02:56:49 +01:00
* Adds a welcome pane, control buttons, ... etc to index.html
*/
export default class DefaultGUI {
2022-09-08 21:40:48 +02:00
private readonly guiState: DefaultGuiState
private readonly state: FeaturePipelineState
private readonly geolocationHandler: GeoLocationHandler | undefined
constructor(state: FeaturePipelineState, guiState: DefaultGuiState) {
2022-09-08 21:40:48 +02:00
this.state = state
this.guiState = guiState
if (this.state.featureSwitchGeolocation.data) {
this.geolocationHandler = new GeoLocationHandler(new GeoLocationState(), state)
}
2022-09-08 21:40:48 +02:00
}
2022-04-08 17:59:14 +02:00
2022-09-08 21:40:48 +02:00
public setup() {
this.SetupUIElements()
this.SetupMap()
ScrollableFullScreen.ActivateCurrent()
2022-01-14 02:40:55 +01:00
2022-09-08 21:40:48 +02:00
if (
this.state.layoutToUse.customCss !== undefined &&
window.location.pathname.indexOf("index") >= 0
) {
2022-04-08 17:59:14 +02:00
Utils.LoadCustomCss(this.state.layoutToUse.customCss)
}
2022-09-08 21:40:48 +02:00
Hotkeys.RegisterHotkey(
{ shift: "O" },
Translations.t.hotkeyDocumentation.selectMapnik,
() => {
this.state.backgroundLayer.setData(AvailableBaseLayers.osmCarto)
}
)
2022-09-08 21:40:48 +02:00
Utils.downloadJson("./service-worker-version")
.then((data) => console.log("Service worker", data))
2022-12-08 02:56:49 +01:00
.catch((_) => console.log("Service worker not active"))
}
2021-11-07 16:34:51 +01:00
2022-09-08 21:40:48 +02:00
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
2022-01-14 02:40:55 +01:00
if (noteLayer !== undefined) {
addNewNoteDialog = (isShown) => new NewNoteUi(noteLayer, isShown, state)
}
2022-01-14 02:40:55 +01:00
function setup() {
if (!hasPresets && addNewNoteDialog === undefined) {
2022-09-08 21:40:48 +02:00
return // nothing to do
}
2022-09-08 21:40:48 +02:00
const newPointDialogIsShown = new UIEventSource<boolean>(false)
2021-11-07 16:34:51 +01:00
const addNewPoint = new ScrollableFullScreen(
2022-09-08 21:40:48 +02:00
() =>
hasPresets
? Translations.t.general.add.title
: Translations.t.notes.createNoteTitle,
({ resetScrollSignal }) => {
let addNew = undefined
2022-01-14 02:40:55 +01:00
if (hasPresets) {
2022-09-08 21:40:48 +02:00
addNew = new SimpleAddUI(
newPointDialogIsShown,
resetScrollSignal,
filterViewIsOpened,
state
)
}
2022-09-08 21:40:48 +02:00
let addNote = undefined
2022-01-14 02:40:55 +01:00
if (noteLayer !== undefined) {
addNote = addNewNoteDialog(newPointDialogIsShown)
}
return new Combine([addNew, addNote]).SetClass("flex flex-col font-lg text-lg")
},
2021-11-07 16:34:51 +01:00
"new",
newPointDialogIsShown
2022-09-08 21:40:48 +02:00
)
2022-01-14 02:40:55 +01:00
2021-11-07 16:34:51 +01:00
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
2022-09-08 21:40:48 +02:00
state.LastClickLocation.setData(undefined)
2021-11-07 16:34:51 +01:00
}
2022-09-08 21:40:48 +02:00
})
2022-01-26 21:40:38 +01:00
2022-09-08 21:40:48 +02:00
let noteMarker = undefined
2022-01-26 21:40:38 +01:00
if (!hasPresets && addNewNoteDialog !== undefined) {
2022-09-08 21:40:48 +02:00
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;"),
])
2022-01-14 02:40:55 +01:00
.SetClass("block relative h-full")
.SetStyle("left: calc( 50% - 15px )") // This is a bit hacky, yes I know!
}
2021-11-07 16:34:51 +01:00
StrayClickHandler.construct(
state,
addNewPoint,
2022-01-14 02:40:55 +01:00
hasPresets ? new AddNewMarker(state.filteredLayers) : noteMarker
2022-09-08 21:40:48 +02:00
)
2022-12-16 13:44:25 +01:00
state.LastClickLocation.addCallbackAndRunD((_) => {
ScrollableFullScreen.collapse()
})
2021-11-07 16:34:51 +01:00
}
2022-01-14 02:40:55 +01:00
if (noteLayer !== undefined) {
setup()
2022-01-14 02:40:55 +01:00
} else {
2022-09-08 21:40:48 +02:00
state.featureSwitchAddNew.addCallbackAndRunD((addNewAllowed) => {
if (addNewAllowed) {
setup()
2022-09-08 21:40:48 +02:00
return true
}
})
}
2021-11-07 16:34:51 +01:00
}
private SetupMap() {
if (Utils.runningFromConsole) {
return
}
2022-09-08 21:40:48 +02:00
const state = this.state
const guiState = this.guiState
// Attach the map
2022-09-08 21:40:48 +02:00
state.mainMapObject.SetClass("w-full h-full").AttachTo("leafletDiv")
2022-09-08 21:40:48 +02:00
this.setupClickDialogOnMap(guiState.filterViewIsOpened, state)
new ShowDataLayer({
leafletMap: state.leafletMap,
2022-07-13 17:58:01 +02:00
layerToShow: new LayerConfig(home_location_json, "home_location", true),
features: state.homeLocation,
2022-09-08 21:40:48 +02:00
state,
})
const selectedElement: FilteredLayer = state.filteredLayers.data.filter(
(l) => l.layerDef.id === "selected_element"
)[0]
new ShowDataLayer({
leafletMap: state.leafletMap,
layerToShow: selectedElement.layerDef,
features: state.selectedElementsLayer,
2022-12-16 13:44:25 +01:00
state,
})
2022-09-08 21:40:48 +02:00
state.leafletMap.addCallbackAndRunD((_) => {
// Lets assume that all showDataLayers are initialized at this point
state.selectedElement.ping()
2022-09-08 21:40:48 +02:00
State.state.locationControl.ping()
return true
})
}
2021-11-07 16:34:51 +01:00
private SetupUIElements() {
2022-09-08 21:40:48 +02:00
const state = this.state
const guiState = this.guiState
2021-11-07 16:34:51 +01:00
const self = this
const userInfoMapControl = Toggle.If(state.featureSwitchUserbadge, () => {
new UserInformationPanel(state, {
isOpened: guiState.userInfoIsOpened,
userInfoFocusedQuestion: guiState.userInfoFocusedQuestion,
})
const mapControl = new MapControlButton(
new VariableUiElement(
state.osmConnection.userDetails.map((ud) => {
if (ud?.img === undefined) {
return Svg.person_ui().SetClass("mt-1 block")
}
return new Img(ud?.img)
})
).SetClass("block rounded-full overflow-hidden"),
{
dontStyle: true,
}
).onClick(() => {
self.guiState.userInfoIsOpened.setData(true)
})
return new LoginToggle(mapControl, Translations.t.general.loginWithOpenStreetMap, state)
})
const extraLink = Toggle.If(
state.featureSwitchExtraLinkEnabled,
() => new ExtraLinkButton(state, state.layoutToUse.extraLink)
)
const welcomeMessageMapControl = Toggle.If(state.featureSwitchWelcomeMessage, () =>
self.InitWelcomeMessage()
)
const communityIndex = Toggle.If(state.featureSwitchCommunityIndex, () => {
const communityIndexControl = new MapControlButton(Svg.community_svg())
const communityIndex = new ScrollableFullScreen(
() => Translations.t.communityIndex.title,
() => new SvelteUIElement(CommunityIndexView, { ...state }),
"community_index"
)
communityIndexControl.onClick(() => {
communityIndex.Activate()
})
return communityIndexControl
})
const testingBadge = Toggle.If(state.featureSwitchIsTesting, () =>
new FixedUiElement("TESTING").SetClass("alert m-2 border-2 border-black")
)
new ScrollableFullScreen(
() => Translations.t.general.attribution.attributionTitle,
() => new CopyrightPanel(state),
"copyright",
guiState.copyrightViewIsOpened
)
const copyright = new MapControlButton(Svg.copyright_svg()).onClick(() =>
guiState.copyrightViewIsOpened.setData(true)
)
new Combine([
welcomeMessageMapControl,
userInfoMapControl,
copyright,
communityIndex,
extraLink,
testingBadge,
])
2022-09-08 21:40:48 +02:00
.SetClass("flex flex-col")
.AttachTo("top-left")
2022-03-10 23:18:40 +01:00
new Combine([
2022-09-08 21:40:48 +02:00
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")
2022-03-10 23:18:40 +01:00
.AttachTo("on-small-screen")
new Combine([
Toggle.If(state.featureSwitchSearch, () => {
const search = new SearchAndGo(state).SetClass(
"shadow rounded-full h-min w-full overflow-hidden sm:max-w-sm pointer-events-auto"
)
Hotkeys.RegisterHotkey(
{ ctrl: "F" },
Translations.t.hotkeyDocumentation.selectSearch,
() => {
search.focus()
}
)
return search
}),
]).AttachTo("top-right")
2022-09-08 21:40:48 +02:00
new LeftControls(state, guiState).AttachTo("bottom-left")
new RightControls(state, this.geolocationHandler).AttachTo("bottom-right")
2022-09-08 21:40:48 +02:00
new CenterMessageBox(state).AttachTo("centermessage")
document?.getElementById("centermessage")?.classList?.add("pointer-events-none")
}
2021-11-07 16:34:51 +01:00
private InitWelcomeMessage(): BaseUIElement {
2022-04-08 17:59:14 +02:00
const isOpened = this.guiState.welcomeMessageIsOpened
new FullWelcomePaneWithTabs(
isOpened,
this.guiState.welcomeMessageOpenedTab,
this.state,
this.guiState
)
// ?-Button on Desktop, opens panel with close-X.
2022-09-08 21:40:48 +02:00
const help = new MapControlButton(Svg.help_svg())
help.onClick(() => isOpened.setData(true))
2022-09-08 21:40:48 +02:00
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
2022-09-08 21:40:48 +02:00
return
}
2022-09-08 21:40:48 +02:00
isOpened.setData(false)
return true // Unregister this caller - we only autoclose once
})
this.state.selectedElement.addCallbackAndRunD((_) => {
2022-09-08 21:40:48 +02:00
isOpened.setData(false)
})
2022-12-16 13:44:25 +01:00
return help.SetClass("pointer-events-auto")
}
2022-09-08 21:40:48 +02:00
}