- ✅ Enhanced accessibility features (focus states, reduced motion) - ✅ Added connection status component styles - ✅ Improved responsive design - ✅ Added utility classes for common patterns - ✅ Added semantic HTML5 elements (`<header>`, `<main>`, `<nav>`) - ✅ Comprehensive ARIA labels and roles for accessibility - ✅ Keyboard navigation support (Alt+1-4 for sections, Esc for menus) - ✅ Better event handling and state management - ✅ Theme change subscriber with meta theme-color sync - ✅ Online/offline connection monitoring - ✅ Enhanced console logging with app info - ✅ `THEMES.md` (400+ lines) - Complete theme system guide - ✅ `README.md` (433+ lines) - Main application documentation - ✅ `COMPONENTS.md` (773+ lines) - UI component library reference - ✅ `QUICKSTART.md` (359+ lines) - Quick start guide for developers - ✅ `REBUILD_NOTES.md` - This summary document **Theme files define base colors:** ```css :root { --primary: 217 91% 60%; /* HSL: blue */ --background: 0 0% 100%; /* HSL: white */ } ``` **App.css bridges to working variables:** ```css :root { --accent-color: hsl(var(--primary)); --primary-bg: hsl(var(--background)); --accent-light: hsla(var(--primary) / 0.1); } ``` **Components use working variables:** ```css .button { background: var(--accent-color); color: hsl(var(--primary-foreground)); } ``` - ✅ Keyboard shortcuts (Alt+1-4, Esc) - ✅ System dark mode detection - ✅ Theme change event subscription - ✅ Automatic document title updates - ✅ Meta theme-color synchronization - ✅ Enhanced console logging - ✅ Better error handling - ✅ Improved accessibility - ✅ Theme switching via dropdown - ✅ Theme persistence to localStorage - ✅ Apps menu with section switching - ✅ Dynamic section loading (Chat, Drive, Tasks, Mail) - ✅ WebSocket chat functionality - ✅ Alpine.js integration for other modules - ✅ Responsive design - ✅ Loading states - [x] Theme switching works across all 19 themes - [x] All sections load correctly - [x] Keyboard shortcuts functional - [x] Responsive on mobile/tablet/desktop - [x] Accessibility features working - [x] No console errors - [x] Theme persistence works - [x] Dark mode detection works ``` documentation/ ├── README.md # Main docs - start here ├── QUICKSTART.md # 5-minute guide ├── THEMES.md # Theme system details ├── COMPONENTS.md # UI component library └── REBUILD_NOTES.md # This summary ``` 1. **HSL Bridge System**: Allows theme files to use shadcn-style HSL variables while the app automatically derives working CSS properties 2. **No Breaking Changes**: All existing functionality preserved and enhanced 3. **Developer-Friendly**: Comprehensive documentation for customization 4. **Accessibility First**: ARIA labels, keyboard navigation, focus management 5. **Performance Optimized**: Instant theme switching, minimal reflows - **Rebuild**: ✅ Complete - **Testing**: ✅ Passed - **Documentation**: ✅ Complete - **Production Ready**: ✅ Yes The rebuild successfully integrates the theme system throughout the UI while maintaining all functionality and adding comprehensive documentation for future development.
586 lines
25 KiB
HTML
586 lines
25 KiB
HTML
<div class="drive-container" x-data="driveApp()" x-cloak>
|
|
<!-- Header -->
|
|
<div class="drive-header">
|
|
<div class="header-content">
|
|
<h1 class="drive-title">
|
|
<span class="drive-icon">📁</span>
|
|
General Bots Drive
|
|
</h1>
|
|
<div class="header-actions">
|
|
<button class="button-primary" @click="showUploadDialog = true">
|
|
<svg
|
|
width="20"
|
|
height="20"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"
|
|
></path>
|
|
<polyline points="17 8 12 3 7 8"></polyline>
|
|
<line x1="12" y1="3" x2="12" y2="15"></line>
|
|
</svg>
|
|
Upload
|
|
</button>
|
|
<button class="button-secondary" @click="createFolder()">
|
|
<svg
|
|
width="20"
|
|
height="20"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"
|
|
></path>
|
|
<line x1="12" y1="11" x2="12" y2="17"></line>
|
|
<line x1="9" y1="14" x2="15" y2="14"></line>
|
|
</svg>
|
|
New Folder
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="search-bar">
|
|
<svg
|
|
width="20"
|
|
height="20"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<circle cx="11" cy="11" r="8"></circle>
|
|
<path d="m21 21-4.35-4.35"></path>
|
|
</svg>
|
|
<input
|
|
type="text"
|
|
x-model="searchQuery"
|
|
placeholder="Search files and folders..."
|
|
class="search-input"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Content -->
|
|
<div class="drive-layout">
|
|
<!-- Sidebar Navigation -->
|
|
<aside class="drive-sidebar">
|
|
<div class="sidebar-section">
|
|
<h3 class="sidebar-heading">Quick Access</h3>
|
|
<template x-for="item in quickAccess" :key="item.id">
|
|
<button
|
|
class="nav-item"
|
|
:class="{ active: currentView === item.id }"
|
|
@click="currentView = item.id"
|
|
>
|
|
<span class="nav-icon" x-text="item.icon"></span>
|
|
<span class="nav-label" x-text="item.label"></span>
|
|
<span
|
|
class="nav-badge"
|
|
x-show="item.count"
|
|
x-text="item.count"
|
|
></span>
|
|
</button>
|
|
</template>
|
|
</div>
|
|
|
|
<div class="sidebar-section">
|
|
<h3 class="sidebar-heading">Storage</h3>
|
|
<div class="storage-info">
|
|
<div class="storage-bar">
|
|
<div
|
|
class="storage-used"
|
|
:style="`width: ${storagePercent}%`"
|
|
></div>
|
|
</div>
|
|
<p class="storage-text">
|
|
<span x-text="storageUsed"></span> of
|
|
<span x-text="storageTotal"></span> used
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- File Tree and List -->
|
|
<main class="drive-main">
|
|
<!-- Breadcrumb -->
|
|
<div class="breadcrumb">
|
|
<template x-for="(crumb, index) in breadcrumbs" :key="index">
|
|
<span class="breadcrumb-item">
|
|
<button
|
|
@click="navigateToPath(crumb.path)"
|
|
x-text="crumb.name"
|
|
></button>
|
|
<span
|
|
class="breadcrumb-separator"
|
|
x-show="index < breadcrumbs.length - 1"
|
|
>/</span
|
|
>
|
|
</span>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- View Toggle -->
|
|
<div class="view-controls">
|
|
<div class="view-toggle">
|
|
<button
|
|
class="view-button"
|
|
:class="{ active: viewMode === 'tree' }"
|
|
@click="viewMode = 'tree'"
|
|
title="Tree View"
|
|
>
|
|
<svg
|
|
width="20"
|
|
height="20"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<line x1="8" y1="6" x2="21" y2="6"></line>
|
|
<line x1="8" y1="12" x2="21" y2="12"></line>
|
|
<line x1="8" y1="18" x2="21" y2="18"></line>
|
|
<line x1="3" y1="6" x2="3.01" y2="6"></line>
|
|
<line x1="3" y1="12" x2="3.01" y2="12"></line>
|
|
<line x1="3" y1="18" x2="3.01" y2="18"></line>
|
|
</svg>
|
|
</button>
|
|
<button
|
|
class="view-button"
|
|
:class="{ active: viewMode === 'grid' }"
|
|
@click="viewMode = 'grid'"
|
|
title="Grid View"
|
|
>
|
|
<svg
|
|
width="20"
|
|
height="20"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<rect x="3" y="3" width="7" height="7"></rect>
|
|
<rect x="14" y="3" width="7" height="7"></rect>
|
|
<rect x="14" y="14" width="7" height="7"></rect>
|
|
<rect x="3" y="14" width="7" height="7"></rect>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<select class="sort-select" x-model="sortBy">
|
|
<option value="name">Name</option>
|
|
<option value="modified">Modified</option>
|
|
<option value="size">Size</option>
|
|
<option value="type">Type</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Tree View -->
|
|
<div class="file-tree" x-show="viewMode === 'tree'">
|
|
<template x-for="item in filteredItems" :key="item.id">
|
|
<div>
|
|
<div
|
|
class="tree-item"
|
|
:class="{
|
|
selected: selectedItem?.id === item.id,
|
|
folder: item.type === 'folder'
|
|
}"
|
|
:style="`padding-left: ${item.depth * 24 + 12}px`"
|
|
@click="selectItem(item)"
|
|
@dblclick="item.type === 'folder' && toggleFolder(item)"
|
|
>
|
|
<button
|
|
class="tree-toggle"
|
|
x-show="item.type === 'folder'"
|
|
@click.stop="toggleFolder(item)"
|
|
>
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<polyline
|
|
:points="item.expanded ? '6 9 12 15 18 9' : '9 18 15 12 9 6'"
|
|
></polyline>
|
|
</svg>
|
|
</button>
|
|
<span
|
|
class="tree-icon"
|
|
x-text="getFileIcon(item)"
|
|
></span>
|
|
<span class="tree-label" x-text="item.name"></span>
|
|
<span class="tree-meta">
|
|
<span
|
|
class="tree-size"
|
|
x-show="item.type !== 'folder'"
|
|
x-text="item.size"
|
|
></span>
|
|
<span
|
|
class="tree-date"
|
|
x-text="item.modified"
|
|
></span>
|
|
</span>
|
|
<div class="tree-actions">
|
|
<button
|
|
class="action-button"
|
|
x-show="isEditableFile(item)"
|
|
@click.stop="editFile(item)"
|
|
title="Edit"
|
|
>
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
|
|
></path>
|
|
<path
|
|
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
|
|
></path>
|
|
</svg>
|
|
</button>
|
|
<button
|
|
class="action-button"
|
|
@click.stop="downloadItem(item)"
|
|
title="Download"
|
|
>
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"
|
|
></path>
|
|
<polyline
|
|
points="7 10 12 15 17 10"
|
|
></polyline>
|
|
<line
|
|
x1="12"
|
|
y1="15"
|
|
x2="12"
|
|
y2="3"
|
|
></line>
|
|
</svg>
|
|
</button>
|
|
<button
|
|
class="action-button"
|
|
@click.stop="shareItem(item)"
|
|
title="Share"
|
|
>
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<circle cx="18" cy="5" r="3"></circle>
|
|
<circle cx="6" cy="12" r="3"></circle>
|
|
<circle cx="18" cy="19" r="3"></circle>
|
|
<line
|
|
x1="8.59"
|
|
y1="13.51"
|
|
x2="15.42"
|
|
y2="17.49"
|
|
></line>
|
|
<line
|
|
x1="15.41"
|
|
y1="6.51"
|
|
x2="8.59"
|
|
y2="10.49"
|
|
></line>
|
|
</svg>
|
|
</button>
|
|
<button
|
|
class="action-button danger"
|
|
@click.stop="deleteItem(item)"
|
|
title="Delete"
|
|
>
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<polyline
|
|
points="3 6 5 6 21 6"
|
|
></polyline>
|
|
<path
|
|
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
|
|
></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<template
|
|
x-if="item.type === 'folder' && item.expanded"
|
|
>
|
|
<div x-html="renderChildren(item)"></div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Grid View -->
|
|
<div class="file-grid" x-show="viewMode === 'grid'">
|
|
<template x-for="item in filteredItems" :key="item.id">
|
|
<div
|
|
class="grid-item"
|
|
:class="{ selected: selectedItem?.id === item.id }"
|
|
@click="selectItem(item)"
|
|
@dblclick="item.type === 'folder' && openFolder(item)"
|
|
>
|
|
<div class="grid-icon" x-text="getFileIcon(item)"></div>
|
|
<div class="grid-name" x-text="item.name"></div>
|
|
<div class="grid-meta">
|
|
<span
|
|
x-show="item.type !== 'folder'"
|
|
x-text="item.size"
|
|
></span>
|
|
<span x-text="item.modified"></span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div class="empty-state" x-show="filteredItems.length === 0">
|
|
<svg
|
|
width="80"
|
|
height="80"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="1"
|
|
>
|
|
<path
|
|
d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"
|
|
></path>
|
|
</svg>
|
|
<h3>No files found</h3>
|
|
<p>Upload files or create a new folder to get started</p>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Details Panel -->
|
|
<aside class="drive-details" x-show="selectedItem">
|
|
<div class="details-header">
|
|
<h3>Details</h3>
|
|
<button class="close-button" @click="selectedItem = null">
|
|
<svg
|
|
width="20"
|
|
height="20"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<template x-if="selectedItem">
|
|
<div class="details-content">
|
|
<div class="details-preview">
|
|
<div
|
|
class="preview-icon"
|
|
x-text="getFileIcon(selectedItem)"
|
|
></div>
|
|
</div>
|
|
<div class="details-info">
|
|
<h4 x-text="selectedItem.name"></h4>
|
|
<div class="info-row">
|
|
<span class="info-label">Type</span>
|
|
<span
|
|
class="info-value"
|
|
x-text="selectedItem.type"
|
|
></span>
|
|
</div>
|
|
<div
|
|
class="info-row"
|
|
x-show="selectedItem.type !== 'folder'"
|
|
>
|
|
<span class="info-label">Size</span>
|
|
<span
|
|
class="info-value"
|
|
x-text="selectedItem.size"
|
|
></span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Modified</span>
|
|
<span
|
|
class="info-value"
|
|
x-text="selectedItem.modified"
|
|
></span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Created</span>
|
|
<span
|
|
class="info-value"
|
|
x-text="selectedItem.created"
|
|
></span>
|
|
</div>
|
|
</div>
|
|
<div class="details-actions">
|
|
<button
|
|
class="button-primary"
|
|
x-show="isEditableFile(selectedItem)"
|
|
@click="editFile(selectedItem)"
|
|
>
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
|
|
></path>
|
|
<path
|
|
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
|
|
></path>
|
|
</svg>
|
|
Edit
|
|
</button>
|
|
<button
|
|
class="button-secondary"
|
|
@click="downloadItem(selectedItem)"
|
|
>
|
|
Download
|
|
</button>
|
|
<button
|
|
class="button-secondary"
|
|
@click="shareItem(selectedItem)"
|
|
>
|
|
Share
|
|
</button>
|
|
<button
|
|
class="button-secondary danger"
|
|
@click="deleteItem(selectedItem)"
|
|
>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</aside>
|
|
</div>
|
|
|
|
<!-- Text Editor Modal -->
|
|
<div
|
|
class="editor-modal"
|
|
x-show="showEditor"
|
|
x-cloak
|
|
@click.self="closeEditor()"
|
|
>
|
|
<div class="editor-container">
|
|
<!-- Editor Header -->
|
|
<div class="editor-header">
|
|
<div class="editor-title">
|
|
<svg
|
|
width="20"
|
|
height="20"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
|
|
></path>
|
|
<path
|
|
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
|
|
></path>
|
|
</svg>
|
|
<span x-text="editorFileName"></span>
|
|
</div>
|
|
<div class="editor-actions">
|
|
<button
|
|
class="button-primary"
|
|
@click="saveFile()"
|
|
:disabled="editorSaving"
|
|
>
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"
|
|
></path>
|
|
<polyline points="17 21 17 13 7 13 7 21"></polyline>
|
|
<polyline points="7 3 7 8 15 8"></polyline>
|
|
</svg>
|
|
<span
|
|
x-text="editorSaving ? 'Saving...' : 'Save'"
|
|
></span>
|
|
</button>
|
|
<button class="button-secondary" @click="closeEditor()">
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
</svg>
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Editor Content -->
|
|
<div class="editor-content">
|
|
<template x-if="editorLoading">
|
|
<div class="editor-loading">
|
|
<div class="loading-spinner"></div>
|
|
<p>Loading file...</p>
|
|
</div>
|
|
</template>
|
|
<template x-if="!editorLoading">
|
|
<textarea
|
|
class="editor-textarea"
|
|
x-model="editorContent"
|
|
placeholder="Start typing..."
|
|
spellcheck="false"
|
|
></textarea>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Editor Footer -->
|
|
<div class="editor-footer">
|
|
<span class="editor-info">
|
|
<span x-text="editorContent.length"></span> characters ·
|
|
<span x-text="editorContent.split('\\n').length"></span>
|
|
lines
|
|
</span>
|
|
<span class="editor-path" x-text="editorFilePath"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|