mirror of
https://github.com/livebook-dev/livebook.git
synced 2024-12-27 18:23:49 +08:00
Virtualize output lines (#70)
* Virtualize output lines * Remove unused dependency * Remove VirtualizedLinesComponent * Pass lines as HTML nodes and use as template * Validate hook children * Refactor markup
This commit is contained in:
parent
199602016c
commit
e65a5f712c
6 changed files with 15787 additions and 26 deletions
|
@ -19,7 +19,7 @@ to be consistent with the editor.
|
|||
--ansi-color-light-green: #34d399;
|
||||
--ansi-color-light-yellow: #fde68a;
|
||||
--ansi-color-light-blue: #93c5fd;
|
||||
--ansi-color-light-magenta: #F472b6;
|
||||
--ansi-color-light-magenta: #f472b6;
|
||||
--ansi-color-light-cyan: #6be3f2;
|
||||
--ansi-color-light-white: white;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import Cell from "./cell";
|
|||
import Session from "./session";
|
||||
import FocusOnUpdate from "./focus_on_update";
|
||||
import ScrollOnUpdate from "./scroll_on_update";
|
||||
import VirtualizedLines from "./virtualized_lines";
|
||||
|
||||
const Hooks = {
|
||||
ContentEditable,
|
||||
|
@ -16,6 +17,7 @@ const Hooks = {
|
|||
Session,
|
||||
FocusOnUpdate,
|
||||
ScrollOnUpdate,
|
||||
VirtualizedLines,
|
||||
};
|
||||
|
||||
const csrfToken = document
|
||||
|
|
79
assets/js/virtualized_lines/index.js
Normal file
79
assets/js/virtualized_lines/index.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
import HyperList from "hyperlist";
|
||||
import { getAttributeOrThrow, parseInteger } from "../lib/attribute";
|
||||
|
||||
/**
|
||||
* A hook used to render text lines as a virtual list,
|
||||
* so that only the visible lines are actually in the DOM.
|
||||
*
|
||||
* Configuration:
|
||||
*
|
||||
* * `data-max-height` - the maximum height of the element, exceeding this height enables scrolling
|
||||
*
|
||||
* The element should have two children:
|
||||
*
|
||||
* * one annotated with `data-template` attribute, it should be hidden
|
||||
* and contain all the line elements as its children
|
||||
*
|
||||
* * one annotated with `data-content` where the visible elements are rendered,
|
||||
* it should contain any styling relevant for the container
|
||||
*/
|
||||
const VirtualizedLines = {
|
||||
mounted() {
|
||||
this.props = getProps(this);
|
||||
|
||||
const computedStyle = window.getComputedStyle(this.el);
|
||||
this.lineHeight = parseInt(computedStyle.lineHeight, 10);
|
||||
|
||||
this.templateElement = this.el.querySelector('[data-template]');
|
||||
|
||||
if (!this.templateElement) {
|
||||
throw new Error('VirtualizedLines must have a child with data-template attribute');
|
||||
}
|
||||
|
||||
this.contentElement = this.el.querySelector('[data-content]');
|
||||
|
||||
if (!this.templateElement) {
|
||||
throw new Error('VirtualizedLines must have a child with data-content');
|
||||
}
|
||||
|
||||
const config = hyperListConfig(
|
||||
this.templateElement,
|
||||
this.props.maxHeight,
|
||||
this.lineHeight
|
||||
);
|
||||
this.virtualizedList = new HyperList(this.contentElement, config);
|
||||
},
|
||||
|
||||
updated() {
|
||||
this.props = getProps(this);
|
||||
|
||||
const config = hyperListConfig(
|
||||
this.templateElement,
|
||||
this.props.maxHeight,
|
||||
this.lineHeight
|
||||
);
|
||||
this.virtualizedList.refresh(this.contentElement, config);
|
||||
},
|
||||
};
|
||||
|
||||
function hyperListConfig(templateElement, maxHeight, lineHeight) {
|
||||
const numberOfLines = templateElement.childElementCount;
|
||||
|
||||
return {
|
||||
height: Math.min(maxHeight, lineHeight * numberOfLines),
|
||||
total: numberOfLines,
|
||||
itemHeight: lineHeight,
|
||||
generate: (index) => {
|
||||
// Clone n-th child of the template container.
|
||||
return templateElement.children.item(index).cloneNode(true);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getProps(hook) {
|
||||
return {
|
||||
maxHeight: getAttributeOrThrow(hook.el, "data-max-height", parseInteger),
|
||||
};
|
||||
}
|
||||
|
||||
export default VirtualizedLines;
|
15686
assets/package-lock.json
generated
15686
assets/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -11,6 +11,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"dompurify": "^2.2.6",
|
||||
"hyperlist": "^1.0.0",
|
||||
"marked": "^1.2.8",
|
||||
"monaco-editor": "^0.21.2",
|
||||
"morphdom": "^2.6.1",
|
||||
|
|
|
@ -86,7 +86,7 @@ defmodule LivebookWeb.CellComponent do
|
|||
|
||||
<%= if @cell.outputs != [] do %>
|
||||
<div class="mt-2">
|
||||
<%= render_outputs(@cell.outputs) %>
|
||||
<%= render_outputs(@cell.outputs, @cell.id) %>
|
||||
</div>
|
||||
<% end %>
|
||||
"""
|
||||
|
@ -162,15 +162,15 @@ defmodule LivebookWeb.CellComponent do
|
|||
"""
|
||||
end
|
||||
|
||||
defp render_outputs(outputs) do
|
||||
assigns = %{outputs: outputs}
|
||||
defp render_outputs(outputs, cell_id) do
|
||||
assigns = %{outputs: outputs, cell_id: cell_id}
|
||||
|
||||
~L"""
|
||||
<div class="flex flex-col rounded-md border border-gray-200 divide-y divide-gray-200 font-editor">
|
||||
<%= for output <- Enum.reverse(@outputs) do %>
|
||||
<%= for {output, index} <- @outputs |> Enum.reverse() |> Enum.with_index() do %>
|
||||
<div class="p-4">
|
||||
<div class="max-h-80 overflow-auto tiny-scrollbar">
|
||||
<%= render_output(output) %>
|
||||
<div class="">
|
||||
<%= render_output(output, "#{@cell_id}-output#{index}") %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
@ -178,25 +178,31 @@ defmodule LivebookWeb.CellComponent do
|
|||
"""
|
||||
end
|
||||
|
||||
defp render_output(output) when is_binary(output) do
|
||||
output_html = ansi_string_to_html(output)
|
||||
assigns = %{output_html: output_html}
|
||||
defp render_output(output, id) when is_binary(output) do
|
||||
lines = ansi_to_html_lines(output)
|
||||
assigns = %{lines: lines, id: id}
|
||||
|
||||
~L"""
|
||||
<div class="whitespace-pre text-gray-500"><%= @output_html %></div>
|
||||
<div id="<%= @id %>" phx-hook="VirtualizedLines" data-max-height="300">
|
||||
<div data-template class="hidden"><%= for line <- @lines do %><div><%= raw line %></div><% end %></div>
|
||||
<div data-content phx-update="ignore" class="overflow-auto whitespace-pre text-gray-500 tiny-scrollbar"></div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_output({:inspect, inspected}) do
|
||||
inspected_html = ansi_string_to_html(inspected)
|
||||
assigns = %{inspected_html: inspected_html}
|
||||
defp render_output({:inspect, inspected}, id) do
|
||||
lines = ansi_to_html_lines(inspected)
|
||||
assigns = %{lines: lines, id: id}
|
||||
|
||||
~L"""
|
||||
<div class="whitespace-pre text-gray-500"><%= @inspected_html %></div>
|
||||
<div id="<%= @id %>" phx-hook="VirtualizedLines" data-max-height="300">
|
||||
<div data-template class="hidden"><%= for line <- @lines do %><div><%= raw line %></div><% end %></div>
|
||||
<div data-content phx-update="ignore" class="overflow-auto whitespace-pre text-gray-500 tiny-scrollbar"></div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_output({:error, formatted}) do
|
||||
defp render_output({:error, formatted}, _id) do
|
||||
assigns = %{formatted: formatted}
|
||||
|
||||
~L"""
|
||||
|
@ -204,6 +210,13 @@ defmodule LivebookWeb.CellComponent do
|
|||
"""
|
||||
end
|
||||
|
||||
defp ansi_to_html_lines(string) do
|
||||
string
|
||||
|> ansi_string_to_html()
|
||||
|> Phoenix.HTML.safe_to_string()
|
||||
|> String.split("\n")
|
||||
end
|
||||
|
||||
defp render_cell_status(%{evaluation_status: :evaluating}) do
|
||||
assigns = %{}
|
||||
|
||||
|
|
Loading…
Reference in a new issue