cozy-ui 67.0.4 → 68.0.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/CHANGELOG.md CHANGED
@@ -1,3 +1,41 @@
1
+ # [68.0.0](https://github.com/cozy/cozy-ui/compare/v67.0.4...v68.0.0) (2022-05-24)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Warning of proptypes ([7b2c464](https://github.com/cozy/cozy-ui/commit/7b2c464))
7
+
8
+
9
+ ### Features
10
+
11
+ * Add FooterActionButtons component ([1c3efc1](https://github.com/cozy/cozy-ui/commit/1c3efc1))
12
+ * Refactor the viewer to let the app handle the action buttons ([60084d6](https://github.com/cozy/cozy-ui/commit/60084d6))
13
+
14
+
15
+ ### BREAKING CHANGES
16
+
17
+ * The management of action buttons
18
+ should now be handled on the application side
19
+ via the `FooterActionButtons` component.
20
+ The `disableSharing` prop has also been removed, as it is no longer needed.
21
+
22
+ Example:
23
+ ```
24
+ <Viewer {...props}>
25
+ <FooterActionButtons>
26
+ <SharingButton />
27
+ <ForwardOrDownloadButton />
28
+ </FooterActionButtons>
29
+ </Viewer>
30
+ ```
31
+ You can use a codemods `transform-viewer.js` to automatically handle this breaking change.
32
+ [See the codemods documentation](https://github.com/cozy/cozy-libs/tree/master/packages/cozy-codemods).
33
+ Using linter js auto-correction can be a good idea after that.
34
+ Here a common example (don't forget to change `src` value):
35
+ ```
36
+ jscodeshift -t $(yarn global dir)/node_modules/@cozy/codemods/src/transforms/transform-viewer.js src --parser babel --extensions js,jsx && yarn lint:js --fix
37
+ ```
38
+
1
39
  ## [67.0.4](https://github.com/cozy/cozy-ui/compare/v67.0.3...v67.0.4) (2022-05-20)
2
40
 
3
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-ui",
3
- "version": "67.0.4",
3
+ "version": "68.0.0",
4
4
  "description": "Cozy apps UI SDK",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -31,7 +31,7 @@ const DownloadButton = ({ file, t }) => {
31
31
  }
32
32
 
33
33
  DownloadButton.propTypes = {
34
- file: PropTypes.object.isRequired
34
+ file: PropTypes.object
35
35
  }
36
36
 
37
37
  export default withViewerLocales(DownloadButton)
@@ -0,0 +1,20 @@
1
+ import { cloneElement } from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import { mapToAllChildren } from './helpers'
5
+
6
+ const FooterActionButtons = ({ children, file }) => {
7
+ if (!children) return null
8
+
9
+ return mapToAllChildren(children, child => cloneElement(child, { file }))
10
+ }
11
+
12
+ FooterActionButtons.propTypes = {
13
+ children: PropTypes.oneOfType([
14
+ PropTypes.node,
15
+ PropTypes.arrayOf(PropTypes.node)
16
+ ]),
17
+ file: PropTypes.object
18
+ }
19
+
20
+ export default FooterActionButtons
@@ -0,0 +1,30 @@
1
+ import React from 'react'
2
+ import { render } from '@testing-library/react'
3
+
4
+ import FooterActionButtons from './FooterActionButtons'
5
+
6
+ describe('FooterActionButtons', () => {
7
+ it('should not return children', () => {
8
+ const { container } = render(<FooterActionButtons />)
9
+
10
+ expect(container).toMatchInlineSnapshot('<div />')
11
+ })
12
+
13
+ it('should render all childrens', () => {
14
+ const { getByText } = render(
15
+ <FooterActionButtons>
16
+ <div>
17
+ <p>First child</p>
18
+ <div>
19
+ <p>Sub first children</p>
20
+ </div>
21
+ </div>
22
+ <div>Second child</div>
23
+ </FooterActionButtons>
24
+ )
25
+
26
+ expect(getByText('First child'))
27
+ expect(getByText('Sub first children'))
28
+ expect(getByText('Second child'))
29
+ })
30
+ })
@@ -1,22 +1,12 @@
1
- import React, { useMemo } from 'react'
1
+ import React, { useMemo, Children, cloneElement } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import { makeStyles } from '@material-ui/core/styles'
4
4
 
5
- import { getReferencedBy, useQuery, models, useClient } from 'cozy-client'
6
-
7
5
  import BottomSheet, { BottomSheetHeader } from '../../BottomSheet'
8
6
 
9
- import { buildContactByIdsQuery } from '../queries'
10
7
  import { isValidForPanel } from '../helpers'
11
- import Sharing from './Sharing'
12
- import ForwardButton from './ForwardButton'
13
- import DownloadButton from './DownloadButton'
14
8
  import BottomSheetContent from './BottomSheetContent'
15
- import { shouldBeForwardButton } from './helpers'
16
-
17
- const {
18
- contact: { getDisplayName }
19
- } = models
9
+ import useReferencedContactName from './useReferencedContactName'
20
10
 
21
11
  const useStyles = makeStyles(theme => ({
22
12
  footer: {
@@ -30,54 +20,49 @@ const useStyles = makeStyles(theme => ({
30
20
  }
31
21
  }))
32
22
 
33
- const FooterContent = ({ file, toolbarRef, disableSharing }) => {
23
+ const FooterContent = ({ file, toolbarRef, children }) => {
34
24
  const styles = useStyles()
35
- const client = useClient()
36
- const FileActionButton = shouldBeForwardButton(client)
37
- ? ForwardButton
38
- : DownloadButton
25
+
39
26
  const toolbarProps = useMemo(() => ({ ref: toolbarRef }), [toolbarRef])
40
27
 
41
- const contactIds = getReferencedBy(file, 'io.cozy.contacts').map(
42
- ref => ref.id
43
- )
44
- const contactByIdsQuery = buildContactByIdsQuery(contactIds)
45
- const { data: contactList } = useQuery(contactByIdsQuery.definition, {
46
- ...contactByIdsQuery.options,
47
- enabled: contactIds.length > 0
28
+ const { contactName, isLoadingContacts } = useReferencedContactName(file)
29
+
30
+ const FooterActionButtons = Children.toArray(children).find(child => {
31
+ return (
32
+ child.type.name === 'FooterActionButtons' ||
33
+ child.type.displayName === 'FooterActionButtons'
34
+ )
48
35
  })
49
36
 
50
- const contactsFullname = Array.isArray(contactList)
51
- ? contactList.map(contact => `${getDisplayName(contact)}`).join(', ')
52
- : ''
37
+ const FooterActionButtonsWithFile = cloneElement(FooterActionButtons, {
38
+ file
39
+ })
53
40
 
54
- if (
55
- isValidForPanel({ file }) &&
56
- (contactsFullname || contactIds.length === 0)
57
- ) {
41
+ // We have to wait for the Contact request to finish before rendering `BottomSheet`, because it doesn't handle async well.
42
+ if (isValidForPanel({ file }) && !isLoadingContacts) {
58
43
  return (
59
44
  <BottomSheet toolbarProps={toolbarProps}>
60
45
  <BottomSheetHeader className="u-ph-1 u-pb-1">
61
- {!disableSharing && <Sharing file={file} />}
62
- <FileActionButton file={file} />
46
+ {FooterActionButtonsWithFile}
63
47
  </BottomSheetHeader>
64
- <BottomSheetContent file={file} contactsFullname={contactsFullname} />
48
+ <BottomSheetContent file={file} contactsFullname={contactName} />
65
49
  </BottomSheet>
66
50
  )
67
51
  }
68
52
 
69
- return (
70
- <div className={styles.footer}>
71
- {!disableSharing && <Sharing file={file} />}
72
- <FileActionButton file={file} />
73
- </div>
74
- )
53
+ // If `FooterActionButtons` hasn't children
54
+ if (!FooterActionButtons) return null
55
+
56
+ return <div className={styles.footer}>{FooterActionButtonsWithFile}</div>
75
57
  }
76
58
 
77
59
  FooterContent.propTypes = {
78
60
  file: PropTypes.object.isRequired,
79
61
  toolbarRef: PropTypes.object,
80
- disableSharing: PropTypes.bool
62
+ children: PropTypes.oneOfType([
63
+ PropTypes.node,
64
+ PropTypes.arrayOf(PropTypes.node)
65
+ ])
81
66
  }
82
67
 
83
68
  export default FooterContent
@@ -0,0 +1,24 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import { useClient } from 'cozy-client'
5
+
6
+ import ForwardButton from './ForwardButton'
7
+ import DownloadButton from './DownloadButton'
8
+ import { shouldBeForwardButton } from './helpers'
9
+
10
+ const ForwardOrDownloadButton = ({ file }) => {
11
+ const client = useClient()
12
+
13
+ const FileActionButton = shouldBeForwardButton(client)
14
+ ? ForwardButton
15
+ : DownloadButton
16
+
17
+ return <FileActionButton file={file} />
18
+ }
19
+
20
+ ForwardOrDownloadButton.propTypes = {
21
+ file: PropTypes.object
22
+ }
23
+
24
+ export default ForwardOrDownloadButton
@@ -37,7 +37,7 @@ const Sharing = ({ file }) => {
37
37
  }
38
38
 
39
39
  Sharing.propTypes = {
40
- file: PropTypes.object.isRequired
40
+ file: PropTypes.object
41
41
  }
42
42
 
43
43
  export default Sharing
@@ -1,3 +1,4 @@
1
+ import { isValidElement, Children, cloneElement } from 'react'
1
2
  import { saveFileWithCordova } from 'cozy-client/dist/models/fsnative'
2
3
  import { isIOS, isMobileApp } from 'cozy-device-helper'
3
4
 
@@ -73,3 +74,18 @@ export const exportFilesNative = async (client, files, filename) => {
73
74
  Alerter.error(downloadFileError(error))
74
75
  }
75
76
  }
77
+
78
+ export const mapToAllChildren = (children, cb) => {
79
+ return Children.map(children, child => {
80
+ if (!isValidElement(child)) return child
81
+
82
+ const grandchildren = child.props.children
83
+ if (grandchildren) {
84
+ return cloneElement(child, {
85
+ children: mapToAllChildren(grandchildren, cb)
86
+ })
87
+ }
88
+
89
+ return cb(child)
90
+ })
91
+ }
@@ -0,0 +1,32 @@
1
+ import { getReferencedBy, useQuery, models, isQueryLoading } from 'cozy-client'
2
+
3
+ import { buildContactByIdsQuery } from '../queries'
4
+
5
+ const {
6
+ contact: { getDisplayName }
7
+ } = models
8
+
9
+ const useReferencedContactName = file => {
10
+ const contactIds = getReferencedBy(file, 'io.cozy.contacts').map(
11
+ ref => ref.id
12
+ )
13
+ const contactByIdsQuery = buildContactByIdsQuery(contactIds)
14
+ const { data: contacts, ...contactsQueryResult } = useQuery(
15
+ contactByIdsQuery.definition,
16
+ {
17
+ ...contactByIdsQuery.options,
18
+ enabled: contactIds.length > 0
19
+ }
20
+ )
21
+
22
+ const isLoadingContacts =
23
+ isQueryLoading(contactsQueryResult) || contactsQueryResult.hasMore
24
+
25
+ const contactName =
26
+ contacts && contacts.length > 0
27
+ ? contacts.map(contact => `${getDisplayName(contact)}`).join(', ')
28
+ : ''
29
+
30
+ return { contactName, isLoadingContacts }
31
+ }
32
+ export default useReferencedContactName
@@ -1,12 +1,14 @@
1
1
  import React from 'react'
2
2
 
3
- import withBreakpoints from '../helpers/withBreakpoints'
3
+ import useBreakpoints from '../hooks/useBreakpoints'
4
4
 
5
5
  import styles from './styles.styl'
6
6
 
7
- const Footer = ({ children, breakpoints: { isDesktop } }) => {
7
+ const Footer = ({ children }) => {
8
+ const { isDesktop } = useBreakpoints()
9
+
8
10
  if (isDesktop) return null
9
11
  return <div className={styles['viewer-footer']}>{children}</div>
10
12
  }
11
13
 
12
- export default withBreakpoints()(Footer)
14
+ export default Footer
@@ -28,11 +28,14 @@ import CarbonCopyIcon from 'cozy-ui/transpiled/react/Icons/CarbonCopy';
28
28
  // The DemoProvider inserts a fake cozy-client in the React context.
29
29
  import DemoProvider from './docs/DemoProvider';
30
30
  import Overlay from 'cozy-ui/transpiled/react/Overlay';
31
- import Button from 'cozy-ui/transpiled/react/Button';
31
+ import Button from 'cozy-ui/transpiled/react/Buttons';
32
32
  import DownloadIcon from 'cozy-ui/transpiled/react/Icons/Download';
33
33
  import ShareIcon from 'cozy-ui/transpiled/react/Icons/Share';
34
34
  import { isValidForPanel } from 'cozy-ui/transpiled/react/Viewer/helpers';
35
35
  import getPanelBlocks, { panelBlocksSpecs } from 'cozy-ui/transpiled/react/Viewer/Panel/getPanelBlocks';
36
+ import FooterActionButtons from 'cozy-ui/transpiled/react/Viewer/Footer/FooterActionButtons';
37
+ import ForwardOrDownloadButton from 'cozy-ui/transpiled/react/Viewer/Footer/ForwardOrDownloadButton';
38
+
36
39
 
37
40
  // We provide a collection of (fake) io.cozy.files to be rendered
38
41
  const files = [
@@ -95,6 +98,20 @@ const files = [
95
98
  }
96
99
  ];
97
100
 
101
+ const ShareButtonFake = () => {
102
+ return (
103
+ <Button
104
+ label="Share"
105
+ className="u-w-100 u-ml-0 u-mr-half"
106
+ variant="secondary"
107
+ startIcon={<Icon icon={ShareIcon} />}
108
+ onClick={() => {
109
+ return alert("This is a demo, there's no actual Cozy to share the file from ¯\\_(ツ)_/¯")
110
+ }}
111
+ />
112
+ )
113
+ };
114
+
98
115
  // The host app will usually need a small wrapper to display the Viewer. This is a very small example of such a wrapper that handles opening, closing, and navigating between files.
99
116
  initialState = {
100
117
  viewerOpened: isTesting(),
@@ -136,7 +153,7 @@ const onFileChange = (file, nextIndex) => setState({ currentIndex: nextIndex, cu
136
153
  />
137
154
  </Card>
138
155
  )}
139
- <button onClick={toggleViewer}>Open viewer</button>
156
+ <Button label="Open viewer" variant="ghost" size="small" onClick={toggleViewer} />
140
157
  {state.viewerOpened && (
141
158
  <Overlay>
142
159
  <Viewer
@@ -154,7 +171,12 @@ const onFileChange = (file, nextIndex) => setState({ currentIndex: nextIndex, cu
154
171
  showToolbar: variant.toolbar,
155
172
  showClose: state.showToolbarCloseButton
156
173
  }}
157
- />
174
+ >
175
+ <FooterActionButtons>
176
+ <ShareButtonFake />
177
+ <ForwardOrDownloadButton />
178
+ </FooterActionButtons>
179
+ </Viewer>
158
180
  </Overlay>
159
181
  )}
160
182
  </>
@@ -12,13 +12,7 @@ import ViewerInformationsWrapper from './ViewerInformationsWrapper'
12
12
  import EncryptedProvider from './EncryptedProvider'
13
13
 
14
14
  const ViewerContainer = props => {
15
- const {
16
- className,
17
- disableFooter,
18
- disablePanel,
19
- disableSharing,
20
- ...rest
21
- } = props
15
+ const { className, disableFooter, disablePanel, children, ...rest } = props
22
16
  const { currentIndex, files, currentURL } = props
23
17
  const toolbarRef = createRef()
24
18
  const { isDesktop } = useBreakpoints()
@@ -43,11 +37,12 @@ const ViewerContainer = props => {
43
37
  </EncryptedProvider>
44
38
  <ViewerInformationsWrapper
45
39
  disableFooter={disableFooter}
46
- disableSharing={disableSharing}
47
40
  validForPanel={validForPanel}
48
41
  currentFile={currentFile}
49
42
  toolbarRef={toolbarRef}
50
- />
43
+ >
44
+ {children}
45
+ </ViewerInformationsWrapper>
51
46
  </ViewerWrapper>
52
47
  )
53
48
  }
@@ -79,9 +74,7 @@ ViewerContainer.propTypes = {
79
74
  /** Show/Hide the panel containing more information about the file only on Desktop */
80
75
  disablePanel: PropTypes.bool,
81
76
  /** Show/Hide the panel containing more information about the file only on Phone & Tablet devices */
82
- disableFooter: PropTypes.bool,
83
- /** Show/Hide cozy share button */
84
- disableSharing: PropTypes.bool
77
+ disableFooter: PropTypes.bool
85
78
  }
86
79
 
87
80
  ViewerContainer.defaultProps = {
@@ -13,9 +13,9 @@ import { useSetFlagshipUI } from '../hooks/useSetFlagshipUi/useSetFlagshipUI'
13
13
  const ViewerInformationsWrapper = ({
14
14
  currentFile,
15
15
  disableFooter,
16
- disableSharing,
17
16
  validForPanel,
18
- toolbarRef
17
+ toolbarRef,
18
+ children
19
19
  }) => {
20
20
  const theme = useTheme()
21
21
  const sidebar = document.querySelector('[class*="sidebar"]')
@@ -34,11 +34,9 @@ const ViewerInformationsWrapper = ({
34
34
  <>
35
35
  {!disableFooter && (
36
36
  <Footer>
37
- <FooterContent
38
- file={currentFile}
39
- toolbarRef={toolbarRef}
40
- disableSharing={disableSharing}
41
- />
37
+ <FooterContent file={currentFile} toolbarRef={toolbarRef}>
38
+ {children}
39
+ </FooterContent>
42
40
  </Footer>
43
41
  )}
44
42
  {validForPanel && (
@@ -53,9 +51,12 @@ const ViewerInformationsWrapper = ({
53
51
  ViewerInformationsWrapper.propTypes = {
54
52
  currentFile: FileDoctype.isRequired,
55
53
  disableFooter: PropTypes.bool,
56
- disableSharing: PropTypes.bool,
57
54
  validForPanel: PropTypes.bool,
58
- toolbarRef: PropTypes.object
55
+ toolbarRef: PropTypes.object,
56
+ children: PropTypes.oneOfType([
57
+ PropTypes.node,
58
+ PropTypes.arrayOf(PropTypes.node)
59
+ ])
59
60
  }
60
61
 
61
62
  export default ViewerInformationsWrapper
@@ -0,0 +1,63 @@
1
+ import React from 'react'
2
+ import { render } from '@testing-library/react'
3
+
4
+ import ViewerInformationsWrapper from './ViewerInformationsWrapper'
5
+
6
+ /* eslint-disable react/display-name */
7
+ jest.mock('./Footer', () => ({ children }) => (
8
+ <div data-testid="Footer">{children}</div>
9
+ ))
10
+ jest.mock('./InformationPanel', () => ({ children }) => (
11
+ <div data-testid="InformationPanel">{children}</div>
12
+ ))
13
+ jest.mock('./Panel/PanelContent', () => () => (
14
+ <div data-testid="PanelContent" />
15
+ ))
16
+ jest.mock('./Footer/FooterContent', () => () => (
17
+ <div data-testid="FooterContent" />
18
+ ))
19
+ /* eslint-enable react/display-name */
20
+
21
+ const setup = ({ validForPanel, disableFooter } = {}) => {
22
+ return render(
23
+ <ViewerInformationsWrapper
24
+ currentFile={{}}
25
+ disableFooter={disableFooter}
26
+ validForPanel={validForPanel}
27
+ />
28
+ )
29
+ }
30
+
31
+ describe('ViewerInformationsWrapper', () => {
32
+ describe('disableFooter', () => {
33
+ it('should render FooterContent components', () => {
34
+ const { getByTestId } = setup({ disableFooter: false })
35
+
36
+ expect(getByTestId('Footer'))
37
+ expect(getByTestId('FooterContent'))
38
+ })
39
+
40
+ it('should not render FooterContent components', () => {
41
+ const { queryByTestId } = setup({ disableFooter: true })
42
+
43
+ expect(queryByTestId('Footer')).toBeNull()
44
+ expect(queryByTestId('FooterContent')).toBeNull()
45
+ })
46
+ })
47
+
48
+ describe('validForPanel', () => {
49
+ it('should render InformationPanel & PanelContent components', () => {
50
+ const { getByTestId } = setup({ validForPanel: true })
51
+
52
+ expect(getByTestId('InformationPanel'))
53
+ expect(getByTestId('PanelContent'))
54
+ })
55
+
56
+ it('should not render InformationPanel & PanelContent components', () => {
57
+ const { queryByTestId } = setup({ validForPanel: false })
58
+
59
+ expect(queryByTestId('InformationPanel')).toBeNull()
60
+ expect(queryByTestId('PanelContent')).toBeNull()
61
+ })
62
+ })
63
+ })
@@ -27,6 +27,16 @@ export const isFromKonnector = ({ file }) => {
27
27
  return has(file, 'cozyMetadata.sourceAccount')
28
28
  }
29
29
 
30
+ /**
31
+ * Checks if the file matches one of the following conditions:
32
+ * - Is certified
33
+ * - Is Qualified
34
+ * - From a Connector
35
+ *
36
+ * @param {object} param
37
+ * @param {IOCozyFile} param.file
38
+ * @returns {boolean}
39
+ */
30
40
  export const isValidForPanel = ({ file }) => {
31
41
  return (
32
42
  hasCertifications({ file }) ||
@@ -57,6 +57,6 @@ var DownloadButton = function DownloadButton(_ref) {
57
57
  };
58
58
 
59
59
  DownloadButton.propTypes = {
60
- file: PropTypes.object.isRequired
60
+ file: PropTypes.object
61
61
  };
62
62
  export default withViewerLocales(DownloadButton);
@@ -0,0 +1,20 @@
1
+ import { cloneElement } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { mapToAllChildren } from "cozy-ui/transpiled/react/Viewer/Footer/helpers";
4
+
5
+ var FooterActionButtons = function FooterActionButtons(_ref) {
6
+ var children = _ref.children,
7
+ file = _ref.file;
8
+ if (!children) return null;
9
+ return mapToAllChildren(children, function (child) {
10
+ return /*#__PURE__*/cloneElement(child, {
11
+ file: file
12
+ });
13
+ });
14
+ };
15
+
16
+ FooterActionButtons.propTypes = {
17
+ children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
18
+ file: PropTypes.object
19
+ };
20
+ export default FooterActionButtons;
@@ -1,22 +1,10 @@
1
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
-
3
- function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
4
-
5
- function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
6
-
7
- import React, { useMemo } from 'react';
1
+ import React, { useMemo, Children, cloneElement } from 'react';
8
2
  import PropTypes from 'prop-types';
9
3
  import { makeStyles } from '@material-ui/core/styles';
10
- import { getReferencedBy, useQuery, models, useClient } from 'cozy-client';
11
4
  import BottomSheet, { BottomSheetHeader } from "cozy-ui/transpiled/react/BottomSheet";
12
- import { buildContactByIdsQuery } from "cozy-ui/transpiled/react/Viewer/queries";
13
5
  import { isValidForPanel } from "cozy-ui/transpiled/react/Viewer/helpers";
14
- import Sharing from "cozy-ui/transpiled/react/Viewer/Footer/Sharing";
15
- import ForwardButton from "cozy-ui/transpiled/react/Viewer/Footer/ForwardButton";
16
- import DownloadButton from "cozy-ui/transpiled/react/Viewer/Footer/DownloadButton";
17
6
  import BottomSheetContent from "cozy-ui/transpiled/react/Viewer/Footer/BottomSheetContent";
18
- import { shouldBeForwardButton } from "cozy-ui/transpiled/react/Viewer/Footer/helpers";
19
- var getDisplayName = models.contact.getDisplayName;
7
+ import useReferencedContactName from "cozy-ui/transpiled/react/Viewer/Footer/useReferencedContactName";
20
8
  var useStyles = makeStyles(function (theme) {
21
9
  return {
22
10
  footer: {
@@ -34,58 +22,48 @@ var useStyles = makeStyles(function (theme) {
34
22
  var FooterContent = function FooterContent(_ref) {
35
23
  var file = _ref.file,
36
24
  toolbarRef = _ref.toolbarRef,
37
- disableSharing = _ref.disableSharing;
25
+ children = _ref.children;
38
26
  var styles = useStyles();
39
- var client = useClient();
40
- var FileActionButton = shouldBeForwardButton(client) ? ForwardButton : DownloadButton;
41
27
  var toolbarProps = useMemo(function () {
42
28
  return {
43
29
  ref: toolbarRef
44
30
  };
45
31
  }, [toolbarRef]);
46
- var contactIds = getReferencedBy(file, 'io.cozy.contacts').map(function (ref) {
47
- return ref.id;
48
- });
49
- var contactByIdsQuery = buildContactByIdsQuery(contactIds);
50
32
 
51
- var _useQuery = useQuery(contactByIdsQuery.definition, _objectSpread(_objectSpread({}, contactByIdsQuery.options), {}, {
52
- enabled: contactIds.length > 0
53
- })),
54
- contactList = _useQuery.data;
33
+ var _useReferencedContact = useReferencedContactName(file),
34
+ contactName = _useReferencedContact.contactName,
35
+ isLoadingContacts = _useReferencedContact.isLoadingContacts;
55
36
 
56
- var contactsFullname = Array.isArray(contactList) ? contactList.map(function (contact) {
57
- return "".concat(getDisplayName(contact));
58
- }).join(', ') : '';
37
+ var FooterActionButtons = Children.toArray(children).find(function (child) {
38
+ return child.type.name === 'FooterActionButtons' || child.type.displayName === 'FooterActionButtons';
39
+ });
40
+ var FooterActionButtonsWithFile = /*#__PURE__*/cloneElement(FooterActionButtons, {
41
+ file: file
42
+ }); // We have to wait for the Contact request to finish before rendering `BottomSheet`, because it doesn't handle async well.
59
43
 
60
44
  if (isValidForPanel({
61
45
  file: file
62
- }) && (contactsFullname || contactIds.length === 0)) {
46
+ }) && !isLoadingContacts) {
63
47
  return /*#__PURE__*/React.createElement(BottomSheet, {
64
48
  toolbarProps: toolbarProps
65
49
  }, /*#__PURE__*/React.createElement(BottomSheetHeader, {
66
50
  className: "u-ph-1 u-pb-1"
67
- }, !disableSharing && /*#__PURE__*/React.createElement(Sharing, {
68
- file: file
69
- }), /*#__PURE__*/React.createElement(FileActionButton, {
70
- file: file
71
- })), /*#__PURE__*/React.createElement(BottomSheetContent, {
51
+ }, FooterActionButtonsWithFile), /*#__PURE__*/React.createElement(BottomSheetContent, {
72
52
  file: file,
73
- contactsFullname: contactsFullname
53
+ contactsFullname: contactName
74
54
  }));
75
- }
55
+ } // If `FooterActionButtons` hasn't children
56
+
76
57
 
58
+ if (!FooterActionButtons) return null;
77
59
  return /*#__PURE__*/React.createElement("div", {
78
60
  className: styles.footer
79
- }, !disableSharing && /*#__PURE__*/React.createElement(Sharing, {
80
- file: file
81
- }), /*#__PURE__*/React.createElement(FileActionButton, {
82
- file: file
83
- }));
61
+ }, FooterActionButtonsWithFile);
84
62
  };
85
63
 
86
64
  FooterContent.propTypes = {
87
65
  file: PropTypes.object.isRequired,
88
66
  toolbarRef: PropTypes.object,
89
- disableSharing: PropTypes.bool
67
+ children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)])
90
68
  };
91
69
  export default FooterContent;
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useClient } from 'cozy-client';
4
+ import ForwardButton from "cozy-ui/transpiled/react/Viewer/Footer/ForwardButton";
5
+ import DownloadButton from "cozy-ui/transpiled/react/Viewer/Footer/DownloadButton";
6
+ import { shouldBeForwardButton } from "cozy-ui/transpiled/react/Viewer/Footer/helpers";
7
+
8
+ var ForwardOrDownloadButton = function ForwardOrDownloadButton(_ref) {
9
+ var file = _ref.file;
10
+ var client = useClient();
11
+ var FileActionButton = shouldBeForwardButton(client) ? ForwardButton : DownloadButton;
12
+ return /*#__PURE__*/React.createElement(FileActionButton, {
13
+ file: file
14
+ });
15
+ };
16
+
17
+ ForwardOrDownloadButton.propTypes = {
18
+ file: PropTypes.object
19
+ };
20
+ export default ForwardOrDownloadButton;
@@ -37,6 +37,6 @@ var Sharing = function Sharing(_ref) {
37
37
  };
38
38
 
39
39
  Sharing.propTypes = {
40
- file: PropTypes.object.isRequired
40
+ file: PropTypes.object
41
41
  };
42
42
  export default Sharing;
@@ -1,5 +1,6 @@
1
1
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
2
  import _regeneratorRuntime from "@babel/runtime/regenerator";
3
+ import { isValidElement, Children, cloneElement } from 'react';
3
4
  import { saveFileWithCordova } from 'cozy-client/dist/models/fsnative';
4
5
  import { isIOS, isMobileApp } from 'cozy-device-helper';
5
6
  import Alerter from "cozy-ui/transpiled/react/Alerter";
@@ -121,4 +122,18 @@ export var exportFilesNative = /*#__PURE__*/function () {
121
122
  return function exportFilesNative(_x, _x2, _x3) {
122
123
  return _ref.apply(this, arguments);
123
124
  };
124
- }();
125
+ }();
126
+ export var mapToAllChildren = function mapToAllChildren(children, cb) {
127
+ return Children.map(children, function (child) {
128
+ if (! /*#__PURE__*/isValidElement(child)) return child;
129
+ var grandchildren = child.props.children;
130
+
131
+ if (grandchildren) {
132
+ return /*#__PURE__*/cloneElement(child, {
133
+ children: mapToAllChildren(grandchildren, cb)
134
+ });
135
+ }
136
+
137
+ return cb(child);
138
+ });
139
+ };
@@ -0,0 +1,35 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
+ var _excluded = ["data"];
4
+
5
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
6
+
7
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
8
+
9
+ import { getReferencedBy, useQuery, models, isQueryLoading } from 'cozy-client';
10
+ import { buildContactByIdsQuery } from "cozy-ui/transpiled/react/Viewer/queries";
11
+ var getDisplayName = models.contact.getDisplayName;
12
+
13
+ var useReferencedContactName = function useReferencedContactName(file) {
14
+ var contactIds = getReferencedBy(file, 'io.cozy.contacts').map(function (ref) {
15
+ return ref.id;
16
+ });
17
+ var contactByIdsQuery = buildContactByIdsQuery(contactIds);
18
+
19
+ var _useQuery = useQuery(contactByIdsQuery.definition, _objectSpread(_objectSpread({}, contactByIdsQuery.options), {}, {
20
+ enabled: contactIds.length > 0
21
+ })),
22
+ contacts = _useQuery.data,
23
+ contactsQueryResult = _objectWithoutProperties(_useQuery, _excluded);
24
+
25
+ var isLoadingContacts = isQueryLoading(contactsQueryResult) || contactsQueryResult.hasMore;
26
+ var contactName = contacts && contacts.length > 0 ? contacts.map(function (contact) {
27
+ return "".concat(getDisplayName(contact));
28
+ }).join(', ') : '';
29
+ return {
30
+ contactName: contactName,
31
+ isLoadingContacts: isLoadingContacts
32
+ };
33
+ };
34
+
35
+ export default useReferencedContactName;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import withBreakpoints from "cozy-ui/transpiled/react/helpers/withBreakpoints";
2
+ import useBreakpoints from "cozy-ui/transpiled/react/hooks/useBreakpoints";
3
3
  var styles = {
4
4
  "CozyTheme--inverted": "styles__CozyTheme--inverted___1II8o",
5
5
  "CozyTheme--normal": "styles__CozyTheme--normal___382qj",
@@ -32,12 +32,15 @@ var styles = {
32
32
  };
33
33
 
34
34
  var Footer = function Footer(_ref) {
35
- var children = _ref.children,
36
- isDesktop = _ref.breakpoints.isDesktop;
35
+ var children = _ref.children;
36
+
37
+ var _useBreakpoints = useBreakpoints(),
38
+ isDesktop = _useBreakpoints.isDesktop;
39
+
37
40
  if (isDesktop) return null;
38
41
  return /*#__PURE__*/React.createElement("div", {
39
42
  className: styles['viewer-footer']
40
43
  }, children);
41
44
  };
42
45
 
43
- export default withBreakpoints()(Footer);
46
+ export default Footer;
@@ -1,6 +1,6 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
2
  import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
- var _excluded = ["className", "disableFooter", "disablePanel", "disableSharing"];
3
+ var _excluded = ["className", "disableFooter", "disablePanel", "children"];
4
4
  import React, { createRef } from 'react';
5
5
  import PropTypes from 'prop-types';
6
6
  import useBreakpoints from "cozy-ui/transpiled/react/hooks/useBreakpoints";
@@ -16,7 +16,7 @@ var ViewerContainer = function ViewerContainer(props) {
16
16
  var className = props.className,
17
17
  disableFooter = props.disableFooter,
18
18
  disablePanel = props.disablePanel,
19
- disableSharing = props.disableSharing,
19
+ children = props.children,
20
20
  rest = _objectWithoutProperties(props, _excluded);
21
21
 
22
22
  var currentIndex = props.currentIndex,
@@ -46,11 +46,10 @@ var ViewerContainer = function ViewerContainer(props) {
46
46
  toolbarRef: toolbarRef
47
47
  }))), /*#__PURE__*/React.createElement(ViewerInformationsWrapper, {
48
48
  disableFooter: disableFooter,
49
- disableSharing: disableSharing,
50
49
  validForPanel: validForPanel,
51
50
  currentFile: currentFile,
52
51
  toolbarRef: toolbarRef
53
- }));
52
+ }, children));
54
53
  };
55
54
 
56
55
  ViewerContainer.propTypes = {
@@ -90,10 +89,7 @@ ViewerContainer.propTypes = {
90
89
  disablePanel: PropTypes.bool,
91
90
 
92
91
  /** Show/Hide the panel containing more information about the file only on Phone & Tablet devices */
93
- disableFooter: PropTypes.bool,
94
-
95
- /** Show/Hide cozy share button */
96
- disableSharing: PropTypes.bool
92
+ disableFooter: PropTypes.bool
97
93
  };
98
94
  ViewerContainer.defaultProps = {
99
95
  currentIndex: 0,
@@ -11,9 +11,9 @@ import { useSetFlagshipUI } from "cozy-ui/transpiled/react/hooks/useSetFlagshipU
11
11
  var ViewerInformationsWrapper = function ViewerInformationsWrapper(_ref) {
12
12
  var currentFile = _ref.currentFile,
13
13
  disableFooter = _ref.disableFooter,
14
- disableSharing = _ref.disableSharing,
15
14
  validForPanel = _ref.validForPanel,
16
- toolbarRef = _ref.toolbarRef;
15
+ toolbarRef = _ref.toolbarRef,
16
+ children = _ref.children;
17
17
  var theme = useTheme();
18
18
  var sidebar = document.querySelector('[class*="sidebar"]');
19
19
  useSetFlagshipUI({
@@ -24,9 +24,8 @@ var ViewerInformationsWrapper = function ViewerInformationsWrapper(_ref) {
24
24
  });
25
25
  return /*#__PURE__*/React.createElement(React.Fragment, null, !disableFooter && /*#__PURE__*/React.createElement(Footer, null, /*#__PURE__*/React.createElement(FooterContent, {
26
26
  file: currentFile,
27
- toolbarRef: toolbarRef,
28
- disableSharing: disableSharing
29
- })), validForPanel && /*#__PURE__*/React.createElement(InformationPanel, null, /*#__PURE__*/React.createElement(PanelContent, {
27
+ toolbarRef: toolbarRef
28
+ }, children)), validForPanel && /*#__PURE__*/React.createElement(InformationPanel, null, /*#__PURE__*/React.createElement(PanelContent, {
30
29
  file: currentFile
31
30
  })));
32
31
  };
@@ -34,8 +33,8 @@ var ViewerInformationsWrapper = function ViewerInformationsWrapper(_ref) {
34
33
  ViewerInformationsWrapper.propTypes = {
35
34
  currentFile: FileDoctype.isRequired,
36
35
  disableFooter: PropTypes.bool,
37
- disableSharing: PropTypes.bool,
38
36
  validForPanel: PropTypes.bool,
39
- toolbarRef: PropTypes.object
37
+ toolbarRef: PropTypes.object,
38
+ children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)])
40
39
  };
41
40
  export default ViewerInformationsWrapper;
@@ -27,6 +27,17 @@ export var isFromKonnector = function isFromKonnector(_ref3) {
27
27
  var file = _ref3.file;
28
28
  return has(file, 'cozyMetadata.sourceAccount');
29
29
  };
30
+ /**
31
+ * Checks if the file matches one of the following conditions:
32
+ * - Is certified
33
+ * - Is Qualified
34
+ * - From a Connector
35
+ *
36
+ * @param {object} param
37
+ * @param {IOCozyFile} param.file
38
+ * @returns {boolean}
39
+ */
40
+
30
41
  export var isValidForPanel = function isValidForPanel(_ref4) {
31
42
  var file = _ref4.file;
32
43
  return hasCertifications({