Skip to content

Commit bdd2ea3

Browse files
Sebastian Fellner 💯Sebastian Fellner 💯
authored andcommitted
fix: restore PDF downloads and locale switching
1 parent 748e7c0 commit bdd2ea3

5 files changed

Lines changed: 104 additions & 80 deletions

File tree

components/ChatVisualization/PdfDownloadPopup.vue

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,12 @@ export default {
204204
GTAG_PAYMENT,
205205
GTAG_PDF,
206206
progress: 0,
207+
pdfWorker: null,
207208
};
208209
},
210+
beforeUnmount() {
211+
this.closePdfWorker();
212+
},
209213
methods: {
210214
handleFreePdfClick() {
211215
this.downloadSample();
@@ -225,9 +229,12 @@ export default {
225229
},
226230
onError() {},
227231
async download(isSample = false) {
228-
if (process.browser) {
229-
this.isLoading = true;
230-
this.progress = 0;
232+
if (!import.meta.client) return;
233+
234+
this.isLoading = true;
235+
this.progress = 0;
236+
237+
try {
231238
// the graphs need to be converted to an image beforehand, as the web worker has no access to document
232239
const chatTimeline = await loadImage("#chat-timeline");
233240
const messagesPerTimeOfDay = await loadImage(
@@ -237,16 +244,17 @@ export default {
237244
const radarMonth = await loadImage("#radar-month");
238245
const radarDay = await loadImage("#radar-day");
239246
240-
const worker = new PDFWorker();
241-
worker.addEventListener("message", this.workerResponseHandler);
247+
this.pdfWorker = new PDFWorker();
248+
this.pdfWorker.addEventListener("message", this.workerResponseHandler);
249+
this.pdfWorker.addEventListener("error", this.pdfErrorHandler);
242250
243251
const chat = objectToDictionary(this.chat); // remove functions
244252
chat.funFacts = await this.chat.getFunFacts(); // set funfacts beforehand instead of using function call
245253
246-
worker.postMessage({
254+
this.pdfWorker.postMessage({
247255
// pass all data to service worker
248256
chat: chat,
249-
attachments: this.attachments,
257+
attachments: objectToDictionary(this.attachments),
250258
ego: this.ego,
251259
isSample,
252260
chatTimeline,
@@ -255,6 +263,8 @@ export default {
255263
radarMonth,
256264
radarDay,
257265
});
266+
} catch (error) {
267+
this.pdfErrorHandler(error);
258268
}
259269
},
260270
downloadSample() {
@@ -269,11 +279,27 @@ export default {
269279
const blob = new Blob([data.data], { type: "application/pdf" });
270280
saveAs(blob, "WhatsAnalyze - " + this.ego);
271281
this.isLoading = false;
282+
this.closePdfWorker();
272283
}
273284
if (data.type === "progress") {
274285
this.progress = data.data;
275286
}
276287
},
288+
pdfErrorHandler(error) {
289+
console.error("PDF generation failed", error);
290+
this.$sentry?.captureException(error);
291+
this.isLoading = false;
292+
this.progress = 0;
293+
this.closePdfWorker();
294+
},
295+
closePdfWorker() {
296+
if (!this.pdfWorker) return;
297+
298+
this.pdfWorker.removeEventListener("message", this.workerResponseHandler);
299+
this.pdfWorker.removeEventListener("error", this.pdfErrorHandler);
300+
this.pdfWorker.terminate();
301+
this.pdfWorker = null;
302+
},
277303
gtagEvent,
278304
},
279305
};

components/LanguageSwitcher.vue

Lines changed: 24 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,36 @@
11
<template>
2-
<select v-model="selectedLocale" class="text-md-h3 text-h4">
2+
<select
3+
:value="locale"
4+
class="text-md-h3 text-h4"
5+
aria-label="Select language"
6+
@change="setLocale"
7+
>
38
<option
49
v-for="locale in availableLocales"
510
:key="locale.code"
611
:value="locale.code"
712
>
8-
{{ locale.flag }}
13+
{{ flags[locale.code] }} {{ locale.name }}
914
</option>
1015
</select>
1116
</template>
1217

13-
<script>
14-
export default {
15-
data() {
16-
return {
17-
selectedLocale: this.$i18n.locale,
18-
availableLocales: [
19-
{
20-
code: "en",
21-
name: "English",
22-
flag: "🇬🇧",
23-
iso: "en-GB",
24-
},
25-
{
26-
code: "de",
27-
name: "Deutsch",
28-
flag: "🇩🇪",
29-
iso: "de-DE",
30-
},
31-
{
32-
code: "es",
33-
name: "Español",
34-
flag: "🇪🇸",
35-
iso: "es-ES",
36-
},
37-
{
38-
code: "pt",
39-
name: "Português",
40-
flag: "🇧🇷",
41-
iso: "pt-PT",
42-
},
43-
{
44-
code: "fr",
45-
name: "Français",
46-
flag: "🇫🇷",
47-
iso: "fr-FR",
48-
},
49-
{
50-
code: "it",
51-
name: "Italiano",
52-
flag: "🇮🇹",
53-
iso: "it-IT",
54-
},
55-
],
56-
};
57-
},
58-
watch: {
59-
selectedLocale(newLocale) {
60-
this.$router.push(this.switchLocalePath(newLocale));
61-
},
62-
},
18+
<script setup>
19+
const { locale, locales } = useI18n();
20+
const switchLocalePath = useSwitchLocalePath();
21+
22+
const flags = {
23+
en: "🇬🇧",
24+
de: "🇩🇪",
25+
es: "🇪🇸",
26+
pt: "🇧🇷",
27+
fr: "🇫🇷",
28+
it: "🇮🇹",
29+
};
30+
31+
const availableLocales = computed(() => locales.value);
32+
33+
const setLocale = (event) => {
34+
return navigateTo(switchLocalePath(event.target.value));
6335
};
6436
</script>

nuxt.config.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,12 @@ export default defineNuxtConfig({
133133
defaultLocale: "en",
134134
strategy: "prefix_except_default",
135135
locales: [
136-
{ code: "en", language: "en-US" },
137-
{ code: "de", language: "de-DE" },
138-
{ code: "es", language: "es-ES" },
139-
{ code: "fr", language: "fr-FR" },
140-
{ code: "pt", language: "pt-PT" },
141-
{ code: "it", language: "it-IT" },
136+
{ code: "en", language: "en-US", name: "English" },
137+
{ code: "de", language: "de-DE", name: "Deutsch" },
138+
{ code: "es", language: "es-ES", name: "Español" },
139+
{ code: "fr", language: "fr-FR", name: "Français" },
140+
{ code: "pt", language: "pt-PT", name: "Português" },
141+
{ code: "it", language: "it-IT", name: "Italiano" },
142142
],
143143
detectBrowserLanguage: {
144144
alwaysRedirect: false,

tests/e2e/home.spec.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,21 @@ test("analyzes the example chat without uploading its contents", async ({
4444
return body.includes("Jane Doe") || body.includes("John Doe");
4545
});
4646
expect(uploadedChatRequests).toEqual([]);
47+
48+
const downloadPromise = page.waitForEvent("download", { timeout: 60_000 });
49+
await page
50+
.getByRole("button", { name: /Download free preview PDF/i })
51+
.click();
52+
await downloadPromise;
53+
});
54+
55+
test("switches to a localized route", async ({ page }) => {
56+
await page.getByLabel("Select language").selectOption("de");
57+
58+
await expect(page).toHaveURL(/\/de\/?$/);
59+
await expect(
60+
page.getByText("Analysiere dein WhatsApp Chat in Sekunden", { exact: true })
61+
).toBeVisible();
4762
});
4863

4964
test("renders migrated markdown content", async ({ page }) => {

utils/utils.js

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,27 @@ export function lastDate(chat) {
3535

3636
// this is used on objects that should be transfered to the web worker
3737
// the webworker can not receive functions
38-
export function objectToDictionary(obj, dict = {}) {
39-
for (const [key, value] of Object.entries(obj)) {
40-
if (
41-
typeof value === "object" &&
42-
value !== null &&
43-
!Array.isArray(value) &&
44-
!(value instanceof Date)
45-
) {
46-
objectToDictionary(value, (dict[key] = {}));
47-
} else if (typeof value !== "function") {
48-
dict[key] = value;
49-
}
38+
export function objectToDictionary(value) {
39+
if (typeof value === "function") return undefined;
40+
if (value === null || typeof value !== "object") return value;
41+
if (
42+
value instanceof Date ||
43+
value instanceof ArrayBuffer ||
44+
ArrayBuffer.isView(value)
45+
) {
46+
return value;
47+
}
48+
49+
if (Array.isArray(value)) {
50+
return value.map(objectToDictionary);
51+
}
52+
53+
const dict = {};
54+
for (const [key, nestedValue] of Object.entries(value)) {
55+
const clonedValue = objectToDictionary(nestedValue);
56+
if (clonedValue !== undefined) dict[key] = clonedValue;
5057
}
58+
5159
return dict;
5260
}
5361

@@ -63,9 +71,12 @@ export const getImgSizes = function (imgUrl) {
6371
};
6472

6573
export const loadImage = async function (selector) {
66-
const imgUrl = document
67-
.querySelector(selector + ">*>canvas")
68-
.toDataURL("image/png");
74+
const canvas = document.querySelector(`${selector} canvas`);
75+
if (!(canvas instanceof HTMLCanvasElement)) {
76+
throw new Error(`Could not find chart canvas in "${selector}"`);
77+
}
78+
79+
const imgUrl = canvas.toDataURL("image/png");
6980
const sizes = await getImgSizes(imgUrl);
7081
return { img: imgUrl, width: sizes[0], height: sizes[1] };
7182
};

0 commit comments

Comments
 (0)