This commit is contained in:
Chris McCord 2022-03-18 08:57:13 -04:00
parent b5edfdd9cd
commit 7ea456d49f
9 changed files with 155 additions and 34 deletions

View file

@ -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() }

View file

@ -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)

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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>

View file

@ -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},

View file

@ -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"},

View file

@ -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