From c00958e97e3b5b1cf729f5ff1cd572b20ed02ec2 Mon Sep 17 00:00:00 2001 From: Alex Lion Date: Mon, 9 Feb 2026 19:20:08 +0100 Subject: [PATCH] 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. --- lib/claper/presentations.ex | 35 + lib/claper/tasks/converter.ex | 74 +- .../live/event_live/manage.html.heex | 1429 +++++------------ .../event_live/manage_floating_action_bar.ex | 101 ++ .../manage_interaction_list_component.ex | 194 +++ .../manage_slide_preview_component.ex | 141 ++ .../manage_slide_sidebar_component.ex | 52 + .../event_live/manager_settings_component.ex | 527 ++---- 8 files changed, 1146 insertions(+), 1407 deletions(-) create mode 100644 lib/claper_web/live/event_live/manage_floating_action_bar.ex create mode 100644 lib/claper_web/live/event_live/manage_interaction_list_component.ex create mode 100644 lib/claper_web/live/event_live/manage_slide_preview_component.ex create mode 100644 lib/claper_web/live/event_live/manage_slide_sidebar_component.ex diff --git a/lib/claper/presentations.ex b/lib/claper/presentations.ex index c73361c..52840ff 100644 --- a/lib/claper/presentations.ex +++ b/lib/claper/presentations.ex @@ -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. diff --git a/lib/claper/tasks/converter.ex b/lib/claper/tasks/converter.ex index ff8baf7..ea2d8df 100644 --- a/lib/claper/tasks/converter.ex +++ b/lib/claper/tasks/converter.ex @@ -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 diff --git a/lib/claper_web/live/event_live/manage.html.heex b/lib/claper_web/live/event_live/manage.html.heex index 07303b1..532effe 100644 --- a/lib/claper_web/live/event_live/manage.html.heex +++ b/lib/claper_web/live/event_live/manage.html.heex @@ -5,7 +5,9 @@ phx-hook="Manager" data-max-page={@event.presentation_file.length} data-current-page={@state.position} + class="h-screen flex flex-col overflow-hidden bg-gray-50" > + + +
+ + + +
-

- {gettext("Poll")} -

-

- {gettext("Add poll to know opinion of your public.")} -

+

{gettext("Poll")}

+

{gettext("Add poll to know opinion of your public.")}

@@ -223,12 +222,8 @@
-

- {gettext("Form")} -

-

- {gettext("Add form to collect data from your public.")} -

+

{gettext("Form")}

+

{gettext("Add form to collect data from your public.")}

@@ -257,9 +252,7 @@

{gettext("Web content")}

-

- {gettext("Add a Youtube video or any web content.")} -

+

{gettext("Add a Youtube video or any web content.")}

@@ -281,17 +274,14 @@ >

{gettext("Quiz")}

-

- {gettext("Add a quiz to test knowledge.")} -

+

{gettext("Add a quiz to test knowledge.")}

@@ -317,7 +307,6 @@ /> <% end %> - <%= if @create=="form" do %>

@@ -338,7 +327,6 @@ />

<% end %> - <%= if @create == "embed" do %>

@@ -359,7 +347,6 @@ />

<% end %> - <%= if @create=="quiz" do %>

@@ -380,12 +367,9 @@ />

<% end %> - <%= if @create == "import" do %>
-

- {gettext("Select presentation")} -

+

{gettext("Select presentation")}

    <%= for event <- @events do %>
  • @@ -408,9 +392,7 @@ d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" /> - - {event.name} - + {event.name}
  • <% end %> @@ -421,9 +403,130 @@
+ +
+ +
+ + + + + +
+

+ {@event.name} +

+ + #{@event.code} + +
+
+ + + + + +
+ + + + +
+
+ +
-
-
- - - - - -
-

- {@event.name} -

