mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-12-02 15:39:57 +00:00
feat(web-app-littlejs): add JS submenu support, left-expand menus, improve headline & cleanup examples
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
This commit is contained in:
@@ -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 }}"
|
||||
|
||||
35
roles/web-app-littlejs/files/javascript.js
Normal file
35
roles/web-app-littlejs/files/javascript.js
Normal file
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<main class="container py-4">
|
||||
<header class="mb-4">
|
||||
<h1 class="h3 mb-2">LittleJS Examples & Games</h1>
|
||||
<h1 class="h3 mb-2">{{ LITTLEJS_HEADLINE }}</h1>
|
||||
<p class="text-secondary small mb-0">
|
||||
Browse and launch LittleJS demos rendered dynamically through Infinito.Nexus.
|
||||
Browse and launch LittleJS demos rendered dynamically through {{ SOFTWARE_NAME }}.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
<div class="mt-auto">
|
||||
<a href="{{ href }}" class="btn btn-primary app-footer-btn">
|
||||
<i class="{{ ex.icon }} me-2"></i>Play {{ ex.name }}
|
||||
<i class="{{ ex.icon }} me-2"></i>Start {{ ex.name }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,17 +20,16 @@
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end">
|
||||
|
||||
{% for key, cat in littlejs_examples|dictsort %}
|
||||
|
||||
<li class="dropdown-submenu dropstart">
|
||||
<a class="dropdown-item dropdown-toggle d-flex align-items-center" href="#" data-bs-toggle="dropdown">
|
||||
<li class="dropdown-submenu">
|
||||
<a class="dropdown-item dropdown-toggle d-flex align-items-center"
|
||||
href="#">
|
||||
<i class="{{ cat.icon }} me-2"></i>
|
||||
{{ cat.label|replace('---','')|trim }}
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu dropdown-menu-dark">
|
||||
|
||||
{% for ex in cat['items'] %}
|
||||
{% set href = ex | littlejs_href(WEB_PROTOCOL, domain) %}
|
||||
<li>
|
||||
@@ -39,14 +38,12 @@
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
{% if not loop.last %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user