UtilityActions.tsx 7.5 KB

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