@omercnet/paseo-omp 5.1.1-next.342.1 → 5.1.1-next.344.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import type { PluginTimelineItemProps } from "@getpaseo/plugin/client";
2
- import { useMemo, useState } from "react";
3
- import { Image, Text, View } from "react-native";
2
+ import { Modal, ScrollView } from "@getpaseo/plugin/client/ui";
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { Image, Pressable, Text, View } from "react-native";
4
5
  import { type OmpImageTimelineData, visibleOmpImageText } from "../shared/provider-image";
5
6
 
6
7
  export function OmpImageTimeline({ item, theme }: PluginTimelineItemProps<OmpImageTimelineData>) {
@@ -31,6 +32,8 @@ export function OmpImageTimeline({ item, theme }: PluginTimelineItemProps<OmpIma
31
32
  }),
32
33
  [theme],
33
34
  );
35
+ const [enlargedId, setEnlargedId] = useState<string | null>(null);
36
+ const enlarged = item.data.images.find((image) => image.id === enlargedId);
34
37
  const [failedImageIds, setFailedImageIds] = useState<ReadonlySet<string>>(() => new Set());
35
38
  const visibleText = useMemo(() => visibleOmpImageText(item.data.text), [item.data.text]);
36
39
  return (
@@ -46,21 +49,131 @@ export function OmpImageTimeline({ item, theme }: PluginTimelineItemProps<OmpIma
46
49
  </Text>
47
50
  </View>
48
51
  ) : (
49
- <Image
52
+ <Pressable
50
53
  key={image.id}
51
- source={{ uri: `data:${image.mimeType};base64,${image.data}` }}
52
- resizeMode="contain"
53
- accessibilityLabel={`${item.data.label} image ${index + 1}`}
54
- onError={() =>
55
- setFailedImageIds((current) => {
56
- if (current.has(image.id)) return current;
57
- return new Set([...current, image.id]);
58
- })
59
- }
60
- style={styles.image}
61
- />
54
+ accessibilityRole="button"
55
+ accessibilityLabel={`Enlarge ${item.data.label} image ${index + 1}`}
56
+ onPress={() => setEnlargedId(image.id)}
57
+ >
58
+ <Image
59
+ source={{ uri: `data:${image.mimeType};base64,${image.data}` }}
60
+ resizeMode="contain"
61
+ accessibilityLabel={`${item.data.label} image ${index + 1}`}
62
+ onError={() =>
63
+ setFailedImageIds((current) => {
64
+ if (current.has(image.id)) return current;
65
+ return new Set([...current, image.id]);
66
+ })
67
+ }
68
+ style={styles.image}
69
+ />
70
+ </Pressable>
62
71
  ),
63
72
  )}
73
+ <Modal
74
+ title={item.data.label}
75
+ open={enlarged !== undefined}
76
+ onOpenChange={(open) => {
77
+ if (!open) setEnlargedId(null);
78
+ }}
79
+ >
80
+ <Modal.Content scrollable={false}>
81
+ {enlarged ? (
82
+ <EnlargedImage
83
+ key={enlarged.id}
84
+ failed={failedImageIds.has(enlarged.id)}
85
+ uri={`data:${enlarged.mimeType};base64,${enlarged.data}`}
86
+ label={item.data.label}
87
+ theme={theme}
88
+ />
89
+ ) : null}
90
+ </Modal.Content>
91
+ </Modal>
92
+ </View>
93
+ );
94
+ }
95
+
96
+ /** Fits the image to the dialog; a tap shows it at its real pixel size, panned by scrolling. */
97
+ function EnlargedImage({
98
+ uri,
99
+ label,
100
+ failed,
101
+ theme,
102
+ }: {
103
+ uri: string;
104
+ failed: boolean;
105
+ label: string;
106
+ theme: PluginTimelineItemProps<OmpImageTimelineData>["theme"];
107
+ }) {
108
+ const [natural, setNatural] = useState<{ width: number; height: number } | null>(null);
109
+ const [actualSize, setActualSize] = useState(false);
110
+ const [renderFailed, setRenderFailed] = useState(false);
111
+ const [sizeUnavailable, setSizeUnavailable] = useState(false);
112
+ useEffect(() => {
113
+ let current = true;
114
+ Image.getSize(
115
+ uri,
116
+ (width, height) => current && setNatural({ width, height }),
117
+ () => current && setSizeUnavailable(true),
118
+ );
119
+ return () => {
120
+ current = false;
121
+ };
122
+ }, [uri]);
123
+ const canZoom = natural !== null;
124
+ // The modal body is bounded (`scrollable={false}`), so the image and its scroller share its height
125
+ // with the caption instead of guessing from the window.
126
+ const fill = { flex: 1, minHeight: 0 };
127
+ const mutedText = {
128
+ color: theme.colors.foregroundMuted,
129
+ fontSize: 12,
130
+ textAlign: "center" as const,
131
+ };
132
+ if (failed || renderFailed) {
133
+ return <Text style={mutedText}>This image could not be rendered on this Paseo client.</Text>;
134
+ }
135
+ return (
136
+ <View style={[fill, { gap: 8 }]}>
137
+ <Pressable
138
+ accessibilityRole="button"
139
+ accessibilityLabel={
140
+ actualSize ? `Fit ${label} image to window` : `Show ${label} image at actual size`
141
+ }
142
+ disabled={!canZoom}
143
+ onPress={() => setActualSize((value) => !value)}
144
+ style={fill}
145
+ >
146
+ {actualSize && natural ? (
147
+ <ScrollView style={fill}>
148
+ <ScrollView horizontal>
149
+ <Image
150
+ source={{ uri }}
151
+ accessibilityLabel={`${label} enlarged image`}
152
+ onError={() => setRenderFailed(true)}
153
+ style={{ width: natural.width, height: natural.height }}
154
+ />
155
+ </ScrollView>
156
+ </ScrollView>
157
+ ) : (
158
+ <Image
159
+ source={{ uri }}
160
+ resizeMode="contain"
161
+ accessibilityLabel={`${label} enlarged image`}
162
+ onError={() => setRenderFailed(true)}
163
+ style={[fill, { width: "100%" }]}
164
+ />
165
+ )}
166
+ </Pressable>
167
+ {canZoom ? (
168
+ <Text style={mutedText}>
169
+ {actualSize
170
+ ? `${natural.width} × ${natural.height}px · tap to fit`
171
+ : `Tap the image to see it at ${natural.width} × ${natural.height}px`}
172
+ </Text>
173
+ ) : null}
174
+ {sizeUnavailable ? (
175
+ <Text style={mutedText}>The actual size of this image is not available.</Text>
176
+ ) : null}
64
177
  </View>
65
178
  );
66
179
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omercnet/paseo-omp",
3
- "version": "5.1.1-next.342.1",
3
+ "version": "5.1.1-next.344.1",
4
4
  "type": "module",
5
5
  "description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
6
6
  "license": "MIT",
@@ -1,2 +1,2 @@
1
1
  /** Release Please keeps this bundle-safe constant aligned with package.json. */
2
- export const PASEO_OMP_PACKAGE_VERSION = "5.1.1-next.342.1"; // x-release-please-version
2
+ export const PASEO_OMP_PACKAGE_VERSION = "5.1.1-next.344.1"; // x-release-please-version