Add notebook presentation view (#1852)

Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com>
This commit is contained in:
Franklin Rakotomalala 2023-04-28 20:16:18 +02:00 committed by GitHub
parent 915bd0406b
commit 0eb0f417f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 121 additions and 78 deletions

View file

@ -65,7 +65,7 @@ solely client-side operations.
[data-el-section-headline]:not(:hover):not([data-js-focused]) [data-el-section-headline]:not(:hover):not([data-js-focused])
[data-el-section-collapse-button], [data-el-section-collapse-button],
[data-el-section]:not([data-js-collapsed]) [data-el-section-expand-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][data-js-collapsed]
> [data-el-section-content], > [data-el-section-content],
[data-el-section]:not([data-js-collapsed]) [data-el-section]:not([data-js-collapsed])
@ -276,40 +276,55 @@ solely client-side operations.
@apply hidden; @apply hidden;
} }
[data-el-session][data-js-code-zen] [data-el-section-headline], /* === Views === */
[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-view="code-zen"] [data-el-section-headline],
[data-el-session][data-js-code-zen] [data-el-cell][data-type="markdown"], [data-el-session][data-js-view="code-zen"] [data-el-section-subheadline],
[data-el-session][data-js-code-zen] [data-el-actions], [data-el-session][data-js-view="code-zen"]
[data-el-session][data-js-code-zen] [data-el-insert-buttons] { [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; @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; @apply space-y-0 mt-0;
} }
[data-el-session][data-js-code-zen][data-js-no-outputs] [data-el-session][data-js-view="code-zen"] [data-el-view-toggle="code-zen"] {
[data-el-outputs-container] { @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; @apply hidden;
} }
[data-el-session][data-js-code-zen][data-js-no-outputs] [data-el-session][data-js-view="presentation"]
[data-el-code-zen-outputs-toggle] [data-el-view-toggle="presentation"] {
[data-label-hide] { @apply text-green-bright-400;
}
[data-el-session]:is([data-js-view="code-zen"], [data-js-view="presentation"])
[data-el-views-disabled] {
@apply hidden; @apply hidden;
} }
[data-el-session][data-js-code-zen]:not([data-js-no-outputs]) [data-el-session]:not([data-js-view="code-zen"], [data-js-view="presentation"])
[data-el-code-zen-outputs-toggle] [data-el-views-enabled] {
[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] {
@apply hidden; @apply hidden;
} }

View file

