feat: implement panel visibility toggles and toast notifications using Alpine.js

This commit is contained in:
2025-11-24 23:02:00 +02:00
parent 93299b1c79
commit ba89c3bd3a
5 changed files with 165 additions and 94 deletions

View File

@@ -143,13 +143,7 @@ document.addEventListener('DOMContentLoaded', function () {
initializeURLStateManagement();
});
// Toggle panel buttons
document.querySelectorAll('[id^="toggle-"][id$="-panel"]').forEach(button => {
button.addEventListener('click', function () {
const panelId = this.id.replace('toggle-', '');
togglePanel(panelId);
});
});
// Toggle panel buttons (now handled by Alpine.js in index.html)
// Header links
const importLink = document.getElementById('import-link');

View File

@@ -144,63 +144,68 @@ const AppSettings = {
}
};
// Toast Notification System
// Alpine.js Store for toast notifications
document.addEventListener('alpine:init', () => {
Alpine.store('toasts', {
items: [],
counter: 0,
DURATION: 4000,
add(message, type = 'info') {
const id = ++this.counter;
const toast = { id, message, type, visible: false };
this.items.push(toast);
// Trigger show animation on next tick
setTimeout(() => {
const found = this.items.find(t => t.id === id);
if (found) found.visible = true;
}, 10);
// Auto-dismiss
setTimeout(() => this.remove(id), this.DURATION);
},
remove(id) {
const toast = this.items.find(t => t.id === id);
if (toast) {
toast.visible = false;
// Remove from array after animation
setTimeout(() => {
this.items = this.items.filter(t => t.id !== id);
}, 300);
}
},
getIcon(type) {
const icons = {
error: '❌',
success: '✓',
warning: '⚠️',
info: ''
};
return icons[type] || icons.info;
}
});
});
// Toast Notification System (now backed by Alpine store)
const Toast = {
// Auto-dismiss duration in milliseconds
DURATION: 4000,
// Toast counter for unique IDs
toastCounter: 0,
// Show toast notification
show(message, type = 'info') {
const container = document.getElementById('toast-container');
if (!container) return;
// Create toast element
const toast = document.createElement('div');
const toastId = `toast-${++this.toastCounter}`;
toast.id = toastId;
toast.className = `toast toast-${type}`;
// Toast icon based on type
const icons = {
error: '❌',
success: '✓',
warning: '⚠️',
info: ''
};
toast.innerHTML = `
<span class="toast-icon">${icons[type] || icons.info}</span>
<span class="toast-message">${message}</span>
<button class="toast-close" onclick="Toast.dismiss('${toastId}')">×</button>
`;
// Add to container
container.appendChild(toast);
// Trigger animation
setTimeout(() => toast.classList.add('toast-show'), 10);
// Auto-dismiss
setTimeout(() => this.dismiss(toastId), this.DURATION);
if (Alpine.store('toasts')) {
Alpine.store('toasts').add(message, type);
}
},
// Dismiss specific toast
dismiss(toastId) {
const toast = document.getElementById(toastId);
if (!toast) return;
toast.classList.remove('toast-show');
toast.classList.add('toast-hide');
// Remove from DOM after animation
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 300);
if (Alpine.store('toasts')) {
Alpine.store('toasts').remove(toastId);
}
},
// Convenience methods

View File

@@ -1,3 +1,12 @@
// Alpine.js Store for panel visibility state
document.addEventListener('alpine:init', () => {
Alpine.store('panels', {
snippetVisible: true,
editorVisible: true,
previewVisible: true
});
});
// Panel toggle and expansion functions
function updatePanelMemory() {
const snippetPanel = document.getElementById('snippet-panel');
@@ -19,26 +28,34 @@ function updatePanelMemory() {
function togglePanel(panelId) {
const panel = document.getElementById(panelId);
const button = document.getElementById(`toggle-${panelId}`);
if (!panel || !button) return;
if (!panel) return;
if (panel.style.display === 'none') {
const isVisible = panel.style.display !== 'none';
const newVisibility = !isVisible;
// Update panel display
if (newVisibility) {
// Show panel
panel.style.display = 'flex';
button.classList.add('active');
// Restore from memory and redistribute
redistributePanelWidths();
} else {
// Hide panel - DON'T update memory, just hide
panel.style.display = 'none';
button.classList.remove('active');
// Redistribute remaining panels
redistributePanelWidths();
}
// Update Alpine store for button states
if (Alpine.store('panels')) {
if (panelId === 'snippet-panel') {
Alpine.store('panels').snippetVisible = newVisibility;
} else if (panelId === 'editor-panel') {
Alpine.store('panels').editorVisible = newVisibility;
} else if (panelId === 'preview-panel') {
Alpine.store('panels').previewVisible = newVisibility;
}
}
saveLayoutToStorage();
}
@@ -113,10 +130,12 @@ function loadLayoutFromStorage() {
editorPanel.style.display = layout.editorVisible !== false ? 'flex' : 'none';
previewPanel.style.display = layout.previewVisible !== false ? 'flex' : 'none';
// Update toggle button states
document.getElementById('toggle-snippet-panel').classList.toggle('active', layout.snippetVisible !== false);
document.getElementById('toggle-editor-panel').classList.toggle('active', layout.editorVisible !== false);
document.getElementById('toggle-preview-panel').classList.toggle('active', layout.previewVisible !== false);
// Update Alpine store for button states
if (Alpine.store('panels')) {
Alpine.store('panels').snippetVisible = layout.snippetVisible !== false;
Alpine.store('panels').editorVisible = layout.editorVisible !== false;
Alpine.store('panels').previewVisible = layout.previewVisible !== false;
}
// Restore widths and redistribute
snippetPanel.style.width = layout.snippetWidth;