From 6fda85788a661bc4b3358ef51c9006e825bb3eea Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Thu, 27 Nov 2025 21:30:15 +0100 Subject: [PATCH] feat(web-app-littlejs): add JS submenu support, left-expand menus, improve headline & cleanup examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This update introduces full JavaScript-based nested submenu handling for the Apps menu, enabling reliable click-based toggling without interference from Bootstrap’s native dropdown logic. Submenus now expand to the left via custom CSS and no longer require dropstart or data-bs-toggle attributes. Changes include: - Add javascript feature flag and enable inline eval in CSP - Add javascript.js implementing custom submenu toggle logic - Add CSS rules for left-expanding nested dropdown menus - Replace hardcoded headline with LITTLEJS_HEADLINE variable - Modernize “Play” → “Start” labels in cards - Remove unused/legacy examples from examples.yml (commented out, not deleted) - Cleanup nav_top.html.j2 to remove conflicting Bootstrap attributes Conversation reference: https://chatgpt.com/share/6928b4c7-19ec-800f-a087-9af304ef4ed9 --- roles/web-app-littlejs/config/main.yml | 11 +- roles/web-app-littlejs/files/javascript.js | 35 +++ roles/web-app-littlejs/files/style.css | 12 + .../templates/html/main.html.j2 | 6 +- .../templates/html/nav_top.html.j2 | 9 +- roles/web-app-littlejs/vars/examples.yml | 259 +++++++++--------- 6 files changed, 186 insertions(+), 146 deletions(-) create mode 100644 roles/web-app-littlejs/files/javascript.js diff --git a/roles/web-app-littlejs/config/main.yml b/roles/web-app-littlejs/config/main.yml index 9d6f2019..2aaa8afe 100644 --- a/roles/web-app-littlejs/config/main.yml +++ b/roles/web-app-littlejs/config/main.yml @@ -1,8 +1,9 @@ features: - css: true - matomo: true - desktop: true - logout: false + css: true + matomo: true + desktop: true + logout: false + javascript: true server: csp: @@ -25,8 +26,10 @@ server: unsafe-inline: true script-src-elem: unsafe-inline: true + unsafe-eval: true script-src-attr: unsafe-inline: true + unsafe-eval: true domains: canonical: - "littlejs.{{ PRIMARY_DOMAIN }}" diff --git a/roles/web-app-littlejs/files/javascript.js b/roles/web-app-littlejs/files/javascript.js new file mode 100644 index 00000000..1e950004 --- /dev/null +++ b/roles/web-app-littlejs/files/javascript.js @@ -0,0 +1,35 @@ +document.addEventListener('DOMContentLoaded', function () { + // Toggle second-level menus on click + const submenuTriggers = document.querySelectorAll('.dropdown-submenu > a.dropdown-toggle'); + + submenuTriggers.forEach(function (trigger) { + trigger.addEventListener('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + + const submenu = this.nextElementSibling; + if (!submenu) return; + + // Close other open submenus on the same level + const parentMenu = this.closest('.dropdown-menu'); + if (parentMenu) { + parentMenu.querySelectorAll('.dropdown-menu.show').forEach(function (menu) { + if (menu !== submenu) { + menu.classList.remove('show'); + } + }); + } + + submenu.classList.toggle('show'); + }); + }); + + // When the main Apps dropdown closes, close all submenus + document.querySelectorAll('.navbar .dropdown').forEach(function (dropdown) { + dropdown.addEventListener('hide.bs.dropdown', function () { + this.querySelectorAll('.dropdown-menu.show').forEach(function (menu) { + menu.classList.remove('show'); + }); + }); + }); +}); diff --git a/roles/web-app-littlejs/files/style.css b/roles/web-app-littlejs/files/style.css index 2d91c01b..729a9935 100644 --- a/roles/web-app-littlejs/files/style.css +++ b/roles/web-app-littlejs/files/style.css @@ -53,3 +53,15 @@ body { .footer-bar .btn-link:hover { text-decoration: underline; } + +/* Nested dropdown - open to the left */ +.dropdown-menu .dropdown-submenu { + position: relative; +} + +.dropdown-menu .dropdown-submenu > .dropdown-menu { + top: 0; + right: 100%; /* statt left:100%; */ + left: auto; /* überschreibt Bootstrap default */ + margin-right: .1rem; +} diff --git a/roles/web-app-littlejs/templates/html/main.html.j2 b/roles/web-app-littlejs/templates/html/main.html.j2 index cfb37901..97513ada 100644 --- a/roles/web-app-littlejs/templates/html/main.html.j2 +++ b/roles/web-app-littlejs/templates/html/main.html.j2 @@ -1,8 +1,8 @@
-

