Packages
livebook
0.11.4
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
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="p-6 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">
<.link patch={@return_to} class="button-base button-outlined-gray">
Cancel
</.link>
<button
class="button-base button-blue"
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