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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@arimadata/resource-manager

v0.1.6

Published

A React resource manager with event-driven architecture and keyboard shortcuts. Designed to resemble Dropbox, it provides a complete interface for managing files and folders with built-in hooks for database persistence.

Readme

📦️ Resource Manager

✨ Features

  • Event-Driven Architecture: Clean separation between UI and business logic with an event broker system
  • Database Integration: Built-in hooks for persisting changes with optional UI locking during API calls
  • Keyboard Shortcuts: Full keyboard navigation and shortcuts for all operations
  • Dropbox-Like Interface: Familiar file management experience with breadcrumbs, context menus, and drag-drop
  • Resource Management: Handle files, folders, and custom resources with flexible data structures
  • Multi-Selection: Select multiple items with Ctrl+Click and Shift+Click for bulk operations
  • Context Menus: Right-click context menus with customizable actions
  • Inline Editing: Rename files and create folders with inline editing
  • Customizable & Sortable Headers: Add column headers (e.g., Description, Last Modified, Owner) with full rendering and sorting control
  • Customizable Toolbar: Extend existing toolbar with custom features (e.g Search)

🚀 Installation

npm i @arimadata/resource-manager

💻 Basic Usage

import { useState } from "react";
import { ResourceManager } from "@arimadata/resource-manager";

const headers = [
  {
    columnName: 'Model Name',
    getValue: (item) => item.name || 'None',
    isNameColumn: true,
  },

  {
    columnName: 'Description',
    getValue: (item) => {
      const description =
        item.itemType === 'folder' ? '--' : item.resource?.description;
      return description || 'None';
    },
  },
  {
    columnName: 'Last Modified',
    getValue: (item) => {
      const updatedAt =
        item.itemType === 'folder'
          ? new Date(item.updatedAt).toLocaleString('en-US')
          : new Date(item.resource?.updatedAt).toLocaleString('en-US');
      return updatedAt || 'Unknown';
    },
    sortAccessor: (v) => new Date(v).getTime(),
  },
  {
    columnName: 'Owner',
    getValue: (item) => {
      const pk =
        item.itemType === 'folder' ? item.scopePk : item.resource?.userPk;
      return pk || 'None';
    },
  },
];

function App() {
  const [isLoading, setIsLoading] = useState(false)
  const [items, setItems] = useState([
    {
      pk: "1",
      name: "Documents",
      itemType: "folder",
      iconName: "BsFolderFill",
      parentPk: null,
      updatedAt: "2025-09-04T15:38:24.358281Z",
    },
    {
      pk: "2",
      name: "Main Report",
      itemType: "resource",
      iconName: "BsFileEarmarkFill"
      scopePk: '[email protected]'
      parentPk: "1",
      updatedAt: "2025-09-04T15:38:24.358281Z",
      resourceType: "report",
      resource: {
        createdAt: "2025-09-04T15:39:24.358281Z",
        updatedAt: "2025-09-04T15:38:24.358281Z",
        description: "Test Description",
        name: "Main Report",
        pk: "6250780034072576",
        userPk: "[email protected]",
        tabs: [],
      },
    },
  ]);

const handleCreateFolder = (data, lock) => {
    // Optional: Lock UI during API call
    const release = lock();

    createFolderAPI(data)
      .then((newFolder) => {
        setIsLoading(true)
        setItems((prev) => [...prev, { ...newFolder, iconName: "BsFolderFill" }]);
      })
      .finally(() => {
        release(); // Unlock UI
        setIsLoading(false)
      });
  };

  return (
    <ResourceManager
      isLoading={isLoading}
      headers={headers}
      items={items}
      onCreateFolder={handleCreateFolder}
      onDelete={(items, lock) => {
        /* Handle delete */
      }}
      onRename={(item, lock) => {
        /* Handle rename */
      }}
      // ... other handlers
    />
  );
}

📂 Data Structure

  • Each item in the items array follows this structure:
interface ResourceManagerItem<T extends object = object> {
  pk: string; // Primary key
  name: string; // Display name
  itemType: "folder" | "resource"; // Type
  iconName: string; // Icon name
  isFavorited?: boolean; // Favorite status
  scope: string; // External resource reference
  scopePk: string; // External resource reference
  createdAt: string; // ISO 8601 timestamp
  updatedAt: string; // ISO 8601 timestamp
  resource?: T; // External resource reference
  resourcePk?: string; // External resource reference
  resourceType: "mmm" | "report" | "audience"; // Type of resource
  parentPk?: string; // Parent item key (null for root)

