app: alpine.js + dropdown
This commit is contained in:
		
							parent
							
								
									c7b9be3ee4
								
							
						
					
					
						commit
						efd5f1d126
					
				
					 8 changed files with 192 additions and 64 deletions
				
			
		| 
						 | 
				
			
			@ -2,6 +2,10 @@ root = true
 | 
			
		|||
charset = utf-8
 | 
			
		||||
end_of_line = lf
 | 
			
		||||
 | 
			
		||||
[*.js]
 | 
			
		||||
indent_style = space
 | 
			
		||||
indent_size = 2
 | 
			
		||||
 | 
			
		||||
[*.nix]
 | 
			
		||||
indent_style = space
 | 
			
		||||
indent_size = 2
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,15 +15,26 @@
 | 
			
		|||
//     import "some-package"
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
 | 
			
		||||
import "phoenix_html"
 | 
			
		||||
// Establish Phoenix Socket and LiveView configuration.
 | 
			
		||||
import {Socket} from "phoenix"
 | 
			
		||||
import {LiveSocket} from "phoenix_live_view"
 | 
			
		||||
import topbar from "../vendor/topbar"
 | 
			
		||||
import Alpine from "alpinejs"
 | 
			
		||||
 | 
			
		||||
Alpine.start()
 | 
			
		||||
window.Alpine = Alpine
 | 
			
		||||
 | 
			
		||||
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
 | 
			
		||||
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
 | 
			
		||||
