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

Introducing WebAssembly, Specs, ReactApeTree and Platform module #129

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,12 @@ packages/website/build/
packages/website/yarn.lock
packages/website/i18n/*

# App
packages/app/bundle.js

# Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

packages/react-ape/ape/target
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
# [React Ape](https://raphamorim.io/react-ape)

> React Renderer to build UI interfaces using Canvas/WebGL
> React Renderer to build UI interfaces using WebAssembly/Canvas (fallback to JS/Canvas)

<img src="assets/logo.png" width="220" />

## [Check the Docs (raphamorim.io/react-ape)](https://raphamorim.io/react-ape)
## [Check the Docs](https://raphamorim.io/react-ape)

### DISCLAIMER: In experimental stage

React Ape is a react renderer to build UI interfaces using canvas/WebGL. React Ape was built to be an optional [React-TV](https://github.com/raphamorim/react-tv) renderer. It's mainly a renderer focused on creating things for TV, PS5, PS4, Nintendo Switch, PS Vita, PS3 and low memory devices.

React Ape lets you build Canvas apps using React. React Ape uses the same design as React, letting you compose a rich UI from declarative components.

## Elements

- [`<View/>`](specs/view-spec.md)
- [`<Text/>`](specs/text-spec.md)
- [`<Image/>`](specs/image-spec.md)

## Understanding the Problem

*tl;dr:* [Crafting a high-performance TV user interface using React](https://netflixtechblog.com/crafting-a-high-performance-tv-user-interface-using-react-3350e5a6ad3b) (and also good to read: [60 FPS on the mobile web](https://engineering.flipboard.com/2015/02/mobile-web))
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"license": "MIT",
"scripts": {
"build": "node ./scripts/rollup",
"wasm": "cd ./packages/react-ape/ape && cargo build --release --target wasm32-unknown-unknown && mv ./target/wasm32-unknown-unknown/release/ape.wasm ./",
"ci": "yarn test && yarn build",
"clean-node-modules": "find ./ -name 'node_modules' -exec rm -rf '{}' +",
"docs-build": "cd ./packages/website && yarn run build",
Expand Down
67 changes: 58 additions & 9 deletions packages/app/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
StyleSheet,
registerComponent,
withNavigation,
} from '../../react-ape/reactApeEntry';
} from '../../react-ape/entry';

import Sidebar from './Sidebar';
import Grid from './Grid';
Expand All @@ -25,6 +25,18 @@ const styles = StyleSheet.create({
},
});

const s = StyleSheet.create({
container: {
backgroundColor: 'aliceblue',
height: 300,
width: 200,
},
child: {
height: 100,
width: 100,
}
});

class App extends Component {
constructor(props) {
super(props);
Expand Down Expand Up @@ -55,14 +67,51 @@ class App extends Component {
return null;
}

return (
<View style={styles.surface}>
<Clock />
<Sidebar />
<Slideshow />
<Grid />
</View>
);
// First problem: Text style hierarchy doesn't work (it should render in orange)
// return (
// <View
// style={{width: 80, height: 80, backgroundColor: 'grey', color: 'navy'}}>
// {/*<View>*/}
// <View style={{width: 40, height: 40, backgroundColor: 'powderblue'}}>
// {/*<View
// style={{
// width: 30,
// height: 30,
// }}>
// <Text>should be in navy</Text>
// </View>*/}
// </View>
// <View style={{width: 40, height: 40, backgroundColor: 'yellow'}}>

// </View>

// {/*</View>*/}
// </View>
// );

// Second problem: Views should be relative to the parent and not the page
return [
<View
style={{width: 50, height: 50, backgroundColor: 'powderblue'}}
/>,
<View
style={{width: 100, height: 100, backgroundColor: 'skyblue'}}
/>,
<View
style={{width: 150, height: 150, backgroundColor: 'steelblue'}}
/>
];

// Third problem: View backgroundColor should propagate to children

// return (
// <View style={styles.surface}>
// <Clock />
// <Sidebar />
// <Slideshow />
// <Grid />
// </View>
// );
}
}

Expand Down
6 changes: 4 additions & 2 deletions packages/app/src/Clock.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
Dimensions,
StyleSheet,
registerComponent,
} from '../../react-ape/reactApeEntry';
} from '../../react-ape/entry';

const {width, height} = Dimensions.get('screen');

Expand Down Expand Up @@ -48,7 +48,9 @@ class Clock extends React.Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.time}>{this.state.time}</Text>
<Text style={styles.time}>
{this.state.time}
</Text>
{/*<Text style={styles.time}>
{this.state.time}
</Text>
Expand Down
8 changes: 1 addition & 7 deletions packages/app/src/Grid.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import React, {Component, useState, useEffect} from 'react';
import {
Text,
View,
Image,
Dimensions,
StyleSheet,
} from '../../react-ape/reactApeEntry';
import {Text, View, Image, Dimensions, StyleSheet} from '../../react-ape/entry';

import Loader from './Loader';

Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/Loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
registerComponent,
StyleSheet,
Dimensions,
} from '../../react-ape/reactApeEntry';
} from '../../react-ape/entry';

import Spinner from './Spinner';

Expand Down
10 changes: 6 additions & 4 deletions packages/app/src/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
StyleSheet,
registerComponent,
withFocus,
} from '../../react-ape/reactApeEntry';
} from '../../react-ape/entry';

const {height} = Dimensions.get('window');

Expand Down Expand Up @@ -39,8 +39,7 @@ class Item extends React.Component {
style={{
color: focused ? '#331A00' : 'white',
fontSize: 24,
}}
>
}}>
{text}
</Text>
</View>
Expand All @@ -64,8 +63,11 @@ class Sidebar extends Component {
text="Rio de Janeiro"
idx={120}
/>
<FocusableItem focusKey="sidebar-item-2" text="Kyoto" idx={160} />
<FocusableItem focusKey="sidebar-item-2" text="京都" idx={160} />
<FocusableItem focusKey="sidebar-item-3" text="Stockholm" idx={200} />
<FocusableItem focusKey="sidebar-item-4" text="Phoenix" idx={240} />
<FocusableItem focusKey="sidebar-item-5" text="नई दिल्ली" idx={280} />
<FocusableItem focusKey="sidebar-item-6" text="Cusco" idx={320} />
{/*</View>*/}
</View>
);
Expand Down
33 changes: 15 additions & 18 deletions packages/app/src/Slideshow.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import React, {Component, useState, useEffect} from 'react';
import {
Text,
View,
Image,
Dimensions,
StyleSheet,
} from '../../react-ape/reactApeEntry';
import {Text, View, Image, Dimensions, StyleSheet} from '../../react-ape/entry';

