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"
|
||||
>
|
||||
<%= server.name %>
|
||||
</.link>
|
||||
</:item>
|
||||
</.dropdown>
|
||||
<button
|
||||
class="ml-4 inline-flex items-center"
|
||||
title="Edit server name"
|
||||
phx-click={show_edit_server_name()}
|
||||
>
|
||||
<%= 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>
|
||||
<.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 %>
|
||||
<p><%= task.progress %> complete</p>
|
||||
<% end %>
|
||||
<.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>
|
||||
</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