Compare commits
1 Commits
main
...
mimo/code/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45163cf14e |
24
app.js
24
app.js
@@ -2006,6 +2006,30 @@ function setupControls() {
|
|||||||
|
|
||||||
document.getElementById('atlas-toggle-btn').addEventListener('click', openPortalAtlas);
|
document.getElementById('atlas-toggle-btn').addEventListener('click', openPortalAtlas);
|
||||||
document.getElementById('atlas-close-btn').addEventListener('click', closePortalAtlas);
|
document.getElementById('atlas-close-btn').addEventListener('click', closePortalAtlas);
|
||||||
|
|
||||||
|
// Mnemosyne export/import (#1174)
|
||||||
|
document.getElementById('mnemosyne-export-btn').addEventListener('click', () => {
|
||||||
|
const result = SpatialMemory.exportToFile();
|
||||||
|
if (result) {
|
||||||
|
addChatMessage('system', 'Mnemosyne: Exported ' + result.count + ' memories to ' + result.filename);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('mnemosyne-import-btn').addEventListener('click', () => {
|
||||||
|
document.getElementById('mnemosyne-import-file').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('mnemosyne-import-file').addEventListener('change', async (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
try {
|
||||||
|
const result = await SpatialMemory.importFromFile(file);
|
||||||
|
addChatMessage('system', 'Mnemosyne: Imported ' + result.count + ' of ' + result.total + ' memories');
|
||||||
|
} catch (err) {
|
||||||
|
addChatMessage('system', 'Mnemosyne: Import failed — ' + err.message);
|
||||||
|
}
|
||||||
|
e.target.value = '';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendChatMessage(overrideText = null) {
|
function sendChatMessage(overrideText = null) {
|
||||||
|
|||||||
@@ -233,6 +233,11 @@
|
|||||||
<div class="memory-meta-row"><span class="memory-meta-label">Time</span><span id="memory-panel-time">\u2014</span></div>
|
<div class="memory-meta-row"><span class="memory-meta-label">Time</span><span id="memory-panel-time">\u2014</span></div>
|
||||||
<div class="memory-meta-row memory-meta-row--related"><span class="memory-meta-label">Related</span><span id="memory-panel-connections">\u2014</span></div>
|
<div class="memory-meta-row memory-meta-row--related"><span class="memory-meta-label">Related</span><span id="memory-panel-connections">\u2014</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="memory-panel-actions">
|
||||||
|
<button id="mnemosyne-export-btn" class="mnemosyne-action-btn" title="Export spatial memory to JSON">⤓ Export</button>
|
||||||
|
<button id="mnemosyne-import-btn" class="mnemosyne-action-btn" title="Import spatial memory from JSON">⤒ Import</button>
|
||||||
|
<input type="file" id="mnemosyne-import-file" accept=".json" style="display:none;">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -652,11 +652,93 @@ const SpatialMemory = (() => {
|
|||||||
return _selectedId;
|
return _selectedId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── FILE EXPORT ──────────────────────────────────────
|
||||||
|
function exportToFile() {
|
||||||
|
const index = exportIndex();
|
||||||
|
const json = JSON.stringify(index, null, 2);
|
||||||
|
const date = new Date().toISOString().slice(0, 10);
|
||||||
|
const filename = 'mnemosyne-export-' + date + '.json';
|
||||||
|
|
||||||
|
const blob = new Blob([json], { type: 'application/json' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
console.info('[Mnemosyne] Exported', index.memories.length, 'memories to', filename);
|
||||||
|
return { filename, count: index.memories.length };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── FILE IMPORT ──────────────────────────────────────
|
||||||
|
function importFromFile(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!file) {
|
||||||
|
reject(new Error('No file provided'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(e.target.result);
|
||||||
|
|
||||||
|
// Schema validation
|
||||||
|
if (!data || typeof data !== 'object') {
|
||||||
|
reject(new Error('Invalid JSON: not an object'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof data.version !== 'number') {
|
||||||
|
reject(new Error('Invalid schema: missing version field'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.version !== STORAGE_VERSION) {
|
||||||
|
reject(new Error('Version mismatch: got ' + data.version + ', expected ' + STORAGE_VERSION));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(data.memories)) {
|
||||||
|
reject(new Error('Invalid schema: memories is not an array'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate each memory entry
|
||||||
|
for (let i = 0; i < data.memories.length; i++) {
|
||||||
|
const mem = data.memories[i];
|
||||||
|
if (!mem.id || typeof mem.id !== 'string') {
|
||||||
|
reject(new Error('Invalid memory at index ' + i + ': missing or invalid id'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mem.category || typeof mem.category !== 'string') {
|
||||||
|
reject(new Error('Invalid memory "' + mem.id + '": missing category'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = importIndex(data);
|
||||||
|
saveToStorage();
|
||||||
|
console.info('[Mnemosyne] Imported', count, 'memories from file');
|
||||||
|
resolve({ count, total: data.memories.length });
|
||||||
|
} catch (parseErr) {
|
||||||
|
reject(new Error('Failed to parse JSON: ' + parseErr.message));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onerror = function() {
|
||||||
|
reject(new Error('Failed to read file'));
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init, placeMemory, removeMemory, update,
|
init, placeMemory, removeMemory, update,
|
||||||
getMemoryAtPosition, getRegionAtPosition, getMemoriesInRegion, getAllMemories,
|
getMemoryAtPosition, getRegionAtPosition, getMemoriesInRegion, getAllMemories,
|
||||||
getCrystalMeshes, getMemoryFromMesh, highlightMemory, clearHighlight, getSelectedId,
|
getCrystalMeshes, getMemoryFromMesh, highlightMemory, clearHighlight, getSelectedId,
|
||||||
exportIndex, importIndex, searchNearby, REGIONS,
|
exportIndex, importIndex, exportToFile, importFromFile, searchNearby, REGIONS,
|
||||||
saveToStorage, loadFromStorage, clearStorage,
|
saveToStorage, loadFromStorage, clearStorage,
|
||||||
runGravityLayout
|
runGravityLayout
|
||||||
};
|
};
|
||||||
|
|||||||
37
style.css
37
style.css
@@ -1461,6 +1461,43 @@ canvas#nexus-canvas {
|
|||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════════════
|
||||||
|
PROJECT MNEMOSYNE — EXPORT/IMPORT ACTIONS (#1174)
|
||||||
|
═══════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
.memory-panel-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid rgba(123, 92, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mnemosyne-action-btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: rgba(123, 92, 255, 0.12);
|
||||||
|
border: 1px solid rgba(123, 92, 255, 0.3);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #a08cff;
|
||||||
|
font-size: 11px;
|
||||||
|
font-family: monospace;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mnemosyne-action-btn:hover {
|
||||||
|
background: rgba(123, 92, 255, 0.25);
|
||||||
|
border-color: rgba(123, 92, 255, 0.6);
|
||||||
|
color: #c4b5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mnemosyne-action-btn:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════
|
||||||
PROJECT MNEMOSYNE — SESSION ROOM HUD PANEL (#1171)
|
PROJECT MNEMOSYNE — SESSION ROOM HUD PANEL (#1171)
|
||||||
═══════════════════════════════════════════════════════ */
|
═══════════════════════════════════════════════════════ */
|
||||||
|
|||||||
Reference in New Issue
Block a user