Skip to content

Commit 9435c5f

Browse files
committed
chore: review components
1 parent 9a36ac7 commit 9435c5f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+721
-2828
lines changed

.editorconfig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ root = true
77
[*]
88
charset = utf-8
99
end_of_line = lf
10-
indent_size = 2
10+
indent_size = 4
1111
indent_style = space
1212
insert_final_newline = true
1313
trim_trailing_whitespace = true
1414

1515
# Python Code Style
16-
[*.py]
17-
indent_size = 4
16+
[*.{ts,js}]
17+
indent_size = 2

ckanext/theming/cli.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@
55
import logging
66
import os
77
import pprint
8+
import random
89
import re
910
import shutil
11+
import string
1012
from collections import defaultdict
1113
from collections.abc import Callable, Collection
1214
from typing import Any
1315

1416
import click
1517
import flask.signals
1618
from jinja2 import TemplateNotFound
19+
from jinja2.exceptions import UndefinedError
1720
from jinja2.runtime import Macro
1821
from werkzeug.exceptions import NotFound
1922
from werkzeug.routing import BuildError
@@ -127,10 +130,7 @@ def component_list(ctx: click.Context, theme: lib.Theme):
127130
"""List available components."""
128131
with _make_ui(ctx, theme) as ui:
129132
for item in ui:
130-
el = getattr(ui, item)
131-
if not el:
132-
el = "❌"
133-
click.echo(f"{item}: {el}")
133+
click.echo(item)
134134

135135

