diff --git a/Docs/ServerConfig/hetzner/Caddyfile b/Docs/ServerConfig/hetzner/Caddyfile index 9d7516b4b..5164ad482 100644 --- a/Docs/ServerConfig/hetzner/Caddyfile +++ b/Docs/ServerConfig/hetzner/Caddyfile @@ -17,7 +17,7 @@ countrycoder.mapcomplete.org { report.mapcomplete.org { reverse_proxy { - to http://127.0.0.1:2600 + to http://127.0.0.1:2348 } } diff --git a/Docs/ServerConfig/hetzner/hetzner.txt b/Docs/ServerConfig/hetzner/hetzner.txt index 4009a6f92..fd39098f4 100644 --- a/Docs/ServerConfig/hetzner/hetzner.txt +++ b/Docs/ServerConfig/hetzner/hetzner.txt @@ -2,11 +2,49 @@ This server hosts the studio files and is used for expermintal builds. -For used hosts, see the Caddyfile +For used ports, see the Caddyfile + +To update caddy + +``` + cp Caddyfile /etc/caddy/ + # If caddy was running via a console instead of as a service, do `caddy stop` now + systemctl reload caddy +``` + +Debug logs with: `journalctl -u caddy --no-pager | less +G` + +Services: -## Cache forwarding +### studio + theme sync -As the ISP of Nerdlab is a bit picky, we use SSH-port-forwarding on the cache server: +The studio server, handling those requests. -`ssh -R 5445:127.0.0.1:5445 hetzner` +`npm run server:studio` + +Additionally, this runs syncthing to make a backup of all theme files. + +### LOD-server + +A server scraping other websites. + +`npm run server:ldjson` + +### Error report server + +A simple server logging everything it receives + +`npm run server:errorreport` + +### geo-ip + +Provides geolocation based on + + ``` + git clone https://github.com/pietervdvn/geoip-server + cd geoip-server + mkdir data + # Drop the databases from https://lite.ip2location.com/ in the data dir + npm run start + ``` diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index 71a45eb7f..b4818b4eb 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -1,13 +1,17 @@ { "id": "mapcomplete-changes", "title": { - "en": "Changes made with MapComplete" + "en": "Changes made with MapComplete", + "de": "Änderungen mit MapComplete" }, "shortDescription": { - "en": "Shows changes made by MapComplete" + "en": "Shows changes made by MapComplete", + "de": "Änderungen von MapComplete anzeigen" }, "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", "hideFromOverview": true, @@ -18,7 +22,9 @@ { "id": "mapcomplete-changes", "name": { - "en": "Changeset centers" + "en": "Changeset centers", + "de": "Zentrum der Änderungssätze", + "zh_Hant": "變更集中心" }, "minzoom": 0, "source": { @@ -28,41 +34,48 @@ }, "title": { "render": { - "en": "Changeset for {theme}" + "en": "Changeset for {theme}", + "de": "Änderungssatz für {theme}" } }, "description": { - "en": "Shows all MapComplete changes" + "en": "Shows all MapComplete changes", + "de": "Alle MapComplete-Änderungen anzeigen" }, "tagRenderings": [ { "id": "show_changeset_id", "render": { - "en": "Changeset {id}" + "en": "Changeset {id}", + "de": "Änderungssatz {id}" } }, { "id": "contributor", "question": { - "en": "What contributor did make this change?" + "en": "What contributor did make this change?", + "de": "Welcher Mitwirkende hat diese Änderung vorgenommen?" }, "freeform": { "key": "user" }, "render": { - "en": "Change made by {user}" + "en": "Change made by {user}", + "de": "Änderung vorgenommen von {user}" } }, { "id": "theme-id", "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": { "key": "theme" }, "render": { - "en": "Change with theme {theme}" + "en": "Change with theme {theme}", + "de": "Geändert mit Thema {theme}" } }, { @@ -71,19 +84,23 @@ "key": "locale" }, "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": { - "en": "User locale is {locale}" + "en": "User locale is {locale}", + "de": "Benutzersprache {locale}" } }, { "id": "host", "render": { - "en": "Change with with {host}" + "en": "Change with with {host}", + "de": "Änderung über {host}" }, "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": { "key": "host" @@ -104,10 +121,12 @@ { "id": "version", "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": { - "en": "Made with {editor}" + "en": "Made with {editor}", + "de": "Erstellt mit {editor}" }, "freeform": { "key": "editor" @@ -493,7 +512,9 @@ } ], "question": { - "en": "Themename contains {search}" + "en": "Themename contains {search}", + "de": "Themenname enthält {search}", + "pl": "Nazwa tematu zawiera {search}" } } ] @@ -509,7 +530,8 @@ } ], "question": { - "en": "Themename does not contain {search}" + "en": "Themename does not contain {search}", + "de": "Themename enthält not {search}" } } ] @@ -525,7 +547,8 @@ } ], "question": { - "en": "Made by contributor {search}" + "en": "Made by contributor {search}", + "de": "Erstellt vom Mitwirkenden {search}" } } ] @@ -541,7 +564,8 @@ } ], "question": { - "en": "Not made by contributor {search}" + "en": "Not made by contributor {search}", + "de": "Nicht erstellt von Mitwirkendem {search}" } } ] @@ -558,7 +582,8 @@ } ], "question": { - "en": "Made before {search}" + "en": "Made before {search}", + "de": "Erstellt vor {search}" } } ] @@ -575,7 +600,8 @@ } ], "question": { - "en": "Made after {search}" + "en": "Made after {search}", + "de": "Erstellt nach {search}" } } ] @@ -591,7 +617,8 @@ } ], "question": { - "en": "User language (iso-code) {search}" + "en": "User language (iso-code) {search}", + "de": "Benutzersprache (ISO-Code) {search}" } } ] @@ -607,7 +634,8 @@ } ], "question": { - "en": "Made with host {search}" + "en": "Made with host {search}", + "de": "Erstellt mit Host {search}" } } ] @@ -618,7 +646,8 @@ { "osmTags": "add-image>0", "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", "question": { - "en": "Exclude GRB theme" + "en": "Exclude GRB theme", + "de": "GRB-Thema ausschließen" } } ] @@ -640,7 +670,8 @@ { "osmTags": "theme!=etymology", "question": { - "en": "Exclude etymology theme" + "en": "Exclude etymology theme", + "de": "Etymologie-Thema ausschließen" } } ] @@ -655,7 +686,8 @@ { "id": "link_to_more", "render": { - "en": "More statistics can be found here" + "en": "More statistics can be found here", + "de": "Weitere Statistiken gibt es hier" } }, { diff --git a/langs/en.json b/langs/en.json index edcc20e7b..88cfb2e3b 100644 --- a/langs/en.json +++ b/langs/en.json @@ -288,6 +288,7 @@ "loadingTheme": "Loading {theme}…", "loginFailed": "Logging in into OpenStreetMap failed", "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", "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", diff --git a/package.json b/package.json index cd6be5755..48fa3a646 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "#summary_server": "Should be the endpoint; appending status.json should work", "summary_server": "https://cache.mapcomplete.org/", "geoip_server": "https://ipinfo.mapcomplete.org/", + "error_server": "https://report.mapcomplete.org/report", "disabled:oauth_credentials": { "##": "DEV", "#": "This client-id is registered by 'MapComplete' on https://master.apis.dev.openstreetmap.org/", @@ -114,8 +115,10 @@ "dloadVelopark": "vite-node scripts/velopark/veloParkToGeojson.ts ", "compareVelopark": "vite-node scripts/velopark/compare.ts -- velopark_nonsynced_.geojson ~/Projecten/OSM/Fietsberaad/2024-02-02\\ Fietsenstallingen_OSM_met_velopark_ref.geojson\n", "scrapeWebsites": "vite-node scripts/importscripts/compareWebsiteData.ts -- ~/Downloads/ShopsWithWebsiteNodes.csv ~/data/scraped_websites/", - "summary-server": "vite-node scripts/osm2pgsql/tilecountServer.ts", - "ldjson-server": "vite-node scripts/serverLdScrape.ts", + "server:summary": "vite-node scripts/osm2pgsql/tilecountServer.ts", + "server:ldjson": "vite-node scripts/serverLdScrape.ts", + "sever:studio": "vite-node scripts/studioServer -- /root/git/MapComplete/assets", + "server:errorreport": "vite-node scripts/serverErrorReport.ts -- /root/error_reports/", "generate:buildDbScript": "vite-node scripts/osm2pgsql/generateBuildDbScript.ts" }, "keywords": [ diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index ff5704e5d..7fe879a30 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -1229,14 +1229,14 @@ video { height: 6rem; } -.h-screen { - height: 100vh; -} - .h-full { height: 100%; } +.h-screen { + height: 100vh; +} + .h-fit { height: -webkit-fit-content; height: -moz-fit-content; diff --git a/scripts/server.ts b/scripts/server.ts index 9670433a2..df7205c2f 100644 --- a/scripts/server.ts +++ b/scripts/server.ts @@ -1,17 +1,36 @@ import http from "node:http" +export interface Handler { + mustMatch: string | RegExp + mimetype: string + addHeaders?: Record + handle: (path: string, queryParams: URLSearchParams, req: http.IncomingMessage) => Promise + +} + +class ServerUtils { + + public static getBody(req: http.IncomingMessage): Promise { + return new Promise((resolve) => { + let body = ''; + req.on('data', (chunk) => { + body += chunk; + }); + req.on('end', () => { + resolve(body) + }); + }) + } + +} + export class Server { constructor( port: number, options: { ignorePathPrefix?: string[] }, - handle: { - mustMatch: string | RegExp - mimetype: string - addHeaders?: Record - handle: (path: string, queryParams: URLSearchParams) => Promise - }[] + handle: Handler[] ) { handle.push({ mustMatch: "", @@ -81,8 +100,9 @@ export class Server { res.end() return } + let body = undefined if (req.method === "POST" || req.method === "UPDATE") { - return + body = await ServerUtils.getBody(req) } if (req.method === "DELETE") { @@ -90,7 +110,7 @@ export class Server { } try { - const result = await handler.handle(path, url.searchParams) + const result = await handler.handle(path, url.searchParams, req, body) if (result === undefined) { res.writeHead(500) res.write("Could not fetch this website, probably blocked by them") diff --git a/scripts/serverErrorReport.ts b/scripts/serverErrorReport.ts new file mode 100644 index 000000000..cbe6055c9 --- /dev/null +++ b/scripts/serverErrorReport.ts @@ -0,0 +1,52 @@ +import { Handler, Server } from "./server" +import Script from "./Script" +import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "fs" +import { mkdir } from "node:fs" +import ScriptUtils from "./ScriptUtils" + +class ServerErrorReport extends Script { + constructor() { + super("A server which receives and logs error reports") + } + + async main(args: string[]): Promise { + const logDirectory = args[0] ?? "./error_logs" + console.log("Logging to directory", logDirectory) + if (!existsSync(logDirectory)) { + mkdirSync(logDirectory) + console.log("Created this directory") + } + let errorReport = 0 + new Server(2348, {}, + [{ + mustMatch: "report", + mimetype: "application/json", + handle: async (_, queryParams, req, body) => { + if (!body) { + throw "{\"error\": \"No body; use a post request\"}" + } + console.log(body) + const ip = req.headers["x-forwarded-for"] + const d = new Date() + const date = d.toISOString() + const file = logDirectory + "/" + d.getUTCFullYear() + "_" + d.getUTCMonth() + "_" + d.getUTCDay() + ".lines.json" + try{ + body = JSON.parse(body) + }catch (e) { + // could not parse, we'll save it as is + } + const contents = "\n"+JSON.stringify({ ip, index: errorReport, date, message: body }) + if (!existsSync(file)) { + writeFileSync(file, contents) + } else { + appendFileSync(file, contents) + } + errorReport++ + return `{"status":"ok", "nr": ${errorReport}}` + }, + + }]) + } +} + +new ServerErrorReport().run() diff --git a/src/Logic/Osm/Changes.ts b/src/Logic/Osm/Changes.ts index 58ece3d2e..d59c1047f 100644 --- a/src/Logic/Osm/Changes.ts +++ b/src/Logic/Osm/Changes.ts @@ -43,6 +43,7 @@ export class Changes { private readonly previouslyCreated: OsmObject[] = [] private readonly _leftRightSensitive: boolean private readonly _changesetHandler: ChangesetHandler + private readonly _reportError?: (string: string | Error) => void constructor( state: { @@ -53,7 +54,8 @@ export class Changes { historicalUserLocations?: FeatureSource featureSwitches?: FeatureSwitchState }, - leftRightSensitive: boolean = false + leftRightSensitive: boolean = false, + reportError?: (string: string | Error) => void, ) { this._leftRightSensitive = leftRightSensitive // 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.state = state this.backend = state.osmConnection.Backend() + this._reportError = reportError this._changesetHandler = new ChangesetHandler( state.dryRun, state.osmConnection, state.featurePropertiesStore, - this + this, + e => this._reportError(e) ) this.historicalUserLocations = state.historicalUserLocations @@ -234,6 +238,7 @@ export class Changes { console.log("Changes flushed. Your changeset is " + csNumber) this.errors.setData([]) } catch (e) { + this._reportError(e) this.isUploading.setData(false) this.errors.data.push(e) this.errors.ping() @@ -518,6 +523,9 @@ export class Changes { const osmObj = await downloader.DownloadObjectAsync(id, 0) return { id, osmObj } } catch (e) { + this._reportError( "Could not download OSM-object"+ + id+ + " dropping it from the changes (" + e + ")") console.error( "Could not download OSM-object", id, @@ -685,6 +693,7 @@ export class Changes { } return result } catch (e) { + this._reportError(e) console.error("Could not upload some changes:", e) this.errors.data.push(e) this.errors.ping() diff --git a/src/Logic/Osm/ChangesetHandler.ts b/src/Logic/Osm/ChangesetHandler.ts index 2a6769264..72bd71383 100644 --- a/src/Logic/Osm/ChangesetHandler.ts +++ b/src/Logic/Osm/ChangesetHandler.ts @@ -26,6 +26,7 @@ export class ChangesetHandler { * @private */ private readonly _remappings = new Map() + private readonly _reportError: (e: (string | Error)) => void constructor( dryRun: Store, @@ -34,9 +35,11 @@ export class ChangesetHandler { | FeaturePropertiesStore | { addAlias: (id0: string, id1: string) => void } | undefined, - changes: Changes + changes: Changes, + reportError: (e: string | Error) => void ) { this.osmConnection = osmConnection + this._reportError = reportError this.allElements = allElements this.changes = changes this._dryRun = dryRun @@ -148,8 +151,13 @@ export class ChangesetHandler { await this.UpdateTags(csId, extraMetaTags) } } 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) + + throw e } } else { // There still exists an open changeset (or at least we hope so) @@ -178,8 +186,12 @@ export class ChangesetHandler { ) await this.UpdateTags(csId, rewrittenTags) } catch (e) { + if(this._reportError){ + this._reportError(e) + } console.warn("Could not upload, changeset is probably closed: ", e) openChangeset.setData(undefined) + throw e } } } diff --git a/src/Models/Constants.ts b/src/Models/Constants.ts index af61a7181..1557cbcea 100644 --- a/src/Models/Constants.ts +++ b/src/Models/Constants.ts @@ -164,6 +164,7 @@ export default class Constants { */ public static VectorTileServer: string | undefined = Constants.config.mvt_layer_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 SummaryServer: string = Constants.config.summary_server diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index fea45494b..4443f76a3 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -2,11 +2,7 @@ import LayoutConfig from "./ThemeConfig/LayoutConfig" import { SpecialVisualizationState } from "../UI/SpecialVisualization" import { Changes } from "../Logic/Osm/Changes" import { Store, UIEventSource } from "../Logic/UIEventSource" -import { - FeatureSource, - IndexedFeatureSource, - WritableFeatureSource, -} from "../Logic/FeatureSource/FeatureSource" +import { FeatureSource, IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" import { OsmConnection } from "../Logic/Osm/OsmConnection" import { ExportableMap, MapProperties } from "./MapProperties" import LayerState from "../Logic/State/LayerState" @@ -50,9 +46,7 @@ import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter" import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" -import NoElementsInViewDetector, { - FeatureViewState, -} from "../Logic/Actors/NoElementsInViewDetector" +import NoElementsInViewDetector, { FeatureViewState } from "../Logic/Actors/NoElementsInViewDetector" import FilteredLayer from "./FilteredLayer" import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector" import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" @@ -158,7 +152,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(undefined) const geolocationState = new GeoLocationState() @@ -174,14 +168,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") @@ -192,13 +186,13 @@ 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, ) const self = this @@ -210,7 +204,7 @@ export default class ThemeViewState implements SpecialVisualizationState { const isDisplayed = QueryParameters.GetBooleanQueryParameter( "overlay-" + rasterInfo.id, rasterInfo.defaultState ?? true, - "Wether or not overlayer layer " + rasterInfo.id + " is shown" + "Wether or not overlayer layer " + rasterInfo.id + " is shown", ) const state = { isDisplayed } overlayLayerStates.set(rasterInfo.id, state) @@ -235,7 +229,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.osmConnection.Backend(), (id) => self.layerState.filteredLayers.get(id).isDisplayed, mvtAvailableLayers, - this.fullNodeDatabase + this.fullNodeDatabase, ) let currentViewIndex = 0 @@ -253,7 +247,7 @@ export default class ThemeViewState implements SpecialVisualizationState { id: "current_view_" + currentViewIndex, }), ] - }) + }), ) this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) @@ -270,19 +264,20 @@ export default class ThemeViewState implements SpecialVisualizationState { historicalUserLocations: this.geolocation.historicalUserLocations, featureSwitches: this.featureSwitches, }, - layout?.isLeftRightSensitive() ?? false + layout?.isLeftRightSensitive() ?? false, + 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), { @@ -293,10 +288,10 @@ export default class ThemeViewState implements SpecialVisualizationState { "Got ", features.length, "leftover features, such as", - features[0].properties + features[0].properties, ) }, - } + }, ) this.perLayer = perLayer.perLayer } @@ -335,12 +330,12 @@ export default class ThemeViewState implements SpecialVisualizationState { this.lastClickObject = new LastClickFeatureSource( this.layout, - this.mapProperties.lastClickLocation + this.mapProperties.lastClickLocation, ) this.osmObjectDownloader = new OsmObjectDownloader( this.osmConnection.Backend(), - this.changes + this.changes, ) this.perLayerFiltered = this.showNormalDataOn(this.map) @@ -351,7 +346,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( @@ -359,7 +354,7 @@ export default class ThemeViewState implements SpecialVisualizationState { Imgur.singleton, this.featureProperties, this.osmConnection, - this.changes + this.changes, ) this.favourites = new FavouritesFeatureSource(this) @@ -402,7 +397,7 @@ export default class ThemeViewState implements SpecialVisualizationState { LayoutSource.fromCacheZoomLevel, fs, this.featureProperties, - fs.layer.layerDef.maxAgeOfCache + fs.layer.layerDef.maxAgeOfCache, ) toLocalStorage.set(layerId, storage) }) @@ -415,7 +410,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) { @@ -432,7 +427,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) @@ -573,7 +568,7 @@ export default class ThemeViewState implements SpecialVisualizationState { return } this.selectClosestAtCenter(0) - } + }, ) for (let i = 1; i < 9; i++) { @@ -591,7 +586,7 @@ export default class ThemeViewState implements SpecialVisualizationState { onUp: true, }, doc, - () => this.selectClosestAtCenter(i - 1) + () => this.selectClosestAtCenter(i - 1), ) } @@ -608,7 +603,7 @@ export default class ThemeViewState implements SpecialVisualizationState { if (this.featureSwitches.featureSwitchBackgroundSelection.data) { this.guistate.backgroundLayerSelectionIsOpened.setData(true) } - } + }, ) Hotkeys.RegisterHotkey( { @@ -620,14 +615,14 @@ 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 available = this.availableLayers.data @@ -635,7 +630,7 @@ export default class ThemeViewState implements SpecialVisualizationState { const best = RasterLayerUtils.SelectBestLayerAccordingTo( available, category, - current.data + current.data, ) console.log("Best layer for category", category, "is", best.properties.id) current.setData(best) @@ -644,26 +639,26 @@ 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( { nomod: "L" }, Translations.t.hotkeyDocumentation.geolocate, () => { this.geolocationControl.handleClick() - } + }, ) return true }) @@ -675,7 +670,7 @@ export default class ThemeViewState implements SpecialVisualizationState { Translations.t.hotkeyDocumentation.translationMode, () => { Locale.showLinkToWeblate.setData(!Locale.showLinkToWeblate.data) - } + }, ) } @@ -686,7 +681,7 @@ export default class ThemeViewState implements SpecialVisualizationState { const normalLayers = this.layout.layers.filter( (l) => Constants.priviliged_layers.indexOf(l.id) < 0 && - !l.id.startsWith("note_import") + !l.id.startsWith("note_import"), ) const maxzoom = Math.min(...normalLayers.map((l) => l.minzoom)) @@ -694,7 +689,7 @@ export default class ThemeViewState implements SpecialVisualizationState { (l) => Constants.priviliged_layers.indexOf(l.id) < 0 && l.source.geojsonSource === undefined && - l.doCount + l.doCount, ) const summaryTileSource = new SummaryTileSource( Constants.SummaryServer, @@ -703,7 +698,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.mapProperties, { isActive: this.mapProperties.zoom.map((z) => z <= maxzoom), - } + }, ) return new SummaryTileSourceRewriter(summaryTileSource, this.layerState.filteredLayers) } @@ -723,12 +718,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 : [bbox.asGeoJson({ id: "range" })] - ) + bbox === undefined ? empty : [bbox.asGeoJson({ id: "range" })], + ), ), current_view: this.currentView, favourite: this.favourites, @@ -743,7 +738,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") @@ -757,7 +752,7 @@ export default class ThemeViewState implements SpecialVisualizationState { currentViewLayer, this.layout, this.osmObjectDownloader, - this.featureProperties + this.featureProperties, ) }) } @@ -801,20 +796,20 @@ export default class ThemeViewState implements SpecialVisualizationState { const lastClickLayerConfig = new LayerConfig( 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.log("LastClick ", f, "matches", matches) - return matches - }) - ) + fs.filter((f) => { + const matches = lastClickLayerConfig.isShown.matchesProperties( + f.properties, + ) + console.log("LastClick ", f, "matches", matches) + return matches + }), + ) new ShowDataLayer(this.map, { features: new StaticFeatureSource(lastClickFiltered), layer: lastClickLayerConfig, @@ -859,7 +854,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.mapProperties.rasterLayer, this.availableLayers, this.featureSwitches.backgroundLayerId, - this.userRelatedState.preferredBackgroundLayer + this.userRelatedState.preferredBackgroundLayer, ) } @@ -867,4 +862,21 @@ export default class ThemeViewState implements SpecialVisualizationState { this.guistate.closeAll() 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, + }), + }) + } } diff --git a/src/UI/Base/LoginToggle.svelte b/src/UI/Base/LoginToggle.svelte index 3192e5acb..288f30543 100644 --- a/src/UI/Base/LoginToggle.svelte +++ b/src/UI/Base/LoginToggle.svelte @@ -7,6 +7,7 @@ import Tr from "./Tr.svelte" import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource" import Invalid from "../../assets/svg/Invalid.svelte" + import ArrowPath from "@babeard/svelte-heroicons/mini/ArrowPath" export let state: { osmConnection: OsmConnection @@ -40,9 +41,13 @@ {:else if !silentFail && $loadingStatus === "error"} -
+
- + +
{:else if $loadingStatus === "logged-in"} diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index 53cd865fa..4b7d850eb 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -95,6 +95,7 @@ export interface SpecialVisualizationState { readonly geolocation: GeoLocationHandler showCurrentLocationOn(map: Store): ShowDataLayer + reportError(message: string): Promise } export interface SpecialVisualization {