From c14428346e43eeff3009253e13d91cffe4cf1c42 Mon Sep 17 00:00:00 2001 From: Shubham Raj Date: Sun, 6 Oct 2024 02:42:56 +0530 Subject: [PATCH] [feat] Add the command usage syntax hint, inline --- .../playground-web/components/Shell/Shell.tsx | 26 ++++++++- .../components/Shell/__tests__/index.test.tsx | 48 ++++++++++++++++ .../components/Shell/hooks/useShell.tsx | 29 +++++++++- apps/playground-web/data/commandSyntaxMap.ts | 56 +++++++++++++++++++ 4 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 apps/playground-web/data/commandSyntaxMap.ts diff --git a/apps/playground-web/components/Shell/Shell.tsx b/apps/playground-web/components/Shell/Shell.tsx index 96768ee..76d4a69 100644 --- a/apps/playground-web/components/Shell/Shell.tsx +++ b/apps/playground-web/components/Shell/Shell.tsx @@ -1,13 +1,21 @@ // src/components/Shell/Shell.tsx 'use client'; +import React from 'react'; // hooks import { useShell } from './hooks/useShell'; +import { SyntaxPart } from '@/data/commandSyntaxMap'; interface ShellProps { decreaseCommandsLeft: () => void; } +const InlineHint = ({ part }: { part: SyntaxPart }) => ( + + {' ' + part.syntax} + +); + export default function Shell({ decreaseCommandsLeft }: ShellProps) { const { handleInputChange, @@ -16,7 +24,9 @@ export default function Shell({ decreaseCommandsLeft }: ShellProps) { inputRef, output, command, + remainingSyntax, } = useShell(decreaseCommandsLeft); + return (
))}
-

dice ~$

-
+

dice ~$

+
+
+ {remainingSyntax.map((part, index) => ( + + {index > 0} + + + ))} +
diff --git a/apps/playground-web/components/Shell/__tests__/index.test.tsx b/apps/playground-web/components/Shell/__tests__/index.test.tsx index 1fd31f8..704f812 100644 --- a/apps/playground-web/components/Shell/__tests__/index.test.tsx +++ b/apps/playground-web/components/Shell/__tests__/index.test.tsx @@ -109,4 +109,52 @@ describe('Shell Component', () => { await user.keyboard('[ArrowDown]'); expect(cliInputElement.value).toBe(newCommand); }); + + it('should show syntax usage hint for SET', async () => { + const { cliInputElement, user, getByTestId } = setupTest(); + + const newCommand = 'set'; + await user.type(cliInputElement, newCommand); + + const inlineHint = getByTestId('inline-hint'); + expect(inlineHint.childElementCount).toBe(4); + + const inlineHintChild = inlineHint.childNodes; + + expect(inlineHintChild[0]).toHaveTextContent('Key'); + expect(inlineHintChild[1]).toHaveTextContent('Value'); + expect(inlineHintChild[2]).toHaveTextContent('[NX | XX]'); + expect(inlineHintChild[3]).toHaveTextContent( + '[EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]', + ); + }); + + it('should show syntax usage hint for GET', async () => { + const { cliInputElement, user, getByTestId } = setupTest(); + + const newCommand = 'get'; + await user.type(cliInputElement, newCommand); + + const inlineHint = getByTestId('inline-hint'); + expect(inlineHint.childElementCount).toBe(1); + + const inlineHintChild = inlineHint.childNodes; + + expect(inlineHintChild[0]).toHaveTextContent('Key'); + }); + + it('should show syntax usage hint for DEL', async () => { + const { cliInputElement, user, getByTestId } = setupTest(); + + const newCommand = 'del'; + await user.type(cliInputElement, newCommand); + + const inlineHint = getByTestId('inline-hint'); + expect(inlineHint.childElementCount).toBe(2); + + const inlineHintChild = inlineHint.childNodes; + + expect(inlineHintChild[0]).toHaveTextContent('Key'); + expect(inlineHintChild[1]).toHaveTextContent('[Key ...]'); + }); }); diff --git a/apps/playground-web/components/Shell/hooks/useShell.tsx b/apps/playground-web/components/Shell/hooks/useShell.tsx index b3ced95..0033aec 100644 --- a/apps/playground-web/components/Shell/hooks/useShell.tsx +++ b/apps/playground-web/components/Shell/hooks/useShell.tsx @@ -3,7 +3,8 @@ import { useState, useEffect, useRef, KeyboardEvent, ChangeEvent } from 'react'; // utils import { handleCommand } from '@/shared/utils/shellUtils'; -import blacklistedCommands from '@/shared/utils/blacklist'; +import blacklistedCommands from '@/shared/utils/blacklist'; // Assuming you added blacklist here +import { syntaxMap, SyntaxPart } from '@/data/commandSyntaxMap'; export const useShell = (decreaseCommandsLeft: () => void) => { // states @@ -13,6 +14,7 @@ export const useShell = (decreaseCommandsLeft: () => void) => { // Initialise the command history with sessionStorage const [commandHistory, setCommandHistory] = useState([]); const [historyIndex, setHistoryIndex] = useState(-1); + const [remainingSyntax, setRemainingSyntax] = useState([]); // useRefs const terminalRef = useRef(null); @@ -39,6 +41,24 @@ export const useShell = (decreaseCommandsLeft: () => void) => { decreaseCommandsLeft(); // Call to update remaining commands }; + const updateSyntax = (value: string) => { + const inputParts = value.trim().split(' '); + const command = inputParts[0].toUpperCase(); + if (syntaxMap[command]) { + const parts = syntaxMap[command].parts; + if (inputParts.length === 1) { + // Only command typed, show all parts + setRemainingSyntax(parts); + } else { + // Show remaining parts based on what's already typed + const remainingParts = parts.slice(inputParts.length - 1); + setRemainingSyntax(remainingParts); + } + } else { + setRemainingSyntax([]); + } + }; + useEffect(() => { if (terminalRef.current) { terminalRef.current.scrollTop = terminalRef.current.scrollHeight; @@ -64,9 +84,10 @@ export const useShell = (decreaseCommandsLeft: () => void) => { }, []); const handleInputChange = (e: ChangeEvent) => { - setCommand(e.target.value); - // Save current input when starting to navigate history + const value = e.target.value; + setCommand(value); setTempCommand(e.target.value); + updateSyntax(value); }; const handleKeyDown = (e: KeyboardEvent) => { @@ -76,6 +97,7 @@ export const useShell = (decreaseCommandsLeft: () => void) => { setCommandHistory((prev) => [...prev, command]); setHistoryIndex(-1); } + setRemainingSyntax([]); return; } @@ -125,5 +147,6 @@ export const useShell = (decreaseCommandsLeft: () => void) => { command, tempCommand, setTempCommand, + remainingSyntax, }; }; diff --git a/apps/playground-web/data/commandSyntaxMap.ts b/apps/playground-web/data/commandSyntaxMap.ts new file mode 100644 index 0000000..ff7acc5 --- /dev/null +++ b/apps/playground-web/data/commandSyntaxMap.ts @@ -0,0 +1,56 @@ +export type SyntaxPart = { + syntax: string; + doc: string; +}; + +export type CommandSyntax = { + parts: SyntaxPart[]; +}; + +export type SyntaxMap = { + [command: string]: CommandSyntax; +}; + +export const syntaxMap: SyntaxMap = { + SET: { + parts: [ + { + syntax: 'Key', + doc: 'The key under which to store the value', + }, + { + syntax: 'Value', + doc: 'The value to be stored', + }, + { + syntax: '[NX | XX]', + doc: 'NX - Only set if key does not exist. XX - Only set if key exists', + }, + { + syntax: + '[EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]', + doc: 'Options to set the key expiration: EX (seconds), PX (milliseconds), EXAT/PXAT (unix timestamp), or KEEPTTL to retain existing TTL', + }, + ], + }, + GET: { + parts: [ + { + syntax: 'Key', + doc: 'Key of the value you want to retrive', + }, + ], + }, + DEL: { + parts: [ + { + syntax: 'Key', + doc: 'Key that you want to delete', + }, + { + syntax: '[Key ...]', + doc: 'Multiple keys you want to delete', + }, + ], + }, +};