Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d57687adee | ||
|
|
64d41af21b | ||
|
|
a8f5a6d3bc | ||
|
|
062cfc0dd4 | ||
|
|
32d25b1b69 | ||
|
|
56626aaa40 | ||
|
|
0697fcb1df | ||
|
|
c08c903810 |
64
README.md
@@ -1,15 +1,15 @@
|
|||||||
# MFE - Lightweight Multi File Upload Editor
|
# FileRise - Elevate your File Management
|
||||||
|
|
||||||
**Video demo:**
|
**Video demo:**
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/179e6940-5798-4482-9a69-696f806c37de
|
https://github.com/user-attachments/assets/9546a76b-afb0-4068-875a-0eab478b514d
|
||||||
|
|
||||||
**Dark mode:**
|
**Dark mode:**
|
||||||

|

|
||||||
|
|
||||||
changelogs available here: <https://github.com/error311/multi-file-upload-editor-docker/>
|
changelogs available here: <https://github.com/error311/FileRise-docker/>
|
||||||
|
|
||||||
MFE - Multi File Upload Editor is a lightweight, secure, self-hosted web application for uploading, syntax highlight editing, drag & drop and managing files. Built with an Apache/PHP backend and a modern JavaScript (ES6 modules) frontend, it offers a responsive, dynamic file management interface. It serves as an alternative to solutions like FileGator TinyFileManager or ProjectSend, providing an easy-to-setup experience ideal for document management, image galleries, firmware file hosting, and more.
|
FileRise is a lightweight, secure, self-hosted web application for uploading, syntax-highlight editing, drag & drop file management, and more. Built with an Apache/PHP backend and a modern JavaScript (ES6 modules) frontend, it offers a responsive and dynamic interface designed to simplify file handling. As an alternative to solutions like FileGator, TinyFileManager, or ProjectSend, FileRise provides an easy-to-set-up experience ideal for document management, image galleries, firmware hosting, and other file-intensive applications.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ MFE - Multi File Upload Editor is a lightweight, secure, self-hosted web applica
|
|||||||
- **Move Files:** Move selected files to a different folder, automatically generating a unique filename if needed to avoid data loss.
|
- **Move Files:** Move selected files to a different folder, automatically generating a unique filename if needed to avoid data loss.
|
||||||
- **Download Files as ZIP:** Download selected files as a ZIP archive. Users can specify a custom name for the ZIP file via a modal dialog.
|
- **Download Files as ZIP:** Download selected files as a ZIP archive. Users can specify a custom name for the ZIP file via a modal dialog.
|
||||||
- **Extract Zip:** When one or more ZIP files are selected, users can extract the archive(s) directly into the current folder.
|
- **Extract Zip:** When one or more ZIP files are selected, users can extract the archive(s) directly into the current folder.
|
||||||
- **Drag & Drop:** Easily move files by selecting them from the file list and simply dragging them onto your desired folder in the folder tree or breadcrumb. When you drop the files onto a folder, the system automatically moves them, updating your file organization in one seamless action.
|
- **Drag & Drop (File Movement):** Easily move files by selecting them from the file list and dragging them onto your desired folder in the folder tree or breadcrumb. When you drop the files onto a folder, the system automatically moves them, updating your file organization in one seamless action.
|
||||||
- **Enhanced Context Menu & Keyboard Shortcuts:**
|
- **Enhanced Context Menu & Keyboard Shortcuts:**
|
||||||
- **Right-Click Context Menu:**
|
- **Right-Click Context Menu:**
|
||||||
- A custom context menu appears on right-clicking within the file list.
|
- A custom context menu appears on right-clicking within the file list.
|
||||||
@@ -126,29 +126,45 @@ MFE - Multi File Upload Editor is a lightweight, secure, self-hosted web applica
|
|||||||
- The trash modal displays details such as file name, uploader/deleter, and the trashed date/time.
|
- The trash modal displays details such as file name, uploader/deleter, and the trashed date/time.
|
||||||
- Material icons with tooltips visually represent the restore and delete actions.
|
- Material icons with tooltips visually represent the restore and delete actions.
|
||||||
|
|
||||||
|
- **Drag & Drop Cards with Dedicated Drop Zones:**
|
||||||
|
- **Sidebar Drop Zone:**
|
||||||
|
- Cards (such as the upload card or folder management card) can be dragged into a dedicated sidebar drop zone for quick access to frequently used operations.
|
||||||
|
- The sidebar drop zone expands dynamically to accept drops anywhere within its visual area.
|
||||||
|
- **Top Bar Drop Zone:**
|
||||||
|
- A top drop zone is available for reordering or managing cards quickly.
|
||||||
|
- Dragging a card to the top drop zone provides immediate visual feedback, ensuring a fluid and customizable workflow.
|
||||||
|
- **Seamless Interaction:**
|
||||||
|
- Both drop zones support smooth drag and drop interactions with animations and pointer event adjustments to prevent interference, ensuring that cards can be dropped reliably regardless of screen position.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
**Light mode:**
|
**Light mode:**
|
||||||

|

|
||||||
|
|
||||||
|
**Dark mode default:**
|
||||||
|

|
||||||
|
|
||||||
**Dark editor:**
|
**Dark editor:**
|
||||||

|

|
||||||
|
|
||||||
**Dark preview**
|
**Light preview**
|
||||||

|

|
||||||
|
|
||||||
**Restore or Delete Trash:**
|
**Restore or Delete Trash:**
|
||||||

|

|
||||||
|
|
||||||
**Login page:**
|
**Dark Login page:**
|
||||||

|

|
||||||
|
|
||||||
|
**Gallery view:**
|
||||||
|

