diff --git a/assets/css/app.css b/assets/css/app.css index bbc55be..1d9f5a8 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -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; diff --git a/assets/js/app.js b/assets/js/app.js index 571300d..d44e804 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -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 diff --git a/assets/package-lock.json b/assets/package-lock.json index e1f95f7..91ed3af 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -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", diff --git a/assets/package.json b/assets/package.json index e36c37d..ac6a102 100644 --- a/assets/package.json +++ b/assets/package.json @@ -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", diff --git a/lib/live_beats_web.ex b/lib/live_beats_web.ex index aa1ab7f..74d26a2 100644 --- a/lib/live_beats_web.ex +++ b/lib/live_beats_web.ex @@ -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 diff --git a/lib/live_beats_web/controllers/oauth_callback_controller.ex b/lib/live_beats_web/controllers/oauth_callback_controller.ex index 1eb59ff..4105084 100644 --- a/lib/live_beats_web/controllers/oauth_callback_controller.ex +++ b/lib/live_beats_web/controllers/oauth_callback_controller.ex @@ -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 diff --git a/lib/live_beats_web/controllers/user_auth.ex b/lib/live_beats_web/controllers/user_auth.ex index 19e7bb7..780b895 100644 --- a/lib/live_beats_web/controllers/user_auth.ex +++ b/lib/live_beats_web/controllers/user_auth.ex @@ -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)} diff --git a/lib/live_beats_web/live/home_live.ex b/lib/live_beats_web/live/home_live.ex index 4894ef4..2d6bf5e 100644 --- a/lib/live_beats_web/live/home_live.ex +++ b/lib/live_beats_web/live/home_live.ex @@ -3,6 +3,18 @@ defmodule LiveBeatsWeb.HomeLive do def render(assigns) do ~H""" + <.title_bar> + LiveBeats - Chill + + <:action>Share + <:action primary phx-click={show_modal("add-songs")}>Add Songs + + <.modal id="add-songs"> + <:title>Add Songs + a modal + <:cancel>Close + <:confirm>Add +

Who's Here

diff --git a/lib/live_beats_web/live/live_helpers.ex b/lib/live_beats_web/live/live_helpers.ex new file mode 100644 index 0000000..4b05c29 --- /dev/null +++ b/lib/live_beats_web/live/live_helpers.ex @@ -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""" + + """ + 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""" +
+
+
+
+ """ + end + + def title_bar(assigns) do + assigns = assign_new(assigns, :action, fn -> [] end) + + ~H""" + +
+
+

+ <%= render_slot(@inner_block) %> +

+
+
+ <%= for action <- @action, rest = assigns_to_attributes(action) do %> + <%= if action[:primary] do %> + + <% else %> + + <% end %> + <% end %> +
+
+ """ + end +end diff --git a/lib/live_beats_web/live/player_live.ex b/lib/live_beats_web/live/player_live.ex index d7eac1d..14fb525 100644 --- a/lib/live_beats_web/live/player_live.ex +++ b/lib/live_beats_web/live/player_live.ex @@ -1,6 +1,8 @@ defmodule LiveBeatsWeb.PlayerLive do use LiveBeatsWeb, :live_view + on_mount LiveBeatsWeb.UserAuth + def render(assigns) do ~H""" @@ -17,13 +19,9 @@ defmodule LiveBeatsWeb.PlayerLive do

-
-
-
-
+ + <.progress_bar nonce={@nonce} /> +
<%= @time %>
<%= @count %>
diff --git a/lib/live_beats_web/router.ex b/lib/live_beats_web/router.ex index 3351f37..0e14906 100644 --- a/lib/live_beats_web/router.ex +++ b/lib/live_beats_web/router.ex @@ -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 diff --git a/lib/live_beats_web/templates/layout/root.html.heex b/lib/live_beats_web/templates/layout/root.html.heex index 3a74e5c..86b3b84 100644 --- a/lib/live_beats_web/templates/layout/root.html.heex +++ b/lib/live_beats_web/templates/layout/root.html.heex @@ -10,36 +10,20 @@ -
-