@ethora/chat-component-rn 26.10.9 → 26.10.10

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.
Files changed (154) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/README.md +1 -1
  3. package/lib/commonjs/components/MainComponents/AlbumGrid.js +6 -2
  4. package/lib/commonjs/components/MainComponents/AlbumGrid.js.map +1 -1
  5. package/lib/commonjs/components/MainComponents/ChatHeader.js +16 -11
  6. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  7. package/lib/commonjs/components/MainComponents/MediaMessage.js +13 -0
  8. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  9. package/lib/commonjs/components/MainComponents/RoomList.js +7 -16
  10. package/lib/commonjs/components/MainComponents/RoomList.js.map +1 -1
  11. package/lib/commonjs/components/MessageBubble/Message.js +27 -1
  12. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  13. package/lib/commonjs/components/Modals/ChatProfileModal/ChatMediaGrid.js +159 -0
  14. package/lib/commonjs/components/Modals/ChatProfileModal/ChatMediaGrid.js.map +1 -0
  15. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +203 -134
  16. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  17. package/lib/commonjs/components/Modals/ChatProfileModal/LeaveChatModal.js +180 -0
  18. package/lib/commonjs/components/Modals/ChatProfileModal/LeaveChatModal.js.map +1 -0
  19. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +100 -162
  20. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  21. package/lib/commonjs/components/Modals/FilePreviewModal/MediaViewer.js +824 -0
  22. package/lib/commonjs/components/Modals/FilePreviewModal/MediaViewer.js.map +1 -0
  23. package/lib/commonjs/components/Modals/FilePreviewModal/useSaveFile.js +179 -0
  24. package/lib/commonjs/components/Modals/FilePreviewModal/useSaveFile.js.map +1 -0
  25. package/lib/commonjs/components/Modals/MessageSearchModal/MessageSearchModal.js +22 -5
  26. package/lib/commonjs/components/Modals/MessageSearchModal/MessageSearchModal.js.map +1 -1
  27. package/lib/commonjs/components/styled/MessageImage.js +4 -1
  28. package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
  29. package/lib/commonjs/components/styled/VideoMessage.js +5 -1
  30. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  31. package/lib/commonjs/context/MediaSourceContext.js +18 -0
  32. package/lib/commonjs/context/MediaSourceContext.js.map +1 -0
  33. package/lib/commonjs/helpers/constants/MODAL_COMPONENTS.js +3 -1
  34. package/lib/commonjs/helpers/constants/MODAL_COMPONENTS.js.map +1 -1
  35. package/lib/commonjs/helpers/constants/MODAL_TYPES.js +2 -0
  36. package/lib/commonjs/helpers/constants/MODAL_TYPES.js.map +1 -1
  37. package/lib/commonjs/helpers/publicChatsEntry.js +8 -6
  38. package/lib/commonjs/helpers/publicChatsEntry.js.map +1 -1
  39. package/lib/commonjs/helpers/roomMedia.js +118 -0
  40. package/lib/commonjs/helpers/roomMedia.js.map +1 -0
  41. package/lib/commonjs/i18n/strings.js +36 -0
  42. package/lib/commonjs/i18n/strings.js.map +1 -1
  43. package/lib/commonjs/networking/api-requests/rooms.api.js +9 -13
  44. package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
  45. package/lib/commonjs/version.js +1 -1
  46. package/lib/commonjs/version.js.map +1 -1
  47. package/lib/module/components/MainComponents/AlbumGrid.js +6 -2
  48. package/lib/module/components/MainComponents/AlbumGrid.js.map +1 -1
  49. package/lib/module/components/MainComponents/ChatHeader.js +18 -13
  50. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  51. package/lib/module/components/MainComponents/MediaMessage.js +13 -0
  52. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  53. package/lib/module/components/MainComponents/RoomList.js +7 -16
  54. package/lib/module/components/MainComponents/RoomList.js.map +1 -1
  55. package/lib/module/components/MessageBubble/Message.js +27 -1
  56. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  57. package/lib/module/components/Modals/ChatProfileModal/ChatMediaGrid.js +154 -0
  58. package/lib/module/components/Modals/ChatProfileModal/ChatMediaGrid.js.map +1 -0
  59. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +204 -136
  60. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  61. package/lib/module/components/Modals/ChatProfileModal/LeaveChatModal.js +175 -0
  62. package/lib/module/components/Modals/ChatProfileModal/LeaveChatModal.js.map +1 -0
  63. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +100 -163
  64. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  65. package/lib/module/components/Modals/FilePreviewModal/MediaViewer.js +819 -0
  66. package/lib/module/components/Modals/FilePreviewModal/MediaViewer.js.map +1 -0
  67. package/lib/module/components/Modals/FilePreviewModal/useSaveFile.js +172 -0
  68. package/lib/module/components/Modals/FilePreviewModal/useSaveFile.js.map +1 -0
  69. package/lib/module/components/Modals/MessageSearchModal/MessageSearchModal.js +20 -4
  70. package/lib/module/components/Modals/MessageSearchModal/MessageSearchModal.js.map +1 -1
  71. package/lib/module/components/styled/MessageImage.js +4 -1
  72. package/lib/module/components/styled/MessageImage.js.map +1 -1
  73. package/lib/module/components/styled/VideoMessage.js +5 -1
  74. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  75. package/lib/module/context/MediaSourceContext.js +13 -0
  76. package/lib/module/context/MediaSourceContext.js.map +1 -0
  77. package/lib/module/helpers/constants/MODAL_COMPONENTS.js +2 -1
  78. package/lib/module/helpers/constants/MODAL_COMPONENTS.js.map +1 -1
  79. package/lib/module/helpers/constants/MODAL_TYPES.js +2 -0
  80. package/lib/module/helpers/constants/MODAL_TYPES.js.map +1 -1
  81. package/lib/module/helpers/publicChatsEntry.js +8 -6
  82. package/lib/module/helpers/publicChatsEntry.js.map +1 -1
  83. package/lib/module/helpers/roomMedia.js +112 -0
  84. package/lib/module/helpers/roomMedia.js.map +1 -0
  85. package/lib/module/i18n/strings.js +36 -0
  86. package/lib/module/i18n/strings.js.map +1 -1
  87. package/lib/module/networking/api-requests/rooms.api.js +9 -13
  88. package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
  89. package/lib/module/version.js +1 -1
  90. package/lib/module/version.js.map +1 -1
  91. package/lib/typescript/components/MainComponents/AlbumGrid.d.ts.map +1 -1
  92. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  93. package/lib/typescript/components/MainComponents/MediaMessage.d.ts +3 -1
  94. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  95. package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
  96. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  97. package/lib/typescript/components/Modals/ChatProfileModal/ChatMediaGrid.d.ts +16 -0
  98. package/lib/typescript/components/Modals/ChatProfileModal/ChatMediaGrid.d.ts.map +1 -0
  99. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
  100. package/lib/typescript/components/Modals/ChatProfileModal/LeaveChatModal.d.ts +18 -0
  101. package/lib/typescript/components/Modals/ChatProfileModal/LeaveChatModal.d.ts.map +1 -0
  102. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts +16 -0
  103. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  104. package/lib/typescript/components/Modals/FilePreviewModal/MediaViewer.d.ts +27 -0
  105. package/lib/typescript/components/Modals/FilePreviewModal/MediaViewer.d.ts.map +1 -0
  106. package/lib/typescript/components/Modals/FilePreviewModal/useSaveFile.d.ts +9 -0
  107. package/lib/typescript/components/Modals/FilePreviewModal/useSaveFile.d.ts.map +1 -0
  108. package/lib/typescript/components/Modals/MessageSearchModal/MessageSearchModal.d.ts +8 -0
  109. package/lib/typescript/components/Modals/MessageSearchModal/MessageSearchModal.d.ts.map +1 -1
  110. package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
  111. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  112. package/lib/typescript/context/MediaSourceContext.d.ts +4 -0
  113. package/lib/typescript/context/MediaSourceContext.d.ts.map +1 -0
  114. package/lib/typescript/helpers/constants/MODAL_COMPONENTS.d.ts.map +1 -1
  115. package/lib/typescript/helpers/constants/MODAL_TYPES.d.ts +2 -0
  116. package/lib/typescript/helpers/constants/MODAL_TYPES.d.ts.map +1 -1
  117. package/lib/typescript/helpers/publicChatsEntry.d.ts +7 -5
  118. package/lib/typescript/helpers/publicChatsEntry.d.ts.map +1 -1
  119. package/lib/typescript/helpers/roomMedia.d.ts +26 -0
  120. package/lib/typescript/helpers/roomMedia.d.ts.map +1 -0
  121. package/lib/typescript/i18n/strings.d.ts.map +1 -1
  122. package/lib/typescript/networking/api-requests/rooms.api.d.ts +6 -5
  123. package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
  124. package/lib/typescript/types/models/modal.model.d.ts +18 -0
  125. package/lib/typescript/types/models/modal.model.d.ts.map +1 -1
  126. package/lib/typescript/types/types.d.ts +18 -0
  127. package/lib/typescript/types/types.d.ts.map +1 -1
  128. package/lib/typescript/version.d.ts +1 -1
  129. package/lib/typescript/version.d.ts.map +1 -1
  130. package/package.json +9 -2
  131. package/src/components/MainComponents/AlbumGrid.tsx +5 -1
  132. package/src/components/MainComponents/ChatHeader.tsx +16 -11
  133. package/src/components/MainComponents/MediaMessage.tsx +13 -2
  134. package/src/components/MainComponents/RoomList.tsx +8 -19
  135. package/src/components/MessageBubble/Message.tsx +24 -1
  136. package/src/components/Modals/ChatProfileModal/ChatMediaGrid.tsx +174 -0
  137. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +119 -48
  138. package/src/components/Modals/ChatProfileModal/LeaveChatModal.tsx +191 -0
  139. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +115 -185
  140. package/src/components/Modals/FilePreviewModal/MediaViewer.tsx +880 -0
  141. package/src/components/Modals/FilePreviewModal/useSaveFile.ts +207 -0
  142. package/src/components/Modals/MessageSearchModal/MessageSearchModal.tsx +56 -30
  143. package/src/components/styled/MessageImage.tsx +3 -1
  144. package/src/components/styled/VideoMessage.tsx +5 -1
  145. package/src/context/MediaSourceContext.tsx +14 -0
  146. package/src/helpers/constants/MODAL_COMPONENTS.tsx +4 -1
  147. package/src/helpers/constants/MODAL_TYPES.ts +2 -0
  148. package/src/helpers/publicChatsEntry.ts +8 -7
  149. package/src/helpers/roomMedia.ts +126 -0
  150. package/src/i18n/strings.ts +36 -0
  151. package/src/networking/api-requests/rooms.api.ts +13 -12
  152. package/src/types/models/modal.model.ts +16 -0
  153. package/src/types/types.ts +16 -0
  154. package/src/version.ts +1 -1
