chore: automated housekeeping...

This commit is contained in:
Pieter Vander Vennet 2025-04-15 18:18:44 +02:00
parent 79b6927b56
commit 42ded4c1b1
328 changed files with 4062 additions and 1284 deletions

View file

@ -165,7 +165,7 @@
<Page {onlyLink} shown={pg.failedImages} bodyPadding="p-0 pb-4">
<svelte:fragment slot="header">
<PhotoIcon />
<Tr t={Translations.t.imageQueue.menu.Subs({count: $nrOfFailedImages.length})} />
<Tr t={Translations.t.imageQueue.menu.Subs({ count: $nrOfFailedImages.length })} />
</svelte:fragment>
<QueuedImagesView {state} />
</Page>

View file

@ -35,7 +35,9 @@
export let attributionFormat: "minimal" | "medium" | "large" = "medium"
let previewedImage: UIEventSource<Partial<ProvidedImage>> = MenuState.previewedImage
export let canZoom = previewedImage !== undefined
export let nearbyFeatures: Feature<Geometry, HotspotProperties>[] | Store<Feature<Geometry, HotspotProperties>[]> = []
export let nearbyFeatures:
| Feature<Geometry, HotspotProperties>[]
| Store<Feature<Geometry, HotspotProperties>[]> = []
let loaded = false
let showBigPreview = new UIEventSource(false)
@ -49,7 +51,7 @@
previewedImage.addCallbackAndRun((previewedImage) => {
showBigPreview.set(
previewedImage !== undefined &&
(previewedImage?.id ?? previewedImage?.url) === (image.id ?? image.url)
(previewedImage?.id ?? previewedImage?.url) === (image.id ?? image.url)
)
})
)
@ -67,16 +69,15 @@
type: "Feature",
properties: {
id: image.id,
rotation: image.rotation
rotation: image.rotation,
},
geometry: {
type: "Point",
coordinates: [image.lon, image.lat]
}
coordinates: [image.lon, image.lat],
},
}
state?.geocodedImages.set([f])
}
</script>
<Popup shown={showBigPreview} bodyPadding="p-0" dismissable={true}>
@ -135,10 +136,12 @@
/>
{#if image.isSpherical}
<div class="absolute top-0 left-0 w-full h-full flex justify-center items-center pointer-events-none">
<div class="bg-black opacity-50 rounded-full p-[3.25rem]">
<div class="w-0 h-0 relative flex items-center justify-center">
<Panorama360 class="absolute w-16 h-16" color="#ffffff" />
<div
class="pointer-events-none absolute left-0 top-0 flex h-full w-full items-center justify-center"
>
<div class="rounded-full bg-black p-[3.25rem] opacity-50">
<div class="relative flex h-0 w-0 items-center justify-center">
<Panorama360 class="absolute h-16 w-16" color="#ffffff" />
</div>
</div>
</div>
@ -157,7 +160,6 @@
<ImageAttribution {image} {attributionFormat} />
</div>
</div>
{:else if image.status === "hidden"}
<div class="subtle">This image has been reported</div>
{/if}

View file

@ -18,12 +18,14 @@
type: "Feature",
geometry: {
type: "Point",
coordinates: GeoOperations.centerpointCoordinates(feature)
coordinates: GeoOperations.centerpointCoordinates(feature),
},
properties: {
name: layer?.title?.GetRenderValue(feature.properties)?.Subs(feature.properties)?.txt ?? feature?.properties?.name,
focus: true
}
name:
layer?.title?.GetRenderValue(feature.properties)?.Subs(feature.properties)?.txt ??
feature?.properties?.name,
focus: true,
},
}
</script>

View file

