Skip to content

Commit 867e8d6

Browse files
authored
Merge pull request #95 from ashphy/ashphy/issue25
Import json file and save the output as a file
2 parents d323e79 + c9e7002 commit 867e8d6

File tree

9 files changed

+315
-26
lines changed

9 files changed

+315
-26
lines changed

package-lock.json

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"preview": "vite preview"
1414
},
1515
"dependencies": {
16+
"@formkit/tempo": "^0.1.2",
1617
"@icons-pack/react-simple-icons": "^10.2.0",
1718
"@monaco-editor/react": "^4.6.0",
1819
"@radix-ui/react-alert-dialog": "^1.1.6",
@@ -23,6 +24,7 @@
2324
"@radix-ui/react-select": "^2.1.4",
2425
"@radix-ui/react-slot": "^1.1.2",
2526
"@radix-ui/react-switch": "^1.1.3",
27+
"@radix-ui/react-tooltip": "^1.1.8",
2628
"class-variance-authority": "^0.7.1",
2729
"clsx": "^2.1.1",
2830
"cmdk": "^1.0.0",
@@ -41,6 +43,7 @@
4143
},
4244
"devDependencies": {
4345
"@eslint/js": "^9.17.0",
46+
"@types/file-saver": "^2.0.7",
4447
"@types/node": "^22.13.14",
4548
"@types/react": "^18.3.18",
4649
"@types/react-dom": "^18.3.5",
@@ -49,6 +52,7 @@
4952
"eslint": "^9.23.0",
5053
"eslint-plugin-react-hooks": "^5.2.0",
5154
"eslint-plugin-react-refresh": "^0.4.19",
55+
"file-saver": "^2.0.5",
5256
"globals": "^15.14.0",
5357
"peggy": "^4.2.0",
5458
"postcss": "^8.4.49",
@@ -58,4 +62,4 @@
5862
"vite": "^6.2.5",
5963
"vitest": "^3.0.9"
6064
}
61-
}
65+
}

src/components/download-button.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Button, ButtonProps } from "@/components/ui/button";
2+
import { saveAs } from "file-saver";
3+
import { Download } from "lucide-react";
4+
import {
5+
Tooltip,
6+
TooltipContent,
7+
TooltipProvider,
8+
TooltipTrigger,
9+
} from "@/components/ui/tooltip";
10+
import { cn } from "@/lib/utils";
11+
import { useJSONPath } from "@/hooks/use-jsonpath";
12+
import { format } from "@formkit/tempo";
13+
14+
export const DownloadButton = ({ className }: ButtonProps) => {
15+
const { result } = useJSONPath();
16+
17+
const handleDownload = () => {
18+
const text = result.isValid ? JSON.stringify(result.values, null, 2) : "[]";
19+
const blob = new Blob([text], {
20+
type: "application/json",
21+
});
22+
saveAs(
23+
blob,
24+
`evaluation_results_${format(new Date(), "YYYYMMDD_HHmmss")}.json`
25+
);
26+
};
27+
28+
return (
29+
<TooltipProvider>
30+
<Tooltip>
31+
<TooltipTrigger asChild>
32+
<Button
33+
variant="outline"
34+
size="icon"
35+
className={cn("rounded-full", className)}
36+
onClick={handleDownload}
37+
>
38+
<Download />
39+
</Button>
40+
</TooltipTrigger>
41+
<TooltipContent>
42+
<p>Download file</p>
43+
</TooltipContent>
44+
</Tooltip>
45+
</TooltipProvider>
46+
);
47+
};

src/components/drop-zone.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Upload } from "lucide-react";
2+
import { ReactNode, useState } from "react";
3+
4+
interface DropZoneProps {
5+
onDrop?: (file: File) => void;
6+
children: ReactNode;
7+
}
8+
9+
export const DropZone = ({ onDrop, children }: DropZoneProps) => {
10+
const [isDragging, setIsDragging] = useState(false);
11+
12+
const handleOnDrop = (e: React.DragEvent<HTMLDivElement>) => {
13+
e.preventDefault();
14+
setIsDragging(false);
15+
const file = e.dataTransfer.files[0];
16+
if (file) {
17+
onDrop?.(file);
18+
}
19+
};
20+
21+
return (
22+
<div
23+
onDrop={handleOnDrop}
24+
onDragOver={(e) => {
25+
setIsDragging(true);
26+
e.preventDefault();
27+
}}
28+
onDragEnter={() => setIsDragging(true)}
29+
onDragLeave={() => setIsDragging(false)}
30+
data-drag={isDragging ? "true" : "false"}
31+
className="relative"
32+
>
33+
{children}
34+
<div
35+
data-drag={isDragging ? "true" : "false"}
36+
className="
37+
invisible data-[drag=true]:visible opacity-0 data-[drag=true]:opacity-80
38+
absolute flex w-full h-full top-0 left-0 p-2 bg-white
39+
transition-opacity duration-200
40+
"
41+
>
42+
<div
43+
className="
44+
flex flex-col items-center justify-center gap-4
45+
w-full h-full
46+
border-2 border-dashed border-joe-green-600 rounded-lg text-lg
47+
"
48+
>
49+
<div>Drop JSON file here</div>
50+
<div>
51+
<Upload />
52+
</div>
53+
</div>
54+
</div>
55+
</div>
56+
);
57+
};

