dbdiff-app 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/bin/cli.js +83 -0
- package/bin/install-local.js +57 -0
- package/electron/generate-icon.mjs +54 -0
- package/electron/icon.icns +0 -0
- package/electron/icon.png +0 -0
- package/electron/icon.svg +21 -0
- package/electron/main.js +169 -0
- package/electron/patch-dev-plist.js +31 -0
- package/electron/preload.cjs +18 -0
- package/electron/wait-for-vite.js +43 -0
- package/index.html +13 -0
- package/package.json +91 -0
- package/public/favicon.svg +15 -0
- package/public/vite.svg +1 -0
- package/server/export.ts +57 -0
- package/server/index.ts +392 -0
- package/src/App.css +1 -0
- package/src/App.tsx +543 -0
- package/src/assets/react.svg +1 -0
- package/src/components/CommandPalette.tsx +243 -0
- package/src/components/ConnectedView.tsx +78 -0
- package/src/components/ConnectionPicker.tsx +381 -0
- package/src/components/ConsoleView.tsx +360 -0
- package/src/components/CsvExportModal.tsx +144 -0
- package/src/components/DataGrid/DataGrid.tsx +262 -0
- package/src/components/DataGrid/DataGridCell.tsx +73 -0
- package/src/components/DataGrid/DataGridHeader.tsx +89 -0
- package/src/components/DataGrid/index.ts +20 -0
- package/src/components/DataGrid/types.ts +63 -0
- package/src/components/DataGrid/useColumnResize.ts +153 -0
- package/src/components/DataGrid/useDataGridSelection.ts +340 -0
- package/src/components/DataGrid/utils.ts +184 -0
- package/src/components/DatabaseMenu.tsx +93 -0
- package/src/components/DatabaseSwitcher.tsx +208 -0
- package/src/components/DiffView.tsx +215 -0
- package/src/components/EditConnectionModal.tsx +417 -0
- package/src/components/ErrorBoundary.tsx +69 -0
- package/src/components/GlobalShortcuts.tsx +201 -0
- package/src/components/InnerTabBar.tsx +129 -0
- package/src/components/JsonTreeViewer.tsx +387 -0
- package/src/components/MemberAccessEditor.tsx +443 -0
- package/src/components/MembersModal.tsx +446 -0
- package/src/components/NewConnectionModal.tsx +274 -0
- package/src/components/Resizer.tsx +66 -0
- package/src/components/ScanSuccessModal.tsx +113 -0
- package/src/components/ShortcutSettingsModal.tsx +318 -0
- package/src/components/Sidebar.tsx +532 -0
- package/src/components/TabBar.tsx +188 -0
- package/src/components/TableView.tsx +2147 -0
- package/src/components/ThemeToggle.tsx +44 -0
- package/src/components/index.ts +17 -0
- package/src/constants.ts +12 -0
- package/src/electron.d.ts +12 -0
- package/src/index.css +44 -0
- package/src/main.tsx +13 -0
- package/src/stores/hooks.ts +1146 -0
- package/src/stores/index.ts +12 -0
- package/src/stores/store.ts +1514 -0
- package/src/stores/useCloudSync.ts +274 -0
- package/src/stores/useSyncDatabase.ts +422 -0
- package/src/types.ts +277 -0
- package/src/utils/csv.ts +27 -0
- package/src/vite-env.d.ts +2 -0
- package/tsconfig.app.json +28 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +26 -0
- package/tsconfig.server.json +14 -0
- package/vite.config.ts +14 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { useState, useRef, useEffect } from "react";
|
|
2
|
+
import { X } from "lucide-react";
|
|
3
|
+
import type { ConnectionTab, DatabaseConfig } from "../types";
|
|
4
|
+
import { ThemeToggle } from "./ThemeToggle";
|
|
5
|
+
import { DatabaseMenu } from "./DatabaseMenu";
|
|
6
|
+
|
|
7
|
+
const isElectron = navigator.userAgent.includes("Electron");
|
|
8
|
+
const dragStyle = isElectron
|
|
9
|
+
? ({ WebkitAppRegion: "drag" } as React.CSSProperties)
|
|
10
|
+
: undefined;
|
|
11
|
+
const noDragStyle = isElectron
|
|
12
|
+
? ({ WebkitAppRegion: "no-drag" } as React.CSSProperties)
|
|
13
|
+
: undefined;
|
|
14
|
+
|
|
15
|
+
interface TabBarProps {
|
|
16
|
+
tabs: ConnectionTab[];
|
|
17
|
+
activeTabId: string;
|
|
18
|
+
draggedTabId: string | null;
|
|
19
|
+
databaseConfigs: DatabaseConfig[];
|
|
20
|
+
darkMode: boolean;
|
|
21
|
+
onTabSelect: (tabId: string) => void;
|
|
22
|
+
onTabClose: (tabId: string) => void;
|
|
23
|
+
onNewTab: () => void;
|
|
24
|
+
onDragStart: (e: React.DragEvent, tabId: string) => void;
|
|
25
|
+
onDragOver: (e: React.DragEvent, tabId: string) => void;
|
|
26
|
+
onDragEnd: () => void;
|
|
27
|
+
onThemeToggle: () => void;
|
|
28
|
+
onOpenShortcutSettings: () => void;
|
|
29
|
+
onScanLocalhost?: () => void;
|
|
30
|
+
isScanning: boolean;
|
|
31
|
+
onResetUIState: () => void;
|
|
32
|
+
activeDatabaseConfig?: DatabaseConfig | null;
|
|
33
|
+
hideMenus?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function TabBar({
|
|
37
|
+
tabs,
|
|
38
|
+
activeTabId,
|
|
39
|
+
draggedTabId,
|
|
40
|
+
databaseConfigs,
|
|
41
|
+
darkMode,
|
|
42
|
+
onTabSelect,
|
|
43
|
+
onTabClose,
|
|
44
|
+
onNewTab,
|
|
45
|
+
onDragStart,
|
|
46
|
+
onDragOver,
|
|
47
|
+
onDragEnd,
|
|
48
|
+
onThemeToggle,
|
|
49
|
+
onOpenShortcutSettings,
|
|
50
|
+
onScanLocalhost,
|
|
51
|
+
isScanning,
|
|
52
|
+
onResetUIState,
|
|
53
|
+
activeDatabaseConfig,
|
|
54
|
+
hideMenus,
|
|
55
|
+
}: TabBarProps) {
|
|
56
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
57
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
function handleClickOutside(e: MouseEvent) {
|
|
61
|
+
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
62
|
+
setMenuOpen(false);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (menuOpen) {
|
|
66
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
67
|
+
return () =>
|
|
68
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
69
|
+
}
|
|
70
|
+
}, [menuOpen]);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div
|
|
74
|
+
className={`flex items-center h-11 bg-stone-100 dark:bg-[#0a0a0a] border-b border-stone-200 dark:border-white/[0.06] px-3 gap-1${isElectron ? " pl-[80px]" : ""}`}
|
|
75
|
+
style={dragStyle}
|
|
76
|
+
>
|
|
77
|
+
{!hideMenus && (
|
|
78
|
+
<div className="relative mr-2" ref={menuRef} style={noDragStyle}>
|
|
79
|
+
<button
|
|
80
|
+
className="px-3 h-8 rounded-md text-[13px] font-semibold text-secondary hover:text-primary hover:bg-stone-200/50 dark:hover:bg-white/[0.06] transition-all duration-150"
|
|
81
|
+
onClick={() => setMenuOpen(!menuOpen)}
|
|
82
|
+
>
|
|
83
|
+
dbdiff
|
|
84
|
+
</button>
|
|
85
|
+
{menuOpen && (
|
|
86
|
+
<div className="absolute top-full left-0 mt-1 p-1 min-w-[200px] bg-white/90 dark:bg-[#2a2a2a]/90 backdrop-blur-xl border border-stone-200/50 dark:border-white/10 rounded-lg shadow-xl z-50">
|
|
87
|
+
<button
|
|
88
|
+
className="w-full px-2.5 py-1 text-left text-[13px] text-primary rounded-md hover:bg-stone-100 dark:hover:bg-white/10 transition-colors"
|
|
89
|
+
onClick={() => {
|
|
90
|
+
setMenuOpen(false);
|
|
91
|
+
onOpenShortcutSettings();
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
Shortcut Settings
|
|
95
|
+
</button>
|
|
96
|
+
{onScanLocalhost && (
|
|
97
|
+
<button
|
|
98
|
+
className="w-full px-2.5 py-1 text-left text-[13px] text-primary rounded-md hover:bg-stone-100 dark:hover:bg-white/10 transition-colors disabled:opacity-50"
|
|
99
|
+
disabled={isScanning}
|
|
100
|
+
onClick={() => {
|
|
101
|
+
setMenuOpen(false);
|
|
102
|
+
onScanLocalhost();
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
{isScanning ? "Scanning..." : "Scan Localhost"}
|
|
106
|
+
</button>
|
|
107
|
+
)}
|
|
108
|
+
<div className="my-1 border-t border-stone-200/50 dark:border-white/10" />
|
|
109
|
+
<button
|
|
110
|
+
className="w-full px-2.5 py-1 text-left text-[13px] text-primary rounded-md hover:bg-stone-100 dark:hover:bg-white/10 transition-colors"
|
|
111
|
+
onClick={() => {
|
|
112
|
+
setMenuOpen(false);
|
|
113
|
+
onResetUIState();
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
Reset UI State
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
{!hideMenus && activeDatabaseConfig && (
|
|
123
|
+
<div style={noDragStyle}>
|
|
124
|
+
<DatabaseMenu databaseConfig={activeDatabaseConfig} />
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
127
|
+
{tabs.map((tab) => {
|
|
128
|
+
const config = tab.databaseConfigId
|
|
129
|
+
? databaseConfigs.find((c) => c.id === tab.databaseConfigId)
|
|
130
|
+
: null;
|
|
131
|
+
return (
|
|
132
|
+
<div
|
|
133
|
+
key={tab.id}
|
|
134
|
+
draggable
|
|
135
|
+
onDragStart={(e) => onDragStart(e, tab.id)}
|
|
136
|
+
onDragOver={(e) => onDragOver(e, tab.id)}
|
|
137
|
+
onDragEnd={onDragEnd}
|
|
138
|
+
style={noDragStyle}
|
|
139
|
+
className={`group flex items-center gap-2 pl-4 pr-2 h-8 rounded-md cursor-pointer select-none transition-all duration-150 ${
|
|
140
|
+
tab.id === activeTabId
|
|
141
|
+
? "bg-white dark:bg-white/[0.08] text-primary shadow-sm dark:shadow-none"
|
|
142
|
+
: "text-secondary hover:text-primary hover:bg-stone-200/50 dark:hover:bg-white/[0.04]"
|
|
143
|
+
} ${draggedTabId === tab.id ? "opacity-50" : ""}`}
|
|
144
|
+
onClick={() => onTabSelect(tab.id)}
|
|
145
|
+
>
|
|
146
|
+
{config && (
|
|
147
|
+
<span
|
|
148
|
+
className="w-2 h-2 rounded-full flex-shrink-0"
|
|
149
|
+
style={{ backgroundColor: config.display.color }}
|
|
150
|
+
/>
|
|
151
|
+
)}
|
|
152
|
+
<span className="text-[13px] font-medium tracking-[-0.01em] truncate max-w-[140px]">
|
|
153
|
+
{tab.name}
|
|
154
|
+
</span>
|
|
155
|
+
<button
|
|
156
|
+
className="w-5 h-5 flex items-center justify-center rounded-full transition-all hover:bg-stone-200 dark:hover:bg-white/10 opacity-0 group-hover:opacity-40 hover:!opacity-100 cursor-pointer"
|
|
157
|
+
onClick={(e) => {
|
|
158
|
+
e.stopPropagation();
|
|
159
|
+
onTabClose(tab.id);
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
<X className="w-3 h-3" strokeWidth={1.5} />
|
|
163
|
+
</button>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
})}
|
|
167
|
+
<button
|
|
168
|
+
className="w-8 h-8 flex items-center justify-center rounded-md text-interactive hover:bg-stone-200/50 dark:hover:bg-white/[0.04] transition-all duration-150 focus:outline-none"
|
|
169
|
+
style={noDragStyle}
|
|
170
|
+
onClick={onNewTab}
|
|
171
|
+
>
|
|
172
|
+
<svg
|
|
173
|
+
className="w-4 h-4"
|
|
174
|
+
viewBox="0 0 16 16"
|
|
175
|
+
fill="none"
|
|
176
|
+
stroke="currentColor"
|
|
177
|
+
strokeWidth="1.5"
|
|
178
|
+
>
|
|
179
|
+
<path d="M8 3v10M3 8h10" />
|
|
180
|
+
</svg>
|
|
181
|
+
</button>
|
|
182
|
+
|
|
183
|
+
<div className="ml-auto" style={noDragStyle}>
|
|
184
|
+
<ThemeToggle darkMode={darkMode} onToggle={onThemeToggle} />
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|