checkin of first version
This commit is contained in:
9
lib/live_view_counter.ex
Normal file
9
lib/live_view_counter.ex
Normal file
@@ -0,0 +1,9 @@
|
||||
defmodule LiveViewCounter do
|
||||
@moduledoc """
|
||||
LiveViewCounter keeps the contexts that define your domain
|
||||
and business logic.
|
||||
|
||||
Contexts are also responsible for managing your data, regardless
|
||||
if it comes from the database, an external API or others.
|
||||
"""
|
||||
end
|
||||
34
lib/live_view_counter/application.ex
Normal file
34
lib/live_view_counter/application.ex
Normal file
@@ -0,0 +1,34 @@
|
||||
defmodule LiveViewCounter.Application do
|
||||
# See https://hexdocs.pm/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@moduledoc false
|
||||
|
||||
use Application
|
||||
|
||||
@impl true
|
||||
def start(_type, _args) do
|
||||
children = [
|
||||
# Start the Telemetry supervisor
|
||||
LiveViewCounterWeb.Telemetry,
|
||||
# Start the PubSub system
|
||||
{Phoenix.PubSub, name: LiveViewCounter.PubSub},
|
||||
# Start the Endpoint (http/https)
|
||||
LiveViewCounterWeb.Endpoint
|
||||
# Start a worker by calling: LiveViewCounter.Worker.start_link(arg)
|
||||
# {LiveViewCounter.Worker, arg}
|
||||
]
|
||||
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: LiveViewCounter.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
|
||||
# Tell Phoenix to update the endpoint configuration
|
||||
# whenever the application is updated.
|
||||
@impl true
|
||||
def config_change(changed, _new, removed) do
|
||||
LiveViewCounterWeb.Endpoint.config_change(changed, removed)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
3
lib/live_view_counter/mailer.ex
Normal file
3
lib/live_view_counter/mailer.ex
Normal file
@@ -0,0 +1,3 @@
|
||||
defmodule LiveViewCounter.Mailer do
|
||||
use Swoosh.Mailer, otp_app: :live_view_counter
|
||||
end
|
||||
110
lib/live_view_counter_web.ex
Normal file
110
lib/live_view_counter_web.ex
Normal file
@@ -0,0 +1,110 @@
|
||||
defmodule LiveViewCounterWeb do
|
||||
@moduledoc """
|
||||
The entrypoint for defining your web interface, such
|
||||
as controllers, views, channels and so on.
|
||||
|
||||
This can be used in your application as:
|
||||
|
||||
use LiveViewCounterWeb, :controller
|
||||
use LiveViewCounterWeb, :view
|
||||
|
||||
The definitions below will be executed for every view,
|
||||
controller, etc, so keep them short and clean, focused
|
||||
on imports, uses and aliases.
|
||||
|
||||
Do NOT define functions inside the quoted expressions
|
||||
below. Instead, define any helper function in modules
|
||||
and import those modules here.
|
||||
"""
|
||||
|
||||
def controller do
|
||||
quote do
|
||||
use Phoenix.Controller, namespace: LiveViewCounterWeb
|
||||
|
||||
import Plug.Conn
|
||||
import LiveViewCounterWeb.Gettext
|
||||
alias LiveViewCounterWeb.Router.Helpers, as: Routes
|
||||
end
|
||||
end
|
||||
|
||||
def view do
|
||||
quote do
|
||||
use Phoenix.View,
|
||||
root: "lib/live_view_counter_web/templates",
|
||||
namespace: LiveViewCounterWeb
|
||||
|
||||
# Import convenience functions from controllers
|
||||
import Phoenix.Controller,
|
||||
only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
|
||||
|
||||
# Include shared imports and aliases for views
|
||||
unquote(view_helpers())
|
||||
end
|
||||
end
|
||||
|
||||
def live_view do
|
||||
quote do
|
||||
use Phoenix.LiveView,
|
||||
layout: {LiveViewCounterWeb.LayoutView, "live.html"}
|
||||
|
||||
unquote(view_helpers())
|
||||
end
|
||||
end
|
||||
|
||||
def live_component do
|
||||
quote do
|
||||
use Phoenix.LiveComponent
|
||||
|
||||
unquote(view_helpers())
|
||||
end
|
||||
end
|
||||
|
||||
def component do
|
||||
quote do
|
||||
use Phoenix.Component
|
||||
|
||||
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
|
||||
|
||||
def channel do
|
||||
quote do
|
||||
use Phoenix.Channel
|
||||
import LiveViewCounterWeb.Gettext
|
||||
end
|
||||
end
|
||||
|
||||
defp view_helpers do
|
||||
quote do
|
||||
# Use all HTML functionality (forms, tags, etc)
|
||||
use Phoenix.HTML
|
||||
|
||||
# Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
|
||||
import Phoenix.LiveView.Helpers
|
||||
|
||||
# Import basic rendering functionality (render, render_layout, etc)
|
||||
import Phoenix.View
|
||||
|
||||
import LiveViewCounterWeb.ErrorHelpers
|
||||
import LiveViewCounterWeb.Gettext
|
||||
alias LiveViewCounterWeb.Router.Helpers, as: Routes
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
When used, dispatch to the appropriate controller/view/etc.
|
||||
"""
|
||||
defmacro __using__(which) when is_atom(which) do
|
||||
apply(__MODULE__, which, [])
|
||||
end
|
||||
end
|
||||
7
lib/live_view_counter_web/controllers/page_controller.ex
Normal file
7
lib/live_view_counter_web/controllers/page_controller.ex
Normal file
@@ -0,0 +1,7 @@
|
||||
defmodule LiveViewCounterWeb.PageController do
|
||||
use LiveViewCounterWeb, :controller
|
||||
|
||||
def index(conn, _params) do
|
||||
render(conn, "index.html")
|
||||
end
|
||||
end
|
||||
49
lib/live_view_counter_web/endpoint.ex
Normal file
49
lib/live_view_counter_web/endpoint.ex
Normal file
@@ -0,0 +1,49 @@
|
||||
defmodule LiveViewCounterWeb.Endpoint do
|
||||
use Phoenix.Endpoint, otp_app: :live_view_counter
|
||||
|
||||
# The session will be stored in the cookie and signed,
|
||||
# this means its contents can be read but not tampered with.
|
||||
# Set :encryption_salt if you would also like to encrypt it.
|
||||
@session_options [
|
||||
store: :cookie,
|
||||
key: "_live_view_counter_key",
|
||||
signing_salt: "HrsQSAvQ"
|
||||
]
|
||||
|
||||
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
|
||||
# when deploying your static files in production.
|
||||
plug Plug.Static,
|
||||
at: "/",
|
||||
from: :live_view_counter,
|
||||
gzip: false,
|
||||
only: ~w(assets fonts images favicon.ico robots.txt)
|
||||
|
||||
# Code reloading can be explicitly enabled under the
|
||||
# :code_reloader configuration of your endpoint.
|
||||
if code_reloading? do
|
||||
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
|
||||
plug Phoenix.LiveReloader
|
||||
plug Phoenix.CodeReloader
|
||||
end
|
||||
|
||||
plug Phoenix.LiveDashboard.RequestLogger,
|
||||
param_key: "request_logger",
|
||||
cookie_key: "request_logger"
|
||||
|
||||
plug Plug.RequestId
|
||||
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
|
||||
|
||||
plug Plug.Parsers,
|
||||
parsers: [:urlencoded, :multipart, :json],
|
||||
pass: ["*/*"],
|
||||
json_decoder: Phoenix.json_library()
|
||||
|
||||
plug Plug.MethodOverride
|
||||
plug Plug.Head
|
||||
plug Plug.Session, @session_options
|
||||
plug LiveViewCounterWeb.Router
|
||||
end
|
||||
24
lib/live_view_counter_web/gettext.ex
Normal file
24
lib/live_view_counter_web/gettext.ex
Normal file
@@ -0,0 +1,24 @@
|
||||
defmodule LiveViewCounterWeb.Gettext do
|
||||
@moduledoc """
|
||||
A module providing Internationalization with a gettext-based API.
|
||||
|
||||
By using [Gettext](https://hexdocs.pm/gettext),
|
||||
your module gains a set of macros for translations, for example:
|
||||
|
||||
import LiveViewCounterWeb.Gettext
|
||||
|
||||
# Simple translation
|
||||
gettext("Here is the string to translate")
|
||||
|
||||
# Plural translation
|
||||
ngettext("Here is the string to translate",
|
||||
"Here are the strings to translate",
|
||||
3)
|
||||
|
||||
# Domain-based translation
|
||||
dgettext("errors", "Here is the error message to translate")
|
||||
|
||||
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
|
||||
"""
|
||||
use Gettext, otp_app: :live_view_counter
|
||||
end
|
||||
25
lib/live_view_counter_web/live/counter.ex
Normal file
25
lib/live_view_counter_web/live/counter.ex
Normal file
@@ -0,0 +1,25 @@
|
||||
defmodule LiveViewCounterWeb.Counter do
|
||||
use Phoenix.LiveView
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, assign(socket, :val, 0)}
|
||||
end
|
||||
|
||||
def handle_event("inc", _, socket) do
|
||||
{:noreply, update(socket, :val, &(&1 + 1))}
|
||||
end
|
||||
|
||||
def handle_event("dec", _, socket) do
|
||||
{:noreply, update(socket, :val, &(&1 - 1))}
|
||||
end
|
||||
|
||||
def render(assigns) do
|
||||
~L"""
|
||||
<div>
|
||||
<h1>The count is: <%= @val %></h1>
|
||||
<button phx-click="dec">-</button>
|
||||
<button phx-click="inc">+</button>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
||||
55
lib/live_view_counter_web/router.ex
Normal file
55
lib/live_view_counter_web/router.ex
Normal file
@@ -0,0 +1,55 @@
|
||||
defmodule LiveViewCounterWeb.Router do
|
||||
use LiveViewCounterWeb, :router
|
||||
|
||||
pipeline :browser do
|
||||
plug :accepts, ["html"]
|
||||
plug :fetch_session
|
||||
plug :fetch_live_flash
|
||||
plug :put_root_layout, {LiveViewCounterWeb.LayoutView, :root}
|
||||
plug :protect_from_forgery
|
||||
plug :put_secure_browser_headers
|
||||
end
|
||||
|
||||
pipeline :api do
|
||||
plug :accepts, ["json"]
|
||||
end
|
||||
|
||||
scope "/", LiveViewCounterWeb do
|
||||
pipe_through :browser
|
||||
live("/", Counter)
|
||||
end
|
||||
|
||||
# Other scopes may use custom stacks.
|
||||
# scope "/api", LiveViewCounterWeb do
|
||||
# pipe_through :api
|
||||
# end
|
||||
|
||||
# Enables LiveDashboard only for development
|
||||
#
|
||||
# If you want to use the LiveDashboard in production, you should put
|
||||
# it behind authentication and allow only admins to access it.
|
||||
# If your application does not have an admins-only section yet,
|
||||
# you can use Plug.BasicAuth to set up some basic authentication
|
||||
# as long as you are also using SSL (which you should anyway).
|
||||
if Mix.env() in [:dev, :test] do
|
||||
import Phoenix.LiveDashboard.Router
|
||||
|
||||
scope "/" do
|
||||
pipe_through :browser
|
||||
|
||||
live_dashboard "/dashboard", metrics: LiveViewCounterWeb.Telemetry
|
||||
end
|
||||
end
|
||||
|
||||
# Enables the Swoosh mailbox preview in development.
|
||||
#
|
||||
# Note that preview only shows emails that were sent by the same
|
||||
# node running the Phoenix server.
|
||||
if Mix.env() == :dev do
|
||||
scope "/dev" do
|
||||
pipe_through :browser
|
||||
|
||||
forward "/mailbox", Plug.Swoosh.MailboxPreview
|
||||
end
|
||||
end
|
||||
end
|
||||
48
lib/live_view_counter_web/telemetry.ex
Normal file
48
lib/live_view_counter_web/telemetry.ex
Normal file
@@ -0,0 +1,48 @@
|
||||
defmodule LiveViewCounterWeb.Telemetry do
|
||||
use Supervisor
|
||||
import Telemetry.Metrics
|
||||
|
||||
def start_link(arg) do
|
||||
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(_arg) do
|
||||
children = [
|
||||
# Telemetry poller will execute the given period measurements
|
||||
# every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
|
||||
{:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
|
||||
# Add reporters as children of your supervision tree.
|
||||
# {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
|
||||
]
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
end
|
||||
|
||||
def metrics do
|
||||
[
|
||||
# Phoenix Metrics
|
||||
summary("phoenix.endpoint.stop.duration",
|
||||
unit: {:native, :millisecond}
|
||||
),
|
||||
summary("phoenix.router_dispatch.stop.duration",
|
||||
tags: [:route],
|
||||
unit: {:native, :millisecond}
|
||||
),
|
||||
|
||||
# VM Metrics
|
||||
summary("vm.memory.total", unit: {:byte, :kilobyte}),
|
||||
summary("vm.total_run_queue_lengths.total"),
|
||||
summary("vm.total_run_queue_lengths.cpu"),
|
||||
summary("vm.total_run_queue_lengths.io")
|
||||
]
|
||||
end
|
||||
|
||||
defp periodic_measurements do
|
||||
[
|
||||
# A module, function and arguments to be invoked periodically.
|
||||
# This function must call :telemetry.execute/3 and a metric must be added above.
|
||||
# {LiveViewCounterWeb, :count_users, []}
|
||||
]
|
||||
end
|
||||
end
|
||||
5
lib/live_view_counter_web/templates/layout/app.html.heex
Normal file
5
lib/live_view_counter_web/templates/layout/app.html.heex
Normal file
@@ -0,0 +1,5 @@
|
||||
<main class="container">
|
||||
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
|
||||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
||||
<%= @inner_content %>
|
||||
</main>
|
||||
11
lib/live_view_counter_web/templates/layout/live.html.heex
Normal file
11
lib/live_view_counter_web/templates/layout/live.html.heex
Normal file
@@ -0,0 +1,11 @@
|
||||
<main class="container">
|
||||
<p class="alert alert-info" role="alert"
|
||||
phx-click="lv:clear-flash"
|
||||
phx-value-key="info"><%= live_flash(@flash, :info) %></p>
|
||||
|
||||
<p class="alert alert-danger" role="alert"
|
||||
phx-click="lv:clear-flash"
|
||||
phx-value-key="error"><%= live_flash(@flash, :error) %></p>
|
||||
|
||||
<%= @inner_content %>
|
||||
</main>
|
||||
30
lib/live_view_counter_web/templates/layout/root.html.heex
Normal file
30
lib/live_view_counter_web/templates/layout/root.html.heex
Normal file
@@ -0,0 +1,30 @@
|
||||
<!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"/>
|
||||
<meta name="csrf-token" content={csrf_token_value()}>
|
||||
<%= live_title_tag assigns[:page_title] || "LiveViewCounter", suffix: " · Phoenix Framework" %>
|
||||
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")}/>
|
||||
<script defer phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/assets/app.js")}></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<section class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
|
||||
<%= if function_exported?(Routes, :live_dashboard_path, 2) do %>
|
||||
<li><%= link "LiveDashboard", to: Routes.live_dashboard_path(@conn, :home) %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</nav>
|
||||
<a href="https://phoenixframework.org/" class="phx-logo">
|
||||
<img src={Routes.static_path(@conn, "/images/phoenix.png")} alt="Phoenix Framework Logo"/>
|
||||
</a>
|
||||
</section>
|
||||
</header>
|
||||
<%= @inner_content %>
|
||||
</body>
|
||||
</html>
|
||||
41
lib/live_view_counter_web/templates/page/index.html.heex
Normal file
41
lib/live_view_counter_web/templates/page/index.html.heex
Normal file
@@ -0,0 +1,41 @@
|
||||
<section class="phx-hero">
|
||||
<h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
|
||||
<p>Peace of mind from prototype to production</p>
|
||||
</section>
|
||||
|
||||
<section class="row">
|
||||
<article class="column">
|
||||
<h2>Resources</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://hexdocs.pm/phoenix/overview.html">Guides & Docs</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/phoenixframework/phoenix">Source</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/phoenixframework/phoenix/blob/v1.6/CHANGELOG.md">v1.6 Changelog</a>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="column">
|
||||
<h2>Help</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://elixirforum.com/c/phoenix-forum">Forum</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://web.libera.chat/#elixir">#elixir on Libera Chat (IRC)</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://twitter.com/elixirphoenix">Twitter @elixirphoenix</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://elixir-slackin.herokuapp.com/">Elixir on Slack</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://discord.gg/elixir">Elixir on Discord</a>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</section>
|
||||
47
lib/live_view_counter_web/views/error_helpers.ex
Normal file
47
lib/live_view_counter_web/views/error_helpers.ex
Normal file
@@ -0,0 +1,47 @@
|
||||
defmodule LiveViewCounterWeb.ErrorHelpers do
|
||||
@moduledoc """
|
||||
Conveniences for translating and building error messages.
|
||||
"""
|
||||
|
||||
use Phoenix.HTML
|
||||
|
||||
@doc """
|
||||
Generates tag for inlined form input errors.
|
||||
"""
|
||||
def error_tag(form, field) do
|
||||
Enum.map(Keyword.get_values(form.errors, field), fn error ->
|
||||
content_tag(:span, translate_error(error),
|
||||
class: "invalid-feedback",
|
||||
phx_feedback_for: input_name(form, field)
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Translates an error message using gettext.
|
||||
"""
|
||||
def translate_error({msg, opts}) do
|
||||
# When using gettext, we typically pass the strings we want
|
||||
# to translate as a static argument:
|
||||
#
|
||||
# # Translate "is invalid" in the "errors" domain
|
||||
# dgettext("errors", "is invalid")
|
||||
#
|
||||
# # Translate the number of files with plural rules
|
||||
# dngettext("errors", "1 file", "%{count} files", count)
|
||||
#
|
||||
# Because the error messages we show in our forms and APIs
|
||||
# are defined inside Ecto, we need to translate them dynamically.
|
||||
# This requires us to call the Gettext module passing our gettext
|
||||
# backend as first argument.
|
||||
#
|
||||
# Note we use the "errors" domain, which means translations
|
||||
# should be written to the errors.po file. The :count option is
|
||||
# set by Ecto and indicates we should also apply plural rules.
|
||||
if count = opts[:count] do
|
||||
Gettext.dngettext(LiveViewCounterWeb.Gettext, "errors", msg, msg, count, opts)
|
||||
else
|
||||
Gettext.dgettext(LiveViewCounterWeb.Gettext, "errors", msg, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
16
lib/live_view_counter_web/views/error_view.ex
Normal file
16
lib/live_view_counter_web/views/error_view.ex
Normal file
@@ -0,0 +1,16 @@
|
||||
defmodule LiveViewCounterWeb.ErrorView do
|
||||
use LiveViewCounterWeb, :view
|
||||
|
||||
# If you want to customize a particular status code
|
||||
# for a certain format, you may uncomment below.
|
||||
# def render("500.html", _assigns) do
|
||||
# "Internal Server Error"
|
||||
# end
|
||||
|
||||
# By default, Phoenix returns the status message from
|
||||
# the template name. For example, "404.html" becomes
|
||||
# "Not Found".
|
||||
def template_not_found(template, _assigns) do
|
||||
Phoenix.Controller.status_message_from_template(template)
|
||||
end
|
||||
end
|
||||
7
lib/live_view_counter_web/views/layout_view.ex
Normal file
7
lib/live_view_counter_web/views/layout_view.ex
Normal file
@@ -0,0 +1,7 @@
|
||||
defmodule LiveViewCounterWeb.LayoutView do
|
||||
use LiveViewCounterWeb, :view
|
||||
|
||||
# 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}}
|
||||
end
|
||||
3
lib/live_view_counter_web/views/page_view.ex
Normal file
3
lib/live_view_counter_web/views/page_view.ex
Normal file
@@ -0,0 +1,3 @@
|
||||
defmodule LiveViewCounterWeb.PageView do
|
||||
use LiveViewCounterWeb, :view
|
||||
end
|
||||
Reference in New Issue
Block a user