move fullscreen icon to right of preview pane; use SVG icon Hunter Wednesday, July 16th, 2025 at 1:13 PM
-name: Generate Image Manifest 1 +name: Generate Resource Manifest 2 3 on: 4 push: 5 branches: [ main ] - paths: [ 'images/**' ] # Only run when images are added/changed 6 + paths: [ 'images/**', 'resources/**' ] # Run when images or resources are added/changed 7 workflow_dispatch: # Allow manual trigger 8 9 jobs: 23 with: 24 python-version: '3.x' 25 - - name: Generate image manifest - run: python generate_image_manifest.py 26 + - name: Generate resource manifest 27 + run: python generate_resource_manifest.py 28 29 - name: Check for changes 30 id: verify-changed-files 40 run: | 41 git config --local user.email "action@github.com" 42 git config --local user.name "GitHub Action" - git add image-manifest.json - git commit -m "Auto-update image manifest [skip ci]" 43 + git add resource-manifest.json 44 + git commit -m "Auto-update resource manifest [skip ci]" 45 git push
-#!/usr/bin/env python3 -""" -Generate image manifest for PWA caching -Scans the images/ directory and creates a JSON manifest file -""" - -import os -import json -from pathlib import Path - -def generate_image_manifest(): - """Generate a JSON manifest of all images in the images/ directory""" - - # Define supported image extensions - image_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.bmp'} - - # Get the directory where this script is located - script_dir = Path(__file__).parent - images_dir = script_dir / 'images' - - # Check if images directory exists - if not images_dir.exists(): - print(f"Images directory not found: {images_dir}") - return - - # Collect all image files - image_files = [] - - for file_path in images_dir.iterdir(): - if file_path.is_file() and file_path.suffix.lower() in image_extensions: - # Create relative path from web root (GitHub Pages subdirectory) - relative_path = f"/web_workshop/images/{file_path.name}" - image_files.append(relative_path) - - # Sort for consistent output - image_files.sort() - - # Create manifest object - manifest = { - "images": image_files, - "generated_at": "auto-generated by GitHub Actions", - "total_images": len(image_files) - } - - # Write manifest file - manifest_path = script_dir / 'image-manifest.json' - with open(manifest_path, 'w', encoding='utf-8') as f: - json.dump(manifest, f, indent=2, sort_keys=True) - - print(f"Generated manifest with {len(image_files)} images:") - for img in image_files: - print(f" - {img}") - - return manifest - -if __name__ == "__main__": - manifest = generate_image_manifest() - print(f"\nManifest saved to: image-manifest.json")
1 +#!/usr/bin/env python3 2 +""" 3 +Generate resource manifest for PWA caching 4 +Scans the images/ and resources/ directories and creates a JSON manifest file 5 +""" 6 + 7 +import os 8 +import json 9 +from pathlib import Path 10 + 11 +def generate_resource_manifest(): 12 + """Generate a JSON manifest of all resources in the images/ and resources/ directories""" 13 + 14 + # Define supported image extensions 15 + image_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.bmp'} 16 + 17 + # Get the directory where this script is located 18 + script_dir = Path(__file__).parent 19 + images_dir = script_dir / 'images' 20 + resources_dir = script_dir / 'resources' 21 + 22 + # Collect all image files 23 + image_files = [] 24 + if images_dir.exists(): 25 + for file_path in images_dir.iterdir(): 26 + if file_path.is_file() and file_path.suffix.lower() in image_extensions: 27 + # Create relative path from web root (GitHub Pages subdirectory) 28 + relative_path = f"/web_workshop/images/{file_path.name}" 29 + image_files.append(relative_path) 30 + 31 + # Collect ALL resource files (no extension filtering) 32 + resource_files = [] 33 + if resources_dir.exists(): 34 + for file_path in resources_dir.rglob('*'): 35 + if file_path.is_file(): 36 + # Create relative path from web root (GitHub Pages subdirectory) 37 + relative_path = f"/web_workshop/resources/{file_path.relative_to(resources_dir).as_posix()}" 38 + resource_files.append(relative_path) 39 + 40 + # Sort for consistent output 41 + image_files.sort() 42 + resource_files.sort() 43 + 44 + # Create manifest object 45 + manifest = { 46 + "images": image_files, 47 + "resources": resource_files, 48 + "generated_at": "auto-generated by GitHub Actions", 49 + "total_images": len(image_files), 50 + "total_resources": len(resource_files), 51 + "total_files": len(image_files) + len(resource_files) 52 + } 53 + 54 + # Write manifest file 55 + manifest_path = script_dir / 'resource-manifest.json' 56 + with open(manifest_path, 'w', encoding='utf-8') as f: 57 + json.dump(manifest, f, indent=2, sort_keys=True) 58 + 59 + print(f"Generated manifest with {len(image_files)} images and {len(resource_files)} resources:") 60 + for img in image_files: 61 + print(f" - {img}") 62 + for res in resource_files: 63 + print(f" - {res}") 64 + 65 + return manifest 66 + 67 +if __name__ == "__main__": 68 + manifest = generate_resource_manifest() 69 + print(f"\nManifest saved to: resource-manifest.json")
-{ - "generated_at": "auto-generated by GitHub Actions", - "images": [ - "/web_workshop/images/bigN.gif", - "/web_workshop/images/bright_idea.gif", - "/web_workshop/images/calamar.gif", - "/web_workshop/images/cd.gif", - "/web_workshop/images/email.gif", - "/web_workshop/images/gaia.gif", - "/web_workshop/images/hint.gif", - "/web_workshop/images/mac.png", - "/web_workshop/images/mario64.gif", - "/web_workshop/images/pastel_square.png", - "/web_workshop/images/pika_construction.gif", - "/web_workshop/images/pink_clock.gif", - "/web_workshop/images/rollerskate.png", - "/web_workshop/images/scared_mouse.gif", - "/web_workshop/images/smiley_dancing.gif", - "/web_workshop/images/smiling_pizza.gif", - "/web_workshop/images/spinninbrain.gif", - "/web_workshop/images/splat.png", - "/web_workshop/images/sun.png", - "/web_workshop/images/underconstruction.gif", - "/web_workshop/images/welcome.gif", - "/web_workshop/images/windows.gif" - ], - "total_images": 22 -}
23 --editor-bg: #16181b; 24 } 25 - 26 * { 27 margin: 0; 28 padding: 0; 53 .fullscreen-toggle { 54 position: absolute; 55 top: 5px; - left: 5px; - z-index: 1000; 56 + right: 5px; 57 + z-index: 10000; 58 background: rgba(0, 0, 0, 0.2); 59 color: white; 60 border: none; 61 border-radius: 4px; - padding: 5px 8px; 62 + width: 32px; 63 + height: 32px; 64 + display: flex; 65 + align-items: center; 66 + justify-content: center; 67 cursor: pointer; - font-size: 16px; 68 transition: background-color 0.2s; 69 + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); 70 + } 71 + 72 + .fullscreen-toggle.has-scrollbar { 73 + right: calc(5px + env(scrollbar-width, 15px)); 74 } 75 76 .fullscreen-toggle:hover { - background: rgba(0, 0, 0, 0.5); 77 + background: rgba(0, 0, 0, 0.35); 78 + } 79 + 80 + .fullscreen-toggle img { 81 + filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.3)); 82 + opacity: 0.8; 83 + transition: opacity 0.2s; 84 + } 85 + 86 + .fullscreen-toggle:hover img { 87 + opacity: 1; 88 } 89 90 .preview-pane.fullscreen { 97 background: white; 98 } 99 - .preview-pane.fullscreen .fullscreen-toggle { - top: 20px; - left: 20px; - } - 100 /* Hide editor when preview is fullscreen */ 101 .editor-pane.hidden { 102 display: none; 183 </div> 184 185 <div class="preview-pane"> - <button class="fullscreen-toggle" id="fullscreenToggle" title="Toggle fullscreen">⛶</button> 186 + <button class="fullscreen-toggle" id="fullscreenToggle" title="Toggle fullscreen"> 187 + <img src="resources/fullscreen.svg" width="20" height="20" alt="Fullscreen"> 188 + </button> 189 <iframe id="preview"></iframe> 190 </div> 191 206 if (isFullscreen) { 207 previewPane.classList.add('fullscreen'); 208 editorPane.classList.add('hidden'); - toggleButton.textContent = '⛶'; 209 toggleButton.title = 'Exit fullscreen'; 210 } else { 211 previewPane.classList.remove('fullscreen'); 212 editorPane.classList.remove('hidden'); - toggleButton.textContent = '⛶'; 213 toggleButton.title = 'Toggle fullscreen'; 214 } 215 } 219 toggleButton.addEventListener('click', toggleFullscreen); 220 }); 221 222 + function updateScrollbarDetection() { 223 + const toggleButton = document.getElementById('fullscreenToggle'); 224 + try { 225 + if (preview.contentDocument && preview.contentDocument.body) { 226 + const body = preview.contentDocument.body; 227 + const hasScrollbar = body.scrollHeight > preview.clientHeight; 228 + toggleButton.classList.toggle('has-scrollbar', hasScrollbar); 229 + } 230 + } catch (e) { 231 + // Ignore cross-origin errors 232 + } 233 + } 234 + 235 function updatePreview() { 236 const code = editorView.state.doc.toString(); 237 269 } catch (e) { 270 // Ignore cross-origin errors 271 } 272 + // Check for scrollbar after content is rendered 273 + updateScrollbarDetection(); 274 }, 10); 275 } 276
1 +{ 2 + "generated_at": "auto-generated by GitHub Actions", 3 + "images": [ 4 + "/web_workshop/images/bigN.gif", 5 + "/web_workshop/images/bright_idea.gif", 6 + "/web_workshop/images/calamar.gif", 7 + "/web_workshop/images/cd.gif", 8 + "/web_workshop/images/email.gif", 9 + "/web_workshop/images/gaia.gif", 10 + "/web_workshop/images/hint.gif", 11 + "/web_workshop/images/mac.png", 12 + "/web_workshop/images/mario64.gif", 13 + "/web_workshop/images/pastel_square.png", 14 + "/web_workshop/images/pika_construction.gif", 15 + "/web_workshop/images/pink_clock.gif", 16 + "/web_workshop/images/rollerskate.png", 17 + "/web_workshop/images/scared_mouse.gif", 18 + "/web_workshop/images/smiley_dancing.gif", 19 + "/web_workshop/images/smiling_pizza.gif", 20 + "/web_workshop/images/spinninbrain.gif", 21 + "/web_workshop/images/splat.png", 22 + "/web_workshop/images/sun.png", 23 + "/web_workshop/images/underconstruction.gif", 24 + "/web_workshop/images/welcome.gif", 25 + "/web_workshop/images/windows.gif" 26 + ], 27 + "resources": [ 28 + "/web_workshop/resources/fullscreen.svg" 29 + ], 30 + "total_files": 23, 31 + "total_images": 22, 32 + "total_resources": 1 33 +}
1 +<?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 +<svg 3 + width="14" 4 + height="14" 5 + viewBox="0 0 14 14" 6 + version="1.1" 7 + id="svg11" 8 + sodipodi:docname="fullscreen.svg" 9 + inkscape:version="1.2.1 (9c6d41e, 2022-07-14)" 10 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 11 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 12 + xmlns="http://www.w3.org/2000/svg" 13 + xmlns:svg="http://www.w3.org/2000/svg"> 14 + <defs 15 + id="defs15" /> 16 + <sodipodi:namedview 17 + id="namedview13" 18 + pagecolor="#ffffff" 19 + bordercolor="#000000" 20 + borderopacity="0.25" 21 + inkscape:showpageshadow="2" 22 + inkscape:pageopacity="0.0" 23 + inkscape:pagecheckerboard="0" 24 + inkscape:deskcolor="#d1d1d1" 25 + showgrid="false" 26 + inkscape:zoom="9.8333333" 27 + inkscape:cx="-9.1525424" 28 + inkscape:cy="7.0169492" 29 + inkscape:window-width="1309" 30 + inkscape:window-height="404" 31 + inkscape:window-x="0" 32 + inkscape:window-y="38" 33 + inkscape:window-maximized="0" 34 + inkscape:current-layer="svg11" /> 35 + <path 36 + d="M 7,14 H 5 v 5 h 5 V 17 H 7 Z M 5,10 H 7 V 7 h 3 V 5 H 5 Z m 12,7 h -3 v 2 h 5 V 14 H 17 Z M 14,5 v 2 h 3 v 3 h 2 V 5 Z" 37 + id="path9" 38 + style="fill:#ffffff" 39 + transform="translate(-5,-5)" /> 40 +</svg>
-const CACHE_NAME = 'web-workshop-v7'; 1 +const CACHE_NAME = 'web-workshop-v8'; 2 const urlsToCache = [ 3 '/web_workshop/', 4 '/web_workshop/index.html', 45 } 46 } 47 -// Function to cache all images using the generated manifest -async function cacheImages(cache) { 48 +// Function to cache all resources using the generated manifest 49 +async function cacheResources(cache) { 50 try { - const response = await fetch('/web_workshop/image-manifest.json'); 51 + const response = await fetch('/web_workshop/resource-manifest.json'); 52 if (response.ok) { 53 const manifest = await response.json(); - console.log(`Caching ${manifest.total_images} images from manifest`); 54 + console.log(`Caching ${manifest.total_images} images and ${manifest.total_resources} resources from manifest`); 55 56 + // Cache images 57 for (const imagePath of manifest.images) { 58 try { 59 await cache.add(imagePath); 62 console.log(`Failed to cache image ${imagePath}:`, e); 63 } 64 } 65 + 66 + // Cache resources 67 + for (const resourcePath of manifest.resources) { 68 + try { 69 + await cache.add(resourcePath); 70 + console.log(`Cached resource: ${resourcePath}`); 71 + } catch (e) { 72 + console.log(`Failed to cache resource ${resourcePath}:`, e); 73 + } 74 + } 75 } else { - console.log('No image manifest found, skipping image caching'); 76 + console.log('No resource manifest found, skipping resource caching'); 77 } 78 } catch (e) { - console.log('Failed to load image manifest:', e); 79 + console.log('Failed to load resource manifest:', e); 80 } 81 } 82 99 } 100 } 101 - // Cache all images programmatically - await cacheImages(cache); 102 + // Cache all resources programmatically 103 + await cacheResources(cache); 104 }) 105 .then(() => self.skipWaiting()) 106 );