SAFE-Stack / SAFE-Nightwatch

Demo of SAFE-Stack applied to React Native for cross platform native mobile apps
The Unlicense
157 stars 29 forks source link

attempt to extend with Mapbox #57

Closed ctaggart closed 6 years ago

ctaggart commented 6 years ago

I've tried extending this to use Mapbox on a new page as part of my tech evaluation. If I can get it to work (very soon), I'll use Fable, if not I have to use TypeScript.

https://www.mapbox.com/help/first-steps-react-native-sdk/

The problem I'm stuck on leads to this error: image

module Maps

open Fable.Helpers.React
open Fable.Helpers.ReactNative
open Elmish
open ReactNativeMapbox
open ReactNativeMapbox.Mapbox
open Fable.Core.JsInterop
open Fable.Import.React

// Model
type Msg =
| TODO
| A

type Model = { StatusText : string  }

// Update
let update (msg:Msg) model : Model*Cmd<Msg> =
    match msg with
    | TODO -> model, Cmd.none
    | A -> model, Cmd.none

let init () =
    { StatusText = "" }, Cmd.Empty

let inline mapView (props: MapViewPropTypes) (children: ReactElement list): ReactElement =
     ofType<MapView,MapViewPropTypes,obj> props children

// View
let view (model:Model) (dispatch: Msg -> unit) =

    let mvprops =
        jsOptions<MapViewPropTypes>(fun o ->
            o.showsUserLocation <- Some true
            o.initialZoomLevel <- Some 12.
            // o.userTrackingMode <- Some UserTrackingMode.Follow // TODO
            o.userTrackingMode <- Some 1.
        )

    view [Styles.sceneBackground] [ mapView mvprops [] ]
// ts2fable 0.6.0-build.320
module rec ReactNativeMapbox
open System
open Fable.Core
open Fable.Import.JS

open Fable.Import

type Component<'P,'S> = React.Component<'P,'S>
type ViewProperties = ReactNative.ViewProperties
// type NativeModules = ReactNative.NativeModules
// type EmitterSubscription = ReactNative.EmitterSubscription

// let [<Import("*","mapbox")>] mapbox: Mapbox.IExports = jsNative
let [<Import("*","@mapbox/react-native-mapbox-gl")>] mapbox: Mapbox.IExports = jsNative

type [<AllowNullLiteral>] MapStyles =
    abstract streets: string with get, set
    abstract dark: string with get, set
    abstract light: string with get, set
    abstract satellite: string with get, set
    abstract hybrid: string with get, set
    abstract emerald: string with get, set

type [<RequireQualifiedAccess>] UserTrackingMode =
    | None = 0
    | Follow = 1
    | FollowWithCourse = 2
    | FollowWithHeading = 3

type [<RequireQualifiedAccess>] UserLocationVerticalAlignment =
    | Center = 0
    | Top = 1
    | Bottom = 2

// type [<AllowNullLiteral>] MapViewPropTypes =
type MapViewPropTypes =
    inherit ViewProperties
    abstract initialZoomLevel: float option with get, set
    abstract initialDirection: float option with get, set
    abstract initialCenterCoordinate: obj with get, set
    abstract clipsToBounds: bool option with get, set
    abstract debugActive: bool option with get, set
    abstract rotateEnabled: bool option with get, set
    abstract scrollEnabled: bool option with get, set
    abstract zoomEnabled: bool option with get, set
    abstract minimumZoomLevel: float option with get, set
    abstract maximumZoomLevel: float option with get, set
    abstract pitchEnabled: bool option with get, set
    abstract annotationsPopUpEnabled: bool option with get, set
    abstract showsUserLocation: bool option with get, set
    abstract styleURL: string with get, set
    abstract userTrackingMode: float option with get, set
    abstract attributionButtonIsHidden: bool option with get, set
    abstract logoIsHidden: bool option with get, set
    abstract compassIsHidden: bool option with get, set
    abstract userLocationVerticalAlignment: float option with get, set
    abstract contentInset: U2<float, ResizeArray<string>> option with get, set
    abstract annotations: ResizeArray<Annotations> option with get, set
    abstract annotationsAreImmutable: bool option with get, set
    abstract onRegionDidChange: (unit -> unit) option with get, set
    abstract onRegionWillChange: (unit -> unit) option with get, set
    abstract onOpenAnnotation: (unit -> unit) option with get, set
    abstract onCloseAnnotation: (unit -> unit) option with get, set
    abstract onUpdateUserLocation: (unit -> unit) option with get, set
    abstract onRightAnnotationTapped: (unit -> unit) option with get, set
    abstract onFinishLoadingMap: (unit -> unit) option with get, set
    abstract onStartLoadingMap: (unit -> unit) option with get, set
    abstract onLocateUserFailed: (unit -> unit) option with get, set
    abstract onLongPress: (unit -> unit) option with get, set
    abstract onTap: (unit -> unit) option with get, set
    abstract onChangeUserTrackingMode: (unit -> unit) option with get, set

