UtilityActions.tsx221 lines · main
| 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 | |
| 19 | import { SqlRunButton } from './RunButton' |
| 20 | import SavingIndicator from './SavingIndicator' |
| 21 | import { RoleImpersonationPopover } from '@/components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover' |
| 22 | import { DatabaseSelector } from '@/components/ui/DatabaseSelector' |
| 23 | import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' |
| 24 | import { IS_PLATFORM } from '@/lib/constants' |
| 25 | import { hotkeyToKeys } from '@/state/shortcuts/formatShortcut' |
| 26 | import { SHORTCUT_DEFINITIONS, SHORTCUT_IDS } from '@/state/shortcuts/registry' |
| 27 | import { useSqlEditorV2StateSnapshot } from '@/state/sql-editor-v2' |
| 28 | |
| 29 | export type UtilityActionsProps = { |
| 30 | id: string |
| 31 | isExecuting?: boolean |
| 32 | isDisabled?: boolean |
| 33 | hasSelection?: boolean |
| 34 | prettifyQuery: () => void |
| 35 | executeQuery: () => void |
| 36 | } |
| 37 | |
| 38 | export 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 | } |