12 KiB
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
-
Customization Options:
- Custom emoji (e.g., 📁, 🎵, 📷, 💼, 🏠)
- Predefined cross-browser icons from a curated set
- Default folder icon when no customization is set
-
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
-
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.
// 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:
encrypted_custom_icon TEXT,
1.2 Update Domain Model
File: internal/domain/collection/model.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:
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 INSERTinternal/repo/collection/update.go- Include new field in UPDATEinternal/repo/collection/get.go- Include new field in SELECTinternal/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
// 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:
{/* 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:
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.jsxCollectionDetails.jsx- Sidebar navigation (if applicable)
2.5 Update Encryption/Decryption
Update the collection encryption service to handle the new field:
- Encrypt
customIconwhen saving - Decrypt
encrypted_custom_iconwhen loading
Phase 3: Native Desktop (native/desktop/maplefile)
3.1 Update Domain Model
File: internal/domain/collection/model.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
- Backward Compatible: Empty
encrypted_custom_iconmeans default icon - No Data Migration Needed: New collections get the field, old collections have NULL/empty
- 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
- E2EE: Custom icon is encrypted with collection key
- Input Validation: Only allow valid emoji or predefined icon IDs
- XSS Prevention: Sanitize icon display (emoji rendering, no HTML)
- Size Limits: Max length for custom icon field (e.g., 50 chars)
Future Enhancements
- Custom uploaded icons (requires more complex storage)
- Icon color customization
- Icon packs/themes
- Bulk icon changes (apply to multiple collections)
Files to Modify
Backend (cloud/maplefile-backend)
Schema Update (modify existing migration):
migrations/012_create_collections_by_id.up.cql- Addencrypted_custom_icon TEXTcolumn
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:
src/components/IconPicker/IconPicker.jsx- Modal with emoji grid + predefined iconssrc/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:
internal/domain/collection/model.go- AddCustomIconandEncryptedCustomIconfields
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 |