I resolved it by not using maps in my updates. But here is all of the relevant code.
There is a live component
defmodule CapabilityComponent do
use App, :live_component
import App.PropertiesCard
alias App.CapabilityManager
@impl true
def render(assigns) do
~H"""
<div id={"caps-#{@device_id}"}>
<.property_list id={"list-"<>@device.id} device={@device} capabilities={@streams.capabilities} />
</div>
"""
end
@impl true
def update(%{device: device} = _assigns, socket) do
{:ok,
socket
|> assign(:device_id, device.id)
|> assign(:device, device)
|> stream(:capabilities, device.capabilities)
}
end
def update(%{event: data}, socket) do
{:ok, stream_insert(socket, :capabilities, data, at: -1)}
end
@impl true
def handle_event("delete_cap", params, socket) do
cap = CapabilityManager.get_capability!(params["id"])
{:ok, _} = CapabilityManager.delete_capability(cap)
{:noreply, stream_delete(socket, :capabilities, cap)}
end
end
Each live component renders a list of properties
attr :capabilities, :list
attr :device, Lorp.Core.DeviceManager.Device
attr :id, :any
attr :div_id, :any, default: nil
slot :content
def property_list(assigns) do
~H"""
<div id={@id} phx-update="stream">
<div :for={{id, capability} <- @capabilities} id={id} class="px-2">
<.property_item capability={capability} device={@device} parent={id} id={"child_#{id}"}/>
</div>
</div>
"""
end
Each item could be one of many given types. Here is the one relevant to three axis
attr :parent, :string
attr :device, Lorp.Core.DeviceManager.Device
attr :capability, Lorp.CapabilityManager.Capability
attr :capability_id, :string
attr :id, :string
def property_item(assigns) do
~H"""
<.three_axis_sensor
:if={@capability.type == "three_axis_sensor"}
capability_id={@capability.id}
name={"Three Axis"}
type={"three_axis_sensor"}
parent={@parent}
device_id={@device.id}
x_value={@capability.state.value_x}
y_value={@capability.state.value_y}
z_value={@capability.state.value_z}
/>
"""
end
Here is the three axis card
attr :capability_id, :string, required: true
attr :device_id, :string
attr :name, :string, required: true
attr :value, :map
attr :type, :string, required: true
attr :state, :string
attr :unit, :string
attr :parent, :string
attr :x_value, :any
attr :y_value, :any
attr :z_value, :any
def three_axis_sensor(assigns) do
~H"""
<div class={"flex w-full"} >
<div class="flex justify-start w-1/2">
<%= @name %>
</div>
<div class="flex justify-end w-1/2 pl-34">
<span id={@type<>"-"<>@capability_id}><%= @x_value %>, <%= @y_value %>, <%= @z_value %></span>
<.link phx-click={JS.push("delete_cap", value: %{id: @capability_id})} data-confirm="Are you sure?" class={"pl-3"} phx-target={ LorpWeb.CapabilityComponent }>
<.icon name="hero-trash" class="h-4 w-4"/>
</.link>
</div>
</div>
"""
end