Source: editor/extensions/ext-opensave/ext-opensave.js

/* globals seConfirm */
/**
 * @file ext-opensave.js
 *
 * @license MIT
 *
 * @copyright 2020 OptimistikSAS
 *
 */

/**
   * @type {module:svgcanvas.EventHandler}
   * @param {external:Window} wind
   * @param {module:svgcanvas.SvgCanvas#event:saved} svg The SVG source
   * @listens module:svgcanvas.SvgCanvas#event:saved
   * @returns {void}
   */
import { fileOpen, fileSave } from 'browser-fs-access';

const name = "opensave";
let handle = null;

const loadExtensionTranslation = async function (svgEditor) {
  let translationModule;
  const lang = svgEditor.configObj.pref('lang');
  try {
    // eslint-disable-next-line no-unsanitized/method
    translationModule = await import(`./locale/${lang}.js`);
  } catch (_error) {
    // eslint-disable-next-line no-console
    console.warn(`Missing translation (${lang}) for ${name} - using 'en'`);
    // eslint-disable-next-line no-unsanitized/method
    translationModule = await import(`./locale/en.js`);
  }
  svgEditor.i18next.addResourceBundle(lang, 'translation', translationModule.default, true, true);
};

export default {
  name,
  async init(_S) {
    const svgEditor = this;
    const { svgCanvas } = svgEditor;
    const { $id } = svgCanvas;
    await loadExtensionTranslation(svgEditor);

    /**
     * @fires module:svgcanvas.SvgCanvas#event:ext_onNewDocument
     * @returns {void}
     */
    const clickClear = async function () {
      const [ x, y ] = svgEditor.configObj.curConfig.dimensions;
      const ok = await seConfirm(svgEditor.i18next.t('notification.QwantToClear'));
      if (ok === "Cancel") {
        return;
      }
      svgEditor.leftPanel.clickSelect();
      svgEditor.svgCanvas.clear();
      svgEditor.svgCanvas.setResolution(x, y);
      svgEditor.updateCanvas(true);
      svgEditor.zoomImage();
      svgEditor.layersPanel.populateLayers();
      svgEditor.topPanel.updateContextPanel();
      svgEditor.svgCanvas.runExtensions("onNewDocument");
    };

    /**
     * By default,  this.editor.svgCanvas.open() is a no-op. It is up to an extension
     *  mechanism (opera widget, etc.) to call `setCustomHandlers()` which
     *  will make it do something.
     * @returns {void}
     */
    const clickOpen = async function () {
      // ask user before clearing an unsaved SVG
      const response = await svgEditor.openPrep();
      if (response === 'Cancel') { return; }
      svgCanvas.clear();
      try {
        const blob = await fileOpen({
          mimeTypes: [ 'image/*' ]
        });
        const svgContent = await blob.text();
        await svgEditor.loadSvgString(svgContent);
        svgEditor.updateCanvas();
      } catch (err) {
        if (err.name !== 'AbortError') {
          return console.error(err);
        }
      }
    };

    const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
      const byteCharacters = atob(b64Data);
      const byteArrays = [];
      for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
        const slice = byteCharacters.slice(offset, offset + sliceSize);
        const byteNumbers = new Array(slice.length);
        for (let i = 0; i < slice.length; i++) {
          byteNumbers[i] = slice.charCodeAt(i);
        }
        const byteArray = new Uint8Array(byteNumbers);
        byteArrays.push(byteArray);
      }
      const blob = new Blob(byteArrays, { type: contentType });
      return blob;
    };

    /**
     *
     * @returns {void}
     */
    const clickSave = async function (type, _) {
      const $editorDialog = $id("se-svg-editor-dialog");
      const editingsource = $editorDialog.getAttribute("dialog") === "open";
      if (editingsource) {
        svgEditor.saveSourceEditor();
      } else {
        // In the future, more options can be provided here
        const saveOpts = {
          images: svgEditor.configObj.pref("img_save"),
          round_digits: 6
        };
        // remove the selected outline before serializing
        svgCanvas.clearSelection();
        // Update save options if provided
        if (saveOpts) {
          const saveOptions = svgCanvas.mergeDeep(svgCanvas.getSvgOption(), saveOpts);
          for (const [ key, value ] of Object.entries(saveOptions)) {
            svgCanvas.setSvgOption(key, value);
          }
        }
        svgCanvas.setSvgOption('apply', true);

        // no need for doctype, see https://jwatt.org/svg/authoring/#doctype-declaration
        const svg = '<?xml version="1.0"?>\n' + svgCanvas.svgCanvasToString();
        const b64Data = svgCanvas.encode64(svg);
        const blob = b64toBlob(b64Data, 'image/svg+xml');
        try {
          if(type === "save" && handle !== null) {
            const throwIfExistingHandleNotGood = false;
            handle = await fileSave(blob, {
              fileName: 'icon.svg',
              extensions: [ '.svg' ]
            }, handle, throwIfExistingHandleNotGood);
          } else {
            handle = await fileSave(blob, {
              fileName: 'icon.svg',
              extensions: [ '.svg' ]
            });
          }
        } catch (err) {
          if (err.name !== 'AbortError') {
            return console.error(err);
          }
        }
      }
    };

    return {
      name: svgEditor.i18next.t(`${name}:name`),
      // The callback should be used to load the DOM with the appropriate UI items
      callback() {
        // eslint-disable-next-line no-unsanitized/property
        const buttonTemplate = `
        <se-menu-item id="tool_clear" label="opensave.new_doc" shortcut="N" src="new.svg"></se-menu-item>`;
        svgCanvas.insertChildAtIndex($id('main_button'), buttonTemplate, 0);
        const openButtonTemplate = `<se-menu-item id="tool_open" label="opensave.open_image_doc" src="open.svg"></se-menu-item>`;
        svgCanvas.insertChildAtIndex($id('main_button'), openButtonTemplate, 1);
        const saveButtonTemplate = `<se-menu-item id="tool_save" label="opensave.save_doc" shortcut="S" src="saveImg.svg"></se-menu-item>`;
        svgCanvas.insertChildAtIndex($id('main_button'), saveButtonTemplate, 2);
        const saveAsButtonTemplate = `<se-menu-item id="tool_save_as" label="opensave.save_as_doc" src="saveImg.svg"></se-menu-item>`;
        svgCanvas.insertChildAtIndex($id('main_button'), saveAsButtonTemplate, 3);
        // handler
        $id("tool_clear").addEventListener("click", clickClear.bind(this));
        $id("tool_open").addEventListener("click", clickOpen.bind(this));
        $id("tool_save").addEventListener("click", clickSave.bind(this, "save"));
        $id("tool_save_as").addEventListener("click", clickSave.bind(this, "saveas"));
      }
    };
  }
};