Files
leporello-js/src/editor/ui.js

342 lines
10 KiB
JavaScript
Raw Normal View History

import {exec, get_state, open_app_window, exec_and_reload_app_window} from '../index.js'
2022-09-10 02:48:13 +08:00
import {Editor} from './editor.js'
import {Files} from './files.js'
import {CallTree} from './calltree.js'
import {Logs} from './logs.js'
2023-06-27 15:03:03 +03:00
import {IO_Trace} from './io_trace.js'
2023-10-02 03:27:32 +03:00
import {ShareDialog} from './share_dialog.js'
2022-09-10 02:48:13 +08:00
import {el} from './domutils.js'
export class UI {
constructor(container, state){
2023-07-11 18:24:28 +03:00
this.open_app_window = this.open_app_window.bind(this)
2022-09-10 02:48:13 +08:00
this.files = new Files(this)
this.tabs = {}
this.debugger = {}
2022-09-10 02:48:13 +08:00
container.appendChild(
2023-05-19 15:59:15 +03:00
(this.root = el('div', 'root',
2022-09-10 02:48:13 +08:00
this.editor_container = el('div', 'editor_container'),
el('div', 'bottom',
this.debugger_container = el('div', 'debugger',
2023-01-15 22:00:49 +08:00
this.debugger_loaded = el('div', 'debugger_wrapper',
el('div', 'tabs',
this.tabs.calltree = el('div', 'tab',
el('a', {
click: () => this.set_active_tab('calltree'),
href: 'javascript: void(0)',
}, 'Call tree (F2)')
),
this.tabs.logs = el('div', 'tab',
el('a', {
click: () => this.set_active_tab('logs'),
href: 'javascript: void(0)',
}, 'Logs (F3)')
),
2023-06-27 15:03:03 +03:00
this.tabs.io_trace = el('div', 'tab',
2023-02-13 17:39:34 +08:00
el('a', {
2023-06-27 15:03:03 +03:00
click: () => this.set_active_tab('io_trace'),
2023-02-13 17:39:34 +08:00
href: 'javascript: void(0)',
2023-06-27 15:03:03 +03:00
}, 'IO trace (F4)')
2023-02-13 17:39:34 +08:00
),
),
2023-01-15 22:00:49 +08:00
this.debugger.calltree = el('div', {
'class': 'tab_content',
tabindex: 0,
}),
this.debugger.logs = el('div', {
'class': 'tab_content logs',
tabindex: 0,
}),
2023-06-27 15:03:03 +03:00
this.debugger.io_trace = el('div', {
'class': 'tab_content io_trace',
2023-02-13 17:39:34 +08:00
tabindex: 0,
}),
),
this.debugger_loading = el('div', 'debugger_wrapper',
this.debugger_loading_message = el('div'),
),
),
this.problems_container = el('div', {"class": 'problems_container', tabindex: 0},
this.problems = el('div'),
)
2022-09-10 02:48:13 +08:00
),
this.files.el,
this.statusbar = el('div', 'statusbar',
this.status = el('div', 'status'),
this.current_module = el('div', 'current_module'),
2022-10-26 01:05:52 +08:00
el('a', {
2023-02-13 17:39:34 +08:00
'class': 'statusbar_action first',
href: 'javascript: void(0)',
click: () => this.clear_io_trace(),
2023-02-13 17:39:34 +08:00
},
2023-06-27 15:03:03 +03:00
'Clear IO trace (F6)'
2023-02-13 17:39:34 +08:00
),
el('a', {
2023-07-14 03:02:10 +03:00
'class': 'statusbar_action open_app_window_button',
2022-10-26 01:05:52 +08:00
href: 'javascript: void(0)',
2023-07-11 18:24:28 +03:00
click: this.open_app_window,
2022-10-26 01:05:52 +08:00
},
2023-07-14 03:02:10 +03:00
'(Re)open app window (F7)',
this.open_app_window_tooltip = el('div', {
'class': 'open_app_window_tooltip',
},
'Click here to open app window'
)
2022-10-26 01:05:52 +08:00
),
2022-09-10 02:48:13 +08:00
this.options = el('div', 'options',
el('label', {'for': 'standard'},
el('input', {
id: 'standard',
type: 'radio',
name: 'keyboard',
checked: localStorage.keyboard == 'standard'
|| localStorage.keyboard == null,
change: () => {
this.editor.set_keyboard_handler('standard')
}
}),
'Standard'
),
el('label', {'for': 'vim'},
el('input', {
id: 'vim',
type: 'radio',
name: 'keyboard',
checked: localStorage.keyboard == 'vim',
change: () => {
this.editor.set_keyboard_handler('vim')
}
}),
'VIM'
)
),
el('a', {
'class': 'show_help',
href: 'javascript: void(0)',
click: () => this.help_dialog.showModal(),
},
'Help',
),
el('a', {
'class': 'github',
href: 'https://github.com/leporello-js/leporello-js',
target: '__blank',
}, 'Github'),
2023-10-02 03:27:32 +03:00
el('button', {
'class': 'share_button',
'click': () => this.share_dialog.showModal(),
}, 'Share'),
2022-09-10 02:48:13 +08:00
this.help_dialog = this.render_help(),
2023-10-02 03:27:32 +03:00
this.share_dialog = new ShareDialog().el,
2022-09-10 02:48:13 +08:00
)
))
)
2022-10-26 00:39:22 +08:00
window.addEventListener('keydown', () => this.clear_status(), true)
window.addEventListener('click', () => this.clear_status(), true)
2022-09-10 02:48:13 +08:00
2022-10-26 00:39:22 +08:00
window.addEventListener('keydown', e => {
2022-10-25 04:43:35 +08:00
if(e.key == 'F2') {
this.set_active_tab('calltree')
}
if(e.key == 'F3'){
this.set_active_tab('logs')
2022-09-10 02:48:13 +08:00
}
2023-02-13 17:39:34 +08:00
if(e.key == 'F4'){
2023-06-27 15:03:03 +03:00
this.set_active_tab('io_trace')
2022-09-10 02:48:13 +08:00
}
2022-10-26 01:05:52 +08:00
if(e.key == 'F6'){
2023-06-27 15:03:03 +03:00
exec('clear_io_trace')
2023-02-13 17:39:34 +08:00
}
if(e.key == 'F7'){
2023-07-11 18:24:28 +03:00
this.open_app_window()
2022-10-26 01:05:52 +08:00
}
2022-10-25 04:43:35 +08:00
})
2022-09-10 02:48:13 +08:00
this.editor = new Editor(this, this.editor_container)
this.calltree = new CallTree(this, this.debugger.calltree)
this.logs = new Logs(this, this.debugger.logs)
2023-06-27 15:03:03 +03:00
this.io_trace = new IO_Trace(this, this.debugger.io_trace)
2022-09-10 02:48:13 +08:00
this.render_current_module(state.current_module)
this.set_active_tab('calltree', true)
}
set_active_tab(tab_id, skip_focus = false) {
2023-02-13 17:39:34 +08:00
this.active_tab = tab_id
Object.values(this.tabs).forEach(el => el.classList.remove('active'))
this.tabs[tab_id].classList.add('active')
Object.values(this.debugger).forEach(el => el.style.display = 'none')
this.debugger[tab_id].style.display = 'block'
2023-02-14 18:03:10 +08:00
2023-06-27 15:03:03 +03:00
if(tab_id == 'io_trace') {
this.io_trace.render_io_trace(get_state(), false)
2023-02-14 18:03:10 +08:00
}
if(!skip_focus) {
this.debugger[tab_id].focus()
}
if(tab_id == 'calltree' && !skip_focus) {
exec('calltree.show_value_explorer')
}
2022-09-10 02:48:13 +08:00
}
clear_io_trace() {
exec_and_reload_app_window('clear_io_trace')
}
2023-07-11 18:24:28 +03:00
open_app_window() {
2023-07-14 03:02:10 +03:00
this.toggle_open_app_window_tooltip(false)
localStorage.onboarding_open_app_window = true
2023-07-11 18:24:28 +03:00
open_app_window(get_state())
2022-11-28 20:53:35 +08:00
}
2023-01-17 11:14:10 +08:00
render_debugger_loading(state) {
this.debugger_container.style = ''
2022-09-10 02:48:13 +08:00
this.problems_container.style = 'display: none'
2023-01-17 11:14:10 +08:00
this.debugger_loaded.style = 'display: none'
this.debugger_loading.style = ''
this.debugger_loading_message.innerText =
2023-01-15 22:00:49 +08:00
state.loading_external_imports_state != null
2023-01-17 11:14:10 +08:00
? 'Loading external modules...'
: 'Waiting...'
}
render_debugger(state) {
this.debugger_container.style = ''
this.problems_container.style = 'display: none'
this.debugger_loading.style = 'display: none'
this.debugger_loaded.style = ''
2023-02-13 17:39:34 +08:00
2023-01-17 11:14:10 +08:00
this.calltree.render_calltree(state)
this.logs.render_logs(state, null, state.logs)
2023-02-14 18:03:10 +08:00
}
2023-02-13 17:39:34 +08:00
2023-06-27 15:03:03 +03:00
render_io_trace(state) {
2023-02-14 18:03:10 +08:00
// render lazily, only if selected
2023-06-27 15:03:03 +03:00
if(this.active_tab == 'io_trace') {
this.io_trace.render_io_trace(state, true)
2023-02-14 18:03:10 +08:00
} else {
// Do not render until user switch to the tab
2023-06-27 15:03:03 +03:00
this.io_trace.clear()
2023-02-14 18:03:10 +08:00
}
2022-09-10 02:48:13 +08:00
}
render_problems(problems) {
this.debugger_container.style = 'display: none'
2022-09-10 02:48:13 +08:00
this.problems_container.style = ''
this.problems.innerHTML = ''
2022-09-10 02:48:13 +08:00
problems.forEach(p => {
const s = this.editor.get_session(p.module)
const pos = s.doc.indexToPosition(p.index)
const module = p.module == '' ? "*scratch*" : p.module
this.problems.appendChild(
2022-09-10 02:48:13 +08:00
el('div', 'problem',
el('a', {
href: 'javascript:void(0)',
click: () => exec('goto_problem', p)
},
`${module}:${pos.row + 1}:${pos.column} - ${p.message}`
)
)
)
})
}
set_status(text){
this.current_module.style = 'display: none'
this.status.style = ''
this.status.innerText = text
}
clear_status(){
this.render_current_module(get_state().current_module)
}
render_current_module(current_module) {
this.status.style = 'display: none'
this.current_module.innerText =
current_module == ''
? '*scratch*'
: current_module
this.current_module.style = ''
}
render_help() {
const options = [
['Focus value explorer', 'F1'],
2022-09-10 02:48:13 +08:00
['Navigate value explorer', '← → ↑ ↓ or hjkl'],
2022-10-25 04:43:35 +08:00
['Leave value explorer', 'F1 or Esc'],
['Focus call tree view', 'F2'],
['Navigate call tree view', '← → ↑ ↓ or hjkl'],
['Leave call tree view', 'F2 or Esc'],
['Focus console logs', 'F3'],
['Navigate console logs', '↑ ↓ or jk'],
2022-10-25 04:43:35 +08:00
['Leave console logs', 'F3 or Esc'],
2023-06-27 15:03:03 +03:00
['Focus IO trace', 'F4'],
['Leave IO trace', 'F4 or Esc'],
2023-02-13 17:39:34 +08:00
['Jump to definition', 'F5', 'gd'],
2022-09-10 02:48:13 +08:00
['Expand selection to eval expression', 'Ctrl-↓ or Ctrl-j'],
['Collapse selection', 'Ctrl-↑ or Ctrl-k'],
['Step into call', 'Ctrl-i', '\\i'],
['Step out of call', 'Ctrl-o', '\\o'],
['When in call tree view, jump to return statement', 'Enter'],
['When in call tree view, jump to function arguments', 'a'],
2023-10-26 09:41:17 +08:00
['When in call tree view, jump to error origin', 'e'],
2023-06-27 15:03:03 +03:00
['Clear IO trace', 'F6'],
2023-02-13 17:39:34 +08:00
['(Re)open run window (F7)', 'F7'],
2022-09-10 02:48:13 +08:00
]
return el('dialog', 'help_dialog',
el('table', 'help',
el('thead', '',
el('th', '', 'Action'),
el('th', 'key', 'Standard'),
el('th', 'key', 'VIM'),
),
el('tbody', '',
options.map(([text, standard, vim]) =>
el('tr', '',
el('td', '', text),
el('td',
vim == null
? {'class': 'key spanned', colspan: 2}
: {'class': 'key'},
standard
),
vim == null
? null
: el('td', 'key', vim),
)
)
)
),
el('form', {method: 'dialog'},
el('button', null, 'Close'),
),
)
}
2023-07-14 03:02:10 +03:00
toggle_open_app_window_tooltip(on) {
this.open_app_window_tooltip.classList.toggle('on', on)
}
2022-09-10 02:48:13 +08:00
}