type [<AllowNullLiteral>] Annotations =
    /// For type polyline and polygon must be an array of arrays. For type point, array as [latitude longitude]
    abstract coordinates: U2<ResizeArray<float>, ResizeArray<float>> with get, set
    abstract ``type``: U3<string, string, string> with get, set
    /// Unique identifier used for adding or selecting an annotation.
    abstract id: string with get, set
    /// Title string. Appears when marker pressed
    abstract title: string option with get, set
    abstract subtitle: string option with get, set
    /// Only for type=polygon. Controls the opacity of the polygon
    abstract fillAlpha: float option with get, set
    /// Only for type=polygon. CSS color (#rrggbb). Controls the fill color of the polygon
    abstract fillColor: string option with get, set
    /// Only for type=polygon or type=polyline. Controls the opacity of the line
    abstract strokeAlpha: float option with get, set
    /// Only for type=polygon or type=polyline. CSS color (#rrggbb). Controls line color.
    abstract strokeColor: string option with get, set
    /// Only for type=polygon or type=polyline. Controls line width.
    abstract strokeWidth: float option with get, set
    /// Marker image for type=point
    abstract annotationImage: obj option with get, set
    /// iOS only. Clickable image that appears when type=point marker pressed
    abstract rightCalloutAccessory: obj option with get, set

type [<AllowNullLiteral>] CameraPosition =
    abstract latitude: float option with get, set
    abstract longitude: float option with get, set
    /// Can't be specified at the same time with altitude
    abstract zoomLevel: float option with get, set
    abstract direction: float option with get, set
    /// On iOS, pitch can't be specified at the same time as zoomLevel. altitude must be used instead.
    abstract pitch: float option with get, set
    /// Not available on android, use zoomLevel instead.
    abstract altitude: float option with get, set

type [<AllowNullLiteral>] OfflinePackOptions =
    abstract name: string with get, set
    abstract ``type``: string with get, set
    /// You can put any information in here that may be useful to you
    abstract metadata: obj option with get, set
    /// The corners of the bounded rectangle region being saved offline
    /// [latitudeSW, longitudeSW, latitudeNE, longitudeNE]
    abstract bounds: ResizeArray<float> with get, set
    abstract minZoomLevel: float with get, set
    abstract maxZoomLevel: float with get, set
    abstract styleURL: MapStyles with get, set

type [<AllowNullLiteral>] Progress =
    /// The name this pack was registered with
    abstract name: string with get, set
    /// The value that was previously passed as metadata
    abstract metadata: obj option with get, set
    /// The number of bytes downloaded for this pack
    abstract countOfBytesCompleted: float with get, set
    /// The number of tiles that have been downloaded for this pack
    abstract countOfResourcesCompleted: float with get, set
    /// The estimated minimum number of total tiles in this pack
    abstract countOfResourcesExpected: float with get, set
    /// The estimated maximum number of total tiles in this pack
    abstract maximumResourcesExpected: float with get, set

