chore: use monaco editor

This commit is contained in:
Ibrahima G. Coulibaly
2025-07-18 03:26:42 +01:00
parent 64d6628270
commit db87355274
6 changed files with 163 additions and 212 deletions

37
.idea/workspace.xml generated
View File

@@ -4,17 +4,24 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="docs: translation docs">
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="fix: json comparison">
<change afterPath="$PROJECT_DIR$/src/components/input/ToolCodeInput.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/Navbar/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/Navbar/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/input/LineNumberInput.tsx" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/json/json-comparison/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/json/json-comparison/index.tsx" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ChangesViewManager">
<option name="groupingKeys">
<option value="directory" />
</option>
</component>
<component name="Git.Merge.Settings">
<option name="BRANCH" value="origin/main" />
</component>
@@ -272,7 +279,7 @@
"Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run",
"Vitest.replaceText function.executor": "Run",
"Vitest.timeBetweenDates.executor": "Run",
"git-widget-placeholder": "#190 on fork/AshAnand34/en-hi-translation",
"git-widget-placeholder": "#197 on fork/bhavesh158/json-compare",
"ignore.virus.scanning.warn.message": "true",
"kotlin-language-version-configured": "true",
"last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools",
@@ -519,14 +526,6 @@
<workItem from="1752493585622" duration="11629000" />
<workItem from="1752507105323" duration="9008000" />
</task>
<task id="LOCAL-00184" summary="refactor: lib">
<option name="closed" value="true" />
<created>1743644942488</created>
<option name="number" value="00184" />
<option name="presentableId" value="LOCAL-00184" />
<option name="project" value="LOCAL" />
<updated>1743644942488</updated>
</task>
<task id="LOCAL-00185" summary="fix: path">
<option name="closed" value="true" />
<created>1743645074051</created>
@@ -911,7 +910,15 @@
<option name="project" value="LOCAL" />
<updated>1752515675314</updated>
</task>
<option name="localTasksCounter" value="233" />
<task id="LOCAL-00233" summary="fix: json comparison">
<option name="closed" value="true" />
<created>1752803825523</created>
<option name="number" value="00233" />
<option name="presentableId" value="LOCAL-00233" />
<option name="project" value="LOCAL" />
<updated>1752803825523</updated>
</task>
<option name="localTasksCounter" value="234" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -958,7 +965,6 @@
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
<option name="CHECK_NEW_TODO" value="false" />
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
<MESSAGE value="chore: new logo and font" />
<MESSAGE value="chore: white logo" />
<MESSAGE value="chore: png icon" />
<MESSAGE value="fix: remove xml viewer" />
@@ -983,7 +989,8 @@
<MESSAGE value="fix: translation related behaviors" />
<MESSAGE value="feat: password generator to test translation" />
<MESSAGE value="docs: translation docs" />
<option name="LAST_COMMIT_MESSAGE" value="docs: translation docs" />
<MESSAGE value="fix: json comparison" />
<option name="LAST_COMMIT_MESSAGE" value="fix: json comparison" />
</component>
<component name="VgoProject">
<integration-enabled>false</integration-enabled>

37
package-lock.json generated
View File

@@ -15,6 +15,7 @@
"@ffmpeg/util": "^0.12.2",
"@imgly/background-removal": "^1.6.0",
"@jimp/types": "^1.6.0",
"@monaco-editor/react": "^4.7.0",
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
"@playwright/test": "^1.45.0",
@@ -2104,6 +2105,29 @@
"dev": true,
"license": "MIT"
},
"node_modules/@monaco-editor/loader": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz",
"integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==",
"license": "MIT",
"dependencies": {
"state-local": "^1.0.6"
}
},
"node_modules/@monaco-editor/react": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz",
"integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==",
"license": "MIT",
"dependencies": {
"@monaco-editor/loader": "^1.5.0"
},
"peerDependencies": {
"monaco-editor": ">= 0.25.0 < 1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@mui/base": {
"version": "5.0.0-beta.40",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz",
@@ -8902,6 +8926,13 @@
"ufo": "^1.5.3"
}
},
"node_modules/monaco-editor": {
"version": "0.52.2",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
"integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==",
"license": "MIT",
"peer": true
},
"node_modules/morsee": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/morsee/-/morsee-1.0.9.tgz",
@@ -11271,6 +11302,12 @@
"node": ">=6"
}
},
"node_modules/state-local": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
"license": "MIT"
},
"node_modules/std-env": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",

View File

@@ -35,6 +35,7 @@
"@ffmpeg/util": "^0.12.2",
"@imgly/background-removal": "^1.6.0",
"@jimp/types": "^1.6.0",
"@monaco-editor/react": "^4.7.0",
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
"@playwright/test": "^1.45.0",

View File

@@ -1,127 +0,0 @@
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<HTMLInputElement>(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<HTMLInputElement>) => {
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 (
<Box>
<InputHeader title={title || t('toolTextInput.input')} />
<LineNumberWrapper>
<pre className="line-numbers">{lineNumbers}</pre>
<TextField
value={value}
onChange={(event) => onChange(event.target.value)}
fullWidth
multiline
rows={10}
placeholder={placeholder || t('toolTextInput.placeholder')}
sx={{
'&.MuiTextField-root': {
backgroundColor: 'background.paper'
}
}}
inputProps={{
'data-testid': 'text-input'
}}
/>
</LineNumberWrapper>
<InputFooter handleCopy={handleCopy} handleImport={handleImportClick} />
<input
type="file"
accept="*"
ref={fileInputRef}
style={{ display: 'none' }}
onChange={handleFileChange}
/>
</Box>
);
}

View File

@@ -0,0 +1,73 @@
import { Box } 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';
import Editor from '@monaco-editor/react';
import { globalInputHeight } from '../../config/uiConfig';
export default function ToolCodeInput({
value,
onChange,
title = 'Input text',
language
}: {
title?: string;
value: string;
language: string;
onChange: (value: string) => void;
}) {
const { t } = useTranslation();
const { showSnackBar } = useContext(CustomSnackBarContext);
const fileInputRef = useRef<HTMLInputElement>(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<HTMLInputElement>) => {
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();
};
return (
<Box>
<InputHeader title={title || t('toolTextInput.input')} />
<Box height={globalInputHeight}>
<Editor
height={'87%'}
language={language}
value={value}
onChange={(value) => onChange(value ?? '')}
/>
<InputFooter handleCopy={handleCopy} handleImport={handleImportClick} />
<input
type="file"
accept="*"
ref={fileInputRef}
style={{ display: 'none' }}
onChange={handleFileChange}
/>
</Box>
</Box>
);
}

View File

@@ -1,42 +1,10 @@
import { useState, useEffect } from 'react';
import { useEffect, useState } from 'react';
import ToolContent from '@components/ToolContent';
import LineNumberInput from '@components/input/LineNumberInput';
import ToolCodeInput from '@components/input/ToolCodeInput';
import ToolTextResult from '@components/result/ToolTextResult';
import { compareJson } from './service';
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'
}
}
});
import { Grid } from '@mui/material';
type InitialValuesType = {};
@@ -73,8 +41,8 @@ export default function JsonComparison({ title }: ToolComponentProps) {
compareInputs();
}, [input1, input2]);
const handleInput1Change = (value: string) => {
setInput1(value);
const handleInput1Change = (value: string | undefined) => {
setInput1(value ?? '');
};
const handleInput2Change = (value: string) => {
@@ -90,39 +58,31 @@ export default function JsonComparison({ title }: ToolComponentProps) {
getGroups={null}
compute={() => {}}
inputComponent={
<StyledContainer>
<StyledGrid container spacing={2}>
<Grid item xs={4}>
<StyledInputWrapper>
<LineNumberInput
title="First JSON"
value={input1}
onChange={handleInput1Change}
placeholder="Paste your first JSON here..."
/>
</StyledInputWrapper>
</Grid>
<Grid item xs={4}>
<StyledInputWrapper>
<LineNumberInput
title="Second JSON"
value={input2}
onChange={handleInput2Change}
placeholder="Paste your second JSON here..."
/>
</StyledInputWrapper>
</Grid>
<Grid item xs={4}>
<StyledInputWrapper>
<ToolTextResult
title="Differences"
value={result}
extension={'txt'}
/>
</StyledInputWrapper>
</Grid>
</StyledGrid>
</StyledContainer>
<Grid container spacing={2}>
<Grid item xs={12} md={6} lg={4}>
<ToolCodeInput
title="First JSON"
value={input1}
onChange={handleInput1Change}
language={'json'}
/>
</Grid>
<Grid item xs={12} md={6} lg={4}>
<ToolCodeInput
title="Second JSON"
language={'json'}
value={input2}
onChange={handleInput2Change}
/>
</Grid>
<Grid item xs={12} md={12} lg={4}>
<ToolTextResult
title="Differences"
value={result}
extension={'txt'}
/>
</Grid>
</Grid>
}
/>
);