mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-09-07 05:24:40 +08:00
Add copy to clipboard button to virtualized output (#393)
* Add copy to clipboard button to virtualized output * Move text output into its own component * Update button background
This commit is contained in:
parent
cdf2acf121
commit
6c53c09a61
5 changed files with 60 additions and 37 deletions
|
@ -54,11 +54,11 @@
|
|||
}
|
||||
|
||||
.icon-button {
|
||||
@apply p-1 flex items-center justify-center text-gray-400 hover:text-gray-800;
|
||||
@apply p-1 flex items-center justify-center text-gray-400 hover:text-gray-800 rounded-full;
|
||||
}
|
||||
|
||||
.icon-button:focus {
|
||||
@apply rounded-full bg-gray-100;
|
||||
@apply bg-gray-100;
|
||||
}
|
||||
|
||||
.icon-button i {
|
||||
|
|
|
@ -121,3 +121,7 @@ solely client-side operations.
|
|||
[data-element="clients-list-item"][data-js-followed] [data-meta="follow"] {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
[phx-hook="VirtualizedLines"]:not(:hover) [data-clipboard] {
|
||||
@apply hidden;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ import { getLineHeight } from "../lib/utils";
|
|||
*
|
||||
* * one annotated with `data-content` where the visible elements are rendered,
|
||||
* it should contain any styling relevant for the container
|
||||
*
|
||||
* Also a `data-clipboard` child button is used for triggering copy-to-clipboard.
|
||||
*/
|
||||
const VirtualizedLines = {
|
||||
mounted() {
|
||||
|
@ -62,6 +64,18 @@ const VirtualizedLines = {
|
|||
this.state.contentElement,
|
||||
config
|
||||
);
|
||||
|
||||
this.el
|
||||
.querySelector("[data-clipboard]")
|
||||
.addEventListener("click", (event) => {
|
||||
const content = Array.from(this.state.templateElement.children)
|
||||
.map((child) => child.innerText)
|
||||
.join("");
|
||||
|
||||
if ("clipboard" in navigator) {
|
||||
navigator.clipboard.writeText(content);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updated() {
|
||||
|
|
29
lib/livebook_web/live/output/text_component.ex
Normal file
29
lib/livebook_web/live/output/text_component.ex
Normal file
|
@ -0,0 +1,29 @@
|
|||
defmodule LivebookWeb.Output.TextComponent do
|
||||
use LivebookWeb, :live_component
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~L"""
|
||||
<div id="virtualized-text-<%= @id %>"
|
||||
class="relative"
|
||||
phx-hook="VirtualizedLines"
|
||||
data-max-height="300"
|
||||
data-follow="<%= @follow %>">
|
||||
<div data-template class="hidden">
|
||||
<%= for line <- ansi_to_html_lines(@content) do %>
|
||||
<%# Add a newline, so that multiple lines can be copied properly %>
|
||||
<div><%= [line, "\n"] %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div data-content class="overflow-auto whitespace-pre font-editor text-gray-500 tiny-scrollbar"
|
||||
id="virtualized-text-<%= @id %>-content"
|
||||
phx-update="ignore"></div>
|
||||
<div class="absolute right-4 top-0 z-10">
|
||||
<button class="icon-button bg-gray-100" data-clipboard>
|
||||
<%= remix_icon("clipboard-line", class: "text-lg") %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
|
@ -286,15 +286,22 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
|||
end
|
||||
|
||||
defp render_output(_socket, text, id) when is_binary(text) do
|
||||
text
|
||||
# Captured output usually has a trailing newline that we can ignore,
|
||||
# because each line is itself an HTML block anyway.
|
||||
|> String.replace_suffix("\n", "")
|
||||
|> render_virtualized_output(id, follow: true)
|
||||
text = String.replace_suffix(text, "\n", "")
|
||||
live_component(LivebookWeb.Output.TextComponent, id: id, content: text, follow: true)
|
||||
end
|
||||
|
||||
defp render_output(_socket, {:text, text}, id) do
|
||||
render_virtualized_output(text, id)
|
||||
live_component(LivebookWeb.Output.TextComponent, id: id, content: text, follow: false)
|
||||
end
|
||||
|
||||
defp render_output(_socket, {:image, content, mime_type}, id) do
|
||||
live_component(LivebookWeb.Output.ImageComponent,
|
||||
id: id,
|
||||
content: content,
|
||||
mime_type: mime_type
|
||||
)
|
||||
end
|
||||
|
||||
defp render_output(_socket, {:vega_lite_static, spec}, id) do
|
||||
|
@ -315,14 +322,6 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
|||
)
|
||||
end
|
||||
|
||||
defp render_output(_socket, {:image, content, mime_type}, id) do
|
||||
live_component(LivebookWeb.Output.ImageComponent,
|
||||
id: id,
|
||||
content: content,
|
||||
mime_type: mime_type
|
||||
)
|
||||
end
|
||||
|
||||
defp render_output(_socket, {:error, formatted}, _id) do
|
||||
render_error_message_output(formatted)
|
||||
end
|
||||
|
@ -334,29 +333,6 @@ defmodule LivebookWeb.SessionLive.CellComponent do
|
|||
""")
|
||||
end
|
||||
|
||||
defp render_virtualized_output(text, id, opts \\ []) do
|
||||
follow = Keyword.get(opts, :follow, false)
|
||||
lines = ansi_to_html_lines(text)
|
||||
assigns = %{lines: lines, id: id, follow: follow}
|
||||
|
||||
~L"""
|
||||
<div id="<%= @id %>"
|
||||
phx-hook="VirtualizedLines"
|
||||
data-max-height="300"
|
||||
data-follow="<%= follow %>">
|
||||
<div data-template class="hidden">
|
||||
<%= for line <- @lines do %>
|
||||
<%# Add a newline, so that multiple lines can be copied properly %>
|
||||
<div><%= [line, "\n"] %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div data-content class="overflow-auto whitespace-pre font-editor text-gray-500 tiny-scrollbar"
|
||||
id="<%= @id %>-content"
|
||||
phx-update="ignore"></div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_error_message_output(message) do
|
||||
assigns = %{message: message}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue