You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
7.1 KiB
220 lines
7.1 KiB
/** * 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. * *
|
|
Features: * - Platform-specific export handling (web vs. native) * - Proper
|
|
resource cleanup for blob URLs * - Robust error handling with user-friendly
|
|
messages * - Conditional UI based on platform capabilities * * @component *
|
|
@displayName DataExportSection * @example * ```vue *
|
|
<DataExportSection :active-did="currentDid" />
|
|
* ``` * * @author Matthew Raymer * @since 2025-01-25 * @version 1.1.0 */
|
|
|
|
<template>
|
|
<div id="sectionDataExport" :class="containerClasses">
|
|
<div :class="titleClasses">Data Export</div>
|
|
<router-link
|
|
v-if="activeDid"
|
|
:to="{ name: 'seed-backup' }"
|
|
:class="backupButtonClasses"
|
|
>
|
|
Backup Identifier Seed
|
|
</router-link>
|
|
|
|
<button
|
|
:disabled="isExporting"
|
|
:class="exportButtonClasses"
|
|
@click="exportDatabase()"
|
|
>
|
|
{{ isExporting ? "Exporting..." : "Download Contacts" }}
|
|
</button>
|
|
|
|
<div
|
|
v-if="capabilities.needsFileHandlingInstructions"
|
|
:class="instructionsContainerClasses"
|
|
>
|
|
<p>
|
|
After the export, you can save the file in your preferred storage
|
|
location.
|
|
</p>
|
|
<ul>
|
|
<li v-if="capabilities.isIOS" :class="listItemClasses">
|
|
On iOS: You will be prompted to choose a location to save your backup
|
|
file.
|
|
</li>
|
|
<li
|
|
v-if="capabilities.isMobile && !capabilities.isIOS"
|
|
:class="listItemClasses"
|
|
>
|
|
On Android: You will be prompted to choose a location to save your
|
|
backup file.
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Component, Prop, Vue } from "vue-facing-decorator";
|
|
import * as R from "ramda";
|
|
|
|
import { AppString, NotificationIface } from "../constants/app";
|
|
import { Contact } from "../db/tables/contacts";
|
|
|
|
import { logger } from "../utils/logger";
|
|
import { contactsToExportJson } from "../libs/util";
|
|
import { createNotifyHelpers } from "@/utils/notify";
|
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
|
|
|
/**
|
|
* @vue-component
|
|
* Data Export Section Component
|
|
* Handles database export and seed backup functionality with platform-specific behavior
|
|
*
|
|
* Features:
|
|
* - Automatic date stamping of backup files (YYYY-MM-DD format)
|
|
* - Platform-specific export handling with proper abstraction
|
|
* - Robust error handling and user notifications
|
|
* - Template streamlined with computed CSS properties
|
|
*/
|
|
@Component({
|
|
mixins: [PlatformServiceMixin],
|
|
})
|
|
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;
|
|
|
|
/**
|
|
* Flag indicating if export is currently in progress
|
|
* Used to show loading state and prevent multiple simultaneous exports
|
|
*/
|
|
isExporting = false;
|
|
|
|
/**
|
|
* Notification helper for consistent notification patterns
|
|
* Created as a getter to ensure $notify is available when called
|
|
*/
|
|
notify!: ReturnType<typeof createNotifyHelpers>;
|
|
|
|
/**
|
|
* NOTE: PlatformServiceMixin provides both concise helpers (e.g. $contacts, capabilities)
|
|
* and the full platformService instance for advanced/native features (e.g. writeAndShareFile).
|
|
* Always use 'this.platformService' as a property (never as a function).
|
|
*/
|
|
declare readonly platformService: import("@/services/PlatformService").PlatformService;
|
|
|
|
/**
|
|
* CSS classes for the main container
|
|
*/
|
|
get containerClasses(): string {
|
|
return "bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8";
|
|
}
|
|
|
|
/**
|
|
* CSS classes for the section title
|
|
*/
|
|
get titleClasses(): string {
|
|
return "mb-2 font-bold";
|
|
}
|
|
|
|
/**
|
|
* CSS classes for the backup button (router link)
|
|
*/
|
|
get backupButtonClasses(): string {
|
|
return "block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2 mt-2";
|
|
}
|
|
|
|
/**
|
|
* CSS classes for the export button
|
|
*/
|
|
get exportButtonClasses(): string {
|
|
return "block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md disabled:opacity-50 disabled:cursor-not-allowed";
|
|
}
|
|
|
|
/**
|
|
* CSS classes for the instructions container
|
|
*/
|
|
get instructionsContainerClasses(): string {
|
|
return "mt-4";
|
|
}
|
|
|
|
/**
|
|
* CSS classes for list items in instructions
|
|
*/
|
|
get listItemClasses(): string {
|
|
return "list-disc list-outside ml-4";
|
|
}
|
|
|
|
/**
|
|
* Computed property for the export file name
|
|
* Includes today's date for easy identification of backup files
|
|
*/
|
|
private get fileName(): string {
|
|
const today = new Date();
|
|
const dateString = today.toISOString().split("T")[0]; // YYYY-MM-DD format
|
|
return `${AppString.APP_NAME_NO_SPACES}-backup-contacts-${dateString}.json`;
|
|
}
|
|
|
|
/**
|
|
* Exports the database to a JSON file
|
|
* Uses the platform service to handle platform-specific export logic
|
|
* Shows success/error notifications to user
|
|
*
|
|
* @throws {Error} If export fails
|
|
*/
|
|
public async exportDatabase(): Promise<void> {
|
|
if (this.isExporting) {
|
|
return; // Prevent multiple simultaneous exports
|
|
}
|
|
|
|
try {
|
|
this.isExporting = true;
|
|
|
|
// Fetch contacts from database using mixin's cached method
|
|
const allContacts = await this.$contacts();
|
|
|
|
// Convert contacts to export format
|
|
const processedContacts: Contact[] = allContacts.map((contact) => {
|
|
// first remove the contactMethods field, mostly to cast to a clear type (that will end up with JSON objects)
|
|
const exContact: Contact = R.omit(["contactMethods"], contact);
|
|
// now add contactMethods as a true array of ContactMethod objects
|
|
exContact.contactMethods = contact.contactMethods
|
|
? (typeof contact.contactMethods === 'string' && contact.contactMethods.trim() !== ''
|
|
? JSON.parse(contact.contactMethods)
|
|
: [])
|
|
: [];
|
|
return exContact;
|
|
});
|
|
|
|
const exportData = contactsToExportJson(processedContacts);
|
|
const jsonStr = JSON.stringify(exportData, null, 2);
|
|
|
|
// Use platform service to handle export (no platform-specific logic here!)
|
|
await this.platformService.writeAndShareFile(this.fileName, jsonStr);
|
|
|
|
this.notify.success(
|
|
"Contact export completed successfully. Check your downloads or share dialog.",
|
|
);
|
|
} catch (error) {
|
|
logger.error("Export Error:", error);
|
|
this.notify.error(
|
|
`There was an error exporting the data: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
);
|
|
} finally {
|
|
this.isExporting = false;
|
|
}
|
|
}
|
|
|
|
created() {
|
|
this.notify = createNotifyHelpers(this.$notify);
|
|
}
|
|
}
|
|
</script>
|
|
|