You've already forked algora-tv
							
							
				mirror of
				https://github.com/algora-io/tv.git
				synced 2025-10-30 23:07:56 +02:00 
			
		
		
		
	add transcript tab (#4)
* init side panel * reorganize stuff * delete unused components * fix some ids * more fixes * conditionally render transcript tab * reorder some code * delete redundant line * remove unused alias
This commit is contained in:
		| @@ -7,9 +7,9 @@ const init = () => { | ||||
|   socket.connect(); | ||||
|  | ||||
|   const main = document.querySelector("body"); | ||||
|   const sidePanel = document.querySelector("#video-side-panel"); | ||||
|  | ||||
|   let channel; | ||||
|   let chatBox; | ||||
|   let chatInput; | ||||
|   let chatMessages; | ||||
|   let handleSend; | ||||
| @@ -20,9 +20,9 @@ const init = () => { | ||||
|       chatInput.value = ""; | ||||
|       chatInput.removeEventListener("keypress", handleSend); | ||||
|     } | ||||
|     chatBox.classList.add("lg:w-0"); | ||||
|     chatBox.classList.remove("lg:w-[24rem]"); | ||||
|     chatBox.classList.remove("lg:flex"); | ||||
|     sidePanel.classList.add("lg:w-0"); | ||||
|     sidePanel.classList.remove("lg:w-[24rem]"); | ||||
|     sidePanel.classList.remove("lg:flex"); | ||||
|     main.classList.remove("lg:mr-[24rem]"); | ||||
|   }; | ||||
|  | ||||
| @@ -31,15 +31,13 @@ const init = () => { | ||||
|       leave(channel); | ||||
|     } | ||||
|  | ||||
|     player = player; | ||||
|     channel = socket.channel(`room:${id}`, {}); | ||||
|     chatBox = document.querySelector("#chat-box"); | ||||
|     chatInput = document.querySelector("#chat-input"); | ||||
|     chatMessages = document.querySelector("#chat-messages"); | ||||
|     chatMessages.scrollTop = chatMessages.scrollHeight; | ||||
|     chatBox.classList.add("lg:w-[24rem]"); | ||||
|     chatBox.classList.add("lg:flex"); | ||||
|     chatBox.classList.remove("lg:w-0"); | ||||
|     sidePanel.classList.add("lg:w-[24rem]"); | ||||
|     sidePanel.classList.add("lg:flex"); | ||||
|     sidePanel.classList.remove("lg:w-0"); | ||||
|     main.classList.add("lg:mr-[24rem]"); | ||||
|  | ||||
|     handleSend = (event) => { | ||||
|   | ||||
| @@ -145,6 +145,10 @@ defmodule Algora.Library do | ||||
|     |> Enum.map_join(":", fn count -> String.pad_leading("#{count}", 2, ["0"]) end) | ||||
|   end | ||||
|  | ||||
|   def to_hhmmss(duration) when is_float(duration) do | ||||
|     to_hhmmss(trunc(duration)) | ||||
|   end | ||||
|  | ||||
|   def unsubscribe_to_channel(%Channel{} = channel) do | ||||
|     Phoenix.PubSub.unsubscribe(@pubsub, topic(channel.user_id)) | ||||
|   end | ||||
|   | ||||
| @@ -79,7 +79,7 @@ defmodule AlgoraWeb.CoreComponents do | ||||
|       id={@id} | ||||
|       class="cursor-pointer truncate" | ||||
|       phx-click={ | ||||
|         JS.push("join", value: %{video_id: @video.id}, target: "#chat-box") | ||||
|         JS.push("show", value: %{video_id: @video.id}, target: "#side-panel") | ||||
|         |> JS.dispatch("js:play_video", | ||||
|           to: "#video-player", | ||||
|           detail: %{player: %{src: @video.url, type: Library.player_type(@video)}} | ||||
| @@ -117,7 +117,7 @@ defmodule AlgoraWeb.CoreComponents do | ||||
|       id={@id} | ||||
|       class="cursor-pointer truncate" | ||||
|       phx-click={ | ||||
|         JS.push("join", value: %{video_id: @video.id}, target: "#chat-box") | ||||
|         JS.push("show", value: %{video_id: @video.id}, target: "#side-panel") | ||||
|         |> JS.dispatch("js:play_video", | ||||
|           to: "#video-player", | ||||
|           detail: %{player: %{src: @video.url, type: Library.player_type(@video)}} | ||||
|   | ||||
| @@ -126,7 +126,9 @@ | ||||
|  | ||||
|   <.live_component module={AlgoraWeb.LayoutComponent} id="layout" /> | ||||
|  | ||||
|   <%= live_render(@socket, AlgoraWeb.ChatLive, id: "chat", session: %{}, sticky: true) %> | ||||
|   <aside id="video-side-panel" class="hidden fixed top-[64px] right-0 w-0 pr-4"> | ||||
|     <%= live_render(@socket, AlgoraWeb.SidePanelLive, id: "side-panel", session: %{}) %> | ||||
|   </aside> | ||||
|   <main class="flex-1 relative z-0 overflow-y-auto focus:outline-none"> | ||||
|     <%= live_render(@socket, AlgoraWeb.PlayerLive, id: "player", session: %{}, sticky: true) %> | ||||
|     <%= @inner_content %> | ||||
|   | ||||
| @@ -1,66 +0,0 @@ | ||||
| defmodule AlgoraWeb.ChatLive do | ||||
|   alias Algora.Chat.Message | ||||
|   alias Algora.{Library, Chat} | ||||
|   alias Algora.Library.Video | ||||
|   use AlgoraWeb, {:live_view, container: {:div, []}} | ||||
|  | ||||
|   on_mount {AlgoraWeb.UserAuth, :current_user} | ||||
|  | ||||
|   defp system_message?(%Message{} = message) do | ||||
|     message.sender_handle == "algora" | ||||
|   end | ||||
|  | ||||
|   def render(assigns) do | ||||
|     ~H""" | ||||
|     <aside id="chat-box" class="hidden fixed top-[64px] right-0 w-0 flex-col pr-4"> | ||||
|       <div class="p-4 bg-gray-800/40 backdrop-blur-xl rounded-2xl shadow-inner shadow-white/[10%] border border-white/[15%]"> | ||||
|         <div class="pb-2 text-center text-gray-400 text-xs font-medium uppercase tracking-wide"> | ||||
|           Chat | ||||
|         </div> | ||||
|         <div | ||||
|           id="chat-messages" | ||||
|           class="text-sm break-words flex-1 overflow-y-auto h-[calc(100vh-11rem)]" | ||||
|         > | ||||
|           <div :for={message <- @messages} id={"message-#{message.id}"}> | ||||
|             <span class={"font-semibold #{if(system_message?(message), do: "text-emerald-400", else: "text-indigo-400")}"}> | ||||
|               <%= message.sender_handle %>: | ||||
|             </span> | ||||
|             <span class="font-medium text-gray-100"> | ||||
|               <%= message.body %> | ||||
|             </span> | ||||
|           </div> | ||||
|         </div> | ||||
|         <input | ||||
|           :if={@current_user} | ||||
|           id="chat-input" | ||||
|           placeholder="Send a message" | ||||
|           disabled={@current_user == nil} | ||||
|           class="mt-2 bg-gray-950 h-[30px] text-white focus:outline-none focus:ring-purple-400 block w-full min-w-0 rounded-md sm:text-sm ring-1 ring-gray-600 px-2" | ||||
|         /> | ||||
|         <a | ||||
|           :if={!@current_user} | ||||
|           href={Algora.Github.authorize_url()} | ||||
|           class="mt-2 w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-purple-600 hover:bg-purple-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-400" | ||||
|         > | ||||
|           Sign in to chat | ||||
|         </a> | ||||
|       </div> | ||||
|     </aside> | ||||
|     """ | ||||
|   end | ||||
|  | ||||
|   def mount(_params, _session, socket) do | ||||
|     {:ok, socket, layout: false, temporary_assigns: [messages: []]} | ||||
|   end | ||||
|  | ||||
|   def handle_info({Library, _}, socket), do: {:noreply, socket} | ||||
|  | ||||
|   def handle_event("join", %{"video_id" => video_id}, socket) do | ||||
|     socket = | ||||
|       socket | ||||
|       |> assign(messages: Chat.list_messages(%Video{id: video_id})) | ||||
|       |> push_event("join_chat", %{id: video_id}) | ||||
|  | ||||
|     {:noreply, socket} | ||||
|   end | ||||
| end | ||||
							
								
								
									
										127
									
								
								lib/algora_web/live/side_panel_live.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								lib/algora_web/live/side_panel_live.ex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | ||||
| defmodule AlgoraWeb.SidePanelLive do | ||||
|   use AlgoraWeb, {:live_view, container: {:div, class: "flex-1"}} | ||||
|   alias Algora.{Chat, Library} | ||||
|  | ||||
|   on_mount {AlgoraWeb.UserAuth, :current_user} | ||||
|  | ||||
|   def render(assigns) do | ||||
|     tabs = | ||||
|       [:chat] | ||||
|       |> append_if(length(assigns.subtitles) > 0, :transcript) | ||||
|  | ||||
|     assigns = assigns |> assign(:tabs, tabs) | ||||
|  | ||||
|     ~H""" | ||||
|     <div class="p-4 bg-gray-800/40 w-[23rem] backdrop-blur-xl rounded-2xl shadow-inner shadow-white/[10%] border border-white/[15%]"> | ||||
|       <div> | ||||
|         <ul class="pb-2 flex items-center justify-center gap-2 mx-auto text-gray-400"> | ||||
|           <li :for={{tab, i} <- Enum.with_index(@tabs)}> | ||||
|             <button | ||||
|               id={"side-panel-tab-#{tab}"} | ||||
|               class={[ | ||||
|                 "text-xs font-semibold uppercase tracking-wide", | ||||
|                 i == 0 && "active-tab text-white pointer-events-none" | ||||
|               ]} | ||||
|               phx-click={ | ||||
|                 set_active_tab("#side-panel-tab-#{tab}") | ||||
|                 |> set_active_content("#side-panel-content-#{tab}") | ||||
|               } | ||||
|             > | ||||
|               <%= tab %> | ||||
|             </button> | ||||
|           </li> | ||||
|         </ul> | ||||
|       </div> | ||||
|  | ||||
|       <div> | ||||
|         <div | ||||
|           :for={{tab, i} <- Enum.with_index(@tabs)} | ||||
|           id={"side-panel-content-#{tab}"} | ||||
|           class={["side-panel-content", i != 0 && "hidden"]} | ||||
|         > | ||||
|           <div | ||||
|             :if={tab == :transcript} | ||||
|             id="transcript-subtitles" | ||||
|             class="text-sm break-words flex-1 overflow-y-auto h-[calc(100vh-11rem)]" | ||||
|           > | ||||
|             <div :for={subtitle <- @subtitles} id={"subtitle-#{subtitle.id}"}> | ||||
|               <span class="font-semibold text-indigo-400"> | ||||
|                 <%= Library.to_hhmmss(subtitle.start) %> | ||||
|               </span> | ||||
|               <span class="font-medium text-gray-100"> | ||||
|                 <%= subtitle.body %> | ||||
|               </span> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|           <div :if={tab == :chat}> | ||||
|             <div | ||||
|               id="chat-messages" | ||||
|               class="text-sm break-words flex-1 overflow-y-auto h-[calc(100vh-11rem)]" | ||||
|             > | ||||
|               <div :for={message <- @messages} id={"message-#{message.id}"}> | ||||
|                 <span class={"font-semibold #{if(system_message?(message), do: "text-emerald-400", else: "text-indigo-400")}"}> | ||||
|                   <%= message.sender_handle %>: | ||||
|                 </span> | ||||
|                 <span class="font-medium text-gray-100"> | ||||
|                   <%= message.body %> | ||||
|                 </span> | ||||
|               </div> | ||||
|             </div> | ||||
|             <input | ||||
|               :if={@current_user} | ||||
|               id="chat-input" | ||||
|               placeholder="Send a message" | ||||
|               disabled={@current_user == nil} | ||||
|               class="mt-2 bg-gray-950 h-[30px] text-white focus:outline-none focus:ring-purple-400 block w-full min-w-0 rounded-md sm:text-sm ring-1 ring-gray-600 px-2" | ||||
|             /> | ||||
|             <a | ||||
|               :if={!@current_user} | ||||
|               href={Algora.Github.authorize_url()} | ||||
|               class="mt-2 w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-purple-600 hover:bg-purple-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-400" | ||||
|             > | ||||
|               Sign in to chat | ||||
|             </a> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     """ | ||||
|   end | ||||
|  | ||||
|   def mount(_params, _session, socket) do | ||||
|     {:ok, socket, temporary_assigns: [subtitles: [], messages: []]} | ||||
|   end | ||||
|  | ||||
|   def handle_event("show", %{"video_id" => video_id}, socket) do | ||||
|     socket = | ||||
|       socket | ||||
|       |> assign(subtitles: Library.list_subtitles(%Library.Video{id: video_id})) | ||||
|       |> assign(messages: Chat.list_messages(%Library.Video{id: video_id})) | ||||
|       |> push_event("join_chat", %{id: video_id}) | ||||
|  | ||||
|     {:noreply, socket} | ||||
|   end | ||||
|  | ||||
|   defp set_active_content(js \\ %JS{}, to) do | ||||
|     js | ||||
|     |> JS.hide(to: ".side-panel-content") | ||||
|     |> JS.show(to: to) | ||||
|   end | ||||
|  | ||||
|   defp set_active_tab(js \\ %JS{}, tab) do | ||||
|     js | ||||
|     |> JS.remove_class("active-tab text-white pointer-events-none", | ||||
|       to: "#video-side-panel .active-tab" | ||||
|     ) | ||||
|     |> JS.add_class("active-tab text-white pointer-events-none", to: tab) | ||||
|   end | ||||
|  | ||||
|   defp system_message?(%Chat.Message{} = message) do | ||||
|     message.sender_handle == "algora" | ||||
|   end | ||||
|  | ||||
|   defp append_if(list, cond, extra) do | ||||
|     if cond, do: list ++ [extra], else: list | ||||
|   end | ||||
| end | ||||
		Reference in New Issue
	
	Block a user