cozy-viewer 1.1.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 (250) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +21 -0
  3. package/Readme.md +44 -0
  4. package/babel.config.js +23 -0
  5. package/dist/Footer/BottomSheetContent.d.ts +12 -0
  6. package/dist/Footer/BottomSheetContent.js +45 -0
  7. package/dist/Footer/DownloadButton.d.ts +16 -0
  8. package/dist/Footer/DownloadButton.js +113 -0
  9. package/dist/Footer/FooterActionButtons.d.ts +13 -0
  10. package/dist/Footer/FooterActionButtons.js +33 -0
  11. package/dist/Footer/FooterActionButtons.spec.d.ts +1 -0
  12. package/dist/Footer/FooterContent.d.ts +16 -0
  13. package/dist/Footer/FooterContent.js +120 -0
  14. package/dist/Footer/ForwardButton.d.ts +20 -0
  15. package/dist/Footer/ForwardButton.js +177 -0
  16. package/dist/Footer/ForwardButton.spec.d.ts +1 -0
  17. package/dist/Footer/ForwardOrDownloadButton.d.ts +11 -0
  18. package/dist/Footer/ForwardOrDownloadButton.js +42 -0
  19. package/dist/Footer/Sharing.d.ts +16 -0
  20. package/dist/Footer/Sharing.js +79 -0
  21. package/dist/Footer/helpers.d.ts +8 -0
  22. package/dist/Footer/helpers.js +179 -0
  23. package/dist/Footer/helpers.spec.d.ts +1 -0
  24. package/dist/NoViewer/DownloadButton.d.ts +2 -0
  25. package/dist/NoViewer/DownloadButton.js +52 -0
  26. package/dist/NoViewer/FileIcon.d.ts +4 -0
  27. package/dist/NoViewer/FileIcon.js +76 -0
  28. package/dist/NoViewer/NoViewer.d.ts +18 -0
  29. package/dist/NoViewer/NoViewer.js +64 -0
  30. package/dist/NoViewer/NoViewer.spec.d.ts +1 -0
  31. package/dist/NoViewer/index.d.ts +1 -0
  32. package/dist/NoViewer/index.js +15 -0
  33. package/dist/Panel/ActionMenuDesktop.d.ts +3 -0
  34. package/dist/Panel/ActionMenuDesktop.js +91 -0
  35. package/dist/Panel/ActionMenuMobile.d.ts +20 -0
  36. package/dist/Panel/ActionMenuMobile.js +95 -0
  37. package/dist/Panel/ActionMenuWrapper.d.ts +3 -0
  38. package/dist/Panel/ActionMenuWrapper.js +155 -0
  39. package/dist/Panel/Certifications.d.ts +2 -0
  40. package/dist/Panel/Certifications.js +80 -0
  41. package/dist/Panel/PanelContent.d.ts +2 -0
  42. package/dist/Panel/PanelContent.js +71 -0
  43. package/dist/Panel/Qualification.d.ts +2 -0
  44. package/dist/Panel/Qualification.js +148 -0
  45. package/dist/Panel/QualificationListItemContact.d.ts +10 -0
  46. package/dist/Panel/QualificationListItemContact.js +123 -0
  47. package/dist/Panel/QualificationListItemDate.d.ts +3 -0
  48. package/dist/Panel/QualificationListItemDate.js +90 -0
  49. package/dist/Panel/QualificationListItemInformation.d.ts +3 -0
  50. package/dist/Panel/QualificationListItemInformation.js +84 -0
  51. package/dist/Panel/QualificationListItemInformation.spec.d.ts +1 -0
  52. package/dist/Panel/QualificationListItemOther.d.ts +3 -0
  53. package/dist/Panel/QualificationListItemOther.js +78 -0
  54. package/dist/Panel/QualificationListItemText.d.ts +13 -0
  55. package/dist/Panel/QualificationListItemText.js +42 -0
  56. package/dist/Panel/getPanelBlocks.d.ts +26 -0
  57. package/dist/Panel/getPanelBlocks.js +79 -0
  58. package/dist/Panel/getPanelBlocks.spec.d.ts +1 -0
  59. package/dist/Viewer.d.ts +39 -0
  60. package/dist/Viewer.js +191 -0
  61. package/dist/ViewerContainer.d.ts +22 -0
  62. package/dist/ViewerContainer.js +221 -0
  63. package/dist/ViewerExposer.d.ts +2 -0
  64. package/dist/ViewerExposer.js +13 -0
  65. package/dist/ViewerInformationsWrapper.d.ts +20 -0
  66. package/dist/ViewerInformationsWrapper.js +68 -0
  67. package/dist/ViewerInformationsWrapper.spec.d.ts +1 -0
  68. package/dist/ViewerWithCustomPanelAndFooter.d.ts +2 -0
  69. package/dist/ViewerWithCustomPanelAndFooter.js +78 -0
  70. package/dist/ViewersByFile/AudioViewer.d.ts +74 -0
  71. package/dist/ViewersByFile/AudioViewer.js +57 -0
  72. package/dist/ViewersByFile/AudioViewer.spec.d.ts +1 -0
  73. package/dist/ViewersByFile/BlankPaperViewer.d.ts +2 -0
  74. package/dist/ViewersByFile/BlankPaperViewer.js +97 -0
  75. package/dist/ViewersByFile/ImageViewer.d.ts +31 -0
  76. package/dist/ViewersByFile/ImageViewer.js +385 -0
  77. package/dist/ViewersByFile/ImageViewer.spec.d.ts +1 -0
  78. package/dist/ViewersByFile/NoNetworkViewer.d.ts +2 -0
  79. package/dist/ViewersByFile/NoNetworkViewer.js +54 -0
  80. package/dist/ViewersByFile/OnlyOfficeViewer.d.ts +2 -0
  81. package/dist/ViewersByFile/OnlyOfficeViewer.js +46 -0
  82. package/dist/ViewersByFile/PdfJsViewer.d.ts +31 -0
  83. package/dist/ViewersByFile/PdfJsViewer.js +283 -0
  84. package/dist/ViewersByFile/PdfJsViewer.spec.d.ts +1 -0
  85. package/dist/ViewersByFile/PdfMobileViewer.d.ts +4 -0
  86. package/dist/ViewersByFile/PdfMobileViewer.js +185 -0
  87. package/dist/ViewersByFile/PdfMobileViewer.spec.d.ts +1 -0
  88. package/dist/ViewersByFile/ShortcutViewer.d.ts +2 -0
  89. package/dist/ViewersByFile/ShortcutViewer.js +61 -0
  90. package/dist/ViewersByFile/ShortcutViewer.spec.d.ts +4 -0
  91. package/dist/ViewersByFile/TextViewer.d.ts +90 -0
  92. package/dist/ViewersByFile/TextViewer.js +250 -0
  93. package/dist/ViewersByFile/TextViewer.spec.d.ts +1 -0
  94. package/dist/ViewersByFile/VideoViewer.d.ts +74 -0
  95. package/dist/ViewersByFile/VideoViewer.js +46 -0
  96. package/dist/ViewersByFile/VideoViewer.spec.d.ts +1 -0
  97. package/dist/assets/IlluGenericNewPage.svg +10 -0
  98. package/dist/components/ExpirationAlert.d.ts +2 -0
  99. package/dist/components/ExpirationAlert.js +129 -0
  100. package/dist/components/ExpirationAnnotation.d.ts +10 -0
  101. package/dist/components/ExpirationAnnotation.js +56 -0
  102. package/dist/components/Footer.d.ts +4 -0
  103. package/dist/components/Footer.js +41 -0
  104. package/dist/components/InformationPanel.d.ts +12 -0
  105. package/dist/components/InformationPanel.js +36 -0
  106. package/dist/components/Navigation.d.ts +18 -0
  107. package/dist/components/Navigation.js +63 -0
  108. package/dist/components/PdfToolbarButton.d.ts +16 -0
  109. package/dist/components/PdfToolbarButton.js +40 -0
  110. package/dist/components/PrintButton.d.ts +16 -0
  111. package/dist/components/PrintButton.js +163 -0
  112. package/dist/components/Toolbar.d.ts +2 -0
  113. package/dist/components/Toolbar.js +146 -0
  114. package/dist/components/ToolbarButtons.d.ts +5 -0
  115. package/dist/components/ToolbarButtons.js +20 -0
  116. package/dist/components/ToolbarFilePath.d.ts +3 -0
  117. package/dist/components/ToolbarFilePath.js +92 -0
  118. package/dist/components/ViewerByFile.d.ts +7 -0
  119. package/dist/components/ViewerByFile.js +137 -0
  120. package/dist/components/ViewerByFile.spec.d.ts +1 -0
  121. package/dist/components/ViewerControls.d.ts +9 -0
  122. package/dist/components/ViewerControls.js +251 -0
  123. package/dist/components/ViewerControls.spec.d.ts +1 -0
  124. package/dist/components/ViewerSpinner.d.ts +2 -0
  125. package/dist/components/ViewerSpinner.js +31 -0
  126. package/dist/docs/DemoProvider.d.ts +6 -0
  127. package/dist/docs/DemoProvider.js +159 -0
  128. package/dist/helpers.d.ts +37 -0
  129. package/dist/helpers.js +190 -0
  130. package/dist/helpers.spec.d.ts +1 -0
  131. package/dist/hoc/withFileUrl.d.ts +75 -0
  132. package/dist/hoc/withFileUrl.js +231 -0
  133. package/dist/hoc/withViewerLocales.d.ts +1 -0
  134. package/dist/hoc/withViewerLocales.js +15 -0
  135. package/dist/hooks/useReferencedContactName.d.ts +5 -0
  136. package/dist/hooks/useReferencedContactName.js +45 -0
  137. package/dist/index.d.ts +10 -0
  138. package/dist/index.js +75 -0
  139. package/dist/locales/index.d.ts +6 -0
  140. package/dist/locales/index.js +18 -0
  141. package/dist/proptypes.d.ts +7 -0
  142. package/dist/proptypes.js +25 -0
  143. package/dist/providers/ActionMenuProvider.d.ts +13 -0
  144. package/dist/providers/ActionMenuProvider.js +47 -0
  145. package/dist/providers/EncryptedProvider.d.ts +8 -0
  146. package/dist/providers/EncryptedProvider.js +43 -0
  147. package/dist/queries.d.ts +15 -0
  148. package/dist/queries.js +40 -0
  149. package/dist/stylesheet.css +218 -0
  150. package/jest.config.js +23 -0
  151. package/package.json +62 -0
  152. package/preprocess.js +16 -0
  153. package/src/Footer/BottomSheetContent.jsx +30 -0
  154. package/src/Footer/DownloadButton.jsx +66 -0
  155. package/src/Footer/FooterActionButtons.jsx +22 -0
  156. package/src/Footer/FooterActionButtons.spec.jsx +30 -0
  157. package/src/Footer/FooterContent.jsx +102 -0
  158. package/src/Footer/ForwardButton.jsx +95 -0
  159. package/src/Footer/ForwardButton.spec.jsx +87 -0
  160. package/src/Footer/ForwardOrDownloadButton.jsx +24 -0
  161. package/src/Footer/Sharing.jsx +59 -0
  162. package/src/Footer/helpers.js +106 -0
  163. package/src/Footer/helpers.spec.js +77 -0
  164. package/src/NoViewer/DownloadButton.jsx +28 -0
  165. package/src/NoViewer/FileIcon.jsx +46 -0
  166. package/src/NoViewer/NoViewer.jsx +30 -0
  167. package/src/NoViewer/NoViewer.spec.jsx +44 -0
  168. package/src/NoViewer/__snapshots__/NoViewer.spec.jsx.snap +82 -0
  169. package/src/NoViewer/index.jsx +1 -0
  170. package/src/Panel/ActionMenuDesktop.jsx +69 -0
  171. package/src/Panel/ActionMenuMobile.jsx +76 -0
  172. package/src/Panel/ActionMenuWrapper.jsx +104 -0
  173. package/src/Panel/Certifications.jsx +63 -0
  174. package/src/Panel/PanelContent.jsx +50 -0
  175. package/src/Panel/Qualification.jsx +114 -0
  176. package/src/Panel/QualificationListItemContact.jsx +85 -0
  177. package/src/Panel/QualificationListItemDate.jsx +78 -0
  178. package/src/Panel/QualificationListItemInformation.jsx +68 -0
  179. package/src/Panel/QualificationListItemInformation.spec.jsx +73 -0
  180. package/src/Panel/QualificationListItemOther.jsx +61 -0
  181. package/src/Panel/QualificationListItemText.jsx +30 -0
  182. package/src/Panel/getPanelBlocks.jsx +56 -0
  183. package/src/Panel/getPanelBlocks.spec.jsx +79 -0
  184. package/src/Panel/styles.styl +13 -0
  185. package/src/Readme.md +352 -0
  186. package/src/Viewer.jsx +135 -0
  187. package/src/ViewerContainer.jsx +170 -0
  188. package/src/ViewerExposer.js +3 -0
  189. package/src/ViewerInformationsWrapper.jsx +70 -0
  190. package/src/ViewerInformationsWrapper.spec.jsx +63 -0
  191. package/src/ViewerWithCustomPanelAndFooter.jsx +57 -0
  192. package/src/ViewersByFile/AudioViewer.jsx +22 -0
  193. package/src/ViewersByFile/AudioViewer.spec.jsx +40 -0
  194. package/src/ViewersByFile/BlankPaperViewer.jsx +47 -0
  195. package/src/ViewersByFile/ImageViewer.jsx +331 -0
  196. package/src/ViewersByFile/ImageViewer.spec.jsx +73 -0
  197. package/src/ViewersByFile/NoNetworkViewer.jsx +18 -0
  198. package/src/ViewersByFile/OnlyOfficeViewer.jsx +29 -0
  199. package/src/ViewersByFile/PdfJsViewer.jsx +210 -0
  200. package/src/ViewersByFile/PdfJsViewer.spec.jsx +161 -0
  201. package/src/ViewersByFile/PdfMobileViewer.jsx +106 -0
  202. package/src/ViewersByFile/PdfMobileViewer.spec.jsx +76 -0
  203. package/src/ViewersByFile/ShortcutViewer.jsx +38 -0
  204. package/src/ViewersByFile/ShortcutViewer.spec.jsx +32 -0
  205. package/src/ViewersByFile/TextViewer.jsx +126 -0
  206. package/src/ViewersByFile/TextViewer.spec.jsx +118 -0
  207. package/src/ViewersByFile/VideoViewer.jsx +13 -0
  208. package/src/ViewersByFile/VideoViewer.spec.jsx +40 -0
  209. package/src/ViewersByFile/__snapshots__/AudioViewer.spec.jsx.snap +43 -0
  210. package/src/ViewersByFile/__snapshots__/ShortcutViewer.spec.jsx.snap +57 -0
  211. package/src/ViewersByFile/__snapshots__/TextViewer.spec.jsx.snap +100 -0
  212. package/src/ViewersByFile/__snapshots__/VideoViewer.spec.jsx.snap +19 -0
  213. package/src/ViewersByFile/styles.styl +87 -0
  214. package/src/assets/IlluGenericNewPage.svg +10 -0
  215. package/src/components/ExpirationAlert.jsx +86 -0
  216. package/src/components/ExpirationAnnotation.jsx +39 -0
  217. package/src/components/Footer.jsx +14 -0
  218. package/src/components/InformationPanel.jsx +26 -0
  219. package/src/components/Navigation.jsx +40 -0
  220. package/src/components/PdfToolbarButton.jsx +26 -0
  221. package/src/components/PrintButton.jsx +89 -0
  222. package/src/components/Toolbar.jsx +111 -0
  223. package/src/components/ToolbarButtons.jsx +11 -0
  224. package/src/components/ToolbarFilePath.jsx +61 -0
  225. package/src/components/ViewerByFile.jsx +112 -0
  226. package/src/components/ViewerByFile.spec.jsx +100 -0
  227. package/src/components/ViewerControls.jsx +191 -0
  228. package/src/components/ViewerControls.spec.jsx +54 -0
  229. package/src/components/ViewerSpinner.jsx +17 -0
  230. package/src/components/styles.styl +93 -0
  231. package/src/docs/DemoProvider.jsx +90 -0
  232. package/src/helpers.js +131 -0
  233. package/src/helpers.spec.js +136 -0
  234. package/src/hoc/withFileUrl.jsx +93 -0
  235. package/src/hoc/withViewerLocales.jsx +5 -0
  236. package/src/hooks/useReferencedContactName.jsx +26 -0
  237. package/src/index.jsx +12 -0
  238. package/src/locales/en.json +66 -0
  239. package/src/locales/fr.json +66 -0
  240. package/src/locales/index.js +4 -0
  241. package/src/proptypes.js +12 -0
  242. package/src/providers/ActionMenuProvider.jsx +35 -0
  243. package/src/providers/EncryptedProvider.jsx +25 -0
  244. package/src/queries.js +20 -0
  245. package/src/styles.styl +22 -0
  246. package/src/vars.styl +6 -0
  247. package/test/__mocks__/fileMock.js +3 -0
  248. package/test/jestLib/setup.js +5 -0
  249. package/tsconfig-build.json +13 -0
  250. package/tsconfig.json +34 -0
