npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tacel-app-storage

v1.0.1

Published

Windows File Explorer-like UI for Tacel App-Storage share access with SMB service account authentication

Downloads

97

Readme

tacel-app-storage

A Windows File Explorer-like UI for Tacel App-Storage share access with SMB service account authentication.

This module contains NO sensitive data. All credentials, paths, and configuration are provided by the consuming application at runtime.


Table of Contents


Release Notes

v1.0.1 (2026-03-05)

  • Fixed queue dialog live updates so row/group controls stay responsive.
  • Added grouped transfer queue interaction hardening (expand, pause/resume/cancel).
  • Added blocking delete overlay with progress text in frontend for long delete operations.
  • Switched backend recursive delete to safer async/chunked removal for large folders.
  • Reduced unnecessary explorer refresh churn after uploads.

Architecture Overview

This module has two parts:

| Part | File | Process | Purpose | |------|------|---------|---------| | Backend | app-storage-api.js | Main | IPC handler factory — SMB connection, file operations | | Frontend | app-storage.js + app-storage.css | Renderer | Windows Explorer-like UI component |

┌─────────────────────────────────────────────────────────────────────────────┐
│  YOUR APP (provides all sensitive configuration)                             │
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────────┐│
│  │  Main Process                                                            ││
│  │                                                                          ││
│  │  createAppStorageAPI(ipcMain, {                                          ││
│  │    channelPrefix: 'app-storage',                                         ││
│  │    sharePath: '\\\\192.168.1.5\\TacelAppsStorage',  ← App provides       ││
│  │    credentials: {                                                        ││
│  │      username: 'DOMAIN\\ServiceAccount',            ← App provides       ││
│  │      password: getSecurePassword()                  ← App provides       ││
│  │    }                                                                     ││
│  │  })                                                                      ││
│  └─────────────────────────────────────────────────────────────────────────┘│
│                              │                                               │
│                              │ IPC                                           │
│                              ▼                                               │
│  ┌─────────────────────────────────────────────────────────────────────────┐│
│  │  Renderer Process                                                        ││
│  │                                                                          ││
│  │  new AppStorageExplorer(container, {                                     ││
│  │    channelPrefix: 'app-storage',                                         ││
│  │    ipcInvoke: window.electron.ipcRenderer.invoke                         ││
│  │  })                                                                      ││
│  └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘

Folder Structure

The TacelAppsStorage share has a 5-layer architecture:

TacelAppsStorage/
│
├── _System/                    ← Infrastructure (logs, backups, config)
│   ├── Logs/
│   │   ├── OfficeHQ/
│   │   ├── ShipWorks/
│   │   ├── TechPortal/
│   │   ├── WireHub/
│   │   ├── SignalLine/
│   │   ├── AdminPro/
│   │   └── NCRManagement/
│   ├── Backups/
│   │   ├── Daily/
│   │   ├── Weekly/
│   │   └── Monthly/
│   ├── Config/
│   ├── Migrations/
│   └── Audit/
│
├── Platform/                   ← Application data
│   ├── Apps/                   ← Individual app folders (each app controls its own structure)
│   │   ├── OfficeHQ/
│   │   ├── ShipWorks/
│   │   ├── TechPortal/
│   │   ├── WireHub/
│   │   ├── SignalLine/
│   │   ├── AdminPro/
│   │   ├── NCRManagement/
│   │   ├── ShipStatusPro/
│   │   ├── PIU/
│   │   ├── MRP/
│   │   └── LWU/
│   │
│   └── Shared/                 ← Feature-based shared data (cross-app)
│       ├── RMA/
│       │   ├── Active/         ← Active RMA folders (RMA-2024-001, etc.)
│       │   └── Internal/
│       │       ├── Evaluation/
│       │       └── Repair/
│       ├── NCR/
│       ├── Forms/
│       │   ├── Templates/
│       │   ├── Files/
│       │   └── Previews/
│       ├── Customers/
│       ├── Suppliers/
│       ├── Products/
│       ├── Tickets/
│       └── Chat/
│           └── Attachments/
│
├── _Temp/                      ← Temporary files and processing queues
│   ├── OfficeHQ/
│   ├── ShipWorks/
│   ├── TechPortal/
│   ├── WireHub/
│   ├── SignalLine/
│   ├── AdminPro/
│   ├── NCRManagement/
│   └── Processing/
│
├── Updates/                    ← Auto-update packages for each app
│   ├── OfficeHQ/
│   │   └── app/
│   ├── ShipWorks/
│   │   └── app/
│   ├── TechPortal/
│   │   └── app/
│   ├── WireHub/
│   │   └── app/
│   ├── SignalLine/
│   │   └── app/
│   ├── AdminPro/
│   │   └── app/
│   ├── NCRManagement/
│   │   └── app/
│   ├── ShipStatusPro/
│   │   └── app/
│   ├── PIU/
│   │   └── app/
│   └── LWU/
│       └── app/
│
└── Archive/                    ← Old/discontinued data (flattened)
    ├── RMA/
    ├── NCR/
    ├── Apps/
    ├── Projects/
    └── Legacy/