  // Auto-computed fields (added by ResourceManager)
  isDirectory: boolean; // Computed from itemType
  path: string[]; // Computed path array
  isEditing: boolean; // Edit state
  isTemporary: boolean; // Temporary state
}
  • Each header in the headers array follows this structure:
interface ResourceManagerHeader<T extends object> {
  columnName: string; // Column name
  getValue: (item: ResourceManagerItem<T>) => any; // Getter function to extract the value for this column from a resource/folder item
  sortAccessor?: (value: any) => any; // Accessor for sorting purposes
  isNameColumn?: boolean; // Mark this column as name column. Used for favorite button, icons and select checkbox placement
}

⚙️ Event Handlers

All event handlers receive data and an optional lock function for UI control.

| Handler | Parameters | Description | | ---------------- | ------------------ | ----------------------------------------------- | | onCreateFolder | (data, lock) | Called when creating a new folder | | onCreateItem | (data, release) | Called when creating custom items (modal event) | | onOpen | (data, lock) | Called when opening item | | onDelete | (items, lock) | Called when deleting items (modal event) | | onRename | (item, lock) | Called when renaming an item | | onCopy | (items, lock) | Called when copying items | | onCut | (items, lock) | Called when cutting items | | onPaste | (data, lock) | Called when pasting items | | onDuplicate | (data, lock) | Called when duplicating items | | onFavorite | (item, lock) | Called when toggling favorites | | onRefresh | (data, lock) | Called when refreshing | | onSelect | (items, lock) | Called when selection changes | | onShare | (items, release) | Called when sharing items (modal event) | | onPathChange | (path) | Called on path change |

Lock/Release Pattern

You can optionally lock the resource manager (UI) if you need to prevent user interaction during async operations or to ensure order of operations.

const onDelete = (items, lock) => {
  // Optional: Lock UI during operation
  const release = lock();

  deleteAPI(items)
    .then(() => {
      // Update your state
      getItems();
    })
    .finally(() => {
      release(); // Always unlock UI if locked
    });
};

Modal Events

Some events like onCreateItem and onShare are modal events that require a different pattern:

const onCreateItem = (data, release) => {
  console.log("onCreateItem -> data:", data);
  // 1. Setup modal handlers as needed with API calls if necessary
  // Make sure they call "release" when done
  const onConfirm = async (formData) => {
    await createItemAPI(formData);
    getItems();
    release();
  };
  const onCancel = () => {
    release();
  };

  // 2. Open the dialog
  setModal({
    open: true,
    Component: CreateModal,
    data,
    onConfirm,
    onCancel,
  });

  // 3. Tell the package how to close your dialog
  // Calling "release" will call this callback and unlock the package
  return () => {
    setModal(closedModal);
  };
};

⌨️ Keyboard Shortcuts

| Action | Shortcut | | ------------------ | --------------- | | New Folder | Alt + N | | Cut | Ctrl + X | | Copy | Ctrl + C | | Paste | Ctrl + V | | Duplicate | Ctrl + D | | Rename | F2 | | Delete | Del | | Select All | Ctrl + A | | Multi-select | Ctrl + Click | | Range Select | Shift + Click | | Range Expand | Shift + ↑/↓ | | Navigate Up/Down | ↑/↓ arrows | | Jump to First/Last | Home/End | | Refresh | F5 | | Clear Selection | Esc |

🎨 Props

| Prop | Type | Description | | ------------------- | ---------------------------- | -------------------------------------------------- | | items | ResourceManagerItem<T>[] | Array of items to display | | headers | ResourceManagerHeader<T>[] | Array of headers to use | | isLoading | boolean | Loading state indicator | | allowCreateFolder | boolean | Enable folder creation (default: true) | | allowCreateItem | boolean | Enable custom item creation (default: true) | | allowDelete | boolean | Enable deletion (default: true) | | allowDuplicate | boolean | Enable duplicate (default: false) | | allowRefresh | boolean | Enable refresh (default: true) | | allowRename | boolean | Enable renaming (default: true) | | allowCopy | boolean | Enable copying (default: true) | | allowCut | boolean | Enable cutting (default: true) | | allowPaste | boolean | Enable pasting (default: true) | | allowFavorite | boolean | Enable favorites (default: true) | | allowShareItem | boolean | Enable sharing (default: true) | | initialPath | string[] | Initial navigation path | | height | string \| number | Component height (default: "100%") | | width | string \| number | Component width (default: "100%") | | primaryColor | string | Primary theme color (default: "#6155b4") | | fontFamily | string | Font family (default: "Nunito Sans, sans-serif") |

