Core: Don't throw away changes if uploading failed, report errors

This commit is contained in:
Pieter Vander Vennet 2024-06-20 15:12:51 +02:00
parent 13ea16317f
commit 12fec3f312
10 changed files with 170 additions and 96 deletions

View file

@ -1,13 +1,17 @@
{ {
"id": "mapcomplete-changes", "id": "mapcomplete-changes",
"title": { "title": {
"en": "Changes made with MapComplete" "en": "Changes made with MapComplete",
"de": "Änderungen mit MapComplete"
}, },
"shortDescription": { "shortDescription": {
"en": "Shows changes made by MapComplete" "en": "Shows changes made by MapComplete",
"de": "Änderungen von MapComplete anzeigen"
}, },
"description": { "description": {
"en": "This maps shows all the changes made with MapComplete" "en": "This maps shows all the changes made with MapComplete",
"de": "Diese Karte zeigt alle mit MapComplete vorgenommenen Änderungen",
"pl": "Ta mapa pokazuje wszystkie zmiany wprowadzone za pomocą MapComplete"
}, },
"icon": "./assets/svg/logo.svg", "icon": "./assets/svg/logo.svg",
"hideFromOverview": true, "hideFromOverview": true,
@ -18,7 +22,9 @@
{ {
"id": "mapcomplete-changes", "id": "mapcomplete-changes",
"name": { "name": {
"en": "Changeset centers" "en": "Changeset centers",
"de": "Zentrum der Änderungssätze",
"zh_Hant": "變更集中心"
}, },
"minzoom": 0, "minzoom": 0,
"source": { "source": {
@ -28,41 +34,48 @@
}, },
"title": { "title": {
"render": { "render": {
"en": "Changeset for {theme}" "en": "Changeset for {theme}",
"de": "Änderungssatz für {theme}"
} }
}, },
"description": { "description": {
"en": "Shows all MapComplete changes" "en": "Shows all MapComplete changes",
"de": "Alle MapComplete-Änderungen anzeigen"
}, },
"tagRenderings": [ "tagRenderings": [
{ {
"id": "show_changeset_id", "id": "show_changeset_id",
"render": { "render": {
"en": "Changeset <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>" "en": "Changeset <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
"de": "Änderungssatz <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>"
} }
}, },
{ {
"id": "contributor", "id": "contributor",
"question": { "question": {
"en": "What contributor did make this change?" "en": "What contributor did make this change?",
"de": "Welcher Mitwirkende hat diese Änderung vorgenommen?"
}, },
"freeform": { "freeform": {
"key": "user" "key": "user"
}, },
"render": { "render": {
"en": "Change made by <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>" "en": "Change made by <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
"de": "Änderung vorgenommen von <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>"
} }
}, },
{ {
"id": "theme-id", "id": "theme-id",
"question": { "question": {
"en": "What theme was used to make this change?" "en": "What theme was used to make this change?",
"de": "Welches Thema wurde für die Änderung verwendet?"
}, },
"freeform": { "freeform": {
"key": "theme" "key": "theme"
}, },
"render": { "render": {
"en": "Change with theme <a href='https://mapcomplete.org/{theme}'>{theme}</a>" "en": "Change with theme <a href='https://mapcomplete.org/{theme}'>{theme}</a>",
"de": "Geändert mit Thema <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>"
} }
}, },
{ {
@ -71,19 +84,23 @@
"key": "locale" "key": "locale"
}, },
"question": { "question": {
"en": "What locale (language) was this change made in?" "en": "What locale (language) was this change made in?",
"de": "In welcher Benutzersprache wurde die Änderung vorgenommen?"
}, },
"render": { "render": {
"en": "User locale is {locale}" "en": "User locale is {locale}",
"de": "Benutzersprache {locale}"
} }
}, },
{ {
"id": "host", "id": "host",
"render": { "render": {
"en": "Change with with <a href='{host}'>{host}</a>" "en": "Change with with <a href='{host}'>{host}</a>",
"de": "Änderung über <a href='{host}'>{host}</a>"
}, },
"question": { "question": {
"en": "What host (website) was this change made with?" "en": "What host (website) was this change made with?",
"de": "Über welchen Host (Webseite) wurde diese Änderung vorgenommen?"
}, },
"freeform": { "freeform": {
"key": "host" "key": "host"
@ -104,10 +121,12 @@
{ {
"id": "version", "id": "version",
"question": { "question": {
"en": "What version of MapComplete was used to make this change?" "en": "What version of MapComplete was used to make this change?",
"de": "Mit welcher MapComplete Version wurde die Änderung vorgenommen?"
}, },
"render": { "render": {
"en": "Made with {editor}" "en": "Made with {editor}",
"de": "Erstellt mit {editor}"
}, },
"freeform": { "freeform": {
"key": "editor" "key": "editor"
@ -493,7 +512,9 @@
} }
], ],
"question": { "question": {
"en": "Themename contains {search}" "en": "Themename contains {search}",
"de": "Themenname enthält {search}",
"pl": "Nazwa tematu zawiera {search}"
} }
} }
] ]
@ -509,7 +530,8 @@
} }
], ],
"question": { "question": {
"en": "Themename does <b>not</b> contain {search}" "en": "Themename does <b>not</b> contain {search}",
"de": "Themename enthält <b>not</b> {search}"
} }
} }
] ]
@ -525,7 +547,8 @@
} }
], ],
"question": { "question": {
"en": "Made by contributor {search}" "en": "Made by contributor {search}",
"de": "Erstellt vom Mitwirkenden {search}"
} }
} }
] ]
@ -541,7 +564,8 @@
} }
], ],
"question": { "question": {
"en": "<b>Not</b> made by contributor {search}" "en": "<b>Not</b> made by contributor {search}",
"de": "<b>Nicht</b> erstellt von Mitwirkendem {search}"
} }
} }
] ]
@ -558,7 +582,8 @@
} }
], ],
"question": { "question": {
"en": "Made before {search}" "en": "Made before {search}",
"de": "Erstellt vor {search}"
} }
} }
] ]
@ -575,7 +600,8 @@
} }
], ],
"question": { "question": {
"en": "Made after {search}" "en": "Made after {search}",
"de": "Erstellt nach {search}"
} }
} }
] ]
@ -591,7 +617,8 @@
} }
], ],
"question": { "question": {
"en": "User language (iso-code) {search}" "en": "User language (iso-code) {search}",
"de": "Benutzersprache (ISO-Code) {search}"
} }
} }
] ]
@ -607,7 +634,8 @@
} }
], ],
"question": { "question": {
"en": "Made with host {search}" "en": "Made with host {search}",
"de": "Erstellt mit Host {search}"
} }
} }
] ]
@ -618,7 +646,8 @@
{ {
"osmTags": "add-image>0", "osmTags": "add-image>0",
"question": { "question": {
"en": "Changeset added at least one image" "en": "Changeset added at least one image",
"de": "Änderungssatz hat mindestens ein Bild hinzugefügt"
} }
} }
] ]
@ -629,7 +658,8 @@
{ {
"osmTags": "theme!=grb", "osmTags": "theme!=grb",
"question": { "question": {
"en": "Exclude GRB theme" "en": "Exclude GRB theme",
"de": "GRB-Thema ausschließen"
} }
} }
] ]
@ -640,7 +670,8 @@
{ {
"osmTags": "theme!=etymology", "osmTags": "theme!=etymology",
"question": { "question": {
"en": "Exclude etymology theme" "en": "Exclude etymology theme",
"de": "Etymologie-Thema ausschließen"
} }
} }
] ]
@ -655,7 +686,8 @@
{ {
"id": "link_to_more", "id": "link_to_more",
"render": { "render": {
"en": "More statistics can be found <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>here</a>" "en": "More statistics can be found <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>here</a>",
"de": "Weitere Statistiken gibt es <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>hier</a>"
} }
}, },
{ {

View file

@ -288,6 +288,7 @@
"loadingTheme": "Loading {theme}…", "loadingTheme": "Loading {theme}…",
"loginFailed": "Logging in into OpenStreetMap failed", "loginFailed": "Logging in into OpenStreetMap failed",
"loginFailedOfflineMode": "OpenStreetMap.org is currently not available due to maintenance. Making edits will be possible soon", "loginFailedOfflineMode": "OpenStreetMap.org is currently not available due to maintenance. Making edits will be possible soon",
"loginFailedOfflineOther": "OpenStreetMap.org could not be reached",
"loginFailedReadonlyMode": "OpenStreetMap.org is currently in readonly mode due to maintenance. Making edits will be possible soon", "loginFailedReadonlyMode": "OpenStreetMap.org is currently in readonly mode due to maintenance. Making edits will be possible soon",
"loginFailedUnreachableMode": "OpenStreetMap.org is currently not reachable. Are you connected to the internet or do you block third parties? Try again later", "loginFailedUnreachableMode": "OpenStreetMap.org is currently not reachable. Are you connected to the internet or do you block third parties? Try again later",
"loginOnlyNeededToEdit": "if you want to make changes", "loginOnlyNeededToEdit": "if you want to make changes",

View file

@ -25,6 +25,7 @@
"#summary_server": "Should be the endpoint; appending status.json should work", "#summary_server": "Should be the endpoint; appending status.json should work",
"summary_server": "https://cache.mapcomplete.org/", "summary_server": "https://cache.mapcomplete.org/",
"geoip_server": "https://ipinfo.mapcomplete.org/", "geoip_server": "https://ipinfo.mapcomplete.org/",
"error_server": "https://report.mapcomplete.org/report",
"disabled:oauth_credentials": { "disabled:oauth_credentials": {
"##": "DEV", "##": "DEV",
"#": "This client-id is registered by 'MapComplete' on https://master.apis.dev.openstreetmap.org/", "#": "This client-id is registered by 'MapComplete' on https://master.apis.dev.openstreetmap.org/",

View file

@ -1229,14 +1229,14 @@ video {
height: 6rem; height: 6rem;
} }
.h-screen {
height: 100vh;
}
.h-full { .h-full {
height: 100%; height: 100%;
} }
.h-screen {
height: 100vh;
}
.h-fit { .h-fit {
height: -webkit-fit-content; height: -webkit-fit-content;
height: -moz-fit-content; height: -moz-fit-content;

View file

@ -43,6 +43,7 @@ export class Changes {
private readonly previouslyCreated: OsmObject[] = [] private readonly previouslyCreated: OsmObject[] = []
private readonly _leftRightSensitive: boolean private readonly _leftRightSensitive: boolean
private readonly _changesetHandler: ChangesetHandler private readonly _changesetHandler: ChangesetHandler
private readonly _reportError?: (string: string | Error) => void
constructor( constructor(
state: { state: {
@ -53,7 +54,8 @@ export class Changes {
historicalUserLocations?: FeatureSource historicalUserLocations?: FeatureSource
featureSwitches?: FeatureSwitchState featureSwitches?: FeatureSwitchState
}, },
leftRightSensitive: boolean = false leftRightSensitive: boolean = false,
reportError?: (string: string | Error) => void,
) { ) {
this._leftRightSensitive = leftRightSensitive this._leftRightSensitive = leftRightSensitive
// We keep track of all changes just as well // We keep track of all changes just as well
@ -62,11 +64,13 @@ export class Changes {
this._nextId = Math.min(-1, ...(this.pendingChanges.data?.map((pch) => pch.id) ?? [])) this._nextId = Math.min(-1, ...(this.pendingChanges.data?.map((pch) => pch.id) ?? []))
this.state = state this.state = state
this.backend = state.osmConnection.Backend() this.backend = state.osmConnection.Backend()
this._reportError = reportError
this._changesetHandler = new ChangesetHandler( this._changesetHandler = new ChangesetHandler(
state.dryRun, state.dryRun,
state.osmConnection, state.osmConnection,
state.featurePropertiesStore, state.featurePropertiesStore,
this this,
e => this._reportError(e)
) )
this.historicalUserLocations = state.historicalUserLocations this.historicalUserLocations = state.historicalUserLocations
@ -234,6 +238,7 @@ export class Changes {
console.log("Changes flushed. Your changeset is " + csNumber) console.log("Changes flushed. Your changeset is " + csNumber)
this.errors.setData([]) this.errors.setData([])
} catch (e) { } catch (e) {
this._reportError(e)
this.isUploading.setData(false) this.isUploading.setData(false)
this.errors.data.push(e) this.errors.data.push(e)
this.errors.ping() this.errors.ping()
@ -518,6 +523,9 @@ export class Changes {
const osmObj = await downloader.DownloadObjectAsync(id, 0) const osmObj = await downloader.DownloadObjectAsync(id, 0)
return { id, osmObj } return { id, osmObj }
} catch (e) { } catch (e) {
this._reportError( "Could not download OSM-object"+
id+
" dropping it from the changes (" + e + ")")
console.error( console.error(
"Could not download OSM-object", "Could not download OSM-object",
id, id,
@ -685,6 +693,7 @@ export class Changes {
} }
return result return result
} catch (e) { } catch (e) {
this._reportError(e)
console.error("Could not upload some changes:", e) console.error("Could not upload some changes:", e)
this.errors.data.push(e) this.errors.data.push(e)
this.errors.ping() this.errors.ping()

View file

@ -26,6 +26,7 @@ export class ChangesetHandler {
* @private * @private
*/ */
private readonly _remappings = new Map<string, string>() private readonly _remappings = new Map<string, string>()
private readonly _reportError: (e: (string | Error)) => void
constructor( constructor(
dryRun: Store<boolean>, dryRun: Store<boolean>,
@ -34,9 +35,11 @@ export class ChangesetHandler {
| FeaturePropertiesStore | FeaturePropertiesStore
| { addAlias: (id0: string, id1: string) => void } | { addAlias: (id0: string, id1: string) => void }
| undefined, | undefined,
changes: Changes changes: Changes,
reportError: (e: string | Error) => void
) { ) {
this.osmConnection = osmConnection this.osmConnection = osmConnection
this._reportError = reportError
this.allElements = <FeaturePropertiesStore>allElements this.allElements = <FeaturePropertiesStore>allElements
this.changes = changes this.changes = changes
this._dryRun = dryRun this._dryRun = dryRun
@ -148,8 +151,13 @@ export class ChangesetHandler {
await this.UpdateTags(csId, extraMetaTags) await this.UpdateTags(csId, extraMetaTags)
} }
} catch (e) { } catch (e) {
console.error("Could not open/upload changeset due to ", e) if(this._reportError){
this._reportError(e)
}
console.warn("Could not open/upload changeset due to ", e,"trying t")
openChangeset.setData(undefined) openChangeset.setData(undefined)
throw e
} }
} else { } else {
// There still exists an open changeset (or at least we hope so) // There still exists an open changeset (or at least we hope so)
@ -178,8 +186,12 @@ export class ChangesetHandler {
) )
await this.UpdateTags(csId, rewrittenTags) await this.UpdateTags(csId, rewrittenTags)
} catch (e) { } catch (e) {
if(this._reportError){
this._reportError(e)
}
console.warn("Could not upload, changeset is probably closed: ", e) console.warn("Could not upload, changeset is probably closed: ", e)
openChangeset.setData(undefined) openChangeset.setData(undefined)
throw e
} }
} }
} }

View file

@ -164,6 +164,7 @@ export default class Constants {
*/ */
public static VectorTileServer: string | undefined = Constants.config.mvt_layer_server public static VectorTileServer: string | undefined = Constants.config.mvt_layer_server
public static GeoIpServer: string | undefined = Constants.config.geoip_server public static GeoIpServer: string | undefined = Constants.config.geoip_server
public static ErrorReportServer: string | undefined = Constants.config.error_server
public static readonly maptilerApiKey = "GvoVAJgu46I5rZapJuAy" public static readonly maptilerApiKey = "GvoVAJgu46I5rZapJuAy"
public static readonly SummaryServer: string = Constants.config.summary_server public static readonly SummaryServer: string = Constants.config.summary_server

View file

@ -2,11 +2,7 @@ import LayoutConfig from "./ThemeConfig/LayoutConfig"
import { SpecialVisualizationState } from "../UI/SpecialVisualization" import { SpecialVisualizationState } from "../UI/SpecialVisualization"
import { Changes } from "../Logic/Osm/Changes" import { Changes } from "../Logic/Osm/Changes"
import { Store, UIEventSource } from "../Logic/UIEventSource" import { Store, UIEventSource } from "../Logic/UIEventSource"
import { import { FeatureSource, IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"
FeatureSource,
IndexedFeatureSource,
WritableFeatureSource,
} from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection" import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { ExportableMap, MapProperties } from "./MapProperties" import { ExportableMap, MapProperties } from "./MapProperties"
import LayerState from "../Logic/State/LayerState" import LayerState from "../Logic/State/LayerState"
@ -50,9 +46,7 @@ import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter"
import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage"
import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"
import NoElementsInViewDetector, { import NoElementsInViewDetector, { FeatureViewState } from "../Logic/Actors/NoElementsInViewDetector"
FeatureViewState,
} from "../Logic/Actors/NoElementsInViewDetector"
import FilteredLayer from "./FilteredLayer" import FilteredLayer from "./FilteredLayer"
import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector" import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
@ -158,7 +152,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.featureSwitches = new FeatureSwitchState(layout) this.featureSwitches = new FeatureSwitchState(layout)
this.guistate = new MenuState( this.guistate = new MenuState(
this.featureSwitches.featureSwitchWelcomeMessage.data, this.featureSwitches.featureSwitchWelcomeMessage.data,
layout.id layout.id,
) )
this.map = new UIEventSource<MlMap>(undefined) this.map = new UIEventSource<MlMap>(undefined)
const geolocationState = new GeoLocationState() const geolocationState = new GeoLocationState()
@ -174,14 +168,14 @@ export default class ThemeViewState implements SpecialVisualizationState {
oauth_token: QueryParameters.GetQueryParameter( oauth_token: QueryParameters.GetQueryParameter(
"oauth_token", "oauth_token",
undefined, undefined,
"Used to complete the login" "Used to complete the login",
), ),
}) })
this.userRelatedState = new UserRelatedState( this.userRelatedState = new UserRelatedState(
this.osmConnection, this.osmConnection,
layout, layout,
this.featureSwitches, this.featureSwitches,
this.mapProperties this.mapProperties,
) )
this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => { this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => {
this.mapProperties.allowRotating.setData(fixated !== "yes") this.mapProperties.allowRotating.setData(fixated !== "yes")
@ -192,13 +186,13 @@ export default class ThemeViewState implements SpecialVisualizationState {
geolocationState, geolocationState,
this.selectedElement, this.selectedElement,
this.mapProperties, this.mapProperties,
this.userRelatedState.gpsLocationHistoryRetentionTime this.userRelatedState.gpsLocationHistoryRetentionTime,
) )
this.geolocationControl = new GeolocationControlState(this.geolocation, this.mapProperties) this.geolocationControl = new GeolocationControlState(this.geolocation, this.mapProperties)
this.availableLayers = AvailableRasterLayers.layersAvailableAt( this.availableLayers = AvailableRasterLayers.layersAvailableAt(
this.mapProperties.location, this.mapProperties.location,
this.osmConnection.isLoggedIn this.osmConnection.isLoggedIn,
) )
const self = this const self = this
@ -210,7 +204,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
const isDisplayed = QueryParameters.GetBooleanQueryParameter( const isDisplayed = QueryParameters.GetBooleanQueryParameter(
"overlay-" + rasterInfo.id, "overlay-" + rasterInfo.id,
rasterInfo.defaultState ?? true, rasterInfo.defaultState ?? true,
"Wether or not overlayer layer " + rasterInfo.id + " is shown" "Wether or not overlayer layer " + rasterInfo.id + " is shown",
) )
const state = { isDisplayed } const state = { isDisplayed }
overlayLayerStates.set(rasterInfo.id, state) overlayLayerStates.set(rasterInfo.id, state)
@ -235,7 +229,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.osmConnection.Backend(), this.osmConnection.Backend(),
(id) => self.layerState.filteredLayers.get(id).isDisplayed, (id) => self.layerState.filteredLayers.get(id).isDisplayed,
mvtAvailableLayers, mvtAvailableLayers,
this.fullNodeDatabase this.fullNodeDatabase,
) )
let currentViewIndex = 0 let currentViewIndex = 0
@ -253,7 +247,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
id: "current_view_" + currentViewIndex, id: "current_view_" + currentViewIndex,
}), }),
] ]
}) }),
) )
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds)
@ -270,19 +264,20 @@ export default class ThemeViewState implements SpecialVisualizationState {
historicalUserLocations: this.geolocation.historicalUserLocations, historicalUserLocations: this.geolocation.historicalUserLocations,
featureSwitches: this.featureSwitches, featureSwitches: this.featureSwitches,
}, },
layout?.isLeftRightSensitive() ?? false layout?.isLeftRightSensitive() ?? false,
e => this.reportError(e),
) )
this.historicalUserLocations = this.geolocation.historicalUserLocations this.historicalUserLocations = this.geolocation.historicalUserLocations
this.newFeatures = new NewGeometryFromChangesFeatureSource( this.newFeatures = new NewGeometryFromChangesFeatureSource(
this.changes, this.changes,
layoutSource, layoutSource,
this.featureProperties this.featureProperties,
) )
layoutSource.addSource(this.newFeatures) layoutSource.addSource(this.newFeatures)
const perLayer = new PerLayerFeatureSourceSplitter( const perLayer = new PerLayerFeatureSourceSplitter(
Array.from(this.layerState.filteredLayers.values()).filter( Array.from(this.layerState.filteredLayers.values()).filter(
(l) => l.layerDef?.source !== null (l) => l.layerDef?.source !== null,
), ),
new ChangeGeometryApplicator(this.indexedFeatures, this.changes), new ChangeGeometryApplicator(this.indexedFeatures, this.changes),
{ {
@ -293,10 +288,10 @@ export default class ThemeViewState implements SpecialVisualizationState {
"Got ", "Got ",
features.length, features.length,
"leftover features, such as", "leftover features, such as",
features[0].properties features[0].properties,
) )
}, },
} },
) )
this.perLayer = perLayer.perLayer this.perLayer = perLayer.perLayer
} }
@ -335,12 +330,12 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.lastClickObject = new LastClickFeatureSource( this.lastClickObject = new LastClickFeatureSource(
this.layout, this.layout,
this.mapProperties.lastClickLocation this.mapProperties.lastClickLocation,
) )
this.osmObjectDownloader = new OsmObjectDownloader( this.osmObjectDownloader = new OsmObjectDownloader(
this.osmConnection.Backend(), this.osmConnection.Backend(),
this.changes this.changes,
) )
this.perLayerFiltered = this.showNormalDataOn(this.map) this.perLayerFiltered = this.showNormalDataOn(this.map)
@ -351,7 +346,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
currentZoom: this.mapProperties.zoom, currentZoom: this.mapProperties.zoom,
layerState: this.layerState, layerState: this.layerState,
bounds: this.visualFeedbackViewportBounds, bounds: this.visualFeedbackViewportBounds,
} },
) )
this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView
this.imageUploadManager = new ImageUploadManager( this.imageUploadManager = new ImageUploadManager(
@ -359,7 +354,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
Imgur.singleton, Imgur.singleton,
this.featureProperties, this.featureProperties,
this.osmConnection, this.osmConnection,
this.changes this.changes,
) )
this.favourites = new FavouritesFeatureSource(this) this.favourites = new FavouritesFeatureSource(this)
@ -402,7 +397,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
LayoutSource.fromCacheZoomLevel, LayoutSource.fromCacheZoomLevel,
fs, fs,
this.featureProperties, this.featureProperties,
fs.layer.layerDef.maxAgeOfCache fs.layer.layerDef.maxAgeOfCache,
) )
toLocalStorage.set(layerId, storage) toLocalStorage.set(layerId, storage)
}) })
@ -415,7 +410,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
const doShowLayer = this.mapProperties.zoom.map( const doShowLayer = this.mapProperties.zoom.map(
(z) => (z) =>
(fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), (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) { if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) {
@ -432,7 +427,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
fs.layer, fs.layer,
fs, fs,
(id) => this.featureProperties.getStore(id), (id) => this.featureProperties.getStore(id),
this.layerState.globalFilters this.layerState.globalFilters,
) )
filteringFeatureSource.set(layerName, filtered) filteringFeatureSource.set(layerName, filtered)
@ -573,7 +568,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
return return
} }
this.selectClosestAtCenter(0) this.selectClosestAtCenter(0)
} },
) )
for (let i = 1; i < 9; i++) { for (let i = 1; i < 9; i++) {
@ -591,7 +586,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
onUp: true, onUp: true,
}, },
doc, doc,
() => this.selectClosestAtCenter(i - 1) () => this.selectClosestAtCenter(i - 1),
) )
} }
@ -608,7 +603,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
if (this.featureSwitches.featureSwitchBackgroundSelection.data) { if (this.featureSwitches.featureSwitchBackgroundSelection.data) {
this.guistate.backgroundLayerSelectionIsOpened.setData(true) this.guistate.backgroundLayerSelectionIsOpened.setData(true)
} }
} },
) )
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ {
@ -620,14 +615,14 @@ export default class ThemeViewState implements SpecialVisualizationState {
if (this.featureSwitches.featureSwitchFilter.data) { if (this.featureSwitches.featureSwitchFilter.data) {
this.guistate.openFilterView() this.guistate.openFilterView()
} }
} },
) )
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ shift: "O" }, { shift: "O" },
Translations.t.hotkeyDocumentation.selectMapnik, Translations.t.hotkeyDocumentation.selectMapnik,
() => { () => {
this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto) this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto)
} },
) )
const setLayerCategory = (category: EliCategory) => { const setLayerCategory = (category: EliCategory) => {
const available = this.availableLayers.data const available = this.availableLayers.data
@ -635,7 +630,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
const best = RasterLayerUtils.SelectBestLayerAccordingTo( const best = RasterLayerUtils.SelectBestLayerAccordingTo(
available, available,
category, category,
current.data current.data,
) )
console.log("Best layer for category", category, "is", best.properties.id) console.log("Best layer for category", category, "is", best.properties.id)
current.setData(best) current.setData(best)
@ -644,26 +639,26 @@ export default class ThemeViewState implements SpecialVisualizationState {
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ nomod: "O" }, { nomod: "O" },
Translations.t.hotkeyDocumentation.selectOsmbasedmap, Translations.t.hotkeyDocumentation.selectOsmbasedmap,
() => setLayerCategory("osmbasedmap") () => setLayerCategory("osmbasedmap"),
) )
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ nomod: "M" }, { nomod: "M" },
Translations.t.hotkeyDocumentation.selectMap, Translations.t.hotkeyDocumentation.selectMap,
() => setLayerCategory("map") () => setLayerCategory("map"),
) )
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ nomod: "P" }, { nomod: "P" },
Translations.t.hotkeyDocumentation.selectAerial, Translations.t.hotkeyDocumentation.selectAerial,
() => setLayerCategory("photo") () => setLayerCategory("photo"),
) )
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ nomod: "L" }, { nomod: "L" },
Translations.t.hotkeyDocumentation.geolocate, Translations.t.hotkeyDocumentation.geolocate,
() => { () => {
this.geolocationControl.handleClick() this.geolocationControl.handleClick()
} },
) )
return true return true
}) })
@ -675,7 +670,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
Translations.t.hotkeyDocumentation.translationMode, Translations.t.hotkeyDocumentation.translationMode,
() => { () => {
Locale.showLinkToWeblate.setData(!Locale.showLinkToWeblate.data) Locale.showLinkToWeblate.setData(!Locale.showLinkToWeblate.data)
} },
) )
} }
@ -686,7 +681,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
const normalLayers = this.layout.layers.filter( const normalLayers = this.layout.layers.filter(
(l) => (l) =>
Constants.priviliged_layers.indexOf(<any>l.id) < 0 && 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)) const maxzoom = Math.min(...normalLayers.map((l) => l.minzoom))
@ -694,7 +689,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
(l) => (l) =>
Constants.priviliged_layers.indexOf(<any>l.id) < 0 && Constants.priviliged_layers.indexOf(<any>l.id) < 0 &&
l.source.geojsonSource === undefined && l.source.geojsonSource === undefined &&
l.doCount l.doCount,
) )
const summaryTileSource = new SummaryTileSource( const summaryTileSource = new SummaryTileSource(
Constants.SummaryServer, Constants.SummaryServer,
@ -703,7 +698,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.mapProperties, this.mapProperties,
{ {
isActive: this.mapProperties.zoom.map((z) => z <= maxzoom), isActive: this.mapProperties.zoom.map((z) => z <= maxzoom),
} },
) )
return new SummaryTileSourceRewriter(summaryTileSource, this.layerState.filteredLayers) return new SummaryTileSourceRewriter(summaryTileSource, this.layerState.filteredLayers)
} }
@ -723,12 +718,12 @@ export default class ThemeViewState implements SpecialVisualizationState {
gps_location_history: this.geolocation.historicalUserLocations, gps_location_history: this.geolocation.historicalUserLocations,
gps_track: this.geolocation.historicalUserLocationsTrack, gps_track: this.geolocation.historicalUserLocationsTrack,
selected_element: new StaticFeatureSource( selected_element: new StaticFeatureSource(
this.selectedElement.map((f) => (f === undefined ? empty : [f])) this.selectedElement.map((f) => (f === undefined ? empty : [f])),
), ),
range: new StaticFeatureSource( range: new StaticFeatureSource(
this.mapProperties.maxbounds.map((bbox) => 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, current_view: this.currentView,
favourite: this.favourites, favourite: this.favourites,
@ -743,7 +738,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
ShowDataLayer.showRange( ShowDataLayer.showRange(
this.map, this.map,
new StaticFeatureSource([bbox.asGeoJson({ id: "range" })]), new StaticFeatureSource([bbox.asGeoJson({ id: "range" })]),
this.featureSwitches.featureSwitchIsTesting this.featureSwitches.featureSwitchIsTesting,
) )
} }
const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view") const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view")
@ -757,7 +752,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
currentViewLayer, currentViewLayer,
this.layout, this.layout,
this.osmObjectDownloader, this.osmObjectDownloader,
this.featureProperties this.featureProperties,
) )
}) })
} }
@ -801,20 +796,20 @@ export default class ThemeViewState implements SpecialVisualizationState {
const lastClickLayerConfig = new LayerConfig( const lastClickLayerConfig = new LayerConfig(
<LayerConfigJson>last_click_layerconfig, <LayerConfigJson>last_click_layerconfig,
"last_click" "last_click",
) )
const lastClickFiltered = const lastClickFiltered =
lastClickLayerConfig.isShown === undefined lastClickLayerConfig.isShown === undefined
? specialLayers.last_click ? specialLayers.last_click
: specialLayers.last_click.features.mapD((fs) => : specialLayers.last_click.features.mapD((fs) =>
fs.filter((f) => { fs.filter((f) => {
const matches = lastClickLayerConfig.isShown.matchesProperties( const matches = lastClickLayerConfig.isShown.matchesProperties(
f.properties f.properties,
) )
console.log("LastClick ", f, "matches", matches) console.log("LastClick ", f, "matches", matches)
return matches return matches
}) }),
) )
new ShowDataLayer(this.map, { new ShowDataLayer(this.map, {
features: new StaticFeatureSource(lastClickFiltered), features: new StaticFeatureSource(lastClickFiltered),
layer: lastClickLayerConfig, layer: lastClickLayerConfig,
@ -859,7 +854,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.mapProperties.rasterLayer, this.mapProperties.rasterLayer,
this.availableLayers, this.availableLayers,
this.featureSwitches.backgroundLayerId, this.featureSwitches.backgroundLayerId,
this.userRelatedState.preferredBackgroundLayer this.userRelatedState.preferredBackgroundLayer,
) )
} }
@ -867,4 +862,21 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.guistate.closeAll() this.guistate.closeAll()
this.selectedElement.setData(this.currentView.features?.data?.[0]) this.selectedElement.setData(this.currentView.features?.data?.[0])
} }
public async reportError(message: string | Error) {
console.log(">>> Reporting error to",Constants.ErrorReportServer, message)
let stacktrace: string = new Error().stack
await fetch(Constants.ErrorReportServer, {
method: "POST",
body: JSON.stringify({
stacktrace,
message: ""+message,
layout: this.layout.id,
username: this.osmConnection.userDetails.data?.name,
userid: this.osmConnection.userDetails.data?.uid,
pendingChanges: this.changes.pendingChanges.data,
}),
})
}
} }

