Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Advanced File Finder Pro</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
body { | |
font-family: 'Inter', sans-serif; | |
} | |
.file-icon { | |
color: #6366f1; | |
} | |
.folder-icon { | |
color: #f59e0b; | |
} | |
.sort-active { | |
background-color: #e0e7ff; | |
} | |
.file-row:hover { | |
background-color: #f8fafc; | |
transform: translateY(-1px); | |
} | |
.sidebar-item:hover { | |
background-color: #f1f5f9; | |
} | |
.transition-all { | |
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); | |
} | |
.card-shadow { | |
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03); | |
} | |
.modal-shadow { | |
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
} | |
.glow { | |
animation: glow 2s infinite alternate; | |
} | |
@keyframes glow { | |
from { | |
box-shadow: 0 0 0px 0px rgba(99, 102, 241, 0.3); | |
} | |
to { | |
box-shadow: 0 0 8px 2px rgba(99, 102, 241, 0.3); | |
} | |
} | |
.highlight { | |
background: linear-gradient(90deg, rgba(224,231,255,0) 0%, rgba(224,231,255,0.5) 50%, rgba(224,231,255,0) 100%); | |
} | |
::-webkit-scrollbar { | |
width: 8px; | |
height: 8px; | |
} | |
::-webkit-scrollbar-track { | |
background: #f1f5f9; | |
border-radius: 4px; | |
} | |
::-webkit-scrollbar-thumb { | |
background: #cbd5e1; | |
border-radius: 4px; | |
} | |
::-webkit-scrollbar-thumb:hover { | |
background: #94a3b8; | |
} | |
.modal-enter-active, .modal-leave-active { | |
transition: opacity 0.3s ease; | |
} | |
.modal-enter-from, .modal-leave-to { | |
opacity: 0; | |
} | |
.toast { | |
position: fixed; | |
bottom: 20px; | |
right: 20px; | |
background: #4f46e5; | |
color: white; | |
padding: 12px 24px; | |
border-radius: 8px; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
transform: translateY(100px); | |
opacity: 0; | |
transition: all 0.3s ease; | |
z-index: 1000; | |
} | |
.toast.show { | |
transform: translateY(0); | |
opacity: 1; | |
} | |
.context-menu { | |
position: absolute; | |
background: white; | |
border-radius: 8px; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
z-index: 100; | |
min-width: 200px; | |
overflow: hidden; | |
display: none; | |
} | |
.context-menu-item { | |
padding: 8px 16px; | |
cursor: pointer; | |
display: flex; | |
align-items: center; | |
gap: 8px; | |
} | |
.context-menu-item:hover { | |
background: #f1f5f9; | |
} | |
.breadcrumb-item { | |
display: flex; | |
align-items: center; | |
gap: 4px; | |
cursor: pointer; | |
} | |
.breadcrumb-item:hover { | |
color: #4f46e5; | |
} | |
.breadcrumb-separator { | |
margin: 0 4px; | |
color: #94a3b8; | |
} | |
</style> | |
</head> | |
<body class="bg-slate-50"> | |
<div id="app" class="flex h-screen"> | |
<!-- Sidebar --> | |
<div class="w-64 bg-white border-r border-slate-200 flex flex-col"> | |
<div class="p-5 border-b border-slate-200"> | |
<div class="flex items-center space-x-3 mb-6"> | |
<div class="w-9 h-9 rounded-lg bg-indigo-100 flex items-center justify-center"> | |
<i class="fas fa-search text-indigo-500"></i> | |
</div> | |
<h1 class="text-xl font-semibold text-slate-800">Advanced Finder Pro</h1> | |
</div> | |
<div class="relative"> | |
<input | |
type="text" | |
id="sidebarSearch" | |
placeholder="Search locations..." | |
class="w-full px-4 py-2.5 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent placeholder-slate-400" | |
> | |
<i class="fas fa-search absolute right-3 top-3 text-slate-400"></i> | |
</div> | |
</div> | |
<div class="flex-1 overflow-y-auto"> | |
<div id="sidebarLocations" class="p-2"> | |
<!-- Locations will be populated here --> | |
</div> | |
</div> | |
<div class="p-4 border-t border-slate-200 text-xs text-slate-500 bg-slate-50"> | |
<div class="flex items-center mb-1"> | |
<i class="fas fa-database mr-2"></i> | |
<span>Indexed: <span id="indexedCount">2847</span> items</span> | |
</div> | |
<div class="flex items-center"> | |
<i class="fas fa-sync-alt mr-2"></i> | |
<span>Last indexed: <span id="lastIndexed">5 minutes ago</span></span> | |
</div> | |
</div> | |
</div> | |
<!-- Main Content --> | |
<div class="flex-1 flex flex-col overflow-hidden"> | |
<!-- Toolbar --> | |
<div class="bg-white border-b border-slate-200 p-4 flex items-center justify-between"> | |
<div class="flex items-center space-x-2"> | |
<button | |
id="backBtn" | |
class="px-3 py-2 bg-white border border-slate-200 rounded-lg hover:bg-slate-50 flex items-center transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" | |
title="Go back" | |
> | |
<i class="fas fa-arrow-left mr-2"></i> | |
</button> | |
<button | |
id="refreshBtn" | |
class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 flex items-center transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" | |
title="Refresh" | |
> | |
<i class="fas fa-sync-alt mr-2"></i> Refresh | |
</button> | |
<button | |
id="indexBtn" | |
class="px-4 py-2 bg-white border border-slate-200 rounded-lg hover:bg-slate-50 flex items-center transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" | |
title="Reindex files" | |
> | |
<i class="fas fa-database mr-2"></i> | |
<span id="indexBtnText">Index Now</span> | |
</button> | |
<div class="relative" id="deleteBtnContainer" style="display: none;"> | |
<button | |
id="deleteBtn" | |
class="px-4 py-2 bg-red-50 text-red-600 rounded-lg hover:bg-red-100 flex items-center transition-all focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2" | |
title="Delete selected files" | |
> | |
<i class="fas fa-trash mr-2"></i> Delete (<span id="selectedCount">0</span>) | |
</button> | |
</div> | |
</div> | |
<div class="flex items-center space-x-3"> | |
<div class="relative"> | |
<input | |
type="text" | |
id="searchQuery" | |
placeholder="Search files..." | |
class="px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent w-64 placeholder-slate-400" | |
> | |
<i class="fas fa-search absolute right-3 top-3 text-slate-400"></i> | |
</div> | |
<button | |
id="filtersBtn" | |
class="px-4 py-2 bg-white border border-slate-200 rounded-lg hover:bg-slate-50 flex items-center transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" | |
title="Show filters" | |
> | |
<i class="fas fa-sliders-h mr-2"></i> Filters | |
</button> | |
<button | |
id="newBtn" | |
class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 flex items-center transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" | |
title="Create new" | |
> | |
<i class="fas fa-plus mr-2"></i> New | |
</button> | |
</div> | |
</div> | |
<!-- Breadcrumbs --> | |
<div id="breadcrumbs" class="bg-white border-b border-slate-200 px-5 py-2 flex items-center text-sm"> | |
<!-- Breadcrumbs will be populated here --> | |
</div> | |
<!-- Advanced Search Panel --> | |
<div | |
id="advancedSearchPanel" | |
class="bg-white border-b border-slate-200 px-5 py-4 transition-all duration-300 ease-in-out overflow-hidden" | |
style="max-height: 0;" | |
> | |
<div class="grid grid-cols-1 md:grid-cols-3 gap-5"> | |
<div> | |
<label class="block text-sm font-medium text-slate-700 mb-2">File Type</label> | |
<select | |
id="fileTypeFilter" | |
class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" | |
> | |
<option value="all">All Files</option> | |
<option value="documents">Documents</option> | |
<option value="images">Images</option> | |
<option value="audio">Audio</option> | |
<option value="video">Video</option> | |
<option value="archives">Archives</option> | |
<option value="folders">Folders</option> | |
</select> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-slate-700 mb-2">Author/Creator</label> | |
<input | |
type="text" | |
id="authorFilter" | |
placeholder="Search by author..." | |
class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent placeholder-slate-400" | |
> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-slate-700 mb-2">Size Range</label> | |
<div class="flex items-center space-x-3"> | |
<input | |
type="number" | |
id="sizeMinFilter" | |
placeholder="Min (KB)" | |
class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent placeholder-slate-400" | |
> | |
<span class="text-sm text-slate-400">to</span> | |
<input | |
type="number" | |
id="sizeMaxFilter" | |
placeholder="Max (KB)" | |
class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent placeholder-slate-400" | |
> | |
</div> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-slate-700 mb-2">Date Modified</label> | |
<select | |
id="dateRangeFilter" | |
class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" | |
> | |
<option value="all">Any Time</option> | |
<option value="today">Today</option> | |
<option value="week">This Week</option> | |
<option value="month">This Month</option> | |
<option value="year">This Year</option> | |
<option value="custom">Custom Range</option> | |
</select> | |
</div> | |
<div id="customDateRangeContainer" style="display: none;" class="md:col-span-3"> | |
<div class="grid grid-cols-1 md:grid-cols-2 gap-5"> | |
<div> | |
<label class="block text-sm font-medium text-slate-700 mb-2">From</label> | |
<input | |
type="date" | |
id="dateFromFilter" | |
class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" | |
> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-slate-700 mb-2">To</label> | |
<input | |
type="date" | |
id="dateToFilter" | |
class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" | |
> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- File Table Header --> | |
<div class="bg-white border-b border-slate-200 px-5 py-3"> | |
<div class="grid grid-cols-12 gap-4 text-xs font-medium text-slate-500 uppercase tracking-wider"> | |
<div class="col-span-5 flex items-center"> | |
<input | |
type="checkbox" | |
id="selectAllCheckbox" | |
class="mr-3 h-4 w-4 text-indigo-600 rounded border-slate-300 focus:ring-indigo-500" | |
> | |
<span | |
id="sortByName" | |
class="cursor-pointer flex items-center hover:text-slate-700 transition-colors" | |
> | |
Name | |
<i id="nameSortIcon" class="fas ml-1 fa-sort"></i> | |
</span> | |
</div> | |
<div | |
id="sortByAuthor" | |
class="col-span-2 cursor-pointer flex items-center hover:text-slate-700 transition-colors" | |
> | |
<span>Author</span> | |
<i id="authorSortIcon" class="fas ml-1 fa-sort"></i> | |
</div> | |
<div | |
id="sortByType" | |
class="col-span-2 cursor-pointer flex items-center hover:text-slate-700 transition-colors" | |
> | |
<span>Type</span> | |
<i id="typeSortIcon" class="fas ml-1 fa-sort"></i> | |
</div> | |
<div | |
id="sortBySize" | |
class="col-span-2 cursor-pointer flex items-center justify-end hover:text-slate-700 transition-colors" | |
> | |
<span>Size</span> | |
<i id="sizeSortIcon" class="fas ml-1 fa-sort"></i> | |
</div> | |
<div | |
id="sortByModified" | |
class="col-span-1 cursor-pointer flex items-center justify-end hover:text-slate-700 transition-colors" | |
> | |
<span>Modified</span> | |
<i id="modifiedSortIcon" class="fas ml-1 fa-sort"></i> | |
</div> | |
</div> | |
</div> | |
<!-- File List --> | |
<div id="fileList" class="flex-1 overflow-y-auto bg-white"> | |
<div class="flex flex-col items-center justify-center h-64 text-slate-400"> | |
<i class="fas fa-folder-open text-5xl mb-4 opacity-30"></i> | |
<p class="text-lg font-medium text-slate-500">Loading files...</p> | |
</div> | |
</div> | |
<!-- Status Bar --> | |
<div class="bg-slate-50 border-t border-slate-200 px-5 py-2.5 text-xs text-slate-500 flex justify-between"> | |
<div> | |
<span id="statusCount">0 items</span> | |
</div> | |
<div class="flex items-center space-x-5"> | |
<div class="flex items-center"> | |
<span class="mr-2">Index status:</span> | |
<span id="indexStatus" class="text-slate-400">Idle</span> | |
</div> | |
<div class="flex items-center"> | |
<i id="performanceIcon" class="fas fa-circle text-xs mr-1.5 text-emerald-500"></i> | |
<span id="performanceText">fast performance</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Context Menu --> | |
<div id="contextMenu" class="context-menu"> | |
<div class="context-menu-item" id="contextOpen"> | |
<i class="fas fa-folder-open"></i> Open | |
</div> | |
<div class="context-menu-item" id="contextDownload"> | |
<i class="fas fa-download"></i> Download | |
</div> | |
<div class="context-menu-item" id="contextRename"> | |
<i class="fas fa-pen"></i> Rename | |
</div> | |
<div class="context-menu-item" id="contextFavorite"> | |
<i class="fas fa-star"></i> Add to Favorites | |
</div> | |
<div class="border-t border-slate-200"></div> | |
<div class="context-menu-item" id="contextDelete"> | |
<i class="fas fa-trash text-red-500"></i> <span class="text-red-500">Delete</span> | |
</div> | |
</div> | |
<!-- Delete Confirmation Modal --> | |
<div id="deleteModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50 backdrop-blur-sm"> | |
<div class="bg-white rounded-xl modal-shadow p-6 w-96"> | |
<div class="flex items-start mb-5"> | |
<div class="flex-shrink-0 mt-1"> | |
<div class="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center"> | |
<i class="fas fa-exclamation-triangle text-red-500"></i> | |
</div> | |
</div> | |
<div class="ml-4"> | |
<h3 class="text-lg font-medium text-slate-800">Delete Files</h3> | |
<p class="mt-1 text-sm text-slate-500"> | |
Are you sure you want to delete <span id="deleteCount">0</span> file(s)? This action cannot be undone. | |
</p> | |
</div> | |
</div> | |
<div class="mt-6 flex justify-end space-x-3"> | |
<button | |
id="cancelDeleteBtn" | |
type="button" | |
class="px-4 py-2 bg-white border border-slate-200 rounded-lg text-slate-700 hover:bg-slate-50 transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" | |
> | |
Cancel | |
</button> | |
<button | |
id="confirmDeleteBtn" | |
type="button" | |
class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2" | |
> | |
Delete | |
</button> | |
</div> | |
</div> | |
</div> | |
<!-- New File/Folder Modal --> | |
<div id="newModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50 backdrop-blur-sm"> | |
<div class="bg-white rounded-xl modal-shadow p-6 w-96"> | |
<div class="flex items-start mb-5"> | |
<div class="flex-shrink-0 mt-1"> | |
<div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center"> | |
<i class="fas fa-plus text-indigo-500"></i> | |
</div> | |
</div> | |
<div class="ml-4"> | |
<h3 class="text-lg font-medium text-slate-800">Create New</h3> | |
<p class="mt-1 text-sm text-slate-500"> | |
Create a new file or folder in the current location. | |
</p> | |
</div> | |
</div> | |
<div class="mt-4"> | |
<div class="flex items-center space-x-2 mb-4"> | |
<button id="newFileBtn" class="flex-1 px-4 py-3 border border-slate-200 rounded-lg hover:bg-slate-50 flex flex-col items-center"> | |
<i class="fas fa-file text-indigo-500 text-xl mb-2"></i> | |
<span>File</span> | |
</button> | |
<button id="newFolderBtn" class="flex-1 px-4 py-3 border border-slate-200 rounded-lg hover:bg-slate-50 flex flex-col items-center"> | |
<i class="fas fa-folder text-amber-500 text-xl mb-2"></i> | |
<span>Folder</span> | |
</button> | |
</div> | |
<div id="newFormContainer" style="display: none;"> | |
<div class="mb-4"> | |
<label class="block text-sm font-medium text-slate-700 mb-1">Name</label> | |
<input | |
type="text" | |
id="newNameInput" | |
placeholder="Enter name..." | |
class="w-full px-4 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent placeholder-slate-400" | |
> | |
</div> | |
<div class="flex justify-end space-x-3"> | |
<button | |
id="cancelNewBtn" | |
type="button" | |
class="px-4 py-2 bg-white border border-slate-200 rounded-lg text-slate-700 hover:bg-slate-50 transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" | |
> | |
Cancel | |
</button> | |
<button | |
id="confirmNewBtn" | |
type="button" | |
class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" | |
> | |
Create | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Toast Notification --> | |
<div id="toast" class="toast"> | |
<div class="flex items-center"> | |
<i id="toastIcon" class="fas fa-check-circle mr-2"></i> | |
<span id="toastMessage">Operation completed successfully</span> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Enhanced application state | |
const state = { | |
sidebarSearch: '', | |
currentLocation: { id: 1, name: 'Home', icon: 'fas fa-home', path: [] }, | |
indexedCount: 2847, | |
lastIndexed: '5 minutes ago', | |
searchQuery: '', | |
showAdvancedSearch: false, | |
isIndexing: false, | |
activeSort: 'name', | |
sortDirection: 'asc', | |
selectedFiles: [], | |
showDeleteConfirm: false, | |
contextMenuFile: null, | |
filters: { | |
fileType: 'all', | |
author: '', | |
sizeMin: '', | |
sizeMax: '', | |
dateRange: 'all', | |
dateFrom: '', | |
dateTo: '' | |
}, | |
performanceRating: 'fast', | |
newItemType: 'file', | |
locations: [ | |
{ id: 1, name: 'Home', icon: 'fas fa-home', path: [] }, | |
{ id: 2, name: 'Documents', icon: 'fas fa-folder', path: ['Home'] }, | |
{ id: 3, name: 'Downloads', icon: 'fas fa-download', path: ['Home'] }, | |
{ id: 4, name: 'Pictures', icon: 'fas fa-image', path: ['Home'] }, | |
{ id: 5, name: 'Music', icon: 'fas fa-music', path: ['Home'] }, | |
{ id: 6, name: 'Videos', icon: 'fas fa-video', path: ['Home'] }, | |
{ id: 7, name: 'Desktop', icon: 'fas fa-desktop', path: ['Home'] } | |
], | |
files: [ | |
{ id: 1, name: 'Project Proposal.pdf', path: ['Home', 'Documents', 'Projects'], author: 'John Doe', type: 'PDF Document', size: 2456, modified: '2023-06-15T14:30:00', isFavorite: false }, | |
{ id: 2, name: 'Budget 2023.xlsx', path: ['Home', 'Documents', 'Finance'], author: 'Jane Smith', type: 'Excel Spreadsheet', size: 4532, modified: '2023-05-22T09:45:00', isFavorite: true }, | |
{ id: 3, name: 'Team Photo.jpg', path: ['Home', 'Pictures', 'Work'], author: 'Mike Johnson', type: 'JPEG Image', size: 3245, modified: '2023-06-10T11:20:00', isFavorite: false }, | |
{ id: 4, name: 'Meeting Notes.docx', path: ['Home', 'Documents', 'Meetings'], author: 'Sarah Wilson', type: 'Word Document', size: 876, modified: '2023-06-14T16:10:00', isFavorite: false }, | |
{ id: 5, name: 'Presentation.pptx', path: ['Home', 'Documents', 'Presentations'], author: 'David Brown', type: 'PowerPoint', size: 6543, modified: '2023-06-12T13:30:00', isFavorite: false }, | |
{ id: 6, name: 'Project Backup.zip', path: ['Home', 'Downloads'], author: 'Emma Davis', type: 'ZIP Archive', size: 24567, modified: '2023-06-09T18:05:00', isFavorite: false }, | |
{ id: 7, name: 'Interview.mp3', path: ['Home', 'Music', 'Recordings'], author: 'James Miller', type: 'MP3 Audio', size: 12345, modified: '2023-05-30T10:15:00', isFavorite: false }, | |
{ id: 8, name: 'Tutorial.mp4', path: ['Home', 'Videos', 'Learning'], author: 'Olivia Wilson', type: 'MP4 Video', size: 45678, modified: '2023-05-28T14:20:00', isFavorite: false }, | |
{ id: 9, name: 'Configuration.ini', path: ['Home', 'System'], author: 'System', type: 'Configuration File', size: 23, modified: '2023-06-13T12:40:00', isFavorite: false }, | |
{ id: 10, name: 'Code Repository', path: ['Home', 'Documents', 'Projects'], author: 'John Doe', type: 'Folder', size: 10245, modified: '2023-06-05T08:30:00', isFavorite: true } | |
], | |
locationHistory: [], | |
historyIndex: -1 | |
}; | |
// Helper functions | |
function formatFileSize(bytes) { | |
if (bytes === 0) return '0 Bytes'; | |
if (typeof bytes === 'string') bytes = parseInt(bytes); | |
const k = 1024; | |
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; | |
const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
} | |
function formatDate(dateString) { | |
const date = new Date(dateString); | |
return date.toLocaleDateString('en-US', {month: 'short', day: 'numeric'}); | |
} | |
function formatTime(dateString) { | |
const date = new Date(dateString); | |
return date.toLocaleTimeString('en-US', {hour: '2-digit', minute: '2-digit'}); | |
} | |
function getFileIcon(file) { | |
if (file.type === 'Folder') return 'fas fa-folder folder-icon'; | |
const extension = file.name.split('.').pop().toLowerCase(); | |
const iconMap = { | |
'pdf': 'fas fa-file-pdf file-icon', | |
'docx': 'fas fa-file-word file-icon', | |
'xlsx': 'fas fa-file-excel file-icon', | |
'pptx': 'fas fa-file-powerpoint file-icon', | |
'jpg': 'fas fa-file-image file-icon', | |
'jpeg': 'fas fa-file-image file-icon', | |
'png': 'fas fa-file-image file-icon', | |
'gif': 'fas fa-file-image file-icon', | |
'mp3': 'fas fa-file-audio file-icon', | |
'wav': 'fas fa-file-audio file-icon', | |
'mp4': 'fas fa-file-video file-icon', | |
'mov': 'fas fa-file-video file-icon', | |
'zip': 'fas fa-file-archive file-icon', | |
'rar': 'fas fa-file-archive file-icon', | |
'ini': 'fas fa-file-code file-icon' | |
}; | |
return iconMap[extension] || 'fas fa-file file-icon'; | |
} | |
function showToast(message, type = 'success') { | |
const toast = document.getElementById('toast'); | |
const toastIcon = document.getElementById('toastIcon'); | |
const toastMessage = document.getElementById('toastMessage'); | |
// Set icon and color based on type | |
if (type === 'success') { | |
toastIcon.className = 'fas fa-check-circle mr-2'; | |
toast.style.backgroundColor = '#4f46e5'; | |
} else if (type === 'error') { | |
toastIcon.className = 'fas fa-exclamation-circle mr-2'; | |
toast.style.backgroundColor = '#ef4444'; | |
} else if (type === 'warning') { | |
toastIcon.className = 'fas fa-exclamation-triangle mr-2'; | |
toast.style.backgroundColor = '#f59e0b'; | |
} else if (type === 'info') { | |
toastIcon.className = 'fas fa-info-circle mr-2'; | |
toast.style.backgroundColor = '#3b82f6'; | |
} | |
toastMessage.textContent = message; | |
toast.classList.add('show'); | |
// Hide after 3 seconds | |
setTimeout(() => { | |
toast.classList.remove('show'); | |
}, 3000); | |
} | |
function filterFiles() { | |
let files = [...state.files]; | |
const filters = state.filters; | |
const search = state.searchQuery.toLowerCase(); | |
// Filter by current location | |
files = files.filter(file => | |
JSON.stringify(file.path) === JSON.stringify(state.currentLocation.path) | |
); | |
if (search) { | |
files = files.filter(file => | |
file.name.toLowerCase().includes(search) || | |
file.type.toLowerCase().includes(search) || | |
(file.author && file.author.toLowerCase().includes(search)) | |
); | |
} | |
if (filters.fileType !== 'all') { | |
const typeMap = { | |
'documents': ['PDF Document', 'Word Document', 'Excel Spreadsheet', 'PowerPoint'], | |
'images': ['JPEG Image', 'PNG Image', 'GIF Image'], | |
'audio': ['MP3 Audio', 'WAV Audio'], | |
'video': ['MP4 Video', 'MOV Video'], | |
'archives': ['ZIP Archive', 'RAR Archive'], | |
'folders': ['Folder'] | |
}; | |
const types = typeMap[filters.fileType] || []; | |
files = files.filter(file => | |
types.includes(file.type) || | |
(filters.fileType === 'documents' && file.type.includes('Document')) | |
); | |
} | |
if (filters.author) { | |
const authorSearch = filters.author.toLowerCase(); | |
files = files.filter(file => | |
file.author && file.author.toLowerCase().includes(authorSearch) | |
); | |
} | |
if (filters.sizeMin || filters.sizeMax) { | |
const min = filters.sizeMin ? parseInt(filters.sizeMin) : 0; | |
const max = filters.sizeMax ? parseInt(filters.sizeMax) : Infinity; | |
files = files.filter(file => file.size >= min && file.size <= max); | |
} | |
if (filters.dateRange && filters.dateRange !== 'all') { | |
const now = new Date(); | |
let fromDate = new Date(); | |
switch (filters.dateRange) { | |
case 'today': | |
fromDate.setHours(0, 0, 0, 0); | |
break; | |
case 'week': | |
fromDate.setDate(fromDate.getDate() - 7); | |
break; | |
case 'month': | |
fromDate.setMonth(fromDate.getMonth() - 1); | |
break; | |
case 'year': | |
fromDate.setFullYear(fromDate.getFullYear() - 1); | |
break; | |
case 'custom': | |
if (filters.dateFrom) { | |
fromDate = new Date(filters.dateFrom); | |
} | |
break; | |
} | |
files = files.filter(file => { | |
const fileDate = new Date(file.modified); | |
return fileDate >= fromDate; | |
}); | |
if (filters.dateRange === 'custom' && filters.dateTo) { | |
const toDate = new Date(filters.dateTo); | |
toDate.setHours(23, 59, 59, 999); | |
files = files.filter(file => { | |
const fileDate = new Date(file.modified); | |
return fileDate <= toDate; | |
}); | |
} | |
} | |
return sortFiles(files); | |
} | |
function sortFiles(files) { | |
const activeSort = state.activeSort; | |
const sortDirection = state.sortDirection; | |
return files.sort((a, b) => { | |
let aVal, bVal; | |
switch (activeSort) { | |
case 'name': | |
aVal = a.name.toLowerCase(); | |
bVal = b.name.toLowerCase(); | |
break; | |
case 'author': | |
aVal = a.author ? a.author.toLowerCase() : ''; | |
bVal = b.author ? b.author.toLowerCase() : ''; | |
break; | |
case 'type': | |
aVal = a.type.toLowerCase(); | |
bVal = b.type.toLowerCase(); | |
break; | |
case 'size': | |
aVal = a.size; | |
bVal = b.size; | |
break; | |
case 'modified': | |
aVal = new Date(a.modified); | |
bVal = new Date(b.modified); | |
break; | |
default: | |
aVal = a.name.toLowerCase(); | |
bVal = b.name.toLowerCase(); | |
} | |
if (aVal < bVal) return sortDirection === 'asc' ? -1 : 1; | |
if (aVal > bVal) return sortDirection === 'asc' ? 1 : -1; | |
return 0; | |
}); | |
} | |
function filterLocations() { | |
const search = state.sidebarSearch.toLowerCase(); | |
return state.locations.filter(loc => | |
loc.name.toLowerCase().includes(search) | |
); | |
} | |
function updateBreadcrumbs() { | |
const breadcrumbs = document.getElementById('breadcrumbs'); | |
breadcrumbs.innerHTML = ''; | |
// Add home breadcrumb | |
const homeCrumb = document.createElement('div'); | |
homeCrumb.className = 'breadcrumb-item'; | |
homeCrumb.innerHTML = ` | |
<i class="fas fa-home"></i> | |
<span>Home</span> | |
`; | |
homeCrumb.addEventListener('click', () => { | |
const homeLocation = state.locations.find(loc => loc.id === 1); | |
selectLocation(homeLocation); | |
}); | |
breadcrumbs.appendChild(homeCrumb); | |
// Add path breadcrumbs | |
state.currentLocation.path.forEach((pathItem, index) => { | |
const separator = document.createElement('div'); | |
separator.className = 'breadcrumb-separator'; | |
separator.innerHTML = '<i class="fas fa-chevron-right"></i>'; | |
breadcrumbs.appendChild(separator); | |
const crumb = document.createElement('div'); | |
crumb.className = 'breadcrumb-item'; | |
crumb.textContent = pathItem; | |
crumb.addEventListener('click', () => { | |
// Find the location that matches this path | |
const targetPath = state.currentLocation.path.slice(0, index + 1); | |
const targetLocation = state.locations.find(loc => | |
loc.name === pathItem && JSON.stringify(loc.path) === JSON.stringify(targetPath.slice(0, -1)) | |
); | |
if (targetLocation) { | |
selectLocation(targetLocation); | |
} | |
}); | |
breadcrumbs.appendChild(crumb); | |
}); | |
// Add current location if it's not home | |
if (state.currentLocation.id !== 1) { | |
const separator = document.createElement('div'); | |
separator.className = 'breadcrumb-separator'; | |
separator.innerHTML = '<i class="fas fa-chevron-right"></i>'; | |
breadcrumbs.appendChild(separator); | |
const currentCrumb = document.createElement('div'); | |
currentCrumb.className = 'breadcrumb-item font-medium text-indigo-600'; | |
currentCrumb.textContent = state.currentLocation.name; | |
breadcrumbs.appendChild(currentCrumb); | |
} | |
} | |
// UI update functions | |
function updateSidebar() { | |
const sidebarLocations = document.getElementById('sidebarLocations'); | |
const filteredLocations = filterLocations(); | |
sidebarLocations.innerHTML = ''; | |
filteredLocations.forEach(location => { | |
const locationEl = document.createElement('div'); | |
locationEl.className = `sidebar-item px-3 py-2.5 rounded-lg cursor-pointer flex items-center transition-all group ${ | |
state.currentLocation.id === location.id ? 'bg-indigo-50' : '' | |
}`; | |
locationEl.innerHTML = ` | |
<div class="w-8 h-8 rounded-md flex items-center justify-center mr-2 ${ | |
state.currentLocation.id === location.id ? 'bg-indigo-100' : 'bg-slate-100 group-hover:bg-slate-200' | |
}"> | |
<i class="${location.icon} text-slate-600 group-hover:text-slate-800 ${ | |
state.currentLocation.id === location.id ? 'text-indigo-500' : '' | |
}"></i> | |
</div> | |
<span class="text-sm font-medium truncate ${ | |
state.currentLocation.id === location.id ? 'text-indigo-600' : 'text-slate-700 group-hover:text-slate-900' | |
}"> | |
${location.name} | |
</span> | |
`; | |
locationEl.addEventListener('click', () => selectLocation(location)); | |
sidebarLocations.appendChild(locationEl); | |
}); | |
} | |
function updateFileList() { | |
const fileList = document.getElementById('fileList'); | |
const filteredFiles = filterFiles(); | |
if (filteredFiles.length === 0) { | |
fileList.innerHTML = ` | |
<div class="flex flex-col items-center justify-center h-64 text-slate-400"> | |
<i class="fas fa-folder-open text-5xl mb-4 opacity-30"></i> | |
<p class="text-lg font-medium text-slate-500">No files found</p> | |
<p class="text-sm mt-1 text-slate-400">Try adjusting your search or filters</p> | |
</div> | |
`; | |
} else { | |
fileList.innerHTML = ''; | |
filteredFiles.forEach(file => { | |
const fileEl = document.createElement('div'); | |
fileEl.className = `file-row px-5 py-3 border-b border-slate-100 grid grid-cols-12 gap-4 text-sm items-center transition-all ${ | |
state.selectedFiles.includes(file) ? 'highlight' : '' | |
}`; | |
fileEl.innerHTML = ` | |
<div class="col-span-5 flex items-center truncate"> | |
<input | |
type="checkbox" | |
class="mr-3 h-4 w-4 text-indigo-600 rounded border-slate-300 focus:ring-indigo-500" | |
${state.selectedFiles.includes(file) ? 'checked' : ''} | |
data-file-id="${file.id}" | |
> | |
<div class="w-8 h-8 rounded-md flex items-center justify-center mr-2 ${ | |
state.selectedFiles.includes(file) ? 'bg-indigo-50' : 'bg-slate-100' | |
}"> | |
<i class="${getFileIcon(file)} text-slate-600"></i> | |
</div> | |
<div class="truncate"> | |
<div class="font-medium text-slate-800 truncate">${file.name}</div> | |
<div class="text-xs text-slate-500 truncate">${formatTime(file.modified)}</div> | |
</div> | |
${file.isFavorite ? '<i class="fas fa-star text-yellow-400 ml-2 text-xs"></i>' : ''} | |
</div> | |
<div class="col-span-2 text-slate-600 truncate"> | |
<span class="inline-flex items-center"> | |
<i class="fas fa-user-circle mr-2 text-slate-400"></i> | |
${file.author || 'Unknown'} | |
</span> | |
</div> | |
<div class="col-span-2 text-slate-600 truncate text-sm">${file.type}</div> | |
<div class="col-span-2 text-slate-600 text-right text-sm">${formatFileSize(file.size)}</div> | |
<div class="col-span-1 text-slate-600 text-right text-sm">${formatDate(file.modified)}</div> | |
`; | |
// Add event listeners | |
const checkbox = fileEl.querySelector('input[type="checkbox"]'); | |
checkbox.addEventListener('change', (e) => toggleFileSelection(file, e)); | |
fileEl.addEventListener('dblclick', () => openFile(file)); | |
// Add context menu | |
fileEl.addEventListener('contextmenu', (e) => { | |
e.preventDefault(); | |
showContextMenu(file, e.clientX, e.clientY); | |
}); | |
fileList.appendChild(fileEl); | |
}); | |
} | |
// Update status bar | |
document.getElementById('statusCount').textContent = | |
state.selectedFiles.length > 0 ? | |
`${state.selectedFiles.length} selected of ${filteredFiles.length} items` : | |
`${filteredFiles.length} items`; | |
} | |
function showContextMenu(file, x, y) { | |
const contextMenu = document.getElementById('contextMenu'); | |
state.contextMenuFile = file; | |
// Update context menu items based on file type | |
const favoriteItem = document.getElementById('contextFavorite'); | |
if (file.isFavorite) { | |
favoriteItem.innerHTML = '<i class="fas fa-star"></i> Remove from Favorites'; | |
} else { | |
favoriteItem.innerHTML = '<i class="fas fa-star"></i> Add to Favorites'; | |
} | |
// Position the menu | |
contextMenu.style.left = `${x}px`; | |
contextMenu.style.top = `${y}px`; | |
contextMenu.style.display = 'block'; | |
// Close menu when clicking elsewhere | |
const closeMenu = () => { | |
contextMenu.style.display = 'none'; | |
document.removeEventListener('click', closeMenu); | |
}; | |
setTimeout(() => { | |
document.addEventListener('click', closeMenu); | |
}, 100); | |
} | |
function updateSortIcons() { | |
// Reset all icons | |
document.getElementById('nameSortIcon').className = 'fas ml-1 fa-sort'; | |
document.getElementById('authorSortIcon').className = 'fas ml-1 fa-sort'; | |
document.getElementById('typeSortIcon').className = 'fas ml-1 fa-sort'; | |
document.getElementById('sizeSortIcon').className = 'fas ml-1 fa-sort'; | |
document.getElementById('modifiedSortIcon').className = 'fas ml-1 fa-sort'; | |
// Set active sort icon | |
const activeIcon = document.getElementById(`${state.activeSort}SortIcon`); | |
if (activeIcon) { | |
activeIcon.className = `fas ml-1 fa-sort-${state.sortDirection === 'asc' ? 'up' : 'down'}`; | |
} | |
} | |
function updateDeleteButton() { | |
const deleteBtnContainer = document.getElementById('deleteBtnContainer'); | |
const selectedCount = document.getElementById('selectedCount'); | |
if (state.selectedFiles.length > 0) { | |
deleteBtnContainer.style.display = 'block'; | |
selectedCount.textContent = state.selectedFiles.length; | |
} else { | |
deleteBtnContainer.style.display = 'none'; | |
} | |
} | |
function updateSelectAllCheckbox() { | |
const selectAllCheckbox = document.getElementById('selectAllCheckbox'); | |
const filteredFiles = filterFiles(); | |
selectAllCheckbox.checked = | |
state.selectedFiles.length === filteredFiles.length && | |
filteredFiles.length > 0; | |
} | |
// Core functions | |
function selectLocation(location) { | |
// Add to history | |
if (state.historyIndex === -1 || | |
JSON.stringify(state.locationHistory[state.historyIndex]) !== JSON.stringify(location)) { | |
// If we're not at the end of history, truncate the future | |
if (state.historyIndex < state.locationHistory.length - 1) { | |
state.locationHistory = state.locationHistory.slice(0, state.historyIndex + 1); | |
} | |
state.locationHistory.push({...location}); | |
state.historyIndex = state.locationHistory.length - 1; | |
} | |
state.currentLocation = location; | |
state.searchQuery = ''; | |
state.selectedFiles = []; | |
updateFileList(); | |
updateDeleteButton(); | |
updateSelectAllCheckbox(); | |
updateBreadcrumbs(); | |
// Update back button state | |
updateBackForwardButtons(); | |
} | |
function navigateBack() { | |
if (state.historyIndex > 0) { | |
state.historyIndex--; | |
state.currentLocation = {...state.locationHistory[state.historyIndex]}; | |
updateFileList(); | |
updateDeleteButton(); | |
updateSelectAllCheckbox(); | |
updateBreadcrumbs(); | |
updateBackForwardButtons(); | |
} | |
} | |
function navigateForward() { | |
if (state.historyIndex < state.locationHistory.length - 1) { | |
state.historyIndex++; | |
state.currentLocation = {...state.locationHistory[state.historyIndex]}; | |
updateFileList(); | |
updateDeleteButton(); | |
updateSelectAllCheckbox(); | |
updateBreadcrumbs(); | |
updateBackForwardButtons(); | |
} | |
} | |
function updateBackForwardButtons() { | |
const backBtn = document.getElementById('backBtn'); | |
const forwardBtn = document.getElementById('forwardBtn'); | |
if (state.historyIndex > 0) { | |
backBtn.disabled = false; | |
backBtn.classList.remove('opacity-50', 'cursor-not-allowed'); | |
} else { | |
backBtn.disabled = true; | |
backBtn.classList.add('opacity-50', 'cursor-not-allowed'); | |
} | |
if (state.historyIndex < state.locationHistory.length - 1) { | |
forwardBtn.disabled = false; | |
forwardBtn.classList.remove('opacity-50', 'cursor-not-allowed'); | |
} else { | |
forwardBtn.disabled = true; | |
forwardBtn.classList.add('opacity-50', 'cursor-not-allowed'); | |
} | |
} | |
function toggleIndexing() { | |
state.isIndexing = !state.isIndexing; | |
const indexBtn = document.getElementById('indexBtn'); | |
const indexBtnText = document.getElementById('indexBtnText'); | |
const indexStatus = document.getElementById('indexStatus'); | |
if (state.isIndexing) { | |
indexBtn.classList.add('glow'); | |
indexBtnText.textContent = 'Indexing...'; | |
indexStatus.innerHTML = '<span class="text-indigo-600 font-medium flex items-center"><span class="w-2 h-2 rounded-full bg-indigo-500 mr-1.5 animate-pulse"></span>Running</span>'; | |
let count = state.indexedCount; | |
const interval = setInterval(() => { | |
count += Math.floor(Math.random() * 10); | |
document.getElementById('indexedCount').textContent = count; | |
document.getElementById('lastIndexed').textContent = 'Just now'; | |
if (!state.isIndexing) { | |
clearInterval(interval); | |
} | |
}, 3000); | |
setTimeout(() => { | |
if (Math.random() > 0.7) { | |
state.isIndexing = false; | |
indexBtn.classList.remove('glow'); | |
indexBtnText.textContent = 'Index Now'; | |
indexStatus.textContent = 'Idle'; | |
showToast('Indexing completed successfully', 'success'); | |
} | |
}, 15000); | |
} else { | |
indexBtn.classList.remove('glow'); | |
indexBtnText.textContent = 'Index Now'; | |
indexStatus.textContent = 'Idle'; | |
} | |
} | |
function refreshFiles() { | |
state.performanceRating = ['fast', 'normal', 'slow'][Math.floor(Math.random() * 3)]; | |
const performanceIcon = document.getElementById('performanceIcon'); | |
const performanceText = document.getElementById('performanceText'); | |
performanceText.textContent = `${state.performanceRating} performance`; | |
if (state.performanceRating === 'fast') { | |
performanceIcon.className = 'fas fa-circle text-xs mr-1.5 text-emerald-500'; | |
} else if (state.performanceRating === 'normal') { | |
performanceIcon.className = 'fas fa-circle text-xs mr-1.5 text-amber-500'; | |
} else { | |
performanceIcon.className = 'fas fa-circle text-xs mr-1.5 text-rose-500'; | |
} | |
if (Math.random() > 0.7) { | |
const newFiles = [...state.files]; | |
if (newFiles.length > 5 && Math.random() > 0.5) { | |
newFiles.splice(Math.floor(Math.random() * newFiles.length), 1); | |
} else { | |
const authors = ['John Doe', 'Jane Smith', 'Mike Johnson', 'Sarah Wilson', 'Emma Davis']; | |
const newFile = { | |
id: Math.max(...newFiles.map(f => f.id)) + 1, | |
name: ['New Document.docx', 'Report.pdf', 'Image.jpg', 'Archive.zip', 'Data.xlsx'][Math.floor(Math.random() * 5)], | |
path: [...state.currentLocation.path], | |
author: authors[Math.floor(Math.random() * authors.length)], | |
type: ['PDF Document', 'Word Document', 'Excel Spreadsheet', 'JPEG Image', 'ZIP Archive'][Math.floor(Math.random() * 5)], | |
size: Math.floor(Math.random() * 10000), | |
modified: new Date().toISOString(), | |
isFavorite: Math.random() > 0.8 | |
}; | |
newFiles.push(newFile); | |
} | |
state.files = newFiles; | |
updateFileList(); | |
showToast('Files refreshed', 'success'); | |
} else { | |
showToast('Files are up to date', 'info'); | |
} | |
} | |
function sortBy(column) { | |
if (state.activeSort === column) { | |
state.sortDirection = state.sortDirection === 'asc' ? 'desc' : 'asc'; | |
} else { | |
state.activeSort = column; | |
state.sortDirection = 'asc'; | |
} | |
updateSortIcons(); | |
updateFileList(); | |
} | |
function toggleSelectAll(e) { | |
const allFiles = filterFiles(); | |
if (e.target.checked) { | |
state.selectedFiles = [...allFiles]; | |
} else { | |
state.selectedFiles = []; | |
} | |
updateFileList(); | |
updateDeleteButton(); | |
} | |
function toggleFileSelection(file, e) { | |
if (e.target.checked) { | |
state.selectedFiles.push(file); | |
} else { | |
const index = state.selectedFiles.findIndex(f => f.id === file.id); | |
if (index !== -1) { | |
state.selectedFiles.splice(index, 1); | |
} | |
} | |
updateDeleteButton(); | |
updateSelectAllCheckbox(); | |
} | |
function openFile(file) { | |
if (file.type === 'Folder') { | |
// Navigate to folder | |
const newLocation = { | |
id: Date.now(), | |
name | |
</html> |