Files
tourrism_site/public/js/universal-media-manager-integration.js
Andrey K. Choi 13c752b93a feat: Оптимизация навигации AdminJS в логические группы
- Объединены ресурсы в 5 логических групп: Контент сайта, Бронирования, Отзывы и рейтинги, Персонал и гиды, Администрирование
- Удалены дублирующие настройки navigation для чистой группировки
- Добавлены CSS стили для визуального отображения иерархии с отступами
- Добавлены эмодзи-иконки для каждого типа ресурсов через CSS
- Улучшена навигация с правильной вложенностью элементов
2025-11-30 21:57:58 +09:00

477 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Универсальная интеграция медиа-менеджера в AdminJS
* Заменяет все стандартные диалоги выбора файлов на медиа-менеджер
*/
(function() {
'use strict';
console.log('🚀 Загружается универсальный медиа-менеджер для AdminJS...');
let mediaManagerModal = null;
let currentCallback = null;
// Создание модального окна медиа-менеджера
function createMediaManagerModal() {
if (mediaManagerModal) return mediaManagerModal;
const modal = document.createElement('div');
modal.className = 'universal-media-modal';
modal.innerHTML = `
<div class="universal-media-overlay"></div>
<div class="universal-media-content">
<div class="universal-media-header">
<h3>📁 Выбор изображения</h3>
<button class="universal-media-close">×</button>
</div>
<iframe class="universal-media-frame" src="/universal-media-manager.html"></iframe>
</div>
`;
// CSS стили
const style = document.createElement('style');
style.textContent = `
.universal-media-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10000;
display: none;
align-items: center;
justify-content: center;
}
.universal-media-modal.active {
display: flex;
}
.universal-media-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
}
.universal-media-content {
position: relative;
width: 90vw;
height: 90vh;
max-width: 1200px;
max-height: 800px;
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.universal-media-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.universal-media-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
}
.universal-media-close {
background: none;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background-color 0.2s ease;
}
.universal-media-close:hover {
background: rgba(255, 255, 255, 0.2);
}
.universal-media-frame {
width: 100%;
height: calc(100% - 60px);
border: none;
}
/* Стили для кнопок медиа-менеджера */
.media-manager-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
margin: 5px;
}
.media-manager-btn:hover {
background: #0056b3;
transform: translateY(-1px);
}
.media-manager-btn.small {
padding: 4px 8px;
font-size: 12px;
}
/* Скрываем стандартные input[type="file"] */
.media-replaced input[type="file"] {
display: none !important;
}
/* Стили для preview изображений */
.media-preview {
max-width: 200px;
max-height: 150px;
border-radius: 6px;
margin: 10px 0;
border: 2px solid #e9ecef;
object-fit: cover;
}
.media-preview.selected {
border-color: #28a745;
}
`;
if (!document.querySelector('#universal-media-styles')) {
style.id = 'universal-media-styles';
document.head.appendChild(style);
}
// События
const closeBtn = modal.querySelector('.universal-media-close');
const overlay = modal.querySelector('.universal-media-overlay');
closeBtn.addEventListener('click', closeMediaManager);
overlay.addEventListener('click', closeMediaManager);
document.body.appendChild(modal);
mediaManagerModal = modal;
return modal;
}
// Открытие медиа-менеджера
function openMediaManager(callback, options = {}) {
const modal = createMediaManagerModal();
currentCallback = callback;
// Обновляем заголовок если нужно
const header = modal.querySelector('.universal-media-header h3');
header.textContent = options.title || '📁 Выбор изображения';
modal.classList.add('active');
document.body.style.overflow = 'hidden';
}
// Закрытие медиа-менеджера
function closeMediaManager() {
if (mediaManagerModal) {
mediaManagerModal.classList.remove('active');
document.body.style.overflow = '';
currentCallback = null;
}
}
// Обработка сообщений от медиа-менеджера
window.addEventListener('message', function(event) {
if (event.data.type === 'media-manager-selection' && currentCallback) {
const files = event.data.files;
if (files && files.length > 0) {
currentCallback(files);
closeMediaManager();
}
}
});
// Замена стандартных input[type="file"] на медиа-менеджер
function replaceFileInputs() {
const fileInputs = document.querySelectorAll('input[type="file"]:not(.media-replaced)');
fileInputs.forEach(input => {
if (input.accept && !input.accept.includes('image')) {
return; // Пропускаем не-изображения
}
input.classList.add('media-replaced');
// Создаем кнопку медиа-менеджера
const button = document.createElement('button');
button.type = 'button';
button.className = 'media-manager-btn';
button.innerHTML = '📷 Выбрать изображение';
// Добавляем preview
const preview = document.createElement('img');
preview.className = 'media-preview';
preview.style.display = 'none';
// Добавляем скрытый input для хранения пути
const hiddenInput = document.createElement('input');
hiddenInput.type = 'hidden';
hiddenInput.name = input.name;
hiddenInput.value = input.value || '';
// Событие клика
button.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
openMediaManager((files) => {
const file = files[0];
// Обновляем значения
hiddenInput.value = file.url;
input.value = file.url;
// Показываем preview
preview.src = file.url;
preview.style.display = 'block';
preview.alt = file.name;
// Обновляем кнопку
button.innerHTML = '✏️ Заменить изображение';
// Добавляем кнопку удаления
if (!button.nextElementSibling?.classList.contains('media-remove-btn')) {
const removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.className = 'media-manager-btn small';
removeBtn.style.background = '#dc3545';
removeBtn.innerHTML = '🗑️ Удалить';
removeBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
// Очищаем значения
hiddenInput.value = '';
input.value = '';
// Скрываем preview
preview.style.display = 'none';
// Восстанавливаем кнопку
button.innerHTML = '📷 Выбрать изображение';
removeBtn.remove();
});
button.parentElement.insertBefore(removeBtn, button.nextSibling);
}
// Вызываем событие change для совместимости
const changeEvent = new Event('change', { bubbles: true });
input.dispatchEvent(changeEvent);
}, {
title: input.dataset.title || 'Выбор изображения'
});
});
// Вставляем элементы
input.parentElement.insertBefore(button, input.nextSibling);
input.parentElement.insertBefore(preview, button.nextSibling);
input.parentElement.insertBefore(hiddenInput, preview.nextSibling);
// Если есть начальное значение, показываем preview
if (input.value) {
preview.src = input.value;
preview.style.display = 'block';
button.innerHTML = '✏️ Заменить изображение';
hiddenInput.value = input.value;
}
});
}
// Замена кнопок "Browse" в формах AdminJS
function replaceAdminJSBrowseButtons() {
// Ищем кнопки загрузки файлов AdminJS
const browseButtons = document.querySelectorAll('button[type="button"]:not(.media-replaced)');
browseButtons.forEach(button => {
const buttonText = button.textContent.toLowerCase();
if (buttonText.includes('browse') ||
buttonText.includes('выбрать') ||
buttonText.includes('загрузить') ||
buttonText.includes('upload')) {
button.classList.add('media-replaced');
// Заменяем обработчик клика
const originalHandler = button.onclick;
button.onclick = null;
button.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
openMediaManager((files) => {
const file = files[0];
// Ищем соответствующий input
const container = button.closest('.form-group, .field, .input-group');
const input = container?.querySelector('input[type="text"], input[type="url"], input[type="hidden"]');
if (input) {
input.value = file.url;
// Вызываем событие change
const changeEvent = new Event('change', { bubbles: true });
input.dispatchEvent(changeEvent);
// Обновляем preview если есть
const preview = container.querySelector('img');
if (preview) {
preview.src = file.url;
}
}
});
});
// Обновляем текст кнопки
button.innerHTML = '📷 Медиа-менеджер';
}
});
}
// Интеграция с полями изображений AdminJS
function integrateWithAdminJSImageFields() {
// Ищем поля с атрибутом accept="image/*"
const imageFields = document.querySelectorAll('input[accept*="image"]:not(.media-replaced)');
imageFields.forEach(field => {
field.classList.add('media-replaced');
const container = field.closest('.form-group, .field');
if (!container) return;
// Создаем кнопку медиа-менеджера
const mediaBtn = document.createElement('button');
mediaBtn.type = 'button';
mediaBtn.className = 'media-manager-btn';
mediaBtn.innerHTML = '📷 Открыть медиа-менеджер';
mediaBtn.addEventListener('click', (e) => {
e.preventDefault();
openMediaManager((files) => {
const file = files[0];
// Обновляем поле
field.value = file.url;
// Создаем событие change
const event = new Event('change', { bubbles: true });
field.dispatchEvent(event);
// Если есть label, обновляем его
const label = container.querySelector('label');
if (label && !label.querySelector('.selected-file')) {
const selectedSpan = document.createElement('span');
selectedSpan.className = 'selected-file';
selectedSpan.style.cssText = 'color: #28a745; font-weight: 500; margin-left: 10px;';
selectedSpan.textContent = `${file.name}`;
label.appendChild(selectedSpan);
}
});
});
// Вставляем кнопку после поля
field.parentElement.insertBefore(mediaBtn, field.nextSibling);
});
}
// Основная функция инициализации
function initMediaManager() {
console.log('🔧 Инициализация медиа-менеджера...');
// Замена различных типов полей
replaceFileInputs();
replaceAdminJSBrowseButtons();
integrateWithAdminJSImageFields();
console.log('✅ Медиа-менеджер инициализирован');
}
// Наблюдатель за изменениями DOM
const observer = new MutationObserver((mutations) => {
let shouldReinit = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1) { // Element node
if (node.querySelector && (
node.querySelector('input[type="file"]') ||
node.querySelector('input[accept*="image"]') ||
node.querySelector('button[type="button"]')
)) {
shouldReinit = true;
}
}
});
}
});
if (shouldReinit) {
setTimeout(initMediaManager, 100);
}
});
// Запуск
function start() {
// Ждем загрузки DOM
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initMediaManager);
} else {
initMediaManager();
}
// Запуск наблюдателя
observer.observe(document.body, {
childList: true,
subtree: true
});
// Переинициализация при изменениях в SPA
let lastUrl = location.href;
setInterval(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
setTimeout(initMediaManager, 500);
}
}, 1000);
}
// Глобальная функция для ручного открытия медиа-менеджера
window.openUniversalMediaManager = function(callback, options) {
openMediaManager(callback, options);
};
// Запуск
start();
})();