LittleJS Examples & Games

+

{{ LITTLEJS_HEADLINE }}

- Browse and launch LittleJS demos rendered dynamically through Infinito.Nexus. + Browse and launch LittleJS demos rendered dynamically through {{ SOFTWARE_NAME }}.

@@ -37,7 +37,7 @@ diff --git a/roles/web-app-littlejs/templates/html/nav_top.html.j2 b/roles/web-app-littlejs/templates/html/nav_top.html.j2 index a2a91d93..4e6681a5 100644 --- a/roles/web-app-littlejs/templates/html/nav_top.html.j2 +++ b/roles/web-app-littlejs/templates/html/nav_top.html.j2 @@ -20,17 +20,16 @@ diff --git a/roles/web-app-littlejs/vars/examples.yml b/roles/web-app-littlejs/vars/examples.yml index 9c914e13..c2e9b283 100644 --- a/roles/web-app-littlejs/vars/examples.yml +++ b/roles/web-app-littlejs/vars/examples.yml @@ -6,26 +6,26 @@ littlejs_examples: label: "--- BASIC ---" icon: "fa-solid fa-shapes" items: - - name: "Hello World" - file: "helloWorld.js" - description: "Simple starter example" - is_project: false - tags: "beginner, gradient, text, tiles" - icon: "fa-solid fa-seedling" + # - name: "Hello World" + # file: "helloWorld.js" + # description: "Simple starter example" + # is_project: false + # tags: "beginner, gradient, text, tiles" + # icon: "fa-solid fa-seedling" - - name: "Empty" - file: "empty.js" - description: "Empty example template" - is_project: false - tags: "beginner, text" - icon: "fa-regular fa-file" + # - name: "Empty" + # file: "empty.js" + # description: "Empty example template" + # is_project: false + # tags: "beginner, text" + # icon: "fa-regular fa-file" - - name: "Texture" - file: "texture.js" - description: "Texture display and manipulation" - is_project: false - tags: "sprites, loading, tiles" - icon: "fa-regular fa-image" + # - name: "Texture" + # file: "texture.js" + # description: "Texture display and manipulation" + # is_project: false + # tags: "sprites, loading, tiles" + # icon: "fa-regular fa-image" - name: "Animation" file: "animation.js" @@ -48,12 +48,12 @@ littlejs_examples: tags: "hue, saturation, blending, rectangle" icon: "fa-solid fa-palette" - - name: "Sprite Atlas" - file: "spriteAtlas.js" - description: "Sprite atlas and tile rendering" - is_project: false - tags: "sheet, frames, tiles" - icon: "fa-solid fa-table-cells-large" + # - name: "Sprite Atlas" + # file: "spriteAtlas.js" + # description: "Sprite atlas and tile rendering" + # is_project: false + # tags: "sheet, frames, tiles" + # icon: "fa-solid fa-table-cells-large" - name: "Blending" file: "blending.js" @@ -76,19 +76,19 @@ littlejs_examples: tags: "effects, emitter, physics, fire, smoke" icon: "fa-solid fa-burst" - - name: "Input" - file: "input.js" - description: "Input system demo for keyboard, mouse, touch, and gamepad." - is_project: false - tags: "input, control" - icon: "fa-regular fa-keyboard" + # - name: "Input" + # file: "input.js" + # description: "Input system demo for keyboard, mouse, touch, and gamepad." + # is_project: false + # tags: "input, control" + # icon: "fa-regular fa-keyboard" - - name: "Timers" - file: "timers.js" - description: "Timer objects and UI" - is_project: false - tags: "delay, interval, slider" - icon: "fa-regular fa-clock" + # - name: "Timers" + # file: "timers.js" + # description: "Timer objects and UI" + # is_project: false + # tags: "delay, interval, slider" + # icon: "fa-regular fa-clock" - name: "Sound Effects" file: "sound.js" @@ -104,33 +104,33 @@ littlejs_examples: tags: "music, sound, audio, streaming, volume, ui" icon: "fa-solid fa-headphones-simple" - - name: "Video" - file: "videoPlayer.js" - description: "Basic video play, pause, and stop" - is_project: false - tags: "movie, sound, audio, streaming, volume, ui" - icon: "fa-solid fa-video" + # - name: "Video" + # file: "videoPlayer.js" + # description: "Basic video play, pause, and stop" + # is_project: false + # tags: "movie, sound, audio, streaming, volume, ui" + # icon: "fa-solid fa-video" - - name: "Font Image" - file: "fontImage.js" - description: "Bitmap font system with built-in system font" - is_project: false - tags: "text, characters" - icon: "fa-solid fa-font" + # - name: "Font Image" + # file: "fontImage.js" + # description: "Bitmap font system with built-in system font" + # is_project: false + # tags: "text, characters" + # icon: "fa-solid fa-font" - - name: "Medals" - file: "medals.js" - description: "Achievement system" - is_project: false - tags: "unlock, progress, newgrounds" - icon: "fa-solid fa-medal" + # - name: "Medals" + # file: "medals.js" + # description: "Achievement system" + # is_project: false + # tags: "unlock, progress, newgrounds" + # icon: "fa-solid fa-medal" - - name: "Tile Raycast" - file: "tileRaycast.js" - description: "Example of the tile layer raycast collision" - is_project: false - tags: "level, map, grid" - icon: "fa-solid fa-crosshairs" + # - name: "Tile Raycast" + # file: "tileRaycast.js" + # description: "Example of the tile layer raycast collision" + # is_project: false + # tags: "level, map, grid" + # icon: "fa-solid fa-crosshairs" - name: "Camera Mouse Drag" file: "cameraDrag.js" @@ -139,12 +139,12 @@ littlejs_examples: tags: "ui, input, control" icon: "fa-solid fa-arrows-up-down-left-right" - - name: "Debug Drawing" - file: "debugDraw.js" - description: "Debug drawing system" - is_project: false - tags: "debug, circle, line, rectangle" - icon: "fa-solid fa-bug" + # - name: "Debug Drawing" + # file: "debugDraw.js" + # description: "Debug drawing system" + # is_project: false + # tags: "debug, circle, line, rectangle" + # icon: "fa-solid fa-bug" advanced: label: "--- ADVANCED ---" @@ -192,12 +192,12 @@ littlejs_examples: tags: "music, sound, audio, notes, ui, instrument" icon: "fa-solid fa-sliders" - - name: "Music Player" - file: "musicPlayer.js" - description: "Music player with audio seeking and drag and drop" - is_project: false - tags: "sound, loading, audio, ui" - icon: "fa-solid fa-compact-disc" + # - name: "Music Player" + # file: "musicPlayer.js" + # description: "Music player with audio seeking and drag and drop" + # is_project: false + # tags: "sound, loading, audio, ui" + # icon: "fa-solid fa-compact-disc" games: label: "--- GAMES ---" @@ -277,33 +277,33 @@ littlejs_examples: label: "--- PLUGINS ---" icon: "fa-solid fa-puzzle-piece" items: - - name: "Box2d Demo" - file: "box2d.js" - description: "Box2D physics plugin" - is_project: false - tags: "objects, mouse" - icon: "fa-solid fa-cubes" + # - name: "Box2d Demo" + # file: "box2d.js" + # description: "Box2D physics plugin" + # is_project: false + # tags: "objects, mouse" + # icon: "fa-solid fa-cubes" - - name: "Box2d Car" - file: "box2dCar.js" - description: "Drivable car with Box2D physics" - is_project: false - tags: "objects, vehicle, suspension, wheels" - icon: "fa-solid fa-car" + # - name: "Box2d Car" + # file: "box2dCar.js" + # description: "Drivable car with Box2D physics" + # is_project: false + # tags: "objects, vehicle, suspension, wheels" + # icon: "fa-solid fa-car" - - name: "Box2d Pool" - file: "box2dPool.js" - description: "Billiard table pool game with Box2d physics" - is_project: false - tags: "objects, game" - icon: "fa-solid fa-pool-8-ball" + # - name: "Box2d Pool" + # file: "box2dPool.js" + # description: "Billiard table pool game with Box2d physics" + # is_project: false + # tags: "objects, game" + # icon: "fa-solid fa-pool-8-ball" - - name: "Box2d Tile Layer" - file: "box2dTileLayer.js" - description: "Tile layer with Box2d physics" - is_project: false - tags: "objects, level, map, grid" - icon: "fa-solid fa-border-all" + # - name: "Box2d Tile Layer" + # file: "box2dTileLayer.js" + # description: "Tile layer with Box2d physics" + # is_project: false + # tags: "objects, level, map, grid" + # icon: "fa-solid fa-border-all" - name: "WebGL Shader" file: "shader.js" @@ -319,30 +319,30 @@ littlejs_examples: tags: "webgl, visual, effect" icon: "fa-solid fa-sparkles" - - name: "UI System" - file: "uiSystem.js" - description: "Buttons, sliders, and checkboxes" - is_project: false - tags: "objects, widgets, interactive" - icon: "fa-solid fa-sliders" + # - name: "UI System" + # file: "uiSystem.js" + # description: "Buttons, sliders, and checkboxes" + # is_project: false + # tags: "objects, widgets, interactive" + # icon: "fa-solid fa-sliders" - - name: "Nine Slice" - file: "nineSlice.js" - description: "Scalable UI panels" - is_project: false - tags: "three slice, stretch, corners, text, tiles" - icon: "fa-solid fa-vector-square" + # - name: "Nine Slice" + # file: "nineSlice.js" + # description: "Scalable UI panels" + # is_project: false + # tags: "three slice, stretch, corners, text, tiles" + # icon: "fa-solid fa-vector-square" full_examples: label: "--- FULL EXAMPLES ---" icon: "fa-solid fa-box-open" items: - - name: "Starter" - file: "starter" - description: "Clean project template" - is_project: true - tags: "base, empty, particles" - icon: "fa-solid fa-seedling" + # - name: "Starter" + # file: "starter" + # description: "Clean project template" + # is_project: true + # tags: "base, empty, particles" + # icon: "fa-solid fa-seedling" - name: "Breakout Tutorial" file: "breakoutTutorial" @@ -379,23 +379,16 @@ littlejs_examples: tags: "optimization, tiles, sprites" icon: "fa-solid fa-gauge-high" - - name: "Box2D Plugin" - file: "box2d" - description: "Full Box2D physics demo" - is_project: true - tags: "objects, bodies, joints" - icon: "fa-solid fa-cubes" + # - name: "HTML Menus" + # file: "htmlMenu" + # description: "HTML UI integration" + # is_project: true + # tags: "web, browser, overlay, button, slider, textbox" + # icon: "fa-solid fa-code" - - name: "HTML Menus" - file: "htmlMenu" - description: "HTML UI integration" - is_project: true - tags: "web, browser, overlay, button, slider, textbox" - icon: "fa-solid fa-code" - - - name: "UI System Plugin Demo" - file: "uiSystem" - description: "Complete UI system demo" - is_project: true - tags: "menu, overlay, button, slider, checkbox" - icon: "fa-solid fa-sliders" + # - name: "UI System Plugin Demo" + # file: "uiSystem" + # description: "Complete UI system demo" + # is_project: true + # tags: "menu, overlay, button, slider, checkbox" + # icon: "fa-solid fa-sliders"