rebuild editor setting ui, bind open and save events

This commit is contained in:
Rachel Powers
2024-04-06 14:55:39 -07:00
parent 00887575ce
commit 568601418a
10 changed files with 712 additions and 553 deletions

View File

@@ -9,8 +9,7 @@ import "../editor";
import { IC10Editor } from "../editor";
import { Session } from "../session";
import { VirtualMachine } from "../virtual_machine";
import { openFile, saveFile } from "../utils";
@customElement("ic10emu-app")
export class App extends BaseElement {
@@ -42,7 +41,7 @@ export class App extends BaseElement {
editorSettings: { fontSize: number; relativeLineNumbers: boolean };
get editor() {
return this.renderRoot.querySelector('ace-ic10') as IC10Editor;
return this.renderRoot.querySelector("ace-ic10") as IC10Editor;
}
vm!: VirtualMachine;
@@ -56,10 +55,18 @@ export class App extends BaseElement {
}
protected createRenderRoot(): HTMLElement | DocumentFragment {
const root = super.createRenderRoot();
root.addEventListener('app-share-session', this._handleShare);
root.addEventListener('app-open-file', this._handleOpenFile);
root.addEventListener('app-save-as', this._handleSaveAs);
return root;
}
protected render(): HTMLTemplateResult {
return html`
<div class="app-container">
<app-nav class=z-fix></app-nav>
<app-nav class="z-fix"></app-nav>
<div class="app-body">
<sl-split-panel
style="--min: 20em; --max: calc(100% - 20em);"
@@ -74,6 +81,22 @@ export class App extends BaseElement {
</div>
`;
}
firstUpdated(): void {
}
_handleShare(_e: Event) {
// TODO:
}
_handleSaveAs(_e: Event) {
saveFile(window.Editor.editorValue);
}
_handleOpenFile(_e: Event) {
openFile(window.Editor.editor);
}
}
declare global {
@@ -81,4 +104,3 @@ declare global {
App?: App;
}
}

View File

@@ -6,6 +6,7 @@ import "@shoelace-style/shoelace/dist/components/menu/menu.js";
import "@shoelace-style/shoelace/dist/components/divider/divider.js";
import "@shoelace-style/shoelace/dist/components/menu-item/menu-item.js";
import "@shoelace-style/shoelace/dist/components/dropdown/dropdown.js";
import SlMenuItem from "@shoelace-style/shoelace/dist/components/menu-item/menu-item.js";
@customElement("app-nav")
export class Nav extends BaseElement {
@@ -100,15 +101,15 @@ export class Nav extends BaseElement {
label="Main Menu"
></sl-icon-button>
<sl-menu class="menu">
<sl-menu class="menu" @sl-select=${this._menuClickHandler}>
<sl-menu-item value="share">Share</sl-menu-item>
<sl-menu-item value="openFile">Open File</sl-menu-item>
<sl-menu-item value="saveAs">Save As</sl-menu-item>
<sl-devider></sl-devider>
<sl-divider></sl-divider>
<sl-menu-item value="editorSettings"
>Editor Settings</sl-menu-item
>
<sl-devider></sl-devider>
<sl-divider></sl-divider>
<sl-menu-item value="keyboardShortcuts"
>Editor Keyboard Shortcuts</sl-menu-item
>
@@ -161,4 +162,31 @@ export class Nav extends BaseElement {
</nav>
`;
}
firstUpdated(): void {}
_menuClickHandler(e: CustomEvent) {
const item = e.detail.item as SlMenuItem;
switch (item.value) {
case "share":
this.dispatchEvent(
new CustomEvent("app-share-session", { bubbles: true }),
);
break;
case "openFile":
this.dispatchEvent(new CustomEvent("app-open-file", { bubbles: true }));
break;
case "saveAs":
this.dispatchEvent(new CustomEvent("app-save-as", { bubbles: true }));
break;
case "editorSettings":
window.Editor?.settingDialog.show();
break;
case "keyboardShortcuts":
// TODO: bind
break;
default:
console.log("Unknown main menu item", item.value);
}
}
}

View File

@@ -1,43 +0,0 @@
import * as ace from "ace-code"
// make sure Ace can load through webpack
// trimmed down version of https://github.com/ajaxorg/ace-builds/blob/master/esm-resolver.js but for ace-code
ace.config.setModuleLoader("ace/theme/one_dark", () => import("ace-code/src/theme/one_dark"));
ace.config.setModuleLoader("ace/theme/textmate", () => import("ace-code/src/theme/textmate"));
ace.config.setModuleLoader('ace/ext/beautify', () => import('ace-code/src/ext/beautify.js'));
ace.config.setModuleLoader('ace/ext/code_lens', () => import('ace-code/src/ext/code_lens.js'));
ace.config.setModuleLoader('ace/ext/command_bar', () => import('ace-code/src/ext/command_bar.js'));
ace.config.setModuleLoader('ace/ext/elastic_tabstops_lite', () => import('ace-code/src/ext/elastic_tabstops_lite.js'));
ace.config.setModuleLoader('ace/ext/emmet', () => import('ace-code/src/ext/emmet.js'));
ace.config.setModuleLoader('ace/ext/error_marker', () => import('ace-code/src/ext/error_marker.js'));
ace.config.setModuleLoader('ace/ext/hardwrap', () => import('ace-code/src/ext/hardwrap.js'));
ace.config.setModuleLoader('ace/ext/inline_autocomplete', () => import('ace-code/src/ext/inline_autocomplete.js'));
ace.config.setModuleLoader('ace/ext/keyboard_menu', () => import('ace-code/src/ext/keybinding_menu.js'));
ace.config.setModuleLoader('ace/ext/language_tools', () => import('ace-code/src/ext/language_tools.js'));
ace.config.setModuleLoader('ace/ext/linking', () => import('ace-code/src/ext/linking.js'));
ace.config.setModuleLoader('ace/ext/modelist', () => import('ace-code/src/ext/modelist.js'));
ace.config.setModuleLoader('ace/ext/options', () => import('ace-code/src/ext/options.js'));
// ace.config.setModuleLoader('ace/ext/prompt', () => import('ace-code/src/ext/prompt.js'));
ace.config.setModuleLoader('ace/ext/prompt', () => import('./prompt_patch'));
ace.config.setModuleLoader('ace/ext/rtl', () => import('ace-code/src/ext/rtl.js'));
ace.config.setModuleLoader('ace/ext/searchbox', () => import('ace-code/src/ext/searchbox.js'));
// ace.config.setModuleLoader('ace/ext/settings_menu', () => import('ace-code/src/ext/settings_menu.js'));
ace.config.setModuleLoader('ace/ext/simple_tokenizer', () => import('ace-code/src/ext/simple_tokenizer.js'));
ace.config.setModuleLoader('ace/ext/spellcheck', () => import('ace-code/src/ext/spellcheck.js'));
ace.config.setModuleLoader('ace/ext/split', () => import('ace-code/src/ext/split.js'));
ace.config.setModuleLoader('ace/ext/static_highlight', () => import('ace-code/src/ext/static_highlight.js'));
ace.config.setModuleLoader('ace/ext/statusbar', () => import('ace-code/src/ext/statusbar.js'));
ace.config.setModuleLoader('ace/ext/textarea', () => import('ace-code/src/ext/textarea.js'));
ace.config.setModuleLoader('ace/ext/themelist', () => import('ace-code/src/ext/themelist.js'));
ace.config.setModuleLoader('ace/ext/whitespace', () => import('ace-code/src/ext/whitespace.js'));
ace.config.setModuleLoader('ace/keyboard/emacs', () => import('ace-code/src/keyboard/emacs.js'));
ace.config.setModuleLoader('ace/keyboard/sublime', () => import('ace-code/src/keyboard/sublime.js'));
ace.config.setModuleLoader('ace/keyboard/vim', () => import('ace-code/src/keyboard/vim.js'));
ace.config.setModuleLoader('ace/keyboard/vscode', () => import('ace-code/src/keyboard/vscode.js'));
ace.config.setModuleLoader('ace/range', () => import('ace-code/src/range'));
console.log("ace module loaders patched");
export { ace };

26
www/src/js/editor/ace.ts Normal file
View File

@@ -0,0 +1,26 @@
import ace from "ace-builds";
import "ace-builds/esm-resolver";
import { AceLanguageClient } from "ace-linters/build/ace-language-client";
// to make sure language tools are loaded
ace.config.loadModule("ace/ext/language_tools");
import { Mode as TextMode } from "ace-builds/src-noconflict/mode-text";
export async function setupLspWorker() {
// Create a web worker
let worker = new Worker(new URL("./lspWorker.ts", import.meta.url));
const loaded = (w: Worker) =>
new Promise((r) => w.addEventListener("message", r, { once: true }));
await Promise.all([loaded(worker)]);
// Register the editor with the language provider
return worker;
}
export import EditSession = ace.Ace.EditSession;
import { Range } from "ace-builds";
export { ace, TextMode, Range, AceLanguageClient }

View File

@@ -1,31 +1,24 @@
import ace from "ace-builds";
import "ace-builds/esm-resolver";
import {
ace,
EditSession,
Range,
AceLanguageClient,
setupLspWorker,
} from "./ace";
import { AceLanguageClient } from "ace-linters/build/ace-language-client";
import { LanguageProvider } from "ace-linters/types/language-provider";
import { IC10EditorUI } from "./ui";
import { Range } from "ace-builds";
import { Session } from "../session";
// import { Mode as TextMode } from 'ace-code/src/mode/text';
// to make sure language tools are loaded
ace.config.loadModule("ace/ext/language_tools");
import { Mode as TextMode } from "ace-builds/src-noconflict/mode-text";
async function setupLspWorker() {
// Create a web worker
let worker = new Worker(new URL("./lspWorker.ts", import.meta.url));
const loaded = (w: Worker) =>
new Promise((r) => w.addEventListener("message", r, { once: true }));
await Promise.all([loaded(worker)]);
// Register the editor with the language provider
return worker;
}
import "@shoelace-style/shoelace/dist/components/dialog/dialog.js";
import "@shoelace-style/shoelace/dist/components/button-group/button-group.js";
import "@shoelace-style/shoelace/dist/components/button/button.js";
import "@shoelace-style/shoelace/dist/components/input/input.js";
import "@shoelace-style/shoelace/dist/components/radio-button/radio-button.js";
import "@shoelace-style/shoelace/dist/components/radio-group/radio-group.js";
import '@shoelace-style/shoelace/dist/components/switch/switch.js';
import SlDialog from "@shoelace-style/shoelace/dist/components/dialog/dialog.js";
import SlRadioGroup from "@shoelace-style/shoelace/dist/components/radio-group/radio-group.js";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js";
import SlSwitch from "@shoelace-style/shoelace/dist/components/switch/switch.js";
declare global {
interface Window {
@@ -34,8 +27,9 @@ declare global {
}
import { BaseElement, defaultCss } from "../components";
import { html, css, HTMLTemplateResult } from "lit";
import { html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { editorStyles } from "./styles";
@customElement("ace-ic10")
export class IC10Editor extends BaseElement {
@@ -46,7 +40,7 @@ export class IC10Editor extends BaseElement {
fontSize: number;
relativeLineNumbers: boolean;
};
sessions: Map<number, ace.Ace.EditSession>;
sessions: Map<number, EditSession>;
@property({ type: Number })
accessor active_session: number = 0;
@@ -55,258 +49,7 @@ export class IC10Editor extends BaseElement {
languageProvider?: LanguageProvider;
// ui: IC10EditorUI;
static styles = [
...defaultCss,
css`
:host {
display: block;
width: 100%;
height: 100%;
}
#editor {
// border: 1px solid;
// border-radius: 4px;
@apply --ace-widget-editor;
}
#editorStatusbar {
z-index: 9 !important;
position: absolute !important;
right: 4px;
bottom: 4px;
}
.ace_status-indicator {
background-color: #777;
color: white;
text-align: center;
border: none;
border-radius: 7px;
padding-right: 3px;
padding-left: 3px;
padding-bottom: 1px;
font-size: small;
opacity: 0.9;
}
.hide_statusbar {
display: none;
}
.ace_marker-layer .green {
// background-color: ;
// color: ;
position: absolute;
}
.ace_marker-layer .darkGrey {
// background-color: ;
// color: ;
position: absolute;
}
.ace_marker-layer .red {
// background-color: ;
// color: ;
position: absolute;
}
.ace_marker-layer .blue {
// background-color: ;
// color: ;
position: absolute;
}
.ace_marker-layer .orange {
background-color: #ff9900;
color: #555;
position: absolute;
}
.ace_placeholder {
color: #808080 !important;
// font-family: "" !important;
transform: scale(1) !important;
opacity: 1 !important;
font-style: italic !important;
}
/* ------------------------------------------------------------------------------------------
* Editor Search Form
* --------------------------------------------------------------------------------------- */
.ace_search {
background-color: #2b3035;
color: #dee2e6;
border: 1px solid #495057;
border-top: 0 none;
overflow: hidden;
margin: 0;
padding: 4px 6px 0 4px;
position: absolute;
top: 0;
z-index: 99;
white-space: normal;
}
.ace_search.left {
border-left: 0 none;
border-radius: 0px 0px 5px 0px;
left: 0;
}
.ace_search.right {
border-radius: 0px 0px 0px 5px;
border-right: 0 none;
right: 0;
}
.ace_search_form,
.ace_replace_form {
margin: 0 20px 4px 0;
overflow: hidden;
line-height: 1.9;
}
.ace_replace_form {
margin-right: 0;
}
.ace_search_form.ace_nomatch {
outline: 1px solid red;
}
.ace_search_field {
border-radius: 3px 0 0 3px;
background-color: #343a40;
color: #dee2e6;
border: 1px solid #41464b;
border-right: 0 none;
outline: 0;
padding: 0;
font-size: inherit;
margin: 0;
line-height: inherit;
padding: 0 6px;
min-width: 17em;
vertical-align: top;
min-height: 1.8em;
box-sizing: content-box;
}
.ace_searchbtn {
border: 1px solid #6c757d;
line-height: inherit;
display: inline-block;
padding: 0 6px;
background: #343a40;
border-right: 0 none;
border-left: 1px solid #6c757d;
cursor: pointer;
margin: 0;
position: relative;
color: #fff;
}
.ace_searchbtn:last-child {
border-radius: 0 3px 3px 0;
border-right: 1px solid #6c757d;
}
.ace_searchbtn:disabled {
background: none;
cursor: default;
}
.ace_searchbtn:hover {
background-color: #161719;
}
.ace_searchbtn.prev,
.ace_searchbtn.next {
padding: 0px 0.7em;
}
.ace_searchbtn.prev:after,
.ace_searchbtn.next:after {
content: "";
border: solid 2px #6c757d;
width: 0.5em;
height: 0.5em;
border-width: 2px 0 0 2px;
display: inline-block;
transform: rotate(-45deg);
}
.ace_searchbtn.next:after {
border-width: 0 2px 2px 0;
}
.ace_searchbtn_close {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAcCAYAAABRVo5BAAAAZ0lEQVR42u2SUQrAMAhDvazn8OjZBilCkYVVxiis8H4CT0VrAJb4WHT3C5xU2a2IQZXJjiQIRMdkEoJ5Q2yMqpfDIo+XY4k6h+YXOyKqTIj5REaxloNAd0xiKmAtsTHqW8sR2W5f7gCu5nWFUpVjZwAAAABJRU5ErkJggg==)
no-repeat 50% 0;
border-radius: 50%;
border: 0 none;
color: #343a40;
cursor: pointer;
font: 16px/16px Arial;
padding: 0;
height: 14px;
width: 14px;
top: 9px;
right: 7px;
position: absolute;
}
.ace_searchbtn_close:hover {
background-color: #656565;
background-position: 50% 100%;
color: white;
}
.ace_button {
background-color: #343a40;
margin-left: 2px;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-o-user-select: none;
-ms-user-select: none;
user-select: none;
overflow: hidden;
opacity: 0.7;
border: 1px solid #6c757d;
padding: 1px;
box-sizing: border-box !important;
color: #fff;
}
.ace_button:hover {
background-color: #161719;
opacity: 1;
}
.ace_button:active {
background-color: #6c757d;
}
.ace_button.checked {
background-color: #6c757d;
border-color: #6c757d;
opacity: 1;
}
.ace_search_options {
margin-bottom: 3px;
text-align: right;
-webkit-user-select: none;
-moz-user-select: none;
-o-user-select: none;
-ms-user-select: none;
user-select: none;
clear: both;
}
.ace_search_counter {
float: left;
font-family: arial;
padding: 0 8px;
}
/* ----------------
* End Ace Search
* --------------- */
`,
];
static styles = [...defaultCss, editorStyles];
initialInit: boolean;
editorDiv: HTMLElement;
@@ -319,7 +62,14 @@ export class IC10Editor extends BaseElement {
private _statusbarIndex: number;
private _statusbar: any;
vScrollbarObserver: IntersectionObserver;
hScrollbarObserver: any;
hScrollbarObserver: IntersectionObserver;
stylesObserver: MutationObserver;
stylesAdded: string[];
tooltipObserver: MutationObserver;
get settingDialog() {
return this.shadowRoot?.querySelector('sl-dialog') as SlDialog
}
constructor() {
super();
@@ -339,11 +89,10 @@ export class IC10Editor extends BaseElement {
this.active_line_markers = new Map();
// this.ui = new IC10EditorUI(this);
}
protected render() {
return html`
const result = html`
<div
id="editorContainer"
style="height: 100%; width: 100%; position: relative;"
@@ -354,7 +103,31 @@ export class IC10Editor extends BaseElement {
></div>
<div id="editorStatusbar"></div>
</div>
<sl-dialog label="Editor Settings" class="dialog-focus">
<sl-radio-group id="editorKeyboardRadio" label="Editor Keyboard Bindings" value=${this.settings.keyboard}>
<sl-radio-button value="ace">Ace</sl-radio-button>
<sl-radio-button value="vim">Vim</sl-radio-button>
<sl-radio-button value="emacs">Emacs</sl-radio-button>
<sl-radio-button value="sublime">Sublime</sl-radio-button>
<sl-radio-button value="vscode">VS Code</sl-radio-button>
</sl-radio-group>
<sl-radio-group id="editorCursorRadio" label="Editor Cursor Style" value=${this.settings.cursor}>
<sl-radio-button value="ace">Ace</sl-radio-button>
<sl-radio-button value="slim">Slim</sl-radio-button>
<sl-radio-button value="smooth">Smooth</sl-radio-button>
<sl-radio-button value="smooth slim">Smooth And Slim</sl-radio-button>
<sl-radio-button value="wide">Wide</sl-radio-button>
</sl-radio-group>
<sl-input id="editorFontSize" label="Font Size" type="number" value="${this.settings.fontSize}"></sl-input>
<sl-switch id="editorRelativeLineNumbers" ?checked=${this.settings.relativeLineNumbers}>Relative Line Numbers</sl-switch>
</sl-dialog>
`;
return result;
}
connectedCallback(): void {
super.connectedCallback();
this.loadEditorSettings();
}
async firstUpdated() {
@@ -378,8 +151,12 @@ export class IC10Editor extends BaseElement {
this.initialInit = true;
this.editorDiv = this.shadowRoot?.getElementById("editor") as HTMLElement;
this.editorContainerDiv = this.shadowRoot?.getElementById("editorContainer") as HTMLElement;
this.editorStatusbarDiv = this.shadowRoot?.getElementById("editorStatusbar") as HTMLElement;
this.editorContainerDiv = this.shadowRoot?.getElementById(
"editorContainer",
) as HTMLElement;
this.editorStatusbarDiv = this.shadowRoot?.getElementById(
"editorStatusbar",
) as HTMLElement;
this.editor = ace.edit(this.editorDiv, {
mode: this.mode,
@@ -397,6 +174,51 @@ export class IC10Editor extends BaseElement {
this.statusBar = ace.require("ace/ext/statusbar").StatusBar;
this.snippetManager = ace.require("ace/snippets").snippetManager;
this.stylesAdded = [];
const stylesToMove: string[] = ["vimMode"];
const stylesToCopy: string[] = ["autocompletion.css"];
const that = this;
this.stylesObserver = new MutationObserver((_mutations, _observer) => {
// ace adds <style></style> nodes, ours should be <link rel="stylesheet">
for (const sheet of document.head.querySelectorAll("style")) {
if (!that.stylesAdded.includes(sheet.id)) {
if (stylesToMove.includes(sheet.id)) {
that.shadowRoot?.appendChild(sheet);
that.stylesAdded.push(sheet.id);
} else if (stylesToCopy.includes(sheet.id)) {
let new_sheet = sheet.cloneNode() as HTMLStyleElement;
new_sheet.id = `${sheet.id}_clone`;
that.shadowRoot?.appendChild(new_sheet);
that.stylesAdded.push(sheet.id);
}
}
}
});
this.stylesObserver.observe(document.head, {
attributes: false,
childList: true,
subtree: true,
characterData: false,
});
// Fornow this seems uneeded, tooltips seem to work better on the lightdom
// this.tooltipObserver = new MutationObserver((_mutations, _observer) => {
// // we want the toltips on the shadow-dom not the light dom body
// for (const node of document.body.querySelectorAll(
// ".ace_tooltip, .ace_editor.ace_autocomplete",
// )) {
// that.shadowRoot?.appendChild(node);
// }
// });
// this.tooltipObserver.observe(document.body, {
// attributes: false,
// childList: true,
// subtree: true,
// characterData: false,
// });
this.sessions.set(this.active_session, this.editor.getSession());
this.bindSession(
this.active_session,
@@ -407,8 +229,6 @@ export class IC10Editor extends BaseElement {
const worker = await setupLspWorker();
this.setupLsp(worker);
const that = this;
// when the CSS resize Property is added (to a container-div or ace-ic10 )
// the correct sizing is maintained (after user resize)
document.addEventListener("mouseup", function (e) {
@@ -424,6 +244,11 @@ export class IC10Editor extends BaseElement {
this.observer.observe(this.editorContainerDiv);
this.initializeEditor();
}
initializeEditor() {
let editor = this.editor;
const that = this;
window.App!.session.onLoad(((e: CustomEvent) => {
const session = e.detail;
@@ -471,10 +296,6 @@ export class IC10Editor extends BaseElement {
}
}
}) as EventListener);
}
initializeEditor() {
let editor = this.editor;
// change -> possibility to allow saving the value without having to wait for blur
editor.on("change", () => this.editorChangeAction());
@@ -492,7 +313,7 @@ export class IC10Editor extends BaseElement {
{ root: null },
);
this.vScrollbarObserver.observe(
this.shadowRoot?.querySelector(".ace_scrollbar-v") as Element,
this.shadowRoot!.querySelector(".ace_scrollbar-v")!,
);
this.hScrollbarObserver = new IntersectionObserver(
@@ -500,8 +321,44 @@ export class IC10Editor extends BaseElement {
{ root: null },
);
this.hScrollbarObserver.observe(
this.shadowRoot?.querySelector(".ace_scrollbar-h"),
this.shadowRoot!.querySelector(".ace_scrollbar-h")!,
);
editor.commands.addCommands([{
name: "showSettingsMenu",
// description: "Show settings menu",
bindKey: { win: "Ctrl-,", mac: "Command-,"},
exec: (_editor: ace.Ace.Editor) => {
that.settingDialog.show();
},
}]);
this.updateEditorSettings();
const keyboardRadio = this.renderRoot.querySelector("#editorKeyboardRadio")! as SlRadioGroup;
const cursorRadio = this.renderRoot.querySelector("#editorCursorRadio")! as SlRadioGroup;
const fontSize = this.renderRoot.querySelector("#editorFontSize")! as SlInput;
const relativeLineNumbers = this.renderRoot.querySelector("#editorRelativeLineNumbers")! as SlSwitch;
keyboardRadio.addEventListener("sl-change", _e => {
that.settings.keyboard = keyboardRadio.value;
that.updateEditorSettings();
that.saveEditorSettings();
});
cursorRadio?.addEventListener("sl-change", _e => {
that.settings.cursor = cursorRadio.value;
that.updateEditorSettings();
that.saveEditorSettings();
});
fontSize?.addEventListener("sl-change", _e => {
that.settings.fontSize = parseInt(fontSize.value)
that.updateEditorSettings();
that.saveEditorSettings();
});
relativeLineNumbers?.addEventListener("sl-change", _e => {
that.settings.relativeLineNumbers = relativeLineNumbers.checked;
that.updateEditorSettings();
that.saveEditorSettings();
});
}
resizeEditor() {
@@ -624,7 +481,7 @@ export class IC10Editor extends BaseElement {
this.editor?.setSession(session);
const mode = ace.require(this.mode);
const options = mode?.options ?? {};
this.languageProvider?.setSessionOptions(session, options)
this.languageProvider?.setSessionOptions(session, options);
this.active_session = session_id;
return true;
}
@@ -647,6 +504,17 @@ export class IC10Editor extends BaseElement {
window.localStorage.setItem("editorSettings", toSave);
}
updateEditorSettings() {
if (this.settings.keyboard === 'ace') {
this.editor.setOption('keyboardHandler', null);
} else {
this.editor.setOption('keyboardHandler', `ace/keyboard/${this.settings.keyboard}`);
}
this.editor.setOption('cursorStyle', this.settings.cursor as any);
this.editor.setOption('fontSize', this.settings.fontSize);
this.editor.setOption('relativeLineNumbers', this.settings.relativeLineNumbers);
}
destroySession(session_id: number) {
if (!this.sessions.hasOwnProperty(session_id)) {
return false;
@@ -663,7 +531,7 @@ export class IC10Editor extends BaseElement {
return true;
}
bindSession(session_id: number, session?: ace.Ace.EditSession) {
bindSession(session_id: number, session?: EditSession) {
if (session) {
session.on("change", () => {
var val = session.getValue();

345
www/src/js/editor/styles.ts Normal file
View File

@@ -0,0 +1,345 @@
import { css } from "lit";
export const editorStyles = css`
:host {
display: block;
width: 100%;
height: 100%;
}
#editor {
// border: 1px solid;
// border-radius: 4px;
@apply --ace-widget-editor;
}
#editorStatusbar {
z-index: 9 !important;
position: absolute !important;
right: 4px;
bottom: 4px;
}
.ace_status-indicator {
background-color: #777;
color: white;
text-align: center;
border: none;
border-radius: 7px;
padding-right: 3px;
padding-left: 3px;
padding-bottom: 1px;
font-size: small;
opacity: 0.9;
}
.ace_marker-layer .green {
// background-color: ;
// color: ;
position: absolute;
}
.ace_marker-layer .darkGrey {
// background-color: ;
// color: ;
position: absolute;
}
.ace_marker-layer .red {
// background-color: ;
// color: ;
position: absolute;
}
.ace_marker-layer .blue {
// background-color: ;
// color: ;
position: absolute;
}
.ace_marker-layer .orange {
background-color: #ff9900;
color: #555;
position: absolute;
}
.ace_placeholder {
color: #808080 !important;
// font-family: "" !important;
transform: scale(1) !important;
opacity: 1 !important;
font-style: italic !important;
}
/* ------------------------------------------------------------------------------------------
* Editor Search Form
* --------------------------------------------------------------------------------------- */
.ace_search {
background-color: #2b3035;
color: #dee2e6;
border: 1px solid #495057;
border-top: 0 none;
overflow: hidden;
margin: 0;
padding: 4px 6px 0 4px;
position: absolute;
top: 0;
z-index: 99;
white-space: normal;
}
.ace_search.left {
border-left: 0 none;
border-radius: 0px 0px 5px 0px;
left: 0;
}
.ace_search.right {
border-radius: 0px 0px 0px 5px;
border-right: 0 none;
right: 0;
}
.ace_search_form,
.ace_replace_form {
margin: 0 20px 4px 0;
overflow: hidden;
line-height: 1.9;
}
.ace_replace_form {
margin-right: 0;
}
.ace_search_form.ace_nomatch {
outline: 1px solid red;
}
.ace_search_field {
border-radius: 3px 0 0 3px;
background-color: #343a40;
color: #dee2e6;
border: 1px solid #41464b;
border-right: 0 none;
outline: 0;
padding: 0;
font-size: inherit;
margin: 0;
line-height: inherit;
padding: 0 6px;
min-width: 17em;
vertical-align: top;
min-height: 1.8em;
box-sizing: content-box;
}
.ace_searchbtn {
border: 1px solid #6c757d;
line-height: inherit;
display: inline-block;
padding: 0 6px;
background: #343a40;
border-right: 0 none;
border-left: 1px solid #6c757d;
cursor: pointer;
margin: 0;
position: relative;
color: #fff;
}
.ace_searchbtn:last-child {
border-radius: 0 3px 3px 0;
border-right: 1px solid #6c757d;
}
.ace_searchbtn:disabled {
background: none;
cursor: default;
}
.ace_searchbtn:hover {
background-color: #161719;
}
.ace_searchbtn.prev,
.ace_searchbtn.next {
padding: 0px 0.7em;
}
.ace_searchbtn.prev:after,
.ace_searchbtn.next:after {
content: "";
border: solid 2px #6c757d;
width: 0.5em;
height: 0.5em;
border-width: 2px 0 0 2px;
display: inline-block;
transform: rotate(-45deg);
}
.ace_searchbtn.next:after {
border-width: 0 2px 2px 0;
}
.ace_searchbtn_close {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAcCAYAAABRVo5BAAAAZ0lEQVR42u2SUQrAMAhDvazn8OjZBilCkYVVxiis8H4CT0VrAJb4WHT3C5xU2a2IQZXJjiQIRMdkEoJ5Q2yMqpfDIo+XY4k6h+YXOyKqTIj5REaxloNAd0xiKmAtsTHqW8sR2W5f7gCu5nWFUpVjZwAAAABJRU5ErkJggg==)
no-repeat 50% 0;
border-radius: 50%;
border: 0 none;
color: #343a40;
cursor: pointer;
font: 16px/16px Arial;
padding: 0;
height: 14px;
width: 14px;
top: 9px;
right: 7px;
position: absolute;
}
.ace_searchbtn_close:hover {
background-color: #656565;
background-position: 50% 100%;
color: white;
}
.ace_button {
background-color: #343a40;
margin-left: 2px;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-o-user-select: none;
-ms-user-select: none;
user-select: none;
overflow: hidden;
opacity: 0.7;
border: 1px solid #6c757d;
padding: 1px;
box-sizing: border-box !important;
color: #fff;
}
.ace_button:hover {
background-color: #161719;
opacity: 1;
}
.ace_button:active {
background-color: #6c757d;
}
.ace_button.checked {
background-color: #6c757d;
border-color: #6c757d;
opacity: 1;
}
.ace_search_options {
margin-bottom: 3px;
text-align: right;
-webkit-user-select: none;
-moz-user-select: none;
-o-user-select: none;
-ms-user-select: none;
user-select: none;
clear: both;
}
.ace_search_counter {
float: left;
font-family: arial;
padding: 0 8px;
}
/* ----------------
* Ace Tooltips
* --------------- */
code {
// color: #e685b5
color: #c678dd;
}
.ace_tooltip code {
font-style: italic;
font-size: 12px;
}
.ace_tooltip {
background: #282c34;
color: #c1c1c1;
border: 1px #484747 solid;
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.51);
}
.ace_tooltip.ace_dark {
background: #282c34;
color: #c1c1c1;
border: 1px #484747 solid;
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.51);
}
/* ----------------
* Ace tooltip
* --------------- */
.ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight {
color: #c678dd;
}
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {
background-color: rgba(76, 87, 103, 0.19);
}
.ace_dark.ace_editor.ace_autocomplete .ace_line-hover {
border: 1px solid rgba(8, 121, 144, 0.5);
background: rgba(76, 87, 103, 0.19);
}
.ace_dark.ace_editor.ace_autocomplete {
border: 1px #484747 solid;
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.51);
line-height: 1.4;
background: #282c34;
color: #c1c1c1;
}
.ace_editor.ace_autocomplete {
width: 300px;
z-index: 200000;
border: 1px #484747 solid;
position: fixed;
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.51);
line-height: 1.4;
background: #282c34;
color: #c1c1c1;
}
.ace_editor.ace_autocomplete .ace_completion-highlight {
color: #c678dd;
}
.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {
background-color: rgba(76, 87, 103, 0.19);
}
.ace_editor.ace_autocomplete .ace_line-hover {
border: 1px solid rgba(8, 121, 144, 0.5);
background: rgba(76, 87, 103, 0.19);
}
/* ----------------------
* Editor Setting dialog
* ---------------------- */
.label-on-left {
--label-width: 3.75rem;
--gap-width: 1rem;
}
.label-on-left + .label-on-left {
margin-top: var(--sl-spacing-medium);
}
.label-on-left::part(form-control) {
display: grid;
grid: auto / var(--label-width) 1fr;
gap: var(--sl-spacing-3x-small) var(--gap-width);
align-items: center;
}
.label-on-left::part(form-control-label) {
text-align: right;
}
.label-on-left::part(form-control-help-text) {
grid-column-start: 2;
}
`;

View File

@@ -1,109 +0,0 @@
import { IC10Editor } from ".";
import * as ace from "ace-builds";
import { Offcanvas } from 'bootstrap';
class IC10EditorUI {
ic10editor: IC10Editor;
constructor(ic10editor: IC10Editor) {
const that = this;
that.ic10editor = ic10editor;
// that.ic10editor.editor.commands.addCommand({
// name: "showSettingsMenu",
// description: "Show settings menu",
// bindKey: { win: "Ctrl-,", mac: "Command-," },
// exec: (_editor: ace.Ace.Editor) => {
// const offCanvas = new Offcanvas(document.getElementById("editorSettings"));
// offCanvas.toggle();
// }
// } as any);
ace.config.loadModule("ace/ext/keyboard_menu", function (module) {
console.log("keybinding_menu loaded");
module.init(that.ic10editor.editor);
});
that.ic10editor.loadEditorSettings();
that.displayEditorSettings();
that.updateEditorSettings();
that.reCalcEditorSize();
window.addEventListener('resize', (e) => { that.reCalcEditorSize(); });
document.getElementsByName("editorKeybindRadio").forEach((el) => {
el.addEventListener('change', (e) => {
that.ic10editor.settings.keyboard = (e.target as any).value;
that.ic10editor.saveEditorSettings();
that.updateEditorSettings();
});
});
document.getElementsByName("editorCursorRadio").forEach((el) => {
el.addEventListener('change', (e) => {
that.ic10editor.settings.cursor = (e.target as any).value;
that.ic10editor.saveEditorSettings();
that.updateEditorSettings();
});
});
document.getElementById("editorSettingsFontSize").addEventListener('change', (e) => {
window.App.editorSettings.fontSize = parseInt((e.target as any).value);
that.ic10editor.saveEditorSettings();
that.updateEditorSettings();
});
document.getElementById("editorSettingsRelativeLineNumbers").addEventListener('change', (e) => {
window.App.editorSettings.relativeLineNumbers = (e.target as any).checked;
that.ic10editor.saveEditorSettings();
that.updateEditorSettings();
});
console.log(that.ic10editor.editor.getOption('keyboardHandler'));
that.ic10editor.editor.setTheme("ace/theme/one_dark");
that.ic10editor.editor.setAutoScrollEditorIntoView(true);
}
updateEditorSettings() {
const settings = this.ic10editor.settings;
const editor = this.ic10editor.editor;
if (settings.keyboard === 'ace') {
editor.setOption('keyboardHandler', null);
} else {
editor.setOption('keyboardHandler', `ace/keyboard/${settings.keyboard}`);
}
editor.setOption('cursorStyle', settings.cursor as any);
editor.setOption('fontSize', settings.fontSize);
editor.setOption('relativeLineNumbers', settings.relativeLineNumbers);
}
displayEditorSettings() {
const settings = this.ic10editor.settings;
document.getElementsByName("editorKeybindRadio").forEach((el: any) => {
el.checked = el.value === settings.keyboard;
});
document.getElementsByName("editorCursorRadio").forEach((el: any) => {
el.checked = el.value === settings.cursor;
});
(document.getElementById("editorSettingsFontSize") as any).value = settings.fontSize;
(document.getElementById("editorSettingsRelativeLineNumbers") as any).checked = settings.relativeLineNumbers;
}
reCalcEditorSize() {
const editor = this.ic10editor.editor;
const navBar = document.getElementById("navBar");
const statusBarContainer = document.getElementById("statusBarContainer");
const correction = navBar.offsetHeight + statusBarContainer.offsetHeight;
const editorContainer = document.getElementById("editor");
editorContainer.style.height = `calc( 100vh - ${correction}px - 0.5rem)`;
editor.resize(true);
}
}
export { IC10EditorUI };

View File

@@ -1,9 +1,11 @@
import { Ace } from "ace-builds";
function docReady(fn: ()=> void) {
function docReady(fn: () => void) {
// see if DOM is already available
if (document.readyState === "complete" || document.readyState === "interactive") {
if (
document.readyState === "complete" ||
document.readyState === "interactive"
) {
setTimeout(fn, 1);
} else {
document.addEventListener("DOMContentLoaded", fn);
@@ -11,7 +13,12 @@ function docReady(fn: ()=> void) {
}
// probably not needed, fetch() exists now
function makeRequest(opts: { method: string, url: string, headers: { [key: string]: string }, params: any }) {
function makeRequest(opts: {
method: string;
url: string;
headers: { [key: string]: string };
params: any;
}) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(opts.method, opts.url);
@@ -21,14 +28,14 @@ function makeRequest(opts: { method: string, url: string, headers: { [key: strin
} else {
reject({
status: xhr.status,
statusText: xhr.statusText
statusText: xhr.statusText,
});
}
};
xhr.onerror = function () {
reject({
status: xhr.status,
statusText: xhr.statusText
statusText: xhr.statusText,
});
};
if (opts.headers) {
@@ -37,10 +44,14 @@ function makeRequest(opts: { method: string, url: string, headers: { [key: strin
});
}
var params = opts.params;
if (params && typeof params === 'object') {
params = Object.keys(params).map(function (key) {
return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
}).join('&');
if (params && typeof params === "object") {
params = Object.keys(params)
.map(function (key) {
return (
encodeURIComponent(key) + "=" + encodeURIComponent(params[key])
);
})
.join("&");
}
xhr.send(params);
});
@@ -49,24 +60,28 @@ function makeRequest(opts: { method: string, url: string, headers: { [key: strin
async function saveFile(content: BlobPart) {
const blob = new Blob([content], { type: "text/plain" });
if (typeof window.showSaveFilePicker !== "undefined") {
console.log("Saving via FileSystem API")
const saveHandle = await window.showSaveFilePicker({
types: [
{
// suggestedName: "code.ic10",
description: 'Text Files',
accept: {
'text/plain': ['.txt', '.ic10'],
console.log("Saving via FileSystem API");
try {
const saveHandle = await window.showSaveFilePicker({
types: [
{
// suggestedName: "code.ic10",
description: "Text Files",
accept: {
"text/plain": [".txt", ".ic10"],
},
},
},
],
});
const ws = await saveHandle.createWritable();
await ws.write(blob);
await ws.close();
],
});
const ws = await saveHandle.createWritable();
await ws.write(blob);
await ws.close();
} catch (e) {
console.log(e);
}
} else {
console.log("saving file via hidden link event");
var a = document.createElement('a');
var a = document.createElement("a");
a.download = "code.ic10";
a.href = window.URL.createObjectURL(blob);
a.click();
@@ -76,23 +91,27 @@ async function saveFile(content: BlobPart) {
async function openFile(editor: Ace.Editor) {
if (typeof window.showOpenFilePicker !== "undefined") {
console.log("opening file via FileSystem Api");
const [fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const contents = await file.text();
const session = editor.getSession();
session.setValue(contents);
try {
const [fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const contents = await file.text();
const session = editor.getSession();
session.setValue(contents);
} catch (e) {
console.log(e);
}
} else {
console.log("opening file via hidden input event");
let input = document.createElement('input');
input.type = 'file';
let input = document.createElement("input");
input.type = "file";
input.accept = ".txt,.ic10,.mips,text/*";
input.onchange = _ => {
const files = Array.from(input.files);
input.onchange = (_) => {
const files = Array.from(input.files!);
console.log(files);
const file = files[0];
var reader = new FileReader();
reader.onload = (e) => {
const contents = e.target.result as string;
const contents = e.target!.result as string;
const session = editor.getSession();
// session.id = file.name;
session.setValue(contents);
@@ -102,4 +121,4 @@ async function openFile(editor: Ace.Editor) {
input.click();
}
}
export { docReady, makeRequest, saveFile, openFile }
export { docReady, makeRequest, saveFile, openFile };

View File

@@ -4,21 +4,6 @@ body {
margin: 0;
}
.ace_tooltip {
background: #282c34;
color: #c1c1c1;
border: 1px #484747 solid;
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.51);
}
.ace_tooltip.ace_dark {
background: #282c34;
color: #c1c1c1;
border: 1px #484747 solid;
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.51);
}
#ace_settingsmenu,
#kbshortcutmenu {
background-color: rgb(33, 37, 41);
@@ -37,12 +22,12 @@ body {
.ace_optionsMenuEntry:hover {
background-color: #161719;
transition: all 0.3s
transition: all 0.3s;
}
.ace_closeButton {
background: rgba(245, 146, 146, 0.5);
border: 1px solid #F48A8A;
border: 1px solid #f48a8a;
border-radius: 50%;
padding: 7px;
position: absolute;
@@ -70,7 +55,7 @@ body {
vertical-align: middle;
}
.ace_optionsMenuEntry button[ace_selected_button=true] {
.ace_optionsMenuEntry button[ace_selected_button="true"] {
background: #e7e7e7;
box-shadow: 1px 0px 2px 0px #adadad inset;
border-color: #adadad;
@@ -96,18 +81,7 @@ body {
box-shadow: 0px 2px 3px 0px #555;
}
code {
// color: #e685b5
color: #c678dd;
}
.ace_tooltip code {
font-style: italic;
font-size: 12px;
}
.navbar-default .navbar-nav>li>a {
.navbar-default .navbar-nav > li > a {
color: #fff;
}
@@ -118,7 +92,7 @@ code {
color: #fff;
}
.navbar-nav>li>a {
.navbar-nav > li > a {
padding-top: 10px;
padding-bottom: 10px;
line-height: 20px;
@@ -131,7 +105,7 @@ code {
// }
// }
.nav>li>a {
.nav > li > a {
position: relative;
display: block;
padding: 10px 15px;
@@ -141,7 +115,7 @@ code {
.navbar-nav .dropdown-menu {
position: absolute;
left: .5rem;
left: 0.5rem;
}
.modal-title {
@@ -149,21 +123,51 @@ code {
}
.modal-body button {
color: #fff
color: #fff;
}
/* ----------------
* Ace Tooltips
* --------------- */
code {
// color: #e685b5
color: #c678dd;
}
.ace_tooltip code {
font-style: italic;
font-size: 12px;
}
.ace_tooltip {
background: #282c34;
color: #c1c1c1;
border: 1px #484747 solid;
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.51);
}
.ace_tooltip.ace_dark {
background: #282c34;
color: #c1c1c1;
border: 1px #484747 solid;
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.51);
}
/* ----------------
* Ace tooltip
* --------------- */
.ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight {
color: #c678dd;
}
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {
background-color: rgba(76, 87, 103, .19);
background-color: rgba(76, 87, 103, 0.19);
}
.ace_dark.ace_editor.ace_autocomplete .ace_line-hover {
border: 1px solid rgba(8, 121, 144, 0.5);
background: rgba(76, 87, 103, .19);
background: rgba(76, 87, 103, 0.19);
}
.ace_dark.ace_editor.ace_autocomplete {
@@ -190,12 +194,12 @@ code {
}
.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {
background-color: rgba(76, 87, 103, .19);
background-color: rgba(76, 87, 103, 0.19);
}
.ace_editor.ace_autocomplete .ace_line-hover {
border: 1px solid rgba(8, 121, 144, 0.5);
background: rgba(76, 87, 103, .19);
background: rgba(76, 87, 103, 0.19);
}
.vm_ic_active_line {
@@ -294,13 +298,12 @@ code {
border-radius: 0;
}
.vm_stack_cel span.stack_pointer {
.vm_stack_cel span.stack_pointer {
background-color: var(--bs-success);
}
.vm_device_summary button.btn {
line-height: 0.75rem;
}
.vm_device_summary span.badge {

View File

@@ -32,31 +32,31 @@ $accordion-button-padding-y: 0.5rem;
// @import "bootstrap/scss/images";
@import "bootstrap/scss/containers";
@import "bootstrap/scss/grid";
@import "bootstrap/scss/tables";
@import "bootstrap/scss/forms";
@import "bootstrap/scss/buttons";
@import "bootstrap/scss/transitions";
@import "bootstrap/scss/dropdown";
@import "bootstrap/scss/button-group";
@import "bootstrap/scss/nav";
@import "bootstrap/scss/navbar"; // Requires nav
@import "bootstrap/scss/card";
// @import "bootstrap/scss/tables";
// @import "bootstrap/scss/forms";
// @import "bootstrap/scss/buttons";
// @import "bootstrap/scss/transitions";
// @import "bootstrap/scss/dropdown";
// @import "bootstrap/scss/button-group";
// @import "bootstrap/scss/nav";
// @import "bootstrap/scss/navbar"; // Requires nav
// @import "bootstrap/scss/card";
// @import "bootstrap/scss/breadcrumb";
@import "bootstrap/scss/accordion";
// @import "bootstrap/scss/accordion";
// @import "bootstrap/scss/pagination";
@import "bootstrap/scss/badge";
@import "bootstrap/scss/alert";
@import "bootstrap/scss/progress";
@import "bootstrap/scss/list-group";
@import "bootstrap/scss/close";
@import "bootstrap/scss/toasts";
@import "bootstrap/scss/modal"; // Requires transitions
@import "bootstrap/scss/tooltip";
@import "bootstrap/scss/popover";
// @import "bootstrap/scss/badge";
// @import "bootstrap/scss/alert";
// @import "bootstrap/scss/progress";
// @import "bootstrap/scss/list-group";
// @import "bootstrap/scss/close";
// @import "bootstrap/scss/toasts";
// @import "bootstrap/scss/modal"; // Requires transitions
// @import "bootstrap/scss/tooltip";
// @import "bootstrap/scss/popover";
// @import "bootstrap/scss/carousel";
@import "bootstrap/scss/spinners";
@import "bootstrap/scss/offcanvas"; // Requires transitions
// @import "bootstrap/scss/spinners";
// @import "bootstrap/scss/offcanvas"; // Requires transitions
// @import "bootstrap/scss/placeholders";
// Helpers
@@ -65,8 +65,8 @@ $accordion-button-padding-y: 0.5rem;
// Utilities
@import "bootstrap/scss/utilities/api";
@import "./dark.scss"
//
// Custom styles
//
//
@import "./dark.scss"