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": {
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);
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..796f5e5 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,42 @@ 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';
}
+
+/**
+ * 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);
+ }
+}
+
+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 = filename;
+ a.click();
+
+ URL.revokeObjectURL(url);
+}
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
}
}