Skip to content

Commit 3f9d557

Browse files
committed
Merge branch 'main' of https://github.com/wannabespace/conar into feat/sql-auto-completions
2 parents b782c7e + 840725d commit 3f9d557

16 files changed

Lines changed: 278 additions & 432 deletions

File tree

apps/api/src/orpc/context.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Context as HonoContext } from 'hono'
33
export function createContext(c: HonoContext) {
44
const cookie = c.req.header('Cookie')
55
const authorization = c.req.header('Authorization')
6+
const desktopVersion = c.req.header('x-desktop-version') || null
67

78
const h = new Headers()
89

@@ -12,11 +13,15 @@ export function createContext(c: HonoContext) {
1213
if (authorization)
1314
h.set('Authorization', authorization)
1415

16+
if (desktopVersion)
17+
h.set('x-desktop-version', desktopVersion)
18+
1519
return {
1620
headers: h,
1721
setHeader: (key: string, value: string) => {
1822
c.res.headers.set(key, value)
1923
},
24+
desktopVersion,
2025
}
2126
}
2227

apps/desktop/package.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,11 @@
3030
"dependencies": {
3131
"@clickhouse/client": "catalog:",
3232
"@todesktop/runtime": "catalog:",
33-
"ecdsa-sig-formatter": "catalog:",
3433
"electron-store": "catalog:",
35-
"fill-range": "catalog:",
3634
"monacopilot": "catalog:",
3735
"mssql": "catalog:",
3836
"mysql2": "catalog:",
39-
"pg": "catalog:",
40-
"reusify": "catalog:",
41-
"run-parallel": "catalog:"
37+
"pg": "catalog:"
4238
},
4339
"devDependencies": {
4440
"@ai-sdk/react": "catalog:",

apps/desktop/src/routes/(protected)/_protected/-components/databases-list.tsx

Lines changed: 127 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -3,103 +3,110 @@ import type { databases } from '~/drizzle'
33
import { SafeURL } from '@conar/shared/utils/safe-url'
44
import { Badge } from '@conar/ui/components/badge'
55
import { Button } from '@conar/ui/components/button'
6+
import { ButtonGroup } from '@conar/ui/components/button-group'
67
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@conar/ui/components/dropdown-menu'
7-
import { Skeleton } from '@conar/ui/components/skeleton'
88
import { copy } from '@conar/ui/lib/copy'
99
import { cn } from '@conar/ui/lib/utils'
1010
import { RiDeleteBinLine, RiEditLine, RiFileCopyLine, RiMoreLine } from '@remixicon/react'
1111
import { useLiveQuery } from '@tanstack/react-db'
1212
import { Link } from '@tanstack/react-router'
13-
import { useMemo, useRef } from 'react'
13+
import { AnimatePresence, motion } from 'motion/react'
14+
import { useRef, useState } from 'react'
1415
import { DatabaseIcon, databasesCollection, useDatabaseLinkParams } from '~/entities/database'
1516
import { RemoveConnectionDialog } from './remove-connection-dialog'
1617
import { RenameConnectionDialog } from './rename-connection-dialog'
1718

1819
function DatabaseCard({ database, onRemove, onRename }: { database: typeof databases.$inferSelect, onRemove: () => void, onRename: () => void }) {
19-
const connectionString = useMemo(() => {
20-
const url = new SafeURL(database.connectionString)
20+
const url = new SafeURL(database.connectionString)
2121

22-
if (database.isPasswordExists || url.password) {
23-
url.password = '*'.repeat(url.password.length || 6)
24-
}
22+
if (database.isPasswordExists || url.password) {
23+
url.password = '*'.repeat(url.password.length || 6)
24+
}
2525

26-
return url.toString()
27-
}, [database.connectionString, database.isPasswordExists])
26+
const connectionString = url.toString()
2827

2928
const params = useDatabaseLinkParams(database.id)
3029

3130
return (
32-
<Link
33-
className={cn(
34-
'relative flex items-center justify-between gap-4',
35-
'rounded-lg p-5 bg-muted/30 border overflow-hidden border-border/50 border-l-4 group',
36-
database.color
37-
? 'hover:border-(--color)/60 border-l-(--color)/60'
38-
: 'hover:border-primary/60',
39-
)}
40-
style={database.color ? { '--color': database.color } : {}}
41-
{...params}
31+
<motion.div
32+
layout
33+
initial={{ opacity: 0, scale: 0.75 }}
34+
animate={{ opacity: 1, scale: 1 }}
35+
exit={{ opacity: 0, scale: 0.75 }}
36+
transition={{ duration: 0.15 }}
4237
>
43-
<div
38+
<Link
4439
className={cn(
45-
'size-12 shrink-0 rounded-lg p-3',
46-
'bg-muted/70',
40+
'relative flex items-center justify-between gap-4',
41+
'rounded-lg p-5 bg-muted/30 border overflow-hidden border-border/50 border-l-4 group',
42+
database.color
43+
? 'hover:border-(--color)/60 border-l-(--color)/60'
44+
: 'hover:border-primary/60',
4745
)}
46+
style={database.color ? { '--color': database.color } : {}}
47+
{...params}
4848
>
49-
<DatabaseIcon
50-
type={database.type}
51-
className="size-full"
52-
/>
53-
</div>
54-
<div className="flex flex-1 flex-col min-w-0">
55-
<div className="font-medium tracking-tight truncate flex items-center gap-2">
56-
<span className={database.color ? 'text-(--color) group-hover:text-(--color)/80' : ''}>
57-
{database.name}
58-
</span>
59-
{database.label && (
60-
<Badge variant="secondary">
61-
{database.label}
62-
</Badge>
49+
<div
50+
className={cn(
51+
'size-12 shrink-0 rounded-lg p-3',
52+
'bg-muted/70',
6353
)}
54+
>
55+
<DatabaseIcon
56+
type={database.type}
57+
className="size-full"
58+
/>
6459
</div>
65-
<div data-mask className="text-xs text-muted-foreground font-mono truncate">{connectionString.replaceAll('*', '•')}</div>
66-
</div>
67-
<DropdownMenu>
68-
<DropdownMenuTrigger className="rounded-md p-2 hover:bg-accent-foreground/5">
69-
<RiMoreLine className="size-4" />
70-
</DropdownMenuTrigger>
71-
<DropdownMenuContent align="end">
72-
<DropdownMenuItem
73-
onClick={(e) => {
74-
e.stopPropagation()
75-
copy(database.connectionString, 'Connection string copied to clipboard')
76-
}}
77-
>
78-
<RiFileCopyLine className="size-4 opacity-50" />
79-
Copy Connection String
80-
</DropdownMenuItem>
81-
<DropdownMenuItem
82-
onClick={(e) => {
83-
e.stopPropagation()
84-
onRename()
85-
}}
86-
>
87-
<RiEditLine className="size-4 opacity-50" />
88-
Rename
89-
</DropdownMenuItem>
90-
<DropdownMenuItem
91-
className="text-destructive focus:text-destructive"
92-
onClick={(e) => {
93-
e.stopPropagation()
94-
onRemove()
95-
}}
96-
>
97-
<RiDeleteBinLine className="size-4" />
98-
Remove
99-
</DropdownMenuItem>
100-
</DropdownMenuContent>
101-
</DropdownMenu>
102-
</Link>
60+
<div className="flex flex-1 flex-col min-w-0">
61+
<div className="font-medium tracking-tight truncate flex items-center gap-2">
62+
<span className={database.color ? 'text-(--color) group-hover:text-(--color)/80' : ''}>
63+
{database.name}
64+
</span>
65+
{database.label && (
66+
<Badge variant="secondary">
67+
{database.label}
68+
</Badge>
69+
)}
70+
</div>
71+
<div data-mask className="text-xs text-muted-foreground font-mono truncate">{connectionString.replaceAll('*', '•')}</div>
72+
</div>
73+
<DropdownMenu>
74+
<DropdownMenuTrigger className="rounded-md p-2 hover:bg-accent-foreground/5">
75+
<RiMoreLine className="size-4" />
76+
</DropdownMenuTrigger>
77+
<DropdownMenuContent align="end">
78+
<DropdownMenuItem
79+
onClick={(e) => {
80+
e.stopPropagation()
81+
copy(database.connectionString, 'Connection string copied to clipboard')
82+
}}
83+
>
84+
<RiFileCopyLine className="size-4 opacity-50" />
85+
Copy Connection String
86+
</DropdownMenuItem>
87+
<DropdownMenuItem
88+
onClick={(e) => {
89+
e.stopPropagation()
90+
onRename()
91+
}}
92+
>
93+
<RiEditLine className="size-4 opacity-50" />
94+
Rename
95+
</DropdownMenuItem>
96+
<DropdownMenuItem
97+
className="text-destructive focus:text-destructive"
98+
onClick={(e) => {
99+
e.stopPropagation()
100+
onRemove()
101+
}}
102+
>
103+
<RiDeleteBinLine className="size-4" />
104+
Remove
105+
</DropdownMenuItem>
106+
</DropdownMenuContent>
107+
</DropdownMenu>
108+
</Link>
109+
</motion.div>
103110
)
104111
}
105112

@@ -121,52 +128,66 @@ export function Empty() {
121128
)
122129
}
123130

124-
function DatabaseCardSkeleton() {
125-
return (
126-
<div className="relative flex items-center justify-between gap-4 rounded-lg bg-card p-5">
127-
<Skeleton className="size-14 shrink-0 rounded-full" />
128-
<div className="flex flex-1 flex-col gap-2 min-w-0">
129-
<Skeleton className="h-5 w-1/3" />
130-
<Skeleton className="h-4 w-2/3" />
131-
</div>
132-
</div>
133-
)
134-
}
135-
136131
export function DatabasesList() {
137132
const { data: databases } = useLiveQuery(q => q
138133
.from({ databases: databasesCollection })
139134
.orderBy(({ databases }) => databases.createdAt, 'desc'))
140135
const renameDialogRef = useRef<ComponentRef<typeof RenameConnectionDialog>>(null)
141136
const removeDialogRef = useRef<ComponentRef<typeof RemoveConnectionDialog>>(null)
137+
const [selectedLabel, setSelectedLabel] = useState<string | null>(null)
138+
139+
const availableLabels = Array.from(new Set(databases.map(db => db.label).filter(Boolean) as string[])).sort()
140+
141+
const filteredDatabases = selectedLabel
142+
? databases.filter(db => db.label === selectedLabel)
143+
: databases
142144

143145
return (
144146
<div className="flex flex-col gap-6">
145147
<RemoveConnectionDialog ref={removeDialogRef} />
146148
<RenameConnectionDialog ref={renameDialogRef} />
149+
{availableLabels.length > 0 && (
150+
<ButtonGroup>
151+
<Button
152+
variant={selectedLabel === null ? 'default' : 'outline'}
153+
size="xs"
154+
onClick={() => setSelectedLabel(null)}
155+
className="border!"
156+
>
157+
All
158+
</Button>
159+
{availableLabels.map(label => (
160+
<Button
161+
key={label}
162+
variant={selectedLabel === label ? 'default' : 'outline'}
163+
size="xs"
164+
onClick={() => setSelectedLabel(label)}
165+
className="border!"
166+
>
167+
{label}
168+
</Button>
169+
))}
170+
</ButtonGroup>
171+
)}
147172
<div className="flex flex-col gap-2">
148-
{!databases
173+
{filteredDatabases.length > 0
149174
? (
150-
<>
151-
<DatabaseCardSkeleton />
152-
<DatabaseCardSkeleton />
153-
<DatabaseCardSkeleton />
154-
</>
175+
<AnimatePresence initial={false} mode="popLayout">
176+
{filteredDatabases.map(database => (
177+
<DatabaseCard
178+
key={database.id}
179+
database={database}
180+
onRemove={() => {
181+
removeDialogRef.current?.remove(database)
182+
}}
183+
onRename={() => {
184+
renameDialogRef.current?.rename(database)
185+
}}
186+
/>
187+
))}
188+
</AnimatePresence>
155189
)
156-
: databases?.length
157-
? databases.map(database => (
158-
<DatabaseCard
159-
key={database.id}
160-
database={database}
161-
onRemove={() => {
162-
removeDialogRef.current?.remove(database)
163-
}}
164-
onRename={() => {
165-
renameDialogRef.current?.rename(database)
166-
}}
167-
/>
168-
))
169-
: <Empty />}
190+
: <Empty />}
170191
</div>
171192
</div>
172193
)

0 commit comments

Comments
 (0)