Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add base focusable api #12

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"presets": [ "env", "react", "stage-2" ],
"presets": [ "env", "react", "stage-2", "flow" ],
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"devDependencies": {
"babel-jest": "21.0.2",
"babel-plugin-external-helpers": "^6.22.0",
"babel-preset-flow": "^6.23.0",
"babel-preset-stage-2": "^6.24.1",
"chalk": "^2.4.1",
"flow-bin": "^0.76.0",
Expand Down
5 changes: 0 additions & 5 deletions packages/example-movie-list/.babelrc

This file was deleted.

1 change: 1 addition & 0 deletions packages/example-movie-list/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"babel-loader": "^7.1.2",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-preset-flow": "^6.23.0",
"babel-preset-react": "^6.24.1",
"uglifyjs-webpack-plugin": "^1.0.1",
"webpack": "^3.8.1",
Expand Down
99 changes: 63 additions & 36 deletions packages/example-movie-list/src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import React from 'react';
import {render, Text, ListView, View, Image, StyleSheet} from 'react-ape';
import {
render,
Text,
ListView,
View,
Image,
StyleSheet,
withFocus,
withNavigation
} from 'react-ape';

const styles = StyleSheet.create({
heading: {
Expand All @@ -8,53 +17,79 @@ const styles = StyleSheet.create({
color: 'white',
fontFamily: 'Arial',
fontWeight: 'bold',
fontSize: 29,
fontSize: 29
},
time: {
top: 62,
left: 1150,
color: 'red',
fontFamily: 'Arial',
fontWeight: 'bold',
fontSize: 25,
fontSize: 25
},
logo: {
top: 10,
left: 30,
left: 30
},
infoAboutRenderer: {
top: 590,
left: 30,
fontFamily: 'Arial',
fontWeight: 'bold',
color: 'lightblue',
fontSize: 23,
fontSize: 23
},
list: {
top: 100,
left: 0,
backgroundColor: '#303030',
width: 2000,
height: 400,
},
height: 400
}
});

class Item extends React.Component {
render() {
const { idx, data } = this.props;
return (
<View
height={200}
width={200}
key={'poster-list-' + idx}
onClick={() => {
console.log(data);
}}
>
<Image
style={{ x: 220 * idx + 30, y: 140, width: 200, height: 300 }}
src={data.src}
/>
<Text style={{ x: 220 * idx + 30, y: 460, color: '#FFF' }}>
{data.name}
</Text>
</View>
);
}
}

const FocusableItem = withFocus(Item);

class App extends React.Component {
constructor() {
super();
super(...arguments);
this.posters = [
{name: 'Narcos', src: 'posters/narcos.jpg'},
{name: 'Daredevil', src: 'posters/daredevil.jpg'},
{name: 'Stranger Things', src: 'posters/stranger-things.jpg'},
{name: 'Narcos', src: 'posters/narcos.jpg'},
{name: 'Daredevil', src: 'posters/daredevil.jpg'},
{name: 'Stranger Things', src: 'posters/stranger-things.jpg'},
{name: 'Narcos', src: 'posters/narcos.jpg'},
{name: 'Daredevil', src: 'posters/daredevil.jpg'},
{name: 'Stranger Things', src: 'posters/stranger-things.jpg'},
{ name: 'Narcos', src: 'posters/narcos.jpg' },
{ name: 'Daredevil', src: 'posters/daredevil.jpg' },
{ name: 'Stranger Things', src: 'posters/stranger-things.jpg' },
{ name: 'Narcos', src: 'posters/narcos.jpg' },
{ name: 'Daredevil', src: 'posters/daredevil.jpg' },
{ name: 'Stranger Things', src: 'posters/stranger-things.jpg' },
{ name: 'Narcos', src: 'posters/narcos.jpg' },
{ name: 'Daredevil', src: 'posters/daredevil.jpg' },
{ name: 'Stranger Things', src: 'posters/stranger-things.jpg' }
];
this.state = {
time: new Date().toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1'),
time: new Date().toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1')
};
}

Expand All @@ -63,29 +98,19 @@ class App extends React.Component {
const time = new Date()
.toTimeString()
.replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1');
this.setState({time});
this.setState({ time });
}, 100);
}

renderPostersList() {
const renderRow = (data, idx) => (
<View
height={200}
width={200}
key={'poster-list-' + idx}
onClick={() => {
console.log(data);
}}>
<Image
style={{x: 220 * idx + 30, y: 140, width: 200, height: 300}}
src={data.src}
/>
<Text style={{x: 220 * idx + 30, y: 460, color: '#FFF'}}>
{data.name}
</Text>
</View>
<FocusableItem
focusKey={`movie-card-${idx}`}
key={`poster-list-${idx}`}
idx={idx}
data={data}
/>
);

return (
<ListView
dataSource={this.posters}
Expand Down Expand Up @@ -113,4 +138,6 @@ class App extends React.Component {
}
}

render(<App />, document.getElementById('root'));
const NavigationApp = withNavigation(App);

render(<NavigationApp />, document.getElementById('root'));
15 changes: 6 additions & 9 deletions packages/example-movie-list/webpack.config.babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ const config = {
module: {
rules: [
{
test: /\.(js|jsx)$/,
test: /\.jsx?$/,
exclude: /node_modules/,
use: ['babel-loader'],
include: sourcePath,
use: { loader: 'babel-loader' }
},
],
},
Expand All @@ -43,15 +42,13 @@ if (process.env.NODE_ENV === 'production') {
warnings: false,
},
},
})
);
config.plugins.push(
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
})
}),
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.HashedModuleIdsPlugin(),
);
config.plugins.push(new webpack.optimize.ModuleConcatenationPlugin());
config.plugins.push(new webpack.HashedModuleIdsPlugin());
}

