<%= if i < 3 and @separator != [] do %>
<% separator_struct = %Separator{id: "timer:#{@id}:sep:#{i}", dir: @dir, orientation: @orientation, hidden: hv} %>
{render_slot(@separator)}
<% end %>
<% end %>
"""
end
defp label_slot(assigns, :days), do: assigns.day_label
defp label_slot(assigns, :hours), do: assigns.hour_label
defp label_slot(assigns, :minutes), do: assigns.minute_label
defp label_slot(assigns, :seconds), do: assigns.second_label
defp props_struct(assigns, segments) do
%Props{
id: assigns.id,
countdown: assigns.countdown,
start_ms: assigns.start_ms,
target_ms: assigns.target_ms,
auto_start: assigns.auto_start,
interval: assigns.interval,
on_tick: assigns.on_tick,
on_tick_client: assigns.on_tick_client,
on_complete: assigns.on_complete,
on_complete_client: assigns.on_complete_client,
dir: assigns.dir,
orientation: assigns.orientation,
collapse_leading_zeros: assigns.collapse_leading_zeros,
segments: segments,
translation: assigns.translation
}
end
defp normalize_segments(nil), do: nil
defp normalize_segments([]) do
raise ArgumentError, "Corex.Timer: segments must not be empty when provided"
end
defp normalize_segments(list) when is_list(list) do
allowed = MapSet.new(@parts)
unless MapSet.subset?(MapSet.new(list), allowed) do
raise ArgumentError, "Corex.Timer: segments must be a subset of #{inspect(@parts)}"
end
idx = fn atom -> Enum.find_index(@parts, &(&1 == atom)) end
indexes =
list
|> Enum.map(fn a ->
case idx.(a) do
nil -> raise ArgumentError, "Corex.Timer: invalid segment #{inspect(a)}"
i -> i
end
end)
unless indexes == Enum.sort(indexes) do
raise ArgumentError, "Corex.Timer: segments must follow order #{inspect(@parts)}"
end
list
end
defp visibility_hidden(_countdown, _collapse_opt, segments, _time_values)
when is_list(segments) do
Enum.map(@parts, fn atom -> atom not in segments end)
end
defp visibility_hidden(countdown, collapse_opt, nil, time_values) do
vals = Enum.map(@parts, &Map.fetch!(time_values, &1))
cond do
collapse_opt == false ->
[false, false, false, false]
collapse_opt == true ->
start_i = collapse_start_index(vals)
Enum.map(0..3, fn i -> i < start_i end)
countdown ->
start_i = collapse_start_index(vals)
Enum.map(0..3, fn i -> i < start_i end)
true ->
[false, false, false, false]
end
end
defp collapse_start_index(vals) do
collapse_start_index(vals, 0)
end
defp collapse_start_index(_vals, idx) when idx > 2 do
idx
end
defp collapse_start_index(vals, idx) do
rest_after = length(vals) - idx
if idx < 3 && Enum.at(vals, idx) == 0 && rest_after > 2 do
collapse_start_index(vals, idx + 1)
else
idx
end
end
api_doc(~S"""
Start the timer from `phx-click`. Dispatches `corex:timer:start` on the timer root (`id` matches the DOM host).
```heex
<.action phx-click={Corex.Timer.start("my-timer")}>Start
<.timer id="my-timer" start_ms={60_000} class="timer">
```
```javascript
document.getElementById("my-timer")?.dispatchEvent(new CustomEvent("corex:timer:start", { bubbles: false }));
```
""")
def start(timer_id) when is_binary(timer_id) do
JS.dispatch("corex:timer:start", to: "##{timer_id}", bubbles: false)
end
api_doc(~S"""
Start the timer from `handle_event` via [`push_event/3`](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#push_event/3) (`timer_start`).
```elixir
def handle_event("start_timer", _params, socket) do
{:noreply, Corex.Timer.start(socket, "my-timer")}
end
```
""")
def start(socket, timer_id)
when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(timer_id) do
LiveView.push_event(socket, "timer_start", %{id: timer_id})
end
api_doc(~S"""
Pause the timer from `phx-click`. Dispatches `corex:timer:pause`.
```heex
<.action phx-click={Corex.Timer.pause("my-timer")}>Pause
<.timer id="my-timer" start_ms={60_000} class="timer">
```
""")
def pause(timer_id) when is_binary(timer_id) do
JS.dispatch("corex:timer:pause", to: "##{timer_id}", bubbles: false)
end
api_doc(~S"""
Pause the timer from `handle_event` (`timer_pause`).
```elixir
def handle_event("pause_timer", _params, socket) do
{:noreply, Corex.Timer.pause(socket, "my-timer")}
end
```
""")
def pause(socket, timer_id)
when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(timer_id) do
LiveView.push_event(socket, "timer_pause", %{id: timer_id})
end
api_doc(~S"""
Resume the timer from `phx-click`. Dispatches `corex:timer:resume`.
```heex
<.action phx-click={Corex.Timer.resume("my-timer")}>Resume
<.timer id="my-timer" start_ms={60_000} class="timer">
```
""")
def resume(timer_id) when is_binary(timer_id) do
JS.dispatch("corex:timer:resume", to: "##{timer_id}", bubbles: false)
end
api_doc(~S"""
Resume the timer from `handle_event` (`timer_resume`).
```elixir
def handle_event("resume_timer", _params, socket) do
{:noreply, Corex.Timer.resume(socket, "my-timer")}
end
```
""")
def resume(socket, timer_id)
when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(timer_id) do
LiveView.push_event(socket, "timer_resume", %{id: timer_id})
end
api_doc(~S"""
Reset the timer from `phx-click`. Dispatches `corex:timer:reset`.
```heex
<.action phx-click={Corex.Timer.reset("my-timer")}>Reset
<.timer id="my-timer" start_ms={60_000} class="timer">
```
""")
def reset(timer_id) when is_binary(timer_id) do
JS.dispatch("corex:timer:reset", to: "##{timer_id}", bubbles: false)
end
api_doc(~S"""
Reset the timer from `handle_event` (`timer_reset`).
```elixir
def handle_event("reset_timer", _params, socket) do
{:noreply, Corex.Timer.reset(socket, "my-timer")}
end
```
""")
def reset(socket, timer_id)
when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(timer_id) do
LiveView.push_event(socket, "timer_reset", %{id: timer_id})
end
api_doc(~S"""
Restart the timer from `phx-click`. Dispatches `corex:timer:restart`.
```heex
<.action phx-click={Corex.Timer.restart("my-timer")}>Restart
<.timer id="my-timer" start_ms={60_000} class="timer">
```
""")
def restart(timer_id) when is_binary(timer_id) do
JS.dispatch("corex:timer:restart", to: "##{timer_id}", bubbles: false)
end
api_doc(~S"""
Restart the timer from `handle_event` (`timer_restart`).
```elixir
def handle_event("restart_timer", _params, socket) do
{:noreply, Corex.Timer.restart(socket, "my-timer")}
end
```
""")
def restart(socket, timer_id)
when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(timer_id) do
LiveView.push_event(socket, "timer_restart", %{id: timer_id})
end
api_doc(~S"""
Read machine state from `phx-click`. Dispatches `corex:timer:state`. Optional `respond_to:` `:server`, `:client`, or `:both`.
| | Reply | Payload |
| - | ----- | ------- |
| Server | `timer_state_response` | `%{"id" => id}` plus `running`, `paused`, `progressPercent`, `time`, `formattedTime` |
| Client | `timer-state` on the timer root | same fields in `detail` |
```heex
<.action phx-click={Corex.Timer.state("my-timer")}>State
<.timer id="my-timer" start_ms={60_000} class="timer">
```
```elixir
def handle_event("timer_state_response", %{"id" => _, "running" => running}, socket) do
{:noreply, assign(socket, :running, running)}
end
```
```javascript
document.getElementById("my-timer")?.addEventListener("timer-state", (e) => {
console.log(e.detail.running, e.detail.paused);
});
```
""")
def state(timer_id, opts) when is_binary(timer_id) and is_list(opts) do
JS.dispatch("corex:timer:state",
to: "##{timer_id}",
detail: respond_to_fields(opts),
bubbles: false
)
end
def state(socket, timer_id)
when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(timer_id) do
state(socket, timer_id, [])
end
api_doc_short("Same as [`state/2`](#state/2) with default `respond_to:`.")
def state(timer_id) when is_binary(timer_id), do: state(timer_id, [])
api_doc(~S"""
Read machine state from `handle_event` (`timer_state`). Same replies as [`state/2`](#state/2); server-side only unless you also use [`state/2`](#state/2) for a client DOM reply.
| Reply | Payload |
| ----- | ------- |
| `timer_state_response` | `%{"id" => id}` plus `running`, `paused`, `progressPercent`, `time`, `formattedTime` |
```elixir
def handle_event("read_state", _params, socket) do
{:noreply, Corex.Timer.state(socket, "my-timer")}
end
def handle_event("timer_state_response", %{"id" => _, "running" => running}, socket) do
{:noreply, assign(socket, :running, running)}
end
```
""")
def state(socket, timer_id, opts)
when is_struct(socket, Phoenix.LiveView.Socket) and is_binary(timer_id) and is_list(opts) do
LiveView.push_event(
socket,
"timer_state",
Map.merge(%{id: timer_id}, respond_to_fields(opts))
)
end
@doc type: :component
attr(:id, :string, required: false)
attr(:rest, :global)
def timer_skeleton(assigns) do
~H"""
:
:
:
"""
end
defp time_values(ms) do
ms = max(0, ms)
seconds = div(rem(ms, 60_000), 1_000)
minutes = div(rem(ms, 3_600_000), 60_000)
hours = div(rem(ms, 86_400_000), 3_600_000)
days = div(ms, 86_400_000)
%{days: days, hours: hours, minutes: minutes, seconds: seconds}
end
end