diff --git a/assets/css/app.css b/assets/css/app.css index f52d1fcd9..b94823072 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -121,3 +121,17 @@ iframe[hidden] { .bg-editor { background-color: #282c34; } + +.tiny-scrollbar::-webkit-scrollbar { + width: 0.4rem; + height: 0.4rem; +} + +.tiny-scrollbar::-webkit-scrollbar-thumb { + border-radius: 0.25rem; + @apply bg-gray-400; +} + +.tiny-scrollbar::-webkit-scrollbar-track { + @apply bg-gray-100; +} diff --git a/assets/js/cell/index.js b/assets/js/cell/index.js index 84855342b..f96fe3aeb 100644 --- a/assets/js/cell/index.js +++ b/assets/js/cell/index.js @@ -67,6 +67,10 @@ const Cell = { if (isActive(prevProps) && !isActive(this.props)) { this.liveEditor.blur(); } + + if (!prevProps.isFocused && this.props.isFocused) { + this.el.scrollIntoView({ behavior: "smooth", block: "center" }); + } }, }; diff --git a/assets/js/session/index.js b/assets/js/session/index.js index e6d948df4..5c279675b 100644 --- a/assets/js/session/index.js +++ b/assets/js/session/index.js @@ -14,7 +14,7 @@ const Session = { this.props = getProps(this); // Keybindings - document.addEventListener("keydown", (event) => { + this.handleDocumentKeydown = (event) => { if (event.shiftKey && event.key === "Enter" && !event.repeat) { if (this.props.focusedCellId !== null) { // If the editor is focused we don't want it to receive the input @@ -27,33 +27,35 @@ const Session = { } else if (event.altKey && event.key === "k") { event.preventDefault(); this.pushEvent("move_cell_focus", { offset: -1 }); + } else if (event.ctrlKey && event.key === "Enter") { + event.stopPropagation(); + this.pushEvent("queue_cell_evaluation", {}); } - }); + }; + + document.addEventListener("keydown", this.handleDocumentKeydown, true); // Focus/unfocus a cell when the user clicks somewhere - document.addEventListener("click", (event) => { + this.handleDocumentClick = (event) => { // Find the parent with cell id info, if there is one const cell = event.target.closest("[data-cell-id]"); const cellId = cell ? cell.dataset.cellId : null; if (cellId !== this.props.focusedCellId) { this.pushEvent("focus_cell", { cell_id: cellId }); } - }); + }; + + document.addEventListener("click", this.handleDocumentClick); }, updated() { - const prevProps = this.props; this.props = getProps(this); - - // When a new cell gets focus, center it nicely on the page - if ( - this.props.focusedCellId && - this.props.focusedCellId !== prevProps.focusedCellId - ) { - const cell = this.el.querySelector(`#cell-${this.props.focusedCellId}`); - cell.scrollIntoView({ behavior: "smooth", block: "center" }); - } }, + + destroyed() { + document.removeEventListener("keydown", this.handleDocumentKeydown); + document.removeEventListener("click", this.handleDocumentClick); + } }; function getProps(hook) { diff --git a/lib/live_book/notebook/cell.ex b/lib/live_book/notebook/cell.ex index 8f3f84f32..98944d522 100644 --- a/lib/live_book/notebook/cell.ex +++ b/lib/live_book/notebook/cell.ex @@ -18,7 +18,6 @@ defmodule LiveBook.Notebook.Cell do id: id(), type: type(), source: String.t(), - # TODO: expand on this outputs: list(), metadata: %{atom() => term()} } diff --git a/lib/live_book/session/data.ex b/lib/live_book/session/data.ex index f9b80b36f..b3dbbd09b 100644 --- a/lib/live_book/session/data.ex +++ b/lib/live_book/session/data.ex @@ -326,22 +326,35 @@ defmodule LiveBook.Session.Data do |> set_cell_info!(cell.id, evaluation_status: :ready) end - defp add_cell_evaluation_stdout({data, _} = data_actions, _cell, _string) do + defp add_cell_evaluation_stdout({data, _} = data_actions, cell, string) do data_actions |> set!( - # TODO: add stdout to cell outputs - notebook: data.notebook + notebook: + Notebook.update_cell(data.notebook, cell.id, fn cell -> + %{cell | outputs: add_output(cell.outputs, string)} + end) ) end - defp add_cell_evaluation_response({data, _} = data_actions, _cell, _response) do + defp add_cell_evaluation_response({data, _} = data_actions, cell, response) do data_actions |> set!( - # TODO: add result to outputs - notebook: data.notebook + notebook: + Notebook.update_cell(data.notebook, cell.id, fn cell -> + %{cell | outputs: add_output(cell.outputs, response)} + end) ) end + defp add_output([], output), do: [output] + + defp add_output([head | tail], output) when is_binary(head) and is_binary(output) do + # Merge consecutive string outputs + [head <> output | tail] + end + + defp add_output(outputs, output), do: [output | outputs] + defp finish_cell_evaluation(data_actions, cell, section) do data_actions |> set_cell_info!(cell.id, diff --git a/lib/live_book_web/live/cell.ex b/lib/live_book_web/live/cell.ex index 52e765ee0..a04860c28 100644 --- a/lib/live_book_web/live/cell.ex +++ b/lib/live_book_web/live/cell.ex @@ -17,14 +17,16 @@ defmodule LiveBookWeb.Cell do def render_cell_content(%{cell: %{type: :markdown}} = assigns) do ~L""" -