mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-10-18 01:16:29 +08:00
Add notebook presentation view (#1852)
Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com>
This commit is contained in:
parent
915bd0406b
commit
0eb0f417f5
4 changed files with 121 additions and 78 deletions
|
@ -65,7 +65,7 @@ solely client-side operations.
|
|||
[data-el-section-headline]:not(:hover):not([data-js-focused])
|
||||
[data-el-section-collapse-button],
|
||||
[data-el-section]:not([data-js-collapsed]) [data-el-section-expand-button],
|
||||
[data-el-session]:not([data-js-code-zen])
|
||||
[data-el-session]:not([data-js-view="code-zen"])
|
||||
[data-el-section][data-js-collapsed]
|
||||
> [data-el-section-content],
|
||||
[data-el-section]:not([data-js-collapsed])
|
||||
|
@ -276,40 +276,55 @@ solely client-side operations.
|
|||
@apply hidden;
|
||||
}
|
||||
|
||||
[data-el-session][data-js-code-zen] [data-el-section-headline],
|
||||
[data-el-session][data-js-code-zen] [data-el-section-subheadline],
|
||||
[data-el-session][data-js-code-zen] [data-el-section-subheadline-collapsed],
|
||||
[data-el-session][data-js-code-zen] [data-el-cell][data-type="markdown"],
|
||||
[data-el-session][data-js-code-zen] [data-el-actions],
|
||||
[data-el-session][data-js-code-zen] [data-el-insert-buttons] {
|
||||
/* === Views === */
|
||||
|
||||
[data-el-session][data-js-view="code-zen"] [data-el-section-headline],
|
||||
[data-el-session][data-js-view="code-zen"] [data-el-section-subheadline],
|
||||
[data-el-session][data-js-view="code-zen"]
|
||||
[data-el-section-subheadline-collapsed],
|
||||
[data-el-session][data-js-view="code-zen"] [data-el-cell][data-type="markdown"],
|
||||
[data-el-session][data-js-view="code-zen"] [data-el-actions],
|
||||
[data-el-session][data-js-view="code-zen"] [data-el-insert-buttons] {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
[data-el-session][data-js-code-zen] [data-el-sections-container] {
|
||||
[data-el-session][data-js-view="code-zen"] [data-el-sections-container] {
|
||||
@apply space-y-0 mt-0;
|
||||
}
|
||||
|
||||
[data-el-session][data-js-code-zen][data-js-no-outputs]
|
||||
[data-el-outputs-container] {
|
||||
[data-el-session][data-js-view="code-zen"] [data-el-view-toggle="code-zen"] {
|
||||
@apply text-green-bright-400;
|
||||
}
|
||||
|
||||
[data-el-session][data-js-view="presentation"]
|
||||
[data-el-section-headline]:not([data-js-focused]),
|
||||
[data-el-session][data-js-view="presentation"]
|
||||
[data-el-section-subheadline]:not([data-js-focused]),
|
||||
[data-el-session][data-js-view="presentation"]
|
||||
[data-el-cell]:not([data-js-focused]),
|
||||
[data-el-session][data-js-view="presentation"]
|
||||
[data-el-js-view-iframes]
|
||||
iframe:not([data-js-focused]) {
|
||||
@apply opacity-10;
|
||||
}
|
||||
|
||||
[data-el-session][data-js-view="presentation"] [data-el-sidebar],
|
||||
[data-el-session][data-js-view="presentation"] [data-el-side-panel],
|
||||
[data-el-session][data-js-view="presentation"] [data-el-toggle-sidebar] {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
[data-el-session][data-js-code-zen][data-js-no-outputs]
|
||||
[data-el-code-zen-outputs-toggle]
|
||||
[data-label-hide] {
|
||||
[data-el-session][data-js-view="presentation"]
|
||||
[data-el-view-toggle="presentation"] {
|
||||
@apply text-green-bright-400;
|
||||
}
|
||||
|
||||
[data-el-session]:is([data-js-view="code-zen"], [data-js-view="presentation"])
|
||||
[data-el-views-disabled] {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
[data-el-session][data-js-code-zen]:not([data-js-no-outputs])
|
||||
[data-el-code-zen-outputs-toggle]
|
||||
[data-label-show] {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
[data-el-session][data-js-code-zen] [data-el-code-zen-enable] {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
[data-el-session]:not([data-js-code-zen]) [data-el-focus-mode-options] {
|
||||
[data-el-session]:not([data-js-view="code-zen"], [data-js-view="presentation"])
|
||||
[data-el-views-enabled] {
|
||||
@apply hidden;
|
||||
}
|
||||
|
|
|
@ -147,6 +147,11 @@ const JSView = {
|
|||
// default timeout of 10s, so we increase it
|
||||
30_000
|
||||
);
|
||||
|
||||
this.unsubscribeFromCellEvents = globalPubSub.subscribe(
|
||||
"navigation",
|
||||
(event) => this.handleNavigationEvent(event)
|
||||
);
|
||||
},
|
||||
|
||||
updated() {
|
||||
|
@ -167,6 +172,7 @@ const JSView = {
|
|||
this.channel.push("disconnect", { ref: this.props.ref });
|
||||
|
||||
this.unsubscribeFromJSViewEvents();
|
||||
this.unsubscribeFromCellEvents();
|
||||
},
|
||||
|
||||
getProps() {
|
||||
|
@ -454,6 +460,23 @@ const JSView = {
|
|||
});
|
||||
}
|
||||
},
|
||||
|
||||
handleNavigationEvent(event) {
|
||||
if (event.type === "element_focused") {
|
||||
// If a parent focusable element is focused, mirror the attribute
|
||||
// to the iframe element. This way if we need to apply style rules
|
||||
// (such as opacity) to focused elements, we can target the iframe
|
||||
// elements placed elsewhere in the DOM
|
||||
|
||||
const focusableEl = this.el.closest(`[data-focusable-id]`);
|
||||
const focusableId = focusableEl ? focusableEl.dataset.focusableId : null;
|
||||
|
||||
this.iframe.toggleAttribute(
|
||||
"data-js-focused",
|
||||
focusableId === event.focusableId
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default JSView;
|
||||
|
|
|
@ -69,7 +69,7 @@ const Session = {
|
|||
|
||||
this.focusedId = null;
|
||||
this.insertMode = false;
|
||||
this.codeZen = false;
|
||||
this.view = null;
|
||||
this.keyBuffer = new KeyBuffer();
|
||||
this.clientsMap = {};
|
||||
this.lastLocationReportByClientId = {};
|
||||
|
@ -131,20 +131,9 @@ const Session = {
|
|||
this.handleCellIndicatorsClick(event)
|
||||
);
|
||||
|
||||
this.getElement("code-zen-enable-button").addEventListener(
|
||||
"click",
|
||||
(event) => this.setCodeZen(true)
|
||||
);
|
||||
|
||||
this.getElement("code-zen-disable-button").addEventListener(
|
||||
"click",
|
||||
(event) => this.setCodeZen(false)
|
||||
);
|
||||
|
||||
this.getElement("code-zen-outputs-toggle").addEventListener(
|
||||
"click",
|
||||
(event) => this.el.toggleAttribute("data-js-no-outputs")
|
||||
);
|
||||
this.getElement("views").addEventListener("click", (event) => {
|
||||
this.handleViewsClick(event);
|
||||
});
|
||||
|
||||
this.getElement("section-toggle-collapse-all-button").addEventListener(
|
||||
"click",
|
||||
|
@ -421,15 +410,17 @@ const Session = {
|
|||
} else if (keyBuffer.tryMatch(["N"])) {
|
||||
this.insertCellAboveFocused("code");
|
||||
} else if (keyBuffer.tryMatch(["m"])) {
|
||||
!this.codeZen && this.insertCellBelowFocused("markdown");
|
||||
!this.isViewCodeZen() && this.insertCellBelowFocused("markdown");
|
||||
} else if (keyBuffer.tryMatch(["M"])) {
|
||||
!this.codeZen && this.insertCellAboveFocused("markdown");
|
||||
} else if (keyBuffer.tryMatch(["z"])) {
|
||||
this.setCodeZen(!this.codeZen);
|
||||
!this.isViewCodeZen() && this.insertCellAboveFocused("markdown");
|
||||
} else if (keyBuffer.tryMatch(["v", "z"])) {
|
||||
this.toggleView("code-zen");
|
||||
} else if (keyBuffer.tryMatch(["v", "p"])) {
|
||||
this.toggleView("presentation");
|
||||
} else if (keyBuffer.tryMatch(["c"])) {
|
||||
!this.codeZen && this.toggleCollapseSection();
|
||||
!this.isViewCodeZen() && this.toggleCollapseSection();
|
||||
} else if (keyBuffer.tryMatch(["C"])) {
|
||||
!this.codeZen && this.toggleCollapseAllSections();
|
||||
!this.isViewCodeZen() && this.toggleCollapseAllSections();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -975,18 +966,27 @@ const Session = {
|
|||
});
|
||||
},
|
||||
|
||||
setCodeZen(enabled) {
|
||||
this.codeZen = enabled;
|
||||
handleViewsClick(event) {
|
||||
const button = event.target.closest(`[data-el-view-toggle]`);
|
||||
|
||||
if (button) {
|
||||
const view = button.getAttribute("data-el-view-toggle");
|
||||
this.toggleView(view);
|
||||
}
|
||||
},
|
||||
|
||||
toggleView(view) {
|
||||
if (this.view === view) {
|
||||
this.view = null;
|
||||
this.el.removeAttribute("data-js-view");
|
||||
} else {
|
||||
this.view = view;
|
||||
this.el.setAttribute("data-js-view", view);
|
||||
}
|
||||
|
||||
// If nothing is focused, use the first cell in the viewport
|
||||
const focusedId = this.focusedId || this.nearbyFocusableId(null, 0);
|
||||
|
||||
if (enabled) {
|
||||
this.el.setAttribute("data-js-code-zen", "");
|
||||
} else {
|
||||
this.el.removeAttribute("data-js-code-zen");
|
||||
}
|
||||
|
||||
if (focusedId) {
|
||||
const visibleId = this.ensureVisibleFocusableEl(focusedId);
|
||||
|
||||
|
@ -1034,7 +1034,6 @@ const Session = {
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Server event handlers
|
||||
|
||||
handleCellInserted(cellId) {
|
||||
|
@ -1046,7 +1045,7 @@ const Session = {
|
|||
|
||||
handleCellDeleted(cellId, siblingCellId) {
|
||||
if (this.focusedId === cellId) {
|
||||
if (this.codeZen) {
|
||||
if (this.isViewCodeZen()) {
|
||||
const visibleSiblingId = this.ensureVisibleFocusableEl(siblingCellId);
|
||||
this.setFocusedEl(visibleSiblingId);
|
||||
} else {
|
||||
|
@ -1333,6 +1332,14 @@ const Session = {
|
|||
getElement(name) {
|
||||
return this.el.querySelector(`[data-el-${name}]`);
|
||||
},
|
||||
|
||||
isViewCodeZen() {
|
||||
return this.view === "code-zen";
|
||||
},
|
||||
|
||||
isViewPresentation() {
|
||||
return this.view === "presentation";
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -33,7 +33,7 @@ defmodule LivebookWeb.SessionLive.IndicatorsComponent do
|
|||
class="flex flex-row-reverse sm:flex-col items-center justify-end p-2 sm:p-0 space-x-2 space-x-reverse sm:space-x-0 sm:space-y-2"
|
||||
data-el-notebook-indicators
|
||||
>
|
||||
<.code_zen_indicator />
|
||||
<.view_indicator />
|
||||
<.persistence_indicator
|
||||
file={@file}
|
||||
dirty={@dirty}
|
||||
|
@ -52,38 +52,36 @@ defmodule LivebookWeb.SessionLive.IndicatorsComponent do
|
|||
"""
|
||||
end
|
||||
|
||||
defp code_zen_indicator(assigns) do
|
||||
defp view_indicator(assigns) do
|
||||
~H"""
|
||||
<span class="tooltip left" data-tooltip="Enter code zen (z)" data-el-code-zen-enable>
|
||||
<button
|
||||
class="icon-button icon-outlined-button border-gray-200 hover:bg-gray-100 focus:bg-gray-100"
|
||||
aria-label="enter code zen"
|
||||
data-el-code-zen-enable-button
|
||||
>
|
||||
<.remix_icon icon="code-line" class="text-xl text-gray-400" />
|
||||
</button>
|
||||
</span>
|
||||
<div data-el-focus-mode-options>
|
||||
<.menu id="focus-mode-menu" position={:top_right}>
|
||||
<div class="tooltip left" data-tooltip="Choose views to activate" data-el-views>
|
||||
<.menu id="views-menu" position={:top_right}>
|
||||
<:toggle>
|
||||
<button
|
||||
class="icon-button icon-outlined-button border-green-bright-300 hover:bg-green-bright-50 focus:bg-green-bright-50"
|
||||
aria-label="code zen options"
|
||||
class="icon-button icon-outlined-button border-gray-200 hover:bg-gray-100 focus:bg-gray-100"
|
||||
aria-label="choose views to activate"
|
||||
data-el-views-disabled
|
||||
>
|
||||
<.remix_icon icon="code-line" class="text-xl text-green-bright-400" />
|
||||
<.remix_icon icon="layout-5-line" class="text-xl text-gray-400" />
|
||||
</button>
|
||||
<button
|
||||
class="icon-button icon-outlined-button border-green-bright-300 hover:bg-green-bright-50 focus:bg-green-bright-50"
|
||||
aria-label="choose views to activate"
|
||||
data-el-views-enabled
|
||||
>
|
||||
<.remix_icon icon="layout-5-line" class="text-xl text-green-bright-400" />
|
||||
</button>
|
||||
</:toggle>
|
||||
<.menu_item>
|
||||
<button role="menuitem" data-el-code-zen-outputs-toggle>
|
||||
<.remix_icon icon="layout-bottom-2-line" />
|
||||
<span data-label-show>Show outputs</span>
|
||||
<span data-label-hide>Hide outputs</span>
|
||||
<button role="menuitem" data-el-view-toggle="code-zen">
|
||||
<.remix_icon icon="code-line" />
|
||||
<span>Code zen</span>
|
||||
</button>
|
||||
</.menu_item>
|
||||
<.menu_item>
|
||||
<button role="menuitem" data-el-code-zen-disable-button>
|
||||
<.remix_icon icon="close-line" />
|
||||
<span>Exit code zen</span>
|
||||
<button role="menuitem" data-el-view-toggle="presentation">
|
||||
<.remix_icon icon="slideshow-2-line" />
|
||||
<span>Presentation</span>
|
||||
</button>
|
||||
</.menu_item>
|
||||
</.menu>
|
||||
|
|
Loading…
Add table
Reference in a new issue