Obtain MapTiler key from server, providing SSO or Captcha (Turnstile) information for authentication

beta
Manuel Kasper 2025-08-14 17:07:58 +02:00
rodzic 23606b0867
commit 67d2c6f074
15 zmienionych plików z 229 dodań i 67 usunięć

1
.env
Wyświetl plik

@ -3,7 +3,6 @@ VITE_WSS_URL="wss://sotl.as/api"
VITE_PHOTOS_URL="https://photos.sotl.as"
VITE_PHOTOS_ORIGINAL_URL="https://sotlas-photos.s3.eu-central-003.backblazeb2.com/original"
VITE_ELEVATION_API_URL="https://elevation.sotl.as/api"
VITE_MAPTILER_KEY="ngpPmAguBVHe7Dcxki1g"
VITE_GEONAMES_USERNAME="neon1"
VITE_WINDY_API_KEY="FIHFGWMrA0iF5Wz4fnBIR8Sb0GRUUeQY"
VITE_AZ_URL="https://az.sotl.as"

Wyświetl plik

@ -32,8 +32,6 @@
}
</script>
<div id="app"></div>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div class="cf-turnstile" data-sitekey="%VITE_TURNSTILE_SITE_KEY%"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

49
package-lock.json wygenerowano
Wyświetl plik

