forked from trent_larson/crowd-funder-for-time-pwa
refactor(platform): replace platform checks with capability-based system
- Add PlatformCapabilities interface to define available features - Remove isWeb(), isCapacitor(), isElectron(), isPyWebView() methods - Update platform services to implement getCapabilities() - Refactor DataExportSection to use capability checks instead of platform checks - Improve platform abstraction and separation of concerns - Make platform-specific logic more maintainable and extensible This change decouples components from specific platform implementations, making the codebase more maintainable and easier to extend with new platforms.
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -42,17 +42,17 @@
|
||||
>
|
||||
If no download happened yet, click again here to download now.
|
||||
</a>
|
||||
<div class="mt-4" v-if="showPlatformInstructions">
|
||||
<div class="mt-4" v-if="platformCapabilities.needsFileHandlingInstructions">
|
||||
<p>
|
||||
After the download, you can save the file in your preferred storage
|
||||
location.
|
||||
</p>
|
||||
<ul>
|
||||
<li v-if="platformService.isCapacitor() && isIOS" class="list-disc list-outside ml-4">
|
||||
<li v-if="platformCapabilities.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">
|
||||
<li v-if="platformCapabilities.isMobile && !platformCapabilities.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.
|
||||
@@ -68,7 +68,7 @@ import { NotificationIface } from "../constants/app";
|
||||
import { db } from "../db/index";
|
||||
import { logger } from "../utils/logger";
|
||||
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
||||
import { PlatformService } from "../services/PlatformService";
|
||||
import { PlatformService, PlatformCapabilities } from "../services/PlatformService";
|
||||
|
||||
/**
|
||||
* @vue-component
|
||||
@@ -103,17 +103,10 @@ export default class DataExportSection extends Vue {
|
||||
private platformService: PlatformService = PlatformServiceFactory.getInstance();
|
||||
|
||||
/**
|
||||
* Whether the current platform is iOS
|
||||
* Platform capabilities for the current platform
|
||||
*/
|
||||
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 get platformCapabilities(): PlatformCapabilities {
|
||||
return this.platformService.getCapabilities();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,7 +114,7 @@ export default class DataExportSection extends Vue {
|
||||
* Revokes object URL when component is unmounted (web platform only)
|
||||
*/
|
||||
beforeUnmount() {
|
||||
if (this.downloadUrl && this.platformService.isWeb()) {
|
||||
if (this.downloadUrl && this.platformCapabilities.hasFileDownload) {
|
||||
URL.revokeObjectURL(this.downloadUrl);
|
||||
}
|
||||
}
|
||||
@@ -139,7 +132,7 @@ export default class DataExportSection extends Vue {
|
||||
const blob = await db.export({ prettyJson: true });
|
||||
const fileName = `${db.name}-backup.json`;
|
||||
|
||||
if (this.platformService.isWeb()) {
|
||||
if (this.platformCapabilities.hasFileDownload) {
|
||||
// Web platform: Use download link
|
||||
this.downloadUrl = URL.createObjectURL(blob);
|
||||
const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement;
|
||||
@@ -147,13 +140,10 @@ export default class DataExportSection extends Vue {
|
||||
downloadAnchor.download = fileName;
|
||||
downloadAnchor.click();
|
||||
setTimeout(() => URL.revokeObjectURL(this.downloadUrl), 1000);
|
||||
} else if (this.platformService.isCapacitor()) {
|
||||
// Capacitor platform: Write to app directory
|
||||
} else if (this.platformCapabilities.hasFileSystem) {
|
||||
// Native 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());
|
||||
}
|
||||
|
||||
this.$notify(
|
||||
@@ -161,7 +151,7 @@ export default class DataExportSection extends Vue {
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Export Successful",
|
||||
text: this.platformService.isWeb()
|
||||
text: this.platformCapabilities.hasFileDownload
|
||||
? "See your downloads directory for the backup. It is in the Dexie format."
|
||||
: "The backup has been saved to your device.",
|
||||
},
|
||||
@@ -187,7 +177,7 @@ export default class DataExportSection extends Vue {
|
||||
*/
|
||||
public computedStartDownloadLinkClassNames() {
|
||||
return {
|
||||
hidden: this.downloadUrl && this.platformService.isWeb(),
|
||||
hidden: this.downloadUrl && this.platformCapabilities.hasFileDownload,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -197,7 +187,7 @@ export default class DataExportSection extends Vue {
|
||||
*/
|
||||
public computedDownloadLinkClassNames() {
|
||||
return {
|
||||
hidden: !this.downloadUrl || !this.platformService.isWeb(),
|
||||
hidden: !this.downloadUrl || !this.platformCapabilities.hasFileDownload,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,38 @@ export interface ImageResult {
|
||||
fileName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform capabilities interface defining what features are available
|
||||
* on the current platform implementation
|
||||
*/
|
||||
export interface PlatformCapabilities {
|
||||
/** Whether the platform supports native file system access */
|
||||
hasFileSystem: boolean;
|
||||
/** Whether the platform supports native camera access */
|
||||
hasCamera: boolean;
|
||||
/** Whether the platform is a mobile device */
|
||||
isMobile: boolean;
|
||||
/** Whether the platform is iOS specifically */
|
||||
isIOS: boolean;
|
||||
/** Whether the platform supports native file download */
|
||||
hasFileDownload: boolean;
|
||||
/** Whether the platform requires special file handling instructions */
|
||||
needsFileHandlingInstructions: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform-agnostic interface for handling platform-specific operations.
|
||||
* Provides a common API for file system operations, camera interactions,
|
||||
* platform detection, and deep linking across different platforms
|
||||
* (web, mobile, desktop).
|
||||
* and platform detection across different platforms (web, mobile, desktop).
|
||||
*/
|
||||
export interface PlatformService {
|
||||
// Platform capabilities
|
||||
/**
|
||||
* Gets the current platform's capabilities
|
||||
* @returns Object describing what features are available on this platform
|
||||
*/
|
||||
getCapabilities(): PlatformCapabilities;
|
||||
|
||||
// File system operations
|
||||
/**
|
||||
* Reads the contents of a file at the specified path.
|
||||
@@ -59,32 +84,6 @@ export interface PlatformService {
|
||||
*/
|
||||
pickImage(): Promise<ImageResult>;
|
||||
|
||||
// Platform specific features
|
||||
/**
|
||||
* Checks if the current platform is Capacitor (mobile).
|
||||
* @returns true if running on Capacitor
|
||||
*/
|
||||
isCapacitor(): boolean;
|
||||
|
||||
/**
|
||||
* Checks if the current platform is Electron (desktop).
|
||||
* @returns true if running on Electron
|
||||
*/
|
||||
isElectron(): boolean;
|
||||
|
||||
/**
|
||||
* Checks if the current platform is PyWebView.
|
||||
* @returns true if running on PyWebView
|
||||
*/
|
||||
isPyWebView(): boolean;
|
||||
|
||||
/**
|
||||
* Checks if the current platform is web browser.
|
||||
* @returns true if running in a web browser
|
||||
*/
|
||||
isWeb(): boolean;
|
||||
|
||||
// Deep linking
|
||||
/**
|
||||
* Handles deep link URLs for the application.
|
||||
* @param url - The deep link URL to handle
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ImageResult, PlatformService } from "../PlatformService";
|
||||
import { ImageResult, PlatformService, PlatformCapabilities } from "../PlatformService";
|
||||
import { Filesystem, Directory } from "@capacitor/filesystem";
|
||||
import { Camera, CameraResultType, CameraSource } from "@capacitor/camera";
|
||||
import { logger } from "../../utils/logger";
|
||||
@@ -11,6 +11,21 @@ import { logger } from "../../utils/logger";
|
||||
* - Platform-specific features
|
||||
*/
|
||||
export class CapacitorPlatformService implements PlatformService {
|
||||
/**
|
||||
* Gets the capabilities of the Capacitor platform
|
||||
* @returns Platform capabilities object
|
||||
*/
|
||||
getCapabilities(): PlatformCapabilities {
|
||||
return {
|
||||
hasFileSystem: true,
|
||||
hasCamera: true,
|
||||
isMobile: true,
|
||||
isIOS: /iPad|iPhone|iPod/.test(navigator.userAgent),
|
||||
hasFileDownload: false,
|
||||
needsFileHandlingInstructions: true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a file from the app's data directory.
|
||||
* @param path - Relative path to the file in the app's data directory
|
||||
@@ -146,38 +161,6 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
return new Blob(byteArrays, { type: "image/jpeg" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if running on Capacitor platform.
|
||||
* @returns true, as this is the Capacitor implementation
|
||||
*/
|
||||
isCapacitor(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 false, as this is not web
|
||||
*/
|
||||
isWeb(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles deep link URLs for the application.
|
||||
* Note: Capacitor handles deep links automatically.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ImageResult, PlatformService } from "../PlatformService";
|
||||
import { ImageResult, PlatformService, PlatformCapabilities } from "../PlatformService";
|
||||
import { logger } from "../../utils/logger";
|
||||
|
||||
/**
|
||||
@@ -14,6 +14,21 @@ import { logger } from "../../utils/logger";
|
||||
* - System-level features
|
||||
*/
|
||||
export class ElectronPlatformService implements PlatformService {
|
||||
/**
|
||||
* Gets the capabilities of the Electron platform
|
||||
* @returns Platform capabilities object
|
||||
*/
|
||||
getCapabilities(): PlatformCapabilities {
|
||||
return {
|
||||
hasFileSystem: false, // Not implemented yet
|
||||
hasCamera: false, // Not implemented yet
|
||||
isMobile: false,
|
||||
isIOS: false,
|
||||
hasFileDownload: false, // Not implemented yet
|
||||
needsFileHandlingInstructions: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a file from the filesystem.
|
||||
* @param _path - Path to the file to read
|
||||
@@ -79,38 +94,6 @@ export class ElectronPlatformService implements PlatformService {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if running on Capacitor platform.
|
||||
* @returns false, as this is not Capacitor
|
||||
*/
|
||||
isCapacitor(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if running on Electron platform.
|
||||
* @returns true, as this is the Electron implementation
|
||||
*/
|
||||
isElectron(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if running on PyWebView platform.
|
||||
* @returns false, as this is not PyWebView
|
||||
*/
|
||||
isPyWebView(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if running on web platform.
|
||||
* @returns false, as this is not web
|
||||
*/
|
||||
isWeb(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should handle deep link URLs for the desktop application.
|
||||
* @param _url - The deep link URL to handle
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ImageResult, PlatformService } from "../PlatformService";
|
||||
import { ImageResult, PlatformService, PlatformCapabilities } from "../PlatformService";
|
||||
import { logger } from "../../utils/logger";
|
||||
|
||||
/**
|
||||
@@ -15,6 +15,21 @@ import { logger } from "../../utils/logger";
|
||||
* - Python-JavaScript bridge functionality
|
||||
*/
|
||||
export class PyWebViewPlatformService implements PlatformService {
|
||||
/**
|
||||
* Gets the capabilities of the PyWebView platform
|
||||
* @returns Platform capabilities object
|
||||
*/
|
||||
getCapabilities(): PlatformCapabilities {
|
||||
return {
|
||||
hasFileSystem: false, // Not implemented yet
|
||||
hasCamera: false, // Not implemented yet
|
||||
isMobile: false,
|
||||
isIOS: false,
|
||||
hasFileDownload: false, // Not implemented yet
|
||||
needsFileHandlingInstructions: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a file using the Python backend.
|
||||
* @param _path - Path to the file to read
|
||||
@@ -80,38 +95,6 @@ export class PyWebViewPlatformService implements PlatformService {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 true, as this is the PyWebView implementation
|
||||
*/
|
||||
isPyWebView(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if running on web platform.
|
||||
* @returns false, as this is not web
|
||||
*/
|
||||
isWeb(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should handle deep link URLs through the Python backend.
|
||||
* @param _url - The deep link URL to handle
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ImageResult, PlatformService } from "../PlatformService";
|
||||
import { ImageResult, PlatformService, PlatformCapabilities } from "../PlatformService";
|
||||
import { logger } from "../../utils/logger";
|
||||
|
||||
/**
|
||||
@@ -15,6 +15,21 @@ import { logger } from "../../utils/logger";
|
||||
* due to browser security restrictions. These methods throw appropriate errors.
|
||||
*/
|
||||
export class WebPlatformService implements PlatformService {
|
||||
/**
|
||||
* Gets the capabilities of the web platform
|
||||
* @returns Platform capabilities object
|
||||
*/
|
||||
getCapabilities(): PlatformCapabilities {
|
||||
return {
|
||||
hasFileSystem: false,
|
||||
hasCamera: true, // Through file input with capture
|
||||
isMobile: /iPhone|iPad|iPod|Android/i.test(navigator.userAgent),
|
||||
isIOS: /iPad|iPhone|iPod/.test(navigator.userAgent),
|
||||
hasFileDownload: true,
|
||||
needsFileHandlingInstructions: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported in web platform.
|
||||
* @param _path - Unused path parameter
|
||||
|
||||
Reference in New Issue
Block a user