forked from MapComplete/MapComplete
Merge branch 'feature/android-capacitator' of github.com:pietervdvn/MapComplete into feature/android-capacitator
This commit is contained in:
commit
a9b6b6148e
370 changed files with 5765 additions and 3198 deletions
|
|
@ -13,7 +13,6 @@ import StaticFeatureSource, {
|
|||
} from "../FeatureSource/Sources/StaticFeatureSource"
|
||||
import { MapProperties } from "../../Models/MapProperties"
|
||||
import { Orientation } from "../../Sensors/Orientation"
|
||||
|
||||
;("use strict")
|
||||
/**
|
||||
* The geolocation-handler takes a map-location and a geolocation state.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import Constants from "../../Models/Constants"
|
|||
import { Utils } from "../../Utils"
|
||||
import { GeoLocationState } from "../State/GeoLocationState"
|
||||
import { OsmConnection } from "../Osm/OsmConnection"
|
||||
|
||||
;("use strict")
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { FeatureSource, WritableFeatureSource } from "../FeatureSource"
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"
|
||||
import { Feature } from "geojson"
|
||||
|
||||
;("use strict")
|
||||
/**
|
||||
* A simple, read only feature store.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import {
|
|||
import { Tiles } from "../Models/TileRange"
|
||||
import { Utils } from "../Utils"
|
||||
import { NearestPointOnLine } from "@turf/nearest-point-on-line"
|
||||
|
||||
;("use strict")
|
||||
|
||||
export class GeoOperations {
|
||||
|
|
|
|||
|
|
@ -91,7 +91,6 @@ export default class AllImageProviders {
|
|||
However, we override them if a custom image tag is set, e.g. 'image:menu'
|
||||
*/
|
||||
const prefixes = tagKey ?? imageProvider.defaultKeyPrefixes
|
||||
console.log("Prefixes are", tagKey, prefixes)
|
||||
const singleSource = tags.bindD((tags) => imageProvider.getRelevantUrls(tags, prefixes))
|
||||
allSources.push(singleSource)
|
||||
singleSource.addCallbackAndRunD((_) => {
|
||||
|
|
|
|||
|
|
@ -127,7 +127,9 @@ export default class OsmObjectDownloader {
|
|||
* Beware: their geometry will be incomplete!
|
||||
*/
|
||||
public async DownloadReferencingWays(id: string): Promise<OsmWay[]> {
|
||||
const data = await Utils.downloadJsonCached(`${this.backend}api/0.6/${id}/ways`, 60 * 1000)
|
||||
const data = await Utils.downloadJsonCached<{
|
||||
elements: { id: number }[]
|
||||
}>(`${this.backend}api/0.6/${id}/ways`, 60 * 1000)
|
||||
return data.elements.map((wayInfo) => new OsmWay(wayInfo.id, wayInfo))
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +138,7 @@ export default class OsmObjectDownloader {
|
|||
* Beware: their geometry will be incomplete!
|
||||
*/
|
||||
public async DownloadReferencingRelations(id: string): Promise<OsmRelation[]> {
|
||||
const data = await Utils.downloadJsonCached(
|
||||
const data = await Utils.downloadJsonCached<{ elements: { id: number }[] }>(
|
||||
`${this.backend}api/0.6/${id}/relations`,
|
||||
60 * 1000
|
||||
)
|
||||
|
|
|
|||
|
|
@ -38,14 +38,27 @@ export class OsmPreferences {
|
|||
})
|
||||
}
|
||||
|
||||
private setPreferencesAll(key: string, value: string) {
|
||||
/**
|
||||
* Sets a new preferenceValue in 'allPreferences'
|
||||
* @param key
|
||||
* @param value
|
||||
* @param deferping: the end user will ping '_allPreferences'
|
||||
* @private
|
||||
*/
|
||||
private setPreferencesAll(key: string, value: string, deferping = false) {
|
||||
if (this._allPreferences.data[key] !== value) {
|
||||
this._allPreferences.data[key] = value
|
||||
this._allPreferences.ping()
|
||||
if (!deferping) {
|
||||
this._allPreferences.ping()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private initPreference(key: string, value: string = undefined): UIEventSource<string> {
|
||||
private initPreference(
|
||||
key: string,
|
||||
value: string = undefined,
|
||||
deferPing = false
|
||||
): UIEventSource<string> {
|
||||
if (this.preferences[key] !== undefined) {
|
||||
if (value !== undefined) {
|
||||
this.preferences[key].set(value)
|
||||
|
|
@ -54,12 +67,12 @@ export class OsmPreferences {
|
|||
}
|
||||
const pref = (this.preferences[key] = new UIEventSource(value, "preference: " + key))
|
||||
if (value) {
|
||||
this.setPreferencesAll(key, value)
|
||||
this.setPreferencesAll(key, value, deferPing)
|
||||
}
|
||||
pref.addCallback((v) => {
|
||||
console.log("Got an update:", key, "--->", v)
|
||||
this.uploadKvSplit(key, v)
|
||||
this.setPreferencesAll(key, v)
|
||||
this.setPreferencesAll(key, v, deferPing)
|
||||
})
|
||||
return pref
|
||||
}
|
||||
|
|
@ -73,11 +86,12 @@ export class OsmPreferences {
|
|||
await this.removeLegacy(legacy)
|
||||
}
|
||||
for (const key in merged) {
|
||||
this.initPreference(key, prefs[key])
|
||||
this.initPreference(key, prefs[key], true)
|
||||
}
|
||||
for (const key in legacy) {
|
||||
this.initPreference(key, legacy[key])
|
||||
this.initPreference(key, legacy[key], true)
|
||||
}
|
||||
this._allPreferences.ping()
|
||||
}
|
||||
|
||||
public getPreference(key: string, defaultValue: string = undefined, prefix?: string) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
import * as nsi from "../../../node_modules/name-suggestion-index/dist/nsi.json"
|
||||
import * as nsiWD from "../../../node_modules/name-suggestion-index/dist/wikidata.min.json"
|
||||
|
||||
import * as nsiFeatures from "../../../node_modules/name-suggestion-index/dist/featureCollection.json"
|
||||
import { LocationConflation } from "@rapideditor/location-conflation"
|
||||
import type { Feature, MultiPolygon } from "geojson"
|
||||
|
|
@ -56,28 +53,57 @@ export interface NSIItem {
|
|||
}
|
||||
|
||||
export default class NameSuggestionIndex {
|
||||
private static readonly nsiFile: Readonly<NSIFile> = <any>nsi
|
||||
private static readonly nsiWdFile: Readonly<
|
||||
public static readonly supportedTypes = ["brand", "flag", "operator", "transit"] as const
|
||||
private readonly nsiFile: Readonly<NSIFile>
|
||||
private readonly nsiWdFile: Readonly<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
logos: { wikidata?: string; facebook?: string }
|
||||
}
|
||||
>
|
||||
> = <any>nsiWD["wikidata"]
|
||||
>
|
||||
|
||||
private static loco = new LocationConflation(nsiFeatures) // Some additional boundaries
|
||||
|
||||
private static _supportedTypes: string[]
|
||||
private _supportedTypes: string[]
|
||||
|
||||
public static supportedTypes(): string[] {
|
||||
constructor(
|
||||
nsiFile: Readonly<NSIFile>,
|
||||
nsiWdFile: Readonly<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
logos: { wikidata?: string; facebook?: string }
|
||||
}
|
||||
>
|
||||
>
|
||||
) {
|
||||
this.nsiFile = nsiFile
|
||||
this.nsiWdFile = nsiWdFile
|
||||
}
|
||||
|
||||
private static inited: NameSuggestionIndex = undefined
|
||||
|
||||
public static async getNsiIndex(): Promise<NameSuggestionIndex> {
|
||||
if (NameSuggestionIndex.inited) {
|
||||
return NameSuggestionIndex.inited
|
||||
}
|
||||
const [nsi, nsiWd] = await Promise.all(
|
||||
["assets/data/nsi/nsi.json", "assets/data/nsi/wikidata.min.json"].map((url) =>
|
||||
Utils.downloadJsonCached(url, 1000 * 60 * 60 * 24 * 30)
|
||||
)
|
||||
)
|
||||
NameSuggestionIndex.inited = new NameSuggestionIndex(<any>nsi, <any>nsiWd["wikidata"])
|
||||
return NameSuggestionIndex.inited
|
||||
}
|
||||
|
||||
public supportedTypes(): string[] {
|
||||
if (this._supportedTypes) {
|
||||
return this._supportedTypes
|
||||
}
|
||||
const keys = Object.keys(NameSuggestionIndex.nsiFile.nsi)
|
||||
const all = keys.map(
|
||||
(k) => NameSuggestionIndex.nsiFile.nsi[k].properties.path.split("/")[0]
|
||||
)
|
||||
const keys = Object.keys(this.nsiFile.nsi)
|
||||
const all = keys.map((k) => this.nsiFile.nsi[k].properties.path.split("/")[0])
|
||||
this._supportedTypes = Utils.Dedup(all).map((s) => {
|
||||
if (s.endsWith("s")) {
|
||||
s = s.substring(0, s.length - 1)
|
||||
|
|
@ -123,7 +149,12 @@ export default class NameSuggestionIndex {
|
|||
return merged
|
||||
}
|
||||
|
||||
public static isSvg(nsiItem: NSIItem, type: string): boolean | undefined {
|
||||
public isSvg(nsiItem: NSIItem, type: string): boolean | undefined {
|
||||
if (this.nsiWdFile === undefined) {
|
||||
throw (
|
||||
"nsiWdi file is not loaded, cannot determine if " + nsiItem.id + " has an SVG image"
|
||||
)
|
||||
}
|
||||
const logos = this.nsiWdFile[nsiItem?.tags?.[type + ":wikidata"]]?.logos
|
||||
if (!logos) {
|
||||
return undefined
|
||||
|
|
@ -138,7 +169,7 @@ export default class NameSuggestionIndex {
|
|||
return false
|
||||
}
|
||||
|
||||
public static async generateMappings(
|
||||
public async generateMappings(
|
||||
type: string,
|
||||
tags: Record<string, string>,
|
||||
country: string[],
|
||||
|
|
@ -157,7 +188,7 @@ export default class NameSuggestionIndex {
|
|||
continue
|
||||
}
|
||||
const value = tags[key]
|
||||
const actualBrands = NameSuggestionIndex.getSuggestionsForKV(
|
||||
const actualBrands = this.getSuggestionsForKV(
|
||||
type,
|
||||
key,
|
||||
value,
|
||||
|
|
@ -177,7 +208,7 @@ export default class NameSuggestionIndex {
|
|||
if (hasIcon) {
|
||||
// Using <img src=...> works fine without an extension for JPG and PNG, but _not_ svg :(
|
||||
icon = "./assets/data/nsi/logos/" + nsiItem.id
|
||||
if (NameSuggestionIndex.isSvg(nsiItem, type)) {
|
||||
if (this.isSvg(nsiItem, type)) {
|
||||
icon = icon + ".svg"
|
||||
}
|
||||
}
|
||||
|
|
@ -207,13 +238,13 @@ export default class NameSuggestionIndex {
|
|||
return mappings
|
||||
}
|
||||
|
||||
public static supportedTags(
|
||||
public supportedTags(
|
||||
type: "operator" | "brand" | "flag" | "transit" | string
|
||||
): Record<string, string[]> {
|
||||
const tags: Record<string, string[]> = {}
|
||||
const keys = Object.keys(NameSuggestionIndex.nsiFile.nsi)
|
||||
const keys = Object.keys(this.nsiFile.nsi)
|
||||
for (const key of keys) {
|
||||
const nsiItem = NameSuggestionIndex.nsiFile.nsi[key]
|
||||
const nsiItem = this.nsiFile.nsi[key]
|
||||
const path = nsiItem.properties.path
|
||||
const [osmType, osmkey, osmvalue] = path.split("/")
|
||||
if (type !== osmType && type + "s" !== osmType) {
|
||||
|
|
@ -231,9 +262,9 @@ export default class NameSuggestionIndex {
|
|||
* Returns a list of all brands/operators
|
||||
* @param type
|
||||
*/
|
||||
public static allPossible(type: "brand" | "operator"): NSIItem[] {
|
||||
public allPossible(type: "brand" | "operator"): NSIItem[] {
|
||||
const options: NSIItem[] = []
|
||||
const tags = NameSuggestionIndex.supportedTags(type)
|
||||
const tags = this.supportedTags(type)
|
||||
for (const osmKey in tags) {
|
||||
const values = tags[osmKey]
|
||||
for (const osmValue of values) {
|
||||
|
|
@ -249,7 +280,7 @@ export default class NameSuggestionIndex {
|
|||
* @param country: a string containing one or more country codes, separated by ";"
|
||||
* @param location: center point of the feature, should be [lon, lat]
|
||||
*/
|
||||
public static getSuggestionsFor(
|
||||
public getSuggestionsFor(
|
||||
type: string,
|
||||
tags: { key: string; value: string }[],
|
||||
country: string = undefined,
|
||||
|
|
@ -274,7 +305,7 @@ export default class NameSuggestionIndex {
|
|||
* @param country: a string containing one or more country codes, separated by ";"
|
||||
* @param location: center point of the feature, should be [lon, lat]
|
||||
*/
|
||||
public static getSuggestionsForKV(
|
||||
public getSuggestionsForKV(
|
||||
type: string,
|
||||
key: string,
|
||||
value: string,
|
||||
|
|
@ -282,7 +313,7 @@ export default class NameSuggestionIndex {
|
|||
location: [number, number] = undefined
|
||||
): NSIItem[] {
|
||||
const path = `${type}s/${key}/${value}`
|
||||
const entry = NameSuggestionIndex.nsiFile.nsi[path]
|
||||
const entry = this.nsiFile.nsi[path]
|
||||
const countries = country?.split(";") ?? []
|
||||
return entry?.items?.filter((i) => {
|
||||
if (i.locationSet.include.indexOf("001") >= 0) {
|
||||
|
|
@ -312,8 +343,8 @@ export default class NameSuggestionIndex {
|
|||
}
|
||||
|
||||
const hasSpecial =
|
||||
i.locationSet.include?.some((i) => i.endsWith(".geojson") || Array.isArray(i)) ||
|
||||
i.locationSet.exclude?.some((i) => i.endsWith(".geojson") || Array.isArray(i))
|
||||
i.locationSet.include?.some((i) => Array.isArray(i) || i.endsWith(".geojson")) ||
|
||||
i.locationSet.exclude?.some((i) => Array.isArray(i) || i.endsWith(".geojson"))
|
||||
if (!hasSpecial) {
|
||||
return false
|
||||
}
|
||||
|
|
@ -335,4 +366,17 @@ export default class NameSuggestionIndex {
|
|||
return false
|
||||
})
|
||||
}
|
||||
|
||||
public static async generateMappings(
|
||||
key: string,
|
||||
tags: Exclude<Record<string, string>, undefined | null>,
|
||||
country: string[],
|
||||
center: [number, number],
|
||||
options: {
|
||||
sortByFrequency: boolean
|
||||
}
|
||||
): Promise<Mapping[]> {
|
||||
const nsi = await NameSuggestionIndex.getNsiIndex()
|
||||
return nsi.generateMappings(key, tags, country, center, options)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,10 @@ import {
|
|||
} from "../Json/QuestionableTagRenderingConfigJson"
|
||||
import { ConversionContext } from "./ConversionContext"
|
||||
import { Translation } from "../../../UI/i18n/Translation"
|
||||
import NameSuggestionIndex from "../../../Logic/Web/NameSuggestionIndex"
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
||||
import { Tag } from "../../../Logic/Tags/Tag"
|
||||
import Validators from "../../../UI/InputElement/Validators"
|
||||
import { CheckTranslation } from "./Validation"
|
||||
import NameSuggestionIndex from "../../../Logic/Web/NameSuggestionIndex"
|
||||
|
||||
export class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
||||
private readonly _layerConfig: LayerConfigJson
|
||||
|
|
@ -197,11 +196,11 @@ export class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJso
|
|||
}
|
||||
}
|
||||
if (
|
||||
this._layerConfig?.source?.osmTags &&
|
||||
NameSuggestionIndex.supportedTypes().indexOf(json.freeform.key) >= 0
|
||||
this._layerConfig?.source?.["osmTags"] &&
|
||||
NameSuggestionIndex.supportedTypes.indexOf(<any>json.freeform.key) >= 0
|
||||
) {
|
||||
const tags = TagUtils.TagD(this._layerConfig?.source?.osmTags)?.usedTags()
|
||||
const suggestions = NameSuggestionIndex.getSuggestionsFor(json.freeform.key, tags)
|
||||
const tags = TagUtils.TagD(this._layerConfig?.source?.["osmTags"])?.usedTags()
|
||||
/* const suggestions = nameSuggestionIndexBundled.getSuggestionsFor(json.freeform.key, tags)
|
||||
if (suggestions === undefined) {
|
||||
context
|
||||
.enters("freeform", "type")
|
||||
|
|
@ -209,8 +208,8 @@ export class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJso
|
|||
"No entry found in the 'Name Suggestion Index'. None of the 'osmSource'-tags match an entry in the NSI.\n\tOsmSource-tags are " +
|
||||
tags.map((t) => new Tag(t.key, t.value).asHumanString()).join(" ; ")
|
||||
)
|
||||
}
|
||||
} else if (json.freeform.type === "nsi") {
|
||||
}*/
|
||||
} else if (json.freeform["type"] === "nsi") {
|
||||
context
|
||||
.enters("freeform", "type")
|
||||
.warn(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ import { QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRender
|
|||
import MarkdownUtils from "../../Utils/MarkdownUtils"
|
||||
import { And } from "../../Logic/Tags/And"
|
||||
import Combine from "../../UI/Base/Combine"
|
||||
import SvelteUIElement from "../../UI/Base/SvelteUIElement"
|
||||
import DynamicMarker from "../../UI/Map/DynamicMarker.svelte"
|
||||
import { ImmutableStore } from "../../Logic/UIEventSource"
|
||||
|
||||
export default class LayerConfig extends WithContextLoader {
|
||||
public static readonly syncSelectionAllowed = ["no", "local", "theme-only", "global"] as const
|
||||
|
|
@ -67,6 +70,8 @@ export default class LayerConfig extends WithContextLoader {
|
|||
public readonly popupInFloatover: boolean | string
|
||||
public readonly enableMorePrivacy: boolean
|
||||
|
||||
public readonly baseTags: Readonly<Record<string, string>>
|
||||
|
||||
/**
|
||||
* If this layer is based on another layer, this might be indicated here
|
||||
* @private
|
||||
|
|
@ -352,31 +357,17 @@ export default class LayerConfig extends WithContextLoader {
|
|||
)
|
||||
}
|
||||
this.popupInFloatover = json.popupInFloatover ?? false
|
||||
}
|
||||
|
||||
public defaultIcon(properties?: Record<string, string>): BaseUIElement | undefined {
|
||||
if (this.mapRendering === undefined || this.mapRendering === null) {
|
||||
return undefined
|
||||
}
|
||||
const mapRenderings = this.mapRendering.filter((r) => r.location.has("point"))
|
||||
if (mapRenderings.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
return new Combine(
|
||||
mapRenderings.map((mr) =>
|
||||
mr
|
||||
.GetBaseIcon(properties ?? this.GetBaseTags())
|
||||
.SetClass("absolute left-0 top-0 w-full h-full")
|
||||
)
|
||||
).SetClass("relative block w-full h-full")
|
||||
}
|
||||
|
||||
public GetBaseTags(): Record<string, string> {
|
||||
return TagUtils.changeAsProperties(
|
||||
this.baseTags = TagUtils.changeAsProperties(
|
||||
this.source?.osmTags?.asChange({ id: "node/-1" }) ?? [{ k: "id", v: "node/-1" }]
|
||||
)
|
||||
}
|
||||
|
||||
public hasDefaultIcon() {
|
||||
if (this.mapRendering === undefined || this.mapRendering === null) {
|
||||
return false
|
||||
}
|
||||
return this.mapRendering.some((r) => r.location.has("point"))
|
||||
}
|
||||
public GenerateDocumentation(
|
||||
usedInThemes: string[],
|
||||
layerIsNeededBy?: Map<string, string[]>,
|
||||
|
|
|
|||
|
|
@ -143,15 +143,6 @@ export default class PointRenderingConfig extends WithContextLoader {
|
|||
"w-full h-full block absolute top-0 left-0"
|
||||
)
|
||||
}
|
||||
|
||||
public GetBaseIcon(tags?: Record<string, string>): BaseUIElement {
|
||||
return new SvelteUIElement(DynamicMarker, {
|
||||
marker: this.marker,
|
||||
rotation: this.rotation,
|
||||
tags: new ImmutableStore(tags),
|
||||
})
|
||||
}
|
||||
|
||||
public RenderIcon(
|
||||
tags: Store<Record<string, string>>,
|
||||
options?: {
|
||||
|
|
|
|||
|
|
@ -999,7 +999,7 @@ export class TagRenderingConfigUtils {
|
|||
tags: UIEventSource<Record<string, string>>,
|
||||
feature?: Feature
|
||||
): Store<TagRenderingConfig> {
|
||||
const isNSI = NameSuggestionIndex.supportedTypes().indexOf(config.freeform?.key) >= 0
|
||||
const isNSI = NameSuggestionIndex.supportedTypes.indexOf(<any>config.freeform?.key) >= 0
|
||||
if (!isNSI) {
|
||||
return new ImmutableStore(config)
|
||||
}
|
||||
|
|
@ -1019,8 +1019,8 @@ export class TagRenderingConfigUtils {
|
|||
)
|
||||
)
|
||||
})
|
||||
return extraMappings.map((extraMappings) => {
|
||||
if (!extraMappings || extraMappings.length == 0) {
|
||||
return extraMappings.mapD((extraMappings) => {
|
||||
if (extraMappings.length == 0) {
|
||||
return config
|
||||
}
|
||||
const clone: TagRenderingConfig = Object.create(config)
|
||||
|
|
|
|||
|
|
@ -90,7 +90,9 @@
|
|||
}
|
||||
|
||||
let officialSearched: Store<MinimalThemeInformation[]> = filtered(
|
||||
new ImmutableStore(officialThemes)
|
||||
osmConnection.isLoggedIn.map((loggedIn) =>
|
||||
loggedIn ? officialThemes : officialThemes.filter((th) => th.id !== "personal")
|
||||
)
|
||||
)
|
||||
let hiddenSearched: Store<MinimalThemeInformation[]> = filtered(visitedHiddenThemes)
|
||||
let customSearched: Store<MinimalThemeInformation[]> = filtered(customThemes)
|
||||
|
|
@ -103,6 +105,9 @@
|
|||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Opens the first search candidate
|
||||
*/
|
||||
function applySearch() {
|
||||
const didRedirect = SearchUtils.applySpecialSearch(search.data)
|
||||
if (didRedirect) {
|
||||
|
|
|
|||
|
|
@ -5,16 +5,18 @@
|
|||
/**
|
||||
* For some stupid reason, it is very hard to let {#if} work together with UIEventSources, so we wrap then here
|
||||
*/
|
||||
export let condition: Store<boolean>
|
||||
let _c = condition.data
|
||||
onDestroy(
|
||||
condition.addCallback((c) => {
|
||||
/* Do _not_ abbreviate this as `.addCallback(c => _c = c)`. This is the same as writing `.addCallback(c => {return _c = c})`,
|
||||
which will _unregister_ the callback if `c = true`! */
|
||||
_c = c
|
||||
return false
|
||||
})
|
||||
)
|
||||
export let condition: Store<boolean> | undefined
|
||||
let _c = condition?.data
|
||||
if (condition !== undefined) {
|
||||
onDestroy(
|
||||
condition.addCallback((c) => {
|
||||
/* Do _not_ abbreviate this as `.addCallback(c => _c = c)`. This is the same as writing `.addCallback(c => {return _c = c})`,
|
||||
which will _unregister_ the callback if `c = true`! */
|
||||
_c = c
|
||||
return false
|
||||
})
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if _c}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
import Translations from "../i18n/Translations"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import Constants from "../../Models/Constants"
|
||||
import DefaultIcon from "../Map/DefaultIcon.svelte"
|
||||
|
||||
export let state: SpecialVisualizationState
|
||||
export let filteredLayer: FilteredLayer
|
||||
|
|
@ -58,7 +59,7 @@
|
|||
{#if showLayerTitle}
|
||||
<Checkbox selected={isDisplayed}>
|
||||
<div class="no-image-background block h-6 w-6" class:opacity-50={!$isDisplayed}>
|
||||
<ToSvelte construct={() => layer.defaultIcon()} />
|
||||
<DefaultIcon {layer} />
|
||||
</div>
|
||||
|
||||
<Tr t={filteredLayer.layerDef.name} />
|
||||
|
|
|
|||
|
|
@ -93,7 +93,10 @@
|
|||
{/if}
|
||||
{#if currentStep === "init"}
|
||||
{#each $missing as key (key)}
|
||||
<div class:focus={applyAllHovered} class="mx-2 rounded-2xl">
|
||||
<div
|
||||
class:focus={applyAllHovered}
|
||||
class="mx-2 rounded-none border-2 border-gray-300 border-transparent"
|
||||
>
|
||||
<ComparisonAction
|
||||
{key}
|
||||
{state}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import DefaultIcon from "../Map/DefaultIcon.svelte"
|
||||
|
||||
export let onlyShowChangesBy: string[]
|
||||
export let id: OsmId
|
||||
|
|
@ -30,10 +31,11 @@
|
|||
return true
|
||||
}
|
||||
console.log(
|
||||
"Checking if ",
|
||||
"Checking if",
|
||||
step.tags["_last_edit:contributor"],
|
||||
"is contained in",
|
||||
onlyShowChangesBy
|
||||
onlyShowChangesBy,
|
||||
usernames.has(step.tags["_last_edit:contributor"])
|
||||
)
|
||||
return usernames.has(step.tags["_last_edit:contributor"])
|
||||
})
|
||||
|
|
@ -49,7 +51,7 @@
|
|||
* These layers are only shown if there are tag changes as well
|
||||
*/
|
||||
const ignoreLayersIfNoChanges: ReadonlySet<string> = new Set(["walls_and_buildings"])
|
||||
const t = Translations.t.inspector.previousContributors
|
||||
const t = Translations.t.inspector
|
||||
</script>
|
||||
|
||||
{#if !$allGeometry || !ignoreLayersIfNoChanges.has($lastStep?.layer?.id)}
|
||||
|
|
@ -57,7 +59,7 @@
|
|||
<a href={"https://openstreetmap.org/" + $lastStep.step.tags.id} target="_blank">
|
||||
<h3 class="flex items-center gap-x-2">
|
||||
<div class="inline-block h-8 w-8 shrink-0">
|
||||
<ToSvelte construct={$lastStep.layer?.defaultIcon($lastStep.step.tags)} />
|
||||
<DefaultIcon layer={$lastStep.layer} />
|
||||
</div>
|
||||
<Tr
|
||||
t={$lastStep.layer?.title?.GetRenderValue($lastStep.step.tags)?.Subs($lastStep.step.tags)}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
import { createEventDispatcher } from "svelte"
|
||||
import { XCircleIcon } from "@babeard/svelte-heroicons/solid"
|
||||
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||
import Dropdown from "../Base/Dropdown.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
|
||||
export let osmConnection: OsmConnection
|
||||
export let inspectedContributors: UIEventSource<
|
||||
|
|
@ -41,23 +42,31 @@
|
|||
inspectedContributors.data.sort((a, b) => (a[key] ?? "").localeCompare(b[key] ?? ""))
|
||||
inspectedContributors.ping()
|
||||
}
|
||||
|
||||
const t = Translations.t.inspector.previouslySpied
|
||||
</script>
|
||||
|
||||
<LoginToggle ignoreLoading state={{ osmConnection }}>
|
||||
<table class="w-full">
|
||||
<tr>
|
||||
<td>
|
||||
<button class="as-link cursor-pointer" on:click={() => sort("name")}>Contributor</button>
|
||||
</td>
|
||||
<td>
|
||||
<button class="as-link cursor-pointer" on:click={() => sort("visitedTime")}>
|
||||
Visited time
|
||||
<button class="as-link cursor-pointer" on:click={() => sort("name")}>
|
||||
<Tr t={t.username} />
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button class="as-link cursor-pointer" on:click={() => sort("label")}>Label</button>
|
||||
<button class="as-link cursor-pointer" on:click={() => sort("visitedTime")}>
|
||||
<Tr t={t.time} />
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button class="as-link cursor-pointer" on:click={() => sort("label")}>
|
||||
<Tr t={t.label} />
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<Tr t={t.remove} />
|
||||
</td>
|
||||
<td>Remove</td>
|
||||
</tr>
|
||||
{#each $inspectedContributors as c}
|
||||
<tr>
|
||||
|
|
@ -85,7 +94,7 @@
|
|||
<AccordionSingle>
|
||||
<div slot="header">Labels</div>
|
||||
{#if $labels.length === 0}
|
||||
No labels
|
||||
<Tr t={t.noLabels} />
|
||||
{:else}
|
||||
{#each $labels as label}
|
||||
<div class="mx-2">
|
||||
|
|
@ -102,7 +111,8 @@
|
|||
)
|
||||
}}
|
||||
>
|
||||
See all changes for these users
|
||||
<Tr t={t.allChanges} />
|
||||
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
|
|
@ -115,7 +125,7 @@
|
|||
class:disabled={!(labelField?.length > 0)}
|
||||
class="disabled shrink-0"
|
||||
>
|
||||
Add label
|
||||
<Tr t={t.addLabel} />
|
||||
</button>
|
||||
</div>
|
||||
</AccordionSingle>
|
||||
|
|
|
|||
|
|
@ -37,13 +37,16 @@
|
|||
if (!shown) {
|
||||
previewedImage?.set(undefined)
|
||||
}
|
||||
}),
|
||||
})
|
||||
)
|
||||
if (previewedImage) {
|
||||
onDestroy(
|
||||
previewedImage.addCallbackAndRun((previewedImage) => {
|
||||
showBigPreview.set(previewedImage !== undefined && (previewedImage?.id ?? previewedImage?.url) === (image.id ?? image.url))
|
||||
}),
|
||||
showBigPreview.set(
|
||||
previewedImage !== undefined &&
|
||||
(previewedImage?.id ?? previewedImage?.url) === (image.id ?? image.url)
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +93,6 @@
|
|||
</div>
|
||||
</Popup>
|
||||
|
||||
|
||||
{#if image.status !== undefined && image.status !== "ready" && image.status !== "hidden"}
|
||||
<div class="flex h-full flex-col justify-center">
|
||||
<Loading>
|
||||
|
|
@ -115,7 +117,7 @@
|
|||
class={imgClass ?? ""}
|
||||
class:cursor-zoom-in={canZoom}
|
||||
on:click={() => {
|
||||
console.log("Setting",image.url)
|
||||
console.log("Setting", image.url)
|
||||
previewedImage?.set(image)
|
||||
}}
|
||||
on:error={() => {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
import Translations from "../i18n/Translations"
|
||||
import DotMenu from "../Base/DotMenu.svelte"
|
||||
|
||||
export let image: Partial<ProvidedImage> & ({ id: string, url: string })
|
||||
export let image: Partial<ProvidedImage> & { id: string; url: string }
|
||||
export let clss: string = undefined
|
||||
|
||||
let isLoaded = new UIEventSource(false)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
import Translations from "./i18n/Translations"
|
||||
import Tr from "./Base/Tr.svelte"
|
||||
|
||||
console.log("Loading inspector GUI")
|
||||
let username = QueryParameters.GetQueryParameter("user", undefined, "Inspect this user")
|
||||
let step = new UIEventSource<"waiting" | "loading" | "done">("waiting")
|
||||
let map = new UIEventSource<MlMap>(undefined)
|
||||
|
|
@ -121,7 +122,7 @@
|
|||
const t = Translations.t.inspector
|
||||
</script>
|
||||
|
||||
<div class="flex h-full w-full flex-col">
|
||||
<div class="flex h-screen w-full flex-col">
|
||||
<div class="low-interaction flex items-center gap-x-2 p-2">
|
||||
<MagnifyingGlassCircle class="h-12 w-12" />
|
||||
<h1 class="m-0 mx-2 flex-shrink-0">
|
||||
|
|
@ -192,7 +193,7 @@
|
|||
<XCircleIcon class="h-6 w-6" on:click={() => selectedElement.set(undefined)} />
|
||||
</div>
|
||||
|
||||
<History onlyShowChangesBy={$username} id={$selectedElement.properties.id} />
|
||||
<History onlyShowChangesBy={$username.split(";")} id={$selectedElement.properties.id} />
|
||||
</TitledPanel>
|
||||
</Drawer>
|
||||
{/if}
|
||||
|
|
@ -218,7 +219,9 @@
|
|||
</div>
|
||||
|
||||
<Page shown={showPreviouslyVisited}>
|
||||
<div slot="header">Earlier inspected constributors</div>
|
||||
<div slot="header">
|
||||
<Tr t={t.previouslySpied.title} />
|
||||
</div>
|
||||
<PreviouslySpiedUsers
|
||||
{osmConnection}
|
||||
{inspectedContributors}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import InspectorGUI from "./InspectorGUI.svelte"
|
||||
|
||||
const target = document.getElementById("main")
|
||||
target.innerHTML = ""
|
||||
new InspectorGUI({
|
||||
target: document.getElementById("main"),
|
||||
target,
|
||||
})
|
||||
|
|
|
|||
25
src/UI/Map/DefaultIcon.svelte
Normal file
25
src/UI/Map/DefaultIcon.svelte
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<script lang="ts">
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import DynamicIcon from "./DynamicIcon.svelte"
|
||||
import DynamicMarker from "./DynamicMarker.svelte"
|
||||
import Marker from "./Marker.svelte"
|
||||
import { ImmutableStore } from "../../Logic/UIEventSource"
|
||||
|
||||
/**
|
||||
* The 'DefaultIcon' is the icon that a layer shows by default
|
||||
* Used e.g. in the filterview
|
||||
*/
|
||||
export let layer: LayerConfig
|
||||
export let properties: Readonly<Record<string, string>> = layer.baseTags
|
||||
export let clss = ""
|
||||
let tags = new ImmutableStore(properties)
|
||||
let mapRenderings = layer.mapRendering?.filter((r) => r.location.has("point"))
|
||||
</script>
|
||||
|
||||
{#if mapRenderings?.length > 0}
|
||||
<div class={"relative block h-full w-full " + clss}>
|
||||
{#each mapRenderings as mr}
|
||||
<DynamicMarker marker={mr.marker} rotation={mr.rotation} {tags} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
{#if marker && marker}
|
||||
{#if marker}
|
||||
<div class="relative h-full w-full" style={`transform: rotate(${$_rotation})`}>
|
||||
{#each marker as icon}
|
||||
<div class="absolute top-0 left-0 h-full w-full">
|
||||
|
|
|
|||
|
|
@ -2,21 +2,26 @@
|
|||
import Tr from "./Base/Tr.svelte"
|
||||
import Translations from "./i18n/Translations.ts"
|
||||
import BackButton from "./Base/BackButton.svelte"
|
||||
import Not_found from "../assets/svg/Not_found.svelte"
|
||||
import World from "../assets/svg/World.svelte"
|
||||
|
||||
console.log("???")
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div class="flex flex-col">
|
||||
<Tr t={Translations.t.general["404"]} />
|
||||
<BackButton
|
||||
clss="m-8"
|
||||
on:click={() => {
|
||||
window.location = "index.html"
|
||||
}}
|
||||
>
|
||||
<div class="flex w-full justify-center">
|
||||
<Tr t={Translations.t.general.backToIndex} />
|
||||
</div>
|
||||
</BackButton>
|
||||
<div class="flex h-full w-full flex-col items-center justify-center p-8">
|
||||
<div class="flex flex-col items-center">
|
||||
<World style="height: 200px" />
|
||||
<h1>
|
||||
<Tr t={Translations.t.general["404"]} />
|
||||
</h1>
|
||||
</div>
|
||||
</main>
|
||||
<BackButton
|
||||
on:click={() => {
|
||||
window.location = "index.html"
|
||||
}}
|
||||
>
|
||||
<div class="flex w-full justify-center">
|
||||
<Tr t={Translations.t.general.backToIndex} />
|
||||
</div>
|
||||
</BackButton>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -43,14 +43,13 @@ export class DeleteFlowState {
|
|||
console.log("Checking deleteability (internet?", useTheInternet, ")")
|
||||
const t = Translations.t.delete
|
||||
const id = this._id
|
||||
const self = this
|
||||
if (!id.startsWith("node")) {
|
||||
this.canBeDeleted.setData(false)
|
||||
this.canBeDeletedReason.setData(t.isntAPoint)
|
||||
return
|
||||
}
|
||||
|
||||
// Does the currently logged in user have enough experience to delete this point?
|
||||
// Does the currently logged-in user have enough experience to delete this point?
|
||||
const deletingPointsOfOtherAllowed = this._osmConnection.userDetails.map((ud) => {
|
||||
if (ud === undefined) {
|
||||
return undefined
|
||||
|
|
@ -74,10 +73,10 @@ export class DeleteFlowState {
|
|||
// Not yet downloaded
|
||||
return null
|
||||
}
|
||||
const userId = self._osmConnection.userDetails.data.uid
|
||||
const userId = this._osmConnection.userDetails.data.uid
|
||||
return !previous.some((editor) => editor !== userId)
|
||||
},
|
||||
[self._osmConnection.userDetails]
|
||||
[this._osmConnection.userDetails]
|
||||
)
|
||||
|
||||
// User allowed OR only edited by self?
|
||||
|
|
@ -96,14 +95,13 @@ export class DeleteFlowState {
|
|||
|
||||
if (allByMyself.data === null && useTheInternet) {
|
||||
// We kickoff the download here as it hasn't yet been downloaded. Note that this is mapped onto 'all by myself' above
|
||||
const hist = this.objectDownloader
|
||||
.downloadHistory(id)
|
||||
.map((versions) =>
|
||||
UIEventSource.FromPromise(this.objectDownloader.downloadHistory(id))
|
||||
.mapD((versions) =>
|
||||
versions.map((version) =>
|
||||
Number(version.tags["_last_edit:contributor:uid"])
|
||||
)
|
||||
)
|
||||
hist.addCallbackAndRunD((hist) => previousEditors.setData(hist))
|
||||
.addCallbackAndRunD((hist) => previousEditors.setData(hist))
|
||||
}
|
||||
|
||||
if (allByMyself.data === true) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import { Translation } from "../i18n/Translation"
|
||||
import { XMarkIcon } from "@babeard/svelte-heroicons/mini"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import DefaultIcon from "../Map/DefaultIcon.svelte"
|
||||
|
||||
export let layer: LayerConfig
|
||||
export let state: ThemeViewState
|
||||
|
|
@ -28,7 +29,7 @@
|
|||
<div class="low-interaction p-2">
|
||||
<h4 class="my-2 flex">
|
||||
<div class="no-image-background block h-6 w-6">
|
||||
<ToSvelte construct={() => layer.defaultIcon()} />
|
||||
<DefaultIcon {layer} />
|
||||
</div>
|
||||
<Tr t={layer.name} />
|
||||
</h4>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
import Translations from "../../i18n/Translations.js"
|
||||
import { Utils } from "../../../Utils"
|
||||
import { onDestroy } from "svelte"
|
||||
import TagRenderingQuestion from "./TagRenderingQuestion.svelte"
|
||||
import TagRenderingQuestionDynamic from "./TagRenderingQuestionDynamic.svelte"
|
||||
|
||||
export let layer: LayerConfig
|
||||
|
|
@ -54,6 +53,7 @@
|
|||
let skippedQuestions = new UIEventSource<Set<string>>(new Set<string>())
|
||||
let layerDisabledForTheme = state.userRelatedState.getThemeDisabled(state.theme.id, layer.id)
|
||||
layerDisabledForTheme.addCallbackAndRunD((disabled) => {
|
||||
console.log("Disabled questions are ", disabled)
|
||||
skippedQuestions.set(new Set(disabled.concat(Array.from(skippedQuestions.data))))
|
||||
})
|
||||
let questionboxElem: HTMLDivElement
|
||||
|
|
|
|||
|
|
@ -49,9 +49,13 @@
|
|||
function createVisualisation(specpart: Exclude<RenderingSpecification, string>): BaseUIElement {
|
||||
{
|
||||
try {
|
||||
return specpart.func
|
||||
const uiEl = specpart.func
|
||||
.constr(state, tags, specpart.args, feature, layer)
|
||||
?.SetClass(specpart.style)
|
||||
if (uiEl === undefined) {
|
||||
console.error("Invalid special translation")
|
||||
}
|
||||
return uiEl
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"Could not construct a special visualisation with specification",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import TagRenderingAnswer from "./TagRenderingAnswer.svelte"
|
||||
import Loading from "../../Base/Loading.svelte"
|
||||
|
||||
export let tags: UIEventSource<Record<string, string> | undefined>
|
||||
|
||||
|
|
@ -20,12 +21,16 @@
|
|||
let dynamicConfig = TagRenderingConfigUtils.withNameSuggestionIndex(config, tags, selectedElement)
|
||||
</script>
|
||||
|
||||
<TagRenderingAnswer
|
||||
{selectedElement}
|
||||
{layer}
|
||||
config={$dynamicConfig}
|
||||
{extraClasses}
|
||||
{id}
|
||||
{tags}
|
||||
{state}
|
||||
/>
|
||||
{#if $dynamicConfig === undefined}
|
||||
<Loading />
|
||||
{:else}
|
||||
<TagRenderingAnswer
|
||||
{selectedElement}
|
||||
{layer}
|
||||
config={$dynamicConfig}
|
||||
{extraClasses}
|
||||
{id}
|
||||
{tags}
|
||||
{state}
|
||||
/>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import TagRenderingEditable from "./TagRenderingEditable.svelte"
|
||||
import Loading from "../../Base/Loading.svelte"
|
||||
|
||||
export let config: TagRenderingConfig
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
|
|
@ -23,14 +24,18 @@
|
|||
let dynamicConfig = TagRenderingConfigUtils.withNameSuggestionIndex(config, tags, selectedElement)
|
||||
</script>
|
||||
|
||||
<TagRenderingEditable
|
||||
config={$dynamicConfig}
|
||||
{editMode}
|
||||
{clss}
|
||||
{highlightedRendering}
|
||||
{editingEnabled}
|
||||
{layer}
|
||||
{state}
|
||||
{selectedElement}
|
||||
{tags}
|
||||
/>
|
||||
{#if $dynamicConfig}
|
||||
<TagRenderingEditable
|
||||
config={$dynamicConfig}
|
||||
{editMode}
|
||||
{clss}
|
||||
{highlightedRendering}
|
||||
{editingEnabled}
|
||||
{layer}
|
||||
{state}
|
||||
{selectedElement}
|
||||
{tags}
|
||||
/>
|
||||
{:else}
|
||||
<Loading />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
import SvelteUIElement from "../../Base/SvelteUIElement"
|
||||
import Icon from "../../Map/Icon.svelte"
|
||||
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
|
||||
import DefaultIcon from "../../Map/DefaultIcon.svelte"
|
||||
|
||||
export let selectedElement: Feature
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
|
|
@ -27,6 +28,7 @@
|
|||
*/
|
||||
export let clss: string = "ml-2"
|
||||
export let mapping: {
|
||||
readonly if?: TagsFilter
|
||||
readonly then: Translation
|
||||
readonly searchTerms?: Record<string, string[]>
|
||||
readonly icon?: string
|
||||
|
|
@ -46,13 +48,13 @@
|
|||
large: "5rem",
|
||||
}
|
||||
|
||||
function getAutoIcon(mapping: { if?: TagsFilter }): BaseUIElement {
|
||||
function getAutoIcon(mapping: { readonly if?: TagsFilter }): Readonly<Record<string, string>> {
|
||||
for (const preset of layer.presets) {
|
||||
if (!new And(preset.tags).shadows(mapping.if)) {
|
||||
continue
|
||||
}
|
||||
|
||||
return layer.defaultIcon(TagUtils.asProperties(preset.tags))
|
||||
return TagUtils.asProperties(preset.tags)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
|
@ -62,7 +64,7 @@
|
|||
<div class="inline-flex items-center">
|
||||
{#if mapping.icon === "auto"}
|
||||
<div class="mr-2 h-8 w-8 shrink-0">
|
||||
<ToSvelte construct={() => getAutoIcon(mapping)} />
|
||||
<DefaultIcon {layer} properties={getAutoIcon(mapping)} />
|
||||
</div>
|
||||
{:else}
|
||||
<Marker
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@
|
|||
import Markdown from "../../Base/Markdown.svelte"
|
||||
import { Utils } from "../../../Utils"
|
||||
import type { UploadableTag } from "../../../Logic/Tags/TagTypes"
|
||||
import { Modal } from "flowbite-svelte"
|
||||
import Popup from "../../Base/Popup.svelte"
|
||||
import If from "../../Base/If.svelte"
|
||||
import DotMenu from "../../Base/DotMenu.svelte"
|
||||
|
|
@ -341,7 +340,9 @@
|
|||
.catch(console.error)
|
||||
}
|
||||
|
||||
let disabledInTheme = state.userRelatedState.getThemeDisabled(state.theme.id, layer?.id)
|
||||
let disabledInTheme =
|
||||
state.userRelatedState?.getThemeDisabled(state.theme.id, layer?.id) ??
|
||||
new UIEventSource<string[]>([])
|
||||
let menuIsOpened = new UIEventSource(false)
|
||||
|
||||
function disableQuestion() {
|
||||
|
|
@ -361,7 +362,7 @@
|
|||
|
||||
{#if question !== undefined && $apiState !== "readonly" && $apiState !== "offline"}
|
||||
<div class={clss}>
|
||||
{#if layer.isNormal()}
|
||||
{#if layer?.isNormal()}
|
||||
<LoginToggle {state}>
|
||||
<DotMenu hideBackground={true} open={menuIsOpened}>
|
||||
<SidebarUnit>
|
||||
|
|
@ -559,7 +560,7 @@
|
|||
</h2>
|
||||
<Tr t={Translations.t.unknown.explanation} />
|
||||
<If
|
||||
condition={state.userRelatedState.showTags.map(
|
||||
condition={state.userRelatedState?.showTags?.map(
|
||||
(v) => v === "yes" || v === "full" || v === "always"
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
import { twJoin } from "tailwind-merge"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import { TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import Loading from "../../Base/Loading.svelte"
|
||||
|
||||
export let config: TagRenderingConfig
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
|
|
@ -31,14 +32,18 @@
|
|||
let dynamicConfig = TagRenderingConfigUtils.withNameSuggestionIndex(config, tags, selectedElement)
|
||||
</script>
|
||||
|
||||
<TagRenderingQuestion
|
||||
{tags}
|
||||
config={$dynamicConfig}
|
||||
{state}
|
||||
{selectedElement}
|
||||
{layer}
|
||||
{selectedTags}
|
||||
{extraTags}
|
||||
>
|
||||
<slot name="cancel" slot="cancel" />
|
||||
</TagRenderingQuestion>
|
||||
{#if $dynamicConfig}
|
||||
<TagRenderingQuestion
|
||||
{tags}
|
||||
config={$dynamicConfig}
|
||||
{state}
|
||||
{selectedElement}
|
||||
{layer}
|
||||
{selectedTags}
|
||||
{extraTags}
|
||||
>
|
||||
<slot name="cancel" slot="cancel" />
|
||||
</TagRenderingQuestion>
|
||||
{:else}
|
||||
<Loading />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
import FilterSearch from "../../Logic/Search/FilterSearch"
|
||||
|
||||
import Locale from "../i18n/Locale"
|
||||
import DefaultIcon from "../Map/DefaultIcon.svelte"
|
||||
|
||||
export let activeFilters: (FilterSearchResult & ActiveFilter)[]
|
||||
let language = Locale.language
|
||||
|
|
@ -72,7 +73,7 @@
|
|||
{#if $activeLayers.length === 1}
|
||||
<FilterToggle on:click={() => enableAllLayers()}>
|
||||
<div class="h-8 w-8 p-1">
|
||||
<ToSvelte construct={$activeLayers[0].layerDef.defaultIcon()} />
|
||||
<DefaultIcon layer={$activeLayers[0].layerDef} />
|
||||
</div>
|
||||
<b>
|
||||
<Tr t={$activeLayers[0].layerDef.name} />
|
||||
|
|
@ -82,7 +83,7 @@
|
|||
{#each $nonactiveLayers as nonActive (nonActive.layerDef.id)}
|
||||
<FilterToggle on:click={() => nonActive.isDisplayed.set(true)}>
|
||||
<div class="h-8 w-8 p-1">
|
||||
<ToSvelte construct={nonActive.layerDef.defaultIcon()} />
|
||||
<DefaultIcon layer={nonActive.layerDef} />
|
||||
</div>
|
||||
<del class="block-ruby">
|
||||
<Tr t={nonActive.layerDef.name} />
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import type { FilterSearchResult } from "../../Logic/Search/FilterSearch"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import DefaultIcon from "../Map/DefaultIcon.svelte"
|
||||
|
||||
export let entry: FilterSearchResult[] | LayerConfig
|
||||
let asFilter: FilterSearchResult[]
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
<div class="flex items-center gap-x-1">
|
||||
{#if asLayer}
|
||||
<div class="h-8 w-8 p-1">
|
||||
<ToSvelte construct={asLayer.defaultIcon()} />
|
||||
<DefaultIcon layer={asLayer} />
|
||||
</div>
|
||||
<b>
|
||||
<Tr t={asLayer.name} />
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
import Icon from "../Map/Icon.svelte"
|
||||
import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte"
|
||||
import ArrowUp from "@babeard/svelte-heroicons/mini/ArrowUp"
|
||||
import DefaultIcon from "../Map/DefaultIcon.svelte"
|
||||
|
||||
export let entry: GeocodeResult
|
||||
export let state: SpecialVisualizationState
|
||||
|
|
@ -62,9 +63,7 @@
|
|||
<div class="flex w-full items-center gap-y-2 p-2">
|
||||
{#if layer}
|
||||
<div class="h-6">
|
||||
<ToSvelte
|
||||
construct={() => layer.defaultIcon(entry.feature.properties)?.SetClass("w-6 h-6")}
|
||||
/>
|
||||
<DefaultIcon {layer} properties={entry.feature.properties} clss="w-6 h-6" />
|
||||
</div>
|
||||
{:else if entry.category}
|
||||
<Icon
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ export class QuestionViz implements SpecialVisualization {
|
|||
state,
|
||||
onlyForLabels: labels,
|
||||
notForLabels: blacklist,
|
||||
}).SetClass("w-full")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -435,7 +435,7 @@ export default class SpecialVisualizations {
|
|||
return new SvelteUIElement(AddNewPoint, {
|
||||
state,
|
||||
coordinate: { lon, lat },
|
||||
}).SetClass("w-full h-full overflow-auto")
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -564,7 +564,7 @@ export default class SpecialVisualizations {
|
|||
state,
|
||||
feature,
|
||||
layer,
|
||||
}).SetClass("p-0 m-0")
|
||||
})
|
||||
},
|
||||
},
|
||||
new ShareLinkViz(),
|
||||
|
|
@ -1065,7 +1065,7 @@ export default class SpecialVisualizations {
|
|||
|
||||
constr: (state) => {
|
||||
return new SubtleButton(
|
||||
new SvelteUIElement(Trash).SetClass("h-6"),
|
||||
new SvelteUIElement(Trash),
|
||||
Translations.t.general.removeLocationHistory
|
||||
).onClick(() => {
|
||||
state.historicalUserLocations.features.setData([])
|
||||
|
|
@ -1161,8 +1161,6 @@ export default class SpecialVisualizations {
|
|||
feature,
|
||||
layer,
|
||||
})
|
||||
.SetClass("px-1")
|
||||
.setSpan()
|
||||
})
|
||||
),
|
||||
},
|
||||
|
|
@ -1476,6 +1474,7 @@ export default class SpecialVisualizations {
|
|||
state,
|
||||
feature,
|
||||
layer,
|
||||
// clss: classes ?? "",
|
||||
}).SetClass(classes)
|
||||
elements.push(subsTr)
|
||||
}
|
||||
|
|
@ -1689,7 +1688,7 @@ export default class SpecialVisualizations {
|
|||
state,
|
||||
layer,
|
||||
feature,
|
||||
}).SetClass("w-full h-full")
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
import ChevronRight from "@babeard/svelte-heroicons/mini/ChevronRight"
|
||||
import { Drawer } from "flowbite-svelte"
|
||||
import { linear } from "svelte/easing"
|
||||
import DefaultIcon from "./Map/DefaultIcon.svelte"
|
||||
|
||||
export let state: ThemeViewState
|
||||
|
||||
|
|
@ -395,7 +396,7 @@
|
|||
|
||||
<div class="float-left m-1 flex flex-col sm:mt-2">
|
||||
<!-- Current view tools -->
|
||||
{#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()}
|
||||
{#if currentViewLayer?.tagRenderings && currentViewLayer.hasDefaultIcon()}
|
||||
<MapControlButton
|
||||
on:click={() => {
|
||||
state.selectCurrentView()
|
||||
|
|
@ -403,7 +404,7 @@
|
|||
on:keydown={forwardEventToMap}
|
||||
>
|
||||
<div class="h-8 w-8 cursor-pointer">
|
||||
<ToSvelte construct={() => currentViewLayer.defaultIcon()} />
|
||||
<DefaultIcon layer={currentViewLayer} />
|
||||
</div>
|
||||
</MapControlButton>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"contributors": [
|
||||
{
|
||||
"commits": 8779,
|
||||
"commits": 8826,
|
||||
"contributor": "Pieter Vander Vennet"
|
||||
},
|
||||
{
|
||||
"commits": 505,
|
||||
"commits": 506,
|
||||
"contributor": "Robin van der Linde"
|
||||
},
|
||||
{
|
||||
|
|
@ -56,6 +56,10 @@
|
|||
"commits": 24,
|
||||
"contributor": "Ward"
|
||||
},
|
||||
{
|
||||
"commits": 23,
|
||||
"contributor": "Midgard"
|
||||
},
|
||||
{
|
||||
"commits": 21,
|
||||
"contributor": "wjtje"
|
||||
|
|
@ -80,10 +84,6 @@
|
|||
"commits": 18,
|
||||
"contributor": "Arno Deceuninck"
|
||||
},
|
||||
{
|
||||
"commits": 17,
|
||||
"contributor": "Midgard"
|
||||
},
|
||||
{
|
||||
"commits": 17,
|
||||
"contributor": "pgm-chardelv1"
|
||||
|
|
|
|||
|
|
@ -1,131 +0,0 @@
|
|||
[
|
||||
{
|
||||
"name": "CyclOSM",
|
||||
"id": "cyclosm",
|
||||
"url": "https://{switch:a,b,c}.tile-cyclosm.openstreetmap.fr/cyclosm/{zoom}/{x}/{y}.png",
|
||||
"attribution": {
|
||||
"text": "Rendering: CyclOSM (hosted by OpenStreetMap France) © Map data OpenStreetMap contributors",
|
||||
"url": "https://www.cyclosm.org/"
|
||||
},
|
||||
"type": "tms",
|
||||
"category": "osmbasedmap",
|
||||
"max_zoom": 20
|
||||
},
|
||||
{
|
||||
"name": "Esri World Imagery",
|
||||
"id": "EsriWorldImagery",
|
||||
"url": "https://{switch:services,server}.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{zoom}/{y}/{x}",
|
||||
"attribution": {
|
||||
"required": true,
|
||||
"text": "Terms & Feedback",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/Esri"
|
||||
},
|
||||
"type": "tms",
|
||||
"category": "photo",
|
||||
"max_zoom": 22,
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "Esri World Imagery (Clarity) Beta",
|
||||
"id": "EsriWorldImageryClarity",
|
||||
"url": "https://clarity.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/tile/{zoom}/{y}/{x}",
|
||||
"attribution": {
|
||||
"required": true,
|
||||
"text": "Terms & Feedback",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/Esri"
|
||||
},
|
||||
"type": "tms",
|
||||
"category": "photo",
|
||||
"max_zoom": 22,
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "Mapbox Satellite",
|
||||
"id": "Mapbox",
|
||||
"url": "https://{switch:a,b,c,d}.tiles.mapbox.com/v4/mapbox.satellite/{zoom}/{x}/{y}.jpg?access_token=pk.eyJ1Ijoib3BlbnN0cmVldG1hcCIsImEiOiJjbGZkempiNDkyandvM3lwY3M4MndpdWdzIn0.QnvRv52n3qffVEKmQa9vJA",
|
||||
"attribution": {
|
||||
"required": true,
|
||||
"text": "Terms & Feedback",
|
||||
"url": "https://www.mapbox.com/about/maps"
|
||||
},
|
||||
"type": "tms",
|
||||
"category": "photo",
|
||||
"max_zoom": 22,
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "OpenAerialMap Mosaic, by Kontur.io",
|
||||
"id": "OpenAerialMapMosaic",
|
||||
"url": "https://apps.kontur.io/raster-tiler/oam/mosaic/{zoom}/{x}/{y}.png",
|
||||
"type": "tms",
|
||||
"category": "photo",
|
||||
"min_zoom": 1,
|
||||
"max_zoom": 31,
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "OpenStreetMap (Basque Style)",
|
||||
"id": "osmfr-basque",
|
||||
"url": "https://tile.openstreetmap.bzh/eu/{zoom}/{x}/{y}.png",
|
||||
"attribution": {
|
||||
"required": true,
|
||||
"text": "Tiles © OpenStreetMap France, data © OpenStreetMap contributors, ODbL",
|
||||
"url": "https://www.openstreetmap.org/"
|
||||
},
|
||||
"type": "tms",
|
||||
"category": "osmbasedmap",
|
||||
"max_zoom": 20
|
||||
},
|
||||
{
|
||||
"name": "OpenStreetMap (Breton Style)",
|
||||
"id": "osmfr-breton",
|
||||
"url": "https://tile.openstreetmap.bzh/br/{zoom}/{x}/{y}.png",
|
||||
"attribution": {
|
||||
"required": true,
|
||||
"text": "Tiles © OpenStreetMap France, data © OpenStreetMap contributors, ODbL",
|
||||
"url": "https://www.openstreetmap.org/"
|
||||
},
|
||||
"type": "tms",
|
||||
"category": "osmbasedmap",
|
||||
"max_zoom": 20
|
||||
},
|
||||
{
|
||||
"name": "OpenStreetMap (French Style)",
|
||||
"id": "osmfr",
|
||||
"url": "https://{switch:a,b,c}.tile.openstreetmap.fr/osmfr/{zoom}/{x}/{y}.png",
|
||||
"attribution": {
|
||||
"required": true,
|
||||
"text": "Tiles © cquest@Openstreetmap France, data © OpenStreetMap contributors, ODBL",
|
||||
"url": "https://www.openstreetmap.org/"
|
||||
},
|
||||
"type": "tms",
|
||||
"category": "osmbasedmap",
|
||||
"max_zoom": 20
|
||||
},
|
||||
{
|
||||
"name": "OpenStreetMap (HOT Style)",
|
||||
"id": "HDM_HOT",
|
||||
"url": "https://{switch:a,b,c}.tile.openstreetmap.fr/hot/{zoom}/{x}/{y}.png",
|
||||
"attribution": {
|
||||
"required": true,
|
||||
"text": "© OpenStreetMap contributors, tiles courtesy of Humanitarian OpenStreetMap Team",
|
||||
"url": "https://www.hotosm.org/"
|
||||
},
|
||||
"type": "tms",
|
||||
"category": "osmbasedmap",
|
||||
"max_zoom": 20
|
||||
},
|
||||
{
|
||||
"name": "OpenStreetMap (Occitan Style)",
|
||||
"id": "osmfr-occitan",
|
||||
"url": "https://tile.openstreetmap.bzh/oc/{zoom}/{x}/{y}.png",
|
||||
"attribution": {
|
||||
"required": true,
|
||||
"text": "Tiles © OpenStreetMap France, data © OpenStreetMap contributors, ODbL",
|
||||
"url": "https://www.openstreetmap.org/"
|
||||
},
|
||||
"type": "tms",
|
||||
"category": "osmbasedmap",
|
||||
"max_zoom": 20
|
||||
}
|
||||
]
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
"id": "bahasa Indonesia",
|
||||
"it": "italiano",
|
||||
"ja": "日本語",
|
||||
"ko": "한국어",
|
||||
"nb_NO": "bokmål",
|
||||
"nl": "Nederlands",
|
||||
"pl": "język polski",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
src/assets/svg/World.svelte
Normal file
4
src/assets/svg/World.svelte
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -17,7 +17,7 @@
|
|||
"contributor": "Anonymous"
|
||||
},
|
||||
{
|
||||
"commits": 107,
|
||||
"commits": 110,
|
||||
"contributor": "mcliquid"
|
||||
},
|
||||
{
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
"contributor": "Robin van der Linde"
|
||||
},
|
||||
{
|
||||
"commits": 76,
|
||||
"commits": 78,
|
||||
"contributor": "mike140"
|
||||
},
|
||||
{
|
||||
|
|
@ -37,7 +37,11 @@
|
|||
"contributor": "danieldegroot2"
|
||||
},
|
||||
{
|
||||
"commits": 54,
|
||||
"commits": 59,
|
||||
"contributor": "Midgard"
|
||||
},
|
||||
{
|
||||
"commits": 56,
|
||||
"contributor": "Jiří Podhorecký"
|
||||
},
|
||||
{
|
||||
|
|
@ -49,13 +53,9 @@
|
|||
"contributor": "gallegonovato"
|
||||
},
|
||||
{
|
||||
"commits": 47,
|
||||
"commits": 50,
|
||||
"contributor": "Supaplex"
|
||||
},
|
||||
{
|
||||
"commits": 46,
|
||||
"contributor": "Midgard"
|
||||
},
|
||||
{
|
||||
"commits": 45,
|
||||
"contributor": "Babos Gábor"
|
||||
|
|
@ -156,6 +156,10 @@
|
|||
"commits": 11,
|
||||
"contributor": "Túllio Franca"
|
||||
},
|
||||
{
|
||||
"commits": 10,
|
||||
"contributor": "JiwonShin"
|
||||
},
|
||||
{
|
||||
"commits": 10,
|
||||
"contributor": "Jeff Huang"
|
||||
|
|
@ -584,6 +588,10 @@
|
|||
"commits": 2,
|
||||
"contributor": "Leo Alcaraz"
|
||||
},
|
||||
{
|
||||
"commits": 1,
|
||||
"contributor": "Moimoi Ty"
|
||||
},
|
||||
{
|
||||
"commits": 1,
|
||||
"contributor": "Roger"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"languages":["ca","cs","da","de","en","eo","es","eu","fi","fil","fr","gl","he","hu","id","it","ja","nb_NO","nl","pa_PK","pl","pt","pt_BR","ru","sl","sv","uk","zgh","zh_Hans","zh_Hant"]}
|
||||
{"languages":["ca","cs","da","de","en","eo","es","eu","fi","fil","fr","gl","he","hu","id","it","ja","ko","nb_NO","nl","pa_PK","pl","pt","pt_BR","ru","sl","sv","uk","zgh","zh_Hans","zh_Hant"]}
|
||||
Loading…
Add table
Add a link
Reference in a new issue