Use adhoc Component() less
This commit is contained in:
@@ -4,8 +4,6 @@ Split into core / primitives / domain / filters submodules; this package
|
|||||||
re-exports the public API so ``from common.components import X`` keeps working.
|
re-exports the public API so ``from common.components import X`` keeps working.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from common.utils import truncate
|
|
||||||
|
|
||||||
from common.components.core import (
|
from common.components.core import (
|
||||||
Component,
|
Component,
|
||||||
HTMLAttribute,
|
HTMLAttribute,
|
||||||
@@ -13,41 +11,6 @@ from common.components.core import (
|
|||||||
_render_element,
|
_render_element,
|
||||||
randomid,
|
randomid,
|
||||||
)
|
)
|
||||||
from common.components.primitives import (
|
|
||||||
A,
|
|
||||||
AddForm,
|
|
||||||
Button,
|
|
||||||
ButtonGroup,
|
|
||||||
CsrfInput,
|
|
||||||
Div,
|
|
||||||
ExternalScript,
|
|
||||||
H1,
|
|
||||||
Icon,
|
|
||||||
Input,
|
|
||||||
Modal,
|
|
||||||
ModuleScript,
|
|
||||||
Pill,
|
|
||||||
Popover,
|
|
||||||
PopoverTruncated,
|
|
||||||
SearchField,
|
|
||||||
SimpleTable,
|
|
||||||
Span,
|
|
||||||
Label,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
TableTd,
|
|
||||||
Template,
|
|
||||||
YearPicker,
|
|
||||||
paginated_table_content,
|
|
||||||
)
|
|
||||||
from common.components.search_select import (
|
|
||||||
DEFAULT_PREFETCH,
|
|
||||||
FilterSelect,
|
|
||||||
LabeledOption,
|
|
||||||
SearchSelect,
|
|
||||||
SearchSelectOption,
|
|
||||||
searchselect_selected,
|
|
||||||
)
|
|
||||||
from common.components.domain import (
|
from common.components.domain import (
|
||||||
GameLink,
|
GameLink,
|
||||||
GameStatus,
|
GameStatus,
|
||||||
@@ -60,13 +23,53 @@ from common.components.domain import (
|
|||||||
_resolve_name_with_icon,
|
_resolve_name_with_icon,
|
||||||
)
|
)
|
||||||
from common.components.filters import (
|
from common.components.filters import (
|
||||||
FilterBar,
|
|
||||||
PurchaseFilterBar,
|
|
||||||
SessionFilterBar,
|
|
||||||
DeviceFilterBar,
|
DeviceFilterBar,
|
||||||
|
FilterBar,
|
||||||
PlatformFilterBar,
|
PlatformFilterBar,
|
||||||
PlayEventFilterBar,
|
PlayEventFilterBar,
|
||||||
|
PurchaseFilterBar,
|
||||||
|
SessionFilterBar,
|
||||||
)
|
)
|
||||||
|
from common.components.primitives import (
|
||||||
|
H1,
|
||||||
|
A,
|
||||||
|
AddForm,
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
CsrfInput,
|
||||||
|
Div,
|
||||||
|
ExternalScript,
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
Label,
|
||||||
|
Li,
|
||||||
|
Modal,
|
||||||
|
ModuleScript,
|
||||||
|
Pill,
|
||||||
|
Popover,
|
||||||
|
PopoverTruncated,
|
||||||
|
SearchField,
|
||||||
|
SimpleTable,
|
||||||
|
Span,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
TableTd,
|
||||||
|
Td,
|
||||||
|
Template,
|
||||||
|
Tr,
|
||||||
|
Ul,
|
||||||
|
YearPicker,
|
||||||
|
paginated_table_content,
|
||||||
|
)
|
||||||
|
from common.components.search_select import (
|
||||||
|
DEFAULT_PREFETCH,
|
||||||
|
FilterSelect,
|
||||||
|
LabeledOption,
|
||||||
|
SearchSelect,
|
||||||
|
SearchSelectOption,
|
||||||
|
searchselect_selected,
|
||||||
|
)
|
||||||
|
from common.utils import truncate
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"truncate",
|
"truncate",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from django.template.defaultfilters import floatformat
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.safestring import SafeText, mark_safe
|
from django.utils.safestring import SafeText, mark_safe
|
||||||
|
|
||||||
from common.components.core import Component, HTMLTag
|
from common.components.core import HTMLTag
|
||||||
from common.components.primitives import (
|
from common.components.primitives import (
|
||||||
A,
|
A,
|
||||||
Div,
|
Div,
|
||||||
@@ -33,10 +33,9 @@ def GameLink(
|
|||||||
return Span(
|
return Span(
|
||||||
attributes=[("class", "truncate-container")],
|
attributes=[("class", "truncate-container")],
|
||||||
children=[
|
children=[
|
||||||
Component(
|
A(
|
||||||
tag_name="a",
|
href=link,
|
||||||
attributes=[
|
attributes=[
|
||||||
("href", link),
|
|
||||||
("class", "underline decoration-slate-500 sm:decoration-2"),
|
("class", "underline decoration-slate-500 sm:decoration-2"),
|
||||||
],
|
],
|
||||||
children=display if isinstance(display, list) else [display],
|
children=display if isinstance(display, list) else [display],
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ from django.db import models
|
|||||||
from django.utils.safestring import SafeText, mark_safe
|
from django.utils.safestring import SafeText, mark_safe
|
||||||
|
|
||||||
from common.components.core import Component
|
from common.components.core import Component
|
||||||
from common.components.primitives import Label, Span
|
from common.components.primitives import Div, Input, Label, Span
|
||||||
from common.components.search_select import DEFAULT_PREFETCH, FilterSelect, LabeledOption
|
from common.components.search_select import (
|
||||||
|
DEFAULT_PREFETCH,
|
||||||
|
FilterSelect,
|
||||||
|
LabeledOption,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FilterChoice(NamedTuple):
|
class FilterChoice(NamedTuple):
|
||||||
@@ -206,8 +210,7 @@ def _filter_mins_to_hrs(val) -> str:
|
|||||||
|
|
||||||
def _filter_field(label: str, widget) -> SafeText:
|
def _filter_field(label: str, widget) -> SafeText:
|
||||||
"""A labelled filter field: <div><label>…</label>{widget}</div>."""
|
"""A labelled filter field: <div><label>…</label>{widget}</div>."""
|
||||||
return Component(
|
return Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", "flex flex-col gap-1")],
|
attributes=[("class", "flex flex-col gap-1")],
|
||||||
children=[
|
children=[
|
||||||
Label(
|
Label(
|
||||||
@@ -223,8 +226,7 @@ def _filter_checkbox(name: str, label: str, checked: bool) -> SafeText:
|
|||||||
return Label(
|
return Label(
|
||||||
attributes=[("class", "flex items-center gap-2 text-sm text-heading")],
|
attributes=[("class", "flex items-center gap-2 text-sm text-heading")],
|
||||||
children=[
|
children=[
|
||||||
Component(
|
Input(
|
||||||
tag_name="input",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("type", "checkbox"),
|
("type", "checkbox"),
|
||||||
("name", name),
|
("name", name),
|
||||||
@@ -283,13 +285,11 @@ def RangeSlider(
|
|||||||
point_mode = bool(min_value and max_value and min_value == max_value)
|
point_mode = bool(min_value and max_value and min_value == max_value)
|
||||||
initial_mode = "point" if point_mode else "range"
|
initial_mode = "point" if point_mode else "range"
|
||||||
|
|
||||||
return Component(
|
return Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", "range-slider-block mb-4")],
|
attributes=[("class", "range-slider-block mb-4")],
|
||||||
children=[
|
children=[
|
||||||
# ── Label row ──
|
# ── Label row ──
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", "flex items-center gap-2 mb-1")],
|
attributes=[("class", "flex items-center gap-2 mb-1")],
|
||||||
children=[
|
children=[
|
||||||
Label(
|
Label(
|
||||||
@@ -299,8 +299,7 @@ def RangeSlider(
|
|||||||
],
|
],
|
||||||
children=[label],
|
children=[label],
|
||||||
),
|
),
|
||||||
Component(
|
Input(
|
||||||
tag_name="input",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("type", "number"),
|
("type", "number"),
|
||||||
("name", min_input_id),
|
("name", min_input_id),
|
||||||
@@ -324,8 +323,7 @@ def RangeSlider(
|
|||||||
],
|
],
|
||||||
children=["–"],
|
children=["–"],
|
||||||
),
|
),
|
||||||
Component(
|
Input(
|
||||||
tag_name="input",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("type", "number"),
|
("type", "number"),
|
||||||
("name", max_input_id),
|
("name", max_input_id),
|
||||||
@@ -379,8 +377,7 @@ def RangeSlider(
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
# ── Slider row ──
|
# ── Slider row ──
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("class", "range-slider relative h-10 select-none mt-1"),
|
("class", "range-slider relative h-10 select-none mt-1"),
|
||||||
("data-mode", initial_mode),
|
("data-mode", initial_mode),
|
||||||
@@ -389,8 +386,7 @@ def RangeSlider(
|
|||||||
("data-step", str(step)),
|
("data-step", str(step)),
|
||||||
],
|
],
|
||||||
children=[
|
children=[
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
(
|
(
|
||||||
"class",
|
"class",
|
||||||
@@ -399,8 +395,7 @@ def RangeSlider(
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
(
|
(
|
||||||
"class",
|
"class",
|
||||||
@@ -411,8 +406,7 @@ def RangeSlider(
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Min handle (hidden in point mode via JS)
|
# Min handle (hidden in point mode via JS)
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
(
|
(
|
||||||
"class",
|
"class",
|
||||||
@@ -429,8 +423,7 @@ def RangeSlider(
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Max handle
|
# Max handle
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
(
|
(
|
||||||
"class",
|
"class",
|
||||||
@@ -480,8 +473,7 @@ def _filter_collapse_button() -> SafeText:
|
|||||||
|
|
||||||
|
|
||||||
def _filter_action_row(preset_list_url: str, preset_save_url: str) -> SafeText:
|
def _filter_action_row(preset_list_url: str, preset_save_url: str) -> SafeText:
|
||||||
return Component(
|
return Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", "flex gap-3 items-center")],
|
attributes=[("class", "flex gap-3 items-center")],
|
||||||
children=[
|
children=[
|
||||||
Component(
|
Component(
|
||||||
@@ -521,8 +513,7 @@ def _filter_action_row(preset_list_url: str, preset_save_url: str) -> SafeText:
|
|||||||
("id", "save-preset-area"),
|
("id", "save-preset-area"),
|
||||||
],
|
],
|
||||||
children=[
|
children=[
|
||||||
Component(
|
Input(
|
||||||
tag_name="input",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("type", "text"),
|
("type", "text"),
|
||||||
("id", "preset-name-input"),
|
("id", "preset-name-input"),
|
||||||
@@ -572,8 +563,7 @@ def _filter_action_row(preset_list_url: str, preset_save_url: str) -> SafeText:
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("id", "preset-dropdown"),
|
("id", "preset-dropdown"),
|
||||||
("class", "relative"),
|
("class", "relative"),
|
||||||
@@ -594,13 +584,11 @@ def _filter_bar(fields, filter_json, preset_list_url, preset_save_url) -> SafeTe
|
|||||||
"""Shared collapsible filter-bar chrome. `fields` is the per-entity body
|
"""Shared collapsible filter-bar chrome. `fields` is the per-entity body
|
||||||
(grids, sliders, checkboxes); the shell adds the collapse toggle, the form,
|
(grids, sliders, checkboxes); the shell adds the collapse toggle, the form,
|
||||||
the hidden filter-json input and the Apply/Clear/preset action row."""
|
the hidden filter-json input and the Apply/Clear/preset action row."""
|
||||||
return Component(
|
return Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("id", "filter-bar"), ("class", "mb-6")],
|
attributes=[("id", "filter-bar"), ("class", "mb-6")],
|
||||||
children=[
|
children=[
|
||||||
_filter_collapse_button(),
|
_filter_collapse_button(),
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("id", "filter-bar-body"),
|
("id", "filter-bar-body"),
|
||||||
(
|
(
|
||||||
@@ -617,8 +605,7 @@ def _filter_bar(fields, filter_json, preset_list_url, preset_save_url) -> SafeTe
|
|||||||
("onsubmit", "return applyFilterBar(event)"),
|
("onsubmit", "return applyFilterBar(event)"),
|
||||||
],
|
],
|
||||||
children=[
|
children=[
|
||||||
Component(
|
Input(
|
||||||
tag_name="input",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("type", "hidden"),
|
("type", "hidden"),
|
||||||
("id", _FILTER_INPUT_ID),
|
("id", _FILTER_INPUT_ID),
|
||||||
@@ -725,8 +712,7 @@ def FilterBar(
|
|||||||
price_range_max = max(int(price_aggregate.get("price_max") or 100), 1)
|
price_range_max = max(int(price_aggregate.get("price_max") or 100), 1)
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", _FILTER_GRID_CLASS)],
|
attributes=[("class", _FILTER_GRID_CLASS)],
|
||||||
children=[
|
children=[
|
||||||
_filter_field(
|
_filter_field(
|
||||||
@@ -817,8 +803,7 @@ def FilterBar(
|
|||||||
min_placeholder="e.g. 1985",
|
min_placeholder="e.g. 1985",
|
||||||
max_placeholder="e.g. 2010",
|
max_placeholder="e.g. 2010",
|
||||||
),
|
),
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", "flex items-end gap-4 mb-4 flex-wrap")],
|
attributes=[("class", "flex items-end gap-4 mb-4 flex-wrap")],
|
||||||
children=[
|
children=[
|
||||||
_filter_checkbox("filter-mastered", "Mastered", mastered_value),
|
_filter_checkbox("filter-mastered", "Mastered", mastered_value),
|
||||||
@@ -970,8 +955,7 @@ def SessionFilterBar(
|
|||||||
duration_range_max = 200
|
duration_range_max = 200
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", _FILTER_GRID_CLASS)],
|
attributes=[("class", _FILTER_GRID_CLASS)],
|
||||||
children=[
|
children=[
|
||||||
_filter_field(
|
_filter_field(
|
||||||
@@ -1027,8 +1011,7 @@ def SessionFilterBar(
|
|||||||
min_placeholder="e.g. 30",
|
min_placeholder="e.g. 30",
|
||||||
max_placeholder="e.g. 180",
|
max_placeholder="e.g. 180",
|
||||||
),
|
),
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", "flex gap-4 mb-4")],
|
attributes=[("class", "flex gap-4 mb-4")],
|
||||||
children=[
|
children=[
|
||||||
_filter_checkbox("filter-emulated", "Emulated", emulated_value),
|
_filter_checkbox("filter-emulated", "Emulated", emulated_value),
|
||||||
@@ -1079,8 +1062,7 @@ def PurchaseFilterBar(
|
|||||||
num_range_min, num_range_max = 0, 10
|
num_range_min, num_range_max = 0, 10
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", _FILTER_GRID_CLASS)],
|
attributes=[("class", _FILTER_GRID_CLASS)],
|
||||||
children=[
|
children=[
|
||||||
_filter_field(
|
_filter_field(
|
||||||
@@ -1127,42 +1109,48 @@ def PurchaseFilterBar(
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", "flex items-end gap-4 mb-4")],
|
attributes=[("class", "flex items-end gap-4 mb-4")],
|
||||||
children=[
|
children=[
|
||||||
_filter_checkbox("filter-refunded", "Refunded", is_refunded_value),
|
_filter_checkbox("filter-refunded", "Refunded", is_refunded_value),
|
||||||
_filter_checkbox("filter-infinite", "Infinite", infinite_value),
|
_filter_checkbox("filter-infinite", "Infinite", infinite_value),
|
||||||
_filter_checkbox("filter-needs-price-update", "Needs Price Update", needs_price_update_value),
|
_filter_checkbox(
|
||||||
|
"filter-needs-price-update",
|
||||||
|
"Needs Price Update",
|
||||||
|
needs_price_update_value,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", _FILTER_GRID_CLASS)],
|
attributes=[("class", _FILTER_GRID_CLASS)],
|
||||||
children=[
|
children=[
|
||||||
_filter_field(
|
_filter_field(
|
||||||
"Original Currency",
|
"Original Currency",
|
||||||
Component(
|
Input(
|
||||||
tag_name="input",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("type", "text"),
|
("type", "text"),
|
||||||
("name", "filter-price_currency"),
|
("name", "filter-price_currency"),
|
||||||
("value", price_currency_value),
|
("value", price_currency_value),
|
||||||
("placeholder", "e.g. USD, EUR"),
|
("placeholder", "e.g. USD, EUR"),
|
||||||
("class", "w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body"),
|
(
|
||||||
|
"class",
|
||||||
|
"w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_filter_field(
|
_filter_field(
|
||||||
"Converted Currency",
|
"Converted Currency",
|
||||||
Component(
|
Input(
|
||||||
tag_name="input",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("type", "text"),
|
("type", "text"),
|
||||||
("name", "filter-converted_currency"),
|
("name", "filter-converted_currency"),
|
||||||
("value", converted_currency_value),
|
("value", converted_currency_value),
|
||||||
("placeholder", "e.g. USD, EUR"),
|
("placeholder", "e.g. USD, EUR"),
|
||||||
("class", "w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body"),
|
(
|
||||||
|
"class",
|
||||||
|
"w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1193,9 +1181,7 @@ def PurchaseFilterBar(
|
|||||||
return _filter_bar(fields, filter_json, preset_list_url, preset_save_url)
|
return _filter_bar(fields, filter_json, preset_list_url, preset_save_url)
|
||||||
|
|
||||||
|
|
||||||
def DeviceFilterBar(
|
def DeviceFilterBar(filter_json="", preset_list_url="", preset_save_url="") -> SafeText:
|
||||||
filter_json="", preset_list_url="", preset_save_url=""
|
|
||||||
) -> SafeText:
|
|
||||||
"""Collapsible filter bar for the Device list."""
|
"""Collapsible filter bar for the Device list."""
|
||||||
from games.models import Device
|
from games.models import Device
|
||||||
|
|
||||||
@@ -1204,8 +1190,7 @@ def DeviceFilterBar(
|
|||||||
type_choice = _filter_get_choice(existing, "type")
|
type_choice = _filter_get_choice(existing, "type")
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", _FILTER_GRID_CLASS)],
|
attributes=[("class", _FILTER_GRID_CLASS)],
|
||||||
children=[
|
children=[
|
||||||
_filter_field(
|
_filter_field(
|
||||||
@@ -1233,33 +1218,36 @@ def PlatformFilterBar(
|
|||||||
group_value = existing.get("group", {}).get("value", "")
|
group_value = existing.get("group", {}).get("value", "")
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", _FILTER_GRID_CLASS)],
|
attributes=[("class", _FILTER_GRID_CLASS)],
|
||||||
children=[
|
children=[
|
||||||
_filter_field(
|
_filter_field(
|
||||||
"Platform Name",
|
"Platform Name",
|
||||||
Component(
|
Input(
|
||||||
tag_name="input",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("type", "text"),
|
("type", "text"),
|
||||||
("name", "filter-name"),
|
("name", "filter-name"),
|
||||||
("value", name_value),
|
("value", name_value),
|
||||||
("placeholder", "e.g. Nintendo Switch"),
|
("placeholder", "e.g. Nintendo Switch"),
|
||||||
("class", "w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body"),
|
(
|
||||||
|
"class",
|
||||||
|
"w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_filter_field(
|
_filter_field(
|
||||||
"Platform Group",
|
"Platform Group",
|
||||||
Component(
|
Input(
|
||||||
tag_name="input",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("type", "text"),
|
("type", "text"),
|
||||||
("name", "filter-group"),
|
("name", "filter-group"),
|
||||||
("value", group_value),
|
("value", group_value),
|
||||||
("placeholder", "e.g. Nintendo"),
|
("placeholder", "e.g. Nintendo"),
|
||||||
("class", "w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body"),
|
(
|
||||||
|
"class",
|
||||||
|
"w-full rounded border-default-medium p-2 bg-neutral-secondary-medium text-body",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1278,8 +1266,7 @@ def PlayEventFilterBar(
|
|||||||
days_min, days_max = _parse_range(existing, "days_to_finish")
|
days_min, days_max = _parse_range(existing, "days_to_finish")
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", _FILTER_GRID_CLASS)],
|
attributes=[("class", _FILTER_GRID_CLASS)],
|
||||||
children=[
|
children=[
|
||||||
_filter_field(
|
_filter_field(
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ from django.urls import reverse
|
|||||||
from django.utils.html import conditional_escape
|
from django.utils.html import conditional_escape
|
||||||
from django.utils.safestring import SafeText, mark_safe
|
from django.utils.safestring import SafeText, mark_safe
|
||||||
|
|
||||||
|
from common.components.core import Component, HTMLAttribute, HTMLTag, randomid
|
||||||
from common.icons import get_icon
|
from common.icons import get_icon
|
||||||
from common.utils import truncate
|
from common.utils import truncate
|
||||||
from common.components.core import Component, HTMLAttribute, HTMLTag, randomid
|
|
||||||
|
|
||||||
|
|
||||||
_COLOR_CLASSES = {
|
_COLOR_CLASSES = {
|
||||||
"blue": "text-white bg-brand box-border border border-transparent hover:bg-brand-strong focus:ring-4 focus:ring-brand-medium",
|
"blue": "text-white bg-brand box-border border border-transparent hover:bg-brand-strong focus:ring-4 focus:ring-brand-medium",
|
||||||
@@ -57,8 +56,7 @@ def _popover_html(
|
|||||||
"dark:bg-purple-800"
|
"dark:bg-purple-800"
|
||||||
)
|
)
|
||||||
|
|
||||||
div = Component(
|
div = Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("data-popover", ""),
|
("data-popover", ""),
|
||||||
("id", id),
|
("id", id),
|
||||||
@@ -66,12 +64,11 @@ def _popover_html(
|
|||||||
("class", popover_tooltip_class),
|
("class", popover_tooltip_class),
|
||||||
],
|
],
|
||||||
children=[
|
children=[
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", "px-3 py-2")],
|
attributes=[("class", "px-3 py-2")],
|
||||||
children=[popover_content],
|
children=[popover_content],
|
||||||
),
|
),
|
||||||
Component(tag_name="div", attributes=[("data-popper-arrow", "")]),
|
Div(attributes=[("data-popper-arrow", "")]),
|
||||||
mark_safe( # nosec — intentional HTML comment for Tailwind JIT
|
mark_safe( # nosec — intentional HTML comment for Tailwind JIT
|
||||||
"<!-- for Tailwind CSS to generate decoration-dotted CSS "
|
"<!-- for Tailwind CSS to generate decoration-dotted CSS "
|
||||||
"from Python component -->"
|
"from Python component -->"
|
||||||
@@ -323,8 +320,7 @@ def ButtonGroup(buttons: list[dict] | None = None) -> SafeText:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return Component(
|
return Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", "inline-flex rounded-md shadow-xs"), ("role", "group")],
|
attributes=[("class", "inline-flex rounded-md shadow-xs"), ("role", "group")],
|
||||||
children=children,
|
children=children,
|
||||||
)
|
)
|
||||||
@@ -339,6 +335,42 @@ def Div(
|
|||||||
return Component(tag_name="div", attributes=attributes, children=children)
|
return Component(tag_name="div", attributes=attributes, children=children)
|
||||||
|
|
||||||
|
|
||||||
|
def P(
|
||||||
|
attributes: list[HTMLAttribute] | None = None,
|
||||||
|
children: list[HTMLTag] | HTMLTag | None = None,
|
||||||
|
) -> SafeText:
|
||||||
|
attributes = attributes or []
|
||||||
|
children = children or []
|
||||||
|
return Component(tag_name="p", attributes=attributes, children=children)
|
||||||
|
|
||||||
|
|
||||||
|
def Ul(
|
||||||
|
attributes: list[HTMLAttribute] | None = None,
|
||||||
|
children: list[HTMLTag] | HTMLTag | None = None,
|
||||||
|
) -> SafeText:
|
||||||
|
attributes = attributes or []
|
||||||
|
children = children or []
|
||||||
|
return Component(tag_name="ul", attributes=attributes, children=children)
|
||||||
|
|
||||||
|
|
||||||
|
def Li(
|
||||||
|
attributes: list[HTMLAttribute] | None = None,
|
||||||
|
children: list[HTMLTag] | HTMLTag | None = None,
|
||||||
|
) -> SafeText:
|
||||||
|
attributes = attributes or []
|
||||||
|
children = children or []
|
||||||
|
return Component(tag_name="li", attributes=attributes, children=children)
|
||||||
|
|
||||||
|
|
||||||
|
def Strong(
|
||||||
|
attributes: list[HTMLAttribute] | None = None,
|
||||||
|
children: list[HTMLTag] | HTMLTag | None = None,
|
||||||
|
) -> SafeText:
|
||||||
|
attributes = attributes or []
|
||||||
|
children = children or []
|
||||||
|
return Component(tag_name="strong", attributes=attributes, children=children)
|
||||||
|
|
||||||
|
|
||||||
def Input(
|
def Input(
|
||||||
type: str = "text",
|
type: str = "text",
|
||||||
attributes: list[HTMLAttribute] | None = None,
|
attributes: list[HTMLAttribute] | None = None,
|
||||||
@@ -600,8 +632,7 @@ def SearchField(
|
|||||||
],
|
],
|
||||||
children=["Search"],
|
children=["Search"],
|
||||||
),
|
),
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[("class", "relative")],
|
attributes=[("class", "relative")],
|
||||||
children=[
|
children=[
|
||||||
mark_safe(
|
mark_safe(
|
||||||
@@ -612,10 +643,9 @@ def SearchField(
|
|||||||
'd="m21 21-3.5-3.5M17 10a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"/>'
|
'd="m21 21-3.5-3.5M17 10a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"/>'
|
||||||
"</svg></div>"
|
"</svg></div>"
|
||||||
),
|
),
|
||||||
Component(
|
Input(
|
||||||
tag_name="input",
|
type="search",
|
||||||
attributes=[
|
attributes=[
|
||||||
("type", "search"),
|
|
||||||
("id", id),
|
("id", id),
|
||||||
("name", id),
|
("name", id),
|
||||||
("value", search_string),
|
("value", search_string),
|
||||||
@@ -687,8 +717,7 @@ def Modal(
|
|||||||
) -> SafeText:
|
) -> SafeText:
|
||||||
"""Modal overlay with container. Content (form, buttons) goes in children."""
|
"""Modal overlay with container. Content (form, buttons) goes in children."""
|
||||||
children = children or []
|
children = children or []
|
||||||
outer = Component(
|
outer = Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("id", modal_id),
|
("id", modal_id),
|
||||||
(
|
(
|
||||||
@@ -698,8 +727,7 @@ def Modal(
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
children=[
|
children=[
|
||||||
Component(
|
Div(
|
||||||
tag_name="div",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
(
|
(
|
||||||
"class",
|
"class",
|
||||||
@@ -714,13 +742,39 @@ def Modal(
|
|||||||
return mark_safe(str(outer))
|
return mark_safe(str(outer))
|
||||||
|
|
||||||
|
|
||||||
|
def Td(
|
||||||
|
attributes: list[HTMLAttribute] | None = None,
|
||||||
|
children: list[HTMLTag] | HTMLTag | None = None,
|
||||||
|
) -> SafeText:
|
||||||
|
attributes = attributes or []
|
||||||
|
children = children or []
|
||||||
|
return Component(tag_name="td", attributes=attributes, children=children)
|
||||||
|
|
||||||
|
|
||||||
|
def Tr(
|
||||||
|
attributes: list[HTMLAttribute] | None = None,
|
||||||
|
children: list[HTMLTag] | HTMLTag | None = None,
|
||||||
|
) -> SafeText:
|
||||||
|
attributes = attributes or []
|
||||||
|
children = children or []
|
||||||
|
return Component(tag_name="tr", attributes=attributes, children=children)
|
||||||
|
|
||||||
|
|
||||||
|
def Th(
|
||||||
|
attributes: list[HTMLAttribute] | None = None,
|
||||||
|
children: list[HTMLTag] | HTMLTag | None = None,
|
||||||
|
) -> SafeText:
|
||||||
|
attributes = attributes or []
|
||||||
|
children = children or []
|
||||||
|
return Component(tag_name="th", attributes=attributes, children=children)
|
||||||
|
|
||||||
|
|
||||||
def TableTd(
|
def TableTd(
|
||||||
children: list[HTMLTag] | HTMLTag | None = None,
|
children: list[HTMLTag] | HTMLTag | None = None,
|
||||||
) -> SafeText:
|
) -> SafeText:
|
||||||
"""Styled table cell."""
|
"""Styled table cell."""
|
||||||
children = children or []
|
children = children or []
|
||||||
return Component(
|
return Td(
|
||||||
tag_name="td",
|
|
||||||
attributes=[("class", "px-6 py-4 min-w-20-char max-w-20-char")],
|
attributes=[("class", "px-6 py-4 min-w-20-char max-w-20-char")],
|
||||||
children=children if isinstance(children, list) else [children],
|
children=children if isinstance(children, list) else [children],
|
||||||
)
|
)
|
||||||
@@ -765,8 +819,7 @@ def TableRow(data: dict | list | None = None) -> SafeText:
|
|||||||
for i, cell in enumerate(cells):
|
for i, cell in enumerate(cells):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
cell_elements.append(
|
cell_elements.append(
|
||||||
Component(
|
Th(
|
||||||
tag_name="th",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("scope", "row"),
|
("scope", "row"),
|
||||||
(
|
(
|
||||||
@@ -781,7 +834,7 @@ def TableRow(data: dict | list | None = None) -> SafeText:
|
|||||||
else:
|
else:
|
||||||
cell_elements.append(TableTd(children=[cell]))
|
cell_elements.append(TableTd(children=[cell]))
|
||||||
|
|
||||||
return Component(tag_name="tr", attributes=tr_attrs, children=cell_elements)
|
return Tr(attributes=tr_attrs, children=cell_elements)
|
||||||
|
|
||||||
|
|
||||||
def Icon(
|
def Icon(
|
||||||
|
|||||||
+4
-5
@@ -6,6 +6,7 @@ from django.http import HttpResponse
|
|||||||
from django.utils.safestring import SafeText, mark_safe
|
from django.utils.safestring import SafeText, mark_safe
|
||||||
|
|
||||||
from common.components import Component, CsrfInput, Div, Input
|
from common.components import Component, CsrfInput, Div, Input
|
||||||
|
from common.components.primitives import Td, Tr
|
||||||
from common.layout import render_page
|
from common.layout import render_page
|
||||||
|
|
||||||
|
|
||||||
@@ -15,12 +16,10 @@ def _login_content(form, request) -> SafeText:
|
|||||||
children=[
|
children=[
|
||||||
CsrfInput(request),
|
CsrfInput(request),
|
||||||
mark_safe(str(form.as_table())),
|
mark_safe(str(form.as_table())),
|
||||||
Component(
|
Tr(
|
||||||
tag_name="tr",
|
|
||||||
children=[
|
children=[
|
||||||
Component(tag_name="td"),
|
Td(),
|
||||||
Component(
|
Td(
|
||||||
tag_name="td",
|
|
||||||
children=[
|
children=[
|
||||||
Input(type="submit", attributes=[("value", "Login")])
|
Input(type="submit", attributes=[("value", "Login")])
|
||||||
],
|
],
|
||||||
|
|||||||
+30
-53
@@ -2,15 +2,16 @@ from typing import Any
|
|||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.middleware.csrf import get_token
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
||||||
|
from django.middleware.csrf import get_token
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.template.defaultfilters import date as date_filter
|
from django.template.defaultfilters import date as date_filter
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.safestring import SafeText, mark_safe
|
from django.utils.safestring import SafeText, mark_safe
|
||||||
|
|
||||||
from common.components import (
|
from common.components import (
|
||||||
|
H1,
|
||||||
A,
|
A,
|
||||||
AddForm,
|
AddForm,
|
||||||
Button,
|
Button,
|
||||||
@@ -21,9 +22,7 @@ from common.components import (
|
|||||||
FilterBar,
|
FilterBar,
|
||||||
GameStatus,
|
GameStatus,
|
||||||
GameStatusSelector,
|
GameStatusSelector,
|
||||||
H1,
|
|
||||||
Icon,
|
Icon,
|
||||||
SearchField,
|
|
||||||
LinkedPurchase,
|
LinkedPurchase,
|
||||||
Modal,
|
Modal,
|
||||||
ModuleScript,
|
ModuleScript,
|
||||||
@@ -31,9 +30,12 @@ from common.components import (
|
|||||||
Popover,
|
Popover,
|
||||||
PopoverTruncated,
|
PopoverTruncated,
|
||||||
PurchasePrice,
|
PurchasePrice,
|
||||||
|
SearchField,
|
||||||
SimpleTable,
|
SimpleTable,
|
||||||
|
Ul,
|
||||||
paginated_table_content,
|
paginated_table_content,
|
||||||
)
|
)
|
||||||
|
from common.components.primitives import Li, Span, Strong
|
||||||
from common.icons import get_icon
|
from common.icons import get_icon
|
||||||
from common.layout import render_page
|
from common.layout import render_page
|
||||||
from common.time import (
|
from common.time import (
|
||||||
@@ -193,19 +195,13 @@ def _delete_game_confirmation_modal(
|
|||||||
) -> SafeText:
|
) -> SafeText:
|
||||||
data_items = []
|
data_items = []
|
||||||
if session_count:
|
if session_count:
|
||||||
data_items.append(
|
data_items.append(Li(children=[f"{session_count} session(s)"]))
|
||||||
Component(tag_name="li", children=[f"{session_count} session(s)"])
|
|
||||||
)
|
|
||||||
if purchase_count:
|
if purchase_count:
|
||||||
data_items.append(
|
data_items.append(Li(children=[f"{purchase_count} purchase(s)"]))
|
||||||
Component(tag_name="li", children=[f"{purchase_count} purchase(s)"])
|
|
||||||
)
|
|
||||||
if playevent_count:
|
if playevent_count:
|
||||||
data_items.append(
|
data_items.append(Li(children=[f"{playevent_count} play event(s)"]))
|
||||||
Component(tag_name="li", children=[f"{playevent_count} play event(s)"])
|
|
||||||
)
|
|
||||||
if not (session_count or purchase_count or playevent_count):
|
if not (session_count or purchase_count or playevent_count):
|
||||||
data_items.append(Component(tag_name="li", children=["No associated data"]))
|
data_items.append(Li(children=["No associated data"]))
|
||||||
|
|
||||||
form = Component(
|
form = Component(
|
||||||
tag_name="form",
|
tag_name="form",
|
||||||
@@ -218,8 +214,7 @@ def _delete_game_confirmation_modal(
|
|||||||
],
|
],
|
||||||
children=[
|
children=[
|
||||||
CsrfInput(request),
|
CsrfInput(request),
|
||||||
Component(
|
P(
|
||||||
tag_name="p",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
(
|
(
|
||||||
"class",
|
"class",
|
||||||
@@ -231,8 +226,7 @@ def _delete_game_confirmation_modal(
|
|||||||
"This will permanently delete this game and all associated data:"
|
"This will permanently delete this game and all associated data:"
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Component(
|
Ul(
|
||||||
tag_name="ul",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
(
|
(
|
||||||
"class",
|
"class",
|
||||||
@@ -242,8 +236,7 @@ def _delete_game_confirmation_modal(
|
|||||||
],
|
],
|
||||||
children=data_items,
|
children=data_items,
|
||||||
),
|
),
|
||||||
Component(
|
P(
|
||||||
tag_name="p",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
(
|
(
|
||||||
"class",
|
"class",
|
||||||
@@ -279,8 +272,7 @@ def _delete_game_confirmation_modal(
|
|||||||
return Modal(
|
return Modal(
|
||||||
"delete-game-confirmation-modal",
|
"delete-game-confirmation-modal",
|
||||||
children=[
|
children=[
|
||||||
Component(
|
P(
|
||||||
tag_name="h1",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
(
|
(
|
||||||
"class",
|
"class",
|
||||||
@@ -289,12 +281,11 @@ def _delete_game_confirmation_modal(
|
|||||||
],
|
],
|
||||||
children=["Delete Game"],
|
children=["Delete Game"],
|
||||||
),
|
),
|
||||||
Component(
|
P(
|
||||||
tag_name="p",
|
|
||||||
attributes=[("class", "dark:text-white text-center mt-5")],
|
attributes=[("class", "dark:text-white text-center mt-5")],
|
||||||
children=[
|
children=[
|
||||||
"Are you sure you want to delete ",
|
"Are you sure you want to delete ",
|
||||||
Component(tag_name="strong", children=[game.name]),
|
Strong(children=[game.name]),
|
||||||
"?",
|
"?",
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -427,9 +418,7 @@ def _meta_row(
|
|||||||
label: str, value: SafeText | str, extra: SafeText | str = ""
|
label: str, value: SafeText | str, extra: SafeText | str = ""
|
||||||
) -> SafeText:
|
) -> SafeText:
|
||||||
children: list[SafeText | str] = [
|
children: list[SafeText | str] = [
|
||||||
Component(
|
Span(attributes=[("class", "uppercase")], children=[label]),
|
||||||
tag_name="span", attributes=[("class", "uppercase")], children=[label]
|
|
||||||
),
|
|
||||||
value,
|
value,
|
||||||
]
|
]
|
||||||
if extra:
|
if extra:
|
||||||
@@ -452,9 +441,8 @@ def _game_action_buttons(game: Game) -> SafeText:
|
|||||||
"dark:text-white dark:hover:text-white dark:hover:bg-red-700 "
|
"dark:text-white dark:hover:text-white dark:hover:bg-red-700 "
|
||||||
"dark:focus:ring-blue-500 dark:focus:text-white hover:cursor-pointer"
|
"dark:focus:ring-blue-500 dark:focus:text-white hover:cursor-pointer"
|
||||||
)
|
)
|
||||||
edit_link = Component(
|
edit_link = A(
|
||||||
tag_name="a",
|
href=reverse("games:edit_game", args=[game.id]),
|
||||||
attributes=[("href", reverse("games:edit_game", args=[game.id]))],
|
|
||||||
children=[
|
children=[
|
||||||
Component(
|
Component(
|
||||||
tag_name="button",
|
tag_name="button",
|
||||||
@@ -463,10 +451,9 @@ def _game_action_buttons(game: Game) -> SafeText:
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
delete_link = Component(
|
delete_link = A(
|
||||||
tag_name="a",
|
href="#",
|
||||||
attributes=[
|
attributes=[
|
||||||
("href", "#"),
|
|
||||||
("hx-get", reverse("games:delete_game_confirmation", args=[game.id])),
|
("hx-get", reverse("games:delete_game_confirmation", args=[game.id])),
|
||||||
("hx-target", "#global-modal-container"),
|
("hx-target", "#global-modal-container"),
|
||||||
],
|
],
|
||||||
@@ -499,21 +486,16 @@ def _game_history(statuschanges) -> SafeText:
|
|||||||
status=change.new_status,
|
status=change.new_status,
|
||||||
children=[change.get_new_status_display()],
|
children=[change.get_new_status_display()],
|
||||||
)
|
)
|
||||||
edit = Component(
|
edit = A(
|
||||||
tag_name="a",
|
href=reverse("games:edit_statuschange", args=[change.id]),
|
||||||
attributes=[("href", reverse("games:edit_statuschange", args=[change.id]))],
|
|
||||||
children=["Edit"],
|
children=["Edit"],
|
||||||
)
|
)
|
||||||
delete = Component(
|
delete = A(
|
||||||
tag_name="a",
|
href=reverse("games:delete_statuschange", args=[change.id]),
|
||||||
attributes=[
|
|
||||||
("href", reverse("games:delete_statuschange", args=[change.id]))
|
|
||||||
],
|
|
||||||
children=["Delete"],
|
children=["Delete"],
|
||||||
)
|
)
|
||||||
items.append(
|
items.append(
|
||||||
Component(
|
Li(
|
||||||
tag_name="li",
|
|
||||||
attributes=[("class", "text-slate-500")],
|
attributes=[("class", "text-slate-500")],
|
||||||
children=[
|
children=[
|
||||||
f"{prefix} status from ",
|
f"{prefix} status from ",
|
||||||
@@ -528,8 +510,7 @@ def _game_history(statuschanges) -> SafeText:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return Component(
|
return Ul(
|
||||||
tag_name="ul",
|
|
||||||
attributes=[("class", "list-disc list-inside")],
|
attributes=[("class", "list-disc list-inside")],
|
||||||
children=items,
|
children=items,
|
||||||
)
|
)
|
||||||
@@ -576,12 +557,10 @@ def _game_overview_metrics(game: Game) -> dict[str, Any]:
|
|||||||
|
|
||||||
def _game_header(game: Game, request: HttpRequest, metrics: dict[str, Any]) -> SafeText:
|
def _game_header(game: Game, request: HttpRequest, metrics: dict[str, Any]) -> SafeText:
|
||||||
grey_value_class = "text-black dark:text-slate-300"
|
grey_value_class = "text-black dark:text-slate-300"
|
||||||
title_span = Component(
|
title_span = Span(
|
||||||
tag_name="span",
|
|
||||||
attributes=[("class", "text-balance max-w-120 text-4xl")],
|
attributes=[("class", "text-balance max-w-120 text-4xl")],
|
||||||
children=[
|
children=[
|
||||||
Component(
|
Span(
|
||||||
tag_name="span",
|
|
||||||
attributes=[("class", "font-bold font-serif")],
|
attributes=[("class", "font-bold font-serif")],
|
||||||
children=[game.name],
|
children=[game.name],
|
||||||
),
|
),
|
||||||
@@ -634,8 +613,7 @@ def _game_header(game: Game, request: HttpRequest, metrics: dict[str, Any]) -> S
|
|||||||
[
|
[
|
||||||
_meta_row(
|
_meta_row(
|
||||||
"Original year",
|
"Original year",
|
||||||
Component(
|
Span(
|
||||||
tag_name="span",
|
|
||||||
attributes=[("class", grey_value_class)],
|
attributes=[("class", grey_value_class)],
|
||||||
children=[str(game.original_year_released)],
|
children=[str(game.original_year_released)],
|
||||||
),
|
),
|
||||||
@@ -648,8 +626,7 @@ def _game_header(game: Game, request: HttpRequest, metrics: dict[str, Any]) -> S
|
|||||||
_played_row(game, request),
|
_played_row(game, request),
|
||||||
_meta_row(
|
_meta_row(
|
||||||
"Platform",
|
"Platform",
|
||||||
Component(
|
Span(
|
||||||
tag_name="span",
|
|
||||||
attributes=[("class", grey_value_class)],
|
attributes=[("class", grey_value_class)],
|
||||||
children=[str(game.platform)],
|
children=[str(game.platform)],
|
||||||
),
|
),
|
||||||
|
|||||||
+17
-21
@@ -6,13 +6,12 @@ from django.http import (
|
|||||||
HttpResponseRedirect,
|
HttpResponseRedirect,
|
||||||
)
|
)
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.views.decorators.http import require_POST
|
|
||||||
|
|
||||||
from django.template.defaultfilters import date as date_filter
|
from django.template.defaultfilters import date as date_filter
|
||||||
from django.template.defaultfilters import floatformat
|
from django.template.defaultfilters import floatformat
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.safestring import SafeText, mark_safe
|
from django.utils.safestring import SafeText, mark_safe
|
||||||
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
from common.components import (
|
from common.components import (
|
||||||
A,
|
A,
|
||||||
@@ -32,6 +31,7 @@ from common.components import (
|
|||||||
TableRow,
|
TableRow,
|
||||||
paginated_table_content,
|
paginated_table_content,
|
||||||
)
|
)
|
||||||
|
from common.components.primitives import Li, P, Td, Tr, Ul
|
||||||
from common.layout import render_page
|
from common.layout import render_page
|
||||||
from common.time import dateformat
|
from common.time import dateformat
|
||||||
from common.utils import paginate
|
from common.utils import paginate
|
||||||
@@ -129,7 +129,7 @@ def list_purchases(request: HttpRequest) -> HttpResponse:
|
|||||||
elided_page_range=elided_page_range,
|
elided_page_range=elided_page_range,
|
||||||
request=request,
|
request=request,
|
||||||
)
|
)
|
||||||
from common.components import PurchaseFilterBar, ModuleScript
|
from common.components import ModuleScript, PurchaseFilterBar
|
||||||
|
|
||||||
filter_bar = PurchaseFilterBar(
|
filter_bar = PurchaseFilterBar(
|
||||||
filter_json=filter_json,
|
filter_json=filter_json,
|
||||||
@@ -149,12 +149,10 @@ def list_purchases(request: HttpRequest) -> HttpResponse:
|
|||||||
|
|
||||||
def _purchase_additional_row() -> SafeText:
|
def _purchase_additional_row() -> SafeText:
|
||||||
"""The 'Submit & Create Session' row shown below the main Submit button."""
|
"""The 'Submit & Create Session' row shown below the main Submit button."""
|
||||||
return Component(
|
return Tr(
|
||||||
tag_name="tr",
|
|
||||||
children=[
|
children=[
|
||||||
Component(tag_name="td"),
|
Td(),
|
||||||
Component(
|
Td(
|
||||||
tag_name="td",
|
|
||||||
children=[
|
children=[
|
||||||
Button(
|
Button(
|
||||||
[],
|
[],
|
||||||
@@ -262,8 +260,7 @@ def _view_purchase_content(purchase: Purchase) -> SafeText:
|
|||||||
Div(
|
Div(
|
||||||
[("class", row_class)],
|
[("class", row_class)],
|
||||||
[
|
[
|
||||||
Component(
|
P(
|
||||||
tag_name="p",
|
|
||||||
children=[
|
children=[
|
||||||
"Price per game: ",
|
"Price per game: ",
|
||||||
PriceConverted([floatformat(purchase.price_per_game, 0)]),
|
PriceConverted([floatformat(purchase.price_per_game, 0)]),
|
||||||
@@ -273,10 +270,9 @@ def _view_purchase_content(purchase: Purchase) -> SafeText:
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Div([("class", row_class)], ["Games included in this purchase:"]),
|
Div([("class", row_class)], ["Games included in this purchase:"]),
|
||||||
Component(
|
Ul(
|
||||||
tag_name="ul",
|
|
||||||
children=[
|
children=[
|
||||||
Component(tag_name="li", children=[GameLink(game.id, game.name)])
|
Li(children=[GameLink(game.id, game.name)])
|
||||||
for game in purchase.games.all()
|
for game in purchase.games.all()
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -317,8 +313,7 @@ def _refund_confirmation_modal(purchase_id: int, request: HttpRequest) -> SafeTe
|
|||||||
],
|
],
|
||||||
children=[
|
children=[
|
||||||
CsrfInput(request),
|
CsrfInput(request),
|
||||||
Component(
|
P(
|
||||||
tag_name="p",
|
|
||||||
attributes=[("class", "dark:text-white text-center mt-3 text-sm")],
|
attributes=[("class", "dark:text-white text-center mt-3 text-sm")],
|
||||||
children=["Games will be marked as abandoned."],
|
children=["Games will be marked as abandoned."],
|
||||||
),
|
),
|
||||||
@@ -356,8 +351,7 @@ def _refund_confirmation_modal(purchase_id: int, request: HttpRequest) -> SafeTe
|
|||||||
],
|
],
|
||||||
children=["Confirm Refund"],
|
children=["Confirm Refund"],
|
||||||
),
|
),
|
||||||
Component(
|
P(
|
||||||
tag_name="p",
|
|
||||||
attributes=[("class", "dark:text-white text-center mt-5")],
|
attributes=[("class", "dark:text-white text-center mt-5")],
|
||||||
children=["Are you sure you want to mark this purchase as refunded?"],
|
children=["Are you sure you want to mark this purchase as refunded?"],
|
||||||
),
|
),
|
||||||
@@ -408,8 +402,10 @@ def related_purchase_by_game(request: HttpRequest) -> HttpResponse:
|
|||||||
from games.forms import related_purchase_queryset
|
from games.forms import related_purchase_queryset
|
||||||
|
|
||||||
form = PurchaseForm()
|
form = PurchaseForm()
|
||||||
qs = related_purchase_queryset().filter(games__in=games).order_by(
|
qs = (
|
||||||
"games__sort_name"
|
related_purchase_queryset()
|
||||||
|
.filter(games__in=games)
|
||||||
|
.order_by("games__sort_name")
|
||||||
)
|
)
|
||||||
|
|
||||||
form.fields["related_purchase"].queryset = qs
|
form.fields["related_purchase"].queryset = qs
|
||||||
|
|||||||
+13
-22
@@ -15,7 +15,6 @@ from common.components import (
|
|||||||
AddForm,
|
AddForm,
|
||||||
Button,
|
Button,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
Component,
|
|
||||||
Div,
|
Div,
|
||||||
Icon,
|
Icon,
|
||||||
ModuleScript,
|
ModuleScript,
|
||||||
@@ -25,6 +24,7 @@ from common.components import (
|
|||||||
SessionDeviceSelector,
|
SessionDeviceSelector,
|
||||||
paginated_table_content,
|
paginated_table_content,
|
||||||
)
|
)
|
||||||
|
from common.components.primitives import Span, Td, Tr
|
||||||
from common.layout import render_page
|
from common.layout import render_page
|
||||||
from common.time import (
|
from common.time import (
|
||||||
dateformat,
|
dateformat,
|
||||||
@@ -208,8 +208,7 @@ def _session_fields(form) -> SafeText:
|
|||||||
this_side = "start" if field.name == "timestamp_start" else "end"
|
this_side = "start" if field.name == "timestamp_start" else "end"
|
||||||
other_side = "end" if field.name == "timestamp_start" else "start"
|
other_side = "end" if field.name == "timestamp_start" else "start"
|
||||||
children.append(
|
children.append(
|
||||||
Component(
|
Span(
|
||||||
tag_name="span",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
(
|
(
|
||||||
"class",
|
"class",
|
||||||
@@ -292,8 +291,8 @@ def edit_session(request: HttpRequest, session_id: int) -> HttpResponse:
|
|||||||
def _session_row_fragment(session: Session) -> SafeText:
|
def _session_row_fragment(session: Session) -> SafeText:
|
||||||
"""A single session <tr> (the old list_sessions.html#session-row partial),
|
"""A single session <tr> (the old list_sessions.html#session-row partial),
|
||||||
returned by the inline end/clone-session HTMX endpoints."""
|
returned by the inline end/clone-session HTMX endpoints."""
|
||||||
name_link = Component(
|
name_link = A(
|
||||||
tag_name="a",
|
href=reverse("games:view_game", args=[session.game.id]),
|
||||||
attributes=[
|
attributes=[
|
||||||
(
|
(
|
||||||
"class",
|
"class",
|
||||||
@@ -305,12 +304,10 @@ def _session_row_fragment(session: Session) -> SafeText:
|
|||||||
"group-hover:outline-purple-400 group-hover:outline-4 "
|
"group-hover:outline-purple-400 group-hover:outline-4 "
|
||||||
"group-hover:decoration-purple-900 group-hover:text-purple-100",
|
"group-hover:decoration-purple-900 group-hover:text-purple-100",
|
||||||
),
|
),
|
||||||
("href", reverse("games:view_game", args=[session.game.id])),
|
|
||||||
],
|
],
|
||||||
children=[session.game.name],
|
children=[session.game.name],
|
||||||
)
|
)
|
||||||
name_td = Component(
|
name_td = Td(
|
||||||
tag_name="td",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
(
|
(
|
||||||
"class",
|
"class",
|
||||||
@@ -319,15 +316,13 @@ def _session_row_fragment(session: Session) -> SafeText:
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
children=[
|
children=[
|
||||||
Component(
|
Span(
|
||||||
tag_name="span",
|
|
||||||
attributes=[("class", "inline-block relative")],
|
attributes=[("class", "inline-block relative")],
|
||||||
children=[name_link],
|
children=[name_link],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
start_td = Component(
|
start_td = Td(
|
||||||
tag_name="td",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("class", "px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden sm:table-cell")
|
("class", "px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden sm:table-cell")
|
||||||
],
|
],
|
||||||
@@ -336,10 +331,9 @@ def _session_row_fragment(session: Session) -> SafeText:
|
|||||||
|
|
||||||
if not session.timestamp_end:
|
if not session.timestamp_end:
|
||||||
end_url = reverse("games:list_sessions_end_session", args=[session.id])
|
end_url = reverse("games:list_sessions_end_session", args=[session.id])
|
||||||
end_inner: SafeText | str = Component(
|
end_inner: SafeText | str = A(
|
||||||
tag_name="a",
|
href=end_url,
|
||||||
attributes=[
|
attributes=[
|
||||||
("href", end_url),
|
|
||||||
("hx-get", end_url),
|
("hx-get", end_url),
|
||||||
("hx-target", "closest tr"),
|
("hx-target", "closest tr"),
|
||||||
("hx-swap", "outerHTML"),
|
("hx-swap", "outerHTML"),
|
||||||
@@ -351,8 +345,7 @@ def _session_row_fragment(session: Session) -> SafeText:
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
children=[
|
children=[
|
||||||
Component(
|
Span(
|
||||||
tag_name="span",
|
|
||||||
attributes=[("class", "text-yellow-300")],
|
attributes=[("class", "text-yellow-300")],
|
||||||
children=["Finish now?"],
|
children=["Finish now?"],
|
||||||
)
|
)
|
||||||
@@ -362,19 +355,17 @@ def _session_row_fragment(session: Session) -> SafeText:
|
|||||||
end_inner = "--"
|
end_inner = "--"
|
||||||
else:
|
else:
|
||||||
end_inner = date_filter(session.timestamp_end, "d/m/Y H:i")
|
end_inner = date_filter(session.timestamp_end, "d/m/Y H:i")
|
||||||
end_td = Component(
|
end_td = Td(
|
||||||
tag_name="td",
|
|
||||||
attributes=[
|
attributes=[
|
||||||
("class", "px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden lg:table-cell")
|
("class", "px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden lg:table-cell")
|
||||||
],
|
],
|
||||||
children=[end_inner],
|
children=[end_inner],
|
||||||
)
|
)
|
||||||
duration_td = Component(
|
duration_td = Td(
|
||||||
tag_name="td",
|
|
||||||
attributes=[("class", "px-2 sm:px-4 md:px-6 md:py-2 font-mono")],
|
attributes=[("class", "px-2 sm:px-4 md:px-6 md:py-2 font-mono")],
|
||||||
children=[session.duration_formatted()],
|
children=[session.duration_formatted()],
|
||||||
)
|
)
|
||||||
return Component(tag_name="tr", children=[name_td, start_td, end_td, duration_td])
|
return Tr(children=[name_td, start_td, end_td, duration_td])
|
||||||
|
|
||||||
|
|
||||||
def clone_session_by_id(session_id: int) -> Session:
|
def clone_session_by_id(session_id: int) -> Session:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from common.components import (
|
|||||||
Div,
|
Div,
|
||||||
paginated_table_content,
|
paginated_table_content,
|
||||||
)
|
)
|
||||||
|
from common.components.primitives import P
|
||||||
from common.layout import render_page
|
from common.layout import render_page
|
||||||
from common.time import dateformat, local_strftime
|
from common.time import dateformat, local_strftime
|
||||||
from common.utils import paginate
|
from common.utils import paginate
|
||||||
@@ -75,8 +76,7 @@ def _delete_statuschange_content(statuschange, request: HttpRequest) -> SafeText
|
|||||||
inner = Div(
|
inner = Div(
|
||||||
[],
|
[],
|
||||||
[
|
[
|
||||||
Component(
|
P(
|
||||||
tag_name="p",
|
|
||||||
children=["Are you sure you want to delete this status change?"],
|
children=["Are you sure you want to delete this status change?"],
|
||||||
),
|
),
|
||||||
Button(
|
Button(
|
||||||
|
|||||||
Reference in New Issue
Block a user