Compare commits

...

2 Commits

5 changed files with 383 additions and 60 deletions

View File

@ -4,7 +4,6 @@
<script type="importmap"> <script type="importmap">
{ {
"imports": { "imports": {
"codemirror": "./codemirror/index.js",
"@codemirror/view": "./@codemirror/view/index.js", "@codemirror/view": "./@codemirror/view/index.js",
"@codemirror/state": "./@codemirror/state/index.js", "@codemirror/state": "./@codemirror/state/index.js",
"@codemirror/language": "./@codemirror/language/index.js", "@codemirror/language": "./@codemirror/language/index.js",
@ -30,7 +29,6 @@
<link data-trunk rel="css" href="styles.css"/> <link data-trunk rel="css" href="styles.css"/>
<link data-trunk rel="copy-file" href="assets/fonts/JetBrainsMono-Regular.woff2"/> <link data-trunk rel="copy-file" href="assets/fonts/JetBrainsMono-Regular.woff2"/>
<link data-trunk rel="copy-file" href="node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff" data-target-path="fonts"/> <link data-trunk rel="copy-file" href="node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff" data-target-path="fonts"/>
<link data-trunk rel="copy-file" href="node_modules/codemirror/dist/index.js" data-target-path="codemirror"></link>
<link data-trunk rel="copy-file" href="node_modules/@codemirror/view/dist/index.js" data-target-path="@codemirror/view"></link> <link data-trunk rel="copy-file" href="node_modules/@codemirror/view/dist/index.js" data-target-path="@codemirror/view"></link>
<link data-trunk rel="copy-file" href="node_modules/@codemirror/state/dist/index.js" data-target-path="@codemirror/state"></link> <link data-trunk rel="copy-file" href="node_modules/@codemirror/state/dist/index.js" data-target-path="@codemirror/state"></link>
<link data-trunk rel="copy-file" href="node_modules/@codemirror/language/dist/index.js" data-target-path="@codemirror/language"></link> <link data-trunk rel="copy-file" href="node_modules/@codemirror/language/dist/index.js" data-target-path="@codemirror/language"></link>

23
package-lock.json generated
View File

@ -9,13 +9,18 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.18.1",
"@codemirror/commands": "^6.6.2",
"@codemirror/language": "^6.10.3",
"@codemirror/legacy-modes": "^6.4.1", "@codemirror/legacy-modes": "^6.4.1",
"@codemirror/lint": "^6.8.2",
"@codemirror/search": "^6.5.6",
"@codemirror/state": "^6.4.1", "@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.34.1",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3", "bootstrap-icons": "^1.11.3"
"codemirror": "^6.0.1"
} }
}, },
"node_modules/@codemirror/autocomplete": { "node_modules/@codemirror/autocomplete": {
@ -179,20 +184,6 @@
} }
] ]
}, },
"node_modules/codemirror": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
}
},
"node_modules/crelt": { "node_modules/crelt": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",

View File

@ -9,12 +9,17 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.18.1",
"@codemirror/commands": "^6.6.2",
"@codemirror/language": "^6.10.3",
"@codemirror/legacy-modes": "^6.4.1", "@codemirror/legacy-modes": "^6.4.1",
"@codemirror/lint": "^6.8.2",
"@codemirror/search": "^6.5.6",
"@codemirror/state": "^6.4.1", "@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.34.1",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3", "bootstrap-icons": "^1.11.3"
"codemirror": "^6.0.1"
} }
} }

View File

