Skip to content

Instantly share code, notes, and snippets.

@Ser-Gen
Last active February 11, 2026 15:20
Show Gist options
  • Select an option

  • Save Ser-Gen/b089a7bb6bcb8cf794f592fb4fd3a1c3 to your computer and use it in GitHub Desktop.

Select an option

Save Ser-Gen/b089a7bb6bcb8cf794f592fb4fd3a1c3 to your computer and use it in GitHub Desktop.
OPFS Write Test
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;
}
<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>
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();
{"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