forked from MapComplete/MapComplete
Feature: add zoomable image when clicked
This commit is contained in:
parent
c65ccdbc24
commit
d7413e8228
20 changed files with 481 additions and 181 deletions
71
package-lock.json
generated
71
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "mapcomplete",
|
||||
"version": "0.35.1",
|
||||
"version": "0.36.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mapcomplete",
|
||||
"version": "0.35.1",
|
||||
"version": "0.36.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@rgossiaux/svelte-headlessui": "^1.0.2",
|
||||
|
@ -47,6 +47,7 @@
|
|||
"opening_hours": "^3.6.0",
|
||||
"osm-auth": "^2.2.0",
|
||||
"osmtogeojson": "^3.0.0-beta.5",
|
||||
"panzoom": "^9.4.3",
|
||||
"papaparse": "^5.3.1",
|
||||
"pic4carto": "^2.1.15",
|
||||
"prompt-sync": "^4.2.0",
|
||||
|
@ -4502,6 +4503,14 @@
|
|||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/amator": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/amator/-/amator-1.1.0.tgz",
|
||||
"integrity": "sha512-V5+aH8pe+Z3u/UG3L3pG3BaFQGXAyXHVQDroRwjPHdh08bcUEchAVsU1MCuJSCaU5o60wTK6KaE6te5memzgYw==",
|
||||
"dependencies": {
|
||||
"bezier-easing": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-colors": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
|
||||
|
@ -4791,6 +4800,11 @@
|
|||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"node_modules/bezier-easing": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
|
||||
"integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig=="
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
|
@ -9111,6 +9125,11 @@
|
|||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ngraph.events": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-1.2.2.tgz",
|
||||
"integrity": "sha512-JsUbEOzANskax+WSYiAPETemLWYXmixuPAlmZmhIbIj6FH/WDgEGCGnRwUQBK0GjOnVm8Ui+e5IJ+5VZ4e32eQ=="
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.31.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.31.0.tgz",
|
||||
|
@ -9496,6 +9515,16 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/panzoom": {
|
||||
"version": "9.4.3",
|
||||
"resolved": "https://registry.npmjs.org/panzoom/-/panzoom-9.4.3.tgz",
|
||||
"integrity": "sha512-xaxCpElcRbQsUtIdwlrZA90P90+BHip4Vda2BC8MEb4tkI05PmR6cKECdqUCZ85ZvBHjpI9htJrZBxV5Gp/q/w==",
|
||||
"dependencies": {
|
||||
"amator": "^1.1.0",
|
||||
"ngraph.events": "^1.2.2",
|
||||
"wheel": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/papaparse": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.2.tgz",
|
||||
|
@ -13191,6 +13220,11 @@
|
|||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/wheel": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wheel/-/wheel-1.0.0.tgz",
|
||||
"integrity": "sha512-XiCMHibOiqalCQ+BaNSwRoZ9FDTAvOsXxGHXChBugewDj7HC8VBIER71dEOiRH1fSdLbRCQzngKTSiZ06ZQzeA=="
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
@ -16778,6 +16812,14 @@
|
|||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"amator": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/amator/-/amator-1.1.0.tgz",
|
||||
"integrity": "sha512-V5+aH8pe+Z3u/UG3L3pG3BaFQGXAyXHVQDroRwjPHdh08bcUEchAVsU1MCuJSCaU5o60wTK6KaE6te5memzgYw==",
|
||||
"requires": {
|
||||
"bezier-easing": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"ansi-colors": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
|
||||
|
@ -16990,6 +17032,11 @@
|
|||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"bezier-easing": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
|
||||
"integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
|
@ -20236,6 +20283,11 @@
|
|||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true
|
||||
},
|
||||
"ngraph.events": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-1.2.2.tgz",
|
||||
"integrity": "sha512-JsUbEOzANskax+WSYiAPETemLWYXmixuPAlmZmhIbIj6FH/WDgEGCGnRwUQBK0GjOnVm8Ui+e5IJ+5VZ4e32eQ=="
|
||||
},
|
||||
"node-abi": {
|
||||
"version": "3.31.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.31.0.tgz",
|
||||
|
@ -20523,6 +20575,16 @@
|
|||
"p-limit": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"panzoom": {
|
||||
"version": "9.4.3",
|
||||
"resolved": "https://registry.npmjs.org/panzoom/-/panzoom-9.4.3.tgz",
|
||||
"integrity": "sha512-xaxCpElcRbQsUtIdwlrZA90P90+BHip4Vda2BC8MEb4tkI05PmR6cKECdqUCZ85ZvBHjpI9htJrZBxV5Gp/q/w==",
|
||||
"requires": {
|
||||
"amator": "^1.1.0",
|
||||
"ngraph.events": "^1.2.2",
|
||||
"wheel": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"papaparse": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.2.tgz",
|
||||
|
@ -23227,6 +23289,11 @@
|
|||
"webidl-conversions": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"wheel": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wheel/-/wheel-1.0.0.tgz",
|
||||
"integrity": "sha512-XiCMHibOiqalCQ+BaNSwRoZ9FDTAvOsXxGHXChBugewDj7HC8VBIER71dEOiRH1fSdLbRCQzngKTSiZ06ZQzeA=="
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
|
|
@ -133,6 +133,7 @@
|
|||
"opening_hours": "^3.6.0",
|
||||
"osm-auth": "^2.2.0",
|
||||
"osmtogeojson": "^3.0.0-beta.5",
|
||||
"panzoom": "^9.4.3",
|
||||
"papaparse": "^5.3.1",
|
||||
"pic4carto": "^2.1.15",
|
||||
"prompt-sync": "^4.2.0",
|
||||
|
|
|
@ -729,6 +729,14 @@ video {
|
|||
bottom: 0px;
|
||||
}
|
||||
|
||||
.right-4 {
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.top-4 {
|
||||
top: 1rem;
|
||||
}
|
||||
|
||||
.right-1\/3 {
|
||||
right: 33.333333%;
|
||||
}
|
||||
|
@ -769,6 +777,10 @@ video {
|
|||
float: left;
|
||||
}
|
||||
|
||||
.m-8 {
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
.m-4 {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
@ -781,10 +793,6 @@ video {
|
|||
margin: 0px;
|
||||
}
|
||||
|
||||
.m-8 {
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
.m-2 {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
@ -797,10 +805,58 @@ video {
|
|||
margin: 0.125rem;
|
||||
}
|
||||
|
||||
.m-11 {
|
||||
margin: 2.75rem;
|
||||
}
|
||||
|
||||
.m-20 {
|
||||
margin: 5rem;
|
||||
}
|
||||
|
||||
.m-9 {
|
||||
margin: 2.25rem;
|
||||
}
|
||||
|
||||
.m-5 {
|
||||
margin: 1.25rem;
|
||||
}
|
||||
|
||||
.m-14 {
|
||||
margin: 3.5rem;
|
||||
}
|
||||
|
||||
.m-52 {
|
||||
margin: 13rem;
|
||||
}
|
||||
|
||||
.m-36 {
|
||||
margin: 9rem;
|
||||
}
|
||||
|
||||
.m-72 {
|
||||
margin: 18rem;
|
||||
}
|
||||
|
||||
.m-6 {
|
||||
margin: 1.5rem;
|
||||
}
|
||||
|
||||
.m-32 {
|
||||
margin: 8rem;
|
||||
}
|
||||
|
||||
.m-44 {
|
||||
margin: 11rem;
|
||||
}
|
||||
|
||||
.m-28 {
|
||||
margin: 7rem;
|
||||
}
|
||||
|
||||
.m-7 {
|
||||
margin: 1.75rem;
|
||||
}
|
||||
|
||||
.m-px {
|
||||
margin: 1px;
|
||||
}
|
||||
|
@ -845,6 +901,10 @@ video {
|
|||
margin-right: 3rem;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
@ -881,10 +941,6 @@ video {
|
|||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ml-1 {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
@ -1369,6 +1425,10 @@ video {
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.justify-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.gap-1 {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
@ -1679,6 +1739,10 @@ video {
|
|||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-white\/50 {
|
||||
background-color: rgb(255 255 255 / 0.5);
|
||||
}
|
||||
|
||||
.bg-red-400 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(248 113 113 / var(--tw-bg-opacity));
|
||||
|
@ -1796,6 +1860,10 @@ video {
|
|||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.pr-1 {
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.pl-3 {
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
@ -1804,10 +1872,6 @@ video {
|
|||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.pr-1 {
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.pb-10 {
|
||||
padding-bottom: 2.5rem;
|
||||
}
|
||||
|
@ -1981,6 +2045,10 @@ video {
|
|||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.opacity-100 {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
||||
|
@ -2082,6 +2150,12 @@ video {
|
|||
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
|
||||
}
|
||||
|
||||
.transition-colors {
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
.transition {
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, -webkit-transform, -webkit-filter, -webkit-backdrop-filter;
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
||||
|
@ -2090,10 +2164,8 @@ video {
|
|||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
.transition-colors {
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
.duration-200 {
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
|
||||
.ease-in-out {
|
||||
|
@ -2746,6 +2818,11 @@ a.link-underline {
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
.hover\:bg-white:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-indigo-200:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(199 210 254 / var(--tw-bg-opacity));
|
||||
|
|
|
@ -94,6 +94,8 @@ export class Imgur extends ImageProvider implements ImageUploader {
|
|||
|
||||
const descr: string = response.data.description ?? ""
|
||||
const data: any = {}
|
||||
const imgurData = response.data
|
||||
|
||||
for (const tag of descr.split("\n")) {
|
||||
const kv = tag.split(":")
|
||||
const k = kv[0]
|
||||
|
@ -104,6 +106,8 @@ export class Imgur extends ImageProvider implements ImageUploader {
|
|||
|
||||
licenseInfo.licenseShortName = data.license
|
||||
licenseInfo.artist = data.author
|
||||
licenseInfo.date = new Date(Number(imgurData.datetime) * 1000)
|
||||
licenseInfo.views = imgurData.views
|
||||
|
||||
return licenseInfo
|
||||
}
|
||||
|
|
|
@ -9,4 +9,6 @@ export class LicenseInfo {
|
|||
credit: string = ""
|
||||
description: string = ""
|
||||
informationLocation: URL = undefined
|
||||
date?: Date
|
||||
views?: number
|
||||
}
|
||||
|
|
|
@ -60,8 +60,8 @@ export class Mapillary extends ImageProvider {
|
|||
} = undefined, zoom: number = 17, pKey?: string) {
|
||||
const params = {
|
||||
focus: pKey === undefined ? "map" : "photo",
|
||||
lat: location.lat,
|
||||
lng: location.lon,
|
||||
lat: location?.lat,
|
||||
lng: location?.lon,
|
||||
z: location === undefined ? undefined : Math.max((zoom ?? 2) - 1, 1),
|
||||
pKey,
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
|
|||
import { Imgur } from "../Logic/ImageProviders/Imgur"
|
||||
import NearbyFeatureSource from "../Logic/FeatureSource/Sources/NearbyFeatureSource"
|
||||
import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource"
|
||||
import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider"
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -113,6 +114,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
readonly geolocation: GeoLocationHandler
|
||||
|
||||
readonly imageUploadManager: ImageUploadManager
|
||||
readonly previewedImage = new UIEventSource<ProvidedImage>(undefined)
|
||||
|
||||
readonly addNewPoint: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
||||
|
||||
|
@ -475,6 +477,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
() => {
|
||||
this.selectedElement.setData(undefined)
|
||||
this.guistate.closeAll()
|
||||
this.previewedImage.setData(undefined)
|
||||
this.focusOnMap()
|
||||
}
|
||||
)
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
/**
|
||||
* The slotted element will be shown on top, with a lower-opacity border
|
||||
*/
|
||||
const dispatch = createEventDispatcher<{ close }>()
|
||||
|
||||
export let extraClasses = "p-4 md:p-6"
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="absolute top-0 right-0 h-screen w-screen p-4 md:p-6"
|
||||
class={twMerge("absolute top-0 right-0 h-screen w-screen", extraClasses)}
|
||||
style="background-color: #00000088; z-index: 20"
|
||||
on:click={() => {
|
||||
dispatch("close")
|
||||
|
@ -33,9 +36,9 @@
|
|||
|
||||
<style>
|
||||
.content {
|
||||
height: calc(100vh - 2rem);
|
||||
height: 100%;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
overflow-x: hidden;
|
||||
box-shadow: 0 0 1rem #00000088;
|
||||
}
|
||||
</style>
|
||||
|
|
29
src/UI/Image/AttributedImage.svelte
Normal file
29
src/UI/Image/AttributedImage.svelte
Normal file
|
@ -0,0 +1,29 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* Shows an image with attribution
|
||||
*/
|
||||
import ImageAttribution from "./ImageAttribution.svelte"
|
||||
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
|
||||
|
||||
export let image: ProvidedImage
|
||||
let fallbackImage: string = undefined
|
||||
if (image.provider === Mapillary.singleton) {
|
||||
fallbackImage = "./assets/svg/blocked.svg"
|
||||
}
|
||||
|
||||
let imgEl: HTMLImageElement
|
||||
</script>
|
||||
|
||||
|
||||
<div class="relative">
|
||||
<img bind:this={imgEl} src={image.url} on:error={(event) => {
|
||||
if(fallbackImage){
|
||||
imgEl.src = fallbackImage
|
||||
}
|
||||
}}>
|
||||
|
||||
<div class="absolute bottom-0 left-0">
|
||||
<ImageAttribution {image}/>
|
||||
</div>
|
||||
</div>
|
|
@ -1,45 +0,0 @@
|
|||
import Combine from "../Base/Combine"
|
||||
import Attribution from "./Attribution"
|
||||
import Img from "../Base/Img"
|
||||
import ImageProvider from "../../Logic/ImageProviders/ImageProvider"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Feature } from "geojson"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
|
||||
export class AttributedImage extends Combine {
|
||||
constructor(imageInfo: {
|
||||
id: string,
|
||||
url: string;
|
||||
provider?: ImageProvider;
|
||||
date?: Date
|
||||
}, feature?: Feature) {
|
||||
let img: BaseUIElement
|
||||
img = new Img(imageInfo.url, false, {
|
||||
fallbackImage:
|
||||
imageInfo.provider === Mapillary.singleton ? "./assets/svg/blocked.svg" : undefined,
|
||||
})
|
||||
|
||||
let location: {
|
||||
lon: number,
|
||||
lat: number
|
||||
} = undefined
|
||||
if (feature) {
|
||||
|
||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||
location = { lon, lat }
|
||||
}
|
||||
let attr: BaseUIElement = undefined
|
||||
if (imageInfo.provider !== undefined) {
|
||||
attr = new Attribution(
|
||||
UIEventSource.FromPromise(imageInfo.provider?.DownloadAttribution(imageInfo.url)),
|
||||
imageInfo.provider?.SourceIcon(imageInfo.id, location),
|
||||
imageInfo.date,
|
||||
)
|
||||
}
|
||||
|
||||
super([img, attr])
|
||||
this.SetClass("block relative h-full")
|
||||
}
|
||||
}
|
66
src/UI/Image/ImageAttribution.svelte
Normal file
66
src/UI/Image/ImageAttribution.svelte
Normal file
|
@ -0,0 +1,66 @@
|
|||
<script lang="ts">
|
||||
import { LicenseInfo } from "../../Logic/ImageProviders/LicenseInfo"
|
||||
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import { EyeIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
|
||||
/**
|
||||
* A small element showing the attribution of a single image
|
||||
*/
|
||||
export let image: ProvidedImage
|
||||
let license: Store<LicenseInfo> = UIEventSource.FromPromise(image.provider?.DownloadAttribution(image.url))
|
||||
let icon = image.provider?.SourceIcon(image.id)?.SetClass("block h-8 w-8 pr-2")
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
{#if $license !== undefined}
|
||||
<div class="flex bg-black text-white text-sm p-0.5 pl-5 pr-3 rounded-lg no-images">
|
||||
|
||||
{#if icon !== undefined}
|
||||
<ToSvelte construct={icon} />
|
||||
{/if}
|
||||
|
||||
|
||||
<div class="flex flex-col">
|
||||
{#if $license.title}
|
||||
{#if $license.informationLocation}
|
||||
<a href={$license.informationLocation} target="_blank" rel="noopener nofollower">{$license.title}</a>
|
||||
{:else}
|
||||
$license.title
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if $license.artist}
|
||||
<div class="font-bold">
|
||||
{$license.artist}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex justify-between">
|
||||
|
||||
{#if $license.license !== undefined || $license.licenseShortName !== undefined}
|
||||
<div>
|
||||
{$license?.license ?? $license?.licenseShortName}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $license.date}
|
||||
<div>
|
||||
{$license.date.toLocaleDateString()}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if $license.views}
|
||||
<div class="flex justify-around self-center">
|
||||
<EyeIcon class="w-4 h-4 pr-1"/>
|
||||
{$license.views}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/if}
|
|
@ -1,21 +1,22 @@
|
|||
import { SlideShow } from "./SlideShow"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import Combine from "../Base/Combine"
|
||||
import DeleteImage from "./DeleteImage"
|
||||
import { AttributedImage } from "./AttributedImage"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import Toggle from "../Input/Toggle"
|
||||
import ImageProvider from "../../Logic/ImageProviders/ImageProvider"
|
||||
import ImageProvider, { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||
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"
|
||||
|
||||
export class ImageCarousel extends Toggle {
|
||||
constructor(
|
||||
images: Store<{ id:string, key: string; url: string; provider: ImageProvider }[]>,
|
||||
tags: Store<any>,
|
||||
state: { osmConnection?: OsmConnection; changes?: Changes; layout: LayoutConfig },
|
||||
state: { osmConnection?: OsmConnection; changes?: Changes; layout: LayoutConfig, previewedImage?: UIEventSource<ProvidedImage> },
|
||||
feature: Feature
|
||||
) {
|
||||
const uiElements = images.map(
|
||||
|
@ -23,7 +24,7 @@ export class ImageCarousel extends Toggle {
|
|||
const uiElements: BaseUIElement[] = []
|
||||
for (const url of imageURLS) {
|
||||
try {
|
||||
let image = new AttributedImage(url, feature)
|
||||
let image: BaseUIElement = new SvelteUIElement(AttributedImage, {image: url})
|
||||
|
||||
if (url.key !== undefined) {
|
||||
image = new Combine([
|
||||
|
@ -36,6 +37,7 @@ export class ImageCarousel extends Toggle {
|
|||
image
|
||||
.SetClass("w-full block")
|
||||
.SetStyle("min-width: 50px; background: grey;")
|
||||
.onClick(() => state.previewedImage.setData(url))
|
||||
uiElements.push(image)
|
||||
} catch (e) {
|
||||
console.error("Could not generate image element for", url.url, "due to", e)
|
||||
|
|
41
src/UI/Image/ImageOperations.svelte
Normal file
41
src/UI/Image/ImageOperations.svelte
Normal file
|
@ -0,0 +1,41 @@
|
|||
<script lang="ts">/**
|
||||
* The 'imageOperations' previews an image and offers some extra tools (e.g. download)
|
||||
*/
|
||||
|
||||
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||
import ImageAttribution from "./ImageAttribution.svelte"
|
||||
import ImagePreview from "./ImagePreview.svelte"
|
||||
import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
export let image: ProvidedImage
|
||||
|
||||
async function download() {
|
||||
const response = await fetch(image.url)
|
||||
const blob = await response.blob()
|
||||
Utils.offerContentsAsDownloadableFile(blob, new URL(image.url).pathname.split("/").at(-1), {
|
||||
mimetype: "image/jpg",
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full relative">
|
||||
<div class="absolute top-0 left-0">
|
||||
<ImagePreview image={image} />
|
||||
</div>
|
||||
<div class="absolute bottom-0 left-0 w-full pointer-events-none flex justify-between items-end">
|
||||
<div class="pointer-events-auto w-fit opacity-50 hover:opacity-100 transition-colors duration-200">
|
||||
<ImageAttribution image={image} />
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="no-image-background flex items-center pointer-events-auto bg-black opacity-50 hover:opacity-100 text-white transition-colors duration-200"
|
||||
on:click={() => download()}>
|
||||
<DownloadIcon class="w-6 h-6 px-2 opacity-100" />
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
29
src/UI/Image/ImagePreview.svelte
Normal file
29
src/UI/Image/ImagePreview.svelte
Normal file
|
@ -0,0 +1,29 @@
|
|||
<script lang="ts">
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
|
||||
/**
|
||||
* The image preview allows to drag and zoom in to the image
|
||||
*/
|
||||
import * as panzoom from "panzoom"
|
||||
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||
|
||||
export let image : ProvidedImage
|
||||
let panzoomInstance = undefined
|
||||
let panzoomEl: HTMLElement
|
||||
|
||||
$: {
|
||||
if (panzoomEl) {
|
||||
panzoomInstance = panzoom(panzoomEl, { bounds: true,
|
||||
boundsPadding: 1,
|
||||
minZoom: 1,
|
||||
maxZoom: 25
|
||||
})
|
||||
} else {
|
||||
panzoomInstance?.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<img bind:this={panzoomEl} src={image.url} />
|
||||
|
|
@ -1,21 +1,21 @@
|
|||
<script lang="ts">
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import type { OsmTags } from "../../Models/OsmFeature"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
import { AttributedImage } from "../Image/AttributedImage"
|
||||
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
|
||||
import LinkImageAction from "../../Logic/Osm/Actions/LinkImageAction"
|
||||
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
|
||||
import { Tag } from "../../Logic/Tags/Tag"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import type { Feature } from "geojson"
|
||||
import Translations from "../i18n/Translations"
|
||||
import SpecialTranslation from "./TagRendering/SpecialTranslation.svelte"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import type { OsmTags } from "../../Models/OsmFeature"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch"
|
||||
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
|
||||
import LinkImageAction from "../../Logic/Osm/Actions/LinkImageAction"
|
||||
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
|
||||
import { Tag } from "../../Logic/Tags/Tag"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import type { Feature } from "geojson"
|
||||
import Translations from "../i18n/Translations"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||
import AttributedImage from "./AttributedImage.svelte"
|
||||
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"
|
||||
|
||||
export let tags: Store<OsmTags>
|
||||
export let tags: Store<OsmTags>
|
||||
export let lon: number
|
||||
export let lat: number
|
||||
export let state: SpecialVisualizationState
|
||||
|
@ -28,12 +28,12 @@
|
|||
|
||||
const t = Translations.t.image.nearby
|
||||
const c = [lon, lat]
|
||||
let attributedImage = new AttributedImage({
|
||||
url: image.thumbUrl ?? image.pictureUrl,
|
||||
provider: AllImageProviders.byName(image.provider),
|
||||
date: new Date(image.date),
|
||||
const providedImage: ProvidedImage = {
|
||||
url: image.thumbUrl ?? image.pictureUrl,
|
||||
provider: AllImageProviders.byName(image.provider),
|
||||
date: new Date(image.date),
|
||||
id: Object.values(image.osmTags)[0]
|
||||
}, feature)
|
||||
}
|
||||
let distance = Math.round(
|
||||
GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c)
|
||||
)
|
||||
|
@ -64,7 +64,7 @@
|
|||
</script>
|
||||
|
||||
<div class="flex w-fit shrink-0 flex-col">
|
||||
<ToSvelte construct={attributedImage.SetClass("h-48 w-fit")} />
|
||||
<AttributedImage image={providedImage} />
|
||||
{#if linkable}
|
||||
<label>
|
||||
<input bind:checked={isLinked} type="checkbox" />
|
|
@ -18,6 +18,7 @@ import { RasterLayerPolygon } from "../Models/RasterLayers"
|
|||
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
|
||||
import { OsmTags } from "../Models/OsmFeature"
|
||||
import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource"
|
||||
import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider"
|
||||
|
||||
/**
|
||||
* The state needed to render a special Visualisation.
|
||||
|
@ -84,6 +85,8 @@ export interface SpecialVisualizationState {
|
|||
readonly availableLayers: Store<RasterLayerPolygon[]>
|
||||
|
||||
readonly imageUploadManager: ImageUploadManager
|
||||
|
||||
readonly previewedImage: UIEventSource<ProvidedImage>
|
||||
}
|
||||
|
||||
export interface SpecialVisualization {
|
||||
|
|
|
@ -61,8 +61,6 @@ import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"
|
|||
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
|
||||
import FediverseValidator from "./InputElement/Validators/FediverseValidator"
|
||||
import SendEmail from "./Popup/SendEmail.svelte"
|
||||
import NearbyImages from "./Popup/NearbyImages.svelte"
|
||||
import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte"
|
||||
import UploadImage from "./Image/UploadImage.svelte"
|
||||
import { Imgur } from "../Logic/ImageProviders/Imgur"
|
||||
import Constants from "../Models/Constants"
|
||||
|
@ -82,6 +80,8 @@ import OpenJosm from "./Base/OpenJosm.svelte"
|
|||
import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte"
|
||||
import MarkAsFavouriteMini from "./Popup/MarkAsFavouriteMini.svelte"
|
||||
import NextChangeViz from "./OpeningHours/NextChangeViz.svelte"
|
||||
import NearbyImages from "./Image/NearbyImages.svelte"
|
||||
import NearbyImagesCollapsed from "./Image/NearbyImagesCollapsed.svelte"
|
||||
|
||||
class NearbyImageVis implements SpecialVisualization {
|
||||
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
||||
|
|
|
@ -1,91 +1,92 @@
|
|||
<script lang="ts">
|
||||
import { Store, UIEventSource } from "../Logic/UIEventSource";
|
||||
import { Map as MlMap } from "maplibre-gl";
|
||||
import MaplibreMap from "./Map/MaplibreMap.svelte";
|
||||
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
|
||||
import MapControlButton from "./Base/MapControlButton.svelte";
|
||||
import ToSvelte from "./Base/ToSvelte.svelte";
|
||||
import If from "./Base/If.svelte";
|
||||
import { GeolocationControl } from "./BigComponents/GeolocationControl";
|
||||
import type { Feature } from "geojson";
|
||||
import SelectedElementView from "./BigComponents/SelectedElementView.svelte";
|
||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
||||
import Filterview from "./BigComponents/Filterview.svelte";
|
||||
import ThemeViewState from "../Models/ThemeViewState";
|
||||
import type { MapProperties } from "../Models/MapProperties";
|
||||
import Geosearch from "./BigComponents/Geosearch.svelte";
|
||||
import Translations from "./i18n/Translations";
|
||||
import { CogIcon, EyeIcon, HeartIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import Tr from "./Base/Tr.svelte";
|
||||
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
|
||||
import FloatOver from "./Base/FloatOver.svelte";
|
||||
import PrivacyPolicy from "./BigComponents/PrivacyPolicy";
|
||||
import Constants from "../Models/Constants";
|
||||
import TabbedGroup from "./Base/TabbedGroup.svelte";
|
||||
import UserRelatedState from "../Logic/State/UserRelatedState";
|
||||
import LoginToggle from "./Base/LoginToggle.svelte";
|
||||
import LoginButton from "./Base/LoginButton.svelte";
|
||||
import CopyrightPanel from "./BigComponents/CopyrightPanel";
|
||||
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte";
|
||||
import ModalRight from "./Base/ModalRight.svelte";
|
||||
import { Utils } from "../Utils";
|
||||
import Hotkeys from "./Base/Hotkeys";
|
||||
import { VariableUiElement } from "./Base/VariableUIElement";
|
||||
import SvelteUIElement from "./Base/SvelteUIElement";
|
||||
import OverlayToggle from "./BigComponents/OverlayToggle.svelte";
|
||||
import LevelSelector from "./BigComponents/LevelSelector.svelte";
|
||||
import ExtraLinkButton from "./BigComponents/ExtraLinkButton";
|
||||
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte";
|
||||
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte";
|
||||
import type { RasterLayerPolygon } from "../Models/RasterLayers";
|
||||
import { AvailableRasterLayers } from "../Models/RasterLayers";
|
||||
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte";
|
||||
import IfHidden from "./Base/IfHidden.svelte";
|
||||
import { onDestroy } from "svelte";
|
||||
import MapillaryLink from "./BigComponents/MapillaryLink.svelte";
|
||||
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte";
|
||||
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte";
|
||||
import StateIndicator from "./BigComponents/StateIndicator.svelte";
|
||||
import ShareScreen from "./BigComponents/ShareScreen.svelte";
|
||||
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte";
|
||||
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte";
|
||||
import Cross from "../assets/svg/Cross.svelte";
|
||||
import Summary from "./BigComponents/Summary.svelte";
|
||||
import LanguagePicker from "./InputElement/LanguagePicker.svelte";
|
||||
import Mastodon from "../assets/svg/Mastodon.svelte";
|
||||
import Bug from "../assets/svg/Bug.svelte";
|
||||
import Liberapay from "../assets/svg/Liberapay.svelte";
|
||||
import OpenJosm from "./Base/OpenJosm.svelte";
|
||||
import Min from "../assets/svg/Min.svelte";
|
||||
import Plus from "../assets/svg/Plus.svelte";
|
||||
import Filter from "../assets/svg/Filter.svelte";
|
||||
import Add from "../assets/svg/Add.svelte";
|
||||
import Statistics from "../assets/svg/Statistics.svelte";
|
||||
import Community from "../assets/svg/Community.svelte";
|
||||
import Download from "../assets/svg/Download.svelte";
|
||||
import Share from "../assets/svg/Share.svelte";
|
||||
import Favourites from "./Favourites/Favourites.svelte";
|
||||
import { Store, UIEventSource } from "../Logic/UIEventSource"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import MaplibreMap from "./Map/MaplibreMap.svelte"
|
||||
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
|
||||
import MapControlButton from "./Base/MapControlButton.svelte"
|
||||
import ToSvelte from "./Base/ToSvelte.svelte"
|
||||
import If from "./Base/If.svelte"
|
||||
import { GeolocationControl } from "./BigComponents/GeolocationControl"
|
||||
import type { Feature } from "geojson"
|
||||
import SelectedElementView from "./BigComponents/SelectedElementView.svelte"
|
||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
||||
import Filterview from "./BigComponents/Filterview.svelte"
|
||||
import ThemeViewState from "../Models/ThemeViewState"
|
||||
import type { MapProperties } from "../Models/MapProperties"
|
||||
import Geosearch from "./BigComponents/Geosearch.svelte"
|
||||
import Translations from "./i18n/Translations"
|
||||
import { CogIcon, EyeIcon, HeartIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import Tr from "./Base/Tr.svelte"
|
||||
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
|
||||
import FloatOver from "./Base/FloatOver.svelte"
|
||||
import PrivacyPolicy from "./BigComponents/PrivacyPolicy"
|
||||
import Constants from "../Models/Constants"
|
||||
import TabbedGroup from "./Base/TabbedGroup.svelte"
|
||||
import UserRelatedState from "../Logic/State/UserRelatedState"
|
||||
import LoginToggle from "./Base/LoginToggle.svelte"
|
||||
import LoginButton from "./Base/LoginButton.svelte"
|
||||
import CopyrightPanel from "./BigComponents/CopyrightPanel"
|
||||
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
|
||||
import ModalRight from "./Base/ModalRight.svelte"
|
||||
import { Utils } from "../Utils"
|
||||
import Hotkeys from "./Base/Hotkeys"
|
||||
import { VariableUiElement } from "./Base/VariableUIElement"
|
||||
import SvelteUIElement from "./Base/SvelteUIElement"
|
||||
import OverlayToggle from "./BigComponents/OverlayToggle.svelte"
|
||||
import LevelSelector from "./BigComponents/LevelSelector.svelte"
|
||||
import ExtraLinkButton from "./BigComponents/ExtraLinkButton"
|
||||
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"
|
||||
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
|
||||
import type { RasterLayerPolygon } from "../Models/RasterLayers"
|
||||
import { AvailableRasterLayers } from "../Models/RasterLayers"
|
||||
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
|
||||
import IfHidden from "./Base/IfHidden.svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
import MapillaryLink from "./BigComponents/MapillaryLink.svelte"
|
||||
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
|
||||
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
|
||||
import StateIndicator from "./BigComponents/StateIndicator.svelte"
|
||||
import ShareScreen from "./BigComponents/ShareScreen.svelte"
|
||||
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"
|
||||
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte"
|
||||
import Cross from "../assets/svg/Cross.svelte"
|
||||
import Summary from "./BigComponents/Summary.svelte"
|
||||
import LanguagePicker from "./InputElement/LanguagePicker.svelte"
|
||||
import Mastodon from "../assets/svg/Mastodon.svelte"
|
||||
import Bug from "../assets/svg/Bug.svelte"
|
||||
import Liberapay from "../assets/svg/Liberapay.svelte"
|
||||
import OpenJosm from "./Base/OpenJosm.svelte"
|
||||
import Min from "../assets/svg/Min.svelte"
|
||||
import Plus from "../assets/svg/Plus.svelte"
|
||||
import Filter from "../assets/svg/Filter.svelte"
|
||||
import Add from "../assets/svg/Add.svelte"
|
||||
import Statistics from "../assets/svg/Statistics.svelte"
|
||||
import Community from "../assets/svg/Community.svelte"
|
||||
import Download from "../assets/svg/Download.svelte"
|
||||
import Share from "../assets/svg/Share.svelte"
|
||||
import Favourites from "./Favourites/Favourites.svelte"
|
||||
import ImageOperations from "./Image/ImageOperations.svelte"
|
||||
|
||||
export let state: ThemeViewState
|
||||
export let state: ThemeViewState
|
||||
let layout = state.layout
|
||||
|
||||
let maplibremap: UIEventSource<MlMap> = state.map
|
||||
let selectedElement: UIEventSource<Feature> = state.selectedElement
|
||||
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer
|
||||
|
||||
let currentZoom = state.mapProperties.zoom;
|
||||
let showCrosshair = state.userRelatedState.showCrosshair;
|
||||
let arrowKeysWereUsed = state.mapProperties.lastKeyNavigation;
|
||||
let centerFeatures = state.closestFeatures.features;
|
||||
const selectedElementView = selectedElement.map(
|
||||
(selectedElement) => {
|
||||
// Svelte doesn't properly reload some of the legacy UI-elements
|
||||
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
||||
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
||||
const layer = selectedLayer.data;
|
||||
if (selectedElement === undefined || layer === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
let currentZoom = state.mapProperties.zoom
|
||||
let showCrosshair = state.userRelatedState.showCrosshair
|
||||
let arrowKeysWereUsed = state.mapProperties.lastKeyNavigation
|
||||
let centerFeatures = state.closestFeatures.features
|
||||
const selectedElementView = selectedElement.map(
|
||||
(selectedElement) => {
|
||||
// Svelte doesn't properly reload some of the legacy UI-elements
|
||||
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
||||
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
||||
const layer = selectedLayer.data
|
||||
if (selectedElement === undefined || layer === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) {
|
||||
return undefined
|
||||
|
@ -131,6 +132,7 @@
|
|||
rasterLayerName = l.properties.name
|
||||
}),
|
||||
)
|
||||
let previewedImage = state.previewedImage
|
||||
</script>
|
||||
|
||||
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
|
||||
|
@ -236,7 +238,8 @@
|
|||
<div class="pointer-events-auto interactive p-1">
|
||||
{#each $centerFeatures as feat, i (feat.properties.id)}
|
||||
<div class="flex">
|
||||
<b>{i+1}.</b><Summary {state} feature={feat}/>
|
||||
<b>{i + 1}.</b>
|
||||
<Summary {state} feature={feat} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
@ -281,6 +284,19 @@
|
|||
{/if}
|
||||
</LoginToggle>
|
||||
|
||||
<If condition={state.previewedImage.map(i => i!==undefined)}>
|
||||
<FloatOver on:close={() => state.previewedImage.setData(undefined)} extraClasses="">
|
||||
<div
|
||||
slot="close-button"
|
||||
class="absolute right-4 top-4 h-8 w-8 cursor-pointer rounded-full hover:bg-white bg-white/50 transition-colors duration-200"
|
||||
on:click={() => previewedImage.setData(undefined)}
|
||||
>
|
||||
<XCircleIcon />
|
||||
</div>
|
||||
<ImageOperations image={$previewedImage} />
|
||||
</FloatOver>
|
||||
</If>
|
||||
|
||||
<If
|
||||
condition={selectedElementView.map(
|
||||
(v) =>
|
||||
|
@ -495,12 +511,14 @@
|
|||
|
||||
<div class="flex" slot="title2">
|
||||
<HeartIcon class="h-6 w-6" />
|
||||
<Tr t={Translations.t.favouritePoi.tab}/>
|
||||
<Tr t={Translations.t.favouritePoi.tab} />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col m-2" slot="content2">
|
||||
<h3> <Tr t={Translations.t.favouritePoi.title}/></h3>
|
||||
<Favourites {state}/>
|
||||
<h3>
|
||||
<Tr t={Translations.t.favouritePoi.title} />
|
||||
</h3>
|
||||
<Favourites {state} />
|
||||
</div>
|
||||
<div class="flex" slot="title3">
|
||||
<Community class="h-6 w-6" />
|
||||
|
|
Loading…
Reference in a new issue