@ -1,11 +1,13 @@
use std::{ops::Deref, rc::Rc}; use std::rc::Rc;
use anyhow::Error; use anyhow::Error;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; use itertools::Itertools;
use web_sys::{window, Node}; use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen};
use wasm_bindgen_futures::js_sys::Object;
use web_sys::{window, HtmlElement};
use yew::{ use yew::{
function_component, html, use_effect_with, use_node_ref, use_state_eq, AttrValue, Callback, function_component, html, use_effect_with, use_node_ref, use_state, use_state_eq, AttrValue,
Html, Properties, UseStateHandle, Callback, Html, Properties, UseStateHandle,
}; };
use crate::{FrameId, GlobalLayoutState, GlobalMemoCell, PanelDirection, SplitPanel}; use crate::{FrameId, GlobalLayoutState, GlobalMemoCell, PanelDirection, SplitPanel};
@ -17,50 +19,150 @@ pub struct EditorViewProps {
pub global_layout: UseStateHandle<Rc<GlobalLayoutState>>, pub global_layout: UseStateHandle<Rc<GlobalLayoutState>>,
} }
#[derive(Properties, PartialEq, Clone)]
pub struct EditorViewDetailProps {
pub frame: FrameId,
pub global_memo: GlobalMemoCell,
pub global_layout: UseStateHandle<Rc<GlobalLayoutState>>,
pub editor_state: UseStateHandle<Rc<EditorViewState>>,
}
fn close_editor(frame: &FrameId, global_layout: &UseStateHandle<Rc<GlobalLayoutState>>) { fn close_editor(frame: &FrameId, global_layout: &UseStateHandle<Rc<GlobalLayoutState>>) {
let mut gl_new: GlobalLayoutState = global_layout.as_ref().clone(); let mut gl_new: GlobalLayoutState = global_layout.as_ref().clone();
gl_new.frame_views.remove(frame); gl_new.frame_views.remove(frame);
global_layout.set(gl_new.into()); global_layout.set(gl_new.into());
} }
#[derive(PartialEq, Clone)] #[derive(Clone, PartialEq)]
pub struct EditorState { pub struct EditorViewState {
error_msg: Option<AttrValue>, error_msg: Option<AttrValue>,
available_files: Vec<AttrValue>, available_files: Vec<AttrValue>,
open_file: AttrValue,
} }
fn fetch_initial_editor_state_or_fail() -> anyhow::Result<EditorState> { pub struct EditorKeepClosures {
change_closure: Rc<Closure<dyn FnMut(ViewUpdate)>>,
}
fn fetch_initial_editor_state_or_fail() -> anyhow::Result<EditorViewState> {
let win = window().ok_or_else(|| Error::msg("Can't get window"))?; let win = window().ok_or_else(|| Error::msg("Can't get window"))?;
win.local_storage() let local = win
.local_storage()
.map_err(|_| Error::msg("Error retrieving localStorage"))? .map_err(|_| Error::msg("Error retrieving localStorage"))?
.ok_or_else(|| Error::msg("Local storage not available"))?; .ok_or_else(|| Error::msg("Local storage not available"))?;
Ok(EditorState {
let n_keys = local
.length()
.map_err(|_| Error::msg("localStorage broken"))?;
let mut available_files: Vec<AttrValue> = vec![];
for i in 0..n_keys {
if let Some(key) = local
.key(i)
.map_err(|_| Error::msg("localStorage broken"))?
{
match key.strip_prefix("scriptfile_") {
None => {}
Some(filename) => {
available_files.push(filename.to_owned().into());
}
}
}
}
let init_str: AttrValue = "init.lua".into();
if !available_files.contains(&init_str) {
available_files.push(init_str);
local
.set(
"scriptfile_init.lua",
"-- Put any code to run on every client load here.\n",
)
.map_err(|_| Error::msg("localStorage broken"))?;
}
Ok(EditorViewState {
error_msg: None, error_msg: None,
available_files: vec![], available_files,
open_file: "init.lua".into(),
}) })
} }
fn fetch_initial_editor_state() -> EditorState { fn fetch_initial_editor_state() -> Rc<EditorViewState> {
match fetch_initial_editor_state_or_fail() { match fetch_initial_editor_state_or_fail() {
Ok(s) => s, Ok(s) => s.into(),
Err(e) => EditorState { Err(e) => EditorViewState {
error_msg: Some(format!("Can't load editor data: {}", e.to_string()).into()), error_msg: Some(format!("Can't load editor data: {}", e).into()),
available_files: vec![], available_files: vec![],
}, open_file: "init.lua".into(),
}
.into(),
}
}
pub fn try_run_script(script: &str, global_memo: &GlobalMemoCell) -> anyhow::Result<()> {
let script = load_file_contents(script)?;
global_memo
.lua_engine
.borrow_mut()
.execute(&script)
.map_err(Error::msg)?;
Ok(())
}
fn run_script<F>(script: &str, global_memo: &GlobalMemoCell, set_err: Rc<F>)
where
F: Fn(Option<&str>),
{
match try_run_script(script, global_memo) {
Ok(()) => {
set_err(None);
}
Err(e) => {
set_err(Some(&format!("Script error: {}", e)));
}
} }
} }
#[function_component(EditorNav)] #[function_component(EditorNav)]
fn editor_nav(props: &EditorViewProps) -> Html { fn editor_nav(props: &EditorViewDetailProps) -> Html {
let global_memo = props.global_memo.clone();
let global_layout = props.global_layout.clone(); let global_layout = props.global_layout.clone();
let frame = props.frame.clone(); let frame = props.frame.clone();
let current_script = props.editor_state.open_file.clone();
let set_err_state = props.editor_state.clone();
let set_err = Rc::new(move |msg: Option<&str>| {
let mut new_state = (*set_err_state.as_ref()).clone();
new_state.error_msg = msg.map(|v| AttrValue::from(String::from(v)));
set_err_state.set(new_state.into());
});
html! { html! {
<div class="editornav"> <div class="editornav">
<nav class="navbar navbar-expand-lg bg-body-tertiary"> <nav class="navbar navbar-expand-lg bg-body-tertiary">
<button class="btn" aria-label="Run" title="Run"
onclick={move |_ev| run_script(&current_script, &global_memo, set_err.clone())}>
<i class="bi bi-file-earmark-play"></i></button>
<button class="btn" aria-label="New file" title="New file"><i class="bi bi-file-earmark-plus"></i></button> <button class="btn" aria-label="New file" title="New file"><i class="bi bi-file-earmark-plus"></i></button>
<div class="flex-fill"/> <div class="flex-fill"/>
<button class="btn" onclick={move |_ev| close_editor(&frame, &global_layout)} aria-label="Close Editor" title="Close editor"><i class="bi bi-arrow-return-left"></i></button> <button class="btn" onclick={move |_ev| close_editor(&frame, &global_layout)} aria-label="Close Editor" title="Close editor"><i class="bi bi-arrow-return-left"></i></button>
</nav> </nav>
<ul class="p-2 list-group">
{props
.editor_state
.available_files
.iter()
.map(|f| {
let mut classes = vec!["list-group-item"];
let mut aria_current = None;
if *f == props.editor_state.open_file {
aria_current = Some("true");
classes.push("active");
}
html! { <li aria-current={aria_current} class={classes.iter().join(" ")}>{f}</li>}
})
.collect::<Vec<Html>>()
}
</ul>
</div> </div>
} }
} }
@ -68,21 +170,84 @@ fn editor_nav(props: &EditorViewProps) -> Html {
#[wasm_bindgen(getter_with_clone)] #[wasm_bindgen(getter_with_clone)]
struct EditorViewConfig { struct EditorViewConfig {
pub doc: String, pub doc: String,
pub extensions: Vec<CMExtension>,
pub parent: Node,
} }
#[wasm_bindgen(module = codemirror)] #[wasm_bindgen(getter_with_clone)]
struct EditorStateConfig {
pub doc: String,
pub extensions: Vec<CMExtension>,
}
#[wasm_bindgen(getter_with_clone)]
struct HighlightingConfig {
pub fallback: bool,
}
#[wasm_bindgen(module = "@codemirror/state")]
extern "C" {
type EditorState;
#[wasm_bindgen(extends = Object)]
type CMDocument;
#[wasm_bindgen(static_method_of = EditorState)]
fn create(settings: EditorStateConfig) -> EditorState;
#[wasm_bindgen(method, getter)]
fn doc(st: &EditorState) -> CMDocument;
#[wasm_bindgen(js_namespace = ["EditorState", "allowMultipleSelections"], js_name = of)]
fn editorstate_allow_multiple_selections_of(v: bool) -> CMExtension;
}
#[wasm_bindgen(module = "@codemirror/view")]
extern "C" { extern "C" {
type EditorView; type EditorView;
type ViewUpdate;
#[derive(Clone)]
type KeyBinding;
#[derive(Clone)] #[derive(Clone)]
type CMExtension; type CMExtension;
#[wasm_bindgen(constructor)] #[wasm_bindgen(constructor)]
fn new(settings: EditorViewConfig) -> EditorView; fn new(settings: EditorViewConfig) -> EditorView;
#[wasm_bindgen(js_name = basicSetup, thread_local)] #[wasm_bindgen(js_namespace = ["EditorView", "updateListener"], js_name = of)]
static BASIC_SETUP: CMExtension; fn editorview_updatelistener_of(cb: &Closure<dyn FnMut(ViewUpdate)>) -> CMExtension;
#[wasm_bindgen(method, getter)]
fn dom(upd: &EditorView) -> HtmlElement;
#[wasm_bindgen(method, getter)]
fn state(v: &EditorView) -> EditorState;
#[wasm_bindgen(method, js_name = setState)]
fn set_state(v: &EditorView, st: &EditorState);
#[wasm_bindgen(method, getter, js_name = docChanged)]
fn doc_changed(upd: &ViewUpdate) -> bool;
#[wasm_bindgen(method, getter)]
fn view(upd: &ViewUpdate) -> EditorView;
#[wasm_bindgen(js_name = highlightSpecialChars)]
fn highlight_special_chars() -> CMExtension;
#[wasm_bindgen(js_name = drawSelection)]
fn draw_selection() -> CMExtension;
#[wasm_bindgen(js_name = highlightActiveLine)]
fn highlight_active_line() -> CMExtension;
#[wasm_bindgen(js_name = dropCursor)]
fn drop_cursor() -> CMExtension;
#[wasm_bindgen(js_name = rectangularSelection)]
fn rectangular_selection() -> CMExtension;
#[wasm_bindgen(js_name = crosshairCursor)]
fn crosshair_cursor() -> CMExtension;
#[wasm_bindgen(js_name = lineNumbers)]
fn line_numbers() -> CMExtension;
#[wasm_bindgen(js_name = highlightActiveLineGutter)]
fn highlight_active_line_gutter() -> CMExtension;
#[wasm_bindgen(js_namespace = keymap, js_name = of)]
fn keymap_of(keys: Vec<KeyBinding>) -> CMExtension;
} }
#[wasm_bindgen(module = "@codemirror/legacy-modes/mode/lua")] #[wasm_bindgen(module = "@codemirror/legacy-modes/mode/lua")]
@ -95,25 +260,169 @@ extern "C" {
extern "C" { extern "C" {
#[derive(Clone)] #[derive(Clone)]
type StreamLanguage; type StreamLanguage;
#[derive(Clone)]
type CMHighlightStyle;
#[wasm_bindgen(js_namespace = StreamLanguage, js_name = define)] #[wasm_bindgen(js_namespace = StreamLanguage, js_name = define)]
fn streamlanguage_define(input: StreamLanguage) -> CMExtension; fn streamlanguage_define(input: StreamLanguage) -> CMExtension;
#[wasm_bindgen(js_name = defaultHighlightStyle, thread_local)]
static DEFAULT_HIGHLIGHT_STYLE: CMHighlightStyle;
#[wasm_bindgen(js_name = syntaxHighlighting)]
fn syntax_highlighting(style: CMHighlightStyle, config: HighlightingConfig) -> CMExtension;
#[wasm_bindgen(js_name = indentOnInput)]
fn indent_on_input() -> CMExtension;
#[wasm_bindgen(js_name = bracketMatching)]
fn bracket_matching() -> CMExtension;
#[wasm_bindgen(js_name = foldGutter)]
fn fold_gutter() -> CMExtension;
#[wasm_bindgen(js_name = foldKeymap, thread_local)]
static FOLD_KEYMAP: Vec<KeyBinding>;
}
#[wasm_bindgen(module = "@codemirror/commands")]
extern "C" {
#[wasm_bindgen(js_name = defaultKeymap, thread_local)]
static DEFAULT_KEYMAP: Vec<KeyBinding>;
#[wasm_bindgen(js_name = historyKeymap, thread_local)]
static HISTORY_KEYMAP: Vec<KeyBinding>;
#[wasm_bindgen]
fn history() -> CMExtension;
}
#[wasm_bindgen(module = "@codemirror/autocomplete")]
extern "C" {
#[wasm_bindgen]
fn autocompletion() -> CMExtension;
#[wasm_bindgen(js_name = closeBrackets)]
fn close_brackets() -> CMExtension;
#[wasm_bindgen(js_name = completionKeymap, thread_local)]
static COMPLETION_KEYMAP: Vec<KeyBinding>;
#[wasm_bindgen(js_name = closeBracketsKeymap, thread_local)]
static CLOSE_BRACKETS_KEYMAP: Vec<KeyBinding>;
}
#[wasm_bindgen(module = "@codemirror/search")]
extern "C" {
#[wasm_bindgen(js_name = searchKeymap, thread_local)]
static SEARCH_KEYMAP: Vec<KeyBinding>;
#[wasm_bindgen(js_name = highlightSelectionMatches)]
fn highlight_selection_matches() -> CMExtension;
}
#[wasm_bindgen(module = "@codemirror/lint")]
extern "C" {
#[wasm_bindgen(js_name = lintKeymap, thread_local)]
static LINT_KEYMAP: Vec<KeyBinding>;
}
fn try_save_document(file: &str, contents: &str) -> anyhow::Result<()> {
let win = window().ok_or_else(|| Error::msg("Can't get window"))?;
let local = win
.local_storage()
.map_err(|_| Error::msg("Error retrieving localStorage"))?
.ok_or_else(|| Error::msg("Local storage not available"))?;
local
.set(&format!("scriptfile_{}", file), contents)
.map_err(|_| Error::msg("Error saving to localStorage"))
}
fn load_file_contents(file: &str) -> anyhow::Result<String> {
let win = window().ok_or_else(|| Error::msg("Can't get window"))?;
let local = win
.local_storage()
.map_err(|_| Error::msg("Error retrieving localStorage"))?
.ok_or_else(|| Error::msg("Local storage not available"))?;
Ok(local
.get(&format!("scriptfile_{}", file))
.map_err(|_| Error::msg("Error retrieving localStorage"))?
.unwrap_or_else(|| "".to_owned()))
} }
#[function_component(EditorArea)] #[function_component(EditorArea)]
fn editor_area(props: &EditorViewProps) -> Html { fn editor_area(props: &EditorViewDetailProps) -> Html {
let node_ref = use_node_ref(); let node_ref = use_node_ref();
use_effect_with(node_ref.clone(), |node_ref| match node_ref.get() { let editor_state = props.editor_state.clone();
let closures = use_state(|| EditorKeepClosures {
change_closure: Closure::new(move |view_update: ViewUpdate| {
if view_update.doc_changed() {
if let Some(contents) = view_update.view().state().doc().to_string().as_string() {
match try_save_document(&editor_state.open_file, &contents) {
Ok(()) => {}
Err(e) => {
let mut new_state = (*editor_state.as_ref()).clone();
new_state.error_msg = Some(format!("Can't save: {}", e).into());
editor_state.set(new_state.into());
}
}
}
}
})
.into(),
});
let view = use_state(move || {
EditorView::new(EditorViewConfig {
doc: "Loading...".to_owned(),
})
});
{
let view = view.clone();
use_effect_with(props.editor_state.open_file.clone(), move |open_file| {
view.set_state(&EditorState::create(EditorStateConfig {
doc: load_file_contents(open_file)
.unwrap_or_else(|e| format!("Error loading content: {}", e.to_string())),
extensions: vec![
// BASIC_SETUP.with(CMExtension::clone),
line_numbers(),
highlight_active_line_gutter(),
highlight_special_chars(),
history(),
fold_gutter(),
draw_selection(),
drop_cursor(),
editorstate_allow_multiple_selections_of(true),
indent_on_input(),
syntax_highlighting(
DEFAULT_HIGHLIGHT_STYLE.with(CMHighlightStyle::clone),
HighlightingConfig { fallback: true },
),
bracket_matching(),
close_brackets(),
autocompletion(),
rectangular_selection(),
crosshair_cursor(),
highlight_active_line(),
highlight_selection_matches(),
keymap_of(
vec![
&CLOSE_BRACKETS_KEYMAP,
&DEFAULT_KEYMAP,
&SEARCH_KEYMAP,
&HISTORY_KEYMAP,
&FOLD_KEYMAP,
&COMPLETION_KEYMAP,
&LINT_KEYMAP,
]
.into_iter()
.flat_map(|k| k.with(|v| v.clone()).into_iter())
.collect(),
),
StreamLanguage::streamlanguage_define(LUA.with(StreamLanguage::clone)),
editorview_updatelistener_of(&closures.change_closure),
],
}));
});
}
use_effect_with(node_ref.clone(), move |node_ref| match node_ref.get() {
None => {} None => {}
Some(node) => { Some(node) => {
EditorView::new(EditorViewConfig { let _ = node.append_child(&view.dom());
doc: "Hello World".to_owned(),
extensions: vec![
BASIC_SETUP.with(CMExtension::clone),
StreamLanguage::streamlanguage_define(LUA.with(StreamLanguage::clone)),
],
parent: node,
});
} }
}); });
html! { html! {
@ -141,23 +450,35 @@ fn error_bar(props: &ErrorBarProps) -> Html {
#[function_component(CodeEditorView)] #[function_component(CodeEditorView)]
pub fn editor_view(props: &EditorViewProps) -> Html { pub fn editor_view(props: &EditorViewProps) -> Html {
let editor_state: UseStateHandle<EditorState> = use_state_eq(fetch_initial_editor_state); let editor_state: UseStateHandle<Rc<EditorViewState>> =
use_state_eq(fetch_initial_editor_state);
let detail_props: EditorViewDetailProps = EditorViewDetailProps {
frame: props.frame.clone(),
global_memo: props.global_memo.clone(),
global_layout: props.global_layout.clone(),
editor_state: editor_state.clone(),
};
html! { html! {
<> <>
{match editor_state.error_msg.as_ref() { {match editor_state.error_msg.as_ref() {
None => { html! { <></> }} None => { html! { <></> }}
Some(msg) => html! { Some(msg) => {
let editor_state = editor_state.clone();
html! {
<ErrorBar msg={msg.clone()} <ErrorBar msg={msg.clone()}
dismiss={move |()| editor_state.set(EditorState { dismiss={move |()| editor_state.set(EditorViewState {
error_msg: None, error_msg: None,
..(editor_state.deref().clone()) ..((*editor_state.as_ref()).clone())
}) }.into())
}/> } }/>
}
}
}} }}
<SplitPanel direction={PanelDirection::Horizontal} <SplitPanel direction={PanelDirection::Horizontal}
first={html!{<EditorNav ..props.clone()/>}} first={html!{<EditorNav ..detail_props.clone() />}}
second={html!{<EditorArea ..props.clone()/>}} second={html!{<EditorArea ..detail_props.clone()/>}}
/> />
</> </>
} }

View File

@ -5,6 +5,7 @@ use std::rc::Rc;
use logging::LoggingEngine; use logging::LoggingEngine;
use parsing::ParsedCommand; use parsing::ParsedCommand;
use term_split::TermSplit; use term_split::TermSplit;
use web_sys::console;
use yew::prelude::*; use yew::prelude::*;
pub mod command_handler; pub mod command_handler;
@ -21,6 +22,7 @@ pub mod telnet;
pub mod term_split; pub mod term_split;
pub mod timer_host; pub mod timer_host;
pub mod websocket; pub mod websocket;
use crate::editor_view::try_run_script;
use crate::frame_view::*; use crate::frame_view::*;
use crate::lua_engine::{install_lua_globals, LuaState}; use crate::lua_engine::{install_lua_globals, LuaState};
use crate::split_panel::*; use crate::split_panel::*;
@ -82,6 +84,12 @@ fn app() -> Html {
use_memo((), |_| { use_memo((), |_| {
install_lua_globals(&global_memo, global_layout.setter()) install_lua_globals(&global_memo, global_layout.setter())
.expect("Couldn't install Lua globals"); .expect("Couldn't install Lua globals");
match try_run_script("init.lua", &global_memo) {
Ok(_) => {}
Err(e) => {
console::log_1(&format!("Error running init.lua: {}", e).into());
}
}
}); });
html! { html! {