diff --git a/src/apps/dashboard/migrations/0003_seed_applets.py b/src/apps/dashboard/migrations/0003_seed_applets.py index ead2bcc..78c4666 100644 --- a/src/apps/dashboard/migrations/0003_seed_applets.py +++ b/src/apps/dashboard/migrations/0003_seed_applets.py @@ -4,7 +4,7 @@ from django.db import migrations def seed_applets(apps, schema_editor): Applet = apps.get_model("dashboard", "Applet") Applet.objects.get_or_create(slug="username", defaults={"name": "Username"}) - Applet.objects.get_or_create(slug="palette", defaults={"name": "Palette"}) + Applet.objects.get_or_create(slug="theme-switcher", defaults={"name": "Theme Switcher"}) class Migration(migrations.Migration): diff --git a/src/apps/dashboard/migrations/0005_set_applet_grid_defaults.py b/src/apps/dashboard/migrations/0005_set_applet_grid_defaults.py index 7144235..87e605f 100644 --- a/src/apps/dashboard/migrations/0005_set_applet_grid_defaults.py +++ b/src/apps/dashboard/migrations/0005_set_applet_grid_defaults.py @@ -3,7 +3,7 @@ from django.db import migrations def set_grid_defaults(apps, schema_editor): Applet = apps.get_model("dashboard", "Applet") - Applet.objects.filter(slug__in=["username", "palette"]).update(grid_cols=6, grid_rows=3) + Applet.objects.filter(slug__in=["username", "theme-switcher"]).update(grid_cols=6, grid_rows=3) Applet.objects.get_or_create(slug="new-list", defaults={"name": "New List", "grid_cols": 9, "grid_rows": 3}) Applet.objects.get_or_create(slug="my-lists", defaults={"name": "My Lists", "grid_cols": 3, "grid_rows": 3}) diff --git a/src/apps/dashboard/migrations/0006_rename_theme_switcher.py b/src/apps/dashboard/migrations/0006_rename_theme_switcher.py new file mode 100644 index 0000000..3c768cc --- /dev/null +++ b/src/apps/dashboard/migrations/0006_rename_theme_switcher.py @@ -0,0 +1,18 @@ +from django.db import migrations + + +def rename_theme_switcher(apps, schema_editor): + Applet = apps.get_model("dashboard", "Applet") + Applet.objects.filter(slug="theme-switcher").update( + slug="palette", name="Palette", grid_cols=6, grid_rows=3 + ) + + +class Migration(migrations.Migration): + dependencies = [ + ("dashboard", "0005_set_applet_grid_defaults"), + ] + + operations = [ + migrations.RunPython(rename_theme_switcher, migrations.RunPython.noop), + ] diff --git a/src/apps/dashboard/static/apps/scripts/dashboard.js b/src/apps/dashboard/static/apps/scripts/dashboard.js index 704d5bf..91fb42f 100644 --- a/src/apps/dashboard/static/apps/scripts/dashboard.js +++ b/src/apps/dashboard/static/apps/scripts/dashboard.js @@ -2,8 +2,29 @@ const initialize = (inputSelector) => { // console.log("initialize called!"); const textInput = document.querySelector(inputSelector); + if (!textInput) return; textInput.oninput = () => { // console.log("oninput triggered"); textInput.classList.remove("is-invalid"); }; +}; + +const initGearMenu = () => { + const gear = document.getElementById('id_dash_gear'); + if (!gear) return; + + gear.addEventListener('click', (e) => { + e.stopPropagation(); + const menu = document.getElementById('id_applet_menu'); + if (!menu) return; + menu.style.display = menu.style.display === 'none' ? 'block' : 'none'; + }); + + document.addEventListener('click', (e) => { + const menu = document.getElementById('id_applet_menu'); + if (!menu || menu.style.display === 'none') return; + if (e.target.closest('#id_applet_menu_cancel') || !menu.contains(e.target)) { + menu.style.display = 'none'; + } + }); }; \ No newline at end of file diff --git a/src/apps/dashboard/tests/integrated/test_views.py b/src/apps/dashboard/tests/integrated/test_views.py index ebdddee..4417e96 100644 --- a/src/apps/dashboard/tests/integrated/test_views.py +++ b/src/apps/dashboard/tests/integrated/test_views.py @@ -313,7 +313,8 @@ class SetPaletteTest(TestCase): response = self.client.get(self.url) parsed = lxml.html.fromstring(response.content) locked = parsed.cssselect(".swatch.locked") - self.assertEqual(len(locked), 2) + expected_locked = [p for p in response.context["palettes"] if p["locked"]] + self.assertEqual(len(locked), len(expected_locked)) # they mustn't be button els for swatch in locked: self.assertNotEqual(swatch.tag, "button") diff --git a/src/apps/dashboard/views.py b/src/apps/dashboard/views.py index 3ef1a65..3bf27ab 100644 --- a/src/apps/dashboard/views.py +++ b/src/apps/dashboard/views.py @@ -15,6 +15,9 @@ PALETTES = [ {"name": "palette-default", "label": "Earthman", "locked": False}, {"name": "palette-nirvana", "label": "Nirvana", "locked": True}, {"name": "palette-sheol", "label": "Sheol", "locked": True}, + {"name": "palette-inferno", "label": "Inferno", "locked": True}, + {"name": "palette-terrestre", "label": "Terrestre", "locked": True}, + {"name": "palette-celestia", "label": "Celestia", "locked": True}, ] diff --git a/src/functional_tests/test_dashboard.py b/src/functional_tests/test_dashboard.py index e80091c..5f40159 100644 --- a/src/functional_tests/test_dashboard.py +++ b/src/functional_tests/test_dashboard.py @@ -9,6 +9,7 @@ from apps.dashboard.models import Applet class DashboardMaintenanceTest(FunctionalTest): def setUp(self): super().setUp() + Applet.objects.get_or_create(slug="new-list", defaults={"name": "New List"}) Applet.objects.get_or_create(slug="username", defaults={"name": "Username"}) Applet.objects.get_or_create(slug="palette", defaults={"name": "Palette"}) @@ -114,3 +115,46 @@ class DashboardMaintenanceTest(FunctionalTest): ) ) self.assertTrue(self.browser.execute_script("return window.__no_reload_marker === true")) + +class AppletMenuDismissTest(FunctionalTest): + def setUp(self): + super().setUp() + Applet.objects.get_or_create(slug="username", defaults={"name": "Username"}) + Applet.objects.get_or_create(slug="palette", defaults={"name": "Palette"}) + self.create_pre_authenticated_session("discoman@example.com") + self.browser.get(self.live_server_url) + + def _open_menu(self): + self.browser.find_element(By.ID, "id_dash_gear").click() + self.wait_for( + lambda: self.assertTrue( + self.browser.find_element(By.ID, "id_applet_menu").is_displayed() + ) + ) + + def test_gear_click_toggles_menu_closed(self): + self._open_menu() + self.browser.find_element(By.ID, "id_dash_gear").click() + self.wait_for( + lambda: self.assertFalse( + self.browser.find_element(By.ID, "id_applet_menu").is_displayed() + ) + ) + + def test_nvm_btn_closes_menu(self): + self._open_menu() + self.browser.find_element(By.ID, "id_applet_menu_cancel").click() + self.wait_for( + lambda: self.assertFalse( + self.browser.find_element(By.ID, "id_applet_menu").is_displayed() + ) + ) + + def test_click_outside_closes_menu(self): + self._open_menu() + self.browser.find_element(By.TAG_NAME, "h2").click() + self.wait_for( + lambda: self.assertFalse( + self.browser.find_element(By.ID, "id_applet_menu").is_displayed() + ) + ) diff --git a/src/static_src/scss/_base.scss b/src/static_src/scss/_base.scss index d23e2fc..257e25d 100644 --- a/src/static_src/scss/_base.scss +++ b/src/static_src/scss/_base.scss @@ -26,24 +26,40 @@ body { padding: 0.75rem 0; border-bottom: 0.1rem solid rgba(var(--secUser), 0.4); - .container-fluid { - display: flex; - align-items: center; - gap: 1rem; - } - .navbar-brand { - margin-right: auto; - h1 { font-size: 2rem; } } + + .container-fluid { + display: flex; + align-items: center; + gap: 1rem; + + > form { flex-shrink: 0; margin-left: auto; } + } .navbar-text, .navbar-link { - color: rgba(var(--quaUser), 1); - font-size: 0.875rem; + flex: 1; + min-width: 0; + text-align: center; + + .navbar-label { + display: block; + color: rgba(var(--secUser), 0.7); + font-size: 0.75rem; + } + + .navbar-identity { + display: block; + color: rgba(var(--quaUser), 1); + font-size: 0.875rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } } @@ -133,4 +149,18 @@ body { } } } +} + +#id_footer { + flex-shrink: 0; + height: 3rem; + display: flex; + align-items: center; + justify-content: center; + border-top: 0.1rem solid rgba(var(--secUser), 0.3); + background: linear-gradient( + to top, + rgba(var(--priUser), 1) 25%, + transparent 100% + ); } \ No newline at end of file diff --git a/src/static_src/scss/_button-pad.scss b/src/static_src/scss/_button-pad.scss index 5a6dc2d..b3548e1 100644 --- a/src/static_src/scss/_button-pad.scss +++ b/src/static_src/scss/_button-pad.scss @@ -268,6 +268,7 @@ &.btn-disabled { cursor: default !important; font-size: 1.2rem; + padding-bottom: 0.1rem; color: rgba(var(--secUser), 0.25); background-color: rgba(var(--priUser), 1); border-color: rgba(var(--secUser), 0.25); diff --git a/src/static_src/scss/_dashboard.scss b/src/static_src/scss/_dashboard.scss index f4c5386..514ad88 100644 --- a/src/static_src/scss/_dashboard.scss +++ b/src/static_src/scss/_dashboard.scss @@ -1,8 +1,16 @@ +html:has(body.page-dashboard) { + overflow: hidden; +} + body.page-dashboard { overflow: hidden; .container { overflow: hidden; + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; } .row { @@ -12,6 +20,7 @@ body.page-dashboard { #id_dash_content { flex: 1; + min-width: 425px; overflow: hidden; display: flex; flex-direction: column; @@ -20,72 +29,117 @@ body.page-dashboard { #id_dash_gear { position: absolute; - top: 0.5rem; + bottom: 0.5rem; right: 0.5rem; + padding-bottom: 0.2rem; z-index: 1; background: none; border: none; - font-size: 1.5rem; + font-size: 2rem; cursor: pointer; - color: rgba(var(--secUser), 0.6); + color: rgba(var(--secUser), 1); display: inline-flex; align-items: center; justify-content: center; - width: 2rem; - height: 2rem; + width: 3rem; + height: 3rem; border-radius: 50%; - background-color: rgba(var(--priUser), 0.85); - border: 0.15rem solid rgba(var(--secUser), 0.15); + background-color: rgba(var(--priUser), 1); + border: 0.15rem solid rgba(var(--secUser), 1); } #id_applet_menu { position: absolute; - top: 3rem; + bottom: 3rem; right: 0.5rem; z-index: 100; background-color: rgba(var(--priUser), 0.95); border: 0.15rem solid rgba(var(--secUser), 0.5); border-radius: 0.75rem; padding: 1rem; + + .menu-btns { + display: flex; + gap: 0.25rem; + margin-top: 0.75rem; + } } #id_applets_container { container-type: inline-size; --grid-gap: 0.5rem; - flex: 1; overflow-y: auto; + overflow-x: hidden; display: grid; grid-template-columns: repeat(12, 1fr); grid-auto-rows: 3rem; gap: var(--grid-gap); padding: 0.75rem; + -webkit-overflow-scrolling: touch; + mask-image: linear-gradient( + to bottom, + transparent 0%, + black 2%, + black 98%, + transparent 100% + ); section { border: 0.2rem solid rgba(var(--secUser), 0.5); border-radius: 0.75rem; padding: 1rem; overflow: hidden; + min-width: 0; grid-column: span var(--applet-cols, 12); grid-row: span var(--applet-rows, 3); } + #id_applet_my_lists { + padding: 1.25rem 1.5rem; + display: flex; + flex-direction: column; + + .my-lists-main { + font-size: 1.6rem; + } + + .my-lists-container { + flex: 1; + min-height: 0; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + &::-webkit-scrollbar { display: none; } + mask-origin: padding-box; + mask-clip: padding-box; + mask-image: linear-gradient( + to bottom, + transparent 0%, + black 5%, + black 85%, + transparent 100% + ); + } + + } + #id_applet_palette { padding: 0; .palette-scroll { display: flex; - gap: 0.5rem; + gap: 3.5rem; overflow-x: auto; - padding: 1rem; + padding: 0.75rem 2rem; height: 100%; scrollbar-width: none; &::-webkit-scrollbar { display: none; } mask-image: linear-gradient( to right, transparent 0%, - black 8%, - black 92%, + black 2%, + black 98%, transparent 100% ); } @@ -94,15 +148,34 @@ body.page-dashboard { #id_applet_username { display: flex; align-items: center; + overflow: hidden; + + form { + min-width: 0; + width: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + + .save-btn { + align-self: left; + } + } .username-field { display: flex; align-items: baseline; gap: 0.1em; + min-width: 0; + overflow: hidden; .username-at{ user-select: none; - pointer-events: none; + pointer-events: none; + font-size: 1.8rem; + font-weight: bold; + color: rgba(var(--secUser), 0.875); + margin-left: 0.3rem; } input { @@ -114,13 +187,24 @@ body.page-dashboard { color: rgba(var(--secUser), 0.875);; font-family: inherit; padding: 0; - width: auto; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; } } } - @container (max-width: 480px) { +} + +@media (max-width: 550px) { + #id_dash_content { + min-width: 380px; + overflow: hidden; + } + + #id_applets_container { section { grid-column: span 12; } diff --git a/src/static_src/scss/_palette-picker.scss b/src/static_src/scss/_palette-picker.scss index ce6d866..84e5d75 100644 --- a/src/static_src/scss/_palette-picker.scss +++ b/src/static_src/scss/_palette-picker.scss @@ -15,12 +15,14 @@ flex-direction: column; align-items: center; gap: 0.5rem; - flex: 0 0 calc(50% - 0.375rem); + flex: 0 0 auto; + height: 100%; scroll-snap-align: start; } .swatch { - width: 100%; + flex: 1; + min-height: 0; aspect-ratio: 1; border-radius: 0.5rem; background: linear-gradient( diff --git a/src/templates/apps/dashboard/_partials/_applets.html b/src/templates/apps/dashboard/_partials/_applets.html index 5642c8b..276b1dd 100644 --- a/src/templates/apps/dashboard/_partials/_applets.html +++ b/src/templates/apps/dashboard/_partials/_applets.html @@ -18,7 +18,10 @@ {{ entry.applet.name }} {% endfor %} - +
@@ -38,16 +41,18 @@ id="id_applet_my_lists" style="--applet-cols: {{ entry.applet.grid_cols }}; --applet-rows: {{ entry.applet.grid_rows }};" > - My lists: -