From abf18835f6fe38a24468aa0dc9a39f18040690e3 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Sun, 6 Apr 2025 06:58:25 +0000 Subject: [PATCH] feat: update TypeScript config for platform services - Add useDefineForClassFields for class field initialization - Remove test-playwright from includes - Add tsconfig.node.json reference - Remove redundant node_modules exclude --- src/services/PlatformService.ts | 20 +++++ src/services/PlatformServiceFactory.ts | 36 ++++++++ .../platforms/CapacitorPlatformService.ts | 80 +++++++++++++++++ .../platforms/ElectronPlatformService.ts | 47 ++++++++++ .../platforms/PyWebViewPlatformService.ts | 47 ++++++++++ src/services/platforms/WebPlatformService.ts | 87 +++++++++++++++++++ tsconfig.node.json | 10 +++ 7 files changed, 327 insertions(+) create mode 100644 src/services/PlatformService.ts create mode 100644 src/services/PlatformServiceFactory.ts create mode 100644 src/services/platforms/CapacitorPlatformService.ts create mode 100644 src/services/platforms/ElectronPlatformService.ts create mode 100644 src/services/platforms/PyWebViewPlatformService.ts create mode 100644 src/services/platforms/WebPlatformService.ts create mode 100644 tsconfig.node.json diff --git a/src/services/PlatformService.ts b/src/services/PlatformService.ts new file mode 100644 index 00000000..8cb6c8e4 --- /dev/null +++ b/src/services/PlatformService.ts @@ -0,0 +1,20 @@ +export interface PlatformService { + // File system operations + readFile(path: string): Promise; + writeFile(path: string, content: string): Promise; + deleteFile(path: string): Promise; + listFiles(directory: string): Promise; + + // Camera operations + takePicture(): Promise; + pickImage(): Promise; + + // Platform specific features + isCapacitor(): boolean; + isElectron(): boolean; + isPyWebView(): boolean; + isWeb(): boolean; + + // Deep linking + handleDeepLink(url: string): Promise; +} diff --git a/src/services/PlatformServiceFactory.ts b/src/services/PlatformServiceFactory.ts new file mode 100644 index 00000000..28ead483 --- /dev/null +++ b/src/services/PlatformServiceFactory.ts @@ -0,0 +1,36 @@ +import { Capacitor } from "@capacitor/core"; +import { PlatformService } from "./PlatformService"; +import { WebPlatformService } from "./platforms/WebPlatformService"; +import { CapacitorPlatformService } from "./platforms/CapacitorPlatformService"; +import { ElectronPlatformService } from "./platforms/ElectronPlatformService"; +import { PyWebViewPlatformService } from "./platforms/PyWebViewPlatformService"; + +export class PlatformServiceFactory { + private static instance: PlatformService | null = null; + + public static getInstance(): PlatformService { + if (PlatformServiceFactory.instance) { + return PlatformServiceFactory.instance; + } + + const platform = process.env.VITE_PLATFORM || "web"; + + switch (platform) { + case "capacitor": + PlatformServiceFactory.instance = new CapacitorPlatformService(); + break; + case "electron": + PlatformServiceFactory.instance = new ElectronPlatformService(); + break; + case "pywebview": + PlatformServiceFactory.instance = new PyWebViewPlatformService(); + break; + case "web": + default: + PlatformServiceFactory.instance = new WebPlatformService(); + break; + } + + return PlatformServiceFactory.instance; + } +} diff --git a/src/services/platforms/CapacitorPlatformService.ts b/src/services/platforms/CapacitorPlatformService.ts new file mode 100644 index 00000000..ae1aa2ab --- /dev/null +++ b/src/services/platforms/CapacitorPlatformService.ts @@ -0,0 +1,80 @@ +import { PlatformService } from "../PlatformService"; +import { Capacitor } from "@capacitor/core"; +import { Filesystem, Directory } from "@capacitor/filesystem"; +import { Camera, CameraResultType, CameraSource } from "@capacitor/camera"; +import { App } from "@capacitor/app"; + +export class CapacitorPlatformService implements PlatformService { + async readFile(path: string): Promise { + const file = await Filesystem.readFile({ + path, + directory: Directory.Data, + }); + return file.data; + } + + async writeFile(path: string, content: string): Promise { + await Filesystem.writeFile({ + path, + data: content, + directory: Directory.Data, + }); + } + + async deleteFile(path: string): Promise { + await Filesystem.deleteFile({ + path, + directory: Directory.Data, + }); + } + + async listFiles(directory: string): Promise { + const result = await Filesystem.readdir({ + path: directory, + directory: Directory.Data, + }); + return result.files; + } + + async takePicture(): Promise { + const image = await Camera.getPhoto({ + quality: 90, + allowEditing: true, + resultType: CameraResultType.Uri, + source: CameraSource.Camera, + }); + return image.webPath || ""; + } + + async pickImage(): Promise { + const image = await Camera.getPhoto({ + quality: 90, + allowEditing: true, + resultType: CameraResultType.Uri, + source: CameraSource.Photos, + }); + return image.webPath || ""; + } + + isCapacitor(): boolean { + return true; + } + + isElectron(): boolean { + return false; + } + + isPyWebView(): boolean { + return false; + } + + isWeb(): boolean { + return false; + } + + async handleDeepLink(url: string): Promise { + // Capacitor handles deep links automatically + // This is just a placeholder for the interface + return Promise.resolve(); + } +} diff --git a/src/services/platforms/ElectronPlatformService.ts b/src/services/platforms/ElectronPlatformService.ts new file mode 100644 index 00000000..8595d390 --- /dev/null +++ b/src/services/platforms/ElectronPlatformService.ts @@ -0,0 +1,47 @@ +import { PlatformService } from '../PlatformService'; + +export class ElectronPlatformService implements PlatformService { + async readFile(path: string): Promise { + throw new Error('Not implemented'); + } + + async writeFile(path: string, content: string): Promise { + throw new Error('Not implemented'); + } + + async deleteFile(path: string): Promise { + throw new Error('Not implemented'); + } + + async listFiles(directory: string): Promise { + throw new Error('Not implemented'); + } + + async takePicture(): Promise { + throw new Error('Not implemented'); + } + + async pickImage(): Promise { + throw new Error('Not implemented'); + } + + isCapacitor(): boolean { + return false; + } + + isElectron(): boolean { + return true; + } + + isPyWebView(): boolean { + return false; + } + + isWeb(): boolean { + return false; + } + + async handleDeepLink(url: string): Promise { + throw new Error('Not implemented'); + } +} \ No newline at end of file diff --git a/src/services/platforms/PyWebViewPlatformService.ts b/src/services/platforms/PyWebViewPlatformService.ts new file mode 100644 index 00000000..4d10a285 --- /dev/null +++ b/src/services/platforms/PyWebViewPlatformService.ts @@ -0,0 +1,47 @@ +import { PlatformService } from '../PlatformService'; + +export class PyWebViewPlatformService implements PlatformService { + async readFile(path: string): Promise { + throw new Error('Not implemented'); + } + + async writeFile(path: string, content: string): Promise { + throw new Error('Not implemented'); + } + + async deleteFile(path: string): Promise { + throw new Error('Not implemented'); + } + + async listFiles(directory: string): Promise { + throw new Error('Not implemented'); + } + + async takePicture(): Promise { + throw new Error('Not implemented'); + } + + async pickImage(): Promise { + throw new Error('Not implemented'); + } + + isCapacitor(): boolean { + return false; + } + + isElectron(): boolean { + return false; + } + + isPyWebView(): boolean { + return true; + } + + isWeb(): boolean { + return false; + } + + async handleDeepLink(url: string): Promise { + throw new Error('Not implemented'); + } +} \ No newline at end of file diff --git a/src/services/platforms/WebPlatformService.ts b/src/services/platforms/WebPlatformService.ts new file mode 100644 index 00000000..befb7cbc --- /dev/null +++ b/src/services/platforms/WebPlatformService.ts @@ -0,0 +1,87 @@ +import { PlatformService } from "../PlatformService"; + +export class WebPlatformService implements PlatformService { + async readFile(path: string): Promise { + throw new Error("File system access not available in web platform"); + } + + async writeFile(path: string, content: string): Promise { + throw new Error("File system access not available in web platform"); + } + + async deleteFile(path: string): Promise { + throw new Error("File system access not available in web platform"); + } + + async listFiles(directory: string): Promise { + throw new Error("File system access not available in web platform"); + } + + async takePicture(): Promise { + return new Promise((resolve, reject) => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.capture = "environment"; + + input.onchange = (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (event) => { + resolve(event.target?.result as string); + }; + reader.readAsDataURL(file); + } else { + reject(new Error("No file selected")); + } + }; + + input.click(); + }); + } + + async pickImage(): Promise { + return new Promise((resolve, reject) => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + + input.onchange = (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (event) => { + resolve(event.target?.result as string); + }; + reader.readAsDataURL(file); + } else { + reject(new Error("No file selected")); + } + }; + + input.click(); + }); + } + + isCapacitor(): boolean { + return false; + } + + isElectron(): boolean { + return false; + } + + isPyWebView(): boolean { + return false; + } + + isWeb(): boolean { + return true; + } + + async handleDeepLink(url: string): Promise { + // Web platform can handle deep links through URL parameters + return Promise.resolve(); + } +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 00000000..6aa7b5c6 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.*"] +} \ No newline at end of file