Source: utils/componentLoader.js

/**
 * Loads the component catalog for the palette.
 *
 * - Local version: served by the FastAPI backend from the JSON files on disk
 *   (`GET /api/components`).
 * - Web/standalone version (no backend): the JSON files are bundled into the
 *   build at build time, so the app ships with them and makes no network call.
 *
 * @module utils/componentLoader
 */

// Component category folders (the `controls` category is provided by the UI).
const CATEGORIES = ['theorists', 'experimentalists', 'experiment_runners']

// Component JSON files bundled at build time from autora_gui/JSON/components.
// Each key is the module path, e.g.
// '../../../JSON/components/theorists/bms_regressor.json'.
const COMPONENT_MODULES = import.meta.glob('../../../JSON/components/*/*.json', {
  eager: true,
  import: 'default',
})

/**
 * Load all components, keyed by category. Tries the local backend first and
 * falls back to the JSON bundled into the build when no backend is present.
 *
 * @returns {Promise<Object.<string, Object[]>>} Components grouped by category.
 */
export async function loadComponents() {
  try {
    const res = await fetch('/api/components')
    if (res.ok) return await res.json()
  } catch {
    // No local backend (web/standalone build) — fall through to bundled JSON.
  }
  return loadBundledComponents()
}

/**
 * Group the JSON files bundled at build time by category, matching the shape
 * returned by the backend's `/api/components`.
 *
 * @returns {Object.<string, Object[]>} Components grouped by category.
 */
export function loadBundledComponents() {
  const components = { controls: [] }
  CATEGORIES.forEach((category) => {
    components[category] = []
  })

  for (const [path, data] of Object.entries(COMPONENT_MODULES)) {
    // path: '../../../JSON/components/<category>/<file>.json'
    const parts = path.split('/')
    const file = parts[parts.length - 1]
    const category = parts[parts.length - 2]
    if (!components[category]) continue
    components[category].push({ ...data, file })
  }

  CATEGORIES.forEach((category) => {
    components[category].sort((a, b) => (a.name || '').localeCompare(b.name || ''))
  })

  return components
}