Skip to content

Commit d31f196

Browse files
authored
Merge pull request #25 from TeaCoder52/develop
Develop
2 parents 8deb54a + 2320b69 commit d31f196

22 files changed

Lines changed: 828 additions & 90 deletions

File tree

.github/ISSUE_TEMPLATE/feature_request.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ body:
88
attributes:
99
value: |
1010
Thanks for taking the time to suggest an improvement!
11-
Datary is a database management tool, so features related to databases, UX, performance, or integrations are especially welcome.
1211
1312
- type: textarea
1413
attributes:

.github/scripts/telegram-notify.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ if (GITHUB_EVENT_PATH && fs.existsSync(GITHUB_EVENT_PATH)) {
2525

2626
if (GITHUB_EVENT_NAME === 'push') {
2727
message += `\n*Push*\n\n`
28-
message += `Branch: ${GITHUB_REF?.replace('refs/heads/', '')}\n`
28+
message += `Branch: ${GITHUB_REF?.replace('refs/heads/', '')}\n\n`
2929

3030
payload.commits?.slice(0, 3).forEach((c, i) => {
3131
message += `${i + 1}. ${c.message.split('\n')[0]}\n`

CONTRIBUTING.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ We aim to maintain a friendly and inclusive environment. Please respect all part
2828
- Give constructive feedback.
2929
- Report problems or abusive behavior to maintainers.
3030

31-
Full code of conduct can be found in [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md).
32-
3331
---
3432

3533
## How to Contribute

apps/desktop/renderer/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@datary/core": "workspace:",
1414
"@hookform/resolvers": "^5.2.2",
1515
"@tailwindcss/vite": "^4.1.18",
16+
"@tanstack/react-virtual": "^3.13.18",
1617
"class-variance-authority": "^0.7.1",
1718
"clsx": "^2.1.1",
1819
"lucide-react": "^0.563.0",

apps/desktop/renderer/src/app/store/table-data.store.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,48 @@ export const useTableDataStore = create<TableDataState>((set, get) => ({
2626

2727
set({ loading: true })
2828

29-
const data = await window.datary.db.loadTableData(schema, table)
29+
try {
30+
const [columnsRaw, tableDataRaw] = await Promise.all([
31+
window.datary.db.loadColumns(schema, table),
32+
window.datary.db.loadTableData(schema, table)
33+
])
3034

31-
set(state => ({
32-
tables: {
33-
...state.tables,
34-
[key]: data
35-
},
36-
loading: false
37-
}))
35+
// @ts-ignore
36+
const columns = columnsRaw.map(col => {
37+
let type = col.type
38+
if (col.enumValues && col.enumValues.length > 0) {
39+
type = `enum(${col.enumValues.join(', ')})`
40+
}
41+
return {
42+
columnName: col.columnName,
43+
type,
44+
nullable: col.nullable,
45+
enumValues: col.enumValues
46+
}
47+
})
48+
49+
const tableData: TableData = {
50+
// @ts-ignore
51+
columns: columns.map(col => ({
52+
name: col.columnName,
53+
type: col.type,
54+
nullable: col.nullable
55+
})),
56+
rows: tableDataRaw.rows
57+
}
58+
59+
set(state => ({
60+
tables: {
61+
...state.tables,
62+
[key]: tableData
63+
},
64+
loading: false
65+
}))
66+
} catch (err: any) {
67+
toast.error(`Failed to load table ${table}: ${err.message}`)
68+
console.error(err)
69+
set({ loading: false })
70+
}
3871
},
3972
reset: () =>
4073
set({

apps/desktop/renderer/src/main.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import type {
2-
ColumnMetadataContract,
3-
DatabaseMetadataContract,
4-
TableMetadataContract
5-
} from '@datary/core'
1+
import type { DatabaseMetadataContract, TableMetadataContract } from '@datary/core'
62
import ReactDOM from 'react-dom/client'
73
import { HashRouter } from 'react-router-dom'
84

@@ -26,11 +22,7 @@ declare global {
2622
schema: string | null
2723
): Promise<TableMetadataContract[]>
2824
loadViews(database: string, schema: string | null): Promise<TableMetadataContract[]>
29-
loadColumns(
30-
database: string,
31-
schema: string,
32-
table: string
33-
): Promise<ColumnMetadataContract[]>
25+
loadColumns: any
3426
loadTableData: any
3527
disconnect: Function
3628
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type { ReactNode } from 'react'
2+
3+
import { useContextMenu } from '@/shared/hooks/useContextMenu'
4+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem } from '@/shared/ui/dropdown-menu'
5+
6+
interface Props {
7+
children: ReactNode
8+
disabledCloseOthers: boolean
9+
onClose: () => void
10+
onCloseOthers: () => void
11+
onCloseAll: () => void
12+
}
13+
export function ExplorerTabContextMenu({
14+
children,
15+
disabledCloseOthers,
16+
onClose,
17+
onCloseOthers,
18+
onCloseAll
19+
}: Props) {
20+
const { open, position, onContextMenu, close } = useContextMenu()
21+
22+
return (
23+
<>
24+
<div onContextMenu={onContextMenu}>{children}</div>
25+
26+
{open && (
27+
<DropdownMenu open={open} onOpenChange={val => !val && close()}>
28+
<DropdownMenuContent
29+
align="start"
30+
sideOffset={4}
31+
className="absolute"
32+
style={{ top: position!.y, left: position!.x }}
33+
>
34+
<DropdownMenuItem
35+
onClick={() => {
36+
onClose()
37+
close()
38+
}}
39+
>
40+
Close
41+
</DropdownMenuItem>
42+
<DropdownMenuItem
43+
disabled={disabledCloseOthers}
44+
onClick={() => {
45+
onCloseOthers()
46+
close()
47+
}}
48+
>
49+
Close Others
50+
</DropdownMenuItem>
51+
<DropdownMenuItem
52+
disabled={disabledCloseOthers}
53+
onClick={() => {
54+
onCloseAll()
55+
close()
56+
}}
57+
>
58+
Close All
59+
</DropdownMenuItem>
60+
</DropdownMenuContent>
61+
</DropdownMenu>
62+
)}
63+
</>
64+
)
65+
}

apps/desktop/renderer/src/modules/explorer/components/tabs/ExplorerTabs.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,51 @@ import type { ReactNode } from 'react'
22

33
import type { Tab } from '../../types/explorer.types'
44

5+
import { ExplorerTabContextMenu } from './ExplorerTabContextMenu'
56
import { TabContent } from './TabContent'
6-
import { TableTabs } from '@/shared/components/table-tabs'
7+
import { TableTabs } from './TableTabs'
78
import { cn } from '@/shared/lib/utils'
89

910
interface Props {
1011
tabs: Tab[]
1112
activeTab: string
1213
onSelectTab: (id: string) => void
1314
onCloseTab: (id: string) => void
15+
onCloseOthers: (id: string) => void
16+
onCloseAll: () => void
1417
children?: ReactNode
1518
}
1619

17-
export function ExplorerTabs({ tabs, activeTab, onSelectTab, onCloseTab }: Props) {
20+
export function ExplorerTabs({
21+
tabs,
22+
activeTab,
23+
onSelectTab,
24+
onCloseTab,
25+
onCloseOthers,
26+
onCloseAll
27+
}: Props) {
1828
return (
19-
<div className="flex h-full flex-col">
29+
<div className="flex h-full min-h-0 flex-col">
2030
<TableTabs
2131
tabs={tabs}
2232
activeTab={activeTab}
2333
onSelectTab={onSelectTab}
2434
onCloseTab={onCloseTab}
35+
renderTab={(tab, tabNode) => (
36+
<ExplorerTabContextMenu
37+
disabledCloseOthers={tabs.length <= 1}
38+
onClose={() => onCloseTab(tab.id)}
39+
onCloseOthers={() => {
40+
onSelectTab(tab.id)
41+
onCloseOthers(tab.id)
42+
}}
43+
onCloseAll={() => {
44+
onCloseAll()
45+
}}
46+
>
47+
<div className="h-full">{tabNode}</div>
48+
</ExplorerTabContextMenu>
49+
)}
2550
>
2651
{tabs.map(tab => (
2752
<div

apps/desktop/renderer/src/modules/explorer/components/tabs/TabContent.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { useMemo } from 'react'
2+
13
import type { Tab } from '../../types/explorer.types'
24

35
import { useTableDataStore } from '@/app/store/table-data.store'
4-
import { DataTable } from '@/shared/components/data-table'
6+
import { DataGrid } from '@/widgets/data-grid/DataGrid'
57
import { QueryEditor } from '@/widgets/query-editor/components/QueryEditor'
68

79
interface Props {
@@ -26,5 +28,15 @@ export function TabContent({ tab }: Props) {
2628
)
2729
}
2830

29-
return <DataTable data={data} />
31+
const gridData = useMemo(() => {
32+
return {
33+
rows: data.rows,
34+
columns: data.columns.map(col => ({
35+
name: col.name,
36+
type: col.type
37+
}))
38+
}
39+
}, [data])
40+
41+
return <DataGrid data={gridData} rowHeight={32} renderer="dom" editable />
3042
}

apps/desktop/renderer/src/shared/components/table-tabs.tsx renamed to apps/desktop/renderer/src/modules/explorer/components/tabs/TableTabs.tsx

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { ChevronLeft, ChevronRight, Table, X } from 'lucide-react'
2-
import React from 'react'
3-
import { useEffect, useRef, useState } from 'react'
2+
import { type ReactNode, useEffect, useRef, useState } from 'react'
43

5-
import { cn } from '../lib/utils'
6-
import { Button } from '../ui/button'
4+
import { cn } from '../../../../shared/lib/utils'
5+
import { Button } from '../../../../shared/ui/button'
76

87
export interface Tab {
98
id: string
@@ -16,10 +15,18 @@ interface TableTabsProps {
1615
activeTab: string
1716
onSelectTab: (id: string) => void
1817
onCloseTab: (id: string) => void
19-
children: React.ReactNode
18+
renderTab?: (tab: Tab, defaultTab: ReactNode) => ReactNode
19+
children: ReactNode
2020
}
2121

22-
export function TableTabs({ tabs, activeTab, onSelectTab, onCloseTab, children }: TableTabsProps) {
22+
export function TableTabs({
23+
tabs,
24+
activeTab,
25+
onSelectTab,
26+
onCloseTab,
27+
renderTab,
28+
children
29+
}: TableTabsProps) {
2330
const tabsRef = useRef<HTMLDivElement>(null)
2431
const [showScrollButtons, setShowScrollButtons] = useState(false)
2532

@@ -61,38 +68,42 @@ export function TableTabs({ tabs, activeTab, onSelectTab, onCloseTab, children }
6168
)}
6269

6370
<div ref={tabsRef} className="scrollbar-none flex flex-1 overflow-x-auto">
64-
{tabs.map(tab => (
65-
<button
66-
key={tab.id}
67-
type="button"
68-
className={cn(
69-
'group border-border flex shrink-0 items-center gap-2 border-r px-4 py-2 text-sm transition-colors',
70-
activeTab === tab.id
71-
? 'bg-background text-foreground'
72-
: 'bg-card text-muted-foreground hover:bg-secondary hover:text-foreground'
73-
)}
74-
onClick={() => onSelectTab(tab.id)}
75-
>
76-
<Table className="text-primary h-4 w-4" />
77-
<span className="max-w-32 truncate">{tab.name}</span>
71+
{tabs.map(tab => {
72+
const defaultTab = (
7873
<button
74+
key={tab.id}
7975
type="button"
8076
className={cn(
81-
'ml-1 rounded p-0.5 transition-colors',
82-
'text-muted-foreground hover:bg-secondary hover:text-foreground',
83-
'opacity-0 group-hover:opacity-100',
84-
activeTab === tab.id && 'opacity-100'
77+
'group border-border flex shrink-0 items-center gap-2 border-r px-4 py-2 text-sm transition-colors select-none',
78+
activeTab === tab.id
79+
? 'bg-background text-foreground'
80+
: 'bg-card text-muted-foreground hover:bg-secondary hover:text-foreground'
8581
)}
86-
onClick={e => {
87-
e.stopPropagation()
88-
onCloseTab(tab.id)
89-
}}
82+
onClick={() => onSelectTab(tab.id)}
9083
>
91-
<X className="h-3.5 w-3.5" />
92-
<span className="sr-only">Close tab</span>
84+
<Table className="text-primary h-4 w-4" />
85+
<span className="max-w-32 truncate">{tab.name}</span>
86+
87+
<button
88+
type="button"
89+
className={cn(
90+
'ml-1 rounded p-0.5 transition-colors',
91+
'text-muted-foreground hover:bg-secondary hover:text-foreground',
92+
'opacity-0 group-hover:opacity-100',
93+
activeTab === tab.id && 'opacity-100'
94+
)}
95+
onClick={e => {
96+
e.stopPropagation()
97+
onCloseTab(tab.id)
98+
}}
99+
>
100+
<X className="h-3.5 w-3.5" />
101+
</button>
93102
</button>
94-
</button>
95-
))}
103+
)
104+
105+
return renderTab ? renderTab(tab, defaultTab) : defaultTab
106+
})}
96107
</div>
97108

98109
{showScrollButtons && (
@@ -103,7 +114,6 @@ export function TableTabs({ tabs, activeTab, onSelectTab, onCloseTab, children }
103114
onClick={() => scroll('right')}
104115
>
105116
<ChevronRight className="h-4 w-4" />
106-
<span className="sr-only">Scroll right</span>
107117
</Button>
108118
)}
109119
</div>

0 commit comments

Comments
 (0)