Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Form tweaks #840

Merged
merged 3 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion lib/ambry_web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,34 @@ defmodule AmbryWeb.CoreComponents do
]
end

@doc """
Renders a tab bar style radio-group when given a form field.
"""
attr :label, :string, default: nil
attr :field, FormField, required: true
attr :options, :list, required: true

slot :inner_block, required: true

def tabs(assigns) do
~H"""
<div class="flex items-center gap-2">
<%= render_slot(@inner_block) %>

<span class="cursor-default select-none text-sm">•</span>

<.intersperse :let={{label, value}} enum={@options}>
<:separator>
<span class="cursor-default select-none text-sm">•</span>
</:separator>
<label class="cursor-pointer select-none whitespace-nowrap text-sm italic leading-6 has-[:checked]:font-semibold has-[:checked]:not-italic has-[:checked]:underline">
<input type="radio" name={@field.name} value={value} checked={@field.value == value} class="hidden" /> <%= label %>
</label>
</.intersperse>
</div>
"""
end

@doc """
Renders a label.
"""
Expand All @@ -566,7 +594,10 @@ defmodule AmbryWeb.CoreComponents do

def label(assigns) do
~H"""
<label for={@for} class={["block text-sm font-semibold leading-6 text-zinc-800 dark:text-zinc-200", @class]}>
<label
for={@for}
class={["block whitespace-nowrap text-sm font-semibold leading-6 text-zinc-800 dark:text-zinc-200", @class]}
>
<%= render_slot(@inner_block) %>
</label>
"""
Expand Down
3 changes: 1 addition & 2 deletions lib/ambry_web/live/admin/book_live/form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ defmodule AmbryWeb.Admin.BookLive.Form do

defp apply_action(socket, :edit, %{"id" => id}) do
book = Books.get_book!(id)
init_params = if book.image_path, do: %{}, else: %{"image_type" => "upload"}
changeset = Books.change_book(book, init_params)
changeset = Books.change_book(book, %{"image_type" => "upload"})

socket
|> assign_form(changeset)
Expand Down
166 changes: 80 additions & 86 deletions lib/ambry_web/live/admin/book_live/form.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -10,69 +10,58 @@

<div class="max-w-3xl">
<.simple_form for={@form} phx-change="validate" phx-submit="submit" autocomplete="off">
<div class="space-y-2">
<.label for={@form[:title].id}>Title</.label>
<div class="flex items-center gap-2">
<.input field={@form[:title]} show_errors={false} container_class="grow" />
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 sm:gap-2">
<.input field={@form[:title]} label="Title" />

<div class="row-start-1 space-y-2 sm:col-start-2">
<.label>Import from:</.label>
<.button
:if={@scraping_available}
color={:zinc}
class="flex items-center gap-1"
type="button"
phx-click={open_import_form(@book, "goodreads")}
>
<FA.icon name="goodreads" type="brands" class="h-4 w-4 fill-current" /> GoodReads
</.button>
<.button
color={:zinc}
class="flex items-center gap-1"
type="button"
phx-click={open_import_form(@book, "audible")}
>
<FA.icon name="audible" type="brands" class="h-4 w-4 fill-current" /> Audible
</.button>
<div class="flex items-center gap-2">
<.button
:if={@scraping_available}
color={:zinc}
class="flex items-center gap-1"
type="button"
phx-click={open_import_form(@book, "goodreads")}
>
<FA.icon name="goodreads" type="brands" class="h-4 w-4 fill-current" /> GoodReads
</.button>
<.button
color={:zinc}
class="flex items-center gap-1"
type="button"
phx-click={open_import_form(@book, "audible")}
>
<FA.icon name="audible" type="brands" class="h-4 w-4 fill-current" /> Audible
</.button>
</div>
</div>
<.field_errors field={@form[:title]} />
</div>

<div class="space-y-2">
<.label for={@form[:published].id}>First published</.label>

<.note>
This is meant to be print publication date, not audiobook recording date.
</.note>

<div class="flex flex-row items-center gap-2">
<.input field={@form[:published]} type="date" show_errors={false} container_class="grow" />
<.label for={@form[:published_format].id}>Display format</.label>
<.input
field={@form[:published_format]}
type="select"
container_class="grow"
options={[{"Full Date", "full"}, {"Year & Month", "year_month"}, {"Year Only", "year"}]}
/>
</div>

<.field_errors field={@form[:published]} />
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 sm:gap-2">
<.input field={@form[:published]} type="date" label="First print publication" />
<.input
field={@form[:published_format]}
type="select"
label="Date display format"
options={[{"Full Date", "full"}, {"Year & Month", "year_month"}, {"Year Only", "year"}]}
/>
</div>

<div class="flex gap-2">
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<.input
id="description-input"
field={@form[:description]}
label="Description"
type="textarea"
phx-hook="maintain-attrs"
data-attrs="style"
container_class="w-1/2"
/>
<div class="relative w-1/2 flex-1">
<div class="relative">
<div
id="description-preview"
phx-hook="scroll-match"
data-target="description-input"
class="absolute top-8 right-0 bottom-0 left-0 overflow-auto rounded-sm border border-zinc-300 dark:border-zinc-800"
class="absolute top-8 right-0 bottom-0 left-0 hidden overflow-auto rounded-sm border border-zinc-300 dark:border-zinc-800 sm:block"
>
<.markdown content={@form[:description].value || ""} class="p-2" />
</div>
Expand All @@ -84,15 +73,13 @@
<.inputs_for :let={book_author_form} field={@form[:book_authors]}>
<.sort_input field={@form[:book_authors_sort]} index={book_author_form.index} />

