refactor: centralize platform-specific behavior in platform services
- Add platform-specific capability methods to PlatformService interface: - getExportInstructions() - getExportSuccessMessage() - needsSecondaryDownloadLink() - needsDownloadCleanup() - Update platform service implementations: - WebPlatformService: Implement web-specific export behavior - CapacitorPlatformService: Implement mobile-specific export behavior - ElectronPlatformService: Add placeholder for export functionality - PyWebViewPlatformService: Add placeholder for export functionality - Refactor DataExportSection component: - Remove direct platform checks (isWeb, isCapacitor, etc.) - Use platform service capabilities for UI behavior - Improve error handling and logging - Add proper cleanup for web platform downloads - Update PlatformServiceFactory: - Make getInstance() async to support dynamic imports - Improve platform service initialization - Fix code style and documentation: - Update JSDoc comments - Fix string quotes consistency - Add proper error handling - Improve logging messages - Update Vite config: - Add all Capacitor dependencies to external list - Ensure consistent handling across platforms
This commit is contained in:
@@ -1,16 +1,9 @@
|
|||||||
/**
|
/** * Data Export Section Component * * Provides UI and functionality for
|
||||||
* Data Export Section Component
|
exporting user data and backing up identifier seeds. * Includes buttons for seed
|
||||||
*
|
backup and database export, with platform-specific download instructions. * *
|
||||||
* Provides UI and functionality for exporting user data and backing up identifier seeds.
|
@component * @displayName DataExportSection * @example * ```vue *
|
||||||
* Includes buttons for seed backup and database export, with platform-specific download instructions.
|
<DataExportSection :active-did="currentDid" />
|
||||||
*
|
* ``` */
|
||||||
* @component
|
|
||||||
* @displayName DataExportSection
|
|
||||||
* @example
|
|
||||||
* ```vue
|
|
||||||
* <DataExportSection :active-did="currentDid" />
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
@@ -36,28 +29,24 @@
|
|||||||
(excluding Identifier Data)
|
(excluding Identifier Data)
|
||||||
</button>
|
</button>
|
||||||
<a
|
<a
|
||||||
|
v-if="platformService?.needsSecondaryDownloadLink()"
|
||||||
ref="downloadLink"
|
ref="downloadLink"
|
||||||
:class="computedDownloadLinkClassNames()"
|
: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"
|
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.
|
If no download happened yet, click again here to download now.
|
||||||
</a>
|
</a>
|
||||||
<div class="mt-4" v-if="showPlatformInstructions">
|
<div
|
||||||
<p>
|
v-if="platformService?.getExportInstructions().length > 0"
|
||||||
After the download, you can save the file in your preferred storage
|
class="mt-4"
|
||||||
location.
|
>
|
||||||
|
<p
|
||||||
|
v-for="instruction in platformService?.getExportInstructions()"
|
||||||
|
:key="instruction"
|
||||||
|
class="list-disc list-outside ml-4"
|
||||||
|
>
|
||||||
|
{{ instruction }}
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
|
||||||
<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 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.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -100,32 +89,26 @@ export default class DataExportSection extends Vue {
|
|||||||
/**
|
/**
|
||||||
* Platform service instance for platform-specific operations
|
* Platform service instance for platform-specific operations
|
||||||
*/
|
*/
|
||||||
private platformService: PlatformService = PlatformServiceFactory.getInstance();
|
private platformService?: PlatformService;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Lifecycle hook to clean up resources
|
||||||
* Revokes object URL when component is unmounted (web platform only)
|
* Revokes object URL when component is unmounted (web platform only)
|
||||||
*/
|
*/
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
if (this.downloadUrl && this.platformService.isWeb()) {
|
if (this.downloadUrl && this.platformService?.needsDownloadCleanup()) {
|
||||||
URL.revokeObjectURL(this.downloadUrl);
|
URL.revokeObjectURL(this.downloadUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
this.platformService = await PlatformServiceFactory.getInstance();
|
||||||
|
logger.log(
|
||||||
|
"DataExportSection mounted on platform:",
|
||||||
|
process.env.VITE_PLATFORM,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exports the database to a JSON file
|
* Exports the database to a JSON file
|
||||||
* Uses platform-specific methods for saving the exported data
|
* Uses platform-specific methods for saving the exported data
|
||||||
@@ -136,24 +119,42 @@ export default class DataExportSection extends Vue {
|
|||||||
*/
|
*/
|
||||||
public async exportDatabase() {
|
public async exportDatabase() {
|
||||||
try {
|
try {
|
||||||
|
if (!this.platformService) {
|
||||||
|
this.platformService = await PlatformServiceFactory.getInstance();
|
||||||
|
}
|
||||||
|
logger.log(
|
||||||
|
"Starting database export on platform:",
|
||||||
|
process.env.VITE_PLATFORM,
|
||||||
|
);
|
||||||
|
|
||||||
const blob = await db.export({ prettyJson: true });
|
const blob = await db.export({ prettyJson: true });
|
||||||
const fileName = `${db.name}-backup.json`;
|
const fileName = `${db.name}-backup.json`;
|
||||||
|
logger.log("Database export details:", {
|
||||||
|
fileName,
|
||||||
|
blobSize: `${blob.size} bytes`,
|
||||||
|
platform: process.env.VITE_PLATFORM,
|
||||||
|
});
|
||||||
|
|
||||||
await this.platformService.exportDatabase(blob, fileName);
|
await this.platformService.exportDatabase(blob, fileName);
|
||||||
|
logger.log("Database export completed successfully:", {
|
||||||
|
fileName,
|
||||||
|
platform: process.env.VITE_PLATFORM,
|
||||||
|
});
|
||||||
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Export Successful",
|
title: "Export Successful",
|
||||||
text: this.platformService.isWeb()
|
text: this.platformService.getExportSuccessMessage(),
|
||||||
? "See your downloads directory for the backup. It is in the Dexie format."
|
|
||||||
: "The backup has been saved to your device.",
|
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Export Error:", error);
|
logger.error("Database export failed:", {
|
||||||
|
error,
|
||||||
|
platform: process.env.VITE_PLATFORM,
|
||||||
|
});
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -172,7 +173,8 @@ export default class DataExportSection extends Vue {
|
|||||||
*/
|
*/
|
||||||
public computedStartDownloadLinkClassNames() {
|
public computedStartDownloadLinkClassNames() {
|
||||||
return {
|
return {
|
||||||
hidden: this.downloadUrl && this.platformService.isWeb(),
|
hidden:
|
||||||
|
this.downloadUrl && this.platformService?.needsSecondaryDownloadLink(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +184,9 @@ export default class DataExportSection extends Vue {
|
|||||||
*/
|
*/
|
||||||
public computedDownloadLinkClassNames() {
|
public computedDownloadLinkClassNames() {
|
||||||
return {
|
return {
|
||||||
hidden: !this.downloadUrl || !this.platformService.isWeb(),
|
hidden:
|
||||||
|
!this.downloadUrl ||
|
||||||
|
!this.platformService?.needsSecondaryDownloadLink(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ import { retrieveSettingsForActiveAccount } from "../db/index";
|
|||||||
import { accessToken } from "../libs/crypto";
|
import { accessToken } from "../libs/crypto";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
||||||
|
import { PlatformService } from "../services/PlatformService";
|
||||||
|
|
||||||
@Component({ components: { VuePictureCropper } })
|
@Component({ components: { VuePictureCropper } })
|
||||||
export default class PhotoDialog extends Vue {
|
export default class PhotoDialog extends Vue {
|
||||||
@@ -123,13 +124,14 @@ export default class PhotoDialog extends Vue {
|
|||||||
uploading = false;
|
uploading = false;
|
||||||
visible = false;
|
visible = false;
|
||||||
|
|
||||||
private platformService = PlatformServiceFactory.getInstance();
|
private platformService?: PlatformService;
|
||||||
URL = window.URL || window.webkitURL;
|
URL = window.URL || window.webkitURL;
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
try {
|
try {
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const settings = await retrieveSettingsForActiveAccount();
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
|
this.platformService = await PlatformServiceFactory.getInstance();
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
logger.error("Error retrieving settings from database:", err);
|
logger.error("Error retrieving settings from database:", err);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -137,7 +139,10 @@ export default class PhotoDialog extends Vue {
|
|||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: err.message || "There was an error retrieving your settings.",
|
text:
|
||||||
|
err instanceof Error
|
||||||
|
? err.message
|
||||||
|
: "There was an error retrieving your settings.",
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -181,6 +186,9 @@ export default class PhotoDialog extends Vue {
|
|||||||
|
|
||||||
async takePhoto() {
|
async takePhoto() {
|
||||||
try {
|
try {
|
||||||
|
if (!this.platformService) {
|
||||||
|
this.platformService = await PlatformServiceFactory.getInstance();
|
||||||
|
}
|
||||||
const result = await this.platformService.takePicture();
|
const result = await this.platformService.takePicture();
|
||||||
this.blob = result.blob;
|
this.blob = result.blob;
|
||||||
this.fileName = result.fileName;
|
this.fileName = result.fileName;
|
||||||
@@ -200,6 +208,9 @@ export default class PhotoDialog extends Vue {
|
|||||||
|
|
||||||
async pickPhoto() {
|
async pickPhoto() {
|
||||||
try {
|
try {
|
||||||
|
if (!this.platformService) {
|
||||||
|
this.platformService = await PlatformServiceFactory.getInstance();
|
||||||
|
}
|
||||||
const result = await this.platformService.pickImage();
|
const result = await this.platformService.pickImage();
|
||||||
this.blob = result.blob;
|
this.blob = result.blob;
|
||||||
this.fileName = result.fileName;
|
this.fileName = result.fileName;
|
||||||
|
|||||||
@@ -100,4 +100,28 @@ export interface PlatformService {
|
|||||||
* @returns Promise that resolves when the deep link has been handled
|
* @returns Promise that resolves when the deep link has been handled
|
||||||
*/
|
*/
|
||||||
handleDeepLink(url: string): Promise<void>;
|
handleDeepLink(url: string): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets platform-specific instructions for saving exported files
|
||||||
|
* @returns Array of instruction strings for the current platform
|
||||||
|
*/
|
||||||
|
getExportInstructions(): string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the success message for database export
|
||||||
|
* @returns Success message appropriate for the current platform
|
||||||
|
*/
|
||||||
|
getExportSuccessMessage(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the platform requires a secondary download link
|
||||||
|
* @returns true if platform needs a secondary download link
|
||||||
|
*/
|
||||||
|
needsSecondaryDownloadLink(): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the platform needs cleanup after download
|
||||||
|
* @returns true if platform needs cleanup after download
|
||||||
|
*/
|
||||||
|
needsDownloadCleanup(): boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { PlatformService } from "./PlatformService";
|
import { PlatformService } from "./PlatformService";
|
||||||
import { WebPlatformService } from "./platforms/WebPlatformService";
|
import { WebPlatformService } from "./platforms/WebPlatformService";
|
||||||
import { CapacitorPlatformService } from "./platforms/CapacitorPlatformService";
|
|
||||||
import { ElectronPlatformService } from "./platforms/ElectronPlatformService";
|
|
||||||
import { PyWebViewPlatformService } from "./platforms/PyWebViewPlatformService";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory class for creating platform-specific service implementations.
|
* Factory class for creating platform-specific service implementations.
|
||||||
@@ -30,29 +27,43 @@ export class PlatformServiceFactory {
|
|||||||
*
|
*
|
||||||
* @returns {PlatformService} The singleton instance of PlatformService
|
* @returns {PlatformService} The singleton instance of PlatformService
|
||||||
*/
|
*/
|
||||||
public static getInstance(): PlatformService {
|
public static async getInstance(): Promise<PlatformService> {
|
||||||
if (PlatformServiceFactory.instance) {
|
if (PlatformServiceFactory.instance) {
|
||||||
return PlatformServiceFactory.instance;
|
return PlatformServiceFactory.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
const platform = process.env.VITE_PLATFORM || "web";
|
const platform = process.env.VITE_PLATFORM || "web";
|
||||||
|
let service: PlatformService;
|
||||||
|
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case "capacitor":
|
case "capacitor": {
|
||||||
PlatformServiceFactory.instance = new CapacitorPlatformService();
|
const { CapacitorPlatformService } = await import(
|
||||||
|
"./platforms/CapacitorPlatformService"
|
||||||
|
);
|
||||||
|
service = new CapacitorPlatformService();
|
||||||
break;
|
break;
|
||||||
case "electron":
|
}
|
||||||
PlatformServiceFactory.instance = new ElectronPlatformService();
|
case "electron": {
|
||||||
|
const { ElectronPlatformService } = await import(
|
||||||
|
"./platforms/ElectronPlatformService"
|
||||||
|
);
|
||||||
|
service = new ElectronPlatformService();
|
||||||
break;
|
break;
|
||||||
case "pywebview":
|
}
|
||||||
PlatformServiceFactory.instance = new PyWebViewPlatformService();
|
case "pywebview": {
|
||||||
|
const { PyWebViewPlatformService } = await import(
|
||||||
|
"./platforms/PyWebViewPlatformService"
|
||||||
|
);
|
||||||
|
service = new PyWebViewPlatformService();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case "web":
|
case "web":
|
||||||
default:
|
default:
|
||||||
PlatformServiceFactory.instance = new WebPlatformService();
|
service = new WebPlatformService();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return PlatformServiceFactory.instance;
|
PlatformServiceFactory.instance = service;
|
||||||
|
return service;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ export class CapacitorPlatformService implements PlatformService {
|
|||||||
path: directory,
|
path: directory,
|
||||||
directory: Directory.Data,
|
directory: Directory.Data,
|
||||||
});
|
});
|
||||||
return result.files.map(file => typeof file === 'string' ? file : file.name);
|
return result.files.map((file) =>
|
||||||
|
typeof file === "string" ? file : file.name,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -190,17 +192,50 @@ export class CapacitorPlatformService implements PlatformService {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getExportInstructions(): string[] {
|
||||||
|
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
||||||
|
if (isIOS) {
|
||||||
|
return [
|
||||||
|
"On iOS: Choose 'More...' and select a place in iCloud, or go 'Back' and save to another location.",
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
"On Android: Choose 'Open' and then share to your preferred place.",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getExportSuccessMessage(): string {
|
||||||
|
return "The backup has been saved to your device.";
|
||||||
|
}
|
||||||
|
|
||||||
|
needsSecondaryDownloadLink(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
needsDownloadCleanup(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
async exportDatabase(blob: Blob, fileName: string): Promise<void> {
|
async exportDatabase(blob: Blob, fileName: string): Promise<void> {
|
||||||
|
logger.log("Starting database export on Capacitor platform:", {
|
||||||
|
fileName,
|
||||||
|
blobSize: `${blob.size} bytes`,
|
||||||
|
});
|
||||||
|
|
||||||
// Create a File object from the Blob
|
// Create a File object from the Blob
|
||||||
const file = new File([blob], fileName, { type: 'application/json' });
|
const file = new File([blob], fileName, { type: "application/json" });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
logger.log("Attempting to use native share sheet");
|
||||||
// Use the native share sheet
|
// Use the native share sheet
|
||||||
await navigator.share({
|
await navigator.share({
|
||||||
files: [file],
|
files: [file],
|
||||||
title: fileName,
|
title: fileName,
|
||||||
});
|
});
|
||||||
|
logger.log("Database export completed via native share sheet");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.log("Native share failed, falling back to Capacitor Share API");
|
||||||
// Fallback to Capacitor Share API if Web Share API fails
|
// Fallback to Capacitor Share API if Web Share API fails
|
||||||
// First save to temporary file
|
// First save to temporary file
|
||||||
const base64Data = await this.blobToBase64(blob);
|
const base64Data = await this.blobToBase64(blob);
|
||||||
@@ -208,23 +243,26 @@ export class CapacitorPlatformService implements PlatformService {
|
|||||||
path: fileName,
|
path: fileName,
|
||||||
data: base64Data,
|
data: base64Data,
|
||||||
directory: Directory.Cache, // Use Cache instead of Documents for temporary files
|
directory: Directory.Cache, // Use Cache instead of Documents for temporary files
|
||||||
recursive: true
|
recursive: true,
|
||||||
});
|
});
|
||||||
|
logger.log("Temporary file created for sharing:", result.uri);
|
||||||
|
|
||||||
// Then share using Capacitor Share API
|
// Then share using Capacitor Share API
|
||||||
await Share.share({
|
await Share.share({
|
||||||
title: fileName,
|
title: fileName,
|
||||||
url: result.uri
|
url: result.uri,
|
||||||
});
|
});
|
||||||
|
logger.log("Database export completed via Capacitor Share API");
|
||||||
|
|
||||||
// Clean up the temporary file
|
// Clean up the temporary file
|
||||||
try {
|
try {
|
||||||
await Filesystem.deleteFile({
|
await Filesystem.deleteFile({
|
||||||
path: fileName,
|
path: fileName,
|
||||||
directory: Directory.Cache
|
directory: Directory.Cache,
|
||||||
});
|
});
|
||||||
|
logger.log("Temporary file cleaned up successfully");
|
||||||
} catch (cleanupError) {
|
} catch (cleanupError) {
|
||||||
logger.warn('Failed to clean up temporary file:', cleanupError);
|
logger.warn("Failed to clean up temporary file:", cleanupError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,7 +272,7 @@ export class CapacitorPlatformService implements PlatformService {
|
|||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onloadend = () => {
|
reader.onloadend = () => {
|
||||||
const base64data = reader.result as string;
|
const base64data = reader.result as string;
|
||||||
resolve(base64data.split(',')[1]);
|
resolve(base64data.split(",")[1]);
|
||||||
};
|
};
|
||||||
reader.onerror = reject;
|
reader.onerror = reject;
|
||||||
reader.readAsDataURL(blob);
|
reader.readAsDataURL(blob);
|
||||||
|
|||||||
@@ -121,4 +121,16 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
logger.error("handleDeepLink not implemented in Electron platform");
|
logger.error("handleDeepLink not implemented in Electron platform");
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports a database blob to a file.
|
||||||
|
* @param _blob - The database blob to export
|
||||||
|
* @param _fileName - The name of the file to save
|
||||||
|
* @throws Error with "Not implemented" message
|
||||||
|
* @todo Implement file export using Electron's file system API
|
||||||
|
*/
|
||||||
|
async exportDatabase(_blob: Blob, _fileName: string): Promise<void> {
|
||||||
|
logger.error("exportDatabase not implemented in Electron platform");
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,4 +122,16 @@ export class PyWebViewPlatformService implements PlatformService {
|
|||||||
logger.error("handleDeepLink not implemented in PyWebView platform");
|
logger.error("handleDeepLink not implemented in PyWebView platform");
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports a database blob to a file using the Python backend.
|
||||||
|
* @param _blob - The database blob to export
|
||||||
|
* @param _fileName - The name of the file to save
|
||||||
|
* @throws Error with "Not implemented" message
|
||||||
|
* @todo Implement file export through pywebview's Python-JavaScript bridge
|
||||||
|
*/
|
||||||
|
async exportDatabase(_blob: Blob, _fileName: string): Promise<void> {
|
||||||
|
logger.error("exportDatabase not implemented in PyWebView platform");
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,12 +210,52 @@ export class WebPlatformService implements PlatformService {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getExportInstructions(): string[] {
|
||||||
|
return [
|
||||||
|
"After the download, you can save the file in your preferred storage location.",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getExportSuccessMessage(): string {
|
||||||
|
return "See your downloads directory for the backup. It is in the Dexie format.";
|
||||||
|
}
|
||||||
|
|
||||||
|
needsSecondaryDownloadLink(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
needsDownloadCleanup(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
async exportDatabase(blob: Blob, fileName: string): Promise<void> {
|
async exportDatabase(blob: Blob, fileName: string): Promise<void> {
|
||||||
const downloadUrl = URL.createObjectURL(blob);
|
logger.log("Starting database export on web platform:", {
|
||||||
const downloadAnchor = document.createElement('a');
|
fileName,
|
||||||
downloadAnchor.href = downloadUrl;
|
blobSize: `${blob.size} bytes`,
|
||||||
downloadAnchor.download = fileName;
|
});
|
||||||
downloadAnchor.click();
|
|
||||||
setTimeout(() => URL.revokeObjectURL(downloadUrl), 1000);
|
try {
|
||||||
|
logger.log("Creating download link for database export");
|
||||||
|
// Create a download link
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = fileName;
|
||||||
|
|
||||||
|
logger.log("Triggering download");
|
||||||
|
// Trigger the download
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
logger.log("Cleaning up download link");
|
||||||
|
// Clean up
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
|
||||||
|
logger.log("Database export completed successfully");
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to export database:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,21 @@ export async function createBuildConfig(mode: string) {
|
|||||||
assetsDir: 'assets',
|
assetsDir: 'assets',
|
||||||
chunkSizeWarningLimit: 1000,
|
chunkSizeWarningLimit: 1000,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: isCapacitor ? ['@capacitor/app'] : []
|
external: isCapacitor
|
||||||
|
? [
|
||||||
|
'@capacitor/app',
|
||||||
|
'@capacitor/share',
|
||||||
|
'@capacitor/filesystem',
|
||||||
|
'@capacitor/camera',
|
||||||
|
'@capacitor/core'
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
'@capacitor/app',
|
||||||
|
'@capacitor/share',
|
||||||
|
'@capacitor/filesystem',
|
||||||
|
'@capacitor/camera',
|
||||||
|
'@capacitor/core'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
|
|||||||
Reference in New Issue
Block a user