Merge branch 'develop' into RobinLinde-patch-1
This commit is contained in:
commit
0e531ca017
15 changed files with 446 additions and 317 deletions
|
@ -1619,7 +1619,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "multilevels",
|
"id": "multilevels",
|
||||||
"builtin": "level",
|
"builtin": "single_level",
|
||||||
"override": {
|
"override": {
|
||||||
"question": {
|
"question": {
|
||||||
"en": "What levels does this elevator go to?",
|
"en": "What levels does this elevator go to?",
|
||||||
|
@ -1657,7 +1657,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "level",
|
"id": "repeated",
|
||||||
|
"labels": ["level"],
|
||||||
|
"condition": "repeat_on~*",
|
||||||
|
"render": {
|
||||||
|
"en": "Multiple, identical objects can be found on floors {repeat_on}.",
|
||||||
|
"nl": "Er zijn verschillende, identieke objecten op verdiepingen {repeat_on}."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "single_level",
|
||||||
|
"labels": ["level"],
|
||||||
|
"condition": "repeat_on=",
|
||||||
"question": {
|
"question": {
|
||||||
"nl": "Op welke verdieping bevindt dit punt zich?",
|
"nl": "Op welke verdieping bevindt dit punt zich?",
|
||||||
"en": "On what level is this feature located?",
|
"en": "On what level is this feature located?",
|
||||||
|
|
3
config.json
Normal file
3
config.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"#": "Settings in this file override the `config`-section of `package.json`"
|
||||||
|
}
|
|
@ -197,6 +197,7 @@
|
||||||
"example": "Example",
|
"example": "Example",
|
||||||
"examples": "Examples",
|
"examples": "Examples",
|
||||||
"fewChangesBefore": "Please, answer a few questions of existing features before adding a new feature.",
|
"fewChangesBefore": "Please, answer a few questions of existing features before adding a new feature.",
|
||||||
|
"geopermissionDenied": "Using the geolocation was denied",
|
||||||
"getStartedLogin": "Log in with OpenStreetMap to get started",
|
"getStartedLogin": "Log in with OpenStreetMap to get started",
|
||||||
"getStartedNewAccount": " or <a href='https://www.openstreetmap.org/user/new' target='_blank'>create a new account</a>",
|
"getStartedNewAccount": " or <a href='https://www.openstreetmap.org/user/new' target='_blank'>create a new account</a>",
|
||||||
"goToInbox": "Open inbox",
|
"goToInbox": "Open inbox",
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"config": {
|
"config": {
|
||||||
"#": "Various endpoints that are instance-specific",
|
"#": "Various endpoints that are instance-specific. This is the default configuration, which is re-exported in 'Constants.ts'.",
|
||||||
|
"#": "Use MAPCOMPLETE_CONFIGURATION to use an additional configuration, e.g. `MAPCOMPLETE_CONFIGURATION=config_hetzner`",
|
||||||
"#oauth_credentials:comment": [
|
"#oauth_credentials:comment": [
|
||||||
"`oauth_credentials` are the OAuth-2 credentials for the production-OSM server and the test-server.",
|
"`oauth_credentials` are the OAuth-2 credentials for the production-OSM server and the test-server.",
|
||||||
"Are you deploying your own instance? Register your application too.",
|
"Are you deploying your own instance? Register your application too.",
|
||||||
|
|
4
scripts/hetzner/config.json
Normal file
4
scripts/hetzner/config.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"#":"Some configuration tweaks specifically for hetzner",
|
||||||
|
"country_coder_host": "https://countrycoder.mapcomplete.org/"
|
||||||
|
}
|
21
scripts/hetzner/config/Caddyfile
Normal file
21
scripts/hetzner/config/Caddyfile
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
hosted.mapcomplete.org {
|
||||||
|
root * public/
|
||||||
|
file_server
|
||||||
|
header {
|
||||||
|
+Permissions-Policy "interest-cohort=()"
|
||||||
|
+Report-To `\{"group":"csp-endpoint", "max_age": 86400,"endpoints": [\{"url": "https://report.mapcomplete.org/csp"}], "include_subdomains": true}`
|
||||||
|
+Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' https://gc.zgo.at ; img-src * ; report-uri https://report.mapcomplete.org/csp ; report-to csp-endpoint ;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
countrycoder.mapcomplete.org {
|
||||||
|
root * tiles/
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
report.mapcomplete.org {
|
||||||
|
reverse_proxy {
|
||||||
|
to http://127.0.0.1:2600
|
||||||
|
}
|
||||||
|
}
|
7
scripts/hetzner/config/csp-logger-config.json
Normal file
7
scripts/hetzner/config/csp-logger-config.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"store": "console",
|
||||||
|
"allowedOrigin": null,
|
||||||
|
"port": 2600,
|
||||||
|
"domainWhitelist": ["localhost:10179", "localhost:2600","hosted.mapcomplete.org", "dev.mapcomplete.org", "mapcomplete.org","*"],
|
||||||
|
"sourceBlacklist": ["chrome-extension://gighmmpiobklfepjocnamgkkbiglidom"]
|
||||||
|
}
|
23
scripts/hetzner/deployHetzner.sh
Executable file
23
scripts/hetzner/deployHetzner.sh
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
#! /bin/bash
|
||||||
|
### To be run from the root of the repository
|
||||||
|
|
||||||
|
# Some pointers to get started:
|
||||||
|
# apt install npm
|
||||||
|
# apt install unzip
|
||||||
|
# npm i -g csp-logger
|
||||||
|
|
||||||
|
# wget https://github.com/pietervdvn/latlon2country/raw/main/tiles.zip
|
||||||
|
# unzip tiles.zip
|
||||||
|
|
||||||
|
MAPCOMPLETE_CONFIGURATION="config_hetzner"
|
||||||
|
npm run reset:layeroverview
|
||||||
|
npm run test
|
||||||
|
cp config.json config.json.bu &&
|
||||||
|
cp ./scripts/hetzner/config.json . &&
|
||||||
|
npm run prepare-deploy &&
|
||||||
|
mv config.json.bu config.json &&
|
||||||
|
zip dist.zip -r dist/* &&
|
||||||
|
scp -r dist.zip hetzner:/root/ &&
|
||||||
|
scp ./scripts/hetzner/config/* hetzner:/root/
|
||||||
|
ssh hetzner -t "unzip dist.zip && rm dist.zip && rm -rf public/ && mv dist public && caddy stop && caddy start"
|
||||||
|
rm dist.zip
|
|
@ -339,21 +339,37 @@ export default class SimpleMetaTaggers {
|
||||||
)
|
)
|
||||||
private static levels = new InlineMetaTagger(
|
private static levels = new InlineMetaTagger(
|
||||||
{
|
{
|
||||||
doc: "Extract the 'level'-tag into a normalized, ';'-separated value",
|
doc: "Extract the 'level'-tag into a normalized, ';'-separated value called '_level' (which also includes 'repeat_on'). The `level` tag (without underscore) will be normalized with only the value of `level`.",
|
||||||
keys: ["_level"],
|
keys: ["_level"],
|
||||||
},
|
},
|
||||||
(feature) => {
|
(feature) => {
|
||||||
if (feature.properties["level"] === undefined) {
|
let somethingChanged = false
|
||||||
return false
|
if (feature.properties["level"] !== undefined) {
|
||||||
}
|
|
||||||
|
|
||||||
const l = feature.properties["level"]
|
const l = feature.properties["level"]
|
||||||
const newValue = TagUtils.LevelsParser(l).join(";")
|
const newValue = TagUtils.LevelsParser(l).join(";")
|
||||||
if (l === newValue) {
|
if (l !== newValue) {
|
||||||
return false
|
|
||||||
}
|
|
||||||
feature.properties["level"] = newValue
|
feature.properties["level"] = newValue
|
||||||
return true
|
somethingChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (feature.properties["repeat_on"] !== undefined) {
|
||||||
|
const l = feature.properties["repeat_on"]
|
||||||
|
const newValue = TagUtils.LevelsParser(l).join(";")
|
||||||
|
if (l !== newValue) {
|
||||||
|
feature.properties["repeat_on"] = newValue
|
||||||
|
somethingChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const combined = TagUtils.LevelsParser(
|
||||||
|
(feature.properties.repeat_on ?? "") + ";" + (feature.properties.level ?? "")
|
||||||
|
).join(";")
|
||||||
|
if (feature.properties["_level"] !== combined) {
|
||||||
|
feature.properties["_level"] = combined
|
||||||
|
somethingChanged = true
|
||||||
|
}
|
||||||
|
return somethingChanged
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
private static canonicalize = new InlineMetaTagger(
|
private static canonicalize = new InlineMetaTagger(
|
||||||
|
|
|
@ -66,11 +66,11 @@ export default class LayerState {
|
||||||
}
|
}
|
||||||
const t = Translations.t.general.levelSelection
|
const t = Translations.t.general.levelSelection
|
||||||
const conditionsOrred = [
|
const conditionsOrred = [
|
||||||
new Tag("level", "" + level),
|
new Tag("_level", "" + level),
|
||||||
new RegexTag("level", new RegExp("(.*;)?" + level + "(;.*)?")),
|
new RegexTag("_level", new RegExp("(.*;)?" + level + "(;.*)?")),
|
||||||
]
|
]
|
||||||
if (level === "0") {
|
if (level === "0") {
|
||||||
conditionsOrred.push(new Tag("level", "")) // No level tag is the same as level '0'
|
conditionsOrred.push(new Tag("_level", "")) // No level tag is the same as level '0'
|
||||||
}
|
}
|
||||||
console.log("Setting levels filter to", conditionsOrred)
|
console.log("Setting levels filter to", conditionsOrred)
|
||||||
this.globalFilters.data.push({
|
this.globalFilters.data.push({
|
||||||
|
|
|
@ -483,13 +483,22 @@ export class TagUtils {
|
||||||
* TagUtils.LevelsParser("-1") // => ["-1"]
|
* TagUtils.LevelsParser("-1") // => ["-1"]
|
||||||
* TagUtils.LevelsParser("0;-1") // => ["0", "-1"]
|
* TagUtils.LevelsParser("0;-1") // => ["0", "-1"]
|
||||||
* TagUtils.LevelsParser(undefined) // => []
|
* TagUtils.LevelsParser(undefined) // => []
|
||||||
|
* TagUtils.LevelsParser("") // => []
|
||||||
|
* TagUtils.LevelsParser(";") // => []
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public static LevelsParser(level: string): string[] {
|
public static LevelsParser(level: string): string[] {
|
||||||
|
if (level === undefined || level === null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
let spec = Utils.NoNull([level])
|
let spec = Utils.NoNull([level])
|
||||||
spec = [].concat(...spec.map((s) => s?.split(";")))
|
spec = [].concat(...spec.map((s) => s?.split(";")))
|
||||||
spec = [].concat(
|
spec = [].concat(
|
||||||
...spec.map((s) => {
|
...spec.map((s) => {
|
||||||
s = s.trim()
|
s = s.trim()
|
||||||
|
if (s === "") {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
if (s.indexOf("-") < 0 || s.startsWith("-")) {
|
if (s.indexOf("-") < 0 || s.startsWith("-")) {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
import * as meta from "../../package.json"
|
import * as packagefile from "../../package.json"
|
||||||
|
import * as extraconfig from "../../config.json"
|
||||||
import { Utils } from "../Utils"
|
import { Utils } from "../Utils"
|
||||||
|
|
||||||
export type PriviligedLayerType = (typeof Constants.priviliged_layers)[number]
|
export type PriviligedLayerType = (typeof Constants.priviliged_layers)[number]
|
||||||
|
|
||||||
export default class Constants {
|
export default class Constants {
|
||||||
public static vNumber = meta.version
|
public static vNumber = packagefile.version
|
||||||
|
|
||||||
public static ImgurApiKey = meta.config.api_keys.imgur
|
|
||||||
public static readonly mapillary_client_token_v4 = meta.config.api_keys.mapillary_v4
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API key for Maproulette
|
* API key for Maproulette
|
||||||
*
|
*
|
||||||
|
@ -17,9 +14,6 @@ export default class Constants {
|
||||||
* Using an empty string however does work for most actions, but will attribute all actions to the Superuser.
|
* Using an empty string however does work for most actions, but will attribute all actions to the Superuser.
|
||||||
*/
|
*/
|
||||||
public static readonly MaprouletteApiKey = ""
|
public static readonly MaprouletteApiKey = ""
|
||||||
|
|
||||||
public static defaultOverpassUrls = meta.config.default_overpass_urls
|
|
||||||
|
|
||||||
public static readonly added_by_default = [
|
public static readonly added_by_default = [
|
||||||
"selected_element",
|
"selected_element",
|
||||||
"gps_location",
|
"gps_location",
|
||||||
|
@ -47,7 +41,6 @@ export default class Constants {
|
||||||
...Constants.added_by_default,
|
...Constants.added_by_default,
|
||||||
...Constants.no_include,
|
...Constants.no_include,
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
// The user journey states thresholds when a new feature gets unlocked
|
// The user journey states thresholds when a new feature gets unlocked
|
||||||
public static userJourney = {
|
public static userJourney = {
|
||||||
moreScreenUnlock: 1,
|
moreScreenUnlock: 1,
|
||||||
|
@ -104,7 +97,14 @@ export default class Constants {
|
||||||
* In seconds
|
* In seconds
|
||||||
*/
|
*/
|
||||||
static zoomToLocationTimeout = 15
|
static zoomToLocationTimeout = 15
|
||||||
static countryCoderEndpoint: string = meta.config.country_coder_host
|
private static readonly config = (() => {
|
||||||
|
const defaultConfig = packagefile.config
|
||||||
|
return { ...defaultConfig, ...extraconfig }
|
||||||
|
})()
|
||||||
|
public static ImgurApiKey = Constants.config.api_keys.imgur
|
||||||
|
public static readonly mapillary_client_token_v4 = Constants.config.api_keys.mapillary_v4
|
||||||
|
public static defaultOverpassUrls = Constants.config.default_overpass_urls
|
||||||
|
static countryCoderEndpoint: string = Constants.config.country_coder_host
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These are the values that are allowed to use as 'backdrop' icon for a map pin
|
* These are the values that are allowed to use as 'backdrop' icon for a map pin
|
||||||
|
|
|
@ -1,58 +1,62 @@
|
||||||
import LayoutConfig from "./ThemeConfig/LayoutConfig";
|
import LayoutConfig from "./ThemeConfig/LayoutConfig"
|
||||||
import { SpecialVisualizationState } from "../UI/SpecialVisualization";
|
import { SpecialVisualizationState } from "../UI/SpecialVisualization"
|
||||||
import { Changes } from "../Logic/Osm/Changes";
|
import { Changes } from "../Logic/Osm/Changes"
|
||||||
import { Store, UIEventSource } from "../Logic/UIEventSource";
|
import { Store, UIEventSource } from "../Logic/UIEventSource"
|
||||||
import { FeatureSource, IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource";
|
|
||||||
import { OsmConnection } from "../Logic/Osm/OsmConnection";
|
|
||||||
import { ExportableMap, MapProperties } from "./MapProperties";
|
|
||||||
import LayerState from "../Logic/State/LayerState";
|
|
||||||
import { Feature, Point, Polygon } from "geojson";
|
|
||||||
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource";
|
|
||||||
import { Map as MlMap } from "maplibre-gl";
|
|
||||||
import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning";
|
|
||||||
import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor";
|
|
||||||
import { GeoLocationState } from "../Logic/State/GeoLocationState";
|
|
||||||
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
|
|
||||||
import { QueryParameters } from "../Logic/Web/QueryParameters";
|
|
||||||
import UserRelatedState from "../Logic/State/UserRelatedState";
|
|
||||||
import LayerConfig from "./ThemeConfig/LayerConfig";
|
|
||||||
import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler";
|
|
||||||
import { AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils } from "./RasterLayers";
|
|
||||||
import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource";
|
|
||||||
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource";
|
|
||||||
import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore";
|
|
||||||
import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter";
|
|
||||||
import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource";
|
|
||||||
import ShowDataLayer from "../UI/Map/ShowDataLayer";
|
|
||||||
import TitleHandler from "../Logic/Actors/TitleHandler";
|
|
||||||
import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor";
|
|
||||||
import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader";
|
|
||||||
import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater";
|
|
||||||
import { BBox } from "../Logic/BBox";
|
|
||||||
import Constants from "./Constants";
|
|
||||||
import Hotkeys from "../UI/Base/Hotkeys";
|
|
||||||
import Translations from "../UI/i18n/Translations";
|
|
||||||
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore";
|
|
||||||
import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource";
|
|
||||||
import { MenuState } from "./MenuState";
|
|
||||||
import MetaTagging from "../Logic/MetaTagging";
|
|
||||||
import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator";
|
|
||||||
import {
|
import {
|
||||||
NewGeometryFromChangesFeatureSource
|
FeatureSource,
|
||||||
} from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource";
|
IndexedFeatureSource,
|
||||||
import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader";
|
WritableFeatureSource,
|
||||||
import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer";
|
} from "../Logic/FeatureSource/FeatureSource"
|
||||||
import { Utils } from "../Utils";
|
import { OsmConnection } from "../Logic/Osm/OsmConnection"
|
||||||
import { EliCategory } from "./RasterLayerProperties";
|
import { ExportableMap, MapProperties } from "./MapProperties"
|
||||||
import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter";
|
import LayerState from "../Logic/State/LayerState"
|
||||||
import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage";
|
import { Feature, Point, Polygon } from "geojson"
|
||||||
import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource";
|
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
|
||||||
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor";
|
import { Map as MlMap } from "maplibre-gl"
|
||||||
import NoElementsInViewDetector, { FeatureViewState } from "../Logic/Actors/NoElementsInViewDetector";
|
import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning"
|
||||||
import FilteredLayer from "./FilteredLayer";
|
import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor"
|
||||||
import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector";
|
import { GeoLocationState } from "../Logic/State/GeoLocationState"
|
||||||
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager";
|
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
|
||||||
import { Imgur } from "../Logic/ImageProviders/Imgur";
|
import { QueryParameters } from "../Logic/Web/QueryParameters"
|
||||||
|
import UserRelatedState from "../Logic/State/UserRelatedState"
|
||||||
|
import LayerConfig from "./ThemeConfig/LayerConfig"
|
||||||
|
import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
|
||||||
|
import { AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils } from "./RasterLayers"
|
||||||
|
import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource"
|
||||||
|
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"
|
||||||
|
import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore"
|
||||||
|
import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter"
|
||||||
|
import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource"
|
||||||
|
import ShowDataLayer from "../UI/Map/ShowDataLayer"
|
||||||
|
import TitleHandler from "../Logic/Actors/TitleHandler"
|
||||||
|
import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor"
|
||||||
|
import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader"
|
||||||
|
import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater"
|
||||||
|
import { BBox } from "../Logic/BBox"
|
||||||
|
import Constants from "./Constants"
|
||||||
|
import Hotkeys from "../UI/Base/Hotkeys"
|
||||||
|
import Translations from "../UI/i18n/Translations"
|
||||||
|
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"
|
||||||
|
import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"
|
||||||
|
import { MenuState } from "./MenuState"
|
||||||
|
import MetaTagging from "../Logic/MetaTagging"
|
||||||
|
import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator"
|
||||||
|
import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource"
|
||||||
|
import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"
|
||||||
|
import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer"
|
||||||
|
import { Utils } from "../Utils"
|
||||||
|
import { EliCategory } from "./RasterLayerProperties"
|
||||||
|
import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter"
|
||||||
|
import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage"
|
||||||
|
import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
|
||||||
|
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"
|
||||||
|
import NoElementsInViewDetector, {
|
||||||
|
FeatureViewState,
|
||||||
|
} from "../Logic/Actors/NoElementsInViewDetector"
|
||||||
|
import FilteredLayer from "./FilteredLayer"
|
||||||
|
import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"
|
||||||
|
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
|
||||||
|
import { Imgur } from "../Logic/ImageProviders/Imgur"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -63,71 +67,71 @@ import { Imgur } from "../Logic/ImageProviders/Imgur";
|
||||||
* It ties up all the needed elements and starts some actors.
|
* It ties up all the needed elements and starts some actors.
|
||||||
*/
|
*/
|
||||||
export default class ThemeViewState implements SpecialVisualizationState {
|
export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
readonly layout: LayoutConfig;
|
readonly layout: LayoutConfig
|
||||||
readonly map: UIEventSource<MlMap>;
|
readonly map: UIEventSource<MlMap>
|
||||||
readonly changes: Changes;
|
readonly changes: Changes
|
||||||
readonly featureSwitches: FeatureSwitchState;
|
readonly featureSwitches: FeatureSwitchState
|
||||||
readonly featureSwitchIsTesting: Store<boolean>;
|
readonly featureSwitchIsTesting: Store<boolean>
|
||||||
readonly featureSwitchUserbadge: Store<boolean>;
|
readonly featureSwitchUserbadge: Store<boolean>
|
||||||
|
|
||||||
readonly featureProperties: FeaturePropertiesStore;
|
readonly featureProperties: FeaturePropertiesStore
|
||||||
|
|
||||||
readonly osmConnection: OsmConnection;
|
readonly osmConnection: OsmConnection
|
||||||
readonly selectedElement: UIEventSource<Feature>;
|
readonly selectedElement: UIEventSource<Feature>
|
||||||
readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>;
|
readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>
|
||||||
readonly mapProperties: MapProperties & ExportableMap;
|
readonly mapProperties: MapProperties & ExportableMap
|
||||||
readonly osmObjectDownloader: OsmObjectDownloader;
|
readonly osmObjectDownloader: OsmObjectDownloader
|
||||||
|
|
||||||
readonly dataIsLoading: Store<boolean>;
|
readonly dataIsLoading: Store<boolean>
|
||||||
/**
|
/**
|
||||||
* Indicates if there is _some_ data in view, even if it is not shown due to the filters
|
* Indicates if there is _some_ data in view, even if it is not shown due to the filters
|
||||||
*/
|
*/
|
||||||
readonly hasDataInView: Store<FeatureViewState>;
|
readonly hasDataInView: Store<FeatureViewState>
|
||||||
|
|
||||||
readonly guistate: MenuState;
|
readonly guistate: MenuState
|
||||||
readonly fullNodeDatabase?: FullNodeDatabaseSource;
|
readonly fullNodeDatabase?: FullNodeDatabaseSource
|
||||||
|
|
||||||
readonly historicalUserLocations: WritableFeatureSource<Feature<Point>>;
|
readonly historicalUserLocations: WritableFeatureSource<Feature<Point>>
|
||||||
readonly indexedFeatures: IndexedFeatureSource & LayoutSource;
|
readonly indexedFeatures: IndexedFeatureSource & LayoutSource
|
||||||
readonly currentView: FeatureSource<Feature<Polygon>>;
|
readonly currentView: FeatureSource<Feature<Polygon>>
|
||||||
readonly featuresInView: FeatureSource;
|
readonly featuresInView: FeatureSource
|
||||||
readonly newFeatures: WritableFeatureSource;
|
readonly newFeatures: WritableFeatureSource
|
||||||
readonly layerState: LayerState;
|
readonly layerState: LayerState
|
||||||
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>;
|
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>
|
||||||
readonly perLayerFiltered: ReadonlyMap<string, FilteringFeatureSource>;
|
readonly perLayerFiltered: ReadonlyMap<string, FilteringFeatureSource>
|
||||||
|
|
||||||
readonly availableLayers: Store<RasterLayerPolygon[]>;
|
readonly availableLayers: Store<RasterLayerPolygon[]>
|
||||||
readonly selectedLayer: UIEventSource<LayerConfig>;
|
readonly selectedLayer: UIEventSource<LayerConfig>
|
||||||
readonly userRelatedState: UserRelatedState;
|
readonly userRelatedState: UserRelatedState
|
||||||
readonly geolocation: GeoLocationHandler;
|
readonly geolocation: GeoLocationHandler
|
||||||
|
|
||||||
readonly imageUploadManager: ImageUploadManager
|
readonly imageUploadManager: ImageUploadManager
|
||||||
|
|
||||||
readonly lastClickObject: WritableFeatureSource;
|
readonly lastClickObject: WritableFeatureSource
|
||||||
readonly overlayLayerStates: ReadonlyMap<
|
readonly overlayLayerStates: ReadonlyMap<
|
||||||
string,
|
string,
|
||||||
{ readonly isDisplayed: UIEventSource<boolean> }
|
{ readonly isDisplayed: UIEventSource<boolean> }
|
||||||
>;
|
>
|
||||||
/**
|
/**
|
||||||
* All 'level'-tags that are available with the current features
|
* All 'level'-tags that are available with the current features
|
||||||
*/
|
*/
|
||||||
readonly floors: Store<string[]>;
|
readonly floors: Store<string[]>
|
||||||
|
|
||||||
constructor(layout: LayoutConfig) {
|
constructor(layout: LayoutConfig) {
|
||||||
Utils.initDomPurify();
|
Utils.initDomPurify()
|
||||||
this.layout = layout;
|
this.layout = layout
|
||||||
this.featureSwitches = new FeatureSwitchState(layout);
|
this.featureSwitches = new FeatureSwitchState(layout)
|
||||||
this.guistate = new MenuState(
|
this.guistate = new MenuState(
|
||||||
this.featureSwitches.featureSwitchWelcomeMessage.data,
|
this.featureSwitches.featureSwitchWelcomeMessage.data,
|
||||||
layout.id
|
layout.id
|
||||||
);
|
)
|
||||||
this.map = new UIEventSource<MlMap>(undefined);
|
this.map = new UIEventSource<MlMap>(undefined)
|
||||||
const initial = new InitialMapPositioning(layout);
|
const initial = new InitialMapPositioning(layout)
|
||||||
this.mapProperties = new MapLibreAdaptor(this.map, initial);
|
this.mapProperties = new MapLibreAdaptor(this.map, initial)
|
||||||
const geolocationState = new GeoLocationState();
|
const geolocationState = new GeoLocationState()
|
||||||
|
|
||||||
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting;
|
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting
|
||||||
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin;
|
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin
|
||||||
|
|
||||||
this.osmConnection = new OsmConnection({
|
this.osmConnection = new OsmConnection({
|
||||||
dryRun: this.featureSwitches.featureSwitchIsTesting,
|
dryRun: this.featureSwitches.featureSwitchIsTesting,
|
||||||
|
@ -137,68 +141,66 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
undefined,
|
undefined,
|
||||||
"Used to complete the login"
|
"Used to complete the login"
|
||||||
),
|
),
|
||||||
osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data
|
osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data,
|
||||||
});
|
})
|
||||||
this.userRelatedState = new UserRelatedState(
|
this.userRelatedState = new UserRelatedState(
|
||||||
this.osmConnection,
|
this.osmConnection,
|
||||||
layout?.language,
|
layout?.language,
|
||||||
layout,
|
layout,
|
||||||
this.featureSwitches,
|
this.featureSwitches,
|
||||||
this.mapProperties
|
this.mapProperties
|
||||||
);
|
)
|
||||||
this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => {
|
this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => {
|
||||||
this.mapProperties.allowRotating.setData(fixated !== "yes");
|
this.mapProperties.allowRotating.setData(fixated !== "yes")
|
||||||
});
|
})
|
||||||
this.selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element");
|
this.selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element")
|
||||||
this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer");
|
this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer")
|
||||||
|
|
||||||
this.selectedElementAndLayer = this.selectedElement.mapD(
|
this.selectedElementAndLayer = this.selectedElement.mapD(
|
||||||
(feature) => {
|
(feature) => {
|
||||||
const layer = this.selectedLayer.data;
|
const layer = this.selectedLayer.data
|
||||||
if (!layer) {
|
if (!layer) {
|
||||||
return undefined;
|
return undefined
|
||||||
}
|
}
|
||||||
return { layer, feature };
|
return { layer, feature }
|
||||||
},
|
},
|
||||||
[this.selectedLayer]
|
[this.selectedLayer]
|
||||||
);
|
)
|
||||||
|
|
||||||
this.geolocation = new GeoLocationHandler(
|
this.geolocation = new GeoLocationHandler(
|
||||||
geolocationState,
|
geolocationState,
|
||||||
this.selectedElement,
|
this.selectedElement,
|
||||||
this.mapProperties,
|
this.mapProperties,
|
||||||
this.userRelatedState.gpsLocationHistoryRetentionTime
|
this.userRelatedState.gpsLocationHistoryRetentionTime
|
||||||
);
|
)
|
||||||
|
|
||||||
this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location);
|
this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location)
|
||||||
|
|
||||||
|
const self = this
|
||||||
const self = this;
|
this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id)
|
||||||
this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const overlayLayerStates = new Map<string, { isDisplayed: UIEventSource<boolean> }>();
|
const overlayLayerStates = new Map<string, { isDisplayed: UIEventSource<boolean> }>()
|
||||||
for (const rasterInfo of this.layout.tileLayerSources) {
|
for (const rasterInfo of this.layout.tileLayerSources) {
|
||||||
const isDisplayed = QueryParameters.GetBooleanQueryParameter(
|
const isDisplayed = QueryParameters.GetBooleanQueryParameter(
|
||||||
"overlay-" + rasterInfo.id,
|
"overlay-" + rasterInfo.id,
|
||||||
rasterInfo.defaultState ?? true,
|
rasterInfo.defaultState ?? true,
|
||||||
"Wether or not overlayer layer " + rasterInfo.id + " is shown"
|
"Wether or not overlayer layer " + rasterInfo.id + " is shown"
|
||||||
);
|
)
|
||||||
const state = { isDisplayed };
|
const state = { isDisplayed }
|
||||||
overlayLayerStates.set(rasterInfo.id, state);
|
overlayLayerStates.set(rasterInfo.id, state)
|
||||||
new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state);
|
new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state)
|
||||||
}
|
}
|
||||||
this.overlayLayerStates = overlayLayerStates;
|
this.overlayLayerStates = overlayLayerStates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
/* Setup the layout source
|
/* Setup the layout source
|
||||||
* A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too
|
* A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (this.layout.layers.some((l) => l._needsFullNodeDatabase)) {
|
if (this.layout.layers.some((l) => l._needsFullNodeDatabase)) {
|
||||||
this.fullNodeDatabase = new FullNodeDatabaseSource();
|
this.fullNodeDatabase = new FullNodeDatabaseSource()
|
||||||
}
|
}
|
||||||
|
|
||||||
const layoutSource = new LayoutSource(
|
const layoutSource = new LayoutSource(
|
||||||
|
@ -208,49 +210,49 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
this.osmConnection.Backend(),
|
this.osmConnection.Backend(),
|
||||||
(id) => self.layerState.filteredLayers.get(id).isDisplayed,
|
(id) => self.layerState.filteredLayers.get(id).isDisplayed,
|
||||||
this.fullNodeDatabase
|
this.fullNodeDatabase
|
||||||
);
|
)
|
||||||
|
|
||||||
this.indexedFeatures = layoutSource;
|
this.indexedFeatures = layoutSource
|
||||||
|
|
||||||
const empty = [];
|
const empty = []
|
||||||
let currentViewIndex = 0;
|
let currentViewIndex = 0
|
||||||
this.currentView = new StaticFeatureSource(
|
this.currentView = new StaticFeatureSource(
|
||||||
this.mapProperties.bounds.map((bbox) => {
|
this.mapProperties.bounds.map((bbox) => {
|
||||||
if (!bbox) {
|
if (!bbox) {
|
||||||
return empty;
|
return empty
|
||||||
}
|
}
|
||||||
currentViewIndex++;
|
currentViewIndex++
|
||||||
return <Feature[]>[
|
return <Feature[]>[
|
||||||
bbox.asGeoJson({
|
bbox.asGeoJson({
|
||||||
zoom: this.mapProperties.zoom.data,
|
zoom: this.mapProperties.zoom.data,
|
||||||
...this.mapProperties.location.data,
|
...this.mapProperties.location.data,
|
||||||
id: "current_view"
|
id: "current_view",
|
||||||
|
}),
|
||||||
|
]
|
||||||
})
|
})
|
||||||
];
|
)
|
||||||
})
|
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds)
|
||||||
);
|
this.dataIsLoading = layoutSource.isLoading
|
||||||
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds);
|
|
||||||
this.dataIsLoading = layoutSource.isLoading;
|
|
||||||
|
|
||||||
const indexedElements = this.indexedFeatures;
|
const indexedElements = this.indexedFeatures
|
||||||
this.featureProperties = new FeaturePropertiesStore(indexedElements);
|
this.featureProperties = new FeaturePropertiesStore(indexedElements)
|
||||||
this.changes = new Changes(
|
this.changes = new Changes(
|
||||||
{
|
{
|
||||||
dryRun: this.featureSwitches.featureSwitchIsTesting,
|
dryRun: this.featureSwitches.featureSwitchIsTesting,
|
||||||
allElements: indexedElements,
|
allElements: indexedElements,
|
||||||
featurePropertiesStore: this.featureProperties,
|
featurePropertiesStore: this.featureProperties,
|
||||||
osmConnection: this.osmConnection,
|
osmConnection: this.osmConnection,
|
||||||
historicalUserLocations: this.geolocation.historicalUserLocations
|
historicalUserLocations: this.geolocation.historicalUserLocations,
|
||||||
},
|
},
|
||||||
layout?.isLeftRightSensitive() ?? false
|
layout?.isLeftRightSensitive() ?? false
|
||||||
);
|
)
|
||||||
this.historicalUserLocations = this.geolocation.historicalUserLocations;
|
this.historicalUserLocations = this.geolocation.historicalUserLocations
|
||||||
this.newFeatures = new NewGeometryFromChangesFeatureSource(
|
this.newFeatures = new NewGeometryFromChangesFeatureSource(
|
||||||
this.changes,
|
this.changes,
|
||||||
indexedElements,
|
indexedElements,
|
||||||
this.featureProperties
|
this.featureProperties
|
||||||
);
|
)
|
||||||
layoutSource.addSource(this.newFeatures);
|
layoutSource.addSource(this.newFeatures)
|
||||||
|
|
||||||
const perLayer = new PerLayerFeatureSourceSplitter(
|
const perLayer = new PerLayerFeatureSourceSplitter(
|
||||||
Array.from(this.layerState.filteredLayers.values()).filter(
|
Array.from(this.layerState.filteredLayers.values()).filter(
|
||||||
|
@ -266,11 +268,11 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
features.length,
|
features.length,
|
||||||
"leftover features, such as",
|
"leftover features, such as",
|
||||||
features[0].properties
|
features[0].properties
|
||||||
);
|
)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
);
|
this.perLayer = perLayer.perLayer
|
||||||
this.perLayer = perLayer.perLayer;
|
|
||||||
}
|
}
|
||||||
this.perLayer.forEach((fs) => {
|
this.perLayer.forEach((fs) => {
|
||||||
new SaveFeatureSourceToLocalStorage(
|
new SaveFeatureSourceToLocalStorage(
|
||||||
|
@ -280,74 +282,80 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
fs,
|
fs,
|
||||||
this.featureProperties,
|
this.featureProperties,
|
||||||
fs.layer.layerDef.maxAgeOfCache
|
fs.layer.layerDef.maxAgeOfCache
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
|
|
||||||
this.floors = this.featuresInView.features.stabilized(500).map((features) => {
|
this.floors = this.featuresInView.features.stabilized(500).map((features) => {
|
||||||
if (!features) {
|
if (!features) {
|
||||||
return [];
|
return []
|
||||||
}
|
}
|
||||||
const floors = new Set<string>();
|
const floors = new Set<string>()
|
||||||
for (const feature of features) {
|
for (const feature of features) {
|
||||||
const level = feature.properties["level"];
|
let level = feature.properties["_level"]
|
||||||
if (level) {
|
if (level) {
|
||||||
const levels = level.split(";");
|
const levels = level.split(";")
|
||||||
for (const l of levels) {
|
for (const l of levels) {
|
||||||
floors.add(l);
|
floors.add(l)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
floors.add("0"); // '0' is the default and is thus _always_ present
|
floors.add("0") // '0' is the default and is thus _always_ present
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const sorted = Array.from(floors);
|
const sorted = Array.from(floors)
|
||||||
// Sort alphabetically first, to deal with floor "A", "B" and "C"
|
// Sort alphabetically first, to deal with floor "A", "B" and "C"
|
||||||
sorted.sort();
|
sorted.sort()
|
||||||
sorted.sort((a, b) => {
|
sorted.sort((a, b) => {
|
||||||
// We use the laxer 'parseInt' to deal with floor '1A'
|
// We use the laxer 'parseInt' to deal with floor '1A'
|
||||||
const na = parseInt(a);
|
const na = parseInt(a)
|
||||||
const nb = parseInt(b);
|
const nb = parseInt(b)
|
||||||
if (isNaN(na) || isNaN(nb)) {
|
if (isNaN(na) || isNaN(nb)) {
|
||||||
return 0;
|
return 0
|
||||||
}
|
}
|
||||||
return na - nb;
|
return na - nb
|
||||||
});
|
})
|
||||||
sorted.reverse(/* new list, no side-effects */);
|
sorted.reverse(/* new list, no side-effects */)
|
||||||
return sorted;
|
return sorted
|
||||||
});
|
})
|
||||||
|
|
||||||
const lastClick = (this.lastClickObject = new LastClickFeatureSource(
|
const lastClick = (this.lastClickObject = new LastClickFeatureSource(
|
||||||
this.mapProperties.lastClickLocation,
|
this.mapProperties.lastClickLocation,
|
||||||
this.layout
|
this.layout
|
||||||
));
|
))
|
||||||
|
|
||||||
this.osmObjectDownloader = new OsmObjectDownloader(
|
this.osmObjectDownloader = new OsmObjectDownloader(
|
||||||
this.osmConnection.Backend(),
|
this.osmConnection.Backend(),
|
||||||
this.changes
|
this.changes
|
||||||
);
|
)
|
||||||
|
|
||||||
this.perLayerFiltered = this.showNormalDataOn(this.map);
|
this.perLayerFiltered = this.showNormalDataOn(this.map)
|
||||||
|
|
||||||
this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView;
|
this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView
|
||||||
this.imageUploadManager = new ImageUploadManager(layout, Imgur.singleton, this.featureProperties, this.osmConnection, this.changes)
|
this.imageUploadManager = new ImageUploadManager(
|
||||||
|
layout,
|
||||||
|
Imgur.singleton,
|
||||||
|
this.featureProperties,
|
||||||
|
this.osmConnection,
|
||||||
|
this.changes
|
||||||
|
)
|
||||||
|
|
||||||
this.initActors();
|
this.initActors()
|
||||||
this.addLastClick(lastClick);
|
this.addLastClick(lastClick)
|
||||||
this.drawSpecialLayers();
|
this.drawSpecialLayers()
|
||||||
this.initHotkeys();
|
this.initHotkeys()
|
||||||
this.miscSetup();
|
this.miscSetup()
|
||||||
if (!Utils.runningFromConsole) {
|
if (!Utils.runningFromConsole) {
|
||||||
console.log("State setup completed", this);
|
console.log("State setup completed", this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public showNormalDataOn(map: Store<MlMap>): ReadonlyMap<string, FilteringFeatureSource> {
|
public showNormalDataOn(map: Store<MlMap>): ReadonlyMap<string, FilteringFeatureSource> {
|
||||||
const filteringFeatureSource = new Map<string, FilteringFeatureSource>();
|
const filteringFeatureSource = new Map<string, FilteringFeatureSource>()
|
||||||
this.perLayer.forEach((fs, layerName) => {
|
this.perLayer.forEach((fs, layerName) => {
|
||||||
const doShowLayer = this.mapProperties.zoom.map(
|
const doShowLayer = this.mapProperties.zoom.map(
|
||||||
(z) =>
|
(z) =>
|
||||||
(fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0),
|
(fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0),
|
||||||
[fs.layer.isDisplayed]
|
[fs.layer.isDisplayed]
|
||||||
);
|
)
|
||||||
|
|
||||||
if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) {
|
if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) {
|
||||||
/* This layer is hidden and there is no way to enable it (filterview is disabled or this layer doesn't show up in the filter view as the name is not defined)
|
/* This layer is hidden and there is no way to enable it (filterview is disabled or this layer doesn't show up in the filter view as the name is not defined)
|
||||||
|
@ -357,15 +365,15 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
* Note: it is tempting to also permanently disable the layer if it is not visible _and_ the layer name is hidden.
|
* Note: it is tempting to also permanently disable the layer if it is not visible _and_ the layer name is hidden.
|
||||||
* However, this is _not_ correct: the layer might be hidden because zoom is not enough. Zooming in more _will_ reveal the layer!
|
* However, this is _not_ correct: the layer might be hidden because zoom is not enough. Zooming in more _will_ reveal the layer!
|
||||||
* */
|
* */
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
const filtered = new FilteringFeatureSource(
|
const filtered = new FilteringFeatureSource(
|
||||||
fs.layer,
|
fs.layer,
|
||||||
fs,
|
fs,
|
||||||
(id) => this.featureProperties.getStore(id),
|
(id) => this.featureProperties.getStore(id),
|
||||||
this.layerState.globalFilters
|
this.layerState.globalFilters
|
||||||
);
|
)
|
||||||
filteringFeatureSource.set(layerName, filtered);
|
filteringFeatureSource.set(layerName, filtered)
|
||||||
|
|
||||||
new ShowDataLayer(map, {
|
new ShowDataLayer(map, {
|
||||||
layer: fs.layer.layerDef,
|
layer: fs.layer.layerDef,
|
||||||
|
@ -373,30 +381,30 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
doShowLayer,
|
doShowLayer,
|
||||||
selectedElement: this.selectedElement,
|
selectedElement: this.selectedElement,
|
||||||
selectedLayer: this.selectedLayer,
|
selectedLayer: this.selectedLayer,
|
||||||
fetchStore: (id) => this.featureProperties.getStore(id)
|
fetchStore: (id) => this.featureProperties.getStore(id),
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
return filteringFeatureSource;
|
return filteringFeatureSource
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Various small methods that need to be called
|
* Various small methods that need to be called
|
||||||
*/
|
*/
|
||||||
private miscSetup() {
|
private miscSetup() {
|
||||||
this.userRelatedState.markLayoutAsVisited(this.layout);
|
this.userRelatedState.markLayoutAsVisited(this.layout)
|
||||||
|
|
||||||
this.selectedElement.addCallbackAndRunD((feature) => {
|
this.selectedElement.addCallbackAndRunD((feature) => {
|
||||||
// As soon as we have a selected element, we clear the selected element
|
// As soon as we have a selected element, we clear the selected element
|
||||||
// This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature
|
// This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature
|
||||||
// The only exception is if the last element is the 'add_new'-button, as we don't want it to disappear
|
// The only exception is if the last element is the 'add_new'-button, as we don't want it to disappear
|
||||||
if (feature.properties.id === "last_click") {
|
if (feature.properties.id === "last_click") {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
this.lastClickObject.features.setData([]);
|
this.lastClickObject.features.setData([])
|
||||||
});
|
})
|
||||||
|
|
||||||
if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) {
|
if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) {
|
||||||
Utils.LoadCustomCss(this.layout.customCss);
|
Utils.LoadCustomCss(this.layout.customCss)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,74 +413,74 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
{ nomod: "Escape", onUp: true },
|
{ nomod: "Escape", onUp: true },
|
||||||
Translations.t.hotkeyDocumentation.closeSidebar,
|
Translations.t.hotkeyDocumentation.closeSidebar,
|
||||||
() => {
|
() => {
|
||||||
this.selectedElement.setData(undefined);
|
this.selectedElement.setData(undefined)
|
||||||
this.guistate.closeAll();
|
this.guistate.closeAll()
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
Hotkeys.RegisterHotkey(
|
Hotkeys.RegisterHotkey(
|
||||||
{
|
{
|
||||||
nomod: "b"
|
nomod: "b",
|
||||||
},
|
},
|
||||||
Translations.t.hotkeyDocumentation.openLayersPanel,
|
Translations.t.hotkeyDocumentation.openLayersPanel,
|
||||||
() => {
|
() => {
|
||||||
if (this.featureSwitches.featureSwitchFilter.data) {
|
if (this.featureSwitches.featureSwitchFilter.data) {
|
||||||
this.guistate.openFilterView();
|
this.guistate.openFilterView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
Hotkeys.RegisterHotkey(
|
Hotkeys.RegisterHotkey(
|
||||||
{ shift: "O" },
|
{ shift: "O" },
|
||||||
Translations.t.hotkeyDocumentation.selectMapnik,
|
Translations.t.hotkeyDocumentation.selectMapnik,
|
||||||
() => {
|
() => {
|
||||||
this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto);
|
this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto)
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
const setLayerCategory = (category: EliCategory) => {
|
const setLayerCategory = (category: EliCategory) => {
|
||||||
const available = this.availableLayers.data;
|
const available = this.availableLayers.data
|
||||||
const current = this.mapProperties.rasterLayer;
|
const current = this.mapProperties.rasterLayer
|
||||||
const best = RasterLayerUtils.SelectBestLayerAccordingTo(
|
const best = RasterLayerUtils.SelectBestLayerAccordingTo(
|
||||||
available,
|
available,
|
||||||
category,
|
category,
|
||||||
current.data
|
current.data
|
||||||
);
|
)
|
||||||
console.log("Best layer for category", category, "is", best.properties.id);
|
console.log("Best layer for category", category, "is", best.properties.id)
|
||||||
current.setData(best);
|
current.setData(best)
|
||||||
};
|
}
|
||||||
|
|
||||||
Hotkeys.RegisterHotkey(
|
Hotkeys.RegisterHotkey(
|
||||||
{ nomod: "O" },
|
{ nomod: "O" },
|
||||||
Translations.t.hotkeyDocumentation.selectOsmbasedmap,
|
Translations.t.hotkeyDocumentation.selectOsmbasedmap,
|
||||||
() => setLayerCategory("osmbasedmap")
|
() => setLayerCategory("osmbasedmap")
|
||||||
);
|
)
|
||||||
|
|
||||||
Hotkeys.RegisterHotkey({ nomod: "M" }, Translations.t.hotkeyDocumentation.selectMap, () =>
|
Hotkeys.RegisterHotkey({ nomod: "M" }, Translations.t.hotkeyDocumentation.selectMap, () =>
|
||||||
setLayerCategory("map")
|
setLayerCategory("map")
|
||||||
);
|
)
|
||||||
|
|
||||||
Hotkeys.RegisterHotkey(
|
Hotkeys.RegisterHotkey(
|
||||||
{ nomod: "P" },
|
{ nomod: "P" },
|
||||||
Translations.t.hotkeyDocumentation.selectAerial,
|
Translations.t.hotkeyDocumentation.selectAerial,
|
||||||
() => setLayerCategory("photo")
|
() => setLayerCategory("photo")
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private addLastClick(last_click: LastClickFeatureSource) {
|
private addLastClick(last_click: LastClickFeatureSource) {
|
||||||
// The last_click gets a _very_ special treatment as it interacts with various parts
|
// The last_click gets a _very_ special treatment as it interacts with various parts
|
||||||
|
|
||||||
const last_click_layer = this.layerState.filteredLayers.get("last_click");
|
const last_click_layer = this.layerState.filteredLayers.get("last_click")
|
||||||
this.featureProperties.trackFeatureSource(last_click);
|
this.featureProperties.trackFeatureSource(last_click)
|
||||||
this.indexedFeatures.addSource(last_click);
|
this.indexedFeatures.addSource(last_click)
|
||||||
|
|
||||||
last_click.features.addCallbackAndRunD((features) => {
|
last_click.features.addCallbackAndRunD((features) => {
|
||||||
if (this.selectedLayer.data?.id === "last_click") {
|
if (this.selectedLayer.data?.id === "last_click") {
|
||||||
// The last-click location moved, but we have selected the last click of the previous location
|
// The last-click location moved, but we have selected the last click of the previous location
|
||||||
// So, we update _after_ clearing the selection to make sure no stray data is sticking around
|
// So, we update _after_ clearing the selection to make sure no stray data is sticking around
|
||||||
this.selectedElement.setData(undefined);
|
this.selectedElement.setData(undefined)
|
||||||
this.selectedElement.setData(features[0]);
|
this.selectedElement.setData(features[0])
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
new ShowDataLayer(this.map, {
|
new ShowDataLayer(this.map, {
|
||||||
features: new FilteringFeatureSource(last_click_layer, last_click),
|
features: new FilteringFeatureSource(last_click_layer, last_click),
|
||||||
|
@ -484,18 +492,18 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) {
|
if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) {
|
||||||
this.map.data.flyTo({
|
this.map.data.flyTo({
|
||||||
zoom: Constants.minZoomLevelToAddNewPoint,
|
zoom: Constants.minZoomLevelToAddNewPoint,
|
||||||
center: this.mapProperties.lastClickLocation.data
|
center: this.mapProperties.lastClickLocation.data,
|
||||||
});
|
})
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
// We first clear the selection to make sure no weird state is around
|
// We first clear the selection to make sure no weird state is around
|
||||||
this.selectedLayer.setData(undefined);
|
this.selectedLayer.setData(undefined)
|
||||||
this.selectedElement.setData(undefined);
|
this.selectedElement.setData(undefined)
|
||||||
|
|
||||||
this.selectedElement.setData(feature);
|
this.selectedElement.setData(feature)
|
||||||
this.selectedLayer.setData(last_click_layer.layerDef);
|
this.selectedLayer.setData(last_click_layer.layerDef)
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -503,7 +511,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
*/
|
*/
|
||||||
private drawSpecialLayers() {
|
private drawSpecialLayers() {
|
||||||
type AddedByDefaultTypes = (typeof Constants.added_by_default)[number]
|
type AddedByDefaultTypes = (typeof Constants.added_by_default)[number]
|
||||||
const empty = [];
|
const empty = []
|
||||||
/**
|
/**
|
||||||
* A listing which maps the layerId onto the featureSource
|
* A listing which maps the layerId onto the featureSource
|
||||||
*/
|
*/
|
||||||
|
@ -523,21 +531,21 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({ id: "range" })]
|
bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({ id: "range" })]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
current_view: this.currentView
|
current_view: this.currentView,
|
||||||
};
|
}
|
||||||
if (this.layout?.lockLocation) {
|
if (this.layout?.lockLocation) {
|
||||||
const bbox = new BBox(this.layout.lockLocation);
|
const bbox = new BBox(this.layout.lockLocation)
|
||||||
this.mapProperties.maxbounds.setData(bbox);
|
this.mapProperties.maxbounds.setData(bbox)
|
||||||
ShowDataLayer.showRange(
|
ShowDataLayer.showRange(
|
||||||
this.map,
|
this.map,
|
||||||
new StaticFeatureSource([bbox.asGeoJson({})]),
|
new StaticFeatureSource([bbox.asGeoJson({})]),
|
||||||
this.featureSwitches.featureSwitchIsTesting
|
this.featureSwitches.featureSwitchIsTesting
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view");
|
const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view")
|
||||||
if (currentViewLayer?.tagRenderings?.length > 0) {
|
if (currentViewLayer?.tagRenderings?.length > 0) {
|
||||||
const params = MetaTagging.createExtraFuncParams(this);
|
const params = MetaTagging.createExtraFuncParams(this)
|
||||||
this.featureProperties.trackFeatureSource(specialLayers.current_view);
|
this.featureProperties.trackFeatureSource(specialLayers.current_view)
|
||||||
specialLayers.current_view.features.addCallbackAndRunD((features) => {
|
specialLayers.current_view.features.addCallbackAndRunD((features) => {
|
||||||
MetaTagging.addMetatags(
|
MetaTagging.addMetatags(
|
||||||
features,
|
features,
|
||||||
|
@ -546,37 +554,37 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
this.layout,
|
this.layout,
|
||||||
this.osmObjectDownloader,
|
this.osmObjectDownloader,
|
||||||
this.featureProperties
|
this.featureProperties
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range");
|
const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range")
|
||||||
|
|
||||||
const rangeIsDisplayed = rangeFLayer?.isDisplayed;
|
const rangeIsDisplayed = rangeFLayer?.isDisplayed
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef))
|
!QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef))
|
||||||
) {
|
) {
|
||||||
rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true);
|
rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.layerState.filteredLayers.forEach((flayer) => {
|
this.layerState.filteredLayers.forEach((flayer) => {
|
||||||
const id = flayer.layerDef.id;
|
const id = flayer.layerDef.id
|
||||||
const features: FeatureSource = specialLayers[id];
|
const features: FeatureSource = specialLayers[id]
|
||||||
if (features === undefined) {
|
if (features === undefined) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.featureProperties.trackFeatureSource(features);
|
this.featureProperties.trackFeatureSource(features)
|
||||||
// this.indexedFeatures.addSource(features)
|
// this.indexedFeatures.addSource(features)
|
||||||
new ShowDataLayer(this.map, {
|
new ShowDataLayer(this.map, {
|
||||||
features,
|
features,
|
||||||
doShowLayer: flayer.isDisplayed,
|
doShowLayer: flayer.isDisplayed,
|
||||||
layer: flayer.layerDef,
|
layer: flayer.layerDef,
|
||||||
selectedElement: this.selectedElement,
|
selectedElement: this.selectedElement,
|
||||||
selectedLayer: this.selectedLayer
|
selectedLayer: this.selectedLayer,
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -585,30 +593,35 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
private initActors() {
|
private initActors() {
|
||||||
// Unselect the selected element if it is panned out of view
|
// Unselect the selected element if it is panned out of view
|
||||||
this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => {
|
this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => {
|
||||||
const selected = this.selectedElement.data;
|
const selected = this.selectedElement.data
|
||||||
if (selected === undefined) {
|
if (selected === undefined) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
const bbox = BBox.get(selected);
|
const bbox = BBox.get(selected)
|
||||||
if (!bbox.overlapsWith(bounds)) {
|
if (!bbox.overlapsWith(bounds)) {
|
||||||
this.selectedElement.setData(undefined);
|
this.selectedElement.setData(undefined)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
this.selectedElement.addCallback((selected) => {
|
this.selectedElement.addCallback((selected) => {
|
||||||
if (selected === undefined) {
|
if (selected === undefined) {
|
||||||
// We did _unselect_ an item - we always remove the lastclick-object
|
// We did _unselect_ an item - we always remove the lastclick-object
|
||||||
this.lastClickObject.features.setData([]);
|
this.lastClickObject.features.setData([])
|
||||||
this.selectedLayer.setData(undefined);
|
this.selectedLayer.setData(undefined)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
new ThemeViewStateHashActor(this);
|
new ThemeViewStateHashActor(this)
|
||||||
new MetaTagging(this);
|
new MetaTagging(this)
|
||||||
new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this);
|
new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this)
|
||||||
new ChangeToElementsActor(this.changes, this.featureProperties);
|
new ChangeToElementsActor(this.changes, this.featureProperties)
|
||||||
new PendingChangesUploader(this.changes, this.selectedElement);
|
new PendingChangesUploader(this.changes, this.selectedElement)
|
||||||
new SelectedElementTagsUpdater(this);
|
new SelectedElementTagsUpdater(this)
|
||||||
new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers);
|
new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers)
|
||||||
new PreferredRasterLayerSelector(this.mapProperties.rasterLayer, this.availableLayers, this.featureSwitches.backgroundLayerId, this.userRelatedState.preferredBackgroundLayer)
|
new PreferredRasterLayerSelector(
|
||||||
|
this.mapProperties.rasterLayer,
|
||||||
|
this.availableLayers,
|
||||||
|
this.featureSwitches.backgroundLayerId,
|
||||||
|
this.userRelatedState.preferredBackgroundLayer
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,45 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations"
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg"
|
||||||
import Tr from "../Base/Tr.svelte";
|
import Tr from "../Base/Tr.svelte"
|
||||||
import NextButton from "../Base/NextButton.svelte";
|
import NextButton from "../Base/NextButton.svelte"
|
||||||
import Geosearch from "./Geosearch.svelte";
|
import Geosearch from "./Geosearch.svelte"
|
||||||
import ToSvelte from "../Base/ToSvelte.svelte";
|
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||||
import ThemeViewState from "../../Models/ThemeViewState";
|
import ThemeViewState from "../../Models/ThemeViewState"
|
||||||
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid";
|
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||||
import { twJoin } from "tailwind-merge";
|
import { twJoin } from "tailwind-merge"
|
||||||
import { Utils } from "../../Utils";
|
import { Utils } from "../../Utils"
|
||||||
import type { GeolocationPermissionState } from "../../Logic/State/GeoLocationState";
|
import type { GeolocationPermissionState } from "../../Logic/State/GeoLocationState"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The theme introduction panel
|
* The theme introduction panel
|
||||||
*/
|
*/
|
||||||
export let state: ThemeViewState;
|
export let state: ThemeViewState
|
||||||
let layout = state.layout;
|
let layout = state.layout
|
||||||
let selectedElement = state.selectedElement;
|
let selectedElement = state.selectedElement
|
||||||
let selectedLayer = state.selectedLayer;
|
let selectedLayer = state.selectedLayer
|
||||||
|
|
||||||
let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined);
|
let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
|
||||||
let searchEnabled = false;
|
let searchEnabled = false
|
||||||
|
|
||||||
let geopermission: Store<GeolocationPermissionState> = state.geolocation.geolocationState.permission;
|
let geopermission: Store<GeolocationPermissionState> =
|
||||||
let currentGPSLocation = state.geolocation.geolocationState.currentGPSLocation;
|
state.geolocation.geolocationState.permission
|
||||||
|
let currentGPSLocation = state.geolocation.geolocationState.currentGPSLocation
|
||||||
|
|
||||||
geopermission.addCallback(perm => console.log(">>>> Permission", perm));
|
geopermission.addCallback((perm) => console.log(">>>> Permission", perm))
|
||||||
|
|
||||||
function jumpToCurrentLocation() {
|
function jumpToCurrentLocation() {
|
||||||
const glstate = state.geolocation.geolocationState;
|
const glstate = state.geolocation.geolocationState
|
||||||
if (glstate.currentGPSLocation.data !== undefined) {
|
if (glstate.currentGPSLocation.data !== undefined) {
|
||||||
const c: GeolocationCoordinates = glstate.currentGPSLocation.data;
|
const c: GeolocationCoordinates = glstate.currentGPSLocation.data
|
||||||
state.guistate.themeIsOpened.setData(false);
|
state.guistate.themeIsOpened.setData(false)
|
||||||
const coor = { lon: c.longitude, lat: c.latitude };
|
const coor = { lon: c.longitude, lat: c.latitude }
|
||||||
state.mapProperties.location.setData(coor);
|
state.mapProperties.location.setData(coor)
|
||||||
}
|
}
|
||||||
if (glstate.permission.data !== "granted") {
|
if (glstate.permission.data !== "granted") {
|
||||||
glstate.requestPermission();
|
glstate.requestPermission()
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -69,14 +70,19 @@
|
||||||
</button>
|
</button>
|
||||||
<!-- No geolocation granted - we don't show the button -->
|
<!-- No geolocation granted - we don't show the button -->
|
||||||
{:else if $geopermission === "requested"}
|
{:else if $geopermission === "requested"}
|
||||||
<button class="flex w-full items-center gap-x-2 disabled" on:click={jumpToCurrentLocation}>
|
<button class="disabled flex w-full items-center gap-x-2" on:click={jumpToCurrentLocation}>
|
||||||
<!-- Even though disabled, when clicking we request the location again in case the contributor dismissed the location popup -->
|
<!-- Even though disabled, when clicking we request the location again in case the contributor dismissed the location popup -->
|
||||||
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8").SetClass("animate-spin")} />
|
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8").SetClass("animate-spin")} />
|
||||||
<Tr t={Translations.t.general.waitingForGeopermission} />
|
<Tr t={Translations.t.general.waitingForGeopermission} />
|
||||||
</button>
|
</button>
|
||||||
{:else if $geopermission !== "denied"}
|
{:else if $geopermission === "denied"}
|
||||||
<button class="flex w-full items-center gap-x-2 disabled">
|
<button class="disabled flex w-full items-center gap-x-2">
|
||||||
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8").SetClass("motion-safe:animate-spin")} />
|
<ToSvelte construct={Svg.location_refused_svg().SetClass("w-8 h-8")} />
|
||||||
|
<Tr t={Translations.t.general.geopermissionDenied} />
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
<button class="disabled flex w-full items-center gap-x-2">
|
||||||
|
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8").SetClass("animate-spin")} />
|
||||||
<Tr t={Translations.t.general.waitingForLocation} />
|
<Tr t={Translations.t.general.waitingForLocation} />
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -4,9 +4,23 @@ import ThemeViewGUI from "./src/UI/ThemeViewGUI.svelte"
|
||||||
import LayoutConfig from "./src/Models/ThemeConfig/LayoutConfig";
|
import LayoutConfig from "./src/Models/ThemeConfig/LayoutConfig";
|
||||||
import MetaTagging from "./src/Logic/MetaTagging";
|
import MetaTagging from "./src/Logic/MetaTagging";
|
||||||
|
|
||||||
|
function webgl_support() {
|
||||||
|
try {
|
||||||
|
var canvas = document.createElement("canvas")
|
||||||
|
return (
|
||||||
|
!!window.WebGLRenderingContext &&
|
||||||
|
(canvas.getContext("webgl") || canvas.getContext("experimental-webgl"))
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!webgl_support()) {
|
||||||
|
new FixedUiElement("WebGL is not supported or not enabled. This is essential for MapComplete to function, please enable this.").SetClass("block alert").AttachTo("maindiv")
|
||||||
|
}else{
|
||||||
MetaTagging.setThemeMetatagging(new ThemeMetaTagging())
|
MetaTagging.setThemeMetatagging(new ThemeMetaTagging())
|
||||||
const state = new ThemeViewState(new LayoutConfig(<any> layout))
|
const state = new ThemeViewState(new LayoutConfig(<any> layout))
|
||||||
const main = new SvelteUIElement(ThemeViewGUI, { state })
|
const main = new SvelteUIElement(ThemeViewGUI, { state })
|
||||||
main.AttachTo("maindiv")
|
main.AttachTo("maindiv")
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue