diff --git a/Customizations/JSON/LayoutConfig.ts b/Customizations/JSON/LayoutConfig.ts index ab597ea5a7..ea595def9b 100644 --- a/Customizations/JSON/LayoutConfig.ts +++ b/Customizations/JSON/LayoutConfig.ts @@ -42,6 +42,7 @@ export default class LayoutConfig { public readonly enableGeolocation: boolean; public readonly enableBackgroundLayerSelection: boolean; public readonly enableShowAllQuestions: boolean; + public readonly enableExportButton: boolean; public readonly customCss?: string; /* How long is the cache valid, in seconds? @@ -152,6 +153,7 @@ export default class LayoutConfig { this.enableAddNewPoints = json.enableAddNewPoints ?? true; this.enableBackgroundLayerSelection = json.enableBackgroundLayerSelection ?? true; this.enableShowAllQuestions = json.enableShowAllQuestions ?? false; + this.enableExportButton = json.enableExportButton ?? false; this.customCss = json.customCss; this.cacheTimeout = json.cacheTimout ?? (60 * 24 * 60 * 60) diff --git a/Customizations/JSON/LayoutConfigJson.ts b/Customizations/JSON/LayoutConfigJson.ts index 374de70e07..d36a8463d6 100644 --- a/Customizations/JSON/LayoutConfigJson.ts +++ b/Customizations/JSON/LayoutConfigJson.ts @@ -15,6 +15,7 @@ import UnitConfigJson from "./UnitConfigJson"; * General remark: a type (string | any) indicates either a fixed or a translatable string. */ export interface LayoutConfigJson { + /** * The id of this layout. * @@ -335,4 +336,5 @@ export interface LayoutConfigJson { enableGeolocation?: boolean; enableBackgroundLayerSelection?: boolean; enableShowAllQuestions?: boolean; + enableExportButton?: boolean; } diff --git a/Docs/URL_Parameters.md b/Docs/URL_Parameters.md index 6f299adcf9..5c3158fd7e 100644 --- a/Docs/URL_Parameters.md +++ b/Docs/URL_Parameters.md @@ -20,126 +20,158 @@ the URL-parameters are stated in the part between the `?` and the `#`. There are Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case. - layer-control-toggle ----------------------- - - Whether or not the layer control is shown The default value is _false_ - - - tab ------ - - The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >50 changesets) The default value is _0_ - - - z ---- - - The initial/current zoom level The default value is _0_ - - - lat ------ - - The initial/current latitude The default value is _0_ - - - lon ------ - - The initial/current longitude of the app The default value is _0_ - - - fs-userbadge --------------- - - Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode. The default value is _true_ - - - fs-search ------------ - - Disables/Enables the search bar The default value is _true_ - - - fs-layers ------------ - - Disables/Enables the layer control The default value is _true_ - - - fs-add-new ------------- - - Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place) The default value is _true_ - - - fs-welcome-message --------------------- - - Disables/enables the help menu or welcome message The default value is _true_ - - - fs-iframe ------------ - - Disables/Enables the iframe-popup The default value is _false_ - - - fs-more-quests ----------------- - - Disables/Enables the 'More Quests'-tab in the welcome message The default value is _true_ - - - fs-share-screen ------------------ - - Disables/Enables the 'Share-screen'-tab in the welcome message The default value is _true_ - - - fs-geolocation ----------------- - - Disables/Enables the geolocation button The default value is _true_ - - - fs-all-questions ------------------- - - Always show all questions The default value is _false_ - - - test ------- - - If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org The default value is _false_ - - - debug -------- - - If true, shows some extra debugging help such as all the available tags on every object The default value is _false_ - - - backend +backend --------- - The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test' The default value is _osm_ +The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test' The default value is _osm_ - custom-css +test +------ + +If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org The default value is _false_ + + +layout +-------- + +The layout to load into MapComplete The default value is __ + + +userlayout ------------ - If specified, the custom css from the given link will be loaded additionaly The default value is __ +If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: + +- The hash of the URL contains a base64-encoded .json-file containing the theme definition +- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator +- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme The default value is _false_ - background +layer-control-toggle +---------------------- + +Whether or not the layer control is shown The default value is _false_ + + +tab +----- + +The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >50 changesets) The default value is _0_ + + +z +--- + +The initial/current zoom level The default value is _14_ + + +lat +----- + +The initial/current latitude The default value is _51.2095_ + + +lon +----- + +The initial/current longitude of the app The default value is _3.2228_ + + +fs-userbadge +-------------- + +Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode. The default value is _true_ + + +fs-search +----------- + +Disables/Enables the search bar The default value is _true_ + + +fs-layers +----------- + +Disables/Enables the layer control The default value is _true_ + + +fs-add-new ------------ - The id of the background layer to start with The default value is _osm_ +Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place) The default value is _true_ +fs-welcome-message +-------------------- + +Disables/enables the help menu or welcome message The default value is _true_ + + +fs-iframe +----------- + +Disables/Enables the iframe-popup The default value is _false_ + + +fs-more-quests +---------------- + +Disables/Enables the 'More Quests'-tab in the welcome message The default value is _true_ + + +fs-share-screen +----------------- + +Disables/Enables the 'Share-screen'-tab in the welcome message The default value is _true_ + + +fs-geolocation +---------------- + +Disables/Enables the geolocation button The default value is _true_ + + +fs-all-questions +------------------ + +Always show all questions The default value is _false_ + + +fs-export +----------- + +If set, enables the 'download'-button to download everything as geojson The default value is _false_ + + +fake-user +----------- + +If true, 'dryrun' mode is activated and a fake user account is loaded The default value is _false_ + + +debug +------- + +If true, shows some extra debugging help such as all the available tags on every object The default value is _false_ + + +custom-css +------------ + +If specified, the custom css from the given link will be loaded additionaly The default value is __ + + +background +------------ + +The id of the background layer to start with The default value is _osm_ + + +oauth_token +------------- + +Used to complete the login No default value set layer- ------------------ diff --git a/InitUiElements.ts b/InitUiElements.ts index f7c344d451..82b6de8ef7 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -427,6 +427,8 @@ export class InitUiElements { state.locationControl, state.selectedElement); + State.state.featurePipeline = source; + new ShowDataLayer(source.features, State.state.leafletMap, State.state.layoutToUse); const selectedFeatureHandler = new SelectedFeatureHandler(Hash.hash, State.state.selectedElement, source, State.state.osmApiFeatureSource); diff --git a/Logic/FeatureSource/FeatureSource.ts b/Logic/FeatureSource/FeatureSource.ts index ba568271ea..171db39f6a 100644 --- a/Logic/FeatureSource/FeatureSource.ts +++ b/Logic/FeatureSource/FeatureSource.ts @@ -1,9 +1,45 @@ import {UIEventSource} from "../UIEventSource"; +import {Utils} from "../../Utils"; export default interface FeatureSource { - features: UIEventSource<{feature: any, freshness: Date}[]>; + features: UIEventSource<{ feature: any, freshness: Date }[]>; /** * Mainly used for debuging */ name: string; +} + +export class FeatureSourceUtils { + + /** + * Exports given featurePipeline as a geojson FeatureLists (downloads as a json) + * @param featurePipeline The FeaturePipeline you want to export + * @param options The options object + * @param options.metadata True if you want to include the MapComplete metadata, false otherwise + */ + public static extractGeoJson(featurePipeline: FeatureSource, options: { metadata?: boolean } = {}) { + let defaults = { + metadata: false, + } + options = Utils.setDefaults(options, defaults); + + // Select all features, ignore the freshness and other data + let featureList: any[] = featurePipeline.features.data.map((feature) => feature.feature); + + if (!options.metadata) { + for (let i = 0; i < featureList.length; i++) { + let feature = featureList[i]; + for (let property in feature.properties) { + if (property[0] == "_") { + delete featureList[i]["properties"][property]; + } + } + } + } + return {type: "FeatureCollection", features: featureList} + + + } + + } \ No newline at end of file diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 37c8fa1d2e..92a0823f65 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -47,6 +47,7 @@ export class OsmConnection { public auth; public userDetails: UIEventSource; public isLoggedIn: UIEventSource + private fakeUser: boolean; _dryRun: boolean; public preferencesHandler: OsmPreferences; public changesetHandler: ChangesetHandler; @@ -59,12 +60,15 @@ export class OsmConnection { url: string }; - constructor(dryRun: boolean, oauth_token: UIEventSource, + constructor(dryRun: boolean, + fakeUser: boolean, + oauth_token: UIEventSource, // Used to keep multiple changesets open and to write to the correct changeset layoutName: string, singlePage: boolean = true, osmConfiguration: "osm" | "osm-test" = 'osm' ) { + this.fakeUser = fakeUser; this._singlePage = singlePage; this._oauth_config = OsmConnection.oauth_configs[osmConfiguration] ?? OsmConnection.oauth_configs.osm; console.debug("Using backend", this._oauth_config.url) @@ -72,7 +76,15 @@ export class OsmConnection { this._iframeMode = Utils.runningFromConsole ? false : window !== window.top; this.userDetails = new UIEventSource(new UserDetails(this._oauth_config.url), "userDetails"); - this.userDetails.data.dryRun = dryRun; + this.userDetails.data.dryRun = dryRun || fakeUser; + if(fakeUser){ + const ud = this.userDetails.data; + ud.csCount = 5678 + ud.loggedIn= true; + ud.unreadMessages = 0 + ud.name = "Fake user" + ud.totalMessages = 42; + } const self =this; this.isLoggedIn = this.userDetails.map(user => user.loggedIn).addCallback(isLoggedIn => { if(self.userDetails.data.loggedIn == false && isLoggedIn == true){ @@ -138,6 +150,10 @@ export class OsmConnection { } public AttemptLogin() { + if(this.fakeUser){ + console.log("AttemptLogin called, but ignored as fakeUser is set") + return; + } const self = this; console.log("Trying to log in..."); this.updateAuthObject(); diff --git a/State.ts b/State.ts index ec3c441da6..629af8e06c 100644 --- a/State.ts +++ b/State.ts @@ -20,6 +20,7 @@ import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader"; import {Relation} from "./Logic/Osm/ExtractRelations"; import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource"; import ChangeToElementsActor from "./Logic/Actors/ChangeToElementsActor"; +import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline"; /** * Contains the global state: a bunch of UI-event sources @@ -59,8 +60,8 @@ export default class State { public favouriteLayers: UIEventSource; public layerUpdater: OverpassFeatureSource; - - public osmApiFeatureSource : OsmApiFeatureSource ; + + public osmApiFeatureSource: OsmApiFeatureSource; public filteredLayers: UIEventSource<{ @@ -81,7 +82,7 @@ export default class State { * Keeps track of relations: which way is part of which other way? * Set by the overpass-updater; used in the metatagging */ - public readonly knownRelations = new UIEventSource>(undefined, "Relation memberships") + public readonly knownRelations = new UIEventSource>(undefined, "Relation memberships") public readonly featureSwitchUserbadge: UIEventSource; public readonly featureSwitchSearch: UIEventSource; @@ -96,6 +97,11 @@ export default class State { public readonly featureSwitchIsDebugging: UIEventSource; public readonly featureSwitchShowAllQuestions: UIEventSource; public readonly featureSwitchApiURL: UIEventSource; + public readonly featureSwitchEnableExport: UIEventSource; + public readonly featureSwitchFakeUser: UIEventSource; + + + public readonly featurePipeline: FeaturePipeline; /** @@ -126,7 +132,7 @@ export default class State { public welcomeMessageOpenedTab = QueryParameters.GetQueryParameter("tab", "0", `The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)`).map( str => isNaN(Number(str)) ? 0 : Number(str), [], n => "" + n ); - + constructor(layoutToUse: LayoutConfig) { const self = this; @@ -200,16 +206,22 @@ export default class State { "Disables/Enables the geolocation button"); this.featureSwitchShowAllQuestions = featSw("fs-all-questions", (layoutToUse) => layoutToUse?.enableShowAllQuestions ?? false, "Always show all questions"); + this.featureSwitchEnableExport = featSw("fs-export", (layoutToUse) => layoutToUse?.enableExportButton ?? false, + "If set, enables the 'download'-button to download everything as geojson") this.featureSwitchIsTesting = QueryParameters.GetQueryParameter("test", "false", "If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org") .map(str => str === "true", [], b => "" + b); - - this.featureSwitchIsDebugging = QueryParameters.GetQueryParameter("debug","false", + + this.featureSwitchFakeUser = QueryParameters.GetQueryParameter("fake-user", "false", + "If true, 'dryrun' mode is activated and a fake user account is loaded") + .map(str => str === "true", [], b => "" + b); + + this.featureSwitchIsDebugging = QueryParameters.GetQueryParameter("debug", "false", "If true, shows some extra debugging help such as all the available tags on every object") .map(str => str === "true", [], b => "" + b) - this.featureSwitchApiURL = QueryParameters.GetQueryParameter("backend","osm", + this.featureSwitchApiURL = QueryParameters.GetQueryParameter("backend", "osm", "The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'") } @@ -222,18 +234,19 @@ export default class State { this.backgroundLayerId = QueryParameters.GetQueryParameter("background", - layoutToUse?.defaultBackgroundId ?? "osm", - "The id of the background layer to start with") + layoutToUse?.defaultBackgroundId ?? "osm", + "The id of the background layer to start with") } - - - if(Utils.runningFromConsole){ + + + if (Utils.runningFromConsole) { return; } this.osmConnection = new OsmConnection( this.featureSwitchIsTesting.data, + this.featureSwitchFakeUser.data, QueryParameters.GetQueryParameter("oauth_token", undefined, "Used to complete the login"), layoutToUse?.id, @@ -249,7 +262,7 @@ export default class State { new ChangeToElementsActor(this.changes, this.allElements) this.osmApiFeatureSource = new OsmApiFeatureSource() - + new PendingChangesUploader(this.changes, this.selectedElement); this.mangroveIdentity = new MangroveIdentity( diff --git a/Svg.ts b/Svg.ts index 7c3d9022ad..ee1eb79556 100644 --- a/Svg.ts +++ b/Svg.ts @@ -94,7 +94,7 @@ export default class Svg { public static crosshair_empty_svg() { return new Img(Svg.crosshair_empty, true);} public static crosshair_empty_ui() { return new FixedUiElement(Svg.crosshair_empty_img);} - public static crosshair_locked = " image/svg+xml " + public static crosshair_locked = " image/svg+xml " public static crosshair_locked_img = Img.AsImageElement(Svg.crosshair_locked) public static crosshair_locked_svg() { return new Img(Svg.crosshair_locked, true);} public static crosshair_locked_ui() { return new FixedUiElement(Svg.crosshair_locked_img);} diff --git a/UI/Base/Minimap.ts b/UI/Base/Minimap.ts index 82c21ea5d5..a3b8ae001c 100644 --- a/UI/Base/Minimap.ts +++ b/UI/Base/Minimap.ts @@ -83,7 +83,7 @@ export default class Minimap extends BaseUIElement { doubleClickZoom: this._allowMoving, keyboard: this._allowMoving, touchZoom: this._allowMoving, - zoomAnimation: this._allowMoving, + // Disabling this breaks the geojson layer - don't ask me why! zoomAnimation: this._allowMoving, fadeAnimation: this._allowMoving }); diff --git a/UI/BigComponents/ExportDataButton.ts b/UI/BigComponents/ExportDataButton.ts new file mode 100644 index 0000000000..9a161de9fd --- /dev/null +++ b/UI/BigComponents/ExportDataButton.ts @@ -0,0 +1,21 @@ +import {SubtleButton} from "../Base/SubtleButton"; +import Svg from "../../Svg"; +import Translations from "../i18n/Translations"; +import State from "../../State"; +import {FeatureSourceUtils} from "../../Logic/FeatureSource/FeatureSource"; +import {Utils} from "../../Utils"; +import Combine from "../Base/Combine"; + +export class ExportDataButton extends Combine { + constructor() { + const t = Translations.t.general.download + const button = new SubtleButton(Svg.floppy_ui(), t.downloadGeojson.Clone().SetClass("font-bold")) + .onClick(() => { + const geojson = FeatureSourceUtils.extractGeoJson(State.state.featurePipeline) + const name = State.state.layoutToUse.data.id; + Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson), `MapComplete_${name}_export_${new Date().toISOString().substr(0,19)}.geojson`); + }) + + super([button, t.licenseInfo.Clone().SetClass("link-underline")]) + } +} \ No newline at end of file diff --git a/UI/BigComponents/LayerControlPanel.ts b/UI/BigComponents/LayerControlPanel.ts index 42a3eda125..c8837fbccc 100644 --- a/UI/BigComponents/LayerControlPanel.ts +++ b/UI/BigComponents/LayerControlPanel.ts @@ -2,11 +2,12 @@ import State from "../../State"; import BackgroundSelector from "./BackgroundSelector"; import LayerSelection from "./LayerSelection"; import Combine from "../Base/Combine"; -import {FixedUiElement} from "../Base/FixedUiElement"; import ScrollableFullScreen from "../Base/ScrollableFullScreen"; import Translations from "../i18n/Translations"; import {UIEventSource} from "../../Logic/UIEventSource"; import BaseUIElement from "../BaseUIElement"; +import Toggle from "../Input/Toggle"; +import {ExportDataButton} from "./ExportDataButton"; export default class LayerControlPanel extends ScrollableFullScreen { @@ -14,27 +15,34 @@ export default class LayerControlPanel extends ScrollableFullScreen { super(LayerControlPanel.GenTitle, LayerControlPanel.GeneratePanel, "layers", isShown); } - private static GenTitle():BaseUIElement { + private static GenTitle(): BaseUIElement { return Translations.t.general.layerSelection.title.Clone().SetClass("text-2xl break-words font-bold p-2") } - private static GeneratePanel() : BaseUIElement { - let layerControlPanel: BaseUIElement = new FixedUiElement(""); + private static GeneratePanel(): BaseUIElement { + const elements: BaseUIElement[] = [] + if (State.state.layoutToUse.data.enableBackgroundLayerSelection) { - layerControlPanel = new BackgroundSelector(); - layerControlPanel.SetStyle("margin:1em"); - layerControlPanel.onClick(() => { + const backgroundSelector = new BackgroundSelector(); + backgroundSelector.SetStyle("margin:1em"); + backgroundSelector.onClick(() => { }); + elements.push(backgroundSelector) } - if (State.state.filteredLayers.data.length > 1) { - const layerSelection = new LayerSelection(State.state.filteredLayers); - layerSelection.onClick(() => { - }); - layerControlPanel = new Combine([layerSelection, "
", layerControlPanel]); - } + elements.push(new Toggle( + new LayerSelection(State.state.filteredLayers), + undefined, + State.state.filteredLayers.map(layers => layers.length > 1) + )) - return layerControlPanel; + elements.push(new Toggle( + new ExportDataButton(), + undefined, + State.state.featureSwitchEnableExport + )) + + return new Combine(elements).SetClass("flex flex-col") } } \ No newline at end of file diff --git a/UI/BigComponents/LayerSelection.ts b/UI/BigComponents/LayerSelection.ts index e282947096..3c7f108e8c 100644 --- a/UI/BigComponents/LayerSelection.ts +++ b/UI/BigComponents/LayerSelection.ts @@ -74,7 +74,6 @@ export default class LayerSelection extends Combine { ); } - super(checkboxes) this.SetStyle("display:flex;flex-direction:column;") diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index 92a7e27e82..1bd09cb143 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -37,7 +37,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { .SetClass("break-words font-bold sm:p-0.5 md:p-1 sm:p-1.5 md:p-2"); const titleIcons = new Combine( layerConfig.titleIcons.map(icon => new TagRenderingAnswer(tags, icon, - "block w-8 h-8 align-baseline box-content sm:p-0.5") + "block w-8 h-8 align-baseline box-content sm:p-0.5", "width: 2rem;") )) .SetClass("flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2") diff --git a/UI/Popup/TagRenderingAnswer.ts b/UI/Popup/TagRenderingAnswer.ts index 6c8fd257ec..c8953dd011 100644 --- a/UI/Popup/TagRenderingAnswer.ts +++ b/UI/Popup/TagRenderingAnswer.ts @@ -16,31 +16,31 @@ export default class TagRenderingAnswer extends VariableUiElement { throw "Trying to generate a tagRenderingAnswer without configuration..." } super(tagsSource.map(tags => { - if(tags === undefined){ + if (tags === undefined) { return undefined; } - - if(configuration.condition){ - if(!configuration.condition.matchesProperties(tags)){ + + if (configuration.condition) { + if (!configuration.condition.matchesProperties(tags)) { return undefined; } } - - const trs = Utils.NoNull(configuration.GetRenderValues(tags)); - if(trs.length === 0){ - return undefined; - } - - const valuesToRender: BaseUIElement[] = trs.map(tr => new SubstitutedTranslation(tr, tagsSource)) - if(valuesToRender.length === 1){ - return valuesToRender[0]; - }else if(valuesToRender.length > 1){ - return new List(valuesToRender) - } - return undefined; - }).map((element : BaseUIElement) => element?.SetClass(contentClasses)?.SetStyle(contentStyle))) - this.SetClass("flex items-center flex-row text-lg link-underline tag-renering-answer") + const trs = Utils.NoNull(configuration.GetRenderValues(tags)); + if (trs.length === 0) { + return undefined; + } + + const valuesToRender: BaseUIElement[] = trs.map(tr => new SubstitutedTranslation(tr, tagsSource)) + if (valuesToRender.length === 1) { + return valuesToRender[0]; + } else if (valuesToRender.length > 1) { + return new List(valuesToRender) + } + return undefined; + }).map((element: BaseUIElement) => element?.SetClass(contentClasses)?.SetStyle(contentStyle))) + + this.SetClass("flex items-center flex-row text-lg link-underline") this.SetStyle("word-wrap: anywhere;"); } diff --git a/Utils.ts b/Utils.ts index da0672db18..af3970c3c4 100644 --- a/Utils.ts +++ b/Utils.ts @@ -461,5 +461,12 @@ export class Utils { b: parseInt(hex.substr(5, 2), 16), } } + + public static setDefaults(options, defaults){ + for (let key in defaults){ + if (!(key in options)) options[key] = defaults[key]; + } + return options; + } } diff --git a/assets/svg/crosshair-locked.svg b/assets/svg/crosshair-locked.svg index b1a741c287..d8d04340cd 100644 --- a/assets/svg/crosshair-locked.svg +++ b/assets/svg/crosshair-locked.svg @@ -25,9 +25,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="2.8284271" - inkscape:cx="67.47399" - inkscape:cy="29.788021" + inkscape:zoom="5.6568542" + inkscape:cx="27.044982" + inkscape:cy="77.667126" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" @@ -68,40 +68,39 @@ inkscape:groupmode="layer" id="layer1" transform="translate(0,-270.54165)"> - - - - - + id="g827"> + + inkscape:connector-curvature="0" + id="path817" + d="M 3.2841366,283.77082 H 1.0418969" + style="fill:none;stroke:#5555ec;stroke-width:2.09723878;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529" /> + + + + diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index 1ef8f94c8e..2d0848c432 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -162,6 +162,18 @@ "license": "CC0; trivial", "sources": [] }, + { + "authors": [], + "path": "crosshair-empty.svg", + "license": "CC0; trivial", + "sources": [] + }, + { + "authors": [], + "path": "crosshair-locked.svg", + "license": "CC0; trivial", + "sources": [] + }, { "authors": [ "Dave Gandy" @@ -204,7 +216,7 @@ "license": "CC0", "sources": [ "https://commons.wikimedia.org/wiki/File:Media-floppy.svg", - " http://tango.freedesktop.org/Tango_Desktop_Project" + "http://tango.freedesktop.org/Tango_Desktop_Project" ] }, { @@ -582,5 +594,15 @@ "sources": [ "https://www.mapillary.com/" ] + }, + { + "authors": [ + "The Tango! Desktop Project" + ], + "path": "floppy.svg", + "license": "CC0", + "sources": [ + "https://commons.wikimedia.org/wiki/File:Media-floppy.svg" + ] } ] \ No newline at end of file diff --git a/langs/en.json b/langs/en.json index e632a1378c..d29262f257 100644 --- a/langs/en.json +++ b/langs/en.json @@ -157,6 +157,10 @@ "zoomInToSeeThisLayer": "Zoom in to see this layer", "title": "Select layers" }, + "download": { + "downloadGeojson": "Download visible data as geojson", + "licenseInfo": "

Copyright notice

The provided is available under ODbL. Reusing this data is free for any purpose, but
  • the attribution © OpenStreetMap contributors
  • Any change to this data must be republished under the same license
. Please see the full copyright notice for details" + }, "weekdays": { "abbreviations": { "monday": "Mon", diff --git a/preferences.ts b/preferences.ts index 1c1773a143..a7ae07dedc 100644 --- a/preferences.ts +++ b/preferences.ts @@ -12,7 +12,7 @@ import BaseUIElement from "./UI/BaseUIElement"; import Table from "./UI/Base/Table"; -const connection = new OsmConnection(false, new UIEventSource(undefined), ""); +const connection = new OsmConnection(false, false, new UIEventSource(undefined), ""); let rendered = false; diff --git a/test/OsmConnection.spec.ts b/test/OsmConnection.spec.ts index ffcb4840ca..2253e56c38 100644 --- a/test/OsmConnection.spec.ts +++ b/test/OsmConnection.spec.ts @@ -15,7 +15,7 @@ export default class OsmConnectionSpec extends T { super("OsmConnectionSpec-test", [ ["login on dev", () => { - const osmConn = new OsmConnection(false, + const osmConn = new OsmConnection(false,false, new UIEventSource(undefined), "Unit test", true,