//     Copyright © 2011-2025 Cambridge Intelligence Limited.
//     All rights reserved.
//
//     Sample Code
//!    Interact with the time bar to explore keyboard, mouse and touch events.
import KeyLines from "keylines";
import data from "./tbevents-data.js";

let timebar;

const selections = [
  { id: "07086312446-07691640598", index: 0 },
  { id: "07456622368-07252637450", index: 1 },
];

const events = [
  "change",
  "click",
  "context-menu",
  "double-click",
  "drag-end",
  "drag-start",
  "end",
  "hover",
  "key-down",
  "key-up",
  "pause",
  "play",
  "pointer-down",
  "pointer-move",
  "pointer-up",
  "start",
  "wheel",
];

const ignoreList = ["pointer-move"];

let eventDetails = {};
let eventCounter = 0;

function positionModifierKeys() {
  const tbDivRect = document
    .getElementById("kltimebar")
    .getBoundingClientRect();
  const modifierKeysDiv = document.getElementById("modifier-keys");
  modifierKeysDiv.style.top = `${tbDivRect.top}px`;
  modifierKeysDiv.style.left = `${tbDivRect.left}px`;
  modifierKeysDiv.style.opacity = 1;
}

function initialiseInteractions() {
  // Create a map of which events should be listened to
  const onEvents = new Map(
    events
      .filter((name) => ignoreList.indexOf(name) === -1)
      .map((event) => [event, true])
  );

  // Get all the elements we wish to be able to modify
  const eventsDropdown = document.getElementById("eventsDropdown");
  const eventsContainer = document.getElementById("eventsContainer");
  const eventsList = document.getElementById("eventsList");
  const noEventsButton = document.getElementById("noEvents");
  const allEventsButton = document.getElementById("allEvents");
  const eventNameDisplay = document.getElementById("eventName");
  const eventDetailsPre = document.getElementById("eventDetails");
  const prevEventButton = document.getElementById("prevEvent");
  const nextEventButton = document.getElementById("nextEvent");
  const eventLog = document.getElementById("eventLog");
  const eventsTable = document.getElementById("events");
  const clearButton = document.getElementById("clear");
  // Get the bounds of the event log so can ensure the highlighted event is in view
  const {
    top: eventLogTop,
    bottom: eventLogBottom,
  } = eventLog.getBoundingClientRect();

  function displayEvent(evtId) {
    const details = eventDetails[evtId];
    const json = JSON.stringify(details.event, undefined, 2);
    eventDetailsPre.innerHTML = json;
    eventDetailsPre.dataset.id = evtId;
    eventNameDisplay.innerHTML = `: '${details.name}'`;

    // Disable the previous / next event buttons if no such event exists
    prevEventButton.disabled = !eventDetails[evtId - 1];
    nextEventButton.disabled = !eventDetails[evtId + 1];
  }

  function highlightRow(eventRow) {
    [...document.getElementsByClassName("selectedEvent")].forEach((el) => {
      el.classList.remove("selectedEvent");
    });
    eventRow.classList.add("selectedEvent");
    // Check if row is visible, if not scroll into view
    const distToTop = eventRow.getBoundingClientRect().top - eventLogTop;
    if (distToTop < 0) {
      eventLog.scrollTop += distToTop;
    } else {
      const distToBottom =
        eventRow.getBoundingClientRect().bottom - eventLogBottom;
      if (distToBottom > 0) {
        eventLog.scrollTop += distToBottom;
      }
    }
  }

  function onRowClick(rowClickEvt) {
    const row = rowClickEvt.currentTarget;
    highlightRow(row);
    displayEvent(parseInt(row.dataset.lastId, 10));
  }

  function displayNextEvent() {
    // Get the ID of the next event
    const evtId = parseInt(eventDetailsPre.dataset.id, 10) + 1;
    // Check if it is grouped in the currently highlighted row
    const currEvtRow = document.getElementsByClassName("selectedEvent")[0];
    if (parseInt(currEvtRow.dataset.lastId, 10) < evtId) {
      highlightRow(currEvtRow.previousSibling);
    }
    // Display the details of this event
    displayEvent(evtId);
  }

  function displayPrevEvent() {
    // Get the ID of the previous event
    const evtId = parseInt(eventDetailsPre.dataset.id, 10) - 1;
    // Check if it is grouped in the currently highlighted row
    const currEvtRow = document.getElementsByClassName("selectedEvent")[0];
    if (parseInt(currEvtRow.dataset.firstId, 10) > evtId) {
      highlightRow(currEvtRow.nextSibling);
    }
    // Display the details of this event
    displayEvent(evtId);
  }

  // Add the HTML for the event filter
  events.forEach((event) => {
    // Create a new element
    const eventLabel = document.createElement("label");
    eventLabel.innerHTML = `<input type="checkbox" id="${event}" ${
      onEvents.has(event) ? 'checked="checked"' : ""
    }"> ${event}`;
    const eventLabelinput = eventLabel.querySelector("input");
    // Add the on change listener
    eventLabel.addEventListener("change", () => {
      onEvents.set(event, eventLabelinput.checked);
    });

    // Make the no events button disable this input
    noEventsButton.addEventListener("click", () => {
      eventLabelinput.checked = false;
      onEvents.set(event, false);
    });

    // Make the all events button enable this input
    allEventsButton.addEventListener("click", () => {
      eventLabelinput.checked = true;
      onEvents.set(event, true);
    });

    // Add it to the event list
    eventsList.append(eventLabel);
  });

  clearButton.addEventListener("click", () => {
    clearButton.classList.add("hide");
    eventsTable.innerHTML = "";
    eventNameDisplay.innerHTML = "";
    eventDetailsPre.innerHTML = "No event selected";
    eventDetailsPre.dataset.id = undefined;
    prevEventButton.disabled = true;
    nextEventButton.disabled = true;
    eventDetails = {};
  });

  eventsDropdown.addEventListener("click", (event) => {
    const classList = eventsContainer.classList;
    if (classList.contains("hide")) classList.remove("hide");
    else classList.add("hide");
    event.stopPropagation();
  });

  document.addEventListener("click", (event) => {
    if (event.target.closest("#eventsContainer") === null) {
      eventsContainer.classList.add("hide");
    }
  });

  nextEventButton.addEventListener("click", displayNextEvent);
  prevEventButton.addEventListener("click", displayPrevEvent);

  // Attach an event handler to all the time bar events
  timebar.on("all", (evt) => {
    // Update the active modifier keys
    if (evt.event && evt.event.modifierKeys) {
      Object.keys(evt.event.modifierKeys).forEach((key) => {
        if (evt.event.modifierKeys[key]) {
          document.getElementById(key).classList.add("active");
        } else {
          document.getElementById(key).classList.remove("active");
        }
      });
    }
    if (onEvents.get(evt.name)) {
      // Add this event to the log
      const eventObject = {};
      if (evt.event) {
        Object.keys(evt.event).forEach((prop) => {
          if (prop !== "defaultPrevented") eventObject[prop] = evt.event[prop];
        });
      }
      const lastEvt = eventDetails[eventCounter++];
      eventDetails[eventCounter] = { name: evt.name, event: eventObject };
      // Check if a repeat of the previous event
      if (
        eventObject &&
        lastEvt &&
        evt.name === lastEvt.name &&
        eventObject.id === lastEvt.event.id
      ) {
        // Group it with the previous event and up the counter for that event
        const td = document.getElementsByClassName("eventname")[0];
        const rowData = td.parentElement.dataset;
        const countSpan = td.children[0];
        countSpan.innerText = `(${eventCounter - rowData.firstId + 1})`;
        rowData.lastId = eventCounter;
      } else {
        // Add it to the top of the event log
        const tr = document.createElement("tr");
        tr.innerHTML = `<td id='${eventCounter}' class='eventname'>'${evt.name}'<span></span></td>`;
        tr.dataset.firstId = eventCounter;
        tr.dataset.lastId = eventCounter;
        tr.onclick = onRowClick;
        eventsTable.insertBefore(tr, eventsTable.firstChild);
      }
      // Ensure the top row is highlighed
      highlightRow(
        document.getElementsByClassName("eventname")[0].parentElement
      );
      // Update the displayed event details to show this event
      displayEvent(eventCounter);

      // Unhide the clear button
      clearButton.classList.remove("hide");
    }
  });

  // Hide the modifier keys when open the source tab
  [...document.getElementsByClassName("tablinks")].forEach((el) => {
    el.addEventListener("click", () => {
      document.getElementById("modifier-keys").style.opacity =
        el.name === "sourceTab" ? 0 : 1;
    });
  });
}

