Support run selected sql

This commit is contained in:
Spxg
2025-05-17 22:42:18 +08:00
parent 8931532310
commit 4848e73723
9 changed files with 197 additions and 11 deletions

View File

@@ -36,6 +36,9 @@ mod bindgen {
#[wasm_bindgen(method, js_name = getValue)]
pub fn get_value(this: &Editor) -> String;
#[wasm_bindgen(method, js_name = getSelectedText)]
pub fn get_selected_text(this: &Editor) -> String;
}
}
@@ -171,4 +174,8 @@ impl Editor {
pub fn get_value(&self) -> String {
self.js.get_value()
}
pub fn get_selected_value(&self) -> String {
self.js.get_selected_text()
}
}

View File

@@ -0,0 +1,35 @@
use leptos::prelude::*;
use reactive_stores::Store;
use crate::app::{
GlobalState, GlobalStateStoreFields, config_element::Either, menu_group::MenuGroup,
};
#[component]
pub fn AdvancedOptionsMenu() -> impl IntoView {
let state = expect_context::<Store<GlobalState>>();
let value = move || *state.run_selected_code().read();
let is_default = move || !*state.run_selected_code().read();
let on_change = move |value: &bool| {
state.run_selected_code().set(*value);
};
view! {
<>
<MenuGroup title="Advanced options".into()>
<Either
id="run_selected_code".into()
name="Run Selected Code".into()
a=true
b=false
a_label=Some("On".to_string())
b_label=Some("Off".to_string())
value=value
is_default=Box::new(is_default)
on_change=on_change
/>
</MenuGroup>
</>
}
}

View File

@@ -66,3 +66,22 @@ where
pub fn Rule() -> impl IntoView {
view! { <span class=styles::rule /> }
}
#[component]
pub fn IconButton<C>(
#[prop(default = false)] is_small: bool,
#[prop(optional)] node_ref: NodeRef<html::element::Button>,
on_click: C,
children: Children,
) -> impl IntoView
where
C: FnMut(MouseEvent) + Send + 'static,
{
let style = if is_small { styles::small } else { "" };
let style = format!("{} {style}", styles::icon);
view! {
<button node_ref=node_ref class=style on:click=on_click>
{children()}
</button>
}
}

View File

@@ -1,3 +1,5 @@
use std::sync::Arc;
use istyles::istyles;
use leptos::prelude::*;
use web_sys::Event;
@@ -9,11 +11,83 @@ istyles!(
"assets/module.postcss/config_element.module.css.map"
);
#[component]
pub fn Either<E, V, T>(
on_change: E,
id: String,
a: T,
b: T,
a_label: Option<String>,
b_label: Option<String>,
value: V,
name: String,
#[prop(default = Box::new(|| true))] is_default: Box<dyn Fn() -> bool + Send>,
) -> impl IntoView
where
E: Fn(&T) + Send + Sync + 'static,
V: Fn() -> T + Send + Sync + 'static,
T: PartialEq + Eq + Send + Sync + ToString + 'static,
{
let a_label = if let Some(label) = a_label {
label
} else {
a.to_string()
};
let b_label = if let Some(label) = b_label {
label
} else {
b.to_string()
};
let a_id = format!("{id}-a");
let b_id = format!("{id}-b");
let on_change1 = Arc::new(on_change);
let on_change2 = Arc::clone(&on_change1);
let value1 = Arc::new(value);
let value2 = Arc::clone(&value1);
let a_value = a.to_string();
let b_value = b.to_string();
let a1 = Arc::new(a);
let a2 = Arc::clone(&a1);
let b1 = Arc::new(b);
let b2 = Arc::clone(&b1);
view! {
<ConfigElement name=name is_default=is_default>
<div class=styles::toggle>
<input
id=a_id.clone()
name=id.clone()
value=a_value
type="radio"
checked=move || value1().eq(&a1)
on:change=move |_ev| on_change1(&a2)
/>
<label for=a_id>{a_label}</label>
<input
id=b_id.clone()
name=id.clone()
value=b_value
type="radio"
checked=move || value2().eq(&b1)
on:change=move |_ev| on_change2(&b2)
/>
<label for=b_id>{b_label}</label>
</div>
</ConfigElement>
}
}
#[component]
pub fn Select<E>(
on_change: E,
name: String,
#[prop(default = true)] is_default: bool,
#[prop(default = Box::new(|| true))] is_default: Box<dyn Fn() -> bool + Send>,
children: Children,
) -> impl IntoView
where
@@ -31,13 +105,15 @@ where
#[component]
pub fn ConfigElement(
name: String,
#[prop(default = true)] is_default: bool,
#[prop(default = Box::new(|| true))] is_default: Box<dyn Fn() -> bool + Send>,
children: Children,
) -> impl IntoView {
let style = if is_default {
styles::name
} else {
styles::notDefault
let style = move || {
if is_default() {
styles::name
} else {
styles::notDefault
}
};
view! {
<MenuItem>

View File

@@ -6,10 +6,11 @@ use web_sys::{Url, UrlSearchParams};
use crate::{
PrepareOptions, WorkerRequest,
app::{
button_set::{Button, ButtonSet, Rule},
advanced_options_menu::AdvancedOptionsMenu,
button_set::{Button, ButtonSet, IconButton, Rule},
config_menu::ConfigMenu,
context_menu::ContextMenu,
icon::{build_icon, config_icon, expandable_icon},
icon::{build_icon, config_icon, expandable_icon, more_options_icon},
output::change_focus,
pop_button::PopButton,
state::{Focus, GlobalState, GlobalStateStoreFields},
@@ -35,6 +36,8 @@ pub fn Header() -> impl IntoView {
<VfsMenuButton menu_container=menu_container />
<Rule />
<ContextMenuButton menu_container=menu_container />
<Rule />
<AdvancedOptionsMenuButton menu_container=menu_container />
</ButtonSet>
</div>
<div class=styles::right>
@@ -53,21 +56,29 @@ pub fn Header() -> impl IntoView {
pub fn execute(state: Store<GlobalState>) -> Box<dyn Fn() + Send + 'static> {
Box::new(move || {
let Some(code) = state
let Some((code, selected_code)) = state
.editor()
.read_untracked()
.as_ref()
.map(|editor| editor.get_value())
.map(|editor| (editor.get_value(), editor.get_selected_value()))
else {
return;
};
let run_selected_code = state.run_selected_code().get();
state.code().set(code.clone());
change_focus(state, Some(Focus::Execute));
std::mem::take(&mut *state.output().write());
if let Some(worker) = &*state.worker().read_untracked() {
worker.send_task(WorkerRequest::Prepare(PrepareOptions {
id: String::new(),
sql: code,
sql: if !selected_code.is_empty() && run_selected_code {
selected_code
} else {
code
},
clear_on_prepare: !*state.keep_ctx().read_untracked(),
}));
worker.send_task(WorkerRequest::Continue(String::new()));
@@ -157,6 +168,26 @@ fn ConfigMenuButton(menu_container: NodeRef<html::element::Div>) -> impl IntoVie
}
}
#[component]
fn AdvancedOptionsMenuButton(menu_container: NodeRef<html::element::Div>) -> impl IntoView {
let button = |toggle, node_ref| {
view! {
<IconButton on_click=toggle node_ref=node_ref>
{more_options_icon()}
</IconButton>
}
.into_any()
};
view! {
<PopButton
button=button
menu=Box::new(|_close| { view! { <AdvancedOptionsMenu /> }.into_any() })
menu_container=menu_container
></PopButton>
}
}
#[component]
fn ShareButton() -> impl IntoView {
let state = expect_context::<Store<GlobalState>>();

View File

@@ -79,3 +79,17 @@ pub fn clipboard_icon() -> AnyView {
</svg>
}.into_any()
}
pub fn more_options_icon() -> AnyView {
view! {
<svg
class=styles::icon
height="18"
viewBox="0 0 24 24"
width="18"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" />
</svg>
}.into_any()
}

View File

@@ -1,3 +1,4 @@
mod advanced_options_menu;
mod button_set;
mod config_element;
mod config_menu;

View File

@@ -58,6 +58,7 @@ fn hanlde_save_state(state: Store<GlobalState>) {
state.theme().track();
state.keep_ctx().track();
state.code().track();
state.run_selected_code().track();
state.read_untracked().save();
});

View File

@@ -18,6 +18,7 @@ pub struct GlobalState {
theme: Theme,
keep_ctx: bool,
code: String,
run_selected_code: bool,
// runtime state below
#[serde(skip)]
worker: Option<WorkerHandle>,
@@ -57,6 +58,7 @@ impl Default for GlobalState {
worker: None,
editor: None,
last_error: None,
run_selected_code: false,
}
}
}