mirror of
https://github.com/fly-apps/live_beats.git
synced 2024-11-24 08:51:00 +00:00
WIP
This commit is contained in:
parent
b5edfdd9cd
commit
7ea456d49f
9 changed files with 155 additions and 34 deletions
|
@ -1,6 +1,6 @@
|
|||
import "phoenix_html"
|
||||
import {Socket} from "phoenix"
|
||||
import {LiveSocket} from "phoenix_live_view"
|
||||
import {LiveSocket} from "./phoenix_live_view"
|
||||
import topbar from "../vendor/topbar"
|
||||
|
||||
let nowSeconds = () => Math.round(Date.now() / 1000)
|
||||
|
@ -13,6 +13,10 @@ let execJS = (selector, attr) => {
|
|||
|
||||
let Hooks = {}
|
||||
|
||||
Hooks.Test = {
|
||||
mounted(){ console.log("mounted test") },
|
||||
destroyed(){ console.log("destroyed test") }
|
||||
}
|
||||
Hooks.Flash = {
|
||||
mounted(){
|
||||
let hide = () => liveSocket.execJS(this.el, this.el.getAttribute("phx-click"))
|
||||
|
@ -184,12 +188,12 @@ Hooks.AudioPlayer = {
|
|||
|
||||
Hooks.Ping = {
|
||||
mounted(){
|
||||
this.handleEvent("pong", () => {
|
||||
let rtt = Date.now() - this.nowMs
|
||||
this.el.innerText = `ping: ${rtt}ms`
|
||||
this.timer = setTimeout(() => this.ping(rtt), 1000)
|
||||
})
|
||||
this.ping(null)
|
||||
// this.handleEvent("pong", () => {
|
||||
// let rtt = Date.now() - this.nowMs
|
||||
// this.el.innerText = `ping: ${rtt}ms`
|
||||
// this.timer = setTimeout(() => this.ping(rtt), 1000)
|
||||
// })
|
||||
// this.ping(null)
|
||||
},
|
||||
reconnected(){
|
||||
clearTimeout(this.timer)
|
||||
|
@ -303,7 +307,10 @@ window.addEventListener("phx:page-loading-stop", info => topbar.hide())
|
|||
// Accessible routing
|
||||
window.addEventListener("phx:page-loading-stop", e => routeUpdated(e.detail))
|
||||
|
||||
window.addEventListener("js:exec", e => e.target[e.detail.call](...e.detail.args))
|
||||
window.addEventListener("js:exec", e => {
|
||||
console.log(e.detail)
|
||||
e.target[e.detail.call](...e.detail.args)
|
||||
})
|
||||
window.addEventListener("js:focus", e => {
|
||||
let parent = document.querySelector(e.detail.parent)
|
||||
if(parent && isVisible(parent)){ e.target.focus() }
|
||||
|
|
|
@ -240,6 +240,14 @@ defmodule LiveBeats.MediaLibrary do
|
|||
Repo.replica().all(Genre, order_by: [asc: :title])
|
||||
end
|
||||
|
||||
def suggest_genres(like) do
|
||||
Repo.replica().all(from g in Genre, where: ilike(g.title, ^"%#{like}%"))
|
||||
end
|
||||
|
||||
def get_genre!(id) do
|
||||
Repo.replica().get!(Genre, id)
|
||||
end
|
||||
|
||||
def list_profile_songs(%Profile{} = profile, limit \\ 100) do
|
||||
from(s in Song, where: s.user_id == ^profile.user_id, limit: ^limit)
|
||||
|> order_by_playlist(:asc)
|
||||
|
|
|
@ -348,7 +348,10 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
|> assign_rest(~w(id show patch navigate on_cancel on_confirm title confirm cancel)a)
|
||||
|
||||
~H"""
|
||||
<div id={@id} class={"fixed z-10 inset-0 overflow-y-auto #{if @show, do: "fade-in", else: "hidden"}"} {@rest}>
|
||||
<div
|
||||
id={@id}
|
||||
phx-remove={hide_modal(@id)}
|
||||
class={"fixed z-10 inset-0 overflow-y-auto #{if @show, do: "fade-in", else: "hidden"}"} {@rest}>
|
||||
<.focus_wrap id={"#{@id}-focus-wrap"} content={"##{@id}-container"}>
|
||||
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0" aria-labelledby={"#{@id}-title"} aria-describedby={"#{@id}-description"} role="dialog" aria-modal="true" tabindex="0">
|
||||
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
|
||||
|
|
|
@ -1,7 +1,99 @@
|
|||
defmodule LiveBeatsWeb.AutocompleteInput do
|
||||
use LiveBeatsWeb, :live_component
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div>
|
||||
<label for="combobox" class="block text-sm font-medium text-gray-700">Genre</label>
|
||||
<div class="relative mt-1">
|
||||
<input type="hidden" name={@name} value={@value}/>
|
||||
<%= if @selected_option do %>
|
||||
<span class="inline-flex items-center px-3 py-0.5 rounded-full text-md font-medium bg-indigo-100 text-indigo-800">
|
||||
<svg class="-ml-1 mr-1.5 h-2 w-2 text-indigo-400" fill="currentColor" viewBox="0 0 8 8">
|
||||
<circle cx="4" cy="4" r="3" />
|
||||
</svg>
|
||||
<%= @title_func.(@selected_option) %>
|
||||
</span>
|
||||
<.link phx-click="change" phx-target={@myself} class="text-indigo-500 text-sm ml-2">change</.link>
|
||||
<% else %>
|
||||
<input
|
||||
id={"#{@id}-combobox"}
|
||||
name="ac_value"
|
||||
phx-change="suggest"
|
||||
phx-target={@myself}
|
||||
type="text"
|
||||
class="w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm"
|
||||
role="combobox"
|
||||
aria-controls="options"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<button type="button" class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
|
||||
<.icon name={:selector}/>
|
||||
</button>
|
||||
|
||||
<ul id={"#{@id}-options"}
|
||||
class={"#{if @options == [], do: "hidden"} absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"}
|
||||
role="listbox"
|
||||
>
|
||||
<%= for option <- @options do %>
|
||||
<li
|
||||
phx-click={JS.push("select", value: %{id: option.id}, target: @myself)}
|
||||
class="group relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900 hover:text-white hover:bg-indigo-600"
|
||||
role="option"
|
||||
tabindex="-1"
|
||||
>
|
||||
<%= if @selected_option && @selected_option.id == option.id do %>
|
||||
<span class="block truncate font-semibold"><%= @title_func.(option) %></span>
|
||||
<span class="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600 group-hover:text-white">
|
||||
<.icon name={:check}/>
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="block truncate"><%= @title_func.(option) %></span>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def update(assigns, socket) do
|
||||
%{id: id, suggest: suggest, get: get, title: title_func, name: name, value: value_func} =
|
||||
assigns
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(id: "ac-#{id}", name: name, value: nil)
|
||||
|> assign(title_func: title_func)
|
||||
|> assign(value_func: value_func)
|
||||
|> assign(suggest_func: suggest)
|
||||
|> assign(get_func: get)
|
||||
|> assign_new(:selected_option, fn -> nil end)
|
||||
|> assign_new(:options, fn -> [] end)}
|
||||
end
|
||||
|
||||
def handle_event("select", %{"id" => id}, socket) do
|
||||
selected = socket.assigns.get_func.(id)
|
||||
value = socket.assigns.value_func.(selected)
|
||||
{:noreply, assign(socket, selected_option: selected, value: value)}
|
||||
end
|
||||
|
||||
def handle_event("change", _, socket) do
|
||||
{:noreply, assign(socket, selected_option: nil, value: nil)}
|
||||
end
|
||||
|
||||
def handle_event("suggest", %{"ac_value" => str}, socket) do
|
||||
{:noreply, assign(socket, :options, socket.assigns.suggest_func.(str))}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule LiveBeatsWeb.ProfileLive.SongEntryComponent do
|
||||
use LiveBeatsWeb, :live_component
|
||||
|
||||
alias LiveBeats.MP3Stat
|
||||
alias LiveBeats.{MediaLibrary, MP3Stat}
|
||||
alias LiveBeatsWeb.AutocompleteInput
|
||||
|
||||
def send_progress(%Phoenix.LiveView.UploadEntry{} = entry) do
|
||||
send_update(__MODULE__, id: entry.ref, progress: entry.progress)
|
||||
|
@ -45,6 +137,17 @@ defmodule LiveBeatsWeb.ProfileLive.SongEntryComponent do
|
|||
<div class="col-span-full sm:grid sm:grid-cols-2 sm:gap-2 sm:items-start">
|
||||
<.error input_name={"songs[#{@ref}][attribution]"} field={:attribution} errors={@errors} class="-mt-1"/>
|
||||
</div>
|
||||
|
||||
<.live_component
|
||||
module={AutocompleteInput}
|
||||
id={@ref}
|
||||
title={fn genre -> genre.title end}
|
||||
get={&MediaLibrary.get_genre!/1}
|
||||
suggest={&MediaLibrary.suggest_genres/1}
|
||||
name={"songs[#{@ref}][genre_id]"}
|
||||
value={fn genre -> genre.id end}
|
||||
/>
|
||||
|
||||
<div
|
||||
role="progressbar"
|
||||
aria-valuemin="0"
|
||||
|
@ -66,11 +169,22 @@ defmodule LiveBeatsWeb.ProfileLive.SongEntryComponent do
|
|||
socket
|
||||
|> assign(ref: id)
|
||||
|> assign(index: index)
|
||||
|> assign(options: [])
|
||||
|> assign(:errors, changeset.errors)
|
||||
|> assign(title: Ecto.Changeset.get_field(changeset, :title))
|
||||
|> assign(artist: Ecto.Changeset.get_field(changeset, :artist))
|
||||
|> assign(duration: Ecto.Changeset.get_field(changeset, :duration))
|
||||
|> assign(attribution: Ecto.Changeset.get_field(changeset, :attribution))
|
||||
|> assign_new(:progress, fn -> 0 end)}
|
||||
|> assign_new(:progress, fn -> 0 end)
|
||||
|> assign_new(:selected_option, fn -> nil end)}
|
||||
end
|
||||
|
||||
def handle_event(
|
||||
"artist_changed",
|
||||
%{"_target" => ["songs", idx, "artist"], "songs" => song_params},
|
||||
socket
|
||||
) do
|
||||
IO.inspect(Map.fetch!(song_params, idx))
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -44,6 +44,7 @@ defmodule LiveBeatsWeb.ProfileLive.UploadFormComponent do
|
|||
end
|
||||
|
||||
def handle_event("validate", %{"songs" => params, "_target" => ["songs", _, _]}, socket) do
|
||||
IO.inspect(params)
|
||||
{:noreply, apply_params(socket, params, :validate)}
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div>
|
||||
<p class="inline text-gray-500 text-sm">(songs expire every six hours)</p>
|
||||
<p class="inline text-gray-500 text-sm" id="test" phx-hook="Test">(songs expire every six hours)</p>
|
||||
|
||||
<.form
|
||||
for={:songs}
|
||||
|
@ -12,12 +12,12 @@
|
|||
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
|
||||
<div class="space-y-2 sm:space-y-2">
|
||||
<%= for {{ref, changeset}, i} <- Enum.with_index(@changesets) do %>
|
||||
<.live_component id={ref} module={SongEntryComponent} changeset={changeset} index={i} />
|
||||
<.live_component id={ref} module={SongEntryComponent} changeset={changeset} index={i}/>
|
||||
<% end %>
|
||||
|
||||
<!-- upload -->
|
||||
<div class="sm:grid sm:border-t sm:border-gray-200 sm:pt-5">
|
||||
<div class="mt-1 sm:mt-0" phx-drop-target={@uploads.mp3.ref}>
|
||||
<div class="mt-1 sm:mt-0" phx-drop-target={@uploads.mp3.ref} phx-click={JS.dispatch("click", to: "##{@uploads.mp3.ref}", bubbles: false)}>
|
||||
<%= if Enum.any?(@error_messages) do %>
|
||||
<div class="rounded-md bg-red-50 p-4 mb-2">
|
||||
<div class="flex">
|
||||
|
@ -47,7 +47,7 @@
|
|||
</svg>
|
||||
<div class="flex text-sm text-gray-600">
|
||||
<label for="file-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
|
||||
<span phx-click={js_exec("##{@uploads.mp3.ref}", "click", [])}>Upload files</span>
|
||||
<span>Upload files</span>
|
||||
<%= live_file_input @uploads.mp3, class: "sr-only", tabindex: "0" %>
|
||||
</label>
|
||||
<p class="pl-1">or drag and drop</p>
|
||||
|
|
3
mix.exs
3
mix.exs
|
@ -40,7 +40,8 @@ defmodule LiveBeats.MixProject do
|
|||
{:postgrex, ">= 0.0.0"},
|
||||
{:phoenix_html, "~> 3.0"},
|
||||
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
||||
{:phoenix_live_view, "~> 0.17.6"},
|
||||
# {:phoenix_live_view, "~> 0.17.6"},
|
||||
{:phoenix_live_view, path: "~/oss/phoenix_live_view", override: true},
|
||||
{:floki, ">= 0.30.0", only: :test},
|
||||
{:phoenix_live_dashboard, "~> 0.6"},
|
||||
{:esbuild, "~> 0.2", runtime: Mix.env() == :dev},
|
||||
|
|
4
mix.lock
4
mix.lock
|
@ -30,7 +30,7 @@
|
|||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.17.6", "3665f3ec426ac8d681cd7753ad4c85d2d247094dc4dc6add80dd6e3026045389", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "62f06d4bbfc4dc5595070bc338119ab08e8e67a011e2923f9366419622149b9c"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
|
||||
"phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"},
|
||||
"plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
|
||||
"plug": {:hex, :plug, "1.13.5", "a86bdb8acaa5901034d80502189babe2b28e79a2fd866da01d37f8c84385b8d2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "77cfab9cadcedc49dfb9be0bf2fc006cead1410ea9fd81c433b3d3eb47fe474f"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
|
||||
"porcelain": {:hex, :porcelain, "2.0.3", "2d77b17d1f21fed875b8c5ecba72a01533db2013bd2e5e62c6d286c029150fdc", [:mix], [], "hexpm", "dc996ab8fadbc09912c787c7ab8673065e50ea1a6245177b0c24569013d23620"},
|
||||
|
@ -39,7 +39,7 @@
|
|||
"rustler": {:hex, :rustler, "0.22.2", "f92d6dba71bef6fe5f0d955649cb071127adc92f32a78890e8fa9939e59a1b41", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.5.2", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "56b129141e86d60a2d670af9a2b55a9071e10933ef593034565af77e84655118"},
|
||||
"swoosh": {:hex, :swoosh, "1.5.0", "2be4cfc1be10f2203d1854c85b18d8c7be0321445a782efd53ef0b2b88f03ce4", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b53891359e3ddca263ece784051243de84c9244c421a0dee1bff1d52fc5ca420"},
|
||||
"tailwind": {:hex, :tailwind, "0.1.3", "117ada41ee06343d77269ee5f5ef58ef93ddf4d596a0486db7be9d6157487f9e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "1fe9ecb50ae521a328a787f22b60fb0e14d29c8d2a5983186962fdbd8162608a"},
|
||||
"telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"},
|
||||
"telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
|
||||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
|
||||
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
|
||||
"toml": {:hex, :toml, "0.5.2", "e471388a8726d1ce51a6b32f864b8228a1eb8edc907a0edf2bb50eab9321b526", [:mix], [], "hexpm", "f1e3dabef71fb510d015fad18c0e05e7c57281001141504c6b69d94e99750a07"},
|
||||
|
|
|
@ -10,19 +10,6 @@
|
|||
# We recommend using the bang functions (`insert!`, `update!`
|
||||
# and so on) as they will fail if something goes wrong.
|
||||
|
||||
# for title <- ~w(Chill Pop Hip-hop Electronic) do
|
||||
# {:ok, _} = LiveBeats.MediaLibrary.create_genre(%{title: title})
|
||||
# end
|
||||
|
||||
# for i <- 1..200 do
|
||||
# filename = Ecto.UUID.generate()
|
||||
|
||||
# {:ok, _} =
|
||||
# LiveBeats.Repo.insert(%LiveBeats.MediaLibrary.Song{
|
||||
# artist: "Bonobo",
|
||||
# title: "Black Sands #{i}",
|
||||
# duration: 180_000,
|
||||
# mp3_filename: filename,
|
||||
# mp3_path: "uploads/songs/#{filename}"
|
||||
# })
|
||||
# end
|
||||
for title <- ~w(Chill Pop Hip-hop Electronic) do
|
||||
{:ok, _} = LiveBeats.MediaLibrary.create_genre(%{title: title})
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue