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 18 commits
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
84 changes: 80 additions & 4 deletions assets/css/js_interop.css
Original file line number Diff line number Diff line change
Expand Up @@ -296,20 +296,59 @@ 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-embedded] {
@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 Expand Up @@ -357,3 +396,40 @@ solely client-side operations.
:is([data-el-sidebar], [data-el-side-panel], [data-el-toggle-sidebar]) {
@apply hidden;
}

[data-el-session][data-js-view^="output-panel"]
~ #app-settings-modal
[data-el-app-settings-enable-output-panel-button] {
@apply hidden;
}

[data-el-session]:not([data-js-view="output-panel-popped-out"])
~ #app-settings-modal
[data-el-app-settings-popin-output-panel-button] {
@apply hidden;
}

/* === Output Panel === */

[data-el-output-panel-content]:not([data-js-dragging]) {
@apply gap-4 pt-4;
}

[data-el-output-panel-content]:not([data-js-dragging])
[data-el-output-panel-row-drop-area] {
@apply hidden;
}

[data-el-output-panel-row-drop-area][data-js-dragging] {
@apply h-16;
}

[data-el-output-panel-content][data-js-dragging]
[data-el-output-panel-item-options-controls] {
@apply hidden;
}

[data-el-output-panel-content]:not([data-js-dragging])
[data-el-output-panel-item-options-drag-label] {
@apply hidden;
}
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;
4 changes: 4 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 @@ -13,6 +14,7 @@ import ImageOutput from "./image_output";
import JSView from "./js_view";
import KeyboardControl from "./keyboard_control";
import MarkdownRenderer from "./markdown_renderer";
import OutputPanel from "./output_panel";
import ScrollOnUpdate from "./scroll_on_update";
import Session from "./session";
import TextareaAutosize from "./textarea_autosize";
Expand All @@ -31,6 +33,7 @@ export default {
Dropzone,
EditorSettings,
EmojiPicker,
ExternalWindow,
FocusOnUpdate,
Headline,
Highlight,
Expand All @@ -39,6 +42,7 @@ export default {
JSView,
KeyboardControl,
MarkdownRenderer,
OutputPanel,
ScrollOnUpdate,
Session,
TextareaAutosize,
Expand Down
21 changes: 12 additions & 9 deletions assets/js/hooks/js_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,9 @@ const JSView = {
this.iframe.className = "w-full h-0 absolute z-[1]";

const notebookEl = document.querySelector(`[data-el-notebook]`);
const notebookContentEl = notebookEl.querySelector(
`[data-el-notebook-content]`
);
const notebookContentEl =
notebookEl && notebookEl.querySelector(`[data-el-notebook-content]`);
const outputPanelEl = document.querySelector(`[data-el-output-panel]`);

// Most placeholder position changes are accompanied by changes to the
// notebook content element height (adding cells, inserting newlines
Expand All @@ -234,8 +234,9 @@ const JSView = {
const resizeObserver = new ResizeObserver((entries) => {
this.repositionIframe();
});
resizeObserver.observe(notebookContentEl);
resizeObserver.observe(notebookEl);
notebookContentEl && resizeObserver.observe(notebookContentEl);
notebookEl && resizeObserver.observe(notebookEl);
outputPanelEl && resizeObserver.observe(outputPanelEl);

// On certain events, like section/cell moved, a global event is
// dispatched to trigger reposition. This way we don't need to
Expand Down Expand Up @@ -307,19 +308,21 @@ const JSView = {

repositionIframe() {
const { iframe, iframePlaceholder } = this;
const notebookEl = document.querySelector(`[data-el-notebook]`);
const containerEl =
document.querySelector(`[data-el-notebook]`) ||
document.querySelector(`[data-el-output-panel]`);

if (isElementHidden(iframePlaceholder)) {
// When the placeholder is hidden, we hide the iframe as well
iframe.classList.add("hidden");
} else {
iframe.classList.remove("hidden");
const notebookBox = notebookEl.getBoundingClientRect();
const notebookBox = containerEl.getBoundingClientRect();
const placeholderBox = iframePlaceholder.getBoundingClientRect();
const top = placeholderBox.top - notebookBox.top + notebookEl.scrollTop;
const top = placeholderBox.top - notebookBox.top + containerEl.scrollTop;
iframe.style.top = `${top}px`;
const left =
placeholderBox.left - notebookBox.left + notebookEl.scrollLeft;
placeholderBox.left - notebookBox.left + containerEl.scrollLeft;
iframe.style.left = `${left}px`;
iframe.style.height = `${placeholderBox.height}px`;
iframe.style.width = `${placeholderBox.width}px`;
Expand Down
125 changes: 125 additions & 0 deletions assets/js/hooks/output_panel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import {
getAttributeOrThrow,
getAttributeOrDefault,
parseInteger,
} from "../lib/attribute";
import { globalPubSub } from "../lib/pub_sub";
/**
* A hook for the output panel.
*/

const OutputPanel = {
mounted() {
this.props = this.getProps();
this.isDragging = false;
this.draggedEl = null;

this.el.addEventListener("dragstart", (event) => {
this.startDragging(event.target);
});

this.el.addEventListener("dragend", (event) => {
this.stopDragging();
});

this.el.addEventListener("dragenter", (event) => {
//console.log("Valid drop area", event);
});

this.el.addEventListener("dragleave", (event) => {
//console.log("Valid drop area left", event);
});

this.el.addEventListener("dragover", (event) => {
event.stopPropagation();
event.preventDefault();
});

this.el.addEventListener("drop", (event) => {
event.stopPropagation();
event.preventDefault();

const dstEl = event.target.closest(`[phx-hook="Dropzone"]`);

const srcEl = this.draggedEl.closest(`[data-el-output-panel-item]`);

if (dstEl && srcEl) {
const cellId = getAttributeOrThrow(srcEl, "data-cell-id");
const srcRow = getAttributeOrThrow(
srcEl,
"data-row-index",
parseInteger
);
const srcCol = getAttributeOrThrow(
srcEl,
"data-col-index",
parseInteger
);
let dstRow = getAttributeOrThrow(dstEl, "data-row-index", parseInteger);
let dstCol = getAttributeOrDefault(
dstEl,
"data-col-index",
null,
parseInteger
);

if (dstCol !== null) {
console.log("New position");
console.log("Rows", srcRow, "->", dstRow);
console.log("Cols", srcCol, "->", dstCol);
// when dropping on the right side, move element one column to the right
if (srcRow !== dstRow && event.layerX > dstEl.offsetWidth / 2)
dstCol += 1;
if (srcRow === dstRow && srcCol < dstCol) dstCol += 1;

this.pushEventTo(this.props.phxTarget, "handle_move_item", {
cell_id: cellId,
row_index: dstRow,
col_index: dstCol,
});
} else {
console.log("New row");
console.log("Rows", srcRow, "->", dstRow);
console.log("Cols", srcCol, "->", dstCol);
this.pushEventTo(
this.props.phxTarget,
"handle_move_item_to_new_row",
{
cell_id: cellId,
row_index: dstRow,
}
);
}
}
setTimeout(
() => globalPubSub.broadcast("js_views", { type: "reposition" }),
200
jannikbecher marked this conversation as resolved.
Show resolved Hide resolved
);
this.stopDragging();
});
},
update() {
this.props = this.getProps();
},
getProps() {
return {
phxTarget: getAttributeOrThrow(this.el, "data-phx-target", parseInteger),
};
},
startDragging(element) {
if (!this.isDragging) {
this.isDragging = true;
this.draggedEl = element;

this.el.setAttribute("data-js-dragging", "");
}
},
stopDragging() {
if (this.isDragging) {
this.isDragging = false;
this.el.removeAttribute("data-js-dragging");
}
},
};

export default OutputPanel;
Loading