|
||||||
|
|
||||||
**iphone screenshots:**
|
**iphone screenshots:**
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://raw.githubusercontent.com/error311/multi-file-upload-editor/refs/heads/master/resources/dark-iphone.png" width="45%">
|
<img src="https://raw.githubusercontent.com/error311/FileRise/refs/heads/master/resources/dark-iphone.png" width="45%">
|
||||||
<img src="https://raw.githubusercontent.com/error311/multi-file-upload-editor/refs/heads/master/resources/light-preview-iphone.png" width="45%">
|
<img src="https://raw.githubusercontent.com/error311/FileRise/refs/heads/master/resources/light-preview-iphone.png" width="45%">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -161,7 +177,7 @@ MFE - Multi File Upload Editor is a lightweight, secure, self-hosted web applica
|
|||||||
- **Clone:**
|
- **Clone:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/error311/multi-file-upload-editor.git
|
git clone https://github.com/error311/FileRise.git
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Download:**
|
- **Download:**
|
||||||
@@ -211,7 +227,13 @@ For users who prefer containerization, a Docker image is available
|
|||||||
1. **Pull the Docker Image:**
|
1. **Pull the Docker Image:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull error311/multi-file-upload-editor-docker:latest
|
docker pull error311/filerise-docker:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
macos M series:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull --platform linux/x86_64 error311/filerise-docker:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Run the Container:**
|
2. **Run the Container:**
|
||||||
@@ -225,8 +247,8 @@ For users who prefer containerization, a Docker image is available
|
|||||||
-v /path/to/your/uploads:/var/www/uploads \
|
-v /path/to/your/uploads:/var/www/uploads \
|
||||||
-v /path/to/your/users:/var/www/users \
|
-v /path/to/your/users:/var/www/users \
|
||||||
-v /path/to/your/metadata:/var/www/metadata \
|
-v /path/to/your/metadata:/var/www/metadata \
|
||||||
--name multi-file-upload-editor \
|
--name FileRise \
|
||||||
error311/multi-file-upload-editor-docker:latest
|
error311/filerise-docker:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Using Docker Compose:**
|
3. **Using Docker Compose:**
|
||||||
@@ -237,7 +259,7 @@ For users who prefer containerization, a Docker image is available
|
|||||||
version: "3.8"
|
version: "3.8"
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
image: error311/multi-file-upload-editor-docker:latest
|
image: error311/filerise-docker:latest
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
environment:
|
environment:
|
||||||
@@ -291,4 +313,4 @@ The `config.php` file contains several key constants that may need adjustment fo
|
|||||||
- **Logging & Troubleshooting:**
|
- **Logging & Troubleshooting:**
|
||||||
Check Apache logs (located in `/var/log/apache2/`) for troubleshooting any issues during deployment or operation.
|
Check Apache logs (located in `/var/log/apache2/`) for troubleshooting any issues during deployment or operation.
|
||||||
|
|
||||||
Enjoy using the Multi File Upload Editor! For any issues or contributions, please refer to the [GitHub repository](https://github.com/error311/multi-file-upload-editor).
|
Enjoy using the Multi File Upload Editor! For any issues or contributions, please refer to the [GitHub repository](https://github.com/error311/FileRise).
|
||||||
|
|||||||
364
dragAndDrop.js
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
// dragAndDrop.js
|
||||||
|
|
||||||
|
// Moves cards into the sidebar based on the saved order in localStorage.
|
||||||
|
export function loadSidebarOrder() {
|
||||||
|
const sidebar = document.getElementById('sidebarDropArea');
|
||||||
|
if (!sidebar) return;
|
||||||
|
const orderStr = localStorage.getItem('sidebarOrder');
|
||||||
|
if (orderStr) {
|
||||||
|
const order = JSON.parse(orderStr);
|
||||||
|
if (order.length > 0) {
|
||||||
|
// Ensure main wrapper is visible.
|
||||||
|
const mainWrapper = document.querySelector('.main-wrapper');
|
||||||
|
if (mainWrapper) {
|
||||||
|
mainWrapper.style.display = 'flex';
|
||||||
|
}
|
||||||
|
// For each saved ID, move the card into the sidebar.
|
||||||
|
order.forEach(id => {
|
||||||
|
const card = document.getElementById(id);
|
||||||
|
if (card && card.parentNode.id !== 'sidebarDropArea') {
|
||||||
|
sidebar.appendChild(card);
|
||||||
|
// Animate vertical slide for sidebar card
|
||||||
|
animateVerticalSlide(card);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateSidebarVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal helper: update sidebar visibility based on its content.
|
||||||
|
function updateSidebarVisibility() {
|
||||||
|
const sidebar = document.getElementById('sidebarDropArea');
|
||||||
|
if (sidebar) {
|
||||||
|
const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard');
|
||||||
|
if (cards.length > 0) {
|
||||||
|
sidebar.classList.add('active');
|
||||||
|
sidebar.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
sidebar.classList.remove('active');
|
||||||
|
sidebar.style.display = 'none';
|
||||||
|
}
|
||||||
|
// Save the current order in localStorage.
|
||||||
|
saveSidebarOrder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal helper: update top zone layout (center a card if one column is empty).
|
||||||
|
function updateTopZoneLayout() {
|
||||||
|
const leftCol = document.getElementById('leftCol');
|
||||||
|
const rightCol = document.getElementById('rightCol');
|
||||||
|
|
||||||
|
const leftIsEmpty = !leftCol.querySelector('#uploadCard');
|
||||||
|
const rightIsEmpty = !rightCol.querySelector('#folderManagementCard');
|
||||||
|
|
||||||
|
if (leftIsEmpty && !rightIsEmpty) {
|
||||||
|
leftCol.style.display = 'none';
|
||||||
|
rightCol.style.margin = '0 auto';
|
||||||
|
} else if (rightIsEmpty && !leftIsEmpty) {
|
||||||
|
rightCol.style.display = 'none';
|
||||||
|
leftCol.style.margin = '0 auto';
|
||||||
|
} else {
|
||||||
|
leftCol.style.display = '';
|
||||||
|
rightCol.style.display = '';
|
||||||
|
leftCol.style.margin = '';
|
||||||
|
rightCol.style.margin = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When a card is being dragged, if the top drop zone is empty, set its min-height.
|
||||||
|
function addTopZoneHighlight() {
|
||||||
|
const topZone = document.getElementById('uploadFolderRow');
|
||||||
|
if (topZone) {
|
||||||
|
topZone.classList.add('highlight');
|
||||||
|
if (topZone.querySelectorAll('#uploadCard, #folderManagementCard').length === 0) {
|
||||||
|
topZone.style.minHeight = '375px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the drag ends, remove the extra min-height.
|
||||||
|
function removeTopZoneHighlight() {
|
||||||
|
const topZone = document.getElementById('uploadFolderRow');
|
||||||
|
if (topZone) {
|
||||||
|
topZone.classList.remove('highlight');
|
||||||
|
topZone.style.minHeight = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical slide/fade animation helper.
|
||||||
|
function animateVerticalSlide(card) {
|
||||||
|
card.style.transform = 'translateY(30px)';
|
||||||
|
card.style.opacity = '0';
|
||||||
|
// Force reflow.
|
||||||
|
card.offsetWidth;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
card.style.transition = 'transform 0.3s ease, opacity 0.3s ease';
|
||||||
|
card.style.transform = 'translateY(0)';
|
||||||
|
card.style.opacity = '1';
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
card.style.transition = '';
|
||||||
|
card.style.transform = '';
|
||||||
|
card.style.opacity = '';
|
||||||
|
}, 310);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal helper: insert card into sidebar at a proper position based on event.clientY.
|
||||||
|
function insertCardInSidebar(card, event) {
|
||||||
|
const sidebar = document.getElementById('sidebarDropArea');
|
||||||
|
if (!sidebar) return;
|
||||||
|
const existingCards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
|
||||||
|
let inserted = false;
|
||||||
|
for (const currentCard of existingCards) {
|
||||||
|
const rect = currentCard.getBoundingClientRect();
|
||||||
|
const midY = rect.top + rect.height / 2;
|
||||||
|
if (event.clientY < midY) {
|
||||||
|
sidebar.insertBefore(card, currentCard);
|
||||||
|
inserted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!inserted) {
|
||||||
|
sidebar.appendChild(card);
|
||||||
|
}
|
||||||
|
// Ensure card fills the sidebar.
|
||||||
|
card.style.width = '100%';
|
||||||
|
animateVerticalSlide(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal helper: save the current sidebar card order to localStorage.
|
||||||
|
function saveSidebarOrder() {
|
||||||
|
const sidebar = document.getElementById('sidebarDropArea');
|
||||||
|
if (sidebar) {
|
||||||
|
const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard');
|
||||||
|
const order = Array.from(cards).map(card => card.id);
|
||||||
|
localStorage.setItem('sidebarOrder', JSON.stringify(order));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: move cards from sidebar back to the top drop area when on small screens.
|
||||||
|
function moveSidebarCardsToTop() {
|
||||||
|
if (window.innerWidth < 1205) {
|
||||||
|
const sidebar = document.getElementById('sidebarDropArea');
|
||||||
|
if (!sidebar) return;
|
||||||
|
const cards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
|
||||||
|
cards.forEach(card => {
|
||||||
|
const orig = document.getElementById(card.dataset.originalContainerId);
|
||||||
|
if (orig) {
|
||||||
|
orig.appendChild(card);
|
||||||
|
animateVerticalSlide(card);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateSidebarVisibility();
|
||||||
|
updateTopZoneLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for window resize to automatically move sidebar cards back to top on small screens.
|
||||||
|
window.addEventListener('resize', function () {
|
||||||
|
if (window.innerWidth < 1205) {
|
||||||
|
moveSidebarCardsToTop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// This function ensures the top drop zone (#uploadFolderRow) has a stable width when empty.
|
||||||
|
function ensureTopZonePlaceholder() {
|
||||||
|
const topZone = document.getElementById('uploadFolderRow');
|
||||||
|
if (!topZone) return;
|
||||||
|
if (topZone.querySelectorAll('#uploadCard, #folderManagementCard').length === 0) {
|
||||||
|
let placeholder = topZone.querySelector('.placeholder');
|
||||||
|
if (!placeholder) {
|
||||||
|
placeholder = document.createElement('div');
|
||||||
|
placeholder.className = 'placeholder';
|
||||||
|
placeholder.style.visibility = 'hidden';
|
||||||
|
placeholder.style.display = 'block';
|
||||||
|
placeholder.style.width = '100%';
|
||||||
|
placeholder.style.height = '375px';
|
||||||
|
topZone.appendChild(placeholder);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const placeholder = topZone.querySelector('.placeholder');
|
||||||
|
if (placeholder) placeholder.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This sets up all drag-and-drop event listeners for cards.
|
||||||
|
export function initDragAndDrop() {
|
||||||
|
function run() {
|
||||||
|
const draggableCards = document.querySelectorAll('#uploadCard, #folderManagementCard');
|
||||||
|
draggableCards.forEach(card => {
|
||||||
|
if (!card.dataset.originalContainerId) {
|
||||||
|
card.dataset.originalContainerId = card.parentNode.id;
|
||||||
|
}
|
||||||
|
const header = card.querySelector('.card-header');
|
||||||
|
if (header) {
|
||||||
|
header.classList.add('drag-header');
|
||||||
|
}
|
||||||
|
|
||||||
|
let isDragging = false;
|
||||||
|
let dragTimer = null;
|
||||||
|
let offsetX = 0, offsetY = 0;
|
||||||
|
let initialLeft, initialTop;
|
||||||
|
|
||||||
|
if (header) {
|
||||||
|
header.addEventListener('mousedown', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const card = this.closest('.card');
|
||||||
|
const rect = card.getBoundingClientRect();
|
||||||
|
const originX = ((e.clientX - rect.left) / rect.width) * 100;
|
||||||
|
const originY = ((e.clientY - rect.top) / rect.height) * 100;
|
||||||
|
card.style.transformOrigin = `${originX}% ${originY}%`;
|
||||||
|
dragTimer = setTimeout(() => {
|
||||||
|
isDragging = true;
|
||||||
|
card.classList.add('dragging');
|
||||||
|
// Disable pointer events on the card so it doesn't block drop detection.
|
||||||
|
card.style.pointerEvents = 'none';
|
||||||
|
addTopZoneHighlight();
|
||||||
|
const sidebar = document.getElementById('sidebarDropArea');
|
||||||
|
if (sidebar) {
|
||||||
|
sidebar.classList.add('active');
|
||||||
|
sidebar.style.display = 'block';
|
||||||
|
sidebar.classList.add('highlight');
|
||||||
|
// Force the sidebar to have a tall drop zone while dragging.
|
||||||
|
sidebar.style.height = '800px';
|
||||||
|
}
|
||||||
|
const rect = card.getBoundingClientRect();
|
||||||
|
initialLeft = rect.left + window.pageXOffset;
|
||||||
|
initialTop = rect.top + window.pageYOffset;
|
||||||
|
offsetX = e.pageX - initialLeft;
|
||||||
|
offsetY = e.pageY - initialTop;
|
||||||
|
document.body.appendChild(card);
|
||||||
|
card.style.position = 'absolute';
|
||||||
|
card.style.left = initialLeft + 'px';
|
||||||
|
card.style.top = initialTop + 'px';
|
||||||
|
card.style.width = rect.width + 'px';
|
||||||
|
card.style.zIndex = '10000';
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
header.addEventListener('mouseup', function () {
|
||||||
|
clearTimeout(dragTimer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', function (e) {
|
||||||
|
if (isDragging) {
|
||||||
|
card.style.left = (e.pageX - offsetX) + 'px';
|
||||||
|
card.style.top = (e.pageY - offsetY) + 'px';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', function (e) {
|
||||||
|
if (isDragging) {
|
||||||
|
isDragging = false;
|
||||||
|
// Re-enable pointer events on the card.
|
||||||
|
card.style.pointerEvents = '';
|
||||||
|
card.classList.remove('dragging');
|
||||||
|
removeTopZoneHighlight();
|
||||||
|
const sidebar = document.getElementById('sidebarDropArea');
|
||||||
|
if (sidebar) {
|
||||||
|
sidebar.classList.remove('highlight');
|
||||||
|
// Remove the forced height once the drag ends.
|
||||||
|
sidebar.style.height = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const leftCol = document.getElementById('leftCol');
|
||||||
|
const rightCol = document.getElementById('rightCol');
|
||||||
|
let droppedInSidebar = false;
|
||||||
|
let droppedInTop = false;
|
||||||
|
|
||||||
|
const sidebarElem = document.getElementById('sidebarDropArea');
|
||||||
|
if (sidebarElem) {
|
||||||
|
// Instead of using elementsFromPoint, use a virtual drop zone.
|
||||||
|
const rect = sidebarElem.getBoundingClientRect();
|
||||||
|
// Define a drop zone from the top of the sidebar to 1000px below its top.
|
||||||
|
const dropZoneBottom = rect.top + 800;
|
||||||
|
if (
|
||||||
|
e.clientX >= rect.left &&
|
||||||
|
e.clientX <= rect.right &&
|
||||||
|
e.clientY >= rect.top &&
|
||||||
|
e.clientY <= dropZoneBottom
|
||||||
|
) {
|
||||||
|
insertCardInSidebar(card, e);
|
||||||
|
droppedInSidebar = true;
|
||||||
|
sidebarElem.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const topRow = document.getElementById('uploadFolderRow');
|
||||||
|
if (!droppedInSidebar && topRow) {
|
||||||
|
const rect = topRow.getBoundingClientRect();
|
||||||
|
if (
|
||||||
|
e.clientX >= rect.left &&
|
||||||
|
e.clientX <= rect.right &&
|
||||||
|
e.clientY >= rect.top &&
|
||||||
|
e.clientY <= rect.bottom
|
||||||
|
) {
|
||||||
|
let container;
|
||||||
|
if (card.id === 'uploadCard') {
|
||||||
|
container = document.getElementById('leftCol');
|
||||||
|
} else if (card.id === 'folderManagementCard') {
|
||||||
|
container = document.getElementById('rightCol');
|
||||||
|
}
|
||||||
|
if (container) {
|
||||||
|
ensureTopZonePlaceholder();
|
||||||
|
container.appendChild(card);
|
||||||
|
droppedInTop = true;
|
||||||
|
|
||||||
|
container.style.position = 'relative';
|
||||||
|
card.style.position = 'absolute';
|
||||||
|
card.style.left = '0px';
|
||||||
|
|
||||||
|
// Animate vertical slide/fade.
|
||||||
|
card.style.transform = 'translateY(30px)';
|
||||||
|
card.style.opacity = '0';
|
||||||
|
|
||||||
|
card.offsetWidth; // Force reflow.
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
card.style.transition = 'transform 0.3s ease, opacity 0.3s ease';
|
||||||
|
card.style.transform = 'translateY(0)';
|
||||||
|
card.style.opacity = '1';
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
card.style.position = '';
|
||||||
|
container.style.position = '';
|
||||||
|
card.style.transition = '';
|
||||||
|
card.style.transform = '';
|
||||||
|
card.style.opacity = '';
|
||||||
|
card.style.width = '';
|
||||||
|
}, 310);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (droppedInSidebar || droppedInTop) {
|
||||||
|
card.style.position = '';
|
||||||
|
card.style.left = '';
|
||||||
|
card.style.top = '';
|
||||||
|
card.style.zIndex = '';
|
||||||
|
} else {
|
||||||
|
const orig = document.getElementById(card.dataset.originalContainerId);
|
||||||
|
if (orig) {
|
||||||
|
orig.appendChild(card);
|
||||||
|
card.style.position = '';
|
||||||
|
card.style.left = '';
|
||||||
|
card.style.top = '';
|
||||||
|
card.style.zIndex = '';
|
||||||
|
card.style.width = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateTopZoneLayout();
|
||||||
|
updateSidebarVisibility();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', run);
|
||||||
|
} else {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,8 +80,9 @@ $srcMetadata = file_exists($srcMetaFile) ? json_decode(file_get_contents($srcMet
|
|||||||
$destMetadata = file_exists($destMetaFile) ? json_decode(file_get_contents($destMetaFile), true) : [];
|
$destMetadata = file_exists($destMetaFile) ? json_decode(file_get_contents($destMetaFile), true) : [];
|
||||||
|
|
||||||
$errors = [];
|
$errors = [];
|
||||||
$safeFileNamePattern = '/^[A-Za-z0-9_\-\.\(\) ]+$/';
|
|
||||||
$allSuccess = true;
|
$allSuccess = true;
|
||||||
|
$extractedFiles = array(); // Array to collect names of extracted files
|
||||||
|
$safeFileNamePattern = '/^[A-Za-z0-9_\-\.\(\) ]+$/';
|
||||||
|
|
||||||
// ---------- Process Each File ----------
|
// ---------- Process Each File ----------
|
||||||
foreach ($files as $zipFileName) {
|
foreach ($files as $zipFileName) {
|
||||||
@@ -115,6 +116,14 @@ foreach ($files as $zipFileName) {
|
|||||||
$errors[] = "Failed to extract $originalName.";
|
$errors[] = "Failed to extract $originalName.";
|
||||||
$allSuccess = false;
|
$allSuccess = false;
|
||||||
} else {
|
} else {
|
||||||
|
// Collect extracted file names from this zip.
|
||||||
|
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||||
|
$entryName = $zip->getNameIndex($i);
|
||||||
|
$extractedFileName = basename($entryName);
|
||||||
|
if ($extractedFileName) {
|
||||||
|
$extractedFiles[] = $extractedFileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Update metadata for each extracted file if the zip file has metadata.
|
// Update metadata for each extracted file if the zip file has metadata.
|
||||||
if (isset($srcMetadata[$originalName])) {
|
if (isset($srcMetadata[$originalName])) {
|
||||||
$zipMeta = $srcMetadata[$originalName];
|
$zipMeta = $srcMetadata[$originalName];
|
||||||
@@ -138,7 +147,7 @@ if (file_put_contents($destMetaFile, json_encode($destMetadata, JSON_PRETTY_PRIN
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($allSuccess) {
|
if ($allSuccess) {
|
||||||
echo json_encode(["success" => true]);
|
echo json_encode(["success" => true, "extractedFiles" => $extractedFiles]);
|
||||||
} else {
|
} else {
|
||||||
echo json_encode(["success" => false, "error" => implode(" ", $errors)]);
|
echo json_encode(["success" => false, "error" => implode(" ", $errors)]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -756,7 +756,12 @@ export function handleExtractZipSelected(e) {
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
showToast("Zip file(s) extracted successfully!");
|
// If the server returned a list of extracted files, join them into a string.
|
||||||
|
let toastMessage = "Zip file(s) extracted successfully!";
|
||||||
|
if (data.extractedFiles && Array.isArray(data.extractedFiles) && data.extractedFiles.length) {
|
||||||
|
toastMessage = "Extracted: " + data.extractedFiles.join(", ");
|
||||||
|
}
|
||||||
|
showToast(toastMessage);
|
||||||
loadFileList(window.currentFolder);
|
loadFileList(window.currentFolder);
|
||||||
} else {
|
} else {
|
||||||
showToast("Error extracting zip: " + (data.error || "Unknown error"));
|
showToast("Error extracting zip: " + (data.error || "Unknown error"));
|
||||||
@@ -1371,13 +1376,15 @@ document.addEventListener("keydown", function(e) {
|
|||||||
|
|
||||||
// ---------- CONTEXT MENU SUPPORT FOR FILE LIST ----------
|
// ---------- CONTEXT MENU SUPPORT FOR FILE LIST ----------
|
||||||
|
|
||||||
|
// Function to display the context menu with provided items at (x, y)
|
||||||
// Function to display the context menu with provided items at (x, y)
|
// Function to display the context menu with provided items at (x, y)
|
||||||
function showFileContextMenu(x, y, menuItems) {
|
function showFileContextMenu(x, y, menuItems) {
|
||||||
let menu = document.getElementById("fileContextMenu");
|
let menu = document.getElementById("fileContextMenu");
|
||||||
if (!menu) {
|
if (!menu) {
|
||||||
menu = document.createElement("div");
|
menu = document.createElement("div");
|
||||||
menu.id = "fileContextMenu";
|
menu.id = "fileContextMenu";
|
||||||
menu.style.position = "absolute";
|
// Use fixed positioning so the menu is relative to the viewport
|
||||||
|
menu.style.position = "fixed";
|
||||||
menu.style.backgroundColor = "#fff";
|
menu.style.backgroundColor = "#fff";
|
||||||
menu.style.border = "1px solid #ccc";
|
menu.style.border = "1px solid #ccc";
|
||||||
menu.style.boxShadow = "2px 2px 6px rgba(0,0,0,0.2)";
|
menu.style.boxShadow = "2px 2px 6px rgba(0,0,0,0.2)";
|
||||||
@@ -1394,11 +1401,7 @@ function showFileContextMenu(x, y, menuItems) {
|
|||||||
menuItem.style.padding = "5px 15px";
|
menuItem.style.padding = "5px 15px";
|
||||||
menuItem.style.cursor = "pointer";
|
menuItem.style.cursor = "pointer";
|
||||||
menuItem.addEventListener("mouseover", () => {
|
menuItem.addEventListener("mouseover", () => {
|
||||||
if (document.body.classList.contains("dark-mode")) {
|
menuItem.style.backgroundColor = document.body.classList.contains("dark-mode") ? "#444" : "#f0f0f0";
|
||||||
menuItem.style.backgroundColor = "#444"; // darker gray for dark mode
|
|
||||||
} else {
|
|
||||||
menuItem.style.backgroundColor = "#f0f0f0"; // light gray for light mode
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
menuItem.addEventListener("mouseout", () => {
|
menuItem.addEventListener("mouseout", () => {
|
||||||
menuItem.style.backgroundColor = "";
|
menuItem.style.backgroundColor = "";
|
||||||
@@ -1409,9 +1412,20 @@ function showFileContextMenu(x, y, menuItems) {
|
|||||||
});
|
});
|
||||||
menu.appendChild(menuItem);
|
menu.appendChild(menuItem);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Use the event's clientX and clientY coordinates (which are viewport-relative)
|
||||||
menu.style.left = x + "px";
|
menu.style.left = x + "px";
|
||||||
menu.style.top = y + "px";
|
menu.style.top = y + "px";
|
||||||
menu.style.display = "block";
|
menu.style.display = "block";
|
||||||
|
|
||||||
|
// Adjust if the menu would extend past the bottom of the viewport
|
||||||
|
const menuRect = menu.getBoundingClientRect();
|
||||||
|
const viewportHeight = window.innerHeight;
|
||||||
|
if (menuRect.bottom > viewportHeight) {
|
||||||
|
let newTop = viewportHeight - menuRect.height;
|
||||||
|
if (newTop < 0) newTop = 0;
|
||||||
|
menu.style.top = newTop + "px";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideFileContextMenu() {
|
function hideFileContextMenu() {
|
||||||
@@ -1484,7 +1498,7 @@ function fileListContextMenuHandler(e) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showFileContextMenu(e.pageX, e.pageY, menuItems);
|
showFileContextMenu(e.clientX, e.clientY, menuItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind the context menu to the file list container.
|
// Bind the context menu to the file list container.
|
||||||
|
|||||||
@@ -63,8 +63,6 @@ function getParentFolder(folder) {
|
|||||||
// Breadcrumb Functions
|
// Breadcrumb Functions
|
||||||
// ----------------------
|
// ----------------------
|
||||||
// Render breadcrumb for a normalized folder path.
|
// Render breadcrumb for a normalized folder path.
|
||||||
// For example, if window.currentFolder is "Folder1/Folder1SubFolder2",
|
|
||||||
// this will return: Root / Folder1 / Folder1SubFolder2.
|
|
||||||
function renderBreadcrumb(normalizedFolder) {
|
function renderBreadcrumb(normalizedFolder) {
|
||||||
if (normalizedFolder === "root") {
|
if (normalizedFolder === "root") {
|
||||||
return `<span class="breadcrumb-link" data-folder="root">Root</span>`;
|
return `<span class="breadcrumb-link" data-folder="root">Root</span>`;
|
||||||
@@ -307,16 +305,10 @@ export async function loadFolderTree(selectedFolder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let html = `<div id="rootRow" class="root-row">
|
let html = `<div id="rootRow" class="root-row">
|
||||||
<span class="folder-toggle" data-folder="root">[<span class="custom-dash">-</span>]</span>
|
<span class="folder-toggle" data-folder="root">[<span class="custom-dash">-</span>]</span>
|
||||||
<span class="folder-option root-folder-option" data-folder="root">(Root)</span>
|
<span class="folder-option root-folder-option" data-folder="root">(Root)</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
if (folders.length === 0) {
|
if (folders.length > 0) {
|
||||||
html += `<ul class="folder-tree expanded">
|
|
||||||
<li class="folder-item">
|
|
||||||
<span class="folder-option" data-folder="root">(Root)</span>
|
|
||||||
</li>
|
|
||||||
</ul>`;
|
|
||||||
} else {
|
|
||||||
const tree = buildFolderTree(folders);
|
const tree = buildFolderTree(folders);
|
||||||
html += renderFolderTree(tree, "", "block");
|
html += renderFolderTree(tree, "", "block");
|
||||||
}
|
}
|
||||||
@@ -730,8 +722,8 @@ document.addEventListener("click", function () {
|
|||||||
hideFolderManagerContextMenu();
|
hideFolderManagerContextMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
document.addEventListener("keydown", function(e) {
|
document.addEventListener("keydown", function (e) {
|
||||||
// Skip if the user is typing in an input, textarea, or contentEditable element.
|
// Skip if the user is typing in an input, textarea, or contentEditable element.
|
||||||
const tag = e.target.tagName.toLowerCase();
|
const tag = e.target.tagName.toLowerCase();
|
||||||
if (tag === "input" || tag === "textarea" || e.target.isContentEditable) {
|
if (tag === "input" || tag === "textarea" || e.target.isContentEditable) {
|
||||||
|
|||||||
415
index.html
@@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Multi File Upload Editor</title>
|
<title>FileRise</title>
|
||||||
<link rel="icon" type="image/png" href="/assets/logo.png">
|
<link rel="icon" type="image/png" href="/assets/logo.png">
|
||||||
<link rel="icon" type="image/svg+xml" href="/assets/logo.svg">
|
<link rel="icon" type="image/svg+xml" href="/assets/logo.svg">
|
||||||
<meta name="csrf-token" content="">
|
<meta name="csrf-token" content="">
|
||||||
@@ -36,9 +36,15 @@
|
|||||||
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
|
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
|
||||||
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
|
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<!-- Drop shadow filter -->
|
<!-- Drop shadow filter with animated attributes for a lifting effect -->
|
||||||
<filter id="shadowFilter" x="-20%" y="-20%" width="140%" height="140%">
|
<filter id="shadowFilter" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
<feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="#000" flood-opacity="0.2" />
|
<feDropShadow id="dropShadow" dx="0" dy="2" stdDeviation="2" flood-color="#000" flood-opacity="0.2">
|
||||||
|
<!-- Animate the vertical offset: from 2 to 1 (as it rises), hold, then back to 2 -->
|
||||||
|
<animate attributeName="dy" values="2;1;1;2" keyTimes="0;0.2;0.8;1" dur="5s" fill="freeze" />
|
||||||
|
<!-- Animate the blur similarly: from 2 to 1.5 then back to 2 -->
|
||||||
|
<animate attributeName="stdDeviation" values="2;1.5;1.5;2" keyTimes="0;0.2;0.8;1" dur="5s"
|
||||||
|
fill="freeze" />
|
||||||
|
</feDropShadow>
|
||||||
</filter>
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
@@ -62,31 +68,32 @@
|
|||||||
fill: #1565C0;
|
fill: #1565C0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<!-- Cabinet Body with rounded corners, white outline, and drop shadow -->
|
<!-- Group that will animate upward and then back down once -->
|
||||||
<rect x="4" y="4" width="56" height="56" rx="6" ry="6" class="cabinet" filter="url(#shadowFilter)" />
|
<g id="cabinetGroup">
|
||||||
<!-- Divider lines for drawers -->
|
<!-- Cabinet Body with rounded corners, white outline, and drop shadow -->
|
||||||
<line x1="5" y1="22" x2="59" y2="22" class="divider" />
|
<rect x="4" y="4" width="56" height="56" rx="6" ry="6" class="cabinet" filter="url(#shadowFilter)" />
|
||||||
<line x1="5" y1="34" x2="59" y2="34" class="divider" />
|
<!-- Divider lines for drawers -->
|
||||||
<!-- Drawers with Handles -->
|
<line x1="5" y1="22" x2="59" y2="22" class="divider" />
|
||||||
<rect x="8" y="24" width="48" height="6" rx="1" ry="1" class="drawer" />
|
<line x1="5" y1="34" x2="59" y2="34" class="divider" />
|
||||||
<circle cx="54" cy="27" r="1.5" class="handle" />
|
<!-- Drawers with Handles -->
|
||||||
|
<rect x="8" y="24" width="48" height="6" rx="1" ry="1" class="drawer" />
|
||||||
<rect x="8" y="36" width="48" height="6" rx="1" ry="1" class="drawer" />
|
<circle cx="54" cy="27" r="1.5" class="handle" />
|
||||||
<circle cx="54" cy="39" r="1.5" class="handle" />
|
<rect x="8" y="36" width="48" height="6" rx="1" ry="1" class="drawer" />
|
||||||
|
<circle cx="54" cy="39" r="1.5" class="handle" />
|
||||||
<rect x="8" y="48" width="48" height="6" rx="1" ry="1" class="drawer" />
|
<rect x="8" y="48" width="48" height="6" rx="1" ry="1" class="drawer" />
|
||||||
<circle cx="54" cy="51" r="1.5" class="handle" />
|
<circle cx="54" cy="51" r="1.5" class="handle" />
|
||||||
|
<!-- Additional detail: a small top handle on the cabinet door -->
|
||||||
<!-- Additional detail: a small top handle on the cabinet door -->
|
<rect x="28" y="10" width="8" height="4" rx="1" ry="1" fill="#1565C0" />
|
||||||
<rect x="28" y="10" width="8" height="4" rx="1" ry="1" fill="#1565C0" />
|
<!-- Animate transform: rises by 2 pixels over 1s, holds for 3s, then falls over 1s (total 5s) -->
|
||||||
|
<animateTransform attributeName="transform" type="translate" values="0 0; 0 -2; 0 -2; 0 0"
|
||||||
|
keyTimes="0;0.2;0.8;1" dur="5s" fill="freeze" />
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header-title">
|
<div class="header-title">
|
||||||
<h1>Multi File Upload Editor</h1>
|
<h1>FileRise</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="header-buttons">
|
<div class="header-buttons">
|
||||||
<button id="logoutBtn" title="Logout">
|
<button id="logoutBtn" title="Logout">
|
||||||
@@ -95,7 +102,6 @@
|
|||||||
<button id="changePasswordBtn" title="Change Password">
|
<button id="changePasswordBtn" title="Change Password">
|
||||||
<i class="material-icons">vpn_key</i>
|
<i class="material-icons">vpn_key</i>
|
||||||
</button>
|
</button>
|
||||||
<!-- Restore Files Modal (Admin Only) -->
|
|
||||||
<div id="restoreFilesModal" class="modal centered-modal" style="display: none;">
|
<div id="restoreFilesModal" class="modal centered-modal" style="display: none;">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h4 class="custom-restore-header">
|
<h4 class="custom-restore-header">
|
||||||
@@ -130,198 +136,193 @@
|
|||||||
|
|
||||||
<!-- Custom Toast Container -->
|
<!-- Custom Toast Container -->
|
||||||
<div id="customToast"></div>
|
<div id="customToast"></div>
|
||||||
<div class="container-fluid">
|
|
||||||
<!-- Login Form -->
|
|
||||||
<div class="row" id="loginForm">
|
|
||||||
<div class="col-12">
|
|
||||||
<form id="authForm" method="post">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="loginUsername">User:</label>
|
|
||||||
<input type="text" class="form-control" id="loginUsername" name="username" required />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="loginPassword">Password:</label>
|
|
||||||
<input type="password" class="form-control" id="loginPassword" name="password" required />
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary btn-block btn-login">Login</button>
|
|
||||||
<div class="form-group remember-me-container">
|
|
||||||
<input type="checkbox" id="rememberMeCheckbox" name="remember_me" />
|
|
||||||
<label for="rememberMeCheckbox">Remember me</label>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main Operations: Upload and Folder Management -->
|
<!-- Main Wrapper: Hidden by default; remove "display: none;" after login -->
|
||||||
<div id="mainOperations">
|
<div class="main-wrapper">
|
||||||
<div class="container" style="max-width: 1400px; margin: 0 auto;">
|
<!-- Sidebar Drop Zone: Hidden until you drag a card (display controlled by JS) -->
|
||||||
<div class="row align-items-start" id="uploadFolderRow">
|
<div id="sidebarDropArea" class="drop-target-sidebar"></div>
|
||||||
<!-- Upload Card: 50% width on medium, 58% on large -->
|
<!-- Main Column -->
|
||||||
<div class="col-md-6 col-lg-7 d-flex">
|
<div id="mainColumn" class="main-column">
|
||||||
<div id="uploadCard" class="card flex-fill" style="max-width: 900px; width: 100%;">
|
<div class="container-fluid">
|
||||||
<div class="card-header">Upload Files/Folders</div>
|
<!-- Login Form (unchanged) -->
|
||||||
<div class="card-body d-flex flex-column">
|
<div class="row" id="loginForm">
|
||||||
<form id="uploadFileForm" method="post" enctype="multipart/form-data" class="d-flex flex-column"
|
<div class="col-12">
|
||||||
style="height: 100%;" novalidate>
|
<form id="authForm" method="post">
|
||||||
<div class="form-group flex-grow-1" style="margin-bottom: 1rem;">
|
<div class="form-group">
|
||||||
<div id="uploadDropArea"
|
<label for="loginUsername">User:</label>
|
||||||
style="border:2px dashed #ccc; padding:20px; cursor:pointer; height:100%; display:flex; flex-direction:column; justify-content:center; align-items:center; position:relative;">
|
<input type="text" class="form-control" id="loginUsername" name="username" required />
|
||||||
<span>Drop files/folders here or click 'Choose Files'</span>
|
</div>
|
||||||
<br />
|
<div class="form-group">
|
||||||
<!-- Note: Remove directory attributes so file picker only allows files -->
|
<label for="loginPassword">Password:</label>
|
||||||
<input type="file" id="file" name="file[]" class="form-control-file" multiple
|
<input type="password" class="form-control" id="loginPassword" name="password" required />
|
||||||
style="opacity:0; position:absolute; width:1px; height:1px;" />
|
</div>
|
||||||
<button type="button" id="customChooseBtn">Choose Files</button>
|
<button type="submit" class="btn btn-primary btn-block btn-login">Login</button>
|
||||||
|
<div class="form-group remember-me-container">
|
||||||
|
<input type="checkbox" id="rememberMeCheckbox" name="remember_me" />
|
||||||
|
<label for="rememberMeCheckbox">Remember me</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Operations: Upload and Folder Management -->
|
||||||
|
<div id="mainOperations">
|
||||||
|
<div class="container" style="max-width: 1400px; margin: 0 auto;">
|
||||||
|
<!-- Top Zone: Two columns (60% and 40%) -->
|
||||||
|
<div id="uploadFolderRow" class="row">
|
||||||
|
<!-- Left Column (60% for Upload Card) -->
|
||||||
|
<div id="leftCol" class="col-md-7" style="display: flex; justify-content: center;">
|
||||||
|
<div id="uploadCard" class="card" style="width: 100%;">
|
||||||
|
<div class="card-header">Upload Files/Folders</div>
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
<form id="uploadFileForm" method="post" enctype="multipart/form-data" class="d-flex flex-column">
|
||||||
|
<div class="form-group flex-grow-1" style="margin-bottom: 1rem;">
|
||||||
|
<div id="uploadDropArea"
|
||||||
|
style="border:2px dashed #ccc; padding:20px; cursor:pointer; display:flex; flex-direction:column; justify-content:center; align-items:center; position:relative;">
|
||||||
|
<span>Drop files/folders here or click 'Choose Files'</span>
|
||||||
|
<br />
|
||||||
|
<input type="file" id="file" name="file[]" class="form-control-file" multiple
|
||||||
|
style="opacity:0; position:absolute; width:1px; height:1px;" />
|
||||||
|
<button type="button" id="customChooseBtn">Choose Files</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" id="uploadBtn" class="btn btn-primary d-block mx-auto">Upload</button>
|
||||||
|
<div id="uploadProgressContainer"></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Right Column (40% for Folder Management Card) -->
|
||||||
|
<div id="rightCol" class="col-md-5" style="display: flex; justify-content: center;">
|
||||||
|
<div id="folderManagementCard" class="card" style="width: 100%; position: relative;">
|
||||||
|
<div class="card-header" style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<span>Folder Navigation & Management</span>
|
||||||
|
<button id="folderHelpBtn" class="btn btn-link" title="Folder Help"
|
||||||
|
style="padding: 0; border: none; background: none;">
|
||||||
|
<i class="material-icons folder-help-icon" style="font-size: 24px;">info</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body custom-folder-card-body">
|
||||||
|
<div class="form-group d-flex align-items-top" style="padding-top:0; margin-bottom:0;">
|
||||||
|
<div id="folderTreeContainer"></div>
|
||||||
|
</div>
|
||||||
|
<div class="folder-actions mt-3">
|
||||||
|
<button id="createFolderBtn" class="btn btn-primary">Create Folder</button>
|
||||||
|
<div id="createFolderModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Create Folder</h4>
|
||||||
|
<input type="text" id="newFolderName" class="form-control" placeholder="Enter folder name"
|
||||||
|
style="margin-top:10px;" />
|
||||||
|
<div style="margin-top:15px; text-align:right;">
|
||||||
|
<button id="cancelCreateFolder" class="btn btn-secondary">Cancel</button>
|
||||||
|
<button id="submitCreateFolder" class="btn btn-primary">Create</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="renameFolderBtn" class="btn btn-secondary ml-2" title="Rename Folder">
|
||||||
|
<i class="material-icons">drive_file_rename_outline</i>
|
||||||
|
</button>
|
||||||
|
<div id="renameFolderModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Rename Folder</h4>
|
||||||
|
<input type="text" id="newRenameFolderName" class="form-control"
|
||||||
|
placeholder="Enter new folder name" style="margin-top:10px;" />
|
||||||
|
<div style="margin-top:15px; text-align:right;">
|
||||||
|
<button id="cancelRenameFolder" class="btn btn-secondary">Cancel</button>
|
||||||
|
<button id="submitRenameFolder" class="btn btn-primary">Rename</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="deleteFolderBtn" class="btn btn-danger ml-2" title="Delete Folder">
|
||||||
|
<i class="material-icons">delete</i>
|
||||||
|
</button>
|
||||||
|
<div id="deleteFolderModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Delete Folder</h4>
|
||||||
|
<p id="deleteFolderMessage">Are you sure you want to delete this folder?</p>
|
||||||
|
<div style="margin-top:15px; text-align:right;">
|
||||||
|
<button id="cancelDeleteFolder" class="btn btn-secondary">Cancel</button>
|
||||||
|
<button id="confirmDeleteFolder" class="btn btn-danger">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="folderHelpTooltip" class="folder-help-tooltip"
|
||||||
|
style="display: none; position: absolute; top: 50px; right: 15px; background: #fff; border: 1px solid #ccc; padding: 10px; z-index: 1000; box-shadow: 2px 2px 6px rgba(0,0,0,0.2);">
|
||||||
|
<ul class="folder-help-list" style="margin: 0; padding-left: 20px;">
|
||||||
|
<li>Click on a folder in the tree to view its files.</li>
|
||||||
|
<li>Use [-] to collapse and [+] to expand folders.</li>
|
||||||
|
<li>Select a folder and click "Create Folder" to add a subfolder.</li>
|
||||||
|
<li>To rename or delete a folder, select it and then click the appropriate button.</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" id="uploadBtn" class="btn btn-primary d-block mx-auto">Upload</button>
|
</div>
|
||||||
<div id="uploadProgressContainer"></div>
|
</div>
|
||||||
</form>
|
</div> <!-- end uploadFolderRow -->
|
||||||
|
</div> <!-- end container -->
|
||||||
|
</div> <!-- end mainOperations -->
|
||||||
|
|
||||||
|
<!-- File List Section -->
|
||||||
|
<div id="fileListContainer" style="display: none;">
|
||||||
|
<h2 id="fileListTitle">Files in (Root)</h2>
|
||||||
|
<div id="fileListActions" class="file-list-actions">
|
||||||
|
<button id="deleteSelectedBtn" class="btn action-btn" style="display: none;">Delete Files</button>
|
||||||
|
<div id="deleteFilesModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Delete Selected Files</h4>
|
||||||
|
<p id="deleteFilesMessage">Are you sure you want to delete the selected files?</p>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button id="cancelDeleteFiles" class="btn btn-secondary">Cancel</button>
|
||||||
|
<button id="confirmDeleteFiles" class="btn btn-danger">Delete</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<button id="copySelectedBtn" class="btn action-btn" style="display: none;" disabled>Copy Files</button>
|
||||||
|
<div id="copyFilesModal" class="modal">
|
||||||
<!-- Folder Management Card -->
|
<div class="modal-content">
|
||||||
<div class="col-md-6 col-lg-5 d-flex">
|
<h4>Copy Selected Files</h4>
|
||||||
<div id="folderManagementCard" class="card flex-fill"
|
<p id="copyFilesMessage">Select a target folder for copying the selected files:</p>
|
||||||
style="max-width: 900px; width: 100%; position: relative;">
|
<select id="copyTargetFolder" class="form-control modal-input"></select>
|
||||||
<!-- Card header with folder management title and help icon -->
|
<div class="modal-footer">
|
||||||
<div class="card-header" style="display: flex; justify-content: space-between; align-items: center;">
|
<button id="cancelCopyFiles" class="btn btn-secondary">Cancel</button>
|
||||||
<span>Folder Navigation & Management</span>
|
<button id="confirmCopyFiles" class="btn btn-primary">Copy</button>
|
||||||
<button id="folderHelpBtn" class="btn btn-link" title="Folder Help"
|
</div>
|
||||||
style="padding: 0; border: none; background: none;">
|
|
||||||
<i class="material-icons folder-help-icon" style="font-size: 24px;">info</i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body custom-folder-card-body">
|
</div>
|
||||||
<div class="form-group d-flex align-items-top" style="padding-top:0; margin-bottom:0;">
|
<button id="moveSelectedBtn" class="btn action-btn" style="display: none;" disabled>Move Files</button>
|
||||||
<div id="folderTreeContainer"></div>
|
<div id="moveFilesModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h4>Move Selected Files</h4>
|
||||||
|
<p id="moveFilesMessage">Select a target folder for moving the selected files:</p>
|
||||||
|
<select id="moveTargetFolder" class="form-control modal-input"></select>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button id="cancelMoveFiles" class="btn btn-secondary">Cancel</button>
|
||||||
|
<button id="confirmMoveFiles" class="btn btn-primary">Move</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Folder actions (create, rename, delete) -->
|
</div>
|
||||||
<div class="folder-actions mt-3">
|
</div>
|
||||||
<button id="createFolderBtn" class="btn btn-primary">Create Folder</button>
|
<button id="downloadZipBtn" class="btn action-btn" style="display: none;" disabled>Download ZIP</button>
|
||||||
<!-- Create Folder Modal -->
|
<button id="extractZipBtn" class="btn btn-sm btn-info" title="Extract Zip">Extract Zip</button>
|
||||||
<div id="createFolderModal" class="modal">
|
<div id="downloadZipModal" class="modal" style="display:none;">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h4>Create Folder</h4>
|
<h4>Download Selected Files as Zip</h4>
|
||||||
<input type="text" id="newFolderName" class="form-control" placeholder="Enter folder name"
|
<p>Enter a name for the zip file:</p>
|
||||||
style="margin-top:10px;" />
|
<input type="text" id="zipFileNameInput" class="form-control" placeholder="files.zip" />
|
||||||
<div style="margin-top:15px; text-align:right;">
|
<div class="modal-footer" style="margin-top:15px; text-align:right;">
|
||||||
<button id="cancelCreateFolder" class="btn btn-secondary">Cancel</button>
|
<button id="cancelDownloadZip" class="btn btn-secondary">Cancel</button>
|
||||||
<button id="submitCreateFolder" class="btn btn-primary">Create</button>
|
<button id="confirmDownloadZip" class="btn btn-primary">Download</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button id="renameFolderBtn" class="btn btn-secondary ml-2" title="Rename Folder">
|
|
||||||
<i class="material-icons">drive_file_rename_outline</i>
|
|
||||||
</button>
|
|
||||||
<!-- Rename Folder Modal -->
|
|
||||||
<div id="renameFolderModal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h4>Rename Folder</h4>
|
|
||||||
<input type="text" id="newRenameFolderName" class="form-control"
|
|
||||||
placeholder="Enter new folder name" style="margin-top:10px;" />
|
|
||||||
<div style="margin-top:15px; text-align:right;">
|
|
||||||
<button id="cancelRenameFolder" class="btn btn-secondary">Cancel</button>
|
|
||||||
<button id="submitRenameFolder" class="btn btn-primary">Rename</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button id="deleteFolderBtn" class="btn btn-danger ml-2" title="Delete Folder">
|
|
||||||
<i class="material-icons">delete</i>
|
|
||||||
</button>
|
|
||||||
<!-- Delete Folder Modal -->
|
|
||||||
<div id="deleteFolderModal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h4>Delete Folder</h4>
|
|
||||||
<p id="deleteFolderMessage">Are you sure you want to delete this folder?</p>
|
|
||||||
<div style="margin-top:15px; text-align:right;">
|
|
||||||
<button id="cancelDeleteFolder" class="btn btn-secondary">Cancel</button>
|
|
||||||
<button id="confirmDeleteFolder" class="btn btn-danger">Delete</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Help Tooltip: Initially hidden -->
|
|
||||||
<div id="folderHelpTooltip" class="folder-help-tooltip"
|
|
||||||
style="display: none; position: absolute; top: 50px; right: 15px; background: #fff; border: 1px solid #ccc; padding: 10px; z-index: 1000; box-shadow: 2px 2px 6px rgba(0,0,0,0.2);">
|
|
||||||
<ul class="folder-help-list" style="margin: 0; padding-left: 20px;">
|
|
||||||
<li>Click on a folder in the tree to view its files.</li>
|
|
||||||
<li>Use [-] to collapse and [+] to expand folders.</li>
|
|
||||||
<li>Select a folder and click "Create Folder" to add a subfolder.</li>
|
|
||||||
<li>To rename or delete a folder, select it and then click the appropriate button.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="fileList"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> <!-- end container-fluid -->
|
||||||
</div>
|
</div> <!-- end mainColumn -->
|
||||||
<!-- File List Section -->
|
</div> <!-- end main-wrapper -->
|
||||||
<div id="fileListContainer" style="display: none;">
|
|
||||||
<h2 id="fileListTitle">Files in (Root)</h2>
|
|
||||||
<div id="fileListActions" class="file-list-actions">
|
|
||||||
<button id="deleteSelectedBtn" class="btn action-btn" style="display: none;">Delete Files</button>
|
|
||||||
<!-- Delete Files Modal -->
|
|
||||||
<div id="deleteFilesModal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h4>Delete Selected Files</h4>
|
|
||||||
<p id="deleteFilesMessage">Are you sure you want to delete the selected files?</p>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button id="cancelDeleteFiles" class="btn btn-secondary">Cancel</button>
|
|
||||||
<button id="confirmDeleteFiles" class="btn btn-danger">Delete</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="copySelectedBtn" class="btn action-btn" style="display: none;" disabled>Copy Files</button>
|
<!-- Change Password, Add User, Remove User, Rename File, and Custom Confirm Modals (unchanged) -->
|
||||||
<!-- Copy Files Modal -->
|
|
||||||
<div id="copyFilesModal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h4>Copy Selected Files</h4>
|
|
||||||
<p id="copyFilesMessage">Select a target folder for copying the selected files:</p>
|
|
||||||
<select id="copyTargetFolder" class="form-control modal-input"></select>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button id="cancelCopyFiles" class="btn btn-secondary">Cancel</button>
|
|
||||||
<button id="confirmCopyFiles" class="btn btn-primary">Copy</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="moveSelectedBtn" class="btn action-btn" style="display: none;" disabled>Move Files</button>
|
|
||||||
<!-- Move Files Modal -->
|
|
||||||
<div id="moveFilesModal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h4>Move Selected Files</h4>
|
|
||||||
<p id="moveFilesMessage">Select a target folder for moving the selected files:</p>
|
|
||||||
<select id="moveTargetFolder" class="form-control modal-input"></select>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button id="cancelMoveFiles" class="btn btn-secondary">Cancel</button>
|
|
||||||
<button id="confirmMoveFiles" class="btn btn-primary">Move</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button id="downloadZipBtn" class="btn action-btn" style="display: none;" disabled>Download ZIP</button>
|
|
||||||
<button id="extractZipBtn" class="btn btn-sm btn-info" title="Extract Zip">Extract Zip</button>
|
|
||||||
<!-- Download Zip Modal -->
|
|
||||||
<div id="downloadZipModal" class="modal" style="display:none;">
|
|
||||||
<div class="modal-content">
|
|
||||||
<h4>Download Selected Files as Zip</h4>
|
|
||||||
<p>Enter a name for the zip file:</p>
|
|
||||||
<input type="text" id="zipFileNameInput" class="form-control" placeholder="files.zip" />
|
|
||||||
<div class="modal-footer" style="margin-top:15px; text-align:right;">
|
|
||||||
<button id="cancelDownloadZip" class="btn btn-secondary">Cancel</button>
|
|
||||||
<button id="confirmDownloadZip" class="btn btn-primary">Download</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="fileList"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Change Password-->
|
|
||||||
<div id="changePasswordModal" class="modal" style="display:none;">
|
<div id="changePasswordModal" class="modal" style="display:none;">
|
||||||
<div class="modal-content" style="max-width:400px; margin:auto;">
|
<div class="modal-content" style="max-width:400px; margin:auto;">
|
||||||
<span id="closeChangePasswordModal" style="cursor:pointer;">×</span>
|
<span id="closeChangePasswordModal" style="cursor:pointer;">×</span>
|
||||||
@@ -333,8 +334,6 @@
|
|||||||
<button id="saveNewPasswordBtn" class="btn btn-primary" style="width:100%;">Save</button>
|
<button id="saveNewPasswordBtn" class="btn btn-primary" style="width:100%;">Save</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add User Modal -->
|
|
||||||
<div id="addUserModal" class="modal">
|
<div id="addUserModal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h3>Create New User</h3>
|
<h3>Create New User</h3>
|
||||||
@@ -352,8 +351,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Remove User Modal -->
|
|
||||||
<div id="removeUserModal" class="modal">
|
<div id="removeUserModal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h3>Remove User</h3>
|
<h3>Remove User</h3>
|
||||||
@@ -365,8 +362,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rename File Modal -->
|
|
||||||
<div id="renameFileModal" class="modal">
|
<div id="renameFileModal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h4>Rename File</h4>
|
<h4>Rename File</h4>
|
||||||
@@ -378,8 +373,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Custom Confirm Modal -->
|
|
||||||
<div id="customConfirmModal" class="modal" style="display:none;">
|
<div id="customConfirmModal" class="modal" style="display:none;">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<p id="confirmMessage"></p>
|
<p id="confirmMessage"></p>
|
||||||
|
|||||||
11
main.js
@@ -17,6 +17,7 @@ import { loadFolderTree } from './folderManager.js';
|
|||||||
import { initUpload } from './upload.js';
|
import { initUpload } from './upload.js';
|
||||||
import { initAuth, checkAuthentication } from './auth.js';
|
import { initAuth, checkAuthentication } from './auth.js';
|
||||||
import { setupTrashRestoreDelete } from './trashRestoreDelete.js';
|
import { setupTrashRestoreDelete } from './trashRestoreDelete.js';
|
||||||
|
import { initDragAndDrop, loadSidebarOrder } from './dragAndDrop.js'
|
||||||
|
|
||||||
function loadCsrfToken() {
|
function loadCsrfToken() {
|
||||||
fetch('token.php', { credentials: 'include' })
|
fetch('token.php', { credentials: 'include' })
|
||||||
@@ -64,6 +65,14 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
// Call initAuth synchronously.
|
// Call initAuth synchronously.
|
||||||
initAuth();
|
initAuth();
|
||||||
|
|
||||||
|
const newPasswordInput = document.getElementById("newPassword");
|
||||||
|
if (newPasswordInput) {
|
||||||
|
newPasswordInput.addEventListener("input", function() {
|
||||||
|
console.log("newPassword input event:", this.value);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error("newPassword input not found!");
|
||||||
|
}
|
||||||
// --- Dark Mode Persistence ---
|
// --- Dark Mode Persistence ---
|
||||||
const darkModeToggle = document.getElementById("darkModeToggle");
|
const darkModeToggle = document.getElementById("darkModeToggle");
|
||||||
const storedDarkMode = localStorage.getItem("darkMode");
|
const storedDarkMode = localStorage.getItem("darkMode");
|
||||||
@@ -121,6 +130,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
window.currentFolder = "root";
|
window.currentFolder = "root";
|
||||||
loadFileList(window.currentFolder);
|
loadFileList(window.currentFolder);
|
||||||
|
initDragAndDrop();
|
||||||
|
loadSidebarOrder();
|
||||||
initFileActions();
|
initFileActions();
|
||||||
initUpload();
|
initUpload();
|
||||||
loadFolderTree();
|
loadFolderTree();
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 574 KiB After Width: | Height: | Size: 626 KiB |
BIN
resources/dark-gallery.png
Normal file
|
After Width: | Height: | Size: 662 KiB |
BIN
resources/dark-login.png
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
resources/dark-mode-default.png
Normal file
|
After Width: | Height: | Size: 346 KiB |
|
Before Width: | Height: | Size: 311 KiB After Width: | Height: | Size: 376 KiB |
|
Before Width: | Height: | Size: 3.7 MiB After Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 321 KiB After Width: | Height: | Size: 400 KiB |
BIN
resources/light-preview.png
Normal file
|
After Width: | Height: | Size: 3.2 MiB |
BIN
resources/light-share.png
Normal file
|
After Width: | Height: | Size: 412 KiB |
BIN
resources/light-trash.png
Normal file
|
After Width: | Height: | Size: 502 KiB |
|
Before Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 416 KiB |
324
styles.css
@@ -25,22 +25,15 @@ body {
|
|||||||
/* CONTAINER */
|
/* CONTAINER */
|
||||||
.container,
|
.container,
|
||||||
.container-fluid {
|
.container-fluid {
|
||||||
margin-top: 20px;
|
margin-top: 10px;
|
||||||
padding-right: 4px !important;
|
padding-right: 4px !important;
|
||||||
padding-left: 4px !important;
|
padding-left: 4px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 1300px) {
|
||||||
.container-fluid {
|
.container-fluid {
|
||||||
padding-left: 50px !important;
|
padding-left: 40px !important;
|
||||||
padding-right: 50px !important;
|
padding-right: 40px !important;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
.container-fluid {
|
|
||||||
padding-left: 100px !important;
|
|
||||||
padding-right: 100px !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,12 +45,6 @@ body {
|
|||||||
/* FLEXBOX HEADER: LOGO, TITLE, BUTTONS FIXED */
|
/* FLEXBOX HEADER: LOGO, TITLE, BUTTONS FIXED */
|
||||||
/************************************************************/
|
/************************************************************/
|
||||||
|
|
||||||
|
|
||||||
#uploadCard,
|
|
||||||
#folderManagementCard {
|
|
||||||
min-height: 342px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-login {
|
.btn-login {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
@@ -236,17 +223,19 @@ body.dark-mode .folder-help-tooltip {
|
|||||||
#folderHelpBtn i.material-icons.folder-help-icon {
|
#folderHelpBtn i.material-icons.folder-help-icon {
|
||||||
-webkit-text-fill-color: orange !important;
|
-webkit-text-fill-color: orange !important;
|
||||||
color: inherit !important;
|
color: inherit !important;
|
||||||
|
padding-right: 10px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark-mode #folderHelpBtn i.material-icons.folder-help-icon {
|
body.dark-mode #folderHelpBtn i.material-icons.folder-help-icon {
|
||||||
-webkit-text-fill-color: #ffa500 !important;
|
-webkit-text-fill-color: #ffa500 !important;
|
||||||
|
padding-right: 10px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************************************/
|
/************************************************************/
|
||||||
/* RESPONSIVE HEADER FIXES */
|
/* RESPONSIVE HEADER FIXES */
|
||||||
/************************************************************/
|
/************************************************************/
|
||||||
|
|
||||||
@media (max-width: 970px) {
|
@media (max-width: 790px) {
|
||||||
.header-container {
|
.header-container {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
height: auto;
|
height: auto;
|
||||||
@@ -286,7 +275,7 @@ body.dark-mode #folderHelpBtn i.material-icons.folder-help-icon {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
margin-top: 10px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,21 +368,6 @@ body.dark-mode #loginForm button:hover {
|
|||||||
CARDS & MODALS
|
CARDS & MODALS
|
||||||
=========================================================== */
|
=========================================================== */
|
||||||
|
|
||||||
.card {
|
|
||||||
background-color: #fff;
|
|
||||||
color: #000;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
max-width: 900px;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .card {
|
|
||||||
background-color: #2c2c2c;
|
|
||||||
color: #e0e0e0;
|
|
||||||
border: 1px solid #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
#restoreFilesModal .modal-content {
|
#restoreFilesModal .modal-content {
|
||||||
position: fixed !important;
|
position: fixed !important;
|
||||||
top: 50% !important;
|
top: 50% !important;
|
||||||
@@ -1023,29 +997,6 @@ label {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#uploadFolderRow {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
#uploadFolderRow .col-md-6 {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#uploadFolderRow .col-md-6:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-body .form-group {
|
|
||||||
margin-bottom: 5px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#createFolderBtn {
|
#createFolderBtn {
|
||||||
margin-top: 0px !important;
|
margin-top: 0px !important;
|
||||||
height: 40px !important;
|
height: 40px !important;
|
||||||
@@ -1100,34 +1051,6 @@ body.dark-mode .custom-prev-next-btn:hover:not(:disabled) {
|
|||||||
background-color: #555;
|
background-color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.folder-option:hover {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
padding: 2px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.folder-option.selected {
|
|
||||||
background-color: #d0d0d0;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 2px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .folder-option.selected {
|
|
||||||
background-color: #444;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 2px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .folder-option:hover {
|
|
||||||
background-color: #333;
|
|
||||||
color: #fff;
|
|
||||||
padding: 2px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-folder-card-body {
|
|
||||||
padding-top: 5px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#customToast {
|
#customToast {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
@@ -1175,8 +1098,10 @@ body.dark-mode .folder-option:hover {
|
|||||||
|
|
||||||
#fileListContainer {
|
#fileListContainer {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 10px 5px;
|
padding-bottom: 10px !important;
|
||||||
margin: 20px auto;
|
padding-left: 5px !important;
|
||||||
|
padding-right: 5px !important;
|
||||||
|
margin: 0 auto 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 750px) {
|
@media (max-width: 750px) {
|
||||||
@@ -1190,11 +1115,6 @@ body.dark-mode #fileListContainer {
|
|||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
border: 1px solid #444;
|
border: 1px solid #444;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding-top: 10px !important;
|
|
||||||
padding-bottom: 10px !important;
|
|
||||||
padding-left: 5px !important;
|
|
||||||
padding-right: 5px !important;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#fileListContainer>h2,
|
#fileListContainer>h2,
|
||||||
@@ -1224,6 +1144,7 @@ body.dark-mode #fileListContainer {
|
|||||||
|
|
||||||
#fileListTitle {
|
#fileListTitle {
|
||||||
font-size: 1.8em;
|
font-size: 1.8em;
|
||||||
|
margin-top: 10px;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1288,16 +1209,13 @@ body.dark-mode #fileListContainer {
|
|||||||
.breadcrumb-link {
|
.breadcrumb-link {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #007bff;
|
color: #007bff;
|
||||||
/* Blue color, for example */
|
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Change color on hover */
|
|
||||||
.breadcrumb-link:hover {
|
.breadcrumb-link:hover {
|
||||||
color: #0056b3;
|
color: #0056b3;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style for the selected breadcrumb */
|
|
||||||
.breadcrumb-link.selected {
|
.breadcrumb-link.selected {
|
||||||
background-color: #e9ecef;
|
background-color: #e9ecef;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -1337,15 +1255,38 @@ body.dark-mode #fileListContainer {
|
|||||||
width: 30px;
|
width: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.folder-option {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#folderTreeContainer {
|
#folderTreeContainer {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.folder-option {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-option:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-option.selected {
|
||||||
|
background-color: #d0d0d0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .folder-option.selected {
|
||||||
|
background-color: #444;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .folder-option:hover {
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ===========================================================
|
/* ===========================================================
|
||||||
FILE MANAGER INLINE STYLE REMOVAL - New Classes
|
FILE MANAGER INLINE STYLE REMOVAL - New Classes
|
||||||
=========================================================== */
|
=========================================================== */
|
||||||
@@ -1353,17 +1294,11 @@ body.dark-mode #fileListContainer {
|
|||||||
.image-modal-header {
|
.image-modal-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
/* Vertically center the text within a fixed height */
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
/* Center horizontally */
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
/* Prevent wrapping */
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
/* Hide any overflowing text */
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
/* Truncate with an ellipsis */
|
|
||||||
height: 25px;
|
height: 25px;
|
||||||
/* Fixed height for a single line */
|
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
@@ -1410,7 +1345,6 @@ body.dark-mode .image-preview-modal-content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.share-btn {
|
.share-btn {
|
||||||
/* Your custom styles here */
|
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
@@ -1903,3 +1837,179 @@ body.dark-mode #folderContextMenu {
|
|||||||
border-color: #555;
|
border-color: #555;
|
||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-target-sidebar {
|
||||||
|
display: none;
|
||||||
|
width: 50px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-right: 2px dashed #1565C0;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
.drop-target-sidebar {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.drop-target-sidebar.active {
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-column {
|
||||||
|
flex: 1;
|
||||||
|
transition: margin-left 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#uploadFolderRow {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
#uploadFolderRow {
|
||||||
|
gap: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#leftCol,
|
||||||
|
#rightCol {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 370px;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
#leftCol {
|
||||||
|
flex: 0 1 60%;
|
||||||
|
}
|
||||||
|
#rightCol {
|
||||||
|
flex: 0 1 40%;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.main-wrapper {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.drop-target-sidebar {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
#uploadFolderRow {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
#leftCol, #rightCol {
|
||||||
|
flex: 0 1 100% !important;
|
||||||
|
}
|
||||||
|
#rightCol {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebarDropArea.highlight,
|
||||||
|
#uploadFolderRow.highlight {
|
||||||
|
border: 2px dashed #1565C0;
|
||||||
|
background-color: #eef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-header {
|
||||||
|
cursor: grab;
|
||||||
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-header::after {
|
||||||
|
content: '⋮⋮';
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
font-size: 16px;
|
||||||
|
color: #1565C0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dragging {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 20px 30px rgba(0, 0, 0, 0.3);
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#uploadCard,
|
||||||
|
#folderManagementCard {
|
||||||
|
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
min-height: 353px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#uploadFolderRow.highlight {
|
||||||
|
min-height: 353px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebarDropArea,
|
||||||
|
#uploadFolderRow {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
#sidebarDropArea {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode #sidebarDropArea,
|
||||||
|
body.dark-mode #uploadFolderRow {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode #sidebarDropArea.highlight,
|
||||||
|
body.dark-mode #uploadFolderRow.highlight {
|
||||||
|
background-color: #333;
|
||||||
|
border: 2px dashed #555;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-target-sidebar.highlight {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-target-sidebar:not(.highlight) {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dragging:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebarDropArea > .card {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #000;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
max-width: 900px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .card {
|
||||||
|
background-color: #2c2c2c;
|
||||||
|
color: #e0e0e0;
|
||||||
|
border: 1px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-folder-card-body {
|
||||||
|
padding-top: 5px !important;
|
||||||
|
padding-right: 0 !important;
|
||||||
|
}
|
||||||