Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add output panel #2109

Open
wants to merge 45 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
aba0b73
Add output panel
jannikbecher Jul 25, 2023
af32c3d
Add add/remove output from output panel functionality
jannikbecher Jul 25, 2023
e5798a7
Apply suggestions
jannikbecher Jul 25, 2023
53038a5
Hide add/remove button when output already on output_panel
jannikbecher Jul 25, 2023
6a1b59f
Apply suggestion
jannikbecher Jul 25, 2023
8318200
Added functionality to reorder the output panel rows
jannikbecher Jul 26, 2023
dfef58e
Remove whole row when moved item is the only item in the row
jannikbecher Jul 26, 2023
18a03f7
Fix not refreshing bug
jannikbecher Jul 26, 2023
b7d46b9
Make output panel independant scrollable from notebook
jannikbecher Jul 26, 2023
d2fb3ea
Add drag element
jannikbecher Jul 26, 2023
9811b7d
Move output_panel to own file
jannikbecher Jul 27, 2023
716a49a
Add multi column functionality
jannikbecher Jul 27, 2023
c79de30
Fix bug not able to move one item up into new row
jannikbecher Jul 27, 2023
0bf5db5
Restructure js file and fix same bugs
jannikbecher Jul 27, 2023
df921eb
Handle iframes correctly in output panel
jannikbecher Jul 28, 2023
e7797c5
Fix iframe not repositioning
jannikbecher Jul 28, 2023
1a3a746
Bug fixes..
jannikbecher Jul 28, 2023
d9fdb48
Add deployment
jannikbecher Jul 28, 2023
531418c
Fix embedded scroll behaviour. Still vertical scroll though
jannikbecher Jul 28, 2023
8225450
Restrucutre app_session data_view to not always recalc for all view t…
jannikbecher Jul 28, 2023
b6f91c9
Fix some warnings
jannikbecher Jul 28, 2023
9acd116
Fix input_views
jannikbecher Jul 28, 2023
c553cab
Remove output from output panel when cell is removed
jannikbecher Jul 29, 2023
6bcd42c
Updated tests, fixed some bugs
jannikbecher Jul 29, 2023
ad63d14
Fix bug when moving item to same location
jannikbecher Jul 29, 2023
117c8d8
Only show move to output panel button when cell is evaluated
jannikbecher Jul 29, 2023
4b35310
Reposition iframes without timeout
jannikbecher Jul 29, 2023
34e283a
Restructure drop hooks and fix idrame hover
jannikbecher Jul 29, 2023
8170ee7
removed header from output panel, fixed warning
jannikbecher Jul 29, 2023
7bae074
Reposition iframe when output item is removed from panel
jannikbecher Jul 30, 2023
214edb0
UI: Show iframe content while dragging
jannikbecher Jul 30, 2023
d3e8922
Merge branch 'main' into jb-output-panel
jannikbecher Aug 15, 2023
3a48ecc
Fix warnings
jannikbecher Aug 15, 2023
2cbe926
Improve layout
jannikbecher Aug 15, 2023
be313bf
Add app_menu to deployed output panel
jannikbecher Aug 15, 2023
76583a2
Fix warning
jannikbecher Aug 15, 2023
0899d07
Fix messed up session_live styles
jannikbecher Aug 16, 2023
c22ed52
Remove duplicate
jannikbecher Aug 16, 2023
c6a37f2
Remove test warnings
jannikbecher Aug 31, 2023
79861f3
Merge branch 'main' into jb-output-panel
jannikbecher Aug 31, 2023
6ce9da5
move send to output button before link button
jannikbecher Sep 1, 2023
6b88be2
add evaluate cell functionality to output panel
jannikbecher Sep 1, 2023
1c8731c
WIP
jannikbecher Sep 1, 2023
8fa6761
WIP
jannikbecher Sep 2, 2023
7a38d25
Fix dragging issues
jannikbecher Sep 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions assets/css/js_interop.css
Original file line number Diff line number Diff line change
Expand Up @@ -296,20 +296,58 @@ solely client-side operations.

/* === Session views === */

