Skip to content

Commit

Permalink
Merge branch 'release/0.9.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
SoHotSoup committed Sep 26, 2016
2 parents d9de702 + a9718be commit 22df273
Show file tree
Hide file tree
Showing 33 changed files with 678 additions and 840 deletions.
3 changes: 3 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["react-native"],
}
16 changes: 8 additions & 8 deletions _tests_/VideoSourceReader.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import VideoSourceReader from '../components/Video/VideoSourceReader';

describe('VideoSource', () => {
describe('YouTube source', () => {
const testSource = 'http://www.youtube.com/embed/M7lc1UVf-VE?autoplay=1';
const testSource = 'https://www.youtube.com/embed/M7lc1UVf-VE?autoplay=1';
const videoSourceReader = new VideoSourceReader(testSource);
describe('isWebVideo', () => {
it('returns true', () => {
const expected = true;
assert.equal(videoSourceReader.isWebVideo(), expected, 'web video not recoginized');
assert.equal(videoSourceReader.isEmbeddableVideo(), expected, 'web video not recoginized');
});
});
describe('getUrl', () => {
it('returns an embedded YouTube video url', () => {
const expected = 'http://www.youtube.com/embed/M7lc1UVf-VE';
const expected = 'https://www.youtube.com/embed/M7lc1UVf-VE';
assert.equal(videoSourceReader.getUrl(), expected, 'video url not correct');
});
});
Expand All @@ -22,15 +22,15 @@ describe('VideoSource', () => {
describe('Vimeo source', () => {
const testSource = 'https://player.vimeo.com/video/122732445';
const videoSourceReader = new VideoSourceReader(testSource);
describe('isWebVideo', () => {
describe('isEmbeddableVideo', () => {
it('returns true', () => {
const expected = true;
assert.equal(videoSourceReader.isWebVideo(), expected, 'web video not recoginized');
assert.equal(videoSourceReader.isEmbeddableVideo(), expected, 'web video not recoginized');
});
});
describe('getUrl', () => {
it('returns an embedded YouTube video url', () => {
const expected = 'http://player.vimeo.com/video/122732445?title=0&byline=0&portrait=0';
const expected = 'https://player.vimeo.com/video/122732445?title=0&byline=0&portrait=0';
assert.equal(videoSourceReader.getUrl(), expected, 'video url not correct');
});
});
Expand All @@ -39,10 +39,10 @@ describe('VideoSource', () => {
describe('Non web video source', () => {
const testSource = 'https://falcon479.startdedicated.com/files/round_boxes.mp4';
const videoSourceReader = new VideoSourceReader(testSource);
describe('isWebVideo', () => {
describe('isEmbeddableVideo', () => {
it('returns false', () => {
const expected = false;
assert.equal(videoSourceReader.isWebVideo(), expected, 'web video recoginized incorrectly');
assert.equal(videoSourceReader.isEmbeddableVideo(), expected, 'web video recoginized incorrectly');
});
});
describe('getUrl', () => {
Expand Down
26 changes: 18 additions & 8 deletions components/DropDownMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,20 @@ class DropDownMenu extends Component {
}

componentWillMount() {
this.autoSelect(this.props.options, this.props.selectedOption);
this.autoSelect(this.props.options);
this.scrollDriver = new ScrollDriver();
this.timingDriver = new TimingDriver();
}

componentWillReceiveProps(nextProps) {
if (!this.state.selectedOption && _.size(nextProps.options) > 0) {
this.autoSelect(nextProps.options, nextProps.selectedOption);
const { selectedOption } = this.state;
if (selectedOption !== nextProps.selectedOption && nextProps.selectedOption) {
// Select the option when the selectedOption prop changes
this.selectOption(nextProps.selectedOption);
} else if (!selectedOption || nextProps.options !== this.props.options) {
// Automatically select the first option if there is no selected option
// or when options change
this.autoSelect(nextProps.options);
}
}

Expand All @@ -104,12 +110,16 @@ class DropDownMenu extends Component {
return this.state.selectedOption;
}

selectOption(option) {
this.setState({ selectedOption: option }, this.emitOnOptionSelectedEvent);
}

/**
* Selects first item as default if non is selected
* Selects the first option by default.
*/
autoSelect(options = [], selectedOption) {
if (!selectedOption && !this.state.selectedOption && _.size(options) > 0) {
this.setState({ selectedOption: options[0] }, this.emitOnOptionSelectedEvent);
autoSelect(options = []) {
if (_.size(options) > 0) {
this.selectOption(options[0]);
}
}

Expand Down Expand Up @@ -169,7 +179,7 @@ class DropDownMenu extends Component {
const fadeInEnd = optionPosition - (visibleOptions - 1.5) * optionHeight;
const onPress = () => {
this.close();
this.setState({ selectedOption: option }, this.emitOnOptionSelectedEvent);
this.selectOption(option);
};
return (
<TouchableOpacity onPress={onPress} style={style.modalItem} onLayout={this.onOptionLayout}>
Expand Down
12 changes: 10 additions & 2 deletions components/Image.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,31 @@ import {
Image as RNImage,
Platform,
} from 'react-native';
import _ from 'lodash';

import { connectStyle } from '@shoutem/theme';
import { connectAnimation } from '@shoutem/animation';

// a valid source is either an object with an uri key or a number (from a `require` call)
const isValidSource = (source) => _.isNumber(source) || (_.isObject(source) && source.uri)

class Image extends Component {
static propTypes = {
...RNImage.propTypes,
}

setNativeProps(nativeProps) {
this._root.setNativeProps(nativeProps);
}

render() {
const { props } = this;
let resolvedProps = props;
const { source, defaultSource } = props;

// defaultSource is not supported on Android, so we manually
// reassign the defaultSource prop to source if source is not defined
if ((Platform.OS === 'android') && (!source || !source.uri)) {
if (Platform.OS === 'android' && !isValidSource(source)) {
resolvedProps = {
...props,
// Image views are not rendered on Android if there is no image to display,
Expand All @@ -32,7 +40,7 @@ class Image extends Component {
}

return (
<RNImage {...resolvedProps} />
<RNImage {...resolvedProps} ref={component => this._root = component} />
);
}
}
Expand Down
7 changes: 7 additions & 0 deletions components/ListView.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ class ListView extends React.Component {
(nextState.status !== this.state.status);
}

componentWillUnmount() {
if ((Platform.OS === 'ios') && (this.state.status !== Status.IDLE)) {
// Reset the global network indicator state
StatusBar.setNetworkActivityIndicatorVisible(false);
}
}

onRefresh() {
this.setState({
status: Status.REFRESHING,
Expand Down
10 changes: 8 additions & 2 deletions components/NavigationBar/composeChildren.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ const composers = {
return {
rightComponent: (
<Button onPress={onShare}>
<Icon name="share" />
<Icon
name="share"
animationName={props.animationName}
/>
</Button>
),
};
Expand All @@ -54,7 +57,10 @@ const composers = {
styleName="clear"
onPress={navigateBackWithoutEventParameter}
>
<Icon name="back" />
<Icon
name="back"
animationName={props.animationName}
/>
</Button>
) :
null;
Expand Down
161 changes: 161 additions & 0 deletions components/RichMedia/HypermediaComposer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import React, {
Component,
PropTypes,
} from 'react';

import { Text } from '../Text';
import { View } from '../View';
import { AllHtmlEntities } from 'html-entities';

import transformImageTag from './transformImageTag';
import transformVideoTag from './transformVideoTag';
import transformHtmlTag from './transformHtmlTag';
import transformAnchorTag from './transformAnchorTag';

const TEXT_NODE = 'text';
const ELEMENT_NODE = 'tag';

const defaultElementTransformers = [
transformImageTag,
transformVideoTag,
];

const defaultHtmlTextTransformers = [
transformHtmlTag,
];

function decodeHTML(string) {
return AllHtmlEntities.decode(string);
}

/**
* Composes react components by using transformer functions.
* It uses transformers for HTML tags but can also
* use other tags if their tag transformers are provided
* as the constructor arguments.
*/
export default class HypermediaComposer extends Component {

constructor(props) {
super(props);
const {
elementTransformer,
textTransformer,
style,
openUrl,
} = props;

this.containsElement = this.containsElement.bind(this);

const anchorTagTransformer = transformAnchorTag(this.containsElement, openUrl);
defaultHtmlTextTransformers.push(anchorTagTransformer);

if (elementTransformer) {
// custom element transformer takes precedence over all other transformers
this.elementTransformers = [elementTransformer, ...defaultElementTransformers];
} else {
this.elementTransformers = defaultElementTransformers;
}

if (textTransformer) {
// custom text transformer takes precedence over all other transformers
this.textTransformers = [textTransformer, ...defaultHtmlTextTransformers];
} else {
this.textTransformers = defaultHtmlTextTransformers;
}

this.transformers = [...this.elementTransformers, ...this.textTransformers];
this.renderElementNode = this.renderElementNode.bind(this);
this.style = style;
}

isElementNode(node) {
const { style } = this.props;
return this.elementTransformers.some(transformer => transformer(node, style, () => {}));
}

containsElement(node) {
return this.isElementNode(node)
|| (!!node.children && node.children.some((childNode) => this.containsElement(childNode)));
}

/**
* Return an array of react components representing the provided
* array of dom nodes.
*
* @param dom array of nodes to be transformed.
* @param parent parent node.
* @returns [ReactComponents] an array of React components.
*/
domToElement(dom, parent) {
if (!dom) return null;

const styles = this.style;

return dom.map((node, index) => {
if (node.type === TEXT_NODE) {
const parentStyle = parent ? styles[parent.name] : null;
return (
<Text key={index} style={parentStyle}>
{decodeHTML(node.data)}
</Text>
);
}

if (node.type === ELEMENT_NODE) {
return this.renderElementNode(node, index);
}

return null;
});
}

renderElementNode(node, index) {
const ContainerElement = this.containsElement(node) ? View : Text;
const renderChildren = this.domToElement.bind(this, node.children, node);

// check if there is a transformer which is able to render a truthy value
// and if there is, save it as a transformedTag
const transformedTag = this.transformers.reduce((acc, transform) => {
const transformedContent = transform(node, this.style, renderChildren);

if (transformedContent) {
return transformedContent;
}

return acc;
}, null);

if (transformedTag) {
return (
<ContainerElement key={index} >
{transformedTag}
</ContainerElement>
);
}

return this.domToElement(node.children, node);
}

render() {
const { dom } = this.props;
const content = this.domToElement(dom);

return (
<View style={this.props.style.container}>
{content}
</View>
);
}
}

HypermediaComposer.propTypes = {
dom: PropTypes.arrayOf(PropTypes.object),
onError: PropTypes.func,
style: PropTypes.shape({
container: PropTypes.object,
}),
openUrl: PropTypes.func,
elementTransformer: PropTypes.func,
textTransformer: PropTypes.func,
};
Loading

0 comments on commit 22df273

Please sign in to comment.