@shoutem/ui 6.4.0-rc.0 → 6.4.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.
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { Vimeo } from 'react-native-vimeo-iframe';
2
3
  import YoutubePlayer from 'react-native-youtube-iframe';
3
4
  import iframe from '@native-html/iframe-plugin';
4
5
  import PropTypes from 'prop-types';
@@ -41,6 +42,19 @@ const IframeRenderer = props => {
41
42
  );
42
43
  }
43
44
 
45
+ if (url && url.includes('vimeo')) {
46
+ const vimeoIdRegEx = /(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|video\/|)(\d+)(?:[a-zA-Z0-9_\-]+)?/i;
47
+ const regExMatches = url.match(vimeoIdRegEx);
48
+ const vimeoId = regExMatches[1];
49
+
50
+ return (
51
+ <Vimeo
52
+ videoId={vimeoId}
53
+ style={{ height: shoutemStyle.vimeoVideo.height }}
54
+ />
55
+ );
56
+ }
57
+
44
58
  return iframe(props);
45
59
  };
46
60
 
@@ -13,8 +13,11 @@ import autoBindReact from 'auto-bind/react';
13
13
  import _ from 'lodash';
14
14
  import PropTypes from 'prop-types';
15
15
  import { connectStyle } from '@shoutem/theme';
16
+ import VideoRenderer from '@shoutem/ui/html/components/VideoRenderer';
17
+ import { videoModel } from '@shoutem/ui/html/services/HTMLElementModels';
16
18
  import { View } from '../../components/View';
17
19
  import { resolveMaxWidth } from '../services/Dimensions';
20
+ import { onElement } from '../services/DomVisitors';
18
21
  import AttachmentRenderer from './AttachmentRenderer';
19
22
  import IframeRenderer from './IframeRenderer';
20
23
 
@@ -37,9 +40,14 @@ class SimpleHtml extends PureComponent {
37
40
  const {
38
41
  style,
39
42
  body,
40
- customTagStyles,
41
43
  unsupportedVideoFormatMessage,
42
44
  attachments,
45
+ customDomVisitors,
46
+ customCustomRenderers,
47
+ customTagStyles,
48
+ customHtmlElementModels,
49
+ customIgnoredStyles,
50
+ customRendererProps,
43
51
  ...otherProps
44
52
  } = this.props;
45
53
 
@@ -74,6 +82,8 @@ class SimpleHtml extends PureComponent {
74
82
  style={style}
75
83
  />
76
84
  ),
85
+ video: VideoRenderer,
86
+ ...customCustomRenderers,
77
87
  };
78
88
 