@ -20,7 +20,9 @@
export let image: Partial<ProvidedImage> & { id: string; url: string }
export let clss: string = undefined
export let nearbyFeatures: Feature<Geometry, HotspotProperties>[] | Store<Feature<Geometry, HotspotProperties>[]> = []
export let nearbyFeatures:
| Feature<Geometry, HotspotProperties>[]
| Store<Feature<Geometry, HotspotProperties>[]> = []
let isLoaded = new UIEventSource(false)
console.log(">>> slots are", $$slots)
@ -37,17 +39,17 @@
</div>
{#if $$slots["dot-menu-actions"]}
<DotMenu dotsPosition="top-0 left-0" dotsSize="w-8 h-8" hideBackground>
<slot name="dot-menu-actions">
<button
class="no-image-background pointer-events-auto flex items-center"
on:click={() => ImageProvider.offerImageAsDownload(image)}
>
<DownloadIcon class="h-6 w-6 px-2 opacity-100" />
<Tr t={Translations.t.general.download.downloadImage} />
</button>
</slot>
</DotMenu>
<DotMenu dotsPosition="top-0 left-0" dotsSize="w-8 h-8" hideBackground>
<slot name="dot-menu-actions">
<button
class="no-image-background pointer-events-auto flex items-center"
on:click={() => ImageProvider.offerImageAsDownload(image)}
>
<DownloadIcon class="h-6 w-6 px-2 opacity-100" />
<Tr t={Translations.t.general.download.downloadImage} />
</button>
</slot>
</DotMenu>
{/if}
<div
class="pointer-events-none absolute bottom-0 left-0 flex w-full flex-wrap items-end justify-between"

View file

@ -13,8 +13,9 @@
import type { Feature, Geometry, Point } from "geojson"
import { Store } from "../../Logic/UIEventSource"
export let nearbyFeatures: Feature<Geometry, HotspotProperties>[] | Store<Feature<Geometry, HotspotProperties>[]> = []
export let nearbyFeatures:
| Feature<Geometry, HotspotProperties>[]
| Store<Feature<Geometry, HotspotProperties>[]> = []
export let image: Partial<ProvidedImage>
let panzoomInstance = undefined
let panzoomEl: HTMLElement
@ -34,17 +35,15 @@
if (Array.isArray(nearbyFeatures)) {
viewer.setNearbyFeatures(nearbyFeatures)
} else {
nearbyFeatures.addCallbackAndRunD(feats => {
nearbyFeatures.addCallbackAndRunD((feats) => {
viewer.setNearbyFeatures(feats)
})
}
isLoaded.set(true)
}
$: {
if (image.isSpherical) {
initPhotosphere()
} else if (panzoomEl) {
panzoomInstance = panzoom(panzoomEl, {
@ -52,7 +51,7 @@
boundsPadding: 0.49,
minZoom: 0.1,
maxZoom: 25,
initialZoom: 1.0
initialZoom: 1.0,
})
} else {
panzoomInstance?.dispose()
@ -61,17 +60,17 @@
</script>
<head>
<link rel="stylesheet" href="./css/pannellum.css">
<link rel="stylesheet" href="./css/pannellum.css" />
</head>
{#if image.isSpherical}
<div bind:this={viewerEl} class="w-full h-full" />
<div bind:this={viewerEl} class="h-full w-full" />
{:else}
<img
bind:this={panzoomEl}
class="panzoom-image h-fit max-w-fit"
on:load={() => {
isLoaded?.setData(true)
}}
isLoaded?.setData(true)
}}
src={image.url_hd ?? image.url}
/>
{/if}

View file

@ -30,7 +30,7 @@
export let linkable = true
let targetValue = Object.values(image.osmTags)[0]
let isLinked = new UIEventSource(Object.values(tags.data).some((v) => targetValue === v))
isLinked.addCallbackAndRun(linked => {
isLinked.addCallbackAndRun((linked) => {
if (linked) {
MenuState.previewedImage.set(undefined)
}
@ -43,7 +43,7 @@
provider: AllImageProviders.byName(image.provider),
date: new Date(image.date),
id: Object.values(image.osmTags)[0],
isSpherical: image.details.isSpherical
isSpherical: image.details.isSpherical,
}
async function applyLink(isLinked: boolean) {
@ -54,7 +54,7 @@
if (isLinked) {
const action = new LinkImageAction(currentTags.id, key, url, tags, {
theme: tags.data._orig_theme ?? state.theme.id,
changeType: "link-image"
changeType: "link-image",
})
await state.changes.applyAction(action)
} else {
@ -63,7 +63,7 @@
if (v === url) {
const action = new ChangeTagAction(currentTags.id, new Tag(k, ""), currentTags, {
theme: tags.data._orig_theme ?? state.theme.id,
changeType: "remove-image"
changeType: "remove-image",
})
state.changes.applyAction(action)
}
@ -99,9 +99,7 @@
attributionFormat="minimal"
>
<svelte:fragment slot="dot-menu-actions">
<LoginToggle {state} silentFail={true} hiddenFail={true}>
{#if linkable}
<label>
<input bind:checked={$isLinked} type="checkbox" />
@ -110,7 +108,6 @@
{/if}
</LoginToggle>
</svelte:fragment>
</AttributedImage>
<LoginToggle {state} silentFail={true}>
{#if linkable}

View file

@ -46,8 +46,7 @@
(pics: P4CPicture[]) =>
pics
.filter(
(p: P4CPicture) =>
!loadedImages.data.has(p.pictureUrl) // We don't show any image which is already linked
(p: P4CPicture) => !loadedImages.data.has(p.pictureUrl) // We don't show any image which is already linked
)
.slice(0, 25),
[loadedImages]
@ -60,15 +59,15 @@
type: "Feature",
geometry: {
type: "Point",
coordinates: [p4c.coordinates.lng, p4c.coordinates.lat]
coordinates: [p4c.coordinates.lng, p4c.coordinates.lat],
},
properties: <PanoramaView>{
id: p4c.pictureUrl,
url: p4c.pictureUrl,
northOffset: p4c.direction,
rotation: p4c.direction,
spherical: p4c.details.isSpherical ? "yes" : "no"
}
spherical: p4c.details.isSpherical ? "yes" : "no",
},
}
)
)
@ -80,14 +79,14 @@
type: "Feature",
geometry: {
type: "Point",
coordinates: [s.coordinates.lng, s.coordinates.lat]
coordinates: [s.coordinates.lng, s.coordinates.lat],
},
properties: {
id: s.pictureUrl,
selected: "yes",
rotation: s.direction
}
}
rotation: s.direction,
},
},
]
})
@ -112,7 +111,7 @@
rotation: state.mapProperties.rotation,
pitch: state.mapProperties.pitch,
zoom: new UIEventSource<number>(16),
location: new UIEventSource({ lon, lat })
location: new UIEventSource({ lon, lat }),
})
const geocodedImageLayer = new LayerConfig(<LayerConfigJson>geocoded_image)
@ -123,7 +122,7 @@
onClick: (feature) => {
console.log("CLicked:", feature.properties)
highlighted.set(feature.properties.id)
}
},
})
ShowDataLayer.showMultipleLayers(map, new StaticFeatureSource([feature]), state.theme.layers)
@ -146,24 +145,29 @@
layer: geocodedImageLayer,
onClick: (feature) => {
highlighted.set(feature.properties.id)
}
},
})
let nearbyFeatures: Store<Feature[]> = asFeatures.map(nearbyPoints => {
return [{
type: "Feature",
geometry: { type: "Point", coordinates: GeoOperations.centerpointCoordinates(feature) },
properties: {
name: layer.title?.GetRenderValue(feature.properties).Subs(feature.properties).txt,
focus: true
}
}, ...nearbyPoints.filter(p => p.properties.spherical === "yes").map(f => ({
...f, properties: {
name: "Nearby panorama",
pitch: "auto",
type: "scene",
gotoPanorama: f
}
}))
let nearbyFeatures: Store<Feature[]> = asFeatures.map((nearbyPoints) => {
return [
{
type: "Feature",
geometry: { type: "Point", coordinates: GeoOperations.centerpointCoordinates(feature) },
properties: {
name: layer.title?.GetRenderValue(feature.properties).Subs(feature.properties).txt,
focus: true,
},
},
...nearbyPoints
.filter((p) => p.properties.spherical === "yes")
.map((f) => ({
...f,
properties: {
name: "Nearby panorama",
pitch: "auto",
type: "scene",
gotoPanorama: f,
},
})),
]
})
@ -204,7 +208,16 @@
selected.set(undefined)
}}
>
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} {highlighted} {nearbyFeatures} />
<LinkableImage
{tags}
{image}
{state}
{feature}
{layer}
{linkable}
{highlighted}
{nearbyFeatures}
/>
</span>
{/each}
</div>