const {width} = Dimensions.get('window');

Expand Down Expand Up @@ -40,18 +34,21 @@ function Slideshow() {
}
}

React.useEffect(() => {
resetTimeout();
timeoutRef.current = setTimeout(
() =>
setCurrentSlide((prev) => (prev === slides.length - 1 ? 0 : prev + 1)),
delay
);

return () => {
React.useEffect(
() => {
resetTimeout();
};
}, [currentSlide]);
timeoutRef.current = setTimeout(
() =>
setCurrentSlide(prev => (prev === slides.length - 1 ? 0 : prev + 1)),
delay
);

return () => {
resetTimeout();
};
},
[currentSlide]
);

return (
<View style={{...styles.slideshow}}>
Expand Down
14 changes: 13 additions & 1 deletion packages/docs/apis-platform.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,16 @@ title: Platform
sidebar_label: Platform
---

## Platform
When building a cross-platform app, you'll want to re-use as much code as possible. You'll probably have different scenarios where different code might be necessary.

For instance, you may want to implement separated visual components for `LG-webOS` and `Samsung-Tizen`.

React-Ape provides the Platform module to easily organize your code and separate it by platform:

```js
import { Platform } from 'react-ape';

console.log(Platform('webos')); // true
console.log(Platform('tizen')); // false
console.log(Platform('orsay')); // false
```
6 changes: 4 additions & 2 deletions packages/react-ape/__tests__/layout-test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import {render, View} from '../reactApeEntry';
import {render, View} from '../entry';
import testCanvasSnapshot from '../../../tests/testCanvasSnapshot';

describe('Layout test', () => {
describe('Test "relative" and "absolute" <Views/>', () => {
describe('Test "relative" and "absolute" Views', () => {
test('Test 5 views with different positions (4 relative and 1 absolute)', () => {
const canvas = document.createElement('canvas');
canvas.height = 600;
Expand Down Expand Up @@ -68,6 +68,8 @@ describe('Layout test', () => {

render(<Layout />, canvas, () => testCanvasSnapshot(expect, canvas));
});
});
describe('BorderRadius', () => {
test('Test 2 View with BorderRadius', () => {
const canvas = document.createElement('canvas');
canvas.height = 600;
Expand Down
28 changes: 28 additions & 0 deletions packages/react-ape/__tests__/specs/view-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import {render, View, Text, StyleSheet} from '../../entry';

import testCanvasSnapshot from '../../../../tests/testCanvasSnapshot';
import ViewElement from '../../renderer/elements/View';

describe('View Spec', () => {
it('Relative should be respected in Views of the same root level', () => {
const canvas = document.createElement('canvas');
const app = [
<View
style={{width: 50, height: 50, backgroundColor: 'powderblue'}}
/>,
<View
style={{width: 100, height: 100, backgroundColor: 'skyblue'}}
/>,
<View
style={{width: 150, height: 150, backgroundColor: 'steelblue'}}
/>
];

render(app, canvas, () => {
const dataUrl = canvas.toDataURL();
testCanvasSnapshot(expect, canvas);
}
);
});
});
53 changes: 53 additions & 0 deletions packages/react-ape/__tests__/style-hierarchy-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import {render, View} from '../entry';
import testCanvasSnapshot from '../../../tests/testCanvasSnapshot';

describe('Style hierarchy test', () => {
describe('Test style props hierarchy', () => {
test.skip('backgroundColor and color', () => {
const canvas = document.createElement('canvas');
canvas.height = 100;
canvas.width = 100;
class Layout extends React.Component {
constructor(props) {
super(props);
}

render() {
// Text style should render in orange
// View backgroundColor:
// - 2nd View should have same bgc as 1st
// - 4th should have same bgc as 3rd
return (
<View
style={{
width: 80,
height: 80,
backgroundColor: 'grey',
color: 'orange',
}}>
<View>
<View
style={{
width: 80,
height: 80,
backgroundColor: 'powderblue',
}}>
<View
style={{
width: 30,
height: 30,
}}>
<Text>should be in orange</Text>
</View>
</View>
</View>
</View>
);
}
}

render(<Layout />, canvas, () => testCanvasSnapshot(expect, canvas));
});
});
});
2 changes: 1 addition & 1 deletion packages/react-ape/__tests__/text-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import {render, View, Text, StyleSheet} from '../reactApeEntry';
import {render, View, Text, StyleSheet} from '../entry';

import testCanvasSnapshot from '../../../tests/testCanvasSnapshot';
import ViewElement from '../renderer/elements/View';
Expand Down
Loading