136136
@component.command("analyze")
@@ -179,7 +179,7 @@ def component_analyze(ctx: click.Context, components: Collection[str], theme: li
179179
@component.command("check")
180180
@theme_option
181181
@click.pass_context
182-
def component_check(ctx: click.Context, theme: lib.Theme):
182+
def component_check(ctx: click.Context, theme: lib.Theme): # noqa: C901
183183
"""Verify that a theme implements all required UI components."""
184184
categorized: dict[reference.Category, set[str]] = defaultdict(set)
185185
for name, info in reference.components.items():
@@ -209,6 +209,32 @@ def component_check(ctx: click.Context, theme: lib.Theme):
209209
click.secho(f" Extra components ({len(extra_components)})", fg="yellow")
210210
click.secho(" " + ", ".join(extra_components), fg="blue")
211211

212+
with ctx.meta["flask_app"].test_request_context():
213+
args = {
214+
"".join(random.sample(string.ascii_letters, 10)): random.randint(0, 100), # noqa: S311
215+
}
216+
rigid: dict[type, dict[str, Exception]] = defaultdict(dict)
217+
allow_rigid = {"user", "group", "package", "resource", "organization"}
218+
219+
for name in sorted(theme_components):
220+
func = getattr(ui, name)
221+
try:
222+
func(**args)
223+
except Exception as err: # noqa: BLE001
224+
if isinstance(err, UndefinedError) and name in allow_rigid:
225+
continue
226+
rigid[type(err)][name] = err
227+
228+
if rigid:
229+
click.secho(
230+
f" {sum(map(len, rigid.values()))} components produced an error with random arguments",
231+
fg="yellow",
232+
)
233+
for key, values in rigid.items():
234+
click.secho(f" {key.__name__}:", bold=False)
235+
for name, err in values.items():
236+
click.secho(f" {click.style(name, bold=True)}: {err}")
237+
212238

213239
@theme.group()
214240
def template():

ckanext/theming/lib.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,32 @@ def get_items(self, category: str, key: str | None = None, default: Any = None)
174174
storage = tk.g.setdefault(self._storage_key, defaultdict(dict))
175175
return storage[category].get(key, default) if key else storage[category]
176176

177+
def icon(self, name: str) -> str:
178+
"""Normalize icon name.
179+
180+
Maps common icon names to their corresponding names in the theme's icon
181+
set. If no mapping exists, returns the original name. This allows using
182+
consistent icon names across different themes. For example, "search" is
183+
mapped to "magnifying-glass" if the theme does not have icon with ID
184+
`search`. Themes can override these mappings as needed.
185+
186+
# TODO: Make this configurable per theme.
187+
188+
:param name: Common name of the icon.
189+
:return: The name of the corresponding icon provided by theme
190+
191+
"""
192+
icon_map = {
193+
"search": "magnifying-glass",
194+
"edit": "pencil",
195+
"delete": "trash",
196+
"add": "plus-circle",
197+
"info": "info-circle",
198+
"warning": "exclamation-triangle",
199+
"user": "user-circle",
200+
}
201+
return icon_map.get(name, name)
202+
177203

178204
class UI(Iterable[str], abc.ABC):
179205
"""Abstract base class for theme UIs.

ckanext/theming/reference.py

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,132 @@ def make_params(self, endpoint: str, data: dict[str, Any]): # noqa: C901
8282

8383
components.update(
8484
{
85+
# wrappers ############################################################
86+
"accordion_wrapper": Component(Category.ESSENTIAL),
87+
"account_nav_wrapper": Component(Category.ESSENTIAL),
88+
"activity_wrapper": Component(Category.ESSENTIAL),
89+
"breadcrumb_wrapper": Component(Category.ESSENTIAL),
90+
"content_action_wrapper": Component(Category.ESSENTIAL),
91+
"content_nav_wrapper": Component(Category.ESSENTIAL),
92+
"dropdown_wrapper": Component(Category.ESSENTIAL),
93+
"facet_wrapper": Component(Category.ESSENTIAL),
94+
"group_wrapper": Component(Category.ESSENTIAL),
95+
"main_nav_wrapper": Component(Category.ESSENTIAL),
96+
"menu_wrapper": Component(Category.ESSENTIAL),
97+
"nav_wrapper": Component(Category.ESSENTIAL),
98+
"organization_wrapper": Component(Category.ESSENTIAL),
99+
"package_wrapper": Component(Category.ESSENTIAL),
100+
"page_action_wrapper": Component(Category.ESSENTIAL),
101+
"panel_wrapper": Component(Category.ESSENTIAL),
102+
"resource_wrapper": Component(Category.ESSENTIAL),
103+
"tab_wrapper": Component(Category.ESSENTIAL),
104+
"user_wrapper": Component(Category.ESSENTIAL),
105+
# content #############################################################
106+
"activity": Component(Category.ESSENTIAL),
107+
"facet": Component(Category.ESSENTIAL),
108+
"group": Component(Category.ESSENTIAL),
109+
"organization": Component(Category.ESSENTIAL),
110+
"package": Component(Category.ESSENTIAL),
111+
"resource": Component(Category.ESSENTIAL),
112+
"user": Component(Category.ESSENTIAL),
113+
# containers ##########################################################
114+
"accordion": Component(Category.ESSENTIAL),
115+
"button_group": Component(Category.ESSENTIAL),
116+
"card": Component(Category.ESSENTIAL),
117+
"column": Component(Category.ESSENTIAL),
118+
"container": Component(Category.ESSENTIAL),
119+
"grid": Component(Category.ESSENTIAL),
120+
"list": Component(Category.ESSENTIAL),
121+
"list_item": Component(Category.ESSENTIAL),
122+
"panel": Component(Category.ESSENTIAL),
123+
# sections ############################################################
124+
"facet_section": Component(Category.ESSENTIAL),
125+
"section": Component(Category.ESSENTIAL),
126+
"sidebar_section": Component(Category.ESSENTIAL),
127+
# feedback ############################################################
128+
"alert": Component(Category.ESSENTIAL),
129+
"confirm_modal": Component(Category.ESSENTIAL),
130+
"modal": Component(Category.ESSENTIAL),
131+
"popover": Component(Category.ESSENTIAL),
132+
"progress": Component(Category.ESSENTIAL),
133+
"spinner": Component(Category.ESSENTIAL),
134+
"toast": Component(Category.ESSENTIAL),
135+
"tooltip": Component(Category.ESSENTIAL),
136+
# elements ############################################################
137+
"avatar": Component(Category.ESSENTIAL),
138+
"badge": Component(Category.ESSENTIAL),
139+
"button": Component(Category.ESSENTIAL),
140+
"divider": Component(Category.ESSENTIAL),
141+
"heading": Component(Category.ESSENTIAL),
142+
"icon": Component(Category.ESSENTIAL),
143+
"image": Component(Category.ESSENTIAL),
85144
"link": Component(Category.ESSENTIAL),
86-
"accordion": Component(Category.RECOMMENDED),
145+
"tag": Component(Category.ESSENTIAL),
146+
"text": Component(Category.ESSENTIAL),
147+
"video": Component(Category.ESSENTIAL),
148+
# data ################################################################
149+
"chart": Component(Category.RECOMMENDED),
150+
"code": Component(Category.ESSENTIAL),
151+
"definition_list": Component(Category.ESSENTIAL),
152+
"table": Component(Category.ESSENTIAL),
153+
"table_body": Component(Category.ESSENTIAL),
154+
"table_cell": Component(Category.ESSENTIAL),
155+
"table_head": Component(Category.ESSENTIAL),
156+
"table_row": Component(Category.ESSENTIAL),
157+
# form ################################################################
158+
"autocomplete": Component(Category.ESSENTIAL),
159+
"checkbox": Component(Category.ESSENTIAL),
160+
"extra_field": Component(Category.ESSENTIAL),
161+
"extra_field_multiplicator": Component(Category.ESSENTIAL),
162+
"extra_fields_collection": Component(Category.ESSENTIAL),
163+
"field_errors": Component(Category.ESSENTIAL),
164+
"file_input": Component(Category.ESSENTIAL),
165+
"form": Component(Category.ESSENTIAL),
166+
"form_actions": Component(Category.ESSENTIAL),
167+
"form_end": Component(Category.ESSENTIAL),
168+
"form_errors": Component(Category.ESSENTIAL),
169+
"form_start": Component(Category.ESSENTIAL),
170+
"hidden_input": Component(Category.ESSENTIAL),
171+
"input": Component(Category.ESSENTIAL),
172+
"markdown": Component(Category.ESSENTIAL),
173+
"radio": Component(Category.ESSENTIAL),
174+
"range_input": Component(Category.ESSENTIAL),
175+
"select": Component(Category.ESSENTIAL),
176+
"select_block": Component(Category.ESSENTIAL),
177+
"select_option": Component(Category.ESSENTIAL),
178+
"submit": Component(Category.ESSENTIAL),
179+
"textarea": Component(Category.ESSENTIAL),
180+
# review ##############################################################
181+
"account_block": Component(Category.ESSENTIAL),
182+
"account_nav_item": Component(Category.ESSENTIAL),
183+
"breadcrumb_divider": Component(Category.ESSENTIAL),
184+
"breadcrumb_item": Component(Category.ESSENTIAL),
185+
"content_action_item": Component(Category.ESSENTIAL),
186+
"content_nav_item": Component(Category.ESSENTIAL),
187+
"datetime": Component(Category.ESSENTIAL),
188+
"dropdown_item": Component(Category.ESSENTIAL),
189+
"footer_block": Component(Category.ESSENTIAL),
190+
"header_block": Component(Category.ESSENTIAL),
191+
"license": Component(Category.ESSENTIAL),
192+
"main_nav_item": Component(Category.ESSENTIAL),
193+
"menu_item": Component(Category.ESSENTIAL),
194+
"modal_close_handle": Component(Category.ESSENTIAL),
195+
"modal_handle": Component(Category.ESSENTIAL),
196+
"nav_item": Component(Category.ESSENTIAL),
197+
"page_action_item": Component(Category.ESSENTIAL),
198+
"pagination": Component(Category.ESSENTIAL),
199+
"pagination_info": Component(Category.ESSENTIAL),
200+
"panel_handle": Component(Category.ESSENTIAL),
201+
"popover_handle": Component(Category.ESSENTIAL),
202+
"search_active_filters": Component(Category.ESSENTIAL),
203+
"search_advanced_controls": Component(Category.ESSENTIAL),
204+
"search_form": Component(Category.ESSENTIAL),
205+
"search_input": Component(Category.ESSENTIAL),
206+
"search_results_header": Component(Category.ESSENTIAL),
207+
"search_sort_control": Component(Category.ESSENTIAL),
208+
"search_submit_button": Component(Category.ESSENTIAL),
209+
"subtitle_item": Component(Category.ESSENTIAL),
210+
"tab_item": Component(Category.ESSENTIAL),
87211
}
88212
)
89213

ckanext/theming/themes/bare/templates/admin/trash.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
{%- for ent_type, entities in data.items() -%}
2525
{% set entities = entities|list %}
2626

27-
{%- call ui.util.call(ui.accordion_item, title=titles[ent_type]) -%}
27+
{%- call ui.util.call(ui.accordion, title=titles[ent_type]) -%}
2828

2929
{%- with form_id = ui.util.id(), modal_id = ui.util.id() -%}
3030
{{ ui.modal_handle(_('Purge'), id=modal_id) }}

ckanext/theming/themes/bare/templates/ckanext/stats/index.html

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,29 @@
77

88
{% block secondary_content %}
99
{%- call ui.util.call(ui.sidebar_section, title=_('Statistics Menu')) -%}
10-
{%- call ui.util.call(ui.listing) -%}
11-
{%- call ui.util.call(ui.listing_item) -%}
10+
{%- call ui.util.call(ui.list) -%}
11+
{%- call ui.util.call(ui.list_item) -%}
1212
{{ ui.panel_handle(_('Total Number of Datasets'), id="stats-total-datasets") }}
1313
{%- endcall %}
1414

15-
{%- call ui.util.call(ui.listing_item) -%}
15+
{%- call ui.util.call(ui.list_item) -%}
1616
{{ ui.panel_handle(_('Dataset Revisions per Week'), id="stats-dataset-revisions") }}
1717
{%- endcall %}
1818

19-
{%- call ui.util.call(ui.listing_item) -%}
19+
{%- call ui.util.call(ui.list_item) -%}
2020
{{ ui.panel_handle(_('Most Edited Datasets'), id="stats-most-edited") }}
2121
{%- endcall %}
2222

23-
{%- call ui.util.call(ui.listing_item) -%}
23+
{%- call ui.util.call(ui.list_item) -%}
2424
{{ ui.panel_handle(_('Largest Groups'), id="stats-largest-groups") }}
2525
{%- endcall %}
2626

27-
{%- call ui.util.call(ui.listing_item) -%}
27+
{%- call ui.util.call(ui.list_item) -%}
2828
{{ ui.panel_handle(_('Top Tags'), id="stats-top-tags") }}
2929
{%- endcall %}
3030

3131
{% if top_package_creators %}
32-
{%- call ui.util.call(ui.listing_item) -%}
32+
{%- call ui.util.call(ui.list_item) -%}
3333
{{ ui.panel_handle(_('Users Creating Most Datasets'), id="stats-most-create") }}
3434
{%- endcall %}
3535
{% endif %}
@@ -41,7 +41,7 @@
4141
{%- block primary_content_inner -%}
4242

4343
{%- call ui.util.call(ui.panel_wrapper) -%}
44-
{%- call ui.util.call(ui.panel, open=true, id="stats-total-datasets") -%}
44+
{%- call ui.util.call(ui.panel, active=true, id="stats-total-datasets") -%}
4545
{{ ui.heading(_('Total number of Datasets'), level=2) }}
4646
{%- call ui.util.call(ui.table, striped=true) -%}
4747
{{ ui.table_head(ui.table_row(cells=[_("Date"), _("Total datasets")], heading=true)) }}

ckanext/theming/themes/bare/templates/datapusher/resource_data.html

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,22 @@
5454

5555
{% if status.status and status.task_info and not has_error %}
5656
{{ ui.heading(_('Upload Log'), level=3) }}
57-
{%- call ui.util.call(ui.listing) -%}
57+
{%- call ui.util.call(ui.list) -%}
5858
{% for item in status.task_info.logs|sort(attribute='timestamp') %}
5959
{% set icon = 'check' if item.level == 'INFO' else 'exclamation' %}
6060
{% set class = ' failure' if icon == 'exclamation' else ' success' %}
61-
{% set popover_content = 'test' %}
62-
{%- call ui.util.call(ui.listing_item) -%}
61+
62+
{%- call ui.util.call(ui.list_item) -%}
6363
{% for line in item.message.strip().split('\n') %}
6464
{{ line | urlize }}<br />
6565
{% endfor %}
6666
<span title="{{ h.render_datetime(item.timestamp, with_hours=True) }}">
6767
{{ h.time_ago_from_timestamp(item.timestamp) }}
6868
{%- with popover_id=ui.util.id() -%}
69-
{%- call ui.util.call(ui.popover_content, title=_("Details"), id=popover_id) -%}
70-
{%- call ui.util.call(ui.listing) -%}
69+
{%- call ui.util.call(ui.popover, title=_("Details"), id=popover_id) -%}
70+
{%- call ui.util.call(ui.list) -%}
7171
{% for key, value in item.items() %}
72-
{%- call ui.util.call(ui.listing_item) -%}
72+
{%- call ui.util.call(ui.list_item) -%}
7373
<strong>{{ key }}</strong>: {{ h.clean_html(value|string) }}
7474
{%- endcall %}
7575
{% endfor %}
@@ -82,7 +82,7 @@
8282

8383
{% endfor %}
8484

85-
{{ ui.listing_item(_('End of log')) }}
85+
{{ ui.list_item(_('End of log')) }}
8686
{%- endcall %}
8787
{% endif %}
8888

ckanext/theming/themes/bare/templates/datastore/dictionary.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
{% for field in fields %}
1818
{%- set position = loop.index -%}
1919

20-
{%- call ui.util.call(ui.accordion_item, title=_( "Field {num}.").format(num=position) ~ " " ~ field.id ~ " " ~ field.type ~ (", " ~ _("Primary key") if field.schema and field.schema.is_index else "")) -%}
20+
{%- call ui.util.call(ui.accordion, title=_( "Field {num}.").format(num=position) ~ " " ~ field.id ~ " " ~ field.type ~ (", " ~ _("Primary key") if field.schema and field.schema.is_index else "")) -%}
2121

2222

2323

ckanext/theming/themes/bare/templates/datastore/snippets/_dictionary_view.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{%- call ui.util.call(ui.accordion_wrapper) -%}
33
{% for field in ddict %}
44

5-
{%- call ui.util.call(ui.accordion_item, title=ui.grid(ui.column(loop.index) ~ ui.column(h.get_translated(field.get('info', {}), 'label') or field.id, span={"xs": 7}) ~ ui.column(field.type, span={"xs": 4}))) -%}
5+
{%- call ui.util.call(ui.accordion, title=ui.grid(ui.column(loop.index) ~ ui.column(h.get_translated(field.get('info', {}), 'label') or field.id, span={"xs": 7}) ~ ui.column(field.type, span={"xs": 4}))) -%}
66

77
{%- call ui.util.call(ui.table, striped=true) -%}
88
{{ ui.table_row(ui.table_cell(_('ID'), heading=true) ~ ui.table_cell(field.id)) }}

ckanext/theming/themes/bare/templates/datastore/snippets/api_info.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@
4141
{% endfor %}
4242

4343
{%- call ui.util.call(ui.accordion_wrapper) -%}
44-
{%- call ui.util.call(ui.accordion_item, title=_('Querying')) -%}
44+
{%- call ui.util.call(ui.accordion, title=_('Querying')) -%}
4545
{% block query_examples %}
4646
<strong>{{ _('Get 5 results containing "jones" in any field:') }}</strong>
4747
{% for clang in code_languages %}
48-
{{ ui.panel(code_examples[clang]['request_limit'], id="lang-" ~ clang, open=loop.index == 1) }}
48+
{{ ui.panel(code_examples[clang]['request_limit'], id="lang-" ~ clang, active=loop.index == 1) }}
4949
{% endfor %}
5050
<strong>{{ _('Get results with either "watershed" or "survey" as subject and "active" as its stage:') }}</strong>
5151
{% for clang in code_languages %}
@@ -62,7 +62,7 @@
6262
{%- endcall %}
6363

6464
{%- call ui.util.call(ui.accordion_wrapper) -%}
65-
{%- call ui.util.call(ui.accordion_item, title=_('Using the API with this Web Browser')) -%}
65+
{%- call ui.util.call(ui.accordion, title=_('Using the API with this Web Browser')) -%}
6666
{% block get_query_examples %}
6767
<p>{{ _('Some API endpoints may be accessed using a GET query string.') }}</p>
6868
<strong>{{ _('Query example (first 5 results)') }}</strong>

0 commit comments

Comments
 (0)