CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-uppy--dashboard

Universal UI plugin for Uppy file uploader providing comprehensive dashboard interface with drag-and-drop, file previews, metadata editing, and progress tracking.

Pending
Overview
Eval results
Files

file-editor.mddocs/

File Editor Integration

Integrate with file editor plugins for image editing and metadata management.

Capabilities

Open File Editor

Open the file editor interface for a specific file with compatible editor plugins.

/**
 * Open file editor for specific file
 * Checks for compatible editor plugins and activates editor overlay
 * @param file - File to open in editor
 */
openFileEditor(file: UppyFile): void;

Usage Examples:

import Dashboard from "@uppy/dashboard";

const dashboard = uppy.getPlugin("Dashboard") as Dashboard;

// Open editor for file
const file = uppy.getFile("file-id");
dashboard.openFileEditor(file);

// Open editor from file list click
function handleEditClick(fileId: string) {
  const file = uppy.getFile(fileId);
  if (file && dashboard.canEditFile(file)) {
    dashboard.openFileEditor(file);
  }
}

// Auto-open editor for images
uppy.on("file-added", (file) => {
  if (file.type?.startsWith("image/") && dashboard.canEditFile(file)) {
    dashboard.openFileEditor(file);
  }
});

Behavior:

  • Sets showFileEditor to true in dashboard state
  • Sets fileCardFor to the file ID
  • Sets activeOverlayType to 'FileEditor'
  • Calls selectFile(file) on all registered editor plugins
  • Only works if compatible editor plugins are available

Close File Editor

Close the file editor interface and return to appropriate dashboard state.

/**
 * Close file editor panel
 * Returns to file card if metadata editing is enabled, otherwise to main view
 */
closeFileEditor(): void;

Usage Examples:

// Close editor programmatically
dashboard.closeFileEditor();

// Close editor on escape key
document.addEventListener("keydown", (event) => {
  if (event.key === "Escape" && dashboard.getPluginState().showFileEditor) {
    dashboard.closeFileEditor();
  }
});

// Close editor after timeout
function autoCloseEditor(timeoutMs: number = 300000) { // 5 minutes
  setTimeout(() => {
    if (dashboard.getPluginState().showFileEditor) {
      dashboard.closeFileEditor();
    }
  }, timeoutMs);
}

Behavior:

  • Sets showFileEditor to false
  • If metadata editing is enabled: sets activeOverlayType to 'FileCard'
  • If metadata editing is disabled: sets fileCardFor to null and activeOverlayType to 'AddFiles'

Save File Editor

Save changes from the file editor and close the editor interface.

/**
 * Save file editor changes
 * Calls save() on all registered editor plugins and closes editor
 */
saveFileEditor(): void;

Usage Examples:

// Save editor changes
dashboard.saveFileEditor();

// Save with confirmation
function saveWithConfirmation() {
  if (confirm("Save changes to file?")) {
    dashboard.saveFileEditor();
  } else {
    dashboard.closeFileEditor();
  }
}

// Auto-save editor changes
let autoSaveInterval: number;

uppy.on("dashboard:file-edit-start", () => {
  autoSaveInterval = setInterval(() => {
    if (dashboard.getPluginState().showFileEditor) {
      dashboard.saveFileEditor();
    }
  }, 60000); // Auto-save every minute
});

uppy.on("dashboard:file-edit-complete", () => {
  clearInterval(autoSaveInterval);
});

Behavior:

  • Calls save() method on all registered editor plugins
  • Calls closeFileEditor() to hide the editor interface

Check File Editor Compatibility

Determine if a file can be edited with available editor plugins.

/**
 * Check if file can be edited with available editor plugins
 * @param file - File to check for editor compatibility
 * @returns True if file can be edited, false otherwise
 */
canEditFile(file: UppyFile): boolean;

Usage Examples:

// Check if file can be edited
const file = uppy.getFile("file-id");
if (dashboard.canEditFile(file)) {
  // Show edit button
  showEditButton(file.id);
} else {
  // Hide edit button
  hideEditButton(file.id);
}

// Filter editable files
const editableFiles = uppy.getFiles().filter(file => 
  dashboard.canEditFile(file)
);

// Conditional UI rendering
function renderFileActions(file: UppyFile) {
  const canEdit = dashboard.canEditFile(file);
  return (
    <div className="file-actions">
      <button onClick={() => removeFile(file.id)}>Remove</button>
      {canEdit && (
        <button onClick={() => dashboard.openFileEditor(file)}>
          Edit
        </button>
      )}
    </div>
  );
}

Editor Plugin Integration

How editor plugins integrate with the dashboard file editor system.

/**
 * Editor plugin requirements for dashboard integration
 */
interface EditorPlugin {
  /** Plugin must have unique ID */
  id: string;
  /** Plugin must be 'editor' type */
  type: 'editor';
  /** Check if plugin can edit specific file */
  canEditFile(file: UppyFile): boolean;
  /** Select file for editing */
  selectFile(file: UppyFile): void;
  /** Save editor changes */
  save(): void;
  /** Render editor interface */
  render(): ComponentChild;
}

Editor Plugin Examples:

// Image editor plugin
class ImageEditor extends BasePlugin {
  type = 'editor';
  id = 'ImageEditor';

  canEditFile(file: UppyFile): boolean {
    return file.type?.startsWith('image/') || false;
  }

  selectFile(file: UppyFile): void {
    this.selectedFile = file;
    this.loadImageIntoEditor(file.data);
  }

  save(): void {
    if (this.selectedFile && this.hasChanges) {
      const editedBlob = this.exportEditedImage();
      this.uppy.setFileData(this.selectedFile.id, editedBlob);
    }
  }

  render() {
    return h('div', { className: 'image-editor' }, [
      h('canvas', { ref: this.canvasRef }),
      h('div', { className: 'editor-tools' }, this.renderTools())
    ]);
  }
}

// Text metadata editor
class MetadataEditor extends BasePlugin {
  type = 'editor';
  id = 'MetadataEditor';

  canEditFile(): boolean {
    return true; // Can edit metadata for any file
  }

  selectFile(file: UppyFile): void {
    this.selectedFile = file;
    this.loadMetadata(file.meta);
  }

  save(): void {
    if (this.selectedFile) {
      const newMeta = this.getFormData();
      this.uppy.setFileMeta(this.selectedFile.id, newMeta);
    }
  }
}

Auto-Open Configuration

Configure automatic editor opening based on file types or conditions.

/**
 * Auto-open configuration options
 */
interface AutoOpenConfig {
  /** Auto-open editor type on file selection */
  autoOpen?: 'metaEditor' | 'imageEditor' | null;
}

Auto-Open Examples:

// Auto-open metadata editor
uppy.use(Dashboard, {
  autoOpen: 'metaEditor'
});

// Auto-open image editor for images
uppy.use(Dashboard, {
  autoOpen: 'imageEditor'
});

// Custom auto-open logic
uppy.use(Dashboard, {
  autoOpen: null // Disable automatic opening
});

uppy.on('files-added', (files) => {
  files.forEach(file => {
    if (file.type?.startsWith('image/') && needsImageEditing(file)) {
      dashboard.openFileEditor(file);
    } else if (needsMetadataEditing(file)) {
      dashboard.toggleFileCard(true, file.id);
    }
  });
});

Editor State Management

State properties related to file editor functionality.

/**
 * File editor state properties
 */
interface FileEditorState {
  /** Whether file editor is currently shown */
  showFileEditor: boolean;
  /** File ID being edited, null if no file selected */
  fileCardFor: string | null;
  /** Active overlay type */
  activeOverlayType: 'FileEditor' | 'FileCard' | null;
}

Editor Events

Events emitted during file editor lifecycle.

/**
 * File editor events
 */
interface FileEditorEvents<M extends Meta, B extends Body> {
  /** Emitted when file editing starts */
  "dashboard:file-edit-start": (file?: UppyFile<M, B>) => void;
  /** Emitted when file editing completes */
  "dashboard:file-edit-complete": (file?: UppyFile<M, B>) => void;
}

Event Usage Examples:

// Track editor usage
uppy.on('dashboard:file-edit-start', (file) => {
  console.log(`Started editing: ${file?.name}`);
  trackEvent('file_edit_start', {
    fileType: file?.type,
    fileSize: file?.size
  });
});

uppy.on('dashboard:file-edit-complete', (file) => {
  console.log(`Finished editing: ${file?.name}`);
  trackEvent('file_edit_complete', {
    fileType: file?.type,
    duration: Date.now() - editStartTime
  });
});

// Editor session management
let editSession: EditSession | null = null;

uppy.on('dashboard:file-edit-start', (file) => {
  editSession = new EditSession(file);
  editSession.start();
});

uppy.on('dashboard:file-edit-complete', (file) => {
  if (editSession) {
    editSession.complete();
    editSession = null;
  }
});

Multiple Editor Support

Handle multiple editor plugins and editor selection.

/**
 * Multiple editor management
 */
interface MultipleEditorSupport {
  /** Get all registered editor plugins */
  getEditors(targets: Target[]): TargetWithRender[];
  /** Check if any editor can handle file */
  canEditFile(file: UppyFile): boolean;
  /** Open file in all compatible editors */
  openFileEditor(file: UppyFile): void;
}

Multiple Editor Examples:

// Register multiple editors
uppy.use(ImageEditor, { /* options */ });
uppy.use(MetadataEditor, { /* options */ });
uppy.use(CropEditor, { /* options */ });

// Selective editor activation
function openSpecificEditor(file: UppyFile, editorType: string) {
  const editors = dashboard.getPluginState().targets
    .filter(target => target.type === 'editor' && target.id === editorType);
  
  if (editors.length > 0) {
    const plugin = uppy.getPlugin(editorType);
    if (plugin && plugin.canEditFile(file)) {
      dashboard.openFileEditor(file);
    }
  }
}

// Editor selection UI
function renderEditorOptions(file: UppyFile) {
  const availableEditors = dashboard.getPluginState().targets
    .filter(target => target.type === 'editor')
    .filter(target => uppy.getPlugin(target.id).canEditFile(file));

  return availableEditors.map(editor => (
    <button 
      key={editor.id}
      onClick={() => openSpecificEditor(file, editor.id)}
    >
      Edit with {editor.name}
    </button>
  ));
}

Editor Integration Best Practices

Recommended patterns for editor plugin development and integration.

/**
 * Editor plugin best practices
 */
interface EditorBestPractices {
  /** Implement proper file type checking */
  canEditFile(file: UppyFile): boolean;
  /** Handle file loading and error states */
  selectFile(file: UppyFile): Promise<void>;
  /** Provide clear save/cancel actions */
  save(): Promise<void>;
  /** Clean up resources on unmount */
  unmount(): void;
  /** Provide keyboard shortcuts */
  handleKeyboard(event: KeyboardEvent): void;
  /** Support undo/redo operations */
  undo(): void;
  redo(): void;
}

Best Practice Examples:

class AdvancedImageEditor extends BasePlugin {
  type = 'editor';
  
  async selectFile(file: UppyFile) {
    try {
      this.setStatus('loading');
      await this.loadImageData(file.data);
      this.setStatus('ready');
    } catch (error) {
      this.setStatus('error');
      this.uppy.info('Failed to load image in editor', 'error');
    }
  }

  async save() {
    try {
      this.setStatus('saving');
      const editedData = await this.exportImage();
      this.uppy.setFileData(this.selectedFile.id, editedData);
      this.setStatus('saved');
    } catch (error) {
      this.setStatus('error');
      this.uppy.info('Failed to save edited image', 'error');
    }
  }

  handleKeyboard(event: KeyboardEvent) {
    if (event.ctrlKey || event.metaKey) {
      switch (event.key) {
        case 'z':
          event.preventDefault();
          if (event.shiftKey) {
            this.redo();
          } else {
            this.undo();
          }
          break;
        case 's':
          event.preventDefault();
          this.save();
          break;
      }
    }
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-uppy--dashboard

docs

configuration.md

file-editor.md

file-management.md

index.md

modal-management.md

panel-management.md

plugin-integration.md

tile.json