forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
5a9f8f0a0a
62 changed files with 4448 additions and 710 deletions
|
|
@ -6,7 +6,8 @@
|
|||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { onDestroy } from "svelte"
|
||||
import { Utils } from "../../Utils"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import type { ValidatorType } from "../InputElement/Validators"
|
||||
import InputHelper from "../InputElement/InputHelper.svelte"
|
||||
|
||||
export let filteredLayer: FilteredLayer
|
||||
export let option: FilterConfigOption
|
||||
|
|
@ -18,7 +19,7 @@
|
|||
parts = Utils.splitIntoSubstitutionParts(template)
|
||||
}
|
||||
let fieldValues: Record<string, UIEventSource<string>> = {}
|
||||
let fieldTypes: Record<string, string> = {}
|
||||
let fieldTypes: Record<string, ValidatorType> = {}
|
||||
let appliedFilter = <UIEventSource<string>>filteredLayer.appliedFilters.get(id)
|
||||
let initialState: Record<string, string> = JSON.parse(appliedFilter?.data ?? "{}")
|
||||
|
||||
|
|
@ -35,25 +36,30 @@
|
|||
appliedFilter?.setData(FilteredLayer.fieldsToString(properties))
|
||||
}
|
||||
|
||||
let firstValue : UIEventSource<string>
|
||||
for (const field of option.fields) {
|
||||
// A bit of cheating: the 'parts' will have '}' suffixed for fields
|
||||
const src = new UIEventSource<string>(initialState[field.name] ?? "")
|
||||
firstValue ??= src
|
||||
fieldTypes[field.name] = field.type
|
||||
console.log(field.name, "-->", field.type)
|
||||
fieldValues[field.name] = src
|
||||
onDestroy(
|
||||
src.stabilized(200).addCallback(() => {
|
||||
setFields()
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="low-interaction p-1 rounded-2xl px-3" class:interactive={$firstValue?.length > 0}>
|
||||
{#each parts as part, i}
|
||||
{#if part["subs"]}
|
||||
<!-- This is a field! -->
|
||||
<span class="mx-1">
|
||||
<ValidatedInput value={fieldValues[part["subs"]]} type={fieldTypes[part["subs"]]} />
|
||||
<InputHelper value={fieldValues[part["subs"]]} type={fieldTypes[part["subs"]]}>
|
||||
<ValidatedInput slot="fallback" value={fieldValues[part["subs"]]} type={fieldTypes[part["subs"]]} />
|
||||
</InputHelper>
|
||||
</span>
|
||||
{:else}
|
||||
{@html part["message"]}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,12 @@
|
|||
export let coordinate: { lon: number; lat: number } = undefined
|
||||
|
||||
/**
|
||||
* The center of the map at all times
|
||||
* Max distance that one is allowed to move, to prevent to stray too much
|
||||
*/
|
||||
export let maxDistanceInMeters = 50
|
||||
|
||||
/**
|
||||
* The resulting location; either the map center or the snapped coordinate
|
||||
* If undefined at the beginning, 'coordinate' will be used
|
||||
*/
|
||||
export let value: UIEventSource<{ lon: number; lat: number }>
|
||||
|
|
@ -57,11 +62,6 @@
|
|||
|
||||
export let snappedTo: UIEventSource<WayId | undefined>
|
||||
|
||||
let preciseLocation: UIEventSource<{ lon: number; lat: number }> = new UIEventSource<{
|
||||
lon: number
|
||||
lat: number
|
||||
}>(undefined)
|
||||
|
||||
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
||||
export let mapProperties: Partial<MapProperties> & { location } = {
|
||||
zoom: new UIEventSource<number>(19),
|
||||
|
|
@ -69,10 +69,7 @@
|
|||
/*If no snapping needed: the value is simply the map location;
|
||||
* If snapping is needed: the value will be set later on by the snapping feature source
|
||||
* */
|
||||
location:
|
||||
snapToLayers?.length > 0
|
||||
? new UIEventSource<{ lon: number; lat: number }>(coordinate)
|
||||
: value,
|
||||
location: new UIEventSource<{ lon: number; lat: number }>(coordinate),
|
||||
bounds: new UIEventSource<BBox>(undefined),
|
||||
allowMoving: new UIEventSource<boolean>(true),
|
||||
allowZooming: new UIEventSource<boolean>(true),
|
||||
|
|
@ -143,15 +140,16 @@
|
|||
})
|
||||
withCorrectedAttributes.features.addCallbackAndRunD((f) => console.log("Snapped point is", f))
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<LocationInput
|
||||
{map}
|
||||
on:click
|
||||
{mapProperties}
|
||||
value={preciseLocation}
|
||||
value={ snapToLayers?.length > 0 ? new UIEventSource(undefined) : value}
|
||||
initialCoordinate={coordinate}
|
||||
maxDistanceInMeters={50}
|
||||
{maxDistanceInMeters}
|
||||
>
|
||||
<slot name="image" slot="image">
|
||||
<Move_arrows class="h-full max-h-24" />
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@
|
|||
<div class="flex h-32 w-max gap-x-2">
|
||||
{#each $unknownImages as image (image)}
|
||||
<AttributedImage
|
||||
{state}
|
||||
imgClass="h-32 w-max shrink-0"
|
||||
image={{ url: image }}
|
||||
previewedImage={state.previewedImage}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@
|
|||
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { MagnifyingGlassPlusIcon } from "@babeard/svelte-heroicons/outline"
|
||||
import { CloseButton, Modal } from "flowbite-svelte"
|
||||
import { CloseButton } from "flowbite-svelte"
|
||||
import ImageOperations from "./ImageOperations.svelte"
|
||||
import Popup from "../Base/Popup.svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import type { Feature, Point } from "geojson"
|
||||
|
||||
export let image: Partial<ProvidedImage>
|
||||
let fallbackImage: string = undefined
|
||||
|
|
@ -20,19 +22,43 @@
|
|||
|
||||
let imgEl: HTMLImageElement
|
||||
export let imgClass: string = undefined
|
||||
export let state: SpecialVisualizationState = undefined
|
||||
export let attributionFormat: "minimal" | "medium" | "large" = "medium"
|
||||
export let previewedImage: UIEventSource<ProvidedImage>
|
||||
export let canZoom = previewedImage !== undefined
|
||||
let loaded = false
|
||||
let showBigPreview = new UIEventSource(false)
|
||||
onDestroy(showBigPreview.addCallbackAndRun(shown=>{
|
||||
if(!shown){
|
||||
let showBigPreview = new UIEventSource(false)
|
||||
onDestroy(showBigPreview.addCallbackAndRun(shown => {
|
||||
if (!shown) {
|
||||
previewedImage.set(false)
|
||||
}
|
||||
}))
|
||||
onDestroy(previewedImage.addCallbackAndRun(previewedImage => {
|
||||
showBigPreview.set(previewedImage?.id === image.id)
|
||||
}))
|
||||
|
||||
function highlight(entered: boolean = true) {
|
||||
if (!entered) {
|
||||
state?.geocodedImages.set([])
|
||||
return
|
||||
}
|
||||
if (isNaN(image.lon) || isNaN(image.lat)) {
|
||||
return
|
||||
}
|
||||
const f: Feature<Point> = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
id: image.id,
|
||||
rotation: image.rotation
|
||||
},
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [image.lon, image.lat]
|
||||
}
|
||||
}
|
||||
console.log(f)
|
||||
state?.geocodedImages.set([f])
|
||||
}
|
||||
</script>
|
||||
|
||||
<Popup shown={showBigPreview} bodyPadding="p-0" dismissable={true}>
|
||||
|
|
@ -48,7 +74,10 @@
|
|||
</div>
|
||||
</Popup>
|
||||
<div class="relative shrink-0">
|
||||
<div class="relative w-fit">
|
||||
<div class="relative w-fit"
|
||||
on:mouseenter={() => highlight()}
|
||||
on:mouseleave={() => highlight(false)}
|
||||
>
|
||||
<img
|
||||
bind:this={imgEl}
|
||||
on:load={() => (loaded = true)}
|
||||
|
|
@ -68,7 +97,7 @@
|
|||
{#if canZoom && loaded}
|
||||
<div
|
||||
class="bg-black-transparent absolute right-0 top-0 rounded-bl-full"
|
||||
on:click={() => previewedImage.set(image)}>
|
||||
on:click={() => previewedImage.set(image)}>
|
||||
<MagnifyingGlassPlusIcon class="h-8 w-8 cursor-zoom-in pl-3 pb-3" color="white" />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import ImageProvider, { ProvidedImage } from "../../Logic/ImageProviders/ImagePr
|
|||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import { Changes } from "../../Logic/Osm/Changes"
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import { Feature } from "geojson"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import AttributedImage from "./AttributedImage.svelte"
|
||||
|
||||
|
|
@ -30,6 +29,7 @@ export class ImageCarousel extends Toggle {
|
|||
try {
|
||||
let image: BaseUIElement = new SvelteUIElement(AttributedImage, {
|
||||
image: url,
|
||||
state,
|
||||
previewedImage: state?.previewedImage,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
import AttributedImage from "./AttributedImage.svelte"
|
||||
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"
|
||||
import LoginToggle from "../Base/LoginToggle.svelte"
|
||||
import ImagePreview from "./ImagePreview.svelte"
|
||||
import FloatOver from "../Base/FloatOver.svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
export let tags: UIEventSource<OsmTags>
|
||||
export let state: SpecialVisualizationState
|
||||
|
|
@ -23,6 +23,8 @@
|
|||
export let feature: Feature
|
||||
export let layer: LayerConfig
|
||||
|
||||
export let highlighted: UIEventSource<string> = undefined
|
||||
|
||||
export let linkable = true
|
||||
let targetValue = Object.values(image.osmTags)[0]
|
||||
let isLinked = new UIEventSource(Object.values(tags.data).some((v) => targetValue === v))
|
||||
|
|
@ -33,7 +35,7 @@
|
|||
key: undefined,
|
||||
provider: AllImageProviders.byName(image.provider),
|
||||
date: new Date(image.date),
|
||||
id: Object.values(image.osmTags)[0],
|
||||
id: Object.values(image.osmTags)[0]
|
||||
}
|
||||
|
||||
async function applyLink(isLinked: boolean) {
|
||||
|
|
@ -44,7 +46,7 @@
|
|||
if (isLinked) {
|
||||
const action = new LinkImageAction(currentTags.id, key, url, tags, {
|
||||
theme: tags.data._orig_theme ?? state.layout.id,
|
||||
changeType: "link-image",
|
||||
changeType: "link-image"
|
||||
})
|
||||
await state.changes.applyAction(action)
|
||||
} else {
|
||||
|
|
@ -53,7 +55,7 @@
|
|||
if (v === url) {
|
||||
const action = new ChangeTagAction(currentTags.id, new Tag(k, ""), currentTags, {
|
||||
theme: tags.data._orig_theme ?? state.layout.id,
|
||||
changeType: "remove-image",
|
||||
changeType: "remove-image"
|
||||
})
|
||||
state.changes.applyAction(action)
|
||||
}
|
||||
|
|
@ -62,16 +64,30 @@
|
|||
}
|
||||
|
||||
isLinked.addCallback((isLinked) => applyLink(isLinked))
|
||||
|
||||
let element: HTMLDivElement
|
||||
if (highlighted) {
|
||||
|
||||
onDestroy(
|
||||
highlighted.addCallbackD(highlightedUrl => {
|
||||
if (highlightedUrl === image.pictureUrl) {
|
||||
Utils.scrollIntoView(element)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex w-fit shrink-0 flex-col overflow-hidden rounded-lg"
|
||||
class:border-interactive={$isLinked}
|
||||
class:border-interactive={$isLinked || $highlighted === image.pictureUrl}
|
||||
style="border-width: 2px"
|
||||
bind:this={element}
|
||||
>
|
||||
<AttributedImage
|
||||
{state}
|
||||
image={providedImage}
|
||||
imgClass="max-h-64 w-auto"
|
||||
imgClass="max-h-64 w-auto sm:h-32 md:h-64"
|
||||
previewedImage={state.previewedImage}
|
||||
attributionFormat="minimal"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -7,13 +7,23 @@
|
|||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch"
|
||||
import LinkableImage from "./LinkableImage.svelte"
|
||||
import type { Feature } from "geojson"
|
||||
import type { Feature, Point } from "geojson"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import MapillaryLink from "../BigComponents/MapillaryLink.svelte"
|
||||
import MaplibreMap from "../Map/MaplibreMap.svelte"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import { MapLibreAdaptor } from "../Map/MapLibreAdaptor"
|
||||
import ShowDataLayer from "../Map/ShowDataLayer"
|
||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
|
||||
import * as geocoded_image from "../../assets/generated/layers/geocoded_image.json"
|
||||
import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
||||
import { onDestroy } from "svelte"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
|
||||
|
||||
export let tags: UIEventSource<OsmTags>
|
||||
export let state: SpecialVisualizationState
|
||||
|
|
@ -42,12 +52,100 @@
|
|||
[loadedImages]
|
||||
)
|
||||
|
||||
let asFeatures = result.map(p4cs => p4cs.map(p4c => (<Feature<Point>>{
|
||||
type: "Feature",
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [p4c.coordinates.lng, p4c.coordinates.lat]
|
||||
},
|
||||
properties: {
|
||||
id: p4c.pictureUrl,
|
||||
rotation: p4c.direction
|
||||
}
|
||||
})))
|
||||
|
||||
let selected = new UIEventSource<P4CPicture>(undefined)
|
||||
let selectedAsFeature = selected.mapD(s => {
|
||||
return [<Feature<Point>>{
|
||||
type: "Feature",
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [s.coordinates.lng, s.coordinates.lat]
|
||||
},
|
||||
properties: {
|
||||
id: s.pictureUrl,
|
||||
selected: "yes",
|
||||
rotation: s.direction
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
let someLoading = imageState.state.mapD((stateRecord) =>
|
||||
Object.values(stateRecord).some((v) => v === "loading")
|
||||
)
|
||||
let errors = imageState.state.mapD((stateRecord) =>
|
||||
Object.keys(stateRecord).filter((k) => stateRecord[k] === "error")
|
||||
)
|
||||
let highlighted = new UIEventSource<string>(undefined)
|
||||
|
||||
onDestroy(highlighted.addCallbackD(hl => {
|
||||
const p4c = result.data?.find(i => i.pictureUrl === hl)
|
||||
selected.set(p4c)
|
||||
}
|
||||
))
|
||||
|
||||
let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
||||
let mapProperties = new MapLibreAdaptor(map, {
|
||||
rasterLayer: state.mapProperties.rasterLayer,
|
||||
rotation: state.mapProperties.rotation,
|
||||
pitch: state.mapProperties.pitch,
|
||||
zoom: new UIEventSource<number>(16),
|
||||
location: new UIEventSource({ lon, lat }),
|
||||
})
|
||||
|
||||
|
||||
const geocodedImageLayer = new LayerConfig(<LayerConfigJson>geocoded_image)
|
||||
new ShowDataLayer(map, {
|
||||
features: new StaticFeatureSource(asFeatures),
|
||||
layer: geocodedImageLayer,
|
||||
zoomToFeatures: true,
|
||||
onClick: (feature) => {
|
||||
highlighted.set(feature.properties.id)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
ShowDataLayer.showMultipleLayers(
|
||||
map,
|
||||
new StaticFeatureSource([feature]),
|
||||
state.layout.layers
|
||||
)
|
||||
|
||||
onDestroy(
|
||||
asFeatures.addCallbackAndRunD(features => {
|
||||
if(features.length == 0){
|
||||
return
|
||||
}
|
||||
let bbox = BBox.get(features[0])
|
||||
for (const f of features) {
|
||||
bbox = bbox.unionWith(BBox.get(f))
|
||||
}
|
||||
mapProperties.maxbounds.set(bbox.pad(1.1))
|
||||
})
|
||||
|
||||
)
|
||||
|
||||
new ShowDataLayer(map, {
|
||||
features: new StaticFeatureSource(selectedAsFeature),
|
||||
layer: geocodedImageLayer,
|
||||
onClick: (feature) => {
|
||||
highlighted.set(feature.properties.id)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col">
|
||||
|
|
@ -62,12 +160,24 @@
|
|||
{:else}
|
||||
<div class="flex w-full space-x-4 overflow-x-auto" style="scroll-snap-type: x proximity">
|
||||
{#each $result as image (image.pictureUrl)}
|
||||
<span class="w-fit shrink-0" style="scroll-snap-align: start">
|
||||
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} />
|
||||
<span class="w-fit shrink-0" style="scroll-snap-align: start"
|
||||
on:mouseenter={() => {highlighted.set(image.pictureUrl)}}
|
||||
on:mouseleave={() =>{ highlighted.set(undefined); selected.set(undefined)}}
|
||||
>
|
||||
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} {highlighted} />
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<span class="self-end pt-2">
|
||||
|
||||
<MapillaryLink
|
||||
large={false}
|
||||
mapProperties={{ zoom: new ImmutableStore(16), location: new ImmutableStore({ lon, lat }) }}
|
||||
/>
|
||||
</span>
|
||||
|
||||
|
||||
<div class="my-2 flex justify-between">
|
||||
<div>
|
||||
{#if $someLoading && $result.length > 0}
|
||||
|
|
@ -80,9 +190,10 @@
|
|||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<MapillaryLink
|
||||
large={false}
|
||||
mapProperties={{ zoom: new ImmutableStore(16), location: new ImmutableStore({ lon, lat }) }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="h-48">
|
||||
<MaplibreMap interactive={false} {map} {mapProperties} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@
|
|||
import Camera_plus from "../../assets/svg/Camera_plus.svelte"
|
||||
import LoginToggle from "../Base/LoginToggle.svelte"
|
||||
import { ariaLabel } from "../../Utils/ariaLabel"
|
||||
import { Accordion, AccordionItem } from "flowbite-svelte"
|
||||
import { Accordion, AccordionItem, Modal } from "flowbite-svelte"
|
||||
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||
import Popup from "../Base/Popup.svelte"
|
||||
|
||||
export let tags: UIEventSource<OsmTags>
|
||||
export let state: SpecialVisualizationState
|
||||
|
|
@ -24,15 +25,16 @@
|
|||
export let layer: LayerConfig
|
||||
const t = Translations.t.image.nearby
|
||||
|
||||
let expanded = false
|
||||
let enableLogin = state.featureSwitches.featureSwitchEnableLogin
|
||||
export let shown = new UIEventSource(false)
|
||||
</script>
|
||||
|
||||
{#if enableLogin.data}
|
||||
<AccordionSingle>
|
||||
<span slot="header" class="p-2 text-base">
|
||||
<button on:click={() => {shown.set(!shown.data)}}><Tr t={t.seeNearby}/> </button>
|
||||
<Popup {shown} bodyPadding="p-4">
|
||||
<span slot="header">
|
||||
<Tr t={t.seeNearby} />
|
||||
</span>
|
||||
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable} {layer} />
|
||||
</AccordionSingle>
|
||||
</Popup>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,15 @@
|
|||
/**
|
||||
* A visualisation to pick a location on a map background
|
||||
*/
|
||||
/**
|
||||
* The center of the map. If it contains a value (and initialCoordinate is not given), this will be used as start coordinate
|
||||
*/
|
||||
export let value: UIEventSource<{ lon: number; lat: number }>
|
||||
export let initialCoordinate: { lon: number; lat: number }
|
||||
export let initialCoordinate: { lon: number; lat: number } = undefined
|
||||
initialCoordinate = initialCoordinate ?? value.data
|
||||
/**
|
||||
* Max distance that one is allowed to stray from the initial coordinate
|
||||
*/
|
||||
export let maxDistanceInMeters: number = undefined
|
||||
export let mapProperties: Partial<MapProperties> & {
|
||||
readonly location: UIEventSource<{ lon: number; lat: number }>
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@
|
|||
export let type: ValidatorType
|
||||
export let value: UIEventSource<string | object>
|
||||
|
||||
export let feature: Feature
|
||||
export let feature: Feature = undefined
|
||||
export let args: (string | number | boolean)[] = undefined
|
||||
export let state: SpecialVisualizationState
|
||||
export let state: SpecialVisualizationState = undefined
|
||||
</script>
|
||||
|
||||
{#if type === "translation"}
|
||||
|
|
@ -51,4 +51,6 @@
|
|||
<SlopeInput {value} {feature} {state} />
|
||||
{:else if type === "wikidata"}
|
||||
<WikidataInputHelper {value} {feature} {state} {args} />
|
||||
{:else}
|
||||
<slot name="fallback" />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@
|
|||
<HeartOutlineIcon style="--svg-color: {color}" class={twMerge(clss, "apply-fill")} />
|
||||
{:else if icon === "confirm"}
|
||||
<Confirm class={clss} {color} />
|
||||
{:else if icon === "direction"}
|
||||
{:else if icon === "direction" || icon === "direction_gradient"}
|
||||
<Direction_gradient class={clss} {color} />
|
||||
{:else if icon === "not_found"}
|
||||
<Not_found class={twMerge(clss, "no-image-background")} {color} />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import maplibregl, { Map as MLMap, Map as MlMap, SourceSpecification } from "maplibre-gl"
|
||||
import maplibregl, { Map as MLMap, Map as MlMap, ScaleControl, SourceSpecification } from "maplibre-gl"
|
||||
import { RasterLayerPolygon } from "../../Models/RasterLayers"
|
||||
import { Utils } from "../../Utils"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
|
|
@ -48,6 +48,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
readonly rotation: UIEventSource<number>
|
||||
readonly pitch: UIEventSource<number>
|
||||
readonly useTerrain: Store<boolean>
|
||||
readonly showScale: UIEventSource<boolean>
|
||||
|
||||
private static pmtilesInited = false
|
||||
/**
|
||||
|
|
@ -93,6 +94,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
this.useTerrain = state?.useTerrain ?? new ImmutableStore<boolean>(false)
|
||||
this.rasterLayer =
|
||||
state?.rasterLayer ?? new UIEventSource<RasterLayerPolygon | undefined>(undefined)
|
||||
this.showScale = state?.showScale ?? new UIEventSource<boolean>(false)
|
||||
|
||||
const lastClickLocation = new UIEventSource<{
|
||||
lat: number
|
||||
|
|
@ -131,6 +133,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
self.setMaxzoom(self.maxzoom.data)
|
||||
self.setBounds(self.bounds.data)
|
||||
self.setTerrain(self.useTerrain.data)
|
||||
self.setScale(self.showScale.data)
|
||||
this.updateStores(true)
|
||||
})
|
||||
self.MoveMapToCurrentLoc(self.location.data)
|
||||
|
|
@ -144,6 +147,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
self.setBounds(self.bounds.data)
|
||||
self.SetRotation(self.rotation.data)
|
||||
self.setTerrain(self.useTerrain.data)
|
||||
self.setScale(self.showScale.data)
|
||||
this.updateStores(true)
|
||||
map.on("movestart", () => {
|
||||
this.isFlying.setData(true)
|
||||
|
|
@ -221,6 +225,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
this.allowZooming.addCallbackAndRun((allowZooming) => self.setAllowZooming(allowZooming))
|
||||
this.bounds.addCallbackAndRunD((bounds) => self.setBounds(bounds))
|
||||
this.useTerrain?.addCallbackAndRun((useTerrain) => self.setTerrain(useTerrain))
|
||||
this.showScale?.addCallbackAndRun(showScale => self.setScale(showScale))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -703,6 +708,32 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
}
|
||||
}
|
||||
|
||||
private scaleControl: maplibregl.ScaleControl = undefined
|
||||
|
||||
private setScale(showScale: boolean) {
|
||||
const map = this._maplibreMap.data
|
||||
if (!map) {
|
||||
return
|
||||
}
|
||||
if (!showScale) {
|
||||
if(this.scaleControl){
|
||||
map.removeControl(this.scaleControl)
|
||||
this.scaleControl = undefined
|
||||
}
|
||||
return
|
||||
}
|
||||
if (this.scaleControl === undefined) {
|
||||
|
||||
this.scaleControl = new ScaleControl({
|
||||
maxWidth: 100,
|
||||
unit: "metric"
|
||||
})
|
||||
}
|
||||
if (!map.hasControl(this.scaleControl)) {
|
||||
map.addControl(this.scaleControl, "bottom-right")
|
||||
}
|
||||
}
|
||||
|
||||
public flyTo(lon: number, lat: number, zoom: number) {
|
||||
this.lockZoom()
|
||||
window.requestAnimationFrame(() => {
|
||||
|
|
|
|||
|
|
@ -159,10 +159,9 @@ class PointRenderingLayer {
|
|||
})
|
||||
|
||||
if (this._onClick) {
|
||||
const self = this
|
||||
el.addEventListener("click", function (ev) {
|
||||
el.addEventListener("click", (ev)=> {
|
||||
ev.preventDefault()
|
||||
self._onClick(feature)
|
||||
this._onClick(feature)
|
||||
// Workaround to signal the MapLibreAdaptor to ignore this click
|
||||
ev["consumed"] = true
|
||||
})
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
snapToLayers={$reason.snapTo}
|
||||
targetLayer={layer}
|
||||
dontShow={[id]}
|
||||
maxDistanceInMeters={200}
|
||||
/>
|
||||
<div class="absolute bottom-0 left-0">
|
||||
<OpenBackgroundSelectorButton {state} />
|
||||
|
|
|
|||
|
|
@ -48,16 +48,14 @@
|
|||
}
|
||||
|
||||
let htmlElem: HTMLDivElement
|
||||
$: {
|
||||
if (editMode && htmlElem !== undefined && config.IsKnown($tags)) {
|
||||
// EditMode switched to true yet the answer is already known, so the person wants to make a change
|
||||
// Make sure that the question is in the scrollview!
|
||||
|
||||
function enableEditMode(){
|
||||
editMode = true
|
||||
// EditMode switched to true yet the answer is already known, so the person wants to make a change
|
||||
// Make sure that the question is in the scrollview!
|
||||
window.setTimeout(() => {
|
||||
// Some delay is applied to give Svelte the time to render the _question_
|
||||
window.setTimeout(() => {
|
||||
Utils.scrollIntoView(<any>htmlElem)
|
||||
}, 50)
|
||||
}
|
||||
Utils.scrollIntoView(<any>htmlElem)
|
||||
}, 50)
|
||||
}
|
||||
|
||||
const _htmlElement = new UIEventSource<HTMLElement>(undefined)
|
||||
|
|
@ -132,9 +130,7 @@
|
|||
<EditButton
|
||||
arialabel={config.editButtonAriaLabel}
|
||||
ariaLabelledBy={answerId}
|
||||
on:click={() => {
|
||||
editMode = true
|
||||
}}
|
||||
on:click={() => enableEditMode()}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@
|
|||
{#if question !== undefined}
|
||||
<div class={clss}>
|
||||
<form
|
||||
class="relative flex flex-col overflow-y-auto px-2"
|
||||
class="relative flex flex-col overflow-y-auto px-4"
|
||||
style="max-height: 75vh"
|
||||
on:submit|preventDefault={() => {
|
||||
/*onSave(); This submit is not needed and triggers too early, causing bugs: see #1808*/
|
||||
|
|
@ -351,7 +351,7 @@
|
|||
>
|
||||
<fieldset>
|
||||
<legend>
|
||||
<div class="sticky top-0 justify-between pt-1 font-bold" style="z-index: 11">
|
||||
<div class="sticky top-0 justify-between pt-4 pb-2 font-bold" style="z-index: 11">
|
||||
<SpecialTranslation t={question} {tags} {state} {layer} feature={selectedElement} />
|
||||
</div>
|
||||
|
||||
|
|
@ -557,7 +557,7 @@
|
|||
{/if}
|
||||
|
||||
|
||||
<div class="flex flex-wrap-reverse items-stretch justify-end sm:flex-nowrap self-end flex-grow">
|
||||
<div class="flex flex-wrap-reverse items-stretch justify-end sm:flex-nowrap self-end flex-grow mt-4 mb-2">
|
||||
|
||||
<!-- TagRenderingQuestion-buttons -->
|
||||
<slot name="cancel" />
|
||||
|
|
@ -590,7 +590,7 @@
|
|||
<TagHint {state} tags={selectedTags} currentProperties={$tags} />
|
||||
<span class="flex flex-wrap">
|
||||
{#if $featureSwitchIsTesting}
|
||||
<div class="alert">Testmode </div>
|
||||
<div class="alert" style="padding: 0; margin: 0; margin-right: 0.5rem">Testmode </div>
|
||||
{/if}
|
||||
{#if $featureSwitchIsTesting || $featureSwitchIsDebugging}
|
||||
<a class="small" on:click={() => console.log("Configuration is ", config)}>
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ export interface SpecialVisualizationState {
|
|||
readonly previewedImage: UIEventSource<ProvidedImage>
|
||||
readonly nearbyImageSearcher: CombinedFetcher
|
||||
readonly geolocation: GeoLocationHandler
|
||||
readonly geocodedImages : UIEventSource<Feature[]>
|
||||
readonly searchState: SearchState
|
||||
|
||||
getMatchingLayer(properties: Record<string, string>);
|
||||
|
|
|
|||
|
|
@ -32,12 +32,23 @@
|
|||
return type.some((t) => mightBeBoolean(t))
|
||||
}
|
||||
|
||||
function mightBeTag(){
|
||||
const t = schema.type
|
||||
if(!Array.isArray(t)){
|
||||
return false
|
||||
}
|
||||
const hasAnd = t.some(obj => obj["$ref"] === "#/definitions/{and:TagConfigJson[];}")
|
||||
const hasOr = t.some(obj => obj["$ref"] === "#/definitions/{or:TagConfigJson[];}")
|
||||
const hasString = t.some(obj => obj["type"] === "string")
|
||||
return hasAnd && hasOr && hasString
|
||||
}
|
||||
|
||||
const isTranslation =
|
||||
schema.hints?.typehint === "translation" ||
|
||||
schema.hints?.typehint === "rendered" ||
|
||||
ConfigMetaUtils.isTranslation(schema)
|
||||
let type = schema.hints.typehint ?? "string"
|
||||
|
||||
let type = schema.hints.typehint ?? (mightBeTag() ? "tag" : "string")
|
||||
let rendervalue =
|
||||
(schema.hints.inline ?? schema.path.join(".")) +
|
||||
(isTranslation ? " <b>{translated(value)}</b>" : " <b>{value}</b>")
|
||||
|
|
|
|||
|
|
@ -310,6 +310,11 @@
|
|||
{/if}
|
||||
</div>
|
||||
</If>
|
||||
<If condition={state.mapProperties.showScale}>
|
||||
<div class="h-6">
|
||||
<!-- Empty. We just provide some space for the maplibre scalecontrol -->
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue