mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 13:04:30 -08:00
async calls WIP
This commit is contained in:
@@ -113,7 +113,9 @@ import BigNumber from './path/to/bignumber.mjs';
|
||||
|
||||

|
||||
|
||||
Currently every external is loaded once and cached until Leporello is restarted (TODO what happens if we load modules in iframe and then recreate iframe)
|
||||
Currently every external is loaded once and cached until Leporello is restarted
|
||||
(TODO what happens if we load modules in iframe and then recreate iframe)
|
||||
(TODO serve modules from service worker, change host every time)
|
||||
|
||||
## Hotkeys
|
||||
|
||||
@@ -123,6 +125,10 @@ See built-in Help
|
||||
|
||||
Editing local files is possible via [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API). Click "Allow access to local project folder" to grant access to local directory.
|
||||
|
||||
## Run and debug UI code in separate window
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
## Run Leporello locally
|
||||
To run it locally, you need to clone repo to local folder and serve it via HTTPS protocol (HTTPS is required by [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API)). See [How to use HTTPS for local development](https://web.dev/how-to-use-local-https/)
|
||||
|
||||
23
docs/examples/preact/index.js
Normal file
23
docs/examples/preact/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/* external */
|
||||
import {h, render} from 'https://unpkg.com/preact?module';
|
||||
|
||||
/* external */
|
||||
import {Stateful} from './stateful.js'
|
||||
|
||||
const Counter = Stateful({
|
||||
getInitialState: () => ({counter: 0}),
|
||||
|
||||
handlers: {
|
||||
inc: ({counter}) => ({counter: counter + 1}),
|
||||
dec: ({counter}) => ({counter: counter - 1}),
|
||||
},
|
||||
|
||||
render: (props, state, handlers) =>
|
||||
h('div', null,
|
||||
h('span', null, state.counter),
|
||||
h('button', {onClick: handlers.inc}, 'Increment'),
|
||||
h('button', {onClick: handlers.dec}, 'Decrement'),
|
||||
)
|
||||
})
|
||||
|
||||
render(h(Counter), globalThis.document.body)
|
||||
31
docs/examples/preact/stateful.js
Normal file
31
docs/examples/preact/stateful.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import {Component} from 'https://unpkg.com/preact?module';
|
||||
|
||||
export const Stateful = ({getInitialState, handlers, render}) => {
|
||||
|
||||
return class extends Component {
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.compState = getInitialState()
|
||||
this.handlers = Object.fromEntries(
|
||||
Object
|
||||
.entries(handlers)
|
||||
.map(([name, h]) =>
|
||||
[name, this.makeHandler(h)]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
makeHandler(h) {
|
||||
return (...args) => {
|
||||
this.compState = h(this.compState, ...args)
|
||||
this.forceUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return render(this.props, this.compState, this.handlers)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -447,10 +447,15 @@ nodes that are in the second tree that are not in the first tree
|
||||
*/
|
||||
const merge_calltrees = (prev, next) => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(prev).map(([module, {exports, calls}]) =>
|
||||
Object.entries(prev).map(([module, {is_external, exports, calls}]) =>
|
||||
[
|
||||
module,
|
||||
{exports, calls: merge_calltree_nodes(calls, next[module].calls)[1]}
|
||||
is_external
|
||||
? {is_external, exports}
|
||||
: {
|
||||
exports,
|
||||
calls: merge_calltree_nodes(calls, next[module].calls)[1]
|
||||
}
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
@@ -71,6 +71,7 @@ const run_code = (s, index, dirty_files) => {
|
||||
...s,
|
||||
parse_result,
|
||||
calltree: null,
|
||||
async_calls: null,
|
||||
|
||||
// Shows that calltree is brand new and requires entire rerender
|
||||
calltree_changed_token: {},
|
||||
@@ -82,7 +83,6 @@ const run_code = (s, index, dirty_files) => {
|
||||
calltree_node_is_expanded: null,
|
||||
frames: null,
|
||||
calltree_node_by_loc: null,
|
||||
// TODO keep selection_state?
|
||||
selection_state: null,
|
||||
loading_external_imports_state: null,
|
||||
}
|
||||
@@ -734,8 +734,10 @@ const move_cursor = (s, index) => {
|
||||
return do_move_cursor(state, index)
|
||||
}
|
||||
|
||||
const on_async_call = (state, ...args) => {
|
||||
console.log('on_async_call', state, args)
|
||||
const on_async_call = (state, call) => {
|
||||
return {...state,
|
||||
async_calls: [...(state.async_calls ?? []), call]
|
||||
}
|
||||
}
|
||||
|
||||
const load_dir = (state, dir) => {
|
||||
|
||||
15
src/eval.js
15
src/eval.js
@@ -391,7 +391,12 @@ export const eval_modules = (
|
||||
is_toplevel_call = is_toplevel_call_copy
|
||||
|
||||
if(is_recording_async_calls && is_toplevel_call) {
|
||||
on_async_call(children)
|
||||
if(children.length != 1) {
|
||||
throw new Error('illegal state')
|
||||
}
|
||||
const call = children[0]
|
||||
children = null
|
||||
on_async_call(call)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -542,13 +547,7 @@ export const eval_modules = (
|
||||
,
|
||||
|
||||
/* on_async_call */
|
||||
calls => {
|
||||
on_async_call(
|
||||
calls.map(c =>
|
||||
assign_code(modules, c)
|
||||
)
|
||||
)
|
||||
},
|
||||
call => on_async_call(assign_code(modules, call))
|
||||
)
|
||||
|
||||
const calltree_actions = {
|
||||
|
||||
11
src/index.js
11
src/index.js
@@ -10,6 +10,17 @@ const EXAMPLE = `const fib = n =>
|
||||
: fib(n - 1) + fib(n - 2)
|
||||
fib(6)`
|
||||
|
||||
|
||||
// By default run code in hidden iframe, until user explicitly opens visible
|
||||
// window
|
||||
globalThis.run_window = (() => {
|
||||
const iframe = document.createElement('iframe')
|
||||
iframe.src = 'about:blank'
|
||||
iframe.setAttribute('hidden', '')
|
||||
document.body.appendChild(iframe)
|
||||
return iframe.contentWindow
|
||||
})()
|
||||
|
||||
export const open_run_window = () => {
|
||||
if(globalThis.run_window != null) {
|
||||
globalThis.run_window.close()
|
||||
|
||||
26
test/test.js
26
test/test.js
@@ -2306,11 +2306,27 @@ const y = x()`
|
||||
// Use Function constructor to exec impure code for testing
|
||||
new Function('fn', 'globalThis.__run_async_call = fn')(fn)
|
||||
`
|
||||
const i = test_initial_state(code, {
|
||||
on_async_call: (calls) => {console.log('test on async call', calls)}
|
||||
})
|
||||
globalThis.__run_async_call()
|
||||
//delete globalThis.__run_async_call
|
||||
|
||||
const {get_async_call, on_async_call} = (new Function(`
|
||||
let call
|
||||
return {
|
||||
get_async_call() {
|
||||
return call
|
||||
},
|
||||
on_async_call(_call) {
|
||||
call = _call
|
||||
}
|
||||
}
|
||||
`))()
|
||||
|
||||
const i = test_initial_state(code, { on_async_call })
|
||||
globalThis.__run_async_call(10)
|
||||
const call = get_async_call()
|
||||
assert_equal(call.fn.name, 'fn')
|
||||
assert_equal(call.code.index, code.indexOf('() => {'))
|
||||
assert_equal(call.args, [10])
|
||||
const state = COMMANDS.on_async_call(i, call)
|
||||
assert_equal(state.async_calls, [call])
|
||||
}),
|
||||
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user