@ -147,6 +147,11 @@ const JSView = {
// default timeout of 10s, so we increase it // default timeout of 10s, so we increase it
30_000 30_000
); );
this.unsubscribeFromCellEvents = globalPubSub.subscribe(
"navigation",
(event) => this.handleNavigationEvent(event)
);
}, },
updated() { updated() {
@ -167,6 +172,7 @@ const JSView = {
this.channel.push("disconnect", { ref: this.props.ref }); this.channel.push("disconnect", { ref: this.props.ref });
this.unsubscribeFromJSViewEvents(); this.unsubscribeFromJSViewEvents();
this.unsubscribeFromCellEvents();
}, },
getProps() { 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; export default JSView;

View file

@ -69,7 +69,7 @@ const Session = {
this.focusedId = null; this.focusedId = null;
this.insertMode = false; this.insertMode = false;
this.codeZen = false; this.view = null;
this.keyBuffer = new KeyBuffer(); this.keyBuffer = new KeyBuffer();
this.clientsMap = {}; this.clientsMap = {};
this.lastLocationReportByClientId = {}; this.lastLocationReportByClientId = {};
@ -131,20 +131,9 @@ const Session = {
this.handleCellIndicatorsClick(event) this.handleCellIndicatorsClick(event)
); );
this.getElement("code-zen-enable-button").addEventListener( this.getElement("views").addEventListener("click", (event) => {
"click", this.handleViewsClick(event);
(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("section-toggle-collapse-all-button").addEventListener( this.getElement("section-toggle-collapse-all-button").addEventListener(
"click", "click",
@ -421,15 +410,17 @@ const Session = {
} else if (keyBuffer.tryMatch(["N"])) { } else if (keyBuffer.tryMatch(["N"])) {
this.insertCellAboveFocused("code"); this.insertCellAboveFocused("code");
} else if (keyBuffer.tryMatch(["m"])) { } else if (keyBuffer.tryMatch(["m"])) {
!this.codeZen && this.insertCellBelowFocused("markdown"); !this.isViewCodeZen() && this.insertCellBelowFocused("markdown");
} else if (keyBuffer.tryMatch(["M"])) { } else if (keyBuffer.tryMatch(["M"])) {
!this.codeZen && this.insertCellAboveFocused("markdown"); !this.isViewCodeZen() && this.insertCellAboveFocused("markdown");
} else if (keyBuffer.tryMatch(["z"])) { } else if (keyBuffer.tryMatch(["v", "z"])) {
this.setCodeZen(!this.codeZen); this.toggleView("code-zen");
} else if (keyBuffer.tryMatch(["v", "p"])) {
this.toggleView("presentation");
} else if (keyBuffer.tryMatch(["c"])) { } else if (keyBuffer.tryMatch(["c"])) {
!this.codeZen && this.toggleCollapseSection(); !this.isViewCodeZen() && this.toggleCollapseSection();
} else if (keyBuffer.tryMatch(["C"])) { } else if (keyBuffer.tryMatch(["C"])) {
!this.codeZen && this.toggleCollapseAllSections(); !this.isViewCodeZen() && this.toggleCollapseAllSections();
} }
} }
}, },
@ -975,18 +966,27 @@ const Session = {
}); });
}, },
setCodeZen(enabled) { handleViewsClick(event) {
this.codeZen = enabled; 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 // If nothing is focused, use the first cell in the viewport
const focusedId = this.focusedId || this.nearbyFocusableId(null, 0); 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) { if (focusedId) {
const visibleId = this.ensureVisibleFocusableEl(focusedId); const visibleId = this.ensureVisibleFocusableEl(focusedId);
@ -1034,7 +1034,6 @@ const Session = {
} }
} }
}, },
// Server event handlers // Server event handlers
handleCellInserted(cellId) { handleCellInserted(cellId) {
@ -1046,7 +1045,7 @@ const Session = {
handleCellDeleted(cellId, siblingCellId) { handleCellDeleted(cellId, siblingCellId) {
if (this.focusedId === cellId) { if (this.focusedId === cellId) {
if (this.codeZen) { if (this.isViewCodeZen()) {
const visibleSiblingId = this.ensureVisibleFocusableEl(siblingCellId); const visibleSiblingId = this.ensureVisibleFocusableEl(siblingCellId);
this.setFocusedEl(visibleSiblingId); this.setFocusedEl(visibleSiblingId);
} else { } else {
@ -1333,6 +1332,14 @@ const Session = {
getElement(name) { getElement(name) {
return this.el.querySelector(`[data-el-${name}]`); return this.el.querySelector(`[data-el-${name}]`);
}, },
isViewCodeZen() {
return this.view === "code-zen";
},
isViewPresentation() {
return this.view === "presentation";
},
}; };
/** /**

View file

@ -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" 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 data-el-notebook-indicators
> >
<.code_zen_indicator /> <.view_indicator />
<.persistence_indicator <.persistence_indicator
file={@file} file={@file}
dirty={@dirty} dirty={@dirty}
@ -52,38 +52,36 @@ defmodule LivebookWeb.SessionLive.IndicatorsComponent do
""" """
end end
defp code_zen_indicator(assigns) do defp view_indicator(assigns) do
~H""" ~H"""
<span class="tooltip left" data-tooltip="Enter code zen (z)" data-el-code-zen-enable> <div class="tooltip left" data-tooltip="Choose views to activate" data-el-views>
<button <.menu id="views-menu" position={:top_right}>
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}>
<:toggle> <:toggle>
<button <button
class="icon-button icon-outlined-button border-green-bright-300 hover:bg-green-bright-50 focus:bg-green-bright-50" class="icon-button icon-outlined-button border-gray-200 hover:bg-gray-100 focus:bg-gray-100"
aria-label="code zen options" 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> </button>
</:toggle> </:toggle>
<.menu_item> <.menu_item>
<button role="menuitem" data-el-code-zen-outputs-toggle> <button role="menuitem" data-el-view-toggle="code-zen">
<.remix_icon icon="layout-bottom-2-line" /> <.remix_icon icon="code-line" />
<span data-label-show>Show outputs</span> <span>Code zen</span>
<span data-label-hide>Hide outputs</span>
</button> </button>
</.menu_item> </.menu_item>
<.menu_item> <.menu_item>
<button role="menuitem" data-el-code-zen-disable-button> <button role="menuitem" data-el-view-toggle="presentation">
<.remix_icon icon="close-line" /> <.remix_icon icon="slideshow-2-line" />
<span>Exit code zen</span> <span>Presentation</span>
</button> </button>
</.menu_item> </.menu_item>
</.menu> </.menu>