refactor: implement view mode toggle using Alpine.js for draft/published states

This commit is contained in:
2025-11-24 18:33:00 +02:00
parent d08f995feb
commit edcbf0ed2b
5 changed files with 47 additions and 42 deletions

View File

@@ -196,7 +196,7 @@
<div class="resize-handle" id="resize-handle-1"></div> <div class="resize-handle" id="resize-handle-1"></div>
<!-- Editor Panel --> <!-- Editor Panel -->
<div class="panel editor-panel" id="editor-panel"> <div class="panel editor-panel" id="editor-panel" x-data>
<div class="panel-header"> <div class="panel-header">
<span>Editor</span> <span>Editor</span>
<div class="editor-controls"> <div class="editor-controls">
@@ -205,8 +205,16 @@
<button class="btn btn-action revert" id="revert-btn" title="Discard draft and revert to published version">Revert</button> <button class="btn btn-action revert" id="revert-btn" title="Discard draft and revert to published version">Revert</button>
<span class="view-label">View:</span> <span class="view-label">View:</span>
<div class="view-toggle-group"> <div class="view-toggle-group">
<button class="btn btn-toggle active" id="view-draft" title="View and edit draft version">Draft</button> <button class="btn btn-toggle"
<button class="btn btn-toggle" id="view-published" title="View published version (read-only if draft exists)">Published</button> id="view-draft"
:class="{ 'active': $store.snippets.viewMode === 'draft' }"
@click="$store.snippets.viewMode = 'draft'; switchViewMode('draft')"
title="View and edit draft version">Draft</button>
<button class="btn btn-toggle"
id="view-published"
:class="{ 'active': $store.snippets.viewMode === 'published' }"
@click="$store.snippets.viewMode = 'published'; switchViewMode('published')"
title="View published version (read-only if draft exists)">Published</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -104,26 +104,38 @@ Incremental migration of Astrolabe from vanilla JavaScript to Alpine.js for reac
--- ---
## Phase 3: View Mode Toggle (Draft/Published) ## Phase 3: View Mode Toggle (Draft/Published) ✅ COMPLETE
**Status**: Planned **Status**: Done
**Files**: `index.html`, `src/js/snippet-manager.js` **Files**: `index.html`, `src/js/snippet-manager.js`, `src/js/app.js`, `src/js/config.js`
### What to Convert ### What Was Converted
View mode toggle buttons (Draft/Published) currently use manual class manipulation. - View mode toggle buttons (Draft/Published) with `:class` binding and `@click` handlers
- All references to `currentViewMode` global variable now use Alpine store
- Removed vanilla event listeners from app.js
- Removed `currentViewMode` global variable from config.js
### Implementation Approach ### Implementation Approach
1. Add `viewMode` property to Alpine snippets store (default: 'draft') 1. Added `viewMode` property to Alpine snippets store (default: 'draft')
2. Convert button HTML to use `:class` binding and `@click` handlers 2. Converted button HTML to use `:class` binding and `@click` handlers
3. Update `loadSnippetIntoEditor()` to read view mode from Alpine store 3. Updated all references to `currentViewMode` to use `Alpine.store('snippets').viewMode`
4. Remove `updateViewModeUI()` function (no longer needed) 4. Simplified `updateViewModeUI()` function (now only handles publish/revert button visibility)
5. Removed vanilla event listeners from app.js
### What Stays Vanilla ### What Stays Vanilla
- Editor integration logic - Editor integration logic
- Publish/discard actions - Publish/discard actions
- Publish/revert button visibility logic (handled by `updateViewModeUI`)
### Key Learnings
- Alpine store provides clean reactive state for view mode
- Toggle button active states now automatically update via Alpine `:class` binding
- All business logic references updated to use Alpine store instead of global variable
- `updateViewModeUI` simplified but still needed for publish/revert button management
--- ---
@@ -248,7 +260,7 @@ Toast notification system with auto-dismiss.
1.**Phase 1: Snippet Panel** - DONE 1.**Phase 1: Snippet Panel** - DONE
2.**Phase 2: Dataset Manager** - DONE 2.**Phase 2: Dataset Manager** - DONE
3. **Phase 3: View Mode Toggle** - Quick win 3. **Phase 3: View Mode Toggle** - DONE
4. **Phase 4: Settings Modal** - Another modal, builds confidence 4. **Phase 4: Settings Modal** - Another modal, builds confidence
5. **Phase 6: Meta Fields** - Before Chart Builder (simpler) 5. **Phase 6: Meta Fields** - Before Chart Builder (simpler)
6. **Phase 7: Panel Toggles** - Quick win 6. **Phase 7: Panel Toggles** - Quick win

View File

@@ -367,14 +367,7 @@ document.addEventListener('DOMContentLoaded', function () {
} }
}); });
// View mode toggle buttons // View mode toggle buttons (now handled by Alpine.js in index.html)
document.getElementById('view-draft').addEventListener('click', () => {
switchViewMode('draft');
});
document.getElementById('view-published').addEventListener('click', () => {
switchViewMode('published');
});
// Preview fit mode buttons // Preview fit mode buttons
document.getElementById('preview-fit-default').addEventListener('click', () => { document.getElementById('preview-fit-default').addEventListener('click', () => {
@@ -481,7 +474,7 @@ const KeyboardActions = {
}, },
publishDraft: function() { publishDraft: function() {
if (currentViewMode === 'draft' && window.currentSnippetId) { if (Alpine.store('snippets').viewMode === 'draft' && window.currentSnippetId) {
publishDraft(); publishDraft();
} }
}, },

View File

@@ -4,7 +4,6 @@ const APP_VERSION = '0.3.0';
// Global variables and configuration // Global variables and configuration
let editor; // Global editor instance let editor; // Global editor instance
let renderTimeout; // For debouncing let renderTimeout; // For debouncing
let currentViewMode = 'draft'; // Track current view mode: 'draft' or 'published'
// Panel resizing variables // Panel resizing variables
let isResizing = false; let isResizing = false;

View File

@@ -4,7 +4,8 @@
// Business logic stays in SnippetStorage // Business logic stays in SnippetStorage
document.addEventListener('alpine:init', () => { document.addEventListener('alpine:init', () => {
Alpine.store('snippets', { Alpine.store('snippets', {
currentSnippetId: null currentSnippetId: null,
viewMode: 'draft' // 'draft' or 'published'
}); });
}); });
@@ -559,7 +560,7 @@ function autoSaveDraft() {
if (!window.currentSnippetId || !editor) return; if (!window.currentSnippetId || !editor) return;
// Only save to draft if we're in draft mode // Only save to draft if we're in draft mode
if (currentViewMode !== 'draft') return; if (Alpine.store('snippets').viewMode !== 'draft') return;
try { try {
const currentSpec = JSON.parse(editor.getValue()); const currentSpec = JSON.parse(editor.getValue());
@@ -592,13 +593,13 @@ function debouncedAutoSave() {
if (window.isUpdatingEditor) return; if (window.isUpdatingEditor) return;
// If viewing published and no draft exists, create draft automatically // If viewing published and no draft exists, create draft automatically
if (currentViewMode === 'published') { if (Alpine.store('snippets').viewMode === 'published') {
const snippet = getCurrentSnippet(); const snippet = getCurrentSnippet();
if (snippet) { if (snippet) {
const hasDraft = JSON.stringify(snippet.spec) !== JSON.stringify(snippet.draftSpec); const hasDraft = JSON.stringify(snippet.spec) !== JSON.stringify(snippet.draftSpec);
if (!hasDraft) { if (!hasDraft) {
// No draft exists, automatically switch to draft mode // No draft exists, automatically switch to draft mode
currentViewMode = 'draft'; Alpine.store('snippets').viewMode = 'draft';
updateViewModeUI(snippet); updateViewModeUI(snippet);
editor.updateOptions({ readOnly: false }); editor.updateOptions({ readOnly: false });
} }
@@ -859,7 +860,7 @@ async function extractToDataset() {
SnippetStorage.saveSnippet(snippet); SnippetStorage.saveSnippet(snippet);
// Update editor with new spec // Update editor with new spec
if (editor && currentViewMode === 'draft') { if (editor && Alpine.store('snippets').viewMode === 'draft') {
window.isUpdatingEditor = true; window.isUpdatingEditor = true;
editor.setValue(JSON.stringify(snippet.draftSpec, null, 2)); editor.setValue(JSON.stringify(snippet.draftSpec, null, 2));
window.isUpdatingEditor = false; window.isUpdatingEditor = false;
@@ -935,7 +936,7 @@ function loadSnippetIntoEditor(snippet) {
const hasDraft = JSON.stringify(snippet.spec) !== JSON.stringify(snippet.draftSpec); const hasDraft = JSON.stringify(snippet.spec) !== JSON.stringify(snippet.draftSpec);
if (currentViewMode === 'draft') { if (Alpine.store('snippets').viewMode === 'draft') {
editor.setValue(JSON.stringify(snippet.draftSpec, null, 2)); editor.setValue(JSON.stringify(snippet.draftSpec, null, 2));
editor.updateOptions({ readOnly: false }); editor.updateOptions({ readOnly: false });
} else { } else {
@@ -954,19 +955,11 @@ function updateViewModeUI(snippet) {
const publishBtn = document.getElementById('publish-btn'); const publishBtn = document.getElementById('publish-btn');
const revertBtn = document.getElementById('revert-btn'); const revertBtn = document.getElementById('revert-btn');
// Update toggle button states // Update toggle button states (now handled by Alpine :class binding)
if (currentViewMode === 'draft') { // But we still need to update the action buttons (publish/revert)
draftBtn.classList.add('active');
publishedBtn.classList.remove('active');
} else {
draftBtn.classList.remove('active');
publishedBtn.classList.add('active');
}
// Show/hide and enable/disable action buttons based on mode
const hasDraft = JSON.stringify(snippet.spec) !== JSON.stringify(snippet.draftSpec); const hasDraft = JSON.stringify(snippet.spec) !== JSON.stringify(snippet.draftSpec);
if (currentViewMode === 'draft') { if (Alpine.store('snippets').viewMode === 'draft') {
// In draft mode: show both buttons, enable based on draft existence // In draft mode: show both buttons, enable based on draft existence
publishBtn.classList.add('visible'); publishBtn.classList.add('visible');
revertBtn.classList.add('visible'); revertBtn.classList.add('visible');
@@ -981,7 +974,7 @@ function updateViewModeUI(snippet) {
// Switch view mode // Switch view mode
function switchViewMode(mode) { function switchViewMode(mode) {
currentViewMode = mode; Alpine.store('snippets').viewMode = mode;
const snippet = getCurrentSnippet(); const snippet = getCurrentSnippet();
if (snippet) { if (snippet) {
loadSnippetIntoEditor(snippet); loadSnippetIntoEditor(snippet);
@@ -1026,7 +1019,7 @@ function revertDraft() {
SnippetStorage.saveSnippet(snippet); SnippetStorage.saveSnippet(snippet);
// Reload editor if in draft view // Reload editor if in draft view
if (currentViewMode === 'draft') { if (Alpine.store('snippets').viewMode === 'draft') {
loadSnippetIntoEditor(snippet); loadSnippetIntoEditor(snippet);
} }