├── .cargo └── config.toml ├── .github └── workflows │ └── build-app.yml ├── .gitignore ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── README_CN.md ├── components.json ├── index.html ├── package.json ├── pnpm-lock.yaml ├── src-tauri ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── capabilities │ └── default.json ├── icons │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── 32x32.png │ ├── 64x64.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── StoreLogo.png │ ├── android │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ └── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ └── ios │ │ ├── AppIcon-20x20@1x.png │ │ ├── AppIcon-20x20@2x-1.png │ │ ├── AppIcon-20x20@2x.png │ │ ├── AppIcon-20x20@3x.png │ │ ├── AppIcon-29x29@1x.png │ │ ├── AppIcon-29x29@2x-1.png │ │ ├── AppIcon-29x29@2x.png │ │ ├── AppIcon-29x29@3x.png │ │ ├── AppIcon-40x40@1x.png │ │ ├── AppIcon-40x40@2x-1.png │ │ ├── AppIcon-40x40@2x.png │ │ ├── AppIcon-40x40@3x.png │ │ ├── AppIcon-512@2x.png │ │ ├── AppIcon-60x60@2x.png │ │ ├── AppIcon-60x60@3x.png │ │ ├── AppIcon-76x76@1x.png │ │ ├── AppIcon-76x76@2x.png │ │ └── AppIcon-83.5x83.5@2x.png ├── src │ ├── lib.rs │ ├── main.rs │ └── utility │ │ ├── codec.rs │ │ ├── formatter.rs │ │ ├── generator.rs │ │ └── mod.rs └── tauri.conf.json ├── src ├── app.tsx ├── assets │ └── react.svg ├── components │ ├── sidebar │ │ ├── index.tsx │ │ └── search-form.tsx │ └── ui │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── checkbox.tsx │ │ ├── dropdown-menu.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── radio-group.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── sidebar.tsx │ │ ├── skeleton.tsx │ │ ├── textarea.tsx │ │ └── tooltip.tsx ├── hooks │ └── use-mobile.ts ├── index.css ├── lib │ ├── copyboard.ts │ └── utils.ts ├── main.tsx ├── tauri.ts ├── utilities │ ├── formatter │ │ ├── css.tsx │ │ ├── html.tsx │ │ ├── js.tsx │ │ └── json.tsx │ ├── generators │ │ ├── hash.tsx │ │ └── id.tsx │ └── types.ts └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustdocflags = ["-C", "target-cpu=native"] 3 | rustflags = ["-g", "-C", "target-cpu=native"] 4 | 5 | [bench] 6 | rustdocflags = ["-C", "target-cpu=native"] 7 | rustflags = ["-g", "-C", "target-cpu=native"] -------------------------------------------------------------------------------- /.github/workflows/build-app.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - release 6 | tags: 7 | - 'v*' 8 | workflow_dispatch: 9 | 10 | jobs: 11 | release: 12 | permissions: 13 | contents: write 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | platform: [macos-latest, ubuntu-20.04, windows-latest] 18 | runs-on: ${{ matrix.platform }} 19 | 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v3 23 | 24 | - name: Install dependencies (ubuntu only) 25 | if: matrix.platform == 'ubuntu-20.04' 26 | # You can remove libayatana-appindicator3-dev if you don't use the system tray feature. 27 | run: | 28 | sudo apt-get update 29 | sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev 30 | 31 | - name: Rust setup 32 | uses: dtolnay/rust-toolchain@stable 33 | 34 | - name: Rust cache 35 | uses: swatinem/rust-cache@v2 36 | with: 37 | workspaces: './src-tauri -> target' 38 | 39 | - uses: pnpm/action-setup@v2.4.0 40 | 41 | - name: Sync node version and setup cache 42 | uses: actions/setup-node@v3 43 | with: 44 | node-version: 'lts/*' 45 | cache: 'pnpm' # Set this to npm, yarn or pnpm. 46 | 47 | - name: Install frontend dependencies 48 | # If you don't have `beforeBuildCommand` configured you may want to build your frontend here too. 49 | run: pnpm install # Change this to npm, yarn or pnpm. 50 | 51 | - name: Build the app 52 | uses: tauri-apps/tauri-action@v0 53 | 54 | env: 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | with: 57 | tagName: ${{ github.ref_name }} # This only works if your workflow triggers on new tags. 58 | releaseName: 'App Name v__VERSION__' # tauri-action replaces \_\_VERSION\_\_ with the app version. 59 | releaseBody: 'See the assets to download and install this version.' 60 | releaseDraft: true 61 | prerelease: false 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > 🚧Under development 3 | 4 |

English | 简体中文

5 |

6 | 7 |

8 |

Developer Utility

9 |

All-in-one Utility Box for Developers

10 | 11 |

12 | Stars Badge 13 | Forks Badge 14 | Pull Requests Badge 15 | Issues Badge 16 | License Badge 17 |
18 | 19 |

20 | 21 | 22 | 23 | 24 | ## Tools 25 | 26 | | Tools Name | JavaScript Implementation | Rust Implementation | 27 | | --------------------------- | ---------------------------------- | ------------------------------- | 28 | | Base64 String Encode/Decode | :white_check_mark: | | 29 | | Base64 Image Encode/Decode | :white_check_mark: | | 30 | | GPT Tokenizer | :white_check_mark: `gpt-tokenizer` | :o: `tiktoken-rs` | 31 | | JSON Format/Validate | :white_check_mark: `JSON` | :white_check_mark: `serde_json` | 32 | | JSON ↔ YAML ↔ TOML | :white_check_mark: | | 33 | | JWT Debugger | :white_check_mark: | | 34 | | Unix Time Converter | :white_check_mark: | | 35 | 36 | ## Project Status 37 | 38 | ![Project Status](https://repobeats.axiom.co/api/embed/0529cb5e27377848133ad8196106758f6a908472.svg "Repobeats analytics image") 39 | 40 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > 🚧Under development 3 | 4 |

English | 简体中文

5 |

6 | 7 |

8 |

DevUtils

9 |

开发人员多功能工具箱

10 |
11 | 12 | 13 |
14 | 15 |
16 | 17 | [开始](https://utils.sku.moe) / [技术详情](https://manual.sku.moe/project/devutils) / [GitHub Sponsor](https://github.com/sponsors/AprilNEA) / [爱发电](https://afdian.net/a/aprilnea) 18 | 19 |
20 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/index.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Developer Utility 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "utility-dev", 3 | "private": true, 4 | "version": "0.1.2", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview", 10 | "tauri": "tauri" 11 | }, 12 | "dependencies": { 13 | "@radix-ui/react-checkbox": "^1.3.2", 14 | "@radix-ui/react-dialog": "^1.1.14", 15 | "@radix-ui/react-dropdown-menu": "^2.1.15", 16 | "@radix-ui/react-label": "^2.1.7", 17 | "@radix-ui/react-radio-group": "^1.3.7", 18 | "@radix-ui/react-scroll-area": "^1.2.9", 19 | "@radix-ui/react-select": "^2.2.5", 20 | "@radix-ui/react-separator": "^1.1.7", 21 | "@radix-ui/react-slot": "^1.2.3", 22 | "@radix-ui/react-tooltip": "^1.2.7", 23 | "@tailwindcss/vite": "^4.1.8", 24 | "@tauri-apps/api": "^2", 25 | "@tauri-apps/plugin-clipboard-manager": "~2.2.2", 26 | "@tauri-apps/plugin-opener": "^2", 27 | "class-variance-authority": "^0.7.1", 28 | "clsx": "^2.1.1", 29 | "foxact": "^0.2.45", 30 | "lucide-react": "^0.513.0", 31 | "react": "^18.3.1", 32 | "react-dom": "^18.3.1", 33 | "tailwind-merge": "^3.3.0", 34 | "tailwind-scrollbar": "^4.0.2", 35 | "tailwindcss": "^4.1.8", 36 | "wouter": "^3.7.1" 37 | }, 38 | "devDependencies": { 39 | "@tauri-apps/cli": "^2", 40 | "@types/node": "^22.15.30", 41 | "@types/react": "^18.3.1", 42 | "@types/react-dom": "^18.3.1", 43 | "@vitejs/plugin-react": "^4.3.4", 44 | "tw-animate-css": "^1.3.4", 45 | "typescript": "~5.6.2", 46 | "vite": "^6.0.3" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Generated by Tauri 6 | # will have schema files for capabilities auto-completion 7 | /gen/schemas 8 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "utility-dev" 3 | version = "0.1.2" 4 | description = "A Tauri App" 5 | authors = ["you"] 6 | edition = "2021" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [lib] 11 | # The `_lib` suffix may seem redundant but it is necessary 12 | # to make the lib name unique and wouldn't conflict with the bin name. 13 | # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 14 | name = "utility_dev_lib" 15 | crate-type = ["staticlib", "cdylib", "rlib"] 16 | 17 | [build-dependencies] 18 | tauri-build = { version = "2", features = [] } 19 | 20 | [dependencies] 21 | tauri = { version = "2", features = ["macos-private-api"] } 22 | tauri-plugin-opener = "2" 23 | serde = { version = "1", features = ["derive"] } 24 | serde_json = "1" 25 | window-vibrancy = "0.6.0" 26 | rayon = "1.7" 27 | # ID Generators 28 | uuid-simd = "0.8.0" 29 | uuid = { version = "1.17", features = [ 30 | "v1", 31 | "v3", 32 | "v4", 33 | "v5", 34 | "v6", 35 | "v7", 36 | "v8", 37 | "serde", 38 | ] } 39 | ulid = "1.2.1" 40 | nanoid = "0.4.0" 41 | 42 | # Hash Generators 43 | sha1 = "0.10" 44 | sha2 = "0.10" 45 | sha3 = "0.10" 46 | md2 = "0.10" 47 | md4 = "0.10" 48 | md-5 = "0.10" 49 | digest = "0.10" 50 | hex = "0.4" 51 | tiny-keccak = { version = "2.0", features = ["sha3"] } 52 | 53 | # Formatters 54 | sonic-rs = "0.5.1" 55 | tauri-plugin-clipboard-manager = "2" 56 | 57 | # Encoders 58 | base64 = "0.22.1" 59 | 60 | # Decoders 61 | jsonwebtoken = "9.3.1" 62 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/capabilities/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../gen/schemas/desktop-schema.json", 3 | "identifier": "default", 4 | "description": "Capability for the main window", 5 | "windows": [ 6 | "main" 7 | ], 8 | "permissions": [ 9 | "core:default", 10 | "opener:default", 11 | "core:window:default", 12 | "core:window:allow-start-dragging", 13 | "core:window:allow-set-always-on-top", 14 | "clipboard-manager:default" 15 | ] 16 | } -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/64x64.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-20x20@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-20x20@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-20x20@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-20x20@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-29x29@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-29x29@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-29x29@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-29x29@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-40x40@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-40x40@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-40x40@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-40x40@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-512@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-60x60@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-60x60@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-76x76@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-76x76@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AprilNEA/DevUtils/0db5c090e052c775c71025657169154277011bc1/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png -------------------------------------------------------------------------------- /src-tauri/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all(not(debug_assertions), target_os = "windows"), 3 | windows_subsystem = "windows" 4 | )] 5 | 6 | mod utility; 7 | use tauri::Manager; 8 | use window_vibrancy::*; 9 | 10 | // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ 11 | #[tauri::command] 12 | fn greet(name: &str) -> String { 13 | format!("Hello, {}! You've been greeted from Rust!", name) 14 | } 15 | 16 | #[cfg_attr(mobile, tauri::mobile_entry_point)] 17 | pub fn run() { 18 | tauri::Builder::default() 19 | .plugin(tauri_plugin_clipboard_manager::init()) 20 | .setup(|app| { 21 | let window = app.get_webview_window("main").unwrap(); 22 | 23 | #[cfg(target_os = "macos")] 24 | apply_vibrancy( 25 | &window, 26 | NSVisualEffectMaterial::HudWindow, 27 | Some(NSVisualEffectState::Inactive), 28 | None, 29 | ) 30 | .expect("Unsupported platform! 'apply_vibrancy' is only supported on macOS"); 31 | 32 | #[cfg(target_os = "windows")] 33 | apply_blur(&window, Some((18, 18, 18, 125))) 34 | .expect("Unsupported platform! 'apply_blur' is only supported on Windows"); 35 | 36 | Ok(()) 37 | }) 38 | .plugin(tauri_plugin_opener::init()) 39 | .invoke_handler(tauri::generate_handler![ 40 | greet, 41 | utility::generator::generate_uuid_v4, 42 | utility::generator::generate_uuid_v7, 43 | utility::generator::generate_ulid, 44 | utility::generator::generate_nanoid, 45 | utility::formatter::format_json, 46 | utility::generator::generate_hashes, 47 | ]) 48 | .run(tauri::generate_context!()) 49 | .expect("error while running tauri application"); 50 | } 51 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | fn main() { 5 | utility_dev_lib::run() 6 | } 7 | -------------------------------------------------------------------------------- /src-tauri/src/utility/codec.rs: -------------------------------------------------------------------------------- 1 | use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _}; 2 | use jsonwebtoken::{decode, DecodingKey, Validation}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[tauri::command] 6 | pub fn decode_base64(input: &str) -> Result { 7 | BASE64 8 | .decode(input) 9 | .map_err(|e| e.to_string()) 10 | .and_then(|bytes| String::from_utf8(bytes).map_err(|e| e.to_string())) 11 | } 12 | 13 | #[tauri::command] 14 | pub fn encode_base64(input: &str) -> String { 15 | BASE64.encode(input.as_bytes()) 16 | } 17 | 18 | #[derive(Debug, Serialize, Deserialize)] 19 | pub struct JwtHeader { 20 | pub alg: String, 21 | pub typ: Option, 22 | pub kid: Option, 23 | } 24 | 25 | #[derive(Debug, Serialize, Deserialize)] 26 | pub struct JwtPayload { 27 | #[serde(flatten)] 28 | pub claims: serde_json::Value, 29 | } 30 | 31 | #[derive(Debug, Serialize, Deserialize)] 32 | pub struct JwtDecodeResult { 33 | pub header: JwtHeader, 34 | pub payload: JwtPayload, 35 | pub signature: String, 36 | } 37 | 38 | #[tauri::command] 39 | pub fn decode_jwt(token: &str) -> Result { 40 | let parts: Vec<&str> = token.split('.').collect(); 41 | if parts.len() != 3 { 42 | return Err("Invalid JWT format".to_string()); 43 | } 44 | 45 | let header = base64::decode(parts[0]) 46 | .map_err(|_| "Invalid header encoding".to_string()) 47 | .and_then(|bytes| { 48 | serde_json::from_slice::(&bytes) 49 | .map_err(|_| "Invalid header format".to_string()) 50 | })?; 51 | 52 | let payload = base64::decode(parts[1]) 53 | .map_err(|_| "Invalid payload encoding".to_string()) 54 | .and_then(|bytes| { 55 | serde_json::from_slice::(&bytes) 56 | .map_err(|_| "Invalid payload format".to_string()) 57 | })?; 58 | 59 | let signature = parts[2].to_string(); 60 | 61 | Ok(JwtDecodeResult { 62 | header, 63 | payload, 64 | signature, 65 | }) 66 | } 67 | 68 | #[tauri::command] 69 | pub fn verify_jwt(token: &str, secret: &str) -> Result { 70 | let validation = Validation::default(); 71 | let key = DecodingKey::from_secret(secret.as_bytes()); 72 | 73 | match decode::(token, &key, &validation) { 74 | Ok(_) => decode_jwt(token), 75 | Err(e) => Err(e.to_string()), 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src-tauri/src/utility/formatter.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use sonic_rs; 3 | 4 | #[derive(Debug, Clone, Serialize, Deserialize)] 5 | #[serde(rename_all = "lowercase")] 6 | pub enum IndentStyle { 7 | Spaces(usize), 8 | Tabs, 9 | Minified, 10 | } 11 | 12 | #[tauri::command] 13 | pub fn format_json(input: &str, style: IndentStyle) -> String { 14 | let value: sonic_rs::Value = sonic_rs::from_str(input).unwrap(); 15 | // let pretty_str = sonic_rs::to_string_pretty(&value).unwrap_or_else(|_| "{}".to_string()); 16 | match style { 17 | IndentStyle::Minified => sonic_rs::to_string(&value).unwrap_or_else(|_| "{}".to_string()), 18 | IndentStyle::Spaces(size) => sonic_rs::to_string_pretty(&value) 19 | .unwrap_or_else(|_| "{}".to_string()) 20 | .lines() 21 | .map(|line| { 22 | if size == 2 { 23 | return line.to_string(); 24 | } 25 | let leading_spaces = line.len() - line.trim_start().len(); 26 | let indent_level = leading_spaces / 2; 27 | 28 | format!("{}{}", " ".repeat(indent_level * size), line.trim_start()) 29 | }) 30 | .collect::>() 31 | .join("\n"), 32 | IndentStyle::Tabs => sonic_rs::to_string_pretty(&value) 33 | .unwrap_or_else(|_| "{}".to_string()) 34 | .lines() 35 | .map(|line| { 36 | let leading_spaces = line.len() - line.trim_start().len(); 37 | let indent_level = leading_spaces / 2; // Default is 2-space indentation 38 | format!("{}{}", "\t".repeat(indent_level), line.trim_start()) 39 | }) 40 | .collect::>() 41 | .join("\n"), 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src-tauri/src/utility/generator.rs: -------------------------------------------------------------------------------- 1 | use digest::Digest; 2 | use hex; 3 | use nanoid::nanoid; 4 | use rayon::prelude::*; 5 | use serde::{Deserialize, Serialize}; 6 | use std::sync::Arc; 7 | use std::time::{SystemTime, UNIX_EPOCH}; 8 | use ulid::Ulid; 9 | use uuid::{Timestamp, Uuid}; 10 | 11 | #[tauri::command] 12 | pub fn generate_uuid_v4(count: u32) -> String { 13 | (0..count) 14 | .map(|_| Uuid::new_v4().to_string()) 15 | .collect::>() 16 | .join("\n") 17 | } 18 | 19 | #[tauri::command] 20 | pub fn generate_uuid_v7(count: u32, timestamp: Option) -> String { 21 | let context = uuid::Context::new_random(); 22 | 23 | let base_seconds = timestamp.unwrap_or_else(|| { 24 | let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 25 | now.as_secs() 26 | }); 27 | (0..count) 28 | .map(|i| { 29 | let nanos = i; 30 | let time = Timestamp::from_unix(&context, base_seconds, nanos); 31 | Uuid::new_v7(time).to_string() 32 | }) 33 | .collect::>() 34 | .join("7\n") 35 | } 36 | 37 | #[tauri::command] 38 | pub fn generate_ulid(count: u32) -> String { 39 | (0..count) 40 | .map(|_| Ulid::new().to_string()) 41 | .collect::>() 42 | .join("\n") 43 | } 44 | 45 | #[tauri::command] 46 | pub fn generate_nanoid(count: u32) -> String { 47 | (0..count) 48 | .map(|_| nanoid!()) 49 | .collect::>() 50 | .join("\n") 51 | } 52 | 53 | #[derive(Debug, Serialize, Deserialize)] 54 | pub struct HashResult { 55 | pub md2: String, 56 | pub md4: String, 57 | pub md5: String, 58 | pub sha1: String, 59 | pub sha224: String, 60 | pub sha256: String, 61 | pub sha384: String, 62 | pub sha512: String, 63 | pub sha3_256: String, 64 | pub keccak256: String, 65 | } 66 | 67 | fn hash_with_algorithm(data: &[u8]) -> String { 68 | let mut hasher = D::new(); 69 | hasher.update(data); 70 | hex::encode(hasher.finalize()) 71 | } 72 | 73 | #[tauri::command] 74 | pub fn generate_hashes(input: &str) -> HashResult { 75 | let data = Arc::new(input.as_bytes().to_vec()); 76 | 77 | let tasks: Vec<(&str, Box String + Send + Sync>)> = vec![ 78 | ( 79 | "MD2", 80 | Box::new(|data| hash_with_algorithm::(data)), 81 | ), 82 | ( 83 | "MD5", 84 | Box::new(|data| hash_with_algorithm::(data)), 85 | ), 86 | ( 87 | "MD4", 88 | Box::new(|data| hash_with_algorithm::(data)), 89 | ), 90 | ( 91 | "SHA1", 92 | Box::new(|data| hash_with_algorithm::(data)), 93 | ), 94 | ( 95 | "SHA224", 96 | Box::new(|data| hash_with_algorithm::(data)), 97 | ), 98 | ( 99 | "SHA256", 100 | Box::new(|data| hash_with_algorithm::(data)), 101 | ), 102 | ( 103 | "SHA384", 104 | Box::new(|data| hash_with_algorithm::(data)), 105 | ), 106 | ( 107 | "SHA512", 108 | Box::new(|data| hash_with_algorithm::(data)), 109 | ), 110 | ( 111 | "SHA3-256", 112 | Box::new(|data| hash_with_algorithm::(data)), 113 | ), 114 | ( 115 | "Keccak256", 116 | Box::new(|data| hash_with_algorithm::(data)), 117 | ), 118 | ]; 119 | 120 | let results: Vec<(String, String)> = tasks 121 | .into_par_iter() 122 | .map(|(name, hasher)| { 123 | let hash = hasher(&data); 124 | (name.to_string(), hash) 125 | }) 126 | .collect(); 127 | 128 | let mut hash_map = std::collections::HashMap::new(); 129 | for (name, hash) in results { 130 | hash_map.insert(name, hash); 131 | } 132 | 133 | HashResult { 134 | md2: hash_map.get("MD2").unwrap().clone(), 135 | md4: hash_map.get("MD4").unwrap().clone(), 136 | md5: hash_map.get("MD5").unwrap().clone(), 137 | sha1: hash_map.get("SHA1").unwrap().clone(), 138 | sha224: hash_map.get("SHA224").unwrap().clone(), 139 | sha256: hash_map.get("SHA256").unwrap().clone(), 140 | sha384: hash_map.get("SHA384").unwrap().clone(), 141 | sha512: hash_map.get("SHA512").unwrap().clone(), 142 | sha3_256: hash_map.get("SHA3-256").unwrap().clone(), 143 | keccak256: hash_map.get("Keccak256").unwrap().clone(), 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src-tauri/src/utility/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod codec; 2 | pub mod formatter; 3 | pub mod generator; 4 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.tauri.app/config/2", 3 | "productName": "utility-dev", 4 | "version": "0.1.0", 5 | "identifier": "dev.utility.app", 6 | "build": { 7 | "beforeDevCommand": "pnpm dev", 8 | "devUrl": "http://localhost:1420", 9 | "beforeBuildCommand": "pnpm build", 10 | "frontendDist": "../dist" 11 | }, 12 | "app": { 13 | "withGlobalTauri": true, 14 | "windows": [ 15 | { 16 | "title": "", 17 | "titleBarStyle": "Overlay", 18 | "width": 1200, 19 | "height": 800, 20 | "transparent": true 21 | } 22 | ], 23 | "macOSPrivateApi": true, 24 | "security": { 25 | "csp": null 26 | } 27 | }, 28 | "bundle": { 29 | "active": true, 30 | "category": "Utility", 31 | "targets": "all", 32 | "icon": [ 33 | "icons/32x32.png", 34 | "icons/128x128.png", 35 | "icons/128x128@2x.png", 36 | "icons/icon.icns", 37 | "icons/icon.ico" 38 | ] 39 | } 40 | } -------------------------------------------------------------------------------- /src/app.tsx: -------------------------------------------------------------------------------- 1 | import "./index.css"; 2 | 3 | import { Router, Route } from "wouter"; 4 | 5 | import AppSidebar from "./components/sidebar"; 6 | 7 | import JsonFormatterPage from "./utilities/formatter/json"; 8 | import HtmlEncoderDecoderPage from "./utilities/formatter/html"; 9 | import { CssBeautifyMinifyTool } from "./utilities/formatter/css"; 10 | 11 | import IdGeneratorPage from "./utilities/generators/id"; 12 | import HashGeneratorPage from "./utilities/generators/hash"; 13 | 14 | function App() { 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | } 31 | 32 | export default App; 33 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/sidebar/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { getCurrentWindow } from "@tauri-apps/api/window"; 3 | import { 4 | Sidebar, 5 | SidebarContent, 6 | SidebarGroup, 7 | SidebarGroupContent, 8 | SidebarGroupLabel, 9 | SidebarHeader, 10 | SidebarInset, 11 | SidebarMenu, 12 | SidebarMenuButton, 13 | SidebarMenuItem, 14 | SidebarProvider, 15 | SidebarRail, 16 | SidebarTrigger, 17 | } from "@/components/ui/sidebar"; 18 | import { Separator } from "@radix-ui/react-separator"; 19 | import { Link, useLocation } from "wouter"; 20 | import { 21 | FileJsonIcon, 22 | FileCodeIcon, 23 | FileTextIcon, 24 | FileTypeIcon, 25 | FileCode2Icon, 26 | FileIcon, 27 | FileArchiveIcon, 28 | ListOrderedIcon, 29 | LinkIcon, 30 | BinaryIcon, 31 | TableIcon, 32 | Code2Icon, 33 | TypeIcon, 34 | TerminalIcon, 35 | CodeIcon, 36 | HashIcon, 37 | TextIcon, 38 | QrCodeIcon, 39 | PaletteIcon, 40 | CalendarIcon, 41 | SearchIcon, 42 | DiffIcon, 43 | ClockIcon, 44 | KeyIcon, 45 | RegexIcon, 46 | ImageIcon, 47 | PinIcon, 48 | PinOffIcon, 49 | } from "lucide-react"; 50 | import { SearchForm } from "./search-form"; 51 | import { useState } from "react"; 52 | import { Button } from "../ui/button"; 53 | import { useSidebar } from "../ui/sidebar"; 54 | import { cn } from "@/lib/utils"; 55 | 56 | // This is sample data. 57 | const navMain = [ 58 | { 59 | title: "Format / Validate / Minify", 60 | url: "formatter", 61 | items: [ 62 | { 63 | title: "JSON Format/Validate", 64 | url: "json", 65 | icon: FileJsonIcon, 66 | }, 67 | { 68 | title: "HTML Beautify/Minify", 69 | url: "html", 70 | icon: FileCodeIcon, 71 | }, 72 | { 73 | title: "CSS Beautify/Minify", 74 | url: "css", 75 | icon: FileIcon, 76 | }, 77 | { 78 | title: "JS Beautify/Minify", 79 | url: "js", 80 | icon: FileCode2Icon, 81 | }, 82 | // { 83 | // title: "ERB Beautify/Minify", 84 | // url: "erb", 85 | // icon: FileTypeIcon, 86 | // }, 87 | // { 88 | // title: "LESS Beautify/Minify", 89 | // url: "less", 90 | // icon: FileIcon, 91 | // }, 92 | // { 93 | // title: "SCSS Beautify/Minify", 94 | // url: "scss", 95 | // icon: FileIcon, 96 | // }, 97 | // { 98 | // title: "XML Beautify/Minify", 99 | // url: "xml", 100 | // icon: FileCodeIcon, 101 | // }, 102 | // { 103 | // title: "SQL Formatter", 104 | // url: "sql", 105 | // icon: FileIcon, 106 | // }, 107 | // { 108 | // title: "Line Sort/Dedupe", 109 | // url: "lines", 110 | // icon: ListOrderedIcon, 111 | // }, 112 | ], 113 | }, 114 | { 115 | title: "Data Converter", 116 | url: "#", 117 | items: [ 118 | { 119 | title: "URL Parser", 120 | url: "url-parser", 121 | icon: LinkIcon, 122 | }, 123 | // { 124 | // title: "YAML to JSON", 125 | // url: "yaml-to-json", 126 | // icon: FileIcon, 127 | // }, 128 | // { 129 | // title: "JSON to YAML", 130 | // url: "json-to-yaml", 131 | // icon: FileJsonIcon, 132 | // }, 133 | // { 134 | // title: "Number Base Converter", 135 | // url: "number-base-converter", 136 | // icon: BinaryIcon, 137 | // }, 138 | // { 139 | // title: "JSON to CSV", 140 | // url: "json-to-csv", 141 | // icon: TableIcon, 142 | // }, 143 | // { 144 | // title: "CSV to JSON", 145 | // url: "csv-to-json", 146 | // icon: TableIcon, 147 | // }, 148 | // { 149 | // title: "HTML to JSX", 150 | // url: "html-to-jsx", 151 | // icon: Code2Icon, 152 | // }, 153 | // { 154 | // title: "String Case Converter", 155 | // url: "string-case-converter", 156 | // icon: TypeIcon, 157 | // }, 158 | // { 159 | // title: "PHP to JSON", 160 | // url: "php-to-json", 161 | // icon: FileCodeIcon, 162 | // }, 163 | // { 164 | // title: "JSON to PHP", 165 | // url: "json-to-php", 166 | // icon: FileJsonIcon, 167 | // }, 168 | // { 169 | // title: "PHP Serializer", 170 | // url: "php-serializer", 171 | // icon: FileArchiveIcon, 172 | // }, 173 | // { 174 | // title: "PHP Unserializer", 175 | // url: "php-unserializer", 176 | // icon: FileArchiveIcon, 177 | // }, 178 | // { 179 | // title: "SVG to CSS", 180 | // url: "svg-to-css", 181 | // icon: FileIcon, 182 | // }, 183 | // { 184 | // title: "cURL to Code", 185 | // url: "curl-to-code", 186 | // icon: TerminalIcon, 187 | // }, 188 | // { 189 | // title: "JSON to Code", 190 | // url: "json-to-code", 191 | // icon: CodeIcon, 192 | // }, 193 | // { 194 | // title: "Hex to ASCII", 195 | // url: "hex-to-ascii", 196 | // icon: HashIcon, 197 | // }, 198 | // { 199 | // title: "ASCII to Hex", 200 | // url: "ascii-to-hex", 201 | // icon: TextIcon, 202 | // }, 203 | ], 204 | }, 205 | { 206 | title: "Inspect, Preview, Debug", 207 | items: [ 208 | { 209 | title: "Unix Time Converter", 210 | url: "unix-time-converter", 211 | icon: ClockIcon, 212 | }, 213 | { 214 | title: "JWT Debugger", 215 | url: "jwt-debugger", 216 | icon: KeyIcon, 217 | }, 218 | { 219 | title: "RegExp Tester", 220 | url: "regexp-tester", 221 | icon: RegexIcon, 222 | }, 223 | { 224 | title: "HTML Preview", 225 | url: "html-preview", 226 | icon: FileCodeIcon, 227 | }, 228 | { 229 | title: "Text Diff Checker", 230 | url: "text-diff-checker", 231 | icon: DiffIcon, 232 | }, 233 | { 234 | title: "String Inspector", 235 | url: "string-inspector", 236 | icon: SearchIcon, 237 | }, 238 | { 239 | title: "Markdown Preview", 240 | url: "markdown-preview", 241 | icon: FileTextIcon, 242 | }, 243 | { 244 | title: "Cron Job Parser", 245 | url: "cron-job-parser", 246 | icon: CalendarIcon, 247 | }, 248 | { 249 | title: "Color Converter", 250 | url: "color-converter", 251 | icon: PaletteIcon, 252 | }, 253 | ], 254 | }, 255 | { 256 | title: "Generators", 257 | url: "generator", 258 | items: [ 259 | { 260 | title: "UUID/ULID Generate/Decode", 261 | url: "id", 262 | icon: HashIcon, 263 | }, 264 | { 265 | title: "Lorem Ipsum Generator", 266 | url: "lorem-ipsum-generator", 267 | icon: TextIcon, 268 | }, 269 | { 270 | title: "QR Code Reader/Generator", 271 | url: "qr-code-generator", 272 | icon: QrCodeIcon, 273 | }, 274 | { 275 | title: "Hash Generator", 276 | url: "hash", 277 | icon: HashIcon, 278 | }, 279 | { 280 | title: "Random String Generator", 281 | url: "random-string-generator", 282 | icon: TextIcon, 283 | }, 284 | ], 285 | }, 286 | { 287 | title: "Encoder, Decoder", 288 | items: [ 289 | { 290 | title: "Base64 String Encode/Decode", 291 | url: "base64-string", 292 | icon: FileTextIcon, 293 | }, 294 | { 295 | title: "Base64 Image Encode/Decode", 296 | url: "base64-image", 297 | icon: ImageIcon, 298 | }, 299 | { 300 | title: "URL Encode/Decode", 301 | url: "url-encoder", 302 | icon: LinkIcon, 303 | }, 304 | { 305 | title: "HTML Entity Encode/Decode", 306 | url: "html-entity", 307 | icon: FileCodeIcon, 308 | }, 309 | { 310 | title: "Backslash Escape/Unescape", 311 | url: "backslash-escape", 312 | icon: TextIcon, 313 | }, 314 | { 315 | title: "Certificate Decoder (X.509)", 316 | url: "certificate-decoder", 317 | icon: FileTextIcon, 318 | }, 319 | ], 320 | }, 321 | ]; 322 | 323 | const InsetHeader: React.FC<{ title: string }> = ({ title }) => { 324 | const { open } = useSidebar(); 325 | 326 | const setOnTop = async () => { 327 | await getCurrentWindow().setAlwaysOnTop(!isOnTop); 328 | setIsOnTop(!isOnTop); 329 | }; 330 | const [isOnTop, setIsOnTop] = useState(false); 331 | 332 | return ( 333 |
337 | 338 | 342 |
346 |
347 |
348 | {title} 349 |
350 |
351 |
352 | 353 | 356 |
357 | ); 358 | }; 359 | 360 | export default function AppSidebar({ 361 | children, 362 | ...props 363 | }: React.ComponentProps) { 364 | const [title, setTitle] = useState("Developer Utility"); 365 | const [pathname] = useLocation(); 366 | 367 | return ( 368 | 369 | 370 | 371 | 372 | 373 | 374 | {/* We create a SidebarGroup for each parent. */} 375 | {navMain.map((category) => ( 376 | 377 | {category.title} 378 | 379 | 380 | {category.items.map((util) => { 381 | const href = `/${category.url}/${util.url}`; 382 | return ( 383 | 384 | 385 | setTitle(util.title)} 389 | > 390 | 391 | {util.title} 392 | 393 | 394 | 395 | ); 396 | })} 397 | 398 | 399 | 400 | ))} 401 | 402 | 403 | 404 | 405 | 406 |
407 | {children} 408 |
409 |
410 |
411 | ); 412 | } 413 | -------------------------------------------------------------------------------- /src/components/sidebar/search-form.tsx: -------------------------------------------------------------------------------- 1 | import { Search } from "lucide-react" 2 | 3 | import { Label } from "@/components/ui/label" 4 | import { 5 | SidebarGroup, 6 | SidebarGroupContent, 7 | SidebarInput, 8 | } from "@/components/ui/sidebar" 9 | 10 | export function SearchForm({ ...props }: React.ComponentProps<"form">) { 11 | return ( 12 |
13 | 14 | 15 | 18 | 23 | 24 | 25 | 26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 16 | outline: 17 | "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", 20 | ghost: 21 | "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", 22 | link: "text-primary underline-offset-4 hover:underline", 23 | }, 24 | size: { 25 | default: "h-9 px-4 py-2 has-[>svg]:px-3", 26 | sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", 27 | lg: "h-10 rounded-md px-6 has-[>svg]:px-4", 28 | icon: "size-9", 29 | }, 30 | }, 31 | defaultVariants: { 32 | variant: "default", 33 | size: "default", 34 | }, 35 | } 36 | ) 37 | 38 | function Button({ 39 | className, 40 | variant, 41 | size, 42 | asChild = false, 43 | ...props 44 | }: React.ComponentProps<"button"> & 45 | VariantProps & { 46 | asChild?: boolean 47 | }) { 48 | const Comp = asChild ? Slot : "button" 49 | 50 | return ( 51 | 56 | ) 57 | } 58 | 59 | export { Button, buttonVariants } 60 | -------------------------------------------------------------------------------- /src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Card({ className, ...props }: React.ComponentProps<"div">) { 6 | return ( 7 |
15 | ) 16 | } 17 | 18 | function CardHeader({ className, ...props }: React.ComponentProps<"div">) { 19 | return ( 20 |
28 | ) 29 | } 30 | 31 | function CardTitle({ className, ...props }: React.ComponentProps<"div">) { 32 | return ( 33 |
38 | ) 39 | } 40 | 41 | function CardDescription({ className, ...props }: React.ComponentProps<"div">) { 42 | return ( 43 |
48 | ) 49 | } 50 | 51 | function CardAction({ className, ...props }: React.ComponentProps<"div">) { 52 | return ( 53 |
61 | ) 62 | } 63 | 64 | function CardContent({ className, ...props }: React.ComponentProps<"div">) { 65 | return ( 66 |
71 | ) 72 | } 73 | 74 | function CardFooter({ className, ...props }: React.ComponentProps<"div">) { 75 | return ( 76 |
81 | ) 82 | } 83 | 84 | export { 85 | Card, 86 | CardHeader, 87 | CardFooter, 88 | CardTitle, 89 | CardAction, 90 | CardDescription, 91 | CardContent, 92 | } 93 | -------------------------------------------------------------------------------- /src/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox" 3 | import { CheckIcon } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | function Checkbox({ 8 | className, 9 | ...props 10 | }: React.ComponentProps) { 11 | return ( 12 | 20 | 24 | 25 | 26 | 27 | ) 28 | } 29 | 30 | export { Checkbox } 31 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 3 | import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | function DropdownMenu({ 8 | ...props 9 | }: React.ComponentProps) { 10 | return 11 | } 12 | 13 | function DropdownMenuPortal({ 14 | ...props 15 | }: React.ComponentProps) { 16 | return ( 17 | 18 | ) 19 | } 20 | 21 | function DropdownMenuTrigger({ 22 | ...props 23 | }: React.ComponentProps) { 24 | return ( 25 | 29 | ) 30 | } 31 | 32 | function DropdownMenuContent({ 33 | className, 34 | sideOffset = 4, 35 | ...props 36 | }: React.ComponentProps) { 37 | return ( 38 | 39 | 48 | 49 | ) 50 | } 51 | 52 | function DropdownMenuGroup({ 53 | ...props 54 | }: React.ComponentProps) { 55 | return ( 56 | 57 | ) 58 | } 59 | 60 | function DropdownMenuItem({ 61 | className, 62 | inset, 63 | variant = "default", 64 | ...props 65 | }: React.ComponentProps & { 66 | inset?: boolean 67 | variant?: "default" | "destructive" 68 | }) { 69 | return ( 70 | 80 | ) 81 | } 82 | 83 | function DropdownMenuCheckboxItem({ 84 | className, 85 | children, 86 | checked, 87 | ...props 88 | }: React.ComponentProps) { 89 | return ( 90 | 99 | 100 | 101 | 102 | 103 | 104 | {children} 105 | 106 | ) 107 | } 108 | 109 | function DropdownMenuRadioGroup({ 110 | ...props 111 | }: React.ComponentProps) { 112 | return ( 113 | 117 | ) 118 | } 119 | 120 | function DropdownMenuRadioItem({ 121 | className, 122 | children, 123 | ...props 124 | }: React.ComponentProps) { 125 | return ( 126 | 134 | 135 | 136 | 137 | 138 | 139 | {children} 140 | 141 | ) 142 | } 143 | 144 | function DropdownMenuLabel({ 145 | className, 146 | inset, 147 | ...props 148 | }: React.ComponentProps & { 149 | inset?: boolean 150 | }) { 151 | return ( 152 | 161 | ) 162 | } 163 | 164 | function DropdownMenuSeparator({ 165 | className, 166 | ...props 167 | }: React.ComponentProps) { 168 | return ( 169 | 174 | ) 175 | } 176 | 177 | function DropdownMenuShortcut({ 178 | className, 179 | ...props 180 | }: React.ComponentProps<"span">) { 181 | return ( 182 | 190 | ) 191 | } 192 | 193 | function DropdownMenuSub({ 194 | ...props 195 | }: React.ComponentProps) { 196 | return 197 | } 198 | 199 | function DropdownMenuSubTrigger({ 200 | className, 201 | inset, 202 | children, 203 | ...props 204 | }: React.ComponentProps & { 205 | inset?: boolean 206 | }) { 207 | return ( 208 | 217 | {children} 218 | 219 | 220 | ) 221 | } 222 | 223 | function DropdownMenuSubContent({ 224 | className, 225 | ...props 226 | }: React.ComponentProps) { 227 | return ( 228 | 236 | ) 237 | } 238 | 239 | export { 240 | DropdownMenu, 241 | DropdownMenuPortal, 242 | DropdownMenuTrigger, 243 | DropdownMenuContent, 244 | DropdownMenuGroup, 245 | DropdownMenuLabel, 246 | DropdownMenuItem, 247 | DropdownMenuCheckboxItem, 248 | DropdownMenuRadioGroup, 249 | DropdownMenuRadioItem, 250 | DropdownMenuSeparator, 251 | DropdownMenuShortcut, 252 | DropdownMenuSub, 253 | DropdownMenuSubTrigger, 254 | DropdownMenuSubContent, 255 | } 256 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) { 6 | return ( 7 | 18 | ) 19 | } 20 | 21 | export { Input } 22 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | function Label({ 7 | className, 8 | ...props 9 | }: React.ComponentProps) { 10 | return ( 11 | 19 | ) 20 | } 21 | 22 | export { Label } 23 | -------------------------------------------------------------------------------- /src/components/ui/radio-group.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" 5 | import { CircleIcon } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | function RadioGroup({ 10 | className, 11 | ...props 12 | }: React.ComponentProps) { 13 | return ( 14 | 19 | ) 20 | } 21 | 22 | function RadioGroupItem({ 23 | className, 24 | ...props 25 | }: React.ComponentProps) { 26 | return ( 27 | 35 | 39 | 40 | 41 | 42 | ) 43 | } 44 | 45 | export { RadioGroup, RadioGroupItem } 46 | -------------------------------------------------------------------------------- /src/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | function ScrollArea({ 7 | className, 8 | children, 9 | ...props 10 | }: React.ComponentProps) { 11 | return ( 12 | 17 | 21 | {children} 22 | 23 | 24 | 25 | 26 | ) 27 | } 28 | 29 | function ScrollBar({ 30 | className, 31 | orientation = "vertical", 32 | ...props 33 | }: React.ComponentProps) { 34 | return ( 35 | 48 | 52 | 53 | ) 54 | } 55 | 56 | export { ScrollArea, ScrollBar } 57 | -------------------------------------------------------------------------------- /src/components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SelectPrimitive from "@radix-ui/react-select" 5 | import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | function Select({ 10 | ...props 11 | }: React.ComponentProps) { 12 | return 13 | } 14 | 15 | function SelectGroup({ 16 | ...props 17 | }: React.ComponentProps) { 18 | return 19 | } 20 | 21 | function SelectValue({ 22 | ...props 23 | }: React.ComponentProps) { 24 | return 25 | } 26 | 27 | function SelectTrigger({ 28 | className, 29 | size = "default", 30 | children, 31 | ...props 32 | }: React.ComponentProps & { 33 | size?: "sm" | "default" 34 | }) { 35 | return ( 36 | 45 | {children} 46 | 47 | 48 | 49 | 50 | ) 51 | } 52 | 53 | function SelectContent({ 54 | className, 55 | children, 56 | position = "popper", 57 | ...props 58 | }: React.ComponentProps) { 59 | return ( 60 | 61 | 72 | 73 | 80 | {children} 81 | 82 | 83 | 84 | 85 | ) 86 | } 87 | 88 | function SelectLabel({ 89 | className, 90 | ...props 91 | }: React.ComponentProps) { 92 | return ( 93 | 98 | ) 99 | } 100 | 101 | function SelectItem({ 102 | className, 103 | children, 104 | ...props 105 | }: React.ComponentProps) { 106 | return ( 107 | 115 | 116 | 117 | 118 | 119 | 120 | {children} 121 | 122 | ) 123 | } 124 | 125 | function SelectSeparator({ 126 | className, 127 | ...props 128 | }: React.ComponentProps) { 129 | return ( 130 | 135 | ) 136 | } 137 | 138 | function SelectScrollUpButton({ 139 | className, 140 | ...props 141 | }: React.ComponentProps) { 142 | return ( 143 | 151 | 152 | 153 | ) 154 | } 155 | 156 | function SelectScrollDownButton({ 157 | className, 158 | ...props 159 | }: React.ComponentProps) { 160 | return ( 161 | 169 | 170 | 171 | ) 172 | } 173 | 174 | export { 175 | Select, 176 | SelectContent, 177 | SelectGroup, 178 | SelectItem, 179 | SelectLabel, 180 | SelectScrollDownButton, 181 | SelectScrollUpButton, 182 | SelectSeparator, 183 | SelectTrigger, 184 | SelectValue, 185 | } 186 | -------------------------------------------------------------------------------- /src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Separator({ 9 | className, 10 | orientation = "horizontal", 11 | decorative = true, 12 | ...props 13 | }: React.ComponentProps) { 14 | return ( 15 | 25 | ) 26 | } 27 | 28 | export { Separator } 29 | -------------------------------------------------------------------------------- /src/components/ui/sheet.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as SheetPrimitive from "@radix-ui/react-dialog" 3 | import { XIcon } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | function Sheet({ ...props }: React.ComponentProps) { 8 | return 9 | } 10 | 11 | function SheetTrigger({ 12 | ...props 13 | }: React.ComponentProps) { 14 | return 15 | } 16 | 17 | function SheetClose({ 18 | ...props 19 | }: React.ComponentProps) { 20 | return 21 | } 22 | 23 | function SheetPortal({ 24 | ...props 25 | }: React.ComponentProps) { 26 | return 27 | } 28 | 29 | function SheetOverlay({ 30 | className, 31 | ...props 32 | }: React.ComponentProps) { 33 | return ( 34 | 42 | ) 43 | } 44 | 45 | function SheetContent({ 46 | className, 47 | children, 48 | side = "right", 49 | ...props 50 | }: React.ComponentProps & { 51 | side?: "top" | "right" | "bottom" | "left" 52 | }) { 53 | return ( 54 | 55 | 56 | 72 | {children} 73 | 74 | 75 | Close 76 | 77 | 78 | 79 | ) 80 | } 81 | 82 | function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { 83 | return ( 84 |
89 | ) 90 | } 91 | 92 | function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { 93 | return ( 94 |
99 | ) 100 | } 101 | 102 | function SheetTitle({ 103 | className, 104 | ...props 105 | }: React.ComponentProps) { 106 | return ( 107 | 112 | ) 113 | } 114 | 115 | function SheetDescription({ 116 | className, 117 | ...props 118 | }: React.ComponentProps) { 119 | return ( 120 | 125 | ) 126 | } 127 | 128 | export { 129 | Sheet, 130 | SheetTrigger, 131 | SheetClose, 132 | SheetContent, 133 | SheetHeader, 134 | SheetFooter, 135 | SheetTitle, 136 | SheetDescription, 137 | } 138 | -------------------------------------------------------------------------------- /src/components/ui/sidebar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, VariantProps } from "class-variance-authority"; 4 | import { PanelLeftIcon } from "lucide-react"; 5 | 6 | import { useIsMobile } from "@/hooks/use-mobile"; 7 | import { cn } from "@/lib/utils"; 8 | import { Button } from "@/components/ui/button"; 9 | import { Input } from "@/components/ui/input"; 10 | import { Separator } from "@/components/ui/separator"; 11 | import { 12 | Sheet, 13 | SheetContent, 14 | SheetDescription, 15 | SheetHeader, 16 | SheetTitle, 17 | } from "@/components/ui/sheet"; 18 | import { Skeleton } from "@/components/ui/skeleton"; 19 | import { 20 | Tooltip, 21 | TooltipContent, 22 | TooltipProvider, 23 | TooltipTrigger, 24 | } from "@/components/ui/tooltip"; 25 | 26 | const SIDEBAR_COOKIE_NAME = "sidebar_state"; 27 | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; 28 | const SIDEBAR_WIDTH = "16rem"; 29 | const SIDEBAR_WIDTH_MOBILE = "18rem"; 30 | const SIDEBAR_WIDTH_ICON = "3rem"; 31 | const SIDEBAR_KEYBOARD_SHORTCUT = "b"; 32 | 33 | type SidebarContextProps = { 34 | state: "expanded" | "collapsed"; 35 | open: boolean; 36 | setOpen: (open: boolean) => void; 37 | openMobile: boolean; 38 | setOpenMobile: (open: boolean) => void; 39 | isMobile: boolean; 40 | toggleSidebar: () => void; 41 | }; 42 | 43 | const SidebarContext = React.createContext(null); 44 | 45 | function useSidebar() { 46 | const context = React.useContext(SidebarContext); 47 | if (!context) { 48 | throw new Error("useSidebar must be used within a SidebarProvider."); 49 | } 50 | 51 | return context; 52 | } 53 | 54 | function SidebarProvider({ 55 | defaultOpen = true, 56 | open: openProp, 57 | onOpenChange: setOpenProp, 58 | className, 59 | style, 60 | children, 61 | ...props 62 | }: React.ComponentProps<"div"> & { 63 | defaultOpen?: boolean; 64 | open?: boolean; 65 | onOpenChange?: (open: boolean) => void; 66 | }) { 67 | const isMobile = useIsMobile(); 68 | const [openMobile, setOpenMobile] = React.useState(false); 69 | 70 | // This is the internal state of the sidebar. 71 | // We use openProp and setOpenProp for control from outside the component. 72 | const [_open, _setOpen] = React.useState(defaultOpen); 73 | const open = openProp ?? _open; 74 | const setOpen = React.useCallback( 75 | (value: boolean | ((value: boolean) => boolean)) => { 76 | const openState = typeof value === "function" ? value(open) : value; 77 | if (setOpenProp) { 78 | setOpenProp(openState); 79 | } else { 80 | _setOpen(openState); 81 | } 82 | 83 | // This sets the cookie to keep the sidebar state. 84 | document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; 85 | }, 86 | [setOpenProp, open] 87 | ); 88 | 89 | // Helper to toggle the sidebar. 90 | const toggleSidebar = React.useCallback(() => { 91 | return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); 92 | }, [isMobile, setOpen, setOpenMobile]); 93 | 94 | // Adds a keyboard shortcut to toggle the sidebar. 95 | React.useEffect(() => { 96 | const handleKeyDown = (event: KeyboardEvent) => { 97 | if ( 98 | event.key === SIDEBAR_KEYBOARD_SHORTCUT && 99 | (event.metaKey || event.ctrlKey) 100 | ) { 101 | event.preventDefault(); 102 | toggleSidebar(); 103 | } 104 | }; 105 | 106 | window.addEventListener("keydown", handleKeyDown); 107 | return () => window.removeEventListener("keydown", handleKeyDown); 108 | }, [toggleSidebar]); 109 | 110 | // We add a state so that we can do data-state="expanded" or "collapsed". 111 | // This makes it easier to style the sidebar with Tailwind classes. 112 | const state = open ? "expanded" : "collapsed"; 113 | 114 | const contextValue = React.useMemo( 115 | () => ({ 116 | state, 117 | open, 118 | setOpen, 119 | isMobile, 120 | openMobile, 121 | setOpenMobile, 122 | toggleSidebar, 123 | }), 124 | [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] 125 | ); 126 | 127 | return ( 128 | 129 | 130 |
145 | {children} 146 |
147 |
148 |
149 | ); 150 | } 151 | 152 | function Sidebar({ 153 | side = "left", 154 | variant = "sidebar", 155 | collapsible = "offcanvas", 156 | className, 157 | children, 158 | ...props 159 | }: React.ComponentProps<"div"> & { 160 | side?: "left" | "right"; 161 | variant?: "sidebar" | "floating" | "inset"; 162 | collapsible?: "offcanvas" | "icon" | "none"; 163 | }) { 164 | const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); 165 | 166 | if (collapsible === "none") { 167 | return ( 168 |
176 | {children} 177 |
178 | ); 179 | } 180 | 181 | if (isMobile) { 182 | return ( 183 | 184 | 196 | 197 | Sidebar 198 | Displays the mobile sidebar. 199 | 200 |
{children}
201 |
202 |
203 | ); 204 | } 205 | 206 | return ( 207 |
215 | {/* This is what handles the sidebar gap on desktop */} 216 |
227 | 251 |
252 | ); 253 | } 254 | 255 | function SidebarTrigger({ 256 | className, 257 | onClick, 258 | ...props 259 | }: React.ComponentProps) { 260 | const { toggleSidebar } = useSidebar(); 261 | 262 | return ( 263 | 278 | ); 279 | } 280 | 281 | function SidebarRail({ className, ...props }: React.ComponentProps<"button">) { 282 | const { toggleSidebar } = useSidebar(); 283 | 284 | return ( 285 |