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

Use virtual-dom + main instead of VTree + ports #23

Closed
wants to merge 17 commits into from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
104 changes: 104 additions & 0 deletions AppWrapper.js
@@ -0,0 +1,104 @@
'use strict';
require('./browser-polyfills');


const React = require('react-native');
const {
AppRegistry,
StyleSheet,
Text,
View,
} = React;

const Elm = require('./elm');
const ReactComponents = require('./ReactComponents');

// Returns a `make` function that will patch the local elm runtime and
// replace the `render` and `update` functions in localRuntime.Native.VirtualDom
// with implementations that call `setState` on the `AppWrapper` component instance

function runtimePatcher(appWrapperInstance) {
function make(localRuntime) {
function render(state) {
console.log('rendering new state: ', state);
appWrapperInstance.setState({vdom: state});
return appWrapperInstance;
}

function updateAndReplace(elem, oldState, newState) {
return render(newState);
}

var VirtualDom = Elm.Native.VirtualDom.make(localRuntime);
localRuntime.Native.VirtualDom.values = {
...VirtualDom,
render: render,
updateAndReplace: updateAndReplace,
patchedForReactNative: true,
};

return Elm.Main.make(localRuntime);
}

return make;
}


const AppWrapper = React.createClass({
componentWillMount() {
Elm.fullscreen({make: runtimePatcher(this)});
},

getInitialState() {
return {vdom: null};
},

render() {
let vdom = this.state.vdom;
return React.createElement(View, {style: styles.container},
vDomToReactElement(vdom)
);
}
});


function getNested(obj, path) {
if (typeof(obj) !== 'object' || typeof(path) !== 'string') {
return undefined;
}
let pathComponents = path.split('.');
while (typeof(obj) === 'object') {
let p = pathComponents.shift();
obj = obj[p];
}
return obj;
}

function vDomToReactElement(vdomNode) {
if (!vdomNode) {
return undefined;
}

if (typeof(vdomNode.text) === 'string') {
return vdomNode.text;
}

let {tagName, properties, key} = vdomNode;
properties = {...properties, key};
let children = vdomNode.children || [];
children = children.map(vDomToReactElement);
let reactClass = getNested(ReactComponents, tagName);
return React.createElement(reactClass, properties, ...children);
}



const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});

export default AppWrapper;
12 changes: 12 additions & 0 deletions BundledAssets.js
@@ -0,0 +1,12 @@
/*
* The providesModule directive makes this file available to `require` using an absolute path.
* This lets us require it from Elm 'Native' code, so we can pull the bundled assets out and
* pass them into Elm without having to rely on `elm.js` always being at the project root.
*
@providesModule BundledAssets
*/

module.exports = {
'elm-native-160.png': require('./img/elm-native-160.png'),
'waiting.gif': require('./img/waiting.gif'),
}
93 changes: 17 additions & 76 deletions Main.elm
@@ -1,83 +1,24 @@
module Main where
module Main (..) where

import Time
import Signal
import Json.Encode
import ReactNative.ReactNative as RN
import ReactNative.NativeApp as NativeApp
import ReactNative.Style as Style exposing ( defaultTransform )
import Effects exposing (Never)
import RandomGif exposing (init, update, view)
import StartApp
import Task


-- "main"
port viewTree : Signal Json.Encode.Value
port viewTree =
NativeApp.start { model = model, view = view, update = update, init = init }
app =
StartApp.start
{ init = init "funny cats"
, update = update
, view = view
, inputs = []
}


type alias Model = Int
main =
app.html


model : Model
model = 9000


view : Signal.Address Action -> Model -> RN.VTree
view address count =
RN.view
[ Style.alignItems "center"
]
[ RN.image
[ Style.height 64
, Style.width 64
, Style.marginBottom 30
]
"https://raw.githubusercontent.com/futurice/spiceprogram/master/assets/img/logo/chilicorn_no_text-128.png"
, RN.text
[ Style.textAlign "center"
, Style.marginBottom 30
]
Nothing
("Counter: " ++ toString count)
, RN.view
[ Style.width 80
, Style.flexDirection "row"
, Style.justifyContent "space-between"
]
[ button address Decrement "#d33" "-"
, button address Increment "#3d3" "+"
]
]


