Merge develop

This commit is contained in:
Pieter Vander Vennet 2024-01-16 04:27:59 +01:00
commit c672fe7668
138 changed files with 14304 additions and 1299 deletions

View file

@ -153,11 +153,7 @@ export default class GeoLocationHandler {
const features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
this.currentUserLocation = new StaticFeatureSource(features)
let i = 0
this.geolocationState.currentGPSLocation.addCallbackAndRun((location) => {
if (location === undefined) {
return
}
this.geolocationState.currentGPSLocation.addCallbackAndRunD((location) => {
const properties = {
id: "gps-" + i,
"user:location": "yes",
@ -200,7 +196,6 @@ export default class GeoLocationHandler {
)
})
features.ping()
let i = 0
this.currentUserLocation?.features?.addCallbackAndRunD(([location]: [Feature<Point>]) => {
if (location === undefined) {
return
@ -231,7 +226,6 @@ export default class GeoLocationHandler {
const feature = JSON.parse(JSON.stringify(location))
feature.properties.id = "gps/" + features.data.length
i++
features.data.push(feature)
features.ping()
})

View file

@ -1,13 +1,11 @@
import ImageProvider, { ProvidedImage } from "./ImageProvider"
import BaseUIElement from "../../UI/BaseUIElement"
import Svg from "../../Svg"
import { WikimediaImageProvider } from "./WikimediaImageProvider"
import Wikidata from "../Web/Wikidata"
import SvelteUIElement from "../../UI/Base/SvelteUIElement"
import * as Wikidata_icon from "../../assets/svg/Wikidata.svelte"
export class WikidataImageProvider extends ImageProvider {
public apiUrls(): string[] {
return Wikidata.neededUrls
}
public static readonly singleton = new WikidataImageProvider()
public readonly defaultKeyPrefixes = ["wikidata"]
@ -15,8 +13,12 @@ export class WikidataImageProvider extends ImageProvider {
super()
}
public apiUrls(): string[] {
return Wikidata.neededUrls
}
public SourceIcon(): BaseUIElement {
return Svg.wikidata_svg()
return new SvelteUIElement(Wikidata_icon)
}
public async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {

View file

@ -4,6 +4,8 @@ import Svg from "../../Svg"
import { Utils } from "../../Utils"
import { LicenseInfo } from "./LicenseInfo"
import Wikimedia from "../Web/Wikimedia"
import SvelteUIElement from "../../UI/Base/SvelteUIElement"
import Wikimedia_commons_white from "../../assets/svg/Wikimedia_commons_white.svelte"
/**
* This module provides endpoints for wikimedia and others
@ -70,7 +72,7 @@ export class WikimediaImageProvider extends ImageProvider {
}
SourceIcon(): BaseUIElement {
return Svg.wikimedia_commons_white_svg().SetStyle("width:2em;height: 2em")
return new SvelteUIElement(Wikimedia_commons_white).SetStyle("width:2em;height: 2em")
}
public PrepUrl(value: string): ProvidedImage {

View file

@ -399,11 +399,12 @@ export class OsmConnection {
return id
}
public static GpxTrackVisibility = ["private", "public", "trackable", "identifiable"] as const
public async uploadGpxTrack(
gpx: string,
options: {
description: string
visibility: "private" | "public" | "trackable" | "identifiable"
visibility: (typeof OsmConnection.GpxTrackVisibility)[number]
filename?: string
/**
* Some words to give some properties;
@ -425,11 +426,14 @@ export class OsmConnection {
const contents = {
file: gpx,
description: options.description ?? "",
description: options.description,
tags: options.labels?.join(",") ?? "",
visibility: options.visibility,
}
if (!contents.description) {
throw "The description of a GPS-trace cannot be the empty string, undefined or null"
}
const extras = {
file:
'; filename="' +

View file

@ -487,12 +487,6 @@ export default class SimpleMetaTaggers {
feature.properties._isOpen = "yes"
return true
}
console.log(
"Calculating opening hours for",
feature.properties.name,
":",
feature.properties.opening_hours
)
// _isOpen is calculated dynamically on every call
Object.defineProperty(feature.properties, "_isOpen", {

View file

@ -50,6 +50,11 @@ export default class ThemeViewStateHashActor {
if (!!hash) {
// There is still a hash
// We _only_ have to (at most) close the overlays in this case
if (state.previewedImage.data) {
state.previewedImage.setData(undefined)
return
}
const parts = hash.split(";")
if (parts.indexOf("background") < 0) {
state.guistate.backgroundLayerSelectionIsOpened.setData(false)
@ -176,6 +181,10 @@ export default class ThemeViewStateHashActor {
private back() {
const state = this._state
if (state.previewedImage.data) {
state.previewedImage.setData(undefined)
return
}
// history.pushState(null, null, window.location.pathname);
if (state.selectedElement.data) {
state.selectedElement.setData(undefined)

View file

@ -116,28 +116,42 @@ export default class Constants {
* These are the values that are allowed to use as 'backdrop' icon for a map pin
*/
private static readonly _defaultPinIcons = [
"pin",
"square",
"circle",
"none",
"pin",
"person",
"plus",
"ring",
"star",
"teardrop",
"triangle",
"checkmark",
"clock",
"close",
"crosshair",
"help",
"home",
"invalid",
"location",
"location_empty",
"location_locked",
"note",
"resolved",
"ring",
"scissors",
"teardrop",
"teardrop_with_hole_green",
"triangle",
"brick_wall_square",
"brick_wall_round",
"gps_arrow",
"checkmark",
"help",
"clock",
"invalid",
"close",
"invalid",
"heart",
"heart_outline",
"link",
"confirm",
"direction",
"not_found",
"mastodon",
"party",
"addSmall",
] as const
public static readonly defaultPinIcons: string[] = <any>Constants._defaultPinIcons

View file

@ -3,6 +3,7 @@ import { UIEventSource } from "../Logic/UIEventSource"
import UserRelatedState from "../Logic/State/UserRelatedState"
import { Utils } from "../Utils"
import { LocalStorageSource } from "../Logic/Web/LocalStorageSource"
import Zoomcontrol from "../UI/Zoomcontrol"
export type ThemeViewTabStates = (typeof MenuState._themeviewTabs)[number]
export type MenuViewTabStates = (typeof MenuState._menuviewTabs)[number]
@ -114,7 +115,36 @@ export class MenuState {
name: "background",
showOverOthers: true,
},
{
toggle: this.communityIndexPanelIsOpened,
name: "community",
showOverOthers: true,
},
{
toggle: this.privacyPanelIsOpened,
name: "privacy",
showOverOthers: true,
},
{
toggle: this.filtersPanelIsOpened,
name: "filters",
showOverOthers: true,
},
]
for (const toggle of this.allToggles) {
toggle.toggle.addCallback((isOpen) => {
if (!isOpen) {
this.resetZoomIfAllClosed()
}
})
}
}
private resetZoomIfAllClosed() {
if (this.isSomethingOpen()) {
return
}
Zoomcontrol.resetzoom()
}
public openFilterView(highlightLayer?: LayerConfig | string) {
@ -146,27 +176,23 @@ export class MenuState {
this.highlightedUserSetting.setData(highlightTagRendering)
}
public isSomethingOpen(): boolean {
return this.allToggles.some((t) => t.toggle.data)
}
/**
* Close all floatOvers.
* Returns 'true' if at least one menu was opened
*/
public closeAll(): boolean {
const toggles = [
this.communityIndexPanelIsOpened,
this.privacyPanelIsOpened,
this.backgroundLayerSelectionIsOpened,
this.filtersPanelIsOpened,
this.menuIsOpened,
this.themeIsOpened,
]
let somethingIsOpen = false
for (const t of toggles) {
somethingIsOpen = t.data
t.setData(false)
if (somethingIsOpen) {
let somethingWasOpen = false
for (const t of this.allToggles) {
somethingWasOpen = t.toggle.data
t.toggle.setData(false)
if (somethingWasOpen) {
break
}
}
return somethingIsOpen
return somethingWasOpen
}
}

View file

@ -1,14 +1,4 @@
import {
Concat,
Conversion,
DesugaringContext,
DesugaringStep,
Each,
Fuse,
On,
Pass,
SetDefault,
} from "./Conversion"
import { Concat, Conversion, DesugaringContext, DesugaringStep, Each, Fuse, On, Pass, SetDefault } from "./Conversion"
import { LayoutConfigJson } from "../Json/LayoutConfigJson"
import { PrepareLayer } from "./PrepareLayer"
import { LayerConfigJson } from "../Json/LayerConfigJson"
@ -27,9 +17,9 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
constructor(state: DesugaringContext) {
super(
"Converts the identifier of a builtin layer into the actual layer, or converts a 'builtin' syntax with override in the fully expanded form",
"Converts the identifier of a builtin layer into the actual layer, or converts a 'builtin' syntax with override in the fully expanded form. Note that 'tagRenderings+' will be inserted before 'leftover-questions'",
[],
"SubstituteLayer"
"SubstituteLayer",
)
this._state = state
}
@ -80,21 +70,35 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
(found["tagRenderings"] ?? []).length > 0
) {
context.err(
`When overriding a layer, an override is not allowed to override into tagRenderings. Use "+tagRenderings" or "tagRenderings+" instead to prepend or append some questions.`
`When overriding a layer, an override is not allowed to override into tagRenderings. Use "+tagRenderings" or "tagRenderings+" instead to prepend or append some questions.`,
)
}
try {
const trPlus = json["override"]["tagRenderings+"]
if(trPlus){
let index = found.tagRenderings.findIndex(tr => tr["id"] === "leftover-questions")
if(index < 0){
index = found.tagRenderings.length
}
found.tagRenderings.splice(index, 0, ...trPlus)
delete json["override"]["tagRenderings+"]
}
Utils.Merge(json["override"], found)
layers.push(found)
} catch (e) {
context.err(
`Could not apply an override due to: ${e}.\nThe override is: ${JSON.stringify(
json["override"]
)}`
json["override"],
)}`,
)
}
if (json["hideTagRenderingsWithLabels"]) {
if (typeof json["hideTagRenderingsWithLabels"] === "string") {
throw "At " + context + ".hideTagRenderingsWithLabels should be a list containing strings, you specified a string"
}
const hideLabels: Set<string> = new Set(json["hideTagRenderingsWithLabels"])
// These labels caused at least one deletion
const usedLabels: Set<string> = new Set<string>()
@ -107,9 +111,9 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
usedLabels.add(labels[forbiddenLabel])
context.info(
"Dropping tagRendering " +
tr["id"] +
" as it has a forbidden label: " +
labels[forbiddenLabel]
tr["id"] +
" as it has a forbidden label: " +
labels[forbiddenLabel],
)
continue
}
@ -118,7 +122,7 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
if (hideLabels.has(tr["id"])) {
usedLabels.add(tr["id"])
context.info(
"Dropping tagRendering " + tr["id"] + " as its id is a forbidden label"
"Dropping tagRendering " + tr["id"] + " as its id is a forbidden label",
)
continue
}
@ -127,10 +131,10 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
usedLabels.add(tr["group"])
context.info(
"Dropping tagRendering " +
tr["id"] +
" as its group `" +
tr["group"] +
"` is a forbidden label"
tr["id"] +
" as its group `" +
tr["group"] +
"` is a forbidden label",
)
continue
}
@ -141,8 +145,8 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
if (unused.length > 0) {
context.err(
"This theme specifies that certain tagrenderings have to be removed based on forbidden layers. One or more of these layers did not match any tagRenderings and caused no deletions: " +
unused.join(", ") +
"\n This means that this label can be removed or that the original tagRendering that should be deleted does not have this label anymore"
unused.join(", ") +
"\n This means that this label can be removed or that the original tagRendering that should be deleted does not have this label anymore",
)
}
found.tagRenderings = filtered
@ -159,7 +163,7 @@ class AddDefaultLayers extends DesugaringStep<LayoutConfigJson> {
super(
"Adds the default layers, namely: " + Constants.added_by_default.join(", "),
["layers"],
"AddDefaultLayers"
"AddDefaultLayers",
)
this._state = state
}
@ -183,10 +187,10 @@ class AddDefaultLayers extends DesugaringStep<LayoutConfigJson> {
if (alreadyLoaded.has(v.id)) {
context.warn(
"Layout " +
context +
" already has a layer with name " +
v.id +
"; skipping inclusion of this builtin layer"
context +
" already has a layer with name " +
v.id +
"; skipping inclusion of this builtin layer",
)
continue
}
@ -202,14 +206,14 @@ class AddImportLayers extends DesugaringStep<LayoutConfigJson> {
super(
"For every layer in the 'layers'-list, create a new layer which'll import notes. (Note that priviliged layers and layers which have a geojson-source set are ignored)",
["layers"],
"AddImportLayers"
"AddImportLayers",
)
}
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
if (!(json.enableNoteImports ?? true)) {
context.info(
"Not creating a note import layers for theme " + json.id + " as they are disabled"
"Not creating a note import layers for theme " + json.id + " as they are disabled",
)
return json
}
@ -244,7 +248,7 @@ class AddImportLayers extends DesugaringStep<LayoutConfigJson> {
try {
const importLayerResult = creator.convert(
layer,
context.inOperation(this.name).enter(i1)
context.inOperation(this.name).enter(i1),
)
if (importLayerResult !== undefined) {
json.layers.push(importLayerResult)
@ -263,7 +267,7 @@ class AddContextToTranslationsInLayout extends DesugaringStep<LayoutConfigJson>
super(
"Adds context to translations, including the prefix 'themes:json.id'; this is to make sure terms in an 'overrides' or inline layer are linkable too",
["_context"],
"AddContextToTranlationsInLayout"
"AddContextToTranlationsInLayout",
)
}
@ -278,7 +282,7 @@ class ApplyOverrideAll extends DesugaringStep<LayoutConfigJson> {
super(
"Applies 'overrideAll' onto every 'layer'. The 'overrideAll'-field is removed afterwards",
["overrideAll", "layers"],
"ApplyOverrideAll"
"ApplyOverrideAll",
)
}
@ -292,9 +296,29 @@ class ApplyOverrideAll extends DesugaringStep<LayoutConfigJson> {
delete json.overrideAll
const newLayers = []
let tagRenderingsPlus = undefined
if (overrideAll["tagRenderings+"] !== undefined) {
tagRenderingsPlus = overrideAll["tagRenderings+"]
delete overrideAll["tagRenderings+"]
}
for (let layer of json.layers) {
layer = Utils.Clone(<LayerConfigJson>layer)
Utils.Merge(overrideAll, layer)
if (tagRenderingsPlus) {
if (!layer.tagRenderings) {
layer.tagRenderings = tagRenderingsPlus
} else {
let index = layer.tagRenderings.findIndex(tr => tr["id"] === "leftover-questions")
if (index < 0) {
index = layer.tagRenderings.length - 1
}
layer.tagRenderings.splice(index, 0, ...tagRenderingsPlus)
}
}
newLayers.push(layer)
}
json.layers = newLayers
@ -314,7 +338,7 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
Some layers (e.g. \`all_buildings_and_walls\' or \'streets_with_a_name\') are invisible, so by default, \'force_load\' is set too.
`,
["layers"],
"AddDependencyLayersToTheme"
"AddDependencyLayersToTheme",
)
this._state = state
}
@ -322,7 +346,7 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
private static CalculateDependencies(
alreadyLoaded: LayerConfigJson[],
allKnownLayers: Map<string, LayerConfigJson>,
themeId: string
themeId: string,
): { config: LayerConfigJson; reason: string }[] {
const dependenciesToAdd: { config: LayerConfigJson; reason: string }[] = []
const loadedLayerIds: Set<string> = new Set<string>(alreadyLoaded.map((l) => l.id))
@ -345,7 +369,7 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
for (const layerConfig of alreadyLoaded) {
try {
const layerDeps = DependencyCalculator.getLayerDependencies(
new LayerConfig(layerConfig, themeId + "(dependencies)")
new LayerConfig(layerConfig, themeId + "(dependencies)"),
)
dependencies.push(...layerDeps)
} catch (e) {
@ -382,10 +406,10 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
if (dep === undefined) {
const message = [
"Loading a dependency failed: layer " +
unmetDependency.neededLayer +
" is not found, neither as layer of " +
themeId +
" nor as builtin layer.",
unmetDependency.neededLayer +
" is not found, neither as layer of " +
themeId +
" nor as builtin layer.",
reason,
"Loaded layers are: " + alreadyLoaded.map((l) => l.id).join(","),
]
@ -401,7 +425,7 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
})
loadedLayerIds.add(dep.id)
unmetDependencies = unmetDependencies.filter(
(d) => d.neededLayer !== unmetDependency.neededLayer
(d) => d.neededLayer !== unmetDependency.neededLayer,
)
}
} while (unmetDependencies.length > 0)
@ -422,14 +446,14 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
const dependencies = AddDependencyLayersToTheme.CalculateDependencies(
layers,
allKnownLayers,
theme.id
theme.id,
)
for (const dependency of dependencies) {
}
if (dependencies.length > 0) {
for (const dependency of dependencies) {
context.info(
"Added " + dependency.config.id + " to the theme. " + dependency.reason
"Added " + dependency.config.id + " to the theme. " + dependency.reason,
)
}
}
@ -471,7 +495,7 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep<LayoutConfigJson>
super(
"Generates a warning if a theme uses an unsubstituted layer",
["layers"],
"WarnForUnsubstitutedLayersInTheme"
"WarnForUnsubstitutedLayersInTheme",
)
}
@ -483,7 +507,7 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep<LayoutConfigJson>
context
.enter("layers")
.err(
"No layers are defined. You must define at least one layer to have a valid theme"
"No layers are defined. You must define at least one layer to have a valid theme",
)
return json
}
@ -507,10 +531,10 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep<LayoutConfigJson>
context.warn(
"The theme " +
json.id +
" has an inline layer: " +
layer["id"] +
". This is discouraged."
json.id +
" has an inline layer: " +
layer["id"] +
". This is discouraged.",
)
}
return json
@ -519,11 +543,12 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep<LayoutConfigJson>
export class PrepareTheme extends Fuse<LayoutConfigJson> {
private state: DesugaringContext
constructor(
state: DesugaringContext,
options?: {
skipDefaultLayers: false | boolean
}
},
) {
super(
"Fully prepares and expands a theme",
@ -536,6 +561,7 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
// We expand all tagrenderings first...
new On("layers", new Each(new PrepareLayer(state))),
// Then we apply the override all. We must first expand everything in case that we override something in an expanded tag
// Note that it'll cheat with tagRenderings+
new ApplyOverrideAll(),
// And then we prepare all the layers _again_ in case that an override all contained unexpanded tagrenderings!
new On("layers", new Each(new PrepareLayer(state))),
@ -543,7 +569,7 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
? new Pass("AddDefaultLayers is disabled due to the set flag")
: new AddDefaultLayers(state),
new AddDependencyLayersToTheme(state),
new AddImportLayers()
new AddImportLayers(),
)
this.state = state
}
@ -558,13 +584,13 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
const needsNodeDatabase = result.layers?.some((l: LayerConfigJson) =>
l.tagRenderings?.some((tr) =>
ValidationUtils.getSpecialVisualisations(<any>tr)?.some(
(special) => special.needsNodeDatabase
)
)
(special) => special.needsNodeDatabase,
),
),
)
if (needsNodeDatabase) {
context.info(
"Setting 'enableNodeDatabase' as this theme uses a special visualisation which needs to keep track of _all_ nodes"
"Setting 'enableNodeDatabase' as this theme uses a special visualisation which needs to keep track of _all_ nodes",
)
result.enableNodeDatabase = true
}

View file

@ -24,6 +24,7 @@ import { ConversionContext } from "./ConversionContext"
import * as eli from "../../../assets/editor-layer-index.json"
import { AvailableRasterLayers } from "../../RasterLayers"
import Back from "../../../assets/svg/Back.svelte"
import PointRenderingConfigJson from "../Json/PointRenderingConfigJson"
class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> {
private readonly _languages: string[]
@ -177,6 +178,9 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
if (!json.title) {
context.enter("title").err(`The theme ${json.id} does not have a title defined.`)
}
if(!json.icon){
context.enter("icon").err("A theme should have an icon")
}
if (this._isBuiltin && this._extractImages !== undefined) {
// Check images: are they local, are the licenses there, is the theme icon square, ...
const images = this._extractImages.convert(json, context.inOperation("ValidateTheme"))
@ -243,7 +247,8 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
new ValidateLanguageCompleteness("en").convert(theme, context)
}
} catch (e) {
context.err(e)
console.error(e)
context.err("Could not validate the theme due to: " + e)
}
if (theme.id !== "personal") {
@ -411,7 +416,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingCo
return json
} catch (e) {
context.err(e)
context.err("Could not check for conflicting extra tags due to: " + e)
return undefined
}
}
@ -1016,6 +1021,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
*/
private readonly _path: string
private readonly _studioValidations: boolean
private readonly _validatePointRendering = new ValidatePointRendering()
constructor(path: string, isBuiltin, doesImageExist, studioValidations) {
super("Runs various checks against common mistakes for a layer", [], "PrevalidateLayer")
@ -1105,6 +1111,8 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
context.enter("pointRendering").err("There are no pointRenderings at all...")
}
json.pointRendering?.forEach((pr,i) => this._validatePointRendering.convert(pr, context.enters("pointeRendering", i)))
if (json["mapRendering"]) {
context.enter("mapRendering").err("This layer has a legacy 'mapRendering'")
}
@ -1409,13 +1417,40 @@ export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> {
}
}
class ValidatePointRendering extends DesugaringStep<PointRenderingConfigJson> {
constructor() {
super("Various checks for pointRenderings", [], "ValidatePOintRendering")
}
convert(json: PointRenderingConfigJson, context: ConversionContext): PointRenderingConfigJson {
if (json.marker === undefined && json.label === undefined) {
context.err(`A point rendering should define at least an marker or a label`)
}
if (json["markers"]) {
context.enter("markers").err(`Detected a field 'markerS' in pointRendering. It is written as a singular case`)
}
if (json.marker && !Array.isArray(json.marker)) {
context.enter("marker").err(
"The marker in a pointRendering should be an array"
)
}
if (json.location.length == 0) {
context.enter("location").err (
"A pointRendering should have at least one 'location' to defined where it should be rendered. "
)
}
return json
}
}
export class ValidateLayer extends Conversion<
LayerConfigJson,
{ parsed: LayerConfig; raw: LayerConfigJson }
> {
private readonly _skipDefaultLayers: boolean
private readonly _prevalidation: PrevalidateLayer
constructor(
path: string,
isBuiltin: boolean,

View file

@ -506,7 +506,7 @@ export interface LayerConfigJson {
* If the way is part of a relation, MapComplete will attempt to update this relation as well
* question: Should the contributor be able to split ways using this layer?
* iftrue: enable the 'split-roads'-component
* iffalse: don't enable the split-roads componenet
* iffalse: don't enable the split-roads component
* ifunset: don't enable the split-roads component
* group: editing
*/

View file

@ -5,7 +5,7 @@ export interface IconConfigJson {
/**
* question: What icon should be used?
* type: icon
* suggestions: return ["pin","square","circle","checkmark","clock","close","crosshair","help","home","invalid","location","location_empty","location_locked","note","resolved","ring","scissors","teardrop","teardrop_with_hole_green","triangle"].map(i => ({if: "value="+i, then: i, icon: i}))
* suggestions: return Constants.defaultPinIcons.map(i => ({if: "value="+i, then: i, icon: i}))
*/
icon: string | MinimalTagRenderingConfigJson | { builtin: string; override: any }
/**

View file

@ -106,8 +106,12 @@ export interface MappingConfigJson {
hideInAnswer?: boolean | TagConfigJson
/**
* question: In what other cases should this item be rendered?
*
* Also show this 'then'-option if the feature matches these tags.
* Ideal for outdated tags.
* Ideal for outdated tags or default assumptions. The tags from this options will <b>not</b> be set if the option is chosen!
*
* ifunset: No other cases when this text is shown
*/
alsoShowIf?: TagConfigJson

View file

@ -79,23 +79,7 @@ export default class PointRenderingConfig extends WithContextLoader {
}
})
if (json.marker === undefined && json.label === undefined) {
throw `At ${context}: A point rendering should define at least an marker or a label`
}
if (json["markers"]) {
throw `At ${context}.markers: detected a field 'markerS' in pointRendering. It is written as a singular case`
}
if (json.marker && !Array.isArray(json.marker)) {
throw `At ${context}.marker: the marker in a pointRendering should be an array`
}
if (this.location.size == 0) {
throw (
"A pointRendering should have at least one 'location' to defined where it should be rendered. (At " +
context +
".location)"
)
}
this.marker = (json.marker ?? []).map((m) => new IconConfig(<any>m))
if (json.css !== undefined) {
this.cssDef = this.tr("css", undefined)

View file

@ -15,7 +15,6 @@ import {
QuestionableTagRenderingConfigJson,
} from "./Json/QuestionableTagRenderingConfigJson"
import { FixedUiElement } from "../../UI/Base/FixedUiElement"
import { Paragraph } from "../../UI/Base/Paragraph"
import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
import Constants from "../Constants"
@ -371,20 +370,9 @@ export default class TagRenderingConfig {
let iconClass = commonSize
if (!!mapping.icon) {
if (typeof mapping.icon === "string" && mapping.icon !== "") {
let stripped = mapping.icon
if (stripped.endsWith(".svg")) {
stripped = stripped.substring(0, stripped.length - 4)
}
if (Constants.defaultPinIcons.indexOf(stripped) >= 0) {
icon = "./assets/svg/" + mapping.icon
if (!icon.endsWith(".svg")) {
icon += ".svg"
}
} else {
icon = mapping.icon
}
icon = mapping.icon.trim()
} else if (mapping.icon["path"]) {
icon = mapping.icon["path"]
icon = mapping.icon["path"].trim()
iconClass = mapping.icon["class"] ?? iconClass
}
}
@ -754,12 +742,10 @@ export default class TagRenderingConfig {
withRender = [
`This rendering asks information about the property `,
Link.OsmWiki(this.freeform.key),
new Paragraph(
new Combine([
"This is rendered with ",
new FixedUiElement(this.render.txt).SetClass("code font-bold"),
])
),
new Combine([
"This is rendered with ",
new FixedUiElement(this.render.txt).SetClass("code font-bold"),
]),
]
}

View file

@ -61,6 +61,7 @@ import NearbyFeatureSource from "../Logic/FeatureSource/Sources/NearbyFeatureSou
import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource"
import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider"
import { GeolocationControlState } from "../UI/BigComponents/GeolocationControl"
import Zoomcontrol from "../UI/Zoomcontrol"
/**
*
@ -481,6 +482,12 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.lastClickObject.features.setData([])
})
this.selectedElement.addCallback((selected) => {
if (selected === undefined) {
Zoomcontrol.resetzoom()
}
})
if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) {
Utils.LoadCustomCss(this.layout.customCss)
}
@ -524,7 +531,10 @@ export default class ThemeViewState implements SpecialVisualizationState {
}
this.selectedElement.setData(undefined)
this.guistate.closeAll()
this.focusOnMap()
if (!this.guistate.isSomethingOpen()) {
Zoomcontrol.resetzoom()
this.focusOnMap()
}
})
Hotkeys.RegisterHotkey({ nomod: "f" }, docs.selectFavourites, () => {

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { createEventDispatcher } from "svelte"
import { createEventDispatcher, onDestroy } from "svelte"
import { twMerge } from "tailwind-merge"
export let accept: string
@ -9,10 +9,52 @@
export let cls: string = ""
let drawAttention = false
let inputElement: HTMLInputElement
let id = Math.random() * 1000000000 + ""
let formElement: HTMLFormElement
let id = "fileinput_" + Math.round(Math.random() * 1000000000000)
function handleDragEvent(e: DragEvent) {
if (e.target["id"] == id) {
return
}
if(formElement.contains(e.target) || document.getElementsByClassName("selected-element-view")[0]?.contains(e.target)){
e.preventDefault()
if(e.type === "drop"){
console.log("Got a 'drop'", e)
drawAttention = false
dispatcher("submit", e.dataTransfer.files)
return
}
drawAttention = true
e.dataTransfer.dropEffect = "copy"
return
/*
drawAttention = false
dispatcher("submit", e.dataTransfer.files)
console.log("Committing")*/
}
drawAttention = false
e.preventDefault()
e.dataTransfer.effectAllowed = "none"
e.dataTransfer.dropEffect = "none"
}
window.addEventListener("dragenter", handleDragEvent)
window.addEventListener("dragover", handleDragEvent)
window.addEventListener("drop", handleDragEvent)
onDestroy(() => {
window.removeEventListener("dragenter", handleDragEvent)
window.removeEventListener("dragover", handleDragEvent)
window.removeEventListener("drop", handleDragEvent)
})
</script>
<form
bind:this={formElement}
on:change|preventDefault={() => {
drawAttention = false
dispatcher("submit", inputElement.files)
@ -24,27 +66,24 @@
on:dragenter|preventDefault|stopPropagation={(e) => {
console.log("Dragging enter")
drawAttention = true
e.dataTransfer.drop = "copy"
e.dataTransfer.dropEffect = "copy"
}}
on:dragstart={() => {
console.log("DragStart")
drawAttention = false
}}
on:drop|preventDefault|stopPropagation={(e) => {
console.log("Got a 'drop'")
drawAttention = false
dispatcher("submit", e.dataTransfer.files)
}}
>
<label
class={twMerge(cls, drawAttention ? "glowing-shadow" : "")}
style="margin-left:0"
tabindex="0"
for={"fileinput" + id}
for={id}
on:click={() => {
console.log("Clicked", inputElement)
inputElement.click()
}}
style="margin-left:0"
tabindex="0"
>
<slot />
</label>
@ -52,7 +91,7 @@
{accept}
bind:this={inputElement}
class="hidden"
id={"fileinput" + id}
{id}
{multiple}
name="file-input"
type="file"

View file

@ -28,7 +28,7 @@
style="z-index: 21"
use:trapFocus
>
<div class="content normal-background" on:click|stopPropagation={() => {}}>
<div class="h-full content normal-background" on:click|stopPropagation={() => {}}>
<div class="h-full rounded-xl">
<slot />
</div>
@ -47,7 +47,6 @@
<style>
.content {
height: 100%;
border-radius: 0.5rem;
overflow-x: hidden;
box-shadow: 0 0 1rem #00000088;

View file

@ -2,6 +2,8 @@ import { VariableUiElement } from "./VariableUIElement"
import Locale from "../i18n/Locale"
import Link from "./Link"
import Svg from "../../Svg"
import SvelteUIElement from "./SvelteUIElement"
import Translate from "../../assets/svg/Translate.svelte"
/**
* The little 'translate'-icon next to every icon + some static helper functions
@ -20,7 +22,7 @@ export default class LinkToWeblate extends VariableUiElement {
if (context === undefined || context.indexOf(":") < 0) {
return undefined
}
const icon = Svg.translate_svg().SetClass(
const icon = new SvelteUIElement(Translate).SetClass(
"rounded-full inline-block w-3 h-3 ml-1 weblate-link self-center"
)
if (availableTranslations[ln] === undefined) {

View file

@ -1,30 +0,0 @@
import BaseUIElement from "../BaseUIElement"
export class Paragraph extends BaseUIElement {
public readonly content: string | BaseUIElement
constructor(html: string | BaseUIElement) {
super()
this.content = html ?? ""
}
AsMarkdown(): string {
let c: string
if (typeof this.content !== "string") {
c = this.content.AsMarkdown()
} else {
c = this.content
}
return "\n\n" + c + "\n\n"
}
protected InnerConstructElement(): HTMLElement {
const e = document.createElement("p")
if (typeof this.content !== "string") {
e.appendChild(this.content.ConstructElement())
} else {
e.innerHTML = this.content
}
return e
}
}

View file

@ -66,32 +66,4 @@ export class SubtleButton extends UIElement {
this.SetClass(classes)
return button
}
public OnClickWithLoading(
loadingText: BaseUIElement | string,
action: () => Promise<void>
): BaseUIElement {
const state = new UIEventSource<"idle" | "running">("idle")
const button = this
button.onClick(async () => {
state.setData("running")
try {
await action()
} catch (e) {
console.error(e)
} finally {
state.setData("idle")
}
})
const loading = new Lazy(() => new Loading(loadingText))
return new VariableUiElement(
state.map((st) => {
if (st === "idle") {
return button
}
return loading
})
)
}
}

View file

@ -1,7 +1,7 @@
<script lang="ts">
/**
* A mapcontrol button which allows the user to select a different background.
* Even though the componenet is very small, it gets it's own class as it is often reused
* Even though the component is very small, it gets it's own class as it is often reused
*/
import { Square3Stack3dIcon } from "@babeard/svelte-heroicons/solid"
import type { SpecialVisualizationState } from "../SpecialVisualization"

View file

@ -43,7 +43,7 @@
<Tr t={Translations.t.general.returnToTheMap} />
</button>
{:else}
<div class="flex h-full w-full flex-col gap-y-2 overflow-y-auto p-1 px-4" tabindex="-1">
<div class="flex h-full flex-col gap-y-2 overflow-y-auto p-1 px-4 w-full selected-element-view" tabindex="-1">
{#each $knownTagRenderings as config (config.id)}
<TagRenderingEditable
{tags}

View file

@ -0,0 +1,181 @@
<script lang="ts">
import LoginToggle from "../Base/LoginToggle.svelte"
import Translations from "../i18n/Translations"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { Translation } from "../i18n/Translation"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Invalid from "../../assets/svg/Invalid.svelte"
import Tr from "../Base/Tr.svelte"
import Confirm from "../../assets/svg/Confirm.svelte"
import Upload from "../../assets/svg/Upload.svelte"
import Loading from "../Base/Loading.svelte"
import Close from "../../assets/svg/Close.svelte"
import { placeholder } from "../../Utils/placeholder"
import { ariaLabel } from "../../Utils/ariaLabel"
import { selectDefault } from "../../Utils/selectDefault"
export let trace: (title: string) => string
export let state: {
layout: LayoutConfig
osmConnection: OsmConnection
readonly featureSwitchUserbadge: Store<boolean>
}
export let options: {
whenUploaded?: () => void | Promise<void>
} = undefined
let t = Translations.t.general.uploadGpx
let currentStep = new UIEventSource<"init" | "please_confirm" | "uploading" | "done" | "error">("init")
let traceVisibilities: {
key: "private" | "public"
name: Translation
docs: Translation
}[] = [
{
key: "private",
...t.modes.private,
},
{
key: "public",
...t.modes.public,
},
]
let gpxServerIsOnline: Store<boolean> = state.osmConnection.gpxServiceIsOnline.map((serviceState) => serviceState === "online")
/**
* More or less the same as the coalescing-operator '??', except that it checks for empty strings too
*/
function createDefault(s: string, defaultValue: string): string {
if (defaultValue.length < 1) {
throw "Default value should have some characters"
}
if (s === undefined || s === null || s === "") {
return defaultValue
}
return s
}
let title: string = undefined
let description: string = undefined
let visibility = <UIEventSource<"public" | "private">>state?.osmConnection?.GetPreference("gps.trace.visibility") ?? new UIEventSource<"public" | "private">("private")
async function uploadTrace() {
try {
currentStep.setData("uploading")
const titleStr = createDefault(
title,
"Track with mapcomplete",
)
const descriptionStr = createDefault(
description,
"Track created with MapComplete with theme " + state?.layout?.id,
)
await state?.osmConnection?.uploadGpxTrack(trace(titleStr), {
visibility: visibility.data ?? "private",
description: descriptionStr,
filename: titleStr + ".gpx",
labels: ["MapComplete", state?.layout?.id],
})
if (options?.whenUploaded !== undefined) {
await options.whenUploaded()
}
currentStep.setData("done")
} catch (e) {
currentStep.setData("error")
console.error(e)
}
}
</script>
<LoginToggle {state}>
{#if !$gpxServerIsOnline}
<div class="flex border alert items-center">
<Invalid class="w-8 h-8 m-2" />
<Tr t={t.gpxServiceOffline} cls="p-2" />
</div>
{:else if $currentStep === "error"}
<div class="alert flex w-full gap-x-2">
<Invalid class="w-8 h-8"/>
<Tr t={Translations.t.general.error} />
</div>
{:else if $currentStep === "init"}
<button class="flex w-full m-0" on:click={() => {currentStep.setData("please_confirm")}}>
<Upload class="w-12 h-12" />
<Tr t={t.title} />
</button>
{:else if $currentStep === "please_confirm"}
<form on:submit|preventDefault={() => uploadTrace()}
class="flex flex-col border-interactive interactive px-2 gap-y-1">
<h2>
<Tr t={t.title} />
</h2>
<Tr t={t.intro0} />
<Tr t={t.intro1} />
<h3>
<Tr t={t.meta.title} />
</h3>
<Tr t={t.meta.intro} />
<input type="text" use:ariaLabel={t.meta.titlePlaceholder} use:placeholder={t.meta.titlePlaceholder}
bind:value={title} />
<Tr t={t.meta.descriptionIntro} />
<textarea use:ariaLabel={t.meta.descriptionPlaceHolder} use:placeholder={t.meta.descriptionPlaceHolder}
bind:value={description} />
<Tr t={t.choosePermission} />
{#each traceVisibilities as option}
<label>
<input
type="radio"
name="visibility"
value={option.key}
bind:group={$visibility}
use:selectDefault={visibility}
/>
<Tr t={option.name} cls="font-bold" />
-
<Tr t={option.docs} />
</label>
{/each}
<div class="flex flex-wrap-reverse justify-between items-stretch">
<button class="flex gap-x-2 w-1/2 flex-grow" on:click={() => currentStep.setData("init")}>
<Close class="w-8 h-8" />
<Tr t={Translations.t.general.cancel} />
</button>
<button class="flex gap-x-2 primary flex-grow" on:click={() => uploadTrace()}>
<Upload class="w-8 h-8" />
<Tr t={t.confirm} />
</button>
</div>
</form>
{:else if $currentStep === "uploading"}
<Loading>
<Tr t={t.uploading} />
</Loading>
{:else if $currentStep === "done"}
<div class="flex p-2 rounded-xl border-2 subtle-border items-center">
<Confirm class="w-12 h-12 mr-2" />
<Tr t={t.uploadFinished} />
</div>
{/if}
</LoginToggle>

View file

@ -1,153 +0,0 @@
import Toggle from "../Input/Toggle"
import { RadioButton } from "../Input/RadioButton"
import { FixedInputElement } from "../Input/FixedInputElement"
import Combine from "../Base/Combine"
import Translations from "../i18n/Translations"
import { TextField } from "../Input/TextField"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Title from "../Base/Title"
import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { Translation } from "../i18n/Translation"
import { LoginToggle } from "../Popup/LoginButton"
import SvelteUIElement from "../Base/SvelteUIElement"
import Upload from "../../assets/svg/Upload.svelte"
export default class UploadTraceToOsmUI extends LoginToggle {
constructor(
trace: (title: string) => string,
state: {
layout: LayoutConfig
osmConnection: OsmConnection
readonly featureSwitchUserbadge: Store<boolean>
},
options?: {
whenUploaded?: () => void | Promise<void>
}
) {
const t = Translations.t.general.uploadGpx
const uploadFinished = new UIEventSource(false)
const traceVisibilities: {
key: "private" | "public"
name: Translation
docs: Translation
}[] = [
{
key: "private",
...t.modes.private,
},
{
key: "public",
...t.modes.public,
},
]
const dropdown = new RadioButton<"private" | "public">(
traceVisibilities.map(
(tv) =>
new FixedInputElement<"private" | "public">(
new Combine([
Translations.W(tv.name).SetClass("font-bold"),
tv.docs,
]).SetClass("flex flex-col"),
tv.key
)
),
{
value: <any>state?.osmConnection?.GetPreference("gps.trace.visibility"),
}
)
const description = new TextField({
placeholder: t.meta.descriptionPlaceHolder,
})
const title = new TextField({
placeholder: t.meta.titlePlaceholder,
})
const clicked = new UIEventSource<boolean>(false)
const confirmPanel = new Combine([
new Title(t.title),
t.intro0,
t.intro1,
t.choosePermission,
dropdown,
new Title(t.meta.title, 4),
t.meta.intro,
title,
t.meta.descriptionIntro,
description,
new Combine([
new SubtleButton(Svg.close_svg(), Translations.t.general.cancel)
.onClick(() => {
clicked.setData(false)
})
.SetClass(""),
new SubtleButton(new SvelteUIElement(Upload, {}), t.confirm).OnClickWithLoading(
t.uploading,
async () => {
const titleStr = UploadTraceToOsmUI.createDefault(
title.GetValue().data,
"Track with mapcomplete"
)
const descriptionStr = UploadTraceToOsmUI.createDefault(
description.GetValue().data,
"Track created with MapComplete with theme " + state?.layout?.id
)
await state?.osmConnection?.uploadGpxTrack(trace(title.GetValue().data), {
visibility: dropdown.GetValue().data,
description: descriptionStr,
filename: titleStr + ".gpx",
labels: ["MapComplete", state?.layout?.id],
})
if (options?.whenUploaded !== undefined) {
await options.whenUploaded()
}
uploadFinished.setData(true)
}
),
]).SetClass("flex flex-wrap flex-wrap-reverse justify-between items-stretch"),
]).SetClass("flex flex-col p-4 rounded border-2 m-2 border-subtle")
super(
new Toggle(
new Toggle(
new Combine([
Svg.confirm_svg().SetClass("w-12 h-12 mr-2"),
t.uploadFinished,
]).SetClass("flex p-2 rounded-xl border-2 subtle-border items-center"),
new Toggle(
confirmPanel,
new SubtleButton(new SvelteUIElement(Upload), t.title)
.onClick(() => clicked.setData(true))
.SetClass("w-full"),
clicked
),
uploadFinished
),
new Combine([
Svg.invalid_svg().SetClass("w-8 h-8 m-2"),
t.gpxServiceOffline.SetClass("p-2"),
]).SetClass("flex border alert items-center"),
state.osmConnection.gpxServiceIsOnline.map(
(serviceState) => serviceState === "online"
)
),
undefined,
state
)
}
private static createDefault(s: string, defaultValue: string) {
if (defaultValue.length < 1) {
throw "Default value should have some characters"
}
if (s === undefined || s === null || s === "") {
return defaultValue
}
return s
}
}

View file

@ -5,12 +5,16 @@
import panzoom from "panzoom"
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import { UIEventSource } from "../../Logic/UIEventSource"
import Zoomcontrol from "../Zoomcontrol"
import { onDestroy, onMount } from "svelte"
export let image: ProvidedImage
let panzoomInstance = undefined
let panzoomEl: HTMLElement
export let isLoaded: UIEventSource<boolean> = undefined
onDestroy(Zoomcontrol.createLock())
$: {
if (panzoomEl) {
panzoomInstance = panzoom(panzoomEl, {

View file

@ -16,7 +16,7 @@
import Translations from "../i18n/Translations"
import LoginToggle from "../Base/LoginToggle.svelte"
export let tags: Store<OsmTags>
export let tags: UIEventSource<OsmTags>
export let state: SpecialVisualizationState
export let lon: number
export let lat: number

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { Store } from "../../Logic/UIEventSource"
import { UIEventSource } from "../../Logic/UIEventSource"
import type { OsmTags } from "../../Models/OsmFeature"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import type { Feature } from "geojson"
@ -12,7 +12,7 @@
import LoginToggle from "../Base/LoginToggle.svelte"
import { ariaLabel } from "../../Utils/ariaLabel"
export let tags: Store<OsmTags>
export let tags: UIEventSource<OsmTags>
export let state: SpecialVisualizationState
export let lon: number
export let lat: number

View file

@ -65,7 +65,7 @@
{/if}
{#if $failed > 0}
<div class="alert flex flex-col">
{#if failed === 1}
{#if $failed === 1}
<Tr cls="self-center" t={t.upload.one.failed} />
{:else}
<Tr cls="self-center" t={t.upload.multiple.someFailed.Subs({ count: $failed })} />

View file

@ -51,6 +51,15 @@
}
}
function onKeyPress(e: KeyboardEvent){
if(e.key === "Enter"){
e.stopPropagation()
e.preventDefault()
dispatch("submit")
}
}
initValueAndDenom()
$: {
@ -126,7 +135,7 @@
let htmlElem: HTMLInputElement | HTMLTextAreaElement
let dispatch = createEventDispatcher<{ selected }>()
let dispatch = createEventDispatcher<{ selected, submit }>()
$: {
if (htmlElem !== undefined) {
htmlElem.onfocus = () => dispatch("selected")
@ -144,6 +153,7 @@
inputmode={validator?.inputmode ?? "text"}
placeholder={_placeholder}
bind:this={htmlElem}
on:keypress={onKeyPress}
/>
{:else}
<div class={twMerge("inline-flex", cls)}>
@ -153,6 +163,7 @@
class="w-full"
inputmode={validator?.inputmode ?? "text"}
placeholder={_placeholder}
on:keypress={onKeyPress}
/>
{#if !$isValid}
<ExclamationIcon class="-ml-6 h-6 w-6" />

View file

@ -13,7 +13,7 @@
let iconItem = icon.icon?.GetRenderValue($tags)?.Subs($tags)?.txt
$: iconItem = icon.icon?.GetRenderValue($tags)?.Subs($tags)?.txt
let color = icon.color?.GetRenderValue(tags)?.txt ?? "#000000"
let color = icon.color?.GetRenderValue($tags)?.txt ?? "#000000"
$: color = icon.color?.GetRenderValue($tags)?.txt ?? "#000000"
</script>

View file

@ -1,47 +1,48 @@
<script lang="ts">
import Pin from "../../assets/svg/Pin.svelte"
import Square from "../../assets/svg/Square.svelte"
import Circle from "../../assets/svg/Circle.svelte"
import Checkmark from "../../assets/svg/Checkmark.svelte"
import Clock from "../../assets/svg/Clock.svelte"
import Close from "../../assets/svg/Close.svelte"
import Crosshair from "../../assets/svg/Crosshair.svelte"
import Help from "../../assets/svg/Help.svelte"
import Home from "../../assets/svg/Home.svelte"
import Invalid from "../../assets/svg/Invalid.svelte"
import Location from "../../assets/svg/Location.svelte"
import Location_empty from "../../assets/svg/Location_empty.svelte"
import Location_locked from "../../assets/svg/Location_locked.svelte"
import Note from "../../assets/svg/Note.svelte"
import Resolved from "../../assets/svg/Resolved.svelte"
import Ring from "../../assets/svg/Ring.svelte"
import Scissors from "../../assets/svg/Scissors.svelte"
import Teardrop from "../../assets/svg/Teardrop.svelte"
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte"
import Triangle from "../../assets/svg/Triangle.svelte"
import Brick_wall_square from "../../assets/svg/Brick_wall_square.svelte"
import Brick_wall_round from "../../assets/svg/Brick_wall_round.svelte"
import Gps_arrow from "../../assets/svg/Gps_arrow.svelte"
import { HeartIcon } from "@babeard/svelte-heroicons/solid"
import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline"
import Confirm from "../../assets/svg/Confirm.svelte"
import Not_found from "../../assets/svg/Not_found.svelte"
import { twMerge } from "tailwind-merge"
import Direction_gradient from "../../assets/svg/Direction_gradient.svelte"
import Mastodon from "../../assets/svg/Mastodon.svelte"
import Party from "../../assets/svg/Party.svelte"
import AddSmall from "../../assets/svg/AddSmall.svelte"
import { LinkIcon } from "@babeard/svelte-heroicons/mini"
import Pin from "../../assets/svg/Pin.svelte"
import Square from "../../assets/svg/Square.svelte"
import Circle from "../../assets/svg/Circle.svelte"
import Checkmark from "../../assets/svg/Checkmark.svelte"
import Clock from "../../assets/svg/Clock.svelte"
import Close from "../../assets/svg/Close.svelte"
import Crosshair from "../../assets/svg/Crosshair.svelte"
import Help from "../../assets/svg/Help.svelte"
import Home from "../../assets/svg/Home.svelte"
import Invalid from "../../assets/svg/Invalid.svelte"
import Location from "../../assets/svg/Location.svelte"
import Location_empty from "../../assets/svg/Location_empty.svelte"
import Location_locked from "../../assets/svg/Location_locked.svelte"
import Note from "../../assets/svg/Note.svelte"
import Resolved from "../../assets/svg/Resolved.svelte"
import Ring from "../../assets/svg/Ring.svelte"
import Scissors from "../../assets/svg/Scissors.svelte"
import Teardrop from "../../assets/svg/Teardrop.svelte"
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte"
import Triangle from "../../assets/svg/Triangle.svelte"
import Brick_wall_square from "../../assets/svg/Brick_wall_square.svelte"
import Brick_wall_round from "../../assets/svg/Brick_wall_round.svelte"
import Gps_arrow from "../../assets/svg/Gps_arrow.svelte"
import { HeartIcon } from "@babeard/svelte-heroicons/solid"
import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline"
import Confirm from "../../assets/svg/Confirm.svelte"
import Not_found from "../../assets/svg/Not_found.svelte"
import { twMerge } from "tailwind-merge"
import Direction_gradient from "../../assets/svg/Direction_gradient.svelte"
import Mastodon from "../../assets/svg/Mastodon.svelte"
import Party from "../../assets/svg/Party.svelte"
import AddSmall from "../../assets/svg/AddSmall.svelte"
import { LinkIcon } from "@babeard/svelte-heroicons/mini"
/**
* Renders a single icon.
*
* Icons -placed on top of each other- form a 'Marker' together
*/
/**
* Renders a single icon.
*
* Icons -placed on top of each other- form a 'Marker' together
*/
export let icon: string | undefined
export let color: string | undefined = undefined
export let clss: string | undefined = undefined
export let icon: string | undefined
export let color: string | undefined = undefined
export let clss: string | undefined = undefined
</script>
{#if icon}
@ -100,9 +101,9 @@
{:else if icon === "invalid"}
<Invalid {color} class={clss} />
{:else if icon === "heart"}
<HeartIcon class={clss} />
<HeartIcon style="--svg-color: {color}" class={twMerge(clss,"apply-fill")} />
{:else if icon === "heart_outline"}
<HeartOutlineIcon class={clss} />
<HeartOutlineIcon style="--svg-color: {color}" class={twMerge(clss, "apply-fill")} />
{:else if icon === "confirm"}
<Confirm class={clss} {color} />
{:else if icon === "direction"}
@ -115,9 +116,10 @@
<Party {color} class={clss} />
{:else if icon === "addSmall"}
<AddSmall {color} class={clss} />
{:else if icon === "link"}
<LinkIcon class={clss}/>
{:else if icon === "link"}
<LinkIcon style="--svg-color: {color}" class={twMerge(clss, "apply-fill")}/>
{:else}
<img class={clss ?? "h-full w-full"} src={icon} aria-hidden="true" alt="" />
{/if}
{/if}

View file

@ -551,7 +551,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
}
}
private async setBackground(): Promise<void> {
private async setBackground(retryAttempts: number = 3): Promise<void> {
const map = this._maplibreMap.data
if (!map) {
return
@ -585,12 +585,23 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
} else {
// Make sure that the default maptiler style is loaded as it gives an overlay with roads
const maptiler = AvailableRasterLayers.maptilerDefaultLayer.properties
try {
await this.awaitStyleIsLoaded()
if (!map.getSource(maptiler.id)) {
this.removeCurrentLayer(map)
map.addSource(maptiler.id, MapLibreAdaptor.prepareWmsSource(maptiler))
map.setStyle(maptiler.url)
await this.awaitStyleIsLoaded()
}
}catch (e) {
if(retryAttempts > 0){
window.requestAnimationFrame(() => {
console.log("Retrying to set the background ("+retryAttempts+" attempts remaining)... Failed because",e)
this.setBackground(retryAttempts-1)
})
}
}
}
if (!map.getLayer(addLayerBeforeId)) {

View file

@ -76,7 +76,7 @@
</script>
{#if hasLayers}
<form class="flex h-full w-full flex-col">
<form class="flex h-full w-full flex-col" on:submit|preventDefault={() => {}}>
<button
tabindex="-1"
on:click={() => apply()}

View file

@ -20,6 +20,7 @@
import ToSvelte from "../../Base/ToSvelte.svelte"
import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"
import FilteredLayer from "../../../Models/FilteredLayer"
import Confirm from "../../../assets/svg/Confirm.svelte"
export let importFlow: ImportFlow<ImportFlowArguments>
let state = importFlow.state
@ -159,7 +160,7 @@
{#if importFlow.args.icon}
<img src={importFlow.args.icon} />
{:else}
<ToSvelte construct={Svg.confirm_svg().SetClass("w-8 h-8 pr-4")} />
<Confirm class="w-8 h-8 pr-4"/>
{/if}
</span>
<slot name="confirm-text">

View file

@ -1,4 +1,3 @@
import Svg from "../../Svg"
import { UIEventSource } from "../../Logic/UIEventSource"
import Translations from "../i18n/Translations"
import { Translation } from "../i18n/Translation"
@ -10,6 +9,9 @@ import { And } from "../../Logic/Tags/And"
import { Tag } from "../../Logic/Tags/Tag"
import { SpecialVisualizationState } from "../SpecialVisualization"
import { Feature, Point } from "geojson"
import SvelteUIElement from "../Base/SvelteUIElement"
import Confirm from "../../assets/svg/Confirm.svelte"
import Relocation from "../../assets/svg/Relocation.svelte"
export interface MoveReason {
text: Translation | string
@ -32,10 +34,7 @@ export class MoveWizardState {
constructor(id: string, options: MoveConfig, state: SpecialVisualizationState) {
this._state = state
const t = Translations.t.move
this.reasons = MoveWizardState.initReasons(options)
if (this.reasons.length > 0) {
this.checkIsAllowed(id)
}
@ -49,7 +48,7 @@ export class MoveWizardState {
reasons.push({
text: t.reasons.reasonRelocation,
invitingText: t.inviteToMove.reasonRelocation,
icon: Svg.relocation_svg(),
icon: new SvelteUIElement(Relocation),
changesetCommentValue: "relocated",
lockBounds: false,
background: undefined,
@ -63,7 +62,7 @@ export class MoveWizardState {
reasons.push({
text: t.reasons.reasonInaccurate,
invitingText: t.inviteToMove.reasonInaccurate,
icon: Svg.crosshair_svg(),
icon: new SvelteUIElement(Confirm),
changesetCommentValue: "improve_accuracy",
lockBounds: true,
includeSearch: false,

View file

@ -9,6 +9,8 @@ import { LoginToggle } from ".././LoginButton"
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
import { UIEventSource } from "../../../Logic/UIEventSource"
import Constants from "../../../Models/Constants"
import SvelteUIElement from "../../Base/SvelteUIElement"
import Checkmark from "../../../assets/svg/Checkmark.svelte"
export class CloseNoteButton implements SpecialVisualization {
public readonly funcName = "close_note"
@ -61,7 +63,7 @@ export class CloseNoteButton implements SpecialVisualization {
zoomButton: string
} = <any>Utils.ParseVisArgs(this.args, args)
let icon = Svg.checkmark_svg()
let icon: BaseUIElement = new SvelteUIElement(Checkmark)
if (params.icon !== "checkmark.svg" && (args[2] ?? "") !== "") {
icon = new Img(args[1])
}

View file

@ -4,7 +4,7 @@
import type { Feature } from "geojson"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import TagRenderingAnswer from "./TagRenderingAnswer.svelte"
import { PencilAltIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import { PencilAltIcon } from "@rgossiaux/svelte-heroicons/solid"
import TagRenderingQuestion from "./TagRenderingQuestion.svelte"
import { onDestroy } from "svelte"
import Tr from "../../Base/Tr.svelte"
@ -27,12 +27,12 @@
/**
* Indicates if this tagRendering currently shows the attribute or asks the question to _change_ the property
*/
export let editMode = !config.IsKnown(tags.data) // || showQuestionIfUnknown;
export let editMode = !config.IsKnown(tags.data)
if (tags) {
onDestroy(
tags.addCallbackD((tags) => {
editMode = !config.IsKnown(tags)
})
}),
)
}

View file

@ -159,7 +159,7 @@
}
}
function onSave() {
function onSave(e) {
if (selectedTags === undefined) {
return
}
@ -198,7 +198,9 @@
function onInputKeypress(e: KeyboardEvent) {
if (e.key === "Enter") {
onSave()
e.preventDefault()
e.stopPropagation()
onSave(e)
}
}

View file

@ -1,8 +1,9 @@
import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI"
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource"
import { GeoOperations } from "../../Logic/GeoOperations"
import Constants from "../../Models/Constants"
import SvelteUIElement from "../Base/SvelteUIElement"
import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI.svelte"
/**
* Wrapper around 'UploadTraceToOsmUI'
@ -20,10 +21,9 @@ export class UploadToOsmViz implements SpecialVisualization {
__: string[]
) {
const locations = state.historicalUserLocations.features.data
return new UploadTraceToOsmUI((title) => GeoOperations.toGpx(locations, title), state, {
whenUploaded: async () => {
state.historicalUserLocations.features.setData([])
},
return new SvelteUIElement(UploadTraceToOsmUI, {
state,
trace: (title: string) => GeoOperations.toGpx(locations, title)
})
}
}

View file

@ -41,7 +41,6 @@
<TagRenderingEditable
{config}
selectedElement={undefined}
showQuestionIfUnknown={true}
{state}
{tags}
/>

View file

@ -172,6 +172,7 @@ export abstract class EditJsonState<T> {
public setValueAt(path: ReadonlyArray<string | number>, v: any) {
let entry = this.configuration.data
console.trace("Setting value at", path,"to",v)
const isUndefined =
v === undefined ||
v === null ||

View file

@ -117,7 +117,6 @@
selectedElement={state.exampleFeature}
{config}
editingEnabled={new ImmutableStore(true)}
showQuestionIfUnknown={true}
{state}
{tags}
/>

View file

@ -147,6 +147,9 @@
return { ...<object>v }
}
if (schema.type === "boolean") {
if(v === null || v === undefined){
return v
}
return v === "true" || v === "yes" || v === "1"
}
if (mightBeBoolean(schema.type)) {
@ -187,7 +190,6 @@
editMode={startInEditMode}
{config}
selectedElement={undefined}
showQuestionIfUnknown={true}
{state}
{tags}
/>

View file

@ -215,7 +215,6 @@
<TagRenderingEditable
{config}
selectedElement={undefined}
showQuestionIfUnknown={!schema.hints?.ifunset}
{state}
{tags}
/>

View file

@ -139,7 +139,6 @@
<TagRenderingEditable
config={configBuiltin}
selectedElement={undefined}
showQuestionIfUnknown={true}
{state}
{tags}
/>

View file

@ -1,16 +1,7 @@
<script lang="ts">
// Testing grounds
import Motion from "../Sensors/Motion"
import { Store, Stores } from "../Logic/UIEventSource"
let maxAcc = Motion.singleton.maxAcc
let shaken = Motion.singleton.lastShakeEvent
let recentlyShaken = Stores.Chronic(250).mapD(
(now) => now.getTime() - 3000 < shaken.data?.getTime()
)
import Icon from "./Map/Icon.svelte"
</script>
Acc: {$maxAcc}
{#if $recentlyShaken}
<div class="text-5xl text-red-500">SHAKEN</div>
{/if}
<Icon clss="h-16 w-16" icon="heart" color="#ff0000"/>

View file

@ -96,6 +96,9 @@
if (element.properties.id.startsWith("current_view")) {
return currentViewLayer
}
if(element.properties.id === "location_track"){
return layout.layers.find(l => l.id === "gps_track")
}
return state.layout.getMatchingLayer(element.properties)
},
)
@ -392,7 +395,7 @@
<!-- Floatover with the selected element, if applicable -->
<FloatOver
on:close={() => {
selectedElement.setData(undefined)
state.selectedElement.setData(undefined)
}}
>
<div class="flex h-full w-full">

View file

@ -9,9 +9,15 @@ import Combine from "../Base/Combine"
import Img from "../Base/Img"
import { WikimediaImageProvider } from "../../Logic/ImageProviders/WikimediaImageProvider"
import Link from "../Base/Link"
import Svg from "../../Svg"
import BaseUIElement from "../BaseUIElement"
import { Utils } from "../../Utils"
import SvelteUIElement from "../Base/SvelteUIElement"
import * as Wikidata_icon from "../../assets/svg/Wikidata.svelte"
import Gender_male from "../../assets/svg/Gender_male.svelte"
import Gender_female from "../../assets/svg/Gender_female.svelte"
import Gender_inter from "../../assets/svg/Gender_inter.svelte"
import Gender_trans from "../../assets/svg/Gender_trans.svelte"
import Gender_queer from "../../assets/svg/Gender_queer.svelte"
export default class WikidataPreviewBox extends VariableUiElement {
private static isHuman = [{ p: 31 /*is a*/, q: 5 /* human */ }]
@ -28,22 +34,36 @@ export default class WikidataPreviewBox extends VariableUiElement {
requires: WikidataPreviewBox.isHuman,
property: "P21",
display: new Map([
["Q6581097", () => Svg.gender_male_svg().SetStyle("width: 1rem; height: auto")],
["Q6581072", () => Svg.gender_female_svg().SetStyle("width: 1rem; height: auto")],
["Q1097630", () => Svg.gender_inter_svg().SetStyle("width: 1rem; height: auto")],
[
"Q6581097",
() => new SvelteUIElement(Gender_male).SetStyle("width: 1rem; height: auto"),
],
[
"Q6581072",
() => new SvelteUIElement(Gender_female).SetStyle("width: 1rem; height: auto"),
],
[
"Q1097630",
() => new SvelteUIElement(Gender_inter).SetStyle("width: 1rem; height: auto"),
],
[
"Q1052281",
() =>
Svg.gender_trans_svg().SetStyle(
new SvelteUIElement(Gender_trans).SetStyle(
"width: 1rem; height: auto"
) /*'transwomen'*/,
],
[
"Q2449503",
() =>
Svg.gender_trans_svg().SetStyle("width: 1rem; height: auto") /*'transmen'*/,
new SvelteUIElement(Gender_trans).SetStyle(
"width: 1rem; height: auto"
) /*'transmen'*/,
],
[
"Q48270",
() => new SvelteUIElement(Gender_queer).SetStyle("width: 1rem; height: auto"),
],
["Q48270", () => Svg.gender_queer_svg().SetStyle("width: 1rem; height: auto")],
]),
textMode: new Map([
["Q6581097", "♂️"],
@ -116,7 +136,9 @@ export default class WikidataPreviewBox extends VariableUiElement {
wikidata.id,
options?.noImages
? wikidata.id
: Svg.wikidata_svg().SetStyle("width: 2.5rem").SetClass("block"),
: new SvelteUIElement(Wikidata_icon)
.SetStyle("width: 2.5rem")
.SetClass("block"),
]).SetClass("flex"),
Wikidata.IdToArticle(wikidata.id),
true

74
src/UI/Zoomcontrol.ts Normal file
View file

@ -0,0 +1,74 @@
import { Stores, UIEventSource } from "../Logic/UIEventSource"
import { Utils } from "../Utils"
/**
* Utilities to (re)set user zoom (this is when the user enlarges HTML-elements by pinching out a non-map element).
* If the user zooms in and goes back to the map, it should reset to 1.0
*/
export default class Zoomcontrol {
private static readonly singleton = new Zoomcontrol()
private static readonly initialValue =
"width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0, user-scalable=1"
private static readonly noZoom =
"width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=1"
private readonly viewportElement: HTMLMetaElement
private readonly _allowZooming: UIEventSource<boolean>
private readonly _lockTokens: Set<any> = new Set<any>()
private constructor() {
if (Utils.runningFromConsole) {
return
}
const metaElems = document.getElementsByTagName("head")[0].getElementsByTagName("meta")
this.viewportElement = Array.from(metaElems).find(
(meta) => meta.getAttribute("name") === "viewport"
)
this._allowZooming = new UIEventSource<boolean>(true)
this._allowZooming.addCallback((allowed) => {
this.apply(allowed ? Zoomcontrol.initialValue : Zoomcontrol.noZoom)
})
Stores.Chronic(1000).addCallback((_) =>
console.log(this.viewportElement.getAttribute("content"))
)
}
private _resetZoom() {
this.apply(Zoomcontrol.noZoom)
requestAnimationFrame(() => {
// Does not work on firefox, see https://bugzilla.mozilla.org/show_bug.cgi?id=1873934
this.allowZoomIfUnlocked()
})
}
private apply(fullSpec: string) {
this.viewportElement?.setAttribute("content", fullSpec)
}
public static createLock(): () => void {
return Zoomcontrol.singleton._createLock()
}
private allowZoomIfUnlocked() {
if (this._lockTokens.size > 0) {
return
}
this.apply(Zoomcontrol.initialValue)
}
private _createLock(): () => void {
const token = {}
const lockTokens = this._lockTokens
lockTokens.add(token)
this._resetZoom()
return () => {
lockTokens.delete(token)
this.allowZoomIfUnlocked()
}
}
public static resetzoom() {
this.singleton._resetZoom()
}
}

View file

@ -1450,7 +1450,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
public static scrollIntoView(element: HTMLBaseElement | HTMLDivElement) {
// Is the element completely in the view?
const parentRect = Utils.findParentWithScrolling(element).getBoundingClientRect()
const parentRect = Utils.findParentWithScrolling(element)?.getBoundingClientRect()
if(!parentRect){
return
}
const elementRect = element.getBoundingClientRect()
// Check if the element is within the vertical bounds of the parent element

View file

@ -0,0 +1,10 @@
import { Store } from "../Logic/UIEventSource"
export function selectDefault(htmlElement: HTMLInputElement, value: Store<string>) {
if (!document.body.contains(htmlElement) || value?.data === undefined) {
return
}
if (value.data === htmlElement.value) {
htmlElement.checked = true
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -225,6 +225,94 @@
],
"description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation"
},
{
"path": [
"mappings",
"alsoShowIf"
],
"required": false,
"hints": {},
"type": [
{
"$ref": "#/definitions/{and:TagConfigJson[];}"
},
{
"$ref": "#/definitions/{or:TagConfigJson[];}"
},
{
"type": "string"
}
],
"description": "Also show this 'then'-option if the feature matches these tags.\nIdeal for outdated tags."
},
{
"path": [
"mappings",
"alsoShowIf",
"and"
],
"required": false,
"hints": {
"typehint": "tag"
},
"type": [
{
"$ref": "#/definitions/{and:TagConfigJson[];}"
},
{
"type": "object",
"properties": {
"or": {
"type": "array",
"items": {
"$ref": "#/definitions/TagConfigJson"
}
}
},
"required": [
"or"
]
},
{
"type": "string"
}
],
"description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation"
},
{
"path": [
"mappings",
"alsoShowIf",
"or"
],
"required": false,
"hints": {
"typehint": "tag"
},
"type": [
{
"$ref": "#/definitions/{and:TagConfigJson[];}"
},
{
"type": "object",
"properties": {
"or": {
"type": "array",
"items": {
"$ref": "#/definitions/TagConfigJson"
}
}
},
"required": [
"or"
]
},
{
"type": "string"
}
],
"description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation"
},
{
"path": [
"mappings",
@ -565,6 +653,10 @@
{
"if": "value=id",
"then": "<b>id</b> Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
},
{
"if": "value=slope",
"then": "<b>slope</b> Validates that the slope is a valid number.The accompanying input element uses the gyroscope and the compass to determine the correct incline. The sign of the incline will be set automatically. The bearing of the way is compared to the bearing of the compass, as such, the device knows if it is measuring in the forward or backward direction."
}
]
},
@ -775,6 +867,22 @@
],
"description": "This hint is shown in subtle text under the question.\nThis can give some extra information on what the answer should ook like"
},
{
"path": [
"editButtonAriaLabel"
],
"required": false,
"hints": {},
"type": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
],
"description": "When using a screenreader and selecting the 'edit' button, the current rendered value is read aloud in normal circumstances.\nIn some rare cases, this is not desirable. For example, if the rendered value is a link to a website, this link can be selected (and will be read aloud).\nIf the user presses _tab_ again, they'll select the button and have the link read aloud a second time."
},
{
"path": [
"labels"

View file

@ -53,6 +53,9 @@
--image-carousel-height: 350px;
/** Technical value, used by icon.svelte
*/
--svg-color: #000000;
}
/***********************************************************************\
@ -612,6 +615,10 @@ a.link-underline {
overflow: visible !important;
}
svg.apply-fill path {
fill: var(--svg-color)
}
.compass_arrow {
width: calc( 2.5rem - 1px ) ;
height: calc( 2.5rem - 1px )

View file

@ -58,6 +58,7 @@ self.addEventListener("fetch", async (e) => {
origin.host === requestUrl.host &&
origin.hostname !== "127.0.0.1" &&
origin.hostname !== "localhost" &&
!origin.hostname.endsWith(".local") &&
!origin.host.endsWith(".gitpod.io")
if (!shouldBeCached) {
console.log("Not intercepting ", requestUrl.toString(), origin.host, requestUrl.host)