UX: add proper delete dialog, add option to report images
This commit is contained in:
parent
8690ad35bb
commit
5b618dc367
18 changed files with 334 additions and 176 deletions
|
@ -596,10 +596,33 @@
|
||||||
"seeNearby": "Browse nearby pictures",
|
"seeNearby": "Browse nearby pictures",
|
||||||
"title": "Nearby streetview imagery"
|
"title": "Nearby streetview imagery"
|
||||||
},
|
},
|
||||||
|
"panoramax": {
|
||||||
|
"deletionRequested": "The report has been sent. A moderator will look to it shortly",
|
||||||
|
"freeform": "Is there other relevant information?",
|
||||||
|
"otherFreeform": "Please specify why this image should be removed:",
|
||||||
|
"placeholder": "Explain why the picture should be deleted",
|
||||||
|
"report": {
|
||||||
|
"blur_excess": "To much is blurred, making the picture useless",
|
||||||
|
"blur_missing": "A face or license plate is not blurred in this picture",
|
||||||
|
"copyright": "The picture contains copyrighted content",
|
||||||
|
"inappropriate": "This picture is inappropriate (it contains nudity, calls for hate or is not streetview)",
|
||||||
|
"mislocated": "The picture is from a different location",
|
||||||
|
"other": "Another reason, please specify",
|
||||||
|
"picture_low_quality": "The picture is of low quality",
|
||||||
|
"privacy": "The picture shows a private property"
|
||||||
|
},
|
||||||
|
"requestDeletion": "Request picture deletion",
|
||||||
|
"title": "Why should this image be permanently deleted?"
|
||||||
|
},
|
||||||
"pleaseLogin": "Please log in to add a picture",
|
"pleaseLogin": "Please log in to add a picture",
|
||||||
"processing": "The server is processing your image",
|
"processing": "The server is processing your image",
|
||||||
"respectPrivacy": "Do not upload from Google Maps, Google Streetview or other copyrighted sources.",
|
"respectPrivacy": "Do not upload from Google Maps, Google Streetview or other copyrighted sources.",
|
||||||
"toBig": "Your image is too large as it is {actual_size}. Please use images of at most {max_size}",
|
"toBig": "Your image is too large as it is {actual_size}. Please use images of at most {max_size}",
|
||||||
|
"unlink": {
|
||||||
|
"button": "Unlink picture",
|
||||||
|
"explanation": "By unlinking this image, this picture will not be shown anymore with this object. It will still appear in the nearby-images and possibly other objects.",
|
||||||
|
"title": "Unlink this image?"
|
||||||
|
},
|
||||||
"upload": {
|
"upload": {
|
||||||
"failReasons": "You might have lost connection to the internet",
|
"failReasons": "You might have lost connection to the internet",
|
||||||
"failReasonsAdvanced": "Alternatively, make sure your browser and extensions do not block third-party API's.",
|
"failReasonsAdvanced": "Alternatively, make sure your browser and extensions do not block third-party API's.",
|
||||||
|
|
14
package-lock.json
generated
14
package-lock.json
generated
|
@ -65,7 +65,7 @@
|
||||||
"opening_hours": "^3.6.0",
|
"opening_hours": "^3.6.0",
|
||||||
"osm-auth": "^2.5.0",
|
"osm-auth": "^2.5.0",
|
||||||
"osmtogeojson": "^3.0.0-beta.5",
|
"osmtogeojson": "^3.0.0-beta.5",
|
||||||
"panoramax-js": "^0.3.10",
|
"panoramax-js": "^0.4.7",
|
||||||
"panzoom": "^9.4.3",
|
"panzoom": "^9.4.3",
|
||||||
"papaparse": "^5.3.1",
|
"papaparse": "^5.3.1",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
|
@ -16128,9 +16128,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/panoramax-js": {
|
"node_modules/panoramax-js": {
|
||||||
"version": "0.3.10",
|
"version": "0.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/panoramax-js/-/panoramax-js-0.3.10.tgz",
|
"resolved": "https://registry.npmjs.org/panoramax-js/-/panoramax-js-0.4.7.tgz",
|
||||||
"integrity": "sha512-ZI9gH98FB3RFWYy69Evsv6vWA+crwhlsdiY8KiZgXAdVYnW7C1YzuQg/Mls546ZHh8/WHj1GMwfe8w5UU6OcFg==",
|
"integrity": "sha512-Lai4IXbxQ/sDBUyl11zgoL7D+4s7YErPPgvGjWj5oZJBjsBFMLnai+du8WcVvRYrZNIDKCGk1vPLsmIvFsR4rw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ogcapi-js/features": "^1.1.1",
|
"@ogcapi-js/features": "^1.1.1",
|
||||||
"@ogcapi-js/shared": "^1.1.1",
|
"@ogcapi-js/shared": "^1.1.1",
|
||||||
|
@ -32312,9 +32312,9 @@
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
},
|
},
|
||||||
"panoramax-js": {
|
"panoramax-js": {
|
||||||
"version": "0.3.10",
|
"version": "0.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/panoramax-js/-/panoramax-js-0.3.10.tgz",
|
"resolved": "https://registry.npmjs.org/panoramax-js/-/panoramax-js-0.4.7.tgz",
|
||||||
"integrity": "sha512-ZI9gH98FB3RFWYy69Evsv6vWA+crwhlsdiY8KiZgXAdVYnW7C1YzuQg/Mls546ZHh8/WHj1GMwfe8w5UU6OcFg==",
|
"integrity": "sha512-Lai4IXbxQ/sDBUyl11zgoL7D+4s7YErPPgvGjWj5oZJBjsBFMLnai+du8WcVvRYrZNIDKCGk1vPLsmIvFsR4rw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@ogcapi-js/features": "^1.1.1",
|
"@ogcapi-js/features": "^1.1.1",
|
||||||
"@ogcapi-js/shared": "^1.1.1",
|
"@ogcapi-js/shared": "^1.1.1",
|
||||||
|
|
|
@ -212,7 +212,7 @@
|
||||||
"opening_hours": "^3.6.0",
|
"opening_hours": "^3.6.0",
|
||||||
"osm-auth": "^2.5.0",
|
"osm-auth": "^2.5.0",
|
||||||
"osmtogeojson": "^3.0.0-beta.5",
|
"osmtogeojson": "^3.0.0-beta.5",
|
||||||
"panoramax-js": "^0.3.10",
|
"panoramax-js": "^0.4.7",
|
||||||
"panzoom": "^9.4.3",
|
"panzoom": "^9.4.3",
|
||||||
"papaparse": "^5.3.1",
|
"papaparse": "^5.3.1",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
|
|
|
@ -1462,6 +1462,10 @@ input[type="range"].range-lg::-moz-range-thumb {
|
||||||
margin-right: 4rem;
|
margin-right: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mb-4 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mt-4 {
|
.mt-4 {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -1478,10 +1482,6 @@ input[type="range"].range-lg::-moz-range-thumb {
|
||||||
margin-bottom: 4rem;
|
margin-bottom: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mb-4 {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-1 {
|
.ml-1 {
|
||||||
margin-left: 0.25rem;
|
margin-left: 0.25rem;
|
||||||
}
|
}
|
||||||
|
@ -1698,14 +1698,14 @@ input[type="range"].range-lg::-moz-range-thumb {
|
||||||
height: 6rem;
|
height: 6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-full {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-screen {
|
.h-screen {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-full {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.h-fit {
|
.h-fit {
|
||||||
height: -webkit-fit-content;
|
height: -webkit-fit-content;
|
||||||
height: -moz-fit-content;
|
height: -moz-fit-content;
|
||||||
|
@ -2157,6 +2157,11 @@ input[type="range"].range-lg::-moz-range-thumb {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.max-w-max {
|
||||||
|
max-width: -webkit-max-content;
|
||||||
|
max-width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
.max-w-fit {
|
.max-w-fit {
|
||||||
max-width: -webkit-fit-content;
|
max-width: -webkit-fit-content;
|
||||||
max-width: -moz-fit-content;
|
max-width: -moz-fit-content;
|
||||||
|
@ -2576,18 +2581,18 @@ input[type="range"].range-lg::-moz-range-thumb {
|
||||||
margin-bottom: calc(0px * var(--tw-space-y-reverse));
|
margin-bottom: calc(0px * var(--tw-space-y-reverse));
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
|
||||||
--tw-space-x-reverse: 0;
|
|
||||||
margin-right: calc(1rem * var(--tw-space-x-reverse));
|
|
||||||
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
|
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
|
||||||
--tw-space-x-reverse: 0;
|
--tw-space-x-reverse: 0;
|
||||||
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
|
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
|
||||||
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
|
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
--tw-space-x-reverse: 0;
|
||||||
|
margin-right: calc(1rem * var(--tw-space-x-reverse));
|
||||||
|
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
|
||||||
|
}
|
||||||
|
|
||||||
.space-x-3 > :not([hidden]) ~ :not([hidden]) {
|
.space-x-3 > :not([hidden]) ~ :not([hidden]) {
|
||||||
--tw-space-x-reverse: 0;
|
--tw-space-x-reverse: 0;
|
||||||
margin-right: calc(0.75rem * var(--tw-space-x-reverse));
|
margin-right: calc(0.75rem * var(--tw-space-x-reverse));
|
||||||
|
@ -4108,6 +4113,10 @@ input[type="range"].range-lg::-moz-range-thumb {
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-start {
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
.text-xl {
|
.text-xl {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
line-height: 1.75rem;
|
line-height: 1.75rem;
|
||||||
|
@ -5612,12 +5621,6 @@ svg.apply-fill path {
|
||||||
|
|
||||||
/************************* LEGACY MARKER - CLEANUP BELOW ********************************/
|
/************************* LEGACY MARKER - CLEANUP BELOW ********************************/
|
||||||
|
|
||||||
.slideshow-item img {
|
|
||||||
/* Legacy: should be replace when the image element is ported to Svelte*/
|
|
||||||
height: var(--image-carousel-height);
|
|
||||||
width: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-height {
|
.animate-height {
|
||||||
/* Legacy: should be replaced by headlessui disclosure in time */
|
/* Legacy: should be replaced by headlessui disclosure in time */
|
||||||
transition: max-height 0.5s ease-in-out;
|
transition: max-height 0.5s ease-in-out;
|
||||||
|
|
|
@ -66,8 +66,9 @@ export default class AllImageProviders {
|
||||||
return AllImageProviders.genericImageProvider
|
return AllImageProviders.genericImageProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly _cachedImageStores: Record<string, Store<ProvidedImage[]>> = {}
|
||||||
/**
|
/**
|
||||||
* Tries to extract all image data for this image
|
* Tries to extract all image data for this image. Cachedon tags?.data?.id
|
||||||
*/
|
*/
|
||||||
public static LoadImagesFor(
|
public static LoadImagesFor(
|
||||||
tags: Store<Record<string, string>>,
|
tags: Store<Record<string, string>>,
|
||||||
|
@ -76,6 +77,10 @@ export default class AllImageProviders {
|
||||||
if (tags?.data?.id === undefined) {
|
if (tags?.data?.id === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
const id = tags?.data?.id
|
||||||
|
if(this._cachedImageStores[id]){
|
||||||
|
return this._cachedImageStores[id]
|
||||||
|
}
|
||||||
|
|
||||||
const source = new UIEventSource([])
|
const source = new UIEventSource([])
|
||||||
const allSources: Store<ProvidedImage[]>[] = []
|
const allSources: Store<ProvidedImage[]>[] = []
|
||||||
|
@ -93,6 +98,7 @@ export default class AllImageProviders {
|
||||||
source.set(dedup)
|
source.set(dedup)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
this._cachedImageStores[id] = source
|
||||||
return source
|
return source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,4 +88,12 @@ export default abstract class ImageProvider {
|
||||||
}): Promise<LicenseInfo>
|
}): Promise<LicenseInfo>
|
||||||
|
|
||||||
public abstract apiUrls(): string[]
|
public abstract apiUrls(): string[]
|
||||||
|
|
||||||
|
public static async offerImageAsDownload(image: ProvidedImage){
|
||||||
|
const response = await fetch(image.url_hd ?? image.url)
|
||||||
|
const blob = await response.blob()
|
||||||
|
Utils.offerContentsAsDownloadableFile(blob, new URL(image.url).pathname.split("/").at(-1), {
|
||||||
|
mimetype: "image/jpg",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,11 +138,12 @@ export default class PanoramaxImageProvider extends ImageProvider {
|
||||||
}
|
}
|
||||||
return data?.some(
|
return data?.some(
|
||||||
(img) =>
|
(img) =>
|
||||||
img?.status !== undefined && img?.status !== "ready" && img?.status !== "broken"
|
img?.status !== undefined && img?.status !== "ready" && img?.status !== "broken" && img?.status !== "hidden"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Stores.Chronic(1500, () => hasLoading(source.data)).addCallback((_) => {
|
Stores.Chronic(1500, () => hasLoading(source.data)).addCallback((_) => {
|
||||||
|
console.log("Testing panoramax URLS again as some were loading", source.data, hasLoading(source.data))
|
||||||
super.getRelevantUrlsFor(tags, prefixes).then((data) => {
|
super.getRelevantUrlsFor(tags, prefixes).then((data) => {
|
||||||
source.set(data)
|
source.set(data)
|
||||||
return !hasLoading(data)
|
return !hasLoading(data)
|
||||||
|
@ -168,6 +169,17 @@ export default class PanoramaxImageProvider extends ImageProvider {
|
||||||
public apiUrls(): string[] {
|
public apiUrls(): string[] {
|
||||||
return ["https://panoramax.mapcomplete.org", "https://panoramax.xyz"]
|
return ["https://panoramax.mapcomplete.org", "https://panoramax.xyz"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static getPanoramaxInstance (host: string){
|
||||||
|
host = new URL(host).host
|
||||||
|
if(new URL(this.defaultPanoramax.host).host === host){
|
||||||
|
return this.defaultPanoramax
|
||||||
|
}
|
||||||
|
if(new URL(this.xyz.host).host === host){
|
||||||
|
return this.xyz
|
||||||
|
}
|
||||||
|
return new Panoramax(host)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PanoramaxUploader implements ImageUploader {
|
export class PanoramaxUploader implements ImageUploader {
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative" style="z-index: 50">
|
<div class="relative" style="z-index: 39">
|
||||||
<div
|
<div
|
||||||
class="sidebar-unit absolute {menuPosition} collapsable normal-background button-unstyled"
|
class="sidebar-unit absolute {menuPosition} collapsable normal-background button-unstyled"
|
||||||
class:transition-background={hideBackground}
|
class:transition-background={hideBackground}
|
||||||
|
|
|
@ -7,6 +7,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export let fullscreen: boolean = false
|
export let fullscreen: boolean = false
|
||||||
|
export let bodyPadding = "p-4 md:p-5 "
|
||||||
|
export let shown: UIEventSource<boolean>
|
||||||
|
export let dismissable = true
|
||||||
|
/**
|
||||||
|
* Default: 50
|
||||||
|
*/
|
||||||
|
export let zIndex : string = "z-50"
|
||||||
|
|
||||||
const shared =
|
const shared =
|
||||||
"in-page normal-background dark:bg-gray-800 rounded-lg border-gray-200 dark:border-gray-700 border-gray-200 dark:border-gray-700 divide-gray-200 dark:divide-gray-700 shadow-md"
|
"in-page normal-background dark:bg-gray-800 rounded-lg border-gray-200 dark:border-gray-700 border-gray-200 dark:border-gray-700 divide-gray-200 dark:divide-gray-700 shadow-md"
|
||||||
|
@ -14,19 +21,16 @@
|
||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
defaultClass = shared
|
defaultClass = shared
|
||||||
}
|
}
|
||||||
let dialogClass = "fixed top-0 start-0 end-0 h-modal inset-0 z-50 w-full p-4 flex"
|
let dialogClass = "fixed top-0 start-0 end-0 h-modal inset-0 w-full p-4 flex "+zIndex
|
||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
dialogClass += " h-full-child"
|
dialogClass += " h-full-child"
|
||||||
}
|
}
|
||||||
export let bodyPadding = "p-4 md:p-5 "
|
|
||||||
let bodyClass = bodyPadding + " h-full space-y-4 flex-1 overflow-y-auto overscroll-contain"
|
let bodyClass = bodyPadding + " h-full space-y-4 flex-1 overflow-y-auto overscroll-contain"
|
||||||
|
|
||||||
let headerClass = "flex justify-between items-center p-2 px-4 md:px-5 rounded-t-lg"
|
let headerClass = "flex justify-between items-center p-2 px-4 md:px-5 rounded-t-lg"
|
||||||
if (!$$slots.header) {
|
if (!$$slots.header) {
|
||||||
headerClass = "hidden"
|
headerClass = "hidden"
|
||||||
}
|
}
|
||||||
export let shown: UIEventSource<boolean>
|
|
||||||
export let dismissable = true
|
|
||||||
let _shown = false
|
let _shown = false
|
||||||
shown.addCallbackAndRun((sh) => {
|
shown.addCallbackAndRun((sh) => {
|
||||||
_shown = sh
|
_shown = sh
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
export let expanded = false
|
export let expanded = false
|
||||||
export let noBorder = false
|
export let noBorder = false
|
||||||
|
export let contentClass = noBorder ? "normal-background" : "low-interaction rounded-b p-2"
|
||||||
let defaultClass: string = undefined
|
let defaultClass: string = undefined
|
||||||
if (noBorder) {
|
if (noBorder) {
|
||||||
defaultClass = "unstyled w-full flex-grow"
|
defaultClass = "unstyled w-full flex-grow"
|
||||||
|
@ -14,7 +15,7 @@
|
||||||
<span slot="header" class={!noBorder ? "w-full p-2 text-base" : "w-full"}>
|
<span slot="header" class={!noBorder ? "w-full p-2 text-base" : "w-full"}>
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
</span>
|
</span>
|
||||||
<div class="low-interaction rounded-b p-2">
|
<div class={contentClass}>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
import Loading from "../Base/Loading.svelte"
|
import Loading from "../Base/Loading.svelte"
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations"
|
||||||
import Tr from "../Base/Tr.svelte"
|
import Tr from "../Base/Tr.svelte"
|
||||||
|
import DotMenu from "../Base/DotMenu.svelte"
|
||||||
|
|
||||||
export let image: Partial<ProvidedImage>
|
export let image: Partial<ProvidedImage>
|
||||||
let fallbackImage: string = undefined
|
let fallbackImage: string = undefined
|
||||||
|
@ -36,12 +37,12 @@
|
||||||
if (!shown) {
|
if (!shown) {
|
||||||
previewedImage.set(undefined)
|
previewedImage.set(undefined)
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
onDestroy(
|
onDestroy(
|
||||||
previewedImage.addCallbackAndRun((previewedImage) => {
|
previewedImage.addCallbackAndRun((previewedImage) => {
|
||||||
showBigPreview.set(previewedImage?.id === image.id)
|
showBigPreview.set(previewedImage?.id === image.id)
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
function highlight(entered: boolean = true) {
|
function highlight(entered: boolean = true) {
|
||||||
|
@ -73,6 +74,7 @@
|
||||||
<div style="height: 80vh">
|
<div style="height: 80vh">
|
||||||
<ImageOperations {image}>
|
<ImageOperations {image}>
|
||||||
<slot name="preview-action" />
|
<slot name="preview-action" />
|
||||||
|
<slot name="dot-menu-actions" slot="dot-menu-actions" />
|
||||||
</ImageOperations>
|
</ImageOperations>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute top-4 right-4">
|
<div class="absolute top-4 right-4">
|
||||||
|
@ -85,7 +87,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Popup>
|
</Popup>
|
||||||
{#if image.status !== undefined && image.status !== "ready"}
|
{#if image.status !== undefined && image.status !== "ready" && image.status !== "hidden"}
|
||||||
<div class="flex h-full flex-col justify-center">
|
<div class="flex h-full flex-col justify-center">
|
||||||
<Loading>
|
<Loading>
|
||||||
<Tr t={Translations.t.image.processing} />
|
<Tr t={Translations.t.image.processing} />
|
||||||
|
@ -98,6 +100,11 @@
|
||||||
on:mouseenter={() => highlight()}
|
on:mouseenter={() => highlight()}
|
||||||
on:mouseleave={() => highlight(false)}
|
on:mouseleave={() => highlight(false)}
|
||||||
>
|
>
|
||||||
|
{#if $$slots["dot-menu-actions"]}
|
||||||
|
<DotMenu dotsPosition="top-0 left-0 absolute" hideBackground>
|
||||||
|
<slot name="dot-menu-actions" />
|
||||||
|
</DotMenu>
|
||||||
|
{/if}
|
||||||
<img
|
<img
|
||||||
bind:this={imgEl}
|
bind:this={imgEl}
|
||||||
on:load={() => (loaded = true)}
|
on:load={() => (loaded = true)}
|
||||||
|
@ -122,6 +129,8 @@
|
||||||
<MagnifyingGlassPlusIcon class="h-8 w-8 cursor-zoom-in pl-3 pb-3" color="white" />
|
<MagnifyingGlassPlusIcon class="h-8 w-8 cursor-zoom-in pl-3 pb-3" color="white" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute bottom-0 left-0">
|
<div class="absolute bottom-0 left-0">
|
||||||
<ImageAttribution {image} {attributionFormat} />
|
<ImageAttribution {image} {attributionFormat} />
|
||||||
|
|
188
src/UI/Image/DeletableImage.svelte
Normal file
188
src/UI/Image/DeletableImage.svelte
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import ImageProvider from "../../Logic/ImageProviders/ImageProvider"
|
||||||
|
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||||
|
|
||||||
|
import Popup from "../Base/Popup.svelte"
|
||||||
|
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||||
|
import NextButton from "../Base/NextButton.svelte"
|
||||||
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
import AttributedImage from "./AttributedImage.svelte"
|
||||||
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
|
import Dropdown from "../Base/Dropdown.svelte"
|
||||||
|
import { REPORT_REASONS, ReportReason } from "panoramax-js"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
import PanoramaxImageProvider from "../../Logic/ImageProviders/Panoramax"
|
||||||
|
import Translations from "../i18n/Translations"
|
||||||
|
import Tr from "../Base/Tr.svelte"
|
||||||
|
import { TrashIcon } from "@babeard/svelte-heroicons/mini"
|
||||||
|
import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||||
|
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
|
||||||
|
import { Tag } from "../../Logic/Tags/Tag"
|
||||||
|
|
||||||
|
export let image: ProvidedImage
|
||||||
|
export let state: SpecialVisualizationState
|
||||||
|
export let tags: UIEventSource<Record<string, string>>
|
||||||
|
let showDeleteDialog = new UIEventSource(false)
|
||||||
|
onDestroy(showDeleteDialog.addCallbackAndRunD(shown => {
|
||||||
|
if (shown) {
|
||||||
|
state.previewedImage.set(undefined)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
let reportReason = new UIEventSource<ReportReason>(REPORT_REASONS[0])
|
||||||
|
let reportFreeText = new UIEventSource<string>(undefined)
|
||||||
|
let reported = new UIEventSource<boolean>(false)
|
||||||
|
|
||||||
|
async function requestDeletion() {
|
||||||
|
if (reportReason.data === "other" && !reportFreeText.data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const panoramax = PanoramaxImageProvider.getPanoramaxInstance(image.host)
|
||||||
|
const url = window.location.href
|
||||||
|
const imageInfo = await panoramax.imageInfo(image.id)
|
||||||
|
let reporter_email: string = undefined
|
||||||
|
const userdetails = state.userRelatedState.osmConnection.userDetails
|
||||||
|
if (userdetails.data.loggedIn) {
|
||||||
|
reporter_email = userdetails.data.name + "@openstreetmap.org"
|
||||||
|
}
|
||||||
|
|
||||||
|
await panoramax.report({
|
||||||
|
picture_id: image.id,
|
||||||
|
issue: reportReason.data,
|
||||||
|
sequence_id: imageInfo.collection,
|
||||||
|
reporter_comments: (reportFreeText.data ?? "") + "\n\n" + "Reported from " + url,
|
||||||
|
reporter_email,
|
||||||
|
})
|
||||||
|
reported.set(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unlink() {
|
||||||
|
await state?.changes?.applyAction(
|
||||||
|
new ChangeTagAction(tags.data.id,
|
||||||
|
new Tag(image.key, ""),
|
||||||
|
tags.data, {
|
||||||
|
changeType: "delete-image",
|
||||||
|
theme: state.theme.id,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = Translations.t.image.panoramax
|
||||||
|
const tu = Translations.t.image.unlink
|
||||||
|
const placeholder = t.placeholder.current
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<Popup shown={showDeleteDialog}>
|
||||||
|
<Tr slot="header" t={tu.title} />
|
||||||
|
|
||||||
|
<div class="flex flex-col sm:flex-row gap-x-4">
|
||||||
|
<img class="w-32 sm:w-64" src={image.url} />
|
||||||
|
<div>
|
||||||
|
<div class="flex flex-col justify-between h-full">
|
||||||
|
<Tr t={tu.explanation} />
|
||||||
|
{#if $reported}
|
||||||
|
<Tr cls="thanks p-2" t={t.deletionRequested} />
|
||||||
|
{:else if image.provider.name === "panoramax"}
|
||||||
|
<div class="my-4">
|
||||||
|
<AccordionSingle noBorder>
|
||||||
|
<div slot="header" class="text-sm flex">Report inappropriate picture</div>
|
||||||
|
<div class="interactive p-2 flex flex-col">
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
<Tr t={t.title} />
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<Dropdown value={reportReason} cls="w-full mt-2">
|
||||||
|
{#each REPORT_REASONS as reason}
|
||||||
|
<option value={reason}>
|
||||||
|
{#if t.report[reason]}
|
||||||
|
<Tr t={t.report[reason]} />
|
||||||
|
{:else}
|
||||||
|
{reason}
|
||||||
|
{/if}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
|
{#if $reportReason === "other" && !$reportFreeText}
|
||||||
|
<Tr cls="font-bold" t={t.otherFreeform} />
|
||||||
|
{:else}
|
||||||
|
<Tr t={t.freeform} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
class="w-full"
|
||||||
|
bind:value={$reportFreeText}
|
||||||
|
inputmode={"text"}
|
||||||
|
placeholder={$placeholder}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button class="primary self-end" class:disabled={$reportReason === "other" && !$reportFreeText}
|
||||||
|
on:click={() => requestDeletion()}>
|
||||||
|
<Tr t={t.requestDeletion} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</AccordionSingle>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div slot="footer" class="flex justify-end flex-wrap">
|
||||||
|
<button on:click={() => showDeleteDialog.set(false)}>
|
||||||
|
<Tr t={Translations.t.general.cancel} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<NextButton clss={"primary "+($reported ? "disabled" : "") } on:click={() => unlink()}>
|
||||||
|
<TrashIcon class="w-6 h-6 mr-2" />
|
||||||
|
<Tr t={tu.button} />
|
||||||
|
</NextButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</Popup>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="w-fit shrink-0 relative"
|
||||||
|
style="scroll-snap-align: start"
|
||||||
|
>
|
||||||
|
<div class="relative bg-gray-200 max-w-max flex items-center">
|
||||||
|
|
||||||
|
<AttributedImage
|
||||||
|
imgClass="carousel-max-height"
|
||||||
|
{image}
|
||||||
|
{state}
|
||||||
|
previewedImage={state?.previewedImage}
|
||||||
|
>
|
||||||
|
|
||||||
|
<svelte:fragment slot="dot-menu-actions">
|
||||||
|
|
||||||
|
<button on:click={() => ImageProvider.offerImageAsDownload(image)}>
|
||||||
|
<DownloadIcon />
|
||||||
|
<Tr t={Translations.t.general.download.downloadImage} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={() => showDeleteDialog.set(true)}
|
||||||
|
class="flex items-center"
|
||||||
|
>
|
||||||
|
<TrashIcon />
|
||||||
|
<Tr t={tu.button} />
|
||||||
|
</button>
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
|
||||||
|
</AttributedImage>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(.carousel-max-height) {
|
||||||
|
max-height: var(--image-carousel-height);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
27
src/UI/Image/ImageCarousel.svelte
Normal file
27
src/UI/Image/ImageCarousel.svelte
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Store, UIEventSource } from "../../Logic/UIEventSource.js"
|
||||||
|
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||||
|
import AttributedImage from "../Image/AttributedImage.svelte"
|
||||||
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
|
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||||
|
import DeleteImage from "./DeleteImage"
|
||||||
|
import Popup from "../Base/Popup.svelte"
|
||||||
|
import TitledPanel from "../Base/TitledPanel.svelte"
|
||||||
|
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||||
|
import NextButton from "../Base/NextButton.svelte"
|
||||||
|
import DeletableImage from "./DeletableImage.svelte"
|
||||||
|
|
||||||
|
export let images: Store<ProvidedImage[]>
|
||||||
|
export let state: SpecialVisualizationState
|
||||||
|
export let tags: Store<Record<string, string>>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="flex w-full space-x-2 overflow-x-auto" style="scroll-snap-type: x proximity">
|
||||||
|
{#each $images as image (image.url)}
|
||||||
|
<DeletableImage {image} {state} {tags}/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
import { SlideShow } from "./SlideShow"
|
|
||||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
|
||||||
import Combine from "../Base/Combine"
|
|
||||||
import DeleteImage from "./DeleteImage"
|
|
||||||
import BaseUIElement from "../BaseUIElement"
|
|
||||||
import Toggle from "../Input/Toggle"
|
|
||||||
import ImageProvider, { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
|
||||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
|
||||||
import { Changes } from "../../Logic/Osm/Changes"
|
|
||||||
import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig"
|
|
||||||
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
|
|
||||||
theme: ThemeConfig
|
|
||||||
previewedImage?: UIEventSource<ProvidedImage>
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const uiElements = images.map(
|
|
||||||
(imageURLS: { key: string; url: string; provider: ImageProvider; id: string }[]) => {
|
|
||||||
const uiElements: BaseUIElement[] = []
|
|
||||||
for (const url of imageURLS) {
|
|
||||||
try {
|
|
||||||
let image: BaseUIElement = new SvelteUIElement(AttributedImage, {
|
|
||||||
image: url,
|
|
||||||
state,
|
|
||||||
previewedImage: state?.previewedImage,
|
|
||||||
}).SetClass("h-full")
|
|
||||||
|
|
||||||
if (url.key !== undefined) {
|
|
||||||
image = new Combine([
|
|
||||||
image,
|
|
||||||
new DeleteImage(url.key, tags, state).SetClass(
|
|
||||||
"delete-image-marker absolute top-0 left-0 pl-3"
|
|
||||||
),
|
|
||||||
]).SetClass("relative")
|
|
||||||
}
|
|
||||||
image
|
|
||||||
.SetClass("w-full h-full block cursor-zoom-in low-interaction")
|
|
||||||
.SetStyle("min-width: 50px;")
|
|
||||||
uiElements.push(image)
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Could not generate image element for", url.url, "due to", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uiElements
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
super(
|
|
||||||
new SlideShow(uiElements).SetClass("w-full block w-full my-4"),
|
|
||||||
undefined,
|
|
||||||
uiElements.map((els) => els.length > 0)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,11 +3,11 @@
|
||||||
* The 'imageOperations' previews an image and offers some extra tools (e.g. download)
|
* The 'imageOperations' previews an image and offers some extra tools (e.g. download)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ImageProvider from "../../Logic/ImageProviders/ImageProvider"
|
||||||
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||||
import ImageAttribution from "./ImageAttribution.svelte"
|
import ImageAttribution from "./ImageAttribution.svelte"
|
||||||
import ImagePreview from "./ImagePreview.svelte"
|
import ImagePreview from "./ImagePreview.svelte"
|
||||||
import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid"
|
import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||||
import { Utils } from "../../Utils"
|
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge"
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import Loading from "../Base/Loading.svelte"
|
import Loading from "../Base/Loading.svelte"
|
||||||
|
@ -20,13 +20,6 @@
|
||||||
|
|
||||||
let isLoaded = new UIEventSource(false)
|
let isLoaded = new UIEventSource(false)
|
||||||
|
|
||||||
async function download() {
|
|
||||||
const response = await fetch(image.url_hd ?? image.url)
|
|
||||||
const blob = await response.blob()
|
|
||||||
Utils.offerContentsAsDownloadableFile(blob, new URL(image.url).pathname.split("/").at(-1), {
|
|
||||||
mimetype: "image/jpg",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={twMerge("relative h-full w-full", clss)}>
|
<div class={twMerge("relative h-full w-full", clss)}>
|
||||||
|
@ -40,13 +33,16 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DotMenu dotsPosition="top-0 left-0" dotsSize="w-8 h-8" hideBackground>
|
<DotMenu dotsPosition="top-0 left-0" dotsSize="w-8 h-8" hideBackground>
|
||||||
|
<slot name="dot-menu-actions">
|
||||||
<button
|
<button
|
||||||
class="no-image-background pointer-events-auto flex items-center"
|
class="no-image-background pointer-events-auto flex items-center"
|
||||||
on:click={() => download()}
|
on:click={() => ImageProvider.offerImageAsDownload(image)}
|
||||||
>
|
>
|
||||||
<DownloadIcon class="h-6 w-6 px-2 opacity-100" />
|
<DownloadIcon class="h-6 w-6 px-2 opacity-100" />
|
||||||
<Tr t={Translations.t.general.download.downloadImage} />
|
<Tr t={Translations.t.general.download.downloadImage} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
</slot>
|
||||||
</DotMenu>
|
</DotMenu>
|
||||||
<div
|
<div
|
||||||
class="pointer-events-none absolute bottom-0 left-0 flex w-full flex-wrap items-end justify-between"
|
class="pointer-events-none absolute bottom-0 left-0 flex w-full flex-wrap items-end justify-between"
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
import { Store } from "../../Logic/UIEventSource"
|
|
||||||
import BaseUIElement from "../BaseUIElement"
|
|
||||||
import { Utils } from "../../Utils"
|
|
||||||
import Combine from "../Base/Combine"
|
|
||||||
|
|
||||||
export class SlideShow extends BaseUIElement {
|
|
||||||
private readonly embeddedElements: Store<BaseUIElement[]>
|
|
||||||
|
|
||||||
constructor(embeddedElements: Store<BaseUIElement[]>) {
|
|
||||||
super()
|
|
||||||
this.embeddedElements = embeddedElements
|
|
||||||
this.SetStyle("scroll-snap-type: x mandatory; overflow-x: auto")
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InnerConstructElement(): HTMLElement {
|
|
||||||
const el = document.createElement("div")
|
|
||||||
el.style.minWidth = "min-content"
|
|
||||||
el.style.display = "flex"
|
|
||||||
el.style.justifyContent = "center"
|
|
||||||
this.embeddedElements.addCallbackAndRun((elements) => {
|
|
||||||
if (elements.length > 1) {
|
|
||||||
el.style.justifyContent = "unset"
|
|
||||||
}
|
|
||||||
|
|
||||||
while (el.firstChild) {
|
|
||||||
el.removeChild(el.lastChild)
|
|
||||||
}
|
|
||||||
|
|
||||||
elements = Utils.NoNull(elements).map((el) =>
|
|
||||||
new Combine([el])
|
|
||||||
.SetClass("block relative ml-1 bg-gray-200 m-1 rounded slideshow-item")
|
|
||||||
.SetStyle(
|
|
||||||
"min-width: 150px; width: max-content; height: var(--image-carousel-height);max-height: var(--image-carousel-height);scroll-snap-align: start;"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const element of elements ?? []) {
|
|
||||||
el.appendChild(element.ConstructElement())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const wrapper = document.createElement("div")
|
|
||||||
wrapper.style.maxWidth = "100%"
|
|
||||||
wrapper.style.overflowX = "auto"
|
|
||||||
wrapper.appendChild(el)
|
|
||||||
return wrapper
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,7 +15,6 @@ import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"
|
||||||
import { ImmutableStore, Store, Stores, UIEventSource } from "../Logic/UIEventSource"
|
import { ImmutableStore, Store, Stores, UIEventSource } from "../Logic/UIEventSource"
|
||||||
import AllTagsPanel from "./Popup/AllTagsPanel.svelte"
|
import AllTagsPanel from "./Popup/AllTagsPanel.svelte"
|
||||||
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"
|
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"
|
||||||
import { ImageCarousel } from "./Image/ImageCarousel"
|
|
||||||
import { VariableUiElement } from "./Base/VariableUIElement"
|
import { VariableUiElement } from "./Base/VariableUIElement"
|
||||||
import { Utils } from "../Utils"
|
import { Utils } from "../Utils"
|
||||||
import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"
|
import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"
|
||||||
|
@ -83,7 +82,6 @@ import DynLink from "./Base/DynLink.svelte"
|
||||||
import Locale from "./i18n/Locale"
|
import Locale from "./i18n/Locale"
|
||||||
import LanguageUtils from "../Utils/LanguageUtils"
|
import LanguageUtils from "../Utils/LanguageUtils"
|
||||||
import MarkdownUtils from "../Utils/MarkdownUtils"
|
import MarkdownUtils from "../Utils/MarkdownUtils"
|
||||||
import ArrowDownTray from "@babeard/svelte-heroicons/mini/ArrowDownTray"
|
|
||||||
import Trash from "@babeard/svelte-heroicons/mini/Trash"
|
import Trash from "@babeard/svelte-heroicons/mini/Trash"
|
||||||
import NothingKnown from "./Popup/NothingKnown.svelte"
|
import NothingKnown from "./Popup/NothingKnown.svelte"
|
||||||
import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch"
|
import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch"
|
||||||
|
@ -96,6 +94,7 @@ import GroupedView from "./Popup/GroupedView.svelte"
|
||||||
import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||||
import NoteCommentElement from "./Popup/Notes/NoteCommentElement.svelte"
|
import NoteCommentElement from "./Popup/Notes/NoteCommentElement.svelte"
|
||||||
import FediverseLink from "./Popup/FediverseLink.svelte"
|
import FediverseLink from "./Popup/FediverseLink.svelte"
|
||||||
|
import ImageCarousel from "./Image/ImageCarousel.svelte"
|
||||||
|
|
||||||
class NearbyImageVis implements SpecialVisualization {
|
class NearbyImageVis implements SpecialVisualization {
|
||||||
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
||||||
|
@ -712,11 +711,8 @@ export default class SpecialVisualizations {
|
||||||
if (args.length > 0) {
|
if (args.length > 0) {
|
||||||
imagePrefixes = [].concat(...args.map((a) => a.split(",")))
|
imagePrefixes = [].concat(...args.map((a) => a.split(",")))
|
||||||
}
|
}
|
||||||
return new ImageCarousel(
|
const images = AllImageProviders.LoadImagesFor(tags, imagePrefixes)
|
||||||
AllImageProviders.LoadImagesFor(tags, imagePrefixes),
|
return new SvelteUIElement(ImageCarousel, { state, tags, images })
|
||||||
tags,
|
|
||||||
state,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -647,11 +647,6 @@ svg.apply-fill path {
|
||||||
|
|
||||||
/************************* LEGACY MARKER - CLEANUP BELOW ********************************/
|
/************************* LEGACY MARKER - CLEANUP BELOW ********************************/
|
||||||
|
|
||||||
.slideshow-item img {
|
|
||||||
/* Legacy: should be replace when the image element is ported to Svelte*/
|
|
||||||
height: var(--image-carousel-height);
|
|
||||||
width: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-height {
|
.animate-height {
|
||||||
/* Legacy: should be replaced by headlessui disclosure in time */
|
/* Legacy: should be replaced by headlessui disclosure in time */
|
||||||
|
|
Loading…
Reference in a new issue