staxman-old/static/scripts/components/editor/script.ts

162 lines
4.2 KiB
TypeScript

import "/static/vendor/ace/ace.js";
import { $el } from "/static/vendor/domutil/domutil.js";
class EditorTab extends HTMLElement {
static observedAttributes = ["name", "selected"];
private tabEl: HTMLDivElement;
private buttonsEl: HTMLDivElement;
private root: ShadowRoot;
constructor() {
super();
this.root = this.attachShadow({ mode: "open" });
const style = $el("style");
style.textContent = `
:host {
border: 2px solid var(--table-border-color);
padding: 0.5ch 0.8ch;
background-color: var(--button-bg);
cursor: pointer;
}
:host([selected="true"]) {
background-color: var(--table-border-color);
}
:host([modified="true"]) .tab-name::after {
content: " ᴹ";
}
:host([unsaved="true"]) {
font-style: italic;
}
`;
this.root.append(style);
this.tabEl = $el("div", { className: "tab-name" }, this.getAttribute("name") || $el("i", "unknown"));
this.buttonsEl = $el("div", { className: "buttons" });
this.root.append(this.tabEl, this.buttonsEl);
}
connectedCallback() {
}
attributeChangedCallback(name, _, value) {
switch (name) {
case "name":
this.tabEl.textContent = value;
break;
case "selected":
this.classList.toggle("active", Boolean(value));
break;
}
}
}
customElements.define("file-editor-tab", EditorTab);
class Editor extends HTMLElement {
private editor: AceAjax.Editor;
private tabEl: HTMLDivElement;
private sessions: Record<string, AceAjax.IEditSession>;
private root: ShadowRoot;
private addTabButton: EditorTab;
private original: Record<string, string>;
constructor() {
super();
this.sessions = {};
this.original = {};
this.root = this.attachShadow({ mode: "open" });
}
connectedCallback() {
this.addTabButton = new EditorTab();
this.addTabButton.setAttribute("name", "+");
this.addTabButton.addEventListener("click", () => this.#createNewTab());
this.tabEl = $el("div", { className: "tab-container" }, this.addTabButton);
this.root.append(this.tabEl);
const style = $el("style");
style.textContent = `
.tab-container {
display: flex;
margin-bottom: -2px;
}
file-editor-tab:not(:first-child) {
margin-left: -2px;
}
`;
const slot = $el("slot", { name: "body" });
this.root.append(style, slot);
const editorEl = $el("div", { slot: "body" });
this.append(editorEl);
// Create editor
ace.config.set('basePath', '/static/vendor/ace')
this.editor = ace.edit(editorEl);
// todo make this stuff customizable??
this.editor.setTheme("ace/theme/dracula");
this.editor.setOptions({
fontFamily: "Iosevka Web",
fontSize: "12pt",
});
this.editor.getSession().setUseWrapMode(true);
this.editor.setKeyboardHandler("ace/keyboard/vim");
}
/**
* Add a file editing session
* @param name File name
* @param content File contents
*/
addFile(name: string, content: string, saved = true) {
// Add to session list
this.sessions[name] = ace.createEditSession(content);
this.original[name] = content;
// TODO replace this with auto-detection
this.sessions[name].setMode("ace/mode/nix");
// Create tab and set as active
const tab = this.#addTab(name, saved);
tab.click();
this.sessions[name].on("change", () => {
tab.setAttribute("modified", this.sessions[name].getValue() != this.original[name] ? "true" : "false");
});
}
setCurrent(name: string) {
this.editor.setSession(this.sessions[name]);
this.tabEl.querySelectorAll<EditorTab>("file-editor-tab").forEach(el => {
el.setAttribute("selected", el.getAttribute("name") == name ? "true" : "false");
});
}
files(): [String, String][] {
return Object.entries(this.sessions).map(([name, session]) => [name, session.getValue()]);
}
/**
* Create a new tab
* @param name Tab name
* @returns Tab element
*/
#addTab(name: string, saved = true) {
const tab = new EditorTab();
tab.setAttribute("name", name);
tab.setAttribute("unsaved", saved ? "false" : "true");
tab.addEventListener("click", () => this.setCurrent(name));
this.tabEl.insertBefore(tab, this.addTabButton);
return tab;
}
#createNewTab() {
this.addFile(`untitled${Object.keys(this.sessions).filter(s => s.startsWith("untitled")).length + 1}`, "", false);
}
}
customElements.define("file-editor", Editor);