View file

@ -13,40 +13,45 @@
export let imageArguments: ImageUploadArguments
let confirmDelete = new UIEventSource(false)
function del() {
queue.delete(imageArguments)
}
const t = Translations.t
let src = undefined
try{
try {
src = URL.createObjectURL(imageArguments.blob)
}catch (e) {
} catch (e) {
console.error("Could not create an ObjectURL for blob", imageArguments.blob)
}
</script>
<div class="low-interaction rounded border-interactive w-fit p-2 m-1 flex flex-col">
<img class="max-w-64 w-auto max-h-64 w-auto" {src} />
{imageArguments.featureId} {imageArguments.layoutId}
<button class="as-link self-end" on:click={() => {confirmDelete.set(true)}}>
<div class="low-interaction border-interactive m-1 flex w-fit flex-col rounded p-2">
<img class="max-h-64 w-auto w-auto max-w-64" {src} />
{imageArguments.featureId}
{imageArguments.layoutId}
<button
class="as-link self-end"
on:click={() => {
confirmDelete.set(true)
}}
>
<TrashIcon class="w-4" />
<Tr t={t.imageQueue.delete} />
</button>
<Popup shown={confirmDelete} dismissable={true}>
<Page shown={confirmDelete}>
<svelte:fragment slot="header">
<TrashIcon class="w-8 m-1" />
<TrashIcon class="m-1 w-8" />
<Tr t={t.imageQueue.confirmDeleteTitle} />
</svelte:fragment>
<div class="flex flex-col ">
<div class="flex flex-col">
<div class="flex justify-center">
<img class="max-w-128 w-auto max-h-128 w-auto" src={URL.createObjectURL(imageArguments.blob)} />
<img
class="max-w-128 max-h-128 w-auto w-auto"
src={URL.createObjectURL(imageArguments.blob)}
/>
</div>
<div class="flex w-full">
@ -54,8 +59,7 @@
<Tr t={t.general.back} />
</BackButton>
<button on:click={() => del()} class="primary w-full">
<TrashIcon class="w-8 m-1" />
<TrashIcon class="m-1 w-8" />
<Tr t={t.imageQueue.confirmDelete} />
</button>
</div>

View file

@ -23,13 +23,13 @@
<Tr t={q.intro} />
</div>
<UploadingImageCounter {state}/>
<UploadingImageCounter {state} />
{#if $isUploading}
<Loading />
{:else}
<button class="primary" on:click={() => state.imageUploadManager.uploadQueue()}>
<ArrowPathIcon class="w-8 h-8 m-1" />
<ArrowPathIcon class="m-1 h-8 w-8" />
<Tr t={q.retryAll} />
</button>
{/if}

View file

@ -47,7 +47,14 @@
errs.push(canBeUploaded.error)
continue
}
await state?.imageUploadManager?.uploadImageAndApply(file, tags, targetKey, noBlur, feature, { ignoreGPS })
await state?.imageUploadManager?.uploadImageAndApply(
file,
tags,
targetKey,
noBlur,
feature,
{ ignoreGPS }
)
} catch (e) {
console.error(e)
state.reportError(e, "Could not upload image")

View file

@ -26,9 +26,9 @@
*/
function getCount(input: Store<string[]>): Store<number> {
if (featureId == "*") {
return input.map(inp => inp.length)
return input.map((inp) => inp.length)
}
return input.map(success => success.filter(item => item === featureId).length)
return input.map((success) => success.filter((item) => item === featureId).length)
}
let successfull = getCount(state.imageUploadManager.successfull)
@ -39,7 +39,7 @@
const t = Translations.t.image
const debugging = state.featureSwitches.featureSwitchIsDebugging
let dismissed = 0
failed.addCallbackAndRun(failed => {
failed.addCallbackAndRun((failed) => {
dismissed = Math.min(failed, dismissed)
})
</script>
@ -56,7 +56,7 @@
{#if $pending - $failed === 1}
<Tr t={t.upload.one.uploading} />
{:else if $pending - $failed > 1}
<Tr t={t.upload.multiple.uploading.Subs({count: $pending})} />
<Tr t={t.upload.multiple.uploading.Subs({ count: $pending })} />
{/if}
</Loading>
</div>
@ -70,6 +70,6 @@
{#if $successfull === 1}
<Tr cls="thanks" t={t.upload.one.done} />
{:else if $successfull > 1}
<Tr cls="thanks" t={t.upload.multiple.done.Subs({count: $successfull})} />
<Tr cls="thanks" t={t.upload.multiple.done.Subs({ count: $successfull })} />
{/if}
{/if}

View file

@ -4,38 +4,37 @@ import { Feature, Geometry, Point } from "geojson"
import { GeoOperations } from "../../Logic/GeoOperations"
import { HotspotProperties, PanoramaView } from "../../Logic/ImageProviders/ImageProvider"
export class PhotoSphereViewerWrapper {
private imageInfo: Feature<Point, PanoramaView>
private readonly viewer: Pannellum.Viewer
private nearbyFeatures: Feature<Geometry, HotspotProperties>[] = []
constructor(container: HTMLElement, imageInfo: Feature<Point, PanoramaView>, nearbyFeatures?: Feature<Geometry, HotspotProperties>[]) {
constructor(
container: HTMLElement,
imageInfo: Feature<Point, PanoramaView>,
nearbyFeatures?: Feature<Geometry, HotspotProperties>[]
) {
this.imageInfo = imageInfo
this.viewer = pannellum.viewer(container,
<any>{
default: {
firstScene: imageInfo.properties.url,
sceneFadeDuration: 250
this.viewer = pannellum.viewer(container, <any>{
default: {
firstScene: imageInfo.properties.url,
sceneFadeDuration: 250,
},
scenes: {
[imageInfo.properties.url]: {
type: "equirectangular",
hfov: 110,
panorama: imageInfo.properties.url,
autoLoad: true,
hotSpots: [],
sceneFadeDuration: 250,
compass: true,
showControls: false,
northOffset: imageInfo.properties.northOffset,
horizonPitch: imageInfo.properties.pitchOffset,
},
scenes: {
[imageInfo.properties.url]:
{
type: "equirectangular",
hfov: 110,
panorama: imageInfo.properties.url,
autoLoad: true,
hotSpots: [],
sceneFadeDuration: 250,
compass: true,
showControls: false,
northOffset: imageInfo.properties.northOffset,
horizonPitch: imageInfo.properties.pitchOffset
}
}
}
)
},
})
this.setNearbyFeatures(nearbyFeatures)
}
@ -43,12 +42,13 @@ export class PhotoSphereViewerWrapper {
public calculatePitch(feature: Feature): number {
const coors = this.imageInfo.geometry.coordinates
const distance = GeoOperations.distanceBetween(
<[number, number]>coors, GeoOperations.centerpointCoordinates(feature)
<[number, number]>coors,
GeoOperations.centerpointCoordinates(feature)
)
// In: -pi/2 up to pi/2
const alpha = Math.atan(distance / 4) // in radians
const degrees = alpha * 360 / (2 * Math.PI)
const degrees = (alpha * 360) / (2 * Math.PI)
return -degrees
}
@ -62,7 +62,7 @@ export class PhotoSphereViewerWrapper {
this.viewer.addScene(imageInfo.properties.url, <any>{
panorama: imageInfo.properties.url,
northOffset: imageInfo.properties.northOffset,
type: "equirectangular"
type: "equirectangular",
})
this.viewer.loadScene(imageInfo.properties.url, 0, imageInfo.properties.northOffset)
@ -70,7 +70,8 @@ export class PhotoSphereViewerWrapper {
}
private clearHotspots() {
const hotspots = this.viewer.getConfig()["scenes"][this.imageInfo.properties.url].hotSpots ?? []
const hotspots =
this.viewer.getConfig()["scenes"][this.imageInfo.properties.url].hotSpots ?? []
for (const hotspot of hotspots) {
this.viewer.removeHotSpot(hotspot?.id, this.imageInfo.properties.url)
}
@ -95,20 +96,21 @@ export class PhotoSphereViewerWrapper {
} else if (!isNaN(f.properties.pitch)) {
pitch = f.properties.pitch
}
this.viewer.addHotSpot({
type: f.properties.gotoPanorama !== undefined ? "scene" : "info",
yaw: (yaw - northOffs) % 360,
pitch,
text: f.properties.name,
clickHandlerFunc: () => {
this.setPanorama(f.properties.gotoPanorama)
}
}, this.imageInfo.properties.url)
this.viewer.addHotSpot(
{
type: f.properties.gotoPanorama !== undefined ? "scene" : "info",
yaw: (yaw - northOffs) % 360,
pitch,
text: f.properties.name,
clickHandlerFunc: () => {
this.setPanorama(f.properties.gotoPanorama)
},
},
this.imageInfo.properties.url
)
if (f.properties.focus) {
this.viewer.setYaw(yaw - northOffs)
}
}
}
}

View file

@ -1,5 +1,4 @@
<script lang="ts">
/**
* Used to quickly calculate a distance by dragging a map (and selecting start- and endpoints)
*/
@ -24,71 +23,77 @@
export let value: UIEventSource<number>
export let feature: Feature
export let args: { background?: string, zoom?: number }
export let args: { background?: string; zoom?: number }
export let state: ThemeViewState = undefined
export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
let center = GeoOperations.centerpointCoordinates(feature)
export let initialCoordinate: { lon: number, lat: number } = { lon: center[0], lat: center[1] }
let mapLocation: UIEventSource<{ lon: number, lat: number }> = new UIEventSource(initialCoordinate)
export let initialCoordinate: { lon: number; lat: number } = { lon: center[0], lat: center[1] }
let mapLocation: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(
initialCoordinate
)
let bg = args?.background
let rasterLayer = state?.mapProperties.rasterLayer
if (bg !== undefined) {
if (eliCategory.indexOf(bg) >= 0) {
const availableLayers = state.availableLayers.store.data
const startLayer: RasterLayerPolygon = RasterLayerUtils.SelectBestLayerAccordingTo(availableLayers, bg)
const startLayer: RasterLayerPolygon = RasterLayerUtils.SelectBestLayerAccordingTo(
availableLayers,
bg
)
rasterLayer = new UIEventSource(startLayer)
state?.mapProperties.rasterLayer.addCallbackD(layer => rasterLayer.set(layer))
state?.mapProperties.rasterLayer.addCallbackD((layer) => rasterLayer.set(layer))
}
}
let mapProperties: Partial<MapProperties> = {
rasterLayer: rasterLayer,
location: mapLocation,
zoom: new UIEventSource(args?.zoom ?? 18)
zoom: new UIEventSource(args?.zoom ?? 18),
}
let start: UIEventSource<{ lon: number, lat: number }> = new UIEventSource(undefined)
let start: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined)
function selectStart() {
start.set(mapLocation.data)
}
let lengthFeature: Store<Feature[]> = start.map(start => {
if (!start) {
return []
}
// A bit of a double task: calculate the actual value _and_ the map rendering
const end = mapLocation.data
const distance = GeoOperations.distanceBetween([start.lon, start.lat], [end.lon, end.lat])
value.set(distance.toFixed(2))
return <Feature[]>[
{
type: "Feature",
properties: {
id: "distance_line_" + distance,
distance: "" + distance
},
geometry: {
type: "LineString",
coordinates: [[start.lon, start.lat], [end.lon, end.lat]]
}
let lengthFeature: Store<Feature[]> = start.map(
(start) => {
if (!start) {
return []
}
]
// A bit of a double task: calculate the actual value _and_ the map rendering
const end = mapLocation.data
const distance = GeoOperations.distanceBetween([start.lon, start.lat], [end.lon, end.lat])
value.set(distance.toFixed(2))
}, [mapLocation])
return <Feature[]>[
{
type: "Feature",
properties: {
id: "distance_line_" + distance,
distance: "" + distance,
},
geometry: {
type: "LineString",
coordinates: [
[start.lon, start.lat],
[end.lon, end.lat],
],
},
},
]
},
[mapLocation]
)
new ShowDataLayer(map, {
layer: new LayerConfig(conflation),
features: new StaticFeatureSource(lengthFeature)
features: new StaticFeatureSource(lengthFeature),
})
</script>
<div class="relative w-full h-64">
<div class="relative h-64 w-full">
<LocationInput value={mapLocation} {mapProperties} {map} />
<div class="absolute bottom-0 left-0 p-4">
<OpenBackgroundSelectorButton {state} {map} />

View file

@ -61,7 +61,7 @@ export default class Validators {
"translation",
"url",
"velopark",
"wikidata"
"wikidata",
] as const
public static readonly AllValidators: ReadonlyArray<Validator> = [
@ -93,7 +93,7 @@ export default class Validators {
new VeloparkValidator(),
new NameSuggestionIndexValidator(),
new CurrencyValidator(),
new RegexValidator()
new RegexValidator(),
]
private static _byType = Validators._byTypeConstructor()

View file

@ -6,17 +6,19 @@ export default class DistanceValidator extends Validator {
private readonly docs: string = [
"#### Helper-arguments",
"Options are:",
["````json",
" \"background\": \"some_background_id or category, e.g. 'map'\"",
" \"zoom\": 20 # initial zoom level of the map",
[
"````json",
' "background": "some_background_id or category, e.g. \'map\'"',
' "zoom": 20 # initial zoom level of the map',
"}",
"```"].join("\n")
"```",
].join("\n"),
].join("\n\n")
constructor() {
super(
"distance",
"A geographical distance in meters (rounded at two points). Will give an extra minimap with a measurement tool. Arguments: [ zoomlevel, preferredBackgroundMapType (comma separated) ], e.g. `[\"21\", \"map,photo\"]",
'A geographical distance in meters (rounded at two points). Will give an extra minimap with a measurement tool. Arguments: [ zoomlevel, preferredBackgroundMapType (comma separated) ], e.g. `["21", "map,photo"]',
"decimal"
)
}
@ -35,14 +37,18 @@ export default class DistanceValidator extends Validator {
}
const optionalKeys = ["background", "zoom"]
const keys = Object.keys(args).filter(k => optionalKeys.indexOf(k) < 0)
const keys = Object.keys(args).filter((k) => optionalKeys.indexOf(k) < 0)
if (keys.length > 0) {
return "Unknown key " + keys.join("; ") + "; use " + optionalKeys.join("; ") + " instead"
return (
"Unknown key " + keys.join("; ") + "; use " + optionalKeys.join("; ") + " instead"
)
}
const bg = args["background"]
if (bg && eliCategory.indexOf(bg) < 0) {
return "The given background layer is not a recognized ELI-type. Perhaps you meant one of " +
Utils.sortedByLevenshteinDistance(bg, eliCategory, x => x).slice(0, 5)
return (
"The given background layer is not a recognized ELI-type. Perhaps you meant one of " +
Utils.sortedByLevenshteinDistance(bg, eliCategory, (x) => x).slice(0, 5)
)
}
if (typeof args["zoom"] !== "number") {
return "zoom must be a number, got a " + typeof args["zoom"]

View file

@ -302,9 +302,7 @@
</div>
</NextButton>
</div>
<TagHint
tags={selectedPreset.preset.tags}
/>
<TagHint tags={selectedPreset.preset.tags} />
</TitledPanel>
{:else if _globalFilter?.length > 0 && _globalFilter?.length > checkedOfGlobalFilters}
<Tr t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.safetyCheck} cls="mx-12" />

View file

@ -48,7 +48,9 @@
let selectedTags: UploadableTag[]
let changedProperties = undefined
$: changedProperties = TagUtils.changeAsProperties(And.construct(selectedTags)?.asChange(tags?.data ?? {}) ?? [])
$: changedProperties = TagUtils.changeAsProperties(
And.construct(selectedTags)?.asChange(tags?.data ?? {}) ?? []
)
let isHardDelete = undefined
$: isHardDelete = changedProperties[DeleteConfig.deleteReasonKey] !== undefined
@ -58,7 +60,9 @@
}
currentState = "applying"
let actionToTake: OsmChangeAction
const changedProperties = TagUtils.changeAsProperties(And.construct(selectedTags)?.asChange(tags?.data ?? {}))
const changedProperties = TagUtils.changeAsProperties(
And.construct(selectedTags)?.asChange(tags?.data ?? {})
)
const deleteReason = changedProperties[DeleteConfig.deleteReasonKey]
if (deleteReason) {
let softDeletionTags: UploadableTag

View file

@ -37,21 +37,19 @@
{#if tagRenderings.length > 0}
<div class="mb-8">
<AccordionSingle>
<div slot="header">
{#if headerTr}
<TagRenderingAnswer {tags} {layer} config={headerTr} {state} {selectedElement} />
{:else}
{header}
{/if}
</div>
<div slot="header">
{#if headerTr}
<TagRenderingAnswer {tags} {layer} config={headerTr} {state} {selectedElement} />
{:else}
{header}
{/if}
</div>
{#each tagRenderings as config (config.id)}
{#if config.IsKnown($tags) && (config.condition === undefined || config.condition.matchesProperties($tags))}
<TagRenderingEditableDynamic {tags} {config} {state} {selectedElement} {layer} />
{/if}
{/each}
</AccordionSingle>
{#if config.IsKnown($tags) && (config.condition === undefined || config.condition.matchesProperties($tags))}
<TagRenderingEditableDynamic {tags} {config} {state} {selectedElement} {layer} />
{/if}
{/each}
</AccordionSingle>
</div>
{/if}

View file

@ -119,6 +119,7 @@
}, 50)
}
</script>
{#if $loginEnabled}
<div
bind:this={questionboxElem}

View file

@ -23,7 +23,7 @@
if (config === undefined) {
console.error("TagRenderingAnswer: Config is undefined")
throw ("Config is undefined in tagRenderingAnswer")
throw "Config is undefined in tagRenderingAnswer"
}
let trs: Store<{ then: Translation; icon?: string; iconClass?: string }[]> = tags.mapD((tags) =>
Utils.NoNull(config?.GetRenderValues(tags))

View file

@ -23,11 +23,21 @@
export let getCountry = () => "?"
onMount(() => {
console.log("Setting selected unit based on country", getCountry(), "and upstream value:", upstreamValue.data)
console.log(
"Setting selected unit based on country",
getCountry(),
"and upstream value:",
upstreamValue.data
)
if (upstreamValue.data === undefined || upstreamValue.data === "") {
// Init the selected unit
let denomination: Denomination = unit.getDefaultDenomination(getCountry)
console.log("Found denom", denomination.canonical, "available denominations are:", unit.denominations.map(denom => denom.canonical))
console.log(
"Found denom",
denomination.canonical,
"available denominations are:",
unit.denominations.map((denom) => denom.canonical)
)
selectedUnit.setData(denomination.canonical)
}
})

View file

@ -16,17 +16,16 @@
export let state: SpecialVisualizationState
let searchTerm = state.searchState.searchTerm
let recentThemes = state.userRelatedState.recentlyVisitedThemes.value.map((themes) => {
const recent = themes.filter((th) => th !== state.theme.id).slice(0, 6)
const deduped: MinimalThemeInformation[] = []
for (const theme of recent) {
if (deduped.some(th => th.id === theme.id)) {
continue
}
deduped.push(theme)
const recent = themes.filter((th) => th !== state.theme.id).slice(0, 6)
const deduped: MinimalThemeInformation[] = []
for (const theme of recent) {
if (deduped.some((th) => th.id === theme.id)) {
continue
}
return deduped
deduped.push(theme)
}
)
return deduped
})
let themeResults = state.searchState.themeSuggestions
const t = Translations.t.general.search

View file

@ -79,7 +79,13 @@ export class ImageVisualisations {
const estimated = tags.mapD((tags) =>
AllImageProviders.estimateNumberOfImages(tags, imagePrefixes)
)
return new SvelteUIElement(ImageCarousel, { state, tags, images, estimated, feature })
return new SvelteUIElement(ImageCarousel, {
state,
tags,
images,
estimated,
feature,
})
},
},
{

View file

@ -1,4 +1,8 @@
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import {
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import Constants from "../../Models/Constants"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Feature } from "geojson"

View file

@ -80,23 +80,22 @@ export class SettingsVisualisations {
group: "settings",
docs: "Shows the current state of storage",
args: [],
constr: function(state: SpecialVisualizationState): SvelteUIElement {
constr: function (state: SpecialVisualizationState): SvelteUIElement {
const data = {}
for (const key in localStorage) {
data[key] = localStorage[key]
}
const tags = new UIEventSource(data)
navigator.storage.estimate().then(estimate => {
navigator.storage.estimate().then((estimate) => {
data["__usage:current:bytes"] = estimate.usage
data["__usage:current:human"] = Utils.toHumanByteSize(estimate.usage)
data["__usage:quota:bytes"] = estimate.quota
data["__usage:quota:human"] = Utils.toHumanByteSize(estimate.quota)
tags.ping()
})
return new SvelteUIElement(AllTagsPanel, { state, tags })
}
},
},
{
funcName: "clear_caches",

View file

@ -29,13 +29,13 @@ class QuestionViz implements SpecialVisualizationSvelte {
},
{
name: "blacklisted-labels",
doc: "One or more ';'-separated labels of questions which should _not_ be included. Note that the questionbox which is added by default will blacklist 'hidden'"
doc: "One or more ';'-separated labels of questions which should _not_ be included. Note that the questionbox which is added by default will blacklist 'hidden'",
},
{
name: "show_all",
default: "user-preference",
doc: "Either `no`, `yes` or `user-preference`. Indicates if all questions should be shown at once"
}
doc: "Either `no`, `yes` or `user-preference`. Indicates if all questions should be shown at once",
},
]
svelteBased = true
group: "default"
@ -69,7 +69,7 @@ class QuestionViz implements SpecialVisualizationSvelte {
state,
onlyForLabels: labels,
notForLabels: blacklist,
showAllQuestionsAtOnce
showAllQuestionsAtOnce,
})
}
}

View file

@ -24,8 +24,8 @@ export class WebAndCommunicationSpecialVisualisations {
{
name: "key",
doc: "The attribute-name containing the link",
required: true
}
required: true,
},
],
constr(
@ -35,7 +35,7 @@ export class WebAndCommunicationSpecialVisualisations {
): BaseUIElement {
const key = argument[0]
return new SvelteUIElement(FediverseLink, { key, tags, state })
}
},
},
{
funcName: "wikipedia",
@ -45,8 +45,8 @@ export class WebAndCommunicationSpecialVisualisations {
{
name: "keyToShowWikipediaFor",
doc: "Use the wikidata entry from this key to show the wikipedia article for. Multiple keys can be given (separated by ';'), in which case the first matching value is used",
defaultValue: "wikidata;wikipedia"
}
defaultValue: "wikidata;wikipedia",
},
],
needsUrls: [...Wikidata.neededUrls, ...Wikipedia.neededUrls],
@ -59,9 +59,9 @@ export class WebAndCommunicationSpecialVisualisations {
return tags[key]?.split(";")?.map((id) => id.trim()) ?? []
})
return new SvelteUIElement(WikipediaPanel, {
wikiIds
wikiIds,
})
}
},
},
{
funcName: "wikidata_label",
@ -72,8 +72,8 @@ export class WebAndCommunicationSpecialVisualisations {
{
name: "keyToShowWikidataFor",
doc: "Use the wikidata entry from this key to show the label",
defaultValue: "wikidata"
}
defaultValue: "wikidata",
},
],
needsUrls: Wikidata.neededUrls,
example:
@ -87,7 +87,7 @@ export class WebAndCommunicationSpecialVisualisations {
)
return wikidataIds?.[0]
})
const entry = id.bind(id => Wikidata.LoadWikidataEntry(id))
const entry = id.bind((id) => Wikidata.LoadWikidataEntry(id))
return new VariableUiElement(
entry.map((e) => {
@ -96,8 +96,9 @@ export class WebAndCommunicationSpecialVisualisations {
}
const response = <WikidataResponse>e["success"]
return Translation.fromMap(response.labels)
}))
}
})
)
},
},
new MapillaryLinkVis(),
{
@ -108,29 +109,29 @@ export class WebAndCommunicationSpecialVisualisations {
{
name: "to",
doc: "Who to send the email to?",
required: true
required: true,
},
{
name: "subject",
doc: "The subject of the email",
required: true
required: true,
},
{
name: "body",
doc: "The text in the email",
required: true
required: true,
},
{
name: "button_text",
doc: "The text shown on the button in the UI",
required: true
}
required: true,
},
],
constr(__, tags, args) {
return new SvelteUIElement(SendEmail, { args, tags })
}
},
},
{
funcName: "link",
@ -140,29 +141,29 @@ export class WebAndCommunicationSpecialVisualisations {
{
name: "text",
doc: "Text to be shown",
required: true
required: true,
},
{
name: "href",
doc: "The URL to link to. Note that this will be URI-encoded before ",
required: true
required: true,
},
{
name: "class",
doc: "CSS-classes to add to the element"
doc: "CSS-classes to add to the element",
},
{
name: "download",
doc: "Expects a string which denotes the filename to download the contents of `href` into. If set, this link will act as a download-button."
doc: "Expects a string which denotes the filename to download the contents of `href` into. If set, this link will act as a download-button.",
},
{
name: "arialabel",
doc: "If set, this text will be used as aria-label"
doc: "If set, this text will be used as aria-label",
},
{
name: "icon",
doc: "If set, show this icon next to the link. You might want to combine this with `class: button`"
}
doc: "If set, show this icon next to the link. You might want to combine this with `class: button`",
},
],
constr(
@ -184,10 +185,10 @@ export class WebAndCommunicationSpecialVisualisations {
download: tagSource.map((tags) => Utils.SubstituteKeys(download, tags)),
ariaLabel: tagSource.map((tags) => Utils.SubstituteKeys(ariaLabel, tags)),
newTab: new ImmutableStore(newTab),
icon: tagSource.map((tags) => Utils.SubstituteKeys(icon, tags))
icon: tagSource.map((tags) => Utils.SubstituteKeys(icon, tags)),
}).setSpan()
}
}
},
},
]
}
}

