worldwideportal/src/editor_view.rs

165 lines
5.0 KiB
Rust
Raw Normal View History

use std::{ops::Deref, rc::Rc};
use anyhow::Error;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
use web_sys::{window, Node};
use yew::{
function_component, html, use_effect_with, use_node_ref, use_state_eq, AttrValue, Callback,
Html, Properties, UseStateHandle,
};
use crate::{FrameId, GlobalLayoutState, GlobalMemoCell, PanelDirection, SplitPanel};
#[derive(Properties, PartialEq, Clone)]
pub struct EditorViewProps {
pub frame: FrameId,
pub global_memo: GlobalMemoCell,
pub 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();
gl_new.frame_views.remove(frame);
global_layout.set(gl_new.into());
}
#[derive(PartialEq, Clone)]
pub struct EditorState {
error_msg: Option<AttrValue>,
available_files: Vec<AttrValue>,
}
fn fetch_initial_editor_state_or_fail() -> anyhow::Result<EditorState> {
let win = window().ok_or_else(|| Error::msg("Can't get window"))?;
win.local_storage()
.map_err(|_| Error::msg("Error retrieving localStorage"))?
.ok_or_else(|| Error::msg("Local storage not available"))?;
Ok(EditorState {
error_msg: None,
available_files: vec![],
})
}
fn fetch_initial_editor_state() -> EditorState {
match fetch_initial_editor_state_or_fail() {
Ok(s) => s,
Err(e) => EditorState {
error_msg: Some(format!("Can't load editor data: {}", e.to_string()).into()),
available_files: vec![],
},
}
}
#[function_component(EditorNav)]
fn editor_nav(props: &EditorViewProps) -> Html {
let global_layout = props.global_layout.clone();
let frame = props.frame.clone();
html! {
<div class="editornav">
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<button class="btn" aria-label="New file" title="New file"><i class="bi bi-file-earmark-plus"></i></button>
<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>
</nav>
</div>
}
}
#[wasm_bindgen(getter_with_clone)]
struct EditorViewConfig {
pub doc: String,
pub extensions: Vec<CMExtension>,
pub parent: Node,
}
#[wasm_bindgen(module = codemirror)]
extern "C" {
type EditorView;
#[derive(Clone)]
type CMExtension;
#[wasm_bindgen(constructor)]
fn new(settings: EditorViewConfig) -> EditorView;
#[wasm_bindgen(js_name = basicSetup, thread_local)]
static BASIC_SETUP: CMExtension;
}
#[wasm_bindgen(module = "@codemirror/legacy-modes/mode/lua")]
extern "C" {
#[wasm_bindgen(js_name = lua, thread_local)]
static LUA: StreamLanguage;
}
#[wasm_bindgen(module = "@codemirror/language")]
extern "C" {
#[derive(Clone)]
type StreamLanguage;
#[wasm_bindgen(js_namespace = StreamLanguage, js_name = define)]
fn streamlanguage_define(input: StreamLanguage) -> CMExtension;
}
#[function_component(EditorArea)]
fn editor_area(props: &EditorViewProps) -> Html {
let node_ref = use_node_ref();
use_effect_with(node_ref.clone(), |node_ref| match node_ref.get() {
None => {}
Some(node) => {
EditorView::new(EditorViewConfig {
doc: "Hello World".to_owned(),
extensions: vec![
BASIC_SETUP.with(CMExtension::clone),
StreamLanguage::streamlanguage_define(LUA.with(StreamLanguage::clone)),
],
parent: node,
});
}
});
html! {
<div class="editorarea" ref={node_ref.clone()}>
</div>
}
}
#[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>
}
}
#[function_component(CodeEditorView)]
pub fn editor_view(props: &EditorViewProps) -> Html {
let editor_state: UseStateHandle<EditorState> = use_state_eq(fetch_initial_editor_state);
html! {
<>
{match editor_state.error_msg.as_ref() {
None => { html! { <></> }}
Some(msg) => html! {
<ErrorBar msg={msg.clone()}
dismiss={move |()| editor_state.set(EditorState {
error_msg: None,
..(editor_state.deref().clone())
})
}/> }
}}
<SplitPanel direction={PanelDirection::Horizontal}
first={html!{<EditorNav ..props.clone()/>}}
second={html!{<EditorArea ..props.clone()/>}}
/>
</>
}
}