Skip to content

Commit

Permalink
Merge pull request #987 from geoadmin/fix-PB-704-iframe-preview-update
Browse files Browse the repository at this point in the history
  • Loading branch information
pakb authored Jul 12, 2024
2 parents b24a85c + f756533 commit ffd9b2b
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 54 deletions.
55 changes: 52 additions & 3 deletions src/api/iframeFeatureEvent.api.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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'
Expand All @@ -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,
Expand All @@ -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,
},
},
'*'
)
}
58 changes: 38 additions & 20 deletions src/modules/menu/components/share/MenuShareEmbed.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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 }
Expand Down Expand Up @@ -96,7 +96,7 @@ const iFrameStyle = computed(
)
const iFrameLink = computed(
() =>
`<iframe src="${shortLink.value}" style="${iFrameStyle.value}" allow="geolocation"></iframe>`
`<iframe src="${embedSource.value}" style="${iFrameStyle.value}" allow="geolocation"></iframe>`
)
const buttonIcon = computed(() => {
if (copied.value) {
Expand All @@ -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() {
Expand All @@ -134,6 +146,13 @@ async function copyValue() {
log.error(`Failed to copy to clipboard:`, error)
}
}
watch(
() => route.query,
() => {
embedSource.value = transformUrlMapToEmbed(window.location.href)
}
)
</script>
<template>
Expand All @@ -149,7 +168,7 @@ async function copyValue() {
:class="{ 'text-primary': showEmbedSharing }"
:icon="`caret-${showEmbedSharing ? 'down' : 'right'}`"
/>
<span class="px-1">{{ $t('share_more') }}</span>
<span class="px-1">{{ t('share_more') }}</span>
</a>
<CollapseTransition :duration="200">
<div v-show="showEmbedSharing" class="p-2 ps-4 card border-light">
Expand Down Expand Up @@ -182,17 +201,17 @@ async function copyValue() {
data-cy="menu-share-embed-preview-button"
@click="togglePreviewModal"
>
{{ $t('show_more_options') }}
{{ t('show_more_options') }}
</button>
</div>
<!-- eslint-disable vue/no-v-html-->
<div class="py-2" v-html="$t('share_disclaimer')"></div>
<div class="py-2" v-html="t('share_disclaimer')"></div>
<!-- eslint-enable vue/no-v-html-->
</div>
</CollapseTransition>
<ModalWithBackdrop
v-if="showPreviewModal"
:title="$t('embed_map')"
:title="t('embed_map')"
fluid
@close="togglePreviewModal"
>
Expand All @@ -210,7 +229,7 @@ async function copyValue() {
class="embed-preview-modal-size-selector-option"
:data-cy="`menu-share-embed-iframe-size-${size.i18nKey.toLowerCase()}`"
>
{{ $t(size.i18nKey) }}
{{ t(size.i18nKey) }}
</option>
</select>
<div v-if="isPreviewSizeCustom" class="d-flex flex-row ms-2">
Expand Down Expand Up @@ -249,7 +268,7 @@ async function copyValue() {
data-cy="menu-share-embed-iframe-full-width"
/>
<label class="form-check-label" for="fullWidthCheckbox">
{{ $t('full_width') }}
{{ t('full_width') }}
</label>
</div>
</div>
Expand All @@ -268,15 +287,14 @@ async function copyValue() {
<!-- so I've opted to keep this piece of code for a better user experience -->
<div class="d-flex justify-content-center mb-2">
<iframe
ref="iFramePreview"
:title="$t('embed_map')"
:src="shortLink"
:title="t('embed_map')"
:src="embedSource"
:style="iFrameStyle"
allow="geolocation"
></iframe>
</div>
<!-- eslint-disable vue/no-v-html-->
<div class="small text-wrap text-center" v-html="$t('share_disclaimer')"></div>
<div class="small text-wrap text-center" v-html="t('share_disclaimer')"></div>
<!-- eslint-enable vue/no-v-html-->
</div>
</ModalWithBackdrop>
Expand Down
3 changes: 1 addition & 2 deletions src/modules/menu/components/share/MenuShareSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
:small="compact"
/>
</div>
<MenuShareEmbed :short-link="embeddedShortLink" class="menu-share-embed border-top" />
<MenuShareEmbed class="menu-share-embed border-top" />
</MenuSection>
</template>

Expand Down Expand Up @@ -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,
Expand Down
20 changes: 0 additions & 20 deletions src/store/modules/share.store.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createShortLink } from '@/api/shortlink.api'
import log from '@/utils/logging'
import { transformUrlMapToEmbed } from '@/utils/utils'

export default {
state: {
Expand All @@ -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.
Expand All @@ -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 })
Expand All @@ -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
},
Expand Down
7 changes: 6 additions & 1 deletion src/views/EmbedView.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<script setup>
import { computed, onBeforeMount, onMounted } from 'vue'
import { computed, onBeforeMount, onMounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useStore } from 'vuex'
import { sendChangeEventToParent } from '@/api/iframeFeatureEvent.api'
import I18nModule from '@/modules/i18n/I18nModule.vue'
import InfoboxModule from '@/modules/infobox/InfoboxModule.vue'
import MapFooter from '@/modules/map/components/footer/MapFooter.vue'
Expand All @@ -15,6 +17,7 @@ import log from '@/utils/logging'
const dispatcher = { dispatcher: 'EmbedView.vue' }
const store = useStore()
const route = useRoute()
const is3DActive = computed(() => store.state.cesium.active)
Expand All @@ -25,6 +28,8 @@ onBeforeMount(() => {
onMounted(() => {
log.info(`Embedded map view mounted`)
})
watch(() => route.query, sendChangeEventToParent)
</script>

<template>
Expand Down
8 changes: 0 additions & 8 deletions tests/cypress/tests-e2e/shareShortLink.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,8 @@ describe('Testing the share menu', () => {
cy.get('[data-cy="menu-share-section"]').click()
cy.get('[data-cy="menu-share-embed-button"]').click()
})
it('uses the embedded shortlink url to generate the iFrame code snippet', () => {
cy.get('[data-cy="menu-share-embed-simple-iframe-snippet"]')
.should('contain.value', dummyEmbeddedShortLink)
.should('not.contain.value', dummyShortLink)
})
it('enables the user to choose pre-made sizes for the generated iframe code snippet', () => {
cy.get('[data-cy="menu-share-embed-preview-button"]').click()
cy.wait('@dummyShortLinkAccess')
// checking that the iframe code snippet is set to the Small size by default
checkIFrameSnippetSize(400, 300)
// Checking that the name of the size is displayed in the selector, then switching to the Medium size
Expand All @@ -195,7 +189,6 @@ describe('Testing the share menu', () => {
})
it('enables the user to customize the size of the generated iframe code snippet', () => {
cy.get('[data-cy="menu-share-embed-preview-button"]').click()
cy.wait('@dummyShortLinkAccess')
cy.get('[data-cy="menu-share-embed-iframe-custom-width"]').should('not.exist')
cy.get('[data-cy="menu-share-embed-iframe-custom-height"]').should('not.exist')
cy.get('[data-cy="menu-share-embed-iframe-size-selector"]').select('Custom size')
Expand All @@ -217,7 +210,6 @@ describe('Testing the share menu', () => {
})
it('enables the user to create a full width iframe code snippet', () => {
cy.get('[data-cy="menu-share-embed-preview-button"]').click()
cy.wait('@dummyShortLinkAccess')
cy.get('[data-cy="menu-share-embed-iframe-size-selector"]').select('Custom size')
cy.get('[data-cy="menu-share-embed-iframe-custom-width"]')
.should('be.visible')
Expand Down

0 comments on commit ffd9b2b

Please sign in to comment.