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,100 @@
1
+ import { isMobile as isMobileDevice } from 'cozy-device-helper'
2
+
3
+ import { getViewerComponentName } from './ViewerByFile'
4
+
5
+ jest.mock('cozy-device-helper', () => ({
6
+ ...jest.requireActual('cozy-device-helper'),
7
+ isMobile: jest.fn()
8
+ }))
9
+ jest.mock('../ViewersByFile/ShortcutViewer', () => 'ShortcutViewer')
10
+ jest.mock('../ViewersByFile/ImageViewer', () => 'ImageViewer')
11
+ jest.mock('../ViewersByFile/AudioViewer', () => 'AudioViewer')
12
+ jest.mock('../ViewersByFile/AudioViewer', () => 'AudioViewer')
13
+ jest.mock('../ViewersByFile/VideoViewer', () => 'VideoViewer')
14
+ jest.mock('../ViewersByFile/PdfJsViewer', () => 'PdfJsViewer')
15
+ jest.mock('../ViewersByFile/PdfMobileViewer', () => 'PdfMobileViewer')
16
+ jest.mock('../ViewersByFile/TextViewer', () => 'TextViewer')
17
+ jest.mock('../ViewersByFile/OnlyOfficeViewer', () => 'OnlyOfficeViewer')
18
+ jest.mock('../NoViewer', () => 'NoViewer')
19
+
20
+ describe('getViewerComponentName', () => {
21
+ beforeEach(() => {
22
+ isMobileDevice.mockReturnValue(false)
23
+ })
24
+
25
+ it('should return the correct component', () => {
26
+ expect(getViewerComponentName({ file: { class: 'shortcut' } })).toBe(
27
+ 'ShortcutViewer'
28
+ )
29
+ expect(getViewerComponentName({ file: { class: 'image' } })).toBe(
30
+ 'ImageViewer'
31
+ )
32
+ expect(getViewerComponentName({ file: { class: 'audio' } })).toBe(
33
+ 'AudioViewer'
34
+ )
35
+ expect(getViewerComponentName({ file: { class: 'video' } })).toBe(
36
+ 'VideoViewer'
37
+ )
38
+ isMobileDevice.mockReturnValue(true)
39
+ expect(getViewerComponentName({ file: { class: 'video' } })).toBe(
40
+ 'NoViewer'
41
+ )
42
+ isMobileDevice.mockReturnValue(false)
43
+ expect(
44
+ getViewerComponentName({ file: { class: 'pdf' }, isDesktop: false })
45
+ ).toBe('PdfMobileViewer')
46
+ expect(
47
+ getViewerComponentName({ file: { class: 'pdf' }, isDesktop: true })
48
+ ).toBe('PdfJsViewer')
49
+ expect(
50
+ getViewerComponentName({
51
+ file: {
52
+ class: 'text',
53
+ mime: 'text/plain',
54
+ name: 'test.txt'
55
+ }
56
+ })
57
+ ).toBe('TextViewer')
58
+ expect(
59
+ getViewerComponentName({
60
+ file: { class: 'text', name: 'test.docx' },
61
+ isOnlyOfficeEnabled: true
62
+ })
63
+ ).toBe('OnlyOfficeViewer')
64
+ expect(
65
+ getViewerComponentName({
66
+ file: { class: 'text', name: 'test.docx' },
67
+ isOnlyOfficeEnabled: false
68
+ })
69
+ ).toBe('NoViewer')
70
+ expect(
71
+ getViewerComponentName({
72
+ file: { class: 'slide' },
73
+ isOnlyOfficeEnabled: true
74
+ })
75
+ ).toBe('OnlyOfficeViewer')
76
+ expect(
77
+ getViewerComponentName({
78
+ file: { class: 'slide' },
79
+ isOnlyOfficeEnabled: false
80
+ })
81
+ ).toBe('NoViewer')
82
+ expect(
83
+ getViewerComponentName({
84
+ file: { class: 'spreadsheet' },
85
+ isOnlyOfficeEnabled: true
86
+ })
87
+ ).toBe('OnlyOfficeViewer')
88
+ expect(
89
+ getViewerComponentName({
90
+ file: { class: 'spreadsheet' },
91
+ isOnlyOfficeEnabled: false
92
+ })
93
+ ).toBe('NoViewer')
94
+ expect(
95
+ getViewerComponentName({
96
+ file: { class: 'notSupportedClass' }
97
+ })
98
+ ).toBe('NoViewer')
99
+ })
100
+ })
@@ -0,0 +1,191 @@
1
+ import cx from 'classnames'
2
+ import Hammer from 'hammerjs'
3
+ import flow from 'lodash/flow'
4
+ import PropTypes from 'prop-types'
5
+ import React, { Component } from 'react'
6
+
7
+ import withBreakpoints from 'cozy-ui/transpiled/react/helpers/withBreakpoints'
8
+ import { withStyles } from 'cozy-ui/transpiled/react/styles'
9
+
10
+ import { infoWidth } from './InformationPanel'
11
+ import Navigation from './Navigation'
12
+ import Toolbar from './Toolbar'
13
+ import styles from './styles.styl'
14
+ import { isValidForPanel } from '../helpers'
15
+ import { toolbarPropsPropType } from '../proptypes'
16
+
17
+ const ACTIONS_HIDE_DELAY = 3000
18
+
19
+ const customStyles = () => ({
20
+ viewerControlsWithInfo: {
21
+ width: `calc(100% - ${infoWidth}) !important`
22
+ }
23
+ })
24
+
25
+ class ViewerControls extends Component {
26
+ state = {
27
+ hidden: false,
28
+ gestures: null
29
+ }
30
+
31
+ _mounted = false
32
+
33
+ showControls = () => {
34
+ if (this._mounted) {
35
+ this.setState({ hidden: false })
36
+ this.hideAfterDelay()
37
+ document.removeEventListener('mousemove', this.showControls)
38
+ document.addEventListener('mousemove', this.hideAfterDelay)
39
+ }
40
+ }
41
+
42
+ hideControls = () => {
43
+ if (this._mounted) {
44
+ this.setState({ hidden: true })
45
+ document.removeEventListener('mousemove', this.hideAfterDelay)
46
+ document.addEventListener('mousemove', this.showControls)
47
+ }
48
+ }
49
+
50
+ hideAfterDelay = () => {
51
+ clearTimeout(this.hideTimeout)
52
+ this.hideTimeout = setTimeout(() => {
53
+ this.hideControls()
54
+ }, ACTIONS_HIDE_DELAY)
55
+ }
56
+
57
+ onSwipe = e => {
58
+ if (e.direction === Hammer.DIRECTION_LEFT) this.props.onNext()
59
+ else if (e.direction === Hammer.DIRECTION_RIGHT) this.props.onPrevious()
60
+ }
61
+
62
+ initGestures = () => {
63
+ const gestures = new Hammer(
64
+ this.wrapped,
65
+ this.props.breakpoints.isDesktop
66
+ ? {
67
+ cssProps: {
68
+ userSelect: 'auto'
69
+ }
70
+ }
71
+ : {}
72
+ )
73
+ if (!this.props.breakpoints.isDesktop) gestures.on('swipe', this.onSwipe)
74
+
75
+ return gestures
76
+ }
77
+
78
+ componentDidMount() {
79
+ this._mounted = true
80
+ clearTimeout(this.hideTimeout)
81
+ this.hideAfterDelay()
82
+ const gestures = this.initGestures()
83
+ this.setState({ gestures })
84
+ }
85
+
86
+ componentWillUnmount() {
87
+ this._mounted = false
88
+ clearTimeout(this.hideTimeout)
89
+ if (this.state.gestures) this.state.gestures.destroy()
90
+ }
91
+
92
+ renderChildren(children) {
93
+ if (!children) return null
94
+
95
+ return React.Children.map(children, child => {
96
+ if (
97
+ child?.type?.name === 'ViewerByFile' ||
98
+ child?.type?.displayName === 'ViewerByFile'
99
+ ) {
100
+ return React.cloneElement(child, {
101
+ gestures: this.state.gestures,
102
+ gesturesRef: this.wrapped,
103
+ onSwipe: this.onSwipe
104
+ })
105
+ }
106
+ })
107
+ }
108
+
109
+ render() {
110
+ const {
111
+ file,
112
+ onClose,
113
+ hasPrevious,
114
+ hasNext,
115
+ onPrevious,
116
+ onNext,
117
+ expanded,
118
+ toolbarProps,
119
+ showNavigation,
120
+ showInfoPanel,
121
+ children,
122
+ classes,
123
+ breakpoints: { isDesktop }
124
+ } = this.props
125
+ const { showToolbar, showClose, toolbarRef, showFilePath } = toolbarProps
126
+ const { hidden } = this.state
127
+
128
+ const shouldDisplayContentTop = isValidForPanel({ file })
129
+
130
+ return (
131
+ <div
132
+ className={cx(styles['viewer-controls'], {
133
+ [styles['viewer-controls--expanded']]: expanded,
134
+ [styles['viewer-controls--display-content-top']]:
135
+ shouldDisplayContentTop,
136
+ [classes.viewerControlsWithInfo]: showInfoPanel
137
+ })}
138
+ ref={wrapped => {
139
+ this.wrapped = wrapped
140
+ }}
141
+ >
142
+ {showToolbar && (
143
+ <Toolbar
144
+ toolbarRef={toolbarRef}
145
+ file={file}
146
+ showFilePath={showFilePath}
147
+ onMouseEnter={this.showControls}
148
+ onMouseLeave={this.hideControls}
149
+ onClose={showClose && onClose}
150
+ >
151
+ {children}
152
+ </Toolbar>
153
+ )}
154
+ {showNavigation && isDesktop && hasPrevious && (
155
+ <Navigation
156
+ className={styles['viewer-nav--previous']}
157
+ hidden={hidden}
158
+ onMouseEnter={this.showControls}
159
+ onMouseLeave={this.hideControls}
160
+ onClick={onPrevious}
161
+ />
162
+ )}
163
+ {this.renderChildren(children)}
164
+ {showNavigation && isDesktop && hasNext && (
165
+ <Navigation
166
+ className={styles['viewer-nav--next']}
167
+ hidden={hidden}
168
+ onMouseEnter={this.showControls}
169
+ onMouseLeave={this.hideControls}
170
+ onClick={onNext}
171
+ />
172
+ )}
173
+ </div>
174
+ )
175
+ }
176
+ }
177
+
178
+ ViewerControls.propTypes = {
179
+ file: PropTypes.object.isRequired,
180
+ onClose: PropTypes.func.isRequired,
181
+ hasPrevious: PropTypes.bool.isRequired,
182
+ hasNext: PropTypes.bool.isRequired,
183
+ onPrevious: PropTypes.func.isRequired,
184
+ onNext: PropTypes.func.isRequired,
185
+ expanded: PropTypes.bool.isRequired,
186
+ toolbarProps: PropTypes.shape(toolbarPropsPropType),
187
+ showNavigation: PropTypes.bool.isRequired,
188
+ showInfoPanel: PropTypes.bool
189
+ }
190
+
191
+ export default flow(withBreakpoints(), withStyles(customStyles))(ViewerControls)
@@ -0,0 +1,54 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import React from 'react'
3
+
4
+ import ViewerByFile from './ViewerByFile'
5
+ import ViewerControls from './ViewerControls'
6
+
7
+ jest.mock('../ViewersByFile/AudioViewer', () => () => <div>AudioViewer</div>)
8
+ jest.mock('../providers/EncryptedProvider', () => ({
9
+ useEncrypted: () => ({ url: 'random' })
10
+ }))
11
+
12
+ describe('ViewerControls', () => {
13
+ const file = {
14
+ _id: 'audio',
15
+ class: 'audio',
16
+ mime: 'audio/mp3',
17
+ name: 'sample.mp3'
18
+ }
19
+
20
+ const setup = ({ children } = {}) => {
21
+ render(
22
+ <ViewerControls
23
+ file={file}
24
+ onClose={() => {}}
25
+ hasPrevious={false}
26
+ hasNext={false}
27
+ onPrevious={() => {}}
28
+ onNext={() => {}}
29
+ expanded={false}
30
+ toolbarProps={{
31
+ showToolbar: false,
32
+ showClose: false,
33
+ showFilePath: false,
34
+ toolbarRef: undefined
35
+ }}
36
+ showNavigation={false}
37
+ >
38
+ {children}
39
+ </ViewerControls>
40
+ )
41
+ }
42
+
43
+ it('should only render children if they are ViewerByFile', () => {
44
+ setup({
45
+ children: [
46
+ undefined,
47
+ <div key="notViewer">not ViewerByFile</div>,
48
+ <ViewerByFile key="viewer" file={file} onClose={() => {}} />
49
+ ]
50
+ })
51
+ expect(screen.queryByText('not ViewerByFile')).not.toBeInTheDocument()
52
+ expect(screen.getByText('AudioViewer')).toBeInTheDocument()
53
+ })
54
+ })
@@ -0,0 +1,17 @@
1
+ import React from 'react'
2
+
3
+ import Spinner from 'cozy-ui/transpiled/react/Spinner'
4
+ import withBreakpoints from 'cozy-ui/transpiled/react/helpers/withBreakpoints'
5
+
6
+ const ViewerSpinner = ({ breakpoints: { isDesktop } }) => {
7
+ return (
8
+ <Spinner
9
+ size="xxlarge"
10
+ middle
11
+ noMargin
12
+ {...(isDesktop && { color: 'white' })}
13
+ />
14
+ )
15
+ }
16
+
17
+ export default withBreakpoints()(ViewerSpinner)
@@ -0,0 +1,93 @@
1
+ @require 'components/selectionbar.styl'
2
+ @require 'settings/breakpoints.styl'
3
+
4
+ @require '../vars.styl'
5
+
6
+ .viewer-nav
7
+ position absolute
8
+ top $toolbarHeight
9
+ bottom 0
10
+ z-index var(--zIndex-modal-toolbar)
11
+ width 20%
12
+ cursor pointer
13
+ background-color transparent
14
+ transition .4s opacity ease-out
15
+ opacity 0
16
+ display flex
17
+ align-items center
18
+ justify-content center
19
+ flex-direction column
20
+
21
+ &--visible:hover
22
+ opacity 1
23
+ transition none
24
+
25
+ &--previous
26
+ left 0
27
+ .viewer-nav-arrow
28
+ align-self flex-start
29
+ margin-left 2.5rem
30
+ transform rotate(180deg)
31
+
32
+ &--next
33
+ right 0
34
+ .viewer-nav-arrow
35
+ align-self flex-end
36
+ margin-right 2.5rem
37
+
38
+ +medium-screen()
39
+ display none
40
+
41
+ .viewer-nav-arrow
42
+ opacity .7
43
+
44
+ .viewer-controls
45
+ position relative
46
+ display flex
47
+ flex-direction column
48
+ justify-content center
49
+ align-items center
50
+ width 100%
51
+ height 100%
52
+
53
+ .viewer-controls.--expanded .viewer-nav
54
+ margin-top 0
55
+ width 40%
56
+
57
+ .viewer-controls--display-content-top
58
+ +medium-screen()
59
+ justify-content flex-start
60
+ padding-top $toolbarHeightMedium - $viewerMarginTopMedium + 1rem
61
+
62
+ .viewer-toolbar
63
+ position absolute
64
+ top 0
65
+ z-index var(--zIndex-modal-toolbar)
66
+ display flex
67
+ flex-shrink 0
68
+ width calc(100% - 1rem)
69
+ padding 0 0.5rem
70
+ height $toolbarHeight
71
+ transition .4s opacity ease-out
72
+ background linear-gradient(to bottom, var(--charcoalGrey), rgba(50, 54, 63, 0))
73
+ justify-content flex-start
74
+ align-items center
75
+
76
+ &--hidden
77
+ opacity 0
78
+
79
+ +medium-screen()
80
+ height $toolbarHeightMedium
81
+ width 100%
82
+ padding var(--flagship-top-height) 1rem 0 0
83
+ background var(--paperBackgroundColor)
84
+ border-bottom 1px solid var(--dividerColor)
85
+
86
+ .viewer-footer
87
+ position fixed
88
+ bottom 0
89
+ z-index var(--zIndex-modal-footer)
90
+ width 100%
91
+ height $footerHeight
92
+ padding-bottom var(--flagship-bottom-height, env(safe-area-inset-bottom))
93
+ background var(--paperBackgroundColor)
@@ -0,0 +1,90 @@
1
+ import React from 'react'
2
+
3
+ import { CozyProvider } from 'cozy-client'
4
+ import CloudWallpaper from 'cozy-ui/docs/cloud-wallpaper.jpg'
5
+ import { BreakpointsProvider } from 'cozy-ui/transpiled/react/providers/Breakpoints'
6
+ import I18n from 'cozy-ui/transpiled/react/providers/I18n'
7
+
8
+ import { locales } from '../locales/index'
9
+
10
+ const demoTextFileResponse = {
11
+ text: () => new Promise(resolve => resolve('Hello World !'))
12
+ }
13
+
14
+ const demoFilesByClass = {
15
+ pdf: 'https://raw.githubusercontent.com/rospdf/pdf-php/2ccf7591fc2f18e63342ebfedad7997b08c34ed2/readme.pdf',
16
+ audio: 'https://viewerdemo.cozycloud.cc/Z.mp3',
17
+ video: 'https://viewerdemo.cozycloud.cc/Nextcloud.mp4',
18
+ text: 'https://viewerdemo.cozycloud.cc/notes.md'
19
+ }
20
+
21
+ const mockClient = {
22
+ plugins: {
23
+ realtime: {
24
+ subscribe: () => {},
25
+ unsubscribe: () => {},
26
+ unsubscribeAll: () => {}
27
+ }
28
+ },
29
+ on: () => {},
30
+ collection: () => ({
31
+ getDownloadLinkById: id =>
32
+ new Promise(resolve => resolve(demoFilesByClass[id])),
33
+ download: () =>
34
+ alert(
35
+ "This is a demo, there's no actual Cozy to download the file from ¯\\_(ツ)_/¯"
36
+ ),
37
+ get: () =>
38
+ new Promise(resolve =>
39
+ resolve({
40
+ data: {
41
+ links: {
42
+ large: CloudWallpaper
43
+ }
44
+ }
45
+ })
46
+ )
47
+ }),
48
+ getStackClient: () => ({
49
+ uri: '',
50
+ fetch: () => new Promise(resolve => resolve(demoTextFileResponse))
51
+ }),
52
+ getClient: () => mockClient,
53
+ store: {
54
+ getState: () => {},
55
+ subscribe: () => {},
56
+ unsubscribe: () => {}
57
+ },
58
+ getQueryFromState: queryName => {
59
+ if (queryName === 'io.cozy.files/parent_folder') {
60
+ return {
61
+ data: {
62
+ _id: 'parent_id',
63
+ path: '/Parent'
64
+ }
65
+ }
66
+ }
67
+ },
68
+ query: () => ({
69
+ data: [{ attributes: { slug: 'mespapiers' }, links: { related: '' } }]
70
+ }),
71
+ getInstanceOptions: () => ({ app: { slug: 'mespapiers' }, subdomain: 'flat' })
72
+ }
73
+
74
+ class Wrapper extends React.Component {
75
+ render() {
76
+ const lang = localStorage.getItem('lang') || 'en'
77
+
78
+ return (
79
+ <CozyProvider client={mockClient}>
80
+ <BreakpointsProvider>
81
+ <I18n dictRequire={lang => locales[lang]} lang={lang}>
82
+ {this.props.children}
83
+ </I18n>
84
+ </BreakpointsProvider>
85
+ </CozyProvider>
86
+ )
87
+ }
88
+ }
89
+
90
+ export default Wrapper
package/src/helpers.js ADDED
@@ -0,0 +1,131 @@
1
+ import { generateWebLink } from 'cozy-client'
2
+ import {
3
+ isEncrypted,
4
+ isFromKonnector,
5
+ hasQualifications,
6
+ hasCertifications,
7
+ normalize
8
+ } from 'cozy-client/dist/models/file'
9
+ import {
10
+ KNOWN_DATE_METADATA_NAMES,
11
+ KNOWN_INFORMATION_METADATA_NAMES,
12
+ KNOWN_BILLS_ATTRIBUTES_NAMES
13
+ } from 'cozy-client/dist/models/paper'
14
+
15
+ export const getCurrentModel = metadataName => {
16
+ if (
17
+ KNOWN_DATE_METADATA_NAMES.includes(metadataName) ||
18
+ KNOWN_INFORMATION_METADATA_NAMES.includes(metadataName) ||
19
+ KNOWN_BILLS_ATTRIBUTES_NAMES.includes(metadataName)
20
+ ) {
21
+ return 'information'
22
+ }
23
+ if (metadataName === 'contact') return 'contact'
24
+ if (metadataName === 'page') return 'page'
25
+ }
26
+
27
+ /**
28
+ * @typedef {object} Reference
29
+ * @property {string} id - id of the document
30
+ * @property {string} type - doctype of the document
31
+ */
32
+
33
+ /**
34
+ * Checks if the file matches one of the following conditions:
35
+ * - Is certified
36
+ * - Is Qualified
37
+ * - From a Connector
38
+ *
39
+ * @param {object} param
40
+ * @param {IOCozyFile} param.file
41
+ * @returns {boolean}
42
+ */
43
+ export const isValidForPanel = ({ file }) => {
44
+ return (
45
+ hasCertifications(file) || hasQualifications(file) || isFromKonnector(file)
46
+ )
47
+ }
48
+
49
+ export const downloadFile = async ({ client, file, url }) => {
50
+ if (isEncrypted(file)) {
51
+ return client.collection('io.cozy.files').forceFileDownload(url, file.name)
52
+ }
53
+ return client.collection('io.cozy.files').download(file)
54
+ }
55
+
56
+ export const isFileEncrypted = file => isEncrypted(file)
57
+
58
+ export const formatDate = ({ f, lang, date }) => {
59
+ if (lang === 'en') {
60
+ return f(date, 'MM/DD/YYYY')
61
+ }
62
+ return f(date, 'DD/MM/YYYY')
63
+ }
64
+
65
+ /**
66
+ * @param {{ information: string, page: string }} editPathByModelProps
67
+ * @param {string} currentModel
68
+ * @param {string} name
69
+ * @returns {string}
70
+ */
71
+ export const buildEditAttributePath = (
72
+ editPathByModelProps,
73
+ currentModel,
74
+ name
75
+ ) => {
76
+ const currentPath = editPathByModelProps[currentModel]
77
+ return currentPath?.replace(/__NAME__/, name) ?? ''
78
+ }
79
+
80
+ export const isEditableAttribute = (name, file) => {
81
+ const isNotEditableAttributes = ['datetime', 'qualification']
82
+
83
+ return (
84
+ !isNotEditableAttributes.includes(name) &&
85
+ ((name === 'issueDate' && !isFromKonnector(file)) || name !== 'issueDate')
86
+ )
87
+ }
88
+
89
+ export const normalizeAndSpreadAttributes = rawFile => {
90
+ const normalizedFile = normalize(rawFile)
91
+
92
+ return {
93
+ ...normalizedFile,
94
+ ...normalizedFile?.attributes
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Return a web link to an application in the Cozy environment with the specified path
100
+ * @param {object} param
101
+ * @param {CozyClient} param.client - Instance of CozyClient
102
+ * @param {string} param.slug - Slug of the application
103
+ * @param {string} param.path - Path into the application
104
+ * @returns {string} web link
105
+ */
106
+ export const makeWebLink = ({ client, slug, path }) => {
107
+ try {
108
+ const cozyURL = new URL(client.getStackClient().uri)
109
+ const { subdomain: subDomainType } = client.getInstanceOptions()
110
+
111
+ return generateWebLink({
112
+ pathname: '/',
113
+ cozyUrl: cozyURL.origin,
114
+ slug,
115
+ hash: path,
116
+ subDomainType
117
+ })
118
+ } catch (e) {
119
+ return null
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Remove the file name at the end of a path
125
+ * @param {string} path
126
+ * @returns {string} new path
127
+ */
128
+ export const removeFilenameFromPath = path => {
129
+ const newPath = path.substring(0, path.lastIndexOf('/'))
130
+ return newPath === '' ? '/' : newPath
131
+ }