forked from MapComplete/MapComplete
Merge branch 'master' into develop
This commit is contained in:
commit
fbf5ce6fec
26 changed files with 1225 additions and 847 deletions
|
@ -19,13 +19,14 @@ export default class BackgroundLayerResetter {
|
|||
return
|
||||
}
|
||||
|
||||
currentBackgroundLayer.addCallbackAndRunD((l) => {
|
||||
currentBackgroundLayer.addCallbackAndRunD(async (l) => {
|
||||
if (
|
||||
l.geometry !== undefined &&
|
||||
AvailableRasterLayers.globalLayers.find(
|
||||
(global) => global.properties.id !== l.properties.id
|
||||
)
|
||||
) {
|
||||
await AvailableRasterLayers.editorLayerIndex()
|
||||
BackgroundLayerResetter.installHandler(
|
||||
currentBackgroundLayer,
|
||||
availableLayers.store
|
||||
|
|
|
@ -61,9 +61,9 @@ export class PreferredRasterLayerSelector {
|
|||
* Returns 'true' if the target layer is set or is the current layer
|
||||
* @private
|
||||
*/
|
||||
private updateLayer() {
|
||||
private async updateLayer() {
|
||||
// What is the ID of the layer we have to (try to) load?
|
||||
const targetLayerId = this._queryParameter.data ?? this._preferredBackgroundLayer.data
|
||||
const targetLayerId = (this._queryParameter.data ?? this._preferredBackgroundLayer.data)?.toLowerCase()
|
||||
if (targetLayerId === undefined || targetLayerId === "default") {
|
||||
return
|
||||
}
|
||||
|
@ -74,12 +74,13 @@ export class PreferredRasterLayerSelector {
|
|||
this._rasterLayerSetting.setData(global)
|
||||
return
|
||||
}
|
||||
await AvailableRasterLayers.editorLayerIndex()
|
||||
const isCategory =
|
||||
targetLayerId === "photo" || targetLayerId === "osmbasedmap" || targetLayerId === "map"
|
||||
const available = this._availableLayers.store.data
|
||||
const foundLayer = isCategory
|
||||
? available.find((l) => l.properties.category === targetLayerId)
|
||||
: available.find((l) => l.properties.id === targetLayerId)
|
||||
: available.find((l) => l.properties.id.toLowerCase() === targetLayerId)
|
||||
console.debug("Updating background layer to", foundLayer?.id, {
|
||||
targetLayerId,
|
||||
queryParam: this._queryParameter?.data,
|
||||
|
|
|
@ -14,6 +14,10 @@ export class And extends TagsFilter {
|
|||
constructor(and: ReadonlyArray<TagsFilter>) {
|
||||
super()
|
||||
this.and = and
|
||||
if(and.some(p => typeof p === "string")){
|
||||
console.error("Assertion failed: invalid subtags:", and)
|
||||
throw "Assertion failed: invalid subtags found"
|
||||
}
|
||||
}
|
||||
|
||||
public static construct(and: ReadonlyArray<TagsFilter>): TagsFilter
|
||||
|
|
|
@ -28,7 +28,7 @@ export class AvailableRasterLayers {
|
|||
return this._editorLayerIndex
|
||||
}
|
||||
|
||||
public static globalLayers: RasterLayerPolygon[] = globallayers.layers
|
||||
public static globalLayers: ReadonlyArray<RasterLayerPolygon> = globallayers.layers
|
||||
.filter(
|
||||
(properties) =>
|
||||
properties.id !== "osm.carto" && properties.id !== "Bing" /*Added separately*/
|
||||
|
@ -140,28 +140,24 @@ export class RasterLayerUtils {
|
|||
* @param available
|
||||
* @param preferredCategory
|
||||
* @param ignoreLayer
|
||||
* @param skipLayers Skip the first N layers
|
||||
*/
|
||||
public static SelectBestLayerAccordingTo(
|
||||
available: RasterLayerPolygon[],
|
||||
preferredCategory: string,
|
||||
ignoreLayer?: RasterLayerPolygon
|
||||
ignoreLayer?: RasterLayerPolygon,
|
||||
skipLayers: number = 0
|
||||
): RasterLayerPolygon {
|
||||
let secondBest: RasterLayerPolygon = undefined
|
||||
for (const rasterLayer of available) {
|
||||
if (rasterLayer === ignoreLayer) {
|
||||
continue
|
||||
}
|
||||
const p = rasterLayer.properties
|
||||
if (p.category === preferredCategory) {
|
||||
if (p.best) {
|
||||
return rasterLayer
|
||||
}
|
||||
if (!secondBest) {
|
||||
secondBest = rasterLayer
|
||||
}
|
||||
}
|
||||
const inCategory = available.filter(l => l.properties.category === preferredCategory)
|
||||
const best : RasterLayerPolygon[] = inCategory.filter(l => l.properties.best)
|
||||
const others : RasterLayerPolygon[] = inCategory.filter(l => !l.properties.best)
|
||||
let all = best.concat(others)
|
||||
console.log("Selected layers are:", all.map(l => l.properties.id))
|
||||
if(others.length > skipLayers){
|
||||
all = all.slice(skipLayers)
|
||||
}
|
||||
return secondBest
|
||||
|
||||
return all.find(l => l !== ignoreLayer)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -973,12 +973,11 @@ export class TagRenderingConfigUtils {
|
|||
}
|
||||
const clone: TagRenderingConfig = Object.create(config)
|
||||
// The original mappings get "priorityIf" set
|
||||
const oldMappingsCloned =
|
||||
clone.mappings?.map((m) => ({
|
||||
...m,
|
||||
addExtraTags: "nobrand=",
|
||||
priorityIf: m.priorityIf ?? TagUtils.Tag("id~*"),
|
||||
})) ?? []
|
||||
const oldMappingsCloned = clone.mappings?.map((m) => (<Mapping> {
|
||||
...m,
|
||||
addExtraTags: [new Tag("nobrand","")],
|
||||
priorityIf: m.priorityIf ?? TagUtils.Tag("id~*"),
|
||||
})) ?? [];
|
||||
clone.mappings = [...oldMappingsCloned, ...extraMappings]
|
||||
return clone
|
||||
})
|
||||
|
|
|
@ -161,7 +161,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
this.featureSwitches = new FeatureSwitchState(layout)
|
||||
this.guistate = new MenuState(
|
||||
this.featureSwitches.featureSwitchWelcomeMessage.data,
|
||||
layout.id
|
||||
layout.id,
|
||||
)
|
||||
this.map = new UIEventSource<MlMap>(undefined)
|
||||
const geolocationState = new GeoLocationState()
|
||||
|
@ -177,14 +177,14 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
oauth_token: QueryParameters.GetQueryParameter(
|
||||
"oauth_token",
|
||||
undefined,
|
||||
"Used to complete the login"
|
||||
"Used to complete the login",
|
||||
),
|
||||
})
|
||||
this.userRelatedState = new UserRelatedState(
|
||||
this.osmConnection,
|
||||
layout,
|
||||
this.featureSwitches,
|
||||
this.mapProperties
|
||||
this.mapProperties,
|
||||
)
|
||||
this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => {
|
||||
this.mapProperties.allowRotating.setData(fixated !== "yes")
|
||||
|
@ -195,20 +195,20 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
geolocationState,
|
||||
this.selectedElement,
|
||||
this.mapProperties,
|
||||
this.userRelatedState.gpsLocationHistoryRetentionTime
|
||||
this.userRelatedState.gpsLocationHistoryRetentionTime,
|
||||
)
|
||||
this.geolocationControl = new GeolocationControlState(this.geolocation, this.mapProperties)
|
||||
|
||||
this.availableLayers = AvailableRasterLayers.layersAvailableAt(
|
||||
this.mapProperties.location,
|
||||
this.osmConnection.isLoggedIn
|
||||
this.osmConnection.isLoggedIn,
|
||||
)
|
||||
|
||||
this.layerState = new LayerState(
|
||||
this.osmConnection,
|
||||
layout.layers,
|
||||
layout.id,
|
||||
this.featureSwitches.featureSwitchLayerDefault
|
||||
this.featureSwitches.featureSwitchLayerDefault,
|
||||
)
|
||||
|
||||
{
|
||||
|
@ -217,7 +217,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
const isDisplayed = QueryParameters.GetBooleanQueryParameter(
|
||||
"overlay-" + rasterInfo.id,
|
||||
rasterInfo.defaultState ?? true,
|
||||
"Whether or not overlay layer " + rasterInfo.id + " is shown"
|
||||
"Whether or not overlay layer " + rasterInfo.id + " is shown",
|
||||
)
|
||||
const state = { isDisplayed }
|
||||
overlayLayerStates.set(rasterInfo.id, state)
|
||||
|
@ -242,7 +242,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
this.osmConnection.Backend(),
|
||||
(id) => this.layerState.filteredLayers.get(id).isDisplayed,
|
||||
mvtAvailableLayers,
|
||||
this.fullNodeDatabase
|
||||
this.fullNodeDatabase,
|
||||
)
|
||||
|
||||
let currentViewIndex = 0
|
||||
|
@ -260,7 +260,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
id: "current_view_" + currentViewIndex,
|
||||
}),
|
||||
]
|
||||
})
|
||||
}),
|
||||
)
|
||||
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds)
|
||||
|
||||
|
@ -278,19 +278,19 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
featureSwitches: this.featureSwitches,
|
||||
},
|
||||
layout?.isLeftRightSensitive() ?? false,
|
||||
(e) => this.reportError(e)
|
||||
(e) => this.reportError(e),
|
||||
)
|
||||
this.historicalUserLocations = this.geolocation.historicalUserLocations
|
||||
this.newFeatures = new NewGeometryFromChangesFeatureSource(
|
||||
this.changes,
|
||||
layoutSource,
|
||||
this.featureProperties
|
||||
this.featureProperties,
|
||||
)
|
||||
layoutSource.addSource(this.newFeatures)
|
||||
|
||||
const perLayer = new PerLayerFeatureSourceSplitter(
|
||||
Array.from(this.layerState.filteredLayers.values()).filter(
|
||||
(l) => l.layerDef?.source !== null
|
||||
(l) => l.layerDef?.source !== null,
|
||||
),
|
||||
new ChangeGeometryApplicator(this.indexedFeatures, this.changes),
|
||||
{
|
||||
|
@ -301,10 +301,10 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
"Got ",
|
||||
features.length,
|
||||
"leftover features, such as",
|
||||
features[0].properties
|
||||
features[0].properties,
|
||||
)
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
this.perLayer = perLayer.perLayer
|
||||
}
|
||||
|
@ -344,12 +344,12 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
this.lastClickObject = new LastClickFeatureSource(
|
||||
this.layout,
|
||||
this.mapProperties.lastClickLocation,
|
||||
this.userRelatedState.addNewFeatureMode
|
||||
this.userRelatedState.addNewFeatureMode,
|
||||
)
|
||||
|
||||
this.osmObjectDownloader = new OsmObjectDownloader(
|
||||
this.osmConnection.Backend(),
|
||||
this.changes
|
||||
this.changes,
|
||||
)
|
||||
|
||||
this.perLayerFiltered = this.showNormalDataOn(this.map)
|
||||
|
@ -360,7 +360,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
currentZoom: this.mapProperties.zoom,
|
||||
layerState: this.layerState,
|
||||
bounds: this.visualFeedbackViewportBounds,
|
||||
}
|
||||
},
|
||||
)
|
||||
this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView
|
||||
this.imageUploadManager = new ImageUploadManager(
|
||||
|
@ -368,7 +368,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
Imgur.singleton,
|
||||
this.featureProperties,
|
||||
this.osmConnection,
|
||||
this.changes
|
||||
this.changes,
|
||||
)
|
||||
this.favourites = new FavouritesFeatureSource(this)
|
||||
const longAgo = new Date()
|
||||
|
@ -414,7 +414,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
LayoutSource.fromCacheZoomLevel,
|
||||
fs,
|
||||
this.featureProperties,
|
||||
fs.layer.layerDef.maxAgeOfCache
|
||||
fs.layer.layerDef.maxAgeOfCache,
|
||||
)
|
||||
toLocalStorage.set(layerId, storage)
|
||||
})
|
||||
|
@ -427,7 +427,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
const doShowLayer = this.mapProperties.zoom.map(
|
||||
(z) =>
|
||||
(fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0),
|
||||
[fs.layer.isDisplayed]
|
||||
[fs.layer.isDisplayed],
|
||||
)
|
||||
|
||||
if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) {
|
||||
|
@ -444,7 +444,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
fs.layer,
|
||||
fs,
|
||||
(id) => this.featureProperties.getStore(id),
|
||||
this.layerState.globalFilters
|
||||
this.layerState.globalFilters,
|
||||
)
|
||||
filteringFeatureSource.set(layerName, filtered)
|
||||
|
||||
|
@ -588,7 +588,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
return
|
||||
}
|
||||
this.selectClosestAtCenter(0)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
for (let i = 1; i < 9; i++) {
|
||||
|
@ -606,7 +606,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
onUp: true,
|
||||
},
|
||||
doc,
|
||||
() => this.selectClosestAtCenter(i - 1)
|
||||
() => this.selectClosestAtCenter(i - 1),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -623,7 +623,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
if (this.featureSwitches.featureSwitchBackgroundSelection.data) {
|
||||
this.guistate.backgroundLayerSelectionIsOpened.setData(true)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
Hotkeys.RegisterHotkey(
|
||||
{
|
||||
|
@ -635,18 +635,11 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
if (this.featureSwitches.featureSwitchFilter.data) {
|
||||
this.guistate.openFilterView()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
Hotkeys.RegisterHotkey(
|
||||
{ shift: "O" },
|
||||
Translations.t.hotkeyDocumentation.selectMapnik,
|
||||
() => {
|
||||
this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto)
|
||||
}
|
||||
)
|
||||
const setLayerCategory = (category: EliCategory) => {
|
||||
const setLayerCategory = (category: EliCategory, skipLayers: number = 0) => {
|
||||
const timeOfCall = new Date()
|
||||
const available = this.availableLayers.store.addCallbackAndRunD((available) => {
|
||||
this.availableLayers.store.addCallbackAndRunD((available) => {
|
||||
const now = new Date()
|
||||
const timeDiff = (now.getTime() - timeOfCall.getTime()) / 1000
|
||||
if (timeDiff > 3) {
|
||||
|
@ -656,9 +649,13 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
const best = RasterLayerUtils.SelectBestLayerAccordingTo(
|
||||
available,
|
||||
category,
|
||||
current.data
|
||||
current.data,
|
||||
skipLayers
|
||||
)
|
||||
console.log("Best layer for category", category, "is", best.properties.id)
|
||||
if(!best){
|
||||
return
|
||||
}
|
||||
console.log("Best layer for category", category, "is", best?.properties?.id)
|
||||
current.setData(best)
|
||||
})
|
||||
}
|
||||
|
@ -666,26 +663,43 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
Hotkeys.RegisterHotkey(
|
||||
{ nomod: "O" },
|
||||
Translations.t.hotkeyDocumentation.selectOsmbasedmap,
|
||||
() => setLayerCategory("osmbasedmap")
|
||||
() => setLayerCategory("osmbasedmap"),
|
||||
)
|
||||
|
||||
Hotkeys.RegisterHotkey(
|
||||
{ nomod: "M" },
|
||||
Translations.t.hotkeyDocumentation.selectMap,
|
||||
() => setLayerCategory("map")
|
||||
() => setLayerCategory("map"),
|
||||
)
|
||||
|
||||
Hotkeys.RegisterHotkey(
|
||||
{ nomod: "P" },
|
||||
Translations.t.hotkeyDocumentation.selectAerial,
|
||||
() => setLayerCategory("photo")
|
||||
() => setLayerCategory("photo"),
|
||||
)
|
||||
Hotkeys.RegisterHotkey(
|
||||
{ shift: "O" },
|
||||
Translations.t.hotkeyDocumentation.selectOsmbasedmap,
|
||||
() => setLayerCategory("osmbasedmap",2),
|
||||
)
|
||||
|
||||
Hotkeys.RegisterHotkey(
|
||||
{ shift: "M" },
|
||||
Translations.t.hotkeyDocumentation.selectMap,
|
||||
() => setLayerCategory("map",2),
|
||||
)
|
||||
|
||||
Hotkeys.RegisterHotkey(
|
||||
{ shift: "P" },
|
||||
Translations.t.hotkeyDocumentation.selectAerial,
|
||||
() => setLayerCategory("photo",2),
|
||||
)
|
||||
Hotkeys.RegisterHotkey(
|
||||
{ nomod: "L" },
|
||||
Translations.t.hotkeyDocumentation.geolocate,
|
||||
() => {
|
||||
this.geolocationControl.handleClick()
|
||||
}
|
||||
},
|
||||
)
|
||||
return true
|
||||
})
|
||||
|
@ -697,7 +711,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
Translations.t.hotkeyDocumentation.translationMode,
|
||||
() => {
|
||||
Locale.showLinkToWeblate.setData(!Locale.showLinkToWeblate.data)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -708,7 +722,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
const normalLayers = this.layout.layers.filter(
|
||||
(l) =>
|
||||
Constants.priviliged_layers.indexOf(<any>l.id) < 0 &&
|
||||
!l.id.startsWith("note_import")
|
||||
!l.id.startsWith("note_import"),
|
||||
)
|
||||
const maxzoom = Math.min(...normalLayers.map((l) => l.minzoom))
|
||||
|
||||
|
@ -716,7 +730,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
(l) =>
|
||||
Constants.priviliged_layers.indexOf(<any>l.id) < 0 &&
|
||||
l.source.geojsonSource === undefined &&
|
||||
l.doCount
|
||||
l.doCount,
|
||||
)
|
||||
const summaryTileSource = new SummaryTileSource(
|
||||
Constants.SummaryServer,
|
||||
|
@ -725,7 +739,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
this.mapProperties,
|
||||
{
|
||||
isActive: this.mapProperties.zoom.map((z) => z < maxzoom),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return new SummaryTileSourceRewriter(summaryTileSource, this.layerState.filteredLayers)
|
||||
|
@ -746,12 +760,12 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
gps_location_history: this.geolocation.historicalUserLocations,
|
||||
gps_track: this.geolocation.historicalUserLocationsTrack,
|
||||
selected_element: new StaticFeatureSource(
|
||||
this.selectedElement.map((f) => (f === undefined ? empty : [f]))
|
||||
this.selectedElement.map((f) => (f === undefined ? empty : [f])),
|
||||
),
|
||||
range: new StaticFeatureSource(
|
||||
this.mapProperties.maxbounds.map((bbox) =>
|
||||
bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({ id: "range" })]
|
||||
)
|
||||
bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({ id: "range" })],
|
||||
),
|
||||
),
|
||||
current_view: this.currentView,
|
||||
favourite: this.favourites,
|
||||
|
@ -766,7 +780,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
ShowDataLayer.showRange(
|
||||
this.map,
|
||||
new StaticFeatureSource([bbox.asGeoJson({ id: "range" })]),
|
||||
this.featureSwitches.featureSwitchIsTesting
|
||||
this.featureSwitches.featureSwitchIsTesting,
|
||||
)
|
||||
}
|
||||
const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view")
|
||||
|
@ -780,7 +794,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
currentViewLayer,
|
||||
this.layout,
|
||||
this.osmObjectDownloader,
|
||||
this.featureProperties
|
||||
this.featureProperties,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -824,20 +838,20 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
|
||||
const lastClickLayerConfig = new LayerConfig(
|
||||
<LayerConfigJson>last_click_layerconfig,
|
||||
"last_click"
|
||||
"last_click",
|
||||
)
|
||||
const lastClickFiltered =
|
||||
lastClickLayerConfig.isShown === undefined
|
||||
? specialLayers.last_click
|
||||
: specialLayers.last_click.features.mapD((fs) =>
|
||||
fs.filter((f) => {
|
||||
const matches = lastClickLayerConfig.isShown.matchesProperties(
|
||||
f.properties
|
||||
)
|
||||
console.debug("LastClick ", f, "matches", matches)
|
||||
return matches
|
||||
})
|
||||
)
|
||||
fs.filter((f) => {
|
||||
const matches = lastClickLayerConfig.isShown.matchesProperties(
|
||||
f.properties,
|
||||
)
|
||||
console.debug("LastClick ", f, "matches", matches)
|
||||
return matches
|
||||
}),
|
||||
)
|
||||
new ShowDataLayer(this.map, {
|
||||
features: new StaticFeatureSource(lastClickFiltered),
|
||||
layer: lastClickLayerConfig,
|
||||
|
@ -884,7 +898,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
this.mapProperties.rasterLayer,
|
||||
this.availableLayers,
|
||||
this.featureSwitches.backgroundLayerId,
|
||||
this.userRelatedState.preferredBackgroundLayer
|
||||
this.userRelatedState.preferredBackgroundLayer,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -900,7 +914,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
? ">>> _Not_ reporting error to report server as testmode is on"
|
||||
: ">>> Reporting error to",
|
||||
Constants.ErrorReportServer,
|
||||
message
|
||||
message,
|
||||
)
|
||||
if (isTesting) {
|
||||
return
|
||||
|
|
|
@ -85,13 +85,12 @@
|
|||
feedback?.setData(undefined)
|
||||
return
|
||||
}
|
||||
feedback?.setData(validator?.getFeedback(v, getCountry))
|
||||
if (!validator?.isValid(v, getCountry)) {
|
||||
feedback?.setData(validator?.getFeedback(v, getCountry))
|
||||
value.setData(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
feedback?.setData(undefined)
|
||||
if (selectedUnit.data) {
|
||||
value.setData(unit.toOsm(v, selectedUnit.data))
|
||||
} else {
|
||||
|
|
|
@ -1,16 +1,30 @@
|
|||
import { Validator } from "../Validator"
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
import Translations from "../../i18n/Translations"
|
||||
|
||||
export default class UrlValidator extends Validator {
|
||||
private readonly _forceHttps: boolean
|
||||
|
||||
private static readonly aggregatorWebsites = new Set<string>([
|
||||
"booking.com",
|
||||
"hotel-details-guide.com", "tripingguide.com",
|
||||
"tripadvisor.com", "tripadvisor.co.uk", "tripadvisor.com.au",
|
||||
])
|
||||
|
||||
constructor(name?: string, explanation?: string, forceHttps?: boolean) {
|
||||
super(
|
||||
name ?? "url",
|
||||
explanation ??
|
||||
"The validatedTextField will format URLs to always be valid and have a https://-header (even though the 'https'-part will be hidden from the user. Furthermore, some tracking parameters will be removed",
|
||||
"url"
|
||||
"The validatedTextField will format URLs to always be valid and have a https://-header (even though the 'https'-part will be hidden from the user. Furthermore, some tracking parameters will be removed",
|
||||
"url",
|
||||
)
|
||||
this._forceHttps = forceHttps ?? false
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* new UrlValidator().reformat("https://example.com/page?fbclid=123456&utm_source=mastodon") // => "https://example.com/page"
|
||||
*/
|
||||
reformat(str: string): string {
|
||||
try {
|
||||
let url: URL
|
||||
|
@ -63,6 +77,33 @@ export default class UrlValidator extends Validator {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* const v = new UrlValidator()
|
||||
* v.getFeedback("example.").textFor("en") // => "This is not a valid web address"
|
||||
* v.getFeedback("https://booking.com/some-hotel.html").textFor("en").indexOf("search the official website") > 0 // => true
|
||||
*
|
||||
*/
|
||||
getFeedback(s: string, getCountry?: () => string): Translation | undefined {
|
||||
const upstream = super.getFeedback(s, getCountry)
|
||||
if (upstream) {
|
||||
return upstream
|
||||
}
|
||||
/*
|
||||
Upstream calls 'isValid', which checks if it is an actual URL.
|
||||
If we reach this point, we can safely assume 'new URL' will work
|
||||
*/
|
||||
const url = new URL(s)
|
||||
let host = url.host.toLowerCase()
|
||||
if (host.startsWith("www.")) {
|
||||
host = host.slice(4)
|
||||
}
|
||||
if (UrlValidator.aggregatorWebsites.has(host)) {
|
||||
return Translations.t.validation.url.aggregator.Subs({ host })
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
isValid(str: string): boolean {
|
||||
try {
|
||||
if (
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
export let color: string | undefined = undefined
|
||||
export let clss: string | undefined = ""
|
||||
clss ??= ""
|
||||
export let emojiHeight = 40
|
||||
export let emojiHeight = "40px"
|
||||
</script>
|
||||
|
||||
{#if icon}
|
||||
|
@ -147,7 +147,7 @@
|
|||
{:else if icon === "user_circle"}
|
||||
<UserCircleIcon class={clss} {color} />
|
||||
{:else if Utils.isEmoji(icon)}
|
||||
<span style={`font-size: ${emojiHeight}px; line-height: ${emojiHeight}px`}>
|
||||
<span style= {`font-size: ${emojiHeight}; line-height: ${emojiHeight}`}>
|
||||
{icon}
|
||||
</span>
|
||||
{:else}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
* Class which is applied onto the individual icons
|
||||
*/
|
||||
export let clss = ""
|
||||
export let emojiHeight : string = "40px"
|
||||
|
||||
/**
|
||||
* Class applied onto the entire element
|
||||
|
@ -41,7 +42,7 @@
|
|||
<div class={twMerge("relative", size)}>
|
||||
{#each iconsParsed as icon}
|
||||
<div class="absolute top-0 left-0 flex h-full w-full items-center">
|
||||
<Icon icon={icon.icon} color={icon.color} {clss} />
|
||||
<Icon icon={icon.icon} color={icon.color} {clss} {emojiHeight} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
let rasterLayerId = rasterLayer.sync(
|
||||
(l) => l?.properties?.id,
|
||||
[],
|
||||
(id) => availableLayers.find((l) => l.properties.id === id)
|
||||
(id) => availableLayers.find((l) => l.properties.id === id),
|
||||
)
|
||||
rasterLayer.setData(availableLayers[0])
|
||||
$: rasterLayer.setData(availableLayers[0])
|
||||
|
@ -36,13 +36,13 @@
|
|||
return
|
||||
}
|
||||
rasterLayer.setData(fav)
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
onDestroy(
|
||||
rasterLayer.addCallbackAndRunD((selected) => {
|
||||
favourite?.setData(selected.properties.id)
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@
|
|||
} else {
|
||||
rasterLayerOnMap.setData(undefined)
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -93,6 +93,15 @@
|
|||
{#each availableLayers as availableLayer}
|
||||
<option value={availableLayer.properties.id}>
|
||||
{availableLayer.properties.name}
|
||||
{#if availableLayer.properties.category.startsWith("historic")}
|
||||
⏱️
|
||||
{/if}
|
||||
{#if availableLayer.properties.category.endsWith("elevation")}
|
||||
⛰
|
||||
{/if}
|
||||
{#if availableLayer.properties.best}
|
||||
⭐
|
||||
{/if}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
|
|
@ -30,6 +30,12 @@
|
|||
| "large-height"
|
||||
| string
|
||||
}
|
||||
|
||||
const emojiHeights = {
|
||||
"small":"2rem",
|
||||
"medium":"3rem",
|
||||
"large":"5rem"
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if mapping.icon !== undefined}
|
||||
|
@ -42,6 +48,9 @@
|
|||
}-width`,
|
||||
"shrink-0"
|
||||
)}
|
||||
|
||||
emojiHeight={ emojiHeights[mapping.iconClass] ?? "2rem"}
|
||||
|
||||
clss={`mapping-icon-${mapping.iconClass ?? "small"}`}
|
||||
/>
|
||||
<SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement} {clss} />
|
||||
|
|
|
@ -1727,11 +1727,12 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
}
|
||||
}
|
||||
|
||||
private static emojiRegex = /^\p{Extended_Pictographic}+$/u
|
||||
private static emojiRegex = /[\p{Extended_Pictographic}🛰️]$/u
|
||||
|
||||
/**
|
||||
* Returns 'true' if the given string contains at least one and only emoji characters
|
||||
* @param string
|
||||
*
|
||||
* Utils.isEmoji("⛰\uFE0F") // => true
|
||||
*/
|
||||
public static isEmoji(string: string) {
|
||||
return Utils.emojiRegex.test(string)
|
||||
|
|
|
@ -1,5 +1,21 @@
|
|||
{
|
||||
"layers": [
|
||||
{
|
||||
"url": "pmtiles://https://api.protomaps.com/tiles/v3.json?key=2af8b969a9e8b692",
|
||||
"style": "assets/sunny.json",
|
||||
"connect-src": [
|
||||
"https://protomaps.github.io"
|
||||
],
|
||||
"best": true,
|
||||
"id": "protomaps.sunny",
|
||||
"name": "Protomaps Sunny",
|
||||
"type": "vector",
|
||||
"category": "osmbasedmap",
|
||||
"attribution": {
|
||||
"text": "Protomaps",
|
||||
"url": "https://protomaps.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "OpenStreetMap Carto",
|
||||
"url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
|
@ -87,21 +103,6 @@
|
|||
"url": "https://protomaps.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "pmtiles://https://api.protomaps.com/tiles/v3.json?key=2af8b969a9e8b692",
|
||||
"style": "assets/sunny.json",
|
||||
"connect-src": [
|
||||
"https://protomaps.github.io"
|
||||
],
|
||||
"id": "protomaps.sunny",
|
||||
"name": "Protomaps Sunny",
|
||||
"type": "vector",
|
||||
"category": "osmbasedmap",
|
||||
"attribution": {
|
||||
"text": "Protomaps",
|
||||
"url": "https://protomaps.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "pmtiles://https://api.protomaps.com/tiles/v3.json?key=2af8b969a9e8b692",
|
||||
"style": "assets/sunny-unlabeled.json",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue