diff --git a/src/api/iframeFeatureEvent.api.js b/src/api/iframeFeatureEvent.api.js index 5f14753ac..482765e4a 100644 --- a/src/api/iframeFeatureEvent.api.js +++ b/src/api/iframeFeatureEvent.api.js @@ -1,4 +1,27 @@ -import log from '@/utils/logging.js' +import log from '@/utils/logging' + +const targetWindow = parent ?? window.parent ?? window.opener ?? window.top + +/** + * All events fired by the iFrame postMessage implementation. + * + * @enum + */ +export const IFRAME_EVENTS = { + /** + * Event raised whenever the app changes its state (the URL changed). + * + * Payload of this event : a String with the new URL of the viewer + */ + CHANGE: 'gaChange', + /** + * Event raised when a feature has been selected. Will fire as many events as there are feature + * selected on the map (won't bundle all features into one event) + * + * Payload of this event : a JSON containing the layerId and featureId of the selected feature + */ + FEATURE_SELECTION: 'gaFeatureSelection', +} /** * Sends information to the iFrame's parent about features, through the use of the postMessage @@ -7,9 +30,9 @@ import log from '@/utils/logging.js' * @param {LayerFeature[]} features List of features for which we want to send information to the * iFrame's parent * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage + * @see https://codepen.io/geoadmin/pen/yOBzqM?editors=0010 */ export function sendFeatureInformationToIFrameParent(features) { - const targetWindow = parent ?? window.parent ?? window.opener ?? window.top if (!targetWindow) { log.debug( 'Embed view loaded as root document of a browser tab, cannot communicate with opener/parent' @@ -26,7 +49,7 @@ export function sendFeatureInformationToIFrameParent(features) { targetWindow.postMessage( { // see codepen above, for backward compatibility reasons we need to use the same type as mf-geoadmin3 - type: 'gaFeatureSelection', + type: IFRAME_EVENTS.FEATURE_SELECTION, payload: { layerId: feature.layer.id, featureId: feature.id, @@ -43,3 +66,29 @@ export function sendFeatureInformationToIFrameParent(features) { // so let's not implement this format in the new viewer and see what happens. }) } + +/** + * Is used to notify the parent the state of the app has changed. While embedding with VueJS, it's + * not possible to watch the iFrame src attribute, so an event is required to be notified of a + * children change. + * + * This is mainly used so that the iframe generator (menu share -> embed) can change the iframe + * snippet if the user decide to move / zoom the map while looking at the preview + */ +export function sendChangeEventToParent() { + if (!targetWindow) { + log.debug( + 'Embed view loaded as root document of a browser tab, cannot communicate with opener/parent' + ) + return + } + targetWindow.postMessage( + { + type: IFRAME_EVENTS.CHANGE, + payload: { + newUrl: window.location.href, + }, + }, + '*' + ) +} diff --git a/src/modules/menu/components/share/MenuShareEmbed.vue b/src/modules/menu/components/share/MenuShareEmbed.vue index c1af4bea1..5adbd4a5b 100644 --- a/src/modules/menu/components/share/MenuShareEmbed.vue +++ b/src/modules/menu/components/share/MenuShareEmbed.vue @@ -9,12 +9,16 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' // importing directly the vue component, see https://github.com/ivanvermeyen/vue-collapse-transition/issues/5 import CollapseTransition from '@ivanv/vue-collapse-transition/src/CollapseTransition.vue' -import { computed, nextTick, ref, toRefs } from 'vue' +import { computed, nextTick, ref, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import { useRoute } from 'vue-router' +import { IFRAME_EVENTS } from '@/api/iframeFeatureEvent.api' import MenuShareInputCopyButton from '@/modules/menu/components/share/MenuShareInputCopyButton.vue' import ModalWithBackdrop from '@/utils/components/ModalWithBackdrop.vue' import { useTippyTooltip } from '@/utils/composables/useTippyTooltip' import log from '@/utils/logging' +import { transformUrlMapToEmbed } from '@/utils/utils' /** * Different pre-defined sizes that an iFrame can take @@ -45,14 +49,6 @@ const EmbedSizes = { useTippyTooltip('.menu-share-embed [data-tippy-content]') -const props = defineProps({ - shortLink: { - type: String, - default: null, - }, -}) -const { shortLink } = toRefs(props) - const embedInput = ref(null) const showEmbedSharing = ref(false) const showPreviewModal = ref(false) @@ -64,6 +60,10 @@ const customSize = ref({ }) const copied = ref(false) +const { t } = useI18n() +const route = useRoute() + +const embedSource = ref(transformUrlMapToEmbed(window.location.href)) const embedPreviewModalWidth = computed(() => { // Uses the iframe's width as maximal width for the entire modal window let style = { 'max-width': iFrameWidth.value } @@ -96,7 +96,7 @@ const iFrameStyle = computed( ) const iFrameLink = computed( () => - `` + `` ) const buttonIcon = computed(() => { if (copied.value) { @@ -120,6 +120,18 @@ function toggleEmbedSharing() { function togglePreviewModal() { showPreviewModal.value = !showPreviewModal.value + if (showPreviewModal.value) { + window.addEventListener('message', onPreviewChange) + } else { + window.removeEventListener('message', onPreviewChange) + } +} + +function onPreviewChange(e) { + if (e?.data?.type === IFRAME_EVENTS.CHANGE) { + // see iframeFeatureEvent.api.js -> sendChangeEventToParent + embedSource.value = e.data.payload.newUrl + } } async function copyValue() { @@ -134,6 +146,13 @@ async function copyValue() { log.error(`Failed to copy to clipboard:`, error) } } + +watch( + () => route.query, + () => { + embedSource.value = transformUrlMapToEmbed(window.location.href) + } +) @@ -55,7 +55,6 @@ export default { computed: { ...mapState({ shortLink: (state) => state.share.shortLink, - embeddedShortLink: (state) => state.share.embeddedShortLink, isSectionShown: (state) => state.share.isMenuSectionShown, isTrackingGeolocation: (state) => state.geolocation.active && state.geolocation.tracking, diff --git a/src/store/modules/share.store.js b/src/store/modules/share.store.js index 0ce4f17d5..24a0cedf9 100644 --- a/src/store/modules/share.store.js +++ b/src/store/modules/share.store.js @@ -1,6 +1,5 @@ import { createShortLink } from '@/api/shortlink.api' import log from '@/utils/logging' -import { transformUrlMapToEmbed } from '@/utils/utils' export default { state: { @@ -12,11 +11,6 @@ export default { * @type String */ shortLink: null, - /** - * Same thing as shortLink, but with the flag embed=true send to the backend before - * shortening - */ - embeddedShortLink: null, /** * The state of the shortlink share menu section. As we need to be able to change this * whenever the user moves the map, and it should only be done within mutations. @@ -37,16 +31,6 @@ export default { log.error('Error while creating short link for', window.location.href, err) commit('setShortLink', { shortLink: window.location.href, dispatcher }) } - const embedUrl = transformUrlMapToEmbed(window.location.href) - try { - const embeddedShortLink = await createShortLink(embedUrl) - if (embeddedShortLink) { - commit('setEmbeddedShortLink', { shortLink: embeddedShortLink, dispatcher }) - } - } catch (err) { - log.error('Error while creating embedded short link for', embedUrl, err) - commit('setEmbeddedShortLink', { shortLink: embedUrl, dispatcher }) - } }, closeShareMenuAndRemoveShortLinks({ commit, dispatch }, { dispatcher }) { commit('setIsMenuSectionShown', { show: false, dispatcher }) @@ -57,16 +41,12 @@ export default { }, clearShortLinks({ commit }, { dispatcher }) { commit('setShortLink', { shortLink: null, dispatcher }) - commit('setEmbeddedShortLink', { shortLink: null, dispatcher }) }, }, mutations: { setShortLink(state, { shortLink }) { state.shortLink = shortLink }, - setEmbeddedShortLink(state, { shortLink }) { - state.embeddedShortLink = shortLink - }, setIsMenuSectionShown(state, { show }) { state.isMenuSectionShown = show }, diff --git a/src/views/EmbedView.vue b/src/views/EmbedView.vue index 68c6990aa..d6042bdba 100644 --- a/src/views/EmbedView.vue +++ b/src/views/EmbedView.vue @@ -1,7 +1,9 @@