forked from MapComplete/MapComplete
		
	Refactoring: allow to export the map as PNG
This commit is contained in:
		
							parent
							
								
									e36e9123f3
								
							
						
					
					
						commit
						7f8969146a
					
				
					 16 changed files with 207 additions and 66 deletions
				
			
		| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
import { Utils } from "../Utils"
 | 
					import { Utils } from "../Utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type PriviligedLayerType = typeof Constants.priviliged_layers[number]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class Constants {
 | 
					export default class Constants {
 | 
				
			||||||
    public static vNumber = "0.30.0"
 | 
					    public static vNumber = "0.30.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,3 +15,7 @@ export interface MapProperties {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    readonly allowZooming: UIEventSource<true | boolean>
 | 
					    readonly allowZooming: UIEventSource<true | boolean>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ExportableMap {
 | 
				
			||||||
 | 
					    exportAsPng(): Promise<Blob>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ import LayerConfig from "./ThemeConfig/LayerConfig"
 | 
				
			||||||
import { UIEventSource } from "../Logic/UIEventSource"
 | 
					import { UIEventSource } from "../Logic/UIEventSource"
 | 
				
			||||||
import UserRelatedState from "../Logic/State/UserRelatedState"
 | 
					import UserRelatedState from "../Logic/State/UserRelatedState"
 | 
				
			||||||
import { Utils } from "../Utils"
 | 
					import { Utils } from "../Utils"
 | 
				
			||||||
 | 
					import { LocalStorageSource } from "../Logic/Web/LocalStorageSource"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Indicates if a menu is open, and if so, which tab is selected;
 | 
					 * Indicates if a menu is open, and if so, which tab is selected;
 | 
				
			||||||
| 
						 | 
					@ -11,12 +12,12 @@ import { Utils } from "../Utils"
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export class MenuState {
 | 
					export class MenuState {
 | 
				
			||||||
    private static readonly _themeviewTabs = ["intro", "filters", "download", "copyright"] as const
 | 
					    private static readonly _themeviewTabs = ["intro", "filters", "download", "copyright"] as const
 | 
				
			||||||
    public readonly themeIsOpened = new UIEventSource(true)
 | 
					    public readonly themeIsOpened: UIEventSource<boolean>
 | 
				
			||||||
    public readonly themeViewTabIndex: UIEventSource<number>
 | 
					    public readonly themeViewTabIndex: UIEventSource<number>
 | 
				
			||||||
    public readonly themeViewTab: UIEventSource<typeof MenuState._themeviewTabs[number]>
 | 
					    public readonly themeViewTab: UIEventSource<typeof MenuState._themeviewTabs[number]>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static readonly _menuviewTabs = ["about", "settings", "community", "privacy"] as const
 | 
					    private static readonly _menuviewTabs = ["about", "settings", "community", "privacy"] as const
 | 
				
			||||||
    public readonly menuIsOpened = new UIEventSource(false)
 | 
					    public readonly menuIsOpened: UIEventSource<boolean>
 | 
				
			||||||
    public readonly menuViewTabIndex: UIEventSource<number>
 | 
					    public readonly menuViewTabIndex: UIEventSource<number>
 | 
				
			||||||
    public readonly menuViewTab: UIEventSource<typeof MenuState._menuviewTabs[number]>
 | 
					    public readonly menuViewTab: UIEventSource<typeof MenuState._menuviewTabs[number]>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,15 +25,20 @@ export class MenuState {
 | 
				
			||||||
        undefined
 | 
					        undefined
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    public highlightedUserSetting: UIEventSource<string> = new UIEventSource<string>(undefined)
 | 
					    public highlightedUserSetting: UIEventSource<string> = new UIEventSource<string>(undefined)
 | 
				
			||||||
    constructor() {
 | 
					    constructor(themeid: string = "") {
 | 
				
			||||||
        this.themeViewTabIndex = new UIEventSource(0)
 | 
					        if (themeid) {
 | 
				
			||||||
 | 
					            themeid += "-"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.themeIsOpened = LocalStorageSource.GetParsed(themeid + "thememenuisopened", true)
 | 
				
			||||||
 | 
					        this.themeViewTabIndex = LocalStorageSource.GetParsed(themeid + "themeviewtabindex", 0)
 | 
				
			||||||
        this.themeViewTab = this.themeViewTabIndex.sync(
 | 
					        this.themeViewTab = this.themeViewTabIndex.sync(
 | 
				
			||||||
            (i) => MenuState._themeviewTabs[i],
 | 
					            (i) => MenuState._themeviewTabs[i],
 | 
				
			||||||
            [],
 | 
					            [],
 | 
				
			||||||
            (str) => MenuState._themeviewTabs.indexOf(<any>str)
 | 
					            (str) => MenuState._themeviewTabs.indexOf(<any>str)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.menuViewTabIndex = new UIEventSource(1)
 | 
					        this.menuIsOpened = LocalStorageSource.GetParsed(themeid + "menuisopened", false)
 | 
				
			||||||
 | 
					        this.menuViewTabIndex = LocalStorageSource.GetParsed(themeid + "menuviewtabindex", 0)
 | 
				
			||||||
        this.menuViewTab = this.menuViewTabIndex.sync(
 | 
					        this.menuViewTab = this.menuViewTabIndex.sync(
 | 
				
			||||||
            (i) => MenuState._menuviewTabs[i],
 | 
					            (i) => MenuState._menuviewTabs[i],
 | 
				
			||||||
            [],
 | 
					            [],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -424,7 +424,7 @@ export default class LayerConfig extends WithContextLoader {
 | 
				
			||||||
        if (mapRendering === undefined) {
 | 
					        if (mapRendering === undefined) {
 | 
				
			||||||
            return undefined
 | 
					            return undefined
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return mapRendering.GetBaseIcon(this.GetBaseTags())
 | 
					        return mapRendering.GetBaseIcon(this.GetBaseTags(), { noFullWidth: true })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public GetBaseTags(): Record<string, string> {
 | 
					    public GetBaseTags(): Record<string, string> {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -136,7 +136,10 @@ export default class PointRenderingConfig extends WithContextLoader {
 | 
				
			||||||
        multiSpec: string,
 | 
					        multiSpec: string,
 | 
				
			||||||
        rotation: string,
 | 
					        rotation: string,
 | 
				
			||||||
        isBadge: boolean,
 | 
					        isBadge: boolean,
 | 
				
			||||||
        defaultElement: BaseUIElement = undefined
 | 
					        defaultElement: BaseUIElement = undefined,
 | 
				
			||||||
 | 
					        options?: {
 | 
				
			||||||
 | 
					            noFullWidth?: boolean
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        if (multiSpec === undefined) {
 | 
					        if (multiSpec === undefined) {
 | 
				
			||||||
            return defaultElement
 | 
					            return defaultElement
 | 
				
			||||||
| 
						 | 
					@ -150,11 +153,21 @@ export default class PointRenderingConfig extends WithContextLoader {
 | 
				
			||||||
        if (elements.length === 0) {
 | 
					        if (elements.length === 0) {
 | 
				
			||||||
            return defaultElement
 | 
					            return defaultElement
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            return new Combine(elements).SetClass("relative block w-full h-full")
 | 
					            const combine = new Combine(elements).SetClass("relative block")
 | 
				
			||||||
 | 
					            if (options?.noFullWidth) {
 | 
				
			||||||
 | 
					                return combine
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            combine.SetClass("w-full h-full")
 | 
				
			||||||
 | 
					            return combine
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public GetBaseIcon(tags?: Record<string, string>): BaseUIElement {
 | 
					    public GetBaseIcon(
 | 
				
			||||||
 | 
					        tags?: Record<string, string>,
 | 
				
			||||||
 | 
					        options?: {
 | 
				
			||||||
 | 
					            noFullWidth?: boolean
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ): BaseUIElement {
 | 
				
			||||||
        tags = tags ?? { id: "node/-1" }
 | 
					        tags = tags ?? { id: "node/-1" }
 | 
				
			||||||
        let defaultPin: BaseUIElement = undefined
 | 
					        let defaultPin: BaseUIElement = undefined
 | 
				
			||||||
        if (this.label === undefined) {
 | 
					        if (this.label === undefined) {
 | 
				
			||||||
| 
						 | 
					@ -176,7 +189,7 @@ export default class PointRenderingConfig extends WithContextLoader {
 | 
				
			||||||
            // This is probably already prepared HTML
 | 
					            // This is probably already prepared HTML
 | 
				
			||||||
            return new FixedUiElement(Utils.SubstituteKeys(htmlDefs, tags))
 | 
					            return new FixedUiElement(Utils.SubstituteKeys(htmlDefs, tags))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return PointRenderingConfig.FromHtmlMulti(htmlDefs, rotation, false, defaultPin)
 | 
					        return PointRenderingConfig.FromHtmlMulti(htmlDefs, rotation, false, defaultPin, options)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public GetSimpleIcon(tags: Store<Record<string, string>>): BaseUIElement {
 | 
					    public GetSimpleIcon(tags: Store<Record<string, string>>): BaseUIElement {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ import {
 | 
				
			||||||
    WritableFeatureSource,
 | 
					    WritableFeatureSource,
 | 
				
			||||||
} from "../Logic/FeatureSource/FeatureSource"
 | 
					} from "../Logic/FeatureSource/FeatureSource"
 | 
				
			||||||
import { OsmConnection } from "../Logic/Osm/OsmConnection"
 | 
					import { OsmConnection } from "../Logic/Osm/OsmConnection"
 | 
				
			||||||
import { MapProperties } from "./MapProperties"
 | 
					import { ExportableMap, MapProperties } from "./MapProperties"
 | 
				
			||||||
import LayerState from "../Logic/State/LayerState"
 | 
					import LayerState from "../Logic/State/LayerState"
 | 
				
			||||||
import { Feature } from "geojson"
 | 
					import { Feature } from "geojson"
 | 
				
			||||||
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
 | 
					import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
 | 
				
			||||||
| 
						 | 
					@ -63,7 +63,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    readonly osmConnection: OsmConnection
 | 
					    readonly osmConnection: OsmConnection
 | 
				
			||||||
    readonly selectedElement: UIEventSource<Feature>
 | 
					    readonly selectedElement: UIEventSource<Feature>
 | 
				
			||||||
    readonly mapProperties: MapProperties
 | 
					    readonly mapProperties: MapProperties & ExportableMap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    readonly dataIsLoading: Store<boolean> // TODO
 | 
					    readonly dataIsLoading: Store<boolean> // TODO
 | 
				
			||||||
    readonly guistate: MenuState
 | 
					    readonly guistate: MenuState
 | 
				
			||||||
| 
						 | 
					@ -82,7 +82,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
 | 
				
			||||||
    readonly lastClickObject: WritableFeatureSource
 | 
					    readonly lastClickObject: WritableFeatureSource
 | 
				
			||||||
    constructor(layout: LayoutConfig) {
 | 
					    constructor(layout: LayoutConfig) {
 | 
				
			||||||
        this.layout = layout
 | 
					        this.layout = layout
 | 
				
			||||||
        this.guistate = new MenuState()
 | 
					        this.guistate = new MenuState(layout.id)
 | 
				
			||||||
        this.map = new UIEventSource<MlMap>(undefined)
 | 
					        this.map = new UIEventSource<MlMap>(undefined)
 | 
				
			||||||
        const initial = new InitialMapPositioning(layout)
 | 
					        const initial = new InitialMapPositioning(layout)
 | 
				
			||||||
        this.mapProperties = new MapLibreAdaptor(this.map, initial)
 | 
					        this.mapProperties = new MapLibreAdaptor(this.map, initial)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -86,12 +86,6 @@ export default class AllDownloads extends ScrollableFullScreen {
 | 
				
			||||||
            state.featureSwitchExportAsPdf
 | 
					            state.featureSwitchExportAsPdf
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const exportPanel = new Toggle(
 | 
					 | 
				
			||||||
            new DownloadPanel(state),
 | 
					 | 
				
			||||||
            undefined,
 | 
					 | 
				
			||||||
            state.featureSwitchEnableExport
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new Combine([pdf, exportPanel]).SetClass("flex flex-col")
 | 
					        return pdf
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,7 @@ import { SpecialVisualizationState } from "../SpecialVisualization"
 | 
				
			||||||
import { Feature, FeatureCollection } from "geojson"
 | 
					import { Feature, FeatureCollection } from "geojson"
 | 
				
			||||||
import { GeoIndexedStoreForLayer } from "../../Logic/FeatureSource/Actors/GeoIndexedStore"
 | 
					import { GeoIndexedStoreForLayer } from "../../Logic/FeatureSource/Actors/GeoIndexedStore"
 | 
				
			||||||
import LayerState from "../../Logic/State/LayerState"
 | 
					import LayerState from "../../Logic/State/LayerState"
 | 
				
			||||||
 | 
					import { PriviligedLayerType } from "../../Models/Constants"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class DownloadPanel extends Toggle {
 | 
					export class DownloadPanel extends Toggle {
 | 
				
			||||||
    constructor(state: SpecialVisualizationState) {
 | 
					    constructor(state: SpecialVisualizationState) {
 | 
				
			||||||
| 
						 | 
					@ -86,11 +87,37 @@ export class DownloadPanel extends Toggle {
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const buttonPng = new SubtleButton(
 | 
				
			||||||
 | 
					            Svg.floppy_ui(),
 | 
				
			||||||
 | 
					            new Combine([t.downloadAsPng.SetClass("font-bold"), t.downloadAsPngHelper])
 | 
				
			||||||
 | 
					        ).OnClickWithLoading(t.exporting, async () => {
 | 
				
			||||||
 | 
					            const gpsLayer = state.layerState.filteredLayers.get(
 | 
				
			||||||
 | 
					                <PriviligedLayerType>"gps_location"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            const gpsIsDisplayed = gpsLayer.isDisplayed.data
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                gpsLayer.isDisplayed.setData(false)
 | 
				
			||||||
 | 
					                const png = await state.mapProperties.exportAsPng()
 | 
				
			||||||
 | 
					                Utils.offerContentsAsDownloadableFile(
 | 
				
			||||||
 | 
					                    png,
 | 
				
			||||||
 | 
					                    `MapComplete_${name}_export_${new Date().toISOString().substr(0, 19)}.png`,
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        mimetype: "image/png",
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            } catch (e) {
 | 
				
			||||||
 | 
					                console.error(e)
 | 
				
			||||||
 | 
					            } finally {
 | 
				
			||||||
 | 
					                gpsLayer.isDisplayed.setData(gpsIsDisplayed)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const downloadButtons = new Combine([
 | 
					        const downloadButtons = new Combine([
 | 
				
			||||||
            new Title(t.title),
 | 
					            new Title(t.title),
 | 
				
			||||||
            buttonGeoJson,
 | 
					            buttonGeoJson,
 | 
				
			||||||
            buttonCSV,
 | 
					            buttonCSV,
 | 
				
			||||||
            buttonSvg,
 | 
					            buttonSvg,
 | 
				
			||||||
 | 
					            buttonPng,
 | 
				
			||||||
            includeMetaToggle,
 | 
					            includeMetaToggle,
 | 
				
			||||||
            t.licenseInfo.SetClass("link-underline"),
 | 
					            t.licenseInfo.SetClass("link-underline"),
 | 
				
			||||||
        ]).SetClass("w-full flex flex-col")
 | 
					        ]).SetClass("w-full flex flex-col")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,20 +56,10 @@ export default class LeftControls extends Combine {
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        new AllDownloads(guiState.downloadControlIsOpened, state)
 | 
					        new AllDownloads(guiState.downloadControlIsOpened, state)
 | 
				
			||||||
        const toggledDownload = new MapControlButton(Svg.download_svg()).onClick(() =>
 | 
					 | 
				
			||||||
            guiState.downloadControlIsOpened.setData(true)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const downloadButton = new Toggle(
 | 
					 | 
				
			||||||
            toggledDownload,
 | 
					 | 
				
			||||||
            undefined,
 | 
					 | 
				
			||||||
            state.featureSwitchEnableExport.map(
 | 
					 | 
				
			||||||
                (downloadEnabled) => downloadEnabled || state.featureSwitchExportAsPdf.data,
 | 
					 | 
				
			||||||
                [state.featureSwitchExportAsPdf]
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super([currentViewAction, downloadButton])
 | 
					
 | 
				
			||||||
 | 
					        super([currentViewAction])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.SetClass("flex flex-col")
 | 
					        this.SetClass("flex flex-col")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,14 +4,15 @@ import { Map as MlMap } from "maplibre-gl"
 | 
				
			||||||
import { RasterLayerPolygon, RasterLayerProperties } from "../../Models/RasterLayers"
 | 
					import { RasterLayerPolygon, RasterLayerProperties } from "../../Models/RasterLayers"
 | 
				
			||||||
import { Utils } from "../../Utils"
 | 
					import { Utils } from "../../Utils"
 | 
				
			||||||
import { BBox } from "../../Logic/BBox"
 | 
					import { BBox } from "../../Logic/BBox"
 | 
				
			||||||
import { MapProperties } from "../../Models/MapProperties"
 | 
					import { ExportableMap, MapProperties } from "../../Models/MapProperties"
 | 
				
			||||||
import SvelteUIElement from "../Base/SvelteUIElement"
 | 
					import SvelteUIElement from "../Base/SvelteUIElement"
 | 
				
			||||||
import MaplibreMap from "./MaplibreMap.svelte"
 | 
					import MaplibreMap from "./MaplibreMap.svelte"
 | 
				
			||||||
 | 
					import html2canvas from "html2canvas"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties`
 | 
					 * The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties`
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export class MapLibreAdaptor implements MapProperties {
 | 
					export class MapLibreAdaptor implements MapProperties, ExportableMap {
 | 
				
			||||||
    private static maplibre_control_handlers = [
 | 
					    private static maplibre_control_handlers = [
 | 
				
			||||||
        // "scrollZoom",
 | 
					        // "scrollZoom",
 | 
				
			||||||
        // "boxZoom",
 | 
					        // "boxZoom",
 | 
				
			||||||
| 
						 | 
					@ -125,23 +126,6 @@ export class MapLibreAdaptor implements MapProperties {
 | 
				
			||||||
        this.bounds.addCallbackAndRunD((bounds) => self.setBounds(bounds))
 | 
					        this.bounds.addCallbackAndRunD((bounds) => self.setBounds(bounds))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private updateStores() {
 | 
					 | 
				
			||||||
        const map = this._maplibreMap.data
 | 
					 | 
				
			||||||
        if (map === undefined) {
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        const dt = this.location.data
 | 
					 | 
				
			||||||
        dt.lon = map.getCenter().lng
 | 
					 | 
				
			||||||
        dt.lat = map.getCenter().lat
 | 
					 | 
				
			||||||
        this.location.ping()
 | 
					 | 
				
			||||||
        this.zoom.setData(Math.round(map.getZoom() * 10) / 10)
 | 
					 | 
				
			||||||
        const bounds = map.getBounds()
 | 
					 | 
				
			||||||
        const bbox = new BBox([
 | 
					 | 
				
			||||||
            [bounds.getEast(), bounds.getNorth()],
 | 
					 | 
				
			||||||
            [bounds.getWest(), bounds.getSouth()],
 | 
					 | 
				
			||||||
        ])
 | 
					 | 
				
			||||||
        this.bounds.setData(bbox)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Convenience constructor
 | 
					     * Convenience constructor
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
| 
						 | 
					@ -189,6 +173,113 @@ export class MapLibreAdaptor implements MapProperties {
 | 
				
			||||||
        return url
 | 
					        return url
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async exportAsPng(): Promise<Blob> {
 | 
				
			||||||
 | 
					        const map = this._maplibreMap.data
 | 
				
			||||||
 | 
					        if (map === undefined) {
 | 
				
			||||||
 | 
					            return undefined
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function setDPI(canvas, dpi) {
 | 
				
			||||||
 | 
					            // Set up CSS size.
 | 
				
			||||||
 | 
					            canvas.style.width = canvas.style.width || canvas.width + "px"
 | 
				
			||||||
 | 
					            canvas.style.height = canvas.style.height || canvas.height + "px"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Resize canvas and scale future draws.
 | 
				
			||||||
 | 
					            const scaleFactor = dpi / 96
 | 
				
			||||||
 | 
					            canvas.width = Math.ceil(canvas.width * scaleFactor)
 | 
				
			||||||
 | 
					            canvas.height = Math.ceil(canvas.height * scaleFactor)
 | 
				
			||||||
 | 
					            const ctx = canvas.getContext("2d")
 | 
				
			||||||
 | 
					            ctx?.scale(scaleFactor, scaleFactor)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Total hack - see https://stackoverflow.com/questions/42483449/mapbox-gl-js-export-map-to-png-or-pdf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const drawOn = document.createElement("canvas")
 | 
				
			||||||
 | 
					        drawOn.width = document.documentElement.clientWidth
 | 
				
			||||||
 | 
					        drawOn.height = document.documentElement.clientHeight
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setDPI(drawOn, 4 * 96)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const destinationCtx = drawOn.getContext("2d")
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // First, we draw the maplibre-map onto the canvas. This does not export markers
 | 
				
			||||||
 | 
					            // Inspiration by https://github.com/mapbox/mapbox-gl-js/issues/2766
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const promise = new Promise<void>((resolve) => {
 | 
				
			||||||
 | 
					                map.once("render", () => {
 | 
				
			||||||
 | 
					                    destinationCtx.drawImage(map.getCanvas(), 0, 0)
 | 
				
			||||||
 | 
					                    resolve()
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            while (!map.isStyleLoaded()) {
 | 
				
			||||||
 | 
					                console.log("Waiting to fully load the style...")
 | 
				
			||||||
 | 
					                await Utils.waitFor(100)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            map.triggerRepaint()
 | 
				
			||||||
 | 
					            await promise
 | 
				
			||||||
 | 
					            // Reset the canvas width and height
 | 
				
			||||||
 | 
					            map.resize()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // now, we draw the markers on top of the map
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /* We use html2canvas for this, but disable the map canvas object itself:
 | 
				
			||||||
 | 
					             * it cannot deal with this canvas object.
 | 
				
			||||||
 | 
					             *
 | 
				
			||||||
 | 
					             * We also have to patch up a few more objects
 | 
				
			||||||
 | 
					             * */
 | 
				
			||||||
 | 
					            const container = map.getCanvasContainer()
 | 
				
			||||||
 | 
					            const origHeight = container.style.height
 | 
				
			||||||
 | 
					            const origStyle = map.getCanvas().style.display
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                map.getCanvas().style.display = "none"
 | 
				
			||||||
 | 
					                if (!container.style.height) {
 | 
				
			||||||
 | 
					                    container.style.height = document.documentElement.clientHeight + "px"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const markerCanvas: HTMLCanvasElement = await html2canvas(
 | 
				
			||||||
 | 
					                    map.getCanvasContainer(),
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        backgroundColor: "#00000000",
 | 
				
			||||||
 | 
					                        canvas: drawOn,
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                const markers = await new Promise<Blob>((resolve) =>
 | 
				
			||||||
 | 
					                    markerCanvas.toBlob((data) => resolve(data))
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                console.log("Markers:", markers, markerCanvas)
 | 
				
			||||||
 | 
					                // destinationCtx.drawImage(markerCanvas, 0, 0)
 | 
				
			||||||
 | 
					            } catch (e) {
 | 
				
			||||||
 | 
					                console.error(e)
 | 
				
			||||||
 | 
					            } finally {
 | 
				
			||||||
 | 
					                map.getCanvas().style.display = origStyle
 | 
				
			||||||
 | 
					                container.style.height = origHeight
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // At last, we return the actual blob
 | 
				
			||||||
 | 
					        return new Promise<Blob>((resolve) => drawOn.toBlob((data) => resolve(data)))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private updateStores() {
 | 
				
			||||||
 | 
					        const map = this._maplibreMap.data
 | 
				
			||||||
 | 
					        if (map === undefined) {
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const dt = this.location.data
 | 
				
			||||||
 | 
					        dt.lon = map.getCenter().lng
 | 
				
			||||||
 | 
					        dt.lat = map.getCenter().lat
 | 
				
			||||||
 | 
					        this.location.ping()
 | 
				
			||||||
 | 
					        this.zoom.setData(Math.round(map.getZoom() * 10) / 10)
 | 
				
			||||||
 | 
					        const bounds = map.getBounds()
 | 
				
			||||||
 | 
					        const bbox = new BBox([
 | 
				
			||||||
 | 
					            [bounds.getEast(), bounds.getNorth()],
 | 
				
			||||||
 | 
					            [bounds.getWest(), bounds.getSouth()],
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					        this.bounds.setData(bbox)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private SetZoom(z: number) {
 | 
					    private SetZoom(z: number) {
 | 
				
			||||||
        const map = this._maplibreMap.data
 | 
					        const map = this._maplibreMap.data
 | 
				
			||||||
        if (!map || z === undefined) {
 | 
					        if (!map || z === undefined) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
 | 
				
			||||||
import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"
 | 
					import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"
 | 
				
			||||||
import { OsmConnection } from "../Logic/Osm/OsmConnection"
 | 
					import { OsmConnection } from "../Logic/Osm/OsmConnection"
 | 
				
			||||||
import { Changes } from "../Logic/Osm/Changes"
 | 
					import { Changes } from "../Logic/Osm/Changes"
 | 
				
			||||||
import { MapProperties } from "../Models/MapProperties"
 | 
					import { ExportableMap, MapProperties } from "../Models/MapProperties"
 | 
				
			||||||
import LayerState from "../Logic/State/LayerState"
 | 
					import LayerState from "../Logic/State/LayerState"
 | 
				
			||||||
import { Feature, Geometry } from "geojson"
 | 
					import { Feature, Geometry } from "geojson"
 | 
				
			||||||
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
 | 
					import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
 | 
				
			||||||
| 
						 | 
					@ -42,7 +42,7 @@ export interface SpecialVisualizationState {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * State of the main map
 | 
					     * State of the main map
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    readonly mapProperties: MapProperties
 | 
					    readonly mapProperties: MapProperties & ExportableMap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    readonly selectedElement: UIEventSource<Feature>
 | 
					    readonly selectedElement: UIEventSource<Feature>
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								Utils.ts
									
										
									
									
									
								
							| 
						 | 
					@ -1045,6 +1045,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
 | 
				
			||||||
                | "application/vnd.geo+json"
 | 
					                | "application/vnd.geo+json"
 | 
				
			||||||
                | "{gpx=application/gpx+xml}"
 | 
					                | "{gpx=application/gpx+xml}"
 | 
				
			||||||
                | "application/json"
 | 
					                | "application/json"
 | 
				
			||||||
 | 
					                | "image/png"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        const element = document.createElement("a")
 | 
					        const element = document.createElement("a")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -159,6 +159,8 @@
 | 
				
			||||||
        "download": {
 | 
					        "download": {
 | 
				
			||||||
            "downloadAsPdf": "Download a PDF of the current map",
 | 
					            "downloadAsPdf": "Download a PDF of the current map",
 | 
				
			||||||
            "downloadAsPdfHelper": "Ideal to print the current map",
 | 
					            "downloadAsPdfHelper": "Ideal to print the current map",
 | 
				
			||||||
 | 
					            "downloadAsPng": "Download as image",
 | 
				
			||||||
 | 
					            "downloadAsPngHelper": "Ideal to include in reports",
 | 
				
			||||||
            "downloadAsSvg": "Download an SVG of the current map",
 | 
					            "downloadAsSvg": "Download an SVG of the current map",
 | 
				
			||||||
            "downloadAsSvgHelper": "Compatible Inkscape or Adobe Illustrator; will need further processing",
 | 
					            "downloadAsSvgHelper": "Compatible Inkscape or Adobe Illustrator; will need further processing",
 | 
				
			||||||
            "downloadCSV": "Download visible data as CSV",
 | 
					            "downloadCSV": "Download visible data as CSV",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										31
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										31
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -19,6 +19,7 @@
 | 
				
			||||||
        "@turf/distance": "^6.5.0",
 | 
					        "@turf/distance": "^6.5.0",
 | 
				
			||||||
        "@turf/length": "^6.5.0",
 | 
					        "@turf/length": "^6.5.0",
 | 
				
			||||||
        "@turf/turf": "^6.5.0",
 | 
					        "@turf/turf": "^6.5.0",
 | 
				
			||||||
 | 
					        "@types/html2canvas": "^1.0.0",
 | 
				
			||||||
        "@types/showdown": "^2.0.0",
 | 
					        "@types/showdown": "^2.0.0",
 | 
				
			||||||
        "chart.js": "^3.8.0",
 | 
					        "chart.js": "^3.8.0",
 | 
				
			||||||
        "country-language": "^0.1.7",
 | 
					        "country-language": "^0.1.7",
 | 
				
			||||||
| 
						 | 
					@ -29,6 +30,7 @@
 | 
				
			||||||
        "fake-dom": "^1.0.4",
 | 
					        "fake-dom": "^1.0.4",
 | 
				
			||||||
        "geojson2svg": "^1.3.3",
 | 
					        "geojson2svg": "^1.3.3",
 | 
				
			||||||
        "html-to-markdown": "^1.0.0",
 | 
					        "html-to-markdown": "^1.0.0",
 | 
				
			||||||
 | 
					        "html2canvas": "^1.4.1",
 | 
				
			||||||
        "i18next-client": "^1.11.4",
 | 
					        "i18next-client": "^1.11.4",
 | 
				
			||||||
        "idb-keyval": "^6.0.3",
 | 
					        "idb-keyval": "^6.0.3",
 | 
				
			||||||
        "jest-mock": "^29.4.1",
 | 
					        "jest-mock": "^29.4.1",
 | 
				
			||||||
| 
						 | 
					@ -3644,6 +3646,15 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
 | 
				
			||||||
      "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA=="
 | 
					      "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@types/html2canvas": {
 | 
				
			||||||
 | 
					      "version": "1.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@types/html2canvas/-/html2canvas-1.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-BJpVf+FIN9UERmzhbtUgpXj6XBZpG67FMgBLLoj9HZKd9XifcCpSV+UnFcwTZfEyun4U/KmCrrVOG7829L589w==",
 | 
				
			||||||
 | 
					      "deprecated": "This is a stub types definition. html2canvas provides its own type definitions, so you do not need this installed.",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "html2canvas": "*"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@types/istanbul-lib-coverage": {
 | 
					    "node_modules/@types/istanbul-lib-coverage": {
 | 
				
			||||||
      "version": "2.0.4",
 | 
					      "version": "2.0.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
 | 
				
			||||||
| 
						 | 
					@ -4223,7 +4234,6 @@
 | 
				
			||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
 | 
					      "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
 | 
				
			||||||
      "optional": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">= 0.6.0"
 | 
					        "node": ">= 0.6.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -4876,7 +4886,6 @@
 | 
				
			||||||
      "version": "2.1.0",
 | 
					      "version": "2.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
 | 
					      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
 | 
				
			||||||
      "optional": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "utrie": "^1.0.2"
 | 
					        "utrie": "^1.0.2"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -6480,7 +6489,6 @@
 | 
				
			||||||
      "version": "1.4.1",
 | 
					      "version": "1.4.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
 | 
					      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
 | 
				
			||||||
      "optional": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "css-line-break": "^2.1.0",
 | 
					        "css-line-break": "^2.1.0",
 | 
				
			||||||
        "text-segmentation": "^1.0.3"
 | 
					        "text-segmentation": "^1.0.3"
 | 
				
			||||||
| 
						 | 
					@ -10167,7 +10175,6 @@
 | 
				
			||||||
      "version": "1.0.3",
 | 
					      "version": "1.0.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
 | 
					      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
 | 
				
			||||||
      "optional": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "utrie": "^1.0.2"
 | 
					        "utrie": "^1.0.2"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -11527,7 +11534,6 @@
 | 
				
			||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
 | 
					      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
 | 
				
			||||||
      "optional": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "base64-arraybuffer": "^1.0.2"
 | 
					        "base64-arraybuffer": "^1.0.2"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -14804,6 +14810,14 @@
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
 | 
				
			||||||
      "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA=="
 | 
					      "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "@types/html2canvas": {
 | 
				
			||||||
 | 
					      "version": "1.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@types/html2canvas/-/html2canvas-1.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-BJpVf+FIN9UERmzhbtUgpXj6XBZpG67FMgBLLoj9HZKd9XifcCpSV+UnFcwTZfEyun4U/KmCrrVOG7829L589w==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "html2canvas": "*"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "@types/istanbul-lib-coverage": {
 | 
					    "@types/istanbul-lib-coverage": {
 | 
				
			||||||
      "version": "2.0.4",
 | 
					      "version": "2.0.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
 | 
				
			||||||
| 
						 | 
					@ -15288,8 +15302,7 @@
 | 
				
			||||||
    "base64-arraybuffer": {
 | 
					    "base64-arraybuffer": {
 | 
				
			||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
 | 
					      "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="
 | 
				
			||||||
      "optional": true
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "base64-js": {
 | 
					    "base64-js": {
 | 
				
			||||||
      "version": "1.5.1",
 | 
					      "version": "1.5.1",
 | 
				
			||||||
| 
						 | 
					@ -15763,7 +15776,6 @@
 | 
				
			||||||
      "version": "2.1.0",
 | 
					      "version": "2.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
 | 
					      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
 | 
				
			||||||
      "optional": true,
 | 
					 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "utrie": "^1.0.2"
 | 
					        "utrie": "^1.0.2"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -16991,7 +17003,6 @@
 | 
				
			||||||
      "version": "1.4.1",
 | 
					      "version": "1.4.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
 | 
					      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
 | 
				
			||||||
      "optional": true,
 | 
					 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "css-line-break": "^2.1.0",
 | 
					        "css-line-break": "^2.1.0",
 | 
				
			||||||
        "text-segmentation": "^1.0.3"
 | 
					        "text-segmentation": "^1.0.3"
 | 
				
			||||||
| 
						 | 
					@ -19717,7 +19728,6 @@
 | 
				
			||||||
      "version": "1.0.3",
 | 
					      "version": "1.0.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
 | 
					      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
 | 
				
			||||||
      "optional": true,
 | 
					 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "utrie": "^1.0.2"
 | 
					        "utrie": "^1.0.2"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -20828,7 +20838,6 @@
 | 
				
			||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
 | 
					      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
 | 
				
			||||||
      "optional": true,
 | 
					 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "base64-arraybuffer": "^1.0.2"
 | 
					        "base64-arraybuffer": "^1.0.2"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -71,6 +71,7 @@
 | 
				
			||||||
    "@turf/distance": "^6.5.0",
 | 
					    "@turf/distance": "^6.5.0",
 | 
				
			||||||
    "@turf/length": "^6.5.0",
 | 
					    "@turf/length": "^6.5.0",
 | 
				
			||||||
    "@turf/turf": "^6.5.0",
 | 
					    "@turf/turf": "^6.5.0",
 | 
				
			||||||
 | 
					    "@types/html2canvas": "^1.0.0",
 | 
				
			||||||
    "@types/showdown": "^2.0.0",
 | 
					    "@types/showdown": "^2.0.0",
 | 
				
			||||||
    "chart.js": "^3.8.0",
 | 
					    "chart.js": "^3.8.0",
 | 
				
			||||||
    "country-language": "^0.1.7",
 | 
					    "country-language": "^0.1.7",
 | 
				
			||||||
| 
						 | 
					@ -81,6 +82,7 @@
 | 
				
			||||||
    "fake-dom": "^1.0.4",
 | 
					    "fake-dom": "^1.0.4",
 | 
				
			||||||
    "geojson2svg": "^1.3.3",
 | 
					    "geojson2svg": "^1.3.3",
 | 
				
			||||||
    "html-to-markdown": "^1.0.0",
 | 
					    "html-to-markdown": "^1.0.0",
 | 
				
			||||||
 | 
					    "html2canvas": "^1.4.1",
 | 
				
			||||||
    "i18next-client": "^1.11.4",
 | 
					    "i18next-client": "^1.11.4",
 | 
				
			||||||
    "idb-keyval": "^6.0.3",
 | 
					    "idb-keyval": "^6.0.3",
 | 
				
			||||||
    "jest-mock": "^29.4.1",
 | 
					    "jest-mock": "^29.4.1",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue