@planningcenter/chat-react-native 3.33.1 → 3.33.2-qa-664.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.
- package/build/components/conversation/attachments/generic_file_attachment.d.ts +0 -1
- package/build/components/conversation/attachments/generic_file_attachment.d.ts.map +1 -1
- package/build/components/conversation/attachments/generic_file_attachment.js +1 -16
- package/build/components/conversation/attachments/generic_file_attachment.js.map +1 -1
- package/build/components/conversation/message_form/message_form_attachment_file.d.ts +11 -0
- package/build/components/conversation/message_form/message_form_attachment_file.d.ts.map +1 -0
- package/build/components/conversation/message_form/message_form_attachment_file.js +9 -0
- package/build/components/conversation/message_form/message_form_attachment_file.js.map +1 -0
- package/build/components/conversation/message_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +13 -8
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/display/file_attachment_preview.d.ts +11 -0
- package/build/components/display/file_attachment_preview.d.ts.map +1 -0
- package/build/components/display/file_attachment_preview.js +95 -0
- package/build/components/display/file_attachment_preview.js.map +1 -0
- package/build/components/display/image.d.ts.map +1 -1
- package/build/components/display/image.js +4 -1
- package/build/components/display/image.js.map +1 -1
- package/build/components/display/index.d.ts +1 -0
- package/build/components/display/index.d.ts.map +1 -1
- package/build/components/display/index.js +1 -0
- package/build/components/display/index.js.map +1 -1
- package/build/hooks/attachments/fallback_chat_configuration.d.ts +1 -0
- package/build/hooks/attachments/fallback_chat_configuration.d.ts.map +1 -1
- package/build/hooks/attachments/fallback_chat_configuration.js +23 -0
- package/build/hooks/attachments/fallback_chat_configuration.js.map +1 -1
- package/build/hooks/paginator_meta.d.ts +4 -0
- package/build/hooks/paginator_meta.d.ts.map +1 -0
- package/build/hooks/paginator_meta.js +14 -0
- package/build/hooks/paginator_meta.js.map +1 -0
- package/build/hooks/use_api.d.ts.map +1 -1
- package/build/hooks/use_api.js +2 -9
- package/build/hooks/use_api.js.map +1 -1
- package/build/hooks/use_chat_configuration.d.ts +1 -0
- package/build/hooks/use_chat_configuration.d.ts.map +1 -1
- package/build/hooks/use_chat_configuration.js +3 -1
- package/build/hooks/use_chat_configuration.js.map +1 -1
- package/build/hooks/use_suspense_api.d.ts.map +1 -1
- package/build/hooks/use_suspense_api.js +2 -9
- package/build/hooks/use_suspense_api.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/screens/avatar_picker/emoji_tab.d.ts.map +1 -1
- package/build/screens/avatar_picker/emoji_tab.js +10 -1
- package/build/screens/avatar_picker/emoji_tab.js.map +1 -1
- package/build/types/api_primitives.d.ts +8 -1
- package/build/types/api_primitives.d.ts.map +1 -1
- package/build/types/api_primitives.js.map +1 -1
- package/build/types/resources/chat_configuration_resource.d.ts +1 -0
- package/build/types/resources/chat_configuration_resource.d.ts.map +1 -1
- package/build/types/resources/chat_configuration_resource.js.map +1 -1
- package/build/utils/attachment_kind.d.ts +5 -0
- package/build/utils/attachment_kind.d.ts.map +1 -0
- package/build/utils/attachment_kind.js +19 -0
- package/build/utils/attachment_kind.js.map +1 -0
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +1 -0
- package/build/utils/index.js.map +1 -1
- package/build/utils/native_adapters/document_picker.d.ts +5 -2
- package/build/utils/native_adapters/document_picker.d.ts.map +1 -1
- package/build/utils/native_adapters/document_picker.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/contexts/session_context.tsx +3 -8
- package/src/__tests__/hooks/paginator_meta.test.ts +15 -0
- package/src/__tests__/hooks/useTheme.tsx +3 -3
- package/src/__tests__/hooks/use_async_storage.test.tsx +3 -8
- package/src/__tests__/hooks/use_attachment_uploader.test.tsx +3 -2
- package/src/__tests__/hooks/use_chat_configuration.test.tsx +29 -4
- package/src/__utils__/query_client.ts +14 -0
- package/src/components/conversation/attachments/generic_file_attachment.tsx +1 -14
- package/src/components/conversation/message_form/message_form_attachment_file.tsx +27 -0
- package/src/components/conversation/message_form.tsx +23 -8
- package/src/components/display/file_attachment_preview.tsx +135 -0
- package/src/components/display/image.tsx +5 -0
- package/src/components/display/index.ts +1 -0
- package/src/hooks/attachments/fallback_chat_configuration.ts +24 -0
- package/src/hooks/paginator_meta.ts +13 -0
- package/src/hooks/use_api.ts +2 -14
- package/src/hooks/use_chat_configuration.ts +3 -0
- package/src/hooks/use_suspense_api.ts +2 -14
- package/src/index.tsx +1 -0
- package/src/screens/avatar_picker/emoji_tab.tsx +13 -1
- package/src/types/api_primitives.ts +9 -1
- package/src/types/resources/chat_configuration_resource.ts +4 -0
- package/src/utils/__tests__/attachment_kind.test.ts +18 -0
- package/src/utils/attachment_kind.ts +17 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/native_adapters/document_picker.ts +9 -2
|
@@ -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,
|
|
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":"
|
|
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.33.
|
|
3
|
+
"version": "3.33.2-qa-664.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@fortawesome/react-native-fontawesome": "^0.3.2",
|
|
28
28
|
"lodash-inflection": "^1.5.0",
|
|
29
29
|
"react-compiler-runtime": "^1.0.0",
|
|
30
|
-
"rn-emoji-keyboard": "
|
|
30
|
+
"rn-emoji-keyboard": "1.7.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@planningcenter/datetime-fmt": ">=2.0.0",
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"react-native-url-polyfill": "^2.0.0",
|
|
66
66
|
"typescript": "~5.9.2"
|
|
67
67
|
},
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "3d49765002aad8e3cad75a801e1762bff300267d"
|
|
69
69
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
2
|
-
import {
|
|
2
|
+
import { QueryClientProvider } from '@tanstack/react-query'
|
|
3
3
|
import { renderHook, act } from '@testing-library/react-hooks'
|
|
4
4
|
import React, { useContext, Suspense } from 'react'
|
|
5
|
+
import { buildTestQueryClient } from '../../__utils__/query_client'
|
|
5
6
|
import {
|
|
6
7
|
SessionContext,
|
|
7
8
|
SessionContextProvider,
|
|
@@ -67,13 +68,7 @@ const waitForQuery = async () => {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
const createWrapper = (config: SessionContextConfig) => {
|
|
70
|
-
const queryClient =
|
|
71
|
-
defaultOptions: {
|
|
72
|
-
queries: {
|
|
73
|
-
retry: false,
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
})
|
|
71
|
+
const queryClient = buildTestQueryClient()
|
|
77
72
|
|
|
78
73
|
return ({ children }: { children: React.ReactNode }) => (
|
|
79
74
|
<QueryClientProvider client={queryClient}>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getNextPageParamFromMeta } from '../../hooks/paginator_meta'
|
|
2
|
+
|
|
3
|
+
describe('getNextPageParamFromMeta', () => {
|
|
4
|
+
it.each([
|
|
5
|
+
[undefined, undefined],
|
|
6
|
+
[{}, undefined],
|
|
7
|
+
[{ idLt: '01ABC' }, { where: { id_lt: '01ABC' } }],
|
|
8
|
+
[{ idLte: '01ABC' }, { where: { id_lte: '01ABC' } }],
|
|
9
|
+
[{ idGt: '01ABC' }, { where: { id_gt: '01ABC' } }],
|
|
10
|
+
[{ idGte: '01ABC' }, { where: { id_gte: '01ABC' } }],
|
|
11
|
+
[{ offset: '40' }, { offset: 40 }],
|
|
12
|
+
])('maps %p to %p', (input, expected) => {
|
|
13
|
+
expect(getNextPageParamFromMeta(input)).toEqual(expected)
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
2
2
|
import { renderHook } from '@testing-library/react-native'
|
|
3
3
|
import React from 'react'
|
|
4
|
+
import { buildTestQueryClient } from '../../__utils__/query_client'
|
|
4
5
|
import { ChatProvider, CreateChatThemeProps } from '../../contexts/chat_context'
|
|
5
6
|
import { useTheme } from '../../hooks'
|
|
6
7
|
|
|
@@ -14,11 +15,10 @@ let themeProps: CreateChatThemeProps = {
|
|
|
14
15
|
let queryClient: QueryClient
|
|
15
16
|
|
|
16
17
|
beforeEach(() => {
|
|
17
|
-
queryClient =
|
|
18
|
+
queryClient = buildTestQueryClient({
|
|
18
19
|
defaultOptions: {
|
|
19
20
|
queries: {
|
|
20
|
-
|
|
21
|
-
refetchInterval: false, // Disable refetch intervals in tests
|
|
21
|
+
refetchInterval: false,
|
|
22
22
|
refetchOnWindowFocus: false,
|
|
23
23
|
refetchOnReconnect: false,
|
|
24
24
|
},
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
2
|
-
import {
|
|
2
|
+
import { QueryClientProvider } from '@tanstack/react-query'
|
|
3
3
|
import { renderHook, act } from '@testing-library/react-hooks'
|
|
4
4
|
import React, { Suspense } from 'react'
|
|
5
|
+
import { buildTestQueryClient } from '../../__utils__/query_client'
|
|
5
6
|
import { useAsyncStorage } from '../../hooks/use_async_storage'
|
|
6
7
|
|
|
7
8
|
jest.mock('@react-native-async-storage/async-storage')
|
|
@@ -14,13 +15,7 @@ afterAll(() => {
|
|
|
14
15
|
const mockAsyncStorage = AsyncStorage as jest.Mocked<typeof AsyncStorage>
|
|
15
16
|
|
|
16
17
|
const createWrapper = () => {
|
|
17
|
-
const queryClient =
|
|
18
|
-
defaultOptions: {
|
|
19
|
-
queries: {
|
|
20
|
-
retry: false,
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
})
|
|
18
|
+
const queryClient = buildTestQueryClient()
|
|
24
19
|
|
|
25
20
|
return ({ children }: { children: React.ReactNode }) => (
|
|
26
21
|
<QueryClientProvider client={queryClient}>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { QueryClientProvider } from '@tanstack/react-query'
|
|
2
2
|
import { renderHook, act } from '@testing-library/react-hooks'
|
|
3
3
|
import React from 'react'
|
|
4
|
+
import { buildTestQueryClient } from '../../__utils__/query_client'
|
|
4
5
|
import { useApiClient } from '../../hooks/use_api_client'
|
|
5
6
|
import { useAttachmentUploader } from '../../hooks/use_attachment_uploader'
|
|
6
7
|
import { useChatConfiguration } from '../../hooks/use_chat_configuration'
|
|
@@ -18,7 +19,7 @@ const mockedUseChatConfiguration = useChatConfiguration as jest.MockedFunction<
|
|
|
18
19
|
>
|
|
19
20
|
|
|
20
21
|
const createWrapper = () => {
|
|
21
|
-
const queryClient =
|
|
22
|
+
const queryClient = buildTestQueryClient()
|
|
22
23
|
return ({ children }: { children: React.ReactNode }) => (
|
|
23
24
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
24
25
|
)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { QueryClientProvider } from '@tanstack/react-query'
|
|
2
2
|
import { renderHook, act } from '@testing-library/react-hooks'
|
|
3
3
|
import React, { Suspense } from 'react'
|
|
4
|
+
import { buildTestQueryClient } from '../../__utils__/query_client'
|
|
4
5
|
import {
|
|
5
6
|
FALLBACK_ALLOWED_FILE_EXTENSIONS,
|
|
7
|
+
FALLBACK_ALLOWED_MIME_TYPES,
|
|
6
8
|
FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,
|
|
7
9
|
FALLBACK_MAX_FILE_SIZE_IN_BYTES,
|
|
8
10
|
} from '../../hooks/attachments/fallback_chat_configuration'
|
|
@@ -10,9 +12,7 @@ import * as useApiClientModule from '../../hooks/use_api_client'
|
|
|
10
12
|
import { useChatConfiguration } from '../../hooks/use_chat_configuration'
|
|
11
13
|
|
|
12
14
|
const createWrapper = () => {
|
|
13
|
-
const queryClient =
|
|
14
|
-
defaultOptions: { queries: { retry: false } },
|
|
15
|
-
})
|
|
15
|
+
const queryClient = buildTestQueryClient()
|
|
16
16
|
|
|
17
17
|
return ({ children }: { children: React.ReactNode }) => (
|
|
18
18
|
<QueryClientProvider client={queryClient}>
|
|
@@ -47,6 +47,7 @@ describe('useChatConfiguration', () => {
|
|
|
47
47
|
type: 'ChatConfiguration',
|
|
48
48
|
id: 'current',
|
|
49
49
|
allowedFileExtensions: ['.pdf', '.jpg'],
|
|
50
|
+
allowedMimeTypes: ['application/pdf', 'image/jpeg'],
|
|
50
51
|
maxFileSizeInBytes: 1000,
|
|
51
52
|
maxAttachmentsPerMessage: 3,
|
|
52
53
|
},
|
|
@@ -60,11 +61,34 @@ describe('useChatConfiguration', () => {
|
|
|
60
61
|
|
|
61
62
|
expect(result.current).toEqual({
|
|
62
63
|
allowedFileExtensions: ['.pdf', '.jpg'],
|
|
64
|
+
allowedMimeTypes: ['application/pdf', 'image/jpeg'],
|
|
63
65
|
maxFileSizeInBytes: 1000,
|
|
64
66
|
maxAttachmentsPerMessage: 3,
|
|
65
67
|
})
|
|
66
68
|
})
|
|
67
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
|
+
|
|
68
92
|
it('returns fallback values when the API rejects', async () => {
|
|
69
93
|
mockApiClient(() => Promise.reject(new Error('boom')))
|
|
70
94
|
|
|
@@ -73,6 +97,7 @@ describe('useChatConfiguration', () => {
|
|
|
73
97
|
|
|
74
98
|
expect(result.current).toEqual({
|
|
75
99
|
allowedFileExtensions: FALLBACK_ALLOWED_FILE_EXTENSIONS,
|
|
100
|
+
allowedMimeTypes: FALLBACK_ALLOWED_MIME_TYPES,
|
|
76
101
|
maxFileSizeInBytes: FALLBACK_MAX_FILE_SIZE_IN_BYTES,
|
|
77
102
|
maxAttachmentsPerMessage: FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,
|
|
78
103
|
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { QueryClient, QueryClientConfig } from '@tanstack/react-query'
|
|
2
|
+
import { merge } from 'lodash'
|
|
3
|
+
|
|
4
|
+
const baseConfig: QueryClientConfig = {
|
|
5
|
+
defaultOptions: {
|
|
6
|
+
queries: {
|
|
7
|
+
retry: false,
|
|
8
|
+
gcTime: 0,
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const buildTestQueryClient = (overrides: QueryClientConfig = {}) =>
|
|
14
|
+
new QueryClient(merge({}, baseConfig, overrides))
|
|
@@ -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,27 @@
|
|
|
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
|
+
const ready = status === 'success'
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<FileAttachmentPreview
|
|
19
|
+
name={name}
|
|
20
|
+
contentType={contentType}
|
|
21
|
+
onRemovePress={removeAttachment}
|
|
22
|
+
loading={loading}
|
|
23
|
+
error={error}
|
|
24
|
+
hideRemoveButton={!ready}
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -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
|
-
|
|
292
|
+
const kind = pickAttachmentPreviewKind(attachment.file.type)
|
|
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
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { View, StyleSheet } from 'react-native'
|
|
2
|
+
import { useTheme } from '../../hooks'
|
|
3
|
+
import { platformFontWeightMedium } from '../../utils'
|
|
4
|
+
import { getAttachmentIconName } from '../../utils/attachment_kind'
|
|
5
|
+
import { tokens } from '../../vendor/tapestry/tokens'
|
|
6
|
+
import { Icon } from './icon'
|
|
7
|
+
import { IconButton } from './icon_button'
|
|
8
|
+
import { Spinner } from './spinner'
|
|
9
|
+
import { Text } from './text'
|
|
10
|
+
|
|
11
|
+
interface FileAttachmentPreviewProps {
|
|
12
|
+
name: string
|
|
13
|
+
contentType?: string
|
|
14
|
+
onRemovePress: () => void
|
|
15
|
+
loading?: boolean
|
|
16
|
+
error?: boolean
|
|
17
|
+
hideRemoveButton?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const FileAttachmentPreview = ({
|
|
21
|
+
name,
|
|
22
|
+
contentType,
|
|
23
|
+
onRemovePress,
|
|
24
|
+
loading = false,
|
|
25
|
+
error = false,
|
|
26
|
+
hideRemoveButton = false,
|
|
27
|
+
}: FileAttachmentPreviewProps) => {
|
|
28
|
+
const styles = useStyles({ error })
|
|
29
|
+
|
|
30
|
+
if (loading) {
|
|
31
|
+
return (
|
|
32
|
+
<View style={styles.container}>
|
|
33
|
+
<Spinner size={20} style={styles.spinner} />
|
|
34
|
+
</View>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<View style={styles.container}>
|
|
40
|
+
<View style={styles.contentContainer}>
|
|
41
|
+
<Icon name={getAttachmentIconName(contentType)} size={18} style={styles.fileIcon} />
|
|
42
|
+
<View style={styles.textContainer}>
|
|
43
|
+
<Text
|
|
44
|
+
variant="tertiary"
|
|
45
|
+
numberOfLines={1}
|
|
46
|
+
style={styles.nameText}
|
|
47
|
+
accessibilityLabel={`File attachment: ${name}`}
|
|
48
|
+
>
|
|
49
|
+
{name}
|
|
50
|
+
</Text>
|
|
51
|
+
</View>
|
|
52
|
+
</View>
|
|
53
|
+
{!hideRemoveButton && (
|
|
54
|
+
<IconButton
|
|
55
|
+
name="general.x"
|
|
56
|
+
onPress={onRemovePress}
|
|
57
|
+
size="xxs"
|
|
58
|
+
appearance="neutral"
|
|
59
|
+
style={styles.closeButton}
|
|
60
|
+
accessibilityLabel="Remove file attachment"
|
|
61
|
+
/>
|
|
62
|
+
)}
|
|
63
|
+
{error && (
|
|
64
|
+
<View style={styles.errorBadge}>
|
|
65
|
+
<Icon name="general.exclamationTriangle" size={12} style={styles.errorIcon} />
|
|
66
|
+
</View>
|
|
67
|
+
)}
|
|
68
|
+
</View>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const useStyles = ({ error }: Partial<FileAttachmentPreviewProps>) => {
|
|
73
|
+
const { colors } = useTheme()
|
|
74
|
+
const borderRadius = 8
|
|
75
|
+
|
|
76
|
+
return StyleSheet.create({
|
|
77
|
+
container: {
|
|
78
|
+
height: 60,
|
|
79
|
+
minWidth: 150,
|
|
80
|
+
flexDirection: 'row',
|
|
81
|
+
justifyContent: 'space-between',
|
|
82
|
+
alignItems: 'center',
|
|
83
|
+
gap: 8,
|
|
84
|
+
backgroundColor: colors.fillColorNeutral070,
|
|
85
|
+
borderColor: error ? colors.statusErrorBorder : colors.borderColorDefaultBase,
|
|
86
|
+
borderWidth: error ? 2 : 1,
|
|
87
|
+
borderRadius,
|
|
88
|
+
padding: 4,
|
|
89
|
+
},
|
|
90
|
+
contentContainer: {
|
|
91
|
+
flexDirection: 'row',
|
|
92
|
+
gap: 8,
|
|
93
|
+
alignItems: 'center',
|
|
94
|
+
flexShrink: 1,
|
|
95
|
+
paddingHorizontal: 8,
|
|
96
|
+
paddingVertical: 4,
|
|
97
|
+
},
|
|
98
|
+
textContainer: {
|
|
99
|
+
flexShrink: 1,
|
|
100
|
+
flexDirection: 'column',
|
|
101
|
+
},
|
|
102
|
+
fileIcon: {
|
|
103
|
+
color: error ? colors.iconColorDefaultDisabled : colors.iconColorDefaultPrimary,
|
|
104
|
+
},
|
|
105
|
+
nameText: {
|
|
106
|
+
color: error ? colors.textColorDefaultDisabled : colors.textColorDefaultPrimary,
|
|
107
|
+
fontWeight: platformFontWeightMedium,
|
|
108
|
+
flexShrink: 1,
|
|
109
|
+
},
|
|
110
|
+
closeButton: {
|
|
111
|
+
backgroundColor: colors.fillColorNeutral050Base,
|
|
112
|
+
borderRadius: 16,
|
|
113
|
+
height: 20,
|
|
114
|
+
width: 20,
|
|
115
|
+
alignSelf: 'flex-start',
|
|
116
|
+
},
|
|
117
|
+
errorBadge: {
|
|
118
|
+
backgroundColor: colors.statusErrorBorder,
|
|
119
|
+
position: 'absolute',
|
|
120
|
+
bottom: 0,
|
|
121
|
+
right: 0,
|
|
122
|
+
zIndex: 2,
|
|
123
|
+
borderStartStartRadius: borderRadius,
|
|
124
|
+
padding: 4,
|
|
125
|
+
},
|
|
126
|
+
errorIcon: {
|
|
127
|
+
color: tokens.colorNeutral100White,
|
|
128
|
+
transform: [{ translateX: 1 }],
|
|
129
|
+
fontSize: 10,
|
|
130
|
+
},
|
|
131
|
+
spinner: {
|
|
132
|
+
marginHorizontal: 'auto',
|
|
133
|
+
},
|
|
134
|
+
})
|
|
135
|
+
}
|
|
@@ -79,6 +79,10 @@ export function Image({
|
|
|
79
79
|
onLoad?.(event)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
const handleOnError = () => {
|
|
83
|
+
setIsImageLoading(false)
|
|
84
|
+
}
|
|
85
|
+
|
|
82
86
|
const isLoading = isImageLoading || loading
|
|
83
87
|
|
|
84
88
|
const ImageComponent = animatedImageStyle ? Animated.Image : ReactNativeImage
|
|
@@ -94,6 +98,7 @@ export function Image({
|
|
|
94
98
|
<ImageComponent
|
|
95
99
|
style={[styles.image, imageStyles, animatedImageStyle]}
|
|
96
100
|
onLoad={handleOnLoad}
|
|
101
|
+
onError={handleOnError}
|
|
97
102
|
source={source}
|
|
98
103
|
alt={isLoading ? '' : alt}
|
|
99
104
|
{...props}
|
|
@@ -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'
|
|
@@ -56,6 +56,30 @@ export const FALLBACK_ALLOWED_FILE_EXTENSIONS = [
|
|
|
56
56
|
'.xlsx',
|
|
57
57
|
]
|
|
58
58
|
|
|
59
|
+
// Broad MIME categories covering the extensions in
|
|
60
|
+
// FALLBACK_ALLOWED_FILE_EXTENSIONS. Used to constrain native pickers up
|
|
61
|
+
// front; final accept/reject still uses the extension list because the
|
|
62
|
+
// picker's `type` filter is advisory on iOS and Android.
|
|
63
|
+
export const FALLBACK_ALLOWED_MIME_TYPES = [
|
|
64
|
+
'image/*',
|
|
65
|
+
'video/*',
|
|
66
|
+
'audio/*',
|
|
67
|
+
'application/pdf',
|
|
68
|
+
'application/msword',
|
|
69
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
70
|
+
'application/vnd.ms-excel',
|
|
71
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
72
|
+
'application/vnd.ms-powerpoint',
|
|
73
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
74
|
+
'application/vnd.apple.pages',
|
|
75
|
+
'application/vnd.apple.numbers',
|
|
76
|
+
'application/vnd.apple.keynote',
|
|
77
|
+
'application/rtf',
|
|
78
|
+
'text/plain',
|
|
79
|
+
'text/rtf',
|
|
80
|
+
'text/vcard',
|
|
81
|
+
]
|
|
82
|
+
|
|
59
83
|
export const FALLBACK_MAX_FILE_SIZE_IN_BYTES = 50 * 1024 * 1024
|
|
60
84
|
|
|
61
85
|
export const FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE = 10
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NextPageCursor } from '../types/api_primitives'
|
|
2
|
+
import { RequestData } from '../utils/client'
|
|
3
|
+
|
|
4
|
+
export const getNextPageParamFromMeta = (
|
|
5
|
+
next: NextPageCursor = {}
|
|
6
|
+
): Partial<RequestData> | undefined => {
|
|
7
|
+
if (next.idLt) return { where: { id_lt: next.idLt } }
|
|
8
|
+
if (next.idGt) return { where: { id_gt: next.idGt } }
|
|
9
|
+
if (next.idLte) return { where: { id_lte: next.idLte } }
|
|
10
|
+
if (next.idGte) return { where: { id_gte: next.idGte } }
|
|
11
|
+
if (next.offset) return { offset: Number(next.offset) }
|
|
12
|
+
return undefined
|
|
13
|
+
}
|
package/src/hooks/use_api.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from '@tanstack/react-query'
|
|
7
7
|
import { ApiCollection, ApiResource, FailedResponse, ResourceObject } from '../types'
|
|
8
8
|
import { GetRequest, RequestData } from '../utils/client'
|
|
9
|
+
import { getNextPageParamFromMeta } from './paginator_meta'
|
|
9
10
|
import { App, useApiClient } from './use_api_client'
|
|
10
11
|
import { getRequestQueryKey, RequestQueryKey } from './use_suspense_api'
|
|
11
12
|
|
|
@@ -31,11 +32,6 @@ export const useApiGet = <T extends ResourceObject | ResourceObject[]>(args: Api
|
|
|
31
32
|
return { ...data, ...query }
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
type NextMeta = Partial<{
|
|
35
|
-
offset: string
|
|
36
|
-
idLt: string
|
|
37
|
-
}>
|
|
38
|
-
|
|
39
35
|
export type PaginatorOptions = Omit<
|
|
40
36
|
AnyUseSuspenseInfiniteQueryOptions,
|
|
41
37
|
'getNextPageParam' | 'initialPageParam' | 'queryFn' | 'queryKey'
|
|
@@ -68,15 +64,7 @@ export const useApiPaginator = <T extends ResourceObject>(
|
|
|
68
64
|
})
|
|
69
65
|
},
|
|
70
66
|
initialPageParam: {} as Partial<RequestData>,
|
|
71
|
-
getNextPageParam: lastPage =>
|
|
72
|
-
const next: NextMeta = lastPage.meta?.next || {}
|
|
73
|
-
const { offset, idLt } = next
|
|
74
|
-
|
|
75
|
-
if (idLt) return { where: { id_lt: idLt } }
|
|
76
|
-
if (offset) return { offset: Number(offset) }
|
|
77
|
-
|
|
78
|
-
return undefined
|
|
79
|
-
},
|
|
67
|
+
getNextPageParam: lastPage => getNextPageParamFromMeta(lastPage.meta?.next),
|
|
80
68
|
enabled: args.enabled,
|
|
81
69
|
...(opts || {}),
|
|
82
70
|
})
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from '../utils/request/get_chat_configuration'
|
|
8
8
|
import {
|
|
9
9
|
FALLBACK_ALLOWED_FILE_EXTENSIONS,
|
|
10
|
+
FALLBACK_ALLOWED_MIME_TYPES,
|
|
10
11
|
FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,
|
|
11
12
|
FALLBACK_MAX_FILE_SIZE_IN_BYTES,
|
|
12
13
|
} from './attachments/fallback_chat_configuration'
|
|
@@ -34,6 +35,7 @@ export function useChatConfiguration() {
|
|
|
34
35
|
|
|
35
36
|
return {
|
|
36
37
|
allowedFileExtensions: attrs.allowedFileExtensions,
|
|
38
|
+
allowedMimeTypes: attrs.allowedMimeTypes ?? FALLBACK_ALLOWED_MIME_TYPES,
|
|
37
39
|
maxFileSizeInBytes: attrs.maxFileSizeInBytes,
|
|
38
40
|
maxAttachmentsPerMessage: attrs.maxAttachmentsPerMessage,
|
|
39
41
|
}
|
|
@@ -46,6 +48,7 @@ const stableFallbackConfiguration: ApiResource<ChatConfigurationResource> = {
|
|
|
46
48
|
type: 'ChatConfiguration',
|
|
47
49
|
id: 'current',
|
|
48
50
|
allowedFileExtensions: FALLBACK_ALLOWED_FILE_EXTENSIONS,
|
|
51
|
+
allowedMimeTypes: FALLBACK_ALLOWED_MIME_TYPES,
|
|
49
52
|
maxFileSizeInBytes: FALLBACK_MAX_FILE_SIZE_IN_BYTES,
|
|
50
53
|
maxAttachmentsPerMessage: FALLBACK_MAX_ATTACHMENTS_PER_MESSAGE,
|
|
51
54
|
},
|