@@ -0,0 +1,824 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
10
+ var _reactNativeGestureHandler = require("react-native-gesture-handler");
11
+ var _expoVideo = require("expo-video");
12
+ var _reactRedux = require("react-redux");
13
+ var _icons = require("../../../assets/icons");
14
+ var _secureFileUrl = require("../../../helpers/secureFileUrl");
15
+ var _videoCache = require("../../../helpers/videoCache");
16
+ var _chatSettingsSlice = require("../../../roomStore/chatSettingsSlice");
17
+ var _roomsSlice = require("../../../roomStore/roomsSlice");
18
+ var _useT = require("../../../i18n/useT");
19
+ var _jsxRuntime = require("react/jsx-runtime");
20
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
21
+ /** @format */
22
+
23
+ const WHITE = '#FFFFFF';
24
+ const BAR_BACKGROUND = 'rgba(0, 0, 0, 0.45)';
25
+ /** Drag (fraction of the height) or fling speed that closes the viewer. */
26
+ const CLOSE_DISTANCE = 0.15;
27
+ const CLOSE_VELOCITY = 900;
28
+ /** Swipe (fraction of the width) or fling speed that turns the page. */
29
+ const PAGE_DISTANCE = 0.25;
30
+ const PAGE_VELOCITY = 700;
31
+ /** A playing video takes the chrome away after this long without a touch. */
32
+ const AUTO_HIDE_MS = 3000;
33
+ const formatTime = seconds => {
34
+ const total = Math.max(0, Math.floor(seconds || 0));
35
+ const m = Math.floor(total / 60);
36
+ const s = total % 60;
37
+ return `${m}:${s < 10 ? '0' : ''}${s}`;
38
+ };
39
+ /**
40
+ * Full-screen photo / video viewer that behaves like Telegram's. A tap shows
41
+ * or hides everything but the media (a fade both ways); a drag up or down
42
+ * pulls the media off with the backdrop fading behind it, and a long enough
43
+ * drag or a fling closes the viewer; a sideways swipe turns the page in an
44
+ * album. The bottom bar carries Reply (where replies are on) and Delete (on
45
+ * one's own message). A video has its own controls in the chrome: tapping
46
+ * them works the player instead of hiding the chrome, and while it plays the
47
+ * chrome goes away by itself after a few seconds.
48
+ */
49
+ const MediaViewer = ({
50
+ file,
51
+ onClose,
52
+ onPage,
53
+ onSave,
54
+ onReplyOpened
55
+ }) => {
56
+ const t = (0, _useT.useT)();
57
+ const locale = (0, _useT.useUiLocale)();
58
+ const dispatch = (0, _reactRedux.useDispatch)();
59
+ const insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
60
+ const {
61
+ width,
62
+ height
63
+ } = (0, _reactNative.useWindowDimensions)();
64
+ const appear = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
65
+ const dragX = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
66
+ const dragY = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
67
+ const chrome = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current;
68
+ const [chromeVisible, setChromeVisible] = (0, _react.useState)(true);
69
+ const [bottomBarHeight, setBottomBarHeight] = (0, _react.useState)(0);
70
+ const closing = (0, _react.useRef)(false);
71
+ const axis = (0, _react.useRef)(null);
72
+ const pendingSlide = (0, _react.useRef)(0);
73
+ const playing = (0, _react.useRef)(false);
74
+ const hideTimer = (0, _react.useRef)(null);
75
+ const siblings = file.siblings ?? [];
76
+ const index = file.index ?? 0;
77
+ const hasPrev = siblings.length > 1 && index > 0;
78
+ const hasNext = siblings.length > 1 && index < siblings.length - 1;
79
+ const isVideo = (file.mimetype || '').startsWith('video/');
80
+ const source = file.source;
81
+ const canReply = !!source?.replyTo;
82
+ const canDelete = !!source?.own;
83
+ (0, _react.useEffect)(() => {
84
+ _reactNative.Animated.timing(appear, {
85
+ toValue: 1,
86
+ duration: 220,
87
+ useNativeDriver: true
88
+ }).start();
89
+ }, [appear]);
90
+
91
+ // ---- chrome ------------------------------------------------------------
92
+
93
+ const cancelAutoHide = (0, _react.useCallback)(() => {
94
+ if (hideTimer.current) {
95
+ clearTimeout(hideTimer.current);
96
+ }
97
+ hideTimer.current = null;
98
+ }, []);
99
+ const showChrome = (0, _react.useCallback)(visible => {
100
+ setChromeVisible(visible);
101
+ _reactNative.Animated.timing(chrome, {
102
+ toValue: visible ? 1 : 0,
103
+ duration: 180,
104
+ useNativeDriver: true
105
+ }).start();
106
+ }, [chrome]);
107
+ const scheduleAutoHide = (0, _react.useCallback)(() => {
108
+ cancelAutoHide();
109
+ if (!playing.current) {
110
+ return;
111
+ }
112
+ hideTimer.current = setTimeout(() => showChrome(false), AUTO_HIDE_MS);
113
+ }, [cancelAutoHide, showChrome]);
114
+ (0, _react.useEffect)(() => cancelAutoHide, [cancelAutoHide]);
115
+ const toggleChrome = () => {
116
+ const next = !chromeVisible;
117
+ showChrome(next);
118
+ if (next) {
119
+ scheduleAutoHide();
120
+ } else {
121
+ cancelAutoHide();
122
+ }
123
+ };
124
+ const handlePlayback = (0, _react.useCallback)(isPlaying => {
125
+ playing.current = isPlaying;
126
+ if (isPlaying) {
127
+ scheduleAutoHide();
128
+ } else {
129
+ cancelAutoHide();
130
+ }
131
+ }, [cancelAutoHide, scheduleAutoHide]);
132
+
133
+ // ---- close / page --------------------------------------------------------
134
+
135
+ const close = (0, _react.useCallback)((direction = 0) => {
136
+ if (closing.current) {
137
+ return;
138
+ }
139
+ closing.current = true;
140
+ cancelAutoHide();
141
+ const animations = [_reactNative.Animated.timing(appear, {
142
+ toValue: 0,
143
+ duration: 200,
144
+ useNativeDriver: true
145
+ })];
146
+ if (direction !== 0) {
147
+ animations.push(_reactNative.Animated.timing(dragY, {
148
+ toValue: direction * height,
149
+ duration: 200,
150
+ useNativeDriver: true
151
+ }));
152
+ }
153
+ _reactNative.Animated.parallel(animations).start(() => onClose());
154
+ }, [appear, cancelAutoHide, dragY, height, onClose]);
155
+ (0, _react.useEffect)(() => {
156
+ const sub = _reactNative.BackHandler.addEventListener('hardwareBackPress', () => {
157
+ close();
158
+ return true;
159
+ });
160
+ return () => sub.remove();
161
+ }, [close]);
162
+ const springBack = value => _reactNative.Animated.spring(value, {
163
+ toValue: 0,
164
+ useNativeDriver: true,
165
+ bounciness: 4
166
+ }).start();
167
+ const turnPage = (0, _react.useCallback)(direction => {
168
+ _reactNative.Animated.timing(dragX, {
169
+ toValue: -direction * width,
170
+ duration: 160,
171
+ useNativeDriver: true
172
+ }).start(() => {
173
+ pendingSlide.current = direction;
174
+ onPage?.(index + direction);
175
+ });
176
+ }, [dragX, index, onPage, width]);
177
+
178
+ // The new file mounts off-screen on the side it comes from, then slides in.
179
+ (0, _react.useEffect)(() => {
180
+ const direction = pendingSlide.current;
181
+ if (!direction) {
182
+ return;
183
+ }
184
+ pendingSlide.current = 0;
185
+ playing.current = false;
186
+ dragX.setValue(direction * width);
187
+ _reactNative.Animated.timing(dragX, {
188
+ toValue: 0,
189
+ duration: 180,
190
+ useNativeDriver: true
191
+ }).start();
192
+ }, [dragX, file.fileURL, width]);
193
+
194
+ // ---- gestures ------------------------------------------------------------
195
+
196
+ const latest = (0, _react.useRef)({
197
+ hasPrev,
198
+ hasNext,
199
+ width,
200
+ height,
201
+ close,
202
+ turnPage
203
+ });
204
+ latest.current = {
205
+ hasPrev,
206
+ hasNext,
207
+ width,
208
+ height,
209
+ close,
210
+ turnPage
211
+ };
212
+ const pageable = siblings.length > 1;
213
+ const pan = (0, _react.useMemo)(() => _reactNativeGestureHandler.Gesture.Pan().runOnJS(true).activeOffsetY([-12, 12]).activeOffsetX(pageable ? [-12, 12] : [-10000, 10000]).onStart(e => {
214
+ axis.current = pageable && Math.abs(e.translationX) > Math.abs(e.translationY) ? 'x' : 'y';
215
+ }).onUpdate(e => {
216
+ const l = latest.current;
217
+ if (axis.current === 'x') {
218
+ // Past the first or last file the page only gives a little.
219
+ const edge = e.translationX > 0 && !l.hasPrev || e.translationX < 0 && !l.hasNext;
220
+ dragX.setValue(edge ? e.translationX * 0.3 : e.translationX);
221
+ } else {
222
+ dragY.setValue(e.translationY);
223
+ }
224
+ }).onEnd(e => {
225
+ const l = latest.current;
226
+ if (axis.current === 'x') {
227
+ if ((e.translationX < -l.width * PAGE_DISTANCE || e.velocityX < -PAGE_VELOCITY) && l.hasNext) {
228
+ l.turnPage(1);
229
+ } else if ((e.translationX > l.width * PAGE_DISTANCE || e.velocityX > PAGE_VELOCITY) && l.hasPrev) {
230
+ l.turnPage(-1);
231
+ } else {
232
+ springBack(dragX);
233
+ }
234
+ } else if (Math.abs(e.translationY) > l.height * CLOSE_DISTANCE || Math.abs(e.velocityY) > CLOSE_VELOCITY) {
235
+ l.close(Math.sign(e.translationY || e.velocityY) || 1);
236
+ } else {
237
+ springBack(dragY);
238
+ }
239
+ axis.current = null;
240
+ }),
241
+ // eslint-disable-next-line react-hooks/exhaustive-deps
242
+ [pageable]);
243
+
244
+ // ---- actions -------------------------------------------------------------
245
+
246
+ const handleReply = () => {
247
+ if (!source?.replyTo) {
248
+ return;
249
+ }
250
+ const target = source.replyTo;
251
+ close();
252
+ dispatch((0, _roomsSlice.setEditAction)({
253
+ isEdit: false
254
+ }));
255
+ dispatch((0, _roomsSlice.setActiveMessage)({
256
+ id: target.id,
257
+ chatJID: target.chatJID
258
+ }));
259
+ onReplyOpened?.();
260
+ };
261
+ const handleDelete = () => {
262
+ if (!source?.own) {
263
+ return;
264
+ }
265
+ const {
266
+ roomJid,
267
+ messageId
268
+ } = source;
269
+ close();
270
+ dispatch((0, _chatSettingsSlice.setDeleteModal)({
271
+ isDeleteModal: true,
272
+ roomJid,
273
+ messageId
274
+ }));
275
+ };
276
+
277
+ // ---- styles from the animated values -------------------------------------
278
+
279
+ const dragFade = dragY.interpolate({
280
+ inputRange: [-height / 2, 0, height / 2],
281
+ outputRange: [0.15, 1, 0.15],
282
+ extrapolate: 'clamp'
283
+ });
284
+ const dragShrink = dragY.interpolate({
285
+ inputRange: [-height, 0, height],
286
+ outputRange: [0.85, 1, 0.85],
287
+ extrapolate: 'clamp'
288
+ });
289
+ const backdropOpacity = _reactNative.Animated.multiply(appear, dragFade);
290
+ const mediaStyle = {
291
+ transform: [{
292
+ translateX: dragX
293
+ }, {
294
+ translateY: dragY
295
+ }, {
296
+ scale: _reactNative.Animated.multiply(appear.interpolate({
297
+ inputRange: [0, 1],
298
+ outputRange: [0.92, 1]
299
+ }), dragShrink)
300
+ }],
301
+ opacity: appear
302
+ };
303
+ // The chrome steps aside while the media is being dragged.
304
+ const chromeOpacity = _reactNative.Animated.multiply(_reactNative.Animated.multiply(chrome, appear), dragY.interpolate({
305
+ inputRange: [-60, 0, 60],
306
+ outputRange: [0, 1, 0],
307
+ extrapolate: 'clamp'
308
+ }));
309
+ const chromePointerEvents = chromeVisible ? 'box-none' : 'none';
310
+ const subtitle = [source?.timestamp ? new Date(source.timestamp).toLocaleString(locale, {
311
+ day: 'numeric',
312
+ month: 'short',
313
+ hour: '2-digit',
314
+ minute: '2-digit'
315
+ }) : null, siblings.length > 1 ? t('attachment.position', {
316
+ index: index + 1,
317
+ total: siblings.length
318
+ }) : null].filter(Boolean).join(' · ');
319
+ const showBottomBar = canReply || canDelete;
320
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
321
+ style: _reactNative.StyleSheet.absoluteFill,
322
+ testID: "media-viewer",
323
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.StatusBar, {
324
+ barStyle: "light-content",
325
+ hidden: !chromeVisible,
326
+ animated: true
327
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
328
+ style: [_reactNative.StyleSheet.absoluteFill, styles.backdrop, {
329
+ opacity: backdropOpacity
330
+ }]
331
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureDetector, {
332
+ gesture: pan,
333
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
334
+ style: _reactNative.StyleSheet.absoluteFill,
335
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
336
+ testID: "media-viewer-surface",
337
+ style: _reactNative.StyleSheet.absoluteFill,
338
+ onPress: toggleChrome,
339
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
340
+ style: [_reactNative.StyleSheet.absoluteFill, mediaStyle],
341
+ pointerEvents: "none",
342
+ children: isVideo ? null : /*#__PURE__*/(0, _jsxRuntime.jsx)(ViewerImage, {
343
+ uri: file.fileURL
344
+ }, file.fileURL)
345
+ })
346
+ }), isVideo && /*#__PURE__*/(0, _jsxRuntime.jsx)(ViewerVideo, {
347
+ uri: (0, _secureFileUrl.withFileToken)(file.fileURL),
348
+ mimetype: file.mimetype,
349
+ mediaStyle: mediaStyle,
350
+ chromeOpacity: chromeOpacity,
351
+ chromePointerEvents: chromePointerEvents,
352
+ bottomOffset: (showBottomBar ? bottomBarHeight : insets.bottom) + 8,
353
+ outerGesture: pan,
354
+ onPlayback: handlePlayback,
355
+ onActivity: scheduleAutoHide
356
+ }, file.fileURL), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, {
357
+ pointerEvents: chromePointerEvents,
358
+ style: [styles.topBar, {
359
+ paddingTop: insets.top + 6,
360
+ opacity: chromeOpacity
361
+ }],
362
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
363
+ testID: "media-viewer-back",
364
+ accessibilityRole: "button",
365
+ accessibilityLabel: t('action.close'),
366
+ hitSlop: 12,
367
+ style: styles.iconButton,
368
+ onPress: () => close(),
369
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.BackIcon, {
370
+ color: WHITE
371
+ })
372
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
373
+ style: styles.titleBlock,
374
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
375
+ numberOfLines: 1,
376
+ style: styles.title,
377
+ children: source?.senderName || t('modal.filePreview.title')
378
+ }), !!subtitle && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
379
+ numberOfLines: 1,
380
+ style: styles.subtitle,
381
+ children: subtitle
382
+ })]
383
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
384
+ testID: "media-viewer-save",
385
+ accessibilityRole: "button",
386
+ accessibilityLabel: t('action.save'),
387
+ hitSlop: 12,
388
+ style: styles.iconButton,
389
+ onPress: onSave,
390
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.SaveIcon, {
391
+ color: WHITE
392
+ })
393
+ })]
394
+ }), showBottomBar && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, {
395
+ pointerEvents: chromePointerEvents,
396
+ onLayout: e => setBottomBarHeight(e.nativeEvent.layout.height),
397
+ style: [styles.bottomBar, {
398
+ paddingBottom: insets.bottom + 8,
399
+ opacity: chromeOpacity
400
+ }],
401
+ children: [canReply ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
402
+ testID: "media-viewer-reply",
403
+ accessibilityRole: "button",
404
+ accessibilityLabel: t('action.reply'),
405
+ hitSlop: 12,
406
+ style: styles.iconButton,
407
+ onPress: handleReply,
408
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.ReplyIcon, {
409
+ color: WHITE
410
+ })
411
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
412
+ style: styles.iconButton
413
+ }), canDelete && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
414
+ testID: "media-viewer-delete",
415
+ accessibilityRole: "button",
416
+ accessibilityLabel: t('action.delete'),
417
+ hitSlop: 12,
418
+ style: styles.iconButton,
419
+ onPress: handleDelete,
420
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.DeleteIcon, {
421
+ fill: WHITE
422
+ })
423
+ })]
424
+ })]
425
+ })
426
+ })]
427
+ });
428
+ };
429
+ const ViewerImage = ({
430
+ uri
431
+ }) => {
432
+ const [loaded, setLoaded] = (0, _react.useState)(false);
433
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
434
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
435
+ source: {
436
+ uri: (0, _secureFileUrl.withFileToken)(uri)
437
+ },
438
+ style: _reactNative.StyleSheet.absoluteFill,
439
+ resizeMode: "contain",
440
+ onLoad: () => setLoaded(true),
441
+ onError: () => setLoaded(true)
442
+ }), !loaded && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
443
+ style: styles.center,
444
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
445
+ color: WHITE
446
+ })
447
+ })]
448
+ });
449
+ };
450
+ /**
451
+ * The video page: the surface in the media layer, the play / pause button
452
+ * and the scrubber in the chrome layer. Streams when the server allows it
453
+ * and falls back to a downloaded copy (AVPlayer refuses servers that ignore
454
+ * byte ranges), which is also what a reopened video plays from.
455
+ */
456
+ const ViewerVideo = ({
457
+ uri,
458
+ mimetype,
459
+ mediaStyle,
460
+ chromeOpacity,
461
+ chromePointerEvents,
462
+ bottomOffset,
463
+ outerGesture,
464
+ onPlayback,
465
+ onActivity
466
+ }) => {
467
+ const t = (0, _useT.useT)();
468
+ const remote = (0, _videoCache.isRemoteUrl)(uri);
469
+ // What the player is given; null until we know where to play from.
470
+ const [source, setSource] = (0, _react.useState)(remote ? null : uri);
471
+ // null = not downloading, otherwise 0..1 (0 while the size is unknown).
472
+ const [progress, setProgress] = (0, _react.useState)(null);
473
+ const [failed, setFailed] = (0, _react.useState)(false);
474
+ const [isPlaying, setIsPlaying] = (0, _react.useState)(false);
475
+ const [time, setTime] = (0, _react.useState)(0);
476
+ const [duration, setDuration] = (0, _react.useState)(0);
477
+ const [scrubTime, setScrubTime] = (0, _react.useState)(null);
478
+ const [trackWidth, setTrackWidth] = (0, _react.useState)(0);
479
+ const ended = (0, _react.useRef)(false);
480
+ // Opens playing, like Telegram; a pause sticks across a fallback copy.
481
+ const wantsPlay = (0, _react.useRef)(true);
482
+ const downloadStarted = (0, _react.useRef)(false);
483
+ const mounted = (0, _react.useRef)(true);
484
+ (0, _react.useEffect)(() => () => {
485
+ mounted.current = false;
486
+ }, []);
487
+ const download = (0, _react.useCallback)(() => {
488
+ if (downloadStarted.current) {
489
+ return;
490
+ }
491
+ downloadStarted.current = true;
492
+ setProgress(0);
493
+ (0, _videoCache.downloadVideoToCache)(uri, mimetype, fraction => {
494
+ if (mounted.current) {
495
+ setProgress(fraction);
496
+ }
497
+ }).then(localUri => {
498
+ if (!mounted.current) {
499
+ return;
500
+ }
501
+ setProgress(null);
502
+ setSource(localUri);
503
+ }).catch(err => {
504
+ console.warn('video download failed', err);
505
+ if (!mounted.current) {
506
+ return;
507
+ }
508
+ setProgress(null);
509
+ setFailed(true);
510
+ });
511
+ }, [mimetype, uri]);
512
+ (0, _react.useEffect)(() => {
513
+ if (!remote) {
514
+ return;
515
+ }
516
+ let active = true;
517
+ (0, _videoCache.findCachedVideo)(uri, mimetype).then(cached => {
518
+ if (!active) {
519
+ return;
520
+ }
521
+ if (cached) {
522
+ setSource(cached);
523
+ } else if ((0, _videoCache.isStreamingUnsupported)(uri)) {
524
+ download();
525
+ } else {
526
+ setSource(uri);
527
+ }
528
+ });
529
+ return () => {
530
+ active = false;
531
+ };
532
+ }, [download, mimetype, remote, uri]);
533
+ const player = (0, _expoVideo.useVideoPlayer)(source, p => {
534
+ p.muted = false;
535
+ p.timeUpdateEventInterval = 0.25;
536
+ if (wantsPlay.current) {
537
+ p.play();
538
+ }
539
+ });
540
+ (0, _react.useEffect)(() => {
541
+ const subs = [player.addListener('playingChange', ({
542
+ isPlaying: next
543
+ }) => {
544
+ setIsPlaying(next);
545
+ onPlayback(next);
546
+ }), player.addListener('timeUpdate', ({
547
+ currentTime
548
+ }) => setTime(currentTime)), player.addListener('playToEnd', () => {
549
+ ended.current = true;
550
+ }), player.addListener('statusChange', event => {
551
+ if (event.status === 'readyToPlay') {
552
+ setDuration(player.duration || 0);
553
+ return;
554
+ }
555
+ if (event.status !== 'error') {
556
+ return;
557
+ }
558
+ if (source === uri && remote) {
559
+ (0, _videoCache.markStreamingUnsupported)(uri);
560
+ download();
561
+ } else {
562
+ setFailed(true);
563
+ }
564
+ })];
565
+ return () => subs.forEach(sub => sub.remove());
566
+ }, [download, onPlayback, player, remote, source, uri]);
567
+ (0, _react.useEffect)(() => () => onPlayback(false), [onPlayback]);
568
+ const togglePlay = () => {
569
+ onActivity();
570
+ if (isPlaying) {
571
+ wantsPlay.current = false;
572
+ player.pause();
573
+ return;
574
+ }
575
+ if (ended.current) {
576
+ ended.current = false;
577
+ player.currentTime = 0;
578
+ }
579
+ wantsPlay.current = true;
580
+ player.play();
581
+ };
582
+ const latest = (0, _react.useRef)({
583
+ trackWidth,
584
+ duration,
585
+ player,
586
+ onActivity
587
+ });
588
+ latest.current = {
589
+ trackWidth,
590
+ duration,
591
+ player,
592
+ onActivity
593
+ };
594
+ const timeAt = x => {
595
+ const l = latest.current;
596
+ if (l.trackWidth <= 0 || l.duration <= 0) {
597
+ return 0;
598
+ }
599
+ return Math.min(1, Math.max(0, x / l.trackWidth)) * l.duration;
600
+ };
601
+ const seek = seconds => {
602
+ const l = latest.current;
603
+ ended.current = false;
604
+ l.player.currentTime = seconds;
605
+ setTime(seconds);
606
+ l.onActivity();
607
+ };
608
+ const scrub = (0, _react.useMemo)(() => {
609
+ const drag = _reactNativeGestureHandler.Gesture.Pan().runOnJS(true).activeOffsetX([-2, 2]).onUpdate(e => setScrubTime(timeAt(e.x))).onEnd(e => seek(timeAt(e.x))).onFinalize(() => setScrubTime(null)).blocksExternalGesture(outerGesture);
610
+ const tap = _reactNativeGestureHandler.Gesture.Tap().runOnJS(true).onEnd(e => seek(timeAt(e.x))).blocksExternalGesture(outerGesture);
611
+ return _reactNativeGestureHandler.Gesture.Exclusive(drag, tap);
612
+ }, [outerGesture]);
613
+ const loading = progress !== null || source === null;
614
+ const shown = scrubTime ?? time;
615
+ const fraction = duration > 0 ? Math.min(1, shown / duration) : 0;
616
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
617
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
618
+ style: [_reactNative.StyleSheet.absoluteFill, mediaStyle],
619
+ pointerEvents: "none",
620
+ children: !loading && !failed && /*#__PURE__*/(0, _jsxRuntime.jsx)(_expoVideo.VideoView, {
621
+ player: player,
622
+ style: _reactNative.StyleSheet.absoluteFill,
623
+ contentFit: "contain",
624
+ nativeControls: false,
625
+ surfaceType: "textureView"
626
+ })
627
+ }), failed ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
628
+ style: styles.center,
629
+ pointerEvents: "none",
630
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
631
+ style: styles.failedTitle,
632
+ children: t('video.cantPlay')
633
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
634
+ style: styles.failedHint,
635
+ children: t('video.cantPlayHint')
636
+ })]
637
+ }) : loading ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
638
+ style: styles.center,
639
+ pointerEvents: "none",
640
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
641
+ style: styles.playButton,
642
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
643
+ color: WHITE
644
+ })
645
+ }), !!progress && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
646
+ style: styles.progress,
647
+ children: [Math.round(progress * 100), "%"]
648
+ })]
649
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
650
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
651
+ pointerEvents: chromePointerEvents,
652
+ style: [styles.center, {
653
+ opacity: chromeOpacity
654
+ }],
655
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
656
+ testID: "media-viewer-play",
657
+ accessibilityRole: "button",
658
+ activeOpacity: 0.8,
659
+ style: styles.playButton,
660
+ onPress: togglePlay,
661
+ children: isPlaying ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.PauseIcon, {
662
+ width: 30,
663
+ height: 30,
664
+ color: WHITE
665
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.PlayIcon, {
666
+ width: 30,
667
+ height: 30,
668
+ color: WHITE
669
+ })
670
+ })
671
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, {
672
+ pointerEvents: chromePointerEvents,
673
+ style: [styles.scrubberRow, {
674
+ bottom: bottomOffset,
675
+ opacity: chromeOpacity
676
+ }],
677
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
678
+ style: styles.time,
679
+ children: formatTime(shown)
680
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureDetector, {
681
+ gesture: scrub,
682
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
683
+ testID: "media-viewer-scrubber",
684
+ style: styles.track,
685
+ onLayout: e => setTrackWidth(e.nativeEvent.layout.width),
686
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
687
+ style: styles.trackLine,
688
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
689
+ style: [styles.trackFill, {
690
+ width: `${fraction * 100}%`
691
+ }]
692
+ })
693
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
694
+ style: [styles.thumb, {
695
+ left: fraction * trackWidth - 7
696
+ }]
697
+ })]
698
+ })
699
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
700
+ style: styles.time,
701
+ children: formatTime(duration)
702
+ })]
703
+ })]
704
+ })]
705
+ });
706
+ };
707
+ const styles = _reactNative.StyleSheet.create({
708
+ backdrop: {
709
+ backgroundColor: '#000000'
710
+ },
711
+ center: {
712
+ ..._reactNative.StyleSheet.absoluteFill,
713
+ alignItems: 'center',
714
+ justifyContent: 'center'
715
+ },
716
+ topBar: {
717
+ position: 'absolute',
718
+ top: 0,
719
+ left: 0,
720
+ right: 0,
721
+ flexDirection: 'row',
722
+ alignItems: 'center',
723
+ paddingHorizontal: 8,
724
+ paddingBottom: 10,
725
+ backgroundColor: BAR_BACKGROUND
726
+ },
727
+ bottomBar: {
728
+ position: 'absolute',
729
+ left: 0,
730
+ right: 0,
731
+ bottom: 0,
732
+ flexDirection: 'row',
733
+ alignItems: 'center',
734
+ justifyContent: 'space-between',
735
+ paddingHorizontal: 8,
736
+ paddingTop: 10,
737
+ backgroundColor: BAR_BACKGROUND
738
+ },
739
+ iconButton: {
740
+ width: 44,
741
+ height: 44,
742
+ alignItems: 'center',
743
+ justifyContent: 'center'
744
+ },
745
+ titleBlock: {
746
+ flex: 1,
747
+ alignItems: 'center',
748
+ paddingHorizontal: 8
749
+ },
750
+ title: {
751
+ color: WHITE,
752
+ fontSize: 16,
753
+ fontWeight: '600'
754
+ },
755
+ subtitle: {
756
+ color: 'rgba(255, 255, 255, 0.7)',
757
+ fontSize: 13,
758
+ marginTop: 2
759
+ },
760
+ playButton: {
761
+ width: 64,
762
+ height: 64,
763
+ borderRadius: 32,
764
+ backgroundColor: 'rgba(0, 0, 0, 0.55)',
765
+ alignItems: 'center',
766
+ justifyContent: 'center'
767
+ },
768
+ progress: {
769
+ marginTop: 10,
770
+ color: WHITE,
771
+ fontSize: 13,
772
+ fontWeight: '600'
773
+ },
774
+ failedTitle: {
775
+ color: WHITE,
776
+ fontSize: 16,
777
+ fontWeight: '600'
778
+ },
779
+ failedHint: {
780
+ marginTop: 8,
781
+ color: 'rgba(255, 255, 255, 0.7)',
782
+ textAlign: 'center',
783
+ paddingHorizontal: 32
784
+ },
785
+ scrubberRow: {
786
+ position: 'absolute',
787
+ left: 12,
788
+ right: 12,
789
+ flexDirection: 'row',
790
+ alignItems: 'center',
791
+ gap: 10
792
+ },
793
+ time: {
794
+ color: WHITE,
795
+ fontSize: 12,
796
+ fontVariant: ['tabular-nums'],
797
+ minWidth: 36,
798
+ textAlign: 'center'
799
+ },
800
+ track: {
801
+ flex: 1,
802
+ height: 32,
803
+ justifyContent: 'center'
804
+ },
805
+ trackLine: {
806
+ height: 3,
807
+ borderRadius: 1.5,
808
+ backgroundColor: 'rgba(255, 255, 255, 0.35)',
809
+ overflow: 'hidden'
810
+ },
811
+ trackFill: {
812
+ height: '100%',
813
+ backgroundColor: WHITE
814
+ },
815
+ thumb: {
816
+ position: 'absolute',
817
+ width: 14,
818
+ height: 14,
819
+ borderRadius: 7,
820
+ backgroundColor: WHITE
821
+ }
822
+ });
823
+ var _default = exports.default = MediaViewer;
824
+ //# sourceMappingURL=MediaViewer.js.map