forked from MapComplete/MapComplete
Merge branch 'develop' into feature/professional
This commit is contained in:
commit
73ff6ce5f4
143 changed files with 32323 additions and 30401 deletions
|
@ -13,7 +13,8 @@ export interface MinimapOptions {
|
|||
attribution?: BaseUIElement | boolean,
|
||||
onFullyLoaded?: (leaflet: L.Map) => void,
|
||||
leafletMap?: UIEventSource<any>,
|
||||
lastClickLocation?: UIEventSource<{ lat: number, lon: number }>
|
||||
lastClickLocation?: UIEventSource<{ lat: number, lon: number }>,
|
||||
addLayerControl?: boolean | false
|
||||
}
|
||||
|
||||
export interface MinimapObj {
|
||||
|
|
|
@ -10,6 +10,7 @@ import Minimap, {MinimapObj, MinimapOptions} from "./Minimap";
|
|||
import {BBox} from "../../Logic/BBox";
|
||||
import 'leaflet-polylineoffset'
|
||||
import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter";
|
||||
import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch";
|
||||
|
||||
export default class MinimapImplementation extends BaseUIElement implements MinimapObj {
|
||||
private static _nextId = 0;
|
||||
|
@ -24,6 +25,7 @@ export default class MinimapImplementation extends BaseUIElement implements Mini
|
|||
private readonly _attribution: BaseUIElement | boolean;
|
||||
private readonly _lastClickLocation: UIEventSource<{ lat: number; lon: number }>;
|
||||
private readonly _bounds: UIEventSource<BBox> | undefined;
|
||||
private readonly _addLayerControl: boolean;
|
||||
|
||||
private constructor(options: MinimapOptions) {
|
||||
super()
|
||||
|
@ -38,6 +40,7 @@ export default class MinimapImplementation extends BaseUIElement implements Mini
|
|||
this._onFullyLoaded = options.onFullyLoaded
|
||||
this._attribution = options.attribution
|
||||
this._lastClickLocation = options.lastClickLocation;
|
||||
this._addLayerControl = options.addLayerControl ?? false
|
||||
MinimapImplementation._nextId++
|
||||
|
||||
}
|
||||
|
@ -131,6 +134,17 @@ export default class MinimapImplementation extends BaseUIElement implements Mini
|
|||
});
|
||||
|
||||
resizeObserver.observe(div);
|
||||
|
||||
if (this._addLayerControl) {
|
||||
const switcher = new BackgroundMapSwitch({
|
||||
locationControl: this._location,
|
||||
backgroundLayer: this._background
|
||||
},
|
||||
this._background
|
||||
).SetClass("top-0 right-0 z-above-map absolute")
|
||||
wrapper.appendChild(switcher.ConstructElement())
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {Utils} from "../Utils";
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
|
||||
/**
|
||||
* A thin wrapper around a html element, which allows to generate a HTML-element.
|
||||
|
@ -12,7 +11,6 @@ export default abstract class BaseUIElement {
|
|||
private clss: Set<string> = new Set<string>();
|
||||
private style: string;
|
||||
private _onClick: () => void;
|
||||
private _onHover: UIEventSource<boolean>;
|
||||
|
||||
public onClick(f: (() => void)) {
|
||||
this._onClick = f;
|
||||
|
@ -138,18 +136,6 @@ export default abstract class BaseUIElement {
|
|||
el.classList.add("pointer-events-none", "cursor-pointer");
|
||||
}
|
||||
|
||||
if (this._onHover !== undefined) {
|
||||
const self = this;
|
||||
el.addEventListener('mouseover', () => self._onHover.setData(true));
|
||||
el.addEventListener('mouseout', () => self._onHover.setData(false));
|
||||
}
|
||||
|
||||
if (this._onHover !== undefined) {
|
||||
const self = this;
|
||||
el.addEventListener('mouseover', () => self._onHover.setData(true));
|
||||
el.addEventListener('mouseout', () => self._onHover.setData(false));
|
||||
}
|
||||
|
||||
return el
|
||||
} catch (e) {
|
||||
const domExc = e as DOMException;
|
||||
|
|
196
UI/BigComponents/BackgroundMapSwitch.ts
Normal file
196
UI/BigComponents/BackgroundMapSwitch.ts
Normal file
|
@ -0,0 +1,196 @@
|
|||
import Combine from "../Base/Combine";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import Loc from "../../Models/Loc";
|
||||
import Svg from "../../Svg";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import {GeoOperations} from "../../Logic/GeoOperations";
|
||||
|
||||
class SingleLayerSelectionButton extends Toggle {
|
||||
|
||||
public readonly activate: () => void
|
||||
|
||||
/**
|
||||
*
|
||||
* The SingeLayerSelectionButton also acts as an actor to keep the layers in check
|
||||
*
|
||||
* It works the following way:
|
||||
*
|
||||
* - It has a boolean state to indicate wether or not the button is active
|
||||
* - It keeps track of the available layers
|
||||
*/
|
||||
constructor(
|
||||
locationControl: UIEventSource<Loc>,
|
||||
options: {
|
||||
currentBackground: UIEventSource<BaseLayer>,
|
||||
preferredType: string,
|
||||
preferredLayer?: BaseLayer,
|
||||
notAvailable?: () => void
|
||||
}) {
|
||||
|
||||
|
||||
const prefered = options.preferredType
|
||||
const previousLayer = new UIEventSource(options.preferredLayer)
|
||||
|
||||
const unselected = SingleLayerSelectionButton.getIconFor(prefered)
|
||||
.SetClass("rounded-lg p-1 h-12 w-12 overflow-hidden subtle-background border-invisible")
|
||||
|
||||
const selected = SingleLayerSelectionButton.getIconFor(prefered)
|
||||
.SetClass("rounded-lg p-1 h-12 w-12 overflow-hidden subtle-background border-attention-catch")
|
||||
|
||||
|
||||
const available = AvailableBaseLayers
|
||||
.SelectBestLayerAccordingTo(locationControl, new UIEventSource<string | string[]>(options.preferredType))
|
||||
|
||||
let toggle: BaseUIElement = new Toggle(
|
||||
selected,
|
||||
unselected,
|
||||
options.currentBackground.map(bg => bg.category === options.preferredType)
|
||||
)
|
||||
|
||||
|
||||
super(
|
||||
toggle,
|
||||
undefined,
|
||||
available.map(av => av.category === options.preferredType)
|
||||
);
|
||||
|
||||
/**
|
||||
* Checks that the previous layer is still usable on the current location.
|
||||
* If not, clears the 'previousLayer'
|
||||
*/
|
||||
function checkPreviousLayer() {
|
||||
if (previousLayer.data === undefined) {
|
||||
return
|
||||
}
|
||||
if (previousLayer.data.feature === null || previousLayer.data.feature === undefined) {
|
||||
// Global layer
|
||||
return
|
||||
}
|
||||
const loc = locationControl.data
|
||||
if (!GeoOperations.inside([loc.lon, loc.lat], previousLayer.data.feature)) {
|
||||
// The previous layer is out of bounds
|
||||
previousLayer.setData(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
unselected.onClick(() => {
|
||||
// Note: a check if 'available' has the correct type is not needed:
|
||||
// Unselected will _not_ be visible if availableBaseLayer has a wrong type!
|
||||
checkPreviousLayer()
|
||||
|
||||
previousLayer.setData(previousLayer.data ?? available.data)
|
||||
options.currentBackground.setData(previousLayer.data)
|
||||
})
|
||||
|
||||
|
||||
available.addCallbackAndRunD(availableLayer => {
|
||||
|
||||
if (previousLayer.data === undefined) {
|
||||
// PreviousLayer is unset -> we definitively weren't using this category -> no need to switch
|
||||
return;
|
||||
}
|
||||
if (options.currentBackground.data?.id !== previousLayer.data?.id) {
|
||||
// The previously used layer doesn't match the current layer -> no need to switch
|
||||
return;
|
||||
}
|
||||
|
||||
if (availableLayer.category === options.preferredType) {
|
||||
// Allright, we can set this different layer
|
||||
options.currentBackground.setData(availableLayer)
|
||||
previousLayer.setData(availableLayer)
|
||||
} else {
|
||||
// Uh oh - no correct layer is available... We pass the torch!
|
||||
if (options.notAvailable !== undefined) {
|
||||
options.notAvailable()
|
||||
} else {
|
||||
// Fallback to OSM carto
|
||||
options.currentBackground.setData(AvailableBaseLayers.osmCarto)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
options.currentBackground.addCallbackAndRunD(background => {
|
||||
if (background.category === options.preferredType) {
|
||||
previousLayer.setData(background)
|
||||
}
|
||||
})
|
||||
|
||||
this.activate = () => {
|
||||
checkPreviousLayer()
|
||||
if (available.data.category !== options.preferredType) {
|
||||
// This object can't help either - pass the torch!
|
||||
if (options.notAvailable !== undefined) {
|
||||
options.notAvailable()
|
||||
} else {
|
||||
// Fallback to OSM carto
|
||||
options.currentBackground.setData(AvailableBaseLayers.osmCarto)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
previousLayer.setData(previousLayer.data ?? available.data)
|
||||
options.currentBackground.setData(previousLayer.data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static getIconFor(type: string) {
|
||||
switch (type) {
|
||||
case "map":
|
||||
return Svg.generic_map_svg()
|
||||
case "photo":
|
||||
return Svg.satellite_svg()
|
||||
case "osmbasedmap":
|
||||
return Svg.osm_logo_svg()
|
||||
default:
|
||||
return Svg.generic_map_svg()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class BackgroundMapSwitch extends Combine {
|
||||
|
||||
constructor(
|
||||
state: {
|
||||
locationControl: UIEventSource<Loc>,
|
||||
backgroundLayer: UIEventSource<BaseLayer>
|
||||
},
|
||||
currentBackground: UIEventSource<BaseLayer>,
|
||||
preferredCategory?: string
|
||||
) {
|
||||
const allowedCategories = ["osmbasedmap", "photo", "map"]
|
||||
|
||||
const previousLayer = state.backgroundLayer.data
|
||||
const buttons = []
|
||||
let activatePrevious: () => void = undefined
|
||||
for (const category of allowedCategories) {
|
||||
let preferredLayer = undefined
|
||||
if (previousLayer.category === category) {
|
||||
preferredLayer = previousLayer
|
||||
}
|
||||
|
||||
const button = new SingleLayerSelectionButton(
|
||||
state.locationControl,
|
||||
{
|
||||
preferredType: category,
|
||||
preferredLayer: preferredLayer,
|
||||
currentBackground: currentBackground,
|
||||
notAvailable: activatePrevious
|
||||
})
|
||||
activatePrevious = button.activate
|
||||
if (category === preferredCategory) {
|
||||
button.activate()
|
||||
}
|
||||
buttons.push(button)
|
||||
}
|
||||
|
||||
// Selects the initial map
|
||||
|
||||
super(buttons)
|
||||
this.SetClass("flex")
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
import Combine from "../Base/Combine";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Attribution from "./Attribution";
|
||||
import State from "../../State";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import * as licenses from "../../assets/generated/license_info.json"
|
||||
|
@ -21,6 +19,8 @@ import Loc from "../../Models/Loc";
|
|||
import Toggle from "../Input/Toggle";
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
import Constants from "../../Models/Constants";
|
||||
import PrivacyPolicy from "./PrivacyPolicy";
|
||||
import ContributorCount from "../../Logic/ContributorCount";
|
||||
|
||||
/**
|
||||
* The attribution panel shown on mobile
|
||||
|
@ -35,7 +35,7 @@ export default class CopyrightPanel extends Combine {
|
|||
currentBounds: UIEventSource<BBox>,
|
||||
locationControl: UIEventSource<Loc>,
|
||||
osmConnection: OsmConnection
|
||||
}, contributions: UIEventSource<Map<string, number>>) {
|
||||
}) {
|
||||
|
||||
const t = Translations.t.general.attribution
|
||||
const layoutToUse = state.layoutToUse
|
||||
|
@ -102,6 +102,8 @@ export default class CopyrightPanel extends Combine {
|
|||
maintainer = Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.maintainer})
|
||||
}
|
||||
|
||||
const contributions = new ContributorCount(state).Contributors
|
||||
|
||||
super([
|
||||
Translations.t.general.attribution.attributionContent,
|
||||
new FixedUiElement("MapComplete "+Constants.vNumber).SetClass("font-bold"),
|
||||
|
|
|
@ -17,6 +17,9 @@ import UserRelatedState from "../../Logic/State/UserRelatedState";
|
|||
import Loc from "../../Models/Loc";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
import CopyrightPanel from "./CopyrightPanel";
|
||||
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline";
|
||||
import PrivacyPolicy from "./PrivacyPolicy";
|
||||
|
||||
export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
||||
|
||||
|
@ -29,6 +32,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
featureSwitchShareScreen: UIEventSource<boolean>,
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>,
|
||||
locationControl: UIEventSource<Loc>,
|
||||
featurePipeline: FeaturePipeline,
|
||||
backgroundLayer: UIEventSource<BaseLayer>,
|
||||
filteredLayers: UIEventSource<FilteredLayer[]>
|
||||
} & UserRelatedState) {
|
||||
|
@ -46,6 +50,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
osmConnection: OsmConnection,
|
||||
featureSwitchShareScreen: UIEventSource<boolean>,
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>,
|
||||
featurePipeline: FeaturePipeline,
|
||||
locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]>
|
||||
} & UserRelatedState,
|
||||
isShown: UIEventSource<boolean>):
|
||||
|
@ -55,16 +60,8 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
|
||||
const tabs: { header: string | BaseUIElement, content: BaseUIElement }[] = [
|
||||
{header: `<img src='${state.layoutToUse.icon}'>`, content: welcome},
|
||||
{
|
||||
header: Svg.osm_logo_img,
|
||||
content: Translations.t.general.openStreetMapIntro.SetClass("link-underline")
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
if (state.featureSwitchShareScreen.data) {
|
||||
tabs.push({header: Svg.share_img, content: new ShareScreen(state)});
|
||||
}
|
||||
|
||||
if (state.featureSwitchMoreQuests.data) {
|
||||
tabs.push({
|
||||
|
@ -77,6 +74,31 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
if (state.featureSwitchShareScreen.data) {
|
||||
tabs.push({header: Svg.share_img, content: new ShareScreen(state)});
|
||||
}
|
||||
|
||||
const copyright = {
|
||||
header: Svg.copyright_svg(),
|
||||
content:
|
||||
new Combine(
|
||||
[
|
||||
Translations.t.general.openStreetMapIntro.SetClass("link-underline"),
|
||||
Translations.t.general.attribution.attributionTitle,
|
||||
new CopyrightPanel(state)
|
||||
|
||||
]
|
||||
)
|
||||
}
|
||||
tabs.push(copyright)
|
||||
|
||||
const privacy = {
|
||||
header: Svg.eye_svg(),
|
||||
content: new PrivacyPolicy()
|
||||
}
|
||||
tabs.push(privacy)
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
|
@ -85,6 +107,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
osmConnection: OsmConnection,
|
||||
featureSwitchShareScreen: UIEventSource<boolean>,
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>,
|
||||
featurePipeline: FeaturePipeline,
|
||||
locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]>
|
||||
} & UserRelatedState, currentTab: UIEventSource<number>, isShown: UIEventSource<boolean>) {
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import Combine from "../Base/Combine";
|
||||
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
|
||||
import Translations from "../i18n/Translations";
|
||||
import CopyrightPanel from "./CopyrightPanel";
|
||||
import ContributorCount from "../../Logic/ContributorCount";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import MapControlButton from "../MapControlButton";
|
||||
import Svg from "../../Svg";
|
||||
|
@ -16,6 +14,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
|||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
import BackgroundMapSwitch from "./BackgroundMapSwitch";
|
||||
|
||||
export default class LeftControls extends Combine {
|
||||
|
||||
|
@ -38,23 +37,6 @@ export default class LeftControls extends Combine {
|
|||
copyrightViewIsOpened: UIEventSource<boolean>
|
||||
}) {
|
||||
|
||||
const toggledCopyright = new ScrollableFullScreen(
|
||||
() => Translations.t.general.attribution.attributionTitle.Clone(),
|
||||
() =>
|
||||
new CopyrightPanel(
|
||||
state,
|
||||
new ContributorCount(state).Contributors
|
||||
),
|
||||
"copyright",
|
||||
guiState.copyrightViewIsOpened
|
||||
);
|
||||
|
||||
const copyrightButton = new Toggle(
|
||||
toggledCopyright,
|
||||
new MapControlButton(Svg.copyright_svg())
|
||||
.onClick(() => toggledCopyright.isShown.setData(true)),
|
||||
toggledCopyright.isShown
|
||||
).SetClass("p-0.5");
|
||||
|
||||
const toggledDownload = new Toggle(
|
||||
new AllDownloads(
|
||||
|
@ -93,10 +75,10 @@ export default class LeftControls extends Combine {
|
|||
state.featureSwitchFilter
|
||||
);
|
||||
|
||||
|
||||
super([filterButton,
|
||||
downloadButtonn,
|
||||
copyrightButton])
|
||||
new BackgroundMapSwitch(state, state.backgroundLayer)
|
||||
])
|
||||
|
||||
this.SetClass("flex flex-col")
|
||||
|
||||
|
|
26
UI/BigComponents/PrivacyPolicy.ts
Normal file
26
UI/BigComponents/PrivacyPolicy.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import Combine from "../Base/Combine";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Title from "../Base/Title";
|
||||
|
||||
export default class PrivacyPolicy extends Combine {
|
||||
constructor() {
|
||||
const t = Translations.t.privacy
|
||||
super([
|
||||
new Title(t.title, 2),
|
||||
t.intro,
|
||||
|
||||
new Title(t.trackingTitle),
|
||||
t.tracking,
|
||||
new Title(t.geodataTitle),
|
||||
t.geodata,
|
||||
new Title(t.editingTitle),
|
||||
t.editing,
|
||||
new Title(t.miscCookiesTitle),
|
||||
t.miscCookies,
|
||||
new Title(t.whileYoureHere),
|
||||
t.surveillance,
|
||||
|
||||
]);
|
||||
this.SetClass("link-underline")
|
||||
}
|
||||
}
|
|
@ -136,7 +136,7 @@ export default class LengthInput extends InputElement<string> {
|
|||
if (leaflet) {
|
||||
const first = leaflet.layerPointToLatLng(firstClickXY)
|
||||
const last = leaflet.layerPointToLatLng([dx, dy])
|
||||
const geoDist = Math.floor(GeoOperations.distanceBetween([first.lng, first.lat], [last.lng, last.lat]) * 10000) / 10
|
||||
const geoDist = Math.floor(GeoOperations.distanceBetween([first.lng, first.lat], [last.lng, last.lat]) * 10) / 10
|
||||
self.value.setData("" + geoDist)
|
||||
}
|
||||
|
||||
|
|
|
@ -150,7 +150,8 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO
|
|||
background: this.mapBackground,
|
||||
attribution: this.mapBackground !== State.state?.backgroundLayer,
|
||||
lastClickLocation: this.clickLocation,
|
||||
bounds: this._bounds
|
||||
bounds: this._bounds,
|
||||
addLayerControl: true
|
||||
}
|
||||
)
|
||||
this.leafletMap = this.map.leafletMap
|
||||
|
|
|
@ -18,6 +18,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
|||
import MoveConfig from "../../Models/ThemeConfig/MoveConfig";
|
||||
import {ElementStorage} from "../../Logic/ElementStorage";
|
||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
|
||||
interface MoveReason {
|
||||
text: Translation | string,
|
||||
|
@ -133,10 +134,12 @@ export default class MoveWizard extends Toggle {
|
|||
background = reason.background
|
||||
}
|
||||
|
||||
const preferredBackground = AvailableBaseLayers.SelectBestLayerAccordingTo(loc, new UIEventSource(background)).data
|
||||
const locationInput = new LocationInput({
|
||||
minZoom: reason.minZoom,
|
||||
centerLocation: loc,
|
||||
mapBackground: AvailableBaseLayers.SelectBestLayerAccordingTo(loc, new UIEventSource(background))
|
||||
mapBackground: new UIEventSource<BaseLayer>(preferredBackground) // We detach the layer
|
||||
|
||||
})
|
||||
|
||||
if (reason.lockBounds) {
|
||||
|
|
|
@ -93,7 +93,7 @@ export default class SplitRoadWizard extends Toggle {
|
|||
function onMapClick(coordinates) {
|
||||
// First, we check if there is another, already existing point nearby
|
||||
const points = splitPoints.data.map((f, i) => [f.feature, i])
|
||||
.filter(p => GeoOperations.distanceBetween(p[0].geometry.coordinates, coordinates) * 1000 < 5)
|
||||
.filter(p => GeoOperations.distanceBetween(p[0].geometry.coordinates, coordinates) < 5)
|
||||
.map(p => p[1])
|
||||
.sort((a, b) => a - b)
|
||||
.reverse()
|
||||
|
|
|
@ -39,10 +39,11 @@ import {And} from "../Logic/Tags/And";
|
|||
import Toggle from "./Input/Toggle";
|
||||
import {DefaultGuiState} from "./DefaultGuiState";
|
||||
import {GeoOperations} from "../Logic/GeoOperations";
|
||||
import Hash from "../Logic/Web/Hash";
|
||||
|
||||
export interface SpecialVisualization {
|
||||
funcName: string,
|
||||
constr: ((state: State, tagSource: UIEventSource<any>, argument: string[], guistate: DefaultGuiState, ) => BaseUIElement),
|
||||
constr: ((state: State, tagSource: UIEventSource<any>, argument: string[], guistate: DefaultGuiState,) => BaseUIElement),
|
||||
docs: string,
|
||||
example?: string,
|
||||
args: { name: string, defaultValue?: string, doc: string }[]
|
||||
|
@ -174,11 +175,11 @@ export default class SpecialVisualizations {
|
|||
idList = JSON.parse(value)
|
||||
}
|
||||
|
||||
|
||||
for (const id of idList) {
|
||||
const feature = featureStore.get(id)
|
||||
features.push({
|
||||
freshness: new Date(),
|
||||
feature: featureStore.get(id)
|
||||
feature
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -606,8 +607,8 @@ export default class SpecialVisualizations {
|
|||
args: [],
|
||||
constr: (state, tagSource, args) => {
|
||||
const t = Translations.t.general.download;
|
||||
|
||||
return new SubtleButton(Svg.download_ui(),
|
||||
|
||||
return new SubtleButton(Svg.download_ui(),
|
||||
new Combine([t.downloadGpx.SetClass("font-bold text-lg"),
|
||||
t.downloadGpxHelper.SetClass("subtle")]).SetClass("flex flex-col")
|
||||
).onClick(() => {
|
||||
|
@ -617,11 +618,24 @@ export default class SpecialVisualizations {
|
|||
const matchingLayer = state?.layoutToUse?.getMatchingLayer(tags)
|
||||
const gpx = GeoOperations.AsGpx(feature, matchingLayer)
|
||||
const title = matchingLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track"
|
||||
Utils.offerContentsAsDownloadableFile(gpx, title+"_mapcomplete_export.gpx", {
|
||||
Utils.offerContentsAsDownloadableFile(gpx, title + "_mapcomplete_export.gpx", {
|
||||
mimetype: "{gpx=application/gpx+xml}"
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
funcName: "clear_location_history",
|
||||
docs: "A button to remove the travelled track information from the device",
|
||||
args: [],
|
||||
constr: state => {
|
||||
return new SubtleButton(
|
||||
Svg.delete_icon_svg().SetStyle("height: 1.5rem"), Translations.t.general.removeLocationHistory
|
||||
).onClick(() => {
|
||||
state.historicalUserLocations.features.setData([])
|
||||
Hash.hash.setData(undefined)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue