Skip to content

Commit a686bb6

Browse files
authored
Merge pull request #55 from csesoc/I2-15/interactable-terminal
[I-25]/interactable terminal v2
2 parents 1ca1407 + 7887c20 commit a686bb6

File tree

1 file changed

+70
-50
lines changed

1 file changed

+70
-50
lines changed

frontend/src/components/Terminal.tsx

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,120 @@
11
import { useRouter } from "next/router";
2-
import { useRef, useState } from "react";
2+
import { useEffect, useRef, useState } from "react";
33

44
const Terminal = () => {
5-
// Store the value of the input
65
const [value, setValue] = useState("");
6+
const [inputFocused, setInputFocused] = useState(false);
7+
8+
const inputRef = useRef<HTMLInputElement>(null);
9+
const router = useRouter();
710

811
// Automatically select the end of the input as the custom
912
// cursor only works at the end of the input.
10-
const inputRef = useRef<HTMLInputElement>(null);
1113
const setInputEnd = () => {
1214
if (inputRef.current) {
1315
const len = inputRef.current.value.length;
1416
inputRef.current.setSelectionRange(len, len);
1517
}
16-
}
18+
};
1719

18-
// Keep track of if the input is focused
19-
const [inputFocused, setInputFocused] = useState(false);
20+
// Use localStorage to keep focus on the terminal if redirecting using terminal
21+
useEffect(() => {
22+
if (localStorage.getItem("fromTerminal") === "true") {
23+
localStorage.removeItem("fromTerminal");
24+
if (inputRef.current) {
25+
inputRef.current.focus();
26+
setInputEnd();
27+
setInputFocused(true);
28+
}
29+
}
30+
}, []);
2031

21-
// Using the router to change pages seamlessly
22-
const router = useRouter();
2332
const goToPage = (target: string) => {
33+
localStorage.setItem("fromTerminal", "true");
2434
router.push(target);
2535
};
26-
36+
2737
// Checking for "Enter" and if so, changing to
2838
// the inputted page
2939
const handleKey = (key: string) => {
3040
if (key !== "Enter") return;
3141

32-
if (value.toLowerCase() === "~"
33-
|| value.toLowerCase() === "cd"
34-
|| value.toLowerCase() === "cd ~"
35-
|| value.toLowerCase() === "cd .."
36-
) {
42+
const cmd = value.toLowerCase().trim();
43+
44+
if (["~", "cd", "cd ~", "cd .."].includes(cmd)) {
3745
goToPage("/");
38-
} else if (value.toLowerCase() === "cd about"
39-
|| value.toLowerCase() === "cd about us"
40-
|| value.toLowerCase() === "cd about_us"
41-
) {
46+
} else if (["cd about", "cd about us", "cd about_us"].includes(cmd)) {
4247
goToPage("/about");
43-
} else if (value.toLowerCase() === "cd events"
44-
|| value.toLowerCase() === "cd event"
45-
) {
48+
} else if (["cd events", "cd event"].includes(cmd)) {
4649
goToPage("/events");
47-
} else if (value.toLowerCase() === "cd resources"
48-
|| value.toLowerCase() === "cd resource"
49-
) {
50+
} else if (["cd resources", "cd resource"].includes(cmd)) {
5051
goToPage("/resources");
51-
} else if (value.toLowerCase() === "cd sponsors"
52-
|| value.toLowerCase() === "cd sponsor"
53-
) {
52+
} else if (["cd sponsors", "cd sponsor"].includes(cmd)) {
5453
goToPage("/sponsors");
55-
} else if (value.toLowerCase() === "cd contact"
56-
|| value.toLowerCase() === "cd contacts"
57-
|| value.toLowerCase() === "cd contact us"
58-
|| value.toLowerCase() === "cd contact_us"
59-
) {
54+
} else if (["cd contact", "cd contacts", "cd contact us", "cd contact_us"].includes(cmd)) {
6055
goToPage("/contact-us");
56+
} else if (cmd === "cd constitution") {
57+
goToPage("/about/constitution");
58+
} else if (
59+
["cd execs", "cd directors", "cd subcom", "cd execs directors subcom", "cd execs-directors-subcom", "cd execs_directors_subcom"].includes(cmd)
60+
) {
61+
goToPage("/about/execs-directors-subcom");
62+
} else if (
63+
["history", "cd our history", "cd our-history", "cd our_history"].includes(cmd)
64+
) {
65+
goToPage("/about/our-history");
66+
} else if (
67+
["cd faq", "cd faqs", "cd questions", "cd frequently asked questions"].includes(cmd)
68+
) {
69+
goToPage("/about/faqs");
70+
} else if (
71+
["cd election-guide", "cd election guide", "cd election"].includes(cmd)
72+
) {
73+
goToPage("/about/election-guide");
6174
}
6275

63-
clearInput()
76+
clearInput();
6477
};
6578

6679
const clearInput = () => {
6780
setValue("");
6881
};
6982

7083
return (
71-
// Using relative + absolute to overlap the `input` and `span`
7284
<span className="relative">
73-
{/* The input */}
74-
<input type="text" id="input" value={value} ref={inputRef} maxLength={40}
85+
<input
86+
type="text"
87+
id="input"
88+
value={value}
89+
ref={inputRef}
90+
maxLength={40}
7591
className="absolute text-blue-500 p-0 m-0 bg-transparent outline-none caret-transparent w-[50vw] z-10"
7692
onKeyDown={(e) => {
77-
handleKey(e.key)
78-
setInputEnd()
93+
handleKey(e.key);
94+
setInputEnd();
7995
}}
8096
onChange={(e) => setValue(e.target.value)}
8197
onFocus={() => setInputFocused(true)}
8298
onBlur={() => {
83-
clearInput()
84-
setInputFocused(false)
99+
clearInput();
100+
setInputFocused(false);
85101
}}
86-
></input>
87-
{/* The custom cursor */}
102+
/>
88103
<span className="absolute w-[60vw] p-0 m-0 z-0">
89-
{/* The invisable span that is the same length as the input */}
104+
<span className="invisible whitespace-pre pointer-events-none text-base">
105+
{value}
106+
</span>
90107
<span
91-
className="invisible whitespace-pre pointer-events-none text-base"
92-
>{value}</span>
93-
{/* The custom cursor */}
94-
<span id="cursor" className={`text-${inputFocused ? "white" : "gray-500"} pointer-events-none inline-block animate-blink p-0 m-0`}>_</span>
108+
id="cursor"
109+
className={`text-${
110+
inputFocused ? "white" : "gray-500"
111+
} pointer-events-none inline-block animate-blink p-0 m-0`}
112+
>
113+
_
114+
</span>
95115
</span>
96116
</span>
97-
)
98-
}
117+
);
118+
};
99119

100-
export default Terminal
120+
export default Terminal;

0 commit comments

Comments
 (0)