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.
261 lines
7.7 KiB
261 lines
7.7 KiB
import { ImageResult, PlatformService } from "../PlatformService";
|
|
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.
|
|
*/
|
|
export class WebPlatformService implements PlatformService {
|
|
/**
|
|
* Not supported in web platform.
|
|
* @param _path - Unused path parameter
|
|
* @throws Error indicating file system access is not available
|
|
*/
|
|
async readFile(_path: string): Promise<string> {
|
|
throw new Error("File system access not available in web platform");
|
|
}
|
|
|
|
/**
|
|
* Not supported in web platform.
|
|
* @param _path - Unused path parameter
|
|
* @param _content - Unused content parameter
|
|
* @throws Error indicating file system access is not available
|
|
*/
|
|
async writeFile(_path: string, _content: string): Promise<void> {
|
|
throw new Error("File system access not available in web platform");
|
|
}
|
|
|
|
/**
|
|
* Not supported in web platform.
|
|
* @param _path - Unused path parameter
|
|
* @throws Error indicating file system access is not available
|
|
*/
|
|
async deleteFile(_path: string): Promise<void> {
|
|
throw new Error("File system access not available in web platform");
|
|
}
|
|
|
|
/**
|
|
* Not supported in web platform.
|
|
* @param _directory - Unused directory parameter
|
|
* @throws Error indicating file system access is not available
|
|
*/
|
|
async listFiles(_directory: string): Promise<string[]> {
|
|
throw new Error("File system access not available in web platform");
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* Processes the captured image to ensure consistent format.
|
|
*/
|
|
async takePicture(): Promise<ImageResult> {
|
|
return new Promise((resolve, reject) => {
|
|
const input = document.createElement("input");
|
|
input.type = "file";
|
|
input.accept = "image/*";
|
|
input.capture = "environment";
|
|
|
|
input.onchange = async (e) => {
|
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
if (file) {
|
|
try {
|
|
const blob = await this.processImageFile(file);
|
|
resolve({
|
|
blob,
|
|
fileName: file.name || "photo.jpg",
|
|
});
|
|
} catch (error) {
|
|
logger.error("Error processing camera image:", error);
|
|
reject(new Error("Failed to process camera image"));
|
|
}
|
|
} else {
|
|
reject(new Error("No image captured"));
|
|
}
|
|
};
|
|
|
|
input.click();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
async pickImage(): Promise<ImageResult> {
|
|
return new Promise((resolve, reject) => {
|
|
const input = document.createElement("input");
|
|
input.type = "file";
|
|
input.accept = "image/*";
|
|
|
|
input.onchange = async (e) => {
|
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
if (file) {
|
|
try {
|
|
const blob = await this.processImageFile(file);
|
|
resolve({
|
|
blob,
|
|
fileName: file.name || "photo.jpg",
|
|
});
|
|
} catch (error) {
|
|
logger.error("Error processing picked image:", error);
|
|
reject(new Error("Failed to process picked image"));
|
|
}
|
|
} else {
|
|
reject(new Error("No image selected"));
|
|
}
|
|
};
|
|
|
|
input.click();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
private async processImageFile(file: File): Promise<Blob> {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onload = (event) => {
|
|
const dataUrl = event.target?.result as string;
|
|
// Convert to blob to ensure consistent format
|
|
fetch(dataUrl)
|
|
.then((res) => res.blob())
|
|
.then((blob) => resolve(blob))
|
|
.catch((error) => {
|
|
logger.error("Error converting data URL to blob:", error);
|
|
reject(error);
|
|
});
|
|
};
|
|
reader.onerror = (error) => {
|
|
logger.error("Error reading file:", error);
|
|
reject(error);
|
|
};
|
|
reader.readAsDataURL(file);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Checks if running on Capacitor platform.
|
|
* @returns false, as this is not Capacitor
|
|
*/
|
|
isCapacitor(): boolean {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if running on Electron platform.
|
|
* @returns false, as this is not Electron
|
|
*/
|
|
isElectron(): boolean {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if running on PyWebView platform.
|
|
* @returns false, as this is not PyWebView
|
|
*/
|
|
isPyWebView(): boolean {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if running on web platform.
|
|
* @returns true, as this is the web implementation
|
|
*/
|
|
isWeb(): boolean {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
async handleDeepLink(_url: string): Promise<void> {
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|