🎛️ Customization

Custom Headers

Define custom column headers to display different data fields:

const headers = [
  {
    columnName: "Model Name",
    getValue: (item) => item.name || "None",
    isNameColumn: true, // This column will show icons, checkboxes, and favorite buttons
  },
  {
    columnName: "Description",
    getValue: (item) => {
      const description =
        item.itemType === "folder" ? "--" : item.resource?.description;
      return description || "None";
    },
  },
  {
    columnName: "Last Modified",
    getValue: (item) => {
      const updatedAt =
        item.itemType === "folder"
          ? new Date(item.updatedAt).toLocaleString("en-US")
          : new Date(item.resource?.updatedAt).toLocaleString("en-US");
      return updatedAt || "Unknown";
    },
    sortAccessor: (v) => new Date(v).getTime(), // Custom sorting logic
  },
];

<ResourceManager headers={headers} /* ... other props */ />;

Header Properties:

  • columnName: Display name for the column header
  • getValue: Function that extracts the display value from an item
  • isNameColumn: Boolean to mark this column as the main name column (shows icons, checkboxes, favorites)
  • sortAccessor: Optional function to transform values for sorting purposes

Custom Toolbar

Add custom elements to the toolbar area:

const customToolbar = (
  <>
    <input type="text" placeholder="Type to search..." />
    <button onClick={() => console.log("Searching...")}>Search</button>
  </>
);

<ResourceManager
  renderCustomToolbar={customToolbar}
  // ... other props
/>;

The custom toolbar content will be rendered alongside the default toolbar buttons.

Custom Context Menu Items

Add custom right-click context menu items for both empty space and selected items:

const customSelectCtxItems = [
  {
    title: "Dev Tools",
    icon: "BsTools",
    divider: false,
    hidden: (item) => item?.itemType === "folder", // Action is hidden on folders
    children: [
      {
        title: "Make Dev Copy",
        icon: "BsBasket",
        onClick: (item) => console.log("Make Dev Copy", item),
        hidden: false,
      },
      {
        title: "Favorited Only",
        icon: "BsBullseye",
        onClick: (item) => console.log("Favorited Item", item),
        hidden: (item) => !item?.isFavorited, // Action can be performed on non-favorited items
      },
    ],
  },
  {
    title: "Build MMM",
    icon: "BsBuilding",
    onClick: (item) => console.log("Build MMM", item),
    hidden: (item) => item?.itemType === "folder", // Action is hidden on folders
    divider: true,
  },
];

<ResourceManager
  customSelectCtxItems={customSelectCtxItems}
  customEmptySelectCtxItems={customEmptyItems}
  // ... other props
/>;

Attributes:

  • title: Display text
  • icon: Icon name (string) or React component
  • onClick: Callback function receiving the context item
  • hidden: Boolean or function to conditionally hide items
  • children: Array of sub-menu items
  • divider: Add separator after item

🏗️ Architecture

The ResourceManager uses an event-driven architecture with:

  • Event Broker: Manages state transitions and prevents conflicting operations
  • Context Providers: Handle selection, navigation, clipboard, and items state
  • Keyboard Integration: Global keyboard shortcuts with conflict prevention
  • Lock System: Optional UI locking during async operations

Development Setup

Frontend

cd frontend
npm i
npm run dev

Backend (Example implementation)

python app.py

The example backend uses MongoDB for persistence. See backend/.env.example for configuration.

📋 TODO

Known Issues & Improvements

  • Implement virtualized list lib to optimize large amounts of items: For better optimization of large datasets recommended to use virtualized list libs e.g. react-window, react-virtualized

  • Folder Deletion Behavior: When deleting a folder, all child items become root-level items, which can lead to duplicate file names. Functionally okay, but may not be intended behavior.

  • Browser Navigation: Allow users to go "back" using browser back button or mouse back button for better navigation experience.