Add auto complete, theme select, & full screen toggle in editors

This commit is contained in:
Peter Stockings
2025-11-25 14:55:22 +11:00
parent f0bed51b66
commit 17518c3fcc
3 changed files with 1834 additions and 17 deletions

1698
static/js/mithril.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -45,6 +45,19 @@ const Editor = {
this.generateUrl = vnode.attrs.generateUrl; this.generateUrl = vnode.attrs.generateUrl;
this.logsUrl = vnode.attrs.logsUrl; // Needed for debug feature this.logsUrl = vnode.attrs.logsUrl; // Needed for debug feature
// Editor Features State
this.isFullScreen = false;
this.editorTheme = localStorage.getItem('editorTheme') || 'auto';
this.availableThemes = [
{ id: 'auto', name: 'Auto' },
{ id: 'monokai', name: 'Monokai' },
{ id: 'chrome', name: 'Chrome' },
{ id: 'github', name: 'GitHub' },
{ id: 'tomorrow', name: 'Tomorrow' },
{ id: 'twilight', name: 'Twilight' },
{ id: 'dracula', name: 'Dracula' }
];
// AI State // AI State
this.naturalLanguageQuery = ""; this.naturalLanguageQuery = "";
this.aiLoading = false; // General AI loading state this.aiLoading = false; // General AI loading state
@@ -56,13 +69,23 @@ const Editor = {
oncreate() { oncreate() {
this.editorJS = ace.edit("js-editor"); this.editorJS = ace.edit("js-editor");
this.editorJS.setOptions({ maxLines: 100 });
// Determine initial theme // safe require of language tools
const isDark = document.documentElement.classList.contains('dark'); try {
const theme = isDark ? "ace/theme/monokai" : "ace/theme/chrome"; ace.require("ace/ext/language_tools");
} catch (e) {
console.warn("Ace language tools not loaded");
}
this.editorJS.setOptions({
minLines: this.isFullScreen ? undefined : 15,
maxLines: this.isFullScreen ? undefined : 100,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
});
this.applyTheme();
this.editorJS.setTheme(theme);
this.editorJS.session.setMode( this.editorJS.session.setMode(
this.runtime === "python" ? "ace/mode/python" : "ace/mode/javascript" this.runtime === "python" ? "ace/mode/python" : "ace/mode/javascript"
); );
@@ -74,8 +97,12 @@ const Editor = {
}); });
this.editorJSON = ace.edit("json-editor"); this.editorJSON = ace.edit("json-editor");
this.editorJSON.setOptions({ maxLines: 100 }); this.editorJSON.setOptions({
this.editorJSON.setTheme(theme); minLines: 15,
maxLines: 100,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
});
this.editorJSON.session.setMode("ace/mode/json"); this.editorJSON.session.setMode("ace/mode/json");
this.editorJSON.setValue(this.jsonValue, -1); this.editorJSON.setValue(this.jsonValue, -1);
@@ -86,17 +113,68 @@ const Editor = {
// Listen for theme changes // Listen for theme changes
this.themeListener = (e) => { this.themeListener = (e) => {
const newTheme = e.detail.theme === 'dark' ? "ace/theme/monokai" : "ace/theme/chrome"; if (this.editorTheme === 'auto') {
this.editorJS.setTheme(newTheme); this.applyTheme();
this.editorJSON.setTheme(newTheme); }
}; };
window.addEventListener('themeChanged', this.themeListener); window.addEventListener('themeChanged', this.themeListener);
// Listen for Escape key to exit full screen
this.escListener = (e) => {
if (this.isFullScreen && e.key === 'Escape') {
this.toggleFullScreen();
m.redraw();
}
};
document.addEventListener('keydown', this.escListener);
},
applyTheme() {
let themeToSet = this.editorTheme;
if (themeToSet === 'auto') {
const isDark = document.documentElement.classList.contains('dark');
themeToSet = isDark ? 'monokai' : 'chrome';
}
const aceTheme = `ace/theme/${themeToSet}`;
if (this.editorJS) this.editorJS.setTheme(aceTheme);
if (this.editorJSON) this.editorJSON.setTheme(aceTheme);
},
toggleFullScreen() {
this.isFullScreen = !this.isFullScreen;
if (this.isFullScreen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
// Wait for Mithril to redraw so the DOM is updated (classes/styles applied)
// before resizing the editor.
setTimeout(() => {
if (this.editorJS) {
this.editorJS.setOptions({
minLines: this.isFullScreen ? undefined : 15,
maxLines: this.isFullScreen ? undefined : 100
});
this.editorJS.resize();
}
}, 0);
},
setTheme(themeId) {
this.editorTheme = themeId;
localStorage.setItem('editorTheme', themeId);
this.applyTheme();
}, },
onremove() { onremove() {
if (this.themeListener) { if (this.themeListener) {
window.removeEventListener('themeChanged', this.themeListener); window.removeEventListener('themeChanged', this.themeListener);
} }
if (this.escListener) {
document.removeEventListener('keydown', this.escListener);
}
document.body.style.overflow = '';
}, },
async execute() { async execute() {
@@ -298,12 +376,19 @@ const Editor = {
}, },
view() { view() {
return m("div", { class: "" }, [ const fullScreenStyle = this.isFullScreen
? { position: "fixed", top: "0", left: "0", width: "100vw", height: "100vh", zIndex: "9999", backgroundColor: this.editorTheme === 'monokai' || this.editorTheme === 'twilight' || this.editorTheme === 'dracula' ? '#1a1a1a' : '#ffffff' }
: {};
return m("div", {
class: this.isFullScreen ? "flex flex-col" : "",
style: fullScreenStyle
}, [
/* Header */ /* Header */
m( m(
"div", "div",
{ {
class: "flex items-center justify-between pl-2", class: "flex items-center justify-between pl-2 " + (this.isFullScreen ? "p-4 border-b border-gray-200 dark:border-gray-800" : ""),
}, },
[ [
this.showHeader this.showHeader
@@ -417,7 +502,7 @@ const Editor = {
: null, : null,
]) ])
: m("div"), : m("div"),
m("div"), m("div", { class: "flex items-center space-x-2" }),
] ]
), ),
@@ -473,7 +558,35 @@ const Editor = {
this.aiLoading && m("div", {class: "animate-spin h-4 w-4 border-2 border-blue-500 border-t-transparent rounded-full ml-2"}) this.aiLoading && m("div", {class: "animate-spin h-4 w-4 border-2 border-blue-500 border-t-transparent rounded-full ml-2"})
]), ]),
m("div", { class: "flex items-center space-x-4" }, [ m("div", { class: "flex items-center space-x-2" }, [
// Theme Selector
m("select", {
key: "theme-selector",
class: "bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500",
value: this.editorTheme,
onchange: (e) => this.setTheme(e.target.value)
}, this.availableThemes.map(theme =>
m("option", { value: theme.id }, theme.name)
)),
// Full Screen Toggle
m("button", {
key: "fullscreen-toggle",
class: "p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-400",
onclick: () => this.toggleFullScreen(),
title: this.isFullScreen ? "Exit Full Screen (Esc)" : "Full Screen"
}, m("svg", {
xmlns: "http://www.w3.org/2000/svg",
fill: "none",
viewBox: "0 0 24 24",
stroke: "currentColor",
"stroke-width": 2,
class: "w-5 h-5"
}, this.isFullScreen
? m("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25" })
: m("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" })
)),
m( m(
"select", "select",
{ {
@@ -570,7 +683,9 @@ const Editor = {
] ]
), ),
m("div", { id: "js-editor", class: "rounded shadow h-64" }), m("div", { class: "relative rounded shadow " + (this.isFullScreen ? "flex-1" : "") }, [
m("div", { id: "js-editor", style: "width: 100%; height: 100%;" })
]),
m( m(
"div", "div",
@@ -607,7 +722,9 @@ const Editor = {
] ]
), ),
m("div", { id: "json-editor", class: "rounded shadow h-64" }), m("div", { class: "relative rounded shadow" }, [
m("div", { id: "json-editor", style: "width: 100%; height: 100%;" })
]),
m("input", { type: "hidden", name: "script", value: this.jsValue }), m("input", { type: "hidden", name: "script", value: this.jsValue }),
m("input", { m("input", {

View File

@@ -28,8 +28,10 @@
crossorigin="anonymous" referrerpolicy="no-referrer"></script> crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.1/theme-chrome.min.js" crossorigin="anonymous" <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.1/theme-chrome.min.js" crossorigin="anonymous"
referrerpolicy="no-referrer"></script> referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.1/ext-language_tools.min.js"
referrerpolicy="no-referrer"></script>
<script src="https://unpkg.com/mithril/mithril.js"></script> <script src="/static/js/mithril.js"></script>
<script src="/static/js/mithril/editor.js"></script> <script src="/static/js/mithril/editor.js"></script>
<script src="/static/js/mithril/responseView.js"></script> <script src="/static/js/mithril/responseView.js"></script>
<script src="/static/js/mithril/alert.js"></script> <script src="/static/js/mithril/alert.js"></script>