This commit is contained in:
parent
87f198ae36
commit
6e24ffb499
3 changed files with 81 additions and 32 deletions
2
.swcrc
2
.swcrc
|
@ -7,6 +7,6 @@
|
||||||
"decorators": false,
|
"decorators": false,
|
||||||
"dynamicImport": false
|
"dynamicImport": false
|
||||||
},
|
},
|
||||||
"target": "esnext"
|
"target": "es2022"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 {
|
file-editor-tab:not(:first-child) {
|
||||||
border: 2px solid var(--table-border-color);
|
margin-left: -2px;
|
||||||
padding: 0.5ch 0.8ch;
|
|
||||||
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);
|
|
@ -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",
|
||||||
|
|
Reference in a new issue