/**
* SIS Media Picker & Library Modal Utility
* Standalone reusable module to select and upload media files anywhere in the application.
*
* Usage (simple - just URL returned):
* window.openSISMediaModal(function(selectedUrl, selectedMediaObj) {
* console.log("Selected Image URL:", selectedUrl);
* });
*
* Usage (extended - returns URL + config object with style, attributes, size):
* SISMediaPicker.open(function(selectedUrl, selectedMediaObj, config) {
* // config = { style, customAttributes, width, height }
* });
*
* Or via object API:
* SISMediaPicker.open(callback, options);
* options = { showConfig: true } // default: true
*/
(function (window, document) {
'use strict';
var PREDEFINED_SIZES = [
{ label: 'Auto', width: '', height: '' },
{ label: '25%', width: '25%', height: '' },
{ label: '50%', width: '50%', height: '' },
{ label: '75%', width: '75%', height: '' },
{ label: '100%', width: '100%', height: '' },
{ label: '100px', width: '100px', height: '100px' },
{ label: '150px', width: '150px', height: '150px' },
{ label: '200px', width: '200px', height: '200px' },
{ label: '300px', width: '300px', height: 'auto' },
{ label: '400px', width: '400px', height: 'auto' },
{ label: '500px', width: '500px', height: 'auto' },
{ label: 'Full Width', width: '100%', height: 'auto' },
{ label: 'Thumbnail (80×80)', width: '80px', height: '80px' },
{ label: 'Avatar (120×120)', width: '120px', height: '120px' },
{ label: 'Banner (1200×400)', width: '1200px', height: '400px' },
];
var SISMediaPicker = {
callback: null,
mediaData: [],
modalId: 'sisMediaLibraryModal',
configPanelId: 'sisMediaConfigPanel',
selectedUrl: null,
selectedMediaObj: null,
initModal: function () {
var modalEl = document.getElementById(this.modalId);
if (modalEl) return modalEl;
modalEl = document.createElement('div');
modalEl.id = this.modalId;
modalEl.className = 'modal fade';
modalEl.setAttribute('tabindex', '-1');
modalEl.setAttribute('role', 'dialog');
modalEl.setAttribute('aria-hidden', 'true');
modalEl.innerHTML =
'
' +
'
' +
'' +
'
' +
/* Toolbar row */
'
' +
'
' +
'
' +
'' +
'
' +
'
' +
/* Media Grid */
'
' +
/* Config Panel - hidden until an image is selected */
'
' +
'
Cấu hình hình ảnh đã chọn
' +
'
' +
/* Preview */
'
' +
'
Xem trước
' +
'
' +
'
![Preview]()
' +
'
' +
'
' +
'
' +
/* Config Fields */
'
' +
'
' +
/* Predefined Sizes */
'
' +
'
' +
'
' +
'
' +
'
' +
/* Width */
'
' +
'' +
'' +
'
' +
/* Height */
'
' +
'' +
'' +
'
' +
/* Object Fit */
'
' +
'' +
'' +
'
' +
/* Aspect Ratio */
'
' +
'' +
'' +
'
' +
/* CSS Class */
'
' +
'' +
'' +
'
' +
/* Inline Style */
'
' +
'' +
'' +
'
' +
/* Alt Text */
'
' +
'' +
'' +
'
' +
/* Custom Attributes */
'
' +
'' +
'' +
'
' +
'
' +
'
' +
'' +
'' +
'
' +
'
' +
'
' +
'
' +
'
' +
'' +
'
' +
'
';
document.body.appendChild(modalEl);
// Build predefined size buttons
var sizeContainer = document.getElementById('sisMediaSizeButtons');
if (sizeContainer) {
PREDEFINED_SIZES.forEach(function (size) {
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'btn btn-xs btn-outline-secondary mb-1';
btn.style.fontSize = '10px';
btn.style.padding = '1px 6px';
btn.style.marginRight = '3px';
btn.innerText = size.label;
btn.addEventListener('click', function () {
document.getElementById('sisMediaConfigWidth').value = size.width;
document.getElementById('sisMediaConfigHeight').value = size.height;
sizeContainer.querySelectorAll('button').forEach(function (b) {
b.classList.remove('btn-secondary');
b.classList.add('btn-outline-secondary');
});
btn.classList.remove('btn-outline-secondary');
btn.classList.add('btn-secondary');
});
sizeContainer.appendChild(btn);
});
}
// Bind Search Input Handler
var self = this;
document.addEventListener('input', function (e) {
if (e.target && e.target.id === 'sisMediaSearchInput') {
var term = e.target.value.toLowerCase().trim();
var filtered = self.mediaData.filter(function (item) {
var name = (item.originalFilename || item.name || '').toLowerCase();
return name.includes(term);
});
self.renderGrid(filtered);
}
});
// Bind Upload Input Handler
document.addEventListener('change', function (e) {
if (e.target && e.target.id === 'sisMediaUploadInput') {
var file = e.target.files[0];
if (!file) return;
var formData = new FormData();
formData.append('file', file);
var grid = document.getElementById('sisMediaGrid');
if (grid) {
grid.innerHTML = '
Đang tải tệp lên server...
';
}
fetch('/api/manage/media/upload', {
method: 'POST',
body: formData
})
.then(function (res) { return res.json(); })
.then(function (data) {
if (data && data.success === 1 && data.file && data.file.url) {
self.showConfigPanel(data.file.url, data.file);
} else {
alert('Tải tệp lên thất bại. Vui lòng thử lại!');
self.fetchMediaList();
}
})
.catch(function (err) {
alert('Lỗi tải tệp: ' + err.message);
self.fetchMediaList();
});
}
});
// Config panel Cancel button
document.addEventListener('click', function (e) {
if (e.target && e.target.id === 'sisMediaConfigCancel') {
self.hideConfigPanel();
}
if (e.target && e.target.id === 'sisMediaConfigConfirm') {
self.confirmWithConfig();
}
});
return modalEl;
},
open: function (callback) {
this.callback = callback;
var modalEl = this.initModal();
if (typeof $ !== 'undefined' && $.fn && $.fn.modal) {
$(modalEl).modal('show');
} else {
modalEl.style.display = 'block';
modalEl.classList.add('show');
}
this.fetchMediaList();
},
fetchMediaList: function () {
var grid = document.getElementById('sisMediaGrid');
if (grid) {
grid.innerHTML = '
Đang nạp thư viện...
';
}
this.hideConfigPanel();
var self = this;
fetch('/api/manage/media/list')
.then(function (res) { return res.json(); })
.then(function (data) {
self.mediaData = data || [];
self.renderGrid(self.mediaData);
})
.catch(function (err) {
if (grid) {
grid.innerHTML = '
Không thể tải danh sách tệp. Bạn có thể dán đường dẫn URL trực tiếp.
';
}
});
},
renderGrid: function (list) {
var grid = document.getElementById('sisMediaGrid');
if (!grid) return;
if (!list || list.length === 0) {
grid.innerHTML = '
Chưa có tệp hình ảnh nào. Bấm nút "Tải ảnh mới lên" để thêm.
';
return;
}
var self = this;
grid.innerHTML = list.map(function (item) {
var url = item.fileUrl || item.url || '';
var name = item.originalFilename || item.name || 'Image';
return '';
}).join('');
// Add click listeners to items
grid.querySelectorAll('.media-select-card').forEach(function (card) {
card.addEventListener('click', function () {
var selectedUrl = card.getAttribute('data-url');
var selectedName = card.getAttribute('data-name');
// Highlight selected
grid.querySelectorAll('.media-select-card').forEach(function (c) {
c.style.outline = '';
});
card.style.outline = '3px solid #007bff';
self.showConfigPanel(selectedUrl, { name: selectedName, url: selectedUrl });
});
});
},
showConfigPanel: function (url, mediaObj) {
this.selectedUrl = url;
this.selectedMediaObj = mediaObj || {};
var panel = document.getElementById(this.configPanelId);
if (!panel) return;
// Populate preview
var previewImg = document.getElementById('sisMediaConfigPreview');
if (previewImg) previewImg.src = url;
var nameEl = document.getElementById('sisMediaConfigName');
if (nameEl) nameEl.textContent = mediaObj && (mediaObj.originalFilename || mediaObj.name) ? (mediaObj.originalFilename || mediaObj.name) : '';
// Reset all config fields
var fields = ['sisMediaConfigWidth', 'sisMediaConfigHeight', 'sisMediaConfigStyle', 'sisMediaConfigClass', 'sisMediaConfigAlt', 'sisMediaConfigAttrs'];
var selectFields = ['sisMediaConfigObjectFit', 'sisMediaConfigAspectRatio'];
selectFields.forEach(function (id) { var el = document.getElementById(id); if (el) el.value = ''; });
fields.forEach(function (id) {
var el = document.getElementById(id);
if (el) el.value = '';
});
var fitEl = document.getElementById('sisMediaConfigObjectFit');
if (fitEl) fitEl.value = '';
// Reset size button highlights
var sizeContainer = document.getElementById('sisMediaSizeButtons');
if (sizeContainer) {
sizeContainer.querySelectorAll('button').forEach(function (b) {
b.classList.remove('btn-secondary');
b.classList.add('btn-outline-secondary');
});
}
panel.style.display = 'block';
panel.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
},
hideConfigPanel: function () {
var panel = document.getElementById(this.configPanelId);
if (panel) panel.style.display = 'none';
this.selectedUrl = null;
this.selectedMediaObj = null;
},
confirmWithConfig: function () {
if (!this.selectedUrl) return;
var width = (document.getElementById('sisMediaConfigWidth') || {}).value || '';
var height = (document.getElementById('sisMediaConfigHeight') || {}).value || '';
var objectFit = (document.getElementById('sisMediaConfigObjectFit') || {}).value || '';
var aspectRatio = (document.getElementById('sisMediaConfigAspectRatio') || {}).value || '';
var style = (document.getElementById('sisMediaConfigStyle') || {}).value || '';
var cssClass = (document.getElementById('sisMediaConfigClass') || {}).value || '';
var alt = (document.getElementById('sisMediaConfigAlt') || {}).value || '';
var customAttrs = (document.getElementById('sisMediaConfigAttrs') || {}).value || '';
// Build composite style string from size + aspect-ratio + object-fit + inline style
var computedStyle = '';
if (width) computedStyle += 'width: ' + width + '; ';
if (height) computedStyle += 'height: ' + height + '; ';
if (aspectRatio) computedStyle += 'aspect-ratio: ' + aspectRatio + '; ';
if (objectFit) computedStyle += 'object-fit: ' + objectFit + '; ';
if (style) computedStyle += style;
computedStyle = computedStyle.trim();
var config = {
style: computedStyle,
cssClass: cssClass,
alt: alt,
customAttributes: customAttrs,
width: width,
height: height,
objectFit: objectFit,
aspectRatio: aspectRatio
};
if (typeof this.callback === 'function') {
this.callback(this.selectedUrl, this.selectedMediaObj, config);
}
// Close modal
var modalEl = document.getElementById(this.modalId);
if (modalEl) {
if (typeof $ !== 'undefined' && $.fn && $.fn.modal) {
$(modalEl).modal('hide');
} else {
modalEl.style.display = 'none';
modalEl.classList.remove('show');
}
}
this.hideConfigPanel();
},
selectItem: function (url, mediaObj) {
// Legacy direct select (no config panel) - used by upload flow or external callers
if (typeof this.callback === 'function') {
this.callback(url, mediaObj, {});
}
var modalEl = document.getElementById(this.modalId);
if (modalEl) {
if (typeof $ !== 'undefined' && $.fn && $.fn.modal) {
$(modalEl).modal('hide');
} else {
modalEl.style.display = 'none';
modalEl.classList.remove('show');
}
}
}
};
// Expose standalone module globally
window.SISMediaPicker = SISMediaPicker;
// Backward-compatible global helper function
window.openSISMediaModal = function (callback) {
SISMediaPicker.open(callback);
};
})(window, document);