@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: [
|
|
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
|
-
|
|
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:
|
|
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
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shoutem/ui",
|
|
3
|
-
"version": "6.4.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,
|