<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <link href="https://pedroassuncao.com/feed?page=2" rel="next"/>
  <link href="https://pedroassuncao.com/feed?page=55" rel="last"/>
  <link href="https://pedroassuncao.com/feed?page=1" rel="self"/>
  <link href="https://pedroassuncao.com/feed" rel="first"/>
  <author>
    <name>Pedro Assunção</name>
    <email>pedro@pedroassuncao.com</email>
  </author>
  <id>https://pedroassuncao.com</id>
  <title>Pedro Assunção - Software Developer</title>
  <updated>2026-06-09T06:53:13.986236Z</updated>
  <entry>
    <link href="https://pedroassuncao.com/posts/autocomplete-field-in-phoenix-live-view-surface-and-tailwindcss"/>
    <content type="html">&lt;p&gt;I know it&apos;s a mouthful, but hear me out:&lt;/p&gt;

&lt;p&gt;I&apos;ve been looking for an autocomplete field that worked for my purposes (lookup in the database, not in memory, and support multiselect and the ability to limit the number of items a field could support.&lt;/p&gt;

&lt;p&gt;Since it couldn&apos;t find it and being the dumb dev that i am, i obviously wrote my own. It&apos;s meant for Surface forms, but with a bit of tinkering you can make it work with plain LiveView.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;defmodule MyProjectWeb.Live.Components.Autocomplete do
  @moduledoc &quot;&quot;&quot;
  Autocomplete component to lookup a single item from the database
  &quot;&quot;&quot;
  use MyProjectWeb, :surface_component

  alias Surface.Components.Form

  @doc &quot;&quot;&quot;
  The form field name for the data that we are looking up
  &quot;&quot;&quot;
  prop field_name, :atom, required: true

  @doc &quot;&quot;&quot;
  The label of the field
  &quot;&quot;&quot;
  slot default, required: true

  @doc &quot;&quot;&quot;
  Currently selected values for the field
  &quot;&quot;&quot;
  prop current_values, :map, default: %{}

  @doc &quot;&quot;&quot;
  Suggestions that the user can select
  &quot;&quot;&quot;
  prop suggestions, :list, required: true

  @doc &quot;&quot;&quot;
  Add and remove events that will get triggered
  as user clicks on the suggestions or asks to
  remove one of the current values
  &quot;&quot;&quot;
  prop add_event, :event, required: true
  prop remove_event, :event, required: true

  @doc &quot;&quot;&quot;
  Maximum number of items this field allows for. Controls
  whether to show the search field or not.
  &quot;&quot;&quot;
  prop max_items, :integer, default: 9_999_999

  @impl true
  def render(assigns) do
    assigns =
      assign(assigns, :suggestions, filter_existing(assigns.suggestions, assigns.current_values))

    ~F&quot;&quot;&quot;
    &lt;div id={@id}&gt;
      &lt;div class=&quot;form-control&quot;&gt;
        &lt;Form.Label&gt;&lt;#slot /&gt;&lt;/Form.Label&gt;
        &lt;div class=&quot;flex flex-row items-start&quot;&gt;
          &lt;div class=&quot;flex flex-row&quot;&gt;
            {#for {id, title} &lt;- @current_values}
              &lt;div class=&quot;badge badge-secondary p-3 mr-2 cursor-pointer flex-1&quot;&gt;
                {title}
                &lt;div :on-click={@remove_event} phx-value-id={id}&gt;
                  &lt;Heroicons.Solid.x_circle class=&quot;ml-2 h-6 w-6 cursor-pointer&quot; /&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            {/for}
          &lt;/div&gt;

          &lt;div class=&quot;flex flex-col&quot;&gt;
            &lt;Form.TextInput
              id={&quot;autocomplete_#{@field_name}&quot;}
              name={&quot;autocomplete_#{@field_name}&quot;}
              class=&quot;inline-block max-h-8&quot;
              opts={phx_debounce: 500, autocomplete: &quot;off&quot;, placeholder: &quot;Search...&quot;}
              value=&quot;&quot;
              :if={Enum.count(@current_values) &lt; @max_items}
            /&gt;
            &lt;div
              :if={Enum.any?(@suggestions)}
              class=&quot;mb-3 grid grid-cols-1 divide-y border-t border-b text-sm border-neutral&quot;
            &gt;
              {#for {id, title} &lt;- @suggestions}
                &lt;div
                  class=&quot;p-1 cursor-pointer border-x pl-3 pr-3 hover:bg-secondary border-neutral&quot;
                  :on-click={@add_event}
                  phx-value-id={id}
                &gt;
                  {title}
                &lt;/div&gt;
              {/for}
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;Form.ErrorTag /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &quot;&quot;&quot;
  end

  defp filter_existing(suggestions, current_values) do
    Enum.reject(suggestions, fn s -&gt; Enum.member?(current_values, s) end)
  end
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The gist of it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;current_values&lt;/strong&gt; property accepts what the database object already contains in that field (probably a list of id&apos;s.&lt;/li&gt;
&lt;li&gt;The  &lt;strong&gt;suggestions&lt;/strong&gt; property receives a map for which the key is the id of the suggestion and the value is the title/name to be displayed when suggesting.&lt;/li&gt;
&lt;li&gt;The two events control what gets called when the user adds (i.e. clicks a suggestion) or removes (i.e. clicks remove on an existing item. These events will be controlled on the outside.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;max_items&lt;/strong&gt; property defines at which point do we stop showing the search box for this field (i.e. the max number of selected items was reached).&lt;/li&gt;

&lt;p&gt;The rest is pretty simple:&lt;/p&gt;

&lt;p&gt;Whenever the  user enters something in the search field, the form will get it&apos;s &lt;strong&gt;change&lt;/strong&gt; event triggered and we only need to intercept that event based on whether we are asking for a completion or not. This is easy to achieve, for instance, like described next.&lt;/p&gt;

&lt;p&gt;My form component contains this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@impl true
  def handle_event(
        &quot;change&quot;,
        %{
          &quot;_target&quot; =&gt; [&quot;autocomplete_user&quot;],
          &quot;autocomplete_user&quot; =&gt; query
        },
        socket
      ) do
    cond do
      String.length(query) &lt; 2 -&gt;
        {:noreply, assign(socket, :user_autocomplete_suggestions, [])}

      true -&gt;
        suggestions =
          query
          |&gt; Users.find()
          |&gt; Enum.map(fn %{id: id, title: title} -&gt;
            {id, title}
          end)

        {:noreply, assign(socket, :user_autocomplete_suggestions, suggestions)}
    end
  end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the suggestions for that field get set as a list of tuples (as accepted on the autocomplete component) and those suggestions are passed in when we update state. Here&apos;s the render of the component:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;Autocomplete
  id=&quot;users_lookup&quot;
  field_name={:users}
  current_values={users_autocomplete_values(@blog)}
  suggestions={@users_autocomplete_suggestions}
  max_items={1}
  add_event=&quot;add_users&quot;
  remove_event=&quot;remove_users&quot;
&gt;
  User
&lt;/Autocomplete&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;users_autocomplete_values(@blog)&lt;/strong&gt; is basically building the currently assigned user to the blog:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;defp user_autocomplete_values(%{user_id: nil}), do: %{}
defp user_autocomplete_values(%{user: %{id: id, name: name}}),
    do: %{id =&gt; name}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the function returns a map where the key is the id, and the value is the displayed name, as per what the component expects. Also, this is the case where you want to autocomplete only one value. Here&apos;s an example function for more than one:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;defp user_autocomplete_values(%{users: users}) do
  users
  |&gt; Enum.map(fn %{id: id, name: name} -&gt;
    {id, name}
  end)
  |&gt; Map.new()
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here the blog would have multiple users. Maybe not the best practical example, but you get the idea of how that current_values property can be built.&lt;/p&gt;

&lt;p&gt;Finally, we only need to implement the add and remove functions on the form component (or live view). For a single item autocomplete something like this would work:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@impl true
def handle_event(&quot;add_user&quot;, %{&quot;id&quot; =&gt; id} = _event, socket) do
  validate_and_save(
    %{&quot;blog&quot; =&gt; %{&quot;user_id&quot; =&gt; id}},
    assign(socket, :user_autocomplete_suggestions, [])
  )
end

@impl true
def handle_event(&quot;remove_user&quot;, %{&quot;id&quot; =&gt; _id} = _event, socket) do
  validate_and_save(%{&quot;blog&quot; =&gt; %{&quot;user_id&quot; =&gt; nil}}, socket)
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if you wanted to support multiple users, something like this should do the trick:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@impl true
def handle_event(&quot;add_user&quot;, %{&quot;id&quot; =&gt; id} = _event, socket) do
  existing_user_ids = Enum.map(socket.assigns.customer.users, &amp; &amp;1.id)

  validate_and_save(
    %{&quot;customer&quot; =&gt; %{&quot;customer_users&quot; =&gt; [id | existing_user_ids]}},
    assign(socket, :users_autocomplete_suggestions, [])
  )
end

@impl true
def handle_event(&quot;remove_user&quot;, %{&quot;id&quot; =&gt; id} = _event, socket) do
  existing_user_ids = Enum.map(socket.assigns.customer.users, &amp; &amp;1.id)
  new_user_ids = List.delete(existing_user_ids, String.to_integer(id))

  validate_and_save(
    %{&quot;customer&quot; =&gt; %{&quot;customer_users&quot; =&gt; new_user_ids}},
    assign(socket, :users_autocomplete_suggestions, [])
  )
end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Happy coding!&lt;/p&gt;</content>
    <id>https://pedroassuncao.com/posts/autocomplete-field-in-phoenix-live-view-surface-and-tailwindcss</id>
    <title>Autocomplete field in Phoenix Live View, Surface, and TailwindCSS</title>
    <updated>2022-04-25T19:43:23Z</updated>
  </entry>
  <entry>
    <link href="https://pedroassuncao.com/posts/elixirphoenix-vscode-snippets"/>
    <content type="html">&lt;p&gt;Here&apos;s my current Elixir VSCode snippets &lt;code&gt;elixir.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;You can add it to VSCode by &lt;code&gt;Ctrl/Cmd+Shift+P&lt;/code&gt; and choosing &lt;code&gt;Preferences : Configure User Snippets&lt;/code&gt;, then opening &lt;code&gt;elixir.json&lt;/code&gt;. If you don&apos;t have one yet, you can create a new snippet file in the same place.&lt;/p&gt;

&lt;p&gt;Supported snippets:
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;gs&lt;/b&gt;: GenServer&lt;/li&gt;
&lt;li&gt;&lt;b&gt;gscast&lt;/b&gt;: GenServer Cast Function&lt;/li&gt;
&lt;li&gt;&lt;b&gt;gscall&lt;/b&gt;: GenServer Call Function&lt;/li&gt;
&lt;li&gt;&lt;b&gt;lv&lt;/b&gt;: Phoenix LiveView&lt;/li&gt;
&lt;li&gt;&lt;b&gt;lvc&lt;/b&gt;: Phoenix LiveComponent&lt;/li&gt;
&lt;li&gt;&lt;b&gt;lvhe&lt;/b&gt;: Phoenix LiveView Handle Event Function&lt;/li&gt;
&lt;li&gt;&lt;b&gt;lvhi&lt;/b&gt;: Phoenix LiveView Handle Info Function&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;Hope that&apos;s useful, here&apos;s the file:&lt;/p&gt;
&lt;pre&gt;{
    &quot;My GenServer&quot;: {
        &quot;prefix&quot;: &quot;gs&quot;,
        &quot;body&quot;: [
            &quot;defmodule ${4:package}.${1:name} do&quot;,
            &quot;\tuse GenServer&quot;,
            &quot;\trequire Logger&quot;,
            &quot;&quot;,
            &quot;\t@me __MODULE__&quot;,
            &quot;&quot;,
            &quot;\tdef start_link(${2:opts}) do&quot;,
            &quot;\t\t{:ok, pid} = result = GenServer.start_link(@me, ${2:opts}, name: @me)&quot;,
            &quot;\t\tLogger.debug(\&quot;#{@me} GenServer started with# #{inspect(pid)}.\&quot;)&quot;,
            &quot;\t\tresult&quot;,
            &quot;\tend&quot;,
            &quot;&quot;,
            &quot;\tdef init(${3:initial_state}) do&quot;,
            &quot;\t\t{:ok, ${3:initial_state}}&quot;,
            &quot;\tend&quot;,
            &quot;&quot;,
            &quot;\t$0&quot;,
            &quot;end&quot;
        ],
        &quot;description&quot;: &quot;My GenServer&quot;
    },
    &quot;GenServer Cast&quot;: {
        &quot;prefix&quot;: &quot;gscast&quot;,
        &quot;body&quot;: [
            &quot;def ${1:function}() do&quot;,
            &quot;\tGenServer.cast(@me, :${1:function})&quot;,
            &quot;end&quot;,
            &quot;&quot;,
            &quot;${0}&quot;
        ],
        &quot;description&quot;: &quot;GenServer cast function&quot;
    },
    &quot;GenServer Call&quot;: {
        &quot;prefix&quot;: &quot;gscall&quot;,
        &quot;body&quot;: [
            &quot;def ${1:function}() do&quot;,
            &quot;\tGenServer.call(@me, :${1:function})&quot;,
            &quot;end&quot;,
            &quot;&quot;,
            &quot;${0}&quot;
        ],
        &quot;description&quot;: &quot;GenServer call function&quot;
    },
    &quot;LiveViewLiveComponent&quot;: {
        &quot;prefix&quot;: &quot;lvc&quot;,
        &quot;body&quot;: [
            &quot;defmodule ${1:module} do&quot;,
            &quot;\t@moduledoc \&quot;\&quot;\&quot;&quot;,
            &quot;\t${2:module_doc}&quot;,
            &quot;\t\&quot;\&quot;\&quot;&quot;,
            &quot;\tuse Phoenix.LiveComponent&quot;,
            &quot;&quot;,
            &quot;\t@impl true&quot;,
            &quot;\tdef update(_assigns, socket) do&quot;,
            &quot;\t\t{:ok, socket}&quot;,
            &quot;\tend&quot;,
            &quot;&quot;,
            &quot;\t@impl true&quot;,
            &quot;\tdef render(assigns) do&quot;,
            &quot;\t\t~L\&quot;\&quot;\&quot;&quot;,
            &quot;\t\t${0}&quot;,
            &quot;\t\t\&quot;\&quot;\&quot;&quot;,
            &quot;\tend&quot;,
            &quot;end&quot;,
        ]
    },
    &quot;LiveView&quot;: {
        &quot;prefix&quot;: &quot;lv&quot;,
        &quot;body&quot;: [
            &quot;defmodule ${1:module} do&quot;,
            &quot;\t@moduledoc \&quot;\&quot;\&quot;&quot;,
            &quot;\t${2:module_doc}&quot;,
            &quot;\t\&quot;\&quot;\&quot;&quot;,
            &quot;\tuse Phoenix.LiveView&quot;,
            &quot;&quot;,
            &quot;\t@impl true&quot;,
            &quot;\tdef mount(_params, _session, socket) do&quot;,
            &quot;\t\t{:ok, socket}&quot;,
            &quot;\tend&quot;,
            &quot;&quot;,
            &quot;\t@impl true&quot;,
            &quot;\tdef handle_params(_params, _url, socket) do&quot;,
            &quot;\t\t{:noreply, socket}&quot;,
            &quot;\tend&quot;,
            &quot;&quot;,
            &quot;\t@impl true&quot;,
            &quot;\tdef render(assigns) do&quot;,
            &quot;\t\t~L\&quot;\&quot;\&quot;&quot;,
            &quot;\t\t${0}&quot;,
            &quot;\t\t\&quot;\&quot;\&quot;&quot;,
            &quot;\tend&quot;,
            &quot;end&quot;,
        ]
    },
    &quot;LiveViewHandleEvent&quot;: {
        &quot;prefix&quot;: &quot;lvhe&quot;,
        &quot;body&quot;: [
            &quot;@impl true&quot;,
            &quot;def handle_event(\&quot;${1:event}\&quot;, %{\&quot;${2:var}\&quot; =&gt; ${2:var}} = _event, socket) do&quot;,
            &quot;\t${0}&quot;,
            &quot;\t{:noreply, socket}&quot;,
            &quot;end&quot;
        ]
    },
    &quot;LiveViewHandleInfo&quot;: {
        &quot;prefix&quot;: &quot;lvhi&quot;,
        &quot;body&quot;: [
            &quot;@impl true&quot;,
            &quot;def handle_info(${1:event}, socket) do&quot;,
            &quot;\t${0}&quot;,
            &quot;\t{:noreply, socket}&quot;,
            &quot;end&quot;
        ]
    }
}&lt;/pre&gt;

&lt;p&gt;Happy Elixir&apos;ing&lt;/p&gt;</content>
    <id>https://pedroassuncao.com/posts/elixirphoenix-vscode-snippets</id>
    <title>Elixir/Phoenix VSCode snippets</title>
    <updated>2021-02-23T15:54:26Z</updated>
  </entry>
  <entry>
    <link href="https://pedroassuncao.com/posts/allow-erlangelixir-to-open-ports-80-and-443"/>
    <content type="html">&lt;p&gt;By default users other than root cannot open ports below 1000, for security reasons. If you want to - say - run your website in Elixir without nginx on port 80 or 443, you can allow the Erlang binary to open ports below 1000 using setcap (run as root):&lt;/p&gt;

&lt;pre&gt;setcap &apos;cap_net_bind_service=+ep&apos; &lt;path_to_erlang_folders&gt;/erts-11.1/bin/beam.smp
&lt;/pre&gt;</content>
    <id>https://pedroassuncao.com/posts/allow-erlangelixir-to-open-ports-80-and-443</id>
    <title>Allow Erlang/Elixir to open ports 80 and 443</title>
    <updated>2021-02-16T12:44:56Z</updated>
  </entry>
  <entry>
    <link href="https://pedroassuncao.com/posts/manjaro-510-kernel-will-not-boot"/>
    <content type="html">&lt;p&gt;If - like me - you get stuck on boot after Manjaro updates to Linux Kernel 5.10, the solution is pretty simple:&lt;/p&gt;
&lt;div&gt;
&lt;ol&gt;
&lt;li&gt;Boot into 5.9 (you can choose the option in the grub boot menu&lt;/li&gt;
&lt;li&gt;Reinstall the 5.10 kernel with: &lt;pre&gt;&lt;code&gt;sudo pacman -S linux510&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;For good measure, reinstall the Nvidia driver (if you have an Nvidia card, that is) with: &lt;pre&gt;&lt;code&gt;sudo pacman -S linux510-nvidia&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p&gt;After that, the 5.10 kernel should boot.&lt;/p&gt;</content>
    <id>https://pedroassuncao.com/posts/manjaro-510-kernel-will-not-boot</id>
    <title>Manjaro 5.10 Kernel will not boot</title>
    <updated>2021-01-12T16:13:47Z</updated>
  </entry>
  <entry>
    <link href="https://pedroassuncao.com/posts/automatic-enum-values-conversion-in-elixir"/>
    <content type="html">&lt;p&gt;One annoying detail about web forms is that on submission (unless you are using changesets) all the values come back as strings. This forces you to have to handle the correct formats before, say, passing those params into a service module to execute some action. &lt;/p&gt;

&lt;p&gt;To simplify this process, i recently created a simple guesser function that evaluates the values of a map, namely booleans, integers, floats, dates, and datetimes. It is recursive and can go into lists as well. Check the examples:&lt;/p&gt;

&lt;pre&gt;  @doc &quot;&quot;&quot;
  Function to try to guess the type of a variable and return the guessed value. Some
  applications might include converting a parameter map coming from a form (all strings)
  into a proper map with typed values, for instance:
       %{foo: &quot;123&quot;, bar: &quot;true&quot;, foobar: &quot;2020-12-13&quot;}
  would become:
       %{foo: 123, bar: true, foobar: ~D[2020-12-13]}
  The function supports simple values (a string, for instance), is also recursive into
  lists and maps and, if it can&apos;t figure out the type, it returns the original value.
  Will also trim the values before testing so `&quot; 123&quot;`, for instance, becomes `123`.
  ## Examples
  ## Simple types
      iex&gt; to_typed(&quot;&quot;)
      &quot;&quot;
      iex&gt; to_typed(&quot;123&quot;)
      123
      iex&gt; to_typed(&quot; 456&quot;)
      456
      iex&gt; to_typed(&quot;-123&quot;)
      -123
      iex&gt; to_typed(&quot;123.345&quot;)
      123.345
      iex&gt; to_typed(&quot;-12.3&quot;)
      -12.3
      iex&gt; to_typed(&quot;true&quot;)
      true
      iex&gt; to_typed(&quot;  true  &quot;)
      true
      iex&gt; to_typed(&quot;false&quot;)
      false
      iex&gt; to_typed(&quot;2020-12-13&quot;)
      ~D[2020-12-13]
      iex&gt; to_typed(&quot;2015-01-23T23:50:07Z&quot;)
      ~U[2015-01-23 23:50:07Z]
      iex&gt; to_typed(&quot;2015-01-23T23:50:07.123+02:30&quot;)
      ~U[2015-01-23 21:20:07.123Z]
  ## Maps
      iex&gt; to_typed(%{})
      %{}
      iex&gt; to_typed(%{another_integer: &quot;5345345435&quot;})
      %{another_integer: 5345345435}
      iex&gt; to_typed(%{another_map: %{foo: &quot;true&quot;}})
      %{another_map: %{foo: true}}
      iex&gt; to_typed(%{a_list: [&quot;foo&quot;, &quot;true&quot;]})
      %{a_list: [&quot;foo&quot;, true]}
  ## Lists
      iex&gt; to_typed([])
      []
      iex&gt; to_typed([&quot;123&quot;])
      [123]
      iex&gt; to_typed([&quot;12.3&quot;])
      [12.3]
      iex&gt; to_typed([&quot;true&quot;, &quot;false&quot;])
      [true, false]
      iex&gt; to_typed([&quot;true&quot;, [&quot;false&quot;]])
      [true, [false]]
      iex&gt; to_typed([%{foo: &quot;1&quot;}, %{foo: &quot;2.5&quot;}])
      [%{foo: 1}, %{foo: 2.5}]
      iex&gt; to_typed([&quot;bla&quot;, &quot;bla&quot;, &quot;2015-01-23T23:50:07Z&quot;, [&quot;12345&quot;, %{cool: &quot;true&quot;}]])
      [&quot;bla&quot;, &quot;bla&quot;, ~U[2015-01-23 23:50:07Z], [12345, %{cool: true}]]
  ## Other
      iex&gt; to_typed(nil)
      nil
      iex&gt; to_typed(123)
      123
      iex&gt; to_typed(&quot;actually_a_string&quot;)
      &quot;actually_a_string&quot;
  &quot;&quot;&quot;
  @spec to_typed(any()) :: any()
  def to_typed(map) when is_map(map) do
    map
    |&gt; Enum.map(fn {key, value} -&gt;
      value =
        cond do
          is_map(value) -&gt; to_typed(value)
          is_list(value) -&gt; to_typed(value)
          true -&gt; to_typed(value)
        end

      {key, value}
    end)
    |&gt; Map.new()
  end

  def to_typed(list) when is_list(list) do
    Enum.map(list, fn value -&gt;
      cond do
        is_map(value) or is_list(value) -&gt; to_typed(value)
        is_binary(value) -&gt; to_typed(value)
        true -&gt; value
      end
    end)
  end

  def to_typed(value) when is_binary(value) do
    value = String.trim(value)

    cond do
      value == &quot;true&quot; -&gt;
        true

      value == &quot;false&quot; -&gt;
        false

      value =~ ~r/\A\d{4}-\d{2}-\d{2}\z/ -&gt;
        value |&gt; Timex.parse!(&quot;{ISOdate}&quot;) |&gt; Timex.to_date()

      value =~ ~r/\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/ -&gt;
        value |&gt; Timex.parse!(&quot;{ISO:Extended}&quot;) |&gt; Timex.to_datetime()

      value =~ ~r/\A-?\d+\.\d+\z/ -&gt;
        String.to_float(value)

      value =~ ~r/\A-?\d+\z/ -&gt;
        String.to_integer(value)

      true -&gt;
        value
    end
  end

  def to_typed(value), do: value&lt;/pre&gt;

&lt;p&gt;Hope you find it useful.&lt;/p&gt;

&lt;p&gt;Happy Elixir&apos;ing...&lt;/p&gt;</content>
    <id>https://pedroassuncao.com/posts/automatic-enum-values-conversion-in-elixir</id>
    <title>Automatic enum values conversion in Elixir</title>
    <updated>2020-11-18T19:48:25Z</updated>
  </entry>
  <entry>
    <link href="https://pedroassuncao.com/posts/linux-window-auto-organizer-across-multiple-screens"/>
    <content type="html">&lt;p&gt;One of the things that always makes me crazy is window organization on the desktop. OSX has pretty good apps to manage windows, but i always find Linux a bit lacking so i decided to roll out my own simple solution.&lt;/p&gt;

&lt;p&gt;It&apos;s written in Elixir because i like it, but can easily be adapter for other languages since it uses wmctrl to get information about windows and define their positions. The rest is just find the windows roughly by name and assigning the positions and dimensions i want. And it works across multiple screens because apparently X11 reports positions as one single virtual screen if you are not mirroring your screens, of course.&lt;/p&gt;

&lt;p&gt;Just put the script in a directory that your PATH contains, and don&apos;t forget to change the first line to point to the correct elixir binary path. Then you can just execute whenever you want and all your windows will pop into their positions automagically.&lt;/p&gt;

&lt;p&gt;Happy windowing...&lt;/p&gt;

&lt;p&gt;EDIT: Updated script to not blow up when a particular window is not open.&lt;/p&gt;

&lt;pre&gt;#!/home/void/.asdf/shims/elixir

require Logger

windows = %{
  opera: %{x: 3408, y: 0, w: 1712, h: 1403},
  terminal: %{x: 0, y: 0, w: 1584, h: 1371},
  thunderbird: %{x: 6045, y: 200, w: 995, h: 1051},
  jetbrains: %{x: 2560, y: 0, w: 2060, h: 1373},
  slack: %{x: 5120, y: 200, w: 1184, h: 1050},
  spotify: %{x: 1592, y: 0, w: 968, h: 1373}
}

defmodule WindowGatherer do
  def run(windows) do
    IO.inspect(&quot;Gathering windows...&quot;)

    {window_lines, 0} = System.cmd(&quot;wmctrl&quot;, [&quot;-l&quot;, &quot;-p&quot;, &quot;-G&quot;, &quot;-x&quot;])
    window_lines = window_lines
    |&gt; String.split(&quot;\n&quot;)
    |&gt; Enum.reject(&amp;(String.split(&amp;1) == []))
    |&gt; Enum.map(fn line -&gt;
      [id, _, _, _, _, _, _ | rest] = String.split(line)
      %{
        id: id,
        names: rest |&gt; Enum.join(&quot; &quot;) |&gt; String.downcase()
      }
    end)

    fill_ids(windows, window_lines)
  end

  defp fill_ids(windows, window_lines) do
    windows
    |&gt; Enum.map(fn {window_key, window_data} -&gt;
      window_attributes = window_lines
        |&gt; Enum.filter(&amp;(&amp;1.names =~ Atom.to_string(window_key)))
        |&gt; Enum.reject(&amp;is_nil/1)
        |&gt; Enum.at(0)
      case window_attributes do
        map when is_map(window_attributes) -&gt;
          {window_key, Map.put(window_data, :id, Map.fetch!(map, :id))}
        _ -&gt; nil
      end
    end)
    |&gt; Enum.reject(&amp;is_nil/1)
    |&gt; Map.new()
  end
end

defmodule WindowOrganizer do
  def run(windows) do
    IO.inspect(&quot;Organizing windows...&quot;)

    windows
    |&gt; Enum.each(fn {key, win} -&gt;
      IO.inspect(&quot;Moving #{key}...&quot;)
      System.cmd(&quot;wmctrl&quot;, [
        &quot;-ir&quot;,
        win.id,
        &quot;-e&quot;,
        &quot;0,#{win.x},#{win.y},#{win.w},#{win.h}&quot;
      ])
    end)
  end
end

windows
|&gt; WindowGatherer.run()
|&gt; WindowOrganizer.run()&lt;/pre&gt;</content>
    <id>https://pedroassuncao.com/posts/linux-window-auto-organizer-across-multiple-screens</id>
    <title>Linux window auto-organizer (across multiple screens)</title>
    <updated>2020-09-25T10:55:54Z</updated>
  </entry>
  <entry>
    <link href="https://pedroassuncao.com/posts/how-to-download-files-from-google-drive-using-elixir"/>
    <content type="html">&lt;p&gt;The documentation around downloading a file from a &lt;a href=&quot;https://drive.google.com&quot;&gt;Google Drive&lt;/a&gt; account is a bit spread out, so i figured i could make a mini tutorial on how to do this.&lt;/p&gt;&lt;p&gt;The scenario is having a CSV file in a folder on my Google Drive and being able to read it from &lt;a href=&quot;https://elixir-lang.org/&quot;&gt;Elixir&lt;/a&gt;. Here&amp;#39;s the setup:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Setup a service account using the &lt;a href=&quot;https://console.developers.google.com/apis/credentials&quot;&gt;Google Developer Console Dashboard&lt;/a&gt; (there are plenty tutorials on the web how to setup service accounts), and save the .json credentials file in your Elixir project somewhere.&lt;/li&gt;&lt;/ul&gt;&lt;figure&gt;&lt;img alt=&quot;&quot; src=&quot;https://cdn-images-1.medium.com/max/864/1*CDjPWQ_FJfk4KRTGCm8ZOA.png&quot; /&gt;&lt;/figure&gt;&lt;ul&gt;&lt;li&gt;Make sure the service account has access to all accounts in your domain (unsure this step is necessary, TBH).&lt;/li&gt;&lt;/ul&gt;&lt;figure&gt;&lt;img alt=&quot;&quot; src=&quot;https://cdn-images-1.medium.com/max/458/1*ZXs8OThvHzTClITkqhreuw.png&quot; /&gt;&lt;/figure&gt;&lt;ul&gt;&lt;li&gt;The service account will have an email associated with it (something along the lines of service_account_name@yourproject.iam.gserviceaccount.com). Share the folder you want to read the files from with that email in your Google Drive interface.&lt;/li&gt;&lt;/ul&gt;&lt;figure&gt;&lt;img alt=&quot;&quot; src=&quot;https://cdn-images-1.medium.com/max/496/1*D6YJO9UvhuUfzvghNwgPCA.png&quot; /&gt;&lt;/figure&gt;&lt;ul&gt;&lt;li&gt;Now we are ready to move to Elixir.&lt;/li&gt;&lt;li&gt;Install the following two dependencies in mix.exs:&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;{:google_api_drive, &quot;~&gt; 0.15.0&quot;},&lt;br&gt;{:goth, &quot;~&gt; 1.2.0&quot;},&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Configure goth to use your .json credentials file in config.exs (you can also do it programmatically, just look at &lt;a href=&quot;https://github.com/peburrows/goth&quot;&gt;goth&amp;#39;s docs&lt;/a&gt;):&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;config :goth,&lt;br&gt;  json: “google_service_account.json” |&gt; File.read!&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Finally, the following snippet will take the first CSV file it finds and read its contents:&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;{:ok, %{token: token}} = Goth.Token.for_scope(&quot;https://www.googleapis.com/auth/drive&quot;)&lt;/pre&gt;&lt;pre&gt;conn = GoogleApi.Drive.V3.Connection.new(token)&lt;/pre&gt;&lt;pre&gt;{:ok, %{files: files} = file_list} = GoogleApi.Drive.V3.Api.Files.drive_files_list(&lt;br&gt;  conn,&lt;br&gt;  includeItemsFromAllDrives: true,&lt;br&gt;  supportsAllDrives: true,&lt;br&gt;  corpora: &quot;allDrives&quot;&lt;br&gt;)&lt;/pre&gt;&lt;pre&gt;[first | _rest] = files &lt;br&gt;  |&gt; Enum.filter(&amp;(&amp;1.mimeType == &quot;text/csv&quot;))&lt;br&gt;  |&gt; Enum.map(&amp;(Map.take(&amp;1, [:id, :name])))&lt;/pre&gt;&lt;pre&gt;{:ok, %{body: body}} = GoogleApi.Drive.V3.Api.Files.drive_files_get(&lt;br&gt;  conn,&lt;br&gt;  first.id,&lt;br&gt;  [&lt;br&gt;    mimeType: &quot;text/csv&quot;,&lt;br&gt;    alt: &quot;media&quot;&lt;br&gt;  ],&lt;br&gt;  [&lt;br&gt;    decode: false&lt;br&gt;  ]&lt;br&gt;)&lt;/pre&gt;&lt;pre&gt;body&lt;/pre&gt;&lt;p&gt;Happy Elixir&amp;#39;ing&lt;/p&gt;&lt;img src=&quot;https://medium.com/_/stat?event=post.clientViewed&amp;referrerSource=full_rss&amp;postId=208aec5c5470&quot; width=&quot;1&quot; height=&quot;1&quot;&gt;</content>
    <id>https://pedroassuncao.com/posts/how-to-download-files-from-google-drive-using-elixir</id>
    <title>How to download files from Google Drive using Elixir</title>
    <updated>2020-07-24T20:29:19Z</updated>
  </entry>
  <entry>
    <link href="https://pedroassuncao.com/posts/elixirphoenix-deployments-using-github-actions"/>
    <content type="html">&lt;figure&gt;&lt;img alt=&quot;&quot; src=&quot;https://cdn-images-1.medium.com/max/786/1*gEISXFVxy0FLcvVauAzpzA.png&quot; /&gt;&lt;/figure&gt;&lt;p&gt;One of the trickiest parts of getting &lt;a href=&quot;https://elixir-lang.org/&quot;&gt;Elixir&lt;/a&gt; services into production is the deployment. Common approaches include using Docker to generate containers that you can then deploy to AWS/Google/Azure, a &lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt; cluster, or running the creation of the deployment locally (mix releases) and then publishing the finalised version to the target machine.&lt;/p&gt;&lt;p&gt;For my website &lt;a href=&quot;https://pedroassuncao.com,&quot;&gt;https://pedroassuncao.com&lt;/a&gt; i decided to go with a simpler approach: Instead of building the final product in a local machine, creating a package (container or otherwise), and then deploying it to my host, i chose to do everything in the target system instead, by leveraging &lt;a href=&quot;https://github.com/noozo&quot;&gt;Github&lt;/a&gt; Actions.&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;&quot; src=&quot;https://cdn-images-1.medium.com/max/510/1*EeE3CKYORmsNO5YcQdJhOA.png&quot; /&gt;&lt;figcaption&gt;5 Euro if you can guess what noozo means…&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;This approach has the following advantages:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Migrations don’t require a separate module to run (mix is available in the target system).&lt;/li&gt;&lt;li&gt;Deployment is fast due to cached dependencies on the target system and a simple SSH command running everything at once.&lt;/li&gt;&lt;li&gt;Slack notifications are super easy to integrate into Github actions and they are a fantastic way to keep track of deployment status.&lt;/li&gt;&lt;li&gt;Push and forget strategy for deployment is super easy.&lt;/li&gt;&lt;li&gt;No Docker and all its configuration mess.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Here&amp;#39;s how i do it:&lt;/p&gt;&lt;p&gt;On my host (a simple &lt;a href=&quot;https://www.linode.com/&quot;&gt;Linode&lt;/a&gt; node) i installed Elixir, &lt;a href=&quot;https://www.erlang.org/&quot;&gt;Erlang&lt;/a&gt;, &lt;a href=&quot;https://nodejs.org/en/&quot;&gt;NodeJS&lt;/a&gt; and the rest of the dependencies for any Elixir/Phoenix project. This has the disadvantage of having to manually upgrade their versions, but i can live with that by using &lt;a href=&quot;https://github.com/asdf-vm/asdf&quot;&gt;asdf&lt;/a&gt; to manage them. I then proceeded to clone my repo into a folder on the host, where i will build, release, and control the service. This is done once, and has the advantage of caching dependencies directly on the target, which speeds up the deployment process.&lt;/p&gt;&lt;p&gt;Then, every commit i push to master (my code lives on Github, obviously), is caught by the following action:&lt;/p&gt;&lt;pre&gt;name: Deploy&lt;/pre&gt;&lt;pre&gt;on:&lt;br&gt;  push:&lt;br&gt;    branches: [ master ]&lt;br&gt;  pull_request:&lt;br&gt;    branches: [ master ]&lt;/pre&gt;&lt;pre&gt;jobs:&lt;br&gt;  test_and_deploy:&lt;br&gt;    runs-on: ubuntu-latest&lt;/pre&gt;&lt;pre&gt;steps:&lt;br&gt;  - name: Slack Notification (Start)&lt;br&gt;    env:&lt;br&gt;      SLACK_BOT_TOKEN: ${{ secrets.slack_bot_token }}&lt;br&gt;    uses: pullreminders/slack-action@master&lt;br&gt;    with:&lt;br&gt;      args: &amp;#39;{\&quot;channel\&quot;:\&quot;@nocivus\&quot;,\&quot;text\&quot;:\&quot;Starting website deployment...\&quot;}&amp;#39;&lt;/pre&gt;&lt;pre&gt;  - name: Deploy To Linode&lt;br&gt;    uses: appleboy/ssh-action@master&lt;br&gt;    with:&lt;br&gt;      host: pedroassuncao.com&lt;br&gt;      username: &lt;your_username&gt;&lt;br&gt;      key: ${{ secrets.ssh_key }}&lt;br&gt;      port: 22&lt;br&gt;      script_stop: true&lt;br&gt;      script: &lt;em&gt;|&lt;br&gt;        &lt;/em&gt;echo &quot;Sourcing nvm...&quot;&lt;br&gt;        . /home/deploy/.nvm/nvm.sh&lt;br&gt;        echo &quot;Pulling code...&quot;&lt;br&gt;        cd noozo_v2/pedroassuncao.com&lt;br&gt;        git submodule update --remote&lt;br&gt;        git pull --recurse-submodules origin master&lt;br&gt;        echo &quot;Updating mix deps...&quot;&lt;br&gt;        mix deps.get --only prod&lt;br&gt;        echo &quot;Setting up secrets...&quot;&lt;br&gt;        rm config/prod.secret.server.exs&lt;br&gt;        ln -s ~/prod.secret.exs config/prod.secret.server.exs&lt;br&gt;        echo &quot;Compiling...&quot;&lt;br&gt;        MIX_ENV=prod mix compile&lt;br&gt;        echo &quot;Setting up assets...&quot;&lt;br&gt;        npm install --prefix assets&lt;br&gt;        npm run deploy --prefix assets&lt;br&gt;        MIX_ENV=prod mix phx.digest&lt;br&gt;        echo &quot;Releasing...&quot;&lt;br&gt;        MIX_ENV=prod mix release --overwrite&lt;br&gt;        MIX_ENV=prod mix ecto.migrate&lt;br&gt;        echo &quot;Stopping server...&quot;&lt;br&gt;        _build/prod/rel/noozo/bin/noozo stop&lt;br&gt;        sleep 5&lt;br&gt;        echo &quot;Starting server...&quot;&lt;br&gt;        _build/prod/rel/noozo/bin/noozo daemon&lt;br&gt;        echo &quot;All done.&quot;&lt;/pre&gt;&lt;pre&gt;  - name: Slack Notification (Success)&lt;br&gt;    if: success()&lt;br&gt;    env:&lt;br&gt;      SLACK_BOT_TOKEN: ${{ secrets.slack_bot_token }}&lt;br&gt;    uses: pullreminders/slack-action@master&lt;br&gt;    with:&lt;br&gt;      args: &amp;#39;{\&quot;channel\&quot;:\&quot;@nocivus\&quot;,\&quot;text\&quot;:\&quot;Website successfuly deployed :rocket:\&quot;}&amp;#39;&lt;/pre&gt;&lt;pre&gt;  - name: Slack Notification (Fail)&lt;br&gt;    if: failure()&lt;br&gt;    env:&lt;br&gt;      SLACK_BOT_TOKEN: ${{ secrets.slack_bot_token }}&lt;br&gt;    uses: pullreminders/slack-action@master&lt;br&gt;    with:&lt;br&gt;      args: &amp;#39;{\&quot;channel\&quot;:\&quot;@nocivus\&quot;,\&quot;text\&quot;:\&quot;Website failed to deploy :dizzy_face:\&quot;}&amp;#39;&lt;/pre&gt;&lt;p&gt;As you might have noticed, i use Github secrets to inject the Slack webhooks and also the SSH key to connect to my target machine.&lt;/p&gt;&lt;p&gt;I&amp;#39;m pretty happy with this solution so far, however it&amp;#39;s not without its flaws:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;By not using restart the website is down for 5–10 seconds every deployment (there&amp;#39;s probably a way around that, but last i tried it didn&amp;#39;t work very well when the mix.exs version changes).&lt;/li&gt;&lt;li&gt;Elixir/Erlang/NodeJS versions need to be manually upgraded on the target host.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Let me know if there&amp;#39;s anything i can improve, would love to hear your thoughts!&lt;/p&gt;&lt;p&gt;Happy Elixir&amp;#39;ing&lt;/p&gt;&lt;img src=&quot;https://medium.com/_/stat?event=post.clientViewed&amp;referrerSource=full_rss&amp;postId=ec4178ba5fb&quot; width=&quot;1&quot; height=&quot;1&quot;&gt;</content>
    <id>https://pedroassuncao.com/posts/elixirphoenix-deployments-using-github-actions</id>
    <title>Elixir/Phoenix deployments using Github Actions</title>
    <updated>2020-07-23T16:00:37Z</updated>
  </entry>
  <entry>
    <link href="https://pedroassuncao.com/posts/integrating-visjs-with-phoenix-live-view"/>
    <content type="html">&lt;figure&gt;&lt;img alt=&quot;&quot; src=&quot;https://cdn-images-1.medium.com/max/1024/1*V6bQRDAPPvFNF7kEZLbDJg.png&quot; /&gt;&lt;/figure&gt;&lt;p&gt;One of the projects here at Drover required us to show a directed graph representation of a flow that happens inside that service and, after looking into various alternatives, we settled on &lt;a href=&quot;https://visjs.org/&quot;&gt;https://visjs.org/&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Since this project has a &lt;a href=&quot;https://phoenixframework.org&quot;&gt;Phoenix&lt;/a&gt; &lt;a href=&quot;https://github.com/phoenixframework/phoenix_live_view&quot;&gt;LiveView&lt;/a&gt; back-office, I had to figure out how to integrate the graph drawing mechanism into the LV and the way we ended up doing that was by using the hooks mechanism to inject the data into vis.js and draw the graph after the view was mounted.&lt;/p&gt;&lt;h3&gt;The Live View&lt;/h3&gt;&lt;pre&gt;defmodule OutstandingLittleServiceWeb.Diagrams.ShowView do&lt;br&gt;  @moduledoc “””&lt;br&gt;  Shows a particular diagram&lt;br&gt;  “””&lt;br&gt;  use OutstandingLittleServiceWeb, :live_view&lt;br&gt;  alias OutstandingLittleService.Data&lt;br&gt;  alias OutstandingLittleService.Utils&lt;/pre&gt;&lt;pre&gt;  def render(assigns) do&lt;br&gt;    ~L”””&lt;br&gt;    &lt;%= if @loading do %&gt;&lt;br&gt;      &lt;div&gt;Loading information…&lt;/div&gt;&lt;br&gt;    &lt;% else %&gt;&lt;br&gt;      &lt;h1&gt;&lt;%= @diagram.name %&gt;&lt;/h1&gt;&lt;br&gt;      &lt;h5&gt;&lt;%= @diagram.uuid %&gt;&lt;/h5&gt;&lt;br&gt;      &lt;h3&gt;&lt;%= @diagram.description %&gt;&lt;/h3&gt;&lt;br&gt;      &lt;div id=”diagram”&lt;br&gt;           style=”width:800px; height:400px”&lt;br&gt;           phx-hook=”Diagram”&lt;br&gt;           data-diagram-data=”&lt;%= Poison.encode!(Utils.diagram_to_json(@diagram)) %&gt;”&gt;&lt;/div&gt;&lt;br&gt;      &lt;p/&gt;&lt;br&gt;    &lt;% end %&gt;&lt;br&gt;    “””&lt;br&gt;  end&lt;/pre&gt;&lt;pre&gt;  def mount(_params, _session, socket) do&lt;br&gt;    {:ok, assign(socket, loading: true)}&lt;br&gt;  end&lt;/pre&gt;&lt;pre&gt;  def handle_info({:load_diagram, params}, socket) do&lt;br&gt;    diagram = Data.get_diagram!(params[“id”], eager_load: true)&lt;/pre&gt;&lt;pre&gt;    {:noreply,&lt;br&gt;     assign(socket,&lt;br&gt;     loading: false,&lt;br&gt;     diagram: diagram&lt;br&gt;    )}&lt;br&gt;  end&lt;/pre&gt;&lt;pre&gt;  def handle_params(params, _uri, socket) do&lt;br&gt;    send(self(), {:load_diagram, params})&lt;br&gt;    {:noreply, assign(socket, loading: true)}&lt;br&gt;  end&lt;br&gt;end&lt;/pre&gt;&lt;p&gt;As you can see, it’s a pretty standard LV in which i add the phx-hook “Diagram” to the diagram div and also inject the JSON structure i want as a data attribute. Utils.diagram_to_json is merely a helper function that knows how to convert a diagram to a JSON representation that vis.js can use.&lt;/p&gt;&lt;h3&gt;LiveView hooks&lt;/h3&gt;&lt;p&gt;On app.js i then define the hook that will inject the data and initialise the graph:&lt;/p&gt;&lt;pre&gt;import css from “../css/app.css”&lt;br&gt;import “phoenix_html”&lt;/pre&gt;&lt;pre&gt;import { Socket } from “phoenix”&lt;br&gt;import LiveSocket from “phoenix_live_view”&lt;br&gt;import diagramInit from “./diagram”&lt;/pre&gt;&lt;pre&gt;let csrfToken = document.querySelector(“meta[name=’csrf-token’]”)&lt;br&gt; .getAttribute(“content”)&lt;/pre&gt;&lt;pre&gt;let Hooks = {}&lt;br&gt;Hooks.Diagram = {&lt;br&gt;  mounted() {&lt;br&gt;    let data = this.el.getAttribute(“data-diagram-data”)&lt;br&gt;    diagramInit(JSON.parse(data))&lt;br&gt;  }&lt;br&gt;}&lt;/pre&gt;&lt;pre&gt;let liveSocket = new LiveSocket(&lt;br&gt;  “/live”,&lt;br&gt;  Socket, {&lt;br&gt;    hooks: Hooks,&lt;br&gt;    params: {&lt;br&gt;      _csrf_token: csrfToken&lt;br&gt;    }&lt;br&gt;  }&lt;br&gt;)&lt;br&gt;liveSocket.connect()&lt;/pre&gt;&lt;h3&gt;Initialising the graph&lt;/h3&gt;&lt;p&gt;Finally on diagram.js the graph gets initialised:&lt;/p&gt;&lt;pre&gt;import { DataSet, Network } from ‘vis/index-network’;&lt;/pre&gt;&lt;pre&gt;function getOptions() {&lt;br&gt;  // Return vis.js options here, like layout, physics, etc&lt;br&gt;  // Ommited for brevity&lt;br&gt;}&lt;/pre&gt;&lt;pre&gt;function addNode(nodes, id, label, description, result = null) {&lt;br&gt;  nodes.push({&lt;br&gt;    id: id,&lt;br&gt;    label: “&lt;b&gt;” + label + “&lt;/b&gt;”,&lt;br&gt;    description: description&lt;br&gt;  })&lt;br&gt;  return nodes&lt;br&gt;}&lt;/pre&gt;&lt;pre&gt;function addLink(links, id, start_id, end_id, label = null) {&lt;br&gt;  links.push({&lt;br&gt;    id: id,&lt;br&gt;    from: start_id,&lt;br&gt;    to: end_id,&lt;br&gt;    arrows: “to”,&lt;br&gt;    physics: “false”,&lt;br&gt;    label: “&lt;b&gt;” + label + “&lt;/b&gt;”&lt;br&gt;  })&lt;br&gt;  return links&lt;br&gt;}&lt;/pre&gt;&lt;pre&gt;function diagramInit(data) {&lt;br&gt;  let diagram = data&lt;/pre&gt;&lt;pre&gt;  // Setup data&lt;br&gt;  let nodes = []&lt;br&gt;  diagram.components.forEach(component =&gt; {&lt;br&gt;    nodes = addNode(nodes, component.id, component.name, component.description, component.result)&lt;br&gt;  })&lt;/pre&gt;&lt;pre&gt;  // Setup links&lt;br&gt;  let links = []&lt;br&gt;  diagram.links.forEach(link =&gt; {&lt;br&gt;    links = addLink(links, link.id, link.start_component_id, link.end_component_id)&lt;br&gt;  })&lt;/pre&gt;&lt;pre&gt;  // Setup graph&lt;br&gt;  let container = document.getElementById(‘diagram’)&lt;br&gt;  let data = { nodes: nodes, edges: links }&lt;br&gt;  new Network(container, data, getOptions())&lt;br&gt;}&lt;/pre&gt;&lt;pre&gt;export default diagramInit&lt;/pre&gt;&lt;h3&gt;Notes&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Vis is added to the project using “npm install vis”, which will add the entry to the package.json file for you.&lt;/li&gt;&lt;li&gt;Some code on the diagram.js file is obviously specific to my case, namely the links between the diagram components, which are used to tell vis how to connect things, but it boils down to adding the nodes each with a different id, and then telling vis the start and end id for each connection.&lt;/li&gt;&lt;li&gt;As always, your mileage may vary.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Final thoughts&lt;/h3&gt;&lt;p&gt;Hopeful by now you should have a good idea how to integrate external JS libraries and make them work with LiveView. It’s basically just figuring out how to plug them into the JS hooks mechanism and pass the adequate data between them.&lt;/p&gt;&lt;p&gt;There are other ways to pass the data, data attributes was just the way that seemed the easiest. But you could probably also use events between JS and the LV to accomplish the same goal, or other strategies.&lt;/p&gt;&lt;p&gt;Happy Elixir&amp;#39;ing!&lt;/p&gt;&lt;img src=&quot;https://medium.com/_/stat?event=post.clientViewed&amp;referrerSource=full_rss&amp;postId=91aaae4b3b03&quot; width=&quot;1&quot; height=&quot;1&quot;&gt;&lt;hr&gt;&lt;p&gt;&lt;a href=&quot;https://medium.com/drover-engineering/integrating-vis-js-with-phoenix-live-view-91aaae4b3b03&quot;&gt;Integrating vis.js with Phoenix Live View&lt;/a&gt; was originally published in &lt;a href=&quot;https://medium.com/drover-engineering&quot;&gt;Drover Engineering&lt;/a&gt; on Medium, where people are continuing the conversation by highlighting and responding to this story.&lt;/p&gt;</content>
    <id>https://pedroassuncao.com/posts/integrating-visjs-with-phoenix-live-view</id>
    <title>Integrating vis.js with Phoenix Live View</title>
    <updated>2020-07-23T14:25:13Z</updated>
  </entry>
  <entry>
    <link href="https://pedroassuncao.com/posts/openapispex-with-mix-releases-not-working"/>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://github.com/open-api-spex/open_api_spex&quot;&gt;OpenApiSpex&lt;/a&gt; is an Elixir lib to provide OpenAPI documentation for your Phoenix web application. The way it works is you annotate the controller functions with @doc statements where you specify what the request and responses should look like.&lt;/p&gt;

&lt;p&gt;There is currently an issue with the lib where it relies on the documentation being available inside the compiled BEAM files at runtime which - if you use &lt;code&gt;mix release&lt;/code&gt; - by default is not. This makes any requests to your entry points return a cryptic &lt;code&gt;:chunk_not_found&lt;/code&gt; error. The reason is the documentation &quot;chunk&quot; inside your controller compiled file is not there (i.e. it was striped when mix release ran).&lt;/p&gt;

&lt;p&gt;The workaround for this right now is to pass &lt;code&gt;strip_beams: false&lt;/code&gt; in the mix release configuration in your &lt;code&gt;iex.exs&lt;/code&gt; file:&lt;/p&gt;

&lt;code&gt;
&lt;pre&gt;
  def project do
    [
      ...
      releases: releases()
    ]
  end

  defp releases() do
    [
      prod: [
        include_executables_for: [:unix],
        include_erts: true,
        &lt;strong&gt;strip_beams: false&lt;/strong&gt;,
        quiet: false
      ]
    ]
  end
&lt;/pre&gt;
&lt;/code&gt;

&lt;p&gt;The only issue with this approach right now is that it will add a fair bit to your final package size (around 25MB extra in my 72MB project).&lt;/p&gt;

&lt;p&gt;There is work in progress in the &lt;code&gt;mix release&lt;/code&gt; process to allow more granular control over what gets stripped from the compiled files, which will allow us in the future to strip everything except, for instance, documentation (which is only 1 or 2MB).&lt;/p&gt;

&lt;p&gt;Happy BEAMing&lt;/p&gt;
</content>
    <id>https://pedroassuncao.com/posts/openapispex-with-mix-releases-not-working</id>
    <title>OpenApiSpex with mix releases not working</title>
    <updated>2020-06-25T10:22:07Z</updated>
  </entry>
</feed>