Packages
livebook
0.14.0-rc.0
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/insert_image_component.ex
defmodule LivebookWeb.SessionLive.InsertImageComponent do
use LivebookWeb, :live_component
import Ecto.Changeset
import LivebookWeb.HTMLHelpers
alias Livebook.FileSystem
@impl true
def mount(socket) do
{:ok,
socket
|> assign(changeset: changeset())
|> allow_upload(:image,
accept: ~w(.jpg .jpeg .png .gif .svg),
max_entries: 1,
max_file_size: 5_000_000,
writer: fn _name, _entry, socket ->
file = file_entry_file(socket)
{LivebookWeb.FileSystemWriter, [file: file]}
end
)}
end
defp changeset(attrs \\ %{}) do
data = %{name: nil}
types = %{name: :string}
cast({data, types}, attrs, [:name])
|> validate_required([:name])
|> Livebook.Notebook.validate_file_entry_name(:name)
end
@impl true
def render(assigns) do
~H"""
<div class="flex flex-col space-y-8">
<h3 class="text-2xl font-semibold text-gray-800">
Insert image
</h3>
<div :if={upload_error_messages(@uploads.image) != []} class="flex flex-col gap-2">
<div :for={message <- upload_error_messages(@uploads.image)} class="error-box">
<%= message %>
</div>
</div>
<div :for={entry <- @uploads.image.entries}>
<.live_img_preview entry={entry} class="max-h-80 m-auto" />
</div>
<.form
:let={f}
for={@changeset}
as={:data}
phx-change="validate"
phx-submit="save"
phx-target={@myself}
>
<div class="flex flex-col space-y-4">
<.file_drop_input
upload={@uploads.image}
label="File"
on_clear={JS.push("clear_file", target: @myself)}
/>
<.text_field
field={f[:name]}
label="Name"
id="insert-image-form-name"
autocomplete="off"
phx-debounce="200"
/>
</div>
<div class="mt-8 flex justify-end space-x-2">
<.button color="gray" outlined patch={@return_to}>
Cancel
</.button>
<.button type="submit" disabled={not @changeset.valid? or upload_disabled?(@uploads.image)}>
Upload
</.button>
</div>
</.form>
</div>
"""
end
@impl true
def handle_event("validate", %{"data" => data} = params, socket) do
upload_entries = socket.assigns.uploads.image.entries
{data, socket} =
case {params["_target"], data["name"], upload_entries} do
{["image"], "", [entry]} ->
# Emulate input event to make sure validation errors are shown
socket =
exec_js(
socket,
JS.dispatch("input", to: "#insert-image-form-name")
|> JS.dispatch("blur", to: "#insert-image-form-name")
)
{%{data | "name" => entry.client_name}, socket}
_ ->
{data, socket}
end
changeset = data |> changeset() |> Map.replace!(:action, :validate)
{:noreply, assign(socket, changeset: changeset)}
end
def handle_event("clear_file", %{"ref" => ref}, socket) do
{:noreply, cancel_upload(socket, :image, ref)}
end
def handle_event("save", %{"data" => data}, socket) do
data
|> changeset()
|> apply_action(:insert)
|> case do
{:ok, data} ->
[:ok] =
consume_uploaded_entries(socket, :image, fn %{}, _entry -> {:ok, :ok} end)
file_entry = %{name: data.name, type: :attachment}
Livebook.Session.add_file_entries(socket.assigns.session.pid, [file_entry])
url = "files/#{URI.encode(data.name, &URI.char_unreserved?/1)}"
send(self(), {:insert_image_complete, socket.assigns.insert_image_metadata, url})
{:noreply, push_patch(socket, to: socket.assigns.return_to)}
{:error, changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
defp file_entry_file(socket) do
data = apply_changes(socket.assigns.changeset)
FileSystem.File.resolve(socket.assigns.session.files_dir, data.name)
end
end