Add talk timer
This commit is contained in:
parent
d98b916845
commit
8e15e168ca
4
.formatter.exs
Normal file
4
.formatter.exs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Used by "mix format"
|
||||||
|
[
|
||||||
|
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||||
|
]
|
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# The directory Mix will write compiled artifacts to.
|
||||||
|
/_build/
|
||||||
|
|
||||||
|
# If you run "mix test --cover", coverage assets end up here.
|
||||||
|
/cover/
|
||||||
|
|
||||||
|
# The directory Mix downloads your dependencies sources to.
|
||||||
|
/deps/
|
||||||
|
|
||||||
|
# Where third-party dependencies like ExDoc output generated docs.
|
||||||
|
/doc/
|
||||||
|
|
||||||
|
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||||
|
/.fetch
|
||||||
|
|
||||||
|
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||||
|
erl_crash.dump
|
||||||
|
|
||||||
|
# Also ignore archive artifacts (built via "mix archive.build").
|
||||||
|
*.ez
|
||||||
|
|
||||||
|
# Ignore package tarball (built via "mix hex.build").
|
||||||
|
ranch_talk-*.tar
|
||||||
|
|
||||||
|
# Temporary files, for example, from tests.
|
||||||
|
/tmp/
|
21
README.md
Normal file
21
README.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# RanchTalk
|
||||||
|
|
||||||
|
**TODO: Add description**
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||||
|
by adding `ranch_talk` to your list of dependencies in `mix.exs`:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
def deps do
|
||||||
|
[
|
||||||
|
{:ranch_talk, "~> 0.1.0"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||||
|
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||||
|
be found at <https://hexdocs.pm/ranch_talk>.
|
||||||
|
|
18
lib/ranch_talk.ex
Normal file
18
lib/ranch_talk.ex
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
defmodule RanchTalk do
|
||||||
|
@moduledoc """
|
||||||
|
Documentation for `RanchTalk`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Hello world.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> RanchTalk.hello()
|
||||||
|
:world
|
||||||
|
|
||||||
|
"""
|
||||||
|
def hello do
|
||||||
|
:world
|
||||||
|
end
|
||||||
|
end
|
105
lib/ranch_talk/talk_timer.ex
Normal file
105
lib/ranch_talk/talk_timer.ex
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
defmodule RanchTalk.TalkTimer do
|
||||||
|
use Kino.JS
|
||||||
|
use Kino.JS.Live
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
defmodule Opts do
|
||||||
|
@ten_minutes_in_seconds 60 * 10
|
||||||
|
|
||||||
|
defstruct done_message: "Timer finished!",
|
||||||
|
seconds_to_count: @ten_minutes_in_seconds
|
||||||
|
end
|
||||||
|
|
||||||
|
@default_opts %{
|
||||||
|
done_message: "Timer finished!",
|
||||||
|
seconds_to_count: 600
|
||||||
|
}
|
||||||
|
|
||||||
|
@spec new(opts :: Opts.t()) :: Kino.JS.Live.t()
|
||||||
|
def new(opts) do
|
||||||
|
state =
|
||||||
|
@default_opts
|
||||||
|
|> Map.merge(opts)
|
||||||
|
|> update_state_html()
|
||||||
|
|
||||||
|
Kino.JS.Live.new(__MODULE__, state)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_html(widget, html) do
|
||||||
|
Kino.JS.Live.cast(widget, {:set_html, html})
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_state(widget) do
|
||||||
|
Kino.JS.Live.call(widget, :get_state)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def init(state, ctx) do
|
||||||
|
{:ok, assign(ctx, state)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def tick(widget) do
|
||||||
|
state = get_state(widget)
|
||||||
|
tick(widget, state)
|
||||||
|
end
|
||||||
|
|
||||||
|
def tick(widget, %{seconds_to_count: seconds_to_count, done_message: done_message} = state) do
|
||||||
|
if seconds_to_count < 1 do
|
||||||
|
set_html(widget, done_message)
|
||||||
|
Kino.JS.Live.cast(widget, :timer_finished)
|
||||||
|
else
|
||||||
|
new_state =
|
||||||
|
state
|
||||||
|
|> Map.put(:seconds_to_count, seconds_to_count - 1)
|
||||||
|
|> update_state_html()
|
||||||
|
|
||||||
|
set_html(widget, state.html)
|
||||||
|
Process.sleep(1000)
|
||||||
|
tick(widget, new_state)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_connect(ctx) do
|
||||||
|
{:ok, ctx.assigns, ctx}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_cast({:set_html, html}, ctx) do
|
||||||
|
broadcast_event(ctx, "set_html", html)
|
||||||
|
{:noreply, assign(ctx, html: html)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_cast(:timer_finished, ctx) do
|
||||||
|
broadcast_event(ctx, "timer_finished", ctx.assigns.done_message)
|
||||||
|
{:noreply, ctx}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_call(:get_state, _from, ctx) do
|
||||||
|
{:reply, ctx.assigns, ctx}
|
||||||
|
end
|
||||||
|
|
||||||
|
def start(widget) do
|
||||||
|
spawn(fn -> tick(widget) end)
|
||||||
|
widget
|
||||||
|
end
|
||||||
|
|
||||||
|
defp update_state_html(%{seconds_to_count: seconds} = state) do
|
||||||
|
minutes = seconds |> div(60) |> to_string()
|
||||||
|
seconds = seconds |> rem(60) |> to_string() |> String.pad_leading(2, "0")
|
||||||
|
Map.put(state, :html, Enum.join([minutes, ":", seconds]))
|
||||||
|
end
|
||||||
|
|
||||||
|
asset "main.js" do
|
||||||
|
"""
|
||||||
|
export function init(ctx, state) {
|
||||||
|
ctx.root.innerHTML = state.html;
|
||||||
|
ctx.handleEvent("set_html", html => ctx.root.innerHTML = html)
|
||||||
|
ctx.handleEvent("timer_finished", message => alert(message))
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
25
mix.exs
Normal file
25
mix.exs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
defmodule RanchTalk.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :ranch_talk,
|
||||||
|
version: "0.1.0",
|
||||||
|
elixir: "~> 1.13",
|
||||||
|
start_permanent: Mix.env() == :prod,
|
||||||
|
deps: deps()
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def application do
|
||||||
|
[
|
||||||
|
extra_applications: [:logger]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp deps do
|
||||||
|
[
|
||||||
|
{:kino, "~> 0.5.2"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
3
mix.lock
Normal file
3
mix.lock
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
%{
|
||||||
|
"kino": {:hex, :kino, "0.5.2", "42716edc9e41f17325f81a30985f81366cc673dfa5c559f58b8d7f8560b62212", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:vega_lite, "~> 0.1.0", [hex: :vega_lite, repo: "hexpm", optional: true]}], "hexpm", "0911fb49ea6a26b4ac2ee10a075d9a08f5c8e5b5b9bbd385b7fb06f811c9949b"},
|
||||||
|
}
|
23
ranch-talk.livemd
Normal file
23
ranch-talk.livemd
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# About Ranch
|
||||||
|
|
||||||
|
## Talk Timer
|
||||||
|
|
||||||
|
This talk is supposed to be 5-15 minutes long, so let's make sure we keep it that way with a timer and a super annoying alert!
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
alias RanchTalk.TalkTimer
|
||||||
|
|
||||||
|
%{
|
||||||
|
# 10 minutes
|
||||||
|
seconds_to_count: 3,
|
||||||
|
done_message: "Time's up! Shut up and get back to work!"
|
||||||
|
}
|
||||||
|
|> TalkTimer.new()
|
||||||
|
|> TalkTimer.start()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ranch Overview
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
:observer.start()
|
||||||
|
```
|
29
readme.md
29
readme.md
|
@ -1 +1,30 @@
|
||||||
# ranch-talk
|
# ranch-talk
|
||||||
|
|
||||||
|
I was asked to give a 5-15 minute talk on [Ranch][ranch], a TCP socket acceptor
|
||||||
|
pool, to show how OTP constructs are used in the real world and expose some of
|
||||||
|
[Phoenix's][phoenix] underpinnings. This [Livebook][livebook] contains my code
|
||||||
|
and notes for that talk.
|
||||||
|
|
||||||
|
Thanks to [Divvy][divvy] for inviting me to give this talk.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
Install and run a local Livebook in `attached` mode and automatically grab my
|
||||||
|
code:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mix escript.install github livebook-dev/livebook
|
||||||
|
git clone https://git.lyte.dev/lytedev/ranch-talk.git
|
||||||
|
mix deps.get
|
||||||
|
env LIVEBOOK_PORT=5588 LIVEBOOK_IFRAME_PORT=5589 \
|
||||||
|
livebook server --name ranch_is_neat@localhost --cookie yes-please \
|
||||||
|
--default-runtime attached:ranch_is_neat@localhost:yes-please \
|
||||||
|
"$(pwd)/ranch-talk.livemd"
|
||||||
|
```
|
||||||
|
|
||||||
|
Enjoy!
|
||||||
|
|
||||||
|
[ranch]: https://github.com/ninenines/ranch
|
||||||
|
[phoenix]: https://www.phoenixframework.org/
|
||||||
|
[livebook]: https://github.com/livebook-dev/livebook
|
||||||
|
[divvy]: https://getdivvy.com/
|
||||||
|
|
Loading…
Reference in a new issue