Resources
+-
+
- + Guides & Docs + +
- + Source + +
- + v1.5 Changelog + +
diff --git a/assets/css/app.scss b/assets/css/app.scss
index 5c2c9b5ca..f7775db94 100644
--- a/assets/css/app.scss
+++ b/assets/css/app.scss
@@ -1,5 +1,60 @@
/* This file is for your main application css. */
@import "./phoenix.css";
+@import "../node_modules/nprogress/nprogress.css";
+
+/* LiveView specific classes for your customizations */
+.phx-no-feedback.invalid-feedback,
+.phx-no-feedback .invalid-feedback {
+ display: none;
+}
+
+.phx-click-loading {
+ opacity: 0.5;
+ transition: opacity 1s ease-out;
+}
+
+.phx-disconnected{
+ cursor: wait;
+}
+.phx-disconnected *{
+ pointer-events: none;
+}
+
+.phx-modal {
+ opacity: 1!important;
+ position: fixed;
+ z-index: 1;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background-color: rgb(0,0,0);
+ background-color: rgba(0,0,0,0.4);
+}
+
+.phx-modal-content {
+ background-color: #fefefe;
+ margin: 15% auto;
+ padding: 20px;
+ border: 1px solid #888;
+ width: 80%;
+}
+
+.phx-modal-close {
+ color: #aaa;
+ float: right;
+ font-size: 28px;
+ font-weight: bold;
+}
+
+.phx-modal-close:hover,
+.phx-modal-close:focus {
+ color: black;
+ text-decoration: none;
+ cursor: pointer;
+}
+
/* Alerts and form errors */
.alert {
diff --git a/assets/js/app.js b/assets/js/app.js
index af0059649..dfc0f9759 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -13,3 +13,23 @@ import "../css/app.scss"
// import socket from "./socket"
//
import "phoenix_html"
+import {Socket} from "phoenix"
+import NProgress from "nprogress"
+import {LiveSocket} from "phoenix_live_view"
+
+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
+window.addEventListener("phx:page-loading-start", info => NProgress.start())
+window.addEventListener("phx:page-loading-stop", info => NProgress.done())
+
+// 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) // enabled for duration of browser session
+// >> liveSocket.disableLatencySim()
+window.liveSocket = liveSocket
+
diff --git a/assets/js/socket.js b/assets/js/socket.js
deleted file mode 100644
index 09929abcf..000000000
--- a/assets/js/socket.js
+++ /dev/null
@@ -1,63 +0,0 @@
-// NOTE: The contents of this file will only be executed if
-// you uncomment its entry in "assets/js/app.js".
-
-// To use Phoenix channels, the first step is to import Socket,
-// and connect at the socket path in "lib/web/endpoint.ex".
-//
-// Pass the token on params as below. Or remove it
-// from the params if you are not using authentication.
-import {Socket} from "phoenix"
-
-let socket = new Socket("/socket", {params: {token: window.userToken}})
-
-// When you connect, you'll often need to authenticate the client.
-// For example, imagine you have an authentication plug, `MyAuth`,
-// which authenticates the session and assigns a `:current_user`.
-// If the current user exists you can assign the user's token in
-// the connection for use in the layout.
-//
-// In your "lib/web/router.ex":
-//
-// pipeline :browser do
-// ...
-// plug MyAuth
-// plug :put_user_token
-// end
-//
-// defp put_user_token(conn, _) do
-// if current_user = conn.assigns[:current_user] do
-// token = Phoenix.Token.sign(conn, "user socket", current_user.id)
-// assign(conn, :user_token, token)
-// else
-// conn
-// end
-// end
-//
-// Now you need to pass this token to JavaScript. You can do so
-// inside a script tag in "lib/web/templates/layout/app.html.eex":
-//
-//
-//
-// You will need to verify the user token in the "connect/3" function
-// in "lib/web/channels/user_socket.ex":
-//
-// def connect(%{"token" => token}, socket, _connect_info) do
-// # max_age: 1209600 is equivalent to two weeks in seconds
-// case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do
-// {:ok, user_id} ->
-// {:ok, assign(socket, :user, user_id)}
-// {:error, reason} ->
-// :error
-// end
-// end
-//
-// Finally, connect to the socket:
-socket.connect()
-
-// Now that you are connected, you can join channels with a topic:
-let channel = socket.channel("topic:subtopic", {})
-channel.join()
- .receive("ok", resp => { console.log("Joined successfully", resp) })
- .receive("error", resp => { console.log("Unable to join", resp) })
-
-export default socket
diff --git a/assets/package-lock.json b/assets/package-lock.json
index 7a844cef1..4357a42f3 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -5138,6 +5138,11 @@
"set-blocking": "~2.0.0"
}
},
+ "nprogress": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",
+ "integrity": "sha1-y480xTIT2JVyP8urkH6UIq28r7E="
+ },
"nth-check": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
@@ -5471,6 +5476,9 @@
"phoenix_html": {
"version": "file:../deps/phoenix_html"
},
+ "phoenix_live_view": {
+ "version": "file:../deps/phoenix_live_view"
+ },
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
diff --git a/assets/package.json b/assets/package.json
index 170b4d539..aa41dee12 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -8,7 +8,9 @@
},
"dependencies": {
"phoenix": "file:../deps/phoenix",
- "phoenix_html": "file:../deps/phoenix_html"
+ "phoenix_html": "file:../deps/phoenix_html",
+ "phoenix_live_view": "file:../deps/phoenix_live_view",
+ "nprogress": "^0.2.0"
},
"devDependencies": {
"@babel/core": "^7.0.0",
diff --git a/config/config.exs b/config/config.exs
index 0f63e81e9..41e7cd6d7 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -10,10 +10,10 @@ use Mix.Config
# Configures the endpoint
config :live_book, LiveBookWeb.Endpoint,
url: [host: "localhost"],
- secret_key_base: "xDSK5nu5cynBegaOd+XedDm30Z4iuRnesn/x0MRAv6uxFx+NYiFTNU1gpfq4IkQE",
+ secret_key_base: "9hHHeOiAA8wrivUfuS//jQMurHxoMYUtF788BQMx2KO7mYUE8rVrGGG09djBNQq7",
render_errors: [view: LiveBookWeb.ErrorView, accepts: ~w(html json), layout: false],
pubsub_server: LiveBook.PubSub,
- live_view: [signing_salt: "1Z+NpfzU"]
+ live_view: [signing_salt: "mAPgPEM4"]
# Configures Elixir's Logger
config :logger, :console,
diff --git a/lib/live_book_web.ex b/lib/live_book_web.ex
index b68d2ad72..23abffb4a 100644
--- a/lib/live_book_web.ex
+++ b/lib/live_book_web.ex
@@ -41,12 +41,30 @@ defmodule LiveBookWeb do
end
end
+ def live_view do
+ quote do
+ use Phoenix.LiveView,
+ layout: {LiveBookWeb.LayoutView, "live.html"}
+
+ unquote(view_helpers())
+ end
+ end
+
+ def live_component do
+ quote do
+ use Phoenix.LiveComponent
+
+ unquote(view_helpers())
+ end
+ end
+
def router do
quote do
use Phoenix.Router
import Plug.Conn
import Phoenix.Controller
+ import Phoenix.LiveView.Router
end
end
@@ -61,6 +79,9 @@ defmodule LiveBookWeb do
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
+ # Import LiveView helpers (live_render, live_component, live_patch, etc)
+ import Phoenix.LiveView.Helpers
+
# Import basic rendering functionality (render, render_layout, etc)
import Phoenix.View
diff --git a/lib/live_book_web/endpoint.ex b/lib/live_book_web/endpoint.ex
index a39a7daa3..a08036570 100644
--- a/lib/live_book_web/endpoint.ex
+++ b/lib/live_book_web/endpoint.ex
@@ -7,9 +7,12 @@ defmodule LiveBookWeb.Endpoint do
@session_options [
store: :cookie,
key: "_live_book_key",
- signing_salt: "LZr3S4e9"
+ signing_salt: "SqUy8vWM"
]
+ socket "/live", Phoenix.LiveView.Socket,
+ websocket: [connect_info: [session: @session_options]]
+
# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phx.digest
diff --git a/lib/live_book_web/live/page_live.ex b/lib/live_book_web/live/page_live.ex
new file mode 100644
index 000000000..2905f3c3b
--- /dev/null
+++ b/lib/live_book_web/live/page_live.ex
@@ -0,0 +1,39 @@
+defmodule LiveBookWeb.PageLive do
+ use LiveBookWeb, :live_view
+
+ @impl true
+ def mount(_params, _session, socket) do
+ {:ok, assign(socket, query: "", results: %{})}
+ end
+
+ @impl true
+ def handle_event("suggest", %{"q" => query}, socket) do
+ {:noreply, assign(socket, results: search(query), query: query)}
+ end
+
+ @impl true
+ def handle_event("search", %{"q" => query}, socket) do
+ case search(query) do
+ %{^query => vsn} ->
+ {:noreply, redirect(socket, external: "https://hexdocs.pm/#{query}/#{vsn}")}
+
+ _ ->
+ {:noreply,
+ socket
+ |> put_flash(:error, "No dependencies found matching \"#{query}\"")
+ |> assign(results: %{}, query: query)}
+ end
+ end
+
+ defp search(query) do
+ if not LiveBookWeb.Endpoint.config(:code_reloader) do
+ raise "action disabled when not in development"
+ end
+
+ for {app, desc, vsn} <- Application.started_applications(),
+ app = to_string(app),
+ String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS"),
+ into: %{},
+ do: {app, vsn}
+ end
+end
diff --git a/lib/live_book_web/live/page_live.html.leex b/lib/live_book_web/live/page_live.html.leex
new file mode 100644
index 000000000..f116c9cb2
--- /dev/null
+++ b/lib/live_book_web/live/page_live.html.leex
@@ -0,0 +1,48 @@
+ Peace of mind from prototype to productionWelcome to Phoenix!
+ Resources
+
+
+ Help
+
+
+
<%= get_flash(@conn, :info) %>
-<%= get_flash(@conn, :error) %>
- <%= @inner_content %> -<%= get_flash(@conn, :info) %>
+<%= get_flash(@conn, :error) %>
+ <%= @inner_content %> +<%= live_flash(@flash, :info) %>
+ +<%= live_flash(@flash, :error) %>
+ + <%= @inner_content %> +