diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 2c7bb93..3b2eace 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,6 +1,6 @@
# These are supported funding model platforms
-github: [fosslife] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+github: [fosslife, sparkenstein] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
diff --git a/README.md b/README.md
index 7e21cfc..35a4f30 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,10 @@ So here's DevTools-X -- an x-platform collection of dev-tools that is lighter, s
![GitHub issues](https://badgen.net/github/issues/fosslife/devtools-x) ![GitHub stars](https://badgen.net/github/stars/fosslife/devtools-x)
![Latest release](https://badgen.net/github/release/fosslife/devtools-x)
+## Screenshot
+
+
+
## Installation
Download the relevant package from Github Releases section, and start using it! :D
@@ -40,42 +44,48 @@ This project exists solely because I was fed up switching between different tool
## Features
-DevTools-X has about **32 features** as of now, and growing. The full list in below, details of each are mentioned in a separate file. One big selling point of DevTools-X is it uses `monaco-editor`, the editor used by vscode, so tons of editor features are
-available to you right from the start, as if you are using vscode. And in the backend we use `rust` so large number of heavy duty operations are given to rust to get it done quickly.
-
-- JSON Editor/Formatter/Fixer/Minifier/Beautifier
-- Text Hashes
-- Hashing Files
-- Password Generator
-- JWT Formatter/Parser
-- Number Convertor Binary/Hex/etc
-- SQL Formatter
-- Color Generator/Picker/Convertor
-- Regex Tester
-- Text/Code Diffing With Syntax Highlighting
-- YAML <> JSON Convertor
-- Pastebin (Github Gists)
-- REST API Tester
-- Programming Scratchpad
-- Beautiful Markdown Preview
-- Image Compressor/Convertor with Preview
-- Bulk Image Compressor with Rust for Speed
-- Unit Convertor (All Major Units Supported)
-- React Scratchpad (Live React Editor to Get Preview)
-- Unix EPOCH Convertor
-- Stateless Password Generator/Manager
-- BASE64 Text Convertor
-- BASE64 Image Convertor
-- Generating Structs/Types from JSON
-- CSS/JS/HTML Minifier/Beautifier
-- URL Parser
-- HTML Preview
-- Lorem Ipsum Sample Text Generator
-- QR Code Generator
-- PDF Reader
-- Ping Command Preview
-- Text Compressor
-- And Many More Coming
+#### Checkout [features.md](features.md) for a short video demo on every feature.
+
+DevTools-X has about **34 features** as of now, and growing.
+
+The full list in below, One big selling point of DevTools-X is it uses `monaco-editor`, the editor used by vscode, so tons of editor features are
+available to you right from the start, as if you are using vscode.
+
+1. Basic REST client
+2. Unix epoch timestamp convertor
+3. Graphical ping
+4. Strong password generator
+5. QR code generator
+6. Code format/minify tools
+7. React live scratchpad
+8. Lorem Ipsum text generator
+9. Image compressor/convertor with preview
+10. Pastebin with gist
+11. Programming scratchpad with many languages support
+12. Bulk image compressor with Rust SIMD
+13. Base64 text encode/decode
+14. Base64 image encode/decode
+15. Text hash calculate (md5, sha etc)
+16. Files MD5
+17. JSON formatter/minify etc
+18. JWT decode
+19. Number convertor
+20. SQL formatter
+21. Color convertor/picker
+22. Code/text diff with syntax highlight
+23. Markdown edit/preview
+24. YAML JSON convertor
+25. Multiple units convertor (length/pressure whatnot)
+26. Text gzip/deflate/zlib compression
+27. Stateless password generator
+28. Generate programming Types and Interfaces from json
+29. URL Parser
+30. HTML editor and preview
+31. PDF Reader
+32. Cron edit and explain
+33. UUID generator
+34. Regex Tester
+35. Generate mock data with Faker
## Contributing
@@ -102,14 +112,23 @@ That should be enough to tell you it's built on top of [Tauri](https://tauri.app
## FAQ
-#### What's up with the bad looking UI?
+#### Migrate settings?
+
+There's a backup/restore feature available in settings drawer. you can backup manually as well, copy `settings.json` from [appDir](https://tauri.app/v1/api/js/path#appdatadir)
+
+#### App is not starting/showing empty screen
-Well, it was even worse previously! I am not a UI developer. I understand React, but not colors.
-Feel free to contribute any changes that you think might make it look better.
+Most likely your db is corrupt. delete `settings.json` file in your [appDir](https://tauri.app/v1/api/js/path#appdatadir).
+Create a issue if you can't find it.
+
+#### I do not like the order of modules
+
+All module can be rearranged with drag-n-drop. order is saved in a local db. you can edit this file manually as well, it's a simple json file.
#### Do I need to know Rust to get started?
-Absolutely not. I don't know Rust myself and I have a complete application that I created from scratch.
+Absolutely not. Many modules are written in pure JS, rust is only needed for performance and security sensitive features like calculating hash
+or compressing image etc.
## NEED HELP WITH:
@@ -122,3 +141,7 @@ Absolutely not. I don't know Rust myself and I have a complete application that
## License
[MIT](https://choosealicense.com/licenses/mit/)
+
+## Star History
+
+[![Star History Chart](https://api.star-history.com/svg?repos=fosslife/devtools-x&type=Date)](https://star-history.com/#fosslife/devtools-x&Date)
diff --git a/features.md b/features.md
new file mode 100644
index 0000000..55995ef
--- /dev/null
+++ b/features.md
@@ -0,0 +1,139 @@
+# Features list
+
+> ignore colors in the videos, it's the compression
+
+## Inbuilt REST api client (basic)
+
+https://github.com/fosslife/devtools-x/assets/24642451/8c937aac-a6ec-401b-9209-16f0c4d9e2e8
+
+## Epoch
+
+https://github.com/fosslife/devtools-x/assets/24642451/182cf0da-4237-4373-a9dd-45a470625e37
+
+## Ping
+
+https://github.com/fosslife/devtools-x/assets/24642451/75549e61-edf9-444b-b452-db28d955162e
+
+## Password Generator
+
+https://github.com/fosslife/devtools-x/assets/24642451/742f1a5e-688f-4fce-ac91-831cc2707034
+
+## QR Code generator
+
+https://github.com/fosslife/devtools-x/assets/24642451/c29879b4-9bbd-47c2-a4c1-d4f99036f7da
+
+## Minify/Beautify code
+
+https://github.com/fosslife/devtools-x/assets/24642451/fc4e4518-4734-433b-96d1-3afacb43cd94
+
+## React live scratchpad
+
+https://github.com/fosslife/devtools-x/assets/24642451/e9236298-54c2-4c67-812e-b873160b6d25
+
+## Lorem Ipsum text generator
+
+https://github.com/fosslife/devtools-x/assets/24642451/c8251476-c1b3-4a5e-9662-b8779969b489
+
+## Image compressor with preview
+
+https://github.com/fosslife/devtools-x/assets/24642451/1989b3ed-156d-4b5c-81f1-ca62dd992e86
+
+## Pastebin
+
+https://github.com/fosslife/devtools-x/assets/24642451/ee5b2f2c-a001-4f28-856f-64d3a98c25eb
+
+## Programming scratchpad for any language
+
+https://github.com/fosslife/devtools-x/assets/24642451/5f6c00a7-c3ba-4ca0-925a-c8cbcc536a1f
+
+## Bulk Image compressor (with SIMD - rust)
+
+https://github.com/fosslife/devtools-x/assets/24642451/9606e383-ae69-4748-83dc-e208b573b7c2
+
+## Base 64 text encoder and decoder
+
+https://github.com/fosslife/devtools-x/assets/24642451/4f4ec732-6e27-42a8-aa89-2fc6d2c37e1c
+
+## Base 64 image encoder and decoder
+
+https://github.com/fosslife/devtools-x/assets/24642451/acbaccd1-b889-48f8-bd00-7d0201672d47
+
+## Multi algorithm Hashing text
+
+https://github.com/fosslife/devtools-x/assets/24642451/3eb6b8b3-339b-4b77-b368-369ef0dbb36f
+
+## Fast MD5 calculator for files
+
+https://github.com/fosslife/devtools-x/assets/24642451/c75bf832-c1b5-438f-b62a-186d613fcf95
+
+## JSON compressor/formatter, etc tools
+
+https://github.com/fosslife/devtools-x/assets/24642451/dbcea322-328b-45cd-839e-31db13cd6819
+
+## JWT decoder
+
+https://github.com/fosslife/devtools-x/assets/24642451/c392f72c-8819-42fd-a512-127aa2caef1d
+
+## Number convertor
+
+https://github.com/fosslife/devtools-x/assets/24642451/b7be56e0-7ab5-4642-b9be-d32242251bc3
+
+## SQL formatter
+
+https://github.com/fosslife/devtools-x/assets/24642451/1c0e1bb4-0714-430c-ac8f-57c30a1c46ef
+
+## Color picker/generator/convertor
+
+https://github.com/fosslife/devtools-x/assets/24642451/21f3ca5d-b846-40cc-8b6f-cddb298045f7
+
+## Diff text/code with syntax highlight
+
+https://github.com/fosslife/devtools-x/assets/24642451/f2fd8f53-fb92-454b-b0bf-34cf5db5675d
+
+## Markdown editor and live preview
+
+https://github.com/fosslife/devtools-x/assets/24642451/43301802-a670-4365-8610-bac2fb884852
+
+## YAML JSON convertor
+
+https://github.com/fosslife/devtools-x/assets/24642451/06cfaa56-ba66-453a-9333-b6047bf53ffd
+
+## Variety of unit convertors
+
+https://github.com/fosslife/devtools-x/assets/24642451/b97119b2-c4b3-477e-890e-b2c3e4f43eee
+
+## Text compression gzip/zlib etc
+
+https://github.com/fosslife/devtools-x/assets/24642451/58efb5e0-2981-4b7f-9e5f-01611f240c46
+
+## Stateless password generator
+
+https://github.com/fosslife/devtools-x/assets/24642451/793df3f1-987c-45f7-8116-95116b328891
+
+## Quicktype types generator from sample json
+
+https://github.com/fosslife/devtools-x/assets/24642451/cc6f213d-ebce-45ed-8065-b4618e045a51
+
+## URL parser and visualizer
+
+https://github.com/fosslife/devtools-x/assets/24642451/39749c42-b839-4c77-899b-609b7877df50
+
+## HTML editor and live preview
+
+https://github.com/fosslife/devtools-x/assets/24642451/9c38b8ae-d240-4fdf-8290-496b3aea29ec
+
+## PDF reader
+
+https://github.com/fosslife/devtools-x/assets/24642451/f8438f00-a918-43bf-92ce-7e582ecb06d8
+
+## Cron tester, generate english meaning
+
+https://github.com/fosslife/devtools-x/assets/24642451/ad4ebc4b-0a9d-42b2-abbd-1056438c9504
+
+## UUID generator
+
+https://github.com/fosslife/devtools-x/assets/24642451/8da9a0e3-1916-41bd-a0c2-64a2cdb52a51
+
+## Regex playground
+
+https://github.com/fosslife/devtools-x/assets/24642451/ad64d256-616d-4ca8-909d-9c3a576ed737
diff --git a/package.json b/package.json
index 0009dfd..9c68f70 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "dev-tools",
- "version": "2.9.0",
+ "version": "2.10.0",
"license": "MIT",
"type": "module",
"scripts": {
@@ -18,6 +18,7 @@
},
"dependencies": {
"@aptabase/tauri": "^0.4.1",
+ "@faker-js/faker": "^8.4.1",
"@hello-pangea/dnd": "^16.6.0",
"@loadable/component": "^5.16.3",
"@mantine/charts": "^7.7.1",
@@ -62,7 +63,7 @@
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
"react-live": "^4.1.5",
- "react-pdf": "^7.7.1",
+ "react-pdf": "^7.7.3",
"react-router-dom": "6.22.3",
"react-shepherd": "^4.3.0",
"react-sizeme": "^3.0.2",
@@ -71,8 +72,7 @@
"tauri-plugin-store-api": "https://github.com/tauri-apps/tauri-plugin-store",
"terser": "^5.30.0",
"typescript-eslint": "^7.4.0",
- "uuid": "^9.0.1",
- "wasm-vips": "^0.0.8"
+ "uuid": "^9.0.1"
},
"devDependencies": {
"@actions/github": "^6.0.0",
diff --git a/scripts/prepare.mjs b/scripts/prepare.mjs
index c2abe13..d08fa13 100644
--- a/scripts/prepare.mjs
+++ b/scripts/prepare.mjs
@@ -1,16 +1 @@
-import fs from "fs/promises";
-
-// copy node_modules/wasm-vips/lib/vips-es6.js to assets/vips-es6.js
-
-await fs.mkdir("assets/vips", { recursive: true });
-
-const vipsEs6 = await fs.readFile("node_modules/wasm-vips/lib/vips.js");
-await fs.writeFile("assets/vips/vips.js", vipsEs6);
-
-const vipsWasm = await fs.readFile("node_modules/wasm-vips/lib/vips.wasm");
-await fs.writeFile("assets/vips/vips.wasm", vipsWasm);
-
-const vipsWorker = await fs.readFile(
- "node_modules/wasm-vips/lib/vips.worker.js"
-);
-await fs.writeFile("assets/vips/vips.worker.js", vipsWorker);
+console.log("done");
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 1811a04..ed2380e 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -784,7 +784,7 @@ dependencies = [
[[package]]
name = "devtools-x"
-version = "2.9.0"
+version = "2.10.0"
dependencies = [
"anyhow",
"base16ct",
@@ -1519,9 +1519,9 @@ dependencies = [
[[package]]
name = "h2"
-version = "0.3.25"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb"
+checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
dependencies = [
"bytes",
"fnv",
@@ -4195,9 +4195,9 @@ checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f"
[[package]]
name = "tauri"
-version = "1.6.1"
+version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f078117725e36d55d29fafcbb4b1e909073807ca328ae8deb8c0b3843aac0fed"
+checksum = "047aefcc7721bfb8024a9bc39d4719112262610502de7a224fa62c4570cd78d4"
dependencies = [
"anyhow",
"base64 0.21.7",
@@ -4212,7 +4212,7 @@ dependencies = [
"glib",
"glob",
"gtk",
- "heck 0.4.1",
+ "heck 0.5.0",
"http",
"ignore",
"indexmap 1.9.3",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 15aa32a..9730c23 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "devtools-x"
-version = "2.9.0"
+version = "2.10.0"
description = "Developer tools desktop application"
authors = ["Sparkenstein"]
license = "MIT"
@@ -15,7 +15,7 @@ tauri-build = { version = "1.5.1", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
-tauri = { version = "1.5.4", features = [ "dialog-ask", "updater", "protocol-all", "http-all", "dialog-open", "dialog-confirm", "clipboard-all", "dialog-save", "fs-all", "devtools"] }
+tauri = { version = "1.6.2", features = [ "dialog-ask", "updater", "protocol-all", "http-all", "dialog-open", "dialog-confirm", "clipboard-all", "dialog-save", "fs-all", "devtools"] }
tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
md-5 = "0.10.5"
base16ct = { version = "0.2.0", features = ["alloc"] }
diff --git a/src-tauri/src/commands/image.rs b/src-tauri/src/commands/image.rs
index b919a17..fb7a7ed 100644
--- a/src-tauri/src/commands/image.rs
+++ b/src-tauri/src/commands/image.rs
@@ -1,6 +1,6 @@
pub mod images {
use anyhow::Result;
- use image::codecs::jpeg::JpegEncoder;
+ use image::{codecs::jpeg::JpegEncoder, ImageEncoder};
use oxipng::{optimize, InFile, Options, OutFile};
use rayon::prelude::*;
use serde::Deserialize;
@@ -8,13 +8,14 @@ pub mod images {
fs::File,
io::{BufWriter, Write},
ops::Deref,
+ path::Path,
};
use tokio::time::Instant;
use webp::Encoder as WebPEncoder;
pub fn compress(img_path: &String, destination: &String, quality: u8) -> Result<()> {
- let path = std::path::Path::new(img_path);
- let destination_path = std::path::Path::new(destination);
+ let path = Path::new(img_path);
+ let destination_path = Path::new(destination);
let img: image::DynamicImage = image::open(path).unwrap();
let width = img.width();
let height = img.height();
@@ -86,7 +87,8 @@ pub mod images {
let parent_start = Instant::now();
images.par_iter().for_each(|image| {
let start = Instant::now();
- let done = compress(image, &destination, quality);
+ let done: std::prelude::v1::Result<(), anyhow::Error> =
+ compress(image, &destination, quality);
match done {
Ok(_) => {
window.emit("image_compressor_progress", image).unwrap();
diff --git a/src-tauri/src/commands/image_compressor.rs b/src-tauri/src/commands/image_compressor.rs
new file mode 100644
index 0000000..5e09ee2
--- /dev/null
+++ b/src-tauri/src/commands/image_compressor.rs
@@ -0,0 +1,74 @@
+pub mod images {
+ use std::{ops::Deref, path::Path};
+
+ use image::{
+ codecs::{
+ jpeg::JpegEncoder,
+ png::{CompressionType, PngEncoder},
+ },
+ ColorType, ImageEncoder,
+ };
+ use serde::{Deserialize, Serialize};
+
+ #[derive(Debug, Deserialize, Serialize)]
+ pub enum ImageFormat {
+ Jpeg,
+ Png,
+ Webp,
+ }
+
+ #[tauri::command]
+ pub async fn compress_images_to_buffer(
+ image_path: String,
+ quality: u8,
+ format: ImageFormat,
+ ) -> Result, String> {
+ let time = std::time::Instant::now();
+ let path = Path::new(&image_path);
+
+ let img = image::open(path).map_err(|e| e.to_string()).unwrap();
+
+ match format {
+ ImageFormat::Jpeg => {
+ let mut writer = Vec::new();
+ let mut encoder = JpegEncoder::new_with_quality(&mut writer, quality);
+ encoder
+ .encode(img.as_bytes(), img.width(), img.height(), ColorType::Rgb8)
+ .map_err(|e| e.to_string())
+ .unwrap();
+ println!("Time: {:?}", time.elapsed());
+ return Ok(writer);
+ }
+ ImageFormat::Png => {
+ let mut writer = Vec::new();
+ // convert quality percentage to CompressionType
+ let compression_level = match quality {
+ 0..=33 => CompressionType::Best,
+ 34..=66 => CompressionType::Default,
+ 67..=100 => CompressionType::Fast,
+ _ => CompressionType::Default,
+ };
+ let encoder = PngEncoder::new_with_quality(
+ &mut writer,
+ compression_level,
+ image::codecs::png::FilterType::Sub,
+ );
+ encoder
+ .write_image(img.as_bytes(), img.width(), img.height(), ColorType::Rgb8)
+ .map_err(|e| e.to_string())
+ .unwrap();
+ println!("Time: {:?}", time.elapsed());
+ return Ok(writer);
+ }
+ ImageFormat::Webp => {
+ let x = webp::Encoder::from_image(&img)
+ .unwrap()
+ .encode(quality as f32);
+ // .map_err(|e| e.to_string())
+ // .unwrap();
+ println!("Time: {:?}", time.elapsed());
+ return Ok(x.to_vec());
+ }
+ }
+ }
+}
diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs
index 5332f94..3b9547a 100644
--- a/src-tauri/src/commands/mod.rs
+++ b/src-tauri/src/commands/mod.rs
@@ -2,5 +2,6 @@ pub mod base64_image;
pub mod compress;
pub mod hash;
pub mod image;
+pub mod image_compressor;
pub mod minify;
pub mod ping;
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 26a35a9..6ac8767 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -13,6 +13,7 @@ use commands::base64_image::base64_image::base64_image;
use commands::compress::compress::compress;
use commands::hash::hash::hash;
use commands::image::images::compress_images;
+use commands::image_compressor::images::compress_images_to_buffer;
use commands::minify::minify::minifyhtml;
use commands::ping::ping::ping;
@@ -26,7 +27,8 @@ fn main() {
minifyhtml,
compress_images,
base64_image,
- compress
+ compress,
+ compress_images_to_buffer
])
.setup(|app| {
WindowBuilder::new(app, "main", WindowUrl::App("index.html".into()))
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 2077ff4..dbec559 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"package": {
"productName": "dev-tools",
- "version": "2.9.0"
+ "version": "2.10.0"
},
"build": {
"distDir": "../dist",
diff --git a/src/App.tsx b/src/App.tsx
index 1e84206..e7e24fc 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -70,6 +70,7 @@ const PdfReader = loadable(() => import("./Features/pdf/PdfReader"));
const Cron = loadable(() => import("./Features/cron/Cron"));
const Ids = loadable(() => import("./Features/ids/Ids"));
const Compress = loadable(() => import("./Features/text/TextCompress"));
+const Faker = loadable(() => import("./Features/faker/Faker"));
const shortCuts = [
{
@@ -229,6 +230,7 @@ function App() {
}>
}>
}>
+ }>
diff --git a/src/Features/faker/Faker.tsx b/src/Features/faker/Faker.tsx
new file mode 100644
index 0000000..46e04bd
--- /dev/null
+++ b/src/Features/faker/Faker.tsx
@@ -0,0 +1,426 @@
+import classes from "./styles.module.css";
+import {
+ ActionIcon,
+ Button,
+ Group,
+ ScrollArea,
+ Select,
+ Stack,
+ TextInput,
+ Divider,
+ rem,
+ Text,
+ NumberInput,
+} from "@mantine/core";
+import { FakerInput } from "./FakerInput";
+import { useCallback, useState } from "react";
+import { faker } from "@faker-js/faker";
+import { Monaco } from "../../Components/MonacoWrapper";
+import {
+ MdOutlineRemove,
+ MdOutlineClose,
+ MdOutlineWarning,
+ MdAdd,
+} from "react-icons/md";
+import { notifications } from "@mantine/notifications";
+import YAML from "js-yaml";
+
+const errorIcon = (
+
+);
+const warningIcon = (
+
+);
+
+/**
+ * Supported output formats
+ */
+const outputFormats = [
+ { format: "json", label: "JSON" },
+ { format: "yaml", label: "YAML" },
+ { format: "sql", label: "SQL" },
+ { format: "csv", label: "CSV" },
+];
+
+/**
+ * Generates a mock data using @faker-js/faker based on given data category and type
+ *
+ * @param category The category for which the fake data falls within
+ * @param dataType The specific data type to be fakes
+ * @param locale The locale to be used for generating mock data
+ * @returns The faker generated data
+ */
+const getMockData = (category: string, dataType: string): any => {
+ if (
+ (faker as any)[category] &&
+ typeof (faker as any)[category][dataType] === "function"
+ ) {
+ const op = (faker as any)[category][dataType]();
+ console.log("generated", op);
+ if (typeof op === "string") return op;
+
+ return JSON.stringify(op);
+ } else {
+ throw new Error(`Invalid category or subset: ${category}.${dataType}`);
+ }
+};
+
+interface Field {
+ fieldName: string;
+ category: string;
+ dataType: string;
+}
+
+export default function Faker() {
+ const [outputFormat, setOutputFormat] = useState("json");
+ const [fields, setFields] = useState([
+ { fieldName: "id", category: "datatype", dataType: "uuid" },
+ ]);
+ const [tableName, setTableName] = useState("table-1");
+ const [rowCount, setRowCount] = useState(10);
+ const [csvDelimiter, setCsvDelimiter] = useState(",");
+ const [output, setOutput] = useState();
+
+ // #region Change Handlers
+ const tableNameChange = (event: any) => {
+ setTableName(event.target.value);
+ };
+
+ const csvDelimiterChange = (event: any) => {
+ setCsvDelimiter(event.target.value);
+ };
+
+ const fieldNameChange = (index: number, name: string | null) => {
+ const updatedFields = [...fields];
+ updatedFields[index].fieldName = name || "";
+ setFields(updatedFields);
+ };
+
+ const fieldCategoryChange = (index: number, category: string | null) => {
+ const updatedFields = [...fields];
+ updatedFields[index].category = category || "";
+ setFields(updatedFields);
+ };
+
+ const fieldDataTypeChange = (index: number, dataType: string | null) => {
+ const updatedFields = [...fields];
+ updatedFields[index].dataType = dataType || "";
+ setFields(updatedFields);
+ };
+ // #endregion
+
+ /**
+ * Adds a new field to the list of fields along with a corresponding FakeInput Compponent
+ */
+ const addField = () => {
+ setFields([...fields, { fieldName: "", category: "", dataType: "" }]);
+ };
+
+ /**
+ * Removes a specific FakerInput component form view and corresponding Field from list of fields
+ *
+ * @param index The index of the field to remove
+ */
+ const removeField = (index: number) => {
+ setFields(fields.filter((_, i) => i !== index));
+ };
+
+ /**
+ * Generates the mock data in the desired output
+ * Determines the output format and uses some helpers and one js-yaml library
+ */
+ const generate = useCallback(async () => {
+ const result = validateFields();
+ if (!result.valid) {
+ showWarning("Validation Error", result.message);
+ return;
+ }
+
+ if (outputFormat === "json" || outputFormat === "yaml") {
+ let data = [];
+ for (let i = 0; i < rowCount; i++) {
+ data.push(objectFromFields(fields));
+ }
+ if (outputFormat !== "yaml") {
+ let input = JSON.stringify(data, undefined, 2);
+ setOutput(input);
+ return;
+ }
+ setOutput(
+ YAML.dump(data, {
+ indent: 2,
+ })
+ );
+ }
+ if (outputFormat === "csv") {
+ let output = "";
+ for (let i = 0; i < rowCount; i++) {
+ output += csvRowFromFields(fields, csvDelimiter) + "\n";
+ }
+ setOutput(output);
+ return;
+ }
+ if (outputFormat == "sql") {
+ let output = "";
+ for (let i = 0; i < rowCount; i++) {
+ output += sqlInsertFromFields(tableName, fields) + "\n";
+ }
+ setOutput(output);
+ return;
+ }
+ }, [rowCount, fields, outputFormat, csvDelimiter, tableName]);
+
+ /**
+ * Creates a Javscript object with mock property values from a list of field
+ *
+ * @param fields
+ * @returns
+ */
+ const objectFromFields = (fields: Field[]) => {
+ let obj: { [key: string]: string } = {};
+ fields.forEach((f) => {
+ try {
+ obj[f.fieldName] = getMockData(f.category, f.dataType);
+ } catch (error) {
+ let message;
+ if (error instanceof Error) message = error.message;
+ else message = String(error);
+ showError("Faker Error", message);
+ }
+ });
+ return obj;
+ };
+
+ /**
+ * Simply quotes a string if it contains the CSV delimeter
+ *
+ * @param value The string value to be made safe
+ * @param delimeter The deleimeter to check for in the string
+ */
+ const csvSafe = (value: string, delimeter: string) => {
+ if (value.includes(delimeter)) return `"${value}"`;
+ return value;
+ };
+
+ /**
+ * Creates a CSV row with mock data from a list of fields
+ *
+ * @param fields The list of fields to be comma-separated
+ * @param delimeter Character to distinguish columns
+ * @returns
+ */
+ const csvRowFromFields = (fields: Field[], delimeter: string) => {
+ let line: string = "";
+ fields.forEach((f) => {
+ try {
+ line += `${csvSafe(getMockData(f.category, f.dataType), delimeter)} ${delimeter}`;
+ } catch (error) {
+ let message;
+ if (error instanceof Error) message = error.message;
+ else message = String(error);
+ showError("Faker Error", message);
+ }
+ });
+ return line.slice(0, -1); // remove trailing delimeter
+ };
+
+ const sqlInsertFromFields = (tableName: string, fields: Field[]) => {
+ let obj = objectFromFields(fields);
+ //array.map(item => `'${item}'`).join(delimiter);
+ return `
+ INSERT INTO ${tableName} (${Object.keys(obj).join(",")})
+ VALUES(${Object.values(obj)
+ .map((item) => `'${item}'`)
+ .join(",")});`;
+ };
+
+ /**
+ * Checks is a not empty, undefined or null
+ *
+ * @param val The string to validate
+ * @returns boolean result of validation
+ */
+ const validString = (val: string | null) => {
+ return ![null, undefined, ""].includes(val);
+ };
+
+ type ValidResult = { valid: true };
+ type InvalidResult = { valid: false; message: string }; // Error message needed only if validation fails
+ type ValidationResult = ValidResult | InvalidResult;
+
+ /**
+ * Validate the list of fields
+ * Check that the list is not empty and that each field's entries are non-empty strings
+ *
+ * @returns ValidationResult Result of the validation. It included and error message if validation is false
+ */
+ const validateFields = (): ValidationResult => {
+ if (fields.length < 1) {
+ return {
+ valid: false,
+ message: "Add at least on field.",
+ };
+ }
+
+ // 3 loops are fine here, need granular control over the error message
+ for (let i = 0; i < fields.length; i++) {
+ if (!validString(fields[i].fieldName)) {
+ return {
+ valid: false,
+ message: `Field ${i + 1} is not valid. missing field name.`,
+ };
+ }
+ }
+ for (let i = 0; i < fields.length; i++) {
+ if (!validString(fields[i].category)) {
+ return {
+ valid: false,
+ message: `Field ${i + 1} is not valid. missing category.`,
+ };
+ }
+ }
+
+ for (let i = 0; i < fields.length; i++) {
+ if (!validString(fields[i].dataType)) {
+ return {
+ valid: false,
+ message: `Field ${i + 1} is not valid. missing data type.`,
+ };
+ }
+ }
+ return { valid: true };
+ };
+
+ /**
+ * Shows error notification in a toast dialog
+ *
+ * @param title The title of the toast
+ * @param message Message to be included the content of the notification
+ */
+ const showError = (title: string, message: string) => {
+ notifications.show({
+ icon: errorIcon,
+ title: title,
+ message: message,
+ color: "red",
+ });
+ };
+
+ /**
+ * Shows warning notification in a toast dialog
+ *
+ * @param title The title of the toast
+ * @param message Message to be included the content of the notification
+ */
+ const showWarning = (title: string, message: string) => {
+ notifications.show({
+ icon: warningIcon,
+ title: title,
+ message: message,
+ color: "orange",
+ });
+ };
+
+ return (
+
+
+
+
+ setRowCount(Number(value))}
+ defaultValue={rowCount}
+ />
+
+
+
+
+
+ {fields.map((item, index) => (
+
+ {index + 1}
+
+ fieldNameChange(index, fieldName)
+ }
+ onCategoryChange={(category: string | null) =>
+ fieldCategoryChange(index, category)
+ }
+ onDataTypeChange={(subset: string | null) =>
+ fieldDataTypeChange(index, subset)
+ }
+ />
+ removeField(index)}
+ variant="default"
+ aria-label="Settings"
+ >
+
+
+
+ ))}
+
+
+ {" "}
+ }>
+ Add{" "}
+
+
+
+
+ l.format === outputFormat)?.format ||
+ "text"
+ }
+ value={output}
+ extraOptions={{
+ readOnly: true,
+ }}
+ />
+
+
+
+
+ );
+}
diff --git a/src/Features/faker/FakerInput.tsx b/src/Features/faker/FakerInput.tsx
new file mode 100644
index 0000000..9504cd7
--- /dev/null
+++ b/src/Features/faker/FakerInput.tsx
@@ -0,0 +1,97 @@
+import React, { useEffect, useState } from "react";
+import { faker } from "@faker-js/faker";
+import { Group, Select, TextInput } from "@mantine/core";
+
+// Function to get categories and their subsets dynamically
+const getCategoriesAndSubsets = () => {
+ const categories: { [key: string]: string[] } = {};
+
+ for (const categoryKey in faker) {
+ if (
+ typeof (faker as any)[categoryKey] === "object" &&
+ (faker as any)[categoryKey] !== null
+ ) {
+ const subsets = Object.keys((faker as any)[categoryKey]).filter(
+ (subsetKey) =>
+ typeof (faker as any)[categoryKey][subsetKey] === "function"
+ );
+ if (subsets.length > 0) {
+ categories[categoryKey] = subsets;
+ }
+ }
+ }
+
+ return categories;
+};
+
+const getCategoryNames = (): string[] => {
+ const categories = getCategoriesAndSubsets();
+ return Object.keys(categories).filter((value) => {
+ return value != "_randomizer" && value != "helpers";
+ });
+};
+
+console.log(getCategoryNames());
+
+const getDataTypesForCategory = (categoryName: string): string[] => {
+ const categories = getCategoriesAndSubsets();
+ return categories[categoryName] || [];
+};
+
+interface FakeInputProps {
+ fieldName: string;
+ category: string;
+ dataType: string;
+ onFieldNameChange: (category: string | null) => void;
+ onCategoryChange: (category: string | null) => void;
+ onDataTypeChange: (dataType: string | null) => void;
+}
+
+export const FakerInput: React.FC = ({
+ fieldName,
+ category,
+ dataType,
+ onFieldNameChange,
+ onCategoryChange,
+ onDataTypeChange,
+}) => {
+ const [dataTypes, setDataTypes] = useState([]);
+
+ useEffect(() => {
+ if (category) {
+ const dataTypes = getDataTypesForCategory(category);
+ setDataTypes(dataTypes);
+ if (!dataTypes.includes(dataType || "")) {
+ onDataTypeChange("");
+ }
+ }
+ }, [category]);
+
+ return (
+ <>
+
+ onFieldNameChange(
+ event.currentTarget.value !== "" ? event.currentTarget.value : null
+ )
+ }
+ />
+