1
0
mirror of https://github.com/algora-io/tv.git synced 2025-01-05 01:20:24 +02:00

add subtitle crud (#2)

* init subs

* fix styling issues in core components

* update layout

* show video on subtitles page

* update spacing

* list subtitles by video

* misc

* reorder routes

* clean up module
This commit is contained in:
Zafer Cesur 2024-03-05 06:30:45 +03:00 committed by zafer
parent 31c6c1adcf
commit 1b10930700
10 changed files with 359 additions and 5 deletions

View File

@ -135,7 +135,7 @@ Hooks.VideoPlayer = {
},
});
window.addEventListener("js:play_video", ({ detail }) => {
const playVideo = ({ detail }) => {
const { player } = detail;
this.player.options({
techOrder: [player.type === "video/youtube" ? "youtube" : "html5"],
@ -145,7 +145,10 @@ Hooks.VideoPlayer = {
this.player.el().parentElement.classList.remove("hidden");
this.player.el().parentElement.classList.add("flex");
this.player.el().scrollIntoView();
});
};
window.addEventListener("js:play_video", playVideo);
this.handleEvent("js:play_video", playVideo);
this.handleEvent("join_chat", Chat.join);
},

View File

@ -7,9 +7,8 @@ defmodule Algora.Library do
import Ecto.Query, warn: false
import Ecto.Changeset
alias Algora.Accounts.User
alias Algora.Storage
alias Algora.{Repo, Accounts}
alias Algora.Library.{Channel, Video, Events}
alias Algora.{Repo, Accounts, Storage}
alias Algora.Library.{Channel, Video, Events, Subtitle}
@pubsub Algora.PubSub
@ -291,4 +290,31 @@ defmodule Algora.Library do
defp topic(user_id) when is_integer(user_id), do: "channel:#{user_id}"
def topic_livestreams(), do: "livestreams"
def list_subtitles(%Video{} = video) do
from(s in Subtitle, where: s.video_id == ^video.id, order_by: [asc: s.start])
|> Repo.replica().all()
end
def get_subtitle!(id), do: Repo.get!(Subtitle, id)
def create_subtitle(%Video{} = video, attrs \\ %{}) do
%Subtitle{video_id: video.id}
|> Subtitle.changeset(attrs)
|> Repo.insert()
end
def update_subtitle(%Subtitle{} = subtitle, attrs) do
subtitle
|> Subtitle.changeset(attrs)
|> Repo.update()
end
def delete_subtitle(%Subtitle{} = subtitle) do
Repo.delete(subtitle)
end
def change_subtitle(%Subtitle{} = subtitle, attrs \\ %{}) do
Subtitle.changeset(subtitle, attrs)
end
end

View File

@ -0,0 +1,21 @@
defmodule Algora.Library.Subtitle do
alias Algora.Library
use Ecto.Schema
import Ecto.Changeset
schema "subtitles" do
field :start, :float
field :end, :float
field :body, :string
belongs_to :video, Library.Video
timestamps()
end
@doc false
def changeset(subtitle, attrs) do
subtitle
|> cast(attrs, [:body, :start, :end])
|> validate_required([:body, :start, :end])
end
end

View File

@ -0,0 +1,92 @@
defmodule AlgoraWeb.SubtitleLive.FormComponent do
use AlgoraWeb, :live_component
alias Algora.Library
@impl true
def render(assigns) do
~H"""
<div>
<.header class="pb-6">
<%= @title %>
<:subtitle>Use this form to manage subtitle records in your database.</:subtitle>
</.header>
<.simple_form
for={@form}
id="subtitle-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save"
>
<.input field={@form[:body]} type="text" label="Body" />
<.input field={@form[:start]} type="number" label="Start" step="any" />
<.input field={@form[:end]} type="number" label="End" step="any" />
<:actions>
<.button phx-disable-with="Saving...">Save Subtitle</.button>
</:actions>
</.simple_form>
</div>
"""
end
@impl true
def update(%{subtitle: subtitle} = assigns, socket) do
changeset = Library.change_subtitle(subtitle)
{:ok,
socket
|> assign(assigns)
|> assign_form(changeset)}
end
@impl true
def handle_event("validate", %{"subtitle" => subtitle_params}, socket) do
changeset =
socket.assigns.subtitle
|> Library.change_subtitle(subtitle_params)
|> Map.put(:action, :validate)
{:noreply, assign_form(socket, changeset)}
end
def handle_event("save", %{"subtitle" => subtitle_params}, socket) do
save_subtitle(socket, socket.assigns.action, subtitle_params)
end
defp save_subtitle(socket, :edit, subtitle_params) do
case Library.update_subtitle(socket.assigns.subtitle, subtitle_params) do
{:ok, subtitle} ->
notify_parent({:saved, subtitle})
{:noreply,
socket
|> put_flash(:info, "Subtitle updated successfully")
|> push_patch(to: socket.assigns.patch)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign_form(socket, changeset)}
end
end
defp save_subtitle(socket, :new, subtitle_params) do
case Library.create_subtitle(socket.assigns.video, subtitle_params) do
{:ok, subtitle} ->
notify_parent({:saved, subtitle})
{:noreply,
socket
|> put_flash(:info, "Subtitle created successfully")
|> push_patch(to: socket.assigns.patch)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign_form(socket, changeset)}
end
end
defp assign_form(socket, %Ecto.Changeset{} = changeset) do
assign(socket, :form, to_form(changeset))
end
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
end

View File

@ -0,0 +1,65 @@
defmodule AlgoraWeb.SubtitleLive.Index do
use AlgoraWeb, :live_view
alias Algora.Library
alias Algora.Library.Subtitle
@impl true
def mount(%{"video_id" => video_id}, _session, socket) do
video = Library.get_video!(video_id)
if connected?(socket), do: send(self(), :play_video)
{:ok,
socket
|> assign(:video, video)
|> stream(:subtitles, Library.list_subtitles(video))}
end
@impl true
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
defp apply_action(socket, :edit, %{"id" => id}) do
socket
|> assign(:page_title, "Edit Subtitle")
|> assign(:subtitle, Library.get_subtitle!(id))
end
defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "New Subtitle")
|> assign(:subtitle, %Subtitle{})
end
defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Listing Subtitles")
|> assign(:subtitle, nil)
end
@impl true
def handle_info(:play_video, socket) do
video = socket.assigns.video
{:noreply,
socket
|> push_event("js:play_video", %{
detail: %{player: %{src: video.url, type: Library.player_type(video)}}
})}
end
@impl true
def handle_info({AlgoraWeb.SubtitleLive.FormComponent, {:saved, subtitle}}, socket) do
{:noreply, stream_insert(socket, :subtitles, subtitle)}
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
subtitle = Library.get_subtitle!(id)
{:ok, _} = Library.delete_subtitle(subtitle)
{:noreply, stream_delete(socket, :subtitles, subtitle)}
end
end

View File

@ -0,0 +1,51 @@
<.header class="p-4 sm:p-6 lg:p-8">
Subtitles
<:actions>
<.link patch={~p"/videos/#{@video.id}/subtitles/new"}>
<.button>New Subtitle</.button>
</.link>
</:actions>
</.header>
<.table
id="subtitles"
rows={@streams.subtitles}
row_click={
fn {_id, subtitle} -> JS.navigate(~p"/videos/#{@video.id}/subtitles/#{subtitle}") end
}
>
<:col :let={{_id, subtitle}} label="Start"><%= subtitle.start %></:col>
<:col :let={{_id, subtitle}} label="End"><%= subtitle.end %></:col>
<:col :let={{_id, subtitle}} label="Body"><%= subtitle.body %></:col>
<:action :let={{_id, subtitle}}>
<div class="sr-only">
<.link navigate={~p"/videos/#{@video.id}/subtitles/#{subtitle}"}>Show</.link>
</div>
<.link patch={~p"/videos/#{@video.id}/subtitles/#{subtitle}/edit"}>Edit</.link>
</:action>
<:action :let={{id, subtitle}}>
<.link
phx-click={JS.push("delete", value: %{id: subtitle.id}) |> hide("##{id}")}
data-confirm="Are you sure?"
>
Delete
</.link>
</:action>
</.table>
<.modal
:if={@live_action in [:new, :edit]}
id="subtitle-modal"
show
on_cancel={JS.navigate(~p"/videos/#{@video.id}/subtitles")}
>
<.live_component
module={AlgoraWeb.SubtitleLive.FormComponent}
id={@subtitle.id || :new}
title={@page_title}
action={@live_action}
subtitle={@subtitle}
video={@video}
patch={~p"/videos/#{@video.id}/subtitles"}
/>
</.modal>

View File

@ -0,0 +1,36 @@
defmodule AlgoraWeb.SubtitleLive.Show do
use AlgoraWeb, :live_view
alias Algora.Library
@impl true
def mount(%{"video_id" => video_id}, _session, socket) do
video = Library.get_video!(video_id)
if connected?(socket), do: send(self(), :play_video)
{:ok, socket |> assign(:video, video)}
end
@impl true
def handle_info(:play_video, socket) do
video = socket.assigns.video
{:noreply,
socket
|> push_event("js:play_video", %{
detail: %{player: %{src: video.url, type: Library.player_type(video)}}
})}
end
@impl true
def handle_params(%{"id" => id}, _, socket) do
{:noreply,
socket
|> assign(:page_title, page_title(socket.assigns.live_action))
|> assign(:subtitle, Library.get_subtitle!(id))}
end
defp page_title(:show), do: "Show Subtitle"
defp page_title(:edit), do: "Edit Subtitle"
end

View File

@ -0,0 +1,38 @@
<div class="px-4 sm:px-6 lg:px-8">
<.header class="py-4 sm:py-6 lg:py-8">
Subtitle <%= @subtitle.id %>
<:subtitle>This is a subtitle record from your database.</:subtitle>
<:actions>
<.link
patch={~p"/videos/#{@video.id}/subtitles/#{@subtitle}/show/edit"}
phx-click={JS.push_focus()}
>
<.button>Edit subtitle</.button>
</.link>
</:actions>
</.header>
<.list>
<:item title="Body"><%= @subtitle.body %></:item>
<:item title="Start"><%= @subtitle.start %></:item>
<:item title="End"><%= @subtitle.end %></:item>
</.list>
<.back navigate={~p"/videos/#{@video.id}/subtitles"}>Back to subtitles</.back>
<.modal
:if={@live_action == :edit}
id="subtitle-modal"
show
on_cancel={JS.patch(~p"/videos/#{@video.id}/subtitles/#{@subtitle}")}
>
<.live_component
module={AlgoraWeb.SubtitleLive.FormComponent}
id={@subtitle.id}
title={@page_title}
action={@live_action}
subtitle={@subtitle}
patch={~p"/videos/#{@video.id}/subtitles/#{@subtitle}"}
/>
</.modal>
</div>

View File

@ -50,6 +50,12 @@ defmodule AlgoraWeb.Router do
on_mount: [{AlgoraWeb.UserAuth, :ensure_authenticated}, AlgoraWeb.Nav] do
live "/channel/settings", SettingsLive, :edit
live "/:channel_handle/stream", ChannelLive, :stream
live "/videos/:video_id/subtitles", SubtitleLive.Index, :index
live "/videos/:video_id/subtitles/new", SubtitleLive.Index, :new
live "/videos/:video_id/subtitles/:id", SubtitleLive.Show, :show
live "/videos/:video_id/subtitles/:id/edit", SubtitleLive.Index, :edit
live "/videos/:video_id/subtitles/:id/show/edit", SubtitleLive.Show, :edit
end
live_session :default, on_mount: [{AlgoraWeb.UserAuth, :current_user}, AlgoraWeb.Nav] do

View File

@ -0,0 +1,16 @@
defmodule Algora.Repo.Migrations.CreateSubtitles do
use Ecto.Migration
def change do
create table(:subtitles) do
add :body, :text
add :start, :float
add :end, :float
add :video_id, references(:videos, on_delete: :nothing), null: false
timestamps()
end
create index(:subtitles, [:video_id])
end
end