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 :saved_hubs, :list, required: true
slot :inner_block, required: true
slot :topbar_action
def layout(assigns) do
~H"""
<.live_region role="alert" />
<.sidebar current_page={@current_page} current_user={@current_user} saved_hubs={@saved_hubs} />
JS.toggle(to: "[data-el-toggle-sidebar]")
}
>
<.remix_icon icon="menu-unfold-line" />
<%= if @topbar_action do %>
<%= render_slot(@topbar_action) %>
<% else %>
<.link navigate={~p"/"} class="flex items-center" aria-label="go to home">
<.remix_icon icon="home-6-line" />
Home
<% end %>
<%= render_slot(@inner_block) %>
<.current_user_modal current_user={@current_user} />
"""
end
defp sidebar(assigns) do
~H"""
JS.toggle(to: "[data-el-toggle-sidebar]")
}
>
<.remix_icon icon="menu-fold-line" />
<.link navigate={~p"/"} class="flex items-center border-l-4 border-gray-900 group">
Livebook
v<%= Livebook.Config.app_version() %>
<.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}
/>
<.hub_section hubs={@saved_hubs} current_page={@current_page} />
<.remix_icon icon="shut-down-line" class="text-lg leading-6 w-[56px] flex justify-center" />
Shut Down
<.user_avatar
user={@current_user}
class="w-8 h-8 group-hover:ring-white group-hover:ring-2"
text_class="text-xs"
/>
<%= @current_user.name %>
"""
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" />
<%= @title %>
"""
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)
]}
>
<%= @hub.emoji %>
<%= @hub.name %>
"""
end
defp sidebar_hub_link_with_tooltip(assigns) do
~H"""
<.link {hub_connection_link_opts(@hub, @to, @current)}>
<%= @hub.name %>
"""
end
defp hub_section(assigns) do
~H"""
WORKSPACES
<%= 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} />
"""
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"""
<.link navigate={@back_navigate}>
<.remix_icon icon="arrow-left-line" class="align-middle mr-2 text-2xl text-gray-800" />
<%= if @inner_block != [] do %>
<%= render_slot(@inner_block) %>
<% else %>
<%= @text %>
<% end %>
"""
end
@doc """
Topbar for showing pinned, page-specific messages.
"""
attr :variant, :atom, default: :info, values: [:warning, :info, :error]
slot :inner_block, required: true
def topbar(assigns) do
~H"""
<%= render_slot(@inner_block) %>
"""
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"""
"""
end
else
def dev_script(assigns), do: ~H""
end
end