diff options
author | rohan09-raj <[email protected]> | 2024-02-19 20:50:51 +0530 |
---|---|---|
committer | Blaster4385 <[email protected]> | 2024-02-21 23:52:45 +0530 |
commit | 7ef0354687bb6abeda97a32ebcb0e39de91691e5 (patch) | |
tree | 76e0d842e9d5688d1526ddc9c627d8465d1e4a7c /client/src/components | |
parent | 63f1cbe9c0a52b9b3a8724b7c6f47957414bda8a (diff) |
feat: Implement frontend UI for minibin
- Use prism.js for syntax highlighting
- USe react-router-dom for routing
Diffstat (limited to 'client/src/components')
-rw-r--r-- | client/src/components/Editor/Editor.jsx | 129 | ||||
-rw-r--r-- | client/src/components/Editor/Editor.module.css | 107 | ||||
-rw-r--r-- | client/src/components/Header/Header.jsx | 13 | ||||
-rw-r--r-- | client/src/components/Header/Header.module.css | 9 | ||||
-rw-r--r-- | client/src/components/prism-themes/prism-gruvbox-dark.css | 143 |
5 files changed, 401 insertions, 0 deletions
diff --git a/client/src/components/Editor/Editor.jsx b/client/src/components/Editor/Editor.jsx new file mode 100644 index 0000000..c291872 --- /dev/null +++ b/client/src/components/Editor/Editor.jsx @@ -0,0 +1,129 @@ +import { useState, useEffect, useRef } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import Prism from "prismjs"; +import styles from "./Editor.module.css"; +import "../prism-themes/prism-gruvbox-dark.css"; +import { SERVER_BASE_URL, SUPPORTED_LANGUAGES } from "../../utils/constants"; + +const Editor = () => { + const navigate = useNavigate(); + const params = useParams(); + const [text, setText] = useState(""); + const [language, setLanguage] = useState("js"); + const textareaRef = useRef(null); + const lineNumberRef = useRef(null); + + const handleTextChange = (event) => { + setText(event.target.value); + }; + + const handleScroll = () => { + if (textareaRef.current && lineNumberRef.current) { + lineNumberRef.current.scrollTop = textareaRef.current.scrollTop; + } + }; + + const handleClick = async () => { + const response = await fetch(`${SERVER_BASE_URL}/bin`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + language, + content: text, + }), + }); + const data = await response.json(); + if (response.ok) { + navigate(`/${data.id}`); + } else { + console.error(data); + } + }; + + const handleLanguageChange = (event) => { + setLanguage(event.target.value); + }; + + useEffect(() => { + Prism.highlightAll(); + }, [text, language]); + + useEffect(() => { + if (params.id) fetchData(); + else { + textareaRef.current.value = ""; + setText(""); + } + }, [params.id]); + + const fetchData = async () => { + const response = await fetch(`${SERVER_BASE_URL}/bin/${params.id}`); + const data = await response.json(); + if (response.ok) { + setLanguage(data.language); + setText(data.content); + } + }; + + const lines = text.split("\n"); + + return ( + <div className={styles.container}> + {!params.id && ( + <> + <select + className={styles.languages} + onChange={(event) => handleLanguageChange(event)} + > + {Object.keys(SUPPORTED_LANGUAGES).map((language) => ( + <option + className={styles.languages__option} + key={language} + value={language} + > + {SUPPORTED_LANGUAGES[language]} + </option> + ))} + </select> + <button className={styles.btn__save} onClick={() => handleClick()}> + <img src="assets/icons/save.svg" className={styles.btn__icon} /> + </button> + </> + )} + + <div className={styles.editor}> + <div + className={styles.line__numbers} + ref={lineNumberRef} + onScroll={handleScroll} + > + {lines.map((_, index) => ( + <div key={index + 1} className={styles.line__number}> + {index + 1} + </div> + ))} + </div> + <div className={styles.codespace}> + <textarea + className={styles.codespace__textarea} + onChange={handleTextChange} + onScroll={handleScroll} + hidden={params.id ? true : false} + spellCheck="false" + ref={textareaRef} + placeholder="Type your text here..." + /> + <pre className={styles.codespace__pre}> + <code className={`${styles.codespace__code} language-${language}`}> + {text} + </code> + </pre> + </div> + </div> + </div> + ); +}; + +export default Editor; diff --git a/client/src/components/Editor/Editor.module.css b/client/src/components/Editor/Editor.module.css new file mode 100644 index 0000000..5eabc10 --- /dev/null +++ b/client/src/components/Editor/Editor.module.css @@ -0,0 +1,107 @@ +.container { + width: 100vw; + display: flex; + flex-direction: column; + flex: 1; +} + +.languages { + background-color: transparent; + border: none; + padding: 10px 0; + margin-left: 36px; + font-family: inherit; + font-size: inherit; + cursor: inherit; + line-height: inherit; + width: 7rem; + outline: none; + color: white; +} + +.languages__option { + width: 10rem; + border: none; + cursor: pointer; + transition: 0.3s; + background: #3c3836; +} + +.editor { + display: flex; + flex-direction: row; + flex: 1; + padding: 20px 70px 0 40px; + border-top: 1px solid #3c3836; +} + +.line__numbers { + display: flex; + flex-direction: column; + padding: 0px 20px 0px 0px; + overflow-y: auto; +} + +.line__numbers::-webkit-scrollbar { + display: none; +} + +.line__number { + margin: 0px; + font-size: 16px; + line-height: 1.5rem; + text-align: right; +} + +.codespace { + position: relative; + display: flex; + flex: 1; + margin-left: 10px; +} + +.codespace__textarea { + caret-color: white; + background: transparent; + position: absolute; + color: transparent; + left: 0; + top: 0; + width: 100%; + height: 100%; + font-size: 1rem; + line-height: 1.5rem; + resize: none; + border: none; + outline: none; + padding: 0px; + overflow-y: auto; +} + +.codespace__pre { + background: transparent !important; + height: 100%; + width: 100%; + margin: 0 !important; + padding: 0 !important; +} + +.btn__save { + position: fixed; + bottom: 3rem; + right: 3rem; + height: 8rem; + width: 8rem; + background-color: #ebdbb2; + color: white; + border: none; + border-radius: 50%; + cursor: pointer; + transition: 0.3s; + z-index: 1; +} + +.btn__icon { + height: 4rem; + width: 4rem; +} diff --git a/client/src/components/Header/Header.jsx b/client/src/components/Header/Header.jsx new file mode 100644 index 0000000..9217299 --- /dev/null +++ b/client/src/components/Header/Header.jsx @@ -0,0 +1,13 @@ +import React from 'react' + +import styles from './Header.module.css' + +const Header = () => { + return ( + <div className={styles.header}> + <h1><minibin /></h1> + </div> + ) +} + +export default Header
\ No newline at end of file diff --git a/client/src/components/Header/Header.module.css b/client/src/components/Header/Header.module.css new file mode 100644 index 0000000..c39f528 --- /dev/null +++ b/client/src/components/Header/Header.module.css @@ -0,0 +1,9 @@ +.header { + display: flex; + justify-content: center; + margin: 2rem; +} + +.header h1 { + margin: 0; +} diff --git a/client/src/components/prism-themes/prism-gruvbox-dark.css b/client/src/components/prism-themes/prism-gruvbox-dark.css new file mode 100644 index 0000000..1f368fb --- /dev/null +++ b/client/src/components/prism-themes/prism-gruvbox-dark.css @@ -0,0 +1,143 @@ +/** + * Gruvbox dark theme + * + * Adapted from a theme based on: + * Vim Gruvbox dark Theme (https://github.com/morhetz/gruvbox) + * + * @author Azat S. <[email protected]> + * @version 1.0 + */ + + code[class*="language-"], + pre[class*="language-"] { + color: #ebdbb2; /* fg1 / fg */ + font-family: Consolas, Monaco, "Andale Mono", monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; + } + + pre[class*="language-"]::-moz-selection, + pre[class*="language-"] ::-moz-selection, + code[class*="language-"]::-moz-selection, + code[class*="language-"] ::-moz-selection { + color: #fbf1c7; /* fg0 */ + background: #7c6f64; /* bg4 */ + } + + pre[class*="language-"]::selection, + pre[class*="language-"] ::selection, + code[class*="language-"]::selection, + code[class*="language-"] ::selection { + color: #fbf1c7; /* fg0 */ + background: #7c6f64; /* bg4 */ + } + + /* Code blocks */ + pre[class*="language-"] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; + } + + :not(pre) > code[class*="language-"], + pre[class*="language-"] { + background: #1d2021; /* bg0_h */ + } + + /* Inline code */ + :not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + } + + .token.comment, + .token.prolog, + .token.cdata { + color: #a89984; /* fg4 / gray1 */ + } + + .token.delimiter, + .token.boolean, + .token.keyword, + .token.selector, + .token.important, + .token.atrule { + color: #fb4934; /* red2 */ + } + + .token.operator, + .token.punctuation, + .token.attr-name { + color: #a89984; /* fg4 / gray1 */ + } + + .token.tag, + .token.tag .punctuation, + .token.doctype, + .token.builtin { + color: #fabd2f; /* yellow2 */ + } + + .token.entity, + .token.number, + .token.symbol { + color: #d3869b; /* purple2 */ + } + + .token.property, + .token.constant, + .token.variable { + color: #fb4934; /* red2 */ + } + + .token.string, + .token.char { + color: #b8bb26; /* green2 */ + } + + .token.attr-value, + .token.attr-value .punctuation { + color: #a89984; /* fg4 / gray1 */ + } + + .token.url { + color: #b8bb26; /* green2 */ + text-decoration: underline; + } + + .token.function { + color: #fabd2f; /* yellow2 */ + } + + .token.regex { + background: #b8bb26; /* green2 */ + } + + .token.bold { + font-weight: bold; + } + + .token.italic { + font-style: italic; + } + + .token.inserted { + background: #a89984; /* fg4 / gray1 */ + } + + .token.deleted { + background: #fb4934; /* red2 */ + }
\ No newline at end of file |