mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2025-08-25 08:25:07 +03:00
Implement modal system for adding peers and logout confirmation in WebUI
This commit is contained in:
parent
fcb5efd753
commit
19710fbc19
7 changed files with 789 additions and 32 deletions
415
src/webui/static/modal.js
Normal file
415
src/webui/static/modal.js
Normal file
|
@ -0,0 +1,415 @@
|
|||
/**
|
||||
* Modal System for Yggdrasil Web Interface
|
||||
* Provides flexible modal dialogs with multiple action buttons and input forms
|
||||
*/
|
||||
|
||||
// Global modal state
|
||||
let currentModal = null;
|
||||
let modalCallbacks = {};
|
||||
|
||||
/**
|
||||
* Show a modal dialog
|
||||
* @param {Object} options - Modal configuration
|
||||
* @param {string} options.title - Modal title (supports localization key)
|
||||
* @param {string|HTMLElement} options.content - Modal content (supports localization key)
|
||||
* @param {Array} options.buttons - Array of button configurations
|
||||
* @param {Array} options.inputs - Array of input field configurations
|
||||
* @param {Function} options.onClose - Callback when modal is closed
|
||||
* @param {boolean} options.closable - Whether modal can be closed by clicking overlay or X button
|
||||
* @param {string} options.size - Modal size: 'small', 'medium', 'large'
|
||||
*/
|
||||
function showModal(options = {}) {
|
||||
const {
|
||||
title = 'Modal',
|
||||
content = '',
|
||||
buttons = [{ text: 'modal_close', type: 'secondary', action: 'close' }],
|
||||
inputs = [],
|
||||
onClose = null,
|
||||
closable = true,
|
||||
size = 'medium'
|
||||
} = options;
|
||||
|
||||
const overlay = document.getElementById('modal-overlay');
|
||||
const container = document.getElementById('modal-container');
|
||||
const titleElement = document.getElementById('modal-title');
|
||||
const contentElement = document.getElementById('modal-content');
|
||||
const footerElement = document.getElementById('modal-footer');
|
||||
const closeBtn = document.getElementById('modal-close-btn');
|
||||
|
||||
if (!overlay || !container) {
|
||||
console.error('Modal elements not found in DOM');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set modal size
|
||||
container.className = `modal-container modal-${size}`;
|
||||
|
||||
// Set title (with localization support)
|
||||
titleElement.textContent = getLocalizedText(title);
|
||||
|
||||
// Set content
|
||||
if (typeof content === 'string') {
|
||||
contentElement.innerHTML = `<p>${getLocalizedText(content)}</p>`;
|
||||
} else if (content instanceof HTMLElement) {
|
||||
contentElement.innerHTML = '';
|
||||
contentElement.appendChild(content);
|
||||
} else {
|
||||
contentElement.innerHTML = content;
|
||||
}
|
||||
|
||||
// Add input fields if provided
|
||||
if (inputs && inputs.length > 0) {
|
||||
const formContainer = document.createElement('div');
|
||||
formContainer.className = 'modal-form-container';
|
||||
|
||||
inputs.forEach((input, index) => {
|
||||
const formGroup = createFormGroup(input, index);
|
||||
formContainer.appendChild(formGroup);
|
||||
});
|
||||
|
||||
contentElement.appendChild(formContainer);
|
||||
}
|
||||
|
||||
// Create buttons
|
||||
footerElement.innerHTML = '';
|
||||
buttons.forEach((button, index) => {
|
||||
const btn = createModalButton(button, index);
|
||||
footerElement.appendChild(btn);
|
||||
});
|
||||
|
||||
// Configure close button
|
||||
closeBtn.style.display = closable ? 'flex' : 'none';
|
||||
|
||||
// Set up event handlers
|
||||
modalCallbacks.onClose = onClose;
|
||||
|
||||
// Close on overlay click (if closable)
|
||||
if (closable) {
|
||||
overlay.onclick = (e) => {
|
||||
if (e.target === overlay) {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
overlay.onclick = null;
|
||||
}
|
||||
|
||||
// Close on Escape key (if closable)
|
||||
if (closable) {
|
||||
document.addEventListener('keydown', handleEscapeKey);
|
||||
}
|
||||
|
||||
// Show modal
|
||||
currentModal = options;
|
||||
overlay.classList.add('show');
|
||||
|
||||
// Focus first input if available
|
||||
setTimeout(() => {
|
||||
const firstInput = contentElement.querySelector('.modal-form-input');
|
||||
if (firstInput) {
|
||||
firstInput.focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the current modal
|
||||
*/
|
||||
function closeModal() {
|
||||
const overlay = document.getElementById('modal-overlay');
|
||||
if (!overlay) return;
|
||||
|
||||
overlay.classList.remove('show');
|
||||
|
||||
// Clean up event listeners
|
||||
document.removeEventListener('keydown', handleEscapeKey);
|
||||
overlay.onclick = null;
|
||||
|
||||
// Call onClose callback if provided
|
||||
if (modalCallbacks.onClose) {
|
||||
modalCallbacks.onClose();
|
||||
}
|
||||
|
||||
// Clear state
|
||||
currentModal = null;
|
||||
modalCallbacks = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a form group element
|
||||
*/
|
||||
function createFormGroup(input, index) {
|
||||
const {
|
||||
type = 'text',
|
||||
name = `input_${index}`,
|
||||
label = '',
|
||||
placeholder = '',
|
||||
value = '',
|
||||
required = false,
|
||||
help = '',
|
||||
options = [] // for select inputs
|
||||
} = input;
|
||||
|
||||
const formGroup = document.createElement('div');
|
||||
formGroup.className = 'modal-form-group';
|
||||
|
||||
// Create label
|
||||
if (label) {
|
||||
const labelElement = document.createElement('label');
|
||||
labelElement.className = 'modal-form-label';
|
||||
labelElement.textContent = getLocalizedText(label);
|
||||
labelElement.setAttribute('for', name);
|
||||
formGroup.appendChild(labelElement);
|
||||
}
|
||||
|
||||
// Create input element
|
||||
let inputElement;
|
||||
|
||||
if (type === 'textarea') {
|
||||
inputElement = document.createElement('textarea');
|
||||
inputElement.className = 'modal-form-textarea';
|
||||
} else if (type === 'select') {
|
||||
inputElement = document.createElement('select');
|
||||
inputElement.className = 'modal-form-select';
|
||||
|
||||
// Add options
|
||||
options.forEach(option => {
|
||||
const optionElement = document.createElement('option');
|
||||
optionElement.value = option.value || option;
|
||||
optionElement.textContent = getLocalizedText(option.text || option);
|
||||
if (option.selected || option.value === value) {
|
||||
optionElement.selected = true;
|
||||
}
|
||||
inputElement.appendChild(optionElement);
|
||||
});
|
||||
} else {
|
||||
inputElement = document.createElement('input');
|
||||
inputElement.type = type;
|
||||
inputElement.className = 'modal-form-input';
|
||||
inputElement.value = value;
|
||||
}
|
||||
|
||||
inputElement.name = name;
|
||||
inputElement.id = name;
|
||||
|
||||
if (placeholder) {
|
||||
inputElement.placeholder = getLocalizedText(placeholder);
|
||||
}
|
||||
|
||||
if (required) {
|
||||
inputElement.required = true;
|
||||
}
|
||||
|
||||
formGroup.appendChild(inputElement);
|
||||
|
||||
// Create help text
|
||||
if (help) {
|
||||
const helpElement = document.createElement('div');
|
||||
helpElement.className = 'modal-form-help';
|
||||
helpElement.textContent = getLocalizedText(help);
|
||||
formGroup.appendChild(helpElement);
|
||||
}
|
||||
|
||||
return formGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a modal button
|
||||
*/
|
||||
function createModalButton(button, index) {
|
||||
const {
|
||||
text = 'Button',
|
||||
type = 'secondary',
|
||||
action = null,
|
||||
callback = null,
|
||||
disabled = false
|
||||
} = button;
|
||||
|
||||
const btn = document.createElement('button');
|
||||
btn.className = `modal-btn modal-btn-${type}`;
|
||||
btn.textContent = getLocalizedText(text);
|
||||
btn.disabled = disabled;
|
||||
|
||||
btn.onclick = () => {
|
||||
if (action === 'close') {
|
||||
closeModal();
|
||||
} else if (callback) {
|
||||
const formData = getModalFormData();
|
||||
const result = callback(formData);
|
||||
|
||||
// If callback returns false, don't close modal
|
||||
if (result !== false) {
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get form data from modal inputs
|
||||
*/
|
||||
function getModalFormData() {
|
||||
const formData = {};
|
||||
const inputs = document.querySelectorAll('#modal-content .modal-form-input, #modal-content .modal-form-textarea, #modal-content .modal-form-select');
|
||||
|
||||
inputs.forEach(input => {
|
||||
formData[input.name] = input.value;
|
||||
});
|
||||
|
||||
return formData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Escape key press
|
||||
*/
|
||||
function handleEscapeKey(e) {
|
||||
if (e.key === 'Escape' && currentModal) {
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get localized text or return original if not found
|
||||
*/
|
||||
function getLocalizedText(key) {
|
||||
if (typeof key !== 'string') return key;
|
||||
|
||||
const currentLang = window.getCurrentLanguage ? window.getCurrentLanguage() : 'en';
|
||||
|
||||
if (window.translations &&
|
||||
window.translations[currentLang] &&
|
||||
window.translations[currentLang][key]) {
|
||||
return window.translations[currentLang][key];
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
// Convenience functions for common modal types
|
||||
|
||||
/**
|
||||
* Show a confirmation dialog
|
||||
*/
|
||||
function showConfirmModal(options = {}) {
|
||||
const {
|
||||
title = 'modal_confirm',
|
||||
message = 'modal_confirm_message',
|
||||
confirmText = 'modal_confirm_yes',
|
||||
cancelText = 'modal_cancel',
|
||||
onConfirm = null,
|
||||
onCancel = null,
|
||||
type = 'danger' // danger, primary, success
|
||||
} = options;
|
||||
|
||||
showModal({
|
||||
title,
|
||||
content: message,
|
||||
closable: true,
|
||||
buttons: [
|
||||
{
|
||||
text: cancelText,
|
||||
type: 'secondary',
|
||||
action: 'close',
|
||||
callback: onCancel
|
||||
},
|
||||
{
|
||||
text: confirmText,
|
||||
type: type,
|
||||
callback: () => {
|
||||
if (onConfirm) onConfirm();
|
||||
return true; // Close modal
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an alert dialog
|
||||
*/
|
||||
function showAlertModal(options = {}) {
|
||||
const {
|
||||
title = 'modal_alert',
|
||||
message = '',
|
||||
buttonText = 'modal_ok',
|
||||
type = 'primary',
|
||||
onClose = null
|
||||
} = options;
|
||||
|
||||
showModal({
|
||||
title,
|
||||
content: message,
|
||||
closable: true,
|
||||
onClose,
|
||||
buttons: [
|
||||
{
|
||||
text: buttonText,
|
||||
type: type,
|
||||
action: 'close'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a prompt dialog with input
|
||||
*/
|
||||
function showPromptModal(options = {}) {
|
||||
const {
|
||||
title = 'modal_input',
|
||||
message = '',
|
||||
inputLabel = '',
|
||||
inputPlaceholder = '',
|
||||
inputValue = '',
|
||||
inputType = 'text',
|
||||
inputRequired = true,
|
||||
confirmText = 'modal_ok',
|
||||
cancelText = 'modal_cancel',
|
||||
onConfirm = null,
|
||||
onCancel = null
|
||||
} = options;
|
||||
|
||||
showModal({
|
||||
title,
|
||||
content: message,
|
||||
closable: true,
|
||||
inputs: [
|
||||
{
|
||||
type: inputType,
|
||||
name: 'input_value',
|
||||
label: inputLabel,
|
||||
placeholder: inputPlaceholder,
|
||||
value: inputValue,
|
||||
required: inputRequired
|
||||
}
|
||||
],
|
||||
buttons: [
|
||||
{
|
||||
text: cancelText,
|
||||
type: 'secondary',
|
||||
action: 'close',
|
||||
callback: onCancel
|
||||
},
|
||||
{
|
||||
text: confirmText,
|
||||
type: 'primary',
|
||||
callback: (formData) => {
|
||||
if (inputRequired && !formData.input_value.trim()) {
|
||||
return false; // Don't close modal
|
||||
}
|
||||
if (onConfirm) onConfirm(formData.input_value);
|
||||
return true; // Close modal
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// Export functions to global scope
|
||||
window.showModal = showModal;
|
||||
window.closeModal = closeModal;
|
||||
window.showConfirmModal = showConfirmModal;
|
||||
window.showAlertModal = showAlertModal;
|
||||
window.showPromptModal = showPromptModal;
|
Loading…
Add table
Add a link
Reference in a new issue