diff options
author | elioat <elioat@tilde.institute> | 2025-03-30 10:13:06 -0400 |
---|---|---|
committer | elioat <elioat@tilde.institute> | 2025-03-30 10:13:06 -0400 |
commit | 1c8d453f75411144c724f39c73a75f7bcdd2efe7 (patch) | |
tree | c71933385408ad190d89b8f361f0bd38c789a624 | |
parent | 47a0aa9cc904fd5aca73ad589df1acd56d406682 (diff) | |
download | tour-1c8d453f75411144c724f39c73a75f7bcdd2efe7.tar.gz |
*
-rw-r--r-- | js/leibovitz/index.html | 29 | ||||
-rw-r--r-- | js/leibovitz/leibovitz.js | 84 | ||||
-rw-r--r-- | js/leibovitz/manifest.json | 39 | ||||
-rw-r--r-- | js/leibovitz/service-worker.js | 81 |
4 files changed, 215 insertions, 18 deletions
diff --git a/js/leibovitz/index.html b/js/leibovitz/index.html index 986485c..7a5ca55 100644 --- a/js/leibovitz/index.html +++ b/js/leibovitz/index.html @@ -218,6 +218,19 @@ padding: 10px; box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1); flex-shrink: 0; + position: relative; + } + #offline-status { + position: absolute; + top: 10px; + right: 10px; + font-size: 12px; + color: #666; + display: none; + font-family: 'ChicagoFLF', sans-serif; + } + #offline-status.visible { + display: block; } .top-controls { display: flex; @@ -423,6 +436,7 @@ </div> <div id="settings-container"> + <div id="offline-status">Offline Mode</div> <div class="top-controls"> <select id="dither-select"> <option value="none">No Dithering</option> @@ -451,5 +465,20 @@ <script src="blur.js"></script> <script src="balance.js"></script> <script src="leibovitz.js"></script> +<script> +// Add offline status handling +window.addEventListener('online', () => { + document.getElementById('offline-status').classList.remove('visible'); +}); + +window.addEventListener('offline', () => { + document.getElementById('offline-status').classList.add('visible'); +}); + +// Check initial online status +if (!navigator.onLine) { + document.getElementById('offline-status').classList.add('visible'); +} +</script> </body> </html> diff --git a/js/leibovitz/leibovitz.js b/js/leibovitz/leibovitz.js index cdf9ca4..45821ba 100644 --- a/js/leibovitz/leibovitz.js +++ b/js/leibovitz/leibovitz.js @@ -9,6 +9,7 @@ const focusControl = document.getElementById('focus-control'); const focusSlider = document.getElementById('focus-slider'); const focusValue = document.getElementById('focus-value'); const slideControls = document.querySelector('.slide-controls'); +const SETTINGS_KEY = 'leibovitz-settings'; let cameraOn = false; let stream = null; @@ -418,4 +419,85 @@ DitherManager.reset = function() { this._modeSelect.value = 'none'; this._notifyObservers(); resetEffects(); -}; \ No newline at end of file +}; + +function saveSettings() { + const settings = { + blur: BlurManager._currentBlur, + contrast: ContrastManager._currentContrast, + color: ColorManager._currentColor, + balance: BalanceManager._currentBalance, + dither: DitherManager._currentMode, + blockSize: DitherManager._currentBlockSize + }; + localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); +} + +function loadSettings() { + const savedSettings = localStorage.getItem(SETTINGS_KEY); + if (savedSettings) { + const settings = JSON.parse(savedSettings); + + // Apply saved settings + if (settings.blur !== undefined) { + BlurManager._currentBlur = settings.blur; + BlurManager._slider.value = settings.blur; + BlurManager._value.textContent = `${settings.blur}%`; + } + + if (settings.contrast !== undefined) { + ContrastManager._currentContrast = settings.contrast; + ContrastManager._slider.value = settings.contrast; + document.getElementById('contrast-value').textContent = settings.contrast; + } + + if (settings.color !== undefined) { + ColorManager._currentColor = settings.color; + ColorManager._colorInput.value = settings.color; + } + + if (settings.balance !== undefined) { + BalanceManager._currentBalance = settings.balance; + BalanceManager.balanceSlider.value = settings.balance; + BalanceManager.balanceValue.textContent = `${settings.balance}K`; + } + + if (settings.dither !== undefined) { + DitherManager._currentMode = settings.dither; + DitherManager._modeSelect.value = settings.dither; + } + + if (settings.blockSize !== undefined) { + DitherManager._currentBlockSize = settings.blockSize; + DitherManager._blockSizeSlider.value = settings.blockSize; + document.getElementById('block-size-value').textContent = `${settings.blockSize}px`; + } + } +} + +// Add event listeners for settings changes +function setupSettingsListeners() { + // Blur + BlurManager._slider.addEventListener('change', saveSettings); + + // Contrast + ContrastManager._slider.addEventListener('change', saveSettings); + + // Color + ColorManager._colorInput.addEventListener('change', saveSettings); + + // Balance + BalanceManager.balanceSlider.addEventListener('change', saveSettings); + + // Dither + DitherManager._modeSelect.addEventListener('change', saveSettings); + + // Block Size + if (DitherManager._blockSizeSlider) { + DitherManager._blockSizeSlider.addEventListener('change', saveSettings); + } +} + +// Call this after all managers are initialized +setupSettingsListeners(); +loadSettings(); \ No newline at end of file diff --git a/js/leibovitz/manifest.json b/js/leibovitz/manifest.json index 4b4a2c4..2232716 100644 --- a/js/leibovitz/manifest.json +++ b/js/leibovitz/manifest.json @@ -1,5 +1,12 @@ { "name": "Leibovitz", + "short_name": "Leibovitz", + "description": "A web-based camera that lets you make fun photos", + "start_url": "/", + "display": "standalone", + "background_color": "#f5f5dc", + "theme_color": "#f5f5dc", + "orientation": "portrait", "icons": [ { "src": "\/android-icon-36x36.png", @@ -36,6 +43,38 @@ "sizes": "192x192", "type": "image\/png", "density": "4.0" + }, + { + "src": "\/apple-icon-180x180.png", + "sizes": "180x180", + "type": "image\/png" + } + ], + "screenshots": [ + { + "src": "\/screenshot1.png", + "sizes": "1080x1920", + "type": "image\/png", + "platform": "narrow", + "label": "Leibovitz Camera App" + } + ], + "categories": ["photo", "camera", "art"], + "prefer_related_applications": false, + "shortcuts": [ + { + "name": "Take Photo", + "short_name": "Camera", + "description": "Open camera to take a photo", + "url": "\/?action=camera", + "icons": [{ "src": "\/android-icon-96x96.png", "sizes": "96x96" }] + }, + { + "name": "Edit Photo", + "short_name": "Edit", + "description": "Open photo editor", + "url": "\/?action=edit", + "icons": [{ "src": "\/android-icon-96x96.png", "sizes": "96x96" }] } ] } \ No newline at end of file diff --git a/js/leibovitz/service-worker.js b/js/leibovitz/service-worker.js index 3ed58c1..5f8ab69 100644 --- a/js/leibovitz/service-worker.js +++ b/js/leibovitz/service-worker.js @@ -1,10 +1,18 @@ -const CACHE_NAME = 'lut-cam-cache-v1'; +const CACHE_NAME = 'leibovitz-cache-v1'; const urlsToCache = [ '/', './', 'index.html', - 'lut.js', + 'leibovitz.js', + 'blur.js', + 'contrast.js', + 'color.js', + 'balance.js', + 'dither.js', 'service-worker.js', + 'ChicagoFLF.ttf', + 'manifest.json', + // Icons 'android-icon-192x192.png', 'android-icon-512x512.png', 'favicon.ico', @@ -21,23 +29,62 @@ const urlsToCache = [ 'apple-icon-152x152.png' ]; +// Install event - cache all necessary files self.addEventListener('install', event => { - event.waitUntil( - caches.open(CACHE_NAME) - .then(cache => { - return cache.addAll(urlsToCache); - }) - ); + event.waitUntil( + caches.open(CACHE_NAME) + .then(cache => { + return cache.addAll(urlsToCache); + }) + ); }); +// Activate event - clean up old caches +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(cacheNames => { + return Promise.all( + cacheNames.map(cacheName => { + if (cacheName !== CACHE_NAME) { + return caches.delete(cacheName); + } + }) + ); + }) + ); +}); + +// Fetch event - serve from cache, fallback to network self.addEventListener('fetch', event => { - event.respondWith( - caches.match(event.request) - .then(response => { - if (response) { - return response; - } - return fetch(event.request); - }) - ); + event.respondWith( + caches.match(event.request) + .then(response => { + // Return cached response if found + if (response) { + return response; + } + + // Clone the request because it can only be used once + const fetchRequest = event.request.clone(); + + // Try to fetch from network + return fetch(fetchRequest).then(response => { + // Check if we received a valid response + if (!response || response.status !== 200 || response.type !== 'basic') { + return response; + } + + // Clone the response because it can only be used once + const responseToCache = response.clone(); + + // Cache the fetched response + caches.open(CACHE_NAME) + .then(cache => { + cache.put(event.request, responseToCache); + }); + + return response; + }); + }) + ); }); \ No newline at end of file |