diff --git a/assets/layers/icons/icons.json b/assets/layers/icons/icons.json index 8ce8bed09b..c44e0136b9 100644 --- a/assets/layers/icons/icons.json +++ b/assets/layers/icons/icons.json @@ -292,6 +292,7 @@ } } }, + "metacondition": "_loggedIn=true", "mappings": [ { "if": "id~.*/-.*", diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index c4bc9ec1be..e06d8f696d 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -17,8 +17,7 @@ "description": "This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` and shows the button to upload new images", "render": { "*": "{image_carousel()}{image_upload()}" - }, - "classes": "my-4" + } }, { "id": "mapillary", diff --git a/langs/en.json b/langs/en.json index 14be62b56a..0b0862b328 100644 --- a/langs/en.json +++ b/langs/en.json @@ -200,6 +200,7 @@ "openIssueTracker": "File a bug", "openMapillary": "Open Mapillary here", "openOsmcha": "See latest edits made with {theme}", + "openOsmchaLastWeek": "See edits from the last 7 days", "openThemeDocumentation": "Open the documentation for thematic map {name}", "seeOnMapillary": "See this image on Mapillary", "themeBy": "Theme maintained by {author}", @@ -324,7 +325,7 @@ "opening_hours": { "all_days_from": "Opened every day {ranges}", "closed_permanently": "Closed for an unknown duration", - "closed_until": "Closed until {date}", + "closed_until": "Opens at {date}", "error": "Could not parse the opening hours", "error_loading": "Error: could not visualize these opening hours.", "friday": "On friday {ranges}", @@ -358,6 +359,7 @@ "versionInfo": "v{version} - generated on {date}" }, "pickLanguage": "Select language", + "poweredByMapComplete": "Powered by MapComplete - crowdsourced, thematic maps with OpenStreetMap", "poweredByOsm": "Powered by OpenStreetMap", "questionBox": { "answeredMultiple": "You answered {answered} questions", @@ -393,6 +395,7 @@ "searching": "Searching…" }, "searchAnswer": "Search an option…", + "seeIndex": "See the overview with all thematic maps", "share": "Share", "sharescreen": { "copiedToClipboard": "Link copied to clipboard", diff --git a/langs/layers/en.json b/langs/layers/en.json index 888625333e..e4515702d0 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -10754,6 +10754,26 @@ }, "question": "What accessibility features should be applied?" }, + "add-new-feature": { + "mappings": { + "0": { + "then": "Adding a new feature is done with the button at the bottom left. Clicking the map does nothing" + }, + "1": { + "then": "When clicking or tapping the map, a marker pops up where a new feature is added" + }, + "2": { + "then": "When right-clicking or long-pressing the map, a marker pops up where a new feature can be added" + }, + "3": { + "then": "When clicking or tapping the map, a marker pops up where a new feature can be added. Additionally, a button at the bottom left is shown" + }, + "4": { + "then": "When right-clicking or long-pressing the map, a marker pops up where a new feature can be added. Additionally, a button at the bottom left is shown" + } + }, + "question": "How should the menu to add a new feature be opened?" + }, "all-questions-at-once": { "mappings": { "0": { diff --git a/src/Logic/State/FeatureSwitchState.ts b/src/Logic/State/FeatureSwitchState.ts index ca2d505088..dd9c361e60 100644 --- a/src/Logic/State/FeatureSwitchState.ts +++ b/src/Logic/State/FeatureSwitchState.ts @@ -6,22 +6,26 @@ import { UIEventSource } from "../UIEventSource" import { QueryParameters } from "../Web/QueryParameters" import Constants from "../../Models/Constants" import { Utils } from "../../Utils" +import { Query } from "pg" class FeatureSwitchUtils { + /** Helper function to initialize feature switches + * + */ static initSwitch(key: string, deflt: boolean, documentation: string): UIEventSource { const defaultValue = deflt const queryParam = QueryParameters.GetQueryParameter( key, "" + defaultValue, documentation, - { stackOffset: -1 } + { stackOffset: -1 }, ) // It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened return queryParam.sync( (str) => (str === undefined ? defaultValue : str !== "false"), [], - (b) => (b == defaultValue ? undefined : "" + b) + (b) => (b == defaultValue ? undefined : "" + b), ) } } @@ -33,7 +37,7 @@ export class OsmConnectionFeatureSwitches { this.featureSwitchFakeUser = QueryParameters.GetBooleanQueryParameter( "fake-user", false, - "If true, 'dryrun' mode is activated and a fake user account is loaded" + "If true, 'dryrun' mode is activated and a fake user account is loaded", ) } } @@ -69,19 +73,41 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches { super() this.layoutToUse = layoutToUse - // Helper function to initialize feature switches + + const legacyRewrite: Record = { + "fs-userbadge": "fs-enable-login", + "fs-layers": ["fs-filter", "fs-background"], + + } + + for (const key in legacyRewrite) { + let intoList = legacyRewrite[key] + if (!QueryParameters.wasInitialized(key)) { + continue + } + if (typeof intoList === "string") { + intoList = [intoList] + } + for (const into of intoList) { + if (!QueryParameters.wasInitialized(into)) { + const v = QueryParameters.GetQueryParameter(key, "", "").data + console.log("Adding url param due to legacy:", key, "-->", into, "(", v + ")") + QueryParameters.GetQueryParameter(into, "", "").setData(v) + } + } + } this.featureSwitchEnableLogin = FeatureSwitchUtils.initSwitch( "fs-enable-login", layoutToUse?.enableUserBadge ?? true, - "Disables/Enables logging in and thus disables editing all together. This effectively puts MapComplete into read-only mode." + "Disables/Enables logging in and thus disables editing all together. This effectively puts MapComplete into read-only mode.", ) { if (QueryParameters.wasInitialized("fs-userbadge")) { // userbadge is the legacy name for 'enable-login' this.featureSwitchEnableLogin.setData( QueryParameters.GetBooleanQueryParameter("fs-userbadge", undefined, "Legacy") - .data + .data, ) } } @@ -89,60 +115,60 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches { this.featureSwitchSearch = FeatureSwitchUtils.initSwitch( "fs-search", layoutToUse?.enableSearch ?? true, - "Disables/Enables the search bar" + "Disables/Enables the search bar", ) this.featureSwitchBackgroundSelection = FeatureSwitchUtils.initSwitch( "fs-background", layoutToUse?.enableBackgroundLayerSelection ?? true, - "Disables/Enables the background layer control" + "Disables/Enables the background layer control where a user can enable e.g. aerial imagery", ) this.featureSwitchFilter = FeatureSwitchUtils.initSwitch( "fs-filter", layoutToUse?.enableLayers ?? true, - "Disables/Enables the filter view" + "Disables/Enables the filter view where a user can enable/disable MapComplete-layers or filter for certain properties", ) this.featureSwitchWelcomeMessage = FeatureSwitchUtils.initSwitch( "fs-welcome-message", true, - "Disables/enables the help menu or welcome message" + "Disables/enables the help menu or welcome message", ) this.featureSwitchCommunityIndex = FeatureSwitchUtils.initSwitch( "fs-community-index", this.featureSwitchEnableLogin.data, - "Disables/enables the button to get in touch with the community" + "Disables/enables the button to get in touch with the community", ) this.featureSwitchExtraLinkEnabled = FeatureSwitchUtils.initSwitch( "fs-iframe-popout", true, - "Disables/Enables the extraLink button. By default, if in iframe mode and the welcome message is hidden, a popout button to the full mapcomplete instance is shown instead (unless disabled with this switch or another extraLink button is enabled)" + "Disables/Enables the extraLink button. By default, if in iframe mode and the welcome message is hidden, a popout button to the full mapcomplete instance is shown instead (unless disabled with this switch or another extraLink button is enabled)", ) this.featureSwitchBackToThemeOverview = FeatureSwitchUtils.initSwitch( "fs-homepage-link", layoutToUse?.enableMoreQuests ?? true, - "Disables/Enables the various links which go back to the index page with the theme overview" + "Disables/Enables the various links which go back to the index page with the theme overview", ) this.featureSwitchShareScreen = FeatureSwitchUtils.initSwitch( "fs-share-screen", layoutToUse?.enableShareScreen ?? true, - "Disables/Enables the 'Share-screen'-tab in the welcome message" + "Disables/Enables the 'Share-screen'-tab in the welcome message", ) this.featureSwitchGeolocation = FeatureSwitchUtils.initSwitch( "fs-geolocation", layoutToUse?.enableGeolocation ?? true, - "Disables/Enables the geolocation button" + "Disables/Enables the geolocation button", ) this.featureSwitchShowAllQuestions = FeatureSwitchUtils.initSwitch( "fs-all-questions", layoutToUse?.enableShowAllQuestions ?? false, - "Always show all questions" + "Always show all questions", ) this.featureSwitchEnableExport = FeatureSwitchUtils.initSwitch( "fs-export", layoutToUse?.enableExportButton ?? true, - "Enable the export as GeoJSON and CSV button" + "Enable the export as GeoJSON and CSV button", ) let testingDefaultValue = false @@ -156,59 +182,59 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches { this.featureSwitchIsTesting = QueryParameters.GetBooleanQueryParameter( "test", testingDefaultValue, - "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" + "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", ) this.featureSwitchIsDebugging = QueryParameters.GetBooleanQueryParameter( "debug", false, - "If true, shows some extra debugging help such as all the available tags on every object" + "If true, shows some extra debugging help such as all the available tags on every object", ) this.featureSwitchMorePrivacy = QueryParameters.GetBooleanQueryParameter( "moreprivacy", layoutToUse.enableMorePrivacy, - "If true, the location distance indication will not be written to the changeset and other privacy enhancing measures might be taken." + "If true, the location distance indication will not be written to the changeset and other privacy enhancing measures might be taken.", ) this.overpassUrl = QueryParameters.GetQueryParameter( "overpassUrl", (layoutToUse?.overpassUrl ?? Constants.defaultOverpassUrls).join(","), - "Point mapcomplete to a different overpass-instance. Example: https://overpass-api.de/api/interpreter" + "Point mapcomplete to a different overpass-instance. Example: https://overpass-api.de/api/interpreter", ).sync( (param) => param?.split(","), [], - (urls) => urls?.join(",") + (urls) => urls?.join(","), ) this.overpassTimeout = UIEventSource.asInt( QueryParameters.GetQueryParameter( "overpassTimeout", "" + layoutToUse?.overpassTimeout, - "Set a different timeout (in seconds) for queries in overpass" - ) + "Set a different timeout (in seconds) for queries in overpass", + ), ) this.overpassMaxZoom = UIEventSource.asFloat( QueryParameters.GetQueryParameter( "overpassMaxZoom", "" + layoutToUse?.overpassMaxZoom, - " point to switch between OSM-api and overpass" - ) + " point to switch between OSM-api and overpass", + ), ) this.osmApiTileSize = UIEventSource.asInt( QueryParameters.GetQueryParameter( "osmApiTileSize", "" + layoutToUse?.osmApiTileSize, - "Tilesize when the OSM-API is used to fetch data within a BBOX" - ) + "Tilesize when the OSM-API is used to fetch data within a BBOX", + ), ) this.backgroundLayerId = QueryParameters.GetQueryParameter( "background", layoutToUse?.defaultBackgroundId, - "The id of the background layer to start with" + "The id of the background layer to start with", ) } } diff --git a/src/Logic/State/UserRelatedState.ts b/src/Logic/State/UserRelatedState.ts index dc54f74a65..40f257e928 100644 --- a/src/Logic/State/UserRelatedState.ts +++ b/src/Logic/State/UserRelatedState.ts @@ -297,6 +297,7 @@ export default class UserRelatedState { _applicationOpened: new Date().toISOString(), _supports_sharing: typeof window === "undefined" ? "no" : window.navigator.share ? "yes" : "no", + _iframe: Utils.isIframe ? "yes" : "no" }) for (const key in Constants.userJourney) { diff --git a/src/UI/AllThemesGui.svelte b/src/UI/AllThemesGui.svelte index 5ef42ad7ea..40df2d21d4 100644 --- a/src/UI/AllThemesGui.svelte +++ b/src/UI/AllThemesGui.svelte @@ -25,6 +25,8 @@ import Liberapay from "../assets/svg/Liberapay.svelte" import Bug from "../assets/svg/Bug.svelte" import Github from "../assets/svg/Github.svelte" + import { Utils } from "../Utils" + import { ArrowTrendingUp } from "@babeard/svelte-heroicons/solid/ArrowTrendingUp" const featureSwitches = new OsmConnectionFeatureSwitches() const osmConnection = new OsmConnection({ @@ -146,6 +148,15 @@ + + + + + +

@@ -161,6 +172,11 @@ + + + + + @@ -171,14 +187,6 @@ - - - - - { @@ -36,7 +35,7 @@ href={$href} target={config.newTab ? "_blank" : ""} rel="noopener" - class="pointer-events-auto flex rounded-full border-black" + class="button pointer-events-auto flex rounded-full border-black" > diff --git a/src/UI/BigComponents/ThemeIntroPanel.svelte b/src/UI/BigComponents/ThemeIntroPanel.svelte index 88180b9b1a..f4fecd4e9e 100644 --- a/src/UI/BigComponents/ThemeIntroPanel.svelte +++ b/src/UI/BigComponents/ThemeIntroPanel.svelte @@ -15,6 +15,7 @@ import Location_refused from "../../assets/svg/Location_refused.svelte" import Location from "../../assets/svg/Location.svelte" import ChevronDoubleLeft from "@babeard/svelte-heroicons/mini/ChevronDoubleLeft" + import Constants from "../../Models/Constants" /** * The theme introduction panel @@ -149,13 +150,20 @@ {/if} - - diff --git a/src/UI/Comparison/ComparisonTool.svelte b/src/UI/Comparison/ComparisonTool.svelte index 120503b2c8..f106c42f34 100644 --- a/src/UI/Comparison/ComparisonTool.svelte +++ b/src/UI/Comparison/ComparisonTool.svelte @@ -42,9 +42,10 @@ let knownImages = comparisonState.bindD((ct) => ct.knownImages) let propertyKeysExternal = comparisonState.mapD((ct) => ct.propertyKeysExternal) let hasDifferencesAtStart = comparisonState.mapD((ct) => ct.hasDifferencesAtStart) + let enableLogin= state.featureSwitches.featureSwitchEnableLogin -{#if !$sourceUrl} +{#if !$sourceUrl || !$enableLogin} {:else if $externalData === undefined} diff --git a/src/UI/Image/ImageCarousel.ts b/src/UI/Image/ImageCarousel.ts index 0422031ddf..9b0ee5d21f 100644 --- a/src/UI/Image/ImageCarousel.ts +++ b/src/UI/Image/ImageCarousel.ts @@ -54,10 +54,9 @@ export class ImageCarousel extends Toggle { ) super( - new SlideShow(uiElements).SetClass("w-full"), + new SlideShow(uiElements).SetClass("w-full block w-full my-4"), undefined, uiElements.map((els) => els.length > 0) ) - this.SetClass("block w-full") } } diff --git a/src/UI/Image/NearbyImagesCollapsed.svelte b/src/UI/Image/NearbyImagesCollapsed.svelte index 749791b28f..3c53902293 100644 --- a/src/UI/Image/NearbyImagesCollapsed.svelte +++ b/src/UI/Image/NearbyImagesCollapsed.svelte @@ -25,11 +25,14 @@ const t = Translations.t.image.nearby let expanded = false + let enableLogin = state.featureSwitches.featureSwitchEnableLogin +{#if enableLogin.data} + {/if} diff --git a/src/UI/Image/UploadImage.svelte b/src/UI/Image/UploadImage.svelte index 337fc3ad17..9ed7640de0 100644 --- a/src/UI/Image/UploadImage.svelte +++ b/src/UI/Image/UploadImage.svelte @@ -58,7 +58,7 @@ -
+
{#each $errors as error} diff --git a/src/UI/OpeningHours/OpeningHours.ts b/src/UI/OpeningHours/OpeningHours.ts index 5992bcedc1..857a927ec0 100644 --- a/src/UI/OpeningHours/OpeningHours.ts +++ b/src/UI/OpeningHours/OpeningHours.ts @@ -932,9 +932,11 @@ export class ToTextualDescription { public static createTextualDescriptionFor( oh: opening_hours, ranges: OpeningRange[][] - ): Translation { + ): Translation | undefined { const t = Translations.t.general.opening_hours - + if(!ranges){ + return undefined + } if (!ranges?.some((r) => r.length > 0)) { // if (oh.getNextChange() === undefined) { @@ -1029,9 +1031,9 @@ export class ToTextualDescription { }) } - private static createRangesFor(ranges: OpeningRange[]): Translation { + private static createRangesFor(ranges: OpeningRange[]): Translation | undefined { if (ranges.length === 0) { - // return undefined + return undefined } let tr = ToTextualDescription.createRangeFor(ranges[0]) for (let i = 1; i < ranges.length; i++) { diff --git a/src/UI/OpeningHours/OpeningHoursVisualization.ts b/src/UI/OpeningHours/OpeningHoursVisualization.ts index e7078f7204..02bdaaaa12 100644 --- a/src/UI/OpeningHours/OpeningHoursVisualization.ts +++ b/src/UI/OpeningHours/OpeningHoursVisualization.ts @@ -26,7 +26,6 @@ export default class OpeningHoursVisualization extends Toggle { constructor( tags: UIEventSource>, - state: { osmConnection?: OsmConnection }, key: string, prefix = "", postfix = "" @@ -56,7 +55,7 @@ export default class OpeningHoursVisualization extends Toggle { ) Locale.language.mapD((lng) => { console.debug("Setting OH description to", lng, textual) - vis.ConstructElement().ariaLabel = textual.textFor(lng) + vis.ConstructElement().ariaLabel = textual?.textFor(lng) }) return vis }) @@ -75,17 +74,13 @@ export default class OpeningHoursVisualization extends Toggle { ranges: OpeningRange[][], lastMonday: Date ): BaseUIElement { - /* First, a small sanity check. The business might be permanently closed, 24/7 opened or something other special - * So, we have to handle the case that ranges is completely empty*/ - if (ranges.filter((range) => range.length > 0).length === 0) { - return OpeningHoursVisualization.ShowSpecialCase(oh).SetClass( - "p-4 rounded-full block bg-gray-200" - ) + // First, a small sanity check. The business might be permanently closed, 24/7 opened or something other special + if (ranges.some((range) => range.length > 0)) { + // The normal case: we have items for the coming days + return OpeningHoursVisualization.ConstructVizTable(oh, ranges, lastMonday) } - - /** With all the edge cases handled, we can actually construct the table! **/ - - return OpeningHoursVisualization.ConstructVizTable(oh, ranges, lastMonday) + // The special case that range is completely empty + return OpeningHoursVisualization.ShowSpecialCase(oh) } private static ConstructVizTable( @@ -308,6 +303,6 @@ export default class OpeningHoursVisualization extends Toggle { opensAtDate.getHours(), opensAtDate.getMinutes() )}` - return Translations.t.general.opening_hours.closed_until.Subs({ date: willOpenAt }) + return Translations.t.general.opening_hours.closed_until.Subs({ date: opensAtDate.toLocaleString() }) } } diff --git a/src/UI/Popup/Notes/AddNoteComment.svelte b/src/UI/Popup/Notes/AddNoteComment.svelte index f530dae2e1..dbf493f9eb 100644 --- a/src/UI/Popup/Notes/AddNoteComment.svelte +++ b/src/UI/Popup/Notes/AddNoteComment.svelte @@ -47,12 +47,14 @@ async function closeNote() { await state.osmConnection.closeNote(id, txt.data) tags.data["closed_at"] = new Date().toISOString() + NoteCommentElement.addCommentTo(txt.data, tags, state) tags.ping() } async function reopenNote() { await state.osmConnection.reopenNote(id, txt.data) tags.data["closed_at"] = undefined + NoteCommentElement.addCommentTo(txt.data, tags, state) tags.ping() } diff --git a/src/UI/Popup/Notes/CloseNoteButton.ts b/src/UI/Popup/Notes/CloseNoteButton.ts index c1cb7db975..48930b04b2 100644 --- a/src/UI/Popup/Notes/CloseNoteButton.ts +++ b/src/UI/Popup/Notes/CloseNoteButton.ts @@ -10,6 +10,8 @@ import { UIEventSource } from "../../../Logic/UIEventSource" import Constants from "../../../Models/Constants" import SvelteUIElement from "../../Base/SvelteUIElement" import Checkmark from "../../../assets/svg/Checkmark.svelte" +import NoteCommentElement from "./NoteCommentElement" +import Icon from "../../Map/Icon.svelte" export class CloseNoteButton implements SpecialVisualization { public readonly funcName = "close_note" @@ -62,10 +64,7 @@ export class CloseNoteButton implements SpecialVisualization { zoomButton: string } = Utils.ParseVisArgs(this.args, args) - let icon: BaseUIElement = new SvelteUIElement(Checkmark) - if (params.icon !== "checkmark.svg" && (args[2] ?? "") !== "") { - icon = new Img(args[1]) - } + let icon: BaseUIElement = new SvelteUIElement(Icon, {icon: params.icon ?? "checkmark.svg"}) let textToShow = t.closeNote if ((params.text ?? "") !== "") { textToShow = Translations.T(args[0]) @@ -75,7 +74,9 @@ export class CloseNoteButton implements SpecialVisualization { const isClosed = tags.map((tags) => (tags["closed_at"] ?? "") !== "") closeButton.onClick(() => { const id = tags.data[args[2] ?? "id"] - state.osmConnection.closeNote(id, args[3])?.then((_) => { + const text = args[3] + state.osmConnection.closeNote(id, text)?.then((_) => { + NoteCommentElement.addCommentTo(text, tags, state) tags.data["closed_at"] = new Date().toISOString() tags.ping() }) diff --git a/src/UI/Popup/Notes/CreateNewNote.svelte b/src/UI/Popup/Notes/CreateNewNote.svelte index ef73473fe2..b4c924cce2 100644 --- a/src/UI/Popup/Notes/CreateNewNote.svelte +++ b/src/UI/Popup/Notes/CreateNewNote.svelte @@ -19,6 +19,7 @@ import AddSmall from "../../../assets/svg/AddSmall.svelte" import type { OsmTags } from "../../../Models/OsmFeature" import Loading from "../../Base/Loading.svelte" + import NextButton from "../../Base/NextButton.svelte" export let coordinate: UIEventSource<{ lon: number; lat: number }> export let state: SpecialVisualizationState @@ -109,7 +110,7 @@
{:else} -
+