diff --git a/README.md b/README.md index 4bc0dab..fbf0c9f 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,9 @@ in production, you can do it like this: Leporello.js appends `?leporello` query parameter to your HTML file, so you can test if HTML file is run in Leporello.js or in production. +You can add javascript libraries by including `script` tag to HTML file. If the library is exposing globals, they will be available in your javascript code after you select that HTML file as an entrypoint. + + ## Run and debug UI code in separate window By default your code is run in invisible iframe. If you want to run and debug diff --git a/docs/images/entrypoint.png b/docs/images/entrypoint.png index 90527c3..1c14eb5 100644 Binary files a/docs/images/entrypoint.png and b/docs/images/entrypoint.png differ diff --git a/docs/images/html_file.png b/docs/images/html_file.png index ecc42e7..5b57869 100644 Binary files a/docs/images/html_file.png and b/docs/images/html_file.png differ diff --git a/index.html b/index.html index 56f108d..14a374f 100644 --- a/index.html +++ b/index.html @@ -161,28 +161,6 @@ overflow: auto; } - .entrypoint_select { - overflow: hidden; - display: flex; - width: fit-content; - align-items: center; - margin-left: auto; - min-height: 55px; - } - - .entrypoint_select select { - min-width: 20px; - flex: 1; - } - - .entrypoint_title { - margin-right: 0.5em; - } - - .entrypoint_title:not(:first-child) { - margin-left: 1.5em; - } - .callnode { margin-left: 1em; } @@ -248,10 +226,20 @@ margin-left: 0em !important; } + .files .file_title { + display: flex; + } + .files .file_title.active { background-color: rgb(225, 244, 253); } + .files .file_title .select_entrypoint { + margin-left: auto; + width: 40px; + text-align: center; + } + .files .file_title .icon { display: inline-block; margin-right: 5px; @@ -259,13 +247,27 @@ } .file_actions { + position: relative; display: flex; flex-direction: row; - justify-content: space-evenly; + justify-content: flex-start; + align-items: center; padding: 5px; background-color: rgb(225 244 253 / 80%); } + .file_actions .file_action { + margin-right: 2em; + } + + .file_actions .select_entrypoint_title { + width: 40px; + position: absolute; + right: 7px; + font-size: 0.8em; + text-align: center; + } + /* value_explorer */ .embed_value_explorer_container { diff --git a/src/editor/files.js b/src/editor/files.js index 776fe39..6931224 100644 --- a/src/editor/files.js +++ b/src/editor/files.js @@ -1,7 +1,10 @@ import {el} from './domutils.js' import {map_find} from '../utils.js' import {load_dir, create_file} from '../filesystem.js' -import {exec, get_state, open_directory} from '../index.js' +import {exec, get_state, open_directory, reload_run_window} from '../index.js' + +const is_html = path => path.endsWith('.htm') || path.endsWith('.html') +const is_js = path => path == '' || path.endsWith('.js') || path.endsWith('.mjs') export class Files { constructor(ui) { @@ -10,6 +13,18 @@ export class Files { this.render(get_state()) } + change_entrypoint(e) { + const file = e.target.value + exec('change_entrypoint', file) + this.ui.editor.focus() + } + + change_html_file(e) { + const html_file = e.target.value + exec('change_html_file', html_file) + reload_run_window(get_state()) + } + render(state) { if(state.project_dir == null) { this.el.innerHTML = '' @@ -25,32 +40,40 @@ export class Files { ) ) } else { - this.render_files(state.project_dir, state.current_module) + this.render_files(state) } } - render_files(dir, current_module) { - const files = this.el.querySelector('.files') - + render_files(state) { const children = [ - this.render_file({name: '*scratch*', path: ''}, current_module), - this.render_file(dir, current_module), + this.render_file({name: '*scratch*', path: ''}, state), + this.render_file(state.project_dir, state), ] + const files = this.el.querySelector('.files') + if(files == null) { this.el.innerHTML = '' this.el.appendChild( el('div', 'file_actions', el('a', { + 'class': 'file_action', href: 'javascript: void(0)', click: this.create_file.bind(this, false), }, 'Create file' ), el('a', { + 'class': 'file_action', href: 'javascript: void(0)', click: this.create_file.bind(this, true), }, 'Create dir'), + el('a', { + href: 'https://github.com/leporello-js/leporello-js#selecting-entrypoint-module', + target: '__blank', + "class": 'select_entrypoint_title', + title: 'Select entrypoint', + }, 'Entry point'), ) ) this.el.appendChild( @@ -64,10 +87,40 @@ export class Files { } } - render_file(file, current_module) { + render_select_entrypoint(file, state) { + if(file.kind == 'directory') { + return null + } else if(is_js(file.path)) { + return el('span', 'select_entrypoint', + el('input', { + type: 'radio', + name: 'js_entrypoint', + value: file.path, + checked: state.entrypoint == file.path, + change: e => this.change_entrypoint(e), + click: e => e.stopPropagation(), + }) + ) + } else if(is_html(file.path)) { + return el('span', 'select_entrypoint', + el('input', { + type: 'radio', + name: 'html_file', + value: file.path, + checked: state.html_file == file.path, + change: e => this.change_html_file(e), + click: e => e.stopPropagation(), + }) + ) + } else { + return null + } + } + + render_file(file, state) { const result = el('div', 'file', el('div', { - 'class': 'file_title' + (file.path == current_module ? ' active' : ''), + 'class': 'file_title' + (file.path == state.current_module ? ' active' : ''), click: e => this.on_click(e, file) }, el('span', 'icon', @@ -76,13 +129,14 @@ export class Files { : '\xa0', ), file.name, + this.render_select_entrypoint(file, state), ), file.children == null ? null - : file.children.map(c => this.render_file(c, current_module)) + : file.children.map(c => this.render_file(c, state)) ) - if(file.path == current_module) { + if(file.path == state.current_module) { this.active_el = result this.active_file = file } diff --git a/src/editor/ui.js b/src/editor/ui.js index 56a99c3..e8c38ec 100644 --- a/src/editor/ui.js +++ b/src/editor/ui.js @@ -1,4 +1,4 @@ -import {exec, get_state, open_run_window, reload_run_window} from '../index.js' +import {exec, get_state, open_run_window} from '../index.js' import {Editor} from './editor.js' import {Files} from './files.js' import {CallTree} from './calltree.js' @@ -8,8 +8,6 @@ import {el} from './domutils.js' export class UI { constructor(container, state){ - this.change_entrypoint = this.change_entrypoint.bind(this) - this.change_html_file = this.change_html_file.bind(this) this.open_run_window = this.open_run_window.bind(this) this.files = new Files(this) @@ -42,7 +40,6 @@ export class UI { href: 'javascript: void(0)', }, 'IO trace (F4)') ), - this.entrypoint_select = el('div', 'entrypoint_select') ), this.debugger.calltree = el('div', { 'class': 'tab_content', @@ -58,12 +55,10 @@ export class UI { }), ), this.debugger_loading = el('div', 'debugger_wrapper', - el('div', 'entrypoint_select'), this.debugger_loading_message = el('div'), ), ), this.problems_container = el('div', {"class": 'problems_container', tabindex: 0}, - el('div', 'entrypoint_select'), this.problems = el('div'), ) ), @@ -194,7 +189,6 @@ export class UI { // instead this.debugger.calltree.addEventListener('click', jump_to_fn_location) - this.render_entrypoint_select(state) this.render_current_module(state.current_module) this.set_active_tab('calltree', true) @@ -216,72 +210,10 @@ export class UI { } } - render_entrypoint_select(state) { - for(let select of this.root.getElementsByClassName('entrypoint_select')) { - this.do_render_entrypoint_select(state, select) - } - } - - do_render_entrypoint_select(state, select) { - select.replaceChildren( - el('span', 'entrypoint_title', 'js entrypoint'), - el('select', { - click: e => e.stopPropagation(), - change: this.change_entrypoint, - }, - Object - .keys(state.files) - .sort() - .filter(f => f == '' || f.endsWith('.js') || f.endsWith('.mjs')) - .map(f => - el('option', - state.entrypoint == f - ? { value: f, selected: true } - : { value: f}, - f == '' ? "*scratch*" : f - ) - ) - ), - el('span', 'entrypoint_title', 'html page'), - el('select', { - click: e => e.stopPropagation(), - change: this.change_html_file, - }, - [''] - .concat( - Object - .keys(state.files) - .sort() - .filter(f => f.endsWith('.htm') || f.endsWith('.html')) - ) - .map(f => - el('option', - state.html_file == f - ? { value: f, selected: true } - : { value: f}, - f == '' ? 'about:blank' : f - ) - ) - ), - ) - } - open_run_window() { open_run_window(get_state()) } - change_entrypoint(e) { - const file = e.target.value - exec('change_entrypoint', file) - this.editor.focus() - } - - change_html_file(e) { - const html_file = e.target.value - exec('change_html_file', html_file) - reload_run_window(get_state()) - } - render_debugger_loading(state) { this.debugger_container.style = '' this.problems_container.style = 'display: none' diff --git a/src/effects.js b/src/effects.js index 64d8e20..629ce6f 100644 --- a/src/effects.js +++ b/src/effects.js @@ -140,16 +140,6 @@ export const apply_side_effects = (prev, next, command, ui) => { ui.files.render(next) } - if( - prev.project_dir != next.project_dir - || - prev.entrypoint != next.entrypoint - || - prev.html_file != next.html_file - ) { - ui.render_entrypoint_select(next) - } - if(prev.current_module != next.current_module) { localStorage.current_module = next.current_module ui.render_current_module(next.current_module)