Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"build": "tsup",
"dev": "vite",
"lint": "tsc && eslint",
"fmt": "prettier --write ./src"
"fmt": "prettier --write ./src",
"prepare": "husky"
},
"lint-staged": {
"**/*.{js,ts,tsx}": [
Expand Down Expand Up @@ -49,7 +50,7 @@
"@tabler/icons-react": "^3.19.0",
"@tanstack/react-query": "^5.32.0",
"@types/bytes": "^3.1.4",
"@upstash/redis": "^1.35.8",
"@upstash/redis": "1.36.0-rc.4",
"bytes": "^3.1.2",
"cmdk": "^1.1.1",
"react-hook-form": "^7.53.0",
Expand All @@ -70,6 +71,8 @@
"dotenv": "^16.5.0",
"eslint": "9.10.0",
"eslint-plugin-unicorn": "55.0.0",
"husky": "^9.1.7",
"lint-staged": "^16.2.7",
"postcss": "^8.4.31",
"postcss-prefix-selector": "^2.1.0",
"prettier": "^3.0.3",
Expand Down
154 changes: 154 additions & 0 deletions playground.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/* eslint-disable no-console */
import { Redis, s } from "@upstash/redis";

const redis = Redis.fromEnv();

// await redis.search.index("user-index").drop();

const schema = s.object({
name: s.string(),

age: s.number(),
isStudent: s.boolean(),
isEmployed: s.boolean(),

// M or F
gender: s.string(),

contact: s.object({
email: s.string(),
phone: s.string(),
})
})

const index = redis.search.index("user-index", schema)

if (await index.describe()) {
console.log("index exists, dropping")
await index.drop();
console.log('index dropped')
}

console.log("creating index")
await redis.search.createIndex({
dataType: "string",
prefix: "user:",
name: "user-index",
schema: schema,
});

console.log("created index")

// await index.waitIndexing();

console.log("indexing done")


const res = await index.query({
filter: {
$and: {
name: {
$eq: "Yusuf"
}
},
"contact.email": "asd",
}
})

console.log("query result, should be empty:", res)

// Create the data

await redis.mset({
"user:yusuf": JSON.stringify({
name: "Yusuf",
age: 30,
isStudent: false,
isEmployed: true,
gender: "M",
contact: {
email: "[email protected]",
phone: "1234567890",
}
}),
"user:fatima": JSON.stringify({
name: "Fatima",
age: 35,
isStudent: true,
isEmployed: false,
gender: "F",
contact: {
email: "[email protected]",
phone: "0987654321",
}
}),
// josh
// arda
// ali
// sertug
"user:josh": JSON.stringify({
name: "Josh",
age: 22,
isStudent: true,
isEmployed: false,
gender: "M",
contact: {
email: "[email protected]",
phone: "0987654321",
}
}),
"user:arda": JSON.stringify({
name: "Arda",
age: 20,
isStudent: true,
isEmployed: false,
gender: "M",
contact: {
email: "[email protected]",
phone: "0987654321",
}
}),
"user:ali": JSON.stringify({
name: "Ali",
age: 15,
isStudent: true,
isEmployed: false,
gender: "M",
contact: {
email: "[email protected]",
phone: "0987654321",
}
}),
"user:sertug": JSON.stringify({
name: "Sertug",
age: 5,
isStudent: true,
isEmployed: false,
gender: "M",
contact: {
email: "[email protected]",
phone: "0987654321",
}
}),
})

console.log("created data")

// await index.waitIndexing();

console.log("indexing done")

const res2 = await index.query({
filter: {
$and: {
name: {
$eq: "Yusuf"
}
}
}
})

console.log("query result, should not be empty:", res2)


console.log(await redis.type("user-index"))
15 changes: 10 additions & 5 deletions src/components/databrowser/components/add-key-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState } from "react"
import { useTab } from "@/tab-provider"
import { DATA_TYPES, type DataType } from "@/types"
import { DialogDescription } from "@radix-ui/react-dialog"
import { IconPlus } from "@tabler/icons-react"
import { Controller, useForm } from "react-hook-form"

import { Button } from "@/components/ui/button"
Expand All @@ -22,10 +23,9 @@ import {
SelectValue,
} from "@/components/ui/select"
import { Spinner } from "@/components/ui/spinner"
import { SimpleTooltip } from "@/components/ui/tooltip"
import { TypeTag } from "@/components/databrowser/components/type-tag"
import { useAddKey } from "@/components/databrowser/hooks/use-add-key"
import { SimpleTooltip } from "@/components/ui/tooltip"
import { IconPlus } from "@tabler/icons-react"

export function AddKeyModal() {
const { setSelectedKey } = useTab()
Expand Down Expand Up @@ -65,8 +65,13 @@ export function AddKeyModal() {
>
<SimpleTooltip content="Add key">
<DialogTrigger asChild>
<Button variant="primary" size="icon-sm" data-testid="add-key-button">
<IconPlus className="size-4" />
<Button
variant="primary"
data-testid="add-key-button"
className="flex h-8 items-center gap-1 rounded-lg pl-2 pr-3 text-sm font-medium"
>
<IconPlus className="size-5" />
Key
</Button>
</DialogTrigger>
</SimpleTooltip>
Expand All @@ -91,7 +96,7 @@ export function AddKeyModal() {
</SelectTrigger>
<SelectContent>
<SelectGroup>
{DATA_TYPES.map((type) => (
{DATA_TYPES.filter((t) => t !== "search").map((type) => (
<SelectItem key={type} value={type}>
<TypeTag variant={type} type="badge" />
</SelectItem>
Expand Down
31 changes: 26 additions & 5 deletions src/components/databrowser/components/databrowser-instance.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,46 @@
import { useTab } from "@/tab-provider"
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"

import { cn } from "@/lib/utils"
import { Toaster } from "@/components/ui/toaster"

import { KeysProvider } from "../hooks/use-keys"
import { DataDisplay } from "./display"
import { Header } from "./header"
import { HeaderError } from "./header-error"
import { QueryBuilder } from "./query-builder"
import { Sidebar } from "./sidebar"
import { KeysProvider } from "../hooks/use-keys"

export const PREFIX = "const query: Query = "

export const DatabrowserInstance = ({ hidden }: { hidden?: boolean }) => {
const { isValuesSearchSelected } = useTab()
return (
<KeysProvider>
<div className={cn("min-h-0 grow rounded-md bg-zinc-100", hidden && "hidden")}>
<div
className={cn(
"flex min-h-0 grow flex-col rounded-md bg-white px-5 pb-5",
hidden && "hidden"
)}
>
<div className="space-y-3 py-5">
<Header />

{isValuesSearchSelected && <QueryBuilder />}
<HeaderError />
</div>
<PanelGroup
autoSaveId="persistence"
direction="horizontal"
className="h-full w-full gap-0.5 text-sm antialiased"
className="h-full w-full text-sm antialiased"
>
<Panel defaultSize={30} minSize={30}>
<Sidebar />
</Panel>
<PanelResizeHandle className="group flex h-full w-3 justify-center">
<div className="h-full border-r border-dashed border-zinc-200 transition-colors group-hover:border-zinc-500" />
<PanelResizeHandle className="group mx-[2px] flex h-full flex-col items-center justify-center gap-1 rounded-md px-[8px] transition-colors hover:bg-zinc-300/10">
<div className="h-[3px] w-[3px] rounded-full bg-zinc-300" />
<div className="h-[3px] w-[3px] rounded-full bg-zinc-300" />
<div className="h-[3px] w-[3px] rounded-full bg-zinc-300" />
</PanelResizeHandle>
<Panel minSize={40}>
<DataDisplay />
Expand Down
21 changes: 10 additions & 11 deletions src/components/databrowser/components/databrowser-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import { restrictToHorizontalAxis } from "@dnd-kit/modifiers"
import { horizontalListSortingStrategy, SortableContext, useSortable } from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import { IconChevronDown, IconMaximize, IconPlus } from "@tabler/icons-react"
import { IconChevronDown, IconPlus, IconWindowMaximize } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
Expand Down Expand Up @@ -210,10 +210,8 @@ export const DatabrowserTabs = ({ onFullScreenClick }: { onFullScreenClick?: ()
}

return (
<div className="relative mb-2 shrink-0">
<div className="absolute bottom-0 left-0 right-0 -z-10 h-[1px] w-full bg-zinc-200" />

<div className="flex translate-y-[1px] items-center gap-1">
<div className="relative shrink-0 overflow-hidden rounded-t-lg bg-zinc-300">
<div className="flex items-center gap-1">
{/* Scrollable tabs area */}
<div className="relative min-w-0 flex-1">
<div
Expand All @@ -227,10 +225,11 @@ export const DatabrowserTabs = ({ onFullScreenClick }: { onFullScreenClick?: ()
}`}
/>

{/* TAB */}
<div
ref={scrollRef}
onScroll={recomputeShadows}
className="scrollbar-hide flex min-w-0 flex-1 items-center gap-1 overflow-x-auto pb-[1px] [&::-webkit-scrollbar]:hidden"
className="scrollbar-hide flex min-w-0 flex-1 items-center gap-1 overflow-x-auto [&::-webkit-scrollbar]:hidden"
>
<DndContext
sensors={sensors}
Expand Down Expand Up @@ -268,9 +267,9 @@ export const DatabrowserTabs = ({ onFullScreenClick }: { onFullScreenClick?: ()
variant="secondary"
size="icon-sm"
onClick={onFullScreenClick}
className="flex-shrink-0 bg-blue-100 hover:bg-blue-600 hover:text-white"
className="flex-shrink-0 bg-white text-zinc-500 dark:bg-zinc-100"
>
<IconMaximize size={16} />
<IconWindowMaximize size={16} />
</Button>
)}
</div>
Expand Down Expand Up @@ -301,9 +300,9 @@ function AddTabButton() {
variant="secondary"
size="icon-sm"
onClick={handleAddTab}
className="flex-shrink-0 dark:bg-zinc-200"
className="flex-shrink-0 bg-zinc-200 "
>
<IconPlus className="text-zinc-500 dark:text-zinc-600" size={16} />
<IconPlus className="text-zinc-600" size={16} />
</Button>
)
}
Expand Down Expand Up @@ -345,7 +344,7 @@ function TabsListButton({
<Button
variant="secondary"
size="sm"
className="h-7 gap-1 px-2"
className="gap-1 bg-white px-2"
aria-label="Search in tabs"
>
<span className="text-xs text-zinc-600">{tabs.length}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ export function DeleteAlertDialog({
{isPlural ? `Delete ${count} ${itemsLabel}` : `Delete ${itemLabel}`}
</AlertDialogTitle>
<AlertDialogDescription className="mt-5">
Are you sure you want to delete {isPlural ? `these ${count} ${deletionType}s` : `this ${deletionType}`}?<br />
Are you sure you want to delete{" "}
{isPlural ? `these ${count} ${deletionType}s` : `this ${deletionType}`}?<br />
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
Expand Down
14 changes: 8 additions & 6 deletions src/components/databrowser/components/display/display-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,19 @@ export const DisplayHeader = ({
}

return (
<div className="rounded-lg bg-zinc-100">
<div className="flex min-h-10 items-center justify-between gap-4">
<h2 className="grow truncate text-base">
<div className="rounded-lg">
{/* Key title and actions */}
<div className="flex h-[26px] items-center justify-between gap-4">
<h2 className="grow truncate text-sm">
{dataKey.trim() === "" ? (
<span className="ml-1 text-zinc-500">(Empty Key)</span>
) : (
<span className="font-semibold">{dataKey}</span>
<span className="font-medium text-zinc-950">{dataKey}</span>
)}
</h2>

<div className="flex items-center gap-1">
{type !== "string" && type !== "json" && (
{type !== "string" && type !== "json" && type !== "search" && (
<SimpleTooltip content="Add item">
<Button onClick={handleAddItem} size="icon-sm" aria-label="Add item">
<IconPlus className="size-4 text-zinc-500 dark:text-zinc-600" />
Expand All @@ -48,7 +49,8 @@ export const DisplayHeader = ({
</div>
</div>

<div className="flex h-10 flex-wrap items-center gap-1.5">
{/* Key info badges */}
<div className="flex h-10 items-center gap-1.5 overflow-scroll">
<TypeTag variant={type} type="badge" />
<SizeBadge dataKey={dataKey} />
<LengthBadge dataKey={dataKey} type={type} content={content} />
Expand Down
Loading