Compare commits

...

8 commits

7 changed files with 274 additions and 83 deletions

View file

@ -3,9 +3,12 @@ defmodule FreediveWeb.HomeLive do
def render(assigns) do
~H"""
<.section>
<.block class="px-2 py-4">
<.panel is-info>
<.panel_heading>Home</.panel_heading>
<.panel_heading>
Home
</.panel_heading>
<.panel_tabs is-hidden-mobile>
<a class="is-active">All</a>
<a>Compute</a>
@ -13,21 +16,22 @@ defmodule FreediveWeb.HomeLive do
<a>Network</a>
<a>System</a>
</.panel_tabs>
<.panel_tabs is-hidden-tablet>
<a class="is-active" title="All">
<.icon for="infinity" color="auto" />
<a title="All" is-active>
<.icon for="all" color="auto" />
</a>
<a title="Compute">
<.icon for="binary" color="auto" />
<.icon for="compute" color="auto" />
</a>
<a title="Storage">
<.icon for="hard-drive" color="auto" />
<.icon for="storage" color="auto" />
</a>
<a title="Network">
<.icon for="earth" color="auto" />
<.icon for="network" color="auto" />
</a>
<a title="System">
<.icon for="bot" color="auto" />
<.icon for="system" color="auto" />
</a>
</.panel_tabs>
@ -40,11 +44,10 @@ defmodule FreediveWeb.HomeLive do
name="search"
value={@query}
/>
<span class="icon is-left">
<Lucideicons.search aria-hidden="true" />
</span>
<.icon for="search" size="1.5rem" aria-hidden="true" is-left />
</.control>
</.panel_block>
<.link patch={~p"/services"} class="panel-block pt-1">
<span class="panel-icon">
<.icon for="puzzle" color="auto" aria-hidden="true" />
@ -64,12 +67,32 @@ defmodule FreediveWeb.HomeLive do
<span class="mt-2 ml-2">Software updates</span>
</.link>
</.panel>
</.block>
<.section>
<.box>
<.button phx-click="color" phx-value-enable="true">
Color
</.button>
<.button phx-click="color" phx-value-enable="false">
Grayscale
</.button>
</.box>
</.section>
"""
end
def mount(_params, _session, socket) do
socket = assign(socket, query: "")
socket = assign(socket, query: "all")
{:ok, socket}
end
def handle_event("color", %{"enable" => "true"}, socket) do
Freedive.Features.enable(:colorhash)
{:noreply, assign(socket, query: "color")}
end
def handle_event("color", %{"enable" => "false"}, socket) do
Freedive.Features.disable(:colorhash)
{:noreply, assign(socket, query: "grayscale")}
end
end

View file

@ -1,79 +1,80 @@
defmodule FreediveWeb.LiliformLive do
use FreediveWeb, :live_view
defmacro __using__(opts) do
quote location: :keep, bind_quoted: [opts: opts] do
use FreediveWeb, :live_view
@behaviour FreediveWeb.LiliformLive
@filters_all [
%{
title: "All",
icon: "all",
active: false,
key: :all
}
]
def render(assigns) do
~H"""
<.block class="px-2 py-4">
<.panel is-info>
<.panel_heading>
Home
</.panel_heading>
def(mount(_params, _session, socket)) do
items = items()
filters = filters()
socket = assign(socket, :opts, Keyword.get(unquote(opts), :opts, []))
socket = assign(socket, :items_all, items)
<.panel_tabs is-hidden-mobile>
<a class="is-active">All</a>
<a>Compute</a>
<a>Storage</a>
<a>Network</a>
<a>System</a>
</.panel_tabs>
socket =
assign(
socket,
:items,
FreediveWeb.LiliformLive.filter(
items,
FreediveWeb.LiliformLive.get_active_filter(filters)
)
)
<.panel_tabs is-hidden-tablet>
<a title="All" is-active>
<.icon for="all" color="auto" />
</a>
<a title="Compute">
<.icon for="compute" color="auto" />
</a>
<a title="Storage">
<.icon for="storage" color="auto" />
</a>
<a title="Network">
<.icon for="network" color="auto" />
</a>
<a title="System">
<.icon for="system" color="auto" />
</a>
</.panel_tabs>
socket = assign(socket, :filters, @filters_all ++ filters)
socket = assign(socket, :query, "")
{:ok, socket}
end
<.panel_block>
<.control has-icons-left>
<input
class="input is-info"
type="text"
placeholder="Search"
name="search"
value={@query}
/>
<.icon for="search" size="1.5rem" aria-hidden="true" is-left />
</.control>
</.panel_block>
</.panel>
</.block>
<.section>
<.box>
<.button phx-click="color" phx-value-enable="true">
Color
</.button>
<.button phx-click="color" phx-value-enable="false">
Grayscale
</.button>
</.box>
</.section>
"""
def(handle_event("search", %{"value" => query}, socket)) do
items = search(query)
socket = assign(socket, :items, items)
{:noreply, socket}
end
def(handle_event("filter", %{"key" => key}, socket)) do
key = String.to_existing_atom(key)
items = FreediveWeb.LiliformLive.filter(socket.assigns.items_all, key)
socket = assign(socket, :items, items)
socket =
assign(
socket,
:filters,
Enum.map(socket.assigns.filters, fn filter ->
Map.put(filter, :active, filter.key == key)
end)
)
{:noreply, socket}
end
end
end
def mount(_params, _session, socket) do
socket = assign(socket, query: "all")
{:ok, socket}
@callback items() :: [map]
@callback filters() :: [map]
@callback search(query :: String.t()) :: [map]
def filter(items, key) do
case key do
:all ->
items
_ ->
Enum.filter(items, fn item ->
Map.get(item, key) == true
end)
end
end
def handle_event("color", %{"enable" => "true"}, socket) do
Freedive.Features.enable(:colorhash)
{:noreply, assign(socket, query: "color")}
end
def handle_event("color", %{"enable" => "false"}, socket) do
Freedive.Features.disable(:colorhash)
{:noreply, assign(socket, query: "grayscale")}
def get_active_filter(filters) do
filters |> Enum.find(& &1.active) |> Map.get(:key)
end
end