-
-
- - - - {@event.code} -
-
- - - - - {@attendees_nb} - - - {link(gettext("Join"), - to: ~p"/e/#{@event.code}", - class: "text-xs text-primary-600 font-semibold text-sm ", - target: "_blank" - )} - -
-
-
-
-
-
- -
- - - - - -
-
-
-
- -
0, do: "Split", else: ""}"} - data-type="row" - data-gutter=".gutter" - id="base-layout" - class={"#{if @event.presentation_file.length > 0, do: "md:grid grid-rows-[0.3fr_10px_1fr] overflow-y-auto", else: ""}"} + + + + +
+
0} - id="slides-layout" - class="flex overflow-x-auto w-full md:h-full" + class="flex-shrink-0 h-1/3 border-b border-gray-200" > - + <.live_component + id="slide-preview" + module={ClaperWeb.EventLive.ManageSlidePreviewComponent} + presentation_file={@event.presentation_file} + current_position={@state.position} + total_slides={@event.presentation_file.length} + />
-
0} - class="hidden md:block gutter col-span-full cursor-row-resize z-20 row-2 bg-gray-50 text-center text-gray-300 text-sm leading-3" - > - ••• -
-
+ + +
+
#{gettext("This section contains all your interactions.")}

#{gettext("You can add interactions to your presentation slides.")}

"} + class="lg:w-1/3 flex-shrink-0 p-4 overflow-y-auto border-b lg:border-b-0 lg:border-r border-gray-200" + data-tg-order="2" + data-tg-title={gettext("Your interactions")} + data-tg-tour={"

#{gettext("This section contains all your interactions for the current slide.")}

"} data-tg-group="manage" > - -
-
- - - + <.live_component + id="interaction-list" + module={ClaperWeb.EventLive.ManageInteractionListComponent} + interactions={@interactions} + event_code={@event.code} + /> +
-

- 0}> - {gettext("This slide does not have any interactions.")} - - - {gettext("Create your first interaction.")} - -

- -
-
- <%= for interaction <- @interactions do %> -
-
- <%= case interaction do %> - <% %Claper.Polls.Poll{} -> %> -
-
-
- - - -
- {gettext("Poll")} -
- - - - - - -
- <% %Claper.Forms.Form{} -> %> -
-
-
- - - -
- {gettext("Form")} -
- <.link - class="p-2 rounded-sm text-xs font-medium text-center text-primary-500" - patch={~p"/e/#{@event.code}/manage/edit/form/#{interaction.id}"} - > - - - - -
- <% %Claper.Embeds.Embed{} -> %> -
-
-
- - - -
- - {gettext("Web content")} - -
- - - - - -
- <% %Claper.Quizzes.Quiz{} -> %> -
-
-
- - - -
- - {gettext("Quiz")} - -

- - - - - LTI AGS -

-
- - - - - -
- <% _ -> %> - - <% end %> -
- <%= case interaction do %> - <% %Claper.Embeds.Embed{} -> %> - <%= case(interaction.provider) do %> - <% "youtube" -> %> - - - - - - - <% "vimeo" -> %> - - - - <% "canva" -> %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - <% "googleslides" -> %> - - - - - - <% _ -> %> - <% end %> - <% _ -> %> - <% end %> - {interaction.title} -
-
-
- <%= if interaction.enabled do %> -
- -
- <% else %> - - <% end %> -
-
- <% end %> - -
-
-
- - - -
-
#{gettext("Here you'll find all interactions from your attendees. You can manage messages, pinned messages, and submitted forms.")}

#{gettext("Identify users by their unique avatars.")}

"} - data-tg-group="manage" - > + - <%= if @list_tab == :posts do %> -
- +
+ <%= if @list_tab == :posts do %> +
- - - -

- {gettext("Messages from attendees will appear here.")} -

-
-
0} - id="post-list" - class="overflow-y-auto pb-5 px-3" - phx-update="stream" - phx-hook="ScrollIntoDiv" - > - <.live_component - :for={{id, post} <- @streams.posts} - module={ClaperWeb.EventLive.ManageablePostComponent} - id={id} - event={@event} - post={post} - /> -
- <% end %> - - <%= if @list_tab == :questions do %> -
- - - - -

- {gettext("Questions will appear here.")} -

-
- -
0} class="overflow-y-auto max-h-full"> -
- - + + +

{gettext("Messages from attendees will appear here.")}

