mirror of
https://github.com/olehomelchenko/astrolabe-nvc.git
synced 2025-12-21 21:22:23 +00:00
feat: add storage monitor to track localStorage usage
This commit is contained in:
@@ -15,4 +15,7 @@ Instructions for Claude Code when working on this project.
|
||||
|
||||
## Current Status
|
||||
|
||||
refer `docs/dev-plan.md` for the current status.
|
||||
**Completed**: Phases 0-8 (All core functionality including storage monitoring)
|
||||
**Next**: Phase 9 - Export/Import
|
||||
|
||||
See `docs/dev-plan.md` for complete roadmap and technical details.
|
||||
|
||||
@@ -156,16 +156,17 @@ Astrolabe is a focused tool for managing, editing, and previewing Vega-Lite visu
|
||||
|
||||
---
|
||||
|
||||
### **Phase 8: Storage Monitoring**
|
||||
### **Phase 8: Storage Monitoring** ✅ **COMPLETE**
|
||||
**Goal**: Show storage usage and limits
|
||||
|
||||
- [ ] Calculate total localStorage usage
|
||||
- [ ] Display as progress bar or text (e.g., "2.3 MB / ~5 MB")
|
||||
- [ ] Show individual snippet sizes in list
|
||||
- [ ] Add warning indicator when approaching 80% capacity
|
||||
- [ ] Display draft vs published size differences
|
||||
|
||||
**Deliverable**: User can see storage consumption
|
||||
**Deliverables**:
|
||||
- Storage usage calculation using Blob API for accurate byte counting
|
||||
- Progress bar display showing usage vs 5MB limit (e.g., "2.3 MB / 5 MB")
|
||||
- Visual warning states: green (normal), orange (90%+ warning), red (95%+ critical)
|
||||
- Storage monitor positioned at bottom of snippet panel below metadata
|
||||
- Automatic updates after every save operation
|
||||
- Alert dialog when localStorage quota is exceeded
|
||||
- Flexbox layout ensuring monitor stays at panel bottom with scrollable snippet list
|
||||
|
||||
---
|
||||
|
||||
@@ -304,15 +305,15 @@ Astrolabe is a focused tool for managing, editing, and previewing Vega-Lite visu
|
||||
|
||||
## Current Status
|
||||
|
||||
**Completed**: Phases 0-7 (Storage, UI, editor, rendering, persistence, CRUD, organization, draft/published workflow)
|
||||
**Active**: Phase 8 - Storage Monitoring
|
||||
**Completed**: Phases 0-8 (Storage, UI, editor, rendering, persistence, CRUD, organization, draft/published workflow, storage monitoring)
|
||||
**Next**: Phase 9 - Export/Import
|
||||
**See**: `CLAUDE.md` for concise current state summary
|
||||
|
||||
---
|
||||
|
||||
## Implemented Features
|
||||
|
||||
### Core Capabilities (Phases 0-7)
|
||||
### Core Capabilities (Phases 0-8)
|
||||
- Three-panel resizable layout with memory and persistence
|
||||
- Monaco Editor v0.47.0 with Vega-Lite v5 schema validation
|
||||
- Live Vega-Lite rendering with debounced updates and error display
|
||||
@@ -324,6 +325,7 @@ Astrolabe is a focused tool for managing, editing, and previewing Vega-Lite visu
|
||||
- Draft/Published workflow with version control
|
||||
- Status indicator lights (green/yellow) showing draft state
|
||||
- Context-aware Publish/Revert buttons with color coding
|
||||
- Storage monitoring with visual progress bar and warning states
|
||||
- Retro Windows 2000 aesthetic throughout
|
||||
|
||||
### Technical Implementation
|
||||
@@ -335,3 +337,5 @@ Astrolabe is a focused tool for managing, editing, and previewing Vega-Lite visu
|
||||
- **AMD Resolution**: Temporary `window.define` disabling for Vega library loading
|
||||
- **Panel Memory**: localStorage persistence for sizes and visibility across sessions
|
||||
- **Data Model**: Phase 0 schema with `spec` (published) and `draftSpec` (working) fields
|
||||
- **Storage Calculation**: Blob API for accurate byte counting of snippet data
|
||||
- **Flexbox Layout**: Scrollable snippet list with fixed metadata and storage monitor at bottom
|
||||
@@ -94,6 +94,15 @@
|
||||
<button class="meta-btn delete-btn" id="delete-btn">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="storage-monitor" id="storage-monitor">
|
||||
<div class="storage-info">
|
||||
<span class="storage-label">Storage:</span>
|
||||
<span class="storage-text" id="storage-text">0 KB / 5 MB</span>
|
||||
</div>
|
||||
<div class="storage-bar">
|
||||
<div class="storage-fill" id="storage-fill"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
renderSnippetList();
|
||||
|
||||
// Update storage monitor
|
||||
updateStorageMonitor();
|
||||
|
||||
// Auto-select first snippet on page load
|
||||
const firstSnippet = SnippetStorage.listSnippets()[0];
|
||||
if (firstSnippet) {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// Snippet management and localStorage functionality
|
||||
|
||||
// Storage limits (5MB in bytes)
|
||||
const STORAGE_LIMIT_BYTES = 5 * 1024 * 1024;
|
||||
const WARNING_THRESHOLD = 0.9; // 90% = 4.5MB
|
||||
|
||||
// Generate unique ID using Date.now() + random numbers
|
||||
function generateSnippetId() {
|
||||
return Date.now() + Math.random() * 1000;
|
||||
@@ -39,10 +43,11 @@ const SnippetStorage = {
|
||||
saveSnippets(snippets) {
|
||||
try {
|
||||
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(snippets));
|
||||
updateStorageMonitor();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to save snippets to localStorage:', error);
|
||||
// TODO: Handle quota exceeded, show user error
|
||||
alert('Failed to save: Storage quota may be exceeded. Consider deleting old snippets.');
|
||||
return false;
|
||||
}
|
||||
},
|
||||
@@ -798,3 +803,46 @@ function revertDraft() {
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate storage usage in bytes
|
||||
function calculateStorageUsage() {
|
||||
const snippetsData = localStorage.getItem(SnippetStorage.STORAGE_KEY);
|
||||
if (!snippetsData) return 0;
|
||||
|
||||
// Calculate size in bytes
|
||||
return new Blob([snippetsData]).size;
|
||||
}
|
||||
|
||||
// Format bytes to human-readable size
|
||||
function formatBytes(bytes) {
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
||||
}
|
||||
|
||||
// Update storage monitor display
|
||||
function updateStorageMonitor() {
|
||||
const usedBytes = calculateStorageUsage();
|
||||
const percentage = (usedBytes / STORAGE_LIMIT_BYTES) * 100;
|
||||
|
||||
const storageText = document.getElementById('storage-text');
|
||||
const storageFill = document.getElementById('storage-fill');
|
||||
|
||||
if (storageText) {
|
||||
storageText.textContent = `${formatBytes(usedBytes)} / 5 MB`;
|
||||
}
|
||||
|
||||
if (storageFill) {
|
||||
storageFill.style.width = `${Math.min(percentage, 100)}%`;
|
||||
|
||||
// Remove all state classes
|
||||
storageFill.classList.remove('warning', 'critical');
|
||||
|
||||
// Add warning/critical classes based on usage
|
||||
if (percentage >= 95) {
|
||||
storageFill.classList.add('critical');
|
||||
} else if (percentage >= 90) {
|
||||
storageFill.classList.add('warning');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -332,9 +332,11 @@ body {
|
||||
.panel-content {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
border: 1px inset #c0c0c0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Panel sizing */
|
||||
@@ -392,6 +394,10 @@ body {
|
||||
|
||||
.snippet-list {
|
||||
list-style: none;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.snippet-item {
|
||||
@@ -477,13 +483,14 @@ body {
|
||||
/* Snippet meta section */
|
||||
.snippet-meta {
|
||||
margin-top: 12px;
|
||||
padding: 8px;
|
||||
padding: 8px 8px 16px 8px;
|
||||
border-top: 1px solid #808080;
|
||||
background: #f0f0f0;
|
||||
border: 1px inset #c0c0c0;
|
||||
margin-left: -8px;
|
||||
margin-right: -8px;
|
||||
margin-bottom: -8px;
|
||||
margin-bottom: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.meta-header {
|
||||
@@ -599,3 +606,51 @@ body {
|
||||
.ghost-card .snippet-date {
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
/* Storage monitor */
|
||||
.storage-monitor {
|
||||
padding: 8px;
|
||||
background: #f0f0f0;
|
||||
border-top: 1px solid #808080;
|
||||
margin: 0 -8px -8px -8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.storage-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.storage-label {
|
||||
font-weight: bold;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.storage-text {
|
||||
color: #606060;
|
||||
}
|
||||
|
||||
.storage-bar {
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
background: #ffffff;
|
||||
border: 1px inset #c0c0c0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.storage-fill {
|
||||
height: 100%;
|
||||
background: #00aa00;
|
||||
transition: width 0.3s ease, background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.storage-fill.warning {
|
||||
background: #ff8800;
|
||||
}
|
||||
|
||||
.storage-fill.critical {
|
||||
background: #ff0000;
|
||||
}
|
||||
Reference in New Issue
Block a user