1002 lines
34 KiB
JavaScript
1002 lines
34 KiB
JavaScript
/**
|
|
* Marketing File Management System
|
|
* Professional drag-and-drop upload with folder management and tagging
|
|
*/
|
|
|
|
class MarketingFileManager {
|
|
constructor() {
|
|
this.currentFolder = '';
|
|
this.selectedFiles = [];
|
|
this.uploadQueue = [];
|
|
this.viewMode = 'grid';
|
|
this.filters = {
|
|
search: '',
|
|
tag: '',
|
|
fileTypes: []
|
|
};
|
|
this.folders = []; // Store folders data
|
|
this.loadRequestId = 0; // Track the latest load request
|
|
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.bindEvents();
|
|
this.loadFolders();
|
|
this.loadTags();
|
|
this.loadFiles();
|
|
this.setupDragAndDrop();
|
|
}
|
|
|
|
bindEvents() {
|
|
// Upload modal
|
|
document.getElementById('uploadBtn')?.addEventListener('click', () => {
|
|
this.showUploadModal();
|
|
});
|
|
|
|
// Create folder modal
|
|
document.getElementById('createFolderBtn')?.addEventListener('click', () => {
|
|
this.showFolderModal();
|
|
});
|
|
|
|
// View mode toggle
|
|
document.getElementById('gridViewBtn')?.addEventListener('click', () => {
|
|
this.setViewMode('grid');
|
|
});
|
|
|
|
document.getElementById('listViewBtn')?.addEventListener('click', () => {
|
|
this.setViewMode('list');
|
|
});
|
|
|
|
// Search
|
|
document.getElementById('searchInput')?.addEventListener('input', (e) => {
|
|
this.filters.search = e.target.value;
|
|
this.debounce(this.loadFiles.bind(this), 300)();
|
|
});
|
|
|
|
// Tag filter
|
|
document.getElementById('tagFilter')?.addEventListener('change', (e) => {
|
|
this.filters.tag = e.target.value;
|
|
this.loadFiles();
|
|
});
|
|
|
|
// File type filters
|
|
document.querySelectorAll('.file-type-filters input[type="checkbox"]').forEach(checkbox => {
|
|
checkbox.addEventListener('change', () => {
|
|
this.updateFileTypeFilters();
|
|
});
|
|
});
|
|
|
|
// Modal events
|
|
this.bindModalEvents();
|
|
|
|
// Upload events
|
|
this.bindUploadEvents();
|
|
}
|
|
|
|
bindModalEvents() {
|
|
// Close modals
|
|
document.querySelectorAll('.modal-close, .modal-cancel').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
this.closeModal(e.target.closest('.modal'));
|
|
});
|
|
});
|
|
|
|
// Create folder
|
|
document.getElementById('createFolder')?.addEventListener('click', () => {
|
|
this.createFolder();
|
|
});
|
|
|
|
// Download file
|
|
document.getElementById('downloadFile')?.addEventListener('click', () => {
|
|
if (this.selectedFile) {
|
|
this.downloadFile(this.selectedFile);
|
|
}
|
|
});
|
|
|
|
// Delete file
|
|
document.getElementById('deleteFile')?.addEventListener('click', () => {
|
|
if (this.selectedFile) {
|
|
this.deleteFile(this.selectedFile);
|
|
}
|
|
});
|
|
|
|
// Save edit
|
|
document.getElementById('saveEdit')?.addEventListener('click', () => {
|
|
this.saveEdit();
|
|
});
|
|
}
|
|
|
|
bindUploadEvents() {
|
|
const fileInput = document.getElementById('fileInput');
|
|
const browseBtn = document.getElementById('browseBtn');
|
|
const startUpload = document.getElementById('startUpload');
|
|
|
|
browseBtn?.addEventListener('click', () => {
|
|
fileInput.click();
|
|
});
|
|
|
|
fileInput?.addEventListener('change', (e) => {
|
|
this.handleFileSelect(e.target.files);
|
|
});
|
|
|
|
startUpload?.addEventListener('click', () => {
|
|
this.startUpload();
|
|
});
|
|
}
|
|
|
|
setupDragAndDrop() {
|
|
const uploadArea = document.getElementById('uploadArea');
|
|
const filesContainer = document.getElementById('filesContainer');
|
|
|
|
if (uploadArea) {
|
|
uploadArea.addEventListener('dragover', this.handleDragOver);
|
|
uploadArea.addEventListener('drop', (e) => this.handleDrop(e));
|
|
}
|
|
|
|
if (filesContainer) {
|
|
filesContainer.addEventListener('dragover', this.handleDragOver);
|
|
filesContainer.addEventListener('drop', (e) => this.handleDrop(e));
|
|
}
|
|
}
|
|
|
|
handleDragOver(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
e.currentTarget.classList.add('drag-over');
|
|
}
|
|
|
|
handleDrop(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
e.currentTarget.classList.remove('drag-over');
|
|
|
|
const files = e.dataTransfer.files;
|
|
if (files.length > 0) {
|
|
this.showUploadModal();
|
|
this.handleFileSelect(files);
|
|
}
|
|
}
|
|
|
|
async loadFolders() {
|
|
try {
|
|
const response = await fetch('index.php?page=marketing&action=marketing_folders&tree=true', { cache: 'no-store' });
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const text = await response.text();
|
|
if (!text || text.trim() === '') {
|
|
console.warn('Empty response from folders API');
|
|
this.folders = [];
|
|
this.renderFolderTree([]);
|
|
this.populateFolderSelects([]);
|
|
return;
|
|
}
|
|
|
|
const data = JSON.parse(text);
|
|
|
|
this.folders = data || []; // Store the folders data
|
|
// Always render the folder tree (at minimum shows Root)
|
|
this.renderFolderTree(this.folders);
|
|
this.populateFolderSelects(this.folders);
|
|
} catch (error) {
|
|
console.error('Error loading folders:', error);
|
|
this.folders = [];
|
|
// Show at least root folder on error
|
|
this.renderFolderTree([]);
|
|
this.populateFolderSelects([]);
|
|
}
|
|
}
|
|
|
|
async loadTags() {
|
|
try {
|
|
const response = await fetch('index.php?page=marketing&action=marketing_tags&used_only=true', { cache: 'no-store' });
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const text = await response.text();
|
|
if (!text || text.trim() === '') {
|
|
console.warn('Empty response from tags API');
|
|
this.populateTagFilter([]);
|
|
return;
|
|
}
|
|
|
|
const data = JSON.parse(text);
|
|
|
|
// Always populate tag filter (at minimum shows "All Tags")
|
|
this.populateTagFilter(data || []);
|
|
} catch (error) {
|
|
console.error('Error loading tags:', error);
|
|
// Show empty tag filter on error
|
|
this.populateTagFilter([]);
|
|
}
|
|
}
|
|
|
|
async loadFiles() {
|
|
const container = document.getElementById('filesContainer');
|
|
const loading = document.getElementById('loadingIndicator');
|
|
const emptyState = document.getElementById('emptyState');
|
|
|
|
// Increment request ID to invalidate previous requests
|
|
const requestId = ++this.loadRequestId;
|
|
|
|
// Clear container FIRST to prevent showing old files
|
|
container.innerHTML = '';
|
|
loading.style.display = 'block';
|
|
emptyState.style.display = 'none';
|
|
|
|
try {
|
|
// Use proper folder ID (null for root, or the folder ID)
|
|
const folderId = this.currentFolder ? this.currentFolder : 'null';
|
|
// Add cache busting to prevent browser caching
|
|
let url = `index.php?page=marketing&action=marketing_files&folder_id=${folderId}&limit=50&_t=${Date.now()}`;
|
|
|
|
if (this.filters.search) {
|
|
url += `&search=${encodeURIComponent(this.filters.search)}`;
|
|
}
|
|
|
|
if (this.filters.tag) {
|
|
url += `&tag=${encodeURIComponent(this.filters.tag)}`;
|
|
}
|
|
|
|
if (this.filters.fileTypes.length > 0) {
|
|
// API expects individual file_type parameter, so we'll filter client-side for now
|
|
}
|
|
|
|
const response = await fetch(url, { cache: 'no-store' });
|
|
|
|
// Ignore response if a newer request was made
|
|
if (requestId !== this.loadRequestId) {
|
|
return;
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const text = await response.text();
|
|
|
|
if (!text || text.trim() === '') {
|
|
console.warn('Empty response from files API');
|
|
emptyState.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
const data = JSON.parse(text);
|
|
|
|
if (data && data.length > 0) {
|
|
let files = data;
|
|
|
|
// Client-side file type filtering
|
|
if (this.filters.fileTypes.length > 0) {
|
|
files = files.filter(file =>
|
|
this.filters.fileTypes.includes(file.file_type.toLowerCase())
|
|
);
|
|
}
|
|
|
|
if (files.length === 0) {
|
|
emptyState.style.display = 'block';
|
|
} else {
|
|
this.renderFiles(files);
|
|
}
|
|
} else {
|
|
emptyState.style.display = 'block';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading files:', error);
|
|
this.showToast('Error loading files', 'error');
|
|
} finally {
|
|
loading.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
renderFolderTree(folders, container = null, level = 0) {
|
|
if (!container) {
|
|
container = document.getElementById('folderTree');
|
|
container.innerHTML = '<div class="folder-item root" data-folder=""><i class="fa fa-home"></i> Root</div>';
|
|
|
|
// Add click listener to root folder
|
|
const rootFolder = container.querySelector('.folder-item.root');
|
|
if (rootFolder) {
|
|
rootFolder.addEventListener('click', () => {
|
|
this.selectFolder('');
|
|
});
|
|
}
|
|
}
|
|
|
|
folders.forEach(folder => {
|
|
const folderItem = document.createElement('div');
|
|
folderItem.className = 'folder-item';
|
|
folderItem.setAttribute('data-folder', folder.id);
|
|
folderItem.style.marginLeft = `${level * 20}px`;
|
|
|
|
const hasChildren = folder.children && folder.children.length > 0;
|
|
const expandIcon = hasChildren ? '<i class="fa fa-chevron-right expand-icon"></i>' : '';
|
|
|
|
folderItem.innerHTML = `
|
|
${expandIcon}
|
|
<i class="fa fa-folder"></i>
|
|
<span class="folder-name">${this.escapeHtml(folder.folder_name)}</span>
|
|
<span class="file-count">(${folder.file_count})</span>
|
|
`;
|
|
|
|
folderItem.addEventListener('click', () => {
|
|
this.selectFolder(folder.id);
|
|
});
|
|
|
|
container.appendChild(folderItem);
|
|
|
|
if (hasChildren) {
|
|
this.renderFolderTree(folder.children, container, level + 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
renderFiles(files) {
|
|
const container = document.getElementById('filesContainer');
|
|
container.innerHTML = '';
|
|
|
|
files.forEach(file => {
|
|
const fileElement = this.createFileElement(file);
|
|
container.appendChild(fileElement);
|
|
});
|
|
}
|
|
|
|
createFileElement(file) {
|
|
const fileElement = document.createElement('div');
|
|
fileElement.className = `file-item ${this.viewMode}-item`;
|
|
fileElement.setAttribute('data-file-id', file.id);
|
|
|
|
const thumbnail = this.getThumbnail(file);
|
|
const tags = file.tags.map(tag => `<span class="tag">${this.escapeHtml(tag)}</span>`).join('');
|
|
|
|
fileElement.innerHTML = `
|
|
<div class="file-thumbnail">
|
|
${thumbnail}
|
|
<div class="file-overlay">
|
|
<button class="preview-btn" title="Preview">
|
|
<i class="fa fa-eye"></i>
|
|
</button>
|
|
<button class="download-btn" title="Download">
|
|
<i class="fa fa-download"></i>
|
|
</button>
|
|
<button class="edit-btn" title="Edit">
|
|
<i class="fa fa-edit"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="file-info">
|
|
<div class="file-name" title="${this.escapeHtml(file.original_filename)}">
|
|
${this.escapeHtml(file.title || file.original_filename)}
|
|
</div>
|
|
<div class="file-meta">
|
|
<span class="file-size">${file.file_size_formatted}</span>
|
|
<span class="file-type">.${file.file_type.toUpperCase()}</span>
|
|
<span class="file-date">${this.formatDate(file.created)}</span>
|
|
</div>
|
|
<div class="file-tags">
|
|
${tags}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Bind events
|
|
fileElement.querySelector('.preview-btn').addEventListener('click', () => {
|
|
this.previewFile(file);
|
|
});
|
|
|
|
fileElement.querySelector('.download-btn').addEventListener('click', () => {
|
|
this.downloadFile(file);
|
|
});
|
|
|
|
fileElement.querySelector('.edit-btn').addEventListener('click', () => {
|
|
this.editFile(file);
|
|
});
|
|
|
|
fileElement.addEventListener('dblclick', () => {
|
|
this.previewFile(file);
|
|
});
|
|
|
|
return fileElement;
|
|
}
|
|
|
|
getThumbnail(file) {
|
|
const isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(file.file_type.toLowerCase());
|
|
|
|
if (isImage && file.thumbnail_path) {
|
|
return `<img src="${file.thumbnail_path}" alt="${this.escapeHtml(file.title)}" class="thumbnail-img">`;
|
|
}
|
|
|
|
// File type icons
|
|
const iconMap = {
|
|
pdf: 'fa-file-pdf',
|
|
doc: 'fa-file-word',
|
|
docx: 'fa-file-word',
|
|
xls: 'fa-file-excel',
|
|
xlsx: 'fa-file-excel',
|
|
mp4: 'fa-file-video',
|
|
mov: 'fa-file-video',
|
|
avi: 'fa-file-video'
|
|
};
|
|
|
|
const iconClass = iconMap[file.file_type.toLowerCase()] || 'fa-file';
|
|
|
|
return `<div class="file-icon"><i class="fa ${iconClass}"></i></div>`;
|
|
}
|
|
|
|
showUploadModal() {
|
|
const modal = document.getElementById('uploadModal');
|
|
this.showModal(modal);
|
|
this.populateUploadFolders(this.folders); // Use stored folders data
|
|
}
|
|
|
|
showFolderModal() {
|
|
const modal = document.getElementById('folderModal');
|
|
this.showModal(modal);
|
|
this.populateParentFolders(this.folders); // Use stored folders data
|
|
}
|
|
|
|
showModal(modal) {
|
|
modal.style.display = 'flex';
|
|
modal.classList.add('show');
|
|
document.body.classList.add('modal-open');
|
|
}
|
|
|
|
closeModal(modal) {
|
|
modal.classList.remove('show');
|
|
setTimeout(() => {
|
|
modal.style.display = 'none';
|
|
document.body.classList.remove('modal-open');
|
|
}, 300);
|
|
}
|
|
|
|
handleFileSelect(files) {
|
|
this.uploadQueue = [];
|
|
|
|
Array.from(files).forEach(file => {
|
|
this.uploadQueue.push({
|
|
file: file,
|
|
progress: 0,
|
|
status: 'pending'
|
|
});
|
|
});
|
|
|
|
this.renderUploadQueue();
|
|
document.getElementById('startUpload').disabled = this.uploadQueue.length === 0;
|
|
}
|
|
|
|
renderUploadQueue() {
|
|
const container = document.getElementById('uploadQueue');
|
|
container.innerHTML = '';
|
|
|
|
this.uploadQueue.forEach((item, index) => {
|
|
const queueItem = document.createElement('div');
|
|
queueItem.className = 'upload-item';
|
|
queueItem.innerHTML = `
|
|
<div class="upload-info">
|
|
<div class="file-name">${this.escapeHtml(item.file.name)}</div>
|
|
<div class="file-size">${this.formatFileSize(item.file.size)}</div>
|
|
</div>
|
|
<div class="upload-progress">
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" style="width: ${item.progress}%"></div>
|
|
</div>
|
|
<div class="upload-status">${item.status}</div>
|
|
</div>
|
|
<button class="remove-btn" data-index="${index}">
|
|
<i class="fa fa-times"></i>
|
|
</button>
|
|
`;
|
|
|
|
queueItem.querySelector('.remove-btn').addEventListener('click', () => {
|
|
this.removeFromQueue(index);
|
|
});
|
|
|
|
container.appendChild(queueItem);
|
|
});
|
|
}
|
|
|
|
async startUpload() {
|
|
const folder = document.getElementById('uploadFolder').value;
|
|
const tags = document.getElementById('uploadTags').value
|
|
.split(',')
|
|
.map(tag => tag.trim())
|
|
.filter(tag => tag.length > 0);
|
|
|
|
for (let i = 0; i < this.uploadQueue.length; i++) {
|
|
const item = this.uploadQueue[i];
|
|
await this.uploadFile(item, folder, tags, i);
|
|
}
|
|
|
|
// Switch to the uploaded folder if different from current
|
|
if (folder && folder !== this.currentFolder) {
|
|
this.currentFolder = folder;
|
|
}
|
|
|
|
this.loadFiles();
|
|
this.closeModal(document.getElementById('uploadModal'));
|
|
this.showToast('Files uploaded successfully!', 'success');
|
|
}
|
|
|
|
async uploadFile(item, folderId, tags, index) {
|
|
const formData = new FormData();
|
|
formData.append('file', item.file);
|
|
formData.append('folder_id', folderId);
|
|
formData.append('tags', JSON.stringify(tags));
|
|
formData.append('title', item.file.name.replace(/\.[^/.]+$/, ""));
|
|
|
|
item.status = 'uploading';
|
|
this.updateQueueItem(index, item);
|
|
|
|
try {
|
|
const response = await fetch('index.php?page=marketing&action=marketing_upload', {
|
|
method: 'POST',
|
|
body: formData,
|
|
onUploadProgress: (progressEvent) => {
|
|
item.progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
|
this.updateQueueItem(index, item);
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const text = await response.text();
|
|
if (!text || text.trim() === '') {
|
|
throw new Error('Empty response from upload server');
|
|
}
|
|
|
|
const result = JSON.parse(text);
|
|
|
|
if (result.success) {
|
|
item.status = 'completed';
|
|
item.progress = 100;
|
|
} else {
|
|
throw new Error(result.error || 'Upload failed');
|
|
}
|
|
} catch (error) {
|
|
item.status = 'error';
|
|
item.error = error.message;
|
|
this.showToast(error.message, 'error');
|
|
}
|
|
|
|
this.updateQueueItem(index, item);
|
|
}
|
|
|
|
async createFolder() {
|
|
const folderName = document.getElementById('folderName').value.trim();
|
|
const parentId = document.getElementById('parentFolder').value;
|
|
const description = document.getElementById('folderDescription').value.trim();
|
|
|
|
if (!folderName) {
|
|
this.showToast('Folder name is required', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('folder_name', folderName);
|
|
formData.append('parent_id', parentId || '');
|
|
formData.append('description', description);
|
|
|
|
const response = await fetch('index.php?page=marketing&action=marketing_folders', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const text = await response.text();
|
|
if (!text || text.trim() === '') {
|
|
throw new Error('Empty response from server');
|
|
}
|
|
|
|
const data = JSON.parse(text);
|
|
|
|
if (data && (data.success || data.rowID)) {
|
|
this.closeModal(document.getElementById('folderModal'));
|
|
this.loadFolders();
|
|
this.showToast('Folder created successfully!', 'success');
|
|
} else if (data.error) {
|
|
throw new Error(data.error);
|
|
} else {
|
|
throw new Error('Unexpected response format');
|
|
}
|
|
} catch (error) {
|
|
console.error('Create folder error:', error);
|
|
this.showToast(error.message || 'Error creating folder', 'error');
|
|
}
|
|
}
|
|
|
|
async deleteFile(file) {
|
|
if (!confirm(`Are you sure you want to delete "${file.title || file.original_filename}"?`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('file_id', file.id);
|
|
|
|
const response = await fetch('index.php?page=marketing&action=marketing_delete', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const text = await response.text();
|
|
if (!text || text.trim() === '') {
|
|
throw new Error('Empty response from delete server');
|
|
}
|
|
|
|
const data = JSON.parse(text);
|
|
|
|
if (data && (data.success || !data.error)) {
|
|
this.closeModal(document.getElementById('previewModal'));
|
|
this.loadFiles();
|
|
this.showToast('File deleted successfully!', 'success');
|
|
} else if (data.error) {
|
|
throw new Error(data.error);
|
|
} else {
|
|
throw new Error('Unexpected response format');
|
|
}
|
|
} catch (error) {
|
|
this.showToast(error.message || 'Error deleting file', 'error');
|
|
}
|
|
}
|
|
|
|
previewFile(file) {
|
|
this.selectedFile = file;
|
|
const modal = document.getElementById('previewModal');
|
|
const title = document.getElementById('previewTitle');
|
|
const content = document.getElementById('previewContent');
|
|
|
|
title.textContent = file.title || file.original_filename;
|
|
|
|
// Generate preview content based on file type
|
|
if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(file.file_type.toLowerCase())) {
|
|
content.innerHTML = `<img src="${file.file_path}" alt="${this.escapeHtml(file.title)}" style="max-width: 100%; max-height: 500px;">`;
|
|
} else if (file.file_type.toLowerCase() === 'mp4') {
|
|
content.innerHTML = `<video controls style="max-width: 100%; max-height: 500px;"><source src="${file.file_path}" type="video/mp4"></video>`;
|
|
} else {
|
|
content.innerHTML = `
|
|
<div class="file-preview-info">
|
|
<i class="fa ${this.getFileIcon(file.file_type)} fa-4x"></i>
|
|
<h4>${this.escapeHtml(file.title || file.original_filename)}</h4>
|
|
<p>File Type: ${file.file_type.toUpperCase()}</p>
|
|
<p>Size: ${file.file_size_formatted}</p>
|
|
<p>Created: ${this.formatDate(file.created)}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
this.showModal(modal);
|
|
}
|
|
|
|
downloadFile(file) {
|
|
const link = document.createElement('a');
|
|
link.href = file.file_path;
|
|
link.download = file.original_filename;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
}
|
|
|
|
// Utility methods
|
|
async apiCall(endpoint, params = {}, method = 'GET') {
|
|
const url = new URL(`/api.php${endpoint}`, window.location.origin);
|
|
|
|
let options = {
|
|
method: method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
};
|
|
|
|
if (method === 'GET') {
|
|
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));
|
|
} else {
|
|
options.body = JSON.stringify(params);
|
|
}
|
|
|
|
const response = await fetch(url, options);
|
|
return await response.json();
|
|
}
|
|
|
|
escapeHtml(text) {
|
|
const map = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
};
|
|
return text ? text.replace(/[&<>"']/g, m => map[m]) : '';
|
|
}
|
|
|
|
formatDate(dateString) {
|
|
return new Date(dateString).toLocaleDateString();
|
|
}
|
|
|
|
formatFileSize(bytes) {
|
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
|
}
|
|
|
|
debounce(func, wait) {
|
|
let timeout;
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout);
|
|
func(...args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
}
|
|
|
|
showToast(message, type = 'info') {
|
|
// Simple toast implementation
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast toast-${type}`;
|
|
toast.textContent = message;
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
setTimeout(() => {
|
|
toast.classList.add('show');
|
|
}, 100);
|
|
|
|
setTimeout(() => {
|
|
toast.classList.remove('show');
|
|
setTimeout(() => document.body.removeChild(toast), 300);
|
|
}, 3000);
|
|
}
|
|
|
|
setViewMode(mode) {
|
|
this.viewMode = mode;
|
|
const container = document.getElementById('filesContainer');
|
|
|
|
// Update view mode classes
|
|
container.className = `files-container ${mode}-view`;
|
|
|
|
// Update button states
|
|
document.querySelectorAll('.view-btn').forEach(btn => btn.classList.remove('active'));
|
|
document.getElementById(`${mode}ViewBtn`).classList.add('active');
|
|
|
|
// Re-render files with new view mode
|
|
this.loadFiles();
|
|
}
|
|
|
|
selectFolder(folderId) {
|
|
// Clear current folder selection and files BEFORE setting new folder
|
|
const container = document.getElementById('filesContainer');
|
|
container.innerHTML = '';
|
|
|
|
// Set new current folder
|
|
this.currentFolder = folderId;
|
|
|
|
// Update UI
|
|
this.updateBreadcrumb();
|
|
|
|
// Update active folder in tree
|
|
document.querySelectorAll('.folder-item').forEach(item => {
|
|
item.classList.remove('active');
|
|
});
|
|
const selectedFolder = document.querySelector(`[data-folder="${folderId}"]`);
|
|
if (selectedFolder) {
|
|
selectedFolder.classList.add('active');
|
|
}
|
|
|
|
// Load files for the new folder
|
|
this.loadFiles();
|
|
}
|
|
|
|
updateBreadcrumb() {
|
|
// Implement breadcrumb navigation
|
|
const nav = document.getElementById('breadcrumbNav');
|
|
// This would build breadcrumb based on current folder path
|
|
}
|
|
|
|
updateFileTypeFilters() {
|
|
this.filters.fileTypes = [];
|
|
|
|
document.querySelectorAll('.file-type-filters input[type="checkbox"]:checked').forEach(checkbox => {
|
|
const types = checkbox.value.split(',');
|
|
this.filters.fileTypes.push(...types);
|
|
});
|
|
|
|
this.loadFiles();
|
|
}
|
|
|
|
populateTagFilter(tags) {
|
|
const select = document.getElementById('tagFilter');
|
|
select.innerHTML = '<option value="">All Tags</option>';
|
|
|
|
tags.forEach(tag => {
|
|
const option = document.createElement('option');
|
|
option.value = tag.tag_name;
|
|
option.textContent = `${tag.tag_name} (${tag.usage_count})`;
|
|
select.appendChild(option);
|
|
});
|
|
}
|
|
|
|
populateFolderSelects(folders) {
|
|
this.populateUploadFolders(folders);
|
|
this.populateParentFolders(folders);
|
|
}
|
|
|
|
addFolderOptions(select, folders, level = 0) {
|
|
folders.forEach(folder => {
|
|
const option = document.createElement('option');
|
|
option.value = folder.id;
|
|
option.textContent = ' '.repeat(level) + folder.folder_name;
|
|
select.appendChild(option);
|
|
|
|
if (folder.children && folder.children.length > 0) {
|
|
this.addFolderOptions(select, folder.children, level + 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
populateUploadFolders(folders = []) {
|
|
// Populate upload folder select
|
|
const select = document.getElementById('uploadFolder');
|
|
if (select) {
|
|
select.innerHTML = '<option value="">Root Folder</option>';
|
|
this.addFolderOptions(select, folders);
|
|
}
|
|
}
|
|
|
|
populateParentFolders(folders = []) {
|
|
// Populate parent folder select
|
|
const select = document.getElementById('parentFolder');
|
|
if (select) {
|
|
select.innerHTML = '<option value="">Root Folder</option>';
|
|
this.addFolderOptions(select, folders);
|
|
}
|
|
}
|
|
|
|
getFileIcon(fileType) {
|
|
const iconMap = {
|
|
pdf: 'fa-file-pdf',
|
|
doc: 'fa-file-word',
|
|
docx: 'fa-file-word',
|
|
xls: 'fa-file-excel',
|
|
xlsx: 'fa-file-excel',
|
|
mp4: 'fa-file-video',
|
|
mov: 'fa-file-video',
|
|
avi: 'fa-file-video'
|
|
};
|
|
|
|
return iconMap[fileType.toLowerCase()] || 'fa-file';
|
|
}
|
|
|
|
// Edit file functionality
|
|
editFile(file) {
|
|
this.selectedFile = file;
|
|
this.showEditModal();
|
|
this.populateEditModal(file);
|
|
}
|
|
|
|
showEditModal() {
|
|
const modal = document.getElementById('editModal');
|
|
if (modal) {
|
|
this.showModal(modal);
|
|
}
|
|
}
|
|
|
|
populateEditModal(file) {
|
|
// Populate title
|
|
const titleInput = document.getElementById('editTitle');
|
|
if (titleInput) {
|
|
titleInput.value = file.title || '';
|
|
}
|
|
|
|
// Populate folder select
|
|
const folderSelect = document.getElementById('editFolder');
|
|
if (folderSelect) {
|
|
folderSelect.innerHTML = '<option value="">Root Folder</option>';
|
|
this.addFolderOptions(folderSelect, this.folders);
|
|
|
|
// Select current folder
|
|
if (file.folder_id) {
|
|
folderSelect.value = file.folder_id;
|
|
}
|
|
}
|
|
|
|
// Populate tags
|
|
const tagsInput = document.getElementById('editTags');
|
|
if (tagsInput) {
|
|
tagsInput.value = file.tags ? file.tags.join(', ') : '';
|
|
}
|
|
}
|
|
|
|
saveEdit() {
|
|
if (!this.selectedFile) return;
|
|
|
|
const title = document.getElementById('editTitle').value.trim();
|
|
const folderId = document.getElementById('editFolder').value;
|
|
const tags = document.getElementById('editTags').value.trim();
|
|
|
|
// Prepare update data
|
|
const updateData = {
|
|
file_id: this.selectedFile.id,
|
|
title: title || null,
|
|
folder_id: folderId || null,
|
|
tags: tags ? tags.split(',').map(tag => tag.trim()).filter(tag => tag) : []
|
|
};
|
|
|
|
// Show loading state
|
|
const saveBtn = document.getElementById('saveEdit');
|
|
const originalText = saveBtn.innerHTML;
|
|
saveBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Saving...';
|
|
saveBtn.disabled = true;
|
|
|
|
// Send update request
|
|
fetch('./marketing.php?action=update_file', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(updateData)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
this.showToast('File updated successfully!', 'success');
|
|
this.closeModal(document.getElementById('editModal'));
|
|
this.loadFiles(); // Reload to show changes
|
|
} else {
|
|
throw new Error(data.message || 'Failed to update file');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Update error:', error);
|
|
this.showToast('Error updating file: ' + error.message, 'error');
|
|
})
|
|
.finally(() => {
|
|
// Restore button state
|
|
saveBtn.innerHTML = originalText;
|
|
saveBtn.disabled = false;
|
|
});
|
|
}
|
|
|
|
updateQueueItem(index, item) {
|
|
const queueItems = document.querySelectorAll('.upload-item');
|
|
if (queueItems[index]) {
|
|
const progressFill = queueItems[index].querySelector('.progress-fill');
|
|
const status = queueItems[index].querySelector('.upload-status');
|
|
|
|
progressFill.style.width = `${item.progress}%`;
|
|
status.textContent = item.status;
|
|
|
|
if (item.status === 'error') {
|
|
queueItems[index].classList.add('error');
|
|
} else if (item.status === 'completed') {
|
|
queueItems[index].classList.add('completed');
|
|
}
|
|
}
|
|
}
|
|
|
|
removeFromQueue(index) {
|
|
this.uploadQueue.splice(index, 1);
|
|
this.renderUploadQueue();
|
|
document.getElementById('startUpload').disabled = this.uploadQueue.length === 0;
|
|
}
|
|
}
|
|
|
|
// Initialize when DOM is ready
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.marketingManager = new MarketingFileManager();
|
|
}); |