defmodule LivebookWeb.AppLive do use LivebookWeb, :live_view alias Livebook.Session alias Livebook.Notebook alias Livebook.Notebook.Cell @impl true def mount(%{"slug" => slug}, _session, socket) when socket.assigns.app_authenticated? do {:ok, %{pid: session_pid, id: session_id}} = Livebook.Apps.fetch_session_by_slug(slug) {data, client_id} = if connected?(socket) do {data, client_id} = Session.register_client(session_pid, self(), socket.assigns.current_user) Session.subscribe(session_id) {data, client_id} else data = Session.get_data(session_pid) {data, nil} end session = Session.get_by_pid(session_pid) {:ok, socket |> assign( slug: slug, session: session, page_title: get_page_title(data.notebook.name), client_id: client_id, data_view: data_to_view(data) ) |> assign_private(data: data)} end def mount(%{"slug" => slug}, _session, socket) do if connected?(socket) do {:ok, push_navigate(socket, to: ~p"/apps/#{slug}/authenticate")} else {:ok, socket} end end # Puts the given assigns in `socket.private`, # to ensure they are not used for rendering. defp assign_private(socket, assigns) do Enum.reduce(assigns, socket, fn {key, value}, socket -> put_in(socket.private[key], value) end) end @impl true def render(assigns) when assigns.app_authenticated? do ~H"""

<%= @data_view.notebook_name %>

Booting
Error
""" end def render(assigns) do ~H"""
livebook
""" end defp get_page_title(notebook_name) do "Livebook - #{notebook_name}" end @impl true def handle_info({:operation, operation}, socket) do {:noreply, handle_operation(socket, operation)} end def handle_info({:set_input_values, values, _local = true}, socket) do socket = Enum.reduce(values, socket, fn {input_id, value}, socket -> operation = {:set_input_value, socket.assigns.client_id, input_id, value} handle_operation(socket, operation) end) {:noreply, socket} end def handle_info(:session_closed, socket) do {:noreply, socket |> put_flash(:info, "Session has been closed") |> push_navigate(to: ~p"/")} end def handle_info(_message, socket), do: {:noreply, socket} defp handle_operation(socket, operation) do case Session.Data.apply_operation(socket.private.data, operation) do {:ok, data, _actions} -> socket |> assign_private(data: data) |> assign( data_view: update_data_view(socket.assigns.data_view, socket.private.data, data, operation) ) :error -> socket end end defp update_data_view(data_view, _prev_data, data, operation) do case operation do # See LivebookWeb.SessionLive for more details {:add_cell_evaluation_output, _client_id, _cell_id, {:frame, _outputs, %{type: type, ref: ref}}} when type != :default -> for {idx, {:frame, frame_outputs, _}} <- Notebook.find_frame_outputs(data.notebook, ref) do send_update(LivebookWeb.Output.FrameComponent, id: "output-#{idx}", outputs: frame_outputs, update_type: type ) end data_view _ -> data_to_view(data) end end defp data_to_view(data) do %{ notebook_name: data.notebook.name, output_views: for( output <- visible_outputs(data.notebook), do: %{ output: output, input_values: input_values_for_output(output, data) } ), app_status: data.app_data.status } end defp input_values_for_output(output, data) do input_ids = for attrs <- Cell.find_inputs_in_output(output), do: attrs.id Map.take(data.input_values, input_ids) end defp visible_outputs(notebook) do for section <- Enum.reverse(notebook.sections), cell <- Enum.reverse(section.cells), Cell.evaluable?(cell), output <- filter_outputs(cell.outputs), do: output end defp filter_outputs(outputs) do for output <- outputs, output = filter_output(output), do: output end defp filter_output({idx, output}) when elem(output, 0) in [:markdown, :image, :js, :control], do: {idx, output} defp filter_output({idx, {:tabs, outputs, metadata}}) do outputs_with_labels = for {output, label} <- Enum.zip(outputs, metadata.labels), output = filter_output(output), do: {output, label} {outputs, labels} = Enum.unzip(outputs_with_labels) {idx, {:tabs, outputs, %{metadata | labels: labels}}} end defp filter_output({idx, {:grid, outputs, metadata}}) do outputs = filter_outputs(outputs) if outputs != [] do {idx, {:grid, outputs, metadata}} end end defp filter_output({idx, {:frame, outputs, metadata}}) do outputs = filter_outputs(outputs) {idx, {:frame, outputs, metadata}} end defp filter_output(_output), do: nil end