2024-09-08 19:03:37 +00:00
|
|
|
from random import choices as random_choices
|
|
|
|
from string import ascii_lowercase
|
|
|
|
from typing import Any, Callable
|
|
|
|
|
2024-11-09 21:28:52 +00:00
|
|
|
from django.template import TemplateDoesNotExist
|
2024-09-08 19:03:37 +00:00
|
|
|
from django.template.loader import render_to_string
|
|
|
|
from django.urls import NoReverseMatch, reverse
|
2024-09-14 09:07:38 +00:00
|
|
|
from django.utils.safestring import SafeText, mark_safe
|
|
|
|
|
|
|
|
from common.utils import truncate
|
2024-09-08 19:03:37 +00:00
|
|
|
|
|
|
|
HTMLAttribute = tuple[str, str | int | bool]
|
|
|
|
HTMLTag = str
|
|
|
|
|
|
|
|
|
|
|
|
def Component(
|
|
|
|
attributes: list[HTMLAttribute] = [],
|
|
|
|
children: list[HTMLTag] | HTMLTag = [],
|
|
|
|
template: str = "",
|
|
|
|
tag_name: str = "",
|
|
|
|
) -> HTMLTag:
|
|
|
|
if not tag_name and not template:
|
|
|
|
raise ValueError("One of template or tag_name is required.")
|
|
|
|
if isinstance(children, str):
|
|
|
|
children = [children]
|
|
|
|
childrenBlob = "\n".join(children)
|
2024-09-14 08:40:03 +00:00
|
|
|
if len(attributes) == 0:
|
|
|
|
attributesBlob = ""
|
|
|
|
else:
|
|
|
|
attributesList = [f'{name}="{value}"' for name, value in attributes]
|
|
|
|
# make attribute list into a string
|
|
|
|
# and insert space between tag and attribute list
|
|
|
|
attributesBlob = f" {" ".join(attributesList)}"
|
2024-09-08 19:03:37 +00:00
|
|
|
tag: str = ""
|
|
|
|
if tag_name != "":
|
2024-09-14 08:40:03 +00:00
|
|
|
tag = f"<{tag_name}{attributesBlob}>{childrenBlob}</{tag_name}>"
|
2024-09-08 19:03:37 +00:00
|
|
|
elif template != "":
|
|
|
|
tag = render_to_string(
|
|
|
|
template,
|
|
|
|
{name: value for name, value in attributes}
|
|
|
|
| {"slot": mark_safe("\n".join(children))},
|
|
|
|
)
|
|
|
|
return mark_safe(tag)
|
|
|
|
|
|
|
|
|
|
|
|
def randomid(seed: str = "", length: int = 10) -> str:
|
|
|
|
return seed + "".join(random_choices(ascii_lowercase, k=length))
|
|
|
|
|
|
|
|
|
|
|
|
def Popover(
|
|
|
|
popover_content: str,
|
|
|
|
wrapped_content: str = "",
|
|
|
|
children: list[HTMLTag] = [],
|
|
|
|
attributes: list[HTMLAttribute] = [],
|
|
|
|
) -> str:
|
|
|
|
if not wrapped_content and not children:
|
|
|
|
raise ValueError("One of wrapped_content or children is required.")
|
|
|
|
id = randomid()
|
|
|
|
return Component(
|
|
|
|
attributes=attributes
|
|
|
|
+ [
|
|
|
|
("id", id),
|
|
|
|
("wrapped_content", wrapped_content),
|
|
|
|
("popover_content", popover_content),
|
|
|
|
],
|
|
|
|
children=children,
|
|
|
|
template="cotton/popover.html",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2024-09-14 09:07:38 +00:00
|
|
|
def PopoverTruncated(input_string: str) -> str:
|
|
|
|
if (truncated := truncate(input_string)) != input_string:
|
|
|
|
return Popover(wrapped_content=truncated, popover_content=input_string)
|
|
|
|
else:
|
|
|
|
return input_string
|
|
|
|
|
|
|
|
|
2024-09-08 19:03:37 +00:00
|
|
|
def A(
|
|
|
|
attributes: list[HTMLAttribute] = [],
|
|
|
|
children: list[HTMLTag] | HTMLTag = [],
|
|
|
|
url: str | Callable[..., Any] = "",
|
|
|
|
):
|
|
|
|
"""
|
|
|
|
Returns the HTML tag "a".
|
|
|
|
"url" can either be:
|
|
|
|
- URL (string)
|
|
|
|
- path name passed to reverse() (string)
|
|
|
|
- function
|
|
|
|
"""
|
|
|
|
additional_attributes = []
|
|
|
|
if url:
|
|
|
|
if type(url) is str:
|
|
|
|
try:
|
|
|
|
url_result = reverse(url)
|
|
|
|
except NoReverseMatch:
|
|
|
|
url_result = url
|
|
|
|
elif callable(url):
|
|
|
|
url_result = url()
|
|
|
|
else:
|
|
|
|
raise TypeError("'url' is neither str nor function.")
|
|
|
|
additional_attributes = [("href", url_result)]
|
|
|
|
return Component(
|
|
|
|
tag_name="a", attributes=attributes + additional_attributes, children=children
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def Button(
|
|
|
|
attributes: list[HTMLAttribute] = [],
|
|
|
|
children: list[HTMLTag] | HTMLTag = [],
|
|
|
|
size: str = "base",
|
|
|
|
icon: bool = False,
|
|
|
|
color: str = "blue",
|
|
|
|
):
|
|
|
|
return Component(
|
|
|
|
template="cotton/button.html",
|
|
|
|
attributes=attributes + [("size", size), ("icon", icon), ("color", color)],
|
|
|
|
children=children,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def Div(
|
|
|
|
attributes: list[HTMLAttribute] = [],
|
|
|
|
children: list[HTMLTag] | HTMLTag = [],
|
|
|
|
):
|
|
|
|
return Component(tag_name="div", attributes=attributes, children=children)
|
|
|
|
|
|
|
|
|
2024-11-11 22:29:07 +00:00
|
|
|
def Label(
|
|
|
|
attributes: list[HTMLAttribute] = [],
|
|
|
|
children: list[HTMLTag] | HTMLTag = [],
|
|
|
|
):
|
|
|
|
return Component(tag_name="label", attributes=attributes, children=children)
|
|
|
|
|
|
|
|
|
2024-11-09 21:28:52 +00:00
|
|
|
def Input(
|
|
|
|
type: str = "text",
|
2024-11-11 22:29:07 +00:00
|
|
|
label: str = "",
|
|
|
|
id: str = "",
|
2024-11-09 21:28:52 +00:00
|
|
|
attributes: list[HTMLAttribute] = [],
|
|
|
|
children: list[HTMLTag] | HTMLTag = [],
|
|
|
|
):
|
2024-11-11 22:29:07 +00:00
|
|
|
input_component = Component(
|
|
|
|
tag_name="input",
|
|
|
|
attributes=attributes + [("type", type), ("id", id)],
|
|
|
|
children=children,
|
2024-11-09 21:28:52 +00:00
|
|
|
)
|
2024-11-11 22:29:07 +00:00
|
|
|
if label != "":
|
|
|
|
if id == "":
|
|
|
|
raise ValueError("Label is set but element ID is missing.")
|
|
|
|
return Label(
|
|
|
|
attributes=[("for", id)], children=[label, input_component, *children]
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
return input_component
|
2024-11-09 21:28:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
def Form(
|
|
|
|
action="",
|
|
|
|
method="get",
|
|
|
|
attributes: list[HTMLAttribute] = [],
|
|
|
|
children: list[HTMLTag] | HTMLTag = [],
|
|
|
|
):
|
|
|
|
return Component(
|
|
|
|
tag_name="form",
|
|
|
|
attributes=attributes + [("action", action), ("method", method)],
|
|
|
|
children=children,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2024-11-11 22:29:07 +00:00
|
|
|
def Fieldset(
|
|
|
|
label: str = "",
|
|
|
|
attributes: list[HTMLAttribute] = [],
|
|
|
|
children: list[HTMLTag] | HTMLTag = [],
|
|
|
|
):
|
|
|
|
if label != "":
|
|
|
|
children = [Label(children=[label, *children])]
|
|
|
|
return Component(tag_name="fieldset", attributes=attributes, children=children)
|
|
|
|
|
|
|
|
|
|
|
|
def RadioFieldset(name: str, label: str, radio_buttons: list[dict[str, str]]):
|
|
|
|
return Component(
|
|
|
|
tag_name="span",
|
|
|
|
children=[
|
|
|
|
Component(tag_name="legend", children=label),
|
|
|
|
Component(
|
|
|
|
tag_name="fieldset",
|
|
|
|
children=[
|
|
|
|
Component(
|
|
|
|
tag_name="label",
|
|
|
|
attributes=[
|
|
|
|
("for", f"{name}__{radio["value"]}"),
|
|
|
|
],
|
|
|
|
children=[
|
|
|
|
radio["label"],
|
|
|
|
Input(
|
|
|
|
type="radio",
|
|
|
|
attributes=[
|
|
|
|
("id", f"{name}__{radio["value"]}"),
|
|
|
|
("name", name),
|
|
|
|
("value", radio["value"]),
|
|
|
|
("onClick", radio.get("onclick", "")),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
for radio in radio_buttons
|
|
|
|
],
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def BooleanRadioFieldset(name: str, label: str):
|
|
|
|
return RadioFieldset(
|
|
|
|
name=name,
|
|
|
|
label=label,
|
|
|
|
radio_buttons=[
|
|
|
|
{"label": "True", "value": "true"},
|
|
|
|
{"label": "False", "value": "false"},
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def SubmitButton(label: str):
|
|
|
|
return Input(type="submit", attributes=[("value", label)])
|
|
|
|
|
|
|
|
|
|
|
|
# RadioFieldset(
|
|
|
|
# name="filter__dropped",
|
|
|
|
# label="Dropped",
|
|
|
|
# radio_buttons=[
|
|
|
|
# {"label": "True", "value": "true"},
|
|
|
|
# {"label": "False", "value": "false"},
|
|
|
|
# ],
|
|
|
|
# )
|
|
|
|
|
|
|
|
|
2024-09-08 19:03:37 +00:00
|
|
|
def Icon(
|
|
|
|
name: str,
|
|
|
|
attributes: list[HTMLAttribute] = [],
|
|
|
|
):
|
2024-11-09 21:28:52 +00:00
|
|
|
try:
|
|
|
|
result = Component(template=f"cotton/icon/{name}.html", attributes=attributes)
|
|
|
|
except TemplateDoesNotExist:
|
|
|
|
result = Icon(name="unspecified", attributes=attributes)
|
|
|
|
return result
|
2024-09-14 09:07:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
def LinkedNameWithPlatformIcon(name: str, game_id: int, platform: str) -> SafeText:
|
|
|
|
link = reverse("view_game", args=[int(game_id)])
|
|
|
|
a_content = Div(
|
|
|
|
[("class", "inline-flex gap-2 items-center")],
|
|
|
|
[
|
|
|
|
Icon(
|
|
|
|
platform.icon,
|
|
|
|
[("title", platform.name)],
|
|
|
|
),
|
|
|
|
PopoverTruncated(name),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
return mark_safe(
|
|
|
|
A(
|
|
|
|
url=link,
|
|
|
|
children=[a_content],
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def NameWithPlatformIcon(name: str, platform: str) -> SafeText:
|
|
|
|
content = Div(
|
|
|
|
[("class", "inline-flex gap-2 items-center")],
|
|
|
|
[
|
|
|
|
Icon(
|
|
|
|
platform.icon,
|
|
|
|
[("title", platform.name)],
|
|
|
|
),
|
|
|
|
PopoverTruncated(name),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
return mark_safe(content)
|