View file

@ -7,6 +7,7 @@
import Tr from "./Tr.svelte" import Tr from "./Tr.svelte"
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource" import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
import Invalid from "../../assets/svg/Invalid.svelte" import Invalid from "../../assets/svg/Invalid.svelte"
import ArrowPath from "@babeard/svelte-heroicons/mini/ArrowPath"
export let state: { export let state: {
osmConnection: OsmConnection osmConnection: OsmConnection
@ -40,9 +41,13 @@
</slot> </slot>
{:else if !silentFail && $loadingStatus === "error"} {:else if !silentFail && $loadingStatus === "error"}
<slot name="error"> <slot name="error">
<div class="alert max-w-64 flex items-center"> <div class="alert max-w-64 flex items-center ">
<Invalid class="m-2 h-8 w-8 shrink-0" /> <Invalid class="m-2 h-8 w-8 shrink-0" />
<Tr t={offlineModes[$apiState]} /> <Tr t={offlineModes[$apiState] ?? t.loginFailedOfflineOther} />
<button class="justify-self-end" on:click={() => state.osmConnection.AttemptLogin()}>
<ArrowPath class="w-6 h-6"/>
Retry
</button>
</div> </div>
</slot> </slot>
{:else if $loadingStatus === "logged-in"} {:else if $loadingStatus === "logged-in"}

View file

@ -95,6 +95,7 @@ export interface SpecialVisualizationState {
readonly geolocation: GeoLocationHandler readonly geolocation: GeoLocationHandler
showCurrentLocationOn(map: Store<MlMap>): ShowDataLayer showCurrentLocationOn(map: Store<MlMap>): ShowDataLayer
reportError(message: string): Promise<void>
} }
export interface SpecialVisualization { export interface SpecialVisualization {