async function startKeyLines() {
  const options = {
    showExtend: true,
  };
  timebar = await KeyLines.create({
    container: "kltimebar",
    type: "timebar",
    options,
  });
  await timebar.load(data);
  timebar.selection(selections);
  timebar.zoom("fit", { animate: false });

  // Position the modifier keys overlay on the time bar
  positionModifierKeys();
  window.addEventListener("resize", positionModifierKeys);
  // Deactivate them when the time bar isn't focussed
  document.getElementById("kltimebar").addEventListener("focusout", () => {
    [...document.getElementsByClassName("key")].forEach((el) =>
      el.classList.remove("active")
    );
  });

  initialiseInteractions();
}

window.addEventListener("DOMContentLoaded", startKeyLines);
<!DOCTYPE html>
<html lang="en" style="background-color: #2d383f;">
  <head>
    <meta charset="UTF-8">
    <title>Time Bar Events</title>
    <link rel="stylesheet" type="text/css" href="/css/keylines.css">
    <link rel="stylesheet" type="text/css" href="/css/minimalsdk.css">
    <link rel="stylesheet" type="text/css" href="/css/sdk-layout.css">
    <link rel="stylesheet" type="text/css" href="/css/demo.css">
    <link rel="stylesheet" type="text/css" href="/tbevents.css">
    <script src="/vendor/jquery.js" defer type="text/javascript"></script>
    <script id="keylines" src="/public/keylines.umd.cjs" defer type="text/javascript"></script>
    <script src="/tbevents.js" crossorigin="use-credentials" defer type="module"></script>
  </head>
  <body>
    <div class="chart-wrapper demo-cols">
      <div class="tab-content-panel flex1" id="lhs" data-tab-group="rhs">
        <div class="toggle-content is-visible" id="lhsVisual" style="width:100%; height: 100%;">
          <div class="klchart" id="kltimebar">
          </div>
        </div>
      </div>
      <div class="rhs citext closed" id="demorhscontent">
        <div class="title-bar">
          <svg viewBox="0 0 360 60" style="max-width: 360px; max-height: 60px; font-size: 24px; font-family: Raleway; flex: 1;">
            <text x="0" y="38" style="width: 100%;">Time Bar Events</text>
          </svg>
        </div>
        <div class="tab-content-panel" data-tab-group="rhs">
          <div class="toggle-content is-visible tabcontent" id="controlsTab">
            <p>Interact with the time bar using mouse, keyboard and touch.</p>
            <form autocomplete="off" onsubmit="return false" id="rhsForm">
              <div class="cicontent">
                <div id="eventsDropdown">Filter events<i class="icon-chevron-down pull-right"></i></div>
                <div class="hide span4" id="eventsContainer">
                  <div id="eventsList"></div>
                  <input class="btn" id="noEvents" type="button" value="None">
                  <input class="btn" id="allEvents" type="button" value="All">
                </div>
                <fieldset>
                  <legend>Event details<span id="eventName"></span></legend>
                  <pre id="eventDetails" style="-webkit-user-select: text; -khtml-user-select: text; -moz-user-select: text; -ms-user-select: text; word-break: keep-all;">No event selected</pre>
                  <div>
                    <input class="pull-right" id="nextEvent" type="button" value="Next event" disabled>
                    <input id="prevEvent" type="button" value="Previous event" disabled>
                  </div>
                </fieldset>
                <fieldset>
                  <legend>Event list</legend>
                  <div id="eventLog">
                    <table class="table table-hover table-condensed">
                      <tbody id="events"></tbody>
                    </table>
                  </div>
                  <input class="btn pull-right hide" type="button" value="Clear" id="clear">
                </fieldset>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
    <div id="moreParent">
      <div id="moreContainer">
        <div id="modifier-keys" style="position: absolute; pointer-events: none;"><span class="key" id="alt">alt</span><span class="key" id="ctrl">ctrl</span><span class="key" id="consistentCtrl">consistentCtrl</span><span class="key" id="meta">meta</span><span class="key" id="shift">shift</span></div>
        <script type="text/html" id="eventsTmpl">{{#events}}
          <label class="checkbox">
            <input type="checkbox" id="check{{.}}" checked="checked">{{.}}
          </label>{{/events}}
        </script>
      </div>
    </div>
    <div id="lazyScripts">
    </div>
  </body>
</html>
#events .eventname{
  color: #00aa70;
  font-family: Monaco, Andale Mono, Courier New, monospace;
  padding: 4px 5px;
  font-size: 14px;
  cursor: pointer;
}

#eventsDropdown {
  border-color: #cacdcf;
  border-style: solid;
  border-width: 1px;
  border-radius: 3px;
  padding: 6px;
  cursor: pointer;
  background-color: #fff;
  margin-top: 10px;
}

