fix: move PyodideWebView into Import tab; fix micropip blob URL error
Layout fix: WebView as a sibling in the root layout breaks flex geometry even with position:absolute. Moving it inside the Import tab screen (which Expo Router keeps mounted after first visit) eliminates the issue entirely and restores the original simple root layout. micropip fix: blob: URLs are not a recognised scheme in micropip — they are parsed as package requirement strings, producing InvalidRequirement. Write the wheel bytes to Pyodide's Emscripten FS (/tmp/bincio.whl) and install via emfs:/// instead.
This commit is contained in:
@@ -2,8 +2,9 @@ import * as DocumentPicker from 'expo-document-picker';
|
|||||||
import * as FileSystem from 'expo-file-system/legacy';
|
import * as FileSystem from 'expo-file-system/legacy';
|
||||||
import { useSQLiteContext } from 'expo-sqlite';
|
import { useSQLiteContext } from 'expo-sqlite';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Alert, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||||
import { insertActivity } from '@/db/queries';
|
import { insertActivity } from '@/db/queries';
|
||||||
|
import { PyodideWebView } from '@/extraction/PyodideWebView';
|
||||||
import { extractFile } from '@/extraction/extractActivity';
|
import { extractFile } from '@/extraction/extractActivity';
|
||||||
|
|
||||||
const FIT_EXTENSIONS = ['.fit', '.fit.gz'];
|
const FIT_EXTENSIONS = ['.fit', '.fit.gz'];
|
||||||
@@ -129,6 +130,13 @@ export default function ImportScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<View style={styles.screen}>
|
||||||
|
{/* Hidden WebView for Pyodide — mounted here so it lives inside the tab
|
||||||
|
(Expo Router keeps tabs mounted after first visit, preserving Pyodide state).
|
||||||
|
The 1×1 container clips it out of the scroll layout entirely. */}
|
||||||
|
<View style={styles.hiddenEngine}>
|
||||||
|
<PyodideWebView />
|
||||||
|
</View>
|
||||||
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
|
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
|
||||||
<Text style={styles.header}>Import</Text>
|
<Text style={styles.header}>Import</Text>
|
||||||
|
|
||||||
@@ -192,6 +200,7 @@ export default function ImportScreen() {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +255,9 @@ function arrayBufferToBase64(buf: ArrayBuffer): string {
|
|||||||
// ── Styles ───────────────────────────────────────────────────────────────────
|
// ── Styles ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: { flex: 1, backgroundColor: '#09090b' },
|
screen: { flex: 1, backgroundColor: '#09090b' },
|
||||||
|
hiddenEngine: { position: 'absolute', width: 1, height: 1, overflow: 'hidden' },
|
||||||
|
container: { flex: 1 },
|
||||||
content: { padding: 16, paddingTop: 60, paddingBottom: 40 },
|
content: { padding: 16, paddingTop: 60, paddingBottom: 40 },
|
||||||
header: { color: '#fff', fontSize: 22, fontWeight: '700', marginBottom: 12 },
|
header: { color: '#fff', fontSize: 22, fontWeight: '700', marginBottom: 12 },
|
||||||
body: { color: '#a1a1aa', fontSize: 14, lineHeight: 20, marginBottom: 24 },
|
body: { color: '#a1a1aa', fontSize: 14, lineHeight: 20, marginBottom: 24 },
|
||||||
|
|||||||
@@ -1,24 +1,13 @@
|
|||||||
import { Stack } from 'expo-router';
|
import { Stack } from 'expo-router';
|
||||||
import { SQLiteProvider } from 'expo-sqlite';
|
import { SQLiteProvider } from 'expo-sqlite';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import { StyleSheet, View } from 'react-native';
|
|
||||||
import { migrateDb } from '@/db';
|
import { migrateDb } from '@/db';
|
||||||
import { PyodideWebView } from '@/extraction/PyodideWebView';
|
|
||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
return (
|
return (
|
||||||
<View style={styles.root}>
|
|
||||||
{/* Hidden WebView: starts loading Pyodide immediately so the runtime
|
|
||||||
is warm by the time the user opens the Import tab. */}
|
|
||||||
<PyodideWebView />
|
|
||||||
<SQLiteProvider databaseName="bincio.db" onInit={migrateDb}>
|
<SQLiteProvider databaseName="bincio.db" onInit={migrateDb}>
|
||||||
<StatusBar style="light" />
|
<StatusBar style="light" />
|
||||||
<Stack screenOptions={{ headerShown: false }} />
|
<Stack screenOptions={{ headerShown: false }} />
|
||||||
</SQLiteProvider>
|
</SQLiteProvider>
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
root: { flex: 1 },
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ const PY_INSTALL_PACKAGES = [
|
|||||||
'await micropip.install(["fitdecode", "gpxpy"])',
|
'await micropip.install(["fitdecode", "gpxpy"])',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
// emfs:// is Pyodide's Emscripten-FS URL scheme — the only reliable way to
|
||||||
|
// install a wheel from bytes without an http/https URL (blob: URLs are not
|
||||||
|
// recognised by micropip and cause an InvalidRequirement parse error).
|
||||||
const PY_INSTALL_WHEEL = [
|
const PY_INSTALL_WHEEL = [
|
||||||
'import micropip',
|
'import micropip',
|
||||||
'await micropip.install(_blobUrl, deps=False)',
|
'await micropip.install("emfs:///tmp/bincio.whl", deps=False)',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
const PY_EXTRACT = [
|
const PY_EXTRACT = [
|
||||||
@@ -109,16 +112,14 @@ window._bincioExtract = async function(params) {
|
|||||||
if (initError) throw new Error(initError);
|
if (initError) throw new Error(initError);
|
||||||
|
|
||||||
// Install bincio wheel on first extraction.
|
// Install bincio wheel on first extraction.
|
||||||
// Wheel bytes arrive pre-fetched from the React Native side as base64,
|
// Wheel bytes arrive pre-fetched from React Native (avoids ATS/HTTP issues).
|
||||||
// so the WebView never needs to make an HTTP request (avoids ATS blocks).
|
// Write to Pyodide's Emscripten FS so micropip can install via emfs:// URL
|
||||||
|
// (blob: URLs are not recognised by micropip — they cause an InvalidRequirement error).
|
||||||
if (!wheelReady) {
|
if (!wheelReady) {
|
||||||
post({ type: 'progress', msg: 'Loading Bincio…' });
|
post({ type: 'progress', msg: 'Loading Bincio…' });
|
||||||
var wheelBytes = Uint8Array.from(atob(wheelBase64), function(c) { return c.charCodeAt(0); });
|
var wheelBytes = Uint8Array.from(atob(wheelBase64), function(c) { return c.charCodeAt(0); });
|
||||||
var wheelBlob = new Blob([wheelBytes]);
|
pyodide.FS.writeFile('/tmp/bincio.whl', wheelBytes);
|
||||||
var blobUrl = URL.createObjectURL(wheelBlob);
|
|
||||||
pyodide.globals.set('_blobUrl', blobUrl);
|
|
||||||
await pyodide.runPythonAsync(_PY_INSTALL_WHEEL);
|
await pyodide.runPythonAsync(_PY_INSTALL_WHEEL);
|
||||||
URL.revokeObjectURL(blobUrl);
|
|
||||||
wheelReady = true;
|
wheelReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user