export default function()

in src/app/stores/action_handlers/document.ts [26:435]


export default function (REG: ActionHandlerRegistry<AppStore, Actions.Action>) {
  // eslint-disable-next-line
  REG.add(Actions.Export, function (action) {
    (async () => {
      // Export as vector graphics
      if (action.type == "svg") {
        const svg = await this.renderLocalSVG();
        const blob = new Blob([svg], { type: "image/svg;charset=utf-8" });
        saveAs(blob, "charticulator.svg", true);
      }
      // Export as bitmaps
      if (action.type == "png" || action.type == "jpeg") {
        const svgDataURL = stringToDataURL(
          "image/svg+xml",
          await this.renderLocalSVG()
        );
        renderDataURLToPNG(svgDataURL, {
          mode: "scale",
          scale: action.options.scale || 2,
          background: "#ffffff",
        }).then((png) => {
          png.toBlob((blob) => {
            saveAs(
              blob,
              "charticulator." + (action.type == "png" ? "png" : "jpg"),
              true
            );
          }, "image/" + action.type);
        });
      }
      // Export as interactive HTML
      if (action.type == "html") {
        const containerScriptText = await (
          await fetch(getConfig().ContainerURL)
        ).text();

        const template = deepClone(this.buildChartTemplate());

        const htmlString = `
          <!DOCTYPE html>
          <html>
          <head>
            <meta charset="utf-8" />
            <title>Charticulator HTML Export</title>
            <script type="text/javascript">${containerScriptText}</script>
            <style type="text/css">
              #container {
                display: block;
                position: absolute;
                left: 0; right: 0; top: 0; bottom: 0;
              }
            </style>
          </head>
          <body>
            <div id="container"></div>
            <script type="text/javascript">
              CharticulatorContainer.initialize().then(function() {
                const currentChart = ${JSON.stringify(this.chart)};
                const chartState = ${JSON.stringify(this.chartState)};
                const dataset = ${JSON.stringify(this.dataset)};
                const template = ${JSON.stringify(template)};
                const chartTemplate = new CharticulatorContainer.ChartTemplate(
                  template
                );
                chartTemplate.reset();

                const defaultTable = dataset.tables[0];
                const columns = defaultTable.columns;
                chartTemplate.assignTable(defaultTable.name, defaultTable.name);
                for (const column of columns) {
                  chartTemplate.assignColumn(
                    defaultTable.name,
                    column.name,
                    column.name
                  );
                }

                // links table
                const linksTable = dataset.tables[1];
                const links = linksTable && (linksTable.columns);
                if (links) {
                  chartTemplate.assignTable(linksTable.name, linksTable.name);
                  for (const column of links) {
                    chartTemplate.assignColumn(
                      linksTable.name,
                      column.name,
                      column.name
                    );
                  }
                }
                const instance = chartTemplate.instantiate(dataset);

                const { chart } = instance;

                for (const property of template.properties) {
                  if (property.target.attribute) {
                    CharticulatorContainer.ChartTemplate.SetChartAttributeMapping(
                      chart,
                      property.objectID,
                      property.target.attribute,
                      {
                        type: "value",
                        value: property.default,
                      }
                    );
                  }
                  
                }

                const container = new CharticulatorContainer.ChartContainer({ chart: chart }, dataset);
                const width = document.getElementById("container").getBoundingClientRect().width;
                const height = document.getElementById("container").getBoundingClientRect().height;
                container.mount("container", width, height);
                window.addEventListener("resize", function() {
                  container.resize(
                    document.getElementById("container").getBoundingClientRect().width,
                    document.getElementById("container").getBoundingClientRect().height
                  );
                });
              });
            </script>
          </body>
          </html>
        `;
        const blob = new Blob([htmlString]);
        saveAs(blob, "charticulator.html", true);
      }
    })();
  });

  REG.add(Actions.ExportTemplate, function (this, action) {
    action.target.generate(action.properties).then((base64) => {
      const byteCharacters = atob(base64);
      const byteNumbers = new Array(byteCharacters.length);
      for (let i = 0; i < byteCharacters.length; i++) {
        byteNumbers[i] = byteCharacters.charCodeAt(i);
      }
      const byteArray = new Uint8Array(byteNumbers);

      const blob = new Blob([byteArray], {
        type: "application/x-binary",
      });
      FileSaver.saveAs(
        blob,
        action.target.getFileName
          ? action.target.getFileName(action.properties)
          : "charticulator." + action.target.getFileExtension(action.properties)
      );
    });
  });

  REG.add(Actions.Save, function (action) {
    this.backendSaveChart()
      .then(() => {
        if (action.onFinish) {
          action.onFinish();
        }
      })
      .catch((error: Error) => {
        if (action.onFinish) {
          action.onFinish(error);
        }
      });
  });

  REG.add(Actions.SaveAs, function (action) {
    this.backendSaveChartAs(action.saveAs)
      .then(() => {
        if (action.onFinish) {
          action.onFinish();
        }
      })
      .catch((error: Error) => {
        if (action.onFinish) {
          action.onFinish(error);
        }
      });
  });

  REG.add(Actions.Open, function (action) {
    this.backendOpenChart(action.id)
      .then(() => {
        if (action.onFinish) {
          action.onFinish();
        }
      })
      .catch((error: Error) => {
        if (action.onFinish) {
          action.onFinish(error);
        }
      });
  });

  REG.add(Actions.Load, function (action) {
    this.historyManager.clear();
    const state = new Migrator().migrate(
      action.projectData,
      CHARTICULATOR_PACKAGE.version
    );
    this.loadState(state);
  });

  REG.add(Actions.ImportDataset, function (action) {
    this.currentChartID = null;
    this.dataset = action.dataset;
    this.originDataset = deepClone(this.dataset);
    this.historyManager.clear();
    this.newChartEmpty();
    this.emit(AppStore.EVENT_DATASET);
    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.ImportChartAndDataset, function (action) {
    this.currentChartID = null;
    this.currentSelection = null;
    this.dataset = action.dataset;
    this.originDataset = deepClone(this.dataset);

    this.chart = action.specification;

    this.chartManager = new Prototypes.ChartStateManager(
      this.chart,
      this.dataset,
      null,
      {},
      {},
      action.originSpecification
        ? deepClone(action.originSpecification)
        : this.chartManager.getOriginChart()
    );
    this.chartManager.onUpdate(() => {
      this.solveConstraintsAndUpdateGraphics();
    });
    this.chartState = this.chartManager.chartState;

    this.emit(AppStore.EVENT_DATASET);
    this.emit(AppStore.EVENT_SELECTION);
    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.UpdatePlotSegments, function () {
    this.updatePlotSegments();
    this.solveConstraintsAndUpdateGraphics();
    this.emit(AppStore.EVENT_DATASET);
    this.emit(AppStore.EVENT_SELECTION);
  });

  REG.add(Actions.UpdateDataAxis, function () {
    this.updateDataAxes();
    this.updateScales();
    this.solveConstraintsAndUpdateGraphics();
    this.emit(AppStore.EVENT_DATASET);
    this.emit(AppStore.EVENT_SELECTION);
  });

  REG.add(Actions.ReplaceDataset, function (action) {
    this.currentChartID = null;
    this.currentSelection = null;
    this.dataset = action.dataset;
    this.originDataset = deepClone(this.dataset);

    this.chartManager = new Prototypes.ChartStateManager(
      this.chart,
      this.dataset,
      null,
      {},
      {},
      action.keepState ? this.chartManager.getOriginChart() : null
    );
    this.chartManager.onUpdate(() => {
      this.solveConstraintsAndUpdateGraphics();
    });
    this.chartState = this.chartManager.chartState;
    this.updatePlotSegments();
    this.updateDataAxes();
    this.updateScales();
    this.solveConstraintsAndUpdateGraphics();
    this.emit(AppStore.EVENT_DATASET);
    this.emit(AppStore.EVENT_SELECTION);
  });

  REG.add(Actions.ConvertColumnDataType, function (action) {
    this.saveHistory();

    const table = this.dataset.tables.find(
      (table) => table.name === action.tableName
    );
    if (!table) {
      return;
    }

    const column = table.columns.find(
      (column) => column.name === action.column
    );
    if (!column) {
      return;
    }

    const originTable = this.originDataset.tables.find(
      (table) => table.name === action.tableName
    );
    let originColumn = originTable.columns.find(
      (column) => column.name === action.column
    );
    if (originColumn.metadata.rawColumnName) {
      originColumn = originTable.columns.find(
        (column) => column.name === originColumn.metadata.rawColumnName
      );
    }

    const result = convertColumns(table, column, originTable, action.type);
    if (result) {
      this.messageState.set("columnConvertError", result);
    }

    this.updatePlotSegments();
    this.updateDataAxes();
    this.updateScales();
    this.solveConstraintsAndUpdateGraphics();
    this.emit(AppStore.EVENT_DATASET);
    this.emit(AppStore.EVENT_SELECTION);
  });

  REG.add(Actions.Undo, function () {
    const state = this.historyManager.undo(this.saveDecoupledState());
    if (state) {
      const ss = this.saveSelectionState();
      this.loadState(state);
      this.loadSelectionState(ss);
    }
  });

  REG.add(Actions.Redo, function () {
    const state = this.historyManager.redo(this.saveDecoupledState());
    if (state) {
      const ss = this.saveSelectionState();
      this.loadState(state);
      this.loadSelectionState(ss);
    }
  });

  REG.add(Actions.Reset, function () {
    this.saveHistory();

    this.currentSelection = null;
    this.currentTool = null;
    this.emit(AppStore.EVENT_SELECTION);
    this.emit(AppStore.EVENT_CURRENT_TOOL);

    this.newChartEmpty();

    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.OpenNestedEditor, function ({ options, object, property }) {
    this.emit(AppStore.EVENT_OPEN_NESTED_EDITOR, options, object, property);
    const editorID = uniqueID();
    const newWindow = window.open(
      "index.html#!nestedEditor=" + editorID,
      "nested_chart_" + options.specification._id
    );
    const listener = (e: MessageEvent) => {
      if (e.origin == document.location.origin) {
        const data = <NestedEditorMessage>e.data;
        if (data.id == editorID) {
          switch (data.type) {
            case NestedEditorMessageType.Initialized:
              {
                const builder = new ChartTemplateBuilder(
                  options.specification,
                  options.dataset,
                  this.chartManager,
                  CHARTICULATOR_PACKAGE.version
                );

                const template = builder.build();
                newWindow.postMessage(
                  {
                    id: editorID,
                    type: NestedEditorEventType.Load,
                    specification: options.specification,
                    dataset: options.dataset,
                    width: options.width,
                    template,
                    height: options.height,
                    filterCondition: options.filterCondition,
                  },
                  document.location.origin
                );
              }
              break;
            case NestedEditorMessageType.Save:
              {
                this.setProperty({
                  object,
                  property: property.property,
                  field: property.field,
                  value: data.specification,
                  noUpdateState: property.noUpdateState,
                  noComputeLayout: property.noComputeLayout,
                });
              }
              break;
          }
        }
      }
    };
    window.addEventListener("message", listener);
  });
}