diff --git a/www/src/js/app/app.ts b/www/src/js/app/app.ts index 500d490..be46920 100644 --- a/www/src/js/app/app.ts +++ b/www/src/js/app/app.ts @@ -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`
- +
`; } + + 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; } } - diff --git a/www/src/js/app/nav.ts b/www/src/js/app/nav.ts index b619ca6..beb5d39 100644 --- a/www/src/js/app/nav.ts +++ b/www/src/js/app/nav.ts @@ -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" > - + Share Open File Save As - + Editor Settings - + Editor Keyboard Shortcuts @@ -161,4 +162,31 @@ export class Nav extends BaseElement { `; } + + 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); + } + } } diff --git a/www/src/js/editor/ace.js b/www/src/js/editor/ace.js deleted file mode 100644 index 9a2885e..0000000 --- a/www/src/js/editor/ace.js +++ /dev/null @@ -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 }; diff --git a/www/src/js/editor/ace.ts b/www/src/js/editor/ace.ts new file mode 100644 index 0000000..b420071 --- /dev/null +++ b/www/src/js/editor/ace.ts @@ -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 } diff --git a/www/src/js/editor/index.ts b/www/src/js/editor/index.ts index 2e4935d..68e3465 100644 --- a/www/src/js/editor/index.ts +++ b/www/src/js/editor/index.ts @@ -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; + sessions: Map; @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`
+ + + Ace + Vim + Emacs + Sublime + VS Code + + + Ace + Slim + Smooth + Smooth And Slim + Wide + + + Relative Line Numbers + `; + 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 nodes, ours should be + 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(); diff --git a/www/src/js/editor/styles.ts b/www/src/js/editor/styles.ts new file mode 100644 index 0000000..b3d919b --- /dev/null +++ b/www/src/js/editor/styles.ts @@ -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; + } +`; diff --git a/www/src/js/editor/ui.ts b/www/src/js/editor/ui.ts deleted file mode 100644 index 469b56d..0000000 --- a/www/src/js/editor/ui.ts +++ /dev/null @@ -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 }; diff --git a/www/src/js/utils.ts b/www/src/js/utils.ts index 3e864a6..68b3bdb 100644 --- a/www/src/js/utils.ts +++ b/www/src/js/utils.ts @@ -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 } \ No newline at end of file +export { docReady, makeRequest, saveFile, openFile }; diff --git a/www/src/scss/dark.scss b/www/src/scss/dark.scss index e5c2bd3..94b138c 100644 --- a/www/src/scss/dark.scss +++ b/www/src/scss/dark.scss @@ -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 { diff --git a/www/src/scss/styles.scss b/www/src/scss/styles.scss index 4eaf55b..3efd6b8 100644 --- a/www/src/scss/styles.scss +++ b/www/src/scss/styles.scss @@ -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 -// \ No newline at end of file +// +@import "./dark.scss"