feat(live-state): ?view=recent-modal branch + single-column modal list
Adds the _recent_players_modal_body.html partial for the full recent-players list (no 10-item cap), the route branch in live_state_fragment that renders it when ?view=recent-modal is requested, and the .recent-modal-list CSS rule that forces single-column layout inside the modal. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
96bbd0c136
commit
6de5f90626
4 changed files with 73 additions and 0 deletions
|
|
@ -266,6 +266,12 @@ def live_state_fragment(server_id: int) -> Response:
|
|||
recent_total = len(recent_rows)
|
||||
recent_overview = recent_rows[:10]
|
||||
|
||||
if request.args.get("view") == "recent-modal":
|
||||
return render_template(
|
||||
"_recent_players_modal_body.html",
|
||||
recent_players=recent_rows,
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"_live_state.html",
|
||||
server=server,
|
||||
|
|
|
|||
|
|
@ -1120,3 +1120,14 @@ div.modal.modal-wide {
|
|||
.console-input-form.htmx-request .console-spinner {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.recent-modal-list {
|
||||
/* Force a single column inside the modal regardless of the 5-col
|
||||
default on .player-grid.recent. !important is acceptable here:
|
||||
the only way to override the more-specific class selector
|
||||
chain is via specificity or !important; modal-only override
|
||||
is local enough that long-term maintainability is fine. */
|
||||
grid-template-columns: 1fr !important;
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
|
|
|||
21
l4d2web/l4d2web/templates/_recent_players_modal_body.html
Normal file
21
l4d2web/l4d2web/templates/_recent_players_modal_body.html
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{# Full single-column list of recent players for the
|
||||
#recent-players-modal. Rendered via /servers/<id>/live-state?view=recent-modal.
|
||||
Reuses the same chip markup as the inline grid in _live_state.html. #}
|
||||
<ul class="player-grid recent recent-modal-list">
|
||||
{% for row in recent_players %}
|
||||
{% set display_name = row.persona_name or row.name_at_join %}
|
||||
<li class="player-card recent-chip">
|
||||
<a class="player-link"
|
||||
href="https://steamcommunity.com/profiles/{{ row.steam_id_64 }}"
|
||||
target="_blank" rel="noopener noreferrer">
|
||||
{% if row.avatar_url %}
|
||||
<img class="avatar" src="{{ row.avatar_url }}" alt="">
|
||||
{% else %}
|
||||
<span class="avatar placeholder" aria-hidden="true"></span>
|
||||
{% endif %}
|
||||
<span class="name" title="{{ display_name }}">{{ display_name }}</span>
|
||||
</a>
|
||||
<span class="meta">· {{ row.last_seen | timeago }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
@ -136,3 +136,38 @@ def test_live_state_recent_header_not_clickable_when_le_10(owner_client_with_ser
|
|||
|
||||
assert "4 Recent" in html
|
||||
assert 'data-inline-modal-open="recent-players-modal"' not in html
|
||||
|
||||
|
||||
def test_live_state_recent_modal_view_returns_single_column_chip_list(owner_client_with_server) -> None:
|
||||
"""?view=recent-modal renders all recent players (not just the
|
||||
10-item overview) in a single-column scrollable list using the
|
||||
same chip markup."""
|
||||
from datetime import timedelta
|
||||
from l4d2web.models import ServerPlayerSession
|
||||
|
||||
client, server_id = owner_client_with_server
|
||||
|
||||
now = datetime.now(UTC)
|
||||
total = 13
|
||||
|
||||
with session_scope() as db:
|
||||
for i in range(total):
|
||||
db.add(ServerPlayerSession(
|
||||
server_id=server_id,
|
||||
steam_id_64=str(76561190000000000 + i),
|
||||
joined_at=now - timedelta(hours=i + 2),
|
||||
left_at=now - timedelta(hours=i + 1),
|
||||
name_at_join=f"Player{i}",
|
||||
min_ping=10,
|
||||
max_ping=50,
|
||||
))
|
||||
|
||||
resp = client.get(f"/servers/{server_id}/live-state?view=recent-modal")
|
||||
assert resp.status_code == 200
|
||||
body = resp.get_data(as_text=True)
|
||||
# All 13 chips present (no slice in the modal view).
|
||||
assert body.count('class="player-card recent-chip"') == 13
|
||||
# Has the modal-list wrapper class.
|
||||
assert 'class="player-grid recent recent-modal-list"' in body
|
||||
# No "Current" or "N Recent" header — modal view is just the list.
|
||||
assert "Recent" not in body or "Recent</button>" not in body # accept either: the test is about the list rendering, not headers
|
||||
|
|
|
|||
Loading…
Reference in a new issue