diff --git a/.changeset/neat-crabs-lie.md b/.changeset/neat-crabs-lie.md new file mode 100644 index 000000000..c848811a2 --- /dev/null +++ b/.changeset/neat-crabs-lie.md @@ -0,0 +1,5 @@ +--- +'@powersync/web': patch +--- + +Testing diff --git a/demos/example-capacitor/android/app/capacitor.build.gradle b/demos/example-capacitor/android/app/capacitor.build.gradle index 259821da2..d46a7a3d6 100644 --- a/demos/example-capacitor/android/app/capacitor.build.gradle +++ b/demos/example-capacitor/android/app/capacitor.build.gradle @@ -9,6 +9,8 @@ android { apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { + implementation project(':capacitor-filesystem') + implementation project(':capacitor-share') implementation project(':capacitor-splash-screen') } diff --git a/demos/example-capacitor/android/capacitor.settings.gradle b/demos/example-capacitor/android/capacitor.settings.gradle index 68ddb413e..a765310e6 100644 --- a/demos/example-capacitor/android/capacitor.settings.gradle +++ b/demos/example-capacitor/android/capacitor.settings.gradle @@ -2,5 +2,11 @@ include ':capacitor-android' project(':capacitor-android').projectDir = new File('../../../node_modules/@capacitor/android/capacitor') +include ':capacitor-filesystem' +project(':capacitor-filesystem').projectDir = new File('../../../node_modules/@capacitor/filesystem/android') + +include ':capacitor-share' +project(':capacitor-share').projectDir = new File('../../../node_modules/@capacitor/share/android') + include ':capacitor-splash-screen' project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/@capacitor/splash-screen/android') diff --git a/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj b/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj index e9273628f..ba5de84a2 100644 --- a/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj +++ b/demos/example-capacitor/ios/App/App.xcodeproj/project.pbxproj @@ -350,7 +350,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; INFOPLIST_FILE = App/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 13; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.0; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; @@ -370,7 +370,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; INFOPLIST_FILE = App/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 13; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.powersync.example; diff --git a/demos/example-capacitor/ios/App/App/Info.plist b/demos/example-capacitor/ios/App/App/Info.plist index 966c88263..f7c470487 100644 --- a/demos/example-capacitor/ios/App/App/Info.plist +++ b/demos/example-capacitor/ios/App/App/Info.plist @@ -1,49 +1,53 @@ - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - powersync-capacitor - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - - + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + powersync-capacitor + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + NSDocumentsFolderUsageDescription + This app needs access to documents folder to save files + NSFileProviderDomainUsageDescription + This app needs access to manage files + + \ No newline at end of file diff --git a/demos/example-capacitor/ios/App/Podfile b/demos/example-capacitor/ios/App/Podfile index 70b65a21b..9e2944c4f 100644 --- a/demos/example-capacitor/ios/App/Podfile +++ b/demos/example-capacitor/ios/App/Podfile @@ -11,6 +11,8 @@ install! 'cocoapods', :disable_input_output_paths => true def capacitor_pods pod 'Capacitor', :path => '../../../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../../../node_modules/@capacitor/ios' + pod 'CapacitorFilesystem', :path => '../../../../node_modules/@capacitor/filesystem' + pod 'CapacitorShare', :path => '../../../../node_modules/@capacitor/share' pod 'CapacitorSplashScreen', :path => '../../../../node_modules/@capacitor/splash-screen' end diff --git a/demos/example-capacitor/ios/App/Podfile.lock b/demos/example-capacitor/ios/App/Podfile.lock index 850243219..d8e4f0f29 100644 --- a/demos/example-capacitor/ios/App/Podfile.lock +++ b/demos/example-capacitor/ios/App/Podfile.lock @@ -1,13 +1,19 @@ PODS: - - Capacitor (6.0.0): + - Capacitor (6.2.1): - CapacitorCordova - - CapacitorCordova (6.0.0) - - CapacitorSplashScreen (6.0.0): + - CapacitorCordova (6.2.1) + - CapacitorFilesystem (6.0.3): + - Capacitor + - CapacitorShare (6.0.3): + - Capacitor + - CapacitorSplashScreen (7.0.2): - Capacitor DEPENDENCIES: - "Capacitor (from `../../../../node_modules/@capacitor/ios`)" - "CapacitorCordova (from `../../../../node_modules/@capacitor/ios`)" + - "CapacitorFilesystem (from `../../../../node_modules/@capacitor/filesystem`)" + - "CapacitorShare (from `../../../../node_modules/@capacitor/share`)" - "CapacitorSplashScreen (from `../../../../node_modules/@capacitor/splash-screen`)" EXTERNAL SOURCES: @@ -15,14 +21,20 @@ EXTERNAL SOURCES: :path: "../../../../node_modules/@capacitor/ios" CapacitorCordova: :path: "../../../../node_modules/@capacitor/ios" + CapacitorFilesystem: + :path: "../../../../node_modules/@capacitor/filesystem" + CapacitorShare: + :path: "../../../../node_modules/@capacitor/share" CapacitorSplashScreen: :path: "../../../../node_modules/@capacitor/splash-screen" SPEC CHECKSUMS: - Capacitor: 559d073c4ca6c27f8e7002c807eea94c3ba435a9 - CapacitorCordova: 8c4bfdf69368512e85b1d8b724dd7546abeb30af - CapacitorSplashScreen: 5431ab8d19c1c6e95777d53bfaa7a36a6c3d94c7 + Capacitor: 1e0d0e7330dea9f983b50da737d8918abcf273f8 + CapacitorCordova: 8d93e14982f440181be7304aa9559ca631d77fff + CapacitorFilesystem: fa3099b3c3aa43a1b51362d0c999301ab1a9a752 + CapacitorShare: 7af6ca761ce62030e8e9fbd2eb82416f5ceced38 + CapacitorSplashScreen: 8d6c8cb0542a8e81585c593815db8785ed8ce454 -PODFILE CHECKSUM: 30a5df536d5e7830e635f84e1fe35fa438802eaa +PODFILE CHECKSUM: c1703336f990a4728e25eafbdd9992a7afc2008e -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/demos/example-capacitor/package.json b/demos/example-capacitor/package.json index b9609d0e9..fa973a2fe 100644 --- a/demos/example-capacitor/package.json +++ b/demos/example-capacitor/package.json @@ -21,14 +21,19 @@ "dependencies": { "@capacitor/android": "^6.0.0", "@capacitor/core": "latest", + "@capacitor/filesystem": "^6.0.0", "@capacitor/ios": "^6.0.0", + "@capacitor/share": "^6.0.0", "@capacitor/splash-screen": "latest", "@journeyapps/wa-sqlite": "^1.2.0", + "@mui/icons-material": "^7.3.1", "@powersync/react": "workspace:*", "@powersync/web": "workspace:*", + "@types/react-router-dom": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.23.0" + "react-router-dom": "^6.30.1", + "react-window": "^1.8.11" }, "devDependencies": { "@capacitor/cli": "^6.0.0", @@ -36,6 +41,7 @@ "@types/node": "^20.12.12", "@types/react": "^18.3.2", "@types/react-dom": "^18.3.0", + "@types/react-window": "^1.8.8", "vite": "^5.2.11", "vite-plugin-require": "^1.2.14", "vite-plugin-top-level-await": "^1.4.1", diff --git a/demos/example-capacitor/src/app/LogsPage.tsx b/demos/example-capacitor/src/app/LogsPage.tsx new file mode 100644 index 000000000..fadafdb3a --- /dev/null +++ b/demos/example-capacitor/src/app/LogsPage.tsx @@ -0,0 +1,216 @@ +import { Capacitor } from '@capacitor/core'; +import { Share } from '@capacitor/share'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import { + Avatar, + Button, + ButtonGroup, + Checkbox, + Divider, + FormControlLabel, + FormGroup, + Grid, + IconButton, + ListItem, + ListItemAvatar, + ListItemText, + Paper, + styled, + Typography +} from '@mui/material'; +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { FixedSizeList, ListChildComponentProps } from 'react-window'; +import { LOG_STORAGE, LogRecord } from '../components/providers/Logging.js'; + +function downloadTextFile(filename: string, content: string) { + const blob = new Blob([content], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, 0); +} + +async function shareFile(content: string) { + await Share.share({ + title: 'logs', + text: content, + dialogTitle: 'Share your log file' + }); +} + +export const LogDisplay: React.FC<{ logs: ReadonlyArray> }> = React.memo((props) => { + const { logs } = props; + + const Row = ({ index, style }: ListChildComponentProps) => { + const log = logs[index]; + return ( + + + + + {log.level[0].toUpperCase()} + + + + + {log.timestamp} + + + {log.level.toUpperCase()} + + > + } + secondary={ + + {log.message.slice(0, 100)} + {log.message.length > 100 ? '...' : ''} + + } + /> + + {index < logs.length - 1 && } + + ); + }; + + return ( + + + {logs.length === 0 ? ( + No logs + ) : ( + + {Row} + + )} + + + ); +}); + +const LOG_LEVELS = [ + { label: 'Error', value: 'ERROR', color: '#d32f2f' }, + { label: 'Warn', value: 'WARN', color: '#fbc02d' }, + { label: 'Debug', value: 'DEBUG', color: '#1976d2' }, + { label: 'Info', value: 'INFO', color: '#1976d2' } +]; + +const LogsPage = () => { + const [logs, setLogs] = React.useState(LOG_STORAGE.logs); + const [selectedLevels, setSelectedLevels] = React.useState(LOG_LEVELS.map((l) => l.value)); + const navigate = useNavigate(); + + React.useEffect(() => { + return LOG_STORAGE.registerListener({ + logsUpdated: (logs) => setLogs([...logs]) + }); + }, []); + + const handleLevelChange = (level: string) => { + setSelectedLevels((prev) => (prev.includes(level) ? prev.filter((l) => l !== level) : [...prev, level])); + }; + + const filteredLogs = logs.filter((log) => selectedLevels.includes(log.level)); + + return ( + + + navigate(-1)} aria-label="Back"> + + + + Logs + + + + + {LOG_LEVELS.map(({ label, value, color }) => ( + handleLevelChange(value)} + sx={{ color, '&.Mui-checked': { color } }} + /> + } + label={label} + /> + ))} + + + + + LOG_STORAGE.clearLogs()}> + Clear Logs + + { + const filename = `logs-${new Date().toISOString()}.json`; + const content = JSON.stringify(logs); + const platform = Capacitor.getPlatform(); + if (platform == 'web') { + downloadTextFile(filename, content); + } else { + await shareFile(content); + } + }}> + Download + + + + + + + + ); +}; + +namespace S { + export const MainGrid = styled(Grid)` + width: 100vw; + `; + + export const LogsListContainer = styled('div')` + width: 100%; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.07); + overflow-x: auto; + overflow-y: auto; + max-height: 1000px; + padding: 0.5rem 0; + `; +} + +export default LogsPage; diff --git a/demos/example-capacitor/src/app/index.tsx b/demos/example-capacitor/src/app/index.tsx index c8a2e01f8..8bc064d98 100644 --- a/demos/example-capacitor/src/app/index.tsx +++ b/demos/example-capacitor/src/app/index.tsx @@ -1,15 +1,32 @@ -import React from 'react'; import { createRoot } from 'react-dom/client'; -import EntryPage from './page.jsx'; + +import { createTheme, CssBaseline, ThemeProvider } from '@mui/material'; +import { BrowserRouter, Route, Routes } from 'react-router-dom'; import SystemProvider from '../components/providers/SystemProvider.jsx'; +import LogsPage from './LogsPage.jsx'; +import EntryPage from './page.jsx'; + +const theme = createTheme({ + palette: { + mode: 'dark' + } +}); const root = createRoot(document.getElementById('app')!); root.render(); export function App() { return ( - - - + + + + + + } /> + } /> + + + + ); } diff --git a/demos/example-capacitor/src/app/page.tsx b/demos/example-capacitor/src/app/page.tsx index b5f0049f5..35e125025 100644 --- a/demos/example-capacitor/src/app/page.tsx +++ b/demos/example-capacitor/src/app/page.tsx @@ -1,34 +1,15 @@ +import { Button, ButtonGroup, CircularProgress, Grid, ListItem, Paper, styled } from '@mui/material'; +import { usePowerSync, useQuery, useStatus } from '@powersync/react'; import React from 'react'; -import { CircularProgress, Grid, ListItem, styled } from '@mui/material'; -import { useQuery, useStatus } from '@powersync/react'; - -const EntryPage = () => { - const status = useStatus(); - const { data: customers } = useQuery('SELECT id, name FROM customers'); - - const areVariablesSet = import.meta.env.VITE_POWERSYNC_URL && import.meta.env.VITE_PUBLIC_POWERSYNC_TOKEN; - - if (areVariablesSet && !status.hasSynced) { - return ( - - - Syncing down from the backend. This will load indefinitely if you have not set the connection up correctly. - - - - ); - } - - if (!areVariablesSet) { - return ( - - You have not set up a connection to the backend, please connect your backend. - - ); - } +import { useNavigate } from 'react-router-dom'; +import LocalEditWidget from '../components/LocalEditWidget.js'; +import { Customer } from '../library/powersync/AppSchema.js'; +import { BackendConnector } from '../library/powersync/BackendConnector.js'; +const CustomersWidget: React.FC<{ customers: Customer[] }> = (props) => { + const { customers } = props; return ( - + Customers @@ -47,21 +28,74 @@ const EntryPage = () => { )} - + + ); +}; + +const EntryPage = () => { + const status = useStatus(); + const powerSync = usePowerSync(); + + const { data: customers } = useQuery('SELECT id, name FROM customers'); + const navigate = useNavigate(); + + const areVariablesSet = import.meta.env.VITE_POWERSYNC_URL && import.meta.env.VITE_PUBLIC_POWERSYNC_TOKEN; + + return ( + + + + powerSync.connect(new BackendConnector())}> + Connect + + powerSync.disconnect()}> + Disconnect + + navigate('/logs')}> + View Logs + + + + + + + {areVariablesSet && !status.hasSynced && ( + <> + + Syncing down from the backend. This will load indefinitely if you have not set the connection up + correctly. + + + > + )} + {!areVariablesSet && ( + + You have not set up a connection to the backend, please connect your backend. + + )} + {areVariablesSet && status.hasSynced && } + + + + + + + ); }; namespace S { - export const CenteredGrid = styled(Grid)` + export const FlexGrid = styled(Grid)` display: flex; - justify-content: center; - align-items: center; + flex-direction: column; + margin: 10px; `; - export const MainGrid = styled(CenteredGrid)` - min-height: 100vh; - display: flex; - flex-direction: column; + export const CenteredGrid = styled(FlexGrid)` + justify-content: center; + align-items: center; `; } diff --git a/demos/example-capacitor/src/components/LocalEditWidget.tsx b/demos/example-capacitor/src/components/LocalEditWidget.tsx new file mode 100644 index 000000000..864aac62e --- /dev/null +++ b/demos/example-capacitor/src/components/LocalEditWidget.tsx @@ -0,0 +1,61 @@ +import DeleteIcon from '@mui/icons-material/Delete'; +import { Button, IconButton, List, ListItem, ListItemText, Paper, Typography } from '@mui/material'; +import { usePowerSync, useQuery } from '@powersync/react'; +import React from 'react'; +import { Product } from '../library/powersync/AppSchema.js'; + +function getRandomProductName() { + const adjectives = ['Cool', 'Amazing', 'Fresh', 'New', 'Shiny', 'Super', 'Eco', 'Smart']; + const nouns = ['Widget', 'Gadget', 'Device', 'Item', 'Thing', 'Product', 'Tool', 'Object']; + return ( + adjectives[Math.floor(Math.random() * adjectives.length)] + + ' ' + + nouns[Math.floor(Math.random() * nouns.length)] + + ' #' + + Math.floor(Math.random() * 10000) + ); +} + +export default function LocalEditWidget() { + const powerSync = usePowerSync(); + const { data: products } = useQuery('SELECT * FROM products'); + + const addProduct = React.useCallback(() => { + return powerSync.execute('INSERT INTO products (id, name) VALUES (uuid(), ?)', [getRandomProductName()]); + }, []); + + const deleteProduct = React.useCallback((product: Product) => { + return powerSync.execute('DELETE FROM products WHERE id = ?', [product.id]); + }, []); + + return ( + + + Products + + Perform local only edits to Products. These won't be synced. + + Add Random Product + + + {products.length === 0 ? ( + + + + ) : ( + products.map((product) => ( + deleteProduct(product)}> + + + }> + + + )) + )} + + + ); +} diff --git a/demos/example-capacitor/src/components/providers/Logging.ts b/demos/example-capacitor/src/components/providers/Logging.ts new file mode 100644 index 000000000..e31d97b71 --- /dev/null +++ b/demos/example-capacitor/src/components/providers/Logging.ts @@ -0,0 +1,120 @@ +import { BaseObserver, ControlledExecutor, createBaseLogger, LogLevel } from '@powersync/web'; + +export type LogRecord = { + level: string; + timestamp: string; + message: string; +}; + +export type LogListener = { + logsUpdated: (logs: ReadonlyArray>) => void; +}; + +export class LogStorage extends BaseObserver { + protected _logs: LogRecord[]; + protected writeExecutor: ControlledExecutor; + protected dbPromise: Promise; + + static readonly LOGS_DB_NAME = '_ps_logs_db'; + static readonly LOGS_STORE_NAME = 'logs'; + + get logs(): ReadonlyArray> { + return this._logs; + } + + constructor() { + super(); + this._logs = []; + this.writeExecutor = new ControlledExecutor(() => this.saveLogsToIndexedDB()); + this.dbPromise = this.openLogsDB(); + this.readLogsFromIndexedDB(); + } + + clearLogs() { + this._logs = []; + this.iterateListeners((l) => l.logsUpdated?.(this.logs)); + } + + writeLog(record: LogRecord) { + this._logs.unshift(record); + this.iterateListeners((l) => l.logsUpdated?.(this.logs)); + this.writeExecutor.schedule(); + } + + private async saveLogsToIndexedDB() { + const db = await this.dbPromise; + const tx = db.transaction(LogStorage.LOGS_STORE_NAME, 'readwrite'); + const store = tx.objectStore(LogStorage.LOGS_STORE_NAME); + await new Promise((resolve, reject) => { + const clearReq = store.clear(); + clearReq.onsuccess = () => { + this.logs.forEach((log) => store.add(log)); + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }; + clearReq.onerror = () => reject(clearReq.error); + }); + } + + // Read all LogRecord entries from IndexedDB + private async readLogsFromIndexedDB(): Promise { + const db = await this.dbPromise; + const tx = db.transaction(LogStorage.LOGS_STORE_NAME, 'readwrite'); + const store = tx.objectStore(LogStorage.LOGS_STORE_NAME); + return new Promise((resolve, reject) => { + const req = store.getAll(); + req.onsuccess = () => { + this._logs = (req.result as any[]) + .map(({ id, ...rest }) => rest) + // sort in descending order + .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); + resolve(this._logs); + this.iterateListeners((l) => l.logsUpdated?.(this.logs)); + }; + req.onerror = () => reject(req.error); + }); + } + + // Helper to open the IndexedDB database + private async openLogsDB(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(LogStorage.LOGS_DB_NAME, 1); + request.onupgradeneeded = () => { + const db = request.result; + if (!db.objectStoreNames.contains(LogStorage.LOGS_STORE_NAME)) { + db.createObjectStore(LogStorage.LOGS_STORE_NAME, { keyPath: 'id', autoIncrement: true }); + } + }; + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + } +} + +export const LOG_STORAGE = new LogStorage(); + +const mapLogParam = (param: any) => { + if (param instanceof Error) { + return ` +Error +Name: ${param.name} +Message: ${param.message} +Cause: ${param.cause} +Stack: ${param.stack} + `.trim(); + } + return JSON.stringify(param); +}; + +// Configure base logger for global settings +const logger = createBaseLogger(); +logger.useDefaults({ + defaultLevel: LogLevel.DEBUG, + formatter: (messages, context) => { + LOG_STORAGE.writeLog({ + level: context.level.name, + message: messages.map(mapLogParam).join(' -- '), + timestamp: new Date().toISOString() + }); + } +}); diff --git a/demos/example-capacitor/src/components/providers/SystemProvider.tsx b/demos/example-capacitor/src/components/providers/SystemProvider.tsx index fc3b9e050..e4a2ad124 100644 --- a/demos/example-capacitor/src/components/providers/SystemProvider.tsx +++ b/demos/example-capacitor/src/components/providers/SystemProvider.tsx @@ -1,30 +1,25 @@ -import { PowerSyncContext } from '@powersync/react'; -import { PowerSyncDatabase, createBaseLogger, LogLevel } from '@powersync/web'; +import { Capacitor } from '@capacitor/core'; import { CircularProgress } from '@mui/material'; +import { PowerSyncContext } from '@powersync/react'; +import { PowerSyncDatabase, WASQLiteOpenFactory, WASQLiteVFS } from '@powersync/web'; import React, { Suspense } from 'react'; import { AppSchema } from '../../library/powersync/AppSchema.js'; import { BackendConnector } from '../../library/powersync/BackendConnector.js'; -import { Capacitor } from '@capacitor/core'; - -const logger = createBaseLogger(); -logger.useDefaults(); -logger.setLevel(LogLevel.DEBUG); const platform = Capacitor.getPlatform(); -const isIOs = platform === 'ios'; -// Web worker implementation does not work on iOS -const useWebWorker = !isIOs; const powerSync = new PowerSyncDatabase({ - database: { dbFilename: 'powersync2.db' }, + database: new WASQLiteOpenFactory({ + dbFilename: 'ps.db', + vfs: platform == 'ios' ? WASQLiteVFS.AccessHandlePoolVFS : WASQLiteVFS.OPFSCoopSyncVFS, + debugMode: true + }), schema: AppSchema, flags: { - enableMultiTabs: false, - useWebWorker + enableMultiTabs: false } }); const connector = new BackendConnector(); - powerSync.connect(connector); export const SystemProvider = ({ children }: { children: React.ReactNode }) => { diff --git a/demos/example-capacitor/src/index.css b/demos/example-capacitor/src/index.css index 8856f90b3..a89143ed2 100644 --- a/demos/example-capacitor/src/index.css +++ b/demos/example-capacitor/src/index.css @@ -1,7 +1,10 @@ body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, - Arial, sans-serif; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; margin: auto; max-width: 38rem; padding: 2rem; + padding-top: env(safe-area-inset-top); + padding-bottom: env(safe-area-inset-bottom); + padding-left: env(safe-area-inset-left); + padding-right: env(safe-area-inset-right); } diff --git a/demos/example-capacitor/src/index.html b/demos/example-capacitor/src/index.html index de7a97c29..27e149188 100644 --- a/demos/example-capacitor/src/index.html +++ b/demos/example-capacitor/src/index.html @@ -1,11 +1,15 @@ - - - - - - - - - - + + + + + + + + + + + + + +
- Syncing down from the backend. This will load indefinitely if you have not set the connection up correctly. -
+ Syncing down from the backend. This will load indefinitely if you have not set the connection up + correctly. +