All files / src/controller parameters.js

100% Statements 42/42
100% Branches 18/18
100% Functions 10/10
100% Lines 40/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92                    3x 3x 3x 2x 2x   1x     3x 3x                 1x     1x                   4x 4x 4x 5x 5x 5x 5x 5x 9x 1x 1x   8x 3x   5x 5x 8x         4x         5x 1x   4x 2x 2x   2x         8x 8x 8x 10x 10x 10x     8x    
import { downloadBlob } from "../view/download.js";
 
/**
 * Reads a parameter JSON file and returns the parsed content.
 * Restores composition parameters (theta) from a previously exported JSON file.
 * @param {File|Blob} file - Source containing serialized parameters (theta) as JSON text.
 * @return {Promise<Object>} Parsed parameter object (theta).
 * @throws {Error} If the file cannot be read or the content is not valid JSON.
 */
export function importParameters(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = (e) => {
      try {
        resolve(JSON.parse(e.target.result));
      } catch {
        reject(new Error("Could not parse the JSON file."));
      }
    };
    reader.onerror = () => reject(new Error("Could not read the file."));
    reader.readAsText(file);
  });
}
 
/**
 * Exports the composition parameters (theta) as a JSON file download.
 * @param {Object} parameters - Top-level parameters (theta) to serialize as JSON.
 */
export function exportParameters(parameters) {
  const blob = new Blob([JSON.stringify(parameters, null, 2)], {
    type: "application/json",
  });
  downloadBlob(blob, "parameters.json");
}
 
/**
 * Builds the initial composition parameters (theta) for a list of steps by calling schema() on each.
 * @param {Step[]} steps - Step instances.
 * @return {Promise<{steps: Step[], schemas: Object<string, Object[]>, parameters: Object}>}
 *  Step instances unchanged plus a schemas-by-step.id map and the parameters object.
 */
export async function buildParameters(steps) {
  const schemas = {};
  const parameters = {};
  for (const step of steps) {
    const schema = await step.schema();
    schemas[step.id] = schema;
    parameters[step.id] = {};
    const filled = parameters[step.id];
    for (const param of schema) {
      if (param.type === "checkboxes") {
        for (const cb of param.items) {
          filled[cb.name] = cb.default;
        }
      } else if (param.type !== "nested") {
        filled[param.name] = param.default;
      } else {
        const count = resolveNestedCount(param, schema, parameters);
        filled[param.name] = Array.from({ length: count }, (_, i) =>
          buildNestedItem(param, i),
        );
      }
    }
  }
  return { steps, schemas, parameters };
}
 
/** @private Resolves the count for a nested-parameter array (defaults length, counter ref, or fallback 1). */
function resolveNestedCount(param, schema, parameters) {
  if (param.defaults?.length) {
    return param.defaults.length;
  }
  if (param.counter.includes(".")) {
    const [stepId, paramName] = param.counter.split(".");
    return parameters[stepId]?.[paramName] || 1;
  }
  return schema.find((p) => p.name === param.counter)?.max || 1;
}
 
/** @private Builds one nested-array item: merges perItem overrides over template defaults. */
function buildNestedItem(param, i) {
  const perItem = param.defaults?.[i];
  const item = perItem ? { ...perItem } : {};
  for (const sub of param.template) {
    const fields = sub.type === "checkboxes" ? sub.items : [sub];
    for (const f of fields) {
      item[f.name] = perItem?.[f.name] ?? f.default;
    }
  }
  return item;
}