Layer Descriptions

| Layer | Folder | Purpose | |-------|--------|---------| | System | _System/ | Infrastructure files — logs, backups, config, migrations, audit trails | | Platform | Platform/ | Application data — Apps/ for individual apps, Shared/ for cross-app features | | Temp | _Temp/ | Temporary files and processing queues — auto-cleaned periodically | | Updates | Updates/ | Auto-update packages for each app | | Archive | Archive/ | Old/discontinued data — flattened structure for simplicity |

Apps List

| App | Folder Name | Description | |-----|-------------|-------------| | Office-HQ | OfficeHQ | Office management | | ShipWorks | ShipWorks | Shipping operations | | Tech-Portal | TechPortal | Technical support | | Wire-Hub | WireHub | Wire department | | Signal Line | SignalLine | Signal line management | | Admin-Pro | AdminPro | Administration | | NCR-Management | NCRManagement | NCR management | | ShipStatus Pro | ShipStatusPro | Shipping status | | PIU | PIU | PIU system | | MRP | MRP | MRP system | | LWU | LWU | LWU system |


Security Model

What the Module Contains

| Item | In Module? | Notes | |------|-----------|-------| | Folder structure metadata | ✅ Yes | Names, descriptions, hierarchy | | File icons and UI | ✅ Yes | Visual components | | IPC handler logic | ✅ Yes | File operations code |

What the Module Does NOT Contain

| Item | In Module? | Where It Lives | |------|-----------|----------------| | Share IP address | ❌ No | App's config | | Service account username | ❌ No | App's config | | Service account password | ❌ No | App's safeStorage | | UNC paths | ❌ No | App provides at runtime | | User lists or roles | ❌ No | App's auth system |

Authentication Flow

  1. App starts → retrieves credentials from secure storage
  2. App calls createAppStorageAPI() with credentialsProvider (or static credentials)
  3. Each operation → connect → do work → disconnect (no persistent connection)
  4. Only module-created sessions are disconnected (existing user mappings are preserved)
  5. Credentials never stored in module code

Backend Usage

// In your Electron main process
const { createAppStorageAPI } = require('tacel-app-storage/app-storage-api');

createAppStorageAPI(ipcMain, {
  // Required: IPC channel prefix (must match frontend)
  channelPrefix: 'app-storage',
  
  // Required: UNC path to the share (YOUR app provides this)
  sharePath: '\\\\192.168.1.5\\TacelAppsStorage',
  
  // Preferred: credential provider (resolved at operation time)
  credentialsProvider: () => ({
    username: getServiceUserFromSecureStore(),
    password: getPasswordFromSafeStorage()
  }),

  // Optional alternative: static credentials object
  // credentials: { username: 'DOMAIN\\ServiceAccount', password: '...' },
  
  // Optional: Paths that cannot be accessed
  restrictedPaths: ['_System/Config', '_System/Audit'],

  // Optional security behavior (recommended defaults)
  connectionMode: 'operationScoped', // default: connect per operation, then disconnect
  allowShellOpen: false,             // default: disable system-app open
  allowOpenInExplorer: false,        // default: disable reveal in Explorer
  clearCachedCredentials: false      // default: do NOT clear global Windows cached creds
});

IPC Channels Registered

Using prefix app-storage as an example:

| Channel | Args | Returns | Description | |---------|------|---------|-------------| | app-storage:list | { relativePath } | { ok, items[], currentPath } | List directory contents | | app-storage:search | { query, relativePath?, extensions?, modifiedFrom?, modifiedTo?, includeDirectories?, maxResults? } | { ok, results[], truncated } | Recursive search with filters | | app-storage:read | { relativePath } | { ok, data, info } | Read file as base64 | | app-storage:preview | { relativePath, maxBytes? } | { ok, data, info, mimeType, totalSize, bytesRead, truncated } | Read preview-safe payload | | app-storage:write | { relativePath, data, overwrite? } | { ok, info } | Write file from base64 | | app-storage:upload-start | { relativePath, overwrite?, totalSize? } | { ok, uploadId, chunkSize } | Start chunked upload session | | app-storage:upload-chunk | { uploadId, data } | { ok, bytesWritten, totalSize } | Append chunk to upload session | | app-storage:upload-finish | { uploadId } | { ok, info } | Finalize chunked upload | | app-storage:upload-abort | { uploadId } | { ok, aborted } | Cancel chunked upload and cleanup temp file | | app-storage:delete | { relativePath, recursive? } | { ok } | Delete file or folder | | app-storage:rename | { relativePath, newName } | { ok, info } | Rename file or folder | | app-storage:copy | { sourcePath, destPath, overwrite? } | { ok, info } | Copy file or folder | | app-storage:move | { sourcePath, destPath, overwrite? } | { ok, info } | Move file or folder | | app-storage:create-folder | { relativePath } | { ok, info } | Create new folder | | app-storage:exists | { relativePath } | { ok, exists, info? } | Check if path exists | | app-storage:open | { relativePath } | { ok } | Open with system app (disabled unless allowShellOpen: true) | | app-storage:open-in-explorer | { relativePath } | { ok } | Open in Windows Explorer (disabled unless allowOpenInExplorer: true) | | app-storage:get-info | { relativePath } | { ok, info } | Get detailed file/folder info | | app-storage:get-folder-structure | — | { ok, structure } | Get known folder structure |

Item Info Shape

{
  name: 'document.pdf',
  path: 'Platform/Shared/RMA/Active/RMA-2024-001/document.pdf',
  isDirectory: false,
  size: 245000,           // bytes (null for folders)
  modified: '2024-02-24T10:30:00.000Z',
  created: '2024-02-20T08:00:00.000Z',
  extension: 'pdf',       // null for folders
  // For directories only:
  itemCount: 5,           // total items
  folderCount: 2,         // subfolders
  fileCount: 3            // files
}

Frontend Usage

<!-- Include the CSS -->
<link rel="stylesheet" href="path/to/app-storage.css">

<!-- Include Font Awesome (required for icons) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">

<!-- Include the JS -->
<script src="path/to/app-storage.js"></script>
// Create an instance with FULL control
const explorer = new AppStorageExplorer(document.getElementById('explorer-container'), {
  // ══════════════════════════════════════════════════════════════════
  // REQUIRED
  // ══════════════════════════════════════════════════════════════════
  channelPrefix: 'app-storage',
  ipcInvoke: window.electron.ipcRenderer.invoke,

  // ══════════════════════════════════════════════════════════════════
  // NAVIGATION
  // ══════════════════════════════════════════════════════════════════
  initialPath: 'Platform/Shared/RMA',
  rootLabel: 'App-Storage',
  
  // ══════════════════════════════════════════════════════════════════
  // PERMISSIONS — Control what users can do
  // ══════════════════════════════════════════════════════════════════
  allowUpload: true,
  allowDelete: true,
  allowRename: true,
  allowCreateFolder: true,
  allowCopy: true,
  allowMove: true,
  allowDownload: true,
  allowCreateFile: true,        // Allow creating new files
  allowFavorites: true,         // Allow adding/removing favorites
  allowProperties: true,        // Allow viewing properties
  allowOpenFile: false,         // Allow opening files with system app (disabled by default)

  // ══════════════════════════════════════════════════════════════════
  // DISPLAY — Control what UI elements are shown
  // ══════════════════════════════════════════════════════════════════
  viewMode: 'details',          // 'list', 'grid', or 'details'
  showHidden: false,
  showSidebar: true,
  showToolbar: true,
  showStatusBar: true,
  showBreadcrumb: true,
  showFavorites: true,          // Show favorites section in sidebar
  showFolderTree: true,         // Show folder tree in sidebar
  
  // ══════════════════════════════════════════════════════════════════
  // CONTEXT MENUS — Full control over right-click menus
  // ══════════════════════════════════════════════════════════════════
  enableContextMenu: true,           // Enable right-click menus at all
  enableEmptySpaceContextMenu: true, // Enable right-click on empty space
  enableItemContextMenu: true,       // Enable right-click on items
  
  // Custom context menu builders (return array of menu items, or null for default)
  buildItemContextMenu: (explorer, item) => {
    // Return custom menu items or null for default
    return [
      { icon: 'fa-eye', label: 'Preview', action: () => console.log('Preview', item) },
      { separator: true },
      { icon: 'fa-copy', label: 'Copy', action: () => explorer.copySelected() }
    ];
  },
  buildEmptySpaceContextMenu: (explorer) => null, // Use default
  
  // ══════════════════════════════════════════════════════════════════
  // FILE FILTERING — Control what files are shown
  // ══════════════════════════════════════════════════════════════════
  allowedExtensions: ['pdf', 'docx', 'xlsx'],  // Only show these (null = all)
  blockedExtensions: ['exe', 'bat', 'cmd'],    // Hide these (null = none)
  minFileSize: 1024,                           // Min 1KB
  maxFileSize: 52428800,                       // Max 50MB
  minModifiedDate: new Date('2024-01-01'),     // Only files after this date
  maxModifiedDate: null,                       // No max date
  
  // Custom filter function
  filterItem: (item) => {
    // Return true to show, false to hide
    return !item.name.startsWith('.');
  },
  
  // ══════════════════════════════════════════════════════════════════
  // FILE TEMPLATES — Control what file types can be created
  // ══════════════════════════════════════════════════════════════════
  // null = use defaults, array = custom templates, false = disable
  fileTemplates: [
    { type: 'folder', label: 'Folder', icon: 'fa-folder-plus', extension: null },
    { type: 'separator' },
    { type: 'txt', label: 'Text File', icon: 'fa-file-alt', extension: 'txt', content: '' },
    { type: 'xlsx', label: 'Excel', icon: 'fa-file-excel', extension: 'xlsx', content: null }
  ],
  
  // ══════════════════════════════════════════════════════════════════
  // TOOLBAR — Control which toolbar buttons are shown
  // ══════════════════════════════════════════════════════════════════
  toolbarButtons: {
    back: true,
    forward: true,
    up: true,
    refresh: true,
    newFolder: true,
    upload: true,
    copy: true,
    cut: true,
    paste: true,
    delete: true,
    viewList: true,
    viewGrid: true,
    viewDetails: true
  },
  
  // Custom toolbar renderer
  renderToolbar: (explorer) => null, // Return HTMLElement or null for default
  
  // ══════════════════════════════════════════════════════════════════
  // COLUMNS — Control which columns are shown in details view
  // ══════════════════════════════════════════════════════════════════
  columns: {
    icon: true,
    name: true,
    modified: true,
    type: true,
    size: true
  },
  
  // Custom columns
  customColumns: [
    { key: 'owner', label: 'Owner', render: (item) => item.owner || 'Unknown' }
  ],
  
  // ══════════════════════════════════════════════════════════════════
  // CALLBACKS — Hook into every action (return false to prevent)
  // ══════════════════════════════════════════════════════════════════
  onNavigate: (path, items) => console.log('Navigated to:', path),
  onSelect: (selectedItems) => console.log('Selected:', selectedItems),
  onOpen: (item) => { /* return false to prevent */ },
  onError: (error) => console.error('Error:', error),
  onContextMenu: (event, item) => { /* return false to prevent */ },
  onUpload: (files) => { /* return false to prevent */ },
  onDelete: (items) => { /* return false to prevent */ },
  onRename: (item, newName) => { /* return false to prevent */ },
  onCopy: (items) => {},
  onCut: (items) => {},
  onPaste: (items, destPath) => { /* return false to prevent */ },
  onCreateFolder: (name) => { /* return false to prevent */ },
  onCreateFile: (template, name) => { /* return false to prevent */ },
  onDownload: (item) => { /* return false to prevent */ },
  onFavoriteAdd: (path, label) => { /* return false to prevent */ },
  onFavoriteRemove: (path) => { /* return false to prevent */ },
  onViewChange: (viewMode) => {},
  onSortChange: (column, direction) => {},
  
  // ══════════════════════════════════════════════════════════════════
  // CUSTOM RENDERERS — Override how things are displayed
  // ══════════════════════════════════════════════════════════════════
  renderItem: (item, explorer) => null,        // Custom item renderer
  renderIcon: (item) => null,                  // Custom icon: return class or HTML
  renderSidebar: (explorer) => null,           // Custom sidebar
  renderStatusBar: (items, selected) => null,  // Custom status bar text
  renderBreadcrumb: (explorer) => null,        // Custom breadcrumb
  renderEmptyState: () => null,                // Custom empty folder message
  renderLoadingState: () => null,              // Custom loading indicator
  renderErrorState: (error) => null,           // Custom error display
  
  // ══════════════════════════════════════════════════════════════════
  // LOCALIZATION — Customize all text labels
  // ══════════════════════════════════════════════════════════════════
  labels: {
    back: 'Back',
    forward: 'Forward',
    up: 'Up',
    refresh: 'Refresh',
    newFolder: 'New Folder',
    upload: 'Upload',
    copy: 'Copy',
    cut: 'Cut',
    paste: 'Paste',
    delete: 'Delete',
    open: 'Open',
    openWith: 'Open With...',
    download: 'Download',
    rename: 'Rename',
    properties: 'Properties',
    addToFavorites: 'Add to Favorites',
    removeFromFavorites: 'Remove from Favorites',
    selectAll: 'Select All',
    view: 'View',
    sortBy: 'Sort by',
    new: 'New',
    listView: 'List View',
    gridView: 'Grid View',
    detailsView: 'Details View',
    sortName: 'Name',
    sortModified: 'Date Modified',
    sortType: 'Type',
    sortSize: 'Size',
    loading: 'Loading...',
    emptyFolder: 'This folder is empty',
    favorites: 'Favorites',
    folders: 'Folders'
  }
});

Public Methods

// Navigation
explorer.navigate('Platform/Apps/OfficeHQ');
explorer.goBack();
explorer.goForward();
explorer.goUp();
explorer.refresh();

// Selection
explorer.selectAll();
explorer.clearSelection();
const selected = explorer.getSelectedItems();

// View
explorer.setViewMode('grid');  // 'list', 'grid', 'details'

// File Operations
await explorer.createFolder('New Folder');
await explorer.deleteSelected();
await explorer.renameItem('old/path', 'newName');
explorer.copySelected();
explorer.cutSelected();
await explorer.paste();
await explorer.uploadFiles(fileList);
await explorer.downloadFile('path/to/file.pdf');
await explorer.openFile('path/to/file.pdf');
await explorer.openInExplorer();

// Cleanup
explorer.destroy();

Theming

Override CSS variables to match your app's theme:

.ase-explorer {
  /* Colors */
  --ase-primary: #0078d4;
  --ase-primary-hover: #106ebe;
  --ase-primary-light: #e5f3ff;
  --ase-danger: #d13438;
  --ase-success: #107c10;
  
  /* Backgrounds */
  --ase-bg: #ffffff;
  --ase-bg-sidebar: #f3f3f3;
  --ase-bg-toolbar: #f9f9f9;
  --ase-bg-hover: #f5f5f5;
  --ase-bg-selected: #cce8ff;
  
  /* Borders */
  --ase-border: #e1e1e1;
  --ase-border-radius: 4px;
  
  /* Text */
  --ase-text: #1a1a1a;
  --ase-text-muted: #666666;
  
  /* Typography */
  --ase-font: 'Segoe UI', sans-serif;
  --ase-font-size: 13px;
  
  /* Sizing */
  --ase-sidebar-width: 220px;
  --ase-toolbar-height: 40px;
  --ase-row-height: 28px;
  --ase-grid-item-size: 80px;
}

Example: Office-HQ Theme (Gold)

.ase-explorer {
  --ase-primary: #c9a227;
  --ase-primary-hover: #b8922a;
  --ase-primary-light: #faf8f0;
  --ase-bg-sidebar: #faf8f0;
  --ase-border: #e0d5b8;
}

Example: Tech-Portal Theme (Dark)

.ase-explorer {
  --ase-primary: #10b981;
  --ase-bg: #1a1a2e;
  --ase-bg-sidebar: #16162a;
  --ase-bg-toolbar: #1e1e3f;
  --ase-bg-hover: #2a2a4a;
  --ase-bg-selected: #10b98133;
  --ase-border: #3a3a5a;
  --ase-text: #e0e0e0;
  --ase-text-muted: #888888;
}

Features To Implement

✅ Implemented (Core)

  • [x] Backend IPC API with connect-per-operation pattern
  • [x] List directory contents
  • [x] Read/write files (base64)
  • [x] Delete files and folders
  • [x] Rename files and folders
  • [x] Copy/move files and folders
  • [x] Create folders
  • [x] Check if path exists
  • [x] Open file with system app
  • [x] Get file/folder info
  • [x] Folder structure metadata
  • [x] Restricted paths support
  • [x] Frontend Explorer UI
  • [x] Sidebar tree view (resizable)
  • [x] Breadcrumb navigation
  • [x] Details/Grid/List view modes
  • [x] Toolbar with actions
  • [x] Status bar
  • [x] Selection (single, multi with Ctrl)
  • [x] Context menu (right-click on items)
  • [x] Context menu (right-click on empty space)
  • [x] Drag-and-drop upload
  • [x] Navigation history (back/forward)
  • [x] Clipboard (copy/cut/paste)
  • [x] Column sorting — Click column headers to sort
  • [x] Favorites/Quick Access — Pin folders with persistence
  • [x] Create new files — Text, Markdown, Word, Excel, PowerPoint, HTML, CSS, JS, JSON, XML, CSV
  • [x] Properties dialog — View file/folder properties
  • [x] File filtering — By extension, size, date, custom function
  • [x] Full customization — Every component, menu, label is configurable
  • [x] Custom context menus — Build your own or extend defaults
  • [x] Custom renderers — Override any UI component
  • [x] Localization — All labels customizable
  • [x] Callbacks — Hook into every action with ability to prevent

🔲 To Implement

  • [ ] Search — Search files by name within current folder or recursively
  • [ ] Preview pane — Preview images, PDFs, text files in sidebar
  • [ ] Keyboard shortcuts — Delete, F2 rename, Ctrl+C/X/V, Enter to open
  • [ ] Drag-and-drop move — Drag items to move within explorer
  • [ ] Multi-select with Shift — Range selection
  • [ ] Column resizing — Drag column borders
  • [ ] Recent files — Show recently accessed files
  • [ ] Address bar — Editable path input
  • [ ] Compress/Extract — ZIP operations
  • [ ] File preview thumbnails — Show image thumbnails in grid view
  • [ ] Progress indicator — Show progress for large operations
  • [ ] Undo/Redo — Undo delete, rename, move operations
  • [ ] Dual-pane mode — Two explorers side by side

License

MIT © Tacel Ltd