2024-10-11 10:02:38 +11:00
|
|
|
use std::{ops::Deref, rc::Rc};
|
2024-10-05 19:28:39 +10:00
|
|
|
|
2024-10-11 10:02:38 +11:00
|
|
|
use crate::{FrameId, GlobalLayoutState, GlobalMemoCell, PanelDirection, SplitPanel};
|
2024-10-05 19:28:39 +10:00
|
|
|
use anyhow::Error;
|
2024-10-11 10:23:02 +11:00
|
|
|
use create_script_dialog::CreateScriptDialog;
|
2024-10-11 10:02:38 +11:00
|
|
|
use editor_area::EditorArea;
|
2024-10-11 10:13:49 +11:00
|
|
|
use editor_nav::EditorNav;
|
|
|
|
use storage::fetch_initial_editor_state;
|
2024-10-11 10:02:38 +11:00
|
|
|
use wasm_bindgen::{closure::Closure, JsCast};
|
2024-10-11 10:23:02 +11:00
|
|
|
use web_sys::KeyboardEvent;
|
2024-10-05 19:28:39 +10:00
|
|
|
use yew::{
|
2024-10-11 10:02:38 +11:00
|
|
|
function_component, html, use_effect_with, use_node_ref, use_state, use_state_eq, AttrValue,
|
|
|
|
Callback, Html, Properties, UseStateHandle,
|
2024-10-05 19:28:39 +10:00
|
|
|
};
|
|
|
|
|
2024-10-11 10:13:49 +11:00
|
|
|
use self::storage::load_file_contents;
|
|
|
|
|
2024-10-11 10:23:02 +11:00
|
|
|
mod create_script_dialog;
|
2024-10-11 10:02:38 +11:00
|
|
|
mod editor_area;
|
2024-10-11 10:13:49 +11:00
|
|
|
mod editor_nav;
|
|
|
|
mod storage;
|
2024-10-05 19:28:39 +10:00
|
|
|
|
|
|
|
#[derive(Properties, PartialEq, Clone)]
|
|
|
|
pub struct EditorViewProps {
|
|
|
|
pub frame: FrameId,
|
|
|
|
pub global_memo: GlobalMemoCell,
|
|
|
|
pub global_layout: UseStateHandle<Rc<GlobalLayoutState>>,
|
|
|
|
}
|
|
|
|
|
2024-10-05 22:54:48 +10:00
|
|
|
#[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>>,
|
|
|
|
}
|
|
|
|
|
2024-10-05 19:28:39 +10:00
|
|
|
fn close_editor(frame: &FrameId, global_layout: &UseStateHandle<Rc<GlobalLayoutState>>) {
|
|
|
|
let mut gl_new: GlobalLayoutState = global_layout.as_ref().clone();
|
|
|
|
gl_new.frame_views.remove(frame);
|
|
|
|
global_layout.set(gl_new.into());
|
|
|
|
}
|
|
|
|
|
2024-10-05 22:54:48 +10:00
|
|
|
#[derive(Clone, PartialEq)]
|
|
|
|
pub struct EditorViewState {
|
2024-10-05 19:28:39 +10:00
|
|
|
error_msg: Option<AttrValue>,
|
|
|
|
available_files: Vec<AttrValue>,
|
2024-10-05 22:54:48 +10:00
|
|
|
open_file: AttrValue,
|
2024-10-11 10:02:38 +11:00
|
|
|
show_create_dialog: bool,
|
|
|
|
show_delete_dialog: Option<String>,
|
2024-10-05 19:28:39 +10:00
|
|
|
}
|
|
|
|
|
2024-10-06 22:42:19 +11:00
|
|
|
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)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-05 19:28:39 +10:00
|
|
|
#[derive(Properties, PartialEq, Clone)]
|
|
|
|
pub struct ErrorBarProps {
|
|
|
|
pub msg: AttrValue,
|
|
|
|
pub dismiss: Callback<()>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[function_component(ErrorBar)]
|
|
|
|
fn error_bar(props: &ErrorBarProps) -> Html {
|
|
|
|
let props = props.clone();
|
|
|
|
html! {
|
|
|
|
<div class="errorbar alert alert-danger alert-dismissible" role="alert">
|
|
|
|
<span class="errorbar-msg">{props.msg.clone()}</span>
|
|
|
|
<button class="btn-close" aria-label="Close" onclick={move |_ev| props.dismiss.emit(())}></button>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-11 10:02:38 +11:00
|
|
|
fn close_modals(state: &UseStateHandle<Rc<EditorViewState>>) {
|
|
|
|
let mut new_state = (*state.deref().deref()).clone();
|
|
|
|
new_state.show_create_dialog = false;
|
|
|
|
new_state.show_delete_dialog = None;
|
|
|
|
state.set(new_state.into());
|
|
|
|
}
|
|
|
|
|
2024-10-05 19:28:39 +10:00
|
|
|
#[function_component(CodeEditorView)]
|
|
|
|
pub fn editor_view(props: &EditorViewProps) -> Html {
|
2024-10-05 22:54:48 +10:00
|
|
|
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(),
|
|
|
|
};
|
2024-10-05 19:28:39 +10:00
|
|
|
|
2024-10-09 22:33:53 +11:00
|
|
|
let current_script = editor_state.open_file.clone();
|
|
|
|
|
|
|
|
let set_err_state = 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());
|
|
|
|
});
|
|
|
|
let editor_ref = use_node_ref();
|
|
|
|
let global_layout = props.global_layout.clone();
|
|
|
|
let frame = props.frame.clone();
|
2024-10-11 10:02:38 +11:00
|
|
|
type KbClosure = Closure<dyn FnMut(KeyboardEvent)>;
|
|
|
|
let editor_closure: UseStateHandle<Option<Rc<KbClosure>>> = use_state(|| None);
|
2024-10-09 22:33:53 +11:00
|
|
|
let editor_ref_eff = editor_ref.clone();
|
|
|
|
let global_memo = props.global_memo.clone();
|
2024-10-11 10:02:38 +11:00
|
|
|
use_effect_with(current_script.clone(), move |_| {
|
2024-10-09 22:33:53 +11:00
|
|
|
if let Some(editor_node) = editor_ref_eff.get() {
|
|
|
|
let closure = Closure::new(move |ev: KeyboardEvent| {
|
|
|
|
if ev.code() == "Enter" && ev.get_modifier_state("Control") {
|
|
|
|
run_script(¤t_script, &global_memo, set_err.clone());
|
|
|
|
ev.prevent_default();
|
|
|
|
} else if ev.code() == "Escape" {
|
|
|
|
close_editor(&frame, &global_layout);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
let _ = editor_node
|
|
|
|
.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref());
|
2024-10-11 10:02:38 +11:00
|
|
|
let closure: Rc<Closure<dyn FnMut(KeyboardEvent)>> = closure.into();
|
|
|
|
editor_closure.set(Some(closure.clone()));
|
|
|
|
|
|
|
|
let cleanup: Box<dyn FnOnce()> = Box::new(move || {
|
|
|
|
let _ = editor_node.remove_event_listener_with_callback(
|
|
|
|
"keydown",
|
|
|
|
(*closure.as_ref()).as_ref().unchecked_ref(),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
return cleanup;
|
2024-10-09 22:33:53 +11:00
|
|
|
}
|
2024-10-11 10:02:38 +11:00
|
|
|
Box::new(|| {})
|
2024-10-09 22:33:53 +11:00
|
|
|
});
|
|
|
|
|
2024-10-05 19:28:39 +10:00
|
|
|
html! {
|
2024-10-09 22:33:53 +11:00
|
|
|
<div class="w-100 h-100" ref={editor_ref.clone()}>
|
2024-10-11 10:02:38 +11:00
|
|
|
{if editor_state.show_create_dialog {
|
2024-10-11 10:23:02 +11:00
|
|
|
html! { <CreateScriptDialog ..detail_props.clone() /> }
|
2024-10-11 10:02:38 +11:00
|
|
|
} else {
|
|
|
|
html! { <></> }
|
|
|
|
}}
|
2024-10-05 19:28:39 +10:00
|
|
|
{match editor_state.error_msg.as_ref() {
|
|
|
|
None => { html! { <></> }}
|
2024-10-05 22:54:48 +10:00
|
|
|
Some(msg) => {
|
|
|
|
let editor_state = editor_state.clone();
|
|
|
|
html! {
|
2024-10-05 19:28:39 +10:00
|
|
|
<ErrorBar msg={msg.clone()}
|
2024-10-05 22:54:48 +10:00
|
|
|
dismiss={move |()| editor_state.set(EditorViewState {
|
2024-10-05 19:28:39 +10:00
|
|
|
error_msg: None,
|
2024-10-05 22:54:48 +10:00
|
|
|
..((*editor_state.as_ref()).clone())
|
|
|
|
}.into())
|
|
|
|
}/>
|
|
|
|
}
|
|
|
|
}
|
2024-10-05 19:28:39 +10:00
|
|
|
}}
|
|
|
|
<SplitPanel direction={PanelDirection::Horizontal}
|
2024-10-05 22:54:48 +10:00
|
|
|
first={html!{<EditorNav ..detail_props.clone() />}}
|
|
|
|
second={html!{<EditorArea ..detail_props.clone()/>}}
|
2024-10-05 19:28:39 +10:00
|
|
|
/>
|
2024-10-09 22:33:53 +11:00
|
|
|
</div>
|
2024-10-05 19:28:39 +10:00
|
|
|
}
|
|
|
|
}
|