[data-el-session][data-js-view="code-zen"] [data-el-view-toggle="code-zen"] {
[data-el-session][data-js-view] [data-el-view-activate] {
@apply pointer-events-none;
}

[data-el-session]:not([data-js-view]) [data-el-view-deactivate-button] {
@apply hidden;
}

[data-el-session][data-js-view="output-panel-popped-out"]
[data-el-view-activate="output-panel"],
[data-el-session]:not([data-js-view="output-panel-popped-out"])
[data-el-view-output-panel-popin-button] {
@apply hidden;
}

[data-el-session][data-js-view="output-panel"]
[data-el-view-activate="output-panel"],
[data-el-session][data-js-view="output-panel-popped-out"]
[data-el-view-output-panel-popin-button] {
@apply text-green-bright-400;
}

[data-el-session][data-js-view="code-zen"] [data-el-view-activate="code-zen"] {
@apply text-green-bright-400;
}

[data-el-session][data-js-view="presentation"]
[data-el-view-toggle="presentation"] {
[data-el-view-activate="presentation"] {
@apply text-green-bright-400;
}

[data-el-session][data-js-view="custom"] [data-el-view-toggle="custom"] {
[data-el-session][data-js-view="custom"] [data-el-view-activate="custom"] {
@apply text-green-bright-400;
}

[data-el-session][data-js-view]
[data-el-session]:not([data-js-view="output-panel"]) [data-el-output-panel] {
@apply hidden;
}

[data-el-session][data-js-view="output-panel"] [data-el-notebook-content] {
@apply absolute w-1/2 left-0 px-16 py-4;
}

[data-el-session][data-js-view="output-panel"] [data-el-notebook-indicators] {
@apply sm:fixed bottom-[0.4rem] right-1/2;
jannikbecher marked this conversation as resolved.
Show resolved Hide resolved
}

[data-el-session]:is(
[data-js-view="code-zen"],
[data-js-view="presentation"],
[data-js-view="custom"]
)
:is([data-el-actions], [data-el-insert-buttons]) {
@apply hidden;
}
Expand Down
49 changes: 49 additions & 0 deletions assets/js/hooks/external_window.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { getAttributeOrDefault, parseBoolean } from "../lib/attribute";
/**
* A hook for external windows.
*/
const ExternalWindow = {
mounted() {
this.props = this.getProps();

if (!this.props.isWindowEmbedded) {
this.handleBeforeUnloadEvent = this.handleBeforeUnloadEvent.bind(this);
window.addEventListener("beforeunload", this.handleBeforeUnloadEvent);
this.getElement("external-window-close-button").addEventListener(
"click",
(event) => this.handleExternalWindowCloseClick()
);
this.getElement("external-window-popin-button").addEventListener(
"click",
(event) => this.handleExternalWindowPopinClick()
);
}
},
updated() {
this.props = this.getProps();
},
getProps() {
return {
isWindowEmbedded: this.el.hasAttribute("data-window-embedded"),
};
},
handleBeforeUnloadEvent(event) {
this.sendToParent("external_window_popin_clicked");
},
handleExternalWindowCloseClick() {
window.removeEventListener("beforeunload", this.handleBeforeUnloadEvent);
this.sendToParent("external_window_close_clicked");
},
handleExternalWindowPopinClick() {
window.removeEventListener("beforeunload", this.handleBeforeUnloadEvent);
this.sendToParent("external_window_popin_clicked");
},
getElement(name) {
return document.querySelector(`[data-el-${name}]`);
},
sendToParent(message) {
window.opener.postMessage(message, window.location.origin);
},
};

export default ExternalWindow;
2 changes: 2 additions & 0 deletions assets/js/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import CellEditor from "./cell_editor";
import Dropzone from "./dropzone";
import EditorSettings from "./editor_settings";
import EmojiPicker from "./emoji_picker";
import ExternalWindow from "./external_window";
import FocusOnUpdate from "./focus_on_update";
import Headline from "./headline";
import Highlight from "./highlight";
Expand All @@ -31,6 +32,7 @@ export default {
Dropzone,
EditorSettings,
EmojiPicker,
ExternalWindow,
FocusOnUpdate,
Headline,
Highlight,
Expand Down
104 changes: 92 additions & 12 deletions assets/js/hooks/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ const Session = {
this.clientsMap = {};
this.lastLocationReportByClientId = {};
this.followedClientId = null;
this.outputPanelWindow = null;

this.handleExternalWindowMessage =
this.handleExternalWindowMessage.bind(this);

setFavicon(this.faviconForEvaluationStatus(this.props.globalStatus));

Expand Down Expand Up @@ -135,9 +139,29 @@ const Session = {
this.handleCellIndicatorsClick(event)
);

this.getElement("views").addEventListener("click", (event) => {
this.handleViewsClick(event);
});
this.getElement("views").addEventListener("click", (event) =>
this.handleActivateViewClick(event)
);

this.getElement("view-deactivate-button").addEventListener(
"click",
(event) => this.handleDeactivateViewClick()
);

this.getElement("output-panel-close-button").addEventListener(
"click",
(event) => this.handleOutputPanelCloseClick()
);

this.getElement("output-panel-popout-button").addEventListener(
"click",
(event) => this.handleOutputPanelPopoutClick()
);

this.getElement("view-output-panel-popin-button").addEventListener(
"click",
(event) => this.handleOutputPanelPopinClick()
);

this.getElement("section-toggle-collapse-all-button").addEventListener(
"click",
Expand Down Expand Up @@ -663,6 +687,26 @@ const Session = {
}
},

handleOutputPanelCloseClick() {
this.closeOutputPanelWindow();
this.el.removeAttribute("data-js-view");
},

handleOutputPanelPopoutClick() {
this.outputPanelWindow = window.open(
window.location.pathname + `/external-window?type=output-panel`,
"_blank",
"toolbar=no, location=no, directories=no, titlebar=no, toolbar=0, status=no, menubar=no, scrollbars=yes, resizable=yes, copyhistory=yes, width=600, height=600"
);
window.addEventListener("message", this.handleExternalWindowMessage);
this.el.setAttribute("data-js-view", "output-panel-popped-out");
},

handleOutputPanelPopinClick() {
this.closeOutputPanelWindow();
this.el.setAttribute("data-js-view", "output-panel");
},

/**
* Focuses cell or any other element based on the current
* URL and hook attributes.
Expand Down Expand Up @@ -1085,22 +1129,35 @@ const Session = {
});
},

handleViewsClick(event) {
const button = event.target.closest(`[data-el-view-toggle]`);
handleActivateViewClick(event) {
const button = event.target.closest(`[data-el-view-activate]`);

if (button) {
const view = button.getAttribute("data-el-view-toggle");
this.toggleView(view);
const view = button.getAttribute("data-el-view-activate");
this.activateView(view);
}
},

handleDeactivateViewClick() {
this.deactivateView();
},

toggleView(view) {
if (view === this.view) {
this.unsetView();
if (this.view) {
this.deactivateView();
} else {
this.activateView(view);
}
},

if (view === "custom") {
this.unsubscribeCustomViewFromSettings();
}
activateView(view) {
if (view === "output-panel") {
this.setView(view, {
showSection: true,
showMarkdown: false,
showOutput: true,
spotlight: false,
});
} else if (view === "code-zen") {
this.setView(view, {
showSection: false,
Expand Down Expand Up @@ -1146,6 +1203,14 @@ const Session = {
}
},

deactivateView() {
this.unsetView();

if (this.view === "custom") {
this.unsubscribeCustomViewFromSettings();
}
},

setView(view, options) {
this.view = view;
this.viewOptions = options;
Expand Down Expand Up @@ -1502,6 +1567,21 @@ const Session = {
getElement(name) {
return this.el.querySelector(`[data-el-${name}]`);
},

closeOutputPanelWindow() {
window.removeEventListener("message", this.handleExternalWindowMessage);
this.outputPanelWindow && this.outputPanelWindow.close();
this.outputPanelWindow = null;
},

handleExternalWindowMessage(event) {
if (event.origin != window.location.origin) return;
if (event.data === "external_window_popin_clicked") {
this.handleOutputPanelPopinClick();
} else if (event.data === "external_window_close_clicked") {
this.handleOutputPanelCloseClick();
}
},
};

/**
Expand Down
62 changes: 62 additions & 0 deletions lib/livebook_web/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ defmodule LivebookWeb.Helpers do

use LivebookWeb, :verified_routes

alias Livebook.Notebook.Cell

@doc """
Determines user platform based on the given *User-Agent* header.
"""
Expand Down Expand Up @@ -82,4 +84,64 @@ defmodule LivebookWeb.Helpers do
def format_datetime_relatively(date) do
date |> DateTime.to_naive() |> Livebook.Utils.Time.time_ago_in_words()
end

# TODO
def input_views_for_output(output, data, changed_input_ids) do
input_ids = for attrs <- Cell.find_inputs_in_output(output), do: attrs.id

data.input_infos
|> Map.take(input_ids)
|> Map.new(fn {input_id, %{value: value}} ->
{input_id, %{value: value, changed: MapSet.member?(changed_input_ids, input_id)}}
end)
end

# TODO
def visible_outputs(notebook) do
for section <- Enum.reverse(notebook.sections),
cell <- Enum.reverse(section.cells),
Cell.evaluable?(cell),
output <- filter_outputs(cell.outputs, notebook.app_settings.output_type),
do: {cell.id, output}
end

defp filter_outputs(outputs, :all), do: outputs
defp filter_outputs(outputs, :rich), do: rich_outputs(outputs)

defp rich_outputs(outputs) do
for output <- outputs, output = filter_output(output), do: output
end

defp filter_output({idx, output})
when elem(output, 0) in [:plain_text, :markdown, :image, :js, :control, :input],
do: {idx, output}

defp filter_output({idx, {:tabs, outputs, metadata}}) do
outputs_with_labels =
for {output, label} <- Enum.zip(outputs, metadata.labels),
output = filter_output(output),
do: {output, label}

{outputs, labels} = Enum.unzip(outputs_with_labels)

{idx, {:tabs, outputs, %{metadata | labels: labels}}}
end

defp filter_output({idx, {:grid, outputs, metadata}}) do
outputs = rich_outputs(outputs)

if outputs != [] do
{idx, {:grid, outputs, metadata}}
end
end

defp filter_output({idx, {:frame, outputs, metadata}}) do
outputs = rich_outputs(outputs)
{idx, {:frame, outputs, metadata}}
end

defp filter_output({idx, {:error, _message, {:interrupt, _, _}} = output}),
do: {idx, output}

defp filter_output(_output), do: nil
end
Loading
Loading