blob: 7b739cb6cdd506f070e9bc3a2f82aeb3974a49ea [file] [log] [blame]
-- 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 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 Ports
import Settings
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 =
{ editors : Editors.Model
, settings : Settings.Model
, split : SplitPane.Model
, window : Window.Model Window
}
type alias Flags =
{ model : Maybe Model
, preferredScheme : Settings.Scheme
}
type Window
= HelpWindow
| SettingsWindow
init : Decode.Value -> ( Model, Cmd Msg )
init savedValue =
let
model =
case savedValue |> Decode.decodeValue decodeFlags of
Err _ ->
defaultModel Settings.defaultScheme
Ok flags ->
case flags.model of
Nothing ->
defaultModel flags.preferredScheme
Just savedModel ->
let
newSettings =
Settings.setPreferredScheme
flags.preferredScheme
savedModel.settings
in
{ savedModel
| settings = newSettings
}
in
( model, initCmd model )
initCmd : Model -> Cmd Msg
initCmd model =
Cmd.batch
[ Editors.initCmd model.editors
, Settings.initCmd model.settings
]
defaultModel : Settings.Scheme -> Model
defaultModel scheme =
{ editors = Editors.init
, split = SplitPane.init SplitPane.Vertical 0.5 splitId
, settings = Settings.init scheme
, window = Window.init
}
splitId : String
splitId =
"SplitContainer"
------------- UPDATE -----------------------------------------------------------
type Msg
= EditorMsg Editors.Msg
| SettingsMsg Settings.Msg
| SplitMsg SplitPane.Msg
| WindowMsg (Window.Msg Window)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
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
newModel =
{ model | settings = newSettings }
in
( newModel
, Cmd.batch [ settingsCmd, persistModel newModel ]
)
SplitMsg splitMsg ->
let
( newSplit, splitCmd ) =
SplitPane.update splitMsg model.split
newModel =
{ model | split = newSplit }
cmds =
[ Cmd.map SplitMsg splitCmd
, case splitMsg of
SplitPane.ToggleOrientation ->
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 )
WindowMsg windowMsg ->
let
newWindow =
Window.update windowMsg model.window
newModel =
{ model | window = newWindow }
in
( newModel
, Ports.setMainTabEnabled (Window.isAbsent newWindow)
)
persistModel : Model -> Cmd msg
persistModel model =
Ports.persistModel (encode model)
------------- SUBSCRIPTIONS ----------------------------------------------------
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ Sub.map SplitMsg (SplitPane.subscriptions model.split)
, 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
, 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.split.orientation
]
SettingsWindow ->
Html.div []
[ Html.h2 [] [ Html.text "Settings" ]
, Settings.view SettingsMsg model.settings
]
headingView : Html Msg
headingView =
Html.div
[ Attributes.class "heading" ]
[ Html.h1 []
[ Html.text "🎻"
, Html.span [ Attributes.class "heading-fidl" ] [ Html.text "fidl" ]
, Html.span [ Attributes.class "heading-bolt" ] [ Html.text "bolt" ]
, Html.text "⚡️"
]
]
controlsView : Model -> Html Msg
controlsView model =
let
button title msg =
Html.button
[ Attributes.class "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 "Help" (toggle HelpWindow)
, button "Settings" (toggle SettingsWindow)
, button "Layout" (SplitMsg SplitPane.ToggleOrientation)
]
helpMessage : SplitPane.Orientation -> Html msg
helpMessage 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 "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 "Click the "
, bold "Layout"
, text " "
, multiline """
button to switch between the left/right editor layout and the top/bottom one
(useful for long lines and narrow windows).
"""
]
, 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 []
[ multiline """
Enjoy! If you find a bug, please report it to mkember@google.com.
"""
]
]
------------- 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 )
]
decode : Decode.Decoder Model
decode =
Decode.map4 Model
(Decode.field "editors" Editors.decode)
(Decode.field "settings" Settings.decode)
(Decode.field "split" (SplitPane.decode splitId))
(Decode.succeed Window.init)
decodeFlags : Decode.Decoder Flags
decodeFlags =
Decode.map2 Flags
(Decode.field "model" (Decode.maybe decode))
(Decode.field "preferredScheme" Settings.decodeScheme)