defmodule LivebookWeb.SessionLive.Render do
use LivebookWeb, :html
import LivebookWeb.UserComponents
import LivebookWeb.SessionHelpers
import Livebook.Utils, only: [format_bytes: 1]
alias Livebook.Notebook.Cell
def render(assigns) do
~H"""
<.sidebar
session={@session}
live_action={@live_action}
current_user={@current_user}
runtime_connected_nodes={@data_view.runtime_connected_nodes}
/>
<.side_panel app={@app} session={@session} data_view={@data_view} client_id={@client_id} />
<.indicators
session_id={@session.id}
file={@data_view.file}
dirty={@data_view.dirty}
persistence_warnings={@data_view.persistence_warnings}
autosave_interval_s={@data_view.autosave_interval_s}
runtime_status={@data_view.runtime_status}
global_status={@data_view.global_status}
/>
<.notebook_content
data_view={@data_view}
session={@session}
client_id={@client_id}
allowed_uri_schemes={@allowed_uri_schemes}
saved_hubs={@saved_hubs}
starred_files={@starred_files}
/>
<.current_user_modal current_user={@current_user} />
<.modal
:if={@live_action == :runtime_settings}
id="runtime-settings-modal"
show
width={:big}
patch={@self_path}
>
<.live_component
module={LivebookWeb.SessionLive.RuntimeComponent}
id="runtime-settings"
session={@session}
return_to={@self_path}
runtime={@data_view.runtime}
runtime_status={@data_view.runtime_status}
runtime_connect_info={@data_view.runtime_connect_info}
hub={@data_view.hub}
hub_secrets={@data_view.hub_secrets}
/>
<.modal
:if={@live_action == :file_settings}
id="persistence-modal"
show
width={:big}
patch={@self_path}
>
<.live_component
module={LivebookWeb.SessionLive.PersistenceComponent}
id="persistence"
session={@session}
file={@data_view.file}
hub={@data_view.hub}
context={@action_assigns.context}
persist_outputs={@data_view.persist_outputs}
autosave_interval_s={@data_view.autosave_interval_s}
/>
<.modal
:if={@live_action == :app_settings}
id="app-settings-modal"
show
width={:medium}
patch={@self_path}
>
<.live_component
module={LivebookWeb.SessionLive.AppSettingsComponent}
id="app-settings"
session={@session}
settings={@data_view.app_settings}
context={@action_assigns.context}
deployed_app_slug={@data_view.deployed_app_slug}
/>
<.modal
:if={@live_action == :app_docker}
id="app-docker-modal"
show
width={:large}
patch={@self_path}
>
<.live_component
module={LivebookWeb.SessionLive.AppDockerComponent}
id="app-docker"
session={@session}
hub={@data_view.hub}
file={@data_view.file}
app_settings={@data_view.app_settings}
secrets={@data_view.secrets}
file_entries={@data_view.file_entries}
settings={@data_view.app_settings}
deployment_group_id={@data_view.deployment_group_id}
/>
<.modal :if={@live_action == :app_teams} id="app-teams-modal" show width={:big} patch={@self_path}>
<%= live_render(@socket, LivebookWeb.SessionLive.AppTeamsLive,
id: "app-teams",
session: %{
"session_pid" => @session.pid
}
) %>
<.modal
:if={@live_action == :app_teams_hub_info}
id="app-teams-hub-info-modal"
show
width={:big}
patch={@self_path}
>
<.app_teams_hub_info_content
any_team_hub?={Enum.any?(@saved_hubs, &(Livebook.Hubs.Provider.type(&1.provider) == "team"))}
session={@session}
/>
<.modal
:if={@live_action == :add_file_entry}
id="add-file-entry-modal"
show
width={:big}
patch={@self_path}
>
<.add_file_entry_content
session={@session}
hub={@data_view.hub}
file_entries={@data_view.file_entries}
tab={@action_assigns.tab}
/>
<.modal
:if={@live_action == :rename_file_entry}
id="rename-file-entry-modal"
show
width={:big}
patch={@self_path}
>
<.live_component
module={LivebookWeb.SessionLive.RenameFileEntryComponent}
id="rename-file-entry"
session={@session}
file_entry={@action_assigns.renaming_file_entry}
/>
<.modal
:if={@live_action == :shortcuts}
id="shortcuts-modal"
show
width={:large}
patch={@self_path}
>
<.live_component
module={LivebookWeb.SessionLive.ShortcutsComponent}
id="shortcuts"
platform={@platform}
/>
<.modal
:if={@live_action == :cell_settings}
id="cell-settings-modal"
show
width={:medium}
patch={@self_path}
>
<.live_component
module={settings_component_for(@action_assigns.cell)}
id="cell-settings"
session={@session}
return_to={@self_path}
cell={@action_assigns.cell}
/>
<.modal
:if={@live_action == :insert_image}
id="insert-image-modal"
show
width={:medium}
patch={@self_path}
>
<.live_component
module={LivebookWeb.SessionLive.InsertImageComponent}
id="insert-image"
session={@session}
return_to={@self_path}
insert_image_metadata={@action_assigns.insert_image_metadata}
/>
<.modal
:if={@live_action == :insert_file}
id="insert-file-modal"
show
width={:medium}
patch={@self_path}
>
<.live_component
module={LivebookWeb.SessionLive.InsertFileComponent}
id="insert-file"
session={@session}
return_to={@self_path}
insert_file_metadata={@action_assigns.insert_file_metadata}
/>
<.modal :if={@live_action == :bin} id="bin-modal" show width={:big} patch={@self_path}>
<.live_component
module={LivebookWeb.SessionLive.BinComponent}
id="bin"
session={@session}
return_to={@self_path}
bin_entries={@data_view.bin_entries}
/>
<.modal :if={@live_action == :export} id="export-modal" show width={:big} patch={@self_path}>
<.live_component
module={LivebookWeb.SessionLive.ExportComponent}
id="export"
session={@session}
tab={@action_assigns.tab}
any_stale_cell?={@action_assigns.any_stale_cell?}
/>
<.modal
:if={@live_action == :package_search}
id="package-search-modal"
show
width={:medium}
patch={@self_path}
>
<%= live_render(@socket, LivebookWeb.SessionLive.PackageSearchLive,
id: "package-search",
session: %{
"session_pid" => @session.pid,
"runtime" => @data_view.runtime
}
) %>
<.modal
:if={@live_action == :secrets}
id="secrets-modal"
show
width={if(@action_assigns.select_secret_metadata, do: :large, else: :medium)}
patch={@self_path}
>
<.live_component
module={LivebookWeb.SessionLive.SecretsComponent}
id="secrets"
session={@session}
secrets={@data_view.secrets}
hub_secrets={@data_view.hub_secrets}
hub={@data_view.hub}
select_secret_metadata={@action_assigns.select_secret_metadata}
prefill_secret_name={@action_assigns.prefill_secret_name}
return_to={@self_path}
/>
<.modal
:if={@live_action == :custom_view_settings}
id="custom-view-modal"
show
width={:medium}
patch={@self_path}
>
<.live_component
module={LivebookWeb.SessionLive.CustomViewComponent}
id="custom"
return_to={@self_path}
session={@session}
/>
"""
end
defp settings_component_for(%Cell.Code{}),
do: LivebookWeb.SessionLive.CodeCellSettingsComponent
def sidebar(assigns) do
~H"""
<.link navigate={~p"/"} aria-label="go to homepage">
<%!-- Local functionality --%>
<.button_item
icon="node-tree"
label="Outline (so)"
button_attrs={["data-el-outline-toggle": true]}
/>
<.button_item
icon="group-fill"
label="Connected users (su)"
button_attrs={["data-el-clients-list-toggle": true]}
/>
<.button_item
icon="cpu-line"
label="Runtime settings (sr)"
button_attrs={["data-el-runtime-info-toggle": true]}
/>
<%!-- Hub functionality --%>
<.button_item
icon="lock-password-line"
label="Secrets (ss)"
button_attrs={["data-el-secrets-list-toggle": true]}
/>
<.button_item
icon="folder-open-fill"
label="Files (sf)"
button_attrs={["data-el-files-list-toggle": true]}
/>
<.button_item
icon="rocket-line"
label="App settings (sa)"
button_attrs={["data-el-app-info-toggle": true]}
/>
<.link_item
icon="delete-bin-6-fill"
label="Bin (sb)"
path={~p"/sessions/#{@session.id}/bin"}
active={@live_action == :bin}
link_attrs={["data-btn-show-bin": true]}
/>
<.link_item
icon="keyboard-box-fill"
label="Keyboard shortcuts (?)"
path={~p"/sessions/#{@session.id}/shortcuts"}
active={@live_action == :shortcuts}
link_attrs={["data-btn-show-shortcuts": true]}
/>
<.user_avatar
user={@current_user}
class="w-8 h-8 group-hover:ring-white group-hover:ring-2"
text_class="text-xs"
/>
"""
end
def side_panel(assigns) do
~H"""
<.outline_list data_view={@data_view} />
<.clients_list data_view={@data_view} client_id={@client_id} />
<.live_component
module={LivebookWeb.SessionLive.FilesListComponent}
id="files-list"
session={@session}
file_entries={@data_view.file_entries}
quarantine_file_entry_names={@data_view.quarantine_file_entry_names}
/>
<.live_component
module={LivebookWeb.SessionLive.SecretsListComponent}
id="secrets-list"
session={@session}
secrets={@data_view.secrets}
hub_secrets={@data_view.hub_secrets}
hub={@data_view.hub}
/>
<.live_component
module={LivebookWeb.SessionLive.AppInfoComponent}
id="app-info"
session={@session}
settings={@data_view.app_settings}
app={@app}
deployed_app_slug={@data_view.deployed_app_slug}
any_session_secrets?={@data_view.any_session_secrets?}
hub={@data_view.hub}
/>
<.runtime_info data_view={@data_view} session={@session} />
"""
end
defp button_item(assigns) do
~H"""
<.remix_icon icon={@icon} />
"""
end
defp link_item(assigns) do
assigns = assign_new(assigns, :link_attrs, fn -> [] end)
~H"""
<.link
patch={@path}
class={[
"text-gray-400 hover:text-gray-50 focus:text-gray-50 rounded-xl h-10 w-10 flex items-center justify-center",
@active && "text-gray-50 bg-gray-700"
]}
aria-label={@label}
{@link_attrs}
>
<.remix_icon icon={@icon} class="text-2xl" />
"""
end
defp outline_list(assigns) do
~H"""
Outline
<.remix_icon icon="h-2" class="text-lg font-normal leading-none" />
<%= section_item.name %>
<%!--
Note: the container has overflow-y auto, so we cannot set overflow-x visible,
consequently we show the tooltip wrapped to a fixed number of characters
--%>
<.remix_icon
icon="git-branch-line"
class="text-lg font-normal leading-none flip-horizontally"
/>
<.section_status
status={elem(section_item.status, 0)}
cell_id={elem(section_item.status, 1)}
/>
<.remix_icon icon="braces-line" class="font-normal" />
<%= definition.label %>
<.remix_icon icon="add-line" class="text-lg align-center" />
New section
<.remix_icon icon="split-cells-vertical" class="text-lg align-center" />
Expand/collapse all
"""
end
defp branching_tooltip_attrs(name, parent_name) do
direction = if String.length(name) >= 16, do: "left", else: "right"
wrapped_name = Livebook.Utils.wrap_line("”" <> parent_name <> "”", 16)
label = "Branches from\n#{wrapped_name}"
[class: "tooltip #{direction}", "data-tooltip": label]
end
defp clients_list(assigns) do
~H"""
Users
<%= length(@data_view.clients) %> connected
<.user_avatar user={user} class="shrink-0 h-7 w-7" text_class="text-xs" />
<%= user.name || "Anonymous" %>
<%= if(client_id == @client_id, do: "(you)") %>
<%= if client_id == @client_id do %>
<.icon_button aria-label="edit profile" phx-click={show_current_user_modal()}>
<.remix_icon icon="user-settings-line" />
<% else %>
<.icon_button aria-label="follow this user">
<.remix_icon icon="pushpin-line" />
<.icon_button aria-label="unfollow this user">
<.remix_icon icon="pushpin-fill" />
<% end %>
"""
end
defp runtime_info(assigns) do
~H"""
Runtime
<.icon_button>
<.remix_icon icon="question-line" />
<.labeled_text :for={{label, value} <- @data_view.runtime_metadata} label={label} one_line>
<%= value %>
<.button :if={@data_view.runtime_status == :disconnected} phx-click="connect_runtime">
<.remix_icon icon="wireless-charging-line" />
Connect
<.button :if={@data_view.runtime_status == :connecting} disabled>
<.remix_icon icon="wireless-charging-line" />
Connecting...
<.button :if={@data_view.runtime_status == :connected} phx-click="reconnect_runtime">
<.remix_icon icon="wireless-charging-line" />
Reconnect
<.button color="gray" outlined patch={~p"/sessions/#{@session.id}/settings/runtime"}>
Configure
<.button
:if={@data_view.runtime_status == :connected}
color="red"
outlined
type="button"
phx-click="disconnect_runtime"
class="col-span-2"
>
Disconnect
<.message_box kind={:info}>
<.spinner />
Step: <%= @data_view.runtime_connect_info %>
<.memory_usage_info
memory_usage={@session.memory_usage}
runtime_metadata={@data_view.runtime_metadata}
/>
<.runtime_connected_nodes_info runtime_connected_nodes={@data_view.runtime_connected_nodes} />
"""
end
defp memory_usage_info(assigns) do
~H"""
Memory
<.icon_button
:if={node = runtime_node(@runtime_metadata)}
href={LivebookWeb.HTMLHelpers.live_dashboard_node_path(node)}
target="_blank"
aria-label="see on dashboard"
>
<.remix_icon icon="dashboard-2-line" />
<%= if uses_memory?(@memory_usage) do %>
<.runtime_memory_info memory_usage={@memory_usage} />
<% else %>
<%= format_bytes(@memory_usage.system.free) %> available out of <%= format_bytes(
@memory_usage.system.total
) %>
<% end %>
"""
end
defp runtime_node(runtime_metadata) do
Enum.find_value(runtime_metadata, fn {key, value} -> key == "Node name" && value end)
end
defp runtime_memory_info(assigns) do
assigns = assign(assigns, :runtime_memory, runtime_memory(assigns.memory_usage))
~H"""
<%= type %>
<%= memory.unit %>
Total: <%= format_bytes(@memory_usage.runtime.total) %>
"""
end
defp memory_color(:atom), do: "bg-blue-500"
defp memory_color(:code), do: "bg-yellow-600"
defp memory_color(:processes), do: "bg-blue-700"
defp memory_color(:binary), do: "bg-green-500"
defp memory_color(:ets), do: "bg-red-500"
defp memory_color(:other), do: "bg-gray-400"
defp runtime_memory(%{runtime: memory}) do
memory
|> Map.drop([:total, :system])
|> Enum.map(fn {type, bytes} ->
{type,
%{
unit: format_bytes(bytes),
percentage: Float.round(bytes / memory.total * 100, 2),
value: bytes
}}
end)
end
defp runtime_connected_nodes_info(assigns) do
~H"""
Connected nodes
<%= if @runtime_connected_nodes == [] do %>
No connected nodes
<% else %>
<.remix_icon icon="circle-fill" class="mr-2 text-xs text-blue-500" />
<%= node %>
<.icon_button phx-click="runtime_disconnect_node" phx-value-node={node} small>
<.remix_icon icon="close-line" />
<% end %>
"""
end
defp section_status(%{status: :evaluating} = assigns) do
~H"""
<.status_indicator variant={:progressing} />
"""
end
defp section_status(%{status: :stale} = assigns) do
~H"""
<.status_indicator variant={:warning} />
"""
end
defp section_status(assigns), do: ~H""
def session_menu(assigns) do
~H"""
<.menu id="session-menu">
<:toggle>
<.icon_button aria-label="open notebook menu">
<.remix_icon icon="more-2-fill" />
<.menu_item>
<.link patch={~p"/sessions/#{@session.id}/export/livemd"} role="menuitem">
<.remix_icon icon="download-2-line" />
Export
<.menu_item>
<.remix_icon icon="eraser-fill" />
Erase outputs
<.menu_item>
<.remix_icon icon="git-branch-line" />
Fork
<.menu_item>
<.remix_icon icon="dashboard-2-line" />
See on Dashboard
<.menu_item variant={:danger}>
<.remix_icon icon="close-circle-line" />
Close
"""
end
defp app_teams_hub_info_content(assigns) do
~H"""
App deployment with Livebook Teams
<%= if @any_team_hub? do %>
<.message_box kind={:info}>
In order to deploy your app using Livebook Teams, you need to select a Livebook Teams
workspace. To change the workspace, use the dropdown right below the notebook title.
<.link
class="text-blue-600 font-medium"
patch={~p"/sessions/#{@session.id}"}
phx-click={show_menu(%JS{}, "notebook-hub-menu", animate: true)}
>
Change workspace
<.remix_icon icon="arrow-right-line" />
<% else %>
<.message_box kind={:info}>
In order to deploy your app using Livebook Teams, you need to create an organization.
<.link class="text-blue-600 font-medium" patch={~p"/hub"}>
Add organization
<.remix_icon icon="arrow-right-line" />
<% end %>
"""
end
def add_file_entry_content(assigns) do
~H"""
Add file
<.link
patch={~p"/sessions/#{@session.id}/add-file/storage"}
class={["tab", @tab == "storage" && "active"]}
>
<.remix_icon icon="file-3-line" class="align-middle" />
From storage
<.link
patch={~p"/sessions/#{@session.id}/add-file/url"}
class={["tab", @tab == "url" && "active"]}
>
<.remix_icon icon="download-cloud-2-line" class="align-middle" />
From URL
<.link
patch={~p"/sessions/#{@session.id}/add-file/upload"}
class={["tab", @tab == "upload" && "active"]}
>
<.remix_icon icon="file-upload-line" class="align-middle" />
From upload
<.link
patch={~p"/sessions/#{@session.id}/add-file/unlisted"}
class={["tab", @tab == "unlisted" && "active"]}
>
<.remix_icon icon="folder-shared-line" class="align-middle" />
From unlisted
<.live_component
:if={@tab == "storage"}
module={LivebookWeb.SessionLive.AddFileEntryFileComponent}
id="add-file-entry-from-file"
hub={@hub}
session={@session}
/>
<.live_component
:if={@tab == "url"}
module={LivebookWeb.SessionLive.AddFileEntryUrlComponent}
id="add-file-entry-from-url"
hub={@hub}
session={@session}
/>
<.live_component
:if={@tab == "upload"}
module={LivebookWeb.SessionLive.AddFileEntryUploadComponent}
id="add-file-entry-from-upload"
hub={@hub}
session={@session}
/>
<.live_component
:if={@tab == "unlisted"}
module={LivebookWeb.SessionLive.AddFileEntryUnlistedComponent}
id="add-file-entry-from-unlisted"
hub={@hub}
session={@session}
file_entries={@file_entries}
/>
"""
end
def indicators(assigns) do
~H"""
JS.toggle(to: "[data-el-toggle-sidebar]", display: "flex")
}
>
<.remix_icon icon="menu-fold-line" />
JS.toggle(to: "[data-el-toggle-sidebar]", display: "flex")
}
>
<.remix_icon icon="menu-unfold-line" />
<.view_indicator />
<.persistence_indicator
file={@file}
dirty={@dirty}
persistence_warnings={@persistence_warnings}
autosave_interval_s={@autosave_interval_s}
session_id={@session_id}
/>
<.runtime_indicator
runtime_status={@runtime_status}
global_status={@global_status}
session_id={@session_id}
/>
<.insert_mode_indicator />
"""
end
defp view_indicator(assigns) do
~H"""
<.menu id="views-menu" position={:bottom_right} sm_position={:top_right}>
<:toggle>
<.remix_icon icon="layout-5-line" />
<.remix_icon icon="layout-5-line" class="text-xl text-green-bright-400" />
<.menu_item>
<.remix_icon icon="code-line" />
Code zen
<.menu_item>
<.remix_icon icon="slideshow-2-line" />
Presentation
<.menu_item>
<.remix_icon icon="settings-5-line" />
Custom
"""
end
defp persistence_indicator(%{file: nil} = assigns) do
~H"""
<.link
patch={~p"/sessions/#{@session_id}/settings/file"}
class={status_button_classes(:gray)}
aria-label="choose a file to save the notebook"
>
<.remix_icon icon="save-line" />
"""
end
defp persistence_indicator(%{dirty: false} = assigns) do
~H"""
"Notebook saved"
warnings ->
"Notebook saved with warnings:\n" <> Enum.map_join(warnings, "\n", &("- " <> &1))
end
}
>
<.link
patch={~p"/sessions/#{@session_id}/settings/file"}
class={status_button_classes(:green)}
aria-label="notebook saved, click to open file settings"
>
<.remix_icon icon="save-line" />
<.remix_icon
:if={@persistence_warnings != []}
icon="error-warning-fill"
class="text-lg text-red-400 absolute -top-1.5 -right-2"
/>
"""
end
defp persistence_indicator(%{autosave_interval_s: nil} = assigns) do
~H"""
<.link
patch={~p"/sessions/#{@session_id}/settings/file"}
class={status_button_classes(:yellow)}
aria-label="no autosave configured, click to open file settings"
>
<.remix_icon icon="save-line" />
"""
end
defp persistence_indicator(assigns) do
~H"""
<.link
patch={~p"/sessions/#{@session_id}/settings/file"}
class={status_button_classes(:blue)}
aria-label="autosave pending, click to open file settings"
>
<.remix_icon icon="save-line" />
"""
end
defp runtime_indicator(assigns) do
~H"""
<%= if @runtime_status == :disconnected do %>
<.link
patch={~p"/sessions/#{@session_id}/settings/runtime"}
class={status_button_classes(:gray)}
aria-label="choose a runtime to run the notebook in"
>
<.remix_icon icon="loader-3-line" />
<% else %>
<.global_status status={elem(@global_status, 0)} cell_id={elem(@global_status, 1)} />
<% end %>
"""
end
defp global_status(%{status: :evaluating} = assigns) do
~H"""
<.remix_icon icon="loader-3-line" class="animate-spin" />
"""
end
defp global_status(%{status: :evaluated} = assigns) do
~H"""
<.remix_icon icon="loader-3-line" />
"""
end
defp global_status(%{status: :errored} = assigns) do
~H"""
<.remix_icon icon="loader-3-line" />
"""
end
defp global_status(%{status: :stale} = assigns) do
~H"""
<.remix_icon icon="loader-3-line" />
"""
end
defp global_status(%{status: :fresh} = assigns) do
~H"""
<.remix_icon icon="loader-3-line" />
"""
end
defp status_button_classes(color) do
[
"text-xl leading-none p-1 flex items-center justify-center rounded-full rounded-full border-2 focus-visible:outline-none",
case color do
:gray ->
"text-gray-400 border-gray-200 hover:bg-gray-100 focus-visible:bg-gray-100"
:blue ->
"text-blue-500 border-blue-400 hover:bg-blue-50 focus-visible:bg-blue-50"
:green ->
"text-green-bright-400 border-green-bright-300 hover:bg-green-bright-50 focus-visible:bg-green-bright-50"
:yellow ->
"text-yellow-bright-300 border-yellow-bright-200 hover:bg-yellow-bright-50 focus-visible:bg-yellow-bright-50"
:red ->
"text-red-400 border-red-300 hover:bg-red-50 focus-visible:bg-red-50"
end
]
end
defp insert_mode_indicator(assigns) do
~H"""
<%!-- Note: this indicator is shown/hidden using CSS based on the current mode --%>
ins
"""
end
def notebook_content(assigns) do
~H"""
<%= @data_view.notebook_name %>
<.session_menu session={@session} />
<.menu position={:bottom_left} id="notebook-hub-menu">
<:toggle>
Using
<%= @data_view.hub.hub_emoji %>
<%= @data_view.hub.hub_name %>
<.remix_icon icon="arrow-down-s-line" class="-ml-1" />
workspace
<.menu_item :for={hub <- @saved_hubs}>
<%= hub.emoji %>
<%= hub.name %>
<.menu_item>
<.link navigate={~p"/hub"} aria-label="Add Organization" role="menuitem">
<.remix_icon icon="add-line" class="align-middle mr-1" /> Add Organization
<.star_button file={@data_view.file} starred_files={@starred_files} />
<.live_component
module={LivebookWeb.SessionLive.CellComponent}
id={@data_view.setup_cell_view.id}
session_id={@session.id}
session_pid={@session.pid}
client_id={@client_id}
runtime={@data_view.runtime}
installing?={@data_view.installing?}
allowed_uri_schemes={@allowed_uri_schemes}
cell_view={@data_view.setup_cell_view}
/>
+ Section
<.live_component
:for={{section_view, index} <- Enum.with_index(@data_view.section_views)}
module={LivebookWeb.SessionLive.SectionComponent}
id={section_view.id}
index={index}
session_id={@session.id}
session_pid={@session.pid}
client_id={@client_id}
runtime_status={@data_view.runtime_status}
smart_cell_definitions={@data_view.smart_cell_definitions}
example_snippet_definitions={@data_view.example_snippet_definitions}
installing?={@data_view.installing?}
allowed_uri_schemes={@allowed_uri_schemes}
section_view={section_view}
default_language={@data_view.default_language}
/>
"""
end
defp star_button(%{file: nil} = assigns) do
~H"""
<.icon_button disabled>
<.remix_icon icon="star-line" />
"""
end
defp star_button(assigns) do
~H"""
<%= if starred?(@file, @starred_files) do %>
<.icon_button phx-click="unstar_notebook">
<.remix_icon icon="star-fill" class="text-yellow-600" />
<% else %>
<.icon_button phx-click="star_notebook">
<.remix_icon icon="star-line" />
<% end %>
"""
end
defp starred?(file, starred_files) do
Enum.any?(starred_files, &Livebook.FileSystem.File.equal?(&1, file))
end
end