module.exports = config;
3 changes: 3 additions & 0 deletions packages/react-ape/modules/navigation/FocusPathContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import React from 'react';

export const FocusPathContext = React.createContext();
55 changes: 55 additions & 0 deletions packages/react-ape/modules/navigation/withFocus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @flow
*/

import * as React from 'react';
import { getComponentDisplayName } from '../utils';
import { FocusPathContext } from './FocusPathContext';

type RequiredProps = {
focusKey: string
};

// See why we do `| void`
// https://flow.org/en/docs/react/hoc/#toc-injecting-props-with-a-higher-order-component
type InjectedProps = {
focused: boolean | void
};

/**
* Allows the WrappedComponent to be focusable and provides the `focused`
* property to it.
* The resulting component should provide a `focusKey` property.
*/
export function withFocus<Props: RequiredProps>(
WrappedComponent: React.ComponentType<Props>
): React.ComponentType<$Diff<Props, InjectedProps>> {
return class extends React.Component<Props> {
static WrappedComponent = WrappedComponent;
static displayName = `withFocus(${getComponentDisplayName(
WrappedComponent
)})`;

renderWithFocusPath = focusPath => {
// TODO: I need to listen to a global and observable focusPath that will
// define if this component should be focused or not (the value of focused)
const { focusKey } = this.props;
return (
<FocusPathContext.Provider value={`${focusPath}/${focusKey}`}>
<WrappedComponent
{...this.props}
focused={true}
/>
</FocusPathContext.Provider>
);
};

render() {
return (
<FocusPathContext.Consumer>
{this.renderWithFocusPath}
</FocusPathContext.Consumer>
);
}
};
}
46 changes: 46 additions & 0 deletions packages/react-ape/modules/navigation/withNavigation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* @flow
*/

import * as React from 'react';
import { getComponentDisplayName, unsafeCreateUniqueId } from '../utils';
import { FocusPathContext } from './FocusPathContext';

type RequiredProps = {};

// See why we do `| void`
// https://flow.org/en/docs/react/hoc/#toc-injecting-props-with-a-higher-order-component
type InjectedProps = {
focusPath: string | void
};

/**
* Adds `focusPath` to the context so we can create the `focusPath`
* from the focusable elements inside the WrappedComponent.
* Should be used to wrap the root component of the application.
*/
export function withNavigation<Props: RequiredProps>(
WrappedComponent: React.ComponentType<Props>
): React.ComponentType<$Diff<Props, InjectedProps>> {
return class extends React.Component<Props> {
static WrappedComponent = WrappedComponent;
static displayName = `withNavigation(${getComponentDisplayName(
WrappedComponent
)})`;

rootFocusPath: string;

constructor() {
super(...arguments);
this.rootFocusPath = `root-${unsafeCreateUniqueId()}`;
}

render() {
return (
<FocusPathContext.Provider value={this.rootFocusPath}>
<WrappedComponent {...this.props} />
</FocusPathContext.Provider>
);
}
};
}
18 changes: 18 additions & 0 deletions packages/react-ape/modules/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @flow
*/

import type { ComponentType } from 'react';

export function getComponentDisplayName<T: {}>(Comp: ComponentType<T>): string {
return Comp.displayName || Comp.name || 'Component';
}

/**
* Returns a relatively "guaranteed" unique id.
* It's still not 100% guaranteed, that's why we added the "unsafe" prefix on
* this function.
*/
export function unsafeCreateUniqueId(): string {
return ((Math.random() * 10e18) + Date.now()).toString(36);
}
5 changes: 4 additions & 1 deletion packages/react-ape/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
"name": "react-ape",
"version": "0.0.6",
"description": "React Renderer to build interfaces using canvas/WebGL",
"main": "index.js",
"main": "reactApeEntry.js",
"scripts": {
"flow": "flow"
},
"peerDependencies": {
"react": "^16.4.1"
},
Expand Down
16 changes: 5 additions & 11 deletions packages/react-ape/reactApeEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,13 @@
*
*/

import ReactApeRenderer from './renderer/reactApeRenderer';
import StyleSheetModule from './modules/StyleSheet';

import ListViewComponent from './renderer/components/ListView';
export const ListView = ListViewComponent;
export { View, Image, Text } from './renderer/constants';
export { default as ListView } from './renderer/components/ListView';
export { default as StyleSheet } from './modules/StyleSheet';
export { withFocus, withNavigation } from './modules/navigation';

import ReactApeRenderer from './renderer/reactApeRenderer';
export const render = ReactApeRenderer.render;
// export const unmountComponentAtNode = ReactTVRenderer.unmountComponentAtNode;

export const Image = 'Image';
export const View = 'View';
export const Text = 'Text';

export const StyleSheet = StyleSheetModule;

export default ReactApeRenderer;
6 changes: 3 additions & 3 deletions packages/react-ape/renderer/reactApeComponentTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
*
*/

const randomKey = Math.random()
.toString(36)
.slice(2);
import { unsafeCreateUniqueId } from '../modules/utils';

const randomKey = unsafeCreateUniqueId();
const internalInstanceKey = '__reactInternalInstance$' + randomKey;
const internalEventHandlersKey = '__reactEventHandlers$' + randomKey;

Expand Down
Loading