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",
|
"name": "mapcomplete",
|
||||||
"version": "0.35.1",
|
"version": "0.36.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mapcomplete",
|
"name": "mapcomplete",
|
||||||
"version": "0.35.1",
|
"version": "0.36.0",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rgossiaux/svelte-headlessui": "^1.0.2",
|
"@rgossiaux/svelte-headlessui": "^1.0.2",
|
||||||
|
@ -47,6 +47,7 @@
|
||||||
"opening_hours": "^3.6.0",
|
"opening_hours": "^3.6.0",
|
||||||
"osm-auth": "^2.2.0",
|
"osm-auth": "^2.2.0",
|
||||||
"osmtogeojson": "^3.0.0-beta.5",
|
"osmtogeojson": "^3.0.0-beta.5",
|
||||||
|
"panzoom": "^9.4.3",
|
||||||
"papaparse": "^5.3.1",
|
"papaparse": "^5.3.1",
|
||||||
"pic4carto": "^2.1.15",
|
"pic4carto": "^2.1.15",
|
||||||
"prompt-sync": "^4.2.0",
|
"prompt-sync": "^4.2.0",
|
||||||
|
@ -4502,6 +4503,14 @@
|
||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"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": {
|
"node_modules/ansi-colors": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
|
||||||
|
@ -4791,6 +4800,11 @@
|
||||||
"tweetnacl": "^0.14.3"
|
"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": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||||
|
@ -9111,6 +9125,11 @@
|
||||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/node-abi": {
|
||||||
"version": "3.31.0",
|
"version": "3.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.31.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.31.0.tgz",
|
||||||
|
@ -9496,6 +9515,16 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/papaparse": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.2.tgz",
|
||||||
|
@ -13191,6 +13220,11 @@
|
||||||
"node": ">=14"
|
"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": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
@ -16778,6 +16812,14 @@
|
||||||
"uri-js": "^4.2.2"
|
"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": {
|
"ansi-colors": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
|
||||||
|
@ -16990,6 +17032,11 @@
|
||||||
"tweetnacl": "^0.14.3"
|
"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": {
|
"binary-extensions": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||||
|
@ -20236,6 +20283,11 @@
|
||||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||||
"dev": true
|
"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": {
|
"node-abi": {
|
||||||
"version": "3.31.0",
|
"version": "3.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.31.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.31.0.tgz",
|
||||||
|
@ -20523,6 +20575,16 @@
|
||||||
"p-limit": "^3.0.2"
|
"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": {
|
"papaparse": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.2.tgz",
|
||||||
|
@ -23227,6 +23289,11 @@
|
||||||
"webidl-conversions": "^7.0.0"
|
"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": {
|
"which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|
|
@ -133,6 +133,7 @@
|
||||||
"opening_hours": "^3.6.0",
|
"opening_hours": "^3.6.0",
|
||||||
"osm-auth": "^2.2.0",
|
"osm-auth": "^2.2.0",
|
||||||
"osmtogeojson": "^3.0.0-beta.5",
|
"osmtogeojson": "^3.0.0-beta.5",
|
||||||
|
"panzoom": "^9.4.3",
|
||||||
"papaparse": "^5.3.1",
|
"papaparse": "^5.3.1",
|
||||||
"pic4carto": "^2.1.15",
|
"pic4carto": "^2.1.15",
|
||||||
"prompt-sync": "^4.2.0",
|
"prompt-sync": "^4.2.0",
|
||||||
|
|
|
@ -729,6 +729,14 @@ video {
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right-4 {
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-4 {
|
||||||
|
top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.right-1\/3 {
|
.right-1\/3 {
|
||||||
right: 33.333333%;
|
right: 33.333333%;
|
||||||
}
|
}
|
||||||
|
@ -769,6 +777,10 @@ video {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.m-8 {
|
||||||
|
margin: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.m-4 {
|
.m-4 {
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -781,10 +793,6 @@ video {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-8 {
|
|
||||||
margin: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.m-2 {
|
.m-2 {
|
||||||
margin: 0.5rem;
|
margin: 0.5rem;
|
||||||
}
|
}
|
||||||
|
@ -797,10 +805,58 @@ video {
|
||||||
margin: 0.125rem;
|
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 {
|
.m-6 {
|
||||||
margin: 1.5rem;
|
margin: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.m-32 {
|
||||||
|
margin: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-44 {
|
||||||
|
margin: 11rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-28 {
|
||||||
|
margin: 7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-7 {
|
||||||
|
margin: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.m-px {
|
.m-px {
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
}
|
}
|
||||||
|
@ -845,6 +901,10 @@ video {
|
||||||
margin-right: 3rem;
|
margin-right: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mb-4 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mt-4 {
|
.mt-4 {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -881,10 +941,6 @@ video {
|
||||||
margin-right: 0.25rem;
|
margin-right: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mb-4 {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-1 {
|
.ml-1 {
|
||||||
margin-left: 0.25rem;
|
margin-left: 0.25rem;
|
||||||
}
|
}
|
||||||
|
@ -1369,6 +1425,10 @@ video {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.justify-around {
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
.gap-1 {
|
.gap-1 {
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
@ -1679,6 +1739,10 @@ video {
|
||||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-white\/50 {
|
||||||
|
background-color: rgb(255 255 255 / 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
.bg-red-400 {
|
.bg-red-400 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(248 113 113 / var(--tw-bg-opacity));
|
background-color: rgb(248 113 113 / var(--tw-bg-opacity));
|
||||||
|
@ -1796,6 +1860,10 @@ video {
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pr-1 {
|
||||||
|
padding-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.pl-3 {
|
.pl-3 {
|
||||||
padding-left: 0.75rem;
|
padding-left: 0.75rem;
|
||||||
}
|
}
|
||||||
|
@ -1804,10 +1872,6 @@ video {
|
||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pr-1 {
|
|
||||||
padding-right: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pb-10 {
|
.pb-10 {
|
||||||
padding-bottom: 2.5rem;
|
padding-bottom: 2.5rem;
|
||||||
}
|
}
|
||||||
|
@ -1981,6 +2045,10 @@ video {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.opacity-100 {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.shadow {
|
.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: 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);
|
--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);
|
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 {
|
||||||
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, -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;
|
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-duration: 150ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transition-colors {
|
.duration-200 {
|
||||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
transition-duration: 200ms;
|
||||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
transition-duration: 150ms;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ease-in-out {
|
.ease-in-out {
|
||||||
|
@ -2746,6 +2818,11 @@ a.link-underline {
|
||||||
max-width: 100%;
|
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 {
|
.hover\:bg-indigo-200:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(199 210 254 / var(--tw-bg-opacity));
|
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 descr: string = response.data.description ?? ""
|
||||||
const data: any = {}
|
const data: any = {}
|
||||||
|
const imgurData = response.data
|
||||||
|
|
||||||
for (const tag of descr.split("\n")) {
|
for (const tag of descr.split("\n")) {
|
||||||
const kv = tag.split(":")
|
const kv = tag.split(":")
|
||||||
const k = kv[0]
|
const k = kv[0]
|
||||||
|
@ -104,6 +106,8 @@ export class Imgur extends ImageProvider implements ImageUploader {
|
||||||
|
|
||||||
licenseInfo.licenseShortName = data.license
|
licenseInfo.licenseShortName = data.license
|
||||||
licenseInfo.artist = data.author
|
licenseInfo.artist = data.author
|
||||||
|
licenseInfo.date = new Date(Number(imgurData.datetime) * 1000)
|
||||||
|
licenseInfo.views = imgurData.views
|
||||||
|
|
||||||
return licenseInfo
|
return licenseInfo
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,4 +9,6 @@ export class LicenseInfo {
|
||||||
credit: string = ""
|
credit: string = ""
|
||||||
description: string = ""
|
description: string = ""
|
||||||
informationLocation: URL = undefined
|
informationLocation: URL = undefined
|
||||||
|
date?: Date
|
||||||
|
views?: number
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,8 +60,8 @@ export class Mapillary extends ImageProvider {
|
||||||
} = undefined, zoom: number = 17, pKey?: string) {
|
} = undefined, zoom: number = 17, pKey?: string) {
|
||||||
const params = {
|
const params = {
|
||||||
focus: pKey === undefined ? "map" : "photo",
|
focus: pKey === undefined ? "map" : "photo",
|
||||||
lat: location.lat,
|
lat: location?.lat,
|
||||||
lng: location.lon,
|
lng: location?.lon,
|
||||||
z: location === undefined ? undefined : Math.max((zoom ?? 2) - 1, 1),
|
z: location === undefined ? undefined : Math.max((zoom ?? 2) - 1, 1),
|
||||||
pKey,
|
pKey,
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,7 @@ import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
|
||||||
import { Imgur } from "../Logic/ImageProviders/Imgur"
|
import { Imgur } from "../Logic/ImageProviders/Imgur"
|
||||||
import NearbyFeatureSource from "../Logic/FeatureSource/Sources/NearbyFeatureSource"
|
import NearbyFeatureSource from "../Logic/FeatureSource/Sources/NearbyFeatureSource"
|
||||||
import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource"
|
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 geolocation: GeoLocationHandler
|
||||||
|
|
||||||
readonly imageUploadManager: ImageUploadManager
|
readonly imageUploadManager: ImageUploadManager
|
||||||
|
readonly previewedImage = new UIEventSource<ProvidedImage>(undefined)
|
||||||
|
|
||||||
readonly addNewPoint: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
readonly addNewPoint: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
||||||
|
|
||||||
|
@ -475,6 +477,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
() => {
|
() => {
|
||||||
this.selectedElement.setData(undefined)
|
this.selectedElement.setData(undefined)
|
||||||
this.guistate.closeAll()
|
this.guistate.closeAll()
|
||||||
|
this.previewedImage.setData(undefined)
|
||||||
this.focusOnMap()
|
this.focusOnMap()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
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
|
* The slotted element will be shown on top, with a lower-opacity border
|
||||||
*/
|
*/
|
||||||
const dispatch = createEventDispatcher<{ close }>()
|
const dispatch = createEventDispatcher<{ close }>()
|
||||||
|
|
||||||
|
export let extraClasses = "p-4 md:p-6"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<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"
|
style="background-color: #00000088; z-index: 20"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
dispatch("close")
|
dispatch("close")
|
||||||
|
@ -33,9 +36,9 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.content {
|
.content {
|
||||||
height: calc(100vh - 2rem);
|
height: 100%;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
overflow-x: auto;
|
overflow-x: hidden;
|
||||||
box-shadow: 0 0 1rem #00000088;
|
box-shadow: 0 0 1rem #00000088;
|
||||||
}
|
}
|
||||||
</style>
|
</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 { SlideShow } from "./SlideShow"
|
||||||
import { Store } from "../../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import Combine from "../Base/Combine"
|
import Combine from "../Base/Combine"
|
||||||
import DeleteImage from "./DeleteImage"
|
import DeleteImage from "./DeleteImage"
|
||||||
import { AttributedImage } from "./AttributedImage"
|
|
||||||
import BaseUIElement from "../BaseUIElement"
|
import BaseUIElement from "../BaseUIElement"
|
||||||
import Toggle from "../Input/Toggle"
|
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 { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||||
import { Changes } from "../../Logic/Osm/Changes"
|
import { Changes } from "../../Logic/Osm/Changes"
|
||||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||||
import { Feature } from "geojson"
|
import { Feature } from "geojson"
|
||||||
|
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||||
|
import AttributedImage from "./AttributedImage.svelte"
|
||||||
|
|
||||||
export class ImageCarousel extends Toggle {
|
export class ImageCarousel extends Toggle {
|
||||||
constructor(
|
constructor(
|
||||||
images: Store<{ id:string, key: string; url: string; provider: ImageProvider }[]>,
|
images: Store<{ id:string, key: string; url: string; provider: ImageProvider }[]>,
|
||||||
tags: Store<any>,
|
tags: Store<any>,
|
||||||
state: { osmConnection?: OsmConnection; changes?: Changes; layout: LayoutConfig },
|
state: { osmConnection?: OsmConnection; changes?: Changes; layout: LayoutConfig, previewedImage?: UIEventSource<ProvidedImage> },
|
||||||
feature: Feature
|
feature: Feature
|
||||||
) {
|
) {
|
||||||
const uiElements = images.map(
|
const uiElements = images.map(
|
||||||
|
@ -23,7 +24,7 @@ export class ImageCarousel extends Toggle {
|
||||||
const uiElements: BaseUIElement[] = []
|
const uiElements: BaseUIElement[] = []
|
||||||
for (const url of imageURLS) {
|
for (const url of imageURLS) {
|
||||||
try {
|
try {
|
||||||
let image = new AttributedImage(url, feature)
|
let image: BaseUIElement = new SvelteUIElement(AttributedImage, {image: url})
|
||||||
|
|
||||||
if (url.key !== undefined) {
|
if (url.key !== undefined) {
|
||||||
image = new Combine([
|
image = new Combine([
|
||||||
|
@ -36,6 +37,7 @@ export class ImageCarousel extends Toggle {
|
||||||
image
|
image
|
||||||
.SetClass("w-full block")
|
.SetClass("w-full block")
|
||||||
.SetStyle("min-width: 50px; background: grey;")
|
.SetStyle("min-width: 50px; background: grey;")
|
||||||
|
.onClick(() => state.previewedImage.setData(url))
|
||||||
uiElements.push(image)
|
uiElements.push(image)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Could not generate image element for", url.url, "due to", 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">
|
<script lang="ts">
|
||||||
import { Store } from "../../Logic/UIEventSource"
|
import { Store } from "../../Logic/UIEventSource"
|
||||||
import type { OsmTags } from "../../Models/OsmFeature"
|
import type { OsmTags } from "../../Models/OsmFeature"
|
||||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch"
|
import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch"
|
||||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
|
||||||
import { AttributedImage } from "../Image/AttributedImage"
|
import LinkImageAction from "../../Logic/Osm/Actions/LinkImageAction"
|
||||||
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
|
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
|
||||||
import LinkImageAction from "../../Logic/Osm/Actions/LinkImageAction"
|
import { Tag } from "../../Logic/Tags/Tag"
|
||||||
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
|
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||||
import { Tag } from "../../Logic/Tags/Tag"
|
import type { Feature } from "geojson"
|
||||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
import Translations from "../i18n/Translations"
|
||||||
import type { Feature } from "geojson"
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||||
import Translations from "../i18n/Translations"
|
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||||
import SpecialTranslation from "./TagRendering/SpecialTranslation.svelte"
|
import AttributedImage from "./AttributedImage.svelte"
|
||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"
|
||||||
|
|
||||||
export let tags: Store<OsmTags>
|
export let tags: Store<OsmTags>
|
||||||
export let lon: number
|
export let lon: number
|
||||||
export let lat: number
|
export let lat: number
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
|
@ -28,12 +28,12 @@
|
||||||
|
|
||||||
const t = Translations.t.image.nearby
|
const t = Translations.t.image.nearby
|
||||||
const c = [lon, lat]
|
const c = [lon, lat]
|
||||||
let attributedImage = new AttributedImage({
|
const providedImage: ProvidedImage = {
|
||||||
url: image.thumbUrl ?? image.pictureUrl,
|
url: image.thumbUrl ?? image.pictureUrl,
|
||||||
provider: AllImageProviders.byName(image.provider),
|
provider: AllImageProviders.byName(image.provider),
|
||||||
date: new Date(image.date),
|
date: new Date(image.date),
|
||||||
id: Object.values(image.osmTags)[0]
|
id: Object.values(image.osmTags)[0]
|
||||||
}, feature)
|
}
|
||||||
let distance = Math.round(
|
let distance = Math.round(
|
||||||
GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c)
|
GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c)
|
||||||
)
|
)
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex w-fit shrink-0 flex-col">
|
<div class="flex w-fit shrink-0 flex-col">
|
||||||
<ToSvelte construct={attributedImage.SetClass("h-48 w-fit")} />
|
<AttributedImage image={providedImage} />
|
||||||
{#if linkable}
|
{#if linkable}
|
||||||
<label>
|
<label>
|
||||||
<input bind:checked={isLinked} type="checkbox" />
|
<input bind:checked={isLinked} type="checkbox" />
|
|
@ -18,6 +18,7 @@ import { RasterLayerPolygon } from "../Models/RasterLayers"
|
||||||
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
|
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
|
||||||
import { OsmTags } from "../Models/OsmFeature"
|
import { OsmTags } from "../Models/OsmFeature"
|
||||||
import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource"
|
import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource"
|
||||||
|
import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The state needed to render a special Visualisation.
|
* The state needed to render a special Visualisation.
|
||||||
|
@ -84,6 +85,8 @@ export interface SpecialVisualizationState {
|
||||||
readonly availableLayers: Store<RasterLayerPolygon[]>
|
readonly availableLayers: Store<RasterLayerPolygon[]>
|
||||||
|
|
||||||
readonly imageUploadManager: ImageUploadManager
|
readonly imageUploadManager: ImageUploadManager
|
||||||
|
|
||||||
|
readonly previewedImage: UIEventSource<ProvidedImage>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpecialVisualization {
|
export interface SpecialVisualization {
|
||||||
|
|
|
@ -61,8 +61,6 @@ import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"
|
||||||
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
|
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
|
||||||
import FediverseValidator from "./InputElement/Validators/FediverseValidator"
|
import FediverseValidator from "./InputElement/Validators/FediverseValidator"
|
||||||
import SendEmail from "./Popup/SendEmail.svelte"
|
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 UploadImage from "./Image/UploadImage.svelte"
|
||||||
import { Imgur } from "../Logic/ImageProviders/Imgur"
|
import { Imgur } from "../Logic/ImageProviders/Imgur"
|
||||||
import Constants from "../Models/Constants"
|
import Constants from "../Models/Constants"
|
||||||
|
@ -82,6 +80,8 @@ import OpenJosm from "./Base/OpenJosm.svelte"
|
||||||
import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte"
|
import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte"
|
||||||
import MarkAsFavouriteMini from "./Popup/MarkAsFavouriteMini.svelte"
|
import MarkAsFavouriteMini from "./Popup/MarkAsFavouriteMini.svelte"
|
||||||
import NextChangeViz from "./OpeningHours/NextChangeViz.svelte"
|
import NextChangeViz from "./OpeningHours/NextChangeViz.svelte"
|
||||||
|
import NearbyImages from "./Image/NearbyImages.svelte"
|
||||||
|
import NearbyImagesCollapsed from "./Image/NearbyImagesCollapsed.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
|
||||||
|
|
|
@ -1,91 +1,92 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Store, UIEventSource } from "../Logic/UIEventSource";
|
import { Store, UIEventSource } from "../Logic/UIEventSource"
|
||||||
import { Map as MlMap } from "maplibre-gl";
|
import { Map as MlMap } from "maplibre-gl"
|
||||||
import MaplibreMap from "./Map/MaplibreMap.svelte";
|
import MaplibreMap from "./Map/MaplibreMap.svelte"
|
||||||
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
|
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
|
||||||
import MapControlButton from "./Base/MapControlButton.svelte";
|
import MapControlButton from "./Base/MapControlButton.svelte"
|
||||||
import ToSvelte from "./Base/ToSvelte.svelte";
|
import ToSvelte from "./Base/ToSvelte.svelte"
|
||||||
import If from "./Base/If.svelte";
|
import If from "./Base/If.svelte"
|
||||||
import { GeolocationControl } from "./BigComponents/GeolocationControl";
|
import { GeolocationControl } from "./BigComponents/GeolocationControl"
|
||||||
import type { Feature } from "geojson";
|
import type { Feature } from "geojson"
|
||||||
import SelectedElementView from "./BigComponents/SelectedElementView.svelte";
|
import SelectedElementView from "./BigComponents/SelectedElementView.svelte"
|
||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
||||||
import Filterview from "./BigComponents/Filterview.svelte";
|
import Filterview from "./BigComponents/Filterview.svelte"
|
||||||
import ThemeViewState from "../Models/ThemeViewState";
|
import ThemeViewState from "../Models/ThemeViewState"
|
||||||
import type { MapProperties } from "../Models/MapProperties";
|
import type { MapProperties } from "../Models/MapProperties"
|
||||||
import Geosearch from "./BigComponents/Geosearch.svelte";
|
import Geosearch from "./BigComponents/Geosearch.svelte"
|
||||||
import Translations from "./i18n/Translations";
|
import Translations from "./i18n/Translations"
|
||||||
import { CogIcon, EyeIcon, HeartIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
|
import { CogIcon, EyeIcon, HeartIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||||
import Tr from "./Base/Tr.svelte";
|
import Tr from "./Base/Tr.svelte"
|
||||||
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
|
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
|
||||||
import FloatOver from "./Base/FloatOver.svelte";
|
import FloatOver from "./Base/FloatOver.svelte"
|
||||||
import PrivacyPolicy from "./BigComponents/PrivacyPolicy";
|
import PrivacyPolicy from "./BigComponents/PrivacyPolicy"
|
||||||
import Constants from "../Models/Constants";
|
import Constants from "../Models/Constants"
|
||||||
import TabbedGroup from "./Base/TabbedGroup.svelte";
|
import TabbedGroup from "./Base/TabbedGroup.svelte"
|
||||||
import UserRelatedState from "../Logic/State/UserRelatedState";
|
import UserRelatedState from "../Logic/State/UserRelatedState"
|
||||||
import LoginToggle from "./Base/LoginToggle.svelte";
|
import LoginToggle from "./Base/LoginToggle.svelte"
|
||||||
import LoginButton from "./Base/LoginButton.svelte";
|
import LoginButton from "./Base/LoginButton.svelte"
|
||||||
import CopyrightPanel from "./BigComponents/CopyrightPanel";
|
import CopyrightPanel from "./BigComponents/CopyrightPanel"
|
||||||
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte";
|
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
|
||||||
import ModalRight from "./Base/ModalRight.svelte";
|
import ModalRight from "./Base/ModalRight.svelte"
|
||||||
import { Utils } from "../Utils";
|
import { Utils } from "../Utils"
|
||||||
import Hotkeys from "./Base/Hotkeys";
|
import Hotkeys from "./Base/Hotkeys"
|
||||||
import { VariableUiElement } from "./Base/VariableUIElement";
|
import { VariableUiElement } from "./Base/VariableUIElement"
|
||||||
import SvelteUIElement from "./Base/SvelteUIElement";
|
import SvelteUIElement from "./Base/SvelteUIElement"
|
||||||
import OverlayToggle from "./BigComponents/OverlayToggle.svelte";
|
import OverlayToggle from "./BigComponents/OverlayToggle.svelte"
|
||||||
import LevelSelector from "./BigComponents/LevelSelector.svelte";
|
import LevelSelector from "./BigComponents/LevelSelector.svelte"
|
||||||
import ExtraLinkButton from "./BigComponents/ExtraLinkButton";
|
import ExtraLinkButton from "./BigComponents/ExtraLinkButton"
|
||||||
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte";
|
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"
|
||||||
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte";
|
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
|
||||||
import type { RasterLayerPolygon } from "../Models/RasterLayers";
|
import type { RasterLayerPolygon } from "../Models/RasterLayers"
|
||||||
import { AvailableRasterLayers } from "../Models/RasterLayers";
|
import { AvailableRasterLayers } from "../Models/RasterLayers"
|
||||||
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte";
|
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
|
||||||
import IfHidden from "./Base/IfHidden.svelte";
|
import IfHidden from "./Base/IfHidden.svelte"
|
||||||
import { onDestroy } from "svelte";
|
import { onDestroy } from "svelte"
|
||||||
import MapillaryLink from "./BigComponents/MapillaryLink.svelte";
|
import MapillaryLink from "./BigComponents/MapillaryLink.svelte"
|
||||||
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte";
|
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
|
||||||
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte";
|
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
|
||||||
import StateIndicator from "./BigComponents/StateIndicator.svelte";
|
import StateIndicator from "./BigComponents/StateIndicator.svelte"
|
||||||
import ShareScreen from "./BigComponents/ShareScreen.svelte";
|
import ShareScreen from "./BigComponents/ShareScreen.svelte"
|
||||||
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte";
|
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"
|
||||||
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte";
|
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte"
|
||||||
import Cross from "../assets/svg/Cross.svelte";
|
import Cross from "../assets/svg/Cross.svelte"
|
||||||
import Summary from "./BigComponents/Summary.svelte";
|
import Summary from "./BigComponents/Summary.svelte"
|
||||||
import LanguagePicker from "./InputElement/LanguagePicker.svelte";
|
import LanguagePicker from "./InputElement/LanguagePicker.svelte"
|
||||||
import Mastodon from "../assets/svg/Mastodon.svelte";
|
import Mastodon from "../assets/svg/Mastodon.svelte"
|
||||||
import Bug from "../assets/svg/Bug.svelte";
|
import Bug from "../assets/svg/Bug.svelte"
|
||||||
import Liberapay from "../assets/svg/Liberapay.svelte";
|
import Liberapay from "../assets/svg/Liberapay.svelte"
|
||||||
import OpenJosm from "./Base/OpenJosm.svelte";
|
import OpenJosm from "./Base/OpenJosm.svelte"
|
||||||
import Min from "../assets/svg/Min.svelte";
|
import Min from "../assets/svg/Min.svelte"
|
||||||
import Plus from "../assets/svg/Plus.svelte";
|
import Plus from "../assets/svg/Plus.svelte"
|
||||||
import Filter from "../assets/svg/Filter.svelte";
|
import Filter from "../assets/svg/Filter.svelte"
|
||||||
import Add from "../assets/svg/Add.svelte";
|
import Add from "../assets/svg/Add.svelte"
|
||||||
import Statistics from "../assets/svg/Statistics.svelte";
|
import Statistics from "../assets/svg/Statistics.svelte"
|
||||||
import Community from "../assets/svg/Community.svelte";
|
import Community from "../assets/svg/Community.svelte"
|
||||||
import Download from "../assets/svg/Download.svelte";
|
import Download from "../assets/svg/Download.svelte"
|
||||||
import Share from "../assets/svg/Share.svelte";
|
import Share from "../assets/svg/Share.svelte"
|
||||||
import Favourites from "./Favourites/Favourites.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 layout = state.layout
|
||||||
|
|
||||||
let maplibremap: UIEventSource<MlMap> = state.map
|
let maplibremap: UIEventSource<MlMap> = state.map
|
||||||
let selectedElement: UIEventSource<Feature> = state.selectedElement
|
let selectedElement: UIEventSource<Feature> = state.selectedElement
|
||||||
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer
|
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer
|
||||||
|
|
||||||
let currentZoom = state.mapProperties.zoom;
|
let currentZoom = state.mapProperties.zoom
|
||||||
let showCrosshair = state.userRelatedState.showCrosshair;
|
let showCrosshair = state.userRelatedState.showCrosshair
|
||||||
let arrowKeysWereUsed = state.mapProperties.lastKeyNavigation;
|
let arrowKeysWereUsed = state.mapProperties.lastKeyNavigation
|
||||||
let centerFeatures = state.closestFeatures.features;
|
let centerFeatures = state.closestFeatures.features
|
||||||
const selectedElementView = selectedElement.map(
|
const selectedElementView = selectedElement.map(
|
||||||
(selectedElement) => {
|
(selectedElement) => {
|
||||||
// Svelte doesn't properly reload some of the legacy UI-elements
|
// Svelte doesn't properly reload some of the legacy UI-elements
|
||||||
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
|
// 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
|
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
|
||||||
const layer = selectedLayer.data;
|
const layer = selectedLayer.data
|
||||||
if (selectedElement === undefined || layer === undefined) {
|
if (selectedElement === undefined || layer === undefined) {
|
||||||
return undefined;
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) {
|
if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -131,6 +132,7 @@
|
||||||
rasterLayerName = l.properties.name
|
rasterLayerName = l.properties.name
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
let previewedImage = state.previewedImage
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
|
<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">
|
<div class="pointer-events-auto interactive p-1">
|
||||||
{#each $centerFeatures as feat, i (feat.properties.id)}
|
{#each $centerFeatures as feat, i (feat.properties.id)}
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<b>{i+1}.</b><Summary {state} feature={feat}/>
|
<b>{i + 1}.</b>
|
||||||
|
<Summary {state} feature={feat} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
@ -281,6 +284,19 @@
|
||||||
{/if}
|
{/if}
|
||||||
</LoginToggle>
|
</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
|
<If
|
||||||
condition={selectedElementView.map(
|
condition={selectedElementView.map(
|
||||||
(v) =>
|
(v) =>
|
||||||
|
@ -495,12 +511,14 @@
|
||||||
|
|
||||||
<div class="flex" slot="title2">
|
<div class="flex" slot="title2">
|
||||||
<HeartIcon class="h-6 w-6" />
|
<HeartIcon class="h-6 w-6" />
|
||||||
<Tr t={Translations.t.favouritePoi.tab}/>
|
<Tr t={Translations.t.favouritePoi.tab} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col m-2" slot="content2">
|
<div class="flex flex-col m-2" slot="content2">
|
||||||
<h3> <Tr t={Translations.t.favouritePoi.title}/></h3>
|
<h3>
|
||||||
<Favourites {state}/>
|
<Tr t={Translations.t.favouritePoi.title} />
|
||||||
|
</h3>
|
||||||
|
<Favourites {state} />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex" slot="title3">
|
<div class="flex" slot="title3">
|
||||||
<Community class="h-6 w-6" />
|
<Community class="h-6 w-6" />
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue