Files
dewedev/EDITOR_TOOL_GUIDE.md
dwindown f60c1d16c8 feat: comprehensive editor UX refinement with collapsible sections and data loss prevention
Major improvements to Object Editor, Table Editor, and Invoice Editor:

## UX Enhancements:
- Made export sections collapsible across all editors to reduce page height
- Added comprehensive, collapsible usage tips with eye-catching design
- Implemented consistent input method patterns (file auto-load, inline URL buttons)
- Paste sections now collapse after successful parsing with data summaries

## Data Loss Prevention:
- Added confirmation modals when switching input methods with existing data
- Amber-themed warning design with specific data summaries
- Suggests saving before proceeding with destructive actions
- Prevents accidental data loss across all editor tools

## Consistency Improvements:
- Standardized file input styling with 'tool-input' class
- URL fetch buttons now inline (not below input) across all editors
- Parse buttons positioned consistently on bottom-right
- Auto-load behavior for file inputs matching across editors

## Bug Fixes:
- Fixed Table Editor cell text overflow with proper truncation
- Fixed Object Editor file input to auto-load content
- Removed unnecessary parse buttons and checkboxes
- Fixed Invoice Editor URL form layout

## Documentation:
- Created EDITOR_TOOL_GUIDE.md with comprehensive patterns
- Created EDITOR_CHECKLIST.md for quick reference
- Created PROJECT_ROADMAP.md with future plans
- Created TODO.md with detailed task lists
- Documented data loss prevention patterns
- Added code examples and best practices

All editors now follow consistent UX patterns with improved user experience and data protection.
2025-10-15 00:12:54 +07:00

749 lines
23 KiB
Markdown

# Editor Tool Building Guide
This guide ensures consistency across all editor tools in the Developer Tools application. Follow these patterns when creating new editor tools.
## Table of Contents
1. [Input Section Patterns](#input-section-patterns)
2. [Main Editor Section](#main-editor-section)
3. [Export/Output Section](#export-output-section)
4. [Usage Tips Section](#usage-tips-section)
5. [State Management](#state-management)
6. [Error Handling](#error-handling)
7. [Complete Example](#complete-example)
---
## Input Section Patterns
### Required Tabs
All editor tools must have these 4 input tabs:
1. **Create New** - Start empty or load sample data
2. **URL** - Fetch data from API endpoints
3. **Paste** - Paste data directly
4. **Open** - Upload files
### Tab Structure
```jsx
<div className="flex border-b border-gray-200 dark:border-gray-700">
<button onClick={() => handleTabChange('create')} className={...}>
<Plus className="h-4 w-4" />
Create New
</button>
<button onClick={() => handleTabChange('url')} className={...}>
<Globe className="h-4 w-4" />
URL
</button>
<button onClick={() => handleTabChange('paste')} className={...}>
<FileText className="h-4 w-4" />
Paste
</button>
<button onClick={() => handleTabChange('open')} className={...}>
<Upload className="h-4 w-4" />
Open
</button>
</div>
```
### Create New Tab
```jsx
{activeTab === 'create' && (
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Start Empty Button */}
<button onClick={handleStartEmpty} className="flex flex-col items-center p-6 border-2 border-dashed...">
<Plus className="h-8 w-8..." />
<span className="font-medium...">Start Empty</span>
<span className="text-xs...">Create a blank structure</span>
</button>
{/* Load Sample Button */}
<button onClick={handleLoadSample} className="flex flex-col items-center p-6 border-2 border-dashed...">
<FileText className="h-8 w-8..." />
<span className="font-medium...">Load Sample</span>
<span className="text-xs...">Start with example data</span>
</button>
</div>
{/* Tip */}
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-3">
<p className="text-xs text-blue-700 dark:text-blue-300">
💡 <strong>Tip:</strong> You can always import data later using the URL, Paste, or Open tabs.
</p>
</div>
</div>
)}
```
### URL Tab (CONSISTENT LAYOUT)
**IMPORTANT:** Button must be INLINE on the right, not below!
```jsx
{activeTab === 'url' && (
<div className="space-y-3">
<div className="flex gap-2">
<div className="relative flex-1">
<input
type="url"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://api.telegram.org/bot<token>/getMe"
className="tool-input w-full"
onKeyPress={(e) => e.key === 'Enter' && handleFetchData()}
/>
</div>
<button
onClick={handleFetchData}
disabled={isLoading || !url.trim()}
className="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white font-medium px-4 py-2 rounded-md transition-colors flex items-center whitespace-nowrap"
>
{isLoading ? 'Fetching...' : 'Fetch Data'}
</button>
</div>
<p className="text-xs text-gray-500 dark:text-gray-400">
Enter any URL that returns JSON data. Examples: Telegram Bot API, JSONPlaceholder, GitHub API, etc.
</p>
</div>
)}
```
### Paste Tab (WITH COLLAPSE)
```jsx
{activeTab === 'paste' && (
pasteCollapsed ? (
// Collapsed State
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
<div className="flex items-center justify-between">
<span className="text-sm text-green-700 dark:text-green-300">
Data loaded: {pasteDataSummary.format} ({pasteDataSummary.size.toLocaleString()} chars)
</span>
<button
onClick={() => setPasteCollapsed(false)}
className="text-sm text-blue-600 hover:underline"
>
Edit Input
</button>
</div>
</div>
) : (
// Expanded State
<div className="space-y-3">
<div>
<CodeMirrorEditor
value={inputText}
onChange={setInputText}
language="json"
placeholder="Paste your data here..."
maxLines={12}
showToggle={true}
className="w-full"
/>
</div>
{error && (
<div className="p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
<p className="text-sm text-red-600 dark:text-red-400">
<strong>Invalid Data:</strong> {error}
</p>
</div>
)}
<div className="flex items-center justify-between flex-shrink-0">
<div className="text-sm text-gray-600 dark:text-gray-400">
Auto-detects format
</div>
<button
onClick={handleParseData}
disabled={!inputValid || !inputText.trim()}
className="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-medium px-4 py-2 rounded-md transition-colors flex-shrink-0"
>
Parse Data
</button>
</div>
</div>
)
)}
```
### Open Tab (CONSISTENT WITH TABLE EDITOR)
**IMPORTANT:** Use `tool-input` class and auto-load on file selection!
```jsx
{activeTab === 'open' && (
<div className="space-y-3">
<input
ref={fileInputRef}
type="file"
accept=".json,.txt"
onChange={handleFileImport}
className="tool-input"
/>
<div className="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-md p-3">
<p className="text-xs text-green-700 dark:text-green-300">
🔒 <strong>Privacy:</strong> Your data stays in your browser. We don't store or upload anything - just help you open, edit, and export your files locally.
</p>
</div>
</div>
)}
```
### Parse Button Behavior
- **Valid data**: Collapse input, show summary, display editor
- **Invalid data**: Show error, keep input expanded, hide editor
- **Button disabled**: When no data or invalid format
---
## Main Editor Section
### Structure
```jsx
{(activeTab !== 'create' || createNewCompleted) && (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden mb-6">
{/* Editor Header */}
<div className="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
<Edit3 className="h-5 w-5 text-blue-600 dark:text-blue-400" />
Your Editor Name
</h3>
{/* Optional: Settings or Actions */}
</div>
</div>
{/* Editor Content */}
<div className="p-4">
{/* Your editor implementation */}
</div>
</div>
)}
```
### Fullscreen Support (Optional)
If your editor supports fullscreen:
```jsx
const [isFullscreen, setIsFullscreen] = useState(false);
<div className={`... ${
isFullscreen
? "fixed inset-0 z-[99999] rounded-none border-0 shadow-none overflow-hidden !m-0"
: "..."
}`}
style={isFullscreen ? { marginTop: "0 !important" } : {}}
>
```
---
## Export/Output Section
### Collapsible Export (REQUIRED)
```jsx
{data && (
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden mt-6">
{/* Collapsible Header */}
<div
onClick={() => setExportExpanded(!exportExpanded)}
className="px-4 py-3 border-b border-gray-200 dark:border-gray-700 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
<Download className="h-5 w-5 text-green-600 dark:text-green-400" />
Export Results
{exportExpanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</h3>
<div className="text-sm text-gray-600 dark:text-gray-400">
{/* Summary info */}
</div>
</div>
</div>
{/* Export Content - Collapsible */}
{exportExpanded && (
<div>
{/* Export tabs and content */}
</div>
)}
</div>
)}
```
### Export Tabs Pattern
```jsx
<div className="flex border-b border-gray-200 dark:border-gray-700">
<button
onClick={() => setExportTab('json')}
className={`flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors ${
exportTab === 'json'
? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'
}`}
>
<Braces className="h-4 w-4" />
JSON
</button>
{/* More export format tabs */}
</div>
```
---
## Usage Tips Section
### Collapsible Usage Tips (REQUIRED)
```jsx
{/* Usage Tips */}
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md overflow-hidden mt-6">
<div
onClick={() => setUsageTipsExpanded(!usageTipsExpanded)}
className="px-4 py-3 cursor-pointer hover:bg-blue-100 dark:hover:bg-blue-900/30 transition-colors flex items-center justify-between"
>
<h4 className="text-blue-800 dark:text-blue-200 font-medium flex items-center gap-2">
💡 Usage Tips
</h4>
{usageTipsExpanded ? <ChevronUp className="h-4 w-4 text-blue-600 dark:text-blue-400" /> : <ChevronDown className="h-4 w-4 text-blue-600 dark:text-blue-400" />}
</div>
{usageTipsExpanded && (
<div className="px-4 pb-4 text-blue-700 dark:text-blue-300 text-sm space-y-3">
<div>
<p className="font-medium mb-1">📝 Input Methods:</p>
<ul className="list-disc list-inside space-y-1 ml-2">
<li><strong>Create New:</strong> Description</li>
<li><strong>URL Import:</strong> Description</li>
<li><strong>Paste Data:</strong> Description</li>
<li><strong>Open Files:</strong> Description</li>
</ul>
</div>
<div>
<p className="font-medium mb-1">✏️ Editing Features:</p>
<ul className="list-disc list-inside space-y-1 ml-2">
{/* Your editor-specific features */}
</ul>
</div>
<div>
<p className="font-medium mb-1">📤 Export Options:</p>
<ul className="list-disc list-inside space-y-1 ml-2">
{/* Your export formats */}
</ul>
</div>
<div>
<p className="font-medium mb-1">💾 Data Privacy:</p>
<ul className="list-disc list-inside space-y-1 ml-2">
<li><strong>Local Processing:</strong> All data stays in your browser</li>
<li><strong>No Upload:</strong> We don't store or transmit your data</li>
<li><strong>Secure:</strong> Your information remains private</li>
</ul>
</div>
</div>
)}
</div>
```
---
## Data Loss Prevention (CRITICAL!)
### Confirmation Modal for Tab Changes
**IMPORTANT:** Always confirm before clearing user data when switching input methods!
### When to Show Confirmation
- User has entered/loaded data
- User tries to switch to a different input tab
- User tries to use Create New buttons (Start Empty/Load Sample)
### Implementation Pattern
#### 1. Check if User Has Data
```jsx
const hasUserData = () => {
// Check if there's meaningful data
return Object.keys(editorData).length > 0;
};
const hasModifiedData = () => {
// Check if data has been modified from initial state
// Return false for empty or sample data
// Return true for user-entered data
};
```
#### 2. Handle Tab Change with Confirmation
```jsx
const handleTabChange = (newTab) => {
if (hasModifiedData() && activeTab !== newTab) {
setPendingTabChange(newTab);
setShowInputChangeModal(true);
} else {
setActiveTab(newTab);
}
};
```
#### 3. Confirmation Modal Component
```jsx
const InputChangeConfirmationModal = ({
editorData,
currentMethod,
newMethod,
onConfirm,
onCancel
}) => {
const getMethodName = (method) => {
switch (method) {
case 'create': return 'Create New';
case 'create_empty': return 'Start Empty';
case 'create_sample': return 'Load Sample';
case 'url': return 'URL Import';
case 'paste': return 'Paste Data';
case 'open': return 'File Upload';
default: return method;
}
};
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full overflow-hidden">
{/* Header - Amber Warning */}
<div className="px-6 py-4 border-b border-gray-200 dark:border-gray-700 bg-amber-50 dark:bg-amber-900/20">
<div className="flex items-center gap-3">
<div className="flex-shrink-0">
<AlertTriangle className="h-6 w-6 text-amber-600 dark:text-amber-400" />
</div>
<div>
<h3 className="text-lg font-semibold text-amber-800 dark:text-amber-200">
Confirm Action
</h3>
<p className="text-sm text-amber-700 dark:text-amber-300">
{newMethod === 'create_empty' || newMethod === 'create_sample'
? `Using ${getMethodName(newMethod)} will clear your current data.`
: `Switching from ${getMethodName(currentMethod)} to ${getMethodName(newMethod)} will clear your current data.`
}
</p>
</div>
</div>
</div>
{/* Body - Show Current Data Summary */}
<div className="px-6 py-4">
<div className="space-y-3">
<p className="text-sm text-gray-600 dark:text-gray-400">
You currently have:
</p>
<ul className="text-sm text-gray-700 dark:text-gray-300 space-y-1 ml-4">
{/* List current data items */}
<li> [Data summary item 1]</li>
<li> [Data summary item 2]</li>
<li> [Data summary item 3]</li>
</ul>
{/* Blue Tip Box */}
<div className="mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
<p className="text-xs text-blue-700 dark:text-blue-300">
<strong>Tip:</strong> Consider downloading your current data as JSON before proceeding to save your work.
</p>
</div>
</div>
</div>
{/* Footer - Action Buttons */}
<div className="px-6 py-4 bg-gray-50 dark:bg-gray-700/50 flex justify-end gap-3">
<button
onClick={onCancel}
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded-md hover:bg-gray-50 dark:hover:bg-gray-500 transition-colors"
>
Cancel
</button>
<button
onClick={onConfirm}
className="px-4 py-2 text-sm font-medium text-white bg-amber-600 hover:bg-amber-700 rounded-md transition-colors flex items-center gap-2"
>
<AlertTriangle className="h-4 w-4" />
Switch & Clear Data
</button>
</div>
</div>
</div>
);
};
```
#### 4. Usage in Component
```jsx
{/* Confirmation Modal */}
{showInputChangeModal && (
<InputChangeConfirmationModal
editorData={editorData}
currentMethod={activeTab}
newMethod={pendingTabChange}
onConfirm={() => {
clearAllData();
setActiveTab(pendingTabChange);
setShowInputChangeModal(false);
setPendingTabChange(null);
}}
onCancel={() => {
setShowInputChangeModal(false);
setPendingTabChange(null);
}}
/>
)}
```
### Visual Design
- **Header**: Amber background with AlertTriangle icon
- **Body**: White/dark background with data summary
- **Tip Box**: Blue background with save suggestion
- **Footer**: Gray background with Cancel and Warning buttons
- **Warning Button**: Amber color with AlertTriangle icon
### Key Points
1. **Always show** when user has meaningful data
2. **List specific data** user will lose (not generic message)
3. **Suggest saving** before proceeding (blue tip box)
4. **Clear button text** - "Switch & Clear Data" (not ambiguous)
5. **z-50** to appear above everything
6. **Amber theme** for warning (not red, not blue)
---
## State Management
### Required State Variables
```jsx
// Tab management
const [activeTab, setActiveTab] = useState('create');
const [createNewCompleted, setCreateNewCompleted] = useState(false);
// Input data
const [inputText, setInputText] = useState('');
const [url, setUrl] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
// Collapse states
const [pasteCollapsed, setPasteCollapsed] = useState(false);
const [pasteDataSummary, setPasteDataSummary] = useState(null);
const [exportExpanded, setExportExpanded] = useState(false);
const [usageTipsExpanded, setUsageTipsExpanded] = useState(false);
// Confirmation modals
const [showInputChangeModal, setShowInputChangeModal] = useState(false);
const [pendingTabChange, setPendingTabChange] = useState(null);
// Refs
const fileInputRef = useRef(null);
// Your editor-specific state
const [editorData, setEditorData] = useState(null);
```
### Tab Change with Confirmation
```jsx
const handleTabChange = (newTab) => {
if (hasModifiedData() && activeTab !== newTab) {
setPendingTabChange(newTab);
setShowInputChangeModal(true);
} else {
setActiveTab(newTab);
}
};
const confirmInputChange = () => {
clearAllData();
setActiveTab(pendingTabChange);
setShowInputChangeModal(false);
setPendingTabChange(null);
};
```
---
## Error Handling
### Parse Data with Error Handling
```jsx
const handleParseData = () => {
try {
const parsed = parseYourData(inputText);
if (parsed.valid) {
setEditorData(parsed.data);
setError('');
setCreateNewCompleted(true);
setPasteDataSummary({
format: parsed.format,
size: inputText.length,
items: parsed.data.length
});
setPasteCollapsed(true);
} else {
// Show error, keep input expanded
setError(parsed.error || 'Invalid data format');
setPasteCollapsed(false);
}
} catch (err) {
setError(err.message || 'Failed to parse data');
setPasteCollapsed(false);
}
};
```
### Error Display
```jsx
{error && (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
<div className="flex items-center gap-2">
<AlertTriangle className="h-5 w-5 text-red-600 dark:text-red-400" />
<p className="text-red-700 dark:text-red-300">{error}</p>
</div>
</div>
)}
```
---
## Complete Example
See existing editors for complete implementations:
- **Object Editor** (`/src/pages/ObjectEditor.js`) - Best for JSON/structured data
- **Table Editor** (`/src/pages/TableEditor.js`) - Best for tabular data
- **Invoice Editor** (`/src/pages/InvoiceEditor.js`) - Best for form-based editors
---
## Checklist for New Editor Tools
### Input Section
- [ ] All 4 tabs implemented (Create New, URL, Paste, Open)
- [ ] URL tab has inline button (not below)
- [ ] Open tab uses `tool-input` class
- [ ] Open tab auto-loads on file selection
- [ ] Paste tab has parse button on bottom-right
- [ ] Paste tab collapses after successful parse
- [ ] Error handling shows errors and keeps input expanded
### Main Editor
- [ ] Only shows after data is loaded
- [ ] Has proper header with icon
- [ ] Implements your editor functionality
- [ ] Optional: Fullscreen support with proper z-index
### Export Section
- [ ] Collapsible by default
- [ ] Header shows summary info
- [ ] Chevron icon indicates state
- [ ] Hover effect on header
- [ ] Export tabs if multiple formats
### Usage Tips
- [ ] Collapsible by default
- [ ] 💡 emoji in header
- [ ] Comprehensive tips organized by category
- [ ] Includes Input Methods, Editing Features, Export Options, Data Privacy
### State & Behavior
- [ ] Tab change confirmation when data exists
- [ ] Proper error handling
- [ ] Loading states for async operations
- [ ] Dark mode support
### Styling
- [ ] Uses Tailwind CSS classes
- [ ] Consistent with other editors
- [ ] Responsive design
- [ ] Proper spacing and layout
---
## Common Patterns
### Button Styling
```jsx
// Primary action button
className="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-medium px-4 py-2 rounded-md transition-colors flex-shrink-0"
// Secondary button
className="bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 px-4 py-2 rounded-md transition-colors"
```
### Input Styling
```jsx
// Standard input
className="tool-input w-full"
// Or explicit:
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
```
### Card/Section Styling
```jsx
className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden"
```
---
## Import Requirements
### Required Icons (from lucide-react)
```jsx
import {
Plus, // Create New
Globe, // URL
FileText, // Paste
Upload, // Open
Download, // Export
Edit3, // Editor
AlertTriangle, // Errors
ChevronUp, // Collapse indicators
ChevronDown, // Collapse indicators
// Add your specific icons
} from 'lucide-react';
```
### Required Components
```jsx
import ToolLayout from '../components/ToolLayout';
import CodeMirrorEditor from '../components/CodeMirrorEditor';
import CodeEditor from '../components/CodeEditor'; // For read-only code display
```
---
## Best Practices
1. **Consistency First**: Always follow existing patterns before innovating
2. **User Data Safety**: Always confirm before clearing user data
3. **Error Messages**: Be specific and helpful
4. **Loading States**: Show loading indicators for async operations
5. **Accessibility**: Use semantic HTML and proper ARIA labels
6. **Dark Mode**: Test in both light and dark modes
7. **Mobile**: Ensure responsive design works on small screens
8. **Performance**: Lazy load heavy components when possible
---
## Testing Your Editor
1. **Input Methods**: Test all 4 tabs work correctly
2. **Parse Valid Data**: Ensure data loads and displays
3. **Parse Invalid Data**: Ensure errors show and input stays expanded
4. **Tab Switching**: Confirm data loss prevention works
5. **Export**: Test all export formats
6. **Collapse/Expand**: Test all collapsible sections
7. **Dark Mode**: Toggle and verify styling
8. **Mobile**: Test on small screen sizes
9. **Edge Cases**: Empty data, very large data, special characters
---
## Questions or Issues?
Refer to existing editors:
- Object Editor: JSON/PHP serialized data handling
- Table Editor: Tabular data with inline editing
- Invoice Editor: Form-based data entry
Follow these patterns and your new editor will be consistent with the rest of the application!