@planningcenter/chat-react-native 3.34.0-rc.4 → 3.34.0

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 (63) hide show
  1. package/build/components/conversation/attachments/generic_file_attachment.d.ts +0 -1
  2. package/build/components/conversation/attachments/generic_file_attachment.d.ts.map +1 -1
  3. package/build/components/conversation/attachments/generic_file_attachment.js +1 -16
  4. package/build/components/conversation/attachments/generic_file_attachment.js.map +1 -1
  5. package/build/components/conversation/message_form/message_form_attachment_file.d.ts +11 -0
  6. package/build/components/conversation/message_form/message_form_attachment_file.d.ts.map +1 -0
  7. package/build/components/conversation/message_form/message_form_attachment_file.js +8 -0
  8. package/build/components/conversation/message_form/message_form_attachment_file.js.map +1 -0
  9. package/build/components/conversation/message_form/message_form_attachment_video.d.ts.map +1 -1
  10. package/build/components/conversation/message_form/message_form_attachment_video.js +1 -2
  11. package/build/components/conversation/message_form/message_form_attachment_video.js.map +1 -1
  12. package/build/components/conversation/message_form.d.ts.map +1 -1
  13. package/build/components/conversation/message_form.js +13 -8
  14. package/build/components/conversation/message_form.js.map +1 -1
  15. package/build/components/display/file_attachment_preview.d.ts +11 -0
  16. package/build/components/display/file_attachment_preview.d.ts.map +1 -0
  17. package/build/components/display/file_attachment_preview.js +95 -0
  18. package/build/components/display/file_attachment_preview.js.map +1 -0
  19. package/build/components/display/image.d.ts.map +1 -1
  20. package/build/components/display/image.js +4 -1
  21. package/build/components/display/image.js.map +1 -1
  22. package/build/components/display/index.d.ts +1 -0
  23. package/build/components/display/index.d.ts.map +1 -1
  24. package/build/components/display/index.js +1 -0
  25. package/build/components/display/index.js.map +1 -1
  26. package/build/hooks/attachments/fallback_chat_configuration.d.ts +1 -0
  27. package/build/hooks/attachments/fallback_chat_configuration.d.ts.map +1 -1
  28. package/build/hooks/attachments/fallback_chat_configuration.js +23 -0
  29. package/build/hooks/attachments/fallback_chat_configuration.js.map +1 -1
  30. package/build/hooks/use_chat_configuration.d.ts +1 -0
  31. package/build/hooks/use_chat_configuration.d.ts.map +1 -1
  32. package/build/hooks/use_chat_configuration.js +9 -1
  33. package/build/hooks/use_chat_configuration.js.map +1 -1
  34. package/build/types/resources/chat_configuration_resource.d.ts +1 -0
  35. package/build/types/resources/chat_configuration_resource.d.ts.map +1 -1
  36. package/build/types/resources/chat_configuration_resource.js.map +1 -1
  37. package/build/utils/attachment_kind.d.ts +5 -0
  38. package/build/utils/attachment_kind.d.ts.map +1 -0
  39. package/build/utils/attachment_kind.js +46 -0
  40. package/build/utils/attachment_kind.js.map +1 -0
  41. package/build/utils/index.d.ts +1 -0
  42. package/build/utils/index.d.ts.map +1 -1
  43. package/build/utils/index.js +1 -0
  44. package/build/utils/index.js.map +1 -1
  45. package/build/utils/native_adapters/document_picker.d.ts +5 -2
  46. package/build/utils/native_adapters/document_picker.d.ts.map +1 -1
  47. package/build/utils/native_adapters/document_picker.js.map +1 -1
  48. package/package.json +2 -2
  49. package/src/__tests__/hooks/use_chat_configuration.test.tsx +26 -0
  50. package/src/components/conversation/attachments/generic_file_attachment.tsx +1 -14
  51. package/src/components/conversation/message_form/message_form_attachment_file.tsx +26 -0
  52. package/src/components/conversation/message_form/message_form_attachment_video.tsx +1 -2
  53. package/src/components/conversation/message_form.tsx +23 -8
  54. package/src/components/display/file_attachment_preview.tsx +135 -0
  55. package/src/components/display/image.tsx +5 -0
  56. package/src/components/display/index.ts +1 -0
  57. package/src/hooks/attachments/fallback_chat_configuration.ts +24 -0
  58. package/src/hooks/use_chat_configuration.ts +9 -0
  59. package/src/types/resources/chat_configuration_resource.ts +4 -0
  60. package/src/utils/__tests__/attachment_kind.test.ts +37 -0
  61. package/src/utils/attachment_kind.ts +47 -0
  62. package/src/utils/index.ts +1 -0
  63. package/src/utils/native_adapters/document_picker.ts +9 -2
