Compare commits
2 Commits
playwright
...
cross-plat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ee4a7e411 | ||
|
|
1c8528fb20 |
@@ -1,16 +1,9 @@
|
||||
/**
|
||||
* 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" />
|
||||
* ```
|
||||
*/
|
||||
/** * 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
|
||||
@@ -36,28 +29,24 @@
|
||||
(excluding Identifier Data)
|
||||
</button>
|
||||
<a
|
||||
v-if="platformService?.needsSecondaryDownloadLink()"
|
||||
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 class="mt-4" v-if="showPlatformInstructions">
|
||||
<p>
|
||||
After the download, you can save the file in your preferred storage
|
||||
location.
|
||||
<div
|
||||
v-if="platformService?.getExportInstructions().length > 0"
|
||||
class="mt-4"
|
||||
>
|
||||
<p
|
||||
v-for="instruction in platformService?.getExportInstructions()"
|
||||
:key="instruction"
|
||||
class="list-disc list-outside ml-4"
|
||||
>
|
||||
{{ instruction }}
|
||||
</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>
|
||||
</template>
|
||||
@@ -100,75 +89,72 @@ export default class DataExportSection extends Vue {
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
private platformService?: PlatformService;
|
||||
|
||||
/**
|
||||
* Lifecycle hook to clean up resources
|
||||
* Revokes object URL when component is unmounted (web platform only)
|
||||
*/
|
||||
beforeUnmount() {
|
||||
if (this.downloadUrl && this.platformService.isWeb()) {
|
||||
if (this.downloadUrl && this.platformService?.needsDownloadCleanup()) {
|
||||
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
|
||||
* 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 {
|
||||
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 fileName = `${db.name}-backup.json`;
|
||||
logger.log("Database export details:", {
|
||||
fileName,
|
||||
blobSize: `${blob.size} bytes`,
|
||||
platform: process.env.VITE_PLATFORM,
|
||||
});
|
||||
|
||||
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());
|
||||
}
|
||||
await this.platformService.exportDatabase(blob, fileName);
|
||||
logger.log("Database export completed successfully:", {
|
||||
fileName,
|
||||
platform: process.env.VITE_PLATFORM,
|
||||
});
|
||||
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
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.",
|
||||
text: this.platformService.getExportSuccessMessage(),
|
||||
},
|
||||
-1,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error("Export Error:", error);
|
||||
logger.error("Database export failed:", {
|
||||
error,
|
||||
platform: process.env.VITE_PLATFORM,
|
||||
});
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -187,7 +173,8 @@ export default class DataExportSection extends Vue {
|
||||
*/
|
||||
public computedStartDownloadLinkClassNames() {
|
||||
return {
|
||||
hidden: this.downloadUrl && this.platformService.isWeb(),
|
||||
hidden:
|
||||
this.downloadUrl && this.platformService?.needsSecondaryDownloadLink(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -197,7 +184,9 @@ export default class DataExportSection extends Vue {
|
||||
*/
|
||||
public computedDownloadLinkClassNames() {
|
||||
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 { logger } from "../utils/logger";
|
||||
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
||||
import { PlatformService } from "../services/PlatformService";
|
||||
|
||||
@Component({ components: { VuePictureCropper } })
|
||||
export default class PhotoDialog extends Vue {
|
||||
@@ -123,13 +124,14 @@ export default class PhotoDialog extends Vue {
|
||||
uploading = false;
|
||||
visible = false;
|
||||
|
||||
private platformService = PlatformServiceFactory.getInstance();
|
||||
private platformService?: PlatformService;
|
||||
URL = window.URL || window.webkitURL;
|
||||
|
||||
async mounted() {
|
||||
try {
|
||||
const settings = await retrieveSettingsForActiveAccount();
|
||||
this.activeDid = settings.activeDid || "";
|
||||
this.platformService = await PlatformServiceFactory.getInstance();
|
||||
} catch (err: unknown) {
|
||||
logger.error("Error retrieving settings from database:", err);
|
||||
this.$notify(
|
||||
@@ -137,7 +139,10 @@ export default class PhotoDialog extends Vue {
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
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,
|
||||
);
|
||||
@@ -181,6 +186,9 @@ export default class PhotoDialog extends Vue {
|
||||
|
||||
async takePhoto() {
|
||||
try {
|
||||
if (!this.platformService) {
|
||||
this.platformService = await PlatformServiceFactory.getInstance();
|
||||
}
|
||||
const result = await this.platformService.takePicture();
|
||||
this.blob = result.blob;
|
||||
this.fileName = result.fileName;
|
||||
@@ -200,6 +208,9 @@ export default class PhotoDialog extends Vue {
|
||||
|
||||
async pickPhoto() {
|
||||
try {
|
||||
if (!this.platformService) {
|
||||
this.platformService = await PlatformServiceFactory.getInstance();
|
||||
}
|
||||
const result = await this.platformService.pickImage();
|
||||
this.blob = result.blob;
|
||||
this.fileName = result.fileName;
|
||||
|
||||
@@ -46,6 +46,15 @@ export interface PlatformService {
|
||||
*/
|
||||
listFiles(directory: string): Promise<string[]>;
|
||||
|
||||
/**
|
||||
* Exports a database blob to a file, handling platform-specific save operations.
|
||||
* @param blob - The database blob to export
|
||||
* @param fileName - The name of the file to save
|
||||
* @returns Promise that resolves when the export is complete
|
||||
* @throws Error if export fails
|
||||
*/
|
||||
exportDatabase(blob: Blob, fileName: string): Promise<void>;
|
||||
|
||||
// Camera operations
|
||||
/**
|
||||
* Activates the device camera to take a picture.
|
||||
@@ -91,4 +100,28 @@ export interface PlatformService {
|
||||
* @returns Promise that resolves when the deep link has been handled
|
||||
*/
|
||||
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,20 +1,17 @@
|
||||
import { PlatformService } from "./PlatformService";
|
||||
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.
|
||||
* Implements the Singleton pattern to ensure only one instance of PlatformService exists.
|
||||
*
|
||||
*
|
||||
* The factory determines which platform implementation to use based on the VITE_PLATFORM
|
||||
* environment variable. Supported platforms are:
|
||||
* - capacitor: Mobile platform using Capacitor
|
||||
* - electron: Desktop platform using Electron
|
||||
* - pywebview: Python WebView implementation
|
||||
* - web: Default web platform (fallback)
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const platformService = PlatformServiceFactory.getInstance();
|
||||
@@ -27,32 +24,46 @@ export class PlatformServiceFactory {
|
||||
/**
|
||||
* Gets or creates the singleton instance of PlatformService.
|
||||
* Creates the appropriate platform-specific implementation based on environment.
|
||||
*
|
||||
*
|
||||
* @returns {PlatformService} The singleton instance of PlatformService
|
||||
*/
|
||||
public static getInstance(): PlatformService {
|
||||
public static async getInstance(): Promise<PlatformService> {
|
||||
if (PlatformServiceFactory.instance) {
|
||||
return PlatformServiceFactory.instance;
|
||||
}
|
||||
|
||||
const platform = process.env.VITE_PLATFORM || "web";
|
||||
let service: PlatformService;
|
||||
|
||||
switch (platform) {
|
||||
case "capacitor":
|
||||
PlatformServiceFactory.instance = new CapacitorPlatformService();
|
||||
case "capacitor": {
|
||||
const { CapacitorPlatformService } = await import(
|
||||
"./platforms/CapacitorPlatformService"
|
||||
);
|
||||
service = new CapacitorPlatformService();
|
||||
break;
|
||||
case "electron":
|
||||
PlatformServiceFactory.instance = new ElectronPlatformService();
|
||||
}
|
||||
case "electron": {
|
||||
const { ElectronPlatformService } = await import(
|
||||
"./platforms/ElectronPlatformService"
|
||||
);
|
||||
service = new ElectronPlatformService();
|
||||
break;
|
||||
case "pywebview":
|
||||
PlatformServiceFactory.instance = new PyWebViewPlatformService();
|
||||
}
|
||||
case "pywebview": {
|
||||
const { PyWebViewPlatformService } = await import(
|
||||
"./platforms/PyWebViewPlatformService"
|
||||
);
|
||||
service = new PyWebViewPlatformService();
|
||||
break;
|
||||
}
|
||||
case "web":
|
||||
default:
|
||||
PlatformServiceFactory.instance = new WebPlatformService();
|
||||
service = new WebPlatformService();
|
||||
break;
|
||||
}
|
||||
|
||||
return PlatformServiceFactory.instance;
|
||||
PlatformServiceFactory.instance = service;
|
||||
return service;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* API error handling utilities for the application.
|
||||
* Provides centralized error handling for API requests with platform-specific logging.
|
||||
*
|
||||
*
|
||||
* @module api
|
||||
*/
|
||||
|
||||
@@ -10,12 +10,12 @@ import { logger } from "../utils/logger";
|
||||
|
||||
/**
|
||||
* Handles API errors with platform-specific logging and error processing.
|
||||
*
|
||||
*
|
||||
* @param error - The Axios error object from the failed request
|
||||
* @param endpoint - The API endpoint that was called
|
||||
* @returns null for rate limit errors (400), throws the error otherwise
|
||||
* @throws The original error for non-rate-limit cases
|
||||
*
|
||||
*
|
||||
* @remarks
|
||||
* Special handling includes:
|
||||
* - Enhanced logging for Capacitor platform
|
||||
@@ -25,7 +25,7 @@ import { logger } from "../utils/logger";
|
||||
* - HTTP status
|
||||
* - Response data
|
||||
* - Request configuration (URL, method, headers)
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* try {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
*
|
||||
* Deep Link Format:
|
||||
* timesafari://<route>[/<param>][?queryParam1=value1&queryParam2=value2]
|
||||
*
|
||||
*
|
||||
* Supported Routes:
|
||||
* - user-profile: View user profile
|
||||
* - project-details: View project details
|
||||
@@ -73,7 +73,7 @@ export class DeepLinkHandler {
|
||||
/**
|
||||
* Parses deep link URL into path, params and query components.
|
||||
* Validates URL structure using Zod schemas.
|
||||
*
|
||||
*
|
||||
* @param url - The deep link URL to parse (format: scheme://path[?query])
|
||||
* @throws {DeepLinkError} If URL format is invalid
|
||||
* @returns Parsed URL components (path, params, query)
|
||||
@@ -111,7 +111,7 @@ export class DeepLinkHandler {
|
||||
/**
|
||||
* Processes incoming deep links and routes them appropriately.
|
||||
* Handles validation, error handling, and routing to the correct view.
|
||||
*
|
||||
*
|
||||
* @param url - The deep link URL to process
|
||||
* @throws {DeepLinkError} If URL processing fails
|
||||
*/
|
||||
@@ -142,7 +142,7 @@ export class DeepLinkHandler {
|
||||
/**
|
||||
* Routes the deep link to appropriate view with validated parameters.
|
||||
* Validates route and parameters using Zod schemas before routing.
|
||||
*
|
||||
*
|
||||
* @param path - The route path from the deep link
|
||||
* @param params - URL parameters
|
||||
* @param query - Query string parameters
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Plan service module for handling plan and claim data loading.
|
||||
* Provides functionality to load plans with retry mechanism and error handling.
|
||||
*
|
||||
*
|
||||
* @module plan
|
||||
*/
|
||||
|
||||
@@ -26,11 +26,11 @@ interface PlanResponse {
|
||||
/**
|
||||
* Loads a plan with automatic retry mechanism.
|
||||
* Attempts to load the plan multiple times in case of failure.
|
||||
*
|
||||
*
|
||||
* @param handle - The unique identifier for the plan or claim
|
||||
* @param retries - Number of retry attempts (default: 3)
|
||||
* @returns Promise resolving to PlanResponse
|
||||
*
|
||||
*
|
||||
* @remarks
|
||||
* - Implements exponential backoff with 1 second delay between retries
|
||||
* - Provides detailed logging of each attempt and any errors
|
||||
@@ -39,7 +39,7 @@ interface PlanResponse {
|
||||
* - HTTP status and headers
|
||||
* - Response data
|
||||
* - Request configuration
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const response = await loadPlanWithRetry('plan-123');
|
||||
@@ -104,11 +104,11 @@ export const loadPlanWithRetry = async (
|
||||
/**
|
||||
* Makes a single API request to load a plan or claim.
|
||||
* Determines the appropriate endpoint based on the handle.
|
||||
*
|
||||
*
|
||||
* @param handle - The unique identifier for the plan or claim
|
||||
* @returns Promise resolving to PlanResponse
|
||||
* @throws Will throw an error if the API request fails
|
||||
*
|
||||
*
|
||||
* @remarks
|
||||
* - Automatically detects claim vs plan endpoints based on handle
|
||||
* - Uses axios for HTTP requests
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ImageResult, PlatformService } from "../PlatformService";
|
||||
import { Filesystem, Directory } from "@capacitor/filesystem";
|
||||
import { Camera, CameraResultType, CameraSource } from "@capacitor/camera";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { Share } from "@capacitor/share";
|
||||
|
||||
/**
|
||||
* Platform service implementation for Capacitor (mobile) platform.
|
||||
@@ -65,7 +66,9 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
path: directory,
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,4 +191,91 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
// This is just a placeholder for the interface
|
||||
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> {
|
||||
logger.log("Starting database export on Capacitor platform:", {
|
||||
fileName,
|
||||
blobSize: `${blob.size} bytes`,
|
||||
});
|
||||
|
||||
// Create a File object from the Blob
|
||||
const file = new File([blob], fileName, { type: "application/json" });
|
||||
|
||||
try {
|
||||
logger.log("Attempting to use native share sheet");
|
||||
// Use the native share sheet
|
||||
await navigator.share({
|
||||
files: [file],
|
||||
title: fileName,
|
||||
});
|
||||
logger.log("Database export completed via native share sheet");
|
||||
} catch (error) {
|
||||
logger.log("Native share failed, falling back to Capacitor Share API");
|
||||
// Fallback to Capacitor Share API if Web Share API fails
|
||||
// First save to temporary file
|
||||
const base64Data = await this.blobToBase64(blob);
|
||||
const result = await Filesystem.writeFile({
|
||||
path: fileName,
|
||||
data: base64Data,
|
||||
directory: Directory.Cache, // Use Cache instead of Documents for temporary files
|
||||
recursive: true,
|
||||
});
|
||||
logger.log("Temporary file created for sharing:", result.uri);
|
||||
|
||||
// Then share using Capacitor Share API
|
||||
await Share.share({
|
||||
title: fileName,
|
||||
url: result.uri,
|
||||
});
|
||||
logger.log("Database export completed via Capacitor Share API");
|
||||
|
||||
// Clean up the temporary file
|
||||
try {
|
||||
await Filesystem.deleteFile({
|
||||
path: fileName,
|
||||
directory: Directory.Cache,
|
||||
});
|
||||
logger.log("Temporary file cleaned up successfully");
|
||||
} catch (cleanupError) {
|
||||
logger.warn("Failed to clean up temporary file:", cleanupError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async blobToBase64(blob: Blob): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
const base64data = reader.result as string;
|
||||
resolve(base64data.split(",")[1]);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { logger } from "../../utils/logger";
|
||||
* Platform service implementation for Electron (desktop) platform.
|
||||
* Note: This is a placeholder implementation with most methods currently unimplemented.
|
||||
* Implements the PlatformService interface but throws "Not implemented" errors for most operations.
|
||||
*
|
||||
*
|
||||
* @remarks
|
||||
* This service is intended for desktop application functionality through Electron.
|
||||
* Future implementations should provide:
|
||||
@@ -121,4 +121,16 @@ export class ElectronPlatformService implements PlatformService {
|
||||
logger.error("handleDeepLink not implemented in Electron platform");
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { logger } from "../../utils/logger";
|
||||
* Platform service implementation for PyWebView platform.
|
||||
* Note: This is a placeholder implementation with most methods currently unimplemented.
|
||||
* Implements the PlatformService interface but throws "Not implemented" errors for most operations.
|
||||
*
|
||||
*
|
||||
* @remarks
|
||||
* This service is intended for Python-based desktop applications using pywebview.
|
||||
* Future implementations should provide:
|
||||
@@ -122,4 +122,16 @@ export class PyWebViewPlatformService implements PlatformService {
|
||||
logger.error("handleDeepLink not implemented in PyWebView platform");
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ import { logger } from "../../utils/logger";
|
||||
/**
|
||||
* Platform service implementation for web browser platform.
|
||||
* Implements the PlatformService interface with web-specific functionality.
|
||||
*
|
||||
*
|
||||
* @remarks
|
||||
* This service provides web-based implementations for:
|
||||
* - Image capture using the browser's file input
|
||||
* - Image selection from local filesystem
|
||||
* - Image processing and conversion
|
||||
*
|
||||
*
|
||||
* Note: File system operations are not available in the web platform
|
||||
* due to browser security restrictions. These methods throw appropriate errors.
|
||||
*/
|
||||
@@ -55,10 +55,10 @@ export class WebPlatformService implements PlatformService {
|
||||
/**
|
||||
* Opens a file input dialog configured for camera capture.
|
||||
* Creates a temporary file input element to access the device camera.
|
||||
*
|
||||
*
|
||||
* @returns Promise resolving to the captured image data
|
||||
* @throws Error if image capture fails or no image is selected
|
||||
*
|
||||
*
|
||||
* @remarks
|
||||
* Uses the 'capture' attribute to prefer the device camera.
|
||||
* Falls back to file selection if camera is not available.
|
||||
@@ -96,10 +96,10 @@ export class WebPlatformService implements PlatformService {
|
||||
/**
|
||||
* Opens a file input dialog for selecting an image file.
|
||||
* Creates a temporary file input element to access local files.
|
||||
*
|
||||
*
|
||||
* @returns Promise resolving to the selected image data
|
||||
* @throws Error if image processing fails or no image is selected
|
||||
*
|
||||
*
|
||||
* @remarks
|
||||
* Allows selection of any image file type.
|
||||
* Processes the selected image to ensure consistent format.
|
||||
@@ -135,11 +135,11 @@ export class WebPlatformService implements PlatformService {
|
||||
/**
|
||||
* Processes an image file to ensure consistent format.
|
||||
* Converts the file to a data URL and then to a Blob.
|
||||
*
|
||||
*
|
||||
* @param file - The image File object to process
|
||||
* @returns Promise resolving to processed image Blob
|
||||
* @throws Error if file reading or conversion fails
|
||||
*
|
||||
*
|
||||
* @remarks
|
||||
* This method ensures consistent image format across different
|
||||
* input sources by converting through data URL to Blob.
|
||||
@@ -201,7 +201,7 @@ export class WebPlatformService implements PlatformService {
|
||||
/**
|
||||
* Handles deep link URLs in the web platform.
|
||||
* Deep links are handled through URL parameters in the web environment.
|
||||
*
|
||||
*
|
||||
* @param _url - The deep link URL to handle (unused in web implementation)
|
||||
* @returns Promise that resolves immediately as web handles URLs naturally
|
||||
*/
|
||||
@@ -209,4 +209,53 @@ export class WebPlatformService implements PlatformService {
|
||||
// Web platform can handle deep links through URL parameters
|
||||
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> {
|
||||
logger.log("Starting database export on web platform:", {
|
||||
fileName,
|
||||
blobSize: `${blob.size} bytes`,
|
||||
});
|
||||
|
||||
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',
|
||||
chunkSizeWarningLimit: 1000,
|
||||
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: {
|
||||
|
||||
Reference in New Issue
Block a user