defmodule LivebookWeb.HomeLive do use LivebookWeb, :live_view import LivebookWeb.UserHelpers import LivebookWeb.SessionHelpers alias LivebookWeb.{SidebarHelpers, ExploreHelpers} alias Livebook.{SessionSupervisor, Session, LiveMarkdown, Notebook} @impl true def mount(_params, %{"current_user_id" => current_user_id} = session, socket) do if connected?(socket) do Phoenix.PubSub.subscribe(Livebook.PubSub, "sessions") Phoenix.PubSub.subscribe(Livebook.PubSub, "users:#{current_user_id}") end current_user = build_current_user(session, socket) session_summaries = sort_session_summaries(SessionSupervisor.get_session_summaries()) notebook_infos = Notebook.Explore.notebook_infos() |> Enum.take(3) {:ok, assign(socket, current_user: current_user, path: default_path(), session_summaries: session_summaries, notebook_infos: notebook_infos )} end @impl true def render(assigns) do ~H"""
Livebook
<%= live_patch "Import", to: Routes.home_path(@socket, :import, "url"), class: "button button-outlined-gray whitespace-nowrap" %>
<%= live_component LivebookWeb.PathSelectComponent, id: "path_select", path: @path, extnames: [LiveMarkdown.extension()], running_paths: paths(@session_summaries), phx_target: nil, phx_submit: nil do %>
<%= if path_running?(@path, @session_summaries) do %> <%= live_redirect "Join session", to: Routes.session_path(@socket, :page, session_id_by_path(@path, @session_summaries)), class: "button button-blue" %> <% else %> <% end %>
<% end %>

Explore

<%= live_redirect to: Routes.explore_path(@socket, :page), class: "flex items-center text-blue-600" do %> See all <.remix_icon icon="arrow-right-line" class="align-middle ml-1" /> <% end %>
<%# Note: it's fine to use stateless components in this comprehension, because @notebook_infos never change %> <%= for info <- @notebook_infos do %> <% end %>

Running sessions

<.sessions_list session_summaries={@session_summaries} socket={@socket} />
<%= if @live_action == :user do %> <%= live_modal LivebookWeb.UserComponent, id: "user", modal_class: "w-full max-w-sm", user: @current_user, return_to: Routes.home_path(@socket, :page) %> <% end %> <%= if @live_action == :close_session do %> <%= live_modal LivebookWeb.HomeLive.CloseSessionComponent, id: "close-session", modal_class: "w-full max-w-xl", return_to: Routes.home_path(@socket, :page), session_summary: @session_summary %> <% end %> <%= if @live_action == :import do %> <%= live_modal LivebookWeb.HomeLive.ImportComponent, id: "import", modal_class: "w-full max-w-xl", return_to: Routes.home_path(@socket, :page), tab: @tab %> <% end %> """ end defp open_button_tooltip_attrs(path) do if File.regular?(path) and not file_writable?(path) do [class: "tooltip top", aria_label: "This file is write-protected, please fork instead"] else [] end end defp sessions_list(%{session_summaries: []} = assigns) do ~H"""
<.remix_icon icon="windy-line" class="text-gray-400 text-xl" />
You do not have any running sessions.
Please create a new one by clicking “New notebook”
""" end defp sessions_list(assigns) do ~H"""
<%= for summary <- @session_summaries do %>
<%= live_redirect summary.notebook_name, to: Routes.session_path(@socket, :page, summary.session_id), class: "font-semibold text-gray-800 hover:text-gray-900" %>
<%= summary.path || "No file" %>
<% end %>
""" end @impl true def handle_params(%{"session_id" => session_id}, _url, socket) do session_summary = Enum.find(socket.assigns.session_summaries, &(&1.session_id == session_id)) {:noreply, assign(socket, session_summary: session_summary)} end def handle_params(%{"tab" => tab}, _url, socket) do {:noreply, assign(socket, tab: tab)} end def handle_params(_params, _url, socket), do: {:noreply, socket} @impl true def handle_event("set_path", %{"path" => path}, socket) do {:noreply, assign(socket, path: path)} end def handle_event("new", %{}, socket) do {:noreply, create_session(socket)} end def handle_event("fork", %{}, socket) do path = socket.assigns.path {notebook, messages} = import_notebook(path) socket = put_import_flash_messages(socket, messages) notebook = Notebook.forked(notebook) images_dir = Session.images_dir_for_notebook(path) {:noreply, create_session(socket, notebook: notebook, copy_images_from: images_dir, origin_url: "file://" <> path )} end def handle_event("open", %{}, socket) do path = socket.assigns.path {notebook, messages} = import_notebook(path) socket = put_import_flash_messages(socket, messages) {:noreply, create_session(socket, notebook: notebook, path: path, origin_url: "file://" <> path)} end def handle_event("fork_session", %{"id" => session_id}, socket) do data = Session.get_data(session_id) notebook = Notebook.forked(data.notebook) %{images_dir: images_dir} = Session.get_summary(session_id) origin_url = if data.path do "file://" <> data.path else data.origin_url end {:noreply, create_session(socket, notebook: notebook, copy_images_from: images_dir, origin_url: origin_url )} end @impl true def handle_info({:session_created, id}, socket) do summary = Session.get_summary(id) session_summaries = sort_session_summaries([summary | socket.assigns.session_summaries]) {:noreply, assign(socket, session_summaries: session_summaries)} end def handle_info({:session_closed, id}, socket) do session_summaries = Enum.reject(socket.assigns.session_summaries, &(&1.session_id == id)) {:noreply, assign(socket, session_summaries: session_summaries)} end def handle_info({:import_content, content, session_opts}, socket) do {notebook, messages} = Livebook.LiveMarkdown.Import.notebook_from_markdown(content) socket = put_import_flash_messages(socket, messages) session_opts = Keyword.merge(session_opts, notebook: notebook) {:noreply, create_session(socket, session_opts)} end def handle_info( {:user_change, %{id: id} = user}, %{assigns: %{current_user: %{id: id}}} = socket ) do {:noreply, assign(socket, :current_user, user)} end def handle_info(_message, socket), do: {:noreply, socket} defp default_path(), do: Livebook.Config.root_path() <> "/" defp sort_session_summaries(session_summaries) do Enum.sort_by(session_summaries, & &1.notebook_name) end defp paths(session_summaries) do Enum.map(session_summaries, & &1.path) end defp path_forkable?(path) do File.regular?(path) end defp path_openable?(path, session_summaries) do File.regular?(path) and not path_running?(path, session_summaries) and file_writable?(path) end defp path_running?(path, session_summaries) do running_paths = paths(session_summaries) path in running_paths end defp file_writable?(path) do case File.stat(path) do {:ok, stat} -> stat.access in [:read_write, :write] {:error, _} -> false end end defp import_notebook(path) do content = File.read!(path) LiveMarkdown.Import.notebook_from_markdown(content) end defp session_id_by_path(path, session_summaries) do summary = Enum.find(session_summaries, &(&1.path == path)) summary.session_id end end