JS: Move JavaScript to commandbar subdirectory
This commit is contained in:
parent
ccd2a25920
commit
563693fb79
5 changed files with 0 additions and 0 deletions
267
assets/commandbar/js/commandBar.js
Normal file
267
assets/commandbar/js/commandBar.js
Normal file
|
@ -0,0 +1,267 @@
|
|||
import { Command } from "./command.js"
|
||||
import { CommandExecutor } from "./commandExecutor.js";
|
||||
|
||||
class CommandBar extends HTMLElement {
|
||||
static observedAttributes = [
|
||||
"presented"
|
||||
];
|
||||
|
||||
#shadowRoot;
|
||||
rootElement;
|
||||
inputElement;
|
||||
rulerElement;
|
||||
scrollPositionElement;
|
||||
|
||||
#leader = ":";
|
||||
|
||||
#commandExecutor;
|
||||
#indexOfLastShownCommandFromHistory = null;
|
||||
#shouldPresentAfterConnection = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#commandExecutor = new CommandExecutor(this);
|
||||
this.#shadowRoot = this.attachShadow({ mode: "open" });
|
||||
}
|
||||
|
||||
get #elementHasPresentedAttribute() {
|
||||
return this.rootElement.hasAttribute("presented");
|
||||
}
|
||||
|
||||
// MARK: Presentation
|
||||
|
||||
show(options) {
|
||||
if (options === undefined || options === null) {
|
||||
options = {
|
||||
leader: null,
|
||||
};
|
||||
}
|
||||
|
||||
const inputElement = this.inputElement;
|
||||
if (inputElement) {
|
||||
inputElement.value = options.leader ? options.leader.toString() : "";
|
||||
inputElement.focus();
|
||||
}
|
||||
|
||||
this.rootElement?.setAttribute("presented", "");
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (!this.#elementHasPresentedAttribute) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputElement = this.inputElement;
|
||||
if (inputElement) {
|
||||
inputElement.value = "";
|
||||
inputElement.blur();
|
||||
}
|
||||
|
||||
this.rootElement?.removeAttribute("presented");
|
||||
}
|
||||
|
||||
// MARK: Command Handling
|
||||
|
||||
executeCommand(commandString) {
|
||||
this.#indexOfLastShownCommandFromHistory = null;
|
||||
|
||||
try {
|
||||
const command = Command.parse(commandString);
|
||||
this.#commandExecutor.executeCommand(command);
|
||||
} catch (e) {
|
||||
console.error("Error executing command:", e);
|
||||
}
|
||||
}
|
||||
|
||||
addCommandHandler(name, handler) {
|
||||
this.#commandExecutor.addHandler(name, handler);
|
||||
}
|
||||
|
||||
updateRuler(x, y) {
|
||||
this.rulerElement.innerText = `${x || 0},${y || 0}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the percentage that the document is scrolled relative to the full length
|
||||
* of the document. This algorithm is largely cribbed from neovim's sources.
|
||||
*
|
||||
* @param scrollingElement The element on which to track scrolling
|
||||
*/
|
||||
updateScrollPosition(scrollingElement) {
|
||||
if (!this.scrollPositionElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollY = scrollingElement.scrollTop;
|
||||
const scrollHeight = scrollingElement.clientHeight;
|
||||
const totalHeight = scrollingElement.scrollHeight;
|
||||
const bottomY = scrollY + scrollHeight;
|
||||
|
||||
const pixelLinesBelow = totalHeight - bottomY;
|
||||
|
||||
const scrollPercentage = Math.round(scrollY * 100 / (scrollY + pixelLinesBelow));
|
||||
|
||||
let newScrollPositionText;
|
||||
if (pixelLinesBelow <= 0) {
|
||||
newScrollPositionText = "Bot";
|
||||
} else if (scrollY <= 0) {
|
||||
newScrollPositionText = "Top";
|
||||
} else {
|
||||
newScrollPositionText = `${scrollPercentage}%`;
|
||||
}
|
||||
|
||||
const currentScrollPositionText = this.scrollPositionElement.innerText;
|
||||
const scrollPositionIsEmpty = !currentScrollPositionText || currentScrollPositionText.length === 0;
|
||||
|
||||
if (scrollPositionIsEmpty || newScrollPositionText != currentScrollPositionText) {
|
||||
this.scrollPositionElement.innerText = newScrollPositionText;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Custom Element
|
||||
|
||||
connectedCallback() {
|
||||
let template = document.getElementById("command-bar-template");
|
||||
console.assert(template, "Couldn't find CommandBar component template in the document");
|
||||
|
||||
const shadowRoot = this.#shadowRoot;
|
||||
shadowRoot.appendChild(template.content.cloneNode(true));
|
||||
|
||||
this.rootElement = shadowRoot.querySelector(".command-bar");
|
||||
this.rulerElement = shadowRoot.querySelector(".command-bar__ruler");
|
||||
this.scrollPositionElement = shadowRoot.querySelector(".command-bar__scrollposition");
|
||||
|
||||
const inputElement = shadowRoot.querySelector(".command-bar__input");
|
||||
this.inputElement = inputElement;
|
||||
|
||||
this.updateRuler();
|
||||
this.updateScrollPosition(document.scrollingElement);
|
||||
|
||||
inputElement.addEventListener("input", this.handleInputEvent.bind(this));
|
||||
inputElement.addEventListener("keydown", this.handleInputKeydownEvent.bind(this));
|
||||
|
||||
document.addEventListener("keypress", event => this.handleDocumentKeypressEvent(event));
|
||||
document.addEventListener("mousemove", event => this.updateRuler(event.x, event.y));
|
||||
document.addEventListener("scroll", () => this.updateScrollPosition(document.scrollingElement));
|
||||
|
||||
if (this.#shouldPresentAfterConnection) {
|
||||
this.show({ leader: this.#leader });
|
||||
this.#shouldPresentAfterConnection = false;
|
||||
}
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, _oldValue, newValue) {
|
||||
if (name === "presented") {
|
||||
const rootElement = this.rootElement;
|
||||
if (rootElement) {
|
||||
rootElement.setAttribute("presented", newValue !== undefined && newValue !== null);
|
||||
} else {
|
||||
// The CommandBar custom element hasn't been connected to a document yet.
|
||||
this.#shouldPresentAfterConnection = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Event Listeners
|
||||
|
||||
handleInputEvent(event) {
|
||||
const target = event.target;
|
||||
if (!target.value.length) {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
handleInputKeydownEvent(event) {
|
||||
const target = event.target;
|
||||
|
||||
switch (event.key) {
|
||||
case "Escape": {
|
||||
event.preventDefault();
|
||||
target.value = "";
|
||||
this.hide();
|
||||
break;
|
||||
}
|
||||
case "Enter": {
|
||||
event.preventDefault();
|
||||
|
||||
// Trim the leader
|
||||
let command = target.value.substring(1);
|
||||
if (command.length) {
|
||||
this.executeCommand(command);
|
||||
}
|
||||
|
||||
target.value = this.#leader;
|
||||
break;
|
||||
}
|
||||
case "ArrowUp": {
|
||||
event.preventDefault();
|
||||
|
||||
let historyIndex = this.#indexOfLastShownCommandFromHistory;
|
||||
if (historyIndex === null) {
|
||||
historyIndex = this.#commandExecutor.historyLength - 1;
|
||||
} else {
|
||||
historyIndex--;
|
||||
}
|
||||
|
||||
if (historyIndex >= 0) {
|
||||
const commandFromHistory = this.#commandExecutor.commandAtHistoryIndex(historyIndex);
|
||||
target.value = this.#leader + commandFromHistory.originalString;
|
||||
this.#indexOfLastShownCommandFromHistory = historyIndex;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "ArrowDown": {
|
||||
event.preventDefault();
|
||||
|
||||
const historyLength = this.#commandExecutor.historyLength;
|
||||
|
||||
let historyIndex = this.#indexOfLastShownCommandFromHistory;
|
||||
if (historyIndex === null) {
|
||||
historyIndex = historyLength - 1;
|
||||
}
|
||||
historyIndex++;
|
||||
|
||||
if (historyIndex < historyLength) {
|
||||
const commandFromHistory = this.#commandExecutor.commandAtHistoryIndex(historyIndex);
|
||||
target.value = this.#leader + commandFromHistory.originalString;
|
||||
this.#indexOfLastShownCommandFromHistory = historyIndex;
|
||||
} else {
|
||||
target.value = this.#leader;
|
||||
this.#indexOfLastShownCommandFromHistory = null;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleDocumentKeypressEvent(event) {
|
||||
if (event.repeat) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key == "Escape") {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
const currentTarget = event.currentTarget;
|
||||
if (currentTarget === this.inputElement) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case this.#leader:
|
||||
this.show();
|
||||
break;
|
||||
case "Escape":
|
||||
this.hide();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define("command-bar", CommandBar);
|
Loading…
Add table
Add a link
Reference in a new issue