Skip to content
Source
Sign inSign up

WorkspaceFileWebPreview.tsx

108 lines4.2 KB

1import { useState } from "react";
2import { View } from "react-native";
3import { WebView } from "react-native-webview";
4import { SafeAreaView } from "react-native-safe-area-context";
5​
6import { AppActivityIndicator } from "../../components/ui/AppActivityIndicator";
7import { AppText as Text } from "../../components/ui/AppText";
8import { FilePreviewLoading } from "./FilePreviewFeedback";
9import { LoadingStrip } from "../../components/ui/LoadingStrip";
10import { PdfPreview } from "../../components/PdfPreview";
11import { ControlPill } from "../../components/ui/ControlPill";
12import { useHardwareBack } from "../../lib/backHandler";
13import { isPdfPreviewUri } from "../../lib/pdfPreview";
14import { webViewDecelerationRate } from "../../lib/webViewDecelerationRate";
15import { useContentTopInset } from "../../components/ui/useBarInset";
16​
17export function WorkspaceFileWebPreview(props: { readonly uri: string | null }) {
18 if (props.uri === null) {
19 return <FilePreviewLoading message="Preparing preview..." background="card" />;
20 }
21 return <DocumentWebPreview key={props.uri} uri={props.uri} />;
23​
24function DocumentWebPreview(props: { readonly uri: string }) {
25 const headerInset = useContentTopInset();
26 const [loadProgress, setLoadProgress] = useState(0);
27 const [loadError, setLoadError] = useState<string | null>(null);
28 const [linkedPdf, setLinkedPdf] = useState<string | null>(null);
29​
30 useHardwareBack(linkedPdf !== null, () => {
31 setLinkedPdf(null);
32 return true;
33 });
34​
35 if (isPdfPreviewUri(props.uri)) {
36 return (
37 <View className="flex-1" style={{ paddingTop: headerInset }}>
38 <PdfPreview uri={props.uri} />
39 </View>
40 );
41 }
42​
43 return (
44 <View className="relative flex-1 bg-card">
45 {linkedPdf ? (
46 <View className="flex-1" style={{ paddingTop: headerInset }}>
47 <PdfPreview key={linkedPdf} uri={linkedPdf} />
48 <SafeAreaView edges={["bottom"]} className="border-t border-border px-4 py-2">
49 <ControlPill
50 label="Back to document"
51 variant="pill"
52 onPress={() => setLinkedPdf(null)}
53 />
54 </SafeAreaView>
55 </View>
56 ) : null}
57 <View className="flex-1" style={linkedPdf ? { display: "none" } : undefined}>
58 <WebView
59 source={{ uri: props.uri }}
60 // react-native-webview defaults to "never", which starts the page under the header.
61 contentInsetAdjustmentBehavior="automatic"
62 decelerationRate={webViewDecelerationRate}
63 originWhitelist={["*"]}
64 allowsBackForwardNavigationGestures
65 allowsFullscreenVideo
66 setSupportMultipleWindows={false}
67 onShouldStartLoadWithRequest={(request) => {
68 if (request.isTopFrame !== false && isPdfPreviewUri(request.url)) {
69 setLinkedPdf(request.url);
70 return false;
71 }
72 return true;
73 }}
74 startInLoadingState
75 onLoadProgress={(event) => {
76 setLoadProgress(event.nativeEvent.progress);
77 }}
78 onLoadStart={() => {
79 setLoadProgress(0.05);
80 setLoadError(null);
81 }}
82 onLoadEnd={() => {
83 setLoadProgress(0);
84 }}
85 onError={(event) => {
86 setLoadProgress(0);
87 setLoadError(event.nativeEvent.description || "The file could not be rendered.");
88 }}
89 renderLoading={() => (
90 <View className="absolute inset-0 items-center justify-center bg-card">
91 <AppActivityIndicator />
92 </View>
93 )}
94 style={{ flex: 1, backgroundColor: "transparent" }}
95 />
96 <View pointerEvents="box-none" className="absolute inset-x-0" style={{ top: headerInset }}>
97 {loadProgress > 0 && loadProgress < 1 ? <LoadingStrip progress={loadProgress} /> : null}
98 {loadError ? (
99 <View className="border-b border-border bg-card px-4 py-2">
100 <Text className="text-xs font-t3-bold text-foreground">Preview failed</Text>
101 <Text className="mt-0.5 text-xs leading-snug text-foreground-muted">{loadError}</Text>
102 </View>
103 ) : null}
104 </View>
105 </View>
106 </View>
107 );