From 465bbd83f4e266461c836718bffb32d92b76660c Mon Sep 17 00:00:00 2001 From: Dmitry Vasilev Date: Fri, 14 Jul 2023 03:02:10 +0300 Subject: [PATCH] link to example --- docs/examples/todos-preact/index.js | 1 - index.html | 31 ++++++++++++++++++ src/editor/files.js | 46 ++++++++++++++++++++++----- src/editor/ui.js | 15 +++++++-- src/effects.js | 9 +++++- src/examples.js | 36 +++++++++++++++------ src/index.js | 49 ++++++++++++++++++----------- 7 files changed, 147 insertions(+), 40 deletions(-) diff --git a/docs/examples/todos-preact/index.js b/docs/examples/todos-preact/index.js index af15975..886a851 100644 --- a/docs/examples/todos-preact/index.js +++ b/docs/examples/todos-preact/index.js @@ -1,6 +1,5 @@ import {h, render} from 'https://unpkg.com/preact?module'; -// external import {createApp, handler, connect} from './app.js' // Components diff --git a/index.html b/index.html index 5c6c5a0..157b245 100644 --- a/index.html +++ b/index.html @@ -341,6 +341,37 @@ margin-left: auto; } + .open_app_window_button { + position: relative; + } + .open_app_window_tooltip { + padding: 1em; + position: absolute; + margin-bottom: 20px; + bottom: 100%; + border: none; + font-size: 1.7em; + background-color: rgb(120 206 247); + border-radius: 21px; + transform: scale(0); + transition: transform 0.3s; + } + .open_app_window_tooltip.on { + transform: scale(1); + } + .open_app_window_tooltip:after { + content: ''; + width: 0; + height: 0; + border-left: 20px solid transparent; + border-right: 20px solid transparent; + border-top: 20px solid rgb(120 206 247); + position: absolute; + bottom: -20px; + left: 50%; + transform: translate(-50%); + } + .options { padding: 5px; } diff --git a/src/editor/files.js b/src/editor/files.js index fd0ff75..3c59b8a 100644 --- a/src/editor/files.js +++ b/src/editor/files.js @@ -1,6 +1,7 @@ import {el} from './domutils.js' import {map_find} from '../utils.js' import {open_dir, create_file} from '../filesystem.js' +import {examples} from '../examples.js' import { exec, get_state, @@ -219,16 +220,47 @@ export class Files { on_click(e, file) { e.stopPropagation() - this.render_current_module(file.path) - if(file.kind != 'directory') { - if(get_state().has_file_system_access) { + if(get_state().has_file_system_access) { + if(file.kind != 'directory') { exec('change_current_module', file.path) - } else { - // in examples mode, on click file we also change entrypoint for - // simplicity - exec('change_entrypoint', file.path) + } + } else { + + if(file.path == null) { + // root of examples dir, do nothing + return + } + + if(file.path == '') { + exec('change_entrypoint', '') + return + } + + const find_node = n => + n.path == file.path + || + n.children != null && n.children.find(find_node) + + // find example dir + const example_dir = get_state().project_dir.children.find( + c => find_node(c) != null + ) + + // in examples mode, on click file we also change entrypoint for + // simplicity + const example = examples.find(e => e.path == example_dir.path) + exec('change_entrypoint', example.entrypoint) + if(example.with_app_window && !localStorage.onboarding_open_app_window) { + this.ui.toggle_open_app_window_tooltip(true) } } + + // Note that we call render_current_module AFTER exec('change_entrypoint'), + // because in case we clicked to example dir, entrypoint would be set, and + // rendered active, so file with entrypoint would be active and not dir we + // clicked, which is weird + this.render_current_module(file.path) + } } diff --git a/src/editor/ui.js b/src/editor/ui.js index 9547c0c..935ebb5 100644 --- a/src/editor/ui.js +++ b/src/editor/ui.js @@ -88,11 +88,16 @@ export class UI { ), el('a', { - 'class': 'statusbar_action', + 'class': 'statusbar_action open_app_window_button', href: 'javascript: void(0)', click: this.open_app_window, }, - '(Re)open app window (F7)' + '(Re)open app window (F7)', + this.open_app_window_tooltip = el('div', { + 'class': 'open_app_window_tooltip', + }, + 'Click here to open app window' + ) ), this.options = el('div', 'options', @@ -211,6 +216,8 @@ export class UI { } open_app_window() { + this.toggle_open_app_window_tooltip(false) + localStorage.onboarding_open_app_window = true open_app_window(get_state()) } @@ -350,4 +357,8 @@ export class UI { } } + toggle_open_app_window_tooltip(on) { + this.open_app_window_tooltip.classList.toggle('on', on) + } + } diff --git a/src/effects.js b/src/effects.js index d53022a..aca0efd 100644 --- a/src/effects.js +++ b/src/effects.js @@ -127,9 +127,16 @@ const render_parse_result = (ui, state) => { } } -export const render_initial_state = (ui, state) => { +export const render_initial_state = (ui, state, example) => { ensure_session(ui, state) ui.editor.switch_session(state.current_module) + if( + example != null + && example.with_app_window + && !localStorage.onboarding_open_app_window + ) { + ui.toggle_open_app_window_tooltip(true) + } } export const apply_side_effects = (prev, next, command, ui) => { diff --git a/src/examples.js b/src/examples.js index c2b2a0c..af0badc 100644 --- a/src/examples.js +++ b/src/examples.js @@ -6,17 +6,34 @@ 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('/')) +export const examples = [ + { + path: 'github_api', + entrypoint: 'github_api/index.js', + }, + { + path: 'ethers', + entrypoint: 'ethers/block_by_timestamp.js', + }, + { + path: 'todos-preact', + entrypoint: 'todos-preact/index.js', + with_app_window: true, + files: [ + 'todos-preact/app.js', + ] + }, +].map(e => ({...e, entrypoint: e.entrypoint ?? e.path})) +const files_list = examples + .map(e => { + return (e.files ?? []).concat([e.entrypoint]) + }) + .flat() + .map(l => l.split('/')) const get_children = path => { - const children = list.filter(l => path.every((elem, i) => elem == l[i] )) + const children = files_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) @@ -46,7 +63,7 @@ const get_children = path => { }))) } -export const examples_promise = get_children([]).then(children => { +export const examples_dir_promise = get_children([]).then(children => { return { kind: 'directory', name: 'examples', @@ -54,4 +71,3 @@ export const examples_promise = get_children([]).then(children => { children, } }) - diff --git a/src/index.js b/src/index.js index ee31735..65f33df 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ import { close_dir, init_window_service_worker } from './filesystem.js' -import {examples_promise} from './examples.js' +import {examples, examples_dir_promise} from './examples.js' const EXAMPLE = `function fib(n) { if(n == 0 || n == 1) { @@ -141,20 +141,10 @@ export const reload_app_window = state => { globalThis.app_window.location = get_html_url(state) } - - 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, + current_module: localStorage.current_module ?? '', + entrypoint: localStorage.entrypoint ?? '', html_file: localStorage.html_file ?? '', } } @@ -171,7 +161,7 @@ export const open_directory = () => { export const close_directory = async () => { close_dir() - exec('load_dir', await examples_promise, false, get_entrypoint_settings()) + exec('load_dir', await examples_dir_promise, false, get_entrypoint_settings()) } @@ -185,15 +175,37 @@ export const init = async (container, _COMMANDS) => { set_error_handler(window) const default_module = {'': localStorage.code || EXAMPLE} - let initial_state + let initial_state, entrypoint_settings const project_dir = await open_dir(false) + let example if(project_dir == null) { + /* + extract example from URL params and delete it + */ + const params = new URLSearchParams(window.location.search) + const example_path = params.get('example') + params.delete('example') + globalThis.history.replaceState( + null, + null, + '/' + params.toString() + window.location.hash + ) + + example = examples.find(e => e.path == example_path) + entrypoint_settings = example == null + ? get_entrypoint_settings() + : { + current_module: example.entrypoint, + entrypoint: example.entrypoint, + } + initial_state = { - project_dir: await examples_promise, + project_dir: await examples_dir_promise, files: default_module, has_file_system_access: false, } } else { + entrypoint_settings = get_entrypoint_settings() initial_state = { project_dir, files: default_module, @@ -206,8 +218,7 @@ export const init = async (container, _COMMANDS) => { ...initial_state, on_deferred_call: (...args) => exec('on_deferred_call', ...args) }, - - get_entrypoint_settings(), + entrypoint_settings, ) // Expose state for debugging @@ -216,7 +227,7 @@ export const init = async (container, _COMMANDS) => { // Expose for debugging globalThis.__ui = ui - render_initial_state(ui, state) + render_initial_state(ui, state, example) open_run_iframe(state) }