mirror of
https://github.com/fly-apps/live_beats.git
synced 2024-11-21 15:41:00 +00:00
WIP
This commit is contained in:
parent
e28abc0a0a
commit
708bf715e1
16 changed files with 406 additions and 184 deletions
|
@ -3,6 +3,49 @@
|
|||
@import "tailwindcss/components";
|
||||
@import "tailwindcss/utilities";
|
||||
|
||||
/* animations */
|
||||
.fade-in-scale {
|
||||
animation: 0.2s ease-in 0s normal forwards 1 fade-in-scale-keys;
|
||||
}
|
||||
|
||||
.fade-out-scale {
|
||||
animation: 0.2s ease-out 0s normal forwards 1 fade-out-scale-keys;
|
||||
}
|
||||
.slide-in-right {
|
||||
animation: slide-in-right-keys 0.2s forwards;
|
||||
}
|
||||
.fade-in {
|
||||
animation: 0.2s ease-out 0s normal forwards 1 fade-in-keys;
|
||||
}
|
||||
.fade-out {
|
||||
animation: 0.2s ease-out 0s normal forwards 1 fade-out-keys;
|
||||
}
|
||||
|
||||
@keyframes fade-in-scale-keys{
|
||||
0% { scale: 0.95; opacity: 0; }
|
||||
100% { scale: 1.0; opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes fade-out-scale-keys{
|
||||
0% { scale: 1.0; opacity: 1; }
|
||||
100% { scale: 0.95; opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes fade-in-keys{
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes fade-out-keys{
|
||||
0% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes slide-in-right-keys{
|
||||
100%{ transform: translateX(0%); }
|
||||
}
|
||||
|
||||
|
||||
/* Alerts and form errors used by phx.new */
|
||||
.alert {
|
||||
padding: 15px;
|
||||
|
|
|
@ -1,40 +1,48 @@
|
|||
// We import the CSS which is extracted to its own file by esbuild.
|
||||
// Remove this line if you add a your own CSS build pipeline (e.g postcss).
|
||||
import Alpine from "alpinejs"
|
||||
|
||||
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
|
||||
// to get started and then uncomment the line below.
|
||||
// import "./user_socket.js"
|
||||
|
||||
// You can include dependencies in two ways.
|
||||
//
|
||||
// The simplest option is to put them in assets/vendor and
|
||||
// import them using relative paths:
|
||||
//
|
||||
// import "./vendor/some-package.js"
|
||||
//
|
||||
// Alternatively, you can `npm install some-package` and import
|
||||
// them using a path starting with the package name:
|
||||
//
|
||||
// import "some-package"
|
||||
//
|
||||
|
||||
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
|
||||
import "phoenix_html"
|
||||
// Establish Phoenix Socket and LiveView configuration.
|
||||
import {Socket} from "phoenix"
|
||||
import {LiveSocket} from "./phoenix_live_view"
|
||||
import topbar from "../vendor/topbar"
|
||||
|
||||
let render = (webComponent, html) => {
|
||||
let shadow = webComponent.attachShadow({mode: "open"})
|
||||
document.querySelectorAll("link").forEach(link => shadow.appendChild(link.cloneNode()))
|
||||
let div = document.createElement("div")
|
||||
div.setAttribute("class", webComponent.getAttribute("class"))
|
||||
div.innerHTML = html || webComponent.innerHTML
|
||||
shadow.appendChild(div)
|
||||
return div
|
||||
}
|
||||
|
||||
let Hooks = {}
|
||||
|
||||
Hooks.Progress = {
|
||||
setWidth(at){
|
||||
this.el.style.width = `${Math.floor((at / (this.max - this.min)) * 100)}%`
|
||||
},
|
||||
mounted(){
|
||||
this.min = parseInt(this.el.dataset.min)
|
||||
this.max = parseInt(this.el.dataset.max)
|
||||
this.val = parseInt(this.el.dataset.val)
|
||||
setInterval(() => this.setWidth(this.val++), 1000)
|
||||
}
|
||||
}
|
||||
|
||||
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
||||
let liveSocket = new LiveSocket("/live", Socket, {
|
||||
params: { _csrf_token: csrfToken },
|
||||
hooks: Hooks,
|
||||
params: {_csrf_token: csrfToken},
|
||||
dom: {
|
||||
onBeforeElUpdated(from, to) {
|
||||
if (from._x_dataStack) {
|
||||
window.Alpine.clone(from, to);
|
||||
onNodeAdded(node){
|
||||
if(node.getAttribute && node.getAttribute("data-fade-in")){
|
||||
from.classList.add("fade-in")
|
||||
}
|
||||
},
|
||||
onBeforeElUpdated(from, to) {
|
||||
if(from.classList.contains("fade-in")){
|
||||
from.classList.remove("fade-in")
|
||||
from.classList.add("fade-in")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -52,7 +60,4 @@ liveSocket.connect()
|
|||
// >> liveSocket.disableLatencySim()
|
||||
window.liveSocket = liveSocket
|
||||
|
||||
Alpine.start()
|
||||
|
||||
window.Alpine = Alpine
|
||||
|
||||
|
|
45
assets/package-lock.json
generated
45
assets/package-lock.json
generated
|
@ -4,9 +4,6 @@
|
|||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"alpinejs": "^3.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.3.3",
|
||||
"autoprefixer": "^10.2.0",
|
||||
|
@ -165,19 +162,6 @@
|
|||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.7.tgz",
|
||||
"integrity": "sha512-VDeQiZs6s5m1W7hIX+vzmokDCHPEKNYrSxoHWXj4MiGamcT5XZxACj/VXOCK9c6qz36qK5EQOfDWtmVhxfI2hQ==",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.2.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.7.tgz",
|
||||
"integrity": "sha512-YwGOcNZjOY/MmadpzFBXWyHEwZSf0lVU4XF5zpD7tXC9dmqjdo38Jkk06wATu4LYHDPW4emXKMB5YLFPWPkwFA=="
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||
|
@ -210,14 +194,6 @@
|
|||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/alpinejs": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.2.4.tgz",
|
||||
"integrity": "sha512-nUHI7QarOyqFX1vKTP5MC54yBFh9/J+YyxE50aP3lb6aZGxwX0HIPGVuAwy2SXQFP45P4/jKHv1LOuvRp/etiQ==",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
|
@ -1682,19 +1658,6 @@
|
|||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
|
||||
"dev": true
|
||||
},
|
||||
"@vue/reactivity": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.7.tgz",
|
||||
"integrity": "sha512-VDeQiZs6s5m1W7hIX+vzmokDCHPEKNYrSxoHWXj4MiGamcT5XZxACj/VXOCK9c6qz36qK5EQOfDWtmVhxfI2hQ==",
|
||||
"requires": {
|
||||
"@vue/shared": "3.2.7"
|
||||
}
|
||||
},
|
||||
"@vue/shared": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.7.tgz",
|
||||
"integrity": "sha512-YwGOcNZjOY/MmadpzFBXWyHEwZSf0lVU4XF5zpD7tXC9dmqjdo38Jkk06wATu4LYHDPW4emXKMB5YLFPWPkwFA=="
|
||||
},
|
||||
"acorn": {
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||
|
@ -1718,14 +1681,6 @@
|
|||
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
|
||||
"dev": true
|
||||
},
|
||||
"alpinejs": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.2.4.tgz",
|
||||
"integrity": "sha512-nUHI7QarOyqFX1vKTP5MC54yBFh9/J+YyxE50aP3lb6aZGxwX0HIPGVuAwy2SXQFP45P4/jKHv1LOuvRp/etiQ==",
|
||||
"requires": {
|
||||
"@vue/reactivity": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
"scripts": {
|
||||
"deploy": "NODE_ENV=production tailwindcss --postcss --minify -i css/app.css -o ../priv/static/assets/app.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"alpinejs": "^3.2.2"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.3.3",
|
||||
"autoprefixer": "^10.2.0",
|
||||
|
|
|
@ -88,6 +88,7 @@ defmodule LiveBeatsWeb do
|
|||
import Phoenix.View
|
||||
|
||||
import LiveBeatsWeb.ErrorHelpers
|
||||
import LiveBeatsWeb.LiveHelpers
|
||||
import LiveBeatsWeb.Gettext
|
||||
alias LiveBeatsWeb.Router.Helpers, as: Routes
|
||||
end
|
||||
|
|
|
@ -35,6 +35,10 @@ defmodule LiveBeatsWeb.OAuthCallbackController do
|
|||
redirect(conn, to: "/")
|
||||
end
|
||||
|
||||
def sign_out(conn, _) do
|
||||
LiveBeatsWeb.UserAuth.log_out_user(conn)
|
||||
end
|
||||
|
||||
defp github_client(conn) do
|
||||
conn.assigns[:github_client] || LiveBeats.Github
|
||||
end
|
||||
|
|
|
@ -6,7 +6,8 @@ defmodule LiveBeatsWeb.UserAuth do
|
|||
alias LiveBeats.Accounts
|
||||
alias LiveBeatsWeb.Router.Helpers, as: Routes
|
||||
|
||||
def mount_defaults(_params, session, socket) do
|
||||
def on_mount(:default, _params, session, socket) do
|
||||
socket = LiveView.assign(socket, :nonce, Map.fetch!(session, "nonce"))
|
||||
case session do
|
||||
%{"user_id" => user_id} ->
|
||||
{:cont, LiveView.assign_new(socket, :current_user, fn -> Accounts.get_user!(user_id) end)}
|
||||
|
|
|
@ -3,6 +3,18 @@ defmodule LiveBeatsWeb.HomeLive do
|
|||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<.title_bar>
|
||||
LiveBeats - Chill
|
||||
|
||||
<:action>Share</:action>
|
||||
<:action primary phx-click={show_modal("add-songs")}>Add Songs</:action>
|
||||
</.title_bar>
|
||||
<.modal id="add-songs">
|
||||
<:title>Add Songs</:title>
|
||||
a modal
|
||||
<:cancel>Close</:cancel>
|
||||
<:confirm>Add</:confirm>
|
||||
</.modal>
|
||||
<!-- users -->
|
||||
<div class="px-4 mt-6 sm:px-6 lg:px-8">
|
||||
<h2 class="text-gray-500 text-xs font-medium uppercase tracking-wide">Who's Here</h2>
|
||||
|
|
187
lib/live_beats_web/live/live_helpers.ex
Normal file
187
lib/live_beats_web/live/live_helpers.ex
Normal file
|
@ -0,0 +1,187 @@
|
|||
defmodule LiveBeatsWeb.LiveHelpers do
|
||||
import Phoenix.LiveView
|
||||
import Phoenix.LiveView.Helpers
|
||||
import Phoenix.HTML
|
||||
|
||||
alias Phoenix.LiveView.JS
|
||||
|
||||
def link_patch(assigns) do
|
||||
~H"""
|
||||
<%= live_patch @text, [
|
||||
to: @to,
|
||||
class: "phx-modal-close"
|
||||
] ++ assigns_to_attributes(assigns, [:to, :text]) %>
|
||||
"""
|
||||
end
|
||||
|
||||
def show_mobile_sidebar(js \\ %JS{}) do
|
||||
js
|
||||
|> JS.show(to: "#mobile-sidebar-container", transition: "fade-in")
|
||||
|> JS.show(
|
||||
to: "#mobile-sidebar",
|
||||
display: "flex",
|
||||
time: 300,
|
||||
transition:
|
||||
{"transition ease-in-out duration-300 transform", "-translate-x-full", "translate-x-0"}
|
||||
)
|
||||
end
|
||||
|
||||
def hide_mobile_sidebar(js \\ %JS{}) do
|
||||
js
|
||||
|> JS.hide(to: "#mobile-sidebar-container", transition: "fade-out")
|
||||
|> JS.hide(
|
||||
to: "#mobile-sidebar",
|
||||
time: 300,
|
||||
transition:
|
||||
{"transition ease-in-out duration-300 transform", "translate-x-0", "-translate-x-full"}
|
||||
)
|
||||
end
|
||||
|
||||
def show_dropdown(to) do
|
||||
JS.show(
|
||||
to: to,
|
||||
transition:
|
||||
{"transition ease-out duration-120", "transform opacity-0 scale-95",
|
||||
"transform opacity-100 scale-100"}
|
||||
)
|
||||
end
|
||||
|
||||
def hide_dropdown(to) do
|
||||
JS.hide(
|
||||
to: to,
|
||||
transition:
|
||||
{"transition ease-in duration-120", "transform opacity-100 scale-100",
|
||||
"transform opacity-0 scale-95"}
|
||||
)
|
||||
end
|
||||
|
||||
def show_modal(js \\ %JS{}, id) do
|
||||
js
|
||||
|> JS.show(
|
||||
to: "##{id}",
|
||||
display: "inline-block",
|
||||
transition: {"ease-out duration-300", "opacity-0", "opacity-100"}
|
||||
)
|
||||
|> JS.show(
|
||||
to: "##{id} .modal-content",
|
||||
display: "inline-block",
|
||||
transition:
|
||||
{"ease-out duration-300", "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
|
||||
"opacity-100 translate-y-0 sm:scale-100"}
|
||||
)
|
||||
end
|
||||
|
||||
def hide_modal(js \\ %JS{}, id) do
|
||||
js
|
||||
|> JS.hide(
|
||||
to: "##{id}",
|
||||
transition: {"ease-in duration-200", "opacity-100", "opacity-0"}
|
||||
)
|
||||
|> JS.hide(
|
||||
to: "##{id} .modal-content",
|
||||
transition:
|
||||
{"ease-in duration-200", "opacity-100 translate-y-0 sm:scale-100",
|
||||
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
|
||||
)
|
||||
end
|
||||
|
||||
def modal(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|> assign_new(:title, fn -> "" end)
|
||||
|> assign_new(:return_to, fn -> nil end)
|
||||
|
||||
~H"""
|
||||
<div id={@id} class="hidden fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
||||
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
|
||||
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
||||
<div
|
||||
class="modal-content inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6"
|
||||
phx-click-away={hide_modal(@id)}
|
||||
>
|
||||
<div class="sm:flex sm:items-start">
|
||||
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-green-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<!-- Heroicon name: outline/exclamation -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">
|
||||
<%= render_slot(@title) %>
|
||||
</h3>
|
||||
<div class="mt-2">
|
||||
<p class="text-sm text-gray-500">
|
||||
<%= render_slot(@inner_block) %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button type="button" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
|
||||
<%= render_slot(@confirm) %>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:w-auto sm:text-sm"
|
||||
phx-window-keydown={hide_modal(@id)} phx-key="escape"
|
||||
phx-click={hide_modal(@id)}
|
||||
>
|
||||
<%= render_slot(@cancel) %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def progress_bar(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|> assign_new(:min, fn -> 0 end)
|
||||
|> assign_new(:max, fn -> 100 end)
|
||||
|> assign_new(:value, fn -> assigns[:min] || 0 end)
|
||||
|
||||
~H"""
|
||||
<div class="bg-gray-200 flex-auto dark:bg-black rounded-full overflow-hidden" phx-update="ignore">
|
||||
<div id="progress"
|
||||
class="bg-lime-500 dark:bg-lime-400 h-1.5 w-0"
|
||||
phx-hook="Progress"
|
||||
data-min={@min}
|
||||
data-max={@max}
|
||||
data-val={@value}>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def title_bar(assigns) do
|
||||
assigns = assign_new(assigns, :action, fn -> [] end)
|
||||
|
||||
~H"""
|
||||
<!-- Page title & actions -->
|
||||
<div class="border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate">
|
||||
<%= render_slot(@inner_block) %>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="mt-4 flex sm:mt-0 sm:ml-4">
|
||||
<%= for action <- @action, rest = assigns_to_attributes(action) do %>
|
||||
<%= if action[:primary] do %>
|
||||
<button type="button" class="order-0 inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:order-1 sm:ml-3" {rest}>
|
||||
<%= render_slot(action) %>
|
||||
</button>
|
||||
<% else %>
|
||||
<button type="button" class="order-1 inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:order-0 sm:ml-0 lg:ml-3" {rest}>
|
||||
<%= render_slot(action) %>
|
||||
</button>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
|
@ -1,6 +1,8 @@
|
|||
defmodule LiveBeatsWeb.PlayerLive do
|
||||
use LiveBeatsWeb, :live_view
|
||||
|
||||
on_mount LiveBeatsWeb.UserAuth
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<!-- player -->
|
||||
|
@ -17,13 +19,9 @@ defmodule LiveBeatsWeb.PlayerLive do
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-200 flex-auto dark:bg-black rounded-full overflow-hidden" phx-update="ignore">
|
||||
<div class="bg-lime-500 dark:bg-lime-400 h-1.5" role="progressbar"
|
||||
style="width: 0%;"
|
||||
x-data="{progress: 0}"
|
||||
x-init="setInterval(() => $el.style.width = `${progress++}%`, 1000)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<.progress_bar nonce={@nonce} />
|
||||
|
||||
<div class="text-gray-500 dark:text-gray-400 flex-row justify-between text-sm font-medium tabular-nums">
|
||||
<div><%= @time %></div>
|
||||
<div><%= @count %></div>
|
||||
|
|
|
@ -10,6 +10,7 @@ defmodule LiveBeatsWeb.Router do
|
|||
plug :put_root_layout, {LiveBeatsWeb.LayoutView, :root}
|
||||
plug :protect_from_forgery
|
||||
plug :put_secure_browser_headers
|
||||
plug :put_nonce
|
||||
end
|
||||
|
||||
pipeline :api do
|
||||
|
@ -19,10 +20,10 @@ defmodule LiveBeatsWeb.Router do
|
|||
scope "/", LiveBeatsWeb do
|
||||
pipe_through :browser
|
||||
|
||||
live_session :default, on_mount: {LiveBeatsWeb.UserAuth, :mount_defaults} do
|
||||
live "/test", IndexLive
|
||||
live_session :default, on_mount: LiveBeatsWeb.UserAuth do
|
||||
live "/", HomeLive, :index
|
||||
live "/signin", SigninLive, :index
|
||||
delete "/signout", OAuthCallbackController, :sign_out
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -64,4 +65,20 @@ defmodule LiveBeatsWeb.Router do
|
|||
forward "/mailbox", Plug.Swoosh.MailboxPreview
|
||||
end
|
||||
end
|
||||
|
||||
defp put_nonce(conn, _) do
|
||||
nonce = Phoenix.HTML.Tag.csrf_token_value()
|
||||
endpoint = Phoenix.Controller.endpoint_module(conn)
|
||||
url = endpoint.url()
|
||||
uri = endpoint.struct_url()
|
||||
ws_url = %URI{uri | scheme: "ws"}
|
||||
wss_url = %URI{uri | scheme: "wss"}
|
||||
|
||||
conn
|
||||
|> put_session(:nonce, nonce)
|
||||
|> put_resp_header(
|
||||
"content-security-policy",
|
||||
"script-src 'nonce-#{nonce}' #{url} #{ws_url}; connect-src 'self' #{ws_url} #{wss_url}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,36 +10,20 @@
|
|||
<script defer phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/assets/app.js")}></script>
|
||||
</head>
|
||||
<body>
|
||||
<div x-data="{open: false}" @keydown.window.escape="open = false" class="relative h-screen flex overflow-hidden bg-white">
|
||||
<div x-show="open" class="fixed inset-0 flex z-40 lg:hidden"
|
||||
x-description="Off-canvas menu for mobile, show/hide based on off-canvas menu state." x-ref="dialog"
|
||||
aria-modal="true" style="display: none;">
|
||||
|
||||
<div x-show="open" x-transition:enter="transition-opacity ease-linear duration-300"
|
||||
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition-opacity ease-linear duration-300" x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
x-description="Off-canvas menu overlay, show/hide based on off-canvas menu state."
|
||||
class="fixed inset-0 bg-gray-600 bg-opacity-75" @click="open = false" aria-hidden="true" style="display: none;">
|
||||
<div class="relative h-screen flex overflow-hidden bg-white">
|
||||
<div id="mobile-sidebar-container" class="fixed inset-0 flex z-40 lg:hidden" aria-modal="true" style="display: none;">
|
||||
<div
|
||||
class="fixed inset-0 bg-gray-600 bg-opacity-75"
|
||||
phx-click={hide_mobile_sidebar()}>
|
||||
</div>
|
||||
|
||||
<div x-show="open" x-transition:enter="transition ease-in-out duration-300 transform"
|
||||
x-transition:enter-start="-translate-x-full" x-transition:enter-end="translate-x-0"
|
||||
x-transition:leave="transition ease-in-out duration-300 transform" x-transition:leave-start="translate-x-0"
|
||||
x-transition:leave-end="-translate-x-full"
|
||||
x-description="Off-canvas menu, show/hide based on off-canvas menu state."
|
||||
class="relative flex-1 flex flex-col max-w-xs w-full pt-5 pb-4 bg-white" style="display: none;">
|
||||
|
||||
<div x-show="open" x-transition:enter="ease-in-out duration-300" x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100" x-transition:leave="ease-in-out duration-300"
|
||||
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
|
||||
x-description="Close button, show/hide based on off-canvas menu state."
|
||||
class="absolute top-0 right-0 -mr-12 pt-2" style="display: none;">
|
||||
<div id="mobile-sidebar" class="relative flex-1 flex flex-col max-w-xs w-full pt-5 pb-4 bg-white hidden min-h-screen">
|
||||
<div class="absolute top-0 right-0 -mr-12 pt-2">
|
||||
<button type="button"
|
||||
class="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
|
||||
@click="open = false">
|
||||
phx-click={hide_mobile_sidebar()}>
|
||||
<span class="sr-only">Close sidebar</span>
|
||||
<svg class="h-6 w-6 text-white" x-description="Heroicon name: outline/x" xmlns="http://www.w3.org/2000/svg"
|
||||
<svg class="h-6 w-6 text-white" xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
|
@ -56,9 +40,7 @@
|
|||
|
||||
<%= live_redirect to: Routes.home_path(@conn, :index),
|
||||
class: "bg-gray-100 text-gray-900 group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md" do %>
|
||||
<svg class="text-gray-500 mr-3 flex-shrink-0 h-6 w-6" x-state:on="Current" x-state:off="Default"
|
||||
x-state-description="Current: "text-gray-500", Default: "text-gray-400 group-hover:text-gray-500""
|
||||
x-description="Heroicon name: outline/home" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
<svg class="text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
|
||||
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6">
|
||||
|
@ -68,11 +50,8 @@
|
|||
<% end %>
|
||||
|
||||
<a href="#"
|
||||
class="text-gray-600 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md"
|
||||
x-state-description="undefined: "bg-gray-100 text-gray-900", undefined: "text-gray-600 hover:text-gray-900 hover:bg-gray-50"">
|
||||
class="text-gray-600 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md">
|
||||
<svg class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
|
||||
x-state-description="undefined: "text-gray-500", undefined: "text-gray-400 group-hover:text-gray-500""
|
||||
x-description="Heroicon name: outline/view-list" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
|
||||
|
@ -81,14 +60,55 @@
|
|||
</a>
|
||||
|
||||
<%= if @current_user do %>
|
||||
<!-- User account dropdown -->
|
||||
<div class="px-3 mt-6 relative inline-block text-left w-full">
|
||||
<div>
|
||||
<button type="button"
|
||||
class="group w-full bg-gray-100 rounded-md px-3.5 py-2 text-sm text-left font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-purple-500"
|
||||
id="options-menu-button"
|
||||
phx-click={show_dropdown("#account-dropdown-mobile")}>
|
||||
<span class="flex w-full justify-between items-center">
|
||||
<span class="flex min-w-0 items-center justify-between space-x-3">
|
||||
<img class="w-10 h-10 bg-gray-300 rounded-full flex-shrink-0"
|
||||
src="https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=3&w=256&h=256&q=80"
|
||||
alt="">
|
||||
<span class="flex-1 flex flex-col min-w-0">
|
||||
<span class="text-gray-900 text-sm font-medium truncate"><%= @current_user.name %></span>
|
||||
<span class="text-gray-500 text-sm truncate">@<%= @current_user.username %></span>
|
||||
</span>
|
||||
</span>
|
||||
<svg class="flex-shrink-0 h-5 w-5 text-gray-400 group-hover:text-gray-500"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="account-dropdown-mobile" phx-click-away={hide_dropdown("#account-dropdown-mobile")} class="hidden z-10 mx-3 origin-top absolute right-0 left-0 mt-1 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-200 focus:outline-none" role="menu">
|
||||
<div class="py-1" role="none">
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-0">View profile</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-1">Settings</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-2">Notifications</a>
|
||||
</div>
|
||||
<div class="py-1" role="none">
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-3">Get desktop app</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-4">Support</a>
|
||||
</div>
|
||||
<div class="py-1" role="none">
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-5">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% else %>
|
||||
<%= live_redirect to: Routes.signin_path(@conn, :index),
|
||||
class: "text-gray-600 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md" do %>
|
||||
|
||||
<svg class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
|
||||
x-state-description="undefined: "text-gray-500", undefined: "text-gray-400 group-hover:text-gray-500""
|
||||
x-description="Heroicon name: outline/clock" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
|
@ -150,17 +170,13 @@
|
|||
<!-- Sidebar component, swap this element with another sidebar if you like -->
|
||||
<div class="h-0 flex-1 flex flex-col overflow-y-auto">
|
||||
<%= if @current_user do %>
|
||||
<!-- User account dropdown -->
|
||||
<div x-data="{open: false, activeIndex: 0}"
|
||||
@keydown.escape.stop="open = false"
|
||||
@click.away="open = false"
|
||||
class="px-3 mt-6 relative inline-block text-left">
|
||||
<!-- User account dropdown -->
|
||||
<div class="px-3 mt-6 relative inline-block text-left">
|
||||
<div>
|
||||
<button type="button"
|
||||
class="group w-full bg-gray-100 rounded-md px-3.5 py-2 text-sm text-left font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-purple-500"
|
||||
id="options-menu-button"
|
||||
x-ref="button"
|
||||
@click="open = true">
|
||||
phx-click={show_dropdown("#account-dropdown")}>
|
||||
<span class="flex w-full justify-between items-center">
|
||||
<span class="flex min-w-0 items-center justify-between space-x-3">
|
||||
<img class="w-10 h-10 bg-gray-300 rounded-full flex-shrink-0"
|
||||
|
@ -172,7 +188,7 @@
|
|||
</span>
|
||||
</span>
|
||||
<svg class="flex-shrink-0 h-5 w-5 text-gray-400 group-hover:text-gray-500"
|
||||
x-description="Heroicon name: solid/selector" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
|
||||
|
@ -181,18 +197,18 @@
|
|||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="transform opacity-0 scale-95" x-transition:enter-end="transform opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95" class="z-10 mx-3 origin-top absolute right-0 left-0 mt-1 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-200 focus:outline-none" x-ref="menu-items" x-description="Dropdown menu, show/hide based on menu state." role="menu" aria-orientation="vertical" aria-labelledby="options-menu-button" tabindex="-1" @keydown.arrow-up.prevent="onArrowUp()" @keydown.arrow-down.prevent="onArrowDown()" @keydown.tab="open = false">
|
||||
<div id="account-dropdown" phx-click-away={hide_dropdown("#account-dropdown")} class="hidden z-10 mx-3 origin-top absolute right-0 left-0 mt-1 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-200" role="menu" aria-expanded="true">
|
||||
<div class="py-1" role="none">
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" x-state:on="Active" x-state:off="Not Active" :class="{ 'bg-gray-100 text-gray-900': activeIndex === 0, 'text-gray-700': !(activeIndex === 0) }" role="menuitem" tabindex="-1" id="options-menu-item-0" @mouseenter="activeIndex = 0" @mouseleave="activeIndex = -1" @click="open = false; focusButton()">View profile</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" :class="{ 'bg-gray-100 text-gray-900': activeIndex === 1, 'text-gray-700': !(activeIndex === 1) }" role="menuitem" tabindex="-1" id="options-menu-item-1" @mouseenter="activeIndex = 1" @mouseleave="activeIndex = -1" @click="open = false; focusButton()">Settings</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" :class="{ 'bg-gray-100 text-gray-900': activeIndex === 2, 'text-gray-700': !(activeIndex === 2) }" role="menuitem" tabindex="-1" id="options-menu-item-2" @mouseenter="activeIndex = 2" @mouseleave="activeIndex = -1" @click="open = false; focusButton()">Notifications</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-0">View profile</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-1">Settings</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-2">Notifications</a>
|
||||
</div>
|
||||
<div class="py-1" role="none">
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" :class="{ 'bg-gray-100 text-gray-900': activeIndex === 3, 'text-gray-700': !(activeIndex === 3) }" role="menuitem" tabindex="-1" id="options-menu-item-3" @mouseenter="activeIndex = 3" @mouseleave="activeIndex = -1" @click="open = false; focusButton()">Get desktop app</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" :class="{ 'bg-gray-100 text-gray-900': activeIndex === 4, 'text-gray-700': !(activeIndex === 4) }" role="menuitem" tabindex="-1" id="options-menu-item-4" @mouseenter="activeIndex = 4" @mouseleave="activeIndex = -1" @click="open = false; focusButton()">Support</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-3">Get desktop app</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-4">Support</a>
|
||||
</div>
|
||||
<div class="py-1" role="none">
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" :class="{ 'bg-gray-100 text-gray-900': activeIndex === 5, 'text-gray-700': !(activeIndex === 5) }" role="menuitem" tabindex="-1" id="options-menu-item-5" @mouseenter="activeIndex = 5" @mouseleave="activeIndex = -1" @click="open = false; focusButton()">Logout</a>
|
||||
<%= link "Logout", to: Routes.o_auth_callback_path(@conn, :sign_out), method: :delete, role: "menuitem", id: "options-menu-item-5", class: "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -220,9 +236,8 @@
|
|||
|
||||
<%= live_redirect to: Routes.home_path(@conn, :index),
|
||||
class: "bg-gray-200 text-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md" do %>
|
||||
<svg class="text-gray-500 mr-3 flex-shrink-0 h-6 w-6" x-state:on="Current" x-state:off="Default"
|
||||
x-state-description="Current: "text-gray-500", Default: "text-gray-400 group-hover:text-gray-500""
|
||||
x-description="Heroicon name: outline/home" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
<svg class="text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6">
|
||||
|
@ -232,11 +247,9 @@
|
|||
<% end %>
|
||||
|
||||
<a href="#"
|
||||
class="text-gray-700 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-sm font-medium rounded-md"
|
||||
x-state-description="undefined: "bg-gray-200 text-gray-900", undefined: "text-gray-700 hover:text-gray-900 hover:bg-gray-50"">
|
||||
class="text-gray-700 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-sm font-medium rounded-md">
|
||||
<svg class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
|
||||
x-state-description="undefined: "text-gray-500", undefined: "text-gray-400 group-hover:text-gray-500""
|
||||
x-description="Heroicon name: outline/view-list" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
|
||||
|
@ -248,8 +261,7 @@
|
|||
<%= live_redirect to: Routes.signin_path(@conn, :index),
|
||||
class: "text-gray-700 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-sm font-medium rounded-md" do %>
|
||||
<svg class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
|
||||
x-state-description="undefined: "text-gray-500", undefined: "text-gray-400 group-hover:text-gray-500""
|
||||
x-description="Heroicon name: outline/clock" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
|
@ -300,11 +312,11 @@
|
|||
<div class="flex flex-col w-0 flex-1 overflow-hidden">
|
||||
<!-- Search header -->
|
||||
<div class="relative z-10 flex-shrink-0 flex h-16 bg-white border-b border-gray-200 lg:hidden">
|
||||
<button type="button" x-description="Sidebar toggle, controls the 'sidebarOpen' sidebar state."
|
||||
<button type="button"
|
||||
class="px-4 border-r border-gray-200 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-purple-500 lg:hidden"
|
||||
@click="open = true">
|
||||
phx-click={show_mobile_sidebar()}>
|
||||
<span class="sr-only">Open sidebar</span>
|
||||
<svg class="h-6 w-6" x-description="Heroicon name: outline/menu-alt-1" xmlns="http://www.w3.org/2000/svg"
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16"></path>
|
||||
</svg>
|
||||
|
@ -315,7 +327,7 @@
|
|||
<label for="search-field" class="sr-only">Search</label>
|
||||
<div class="relative w-full text-gray-400 focus-within:text-gray-600">
|
||||
<div class="absolute inset-y-0 left-0 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5" x-description="Heroicon name: solid/search" xmlns="http://www.w3.org/2000/svg"
|
||||
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
||||
|
@ -329,16 +341,14 @@
|
|||
</form>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<!-- Profile dropdown -->
|
||||
<div x-data="{open: false}"
|
||||
@keydown.escape.stop="open = false" @click.away="open = false" class="ml-3 relative">
|
||||
<!-- Profile dropdown TODO -->
|
||||
<div @keydown.escape.stop="open = false" @click.away="open = false" class="ml-3 relative">
|
||||
<div>
|
||||
<button type="button"
|
||||
class="max-w-xs bg-white flex items-center text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500"
|
||||
id="user-menu-button" x-ref="button" @click="open = true" @keyup.space.prevent="onButtonEnter()"
|
||||
@keydown.enter.prevent="onButtonEnter()" aria-expanded="false" aria-haspopup="true"
|
||||
x-bind:aria-expanded="open.toString()" @keydown.arrow-up.prevent="onArrowUp()"
|
||||
@keydown.arrow-down.prevent="onArrowDown()">
|
||||
id="user-menu-button" @click="open = true"
|
||||
aria-expanded="false" aria-haspopup="true"
|
||||
>
|
||||
<span class="sr-only">Open user menu</span>
|
||||
<img class="h-8 w-8 rounded-full"
|
||||
src="https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
|
@ -352,27 +362,7 @@
|
|||
|
||||
<main class="flex-1 relative z-0 overflow-y-auto focus:outline-none">
|
||||
<%= live_render(@conn, LiveBeatsWeb.PlayerLive, session: %{}) %>
|
||||
<!-- Page title & actions -->
|
||||
<div class="border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate">
|
||||
LiveBeats – Chill
|
||||
</h1>
|
||||
</div>
|
||||
<div class="mt-4 flex sm:mt-0 sm:ml-4">
|
||||
<button type="button"
|
||||
class="order-1 ml-3 inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:order-0 sm:ml-0">
|
||||
Share
|
||||
</button>
|
||||
<button type="button"
|
||||
class="order-0 inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:order-1 sm:ml-3">
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= @inner_content %>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
defmodule LiveBeatsWeb.LayoutView do
|
||||
use LiveBeatsWeb, :view
|
||||
|
||||
alias Phoenix.LiveView.JS
|
||||
|
||||
# Phoenix LiveDashboard is available only in development by default,
|
||||
# so we instruct Elixir to not warn if the dashboard route is missing.
|
||||
@compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}}
|
||||
|
|
4
mix.exs
4
mix.exs
|
@ -33,13 +33,13 @@ defmodule LiveBeats.MixProject do
|
|||
# Type `mix help deps` for examples and options.
|
||||
defp deps do
|
||||
[
|
||||
{:phoenix, "~> 1.6.0-rc.0", override: true},
|
||||
{:phoenix, "~> 1.6.0"},
|
||||
{:phoenix_ecto, "~> 4.4"},
|
||||
{:ecto_sql, "~> 3.6"},
|
||||
{:postgrex, ">= 0.0.0"},
|
||||
{:phoenix_html, "~> 3.0"},
|
||||
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
||||
{:phoenix_live_view, "~> 0.16.0"},
|
||||
{:phoenix_live_view, path: "~/oss/phoenix_live_view", override: true},
|
||||
{:floki, ">= 0.30.0", only: :test},
|
||||
{:phoenix_live_dashboard, "~> 0.5"},
|
||||
{:esbuild, "~> 0.2", runtime: Mix.env() == :dev},
|
||||
|
|
10
mix.lock
10
mix.lock
|
@ -14,18 +14,18 @@
|
|||
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
|
||||
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
|
||||
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
|
||||
"mime": {:hex, :mime, "2.0.1", "0de4c81303fe07806ebc2494d5321ce8fb4df106e34dd5f9d787b637ebadc256", [:mix], [], "hexpm", "7a86b920d2aedce5fb6280ac8261ac1a739ae6c1a1ad38f5eadf910063008942"},
|
||||
"mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
|
||||
"mint": {:hex, :mint, "1.3.0", "396b3301102f7b775e103da5a20494b25753aed818d6d6f0ad222a3a018c3600", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "a9aac960562e43ca69a77e5176576abfa78b8398cec5543dd4fb4ab0131d5c1e"},
|
||||
"phoenix": {:hex, :phoenix, "1.6.0-rc.0", "87dc1bb400588019a878ecf32c2d229c7d7f31a520c574860a059934663ffa70", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2a0d344d2a2f654a9300b2b09dbf9c3821762e1364e26fce12d76fcd498b92c0"},
|
||||
"phoenix": {:hex, :phoenix, "1.6.2", "6cbd5c8ed7a797f25a919a37fafbc2fb1634c9cdb12a4448d7a5d0b26926f005", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7bbee475acae0c3abc229b7f189e210ea788e63bd168e585f60c299a4b2f9133"},
|
||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "3.0.2", "0d71bd7dfa5fad2103142206e25e16accd64f41bcbd0002af3f0da17e530968d", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d6c6e85d9bef8d52a5a66fcccd15529651f379eaccbf10500343a17f6f814f82"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "3.1.0", "0b499df05aad27160d697a9362f0e89fa0e24d3c7a9065c2bd9d38b4d1416c09", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c0a98a2cefa63433657983a2a594c7dee5927e4391e0f1bfd3a151d1def33fc"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.5.0", "3282d8646e1bfc1ef1218f508d9fcefd48cf47f9081b7667bd9b281b688a49cf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.6", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.16.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "609740be43de94ae0abd2c4300ff0356a6e8a9487bf340e69967643a59fa7ec8"},
|
||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.16.1", "a17652e936718b6b6b52ef64d4b9860bc30c41b9a491e25f2b49a70604efa436", [: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.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "94bbc572471ad151b756b38dd10acbf91e0bcc132ad8b78240baa0dcf77cea74"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.16.4", "5692edd0bac247a9a816eee7394e32e7a764959c7d0cf9190662fc8b0cd24c97", [: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.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "754ba49aa2e8601afd4f151492c93eb72df69b0b9856bab17711b8397e43bba0"},
|
||||
"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_cowboy": {:hex, :plug_cowboy, "2.5.1", "7cc96ff645158a94cf3ec9744464414f02287f832d6847079adfe0b58761cbd0", [: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]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "107d0a5865fa92bcb48e631cc0729ae9ccfa0a9f9a1bd8f01acb513abf1c2d64"},
|
||||
"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"},
|
||||
"postgrex": {:hex, :postgrex, "0.15.10", "2809dee1b1d76f7cbabe570b2a9285c2e7b41be60cf792f5f2804a54b838a067", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "1560ca427542f6b213f8e281633ae1a3b31cdbcd84ebd7f50628765b8f6132be"},
|
||||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||
|
|
9
priv/repo/migrations/20210908150612_create_genres.exs
Normal file
9
priv/repo/migrations/20210908150612_create_genres.exs
Normal file
|
@ -0,0 +1,9 @@
|
|||
defmodule LiveBeats.Repo.Migrations.CreateGenres do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:genres) do
|
||||
add :title, :text, null: false
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue