feat: Setup LiveView

This commit is contained in:
Robert Prehn 2021-06-19 17:26:20 +00:00
parent 736b0d8296
commit 8929fd8204
24 changed files with 16075 additions and 68 deletions

View file

@ -50,7 +50,7 @@ defmodule Legendary.Admin.MixProject do
{:phoenix_ecto, "~> 4.0"},
{:phoenix_html, "~> 2.11"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_dashboard, "~> 0.2.0"},
{:phoenix_live_dashboard, "~> 0.4.0"},
{:postgrex, ">= 0.0.0"},
{:telemetry_metrics, "~> 0.4"},
{:telemetry_poller, "~> 0.4"},

View file

@ -14,6 +14,7 @@ import "../css/app.css"
//
import "phoenix_html"
import "alpinejs"
import "./live"
import { ready } from "./utils"
function togglePasswordFieldVisibility()
@ -38,7 +39,7 @@ const toggleSidebar = (event) => {
}
ready(() => {
document.getElementById('nav-toggle').onclick = function(){
(document.getElementById('nav-toggle') ||{}).onclick = function(){
document.getElementById("nav-content").classList.toggle("hidden");
}

View file

@ -0,0 +1,23 @@
import {Socket} from "phoenix"
import LiveSocket from "phoenix_live_view"
import topbar from "topbar"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#3B82F6"}, shadowColor: "rgba(0, 0, 0, .3)"})
window.addEventListener("phx:page-loading-start", info => topbar.show())
window.addEventListener("phx:page-loading-stop", info => topbar.hide())
// Connect if there are any LiveViews on the page
liveSocket.connect()
// Expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000)
// The latency simulator is enabled for the duration of the browser session.
// Call disableLatencySim() to disable:
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket

File diff suppressed because it is too large Load diff

View file

@ -16,10 +16,12 @@
"gulp": "^4.0.2",
"phoenix": "file:/../../../deps/phoenix",
"phoenix_html": "file:/../../../deps/phoenix_html",
"phoenix_live_view": "file:../../../deps/phoenix_live_view",
"postcss-color-function": "^4.1.0",
"simplemde": "^1.11.2",
"stylelint": "^13.6.1",
"tailwindcss": "^1.7.3"
"tailwindcss": "^1.7.3",
"topbar": "^1.0.1"
},
"devDependencies": {
"@babel/core": "^7.0.0",

View file

@ -27,6 +27,27 @@ defmodule AppWeb do
end
end
def live_view do
quote do
use Phoenix.LiveView,
layout: {AppWeb.LayoutView, "live.html"}
import AppWeb.LiveHelpers
unquote(view_helpers())
end
end
def live_component do
quote do
use Phoenix.LiveComponent
import AppWeb.LiveHelpers
unquote(view_helpers())
end
end
def view do
quote do
use Phoenix.View,
@ -48,6 +69,7 @@ defmodule AppWeb do
import Plug.Conn
import Phoenix.Controller
import Phoenix.LiveView.Router
end
end
@ -66,9 +88,10 @@ defmodule AppWeb do
# Import basic rendering functionality (render, render_layout, etc)
import Phoenix.View
import Legendary.CoreWeb.Helpers
import AppWeb.ErrorHelpers
import AppWeb.Gettext
import Legendary.CoreWeb.Helpers
import Phoenix.LiveView.Helpers
alias AppWeb.Router.Helpers, as: Routes
end
end

View file

View file

@ -0,0 +1,41 @@
defmodule AppWeb.LiveHelpers do
@moduledoc """
Commonly functions for LiveViews.
"""
import Phoenix.LiveView
alias Legendary.Auth.User
alias Pow.Store.CredentialsCache
alias AppWeb.Pow.Routes
def assign_defaults(socket, session) do
assign_new(socket, :current_user, fn -> get_user(socket, session) end)
end
def require_auth(socket) do
if socket.assigns.current_user do
socket
else
redirect(socket, to: Routes.after_sign_out_path(%Plug.Conn{}))
end
end
defp get_user(socket, session, config \\ [otp_app: :core])
defp get_user(socket, %{"core_auth" => signed_token}, config) do
{otp_app, _config} = Keyword.pop(config, :otp_app, :core)
{store, store_config} = Pow.Plug.Base.store(Application.get_env(otp_app, :pow))
conn = struct!(Plug.Conn, secret_key_base: socket.endpoint.config(:secret_key_base))
salt = Atom.to_string(Pow.Plug.Session)
with {:ok, token} <- Pow.Plug.verify_token(conn, salt, signed_token, config),
{user, _metadata} <- store.get(store_config, token) do
user
else
_any -> nil
end
end
defp get_user(_, _, _), do: nil
end

View file

@ -9,10 +9,11 @@ defmodule AppWeb.Router do
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :fetch_live_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :put_layout, {AppWeb.LayoutView, :app}
plug :put_root_layout, {AppWeb.LayoutView, :root}
end
pipeline :api do

View file

@ -0,0 +1,22 @@
<div class="fixed w-full px-4 z-50 mt-20">
<%= [info: "green", error: "red"] |> Enum.map(fn {level, color} -> %>
<%= if live_flash(@flash, level) do %>
<div
class="relative bg-<%= color %>-100 lg:w-1/2 w-full p-5 object-right rounded shadow-xl m-auto mb-5 js-flash"
phx-click="lv:clear-flash"
phx-value-key="<%= level %>"
>
<div class="flex justify-between text-<%= color %>-700">
<div class="flex space-x-3">
<div class="flex-1 leading-tight text-sm font-medium">
<%= live_flash(@flash, level) %>
</div>
</div>
<div class="flex-none">
&times;
</div>
</div>
</div>
<% end %>
<% end) %>
</div>

View file

@ -1,3 +1,5 @@
<% conn_or_socket = if assigns[:conn], do: assigns[:conn], else: @socket %>
<nav class="flex items-center justify-between flex-wrap bg-grey-dark p-6 w-full z-10 bg-gray-800">
<div class="flex items-center flex-no-shrink text-white mr-6">
<a class="text-white no-underline hover:text-white hover:no-underline" href="/">
@ -13,23 +15,23 @@
<div class="w-full flex-grow lg:flex lg:items-center lg:w-auto hidden lg:block pt-6 lg:pt-0" id="nav-content">
<ul class="list-reset lg:flex justify-end flex-1 items-center">
<%= if has_role?(@conn, :admin) do %>
<%= if has_role?(conn_or_socket, :admin) do %>
<li class="mr-3">
<a class="inline-block py-2 px-4 text-white no-underline" href="/admin">Admin</a>
</li>
<% end %>
<%= if Pow.Plug.current_user(@conn) do %>
<%= if current_user(conn_or_socket) do %>
<li class="mr-3">
<%= link "Sign Out", to: Routes.pow_session_path(@conn, :delete), method: :delete, class: "inline-block text-white no-underline hover:text-white hover:text-underline py-2 px-4" %>
<%= link "Sign Out", to: Routes.pow_session_path(conn_or_socket, :delete), method: :delete, class: "inline-block text-white no-underline hover:text-white hover:text-underline py-2 px-4" %>
</li>
<% else %>
<li class="mr-3">
<%= link to: Routes.pow_session_path(@conn, :new), class: "inline-block text-white no-underline hover:text-white hover:text-underline py-2 px-4" do %>
<%= link to: Routes.pow_session_path(conn_or_socket, :new), class: "inline-block text-white no-underline hover:text-white hover:text-underline py-2 px-4" do %>
Log In
<% end %>
</li>
<li class="mr-3">
<%= link to: Routes.pow_registration_path(@conn, :new), class: "inline-block text-white no-underline hover:text-white hover:text-underline py-2 px-4" do %>
<%= link to: Routes.pow_registration_path(conn_or_socket, :new), class: "inline-block text-white no-underline hover:text-white hover:text-underline py-2 px-4" do %>
Sign Up
<% end %>
</li>

View file

@ -1,24 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title><%= title(@view_module, @view_template, assigns) %></title>
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
<%= feed_tag(@conn, @view_module, @view_template, assigns) %>
<script defer type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
</head>
<body class="text-gray-800 antialiased">
<main role="main">
<!-- Page Contents -->
<div class="bg-gray-100 min-h-screen">
<%= render "_menu.html", assigns %>
<%= render "_menu.html", assigns %>
<%= flash_block(@conn) %>
<%= flash_block(@conn) %>
<%= @inner_content %>
</div>
</main>
</body>
</html>
<%= @inner_content %>

View file

@ -0,0 +1,6 @@
<%= render "_menu.html", assigns %>
<%= render "_live_flash_block.html", flash: @flash %>
<%= @inner_content %>

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<%= csrf_meta_tag() %>
<%= live_title_tag title(@view_module, @view_template, assigns) %>
<link phx-track-static rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
<script defer phx-track-static type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
</head>
<body class="text-gray-800 antialiased">
<main role="main">
<!-- Page Contents -->
<div class="bg-gray-100 min-h-screen">
<%= @inner_content %>
</div>
</main>
</body>
</html>

View file

@ -44,12 +44,14 @@ defmodule App.MixProject do
{:core, in_umbrella: true},
{:ecto_sql, "~> 3.4"},
{:excoveralls, "~> 0.10", only: [:dev, :test]},
{:floki, ">= 0.30.0"},
{:oban, "~> 2.1"},
{:phoenix, "~> 1.5.8"},
{:phoenix_ecto, "~> 4.0"},
{:phoenix_html, "~> 2.11"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_dashboard, "~> 0.2.0"},
{:phoenix_live_dashboard, "~> 0.4.0"},
{:phoenix_live_view, "~> 0.15.7", override: true},
{:postgrex, ">= 0.0.0"},
{:telemetry_metrics, "~> 0.4"},
{:telemetry_poller, "~> 0.4"},

View file

@ -3,8 +3,6 @@ defmodule Legendary.Content.FeedsController do
alias Legendary.Content.{Posts}
plug :put_layout, false when action in [:preview]
def index(conn, params) do
category = params |> Map.get("category")
posts = Posts.list_posts(params)

View file

@ -5,9 +5,9 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title><%= title(@view_module, @view_template, assigns) %></title>
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
<link phx-track-static rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
<%= feed_tag(@conn, @view_module, @view_template, assigns) %>
<script defer type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
<script phx-track-static defer type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
</head>
<body class="text-gray-800 antialiased">
<main role="main">

View file

@ -44,7 +44,7 @@ defmodule Legendary.Content.MixProject do
{:core, in_umbrella: true},
{:earmark, "1.4.3"},
{:excoveralls, "~> 0.10", only: [:dev, :test]},
{:floki, "~> 0.25.0"},
{:floki, ">= 0.30.0"},
{:gettext, "~> 0.11"},
{:jason, "~> 1.0"},
{:mock, "~> 0.3.0", only: :test},
@ -56,7 +56,7 @@ defmodule Legendary.Content.MixProject do
{:phoenix_html, "~> 2.11"},
{:phoenix_html_sanitizer, "~> 1.0.0"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_dashboard, "~> 0.2.0"},
{:phoenix_live_dashboard, "~> 0.4.0"},
{:php_serializer, "~> 0.9.0"},
{:plug_cowboy, "~> 2.0"},
{:sitemap, "~> 1.1"},

View file

@ -0,0 +1,45 @@
defmodule AuthWeb.Pow.ControllerCallbacks do
@moduledoc """
Hook into Pow Controllers to provide additional framework feature. In particular,
we disconnect any active live views when a user logs out. This will cause the
live view to re-connect with the new session environment.
"""
alias Pow.Extension.Phoenix.ControllerCallbacks
alias Plug.Conn
def before_respond(Pow.Phoenix.SessionController, :create, {:ok, conn}, config) do
user = conn.assigns.current_user
conn =
conn
|> Conn.put_session(:current_user_id, user.id)
|> Conn.put_session(:live_socket_id, "users_sockets:#{user.id}")
ControllerCallbacks.before_respond(
Pow.Phoenix.SessionController,
:create,
{:ok, conn},
config
)
end
def before_respond(Pow.Phoenix.SessionController, :delete, {:ok, conn}, config) do
live_socket_id = Conn.get_session(conn, :live_socket_id)
AppWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
conn =
conn
|> Conn.delete_session(:live_socket_id)
ControllerCallbacks.before_respond(
Pow.Phoenix.SessionController,
:delete,
{:ok, conn},
config
)
end
defdelegate before_respond(controller, action, results, config), to: ControllerCallbacks
defdelegate before_process(controller, action, results, config), to: ControllerCallbacks
end

View file

@ -1,7 +1,12 @@
defmodule Legendary.AuthWeb.Helpers do
def has_role?(conn = %Plug.Conn{}, role) do
conn
|> Pow.Plug.current_user()
def current_user(socket = %Phoenix.LiveView.Socket{assigns: %{current_user: user}}), do: user
def current_user(socket = %Phoenix.LiveView.Socket{assigns: %{__assigns__: %{current_user: user}}}), do: user
def current_user(%Phoenix.LiveView.Socket{}), do: nil
def current_user(conn), do: Pow.Plug.current_user(conn)
def has_role?(conn_or_socket, role) do
conn_or_socket
|> current_user()
|> Legendary.Auth.Roles.has_role?(role)
end
end

View file

@ -7,11 +7,8 @@ defmodule Legendary.CoreWeb.Helpers do
import Phoenix.Controller, only: [get_flash: 2]
import Legendary.CoreWeb.ErrorHelpers
def has_role?(conn = %Plug.Conn{}, role) do
conn
|> Pow.Plug.current_user()
|> Legendary.Auth.Roles.has_role?(role)
end
defdelegate current_user(a), to: Legendary.AuthWeb.Helpers
defdelegate has_role?(a, b), to: Legendary.AuthWeb.Helpers
def changeset_error_block(changeset) do
~E"""

View file

@ -150,7 +150,7 @@ defmodule Legendary.Core.MixProject do
{:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 2.11"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_dashboard, "~> 0.2.0"},
{:phoenix_live_dashboard, "~> 0.4.0"},
{:phoenix_pubsub, "~> 2.0"},
{:pow, "~> 1.0.23"},
{:telemetry_metrics, "~> 0.4"},

View file

@ -40,7 +40,7 @@ config :core, :pow,
user: Legendary.Auth.User,
repo: Legendary.Core.Repo,
extensions: [PowEmailConfirmation, PowPersistentSession, PowResetPassword],
controller_callbacks: Pow.Extension.Phoenix.ControllerCallbacks,
controller_callbacks: AuthWeb.Pow.ControllerCallbacks,
mailer_backend: Legendary.AuthWeb.Pow.Mailer,
web_mailer_module: Legendary.AuthWeb,
web_module: Legendary.AuthWeb,

View file

@ -27,7 +27,7 @@
"ex_prompt": {:hex, :ex_prompt, "0.1.5", "b136642d0962f8ea37b3c9fa185ad1f42c71c3b9c6c3950f0358d7f3d2db2970", [:mix], [], "hexpm", "ad19a404708c9c7b05d36090b2d074ceafbed248a8de1a22d45a05ebe6994b83"},
"excoveralls": {:hex, :excoveralls, "0.13.0", "4e1b7cc4e0351d8d16e9be21b0345a7e165798ee5319c7800b9138ce17e0b38e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "fe2a56c8909564e2e6764765878d7d5e141f2af3bc8ff3b018a68ee2a218fced"},
"file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"},
"floki": {:hex, :floki, "0.25.0", "b1c9ddf5f32a3a90b43b76f3386ca054325dc2478af020e87b5111c19f2284ac", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "631f4e627c46d5ecd347df5a2accdaf0621c77c3693c5b75a8ad58e84c61f242"},
"floki": {:hex, :floki, "0.31.0", "f05ee8a8e6a3ced4e62beeb2c79a63bc8e12ab98fbaaf6e6a3d9b76b1278e23f", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "b05afa372f5c345a5bf240ac25ea1f0f3d5fcfd7490ac0beeb4a203f9444891e"},
"fun_with_flags": {:hex, :fun_with_flags, "1.6.0", "507fcbc19374e83d34ba13d63b0816d37af952da1c6592978bbf40dad6a2e671", [:mix], [{:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: true]}, {:redix, "~> 1.0", [hex: :redix, repo: "hexpm", optional: true]}], "hexpm", "96b53f54737906c5a83b5d89922ca1145fda2d50db23b1f619478460b8d5f8d8"},
"fun_with_flags_ui": {:hex, :fun_with_flags_ui, "0.7.2", "c8df9e90f92481c014824ab1ff5db7d501ac34ec28a4599b76251ec5a6db0861", [:mix], [{:cowboy, ">= 1.0.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:fun_with_flags, "~> 1.1", [hex: :fun_with_flags, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "99b3635b067304722560ea3d5aab38ae23ea1217366c95ecb6263559488a22f2"},
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
@ -35,7 +35,7 @@
"gen_state_machine": {:hex, :gen_state_machine, "2.1.0", "a38b0e53fad812d29ec149f0d354da5d1bc0d7222c3711f3a0bd5aa608b42992", [:mix], [], "hexpm", "ae367038808db25cee2f2c4b8d0531522ea587c4995eb6f96ee73410a60fa06b"},
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
"hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"},
"html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.0.1", "2572e7122c78ab7e57b613e7c7f5e42bf9b3c25e430e32f23f1413d86db8a0af", [:mix], [{:mochiweb, "~> 2.12.2", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm", "c334e2835e094fb9c04658bd4cfc7533fa51a8f56f11343c57ab9cb2a01d8613"},
"idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
@ -60,9 +60,9 @@
"phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"},
"phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"},
"phoenix_html_sanitizer": {:hex, :phoenix_html_sanitizer, "1.0.2", "e2c8cfbc83660e362753de127cc957bec3442a8aecdf271fb65a684a906fccf5", [:mix], [{:html_sanitize_ex, "~> 1.0.0", [hex: :html_sanitize_ex, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "47aebb08fa954b7ad95f295fb701df9800ee3a489212119c9c6074a65e1e5a10"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.2.6", "1b4e1b7d797386b7f9d70d2af931dc9843a5f2f2423609d22cef1eec4e4dba7d", [:mix], [{:phoenix_html, "~> 2.14.1 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.13.1", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.4.0 or ~> 0.5.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "b20dcad98c4ca63d38a7f5e7a40936e1e8e9da983d3d722b88ae33afb866c9ca"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.4.0", "87990e68b60213d7487e65814046f9a2bed4a67886c943270125913499b3e5c3", [:mix], [{:ecto_psql_extras, "~> 0.4.1 or ~> 0.5", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.14.1 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.4.0 or ~> 0.5.0 or ~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "8d52149e58188e9e4497cc0d8900ab94d9b66f96998ec38c47c7a4f8f4f50e57"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.4", "940c0344b1d66a2e46eef02af3a70e0c5bb45a4db0bf47917add271b76cd3914", [: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", "38f9308357dea4cc77f247e216da99fcb0224e05ada1469167520bed4cb8cccd"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.13.3", "2186c55cc7c54ca45b97c6f28cfd267d1c61b5f205f3c83533704cd991bdfdec", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.4.17 or ~> 1.5.2", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "c6309a7da2e779cb9cdf2fb603d75f38f49ef324bedc7a81825998bd1744ff8a"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.15.7", "09720b8e5151b3ca8ef739cd7626d4feb987c69ba0b509c9bbdb861d5a365881", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 0.5", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a756cf662420272d0f1b3b908cce5222163b5a95aa9bab404f9d29aff53276e"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
"php_serializer": {:hex, :php_serializer, "0.9.2", "59c5fd6bd3096671fd89358fb8229341ac7423b50ad8d45a15213b02ea2edab2", [:mix], [], "hexpm", "34eb835a460944f7fc216773b363c02e7dcf8ac0390c9e9ccdbd92b31a7ca59a"},
"plug": {:hex, :plug, "1.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [:mix], [{:mime, "~> 1.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", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"},
@ -77,7 +77,7 @@
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm", "94884f84783fc1ba027aba8fe8a7dae4aad78c98e9f9c76667ec3471585c08c6"},
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.5.0", "1b796e74add83abf844e808564275dfb342bcc930b04c7577ab780e262b0d998", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31225e6ce7a37a421a0a96ec55244386aec1c190b22578bd245188a4a33298fd"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.0", "da9d49ee7e6bb1c259d36ce6539cd45ae14d81247a2b0c90edf55e2b50507f7b", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5cfe67ad464b243835512aa44321cee91faed6ea868d7fb761d7016e02915c3d"},
"telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"},
"timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"},
"tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"},