@@ -1 +1 @@
1
- {"version":3,"file":"image.js","sourceRoot":"","sources":["../../../src/components/display/image.tsx"],"names":[],"mappings":"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","sourcesContent":["import { noop } from 'lodash'\nimport React, { useState } from 'react'\nimport {\n AnimatableNumericValue,\n DimensionValue,\n ImageStyle,\n Image as ReactNativeImage,\n ImageProps as ReactNativeImageProps,\n StyleSheet,\n View,\n ViewStyle,\n} from 'react-native'\nimport Animated, { AnimatedStyle } from 'react-native-reanimated'\nimport { useTheme } from '../../hooks'\nimport { Spinner } from './spinner'\n\n// =================================\n// ====== Component ================\n// =================================\n\nexport interface ImageProps extends ReactNativeImageProps {\n /**\n * Describes the image to screen-readers and marks the image as `accessible`.\n * Passing an empty string will hide the image from screen-readers.\n */\n alt: string\n /**\n * Shows the image's loading spinner right away. Enabled by default.\n */\n defaultLoading?: boolean\n /**\n * Externally controls the loading state of the image.\n */\n loading?: boolean\n /**\n * Hide the loading spinner, regardless of the loading state.\n */\n hideLoader?: boolean\n /**\n * Size of the loading spinner.\n */\n loaderSize?: number\n /**\n * Style object for the preload background.\n */\n loadingBackgroundStyles?: ViewStyle\n /**\n * Style the outer View of the image.\n */\n wrapperStyle?: ViewStyle\n /**\n * Style the image if animated.\n */\n animatedImageStyle?: AnimatedStyle<ImageStyle>\n}\n\nexport function Image({\n source,\n onLoad = noop,\n defaultLoading = true,\n loading = false,\n hideLoader = false,\n loaderSize = 24,\n loadingBackgroundStyles,\n style = {},\n wrapperStyle = {},\n alt,\n animatedImageStyle = {},\n ...props\n}: ImageProps) {\n const [isImageLoading, setIsImageLoading] = useState(defaultLoading)\n\n const imageStyles = StyleSheet.flatten(style)\n const { width = '100%', height = '100%', borderRadius = 0 } = imageStyles || {}\n const styles = useStyles({ width, height, borderRadius })\n\n const handleOnLoad = (event: any) => {\n setIsImageLoading(false)\n onLoad?.(event)\n }\n\n const isLoading = isImageLoading || loading\n\n const ImageComponent = animatedImageStyle ? Animated.Image : ReactNativeImage\n\n return (\n <View\n style={wrapperStyle}\n accessible={Boolean(alt)}\n accessibilityRole=\"image\"\n accessibilityState={{ busy: isLoading }}\n collapsable={false}\n >\n <ImageComponent\n style={[styles.image, imageStyles, animatedImageStyle]}\n onLoad={handleOnLoad}\n source={source}\n alt={isLoading ? '' : alt}\n {...props}\n />\n {!hideLoader && isLoading && (\n <View style={[styles.loadingBackground, loadingBackgroundStyles]}>\n <Spinner size={loaderSize} style={styles.spinner} />\n </View>\n )}\n </View>\n )\n}\n\n// =================================\n// ====== Styles ===================\n// =================================\n\ninterface Styles {\n width: DimensionValue\n height: DimensionValue\n borderRadius: AnimatableNumericValue | string\n}\n\nconst useStyles = ({ width, height, borderRadius }: Styles) => {\n const { colors } = useTheme()\n\n return StyleSheet.create({\n loadingBackground: {\n position: 'absolute',\n top: 0,\n left: 0,\n backgroundColor: colors.fillColorNeutral070,\n borderRadius,\n width,\n height,\n },\n image: {\n backgroundColor: colors.fillColorNeutral070,\n width,\n height,\n },\n spinner: {\n width: '100%',\n height: '100%',\n justifyContent: 'center',\n alignItems: 'center',\n position: 'absolute',\n },\n })\n}\n"]}
1
+ {"version":3,"file":"image.js","sourceRoot":"","sources":["../../../src/components/display/image.tsx"],"names":[],"mappings":"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","sourcesContent":["import { noop } from 'lodash'\nimport React, { useState } from 'react'\nimport {\n AnimatableNumericValue,\n DimensionValue,\n ImageStyle,\n Image as ReactNativeImage,\n ImageProps as ReactNativeImageProps,\n StyleSheet,\n View,\n ViewStyle,\n} from 'react-native'\nimport Animated, { AnimatedStyle } from 'react-native-reanimated'\nimport { useTheme } from '../../hooks'\nimport { Spinner } from './spinner'\n\n// =================================\n// ====== Component ================\n// =================================\n\nexport interface ImageProps extends ReactNativeImageProps {\n /**\n * Describes the image to screen-readers and marks the image as `accessible`.\n * Passing an empty string will hide the image from screen-readers.\n */\n alt: string\n /**\n * Shows the image's loading spinner right away. Enabled by default.\n */\n defaultLoading?: boolean\n /**\n * Externally controls the loading state of the image.\n */\n loading?: boolean\n /**\n * Hide the loading spinner, regardless of the loading state.\n */\n hideLoader?: boolean\n /**\n * Size of the loading spinner.\n */\n loaderSize?: number\n /**\n * Style object for the preload background.\n */\n loadingBackgroundStyles?: ViewStyle\n /**\n * Style the outer View of the image.\n */\n wrapperStyle?: ViewStyle\n /**\n * Style the image if animated.\n */\n animatedImageStyle?: AnimatedStyle<ImageStyle>\n}\n\nexport function Image({\n source,\n onLoad = noop,\n defaultLoading = true,\n loading = false,\n hideLoader = false,\n loaderSize = 24,\n loadingBackgroundStyles,\n style = {},\n wrapperStyle = {},\n alt,\n animatedImageStyle = {},\n ...props\n}: ImageProps) {\n const [isImageLoading, setIsImageLoading] = useState(defaultLoading)\n\n const imageStyles = StyleSheet.flatten(style)\n const { width = '100%', height = '100%', borderRadius = 0 } = imageStyles || {}\n const styles = useStyles({ width, height, borderRadius })\n\n const handleOnLoad = (event: any) => {\n setIsImageLoading(false)\n onLoad?.(event)\n }\n\n const handleOnError = () => {\n setIsImageLoading(false)\n }\n\n const isLoading = isImageLoading || loading\n\n const ImageComponent = animatedImageStyle ? Animated.Image : ReactNativeImage\n\n return (\n <View\n style={wrapperStyle}\n accessible={Boolean(alt)}\n accessibilityRole=\"image\"\n accessibilityState={{ busy: isLoading }}\n collapsable={false}\n >\n <ImageComponent\n style={[styles.image, imageStyles, animatedImageStyle]}\n onLoad={handleOnLoad}\n onError={handleOnError}\n source={source}\n alt={isLoading ? '' : alt}\n {...props}\n />\n {!hideLoader && isLoading && (\n <View style={[styles.loadingBackground, loadingBackgroundStyles]}>\n <Spinner size={loaderSize} style={styles.spinner} />\n </View>\n )}\n </View>\n )\n}\n\n// =================================\n// ====== Styles ===================\n// =================================\n\ninterface Styles {\n width: DimensionValue\n height: DimensionValue\n borderRadius: AnimatableNumericValue | string\n}\n\nconst useStyles = ({ width, height, borderRadius }: Styles) => {\n const { colors } = useTheme()\n\n return StyleSheet.create({\n loadingBackground: {\n position: 'absolute',\n top: 0,\n left: 0,\n backgroundColor: colors.fillColorNeutral070,\n borderRadius,\n width,\n height,\n },\n image: {\n backgroundColor: colors.fillColorNeutral070,\n width,\n height,\n },\n spinner: {\n width: '100%',\n height: '100%',\n justifyContent: 'center',\n alignItems: 'center',\n position: 'absolute',\n },\n })\n}\n"]}
@@ -12,6 +12,7 @@ export * from './heading';
12
12
  export * from './icon_button';
13
13
  export * from './icon';
14
14
  export * from './image';
15
+ export * from './file_attachment_preview';
15
16
  export * from './image_attachment_preview';
16
17
  export * from './video_attachment_preview';
17
18
  export * from './person';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/display/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,uBAAuB,CAAA;AACrC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,4BAA4B,CAAA;AAC1C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,QAAQ,CAAA;AACtB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/display/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,uBAAuB,CAAA;AACrC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,2BAA2B,CAAA;AACzC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,QAAQ,CAAA;AACtB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA"}
@@ -12,6 +12,7 @@ export * from './heading';
12
12
  export * from './icon_button';
13
13
  export * from './icon';
14
14
  export * from './image';
15
+ export * from './file_attachment_preview';
15
16
  export * from './image_attachment_preview';
16
17
  export * from './video_attachment_preview';
17
18
  export * from './person';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/display/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,uBAAuB,CAAA;AACrC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,4BAA4B,CAAA;AAC1C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,QAAQ,CAAA;AACtB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA","sourcesContent":["export * from './avatar_group'\nexport * from './avatar'\nexport * from './badge'\nexport * from './conversation_avatar'\nexport * from './emoji_avatar'\nexport * from './icon_avatar'\nexport * from './banner_collapsible'\nexport * from './banner'\nexport * from './button'\nexport * from './child_notice'\nexport * from './heading'\nexport * from './icon_button'\nexport * from './icon'\nexport * from './image'\nexport * from './image_attachment_preview'\nexport * from './video_attachment_preview'\nexport * from './person'\nexport * from './spinner'\nexport * from './switch'\nexport * from './text_button'\nexport * from './text_inline_button'\nexport * from './text'\nexport * from './toggle_button'\nexport * from './keyboard_view'\nexport * from './pressable_row'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/display/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,uBAAuB,CAAA;AACrC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,2BAA2B,CAAA;AACzC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,QAAQ,CAAA;AACtB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA","sourcesContent":["export * from './avatar_group'\nexport * from './avatar'\nexport * from './badge'\nexport * from './conversation_avatar'\nexport * from './emoji_avatar'\nexport * from './icon_avatar'\nexport * from './banner_collapsible'\nexport * from './banner'\nexport * from './button'\nexport * from './child_notice'\nexport * from './heading'\nexport * from './icon_button'\nexport * from './icon'\nexport * from './image'\nexport * from './file_attachment_preview'\nexport * from './image_attachment_preview'\nexport * from './video_attachment_preview'\nexport * from './person'\nexport * from './spinner'\nexport * from './switch'\nexport * from './text_button'\nexport * from './text_inline_button'\nexport * from './text'\nexport * from './toggle_button'\nexport * from './keyboard_view'\nexport * from './pressable_row'\n"]}
@@ -1,4 +1,5 @@
1
1
  export declare const FALLBACK_ALLOWED_FILE_EXTENSIONS: string[];
2
+ export declare const FALLBACK_ALLOWED_MIME_TYPES: string[];
2
3
  export declare const FALLBACK_MAX_FILE_SIZE_IN_BYTES: number;
3
4
  export declare const FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE = 10;
