' if command arg is not valid
26 | // eg: about tt
27 | if (!specialCmds.includes(cmd) && arg.length > 0)
28 | return Usage: {cmd};
29 |
30 | return (
31 |
32 | {
33 | {
34 | about: ,
35 | clear: ,
36 | education: ,
37 | email: ,
38 | help: ,
39 | history: ,
40 | projects: ,
41 | pwd: /home/Zemerik,
42 | socials: ,
43 | welcome: ,
44 | whoami: (
45 |
46 | Well, it looks like you're having an identity crisis even the
47 | terminal can't solve. Maybe try asking a mirror instead? 🪞
48 |
49 | ),
50 | }[cmd]
51 | }
52 |
53 | );
54 | };
55 |
56 | export default Output;
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "terminal-portfolio",
3 | "private": true,
4 | "version": "1.0.0",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "tsc && vite build",
8 | "preview": "vite preview",
9 | "test:once": "vitest run",
10 | "test": "vitest",
11 | "coverage": "vitest run --coverage",
12 | "prepare": "husky install",
13 | "lint": "eslint .",
14 | "format:check": "prettier --check .",
15 | "format": "prettier --write ."
16 | },
17 | "dependencies": {
18 | "lodash": "^4.17.21",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "styled-components": "^5.3.10",
22 | "styled-normalize": "^8.0.7",
23 | "terminal-portfolio": "file:"
24 | },
25 | "devDependencies": {
26 | "@testing-library/jest-dom": "^5.16.5",
27 | "@testing-library/react": "^14.0.0",
28 | "@testing-library/user-event": "^14.4.3",
29 | "@types/lodash": "^4.14.194",
30 | "@types/react": "^18.2.6",
31 | "@types/react-dom": "^18.2.4",
32 | "@types/styled-components": "^5.1.26",
33 | "@types/testing-library__jest-dom": "^5.14.5",
34 | "@typescript-eslint/eslint-plugin": "^5.59.6",
35 | "@typescript-eslint/parser": "^5.59.6",
36 | "@vitejs/plugin-react": "^4.0.0",
37 | "@vitest/coverage-c8": "^0.31.1",
38 | "eslint": "^8.40.0",
39 | "eslint-config-prettier": "^8.8.0",
40 | "eslint-import-resolver-typescript": "^3.5.5",
41 | "eslint-plugin-import": "^2.27.5",
42 | "eslint-plugin-markdown": "^3.0.0",
43 | "eslint-plugin-prettier": "^4.2.1",
44 | "eslint-plugin-react": "^7.32.2",
45 | "eslint-plugin-react-hooks": "^4.6.0",
46 | "husky": "^8.0.3",
47 | "jsdom": "^22.0.0",
48 | "lint-staged": "^13.2.2",
49 | "prettier": "^2.8.8",
50 | "typescript": "^5.0.4",
51 | "vite": "^4.3.8",
52 | "vite-plugin-pwa": "^0.14.7",
53 | "vitest": "^0.31.1"
54 | },
55 | "lint-staged": {
56 | "*": [
57 | "eslint",
58 | "prettier --write"
59 | ]
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/components/styles/GlobalStyle.tsx:
--------------------------------------------------------------------------------
1 | import {createGlobalStyle, DefaultTheme} from "styled-components";
2 | import {normalize} from "styled-normalize";
3 |
4 | const GlobalStyle = createGlobalStyle<{ theme: DefaultTheme }>`
5 | ${normalize}
6 | *, ::before, ::after {
7 | border-width: 0;
8 | border-style: solid;
9 | border-color: theme('borderColor.DEFAULT', currentColor);
10 | }
11 |
12 | blockquote, dl, dd, h1, h2, h3,
13 | h4, h5, h6, hr, figure, p, pre {
14 | margin: 0;
15 | }
16 |
17 | h1, h2, h3, h4, h5, h6 {
18 | font-size: inherit;
19 | font-weight: inherit;
20 | }
21 |
22 | img, svg, video, canvas, audio,
23 | iframe, embed, object {
24 | display: block;
25 | }
26 |
27 | body {
28 | font-family: 'Cascadia Code', 'IBM Plex Mono', monospace;
29 | font-weight: 500;
30 | font-size: 14px;
31 | background-color: ${({theme}) => theme.colors?.body};
32 | color: ${({theme}) => theme.colors?.text[100]};
33 | }
34 |
35 | /* ===== Custom Scroll Bar ===== */
36 | /* width */
37 | ::-webkit-scrollbar {
38 | width: 15px;
39 | }
40 |
41 | /* Track */
42 | ::-webkit-scrollbar-track {
43 | background: ${({theme}) => theme.colors?.body};
44 | }
45 |
46 | /* Handle */
47 | ::-webkit-scrollbar-thumb {
48 | background: ${({theme}) => theme.colors?.scrollHandle};
49 | }
50 |
51 | /* Handle on hover */
52 | ::-webkit-scrollbar-thumb:hover {
53 | background: ${({theme}) => theme.colors?.scrollHandleHover};
54 | }
55 |
56 | input[type=text] {
57 | background-color: ${({theme}) => theme.colors?.body};
58 | color: ${({theme}) => theme.colors?.text[100]};
59 | caret-color: ${({theme}) => theme.colors?.primary};
60 | }
61 |
62 | input[type=text]:focus-visible {
63 | outline: none;
64 | }
65 |
66 | .sr-only {
67 | position: absolute;
68 | left: -10000px;
69 | top: auto;
70 | width: 1px;
71 | height: 1px;
72 | overflow: hidden;
73 | }
74 | `;
75 |
76 | export default GlobalStyle;
77 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useEffect, useState } from "react";
2 | import { DefaultTheme, ThemeProvider } from "styled-components";
3 | import { useTheme } from "./hooks/useTheme";
4 | import GlobalStyle from "./components/styles/GlobalStyle";
5 | import Terminal from "./components/Terminal";
6 |
7 | export const themeContext = createContext<
8 | ((switchTheme: DefaultTheme) => void) | null
9 | >(null);
10 |
11 | function App() {
12 | // themes
13 | const { theme, themeLoaded, setMode } = useTheme();
14 | const [selectedTheme, setSelectedTheme] = useState(theme);
15 |
16 | // Disable browser's default behavior
17 | // to prevent the page go up when Up Arrow is pressed
18 | useEffect(() => {
19 | window.addEventListener(
20 | "keydown",
21 | e => {
22 | ["ArrowUp", "ArrowDown"].indexOf(e.code) > -1 && e.preventDefault();
23 | },
24 | false
25 | );
26 | }, []);
27 |
28 | useEffect(() => {
29 | setSelectedTheme(theme);
30 | }, [themeLoaded]);
31 |
32 | // Update meta tag colors when switching themes
33 | useEffect(() => {
34 | const themeColor = theme.colors?.body;
35 |
36 | const metaThemeColor = document.querySelector("meta[name='theme-color']");
37 | const maskIcon = document.querySelector("link[rel='mask-icon']");
38 | const metaMsTileColor = document.querySelector(
39 | "meta[name='msapplication-TileColor']"
40 | );
41 |
42 | metaThemeColor && metaThemeColor.setAttribute("content", themeColor);
43 | metaMsTileColor && metaMsTileColor.setAttribute("content", themeColor);
44 | maskIcon && maskIcon.setAttribute("color", themeColor);
45 | }, [selectedTheme]);
46 |
47 | const themeSwitcher = (switchTheme: DefaultTheme) => {
48 | setSelectedTheme(switchTheme);
49 | setMode(switchTheme);
50 | };
51 |
52 | return (
53 | <>
54 |
55 | Terminal Portfolio
56 |
57 | {themeLoaded && (
58 |
59 |
60 |
61 |
62 |
63 |
64 | )}
65 | >
66 | );
67 | }
68 |
69 | export default App;
70 |
--------------------------------------------------------------------------------
/src/components/commands/Socials.tsx:
--------------------------------------------------------------------------------
1 | import {useContext, useEffect} from "react";
2 | import {ProjectsIntro} from "../styles/Projects.styled";
3 | import {Cmd, CmdDesc, CmdList, HelpWrapper} from "../styles/Help.styled";
4 | import {checkRedirect, generateTabs, getCurrentCmdArry, isArgInvalid,} from "../../utils/funcs";
5 | import {termContext} from "../Terminal";
6 | import Usage from "../Usage";
7 |
8 | const Socials: React.FC = () => {
9 | const {arg, history, rerender} = useContext(termContext);
10 |
11 | /* ===== get current command ===== */
12 | const currentCommand = getCurrentCmdArry(history);
13 |
14 | /* ===== check current command makes redirect ===== */
15 | useEffect(() => {
16 | if (checkRedirect(rerender, currentCommand, "socials")) {
17 | socials.forEach(({id, url}) => {
18 | id === parseInt(arg[1]) && window.open(url, "_blank");
19 | });
20 | }
21 | }, [arg, rerender, currentCommand]);
22 |
23 | /* ===== check arg is valid ===== */
24 | const checkArg = () =>
25 | isArgInvalid(arg, "go", ["1", "2", "3", "4"]) ? (
26 |
27 | ) : null;
28 |
29 | return arg.length > 0 || arg.length > 2 ? (
30 | checkArg()
31 | ) : (
32 |
33 | Here are my social links
34 | {socials.map(({id, title, url, tab}) => (
35 |
36 | {`${id}. ${title}`}
37 | {generateTabs(tab)}
38 | - {url}
39 |
40 | ))}
41 |
42 |
43 | );
44 | };
45 |
46 | const socials = [
47 | {
48 | id: 1,
49 | title: "GitHub",
50 | url: "https://github.com/Zemerik/",
51 | tab: 3,
52 | },
53 | {
54 | id: 2,
55 | title: "LinkedIn",
56 | url: "https://linkedin.com/in/Zemerik/",
57 | tab: 1,
58 | },
59 | {
60 | id: 3,
61 | title: "Twitter",
62 | url: "https://twitter.com/Zemerik_X/",
63 | tab: 2,
64 | },
65 | {
66 | id: 4,
67 | title: "Instagram",
68 | url: "https://instagram.com/Zemerik_INSTA/",
69 | tab: 0,
70 | },
71 | ];
72 |
73 | export default Socials;
74 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Zemerik's Terminal
15 |
16 |
17 |
18 | An Interactive Terminal
19 |
20 |
21 |
22 |
23 |
24 |
25 | ## ❓ How to Contribute?
26 |
27 | - All Contributions are highly appreciated, if you would like to contribute to this project, follow the steps below.
28 |
29 | 1. Fork a copy of this Repository on your Github account by clicking below,
30 |
31 | - [Fork](https://github.com/Zemerik/Terminal/fork)
32 |
33 | 2. Clone your Forked Repository by using the following `GIT` command:
34 |
35 | ```bash
36 | git clone https://github.com/[YOUR GITHUB USERNAME]/Terminal.git
37 | ```
38 |
39 | 3. Navigate into the Project's `Directory` by using the command below:
40 |
41 | ```bash
42 | cd Terminal
43 | ```
44 |
45 | 4. Initialize a Remote to the original Repository by the following `GIT` command:
46 |
47 | ```bash
48 | git remote add upstream https://github.com/Zemerik/Terminal
49 | ```
50 |
51 | 5. Create a new `branch` in which you can make your desired changes:
52 |
53 | ```bash
54 | git checkout -b newcontribution
55 | ```
56 |
57 | 6. After making your changes, add all your files to the Staging Area:
58 |
59 | ```bash
60 | git add --all
61 | ```
62 |
63 | 7. Commit your Changes:
64 |
65 | ```bash
66 | git commit -m "[COMMIT MSG]"
67 | ```
68 |
69 | > [!Note]
70 | > Remember to live a good commit message
71 |
72 | 8. Push all your Changes:
73 |
74 | ```bash
75 | git push origin newcontribution
76 | ```
77 |
78 | 9. Create a new Pull - Request (PR) on the Original Repository
79 |
80 | > Your Pull Request will be merged / reviewed as soon as possible.
81 |
82 | ## 🐞Bug/Issue/Feedback/Feature Request:
83 |
84 | - If you would like to report a bug, a issue, implement any feedack, or request any feature, you are free to do so by opening a issue on this repository. Remember to give a detailed explanation of what you are trying to say, and how it will help the website.
85 |
86 | ## 💁 Support:
87 |
88 | For any kind of support or inforrmation, you are free to join our **Discord Server**,
89 |
90 |
91 |
92 |
93 |
94 |
95 | Thanks for Visiting🙏
96 |
97 |
98 |
99 | Don't forget to leave a ⭐
100 |
101 | Made with 💖 by Hemang Yadav (Zemerik)
102 |
--------------------------------------------------------------------------------
/src/components/commands/Projects.tsx:
--------------------------------------------------------------------------------
1 | import {useContext, useEffect} from "react";
2 | import {checkRedirect, getCurrentCmdArry, isArgInvalid,} from "../../utils/funcs";
3 | import {ProjectContainer, ProjectDesc, ProjectsIntro, ProjectTitle,} from "../styles/Projects.styled";
4 | import {termContext} from "../Terminal";
5 | import Usage from "../Usage";
6 |
7 | const Projects: React.FC = () => {
8 | const {arg, history, rerender} = useContext(termContext);
9 |
10 | /* ===== get current command ===== */
11 | const currentCommand = getCurrentCmdArry(history);
12 |
13 | /* ===== check current command is redirect ===== */
14 | useEffect(() => {
15 | if (checkRedirect(rerender, currentCommand, "projects")) {
16 | projects.forEach(({id, url, repo}) => {
17 | id === parseInt(arg[1]) &&
18 | window.open(url !== "" ? url : repo, "_blank");
19 | });
20 | }
21 | }, [arg, rerender, currentCommand]);
22 |
23 | /* ===== check arg is valid ===== */
24 | const checkArg = () =>
25 | isArgInvalid(arg, "go", ["1", "2", "3"]) ? (
26 |
27 | ) : null;
28 |
29 | return arg.length > 0 || arg.length > 2 ? (
30 | checkArg()
31 | ) : (
32 |
33 |
34 | The web is like a canvas, and code is the paint. Create your masterpiece.
35 |
36 | {projects.map(({id, title, desc}) => (
37 |
38 | {`${id}. ${title}`}
39 | {desc}
40 |
41 | ))}
42 |
43 |
44 | );
45 | };
46 |
47 | const projects = [
48 | {
49 | id: 1,
50 | title: "ZemPosts",
51 | desc: "Post & Connect with Developers",
52 | url: "https://zemposts.vercel.app",
53 | repo: "https://github.com/Zemerik/ZemPosts",
54 | },
55 | {
56 | id: 2,
57 | title: "ZemShowcase",
58 | desc: "Showcase & Connect with Developers",
59 | url: "https://zemshowcase.vercel.app",
60 | repo: "https://github.com/Zemerik/ZemShowcase",
61 | },
62 | {
63 | id: 3,
64 | title: "ZemProfiles",
65 | desc: "Discover & Connect with Developers",
66 | url: "https://zemprofiles.vercel.app",
67 | repo: "https://github.com/Zemerik/ZemProfiles",
68 | },
69 | {
70 | id: 4,
71 | title: "Resume",
72 | desc: "A Dynamic Resume",
73 | url: "https://zemeriksresume.vercel.app",
74 | repo: "https://github.com/Zemerik/Resume",
75 | },
76 | {
77 | id: 5,
78 | title: "Dashboard",
79 | desc: "A Unifed Digital Identity Hub",
80 | url: "https://zemeriksdashboard.vercel.app",
81 | repo: "https://github.com/Zemerik/Dashboard",
82 | },
83 | ];
84 |
85 | export default Projects;
86 |
--------------------------------------------------------------------------------
/src/components/styles/themes.ts:
--------------------------------------------------------------------------------
1 | import {DefaultTheme} from "styled-components";
2 |
3 | export type Themes = {
4 | [key: string]: DefaultTheme;
5 | };
6 |
7 | const theme: Themes = {
8 | dark: {
9 | id: "T_001",
10 | name: "dark",
11 | colors: {
12 | body: "#1D2A35",
13 | scrollHandle: "#19252E",
14 | scrollHandleHover: "#162028",
15 | primary: "#05CE91",
16 | secondary: "#FF9D00",
17 | text: {
18 | 100: "#cbd5e1",
19 | 200: "#B2BDCC",
20 | 300: "#64748b",
21 | },
22 | },
23 | },
24 | light: {
25 | id: "T_002",
26 | name: "light",
27 | colors: {
28 | body: "#EFF3F3",
29 | scrollHandle: "#C1C1C1",
30 | scrollHandleHover: "#AAAAAA",
31 | primary: "#027474",
32 | secondary: "#FF9D00",
33 | text: {
34 | 100: "#334155",
35 | 200: "#475569",
36 | 300: "#64748b",
37 | },
38 | },
39 | },
40 | "blue-matrix": {
41 | id: "T_003",
42 | name: "blue-matrix",
43 | colors: {
44 | body: "#101116",
45 | scrollHandle: "#424242",
46 | scrollHandleHover: "#616161",
47 | primary: "#00ff9c",
48 | secondary: "#60fdff",
49 | text: {
50 | 100: "#ffffff",
51 | 200: "#c7c7c7",
52 | 300: "#76ff9f",
53 | },
54 | },
55 | },
56 | espresso: {
57 | id: "T_004",
58 | name: "espresso",
59 | colors: {
60 | body: "#323232",
61 | scrollHandle: "#5b5b5b",
62 | scrollHandleHover: "#393939",
63 | primary: "#E1E48B",
64 | secondary: "#A5C260",
65 | text: {
66 | 100: "#F7F7F7",
67 | 200: "#EEEEEE",
68 | 300: "#5b5b5b",
69 | },
70 | },
71 | },
72 | "green-goblin": {
73 | id: "T_005",
74 | name: "green-goblin",
75 | colors: {
76 | body: "#000000",
77 | scrollHandle: "#2E2E2E",
78 | scrollHandleHover: "#414141",
79 | primary: "#E5E500",
80 | secondary: "#04A500",
81 | text: {
82 | 100: "#01FF00",
83 | 200: "#04A5B2",
84 | 300: "#E50101",
85 | },
86 | },
87 | },
88 | ubuntu: {
89 | id: "T_006",
90 | name: "ubuntu",
91 | colors: {
92 | body: "#2D0922",
93 | scrollHandle: "#F47845",
94 | scrollHandleHover: "#E65F31",
95 | primary: "#80D932",
96 | secondary: "#80D932",
97 | text: {
98 | 100: "#FFFFFF",
99 | 200: "#E1E9CC",
100 | 300: "#CDCDCD",
101 | },
102 | },
103 | },
104 | };
105 |
106 | export default theme;
107 |
--------------------------------------------------------------------------------
/src/components/commands/Welcome.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Cmd, HeroContainer, PreName, PreNameMobile, PreWrapper, Seperator,} from "../styles/Welcome.styled";
3 |
4 | const Welcome: React.FC = () => {
5 | return (
6 |
7 |
8 |
9 | {`
10 | ******** ******** **** **** ******** ******* ** ** ** ** ******** ********** ******** ******* **** **** ** **** ** ** **
11 | //////** /**///// /**/** **/**/**///// /**////** /**/** ** //* **////// /////**/// /**///// /**////** /**/** **/**/**/**/** /** **** /**
12 | ** /** /**//** ** /**/** /** /** /**/** ** / /** /** /** /** /** /**//** ** /**/**/**//** /** **//** /**
13 | ** /******* /** //*** /**/******* /******* /**/**** /********* /** /******* /******* /** //*** /**/**/** //** /** ** //** /**
14 | ** /**//// /** //* /**/**//// /**///** /**/**/** ////////** /** /**//// /**///** /** //* /**/**/** //**/** **********/**
15 | ** /** /** / /**/** /** //** /**/**//** /** /** /** /** //** /** / /**/**/** //****/**//////**/**
16 | ********/********/** /**/********/** //**/**/** //** ******** /** /********/** //**/** /**/**/** //***/** /**/********
17 | //////// //////// // // //////// // // // // // //////// // //////// // // // // // // /// // // ////////
18 | `}
19 |
20 |
21 |
22 |
23 | {`
24 |
25 | :::===== :::===== :::======= :::===== :::==== ::: ::: === == :::=== :::==== :::===== :::==== :::======= ::: :::= === :::==== :::
26 | === ::: ::: === === ::: ::: === ::: ::: === == ::: :::==== ::: ::: === ::: === === ::: :::===== ::: === :::
27 | === ====== === === === ====== ======= === ====== ===== === ====== ======= === === === === ======== ======== ===
28 | === === === === === === === === === === === === === === === === === === === ==== === === ===
29 | ======== ======== === === ======== === === === === === ====== === ======== === === === === === === === === === ========
30 |
31 |
32 | `}
33 |
34 |
35 |
Welcome to Hemang Yadav (Zemerik)'s Terminal
36 |
----
37 |
38 | For a list of available commands, type help.
39 |
40 |
41 |
42 |
43 | );
44 | };
45 |
46 | export default Welcome;
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Zemerik's Terminal
15 |
16 |
17 |
18 | An Interactive Terminal
19 |
20 |
21 |
22 |
23 |
24 |
25 | 
26 |
27 | ## ❗About:
28 |
29 | Welcome to my personal terminal-themed website! Dive into an interactive experience where you can explore my world through a classic command-line interface. Just type in the provided commands to learn more about my background, skills, projects, and interests. Whether you're here to discover my professional journey, check out my portfolio, or just curious about who I am, this website offers a unique, nostalgic way to connect. Get ready to type, discover, and enjoy the seamless blend of modern content with a retro touch!
30 |
31 | ## ⭐ Key Features:
32 |
33 | - Interactive Command-Line Interface:
34 |
35 | - Skills
36 |
37 | - Projects
38 |
39 | - Responsive Design
40 |
41 | - User-Friendly Commands
42 |
43 | ## 📱 Guide:
44 |
45 | ### Commands:
46 |
47 | - `about` - About Zemerik
48 | - `clear` - Clear the Terminal
49 | - `education` - Education Background
50 | - `email` - Contact Me through my Email
51 | - `history` - View Command History
52 | - `projects` - View all my Projects
53 | - `pwd` - Your Current Working Directory
54 | - `socials` - Follow Zemerik around the Web
55 | - `welcome` - Display Welcome Section
56 | - `whoami` - Get to Know who you are
57 |
58 | ### Shortcuts:
59 |
60 | - `Tab` / `Ctrl + i` - autocompletes the command
61 | - `Up Arrow` - go back to previous command
62 | - `Ctrl + l` - clear the terminal
63 |
64 | > Feel free to open [Pull-Requests](https://github.com/Zemerik/Terminal/pulls) for more Commands / Shorcuts.
65 |
66 | ## 💻 Screenshots:
67 |
68 | 
69 |
70 | ## 🚀 Quick Start:
71 |
72 | ### Prerequisites:
73 |
74 | - [NodeJS](https://nodejs.org) installed on your machine
75 | - [GIT](https://git-scm.com) installed on your machine
76 | - A Code Editor
77 |
78 | ### Cloning:
79 |
80 | - To make a local copy of this Project on your machine, enter the following `GIT` Commmand in your Terminal:
81 |
82 | ```bash
83 | git clone https://github.com/Zemerik/Terminal
84 | ```
85 |
86 | ### Installing Dependencies:
87 |
88 | - To run this project locally, we first need to download a few `npm` dependencies by using the command below:
89 |
90 | ```bash
91 | npm i
92 | ```
93 |
94 | ### Locally Running:
95 |
96 | - We can locally run this Project on our Network and see the output using the following Command of `NodeJS`:
97 |
98 | ```bash
99 | npm run dev
100 | ```
101 |
102 | ## 😎 Happy Coding!!
103 |
104 | ## 🤝 Contributing:
105 |
106 | Contributions are always welcome and appreciated! **Kindly visit the [CONTRIBUTING.md](https://github.com/Zemerik/Terminal/blob/main/CONTRIBUTING.md) file for more information**
107 |
108 |
109 | ## 💁 Support:
110 |
111 | For any kind of support or inforrmation, you are free to join our **Discord Server**,
112 |
113 |
114 |
115 |
116 |
117 | #
118 |
119 |
120 | Don't forget to leave a ⭐
121 |
122 | Made with 💖 by Hemang Yadav (Zemerik)
123 |
124 |
--------------------------------------------------------------------------------
/src/utils/funcs.ts:
--------------------------------------------------------------------------------
1 | import _ from "lodash";
2 | import theme from "../components/styles/themes";
3 |
4 | /**
5 | * Generates html tabs
6 | * @param {number} num - The number of tabs
7 | * @returns {string} tabs - Tab string
8 | */
9 | export const generateTabs = (num = 0): string => {
10 | let tabs = "\xA0\xA0";
11 | for (let i = 0; i < num; i++) {
12 | tabs += "\xA0";
13 | }
14 | return tabs;
15 | };
16 |
17 | /**
18 | * Check arg is valid
19 | * @param {string[]} arg - The arg array
20 | * @param {string} action - The action to compare | "go" | "set"
21 | * @param {string[]} options - Option array to compare | "dark" | "1"
22 | * @returns {boolean} boolean
23 | */
24 | export const isArgInvalid = (
25 | arg: string[],
26 | action: string,
27 | options: string[]
28 | ) => arg[0] !== action || !_.includes(options, arg[1]) || arg.length > 2;
29 |
30 | /**
31 | * Transform current cmd & arg into array
32 | * then return back the array
33 | * @param {string[]} history - The history array
34 | * @returns {string[]} array of cmd string
35 | */
36 | export const getCurrentCmdArry = (history: string[]) =>
37 | _.split(history[0].trim(), " ");
38 |
39 | /**
40 | * Check current render makes redirect
41 | * @param {boolean} rerender - is submitted or not
42 | * @param {string[]} currentCommand - current submitted command
43 | * @param {string} command - the command of the function
44 | * @returns {boolean} redirect - true | false
45 | */
46 | export const checkRedirect = (
47 | rerender: boolean,
48 | currentCommand: string[],
49 | command: string
50 | ): boolean =>
51 | rerender && // is submitted
52 | currentCommand[0] === command && // current command starts with ('socials'|'projects')
53 | currentCommand[1] === "go" && // first arg is 'go'
54 | currentCommand.length > 1 && // current command has arg
55 | currentCommand.length < 4 && // if num of arg is valid (not `projects go 1 sth`)
56 | _.includes([1, 2, 3, 4], parseInt(currentCommand[2])); // arg last part is one of id
57 |
58 | /**
59 | * Perform advanced tab actions
60 | * @param {string} inputVal - current input value
61 | * @param {(value: React.SetStateAction) => void} setInputVal - setInputVal setState
62 | * @param {(value: React.SetStateAction) => void} setHints - setHints setState
63 | * @param {hintsCmds} hintsCmds - hints command array
64 | * @returns {string[] | undefined} hints command or setState action(undefined)
65 | */
66 | export const argTab = (
67 | inputVal: string,
68 | setInputVal: (value: React.SetStateAction) => void,
69 | setHints: (value: React.SetStateAction) => void,
70 | hintsCmds: string[]
71 | ): string[] | undefined => {
72 | // 1) if input is 'themes '
73 | if (inputVal === "themes ") {
74 | setInputVal(`themes set`);
75 | return [];
76 | }
77 |
78 | // 2) if input is 'themes s'
79 | else if (
80 | _.startsWith("themes", _.split(inputVal, " ")[0]) &&
81 | _.split(inputVal, " ")[1] !== "set" &&
82 | _.startsWith("set", _.split(inputVal, " ")[1])
83 | ) {
84 | setInputVal(`themes set`);
85 | return [];
86 | }
87 |
88 | // 3) if input is 'themes set '
89 | else if (inputVal === "themes set ") {
90 | setHints(_.keys(theme));
91 | return [];
92 | }
93 |
94 | // 4) if input starts with 'themes set ' + theme
95 | else if (_.startsWith(inputVal, "themes set ")) {
96 | _.keys(theme).forEach(t => {
97 | if (_.startsWith(t, _.split(inputVal, " ")[2])) {
98 | hintsCmds = [...hintsCmds, t];
99 | }
100 | });
101 | return hintsCmds;
102 | }
103 |
104 | // 5) if input is 'projects' or 'socials'
105 | else if (inputVal === "projects " || inputVal === "socials ") {
106 | setInputVal(`${inputVal}go`);
107 | return [];
108 | }
109 |
110 | // 6) if input is 'projects g' or 'socials g'
111 | else if (inputVal === "projects g" || inputVal === "socials g") {
112 | setInputVal(`${inputVal}o`);
113 | return [];
114 | }
115 |
116 | // 7) if input is 'socials go '
117 | else if (_.startsWith(inputVal, "socials go ")) {
118 | ["1.Github", "2.Dev.to", "3.Facebook", "4.Instagram"].forEach(t => {
119 | hintsCmds = [...hintsCmds, t];
120 | });
121 | return hintsCmds;
122 | }
123 |
124 | // 8) if input is 'projects go '
125 | else if (_.startsWith(inputVal, "projects go ")) {
126 | ["", "", "", ""].forEach(t => {
127 | hintsCmds = [...hintsCmds, t];
128 | });
129 | return hintsCmds;
130 | }
131 | };
132 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | zemerikY@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/src/components/Terminal.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | createContext,
3 | useCallback,
4 | useEffect,
5 | useRef,
6 | useState,
7 | } from "react";
8 | import _ from "lodash";
9 | import Output from "./Output";
10 | import TermInfo from "./TermInfo";
11 | import {
12 | CmdNotFound,
13 | Empty,
14 | Form,
15 | Hints,
16 | Input,
17 | MobileBr,
18 | MobileSpan,
19 | Wrapper,
20 | } from "./styles/Terminal.styled";
21 | import { argTab } from "../utils/funcs";
22 |
23 | type Command = {
24 | cmd: string;
25 | desc: string;
26 | tab: number;
27 | }[];
28 |
29 | export const commands: Command = [
30 | { cmd: "about", desc: "About Zemerik", tab: 8 },
31 | { cmd: "clear", desc: "Clear the terminal", tab: 8 },
32 | { cmd: "education", desc: "Education background", tab: 4 },
33 | { cmd: "email", desc: "Contact Me Through My Email", tab: 8 },
34 | { cmd: "help", desc: "List Available Commands", tab: 9 },
35 | { cmd: "history", desc: "View Command History", tab: 6 },
36 | { cmd: "projects", desc: "View All My Projects", tab: 5 },
37 | { cmd: "pwd", desc: "Your Current Working Directory", tab: 10 },
38 | { cmd: "socials", desc: "Follow Zemerik around the Web", tab: 6 },
39 | { cmd: "welcome", desc: "Display Welcome Section", tab: 6 },
40 | { cmd: "whoami", desc: "Get to know who you are.", tab: 7 },
41 | ];
42 |
43 | type Term = {
44 | arg: string[];
45 | history: string[];
46 | rerender: boolean;
47 | index: number;
48 | clearHistory?: () => void;
49 | };
50 |
51 | export const termContext = createContext({
52 | arg: [],
53 | history: [],
54 | rerender: false,
55 | index: 0,
56 | });
57 |
58 | const Terminal = () => {
59 | const containerRef = useRef(null);
60 | const inputRef = useRef(null);
61 |
62 | const [inputVal, setInputVal] = useState("");
63 | const [cmdHistory, setCmdHistory] = useState(["welcome"]);
64 | const [rerender, setRerender] = useState(false);
65 | const [hints, setHints] = useState([]);
66 | const [pointer, setPointer] = useState(-1);
67 |
68 | const handleChange = useCallback(
69 | (e: React.ChangeEvent) => {
70 | setRerender(false);
71 | setInputVal(e.target.value);
72 | },
73 | [inputVal]
74 | );
75 |
76 | const handleSubmit = (e: React.FormEvent) => {
77 | e.preventDefault();
78 | setCmdHistory([inputVal, ...cmdHistory]);
79 | setInputVal("");
80 | setRerender(true);
81 | setHints([]);
82 | setPointer(-1);
83 | };
84 |
85 | const clearHistory = () => {
86 | setCmdHistory([]);
87 | setHints([]);
88 | };
89 |
90 | // focus on input when terminal is clicked
91 | const handleDivClick = () => {
92 | inputRef.current && inputRef.current.focus();
93 | };
94 | useEffect(() => {
95 | document.addEventListener("click", handleDivClick);
96 | return () => {
97 | document.removeEventListener("click", handleDivClick);
98 | };
99 | }, [containerRef]);
100 |
101 | // Keyboard Press
102 | const handleKeyDown = (e: React.KeyboardEvent) => {
103 | setRerender(false);
104 | const ctrlI = e.ctrlKey && e.key.toLowerCase() === "i";
105 | const ctrlL = e.ctrlKey && e.key.toLowerCase() === "l";
106 |
107 | // if Tab or Ctrl + I
108 | if (e.key === "Tab" || ctrlI) {
109 | e.preventDefault();
110 | if (!inputVal) return;
111 |
112 | let hintsCmds: string[] = [];
113 | commands.forEach(({ cmd }) => {
114 | if (_.startsWith(cmd, inputVal)) {
115 | hintsCmds = [...hintsCmds, cmd];
116 | }
117 | });
118 |
119 | const returnedHints = argTab(inputVal, setInputVal, setHints, hintsCmds);
120 | hintsCmds = returnedHints ? [...hintsCmds, ...returnedHints] : hintsCmds;
121 |
122 | // if there are many command to autocomplete
123 | if (hintsCmds.length > 1) {
124 | setHints(hintsCmds);
125 | }
126 | // if only one command to autocomplete
127 | else if (hintsCmds.length === 1) {
128 | const currentCmd = _.split(inputVal, " ");
129 | setInputVal(
130 | currentCmd.length !== 1
131 | ? `${currentCmd[0]} ${currentCmd[1]} ${hintsCmds[0]}`
132 | : hintsCmds[0]
133 | );
134 |
135 | setHints([]);
136 | }
137 | }
138 |
139 | // if Ctrl + L
140 | if (ctrlL) {
141 | clearHistory();
142 | }
143 |
144 | // Go previous cmd
145 | if (e.key === "ArrowUp") {
146 | if (pointer >= cmdHistory.length) return;
147 |
148 | if (pointer + 1 === cmdHistory.length) return;
149 |
150 | setInputVal(cmdHistory[pointer + 1]);
151 | setPointer(prevState => prevState + 1);
152 | inputRef?.current?.blur();
153 | }
154 |
155 | // Go next cmd
156 | if (e.key === "ArrowDown") {
157 | if (pointer < 0) return;
158 |
159 | if (pointer === 0) {
160 | setInputVal("");
161 | setPointer(-1);
162 | return;
163 | }
164 |
165 | setInputVal(cmdHistory[pointer - 1]);
166 | setPointer(prevState => prevState - 1);
167 | inputRef?.current?.blur();
168 | }
169 | };
170 |
171 | // For caret position at the end
172 | useEffect(() => {
173 | const timer = setTimeout(() => {
174 | inputRef?.current?.focus();
175 | }, 1);
176 | return () => clearTimeout(timer);
177 | }, [inputRef, inputVal, pointer]);
178 |
179 | return (
180 |
181 | {hints.length > 1 && (
182 |
183 | {hints.map(hCmd => (
184 | {hCmd}
185 | ))}
186 |
187 | )}
188 |
207 |
208 | {cmdHistory.map((cmdH, index) => {
209 | const commandArray = _.split(_.trim(cmdH), " ");
210 | const validCommand = _.find(commands, { cmd: commandArray[0] });
211 | const contextValue = {
212 | arg: _.drop(commandArray),
213 | history: cmdHistory,
214 | rerender,
215 | index,
216 | clearHistory,
217 | };
218 | return (
219 |
220 |
221 |
222 |
223 | >
224 | {cmdH}
225 |
226 | {validCommand ? (
227 |
228 |
229 |
230 | ) : cmdH === "" ? (
231 |
232 | ) : (
233 |
234 | command not found: {cmdH}
235 |
236 | )}
237 |
238 | );
239 | })}
240 |
241 | );
242 | };
243 |
244 | export default Terminal;
245 |
--------------------------------------------------------------------------------