79
89
  const htmlProps = {
@@ -83,7 +93,12 @@ class SimpleHtml extends PureComponent {
83
93
  tagsStyles: _.omitBy(tagStyles, tagStyle => !tagStyle),
84
94
  systemFonts: [...defaultSystemFonts, style.baseFont.fontFamily],
85
95
  baseStyle: style.baseFont,
86
- ignoredStyles: ['fontFamily', 'letterSpacing', 'transform'],
96
+ ignoredStyles: [
97
+ 'fontFamily',
98
+ 'letterSpacing',
99
+ 'transform',
100
+ ...customIgnoredStyles,
101
+ ],
87
102
  renderers: customRenderers,
88
103
  renderersProps: {
89
104
  table: {
@@ -95,13 +110,17 @@ class SimpleHtml extends PureComponent {
95
110
  renderToHardwareTextureAndroid: true,
96
111
  },
97
112
  },
113
+ ...customRendererProps,
98
114
  },
99
115
  customHTMLElementModels: {
100
116
  table: tableModel,
101
117
  iframe: iframeModel,
118
+ video: videoModel,
119
+ ...customHtmlElementModels,
102
120
  },
103
121
  WebView,
104
122
  ignoredTags: IGNORED_TAGS,
123
+ domVisitors: { onElement, ...customDomVisitors },
105
124
  };
106
125
 
107
126
  return (
@@ -116,19 +135,29 @@ SimpleHtml.propTypes = {
116
135
  style: PropTypes.object.isRequired,
117
136
  attachments: PropTypes.array,
118
137
  body: PropTypes.string,
119
- customAlterNode: PropTypes.func,
138
+ customCustomRenderers: PropTypes.object,
139
+ customDomVisitors: PropTypes.object,
120
140
  customHandleLinkPress: PropTypes.func,
141
+ customHtmlElementModels: PropTypes.object,
142
+ customIgnoredStyles: PropTypes.arrayOf(PropTypes.string),
143
+ customRendererProps: PropTypes.object,
121
144
  customTagStyles: PropTypes.object,
145
+ domVisitors: PropTypes.object,
122
146
  unsupportedVideoFormatMessage: PropTypes.string,
123
147
  };
124
148
 
125
149
  SimpleHtml.defaultProps = {
126
150
  attachments: undefined,
127
151
  body: undefined,
128
- customAlterNode: undefined,
129
152
  customHandleLinkPress: undefined,
130
- customTagStyles: undefined,
153
+ customTagStyles: {},
131
154
  unsupportedVideoFormatMessage: undefined,
155
+ domVisitors: { onElement },
156
+ customDomVisitors: {},
157
+ customCustomRenderers: {},
158
+ customHtmlElementModels: {},
159
+ customIgnoredStyles: [],
160
+ customRendererProps: {},
132
161
  };
133
162
 
134
163
  export default connectStyle('shoutem.ui.SimpleHtml')(SimpleHtml);
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+ import {
3
+ useComputeMaxWidthForTag,
4
+ useContentWidth,
5
+ } from 'react-native-render-html';
6
+ import WebView from 'react-native-webview';
7
+ import { findOne } from 'domutils';
8
+ import PropTypes from 'prop-types';
9
+
10
+ function findSource(tnode) {
11
+ if (tnode.attributes.src) {
12
+ return tnode.attributes.src;
13
+ }
14
+
15
+ const sourceElms = findOne(
16
+ elm => elm.tagName === 'source',
17
+ tnode.domNode.children,
18
+ );
19
+
20
+ return sourceElms ? sourceElms.attribs.src : '';
21
+ }
22
+
23
+ /**
24
+ *
25
+ * Generate video html with only selected attributes and parameters.
26
+ * @returns HTML video element
27
+ */
28
+ function generateVideoHtml(tnode) {
29
+ const url = findSource(tnode);
30
+
31
+ const containsTimeParameter = url.match(/(#|\?)t=[^&$]*/g, '');
32
+ // If video has no time parameter, we want to add time parameter at 0.1, just to be able to
33
+ // display the image, first frame of the video. Video tags that don't have autoplay attribute
34
+ // are not showing first frame without this solution.
35
+ // Otherwise, if user has defined time parameter, keep it as is.
36
+ const resolvedUrl = `${url}${containsTimeParameter ? '' : '#t=0.1'}`;
37
+
38
+ return `<video controls src="${resolvedUrl}" style="width: 100%; height: 100%;"/>`;
39
+ }
40
+
41
+ export const VideoRenderer = ({ tnode, style }) => {
42
+ const computeMaxWidth = useComputeMaxWidthForTag('video');
43
+ const width = computeMaxWidth(useContentWidth());
44
+
45
+ // Using uri as a Webview source is not an option, because we don't have as much control of video.
46
+ // E.g. if video from uri has autoplay=true, we can't prevent it from auto-playing when Webview renders.
47
+ // Instead, create video HTML element with only picked attributes and parameters.
48
+ const html = generateVideoHtml(tnode);
49
+
50
+ return (
51
+ <WebView
52
+ scrollEnabled={false}
53
+ source={{
54
+ html,
55
+ }}
56
+ style={[{ aspectRatio: 16 / 9 }, style, { width }]}
57
+ />
58
+ );
59
+ };
60
+
61
+ VideoRenderer.propTypes = {
62
+ tnode: PropTypes.object.isRequired,
63
+ style: PropTypes.object,
64
+ };
65
+
66
+ VideoRenderer.defaultProps = {
67
+ style: {},
68
+ };
69
+
70
+ export default VideoRenderer;
@@ -0,0 +1,37 @@
1
+ import _ from 'lodash';
2
+
3
+ // Shoutem RTE component wraps any video attachment (hosted inside iframe or video) with <figure>
4
+ // element. That element contains style that breaks the SimpleHtml UI - enormous blank
5
+ // space below attachment.
6
+ // This function removes height and padding-bottom styles from figure to fix this UI issue.
7
+ export const removeShoutemRteVideoAttachmentWrapper = element => {
8
+ if (
9
+ (element.name === 'figure' || element.name === 'video') &&
10
+ element.children.length > 0 &&
11
+ // <figure> can have multiple generated children too - type:text. Probably caused by empty space between
12
+ // figure and iframe elements in generated HTML.
13
+ _.some(
14
+ element.children,
15
+ ({ name }) => name === 'iframe' || name === 'video',
16
+ )
17
+ ) {
18
+ // Remove height and padding-bottom styles because they break the UI
19
+ element.attribs.style = element.attribs.style
20
+ ? element.attribs.style
21
+ .replace(/height:[^;]+;?/, '')
22
+ .replace(/padding-bottom:[^;]+;?/, '')
23
+ : '';
24
+ }
25
+
26
+ return element;
27
+ };
28
+
29
+ /**
30
+ * Collection of node (HTML element) alteration callbacks. Define any html element
31
+ * manipulations inside.
32
+ * @param {*} element - HTML element to check against and manipulate if necessary
33
+ * @returns Manipulated HTML element
34
+ */
35
+ export const onElement = element => {
36
+ return removeShoutemRteVideoAttachmentWrapper(element);
37
+ };
@@ -0,0 +1,7 @@
1
+ import { HTMLContentModel, HTMLElementModel } from 'react-native-render-html';
2
+
3
+ export const videoModel = HTMLElementModel.fromCustomModel({
4
+ contentModel: HTMLContentModel.block,
5
+ tagName: 'video',
6
+ isOpaque: true,
7
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shoutem/ui",
3
- "version": "6.4.0-rc.0",
3
+ "version": "6.4.0",
4
4
  "description": "Styleable set of components for React Native applications",
5
5
  "scripts": {
6
6
  "lint": "eslint .",
@@ -11,13 +11,13 @@
11
11
  "test": "jest"
12
12
  },
13
13
  "dependencies": {
14
+ "@native-html/iframe-plugin": "2.6.1",
15
+ "@native-html/table-plugin": "5.3.1",
14
16
  "@openspacelabs/react-native-zoomable-view": "2.0.4",
15
17
  "@react-native-community/datetimepicker": "6.2.0",
16
18
  "@shoutem/animation": "~0.15.0",
17
19
  "@shoutem/eslint-config-react": "~1.0.6",
18
20
  "@shoutem/theme": "~0.13.0",
19
- "@native-html/iframe-plugin": "2.6.1",
20
- "@native-html/table-plugin": "5.3.1",
21
21
  "auto-bind": "4.0.0",
22
22
  "babel-plugin-transform-decorators-legacy": "1.3.5",
23
23
  "buffer": "5.6.0",
@@ -39,6 +39,7 @@
39
39
  "react-native-svg": "12.3.0",
40
40
  "react-native-svg-transformer": "1.0.0",
41
41
  "react-native-toast-message": "2.1.5",
42
+ "react-native-vimeo-iframe": "^1.2.1",
42
43
  "react-native-webview": "11.23.0",
43
44
  "react-native-youtube-iframe": "2.2.2",
44
45
  "stream": "0.0.2",
@@ -50,8 +51,8 @@
50
51
  },
51
52
  "devDependencies": {
52
53
  "@babel/core": "7.16.7",
53
- "@babel/register": "7.9.0",
54
54
  "@babel/plugin-proposal-decorators": "7.16.7",
55
+ "@babel/register": "7.9.0",
55
56
  "@shoutem/eslint-config-react-native": "~1.0.2",
56
57
  "babel-eslint": "10.1.0",
57
58
  "eslint": "6.8.0",
package/theme.js CHANGED
@@ -2284,6 +2284,10 @@ export default () => {
2284
2284
  height: responsiveWidth(194),
2285
2285
  paddingBottom: resolveVariable('smallGutter'),
2286
2286
  },
2287
+ // Just enough to display portrait mode Vimeo videos and all Vimeo player controls
2288
+ vimeoVideo: {
2289
+ height: responsiveHeight(220),
2290
+ },
2287
2291
  fallback: {
2288
2292
  width: window.width,
2289
2293
  height: 40,