Packages
livebook
0.18.1
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook_web/components/layout_components.ex
defmodule LivebookWeb.LayoutComponents do
use LivebookWeb, :html
import LivebookWeb.UserComponents
alias Livebook.Hubs.Provider
@doc """
The layout used in the non-session pages.
"""
attr :current_page, :string, required: true
attr :current_user, Livebook.Users.User, required: true
attr :teams_auth, :atom, values: [:online, :offline]
attr :saved_hubs, :list, required: true
slot :inner_block, required: true
slot :topbar_action
def layout(assigns) do
~H"""
<div class="flex grow h-full">
<div class="absolute md:static h-full z-[600]">
<.live_region role="alert" />
<.sidebar current_page={@current_page} current_user={@current_user} saved_hubs={@saved_hubs} />
</div>
<div class="grow overflow-y-auto">
<.topbar :if={@teams_auth == :online} variant="warning">
This Livebook instance has been configured for notebook deployment and is in read-only mode.
</.topbar>
<.topbar :if={@teams_auth == :offline} variant="warning">
You are running an offline Workspace for deployment. You cannot modify its settings.
</.topbar>
<div class="md:hidden sticky flex items-center justify-between h-14 px-4 top-0 left-0 z-[500] bg-white border-b border-gray-200">
<div class="pt-1 text-xl text-gray-400 hover:text-gray-600 focus:text-gray-600">
<button
data-el-toggle-sidebar
aria-label="show sidebar"
phx-click={
JS.remove_class("hidden", to: "[data-el-sidebar]")
|> JS.toggle(to: "[data-el-toggle-sidebar]")
}
>
<.remix_icon icon="menu-unfold-line" />
</button>
</div>
<div>
<%= if @topbar_action do %>
{render_slot(@topbar_action)}
<% else %>
<div class="text-gray-400 hover:text-gray-600 focus:text-gray-600">
<.link navigate={~p"/"} class="flex items-center" aria-label="go to home">
<.remix_icon icon="home-6-line" />
<span class="pl-2">Home</span>
</.link>
</div>
<% end %>
</div>
</div>
{render_slot(@inner_block)}
</div>
</div>
<.current_user_modal current_user={@current_user} />
"""
end
defp sidebar(assigns) do
~H"""
<nav
class="hidden md:flex w-[17rem] h-full py-2 md:py-5 bg-gray-900"
aria-label="sidebar"
data-el-sidebar
>
<button
class="hidden text-xl text-gray-300 hover:text-white focus:text-white absolute top-4 right-3"
aria-label="hide sidebar"
data-el-toggle-sidebar
phx-click={
JS.add_class("hidden", to: "[data-el-sidebar]")
|> JS.toggle(to: "[data-el-toggle-sidebar]")
}
>
<.remix_icon icon="menu-fold-line" />
</button>
<div class="flex flex-col justify-between h-full w-full">
<div class="flex flex-col">
<div class="space-y-3">
<div class="flex items-center mb-5">
<.link navigate={~p"/"} class="flex items-center ml-1 group">
<img
src={~p"/images/logo.png"}
class="mx-2"
height="40"
width="40"
alt="logo livebook"
/>
<span class="text-gray-300 text-2xl font-logo ml-[-1px] group-hover:text-white pt-1">
Livebook
</span>
</.link>
<span class="text-gray-300 text-xs font-normal font-sans mx-2.5 pt-3 cursor-default">
v{Livebook.Config.app_version()}
</span>
</div>
<.sidebar_link title="Home" icon="home-6-line" to={~p"/"} current={@current_page} />
<.sidebar_link
title="Local apps"
icon="rocket-line"
to={~p"/apps-dashboard"}
current={@current_page}
/>
<.sidebar_link title="Learn" icon="article-line" to={~p"/learn"} current={@current_page} />
<.sidebar_link
title="Settings"
icon="settings-3-line"
to={~p"/settings"}
current={@current_page}
/>
<button
:if={Livebook.Config.shutdown_callback()}
class="h-7 flex items-center text-gray-400 hover:text-white border-l-4 border-transparent hover:border-white"
aria-label="shutdown"
phx-click="shutdown"
>
<.remix_icon
icon="shut-down-line"
class="text-lg leading-6 w-[56px] flex justify-center"
/>
<span class="text-sm font-medium">
Shut Down
</span>
</button>
</div>
<.hub_section hubs={@saved_hubs} current_page={@current_page} />
</div>
<div class="flex flex-col">
<button
:if={Livebook.Config.logout_enabled?() and @current_user.email != nil}
class="h-7 flex items-center text-gray-400 hover:text-white border-l-4 border-transparent hover:border-white"
aria-label="logout"
phx-click="logout"
>
<.remix_icon
icon="logout-box-line"
class="text-lg leading-6 w-[56px] flex justify-center"
/>
<span class="text-sm font-medium">
Logout
</span>
</button>
<button
class="mt-6 flex items-center group border-l-4 border-transparent"
aria_label="user profile"
phx-click={show_current_user_modal()}
>
<div class="w-[56px] flex justify-center">
<.user_avatar
user={@current_user}
class="w-8 h-8 group-hover:ring-white group-hover:ring-2"
text_class="text-xs"
/>
</div>
<span class="text-sm text-gray-400 font-medium group-hover:text-white">
{@current_user.name}
</span>
</button>
</div>
</div>
</nav>
"""
end
defp sidebar_link(assigns) do
~H"""
<.link
navigate={@to}
class={[
"h-7 flex items-center hover:text-white border-l-4 hover:border-white",
sidebar_link_text_color(@to, @current),
sidebar_link_border_color(@to, @current)
]}
>
<.remix_icon icon={@icon} class="text-lg leading-6 w-[56px] flex justify-center" />
<span class="text-sm font-medium">
{@title}
</span>
</.link>
"""
end
defp sidebar_hub_link(assigns) do
~H"""
<.link
id={"hub-#{@hub.id}"}
navigate={@to}
class={[
"h-7 flex items-center hover:text-white border-l-4 hover:border-white",
sidebar_link_text_color(@to, @current),
sidebar_link_border_color(@to, @current)
]}
>
<div class="text-lg leading-6 w-[56px] flex justify-center">
<span class="relative">
{@hub.emoji}
</span>
</div>
<span class="text-sm font-medium">
{@hub.name}
</span>
</.link>
"""
end
defp sidebar_hub_link_with_tooltip(assigns) do
~H"""
<.link {hub_connection_link_opts(@hub, @to, @current)}>
<div class="text-lg leading-6 w-[56px] flex justify-center">
<span class="relative">
{@hub.emoji}
<div class={[
"absolute w-[10px] h-[10px] border-gray-900 border-2 rounded-full right-0 bottom-0",
if(@hub.connected?, do: "bg-green-400", else: "bg-red-400")
]} />
</span>
</div>
<span class="text-sm font-medium">
{@hub.name}
</span>
</.link>
"""
end
defp hub_section(assigns) do
~H"""
<div id="hubs" class="flex flex-col mt-12">
<div class="space-y-3">
<div class="grid grid-cols-1 md:grid-cols-2 relative leading-6 mb-2">
<small class="ml-5 font-medium text-gray-300 cursor-default">WORKSPACES</small>
</div>
<%= for hub <- @hubs do %>
<%= if Provider.connection_spec(hub.provider) do %>
<.sidebar_hub_link_with_tooltip hub={hub} to={~p"/hub/#{hub.id}"} current={@current_page} />
<% else %>
<.sidebar_hub_link hub={hub} to={~p"/hub/#{hub.id}"} current={@current_page} />
<% end %>
<% end %>
<.sidebar_link title="Add Organization" icon="add-line" to={~p"/hub"} current={@current_page} />
</div>
</div>
"""
end
defp sidebar_link_text_color(to, current) when to == current, do: "text-white"
defp sidebar_link_text_color(_to, _current), do: "text-gray-400"
defp sidebar_link_border_color(to, current) when to == current, do: "border-white"
defp sidebar_link_border_color(_to, _current), do: "border-transparent"
defp hub_connection_link_opts(%{provider: hub}, to, current) do
text_color = sidebar_link_text_color(to, current)
border_color = sidebar_link_border_color(to, current)
class =
"h-7 flex items-center hover:text-white #{text_color} border-l-4 #{border_color} hover:border-white"
if message = Provider.connection_status(hub) do
[
id: "hub-#{hub.id}",
navigate: to,
"data-tooltip": message,
class: "tooltip right " <> class
]
else
[id: "hub-#{hub.id}", navigate: to, class: class]
end
end
@doc """
Renders page title.
## Examples
<.title text="Learn" />
"""
attr :text, :string, default: nil
attr :back_navigate, :string, default: nil
slot :inner_block
def title(assigns) do
if assigns.text == nil and assigns.inner_block == [] do
raise ArgumentError, "should pass at least text attribute or an inner block"
end
~H"""
<div class="relative">
<div
:if={@back_navigate}
class="hidden md:flex absolute top-0 bottom-0 left-0 transform -translate-x-full"
>
<.link navigate={@back_navigate}>
<.remix_icon icon="arrow-left-line" class="align-middle mr-2 text-2xl text-gray-800" />
</.link>
</div>
<h1 class="text-2xl text-gray-800 font-medium">
<%= if @inner_block != [] do %>
{render_slot(@inner_block)}
<% else %>
{@text}
<% end %>
</h1>
</div>
"""
end
@doc """
Topbar for showing pinned, page-specific messages.
"""
attr :variant, :string, default: "info", values: ~w(warning info error)
slot :inner_block, required: true
def topbar(assigns) do
~H"""
<div class={["px-2 py-2 text-sm text-center", topbar_class(@variant)]}>
{render_slot(@inner_block)}
</div>
"""
end
defp topbar_class("warning"), do: "bg-yellow-200 text-gray-900"
defp topbar_class("info"), do: "bg-blue-200 text-gray-900"
defp topbar_class("error"), do: "bg-red-200 text-gray-900"
@doc """
Returns an inline script to inject in dev mode.
The main JS file is loaded asynchronously as a module, so we inline
the live reloader listener to make sure it is already registered
when the event is dispatched.
"""
if Mix.env() == :dev do
def dev_script(assigns) do
~H"""
<script type="text/javascript">
window.addEventListener(
"phx:live_reload:attached",
({ detail: reloader }) => {
// Enable server log streaming to client. Disable with reloader.disableServerLogs()
// reloader.enableServerLogs();
// Open configured PLUG_EDITOR at file:line of the clicked element's HEEx component
//
// * click with "c" key pressed to open at caller location
// * click with "d" key pressed to open at function component definition location
//
let keyDown;
window.addEventListener("keydown", (event) => (keyDown = event.key));
window.addEventListener("keyup", (event) => (keyDown = null));
window.addEventListener(
"click",
(event) => {
if (keyDown === "c") {
event.preventDefault();
event.stopImmediatePropagation();
reloader.openEditorAtCaller(event.target);
} else if (keyDown === "d") {
event.preventDefault();
event.stopImmediatePropagation();
reloader.openEditorAtDef(event.target);
}
},
true,
);
window.liveReloader = reloader;
},
);
</script>
"""
end
else
def dev_script(assigns), do: ~H""
end
end