@ -16,6 +16,7 @@
"@fortawesome/pro-regular-svg-icons": "^5.15.4",
"@fortawesome/pro-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^2.0.10",
"@gaviti/vue-turnstile": "^0.6.5",
"@mapbox/mapbox-gl-draw": "github:manuelkasper/mapbox-gl-draw#sotlas2",
"@maptiler/sdk": "^2.0.3",
"@tmcw/togeojson": "^3.2.0",
@ -35,7 +36,7 @@
"proj4": "^2.7.2",
"vue": "^2.7.16",
"vue-clipboard2": "^0.3.1",
"vue-filepond": "^6.0.3",
"vue-filepond": "^6.0.2",
"vue-infinite-loading": "^2.4.5",
"vue-lazy-youtube-video": "^2.3.0",
"vue-mapbox": "github:manuelkasper/vue-mapbox#10cb772",
@ -775,6 +776,18 @@
"vue": "~2"
}
},
"node_modules/@gaviti/vue-turnstile": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/@gaviti/vue-turnstile/-/vue-turnstile-0.6.5.tgz",
"integrity": "sha512-kS1SVFzDAFqiqrvEOPNQIRWfCi6tv9w/2aNBRdUa217PXjjSt35sofMqsz5LiU4xqyvnBMkp1f/aNUhNa8GxFg==",
"license": "MIT",
"dependencies": {
"vue": "^2.7.16"
},
"engines": {
"node": "^14.18.0 || >= 16.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@ -1725,9 +1738,10 @@
}
},
"node_modules/csstype": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
"integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
"node_modules/data-view-buffer": {
"version": "1.0.1",
@ -4812,9 +4826,10 @@
"dev": true
},
"node_modules/vue-filepond": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/vue-filepond/-/vue-filepond-6.0.3.tgz",
"integrity": "sha512-m0wArAdpgzOOs19bWA6zzYlHAb2aK+igPoKPZGrzpgKiiELPKW7XZ2OBDXzk7rhpFLkedujVrMqwjPyZfmQTTQ==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/vue-filepond/-/vue-filepond-6.0.2.tgz",
"integrity": "sha512-HE1TvV2LrKMU3pFeIBbR9Uf6cryNLVnnl35uSL+HLsZ6rfZ3C1alfre3UcaSoKTVuxNsn41Ce4IR528UwdxZfw==",
"license": "MIT",
"peerDependencies": {
"filepond": ">=4.7.4 < 5.x",
"vue": ">=2.6.0 < 3.x"
@ -5369,6 +5384,14 @@
"integrity": "sha512-OTETSXz+3ygD2OK2/vy82cmUBpuJqeOAg4gfnnv+f2Rir1tDIhQg026Q3NQxznq83ZLz8iNqGG9XJm26inpDeg==",
"requires": {}
},
"@gaviti/vue-turnstile": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/@gaviti/vue-turnstile/-/vue-turnstile-0.6.5.tgz",
"integrity": "sha512-kS1SVFzDAFqiqrvEOPNQIRWfCi6tv9w/2aNBRdUa217PXjjSt35sofMqsz5LiU4xqyvnBMkp1f/aNUhNa8GxFg==",
"requires": {
"vue": "^2.7.16"
}
},
"@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@ -6061,9 +6084,9 @@
"dev": true
},
"csstype": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
"integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"data-view-buffer": {
"version": "1.0.1",
@ -8229,9 +8252,9 @@
}
},
"vue-filepond": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/vue-filepond/-/vue-filepond-6.0.3.tgz",
"integrity": "sha512-m0wArAdpgzOOs19bWA6zzYlHAb2aK+igPoKPZGrzpgKiiELPKW7XZ2OBDXzk7rhpFLkedujVrMqwjPyZfmQTTQ==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/vue-filepond/-/vue-filepond-6.0.2.tgz",
"integrity": "sha512-HE1TvV2LrKMU3pFeIBbR9Uf6cryNLVnnl35uSL+HLsZ6rfZ3C1alfre3UcaSoKTVuxNsn41Ce4IR528UwdxZfw==",
"requires": {}
},
"vue-infinite-loading": {

Wyświetl plik

@ -20,6 +20,7 @@
"@fortawesome/pro-regular-svg-icons": "^5.15.4",
"@fortawesome/pro-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^2.0.10",
"@gaviti/vue-turnstile": "^0.6.5",
"@mapbox/mapbox-gl-draw": "github:manuelkasper/mapbox-gl-draw#sotlas2",
"@maptiler/sdk": "^2.0.3",
"@tmcw/togeojson": "^3.2.0",
@ -39,7 +40,7 @@
"proj4": "^2.7.2",
"vue": "^2.7.16",
"vue-clipboard2": "^0.3.1",
"vue-filepond": "^6.0.3",
"vue-filepond": "^6.0.2",
"vue-infinite-loading": "^2.4.5",
"vue-lazy-youtube-video": "^2.3.0",
"vue-mapbox": "github:manuelkasper/vue-mapbox#10cb772",

Wyświetl plik

@ -4,14 +4,28 @@
<keep-alive include="Map">
<router-view />
</keep-alive>
<vue-turnstile v-if="!authenticated" :site-key="siteKey" @verified="turnstileVerified" />
</div>
</template>
<script>
import NavBar from './components/NavBar.vue'
import VueTurnstile from '@gaviti/vue-turnstile'
import utils from './mixins/utils.js'
export default {
components: { NavBar }
mixins: [utils],
components: { NavBar, VueTurnstile },
computed: {
siteKey () {
return import.meta.env.VITE_TURNSTILE_SITE_KEY
}
},
methods: {
turnstileVerified(token) {
this.$store.commit('setTurnstileToken', token)
}
}
}
</script>

Wyświetl plik

@ -27,7 +27,7 @@ export default {
}
},
mounted () {
if (this.homeQth === null) {
if (this.homeQth === null && this.authenticated) {
this.$keycloak.updateToken(60)
.then(() => {
this.$keycloak.loadUserProfile()

Wyświetl plik

@ -0,0 +1,56 @@
<template>
<div class="map-key-failed-info">
<div class="info-card">
<font-awesome-icon :icon="['far', 'exclamation-circle']" class="faicon" />
<h3 class="title">Anti-bot verification failed</h3>
<p class="messagex">The automatic verification that you are not a bot failed. Please log in with your SOTA
account to view the map.</p>
</div>
</div>
</template>
<script>
export default {
name: 'MapKeyFailedInfo'
}
</script>
<style scoped>
.map-key-failed-info {
width: 100%;
height: 100%;
background: #f7f7f7;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.info-card {
max-width: 400px;
width: 90%;
text-align: center;
}
.info-card .faicon {
color: #f59e0b;
width: 2.5rem;
height: 2.5rem;
margin-bottom: 0.75rem;
}
.title {
font-size: 1.5rem;
font-weight: 600;
color: #1f2937;
margin: 0 0 0.75rem 0;
line-height: 1.3;
}
.messagex {
font-size: 1rem;
color: #6b7280;
margin: 0 0 0.5rem 0;
line-height: 1.6;
}
</style>

Wyświetl plik

@ -1,6 +1,11 @@
<template>
<MglMap v-if="(mapCenter || bounds) && mapStyle" :apiKey="mapApiKey" :key="mapKey" :mapStyle="mapStyle" :bounds="bounds" :fitBoundsOptions="fitBoundsOptions" :center="mapCenter" :zoom="12.5" :attributionControl="false" @load="onMapLoaded" @click="onMapClicked" @contextmenu="onMapRightClicked" @idle="onMapIdle">
<MglGeolocateControl v-if="!$mq.mobile || isEnlarged" :positionOptions="{ enableHighAccuracy: true }" :fitBoundsOptions="{ maxZoom: 12.5 }" :trackUserLocation="true" position="top-right" />
<div>
<MglMap v-if="(mapCenter || bounds) && mapStyle" :apiKey="mapTilerApiKey" :key="mapKey"
:mapStyle="mapStyle" :bounds="bounds" :fitBoundsOptions="fitBoundsOptions" :center="mapCenter" :zoom="12.5"
:attributionControl="false" @load="onMapLoaded" @click="onMapClicked" @contextmenu="onMapRightClicked"
@idle="onMapIdle">
<MglGeolocateControl v-if="!$mq.mobile || isEnlarged" :positionOptions="{ enableHighAccuracy: true }"
:fitBoundsOptions="{ maxZoom: 12.5 }" :trackUserLocation="true" position="top-right" />
<MglNavigationControl v-if="!$mq.mobile" position="top-right" :showCompass="false" />
<MglScaleControl v-if="!$mq.mobile || isEnlarged" position="bottom-left" :unit="mapUnits" />
<div v-if="canEnlarge" class="maplibregl-ctrl-top-left">
@ -9,11 +14,15 @@
<MglAttributionControl :compact="$mq.mobile" position="bottom-right" />
<MapRoute v-for="route in routes" :key="route.id" :route="route" />
<MapPhoto v-for="photo in mapPhotos" :key="photo.filename" :summit="summit" :photo="photo" @photoClicked="photo => $emit('photoClicked', photo)" />
<MapPhoto v-for="photo in mapPhotos" :key="photo.filename" :summit="summit" :photo="photo"
@photoClicked="photo => $emit('photoClicked', photo)" />
<MapInfoPopup v-if="infoCoordinates !== null" :coordinates="infoCoordinates" @close="infoCoordinates = null" />
<MapWebcams v-if="mapOptions.webcams" size="is-small" />
<div v-if="zoomWarningVisible" class="zoom-warning">Zoom in to see all activations</div>
</MglMap>
<MapKeyFailedInfo v-if="mapTilerApiKeyFailed" />
<b-loading class="mini-map-loading" :is-full-page="false" :active="!mapStyle && !mapTilerApiKeyFailed" />
</div>
</template>
<script>
@ -27,6 +36,7 @@ import mapstyle from '../mixins/mapstyle.js'
import utils from '../mixins/utils.js'
import longtouch from '../mixins/longtouch.js'
import reportMapSession from '../mapsession.js'
import MapKeyFailedInfo from './MapKeyFailedInfo.vue'
export default {
name: 'MiniMap',
@ -45,7 +55,7 @@ export default {
overviewMap: Boolean
},
components: {
MglMap, MglGeolocateControl, MglNavigationControl, MapEnlargeControl, MglScaleControl, MglAttributionControl, MapRoute, MapPhoto, MapInfoPopup, MapWebcams
MglMap, MglGeolocateControl, MglNavigationControl, MapEnlargeControl, MglScaleControl, MglAttributionControl, MapRoute, MapPhoto, MapInfoPopup, MapWebcams, MapKeyFailedInfo
},
mixins: [utils, mapstyle, longtouch],
watch: {
@ -261,4 +271,9 @@ export default {
text-align: center;
opacity: 0.9;
}
.mini-map-loading {
position: relative;
width: 100%;
height: 100%;
}
</style>

Wyświetl plik

@ -33,6 +33,16 @@ export default {
.then(response => {
return response.data
})
},
loadMapTilerApiKey (turnstileToken = null) {
let params = {}
if (turnstileToken) {
params.token = turnstileToken
}
return this.axiosAuthOptional.get(import.meta.env.VITE_API_URL + '/mapkey/get', { params })
.then(response => {
return response.data
})
}
}
}

Wyświetl plik

@ -1,3 +1,4 @@
import api from './api.js'
import basemapatStyle from '../assets/basemapat.json'
import caltopoStyle from '../assets/caltopo.json'
import norkartStyle from '../assets/norkart.json'
@ -8,11 +9,17 @@ import toposvalbardStyle from '../assets/toposvalbard.json'
import partialSnowcoverDots from '../assets/partial-snowcover-dots.png'
export default {
mixins: [api],
mounted () {
this.initialMapOptions = { ...this.mapOptions }
this.updateMapTilerApiKey()
},
computed: {
mapStyle () {
if (!this.mapTilerApiKey) {
return null
}
if (this.mapType === 'maptiler_outdoor') {
if (this.$store.state.altitudeUnits === 'ft') {
return 'dc9edd90-1320-4fa4-98ba-ad2d4efe5998'
@ -43,10 +50,10 @@ export default {
// Patch MapTiler key
Object.values(style.sources).forEach(source => {
if (source.url) {
source.url = source.url.replace('{key}', import.meta.env.VITE_MAPTILER_KEY)
source.url = source.url.replace('{key}', this.mapTilerApiKey)
}
})
style.glyphs = style.glyphs.replace('{key}', import.meta.env.VITE_MAPTILER_KEY)
style.glyphs = style.glyphs.replace('{key}', this.mapTilerApiKey)
// Patch units
if (this.$store.state.altitudeUnits === 'ft') {
@ -68,8 +75,8 @@ export default {
}
return mapType
},
mapApiKey () {
return import.meta.env.VITE_MAPTILER_KEY
mapTilerApiKey () {
return this.$store.state.mapTilerApiKey
},
mapUnits () {
if (this.$store.state.altitudeUnits === 'ft') {
@ -80,6 +87,26 @@ export default {
}
},
methods: {
updateMapTilerApiKey () {
if (this.$store.state.mapTilerApiKey) {
return
}
// If we are logged in via SSO, then there's no need for Turnstile
if ((this.$keycloak && this.$keycloak.authenticated) || this.$store.state.turnstileToken) {
this.loadMapTilerApiKey(this.$store.state.turnstileToken)
.then(response => {
this.$store.commit('setMapTilerApiKey', response.mapTilerApiKey)
if (this.$store.state.turnstileToken) {
this.$store.commit('setTurnstileToken', null)
}
})
.catch(error => {
console.error(error)
this.mapTilerApiKeyFailed = true
})
}
},
updateLayers (map) {
if (!map) {
return
@ -168,6 +195,13 @@ export default {
}
}
},
watch: {
'$store.state.turnstileToken': {
handler () {
this.updateMapTilerApiKey()
}
}
},
data () {
return {
mapTypes: {
@ -230,7 +264,9 @@ export default {
style: norkartStyle
}
},
initialMapOptions: null
initialMapOptions: null,
mapTilerApiKey: null,
mapTilerApiKeyFailed: false
}
}
}

Wyświetl plik

@ -3,33 +3,32 @@ import axios from 'axios'
export default {
computed: {
axiosAuth () {
let instance = axios.create()
instance.interceptors.request.use(config => new Promise((resolve, reject) => {
if (!this.$keycloak || !this.$keycloak.authenticated) {
reject(new Error('not logged in'))
} else {
this.$keycloak.updateToken(60)
.then(() => {
config.headers.Authorization = 'Bearer ' + this.$keycloak.token
resolve(config)
})
.catch(e => {
reject(e)
})
}
}))
return instance
return this.createAxiosAuth(false)
},
axiosAuthId () {
return this.createAxiosAuth(true)
},
axiosAuthOptional () {
return this.createAxiosAuth(false, true)
}
},
methods: {
createAxiosAuth (includeId = false, optional = false) {
let instance = axios.create()
instance.interceptors.request.use(config => new Promise((resolve, reject) => {
if (!this.$keycloak || !this.$keycloak.authenticated) {
if (optional) {
resolve(config)
} else {
reject(new Error('not logged in'))
}
} else {
this.$keycloak.updateToken(60)
.then(() => {
config.headers.Authorization = 'Bearer ' + this.$keycloak.token
if (includeId) {
config.headers.id_token = this.$keycloak.idToken
}
resolve(config)
})
.catch(e => {

Wyświetl plik

@ -66,7 +66,9 @@ const store = new Vuex.Store({
activatorPage: 1,
mapType,
mapOptions,
mapCenter: null
mapCenter: null,
mapTilerApiKey: null,
turnstileToken: null
},
mutations: {
SOCKET_ONOPEN (state, event) {
@ -157,6 +159,12 @@ const store = new Vuex.Store({
},
setMapCenter (state, center) {
state.mapCenter = center
},
setMapTilerApiKey (state, key) {
state.mapTilerApiKey = key
},
setTurnstileToken (state, token) {
state.turnstileToken = token
}
},
actions: {

Wyświetl plik

@ -1,6 +1,6 @@
<template>
<div class="map-layout" ref="mapLayout">
<MglMap v-if="showMap && mapStyle" :apiKey="mapApiKey" :mapStyle="mapStyle" :bounds.sync="bounds" :fitBoundsOptions="fitBoundsOptions" :center="center" :zoom="zoom" :dragRotate="false" :attributionControl="false" class="map" @load="onMapLoaded" @click="onMapClicked" @contextmenu="onMapRightClicked">
<MglMap v-if="showMap && mapStyle" :apiKey="mapTilerApiKey" :mapStyle="mapStyle" :bounds.sync="bounds" :fitBoundsOptions="fitBoundsOptions" :center="center" :zoom="zoom" :dragRotate="false" :attributionControl="false" class="map" @load="onMapLoaded" @click="onMapClicked" @contextmenu="onMapRightClicked">
<MglGeolocateControl :positionOptions="{ enableHighAccuracy: true }" :fitBoundsOptions="{ maxZoom: 12.5 }" :trackUserLocation="true" position="top-right" />
<MglNavigationControl position="top-right" :showCompass="false" />
<MglScaleControl position="bottom-left" :unit="mapUnits" />
@ -33,7 +33,8 @@
<div v-if="zoomWarning" class="zoom-warning">Zoom in to see all filtered/spotted summits</div>
<SwisstopoInfo />
<BasemapAtInfo />
<b-loading :is-full-page="false" :active="filtering || !showMap || !mapStyle" />
<MapKeyFailedInfo v-if="mapTilerApiKeyFailed" />
<b-loading :is-full-page="false" :active="(filtering || !showMap || !mapStyle) && !mapTilerApiKeyFailed" />
</div>
</template>
@ -57,11 +58,12 @@ import MapDraw from '../components/MapDraw.vue'
import MapWebcams from '../components/MapWebcams.vue'
import SwisstopoInfo from '../components/SwisstopoInfo.vue'
import BasemapAtInfo from '../components/BasemapAtInfo.vue'
import MapKeyFailedInfo from '../components/MapKeyFailedInfo.vue'
export default {
name: 'Map',
components: {
MglMap, MglPopup, MglNavigationControl, MglGeolocateControl, MglScaleControl, MglAttributionControl, MapFilterControl, MapOptionsControl, MapDownloadControl, LoadingRing, SummitPopup, MapRoute, MapInfoPopup, MapDraw, MapWebcams, SwisstopoInfo, BasemapAtInfo
MglMap, MglPopup, MglNavigationControl, MglGeolocateControl, MglScaleControl, MglAttributionControl, MapFilterControl, MapOptionsControl, MapDownloadControl, LoadingRing, SummitPopup, MapRoute, MapInfoPopup, MapDraw, MapWebcams, SwisstopoInfo, BasemapAtInfo, MapKeyFailedInfo
},
mixins: [utils, smptracks, mapstyle, longtouch],
created () {

Wyświetl plik

@ -59,7 +59,7 @@
<div v-if="!enlargeMap" class="column">
<div>Coordinates: <Coordinates v-if="summit.coordinates" :latitude="summit.coordinates.latitude" :longitude="summit.coordinates.longitude" :altitude="summit.altitude" :reference="summit.code" /></div>
<div>Locator: <span class="locator">{{ locator }}</span></div>
<div v-if="$keycloak && $keycloak.authenticated && summit.coordinates">Distance/Bearing: <Bearing :latitude="summit.coordinates.latitude" :longitude="summit.coordinates.longitude" /></div>
<div v-if="authenticated && summit.coordinates">Distance/Bearing: <Bearing :latitude="summit.coordinates.latitude" :longitude="summit.coordinates.longitude" /></div>
<div v-if="firstActivations">
First activation:
<FirstActivator :callsign="firstActivations.activators[0].callsign" :userId="firstActivations.activators[0].userId" />
@ -71,7 +71,7 @@
<SummitAttributes :summit-code="summit.code" />
<template v-if="resources.length > 0">
<h6 class="title is-6">Resources<span v-if="$keycloak && $keycloak.authenticated" class="add-article is-size-7-mobile">(<a :href="addArticleLink">+ Article</a> | <a :href="addRouteLink" target="_blank" onclick="return confirm('Routes can be added by uploading tracks on sotamaps.org. They will then automatically appear on SOTLAS as well. Click OK to go to sotamaps.org now.')">+ Route</a>)</span><span v-else class="add-article is-size-7-mobile">(<span class="disabled" title="Log in to add an article">+ Article</span> | <span class="disabled" title="Log in to add a route">+ Route</span>)</span></h6>
<h6 class="title is-6">Resources<span v-if="authenticated" class="add-article is-size-7-mobile">(<a :href="addArticleLink">+ Article</a> | <a :href="addRouteLink" target="_blank" onclick="return confirm('Routes can be added by uploading tracks on sotamaps.org. They will then automatically appear on SOTLAS as well. Click OK to go to sotamaps.org now.')">+ Route</a>)</span><span v-else class="add-article is-size-7-mobile">(<span class="disabled" title="Log in to add an article">+ Article</span> | <span class="disabled" title="Log in to add a route">+ Route</span>)</span></h6>
<ResourceList :resources="resources" />
</template>
</div>
@ -108,7 +108,7 @@
<h4 class="title is-4">Photos</h4>
<SummitPhotos v-if="summit.photos && summit.photos.length > 0" ref="summitPhotos" :summit="summit" :editable="true" :showWaypointButton="true" @photoDeleted="reloadPhotos" @photoEdited="reloadPhotos" @photosReordered="reloadPhotos" />
<PhotosUploader v-if="$keycloak && $keycloak.authenticated" :summitCode="summitCode" @upload="reloadPhotos" />
<PhotosUploader v-if="authenticated" :summitCode="summitCode" @upload="reloadPhotos" />
<div v-else class="uploader-placeholder box"><font-awesome-icon :icon="['far', 'images']" size="lg" /> Log in and upload your photos of this summit!</div>
</div>
</section>

Wyświetl plik

@ -55,13 +55,14 @@ export default defineConfig(async ({ mode }) => {
],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'vue/dist/vue.esm': 'vue'
'@': path.resolve(__dirname, 'src')
},
},
optimizeDeps: {
include: [
'map-promisified'
'map-promisified',
'events',
'maplibre-gl'
]
},
define: {