View file

@ -1,7 +1,11 @@
import { FixedUiElement } from "./Base/FixedUiElement"
import BaseUIElement from "./BaseUIElement"
import { default as FeatureTitle } from "./Popup/Title.svelte"
import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"
import {
RenderingSpecification,
SpecialVisualization,
SpecialVisualizationState,
} from "./SpecialVisualization"
import { HistogramViz } from "./Popup/HistogramViz"
import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
import { MultiApplyViz } from "./Popup/MultiApplyViz"
@ -36,11 +40,8 @@ import { UISpecialVisualisations } from "./SpecialVisualisations/UISpecialVisual
import { SettingsVisualisations } from "./SpecialVisualisations/SettingsVisualisations"
import { ReviewSpecialVisualisations } from "./SpecialVisualisations/ReviewSpecialVisualisations"
import { DataImportSpecialVisualisations } from "./SpecialVisualisations/DataImportSpecialVisualisations"
import TagrenderingManipulationSpecialVisualisations
from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations"
import {
WebAndCommunicationSpecialVisualisations
} from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations"
import TagrenderingManipulationSpecialVisualisations from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations"
import { WebAndCommunicationSpecialVisualisations } from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations"
import ClearGPSHistory from "./BigComponents/ClearGPSHistory.svelte"
import AllFeaturesStatistics from "./Statistics/AllFeaturesStatistics.svelte"

