Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/screenshot #149

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
51 changes: 50 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"exponential-backoff": "^3.1.1",
"file-saver": "^2.0.5",
"html-loader": "4.2.0",
"html2canvas": "^1.4.1",
"inquirer": "^8.2.0",
"jszip": "^3.7.1",
"jszip-utils": "^0.1.0",
Expand Down
3 changes: 2 additions & 1 deletion public/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,6 @@
"whatIsYourEmail": "What is your email?",
"whoAreYou": "Who are you?",
"wmsLayer": "WMS layer",
"wmsUrl": "WMS url"
"wmsUrl": "WMS url",
"screenshot": "Take a snapshot of the current view and save it to png"
}
3 changes: 2 additions & 1 deletion public/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,6 @@
"whatIsYourEmail": "Wat is uw e-mailadres?",
"whoAreYou": "Wie bent u?",
"wmsLayer": "WMS laag",
"wmsUrl": "WMS url"
"wmsUrl": "WMS url",
"screenshot": "Maak een snapshot van het huidige weergave en sla deze op naar png"
}
14 changes: 14 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
:items="localeItems"
@input="switchLocaleAndAddViewerData($event.title)"
/>
<screenshot-button :map="mapInstance" />
<burger-menu
:loadingburger="localeIsLoading"
@open-contact-form="onOpenContactForm"
Expand Down Expand Up @@ -59,13 +60,15 @@
/>

<v-mapbox
ref="mapbox"
slot="map"
:access-token="accessToken"
map-style="mapbox://styles/siggyf/ckww2c33f0xlf15nujlx41fe2"
:center="mapCenter"
:zoom="mapZoom"
class="mapbox-map"
:style="`--sidebar-width: ${appNavigationWidth}px`"
:map-options="{ preserveDrawingBuffer: true }"
@mb-load="setMapLoaded"
>
<time-slider
Expand Down Expand Up @@ -141,6 +144,7 @@
import FeedbackDialog from './components/FeedbackDialog/FeedbackDialog.vue'
import AcknowledgmentsDialog from '~/components/AcknowledgmentsDialog/AcknowledgmentsDialog.vue'
import { getCookie, setCookie } from './lib/cookies'
import ScreenshotButton from '~/components/ScreenshotButton/ScreenshotButton'

const removeRegion = locale => locale.replace(/-.+/, '')
const localeIsAvailable = locale => availableLocales.includes(locale)
Expand All @@ -166,6 +170,7 @@
UserAgreementDialog,
FeedbackDialog,
AcknowledgmentsDialog,
ScreenshotButton
},

data: () => ({
Expand All @@ -188,6 +193,7 @@
feedbackDialogOpen: false,
clickedUserAgreementOpen: false,
acknowledgmentsDialogOpen: false,
mapInstance: null
}),

computed: {
Expand Down Expand Up @@ -220,6 +226,14 @@
},

