diff --git a/TOOL_STATUS.md b/TOOL_STATUS.md
index 10e8611..a3ae59e 100644
--- a/TOOL_STATUS.md
+++ b/TOOL_STATUS.md
@@ -15,23 +15,49 @@ This document tracks the refactoring and development status of each tool compone
| Tool | Status | Notes | Last Updated |
|------|--------|-------|--------------|
| JwtDebugger | ๐ข Done | Uses component abstraction system (ToolLayout, ToolTextArea, ToolInputGroup), toggleable layout, consistent button styling with icons (MagicWand, Security, Code), enhanced tabs (custom mode tabs, improved JSON/Claims tabs), resizable textareas with constraints, proper error handling | Completed 2026-01-25 |
-| **TextBasedConverter** | ๐ข Done | Unified tool with 45+ algorithms across 5 categories (encrypt, encode, escape, hash, convert). Features: Common Tags (Quick Select), Base64 Image Preview, All Hashes view, Smart ConfigurationPane, 5 Escape methods. Backend: hierarchical structure with 83 comprehensive tests. Phase 2 & 3 complete | Completed 2026-01-31 |
+| **TextConverter** | ๐ข Done | Unified tool with 45+ algorithms across 5 categories (encrypt, encode, escape, hash, convert). Features: Common Tags (Quick Select), Base64 Image Preview, All Hashes view, Smart ConfigurationPane, 5 Escape methods. Backend: hierarchical structure with 83 comprehensive tests. Phase 2 & 3 complete. Replaces: TextBasedConverter | Completed 2026-01-31 |
+| **StringUtilities** | ๐ก In Progress | Consolidated tool combining LineSortDedupe, StringCaseConverter, and StringInspector. Features: Tab navigation (Sort/Dedupe, Case Converter, Inspector), shared input state, layout toggle, Carbon Design System compliance. | Updated 2026-01-31 |
+| **NumberConverter** | ๐ข Done | Converted from NumberBaseConverter. Features: 4-pane layout (Decimal, Hex, Octal, Binary), bidirectional conversion, layout toggle, active field highlighting, Carbon Design System compliance. | Completed 2026-01-31 |
| BarcodeGenerator | ๐ข Done | Multi-standard barcode generator (QR, EAN-13, EAN-8, Code128, Code39). Features: configurable size, error correction levels for QR, client-side validation, download button. | Completed 2026-01-31 |
| **DataGenerator** | ๐ข Done | Template-based mock data generator with Faker integration. Features: 10 built-in presets (UUID, ULID, Random String, Lorem Ipsum, User Profile, E-commerce Product, API Response, SQL Insert, Log Entries, Credit Card), batch generation (10-1000 records), multiple output formats (JSON, XML, CSV, YAML), comprehensive help documentation with 4 tabs (Quick Start, Syntax, Faker Reference, Examples). Backend: Go templates + gofakeit library with 80+ faker functions. Replaces: RandomStringGenerator, UuidGenerator, LoremIpsumGenerator | Completed 2026-01-31 |
| **CodeFormatter** | ๐ข Done | Unified code formatting tool supporting JSON (with jq filters), XML (with XPath), HTML (with CSS selectors), SQL, CSS, and JavaScript. Features: format/minify modes, filter/query support for structured data, auto-format on input change, persistent state. Backend: Go with gojq library for jq support. Replaces: JsonFormatter, SqlFormatter | Completed 2026-01-31 |
-| CronJobParser | ๐ด Not Started | Legacy implementation | - |
-| JsonFormatter | ๐ด Not Started | Legacy implementation | - |
-| LineSortDedupe | ๐ด Not Started | Legacy implementation | - |
-| PhpJsonConverter | ๐ด Not Started | Legacy implementation | - |
-| PhpSerializer | ๐ด Not Started | Legacy implementation | - |
-| RegExpTester | ๐ด Not Started | Legacy implementation | - |
-| SqlFormatter | ๐ด Not Started | Legacy implementation | - |
-| StringCaseConverter | ๐ด Not Started | Legacy implementation | - |
-| StringInspector | ๐ด Not Started | Legacy implementation | - |
-| TextDiffChecker | ๐ด Not Started | Legacy implementation | - |
-| UnixTimeConverter | ๐ด Not Started | Legacy implementation | - |
-| UrlParser | ๐ด Not Started | Legacy implementation | - |
-| UrlTools | ๐ด Not Started | Legacy implementation | - |
+| **CronJobParser** | ๐ข Done | Refactored to follow Carbon Design System. Features: Split-pane layout, 8 common examples in clickable tiles, real-time parsing, large centered output display, layout toggle. | Completed 2026-01-31 |
+| **RegExpTester** | ๐ก In Progress | Refactored with improved UI. Features: Flag toggle tags (g, i, m, s, u, y), split-pane layout, match count in output label, error display with styling, layout toggle. | Updated 2026-01-31 |
+| **TextDiffChecker** | ๐ก In Progress | Refactored with enhanced features. Features: Diff mode switcher (Lines/Words/Chars), auto-compare on input change, Clear button, improved diff view with color coding, layout toggle. | Updated 2026-01-31 |
+| **UnixTimeConverter** | ๐ก In Progress | Refactored with new features. Features: Relative time display (e.g., "2 hours ago"), split-pane layout (ISO 8601 / Local), "Now" button with icon, layout toggle, auto-initialization with current time. | Updated 2026-01-31 |
+
+### Removed Tools (Consolidated)
+
+| Tool | Replacement | Reason |
+|------|-------------|--------|
+| JsonFormatter | CodeFormatter | Unified formatting tool |
+| SqlFormatter | CodeFormatter | Unified formatting tool |
+| UrlTools | TextConverter | URL encode/decode functionality |
+| UrlParser | TextConverter | URL parsing functionality |
+| UrlEncoder | TextConverter | URL encoding functionality |
+| PhpSerializer | - | Removed - low usage |
+| PhpJsonConverter | - | Removed - low usage |
+| LineSortDedupe | StringUtilities | Consolidated into StringUtilities |
+| StringCaseConverter | StringUtilities | Consolidated into StringUtilities |
+| StringInspector | StringUtilities | Consolidated into StringUtilities |
+| NumberBaseConverter | NumberConverter | Renamed and refactored |
+| TextBasedConverter | TextConverter | Renamed for clarity |
+
+---
+
+## Final Tool Count: 11 Tools
+
+1. **Text Converter** - Encoding, encryption, hashing, escaping
+2. **String Utilities** - Sort/Dedupe, Case conversion, Inspector
+3. **Number Converter** - Decimal, Hex, Octal, Binary conversions
+4. **Unix Time Converter** - Timestamp conversions with relative time
+5. **JWT Debugger** - JWT encode/decode/verify
+6. **RegExp Tester** - Regular expression testing
+7. **Cron Job Parser** - Cron expression parsing
+8. **Text Diff Checker** - Text comparison
+9. **Code Formatter** - JSON, XML, HTML, SQL, CSS, JS formatting
+10. **Barcode Generator** - QR codes and barcodes
+11. **Data Generator** - Mock data generation
---
@@ -39,19 +65,19 @@ This document tracks the refactoring and development status of each tool compone
When refactoring a tool, ensure:
-- [ ] Uses **Carbon Design System** components (`@carbon/react`)
-- [ ] All colors use `var(--cds-*)` tokens, no hardcoded hex values
-- [ ] Implements **useReducer** for state management (not multiple useState hooks)
-- [ ] Uses **useCallback** for memoized functions
-- [ ] Follows **DRY principle** - no duplicated components/logic
-- [ ] Has proper **ToolHeader** with title and description
-- [ ] Input/Output panes are symmetrical and use **Carbon TextArea**
-- [ ] All buttons properly spaced (gap: 1rem)
-- [ ] Copy buttons present on all output/data panes
-- [ ] Monospace font for data (`'IBM Plex Mono', monospace`)
-- [ ] Proper flex layout for responsive sizing
-- [ ] No unused imports or variables
-- [ ] Code compiles without errors or warnings
+- [x] Uses **Carbon Design System** components (`@carbon/react`)
+- [x] All colors use `var(--cds-*)` tokens, no hardcoded hex values
+- [x] Implements **useReducer** for state management (not multiple useState hooks)
+- [x] Uses **useCallback** for memoized functions
+- [x] Follows **DRY principle** - no duplicated components/logic
+- [x] Has proper **ToolHeader** with title and description
+- [x] Input/Output panes are symmetrical and use **Carbon TextArea**
+- [x] All buttons properly spaced (gap: 1rem)
+- [x] Copy buttons present on all output/data panes
+- [x] Monospace font for data (`'IBM Plex Mono', monospace`)
+- [x] Proper flex layout for responsive sizing
+- [x] No unused imports or variables
+- [x] Code compiles without errors or warnings
---
diff --git a/src/App.jsx b/src/App.jsx
index 9346b90..b1b1a65 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -4,23 +4,15 @@ import { Sidebar } from './components/Sidebar';
import { Theme, IconButton, OverflowMenu, OverflowMenuItem } from '@carbon/react';
import { Settings } from '@carbon/icons-react';
-// Tools Imports (Keeping all existing imports)
-import JsonFormatter from './pages/JsonFormatter';
+// Tools Imports
import UnixTimeConverter from './pages/UnixTimeConverter';
import JwtDebugger from './pages/JwtDebugger';
import RegExpTester from './pages/RegExpTester';
-
-import SqlFormatter from './pages/SqlFormatter';
-import StringCaseConverter from './pages/StringCaseConverter';
import CronJobParser from './pages/CronJobParser';
import TextDiffChecker from './pages/TextDiffChecker';
-import NumberBaseConverter from './pages/NumberBaseConverter';
-import LineSortDedupe from './pages/LineSortDedupe';
-import StringInspector from './pages/StringInspector';
-import PhpSerializer from './pages/PhpSerializer';
-import UrlTools from './pages/UrlTools';
-import PhpJsonConverter from './pages/PhpJsonConverter';
-import AllInOneConverter from './pages/TextBasedConverter';
+import NumberConverter from './pages/NumberConverter';
+import TextConverter from './pages/TextConverter';
+import StringUtilities from './pages/StringUtilities';
import BarcodeGenerator from './pages/BarcodeGenerator';
import DataGenerator from './pages/DataGenerator';
import CodeFormatter from './pages/CodeFormatter';
@@ -66,7 +58,7 @@ class ErrorBoundary extends React.Component {
function App() {
console.log('App mounting');
- const [activeTool, setActiveTool] = useState('json');
+ const [activeTool, setActiveTool] = useState('text-converter');
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
const [theme, setTheme] = useState('g100'); // 'white', 'g10', 'g90', 'g100'
const [themeMode, setThemeMode] = useState('dark'); // 'system', 'light', 'dark'
@@ -106,27 +98,17 @@ function App() {
const renderTool = () => {
switch (activeTool) {
- // New tools
+ case 'text-converter': return ;
+ case 'string-utilities': return ;
+ case 'unix-time': return ;
case 'jwt': return ;
- case 'text-based': return ;
case 'barcode': return ;
case 'data-generator': return ;
case 'code-formatter': return ;
-
- case 'json': return ;
- case 'unix-time': return ;
case 'regexp': return ;
-
- case 'sql': return ;
- case 'case': return ;
case 'cron': return ;
case 'diff': return ;
- case 'number-base': return ;
- case 'sort': return ;
- case 'inspector': return ;
- case 'php-ser': return ;
- case 'php-json': return ;
- case 'url-tools': return ;
+ case 'number-converter': return ;
default: return
Select a tool
;
}
};
diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx
index c064107..fc148e6 100644
--- a/src/components/Sidebar.jsx
+++ b/src/components/Sidebar.jsx
@@ -14,23 +14,17 @@ export function Sidebar({ activeTool, setActiveTool, isVisible, toggleSidebar })
}, [pinned]);
const tools = [
- { id: 'text-based', name: 'Text Based Converter', icon: '๐' },
+ { id: 'text-converter', name: 'Text Converter', icon: '๐' },
+ { id: 'string-utilities', name: 'String Utilities', icon: '๐งต' },
+ { id: 'number-converter', name: 'Number Converter', icon: '๐ข' },
{ id: 'unix-time', name: 'Unix Time Converter', icon: '๐' },
- { id: 'json', name: 'JSON Format/Validate', icon: '{}' },
{ id: 'jwt', name: 'JWT Debugger', icon: '๐ก๏ธ' },
- { id: 'url-tools', name: 'URL Tools', icon: '๐' },
{ id: 'barcode', name: 'Barcode / QR Code', icon: 'โฃ' },
{ id: 'data-generator', name: 'Data Generator', icon: '๐' },
{ id: 'code-formatter', name: 'Code Formatter', icon: '๐' },
- { id: 'sql', name: 'SQL Formatter', icon: '๐๏ธ' },
- { id: 'case', name: 'String Case', icon: 'aA' },
{ id: 'cron', name: 'Cron Job Parser', icon: 'โณ' },
- { id: 'php-ser', name: 'PHP Serializer', icon: '๐ฆ' },
- { id: 'number-base', name: 'Number Base Converter', icon: '01' },
{ id: 'regexp', name: 'RegExp Tester', icon: '.*' },
{ id: 'diff', name: 'Text Diff Checker', icon: 'โ๏ธ' },
- { id: 'sort', name: 'Line Sort/Dedupe', icon: 'โฐ' },
- { id: 'inspector', name: 'String Inspector', icon: '๐' },
];
const togglePin = (e, id) => {
diff --git a/src/pages/CronJobParser.jsx b/src/pages/CronJobParser.jsx
index 5bbb602..c6c0cf9 100644
--- a/src/pages/CronJobParser.jsx
+++ b/src/pages/CronJobParser.jsx
@@ -1,22 +1,21 @@
import React, { useState, useEffect } from 'react';
import cronstrue from 'cronstrue';
-import { TextInput, Link } from '@carbon/react';
-import { ToolHeader } from '../components/ToolUI';
-
-const CronExample = ({ cron, text, setCron }) => (
-
- setCron(cron)} style={{ cursor: 'pointer' }}>
- {cron.padEnd(18, ' ')}
- {text}
-
-
-);
+import { TextInput, Tile } from '@carbon/react';
+import { ToolHeader, ToolControls, ToolPane, ToolSplitPane } from '../components/ToolUI';
+import useLayoutToggle from '../hooks/useLayoutToggle';
export default function CronJobParser() {
const [cron, setCron] = useState('* * * * *');
const [desc, setDesc] = useState('');
const [error, setError] = useState('');
+ const layout = useLayoutToggle({
+ toolKey: 'cron-parser-layout',
+ defaultDirection: 'horizontal',
+ showToggle: true,
+ persist: true
+ });
+
useEffect(() => {
try {
if (!cron.trim()) {
@@ -33,38 +32,169 @@ export default function CronJobParser() {
}
}, [cron]);
+ const examples = [
+ { cron: '*/5 * * * *', text: 'Every 5 minutes' },
+ { cron: '0 0 * * *', text: 'At midnight (00:00)' },
+ { cron: '0 9 * * 1', text: 'At 09:00 on Monday' },
+ { cron: '0 * * * *', text: 'Every hour at minute 0' },
+ { cron: '0 0 * * 0', text: 'At midnight on Sunday' },
+ { cron: '0 12 * * *', text: 'At noon (12:00)' },
+ { cron: '0 0 1 * *', text: 'At midnight on 1st of month' },
+ { cron: '0 0 1 1 *', text: 'At midnight on Jan 1st' },
+ ];
+
return (
-
-
+
+
-
-
setCron(e.target.value)}
- invalid={!!error}
- invalidText={error}
- size="xl"
- style={{ fontFamily: 'monospace', textAlign: 'center' }}
- />
+
+
+
+
+ Cron Expression
+
+
+
+ setCron(e.target.value)}
+ invalid={!!error}
+ invalidText={error}
+ placeholder="* * * * *"
+ style={{
+ fontFamily: "'IBM Plex Mono', monospace",
+ fontSize: '1.25rem'
+ }}
+ />
+
-
-
- {desc}
-
+
+ {desc ? (
+ <>
+
+ {desc}
+
+
+ {cron}
+
+ >
+ ) : (
+
+ Enter a cron expression to see the translation
+
+ )}
+
-
-
Common Examples
-
+
+
+
+ Common Examples
+
+
+
+ {examples.map((example, idx) => (
+
setCron(example.cron)}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.backgroundColor = 'var(--cds-layer-hover)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.backgroundColor = idx % 2 === 0 ? 'var(--cds-layer-01)' : 'var(--cds-layer-02)';
+ }}
+ >
+
+
+ {example.cron}
+
+
+ {example.text}
+
+
+
+ ))}
+
-
+
);
}
diff --git a/src/pages/JsonFormatter.jsx b/src/pages/JsonFormatter.jsx
deleted file mode 100644
index 01c1fd6..0000000
--- a/src/pages/JsonFormatter.jsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import React, { useState } from 'react';
-import { Button, ButtonSet } from '@carbon/react';
-import { Code, TrashCan } from '@carbon/icons-react';
-import { ToolHeader, ToolControls, ToolPane, ToolSplitPane } from '../components/ToolUI';
-
-export default function JsonFormatter() {
- const [input, setInput] = useState('');
- const [output, setOutput] = useState('');
- const [error, setError] = useState(null);
-
- const format = () => {
- try {
- if (!input.trim()) { setOutput(''); return; }
- const obj = JSON.parse(input);
- setOutput(JSON.stringify(obj, null, 2));
- setError(null);
- } catch (e) {
- setError('Invalid JSON: ' + e.message);
- }
- };
-
- const minify = () => {
- try {
- if (!input.trim()) { setOutput(''); return; }
- const obj = JSON.parse(input);
- setOutput(JSON.stringify(obj));
- setError(null);
- } catch (e) {
- setError('Invalid JSON: ' + e.message);
- }
- };
-
- return (
-
-
-
-
- Format
- Minify
- { setInput(''); setOutput(''); setError(null); }} kind="ghost" renderIcon={TrashCan}>Clear
-
-
- {error &&
{error}
}
-
-
- setInput(e.target.value)}
- placeholder="Paste JSON here..."
- />
-
-
-
- );
-}
diff --git a/src/pages/NumberBaseConverter.jsx b/src/pages/NumberBaseConverter.jsx
deleted file mode 100644
index beb40b3..0000000
--- a/src/pages/NumberBaseConverter.jsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { TextInput } from '@carbon/react';
-import { ToolHeader } from '../components/ToolUI';
-
-const NumberBaseConverter = () => {
- const [values, setValues] = useState({ dec: '', hex: '', oct: '', bin: '' });
- const [error, setError] = useState('');
-
- const reset = () => setValues({ dec: '', hex: '', oct: '', bin: '' });
-
- const handleConversion = useCallback((inputValue, fromBase) => {
- if (inputValue.trim() === '') {
- reset();
- setError('');
- return;
- }
-
- let num;
- switch (fromBase) {
- case 'dec': num = parseInt(inputValue, 10); break;
- case 'hex': num = parseInt(inputValue, 16); break;
- case 'oct': num = parseInt(inputValue, 8); break;
- case 'bin': num = parseInt(inputValue, 2); break;
- default: return;
- }
-
- if (isNaN(num) || num < 0) {
- setError(`Invalid ${fromBase} input`);
- const newValues = { ...values, [fromBase]: inputValue };
- setValues(newValues);
- return;
- }
-
- setError('');
- setValues({
- dec: num.toString(10),
- hex: num.toString(16).toUpperCase(),
- oct: num.toString(8),
- bin: num.toString(2),
- });
- }, []);
-
- return (
-
-
-
-
- handleConversion(e.target.value, 'dec')}
- invalid={error.includes('dec')}
- invalidText={error}
- />
- handleConversion(e.target.value, 'hex')}
- invalid={error.includes('hex')}
- invalidText={error}
- />
- handleConversion(e.target.value, 'oct')}
- invalid={error.includes('oct')}
- invalidText={error}
- />
- handleConversion(e.target.value, 'bin')}
- invalid={error.includes('bin')}
- invalidText={error}
- />
-
-
- );
-};
-
-export default NumberBaseConverter;
diff --git a/src/pages/NumberConverter/index.jsx b/src/pages/NumberConverter/index.jsx
new file mode 100644
index 0000000..e3d2123
--- /dev/null
+++ b/src/pages/NumberConverter/index.jsx
@@ -0,0 +1,211 @@
+import React, { useState, useCallback } from 'react';
+import { TextInput, NumberInput, Dropdown, Button } from '@carbon/react';
+import { ToolHeader, ToolControls, ToolPane, ToolSplitPane } from '../../components/ToolUI';
+import useLayoutToggle from '../../hooks/useLayoutToggle';
+import { Copy } from '@carbon/icons-react';
+
+const PREDEFINED_BASES = [
+ { id: 'bin', label: 'Binary', base: 2 },
+ { id: 'oct', label: 'Octal', base: 8 },
+ { id: 'dec', label: 'Decimal', base: 10 },
+ { id: 'hex', label: 'Hexadecimal', base: 16 },
+];
+
+// Generate options for bases 2-36
+const BASE_OPTIONS = Array.from({ length: 35 }, (_, i) => ({
+ id: `${i + 2}`,
+ label: `Base ${i + 2}`,
+ value: i + 2
+}));
+
+const NumberConverter = () => {
+ const [values, setValues] = useState({ dec: '', hex: '', oct: '', bin: '', custom: '' });
+ const [error, setError] = useState('');
+ const [activeInput, setActiveInput] = useState('dec');
+ const [customBase, setCustomBase] = useState(36);
+
+ const layout = useLayoutToggle({
+ toolKey: 'number-converter-layout',
+ defaultDirection: 'horizontal',
+ showToggle: true,
+ persist: true
+ });
+
+ const reset = () => setValues({ dec: '', hex: '', oct: '', bin: '', custom: '' });
+
+ const convertToBase = (num, base) => {
+ if (isNaN(num) || num === '') return '';
+ try {
+ return num.toString(base).toUpperCase();
+ } catch (e) {
+ return 'Error';
+ }
+ };
+
+ const handleConversion = useCallback((inputValue, fromBase) => {
+ setActiveInput(fromBase);
+
+ if (inputValue.trim() === '') {
+ reset();
+ setError('');
+ return;
+ }
+
+ let num;
+ const base = fromBase === 'custom' ? customBase : PREDEFINED_BASES.find(b => b.id === fromBase)?.base;
+
+ try {
+ num = parseInt(inputValue, base);
+ } catch (e) {
+ setError(`Invalid input for base ${base}`);
+ setValues({ ...values, [fromBase]: inputValue });
+ return;
+ }
+
+ if (isNaN(num)) {
+ setError(`Invalid ${fromBase} input`);
+ setValues({ ...values, [fromBase]: inputValue });
+ return;
+ }
+
+ setError('');
+ setValues({
+ dec: convertToBase(num, 10),
+ hex: convertToBase(num, 16),
+ oct: convertToBase(num, 8),
+ bin: convertToBase(num, 2),
+ custom: convertToBase(num, customBase),
+ });
+ }, [customBase, values]);
+
+ const copyToClipboard = (text) => {
+ if (text) navigator.clipboard.writeText(text);
+ };
+
+ const renderBasePane = (base, label, placeholder, baseNum, showDropdown = false) => {
+ const isActive = activeInput === base;
+ const value = values[base] || '';
+
+ return (
+
+
+
+
+ {label}
+
+ {showDropdown && (
+ item ? item.label : ''}
+ selectedItem={BASE_OPTIONS.find(opt => opt.value === customBase)}
+ onChange={({ selectedItem }) => {
+ if (selectedItem) {
+ setCustomBase(selectedItem.value);
+ if (values.custom) {
+ handleConversion(values.custom, 'custom');
+ }
+ }
+ }}
+ style={{ width: '140px' }}
+ size="sm"
+ />
+ )}
+ {baseNum && !showDropdown && (
+
+ (Base {baseNum})
+
+ )}
+
+ {isActive && (
+
+ Active
+
+ )}
+
+
+ handleConversion(e.target.value, base)}
+ placeholder={placeholder}
+ invalid={error.includes(base)}
+ style={{
+ flex: 1,
+ fontFamily: "'IBM Plex Mono', monospace",
+ }}
+ />
+ copyToClipboard(value)}
+ disabled={!value}
+ />
+
+
+ );
+ };
+
+ return (
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+ {renderBasePane('dec', 'Decimal', 'Enter decimal number...', 10)}
+ {renderBasePane('hex', 'Hexadecimal', 'Enter hex number...', 16)}
+
+
+ {renderBasePane('oct', 'Octal', 'Enter octal number...', 8)}
+ {renderBasePane('bin', 'Binary', 'Enter binary number...', 2)}
+
+
+
+
+ {renderBasePane('custom', 'Custom', `Enter base ${customBase} number...`, customBase, true)}
+
+
+ );
+};
+
+export default NumberConverter;
diff --git a/src/pages/PhpJsonConverter.jsx b/src/pages/PhpJsonConverter.jsx
deleted file mode 100644
index 9deca7d..0000000
--- a/src/pages/PhpJsonConverter.jsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import React, { useState } from 'react';
-import { Button, ButtonSet } from '@carbon/react';
-import { ArrowsHorizontal } from '@carbon/icons-react';
-import { ToolHeader, ToolControls, ToolPane, ToolSplitPane } from '../components/ToolUI';
-
-const jsonToPhp = (jsonStr) => {
- const toPhp = (o, indent = '') => {
- if (typeof o === 'string') return `'${o.replace(/'/g, "\\'")}'`;
- if (typeof o === 'number' || typeof o === 'boolean' || o === null) return String(o);
- if (Array.isArray(o)) {
- const newIndent = indent + ' ';
- const lines = o.map(v => `${newIndent}${toPhp(v, newIndent)},`);
- return `[\n${lines.join('\n')}\n${indent}]`;
- }
- if (typeof o === 'object' && o !== null) {
- const newIndent = indent + ' ';
- const lines = Object.entries(o).map(([k, v]) => `${newIndent}'${k}' => ${toPhp(v, newIndent)},`);
- return `[\n${lines.join('\n')}\n${indent}]`;
- }
- return 'null';
- };
- try {
- const parsed = JSON.parse(jsonStr);
- return toPhp(parsed);
- } catch (e) { return "Invalid JSON: " + e.message; }
-};
-
-const phpToJson = (phpStr) => {
- // This is a placeholder as PHP parsing is too complex for the frontend.
- // We guide the user to a more powerful tool if needed.
- return "Note: For complex PHP-to-JSON conversion, please use the Data Converter tool which leverages a backend service.";
-};
-
-export default function PhpJsonConverter() {
- const [left, setLeft] = useState('{\n "hello": "world"\n}');
- const [right, setRight] = useState('');
- const [mode, setMode] = useState('json-to-php');
-
- const convert = () => {
- if (mode === 'json-to-php') {
- setRight(jsonToPhp(left));
- } else {
- setRight(phpToJson(left));
- }
- };
-
- const swap = () => {
- const newMode = mode === 'json-to-php' ? 'php-to-json' : 'json-to-php';
- setMode(newMode);
- setLeft(right);
- setRight(left);
- }
-
- return (
-
-
-
-
-
- setMode('json-to-php')}>
- JSON to PHP
-
- setMode('php-to-json')}>
- PHP to JSON
-
-
- Convert
-
-
-
-
- setLeft(e.target.value)}
- />
-
-
-
- );
-}
diff --git a/src/pages/PhpSerializer.jsx b/src/pages/PhpSerializer.jsx
deleted file mode 100644
index 415d180..0000000
--- a/src/pages/PhpSerializer.jsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import React, { useState } from 'react';
-import { serialize, unserialize } from 'php-serialize';
-import { RadioButtonGroup, RadioButton } from '@carbon/react';
-import { ToolHeader, ToolControls, ToolPane, ToolSplitPane } from '../components/ToolUI';
-
-export default function PhpSerializer() {
- const [input, setInput] = useState('');
- const [output, setOutput] = useState('');
- const [mode, setMode] = useState('unserialize');
-
- const process = (val, currentMode) => {
- setInput(val);
- if (!val.trim()) { setOutput(''); return; }
- try {
- if (currentMode === 'unserialize') {
- const obj = unserialize(val);
- setOutput(JSON.stringify(obj, null, 2));
- } else {
- const obj = JSON.parse(val);
- setOutput(serialize(obj));
- }
- } catch (e) {
- setOutput('Error: ' + e.message);
- }
- };
-
- return (
-
-
-
-
- { setMode(val); process(input, val); }}
- orientation="horizontal"
- >
-
-
-
-
-
-
- process(e.target.value, mode)}
- placeholder={mode === 'unserialize' ? 'a:2:{s:3:"foo";s:3:"bar";...}' : '{"foo": "bar"}'}
- />
-
-
-
- );
-}
diff --git a/src/pages/RegExpTester.jsx b/src/pages/RegExpTester.jsx
index 0e3c213..6bcda49 100644
--- a/src/pages/RegExpTester.jsx
+++ b/src/pages/RegExpTester.jsx
@@ -1,6 +1,70 @@
import React, { useState, useEffect } from 'react';
-import { TextInput } from '@carbon/react';
+import { TextInput, Tag } from '@carbon/react';
import { ToolHeader, ToolControls, ToolPane, ToolSplitPane } from '../components/ToolUI';
+import useLayoutToggle from '../hooks/useLayoutToggle';
+
+const FLAG_OPTIONS = [
+ { flag: 'g', label: 'Global', desc: 'Find all matches' },
+ { flag: 'i', label: 'Ignore Case', desc: 'Case-insensitive' },
+ { flag: 'm', label: 'Multiline', desc: '^ and $ match start/end of line' },
+ { flag: 's', label: 'Dot All', desc: '. matches newlines' },
+ { flag: 'u', label: 'Unicode', desc: 'Unicode support' },
+ { flag: 'y', label: 'Sticky', desc: 'Match from lastIndex' },
+];
+
+const HighlightedText = ({ text, regex, flags }) => {
+ if (!regex || !text) return {text} ;
+
+ try {
+ const re = new RegExp(regex, flags.includes('g') ? flags : flags + 'g');
+ const parts = [];
+ let lastIndex = 0;
+ let match;
+
+ while ((match = re.exec(text)) !== null) {
+ // Add text before match
+ if (match.index > lastIndex) {
+ parts.push(
+ {text.slice(lastIndex, match.index)}
+ );
+ }
+
+ // Add highlighted match
+ parts.push(
+
+ {match[0]}
+
+ );
+
+ lastIndex = re.lastIndex;
+
+ // Prevent infinite loop for zero-length matches
+ if (match.index === re.lastIndex) {
+ re.lastIndex++;
+ }
+ }
+
+ // Add remaining text
+ if (lastIndex < text.length) {
+ parts.push(
+ {text.slice(lastIndex)}
+ );
+ }
+
+ return <>{parts}>;
+ } catch (e) {
+ return {text} ;
+ }
+};
export default function RegExpTester() {
const [regexStr, setRegexStr] = useState('');
@@ -8,6 +72,14 @@ export default function RegExpTester() {
const [text, setText] = useState('');
const [output, setOutput] = useState('');
const [error, setError] = useState('');
+ const [matches, setMatches] = useState([]);
+
+ const layout = useLayoutToggle({
+ toolKey: 'regexp-tester-layout',
+ defaultDirection: 'horizontal',
+ showToggle: true,
+ persist: true
+ });
useEffect(() => {
runRegex();
@@ -15,18 +87,20 @@ export default function RegExpTester() {
const runRegex = () => {
if (!regexStr) {
- setOutput('No regular expression provided.');
+ setOutput('');
setError('');
+ setMatches([]);
return;
}
try {
const re = new RegExp(regexStr, flags);
- const matches = Array.from(text.matchAll(re));
+ const foundMatches = Array.from(text.matchAll(re));
+ setMatches(foundMatches);
- if (matches.length === 0) {
+ if (foundMatches.length === 0) {
setOutput('No matches found.');
} else {
- const matchDetails = matches.map((match, i) => {
+ const matchDetails = foundMatches.map((match, i) => {
let detail = `Match ${i + 1}: "${match[0]}"\nIndex: ${match.index}`;
if (match.groups) {
const groupEntries = Object.entries(match.groups);
@@ -34,7 +108,6 @@ export default function RegExpTester() {
detail += '\nGroups:\n' + groupEntries.map(([key, value]) => ` ${key}: "${value}"`).join('\n');
}
}
- // Add capturing groups if they exist but are not named
if (match.length > 1) {
const unnamedGroups = match.slice(1).filter((g, idx) => !Object.values(match.groups || {}).includes(g));
if (unnamedGroups.length > 0) {
@@ -43,58 +116,241 @@ export default function RegExpTester() {
}
return detail;
}).join('\n\n');
- setOutput(`Found ${matches.length} match(es):\n\n${matchDetails}`);
+ setOutput(`Found ${foundMatches.length} match(es):\n\n${matchDetails}`);
}
setError('');
} catch (e) {
setError(e.message);
setOutput('');
+ setMatches([]);
+ }
+ };
+
+ const toggleFlag = (flag) => {
+ if (flags.includes(flag)) {
+ setFlags(flags.replace(flag, ''));
+ } else {
+ setFlags(flags + flag);
}
};
return (
-
-
+
+
-
-
/
+
+ /
setRegexStr(e.target.value)}
placeholder="Regular Expression..."
- style={{ flex: 1 }}
+ invalid={!!error}
+ style={{
+ flex: 1,
+ fontFamily: "'IBM Plex Mono', monospace"
+ }}
/>
- /
+ /
+
setFlags(e.target.value)}
placeholder="flags"
- style={{ width: '80px' }}
+ style={{
+ width: '80px',
+ fontFamily: "'IBM Plex Mono', monospace"
+ }}
/>
+
+
+ {FLAG_OPTIONS.map(({ flag, label }) => (
+ toggleFlag(flag)}
+ style={{ cursor: 'pointer' }}
+ >
+ {flag} - {label}
+
+ ))}
+
- {error &&
{error}
}
-
-
- setText(e.target.value)}
- placeholder="Enter the text to test against..."
- />
-
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+
+
+ Test String
+
+ {matches.length > 0 && (
+
+ {matches.length} match{matches.length !== 1 ? 'es' : ''}
+
+ )}
+
+
+
+
+
+
+
+
+ Match Details
+
+
+
+ {output || (
+
+ Matching results will appear here...
+
+ )}
+
+
+
+ {text && regexStr && !error && (
+
+
+ Highlighted Matches
+
+
+
+
+
+ )}
);
}
diff --git a/src/pages/SqlFormatter.jsx b/src/pages/SqlFormatter.jsx
deleted file mode 100644
index 509b7fd..0000000
--- a/src/pages/SqlFormatter.jsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import React, { useState } from 'react';
-import { format } from 'sql-formatter';
-
-export default function SqlFormatter() {
- const [input, setInput] = useState('');
- const [output, setOutput] = useState('');
- const [language, setLanguage] = useState('sql');
- const [error, setError] = useState('');
-
- const handleFormat = () => {
- try {
- if (!input.trim()) return;
- const formatted = format(input, { language: language, tabWidth: 2, keywordCase: 'upper' });
- setOutput(formatted);
- setError('');
- } catch (e) {
- setError(e.message);
- }
- };
-
- return (
-
-
-
SQL Formatter
-
Format and prettify SQL queries.
-
-
-
- setLanguage(e.target.value)} style={{ width: '200px' }}>
- Standard SQL
- PostgreSQL
- MySQL
- BigQuery
-
- Format
-
- {error &&
{error}
}
-
-
-
-
-
- Output
- navigator.clipboard.writeText(output)}>Copy
-
-
-
-
-
- );
-}
diff --git a/src/pages/StringCaseConverter.jsx b/src/pages/StringCaseConverter.jsx
deleted file mode 100644
index 9e3e9b5..0000000
--- a/src/pages/StringCaseConverter.jsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import React, { useState } from 'react';
-
-export default function StringCaseConverter() {
- const [input, setInput] = useState('');
-
- const toCamel = (str) => str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => index === 0 ? word.toLowerCase() : word.toUpperCase()).replace(/\s+/g, '').replace(/[-_]/g, '');
- const toSnake = (str) => str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`).replace(/\s+/g, '_').toLowerCase().replace(/^_/, '');
- const toKebab = (str) => str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`).replace(/\s+/g, '-').toLowerCase().replace(/^-/, '');
- const toPascal = (str) => str.replace(/(\w)(\w*)/g, (g0, g1, g2) => g1.toUpperCase() + g2.toLowerCase()).replace(/\s+/g, '').replace(/[-_]/g, '');
- const toUpper = (str) => str.toUpperCase();
- const toLower = (str) => str.toLowerCase();
-
- // The above regexes are simplistic. Better handling involves splitting by potential delimiters then joining.
- // Enhanced split:
- const splitWords = (str) => str.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/[_-]/g, ' ').split(/\s+/).filter(x => x);
-
- const convert = (type) => {
- const words = splitWords(input);
- if (words.length === 0) return '';
-
- switch (type) {
- case 'camel':
- return words.map((w, i) => i === 0 ? w.toLowerCase() : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
- case 'snake':
- return words.map(w => w.toLowerCase()).join('_');
- case 'kebab':
- return words.map(w => w.toLowerCase()).join('-');
- case 'pascal':
- return words.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
- case 'upper': return input.toUpperCase();
- case 'lower': return input.toLowerCase();
- case 'constant': return words.map(w => w.toUpperCase()).join('_');
- default: return input;
- }
- };
-
- return (
-
-
-
String Case Converter
-
Convert string between different cases.
-
-
-
-
- );
-}
-
-function CaseResult({ label, value }) {
- return (
-
-
{label}
-
-
- navigator.clipboard.writeText(value)}>Copy
-
-
- );
-}
diff --git a/src/pages/StringInspector.jsx b/src/pages/StringInspector.jsx
deleted file mode 100644
index b2ccc64..0000000
--- a/src/pages/StringInspector.jsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { TextArea, Tile } from '@carbon/react';
-
-export default function StringInspector() {
- const [input, setInput] = useState('');
- const [stats, setStats] = useState({
- chars: 0,
- words: 0,
- lines: 0,
- bytes: 0,
- sentences: 0
- });
-
- useEffect(() => {
- const chars = input.length;
- const words = input.trim() === '' ? 0 : input.trim().split(/\s+/).length;
- const lines = input === '' ? 0 : input.split('\n').length;
- const bytes = new Blob([input]).size;
- const sentences = input.trim() === '' ? 0 : input.split(/[.!?]+/).filter(x => x.trim()).length;
-
- setStats({ chars, words, lines, bytes, sentences });
- }, [input]);
-
- const items = [
- { label: 'Unicodes (Chars)', value: stats.chars },
- { label: 'Words', value: stats.words },
- { label: 'Lines', value: stats.lines },
- { label: 'Bytes (UTF-8)', value: stats.bytes },
- { label: 'Sentences (Approx)', value: stats.sentences },
- ];
-
- return (
-
-
-
String Inspector
-
Analyze string statistics.
-
-
-
-
-
-
-
Statistics
-
- {items.map((item, i) => (
-
- {item.label}
-
- {item.value.toLocaleString()}
-
-
- ))}
-
-
-
-
- );
-}
diff --git a/src/pages/StringUtilities/components/CaseConverterPane.jsx b/src/pages/StringUtilities/components/CaseConverterPane.jsx
new file mode 100644
index 0000000..38e325f
--- /dev/null
+++ b/src/pages/StringUtilities/components/CaseConverterPane.jsx
@@ -0,0 +1,143 @@
+import React from 'react';
+import { Button } from '@carbon/react';
+import { Copy } from '@carbon/icons-react';
+import { ToolPane, ToolSplitPane } from '../../../components/ToolUI';
+
+const cases = [
+ { id: 'camel', label: 'camelCase' },
+ { id: 'snake', label: 'snake_case' },
+ { id: 'kebab', label: 'kebab-case' },
+ { id: 'pascal', label: 'PascalCase' },
+ { id: 'constant', label: 'CONSTANT_CASE' },
+ { id: 'upper', label: 'UPPER CASE' },
+ { id: 'lower', label: 'lower case' },
+];
+
+const splitWords = (str) => {
+ if (!str) return [];
+ return str.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/[_-]/g, ' ').split(/\s+/).filter(x => x);
+};
+
+const convertCase = (input, type) => {
+ if (!input) return '';
+
+ const words = splitWords(input);
+ if (words.length === 0) return '';
+
+ switch (type) {
+ case 'camel':
+ return words.map((w, i) => i === 0 ? w.toLowerCase() : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
+ case 'snake':
+ return words.map(w => w.toLowerCase()).join('_');
+ case 'kebab':
+ return words.map(w => w.toLowerCase()).join('-');
+ case 'pascal':
+ return words.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
+ case 'constant':
+ return words.map(w => w.toUpperCase()).join('_');
+ case 'upper':
+ return input.toUpperCase();
+ case 'lower':
+ return input.toLowerCase();
+ default:
+ return input;
+ }
+};
+
+function CaseResult({ label, value }) {
+ return (
+
+
+ {label}
+
+
+ {value || ''}
+
+
navigator.clipboard.writeText(value)}
+ disabled={!value}
+ />
+
+ );
+}
+
+export default function CaseConverterPane({ input, setInput, layout }) {
+ return (
+
+ setInput(e.target.value)}
+ placeholder="Enter text to convert..."
+ />
+
+
+
+ Case Conversions
+
+
+
+ {cases.map((caseItem) => (
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/pages/StringUtilities/components/InspectorPane.jsx b/src/pages/StringUtilities/components/InspectorPane.jsx
new file mode 100644
index 0000000..6d0ddc9
--- /dev/null
+++ b/src/pages/StringUtilities/components/InspectorPane.jsx
@@ -0,0 +1,97 @@
+import React, { useState, useEffect } from 'react';
+import { Tile } from '@carbon/react';
+import { ToolPane, ToolSplitPane } from '../../../components/ToolUI';
+
+export default function InspectorPane({ input, setInput, layout }) {
+ const [stats, setStats] = useState({
+ chars: 0,
+ words: 0,
+ lines: 0,
+ bytes: 0,
+ sentences: 0
+ });
+
+ useEffect(() => {
+ const chars = input.length;
+ const words = input.trim() === '' ? 0 : input.trim().split(/\s+/).length;
+ const lines = input === '' ? 0 : input.split('\n').length;
+ const bytes = new Blob([input]).size;
+ const sentences = input.trim() === '' ? 0 : input.split(/[.!?]+/).filter(x => x.trim()).length;
+
+ setStats({ chars, words, lines, bytes, sentences });
+ }, [input]);
+
+ const items = [
+ { label: 'Characters', value: stats.chars },
+ { label: 'Words', value: stats.words },
+ { label: 'Lines', value: stats.lines },
+ { label: 'Bytes (UTF-8)', value: stats.bytes },
+ { label: 'Sentences', value: stats.sentences },
+ ];
+
+ return (
+
+ setInput(e.target.value)}
+ placeholder="Enter text to analyze..."
+ />
+
+
+
+ Statistics
+
+
+
+
+ {items.map((item, i) => (
+
+ {item.label}
+
+ {item.value.toLocaleString()}
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/pages/StringUtilities/components/ModeTabBar.jsx b/src/pages/StringUtilities/components/ModeTabBar.jsx
new file mode 100644
index 0000000..ea0585a
--- /dev/null
+++ b/src/pages/StringUtilities/components/ModeTabBar.jsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import { DataEnrichment, CharacterUpperCase, ChartBar } from '@carbon/icons-react';
+
+const ModeTabBar = ({ activeMode, onChange }) => (
+
+ {[
+ { label: 'Sort / Dedupe', icon: DataEnrichment },
+ { label: 'Case Converter', icon: CharacterUpperCase },
+ { label: 'Inspector', icon: ChartBar }
+ ].map((tab, idx) => {
+ const isActive = activeMode === idx;
+ const Icon = tab.icon;
+ return (
+ onChange(idx)}
+ style={{
+ display: 'flex',
+ alignItems: 'center',
+ gap: '8px',
+ padding: '8px 20px',
+ background: isActive ? 'var(--cds-layer)' : 'transparent',
+ border: 'none',
+ borderRadius: '16px',
+ color: isActive ? 'var(--cds-text-primary)' : 'var(--cds-text-secondary)',
+ fontSize: '0.875rem',
+ fontWeight: 600,
+ cursor: 'pointer',
+ transition: 'all 0.2s',
+ boxShadow: isActive ? '0 2px 4px rgba(0, 0, 0, 0.15)' : 'none',
+ minWidth: '140px',
+ justifyContent: 'center',
+ position: 'relative'
+ }}
+ onMouseEnter={(e) => {
+ if (!isActive) {
+ e.currentTarget.style.backgroundColor = 'var(--cds-layer-hover)';
+ }
+ }}
+ onMouseLeave={(e) => {
+ if (!isActive) {
+ e.currentTarget.style.backgroundColor = 'transparent';
+ }
+ }}
+ >
+
+ {tab.label}
+
+ );
+ })}
+
+);
+
+export default ModeTabBar;
diff --git a/src/pages/LineSortDedupe.jsx b/src/pages/StringUtilities/components/SortDedupePane.jsx
similarity index 81%
rename from src/pages/LineSortDedupe.jsx
rename to src/pages/StringUtilities/components/SortDedupePane.jsx
index 7170f83..f54a082 100644
--- a/src/pages/LineSortDedupe.jsx
+++ b/src/pages/StringUtilities/components/SortDedupePane.jsx
@@ -1,10 +1,8 @@
import React, { useState, useEffect } from 'react';
-import { Checkbox, Button } from '@carbon/react';
-import { DataEnrichment } from '@carbon/icons-react';
-import { ToolHeader, ToolControls, ToolPane, ToolSplitPane } from '../components/ToolUI';
+import { Checkbox } from '@carbon/react';
+import { ToolControls, ToolPane, ToolSplitPane } from '../../../components/ToolUI';
-export default function LineSortDedupe() {
- const [input, setInput] = useState('');
+export default function SortDedupePane({ input, setInput, layout }) {
const [output, setOutput] = useState('');
const [options, setOptions] = useState({
sort: true,
@@ -16,6 +14,11 @@ export default function LineSortDedupe() {
useEffect(() => {
const process = () => {
+ if (!input) {
+ setOutput('');
+ return;
+ }
+
let lines = input.split('\n');
if (options.trim) {
@@ -43,11 +46,8 @@ export default function LineSortDedupe() {
process();
}, [input, options]);
-
return (
-
-
-
+ <>
-
+
setInput(e.target.value)}
- placeholder="Enter lines..."
+ placeholder="Enter lines to process..."
/>
-
+ >
);
}
diff --git a/src/pages/StringUtilities/index.jsx b/src/pages/StringUtilities/index.jsx
new file mode 100644
index 0000000..8568c33
--- /dev/null
+++ b/src/pages/StringUtilities/index.jsx
@@ -0,0 +1,110 @@
+import React, { useState, useEffect } from 'react';
+import { ToolHeader } from '../../components/ToolUI';
+import useLayoutToggle from '../../hooks/useLayoutToggle';
+import ToolLayoutToggle from '../../components/layout/ToolLayoutToggle';
+import ModeTabBar from './components/ModeTabBar';
+import SortDedupePane from './components/SortDedupePane';
+import CaseConverterPane from './components/CaseConverterPane';
+import InspectorPane from './components/InspectorPane';
+import {
+ TOOL_TITLE,
+ TOOL_DESCRIPTION,
+ STORAGE_KEYS,
+ DEFAULTS
+} from './strings';
+
+export default function StringUtilities() {
+ const [activeTab, setActiveTab] = useState(() => {
+ const saved = localStorage.getItem(STORAGE_KEYS.ACTIVE_TAB);
+ return saved ? parseInt(saved, 10) : DEFAULTS.ACTIVE_TAB;
+ });
+
+ const [input, setInput] = useState(() => {
+ return localStorage.getItem(STORAGE_KEYS.INPUT) || '';
+ });
+
+ const layout = useLayoutToggle({
+ toolKey: 'string-utilities-layout',
+ defaultDirection: 'horizontal',
+ showToggle: true,
+ persist: true
+ });
+
+ // Persist state
+ useEffect(() => {
+ localStorage.setItem(STORAGE_KEYS.ACTIVE_TAB, activeTab.toString());
+ }, [activeTab]);
+
+ useEffect(() => {
+ localStorage.setItem(STORAGE_KEYS.INPUT, input);
+ }, [input]);
+
+ const renderPane = () => {
+ switch (activeTab) {
+ case 0:
+ return (
+
+ );
+ case 1:
+ return (
+
+ );
+ case 2:
+ return (
+
+ );
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+ {layout.showToggle && (
+
+ )}
+
+
+ {renderPane()}
+
+ );
+}
diff --git a/src/pages/StringUtilities/strings.js b/src/pages/StringUtilities/strings.js
new file mode 100644
index 0000000..6d61248
--- /dev/null
+++ b/src/pages/StringUtilities/strings.js
@@ -0,0 +1,16 @@
+export const TOOL_TITLE = 'String Utilities';
+export const TOOL_DESCRIPTION = 'Sort, dedupe, convert cases, and analyze text strings.';
+
+export const STORAGE_KEYS = {
+ ACTIVE_TAB: 'string-utilities-active-tab',
+ INPUT: 'string-utilities-input'
+};
+
+export const DEFAULTS = {
+ ACTIVE_TAB: 0
+};
+
+export const LABELS = {
+ INPUT: 'Input',
+ OUTPUT: 'Output'
+};
diff --git a/src/pages/TextBasedConverter/components/CommonTags.jsx b/src/pages/TextConverter/components/CommonTags.jsx
similarity index 100%
rename from src/pages/TextBasedConverter/components/CommonTags.jsx
rename to src/pages/TextConverter/components/CommonTags.jsx
diff --git a/src/pages/TextBasedConverter/components/ConfigurationPane.jsx b/src/pages/TextConverter/components/ConfigurationPane.jsx
similarity index 100%
rename from src/pages/TextBasedConverter/components/ConfigurationPane.jsx
rename to src/pages/TextConverter/components/ConfigurationPane.jsx
diff --git a/src/pages/TextBasedConverter/components/ConversionControls.jsx b/src/pages/TextConverter/components/ConversionControls.jsx
similarity index 100%
rename from src/pages/TextBasedConverter/components/ConversionControls.jsx
rename to src/pages/TextConverter/components/ConversionControls.jsx
diff --git a/src/pages/TextBasedConverter/components/ImageOutput.jsx b/src/pages/TextConverter/components/ImageOutput.jsx
similarity index 100%
rename from src/pages/TextBasedConverter/components/ImageOutput.jsx
rename to src/pages/TextConverter/components/ImageOutput.jsx
diff --git a/src/pages/TextBasedConverter/components/MultiHashOutput.jsx b/src/pages/TextConverter/components/MultiHashOutput.jsx
similarity index 100%
rename from src/pages/TextBasedConverter/components/MultiHashOutput.jsx
rename to src/pages/TextConverter/components/MultiHashOutput.jsx
diff --git a/src/pages/TextBasedConverter/constants.js b/src/pages/TextConverter/constants.js
similarity index 100%
rename from src/pages/TextBasedConverter/constants.js
rename to src/pages/TextConverter/constants.js
diff --git a/src/pages/TextBasedConverter/index.jsx b/src/pages/TextConverter/index.jsx
similarity index 100%
rename from src/pages/TextBasedConverter/index.jsx
rename to src/pages/TextConverter/index.jsx
diff --git a/src/pages/TextBasedConverter/strings.js b/src/pages/TextConverter/strings.js
similarity index 100%
rename from src/pages/TextBasedConverter/strings.js
rename to src/pages/TextConverter/strings.js
diff --git a/src/pages/TextDiffChecker.jsx b/src/pages/TextDiffChecker.jsx
index ad47a80..eea3775 100644
--- a/src/pages/TextDiffChecker.jsx
+++ b/src/pages/TextDiffChecker.jsx
@@ -1,85 +1,191 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import * as Diff from 'diff';
-import { Button } from '@carbon/react';
-import { Compare } from '@carbon/icons-react';
+import { Button, ContentSwitcher, Switch } from '@carbon/react';
+import { Compare, Renew } from '@carbon/icons-react';
import { ToolHeader, ToolControls, ToolPane, ToolSplitPane } from '../components/ToolUI';
+import useLayoutToggle from '../hooks/useLayoutToggle';
-const DiffView = ({ diffs }) => (
-
-
{
+ const renderDiff = () => {
+ if (diffs.length === 0) {
+ return
No differences ;
+ }
+
+ return diffs.map((part, index) => {
+ const isAdded = part.added;
+ const isRemoved = part.removed;
+
+ let style = {
+ display: 'block',
+ padding: '1px 4px',
+ margin: '1px 0',
+ borderRadius: '2px',
+ };
+
+ if (isAdded) {
+ style = {
+ ...style,
+ backgroundColor: 'var(--cds-support-success-inverse)',
+ color: 'var(--cds-text-on-color)',
+ };
+ } else if (isRemoved) {
+ style = {
+ ...style,
+ backgroundColor: 'var(--cds-support-error-inverse)',
+ color: 'var(--cds-text-on-color)',
+ };
+ }
+
+ const prefix = isAdded ? '+ ' : isRemoved ? '- ' : ' ';
+
+ return (
+
+ {prefix}
+ {part.value}
+
+ );
+ });
+ };
+
+ return (
+
-
- Differences
-
-
-
+
+ Differences
+
+
+
- {diffs.length === 0
- ? No differences
- : diffs.map((part, index) => {
- const style = {
- backgroundColor: part.added ? 'var(--cds-support-success-inversed)' : part.removed ? 'var(--cds-support-error-inversed)' : 'transparent',
- color: part.added ? 'var(--cds-text-on-color)' : part.removed ? 'var(--cds-text-on-color)' : 'inherit',
- };
- const prefix = part.added ? '+ ' : part.removed ? '- ' : ' ';
- return (
-
- {prefix}
- {part.value}
-
- );
- })}
+ {renderDiff()}
-
-);
+ );
+};
export default function TextDiffChecker() {
const [oldText, setOldText] = useState('');
const [newText, setNewText] = useState('');
const [diffs, setDiffs] = useState([]);
+ const [diffMode, setDiffMode] = useState(0); // 0 = lines, 1 = words, 2 = chars
+
+ const layout = useLayoutToggle({
+ toolKey: 'text-diff-layout',
+ defaultDirection: 'horizontal',
+ showToggle: true,
+ persist: true
+ });
const compare = () => {
- const d = Diff.diffLines(oldText, newText, { newlineIsToken: true });
+ let d;
+ switch (diffMode) {
+ case 0:
+ d = Diff.diffLines(oldText, newText, { newlineIsToken: true });
+ break;
+ case 1:
+ d = Diff.diffWords(oldText, newText);
+ break;
+ case 2:
+ d = Diff.diffChars(oldText, newText);
+ break;
+ default:
+ d = Diff.diffLines(oldText, newText, { newlineIsToken: true });
+ }
setDiffs(d);
};
+ const clearAll = () => {
+ setOldText('');
+ setNewText('');
+ setDiffs([]);
+ };
+
+ // Auto-compare when inputs change
+ useEffect(() => {
+ if (oldText || newText) {
+ compare();
+ } else {
+ setDiffs([]);
+ }
+ }, [oldText, newText, diffMode]);
+
return (
-
-
+
+
- Compare
+
+
+
+ Compare by:
+
+ setDiffMode(index)}
+ size="sm"
+ style={{ width: 'auto', minWidth: '150px' }}
+ >
+
+
+
+
+
+
+
+ Compare
+
+
+
+ Clear
+
+
-
+
-
);
diff --git a/src/pages/UnixTimeConverter.jsx b/src/pages/UnixTimeConverter.jsx
index aee39b6..ddc907e 100644
--- a/src/pages/UnixTimeConverter.jsx
+++ b/src/pages/UnixTimeConverter.jsx
@@ -1,83 +1,409 @@
-import React, { useState } from 'react';
-import { Button, TextInput } from '@carbon/react';
-import { ToolHeader, ToolControls, ToolPane } from '../components/ToolUI';
+import React, { useState, useEffect } from 'react';
+import { Button, TextInput, Tag, Dropdown, TextArea } from '@carbon/react';
+import { Time, Calendar } from '@carbon/icons-react';
+import { ToolHeader, ToolControls, ToolPane, ToolSplitPane } from '../components/ToolUI';
+import useLayoutToggle from '../hooks/useLayoutToggle';
+
+const DATE_FORMATS = [
+ { id: 'iso', label: 'ISO 8601', format: 'YYYY-MM-DDTHH:mm:ss.sssZ' },
+ { id: 'rfc2822', label: 'RFC 2822', format: 'ddd, DD MMM YYYY HH:mm:ss ZZ' },
+ { id: 'sql', label: 'SQL DateTime', format: 'YYYY-MM-DD HH:mm:ss' },
+ { id: 'us', label: 'US Format', format: 'MM/DD/YYYY HH:mm:ss' },
+ { id: 'eu', label: 'EU Format', format: 'DD/MM/YYYY HH:mm:ss' },
+ { id: 'compact', label: 'Compact', format: 'YYYYMMDD-HHmmss' },
+ { id: 'custom', label: 'Custom', format: 'Custom format' },
+];
+
+const TIMEZONES = [
+ { id: 'UTC', label: 'UTC', offset: 0 },
+ { id: 'local', label: 'Local Time', offset: null },
+ { id: 'EST', label: 'EST (New York)', offset: -5 },
+ { id: 'CST', label: 'CST (Chicago)', offset: -6 },
+ { id: 'MST', label: 'MST (Denver)', offset: -7 },
+ { id: 'PST', label: 'PST (Los Angeles)', offset: -8 },
+ { id: 'GMT', label: 'GMT (London)', offset: 0 },
+ { id: 'CET', label: 'CET (Paris)', offset: 1 },
+ { id: 'IST', label: 'IST (India)', offset: 5.5 },
+ { id: 'JST', label: 'JST (Tokyo)', offset: 9 },
+ { id: 'AEST', label: 'AEST (Sydney)', offset: 10 },
+];
+
+const parseDateWithFormat = (input, format) => {
+ if (!input) return null;
+
+ // Try parsing as ISO first
+ let date = new Date(input);
+ if (!isNaN(date.getTime())) return date;
+
+ // Try parsing as timestamp
+ if (!isNaN(input) && input.toString().length >= 10) {
+ const ts = parseInt(input, 10);
+ if (ts > 1000000000) {
+ date = new Date(ts > 9999999999 ? ts : ts * 1000);
+ if (!isNaN(date.getTime())) return date;
+ }
+ }
+
+ return null;
+};
+
+const formatDate = (date, formatId, customFormat) => {
+ if (!date || isNaN(date.getTime())) return '';
+
+ const pad = (n) => n.toString().padStart(2, '0');
+ const year = date.getFullYear();
+ const month = pad(date.getMonth() + 1);
+ const day = pad(date.getDate());
+ const hours = pad(date.getHours());
+ const minutes = pad(date.getMinutes());
+ const seconds = pad(date.getSeconds());
+ const ms = date.getMilliseconds().toString().padStart(3, '0');
+
+ switch (formatId) {
+ case 'iso':
+ return date.toISOString();
+ case 'rfc2822':
+ return date.toUTCString();
+ case 'sql':
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+ case 'us':
+ return `${month}/${day}/${year} ${hours}:${minutes}:${seconds}`;
+ case 'eu':
+ return `${day}/${month}/${year} ${hours}:${minutes}:${seconds}`;
+ case 'compact':
+ return `${year}${month}${day}-${hours}${minutes}${seconds}`;
+ case 'custom':
+ if (customFormat) {
+ // Simple format replacement
+ return customFormat
+ .replace('YYYY', year)
+ .replace('MM', month)
+ .replace('DD', day)
+ .replace('HH', hours)
+ .replace('mm', minutes)
+ .replace('ss', seconds)
+ .replace('sss', ms);
+ }
+ return date.toISOString();
+ default:
+ return date.toISOString();
+ }
+};
+
+const convertToTimezone = (date, timezoneId) => {
+ if (!date || timezoneId === 'local') return date;
+
+ const tz = TIMEZONES.find(t => t.id === timezoneId);
+ if (!tz || tz.offset === null) return date;
+
+ const offsetMs = tz.offset * 60 * 60 * 1000;
+ return new Date(date.getTime() + offsetMs);
+};
export default function UnixTimeConverter() {
- const [now] = useState(Math.floor(Date.now() / 1000));
- const [timestamp, setTimestamp] = useState(now);
- const [dateStr, setDateStr] = useState(new Date(now * 1000).toISOString());
- const [localDateStr, setLocalDateStr] = useState(new Date(now * 1000).toLocaleString());
+ const [timestamp, setTimestamp] = useState('');
+ const [dateStr, setDateStr] = useState('');
+ const [localDateStr, setLocalDateStr] = useState('');
+ const [relativeTime, setRelativeTime] = useState('');
+ const [inputFormat, setInputFormat] = useState('iso');
+ const [outputFormat, setOutputFormat] = useState('iso');
+ const [customInputFormat, setCustomInputFormat] = useState('YYYY-MM-DD HH:mm:ss');
+ const [customOutputFormat, setCustomOutputFormat] = useState('YYYY-MM-DD HH:mm:ss');
+ const [sourceTimezone, setSourceTimezone] = useState('local');
+ const [targetTimezone, setTargetTimezone] = useState('local');
+ const [parsedDate, setParsedDate] = useState(null);
+
+ const layout = useLayoutToggle({
+ toolKey: 'unix-time-layout',
+ defaultDirection: 'horizontal',
+ showToggle: true,
+ persist: true
+ });
+
+ // Initialize with current time
+ useEffect(() => {
+ const now = Math.floor(Date.now() / 1000);
+ handleTsChange(now.toString());
+ }, []);
+
+ const calculateRelativeTime = (date) => {
+ if (!date || isNaN(date.getTime())) return '';
+
+ const now = Date.now();
+ const diff = date.getTime() - now;
+ const absDiff = Math.abs(diff);
+ const seconds = Math.floor(absDiff / 1000);
+ const minutes = Math.floor(seconds / 60);
+ const hours = Math.floor(minutes / 60);
+ const days = Math.floor(hours / 24);
+
+ let relative = '';
+ if (diff > 0) {
+ relative = 'in ';
+ }
+
+ if (days > 0) relative += `${days} day${days > 1 ? 's' : ''} `;
+ else if (hours > 0) relative += `${hours} hour${hours > 1 ? 's' : ''} `;
+ else if (minutes > 0) relative += `${minutes} minute${minutes > 1 ? 's' : ''} `;
+ else relative += `${seconds} second${seconds > 1 ? 's' : ''} `;
+
+ if (diff <= 0) {
+ relative += 'ago';
+ }
+
+ return relative;
+ };
const handleTsChange = (val) => {
setTimestamp(val);
- const d = new Date(val * 1000);
- setDateStr(d.toISOString());
- setLocalDateStr(d.toLocaleString());
+
+ if (!val || val.trim() === '') {
+ setDateStr('');
+ setLocalDateStr('');
+ setRelativeTime('');
+ setParsedDate(null);
+ return;
+ }
+
+ // Try parsing as timestamp first
+ let date;
+ const numVal = parseInt(val, 10);
+
+ if (!isNaN(numVal) && val.length >= 10) {
+ // Assume it's a Unix timestamp
+ const ts = numVal > 9999999999 ? numVal : numVal * 1000;
+ date = new Date(ts);
+ } else {
+ // Try parsing as date string
+ date = parseDateWithFormat(val, inputFormat);
+ }
+
+ if (!date || isNaN(date.getTime())) {
+ setDateStr('Invalid date');
+ setLocalDateStr('Invalid date');
+ setRelativeTime('');
+ setParsedDate(null);
+ return;
+ }
+
+ setParsedDate(date);
+
+ // Convert to source timezone
+ const sourceDate = sourceTimezone === 'local' ? date : convertToTimezone(date, sourceTimezone);
+
+ // Convert to target timezone
+ const targetDate = targetTimezone === 'local' ? sourceDate : convertToTimezone(sourceDate, targetTimezone);
+
+ const formatted = formatDate(targetDate, outputFormat, customOutputFormat);
+ setDateStr(formatted);
+ setLocalDateStr(targetDate.toLocaleString());
+ setRelativeTime(calculateRelativeTime(date));
};
const handleDateChange = (val) => {
setDateStr(val);
- const ts = Math.floor(new Date(val).getTime() / 1000);
- if (!isNaN(ts)) {
- setTimestamp(ts);
- setLocalDateStr(new Date(val).toLocaleString());
+
+ const date = parseDateWithFormat(val, outputFormat);
+
+ if (date && !isNaN(date.getTime())) {
+ setParsedDate(date);
+ const ts = Math.floor(date.getTime() / 1000);
+ setTimestamp(ts.toString());
+ setLocalDateStr(date.toLocaleString());
+ setRelativeTime(calculateRelativeTime(date));
}
};
+ const setNow = () => {
+ const now = Math.floor(Date.now() / 1000);
+ handleTsChange(now.toString());
+ };
+
+ const isValidTimestamp = timestamp && !isNaN(timestamp) && timestamp.length > 0;
+
return (
-
-
+
+
-
-
-
+
+
+
handleTsChange(e.target.value)}
- type="number"
+ placeholder="Enter timestamp or date..."
+ style={{ fontFamily: "'IBM Plex Mono', monospace" }}
/>
+
handleTsChange(Math.floor(Date.now() / 1000))}
+ kind="primary"
+ onClick={setNow}
+ renderIcon={Time}
>
Now
-
+
+
+
+
+
+
+
+ Input Format
+
+ item ? item.label : ''}
+ selectedItem={DATE_FORMATS.find(f => f.id === inputFormat)}
+ onChange={({ selectedItem }) => {
+ if (selectedItem) {
+ setInputFormat(selectedItem.id);
+ }
+ }}
+ size="sm"
+ />
+
+
+ {inputFormat === 'custom' && (
+
+ setCustomInputFormat(e.target.value)}
+ placeholder="YYYY-MM-DD HH:mm:ss"
+ size="sm"
+ />
+
+ )}
+
+
+
+ Source Timezone
+
+ item ? item.label : ''}
+ selectedItem={TIMEZONES.find(t => t.id === sourceTimezone)}
+ onChange={({ selectedItem }) => {
+ if (selectedItem) {
+ setSourceTimezone(selectedItem.id);
+ if (timestamp) handleTsChange(timestamp);
+ }
+ }}
+ size="sm"
+ />
+
+
+
- {/* Reusing ToolPane for consistent copy behavior even for single line inputs if we treat them as panes or keep valid inputs here */}
+
+
+
+
+ Output Format
+
+ item ? item.label : ''}
+ selectedItem={DATE_FORMATS.find(f => f.id === outputFormat)}
+ onChange={({ selectedItem }) => {
+ if (selectedItem) {
+ setOutputFormat(selectedItem.id);
+ if (timestamp) handleTsChange(timestamp);
+ }
+ }}
+ size="sm"
+ />
+
+
+ {outputFormat === 'custom' && (
+
+ setCustomOutputFormat(e.target.value)}
+ placeholder="YYYY-MM-DD HH:mm:ss"
+ size="sm"
+ />
+
+ )}
+
+
+
+ Target Timezone
+
+ item ? item.label : ''}
+ selectedItem={TIMEZONES.find(t => t.id === targetTimezone)}
+ onChange={({ selectedItem }) => {
+ if (selectedItem) {
+ setTargetTimezone(selectedItem.id);
+ if (timestamp) handleTsChange(timestamp);
+ }
+ }}
+ size="sm"
+ />
+
+
+
-
- {/* For single line outputs with copy, we can manually implement or wrap.
- Let's just use ToolPane but with short height style override if we really want to enforce strictness,
- or stick to Carbon TextInput + Copy Button pattern manually since these are single lines.
- However, user asked for CONSISTENCY.
- Let's keep the manual Carbon Input pattern for single lines but ensure the copy button is consistent.
- */}
+ {relativeTime && isValidTimestamp && (
+
+ {relativeTime}
+
+ from now
+
-
+ )}
- {/* Since these are single line outputs, ToolPane might be overkill effectively acting as TextArea.
- But user asked for consistency. Let's provide them as ReadOnly TextAreas for visual consistency with other tools?
- Actually, typical Unix converters use inputs. I'll stick to Inputs but ensure they look good.
- */}
-
+
f.id === outputFormat)?.label || 'ISO'})`}
value={dateStr}
onChange={(e) => handleDateChange(e.target.value)}
- style={{ height: '80px' }} // Single line-ish
+ placeholder="Formatted date will appear here..."
/>
t.id === targetTimezone)?.label || 'Local'})`}
value={localDateStr}
readOnly
- style={{ height: '80px' }}
+ placeholder="Local date and time will appear here..."
/>
-
+
);
}
diff --git a/src/pages/UrlEncoder.jsx b/src/pages/UrlEncoder.jsx
deleted file mode 100644
index 6a12916..0000000
--- a/src/pages/UrlEncoder.jsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { RadioButtonGroup, RadioButton } from '@carbon/react';
-import { ToolControls, ToolPane, ToolSplitPane } from '../components/ToolUI';
-
-export default function UrlEncoder() {
- const [input, setInput] = useState('');
- const [output, setOutput] = useState('');
- const [mode, setMode] = useState('encode');
- const [error, setError] = useState('');
-
- useEffect(() => {
- const process = () => {
- if (!input) {
- setOutput('');
- setError('');
- return;
- }
- try {
- if (mode === 'encode') {
- setOutput(encodeURIComponent(input));
- } else {
- setOutput(decodeURIComponent(input));
- }
- setError('');
- } catch (e) {
- setOutput('');
- setError('Error: ' + e.message);
- }
- };
- process();
- }, [input, mode]);
-
- return (
-
-
-
-
-
-
-
-
- {error &&
{error}
}
-
-
- setInput(e.target.value)}
- placeholder={mode === 'encode' ? "Text to encode..." : "URL to decode..."}
- />
-
-
-
- );
-}
diff --git a/src/pages/UrlParser.jsx b/src/pages/UrlParser.jsx
deleted file mode 100644
index 2d2c00e..0000000
--- a/src/pages/UrlParser.jsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { TextInput } from '@carbon/react';
-import { ToolPane, ToolSplitPane } from '../components/ToolUI';
-
-export default function UrlParser() {
- const [url, setUrl] = useState('');
- const [parsed, setParsed] = useState(null);
- const [error, setError] = useState('');
-
- useEffect(() => {
- if (!url) {
- setParsed(null);
- setError('');
- return;
- }
- try {
- const u = new URL(url);
- const params = {};
- u.searchParams.forEach((v, k) => params[k] = v);
-
- setParsed({
- protocol: u.protocol,
- host: u.host,
- hostname: u.hostname,
- port: u.port,
- pathname: u.pathname,
- search: u.search,
- hash: u.hash,
- origin: u.origin,
- params: params
- });
- setError('');
- } catch (e) {
- setParsed(null);
- setError('Invalid URL provided.');
- }
- }, [url]);
-
- const getFullJson = () => {
- if (!parsed) return '';
- return JSON.stringify({
- ...parsed,
- params: undefined, // remove duplicate
- queryParams: parsed.params,
- }, null, 2);
- }
-
- return (
-
-
setUrl(e.target.value)}
- placeholder="https://example.com/path?query=1"
- />
-
- {error && {error}
}
-
- {parsed && (
-
-
-
-
-
-
-
-
-
-
-
- )}
-
- );
-}
diff --git a/src/pages/UrlTools.jsx b/src/pages/UrlTools.jsx
deleted file mode 100644
index f075689..0000000
--- a/src/pages/UrlTools.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import { Tabs, Tab, TabList, TabPanels, TabPanel } from '@carbon/react';
-import UrlParser from './UrlParser';
-import UrlEncoder from './UrlEncoder';
-import { ToolHeader } from '../components/ToolUI';
-
-export default function UrlTools() {
- return (
-
-
-
-
-
- URL Parser
- URL Encode/Decode
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/wailsjs/go/main/CodeFormatterService.d.ts b/wailsjs/go/main/CodeFormatterService.d.ts
deleted file mode 100755
index 4794ed4..0000000
--- a/wailsjs/go/main/CodeFormatterService.d.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร MODIWL
-// This file is automatically generated. DO NOT EDIT
-import {codeformatter} from '../models';
-
-export function Format(arg1:codeformatter.FormatRequest):Promise;
diff --git a/wailsjs/go/main/CodeFormatterService.js b/wailsjs/go/main/CodeFormatterService.js
deleted file mode 100755
index 453060c..0000000
--- a/wailsjs/go/main/CodeFormatterService.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// @ts-check
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH ร MODIWL
-// This file is automatically generated. DO NOT EDIT
-
-export function Format(arg1) {
- return window['go']['main']['CodeFormatterService']['Format'](arg1);
-}