diff --git a/Cargo.lock b/Cargo.lock index 7df0ca3..1aebb3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,8 +304,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -601,6 +603,7 @@ dependencies = [ "base64", "chrono", "fake", + "getrandom", "gloo", "js-sys", "lazy-regex", diff --git a/Cargo.toml b/Cargo.toml index b8ca92c..a3a800e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ gloo = "0.8" serde = { version = "1.0", features = ["derive"] } fake = "2.5" rand = "0.8" +getrandom = { version = "0.2.8", features = ["js"] } chrono = "0.4.23" [dependencies.zip] diff --git a/src/app.rs b/src/app.rs index 9d3668b..0e6f606 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::io::Cursor; use std::collections::{HashMap, self}; use std::rc::Rc; @@ -9,24 +10,32 @@ use web_sys::{DragEvent, Event, FileList, HtmlInputElement, MouseEvent}; use yew::html::TargetCast; use yew::{html, Callback, Component, Context, Html}; +use crate::generate_sql::{SQLValueGuess, generate_table_guessess}; use crate::magicdraw_parser::{parse_project, SQLTableCollection, SQLTable}; use crate::components::sql_column_info::SQLTableColumnInfo; const COLLECTION_STORE_KEY: &str = "current_collection"; +const DEFAULT_ROWS_PER_TABLE: u32 = 20; pub enum Msg { Noop, Loaded(String, Vec), UploadProject(File), UpdateCurrentProject(Option), + UpdateGenarator(String, SQLValueGuess), ShowNextTable, - ShowPrevTable + ShowPrevTable, + AllGoodConfirmation, + GenerateSQL, } pub struct App { active_readers: HashMap, current_collection: Option>>, - currently_shown_table: usize + current_guessess: Vec>>>, + currently_shown_table: usize, + all_good_confirmed: bool, + generated_sql: Option } impl Component for App { @@ -34,15 +43,24 @@ impl Component for App { type Properties = (); fn create(_ctx: &Context) -> Self { + let mut current_guessess = vec![]; let mut current_collection = None; if let Ok(collection) = LocalStorage::get::("current_collection") { + for table in &collection.tables { + let guess = generate_table_guessess(table); + current_guessess.push(Rc::new(RefCell::new(guess))); + } + current_collection = Some(collection.tables.into_iter().map(Rc::new).collect()); } Self { active_readers: HashMap::default(), current_collection, - currently_shown_table: 0 + currently_shown_table: 0, + all_good_confirmed: true, // TODO: make this false, by default + generated_sql: None, + current_guessess } } @@ -84,6 +102,14 @@ impl Component for App { Msg::UpdateCurrentProject(collection) => { if let Some(collection) = collection { LocalStorage::set(COLLECTION_STORE_KEY, &collection).unwrap(); + self.currently_shown_table = 0; + self.all_good_confirmed = false; + self.generated_sql = None; + self.current_guessess = vec![]; + for table in &collection.tables { + let guess = generate_table_guessess(table); + self.current_guessess.push(Rc::new(RefCell::new(guess))); + } self.current_collection = Some(collection.tables.into_iter().map(Rc::new).collect()); } else { LocalStorage::delete(COLLECTION_STORE_KEY); @@ -106,79 +132,36 @@ impl Component for App { } false }, + Msg::AllGoodConfirmation => { + self.all_good_confirmed = true; + true + }, + Msg::UpdateGenarator(column, generator) => { + console_dbg!(column, generator); + let mut guessess = self.current_guessess[self.currently_shown_table].borrow_mut(); + let entry = guessess.get_mut(&column).unwrap(); + *entry = generator; + true + }, + Msg::GenerateSQL => { + false + }, } } fn view(&self, ctx: &Context) -> Html { - let prevent_default_cb = Callback::from(|event: DragEvent| { - event.prevent_default(); - }); - html! {

{ "🪄 MagicDraw SQL Data Generator" }

-
-

- { "1. Upload " } - {".mdzip"} - { " project" } -

- - -

{ "NOTE: This relies on the fact, that you have a .dll script configured" }

-
- if let Some(collection) = &self.current_collection { -
-

{ "2. Make sure everything looks 👌" }

-
- -
{ self.currently_shown_table + 1 } { " / " } { collection.len() }
- -
- { Self::show_table(collection[self.currently_shown_table].clone()) } - -
-
-

{ "3. Copy & Paste" }

-
+ { self.show_step1(ctx) } + if self.current_collection.is_some() { + { self.show_step2(ctx) } + if self.all_good_confirmed { + { self.show_step3(ctx) } + if self.generated_sql.is_some() { + { self.show_step4(ctx) } + } + } }
} @@ -186,10 +169,117 @@ impl Component for App { } impl App { + fn show_step1(&self, ctx: &Context) -> Html { + let prevent_default_cb = Callback::from(|event: DragEvent| { + event.prevent_default(); + }); - fn show_table(table: Rc) -> Html { - html!{ - + html! { +
+

+ { "1. Upload " } + {".mdzip"} + { " project" } +

+ + +

{ "NOTE: This relies on the fact, that you have a .dll script configured" }

+
+ } + } + + fn show_step2(&self, ctx: &Context) -> Html { + let collection = self.current_collection.as_ref().unwrap(); + + html! { +
+

{ "2. Make sure everything looks 👌" }

+
+ +
{ self.currently_shown_table + 1 } { " / " } { collection.len() }
+ +
+ + +
+ } + } + + fn show_step3(&self, ctx: &Context) -> Html { + html! { +
+

{ "3. Final settings" }

+ + + + +
+ } + } + + fn show_step4(&self, ctx: &Context) -> Html { + let sql = self.generated_sql.as_ref().unwrap(); + html! { +
+

{ "4. Copy & Paste" }

+ + { sql } + +
} } diff --git a/src/components/generator_picker.rs b/src/components/generator_picker.rs new file mode 100644 index 0000000..322c2a1 --- /dev/null +++ b/src/components/generator_picker.rs @@ -0,0 +1,191 @@ +use std::{collections::HashMap, str::FromStr}; + +use gloo::console::console_dbg; +use yew::{Html, html, Callback, TargetCast, AttrValue}; +use web_sys::{Event, HtmlInputElement}; + +use crate::{generate_sql::{SQLValueGuess, SQLStringValueGuess, SQLBoolValueGuess, SQLTimeValueGuess, SQLIntValueGuess}, magicdraw_parser::{SQLColumn, SQLCheckConstraint}}; + +fn show_dropdown_picker( + selected: &str, + options: &[AttrValue], + onchange: Callback +) -> Html { + html!{ + + } +} + +fn show_enum_dropdown( + selected: &T, + options: HashMap, + onchange: Callback + ) -> Html { + let keys = options.keys().map(AttrValue::clone).collect::>(); + let guess_str = options.iter().find(|(_, v)| v.eq(&selected)).unwrap().0.clone(); + + show_dropdown_picker(&guess_str, &keys, onchange.reform(move |value_str: String| { + options.get(value_str.as_str()).unwrap().clone() + })) +} + +fn show_range_picker( + min: T, + max: T, + default_min: T, + default_max: T, + onchange: Callback<(T, T)>, + ) -> Html { + + let onchange_min = { + let onchange = onchange.clone(); + let default_min = default_min.clone(); + let max = max.clone(); + Callback::from(move |e: Event| { + let value = e.target_unchecked_into::().value(); + let min_value = value.parse().unwrap_or(default_min.clone()); + onchange.emit((min_value, max.clone())) + }) + }; + + let onchange_max = { + let onchange = onchange.clone(); + let default_max = default_max.clone(); + let min = min.clone(); + Callback::from(move |e: Event| { + let value = e.target_unchecked_into::().value(); + let max_value = value.parse().unwrap_or(default_max.clone()); + onchange.emit((min.clone(), max_value)) + }) + }; + + html! { +
+ +
{ ".." }
+ +
+ } +} + +pub fn generator_picker( + column: &SQLColumn, + value: &SQLValueGuess, + onchange: Callback +) -> Html { + // TODO: Refacotr 'time', 'datetime', and 'date'. They are very similar + match value { + SQLValueGuess::Int(guess) => { + if column.primary_key { + return html!("Auto increment") + } + + let mut min = 0; + let mut max = 0; + if let SQLIntValueGuess::Range(range_min, range_max) = guess { + min = *range_min; + max = *range_max; + } + + // TODO: Disallow entering floating point numbers + show_range_picker(min, max, 0, 100, onchange.reform(|(min, max)| { + SQLValueGuess::Int(SQLIntValueGuess::Range(min, max)) + })) + }, + SQLValueGuess::Float(min, max) => { + show_range_picker(*min, *max, 0.0, 100.0, onchange.reform(|(min, max)| { + SQLValueGuess::Float(min, max) + })) + }, + SQLValueGuess::Date(guess) => { + let options = HashMap::from([ + ("Now".into() , SQLTimeValueGuess::Now), + ("Future".into(), SQLTimeValueGuess::Future), + ("Past".into() , SQLTimeValueGuess::Past), + ]); + + show_enum_dropdown(guess, options, onchange.reform(|enum_value| { + SQLValueGuess::Date(enum_value) + })) + }, + SQLValueGuess::Time(guess) => { + let options = HashMap::from([ + ("Now".into() , SQLTimeValueGuess::Now), + ("Future".into(), SQLTimeValueGuess::Future), + ("Past".into() , SQLTimeValueGuess::Past), + ]); + + show_enum_dropdown(guess, options, onchange.reform(|enum_value| { + SQLValueGuess::Time(enum_value) + })) + }, + SQLValueGuess::Datetime(guess) => { + let options = HashMap::from([ + ("Now".into() , SQLTimeValueGuess::Now), + ("Future".into(), SQLTimeValueGuess::Future), + ("Past".into() , SQLTimeValueGuess::Past), + ]); + + show_enum_dropdown(guess, options, onchange.reform(|enum_value| { + SQLValueGuess::Datetime(enum_value) + })) + } + SQLValueGuess::Bool(guess) => { + let options = HashMap::from([ + ("Random".into(), SQLBoolValueGuess::Random), + ("True".into() , SQLBoolValueGuess::True), + ("False".into() , SQLBoolValueGuess::False), + ]); + + show_enum_dropdown(guess, options, onchange.reform(|enum_value| { + SQLValueGuess::Bool(enum_value) + })) + }, + SQLValueGuess::String(max_size, guess) => { + if let Some(constraint) = &column.check_constraint { + if let SQLCheckConstraint::OneOf(_) = constraint { + return html!("Random Enum"); + } + } + + let options = HashMap::from([ + ("Lorem Ipsum".into() , SQLStringValueGuess::LoremIpsum), + ("First Name".into() , SQLStringValueGuess::FirstName), + ("Last Name".into() , SQLStringValueGuess::LastName), + ("Full Name".into() , SQLStringValueGuess::FullName), + ("Empty".into() , SQLStringValueGuess::Empty), + ("Phone number".into() , SQLStringValueGuess::PhoneNumber), + ("City name".into() , SQLStringValueGuess::CityName), + ("Address".into() , SQLStringValueGuess::Address), + ("Email".into() , SQLStringValueGuess::Email), + ("URL".into() , SQLStringValueGuess::URL), + ]); + + let max_size = *max_size; + show_enum_dropdown(guess, options, onchange.reform(move |enum_value| { + SQLValueGuess::String(max_size, enum_value) + })) + }, + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index fd0325c..3d6be94 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1 +1,2 @@ pub mod sql_column_info; +pub mod generator_picker; diff --git a/src/components/sql_column_info.rs b/src/components/sql_column_info.rs index 4e4fb5f..b389983 100644 --- a/src/components/sql_column_info.rs +++ b/src/components/sql_column_info.rs @@ -1,12 +1,14 @@ -use std::rc::Rc; +use std::{rc::Rc, collections::HashMap, cell::RefCell}; -use yew::{Properties, html, function_component, Html}; +use yew::{Properties, html, function_component, Html, Callback}; -use crate::magicdraw_parser::SQLTable; +use crate::{magicdraw_parser::SQLTable, generate_sql::SQLValueGuess, components::generator_picker::generator_picker}; #[derive(Properties, PartialEq)] pub struct SQLTableColumnInfoProps { - pub table: Rc + pub table: Rc, + pub guessess: Rc>>, + pub onchange: Callback<(String, SQLValueGuess)> } const CHECK_MARK: &str = "✔️"; @@ -22,24 +24,36 @@ pub fn SQLTableColumnInfo(props: &SQLTableColumnInfoProps) -> Html { let rows = table.columns.iter() .map(|col| { - let foreign_key; - if let Some((table_name, prop_name)) = &col.foreign_key { - foreign_key = format!("{} {}", table_name, prop_name); - } else { - foreign_key = CROSS_MARK.into(); - } + let guessess = &props.guessess.borrow(); + let generator = guessess.get(&col.name); - html! { - - { &col.name } - { &col.sql_type } - { bool_to_mark(col.primary_key) } - { bool_to_mark(col.nullable) } - { foreign_key } - - } - } - ); + let foreign_key; + if let Some((table_name, prop_name)) = &col.foreign_key { + foreign_key = format!("{} {}", table_name, prop_name); + } else { + foreign_key = CROSS_MARK.into(); + } + + let name = col.name.clone(); + let onchange = props.onchange.reform(move |value: SQLValueGuess| (name.clone(), value)); + html! { + + { &col.name } + { &col.sql_type } + { + if let Some(generator) = generator { + generator_picker(col, generator, onchange) + } else { + html!(CROSS_MARK) + } + } + { bool_to_mark(col.primary_key) } + { bool_to_mark(col.nullable) } + { foreign_key } + + } + } + ); html!{
Html { border="solid dark100 0.2rem collapse" >

{ &table.name }

- +
+ diff --git a/src/generate_sql.rs b/src/generate_sql.rs index 0774804..da3f5c8 100644 --- a/src/generate_sql.rs +++ b/src/generate_sql.rs @@ -1,4 +1,4 @@ -use std::{rc::Rc, collections::HashSet}; +use std::{rc::Rc, collections::{HashSet, HashMap}}; use anyhow::{Result, bail}; use rand::{seq::SliceRandom, Rng, rngs::ThreadRng}; @@ -9,20 +9,20 @@ use crate::magicdraw_parser::{SQLTable, SQLColumn, SQLType, SQLCheckConstraint}; const INDENT: &str = " "; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum SQLIntValueGuess { Range(i32, i32), AutoIncrement } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum SQLTimeValueGuess { Now, Future, Past } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum SQLStringValueGuess { LoremIpsum, FirstName, @@ -37,14 +37,14 @@ pub enum SQLStringValueGuess { RandomEnum(Vec), } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum SQLBoolValueGuess { True, False, Random, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum SQLValueGuess { Int(SQLIntValueGuess), Date(SQLTimeValueGuess), @@ -58,7 +58,7 @@ pub enum SQLValueGuess { // TODO: Check primary key constraint pub fn generate_fake_entries( tables: &[Rc], - value_guessess: &Vec>, + value_guessess: &Vec>, rows_per_table: u32 ) -> Result { let mut lines = vec![]; @@ -96,9 +96,9 @@ pub fn generate_fake_entries( for (table_idx, table) in tables.iter().enumerate() { let entries = &mut all_entries[table_idx]; - for (column_idx, column) in table.columns.iter().enumerate() { + for column in &table.columns { let mut auto_increment_counter = 0; - let value_guess = &value_guessess[table_idx][column_idx]; + let value_guess = value_guessess[table_idx].get(column.name.as_str()).expect("Failed to get column guess"); for entry_idx in 0..(rows_per_table as usize) { if let Some(_) = &column.foreign_key { entries_with_foreign_keys.insert((table_idx, entry_idx)); @@ -358,8 +358,9 @@ pub fn generate_guess(column: &SQLColumn) -> SQLValueGuess { } } -pub fn generate_table_guessess(table: &SQLTable) -> Vec { +pub fn generate_table_guessess(table: &SQLTable) -> HashMap { table.columns.iter() - .map(|column| generate_guess(column)) + .filter(|column| column.foreign_key.is_none()) + .map(|column| (column.name.clone(), generate_guess(column))) .collect() } diff --git a/styles.css b/styles.css index 0d4dda5..ffeb7f4 100644 --- a/styles.css +++ b/styles.css @@ -7,3 +7,15 @@ button { border: 0 } + +/* Chrome, Safari, Edge, Opera */ +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* Firefox */ +input[type=number] { + -moz-appearance: textfield; +}
{ "Column" } { "Type" } { "Generator" } { "Primary?" } { "Nullable?" } { "Foreign key?" }