UtilityActions.tsx221 lines · main
1import { Hotkey } from '@tanstack/react-hotkeys'
2import { LOCAL_STORAGE_KEYS, useParams } from 'common'
3import { AlignLeft, Check, Heart, Keyboard, MoreVertical } from 'lucide-react'
4import { toast } from 'sonner'
5import {
6 Button,
7 cn,
8 DropdownMenu,
9 DropdownMenuContent,
10 DropdownMenuItem,
11 DropdownMenuSeparator,
12 DropdownMenuTrigger,
13 KeyboardShortcut,
14 Tooltip,
15 TooltipContent,
16 TooltipTrigger,
17} from 'ui'
18
19import { SqlRunButton } from './RunButton'
20import SavingIndicator from './SavingIndicator'
21import { RoleImpersonationPopover } from '@/components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover'
22import { DatabaseSelector } from '@/components/ui/DatabaseSelector'
23import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
24import { IS_PLATFORM } from '@/lib/constants'
25import { hotkeyToKeys } from '@/state/shortcuts/formatShortcut'
26import { SHORTCUT_DEFINITIONS, SHORTCUT_IDS } from '@/state/shortcuts/registry'
27import { useSqlEditorV2StateSnapshot } from '@/state/sql-editor-v2'
28
29export type UtilityActionsProps = {
30 id: string
31 isExecuting?: boolean
32 isDisabled?: boolean
33 hasSelection?: boolean
34 prettifyQuery: () => void
35 executeQuery: () => void
36}
37
38export const UtilityActions = ({
39 id,
40 isExecuting = false,
41 isDisabled = false,
42 hasSelection = false,
43 prettifyQuery,
44 executeQuery,
45}: UtilityActionsProps) => {
46 const { ref } = useParams()
47 const snapV2 = useSqlEditorV2StateSnapshot()
48
49 const [isAiOpen] = useLocalStorageQuery(LOCAL_STORAGE_KEYS.SQL_EDITOR_AI_OPEN, true)
50 const [intellisenseEnabled, setIntellisenseEnabled] = useLocalStorageQuery(
51 LOCAL_STORAGE_KEYS.SQL_EDITOR_INTELLISENSE,
52 true
53 )
54 const [lastSelectedDb, setLastSelectedDb] = useLocalStorageQuery(
55 LOCAL_STORAGE_KEYS.SQL_EDITOR_LAST_SELECTED_DB(ref as string),
56 ''
57 )
58
59 const snippet = snapV2.snippets[id]
60 const isFavorite = snippet !== undefined ? snippet.snippet.favorite : false
61
62 const hotkeySequnece: Hotkey | undefined =
63 SHORTCUT_DEFINITIONS[SHORTCUT_IDS.SQL_EDITOR_FORMAT].sequence[0]
64 const formatKeys = hotkeySequnece ? hotkeyToKeys(hotkeySequnece) : undefined
65
66 const toggleIntellisense = () => {
67 setIntellisenseEnabled(!intellisenseEnabled)
68 toast.success(
69 `Successfully ${intellisenseEnabled ? 'disabled' : 'enabled'} intellisense. ${intellisenseEnabled ? 'Please refresh your browser for changes to take place.' : ''}`
70 )
71 }
72
73 const addFavorite = () => snapV2.addFavorite(id)
74
75 const removeFavorite = () => snapV2.removeFavorite(id)
76
77 const onSelectDatabase = (databaseId: string) => {
78 snapV2.resetResults(id)
79 setLastSelectedDb(databaseId)
80 }
81
82 return (
83 <div className="inline-flex items-center justify-end gap-x-2">
84 {IS_PLATFORM && <SavingIndicator id={id} />}
85
86 <DropdownMenu>
87 <DropdownMenuTrigger asChild>
88 <Button
89 data-testid="sql-editor-utility-actions"
90 type="default"
91 className={cn('px-1', isAiOpen ? 'block 2xl:hidden' : 'hidden')}
92 icon={<MoreVertical className="text-foreground-light" />}
93 />
94 </DropdownMenuTrigger>
95 <DropdownMenuContent className="w-48">
96 <DropdownMenuItem className="justify-between" onClick={toggleIntellisense}>
97 <span className="flex items-center gap-x-2">
98 <Keyboard size={14} className="text-foreground-light" />
99 Intellisense enabled
100 </span>
101 {intellisenseEnabled && <Check className="text-brand" size={16} />}
102 </DropdownMenuItem>
103 {IS_PLATFORM && (
104 <>
105 <DropdownMenuSeparator />
106 <DropdownMenuItem
107 className="gap-x-2"
108 onClick={() => {
109 if (isFavorite) removeFavorite()
110 else addFavorite()
111 }}
112 >
113 <Heart
114 size={14}
115 strokeWidth={2}
116 className={
117 isFavorite ? 'fill-brand stroke-none' : 'fill-none stroke-foreground-light'
118 }
119 />
120 {isFavorite ? 'Remove from' : 'Add to'} favorites
121 </DropdownMenuItem>
122 </>
123 )}
124 <DropdownMenuItem className="justify-between" onClick={prettifyQuery}>
125 <span className="flex items-center gap-x-2">
126 <AlignLeft size={14} strokeWidth={2} className="text-foreground-light" />
127 Prettify SQL
128 </span>
129 {formatKeys && <KeyboardShortcut keys={formatKeys} />}
130 </DropdownMenuItem>
131 </DropdownMenuContent>
132 </DropdownMenu>
133
134 <div className={cn('items-center gap-x-2', isAiOpen ? 'hidden 2xl:flex' : 'flex')}>
135 <DropdownMenu>
136 <DropdownMenuTrigger asChild>
137 <Button
138 type="text"
139 className="px-1"
140 icon={<Keyboard className="text-foreground-light" />}
141 />
142 </DropdownMenuTrigger>
143 <DropdownMenuContent className="w-48">
144 <DropdownMenuItem className="justify-between" onClick={toggleIntellisense}>
145 Intellisense enabled
146 {intellisenseEnabled && <Check className="text-brand" size={16} />}
147 </DropdownMenuItem>
148 </DropdownMenuContent>
149 </DropdownMenu>
150
151 {IS_PLATFORM && (
152 <Tooltip>
153 <TooltipTrigger asChild>
154 {isFavorite ? (
155 <Button
156 type="text"
157 size="tiny"
158 onClick={removeFavorite}
159 className="px-1"
160 icon={<Heart className="fill-brand stroke-none" />}
161 />
162 ) : (
163 <Button
164 type="text"
165 size="tiny"
166 onClick={addFavorite}
167 className="px-1"
168 icon={<Heart className="fill-none stroke-foreground-light" />}
169 />
170 )}
171 </TooltipTrigger>
172 <TooltipContent side="bottom">
173 {isFavorite ? 'Remove from' : 'Add to'} favorites
174 </TooltipContent>
175 </Tooltip>
176 )}
177
178 <Tooltip>
179 <TooltipTrigger asChild>
180 <Button
181 type="text"
182 onClick={prettifyQuery}
183 className="px-1"
184 icon={<AlignLeft strokeWidth={2} className="text-foreground-light" />}
185 />
186 </TooltipTrigger>
187 <TooltipContent side="bottom" className="p-1 pl-2.5">
188 <div className="flex items-center gap-2.5">
189 <span>Prettify SQL</span>
190 {formatKeys && <KeyboardShortcut keys={formatKeys} />}
191 </div>
192 </TooltipContent>
193 </Tooltip>
194 </div>
195
196 <div className="flex items-center justify-between gap-x-2">
197 <div className="flex items-center">
198 {IS_PLATFORM && (
199 <DatabaseSelector
200 selectedDatabaseId={lastSelectedDb.length === 0 ? undefined : lastSelectedDb}
201 variant="connected-on-right"
202 onSelectId={onSelectDatabase}
203 />
204 )}
205 <RoleImpersonationPopover
206 serviceRoleLabel="postgres"
207 header="Run SQL query as a role"
208 variant={IS_PLATFORM ? 'connected-on-both' : 'connected-on-right'}
209 />
210 <SqlRunButton
211 hasSelection={hasSelection}
212 isDisabled={isDisabled || isExecuting}
213 isExecuting={isExecuting}
214 className="rounded-l-none"
215 onClick={executeQuery}
216 />
217 </div>
218 </div>
219 </div>
220 )
221}