Skip to content

Commit

Permalink
Merge pull request #10 from ErickJ3/refact/improvment-key-view
Browse files Browse the repository at this point in the history
Refact/improvment key view
  • Loading branch information
ErickJ3 authored Nov 25, 2024
2 parents 5485095 + e14b5b3 commit 4629969
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 157 deletions.
Binary file modified docs/screenshot-03.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@radix-ui/react-tooltip": "^1.1.4",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-shell": "^2",
"@uiw/react-json-view": "2.0.0-alpha.30",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.460.0",
Expand Down
29 changes: 29 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions src/components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)

export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}

export { Badge, badgeVariants }
28 changes: 28 additions & 0 deletions src/features/keys/constants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export const customColorForJson = {
"--w-rjv-color": "#9cdcfe",
"--w-rjv-key-number": "#268bd2",
"--w-rjv-key-string": "#9cdcfe",
"--w-rjv-line-color": "#36334280",
"--w-rjv-arrow-color": "#838383",
"--w-rjv-edit-color": "#9cdcfe",
"--w-rjv-info-color": "#9c9c9c7a",
"--w-rjv-update-color": "#9cdcfe",
"--w-rjv-copied-color": "#9cdcfe",
"--w-rjv-copied-success-color": "#28a745",
"--w-rjv-curlybraces-color": "#d4d4d4",
"--w-rjv-colon-color": "#d4d4d4",
"--w-rjv-brackets-color": "#d4d4d4",
"--w-rjv-ellipsis-color": "#cb4b16",
"--w-rjv-quotes-color": "#9cdcfe",
"--w-rjv-quotes-string-color": "#ce9178",
"--w-rjv-type-string-color": "#ce9178",
"--w-rjv-type-int-color": "#b5cea8",
"--w-rjv-type-float-color": "#b5cea8",
"--w-rjv-type-bigint-color": "#b5cea8",
"--w-rjv-type-boolean-color": "#569cd6",
"--w-rjv-type-date-color": "#b5cea8",
"--w-rjv-type-url-color": "#3b89cf",
"--w-rjv-type-null-color": "#569cd6",
"--w-rjv-type-nan-color": "#859900",
"--w-rjv-type-undefined-color": "#569cd6",
};
93 changes: 93 additions & 0 deletions src/features/keys/key-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useState, useMemo, useEffect } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { Save } from "lucide-react";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import JsonView from "@uiw/react-json-view";
import { customColorForJson } from "./constants";

interface KeyContentProps {
value: string;
editedValue: string;
onValueChange: (value: string) => void;
onSave: () => void;
}

export const KeyContent = ({
value,
editedValue,
onValueChange,
onSave,
}: KeyContentProps) => {
const [viewMode, setViewMode] = useState("raw");

const parsedJson = useMemo(() => {
try {
return JSON.parse(editedValue || value);
} catch {
return null;
}
}, [value, editedValue]);

const isValidJson = parsedJson !== null;

useEffect(() => {
if (!isValidJson && viewMode === "json") {
setViewMode("raw");
}
}, [isValidJson, viewMode]);

return (
<Card>
<CardContent className="pt-6">
<div className="space-y-4">
<div className="flex justify-between items-center">
<Label>Value</Label>
<Select value={viewMode} onValueChange={setViewMode}>
<SelectTrigger className="w-32">
<SelectValue placeholder="View mode" />
</SelectTrigger>
<SelectContent>
<SelectItem value="raw">Raw</SelectItem>
<SelectItem value="json" disabled={!isValidJson}>
JSON
</SelectItem>
</SelectContent>
</Select>
</div>

{viewMode === "raw" || !isValidJson ? (
<Textarea
value={editedValue || value}
onChange={(e) => onValueChange(e.target.value)}
className="min-h-[200px] font-mono"
/>
) : (
<div className="border rounded-md p-4 min-h-[200px] bg-background">
<JsonView
value={parsedJson}
displayDataTypes={false}
style={customColorForJson}
/>
</div>
)}

<div className="flex justify-end">
<Button onClick={onSave} disabled={!editedValue}>
<Save className="h-4 w-4 mr-2" />
Save Changes
</Button>
</div>
</div>
</CardContent>
</Card>
);
};
145 changes: 145 additions & 0 deletions src/features/keys/key-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { useState } from "react";
import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Clock, Copy, Check, Trash } from "lucide-react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { toast } from "@/hooks/use-toast";
import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";

interface KeyHeaderProps {
keyName: string;
keyType: string;
ttl: number;
onDelete: () => void;
onTtlUpdate: (ttl: number) => void;
}

export const KeyHeader = ({
keyName,
keyType,
ttl,
onDelete,
onTtlUpdate,
}: KeyHeaderProps) => {
const [ttlDialogOpen, setTtlDialogOpen] = useState(false);
const [newTtl, setNewTtl] = useState("");
const [copied, setCopied] = useState(false);

const handleCopy = async () => {
await navigator.clipboard.writeText(keyName);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
toast({
title: "Copied!",
description: "Key name copied to clipboard",
});
};

const handleTtlSave = () => {
const ttlValue = parseInt(newTtl);
onTtlUpdate(ttlValue);
setTtlDialogOpen(false);
setNewTtl("");
};

return (
<Card>
<CardHeader>
<div className="flex justify-between items-start">
<div>
<div className="flex items-center gap-2">
<CardTitle className="text-2xl">{keyName}</CardTitle>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={handleCopy}
>
{copied ? (
<Check className="h-4 w-4 text-green-500" />
) : (
<Copy className="h-4 w-4" />
)}
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Copy key name</p>
</TooltipContent>
</Tooltip>
</div>
<CardDescription>
Type: <Badge className="px-2 mx-1" variant="outline">{keyType}</Badge> | TTL:{" "}
{ttl === -1 ? "No expiration" : `${ttl}s`}
</CardDescription>
</div>

<div className="flex gap-2">
<Dialog open={ttlDialogOpen} onOpenChange={setTtlDialogOpen}>
<DialogTrigger asChild>
<Button variant="outline" size="sm">
<Clock className="h-4 w-4 mr-2" />
Set TTL
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Set Time To Live</DialogTitle>
<DialogDescription>
Set the expiration time in seconds. Use -1 for no
expiration.
</DialogDescription>
</DialogHeader>
<div className="py-4">
<Label htmlFor="ttl">TTL (seconds)</Label>
<Input
id="ttl"
value={newTtl}
onChange={(e) => setNewTtl(e.target.value)}
type="number"
placeholder="Enter TTL in seconds"
/>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setTtlDialogOpen(false)}
>
Cancel
</Button>
<Button onClick={handleTtlSave}>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>

<Button variant="outline" size="sm" onClick={onDelete}>
<Trash className="h-4 w-4 mr-2" />
Delete
</Button>
</div>
</div>
</CardHeader>
</Card>
);
};
Loading

0 comments on commit 4629969

Please sign in to comment.