Make keyboard navigation between files work in editor.
This commit is contained in:
parent
dd1278d6d1
commit
612bcfec50
@ -6,11 +6,10 @@ use create_script_dialog::CreateScriptDialog;
|
|||||||
use editor_area::EditorArea;
|
use editor_area::EditorArea;
|
||||||
use editor_nav::EditorNav;
|
use editor_nav::EditorNav;
|
||||||
use storage::fetch_initial_editor_state;
|
use storage::fetch_initial_editor_state;
|
||||||
use wasm_bindgen::{closure::Closure, JsCast};
|
|
||||||
use web_sys::KeyboardEvent;
|
use web_sys::KeyboardEvent;
|
||||||
use yew::{
|
use yew::{
|
||||||
function_component, html, use_effect_with, use_node_ref, use_state, use_state_eq, AttrValue,
|
function_component, html, use_node_ref, use_state_eq, AttrValue, Callback, Html, Properties,
|
||||||
Callback, Html, Properties, UseStateHandle,
|
UseStateHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::storage::load_file_contents;
|
use self::storage::load_file_contents;
|
||||||
@ -74,6 +73,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)]
|
#[derive(Properties, PartialEq, Clone)]
|
||||||
pub struct ErrorBarProps {
|
pub struct ErrorBarProps {
|
||||||
pub msg: AttrValue,
|
pub msg: AttrValue,
|
||||||
@ -98,6 +103,27 @@ fn close_modals(state: &UseStateHandle<Rc<EditorViewState>>) {
|
|||||||
state.set(new_state.into());
|
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());
|
||||||
|
ev.prevent_default();
|
||||||
|
} else if ev.code() == "Escape" || (ev.code() == "KeyX" && ev.get_modifier_state("Control"))
|
||||||
|
{
|
||||||
|
close_editor(&frame, &global_layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[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>> =
|
||||||
@ -110,8 +136,6 @@ pub fn editor_view(props: &EditorViewProps) -> Html {
|
|||||||
editor_state: editor_state.clone(),
|
editor_state: editor_state.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let current_script = editor_state.open_file.clone();
|
|
||||||
|
|
||||||
let set_err_state = editor_state.clone();
|
let set_err_state = editor_state.clone();
|
||||||
let set_err = Rc::new(move |msg: Option<&str>| {
|
let set_err = Rc::new(move |msg: Option<&str>| {
|
||||||
let mut new_state = (*set_err_state.as_ref()).clone();
|
let mut new_state = (*set_err_state.as_ref()).clone();
|
||||||
@ -121,38 +145,11 @@ 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();
|
||||||
type KbClosure = Closure<dyn FnMut(KeyboardEvent)>;
|
|
||||||
let editor_closure: UseStateHandle<Option<Rc<KbClosure>>> = use_state(|| None);
|
|
||||||
let editor_ref_eff = editor_ref.clone();
|
|
||||||
let global_memo = props.global_memo.clone();
|
let global_memo = props.global_memo.clone();
|
||||||
use_effect_with(current_script.clone(), move |_| {
|
let kb_editor_state = editor_state.clone();
|
||||||
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());
|
|
||||||
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(|| {})
|
|
||||||
});
|
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class="w-100 h-100" ref={editor_ref.clone()}>
|
<div class="w-100 h-100" onkeydown={keyboard_handler(kb_editor_state, global_memo, global_layout, set_err, frame)} ref={editor_ref.clone()}>
|
||||||
{if editor_state.show_create_dialog {
|
{if editor_state.show_create_dialog {
|
||||||
html! { <CreateScriptDialog ..detail_props.clone() /> }
|
html! { <CreateScriptDialog ..detail_props.clone() /> }
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use std::{ops::Deref, rc::Rc};
|
use std::{ops::Deref, rc::Rc};
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use web_sys::KeyboardEvent;
|
||||||
use yew::{function_component, html, AttrValue, Html, UseStateHandle};
|
use yew::{function_component, html, AttrValue, Html, UseStateHandle};
|
||||||
|
|
||||||
use super::{close_editor, run_script, EditorViewDetailProps, EditorViewState};
|
use super::{close_editor, run_script, select_file, EditorViewDetailProps, EditorViewState};
|
||||||
|
|
||||||
fn show_create_dialog(state: &UseStateHandle<Rc<EditorViewState>>) {
|
fn show_create_dialog(state: &UseStateHandle<Rc<EditorViewState>>) {
|
||||||
let mut new_state: EditorViewState = (*state.deref().deref()).clone();
|
let mut new_state: EditorViewState = (*state.deref().deref()).clone();
|
||||||
@ -11,6 +12,43 @@ fn show_create_dialog(state: &UseStateHandle<Rc<EditorViewState>>) {
|
|||||||
state.set(new_state.into());
|
state.set(new_state.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn item_keyboard_handler(
|
||||||
|
item: AttrValue,
|
||||||
|
editor_state: UseStateHandle<Rc<EditorViewState>>,
|
||||||
|
) -> impl Fn(KeyboardEvent) {
|
||||||
|
move |ev: KeyboardEvent| {
|
||||||
|
if ev.code() == "Enter" && !ev.get_modifier_state("Control") {
|
||||||
|
let mut state_mut = (*editor_state.as_ref()).clone();
|
||||||
|
state_mut.open_file = item.clone();
|
||||||
|
editor_state.set(state_mut.into());
|
||||||
|
} else if ev.code() == "ArrowUp" {
|
||||||
|
if let Some((pos, _)) = editor_state
|
||||||
|
.available_files
|
||||||
|
.iter()
|
||||||
|
.find_position(|v| **v == editor_state.open_file)
|
||||||
|
{
|
||||||
|
if pos > 0 {
|
||||||
|
let mut state_mut = (*editor_state.as_ref()).clone();
|
||||||
|
state_mut.open_file = editor_state.available_files[pos - 1].clone();
|
||||||
|
editor_state.set(state_mut.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ev.code() == "ArrowDown" {
|
||||||
|
if let Some((pos, _)) = editor_state
|
||||||
|
.available_files
|
||||||
|
.iter()
|
||||||
|
.find_position(|v| **v == editor_state.open_file)
|
||||||
|
{
|
||||||
|
if pos < editor_state.available_files.len() - 1 {
|
||||||
|
let mut state_mut = (*editor_state.as_ref()).clone();
|
||||||
|
state_mut.open_file = editor_state.available_files[pos + 1].clone();
|
||||||
|
editor_state.set(state_mut.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[function_component(EditorNav)]
|
#[function_component(EditorNav)]
|
||||||
pub(super) fn editor_nav(props: &EditorViewDetailProps) -> Html {
|
pub(super) fn editor_nav(props: &EditorViewDetailProps) -> Html {
|
||||||
let global_memo = props.global_memo.clone();
|
let global_memo = props.global_memo.clone();
|
||||||
@ -49,8 +87,14 @@ pub(super) fn editor_nav(props: &EditorViewDetailProps) -> Html {
|
|||||||
aria_current = Some("true");
|
aria_current = Some("true");
|
||||||
classes.push("active");
|
classes.push("active");
|
||||||
}
|
}
|
||||||
|
let filename_for_click: AttrValue = f.clone();
|
||||||
|
let state_for_click = props.editor_state.clone();
|
||||||
|
let filename_for_kb: AttrValue = f.clone();
|
||||||
|
let state_for_kb = props.editor_state.clone();
|
||||||
html! { <li aria-current={aria_current} tabindex={0}
|
html! { <li aria-current={aria_current} tabindex={0}
|
||||||
class={classes.iter().join(" ")}
|
class={classes.iter().join(" ")}
|
||||||
|
onclick={move |_ev| select_file(&filename_for_click, &state_for_click)}
|
||||||
|
onkeydown={item_keyboard_handler(filename_for_kb, state_for_kb)}
|
||||||
style="cursor: pointer"
|
style="cursor: pointer"
|
||||||
>{f}</li>}
|
>{f}</li>}
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user