4
5
  //# sourceMappingURL=fallback_chat_configuration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fallback_chat_configuration.d.ts","sourceRoot":"","sources":["../../../src/hooks/attachments/fallback_chat_configuration.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,gCAAgC,UA8C5C,CAAA;AAED,eAAO,MAAM,+BAA+B,QAAmB,CAAA;AAE/D,eAAO,MAAM,oCAAoC,KAAK,CAAA"}
1
+ {"version":3,"file":"fallback_chat_configuration.d.ts","sourceRoot":"","sources":["../../../src/hooks/attachments/fallback_chat_configuration.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,gCAAgC,UA8C5C,CAAA;AAMD,eAAO,MAAM,2BAA2B,UAkBvC,CAAA;AAED,eAAO,MAAM,+BAA+B,QAAmB,CAAA;AAE/D,eAAO,MAAM,oCAAoC,KAAK,CAAA"}
@@ -54,6 +54,29 @@ export const FALLBACK_ALLOWED_FILE_EXTENSIONS = [
54
54
  '.xls',
55
55
  '.xlsx',
56
56
  ];
57
+ // Broad MIME categories covering the extensions in
58
+ // FALLBACK_ALLOWED_FILE_EXTENSIONS. Used to constrain native pickers up
59
+ // front; final accept/reject still uses the extension list because the
60
+ // picker's `type` filter is advisory on iOS and Android.
61
+ export const FALLBACK_ALLOWED_MIME_TYPES = [
62
+ 'image/*',
63
+ 'video/*',
64
+ 'audio/*',
65
+ 'application/pdf',
66
+ 'application/msword',
67
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
68
+ 'application/vnd.ms-excel',
69
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
70
+ 'application/vnd.ms-powerpoint',
71
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
72
+ 'application/vnd.apple.pages',
73
+ 'application/vnd.apple.numbers',
74
+ 'application/vnd.apple.keynote',
75
+ 'application/rtf',
76
+ 'text/plain',
77
+ 'text/rtf',
78
+ 'text/vcard',
79
+ ];
57
80
  export const FALLBACK_MAX_FILE_SIZE_IN_BYTES = 50 * 1024 * 1024;
58
81
  export const FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE = 10;
59
82
  //# sourceMappingURL=fallback_chat_configuration.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fallback_chat_configuration.js","sourceRoot":"","sources":["../../../src/hooks/attachments/fallback_chat_configuration.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,0EAA0E;AAC1E,0EAA0E;AAC1E,2BAA2B;AAC3B,EAAE;AACF,uEAAuE;AACvE,0EAA0E;AAC1E,yEAAyE;AACzE,6BAA6B;AAE7B,MAAM,CAAC,MAAM,gCAAgC,GAAG;IAC9C,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,WAAW;IACX,OAAO;IACP,QAAQ;IACR,MAAM;IACN,UAAU;IACV,MAAM;IACN,QAAQ;IACR,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;CACR,CAAA;AAED,MAAM,CAAC,MAAM,+BAA+B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;AAE/D,MAAM,CAAC,MAAM,oCAAoC,GAAG,EAAE,CAAA","sourcesContent":["// Fallback values for use_chat_configuration used when the API request fails.\n// Keeping a static copy here means an API outage doesn't block attachment\n// uploads entirely — users retain the behavior they had before the server\n// was the source of truth.\n//\n// The server is authoritative: if these values drift from the server's\n// ChatConfiguration, the server will still reject uploads that exceed its\n// own rules. These exist only to give the client something reasonable to\n// validate against up-front.\n\nexport const FALLBACK_ALLOWED_FILE_EXTENSIONS = [\n '.3ga',\n '.3gp',\n '.aac',\n '.amr',\n '.avi',\n '.bmp',\n '.doc',\n '.docx',\n '.gif',\n '.h263',\n '.h264',\n '.heic',\n '.heif',\n '.jpeg',\n '.jpg',\n '.key',\n '.m4a',\n '.m4b',\n '.m4p',\n '.m4r',\n '.m4v',\n '.mkv',\n '.mov',\n '.mp3',\n '.mp4',\n '.mp4-latm',\n '.mpeg',\n '.mpeg4',\n '.mpg',\n '.numbers',\n '.ogg',\n '.pages',\n '.pdf',\n '.png',\n '.ppt',\n '.pptx',\n '.rtf',\n '.txt',\n '.vcf',\n '.wav',\n '.webm',\n '.webp',\n '.wmv',\n '.xls',\n '.xlsx',\n]\n\nexport const FALLBACK_MAX_FILE_SIZE_IN_BYTES = 50 * 1024 * 1024\n\nexport const FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE = 10\n"]}
1
+ {"version":3,"file":"fallback_chat_configuration.js","sourceRoot":"","sources":["../../../src/hooks/attachments/fallback_chat_configuration.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,0EAA0E;AAC1E,0EAA0E;AAC1E,2BAA2B;AAC3B,EAAE;AACF,uEAAuE;AACvE,0EAA0E;AAC1E,yEAAyE;AACzE,6BAA6B;AAE7B,MAAM,CAAC,MAAM,gCAAgC,GAAG;IAC9C,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,WAAW;IACX,OAAO;IACP,QAAQ;IACR,MAAM;IACN,UAAU;IACV,MAAM;IACN,QAAQ;IACR,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;CACR,CAAA;AAED,mDAAmD;AACnD,wEAAwE;AACxE,uEAAuE;AACvE,yDAAyD;AACzD,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,SAAS;IACT,SAAS;IACT,SAAS;IACT,iBAAiB;IACjB,oBAAoB;IACpB,yEAAyE;IACzE,0BAA0B;IAC1B,mEAAmE;IACnE,+BAA+B;IAC/B,2EAA2E;IAC3E,6BAA6B;IAC7B,+BAA+B;IAC/B,+BAA+B;IAC/B,iBAAiB;IACjB,YAAY;IACZ,UAAU;IACV,YAAY;CACb,CAAA;AAED,MAAM,CAAC,MAAM,+BAA+B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;AAE/D,MAAM,CAAC,MAAM,oCAAoC,GAAG,EAAE,CAAA","sourcesContent":["// Fallback values for use_chat_configuration used when the API request fails.\n// Keeping a static copy here means an API outage doesn't block attachment\n// uploads entirely — users retain the behavior they had before the server\n// was the source of truth.\n//\n// The server is authoritative: if these values drift from the server's\n// ChatConfiguration, the server will still reject uploads that exceed its\n// own rules. These exist only to give the client something reasonable to\n// validate against up-front.\n\nexport const FALLBACK_ALLOWED_FILE_EXTENSIONS = [\n '.3ga',\n '.3gp',\n '.aac',\n '.amr',\n '.avi',\n '.bmp',\n '.doc',\n '.docx',\n '.gif',\n '.h263',\n '.h264',\n '.heic',\n '.heif',\n '.jpeg',\n '.jpg',\n '.key',\n '.m4a',\n '.m4b',\n '.m4p',\n '.m4r',\n '.m4v',\n '.mkv',\n '.mov',\n '.mp3',\n '.mp4',\n '.mp4-latm',\n '.mpeg',\n '.mpeg4',\n '.mpg',\n '.numbers',\n '.ogg',\n '.pages',\n '.pdf',\n '.png',\n '.ppt',\n '.pptx',\n '.rtf',\n '.txt',\n '.vcf',\n '.wav',\n '.webm',\n '.webp',\n '.wmv',\n '.xls',\n '.xlsx',\n]\n\n// Broad MIME categories covering the extensions in\n// FALLBACK_ALLOWED_FILE_EXTENSIONS. Used to constrain native pickers up\n// front; final accept/reject still uses the extension list because the\n// picker's `type` filter is advisory on iOS and Android.\nexport const FALLBACK_ALLOWED_MIME_TYPES = [\n 'image/*',\n 'video/*',\n 'audio/*',\n 'application/pdf',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/vnd.ms-excel',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'application/vnd.ms-powerpoint',\n 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'application/vnd.apple.pages',\n 'application/vnd.apple.numbers',\n 'application/vnd.apple.keynote',\n 'application/rtf',\n 'text/plain',\n 'text/rtf',\n 'text/vcard',\n]\n\nexport const FALLBACK_MAX_FILE_SIZE_IN_BYTES = 50 * 1024 * 1024\n\nexport const FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE = 10\n"]}
@@ -1,5 +1,6 @@
1
1
  export declare function useChatConfiguration(): {
2
2
  allowedFileExtensions: string[];
3
+ allowedMimeTypes: string[];
3
4
  maxFileSizeInBytes: number;
4
5
  maxAttachmentsPerMessage: number;
5
6
  };
@@ -1 +1 @@
1
- {"version":3,"file":"use_chat_configuration.d.ts","sourceRoot":"","sources":["../../src/hooks/use_chat_configuration.ts"],"names":[],"mappings":"AAkBA,wBAAgB,oBAAoB;;;;EAqBnC"}
1
+ {"version":3,"file":"use_chat_configuration.d.ts","sourceRoot":"","sources":["../../src/hooks/use_chat_configuration.ts"],"names":[],"mappings":"AAmBA,wBAAgB,oBAAoB;;;;;EA4BnC"}
@@ -1,6 +1,6 @@
1
1
  import { useSuspenseQuery } from '@tanstack/react-query';
2
2
  import { getChatConfigurationRequestArgs, getChatConfigurationQueryKey, } from '../utils/request/get_chat_configuration';
3
- import { FALLBACK_ALLOWED_FILE_EXTENSIONS, FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE, FALLBACK_MAX_FILE_SIZE_IN_BYTES, } from './attachments/fallback_chat_configuration';
3
+ import { FALLBACK_ALLOWED_FILE_EXTENSIONS, FALLBACK_ALLOWED_MIME_TYPES, FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE, FALLBACK_MAX_FILE_SIZE_IN_BYTES, } from './attachments/fallback_chat_configuration';
4
4
  import { useApiClient } from './use_api_client';
5
5
  // Client-side mirror of ChatConfiguration (server is source of truth).
6
6
  // Returns fallback values if the API request fails so that the attachment
@@ -21,6 +21,13 @@ export function useChatConfiguration() {
21
21
  const attrs = data.data;
22
22
  return {
23
23
  allowedFileExtensions: attrs.allowedFileExtensions,
24
+ // During the server-side rollout the API may omit allowedMimeTypes.
25
+ // The static fallback covers the same set of file kinds as the server's
26
+ // hardcoded ALLOWED_FILE_EXTENSIONS, so the picker filter and the
27
+ // post-pick extension validator agree. Once the server always returns
28
+ // allowedMimeTypes, the resource type can tighten from optional to
29
+ // required and this nullish-coalesce becomes pure defense-in-depth.
30
+ allowedMimeTypes: attrs.allowedMimeTypes ?? FALLBACK_ALLOWED_MIME_TYPES,
24
31
  maxFileSizeInBytes: attrs.maxFileSizeInBytes,
25
32
  maxAttachmentsPerMessage: attrs.maxAttachmentsPerMessage,
26
33
  };
@@ -32,6 +39,7 @@ const stableFallbackConfiguration = {
32
39
  type: 'ChatConfiguration',
33
40
  id: 'current',
34
41
  allowedFileExtensions: FALLBACK_ALLOWED_FILE_EXTENSIONS,
42
+ allowedMimeTypes: FALLBACK_ALLOWED_MIME_TYPES,
35
43
  maxFileSizeInBytes: FALLBACK_MAX_FILE_SIZE_IN_BYTES,
36
44
  maxAttachmentsPerMessage: FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,
37
45
  },
@@ -1 +1 @@
1
- {"version":3,"file":"use_chat_configuration.js","sourceRoot":"","sources":["../../src/hooks/use_chat_configuration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAGxD,OAAO,EACL,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,yCAAyC,CAAA;AAChD,OAAO,EACL,gCAAgC,EAChC,oCAAoC,EACpC,+BAA+B,GAChC,MAAM,2CAA2C,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,uEAAuE;AACvE,0EAA0E;AAC1E,oEAAoE;AACpE,iEAAiE;AACjE,MAAM,UAAU,oBAAoB;IAClC,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,WAAW,GAAG,+BAA+B,EAAE,CAAA;IAErD,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC;QAChC,QAAQ,EAAE,4BAA4B,EAAE;QACxC,OAAO,EAAE,GAAG,EAAE;YACZ,OAAO,SAAS,CAAC,IAAI;iBAClB,GAAG,CAAyC,WAAW,CAAC;iBACxD,KAAK,CAAC,GAAG,EAAE,CAAC,2BAA2B,CAAC,CAAA;QAC7C,CAAC;QACD,SAAS,EAAE,IAAI,GAAG,EAAE,GAAG,EAAE,EAAE,+BAA+B;KAC3D,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAA;IAEvB,OAAO;QACL,qBAAqB,EAAE,KAAK,CAAC,qBAAqB;QAClD,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;QAC5C,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;KACzD,CAAA;AACH,CAAC;AAED,qEAAqE;AACrE,kCAAkC;AAClC,MAAM,2BAA2B,GAA2C;IAC1E,IAAI,EAAE;QACJ,IAAI,EAAE,mBAAmB;QACzB,EAAE,EAAE,SAAS;QACb,qBAAqB,EAAE,gCAAgC;QACvD,kBAAkB,EAAE,+BAA+B;QACnD,wBAAwB,EAAE,oCAAoC;KAC/D;IACD,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;CACT,CAAA","sourcesContent":["import { useSuspenseQuery } from '@tanstack/react-query'\nimport { ApiResource } from '../types'\nimport type { ChatConfigurationResource } from '../types/resources/chat_configuration_resource'\nimport {\n getChatConfigurationRequestArgs,\n getChatConfigurationQueryKey,\n} from '../utils/request/get_chat_configuration'\nimport {\n FALLBACK_ALLOWED_FILE_EXTENSIONS,\n FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,\n FALLBACK_MAX_FILE_SIZE_IN_BYTES,\n} from './attachments/fallback_chat_configuration'\nimport { useApiClient } from './use_api_client'\n\n// Client-side mirror of ChatConfiguration (server is source of truth).\n// Returns fallback values if the API request fails so that the attachment\n// UX keeps working during transient server issues. The server still\n// enforces its own limits — this hook only drives pre-upload UX.\nexport function useChatConfiguration() {\n const apiClient = useApiClient()\n const requestArgs = getChatConfigurationRequestArgs()\n\n const { data } = useSuspenseQuery({\n queryKey: getChatConfigurationQueryKey(),\n queryFn: () => {\n return apiClient.chat\n .get<ApiResource<ChatConfigurationResource>>(requestArgs)\n .catch(() => stableFallbackConfiguration)\n },\n staleTime: 1000 * 60 * 60, // 1 hour — this rarely changes\n })\n\n const attrs = data.data\n\n return {\n allowedFileExtensions: attrs.allowedFileExtensions,\n maxFileSizeInBytes: attrs.maxFileSizeInBytes,\n maxAttachmentsPerMessage: attrs.maxAttachmentsPerMessage,\n }\n}\n\n// Shape matches what consumers see after transform_response flattens\n// attributes and camelCases keys.\nconst stableFallbackConfiguration: ApiResource<ChatConfigurationResource> = {\n data: {\n type: 'ChatConfiguration',\n id: 'current',\n allowedFileExtensions: FALLBACK_ALLOWED_FILE_EXTENSIONS,\n maxFileSizeInBytes: FALLBACK_MAX_FILE_SIZE_IN_BYTES,\n maxAttachmentsPerMessage: FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,\n },\n links: {},\n meta: {},\n}\n"]}
1
+ {"version":3,"file":"use_chat_configuration.js","sourceRoot":"","sources":["../../src/hooks/use_chat_configuration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAGxD,OAAO,EACL,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,yCAAyC,CAAA;AAChD,OAAO,EACL,gCAAgC,EAChC,2BAA2B,EAC3B,oCAAoC,EACpC,+BAA+B,GAChC,MAAM,2CAA2C,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,uEAAuE;AACvE,0EAA0E;AAC1E,oEAAoE;AACpE,iEAAiE;AACjE,MAAM,UAAU,oBAAoB;IAClC,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,WAAW,GAAG,+BAA+B,EAAE,CAAA;IAErD,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC;QAChC,QAAQ,EAAE,4BAA4B,EAAE;QACxC,OAAO,EAAE,GAAG,EAAE;YACZ,OAAO,SAAS,CAAC,IAAI;iBAClB,GAAG,CAAyC,WAAW,CAAC;iBACxD,KAAK,CAAC,GAAG,EAAE,CAAC,2BAA2B,CAAC,CAAA;QAC7C,CAAC;QACD,SAAS,EAAE,IAAI,GAAG,EAAE,GAAG,EAAE,EAAE,+BAA+B;KAC3D,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAA;IAEvB,OAAO;QACL,qBAAqB,EAAE,KAAK,CAAC,qBAAqB;QAClD,oEAAoE;QACpE,wEAAwE;QACxE,kEAAkE;QAClE,sEAAsE;QACtE,mEAAmE;QACnE,oEAAoE;QACpE,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,IAAI,2BAA2B;QACvE,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;QAC5C,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;KACzD,CAAA;AACH,CAAC;AAED,qEAAqE;AACrE,kCAAkC;AAClC,MAAM,2BAA2B,GAA2C;IAC1E,IAAI,EAAE;QACJ,IAAI,EAAE,mBAAmB;QACzB,EAAE,EAAE,SAAS;QACb,qBAAqB,EAAE,gCAAgC;QACvD,gBAAgB,EAAE,2BAA2B;QAC7C,kBAAkB,EAAE,+BAA+B;QACnD,wBAAwB,EAAE,oCAAoC;KAC/D;IACD,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;CACT,CAAA","sourcesContent":["import { useSuspenseQuery } from '@tanstack/react-query'\nimport { ApiResource } from '../types'\nimport type { ChatConfigurationResource } from '../types/resources/chat_configuration_resource'\nimport {\n getChatConfigurationRequestArgs,\n getChatConfigurationQueryKey,\n} from '../utils/request/get_chat_configuration'\nimport {\n FALLBACK_ALLOWED_FILE_EXTENSIONS,\n FALLBACK_ALLOWED_MIME_TYPES,\n FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,\n FALLBACK_MAX_FILE_SIZE_IN_BYTES,\n} from './attachments/fallback_chat_configuration'\nimport { useApiClient } from './use_api_client'\n\n// Client-side mirror of ChatConfiguration (server is source of truth).\n// Returns fallback values if the API request fails so that the attachment\n// UX keeps working during transient server issues. The server still\n// enforces its own limits — this hook only drives pre-upload UX.\nexport function useChatConfiguration() {\n const apiClient = useApiClient()\n const requestArgs = getChatConfigurationRequestArgs()\n\n const { data } = useSuspenseQuery({\n queryKey: getChatConfigurationQueryKey(),\n queryFn: () => {\n return apiClient.chat\n .get<ApiResource<ChatConfigurationResource>>(requestArgs)\n .catch(() => stableFallbackConfiguration)\n },\n staleTime: 1000 * 60 * 60, // 1 hour — this rarely changes\n })\n\n const attrs = data.data\n\n return {\n allowedFileExtensions: attrs.allowedFileExtensions,\n // During the server-side rollout the API may omit allowedMimeTypes.\n // The static fallback covers the same set of file kinds as the server's\n // hardcoded ALLOWED_FILE_EXTENSIONS, so the picker filter and the\n // post-pick extension validator agree. Once the server always returns\n // allowedMimeTypes, the resource type can tighten from optional to\n // required and this nullish-coalesce becomes pure defense-in-depth.\n allowedMimeTypes: attrs.allowedMimeTypes ?? FALLBACK_ALLOWED_MIME_TYPES,\n maxFileSizeInBytes: attrs.maxFileSizeInBytes,\n maxAttachmentsPerMessage: attrs.maxAttachmentsPerMessage,\n }\n}\n\n// Shape matches what consumers see after transform_response flattens\n// attributes and camelCases keys.\nconst stableFallbackConfiguration: ApiResource<ChatConfigurationResource> = {\n data: {\n type: 'ChatConfiguration',\n id: 'current',\n allowedFileExtensions: FALLBACK_ALLOWED_FILE_EXTENSIONS,\n allowedMimeTypes: FALLBACK_ALLOWED_MIME_TYPES,\n maxFileSizeInBytes: FALLBACK_MAX_FILE_SIZE_IN_BYTES,\n maxAttachmentsPerMessage: FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,\n },\n links: {},\n meta: {},\n}\n"]}
@@ -2,6 +2,7 @@ export interface ChatConfigurationResource {
2
2
  type: 'ChatConfiguration';
3
3
  id: string;
4
4
  allowedFileExtensions: string[];
5
+ allowedMimeTypes?: string[];
5
6
  maxFileSizeInBytes: number;
6
7
  maxAttachmentsPerMessage: number;
7
8
  }
@@ -1 +1 @@
1
- {"version":3,"file":"chat_configuration_resource.d.ts","sourceRoot":"","sources":["../../../src/types/resources/chat_configuration_resource.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,mBAAmB,CAAA;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,wBAAwB,EAAE,MAAM,CAAA;CACjC"}
1
+ {"version":3,"file":"chat_configuration_resource.d.ts","sourceRoot":"","sources":["../../../src/types/resources/chat_configuration_resource.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,mBAAmB,CAAA;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAI/B,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,wBAAwB,EAAE,MAAM,CAAA;CACjC"}
@@ -1 +1 @@
1
- {"version":3,"file":"chat_configuration_resource.js","sourceRoot":"","sources":["../../../src/types/resources/chat_configuration_resource.ts"],"names":[],"mappings":"","sourcesContent":["// Shape after transform_response flattens attributes and camelCases keys.\n// The wire format uses snake_case nested under attributes (see the Rails\n// ChatConfigurationVertex); the JS client transforms it before consumers\n// see it.\nexport interface ChatConfigurationResource {\n type: 'ChatConfiguration'\n id: string\n allowedFileExtensions: string[]\n maxFileSizeInBytes: number\n maxAttachmentsPerMessage: number\n}\n"]}
1
+ {"version":3,"file":"chat_configuration_resource.js","sourceRoot":"","sources":["../../../src/types/resources/chat_configuration_resource.ts"],"names":[],"mappings":"","sourcesContent":["// Shape after transform_response flattens attributes and camelCases keys.\n// The wire format uses snake_case nested under attributes (see the Rails\n// ChatConfigurationVertex); the JS client transforms it before consumers\n// see it.\nexport interface ChatConfigurationResource {\n type: 'ChatConfiguration'\n id: string\n allowedFileExtensions: string[]\n // Optional during server-side rollout. Once the server always returns it,\n // tighten to `string[]`. Until then, useChatConfiguration falls back to\n // FALLBACK_ALLOWED_MIME_TYPES.\n allowedMimeTypes?: string[]\n maxFileSizeInBytes: number\n maxAttachmentsPerMessage: number\n}\n"]}
@@ -0,0 +1,5 @@
1
+ import { IconString } from '../components/display/icon';
2
+ export type AttachmentPreviewKind = 'image' | 'video' | 'file';
3
+ export declare function pickAttachmentPreviewKind(type: string | undefined, name?: string): AttachmentPreviewKind;
4
+ export declare function getAttachmentIconName(type: string | undefined): IconString;
5
+ //# sourceMappingURL=attachment_kind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment_kind.d.ts","sourceRoot":"","sources":["../../src/utils/attachment_kind.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAEvD,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;AA0B9D,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,IAAI,CAAC,EAAE,MAAM,GACZ,qBAAqB,CAOvB;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAM1E"}
@@ -0,0 +1,46 @@
1
+ // Document picker providers sometimes hand back assets without a usable
2
+ // MIME type (Android `MediaStore` content URIs, some cloud-storage
3
+ // providers). The asset's `type` then arrives as `application/octet-stream`
4
+ // or undefined even though the file is genuinely an image or video. Fall
5
+ // back to extension-based classification in that case so the preview
6
+ // matches the actual content.
7
+ const IMAGE_EXTENSIONS = new Set(['bmp', 'gif', 'heic', 'heif', 'jpeg', 'jpg', 'png', 'webp']);
8
+ const VIDEO_EXTENSIONS = new Set([
9
+ '3gp',
10
+ 'avi',
11
+ 'h263',
12
+ 'h264',
13
+ 'm4v',
14
+ 'mkv',
15
+ 'mov',
16
+ 'mp4',
17
+ 'mpeg',
18
+ 'mpeg4',
19
+ 'mpg',
20
+ 'webm',
21
+ 'wmv',
22
+ ]);
23
+ export function pickAttachmentPreviewKind(type, name) {
24
+ if (type?.startsWith('image/'))
25
+ return 'image';
26
+ if (type?.startsWith('video/'))
27
+ return 'video';
28
+ const ext = name?.split('.').pop()?.toLowerCase();
29
+ if (ext && IMAGE_EXTENSIONS.has(ext))
30
+ return 'image';
31
+ if (ext && VIDEO_EXTENSIONS.has(ext))
32
+ return 'video';
33
+ return 'file';
34
+ }
35
+ export function getAttachmentIconName(type) {
36
+ if (type?.startsWith('image/'))
37
+ return 'general.outlinedImageFile';
38
+ if (type?.startsWith('video/'))
39
+ return 'general.outlinedVideoFile';
40
+ if (type?.startsWith('audio/'))
41
+ return 'general.outlinedMusicFile';
42
+ if (type === 'application/pdf')
43
+ return 'general.outlinedPdfFile';
44
+ return 'general.outlinedGenericFile';
45
+ }
46
+ //# sourceMappingURL=attachment_kind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment_kind.js","sourceRoot":"","sources":["../../src/utils/attachment_kind.ts"],"names":[],"mappings":"AAIA,wEAAwE;AACxE,mEAAmE;AACnE,4EAA4E;AAC5E,yEAAyE;AACzE,qEAAqE;AACrE,8BAA8B;AAC9B,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;AAE9F,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;IAC/B,KAAK;IACL,KAAK;IACL,MAAM;IACN,MAAM;IACN,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,MAAM;IACN,OAAO;IACP,KAAK;IACL,MAAM;IACN,KAAK;CACN,CAAC,CAAA;AAEF,MAAM,UAAU,yBAAyB,CACvC,IAAwB,EACxB,IAAa;IAEb,IAAI,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,OAAO,CAAA;IAC9C,IAAI,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,OAAO,CAAA;IAC9C,MAAM,GAAG,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,CAAA;IACjD,IAAI,GAAG,IAAI,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,CAAA;IACpD,IAAI,GAAG,IAAI,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,CAAA;IACpD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAwB;IAC5D,IAAI,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,2BAA2B,CAAA;IAClE,IAAI,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,2BAA2B,CAAA;IAClE,IAAI,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,2BAA2B,CAAA;IAClE,IAAI,IAAI,KAAK,iBAAiB;QAAE,OAAO,yBAAyB,CAAA;IAChE,OAAO,6BAA6B,CAAA;AACtC,CAAC","sourcesContent":["import { IconString } from '../components/display/icon'\n\nexport type AttachmentPreviewKind = 'image' | 'video' | 'file'\n\n// Document picker providers sometimes hand back assets without a usable\n// MIME type (Android `MediaStore` content URIs, some cloud-storage\n// providers). The asset's `type` then arrives as `application/octet-stream`\n// or undefined even though the file is genuinely an image or video. Fall\n// back to extension-based classification in that case so the preview\n// matches the actual content.\nconst IMAGE_EXTENSIONS = new Set(['bmp', 'gif', 'heic', 'heif', 'jpeg', 'jpg', 'png', 'webp'])\n\nconst VIDEO_EXTENSIONS = new Set([\n '3gp',\n 'avi',\n 'h263',\n 'h264',\n 'm4v',\n 'mkv',\n 'mov',\n 'mp4',\n 'mpeg',\n 'mpeg4',\n 'mpg',\n 'webm',\n 'wmv',\n])\n\nexport function pickAttachmentPreviewKind(\n type: string | undefined,\n name?: string\n): AttachmentPreviewKind {\n if (type?.startsWith('image/')) return 'image'\n if (type?.startsWith('video/')) return 'video'\n const ext = name?.split('.').pop()?.toLowerCase()\n if (ext && IMAGE_EXTENSIONS.has(ext)) return 'image'\n if (ext && VIDEO_EXTENSIONS.has(ext)) return 'video'\n return 'file'\n}\n\nexport function getAttachmentIconName(type: string | undefined): IconString {\n if (type?.startsWith('image/')) return 'general.outlinedImageFile'\n if (type?.startsWith('video/')) return 'general.outlinedVideoFile'\n if (type?.startsWith('audio/')) return 'general.outlinedMusicFile'\n if (type === 'application/pdf') return 'general.outlinedPdfFile'\n return 'general.outlinedGenericFile'\n}\n"]}
@@ -10,5 +10,6 @@ export * from './reaction_constants';
10
10
  export * from './destructure_chat_group_graph_id';
11
11
  export * from './convert_attachments_for_create';
12
12
  export * from './assert_keys_are_numbers';
13
+ export * from './attachment_kind';
13
14
  export * from './system_messages';
14
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA;AACpC,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA;AAChD,cAAc,2BAA2B,CAAA;AACzC,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA;AACpC,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA;AAChD,cAAc,2BAA2B,CAAA;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA"}
@@ -10,5 +10,6 @@ export * from './reaction_constants';
10
10
  export * from './destructure_chat_group_graph_id';
11
11
  export * from './convert_attachments_for_create';
12
12
  export * from './assert_keys_are_numbers';
13
+ export * from './attachment_kind';
13
14
  export * from './system_messages';
14
15
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA;AACpC,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA;AAChD,cAAc,2BAA2B,CAAA;AACzC,cAAc,mBAAmB,CAAA","sourcesContent":["export * from './session'\nexport * from './theme'\nexport * from './styles'\nexport * from './client'\nexport * from './uri'\nexport * from './cache'\nexport * from './native_adapters'\nexport * from './pluralize'\nexport * from './reaction_constants'\nexport * from './destructure_chat_group_graph_id'\nexport * from './convert_attachments_for_create'\nexport * from './assert_keys_are_numbers'\nexport * from './system_messages'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA;AACpC,cAAc,mCAAmC,CAAA;AACjD,cAAc,kCAAkC,CAAA;AAChD,cAAc,2BAA2B,CAAA;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA","sourcesContent":["export * from './session'\nexport * from './theme'\nexport * from './styles'\nexport * from './client'\nexport * from './uri'\nexport * from './cache'\nexport * from './native_adapters'\nexport * from './pluralize'\nexport * from './reaction_constants'\nexport * from './destructure_chat_group_graph_id'\nexport * from './convert_attachments_for_create'\nexport * from './assert_keys_are_numbers'\nexport * from './attachment_kind'\nexport * from './system_messages'\n"]}
@@ -13,11 +13,14 @@ type DocumentPickerCanceledResult = {
13
13
  assets: null;
14
14
  };
15
15
  export type DocumentPickerResult = DocumentPickerSuccessResult | DocumentPickerCanceledResult;
16
+ export interface DocumentPickerOpenOptions {
17
+ mimeTypes?: string[];
18
+ }
16
19
  interface DocumentPicker {
17
- openAsync: () => Promise<DocumentPickerResult>;
20
+ openAsync: (options?: DocumentPickerOpenOptions) => Promise<DocumentPickerResult>;
18
21
  }
19
22
  export declare class DocumentPickerAdapter {
20
- openAsync: () => Promise<DocumentPickerResult>;
23
+ openAsync: (options?: DocumentPickerOpenOptions) => Promise<DocumentPickerResult>;
21
24
  configured: boolean;
22
25
  constructor(methods?: DocumentPicker);
23
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"document_picker.d.ts","sourceRoot":"","sources":["../../../src/utils/native_adapters/document_picker.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,KAAK,2BAA2B,GAAG;IACjC,QAAQ,EAAE,KAAK,CAAA;IACf,MAAM,EAAE,mBAAmB,EAAE,CAAA;CAC9B,CAAA;AAED,KAAK,4BAA4B,GAAG;IAClC,QAAQ,EAAE,IAAI,CAAA;IACd,MAAM,EAAE,IAAI,CAAA;CACb,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG,2BAA2B,GAAG,4BAA4B,CAAA;AAE7F,UAAU,cAAc;IACtB,SAAS,EAAE,MAAM,OAAO,CAAC,oBAAoB,CAAC,CAAA;CAC/C;AAED,qBAAa,qBAAqB;IAChC,SAAS,EAAE,MAAM,OAAO,CAAC,oBAAoB,CAAC,CAAA;IAC9C,UAAU,EAAE,OAAO,CAAA;gBAEP,OAAO,CAAC,EAAE,cAAc;CAIrC"}
1
+ {"version":3,"file":"document_picker.d.ts","sourceRoot":"","sources":["../../../src/utils/native_adapters/document_picker.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,KAAK,2BAA2B,GAAG;IACjC,QAAQ,EAAE,KAAK,CAAA;IACf,MAAM,EAAE,mBAAmB,EAAE,CAAA;CAC9B,CAAA;AAED,KAAK,4BAA4B,GAAG;IAClC,QAAQ,EAAE,IAAI,CAAA;IACd,MAAM,EAAE,IAAI,CAAA;CACb,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG,2BAA2B,GAAG,4BAA4B,CAAA;AAE7F,MAAM,WAAW,yBAAyB;IAIxC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,UAAU,cAAc;IACtB,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE,yBAAyB,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAA;CAClF;AAED,qBAAa,qBAAqB;IAChC,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE,yBAAyB,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAA;IACjF,UAAU,EAAE,OAAO,CAAA;gBAEP,OAAO,CAAC,EAAE,cAAc;CAIrC"}
@@ -1 +1 @@
1
- {"version":3,"file":"document_picker.js","sourceRoot":"","sources":["../../../src/utils/native_adapters/document_picker.ts"],"names":[],"mappings":"AAuBA,MAAM,OAAO,qBAAqB;IAChC,SAAS,CAAqC;IAC9C,UAAU,CAAS;IAEnB,YAAY,OAAwB;QAClC,IAAI,CAAC,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACvF,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,OAAO,CAAA;IAC7B,CAAC;CACF","sourcesContent":["export type DocumentPickerAsset = {\n uri: string\n name: string\n size?: number\n mimeType?: string\n}\n\ntype DocumentPickerSuccessResult = {\n canceled: false\n assets: DocumentPickerAsset[]\n}\n\ntype DocumentPickerCanceledResult = {\n canceled: true\n assets: null\n}\n\nexport type DocumentPickerResult = DocumentPickerSuccessResult | DocumentPickerCanceledResult\n\ninterface DocumentPicker {\n openAsync: () => Promise<DocumentPickerResult>\n}\n\nexport class DocumentPickerAdapter {\n openAsync: () => Promise<DocumentPickerResult>\n configured: boolean\n\n constructor(methods?: DocumentPicker) {\n this.openAsync = methods?.openAsync ?? (async () => ({ canceled: true, assets: null }))\n this.configured = !!methods\n }\n}\n"]}
1
+ {"version":3,"file":"document_picker.js","sourceRoot":"","sources":["../../../src/utils/native_adapters/document_picker.ts"],"names":[],"mappings":"AA8BA,MAAM,OAAO,qBAAqB;IAChC,SAAS,CAAwE;IACjF,UAAU,CAAS;IAEnB,YAAY,OAAwB;QAClC,IAAI,CAAC,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACvF,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,OAAO,CAAA;IAC7B,CAAC;CACF","sourcesContent":["export type DocumentPickerAsset = {\n uri: string\n name: string\n size?: number\n mimeType?: string\n}\n\ntype DocumentPickerSuccessResult = {\n canceled: false\n assets: DocumentPickerAsset[]\n}\n\ntype DocumentPickerCanceledResult = {\n canceled: true\n assets: null\n}\n\nexport type DocumentPickerResult = DocumentPickerSuccessResult | DocumentPickerCanceledResult\n\nexport interface DocumentPickerOpenOptions {\n // MIME types the picker should restrict to. Hosts forward to the\n // underlying picker's `type` argument. Omitted/empty means no\n // restriction (the picker behaves as before).\n mimeTypes?: string[]\n}\n\ninterface DocumentPicker {\n openAsync: (options?: DocumentPickerOpenOptions) => Promise<DocumentPickerResult>\n}\n\nexport class DocumentPickerAdapter {\n openAsync: (options?: DocumentPickerOpenOptions) => Promise<DocumentPickerResult>\n configured: boolean\n\n constructor(methods?: DocumentPicker) {\n this.openAsync = methods?.openAsync ?? (async () => ({ canceled: true, assets: null }))\n this.configured = !!methods\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/chat-react-native",
3
- "version": "3.34.0-rc.4",
3
+ "version": "3.34.0",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -65,5 +65,5 @@
65
65
  "react-native-url-polyfill": "^2.0.0",
66
66
  "typescript": "~5.9.2"
67
67
  },
68
- "gitHead": "1dc683fa83955c4b35ef5b98f060ba75a85b464e"
68
+ "gitHead": "819a3311af600662814f2c10095c4089deac5775"
69
69
  }
@@ -4,6 +4,7 @@ import React, { Suspense } from 'react'
4
4
  import { buildTestQueryClient } from '../../__utils__/query_client'
5
5
  import {
6
6
  FALLBACK_ALLOWED_FILE_EXTENSIONS,
7
+ FALLBACK_ALLOWED_MIME_TYPES,
7
8
  FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,
8
9
  FALLBACK_MAX_FILE_SIZE_IN_BYTES,
9
10
  } from '../../hooks/attachments/fallback_chat_configuration'
@@ -46,6 +47,7 @@ describe('useChatConfiguration', () => {
46
47
  type: 'ChatConfiguration',
47
48
  id: 'current',
48
49
  allowedFileExtensions: ['.pdf', '.jpg'],
50
+ allowedMimeTypes: ['application/pdf', 'image/jpeg'],
49
51
  maxFileSizeInBytes: 1000,
50
52
  maxAttachmentsPerMessage: 3,
51
53
  },
@@ -59,11 +61,34 @@ describe('useChatConfiguration', () => {
59
61
 
60
62
  expect(result.current).toEqual({
61
63
  allowedFileExtensions: ['.pdf', '.jpg'],
64
+ allowedMimeTypes: ['application/pdf', 'image/jpeg'],
62
65
  maxFileSizeInBytes: 1000,
63
66
  maxAttachmentsPerMessage: 3,
64
67
  })
65
68
  })
66
69
 
70
+ it('falls back to FALLBACK_ALLOWED_MIME_TYPES when the server omits the field', async () => {
71
+ // During server-side rollout the field may be absent from the response.
72
+ mockApiClient(() =>
73
+ Promise.resolve({
74
+ data: {
75
+ type: 'ChatConfiguration',
76
+ id: 'current',
77
+ allowedFileExtensions: ['.pdf'],
78
+ maxFileSizeInBytes: 1000,
79
+ maxAttachmentsPerMessage: 3,
80
+ },
81
+ links: {},
82
+ meta: {},
83
+ })
84
+ )
85
+
86
+ const { result } = renderHook(() => useChatConfiguration(), { wrapper: createWrapper() })
87
+ await waitForQuery()
88
+
89
+ expect(result.current.allowedMimeTypes).toEqual(FALLBACK_ALLOWED_MIME_TYPES)
90
+ })
91
+
67
92
  it('returns fallback values when the API rejects', async () => {
68
93
  mockApiClient(() => Promise.reject(new Error('boom')))
69
94
 
@@ -72,6 +97,7 @@ describe('useChatConfiguration', () => {
72
97
 
73
98
  expect(result.current).toEqual({
74
99
  allowedFileExtensions: FALLBACK_ALLOWED_FILE_EXTENSIONS,
100
+ allowedMimeTypes: FALLBACK_ALLOWED_MIME_TYPES,
75
101
  maxFileSizeInBytes: FALLBACK_MAX_FILE_SIZE_IN_BYTES,
76
102
  maxAttachmentsPerMessage: FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,
77
103
  })
@@ -3,7 +3,7 @@ import React from 'react'
3
3
  import { StyleSheet, View } from 'react-native'
4
4
  import { useTheme } from '../../../hooks'
5
5
  import { DenormalizedMessageAttachmentResource } from '../../../types/resources/denormalized_attachment_resource'
6
- import { Linking } from '../../../utils'
6
+ import { Linking, getAttachmentIconName } from '../../../utils'
7
7
  import { tokens } from '../../../vendor/tapestry/tokens'
8
8
  import { Icon } from '../../display'
9
9
  import { AttachmentCard, AttachmentCardTitle } from './attachment_card'
@@ -75,16 +75,3 @@ const useStyles = () => {
75
75
  },
76
76
  })
77
77
  }
78
-
79
- export function getAttachmentIconName(type: string) {
80
- const isImage = type.startsWith('image/')
81
- const isVideo = type.startsWith('video/')
82
- const isAudio = type.startsWith('audio/')
83
- const isPdf = type === 'application/pdf'
84
-
85
- if (isImage) return 'general.outlinedImageFile'
86
- if (isVideo) return 'general.outlinedVideoFile'
87
- if (isAudio) return 'general.outlinedMusicFile'
88
- if (isPdf) return 'general.outlinedPdfFile'
89
- return 'general.outlinedGenericFile'
90
- }
@@ -0,0 +1,26 @@
1
+ import React from 'react'
2
+ import { FileAttachment } from '../../../types/resources/denormalized_attachment_resource_for_create'
3
+ import { FileAttachmentPreview } from '../../display'
4
+
5
+ interface Props {
6
+ status: FileAttachment['status']
7
+ name: string
8
+ contentType?: string
9
+ removeAttachment: () => void
10
+ }
11
+
12
+ export function MessageFormAttachmentFile({ name, contentType, status, removeAttachment }: Props) {
13
+ const loading = status === 'uploading'
14
+ const error = status === 'error'
15
+
16
+ return (
17
+ <FileAttachmentPreview
18
+ name={name}
19
+ contentType={contentType}
20
+ onRemovePress={removeAttachment}
21
+ loading={loading}
22
+ error={error}
23
+ hideRemoveButton={loading}
24
+ />
25
+ )
26
+ }
@@ -11,7 +11,6 @@ interface Props {
11
11
  export function MessageFormAttachmentVideo({ name, status, removeAttachment }: Props) {
12
12
  const loading = status === 'uploading'
13
13
  const error = status === 'error'
14
- const ready = status === 'success'
15
14
 
16
15
  return (
17
16
  <VideoAttachmentPreview
@@ -19,7 +18,7 @@ export function MessageFormAttachmentVideo({ name, status, removeAttachment }: P
19
18
  onRemovePress={removeAttachment}
20
19
  loading={loading}
21
20
  error={error}
22
- hideRemoveButton={!ready}
21
+ hideRemoveButton={loading}
23
22
  />
24
23
  )
25
24
  }
@@ -23,6 +23,7 @@ import {
23
23
  } from '../../hooks'
24
24
  import { useAttachmentUploader } from '../../hooks/use_attachment_uploader'
25
25
  import { useBroadcastTypingStatus } from '../../hooks/use_broadcast_typing_status'
26
+ import { useChatConfiguration } from '../../hooks/use_chat_configuration'
26
27
  import { useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'
27
28
  import { useMessageDraft } from '../../hooks/use_message_draft'
28
29
  import { ConversationScreenProps } from '../../screens/conversation_screen'
@@ -36,6 +37,7 @@ import {
36
37
  platformFontWeightMedium,
37
38
  platformPressedOpacityStyle,
38
39
  } from '../../utils'
40
+ import { pickAttachmentPreviewKind } from '../../utils/attachment_kind'
39
41
  import {
40
42
  DocumentPicker,
41
43
  DocumentPickerResult,
@@ -46,6 +48,7 @@ import {
46
48
  import { tokens } from '../../vendor/tapestry/tokens'
47
49
  import { Button } from '../display/button'
48
50
  import BannerPrimitive from '../primitive/banner_primitive'
51
+ import { MessageFormAttachmentFile } from './message_form/message_form_attachment_file'
49
52
  import { MessageFormAttachmentImage } from './message_form/message_form_attachment_image'
50
53
  import { MessageFormAttachmentVideo } from './message_form/message_form_attachment_video'
51
54
 
@@ -286,15 +289,28 @@ function MessageFormAttachments() {
286
289
  contentContainerStyle={styles.messageFormAttachments}
287
290
  >
288
291
  {attachments.map(attachment => {
289
- if (attachment.file.type.startsWith('video/')) {
292
+ const kind = pickAttachmentPreviewKind(attachment.file.type, attachment.file.name)
293
+ const remove = () => attachmentUploader?.removeAttachment(attachment)
294
+
295
+ if (kind === 'video') {
290
296
  return (
291
297
  <MessageFormAttachmentVideo
292
298
  key={attachment.file.uri}
293
299
  name={attachment.file.name}
294
300
  status={attachment.status}
295
- removeAttachment={() => {
296
- attachmentUploader?.removeAttachment(attachment)
297
- }}
301
+ removeAttachment={remove}
302
+ />
303
+ )
304
+ }
305
+
306
+ if (kind === 'file') {
307
+ return (
308
+ <MessageFormAttachmentFile
309
+ key={attachment.file.uri}
310
+ name={attachment.file.name}
311
+ contentType={attachment.file.type}
312
+ status={attachment.status}
313
+ removeAttachment={remove}
298
314
  />
299
315
  )
300
316
  }
@@ -307,9 +323,7 @@ function MessageFormAttachments() {
307
323
  status={attachment.status}
308
324
  width={attachment.file.width}
309
325
  height={attachment.file.height}
310
- removeAttachment={() => {
311
- attachmentUploader?.removeAttachment(attachment)
312
- }}
326
+ removeAttachment={remove}
313
327
  />
314
328
  )
315
329
  })}
@@ -503,6 +517,7 @@ function MessageFormAttachmentPicker() {
503
517
  const styles = useMessageFormStyles()
504
518
  const { usingGiphy, attachmentUploader, currentlyEditingMessage } =
505
519
  React.useContext(MessageFormContext)
520
+ const { allowedMimeTypes } = useChatConfiguration()
506
521
  const [isOpen, setIsOpen] = useState(false)
507
522
 
508
523
  function uploadImagePickerResult(result: ImagePickerResult) {
@@ -561,7 +576,7 @@ function MessageFormAttachmentPicker() {
561
576
 
562
577
  const pickFile = async () => {
563
578
  setIsOpen(false)
564
- let result = await DocumentPicker.openAsync()
579
+ let result = await DocumentPicker.openAsync({ mimeTypes: allowedMimeTypes })
565
580
  if (!result.canceled) {
566
581
  uploadDocumentPickerResult(result)
567
582
  }