Commit 7e3f0e00 authored by David Davó's avatar David Davó

Output widget with text

parent 3c9dec70
...@@ -20,7 +20,7 @@ the widget. ...@@ -20,7 +20,7 @@ the widget.
Any *numeric* property initialized with the empty string is provided the default value by the Any *numeric* property initialized with the empty string is provided the default value by the
frontend. Some numbers need to be sent as actual numbers (when non-null), whereas the ones representing frontend. Some numbers need to be sent as actual numbers (when non-null), whereas the ones representing
lengths in CSS units need to be sent as strings. lengths in CSS units need to be sent as strings specifying the size unit (px,em,cm,etc.).
The initial state must *at least* have the following fields in the `data.state` value of the message: The initial state must *at least* have the following fields in the `data.state` value of the message:
...@@ -40,7 +40,7 @@ the kernel. ...@@ -40,7 +40,7 @@ the kernel.
### Buffer paths ### Buffer paths
To display some widgets, we need to use the `buffer_paths`. It's only an array with arrays of keys on how to get to the fields that are to considered a To display some widgets, we need to use the `buffer_paths`. It's only an array with arrays of keys on how to get to the fields that are to considered a
byte stream. For example, in an image widget, `buffer_paths` would be the array `[ ["value"] ]`, which means that `state.value` is a buffer path. The buffers are specified in the header, so the n-th buffer corresponds to the n-th buffer path. byte stream. For example, in an image widget, `buffer_paths` would be the array `[ ["value"] ]`, which means that `state.value` is a buffer path. The buffers are sent in the header of the message, just before the data, so the n-th buffer corresponds to the n-th buffer path in the array.
```json ```json
"data": { "data": {
...@@ -74,6 +74,18 @@ content = { ...@@ -74,6 +74,18 @@ content = {
} }
``` ```
## Clear output messages
A simple message that indicates that the output of the header message id's should be cleaned.
- `wait=true` indicates that it should clean the output in the next append, while `wait=false` cleans the output inmediately.
```json
method = "clear_output",
content = {
"wait": bool
}
```
## Custom messages ## Custom messages
* Widgets can also send a custom message, having the form: * Widgets can also send a custom message, having the form:
......
...@@ -16,13 +16,38 @@ jupyter nbconvert *.ipynb --to notebook --inplace --clear-output ...@@ -16,13 +16,38 @@ jupyter nbconvert *.ipynb --to notebook --inplace --clear-output
``` ```
## Things to do ## Things to do
- [ ] Validate the JSON implementation of widgets against the MsgSpec schema - [ ] Automatic validation of the JSON implementation of widgets against the MsgSpec schema
- [ ] Create integration tests for the widgets - [ ] Create integration tests for the widgets
- [ ] Make the `output` widget work - [ ] Make the `output` widget work with anything displayable
- [ ] Processing of widget messages concurrently
- [ ] Make the layout widget values more 'Haskelian': Instead of checking if the string is valid at runtime, make some types so it's checked at compile-time - [ ] Make the layout widget values more 'Haskelian': Instead of checking if the string is valid at runtime, make some types so it's checked at compile-time
- [ ] Create a serializable color data type instead of using `Maybe String` - [ ] Create a serializable color data type instead of using `Maybe String`
- [ ] Overload setField so it can be used with `Maybes` without having to put `Just` every time - [ ] Overload setField so it can be used with `Maybes` or other wrapper types without having to put `Just` every time.
- [ ] Add some "utils" work: - [ ] Add some "utils" work:
- [ ] Create media widget from file - [ ] Create media widget from file
- [ ] Get the selected label from a selection value - [ ] Get the selected label from a selection value
\ No newline at end of file
## How to...
This is a mini-guide for developers that want to update to a more recent widgets specification, but without
dwelling into the deeps of the project
### Add a new attribute
If you want to add a new attribute you'll have to:
1. Create a new singleton in [Singletons.hs](./Singletons.hs) inside the type `data Field`.
2. Write the serialization key of the field as specified in the model (see [MsgSpec.md](./MsgSpec.md)) inside the `toKey` function at [Singletons.hs](./Singletons.hs)
3. Because we use the `singletons-th` library, you have to define an alias for the attribute at [Common.hs](./Common.hs) to be able to use it at run-time more easily.
4. Now you have to specify the type of the field. Edit the type family `Fieldtype` at [Types.hs](./Types.hs)
### Add an attribute to a widget
First you have to check if the attribute is only for one widget, or is from a common class. You can check it at [ipywidget's repo](https://github.com/jupyter-widgets/ipywidgets/tree/master/ipywidgets/widgets).
- If it's only for one widget:
1. Edit the `type instance WidgetFields <WidgetNameType> = ...` at [Types.hs](./Types.hs), adding the new field to the field array.
2. Modify the `mk<WidgetName>` at `Module/WidgetName.hs`, adding the default value of the attribute. If the widget doesn't have any attributes yet, you can check how to do it on other widgets.
- If it's for a common class:
1. Edit the `type <ClassName> = ...` at [Types.hs](./Types.hs)
2. Edit the `default<ClassName>Widget` function from the same file, adding the default value for that attribute.
> Some widgets receive messages from the frontend when a value is modified (such as sliders, text areas, buttons...). You'll have to modify the `comm` function instantiated from the class `IHaskellWidget`. You can find an example at [IntSlider.hs](./Int/BoundedInt/IntSlider.hs)
## FAQ
When using widgets in ihaskell, you'll encounter a lot of compilation errors. If you are not very familiar with Haskell, they can be a bit hard to decipher, this is a mini guide that will (hopefully) appear when you paste the error in Google.
\ No newline at end of file
...@@ -116,6 +116,8 @@ pattern HandleColor = S.SHandleColor ...@@ -116,6 +116,8 @@ pattern HandleColor = S.SHandleColor
pattern ButtonWidth = S.SButtonWidth pattern ButtonWidth = S.SButtonWidth
pattern Target = S.STarget pattern Target = S.STarget
pattern Source = S.SSource pattern Source = S.SSource
pattern MsgID = S.SMsgID
pattern Outputs = S.SOutputs
pattern Style = S.SStyle pattern Style = S.SStyle
-- | Close a widget's comm -- | Close a widget's comm
closeWidget :: IHaskellWidget w => w -> IO () closeWidget :: IHaskellWidget w => w -> IO ()
......
...@@ -11,7 +11,9 @@ module IHaskell.Display.Widgets.Output ...@@ -11,7 +11,9 @@ module IHaskell.Display.Widgets.Output
-- * Constructor -- * Constructor
, mkOutputWidget , mkOutputWidget
-- * Using the output widget -- * Using the output widget
, appendOutput , appendStdout
, appendStderr
, appendDisplay
, clearOutput , clearOutput
, clearOutput_ , clearOutput_
, replaceOutput , replaceOutput
...@@ -22,12 +24,15 @@ import Prelude ...@@ -22,12 +24,15 @@ import Prelude
import Data.Aeson import Data.Aeson
import Data.IORef (newIORef) import Data.IORef (newIORef)
import Data.Text
import Data.Vinyl (Rec(..), (<+>))
import IHaskell.Display import IHaskell.Display
import IHaskell.Eval.Widgets import IHaskell.Eval.Widgets
import IHaskell.IPython.Message.UUID as U import IHaskell.IPython.Message.UUID as U
import IHaskell.Display.Widgets.Types import IHaskell.Display.Widgets.Types
import IHaskell.Display.Widgets.Common
import IHaskell.Display.Widgets.Layout.LayoutWidget import IHaskell.Display.Widgets.Layout.LayoutWidget
-- | An 'OutputWidget' represents a Output widget from IPython.html.widgets. -- | An 'OutputWidget' represents a Output widget from IPython.html.widgets.
...@@ -40,7 +45,15 @@ mkOutputWidget = do ...@@ -40,7 +45,15 @@ mkOutputWidget = do
wid <- U.random wid <- U.random
layout <- mkLayout layout <- mkLayout
let widgetState = WidgetState $ defaultDOMWidget "OutputView" "OutputModel" layout let domAttrs = defaultDOMWidget "OutputView" "OutputModel" layout
outAttrs = (ViewModule =:! "@jupyter-widgets/output")
:& (ModelModule =:! "@jupyter-widgets/output")
:& (ViewModuleVersion =:! "1.0.0")
:& (ModelModuleVersion =:! "1.0.0")
:& (MsgID =:: "")
:& (Outputs =:: [])
:& RNil
widgetState = WidgetState $ domAttrs <+> outAttrs
stateIO <- newIORef widgetState stateIO <- newIORef widgetState
...@@ -52,25 +65,42 @@ mkOutputWidget = do ...@@ -52,25 +65,42 @@ mkOutputWidget = do
-- Return the image widget -- Return the image widget
return widget return widget
-- | Append to the output widget appendStd :: StreamName -> OutputWidget -> Text -> IO ()
appendOutput :: IHaskellDisplay a => OutputWidget -> a -> IO () appendStd n out t = do
appendOutput widget out = do getField out Outputs >>= setField out Outputs . updateOutputs
disp <- display out where updateOutputs :: [OutputMsg] -> [OutputMsg]
widgetPublishDisplay widget disp updateOutputs = (++[OutputStream n t])
appendStdout :: OutputWidget -> Text -> IO ()
appendStdout = appendStd STR_STDOUT
appendStderr :: OutputWidget -> Text -> IO ()
appendStderr = appendStd STR_STDERR
-- | Clears the output widget
clearOutput' :: OutputWidget -> IO ()
clearOutput' w = do
_ <- setField w Outputs []
_ <- setField w MsgID ""
return ()
appendDisplay :: IHaskellDisplay a => OutputWidget -> a -> IO ()
appendDisplay a d = error "To be implemented"
-- | Clear the output widget immediately -- | Clear the output widget immediately
clearOutput :: OutputWidget -> IO () clearOutput :: OutputWidget -> IO ()
clearOutput widget = widgetClearOutput widget False clearOutput widget = widgetClearOutput False >> clearOutput' widget
-- | Clear the output widget on next append -- | Clear the output widget on next append
clearOutput_ :: OutputWidget -> IO () clearOutput_ :: OutputWidget -> IO ()
clearOutput_ widget = widgetClearOutput widget True clearOutput_ widget = widgetClearOutput True >> clearOutput' widget
-- | Replace the currently displayed output for output widget -- | Replace the currently displayed output for output widget
replaceOutput :: IHaskellDisplay a => OutputWidget -> a -> IO () replaceOutput :: IHaskellDisplay a => OutputWidget -> a -> IO ()
replaceOutput widget d = do replaceOutput widget d = do
clearOutput_ widget clearOutput_ widget
appendOutput widget d appendDisplay widget d
instance IHaskellWidget OutputWidget where instance IHaskellWidget OutputWidget where
getCommUUID = uuid getCommUUID = uuid
comm widget val _ = print val
...@@ -127,6 +127,8 @@ singletons ...@@ -127,6 +127,8 @@ singletons
| ButtonWidth | ButtonWidth
| Target | Target
| Source | Source
| MsgID
| Outputs
| Style | Style
-- Now the ones for layout -- Now the ones for layout
-- Every layout property comes with an L before the name to avoid conflict -- Every layout property comes with an L before the name to avoid conflict
...@@ -265,6 +267,8 @@ promote ...@@ -265,6 +267,8 @@ promote
toKey ButtonWidth = "button_width" toKey ButtonWidth = "button_width"
toKey Target = "target" toKey Target = "target"
toKey Source = "source" toKey Source = "source"
toKey MsgID = "msg_id"
toKey Outputs = "outputs"
toKey Style = "style" toKey Style = "style"
toKey LAlignContent = "align_content" toKey LAlignContent = "align_content"
toKey LAlignItems = "align_items" toKey LAlignItems = "align_items"
......
...@@ -269,6 +269,8 @@ type instance FieldType 'S.HandleColor = Maybe String ...@@ -269,6 +269,8 @@ type instance FieldType 'S.HandleColor = Maybe String
type instance FieldType 'S.ButtonWidth = String type instance FieldType 'S.ButtonWidth = String
type instance FieldType 'S.Target = WidgetFieldPair type instance FieldType 'S.Target = WidgetFieldPair
type instance FieldType 'S.Source = WidgetFieldPair type instance FieldType 'S.Source = WidgetFieldPair
type instance FieldType 'S.MsgID = Text
type instance FieldType 'S.Outputs = [OutputMsg]
type instance FieldType 'S.Style = StyleWidget type instance FieldType 'S.Style = StyleWidget
-- | Can be used to put different widgets in a list. Useful for dealing with children widgets. -- | Can be used to put different widgets in a list. Useful for dealing with children widgets.
...@@ -386,7 +388,7 @@ type instance WidgetFields 'ImageType = ...@@ -386,7 +388,7 @@ type instance WidgetFields 'ImageType =
type instance WidgetFields 'VideoType = type instance WidgetFields 'VideoType =
MediaClass :++ ['S.VideoFormat, 'S.Width, 'S.Height, 'S.AutoPlay, 'S.Loop, 'S.Controls] MediaClass :++ ['S.VideoFormat, 'S.Width, 'S.Height, 'S.AutoPlay, 'S.Loop, 'S.Controls]
type instance WidgetFields 'OutputType = DOMWidgetClass type instance WidgetFields 'OutputType = DOMWidgetClass :++ ['S.ViewModule,'S.ModelModule,'S.ViewModuleVersion,'S.ModelModuleVersion,'S.MsgID,'S.Outputs]
type instance WidgetFields 'HTMLType = StringClass type instance WidgetFields 'HTMLType = StringClass
type instance WidgetFields 'HTMLMathType = StringClass type instance WidgetFields 'HTMLMathType = StringClass
type instance WidgetFields 'ComboboxType = TextClass :++ [ 'S.Options, 'S.EnsureOption ] type instance WidgetFields 'ComboboxType = TextClass :++ [ 'S.Options, 'S.EnsureOption ]
...@@ -980,4 +982,24 @@ unlink :: ('S.Source ∈ WidgetFields w, 'S.Target ∈ WidgetFields w, IHaskellW ...@@ -980,4 +982,24 @@ unlink :: ('S.Source ∈ WidgetFields w, 'S.Target ∈ WidgetFields w, IHaskellW
unlink w = do unlink w = do
_ <- setField' w Source EmptyWT _ <- setField' w Source EmptyWT
_ <- setField' w Target EmptyWT _ <- setField' w Target EmptyWT
return w return w
\ No newline at end of file
data StreamName = STR_STDERR
| STR_STDOUT
deriving (Eq, Show)
instance ToJSON StreamName where
toJSON STR_STDERR = "stderr"
toJSON STR_STDOUT = "stdout"
data OutputMsg = OutputStream
{ name :: StreamName
, text :: Text
}
deriving (Eq, Show)
instance ToJSON OutputMsg where
toJSON (OutputStream n t) = object [ "output_type" .= str "stream"
, "name" .= toJSON n
, "text" .= toJSON t
]
\ No newline at end of file
...@@ -221,8 +221,8 @@ showMessageType StatusMessage = "status" ...@@ -221,8 +221,8 @@ showMessageType StatusMessage = "status"
showMessageType StreamMessage = "stream" showMessageType StreamMessage = "stream"
showMessageType DisplayDataMessage = "display_data" showMessageType DisplayDataMessage = "display_data"
showMessageType UpdateDisplayDataMessage = "update_display_data" showMessageType UpdateDisplayDataMessage = "update_display_data"
showMessageType OutputMessage = "pyout" showMessageType OutputMessage = "execute_result"
showMessageType InputMessage = "pyin" showMessageType InputMessage = "execute_input"
showMessageType IsCompleteRequestMessage = "is_complete_request" showMessageType IsCompleteRequestMessage = "is_complete_request"
showMessageType IsCompleteReplyMessage = "is_complete_reply" showMessageType IsCompleteReplyMessage = "is_complete_reply"
showMessageType CompleteRequestMessage = "complete_request" showMessageType CompleteRequestMessage = "complete_request"
...@@ -580,7 +580,7 @@ instance ToJSON Message where ...@@ -580,7 +580,7 @@ instance ToJSON Message where
toJSON PublishStatus { executionState = executionState } = toJSON PublishStatus { executionState = executionState } =
object ["execution_state" .= executionState] object ["execution_state" .= executionState]
toJSON PublishStream { streamType = streamType, streamContent = content } = toJSON PublishStream { streamType = streamType, streamContent = content } =
-- Since 5.0 "data" key was renamed to "text" -- Since 5.0 "data" key was renamed to "text""
object ["text" .= content, "name" .= streamType, "output_type" .= string "stream"] object ["text" .= content, "name" .= streamType, "output_type" .= string "stream"]
toJSON r@PublishDisplayData { displayData = datas } toJSON r@PublishDisplayData { displayData = datas }
= object = object
......
...@@ -90,9 +90,9 @@ widgetSendValue widget = queue . JSONValue (Widget widget) ...@@ -90,9 +90,9 @@ widgetSendValue widget = queue . JSONValue (Widget widget)
widgetPublishDisplay :: (IHaskellWidget a, IHaskellDisplay b) => a -> b -> IO () widgetPublishDisplay :: (IHaskellWidget a, IHaskellDisplay b) => a -> b -> IO ()
widgetPublishDisplay widget disp = display disp >>= queue . DispMsg (Widget widget) widgetPublishDisplay widget disp = display disp >>= queue . DispMsg (Widget widget)
-- | Send a `clear_output` message as a [method .= custom] message -- | Send a `clear_output` message
widgetClearOutput :: IHaskellWidget a => a -> Bool -> IO () widgetClearOutput :: Bool -> IO ()
widgetClearOutput widget w = queue $ ClrOutput (Widget widget) w widgetClearOutput w = queue $ ClrOutput w
-- | Handle a single widget message. Takes necessary actions according to the message type, such as -- | Handle a single widget message. Takes necessary actions according to the message type, such as
-- opening comms, storing and updating widget representation in the kernel state etc. -- opening comms, storing and updating widget representation in the kernel state etc.
...@@ -165,10 +165,10 @@ handleMessage send replyHeader state msg = do ...@@ -165,10 +165,10 @@ handleMessage send replyHeader state msg = do
let dmsg = WidgetDisplay dispHeader $ unwrap disp let dmsg = WidgetDisplay dispHeader $ unwrap disp
sendMessage widget (toJSON $ CustomContent $ toJSON dmsg) sendMessage widget (toJSON $ CustomContent $ toJSON dmsg)
ClrOutput widget w -> do ClrOutput w -> do
hdr <- dupHeader replyHeader ClearOutputMessage hdr <- dupHeader replyHeader ClearOutputMessage
let cmsg = WidgetClear hdr w send $ ClearOutput hdr w
sendMessage widget (toJSON $ CustomContent $ toJSON cmsg) return state
where where
oldComms = openComms state oldComms = openComms state
...@@ -222,14 +222,6 @@ instance ToJSON WidgetDisplay where ...@@ -222,14 +222,6 @@ instance ToJSON WidgetDisplay where
let pbval = toJSON $ PublishDisplayData replyHeader ddata Nothing let pbval = toJSON $ PublishDisplayData replyHeader ddata Nothing
in toJSON $ IPythonMessage replyHeader pbval DisplayDataMessage in toJSON $ IPythonMessage replyHeader pbval DisplayDataMessage
-- Override toJSON for ClearOutput
data WidgetClear = WidgetClear MessageHeader Bool
instance ToJSON WidgetClear where
toJSON (WidgetClear replyHeader w) =
let clrVal = toJSON $ ClearOutput replyHeader w
in toJSON $ IPythonMessage replyHeader clrVal ClearOutputMessage
data IPythonMessage = IPythonMessage MessageHeader Value MessageType data IPythonMessage = IPythonMessage MessageHeader Value MessageType
instance ToJSON IPythonMessage where instance ToJSON IPythonMessage where
......
...@@ -250,8 +250,8 @@ data WidgetMsg = Open Widget Value ...@@ -250,8 +250,8 @@ data WidgetMsg = Open Widget Value
DispMsg Widget Display DispMsg Widget Display
| |
-- ^ A 'display_data' message, sent as a [method .= custom] comm_msg -- ^ A 'display_data' message, sent as a [method .= custom] comm_msg
ClrOutput Widget Bool ClrOutput Bool
-- ^ A 'clear_output' message, sent as a [method .= custom] comm_msg -- ^ A 'clear_output' message, sent as a clear_output message
deriving (Show, Typeable) deriving (Show, Typeable)
data WidgetMethod = UpdateState Value [BufferPath] data WidgetMethod = UpdateState Value [BufferPath]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment