defmodule LivebookWeb.HomeLive do use LivebookWeb, :live_view alias Livebook.{SessionSupervisor, Session, LiveMarkdown} @impl true def mount(_params, _session, socket) do if connected?(socket) do Phoenix.PubSub.subscribe(Livebook.PubSub, "sessions") end session_summaries = sort_session_summaries(SessionSupervisor.get_session_summaries()) {:ok, assign(socket, path: default_path(), session_summaries: session_summaries)} end @impl true def render(assigns) do ~L"""
Livebook
<%= live_component @socket, LivebookWeb.PathSelectComponent, id: "path_select", path: @path, extnames: [LiveMarkdown.extension()], running_paths: paths(@session_summaries), phx_target: nil, phx_submit: nil do %>
<%= content_tag :button, class: "button button-outlined-gray whitespace-nowrap", phx_click: "fork", disabled: not path_forkable?(@path) do %> <%= remix_icon("git-branch-line", class: "align-middle mr-1") %> Fork <% end %> <%= if path_running?(@path, @session_summaries) do %> <%= live_patch "Join session", to: Routes.session_path(@socket, :page, session_id_by_path(@path, @session_summaries)), class: "button button-blue" %> <% else %> <%= tag :span, if(File.regular?(@path) and not file_writable?(@path), do: [class: "tooltip top", aria_label: "This file is write-protected, please fork instead"], else: [] ) %> <%= content_tag :button, "Open", class: "button button-blue", phx_click: "open", disabled: not path_openable?(@path, @session_summaries) %> <% end %>
<% end %>

Running sessions

<%= if @session_summaries == [] do %>
<%= remix_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”
<% else %> <%= live_component @socket, LivebookWeb.SessionLive.SessionsComponent, id: "sessions_list", session_summaries: @session_summaries %> <% end %>
<%= if @live_action == :close_session do %> <%= live_modal @socket, LivebookWeb.SessionLive.CloseSessionComponent, id: :close_session_modal, return_to: Routes.home_path(@socket, :page), session_summary: @session_summary %> <% 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(_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("open_welcome", %{}, socket) do create_session(socket, notebook: Livebook.Notebook.Welcome.new()) end def handle_event("new", %{}, socket) do create_session(socket) end def handle_event("fork", %{}, socket) do {notebook, messages} = import_notebook(socket.assigns.path) socket = put_import_flash_messages(socket, messages) notebook = %{notebook | name: notebook.name <> " - fork"} images_dir = Session.images_dir_for_notebook(socket.assigns.path) create_session(socket, notebook: notebook, copy_images_from: images_dir) end def handle_event("open", %{}, socket) do {notebook, messages} = import_notebook(socket.assigns.path) socket = put_import_flash_messages(socket, messages) create_session(socket, notebook: notebook, path: socket.assigns.path) end def handle_event("fork_session", %{"id" => session_id}, socket) do data = Session.get_data(session_id) notebook = %{data.notebook | name: data.notebook.name <> " - fork"} %{images_dir: images_dir} = Session.get_summary(session_id) create_session(socket, notebook: notebook, copy_images_from: images_dir) 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(_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 create_session(socket, opts \\ []) do case SessionSupervisor.create_session(opts) do {:ok, id} -> {:noreply, push_redirect(socket, to: Routes.session_path(socket, :page, id))} {:error, reason} -> {:noreply, put_flash(socket, :error, "Failed to create a notebook: #{reason}")} end end defp import_notebook(path) do content = File.read!(path) LiveMarkdown.Import.notebook_from_markdown(content) end defp put_import_flash_messages(socket, []), do: socket defp put_import_flash_messages(socket, messages) do list = messages |> Enum.map(fn message -> ["- ", message] end) |> Enum.intersperse("\n") flash = IO.iodata_to_binary([ "We found problems while importing the file and tried to autofix them:\n" | list ]) put_flash(socket, :info, flash) end defp session_id_by_path(path, session_summaries) do summary = Enum.find(session_summaries, &(&1.path == path)) summary.session_id end end