many styling changes to applets and palettes applet esp.; all applets seeded w. < 3rows bumped to 3 w. new migration in apps.applets; setting palette no longer reloads entire page, only preset background-color vars; two new ITs in apps.dash.tests.ITs.test_views.SetPaletteTest to ensure dash.views functionality fires; unified h2 applet title html structure & styled its text vertically to waste less applet space
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
This commit is contained in:
24
src/apps/applets/migrations/0005_gameboard_applet_heights.py
Normal file
24
src/apps/applets/migrations/0005_gameboard_applet_heights.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def increase_gameboard_applet_heights(apps, schema_editor):
|
||||||
|
Applet = apps.get_model('applets', 'Applet')
|
||||||
|
Applet.objects.filter(slug__in=['new-game', 'game-kit', 'wallet-payment']).update(grid_rows=3)
|
||||||
|
|
||||||
|
|
||||||
|
def revert_gameboard_applet_heights(apps, schema_editor):
|
||||||
|
Applet = apps.get_model('applets', 'Applet')
|
||||||
|
Applet.objects.filter(slug__in=['new-game', 'game-kit', 'wallet-payment']).update(grid_rows=2)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('applets', '0004_rename_list_applet_slugs')
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
increase_gameboard_applet_heights,
|
||||||
|
revert_gameboard_applet_heights,
|
||||||
|
)
|
||||||
|
]
|
||||||
@@ -8,3 +8,27 @@ const initialize = (inputSelector) => {
|
|||||||
textInput.classList.remove("is-invalid");
|
textInput.classList.remove("is-invalid");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const bindPaletteForms = () => {
|
||||||
|
document.querySelectorAll('form[action*="set_palette"]').forEach(form => {
|
||||||
|
form.addEventListener("submit", async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const resp = await fetch(form.action, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Accept": "application/json" },
|
||||||
|
body: new FormData(form, e.submitter),
|
||||||
|
});
|
||||||
|
if (!resp.ok) return;
|
||||||
|
const { palette } = await resp.json();
|
||||||
|
// Swap body palette class
|
||||||
|
[...document.body.classList]
|
||||||
|
.filter(c => c.startsWith("palette-"))
|
||||||
|
.forEach(c => document.body.classList.remove(c));
|
||||||
|
document.body.classList.add(palette);
|
||||||
|
// Update active swatch indicator
|
||||||
|
document.querySelectorAll(".swatch").forEach(sw => {
|
||||||
|
sw.classList.toggle("active", sw.classList.contains(palette));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -299,6 +299,24 @@ class SetPaletteTest(TestCase):
|
|||||||
response = self.client.post("/dashboard/set_palette", data={"palette": "palette-default"})
|
response = self.client.post("/dashboard/set_palette", data={"palette": "palette-default"})
|
||||||
self.assertRedirects(response, "/", fetch_redirect_response=False)
|
self.assertRedirects(response, "/", fetch_redirect_response=False)
|
||||||
|
|
||||||
|
def test_set_palette_returns_json_when_requested(self):
|
||||||
|
response = self.client.post(
|
||||||
|
"/dashboard/set_palette",
|
||||||
|
data={"palette": "palette-sepia"},
|
||||||
|
headers={"Accept": "application/json"},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.json(), {"palette": "palette-sepia"})
|
||||||
|
|
||||||
|
def test_locked_palette_returns_unchanged_json(self):
|
||||||
|
response = self.client.post(
|
||||||
|
"/dashboard/set_palette",
|
||||||
|
data={"palette": "palette-nirvana"},
|
||||||
|
headers={"Accept": "application/json"},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.json(), {"palette": "palette-default"})
|
||||||
|
|
||||||
def test_dashboard_contains_set_palette_form(self):
|
def test_dashboard_contains_set_palette_form(self):
|
||||||
response = self.client.get(self.url)
|
response = self.client.get(self.url)
|
||||||
parsed = lxml.html.fromstring(response.content)
|
parsed = lxml.html.fromstring(response.content)
|
||||||
|
|||||||
@@ -122,6 +122,8 @@ def set_palette(request):
|
|||||||
if palette in UNLOCKED_PALETTES:
|
if palette in UNLOCKED_PALETTES:
|
||||||
request.user.palette = palette
|
request.user.palette = palette
|
||||||
request.user.save(update_fields=["palette"])
|
request.user.save(update_fields=["palette"])
|
||||||
|
if "application/json" in request.headers.get("Accept", ""):
|
||||||
|
return JsonResponse({"palette": request.user.palette})
|
||||||
return redirect("home")
|
return redirect("home")
|
||||||
|
|
||||||
@login_required(login_url="/")
|
@login_required(login_url="/")
|
||||||
|
|||||||
@@ -1,10 +1,49 @@
|
|||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
|
from apps.applets.models import Applet
|
||||||
from apps.lyric.models import User
|
from apps.lyric.models import User
|
||||||
|
|
||||||
from .base import FunctionalTest
|
from .base import FunctionalTest
|
||||||
|
|
||||||
|
|
||||||
|
class PaletteSwapTest(FunctionalTest):
|
||||||
|
def test_selecting_palette_updates_body_class_without_page_reload(self):
|
||||||
|
Applet.objects.get_or_create(slug="palette", defaults={
|
||||||
|
"name": "Palette", "context": "dashboard",
|
||||||
|
})
|
||||||
|
user, _ = User.objects.get_or_create(email="swap@test.io")
|
||||||
|
self.create_pre_authenticated_session("swap@test.io")
|
||||||
|
self.browser.get(self.live_server_url)
|
||||||
|
|
||||||
|
body = self.browser.find_element(By.TAG_NAME, "body")
|
||||||
|
self.assertIn("palette-default", body.get_attribute("class"))
|
||||||
|
|
||||||
|
# Mark the window — this survives JS execution but is wiped on a real reload
|
||||||
|
self.browser.execute_script("window._no_reload_marker = true;")
|
||||||
|
|
||||||
|
# Click OK on a non-active palette
|
||||||
|
btn = self.wait_for(
|
||||||
|
lambda: self.browser.find_element(
|
||||||
|
By.CSS_SELECTOR,
|
||||||
|
".palette-item:has(.swatch:not(.active)) .btn-confirm",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
btn.click()
|
||||||
|
|
||||||
|
# Body palette class swaps without reload
|
||||||
|
self.wait_for(
|
||||||
|
lambda: self.assertNotIn(
|
||||||
|
"palette-default",
|
||||||
|
self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Marker still present — no full page reload occurred
|
||||||
|
self.assertTrue(
|
||||||
|
self.browser.execute_script("return window._no_reload_marker === true;")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SiteThemeTest(FunctionalTest):
|
class SiteThemeTest(FunctionalTest):
|
||||||
def test_page_renders_with_earthman_palette(self):
|
def test_page_renders_with_earthman_palette(self):
|
||||||
self.browser.get(self.live_server_url)
|
self.browser.get(self.live_server_url)
|
||||||
|
|||||||
@@ -159,9 +159,38 @@
|
|||||||
;
|
;
|
||||||
background-color: rgba(0, 0, 0, 0.125);
|
background-color: rgba(0, 0, 0, 0.125);
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
padding: 1rem;
|
position: relative;
|
||||||
|
padding: 0.75rem 0.75rem 0.75rem 2rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
|
> h2 {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
writing-mode: vertical-rl;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
font-size: 1rem;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
margin: 0;
|
||||||
|
padding-right: 0.2rem;
|
||||||
|
color: rgba(var(--secUser), 1);
|
||||||
|
background-color: rgba(0, 0, 0, 0.125);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0.5rem rgba(var(--priUser), 0.5),
|
||||||
|
0.12rem 0.12rem 0.5rem rgba(0, 0, 0, 0.25),
|
||||||
|
;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
a { color: rgba(var(--terUser), 1); text-decoration: none; }
|
||||||
|
}
|
||||||
grid-column: span var(--applet-cols, 12);
|
grid-column: span var(--applet-cols, 12);
|
||||||
grid-row: span var(--applet-rows, 3);
|
grid-row: span var(--applet-rows, 3);
|
||||||
|
|
||||||
|
|||||||
@@ -31,11 +31,9 @@ body.page-dashboard {
|
|||||||
|
|
||||||
#id_applets_container {
|
#id_applets_container {
|
||||||
#id_applet_my_notes {
|
#id_applet_my_notes {
|
||||||
padding: 1.25rem 1.5rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
h2 { flex-shrink: 0; margin-bottom: 0.25rem; }
|
|
||||||
|
|
||||||
.my-notes-container {
|
.my-notes-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -61,12 +59,10 @@ body.page-dashboard {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
|
|
||||||
h2 { flex-shrink: 0; margin-bottom: 0; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#id_applet_palette {
|
#id_applet_palette {
|
||||||
padding: 0;
|
padding: 0 0 0 1.5rem;
|
||||||
|
|
||||||
.palette-scroll {
|
.palette-scroll {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -101,11 +101,6 @@ body.page-wallet {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
|
||||||
h2 {
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token-row {
|
.token-row {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="billboard-page">
|
<div class="billboard-page">
|
||||||
<h2>My Games</h2>
|
<h2>My Scrolls</h2>
|
||||||
<ul class="game-list">
|
<ul class="game-list">
|
||||||
{% for room in my_rooms %}
|
{% for room in my_rooms %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'billboard:scroll' room.id %}">{{ room.name }}</a>
|
<a href="{% url 'billboard:scroll' room.id %}">{{ room.name }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<li><small>No games yet.</small></li>
|
<li><small>No scrolls yet.</small></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id="id_applet_my_notes"
|
id="id_applet_my_notes"
|
||||||
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
||||||
>
|
>
|
||||||
<h2><a href="{% url 'my_notes' user.id %}" class="my-notes-main">My notes</a></h2>
|
<h2><a href="{% url 'my_notes' user.id %}" class="my-notes-main">My Notes</a></h2>
|
||||||
<div class="my-notes-container">
|
<div class="my-notes-container">
|
||||||
<ul>
|
<ul>
|
||||||
{% for note in recent_notes %}
|
{% for note in recent_notes %}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
id="id_applet_new_note"
|
id="id_applet_new_note"
|
||||||
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
||||||
>
|
>
|
||||||
<h2>Start a new note</h2>
|
<h2>New Note</h2>
|
||||||
{% url "new_note" as form_action %}
|
{% url "new_note" as form_action %}
|
||||||
{% include "apps/dashboard/_partials/_form.html" with form=form form_action=form_action %}
|
{% include "apps/dashboard/_partials/_form.html" with form=form form_action=form_action %}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
class="palette"
|
class="palette"
|
||||||
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
||||||
>
|
>
|
||||||
|
<h2>Palettes</h2>
|
||||||
<div class="palette-scroll">
|
<div class="palette-scroll">
|
||||||
{% for palette in palettes %}
|
{% for palette in palettes %}
|
||||||
<div class="palette-item">
|
<div class="palette-item">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
id="id_applet_username"
|
id="id_applet_username"
|
||||||
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
||||||
>
|
>
|
||||||
|
<h2>Username</h2>
|
||||||
<form method="POST" action="{% url "set_profile" %}">
|
<form method="POST" action="{% url "set_profile" %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="username-field">
|
<div class="username-field">
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
id="id_applet_wallet"
|
id="id_applet_wallet"
|
||||||
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
||||||
>
|
>
|
||||||
<h2><a href="{% url "wallet" %}" class="wallet-manage-link">Manage Wallet</a></h2>
|
<h2><a href="{% url "wallet" %}" class="wallet-manage-link">My Wallet</a></h2>
|
||||||
<span><i class="fa-solid fa-ticket"></i>: {{ user.wallet.writs }}</span>
|
<span><i class="fa-solid fa-ticket"></i>: {{ user.wallet.writs }}</span>
|
||||||
</section>
|
</section>
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
<script>
|
<script>
|
||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
initialize("#id_text");
|
initialize("#id_text");
|
||||||
|
bindPaletteForms();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
id="id_payment_methods"
|
id="id_payment_methods"
|
||||||
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};"
|
||||||
>
|
>
|
||||||
<h2>Payment methods</h2>
|
<h2>Payment</h2>
|
||||||
<button id="id_add_payment_method">Add Payment Method</button>
|
<button id="id_add_payment_method">Add Payment Method</button>
|
||||||
<div id="id_stripe_payment_element"></div>
|
<div id="id_stripe_payment_element"></div>
|
||||||
<button id="id_save_payment_method" hidden>Save Card</button>
|
<button id="id_save_payment_method" hidden>Save Card</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user