Implement avatar rendering logic: add custom tags, template, and styles; update base.html integration and revamp member filter and list designs.
This commit is contained in:
@@ -5,7 +5,42 @@
|
||||
|
||||
@import "tailwindcss";
|
||||
|
||||
@plugin "daisyui";
|
||||
@plugin "daisyui" {
|
||||
}
|
||||
|
||||
@plugin "daisyui/theme" {
|
||||
name: "light";
|
||||
default: true;
|
||||
prefersdark: true;
|
||||
--color-base-100: oklch(100% 0 0);
|
||||
--color-base-200: oklch(98% 0 0);
|
||||
--color-base-300: oklch(95% 0 0);
|
||||
--color-base-content: oklch(21% 0.006 285.885);
|
||||
--color-primary: oklch(45% 0.24 277.023);
|
||||
--color-primary-content: oklch(93% 0.034 272.788);
|
||||
--color-secondary: oklch(65% 0.241 354.308);
|
||||
--color-secondary-content: oklch(94% 0.028 342.258);
|
||||
--color-accent: oklch(77% 0.152 181.912);
|
||||
--color-accent-content: oklch(38% 0.063 188.416);
|
||||
--color-neutral: oklch(14% 0.005 285.823);
|
||||
--color-neutral-content: oklch(92% 0.004 286.32);
|
||||
--color-info: oklch(74% 0.16 232.661);
|
||||
--color-info-content: oklch(29% 0.066 243.157);
|
||||
--color-success: oklch(76% 0.177 163.223);
|
||||
--color-success-content: oklch(37% 0.077 168.94);
|
||||
--color-warning: oklch(82% 0.189 84.429);
|
||||
--color-warning-content: oklch(41% 0.112 45.904);
|
||||
--color-error: oklch(71% 0.194 13.428);
|
||||
--color-error-content: oklch(27% 0.105 12.094);
|
||||
--radius-selector: 0.5rem;
|
||||
--radius-field: 0.5rem;
|
||||
--radius-box: 0.5rem;
|
||||
--size-selector: 0.25rem;
|
||||
--size-field: 0.25rem;
|
||||
--border: 1px;
|
||||
--depth: 0;
|
||||
--noise: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* A catch-all path to Django template files, JavaScript, and Python files
|
||||
@@ -21,57 +56,45 @@
|
||||
--font-sans: Open Sans, Noto Sans, Barlow Semi Condensed, Ubuntu, Fira Sans, Catamaran, Cabin, Roboto, sans-serif;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
@source inline("input-{xs,sm,md,lg,xl}");
|
||||
|
||||
@layer components {
|
||||
h1.page-title {
|
||||
@apply text-3xl font-bold;
|
||||
@apply mb-12;
|
||||
|
||||
/*.navbar-small {
|
||||
& > svg {
|
||||
@apply mr-2;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-small > div {
|
||||
@apply py-2;
|
||||
div.action_bar {
|
||||
@apply flex flex-col lg:flex-row;
|
||||
@apply items-center;
|
||||
|
||||
& > .filter {
|
||||
@apply grow w-full;
|
||||
}
|
||||
|
||||
& > .filter > form {
|
||||
@apply flex flex-col lg:flex-row gap-2;
|
||||
@apply items-end;
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
& > .filter > form > div > button {
|
||||
@apply btn btn-outline btn-xs;
|
||||
@apply grow;
|
||||
}
|
||||
|
||||
& > .add {
|
||||
@apply my-6 lg:my-0;
|
||||
@apply w-full lg:w-fit shrink-0;
|
||||
@apply flex flex-row flex-wrap gap-2;
|
||||
}
|
||||
|
||||
& > .add > a {
|
||||
@apply min-w-fit lg:w-fit;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-small h1 {
|
||||
@apply text-lg;
|
||||
}
|
||||
|
||||
.navbar-small > div img {
|
||||
@apply max-h-8;
|
||||
}
|
||||
|
||||
.navbar-small nav {
|
||||
@apply text-base;
|
||||
}
|
||||
|
||||
.navbar-small nav div.avatar > div {
|
||||
@apply w-6;
|
||||
@apply text-xs;
|
||||
}*/
|
||||
|
||||
/* #sidebar {
|
||||
@apply flex flex-row gap-4 lg:flex-col;
|
||||
@apply lg:min-w-64;
|
||||
@apply mx-auto lg:mx-0;
|
||||
}
|
||||
|
||||
!* Each section is a group, stacked title + items *!
|
||||
#sidebar > div.section {
|
||||
@apply flex flex-col gap-2;
|
||||
}
|
||||
|
||||
!* Title stays simple, click/hover target *!
|
||||
#sidebar > div.section > div.section-title {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
||||
!* Items: - small: hidden by default, show on hover - large: flex as in your original design *!
|
||||
#sidebar > div.section > div.section-items {
|
||||
@apply hidden group-hover:flex flex-col gap-2;
|
||||
@apply bg-red-600;
|
||||
@apply lg:flex;
|
||||
}
|
||||
|
||||
#sidebar > div.section.open > div.section-items {
|
||||
@apply flex;
|
||||
}*/
|
||||
}
|
||||
0
theme/templatetags/__init__.py
Normal file
0
theme/templatetags/__init__.py
Normal file
49
theme/templatetags/avatar.py
Normal file
49
theme/templatetags/avatar.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from hashlib import md5
|
||||
from math import sqrt
|
||||
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
def calculate_brightness(background_color: dict) -> float:
|
||||
"""Calculates the brightness of a background image."""
|
||||
r_coefficient = 0.241
|
||||
g_coefficient = 0.691
|
||||
b_coefficient = 0.068
|
||||
|
||||
return sqrt(r_coefficient ** 2 * background_color["R"] + g_coefficient ** 2 * background_color["G"] + b_coefficient ** 2 * background_color["B"]) * 100
|
||||
|
||||
def foreground(background_color: dict) -> dict:
|
||||
"""Calculates the foreground color based on the background."""
|
||||
black = {"R": 0, "G": 0, "B": 0}
|
||||
white = {"R": 255, "G": 255, "B": 255}
|
||||
|
||||
return black if calculate_brightness(background_color) > 210 else white
|
||||
|
||||
def background(text: str) -> dict:
|
||||
"""Calculates the background color based on the text."""
|
||||
hash_value = md5(text.encode("utf-8")).hexdigest()
|
||||
hash_value_values = (hash_value[:8], hash_value[8:16], hash_value[16:24])
|
||||
background_color = tuple(int(value, 16) % 256 for value in hash_value_values)
|
||||
|
||||
return {"R": background_color[0], "G": background_color[1], "B": background_color[2]}
|
||||
|
||||
@register.inclusion_tag("templatetags/avatar.html")
|
||||
def avatar(first_name: str = "", last_name: str = "", initials: str = "", width: str = "md", button: bool = False) -> dict:
|
||||
if initials:
|
||||
display_name = initials
|
||||
full_name = initials
|
||||
else:
|
||||
display_name = f"{first_name[0]}{last_name[0]}"
|
||||
full_name = f"{first_name} {last_name}"
|
||||
|
||||
avatar_background = background(full_name)
|
||||
avatar_foreground = foreground(avatar_background)
|
||||
|
||||
return {
|
||||
"name": display_name,
|
||||
"width": width,
|
||||
"button": button,
|
||||
"background": "#%02x%02x%02x" % (avatar_background["R"], avatar_background["G"], avatar_background["B"]), # noqa: UP031
|
||||
"foreground": "#%02x%02x%02x" % (avatar_foreground["R"], avatar_foreground["G"], avatar_foreground["B"]), # noqa: UP031
|
||||
}
|
||||
43
theme/templatetags/form_field.py
Normal file
43
theme/templatetags/form_field.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from django import template
|
||||
from django.forms import BoundField
|
||||
from typing import Optional
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.inclusion_tag("templatetags/field.html")
|
||||
def form_field(field: BoundField, label: Optional[str] = None, help_text: Optional[str] = None, show_label: bool = True, show_help_text: bool = True, show_placeholder: bool = True, show_as_toggle: bool = False, size: str = "full") -> dict:
|
||||
if label is not None:
|
||||
field.label = label
|
||||
|
||||
if help_text is not None:
|
||||
field.help_text = help_text
|
||||
|
||||
field_type = None
|
||||
match field.widget_type:
|
||||
case "select" | "nullbooleanselect" | "radioselect" | "selectmultiple":
|
||||
field_type = "select"
|
||||
|
||||
case "checkbox":
|
||||
field_type = "checkbox"
|
||||
|
||||
case "textarea" | "markdownx":
|
||||
field_type = "textarea"
|
||||
|
||||
case "clearablefile":
|
||||
field_type = "file"
|
||||
|
||||
case _:
|
||||
field_type = "input"
|
||||
|
||||
size_modifier = None
|
||||
match size:
|
||||
case "extra-small":
|
||||
size_modifier = "xs"
|
||||
|
||||
case "small":
|
||||
size_modifier = "sm"
|
||||
|
||||
case _:
|
||||
pass
|
||||
|
||||
return {"field": field, "field_type": field_type, "size_modifier": size_modifier, "show_label": show_label, "show_help_text": show_help_text, "show_placeholder": show_placeholder, "show_as_toggle": show_as_toggle}
|
||||
17
theme/templatetags/pagination.py
Normal file
17
theme/templatetags/pagination.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from typing import Optional
|
||||
|
||||
from django import template
|
||||
from django.http import HttpRequest
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag
|
||||
def url_replace(request: HttpRequest, field: str, value: str | int, default_field: Optional[str] = None, default_value: Optional[str | int] = None) -> str:
|
||||
"""Updates the given field in the GET parameters with the supplied field. If it does not exist, the field is added."""
|
||||
dict_ = request.GET.copy()
|
||||
dict_[field] = value
|
||||
|
||||
if default_field is not None and default_field not in dict_.keys():
|
||||
dict_[default_field] = default_value
|
||||
|
||||
return dict_.urlencode()
|
||||
Reference in New Issue
Block a user