Add buttons to quickly swap background layers (also in the locationInput), move copyright into home panel, split privacy policy to seperate welcome message tab
This commit is contained in:
parent
1d0fbe701c
commit
37c0129a6d
22 changed files with 477 additions and 183 deletions
|
@ -64,6 +64,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
|
|||
console.warn("Editor layer index: name not defined on ", props)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
const leafletLayer: () => TileLayer = () => AvailableBaseLayersImplementation.CreateBackgroundLayer(
|
||||
props.id,
|
||||
|
@ -83,7 +84,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
|
|||
min_zoom: props.min_zoom ?? 1,
|
||||
name: props.name,
|
||||
layer: leafletLayer,
|
||||
feature: layer,
|
||||
feature: layer.geometry !== null ? layer : null,
|
||||
isBest: props.best ?? false,
|
||||
category: props.category
|
||||
});
|
||||
|
@ -96,14 +97,14 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
|
|||
X; // Import X to make sure the namespace is not optimized away
|
||||
function l(id: string, name: string): BaseLayer {
|
||||
try {
|
||||
const layer: any = () => L.tileLayer.provider(id, undefined);
|
||||
const layer: any = L.tileLayer.provider(id, undefined);
|
||||
return {
|
||||
feature: null,
|
||||
id: id,
|
||||
name: name,
|
||||
layer: layer,
|
||||
min_zoom: layer.minzoom,
|
||||
max_zoom: layer.maxzoom,
|
||||
layer: () => L.tileLayer.provider(id, undefined),
|
||||
min_zoom: 1,
|
||||
max_zoom: layer.options.maxZoom,
|
||||
category: "osmbasedmap",
|
||||
isBest: false
|
||||
}
|
||||
|
@ -114,7 +115,6 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
|
|||
}
|
||||
|
||||
const layers = [
|
||||
l("CyclOSM", "CyclOSM - A bicycle oriented map"),
|
||||
l("Stamen.TonerLite", "Toner Lite (by Stamen)"),
|
||||
l("Stamen.TonerBackground", "Toner Background - no labels (by Stamen)"),
|
||||
l("Stamen.Watercolor", "Watercolor (by Stamen)"),
|
||||
|
@ -193,37 +193,20 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
|
|||
subdomains: domains
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> {
|
||||
const source = location.map(
|
||||
return UIEventSource.ListStabilized(location.map(
|
||||
(currentLocation) => {
|
||||
|
||||
if (currentLocation === undefined) {
|
||||
return this.layerOverview;
|
||||
}
|
||||
|
||||
const currentLayers = source?.data; // A bit unorthodox - I know
|
||||
const newLayers = this.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat);
|
||||
|
||||
if (currentLayers === undefined) {
|
||||
return newLayers;
|
||||
}
|
||||
if (newLayers.length !== currentLayers.length) {
|
||||
return newLayers;
|
||||
}
|
||||
for (let i = 0; i < newLayers.length; i++) {
|
||||
if (newLayers[i].name !== currentLayers[i].name) {
|
||||
return newLayers;
|
||||
}
|
||||
}
|
||||
|
||||
return currentLayers;
|
||||
});
|
||||
return source;
|
||||
return this.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat);
|
||||
}));
|
||||
}
|
||||
|
||||
public SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> {
|
||||
return this.AvailableLayersAt(location).map(available => {
|
||||
return this.AvailableLayersAt(location)
|
||||
.map(available => {
|
||||
// First float all 'best layers' to the top
|
||||
available.sort((a, b) => {
|
||||
if (a.isBest && b.isBest) {
|
||||
|
@ -267,6 +250,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
|
|||
}, [preferedCategory])
|
||||
}
|
||||
|
||||
|
||||
private CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] {
|
||||
const availableLayers = [this.osmCarto]
|
||||
const globalLayers = [];
|
||||
|
|
|
@ -11,27 +11,28 @@ import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
|||
import {BBox} from "../../BBox";
|
||||
import SimpleFeatureSource from "../Sources/SimpleFeatureSource";
|
||||
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||
import Loc from "../../../Models/Loc";
|
||||
|
||||
export default class SaveTileToLocalStorageActor {
|
||||
private readonly visitedTiles: UIEventSource<Map<number, Date>>
|
||||
private readonly _layer: LayerConfig;
|
||||
private readonly _flayer : FilteredLayer
|
||||
private readonly _flayer: FilteredLayer
|
||||
private readonly initializeTime = new Date()
|
||||
|
||||
constructor(layer: FilteredLayer) {
|
||||
this._flayer = layer
|
||||
this._layer = layer.layerDef
|
||||
this.visitedTiles = IdbLocalStorage.Get("visited_tiles_" + this._layer.id,
|
||||
{defaultValue: new Map<number, Date>(), })
|
||||
this.visitedTiles = IdbLocalStorage.Get("visited_tiles_" + this._layer.id,
|
||||
{defaultValue: new Map<number, Date>(),})
|
||||
this.visitedTiles.stabilized(100).addCallbackAndRunD(tiles => {
|
||||
for (const key of Array.from(tiles.keys())) {
|
||||
const tileFreshness = tiles.get(key)
|
||||
|
||||
const toOld = (this.initializeTime.getTime() - tileFreshness.getTime()) > 1000 * this._layer.maxAgeOfCache
|
||||
if(toOld){
|
||||
const toOld = (this.initializeTime.getTime() - tileFreshness.getTime()) > 1000 * this._layer.maxAgeOfCache
|
||||
if (toOld) {
|
||||
// Purge this tile
|
||||
this.SetIdb(key, undefined)
|
||||
console.debug("Purging tile",this._layer.id,key)
|
||||
console.debug("Purging tile", this._layer.id, key)
|
||||
tiles.delete(key)
|
||||
}
|
||||
}
|
||||
|
@ -39,66 +40,70 @@ export default class SaveTileToLocalStorageActor {
|
|||
return true;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
public LoadTilesFromDisk(currentBounds: UIEventSource<BBox>,
|
||||
registerFreshness: (tileId: number, freshness: Date) => void,
|
||||
registerTile: ((src: FeatureSource & Tiled ) => void)){
|
||||
public LoadTilesFromDisk(currentBounds: UIEventSource<BBox>, location: UIEventSource<Loc>,
|
||||
registerFreshness: (tileId: number, freshness: Date) => void,
|
||||
registerTile: ((src: FeatureSource & Tiled) => void)) {
|
||||
const self = this;
|
||||
const loadedTiles = new Set<number>()
|
||||
this.visitedTiles.addCallbackD(tiles => {
|
||||
if(tiles.size === 0){
|
||||
if (tiles.size === 0) {
|
||||
// We don't do anything yet as probably not yet loaded from disk
|
||||
// We'll unregister later on
|
||||
return;
|
||||
}
|
||||
for (const key of Array.from(tiles.keys())) {
|
||||
const tileFreshness = tiles.get(key)
|
||||
if(tileFreshness > self.initializeTime){
|
||||
// This tile is loaded by another source
|
||||
continue
|
||||
currentBounds.addCallbackAndRunD(bbox => {
|
||||
|
||||
if(self._layer.minzoomVisible > location.data.zoom){
|
||||
// Not enough zoom
|
||||
return;
|
||||
}
|
||||
registerFreshness(key, tileFreshness)
|
||||
|
||||
const tileBbox = BBox.fromTileIndex(key)
|
||||
currentBounds.addCallbackAndRunD(bbox => {
|
||||
if(bbox.overlapsWith(tileBbox)){
|
||||
// The current tile should be loaded from disk
|
||||
this.GetIdb(key).then((features:{feature: any, freshness: Date}[] ) => {
|
||||
console.log("Loaded tile "+self._layer.id+"_"+key+" from disk")
|
||||
const src = new SimpleFeatureSource(self._flayer, key, new UIEventSource<{feature: any; freshness: Date}[]>(features))
|
||||
registerTile(src)
|
||||
})
|
||||
return true; // only load once: unregister
|
||||
|
||||
// Iterate over all available keys in the local storage, check which are needed and fresh enough
|
||||
for (const key of Array.from(tiles.keys())) {
|
||||
const tileFreshness = tiles.get(key)
|
||||
if (tileFreshness > self.initializeTime) {
|
||||
// This tile is loaded by another source
|
||||
continue
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
registerFreshness(key, tileFreshness)
|
||||
const tileBbox = BBox.fromTileIndex(key)
|
||||
if (!bbox.overlapsWith(tileBbox)) {
|
||||
continue;
|
||||
}
|
||||
if (loadedTiles.has(key)) {
|
||||
// Already loaded earlier
|
||||
continue
|
||||
}
|
||||
loadedTiles.add(key)
|
||||
this.GetIdb(key).then((features: { feature: any, freshness: Date }[]) => {
|
||||
console.debug("Loaded tile " + self._layer.id + "_" + key + " from disk")
|
||||
const src = new SimpleFeatureSource(self._flayer, key, new UIEventSource<{ feature: any; freshness: Date }[]>(features))
|
||||
registerTile(src)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return true; // Remove the callback
|
||||
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
private SetIdb(tileIndex, data){
|
||||
IdbLocalStorage.SetDirectly(this._layer.id+"_"+tileIndex, data)
|
||||
}
|
||||
|
||||
private GetIdb(tileIndex){
|
||||
return IdbLocalStorage.GetDirectly(this._layer.id+"_"+tileIndex)
|
||||
}
|
||||
|
||||
public addTile(tile: FeatureSource & Tiled){
|
||||
public addTile(tile: FeatureSource & Tiled) {
|
||||
const self = this
|
||||
tile.features.addCallbackAndRunD(features => {
|
||||
const now = new Date()
|
||||
|
||||
if (features.length > 0) {
|
||||
self.SetIdb(tile.tileIndex, features)
|
||||
self.SetIdb(tile.tileIndex, features)
|
||||
}
|
||||
// We _still_ write the time to know that this tile is empty!
|
||||
this.MarkVisited(tile.tileIndex, now)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
public poison(lon: number, lat: number) {
|
||||
for (let z = 0; z < 25; z++) {
|
||||
const {x, y} = Tiles.embedded_tile(lat, lon, z)
|
||||
|
@ -110,6 +115,14 @@ export default class SaveTileToLocalStorageActor {
|
|||
|
||||
public MarkVisited(tileId: number, freshness: Date) {
|
||||
this.visitedTiles.data.set(tileId, freshness)
|
||||
this.visitedTiles.ping()
|
||||
this.visitedTiles.ping()
|
||||
}
|
||||
|
||||
private SetIdb(tileIndex, data) {
|
||||
IdbLocalStorage.SetDirectly(this._layer.id + "_" + tileIndex, data)
|
||||
}
|
||||
|
||||
private GetIdb(tileIndex) {
|
||||
return IdbLocalStorage.GetDirectly(this._layer.id + "_" + tileIndex)
|
||||
}
|
||||
}
|
|
@ -157,7 +157,7 @@ export default class FeaturePipeline {
|
|||
// We load the cached values and register them
|
||||
// Getting data from upstream happens a bit lower
|
||||
localTileSaver.LoadTilesFromDisk(
|
||||
state.currentBounds,
|
||||
state.currentBounds, state.locationControl,
|
||||
(tileIndex, freshness) => self.freshnesses.get(id).addTileLoad(tileIndex, freshness),
|
||||
(tile) => {
|
||||
new RegisteringAllFromFeatureSourceActor(tile)
|
||||
|
@ -221,7 +221,13 @@ export default class FeaturePipeline {
|
|||
state.filteredLayers.data.forEach(flayer => {
|
||||
const layer = flayer.layerDef
|
||||
if (layer.maxAgeOfCache > 0) {
|
||||
self.localStorageSavers.get(layer.id).MarkVisited(tileId, new Date())
|
||||
const saver = self.localStorageSavers.get(layer.id)
|
||||
if(saver === undefined){
|
||||
console.warn("No local storage saver found for ", layer.id)
|
||||
}else{
|
||||
|
||||
saver.MarkVisited(tileId, new Date())
|
||||
}
|
||||
}
|
||||
self.freshnesses.get(layer.id).addTileLoad(tileId, new Date())
|
||||
})
|
||||
|
|
|
@ -3,7 +3,6 @@ import {UIEventSource} from "../UIEventSource";
|
|||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import AvailableBaseLayers from "../Actors/AvailableBaseLayers";
|
||||
import BackgroundLayerResetter from "../Actors/BackgroundLayerResetter";
|
||||
import Attribution from "../../UI/BigComponents/Attribution";
|
||||
import Minimap, {MinimapObj} from "../../UI/Base/Minimap";
|
||||
import {Tiles} from "../../Models/TileRange";
|
||||
|
@ -84,35 +83,17 @@ export default class MapState extends UserRelatedState {
|
|||
|
||||
this.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(this.locationControl);
|
||||
|
||||
this.backgroundLayer = this.backgroundLayerId.map(
|
||||
(selectedId: string) => {
|
||||
if (selectedId === undefined) {
|
||||
return AvailableBaseLayers.osmCarto;
|
||||
}
|
||||
|
||||
const available = this.availableBackgroundLayers.data;
|
||||
for (const layer of available) {
|
||||
if (layer.id === selectedId) {
|
||||
return layer;
|
||||
}
|
||||
}
|
||||
return AvailableBaseLayers.osmCarto;
|
||||
},
|
||||
[this.availableBackgroundLayers],
|
||||
(layer) => layer.id
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
* Selects a different background layer if the background layer has no coverage at the current location
|
||||
*/
|
||||
new BackgroundLayerResetter(
|
||||
this.backgroundLayer,
|
||||
this.locationControl,
|
||||
this.availableBackgroundLayers,
|
||||
this.layoutToUse.defaultBackgroundId
|
||||
);
|
||||
|
||||
let defaultLayer = AvailableBaseLayers.osmCarto
|
||||
const available = this.availableBackgroundLayers.data;
|
||||
for (const layer of available) {
|
||||
if (this.backgroundLayerId.data === layer.id) {
|
||||
defaultLayer = layer;
|
||||
}
|
||||
}
|
||||
const self = this
|
||||
this.backgroundLayer = new UIEventSource<BaseLayer>(defaultLayer)
|
||||
this.backgroundLayer.addCallbackAndRunD(layer => self.backgroundLayerId.setData(layer.id))
|
||||
|
||||
const attr = new Attribution(
|
||||
this.locationControl,
|
||||
this.osmConnection.userDetails,
|
||||
|
@ -334,10 +315,7 @@ export default class MapState extends UserRelatedState {
|
|||
const filtersPerName = new Map<string, FilterConfig>()
|
||||
layer.filters.forEach(f => filtersPerName.set(f.id, f))
|
||||
const qp = QueryParameters.GetQueryParameter("filter-" + layer.id, "", "Filtering state for a layer")
|
||||
flayer.appliedFilters.map(filters => {
|
||||
filters = filters ?? []
|
||||
return filters.map(f => f.filter.id + "." + f.selected).join(",")
|
||||
}, [], textual => {
|
||||
flayer.appliedFilters.map(filters => (filters ?? []).map(f => f.filter.id + "." + f.selected).join(","), [], textual => {
|
||||
if (textual.length === 0) {
|
||||
return empty
|
||||
}
|
||||
|
|
|
@ -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,24 +1,140 @@
|
|||
import Combine from "../Base/Combine";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
||||
import State from "../../State";
|
||||
import Loc from "../../Models/Loc";
|
||||
import Svg from "../../Svg";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
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 {
|
||||
constructor(state: {
|
||||
locationControl: UIEventSource<Loc>
|
||||
}, prefered: string) {
|
||||
const layer = AvailableBaseLayers.SelectBestLayerAccordingTo(state.locationControl, new UIEventSource(prefered))
|
||||
const layerIsCorrectType = layer.map(bl => bl?.category === prefered)
|
||||
|
||||
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(
|
||||
SingleLayerSelectionButton.getIconFor(prefered).SetClass("rounded-full p-3 h-10 w-10"),
|
||||
toggle,
|
||||
undefined,
|
||||
layerIsCorrectType
|
||||
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) {
|
||||
|
@ -27,27 +143,54 @@ class SingleLayerSelectionButton extends Toggle {
|
|||
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 VariableUiElement {
|
||||
export default class BackgroundMapSwitch extends Combine {
|
||||
|
||||
constructor(
|
||||
state: {
|
||||
locationControl: UIEventSource<Loc>
|
||||
locationControl: UIEventSource<Loc>,
|
||||
backgroundLayer: UIEventSource<BaseLayer>
|
||||
},
|
||||
options?: {
|
||||
allowedLayers?: UIEventSource<string[]>
|
||||
}
|
||||
currentBackground: UIEventSource<BaseLayer>,
|
||||
preferredCategory?: string
|
||||
) {
|
||||
options = options ?? {}
|
||||
options.allowedLayers = options.allowedLayers ?? new UIEventSource<string[]>(["photo", "map"])
|
||||
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
|
||||
}
|
||||
|
||||
super(options.allowedLayers.map(layers => new Combine(layers.map(prefered => new SingleLayerSelectionButton(state, prefered)))));
|
||||
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"
|
||||
|
@ -22,6 +20,7 @@ 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
|
||||
|
@ -36,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
|
||||
|
@ -103,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"),
|
||||
|
@ -144,8 +145,7 @@ export default class CopyrightPanel extends Combine {
|
|||
})),
|
||||
CopyrightPanel.CodeContributors(),
|
||||
new Title(t.iconAttribution.title, 3),
|
||||
...iconAttributions,
|
||||
new PrivacyPolicy()
|
||||
...iconAttributions
|
||||
].map(e => e?.SetClass("mt-4")));
|
||||
this.SetClass("flex flex-col link-underline overflow-hidden")
|
||||
this.SetStyle("max-width: calc(100vw - 3em); width: 40rem; margin-left: 0.75rem; margin-right: 0.5rem")
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
"minzoom": 0,
|
||||
"source": {
|
||||
"osmTags": "user:location=yes",
|
||||
"maxCacheAge": 604800
|
||||
"#": "Cache is disabled here as these points are kept seperately",
|
||||
"maxCacheAge": 0
|
||||
},
|
||||
"mapRendering": null
|
||||
}
|
|
@ -1,4 +1,53 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.08 8.86C8.13 8.53 8.24 8.24 8.38 7.99C8.52 7.74 8.72 7.53 8.97 7.37C9.21 7.22 9.51 7.15 9.88 7.14C10.11 7.15 10.32 7.19 10.51 7.27C10.71 7.36 10.89 7.48 11.03 7.63C11.17 7.78 11.28 7.96 11.37 8.16C11.46 8.36 11.5 8.58 11.51 8.8H13.3C13.28 8.33 13.19 7.9 13.02 7.51C12.85 7.12 12.62 6.78 12.32 6.5C12.02 6.22 11.66 6 11.24 5.84C10.82 5.68 10.36 5.61 9.85 5.61C9.2 5.61 8.63 5.72 8.15 5.95C7.67 6.18 7.27 6.48 6.95 6.87C6.63 7.26 6.39 7.71 6.24 8.23C6.09 8.75 6 9.29 6 9.87V10.14C6 10.72 6.08 11.26 6.23 11.78C6.38 12.3 6.62 12.75 6.94 13.13C7.26 13.51 7.66 13.82 8.14 14.04C8.62 14.26 9.19 14.38 9.84 14.38C10.31 14.38 10.75 14.3 11.16 14.15C11.57 14 11.93 13.79 12.24 13.52C12.55 13.25 12.8 12.94 12.98 12.58C13.16 12.22 13.27 11.84 13.28 11.43H11.49C11.48 11.64 11.43 11.83 11.34 12.01C11.25 12.19 11.13 12.34 10.98 12.47C10.83 12.6 10.66 12.7 10.46 12.77C10.27 12.84 10.07 12.86 9.86 12.87C9.5 12.86 9.2 12.79 8.97 12.64C8.72 12.48 8.52 12.27 8.38 12.02C8.24 11.77 8.13 11.47 8.08 11.14C8.03 10.81 8 10.47 8 10.14V9.87C8 9.52 8.03 9.19 8.08 8.86V8.86ZM10 0C4.48 0 0 4.48 0 10C0 15.52 4.48 20 10 20C15.52 20 20 15.52 20 10C20 4.48 15.52 0 10 0ZM10 18C5.59 18 2 14.41 2 10C2 5.59 5.59 2 10 2C14.41 2 18 5.59 18 10C18 14.41 14.41 18 10 18Z"
|
||||
fill="white"/>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="copyright.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
|
||||
style="fill:none">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="640"
|
||||
inkscape:window-height="480"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="16.68772"
|
||||
inkscape:cx="7.5715453"
|
||||
inkscape:cy="7.3459724"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
d="M 8.4941968,9.1059293 C 8.5334104,8.8471194 8.6196804,8.6196804 8.7294785,8.4236123 8.8392767,8.2275441 8.9961312,8.0628469 9.1921993,7.9373633 9.3804247,7.8197224 9.6157065,7.7648234 9.9058877,7.7569806 c 0.1803823,0.00784 0.3450793,0.039214 0.4940913,0.1019555 0.156854,0.070584 0.298024,0.1646972 0.407822,0.2823381 0.109798,0.1176408 0.196068,0.2588099 0.266652,0.4156644 0.07058,0.1568545 0.101956,0.3293944 0.109798,0.5019344 h 1.403848 C 12.572414,8.6902649 12.501829,8.3530277 12.368503,8.0471615 12.235177,7.7412952 12.054794,7.4746425 11.819512,7.2550462 11.58423,7.0354499 11.301892,6.86291 10.972498,6.7374264 10.643103,6.6119428 10.282338,6.5570437 9.8823595,6.5570437 c -0.5097775,0 -0.9568128,0.08627 -1.3332636,0.2666526 C 8.1726451,7.004079 7.8589361,7.2393608 7.6079689,7.5452271 7.3570017,7.8510933 7.1687762,8.204016 7.0511354,8.6118377 6.9334945,9.0196594 6.86291,9.4431665 6.86291,9.8980443 v 0.2117537 c 0,0.454878 0.062742,0.878385 0.1803826,1.286207 0.1176409,0.407822 0.3058663,0.760744 0.5568335,1.058768 0.2509672,0.298024 0.5646762,0.541148 0.941127,0.713688 0.3764508,0.17254 0.8234862,0.266653 1.3332637,0.266653 0.3686072,0 0.7136872,-0.06274 1.0352392,-0.180383 0.321552,-0.117641 0.60389,-0.282338 0.847014,-0.494092 0.243125,-0.211753 0.439193,-0.454878 0.580362,-0.737216 0.141169,-0.282338 0.227439,-0.580362 0.235282,-0.901913 h -1.403848 c -0.0078,0.164697 -0.04706,0.313709 -0.117641,0.454878 -0.07058,0.141169 -0.164697,0.25881 -0.282338,0.360765 -0.117641,0.101956 -0.250967,0.180383 -0.407822,0.235282 -0.149011,0.0549 -0.305866,0.07058 -0.4705628,0.07843 C 9.6078637,12.243019 9.372582,12.18812 9.1921993,12.070479 8.9961312,11.944996 8.8392767,11.780299 8.7294785,11.58423 8.6196804,11.388162 8.5334104,11.152881 8.4941968,10.894071 8.4549832,10.635261 8.431455,10.368608 8.431455,10.109798 V 9.8980443 c 0,-0.2744951 0.023528,-0.533305 0.062742,-0.792115 z M 10,2.1572749 c -4.3291842,0 -7.8427251,3.5135409 -7.8427251,7.8427248 0,4.3291843 3.5135409,7.8427253 7.8427251,7.8427253 4.329184,0 7.842725,-3.513541 7.842725,-7.8427253 C 17.842725,5.6708158 14.329184,2.1572749 10,2.1572749 Z M 10,16.27418 c -3.4586418,0 -6.2741801,-2.815538 -6.2741801,-6.2741803 0,-3.4586415 2.8155383,-6.2741798 6.2741801,-6.2741798 3.458642,0 6.27418,2.8155383 6.27418,6.2741798 0,3.4586423 -2.815538,6.2741803 -6.27418,6.2741803 z"
|
||||
id="path2"
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:0.78427249"
|
||||
inkscape:connector-curvature="0" />
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.8 KiB |
59
assets/svg/eye.svg
Normal file
59
assets/svg/eye.svg
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 -256 1850 1850"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.3.1 r9886"
|
||||
width="100%"
|
||||
height="100%"
|
||||
sodipodi:docname="eye_open_font_awesome.svg">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="640"
|
||||
inkscape:window-height="480"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.13169643"
|
||||
inkscape:cx="896"
|
||||
inkscape:cy="896"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="25"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<g
|
||||
transform="matrix(1,0,0,-1,30.372881,1259.8983)"
|
||||
id="g4">
|
||||
<path
|
||||
d="m 1664,576 q -152,236 -381,353 61,-104 61,-225 0,-185 -131.5,-316.5 Q 1081,256 896,256 711,256 579.5,387.5 448,519 448,704 448,825 509,929 280,812 128,576 261,371 461.5,249.5 662,128 896,128 1130,128 1330.5,249.5 1531,371 1664,576 z M 944,960 q 0,20 -14,34 -14,14 -34,14 -125,0 -214.5,-89.5 Q 592,829 592,704 q 0,-20 14,-34 14,-14 34,-14 20,0 34,14 14,14 14,34 0,86 61,147 61,61 147,61 20,0 34,14 14,14 14,34 z m 848,-384 q 0,-34 -20,-69 Q 1632,277 1395.5,138.5 1159,0 896,0 633,0 396.5,139 160,278 20,507 0,542 0,576 q 0,34 20,69 140,229 376.5,368 236.5,139 499.5,139 263,0 499.5,-139 236.5,-139 376.5,-368 20,-35 20,-69 z"
|
||||
id="path6"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:currentColor" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -487,6 +487,16 @@
|
|||
"authors": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"path": "eye.svg",
|
||||
"license": "CC-BY-SA 3.0 Unported",
|
||||
"authors": [
|
||||
"Dave Gandy"
|
||||
],
|
||||
"sources": [
|
||||
"https://en.wikipedia.org/wiki/File:Eye_open_font_awesome.svg"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "filter.svg",
|
||||
"license": "CC0",
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns="http://www.w3.org/2000/svg" width="256" height="256" id="svg3038" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
id="svg3038" version="1.1"
|
||||
inkscape:version="0.48.2 r9819" sodipodi:docname="Public-images-osm_logo.svg"
|
||||
inkscape:export-filename="/home/fred/bla.png" inkscape:export-xdpi="180" inkscape:export-ydpi="180"
|
||||
sodipodi:version="0.32" inkscape:output_extension="org.inkscape.output.svg.inkscape">
|
||||
|
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 168 KiB |
|
@ -760,14 +760,14 @@ video {
|
|||
top: 0px;
|
||||
}
|
||||
|
||||
.left-0 {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.right-0 {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.left-0 {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.isolate {
|
||||
isolation: isolate;
|
||||
}
|
||||
|
@ -1920,6 +1920,10 @@ li::marker {
|
|||
border: 5px solid var(--catch-detail-color);
|
||||
}
|
||||
|
||||
.border-invisible {
|
||||
border: 5px solid #00000000;
|
||||
}
|
||||
|
||||
.border-attention {
|
||||
border-color: var(--catch-detail-color);
|
||||
}
|
||||
|
|
|
@ -208,6 +208,10 @@ li::marker {
|
|||
border: 5px solid var(--catch-detail-color);
|
||||
}
|
||||
|
||||
.border-invisible {
|
||||
border: 5px solid #00000000;
|
||||
}
|
||||
|
||||
.border-attention {
|
||||
border-color: var(--catch-detail-color);
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
"emailOf": "Wat is het email-adres van {category}?",
|
||||
"emailIs": "Het email-adres van {category} is <a href=\"mailto:{email}\" target=\"_blank\">{email}</a>"
|
||||
},
|
||||
"openStreetMapIntro": "<h3>Een open kaart</h3><p>Zou het niet fantastisch zijn als er een open kaart zou zijn die door iedereen aangepast én gebruikt kan worden? Een kaart waar iedereen zijn interesses aan zou kunnen toevoegen? Dan zouden er geen duizend-en-één verschillende kleine kaartjes, websites, ... meer nodig zijn</p><p><b><a href=\"https://OpenStreetMap.org\" target=\"_blank\">OpenStreetMap</a></b> is deze open kaart. Je mag de kaartdata gratis gebruiken (mits <a href=\"https://osm.org/copyright\" target=\"_blank\">bronvermelding en herpublicatie van aanpassingen</a>). Daarenboven mag je de kaart ook gratis aanpassen als je een account maakt. Ook deze website is gebaseerd op OpenStreetMap. Als je hier een vraag beantwoord, gaat het antwoord daar ook naartoe</p><p>Tenslotte zijn er reeds vele gebruikers van OpenStreetMap. Denk maar <a href=\"https://organicmaps.app//\" target=\"_blank\">Organic Maps</a>, <a href=\"https://osmAnd.net\" target=\"_blank\">OsmAnd</a>, verschillende gespecialiseerde routeplanners, de achtergrondkaarten op Facebook, Instagram,...<br/>Zelfs Apple Maps en Bing-Maps gebruiken OpenStreetMap in hun kaarten!</p><p></p><p>Kortom, als je hier een punt toevoegd of een vraag beantwoord, zal dat na een tijdje ook in al dié applicaties te zien zijn.</p>",
|
||||
"openStreetMapIntro": "<h3>Een open kaart</h3><p>Zou het niet fantastisch zijn als er een open kaart zou zijn die door iedereen aangepast én gebruikt kan worden? Een kaart waar iedereen zijn interesses aan zou kunnen toevoegen? Dan zouden er geen duizend-en-één verschillende kleine kaartjes, websites, ... meer nodig zijn</p><p><b><a href=\"https://OpenStreetMap.org\" target=\"_blank\">OpenStreetMap</a></b> is deze open kaart. Je mag de kaartdata gratis gebruiken (mits <a href=\"https://osm.org/copyright\" target=\"_blank\">bronvermelding en herpublicatie van aanpassingen</a>). Daarenboven mag je de kaart ook gratis aanpassen als je een account maakt. Ook deze website is gebaseerd op OpenStreetMap. Als je hier een vraag beantwoord, gaat het antwoord daar ook naartoe</p><p>Tenslotte zijn er reeds vele gebruikers van OpenStreetMap. Denk maar <a href=\"https://organicmaps.app//\" target=\"_blank\">Organic Maps</a>, <a href=\"https://osmAnd.net\" target=\"_blank\">OsmAnd</a>, verschillende gespecialiseerde routeplanners, de achtergrondkaarten op Facebook, Instagram,...<br/>;Zelfs Apple Maps en Bing-Maps gebruiken OpenStreetMap in hun kaarten!</p><p></p><p>Kortom, als je hier een punt toevoegd of een vraag beantwoord, zal dat na een tijdje ook in al dié applicaties te zien zijn.</p>",
|
||||
"attribution": {
|
||||
"attributionTitle": "Met dank aan",
|
||||
"attributionContent": "<p>Alle data is voorzien door <a href='https://osm.org' target='_blank'>OpenStreetMap</a>, gratis en vrij te hergebruiken onder <a href='https://osm.org/copyright' target='_blank'>de Open DataBase Licentie</a>.</p>",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as fs from "fs";
|
||||
|
||||
function genImages() {
|
||||
function genImages(dryrun = false) {
|
||||
|
||||
console.log("Generating images")
|
||||
const dir = fs.readdirSync("./assets/svg")
|
||||
|
@ -17,7 +17,7 @@ function genImages() {
|
|||
throw "Non-svg file detected in the svg files: " + path;
|
||||
}
|
||||
|
||||
const svg = fs.readFileSync("./assets/svg/" + path, "utf-8")
|
||||
let svg = fs.readFileSync("./assets/svg/" + path, "utf-8")
|
||||
.replace(/<\?xml.*?>/, "")
|
||||
.replace(/fill: ?none;/g, "fill: none !important;") // This is such a brittle hack...
|
||||
.replace(/\n/g, " ")
|
||||
|
@ -26,11 +26,18 @@ function genImages() {
|
|||
.replace(/"/g, "\\\"")
|
||||
const name = path.substr(0, path.length - 4)
|
||||
.replace(/[ -]/g, "_");
|
||||
|
||||
if (dryrun) {
|
||||
svg = "xxx"
|
||||
}
|
||||
|
||||
module += ` public static ${name} = "${svg}"\n`
|
||||
module += ` public static ${name}_img = Img.AsImageElement(Svg.${name})\n`
|
||||
module += ` public static ${name}_svg() { return new Img(Svg.${name}, true);}\n`
|
||||
module += ` public static ${name}_ui() { return new FixedUiElement(Svg.${name}_img);}\n\n`
|
||||
allNames.push(`"${path}": Svg.${name}`)
|
||||
if (!dryrun) {
|
||||
allNames.push(`"${path}": Svg.${name}`)
|
||||
}
|
||||
}
|
||||
module += `public static All = {${allNames.join(",")}};`
|
||||
module += "}\n";
|
||||
|
|
20
test.ts
20
test.ts
|
@ -3,12 +3,22 @@ import {UIEventSource} from "./Logic/UIEventSource";
|
|||
import Loc from "./Models/Loc";
|
||||
import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayersImplementation";
|
||||
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
|
||||
AvailableBaseLayers.implement(new AvailableBaseLayersImplementation())
|
||||
import BaseLayer from "./Models/BaseLayer";
|
||||
import {VariableUiElement} from "./UI/Base/VariableUIElement";
|
||||
|
||||
new BackgroundMapSwitch({
|
||||
AvailableBaseLayers.implement(new AvailableBaseLayersImplementation())
|
||||
const state = {
|
||||
currentBackground: new UIEventSource<BaseLayer>(AvailableBaseLayers.osmCarto),
|
||||
locationControl: new UIEventSource<Loc>({
|
||||
zoom: 19,
|
||||
lat: 51.5,
|
||||
lon: 4.1
|
||||
lat: 51.2,
|
||||
lon: 3.2
|
||||
})
|
||||
}).AttachTo("maindiv")
|
||||
}
|
||||
const actualBackground = new UIEventSource(AvailableBaseLayers.osmCarto)
|
||||
new BackgroundMapSwitch(state,
|
||||
{
|
||||
currentBackground: actualBackground
|
||||
}).AttachTo("maindiv")
|
||||
|
||||
new VariableUiElement(actualBackground.map(bg => bg.id)).AttachTo("extradiv")
|
Loading…
Reference in a new issue