<div class="flex items-start gap-2">
<.input
field={book_author_form[:author_id]}
type="autocomplete"
options={@authors}
list="authors"
container_class="grow"
<div class="relative">
<.input field={book_author_form[:author_id]} type="autocomplete" options={@authors} list="authors" />
<.delete_button
field={@form[:book_authors_drop]}
index={book_author_form.index}
class="absolute right-2 top-3"
/>
<.delete_button field={@form[:book_authors_drop]} index={book_author_form.index} class="pt-[10px]" />
</div>
</.inputs_for>

Expand All @@ -105,7 +92,7 @@
<.inputs_for :let={series_book_form} field={@form[:series_books]}>
<.sort_input field={@form[:series_books_sort]} index={series_book_form.index} />

<div class="flex items-start gap-2">
<div class="relative flex items-start gap-2">
<.input
field={series_book_form[:book_number]}
placeholder="no."
Expand All @@ -119,47 +106,54 @@
list="series"
container_class="grow"
/>
<.delete_button field={@form[:series_books_drop]} index={series_book_form.index} class="pt-[10px]" />
<.delete_button
field={@form[:series_books_drop]}
index={series_book_form.index}
class="absolute right-2 top-3"
/>
</div>
</.inputs_for>

<.add_button field={@form[:series_books_sort]}>Add series</.add_button>
<.delete_input field={@form[:series_books_drop]} />
</div>

<.input type="hidden" field={@form[:image_type]} />
<.input type="hidden" field={@form[:image_path]} />
<div :if={@form[:image_path].value && @form[:image_path].value != ""} class="space-y-2">
<.label class="flex items-center gap-2">
Current image <.image_delete_button field={@form[:image_path]} />
</.label>
<.image_with_size id={"image-#{@book.id}"} src={@form[:image_path].value} class="h-48 w-48 rounded-sm" />
</div>

<.input
type="select"
label={if @live_action == :new || is_nil(@book.image_path), do: "Image", else: "Replace image"}
field={@form[:image_type]}
prompt=""
options={[
{"Upload file", "upload"},
{"Import image from URL", "url_import"}
]}
/>

<.file_input
:if={@form[:image_type].value == "upload"}
upload={@uploads.image}
on_cancel="cancel-upload"
label="Upload image"
image_preview_class="h-48 w-48 rounded-sm"
/>

<.image_import_input
:if={@form[:image_type].value == "url_import"}
field={@form[:image_import_url]}
label="Import image from URL"
image_preview_class="h-48 w-48 rounded-sm"
/>
<%= if @form[:image_path].value && @form[:image_path].value != "" do %>
<div class="space-y-2">
<.label class="flex items-center gap-2">
Image <.image_delete_button field={@form[:image_path]} />
</.label>
<.image_with_size id={"image-#{@book.id}"} src={@form[:image_path].value} class="h-48 w-48 rounded-sm" />
</div>
<% else %>
<div class="space-y-2">
<.tabs
field={@form[:image_type]}
options={[
{"Upload file", "upload"},
{"Import image from URL", "url_import"}
]}
>
<.label>Image</.label>
</.tabs>

<.file_input
:if={@form[:image_type].value == "upload"}
upload={@uploads.image}
on_cancel="cancel-upload"
image_preview_class="h-48 w-48 rounded-sm"
/>

<.image_import_input
:if={@form[:image_type].value == "url_import"}
field={@form[:image_import_url]}
image_preview_class="h-48 w-48 rounded-sm"
/>
</div>
<% end %>

<:actions>
<.button>Save</.button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ defmodule AmbryWeb.Admin.BookLive.Form.AudibleImportForm do

{"use_cover_image", "true"}, acc ->
Map.merge(acc, %{
"image_path" => "",
"image_type" => "url_import",
"image_import_url" => book.cover_image
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ defmodule AmbryWeb.Admin.BookLive.Form.GoodreadsImportForm do

{"use_cover_image", "true"}, acc ->
Map.merge(acc, %{
"image_path" => "",
"image_type" => "url_import",
"image_import_url" => book.cover_image
})
Expand Down
10 changes: 9 additions & 1 deletion lib/ambry_web/live/admin/media_live/form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,15 @@ defmodule AmbryWeb.Admin.MediaLive.Form do

@impl Phoenix.LiveView
def handle_params(%{"import" => type}, _url, socket) do
query = socket.assigns.media.book.title
book_id = socket.assigns.form.params["book_id"] || socket.assigns.media.book_id

query =
if book_id do
Ambry.Books.get_book!(book_id).title
else
""
end

import_type = String.to_existing_atom(type)
{:noreply, assign(socket, import: %{type: import_type, query: query})}
end
Expand Down
Loading
Loading