mirror of
https://github.com/stablyai/orca.git
synced 2026-11-04 02:57:36 +00:00
* fix(mobile): suspend gestures in hidden and background terminals Adapt the retained-pane interaction lifecycle from community PR #30324. Restore policy across readiness/recovery and keep output/query replies flowing. Release cancelled gesture frames, discard touches interrupted by suspension, and restore the resting pinch transform on every gesture cancellation. Co-authored-by: Wooseong Kim <innocarpe@gmail.com> Co-authored-by: Neil <nwparker@users.noreply.github.com> * test(mobile): stub route focus where the web mount renders terminal panes * test(mobile): send production-shaped file taps and the clipboard prop in activity tests * fix(mobile): keep inactive and interrupted terminal moves away from xterm --------- Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com> Co-authored-by: Wooseong Kim <innocarpe@gmail.com> Co-authored-by: Neil <nwparker@users.noreply.github.com>
139 lines
4.4 KiB
TypeScript
139 lines
4.4 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react'
|
|
import { AppState, StyleSheet, View } from 'react-native'
|
|
import { useFocusEffect } from 'expo-router'
|
|
import type { ParsedFileLinkLocation } from '../../../src/shared/file-link-location'
|
|
import { TerminalWebView } from '../terminal/TerminalWebView'
|
|
import type {
|
|
MobileTerminalTheme,
|
|
TerminalKeyboardAvoidanceMetrics,
|
|
TerminalModes,
|
|
TerminalPointerInput,
|
|
TerminalWebViewHandle
|
|
} from '../terminal/terminal-webview-contract'
|
|
|
|
type TerminalPaneViewProps = {
|
|
handle: string
|
|
active: boolean
|
|
keyboardLift: number
|
|
terminalTheme?: MobileTerminalTheme
|
|
textScale: number
|
|
onRef: (handle: string, ref: TerminalWebViewHandle | null) => void
|
|
onWebReady: (handle: string) => void
|
|
onSelectionMode: (handle: string, active: boolean) => void
|
|
onSelectionCopy: (handle: string, text: string) => void
|
|
onSelectionEvicted: (handle: string) => void
|
|
onClipboardWrite: (handle: string, text: string) => void
|
|
onModesChanged: (handle: string, modes: TerminalModes) => void
|
|
onKeyboardAvoidanceMetrics: (handle: string, metrics: TerminalKeyboardAvoidanceMetrics) => void
|
|
onHaptic: (kind: 'selection' | 'success' | 'error' | 'edge-bump') => void
|
|
onTerminalInput: (handle: string, bytes: string, input?: TerminalPointerInput) => void
|
|
onTerminalQueryReply: (handle: string, bytes: string) => void
|
|
onTerminalTap: (handle: string) => void
|
|
onFileTap: (handle: string, candidates: readonly ParsedFileLinkLocation[]) => void
|
|
onOpenUrl: (handle: string, url: string) => void
|
|
onTextScaleChange: (scale: number) => void
|
|
onCellBoxChange: (handle: string) => void
|
|
}
|
|
|
|
export function TerminalPaneView({
|
|
handle,
|
|
active,
|
|
keyboardLift,
|
|
terminalTheme,
|
|
textScale,
|
|
onRef,
|
|
onWebReady,
|
|
onSelectionMode,
|
|
onSelectionCopy,
|
|
onSelectionEvicted,
|
|
onClipboardWrite,
|
|
onModesChanged,
|
|
onKeyboardAvoidanceMetrics,
|
|
onHaptic,
|
|
onTerminalInput,
|
|
onTerminalQueryReply,
|
|
onTerminalTap,
|
|
onFileTap,
|
|
onOpenUrl,
|
|
onTextScaleChange,
|
|
onCellBoxChange
|
|
}: TerminalPaneViewProps) {
|
|
const [visited, setVisited] = useState(active)
|
|
if (active && !visited) {
|
|
setVisited(true)
|
|
}
|
|
const [routeFocused, setRouteFocused] = useState(false)
|
|
useFocusEffect(
|
|
useCallback(() => {
|
|
setRouteFocused(true)
|
|
return () => setRouteFocused(false)
|
|
}, [])
|
|
)
|
|
const [appActive, setAppActive] = useState(() => AppState.currentState === 'active')
|
|
useEffect(() => {
|
|
const subscription = AppState.addEventListener('change', (state) =>
|
|
setAppActive(state === 'active')
|
|
)
|
|
return () => subscription.remove()
|
|
}, [])
|
|
const setRef = useCallback(
|
|
(ref: TerminalWebViewHandle | null) => {
|
|
onRef(handle, ref)
|
|
},
|
|
[handle, onRef]
|
|
)
|
|
|
|
if (!active && !visited) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<View
|
|
// Why: inactive terminal WebViews stay mounted to preserve xterm state,
|
|
// while touch and visibility are disabled until the tab is active again.
|
|
pointerEvents={active ? 'auto' : 'none'}
|
|
style={[
|
|
styles.terminalPane,
|
|
keyboardLift > 0 && { transform: [{ translateY: -keyboardLift }] },
|
|
!active && styles.terminalPaneHidden
|
|
]}
|
|
>
|
|
<TerminalWebView
|
|
ref={setRef}
|
|
style={styles.terminalWebView}
|
|
terminalTheme={terminalTheme}
|
|
textScale={textScale}
|
|
shownAtMount={active}
|
|
interactionActive={active && appActive && routeFocused}
|
|
onWebReady={() => onWebReady(handle)}
|
|
onSelectionMode={(a) => onSelectionMode(handle, a)}
|
|
onSelectionCopy={(t) => onSelectionCopy(handle, t)}
|
|
onSelectionEvicted={() => onSelectionEvicted(handle)}
|
|
onClipboardWrite={(t) => onClipboardWrite(handle, t)}
|
|
onModesChanged={(m) => onModesChanged(handle, m)}
|
|
onKeyboardAvoidanceMetrics={(m) => onKeyboardAvoidanceMetrics(handle, m)}
|
|
onHaptic={onHaptic}
|
|
onTerminalInput={(bytes, input) => onTerminalInput(handle, bytes, input)}
|
|
onTerminalQueryReply={(bytes) => onTerminalQueryReply(handle, bytes)}
|
|
onTerminalTap={() => onTerminalTap(handle)}
|
|
onFileTap={(candidates) => onFileTap(handle, candidates)}
|
|
onOpenUrl={(url) => onOpenUrl(handle, url)}
|
|
onTextScaleChange={onTextScaleChange}
|
|
onCellBoxChange={() => onCellBoxChange(handle)}
|
|
/>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
terminalPane: {
|
|
...StyleSheet.absoluteFillObject
|
|
},
|
|
terminalPaneHidden: {
|
|
opacity: 0
|
|
},
|
|
terminalWebView: {
|
|
flex: 1
|
|
}
|
|
})
|