forked from MapComplete/MapComplete
Review of many feature switches and iframe context; fix OH-vis when ranges are not given
This commit is contained in:
parent
ecfa7d3d1d
commit
afea9adacb
21 changed files with 157 additions and 88 deletions
|
@ -292,6 +292,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"metacondition": "_loggedIn=true",
|
||||||
"mappings": [
|
"mappings": [
|
||||||
{
|
{
|
||||||
"if": "id~.*/-.*",
|
"if": "id~.*/-.*",
|
||||||
|
|
|
@ -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",
|
"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": {
|
"render": {
|
||||||
"*": "{image_carousel()}{image_upload()}"
|
"*": "{image_carousel()}{image_upload()}"
|
||||||
},
|
}
|
||||||
"classes": "my-4"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "mapillary",
|
"id": "mapillary",
|
||||||
|
|
|
@ -325,7 +325,7 @@
|
||||||
"opening_hours": {
|
"opening_hours": {
|
||||||
"all_days_from": "Opened every day {ranges}",
|
"all_days_from": "Opened every day {ranges}",
|
||||||
"closed_permanently": "Closed for an unknown duration",
|
"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": "Could not parse the opening hours",
|
||||||
"error_loading": "Error: could not visualize these opening hours.",
|
"error_loading": "Error: could not visualize these opening hours.",
|
||||||
"friday": "On friday {ranges}",
|
"friday": "On friday {ranges}",
|
||||||
|
@ -359,6 +359,7 @@
|
||||||
"versionInfo": "v{version} - generated on {date}"
|
"versionInfo": "v{version} - generated on {date}"
|
||||||
},
|
},
|
||||||
"pickLanguage": "Select language",
|
"pickLanguage": "Select language",
|
||||||
|
"poweredByMapComplete": "Powered by MapComplete - crowdsourced, thematic maps with OpenStreetMap",
|
||||||
"poweredByOsm": "Powered by OpenStreetMap",
|
"poweredByOsm": "Powered by OpenStreetMap",
|
||||||
"questionBox": {
|
"questionBox": {
|
||||||
"answeredMultiple": "You answered {answered} questions",
|
"answeredMultiple": "You answered {answered} questions",
|
||||||
|
@ -394,6 +395,7 @@
|
||||||
"searching": "Searching…"
|
"searching": "Searching…"
|
||||||
},
|
},
|
||||||
"searchAnswer": "Search an option…",
|
"searchAnswer": "Search an option…",
|
||||||
|
"seeIndex": "See the overview with all thematic maps",
|
||||||
"share": "Share",
|
"share": "Share",
|
||||||
"sharescreen": {
|
"sharescreen": {
|
||||||
"copiedToClipboard": "Link copied to clipboard",
|
"copiedToClipboard": "Link copied to clipboard",
|
||||||
|
|
|
@ -10754,6 +10754,26 @@
|
||||||
},
|
},
|
||||||
"question": "What accessibility features should be applied?"
|
"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": {
|
"all-questions-at-once": {
|
||||||
"mappings": {
|
"mappings": {
|
||||||
"0": {
|
"0": {
|
||||||
|
|
|
@ -6,22 +6,26 @@ import { UIEventSource } from "../UIEventSource"
|
||||||
import { QueryParameters } from "../Web/QueryParameters"
|
import { QueryParameters } from "../Web/QueryParameters"
|
||||||
import Constants from "../../Models/Constants"
|
import Constants from "../../Models/Constants"
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
|
import { Query } from "pg"
|
||||||
|
|
||||||
class FeatureSwitchUtils {
|
class FeatureSwitchUtils {
|
||||||
|
/** Helper function to initialize feature switches
|
||||||
|
*
|
||||||
|
*/
|
||||||
static initSwitch(key: string, deflt: boolean, documentation: string): UIEventSource<boolean> {
|
static initSwitch(key: string, deflt: boolean, documentation: string): UIEventSource<boolean> {
|
||||||
const defaultValue = deflt
|
const defaultValue = deflt
|
||||||
const queryParam = QueryParameters.GetQueryParameter(
|
const queryParam = QueryParameters.GetQueryParameter(
|
||||||
key,
|
key,
|
||||||
"" + defaultValue,
|
"" + defaultValue,
|
||||||
documentation,
|
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
|
// 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(
|
return queryParam.sync(
|
||||||
(str) => (str === undefined ? defaultValue : str !== "false"),
|
(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(
|
this.featureSwitchFakeUser = QueryParameters.GetBooleanQueryParameter(
|
||||||
"fake-user",
|
"fake-user",
|
||||||
false,
|
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()
|
super()
|
||||||
this.layoutToUse = layoutToUse
|
this.layoutToUse = layoutToUse
|
||||||
|
|
||||||
// Helper function to initialize feature switches
|
|
||||||
|
const legacyRewrite: Record<string, string | string[]> = {
|
||||||
|
"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(
|
this.featureSwitchEnableLogin = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-enable-login",
|
"fs-enable-login",
|
||||||
layoutToUse?.enableUserBadge ?? true,
|
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")) {
|
if (QueryParameters.wasInitialized("fs-userbadge")) {
|
||||||
// userbadge is the legacy name for 'enable-login'
|
// userbadge is the legacy name for 'enable-login'
|
||||||
this.featureSwitchEnableLogin.setData(
|
this.featureSwitchEnableLogin.setData(
|
||||||
QueryParameters.GetBooleanQueryParameter("fs-userbadge", undefined, "Legacy")
|
QueryParameters.GetBooleanQueryParameter("fs-userbadge", undefined, "Legacy")
|
||||||
.data
|
.data,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,60 +115,60 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
|
||||||
this.featureSwitchSearch = FeatureSwitchUtils.initSwitch(
|
this.featureSwitchSearch = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-search",
|
"fs-search",
|
||||||
layoutToUse?.enableSearch ?? true,
|
layoutToUse?.enableSearch ?? true,
|
||||||
"Disables/Enables the search bar"
|
"Disables/Enables the search bar",
|
||||||
)
|
)
|
||||||
this.featureSwitchBackgroundSelection = FeatureSwitchUtils.initSwitch(
|
this.featureSwitchBackgroundSelection = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-background",
|
"fs-background",
|
||||||
layoutToUse?.enableBackgroundLayerSelection ?? true,
|
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(
|
this.featureSwitchFilter = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-filter",
|
"fs-filter",
|
||||||
layoutToUse?.enableLayers ?? true,
|
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(
|
this.featureSwitchWelcomeMessage = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-welcome-message",
|
"fs-welcome-message",
|
||||||
true,
|
true,
|
||||||
"Disables/enables the help menu or welcome message"
|
"Disables/enables the help menu or welcome message",
|
||||||
)
|
)
|
||||||
this.featureSwitchCommunityIndex = FeatureSwitchUtils.initSwitch(
|
this.featureSwitchCommunityIndex = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-community-index",
|
"fs-community-index",
|
||||||
this.featureSwitchEnableLogin.data,
|
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(
|
this.featureSwitchExtraLinkEnabled = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-iframe-popout",
|
"fs-iframe-popout",
|
||||||
true,
|
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(
|
this.featureSwitchBackToThemeOverview = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-homepage-link",
|
"fs-homepage-link",
|
||||||
layoutToUse?.enableMoreQuests ?? true,
|
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(
|
this.featureSwitchShareScreen = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-share-screen",
|
"fs-share-screen",
|
||||||
layoutToUse?.enableShareScreen ?? true,
|
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(
|
this.featureSwitchGeolocation = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-geolocation",
|
"fs-geolocation",
|
||||||
layoutToUse?.enableGeolocation ?? true,
|
layoutToUse?.enableGeolocation ?? true,
|
||||||
"Disables/Enables the geolocation button"
|
"Disables/Enables the geolocation button",
|
||||||
)
|
)
|
||||||
this.featureSwitchShowAllQuestions = FeatureSwitchUtils.initSwitch(
|
this.featureSwitchShowAllQuestions = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-all-questions",
|
"fs-all-questions",
|
||||||
layoutToUse?.enableShowAllQuestions ?? false,
|
layoutToUse?.enableShowAllQuestions ?? false,
|
||||||
"Always show all questions"
|
"Always show all questions",
|
||||||
)
|
)
|
||||||
|
|
||||||
this.featureSwitchEnableExport = FeatureSwitchUtils.initSwitch(
|
this.featureSwitchEnableExport = FeatureSwitchUtils.initSwitch(
|
||||||
"fs-export",
|
"fs-export",
|
||||||
layoutToUse?.enableExportButton ?? true,
|
layoutToUse?.enableExportButton ?? true,
|
||||||
"Enable the export as GeoJSON and CSV button"
|
"Enable the export as GeoJSON and CSV button",
|
||||||
)
|
)
|
||||||
|
|
||||||
let testingDefaultValue = false
|
let testingDefaultValue = false
|
||||||
|
@ -156,59 +182,59 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
|
||||||
this.featureSwitchIsTesting = QueryParameters.GetBooleanQueryParameter(
|
this.featureSwitchIsTesting = QueryParameters.GetBooleanQueryParameter(
|
||||||
"test",
|
"test",
|
||||||
testingDefaultValue,
|
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(
|
this.featureSwitchIsDebugging = QueryParameters.GetBooleanQueryParameter(
|
||||||
"debug",
|
"debug",
|
||||||
false,
|
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(
|
this.featureSwitchMorePrivacy = QueryParameters.GetBooleanQueryParameter(
|
||||||
"moreprivacy",
|
"moreprivacy",
|
||||||
layoutToUse.enableMorePrivacy,
|
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(
|
this.overpassUrl = QueryParameters.GetQueryParameter(
|
||||||
"overpassUrl",
|
"overpassUrl",
|
||||||
(layoutToUse?.overpassUrl ?? Constants.defaultOverpassUrls).join(","),
|
(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(
|
).sync(
|
||||||
(param) => param?.split(","),
|
(param) => param?.split(","),
|
||||||
[],
|
[],
|
||||||
(urls) => urls?.join(",")
|
(urls) => urls?.join(","),
|
||||||
)
|
)
|
||||||
|
|
||||||
this.overpassTimeout = UIEventSource.asInt(
|
this.overpassTimeout = UIEventSource.asInt(
|
||||||
QueryParameters.GetQueryParameter(
|
QueryParameters.GetQueryParameter(
|
||||||
"overpassTimeout",
|
"overpassTimeout",
|
||||||
"" + layoutToUse?.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(
|
this.overpassMaxZoom = UIEventSource.asFloat(
|
||||||
QueryParameters.GetQueryParameter(
|
QueryParameters.GetQueryParameter(
|
||||||
"overpassMaxZoom",
|
"overpassMaxZoom",
|
||||||
"" + layoutToUse?.overpassMaxZoom,
|
"" + layoutToUse?.overpassMaxZoom,
|
||||||
" point to switch between OSM-api and overpass"
|
" point to switch between OSM-api and overpass",
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
this.osmApiTileSize = UIEventSource.asInt(
|
this.osmApiTileSize = UIEventSource.asInt(
|
||||||
QueryParameters.GetQueryParameter(
|
QueryParameters.GetQueryParameter(
|
||||||
"osmApiTileSize",
|
"osmApiTileSize",
|
||||||
"" + layoutToUse?.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(
|
this.backgroundLayerId = QueryParameters.GetQueryParameter(
|
||||||
"background",
|
"background",
|
||||||
layoutToUse?.defaultBackgroundId,
|
layoutToUse?.defaultBackgroundId,
|
||||||
"The id of the background layer to start with"
|
"The id of the background layer to start with",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,6 +297,7 @@ export default class UserRelatedState {
|
||||||
_applicationOpened: new Date().toISOString(),
|
_applicationOpened: new Date().toISOString(),
|
||||||
_supports_sharing:
|
_supports_sharing:
|
||||||
typeof window === "undefined" ? "no" : window.navigator.share ? "yes" : "no",
|
typeof window === "undefined" ? "no" : window.navigator.share ? "yes" : "no",
|
||||||
|
_iframe: Utils.isIframe ? "yes" : "no"
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const key in Constants.userJourney) {
|
for (const key in Constants.userJourney) {
|
||||||
|
|
|
@ -40,8 +40,8 @@ export default class CopyrightPanel extends Combine {
|
||||||
const t = Translations.t.general.attribution
|
const t = Translations.t.general.attribution
|
||||||
const layoutToUse = state.layout
|
const layoutToUse = state.layout
|
||||||
|
|
||||||
const iconAttributions: BaseUIElement[] = layoutToUse
|
const iconAttributions: BaseUIElement[] = Utils.Dedup(layoutToUse
|
||||||
.getUsedImages()
|
.getUsedImages())
|
||||||
.map(CopyrightPanel.IconAttribution)
|
.map(CopyrightPanel.IconAttribution)
|
||||||
|
|
||||||
let maintainer: BaseUIElement = undefined
|
let maintainer: BaseUIElement = undefined
|
||||||
|
|
|
@ -11,10 +11,9 @@
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
let theme = state.layout?.id ?? ""
|
let theme = state.layout?.id ?? ""
|
||||||
let config: ExtraLinkConfig = state.layout.extraLink
|
let config: ExtraLinkConfig = state.layout.extraLink
|
||||||
const isIframe = window !== window.top
|
|
||||||
let basepath = window.location.host
|
let basepath = window.location.host
|
||||||
let showWelcomeMessageSwitch = state.featureSwitches.featureSwitchWelcomeMessage
|
let showWelcomeMessageSwitch = state.featureSwitches.featureSwitchWelcomeMessage
|
||||||
|
const isIframe = Utils.isIframe
|
||||||
const t = Translations.t.general
|
const t = Translations.t.general
|
||||||
const href = state.mapProperties.location.map(
|
const href = state.mapProperties.location.map(
|
||||||
(loc) => {
|
(loc) => {
|
||||||
|
@ -36,7 +35,7 @@
|
||||||
href={$href}
|
href={$href}
|
||||||
target={config.newTab ? "_blank" : ""}
|
target={config.newTab ? "_blank" : ""}
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
class="pointer-events-auto flex rounded-full border-black"
|
class="button pointer-events-auto flex rounded-full border-black"
|
||||||
>
|
>
|
||||||
<Icon icon={config.icon} clss="w-6 h-6 m-2" />
|
<Icon icon={config.icon} clss="w-6 h-6 m-2" />
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
import Location_refused from "../../assets/svg/Location_refused.svelte"
|
import Location_refused from "../../assets/svg/Location_refused.svelte"
|
||||||
import Location from "../../assets/svg/Location.svelte"
|
import Location from "../../assets/svg/Location.svelte"
|
||||||
import ChevronDoubleLeft from "@babeard/svelte-heroicons/mini/ChevronDoubleLeft"
|
import ChevronDoubleLeft from "@babeard/svelte-heroicons/mini/ChevronDoubleLeft"
|
||||||
|
import Constants from "../../Models/Constants"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The theme introduction panel
|
* The theme introduction panel
|
||||||
|
@ -149,13 +150,20 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<If condition={state.featureSwitches.featureSwitchBackToThemeOverview}>
|
{#if Utils.isIframe}
|
||||||
<div class="link-underline m-2 mx-4 flex w-full">
|
<div class="flex justify-end link-underline">
|
||||||
<!-- bottom buttons, a bit hidden away: switch layout -->
|
<a href="https://mapcomplete.org" target="_blank">
|
||||||
<a class="flex w-fit items-center justify-end" href={Utils.HomepageLink()}>
|
<Tr t={Translations.t.general.poweredByMapComplete}/>
|
||||||
<ChevronDoubleLeft class="h-4 w-4" />
|
</a>
|
||||||
<Tr t={Translations.t.general.backToIndex} />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</If>
|
{:else}
|
||||||
|
<If condition={state.featureSwitches.featureSwitchBackToThemeOverview}>
|
||||||
|
<div class="link-underline m-2 mx-4 flex w-full">
|
||||||
|
<a class="flex w-fit items-center justify-end" href={Utils.HomepageLink()}>
|
||||||
|
<ChevronDoubleLeft class="h-4 w-4" />
|
||||||
|
<Tr t={Translations.t.general.backToIndex} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -42,9 +42,10 @@
|
||||||
let knownImages = comparisonState.bindD((ct) => ct.knownImages)
|
let knownImages = comparisonState.bindD((ct) => ct.knownImages)
|
||||||
let propertyKeysExternal = comparisonState.mapD((ct) => ct.propertyKeysExternal)
|
let propertyKeysExternal = comparisonState.mapD((ct) => ct.propertyKeysExternal)
|
||||||
let hasDifferencesAtStart = comparisonState.mapD((ct) => ct.hasDifferencesAtStart)
|
let hasDifferencesAtStart = comparisonState.mapD((ct) => ct.hasDifferencesAtStart)
|
||||||
|
let enableLogin= state.featureSwitches.featureSwitchEnableLogin
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !$sourceUrl}
|
{#if !$sourceUrl || !$enableLogin}
|
||||||
<!-- empty block -->
|
<!-- empty block -->
|
||||||
{:else if $externalData === undefined}
|
{:else if $externalData === undefined}
|
||||||
<Loading />
|
<Loading />
|
||||||
|
|
|
@ -54,10 +54,9 @@ export class ImageCarousel extends Toggle {
|
||||||
)
|
)
|
||||||
|
|
||||||
super(
|
super(
|
||||||
new SlideShow(uiElements).SetClass("w-full"),
|
new SlideShow(uiElements).SetClass("w-full block w-full my-4"),
|
||||||
undefined,
|
undefined,
|
||||||
uiElements.map((els) => els.length > 0)
|
uiElements.map((els) => els.length > 0)
|
||||||
)
|
)
|
||||||
this.SetClass("block w-full")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,11 +25,14 @@
|
||||||
const t = Translations.t.image.nearby
|
const t = Translations.t.image.nearby
|
||||||
|
|
||||||
let expanded = false
|
let expanded = false
|
||||||
|
let enableLogin = state.featureSwitches.featureSwitchEnableLogin
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if enableLogin.data}
|
||||||
<AccordionSingle>
|
<AccordionSingle>
|
||||||
<span slot="header" class="p-2 text-base">
|
<span slot="header" class="p-2 text-base">
|
||||||
<Tr t={t.seeNearby} />
|
<Tr t={t.seeNearby} />
|
||||||
</span>
|
</span>
|
||||||
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable} {layer} />
|
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable} {layer} />
|
||||||
</AccordionSingle>
|
</AccordionSingle>
|
||||||
|
{/if}
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
<LoginButton clss="small w-full" osmConnection={state.osmConnection} slot="not-logged-in">
|
<LoginButton clss="small w-full" osmConnection={state.osmConnection} slot="not-logged-in">
|
||||||
<Tr t={Translations.t.image.pleaseLogin} />
|
<Tr t={Translations.t.image.pleaseLogin} />
|
||||||
</LoginButton>
|
</LoginButton>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col my-4">
|
||||||
<UploadingImageCounter {state} {tags} />
|
<UploadingImageCounter {state} {tags} />
|
||||||
{#each $errors as error}
|
{#each $errors as error}
|
||||||
<Tr t={error} cls="alert" />
|
<Tr t={error} cls="alert" />
|
||||||
|
|
|
@ -932,9 +932,11 @@ export class ToTextualDescription {
|
||||||
public static createTextualDescriptionFor(
|
public static createTextualDescriptionFor(
|
||||||
oh: opening_hours,
|
oh: opening_hours,
|
||||||
ranges: OpeningRange[][]
|
ranges: OpeningRange[][]
|
||||||
): Translation {
|
): Translation | undefined {
|
||||||
const t = Translations.t.general.opening_hours
|
const t = Translations.t.general.opening_hours
|
||||||
|
if(!ranges){
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
if (!ranges?.some((r) => r.length > 0)) {
|
if (!ranges?.some((r) => r.length > 0)) {
|
||||||
// <!-- No changes to the opening hours in the next week; probably open 24/7, permanently closed, opening far in the future or unkown -->
|
// <!-- No changes to the opening hours in the next week; probably open 24/7, permanently closed, opening far in the future or unkown -->
|
||||||
if (oh.getNextChange() === undefined) {
|
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) {
|
if (ranges.length === 0) {
|
||||||
// return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
let tr = ToTextualDescription.createRangeFor(ranges[0])
|
let tr = ToTextualDescription.createRangeFor(ranges[0])
|
||||||
for (let i = 1; i < ranges.length; i++) {
|
for (let i = 1; i < ranges.length; i++) {
|
||||||
|
|
|
@ -26,7 +26,6 @@ export default class OpeningHoursVisualization extends Toggle {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
tags: UIEventSource<Record<string, string>>,
|
tags: UIEventSource<Record<string, string>>,
|
||||||
state: { osmConnection?: OsmConnection },
|
|
||||||
key: string,
|
key: string,
|
||||||
prefix = "",
|
prefix = "",
|
||||||
postfix = ""
|
postfix = ""
|
||||||
|
@ -56,7 +55,7 @@ export default class OpeningHoursVisualization extends Toggle {
|
||||||
)
|
)
|
||||||
Locale.language.mapD((lng) => {
|
Locale.language.mapD((lng) => {
|
||||||
console.debug("Setting OH description to", lng, textual)
|
console.debug("Setting OH description to", lng, textual)
|
||||||
vis.ConstructElement().ariaLabel = textual.textFor(lng)
|
vis.ConstructElement().ariaLabel = textual?.textFor(lng)
|
||||||
})
|
})
|
||||||
return vis
|
return vis
|
||||||
})
|
})
|
||||||
|
@ -75,17 +74,13 @@ export default class OpeningHoursVisualization extends Toggle {
|
||||||
ranges: OpeningRange[][],
|
ranges: OpeningRange[][],
|
||||||
lastMonday: Date
|
lastMonday: Date
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
/* First, a small sanity check. The business might be permanently closed, 24/7 opened or something other special
|
// 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.some((range) => range.length > 0)) {
|
||||||
if (ranges.filter((range) => range.length > 0).length === 0) {
|
// The normal case: we have items for the coming days
|
||||||
return OpeningHoursVisualization.ShowSpecialCase(oh).SetClass(
|
return OpeningHoursVisualization.ConstructVizTable(oh, ranges, lastMonday)
|
||||||
"p-4 rounded-full block bg-gray-200"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
// The special case that range is completely empty
|
||||||
/** With all the edge cases handled, we can actually construct the table! **/
|
return OpeningHoursVisualization.ShowSpecialCase(oh)
|
||||||
|
|
||||||
return OpeningHoursVisualization.ConstructVizTable(oh, ranges, lastMonday)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ConstructVizTable(
|
private static ConstructVizTable(
|
||||||
|
@ -308,6 +303,6 @@ export default class OpeningHoursVisualization extends Toggle {
|
||||||
opensAtDate.getHours(),
|
opensAtDate.getHours(),
|
||||||
opensAtDate.getMinutes()
|
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() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,8 @@
|
||||||
let answered: number = 0
|
let answered: number = 0
|
||||||
let skipped: number = 0
|
let skipped: number = 0
|
||||||
|
|
||||||
|
let loginEnabled = state.featureSwitches.featureSwitchEnableLogin
|
||||||
|
|
||||||
function skip(question: { id: string }, didAnswer: boolean = false) {
|
function skip(question: { id: string }, didAnswer: boolean = false) {
|
||||||
skippedQuestions.data.add(question.id) // Must use ID, the config object might be a copy of the original
|
skippedQuestions.data.add(question.id) // Must use ID, the config object might be a copy of the original
|
||||||
skippedQuestions.ping()
|
skippedQuestions.ping()
|
||||||
|
@ -108,6 +110,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if $loginEnabled}
|
||||||
<div
|
<div
|
||||||
bind:this={questionboxElem}
|
bind:this={questionboxElem}
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
|
@ -197,3 +200,4 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
|
@ -83,7 +83,7 @@
|
||||||
{config.id}
|
{config.id}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if config.question && (!editingEnabled || $editingEnabled)}
|
{#if config.question}
|
||||||
{#if editMode}
|
{#if editMode}
|
||||||
<TagRenderingQuestion
|
<TagRenderingQuestion
|
||||||
{config}
|
{config}
|
||||||
|
@ -106,19 +106,19 @@
|
||||||
</TagRenderingQuestion>
|
</TagRenderingQuestion>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="low-interaction flex items-center justify-between overflow-hidden rounded pl-2">
|
<div class="low-interaction flex items-center justify-between overflow-hidden rounded pl-2">
|
||||||
<TagRenderingAnswer id={answerId} {config} {tags} {selectedElement} {state} {layer} />
|
<TagRenderingAnswer id={answerId} {config} {tags} {selectedElement} {state} {layer} extraClasses="my-2"/>
|
||||||
<EditButton
|
{#if (!editingEnabled || $editingEnabled)}
|
||||||
arialabel={config.editButtonAriaLabel}
|
<EditButton
|
||||||
ariaLabelledBy={answerId}
|
arialabel={config.editButtonAriaLabel}
|
||||||
on:click={() => {
|
ariaLabelledBy={answerId}
|
||||||
editMode = true
|
on:click={() => {
|
||||||
}}
|
editMode = true
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="h-full w-full overflow-auto">
|
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
|
||||||
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -794,7 +794,7 @@ export default class SpecialVisualizations {
|
||||||
"A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`",
|
"A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`",
|
||||||
constr: (state, tagSource: UIEventSource<any>, args) => {
|
constr: (state, tagSource: UIEventSource<any>, args) => {
|
||||||
const [key, prefix, postfix] = args
|
const [key, prefix, postfix] = args
|
||||||
return new OpeningHoursVisualization(tagSource, state, key, prefix, postfix)
|
return new OpeningHoursVisualization(tagSource, key, prefix, postfix)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main />
|
<main >
|
||||||
|
<ToSvelte construct={new OpeningHoursVisualization()}
|
||||||
|
</main>
|
||||||
|
|
|
@ -124,11 +124,11 @@
|
||||||
state.mapProperties.installCustomKeyboardHandler(viewport)
|
state.mapProperties.installCustomKeyboardHandler(viewport)
|
||||||
let canZoomIn = mapproperties.maxzoom.map(
|
let canZoomIn = mapproperties.maxzoom.map(
|
||||||
(mz) => mapproperties.zoom.data < mz,
|
(mz) => mapproperties.zoom.data < mz,
|
||||||
[mapproperties.zoom]
|
[mapproperties.zoom],
|
||||||
)
|
)
|
||||||
let canZoomOut = mapproperties.minzoom.map(
|
let canZoomOut = mapproperties.minzoom.map(
|
||||||
(mz) => mapproperties.zoom.data > mz,
|
(mz) => mapproperties.zoom.data > mz,
|
||||||
[mapproperties.zoom]
|
[mapproperties.zoom],
|
||||||
)
|
)
|
||||||
|
|
||||||
function updateViewport() {
|
function updateViewport() {
|
||||||
|
@ -165,7 +165,7 @@
|
||||||
onDestroy(
|
onDestroy(
|
||||||
rasterLayer.addCallbackAndRunD((l) => {
|
rasterLayer.addCallbackAndRunD((l) => {
|
||||||
rasterLayerName = l.properties.name
|
rasterLayerName = l.properties.name
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
let previewedImage = state.previewedImage
|
let previewedImage = state.previewedImage
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@
|
||||||
let openMapButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
let openMapButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||||
let openMenuButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
let openMenuButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||||
let openCurrentViewLayerButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(
|
let openCurrentViewLayerButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(
|
||||||
undefined
|
undefined,
|
||||||
)
|
)
|
||||||
let _openNewElementButton: HTMLButtonElement
|
let _openNewElementButton: HTMLButtonElement
|
||||||
let openNewElementButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
let openNewElementButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||||
|
@ -478,7 +478,7 @@
|
||||||
<FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
|
<FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
|
||||||
<span slot="close-button"><!-- Disable the close button --></span>
|
<span slot="close-button"><!-- Disable the close button --></span>
|
||||||
<TabbedGroup
|
<TabbedGroup
|
||||||
condition1={state.featureSwitches.featureSwitchFilter}
|
condition1={state.featureSwitches.featureSwitchEnableExport}
|
||||||
tab={state.guistate.themeViewTabIndex}
|
tab={state.guistate.themeViewTabIndex}
|
||||||
>
|
>
|
||||||
<div slot="post-tablist">
|
<div slot="post-tablist">
|
||||||
|
@ -572,9 +572,14 @@
|
||||||
<div class="link-underline links-w-full m-2 flex flex-col gap-y-1" slot="content0">
|
<div class="link-underline links-w-full m-2 flex flex-col gap-y-1" slot="content0">
|
||||||
<Tr t={Translations.t.general.aboutMapComplete.intro} />
|
<Tr t={Translations.t.general.aboutMapComplete.intro} />
|
||||||
|
|
||||||
|
|
||||||
<a class="flex" href={Utils.HomepageLink()}>
|
<a class="flex" href={Utils.HomepageLink()}>
|
||||||
<Add class="h-6 w-6" />
|
<Add class="h-6 w-6" />
|
||||||
<Tr t={Translations.t.general.backToIndex} />
|
{#if Utils.isIframe}
|
||||||
|
<Tr t={Translations.t.general.seeIndex} />
|
||||||
|
{:else}
|
||||||
|
<Tr t={Translations.t.general.backToIndex} />
|
||||||
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a class="flex" href="https://github.com/pietervdvn/MapComplete/" target="_blank">
|
<a class="flex" href="https://github.com/pietervdvn/MapComplete/" target="_blank">
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import colors from "./assets/colors.json"
|
|
||||||
import DOMPurify from "dompurify"
|
import DOMPurify from "dompurify"
|
||||||
|
|
||||||
export class Utils {
|
export class Utils {
|
||||||
|
@ -8,7 +7,6 @@ export class Utils {
|
||||||
* This is a workaround and yet another hack
|
* This is a workaround and yet another hack
|
||||||
*/
|
*/
|
||||||
public static runningFromConsole = typeof window === "undefined"
|
public static runningFromConsole = typeof window === "undefined"
|
||||||
public static readonly assets_path = "./assets/svg/"
|
|
||||||
public static externalDownloadFunction: (
|
public static externalDownloadFunction: (
|
||||||
url: string,
|
url: string,
|
||||||
headers?: any
|
headers?: any
|
||||||
|
@ -146,6 +144,9 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
}
|
}
|
||||||
>()
|
>()
|
||||||
|
|
||||||
|
public static readonly isIframe = !Utils.runningFromConsole && window !== window.top
|
||||||
|
|
||||||
|
|
||||||
public static initDomPurify() {
|
public static initDomPurify() {
|
||||||
if (Utils.runningFromConsole) {
|
if (Utils.runningFromConsole) {
|
||||||
return
|
return
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue