Skip to content

Commit 34ecdd0

Browse files
committed
chore: bump version to 0.1.0
1 parent baa6619 commit 34ecdd0

File tree

5 files changed

+154
-32
lines changed

5 files changed

+154
-32
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "solo",
33
"private": true,
4-
"version": "0.0.18",
4+
"version": "0.1.0",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "solo"
3-
version = "0.0.18"
3+
version = "0.1.0"
44
description = "Solo Client HTTP"
55
authors = ["Igor Vieira <[email protected]>"]
66
edition = "2021"

src-tauri/tauri.conf.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2.0.0",
33
"productName": "solo",
4-
"version": "0.0.18",
4+
"version": "0.1.0",
55
"identifier": "com.solo.app",
66
"build": {
77
"beforeDevCommand": "bun run dev",
@@ -26,7 +26,7 @@
2626
"windows": [
2727
{
2828
"title": "solo",
29-
"width": 1278,
29+
"width": 1600,
3030
"height": 829,
3131
"minWidth": 1182,
3232
"minHeight": 757,

src/components/ResponseView.tsx

Lines changed: 149 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { useState } from "react";
1+
import { useState, useRef, useEffect } from "react";
22
import { useTheme } from "../context/ThemeContext";
33
import { useRequest } from "../context/RequestContext";
44
import clsx from "clsx";
55
import { ShortcutsDisplay } from "./ShortcutsDisplay";
66
import { useCurlGenerator } from "../hooks/useCurlGenerator";
7-
import { CopyIcon } from "./CopyIcon";
87
import { JsonViewer } from "./JsonViewer";
9-
import { Maximize2, Minimize2 } from "lucide-react";
8+
import { Maximize2, Minimize2, Copy, ChevronDown, Check } from "lucide-react";
109

1110
type TabType = "response" | "headers" | "timeline";
1211

@@ -45,21 +44,69 @@ const TabItem = ({ label, value, active, onClick }: TabItemProps) => {
4544
);
4645
};
4746

47+
type ViewMode = "pretty" | "raw";
48+
4849
export const ResponseView = ({ isRequestCollapsed, onToggleCollapse }: ResponseViewProps) => {
4950
const { theme } = useTheme();
5051
const {
5152
response,
5253
error,
5354
loading,
54-
isCopied,
55-
handleCopyResponse,
5655
url,
5756
responseTime,
5857
statusCode,
5958
} = useRequest();
6059

6160
const { generateCurl } = useCurlGenerator();
6261
const [activeTab, setActiveTab] = useState<TabType>("response");
62+
const [viewMode, setViewMode] = useState<ViewMode>("pretty");
63+
const [isViewDropdownOpen, setIsViewDropdownOpen] = useState(false);
64+
const [isCopied, setIsCopied] = useState(false);
65+
const viewDropdownRef = useRef<HTMLDivElement>(null);
66+
67+
// Close dropdown when clicking outside
68+
useEffect(() => {
69+
const handleClickOutside = (event: MouseEvent) => {
70+
if (viewDropdownRef.current && !viewDropdownRef.current.contains(event.target as Node)) {
71+
setIsViewDropdownOpen(false);
72+
}
73+
};
74+
75+
document.addEventListener("mousedown", handleClickOutside);
76+
return () => document.removeEventListener("mousedown", handleClickOutside);
77+
}, []);
78+
79+
// Reset copied state after 2 seconds
80+
useEffect(() => {
81+
if (isCopied) {
82+
const timeout = setTimeout(() => setIsCopied(false), 2000);
83+
return () => clearTimeout(timeout);
84+
}
85+
}, [isCopied]);
86+
87+
const handleCopy = async () => {
88+
let textToCopy = "";
89+
90+
if (activeTab === "response" && response) {
91+
textToCopy = viewMode === "pretty"
92+
? JSON.stringify(response, null, 2)
93+
: JSON.stringify(response);
94+
} else if (url) {
95+
textToCopy = generateCurl();
96+
}
97+
98+
try {
99+
await navigator.clipboard.writeText(textToCopy);
100+
setIsCopied(true);
101+
} catch (err) {
102+
console.error("Failed to copy:", err);
103+
}
104+
};
105+
106+
const handleViewModeChange = (mode: ViewMode) => {
107+
setViewMode(mode);
108+
setIsViewDropdownOpen(false);
109+
};
63110

64111
// Helper function to get status code color
65112
const getStatusCodeColor = (code: number | null) => {
@@ -121,6 +168,19 @@ export const ResponseView = ({ isRequestCollapsed, onToggleCollapse }: ResponseV
121168
return <ShortcutsDisplay />;
122169
}
123170

171+
if (viewMode === "raw") {
172+
return (
173+
<div
174+
className={clsx(
175+
"p-4 whitespace-pre-wrap break-all font-mono text-xs",
176+
theme === "dark" ? "text-gray-300" : "text-gray-800"
177+
)}
178+
>
179+
{JSON.stringify(response)}
180+
</div>
181+
);
182+
}
183+
124184
return <JsonViewer data={response} />;
125185

126186
case "headers":
@@ -267,11 +327,92 @@ export const ResponseView = ({ isRequestCollapsed, onToggleCollapse }: ResponseV
267327
<span className={getStatusCodeColor(statusCode)}>{statusCode}</span>
268328
</div>
269329
)}
270-
{url && (
271-
<div className="flex items-center">
272-
<CopyIcon content={generateCurl()} size={16} />
330+
{/* View Mode Dropdown (Pretty/Raw) - Only for response tab */}
331+
{response && activeTab === "response" && (
332+
<div className="relative" ref={viewDropdownRef}>
333+
<button
334+
onClick={() => setIsViewDropdownOpen(!isViewDropdownOpen)}
335+
className={clsx(
336+
"flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium cursor-pointer transition-all duration-200 border",
337+
theme === "dark"
338+
? "bg-gray-800 text-gray-300 border-gray-700 hover:bg-gray-700 hover:border-purple-600"
339+
: "bg-gray-100 text-gray-700 border-gray-300 hover:bg-gray-200 hover:border-purple-500"
340+
)}
341+
title="View mode"
342+
>
343+
<span className="capitalize">{viewMode}</span>
344+
<ChevronDown className="w-3 h-3" />
345+
</button>
346+
347+
{isViewDropdownOpen && (
348+
<div
349+
className={clsx(
350+
"absolute right-0 mt-1 w-32 rounded-lg shadow-lg border z-50",
351+
theme === "dark"
352+
? "bg-gray-800 border-gray-700"
353+
: "bg-white border-gray-200"
354+
)}
355+
>
356+
<div className="py-1">
357+
<button
358+
onClick={() => handleViewModeChange("pretty")}
359+
className={clsx(
360+
"w-full px-4 py-2 text-left text-sm flex items-center justify-between transition-colors",
361+
theme === "dark"
362+
? "text-gray-300 hover:bg-gray-700"
363+
: "text-gray-700 hover:bg-gray-100",
364+
viewMode === "pretty" && "font-semibold"
365+
)}
366+
>
367+
<span>Pretty</span>
368+
{viewMode === "pretty" && <Check className="w-3.5 h-3.5 text-green-500" />}
369+
</button>
370+
<button
371+
onClick={() => handleViewModeChange("raw")}
372+
className={clsx(
373+
"w-full px-4 py-2 text-left text-sm flex items-center justify-between transition-colors",
374+
theme === "dark"
375+
? "text-gray-300 hover:bg-gray-700"
376+
: "text-gray-700 hover:bg-gray-100",
377+
viewMode === "raw" && "font-semibold"
378+
)}
379+
>
380+
<span>Raw</span>
381+
{viewMode === "raw" && <Check className="w-3.5 h-3.5 text-green-500" />}
382+
</button>
383+
</div>
384+
</div>
385+
)}
273386
</div>
274387
)}
388+
389+
{/* Copy Button */}
390+
{url && (
391+
<button
392+
onClick={handleCopy}
393+
disabled={isCopied}
394+
className={clsx(
395+
"flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium cursor-pointer transition-all duration-200 border",
396+
theme === "dark"
397+
? "bg-gray-800 text-gray-300 border-gray-700 hover:bg-gray-700 hover:border-purple-600"
398+
: "bg-gray-100 text-gray-700 border-gray-300 hover:bg-gray-200 hover:border-purple-500",
399+
isCopied && "border-green-500"
400+
)}
401+
title={activeTab === "response" && response ? `Copy ${viewMode} response` : "Copy cURL"}
402+
>
403+
{isCopied ? (
404+
<>
405+
<Check className="w-3.5 h-3.5 text-green-500" />
406+
<span className="text-green-500">Copied!</span>
407+
</>
408+
) : (
409+
<>
410+
<Copy className="w-3.5 h-3.5" />
411+
<span>Copy</span>
412+
</>
413+
)}
414+
</button>
415+
)}
275416
<div
276417
onClick={onToggleCollapse}
277418
className={clsx(
@@ -321,25 +462,6 @@ export const ResponseView = ({ isRequestCollapsed, onToggleCollapse }: ResponseV
321462
>
322463
{renderContent()}
323464
</div>
324-
325-
{/* Copy Button - Fixed at Bottom */}
326-
{response && activeTab === "response" && (
327-
<div className="flex-shrink-0 pt-2">
328-
<button
329-
onClick={handleCopyResponse}
330-
disabled={isCopied}
331-
className={clsx(
332-
"w-full py-2.5 px-4 rounded-lg cursor-pointer font-medium text-sm transition-all duration-200",
333-
theme === "dark"
334-
? "bg-purple-700 text-white hover:bg-purple-600 active:bg-purple-800"
335-
: "bg-purple-600 text-white hover:bg-purple-700 active:bg-purple-800",
336-
isCopied && "opacity-70 cursor-not-allowed"
337-
)}
338-
>
339-
{isCopied ? "✓ Copied!" : "Copy Response"}
340-
</button>
341-
</div>
342-
)}
343465
</div>
344466
);
345467
};

0 commit comments

Comments
 (0)