codemelted 26.1.1

The aim of this project is to deliver a swiss army knife module to aid software engineers for building full stack solutions for their applications. Utilizing the Rust programming language, the module serves as a backbone to engineer solutions for multiple build targets.
Documentation
<!DOCTYPE html>
<html lang="en"><head>
  <title>CodeMelted | UML Modeler</title>
  <meta charset="UTF-8">
  <meta name="description" content="UML provides a powerful ability to model software systems. I got tired of what was out there so I made my own.">
  <meta name="keywords" content="CodeMeltedPWA, CodeMeltedDEV, SPA, UML, Software Engineering">
  <meta name="author" content="Mark Shaffer">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="icon" type="image/x-icon" href="https://codemelted.com/favicon.png">
  <link rel="stylesheet" href="https://codemelted.com/assets/css/scrollbars.css">
  <style>
    /*
    ---------------------------------------------------------------------------
    [Setup 100% Width / Height] -----------------------------------------------
    ---------------------------------------------------------------------------
    */
    html, body {
      margin: 0;
      padding: 0;
    }

    /*
    ---------------------------------------------------------------------------
    [Header Style] ------------------------------------------------------------
    ---------------------------------------------------------------------------
    */

    header {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      height: 50px;
      background-color: darkblue;
      display: grid;
      grid-template-columns: auto auto auto;
    }

    header div {
      display: flex;
      justify-content: center;
      justify-items: center;
      margin-top: 2.5px;
      font-size: xx-large;
      text-align: center;
    }

    header div a {
      margin-right: 10px;
      cursor: pointer;
      text-decoration: none;
      height: 50px;
      width: 50px;
    }

    header div a:hover {
      background-color: darkred;
    }

    /*
    ---------------------------------------------------------------------------
    [Main Style] --------------------------------------------------------------
    ---------------------------------------------------------------------------
    */

    main {
      position: fixed;
      display: grid;
      grid-template-columns: auto auto;
      padding: 0;
      margin: 0;
      top: 50px;
      bottom: 42px;
      width: 100%;
      overflow: auto;
      background-color: darkblue;
    }

    main textarea {
      padding: 0;
      margin: 0;
      font-family: monospace;
      color: white;
      font-size: x-large;
      margin-top: 2px;
      border: none;
      outline: none;
      resize: none;
      width: 99.5%;
      background-color: black;
      height: 99.5%;
    }

    main pre {
      padding: 0;
      margin: 0;
      background-color: wheat;
      height: 100%;
      width: 100%;
      display: flex;
      align-content: center;
      justify-content: start;
      align-items: center;
    }

    /*
    ---------------------------------------------------------------------------
    [Footer Style] ------------------------------------------------------------
    ---------------------------------------------------------------------------
    */

    footer {
      position: fixed;
      bottom: 0;
      left: 0;
      right: 0;
      background-color: darkblue;
      color: goldenrod;
      display: grid;
      grid-template-columns: auto auto;
      text-align: center;
      font-size: larger;
      font-weight: bold;
    }
    footer div {
      padding: 10px;
    }
  </style>
