Add colorhash

This commit is contained in:
Harshad Sharma 2024-05-14 06:22:55 +05:30
parent c935fbbc42
commit beea7b0caf
6 changed files with 110 additions and 10 deletions

View file

@ -13,7 +13,8 @@ config :freedive,
config :freedive, config :freedive,
features: [ features: [
register_account: true register_account: true,
colorhash: true
] ]
# Configures the endpoint # Configures the endpoint

View file

@ -18,6 +18,12 @@ if config_env() == :prod do
config :freedive, :features, register_account: false config :freedive, :features, register_account: false
end end
# Colorhash feature is enabled by default.
# To disable, set the COLORHASH_ENABLE environment variable to "false".
if System.get_env("COLORHASH_ENABLE") == "false" do
config :freedive, :features, colorhash: false
end
database_path = database_path =
System.get_env("DATABASE_PATH") || System.get_env("DATABASE_PATH") ||
raise """ raise """

View file

@ -11,6 +11,7 @@ TLS_CERT_PATH="<%= @data_dir %>/tls.crt"
# Features # Features
REGISTER_ACCOUNT_ENABLE="false" REGISTER_ACCOUNT_ENABLE="false"
COLORHASH_ENABLE="true"
# Phoenix # Phoenix
PHX_SERVER=true PHX_SERVER=true

48
lib/freedive/colorhash.ex Normal file
View file

@ -0,0 +1,48 @@
defmodule Freedive.Colorhash do
@moduledoc """
Given a string returns consistent HSL color.
"""
alias Freedive.Features
@default_hsl {0, 0, 0}
def hsl(input, css: true) do
{hue, saturation, lightness} = hsl(input)
"hsl(#{hue}, #{saturation}%, #{lightness}%)"
end
def hsl(input) do
if Features.enabled?(:colorhash) do
input = String.downcase(input)
hash = :erlang.phash2(input, 2_147_483_647)
hue = calculate_hue(hash)
saturation = 80 + rem(hash, 21)
lightness = 30 + rem(hash, 31)
{hue, saturation, lightness}
else
@default_hsl
end
end
@doc """
Calculates hue avoiding low contrast colors.
"""
def calculate_hue(hash) do
base_hue = rem(hash, 360)
# Ranges to exclude (yellow to light green, cyan)
excluded_ranges = [
# yellow
{50, 70},
# cyan
{175, 185},
]
# Adjust the hue to skip over excluded ranges
Enum.reduce(excluded_ranges, base_hue, fn {start, stop}, acc_hue ->
if acc_hue >= start and acc_hue <= stop, do: stop + (acc_hue - start + 1), else: acc_hue
end)
end
end

View file

@ -1,5 +1,6 @@
defmodule FreediveWeb.HomeLive do defmodule FreediveWeb.HomeLive do
use FreediveWeb, :live_view use FreediveWeb, :live_view
import Freedive.Colorhash
def render(assigns) do def render(assigns) do
~H""" ~H"""
@ -14,11 +15,19 @@ defmodule FreediveWeb.HomeLive do
<a>System</a> <a>System</a>
</.panel_tabs> </.panel_tabs>
<.panel_tabs is-hidden-tablet> <.panel_tabs is-hidden-tablet>
<a class="is-active"><Lucideicons.infinity aria-hidden="true" /></a> <a class="is-active">
<a><Lucideicons.binary aria-hidden="true" /></a> <Lucideicons.infinity style={"color: #{hsl("all", css: true)}"} aria-hidden="true" />
<a><Lucideicons.hard_drive aria-hidden="true" /></a> </a>
<a><Lucideicons.earth aria-hidden="true" /></a> <a>
<a><Lucideicons.bot aria-hidden="true" /></a> <Lucideicons.binary style={"color: #{hsl("compute", css: true)}"} aria-hidden="true" />
</a>
<a>
<Lucideicons.hard_drive style={"color: #{hsl("storage", css: true)}"} aria-hidden="true" />
</a>
<a>
<Lucideicons.earth style={"color: #{hsl("network", css: true)}"} aria-hidden="true" />
</a>
<a><Lucideicons.bot style={"color: #{hsl("system", css: true)}"} aria-hidden="true" /></a>
</.panel_tabs> </.panel_tabs>
<.panel_block> <.panel_block>
@ -37,25 +46,37 @@ defmodule FreediveWeb.HomeLive do
</.panel_block> </.panel_block>
<.link patch={~p"/users/settings"} class="panel-block pt-1"> <.link patch={~p"/users/settings"} class="panel-block pt-1">
<span class="panel-icon"> <span class="panel-icon">
<Lucideicons.user_cog aria-hidden="true" /> <Lucideicons.user_cog
style={"color: #{hsl("account settings", css: true)}"}
aria-hidden="true"
/>
</span> </span>
<span class="mt-2 ml-2">Account settings</span> <span class="mt-2 ml-2">Account settings</span>
</.link> </.link>
<.link patch={~p"/services"} class="panel-block pt-1"> <.link patch={~p"/services"} class="panel-block pt-1">
<span class="panel-icon"> <span class="panel-icon">
<Lucideicons.puzzle aria-hidden="true" /> <Lucideicons.puzzle
style={"color: #{hsl("system services", css: true)}"}
aria-hidden="true"
/>
</span> </span>
<div class="mt-2 ml-2">System services</div> <div class="mt-2 ml-2">System services</div>
</.link> </.link>
<.link patch={~p"/packages"} class="panel-block pt-1"> <.link patch={~p"/packages"} class="panel-block pt-1">
<span class="panel-icon"> <span class="panel-icon">
<Lucideicons.package aria-hidden="true" /> <Lucideicons.package
style={"color: #{hsl("system packages", css: true)}"}
aria-hidden="true"
/>
</span> </span>
<span class="mt-2 ml-2">System packages</span> <span class="mt-2 ml-2">System packages</span>
</.link> </.link>
<.link patch={~p"/updates"} class="panel-block pt-1"> <.link patch={~p"/updates"} class="panel-block pt-1">
<span class="panel-icon"> <span class="panel-icon">
<Lucideicons.hard_drive_download aria-hidden="true" /> <Lucideicons.hard_drive_download
style={"color: #{hsl("system software updates", css: true)}"}
aria-hidden="true"
/>
</span> </span>
<span class="mt-2 ml-2">System software updates</span> <span class="mt-2 ml-2">System software updates</span>
</.link> </.link>

View file

@ -0,0 +1,23 @@
defmodule Freedive.ColorhashTest do
use ExUnit.Case
test "hsl" do
assert Freedive.Colorhash.hsl("foo") == {312, 80, 48}
assert Freedive.Colorhash.hsl("bar") == {38, 88, 38}
assert Freedive.Colorhash.hsl("baz") == {288, 95, 42}
end
test "hsl with css" do
assert Freedive.Colorhash.hsl("foo", css: true) == "hsl(312, 80%, 48%)"
assert Freedive.Colorhash.hsl("bar", css: true) == "hsl(38, 88%, 38%)"
assert Freedive.Colorhash.hsl("baz", css: true) == "hsl(288, 95%, 42%)"
end
test "hsl with disabled feature" do
Freedive.Features.disable(:colorhash)
assert Freedive.Colorhash.hsl("foo") == {0, 0, 0}
assert Freedive.Colorhash.hsl("bar") == {0, 0, 0}
assert Freedive.Colorhash.hsl("baz") == {0, 0, 0}
Freedive.Features.enable(:colorhash)
end
end