Compare commits
2
Commits
3556eaed5e
...
b84f81611a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b84f81611a | ||
|
|
f4ab75fd30 |
No files matched your search
+6
-4
@@ -1,7 +1,6 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ThemeSwitcher from '@/Components/ThemeSwitcher';
|
import ThemeSwitcher from '@/Components/ThemeSwitcher';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
|
|
||||||
@@ -13,9 +12,12 @@ const Header = () => {
|
|||||||
|
|
||||||
<div className={ 'p-3 px-5' }>
|
<div className={ 'p-3 px-5' }>
|
||||||
<div className={ 'flex flex-row items-center justify-between align-baseline' }>
|
<div className={ 'flex flex-row items-center justify-between align-baseline' }>
|
||||||
<div className={ 'font-bold text-2xl' }>
|
<Link
|
||||||
|
className={ 'font-bold text-2xl' }
|
||||||
|
href={ '/' }
|
||||||
|
>
|
||||||
LMTLab
|
LMTLab
|
||||||
</div>
|
</Link>
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
CheckboxGroup,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
Textarea
|
||||||
|
} from '@nextui-org/react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { HiOutlineDownload, HiOutlineDuplicate } from 'react-icons/hi';
|
||||||
|
|
||||||
|
const workflows = new Map<string, (json: string) => string>([
|
||||||
|
[ 'minify', (json) => {
|
||||||
|
try {
|
||||||
|
const value = JSON.parse(json);
|
||||||
|
return JSON.stringify(value);
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(`Operation Failed: ${ e }`, { autoClose: 5000 });
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
return e.toString();
|
||||||
|
}
|
||||||
|
} ]
|
||||||
|
]);
|
||||||
|
|
||||||
|
type Props = { isOpen: boolean, onOpenChange: () => void, value: string };
|
||||||
|
|
||||||
|
const ExportModal = ({ isOpen, onOpenChange, value }: Props) => {
|
||||||
|
const [ workflow, setWorkflow ] = useState<string[]>([]);
|
||||||
|
const [ editorValue, setEditorValue ] = useState(value);
|
||||||
|
useEffect(() => {
|
||||||
|
setEditorValue(value);
|
||||||
|
}, [ value ]);
|
||||||
|
|
||||||
|
const [ exportValue, setExportValue ] = useState(editorValue);
|
||||||
|
useEffect(() => {
|
||||||
|
let value = editorValue;
|
||||||
|
for (const fn of workflow) {
|
||||||
|
const func = workflows.get(fn);
|
||||||
|
value = func ? func(value) : value;
|
||||||
|
}
|
||||||
|
setExportValue(value);
|
||||||
|
}, [ workflow, editorValue ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={ isOpen }
|
||||||
|
onOpenChange={ onOpenChange }
|
||||||
|
size={ '4xl' }
|
||||||
|
hideCloseButton
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
{ (onClose) => (
|
||||||
|
<>
|
||||||
|
<ModalHeader className="gap-1">Export</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<CheckboxGroup
|
||||||
|
label={ 'Config' }
|
||||||
|
color={ 'primary' }
|
||||||
|
value={ workflow }
|
||||||
|
onChange={ setWorkflow }
|
||||||
|
>
|
||||||
|
<Checkbox value={ 'minify' }>Minify</Checkbox>
|
||||||
|
</CheckboxGroup>
|
||||||
|
|
||||||
|
<Textarea
|
||||||
|
label={ 'Value' }
|
||||||
|
variant={ 'flat' }
|
||||||
|
value={ exportValue }
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={ 'mt-3 flex flex-col md:flex-row gap-1 justify-center' }
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
color={ 'primary' }
|
||||||
|
aria-label={ 'Download' }
|
||||||
|
startContent={ <HiOutlineDownload size={ 20 } /> }
|
||||||
|
onClick={ () => {
|
||||||
|
const blob = new Blob([ exportValue ], { type: 'application/json' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = 'value.json';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
toast.success('File Downloaded');
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color={ 'success' }
|
||||||
|
aria-label={ 'Copy' }
|
||||||
|
startContent={ <HiOutlineDuplicate size={ 20 } /> }
|
||||||
|
onClick={ () => {
|
||||||
|
navigator.clipboard.writeText(exportValue);
|
||||||
|
toast.success('Copied to Clipboard');
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="danger" variant="light" onPress={ onClose }>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</>
|
||||||
|
) }
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExportModal;
|
||||||
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import MonacoEditor from '@/app/public/MonacoEditor';
|
||||||
|
import { Button, useDisclosure } from '@nextui-org/react';
|
||||||
|
import ExportModal from '@/app/jsonparser/ExportModal';
|
||||||
|
import { HiOutlineExternalLink } from 'react-icons/hi';
|
||||||
|
|
||||||
|
const DEFAULT_EDITOR_VALUE = '{}';
|
||||||
|
|
||||||
|
const JsonEditor = () => {
|
||||||
|
const [ editorValue, setEditorValue ] = useState<string>(DEFAULT_EDITOR_VALUE);
|
||||||
|
const { isOpen, onOpen, onOpenChange } = useDisclosure();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={ 'rounded-2xl shadow-2xl overflow-hidden relative' }>
|
||||||
|
<MonacoEditor language={ 'json' } defaultValue={ DEFAULT_EDITOR_VALUE } setValue={ setEditorValue } />
|
||||||
|
|
||||||
|
<div className={ 'absolute top-4 right-4 z-10' }>
|
||||||
|
<Button
|
||||||
|
aria-label={ 'export' }
|
||||||
|
variant={ 'light' }
|
||||||
|
isIconOnly
|
||||||
|
onPress={ onOpen }
|
||||||
|
startContent={ <HiOutlineExternalLink size={ 20 } /> } />
|
||||||
|
<ExportModal isOpen={ isOpen } onOpenChange={ onOpenChange } value={ editorValue } />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default JsonEditor;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MonacoEditor from '@/app/public/MonacoEditor';
|
import JsonEditor from '@/app/jsonparser/JsonEditor';
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
return (
|
return (
|
||||||
@@ -7,7 +7,7 @@ const Page = () => {
|
|||||||
<div
|
<div
|
||||||
className={ 'p-2 lg:p-20 h-full w-full' }
|
className={ 'p-2 lg:p-20 h-full w-full' }
|
||||||
>
|
>
|
||||||
<MonacoEditor defaultValue={ '{}' } language={ 'json' } />
|
<JsonEditor />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
+49
-13
@@ -1,41 +1,77 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
|
|
||||||
import { Editor as Monaco } from '@monaco-editor/react';
|
import { Editor as Monaco } from '@monaco-editor/react';
|
||||||
import { useTheme } from 'next-themes';
|
import { useTheme } from 'next-themes';
|
||||||
import { Button } from '@nextui-org/react';
|
import { Button, Spinner } from '@nextui-org/react';
|
||||||
import { HiOutlineClipboardDocumentList } from 'react-icons/hi2';
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
import { HiOutlineDuplicate } from 'react-icons/hi';
|
||||||
|
import { MdFormatShapes } from 'react-icons/md';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
language: string,
|
language: string,
|
||||||
defaultValue: string
|
defaultValue: string
|
||||||
|
height?: string
|
||||||
|
setValue?: (value: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const MonacoEditor = (props: Props) => {
|
const MonacoEditor = ({ language, defaultValue, height = '75vh', setValue }: Props) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const [ editorValue, setEditorValue ] = useState(props.defaultValue);
|
const [ editorValue, setEditorValue ] = useState(defaultValue);
|
||||||
console.log(editorValue);
|
|
||||||
|
const editorRef = useRef(null);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
const handleEditorDidMount = (editor) => {
|
||||||
|
editorRef.current = editor;
|
||||||
|
};
|
||||||
|
const formatCode = () => {
|
||||||
|
if (editorRef.current) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
editorRef.current.getAction('editor.action.formatDocument').run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ 'rounded-2xl overflow-hidden shadow-2xl relative' }>
|
<div className={ 'relative' }>
|
||||||
<Monaco
|
<Monaco
|
||||||
height="75vh"
|
height={ height }
|
||||||
defaultLanguage={ props.language }
|
defaultLanguage={ language }
|
||||||
theme={ theme === 'light' ? 'light' : 'vs-dark' }
|
theme={ theme === 'light' ? 'light' : 'vs-dark' }
|
||||||
defaultValue={ props.defaultValue }
|
defaultValue={ defaultValue }
|
||||||
onChange={ (val) => setEditorValue(val as string) }
|
loading={ <Spinner size={ 'lg' } /> }
|
||||||
|
onMount={ handleEditorDidMount }
|
||||||
|
onChange={ (val) => {
|
||||||
|
setEditorValue(val as string);
|
||||||
|
if (setValue) {
|
||||||
|
setValue(val as string);
|
||||||
|
}
|
||||||
|
} }
|
||||||
/>
|
/>
|
||||||
<div className={ 'absolute bottom-4 right-4 z-10' }>
|
<div className={ 'flex flex-row-reverse absolute bottom-4 right-4 z-10' }>
|
||||||
<Button
|
<Button
|
||||||
variant={ 'light' }
|
variant={ 'light' }
|
||||||
isIconOnly
|
isIconOnly
|
||||||
|
aria-label={ 'copy to clipboard' }
|
||||||
onClick={ () => {
|
onClick={ () => {
|
||||||
navigator.clipboard.writeText(editorValue);
|
navigator.clipboard.writeText(editorValue);
|
||||||
toast.success('Copied to Clipboard');
|
toast.success('Copied to Clipboard');
|
||||||
} }
|
} }
|
||||||
>
|
>
|
||||||
<HiOutlineClipboardDocumentList size={ 20 } />
|
<HiOutlineDuplicate size={ 20 } />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={ 'light' }
|
||||||
|
isIconOnly
|
||||||
|
aria-label={ 'copy to clipboard' }
|
||||||
|
onClick={ () => {
|
||||||
|
formatCode();
|
||||||
|
toast.success('Formatted');
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
<MdFormatShapes size={ 20 } />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import 'react-toastify/dist/ReactToastify.css';
|
|||||||
// import '../../app/globals.css';
|
// import '../../app/globals.css';
|
||||||
import { ToastContainer } from 'react-toastify';
|
import { ToastContainer } from 'react-toastify';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
|
|
||||||
interface ToastProviderProps {
|
interface ToastProviderProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ToastProvider({ children }: ToastProviderProps) {
|
export default function ToastProvider({ children }: ToastProviderProps) {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -24,7 +26,7 @@ export default function ToastProvider({ children }: ToastProviderProps) {
|
|||||||
pauseOnFocusLoss
|
pauseOnFocusLoss
|
||||||
draggable
|
draggable
|
||||||
pauseOnHover
|
pauseOnHover
|
||||||
theme="dark"
|
theme={ theme }
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in new issue
Block a user