# Source: res/LocalFileDialog.js

    /**
     * A dialog box letting the user open a local file or save data to a local file.
     */
    export class LocalFileDialog extends XUI.Dialog {
        /**
         * Displays a dialog box letting the user open a local file or
         * save specified data to a local file.
         *
         * @param {XMLEditor} xmlEditor - the XML editor hosting this dialog box.
         * @param {object} [options={}] options - dialog box options.
         * See description in {@link ResourceStorage#openResource}.
         * @param {Blob} [savedData=null] savedData - the data to be saved to
         * a file; <code>null</code> to display an open file dialog.
         * @return {Promise} A Promise containing info about the open or save file
         * or <code>null</code> if user clicked <b>Cancel</b>.
         * <dl>
         * <dt><code>file</code>
         * <dd>Opened file.
         * <dt><code>fileHandle</code>
         * <dd>Saved file handle; always <code>null</code>
         * when <code>!FSAccess.isAvailable()</code>.
         * <dt><code>uri</code>
         * <dd>Absolute "file:" URI of selected file.
         * <dt><code><i>option_name</i></code>
         * <dd>Only when a dialog box "accessory" has been specified:
         * <code>true</code> if corresponding check box is checked;
         * <code>false</code> otherwise.
         * </dl>
         * <p>On some Blink-based browsers, for example
         * <a href="https://brave.com/">Brave</a>,
         * the <a href="https://fs.spec.whatwg.org/">"File System Access API"</a>
         * is disabled by default. In order to enable it, visit
         * <code>brave://flags/#file-system-access-api</code> and change
         * this setting from "Default" to "Enabled".
         */
        static showDialog(xmlEditor, options={}, savedData=null) {
            return new Promise((resolve, reject) => {
                let dialog = new LocalFileDialog(xmlEditor, options, savedData,
                                                 resolve);
                dialog.open("center", xmlEditor);
            });
        }

        constructor(xmlEditor, options, savedData, resolve) {
            super({ title: LocalFileDialog.titleDefaulted(options, savedData),
                    movable: true, resizable: true, closeable: true,
                    template: LocalFileDialog.TEMPLATE,
                    buttons: [ { label: "Cancel", action: "cancelAction" },
                               { label: "OK", action: "okAction",
                                 default: true } ] });

            this._xmlEditor = xmlEditor;
            this._options = options;
            this._savedData = savedData;
            this._resolve = resolve;

            let pane = this.contentPane.firstElementChild;
            this._step1 = pane.querySelector(".xxe-lfd-step1");
            this._browseLabel = pane.querySelector(".xxe-lfd-browse-label");
            this._browseButton = pane.querySelector(".xxe-lfd-browse");
            this._browseButton.onclick = this.chooseFileAction.bind(this);

            this._step2 = pane.querySelector(".xxe-lfd-step2");
            this._dirPathText = pane.querySelector(".xxe-lfd-dir-path");

            let pathSepar = pane.querySelector(".xxe-lfd-path-separ");
            pathSepar.textContent = FILE_PATH_SEPARATOR;

            this._fileNameText = pane.querySelector(".xxe-lfd-file-name");
            const openMode = !this._savedData;
            if (FSAccess.isAvailable() || openMode) {
                this._fileNameText.setAttribute("readonly", "readonly");
            }

            this._optPane = pane.querySelector(".xxe-lfd-opt-pane");
            this._optLabel = pane.querySelector(".xxe-lfd-opt-label");
            this._optToggle = pane.querySelector(".xxe-lfd-opt-toggle");

            // ---

            if (openMode) {
                this._browseButton.textContent = XUI.StockIcon["folder-open"];
            } else {
                this._browseLabel.textContent = "Save file:";
                this._browseButton.textContent = XUI.StockIcon["floppy"];
            }

            // ---

            let suggestedDirPath = null;
            let suggestedFileName = null;

            let templatePath = !options.templateURI? null :
                URIUtil.uriToFilePath(options.templateURI);
            if (templatePath !== null) {
                suggestedDirPath =
                    URIUtil.pathParent(templatePath, FILE_PATH_SEPARATOR,
                                       /*trailingSepar*/ false);
                suggestedFileName =
                    URIUtil.pathBasename(templatePath, FILE_PATH_SEPARATOR);
            }

            this._suggestedFileName = null;
            if (!openMode && suggestedFileName) {
                // File chooser option: suggest a save file name.
                this._suggestedFileName = suggestedFileName;
            }

            this.rememberDirPath(suggestedDirPath, /*addDataList*/ true);
            if (!FSAccess.isAvailable()) {
                // In open mode, this._fileNameText is read-only and the
                // corresponding datalist is not useful.
                this.rememberFileName(suggestedFileName, /*addDataList*/ !openMode);
            }
            // Otherwise, this._fileNameText is always read-only and the
            // corresponding datalist is never useful.

            // ---

            if (!options.option ||
                !Array.isArray(options.option) || options.option.length !== 3) {
                this._optionName = null;
                this._optPane.style.display = "none";
            } else {
                this._optionName = options.option[0];
                this._optToggle.checked = options.option[1];
                this._optLabel.appendChild(
                    document.createTextNode(options.option[2]));
            }

            // ---

            this._openedFile = null;
            this._savedFileHandle = null;
        }

        static titleDefaulted(options, savedData) {
            if (!options.title) {
                if (!savedData) {
                    return "Open";
                } else {
                    return "Save";
                }
            } else {
                return options.title;
            }
        }

        rememberDirPath(dirPath, addDataList) {
            let valueList =
                XUI.Util.rememberDatalistItem("XXE.LocalFileDialog.lastDirPaths",
                                              dirPath);

            if (addDataList) {
                XUI.Util.attachDatalist(this._dirPathText, valueList,
                                        this.contentPane.firstElementChild);
            }
        }

        rememberFileName(fileName, addDataList) {
            let valueList =
                XUI.Util.rememberDatalistItem("XXE.LocalFileDialog.lastFileNames",
                                              fileName);

            if (addDataList) {
                XUI.Util.attachDatalist(this._fileNameText, valueList,
                                        this.contentPane.firstElementChild);
            }
        }

        chooseFileAction(event) {
            let opts = { id: "XXE_localFileDialog", multiple: false,
                         startIn: "documents", excludeAcceptAllOption: false };

            const openMode = !this._savedData;
            if (openMode) {
                LocalFileDialog.addExtensions(this._options.extensions, opts);
            } else {
                if (this._suggestedFileName !== null) {
                    opts.fileName = this._suggestedFileName;
                }
            }

            if (openMode) {
                FSAccess.fileOpen(opts)
                    .then((file) => {
                              if (file) {
                                  this._openedFile = file;
                                  this._fileNameText.value = file.name;
                                  this.proceedToStep2();
                              }
                          },
                          (error) => {
                              this.chooseFileError(error);
                          });
            } else {
                // Always interactive. Displays a dialog box.
                FSAccess.fileSave(this._savedData, opts, /*fileHandle*/ null,
                                  /*throwIfUnusableHandle*/ false)
                    .then((handle) => {
                              if (FSAccess.isAvailable()) {
                                  if (handle) {
                                      this._savedFileHandle = handle;
                                      this._fileNameText.value = handle.name;
                                      this.proceedToStep2();
                                  }
                              } else {
                                  // Otherwise, no way to get the chosen file name
                                  // or even to tell whether user clicked OK or
                                  // Cancel.  So we rely on the user typing the
                                  // chosen file name.
                                  this.proceedToStep2();
                              }
                          },
                          (error) => {
                              this.chooseFileError(error);
                          });
            }
        }

        static addExtensions(exts, opts) {
            if (FSAccess.isAvailable()) {
                // Seems unusable with showXXXFilePicker.
                return;
            }
            // Otherwise, anything more complicated than just opts.extensions does
            // not seem to work.

            if (Array.isArray(exts) && exts.length > 0) {
                opts.extensions = [];

                for (let ext of exts) {
                    if (Array.isArray(ext) && ext.length >= 3) {
                        // Example: ["DocBook files", "application/docbook+xml",
                        //           "dbk", "docb"].
                        for (let i = 2; i < ext.length; ++i) {
                            let suffix = ext[i].trim().toLowerCase();
                            if (suffix.length > 0) {
                                if (!suffix.startsWith(".")) {
                                    suffix = "." + suffix;
                                }
                                opts.extensions.push(suffix);
                            }
                        }
                    }
                }
            }
        }

        chooseFileError(error) {
            if (error.name !== "AbortError") {
                // For example: NotAllowedError when the user refuses to grant
                // permission to save the file.

                this._openedFile = null;
                this._savedFileHandle = null;

                XUI.Alert.showError(
                    `Cannot display the file chooser dialog:\n${error}`,
                    this._xmlEditor);
            }
            // AbortError means: cancelled by user.
        }

        proceedToStep2() {
            this._step1.classList.remove("xxe-lfd-curstep");
            this._step2.classList.add("xxe-lfd-curstep");
            this._dirPathText.removeAttribute("disabled");
            this._fileNameText.removeAttribute("disabled");
        }

        dialogClosed(result) {
            // Close icon clicked ==> null result, which is just fine.
            this._resolve(result);
        }

        cancelAction() {
            this.close(null);
        }

        okAction() {
            let choice = {};

            const openMode = !this._savedData;
            if (openMode) {
                if (this._openedFile === null) {
                    this._browseButton.focus();
                    return;
                }

                choice.file = this._openedFile;
            } else {
                if (FSAccess.isAvailable()) {
                    if (this._savedFileHandle === null) {
                        this._browseButton.focus();
                        return;
                    }

                    choice.fileHandle = this._savedFileHandle;
                } else {
                    // No fileHandle. So we rely on the user typing the chosen
                    // file name.
                    choice.fileHandle = null;
                }
            }

            // ---

            let dirPath = this._dirPathText.value.trim();
            if (dirPath.length === 0) {
                XUI.Util.badTextField(this._dirPathText);
                return;
            }

            let fileName = this._fileNameText.value.trim();
            if (fileName.length === 0 ||
                fileName.indexOf(FILE_PATH_SEPARATOR) >= 0) {
                XUI.Util.badTextField(this._fileNameText);
                return;
            }

            let filePath = dirPath;
            if (!filePath.endsWith(FILE_PATH_SEPARATOR)) {
                filePath += FILE_PATH_SEPARATOR;
            }
            filePath += fileName;

            if (!URIUtil.isAbsolutePath(filePath, FILE_PATH_SEPARATOR)) {
                XUI.Util.badTextField(this._dirPathText);
                return;
            }

            // "Normalize" dirPath.
            dirPath = URIUtil.pathParent(filePath, FILE_PATH_SEPARATOR,
                                         /*trailingSepar*/ false);
            this.rememberDirPath(dirPath, /*addDataList*/ false);

            if (!FSAccess.isAvailable()) {
                // Remember everything the user has selected or typed.
                this.rememberFileName(fileName, false);
            }
            // Otherwise, this._fileNameText is always read-only and the
            // corresponding datalist is never useful.

            choice.uri = URIUtil.pathToFileURI(filePath);

            // ---

            if (this._optionName !== null) {
                choice[this._optionName] = this._optToggle.checked;
            }

            this.close(choice);
        }
    }

    LocalFileDialog.TEMPLATE = document.createElement("template");
    LocalFileDialog.TEMPLATE.innerHTML = `
    <div class="xxe-lfd-pane">
      <div class="xxe-lfd-browse-pane"><span class="xxe-lfd-step1
        xxe-lfd-curstep">1.</span>
        <span class="xxe-lfd-browse-label">Open file:</span>
        <button type="button" title="Choose file..."
                class="xui-control xui-small-icon xxe-lfd-browse"></button>
      </div>
      <div class="xxe-lfd-path-label"><span class="xxe-lfd-step2">2.</span>
      Specify the <strong>absolute</strong> path of chosen file:</div>
      <div class="xxe-lfd-path-pane">
        <input type="text" size="50" class="xui-control xxe-lfd-dir-path"
               spellcheck="false" autocomplete="off" disabled="disabled" />
        <span class="xxe-lfd-path-separ"></span>
        <input type="text" size="20" class="xui-control xxe-lfd-file-name"
               spellcheck="false" autocomplete="off" disabled="disabled" />
      </div>
      <div class="xxe-lfd-path-hint"><strong>Required.</strong>
      For security reasons, this application has no way<br />
      to automatically determine the absolute path of chosen file.</div>
      <div class="xxe-lfd-opt-pane">
        <label class="xxe-lfd-opt-label">
          <input type="checkbox" class="xxe-lfd-opt-toggle"/>
        </label>
      </div>
    </div>
    `;