Home Manual Reference Source Test

src/controller/parameters.js

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