<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>esphomeyaml Dashboard</title>
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/css/materialize.min.css">

  <link rel="stylesheet" href="/static/materialize-stepper.min.css">

  <!-- jQuery :( -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
  <script src="https://code.jquery.com/ui/1.8.5/jquery-ui.min.js" integrity="sha256-fOse6WapxTrUSJOJICXXYwHRJOPa6C1OUQXi7C9Ddy8=" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>
  <script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.15.0/jquery.validate.min.js"></script>


  <script src="/static/materialize-stepper.min.js"></script>

  <style>
    nav .brand-logo {
      margin-left: 48px;
      font-size: 20px;
    }

    main .container {
      margin-top: -12vh;
      flex-shrink: 0;
    }

    .ribbon {
      width: 100%;
      height: 17vh;
      background-color: #3F51B5;
      flex-shrink: 0;
    }

    .ribbon-fab:not(.tap-target-origin) {
      position: absolute;
      right: 24px;
      top: calc(17vh + 34px);
    }

    i.very-large {
      font-size: 8rem;
      padding-top: 2px;
      color: #424242;
    }

    .card .card-content {
      padding-left: 18px;
      padding-bottom: 10px;
    }

    .chip {
      height: 26px;
      font-size: 12px;
      line-height: 26px;
    }

    .log {
      background-color: #1c1c1c;
      margin-top: 0;
      margin-bottom: 0;
      font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
      font-size: 12px;
      padding: 16px;
      overflow: auto;
      line-height: 1.45;
      border-radius: 3px;
      white-space: pre-wrap;
      overflow-wrap: break-word;
      color: #DDD;
    }

    .inlinecode {
      box-sizing: border-box;
      padding: 0.2em 0.4em;
      margin: 0;
      font-size: 85%;
      background-color: rgba(27,31,35,0.05);
      border-radius: 3px;
      font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;
    }

    .log.bold {
      font-weight: bold;
    }

    .log .v {
      color: #888888;
    }

    .log .d {
      color: #00DDDD;
    }

    .log .c {
      color: magenta;
    }

    .log .i {
      color: limegreen;
    }

    .log .w {
      color: yellow;
    }

    .log .e {
      color: red;
      font-weight: bold;
    }

    .log .e {
      color: red;
    }

    .log .ww {
      color: white;
    }

    .modal {
      width: 95%;
      max-height: 90%;
      height: 85% !important;
    }

    .page-footer {
      padding-top: 0;
    }

    body {
      display: flex;
      min-height: 100vh;
      flex-direction: column;
    }

    main {
      flex: 1 0 auto;
    }

    ul.browser-default {
      padding-left: 30px;
      margin-top: 10px;
      margin-bottom: 15px;
    }

    ul.browser-default li {
      list-style-type: initial;
    }

    ul.stepper:not(.horizontal) .step.active::before, ul.stepper:not(.horizontal) .step.done::before, ul.stepper.horizontal .step.active .step-title::before, ul.stepper.horizontal .step.done .step-title::before {
      background-color: #3f51b5 !important;
    }

    .select-port-container {
      margin-top: 8px;
      margin-right: 24px;
      width: 350px;
    }
  </style>
</head>
<body>

<header>
<nav>
  <div class="nav-wrapper indigo">
    <a href="#" class="brand-logo left">esphomeyaml Dashboard</a>
    <div class="select-port-container right" id="select-port-target">
      <select></select>
    </div>
  </div>
</nav>

<div class="tap-target pink lighten-1 select-port" data-target="select-port-target">
  <div class="tap-target-content">
    <h5>Select Upload Port</h5>
    <p>
      Here you can select where esphomeyaml will attempt to show logs and upload firmwares to.
      By default, this is "OTA", or Over-The-Air. Note that you might have to restart the HassIO add-on
      for new serial ports to be detected.
    </p>
  </div>
</div>

<div class="ribbon"></div>
</header>

<main>
<div class="container">
  {% for file, full_path in zip(files, full_path_files) %}
  <div class="row">
    <div class="col s8 offset-s2 m10 offset-m1 l12">
      <div class="card horizontal">
        <div class="card-image center-align">
          <i class="material-icons very-large icon-grey">memory</i>
        </div>
        <div class="card-stacked">
          <div class="card-content">
            <span class="card-title">{{ escape(file) }}</span>
            <p>
              Full path: <code class="inlinecode">{{ escape(full_path) }}</code>
            </p>
          </div>
          <div class="card-action">
            <a href="#" class="action-upload" data-node="{{ file }}">Upload</a>
            <a href="#" class="action-compile" data-node="{{ file }}">Compile</a>
            <a href="#" class="action-show-logs" data-node="{{ file }}">Show Logs</a>
            <a href="#" class="action-validate" data-node="{{ file }}">Validate</a>
          </div>
        </div>
      </div>
    </div>
  </div>
  {% end %}
</div>

<div id="modal-logs" class="modal modal-fixed-footer">
  <div class="modal-content">
    <h4>Show Logs <code class="inlinecode filename"></code></h4>
    <div class="log-container">
      <pre class="log"></pre>
    </div>
  </div>
  <div class="modal-footer">
    <a class="modal-close waves-effect waves-green btn-flat stop-logs">Close</a>
  </div>
</div>

<div id="modal-upload" class="modal modal-fixed-footer">
  <div class="modal-content">
    <h4>Compile And Upload <code class="inlinecode filename"></code></h4>
    <div class="log-container">
      <pre class="log"></pre>
    </div>
  </div>
  <div class="modal-footer">
    <a class="modal-close waves-effect waves-green btn-flat stop-logs">Stop</a>
  </div>
</div>

<div id="modal-compile" class="modal modal-fixed-footer">
  <div class="modal-content">
    <h4>Compile <code class="inlinecode filename"></code></h4>
    <div class="log-container">
      <pre class="log"></pre>
    </div>
  </div>
  <div class="modal-footer">
    <a class="modal-close waves-effect waves-green btn-flat disabled download-binary">Download Binary</a>
    <a class="modal-close waves-effect waves-green btn-flat stop-logs">Stop</a>
  </div>
</div>

<div id="modal-validate" class="modal modal-fixed-footer">
  <div class="modal-content">
    <h4>Validate <code class="inlinecode filename"></code></h4>
    <div class="log-container">
      <pre class="log"></pre>
    </div>
  </div>
  <div class="modal-footer">
    <a class="modal-close waves-effect waves-green btn-flat stop-logs">Stop</a>
  </div>
</div>

<div id="modal-wizard" class="modal">
  <div class="modal-content">
    <form action="/wizard.html" method="POST">
    <ul class="stepper linear">
      <li class="step active">
        <div class="step-title waves-effect">Introduction And Name</div>
        <div class="step-content">
          <div class="row">
            <p>
              Hi there! I'm the esphomeyaml setup wizard and will guide you through setting up
              your first ESP8266 or ESP32-powered device using esphomeyaml.
            </p>
              <a href="https://www.espressif.com/en/products/hardware/esp8266ex/overview" target="_blank">ESP8266s</a> and
              their successors (the <a href="https://www.espressif.com/en/products/hardware/esp32/overview" target="_blank">ESP32s</a>)
              are great low-cost microcontrollers that can communicate with the outside world using WiFi.
              They're found in many devices such as the popular Sonoff/iTead, but also exist as development boards
              such as the <a href="https://esphomelib.com/esphomeyaml/devices/nodemcu_esp8266.html" target="_blank">NodeMCU</a>.
            <p>
            </p>
              <a href="https://esphomelib.com/esphomeyaml/index.html" target="_blank">esphomeyaml</a>,
              the tool you're using here, creates custom firmwares for these devices using YAML configuration
              files (similar to the ones you might be used to with Home Assistant).
            <p>
            </p>
              This wizard will create a basic YAML configuration file for your "node" (the microcontroller).
              Later, you will be able to customize this file and add some of
              <a href="https://github.com/OttoWinter/esphomelib" target="_blank">esphomelib's</a>
              many integrations.
            <p>
            <p>
              First, I need to know what this node should be called. Choose this name wisely, changing this
              later makes Over-The-Air Update attempts difficult.
              It may only contain the characters <code class="inlinecode">a-z</code>,
              <code class="inlinecode">0-9</code> and <code class="inlinecode">_</code>
            </p>
            <div class="input-field col s12">
              <input id="node_name" class="validate" type="text" name="name" required>
              <label for="node_name">Name of node</label>
            </div>
          </div>
          <div class="step-actions">
            <button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
          </div>
        </div>
      </li>
      <li class="step">
        <div class="step-title waves-effect">Device Type</div>
        <div class="step-content">
          <div class="row">
            <p>
              Great! Now I need to know what type of microcontroller you're using so that I can compile firmware for them.
              Please choose either ESP32 or ESP8266 (use ESP8266 for Sonoff devices).
            </p>
            <div class="input-field col s12">
              <select id="esp_type" name="platform" required>
                <option value="ESP8266">ESP8266</option>
                <option value="ESP32">ESP32</option>
              </select>
              <label>Microcontroller Type</label>
            </div>
            <p>
              I'm also going to need to know which type of board you're using. Please go to
              <a href="http://docs.platformio.org/en/latest/platforms/espressif32.html#boards" target="_blank">ESP32 boards</a> or
              <a href="http://docs.platformio.org/en/latest/platforms/espressif8266.html#boards" target="_blank">ESP8266 boards</a>,
              find your board and enter it here. For example, enter <code class="inlinecode">nodemcuv2</code>
              for ESP8266 NodeMCU boards. Note: Use <code class="inlinecode">esp01_1m</code> for Sonoff devices.
            </p>
            <div class="input-field col s12">
              <input id="board_type" class="validate" type="text" name="board" required>
              <label for="board_type">Board Type</label>
            </div>
          </div>
          <div class="step-actions">
            <button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
          </div>
        </div>
      </li>
      <li class="step">
        <div class="step-title waves-effect">WiFi And Over-The-Air Updates</div>
        <div class="step-content">
          <div class="row">
            <p>
              Thanks! Now I need to know what WiFi Access Point I should instruct the node to connect to.
              Please enter an SSID (name of the WiFi network) and password (leave empty for no password).
            </p>
            <div class="input-field col s12">
              <input id="wifi_ssid" class="validate" type="text" name="ssid" required>
              <label for="wifi_ssid">WiFi SSID</label>
            </div>
            <div class="input-field col s12">
              <input id="wifi_password" name="psk" type="password">
              <label for="wifi_password">WiFi Password</label>
            </div>
            <p>
              Esphomelib automatically sets up an Over-The-Air update server on the node
              so that you only need to flash a firmware via USB once.
              Optionally, you can set a password for this upload process here:
            </p>
            <div class="input-field col s12">
              <input id="ota_password" class="validate" name="ota_password" type="password">
              <label for="ota_password">OTA Password</label>
            </div>
          </div>
          <div class="step-actions">
            <button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
          </div>
        </div>
      </li>
      <li class="step">
        <div class="step-title waves-effect">MQTT</div>
        <div class="step-content">
          <div class="row">
            <p>
              esphomelib connects to your Home Assistant instance via
              <a href="https://www.home-assistant.io/docs/mqtt/">MQTT</a>. If you haven't already, please set up
              MQTT on your Home Assistant server, for example with the awesome
              <a href="https://www.home-assistant.io/addons/mosquitto/">Mosquitto Hass.io Add-on</a>.
            </p>
            <p>
              When you're done with that, please enter your MQTT broker here. For example
              <code class="inlinecode">192.168.1.100</code> (Note
              <code class="inlinecode">hassio.local</code> doesn't always work, please use a static IP).
              Please also specify the MQTT username and password you wish esphomelib to use
              (leave them empty if you're not using any authentication).
            </p>
            <div class="input-field col s12">
              <input id="mqtt_broker" class="validate" type="text" name="broker" required>
              <label for="mqtt_broker">MQTT Broker</label>
            </div>
            <div class="input-field col s6">
              <input id="mqtt_username" class="validate" type="text" name="mqtt_username">
              <label for="mqtt_username">MQTT Username</label>
            </div>
            <div class="input-field col s6">
              <input id="mqtt_password" class="validate" name="mqtt_password" type="password">
              <label for="mqtt_password">MQTT Password</label>
            </div>
          </div>
          <div class="step-actions">
            <button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
          </div>
        </div>
      </li>
      <li class="step">
        <div class="step-title waves-effect">Done!</div>
        <div class="step-content">
          <p>
            Hooray! 🎉🎉🎉 You've successfully created your first esphomeyaml configuration file.
            When you click Submit, I will save this configuration file under
            <code class="inlinecode">&lt;HASS_CONFIG_FOLDER&gt;/esphomeyaml/&lt;NAME_OF_NODE&gt;.yaml</code> and
            you will be able to edit this file with the
            <a href="https://www.home-assistant.io/addons/configurator/" target="_blank">HASS Configuratior add-on</a>.
          </p>
          <h5>Next steps</h5>
          <ul class="browser-default">
            <li>
              Flash the firmware. This can be done using the “UPLOAD” option in the dashboard. See
              <a href="https://esphomelib.com/esphomeyaml/index.html#devices" target="_blank">this</a>
              for guides on how to flash different types of devices. Note that you need to restart this add-on
              for newly plugged in serial devices to be detected.
            </li>
            <li>
              With the current configuration, your node will only connect to WiFi and MQTT. To make it actually <i>do</i>
              stuff, follow
              <a href="https://esphomelib.com/esphomeyaml/guides/getting_started_hassio.html#adding-some-basic-features">
                the rest of the getting started guide
              </a>.
            </li>
            <li>
              See the <a href="https://esphomelib.com/esphomeyaml/index.html" target="_blank">esphomeyaml index</a>
              for a list of supported sensors/devices.
            </li>
            <li>
              Join the <a href="https://discord.gg/KhAMKrd" target="_blank">Discord server</a> and say hi! When I
              have time, I would be happy to help with issues and discuss new features.
            </li>
            <li>
              Star <a href="https://github.com/OttoWinter/esphomelib" target="_blank">esphomelib</a> and
              <a href="https://github.com/OttoWinter/esphomeyaml" target="_blank">esphomeyaml</a> on GitHub
              if you find this software awesome and report issues using the bug trackers there.
            </li>
          </ul>
          <div class="step-actions">
            <button class="waves-effect waves-dark btn indigo" type="submit">SUBMIT</button>
          </div>
        </div>
      </li>
    </ul>
    </form>
  </div>
  <div class="modal-footer">
    <a href="#!" class="modal-close waves-effect waves-green btn-flat">Abort</a>
  </div>
</div>

<a class="btn-floating btn-large ribbon-fab waves-effect waves-light pink accent-2" id="setup-wizard-start">
  <i class="material-icons">add</i>
</a>

<div class="tap-target pink lighten-1 setup-wizard" data-target="setup-wizard-start">
  <div class="tap-target-content">
    <h5>Set up your first Node</h5>
    <p>
      Huh... It seems like you you don't have any esphomeyaml configuration files yet...
      Fortunately, there's a setup wizard that will step you through setting up your first node 🎉
    </p>
  </div>
</div>
</main>

<footer class="page-footer indigo darken-1">
  <div class="container">

  </div>
  <div class="footer-copyright">
    <div class="container">
      © 2018 Copyright Otto Winter, Made with <a class="grey-text text-lighten-4" href="https://materializecss.com/" target="_blank">Materialize</a>
      <a class="grey-text text-lighten-4 right" href="https://esphomelib.com/esphomeyaml/index.html" target="_blank">esphomeyaml {{ version }} Documentation</a>
    </div>
  </div>
</footer>

<script>
  document.addEventListener('DOMContentLoaded', () => {
    M.AutoInit(document.body);
  });

  const colorReplace = (input) => {
    input = input.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
    input = input.replace(/\\033\[(?:0;)?31m/g, '<span class="e">');
    input = input.replace(/\\033\[(?:1;)?31m/g, '<span class="e bold">');
    input = input.replace(/\\033\[(?:0;)?32m/g, '<span class="i">');
    input = input.replace(/\\033\[(?:1;)?32m/g, '<span class="i bold">');
    input = input.replace(/\\033\[(?:0;)?33m/g, '<span class="w">');
    input = input.replace(/\\033\[(?:1;)?33m/g, '<span class="w bold">');
    input = input.replace(/\\033\[(?:0;)?35m/g, '<span class="c">');
    input = input.replace(/\\033\[(?:1;)?35m/g, '<span class="c bold">');
    input = input.replace(/\\033\[(?:0;)?36m/g, '<span class="d">');
    input = input.replace(/\\033\[(?:1;)?36m/g, '<span class="d bold">');
    input = input.replace(/\\033\[(?:0;)?37m/g, '<span class="v">');
    input = input.replace(/\\033\[(?:1;)?37m/g, '<span class="v bold">');
    input = input.replace(/\\033\[(?:0;)?38m/g, '<span class="vv">');
    input = input.replace(/\\033\[(?:1;)?38m/g, '<span class="vv bold">');
    input = input.replace(/\\033\[0m/g, '</span>');

    return input;
  };

  let configuration = "";
  let wsProtocol = "ws:";
  if (window.location.protocol === "https:") {
    wsProtocol = 'wss:';
  }
  const wsUrl = wsProtocol + '//' + window.location.hostname + ':' + window.location.port;

  const portSelect = document.querySelector('.nav-wrapper select');
  let ports = [];

  const fetchSerialPorts = (begin=false) => {
    fetch('/serial-ports').then(res => res.json())
      .then(response => {
        if (ports.length === response.length) {
          let allEqual = true;
          for (let i = 0; i < response.length; i++) {
            if (ports[i].port !== response[i].port) {
              allEqual = false;
              break;
            }
          }
          if (allEqual)
            return;
        }

        ports = response;

        const inst = M.FormSelect.getInstance(portSelect);
        if (inst !== undefined) {
          inst.destroy();
        }

        portSelect.innerHTML = "";
        const prevSelected = getUploadPort();
        for (let i = 0; i < response.length; i++) {
          const val = response[i];
          if (val.port === prevSelected) {
            portSelect.innerHTML += `<option value="${val.port}" selected>${val.port} (${val.desc})</option>`;
          } else {
            portSelect.innerHTML += `<option value="${val.port}">${val.port} (${val.desc})</option>`;
          }
        }

        M.FormSelect.init(portSelect, {});
        if (!begin)
          M.toast({html: "Discovered new serial port."});
      });
  };

  const getUploadPort = () => {
    const inst = M.FormSelect.getInstance(portSelect);
    if (inst === undefined) {
      return "OTA";
    }

    inst._setSelectedStates();
    return inst.getSelectedValues()[0];
  };
  setInterval(fetchSerialPorts, 5000);
  fetchSerialPorts(true);

  const logsModalElem = document.getElementById("modal-logs");

  document.querySelectorAll(".action-show-logs").forEach((showLogs) => {
    showLogs.addEventListener('click', (e) => {
      configuration = e.target.getAttribute('data-node');
      const modalInstance = M.Modal.getInstance(logsModalElem);
      const log = logsModalElem.querySelector(".log");
      log.innerHTML = "";
      const stopLogsButton = logsModalElem.querySelector(".stop-logs");
      let stopped = false;
      stopLogsButton.innerHTML = "Stop";
      modalInstance.open();

      const filenameField = logsModalElem.querySelector('.filename');
      filenameField.innerHTML = configuration;

      const logSocket = new WebSocket(wsUrl + "/logs");
      logSocket.addEventListener('message', (event) => {
        const data = JSON.parse(event.data);
        if (data.event === "line") {
          const msg = data.data;
          log.innerHTML += colorReplace(msg);
        } else if (data.event === "exit") {
          if (data.code === 0) {
            M.toast({html: "Program exited successfully."});
          } else {
            M.toast({html: `Program failed with code ${data.code}`});
          }

          stopLogsButton.innerHTML = "Close";
          stopped = true;
        }
      });
      logSocket.addEventListener('open', () => {
        const msg = JSON.stringify({configuration: configuration, port: getUploadPort()});
        logSocket.send(msg);
      });
      logSocket.addEventListener('close', () => {
        if (!stopped) {
          M.toast({html: 'Terminated process.'});
        }
      });
      modalInstance.options.onCloseStart = () => {
        logSocket.close();
      };
    });
  });

  const uploadModalElem = document.getElementById("modal-upload");

  document.querySelectorAll(".action-upload").forEach((upload) => {
    upload.addEventListener('click', (e) => {
      configuration = e.target.getAttribute('data-node');
      const modalInstance = M.Modal.getInstance(uploadModalElem);
      const log = uploadModalElem.querySelector(".log");
      log.innerHTML = "";
      const stopLogsButton = uploadModalElem.querySelector(".stop-logs");
      let stopped = false;
      stopLogsButton.innerHTML = "Stop";
      modalInstance.open();

      const filenameField = uploadModalElem.querySelector('.filename');
      filenameField.innerHTML = configuration;

      const logSocket = new WebSocket(wsUrl + "/run");
      logSocket.addEventListener('message', (event) => {
        const data = JSON.parse(event.data);
        if (data.event === "line") {
          const msg = data.data;
          log.innerHTML += colorReplace(msg);
        } else if (data.event === "exit") {
          if (data.code === 0) {
            M.toast({html: "Program exited successfully."});
          } else {
            M.toast({html: `Program failed with code ${data.code}`});
          }

          stopLogsButton.innerHTML = "Close";
          stopped = true;
        }
      });
      logSocket.addEventListener('open', () => {
        const msg = JSON.stringify({configuration: configuration, port: getUploadPort()});
        logSocket.send(msg);
      });
      logSocket.addEventListener('close', () => {
        if (!stopped) {
          M.toast({html: 'Terminated process.'});
        }
      });
      modalInstance.options.onCloseStart = () => {
        logSocket.close();
      };
    });
  });

  const validateModalElem = document.getElementById("modal-validate");

  document.querySelectorAll(".action-validate").forEach((upload) => {
    upload.addEventListener('click', (e) => {
      configuration = e.target.getAttribute('data-node');
      const modalInstance = M.Modal.getInstance(validateModalElem);
      const log = validateModalElem.querySelector(".log");
      log.innerHTML = "";
      const stopLogsButton = validateModalElem.querySelector(".stop-logs");
      let stopped = false;
      stopLogsButton.innerHTML = "Stop";
      modalInstance.open();

      const filenameField = validateModalElem.querySelector('.filename');
      filenameField.innerHTML = configuration;

      const logSocket = new WebSocket(wsUrl + "/validate");
      logSocket.addEventListener('message', (event) => {
        const data = JSON.parse(event.data);
        if (data.event === "line") {
          const msg = data.data;
          log.innerHTML += colorReplace(msg);
        } else if (data.event === "exit") {
          if (data.code === 0) {
            M.toast({
              html: `<code class="inlinecode">${configuration}</code> is valid 👍`,
              displayLength: 5000,
            });
          } else {
            M.toast({
              html: `<code class="inlinecode">${configuration}</code> is invalid 😕`,
              displayLength: 5000,
            });
          }

          stopLogsButton.innerHTML = "Close";
          stopped = true;
        }
      });
      logSocket.addEventListener('open', () => {
        const msg = JSON.stringify({configuration: configuration});
        logSocket.send(msg);
      });
      logSocket.addEventListener('close', () => {
        if (!stopped) {
          M.toast({html: 'Terminated process.'});
        }
      });
      modalInstance.options.onCloseStart = () => {
        logSocket.close();
      };
    });
  });

  const compileModalElem = document.getElementById("modal-compile");
  const downloadButton = compileModalElem.querySelector('.download-binary');

  document.querySelectorAll(".action-compile").forEach((upload) => {
    upload.addEventListener('click', (e) => {
      configuration = e.target.getAttribute('data-node');
      const modalInstance = M.Modal.getInstance(compileModalElem);
      const log = compileModalElem.querySelector(".log");
      log.innerHTML = "";
      const stopLogsButton = compileModalElem.querySelector(".stop-logs");
      let stopped = false;
      stopLogsButton.innerHTML = "Stop";
      downloadButton.classList.add('disabled');

      modalInstance.open();

      const filenameField = compileModalElem.querySelector('.filename');
      filenameField.innerHTML = configuration;

      const logSocket = new WebSocket(wsUrl + "/compile");
      logSocket.addEventListener('message', (event) => {
        const data = JSON.parse(event.data);
        if (data.event === "line") {
          const msg = data.data;
          log.innerHTML += colorReplace(msg);
        } else if (data.event === "exit") {
          if (data.code === 0) {
            M.toast({html: "Program exited successfully."});
            downloadButton.classList.remove('disabled');
          } else {
            M.toast({html: `Program failed with code ${data.code}`});
          }

          stopLogsButton.innerHTML = "Close";
          stopped = true;
        }
      });
      logSocket.addEventListener('open', () => {
        const msg = JSON.stringify({configuration: configuration});
        logSocket.send(msg);
      });
      logSocket.addEventListener('close', () => {
        if (!stopped) {
          M.toast({html: 'Terminated process.'});
        }
      });
      modalInstance.options.onCloseStart = () => {
        logSocket.close();
      };
    });
  });
  downloadButton.addEventListener('click', () => {
    const link = document.createElement("a");
    link.download = name;
    link.href = '/download.bin?configuration=' + encodeURIComponent(configuration);
    link.click();
  });

  const modalSetupElem = document.getElementById("modal-wizard");
  const setupWizardStart = document.getElementById('setup-wizard-start');
  const startWizard = () => {
    const modalInstance = M.Modal.getInstance(modalSetupElem);
    modalInstance.open();

    modalInstance.options.onCloseStart = () => {

    };

    $('.stepper').activateStepper({
      linearStepsNavigation: false,
      autoFocusInput: true,
      autoFormCreation: true,
      showFeedbackLoader: true,
      parallel: false
    });
  };
  setupWizardStart.addEventListener('click', startWizard);
</script>

{% if len(files) == 0 %}
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const tapTargetElem = document.querySelector('.tap-target.setup-wizard');
    const tapTargetInstance = M.TapTarget.getInstance(tapTargetElem);
    tapTargetInstance.options.onOpen = () => {
      $('.tap-target-origin').on('click', () => {
        startWizard();
      });
    };
    tapTargetInstance.open();
  });
</script>
{% end %}

{% if begin %}
<script>
  window.history.replaceState({}, document.title, "/");
  document.addEventListener('DOMContentLoaded', () => {
    const tapTargetElem = document.querySelector('.tap-target.select-port');
    const tapTargetInstance = M.TapTarget.getInstance(tapTargetElem);
    tapTargetInstance.open();

    tapTargetInstance.contentEl.style["top"] = "300px";
    tapTargetInstance.contentEl.style["padding"] = "250px";
    tapTargetInstance.waveEl.style["top"] = "250px";
    tapTargetInstance.waveEl.style["left"] = "250px";
    tapTargetInstance.waveEl.style["width"] = "300px";
    tapTargetInstance.waveEl.style["height"] = "300px";
  });
</script>
{% end %}

</body>
</html>