dotfiles/app/lib/prymn_web/components/terminal.ex
2023-12-16 22:59:19 +02:00

120 lines
3.1 KiB
Elixir

defmodule PrymnWeb.Terminal do
use PrymnWeb, :live_component
alias PrymnProto.Prymn.TerminalRequest
@impl true
def mount(socket) do
{:ok, assign(socket, :open, false)}
end
@impl true
def update(assigns, socket) do
socket =
if assigns[:data],
do: push_event(socket, "data", %{"data" => assigns[:data]}),
else: socket
{:ok, assign(socket, assigns)}
end
@impl true
def render(assigns) do
~H"""
<div>
<PrymnWeb.Button.primary
:if={not @open}
type="button"
phx-target={@myself}
phx-click="open_terminal"
>
Open Terminal
</PrymnWeb.Button.primary>
<PrymnWeb.Button.primary
:if={@open}
type="button"
phx-target={@myself}
phx-click="close_terminal"
>
Close Terminal
</PrymnWeb.Button.primary>
<div :if={@open} class="mt-2 bg-black p-2">
<div phx-hook="Terminal" id="terminal" />
</div>
</div>
"""
end
@impl true
def handle_event("open_terminal", _params, socket) do
agent = Prymn.Agents.from_server(socket.assigns.server)
pid = self()
Task.Supervisor.start_child(Prymn.TaskSupervisor, fn ->
# FIXME: Have to wrap this in a Task because gun sends unsolicited messages
# to calling process
stream = Prymn.Agents.terminal(agent)
{:ok, mux_pid} =
Task.Supervisor.start_child(Prymn.TaskSupervisor, fn -> receive_loop(stream) end)
send_update(pid, PrymnWeb.Terminal, id: "terminal", mux_pid: mux_pid, open: true)
case GRPC.Stub.recv(stream, timeout: :infinity) do
{:ok, stream} ->
Enum.map(stream, fn
{:ok, %{output: data}} ->
send(mux_pid, :data)
send_update(pid, PrymnWeb.Terminal, id: "terminal", data: data)
{:error, _err} ->
send_update(pid, PrymnWeb.Terminal, id: "terminal", open: false)
end)
{:error, error} ->
dbg(error)
end
end)
{:noreply, socket}
end
def handle_event("close_terminal", _params, socket) do
send(socket.assigns.mux_pid, :close)
{:noreply, assign(socket, :open, false)}
end
def handle_event("data_event", data, socket) when is_binary(data) do
send(socket.assigns.mux_pid, {:data_event, data})
{:noreply, socket}
end
def handle_event("resize_event", %{"cols" => cols, "rows" => rows}, socket) do
send(socket.assigns.mux_pid, {:resize_event, rows, cols})
{:noreply, socket}
end
defp receive_loop(stream) do
receive do
{:data_event, data} ->
GRPC.Stub.send_request(stream, %TerminalRequest{input: data})
receive_loop(stream)
{:resize_event, rows, cols} ->
GRPC.Stub.send_request(stream, %TerminalRequest{
resize: %TerminalRequest.Resize{rows: rows, cols: cols}
})
receive_loop(stream)
:data ->
receive_loop(stream)
:close ->
GRPC.Stub.send_request(stream, %TerminalRequest{input: ""}, end_stream: true)
after
120_000 ->
GRPC.Stub.send_request(stream, %TerminalRequest{input: ""}, end_stream: true)
end
end
end