Last active
February 11, 2026 15:20
-
-
Save Ser-Gen/b089a7bb6bcb8cf794f592fb4fd3a1c3 to your computer and use it in GitHub Desktop.
OPFS Write Test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| body { | |
| background-color: #333; | |
| color: #fff; | |
| font-family: monospace; | |
| padding: 20px; | |
| margin: 0; | |
| } | |
| .card { | |
| max-width: 800px; | |
| } | |
| .quota-info { | |
| font-size: 1.2em; | |
| line-height: 1.6; | |
| white-space: pre-wrap; | |
| } | |
| .control-group { | |
| display: flex; | |
| align-items: center; | |
| gap: 15px; | |
| flex-wrap: wrap; | |
| } | |
| .control-group label { | |
| min-width: 140px; | |
| } | |
| input[type=range] { | |
| flex: 1; | |
| min-width: 200px; | |
| height: 8px; | |
| border-radius: 5px; | |
| } | |
| input[type=number] { | |
| width: 100px; | |
| padding: 8px; | |
| font-size: 1rem; | |
| } | |
| .status { | |
| margin-top: 20px; | |
| padding: 15px; | |
| border-radius: 4px; | |
| display: none; | |
| font-weight: 500; | |
| } | |
| .success { | |
| background: #2e7d32; | |
| color: white; | |
| display: block; | |
| } | |
| .error { | |
| background: #c62828; | |
| color: white; | |
| display: block; | |
| } | |
| .size-bytes { | |
| font-family: monospace; | |
| background: #333; | |
| padding: 4px 8px; | |
| border-radius: 4px; | |
| margin-left: 10px; | |
| } | |
| hr { | |
| border: none; | |
| border-top: 1px solid #666; | |
| margin: 20px 0; | |
| } | |
| .spinner { | |
| border: 3px solid #f3f3f3; | |
| border-top: 3px solid #3498db; | |
| border-radius: 50%; | |
| width: 20px; | |
| height: 20px; | |
| animation: spin 1s linear infinite; | |
| display: inline-block; | |
| } | |
| .hidden { | |
| display: none !important; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .button-container { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| margin-bottom: 10px; | |
| } | |
| .log-card h3 { | |
| margin-top: 0; | |
| margin-bottom: 16px; | |
| color: #ddd; | |
| } | |
| .log-container { | |
| background: #2a2a2a; | |
| border-radius: 4px; | |
| padding: 12px; | |
| max-height: 300px; | |
| overflow-y: auto; | |
| font-size: 0.9rem; | |
| margin-bottom: 16px; | |
| } | |
| .log-entry { | |
| padding: 6px 10px; | |
| margin-bottom: 6px; | |
| border-left: 4px solid #666; | |
| background: #333; | |
| border-radius: 0 4px 4px 0; | |
| font-family: monospace; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-start; | |
| } | |
| .log-entry.success { | |
| border-left-color: #2e7d32; | |
| background: #2a3a2a; | |
| } | |
| .log-entry.error { | |
| border-left-color: #c62828; | |
| background: #3a2a2a; | |
| } | |
| .log-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| width: 100%; | |
| } | |
| .log-time { | |
| color: #aaa; | |
| margin-right: 12px; | |
| } | |
| .log-size { | |
| font-weight: bold; | |
| color: #fff; | |
| } | |
| .log-result { | |
| padding: 2px 8px; | |
| border-radius: 12px; | |
| font-size: 0.8rem; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| } | |
| .log-result.success { | |
| background: #2e7d32; | |
| color: white; | |
| } | |
| .log-result.error { | |
| background: #c62828; | |
| color: white; | |
| } | |
| .log-error-message { | |
| margin-top: 4px; | |
| font-size: 0.85rem; | |
| color: #ff8a80; | |
| background: rgba(198, 40, 40, 0.2); | |
| padding: 4px 8px; | |
| border-radius: 4px; | |
| width: 100%; | |
| word-break: break-word; | |
| } | |
| #clearLogButton { | |
| background: #555; | |
| color: white; | |
| border: none; | |
| padding: 8px 16px; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| font-family: monospace; | |
| } | |
| #clearLogButton:hover { | |
| background: #777; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <div class="container"> | |
| <pre id="quotaDisplay" class="card quota-info"> | |
| Loading quota info... | |
| </pre> | |
| <div class="card"> | |
| <div class="control-group"> | |
| <label for="fileSizeSlider">File size (MB):</label> | |
| <input type="range" id="fileSizeSlider" min="1" max="500" value="247" step="1"> | |
| <input type="number" id="fileSizeInput" min="1" max="500" value="247" step="1"> | |
| </div> | |
| <div style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;"> | |
| <span>Size in bytes:</span> | |
| <span id="sizeInBytes" class="size-bytes">259,522,560 B</span> | |
| </div> | |
| <div class="button-container"> | |
| <button id="writeButton">Write file to OPFS</button> | |
| <div id="spinner" class="spinner hidden"></div> | |
| </div> | |
| <div id="statusMessage" class="status"></div> | |
| </div> | |
| <!-- Log card hidden initially --> | |
| <div id="logCard" class="card log-card hidden"> | |
| <div id="logContainer" class="log-container"> | |
| <div id="logEntries"></div> | |
| </div> | |
| <button id="clearLogButton">Clear Log</button> | |
| </div> | |
| </div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function formatBytes(bytes) { | |
| if (bytes === 0) return '0 B'; | |
| const k = 1024; | |
| const sizes = ['B', 'KB', 'MB', 'GB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
| } | |
| async function updateQuotaDisplay() { | |
| try { | |
| const estimate = await navigator.storage.estimate(); | |
| const usage = estimate.usage || 0; | |
| const quota = estimate.quota || 0; | |
| const available = quota - usage; | |
| const quotaDiv = document.getElementById('quotaDisplay'); | |
| quotaDiv.innerHTML = `Used: ${formatBytes(usage)} | |
| Available: ${formatBytes(available)} | |
| Quota limit: ${formatBytes(quota)}`; | |
| } catch (err) { | |
| console.error('Error getting quota:', err); | |
| document.getElementById('quotaDisplay').innerHTML = '<p style="color:#ff8a80;">Failed to get quota info</p>'; | |
| } | |
| } | |
| window.clearOPFS = async function () { | |
| try { | |
| const opfsRoot = await navigator.storage.getDirectory(); | |
| async function removeAll(directory) { | |
| for await (const entry of directory.values()) { | |
| try { | |
| if (entry.kind === "file") { | |
| await directory.removeEntry(entry.name); | |
| } else if (entry.kind === "directory") { | |
| await removeAll(entry); | |
| await directory.removeEntry(entry.name, { recursive: true }); | |
| } | |
| } catch (e) { | |
| console.warn(`Could not delete ${entry.name}:`, e); | |
| } | |
| } | |
| } | |
| await removeAll(opfsRoot); | |
| console.log('✅ OPFS cleared successfully'); | |
| } catch (err) { | |
| console.error('❌ Error clearing OPFS:', err); | |
| throw err; | |
| } | |
| }; | |
| async function writeFileToOPFS(sizeBytes) { | |
| const root = await navigator.storage.getDirectory(); | |
| const blob = new Blob([new Uint8Array(sizeBytes)]); | |
| const fileHandle = await root.getFileHandle('test-file', { create: true }); | |
| const writable = await fileHandle.createWritable(); | |
| await writable.write(blob); | |
| await writable.close(); | |
| } | |
| const slider = document.getElementById('fileSizeSlider'); | |
| const input = document.getElementById('fileSizeInput'); | |
| const bytesSpan = document.getElementById('sizeInBytes'); | |
| function updateSizeUI(megabytes) { | |
| const clamped = Math.min(500, Math.max(1, parseInt(megabytes) || 1)); | |
| input.value = clamped; | |
| slider.value = clamped; | |
| const bytes = clamped * 1024 * 1024; | |
| bytesSpan.textContent = formatBytes(bytes) + ` (${bytes.toLocaleString()} B)`; | |
| } | |
| slider.addEventListener('input', (e) => { | |
| updateSizeUI(e.target.value); | |
| }); | |
| input.addEventListener('input', (e) => { | |
| updateSizeUI(e.target.value); | |
| }); | |
| updateSizeUI(247); | |
| const writeButton = document.getElementById('writeButton'); | |
| const spinner = document.getElementById('spinner'); | |
| const statusDiv = document.getElementById('statusMessage'); | |
| const logCard = document.getElementById('logCard'); | |
| const logEntriesDiv = document.getElementById('logEntries'); | |
| const clearLogButton = document.getElementById('clearLogButton'); | |
| function showStatus(message, type) { | |
| statusDiv.textContent = message; | |
| statusDiv.className = `status ${type}`; | |
| statusDiv.style.display = 'block'; | |
| } | |
| function addLogEntry(sizeBytes, isSuccess, errorMessage = '') { | |
| if (logCard.classList.contains('hidden')) { | |
| logCard.classList.remove('hidden'); | |
| } | |
| const entry = document.createElement('div'); | |
| entry.className = `log-entry ${isSuccess ? 'success' : 'error'}`; | |
| const time = new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }); | |
| const headerDiv = document.createElement('div'); | |
| headerDiv.className = 'log-header'; | |
| headerDiv.innerHTML = ` | |
| <span> | |
| <span class="log-time">[${time}]</span> | |
| <span class="log-size">${formatBytes(sizeBytes)}</span> | |
| </span> | |
| <span class="log-result ${isSuccess ? 'success' : 'error'}">${isSuccess ? 'Success' : 'Error'}</span> | |
| `; | |
| entry.appendChild(headerDiv); | |
| if (!isSuccess && errorMessage) { | |
| const errorDiv = document.createElement('div'); | |
| errorDiv.className = 'log-error-message'; | |
| errorDiv.textContent = `Error: ${errorMessage}`; | |
| entry.appendChild(errorDiv); | |
| } | |
| logEntriesDiv.prepend(entry); | |
| } | |
| clearLogButton.addEventListener('click', () => { | |
| logEntriesDiv.innerHTML = ''; | |
| }); | |
| writeButton.addEventListener('click', async () => { | |
| writeButton.disabled = true; | |
| spinner.classList.remove('hidden'); | |
| statusDiv.style.display = 'none'; | |
| statusDiv.className = 'status'; | |
| const mb = parseInt(input.value, 10) || 0; | |
| if (mb <= 0) { | |
| showStatus('File size must be greater than 0', 'error'); | |
| writeButton.disabled = false; | |
| spinner.classList.add('hidden'); | |
| return; | |
| } | |
| const sizeBytes = mb * 1024 * 1024; | |
| let operationSuccess = false; | |
| let errorMessage = ''; | |
| try { | |
| await writeFileToOPFS(sizeBytes); | |
| operationSuccess = true; | |
| try { | |
| await window.clearOPFS(); | |
| } catch (clearErr) { | |
| console.error('Error during OPFS cleanup:', clearErr); | |
| errorMessage = 'File written, but OPFS cleanup failed: ' + (clearErr.message || ''); | |
| operationSuccess = false; | |
| } | |
| await updateQuotaDisplay(); | |
| } catch (err) { | |
| errorMessage = err.message || 'Unknown write error'; | |
| console.error('Write error:', err); | |
| operationSuccess = false; | |
| await updateQuotaDisplay(); | |
| } | |
| if (operationSuccess) { | |
| showStatus(`✅ File of size ${formatBytes(sizeBytes)} successfully written to OPFS and then cleared.`, 'success'); | |
| } else { | |
| showStatus(`❌ Error: ${errorMessage || 'Failed to write file'}`, 'error'); | |
| } | |
| addLogEntry(sizeBytes, operationSuccess, errorMessage); | |
| writeButton.disabled = false; | |
| spinner.classList.add('hidden'); | |
| }); | |
| updateQuotaDisplay(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {"name":"OPFS Write Test","settings":{},"filenames":["index.html","index.css","index.js"]} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment