From c33e131980827f122d79950f88723b0ae7fce9f8 Mon Sep 17 00:00:00 2001 From: firu11 Date: Sat, 30 May 2026 17:33:40 +0200 Subject: [PATCH 1/5] basic zip setup --- src/types/core.ts | 1 + src/utils.ts | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/types/core.ts b/src/types/core.ts index f7de11b..4ca787f 100644 --- a/src/types/core.ts +++ b/src/types/core.ts @@ -32,6 +32,7 @@ export interface CalendarApi { pullAll(): void; pushAll(): void; + exportZip(calendar: string): ArrayBuffer; createEvent(event: CalendarEvent): CalendarEvent; updateEvent(event: CalendarEvent): CalendarEvent; diff --git a/src/utils.ts b/src/utils.ts index 81c31db..b4b9b10 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,6 +2,7 @@ import { DateTime } from 'luxon'; import { type RouteParamsGeneric, type Router } from 'vue-router'; import { useSettings } from '@/composables/useSettings'; import type { CalendarEvent } from './types/core'; +import { CalendarCore } from './wasm/core-wrapper'; const { settings } = useSettings(); @@ -122,3 +123,17 @@ export function timeInPercentOnTimeline(datetime: DateTime): number { export function isWholeDay(event: CalendarEvent): boolean { return event.from.toFormat('HH:mm') == '00:00' && event.to.toFormat('HH:mm') == '23:59'; } + +export async function exportZip(calendar: string) { + const bytes = await CalendarCore.exportZip(calendar); + + const blob = new Blob([bytes], { type: 'application/zip' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'git-calendar-data.zip'; + a.click(); + + URL.revokeObjectURL(url); +} From 781009a07335f391459e8d6c49fe67b866b5e309 Mon Sep 17 00:00:00 2001 From: firu11 Date: Sat, 30 May 2026 17:34:03 +0200 Subject: [PATCH 2/5] fix byte array parsing --- src/wasm/mapper.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/wasm/mapper.ts b/src/wasm/mapper.ts index 4a5cb0e..a530761 100644 --- a/src/wasm/mapper.ts +++ b/src/wasm/mapper.ts @@ -21,9 +21,25 @@ function toSnakeCase(str: string): string { // // So this hydrateDates tries to parse it beforehand. export function hydrateDates(data: unknown): T { - // non-object, primitive, null + // primitives / null if (data === null || typeof data !== 'object') return data as T; + // preserve binary data + if ( + data instanceof Uint8Array || + data instanceof ArrayBuffer || + ArrayBuffer.isView(data) || + data instanceof Blob || + data instanceof File + ) { + return data as T; + } + + // preserve Luxon values if they ever come through here + if (DateTime.isDateTime(data)) { + return data as T; + } + if (Array.isArray(data)) { return data.map(hydrateDates) as unknown as T; // recursively for arrays } @@ -44,10 +60,8 @@ export function hydrateDates(data: unknown): T { } else { result[key] = dt; } - } else if (value && typeof value === 'object' && !Array.isArray(value)) { - result[key] = hydrateDates(value); // recursively for objects } else { - result[key] = value; // just assign + result[key] = hydrateDates(value); // recursively } } From 0cd9fd685da6d14c4253932262ce066d9b66d61e Mon Sep 17 00:00:00 2001 From: firu11 Date: Sun, 31 May 2026 00:48:22 +0200 Subject: [PATCH 3/5] add zip btn --- src/components/SideBar.vue | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/SideBar.vue b/src/components/SideBar.vue index cdaae3c..60ecb33 100644 --- a/src/components/SideBar.vue +++ b/src/components/SideBar.vue @@ -4,6 +4,8 @@ import { useSlots } from 'vue'; import SidebarCloseBtn from '@/components/SidebarCloseBtn.vue'; import { useSidebar } from '@/composables/useSidebar'; import NewEventBtn from '@/components/NewEventBtn.vue'; +import { exportZip } from '@/utils'; +import { LuFolderArchive } from 'vue-icons-plus/lu'; const slots = useSlots(); const sidebar = useSidebar(); @@ -29,6 +31,9 @@ const sidebar = useSidebar(); + @@ -63,6 +68,7 @@ aside { padding: 0.3rem; width: 2.1rem; aspect-ratio: 1/1; + background-color: transparent; border-radius: var(--small-border-radius); From 2d511769dbc08c9baa9193e8996f67477e7dd113 Mon Sep 17 00:00:00 2001 From: firu11 Date: Sun, 31 May 2026 00:59:55 +0200 Subject: [PATCH 4/5] download with location prompt --- src/utils.ts | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index b4b9b10..796f5e5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -124,15 +124,40 @@ export function isWholeDay(event: CalendarEvent): boolean { return event.from.toFormat('HH:mm') == '00:00' && event.to.toFormat('HH:mm') == '23:59'; } -export async function exportZip(calendar: string) { - const bytes = await CalendarCore.exportZip(calendar); +/** + * Downloads a zipped calendar or all calendars if ''. + */ +export async function exportZip(calendar: string = '') { + const zipBytes = await CalendarCore.exportZip(calendar); + const fileName = calendar ? `${calendar}.zip` : `git-calendar-data.zip`; + + if ('showSaveFilePicker' in window && typeof window.showSaveFilePicker == 'function') { + const handle = await window.showSaveFilePicker({ + suggestedName: fileName, + types: [ + { + description: 'ZIP archive', + accept: { 'application/zip': ['.zip'] }, + }, + ], + }); + + const writable = await handle.createWritable(); + await writable.write(zipBytes); + await writable.close(); + } else { + // firefox doesnt have the showSaveFilePicker method + downloadBlob(zipBytes, fileName); + } +} - const blob = new Blob([bytes], { type: 'application/zip' }); +function downloadBlob(data: BlobPart, filename: string) { + const blob = new Blob([data], { type: 'application/zip' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = 'git-calendar-data.zip'; + a.download = filename; a.click(); URL.revokeObjectURL(url); From e148e143af3864bb93207ea9f703cfa0fa3ec109 Mon Sep 17 00:00:00 2001 From: firu11 Date: Sun, 31 May 2026 01:05:50 +0200 Subject: [PATCH 5/5] update core version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8870fea..d04ffdc 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build-only": "vite build", "type-check": "vue-tsc --build", "format": "prettier --write --experimental-cli src/", - "fetch-wasm": "curl -L -o src/assets/core.wasm https://github.com/git-calendar/core/releases/download/v0.1.1/core.wasm", + "fetch-wasm": "curl -L -o src/assets/core.wasm https://github.com/git-calendar/core/releases/download/v0.1.2/core.wasm", "fetch-wasm-latest": "curl -L -o src/assets/core.wasm https://github.com/git-calendar/core/releases/latest/download/core.wasm" }, "dependencies": {