Initial commit: Open sourcing all of the Maple Open Technologies code.

This commit is contained in:
Bartlomiej Mika 2025-12-02 14:33:08 -05:00
commit 755d54a99d
2010 changed files with 448675 additions and 0 deletions

View file

@ -0,0 +1,391 @@
# Collection Icon Customization Plan
## Overview
Add the ability to customize collection (folder) icons with emojis or predefined icons. This feature enhances the user experience by allowing visual differentiation between collections.
## Requirements
1. **Customization Options**:
- Custom emoji (e.g., 📁, 🎵, 📷, 💼, 🏠)
- Predefined cross-browser icons from a curated set
- Default folder icon when no customization is set
2. **Behavior**:
- Default to standard folder icon if no customization
- Persist across browser sessions (stored in database)
- Easy to change or revert to default
- Intuitive, user-friendly UI
3. **Security Consideration**:
- Icon data should be encrypted (E2EE) like collection name
- Only emoji characters or predefined icon identifiers allowed
---
## Data Model Design
### New Field: `custom_icon`
Add a new encrypted field to the Collection model that stores icon customization data.
```go
// CustomIcon stores the collection's custom icon configuration
type CustomIcon struct {
Type string `json:"type"` // "emoji", "icon", or "" (empty = default)
Value string `json:"value"` // Emoji character or icon identifier
}
```
**Field Storage Options**:
| Option | Pros | Cons |
|--------|------|------|
| A) Single encrypted JSON field | Simple, one field | Requires parsing |
| B) Two fields (type + value) | Clear structure | More columns |
| C) Single string field | Simplest | Limited validation |
**Recommended: Option C** - Single `encrypted_custom_icon` field storing either:
- Empty string `""` → Default folder icon
- Emoji character (e.g., `"📷"`) → Display as emoji
- Icon identifier (e.g., `"icon:briefcase"`) → Predefined icon
This keeps the schema simple and the client handles interpretation.
---
## Implementation Plan
### Phase 1: Backend (cloud/maplefile-backend)
#### 1.1 Database Schema Update
**Note:** No new migration files needed - updating existing migration `012_create_collections_by_id.up.cql` directly (assumes full database wipe).
Add to `collections_by_id` table:
```sql
encrypted_custom_icon TEXT,
```
#### 1.2 Update Domain Model
File: `internal/domain/collection/model.go`
```go
type Collection struct {
// ... existing fields ...
// EncryptedCustomIcon stores the custom icon for this collection.
// Empty string means use default folder icon.
// Contains either an emoji character or "icon:<identifier>" for predefined icons.
// Encrypted with the collection key for E2EE.
EncryptedCustomIcon string `bson:"encrypted_custom_icon" json:"encrypted_custom_icon"`
}
```
Also add to `CollectionSyncItem` for sync operations:
```go
type CollectionSyncItem struct {
// ... existing fields ...
EncryptedCustomIcon string `json:"encrypted_custom_icon,omitempty" bson:"encrypted_custom_icon,omitempty"`
}
```
**Note:** The sync query in `collectionsync.go:getCollectionSyncItem()` fetches minimal data from `collections_by_id`. This query will need to include `encrypted_custom_icon` so clients can display the correct icon during sync.
#### 1.3 Update Repository Layer
Files to modify:
- `internal/repo/collection/create.go` - Include new field in INSERT
- `internal/repo/collection/update.go` - Include new field in UPDATE
- `internal/repo/collection/get.go` - Include new field in SELECT
- `internal/repo/collection/sync.go` - Include new field in sync queries
#### 1.4 Update HTTP Handlers (if needed)
The existing create/update endpoints should automatically handle the new field since they accept the full Collection struct.
---
### Phase 2: Frontend (web/maplefile-frontend)
#### 2.1 Create Icon Picker Component
New file: `src/components/IconPicker/IconPicker.jsx`
Features:
- Emoji picker tab with common categories (objects, activities, symbols)
- Predefined icons tab (Heroicons subset)
- "Default" option to revert to folder icon
- Search/filter functionality
- Recently used icons
```jsx
// Example structure
const IconPicker = ({ value, onChange, onClose }) => {
const [activeTab, setActiveTab] = useState('emoji'); // 'emoji' | 'icons'
const predefinedIcons = [
{ id: 'briefcase', icon: BriefcaseIcon, label: 'Work' },
{ id: 'photo', icon: PhotoIcon, label: 'Photos' },
{ id: 'music', icon: MusicalNoteIcon, label: 'Music' },
{ id: 'document', icon: DocumentIcon, label: 'Documents' },
{ id: 'archive', icon: ArchiveBoxIcon, label: 'Archive' },
// ... more icons
];
const popularEmojis = ['📁', '📷', '🎵', '💼', '🏠', '❤️', '⭐', '🎮', '📚', '🎨'];
return (
// ... picker UI
);
};
```
#### 2.2 Update CollectionEdit Page
File: `src/pages/User/FileManager/Collections/CollectionEdit.jsx`
Add icon customization section:
```jsx
{/* Icon Customization Section */}
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<SparklesIcon className="h-5 w-5 mr-2 text-gray-500" />
Customize Icon
</h2>
<div className="flex items-center space-x-4">
{/* Current Icon Preview */}
<div className="flex items-center justify-center h-16 w-16 bg-gray-100 rounded-xl border-2 border-dashed border-gray-300">
<CollectionIcon icon={formData.customIcon} size="lg" />
</div>
{/* Change/Reset Buttons */}
<div className="space-y-2">
<button onClick={() => setShowIconPicker(true)} className="...">
Change Icon
</button>
{formData.customIcon && (
<button onClick={() => setFormData({...formData, customIcon: ''})} className="...">
Reset to Default
</button>
)}
</div>
</div>
</div>
```
#### 2.3 Create CollectionIcon Component
New file: `src/components/CollectionIcon/CollectionIcon.jsx`
Renders the appropriate icon based on the customIcon value:
```jsx
const CollectionIcon = ({ icon, collectionType = 'folder', size = 'md', className = '' }) => {
const sizes = {
sm: 'h-4 w-4',
md: 'h-6 w-6',
lg: 'h-10 w-10',
};
// Default folder/album icon
if (!icon || icon === '') {
const Icon = collectionType === 'album' ? PhotoIcon : FolderIcon;
return <Icon className={`${sizes[size]} ${className}`} />;
}
// Predefined icon
if (icon.startsWith('icon:')) {
const iconId = icon.replace('icon:', '');
const IconComponent = predefinedIconMap[iconId];
return IconComponent ? <IconComponent className={`${sizes[size]} ${className}`} /> : <FolderIcon className={`${sizes[size]} ${className}`} />;
}
// Emoji
return <span className={`${emojiSizes[size]} ${className}`}>{icon}</span>;
};
```
#### 2.4 Update Collection List/Grid Views
Update anywhere collections are displayed to use the new `CollectionIcon` component:
- `FileManagerIndex.jsx`
- `CollectionDetails.jsx`
- Sidebar navigation (if applicable)
#### 2.5 Update Encryption/Decryption
Update the collection encryption service to handle the new field:
- Encrypt `customIcon` when saving
- Decrypt `encrypted_custom_icon` when loading
---
### Phase 3: Native Desktop (native/desktop/maplefile)
#### 3.1 Update Domain Model
File: `internal/domain/collection/model.go`
```go
type Collection struct {
// ... existing fields ...
// CustomIcon stores the decrypted custom icon for this collection.
// Empty string means use default folder icon.
// Contains either an emoji character or "icon:<identifier>" for predefined icons.
CustomIcon string `json:"custom_icon,omitempty"`
// EncryptedCustomIcon is the encrypted version from cloud
EncryptedCustomIcon string `json:"encrypted_custom_icon,omitempty"`
}
```
#### 3.2 Update Sync Service
Ensure the sync service handles the new field when syncing collections from the cloud.
#### 3.3 Update Frontend (Wails)
The desktop frontend uses the same React patterns, so the IconPicker and CollectionIcon components can be shared or adapted.
---
## Predefined Icon Set
A curated set of cross-browser compatible icons:
| ID | Icon | Use Case |
|----|------|----------|
| `briefcase` | BriefcaseIcon | Work |
| `photo` | PhotoIcon | Photos |
| `music` | MusicalNoteIcon | Music |
| `video` | VideoCameraIcon | Videos |
| `document` | DocumentTextIcon | Documents |
| `archive` | ArchiveBoxIcon | Archive |
| `star` | StarIcon | Favorites |
| `heart` | HeartIcon | Personal |
| `home` | HomeIcon | Home |
| `academic` | AcademicCapIcon | School |
| `code` | CodeBracketIcon | Code |
| `cloud` | CloudIcon | Cloud |
| `lock` | LockClosedIcon | Private |
| `gift` | GiftIcon | Gifts |
| `calendar` | CalendarIcon | Events |
---
## Migration Strategy
1. **Backward Compatible**: Empty `encrypted_custom_icon` means default icon
2. **No Data Migration Needed**: New collections get the field, old collections have NULL/empty
3. **Clients Handle Missing Field**: Treat NULL/empty as default
---
## Testing Checklist
### Backend
- [ ] Migration runs successfully
- [ ] Create collection with custom icon
- [ ] Update collection custom icon
- [ ] Revert to default icon
- [ ] Sync includes custom icon field
- [ ] E2EE encryption/decryption works
### Frontend (Web)
- [ ] Icon picker opens and closes
- [ ] Emoji selection works
- [ ] Predefined icon selection works
- [ ] Reset to default works
- [ ] Icon persists after page reload
- [ ] Icon displays correctly in list/grid views
- [ ] Works across different browsers
### Native Desktop
- [ ] Sync downloads custom icon
- [ ] Icon displays correctly
- [ ] Edit icon works (if implemented)
---
## Security Considerations
1. **E2EE**: Custom icon is encrypted with collection key
2. **Input Validation**: Only allow valid emoji or predefined icon IDs
3. **XSS Prevention**: Sanitize icon display (emoji rendering, no HTML)
4. **Size Limits**: Max length for custom icon field (e.g., 50 chars)
---
## Future Enhancements
1. **Custom uploaded icons** (requires more complex storage)
2. **Icon color customization**
3. **Icon packs/themes**
4. **Bulk icon changes** (apply to multiple collections)
---
## Files to Modify
### Backend (cloud/maplefile-backend)
**Schema Update (modify existing migration):**
1. `migrations/012_create_collections_by_id.up.cql` - Add `encrypted_custom_icon TEXT` column
**Domain Layer:**
2. `internal/domain/collection/model.go` - Add `EncryptedCustomIcon` field to `Collection` and `CollectionSyncItem` structs
**Repository Layer:**
4. `internal/repo/collection/create.go` - Add `encrypted_custom_icon` to INSERT query (line ~57-66)
5. `internal/repo/collection/update.go` - Add `encrypted_custom_icon` to UPDATE query (line ~64-73)
6. `internal/repo/collection/get.go` - Add `encrypted_custom_icon` to SELECT query and scan (line ~44-52, ~72-90)
7. `internal/repo/collection/collectionsync.go` - Add field to sync queries
**Note:** Secondary tables (013-017) do NOT need modification - they are lookup/index tables that only store keys and minimal fields. The `encrypted_custom_icon` is stored only in `collections_by_id`.
### Frontend (web/maplefile-frontend)
**New Components:**
1. `src/components/IconPicker/IconPicker.jsx` - Modal with emoji grid + predefined icons
2. `src/components/CollectionIcon/CollectionIcon.jsx` - Renders appropriate icon based on value
**Modified Pages:**
3. `src/pages/User/FileManager/Collections/CollectionEdit.jsx` - Add icon customization section
4. `src/pages/User/FileManager/Collections/CollectionDetails.jsx` - Display custom icon
5. `src/pages/User/FileManager/Collections/CollectionCreate.jsx` - Optional icon selection on create
6. `src/pages/User/FileManager/FileManagerIndex.jsx` - Display custom icons in list/grid
**Services:**
7. Collection encryption service - Handle `customIcon` field encryption/decryption
### Native Desktop (native/desktop/maplefile)
**Domain:**
1. `internal/domain/collection/model.go` - Add `CustomIcon` and `EncryptedCustomIcon` fields
**Repository:**
2. `internal/repo/collection/repository.go` - Handle new field in CRUD operations
**Sync:**
3. `internal/service/sync/collection.go` - Include field in sync operations
**Frontend:**
4. `frontend/src/` - Adapt IconPicker and CollectionIcon components (if not shared with web)
---
## Estimated Effort
| Phase | Effort |
|-------|--------|
| Backend (migration + model) | 2-3 hours |
| Frontend components | 4-6 hours |
| Frontend integration | 2-3 hours |
| Native desktop | 2-3 hours |
| Testing | 2-3 hours |
| **Total** | **12-18 hours** |