@@ -0,0 +1,73 @@
1
+ import { render, waitFor } from '@testing-library/react'
2
+ import React from 'react'
3
+
4
+ import { checkImageSource } from 'cozy-ui/transpiled/react/FileImageLoader/checkImageSource'
5
+ import { BreakpointsProvider } from 'cozy-ui/transpiled/react/providers/Breakpoints'
6
+
7
+ import ImageViewer from './ImageViewer'
8
+ import DemoProvider from '../docs/DemoProvider'
9
+ import EncryptedProvider from '../providers/EncryptedProvider'
10
+
11
+ jest.mock('cozy-ui/transpiled/react/FileImageLoader/checkImageSource', () => ({
12
+ ...jest.requireActual(
13
+ 'cozy-ui/transpiled/react/FileImageLoader/checkImageSource'
14
+ ),
15
+ checkImageSource: jest.fn()
16
+ }))
17
+
18
+ const file = {
19
+ _id: 'image',
20
+ class: 'image',
21
+ mime: 'image/jpg',
22
+ name: 'sample.jpg',
23
+ links: {
24
+ large: 'https://viewerdemo.cozycloud.cc/IMG_0062.PNG'
25
+ }
26
+ }
27
+
28
+ const gestures = {
29
+ on: jest.fn(),
30
+ off: jest.fn(),
31
+ get: jest.fn()
32
+ }
33
+
34
+ const setup = () => {
35
+ const root = render(
36
+ <DemoProvider>
37
+ <BreakpointsProvider>
38
+ <EncryptedProvider>
39
+ <ImageViewer
40
+ file={file}
41
+ gestures={gestures}
42
+ gesturesRef={{}}
43
+ onSwipe={jest.fn()}
44
+ />
45
+ </EncryptedProvider>
46
+ </BreakpointsProvider>
47
+ </DemoProvider>
48
+ )
49
+
50
+ return { root }
51
+ }
52
+
53
+ describe('ImageViewer', () => {
54
+ afterEach(() => {
55
+ jest.restoreAllMocks
56
+ })
57
+
58
+ it('should render a spinner then the image viewer', async () => {
59
+ const { root } = setup()
60
+ const { container, queryByRole } = root
61
+
62
+ expect(queryByRole('progressbar')).toBeTruthy()
63
+
64
+ // simulate a successfull image loading
65
+ checkImageSource.mockResolvedValue('ok')
66
+
67
+ await waitFor(() => container.querySelector('img'))
68
+
69
+ const img = container.querySelector('img')
70
+ expect(img.getAttribute('alt')).toBe(file.name)
71
+ expect(img.getAttribute('src')).toBe(file.links.large)
72
+ })
73
+ })
@@ -0,0 +1,18 @@
1
+ import React from 'react'
2
+
3
+ import Icon from 'cozy-ui/transpiled/react/Icon'
4
+ import CloudBrokenIcon from 'cozy-ui/transpiled/react/Icons/CloudBroken'
5
+ import Button from 'cozy-ui/transpiled/react/deprecated/Button'
6
+
7
+ import styles from './styles.styl'
8
+ import { withViewerLocales } from '../hoc/withViewerLocales'
9
+
10
+ const NoNetworkViewer = ({ t, onReload }) => (
11
+ <div className={styles['viewer-canceled']}>
12
+ <Icon icon={CloudBrokenIcon} width={160} height={140} />
13
+ <h2>{t('Viewer.error.network')}</h2>
14
+ <Button onClick={onReload} label={t('Viewer.retry')} />
15
+ </div>
16
+ )
17
+
18
+ export default withViewerLocales(NoNetworkViewer)
@@ -0,0 +1,29 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+
4
+ import Button from 'cozy-ui/transpiled/react/deprecated/Button'
5
+ import { FileDoctype } from 'cozy-ui/transpiled/react/proptypes'
6
+
7
+ import NoViewer from '../NoViewer'
8
+ import { withViewerLocales } from '../hoc/withViewerLocales'
9
+
10
+ const OnlyOfficeViewer = ({ file, onlyOfficeOpener, t }) => {
11
+ return (
12
+ <NoViewer
13
+ file={file}
14
+ renderFallbackExtraContent={() => (
15
+ <Button
16
+ label={t('Viewer.openInOnlyOffice')}
17
+ onClick={() => onlyOfficeOpener(file)}
18
+ />
19
+ )}
20
+ />
21
+ )
22
+ }
23
+
24
+ OnlyOfficeViewer.propTypes = {
25
+ file: FileDoctype,
26
+ onlyOfficeOpener: PropTypes.func.isRequired
27
+ }
28
+
29
+ export default withViewerLocales(OnlyOfficeViewer)
@@ -0,0 +1,210 @@
1
+ import cx from 'classnames'
2
+ import flow from 'lodash/flow'
3
+ import throttle from 'lodash/throttle'
4
+ import PropTypes from 'prop-types'
5
+ import React, { Component } from 'react'
6
+ import { Document, Page } from 'react-pdf'
7
+
8
+ import styles from './styles.styl'
9
+ import NoViewer from '../NoViewer'
10
+ import ToolbarButton from '../components/PdfToolbarButton'
11
+ import ViewerSpinner from '../components/ViewerSpinner'
12
+ import withFileUrl from '../hoc/withFileUrl'
13
+ import { withViewerLocales } from '../hoc/withViewerLocales'
14
+
15
+ export const MIN_SCALE = 0.25
16
+ export const MAX_SCALE = 3
17
+ export const MAX_PAGES = 3
18
+ const KEY_CODE_UP = 38
19
+ const KEY_CODE_DOWN = 40
20
+
21
+ export class PdfJsViewer extends Component {
22
+ state = {
23
+ totalPages: 1,
24
+ scale: 1,
25
+ currentPage: 1,
26
+ loaded: false,
27
+ errored: false,
28
+ width: null,
29
+ renderAllPages: false
30
+ }
31
+
32
+ componentDidMount() {
33
+ this.setWrapperSize()
34
+ this.resizeListener = throttle(this.setWrapperSize, 500)
35
+ window.addEventListener('resize', this.resizeListener)
36
+ document.addEventListener('keyup', this.onKeyUp, false)
37
+ }
38
+
39
+ componentWillUnmount() {
40
+ window.removeEventListener('resize', this.resizeListener)
41
+ document.removeEventListener('keyup', this.onKeyUp, false)
42
+ }
43
+
44
+ onKeyUp = e => {
45
+ if (e.keyCode === KEY_CODE_UP) this.previousPage()
46
+ else if (e.keyCode === KEY_CODE_DOWN) this.nextPage()
47
+ }
48
+
49
+ toggleGestures(enable) {
50
+ if (!this.props.gestures) return
51
+ this.props.gestures.get('swipe').set({ enable })
52
+ this.props.gestures.get('pan').set({ enable })
53
+ }
54
+
55
+ setWrapperSize = () => {
56
+ const width = this.wrapper
57
+ ? this.wrapper.getBoundingClientRect().width
58
+ : null
59
+ this.setState({ width })
60
+ }
61
+
62
+ onLoadSuccess = ({ numPages }) => {
63
+ this.setState({
64
+ totalPages: numPages,
65
+ renderAllPages: numPages <= MAX_PAGES,
66
+ loaded: true
67
+ })
68
+ }
69
+
70
+ onLoadError = error => {
71
+ // eslint-disable-next-line no-console
72
+ console.warn(error)
73
+ this.setState({
74
+ errored: true
75
+ })
76
+ }
77
+
78
+ nextPage = () => {
79
+ this.setState(state => ({
80
+ currentPage: Math.min(state.currentPage + 1, state.totalPages)
81
+ }))
82
+ }
83
+
84
+ previousPage = () => {
85
+ this.setState(state => ({
86
+ currentPage: Math.max(state.currentPage - 1, 1)
87
+ }))
88
+ }
89
+
90
+ scaleUp = () => {
91
+ this.setState(state => {
92
+ const previousScale = state.scale
93
+ const scale = Math.min(previousScale + 0.25, MAX_SCALE)
94
+ if (scale > 1 && previousScale <= 1) this.toggleGestures(false)
95
+ return {
96
+ scale
97
+ }
98
+ })
99
+ }
100
+
101
+ scaleDown = () => {
102
+ this.setState(state => {
103
+ const previousScale = state.scale
104
+ const scale = Math.max(previousScale - 0.25, MIN_SCALE)
105
+ if (scale <= 1 && previousScale > 1) this.toggleGestures(true)
106
+ return {
107
+ scale
108
+ }
109
+ })
110
+ }
111
+
112
+ render() {
113
+ const { url, file, renderFallbackExtraContent, t } = this.props
114
+ const {
115
+ loaded,
116
+ errored,
117
+ totalPages,
118
+ currentPage,
119
+ scale,
120
+ width,
121
+ renderAllPages
122
+ } = this.state
123
+ if (errored)
124
+ return (
125
+ <NoViewer
126
+ file={file}
127
+ renderFallbackExtraContent={renderFallbackExtraContent}
128
+ />
129
+ )
130
+ const pageWidth = width ? width * scale : null // newer versions of react-pdf do that automatically
131
+
132
+ return (
133
+ <div
134
+ className={styles['viewer-pdfviewer']}
135
+ ref={ref => (this.wrapper = ref)}
136
+ >
137
+ <Document
138
+ file={url}
139
+ onLoadSuccess={this.onLoadSuccess}
140
+ onLoadError={this.onLoadError}
141
+ className={styles['viewer-pdfviewer-pdf']}
142
+ loading={<ViewerSpinner />}
143
+ >
144
+ {renderAllPages ? (
145
+ [...Array(totalPages)].map((_, page) => (
146
+ <Page
147
+ key={page}
148
+ pageNumber={page + 1}
149
+ width={pageWidth}
150
+ renderAnnotations={false}
151
+ className={cx('u-mv-1', styles['viewer-pdfviewer-page'])}
152
+ />
153
+ ))
154
+ ) : (
155
+ <Page
156
+ pageNumber={currentPage}
157
+ width={pageWidth}
158
+ renderAnnotations={false}
159
+ className={styles['viewer-pdfviewer-page']}
160
+ />
161
+ )}
162
+ </Document>
163
+ {loaded && (
164
+ <div className={cx(styles['viewer-pdfviewer-toolbar'], 'u-p-half')}>
165
+ {!renderAllPages && (
166
+ <span className="u-mh-half">
167
+ <ToolbarButton
168
+ icon="top"
169
+ onClick={this.previousPage}
170
+ disabled={currentPage === 1}
171
+ label={t('Viewer.previous')}
172
+ />
173
+ {currentPage}/{totalPages}
174
+ <ToolbarButton
175
+ icon="bottom"
176
+ onClick={this.nextPage}
177
+ disabled={currentPage === totalPages}
178
+ label={t('Viewer.next')}
179
+ />
180
+ </span>
181
+ )}
182
+
183
+ <span className="u-mh-half">
184
+ <ToolbarButton
185
+ icon="dash"
186
+ onClick={this.scaleDown}
187
+ disabled={scale === MIN_SCALE}
188
+ label={t('Viewer.scaledown')}
189
+ />
190
+ <ToolbarButton
191
+ icon="plus"
192
+ onClick={this.scaleUp}
193
+ disabled={scale === MAX_SCALE}
194
+ label={t('Viewer.scaleup')}
195
+ />
196
+ </span>
197
+ </div>
198
+ )}
199
+ </div>
200
+ )
201
+ }
202
+ }
203
+
204
+ PdfJsViewer.propTypes = {
205
+ url: PropTypes.string.isRequired,
206
+ gestures: PropTypes.object,
207
+ renderFallbackExtraContent: PropTypes.func
208
+ }
209
+
210
+ export default flow(withFileUrl, withViewerLocales)(PdfJsViewer)
@@ -0,0 +1,161 @@
1
+ import { shallow } from 'enzyme'
2
+ import React from 'react'
3
+
4
+ import { PdfJsViewer, MIN_SCALE, MAX_SCALE, MAX_PAGES } from './PdfJsViewer'
5
+
6
+ describe('PDFViewer', () => {
7
+ let component
8
+ const panGestureMock = jest.fn()
9
+ const swipeGestureMock = jest.fn()
10
+ const gesturesMock = {
11
+ get: jest.fn(type => ({
12
+ set: type === 'pan' ? panGestureMock : swipeGestureMock
13
+ }))
14
+ }
15
+ beforeEach(() => {
16
+ component = shallow(
17
+ <PdfJsViewer url="test" file={{}} gestures={gesturesMock} t={x => x} />
18
+ )
19
+ })
20
+ afterEach(() => {
21
+ jest.clearAllMocks()
22
+ })
23
+
24
+ describe('desture integration', () => {
25
+ let instance
26
+ beforeEach(() => {
27
+ instance = component.instance()
28
+ })
29
+
30
+ it('should disable gestures when zooming in', () => {
31
+ instance.scaleUp()
32
+ expect(component.state('scale')).toBeGreaterThan(1)
33
+ expect(panGestureMock).toHaveBeenCalledWith({ enable: false })
34
+ expect(swipeGestureMock).toHaveBeenCalledWith({ enable: false })
35
+ instance.scaleUp()
36
+ expect(panGestureMock).toHaveBeenCalledTimes(1)
37
+ expect(swipeGestureMock).toHaveBeenCalledTimes(1)
38
+ })
39
+
40
+ it('should leave gestures alone when zooming out', () => {
41
+ instance.scaleDown()
42
+ expect(component.state('scale')).toBeLessThan(1)
43
+ expect(panGestureMock).not.toHaveBeenCalled()
44
+ expect(swipeGestureMock).not.toHaveBeenCalled()
45
+ })
46
+
47
+ it('should re-enable gestures when zooming back out', () => {
48
+ instance.scaleUp()
49
+ expect(component.state('scale')).toBeGreaterThan(1)
50
+ expect(panGestureMock).toHaveBeenCalledWith({ enable: false })
51
+ expect(swipeGestureMock).toHaveBeenCalledWith({ enable: false })
52
+ instance.scaleDown()
53
+ expect(component.state('scale')).toBe(1)
54
+ expect(panGestureMock).toHaveBeenCalledWith({ enable: true })
55
+ expect(swipeGestureMock).toHaveBeenCalledWith({ enable: true })
56
+ })
57
+ })
58
+
59
+ describe('with a valid PDF', () => {
60
+ let instance
61
+ beforeEach(() => {
62
+ instance = component.instance()
63
+ instance.onLoadSuccess({ numPages: 3 })
64
+ })
65
+
66
+ it('should start with default options', () => {
67
+ expect(component.state('loaded')).toBe(true)
68
+ expect(component.state('totalPages')).toBe(3)
69
+ expect(component.state('currentPage')).toBe(1)
70
+ expect(component.state('scale')).toBe(1)
71
+ })
72
+
73
+ it('should flip to the next page while possible', () => {
74
+ instance.nextPage()
75
+ expect(component.state('currentPage')).toBe(2)
76
+ instance.nextPage()
77
+ expect(component.state('currentPage')).toBe(3)
78
+ instance.nextPage()
79
+ expect(component.state('currentPage')).toBe(3)
80
+ })
81
+
82
+ it('should flip to the previous page while possible', () => {
83
+ instance.nextPage()
84
+ expect(component.state('currentPage')).toBe(2)
85
+ instance.previousPage()
86
+ expect(component.state('currentPage')).toBe(1)
87
+ instance.previousPage()
88
+ expect(component.state('currentPage')).toBe(1)
89
+ })
90
+
91
+ it('should scale up to a certain point', () => {
92
+ const initialScale = component.state('scale')
93
+ instance.scaleUp()
94
+ expect(component.state('scale')).toBeGreaterThan(initialScale)
95
+
96
+ for (let i = 0; i < 10; i++) instance.scaleUp()
97
+ expect(component.state('scale')).toEqual(MAX_SCALE)
98
+ })
99
+
100
+ it('should scale down to a certain point', () => {
101
+ const initialScale = component.state('scale')
102
+ instance.scaleDown()
103
+ expect(component.state('scale')).toBeLessThan(initialScale)
104
+
105
+ for (let i = 0; i < 10; i++) instance.scaleDown()
106
+ expect(component.state('scale')).toEqual(MIN_SCALE)
107
+ })
108
+ })
109
+
110
+ describe('a PDF with few pages', () => {
111
+ beforeEach(() => {
112
+ component.instance().onLoadSuccess({ numPages: MAX_PAGES })
113
+ })
114
+
115
+ it('should render all the pages', () => {
116
+ const pages = component.find('ForwardRef(Page)')
117
+ expect(pages.length).toEqual(MAX_PAGES)
118
+ })
119
+
120
+ it('should not show pagination controls', () => {
121
+ const pageUp = component.find({ icon: 'top' })
122
+ const pageDown = component.find({ icon: 'bottom' })
123
+ expect(pageUp.length).toEqual(0)
124
+ expect(pageDown.length).toEqual(0)
125
+ })
126
+ })
127
+
128
+ describe('a PDF with many pages', () => {
129
+ beforeEach(() => {
130
+ component.instance().onLoadSuccess({ numPages: MAX_PAGES + 1 })
131
+ })
132
+
133
+ it('should render only the current page', () => {
134
+ const pages = component.find('ForwardRef(Page)')
135
+ expect(pages.length).toEqual(1)
136
+ })
137
+
138
+ it('should show pagination controls', () => {
139
+ const pageUp = component.find({ icon: 'top' })
140
+ const pageDown = component.find({ icon: 'bottom' })
141
+ expect(pageUp.length).toEqual(1)
142
+ expect(pageDown.length).toEqual(1)
143
+ })
144
+ })
145
+
146
+ describe('with a pdf that does not load', () => {
147
+ beforeEach(() => {
148
+ jest.spyOn(console, 'warn').mockImplementation(() => {})
149
+ })
150
+ afterEach(() => {
151
+ // eslint-disable-next-line no-console
152
+ console.warn.mockRestore()
153
+ })
154
+ it('should show a fallback', () => {
155
+ component.instance().onLoadError('pdfviewer test error')
156
+ expect(component.state('errored')).toBe(true)
157
+ const noViewer = component.find('NoViewer')
158
+ expect(noViewer.length).toBe(1)
159
+ })
160
+ })
161
+ })
@@ -0,0 +1,106 @@
1
+ import PropTypes from 'prop-types'
2
+ import React, { useState, useEffect, useRef, useCallback } from 'react'
3
+
4
+ import { useClient } from 'cozy-client'
5
+ import FileImageLoader from 'cozy-ui/transpiled/react/FileImageLoader'
6
+ import Spinner from 'cozy-ui/transpiled/react/Spinner'
7
+ import { FileDoctype } from 'cozy-ui/transpiled/react/proptypes'
8
+ import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
9
+
10
+ import styles from './styles.styl'
11
+ import NoViewer from '../NoViewer'
12
+ import DownloadButton from '../NoViewer/DownloadButton'
13
+ import { withViewerLocales } from '../hoc/withViewerLocales'
14
+
15
+ export const PdfMobileViewer = ({ file, url, t, gestures }) => {
16
+ const [loading, setLoading] = useState(true)
17
+ const [error, setError] = useState(false)
18
+ const imgRef = useRef(null)
19
+ const { showAlert } = useAlert()
20
+
21
+ const client = useClient()
22
+
23
+ const onImageError = () => {
24
+ setLoading(false)
25
+ setError(true)
26
+ }
27
+
28
+ const onImageLoad = () => {
29
+ setLoading(false)
30
+ }
31
+
32
+ const handleOnClick = useCallback(
33
+ async file => {
34
+ try {
35
+ await client.collection('io.cozy.files').download(file)
36
+ } catch (error) {
37
+ showAlert({
38
+ message: t('Viewer.error.generic'),
39
+ severity: 'error',
40
+ variant: 'filled',
41
+ icon: false
42
+ })
43
+ }
44
+ },
45
+ [client, showAlert, t]
46
+ )
47
+
48
+ useEffect(() => {
49
+ if (gestures) {
50
+ gestures.get('pinch').set({ enable: true })
51
+ gestures.on('pinchend tap', evt => {
52
+ if (
53
+ (evt.type === 'pinchend' || evt.tapCount === 1) &&
54
+ evt.target === imgRef.current
55
+ ) {
56
+ handleOnClick(file)
57
+ }
58
+ })
59
+
60
+ return () => {
61
+ gestures.off('pinchend tap')
62
+ }
63
+ }
64
+ }, [client, gestures, file, handleOnClick])
65
+
66
+ if (error) {
67
+ return (
68
+ <NoViewer
69
+ file={file}
70
+ renderFallbackExtraContent={file => <DownloadButton file={file} />}
71
+ />
72
+ )
73
+ }
74
+
75
+ return (
76
+ <div className={styles['viewer-pdfMobile']}>
77
+ {loading && <Spinner size="xxlarge" middle noMargin />}
78
+ {file && (
79
+ <FileImageLoader
80
+ file={file}
81
+ url={url}
82
+ linkType="medium"
83
+ onError={onImageError}
84
+ key={file.id}
85
+ render={src => (
86
+ <img
87
+ ref={imgRef}
88
+ className={styles['viewer-pdfMobile--image']}
89
+ alt={file.name}
90
+ src={src}
91
+ onLoad={onImageLoad}
92
+ />
93
+ )}
94
+ />
95
+ )}
96
+ </div>
97
+ )
98
+ }
99
+
100
+ PdfMobileViewer.prototype = {
101
+ file: FileDoctype.isRequired,
102
+ url: PropTypes.string,
103
+ gestures: PropTypes.object
104
+ }
105
+
106
+ export default withViewerLocales(PdfMobileViewer)
@@ -0,0 +1,76 @@
1
+ import { render, waitFor } from '@testing-library/react'
2
+ import React from 'react'
3
+
4
+ import { CozyProvider, createMockClient } from 'cozy-client'
5
+ import logger from 'cozy-logger'
6
+ import { I18n } from 'cozy-ui/transpiled/react/providers/I18n'
7
+
8
+ import { PdfMobileViewer } from './PdfMobileViewer'
9
+ import EncryptedProvider from '../providers/EncryptedProvider'
10
+
11
+ logger.error = logger.warn = jest.fn()
12
+
13
+ const client = createMockClient({})
14
+ client.collection = jest.fn(() => ({
15
+ getDownloadLinkById: jest.fn()
16
+ }))
17
+ client.plugins.realtime = {
18
+ subscribe: jest.fn(),
19
+ unsubscribe: jest.fn(),
20
+ unsubscribeAll: jest.fn()
21
+ }
22
+ jest.mock('cozy-ui/transpiled/react/providers/Alert', () => ({
23
+ useAlert: jest.fn().mockReturnValue({ showAlert: jest.fn() })
24
+ }))
25
+
26
+ const file = {
27
+ _id: 'pdf',
28
+ class: 'pdf',
29
+ name: 'Demo.pdf',
30
+ mime: 'application/pdf',
31
+ links: {
32
+ medium: 'https://viewerdemo.cozycloud.cc/IMG_0062.PNG'
33
+ }
34
+ }
35
+
36
+ const setup = ({ file }) => {
37
+ const root = render(
38
+ <CozyProvider client={client}>
39
+ <I18n lang="en" dictRequire={() => ''}>
40
+ <EncryptedProvider>
41
+ <PdfMobileViewer file={file} t={x => x} />
42
+ </EncryptedProvider>
43
+ </I18n>
44
+ </CozyProvider>
45
+ )
46
+
47
+ return { root }
48
+ }
49
+
50
+ describe('PdfMobileViewer', () => {
51
+ it('should show a spinner if image is not loaded', () => {
52
+ const { root } = setup({ file })
53
+ const { getByRole } = root
54
+
55
+ expect(getByRole('progressbar'))
56
+ })
57
+
58
+ describe('errors if file as no medium or failed to download', () => {
59
+ let fileWithoutLinks = file
60
+
61
+ beforeAll(() => {
62
+ fileWithoutLinks.links = undefined
63
+ })
64
+
65
+ it('should show "download" button on browser', async () => {
66
+ const { root } = setup({ file: fileWithoutLinks })
67
+ const { getByText, queryByRole } = root
68
+
69
+ await waitFor(() => {
70
+ expect(queryByRole('progressbar')).toBeFalsy()
71
+ expect(getByText('Viewer.download'))
72
+ expect(getByText(file.name))
73
+ })
74
+ })
75
+ })
76
+ })