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 alias Livebook.Runtime def render(assigns) do ~H"""
<.sidebar app={@app} session={@session} live_action={@live_action} current_user={@current_user} /> <.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={@data_view.runtime} 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} runtime={@data_view.runtime} /> <.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} 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} /> <.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 == :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={@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={@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(@cell)} id="cell-settings" session={@session} return_to={@self_path} cell={@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={@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={@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={@tab} any_stale_cell?={@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(@select_secret_ref, 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} prefill_secret_name={@prefill_secret_name} select_secret_ref={@select_secret_ref} select_secret_options={@select_secret_options} 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""" """ end def side_panel(assigns) do ~H"""
<.sections_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?} />
<.runtime_info data_view={@data_view} session={@session} />
""" end defp button_item(assigns) do ~H""" """ 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 sections_list(assigns) do ~H"""

Sections

<.section_status status={elem(section_item.status, 0)} cell_id={elem(section_item.status, 1)} />
""" 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
<%= 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} <- Runtime.describe(@data_view.runtime)} label={label} one_line > <%= value %>
<%= if Runtime.connected?(@data_view.runtime) do %> <.button phx-click="reconnect_runtime"> <.remix_icon icon="wireless-charging-line" /> Reconnect <% else %> <.button phx-click="connect_runtime"> <.remix_icon icon="wireless-charging-line" /> Connect <% end %> <.button color="gray" outlined patch={~p"/sessions/#{@session.id}/settings/runtime"}> Configure
<%= if uses_memory?(@session.memory_usage) do %> <.memory_info memory_usage={@session.memory_usage} /> <% else %>
Memory

<%= format_bytes(@session.memory_usage.system.free) %> available out of <%= format_bytes( @session.memory_usage.system.total ) %>

<% end %> <.button :if={Runtime.connected?(@data_view.runtime)} color="red" outlined type="button" phx-click="disconnect_runtime" > Disconnect
""" end defp memory_info(assigns) do assigns = assign(assigns, :runtime_memory, runtime_memory(assigns.memory_usage)) ~H"""
Memory <%= format_bytes(@memory_usage.system.free) %> available
<%= 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 section_status(%{status: :evaluating} = assigns) do ~H""" """ end defp section_status(%{status: :stale} = assigns) do ~H""" """ 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> <.menu_item> <.menu_item> <.remix_icon icon="dashboard-2-line" /> See on Dashboard <.menu_item variant={:danger}> """ 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"""
<.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={@runtime} 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> <.menu_item> <.menu_item> <.menu_item>
""" 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 Livebook.Runtime.connected?(@runtime) do %> <.global_status status={elem(@global_status, 0)} cell_id={elem(@global_status, 1)} /> <% else %> <.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" /> <% end %> """ end defp global_status(%{status: :evaluating} = assigns) do ~H""" """ end defp global_status(%{status: :evaluated} = assigns) do ~H""" """ end defp global_status(%{status: :errored} = assigns) do ~H""" """ end defp global_status(%{status: :stale} = assigns) do ~H""" """ 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", case color do :gray -> "text-gray-400 border-gray-200 hover:bg-gray-100 focus:bg-gray-100" :blue -> "text-blue-500 border-blue-400 hover:bg-blue-50 focus:bg-blue-50" :green -> "text-green-bright-400 border-green-bright-300 hover:bg-green-bright-50 focus:bg-green-bright-50" :yellow -> "text-yellow-bright-300 border-yellow-bright-200 hover:bg-yellow-bright-50 focus:bg-yellow-bright-50" :red -> "text-red-400 border-red-300 hover:bg-red-50 focus: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>
in <%= @data_view.hub.hub_emoji %> <%= @data_view.hub.hub_name %> <.remix_icon icon="arrow-down-s-line" />
<.menu_item :for={hub <- @saved_hubs}> <.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={@data_view.runtime} 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 @file in @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 end