mirror of
https://github.com/olehomelchenko/astrolabe-nvc.git
synced 2025-12-21 21:22:23 +00:00
feat: add help modal with keyboard shortcuts documentation
This commit is contained in:
@@ -38,3 +38,4 @@ Instructions for Claude Code when working on this project.
|
||||
- On-demand URL preview loading with caching
|
||||
|
||||
See `docs/dev-plan.md` for complete roadmap and technical details.
|
||||
- when updating documentation, do not record intermediate changes - write them always as a matter-of-fact information
|
||||
@@ -303,18 +303,6 @@ Astrolabe is a focused tool for managing, editing, and previewing Vega-Lite visu
|
||||
### **Phase 12: Advanced Dataset Features** ✅ **COMPLETE**
|
||||
**Goal**: Enhanced dataset workflows
|
||||
|
||||
- [x] Detect inline data in Vega-Lite specs
|
||||
- [x] "Extract to dataset" feature for inline data
|
||||
- [x] Update snippet UI to show linked datasets
|
||||
- [x] Dataset usage tracking (which snippets reference which datasets)
|
||||
- [x] Bidirectional linking between snippets and datasets
|
||||
- [x] Usage count badges on dataset list items
|
||||
- [x] "New Snippet" button to create snippet from dataset
|
||||
- [x] Import datasets from file upload
|
||||
- [x] Export individual datasets
|
||||
- [x] Dataset preview with table view
|
||||
- [x] Column type detection and display
|
||||
|
||||
**Deliverables**:
|
||||
- Recursive dataset reference extraction from Vega-Lite specs
|
||||
- Extract to Dataset modal with automatic spec transformation
|
||||
|
||||
36
index.html
36
index.html
@@ -321,6 +321,42 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help Modal -->
|
||||
<div id="help-modal" class="modal" style="display: none;">
|
||||
<div class="modal-content" style="max-width: 600px; height: auto;">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">Help</span>
|
||||
<button class="btn btn-icon" id="help-modal-close">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div style="padding: 20px;">
|
||||
<h3 style="margin-top: 0; margin-bottom: 16px; font-size: 16px; font-weight: bold;">Keyboard Shortcuts</h3>
|
||||
|
||||
<table class="help-shortcuts-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="shortcut-key">Cmd/Ctrl+Shift+N</td>
|
||||
<td class="shortcut-desc">Create new snippet</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="shortcut-key">Cmd/Ctrl+K</td>
|
||||
<td class="shortcut-desc">Toggle dataset manager</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="shortcut-key">Cmd/Ctrl+S</td>
|
||||
<td class="shortcut-desc">Publish draft (save)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="shortcut-key">Escape</td>
|
||||
<td class="shortcut-desc">Close any open modal</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="src/js/config.js"></script>
|
||||
<script src="src/js/snippet-manager.js"></script>
|
||||
<script src="src/js/dataset-manager.js"></script>
|
||||
|
||||
135
src/js/app.js
135
src/js/app.js
@@ -29,6 +29,9 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
// Initialize resize functionality
|
||||
initializeResize();
|
||||
|
||||
// Initialize keyboard shortcuts
|
||||
initializeKeyboardShortcuts();
|
||||
|
||||
// Initialize Monaco Editor
|
||||
require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.47.0/min/vs' } });
|
||||
require(['vs/editor/editor.main'], async function () {
|
||||
@@ -70,6 +73,9 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
formatOnType: true
|
||||
});
|
||||
|
||||
// Register custom keyboard shortcuts in Monaco
|
||||
registerMonacoKeyboardShortcuts();
|
||||
|
||||
// Add debounced auto-render on editor change
|
||||
editor.onDidChangeModelContent(() => {
|
||||
debouncedRender();
|
||||
@@ -118,7 +124,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
if (helpLink) {
|
||||
helpLink.addEventListener('click', function () {
|
||||
alert('Coming soon in a future phase!');
|
||||
openHelpModal();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -229,6 +235,23 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
}
|
||||
|
||||
// Help Modal
|
||||
const helpModal = document.getElementById('help-modal');
|
||||
const helpModalClose = document.getElementById('help-modal-close');
|
||||
|
||||
if (helpModalClose) {
|
||||
helpModalClose.addEventListener('click', closeHelpModal);
|
||||
}
|
||||
|
||||
// Close on overlay click
|
||||
if (helpModal) {
|
||||
helpModal.addEventListener('click', function (e) {
|
||||
if (e.target === helpModal) {
|
||||
closeHelpModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// View mode toggle buttons
|
||||
document.getElementById('view-draft').addEventListener('click', () => {
|
||||
switchViewMode('draft');
|
||||
@@ -315,3 +338,113 @@ function initializeURLStateManagement() {
|
||||
handleURLStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard shortcut action handlers (shared between Monaco and document)
|
||||
const KeyboardActions = {
|
||||
createNewSnippet: function() {
|
||||
createNewSnippet();
|
||||
},
|
||||
|
||||
toggleDatasetManager: function() {
|
||||
const modal = document.getElementById('dataset-modal');
|
||||
if (modal && modal.style.display === 'flex') {
|
||||
closeDatasetManager();
|
||||
} else {
|
||||
openDatasetManager();
|
||||
}
|
||||
},
|
||||
|
||||
publishDraft: function() {
|
||||
if (currentViewMode === 'draft' && window.currentSnippetId) {
|
||||
publishDraft();
|
||||
}
|
||||
},
|
||||
|
||||
closeAnyModal: function() {
|
||||
const helpModal = document.getElementById('help-modal');
|
||||
const datasetModal = document.getElementById('dataset-modal');
|
||||
const extractModal = document.getElementById('extract-modal');
|
||||
|
||||
if (helpModal && helpModal.style.display === 'flex') {
|
||||
closeHelpModal();
|
||||
return true;
|
||||
}
|
||||
if (datasetModal && datasetModal.style.display === 'flex') {
|
||||
closeDatasetManager();
|
||||
return true;
|
||||
}
|
||||
if (extractModal && extractModal.style.display === 'flex') {
|
||||
hideExtractModal();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Keyboard shortcuts handler (document-level)
|
||||
function initializeKeyboardShortcuts() {
|
||||
document.addEventListener('keydown', function(e) {
|
||||
// Escape: Close any open modal
|
||||
if (e.key === 'Escape') {
|
||||
if (KeyboardActions.closeAnyModal()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect modifier key: Cmd on Mac, Ctrl on Windows/Linux
|
||||
const modifierKey = e.metaKey || e.ctrlKey;
|
||||
|
||||
// Cmd/Ctrl+Shift+N: Create new snippet
|
||||
if (modifierKey && e.shiftKey && e.key.toLowerCase() === 'n') {
|
||||
e.preventDefault();
|
||||
KeyboardActions.createNewSnippet();
|
||||
return;
|
||||
}
|
||||
|
||||
// Cmd/Ctrl+K: Toggle dataset manager
|
||||
if (modifierKey && e.key.toLowerCase() === 'k') {
|
||||
e.preventDefault();
|
||||
KeyboardActions.toggleDatasetManager();
|
||||
return;
|
||||
}
|
||||
|
||||
// Cmd/Ctrl+S: Publish draft
|
||||
if (modifierKey && e.key.toLowerCase() === 's') {
|
||||
e.preventDefault();
|
||||
KeyboardActions.publishDraft();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Register keyboard shortcuts in Monaco Editor
|
||||
function registerMonacoKeyboardShortcuts() {
|
||||
if (!editor) return;
|
||||
|
||||
// Cmd/Ctrl+Shift+N: Create new snippet
|
||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyN,
|
||||
KeyboardActions.createNewSnippet);
|
||||
|
||||
// Cmd/Ctrl+K: Toggle dataset manager
|
||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK,
|
||||
KeyboardActions.toggleDatasetManager);
|
||||
|
||||
// Cmd/Ctrl+S: Publish draft
|
||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS,
|
||||
KeyboardActions.publishDraft);
|
||||
}
|
||||
|
||||
// Help modal functions
|
||||
function openHelpModal() {
|
||||
const modal = document.getElementById('help-modal');
|
||||
if (modal) {
|
||||
modal.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
function closeHelpModal() {
|
||||
const modal = document.getElementById('help-modal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,3 +298,11 @@ body { font-family: var(--font-main); height: 100vh; overflow: hidden; backgroun
|
||||
.detection-preview-label { font-size: 10px; font-weight: bold; margin-bottom: 4px; }
|
||||
.dataset-form-error { color: #f00; font-size: 11px; margin-bottom: 12px; min-height: 16px; }
|
||||
.dataset-form-actions { display: flex; gap: 8px; margin-top: 16px; }
|
||||
|
||||
/* Help Modal */
|
||||
.help-shortcuts-table { width: 100%; border-collapse: collapse; }
|
||||
.help-shortcuts-table tbody tr { border-bottom: 1px solid var(--bg-lighter); }
|
||||
.help-shortcuts-table tbody tr:last-child { border-bottom: none; }
|
||||
.help-shortcuts-table td { padding: 12px 8px; font-size: 12px; }
|
||||
.shortcut-key { font-family: var(--font-mono); font-weight: bold; background: var(--bg-light); border: 1px solid var(--win-gray-dark); padding: 6px 10px; border-radius: 2px; white-space: nowrap; width: 180px; }
|
||||
.shortcut-desc { color: var(--win-gray-darker); }
|
||||
|
||||
Reference in New Issue
Block a user