#eventsDropdown .icon-chevron-down {
  width: 12px;
  height: 12px;
  border-color: #333;
  border-style: solid;
  border-width: 4px 4px 0px 0px;
  transform: rotate(135deg);
  margin-right: 5px;
}

#eventsContainer {
  width: 368px;
  border-width: 1px;
  border-color: #cacdcf;
  border-top: 0px;
  border-style: solid;
  border-radius: 3px;
  padding-bottom: 8px;
  background-color: #ffffff;
  position: absolute;
  margin-left: 0px;
  z-index: 1000;
  scrollbar-width: none;
}

#eventsList {
  padding: 6px;
  width: 100%;
}

#eventsList label {
  display: inline-block;
  margin: 4px 50px 4px 0px;
  width: calc(49% - 50px);
  cursor: pointer;
}

#noEvents {
  margin-left: 6px;
}

#eventLog {
  height:250px;
  overflow-y: auto;
  margin-bottom: 10px;
  clear:both;
  border: 1px solid #ccc;
  background-color: #fff;
  cursor: ns-resize;
}

#eventLog table {
  margin-bottom: 5px;
}

#events .selectedEvent {
  background-color: #ddd;
}

#eventDetails {
  height: 250px;
  width: 100%;
  overflow-y: auto;
}

#modifier-keys {
  opacity: 0;
}

.key {
  background-color: #b2df8a;
  border: 2px solid #ffffff;
  display: inline-block;
  color: #5E5E5E;
  font: 14px arial;
  text-decoration: none;
  text-align: center;
  margin: 3px 3px;
  padding: 6px 6px;
}

.key.active {
  border-color: #ffaf00;
  background-color: #ffaf00;
}

/* make the time bar fill the area vertically */
.kltimebar {
  height: 100%;
}

@media (max-width: 979px) {
  #eventsContainer {
    width: 326px;
  }

  #eventsList label {
    margin-right: 11px;
    width: calc(49% - 11px);
  }
}

@media (max-width: 767px) {
  #eventsContainer {
    width: 266px;
  }

  #eventsList label {
    margin-right: 30px;
    width: calc(49% - 30px);
  }
}
Loading source