let liveSocket = new LiveSocket("/live", Socket, {
 | 
			
		||||
  params: {_csrf_token: csrfToken},
 | 
			
		||||
  dom: {
 | 
			
		||||
    onBeforeElUpdated(from, to) {
 | 
			
		||||
      if (from._x_dataStack) {
 | 
			
		||||
        window.Alpine.clone(from, to)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// Show progress bar on live navigation and form submits
 | 
			
		||||
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										33
									
								
								app/assets/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								app/assets/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "assets",
 | 
			
		||||
  "lockfileVersion": 3,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "packages": {
 | 
			
		||||
    "": {
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "alpinejs": "^3.13.3"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@vue/reactivity": {
 | 
			
		||||
      "version": "3.1.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
 | 
			
		||||
      "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@vue/shared": "3.1.5"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@vue/shared": {
 | 
			
		||||
      "version": "3.1.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
 | 
			
		||||
      "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/alpinejs": {
 | 
			
		||||
      "version": "3.13.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.3.tgz",
 | 
			
		||||
      "integrity": "sha512-WZ6WQjkAOl+WdW/jukzNHq9zHFDNKmkk/x6WF7WdyNDD6woinrfXCVsZXm0galjbco+pEpYmJLtwlZwcOfIVdg==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@vue/reactivity": "~3.1.1"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								app/assets/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/assets/package.json
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
{
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "alpinejs": "^3.13.3"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +36,7 @@ defmodule PrymnWeb.Button do
 | 
			
		|||
      assigns,
 | 
			
		||||
      :style,
 | 
			
		||||
      [
 | 
			
		||||
        "inline-flex justify-center items-end rounded-2xl shadow transition-colors active:shadow-sm",
 | 
			
		||||
        "inline-flex justify-center items-center rounded-2xl shadow transition-colors active:shadow-sm",
 | 
			
		||||
        by_variant(assigns.variant),
 | 
			
		||||
        by_size(assigns.size),
 | 
			
		||||
        assigns[:class]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -587,6 +587,62 @@ defmodule PrymnWeb.CoreComponents do
 | 
			
		|||
    """
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  @doc """
 | 
			
		||||
  A simple dropdown menu
 | 
			
		||||
  """
 | 
			
		||||
 | 
			
		||||
  attr :title, :string, default: nil
 | 
			
		||||
  attr :position, :string, default: "left", values: ~w(left right)
 | 
			
		||||
 | 
			
		||||
  slot :button
 | 
			
		||||
  slot :item
 | 
			
		||||
 | 
			
		||||
  def dropdown(assigns) do
 | 
			
		||||
    ~H"""
 | 
			
		||||
    <div
 | 
			
		||||
      class="relative inline-block"
 | 
			
		||||
      x-data="{
 | 
			
		||||
        open: false,
 | 
			
		||||
        close(focusAfter) {
 | 
			
		||||
          this.open = false
 | 
			
		||||
          focusAfter && focusAfter.focus()
 | 
			
		||||
        }
 | 
			
		||||
      }"
 | 
			
		||||
      x-id="['dropdown-button']"
 | 
			
		||||
      x-on:keydown.escape.prevent.stop="close($refs.button)"
 | 
			
		||||
    >
 | 
			
		||||
      <button
 | 
			
		||||
        title={@title}
 | 
			
		||||
        type="button"
 | 
			
		||||
        x-ref="button"
 | 
			
		||||
        x-bind:aria-expanded="open"
 | 
			
		||||
        x-on:click="open = !open"
 | 
			
		||||
      >
 | 
			
		||||
        <%= render_slot(@button) %>
 | 
			
		||||
        <.icon class="ml-2 h-4 w-4" name="hero-chevron-down" />
 | 
			
		||||
      </button>
 | 
			
		||||
      <div
 | 
			
		||||
        class="absolute left-0 z-10 w-56 rounded-xl bg-white shadow-lg ring-1 ring-black ring-opacity-5"
 | 
			
		||||
        role="menu"
 | 
			
		||||
        aria-orientation="vertical"
 | 
			
		||||
        tabindex="-1"
 | 
			
		||||
        x-show="open"
 | 
			
		||||
        x-on:click.outside="close($refs.button)"
 | 
			
		||||
        x-transition.origin.top.left
 | 
			
		||||
      >
 | 
			
		||||
        <div
 | 
			
		||||
          :for={item <- @item}
 | 
			
		||||
          class="my-2 px-4 py-2 hover:bg-slate-100"
 | 
			
		||||
          role="none"
 | 
			
		||||
          x-on:click="close()"
 | 
			
		||||
        >
 | 
			
		||||
          <%= render_slot(item, item) %>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    """
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  ## JS Commands
 | 
			
		||||
 | 
			
		||||
  def show(js \\ %JS{}, selector) do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,18 +16,17 @@
 | 
			
		|||
      </p>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="flex items-center text-zinc-900">
 | 
			
		||||
      <.link class="border-r border-l px-3 py-4 hover:bg-white" href={~p"/users/settings"}>
 | 
			
		||||
        <.icon name="hero-user" />
 | 
			
		||||
        <.icon name="hero-chevron-down" />
 | 
			
		||||
      </.link>
 | 
			
		||||
      <.link
 | 
			
		||||
        title="Log out"
 | 
			
		||||
        class="border-r px-3 py-4 hover:bg-white"
 | 
			
		||||
        method="DELETE"
 | 
			
		||||
        href={~p"/auth/log_out"}
 | 
			
		||||
      >
 | 
			
		||||
        <.icon name="hero-x-mark" />
 | 
			
		||||
      </.link>
 | 
			
		||||
      <.dropdown>
 | 
			
		||||
        <:button>
 | 
			
		||||
          <.icon name="hero-user-solid" />
 | 
			
		||||
        </:button>
 | 
			
		||||
        <:item>
 | 
			
		||||
          <.link href={~p"/users/settings"}>Settings</.link>
 | 
			
		||||
        </:item>
 | 
			
		||||
        <:item>
 | 
			
		||||
          <.link method="DELETE" href={~p"/auth/log_out"}>Log out</.link>
 | 
			
		||||
        </:item>
 | 
			
		||||
      </.dropdown>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</header>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,4 @@
 | 
			
		|||
defmodule PrymnWeb.ServerLive.Show do
 | 
			
		||||
  alias DBConnection.App
 | 
			
		||||
  use PrymnWeb, :live_view
 | 
			
		||||
 | 
			
		||||
  require Logger
 | 
			
		||||
| 
						 | 
				
			
			@ -7,44 +6,38 @@ defmodule PrymnWeb.ServerLive.Show do
 | 
			
		|||
 | 
			
		||||
  @impl true
 | 
			
		||||
  def mount(_params, _session, socket) do
 | 
			
		||||
    {:ok, socket}
 | 
			
		||||
    # TODO: A more lightweight call instead of listing all data?
 | 
			
		||||
    servers = Servers.list_servers()
 | 
			
		||||
    {:ok, assign(socket, :servers, servers)}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  @impl true
 | 
			
		||||
  def render(assigns) do
 | 
			
		||||
    ~H"""
 | 
			
		||||
    <div class="mx-auto max-w-2xl">
 | 
			
		||||
      <.header>
 | 
			
		||||
        <span id="server-name" class="relative flex items-center">
 | 
			
		||||
          <button title="Select a different server">
 | 
			
		||||
            Server <%= @server.name %>
 | 
			
		||||
            <.icon name="hero-chevron-down" />
 | 
			
		||||
          </button>
 | 
			
		||||
          <button class="ml-4" title="Edit server name" phx-click={show_edit_server_name()}>
 | 
			
		||||
            <.icon class="h-3 w-3" name="hero-pencil" />
 | 
			
		||||
          </button>
 | 
			
		||||
          <span
 | 
			
		||||
            role="tooltip"
 | 
			
		||||
            class={[
 | 
			
		||||
              "absolute -left-6 inline-flex h-3 w-3 before:-translate-x-1/2 before:-translate-y-full",
 | 
			
		||||
              "before:-top-2 before:left-1/2 before:absolute before:text-sm before:text-white",
 | 
			
		||||
              "before:font-normal before:content-[attr(data-tip)] before:opacity-0",
 | 
			
		||||
              "hover:before:opacity-100 before:py-1 before:px-2 before:bg-black",
 | 
			
		||||
              "before:rounded before:pointer-events-none before:transition-opacity"
 | 
			
		||||
            ]}
 | 
			
		||||
            data-tip={@health.message}
 | 
			
		||||
      <div class="flex justify-between">
 | 
			
		||||
        <div id="server-name" class="relative flex items-center">
 | 
			
		||||
          <.dropdown title="Select a different server">
 | 
			
		||||
            <:button>Server <%= @server.name %></:button>
 | 
			
		||||
            <:item :for={server <- Enum.filter(@servers, fn s -> s.id != @server.id end)}>
 | 
			
		||||
              <.link
 | 
			
		||||
                patch={~p"/servers/#{server}"}
 | 
			
		||||
                class="block text-sm text-gray-700"
 | 
			
		||||
                role="menuitem"
 | 
			
		||||
              >
 | 
			
		||||
            <%= case @health.message do %>
 | 
			
		||||
              <% "Connected" -> %>
 | 
			
		||||
                <span class="absolute top-0 left-0 h-full w-full animate-ping rounded-full bg-green-400 opacity-75" />
 | 
			
		||||
                <span class="h-3 w-3 rounded-full bg-green-500" />
 | 
			
		||||
              <% "Disconnected" -> %>
 | 
			
		||||
                <span class="h-3 w-3 rounded-full bg-red-500" />
 | 
			
		||||
              <% _ -> %>
 | 
			
		||||
                <span class="h-3 w-3 rounded-full bg-yellow-500" />
 | 
			
		||||
            <% end %>
 | 
			
		||||
          </span>
 | 
			
		||||
        </span>
 | 
			
		||||
                <%= server.name %>
 | 
			
		||||
              </.link>
 | 
			
		||||
            </:item>
 | 
			
		||||
          </.dropdown>
 | 
			
		||||
          <button
 | 
			
		||||
            class="ml-4 inline-flex items-center"
 | 
			
		||||
            title="Edit server name"
 | 
			
		||||
            phx-click={show_edit_server_name()}
 | 
			
		||||
          >
 | 
			
		||||
            <.icon class="h-4 w-4" name="hero-pencil-solid" />
 | 
			
		||||
          </button>
 | 
			
		||||
          <.indicator message={@health.message} />
 | 
			
		||||
        </div>
 | 
			
		||||
        <form class="hidden" id="server-name-edit" phx-submit={submit_edit_server_name()}>
 | 
			
		||||
          <input
 | 
			
		||||
            class="border-0 border-b border-gray-500"
 | 
			
		||||
| 
						 | 
				
			
			@ -53,25 +46,26 @@ defmodule PrymnWeb.ServerLive.Show do
 | 
			
		|||
            value={@server.name}
 | 
			
		||||
            required
 | 
			
		||||
          />
 | 
			
		||||
          <button type="submit" title="Confirm" class="ml-4">
 | 
			
		||||
            <.icon class="h-4 w-4" name="hero-check" />
 | 
			
		||||
          <button type="submit" title="Confirm">
 | 
			
		||||
            <.icon name="hero-check" />
 | 
			
		||||
          </button>
 | 
			
		||||
        </form>
 | 
			
		||||
        <:subtitle>
 | 
			
		||||
          <%= @server.public_ip %>
 | 
			
		||||
        </:subtitle>
 | 
			
		||||
        <:actions>
 | 
			
		||||
        <div class="space-x-2">
 | 
			
		||||
          <Button.primary size="sm">New App</Button.primary>
 | 
			
		||||
          <Button.secondary size="sm">
 | 
			
		||||
            Quick actions <.icon name="hero-chevron-down" class="ml-1" />
 | 
			
		||||
          </Button.secondary>
 | 
			
		||||
        </:actions>
 | 
			
		||||
      </.header>
 | 
			
		||||
      <div class="my-3 text-sm text-slate-700">
 | 
			
		||||
        <%= for {name, task} <- @health.tasks do %>
 | 
			
		||||
          Background task in progress: <%= name %>
 | 
			
		||||
          <.dropdown>
 | 
			
		||||
            <:button>
 | 
			
		||||
              Quick actions
 | 
			
		||||
            </:button>
 | 
			
		||||
            <:item>
 | 
			
		||||
              Test
 | 
			
		||||
            </:item>
 | 
			
		||||
          </.dropdown>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <span class="text-sm opacity-75"><%= @server.public_ip %></span>
 | 
			
		||||
      <div :for={{name, task} <- @health.tasks} class="my-3 text-sm text-slate-700">
 | 
			
		||||
        <p>Background task in progress: <%= name %></p>
 | 
			
		||||
        <p><%= task.progress %> complete</p>
 | 
			
		||||
        <% end %>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div :if={@server.status == :unregistered} class="my-10">
 | 
			
		||||
        <p class="mb-9">
 | 
			
		||||
| 
						 | 
				
			
			@ -229,4 +223,30 @@ defmodule PrymnWeb.ServerLive.Show do
 | 
			
		|||
    |> JS.hide()
 | 
			
		||||
    |> JS.show(to: "#server-name", display: "flex")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp indicator(assigns) do
 | 
			
		||||
    ~H"""
 | 
			
		||||
    <span
 | 
			
		||||
      role="tooltip"
 | 
			
		||||
      class={[
 | 
			
		||||
        "absolute -left-6 inline-flex h-3 w-3 before:-translate-x-1/2 before:-translate-y-full",
 | 
			
		||||
        "before:-top-2 before:left-1/2 before:absolute before:text-sm before:text-white",
 | 
			
		||||
        "before:font-normal before:content-[attr(data-tip)] before:opacity-0",
 | 
			
		||||
        "hover:before:opacity-100 before:py-1 before:px-2 before:bg-black",
 | 
			
		||||
        "before:rounded before:pointer-events-none before:transition-opacity"
 | 
			
		||||
      ]}
 | 
			
		||||
      data-tip={@message}
 | 
			
		||||
    >
 | 
			
		||||
      <%= case @message do %>
 | 
			
		||||
        <% "Connected" -> %>
 | 
			
		||||
          <span class="absolute top-0 left-0 h-full w-full animate-ping rounded-full bg-green-400 opacity-75" />
 | 
			
		||||
          <span class="h-3 w-3 rounded-full bg-green-500" />
 | 
			
		||||
        <% "Disconnected" -> %>
 | 
			
		||||
          <span class="h-3 w-3 rounded-full bg-red-500" />
 | 
			
		||||
        <% _ -> %>
 | 
			
		||||
          <span class="h-3 w-3 rounded-full bg-yellow-500" />
 | 
			
		||||
      <% end %>
 | 
			
		||||
    </span>
 | 
			
		||||
    """
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue