Packages
livebook
0.6.3
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook_web/live/session_live/mix_standalone_live.ex
defmodule LivebookWeb.SessionLive.MixStandaloneLive do
use LivebookWeb, :live_view
alias Livebook.{Session, Runtime, Utils, FileSystem}
@type status :: :initial | :initializing | :finished
@impl true
def mount(_params, %{"session" => session, "current_runtime" => current_runtime}, socket) do
unless Livebook.Config.runtime_enabled?(Livebook.Runtime.MixStandalone) do
raise "runtime module not allowed"
end
if connected?(socket) do
Session.subscribe(session.id)
end
{:ok,
assign(socket,
session: session,
status: :initial,
current_runtime: current_runtime,
data: initial_data(current_runtime),
outputs: [],
outputs_version: 0,
emitter: nil
), temporary_assigns: [outputs: []]}
end
@impl true
def render(assigns) do
~H"""
<div class="flex-col space-y-5">
<p class="text-gray-700">
Start a new local node in the context of a Mix project.
This way all your code and dependencies will be available
within the notebook.
</p>
<p class="text-gray-700">
<span class="font-semibold">Warning:</span>
Notebooks that use <code>Mix.install/1</code> do not work
inside a Mix project because the dependencies of the project
itself have been installed instead.
</p>
<%= if @status != :initializing do %>
<div class="h-full h-52">
<.live_component module={LivebookWeb.FileSelectComponent}
id="mix-project-dir"
file={@data.file}
extnames={[]}
running_files={[]}
submit_event={if(data_valid?(@data), do: :init, else: nil)}
file_system_select_disabled={true} />
</div>
<form phx-change="validate" phx-submit="init">
<div>
<div class="input-label"><code>mix run</code> command-line flags</div>
<input class="input"
type="text"
name="flags"
value={@data.flags}
spellcheck="false"
autocomplete="off" />
</div>
<button class="mt-5 button-base button-blue"
type="submit"
disabled={not data_valid?(@data)}>
<%= if(matching_runtime?(@current_runtime, @data), do: "Reconnect", else: "Connect") %>
</button>
</form>
<% end %>
<%= if @status != :initial do %>
<div class="markdown">
<pre><code class="max-h-40 overflow-y-auto tiny-scrollbar"
id={"mix-standalone-init-output-#{@outputs_version}"}
phx-update="append"
phx-hook="ScrollOnUpdate"
><%= for {output, i} <- @outputs do %><span id={"mix-standalone-init-output-#{@outputs_version}-#{i}"}><%= ansi_string_to_html(output) %></span><% end %></code></pre>
</div>
<% end %>
</div>
"""
end
@impl true
def handle_event("init", _params, socket) do
handle_init(socket)
end
def handle_event("validate", %{"flags" => flags}, socket) do
{:noreply, update(socket, :data, &%{&1 | flags: flags})}
end
@impl true
def handle_info({:set_file, file, _info}, socket) do
{:noreply, update(socket, :data, &%{&1 | file: file})}
end
def handle_info(:init, socket) do
handle_init(socket)
end
def handle_info({:emitter, ref, message}, %{assigns: %{emitter: %{ref: ref}}} = socket) do
case message do
{:output, output} ->
{:noreply, add_output(socket, output)}
{:ok, runtime} ->
Session.set_runtime(socket.assigns.session.pid, runtime)
{:noreply, socket |> assign(status: :finished) |> add_output("Connected successfully")}
{:error, error} ->
{:noreply, socket |> assign(status: :finished) |> add_output("Error: #{error}")}
end
end
def handle_info({:operation, {:set_runtime, _pid, runtime}}, socket) do
{:noreply, assign(socket, current_runtime: runtime)}
end
def handle_info(_, socket), do: {:noreply, socket}
defp handle_init(socket) do
emitter = Utils.Emitter.new(self())
runtime = Runtime.MixStandalone.new(socket.assigns.data.file.path, socket.assigns.data.flags)
Runtime.MixStandalone.connect_async(runtime, emitter)
{:noreply,
socket
|> assign(status: :initializing, emitter: emitter, outputs: [])
|> update(:outputs_version, &(&1 + 1))}
end
defp add_output(socket, output) do
assign(socket, outputs: socket.assigns.outputs ++ [{output, Utils.random_id()}])
end
defp initial_data(%Runtime.MixStandalone{project_path: project_path, flags: flags}) do
file =
project_path
|> FileSystem.Utils.ensure_dir_path()
|> FileSystem.File.local()
%{file: file, flags: flags}
end
defp initial_data(_runtime) do
%{file: Livebook.Config.local_filesystem_home(), flags: ""}
end
defp matching_runtime?(%Runtime.MixStandalone{} = runtime, data) do
Path.expand(runtime.project_path) == Path.expand(data.file.path)
end
defp matching_runtime?(_runtime, _path), do: false
defp data_valid?(data) do
mix_project_root?(data.file.path) and Livebook.Utils.valid_cli_flags?(data.flags)
end
defp mix_project_root?(path) do
File.dir?(path) and File.exists?(Path.join(path, "mix.exs"))
end
end