View file

@ -0,0 +1,97 @@
defmodule FreediveWeb.ServiceLive do
use FreediveWeb.LiliformLive
def render(assigns) do
~H"""
<.block class="px-2 py-4">
<.page name="Services" filters={@filters}>
<FreediveWeb.ServiceLive.Item.items_block items={@items} />
</.page>
</.block>
"""
end
def items() do
[
%{
name: "sshd",
path: "/services/ssh",
icon: "blocks",
description: "Secure Shell Daemon",
enabled: true,
running: true
},
%{
name: "pf",
path: "/services/pf",
icon: "shield",
description: "Packet Filter",
enabled: true,
running: true
},
%{
name: "ntpdate",
path: "/services/ntp",
icon: "clock",
description: "Network Time Protocol Daemon",
enabled: true,
running: false
},
%{
name: "httpd",
path: "/services/httpd",
icon: "globe",
description: "Hypertext Transfer Protocol Daemon",
enabled: false,
running: false
}
]
end
def filters() do
[
%{
title: "Running",
key: :running,
icon: "circle-play",
active: true
},
%{
title: "Enabled",
key: :enabled,
icon: "circle-check",
active: false
}
]
end
def search(query) do
items()
|> Enum.filter(fn item ->
String.contains?(String.downcase(item.name), String.downcase(query))
end)
end
end
defmodule FreediveWeb.ServiceLive.Item do
use Liliform.Component
import Liliform.Icon
@doc """
Returns a list of items.
"""
attr :items, :list, default: [], doc: "items"
def items_block(assigns) do
~H"""
<%= for item <- @items do %>
<.link patch={item.path} class="panel-block pt-1">
<span class="panel-icon">
<.icon for={item.icon} color="auto" aria-hidden="true" />
</span>
<div class="mt-2 ml-2"><%= item.name %></div>
</.link>
<% end %>
"""
end
end

View file

@ -69,7 +69,7 @@ defmodule FreediveWeb.Router do
live_session :require_authenticated_user,
on_mount: [{FreediveWeb.UserAuth, :ensure_authenticated}] do
live "/", HomeLive
live "/liliform", LiliformLive
live "/services", ServiceLive
live "/users/settings", UserSettingsLive, :edit
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
end

View file

@ -15,6 +15,7 @@ defmodule Liliform do
import Liliform.Label
import Liliform.Media
import Liliform.Navbar
import Liliform.Page
import Liliform.Panel
import Liliform.Section
import Liliform.SimpleForm

View file

@ -21,7 +21,6 @@ defmodule Liliform.Colorhash do
"""
def hsl(input, raw: true) do
if Features.enabled?(:colorhash) do
Logger.debug("Colorhash enabled")
input = String.downcase(input) <> @seed
hash = :erlang.phash2(input, 2_147_483_647)
@ -31,7 +30,6 @@ defmodule Liliform.Colorhash do
{hue, saturation, lightness}
else
Logger.debug("Colorhash disabled")
@default_color
end
end

71
lib/liliform/page.ex Normal file
View file

@ -0,0 +1,71 @@
defmodule Liliform.Page do
use Liliform.Component
import Liliform.Icon
import Liliform.Panel
import Liliform.Control
@doc """
Renders a panel as page.
"""
attr :name, :string, required: true, doc: "page name"
attr :class, :string, default: "", doc: "additional classes"
attr :filters, :list, default: [], doc: "filters"
attr :query, :string, default: "", doc: "search query"
attr :rest, :global
slot :inner_block, required: true
def page(assigns) do
assigns =
assigns
|> set_bulma_classes()
~H"""
<.panel class={@class} {@rest} is-info>
<.panel_heading>
<%= @name %>
</.panel_heading>
<.panel_tabs is-hidden-mobile>
<%= for filter <- @filters do %>
<a
class={if filter.active, do: "is-active"}
phx-click="filter"
phx-value-key={filter.key}
>
<%= filter.title %>
</a>
<% end %>
</.panel_tabs>
<.panel_tabs is-hidden-tablet>
<%= for filter <- @filters do %>
<a
title={filter.title}
class={if filter.active, do: "is-active"}
phx-click="filter"
phx-value-key={filter.key}
>
<.icon for={filter.icon} color="auto" />
</a>
<% end %>
</.panel_tabs>
<.panel_block>
<.control has-icons-left>
<input
class="input is-info"
type="text"
placeholder="Search"
name="search"
value={@query}
phx-keyup="search"
/>
<.icon for="search" size="1.5rem" aria-hidden="true" is-left />
</.control>
</.panel_block>
<%= render_slot(@inner_block) %>
</.panel>
"""
end
end