Skip to content

Commit b0c0d96

Browse files
authored
Added termino.js 📥
1 parent 4311995 commit b0c0d96

File tree

1 file changed

+389
-0
lines changed

1 file changed

+389
-0
lines changed

src/termino.js

Lines changed: 389 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,389 @@
1+
/**!
2+
* @license Termino.js - A JavaScript library to make custom terminals in the browser with support for executing your own custom functions!
3+
* VERSION: 1.0.0
4+
* LICENSED UNDER MIT LICENSE
5+
* MORE INFO CAN BE FOUND AT https://github.com/MarketingPipeline/Termino.js/
6+
*/
7+
8+
9+
10+
/* DEV & CONTRIBUTOR NOTES IE: TODO LIST -
11+
12+
- NEED TO IMPROVE / WRITE DOCUMENTATION FOR LIBRARY - URGENT TASK NEEDS HELP BIG TIME!
13+
- CREATE DOCUMENATION WEBSITE HOSTED VIA GITHUB PAGES BRANCH
14+
- REMOVE WIKI FROM REPO.
15+
- NEED TO IMPROVE USAGE OF LOOPING INPUTS / ASKING USER FOR INPUTS CONSTANTLY...? (ANY SUGGESTIONS APPRECIATED)
16+
- SUPPORT FOR MULTIPLE KEYBINDS / KEYBOARD SHORT CUTS (VIA MOUSETRAP ON NPM / GITHUB)
17+
- POSSIBILY MAKE PLUGINS / FUNCTIONS THAT CAN BE SHARED.
18+
- CREATE TEMPLATE TO USE PLUGIN / CREATE PLUGINS
19+
- PLUGIN FOR CREATING TERMINAL ANIMATIONS VIA A TYPEWRITER / TYPING LIBRARY ETC..
20+
- PLUGIN FOR PLAYING PRE-RECORDED TERMINAL ANIMATIONS.
21+
- IMPROVE ERROR HANDLING (CHECK IF PASSED PROPER ARGUMENTS - CHECK IF VARIABLE IS ARRAY TYPE / JSON TYPE ETC...)
22+
- CODE CLEANING - USE CONST INSTEAD OF LET VARIABLES WHEN CAN.
23+
- CLEAN / IMPROVE FILTER FUNCTION.
24+
- REMOVE EVENT HANDLERS WHEN TERMINAL INSTANCE IS KILLED.
25+
- MAKE THIS A EXPORT THIS AS WELL & GLOBAL IN SAME SCRIPT....? (DISCUSSION)
26+
- INTERATION OBERSERVER FOR ANIMATIONS / POSSIBLY ALL INSTANCES.
27+
- DOM OBSERVER (FOR NEW TERMINALS IN DOM)
28+
- ADD ANY POLYFILL SUPPORTS NEEDED / UNTHOUGHT OF.
29+
- CREATE TESTS + ACTION / WORKFLOW
30+
- BROWSER AUTOMATION TESTS VIA PUPPETEER ETC (CHECK ALL DEVICES / BROWSER COMPABILITY + POSSIBLY SCREENSHOTS).
31+
- OTHER TESTS.
32+
- CREATE ACTION THAT AUTO TESTS ON PR.
33+
- IF ANYONE COULD HELP WRITING TESTS / THESE WOULD BE APPRECIATED.
34+
- LOTS OF OTHER IMPROVEMENTS THAT CAN BE MADE THO THIS. IF YOU ARE WILLING TO IMPROVE IT. FEEL FREE! :)
35+
36+
37+
38+
*/
39+
40+
// POLYFILL SUPPORT (AUTO-DETECTED ON LOAD FOR DEVICE)
41+
import 'https://polyfill.io/v3/polyfill.min.js?features=Array.prototype.filter,console,document,JSON,Promise'
42+
43+
export function Termino(terminalSelector, keyCodes, settings) {
44+
45+
try {
46+
// DEFAULT TERMINAL SETTINGS
47+
let DEF_SETTINGS = {
48+
allow_scroll: true, // allow scroll up & down on terminal
49+
prompt: "> ", // default prompt
50+
command_key: 13, // default command key
51+
terminal_killed_placeholder: "TERMINAL DISABLED", // default terminal input placeholder when killed.
52+
terminal_output: ".termino-console", // default output query selector
53+
terminal_input: ".termino-input", // default input query selector
54+
disable_terminal_input: false // disable any user commands / inputs. --- Useful for making terminal animations etc!
55+
}
56+
57+
/// ALLOW DEVS TO PASS CUSTOM SETTINGS FOR TERMINAL
58+
if (settings) {
59+
// function to compare custom settings
60+
function compare(a, b) {
61+
return JSON.stringify(a) === JSON.stringify(b);
62+
}
63+
// CUSTOM SETTINGS PASSED ARE NOT VALID
64+
if (compare(DEF_SETTINGS, settings) != true) {
65+
throw {
66+
message: "Settings Error: Your overwritten Termino settings are not valid"
67+
}
68+
} else {
69+
// CUSTOM SETTINGS ARE VALID
70+
DEF_SETTINGS = settings
71+
}
72+
}
73+
74+
75+
let terminal_console = terminalSelector.querySelector(DEF_SETTINGS.terminal_output)
76+
77+
78+
79+
/// DEFAULT TERMINAL KEY CODES
80+
let KEYCODES = [{
81+
"id": "SCROLL_UP_KEY", /// DEFAULT SCROLL UP KEY - "SCROLL_UP_KEY" ID NAME IS REQUIRED.
82+
"key_code": 38
83+
// "function": example() // you can add your own custom function to the scroll up button if needed!
84+
}, {
85+
"id": "SCROLL_DOWN_KEY", // DFEAULT SCROLL DOWN KEY - "SCROLL_DOWN_KEY" ID NAME IS REQUIRED.
86+
"key_code": 40
87+
// "function": example() // you can add your own custom function to the scroll down button if needed!
88+
}];
89+
90+
91+
92+
// DEFAULT SCROLL BTNS
93+
94+
/// DOWN ARROW
95+
let Scroll_Down_Key = KEYCODES.filter(x => x.id === "SCROLL_DOWN_KEY")[0].key_code
96+
97+
/// UP ARROW
98+
let Scroll_Up_Key = KEYCODES.filter(x => x.id === "SCROLL_UP_KEY")[0].key_code
99+
100+
101+
102+
103+
/// ALLOW DEVS TO PASS CUSTOM KEYCODE FUNCTIONS FOR TERMINAL
104+
105+
if (keyCodes) {
106+
// Check if scroll up key has been set
107+
if (keyCodes.filter(x => x.id === "SCROLL_UP_KEY").length != 0) {
108+
if (keyCodes.filter(x => x.id === "SCROLL_UP_KEY")[0].key_code != undefined) {
109+
// set custom scroll up key
110+
Scroll_Up_Key = keyCodes.filter(x => x.id === "SCROLL_UP_KEY")[0].key_code
111+
}
112+
}
113+
// Check if scroll down key has been set
114+
if (keyCodes.filter(x => x.id === "SCROLL_DOWN_KEY").length != 0) {
115+
if (keyCodes.filter(x => x.id === "SCROLL_DOWN_KEY")[0].key_code != undefined) {
116+
// set custom scroll down key
117+
Scroll_Down_Key = keyCodes.filter(x => x.id === "SCROLL_DOWN_KEY")[0].key_code
118+
}
119+
}
120+
KEYCODES = keyCodes
121+
}
122+
123+
124+
125+
126+
// DEFAULT COMMAND KEY - ie - ENTER BTN
127+
let Command_Key = DEF_SETTINGS.command_key
128+
129+
130+
131+
132+
// Handle keyboard events for the Termino.js instance.
133+
terminalSelector.addEventListener('keydown', e => {
134+
135+
//// DISABLE TERMINAL SCROLL UP / DOWN FOR ANIMATIONS ETC...
136+
if (DEF_SETTINGS.disable_terminal_input != true) {
137+
/// HANDLE INPUTS ON COMMAND KEY.
138+
checkIfCommand()
139+
}
140+
141+
/// SCROLL UP / DOWN TERMINAL FUNCTION
142+
if (DEF_SETTINGS.allow_scroll === true) {
143+
if (e.keyCode == Scroll_Up_Key) {
144+
/// SCROLL TERMINAL UP
145+
terminal_console.scrollTo({
146+
top: 0,
147+
behavior: 'smooth'
148+
});
149+
} else if (e.keyCode == Scroll_Down_Key) {
150+
/// SCROLL TERMINAL DOWN
151+
terminal_console.scrollTop = terminal_console.scrollHeight;
152+
}
153+
}
154+
});
155+
156+
157+
158+
159+
// TERMINAL INPUT STATE / TERMINAL PROMPT FUNCTION
160+
let InputState = false;
161+
162+
function termInput(question) {
163+
return new Promise(function(resolve) {
164+
165+
/// add the question value to terminal
166+
terminal_console.innerHTML += question
167+
168+
169+
termClearValue()
170+
171+
scrollTerminalToBottom()
172+
173+
InputState = true;
174+
175+
function handleCommandForQuestion(event) {
176+
177+
if (event.keyCode == Command_Key) {
178+
if (window.event.preventDefault) {
179+
window.event.preventDefault()
180+
}
181+
let value = terminalSelector.querySelector(DEF_SETTINGS.terminal_input).value
182+
termClearValue()
183+
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).removeEventListener('keypress', handleCommandForQuestion);
184+
InputState = false;
185+
if (value.length != 0) {
186+
// echo value to terminal
187+
termEcho(value)
188+
resolve(value)
189+
} else {
190+
// return an empty prompt
191+
termEcho("")
192+
resolve()
193+
}
194+
195+
}
196+
}
197+
198+
/// Handle inputs for question state.
199+
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).addEventListener('keypress', handleCommandForQuestion);
200+
201+
202+
})
203+
}
204+
205+
206+
207+
208+
// FUNCTION TO OUTPUT TO TERMINAL (WITH TERMINAL PROMPT)
209+
function termEcho(command) {
210+
terminal_console.innerHTML += `<pre>${DEF_SETTINGS.prompt} ${command}</pre>`
211+
scrollTerminalToBottom()
212+
}
213+
214+
215+
// FUNCTION TO OUTPUT TO TERMINAL (WITHOUT TERMINAL PROMPT)
216+
function termOutput(command) {
217+
terminal_console.innerHTML += `<pre>${command}</pre>`
218+
scrollTerminalToBottom()
219+
}
220+
221+
222+
// FUNCTION TO CLEAR TERMINAL CONSOLE OUTPUT
223+
function termClear() {
224+
terminal_console.innerHTML = ``
225+
}
226+
227+
228+
229+
/// DEFAULT FUNCTION TO KILL TERMINAL - DEVS CAN PROGRAM THEIR OWN IF THEY WANT.
230+
function termKill() {
231+
/// TODO - REMOVE EVENT LISTENERS
232+
233+
/// clear terminal
234+
termClear()
235+
236+
/// set the terminal text values to disabled
237+
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).setAttribute("disabled", "");
238+
239+
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).setAttribute("placeholder", DEF_SETTINGS.terminal_killed_placeholder);
240+
241+
}
242+
243+
244+
/// FUNCTION TO ENABLE TERMINAL INPUT
245+
function termEnable() {
246+
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).removeAttribute("disabled", "");
247+
}
248+
249+
/// FUNCTION TO DISABLE TERMINAL INPUT
250+
function termDisable() {
251+
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).setAttribute("disabled", "");
252+
}
253+
254+
255+
/// FUNCTION TO REMOVE / CLEAR INPUT VALUE
256+
function termClearValue() {
257+
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).value = ""
258+
}
259+
260+
/// FUNCTION TO DELAY TERMINAL OUTPUTS / ECHO ETC (AWAIT / PROMISE BASED) - EXAMPLE : await term.delay(xxx) ...
261+
const termDelay = ms => new Promise(res => setTimeout(res, ms));
262+
263+
264+
265+
/// FUNCTION TO SCROLL TERMINAL TO THE BOTTOM
266+
function scrollTerminalToBottom() {
267+
terminal_console.scrollTop = terminal_console.scrollHeight;
268+
}
269+
270+
/// FUNCTION TO SCROLL TERMINAL TO THE TOP
271+
function scrollTerminalToTop() {
272+
terminal_console.scrollTo({
273+
top: 0,
274+
behavior: 'smooth'
275+
});
276+
}
277+
278+
279+
280+
/// DISABLE INPUT IF DEFAULT SETTING IS SET TO TRUE
281+
if (DEF_SETTINGS.disable_terminal_input === true) {
282+
terminalSelector.querySelector(DEF_SETTINGS.terminal_input).setAttribute("disabled", "");
283+
}
284+
285+
286+
/// ADD ELEMENT TO TERMINAL BY ID, HTML & CLASS NAME
287+
function addElementWithID(id, html, class_name) {
288+
let g = null;
289+
g = document.createElement('div');
290+
if (class_name) {
291+
g.setAttribute("class", class_name);
292+
}
293+
g.setAttribute("id", id);
294+
if (html) {
295+
g.innerHTML = html
296+
}
297+
terminal_console.appendChild(g);
298+
}
299+
300+
301+
/// REMOVE ADDED ELEMENT FROM TERMINAL BY ID
302+
function removeElementWithID(id) {
303+
try {
304+
terminalSelector.querySelector("#" + id).outerHTML = "";
305+
} catch (error) {
306+
throw {
307+
message: `Error could not find ${error.message}`
308+
}
309+
}
310+
}
311+
312+
313+
314+
// If the user has pressed COMMAND btn - default btn is enter
315+
async function checkIfCommand() {
316+
317+
let key = window.event.keyCode;
318+
319+
320+
321+
/// RUN ANY FUNCTIONS FOR KEYCODES / KEYBIND SHORTCUTS / BUTTONS.
322+
if (KEYCODES.filter(x => x.key_code === key).length != 0) {
323+
KEYCODES = KEYCODES.filter(x => x.key_code === key)
324+
if (KEYCODES.length != 0) {
325+
if (KEYCODES[0].function != undefined)
326+
try {
327+
await eval(KEYCODES[0].function)
328+
} catch (error) {
329+
throw {
330+
message: `KeyCode Function Error: ${error.message}`
331+
}
332+
}
333+
}
334+
}
335+
336+
337+
338+
/// MAKE SURE USER IS NOT ANSWERING A QUESTION
339+
if (InputState != true) {
340+
341+
342+
/// ECHO INPUT VALUE ON COMMAND BUTTON - BY DEFAULT IS ENTER.
343+
if (key === Command_Key) {
344+
345+
346+
/// STOP ENTER FROM GOING DOWN / DOING WEIRD THINGS..
347+
if (window.event.preventDefault) {
348+
window.event.preventDefault()
349+
}
350+
351+
/// ECHO USER INPUT
352+
termEcho(terminalSelector.querySelector(DEF_SETTINGS.terminal_input).value)
353+
354+
355+
/// CLEAR OUTPUT
356+
termClearValue()
357+
358+
359+
}
360+
361+
}
362+
363+
} /// DEFAULT TERMINO FUNCTIONS FOR DEVELOPER USAGE
364+
return {
365+
echo: termEcho, // ECHO MESSAGE TO TERM WITH CAROT
366+
output: termOutput, // ECHO MESSAGE TO TERM WITHOUT CAROT
367+
clear: termClear, // CLEAR THE TERMINAL
368+
delay: termDelay, // DELAY FUNCTION BY X VALUE OF SECONDS
369+
disable_input: termDisable, // DISABLE TERMINAL INPUT
370+
enable_input: termEnable, // ENABLE TERMINAL INPUT
371+
input: termInput, // ASK USER QUESTION & RETURN VALUE
372+
scroll_to_bottom: scrollTerminalToBottom, // SCROLL TERMINAL TO THE BOTTOM
373+
scroll_to_top: scrollTerminalToTop, // SCROLL TERMINAL TO TOP
374+
add_element: addElementWithID, // ADD HTML ELEMENT WITH ID TO TERMINAL,
375+
remove_element: removeElementWithID, // REMOVE HTML ELEMENT WITH ID TO TERMINAL,
376+
kill: termKill // KILL THE TERMIMAL - IE.. SET INPUT TO DISABLED & CLEAR THE TERMINAL.
377+
};
378+
} catch (error) {
379+
// Something went wrong!
380+
console.error(`Termino.js Error: ${error.message}`)
381+
throw {
382+
message: `Termino.js Error: ${error.message}`
383+
}
384+
}
385+
}
386+
387+
if (typeof document === 'undefined') {
388+
console.error("Termino.js is only supported for the browser")
389+
}

0 commit comments

Comments
 (0)