worldwideportal/src/editor_view.rs

198 lines
6.4 KiB
Rust
Raw Normal View History

use std::{ops::Deref, rc::Rc, str::FromStr};
use crate::{
command_handler::execute_queue, FrameId, GlobalLayoutState, GlobalMemoCell, PanelDirection,
SplitPanel,
};
use anyhow::Error;
use create_script_dialog::CreateScriptDialog;
use delete_script_dialog::DeleteScriptDialog;
2024-10-11 10:02:38 +11:00
use editor_area::EditorArea;
use editor_nav::EditorNav;
use storage::fetch_initial_editor_state;
use wasm_bindgen_futures::js_sys::JsString;
use web_sys::{console, KeyboardEvent};
use yew::{
function_component, html, use_node_ref, use_state_eq, AttrValue, Callback, Html, Properties,
UseStateHandle,
};
pub use self::storage::load_extant_file_contents;
use self::storage::load_file_contents;
mod create_script_dialog;
mod delete_script_dialog;
2024-10-11 10:02:38 +11:00
mod editor_area;
mod editor_nav;
mod storage;
#[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>>,
}
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());
}
#[derive(Clone, PartialEq, Debug)]
2024-10-05 22:54:48 +10:00
pub struct EditorViewState {
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<AttrValue>,
}
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(())
}
// Only intended for use from the editor, since it borrows from global_memo.
fn run_script<F>(script: &str, global_memo: &GlobalMemoCell, set_err: Rc<F>, frame: &FrameId)
where
F: Fn(Option<&str>),
{
global_memo.lua_engine.borrow_mut().set_current_frame(frame);
match try_run_script(script, global_memo) {
Ok(()) => {
set_err(None);
}
Err(e) => {
set_err(Some(&format!("Script error: {}", e)));
}
}
execute_queue(global_memo);
}
fn select_file(script: &AttrValue, state: &UseStateHandle<Rc<EditorViewState>>) {
let mut new_state: EditorViewState = state.as_ref().clone();
new_state.open_file = script.clone();
state.set(new_state.into());
}
#[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());
}
fn keyboard_handler<F>(
editor_state: UseStateHandle<Rc<EditorViewState>>,
global_memo: GlobalMemoCell,
global_layout: UseStateHandle<Rc<GlobalLayoutState>>,
set_err: Rc<F>,
frame: FrameId,
) -> impl Fn(KeyboardEvent)
where
F: Fn(Option<&str>) + 'static,
{
move |ev: KeyboardEvent| {
if ev.code() == "Enter" && ev.get_modifier_state("Control") {
run_script(
&editor_state.open_file,
&global_memo,
set_err.clone(),
&frame,
);
ev.prevent_default();
} else if ev.code() == "Escape" || (ev.code() == "KeyX" && ev.get_modifier_state("Control"))
{
close_editor(&frame, &global_layout);
}
}
}
#[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-09 22:33:53 +11:00
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();
let global_memo = props.global_memo.clone();
let kb_editor_state = editor_state.clone();
2024-10-09 22:33:53 +11:00
console::log_1(&JsString::from_str(&format!("{:?}", editor_state.as_ref())).unwrap());
html! {
<div class="w-100 h-100" onkeydown={keyboard_handler(kb_editor_state, global_memo, global_layout, set_err, frame)} ref={editor_ref.clone()}>
2024-10-11 10:02:38 +11:00
{if editor_state.show_create_dialog {
html! { <CreateScriptDialog ..detail_props.clone() /> }
} else if let Some(ref delete_file) = editor_state.show_delete_dialog {
html! { <DeleteScriptDialog editor_state={editor_state.clone()}
filename={delete_file.clone()}/> }
2024-10-11 10:02:38 +11:00
} else {
html! { <></> }
}}
{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! {
<ErrorBar msg={msg.clone()}
2024-10-05 22:54:48 +10:00
dismiss={move |()| editor_state.set(EditorViewState {
error_msg: None,
2024-10-05 22:54:48 +10:00
..((*editor_state.as_ref()).clone())
}.into())
}/>
}
}
}}
<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-09 22:33:53 +11:00
</div>
}
}