0} + id="post-list" + class="p-4 space-y-3" phx-update="stream" - data-use-parent="true" phx-hook="ScrollIntoDiv" > <.live_component - :for={{id, post} <- @streams.questions} + :for={{id, post} <- @streams.posts} module={ClaperWeb.EventLive.ManageablePostComponent} id={id} event={@event} post={post} />
-
- <% end %> + <% end %> - <%= if @list_tab == :pinned_posts do %> -
- +
- - + + + +

{gettext("Questions will appear here.")}

+
+
0} class="flex flex-col h-full"> +
+ + +
+
+ <.live_component + :for={{id, post} <- @streams.questions} + module={ClaperWeb.EventLive.ManageablePostComponent} + id={id} + event={@event} + post={post} + /> +
+
+ <% end %> -

- {gettext("Pinned messages will appear here.")} -

-
- -
+ <%= if @list_tab == :pinned_posts do %> +
+ + + +

{gettext("Pinned messages will appear here.")}

+
0} id="pinned-post-list" - class="overflow-y-auto pb-5 px-3" + class="p-4 space-y-3" phx-update="stream" phx-hook="ScrollIntoDiv" > @@ -1353,15 +816,16 @@ post={post} />
-
- <% end %> + <% end %> - <%= if @list_tab == :forms do %> - <%= if @form_submit_count == 0 do %> -
+ <%= if @list_tab == :forms do %> +
- -

- {gettext("Form submissions from attendees will appear here.")} -

+

{gettext("Form submissions will appear here.")}

- <% end %> -
-
-
-
- - {link(gettext("Delete"), - to: "#", - phx_click: "delete-form-submit", - phx_value_id: submission.id, - phx_value_event_id: @event.uuid, - data: [confirm: gettext("This cannot be undone, confirm ?")] - )} - -
- -

- - <%= gettext("Form") %> - : {submission.form.title} -

- -
- <%= if submission.attendee_identifier do %> - - <% else %> - - <% end %> - -
- <%= for res <- submission.response do %> -

- - {elem(res, 0)}: - - {elem(res, 1)} -

+
+
+
+
+ + {submission.form.title} + + +
+
+ <%= if submission.attendee_identifier do %> + + <% else %> + <% end %> +
+ <%= for res <- submission.response do %> +

+ {elem(res, 0)}: + {elem(res, 1)} +

