| -- Copyright 2020 The Fuchsia Authors. All rights reserved. |
| -- Use of this source code is governed by a BSD-style license that can be |
| -- found in the LICENSE file. |
| |
| |
| module Main exposing (main) |
| |
| {-| Frontend SPA for fidlbolt. |
| -} |
| |
| import Browser |
| import Deployment |
| import Editors |
| import Html exposing (Html) |
| import Html.Attributes as Attributes |
| import Html.Events as Events |
| import Html.Lazy as Lazy |
| import Json.Decode as Decode |
| import Json.Encode as Encode |
| import News |
| import Ports |
| import Settings |
| import Share |
| import SplitPane |
| import Window |
| |
| |
| |
| ------------- MAIN ------------------------------------------------------------- |
| |
| |
| main : Program Decode.Value Model Msg |
| main = |
| Browser.element |
| { init = init |
| , update = update |
| , subscriptions = subscriptions |
| , view = view |
| } |
| |
| |
| |
| ------------- MODEL ------------------------------------------------------------ |
| |
| |
| type alias Model = |
| { deployment : Deployment.Model |
| , share : Share.Model |
| , editors : Editors.Model |
| , settings : Settings.Model |
| , split : SplitPane.Model |
| , news : News.Model |
| , window : Window.Model Window |
| } |
| |
| |
| type alias Flags = |
| { model : Maybe Model |
| , preferredScheme : Settings.Scheme |
| } |
| |
| |
| type Window |
| = HelpWindow |
| | SettingsWindow |
| | ShareWindow |
| |
| |
| init : Decode.Value -> ( Model, Cmd Msg ) |
| init savedValue = |
| let |
| model = |
| case savedValue |> Decode.decodeValue decodeFlags of |
| Err _ -> |
| -- Failed to decode flags passed from JS. Use defaults. |
| defaultModel Settings.defaultScheme |
| |
| Ok flags -> |
| case flags.model of |
| Nothing -> |
| -- There is no saved model (brand new session). Use |
| -- the defaults, respecting the user's preferred |
| -- light/dark color scheme. |
| defaultModel flags.preferredScheme |
| |
| Just savedModel -> |
| -- Use the saved model. Only update the preferred |
| -- color scheme, which might have changed since the |
| -- last time (e.g. macOS auto dark mode). |
| let |
| savedSettings = |
| savedModel.settings |
| |
| newSettings = |
| { savedSettings |
| | preferredScheme = |
| flags.preferredScheme |
| } |
| in |
| { savedModel | settings = newSettings } |
| in |
| ( model, initCmd model ) |
| |
| |
| initCmd : Model -> Cmd Msg |
| initCmd model = |
| let |
| layout = |
| Settings.getLayout model.settings |
| in |
| Cmd.batch |
| [ Cmd.map DeploymentMsg Deployment.initCmd |
| , Cmd.map EditorMsg (Editors.initCmd model.editors) |
| , Cmd.map SettingsMsg (Settings.initCmd model.settings) |
| , Cmd.map SplitMsg (SplitPane.initCmd layout) |
| ] |
| |
| |
| defaultModel : Settings.Scheme -> Model |
| defaultModel scheme = |
| let |
| settings = |
| Settings.init scheme |
| |
| layout = |
| Settings.getLayout settings |
| in |
| { deployment = Deployment.init |
| , share = Share.init |
| , editors = Editors.init |
| , split = SplitPane.init layout 0.5 splitId |
| , settings = settings |
| , news = News.init |
| , window = Window.init |
| } |
| |
| |
| splitId : String |
| splitId = |
| "SplitContainer" |
| |
| |
| |
| ------------- UPDATE ----------------------------------------------------------- |
| |
| |
| type Msg |
| = DeploymentMsg Deployment.Msg |
| | EditorMsg Editors.Msg |
| | SettingsMsg Settings.Msg |
| | ShareMsg Share.Msg |
| | SplitMsg SplitPane.Msg |
| | NewsMsg News.Msg |
| | WindowMsg (Window.Msg Window) |
| |
| |
| update : Msg -> Model -> ( Model, Cmd Msg ) |
| update msg model = |
| case msg of |
| DeploymentMsg deploymentMsg -> |
| let |
| newDeployment = |
| Deployment.update deploymentMsg model.deployment |
| |
| newModel = |
| { model | deployment = newDeployment } |
| in |
| ( newModel, Cmd.none ) |
| |
| EditorMsg editorMsg -> |
| let |
| ( newEditors, editorCmd ) = |
| Editors.update editorMsg model.editors |
| |
| newModel = |
| { model | editors = newEditors } |
| in |
| ( newModel |
| , Cmd.batch |
| [ editorCmd |
| |
| -- Resize editors in case the options bar appeared/disappeared. |
| , Ports.resizeEditors |
| , persistModel newModel |
| ] |
| ) |
| |
| SettingsMsg settingsMsg -> |
| let |
| ( newSettings, settingsCmd ) = |
| Settings.update settingsMsg model.settings |
| |
| layout = |
| Settings.getLayout newSettings |
| |
| ( newSplit, splitCmd ) = |
| SplitPane.update (SplitPane.SetLayout layout) model.split |
| |
| newModel = |
| { model | settings = newSettings, split = newSplit } |
| |
| cmds = |
| if settingsMsg == Settings.ClearDataAndReload then |
| -- We're about to reload, so avoid other commands which |
| -- could persist the model after we clear localStorage |
| -- but before the browser reloads the page. |
| [ settingsCmd ] |
| |
| else |
| [ settingsCmd |
| , Cmd.map SplitMsg splitCmd |
| , Ports.resizeEditors |
| , persistModel newModel |
| ] |
| in |
| ( newModel |
| , Cmd.batch cmds |
| ) |
| |
| ShareMsg shareMsg -> |
| let |
| ( newShare, shareCmd ) = |
| Share.update shareMsg model.share |
| |
| newModel = |
| { model | share = newShare } |
| in |
| ( newModel |
| , shareCmd |
| ) |
| |
| SplitMsg splitMsg -> |
| let |
| ( newSplit, splitCmd ) = |
| SplitPane.update splitMsg model.split |
| |
| newModel = |
| { model | split = newSplit } |
| |
| cmds = |
| [ Cmd.map SplitMsg splitCmd |
| , case splitMsg of |
| SplitPane.AutoOrient _ -> |
| Cmd.batch |
| [ Ports.resizeEditors |
| , persistModel newModel |
| ] |
| |
| SplitPane.DragMove _ _ -> |
| Ports.resizeEditors |
| |
| SplitPane.DragStop _ -> |
| Cmd.batch |
| [ Ports.resizeEditors |
| , persistModel newModel |
| ] |
| |
| _ -> |
| Cmd.none |
| ] |
| in |
| ( newModel, Cmd.batch cmds ) |
| |
| NewsMsg newsMsg -> |
| let |
| newModel = |
| { model | news = News.update newsMsg model.news } |
| in |
| ( newModel, persistModel newModel ) |
| |
| WindowMsg windowMsg -> |
| let |
| newWindow = |
| Window.update windowMsg model.window |
| |
| newModel = |
| { model | window = newWindow } |
| |
| cmds = |
| [ Ports.setMainTabEnabled (Window.isAbsent newWindow) |
| , case windowMsg of |
| Window.Show HelpWindow -> |
| Cmd.map DeploymentMsg Deployment.checkTime |
| |
| Window.Show ShareWindow -> |
| Ports.updateShareLink |
| |
| _ -> |
| Cmd.none |
| ] |
| in |
| ( newModel |
| , Cmd.batch cmds |
| ) |
| |
| |
| persistModel : Model -> Cmd msg |
| persistModel model = |
| Ports.persistModel (encode model) |
| |
| |
| |
| ------------- SUBSCRIPTIONS ---------------------------------------------------- |
| |
| |
| subscriptions : Model -> Sub Msg |
| subscriptions model = |
| let |
| layout = |
| Settings.getLayout model.settings |
| in |
| Sub.batch |
| [ Sub.map DeploymentMsg Deployment.subscriptions |
| , Sub.map ShareMsg Share.subscriptions |
| , Sub.map SplitMsg (SplitPane.subscriptions model.split layout) |
| , Sub.map WindowMsg (Window.subscriptions model.window) |
| ] |
| |
| |
| |
| ------------- VIEW ------------------------------------------------------------- |
| |
| |
| view : Model -> Html Msg |
| view model = |
| let |
| modalWindow = |
| Window.view (windowView model) WindowMsg model.window |
| |
| themeClass = |
| Settings.themeClass model.settings |
| |
| inputEditor = |
| Lazy.lazy2 (Editors.view Editors.Input EditorMsg) |
| themeClass |
| model.editors |
| |
| outputEditor = |
| Lazy.lazy2 (Editors.view Editors.Output EditorMsg) |
| themeClass |
| model.editors |
| |
| splitPane = |
| SplitPane.view inputEditor outputEditor SplitMsg model.split |
| in |
| Html.div [] |
| [ modalWindow |
| , Html.div |
| [ Attributes.id "Main" ] |
| [ controlsView model |
| , Html.div |
| [ Attributes.class "wrapper" ] |
| [ headingView |
| , News.view NewsMsg model.news |
| , Html.div |
| [ Attributes.class "container" ] |
| [ splitPane ] |
| ] |
| ] |
| ] |
| |
| |
| windowView : Model -> Window -> Html Msg |
| windowView model window = |
| case window of |
| HelpWindow -> |
| Html.div [] |
| [ Html.h2 [] [ Html.text "Help" ] |
| , helpMessage model.deployment model.split.orientation |
| ] |
| |
| SettingsWindow -> |
| Html.div [] |
| [ Html.h2 [] [ Html.text "Settings" ] |
| , Settings.view SettingsMsg model.settings |
| ] |
| |
| ShareWindow -> |
| Html.div [] |
| [ Html.h2 [] [ Html.text "Share" ] |
| , Share.view model.share |
| ] |
| |
| |
| headingView : Html Msg |
| headingView = |
| Html.div |
| [ Attributes.class "heading" ] |
| [ Html.h1 [] |
| [ Html.span [ Attributes.class "heading-fidl" ] [ Html.text "fidl" ] |
| , Html.span [ Attributes.class "heading-bolt" ] [ Html.text "bolt" ] |
| ] |
| ] |
| |
| |
| controlsView : Model -> Html Msg |
| controlsView model = |
| let |
| button title msg = |
| Html.button |
| [ Attributes.class "button controls-button" |
| , Attributes.type_ "button" |
| , Events.onClick msg |
| ] |
| [ Html.text title ] |
| |
| toggle window = |
| if Window.isAbsent model.window then |
| WindowMsg (Window.Show window) |
| |
| else |
| WindowMsg Window.Hide |
| in |
| Html.div |
| [ Attributes.class "controls-container" ] |
| [ button "Share" (toggle ShareWindow) |
| , button "Settings" (toggle SettingsWindow) |
| , button "Help" (toggle HelpWindow) |
| ] |
| |
| |
| helpMessage : Deployment.Model -> SplitPane.Orientation -> Html msg |
| helpMessage deployment orientation = |
| let |
| text = |
| Html.text |
| |
| multiline = |
| Html.text << String.trim |
| |
| bold content = |
| Html.b [] [ text content ] |
| |
| kbd keys = |
| Html.span |
| [ Attributes.class "kbd-group" ] |
| (List.map (\key -> Html.kbd [] [ text key ]) keys) |
| |
| ( inputLocation, outputLocation ) = |
| case orientation of |
| SplitPane.Horizontal -> |
| ( "on top", "below" ) |
| |
| SplitPane.Vertical -> |
| ( "on the left", "on the right" ) |
| in |
| Html.div [] |
| [ Html.p [] |
| [ text "Welcome to " |
| , bold "fidlbolt" |
| , text ", a web app for exploring FIDL code and bytes." |
| ] |
| , Html.p [] |
| [ text "Here are some tips to help you get started:" ] |
| , Html.ul [] |
| [ Html.li [] |
| [ text "The " |
| , bold "input" |
| , text (" editor is " ++ inputLocation ++ ". ") |
| , text "Use the tab bar, or " |
| , kbd [ "Ctrl", "[" ] |
| , text " and " |
| , kbd [ "Ctrl", "]" ] |
| , multiline """ |
| , to choose what kind of input you have. Then, type away in the editor. |
| """ |
| ] |
| , Html.li [] |
| [ text "The " |
| , bold "output" |
| , text (" view is " ++ outputLocation ++ ". ") |
| , multiline """ |
| Use the tab bar to choose what kind of output you want. For example, from FIDL |
| input you can convert to FIDL (which just formats it), or to HLCPP to generate |
| C++ bindings. Some outputs also have options along the bottom. |
| """ |
| ] |
| , Html.li [] |
| [ text "To " |
| , bold "refresh" |
| , text " the output, press " |
| , kbd [ "Ctrl", "Enter" ] |
| , text " or (on macOS) " |
| , kbd [ "Cmd", "Enter" ] |
| , text ". " |
| , multiline """ |
| Alternatively, just wait a few moments — the output will automatically refresh |
| after you stop typing. |
| """ |
| ] |
| , Html.li [] |
| [ text "Use the " |
| , bold "Version" |
| , text " " |
| , multiline """ |
| text field under the FIDL editor to choose versions. For example, |
| “test:7 fuchsia:HEAD” selects version 7 of platform “test” and the HEAD version |
| of Fuchsia. See |
| """ |
| , text " " |
| , Html.a [ Attributes.href rfc0083Url ] [ text "RFC-0083" ] |
| , text " for details." |
| ] |
| , Html.li [] |
| [ text "Click the " |
| , bold "Share" |
| , text " " |
| , multiline """ |
| button to generate a link you can share with other people. This link saves |
| everything from the FIDL editor. |
| """ |
| ] |
| , Html.li [] |
| [ text "Click the " |
| , bold "Settings" |
| , text " " |
| , multiline """ |
| button to configure things. For example, you can try out different color themes, |
| or use keybindings from your favorite editor. |
| """ |
| ] |
| , Html.li [] |
| [ text "The app " |
| , bold "autosaves" |
| , text " " |
| , multiline """ |
| everything to your browser’s local storage. You can always start fresh by |
| clicking “Reset all data” in Settings. |
| """ |
| ] |
| , Html.li [] |
| [ text "To " |
| , bold "close" |
| , text " this help window, press " |
| , kbd [ "Esc" ] |
| , text " or " |
| , kbd [ "Enter" ] |
| , text "." |
| ] |
| ] |
| , Html.p [] |
| [ text "Enjoy! If you find a bug, please " |
| , Html.a [ Attributes.href bugReportUrl ] |
| [ text "report it here" ] |
| , text "." |
| ] |
| , Html.hr [] [] |
| , Deployment.view deployment |
| ] |
| |
| |
| rfc0083Url : String |
| rfc0083Url = |
| "https://fuchsia.dev/fuchsia-src/contribute/governance/rfcs/0083_fidl_versioning" |
| |
| |
| bugReportUrl : String |
| bugReportUrl = |
| "https://bugs.fuchsia.dev/p/fuchsia/issues/entry?components=FIDL%3EFidlbolt" |
| |
| |
| |
| ------------- ENCODE / DECODE -------------------------------------------------- |
| |
| |
| encode : Model -> Encode.Value |
| encode model = |
| Encode.object |
| [ ( "editors", Editors.encode model.editors ) |
| , ( "settings", Settings.encode model.settings ) |
| , ( "split", SplitPane.encode model.split ) |
| , ( "news", News.encode model.news ) |
| ] |
| |
| |
| decode : Decode.Decoder Model |
| decode = |
| Decode.map7 Model |
| (Decode.succeed Deployment.init) |
| (Decode.succeed Share.init) |
| (Decode.field "editors" Editors.decode) |
| (Decode.field "settings" Settings.decode) |
| (Decode.field "split" (SplitPane.decode splitId)) |
| (Decode.field "news" News.decode) |
| (Decode.succeed Window.init) |
| |
| |
| decodeFlags : Decode.Decoder Flags |
| decodeFlags = |
| Decode.map2 Flags |
| (Decode.field "model" (Decode.maybe decode)) |
| (Decode.field "preferredScheme" Settings.decodeScheme) |