src/components/editor/json-editor.tsx

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
findSmallestNode,
1010
generateNormalizedPathNode,
1111
} from "@/lib/normalized-path";
12+
import { DropZone } from "../drop-zone";
1213

1314
export const JSONEditor = () => {
1415
const { document, setDocument, jsonDocument } = useJSONPath();
@@ -63,25 +64,36 @@ export const JSONEditor = () => {
6364
setDocument(value || "");
6465
};
6566

67+
const handleOnDrop = (file: File) => {
68+
const reader = new FileReader();
69+
reader.onload = (event) => {
70+
const content = event.target?.result as string;
71+
setDocument(content);
72+
};
73+
reader.readAsText(file);
74+
};
75+
6676
return (
67-
<Editor
68-
className={cn("border-2", jsonDocument.error && "border-red-400")}
69-
height="600px"
70-
path="json"
71-
defaultLanguage="json"
72-
value={document}
73-
loading="Loading..."
74-
onMount={handleEditorDidMount}
75-
onChange={handleOnChange}
76-
options={{
77-
wordWrap: "on",
78-
minimap: {
79-
enabled: false,
80-
},
81-
scrollBeyondLastLine: false,
82-
formatOnPaste: true,
83-
formatOnType: true,
84-
}}
85-
/>
77+
<DropZone onDrop={handleOnDrop}>
78+
<Editor
79+
className={cn("border-2", jsonDocument.error && "border-red-400")}
80+
height="600px"
81+
path="json"
82+
defaultLanguage="json"
83+
value={document}
84+
loading="Loading..."
85+
onMount={handleEditorDidMount}
86+
onChange={handleOnChange}
87+
options={{
88+
wordWrap: "on",
89+
minimap: {
90+
enabled: false,
91+
},
92+
scrollBeyondLastLine: false,
93+
formatOnPaste: true,
94+
formatOnType: true,
95+
}}
96+
/>
97+
</DropZone>
8698
);
8799
};

src/components/import-file.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { useRef } from "react";
2+
import { Button } from "./ui/button";
3+
import { useJSONPath } from "@/hooks/use-jsonpath";
4+
5+
export const ImportFile = () => {
6+
const { setDocument } = useJSONPath();
7+
const inputRef = useRef<HTMLInputElement>(null);
8+
9+
const handleOnClick = () => {
10+
inputRef.current?.click();
11+
};
12+
13+
const handleOnInput = (event: React.ChangeEvent<HTMLInputElement>) => {
14+
const file = event.target.files?.[0];
15+
if (file) {
16+
const reader = new FileReader();
17+
reader.onload = (event) => {
18+
const content = event.target?.result as string;
19+
setDocument(content);
20+
};
21+
reader.readAsText(file);
22+
}
23+
};
24+
25+
return (
26+
<div>
27+
<Button variant="outline" onClick={handleOnClick}>
28+
Import File
29+
</Button>
30+
<input
31+
ref={inputRef}
32+
className="hidden"
33+
type="file"
34+
onInput={handleOnInput}
35+
accept="
36+
.json,
37+
.jsonl,
38+
.ndjson,
39+
.geojson,
40+
.topojson,
41+
.jwt,
42+
.webmanifest,
43+
.har,
44+
.mcstructure,
45+
.eslintrc,
46+
.prettierrc,
47+
.babelrc,
48+
.code-snippets,
49+
.ipynb,
50+
.vg,
51+
.vl,
52+
.template,
53+
application/json,
54+
application/geo+json,
55+
application/x-ndjson,
56+
application/jsonlines,
57+
application/schema+json,
58+
application/jwt,
59+
application/feed+json,
60+
application/vnd.oai.openapi+json,
61+
application/vnd.swagger+json,
62+
application/manifest+json,
63+
application/x-ipynb+json,
64+
"
65+
></input>
66+
</div>
67+
);
68+
};

0 commit comments

Comments
 (0)