Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "gquick",
"private": true,
"version": "0.2.0",
"version": "0.2.1",
"type": "module",
"engines": {
"node": ">=24.15.0"
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

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

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gquick"
version = "0.2.0"
version = "0.2.1"
description = "A simple, fast and feature rich launcher"
authors = ["Ricky Dane Perlick"]
edition = "2021"
Expand Down
25 changes: 25 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3540,6 +3540,30 @@ fn hide_main_window(window: tauri::Window) -> Result<(), String> {
hide_window(&window, true)
}

#[tauri::command]
fn center_main_window(window: tauri::Window) -> Result<(), String> {
let app = window.app_handle();
if let Some(monitor) = monitor_for_mouse(app).or_else(|| app.primary_monitor().ok().flatten()) {
let scale_factor = monitor.scale_factor();
let (logical_monitor_pos, logical_monitor_size) =
monitor_to_logical_geometry(&monitor);

let window_size = window.inner_size().unwrap_or(tauri::PhysicalSize {
width: 760,
height: 800,
});
let logical_window_size = window_size.to_logical::<f64>(scale_factor);

let x = logical_monitor_pos.x
+ (logical_monitor_size.width - logical_window_size.width) / 2.0;
let y = logical_monitor_pos.y
+ (logical_monitor_size.height - logical_window_size.height) / 2.5;

let _ = window.set_position(tauri::Position::Logical(tauri::LogicalPosition { x, y }));
}
Ok(())
}

fn docker_output(args: &[String]) -> Result<DockerCommandResult, String> {
let status = docker_status_blocking();
if !status.cli_installed {
Expand Down Expand Up @@ -6736,6 +6760,7 @@ pub fn run() {
cancel_terminal_command,
cancel_all_terminal_commands,
hide_main_window,
center_main_window,
execute_python,
execute_sql,
brew_status,
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "GQuick",
"version": "0.2.0",
"version": "0.2.1",
"identifier": "com.gquick.dev",
"build": {
"beforeDevCommand": "npm run dev",
Expand Down
28 changes: 23 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ function App() {
const [autoCheckUpdateInfo, setAutoCheckUpdateInfo] = useState<{ version: string; body: string | null } | null>(null);
const [autoCheckUpdateObj, setAutoCheckUpdateObj] = useState<any>(null);
const appliedWindowModeRef = useRef<"launcher" | "expanded" | null>(null);
const shouldCenterOnNextResizeRef = useRef(false);
const inlineCommandRef = useRef<InlineCommandState | null>(null);
const searchRequestIdRef = useRef(0);
const latestSearchQueryRef = useRef(query);
Expand Down Expand Up @@ -547,7 +548,7 @@ function App() {
? appliedLauncherHeightRef.current
: size.height;
await appWindow.setSize(new LogicalSize(size.width, launcherHeight));
await appWindow.center();
await invoke("center_main_window");
} catch (error) {
console.error("Failed to resize window:", error);
}
Expand All @@ -556,6 +557,10 @@ function App() {
void resizeWindow();
}, [view]);

useEffect(() => {
shouldCenterOnNextResizeRef.current = true;
}, [view]);

useEffect(() => {
const root = document.getElementById("root");
root?.classList.toggle("gquick-expanded-root", isExpandedView(view));
Expand All @@ -581,11 +586,22 @@ function App() {

const targetHeight = pendingLauncherHeightRef.current;
if (targetHeight == null) return;
if (targetHeight === appliedLauncherHeightRef.current) return;

const sizeChanged = targetHeight !== appliedLauncherHeightRef.current;
const shouldCenter = shouldCenterOnNextResizeRef.current;

if (!sizeChanged && !shouldCenter) return;

try {
await getCurrentWindow().setSize(new LogicalSize(LAUNCHER_WINDOW_SIZE.width, targetHeight));
appliedLauncherHeightRef.current = targetHeight;
const appWindow = getCurrentWindow();
if (sizeChanged) {
await appWindow.setSize(new LogicalSize(LAUNCHER_WINDOW_SIZE.width, targetHeight));
appliedLauncherHeightRef.current = targetHeight;
}
if (shouldCenter) {
await invoke("center_main_window");
shouldCenterOnNextResizeRef.current = false;
}
} catch (error) {
console.error("Failed to resize launcher window:", error);
}
Expand Down Expand Up @@ -1552,6 +1568,7 @@ function App() {
read_file: "Access the content of local text files.",
search_notes: "Search your saved notes.",
create_note: "Save new information to your notes database.",
update_note: "Update an existing note in your notes database.",
get_current_weather: "Get current weather conditions for a location.",
get_weather_forecast: "Get 7-day weather forecast for a location.",
calculate: "Perform mathematical calculations.",
Expand Down Expand Up @@ -1579,7 +1596,8 @@ function App() {

const dataMgmtSection = [
enabledToolNames.has("read_file") && "* `read_file`: Access the content of local text files.",
enabledToolNames.has("create_note") && "* `create_note`: Save new information to your notes database."
enabledToolNames.has("create_note") && "* `create_note`: Save new information to your notes database.",
enabledToolNames.has("update_note") && "* `update_note`: Update an existing note in your notes database."
].filter((val): val is string => typeof val === "string").join("\n");

const codeExecSection = [
Expand Down
124 changes: 99 additions & 25 deletions src/components/NotesView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export function NotesView({ initialNoteId, searchQuery = "" }: NotesViewProps) {
const [loading, setLoading] = useState(true);
const [editingNote, setEditingNote] = useState<Note | null>(null);
const [isCreating, setIsCreating] = useState(false);
const [isEditMode, setIsEditMode] = useState(false);
const [editTitle, setEditTitle] = useState("");
const [editContent, setEditContent] = useState("");
const [copiedId, setCopiedId] = useState<number | null>(null);
Expand All @@ -57,11 +58,12 @@ export function NotesView({ initialNoteId, searchQuery = "" }: NotesViewProps) {
hasHandledInitialNote.current = false;
}, [initialNoteId]);

const startEdit = useCallback((note: Note) => {
const startEdit = useCallback((note: Note, editDirectly = false) => {
setEditingNote(note);
setEditTitle(note.title);
setEditContent(note.content);
setIsCreating(false);
setIsEditMode(editDirectly);
}, []);

const fetchNotes = useCallback(async () => {
Expand All @@ -73,7 +75,7 @@ export function NotesView({ initialNoteId, searchQuery = "" }: NotesViewProps) {
if (initialNoteId && !hasHandledInitialNote.current && !isCreating && !editingNote) {
const targetNote = data.find((n) => n.id === initialNoteId);
if (targetNote) {
startEdit(targetNote);
startEdit(targetNote, false);
}
hasHandledInitialNote.current = true;
}
Expand Down Expand Up @@ -124,6 +126,7 @@ export function NotesView({ initialNoteId, searchQuery = "" }: NotesViewProps) {
setIsCreating(false);
setEditTitle("");
setEditContent("");
setIsEditMode(false);
fetchNotes();
} catch (e) {
console.error("Failed to save note:", e);
Expand Down Expand Up @@ -161,13 +164,15 @@ export function NotesView({ initialNoteId, searchQuery = "" }: NotesViewProps) {
setEditingNote(null);
setEditTitle("");
setEditContent("");
setIsEditMode(true);
};

const cancelEdit = () => {
setEditingNote(null);
setIsCreating(false);
setEditTitle("");
setEditContent("");
setIsEditMode(false);
};

const filteredNotes = searchQuery.trim()
Expand All @@ -178,28 +183,87 @@ export function NotesView({ initialNoteId, searchQuery = "" }: NotesViewProps) {
)
: notes;

const isEditing = isCreating || editingNote !== null;
const isDetailActive = isCreating || editingNote !== null;

return (
<div className={cn("flex flex-col min-w-[500px]", isEditing ? "h-auto" : "h-[300px]")}>
<div className={cn("flex flex-col min-w-[500px]", isDetailActive ? "h-auto" : "h-[300px]")}>
<div className="flex-1 overflow-y-auto">
{isEditing ? (
<div className="p-4 space-y-3">
<input
type="text"
value={editTitle}
onChange={(e) => setEditTitle(e.target.value)}
placeholder="Note title (optional)"
className="w-full bg-zinc-800 border border-white/10 rounded-xl px-3 py-2 text-sm text-zinc-200 placeholder-zinc-500 outline-none focus:border-blue-500/50 transition-all"
/>
<textarea
value={editContent}
onChange={(e) => setEditContent(e.target.value)}
placeholder="Write your note in Markdown..."
rows={10}
className="w-full bg-zinc-800 border border-white/10 rounded-xl px-3 py-2 text-sm text-zinc-200 placeholder-zinc-500 outline-none focus:border-blue-500/50 transition-all resize-none"
/>
<div className="flex items-center justify-end gap-2">
{isDetailActive ? (
<div className="p-4 space-y-3 flex flex-col h-full">
{/* Header with Title and Mode Toggle */}
<div className="flex items-center justify-between border-b border-white/5 pb-2 mb-2">
<span className="text-[11px] font-bold text-zinc-500 uppercase tracking-widest">
{editingNote ? "Note Detail" : "New Note"}
</span>
<div className="flex bg-zinc-950 p-0.5 rounded-lg border border-white/5">
<button
type="button"
onClick={() => setIsEditMode(false)}
className={cn(
"px-3 py-1 text-xs font-medium rounded-md transition-all cursor-pointer",
!isEditMode
? "bg-zinc-800 text-zinc-100 shadow-sm"
: "text-zinc-500 hover:text-zinc-300"
)}
>
Preview
</button>
<button
type="button"
onClick={() => setIsEditMode(true)}
className={cn(
"px-3 py-1 text-xs font-medium rounded-md transition-all cursor-pointer",
isEditMode
? "bg-zinc-800 text-zinc-100 shadow-sm"
: "text-zinc-500 hover:text-zinc-300"
)}
>
Edit
</button>
</div>
</div>

{!isEditMode ? (
/* Preview Mode */
<div className="bg-zinc-900/40 border border-white/5 rounded-xl p-4 min-h-[220px] max-h-[350px] overflow-y-auto space-y-3 select-text custom-scrollbar">
{editTitle.trim() ? (
<h2 className="text-base font-semibold text-zinc-100 border-b border-white/5 pb-2">
{editTitle}
</h2>
) : (
<h2 className="text-base font-semibold italic text-zinc-500 border-b border-white/5 pb-2">
Untitled Note
</h2>
)}
<div className="text-sm text-zinc-300 leading-relaxed break-words">
{editContent.trim() ? (
<MarkdownMessage content={editContent} />
) : (
<p className="italic text-zinc-500 text-xs">No content yet. Click "Edit" to add some.</p>
)}
</div>
</div>
) : (
/* Edit Mode */
<div className="space-y-3">
<input
type="text"
value={editTitle}
onChange={(e) => setEditTitle(e.target.value)}
placeholder="Note title (optional)"
className="w-full bg-zinc-800 border border-white/10 rounded-xl px-3 py-2 text-sm text-zinc-200 placeholder-zinc-500 outline-none focus:border-blue-500/50 transition-all"
/>
<textarea
value={editContent}
onChange={(e) => setEditContent(e.target.value)}
placeholder="Write your note in Markdown..."
rows={10}
className="w-full bg-zinc-800 border border-white/10 rounded-xl px-3 py-2 text-sm text-zinc-200 placeholder-zinc-500 outline-none focus:border-blue-500/50 transition-all resize-none"
/>
</div>
)}

<div className="flex items-center justify-end gap-2 pt-2 border-t border-white/5">
<button
onClick={cancelEdit}
className="flex items-center gap-1 px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded-lg text-xs font-medium transition-colors border border-white/10 cursor-pointer"
Expand Down Expand Up @@ -242,7 +306,8 @@ export function NotesView({ initialNoteId, searchQuery = "" }: NotesViewProps) {
{filteredNotes.map((note) => (
<div
key={note.id}
className="group flex flex-col gap-1.5 p-3 rounded-xl bg-white/5 border border-white/5 hover:bg-white/10 hover:border-white/10 transition-all"
onClick={() => startEdit(note, false)}
className="group flex flex-col gap-1.5 p-3 rounded-xl bg-white/5 border border-white/5 hover:bg-white/10 hover:border-white/10 transition-all cursor-pointer"
>
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
Expand All @@ -252,7 +317,10 @@ export function NotesView({ initialNoteId, searchQuery = "" }: NotesViewProps) {
</div>
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
<button
onClick={() => handleCopy(note)}
onClick={(e) => {
e.stopPropagation();
handleCopy(note);
}}
className="p-1.5 hover:bg-white/10 rounded-lg text-zinc-400 hover:text-zinc-200 transition-colors cursor-pointer"
title="Copy content"
>
Expand All @@ -263,14 +331,20 @@ export function NotesView({ initialNoteId, searchQuery = "" }: NotesViewProps) {
)}
</button>
<button
onClick={() => startEdit(note)}
onClick={(e) => {
e.stopPropagation();
startEdit(note, true);
}}
className="p-1.5 hover:bg-white/10 rounded-lg text-zinc-400 hover:text-zinc-200 transition-colors cursor-pointer"
title="Edit note"
>
<Edit2 className="h-3.5 w-3.5" />
</button>
<button
onClick={() => handleDelete(note.id)}
onClick={(e) => {
e.stopPropagation();
handleDelete(note.id);
}}
className="p-1.5 hover:bg-red-500/10 rounded-lg text-zinc-400 hover:text-red-400 transition-colors cursor-pointer"
title="Delete note"
>
Expand Down
7 changes: 7 additions & 0 deletions src/components/ToolCallsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ function getToolDetails(name: string, args: Record<string, any>, isCompleted: bo
? `Created note "${args.title || ""}"`
: `Creating note "${args.title || ""}"...`;
break;
case "update_note":
Icon = StickyNote;
label = "Update Note";
description = isCompleted
? `Updated note "${args.title || ""}"`
: `Updating note "${args.title || ""}"...`;
break;
case "get_current_weather":
Icon = Cloud;
label = "Current Weather";
Expand Down
Loading
Loading