Files
Oxicloud/static/js/core/modal.js
T

242 lines
7.4 KiB
JavaScript
Raw Normal View History

import { i18n } from './i18n.js';
import { replaceIconsInElement } from './icons.js';
2026-04-07 22:48:59 +02:00
/**
* Modal System for OxiCloud
* Provides modern, styled modals to replace browser prompts/alerts
*/
const Modal = {
// Modal element references
2026-04-24 23:37:14 +02:00
/** @private @type {HTMLElement | null} */
2026-04-07 22:48:59 +02:00
overlay: null,
2026-04-24 23:37:14 +02:00
// FIXME: unused ?
2026-04-07 22:48:59 +02:00
container: null,
2026-04-24 23:37:14 +02:00
/** @private @type {HTMLElement | null} */
2026-04-07 22:48:59 +02:00
icon: null,
2026-04-24 23:37:14 +02:00
/** @private @type {HTMLElement | null} */
2026-04-07 22:48:59 +02:00
title: null,
2026-04-24 23:37:14 +02:00
/** @private @type {HTMLElement | null} */
2026-04-07 22:48:59 +02:00
label: null,
2026-04-24 23:37:14 +02:00
/** @private @type {HTMLElement | null} */
2026-04-07 22:48:59 +02:00
input: null,
2026-04-24 23:37:14 +02:00
/** @private @type {HTMLElement | null} */
2026-04-07 22:48:59 +02:00
cancelBtn: null,
2026-04-24 23:37:14 +02:00
/** @private @type {HTMLElement | null} */
2026-04-07 22:48:59 +02:00
confirmBtn: null,
2026-04-24 23:37:14 +02:00
/** @private @type {HTMLElement | null} */
2026-04-07 22:48:59 +02:00
closeBtn: null,
// Current callback
onConfirm: null,
onCancel: null,
// Rename mode: select only name without extension
_selectNameOnly: false,
/**
* Initialize modal system
*/
init() {
this.overlay = document.getElementById('input-modal');
if (!this.overlay) {
console.warn('Modal overlay not found');
return;
}
this.icon = document.getElementById('modal-icon');
this.title = document.getElementById('modal-title');
this.label = document.getElementById('modal-label');
this.input = document.getElementById('modal-input');
this.cancelBtn = document.getElementById('modal-cancel-btn');
this.confirmBtn = document.getElementById('modal-confirm-btn');
this.closeBtn = document.getElementById('modal-close-btn');
// Event listeners
2026-04-30 02:02:27 +02:00
this.cancelBtn?.addEventListener('click', () => this.close(false));
this.closeBtn?.addEventListener('click', () => this.close(false));
this.confirmBtn?.addEventListener('click', () => this.confirm());
2026-04-07 22:48:59 +02:00
// Close on overlay click
this.overlay.addEventListener('click', (e) => {
if (e.target === this.overlay) {
this.close(false);
}
});
// Handle Enter and Escape keys
2026-04-30 02:02:27 +02:00
this.input?.addEventListener('keydown', (e) => {
2026-04-07 22:48:59 +02:00
if (e.key === 'Enter') {
e.preventDefault();
this.confirm();
} else if (e.key === 'Escape') {
this.close(false);
}
});
},
/**
* Show input modal (replacement for prompt())
* @param {Object} options - Modal configuration
* @param {string} options.title - Modal title
* @param {string} options.label - Input label
* @param {string} options.placeholder - Input placeholder
* @param {string} options.value - Initial input value
* @param {string} options.icon - Font Awesome icon class (e.g., 'fa-folder-plus')
* @param {string} options.confirmText - Confirm button text
* @param {string} options.cancelText - Cancel button text
* @returns {Promise<string|null>} - Resolves with input value or null if cancelled
*/
prompt(options = {}) {
return new Promise((resolve) => {
const { title = 'Input', label = '', placeholder = '', value = '', icon = 'fa-keyboard', confirmText = null, cancelText = null } = options;
// Set modal content - update the icon
const iconContainer = document.querySelector('.modal-icon');
if (iconContainer) {
// Replace contents with a fresh <i> that icons.js will convert
iconContainer.innerHTML = `<i id="modal-icon" class="fas ${icon}"></i>`;
this.icon = document.getElementById('modal-icon');
// Let icons.js convert it to SVG
if (replaceIconsInElement) {
replaceIconsInElement(iconContainer);
2026-04-07 22:48:59 +02:00
this.icon = document.getElementById('modal-icon');
}
}
this.title.textContent = title;
this.label.textContent = label;
this.input.placeholder = placeholder;
this.input.value = value;
if (confirmText) {
this.confirmBtn.textContent = confirmText;
} else {
this.confirmBtn.textContent = i18n.t('actions.confirm');
2026-04-07 22:48:59 +02:00
}
if (cancelText) {
this.cancelBtn.textContent = cancelText;
} else {
this.cancelBtn.textContent = i18n.t('actions.cancel');
2026-04-07 22:48:59 +02:00
}
// Set callbacks
this.onConfirm = () => {
const inputValue = this.input.value.trim();
resolve(inputValue || null);
};
this.onCancel = () => resolve(null);
// Show modal
this.open();
});
},
/**
* Show modal for creating new folder
* @returns {Promise<string|null>}
*/
promptNewFolder() {
return this.prompt({
title: i18n.t('dialogs.new_folder_title'),
label: i18n.t('dialogs.folder_name'),
placeholder: i18n.t('dialogs.folder_placeholder'),
2026-04-07 22:48:59 +02:00
icon: 'fa-folder-plus',
confirmText: i18n.t('actions.create')
2026-04-07 22:48:59 +02:00
});
},
/**
* Show modal for renaming
* @param {string} currentName - Current name of file/folder
* @param {boolean} isFolder - Whether it's a folder
* @returns {Promise<string|null>}
*/
promptRename(currentName, isFolder = false) {
this._selectNameOnly = !isFolder;
return this.prompt({
title: i18n.t('dialogs.rename_title'),
label: i18n.t('dialogs.new_name'),
2026-04-07 22:48:59 +02:00
placeholder: '',
value: currentName,
icon: isFolder ? 'fa-folder' : 'fa-file',
confirmText: i18n.t('actions.rename')
2026-04-07 22:48:59 +02:00
});
},
/**
* Open the modal
*/
open() {
if (!this.overlay) return;
// Show overlay
2026-04-24 23:37:14 +02:00
this.overlay.classList.remove('hidden');
2026-04-07 22:48:59 +02:00
// Trigger animation
requestAnimationFrame(() => {
this.overlay.classList.add('active');
});
// Focus input after animation
setTimeout(() => {
this.input.focus();
if (this._selectNameOnly) {
// Select only the filename, excluding the extension
const value = this.input.value;
const lastDot = value.lastIndexOf('.');
if (lastDot > 0) {
this.input.setSelectionRange(0, lastDot);
} else {
this.input.select();
}
this._selectNameOnly = false;
} else {
this.input.select();
}
}, 100);
},
/**
* Close the modal
* @param {boolean} confirmed - Whether the action was confirmed
*/
close(confirmed = false) {
if (!this.overlay) return;
this.overlay.classList.remove('active');
setTimeout(() => {
2026-04-24 23:37:14 +02:00
this.overlay.classList.add('hidden');
2026-04-07 22:48:59 +02:00
if (!confirmed && this.onCancel) {
this.onCancel();
}
// Clear callbacks
this.onConfirm = null;
this.onCancel = null;
}, 200);
},
/**
* Confirm the action
*/
confirm() {
if (this.onConfirm) {
this.onConfirm();
}
this.close(true);
}
};
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
Modal.init();
});
// Export for use in other modules
export { Modal };