Add hotkeys, document hotkeys, make 'DefaultGUI' runnable via NodeJS, generate hotkey documentation
This commit is contained in:
parent
e2419c19cd
commit
43613e4ece
16 changed files with 328 additions and 89 deletions
30
Docs/Hotkeys.md
Normal file
30
Docs/Hotkeys.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
|
||||||
|
|
||||||
|
Hotkeys
|
||||||
|
=========
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
1. [Hotkeys](#hotkeys)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
MapComplete supports the following keys:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Key combination | Action
|
||||||
|
----------------- | --------
|
||||||
|
B | Opens the Background, layers and filters panel
|
||||||
|
Escape | Close the sidebar
|
||||||
|
L | Pan the map to the current location or zoom the map to the current location. Requests geopermission
|
||||||
|
M | Switch to a background layer of category map
|
||||||
|
O | Switch to a background layer of category osmbasedmap
|
||||||
|
P | Switch to a background layer of category photo
|
||||||
|
ctrl+F | Select the search bar to search locations
|
||||||
|
shift+O | Switch to default Mapnik-OpenStreetMap background
|
||||||
|
|
||||||
|
|
||||||
|
This document is autogenerated from
|
|
@ -1,8 +1,6 @@
|
||||||
import * as L from "leaflet"
|
|
||||||
import { UIEventSource } from "../UIEventSource"
|
import { UIEventSource } from "../UIEventSource"
|
||||||
import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen"
|
|
||||||
import FilteredLayer from "../../Models/FilteredLayer"
|
import FilteredLayer from "../../Models/FilteredLayer"
|
||||||
import Constants from "../../Models/Constants"
|
import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen"
|
||||||
import BaseUIElement from "../../UI/BaseUIElement"
|
import BaseUIElement from "../../UI/BaseUIElement"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,70 +8,16 @@ import BaseUIElement from "../../UI/BaseUIElement"
|
||||||
* Shows the given uiToShow-element in the messagebox
|
* Shows the given uiToShow-element in the messagebox
|
||||||
*/
|
*/
|
||||||
export default class StrayClickHandler {
|
export default class StrayClickHandler {
|
||||||
private _lastMarker
|
public static construct = (
|
||||||
|
|
||||||
constructor(
|
|
||||||
state: {
|
state: {
|
||||||
LastClickLocation: UIEventSource<{ lat: number; lon: number }>
|
LastClickLocation: UIEventSource<{ lat: number; lon: number }>
|
||||||
selectedElement: UIEventSource<string>
|
selectedElement: UIEventSource<string>
|
||||||
filteredLayers: UIEventSource<FilteredLayer[]>
|
filteredLayers: UIEventSource<FilteredLayer[]>
|
||||||
leafletMap: UIEventSource<L.Map>
|
leafletMap: UIEventSource<any>
|
||||||
},
|
},
|
||||||
uiToShow: ScrollableFullScreen,
|
uiToShow: ScrollableFullScreen,
|
||||||
iconToShow: BaseUIElement
|
iconToShow: BaseUIElement
|
||||||
) {
|
) => {
|
||||||
const self = this
|
return undefined
|
||||||
const leafletMap = state.leafletMap
|
|
||||||
state.filteredLayers.data.forEach((filteredLayer) => {
|
|
||||||
filteredLayer.isDisplayed.addCallback((isEnabled) => {
|
|
||||||
if (isEnabled && self._lastMarker && leafletMap.data !== undefined) {
|
|
||||||
// When a layer is activated, we remove the 'last click location' in order to force the user to reclick
|
|
||||||
// This reclick might be at a location where a feature now appeared...
|
|
||||||
state.leafletMap.data.removeLayer(self._lastMarker)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
state.LastClickLocation.addCallback(function (lastClick) {
|
|
||||||
if (self._lastMarker !== undefined) {
|
|
||||||
state.leafletMap.data?.removeLayer(self._lastMarker)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastClick === undefined) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
state.selectedElement.setData(undefined)
|
|
||||||
const clickCoor: [number, number] = [lastClick.lat, lastClick.lon]
|
|
||||||
self._lastMarker = L.marker(clickCoor, {
|
|
||||||
icon: L.divIcon({
|
|
||||||
html: iconToShow.ConstructElement(),
|
|
||||||
iconSize: [50, 50],
|
|
||||||
iconAnchor: [25, 50],
|
|
||||||
popupAnchor: [0, -45],
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
self._lastMarker.addTo(leafletMap.data)
|
|
||||||
|
|
||||||
self._lastMarker.on("click", () => {
|
|
||||||
if (leafletMap.data.getZoom() < Constants.userJourney.minZoomLevelToAddNewPoints) {
|
|
||||||
leafletMap.data.flyTo(
|
|
||||||
clickCoor,
|
|
||||||
Constants.userJourney.minZoomLevelToAddNewPoints
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uiToShow.Activate()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
state.selectedElement.addCallback(() => {
|
|
||||||
if (self._lastMarker !== undefined) {
|
|
||||||
leafletMap.data.removeLayer(self._lastMarker)
|
|
||||||
this._lastMarker = undefined
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Utils } from "../Utils"
|
import { Utils } from "../Utils"
|
||||||
|
|
||||||
export default class Constants {
|
export default class Constants {
|
||||||
public static vNumber = "0.25.1"
|
public static vNumber = "0.25.2"
|
||||||
|
|
||||||
public static ImgurApiKey = "7070e7167f0a25a"
|
public static ImgurApiKey = "7070e7167f0a25a"
|
||||||
public static readonly mapillary_client_token_v4 =
|
public static readonly mapillary_client_token_v4 =
|
||||||
|
|
110
UI/Base/Hotkeys.ts
Normal file
110
UI/Base/Hotkeys.ts
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import { Utils } from "../../Utils"
|
||||||
|
import Combine from "./Combine"
|
||||||
|
import BaseUIElement from "../BaseUIElement"
|
||||||
|
import Title from "./Title"
|
||||||
|
import Table from "./Table"
|
||||||
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
import { VariableUiElement } from "./VariableUIElement"
|
||||||
|
|
||||||
|
export default class Hotkeys {
|
||||||
|
private static readonly _docs: UIEventSource<
|
||||||
|
{
|
||||||
|
key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean }
|
||||||
|
documentation: string
|
||||||
|
}[]
|
||||||
|
> = new UIEventSource<
|
||||||
|
{
|
||||||
|
key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean }
|
||||||
|
documentation: string
|
||||||
|
}[]
|
||||||
|
>([])
|
||||||
|
public static RegisterHotkey(
|
||||||
|
key: (
|
||||||
|
| {
|
||||||
|
ctrl: string
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
shift: string
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
alt: string
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
nomod: string
|
||||||
|
}
|
||||||
|
) & {
|
||||||
|
onUp?: boolean
|
||||||
|
},
|
||||||
|
documentation: string,
|
||||||
|
action: () => void
|
||||||
|
) {
|
||||||
|
const type = key["onUp"] ? "keyup" : "keypress"
|
||||||
|
let keycode: string = key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"]
|
||||||
|
if (keycode.length == 1) {
|
||||||
|
keycode = keycode.toLowerCase()
|
||||||
|
if (key["shift"] !== undefined) {
|
||||||
|
keycode = keycode.toUpperCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._docs.data.push({ key, documentation })
|
||||||
|
this._docs.ping()
|
||||||
|
if (Utils.runningFromConsole) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (key["ctrl"] !== undefined) {
|
||||||
|
document.addEventListener("keydown", function (event) {
|
||||||
|
if (event.ctrlKey && event.key === keycode) {
|
||||||
|
action()
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (key["shift"] !== undefined) {
|
||||||
|
document.addEventListener(type, function (event) {
|
||||||
|
if (event.shiftKey && event.key === keycode) {
|
||||||
|
action()
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (key["alt"] !== undefined) {
|
||||||
|
document.addEventListener(type, function (event) {
|
||||||
|
if (event.altKey && event.key === keycode) {
|
||||||
|
action()
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (key["nomod"] !== undefined) {
|
||||||
|
document.addEventListener(type, function (event) {
|
||||||
|
if (event.key === keycode) {
|
||||||
|
action()
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static generateDocumentation(): BaseUIElement {
|
||||||
|
return new Combine([
|
||||||
|
new Title("Hotkeys", 1),
|
||||||
|
"MapComplete supports the following keys:",
|
||||||
|
new Table(
|
||||||
|
["Key combination", "Action"],
|
||||||
|
Hotkeys._docs.data
|
||||||
|
.map(({ key, documentation }) => {
|
||||||
|
const modifiers = Object.keys(key).filter(
|
||||||
|
(k) => k !== "nomod" && k !== "onUp"
|
||||||
|
)
|
||||||
|
const keycode: string =
|
||||||
|
key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"]
|
||||||
|
modifiers.push(keycode)
|
||||||
|
return [modifiers.join("+"), documentation]
|
||||||
|
})
|
||||||
|
.sort()
|
||||||
|
),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
static generateDocumentationDynamic(): BaseUIElement {
|
||||||
|
return new VariableUiElement(Hotkeys._docs.map((_) => Hotkeys.generateDocumentation()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,83 @@ import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch"
|
||||||
import AvailableBaseLayersImplementation from "../../Logic/Actors/AvailableBaseLayersImplementation"
|
import AvailableBaseLayersImplementation from "../../Logic/Actors/AvailableBaseLayersImplementation"
|
||||||
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
|
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
|
||||||
import ShowDataLayerImplementation from "../ShowDataLayer/ShowDataLayerImplementation"
|
import ShowDataLayerImplementation from "../ShowDataLayer/ShowDataLayerImplementation"
|
||||||
|
import FilteredLayer from "../../Models/FilteredLayer"
|
||||||
|
import ScrollableFullScreen from "./ScrollableFullScreen"
|
||||||
|
import Constants from "../../Models/Constants"
|
||||||
|
import StrayClickHandler from "../../Logic/Actors/StrayClickHandler"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stray-click-hanlders adds a marker to the map if no feature was clicked.
|
||||||
|
* Shows the given uiToShow-element in the messagebox
|
||||||
|
*/
|
||||||
|
export class StrayClickHandlerImplementation {
|
||||||
|
private _lastMarker
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
state: {
|
||||||
|
LastClickLocation: UIEventSource<{ lat: number; lon: number }>
|
||||||
|
selectedElement: UIEventSource<string>
|
||||||
|
filteredLayers: UIEventSource<FilteredLayer[]>
|
||||||
|
leafletMap: UIEventSource<L.Map>
|
||||||
|
},
|
||||||
|
uiToShow: ScrollableFullScreen,
|
||||||
|
iconToShow: BaseUIElement
|
||||||
|
) {
|
||||||
|
const self = this
|
||||||
|
const leafletMap = state.leafletMap
|
||||||
|
state.filteredLayers.data.forEach((filteredLayer) => {
|
||||||
|
filteredLayer.isDisplayed.addCallback((isEnabled) => {
|
||||||
|
if (isEnabled && self._lastMarker && leafletMap.data !== undefined) {
|
||||||
|
// When a layer is activated, we remove the 'last click location' in order to force the user to reclick
|
||||||
|
// This reclick might be at a location where a feature now appeared...
|
||||||
|
state.leafletMap.data.removeLayer(self._lastMarker)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
state.LastClickLocation.addCallback(function (lastClick) {
|
||||||
|
if (self._lastMarker !== undefined) {
|
||||||
|
state.leafletMap.data?.removeLayer(self._lastMarker)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastClick === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state.selectedElement.setData(undefined)
|
||||||
|
const clickCoor: [number, number] = [lastClick.lat, lastClick.lon]
|
||||||
|
self._lastMarker = L.marker(clickCoor, {
|
||||||
|
icon: L.divIcon({
|
||||||
|
html: iconToShow.ConstructElement(),
|
||||||
|
iconSize: [50, 50],
|
||||||
|
iconAnchor: [25, 50],
|
||||||
|
popupAnchor: [0, -45],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
self._lastMarker.addTo(leafletMap.data)
|
||||||
|
|
||||||
|
self._lastMarker.on("click", () => {
|
||||||
|
if (leafletMap.data.getZoom() < Constants.userJourney.minZoomLevelToAddNewPoints) {
|
||||||
|
leafletMap.data.flyTo(
|
||||||
|
clickCoor,
|
||||||
|
Constants.userJourney.minZoomLevelToAddNewPoints
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uiToShow.Activate()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
state.selectedElement.addCallback(() => {
|
||||||
|
if (self._lastMarker !== undefined) {
|
||||||
|
leafletMap.data.removeLayer(self._lastMarker)
|
||||||
|
this._lastMarker = undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
export default class MinimapImplementation extends BaseUIElement implements MinimapObj {
|
export default class MinimapImplementation extends BaseUIElement implements MinimapObj {
|
||||||
private static _nextId = 0
|
private static _nextId = 0
|
||||||
public readonly leafletMap: UIEventSource<Map>
|
public readonly leafletMap: UIEventSource<Map>
|
||||||
|
@ -53,6 +129,18 @@ export default class MinimapImplementation extends BaseUIElement implements Mini
|
||||||
AvailableBaseLayers.implement(new AvailableBaseLayersImplementation())
|
AvailableBaseLayers.implement(new AvailableBaseLayersImplementation())
|
||||||
Minimap.createMiniMap = (options) => new MinimapImplementation(options)
|
Minimap.createMiniMap = (options) => new MinimapImplementation(options)
|
||||||
ShowDataLayer.actualContstructor = (options) => new ShowDataLayerImplementation(options)
|
ShowDataLayer.actualContstructor = (options) => new ShowDataLayerImplementation(options)
|
||||||
|
StrayClickHandler.construct = (
|
||||||
|
state: {
|
||||||
|
LastClickLocation: UIEventSource<{ lat: number; lon: number }>
|
||||||
|
selectedElement: UIEventSource<string>
|
||||||
|
filteredLayers: UIEventSource<FilteredLayer[]>
|
||||||
|
leafletMap: UIEventSource<L.Map>
|
||||||
|
},
|
||||||
|
uiToShow: ScrollableFullScreen,
|
||||||
|
iconToShow: BaseUIElement
|
||||||
|
) => {
|
||||||
|
return new StrayClickHandlerImplementation(state, uiToShow, iconToShow)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public installBounds(factor: number | BBox, showRange?: boolean) {
|
public installBounds(factor: number | BBox, showRange?: boolean) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import Hash from "../../Logic/Web/Hash"
|
import Hash from "../../Logic/Web/Hash"
|
||||||
import BaseUIElement from "../BaseUIElement"
|
import BaseUIElement from "../BaseUIElement"
|
||||||
import Title from "./Title"
|
import Title from "./Title"
|
||||||
|
import Hotkeys from "./Hotkeys"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -82,12 +83,11 @@ export default class ScrollableFullScreen {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static initEmpty(): FixedUiElement {
|
private static initEmpty(): FixedUiElement {
|
||||||
document.addEventListener("keyup", function (event) {
|
Hotkeys.RegisterHotkey(
|
||||||
if (event.code === "Escape") {
|
{ nomod: "Escape", onUp: true },
|
||||||
ScrollableFullScreen.collapse()
|
"Close the sidebar",
|
||||||
event.preventDefault()
|
ScrollableFullScreen.collapse
|
||||||
}
|
)
|
||||||
})
|
|
||||||
|
|
||||||
return new FixedUiElement("")
|
return new FixedUiElement("")
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ export default class ScrollableFullScreen {
|
||||||
this._fullscreencomponent.AttachTo("fullscreen")
|
this._fullscreencomponent.AttachTo("fullscreen")
|
||||||
const fs = document.getElementById("fullscreen")
|
const fs = document.getElementById("fullscreen")
|
||||||
ScrollableFullScreen._currentlyOpen = this
|
ScrollableFullScreen._currentlyOpen = this
|
||||||
fs.classList.remove("hidden")
|
fs?.classList?.remove("hidden")
|
||||||
}
|
}
|
||||||
|
|
||||||
private BuildComponent(title: BaseUIElement, content: BaseUIElement): BaseUIElement {
|
private BuildComponent(title: BaseUIElement, content: BaseUIElement): BaseUIElement {
|
||||||
|
|
|
@ -24,6 +24,10 @@ export default abstract class BaseUIElement {
|
||||||
AttachTo(divId: string) {
|
AttachTo(divId: string) {
|
||||||
let element = document.getElementById(divId)
|
let element = document.getElementById(divId)
|
||||||
if (element === null) {
|
if (element === null) {
|
||||||
|
if (Utils.runningFromConsole) {
|
||||||
|
this.ConstructElement()
|
||||||
|
return
|
||||||
|
}
|
||||||
throw "SEVERE: could not attach UIElement to " + divId
|
throw "SEVERE: could not attach UIElement to " + divId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import BaseLayer from "../../Models/BaseLayer"
|
||||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"
|
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"
|
||||||
import BaseUIElement from "../BaseUIElement"
|
import BaseUIElement from "../BaseUIElement"
|
||||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||||
|
import Hotkeys from "../Base/Hotkeys"
|
||||||
|
|
||||||
class SingleLayerSelectionButton extends Toggle {
|
class SingleLayerSelectionButton extends Toggle {
|
||||||
public readonly activate: () => void
|
public readonly activate: () => void
|
||||||
|
@ -48,13 +49,13 @@ class SingleLayerSelectionButton extends Toggle {
|
||||||
let toggle: BaseUIElement = new Toggle(
|
let toggle: BaseUIElement = new Toggle(
|
||||||
selected,
|
selected,
|
||||||
unselected,
|
unselected,
|
||||||
options.currentBackground.map((bg) => bg.category === options.preferredType)
|
options.currentBackground.map((bg) => bg?.category === options.preferredType)
|
||||||
)
|
)
|
||||||
|
|
||||||
super(
|
super(
|
||||||
toggle,
|
toggle,
|
||||||
undefined,
|
undefined,
|
||||||
available.map((av) => av.category === options.preferredType)
|
available.map((av) => av?.category === options.preferredType)
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,6 +175,7 @@ export default class BackgroundMapSwitch extends Combine {
|
||||||
options?: {
|
options?: {
|
||||||
preferredCategory?: string
|
preferredCategory?: string
|
||||||
allowedCategories?: ("osmbasedmap" | "photo" | "map")[]
|
allowedCategories?: ("osmbasedmap" | "photo" | "map")[]
|
||||||
|
enableHotkeys?: boolean
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const allowedCategories = options?.allowedCategories ?? ["osmbasedmap", "photo", "map"]
|
const allowedCategories = options?.allowedCategories ?? ["osmbasedmap", "photo", "map"]
|
||||||
|
@ -183,7 +185,7 @@ export default class BackgroundMapSwitch extends Combine {
|
||||||
let activatePrevious: () => void = undefined
|
let activatePrevious: () => void = undefined
|
||||||
for (const category of allowedCategories) {
|
for (const category of allowedCategories) {
|
||||||
let preferredLayer = undefined
|
let preferredLayer = undefined
|
||||||
if (previousLayer.category === category) {
|
if (previousLayer?.category === category) {
|
||||||
preferredLayer = previousLayer
|
preferredLayer = previousLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,6 +200,16 @@ export default class BackgroundMapSwitch extends Combine {
|
||||||
if (category === options?.preferredCategory) {
|
if (category === options?.preferredCategory) {
|
||||||
button.activate()
|
button.activate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options?.enableHotkeys) {
|
||||||
|
Hotkeys.RegisterHotkey(
|
||||||
|
{ nomod: category.charAt(0).toUpperCase() },
|
||||||
|
"Switch to a background layer of category " + category,
|
||||||
|
() => {
|
||||||
|
button.activate()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
buttons.push(button)
|
buttons.push(button)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ export class DownloadPanel extends Toggle {
|
||||||
currentBounds: UIEventSource<BBox>
|
currentBounds: UIEventSource<BBox>
|
||||||
}) {
|
}) {
|
||||||
const t = Translations.t.general.download
|
const t = Translations.t.general.download
|
||||||
const name = State.state.layoutToUse.id
|
const name = state.layoutToUse.id
|
||||||
|
|
||||||
const includeMetaToggle = new CheckBoxes([t.includeMetaData])
|
const includeMetaToggle = new CheckBoxes([t.includeMetaData])
|
||||||
const metaisIncluded = includeMetaToggle.GetValue().map((selected) => selected.length > 0)
|
const metaisIncluded = includeMetaToggle.GetValue().map((selected) => selected.length > 0)
|
||||||
|
|
|
@ -20,6 +20,7 @@ import FilteredLayer from "../../Models/FilteredLayer"
|
||||||
import CopyrightPanel from "./CopyrightPanel"
|
import CopyrightPanel from "./CopyrightPanel"
|
||||||
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"
|
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"
|
||||||
import PrivacyPolicy from "./PrivacyPolicy"
|
import PrivacyPolicy from "./PrivacyPolicy"
|
||||||
|
import Hotkeys from "../Base/Hotkeys"
|
||||||
|
|
||||||
export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
||||||
public static MoreThemesTabIndex = 1
|
public static MoreThemesTabIndex = 1
|
||||||
|
@ -126,6 +127,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
||||||
osmcha_link: Utils.OsmChaLinkFor(7),
|
osmcha_link: Utils.OsmChaLinkFor(7),
|
||||||
}),
|
}),
|
||||||
"<br/>Version " + Constants.vNumber,
|
"<br/>Version " + Constants.vNumber,
|
||||||
|
Hotkeys.generateDocumentationDynamic(),
|
||||||
]).SetClass("link-underline"),
|
]).SetClass("link-underline"),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler"
|
import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler"
|
||||||
import { BBox } from "../../Logic/BBox"
|
import { BBox } from "../../Logic/BBox"
|
||||||
import Loc from "../../Models/Loc"
|
import Loc from "../../Models/Loc"
|
||||||
|
import Hotkeys from "../Base/Hotkeys"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays an icon depending on the state of the geolocation.
|
* Displays an icon depending on the state of the geolocation.
|
||||||
|
@ -70,7 +71,7 @@ export class GeolocationControl extends VariableUiElement {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
this.onClick(async () => {
|
async function handleClick() {
|
||||||
if (geolocationState.permission.data !== "granted") {
|
if (geolocationState.permission.data !== "granted") {
|
||||||
await geolocationState.requestPermission()
|
await geolocationState.requestPermission()
|
||||||
}
|
}
|
||||||
|
@ -108,7 +109,14 @@ export class GeolocationControl extends VariableUiElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
lastClick.setData(new Date())
|
lastClick.setData(new Date())
|
||||||
})
|
}
|
||||||
|
|
||||||
|
this.onClick(handleClick)
|
||||||
|
Hotkeys.RegisterHotkey(
|
||||||
|
{ nomod: "L" },
|
||||||
|
"Pan the map to the current location or zoom the map to the current location. Requests geopermission",
|
||||||
|
handleClick
|
||||||
|
)
|
||||||
|
|
||||||
lastClick.addCallbackAndRunD((_) => {
|
lastClick.addCallbackAndRunD((_) => {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { VariableUiElement } from "../Base/VariableUIElement"
|
||||||
import FeatureInfoBox from "../Popup/FeatureInfoBox"
|
import FeatureInfoBox from "../Popup/FeatureInfoBox"
|
||||||
import CopyrightPanel from "./CopyrightPanel"
|
import CopyrightPanel from "./CopyrightPanel"
|
||||||
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
|
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
|
||||||
import { FixedUiElement } from "../Base/FixedUiElement"
|
import Hotkeys from "../Base/Hotkeys"
|
||||||
|
|
||||||
export default class LeftControls extends Combine {
|
export default class LeftControls extends Combine {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -73,7 +73,7 @@ export default class LeftControls extends Combine {
|
||||||
guiState.downloadControlIsOpened.setData(true)
|
guiState.downloadControlIsOpened.setData(true)
|
||||||
)
|
)
|
||||||
|
|
||||||
const downloadButtonn = new Toggle(
|
const downloadButton = new Toggle(
|
||||||
toggledDownload,
|
toggledDownload,
|
||||||
undefined,
|
undefined,
|
||||||
state.featureSwitchEnableExport.map(
|
state.featureSwitchEnableExport.map(
|
||||||
|
@ -94,11 +94,20 @@ export default class LeftControls extends Combine {
|
||||||
const toggledFilter = new MapControlButton(Svg.layers_svg()).onClick(() =>
|
const toggledFilter = new MapControlButton(Svg.layers_svg()).onClick(() =>
|
||||||
guiState.filterViewIsOpened.setData(true)
|
guiState.filterViewIsOpened.setData(true)
|
||||||
)
|
)
|
||||||
|
state.featureSwitchFilter.addCallbackAndRun((f) => {
|
||||||
|
Hotkeys.RegisterHotkey(
|
||||||
|
{ nomod: "B" },
|
||||||
|
"Opens the Background, layers and filters panel",
|
||||||
|
() => {
|
||||||
|
guiState.filterViewIsOpened.setData(!guiState.filterViewIsOpened.data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const filterButton = new Toggle(toggledFilter, undefined, state.featureSwitchFilter)
|
const filterButton = new Toggle(toggledFilter, undefined, state.featureSwitchFilter)
|
||||||
|
|
||||||
const mapSwitch = new Toggle(
|
const mapSwitch = new Toggle(
|
||||||
new BackgroundMapSwitch(state, state.backgroundLayer),
|
new BackgroundMapSwitch(state, state.backgroundLayer, { enableHotkeys: true }),
|
||||||
undefined,
|
undefined,
|
||||||
state.featureSwitchBackgroundSelection
|
state.featureSwitchBackgroundSelection
|
||||||
)
|
)
|
||||||
|
@ -120,7 +129,7 @@ export default class LeftControls extends Combine {
|
||||||
state.featureSwitchWelcomeMessage
|
state.featureSwitchWelcomeMessage
|
||||||
)
|
)
|
||||||
|
|
||||||
super([currentViewAction, filterButton, downloadButtonn, copyright, mapSwitch])
|
super([currentViewAction, filterButton, downloadButton, copyright, mapSwitch])
|
||||||
|
|
||||||
this.SetClass("flex flex-col")
|
this.SetClass("flex flex-col")
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@ export default class ShareScreen extends Combine {
|
||||||
includeCurrentBackground.GetValue().map(
|
includeCurrentBackground.GetValue().map(
|
||||||
(includeBG) => {
|
(includeBG) => {
|
||||||
if (includeBG) {
|
if (includeBG) {
|
||||||
return "background=" + currentLayer.data.id
|
return "background=" + currentLayer.data?.id
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,8 @@ import { LoginToggle } from "./Popup/LoginButton"
|
||||||
import { FixedUiElement } from "./Base/FixedUiElement"
|
import { FixedUiElement } from "./Base/FixedUiElement"
|
||||||
import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
|
import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
|
||||||
import { GeoLocationState } from "../Logic/State/GeoLocationState"
|
import { GeoLocationState } from "../Logic/State/GeoLocationState"
|
||||||
|
import Hotkeys from "./Base/Hotkeys"
|
||||||
|
import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default MapComplete GUI initializer
|
* The default MapComplete GUI initializer
|
||||||
|
@ -61,6 +63,14 @@ export default class DefaultGUI {
|
||||||
Utils.LoadCustomCss(this.state.layoutToUse.customCss)
|
Utils.LoadCustomCss(this.state.layoutToUse.customCss)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Hotkeys.RegisterHotkey(
|
||||||
|
{ shift: "O" },
|
||||||
|
"Switch to default Mapnik-OpenStreetMap background",
|
||||||
|
() => {
|
||||||
|
this.state.backgroundLayer.setData(AvailableBaseLayers.osmCarto)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
Utils.downloadJson("./service-worker-version")
|
Utils.downloadJson("./service-worker-version")
|
||||||
.then((data) => console.log("Service worker", data))
|
.then((data) => console.log("Service worker", data))
|
||||||
.catch((_) => console.log("Service worker not active"))
|
.catch((_) => console.log("Service worker not active"))
|
||||||
|
@ -128,7 +138,7 @@ export default class DefaultGUI {
|
||||||
.SetStyle("left: calc( 50% - 15px )") // This is a bit hacky, yes I know!
|
.SetStyle("left: calc( 50% - 15px )") // This is a bit hacky, yes I know!
|
||||||
}
|
}
|
||||||
|
|
||||||
new StrayClickHandler(
|
StrayClickHandler.construct(
|
||||||
state,
|
state,
|
||||||
addNewPoint,
|
addNewPoint,
|
||||||
hasPresets ? new AddNewMarker(state.filteredLayers) : noteMarker
|
hasPresets ? new AddNewMarker(state.filteredLayers) : noteMarker
|
||||||
|
@ -151,6 +161,9 @@ export default class DefaultGUI {
|
||||||
}
|
}
|
||||||
|
|
||||||
private SetupMap() {
|
private SetupMap() {
|
||||||
|
if (Utils.runningFromConsole) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const state = this.state
|
const state = this.state
|
||||||
const guiState = this.guiState
|
const guiState = this.guiState
|
||||||
|
|
||||||
|
@ -242,12 +255,14 @@ export default class DefaultGUI {
|
||||||
const search = new SearchAndGo(state).SetClass(
|
const search = new SearchAndGo(state).SetClass(
|
||||||
"shadow rounded-full h-min w-full overflow-hidden sm:max-w-sm pointer-events-auto"
|
"shadow rounded-full h-min w-full overflow-hidden sm:max-w-sm pointer-events-auto"
|
||||||
)
|
)
|
||||||
document.addEventListener("keydown", function (event) {
|
Hotkeys.RegisterHotkey(
|
||||||
if (event.ctrlKey && event.code === "KeyF") {
|
{ ctrl: "F" },
|
||||||
|
"Select the search bar to search locations",
|
||||||
|
() => {
|
||||||
search.focus()
|
search.focus()
|
||||||
event.preventDefault()
|
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
return search
|
return search
|
||||||
}),
|
}),
|
||||||
]).AttachTo("top-right")
|
]).AttachTo("top-right")
|
||||||
|
@ -256,7 +271,7 @@ export default class DefaultGUI {
|
||||||
new RightControls(state, this.geolocationHandler).AttachTo("bottom-right")
|
new RightControls(state, this.geolocationHandler).AttachTo("bottom-right")
|
||||||
|
|
||||||
new CenterMessageBox(state).AttachTo("centermessage")
|
new CenterMessageBox(state).AttachTo("centermessage")
|
||||||
document.getElementById("centermessage").classList.add("pointer-events-none")
|
document?.getElementById("centermessage")?.classList?.add("pointer-events-none")
|
||||||
|
|
||||||
// We have to ping the welcomeMessageIsOpened and other isOpened-stuff to activate the FullScreenMessage if needed
|
// We have to ping the welcomeMessageIsOpened and other isOpened-stuff to activate the FullScreenMessage if needed
|
||||||
for (const state of guiState.allFullScreenStates) {
|
for (const state of guiState.allFullScreenStates) {
|
||||||
|
|
|
@ -17,7 +17,10 @@ export default class ShowDataLayer {
|
||||||
*/
|
*/
|
||||||
constructor(options: ShowDataLayerOptions & { layerToShow: LayerConfig }) {
|
constructor(options: ShowDataLayerOptions & { layerToShow: LayerConfig }) {
|
||||||
if (ShowDataLayer.actualContstructor === undefined) {
|
if (ShowDataLayer.actualContstructor === undefined) {
|
||||||
throw "Show data layer is called, but it isn't initialized yet. Call ` ShowDataLayer.actualContstructor = (options => new ShowDataLayerImplementation(options)) ` somewhere, e.g. in your init"
|
console.error(
|
||||||
|
"Show data layer is called, but it isn't initialized yet. Call ` ShowDataLayer.actualContstructor = (options => new ShowDataLayerImplementation(options)) ` somewhere, e.g. in your init"
|
||||||
|
)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
ShowDataLayer.actualContstructor(options)
|
ShowDataLayer.actualContstructor(options)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,13 @@ import SharedTagRenderings from "../Customizations/SharedTagRenderings"
|
||||||
import { writeFile } from "fs"
|
import { writeFile } from "fs"
|
||||||
import Translations from "../UI/i18n/Translations"
|
import Translations from "../UI/i18n/Translations"
|
||||||
import * as themeOverview from "../assets/generated/theme_overview.json"
|
import * as themeOverview from "../assets/generated/theme_overview.json"
|
||||||
|
import DefaultGUI from "../UI/DefaultGUI"
|
||||||
|
import FeaturePipelineState from "../Logic/State/FeaturePipelineState"
|
||||||
|
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
|
||||||
|
import * as bookcases from "../assets/generated/themes/bookcases.json"
|
||||||
|
import { DefaultGuiState } from "../UI/DefaultGuiState"
|
||||||
|
import * as fakedom from "fake-dom"
|
||||||
|
import Hotkeys from "../UI/Base/Hotkeys"
|
||||||
function WriteFile(
|
function WriteFile(
|
||||||
filename,
|
filename,
|
||||||
html: BaseUIElement,
|
html: BaseUIElement,
|
||||||
|
@ -217,5 +223,13 @@ WriteFile("./Docs/URL_Parameters.md", QueryParameterDocumentation.GenerateQueryP
|
||||||
"Logic/Web/QueryParameters.ts",
|
"Logic/Web/QueryParameters.ts",
|
||||||
"UI/QueryParameterDocumentation.ts",
|
"UI/QueryParameterDocumentation.ts",
|
||||||
])
|
])
|
||||||
|
if (fakedom === undefined || window === undefined) {
|
||||||
|
throw "FakeDom not initialized"
|
||||||
|
}
|
||||||
|
new DefaultGUI(
|
||||||
|
new FeaturePipelineState(new LayoutConfig(<any>bookcases)),
|
||||||
|
new DefaultGuiState()
|
||||||
|
).setup()
|
||||||
|
|
||||||
|
WriteFile("./Docs/Hotkeys.md", Hotkeys.generateDocumentation(), [])
|
||||||
console.log("Generated docs")
|
console.log("Generated docs")
|
||||||
|
|
Loading…
Reference in a new issue