Packages
livebook
0.11.1
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/persistence_component.ex
defmodule LivebookWeb.SessionLive.PersistenceComponent do
use LivebookWeb, :live_component
alias Livebook.{Sessions, Session, LiveMarkdown, FileSystem}
@impl true
def mount(socket) do
sessions = Sessions.list_sessions()
running_files = Enum.map(sessions, & &1.file)
{:ok, assign(socket, running_files: running_files)}
end
@impl true
def update(%{event: {:set_file, file, _info}}, socket) do
current_file = socket.assigns.draft_file
autosave_interval_s =
cond do
FileSystem.File.same_file_system?(file, current_file) ->
socket.assigns.new_attrs.autosave_interval_s
FileSystem.File.local?(file) ->
Livebook.Notebook.default_autosave_interval_s()
true ->
nil
end
{:ok,
socket
|> assign(draft_file: file)
|> put_new_attr(:autosave_interval_s, autosave_interval_s)}
end
def update(%{event: :confirm_file}, socket) do
{:ok, save(socket)}
end
def update(assigns, socket) do
{file, assigns} = Map.pop!(assigns, :file)
{persist_outputs, assigns} = Map.pop!(assigns, :persist_outputs)
{autosave_interval_s, assigns} = Map.pop!(assigns, :autosave_interval_s)
attrs = %{
persist_outputs: persist_outputs,
autosave_interval_s: autosave_interval_s
}
socket =
socket
|> assign(assigns)
|> assign_new(:attrs, fn -> attrs end)
|> assign_new(:new_attrs, fn -> attrs end)
|> assign_new(:draft_file, fn ->
file ||
case assigns.session.origin do
# If it's a forked notebook, default to the same folder
{:file, file} -> FileSystem.File.containing_dir(file)
nil -> Livebook.Settings.default_dir(assigns.hub)
end
end)
|> assign_new(:saved_file, fn -> file end)
{:ok, socket}
end
@impl true
def render(assigns) do
~H"""
<div class="p-6 flex flex-col space-y-8">
<h3 class="text-2xl font-semibold text-gray-800">
Save to file
</h3>
<div class="w-full flex-col space-y-6">
<div class="h-80">
<.live_component
module={LivebookWeb.FileSelectComponent}
id="persistence_file_select"
file={@draft_file}
hub={@hub}
extnames={[LiveMarkdown.extension()]}
running_files={@running_files}
submit_event={:confirm_file}
target={{__MODULE__, @id}}
/>
</div>
<div>
<.label>File</.label>
<div class="whitespace-nowrap text-gray-700 leading-4">
<%= normalize_file(@draft_file).path %>
</div>
</div>
<form
phx-change="set_options"
phx-target={@myself}
phx-nosubmit
class="flex flex-col items-start max-w-full"
>
<div class="flex flex-col space-y-6">
<.select_field
name="autosave_interval_s"
label="Autosave"
value={@new_attrs.autosave_interval_s || ""}
options={[
{"every 5 seconds", "5"},
{"every 30 seconds", "30"},
{"every minute", "60"},
{"every 10 minutes", "600"},
{"never", ""}
]}
/>
<.checkbox_field
name="persist_outputs"
label="Persist outputs"
value={@new_attrs.persist_outputs}
/>
</div>
</form>
</div>
<div class="flex justify-between">
<div class="flex space-x-3">
<button
class="button-base button-blue"
phx-click="save"
phx-target={@myself}
disabled={not savable?(@draft_file, @saved_file, @running_files)}
>
Save
</button>
<.link patch={~p"/sessions/#{@session.id}"} class="button-base button-outlined-gray">
Cancel
</.link>
</div>
<button
:if={@saved_file}
class="button-base button-outlined-red"
phx-click="stop_saving"
phx-target={@myself}
>
Stop saving to file
</button>
</div>
</div>
"""
end
@impl true
def handle_event(
"set_options",
%{"persist_outputs" => persist_outputs, "autosave_interval_s" => autosave_interval_s},
socket
) do
persist_outputs = persist_outputs == "true"
autosave_interval_s = parse_optional_integer(autosave_interval_s)
{:noreply,
socket
|> put_new_attr(:persist_outputs, persist_outputs)
|> put_new_attr(:autosave_interval_s, autosave_interval_s)}
end
def handle_event("save", %{}, socket) do
{:noreply, save(socket)}
end
def handle_event("stop_saving", %{}, socket) do
Session.set_file(socket.assigns.session.pid, nil)
{:noreply, push_patch(socket, to: ~p"/sessions/#{socket.assigns.session.id}")}
end
defp save(%{assigns: assigns} = socket) do
%{new_attrs: new_attrs, attrs: attrs, draft_file: draft_file, saved_file: saved_file} =
assigns
draft_file = normalize_file(draft_file)
if draft_file != saved_file do
Session.set_file(assigns.session.pid, draft_file)
end
diff = map_diff(new_attrs, attrs)
if diff != %{} do
Session.set_notebook_attributes(assigns.session.pid, diff)
end
Session.save_sync(assigns.session.pid)
# We can't do push_patch from update/2, so we ask the LV to do so
send(self(), {:push_patch, ~p"/sessions/#{assigns.session.id}"})
socket
end
defp parse_optional_integer(string) do
case Integer.parse(string) do
{number, _} -> number
:error -> nil
end
end
defp put_new_attr(socket, key, value) do
new_attrs = socket.assigns.new_attrs
if new_attrs[key] == value do
socket
else
new_attrs = put_in(new_attrs[key], value)
assign(socket, :new_attrs, new_attrs)
end
end
defp normalize_file(file) do
FileSystem.File.ensure_extension(file, LiveMarkdown.extension())
end
defp savable?(draft_file, saved_file, running_files) do
file = normalize_file(draft_file)
not FileSystem.File.dir?(draft_file) and (file not in running_files or file == saved_file)
end
defp map_diff(left, right) do
Map.new(Map.to_list(left) -- Map.to_list(right))
end
end