about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorelioat <hi@eli.li>2025-01-18 15:30:03 -0500
committerelioat <hi@eli.li>2025-01-18 15:30:03 -0500
commite73510d9a67b6c80ea1857986787d37849c9c983 (patch)
tree99f4e10c007fdf490e46f1b1ed697a79444cdb65
parent6191c494abc1dbdba850c9ae9cd327197a86bdc9 (diff)
downloadtour-e73510d9a67b6c80ea1857986787d37849c9c983.tar.gz
*
-rw-r--r--html/read-write/index.html198
1 files changed, 198 insertions, 0 deletions
diff --git a/html/read-write/index.html b/html/read-write/index.html
new file mode 100644
index 0000000..0dd75b5
--- /dev/null
+++ b/html/read-write/index.html
@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>For Akkartik & Konrad</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            max-width: 800px;
+            margin: 0 auto;
+            padding: 20px;
+            background-color: beige;
+        }
+        #imagePreview {
+            max-width: 100%;
+            margin: 20px 0;
+        }
+        .controls {
+            margin: 20px 0;
+        }
+        button {
+            padding: 10px 20px;
+            margin: 5px;
+        }
+        button:hover {
+            cursor: pointer;
+        }
+    </style>
+</head>
+<body>
+    <h1>For Akkartik & Konrad</h1>
+    <p>A demo of how to edit a file in place using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API">File System Access API</a>.</p>
+    <p>At a quick glance, it doesn't seem to be available in Firefox or WebKit, just Chromium browsers.</p>
+    <div class="controls">
+        <button id="selectFile">Select a png</button>
+        <button id="saveChanges" disabled>Save changes</button>
+        <input type="file" id="fileInput" accept=".png" style="display: none;">
+    </div>
+
+    <img id="imagePreview" alt="Preview">
+
+    <div class="controls">
+        <button id="invertColors" disabled>Invert colors</button>
+        <button id="grayscale" disabled>Convert to greyscale</button>
+    </div>
+
+    <script>
+        // Store the file handle for later
+        let fileHandle = null;
+        let currentImageData = null;
+
+        // Get DOM elements
+        const selectButton = document.getElementById('selectFile');
+        const saveButton = document.getElementById('saveChanges');
+        const invertButton = document.getElementById('invertColors');
+        const grayscaleButton = document.getElementById('grayscale');
+        const imagePreview = document.getElementById('imagePreview');
+
+        selectButton.addEventListener('click', async () => {
+            try {
+                if ('showOpenFilePicker' in window) {
+                    // Modern File System Access API approach for browsers that support it
+                    // This will open a file picker and return a file handle
+                    fileHandle = await window.showOpenFilePicker({
+                        types: [{
+                            description: 'PNG Files',
+                            accept: {
+                                'image/png': ['.png']
+                            }
+                        }]
+                    });
+
+                    // Get the file object from the handle
+                    const file = await fileHandle[0].getFile();
+                    handleSelectedFile(file);
+                } else {
+                    // Fallback for browsers that don't support the API
+                    document.getElementById('fileInput').click();
+                }
+            } catch (err) {
+                console.error('Error selecting file:', err);
+            }
+        });
+
+        // Event listener for the fallback file input
+        document.getElementById('fileInput').addEventListener('change', async (e) => {
+            const file = e.target.files[0];
+            if (file) {
+                handleSelectedFile(file);
+                // When in fallback mode, we download a new file, rather than saving in place
+                saveButton.textContent = 'Download edited image';
+            }
+        });
+
+        async function handleSelectedFile(file) {
+            // Create a URL for the image preview
+            const imageUrl = URL.createObjectURL(file);
+            imagePreview.src = imageUrl;
+
+            // Load the image data into a canvas for editing
+            await loadImageData(file);
+
+            // Enable the editing buttons
+            invertButton.disabled = false;
+            grayscaleButton.disabled = false;
+            saveButton.disabled = false;
+        }
+
+        // Load image data into canvas
+        async function loadImageData(file) {
+            return new Promise((resolve) => {
+                const img = new Image();
+                img.onload = () => {
+                    const canvas = document.createElement('canvas');
+                    canvas.width = img.width;
+                    canvas.height = img.height;
+                    const ctx = canvas.getContext('2d');
+                    ctx.drawImage(img, 0, 0);
+                    currentImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+                    resolve();
+                };
+                img.src = URL.createObjectURL(file);
+            });
+        }
+
+        // Invert colors
+        invertButton.addEventListener('click', () => {
+            const data = currentImageData.data;
+            for (let i = 0; i < data.length; i += 4) {
+                data[i] = 255 - data[i];         // Red
+                data[i + 1] = 255 - data[i + 1]; // Green
+                data[i + 2] = 255 - data[i + 2]; // Blue
+            }
+            updatePreview();
+        });
+
+        // Convert to grayscale
+        grayscaleButton.addEventListener('click', () => {
+            const data = currentImageData.data;
+            for (let i = 0; i < data.length; i += 4) {
+                const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
+                data[i] = avg;
+                data[i + 1] = avg;
+                data[i + 2] = avg;
+            }
+            updatePreview();
+        });
+
+        function updatePreview() {
+            const canvas = document.createElement('canvas');
+            canvas.width = currentImageData.width;
+            canvas.height = currentImageData.height;
+            const ctx = canvas.getContext('2d');
+            ctx.putImageData(currentImageData, 0, 0);
+            imagePreview.src = canvas.toDataURL('image/png');
+        }
+
+        saveButton.addEventListener('click', async () => {
+            try {
+                if (fileHandle) {
+                    // Using the File System Access API
+                    const writable = await fileHandle[0].createWritable();
+                    const blob = await getImageBlob();
+                    await writable.write(blob);
+                    await writable.close();
+                    alert('Changes saved successfully!');
+                } else {
+                    // Download the edited image as a new file, if the API is not supported
+                    const blob = await getImageBlob();
+                    const downloadUrl = URL.createObjectURL(blob);
+                    const a = document.createElement('a');
+                    a.href = downloadUrl;
+                    a.download = 'edited-image.png';
+                    document.body.appendChild(a);
+                    a.click();
+                    document.body.removeChild(a);
+                    URL.revokeObjectURL(downloadUrl);
+                }
+            } catch (err) {
+                console.error('Error saving the file:', err);
+            }
+        });
+
+        async function getImageBlob() {
+            const canvas = document.createElement('canvas');
+            canvas.width = currentImageData.width;
+            canvas.height = currentImageData.height;
+            const ctx = canvas.getContext('2d');
+            ctx.putImageData(currentImageData, 0, 0);
+            
+            return new Promise(resolve => {
+                canvas.toBlob(resolve, 'image/png');
+            });
+        }
+    </script>
+</body>
+</html>
\ No newline at end of file