View file

@ -16,7 +16,9 @@
export let layer: LayerConfig
export let state: ThemeViewState
let bbox = state.mapProperties.bounds
let elements: Store<Feature[]> = bbox.mapD(bbox => state.perLayer.get(layer.id).GetFeaturesWithin(bbox))
let elements: Store<Feature[]> = bbox.mapD((bbox) =>
state.perLayer.get(layer.id).GetFeaturesWithin(bbox)
)
let trs = layer.tagRenderings.filter((tr) => tr.question)
</script>

View file

@ -63,7 +63,7 @@
return "offline"
}
}),
message: osmApi
message: osmApi,
})
}
@ -91,7 +91,7 @@
}
const files: string[] = s["success"]["allFiles"]
return "Contains " + (files.length ?? "no") + " files"
})
}),
})
}
{
@ -107,7 +107,7 @@
return "degraded"
}
}),
message: simpleMessage(testDownload(Constants.panoramax.url + "/api"))
message: simpleMessage(testDownload(Constants.panoramax.url + "/api")),
})
}
{
@ -123,7 +123,7 @@
return "degraded"
}
}),
message: simpleMessage(testDownload(Constants.GeoIpServer + "/ip"))
message: simpleMessage(testDownload(Constants.GeoIpServer + "/ip")),
})
}
@ -142,7 +142,7 @@
}
return "degraded"
}),
message: simpleMessage(status)
message: simpleMessage(status),
})
}
@ -161,7 +161,7 @@
}
return "online"
}),
message: simpleMessage(status)
message: simpleMessage(status),
})
}
@ -200,7 +200,7 @@
const json = JSON.stringify(s["success"], null, " ")
return "Database is " + Math.floor(timediffDays) + " days out of sync\n\n" + json
})
}),
})
}
@ -213,7 +213,7 @@
layer: "food",
z: 14,
x: 8848,
y: 5828
y: 5828,
})
)
services.push({
@ -224,7 +224,7 @@
}
return "online"
}),
message: new ImmutableStore("See SettingUpPSQL.md to fix")
message: new ImmutableStore("See SettingUpPSQL.md to fix"),
})
}
@ -243,7 +243,7 @@
}
return "degraded"
}),
message: status.map((s) => JSON.stringify(s))
message: status.map((s) => JSON.stringify(s)),
})
}
@ -262,7 +262,7 @@
return "online"
}
return "degraded"
})
}),
})
}
@ -281,7 +281,7 @@
}
return "degraded"
}),
message: simpleMessage(status)
message: simpleMessage(status),
})
}
@ -307,7 +307,7 @@
return "online"
}),
message: simpleMessage(status)
message: simpleMessage(status),
})
}
}
@ -320,7 +320,7 @@
return "online"
}
return "offline"
})
}),
})
services.push({
@ -331,35 +331,33 @@
}
// This code will break in the future. Time to blame past me!
const response = JSON.parse(r["error"].substring("other error: , ".length))
if (response.message === "\"images\" is required") {
if (response.message === '"images" is required') {
// Actual expected behaviour
return "online"
}
console.log("R", response)
return "offline"
})
}),
})
}
{
services.push({
name: "Version Control Server (Forgéjo)",
status: testDownload("https://source.mapcomplete.org", true).mapD(r => {
status: testDownload("https://source.mapcomplete.org", true).mapD((r) => {
if (r["success"]) {
return "online"
}
return "offline"
})
}),
})
services.push({
name: "Translation service (Weblate)",
status: testDownload("https://translate.mapcomplete.org", true).mapD(r => {
status: testDownload("https://translate.mapcomplete.org", true).mapD((r) => {
if (r["success"]) {
return "online"
}
return "offline"
})
}),
})
}
@ -433,7 +431,6 @@
let now = Math.round(new Date().getTime() / 1000)
let twoDaysAgo = now - 2 * 24 * 60 * 60
let lastHour = now - 60 * 60
</script>
<h1>MapComplete status indicators</h1>
@ -457,16 +454,21 @@
<h3>Panoramax & OSM.fr Blurring service</h3>
<a href="https://status.thibaultmol.link/status/panoramax">Panoramax.MapComplete.org status page</a>
<a href="https://munin.openstreetmap.fr/osm37.openstreetmap.fr/blur.vm.openstreetmap.fr/index.html#system"
target="_blank" rel="noopener">
<a
href="https://munin.openstreetmap.fr/osm37.openstreetmap.fr/blur.vm.openstreetmap.fr/index.html#system"
target="_blank"
rel="noopener"
>
See more statistics for the blurring service
</a>
<img
style="width: 80rem"
src={`https://munin.openstreetmap.fr/munin-cgi/munin-cgi-graph/osm37.openstreetmap.fr/blur.vm.openstreetmap.fr/nvidia_gpu_power-pinpoint=${twoDaysAgo},${now}.png?&lower_limit=&upper_limit=&size_x=800&size_y=400`} />
src={`https://munin.openstreetmap.fr/munin-cgi/munin-cgi-graph/osm37.openstreetmap.fr/blur.vm.openstreetmap.fr/nvidia_gpu_power-pinpoint=${twoDaysAgo},${now}.png?&lower_limit=&upper_limit=&size_x=800&size_y=400`}
/>
<img
style="width: 80rem"
src={`https://munin.openstreetmap.fr/munin-cgi/munin-cgi-graph/osm37.openstreetmap.fr/blur.vm.openstreetmap.fr/nvidia_gpu_power-pinpoint=${lastHour},${now}.png?&lower_limit=&upper_limit=&size_x=800&size_y=400`} />
src={`https://munin.openstreetmap.fr/munin-cgi/munin-cgi-graph/osm37.openstreetmap.fr/blur.vm.openstreetmap.fr/nvidia_gpu_power-pinpoint=${lastHour},${now}.png?&lower_limit=&upper_limit=&size_x=800&size_y=400`}
/>
<button
on:click={() => {
fetch(Constants.ErrorReportServer, {

View file

@ -1,5 +1,4 @@
<script lang="ts">
import { onMount } from "svelte"
export let imageInfo
@ -8,32 +7,25 @@
let container: HTMLElement
onMount(() => {
console.log("Creating viewer...")
const features = [
{
"type": "Feature",
"properties": { "name": "trap" },
"geometry": {
"coordinates": [
3.742395038713312,
51.05237592785801
],
"type": "Point"
}
}
type: "Feature",
properties: { name: "trap" },
geometry: {
coordinates: [3.742395038713312, 51.05237592785801],
type: "Point",
},
},
]
const viewer = new PhotoSphereViewerWrapper(container, imageInfo, features)
// console.log(panorama, container)
})
</script>
<head>
<link rel="stylesheet" href="./node_modules/pannellum/build/pannellum.css">
<link rel="stylesheet" href="./node_modules/pannellum/build/pannellum.css" />
</head>
<div bind:this={container} class="h-screen w-screen border" style="height: 500px"></div>
<div bind:this={container} class="h-screen w-screen border" style="height: 500px" />

View file

@ -165,7 +165,6 @@ export class Translation extends BaseUIElement {
* Which language will be effectively used for the given language of choice?
*/
public actualLanguage(language: string): "*" | string | undefined {
const txt = this.translations[language]
if (txt !== undefined) {
return language