From 726e7a76c3767a966b2be68b80bca946bd0ff0c1 Mon Sep 17 00:00:00 2001 From: Sparkenstein Date: Thu, 11 Jul 2024 09:27:03 +0530 Subject: [PATCH 01/11] feat: rewrote jwt --- src/Features/jwt/JWT.tsx | 177 +++++++++++++++++++++++++++++---------- 1 file changed, 131 insertions(+), 46 deletions(-) diff --git a/src/Features/jwt/JWT.tsx b/src/Features/jwt/JWT.tsx index 6685b03..12937c1 100644 --- a/src/Features/jwt/JWT.tsx +++ b/src/Features/jwt/JWT.tsx @@ -1,59 +1,144 @@ -import { Stack } from "@mantine/core"; -import { decodeJwt, decodeProtectedHeader } from "jose"; -import { useEffect, useState } from "react"; +import { useRef, useState, useEffect } from "react"; +import Editor, { OnMount } from "@monaco-editor/react"; +import { editor } from "monaco-editor"; +import { Group, Stack } from "@mantine/core"; +import { SignJWT, decodeJwt, decodeProtectedHeader } from "jose"; -import { Monaco } from "../../Components/MonacoWrapper"; +/** + * Don't use monaco wrapper for this component + * this is extremly customized component, + * the common wrapper won't fit here unless we change it. + */ +const JWTEditor = () => { + const editorRef = useRef(null); + const [jwt, setJwt] = useState(""); + const [header, setHeader] = useState(""); + const [payload, setPayload] = useState(""); + // TODO: Implement signature + // const [signature, setSignature] = useState(""); -const JWT = () => { - const [jwt, setJwt] = useState({ - token: "", - decoded: {}, - headers: {}, - }); + const handleEditorDidMount: OnMount = (editor, monaco) => { + editorRef.current = editor; + + // Register custom language + monaco.languages.register({ id: "jwt" }); + monaco.languages.setMonarchTokensProvider("jwt", { + tokenizer: { + root: [ + [/^[^.]+/, "jwt-header"], + [/\./, "jwt-dot"], + [/[^.]+(?=\.)/, "jwt-payload"], + [/[^.]+$/, "jwt-signature"], + ], + }, + }); + + import("monaco-themes/themes/Tomorrow-Night.json").then((data: any) => { + monaco.editor.defineTheme("jwtTheme", { + base: "vs-dark", + inherit: false, + rules: [ + { token: "jwt-header", foreground: "fb015b" }, + { token: "jwt-payload", foreground: "d63aff" }, + { token: "jwt-signature", foreground: "00b9f1" }, + { token: "jwt-dot", foreground: "ffffff" }, + ...data.rules, + ], + colors: { + ...data.colors, + }, + }); + monaco.editor.setTheme("jwtTheme"); + }); + }; useEffect(() => { - if (jwt.token) { - try { - let decoded = decodeJwt(jwt.token); - let headers = decodeProtectedHeader(jwt.token); - setJwt({ ...jwt, decoded: decoded, headers: headers }); - } catch { - // ignore error I guess? - setJwt({ token: "", decoded: "Invalid TOken", headers: "" }); - } + (async () => { + const jwt = await new SignJWT({ + foo: "bar", + name: "John Doe", + }) + .setProtectedHeader({ alg: "HS256", typ: "JWT" }) + .setIssuedAt() + .setIssuer("https://jwt.io") + .setAudience("https://jwt.io") + .setExpirationTime("2h") + .setSubject("subject") + .sign(new Uint8Array(32)); + + setJwt(jwt); + })(); + + () => { + editorRef.current?.dispose(); + setJwt(""); + }; + }, []); + + useEffect(() => { + if (jwt) { + const decodedheader = decodeProtectedHeader(jwt); + setHeader(JSON.stringify(decodedheader, null, 2)); + + const decodedPayload = decodeJwt(jwt); + setPayload(JSON.stringify(decodedPayload, null, 2)); } }, [jwt]); return ( - - Token: - setJwt({ ...jwt, token: e || "" })} - /> - Payload: - - Headers: - + setJwt(e || "")} + value={jwt} + options={{ + minimap: { enabled: false }, + lineNumbers: "off", + glyphMargin: false, + folding: true, + lineDecorationsWidth: 0, + wordWrap: "on", + padding: { top: 10 }, + wrappingStrategy: "advanced", + scrollBeyondLastLine: false, + automaticLayout: true, + scrollbar: { vertical: "hidden", horizontal: "hidden" }, + overviewRulerBorder: false, + overviewRulerLanes: 0, + }} + onMount={handleEditorDidMount} /> - + + + + + ); }; -export default JWT; +export default JWTEditor; -// TODO: add token verify? any other features? -// TODO: color jwt +// TODO: Implement signature +// TODO: Token colors From a67b646232648125b6b56ee4e1175afe660abf1d Mon Sep 17 00:00:00 2001 From: Sparkenstein Date: Thu, 11 Jul 2024 09:27:16 +0530 Subject: [PATCH 02/11] chore(doc): add download instructions --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index d60692d..7ed9037 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ So here's DevTools-X -- an x-platform collection of dev-tools that is lighter, s ## Installation +### Compile yourself + Download the relevant package from Github Releases section, and start using it! :D If you prefer compiling your own package, make sure you have all tauri pre-requisites installed: @@ -35,6 +37,17 @@ Then just clone and open the project in terminal and run yarn tauri build ``` +### Download prebuilt binaries + +This project runs a github CI to build binaries for all platforms. Head to [Releases](https://github.com/fosslife/devtools-x/releases) +and download the binary as per your requirements. + +NOTE FOR MACOS USERS, you need this to run the app as binaries are not signed yet. + +```sh +xattr -r -c /Applications/dev-tools.app +``` + ## Acknowledgements This project exists solely because I was fed up switching between different tools on different OSes. Please do star their github repositories, they have inspired many modules in devtools-x From 13c73e452b29fa203788ccd7bf2d317e8916402d Mon Sep 17 00:00:00 2001 From: Sparkenstein Date: Thu, 11 Jul 2024 09:27:34 +0530 Subject: [PATCH 03/11] chore(ui): shortcut styling --- src/App.tsx | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index be8cae6..327623f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import { Box, Drawer, Group, + Kbd, Modal, Stack, Table, @@ -47,7 +48,9 @@ const YamlJson = loadable(() => import("./Features/yaml-json/Yaml")); const Pastebin = loadable(() => import("./Features/pastebin/Pastebin")); const Repl = loadable(() => import("./Features/repl/Repl")); const Image = loadable(() => import("./Features/image/Image")); -const Playground = loadable(() => import("./Features/playground/Playground")); +const Playground = loadable( + () => import("./Features/reactPlayground/Playground") +); const Rest = loadable(() => import("./Features/rest/Rest")); const UnitConverter = loadable( () => import("./Features/unitconverter/UnitConverter") @@ -72,6 +75,7 @@ const Ids = loadable(() => import("./Features/ids/Ids")); const Compress = loadable(() => import("./Features/text/TextCompress")); const Faker = loadable(() => import("./Features/faker/Faker")); const CssPlayground = loadable(() => import("./Features/css/CssPlayground")); +const SvgPreview = loadable(() => import("./Features/svg/Svg")); const shortCuts = [ { @@ -241,6 +245,7 @@ function App() { }> }> }> + }> @@ -281,7 +286,9 @@ function App() { title="Shortcuts and help" > - mod is ctrl on windows/linux + + mod is Ctrl on windows/linux + @@ -292,19 +299,15 @@ function App() { {shortCuts.map((s) => ( - {s.key} + + {s.key} + {s.action} ))}

- Help: - -
    -
  • You can re-order items on the sidebar by dragging
  • -
-
From f561477169335fb09328f9fd675fe891197fb883 Mon Sep 17 00:00:00 2001 From: Sparkenstein Date: Thu, 11 Jul 2024 09:27:52 +0530 Subject: [PATCH 04/11] feat: rest options for monaco --- src/Components/MonacoWrapper.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Components/MonacoWrapper.tsx b/src/Components/MonacoWrapper.tsx index d254a7a..e944767 100644 --- a/src/Components/MonacoWrapper.tsx +++ b/src/Components/MonacoWrapper.tsx @@ -1,4 +1,9 @@ -import Editor, { DiffEditor, DiffOnMount, OnMount } from "@monaco-editor/react"; +import Editor, { + DiffEditor, + DiffOnMount, + OnMount, + EditorProps, +} from "@monaco-editor/react"; import { editor } from "monaco-editor"; type MonacoProps = { @@ -17,7 +22,7 @@ type MonacoProps = { modifiedLanguage: string; originalLanguage: string; }; -}; +} & EditorProps; export const Monaco = ({ value, @@ -30,6 +35,7 @@ export const Monaco = ({ mode, onDiffEditorMounted, diffProps, + ...rest }: MonacoProps) => { const diffOnMount: DiffOnMount = (editor, monaco) => { // disable TS incorrect diagnostic @@ -110,7 +116,7 @@ export const Monaco = ({ return ( ); }; From a6cbac00395306fe7bc1c0548f2140f548930f79 Mon Sep 17 00:00:00 2001 From: Sparkenstein Date: Thu, 11 Jul 2024 09:28:07 +0530 Subject: [PATCH 05/11] chore: rename file --- src/Features/{playground => reactPlayground}/Playground.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Features/{playground => reactPlayground}/Playground.tsx (100%) diff --git a/src/Features/playground/Playground.tsx b/src/Features/reactPlayground/Playground.tsx similarity index 100% rename from src/Features/playground/Playground.tsx rename to src/Features/reactPlayground/Playground.tsx From 37c22180e77e461be880d3671f640b075ebf400d Mon Sep 17 00:00:00 2001 From: Sparkenstein Date: Thu, 11 Jul 2024 09:28:54 +0530 Subject: [PATCH 06/11] feat: svg preview --- src/Features/svg/Svg.tsx | 49 +++++++++++++++++++++++++++++++++++++ src/Layout/Navbar/items.tsx | 9 ++++++- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 src/Features/svg/Svg.tsx diff --git a/src/Features/svg/Svg.tsx b/src/Features/svg/Svg.tsx new file mode 100644 index 0000000..8e36f30 --- /dev/null +++ b/src/Features/svg/Svg.tsx @@ -0,0 +1,49 @@ +import { Box, Button, Group, Stack } from "@mantine/core"; +import { Monaco } from "../../Components/MonacoWrapper"; +import { useState } from "react"; +import { open } from "@tauri-apps/api/dialog"; +import { readTextFile } from "@tauri-apps/api/fs"; + +export default function SvgPreview() { + const [svg, setSvg] = useState(""); + + const readFile = async () => { + const path = await open({ + directory: false, + multiple: false, + title: "Open svg file", + filters: [{ name: "SVG", extensions: ["svg"] }], + }); + + if (path) { + const data = await readTextFile(path as string); + setSvg(data); + } + }; + + return ( + + + + setSvg(e || "")} + /> + + {/* */} + + + ); +} diff --git a/src/Layout/Navbar/items.tsx b/src/Layout/Navbar/items.tsx index 76197df..e1ec2bf 100644 --- a/src/Layout/Navbar/items.tsx +++ b/src/Layout/Navbar/items.tsx @@ -45,6 +45,7 @@ import { VscRegex, } from "react-icons/vsc"; import { NavItem } from "."; +import { TbFileTypeSvg } from "react-icons/tb"; export const navitems: NavItem[] = [ { @@ -268,7 +269,13 @@ export const navitems: NavItem[] = [ text: "HTML Preview", group: "Previewers", }, - + { + id: "svg-preview", + to: "/svg-preview", + icon: , + text: "SVG Preview", + group: "Previewers", + }, { id: "pdf-reader", to: "/pdf-reader", From 7d5a2872a96b3b9755f3b2659c9d89a62e985a42 Mon Sep 17 00:00:00 2001 From: Sparkenstein Date: Thu, 11 Jul 2024 09:45:36 +0530 Subject: [PATCH 07/11] feat: QR code reader --- README.md | 3 +- src-tauri/Cargo.lock | 56 +++++++++++++++++++++++----- src-tauri/Cargo.toml | 1 + src-tauri/src/commands/mod.rs | 1 + src-tauri/src/commands/qr.rs | 25 +++++++++++++ src-tauri/src/main.rs | 4 +- src/App.tsx | 3 ++ src/Features/qrcode/QrCodeReader.tsx | 33 ++++++++++++++++ src/Layout/Navbar/items.tsx | 8 ++++ 9 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 src-tauri/src/commands/qr.rs create mode 100644 src/Features/qrcode/QrCodeReader.tsx diff --git a/README.md b/README.md index 7ed9037..f2d1f10 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ This project exists solely because I was fed up switching between different tool #### Checkout [features.md](features.md) for a short video demo on every feature. -DevTools-X has about **36 features** as of now, and growing. +DevTools-X has about **37 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. @@ -100,6 +100,7 @@ available to you right from the start, as if you are using vscode. 34. Regex Tester 35. Generate mock data with Faker 36. CSS live playground +37. QR Code Reader ## Contributing diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 702cbba..4d4ae99 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -160,6 +160,19 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bardecoder" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757eeab9757d5ddd88aa1420449f3ce92631339b7f5b3785808da7a148de35d9" +dependencies = [ + "anyhow", + "image", + "log", + "newtype_derive", + "thiserror", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -772,7 +785,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version", + "rustc_version 0.4.0", "syn 1.0.109", ] @@ -781,6 +794,7 @@ name = "devtools-x" version = "2.11.1" dependencies = [ "anyhow", + "bardecoder", "base16ct", "base64 0.21.7", "dashmap", @@ -889,7 +903,7 @@ checksum = "c6985554d0688b687c5cb73898a34fbe3ad6c24c58c238a4d91d5e840670ee9d" dependencies = [ "cc", "memchr", - "rustc_version", + "rustc_version 0.4.0", "toml 0.8.12", "vswhom", "winreg 0.52.0", @@ -981,7 +995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ "memoffset", - "rustc_version", + "rustc_version 0.4.0", ] [[package]] @@ -2287,6 +2301,15 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "newtype_derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac8cd24d9f185bb7223958d8c1ff7a961b74b1953fd05dba7cc568a63b3861ec" +dependencies = [ + "rustc_version 0.1.7", +] + [[package]] name = "nix" version = "0.28.0" @@ -2523,7 +2546,7 @@ dependencies = [ "rayon", "rgb", "rustc-hash", - "rustc_version", + "rustc_version 0.4.0", "zopfli", ] @@ -3283,13 +3306,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" +dependencies = [ + "semver 0.1.20", +] + [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver", + "semver 1.0.22", ] [[package]] @@ -3405,6 +3437,12 @@ dependencies = [ "thin-slice", ] +[[package]] +name = "semver" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" + [[package]] name = "semver" version = "1.0.22" @@ -3922,7 +3960,7 @@ dependencies = [ "raw-window-handle", "reqwest", "rfd", - "semver", + "semver 1.0.22", "serde", "serde_json", "serde_repr", @@ -3957,7 +3995,7 @@ dependencies = [ "dirs-next", "heck 0.5.0", "json-patch", - "semver", + "semver 1.0.22", "serde", "serde_json", "tauri-utils", @@ -3979,7 +4017,7 @@ dependencies = [ "png", "proc-macro2", "quote", - "semver", + "semver 1.0.22", "serde", "serde_json", "sha2", @@ -4098,7 +4136,7 @@ dependencies = [ "phf 0.11.2", "proc-macro2", "quote", - "semver", + "semver 1.0.22", "serde", "serde_json", "serde_with", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 4db8539..188bd82 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -38,6 +38,7 @@ webp = "0.2" base64 = "0.21.7" tauri-plugin-aptabase = "0.4" flate2 = "1.0" +bardecoder = "0.5.0" [features] default = ["custom-protocol"] diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 3b9547a..09c2f0e 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -5,3 +5,4 @@ pub mod image; pub mod image_compressor; pub mod minify; pub mod ping; +pub mod qr; diff --git a/src-tauri/src/commands/qr.rs b/src-tauri/src/commands/qr.rs new file mode 100644 index 0000000..7507793 --- /dev/null +++ b/src-tauri/src/commands/qr.rs @@ -0,0 +1,25 @@ +pub mod qr { + + #[tauri::command] + pub fn read_qr(path: String) -> String { + let image = image::open(path).unwrap(); + let decoder = bardecoder::default_decoder(); + let result = decoder.decode(&image); + let mut ret = String::new(); + + for code in result { + match code { + Ok(code) => { + println!("Decoded QR code: {:?}", code); + ret.push_str(&code) + } + Err(e) => { + println!("Error decoding QR code: {:?}", e); + return "".to_string(); + } + } + } + + ret + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6ac8767..4ee79a5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -16,6 +16,7 @@ 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; +use commands::qr::qr::read_qr; fn main() { tauri::Builder::default() @@ -28,7 +29,8 @@ fn main() { compress_images, base64_image, compress, - compress_images_to_buffer + compress_images_to_buffer, + read_qr ]) .setup(|app| { WindowBuilder::new(app, "main", WindowUrl::App("index.html".into())) diff --git a/src/App.tsx b/src/App.tsx index 327623f..2a7db88 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -77,6 +77,8 @@ const Faker = loadable(() => import("./Features/faker/Faker")); const CssPlayground = loadable(() => import("./Features/css/CssPlayground")); const SvgPreview = loadable(() => import("./Features/svg/Svg")); +const QrReadcer = loadable(() => import("./Features/qrcode/QrCodeReader")); + const shortCuts = [ { key: "mod + k", @@ -246,6 +248,7 @@ function App() { }> }> }> + }> diff --git a/src/Features/qrcode/QrCodeReader.tsx b/src/Features/qrcode/QrCodeReader.tsx new file mode 100644 index 0000000..346e1f7 --- /dev/null +++ b/src/Features/qrcode/QrCodeReader.tsx @@ -0,0 +1,33 @@ +import { Button, Group, Stack, Textarea } from "@mantine/core"; +import { invoke } from "@tauri-apps/api"; +import { open } from "@tauri-apps/api/dialog"; +import { useState } from "react"; + +function QrCodeReader() { + const [qrData, setQrData] = useState(null); + + const readQR = async () => { + const path = await open({ + directory: false, + multiple: false, + title: "Select QR image", + filters: [{ name: "Images", extensions: ["png", "jpg", "jpeg"] }], + }); + + if (!path) return; + + // Read QR code + const data = await invoke("read_qr", { path: path as string }); + setQrData(data); + }; + return ( + + + +