mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
* feat(mobile): light and dark themes with desktop's palettes Settings → Appearance offers System, Light and Dark. The shell passes the resolved scheme to over-the-air pages in its init frame. Fixes #22184 * fix(mobile): draw your chat messages in desktop's user bubble Desktop uses a faint surface with a hairline border in both themes; the inverted fill went black in light. * test(mobile): re-measure the page script sweep after the shared theme hooks Every route now imports the theme hooks while the entry does not, so they emit one shared script (96 for the tree). Re-swept every route prefix; the worst route step is now 15. * test(mobile): read themed stylesheets in the text-input font-size census The census only unwrapped StyleSheet.create({...}); inputs styled through themedStyleSheet(() => ({...})) came back unread.
222 lines
7.2 KiB
TypeScript
222 lines
7.2 KiB
TypeScript
import { useMemo } from 'react'
|
|
import { Pressable, Text, View } from 'react-native'
|
|
import { MobileNativeChatOwnedScrollView } from './MobileNativeChatOwnedScrollView'
|
|
import { ChevronRight, FileMinus2, FilePen, FilePlus2 } from 'lucide-react-native'
|
|
import {
|
|
NATIVE_CHAT_DIFF_CARD_COPY,
|
|
nativeChatEditFilePatchText
|
|
} from '../../../src/shared/native-chat/tools/native-chat-edit-cards'
|
|
import {
|
|
nativeChatEditFileBaseName,
|
|
nativeChatEditGutterChars,
|
|
unifiedLineNumber,
|
|
type NativeChatEditFile,
|
|
type NativeChatEditLine
|
|
} from '../../../src/shared/native-chat/tools/native-chat-edit-model'
|
|
import { NATIVE_CHAT_TOOL_ROW_VERB_COPY } from '../../../src/shared/native-chat/tools/native-chat-tool-line-label'
|
|
import { MobileSourceCopyButton } from '../components/MobileSourceCopyButton'
|
|
import { colors } from '../theme/mobile-theme'
|
|
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
|
import { useMobileNativeChatOpenedDetail } from './native-chat/visual/mobile-native-chat-opened-detail'
|
|
import { useMobileNativeChatDisclosure } from './native-chat/transcript/mobile-native-chat-disclosure-store'
|
|
import {
|
|
toolStyles as styles,
|
|
toolType
|
|
} from './native-chat/transcript/mobile-native-chat-tool-styles'
|
|
|
|
function VerbIcon({ kind, size }: { kind: NativeChatEditFile['changeKind']; size: number }) {
|
|
useMobileColorScheme()
|
|
const Icon = kind === 'added' ? FilePlus2 : kind === 'deleted' ? FileMinus2 : FilePen
|
|
return <Icon size={size} color={colors.textMuted} strokeWidth={2} />
|
|
}
|
|
|
|
function DiffRow({
|
|
line,
|
|
gutterChars,
|
|
type
|
|
}: {
|
|
line: NativeChatEditLine
|
|
gutterChars: number
|
|
type: ReturnType<typeof toolType>['code']
|
|
}): React.JSX.Element {
|
|
useMobileColorScheme()
|
|
if (line.kind === 'gap') {
|
|
return (
|
|
<Text style={[styles.diffGap, type]} accessibilityLabel={NATIVE_CHAT_DIFF_CARD_COPY.diffGap}>
|
|
⋯
|
|
</Text>
|
|
)
|
|
}
|
|
return (
|
|
<View
|
|
style={[
|
|
styles.diffRow,
|
|
line.kind === 'add' && styles.diffAddGround,
|
|
line.kind === 'del' && styles.diffDelGround
|
|
]}
|
|
>
|
|
{gutterChars > 0 ? (
|
|
<Text
|
|
style={[
|
|
styles.codeText,
|
|
styles.diffGutter,
|
|
type,
|
|
{ width: gutterChars * type.fontSize * 0.62 },
|
|
line.kind === 'add' && styles.diffAddGutter,
|
|
line.kind === 'del' && styles.diffDelGutter
|
|
]}
|
|
>
|
|
{unifiedLineNumber(line) ?? ''}
|
|
</Text>
|
|
) : null}
|
|
<Text
|
|
style={[
|
|
styles.codeText,
|
|
styles.diffSign,
|
|
type,
|
|
line.kind === 'add' && styles.diffAddText,
|
|
line.kind === 'del' && styles.diffDelText
|
|
]}
|
|
>
|
|
{line.kind === 'add' ? '+' : line.kind === 'del' ? '-' : ' '}
|
|
</Text>
|
|
<Text style={[styles.codeText, styles.diffText, type]}>{line.text}</Text>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
// Mount rows only when expanded so collapsed cards avoid diff work.
|
|
function DiffCardRows({
|
|
file,
|
|
fontScale,
|
|
disclosureKey,
|
|
rootIdentity
|
|
}: {
|
|
file: NativeChatEditFile
|
|
fontScale: number
|
|
disclosureKey?: string
|
|
rootIdentity?: string
|
|
}) {
|
|
useMobileColorScheme()
|
|
const onLayout = useMobileNativeChatOpenedDetail()
|
|
const type = toolType(fontScale).code
|
|
const gutterChars = nativeChatEditGutterChars(file)
|
|
const seen = new Map<string, number>()
|
|
return (
|
|
<MobileNativeChatOwnedScrollView
|
|
rootIdentity={rootIdentity ?? `diff:${file.path}`}
|
|
positionKey={disclosureKey ?? `diff:${file.path}`}
|
|
style={[styles.codeBox, styles.diffBody]}
|
|
nestedScrollEnabled
|
|
keyboardShouldPersistTaps="handled"
|
|
onLayout={onLayout}
|
|
>
|
|
{file.lines.map((line) => {
|
|
const signature = `${line.kind}:${line.oldLineNumber}:${line.newLineNumber}:${line.text}`
|
|
const occurrence = seen.get(signature) ?? 0
|
|
seen.set(signature, occurrence + 1)
|
|
return (
|
|
<DiffRow
|
|
key={`${signature}:${occurrence}`}
|
|
line={line}
|
|
gutterChars={gutterChars}
|
|
type={type}
|
|
/>
|
|
)
|
|
})}
|
|
</MobileNativeChatOwnedScrollView>
|
|
)
|
|
}
|
|
|
|
export function MobileNativeChatDiffCard({
|
|
file,
|
|
fontScale,
|
|
disclosureKey,
|
|
rootIdentity,
|
|
onOpenFile
|
|
}: {
|
|
file: NativeChatEditFile
|
|
fontScale: number
|
|
disclosureKey?: string
|
|
rootIdentity?: string
|
|
onOpenFile?: (path: string) => void
|
|
}): React.JSX.Element {
|
|
useMobileColorScheme()
|
|
const [expanded, setExpanded] = useMobileNativeChatDisclosure(disclosureKey, false)
|
|
const hasBody = file.lines.length > 0
|
|
const copyText = useMemo(() => nativeChatEditFilePatchText(file.lines), [file.lines])
|
|
const type = toolType(fontScale)
|
|
const iconSize = 15 * fontScale
|
|
const openFile = file.changeKind === 'deleted' ? undefined : onOpenFile
|
|
return (
|
|
<View>
|
|
<View style={styles.cardHeader}>
|
|
<Pressable
|
|
style={[styles.line, styles.cardToggle]}
|
|
onPress={() => hasBody && setExpanded(!expanded)}
|
|
disabled={!hasBody}
|
|
accessibilityRole="button"
|
|
accessibilityState={hasBody ? { expanded } : undefined}
|
|
>
|
|
<View style={[styles.iconSlot, { width: iconSize }]}>
|
|
<VerbIcon kind={file.changeKind} size={iconSize} />
|
|
</View>
|
|
<Text style={[styles.lineWord, type.row]}>
|
|
{NATIVE_CHAT_TOOL_ROW_VERB_COPY[file.changeKind]}
|
|
</Text>
|
|
{file.oldPath ? (
|
|
<>
|
|
<Text
|
|
style={[styles.lineWord, styles.shrinkable, styles.struck, type.row]}
|
|
numberOfLines={1}
|
|
accessibilityLabel={file.oldPath}
|
|
>
|
|
{nativeChatEditFileBaseName(file.oldPath)}
|
|
</Text>
|
|
<Text style={[styles.lineWord, type.row]}>→</Text>
|
|
</>
|
|
) : null}
|
|
<Text
|
|
style={[styles.lineTarget, type.row, openFile && styles.lineLink]}
|
|
numberOfLines={1}
|
|
onPress={openFile ? () => openFile(file.path) : undefined}
|
|
suppressHighlighting={!openFile}
|
|
accessibilityRole={openFile ? 'link' : undefined}
|
|
>
|
|
{nativeChatEditFileBaseName(file.path)}
|
|
</Text>
|
|
<Text style={[styles.lineMeta, type.meta]}>
|
|
{file.added > 0 ? <Text style={styles.countAdded}>{`+${file.added}`}</Text> : null}
|
|
{file.added > 0 && file.removed > 0 ? ' ' : null}
|
|
{file.removed > 0 ? (
|
|
<Text style={styles.countRemoved}>{`-${file.removed}`}</Text>
|
|
) : null}
|
|
</Text>
|
|
{file.truncated ? (
|
|
<Text style={[styles.lineMeta, type.meta]}>
|
|
{NATIVE_CHAT_DIFF_CARD_COPY.diffTruncated}
|
|
</Text>
|
|
) : null}
|
|
{hasBody ? (
|
|
<View style={expanded ? styles.caretOpen : undefined}>
|
|
<ChevronRight size={14 * fontScale} color={colors.textMuted} strokeWidth={2} />
|
|
</View>
|
|
) : null}
|
|
</Pressable>
|
|
<MobileSourceCopyButton
|
|
text={copyText}
|
|
accessibilityLabel={NATIVE_CHAT_DIFF_CARD_COPY.copyDiff}
|
|
/>
|
|
</View>
|
|
{hasBody && expanded ? (
|
|
<DiffCardRows
|
|
file={file}
|
|
fontScale={fontScale}
|
|
disclosureKey={disclosureKey}
|
|
rootIdentity={rootIdentity}
|
|
/>
|
|
) : null}
|
|
</View>
|
|
)
|
|
}
|