From 91bc7ac12223fc36bbddf8526885dd8161a1f3f7 Mon Sep 17 00:00:00 2001 From: Bhavesh Kshatriya Date: Tue, 15 Jul 2025 15:22:10 +0530 Subject: [PATCH 1/5] feat: add JSON comparison tool --- src/pages/tools/json/index.ts | 4 +- .../tools/json/json-comparison/index.tsx | 157 +++++++++++++ src/pages/tools/json/json-comparison/meta.ts | 13 ++ .../json/json-comparison/service.test.ts | 64 ++++++ .../tools/json/json-comparison/service.ts | 212 ++++++++++++++++++ 5 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 src/pages/tools/json/json-comparison/index.tsx create mode 100644 src/pages/tools/json/json-comparison/meta.ts create mode 100644 src/pages/tools/json/json-comparison/service.test.ts create mode 100644 src/pages/tools/json/json-comparison/service.ts diff --git a/src/pages/tools/json/index.ts b/src/pages/tools/json/index.ts index 04a7940..9aba4f8 100644 --- a/src/pages/tools/json/index.ts +++ b/src/pages/tools/json/index.ts @@ -5,6 +5,7 @@ import { tool as validateJson } from './validateJson/meta'; import { tool as jsonToXml } from './json-to-xml/meta'; import { tool as escapeJson } from './escape-json/meta'; import { tool as tsvToJson } from './tsv-to-json/meta'; +import { tool as jsonComparison } from './json-comparison/meta'; export const jsonTools = [ validateJson, @@ -13,5 +14,6 @@ export const jsonTools = [ jsonStringify, jsonToXml, escapeJson, - tsvToJson + tsvToJson, + jsonComparison ]; diff --git a/src/pages/tools/json/json-comparison/index.tsx b/src/pages/tools/json/json-comparison/index.tsx new file mode 100644 index 0000000..1c5e97b --- /dev/null +++ b/src/pages/tools/json/json-comparison/index.tsx @@ -0,0 +1,157 @@ +import { useState, useEffect } from 'react'; +import ToolContent from '@components/ToolContent'; +import ToolTextInput from '@components/input/ToolTextInput'; +import ToolTextResult from '@components/result/ToolTextResult'; +import { compareJson } from './service'; +import { CardExampleType } from '@components/examples/ToolExamples'; +import { ToolComponentProps } from '@tools/defineTool'; +import { Box, Grid, styled } from '@mui/material'; + +const StyledContainer = styled(Box)({ + position: 'relative', + width: '100%', + height: '100%', + display: 'flex', + flexDirection: 'column', + minHeight: '500px', + marginBottom: '20px' +}); + +const StyledGrid = styled(Grid)({ + flex: 1, + '& .MuiGrid-item': { + height: '100%' + } +}); + +const StyledInputWrapper = styled(Box)({ + height: '100%', + '& > div': { + height: '100%', + '& textarea': { + height: '100% !important', + minHeight: '450px', + resize: 'none', + fontSize: '14px', + lineHeight: '1.5', + padding: '12px' + } + } +}); + +type InitialValuesType = {}; + +const initialValues: InitialValuesType = {}; + +const exampleCards: CardExampleType[] = [ + { + title: 'Compare Simple JSON Objects', + description: + 'Compare two JSON objects to find differences in their structure and values.', + sampleText: `{ + "name": "John", + "age": 30, + "address": { + "city": "New York", + "country": "USA" + } +}`, + sampleResult: `{ + "name": "John", + "age": 25, + "address": { + "city": "London", + "country": "UK" + } +}`, + sampleOptions: { + ...initialValues + } + } +]; + +export default function JsonComparison({ title }: ToolComponentProps) { + const [input1, setInput1] = useState(''); + const [input2, setInput2] = useState(''); + const [result, setResult] = useState(''); + + useEffect(() => { + const compareInputs = () => { + try { + // Only compare if at least one input has content + if (input1.trim() || input2.trim()) { + const differences = compareJson( + input1 || '{}', + input2 || '{}', + 'text' + ); + setResult(differences); + } else { + setResult(''); + } + } catch (error) { + setResult( + `Error: ${ + error instanceof Error ? error.message : 'Invalid JSON format' + }` + ); + } + }; + + compareInputs(); + }, [input1, input2]); + + const handleInput1Change = (value: string) => { + setInput1(value); + }; + + const handleInput2Change = (value: string) => { + setInput2(value); + }; + + return ( + {}} + exampleCards={exampleCards} + inputComponent={ + + + + + + + + + + + + + + + + + + + + } + /> + ); +} diff --git a/src/pages/tools/json/json-comparison/meta.ts b/src/pages/tools/json/json-comparison/meta.ts new file mode 100644 index 0000000..dc9cc60 --- /dev/null +++ b/src/pages/tools/json/json-comparison/meta.ts @@ -0,0 +1,13 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('json', { + name: 'Compare JSON', + path: 'json-comparison', + icon: 'fluent:branch-compare-24-regular', + description: + 'Compare two JSON objects to identify differences in structure and values.', + shortDescription: 'Find differences between two JSON objects', + keywords: ['json', 'compare', 'diff', 'differences', 'match', 'validation'], + component: lazy(() => import('./index')) +}); diff --git a/src/pages/tools/json/json-comparison/service.test.ts b/src/pages/tools/json/json-comparison/service.test.ts new file mode 100644 index 0000000..1e673ca --- /dev/null +++ b/src/pages/tools/json/json-comparison/service.test.ts @@ -0,0 +1,64 @@ +import { compareJson } from './service'; + +describe('compareJson', () => { + it('should identify missing properties', () => { + const json1 = '{"name": "John", "age": 30}'; + const json2 = '{"name": "John"}'; + + expect(compareJson(json1, json2, 'text')).toContain( + 'age: Missing in second JSON' + ); + }); + + it('should identify value mismatches', () => { + const json1 = '{"name": "John", "age": 30}'; + const json2 = '{"name": "John", "age": 25}'; + + expect(compareJson(json1, json2, 'text')).toContain( + 'age: Mismatch: 30 != 25' + ); + }); + + it('should handle nested objects', () => { + const json1 = '{"person": {"name": "John", "age": 30}}'; + const json2 = '{"person": {"name": "Jane", "age": 30}}'; + + expect(compareJson(json1, json2, 'text')).toContain( + 'person.name: Mismatch: John != Jane' + ); + }); + + it('should return JSON format when specified', () => { + const json1 = '{"name": "John", "age": 30}'; + const json2 = '{"name": "Jane", "age": 25}'; + + const result = compareJson(json1, json2, 'json'); + const parsed = JSON.parse(result); + + expect(parsed).toHaveProperty('name'); + expect(parsed).toHaveProperty('age'); + }); + + it('should handle arrays', () => { + const json1 = '{"numbers": [1, 2, 3]}'; + const json2 = '{"numbers": [1, 2, 4]}'; + + expect(compareJson(json1, json2, 'text')).toContain( + 'numbers.2: Mismatch: 3 != 4' + ); + }); + + it('should return "No differences found" for identical JSONs', () => { + const json1 = '{"name": "John", "age": 30}'; + const json2 = '{"name": "John", "age": 30}'; + + expect(compareJson(json1, json2, 'text')).toBe('No differences found'); + }); + + it('should throw error for invalid JSON', () => { + const json1 = '{"name": "John"'; + const json2 = '{"name": "John"}'; + + expect(() => compareJson(json1, json2, 'text')).toThrow(); + }); +}); diff --git a/src/pages/tools/json/json-comparison/service.ts b/src/pages/tools/json/json-comparison/service.ts new file mode 100644 index 0000000..cb69b38 --- /dev/null +++ b/src/pages/tools/json/json-comparison/service.ts @@ -0,0 +1,212 @@ +const fixTrailingCommas = (json: string): string => { + // Replace trailing commas in objects and arrays with proper JSON syntax + return json + .replace(/,\s*([}\]])/g, '$1') // Remove trailing commas in objects and arrays + .replace(/,\s*\n\s*([}\]])/g, '\n$1'); // Also handle when the closing bracket is on a new line +}; + +const tryParseJSON = ( + json: string +): { valid: boolean; data?: any; error?: string } => { + if (!json.trim()) { + return { valid: true, data: {} }; + } + + try { + // Try to parse after fixing trailing commas + const fixedJson = fixTrailingCommas(json); + const data = JSON.parse(fixedJson); + return { valid: true, data }; + } catch (error) { + return { + valid: false, + error: + error instanceof SyntaxError + ? `Invalid JSON: ${error.message}` + : 'Invalid JSON format' + }; + } +}; + +export const compareJson = ( + json1: string, + json2: string, + format: 'text' | 'json' +): string => { + // Handle empty inputs + if (!json1.trim() && !json2.trim()) return ''; + + // Parse both JSON inputs + const parsed1 = tryParseJSON(json1); + const parsed2 = tryParseJSON(json2); + + // Handle parsing errors + if (!parsed1.valid || !parsed2.valid) { + throw new Error( + [ + parsed1.valid ? null : parsed1.error, + parsed2.valid ? null : parsed2.error + ] + .filter(Boolean) + .join('\n') + ); + } + + // Compare the valid JSON objects + if (format === 'json') { + const diffs = findDifferencesJSON(parsed1.data, parsed2.data); + return JSON.stringify(diffs); + } else { + const differences = findDifferencesText(parsed1.data, parsed2.data); + if (differences.length === 0) { + return 'No differences found'; + } + return differences.join('\n'); + } +}; + +const findDifferencesText = ( + obj1: any, + obj2: any, + path: string[] = [] +): string[] => { + const differences: string[] = []; + const processPath = (p: string[]): string => + p.length ? p.join('.') : 'root'; + + // Compare all keys in obj1 + for (const key in obj1) { + const currentPath = [...path, key]; + + if (!(key in obj2)) { + differences.push(`${processPath(currentPath)}: Missing in second JSON`); + continue; + } + + const value1 = obj1[key]; + const value2 = obj2[key]; + + if ( + typeof value1 === 'object' && + value1 !== null && + typeof value2 === 'object' && + value2 !== null + ) { + differences.push(...findDifferencesText(value1, value2, currentPath)); + } else if (value1 !== value2) { + differences.push( + `${processPath(currentPath)}: Mismatch: ${value1} != ${value2}` + ); + } + } + + // Check for keys in obj2 that don't exist in obj1 + for (const key in obj2) { + if (!(key in obj1)) { + const currentPath = [...path, key]; + differences.push(`${processPath(currentPath)}: Missing in first JSON`); + } + } + + return differences; +}; + +const findDifferencesJSON = (obj1: any, obj2: any): Record => { + const result: Record = {}; + + // Compare all properties + const allKeys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]); + + for (const key of allKeys) { + if (!(key in obj1)) { + result[key] = 'Missing in first JSON'; + } else if (!(key in obj2)) { + result[key] = 'Missing in second JSON'; + } else if (obj1[key] !== obj2[key]) { + result[key] = `Mismatch: ${obj1[key]} != ${obj2[key]}`; + } + } + + return result; +}; + +const findDifferences = ( + obj1: any, + obj2: any, + path: string[] = [] +): string[] => { + const differences: string[] = []; + + // Helper to format values for display + const formatValue = (value: any): string => { + if (value === undefined) return 'undefined'; + if (value === null) return 'null'; + if (typeof value === 'string') return `"${value}"`; + return String(value); + }; + + // Helper to get type description + const getTypeDescription = (value: any): string => { + if (value === null) return 'null'; + if (Array.isArray(value)) return 'array'; + return typeof value; + }; + + const processPath = (p: string[]): string => + p.length ? p.join('.') : 'root'; + + // Compare all keys in obj1 + for (const key in obj1) { + const currentPath = [...path, key]; + + if (!(key in obj2)) { + differences.push( + `Property ${processPath( + currentPath + )} exists only in first JSON:\n ${formatValue(obj1[key])}` + ); + continue; + } + + const value1 = obj1[key]; + const value2 = obj2[key]; + const type1 = getTypeDescription(value1); + const type2 = getTypeDescription(value2); + + if (type1 !== type2) { + differences.push( + `Type mismatch at ${processPath( + currentPath + )}:\n First: ${type1} (${formatValue( + value1 + )})\n Second: ${type2} (${formatValue(value2)})` + ); + continue; + } + + if (type1 === 'object' || type1 === 'array') { + const childDiffs = findDifferences(value1, value2, currentPath); + differences.push(...childDiffs); + } else if (value1 !== value2) { + differences.push( + `Value mismatch at ${processPath(currentPath)}:\n First: ${formatValue( + value1 + )}\n Second: ${formatValue(value2)}` + ); + } + } + + // Check for keys in obj2 that don't exist in obj1 + for (const key in obj2) { + if (!(key in obj1)) { + const currentPath = [...path, key]; + differences.push( + `Property ${processPath( + currentPath + )} exists only in second JSON:\n ${formatValue(obj2[key])}` + ); + } + } + + return differences; +}; From 2c5972023f884d7dc9ef767edccb4ac60727a53d Mon Sep 17 00:00:00 2001 From: Bhavesh Kshatriya Date: Wed, 16 Jul 2025 00:20:01 +0530 Subject: [PATCH 2/5] fix: add JSON comparison translations --- public/locales/en/json.json | 5 +++++ public/locales/hi/json.json | 5 +++++ src/pages/tools/json/json-comparison/meta.ts | 12 +++++++----- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/public/locales/en/json.json b/public/locales/en/json.json index 634742f..8796f27 100644 --- a/public/locales/en/json.json +++ b/public/locales/en/json.json @@ -58,5 +58,10 @@ "title": "What is JSON Validation?" }, "validJson": "✅ Valid JSON" + }, + "comparison": { + "title": "Compare JSON", + "description": "Compare two JSON objects to identify differences in structure and values.", + "shortDescription": "Find differences between two JSON objects" } } diff --git a/public/locales/hi/json.json b/public/locales/hi/json.json index 448d41c..c8b5550 100644 --- a/public/locales/hi/json.json +++ b/public/locales/hi/json.json @@ -76,5 +76,10 @@ "strictMode": "सख्त मोड", "title": "JSON मान्य करें", "validationOptions": "मान्यता विकल्प" + }, + "comparison": { + "title": "JSON तुलना करें", + "description": "दो JSON वस्तुओं की संरचना और मूल्यों में अंतर की पहचान करें।", + "shortDescription": "दो JSON वस्तुओं के बीच अंतर ढूंढें" } } diff --git a/src/pages/tools/json/json-comparison/meta.ts b/src/pages/tools/json/json-comparison/meta.ts index dc9cc60..6172c31 100644 --- a/src/pages/tools/json/json-comparison/meta.ts +++ b/src/pages/tools/json/json-comparison/meta.ts @@ -2,12 +2,14 @@ import { defineTool } from '@tools/defineTool'; import { lazy } from 'react'; export const tool = defineTool('json', { - name: 'Compare JSON', path: 'json-comparison', icon: 'fluent:branch-compare-24-regular', - description: - 'Compare two JSON objects to identify differences in structure and values.', - shortDescription: 'Find differences between two JSON objects', keywords: ['json', 'compare', 'diff', 'differences', 'match', 'validation'], - component: lazy(() => import('./index')) + component: lazy(() => import('./index')), + + i18n: { + name: 'json:comparison.title', + description: 'json:comparison.description', + shortDescription: 'json:comparison.shortDescription' + } }); From 91d743bafce54fd1cc78be3869a0263274cb2724 Mon Sep 17 00:00:00 2001 From: Bhavesh Kshatriya Date: Wed, 16 Jul 2025 00:45:31 +0530 Subject: [PATCH 3/5] fix: improve JSON comparison error reporting and add line numbers --- src/components/input/LineNumberInput.tsx | 127 ++++++++++++++++++ .../tools/json/json-comparison/index.tsx | 6 +- .../tools/json/json-comparison/service.ts | 32 +++-- 3 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 src/components/input/LineNumberInput.tsx diff --git a/src/components/input/LineNumberInput.tsx b/src/components/input/LineNumberInput.tsx new file mode 100644 index 0000000..7adc33e --- /dev/null +++ b/src/components/input/LineNumberInput.tsx @@ -0,0 +1,127 @@ +import { Box, styled, TextField } from '@mui/material'; +import React, { useContext, useRef } from 'react'; +import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext'; +import InputHeader from '../InputHeader'; +import InputFooter from './InputFooter'; +import { useTranslation } from 'react-i18next'; + +const LineNumberWrapper = styled(Box)(({ theme }) => ({ + position: 'relative', + display: 'flex', + backgroundColor: theme.palette.background.paper, + '.line-numbers': { + whiteSpace: 'pre', + position: 'absolute', + left: 0, + top: 0, + bottom: 0, + width: '40px', + backgroundColor: theme.palette.action.hover, + borderRight: `1px solid ${theme.palette.divider}`, + textAlign: 'right', + paddingRight: '8px', + paddingTop: '16px', // Align with TextField content + paddingBottom: '8px', + color: theme.palette.text.secondary, + userSelect: 'none', + fontSize: '14px', + lineHeight: '1.5em', + fontFamily: 'monospace', + zIndex: 1, + overflow: 'hidden' + }, + '.MuiTextField-root': { + position: 'relative', + '& .MuiInputBase-root': { + paddingLeft: '48px', + fontFamily: 'monospace', + fontSize: '14px', + lineHeight: '1.5em' + }, + '& .MuiInputBase-input': { + lineHeight: '1.5em' + } + } +})); + +export default function LineNumberInput({ + value, + onChange, + title = 'Input text', + placeholder +}: { + title?: string; + value: string; + onChange: (value: string) => void; + placeholder?: string; +}) { + const { t } = useTranslation(); + const { showSnackBar } = useContext(CustomSnackBarContext); + const fileInputRef = useRef(null); + + const handleCopy = () => { + navigator.clipboard + .writeText(value) + .then(() => showSnackBar(t('toolTextInput.copied'), 'success')) + .catch((err) => { + showSnackBar(t('toolTextInput.copyFailed', { error: err }), 'error'); + }); + }; + + const handleFileChange = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + const text = e.target?.result; + if (typeof text === 'string') { + onChange(text); + } + }; + reader.readAsText(file); + } + }; + + const handleImportClick = () => { + fileInputRef.current?.click(); + }; + + // Generate line numbers based on the content + const lineCount = value.split('\n').length; + const lineNumbers = Array.from({ length: lineCount }, (_, i) => i + 1).join( + '\n' + ); + + return ( + + + +
{lineNumbers}
+ onChange(event.target.value)} + fullWidth + multiline + rows={10} + placeholder={placeholder || t('toolTextInput.placeholder')} + sx={{ + '&.MuiTextField-root': { + backgroundColor: 'background.paper' + } + }} + inputProps={{ + 'data-testid': 'text-input' + }} + /> +
+ + +
+ ); +} diff --git a/src/pages/tools/json/json-comparison/index.tsx b/src/pages/tools/json/json-comparison/index.tsx index 1c5e97b..a51af22 100644 --- a/src/pages/tools/json/json-comparison/index.tsx +++ b/src/pages/tools/json/json-comparison/index.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import ToolContent from '@components/ToolContent'; -import ToolTextInput from '@components/input/ToolTextInput'; +import LineNumberInput from '@components/input/LineNumberInput'; import ToolTextResult from '@components/result/ToolTextResult'; import { compareJson } from './service'; import { CardExampleType } from '@components/examples/ToolExamples'; @@ -122,7 +122,7 @@ export default function JsonComparison({ title }: ToolComponentProps) { - - Date: Fri, 18 Jul 2025 02:57:01 +0100 Subject: [PATCH 4/5] fix: json comparison --- .../tools/json/json-comparison/index.tsx | 30 +------ .../tools/json/json-comparison/service.ts | 81 ------------------- 2 files changed, 1 insertion(+), 110 deletions(-) diff --git a/src/pages/tools/json/json-comparison/index.tsx b/src/pages/tools/json/json-comparison/index.tsx index a51af22..c3b4878 100644 --- a/src/pages/tools/json/json-comparison/index.tsx +++ b/src/pages/tools/json/json-comparison/index.tsx @@ -3,7 +3,6 @@ import ToolContent from '@components/ToolContent'; import LineNumberInput from '@components/input/LineNumberInput'; import ToolTextResult from '@components/result/ToolTextResult'; import { compareJson } from './service'; -import { CardExampleType } from '@components/examples/ToolExamples'; import { ToolComponentProps } from '@tools/defineTool'; import { Box, Grid, styled } from '@mui/material'; @@ -43,33 +42,6 @@ type InitialValuesType = {}; const initialValues: InitialValuesType = {}; -const exampleCards: CardExampleType[] = [ - { - title: 'Compare Simple JSON Objects', - description: - 'Compare two JSON objects to find differences in their structure and values.', - sampleText: `{ - "name": "John", - "age": 30, - "address": { - "city": "New York", - "country": "USA" - } -}`, - sampleResult: `{ - "name": "John", - "age": 25, - "address": { - "city": "London", - "country": "UK" - } -}`, - sampleOptions: { - ...initialValues - } - } -]; - export default function JsonComparison({ title }: ToolComponentProps) { const [input1, setInput1] = useState(''); const [input2, setInput2] = useState(''); @@ -115,8 +87,8 @@ export default function JsonComparison({ title }: ToolComponentProps) { input={input1} setInput={setInput1} initialValues={initialValues} + getGroups={null} compute={() => {}} - exampleCards={exampleCards} inputComponent={ diff --git a/src/pages/tools/json/json-comparison/service.ts b/src/pages/tools/json/json-comparison/service.ts index cc3174b..6141971 100644 --- a/src/pages/tools/json/json-comparison/service.ts +++ b/src/pages/tools/json/json-comparison/service.ts @@ -137,84 +137,3 @@ const findDifferencesJSON = (obj1: any, obj2: any): Record => { return result; }; - -const findDifferences = ( - obj1: any, - obj2: any, - path: string[] = [] -): string[] => { - const differences: string[] = []; - - // Helper to format values for display - const formatValue = (value: any): string => { - if (value === undefined) return 'undefined'; - if (value === null) return 'null'; - if (typeof value === 'string') return `"${value}"`; - return String(value); - }; - - // Helper to get type description - const getTypeDescription = (value: any): string => { - if (value === null) return 'null'; - if (Array.isArray(value)) return 'array'; - return typeof value; - }; - - const processPath = (p: string[]): string => - p.length ? p.join('.') : 'root'; - - // Compare all keys in obj1 - for (const key in obj1) { - const currentPath = [...path, key]; - - if (!(key in obj2)) { - differences.push( - `Property ${processPath( - currentPath - )} exists only in first JSON:\n ${formatValue(obj1[key])}` - ); - continue; - } - - const value1 = obj1[key]; - const value2 = obj2[key]; - const type1 = getTypeDescription(value1); - const type2 = getTypeDescription(value2); - - if (type1 !== type2) { - differences.push( - `Type mismatch at ${processPath( - currentPath - )}:\n First: ${type1} (${formatValue( - value1 - )})\n Second: ${type2} (${formatValue(value2)})` - ); - continue; - } - - if (type1 === 'object' || type1 === 'array') { - const childDiffs = findDifferences(value1, value2, currentPath); - differences.push(...childDiffs); - } else if (value1 !== value2) { - differences.push( - `Value mismatch at ${processPath(currentPath)}:\n First: ${formatValue( - value1 - )}\n Second: ${formatValue(value2)}` - ); - } - } - - // Check for keys in obj2 that don't exist in obj1 - for (const key in obj2) { - if (!(key in obj1)) { - const currentPath = [...path, key]; - differences.push( - `Property ${processPath( - currentPath - )} exists only in second JSON:\n ${formatValue(obj2[key])}` - ); - } - } - - return differences; -}; From db8735527469d01ac9588d9513c3a2541c3c0a6c Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Fri, 18 Jul 2025 03:26:42 +0100 Subject: [PATCH 5/5] chore: use monaco editor --- .idea/workspace.xml | 37 ++--- package-lock.json | 37 +++++ package.json | 1 + src/components/input/LineNumberInput.tsx | 127 ------------------ src/components/input/ToolCodeInput.tsx | 73 ++++++++++ .../tools/json/json-comparison/index.tsx | 100 +++++--------- 6 files changed, 163 insertions(+), 212 deletions(-) delete mode 100644 src/components/input/LineNumberInput.tsx create mode 100644 src/components/input/ToolCodeInput.tsx diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 7c01f00..fdef5fc 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,17 +4,24 @@