+ <% end %> +
-
- <% end %> -
- - - -
-
+
+ + +
+ + + <.live_component + id="floating-action-bar" + module={ClaperWeb.EventLive.ManageFloatingActionBar} + event_code={@event.code} + />
diff --git a/lib/claper_web/live/event_live/manage_floating_action_bar.ex b/lib/claper_web/live/event_live/manage_floating_action_bar.ex new file mode 100644 index 0000000..770c1a6 --- /dev/null +++ b/lib/claper_web/live/event_live/manage_floating_action_bar.ex @@ -0,0 +1,101 @@ +defmodule ClaperWeb.EventLive.ManageFloatingActionBar do + use ClaperWeb, :live_component + + def render(assigns) do + ~H""" +
+
+ <.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" + > + + + + {gettext("Polls")} + + +
+ + <.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" + > + + + + {gettext("Forms")} + + +
+ + <.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" + > + + + + {gettext("Web Content")} + + +
+ + <.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" + > + + + + {gettext("Quiz")} + +
+
+ """ + end +end diff --git a/lib/claper_web/live/event_live/manage_interaction_list_component.ex b/lib/claper_web/live/event_live/manage_interaction_list_component.ex new file mode 100644 index 0000000..6aa5291 --- /dev/null +++ b/lib/claper_web/live/event_live/manage_interaction_list_component.ex @@ -0,0 +1,194 @@ +defmodule ClaperWeb.EventLive.ManageInteractionListComponent do + use ClaperWeb, :live_component + + def render(assigns) do + ~H""" +
+
+ + + + {gettext("Interactions")} +
+
+
+ + + +

+ {gettext("No interactions on this slide")} +

+
+ + <%= for interaction <- @interactions do %> +
+
+ <%= case interaction do %> + <% %Claper.Polls.Poll{} -> %> +
+ + + +
+
+

{gettext("Poll")}

+

{interaction.title}

+
+ <% %Claper.Forms.Form{} -> %> +
+ + + +
+
+

{gettext("Form")}

+

{interaction.title}

+
+ <% %Claper.Embeds.Embed{} -> %> +
+ + + +
+
+

{gettext("Web content")}

+

{interaction.title}

+
+ <% %Claper.Quizzes.Quiz{} -> %> +
+ + + +
+
+

{gettext("Quiz")}

+

{interaction.title}

+
+ <% _ -> %> + <% end %> +
+
+ <.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" + > + + + + + +
+
+ <% end %> +
+
+ """ + 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 diff --git a/lib/claper_web/live/event_live/manage_slide_preview_component.ex b/lib/claper_web/live/event_live/manage_slide_preview_component.ex new file mode 100644 index 0000000..e0f57fe --- /dev/null +++ b/lib/claper_web/live/event_live/manage_slide_preview_component.ex @@ -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""" +
+
+
+ + + + + {gettext("Preview")} +
+
+ + + + {@current_position + 1}/{@total_slides} +
+
+
+ + +
+ + + +
+ +
+ {"Slide +
+ {gettext("No slide available")} +
+
+ + + +
= @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" + > + + + +
+
+
+ """ + end +end diff --git a/lib/claper_web/live/event_live/manage_slide_sidebar_component.ex b/lib/claper_web/live/event_live/manage_slide_sidebar_component.ex new file mode 100644 index 0000000..28c6538 --- /dev/null +++ b/lib/claper_web/live/event_live/manage_slide_sidebar_component.ex @@ -0,0 +1,52 @@ +defmodule ClaperWeb.EventLive.ManageSlideSidebarComponent do + use ClaperWeb, :live_component + + alias Claper.Presentations + + def render(assigns) do + ~H""" +
+
+ + + + {gettext("Content")} +
+
+ +
+
+ """ + end +end diff --git a/lib/claper_web/live/event_live/manager_settings_component.ex b/lib/claper_web/live/event_live/manager_settings_component.ex index 69677aa..fe1a8ad 100644 --- a/lib/claper_web/live/event_live/manager_settings_component.ex +++ b/lib/claper_web/live/event_live/manager_settings_component.ex @@ -5,22 +5,24 @@ defmodule ClaperWeb.EventLive.ManagerSettingsComponent do assigns = assigns |> assign_new(:show_shortcut, fn -> true end) ~H""" -
-
-
+
+ +
+
- - {gettext("Interaction")} + {gettext("Interactions Options")}
+
+
<%= case @current_interaction do %> <% %Claper.Polls.Poll{} -> %> @@ -196,415 +198,122 @@ defmodule ClaperWeb.EventLive.ManagerSettingsComponent do

No settings available for this interaction

<% end %> -
-
-
-
-
- - - - {gettext("Presentation")} -
+ +
+ <.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} + /> +
-
- - - - - - - - -
- - {gettext("Show instructions to join")} - - - {gettext("Hide instructions to join")} - -
- - q - -
-
-
- -
- - - - - - - -
- {gettext("Show messages")} - {gettext("Hide messages")} -
- - w - -
-
-
- -
- - - - - - - -
- - {gettext("Show only pinned messages")} - - {gettext("Show all messages")} -
- - e - -
-
-
-
- - - - - - - -
- - {gettext("Show attendee count")} - - - {gettext("Hide attendee count")} - -
- - r - -
-
-
-
- -
-
- - - - - - {gettext("Attendees")} -
- -
- - - - - - - -
- {gettext("Enable messages")} - {gettext("Disable messages")} -
- - a - -
-
-
- -
- - - - - - - - -
- - {gettext("Allow anonymous messages")} - - - {gettext("Deny anonymous messages")} - -
- - s - -
-
-
- -
- - - - - - - - -
- - {gettext("Enable reactions")} - - - {gettext("Disable reactions")} - -
- - d - -
-
-
-
+ +
+
+ + + + + {gettext("Attendees settings")} +
+
+ <.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} + />
""" 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""" +
+ {@label} +
+ + {@shortcut} + + +
+
+ """ + end end