Add ManageSlideSidebarComponent and refactor ManagerSettingsComponent

- Introduced ManageSlideSidebarComponent for displaying slide thumbnails with click functionality to navigate to the current page.
- Refactored ManagerSettingsComponent to improve layout and organization, including:
  - Consolidated toggle options into a reusable toggle_row component for better maintainability.
  - Enhanced the presentation and attendees settings sections with clearer labels and improved styling.
  - Removed redundant code and improved readability.
This commit is contained in:
Alex Lion
2026-02-09 19:20:08 +01:00
parent b5d875ac90
commit c00958e97e
8 changed files with 1146 additions and 1407 deletions

View File

@@ -117,6 +117,41 @@ defmodule Claper.Presentations do
end
end
@doc """
Returns a list of thumbnail URLs for a given presentation.
Thumbnails are smaller versions of slides stored in a 'thumbs' subdirectory.
"""
def get_slide_thumbnail_urls(nil), do: []
def get_slide_thumbnail_urls(%PresentationFile{hash: nil}), do: []
def get_slide_thumbnail_urls(%PresentationFile{length: nil}), do: []
def get_slide_thumbnail_urls(%PresentationFile{length: 0}), do: []
def get_slide_thumbnail_urls(%PresentationFile{hash: hash, length: length}) do
get_slide_thumbnail_urls(hash, length)
end
def get_slide_thumbnail_urls(hash, length) when is_binary(hash) and is_integer(length) do
config = Application.get_env(:claper, :presentations)
case Keyword.fetch!(config, :storage) do
"local" ->
for index <- 1..length do
"/uploads/#{hash}/thumbs/#{index}.jpg"
end
"s3" ->
base_url = Keyword.fetch!(config, :s3_public_url)
for index <- 1..length do
base_url <> "/presentations/#{hash}/thumbs/#{index}.jpg"
end
storage ->
raise "Unrecognised presentations storage value #{storage}"
end
end
@doc """
Creates a presentation_files.

View File

@@ -92,25 +92,62 @@ defmodule Claper.Tasks.Converter do
defp pdf_to_jpg(%Result{status: 0}, path, _presentation, _user_id) do
resolution = get_resolution()
Porcelain.exec(
"gs",
[
"-sDEVICE=png16m",
"-o#{path}/%d.jpg",
"-r#{resolution}",
"-dNOPAUSE",
"-dBATCH",
"#{path}/original.pdf"
]
)
result =
Porcelain.exec(
"gs",
[
"-sDEVICE=png16m",
"-o#{path}/%d.jpg",
"-r#{resolution}",
"-dNOPAUSE",
"-dBATCH",
"#{path}/original.pdf"
]
)
# Generate thumbnails after full-size images
case result do
%Porcelain.Result{status: 0} -> generate_thumbnails(path)
_ -> result
end
result
end
defp pdf_to_jpg(_result, path, presentation, user_id) do
failure(presentation, path, user_id)
end
defp generate_thumbnails(path) do
thumbs_dir = Path.join(path, "thumbs")
File.mkdir_p!(thumbs_dir)
files = Path.wildcard("#{path}/*.jpg")
for file <- files do
thumb_path = Path.join(thumbs_dir, Path.basename(file))
# Generate thumbnail with 200px width, maintaining aspect ratio
# Using "magick" for ImageMagick v7+ compatibility
Porcelain.exec(
"magick",
[
file,
"-resize",
"200x",
"-quality",
"80",
thumb_path
]
)
end
IO.puts("Generated #{length(files)} thumbnails in #{thumbs_dir}")
end
defp jpg_upload(%Result{status: 0}, hash, path, presentation, user_id, is_copy) do
files = Path.wildcard("#{path}/*.jpg")
thumb_files = Path.wildcard("#{path}/thumbs/*.jpg")
# assign new hash to avoid cache issues
new_hash = :erlang.phash2("#{hash}-#{System.system_time(:second)}")
@@ -129,6 +166,7 @@ defmodule Claper.Tasks.Converter do
])
)
else
# Upload full-size images
for f <- files do
IO.puts("Uploads #{f} to presentations/#{new_hash}/#{Path.basename(f)}")
@@ -141,6 +179,20 @@ defmodule Claper.Tasks.Converter do
)
|> ExAws.request()
end
# Upload thumbnails
for f <- thumb_files do
IO.puts("Uploads thumbnail #{f} to presentations/#{new_hash}/thumbs/#{Path.basename(f)}")
f
|> ExAws.S3.Upload.stream_file()
|> ExAws.S3.upload(
get_s3_bucket(),
"presentations/#{new_hash}/thumbs/#{Path.basename(f)}",
acl: "public-read"
)
|> ExAws.request()
end
end
if !is_nil(presentation.hash) && !is_copy do

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,101 @@
defmodule ClaperWeb.EventLive.ManageFloatingActionBar do
use ClaperWeb, :live_component
def render(assigns) do
~H"""
<div class="fixed bottom-6 left-1/2 -translate-x-1/2 z-40">
<div class="flex items-center gap-x-1 bg-white rounded-full shadow-xl px-2 py-2 border border-gray-200">
<.link
patch={~p"/e/#{@event_code}/manage/add/poll"}
class="flex items-center gap-x-2 px-4 py-2 rounded-full hover:bg-gray-100 transition-colors text-gray-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-primary-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
<span class="font-medium text-sm">{gettext("Polls")}</span>
</.link>
<div class="w-px h-6 bg-gray-200"></div>
<.link
patch={~p"/e/#{@event_code}/manage/add/form"}
class="flex items-center gap-x-2 px-4 py-2 rounded-full hover:bg-gray-100 transition-colors text-gray-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-primary-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"
/>
</svg>
<span class="font-medium text-sm">{gettext("Forms")}</span>
</.link>
<div class="w-px h-6 bg-gray-200"></div>
<.link
patch={~p"/e/#{@event_code}/manage/add/embed"}
class="flex items-center gap-x-2 px-4 py-2 rounded-full hover:bg-gray-100 transition-colors text-gray-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
class="w-5 h-5 text-primary-500"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.25 9.75L16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0020.25 18V6A2.25 2.25 0 0018 3.75H6A2.25 2.25 0 003.75 6v12A2.25 2.25 0 006 20.25z"
/>
</svg>
<span class="font-medium text-sm">{gettext("Web Content")}</span>
</.link>
<div class="w-px h-6 bg-gray-200"></div>
<.link
patch={~p"/e/#{@event_code}/manage/add/quiz"}
class="flex items-center gap-x-2 px-4 py-2 rounded-full hover:bg-gray-100 transition-colors text-gray-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
class="w-5 h-5 text-primary-500"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z"
/>
</svg>
<span class="font-medium text-sm">{gettext("Quiz")}</span>
</.link>
</div>
</div>
"""
end
end

View File

@@ -0,0 +1,194 @@
defmodule ClaperWeb.EventLive.ManageInteractionListComponent do
use ClaperWeb, :live_component
def render(assigns) do
~H"""
<div class="bg-white rounded-xl shadow-base h-full flex flex-col">
<div class="px-4 py-3 border-b border-gray-200 flex items-center gap-x-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5 text-gray-500"
>
<path
fill-rule="evenodd"
d="M10 2c-2.236 0-4.43.18-6.57.524C1.993 2.755 1 4.014 1 5.426v5.148c0 1.413.993 2.67 2.43 2.902 1.168.188 2.352.327 3.55.414.28.02.521.18.642.413l1.713 3.293a.75.75 0 0 0 1.33 0l1.713-3.293a.783.783 0 0 1 .642-.413 41.102 41.102 0 0 0 3.55-.414c1.437-.231 2.43-1.49 2.43-2.902V5.426c0-1.413-.993-2.67-2.43-2.902A41.289 41.289 0 0 0 10 2ZM6.75 6a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5h-6.5Zm0 2.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5Z"
clip-rule="evenodd"
/>
</svg>
<span class="font-semibold text-gray-700">{gettext("Interactions")}</span>
</div>
<div class="flex-1 overflow-y-auto p-3 space-y-2">
<div
:if={length(@interactions) == 0}
class="h-full flex flex-col items-center justify-center text-gray-400 py-8"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="h-12 w-12 mb-3"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M4 12h6l-6 8h6" /><path d="M14 4h6l-6 8h6" />
</svg>
<p class="text-sm text-center">
{gettext("No interactions on this slide")}
</p>
</div>
<%= for interaction <- @interactions do %>
<div class="flex items-center justify-between p-3 rounded-lg border border-gray-200 hover:border-gray-300 transition-colors">
<div class="flex items-center gap-x-3 flex-1 min-w-0">
<%= case interaction do %>
<% %Claper.Polls.Poll{} -> %>
<div class="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-primary text-white">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
</div>
<div class="min-w-0 flex-1">
<p class="text-xs text-gray-500">{gettext("Poll")}</p>
<p class="text-sm font-medium text-gray-900 truncate">{interaction.title}</p>
</div>
<% %Claper.Forms.Form{} -> %>
<div class="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-primary text-white">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"
/>
</svg>
</div>
<div class="min-w-0 flex-1">
<p class="text-xs text-gray-500">{gettext("Form")}</p>
<p class="text-sm font-medium text-gray-900 truncate">{interaction.title}</p>
</div>
<% %Claper.Embeds.Embed{} -> %>
<div class="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-primary text-white">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.25 9.75L16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0020.25 18V6A2.25 2.25 0 0018 3.75H6A2.25 2.25 0 003.75 6v12A2.25 2.25 0 006 20.25z"
/>
</svg>
</div>
<div class="min-w-0 flex-1">
<p class="text-xs text-gray-500">{gettext("Web content")}</p>
<p class="text-sm font-medium text-gray-900 truncate">{interaction.title}</p>
</div>
<% %Claper.Quizzes.Quiz{} -> %>
<div class="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-primary text-white">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
/>
</svg>
</div>
<div class="min-w-0 flex-1">
<p class="text-xs text-gray-500">{gettext("Quiz")}</p>
<p class="text-sm font-medium text-gray-900 truncate">{interaction.title}</p>
</div>
<% _ -> %>
<% end %>
</div>
<div class="flex items-center gap-x-2 ml-2">
<.link
patch={edit_path(@event_code, interaction)}
class="p-1.5 rounded-md hover:bg-gray-100 text-gray-500 hover:text-gray-700 transition-colors"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
</.link>
<button
phx-click={toggle_event(interaction)}
phx-value-id={interaction.id}
class={"relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none #{if interaction.enabled, do: "bg-primary-500", else: "bg-gray-200"}"}
>
<span class={"pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out #{if interaction.enabled, do: "translate-x-5", else: "translate-x-0"}"}>
</span>
</button>
</div>
</div>
<% end %>
</div>
</div>
"""
end
defp edit_path(event_code, %Claper.Polls.Poll{id: id}),
do: ~p"/e/#{event_code}/manage/edit/poll/#{id}"
defp edit_path(event_code, %Claper.Forms.Form{id: id}),
do: ~p"/e/#{event_code}/manage/edit/form/#{id}"
defp edit_path(event_code, %Claper.Embeds.Embed{id: id}),
do: ~p"/e/#{event_code}/manage/edit/embed/#{id}"
defp edit_path(event_code, %Claper.Quizzes.Quiz{id: id}),
do: ~p"/e/#{event_code}/manage/edit/quiz/#{id}"
defp edit_path(event_code, _), do: ~p"/e/#{event_code}/manage"
defp toggle_event(%Claper.Polls.Poll{enabled: true}), do: "poll-set-inactive"
defp toggle_event(%Claper.Polls.Poll{enabled: false}), do: "poll-set-active"
defp toggle_event(%Claper.Forms.Form{enabled: true}), do: "form-set-inactive"
defp toggle_event(%Claper.Forms.Form{enabled: false}), do: "form-set-active"
defp toggle_event(%Claper.Embeds.Embed{enabled: true}), do: "embed-set-inactive"
defp toggle_event(%Claper.Embeds.Embed{enabled: false}), do: "embed-set-active"
defp toggle_event(%Claper.Quizzes.Quiz{enabled: true}), do: "quiz-set-inactive"
defp toggle_event(%Claper.Quizzes.Quiz{enabled: false}), do: "quiz-set-active"
defp toggle_event(_), do: ""
end

View File

@@ -0,0 +1,141 @@
defmodule ClaperWeb.EventLive.ManageSlidePreviewComponent do
use ClaperWeb, :live_component
alias Claper.Presentations
def render(assigns) do
slide_urls = Presentations.get_slide_urls(assigns.presentation_file)
current_slide_url = Enum.at(slide_urls, assigns.current_position)
assigns = assign(assigns, :current_slide_url, current_slide_url)
~H"""
<div class="flex flex-col h-full">
<div class="px-4 py-3 border-b border-gray-200 flex items-center justify-between">
<div class="flex items-center gap-x-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5 text-gray-500"
>
<path d="M10 12.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z" />
<path
fill-rule="evenodd"
d="M.664 10.59a1.651 1.651 0 0 1 0-1.186A10.004 10.004 0 0 1 10 3c4.257 0 7.893 2.66 9.336 6.41.147.381.146.804 0 1.186A10.004 10.004 0 0 1 10 17c-4.257 0-7.893-2.66-9.336-6.41ZM14 10a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
clip-rule="evenodd"
/>
</svg>
<span class="font-semibold text-gray-700">{gettext("Preview")}</span>
</div>
<div class="flex items-center gap-x-2 text-sm text-gray-500">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M1 2.75A.75.75 0 0 1 1.75 2h16.5a.75.75 0 0 1 0 1.5H18v8.75A2.75 2.75 0 0 1 15.25 15h-1.072l.798 3.06a.75.75 0 0 1-1.452.38L13.41 18H6.59l-.114.44a.75.75 0 0 1-1.452-.38L5.823 15H4.75A2.75 2.75 0 0 1 2 12.25V3.5h-.25A.75.75 0 0 1 1 2.75Z"
clip-rule="evenodd"
/>
</svg>
<span>{@current_position + 1}/{@total_slides}</span>
</div>
</div>
<div class="flex-1 flex items-center justify-center p-2 bg-gray-100 relative overflow-hidden">
<button
:if={@current_position > 0}
phx-click="current-page"
phx-value-page={@current_position - 1}
class="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-white shadow-lg flex items-center justify-center hover:bg-gray-50 transition-colors z-10"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5 text-gray-600"
>
<path
fill-rule="evenodd"
d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z"
clip-rule="evenodd"
/>
</svg>
</button>
<div
:if={@current_position <= 0}
class="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center opacity-50 cursor-not-allowed"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5 text-gray-400"
>
<path
fill-rule="evenodd"
d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class="h-full aspect-video bg-white rounded-lg shadow-lg overflow-hidden">
<img
:if={@current_slide_url}
src={@current_slide_url}
class="w-full h-full object-contain"
alt={"Slide #{@current_position + 1}"}
/>
<div
:if={!@current_slide_url}
class="w-full h-full flex items-center justify-center text-gray-400"
>
<span>{gettext("No slide available")}</span>
</div>
</div>
<button
:if={@current_position < @total_slides - 1}
phx-click="current-page"
phx-value-page={@current_position + 1}
class="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-white shadow-lg flex items-center justify-center hover:bg-gray-50 transition-colors z-10"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5 text-gray-600"
>
<path
fill-rule="evenodd"
d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
</svg>
</button>
<div
:if={@current_position >= @total_slides - 1}
class="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center opacity-50 cursor-not-allowed"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5 text-gray-400"
>
<path
fill-rule="evenodd"
d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
</svg>
</div>
</div>
</div>
"""
end
end

View File

@@ -0,0 +1,52 @@
defmodule ClaperWeb.EventLive.ManageSlideSidebarComponent do
use ClaperWeb, :live_component
alias Claper.Presentations
def render(assigns) do
~H"""
<div class="flex flex-col h-full bg-white">
<div class="px-4 py-3 border-b border-gray-200 flex items-center gap-x-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5 text-gray-500"
>
<path
fill-rule="evenodd"
d="M1 2.75A.75.75 0 0 1 1.75 2h16.5a.75.75 0 0 1 0 1.5H18v8.75A2.75 2.75 0 0 1 15.25 15h-1.072l.798 3.06a.75.75 0 0 1-1.452.38L13.41 18H6.59l-.114.44a.75.75 0 0 1-1.452-.38L5.823 15H4.75A2.75 2.75 0 0 1 2 12.25V3.5h-.25A.75.75 0 0 1 1 2.75Z"
clip-rule="evenodd"
/>
</svg>
<span class="font-semibold text-gray-700">{gettext("Content")}</span>
</div>
<div class="flex-1 overflow-y-auto p-3 space-y-2">
<button
:for={
{src, index} <-
Presentations.get_slide_thumbnail_urls(@presentation_file) |> Enum.with_index(0)
}
id={"slide-thumb-#{index}"}
phx-click="current-page"
phx-value-page={index}
class={"group flex items-start gap-x-2 w-full rounded-lg p-1 transition-all hover:bg-gray-100 #{if @current_position == index, do: "bg-primary-50"}"}
>
<span class={"flex-shrink-0 w-6 text-sm font-medium #{if @current_position == index, do: "text-primary-600", else: "text-gray-500"}"}>
{index + 1}
</span>
<div class={"relative w-28 aspect-video rounded-md overflow-hidden border-2 transition-all #{if @current_position == index, do: "border-primary-500 shadow-md", else: "border-transparent opacity-60 group-hover:opacity-100"}"}>
<img
src={src}
loading="lazy"
decoding="async"
class="w-full h-full object-cover"
alt={"Slide #{index + 1}"}
/>
</div>
</button>
</div>
</div>
"""
end
end

View File

@@ -5,22 +5,24 @@ defmodule ClaperWeb.EventLive.ManagerSettingsComponent do
assigns = assigns |> assign_new(:show_shortcut, fn -> true end)
~H"""
<div class="grid grid-cols-1 @md:grid-cols-2 @md:space-x-5 px-5 py-3 h-full mb-10">
<div>
<div class="flex items-center space-x-2 font-semibold text-lg">
<div class="flex flex-col h-full">
<!-- Interactions Options Section -->
<div class="px-4 py-3 border-b border-gray-200">
<div class="flex items-center gap-x-2 font-semibold text-base text-gray-700">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5"
class="w-5 h-5 text-gray-500"
>
<path d="M6.111 11.89A5.5 5.5 0 1 1 15.501 8 .75.75 0 0 0 17 8a7 7 0 1 0-11.95 4.95.75.75 0 0 0 1.06-1.06Z" />
<path d="M8.232 6.232a2.5 2.5 0 0 0 0 3.536.75.75 0 1 1-1.06 1.06A4 4 0 1 1 14 8a.75.75 0 0 1-1.5 0 2.5 2.5 0 0 0-4.268-1.768Z" />
<path d="M10.766 7.51a.75.75 0 0 0-1.37.365l-.492 6.861a.75.75 0 0 0 1.204.65l1.043-.799.985 3.678a.75.75 0 0 0 1.45-.388l-.978-3.646 1.292.204a.75.75 0 0 0 .74-1.16l-3.874-5.764Z" />
</svg>
<span>{gettext("Interaction")}</span>
<span>{gettext("Interactions Options")}</span>
</div>
</div>
<div class="px-4 py-3 space-y-2 border-b border-gray-200">
<%= case @current_interaction do %>
<% %Claper.Polls.Poll{} -> %>
@@ -196,415 +198,122 @@ defmodule ClaperWeb.EventLive.ManagerSettingsComponent do
<p class="text-gray-400 italic mt-1.5">No settings available for this interaction</p>
<% end %>
<div class="flex space-x-2 items-center mt-3"></div>
</div>
<div class="h-full overflow-visible @md:overflow-auto">
<div class="grid grid-cols-1 space-y-5">
<div class="grid grid-cols-1 space-y-1.5">
<div class="flex items-center space-x-2 font-semibold text-lg">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5"
>
<path
fill-rule="evenodd"
d="M1 2.75A.75.75 0 0 1 1.75 2h16.5a.75.75 0 0 1 0 1.5H18v8.75A2.75 2.75 0 0 1 15.25 15h-1.072l.798 3.06a.75.75 0 0 1-1.452.38L13.41 18H6.59l-.114.44a.75.75 0 0 1-1.452-.38L5.823 15H4.75A2.75 2.75 0 0 1 2 12.25V3.5h-.25A.75.75 0 0 1 1 2.75ZM7.373 15l-.391 1.5h6.037l-.392-1.5H7.373ZM13.25 5a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0v-5.5a.75.75 0 0 1 .75-.75Zm-6.5 4a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 6.75 9Zm4-1.25a.75.75 0 0 0-1.5 0v3.5a.75.75 0 0 0 1.5 0v-3.5Z"
clip-rule="evenodd"
/>
</svg>
<span>{gettext("Presentation")}</span>
</div>
<!-- Presentation Settings Section -->
<div class="px-4 py-3 border-b border-gray-200 space-y-2">
<.toggle_row
label={if @state.join_screen_visible, do: gettext("Hide instructions to join"), else: gettext("Show instructions to join")}
checked={@state.join_screen_visible}
key={:join_screen_visible}
shortcut={if @create == nil, do: "Q", else: nil}
show_shortcut={@show_shortcut}
/>
<.toggle_row
label={if @state.chat_visible, do: gettext("Hide messages"), else: gettext("Show messages")}
checked={@state.chat_visible}
key={:chat_visible}
shortcut={if @create == nil, do: "W", else: nil}
show_shortcut={@show_shortcut}
/>
<.toggle_row
label={if @state.show_only_pinned, do: gettext("Show all messages"), else: gettext("Show pinned messages")}
checked={@state.show_only_pinned}
key={:show_only_pinned}
shortcut={if @create == nil, do: "E", else: nil}
disabled={!@state.chat_visible}
show_shortcut={@show_shortcut}
/>
<.toggle_row
label={if @state.chat_enabled, do: gettext("Disable messages"), else: gettext("Enable messages")}
checked={@state.chat_enabled}
key={:chat_enabled}
shortcut={if @create == nil, do: "A", else: nil}
show_shortcut={@show_shortcut}
/>
<.toggle_row
label={if @state.anonymous_chat_enabled, do: gettext("Reject anonymous messages"), else: gettext("Allow anonymous messages")}
checked={@state.anonymous_chat_enabled}
key={:anonymous_chat_enabled}
shortcut={if @create == nil, do: "S", else: nil}
disabled={!@state.chat_enabled}
show_shortcut={@show_shortcut}
/>
</div>
<div class="flex space-x-1 items-center">
<ClaperWeb.Component.Input.check_button
key={:join_screen_visible}
checked={@state.join_screen_visible}
shortcut={if @create == nil, do: "Q", else: nil}
>
<svg
:if={!@state.join_screen_visible}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" /><path d="M7 17l0 .01" /><path d="M14 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" /><path d="M7 7l0 .01" /><path d="M4 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" /><path d="M17 7l0 .01" /><path d="M14 14l3 0" /><path d="M20 14l0 .01" /><path d="M14 14l0 3" /><path d="M14 20l3 0" /><path d="M17 17l3 0" /><path d="M20 17l0 3" />
</svg>
<svg
:if={@state.join_screen_visible}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M8 4h1a1 1 0 0 1 1 1v1m-.297 3.711a1 1 0 0 1 -.703 .289h-4a1 1 0 0 1 -1 -1v-4c0 -.275 .11 -.524 .29 -.705" /><path d="M7 17v.01" /><path d="M14 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" /><path d="M7 7v.01" /><path d="M4 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" /><path d="M17 7v.01" /><path d="M20 14v.01" /><path d="M14 14v3" /><path d="M14 20h3" /><path d="M3 3l18 18" />
</svg>
<div>
<span :if={!@state.join_screen_visible}>
{gettext("Show instructions to join")}
</span>
<span :if={@state.join_screen_visible}>
{gettext("Hide instructions to join")}
</span>
</div>
<code
:if={@show_shortcut}
class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg"
>
q
</code>
<div :if={!@show_shortcut}></div>
</ClaperWeb.Component.Input.check_button>
</div>
<div class="flex space-x-2 items-center">
<ClaperWeb.Component.Input.check_button
key={:chat_visible}
checked={@state.chat_visible}
shortcut={if @create == nil, do: "W", else: nil}
>
<svg
:if={!@state.chat_visible}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M8 9h8" /><path d="M8 13h6" /><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12z" />
</svg>
<svg
:if={@state.chat_visible}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M8 9h1m4 0h3" /><path d="M8 13h5" /><path d="M8 4h10a3 3 0 0 1 3 3v8c0 .577 -.163 1.116 -.445 1.573m-2.555 1.427h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8c0 -1.085 .576 -2.036 1.439 -2.562" /><path d="M3 3l18 18" />
</svg>
<div>
<span :if={!@state.chat_visible}>{gettext("Show messages")}</span>
<span :if={@state.chat_visible}>{gettext("Hide messages")}</span>
</div>
<code
:if={@show_shortcut}
class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg"
>
w
</code>
<div :if={!@show_shortcut}></div>
</ClaperWeb.Component.Input.check_button>
</div>
<div
class={"#{if !@state.chat_visible, do: "opacity-50"} flex space-x-2 items-center"}
title={
if !@state.chat_visible,
do: gettext("Show messages to change this option"),
else: nil
}
>
<ClaperWeb.Component.Input.check_button
key={:show_only_pinned}
checked={@state.show_only_pinned}
disabled={!@state.chat_visible}
shortcut={if @create == nil, do: "E", else: nil}
>
<svg
:if={!@state.show_only_pinned}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M8 9h8" /><path d="M8 13h4.5" /><path d="M10.325 19.605l-2.325 1.395v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v4.5" /><path d="M17.8 20.817l-2.172 1.138a.392 .392 0 0 1 -.568 -.41l.415 -2.411l-1.757 -1.707a.389 .389 0 0 1 .217 -.665l2.428 -.352l1.086 -2.193a.392 .392 0 0 1 .702 0l1.086 2.193l2.428 .352a.39 .39 0 0 1 .217 .665l-1.757 1.707l.414 2.41a.39 .39 0 0 1 -.567 .411l-2.172 -1.138z" />
</svg>
<svg
:if={@state.show_only_pinned}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M8 9h8" /><path d="M8 13h6" /><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12z" />
</svg>
<div>
<span :if={!@state.show_only_pinned}>
{gettext("Show only pinned messages")}
</span>
<span :if={@state.show_only_pinned}>{gettext("Show all messages")}</span>
</div>
<code
:if={@show_shortcut}
class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg"
>
e
</code>
<div :if={!@show_shortcut}></div>
</ClaperWeb.Component.Input.check_button>
</div>
<div>
<ClaperWeb.Component.Input.check_button
key={:show_attendee_count}
checked={@state.show_attendee_count}
shortcut={if @create == nil, do: "R", else: nil}
>
<svg
:if={!@state.show_attendee_count}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
>
<path
fill="currentColor"
d="M12 4a4 4 0 0 1 4 4a4 4 0 0 1-4 4a4 4 0 0 1-4-4a4 4 0 0 1 4-4m0 10c4.42 0 8 1.79 8 4v2H4v-2c0-2.21 3.58-4 8-4"
/>
</svg>
<svg
:if={@state.show_attendee_count}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
>
<path
fill="currentColor"
d="M12 4a4 4 0 0 1 4 4c0 1.95-1.4 3.58-3.25 3.93L8.07 7.25A4.004 4.004 0 0 1 12 4m.28 10l6 6L20 21.72L18.73 23l-3-3H4v-2c0-1.84 2.5-3.39 5.87-3.86L2.78 7.05l1.27-1.27zM20 18v1.18l-4.86-4.86C18 14.93 20 16.35 20 18"
/>
</svg>
<div>
<span :if={!@state.show_attendee_count}>
{gettext("Show attendee count")}
</span>
<span :if={@state.show_attendee_count}>
{gettext("Hide attendee count")}
</span>
</div>
<code
:if={@show_shortcut}
class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg"
>
r
</code>
<div :if={!@show_shortcut}></div>
</ClaperWeb.Component.Input.check_button>
</div>
</div>
<div class="grid grid-cols-1 space-y-1.5">
<div class="flex items-center space-x-2 font-semibold text-lg">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5"
>
<path d="M8 16.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z" />
<path
fill-rule="evenodd"
d="M4 4a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3V4Zm4-1.5v.75c0 .414.336.75.75.75h2.5a.75.75 0 0 0 .75-.75V2.5h1A1.5 1.5 0 0 1 14.5 4v12a1.5 1.5 0 0 1-1.5 1.5H7A1.5 1.5 0 0 1 5.5 16V4A1.5 1.5 0 0 1 7 2.5h1Z"
clip-rule="evenodd"
/>
</svg>
<span>{gettext("Attendees")}</span>
</div>
<div class="flex space-x-2 items-center">
<ClaperWeb.Component.Input.check_button
key={:chat_enabled}
checked={@state.chat_enabled}
shortcut={if @create == nil, do: "A", else: nil}
>
<svg
:if={!@state.chat_enabled}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M8 9h8" /><path d="M8 13h6" /><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12z" />
</svg>
<svg
:if={@state.chat_enabled}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M8 9h1m4 0h3" /><path d="M8 13h5" /><path d="M8 4h10a3 3 0 0 1 3 3v8c0 .577 -.163 1.116 -.445 1.573m-2.555 1.427h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8c0 -1.085 .576 -2.036 1.439 -2.562" /><path d="M3 3l18 18" />
</svg>
<div>
<span :if={!@state.chat_enabled}>{gettext("Enable messages")}</span>
<span :if={@state.chat_enabled}>{gettext("Disable messages")}</span>
</div>
<code
:if={@show_shortcut}
class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg"
>
a
</code>
<div :if={!@show_shortcut}></div>
</ClaperWeb.Component.Input.check_button>
</div>
<div
class={"#{if !@state.chat_enabled, do: "opacity-50"} flex space-x-2 items-center"}
title={
if !@state.chat_enabled,
do: gettext("Enable messages to change this option"),
else: nil
}
>
<ClaperWeb.Component.Input.check_button
key={:anonymous_chat_enabled}
checked={@state.anonymous_chat_enabled}
disabled={!@state.chat_enabled}
shortcut={if @create == nil, do: "S", else: nil}
>
<svg
:if={!@state.anonymous_chat_enabled}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M3 11h18" /><path d="M5 11v-4a3 3 0 0 1 3 -3h8a3 3 0 0 1 3 3v4" /><path d="M7 17m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" /><path d="M17 17m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" /><path d="M10 17h4" />
</svg>
<svg
:if={@state.anonymous_chat_enabled}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M3 11h8m4 0h6" /><path d="M5 11v-4c0 -.571 .16 -1.105 .437 -1.56m2.563 -1.44h8a3 3 0 0 1 3 3v4" /><path d="M7 17m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" /><path d="M14.88 14.877a3 3 0 1 0 4.239 4.247m.59 -3.414a3.012 3.012 0 0 0 -1.425 -1.422" /><path d="M10 17h4" /><path d="M3 3l18 18" />
</svg>
<div>
<span :if={!@state.anonymous_chat_enabled}>
{gettext("Allow anonymous messages")}
</span>
<span :if={@state.anonymous_chat_enabled}>
{gettext("Deny anonymous messages")}
</span>
</div>
<code
:if={@show_shortcut}
class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg"
>
s
</code>
<div :if={!@show_shortcut}></div>
</ClaperWeb.Component.Input.check_button>
</div>
<div class="flex space-x-2 items-center">
<ClaperWeb.Component.Input.check_button
key={:message_reaction_enabled}
checked={@state.message_reaction_enabled}
shortcut={if @create == nil, do: "D", else: nil}
>
<svg
:if={!@state.message_reaction_enabled}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M19.5 12.572l-7.5 7.428l-7.5 -7.428a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572" />
</svg>
<svg
:if={@state.message_reaction_enabled}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M3 3l18 18" /><path d="M19.5 12.572l-1.5 1.428m-2 2l-4 4l-7.5 -7.428a5 5 0 0 1 -1.288 -5.068a4.976 4.976 0 0 1 1.788 -2.504m3 -1c1.56 0 3.05 .727 4 2a5 5 0 1 1 7.5 6.572" />
</svg>
<div>
<span :if={!@state.message_reaction_enabled}>
{gettext("Enable reactions")}
</span>
<span :if={@state.message_reaction_enabled}>
{gettext("Disable reactions")}
</span>
</div>
<code
:if={@show_shortcut}
class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg"
>
d
</code>
<div :if={!@show_shortcut}></div>
</ClaperWeb.Component.Input.check_button>
</div>
</div>
<!-- Attendees Settings Section -->
<div class="px-4 py-3 border-b border-gray-200">
<div class="flex items-center gap-x-2 font-semibold text-base text-gray-700 mb-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5 text-gray-500"
>
<path d="M8 16.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z" />
<path
fill-rule="evenodd"
d="M4 4a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3V4Zm4-1.5v.75c0 .414.336.75.75.75h2.5a.75.75 0 0 0 .75-.75V2.5h1A1.5 1.5 0 0 1 14.5 4v12a1.5 1.5 0 0 1-1.5 1.5H7A1.5 1.5 0 0 1 5.5 16V4A1.5 1.5 0 0 1 7 2.5h1Z"
clip-rule="evenodd"
/>
</svg>
<span>{gettext("Attendees settings")}</span>
</div>
<div class="space-y-2">
<.toggle_row
label={if @state.message_reaction_enabled, do: gettext("Disable reactions"), else: gettext("Enable reactions")}
checked={@state.message_reaction_enabled}
key={:message_reaction_enabled}
shortcut={if @create == nil, do: "D", else: nil}
show_shortcut={@show_shortcut}
/>
<.toggle_row
label={if @state.show_attendee_count, do: gettext("Hide attendee count"), else: gettext("Show attendee count")}
checked={@state.show_attendee_count}
key={:show_attendee_count}
shortcut={if @create == nil, do: "R", else: nil}
show_shortcut={@show_shortcut}
/>
</div>
</div>
</div>
"""
end
attr :label, :string, required: true
attr :checked, :boolean, required: true
attr :key, :atom, required: true
attr :shortcut, :string, default: nil
attr :disabled, :boolean, default: false
attr :show_shortcut, :boolean, default: true
defp toggle_row(assigns) do
~H"""
<div class={"flex items-center justify-between py-1 #{if @disabled, do: "opacity-50"}"}>
<span class="text-sm text-gray-700">{@label}</span>
<div class="flex items-center gap-x-2">
<code
:if={@show_shortcut && @shortcut}
class="px-1.5 py-0.5 text-xs font-medium text-gray-500 bg-gray-100 rounded"
>
{@shortcut}
</code>
<button
phx-click={ClaperWeb.Component.Input.checked(@checked, @key)}
disabled={@disabled}
phx-value-key={@key}
type="button"
class={"relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none disabled:cursor-not-allowed #{if @checked, do: "bg-primary-500", else: "bg-gray-200"}"}
role="switch"
aria-checked={@checked}
phx-key={@shortcut}
phx-window-keydown={if @shortcut && not @disabled, do: ClaperWeb.Component.Input.checked(@checked, @key)}
>
<span class={"pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out #{if @checked, do: "translate-x-5", else: "translate-x-0"}"}>
</span>
</button>
</div>
</div>
"""
end
end