type Action = Increment | Decrement


update : Action -> Model -> Model
update action model =
case action of
Increment -> model + 1
Decrement -> model - 1


button : Signal.Address Action -> Action -> String -> String -> RN.VTree
button address action color content =
RN.text
[ Style.color "white"
, Style.textAlign "center"
, Style.backgroundColor color
, Style.paddingTop 5
, Style.paddingBottom 5
, Style.width 30
, Style.fontWeight "bold"
, Style.shadowColor "#000"
, Style.shadowOpacity 0.25
, Style.shadowOffset 1 1
, Style.shadowRadius 5
, Style.transform { defaultTransform | rotate = Just "10deg" }
]
(Just <| RN.onPress address action)
content


-- for the first vtree
port init : Signal ()
port tasks : Signal (Task.Task Never ())
port tasks =
app.tasks
122 changes: 122 additions & 0 deletions RandomGif.elm
@@ -0,0 +1,122 @@
module RandomGif (..) where

import Effects exposing (Effects, Never)
import ReactNative.ReactNative as RN
import ReactNative.Style as Style
import Http
import Json.Decode as Json
import Task


-- MODEL


type alias Model =
{ topic : String
, gifUrl : String
}


init : String -> ( Model, Effects Action )
init topic =
( Model topic "image!waiting.gif"
, getRandomGif topic
)



-- UPDATE


type Action
= RequestMore
| NewGif (Maybe String)


update : Action -> Model -> ( Model, Effects Action )
update action model =
case action of
RequestMore ->
( model, getRandomGif model.topic )

NewGif maybeUrl ->
( Model model.topic (Maybe.withDefault model.gifUrl maybeUrl)
, Effects.none
)



-- VIEW


view : Signal.Address Action -> Model -> RN.Node
view address model =
RN.view
[ RN.style
[ Style.flex 1
, Style.flexDirection "column"
, Style.alignItems "center"
, Style.justifyContent "center"
]
]
[ RN.text [ headerStyle ] model.topic
, RN.image [ imgStyle, RN.imageSource model.gifUrl ] []
, moreButton address
]


moreButton : Signal.Address Action -> RN.Node
moreButton address =
RN.text
[ RN.onPress address RequestMore
, RN.style
[ Style.backgroundColor "#ccc"
, Style.borderRadius 5
, Style.margin 10
, Style.padding 10
]
]
"More Please!"


headerStyle : RN.Property
headerStyle =
RN.style
[ Style.fontSize 36
, Style.textAlign "center"
]


imgStyle : RN.Property
imgStyle =
RN.style
[ Style.width 200
, Style.height 200
, Style.resizeMode "cover"
]



-- EFFECTS


getRandomGif : String -> Effects Action
getRandomGif topic =
Http.get decodeUrl (randomUrl topic)
|> Task.toMaybe
|> Task.map NewGif
|> Effects.task


randomUrl : String -> String
randomUrl topic =
Http.url
"http://api.giphy.com/v1/gifs/random"
[ ( "api_key", "dc6zaTOxFJmzC" )
, ( "tag", topic )
]


decodeUrl : Json.Decoder String
decodeUrl =
Json.at [ "data", "image_url" ] Json.string
26 changes: 26 additions & 0 deletions ReactComponents.js
@@ -0,0 +1,26 @@
module.exports = {
React: require('react-native'),

// To render a custom component from elm, you can add it to this object, then write an
// Elm function that calls ReactNative.node "AppComponents.NameOfComponent".
//
// For example, you might set up AppComponents to look like this:
//
// AppComponents: {
// FancyButton: require('./components/FancyButton'),
// }
//
// Then in elm, you could define a function that will add that component to the view tree:
//
// fancyButton : List ReactNative.Property -> List ReactNative.Node -> ReactNative.Node
// fancyButton =
// ReactNative.node "AppComponents.FancyButton"
//
// Note that there's nothing special about the name "AppComponents". You can call it whatever
// you want, or put your components into multiple container objects, etc. Whatever name you
// use here should match what you use in Elm.
//
AppComponents: {

}
};