forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
c672fe7668
138 changed files with 14304 additions and 1299 deletions
|
@ -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()
|
||||
})
|
||||
|
|
|
@ -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>[]> {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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="' +
|
||||
|
|
|
@ -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", {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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 }
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"),
|
||||
]),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -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, () => {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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}
|
||||
|
|
181
src/UI/BigComponents/UploadTraceToOsmUI.svelte
Normal file
181
src/UI/BigComponents/UploadTraceToOsmUI.svelte
Normal 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>
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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, {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 })} />
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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()}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
<TagRenderingEditable
|
||||
{config}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
|
|
|
@ -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 ||
|
||||
|
|
|
@ -117,7 +117,6 @@
|
|||
selectedElement={state.exampleFeature}
|
||||
{config}
|
||||
editingEnabled={new ImmutableStore(true)}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -215,7 +215,6 @@
|
|||
<TagRenderingEditable
|
||||
{config}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={!schema.hints?.ifunset}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
|
|
|
@ -139,7 +139,6 @@
|
|||
<TagRenderingEditable
|
||||
config={configBuiltin}
|
||||
selectedElement={undefined}
|
||||
showQuestionIfUnknown={true}
|
||||
{state}
|
||||
{tags}
|
||||
/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
74
src/UI/Zoomcontrol.ts
Normal 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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
10
src/Utils/selectDefault.ts
Normal file
10
src/Utils/selectDefault.ts
Normal 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
|
@ -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"
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue