Skip to content

Commit

Permalink
implemented search with DiceCmds static data (resolves #8) (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
rishavvajpayee authored Sep 30, 2024
1 parent 02d9156 commit f4055a7
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 99 deletions.
70 changes: 47 additions & 23 deletions src/components/CLI/CLI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,52 @@
import React, { useEffect, useRef, useState } from 'react';

interface CliProps {
output: string[];
command: string;
setCommand: React.Dispatch<React.SetStateAction<string>>;
handleCommand: React.KeyboardEventHandler<HTMLInputElement>;
decreaseCommandsLeft: () => void;
}

export default function Cli({ output, command, setCommand, handleCommand }: CliProps) {
export default function Cli({ decreaseCommandsLeft }: CliProps) {
const [command, setCommand] = useState("");
const [output, setOutput] = useState<string[]>([]);
const terminalRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const [cursorPosition, setCursorPosition] = useState(0);
const [store, setStore] = useState<{ [key: string]: string }>({});

const handleCommand = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
const newOutput = `dice ~$ ${command}`;
let result = "";
const [cmd, ...args] = command.split(" ");

switch (cmd.toUpperCase()) {
case "GET":
result = store[args[0]] || "(nil)";
break;
case "SET":
if (args.length === 2) {
const [key, value] = args;
setStore((prevStore) => ({ ...prevStore, [key]: value }));
result = "OK";
} else {
result = "Invalid command. Usage: SET key value";
}
break;
case "CLEAR":
setOutput([]);
setCommand("");
return;
case "":
setCommand("");
return;
default:
setCommand("");
return;
}

setOutput((prevOutput) => [...prevOutput, newOutput, result]);
setCommand("");
decreaseCommandsLeft();
}
};

useEffect(() => {
if (terminalRef.current) {
Expand All @@ -28,48 +64,36 @@ export default function Cli({ output, command, setCommand, handleCommand }: CliP

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setCommand(e.target.value);
setCursorPosition(e.target.selectionStart || 0);
};

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleCommand(e);
setCursorPosition(0);
} else {
setTimeout(() => {
setCursorPosition(inputRef.current?.selectionStart || 0);
}, 0);
}
};

return (
<div
ref={terminalRef}
className="flex flex-col h-full bg-gray-900 text-white font-mono text-sm overflow-auto p-4"
className="flex flex-col h-full bg-gray-900 text-white font-mono text-sm overflow-auto top-0 pl-4 pb-2"
onClick={() => inputRef.current?.focus()}
>
{output.map((line, index) => (
<div key={index} className={line.startsWith('dice >') ? 'text-gray-300' : 'text-white'}>
<div key={index} className="text-white p-1">
{line}
</div>
))}
<div className="flex items-center">
<span className="text-gray-200 mr-2">dice &gt; </span>
<div className="relative flex-grow p-1">
<p className="text-green-500 mr-2 p-1">dice ~$</p>
<div className="flex-grow">
<input
ref={inputRef}
type="text"
value={command}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
className="w-full bg-transparent border-none outline-none text-white"
className="w-full bg-transparent outline-none text-white"
/>
<div
className="absolute left-0 top-0 pointer-events-none whitespace-pre"
aria-hidden="true"
>
{command.slice(cursorPosition)}
</div>
</div>
</div>
</div>
Expand Down
79 changes: 26 additions & 53 deletions src/components/Playground/Playground.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,15 @@
"use client";

import Image from "next/image";
import React from "react";
import { useState, useEffect } from "react";
import React, { useState, useEffect } from "react";
import Cli from "@/components/CLI/CLI";
import SearchBox from "@/components/Search/SearchBox";
import { Dice1, Dice3, Dice5 } from "lucide-react";

export default function Playground() {
const [command, setCommand] = useState("");
const [output, setOutput] = useState<string[]>([]);
const [triggers, setTriggers] = useState<number>(998);
const [search, setSearch] = useState("");
const [timeLeft, setTimeLeft] = useState<number>(14 * 60 + 40);
const [store, setStore] = useState<{ [key: string]: string }>({});

const handleCommand = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
const newOutput = `dice > ${command}`;
let result = "";

const [cmd, ...args] = command.split(" ");

switch (cmd.toUpperCase()) {
case "GET":
result = store[args[0]] || "(nil)";
break;
case "SET":
if (args.length === 2) {
const [key, value] = args;
setStore((prevStore) => ({ ...prevStore, [key]: value }));
result = "OK";
} else {
result = "Invalid command. Usage: SET key value";
}
break;
default:
result = `Unknown command: ${cmd}`;
}

setOutput([...output, newOutput, result]);
setCommand("");
setTriggers((prev) => prev - 1);
}
};
const [timeLeft, setTimeLeft] = useState<number>(15 * 60);
const [commandsLeft, setCommandsLeft] = useState<number>(1000);

useEffect(() => {
const timer = setInterval(() => {
Expand All @@ -58,37 +25,44 @@ export default function Playground() {
return `${minutes}:${remainingSeconds < 10 ? "0" : ""}${remainingSeconds}`;
};

const decreaseCommandsLeft = () => {
setCommandsLeft((prev) => (prev > 0 ? prev - 1 : 0));
};

return (
<div className="flex flex-col min-h-screen bg-white text-gray-900">
<div className="container mx-auto flex flex-col flex-grow px-2 py-6">
<header className="mb-6 flex items-center justify-between w-10 h-10">
<div className="container mx-auto flex flex-col flex-grow min-h-screen bg-white text-gray-900 line-height-[1.5rem]">
<header className="navbar flex items-center justify-between pt-5 pb-4">
<div className="flex items-center">
<Image
src="https://dicedb.io/dicedb-logo-light.png"
width={100}
height={100}
width={110}
height={110}
alt="DiceDB logo"
className="object-contain"
/>
<h1 className="font-ariel font-light text-xl">PlayGround</h1>
<h2 className="font-light text-xl">PlayGround</h2>
</div>
</header>
<main className="flex flex-grow gap-2 overflow-hidden">
<div className="w-1/2 flex flex-col">
<div className="h-80 border border-gray-300 bg-gray-100 rounded-lg overflow-hidden shadow-md">
<Cli
output={output}
command={command}
setCommand={setCommand}
handleCommand={handleCommand}
/>
<div className="w-1/2 flex flex-col bg">
<div className="bg-gray-900 rounded-lg">
<div className="bg-gray-900 px-4 py-4 flex items-center rounded-lg">
<div className="flex space-x-2">
<Dice5 className="w-4 h-4 bg-red-500"></Dice5>
<Dice1 className="w-4 h-4 bg-yellow-500"></Dice1>
<Dice3 className="w-4 h-4 bg-green-500"></Dice3>
</div>
</div>
<div className="h-80 bg-gray-100 rounded-lg overflow-hidden shadow-md">
<Cli decreaseCommandsLeft={decreaseCommandsLeft}/>
</div>
</div>
<div className="flex flex-row justify-between text-gray-900 ">
<div className="mt-4 flex justify-between border border-gray-400 text-sm bg-transparent p-3 rounded-lg">
<span>Cleanup in : {formatTime(timeLeft)} mins</span>
</div>
<div className="mt-4 flex justify-between border border-gray-400 text-sm bg-transparent p-3 rounded-lg">
<span>Command left: {triggers}</span>
<span>Command left: {commandsLeft}</span>
</div>
</div>
</div>
Expand All @@ -99,6 +73,5 @@ export default function Playground() {
</div>
</main>
</div>
</div>
);
}
34 changes: 26 additions & 8 deletions src/components/Search/SearchBox.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
'use client';
"use client"

import React from "react"
import { Search } from "lucide-react"
import { DiceCmds, DiceCmdMeta } from "@/data/command"
import CommandPage from "./command"

import React from 'react';
import { Search } from 'lucide-react';
interface SearchBoxProps {
search: string;
search: string
setSearch: React.Dispatch<React.SetStateAction<string>>
}

export default function SearchBox({ search , setSearch}: SearchBoxProps) {
export default function SearchBox({ search, setSearch }: SearchBoxProps) {
const filteredCommands = Object.values(DiceCmds).filter((cmd: DiceCmdMeta) =>
cmd.title.toLowerCase().includes(search.toLowerCase())
)

return (
<div>
<div className="w-full max-w-3xl mx-auto">
<div className="mb-4">
<div className="flex items-center bg-gray-200 border border-gray-400 rounded px-2">
<div className="flex items-center bg-gray-200 border border-gray-200 rounded px-2">
<Search className="text-gray-900 mr-2" />
<input
type="text"
Expand All @@ -22,6 +29,17 @@ export default function SearchBox({ search , setSearch}: SearchBoxProps) {
/>
</div>
</div>
<div className="mt-4 space-y-4">
{search.length > 1 && filteredCommands.map((cmdMeta) => (
<CommandPage
key={cmdMeta.title}
title={cmdMeta.title}
syntax={cmdMeta.syntax}
body={cmdMeta.body}
url={cmdMeta.url}
/>
))}
</div>
</div>
);
)
}
61 changes: 61 additions & 0 deletions src/components/Search/command.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { useState } from "react";
import { Clipboard } from "lucide-react";
import { DiceCmdMeta } from "@/data/command";

export default function CommandPage({ title, syntax, body, url }: DiceCmdMeta) {
const [copied, setCopied] = useState(false);

const handleCopy = () => {
navigator.clipboard.writeText(syntax);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};

return (
<div className="p-6 bg-gray-100 text-white rounded-lg shadow-lg border border-gray-700 mb-4">
<h2 className="text-gray-700 text-2xl font-semibold mb-4">{title}</h2>

<div className="flex items-center justify-between mb-4 pt-4">
<h3 className="text-gray-700 text-2xl font-semibold">Syntax</h3>
<div className="flex flex-row">
{copied && (
<div className="text-green-500 text-sm">
Copied!
</div>
)}
<button
onClick={handleCopy}
className="text-gray-500 hover:text-gray-700 flex items-center ml-4"
title="Copy to clipboard"
>
<Clipboard className="w-5 h-5" />
</button>
</div>
</div>

<div
className="bg-gray-200 rounded-lg relative overflow-x-auto p-4"
>
<code className="font-mono text-sm whitespace-pre text-gray-700 inline-block min-w-full">
{syntax}
</code>
</div>

<h2 className="text-gray-700 text-2xl font-semibold pt-4 mb-4">
Description
</h2>
<div className="bg-gray-200 p-4 rounded-lg mb-4">
<p className="text-md text-gray-900">{body}</p>
</div>

<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-400 hover:text-blue-300 underline font-medium transition-colors duration-200"
>
View Documentation
</a>
</div>
);
}
23 changes: 23 additions & 0 deletions src/data/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// this file will contain the command definitions

export interface DiceCmdMeta {
title: string;
syntax: string;
body : string;
url?: string;
}

export const DiceCmds: { [key: string]: DiceCmdMeta } = {
SET: {
title: "SET",
syntax: "SET key value [NX | XX] [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL ]",
body: "The SET command in DiceDB is used to set the value of a key. If the key already holds a value, it is overwritten, regardless of its type. This is one of the most fundamental operations in DiceDB as it allows for both creating and updating key-value pairs.",
url: "https://dicedb.io/commands/set/"
},
GET: {
title: "GET",
syntax: "GET key",
body: "The GET command retrieves the value of a key. If the key does not exist, nil is returned.",
url: "https://dicedb.io/commands/get/"
}
};
Loading

0 comments on commit f4055a7

Please sign in to comment.