use istyles::istyles; use leptos::{html::Input, prelude::*, tachys::html}; use reactive_stores::Store; use sqlformat::{FormatOptions, QueryParams}; use wasm_bindgen::{JsCast, prelude::Closure}; use web_sys::{Blob, Event, FileReader, HtmlInputElement, MouseEvent, Url, UrlSearchParams}; use crate::{ FragileComfirmed, LoadDbOptions, RunOptions, SQLightError, WorkerRequest, app::{ ImportProgress, advanced_options_menu::AdvancedOptionsMenu, button_set::{Button, ButtonSet, IconButton, LinkButton, Rule}, config_menu::ConfigMenu, context_menu::ContextMenu, database_menu::DatabaseMenu, icon::{build_icon, config_icon, expandable_icon, github_icon, more_options_icon}, output::change_focus, pop_button::PopButton, state::{Focus, GlobalState, GlobalStateStoreFields}, tools_menu::ToolsMenu, vfs_menu::VfsMenu, }, send_request, }; istyles!(styles, "assets/module.postcss/header.module.css.map"); #[component] pub fn Header() -> impl IntoView { let menu_container = NodeRef::new(); let input_ref = NodeRef::new(); view! { <>
} } pub fn execute(state: Store) -> Box { Box::new(move || { let editor_guard = state.editor().read_untracked(); let Some(editor) = editor_guard.as_ref() else { return; }; let (code, selected_code) = (editor.get_value(), editor.get_selected_value()); drop(editor_guard); state.sql().set(code.clone()); change_focus(state, Some(Focus::Execute)); std::mem::take(&mut *state.output().write()); let run_selected_code = !selected_code.is_empty() && state.run_selected_sql().get_untracked(); send_request( state, WorkerRequest::Run(RunOptions { embed: false, sql: if run_selected_code { selected_code } else { code }, clear_on_prepare: !*state.keep_ctx().read_untracked(), }), ); }) } #[component] fn ExecuteButton() -> impl IntoView { let state = expect_context::>(); let on_click = execute(state); view! { } } #[component] fn VfsMenuButton(menu_container: NodeRef) -> impl IntoView { let state = expect_context::>(); let button = move |toggle, node_ref| { view! { } .into_any() }; view! { }.into_any() }) menu_container=menu_container > } } #[component] fn ContextMenuButton(menu_container: NodeRef) -> impl IntoView { let state = expect_context::>(); let button = move |toggle, node_ref| { view! { } .into_any() }; view! { }.into_any() }) menu_container=menu_container > } } #[component] fn ConfigMenuButton(menu_container: NodeRef) -> impl IntoView { let button = |toggle, node_ref| { view! { } .into_any() }; view! { }.into_any() }) menu_container=menu_container > } } #[component] fn AdvancedOptionsMenuButton(menu_container: NodeRef) -> impl IntoView { let button = |toggle, node_ref| { view! { {more_options_icon()} } .into_any() }; view! { }.into_any() }) menu_container=menu_container > } } #[component] fn ToolsButton(menu_container: NodeRef) -> impl IntoView { let state = expect_context::>(); let button = |toggle, node_ref| { view! { } .into_any() }; let on_format = move |_event, signal: WriteSignal| { let Some(editor) = &*state.editor().read() else { return; }; let format_options = FormatOptions { uppercase: Some(true), lines_between_queries: 2, ..Default::default() }; let sql = sqlformat::format( &editor.get_value(), &QueryParams::default(), &format_options, ); editor.set_value(sql); signal.set(false); }; let on_embed = move |_event, signal: WriteSignal| { let editor_guard = state.editor().read_untracked(); let Some(editor) = editor_guard.as_ref() else { return; }; let sql = editor.get_value(); drop(editor_guard); std::mem::take(&mut *state.output().write()); send_request( state, WorkerRequest::Run(RunOptions { embed: true, sql, clear_on_prepare: !*state.keep_ctx().read_untracked(), }), ); signal.set(false); }; view! { | { view! { } .into_any() }) menu_container=menu_container > } } #[component] fn DatabaseButton( input_ref: NodeRef, menu_container: NodeRef, ) -> impl IntoView { let state = expect_context::>(); let button = |toggle, node_ref| { view! { } .into_any() }; let (file, set_file) = signal::>>(None); Effect::new(move || { if let Some(file) = &*file.read() { let filename = file.name(); if let Ok(reader) = FileReader::new() { let on_progress = FragileComfirmed::new(Closure::wrap(Box::new( move |ev: web_sys::ProgressEvent| { if ev.length_computable() { state.import_progress().set(Some(ImportProgress { filename: filename.clone(), loaded: ev.loaded(), total: ev.total(), opened: None, })); } }, ) as Box)); let on_error = FragileComfirmed::new(Closure::wrap(Box::new( move |ev: web_sys::ProgressEvent| { let target = ev.target().unwrap(); let reader = target.unchecked_into::(); let dom_error = reader.error().unwrap(); state.last_error().set(Some(FragileComfirmed::new( SQLightError::ImportDb(dom_error.message().to_string()), ))); }, ) as Box)); let on_load = FragileComfirmed::new(Closure::wrap(Box::new(move |ev: web_sys::Event| { let target = ev.target().unwrap(); let reader = target.unchecked_into::(); let result = reader.result().unwrap(); let array_buffer = result.unchecked_into::(); let data = js_sys::Uint8Array::new(&array_buffer); send_request(state, WorkerRequest::LoadDb(LoadDbOptions { data })); }) as Box)); reader.set_onprogress(Some(on_progress.as_ref().unchecked_ref())); reader.set_onerror(Some(on_error.as_ref().unchecked_ref())); reader.set_onload(Some(on_load.as_ref().unchecked_ref())); reader.read_as_array_buffer(file).unwrap(); on_cleanup(move || { drop(on_progress); drop(on_error); drop(on_load); }); } } }); let on_change = move |ev: Event| { if let Some(target) = ev.target() { let input = target.unchecked_into::(); if let Some(files) = input.files() { if files.length() > 0 { let file = FragileComfirmed::new(files.get(0).unwrap()); set_file.set(Some(file)); } else { set_file.set(None); } } } }; let on_load = move |_: MouseEvent, signal: WriteSignal| { if let Some(input) = &*input_ref.read() { if input.onchange().is_none() { let callback = Closure::wrap(Box::new(on_change) as Box); input.set_onchange(Some(callback.as_ref().unchecked_ref::())); callback.forget(); } input.set_value(""); input.click(); } signal.set(false); }; Effect::new(move || { state.exported().track(); let Some(downloaded) = state.exported().write_untracked().take() else { return; }; let filename = downloaded.filename; let buffer = downloaded.data; let array = js_sys::Array::new(); array.push(&buffer); let blob = Blob::new_with_u8_array_sequence(&array).unwrap(); let url = Url::create_object_url_with_blob(&blob).unwrap(); let document = document(); let a = document .create_element("a") .unwrap() .dyn_into::() .unwrap(); a.set_href(&url); a.set_download(&filename); a.click(); Url::revoke_object_url(&url).unwrap(); }); let on_download = move |_: MouseEvent, signal: WriteSignal| { send_request(state, WorkerRequest::DownloadDb); signal.set(false); }; view! { } .into_any() }) menu_container=menu_container > } } #[component] fn ShareButton() -> impl IntoView { let state = expect_context::>(); let click = move |_| { let Some(code) = state .editor() .read() .as_ref() .map(|editor| editor.get_value()) else { return; }; if let Ok(href) = window().location().href().and_then(|href| { let url = Url::new(&href)?; let params = UrlSearchParams::new()?; params.set("code", &code); url.set_search(¶ms.to_string().as_string().unwrap()); Ok(url.href()) }) { state.share_href().set(Some(href)); } change_focus(state, Some(Focus::Share)); }; view! { } } #[component] fn GithubButton() -> impl IntoView { view! { {github_icon()} } }