mounted() {
this.$nextTick(() => {
if (this.$refs.mapbox && this.$refs.mapbox.map) {
this.mapInstance = this.$refs.mapbox.map;
} else {
console.error("Mapbox instance is not available");

Check warning on line 233 in src/App.vue

View workflow job for this annotation

GitHub Actions / deploy-application (nl2120, 1f6372f6-c532-4e0e-bba7-dee08678d518)

Unexpected console statement

Check warning on line 233 in src/App.vue

View workflow job for this annotation

GitHub Actions / deploy-application (openearth-data-viewer, 1785f3f6-b4cf-42de-bea6-b57d48a5c664)

Unexpected console statement

Check warning on line 233 in src/App.vue

View workflow job for this annotation

GitHub Actions / deploy-application (openearth-rws-viewer, 119b8ff3-5b22-4995-b43b-b31f21ba77c3)

Unexpected console statement
}
});

this.$router.onReady(async (route) => {
const savedLocale = window.localStorage.getItem('locale')
let browserLocales = []
Expand Down Expand Up @@ -322,7 +336,7 @@
this.layersDialogOpen = true
}
} catch (e) {
console.log(e)

Check warning on line 339 in src/App.vue

View workflow job for this annotation

GitHub Actions / deploy-application (nl2120, 1f6372f6-c532-4e0e-bba7-dee08678d518)

Unexpected console statement

Check warning on line 339 in src/App.vue

View workflow job for this annotation

GitHub Actions / deploy-application (openearth-data-viewer, 1785f3f6-b4cf-42de-bea6-b57d48a5c664)

Unexpected console statement

Check warning on line 339 in src/App.vue

View workflow job for this annotation

GitHub Actions / deploy-application (openearth-rws-viewer, 119b8ff3-5b22-4995-b43b-b31f21ba77c3)

Unexpected console statement
} finally {
this.searchIsLoading = false
}
Expand Down
167 changes: 167 additions & 0 deletions src/components/ScreenshotButton/ScreenshotButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<template>
<v-tooltip bottom max-width="200px">
<template v-slot:activator="{ on }">
<v-btn
class="ma-auto"
v-on="on"
icon
@click="snapShot"
>
<v-icon> mdi-content-save </v-icon>
</v-btn>
</template>
<span>
{{ $t('screenshot') }}
</span>
</v-tooltip>
</template>

<script>
import { mapGetters } from 'vuex'
import html2canvas from 'html2canvas';

export default {
props: {
map: Object
},
computed: {
...mapGetters('app', [ 'viewerName' ]),
},
methods: {
async snapShot() {
if (!this.map) {
console.error("Map instance is not available");
return;
}

// Ensure the Layer Order component is expanded before capturing
const layerOrderElement = document.querySelector(".layer-order");
if (!layerOrderElement) {
console.error("Layer order UI not found");
return;
}
layerOrderElement.classList.add("layer-order--open");

// Ensure the Mapbox Legend component is expanded before capturing
const mapLegendElement = document.querySelector(".map-legend");
if (!mapLegendElement) {
console.error("Map legend UI not found");
return;
}
mapLegendElement.classList.add("map-legend--open");

// Expand all legend items
document.querySelectorAll(".map-legend .v-expansion-panel").forEach(panel => {
panel.classList.add("v-expansion-panel--active");
const panelContent = panel.querySelector(".v-expansion-panel-content");
if (panelContent) {
panelContent.style.display = "block";
panelContent.style.maxHeight = "none";
}
});

// Clone the Layer Order component
const clonedLayerOrder = layerOrderElement.cloneNode(true);
clonedLayerOrder.style.position = "absolute";
clonedLayerOrder.style.left = "-9999px";
document.body.appendChild(clonedLayerOrder);

// Clone the Mapbox Legend component
const clonedMapLegend = mapLegendElement.cloneNode(true);
clonedMapLegend.style.position = "absolute";
clonedMapLegend.style.left = "-9999px";
document.body.appendChild(clonedMapLegend);

// Convert legend images to Base64 before capturing
await this.convertImagesToBase64(clonedMapLegend);

this.map.once('render', async () => {
const mapCanvas = this.map.getCanvas();
const mapImage = mapCanvas.toDataURL("image/png");

// Capture cloned components with html2canvas
const layerOrderCanvas = await html2canvas(clonedLayerOrder, { backgroundColor: null });
const mapLegendCanvas = await html2canvas(clonedMapLegend, { backgroundColor: null });

document.body.removeChild(clonedLayerOrder);
document.body.removeChild(clonedMapLegend);

// Merge Map Image, Layer Order, and Map Legend
const finalCanvas = document.createElement("canvas");
finalCanvas.width = mapCanvas.width;
finalCanvas.height = mapCanvas.height;
const ctx = finalCanvas.getContext("2d");

// Draw the map first
const mapImg = new Image();
mapImg.src = mapImage;
await new Promise((resolve) => (mapImg.onload = resolve));
ctx.drawImage(mapImg, 0, 0);

// Draw a white box with a grey border for the date and time
const timestamp = new Date().toLocaleString();
const text = this.viewerName + " - " + "Image generated at " + timestamp;
ctx.font = "16px Arial";
const textWidth = ctx.measureText(text).width + 20;
const textHeight = 30;
ctx.fillStyle = "white";
ctx.fillRect(5, 5, textWidth, textHeight);
ctx.strokeStyle = "grey";
ctx.strokeRect(5, 5, textWidth, textHeight);

// Draw the timestamp text inside the box
ctx.fillStyle = "black";
ctx.fillText(text, 15, 25);

// Draw the cloned layer order UI in the bottom left corner
const layerX = 10;
const layerY = finalCanvas.height - layerOrderCanvas.height - 10;
ctx.drawImage(layerOrderCanvas, layerX, layerY);

// Draw the cloned map legend UI in the bottom right corner
const legendX = finalCanvas.width - mapLegendCanvas.width - 10;
const legendY = finalCanvas.height - mapLegendCanvas.height - 10;
ctx.drawImage(mapLegendCanvas, legendX, legendY);

// Convert to an image and trigger download
finalCanvas.toBlob((blob) => {
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = "map_with_layer_order_and_legend.png";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}, "image/png");
});

this.map.triggerRepaint();
},

async convertImagesToBase64(container) {
const images = container.querySelectorAll("img");
const promises = Array.from(images).map(async (img) => {
if (!img.src.startsWith("data:image")) {
const base64 = await this.imageToBase64(img.src);
if (base64) {
img.src = base64;
}
}
});
await Promise.all(promises);
},

imageToBase64(url) {
return new Promise((resolve) => {
fetch(url)
.then(response => response.blob())
.then(blob => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
})
.catch(() => resolve(null));
});
}
}
};
</script>
Loading