// [<Import("*","@mapbox/react-native-mapbox-gl")>]
module Mapbox =
    open Fable.Import.React

    type [<AllowNullLiteral>] IExports =
        abstract MapView: MapViewStatic
        // abstract Annotation: AnnotationStatic
        abstract mapStyles: MapStyles
        abstract userTrackingMode: UserTrackingMode
        abstract userLocationVerticalAlignment: UserLocationVerticalAlignment
        abstract unknownResourceCount: float
        abstract getMetricsEnabled: unit -> bool
        abstract setMetricsEnabled: enabled: bool -> unit
        abstract setAccessToken: token: string -> Promise<unit>
        abstract setConnected: connected: bool -> unit
        /// Offline Methods.
        /// Before using offline packs, you must call Mapbox.initializeOfflinePacks().
        abstract initializeOfflinePacks: unit -> Promise<unit>
        abstract addOfflinePack: options: OfflinePackOptions * callback: (unit -> unit) -> Promise<unit>
        abstract getOfflinePacks: callback: (ResizeArray<Progress> -> unit) -> Promise<ResizeArray<Progress>>
        abstract removeOfflinePack: callback: (unit -> unit) -> Promise<unit>
        // abstract addOfflinePackProgressListener: handler: (Progress -> unit) -> EmitterSubscription
        // abstract addOfflineMaxAllowedTilesListener: handler: (unit -> unit) -> EmitterSubscription
        // abstract addOfflineErrorListener: handler: (unit -> unit) -> EmitterSubscription
        abstract setOfflinePackProgressThrottleInterval: milis: float -> unit

    // type [<AllowNullLiteral>] MapView =
    // type MapView() =
    // type MapView =
    // [<Import("MapView","@mapbox/react-native-mapbox-gl/components")>]
    // [<Import("components/MapView","@mapbox/react-native-mapbox-gl")>]
    [<Import("MapView","@mapbox/react-native-mapbox-gl")>]
    type MapView(props) =
        // inherit Component<MapViewPropTypes>
        inherit Component<MapViewPropTypes,obj>(props)

        /// Viewport setters
        member __.setDirection: direction: float * ?animated: bool * ?callback: (unit -> unit) -> Promise<unit> = jsNative
        member __.setZoomLevel: zoomLevel: float * ?animated: bool * ?callback: (unit -> unit) -> Promise<unit> = jsNative
        member __.setPitch: pitch: float * ?animated: bool * ?callback: (unit -> unit) -> Promise<unit> = jsNative
        member __.setCenterCoordinate: latitude: float * longitude: float * ?animated: bool * ?callback: (unit -> unit) -> Promise<unit> = jsNative latitude: float * longitude: float * zoomLevel: float * ?animated: bool * ?callback: (unit -> unit) -> Promise<unit> = jsNative
        member __.setCenterCoordinateZoomLevelPitch: latitude: float * longitude: float * zoomLevel: float * pitch: float * ?animated: bool * ?callback: (unit -> unit) -> Promise<unit> = jsNative
        member __.easeTo: options: CameraPosition * ?animated: bool * ?callback: (unit -> unit) -> Promise<unit> = jsNative
        /// Adjusts the center location and the zoomLevel of the map so that the rectangle determined by latitudeSW,
        /// longitudeSW, latitudeNE, longitudeNE fits inside the viewport.
        /// You can optionally pass a minimum padding (in screen points) that will be visible around the given coordinate bounds.
        /// The transition is animated unless you pass animated as false
        member __.setVisibleCoordinateBounds: latitudeSW: bool * longitudeSW: bool * latitudeNE: bool * longitudeNE: bool * ?paddingTop: bool * ?paddingRight: bool * ?paddingBottom: bool * ?paddingLeft: bool * ?animated: bool -> unit = jsNative
        /// Getters
        member __.getCenterCoordinateZoomLevel: callback: (unit -> unit) -> unit = jsNative
        member __.getDirection: callback: (unit -> unit) -> unit = jsNative
        member __.getBounds: callback: (unit -> unit) -> unit = jsNative
        member __.getPitch: callback: (unit -> unit) -> unit = jsNative
        /// Others
        member __.selectAnnotation: annotationId: string * ?animated: bool -> unit = jsNative
        member __.deselectAnnotation: unit -> unit = jsNative

    type [<AllowNullLiteral>] MapViewStatic =
        [<Emit "new $0($1...)">] abstract Create: unit -> MapView

    // type [<AllowNullLiteral>] Annotation =
    //     inherit Component

    // type [<AllowNullLiteral>] AnnotationStatic =
    //     [<Emit "new $0($1...)">] abstract Create: unit -> Annotation

cc @forki @alfonsogarciacaro

forki commented 6 years ago

I can give you our map bindings if you like and you can compare.

forki commented 6 years ago

Scratch that. Our map is for react proper. But I assume we need to look at the other plugins that I wrote to see how these manual bindings differ. One thing I that I didn't saw before is the ofType instantiation. I don't think we do it like that

ctaggart commented 6 years ago

ofType looks to wrap createElement. Yes, I'd be curious how you do it. This is based on this example https://github.com/mapbox/react-native-mapbox-gl/blob/master/example/src/components/ShowMap.js

forki commented 6 years ago

https://github.com/fable-compiler/fable-react-native/blob/master/src/extra/react-native-signature-view/Fable.Helpers.ReactNativeSignatureView.fs is the simplest thing I know. There are more samples in that folder.

forki commented 6 years ago

Also you always have to do the manual linking steps that are explained on the package readme

ctaggart commented 6 years ago

Thanks, that gives me some ideas. Will try them in the morning.

ctaggart commented 6 years ago

It is working for me now. 😅

The main changes were:

forki commented 6 years ago

Cool. So this means you are doing elmish react native? Would be very cool.

Cameron Taggart notifications@github.com schrieb am Di., 27. März 2018, 23:45:

It is working for me now. 😅

The main changes were:

  • import default

let [Import("default","@mapbox/react-native-mapbox-gl")] mapbox: Mapbox.IExports = jsNative

  • MapView inherits from ComponentClass

    type MapView = inherit ComponentClass

    abstract setDirection: direction: float * ?animated: bool * ?callback: (unit -> unit) -> Promise<unit>
  • reference MapView not the static wrapper

    //abstract MapView: MapViewStatic
    abstract MapView: MapView
  • The MapView.style is set to flex: Looks kind of ugly, but it works for now:

type StyleProps = abstract flex: float option with get, set

let style = Fable.Import.ReactNative.Globals.StyleSheet.create(jsOptions<StyleProps>(fun o -> o.flex <- Some 1.)) :> obj :?> ViewStyle

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/SAFE-Stack/SAFE-Nightwatch/issues/57#issuecomment-376686285, or mute the thread https://github.com/notifications/unsubscribe-auth/AADgNCwJ4kTM74YMbP7PPPawpW7cUMLWks5tirMCgaJpZM4S8RVy .

ctaggart commented 6 years ago

Cool. So this means you are doing elmish react native? Would be very cool.

I am. I have a short contract to make LaneSpotter work on Android.