mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 13:04:30 -08:00
examples
This commit is contained in:
@@ -200,16 +200,10 @@
|
||||
}
|
||||
|
||||
.allow_file_access {
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
.allow_file_access .subtitle {
|
||||
padding: 10px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
|
||||
17
src/cmd.js
17
src/cmd.js
@@ -808,7 +808,7 @@ const clear_io_trace = state => {
|
||||
return run_code({...state, io_trace: null})
|
||||
}
|
||||
|
||||
const do_load_dir = (state, dir) => {
|
||||
const load_files = (state, dir) => {
|
||||
const collect_files = dir => dir.kind == 'file'
|
||||
? [dir]
|
||||
: dir.children.map(collect_files).flat()
|
||||
@@ -820,7 +820,7 @@ const do_load_dir = (state, dir) => {
|
||||
return {
|
||||
...state,
|
||||
project_dir: dir,
|
||||
files: {...files, ...state.files},
|
||||
files: {...files, '': state.files['']},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -842,24 +842,29 @@ const apply_entrypoint_settings = (state, entrypoint_settings) => {
|
||||
}
|
||||
}
|
||||
|
||||
const load_dir = (state, dir, entrypoint_settings) => {
|
||||
const load_dir = (state, dir, has_file_system_access, entrypoint_settings) => {
|
||||
// Clear parse cache and rerun code
|
||||
const with_dir = do_load_dir(state, dir)
|
||||
const with_dir = load_files(state, dir)
|
||||
return run_code({
|
||||
...(
|
||||
entrypoint_settings == null
|
||||
? with_dir
|
||||
: apply_entrypoint_settings(with_dir, entrypoint_settings)
|
||||
),
|
||||
|
||||
has_file_system_access,
|
||||
|
||||
// remove cache. We have to clear cache because imports of modules that are
|
||||
// not available because project_dir is not available have errors and the
|
||||
// errors are cached
|
||||
parse_result: null,
|
||||
|
||||
external_imports_cache: null,
|
||||
})
|
||||
}
|
||||
|
||||
const create_file = (state, dir, current_module) => {
|
||||
return {...load_dir(state, dir), current_module}
|
||||
return {...load_dir(state, dir, true), current_module}
|
||||
}
|
||||
|
||||
const open_run_window = (state, globals) => {
|
||||
@@ -877,7 +882,7 @@ const open_run_window = (state, globals) => {
|
||||
const get_initial_state = (state, entrypoint_settings) => {
|
||||
const with_files = state.project_dir == null
|
||||
? state
|
||||
: do_load_dir(state, state.project_dir)
|
||||
: load_files(state, state.project_dir)
|
||||
|
||||
const with_settings = apply_entrypoint_settings(with_files, entrypoint_settings)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export function el(tag, className, ...children){
|
||||
const append = child => {
|
||||
if(typeof(child) == 'undefined') {
|
||||
throw new Error('illegal state')
|
||||
} else if(child !== null) {
|
||||
} else if(child !== null && child !== false) {
|
||||
result.appendChild(
|
||||
typeof(child) == 'string'
|
||||
? document.createTextNode(child)
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
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, reload_run_window} from '../index.js'
|
||||
import {open_dir, create_file} from '../filesystem.js'
|
||||
import {
|
||||
exec,
|
||||
get_state,
|
||||
open_directory,
|
||||
reload_run_window,
|
||||
close_directory,
|
||||
} from '../index.js'
|
||||
|
||||
const is_html = path => path.endsWith('.htm') || path.endsWith('.html')
|
||||
const is_js = path => path == '' || path.endsWith('.js') || path.endsWith('.mjs')
|
||||
@@ -25,27 +31,56 @@ export class Files {
|
||||
reload_run_window(get_state())
|
||||
}
|
||||
|
||||
render(state) {
|
||||
if(state.project_dir == null) {
|
||||
this.el.innerHTML = ''
|
||||
this.el.appendChild(
|
||||
el('div', 'allow_file_access',
|
||||
el('a', {
|
||||
href: 'javascript:void(0)',
|
||||
click: open_directory,
|
||||
},
|
||||
`Allow access to local project folder`,
|
||||
),
|
||||
el('div', 'subtitle', `Your files will never leave your device`)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
this.render_files(state)
|
||||
}
|
||||
}
|
||||
|
||||
render_files(state) {
|
||||
const children = [
|
||||
render(state) {
|
||||
const file_actions = state.has_file_system_access
|
||||
? el('div', 'file_actions',
|
||||
el('a', {
|
||||
'class': 'file_action',
|
||||
href: 'javascript: void(0)',
|
||||
click: this.create_file.bind(this, false),
|
||||
},
|
||||
'New file'
|
||||
),
|
||||
|
||||
el('a', {
|
||||
'class': 'file_action',
|
||||
href: 'javascript: void(0)',
|
||||
click: this.create_file.bind(this, true),
|
||||
},
|
||||
'New dir'
|
||||
),
|
||||
|
||||
el('a', {
|
||||
'class': 'file_action',
|
||||
href: 'javascript: void(0)',
|
||||
click: close_directory,
|
||||
},
|
||||
'Revoke access'
|
||||
),
|
||||
|
||||
el('a', {
|
||||
href: 'https://github.com/leporello-js/leporello-js#selecting-entrypoint-module',
|
||||
target: '__blank',
|
||||
"class": 'select_entrypoint_title',
|
||||
title: 'Select entrypoint',
|
||||
},
|
||||
'Entry point'
|
||||
),
|
||||
)
|
||||
: el('div', 'file_actions',
|
||||
el('div', 'file_action allow_file_access',
|
||||
el('a', {
|
||||
href: 'javascript: void(0)',
|
||||
click: open_directory,
|
||||
}, 'Allow access to local project folder'),
|
||||
el('span', 'subtitle', `Your files will never leave your device`)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
const file_elements = [
|
||||
this.render_file({name: '*scratch*', path: ''}, state),
|
||||
this.render_file(state.project_dir, state),
|
||||
]
|
||||
@@ -54,41 +89,19 @@ export class Files {
|
||||
|
||||
if(files == null) {
|
||||
this.el.innerHTML = ''
|
||||
this.el.appendChild(file_actions)
|
||||
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(
|
||||
el('div', 'files',
|
||||
children
|
||||
)
|
||||
el('div', 'files', file_elements)
|
||||
)
|
||||
} else {
|
||||
// Replace to preserve scroll position
|
||||
files.replaceChildren(...children)
|
||||
this.el.replaceChild(file_actions, this.el.children[0])
|
||||
files.replaceChildren(...file_elements)
|
||||
}
|
||||
}
|
||||
|
||||
render_select_entrypoint(file, state) {
|
||||
if(file.kind == 'directory') {
|
||||
if(!state.has_file_system_access || file.kind == 'directory') {
|
||||
return null
|
||||
} else if(is_js(file.path)) {
|
||||
return el('span', 'select_entrypoint',
|
||||
@@ -189,9 +202,9 @@ export class Files {
|
||||
await create_file(path, is_dir)
|
||||
|
||||
// Reload all files for simplicity
|
||||
load_dir(false).then(dir => {
|
||||
open_dir(false).then(dir => {
|
||||
if(is_dir) {
|
||||
exec('load_dir', dir)
|
||||
exec('load_dir', dir, true)
|
||||
} else {
|
||||
exec('create_file', dir, path)
|
||||
}
|
||||
@@ -205,8 +218,15 @@ export class Files {
|
||||
this.active_el = e.currentTarget.parentElement
|
||||
e.currentTarget.classList.add('active')
|
||||
this.active_file = file
|
||||
|
||||
if(file.kind != 'directory') {
|
||||
exec('change_current_module', file.path)
|
||||
if(get_state().has_file_system_access) {
|
||||
exec('change_current_module', file.path)
|
||||
} else {
|
||||
// in examples mode, on click file we also change entrypoint for
|
||||
// simplicity
|
||||
exec('change_entrypoint', file.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
src/effects.js
vendored
9
src/effects.js
vendored
@@ -1,4 +1,5 @@
|
||||
import {write_file} from './filesystem.js'
|
||||
import {write_example} from './examples.js'
|
||||
import {color_file} from './color.js'
|
||||
import {
|
||||
root_calltree_node,
|
||||
@@ -293,6 +294,12 @@ export const EFFECTS = {
|
||||
localStorage[key] = value
|
||||
},
|
||||
|
||||
write: (state, [name, contents], ui) => write_file(name, contents),
|
||||
write: (state, [name, contents], ui) => {
|
||||
if(state.has_file_system_access) {
|
||||
write_file(name, contents)
|
||||
} else {
|
||||
write_example(name, contents)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
57
src/examples.js
Normal file
57
src/examples.js
Normal file
@@ -0,0 +1,57 @@
|
||||
export const write_example = (name, contents) => {
|
||||
localStorage['examples_' + name] = contents
|
||||
}
|
||||
|
||||
const read_example = name => {
|
||||
return localStorage['examples_' + name]
|
||||
}
|
||||
|
||||
const list = [
|
||||
'github_api/index.js',
|
||||
'ethers/block_by_timestamp.js',
|
||||
'ethers/index.js',
|
||||
// TODO for html5 example, open run window or hint that it should be opened
|
||||
]
|
||||
.map(l => l.split('/'))
|
||||
|
||||
|
||||
const get_children = path => {
|
||||
const children = list.filter(l => path.every((elem, i) => elem == l[i] ))
|
||||
const files = children.filter(c => c.length == path.length + 1)
|
||||
const dirs = [...new Set(children
|
||||
.filter(c => c.length != path.length + 1)
|
||||
.map(c => c[path.length])
|
||||
)]
|
||||
return Promise.all(files.map(async f => {
|
||||
const name = f[path.length]
|
||||
const filepath = f.slice(0, path.length + 1).join('/')
|
||||
return {
|
||||
name,
|
||||
path: filepath,
|
||||
kind: 'file',
|
||||
contents:
|
||||
read_example(filepath) ??
|
||||
await fetch(globalThis.location.origin + '/docs/examples/'+ filepath)
|
||||
.then(r => r.text()),
|
||||
}
|
||||
})
|
||||
.concat(dirs.map(async d => {
|
||||
const p = [...path, d]
|
||||
return {
|
||||
name: d,
|
||||
path: p.join('/'),
|
||||
kind: 'directory',
|
||||
children: await get_children(p),
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
export const examples_promise = get_children([]).then(children => {
|
||||
return {
|
||||
kind: 'directory',
|
||||
name: 'examples',
|
||||
path: null,
|
||||
children,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -15,11 +15,10 @@ const send_message = (message) => {
|
||||
});
|
||||
}
|
||||
|
||||
globalThis.clear_directory_handle = () => {
|
||||
export const close_dir = () => {
|
||||
send_message({type: 'SET_DIR_HANDLE', data: null})
|
||||
clearInterval(keepalive_interval_id)
|
||||
keepalive_interval_id = null
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
let dir_handle
|
||||
@@ -120,7 +119,7 @@ const read_file = async handle => {
|
||||
return await file_data.text()
|
||||
}
|
||||
|
||||
const do_load_dir = async (handle, path) => {
|
||||
const do_open_dir = async (handle, path) => {
|
||||
if(handle.kind == 'directory') {
|
||||
const children = []
|
||||
for await (let [name, h] of handle) {
|
||||
@@ -134,7 +133,7 @@ const do_load_dir = async (handle, path) => {
|
||||
kind: 'directory',
|
||||
children: (await Promise.all(
|
||||
children.map(c =>
|
||||
do_load_dir(c, path == null ? c.name : path + '/' + c.name)
|
||||
do_open_dir(c, path == null ? c.name : path + '/' + c.name)
|
||||
)
|
||||
)).sort((a, b) => a.name.localeCompare(b.name))
|
||||
}
|
||||
@@ -159,7 +158,7 @@ export const create_file = (path, is_dir) => {
|
||||
)
|
||||
}
|
||||
|
||||
export const load_dir = async (should_request_access) => {
|
||||
export const open_dir = async (should_request_access) => {
|
||||
let handle
|
||||
if(should_request_access) {
|
||||
handle = await request_directory_handle()
|
||||
@@ -171,5 +170,5 @@ export const load_dir = async (should_request_access) => {
|
||||
} else {
|
||||
keep_service_worker_alive()
|
||||
}
|
||||
return do_load_dir(handle, null)
|
||||
return do_open_dir(handle, null)
|
||||
}
|
||||
|
||||
58
src/index.js
58
src/index.js
@@ -1,12 +1,22 @@
|
||||
import {UI} from './editor/ui.js'
|
||||
import {EFFECTS, render_initial_state, apply_side_effects} from './effects.js'
|
||||
import {load_dir, init_window_service_worker} from './filesystem.js'
|
||||
import {
|
||||
open_dir,
|
||||
close_dir,
|
||||
init_window_service_worker
|
||||
} from './filesystem.js'
|
||||
import {examples_promise} from './examples.js'
|
||||
|
||||
const EXAMPLE = `const fib = n =>
|
||||
n == 0 || n == 1
|
||||
? n
|
||||
: fib(n - 1) + fib(n - 2)
|
||||
fib(6)`
|
||||
const EXAMPLE = `function fib(n) {
|
||||
if(n == 0 || n == 1) {
|
||||
return n
|
||||
} else {
|
||||
return fib(n - 1) + fib(n - 2)
|
||||
}
|
||||
}
|
||||
|
||||
fib(6)
|
||||
`
|
||||
|
||||
|
||||
const set_error_handler = (w, with_unhandled_rejection = true) => {
|
||||
@@ -131,38 +141,56 @@ export const reload_run_window = state => {
|
||||
globalThis.run_window.location = get_html_url(state)
|
||||
}
|
||||
|
||||
|
||||
const read_modules = async () => {
|
||||
const default_module = {'': localStorage.code || EXAMPLE}
|
||||
const project_dir = await load_dir(false)
|
||||
const project_dir = await open_dir(false)
|
||||
if(project_dir == null) {
|
||||
// Single anonymous module
|
||||
return {
|
||||
project_dir: await examples_promise,
|
||||
files: default_module,
|
||||
has_file_system_access: false,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
project_dir,
|
||||
files: default_module,
|
||||
has_file_system_access: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const get_entrypoint_settings = () => ({
|
||||
current_module: localStorage.current_module ?? '',
|
||||
entrypoint: localStorage.entrypoint ?? '',
|
||||
html_file: localStorage.html_file ?? '',
|
||||
})
|
||||
const get_entrypoint_settings = () => {
|
||||
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
|
||||
const entrypoint = null
|
||||
?? params.get('entrypoint')
|
||||
?? localStorage.entrypoint
|
||||
?? ''
|
||||
|
||||
return {
|
||||
current_module: params.get('entrypoint') ?? localStorage.current_module ?? '',
|
||||
entrypoint,
|
||||
html_file: localStorage.html_file ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const open_directory = () => {
|
||||
if(globalThis.showDirectoryPicker == null) {
|
||||
throw new Error('Your browser is not supporting File System Access API')
|
||||
}
|
||||
load_dir(true).then(dir => {
|
||||
exec('load_dir', dir, get_entrypoint_settings())
|
||||
open_dir(true).then(dir => {
|
||||
exec('load_dir', dir, true, get_entrypoint_settings())
|
||||
})
|
||||
}
|
||||
|
||||
export const close_directory = async () => {
|
||||
close_dir()
|
||||
exec('load_dir', await examples_promise, false, get_entrypoint_settings())
|
||||
}
|
||||
|
||||
|
||||
let COMMANDS
|
||||
let ui
|
||||
|
||||
Reference in New Issue
Block a user