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.
252 lines
8.6 KiB
252 lines
8.6 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. * Also
|
|
displays a list of backup files with options to open them in the device's file
|
|
explorer. * * @component * @displayName DataExportSection * @example * ```vue *
|
|
<DataExportSection :active-did="currentDid" />
|
|
* ``` */
|
|
|
|
<template>
|
|
<div
|
|
id="sectionDataExport"
|
|
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
|
|
>
|
|
<div class="mb-2 font-bold">Data Export</div>
|
|
<router-link
|
|
v-if="activeDid"
|
|
:to="{ name: 'seed-backup' }"
|
|
class="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"
|
|
>
|
|
Backup Identifier Seed
|
|
</router-link>
|
|
|
|
<button
|
|
:class="computedStartDownloadLinkClassNames()"
|
|
class="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"
|
|
@click="exportDatabase()"
|
|
>
|
|
Download Contacts
|
|
</button>
|
|
<a
|
|
ref="downloadLink"
|
|
:class="computedDownloadLinkClassNames()"
|
|
class="block w-full text-center text-md bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
|
|
>
|
|
If no download happened yet, click again here to download now.
|
|
</a>
|
|
<div v-if="platformCapabilities.needsFileHandlingInstructions" class="mt-4">
|
|
<p>
|
|
After the download, you can save the file in your preferred storage
|
|
location.
|
|
</p>
|
|
<ul>
|
|
<li
|
|
v-if="platformCapabilities.isIOS"
|
|
class="list-disc list-outside ml-4"
|
|
>
|
|
On iOS: Files are saved to Documents folder (accessible via Files app) and persist between app installations.
|
|
</li>
|
|
<li
|
|
v-if="platformCapabilities.isMobile && !platformCapabilities.isIOS"
|
|
class="list-disc list-outside ml-4"
|
|
>
|
|
On Android: Files are saved to Downloads/TimeSafari or external storage (accessible via file managers) and persist between app installations.
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Backup Files List -->
|
|
<div v-if="platformCapabilities.hasFileSystem" class="mt-6 pt-6 border-t border-gray-300">
|
|
<BackupFilesList ref="backupFilesList" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Component, Prop, Vue } from "vue-facing-decorator";
|
|
import { AppString, NotificationIface } from "../constants/app";
|
|
|
|
import { Contact } from "../db/tables/contacts";
|
|
import * as databaseUtil from "../db/databaseUtil";
|
|
|
|
import { logger, getTimestampForFilename } from "../utils/logger";
|
|
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
|
import {
|
|
PlatformService,
|
|
PlatformCapabilities,
|
|
} from "../services/PlatformService";
|
|
import { contactsToExportJson } from "../libs/util";
|
|
import BackupFilesList from "./BackupFilesList.vue";
|
|
|
|
/**
|
|
* @vue-component
|
|
* Data Export Section Component
|
|
* Handles database export and seed backup functionality with platform-specific behavior
|
|
*/
|
|
@Component({ components: { BackupFilesList } })
|
|
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();
|
|
|
|
/**
|
|
* Platform capabilities for the current platform
|
|
*/
|
|
private get platformCapabilities(): PlatformCapabilities {
|
|
return this.platformService.getCapabilities();
|
|
}
|
|
|
|
/**
|
|
* Lifecycle hook to clean up resources
|
|
* Revokes object URL when component is unmounted (web platform only)
|
|
*/
|
|
beforeUnmount() {
|
|
if (this.downloadUrl && this.platformCapabilities.hasFileDownload) {
|
|
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 {
|
|
let allContacts: Contact[] = [];
|
|
const platformService = PlatformServiceFactory.getInstance();
|
|
const result = await platformService.dbQuery(`SELECT * FROM contacts`);
|
|
if (result) {
|
|
allContacts = databaseUtil.mapQueryResultToValues(
|
|
result,
|
|
) as unknown as Contact[];
|
|
}
|
|
// if (USE_DEXIE_DB) {
|
|
// await db.open();
|
|
// allContacts = await db.contacts.toArray();
|
|
// }
|
|
|
|
// Convert contacts to export format
|
|
const exportData = contactsToExportJson(allContacts);
|
|
const jsonStr = JSON.stringify(exportData, null, 2);
|
|
const blob = new Blob([jsonStr], { type: "application/json" });
|
|
|
|
// Create timestamped filename
|
|
const timestamp = getTimestampForFilename();
|
|
const fileName = `${AppString.APP_NAME_NO_SPACES}-backup-contacts-${timestamp}.json`;
|
|
|
|
if (this.platformCapabilities.hasFileDownload) {
|
|
// 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.platformCapabilities.hasFileSystem) {
|
|
// Native platform: Write to user-accessible location and share
|
|
const result = await this.platformService.writeAndShareFile(
|
|
fileName,
|
|
jsonStr,
|
|
{
|
|
allowLocationSelection: true,
|
|
showLocationSelectionDialog: true,
|
|
mimeType: "application/json"
|
|
}
|
|
);
|
|
|
|
// Handle the result
|
|
if (!result.saved) {
|
|
throw new Error(result.error || "Failed to save file");
|
|
}
|
|
} else {
|
|
throw new Error("This platform does not support file downloads.");
|
|
}
|
|
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "success",
|
|
title: "Export Successful",
|
|
text: this.platformCapabilities.hasFileDownload
|
|
? "See your downloads directory for the backup."
|
|
: "Backup saved to persistent storage that survives app installations. Use the share dialog to access your file and choose where to save it permanently.",
|
|
},
|
|
5000,
|
|
);
|
|
|
|
// Refresh the backup files list
|
|
const backupFilesList = this.$refs.backupFilesList as any;
|
|
if (backupFilesList && typeof backupFilesList.refreshAfterSave === 'function') {
|
|
await backupFilesList.refreshAfterSave();
|
|
}
|
|
} catch (error) {
|
|
logger.error("Export Error:", error);
|
|
this.$notify(
|
|
{
|
|
group: "alert",
|
|
type: "danger",
|
|
title: "Export Error",
|
|
text: "There was an error exporting the data.",
|
|
},
|
|
3000,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 && this.platformCapabilities.hasFileDownload,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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 || !this.platformCapabilities.hasFileDownload,
|
|
};
|
|
}
|
|
|
|
async mounted() {
|
|
// Ensure permissions are requested and refresh backup files list on mount
|
|
if (this.platformCapabilities.hasFileSystem) {
|
|
const backupFilesList = this.$refs.backupFilesList as any;
|
|
if (backupFilesList && typeof backupFilesList.refreshFiles === 'function') {
|
|
await backupFilesList.refreshFiles();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|