diff --git a/src/components/DataExportSection.vue b/src/components/DataExportSection.vue index 8a195918..0c14b570 100644 --- a/src/components/DataExportSection.vue +++ b/src/components/DataExportSection.vue @@ -1,3 +1,17 @@ +/** + * Data Export Section Component + * + * Provides UI and functionality for exporting user data and backing up identifier seeds. + * Includes buttons for seed backup and database export, with platform-specific download instructions. + * + * @component + * @displayName DataExportSection + * @example + * ```vue + * <DataExportSection :active-did="currentDid" /> + * ``` + */ + <template> <div id="sectionDataExport" @@ -28,17 +42,17 @@ > If no download happened yet, click again here to download now. </a> - <div class="mt-4"> + <div class="mt-4" v-if="showPlatformInstructions"> <p> After the download, you can save the file in your preferred storage location. </p> <ul> - <li class="list-disc list-outside ml-4"> + <li v-if="platformService.isCapacitor() && isIOS" class="list-disc list-outside ml-4"> On iOS: Choose "More..." and select a place in iCloud, or go "Back" and save to another location. </li> - <li class="list-disc list-outside ml-4"> + <li v-if="platformService.isCapacitor() && !isIOS" class="list-disc list-outside ml-4"> On Android: Choose "Open" and then share <font-awesome icon="share-nodes" class="fa-fw" /> to your prefered place. @@ -53,38 +67,106 @@ import { Component, Prop, Vue } from "vue-facing-decorator"; import { NotificationIface } from "../constants/app"; import { db } from "../db/index"; import { logger } from "../utils/logger"; +import { PlatformServiceFactory } from "../services/PlatformServiceFactory"; +import { PlatformService } from "../services/PlatformService"; +/** + * @vue-component + * Data Export Section Component + * Handles database export and seed backup functionality with platform-specific behavior + */ @Component export default class DataExportSection extends Vue { + /** + * Notification function injected by Vue + * Used to show success/error messages to the user + */ $notify!: (notification: NotificationIface, timeout?: number) => void; + /** + * Active DID (Decentralized Identifier) of the user + * Controls visibility of seed backup option + * @required + */ @Prop({ required: true }) readonly activeDid!: string; + + /** + * URL for the database export download + * Created and revoked dynamically during export process + * Only used in web platform + */ downloadUrl = ""; + /** + * Platform service instance for platform-specific operations + */ + private platformService: PlatformService = PlatformServiceFactory.getInstance(); + + /** + * Whether the current platform is iOS + */ + private get isIOS(): boolean { + return /iPad|iPhone|iPod/.test(navigator.userAgent); + } + + /** + * Whether to show platform-specific instructions + */ + private get showPlatformInstructions(): boolean { + return this.platformService.isCapacitor(); + } + + /** + * Lifecycle hook to clean up resources + * Revokes object URL when component is unmounted (web platform only) + */ beforeUnmount() { - if (this.downloadUrl) { + if (this.downloadUrl && this.platformService.isWeb()) { URL.revokeObjectURL(this.downloadUrl); } } + /** + * Exports the database to a JSON file + * Uses platform-specific methods for saving the exported data + * Shows success/error notifications to user + * + * @throws {Error} If export fails + * @emits {Notification} Success or error notification + */ public async exportDatabase() { try { const blob = await db.export({ prettyJson: true }); - this.downloadUrl = URL.createObjectURL(blob); - const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement; - downloadAnchor.href = this.downloadUrl; - downloadAnchor.download = `${db.name}-backup.json`; - downloadAnchor.click(); + const fileName = `${db.name}-backup.json`; + + if (this.platformService.isWeb()) { + // Web platform: Use download link + this.downloadUrl = URL.createObjectURL(blob); + const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement; + downloadAnchor.href = this.downloadUrl; + downloadAnchor.download = fileName; + downloadAnchor.click(); + setTimeout(() => URL.revokeObjectURL(this.downloadUrl), 1000); + } else if (this.platformService.isCapacitor()) { + // Capacitor platform: Write to app directory + const content = await blob.text(); + await this.platformService.writeFile(fileName, content); + } else { + // Other platforms: Use platform service + await this.platformService.writeFile(fileName, await blob.text()); + } + this.$notify( { group: "alert", type: "success", - title: "Download Started", - text: "See your downloads directory for the backup. It is in the Dexie format.", + title: "Export Successful", + text: this.platformService.isWeb() + ? "See your downloads directory for the backup. It is in the Dexie format." + : "The backup has been saved to your device.", }, -1, ); - setTimeout(() => URL.revokeObjectURL(this.downloadUrl), 1000); } catch (error) { logger.error("Export Error:", error); this.$notify( @@ -99,15 +181,23 @@ export default class DataExportSection extends Vue { } } + /** + * Computes class names for the initial download button + * @returns Object with 'hidden' class when download is in progress (web platform only) + */ public computedStartDownloadLinkClassNames() { return { - hidden: this.downloadUrl, + hidden: this.downloadUrl && this.platformService.isWeb(), }; } + /** + * Computes class names for the secondary download link + * @returns Object with 'hidden' class when no download is available or not on web platform + */ public computedDownloadLinkClassNames() { return { - hidden: !this.downloadUrl, + hidden: !this.downloadUrl || !this.platformService.isWeb(), }; } }