</head><body>
  <header>
    <div><a class="codemelted-nav-control" id="btnOpenModel" title="Open Model">📂</a></div>
    <div><a class="codemelted-nav-control" id="btnSaveModel" title="Save Model">💾</a></div>
    <div><a class="codemelted-nav-control" id="btnHelp" title="Mermaid Syntax">🆘</a></div>
  </header>
  <main>
    <textarea id="txtEditor" wrap="off"></textarea>
    <pre id="preModel" class="mermaid"></pre>
  </main>
  <footer>
    <div>Editor</div>
    <div>Model</div>
  </footer>
  <dialog id="dlgError">
    <label id="lblError"></label>
    <p>
      <center><button id="btnClose">Close</button></center>
    </p>

    <script>
      const dlgError = document.getElementById("dlgError");
      const btnClose = document.getElementById("btnClose");
      btnClose.onclick = (evt) => { dlgError.close(); };
    </script>
  </dialog>
  <dialog id="dlgSaveFile">
    <label>Save As Filename:</label><br />
    <input id="txtFilename" type="text"></input>
    <p>
      <center>
        <button id="btnOK">OK</button>&nbsp;&nbsp;
        <button id="btnCancel">CANCEL</button>
      </center>
    </p>
    <script>
      const dlgSaveFile = document.getElementById("dlgSaveFile");
      const btnOK = document.getElementById("btnOK");
      const btnCancel = document.getElementById("btnCancel");
      const txtFilename = document.getElementById("txtFilename");
      btnOK.onclick = (e) => { dlgSaveFile.close(txtFilename.value); };
      btnCancel.onclick = (e) => {dlgSaveFile.close(null); };
    </script>
  </dialog>
  <script type="module">
    // ------------------------------------------------------------------------
    // [Setup Script] ---------------------------------------------------------
    // ------------------------------------------------------------------------
    import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";

    // Setup mermaid to not render until called upon
    mermaid.initialize({ startOnLoad: false });

    // Setup tracking variable for the SVG model
    let _modelCode = undefined;
    let _svgModel = undefined;

    // Go get elements on the UI
    const btnOpenModel = document.getElementById("btnOpenModel");
    const btnSaveModel = document.getElementById("btnSaveModel")
    const btnHelp = document.getElementById("btnHelp");
    const txtEditor = document.getElementById("txtEditor");
    const preModel = document.getElementById("preModel");

    // ------------------------------------------------------------------------
    // [Model Rendering] ------------------------------------------------------
    // ------------------------------------------------------------------------

    // Holds the item for updating the model after user is done typing.
    let _timerId = undefined;

    // Updates the model once typing is completed.
    function renderModel() {
      clearTimeout(_timerId);
      _timerId = setTimeout(async () => {
        try {
          _modelCode = txtEditor.value.trim();
          const { svg } = await mermaid.render("mermaid", _modelCode);
          _svgModel = svg;
          preModel.innerHTML = _svgModel;
        } catch (err) {
          console.warn("renderModel", err);
        }
      }, 500);
    }

    // Convert tabs to 4 spaces for the model code.
    txtEditor.onkeydown = (evt) => {
      if (evt.key == "Tab") {
        evt.preventDefault();
        const textArea = evt.currentTarget;
        textArea.setRangeText(
          "    ",
          textArea.selectionStart,
          textArea.selectionEnd,
          "end"
        );
      }
    };

    // Keypress events on editor to kick-off a model update.
    txtEditor.onpaste = (evt) => { renderModel(); };
    txtEditor.onkeypress = (evt) => { renderModel(); };

    // ------------------------------------------------------------------------
    // [Button Handlers] ------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Shows the error dialog display the error message.
     * @param {string} msg The error message to show.
     * @returns {void}
     */
    function showErrorDialog(msg) {
      const dlgError = document.getElementById("dlgError");
      const lblError = document.getElementById("lblError");
      lblError.innerHTML = msg;
      dlgError.showModal();
    }

    /**
     * Will open a file and read its contents for further processing.
     * @param {boolean} isTextFile true if you are expecting text, false for
     * binary data.
     * @param {string} accept A CSV separated string identifying what files
     * to filter on open from the file chooser.
     * @returns {Promise<string | Uint8Array | null>}
     */
    async function openFile(isTextFile, accept="") {
      return new Promise((resolve, reject) => {
        try {
          if (typeof isTextFile !== "boolean" || typeof accept !== "string") {
            throw new SyntaxError("parameters are not of an expected type");
          }
          const input = globalThis.document.createElement("input");
          input.type = "file";
          input.accept = accept;
          input.onchange = async () => {
            let file = undefined;
            if (input.files != null) {
              file = input.files[0];
              if (isTextFile) {
                const buffer = await file.arrayBuffer();
                const decoder = new TextDecoder();
                resolve(decoder.decode(buffer));
              } else {
                const bytes = await file.bytes();
                resolve(bytes);
              }
            } else {
              resolve(null);
            }
          };
          input.click();
        } catch (err) {
          reject(err);
        }
      });
    }

    /**
     * Saves the model code / svg to the download directory.
     * @param {string} filename The filename to identify the data in the
     * download directory
     * @param {any} data The data to save.
     * @returns {Promise<void>}
     */
    async function saveFile(filename, data) {
      return new Promise((resolve, reject) => {
        try {
          // Validate that filename was specified and of an expected type.
          if (typeof filename !== "string" ||
              filename.length === 0 ) {
            throw new SyntaxError("filename was not specified");
          }

          let blobURL = undefined;
          if (typeof data === "string") {
            const buffer = new TextEncoder().encode(data);
            const blob = new Blob([buffer]);
            blobURL = URL.createObjectURL(blob);
          } else if (data instanceof Uint8Array) {
            const blob = new Blob([data]);
            blobURL = URL.createObjectURL(blob);
          } else {
            throw new SyntaxError("data is not of an expected type");
          }

          const a = globalThis.document.createElement('a');
          a.href = blobURL;
          a.download = filename;
          a.style.display = 'none';
          globalThis.document.body.append(a);
          a.click();

          // Revoke the blob URL and remove the element.
          setTimeout(() => {
            URL.revokeObjectURL(blobURL);
            a.remove();
            resolve();
          }, 500);
        } catch (err) {
          reject(err);
        }
      });
    }

    btnOpenModel.onclick = async (evt) => {
      try {
        const modelCode = await openFile(true, ".mmd");
        if (modelCode) {
          txtEditor.textContent = modelCode;
          renderModel();
        }
      } catch (err) {
        showErrorDialog(`Failed to open file. Reason:<br />${err}`);
      }
    };

    btnSaveModel.onclick = (evt) => {
      try {
        // Get access to dialog and configure to save if filename
        // available.
        const dlgSaveFile = document.getElementById("dlgSaveFile");
        dlgSaveFile.onclose = async (e) => {
          const filename = dlgSaveFile.returnValue;
          if (filename) {
            if (_modelCode && _modelCode !== "") {
              await saveFile(`${filename}.mmd`, _modelCode);
              await saveFile(`${filename}.svg`, _svgModel);
            }
          }
        };

        // Now go show the dialog.
        const txtFilename = document.getElementById("txtFilename");
        txtFilename.value = "";
        dlgSaveFile.showModal();
      } catch (err) {
        showErrorDialog(`Failed to save file. Reason:<br />${err}`);
      }
    };

    btnHelp.onclick = (evt) => {
      // Public function from codemelted_navigation.js
      onOpenLinkClicked("mermaid.js.org/intro/syntax-reference.html");
    };

  /**
   * Handles the opening of the social links in a new window.
   * @param {string} url The URL to open the social link as a new window.
   * @returns {void}
   */
  function onOpenLinkClicked(url) {
    const w = 900;
    const h = 600;
    const top = (window.screen.height - h) / 2;
    const left = (window.screen.width - w) / 2;
    const settings = `toolbar=no, location=no, ` +
      `directories=no, status=no, menubar=no, ` +
      `scrollbars=no, resizable=yes, copyhistory=no, ` +
      `width=${w}, height=${h}, top=${top}, left=${left}`;
    window.open(
      `https://${url}`,
      "_blank",
      settings,
    );
  }
  </script>
</body></html>