neater tabs
continuous-integration/drone Build is passing Details

This commit is contained in:
Hamcha 2023-12-03 17:57:12 +01:00
parent 87f198ae36
commit 6e24ffb499
3 changed files with 81 additions and 32 deletions

2
.swcrc
View File

@ -7,6 +7,6 @@
"decorators": false, "decorators": false,
"dynamicImport": false "dynamicImport": false
}, },
"target": "esnext" "target": "es2022"
} }
} }

View File

@ -1,21 +1,77 @@
import "/static/vendor/ace/ace.js"; import "/static/vendor/ace/ace.js";
import { $el } from "/static/vendor/domutil/domutil.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 { class Editor extends HTMLElement {
private editor: AceAjax.Editor; private editor: AceAjax.Editor;
private tabEl: HTMLDivElement; private tabEl: HTMLDivElement;
private sessions: Record<string, AceAjax.IEditSession>; private sessions: Record<string, AceAjax.IEditSession>;
private root: ShadowRoot; private root: ShadowRoot;
private addTabButton: HTMLDivElement; private addTabButton: EditorTab;
private original: Record<string, string>;
constructor() { constructor() {
super(); super();
this.sessions = {}; this.sessions = {};
this.original = {};
this.root = this.attachShadow({ mode: "open" }); this.root = this.attachShadow({ mode: "open" });
} }
connectedCallback() { connectedCallback() {
this.addTabButton = $el("div", { className: "tab" }, "+"); this.addTabButton = new EditorTab();
this.addTabButton.setAttribute("name", "+");
this.addTabButton.addEventListener("click", () => this.#createNewTab()); this.addTabButton.addEventListener("click", () => this.#createNewTab());
this.tabEl = $el("div", { className: "tab-container" }, this.addTabButton); this.tabEl = $el("div", { className: "tab-container" }, this.addTabButton);
this.root.append(this.tabEl); this.root.append(this.tabEl);
@ -25,19 +81,10 @@ class Editor extends HTMLElement {
.tab-container { .tab-container {
display: flex; display: flex;
margin-bottom: -2px; margin-bottom: -2px;
}
& .tab {
border: 2px solid var(--table-border-color); file-editor-tab:not(:first-child) {
padding: 0.5ch 0.8ch; margin-left: -2px;
background-color: var(--button-bg);
cursor: pointer;
&.active {
background-color: var(--table-border-color);
}
&:not(:first-child) {
margin-left: -2px;
}
}
} }
`; `;
const slot = $el("slot", { name: "body" }); const slot = $el("slot", { name: "body" });
@ -65,23 +112,27 @@ class Editor extends HTMLElement {
* @param name File name * @param name File name
* @param content File contents * @param content File contents
*/ */
addFile(name: string, content: string) { addFile(name: string, content: string, saved = true) {
// Add to session list // Add to session list
this.sessions[name] = ace.createEditSession(content); this.sessions[name] = ace.createEditSession(content);
this.original[name] = content;
// TODO replace this with auto-detection // TODO replace this with auto-detection
this.sessions[name].setMode("ace/mode/nix"); this.sessions[name].setMode("ace/mode/nix");
// Create tab and set as active // Create tab and set as active
const tab = this.#addTab(name); const tab = this.#addTab(name, saved);
tab.click(); tab.click();
this.sessions[name].on("change", () => {
tab.setAttribute("modified", this.sessions[name].getValue() != this.original[name] ? "true" : "false");
});
} }
setCurrent(name: string) { setCurrent(name: string) {
this.editor.setSession(this.sessions[name]); this.editor.setSession(this.sessions[name]);
this.tabEl.querySelectorAll<HTMLDivElement>(".tab").forEach(el => { this.tabEl.querySelectorAll<EditorTab>("file-editor-tab").forEach(el => {
if (el.dataset.name === name) el.classList.add("active"); el.setAttribute("selected", el.getAttribute("name") == name ? "true" : "false");
else el.classList.remove("active");
}); });
} }
@ -94,20 +145,18 @@ class Editor extends HTMLElement {
* @param name Tab name * @param name Tab name
* @returns Tab element * @returns Tab element
*/ */
#addTab(name: string) { #addTab(name: string, saved = true) {
const tab = $el("div", { const tab = new EditorTab();
className: "tab", tab.setAttribute("name", name);
dataset: { name }, tab.setAttribute("unsaved", saved ? "false" : "true");
"@click": () => this.setCurrent(name) tab.addEventListener("click", () => this.setCurrent(name));
}, name);
this.tabEl.insertBefore(tab, this.addTabButton); this.tabEl.insertBefore(tab, this.addTabButton);
return tab; return tab;
} }
#createNewTab() { #createNewTab() {
this.addFile(`untitled${Object.keys(this.sessions).filter(s => s.startsWith("untitled")).length + 1}`, ""); this.addFile(`untitled${Object.keys(this.sessions).filter(s => s.startsWith("untitled")).length + 1}`, "", false);
} }
} }
customElements.define("file-editor", Editor); customElements.define("file-editor", Editor);

View File

@ -1,19 +1,19 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "esnext", "target": "ES2022",
"paths": { "paths": {
"/static/*": [ "/static/*": [
"./static/*" "./static/*"
], ],
}, },
"lib": [ "lib": [
"ESNext", "ES2022",
"DOM" "DOM"
], ],
"rootDir": "./", "rootDir": "./",
"baseUrl": "./", "baseUrl": "./",
"strictNullChecks": true, "strictNullChecks": true,
"declaration": true, "declaration": true
}, },
"include": [ "include": [
"./static/**/*.ts", "./static/**/*.ts",