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:
pietervdvn 2021-11-21 02:44:35 +01:00
parent 1d0fbe701c
commit 37c0129a6d
22 changed files with 477 additions and 183 deletions

View file

@ -64,6 +64,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
console.warn("Editor layer index: name not defined on ", props) console.warn("Editor layer index: name not defined on ", props)
continue continue
} }
const leafletLayer: () => TileLayer = () => AvailableBaseLayersImplementation.CreateBackgroundLayer( const leafletLayer: () => TileLayer = () => AvailableBaseLayersImplementation.CreateBackgroundLayer(
props.id, props.id,
@ -83,7 +84,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
min_zoom: props.min_zoom ?? 1, min_zoom: props.min_zoom ?? 1,
name: props.name, name: props.name,
layer: leafletLayer, layer: leafletLayer,
feature: layer, feature: layer.geometry !== null ? layer : null,
isBest: props.best ?? false, isBest: props.best ?? false,
category: props.category 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 X; // Import X to make sure the namespace is not optimized away
function l(id: string, name: string): BaseLayer { function l(id: string, name: string): BaseLayer {
try { try {
const layer: any = () => L.tileLayer.provider(id, undefined); const layer: any = L.tileLayer.provider(id, undefined);
return { return {
feature: null, feature: null,
id: id, id: id,
name: name, name: name,
layer: layer, layer: () => L.tileLayer.provider(id, undefined),
min_zoom: layer.minzoom, min_zoom: 1,
max_zoom: layer.maxzoom, max_zoom: layer.options.maxZoom,
category: "osmbasedmap", category: "osmbasedmap",
isBest: false isBest: false
} }
@ -114,7 +115,6 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
} }
const layers = [ const layers = [
l("CyclOSM", "CyclOSM - A bicycle oriented map"),
l("Stamen.TonerLite", "Toner Lite (by Stamen)"), l("Stamen.TonerLite", "Toner Lite (by Stamen)"),
l("Stamen.TonerBackground", "Toner Background - no labels (by Stamen)"), l("Stamen.TonerBackground", "Toner Background - no labels (by Stamen)"),
l("Stamen.Watercolor", "Watercolor (by Stamen)"), l("Stamen.Watercolor", "Watercolor (by Stamen)"),
@ -193,37 +193,20 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
subdomains: domains subdomains: domains
}); });
} }
public AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> { public AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> {
const source = location.map( return UIEventSource.ListStabilized(location.map(
(currentLocation) => { (currentLocation) => {
if (currentLocation === undefined) { if (currentLocation === undefined) {
return this.layerOverview; return this.layerOverview;
} }
return this.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat);
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;
} }
public SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> { 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 // First float all 'best layers' to the top
available.sort((a, b) => { available.sort((a, b) => {
if (a.isBest && b.isBest) { if (a.isBest && b.isBest) {
@ -267,6 +250,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
}, [preferedCategory]) }, [preferedCategory])
} }
private CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] { private CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] {
const availableLayers = [this.osmCarto] const availableLayers = [this.osmCarto]
const globalLayers = []; const globalLayers = [];

View file

@ -11,27 +11,28 @@ import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import {BBox} from "../../BBox"; import {BBox} from "../../BBox";
import SimpleFeatureSource from "../Sources/SimpleFeatureSource"; import SimpleFeatureSource from "../Sources/SimpleFeatureSource";
import FilteredLayer from "../../../Models/FilteredLayer"; import FilteredLayer from "../../../Models/FilteredLayer";
import Loc from "../../../Models/Loc";
export default class SaveTileToLocalStorageActor { export default class SaveTileToLocalStorageActor {
private readonly visitedTiles: UIEventSource<Map<number, Date>> private readonly visitedTiles: UIEventSource<Map<number, Date>>
private readonly _layer: LayerConfig; private readonly _layer: LayerConfig;
private readonly _flayer : FilteredLayer private readonly _flayer: FilteredLayer
private readonly initializeTime = new Date() private readonly initializeTime = new Date()
constructor(layer: FilteredLayer) { constructor(layer: FilteredLayer) {
this._flayer = layer this._flayer = layer
this._layer = layer.layerDef this._layer = layer.layerDef
this.visitedTiles = IdbLocalStorage.Get("visited_tiles_" + this._layer.id, this.visitedTiles = IdbLocalStorage.Get("visited_tiles_" + this._layer.id,
{defaultValue: new Map<number, Date>(), }) {defaultValue: new Map<number, Date>(),})
this.visitedTiles.stabilized(100).addCallbackAndRunD(tiles => { this.visitedTiles.stabilized(100).addCallbackAndRunD(tiles => {
for (const key of Array.from(tiles.keys())) { for (const key of Array.from(tiles.keys())) {
const tileFreshness = tiles.get(key) const tileFreshness = tiles.get(key)
const toOld = (this.initializeTime.getTime() - tileFreshness.getTime()) > 1000 * this._layer.maxAgeOfCache const toOld = (this.initializeTime.getTime() - tileFreshness.getTime()) > 1000 * this._layer.maxAgeOfCache
if(toOld){ if (toOld) {
// Purge this tile // Purge this tile
this.SetIdb(key, undefined) this.SetIdb(key, undefined)
console.debug("Purging tile",this._layer.id,key) console.debug("Purging tile", this._layer.id, key)
tiles.delete(key) tiles.delete(key)
} }
} }
@ -39,66 +40,70 @@ export default class SaveTileToLocalStorageActor {
return true; return true;
}) })
} }
public LoadTilesFromDisk(currentBounds: UIEventSource<BBox>, public LoadTilesFromDisk(currentBounds: UIEventSource<BBox>, location: UIEventSource<Loc>,
registerFreshness: (tileId: number, freshness: Date) => void, registerFreshness: (tileId: number, freshness: Date) => void,
registerTile: ((src: FeatureSource & Tiled ) => void)){ registerTile: ((src: FeatureSource & Tiled) => void)) {
const self = this; const self = this;
const loadedTiles = new Set<number>()
this.visitedTiles.addCallbackD(tiles => { 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 don't do anything yet as probably not yet loaded from disk
// We'll unregister later on // We'll unregister later on
return; return;
} }
for (const key of Array.from(tiles.keys())) { currentBounds.addCallbackAndRunD(bbox => {
const tileFreshness = tiles.get(key)
if(tileFreshness > self.initializeTime){ if(self._layer.minzoomVisible > location.data.zoom){
// This tile is loaded by another source // Not enough zoom
continue return;
} }
registerFreshness(key, tileFreshness)
// Iterate over all available keys in the local storage, check which are needed and fresh enough
const tileBbox = BBox.fromTileIndex(key) for (const key of Array.from(tiles.keys())) {
currentBounds.addCallbackAndRunD(bbox => { const tileFreshness = tiles.get(key)
if(bbox.overlapsWith(tileBbox)){ if (tileFreshness > self.initializeTime) {
// The current tile should be loaded from disk // This tile is loaded by another source
this.GetIdb(key).then((features:{feature: any, freshness: Date}[] ) => { continue
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
} }
})
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 return true; // Remove the callback
}) })
} }
private SetIdb(tileIndex, data){ public addTile(tile: FeatureSource & Tiled) {
IdbLocalStorage.SetDirectly(this._layer.id+"_"+tileIndex, data)
}
private GetIdb(tileIndex){
return IdbLocalStorage.GetDirectly(this._layer.id+"_"+tileIndex)
}
public addTile(tile: FeatureSource & Tiled){
const self = this const self = this
tile.features.addCallbackAndRunD(features => { tile.features.addCallbackAndRunD(features => {
const now = new Date() const now = new Date()
if (features.length > 0) { 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! // We _still_ write the time to know that this tile is empty!
this.MarkVisited(tile.tileIndex, now) this.MarkVisited(tile.tileIndex, now)
}) })
} }
public poison(lon: number, lat: number) { public poison(lon: number, lat: number) {
for (let z = 0; z < 25; z++) { for (let z = 0; z < 25; z++) {
const {x, y} = Tiles.embedded_tile(lat, lon, z) const {x, y} = Tiles.embedded_tile(lat, lon, z)
@ -110,6 +115,14 @@ export default class SaveTileToLocalStorageActor {
public MarkVisited(tileId: number, freshness: Date) { public MarkVisited(tileId: number, freshness: Date) {
this.visitedTiles.data.set(tileId, freshness) 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)
} }
} }

View file

@ -157,7 +157,7 @@ export default class FeaturePipeline {
// We load the cached values and register them // We load the cached values and register them
// Getting data from upstream happens a bit lower // Getting data from upstream happens a bit lower
localTileSaver.LoadTilesFromDisk( localTileSaver.LoadTilesFromDisk(
state.currentBounds, state.currentBounds, state.locationControl,
(tileIndex, freshness) => self.freshnesses.get(id).addTileLoad(tileIndex, freshness), (tileIndex, freshness) => self.freshnesses.get(id).addTileLoad(tileIndex, freshness),
(tile) => { (tile) => {
new RegisteringAllFromFeatureSourceActor(tile) new RegisteringAllFromFeatureSourceActor(tile)
@ -221,7 +221,13 @@ export default class FeaturePipeline {
state.filteredLayers.data.forEach(flayer => { state.filteredLayers.data.forEach(flayer => {
const layer = flayer.layerDef const layer = flayer.layerDef
if (layer.maxAgeOfCache > 0) { 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()) self.freshnesses.get(layer.id).addTileLoad(tileId, new Date())
}) })

View file

@ -3,7 +3,6 @@ import {UIEventSource} from "../UIEventSource";
import BaseLayer from "../../Models/BaseLayer"; import BaseLayer from "../../Models/BaseLayer";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
import AvailableBaseLayers from "../Actors/AvailableBaseLayers"; import AvailableBaseLayers from "../Actors/AvailableBaseLayers";
import BackgroundLayerResetter from "../Actors/BackgroundLayerResetter";
import Attribution from "../../UI/BigComponents/Attribution"; import Attribution from "../../UI/BigComponents/Attribution";
import Minimap, {MinimapObj} from "../../UI/Base/Minimap"; import Minimap, {MinimapObj} from "../../UI/Base/Minimap";
import {Tiles} from "../../Models/TileRange"; import {Tiles} from "../../Models/TileRange";
@ -84,35 +83,17 @@ export default class MapState extends UserRelatedState {
this.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(this.locationControl); this.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(this.locationControl);
this.backgroundLayer = this.backgroundLayerId.map( let defaultLayer = AvailableBaseLayers.osmCarto
(selectedId: string) => { const available = this.availableBackgroundLayers.data;
if (selectedId === undefined) { for (const layer of available) {
return AvailableBaseLayers.osmCarto; if (this.backgroundLayerId.data === layer.id) {
} defaultLayer = layer;
}
const available = this.availableBackgroundLayers.data; }
for (const layer of available) { const self = this
if (layer.id === selectedId) { this.backgroundLayer = new UIEventSource<BaseLayer>(defaultLayer)
return layer; this.backgroundLayer.addCallbackAndRunD(layer => self.backgroundLayerId.setData(layer.id))
}
}
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
);
const attr = new Attribution( const attr = new Attribution(
this.locationControl, this.locationControl,
this.osmConnection.userDetails, this.osmConnection.userDetails,
@ -334,10 +315,7 @@ export default class MapState extends UserRelatedState {
const filtersPerName = new Map<string, FilterConfig>() const filtersPerName = new Map<string, FilterConfig>()
layer.filters.forEach(f => filtersPerName.set(f.id, f)) layer.filters.forEach(f => filtersPerName.set(f.id, f))
const qp = QueryParameters.GetQueryParameter("filter-" + layer.id, "", "Filtering state for a layer") const qp = QueryParameters.GetQueryParameter("filter-" + layer.id, "", "Filtering state for a layer")
flayer.appliedFilters.map(filters => { flayer.appliedFilters.map(filters => (filters ?? []).map(f => f.filter.id + "." + f.selected).join(","), [], textual => {
filters = filters ?? []
return filters.map(f => f.filter.id + "." + f.selected).join(",")
}, [], textual => {
if (textual.length === 0) { if (textual.length === 0) {
return empty return empty
} }

View file

@ -13,7 +13,8 @@ export interface MinimapOptions {
attribution?: BaseUIElement | boolean, attribution?: BaseUIElement | boolean,
onFullyLoaded?: (leaflet: L.Map) => void, onFullyLoaded?: (leaflet: L.Map) => void,
leafletMap?: UIEventSource<any>, leafletMap?: UIEventSource<any>,
lastClickLocation?: UIEventSource<{ lat: number, lon: number }> lastClickLocation?: UIEventSource<{ lat: number, lon: number }>,
addLayerControl?: boolean | false
} }
export interface MinimapObj { export interface MinimapObj {

View file

@ -10,6 +10,7 @@ import Minimap, {MinimapObj, MinimapOptions} from "./Minimap";
import {BBox} from "../../Logic/BBox"; import {BBox} from "../../Logic/BBox";
import 'leaflet-polylineoffset' import 'leaflet-polylineoffset'
import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter"; import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter";
import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch";
export default class MinimapImplementation extends BaseUIElement implements MinimapObj { export default class MinimapImplementation extends BaseUIElement implements MinimapObj {
private static _nextId = 0; private static _nextId = 0;
@ -24,6 +25,7 @@ export default class MinimapImplementation extends BaseUIElement implements Mini
private readonly _attribution: BaseUIElement | boolean; private readonly _attribution: BaseUIElement | boolean;
private readonly _lastClickLocation: UIEventSource<{ lat: number; lon: number }>; private readonly _lastClickLocation: UIEventSource<{ lat: number; lon: number }>;
private readonly _bounds: UIEventSource<BBox> | undefined; private readonly _bounds: UIEventSource<BBox> | undefined;
private readonly _addLayerControl: boolean;
private constructor(options: MinimapOptions) { private constructor(options: MinimapOptions) {
super() super()
@ -38,6 +40,7 @@ export default class MinimapImplementation extends BaseUIElement implements Mini
this._onFullyLoaded = options.onFullyLoaded this._onFullyLoaded = options.onFullyLoaded
this._attribution = options.attribution this._attribution = options.attribution
this._lastClickLocation = options.lastClickLocation; this._lastClickLocation = options.lastClickLocation;
this._addLayerControl = options.addLayerControl ?? false
MinimapImplementation._nextId++ MinimapImplementation._nextId++
} }
@ -131,6 +134,17 @@ export default class MinimapImplementation extends BaseUIElement implements Mini
}); });
resizeObserver.observe(div); 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; return wrapper;
} }

View file

@ -1,24 +1,140 @@
import Combine from "../Base/Combine"; import Combine from "../Base/Combine";
import {UIEventSource} from "../../Logic/UIEventSource"; import {UIEventSource} from "../../Logic/UIEventSource";
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
import State from "../../State";
import Loc from "../../Models/Loc"; import Loc from "../../Models/Loc";
import Svg from "../../Svg"; import Svg from "../../Svg";
import {VariableUiElement} from "../Base/VariableUIElement";
import Toggle from "../Input/Toggle"; 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 { class SingleLayerSelectionButton extends Toggle {
constructor(state: {
locationControl: UIEventSource<Loc> public readonly activate: () => void
}, prefered: string) {
const layer = AvailableBaseLayers.SelectBestLayerAccordingTo(state.locationControl, new UIEventSource(prefered)) /**
const layerIsCorrectType = layer.map(bl => bl?.category === prefered) *
* 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( super(
SingleLayerSelectionButton.getIconFor(prefered).SetClass("rounded-full p-3 h-10 w-10"), toggle,
undefined, 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) { private static getIconFor(type: string) {
@ -27,27 +143,54 @@ class SingleLayerSelectionButton extends Toggle {
return Svg.generic_map_svg() return Svg.generic_map_svg()
case "photo": case "photo":
return Svg.satellite_svg() return Svg.satellite_svg()
case "osmbasedmap":
return Svg.osm_logo_svg()
default: default:
return Svg.generic_map_svg() return Svg.generic_map_svg()
} }
} }
} }
export default class BackgroundMapSwitch extends VariableUiElement { export default class BackgroundMapSwitch extends Combine {
constructor( constructor(
state: { state: {
locationControl: UIEventSource<Loc> locationControl: UIEventSource<Loc>,
backgroundLayer: UIEventSource<BaseLayer>
}, },
options?: { currentBackground: UIEventSource<BaseLayer>,
allowedLayers?: UIEventSource<string[]> preferredCategory?: string
}
) { ) {
options = options ?? {} const allowedCategories = ["osmbasedmap", "photo", "map"]
options.allowedLayers = options.allowedLayers ?? new UIEventSource<string[]>(["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")
} }
} }

View file

@ -1,7 +1,5 @@
import Combine from "../Base/Combine"; import Combine from "../Base/Combine";
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations";
import Attribution from "./Attribution";
import State from "../../State";
import {UIEventSource} from "../../Logic/UIEventSource"; import {UIEventSource} from "../../Logic/UIEventSource";
import {FixedUiElement} from "../Base/FixedUiElement"; import {FixedUiElement} from "../Base/FixedUiElement";
import * as licenses from "../../assets/generated/license_info.json" 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 {OsmConnection} from "../../Logic/Osm/OsmConnection";
import Constants from "../../Models/Constants"; import Constants from "../../Models/Constants";
import PrivacyPolicy from "./PrivacyPolicy"; import PrivacyPolicy from "./PrivacyPolicy";
import ContributorCount from "../../Logic/ContributorCount";
/** /**
* The attribution panel shown on mobile * The attribution panel shown on mobile
@ -36,7 +35,7 @@ export default class CopyrightPanel extends Combine {
currentBounds: UIEventSource<BBox>, currentBounds: UIEventSource<BBox>,
locationControl: UIEventSource<Loc>, locationControl: UIEventSource<Loc>,
osmConnection: OsmConnection osmConnection: OsmConnection
}, contributions: UIEventSource<Map<string, number>>) { }) {
const t = Translations.t.general.attribution const t = Translations.t.general.attribution
const layoutToUse = state.layoutToUse const layoutToUse = state.layoutToUse
@ -103,6 +102,8 @@ export default class CopyrightPanel extends Combine {
maintainer = Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.maintainer}) maintainer = Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.maintainer})
} }
const contributions = new ContributorCount(state).Contributors
super([ super([
Translations.t.general.attribution.attributionContent, Translations.t.general.attribution.attributionContent,
new FixedUiElement("MapComplete "+Constants.vNumber).SetClass("font-bold"), new FixedUiElement("MapComplete "+Constants.vNumber).SetClass("font-bold"),
@ -144,8 +145,7 @@ export default class CopyrightPanel extends Combine {
})), })),
CopyrightPanel.CodeContributors(), CopyrightPanel.CodeContributors(),
new Title(t.iconAttribution.title, 3), new Title(t.iconAttribution.title, 3),
...iconAttributions, ...iconAttributions
new PrivacyPolicy()
].map(e => e?.SetClass("mt-4"))); ].map(e => e?.SetClass("mt-4")));
this.SetClass("flex flex-col link-underline overflow-hidden") 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") this.SetStyle("max-width: calc(100vw - 3em); width: 40rem; margin-left: 0.75rem; margin-right: 0.5rem")

View file

@ -17,6 +17,9 @@ import UserRelatedState from "../../Logic/State/UserRelatedState";
import Loc from "../../Models/Loc"; import Loc from "../../Models/Loc";
import BaseLayer from "../../Models/BaseLayer"; import BaseLayer from "../../Models/BaseLayer";
import FilteredLayer from "../../Models/FilteredLayer"; 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 { export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
@ -29,6 +32,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
featureSwitchShareScreen: UIEventSource<boolean>, featureSwitchShareScreen: UIEventSource<boolean>,
featureSwitchMoreQuests: UIEventSource<boolean>, featureSwitchMoreQuests: UIEventSource<boolean>,
locationControl: UIEventSource<Loc>, locationControl: UIEventSource<Loc>,
featurePipeline: FeaturePipeline,
backgroundLayer: UIEventSource<BaseLayer>, backgroundLayer: UIEventSource<BaseLayer>,
filteredLayers: UIEventSource<FilteredLayer[]> filteredLayers: UIEventSource<FilteredLayer[]>
} & UserRelatedState) { } & UserRelatedState) {
@ -46,6 +50,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
osmConnection: OsmConnection, osmConnection: OsmConnection,
featureSwitchShareScreen: UIEventSource<boolean>, featureSwitchShareScreen: UIEventSource<boolean>,
featureSwitchMoreQuests: UIEventSource<boolean>, featureSwitchMoreQuests: UIEventSource<boolean>,
featurePipeline: FeaturePipeline,
locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]> locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]>
} & UserRelatedState, } & UserRelatedState,
isShown: UIEventSource<boolean>): isShown: UIEventSource<boolean>):
@ -55,16 +60,8 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
const tabs: { header: string | BaseUIElement, content: BaseUIElement }[] = [ const tabs: { header: string | BaseUIElement, content: BaseUIElement }[] = [
{header: `<img src='${state.layoutToUse.icon}'>`, content: welcome}, {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) { if (state.featureSwitchMoreQuests.data) {
tabs.push({ 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; return tabs;
} }
@ -85,6 +107,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
osmConnection: OsmConnection, osmConnection: OsmConnection,
featureSwitchShareScreen: UIEventSource<boolean>, featureSwitchShareScreen: UIEventSource<boolean>,
featureSwitchMoreQuests: UIEventSource<boolean>, featureSwitchMoreQuests: UIEventSource<boolean>,
featurePipeline: FeaturePipeline,
locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]> locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]>
} & UserRelatedState, currentTab: UIEventSource<number>, isShown: UIEventSource<boolean>) { } & UserRelatedState, currentTab: UIEventSource<number>, isShown: UIEventSource<boolean>) {

View file

@ -1,8 +1,6 @@
import Combine from "../Base/Combine"; import Combine from "../Base/Combine";
import ScrollableFullScreen from "../Base/ScrollableFullScreen"; import ScrollableFullScreen from "../Base/ScrollableFullScreen";
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations";
import CopyrightPanel from "./CopyrightPanel";
import ContributorCount from "../../Logic/ContributorCount";
import Toggle from "../Input/Toggle"; import Toggle from "../Input/Toggle";
import MapControlButton from "../MapControlButton"; import MapControlButton from "../MapControlButton";
import Svg from "../../Svg"; import Svg from "../../Svg";
@ -16,6 +14,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
import FilteredLayer from "../../Models/FilteredLayer"; import FilteredLayer from "../../Models/FilteredLayer";
import BaseLayer from "../../Models/BaseLayer"; import BaseLayer from "../../Models/BaseLayer";
import {OsmConnection} from "../../Logic/Osm/OsmConnection"; import {OsmConnection} from "../../Logic/Osm/OsmConnection";
import BackgroundMapSwitch from "./BackgroundMapSwitch";
export default class LeftControls extends Combine { export default class LeftControls extends Combine {
@ -38,23 +37,6 @@ export default class LeftControls extends Combine {
copyrightViewIsOpened: UIEventSource<boolean> 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( const toggledDownload = new Toggle(
new AllDownloads( new AllDownloads(
@ -93,10 +75,10 @@ export default class LeftControls extends Combine {
state.featureSwitchFilter state.featureSwitchFilter
); );
super([filterButton, super([filterButton,
downloadButtonn, downloadButtonn,
copyrightButton]) new BackgroundMapSwitch(state, state.backgroundLayer)
])
this.SetClass("flex flex-col") this.SetClass("flex flex-col")

View file

@ -150,7 +150,8 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO
background: this.mapBackground, background: this.mapBackground,
attribution: this.mapBackground !== State.state?.backgroundLayer, attribution: this.mapBackground !== State.state?.backgroundLayer,
lastClickLocation: this.clickLocation, lastClickLocation: this.clickLocation,
bounds: this._bounds bounds: this._bounds,
addLayerControl: true
} }
) )
this.leafletMap = this.map.leafletMap this.leafletMap = this.map.leafletMap

View file

@ -18,6 +18,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
import MoveConfig from "../../Models/ThemeConfig/MoveConfig"; import MoveConfig from "../../Models/ThemeConfig/MoveConfig";
import {ElementStorage} from "../../Logic/ElementStorage"; import {ElementStorage} from "../../Logic/ElementStorage";
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
import BaseLayer from "../../Models/BaseLayer";
interface MoveReason { interface MoveReason {
text: Translation | string, text: Translation | string,
@ -133,10 +134,12 @@ export default class MoveWizard extends Toggle {
background = reason.background background = reason.background
} }
const preferredBackground = AvailableBaseLayers.SelectBestLayerAccordingTo(loc, new UIEventSource(background)).data
const locationInput = new LocationInput({ const locationInput = new LocationInput({
minZoom: reason.minZoom, minZoom: reason.minZoom,
centerLocation: loc, centerLocation: loc,
mapBackground: AvailableBaseLayers.SelectBestLayerAccordingTo(loc, new UIEventSource(background)) mapBackground: new UIEventSource<BaseLayer>(preferredBackground) // We detach the layer
}) })
if (reason.lockBounds) { if (reason.lockBounds) {

View file

@ -4,7 +4,8 @@
"minzoom": 0, "minzoom": 0,
"source": { "source": {
"osmTags": "user:location=yes", "osmTags": "user:location=yes",
"maxCacheAge": 604800 "#": "Cache is disabled here as these points are kept seperately",
"maxCacheAge": 0
}, },
"mapRendering": null "mapRendering": null
} }

View file

@ -1,4 +1,53 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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" <svg
fill="white"/> xmlns:dc="http://purl.org/dc/elements/1.1/"
</svg> 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
View 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

View file

@ -487,6 +487,16 @@
"authors": [], "authors": [],
"sources": [] "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", "path": "filter.svg",
"license": "CC0", "license": "CC0",

View file

@ -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: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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 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: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" 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"> sodipodi:version="0.32" inkscape:output_extension="org.inkscape.output.svg.inkscape">

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

View file

@ -760,14 +760,14 @@ video {
top: 0px; top: 0px;
} }
.left-0 {
left: 0px;
}
.right-0 { .right-0 {
right: 0px; right: 0px;
} }
.left-0 {
left: 0px;
}
.isolate { .isolate {
isolation: isolate; isolation: isolate;
} }
@ -1920,6 +1920,10 @@ li::marker {
border: 5px solid var(--catch-detail-color); border: 5px solid var(--catch-detail-color);
} }
.border-invisible {
border: 5px solid #00000000;
}
.border-attention { .border-attention {
border-color: var(--catch-detail-color); border-color: var(--catch-detail-color);
} }

View file

@ -208,6 +208,10 @@ li::marker {
border: 5px solid var(--catch-detail-color); border: 5px solid var(--catch-detail-color);
} }
.border-invisible {
border: 5px solid #00000000;
}
.border-attention { .border-attention {
border-color: var(--catch-detail-color); border-color: var(--catch-detail-color);
} }

View file

@ -71,7 +71,7 @@
"emailOf": "Wat is het email-adres van {category}?", "emailOf": "Wat is het email-adres van {category}?",
"emailIs": "Het email-adres van {category} is <a href=\"mailto:{email}\" target=\"_blank\">{email}</a>" "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,...&lt;br/&gt;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": { "attribution": {
"attributionTitle": "Met dank aan", "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>", "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>",

View file

@ -1,6 +1,6 @@
import * as fs from "fs"; import * as fs from "fs";
function genImages() { function genImages(dryrun = false) {
console.log("Generating images") console.log("Generating images")
const dir = fs.readdirSync("./assets/svg") const dir = fs.readdirSync("./assets/svg")
@ -17,7 +17,7 @@ function genImages() {
throw "Non-svg file detected in the svg files: " + path; 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(/<\?xml.*?>/, "")
.replace(/fill: ?none;/g, "fill: none !important;") // This is such a brittle hack... .replace(/fill: ?none;/g, "fill: none !important;") // This is such a brittle hack...
.replace(/\n/g, " ") .replace(/\n/g, " ")
@ -26,11 +26,18 @@ function genImages() {
.replace(/"/g, "\\\"") .replace(/"/g, "\\\"")
const name = path.substr(0, path.length - 4) const name = path.substr(0, path.length - 4)
.replace(/[ -]/g, "_"); .replace(/[ -]/g, "_");
if (dryrun) {
svg = "xxx"
}
module += ` public static ${name} = "${svg}"\n` module += ` public static ${name} = "${svg}"\n`
module += ` public static ${name}_img = Img.AsImageElement(Svg.${name})\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}_svg() { return new Img(Svg.${name}, true);}\n`
module += ` public static ${name}_ui() { return new FixedUiElement(Svg.${name}_img);}\n\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 += `public static All = {${allNames.join(",")}};`
module += "}\n"; module += "}\n";

20
test.ts
View file

@ -3,12 +3,22 @@ import {UIEventSource} from "./Logic/UIEventSource";
import Loc from "./Models/Loc"; import Loc from "./Models/Loc";
import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayersImplementation"; import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayersImplementation";
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; 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>({ locationControl: new UIEventSource<Loc>({
zoom: 19, zoom: 19,
lat: 51.5, lat: 51.2,
lon: 4.1 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")