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.
Files changed (69) hide show
  1. package/README.md +73 -0
  2. package/bin/cli.js +83 -0
  3. package/bin/install-local.js +57 -0
  4. package/electron/generate-icon.mjs +54 -0
  5. package/electron/icon.icns +0 -0
  6. package/electron/icon.png +0 -0
  7. package/electron/icon.svg +21 -0
  8. package/electron/main.js +169 -0
  9. package/electron/patch-dev-plist.js +31 -0
  10. package/electron/preload.cjs +18 -0
  11. package/electron/wait-for-vite.js +43 -0
  12. package/index.html +13 -0
  13. package/package.json +91 -0
  14. package/public/favicon.svg +15 -0
  15. package/public/vite.svg +1 -0
  16. package/server/export.ts +57 -0
  17. package/server/index.ts +392 -0
  18. package/src/App.css +1 -0
  19. package/src/App.tsx +543 -0
  20. package/src/assets/react.svg +1 -0
  21. package/src/components/CommandPalette.tsx +243 -0
  22. package/src/components/ConnectedView.tsx +78 -0
  23. package/src/components/ConnectionPicker.tsx +381 -0
  24. package/src/components/ConsoleView.tsx +360 -0
  25. package/src/components/CsvExportModal.tsx +144 -0
  26. package/src/components/DataGrid/DataGrid.tsx +262 -0
  27. package/src/components/DataGrid/DataGridCell.tsx +73 -0
  28. package/src/components/DataGrid/DataGridHeader.tsx +89 -0
  29. package/src/components/DataGrid/index.ts +20 -0
  30. package/src/components/DataGrid/types.ts +63 -0
  31. package/src/components/DataGrid/useColumnResize.ts +153 -0
  32. package/src/components/DataGrid/useDataGridSelection.ts +340 -0
  33. package/src/components/DataGrid/utils.ts +184 -0
  34. package/src/components/DatabaseMenu.tsx +93 -0
  35. package/src/components/DatabaseSwitcher.tsx +208 -0
  36. package/src/components/DiffView.tsx +215 -0
  37. package/src/components/EditConnectionModal.tsx +417 -0
  38. package/src/components/ErrorBoundary.tsx +69 -0
  39. package/src/components/GlobalShortcuts.tsx +201 -0
  40. package/src/components/InnerTabBar.tsx +129 -0
  41. package/src/components/JsonTreeViewer.tsx +387 -0
  42. package/src/components/MemberAccessEditor.tsx +443 -0
  43. package/src/components/MembersModal.tsx +446 -0
  44. package/src/components/NewConnectionModal.tsx +274 -0
  45. package/src/components/Resizer.tsx +66 -0
  46. package/src/components/ScanSuccessModal.tsx +113 -0
  47. package/src/components/ShortcutSettingsModal.tsx +318 -0
  48. package/src/components/Sidebar.tsx +532 -0
  49. package/src/components/TabBar.tsx +188 -0
  50. package/src/components/TableView.tsx +2147 -0
  51. package/src/components/ThemeToggle.tsx +44 -0
  52. package/src/components/index.ts +17 -0
  53. package/src/constants.ts +12 -0
  54. package/src/electron.d.ts +12 -0
  55. package/src/index.css +44 -0
  56. package/src/main.tsx +13 -0
  57. package/src/stores/hooks.ts +1146 -0
  58. package/src/stores/index.ts +12 -0
  59. package/src/stores/store.ts +1514 -0
  60. package/src/stores/useCloudSync.ts +274 -0
  61. package/src/stores/useSyncDatabase.ts +422 -0
  62. package/src/types.ts +277 -0
  63. package/src/utils/csv.ts +27 -0
  64. package/src/vite-env.d.ts +2 -0
  65. package/tsconfig.app.json +28 -0
  66. package/tsconfig.json +7 -0
  67. package/tsconfig.node.json +26 -0
  68. package/tsconfig.server.json +14 -0
  69. 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
+ }