Allow creating new scripts.
This commit is contained in:
parent
83d77363e4
commit
180ffda98b
@ -1,16 +1,17 @@
|
|||||||
use std::rc::Rc;
|
use std::{ops::Deref, rc::Rc};
|
||||||
|
|
||||||
use anyhow::Error;
|
|
||||||
use itertools::Itertools;
|
|
||||||
use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsCast};
|
|
||||||
use wasm_bindgen_futures::js_sys::{Function, Object, Reflect};
|
|
||||||
use web_sys::{console, window, HtmlElement, KeyboardEvent};
|
|
||||||
use yew::{
|
|
||||||
function_component, html, use_effect_with, use_memo, use_node_ref, use_state, use_state_eq,
|
|
||||||
AttrValue, Callback, Html, Properties, UseStateHandle,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{FrameId, GlobalLayoutState, GlobalMemoCell, PanelDirection, SplitPanel};
|
use crate::{FrameId, GlobalLayoutState, GlobalMemoCell, PanelDirection, SplitPanel};
|
||||||
|
use anyhow::Error;
|
||||||
|
use editor_area::EditorArea;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use wasm_bindgen::{closure::Closure, JsCast};
|
||||||
|
use web_sys::{window, HtmlInputElement, KeyboardEvent};
|
||||||
|
use yew::{
|
||||||
|
function_component, html, use_effect_with, use_node_ref, use_state, use_state_eq, AttrValue,
|
||||||
|
Callback, Html, Properties, UseStateHandle,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod editor_area;
|
||||||
|
|
||||||
#[derive(Properties, PartialEq, Clone)]
|
#[derive(Properties, PartialEq, Clone)]
|
||||||
pub struct EditorViewProps {
|
pub struct EditorViewProps {
|
||||||
@ -38,10 +39,8 @@ pub struct EditorViewState {
|
|||||||
error_msg: Option<AttrValue>,
|
error_msg: Option<AttrValue>,
|
||||||
available_files: Vec<AttrValue>,
|
available_files: Vec<AttrValue>,
|
||||||
open_file: AttrValue,
|
open_file: AttrValue,
|
||||||
}
|
show_create_dialog: bool,
|
||||||
|
show_delete_dialog: Option<String>,
|
||||||
pub struct EditorKeepClosures {
|
|
||||||
change_closure: Rc<Closure<dyn FnMut(ViewUpdate)>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_initial_editor_state_or_fail() -> anyhow::Result<EditorViewState> {
|
fn fetch_initial_editor_state_or_fail() -> anyhow::Result<EditorViewState> {
|
||||||
@ -84,6 +83,8 @@ fn fetch_initial_editor_state_or_fail() -> anyhow::Result<EditorViewState> {
|
|||||||
error_msg: None,
|
error_msg: None,
|
||||||
available_files,
|
available_files,
|
||||||
open_file: "init.lua".into(),
|
open_file: "init.lua".into(),
|
||||||
|
show_create_dialog: false,
|
||||||
|
show_delete_dialog: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,11 +95,25 @@ fn fetch_initial_editor_state() -> Rc<EditorViewState> {
|
|||||||
error_msg: Some(format!("Can't load editor data: {}", e).into()),
|
error_msg: Some(format!("Can't load editor data: {}", e).into()),
|
||||||
available_files: vec![],
|
available_files: vec![],
|
||||||
open_file: "init.lua".into(),
|
open_file: "init.lua".into(),
|
||||||
|
show_create_dialog: false,
|
||||||
|
show_delete_dialog: None,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn try_run_script(script: &str, global_memo: &GlobalMemoCell) -> anyhow::Result<()> {
|
pub fn try_run_script(script: &str, global_memo: &GlobalMemoCell) -> anyhow::Result<()> {
|
||||||
let script = load_file_contents(script)?;
|
let script = load_file_contents(script)?;
|
||||||
global_memo
|
global_memo
|
||||||
@ -123,6 +138,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_create_dialog(state: &UseStateHandle<Rc<EditorViewState>>) {
|
||||||
|
let mut new_state: EditorViewState = (*state.deref().deref()).clone();
|
||||||
|
new_state.show_create_dialog = true;
|
||||||
|
state.set(new_state.into());
|
||||||
|
}
|
||||||
|
|
||||||
#[function_component(EditorNav)]
|
#[function_component(EditorNav)]
|
||||||
fn editor_nav(props: &EditorViewDetailProps) -> Html {
|
fn editor_nav(props: &EditorViewDetailProps) -> Html {
|
||||||
let global_memo = props.global_memo.clone();
|
let global_memo = props.global_memo.clone();
|
||||||
@ -136,13 +157,16 @@ fn editor_nav(props: &EditorViewDetailProps) -> Html {
|
|||||||
new_state.error_msg = msg.map(|v| AttrValue::from(String::from(v)));
|
new_state.error_msg = msg.map(|v| AttrValue::from(String::from(v)));
|
||||||
set_err_state.set(new_state.into());
|
set_err_state.set(new_state.into());
|
||||||
});
|
});
|
||||||
|
let editor_state = props.editor_state.clone();
|
||||||
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"
|
<button class="btn" aria-label="Run" title="Run"
|
||||||
onclick={move |_ev| run_script(¤t_script, &global_memo, set_err.clone())}>
|
onclick={move |_ev| run_script(¤t_script, &global_memo, set_err.clone())}>
|
||||||
<i class="bi bi-file-earmark-play"></i></button>
|
<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"
|
||||||
|
onclick={move |_ev| show_create_dialog(&editor_state) }
|
||||||
|
><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>
|
||||||
@ -170,317 +194,6 @@ fn editor_nav(props: &EditorViewDetailProps) -> Html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(getter_with_clone)]
|
|
||||||
struct EditorViewConfig {
|
|
||||||
pub doc: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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(getter_with_clone)]
|
|
||||||
struct CustomKeyBinding {
|
|
||||||
pub key: String,
|
|
||||||
pub mac: String,
|
|
||||||
pub run: Function,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn custom_key_binding(inp: CustomKeyBinding) -> KeyBinding {
|
|
||||||
let kb = Object::new();
|
|
||||||
let _ = Reflect::set(&kb, &"key".into(), &inp.key.into());
|
|
||||||
let _ = Reflect::set(&kb, &"mac".into(), &inp.mac.into());
|
|
||||||
let _ = Reflect::set(&kb, &"run".into(), &inp.run.into());
|
|
||||||
kb.unchecked_into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(module = "@codemirror/view")]
|
|
||||||
extern "C" {
|
|
||||||
type EditorView;
|
|
||||||
type ViewUpdate;
|
|
||||||
#[derive(Clone)]
|
|
||||||
type KeyBinding;
|
|
||||||
#[derive(Clone)]
|
|
||||||
type CMExtension;
|
|
||||||
|
|
||||||
#[wasm_bindgen(constructor)]
|
|
||||||
fn new(settings: EditorViewConfig) -> EditorView;
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_namespace = ["EditorView", "updateListener"], js_name = of)]
|
|
||||||
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")]
|
|
||||||
extern "C" {
|
|
||||||
#[wasm_bindgen(js_name = lua, thread_local)]
|
|
||||||
static LUA: StreamLanguage;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(module = "@codemirror/language")]
|
|
||||||
extern "C" {
|
|
||||||
#[derive(Clone)]
|
|
||||||
type StreamLanguage;
|
|
||||||
#[derive(Clone)]
|
|
||||||
type CMHighlightStyle;
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_namespace = StreamLanguage, js_name = define)]
|
|
||||||
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 = emacsStyleKeymap, thread_local)]
|
|
||||||
static EMACS_STYLE_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)]
|
|
||||||
fn editor_area(props: &EditorViewDetailProps) -> Html {
|
|
||||||
let node_ref = use_node_ref();
|
|
||||||
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();
|
|
||||||
|
|
||||||
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());
|
|
||||||
});
|
|
||||||
|
|
||||||
let global_memo_runkey = props.global_memo.clone();
|
|
||||||
let runkey_closure = use_memo((), |()| {
|
|
||||||
Closure::<dyn FnMut(EditorState) -> bool>::new(move |_ed_state| {
|
|
||||||
run_script(¤t_script, &global_memo_runkey, set_err.clone());
|
|
||||||
true
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
use_effect_with(props.editor_state.open_file.clone(), move |open_file| {
|
|
||||||
let mut keymaps: Vec<KeyBinding> = vec![custom_key_binding(CustomKeyBinding {
|
|
||||||
key: "Ctrl-Enter".to_owned(),
|
|
||||||
mac: "Cmd-Enter".to_owned(),
|
|
||||||
run: runkey_closure
|
|
||||||
.as_ref()
|
|
||||||
.as_ref()
|
|
||||||
.unchecked_ref::<Function>()
|
|
||||||
.clone(),
|
|
||||||
})];
|
|
||||||
keymaps.append(
|
|
||||||
&mut vec![
|
|
||||||
&CLOSE_BRACKETS_KEYMAP,
|
|
||||||
&DEFAULT_KEYMAP,
|
|
||||||
&EMACS_STYLE_KEYMAP,
|
|
||||||
&SEARCH_KEYMAP,
|
|
||||||
&HISTORY_KEYMAP,
|
|
||||||
&FOLD_KEYMAP,
|
|
||||||
&COMPLETION_KEYMAP,
|
|
||||||
&LINT_KEYMAP,
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|k| k.with(|v| v.clone()).into_iter())
|
|
||||||
.collect_vec(),
|
|
||||||
);
|
|
||||||
|
|
||||||
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![
|
|
||||||
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(keymaps),
|
|
||||||
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 => {}
|
|
||||||
Some(node) => {
|
|
||||||
let _ = node.append_child(&view.dom());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
html! {
|
|
||||||
<div class="editorarea" ref={node_ref.clone()}>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Properties, PartialEq, Clone)]
|
#[derive(Properties, PartialEq, Clone)]
|
||||||
pub struct ErrorBarProps {
|
pub struct ErrorBarProps {
|
||||||
pub msg: AttrValue,
|
pub msg: AttrValue,
|
||||||
@ -498,6 +211,36 @@ fn error_bar(props: &ErrorBarProps) -> Html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 create_script(state: &UseStateHandle<Rc<EditorViewState>>) {
|
||||||
|
let mut scriptname = window()
|
||||||
|
.and_then(|w| w.document())
|
||||||
|
.and_then(|d| d.get_element_by_id("newscriptname"))
|
||||||
|
.map(|el| el.unchecked_into::<HtmlInputElement>().value())
|
||||||
|
.unwrap_or_default();
|
||||||
|
if scriptname.is_empty() {
|
||||||
|
scriptname = "untitled.lua".to_owned();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new_state = (*state.deref().deref()).clone();
|
||||||
|
|
||||||
|
let scriptname: AttrValue = scriptname.into();
|
||||||
|
if new_state.available_files.contains(&scriptname) {
|
||||||
|
new_state.error_msg = Some("A script with that name already exists".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
new_state.available_files.push(scriptname.clone());
|
||||||
|
new_state.open_file = scriptname;
|
||||||
|
new_state.show_create_dialog = false;
|
||||||
|
state.set(new_state.into());
|
||||||
|
}
|
||||||
|
|
||||||
#[function_component(CodeEditorView)]
|
#[function_component(CodeEditorView)]
|
||||||
pub fn editor_view(props: &EditorViewProps) -> Html {
|
pub fn editor_view(props: &EditorViewProps) -> Html {
|
||||||
let editor_state: UseStateHandle<Rc<EditorViewState>> =
|
let editor_state: UseStateHandle<Rc<EditorViewState>> =
|
||||||
@ -521,14 +264,13 @@ pub fn editor_view(props: &EditorViewProps) -> Html {
|
|||||||
let editor_ref = use_node_ref();
|
let editor_ref = use_node_ref();
|
||||||
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 editor_closure: UseStateHandle<Option<Rc<Closure<dyn FnMut(KeyboardEvent)>>>> =
|
type KbClosure = Closure<dyn FnMut(KeyboardEvent)>;
|
||||||
use_state(|| None);
|
let editor_closure: UseStateHandle<Option<Rc<KbClosure>>> = use_state(|| None);
|
||||||
let editor_ref_eff = editor_ref.clone();
|
let editor_ref_eff = editor_ref.clone();
|
||||||
let global_memo = props.global_memo.clone();
|
let global_memo = props.global_memo.clone();
|
||||||
use_effect_with((), move |()| {
|
use_effect_with(current_script.clone(), move |_| {
|
||||||
if let Some(editor_node) = editor_ref_eff.get() {
|
if let Some(editor_node) = editor_ref_eff.get() {
|
||||||
let closure = Closure::new(move |ev: KeyboardEvent| {
|
let closure = Closure::new(move |ev: KeyboardEvent| {
|
||||||
console::log_1(&ev);
|
|
||||||
if ev.code() == "Enter" && ev.get_modifier_state("Control") {
|
if ev.code() == "Enter" && ev.get_modifier_state("Control") {
|
||||||
run_script(¤t_script, &global_memo, set_err.clone());
|
run_script(¤t_script, &global_memo, set_err.clone());
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
@ -538,12 +280,52 @@ pub fn editor_view(props: &EditorViewProps) -> Html {
|
|||||||
});
|
});
|
||||||
let _ = editor_node
|
let _ = editor_node
|
||||||
.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref());
|
.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref());
|
||||||
editor_closure.set(Some(closure.into()));
|
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;
|
||||||
}
|
}
|
||||||
|
Box::new(|| {})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let state_modalclose1 = editor_state.clone();
|
||||||
|
let state_modalclose2 = editor_state.clone();
|
||||||
|
let state_createscript = editor_state.clone();
|
||||||
html! {
|
html! {
|
||||||
<div class="w-100 h-100" ref={editor_ref.clone()}>
|
<div class="w-100 h-100" ref={editor_ref.clone()}>
|
||||||
|
{if editor_state.show_create_dialog {
|
||||||
|
html! { <>
|
||||||
|
<div class="modal-backdrop fade show"/>
|
||||||
|
<div class="modal" tabindex="-1" aria-modal="true" role="dialog" style="display: block">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">{"Create script"}</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"
|
||||||
|
onclick={move |_ev| close_modals(&state_modalclose1) }></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="d-flex flex-row"><label for="newscriptname" class="pe-1">{"New script name: "}</label>
|
||||||
|
<input class="flex-grow-1" type="text" id="newscriptname" tabindex="1"/></p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"
|
||||||
|
onclick={move |_ev| close_modals(&state_modalclose2) }>{"Close"}</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick={move |_ev| create_script(&state_createscript) }>{"Create script"}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
} else {
|
||||||
|
html! { <></> }
|
||||||
|
}}
|
||||||
{match editor_state.error_msg.as_ref() {
|
{match editor_state.error_msg.as_ref() {
|
||||||
None => { html! { <></> }}
|
None => { html! { <></> }}
|
||||||
Some(msg) => {
|
Some(msg) => {
|
||||||
|
315
src/editor_view/editor_area.rs
Normal file
315
src/editor_view/editor_area.rs
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use anyhow::Error;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsCast};
|
||||||
|
use wasm_bindgen_futures::js_sys::{Function, Object, Reflect};
|
||||||
|
use web_sys::{window, HtmlElement};
|
||||||
|
use yew::{
|
||||||
|
function_component, html, use_effect_with, use_memo, use_node_ref, use_state, AttrValue, Html,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{load_file_contents, run_script, EditorViewDetailProps};
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter_with_clone)]
|
||||||
|
struct EditorViewConfig {
|
||||||
|
pub doc: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter_with_clone)]
|
||||||
|
struct EditorStateConfig {
|
||||||
|
pub doc: String,
|
||||||
|
pub extensions: Vec<CMExtension>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(getter_with_clone)]
|
||||||
|
struct HighlightingConfig {
|
||||||
|
pub fallback: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EditorKeepClosures {
|
||||||
|
change_closure: Rc<Closure<dyn FnMut(ViewUpdate)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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(getter_with_clone)]
|
||||||
|
struct CustomKeyBinding {
|
||||||
|
pub key: String,
|
||||||
|
pub mac: String,
|
||||||
|
pub run: Function,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn custom_key_binding(inp: CustomKeyBinding) -> KeyBinding {
|
||||||
|
let kb = Object::new();
|
||||||
|
let _ = Reflect::set(&kb, &"key".into(), &inp.key.into());
|
||||||
|
let _ = Reflect::set(&kb, &"mac".into(), &inp.mac.into());
|
||||||
|
let _ = Reflect::set(&kb, &"run".into(), &inp.run.into());
|
||||||
|
kb.unchecked_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(module = "@codemirror/view")]
|
||||||
|
extern "C" {
|
||||||
|
type EditorView;
|
||||||
|
type ViewUpdate;
|
||||||
|
#[derive(Clone)]
|
||||||
|
type KeyBinding;
|
||||||
|
#[derive(Clone)]
|
||||||
|
type CMExtension;
|
||||||
|
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
fn new(settings: EditorViewConfig) -> EditorView;
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_namespace = ["EditorView", "updateListener"], js_name = of)]
|
||||||
|
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")]
|
||||||
|
extern "C" {
|
||||||
|
#[wasm_bindgen(js_name = lua, thread_local)]
|
||||||
|
static LUA: StreamLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(module = "@codemirror/language")]
|
||||||
|
extern "C" {
|
||||||
|
#[derive(Clone)]
|
||||||
|
type StreamLanguage;
|
||||||
|
#[derive(Clone)]
|
||||||
|
type CMHighlightStyle;
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_namespace = StreamLanguage, js_name = define)]
|
||||||
|
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 = emacsStyleKeymap, thread_local)]
|
||||||
|
static EMACS_STYLE_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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(EditorArea)]
|
||||||
|
pub(super) fn editor_area(props: &EditorViewDetailProps) -> Html {
|
||||||
|
let node_ref = use_node_ref();
|
||||||
|
let editor_state = props.editor_state.clone();
|
||||||
|
|
||||||
|
let closures = use_memo(editor_state.open_file.clone(), |_| 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();
|
||||||
|
|
||||||
|
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());
|
||||||
|
});
|
||||||
|
|
||||||
|
let global_memo_runkey = props.global_memo.clone();
|
||||||
|
let runkey_closure = use_memo((), |()| {
|
||||||
|
Closure::<dyn FnMut(EditorState) -> bool>::new(move |_ed_state| {
|
||||||
|
run_script(¤t_script, &global_memo_runkey, set_err.clone());
|
||||||
|
true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
use_effect_with(props.editor_state.open_file.clone(), move |open_file| {
|
||||||
|
let mut keymaps: Vec<KeyBinding> = vec![custom_key_binding(CustomKeyBinding {
|
||||||
|
key: "Ctrl-Enter".to_owned(),
|
||||||
|
mac: "Cmd-Enter".to_owned(),
|
||||||
|
run: runkey_closure
|
||||||
|
.as_ref()
|
||||||
|
.as_ref()
|
||||||
|
.unchecked_ref::<Function>()
|
||||||
|
.clone(),
|
||||||
|
})];
|
||||||
|
keymaps.append(
|
||||||
|
&mut vec![
|
||||||
|
&CLOSE_BRACKETS_KEYMAP,
|
||||||
|
&DEFAULT_KEYMAP,
|
||||||
|
&EMACS_STYLE_KEYMAP,
|
||||||
|
&SEARCH_KEYMAP,
|
||||||
|
&HISTORY_KEYMAP,
|
||||||
|
&FOLD_KEYMAP,
|
||||||
|
&COMPLETION_KEYMAP,
|
||||||
|
&LINT_KEYMAP,
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|k| k.with(|v| v.clone()).into_iter())
|
||||||
|
.collect_vec(),
|
||||||
|
);
|
||||||
|
|
||||||
|
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![
|
||||||
|
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(keymaps),
|
||||||
|
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 => {}
|
||||||
|
Some(node) => {
|
||||||
|
let _ = node.append_child(&view.dom());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
html! {
|
||||||
|
<div class="editorarea" ref={node_ref.clone()}>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,12 @@
|
|||||||
font-family: "JetBrainsMono-Regular";
|
font-family: "JetBrainsMono-Regular";
|
||||||
src: url("JetBrainsMono-Regular.woff2");
|
src: url("JetBrainsMono-Regular.woff2");
|
||||||
}
|
}
|
||||||
|
:root {
|
||||||
|
--bs-heading-color: var(--bs-gray-100);
|
||||||
|
}
|
||||||
|
.modal {
|
||||||
|
--bs-modal-color: var(--bs-gray-100);
|
||||||
|
}
|
||||||
html {
|
html {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0; left: 0; right: 0; bottom: 0;
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user