diff --git a/UI/BigComponents/CopyrightPanel.ts b/UI/BigComponents/CopyrightPanel.ts index d88b1a36f..187d21e36 100644 --- a/UI/BigComponents/CopyrightPanel.ts +++ b/UI/BigComponents/CopyrightPanel.ts @@ -19,9 +19,82 @@ import Loc from "../../Models/Loc"; import Toggle from "../Input/Toggle"; import {OsmConnection} from "../../Logic/Osm/OsmConnection"; import Constants from "../../Models/Constants"; -import PrivacyPolicy from "./PrivacyPolicy"; import ContributorCount from "../../Logic/ContributorCount"; +export class OpenIdEditor extends VariableUiElement { + constructor(state : {locationControl: UIEventSource}, iconStyle? : string, objectId?: string) { + const t = Translations.t.general.attribution + super(state.locationControl.map(location => { + let elementSelect = ""; + if(objectId !== undefined){ + const parts = objectId.split("/") + const tp = parts[0] + if(parts.length === 2 && !isNaN(Number(parts[1])) && (tp === "node" || tp === "way" || tp === "relation")){ + elementSelect = "&"+ tp+"="+parts[1] + } + } + const idLink = `https://www.openstreetmap.org/edit?editor=id${elementSelect}#map=${location?.zoom ?? 0}/${location?.lat ?? 0}/${location?.lon ?? 0}` + return new SubtleButton(Svg.pencil_ui().SetStyle(iconStyle), t.editId, {url: idLink, newTab: true}) + })); + } + +} + +export class OpenMapillary extends VariableUiElement { + constructor(state : {locationControl: UIEventSource}, iconStyle? : string) { + const t = Translations.t.general.attribution + super( state.locationControl.map(location => { + const mapillaryLink = `https://www.mapillary.com/app/?focus=map&lat=${location?.lat ?? 0}&lng=${location?.lon ?? 0}&z=${Math.max((location?.zoom ?? 2) - 1, 1)}` + return new SubtleButton(Svg.mapillary_black_ui().SetStyle(iconStyle), t.openMapillary, { + url: mapillaryLink, + newTab: true + }) + })) + } +} + +export class OpenJosm extends Combine { + + constructor(state : {osmConnection: OsmConnection, currentBounds: UIEventSource,}, iconStyle? : string) { + const t = Translations.t.general.attribution + + const josmState = new UIEventSource(undefined) + // Reset after 15s + josmState.stabilized(15000).addCallbackD(_ => josmState.setData(undefined)) + + const stateIndication = new VariableUiElement(josmState.map(state => { + if (state === undefined) { + return undefined + } + state = state.toUpperCase() + if (state === "OK") { + return t.josmOpened.SetClass("thanks") + } + return t.josmNotOpened.SetClass("alert") + })); + + const toggle = new Toggle( + new SubtleButton(Svg.josm_logo_ui().SetStyle(iconStyle), t.editJosm).onClick(() => { + const bounds: any = state.currentBounds.data; + if (bounds === undefined) { + return undefined + } + const top = bounds.getNorth(); + const bottom = bounds.getSouth(); + const right = bounds.getEast(); + const left = bounds.getWest(); + const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}` + Utils.download(josmLink).then(answer => josmState.setData(answer.replace(/\n/g, '').trim())).catch(_ => josmState.setData("ERROR")) + }), undefined, state.osmConnection.userDetails.map(ud => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible)) + + super([stateIndication, toggle]); + + } + + +} + + /** * The attribution panel shown on mobile */ @@ -39,10 +112,7 @@ export default class CopyrightPanel extends Combine { const t = Translations.t.general.attribution const layoutToUse = state.layoutToUse - const josmState = new UIEventSource(undefined) - // Reset after 15s - josmState.stabilized(15000).addCallbackD(_ => josmState.setData(undefined)) - const iconStyle = "height: 1.5rem; width: auto" + const iconStyle = "height: 1.5rem; width: auto" const actionButtons = [ new SubtleButton(Svg.liberapay_ui().SetStyle(iconStyle), t.donate, { url: "https://liberapay.com/pietervdvn/", @@ -56,42 +126,9 @@ export default class CopyrightPanel extends Combine { url: Utils.OsmChaLinkFor(31, state.layoutToUse.id), newTab: true }), - new VariableUiElement(state.locationControl.map(location => { - const idLink = `https://www.openstreetmap.org/edit?editor=id#map=${location?.zoom ?? 0}/${location?.lat ?? 0}/${location?.lon ?? 0}` - return new SubtleButton(Svg.pencil_ui().SetStyle(iconStyle), t.editId, {url: idLink, newTab: true}) - })), - - new VariableUiElement(state.locationControl.map(location => { - const mapillaryLink = `https://www.mapillary.com/app/?focus=map&lat=${location?.lat ?? 0}&lng=${location?.lon ?? 0}&z=${Math.max((location?.zoom ?? 2) - 1, 1)}` - return new SubtleButton(Svg.mapillary_black_ui().SetStyle(iconStyle), t.openMapillary, { - url: mapillaryLink, - newTab: true - }) - })), - new VariableUiElement(josmState.map(state => { - if (state === undefined) { - return undefined - } - state = state.toUpperCase() - if (state === "OK") { - return t.josmOpened.SetClass("thanks") - } - return t.josmNotOpened.SetClass("alert") - })), - new Toggle( - new SubtleButton(Svg.josm_logo_ui().SetStyle(iconStyle), t.editJosm).onClick(() => { - const bounds: any = state.currentBounds.data; - if (bounds === undefined) { - return undefined - } - const top = bounds.getNorth(); - const bottom = bounds.getSouth(); - const right = bounds.getEast(); - const left = bounds.getWest(); - const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}` - Utils.download(josmLink).then(answer => josmState.setData(answer.replace(/\n/g, '').trim())).catch(_ => josmState.setData("ERROR")) - }), undefined, state.osmConnection.userDetails.map(ud => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible)), - + new OpenIdEditor(state, iconStyle), + new OpenMapillary(state, iconStyle), + new OpenJosm(state, iconStyle) ] const iconAttributions = Utils.NoNull(Array.from(layoutToUse.ExtractImages())) diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index 4b6853cdf..9d637f7da 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -18,7 +18,6 @@ import {Utils} from "../../Utils"; import {SubstitutedTranslation} from "../SubstitutedTranslation"; import MoveWizard from "./MoveWizard"; import Toggle from "../Input/Toggle"; -import {FixedUiElement} from "../Base/FixedUiElement"; export default class FeatureInfoBox extends ScrollableFullScreen { @@ -189,8 +188,13 @@ export default class FeatureInfoBox extends ScrollableFullScreen { new VariableUiElement( State.state.featureSwitchIsDebugging.map(isDebugging => { if (isDebugging) { - const config: TagRenderingConfig = new TagRenderingConfig({render: "{all_tags()}"}, ""); - return new TagRenderingAnswer(tags, config, "all_tags") + const config_all_tags: TagRenderingConfig = new TagRenderingConfig({render: "{all_tags()}"}, ""); + const config_download: TagRenderingConfig = new TagRenderingConfig({render: "{export_as_geojson()}"}, ""); + const config_id: TagRenderingConfig = new TagRenderingConfig({render: "{open_in_iD()}"}, ""); + + return new Combine([new TagRenderingAnswer(tags, config_all_tags, "all_tags"), + new TagRenderingAnswer(tags, config_download, ""), + new TagRenderingAnswer(tags, config_id, "")]) } }) ) diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index e7d098da0..df2ff26d6 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -37,6 +37,7 @@ import FeaturePipelineState from "../Logic/State/FeaturePipelineState"; import {ConflateButton, ImportPointButton, ImportWayButton} from "./Popup/ImportButton"; import TagApplyButton from "./Popup/TagApplyButton"; import AutoApplyButton from "./Popup/AutoApplyButton"; +import {OpenIdEditor} from "./BigComponents/CopyrightPanel"; export interface SpecialVisualization { funcName: string, @@ -543,7 +544,7 @@ export default class SpecialVisualizations { const t = Translations.t.general.download; return new SubtleButton(Svg.download_ui(), - new Combine([t.downloadGpx.SetClass("font-bold text-lg"), + new Combine([t.downloadFeatureAsGpx.SetClass("font-bold text-lg"), t.downloadGpxHelper.SetClass("subtle")]).SetClass("flex flex-col") ).onClick(() => { console.log("Exporting as GPX!") @@ -560,6 +561,41 @@ export default class SpecialVisualizations { }) } }, + { + funcName: "export_as_geojson", + docs: "Exports the selected feature as GeoJson-file", + args: [], + constr: (state, tagSource, args) => { + const t = Translations.t.general.download; + + return new SubtleButton(Svg.download_ui(), + new Combine([t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"), + t.downloadGeoJsonHelper.SetClass("subtle")]).SetClass("flex flex-col") + ).onClick(() => { + console.log("Exporting as Geojson") + const tags = tagSource.data + const feature = state.allElements.ContainingFeatures.get(tags.id) + const matchingLayer = state?.layoutToUse?.getMatchingLayer(tags) + const title = matchingLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "geojson" + const data = JSON.stringify(feature, null, " "); + Utils.offerContentsAsDownloadableFile(data, title + "_mapcomplete_export.geojson", { + mimetype: "application/vnd.geo+json" + }) + + + }) + } + }, + { + funcName: "open_in_iD", + docs: "Opens the current view in the iD-editor", + args: [], + constr: (state, feature ) => { + return new OpenIdEditor(state, undefined, feature.data.id) + } + }, + + { funcName: "clear_location_history", docs: "A button to remove the travelled track information from the device", diff --git a/assets/layers/conflation/conflation.json b/assets/layers/conflation/conflation.json index 3ee868157..4661cbdd0 100644 --- a/assets/layers/conflation/conflation.json +++ b/assets/layers/conflation/conflation.json @@ -16,7 +16,7 @@ { "location": "point", "icon": { - "render": "addSmall:#000", + "render": "addSmall:#000", "mappings": [ { "if": "detach=yes", diff --git a/assets/layers/matchpoint/matchpoint.json b/assets/layers/matchpoint/matchpoint.json index 983ba8025..0a3d3080f 100644 --- a/assets/layers/matchpoint/matchpoint.json +++ b/assets/layers/matchpoint/matchpoint.json @@ -3,11 +3,16 @@ "description": "The default rendering for a locationInput which snaps onto another object", "source": { "osmTags": { - "and": []} + "and": [] + } }, - "mapRendering": [{ - "location": ["point","centroid"], - "icon": "./assets/svg/crosshair-empty.svg" - }] - + "mapRendering": [ + { + "location": [ + "point", + "centroid" + ], + "icon": "./assets/svg/crosshair-empty.svg" + } + ] } \ No newline at end of file diff --git a/assets/themes/grb_import/grb.json b/assets/themes/grb_import/grb.json index 6088c8292..6b2948807 100644 --- a/assets/themes/grb_import/grb.json +++ b/assets/themes/grb_import/grb.json @@ -99,8 +99,7 @@ "osmTags": "building~*", "maxCacheAge": 0 }, - "calculatedTags": [ - ], + "calculatedTags": [], "mapRendering": [ { "width": { @@ -319,7 +318,6 @@ "render": "Service road" }, "tagRenderings": [] - }, { "id": "generic_osm_object", @@ -475,7 +473,10 @@ "description": "Geometry which comes from GRB with tools to import them", "source": { "osmTags": { - "and": ["HUISNR~*","man_made!=mast"] + "and": [ + "HUISNR~*", + "man_made!=mast" + ] }, "geoJson": "https://betadata.grbosm.site/grb?bbox={x_min},{y_min},{x_max},{y_max}", "geoJsonZoomLevel": 18, @@ -508,7 +509,8 @@ "id": "Import-button", "render": "{import_way_button(OSM-buildings,building=$building;man_made=$man_made; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber; building:min_level=$_building:min_level, Upload this building to OpenStreetMap,,_is_part_of_building=true,1,_moveable=true)}", "mappings": [ - {"#": "Hide import button if intersection with other objects are detected", + { + "#": "Hide import button if intersection with other objects are detected", "if": "_intersects_with_other_features~*", "then": "This GRB building intersects with the following features: {_intersects_with_other_features}.
Fix the overlap and try again" }, @@ -555,7 +557,6 @@ "if": "_osm_obj:addr:housenumber~*", "then": "The overlapping building only has a housenumber known: {_osm_obj:addr:housenumber}" }, - { "if": "_osm_obj:id=", "then": "No overlapping OpenStreetMap-building found" diff --git a/langs/en.json b/langs/en.json index 6df801607..06d42922b 100644 --- a/langs/en.json +++ b/langs/en.json @@ -198,7 +198,8 @@ "downloadAsPdf": "Download a PDF of the current map", "downloadAsPdfHelper": "Ideal to print the current map", "downloadGeojson": "Download visible data as GeoJSON", - "downloadGpx": "Download as GPX-file", + "downloadFeatureAsGpx": "Download as GPX-file", + "downloadFeatureAsGeojson": "Download as GeoJson-file", "downloadGpxHelper": "A GPX-file can be used with most navigation devices and applications", "uploadGpx": "Upload your track to OpenStreetMap", "exporting": "Exporting…", diff --git a/test/TestHelper.ts b/test/TestHelper.ts index 3e31130f2..5910b9785 100644 --- a/test/TestHelper.ts +++ b/test/TestHelper.ts @@ -56,7 +56,7 @@ export default class T { * Returns an empty list if successful * @constructor */ - public Run(): ({ testsuite: string, name: string, msg: string } []) { + public Run(): { testsuite: string, name: string, msg: string } [] { const failures: { testsuite: string, name: string, msg: string } [] = [] for (const [name, test] of this._tests) { try {