mirror of
https://github.com/supabase/supabase.git
synced 2026-10-19 16:19:10 +03:00
Basic bulk delete query support
This commit is contained in:
1 parent
9a30c1029d
commit
b8b0066a39
5 files changed
+300
-157
No files matched your search
@@ -19,9 +19,7 @@ const ProductMenuBar = ({ title, children }: PropsWithChildren<ProductMenuBarPro
|
||||
>
|
||||
<h4 className="text-lg">{title}</h4>
|
||||
</div>
|
||||
<div className="flex-grow overflow-y-auto" style={{ maxHeight: 'calc(100vh - 96px)' }}>
|
||||
{children}
|
||||
</div>
|
||||
<div className="flex-grow overflow-y-auto">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import clsx from 'clsx'
|
||||
import { useParams } from 'common'
|
||||
import { noop } from 'lodash'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
AlertDescription_Shadcn_,
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
Checkbox_Shadcn_,
|
||||
IconAlertCircle,
|
||||
IconAlertTriangle,
|
||||
IconEye,
|
||||
@@ -30,10 +32,19 @@ import { QueryItemActions } from './QueryItemActions'
|
||||
|
||||
export interface QueryItemProps {
|
||||
tabInfo: SqlSnippet
|
||||
isSelected?: boolean
|
||||
hasQueriesSelected?: boolean
|
||||
onSelectQuery?: () => void
|
||||
onDeleteQuery?: (ids: string[]) => void
|
||||
}
|
||||
|
||||
const QueryItem = ({ tabInfo }: QueryItemProps) => {
|
||||
const router = useRouter()
|
||||
const QueryItem = ({
|
||||
tabInfo,
|
||||
isSelected = false,
|
||||
hasQueriesSelected = false,
|
||||
onSelectQuery = noop,
|
||||
onDeleteQuery = noop,
|
||||
}: QueryItemProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [renameModalOpen, setRenameModalOpen] = useState(false)
|
||||
const [shareModalOpen, setShareModalOpen] = useState(false)
|
||||
@@ -42,7 +53,7 @@ const QueryItem = ({ tabInfo }: QueryItemProps) => {
|
||||
|
||||
const { ref, id: activeId } = useParams()
|
||||
const snap = useSqlEditorStateSnapshot()
|
||||
const { id, name, description, content } = tabInfo || {}
|
||||
const { id, name, description, content, visibility } = tabInfo || {}
|
||||
const isActive = id === activeId
|
||||
const activeItemRef = useRef<HTMLElement | null>(null)
|
||||
|
||||
@@ -55,25 +66,10 @@ const QueryItem = ({ tabInfo }: QueryItemProps) => {
|
||||
isDownloadSnippetModalOpen
|
||||
|
||||
const { mutate: deleteContent, isLoading: isDeleting } = useContentDeleteMutation({
|
||||
onSuccess(data) {
|
||||
if (data.length > 0) snap.removeSnippet(data[0])
|
||||
|
||||
const existingSnippetIds = (snap.orders[ref!] ?? []).filter((x) => x !== id)
|
||||
if (existingSnippetIds.length === 0) {
|
||||
router.push(`/project/${ref}/sql/new`)
|
||||
} else {
|
||||
router.push(`/project/${ref}/sql/${existingSnippetIds[0]}`)
|
||||
}
|
||||
},
|
||||
onError(error, data) {
|
||||
onSuccess: (data) => onDeleteQuery(data),
|
||||
onError: (error, data) => {
|
||||
if (error.code === 404 && error.message.includes('Content not found')) {
|
||||
if (data.ids) snap.removeSnippet(data.ids[0])
|
||||
const existingSnippetIds = (snap.orders[ref!] ?? []).filter((x) => x !== id)
|
||||
if (existingSnippetIds.length === 0) {
|
||||
router.push(`/project/${ref}/sql/new`)
|
||||
} else {
|
||||
router.push(`/project/${ref}/sql/${existingSnippetIds[0]}`)
|
||||
}
|
||||
onDeleteQuery(data.ids)
|
||||
} else {
|
||||
toast.error(`Failed to delete query: ${error.message}`)
|
||||
}
|
||||
@@ -114,26 +110,42 @@ const QueryItem = ({ tabInfo }: QueryItemProps) => {
|
||||
<div
|
||||
key={id}
|
||||
className={clsx(
|
||||
'flex items-center justify-between rounded-md group',
|
||||
'h-7 pl-3 pr-2',
|
||||
'flex items-center justify-between rounded-md group relative',
|
||||
isActive ? 'bg-surface-300' : 'hover:bg-surface-200'
|
||||
)}
|
||||
ref={isActive ? (activeItemRef as React.RefObject<HTMLDivElement>) : null}
|
||||
>
|
||||
<Link href={`/project/${ref}/sql/${id}`} className="py-1 px-3 w-full overflow-hidden">
|
||||
<p
|
||||
title={description || name}
|
||||
{visibility === 'user' && (
|
||||
<Checkbox_Shadcn_
|
||||
className={clsx(
|
||||
'transition absolute left-2.5 border-foreground-lighter',
|
||||
hasQueriesSelected ? '' : 'opacity-0 group-hover:opacity-100'
|
||||
)}
|
||||
checked={isSelected}
|
||||
onCheckedChange={() => onSelectQuery()}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center justify-between w-full gap-x-2">
|
||||
<Link
|
||||
title={description || name}
|
||||
href={`/project/${ref}/sql/${id}`}
|
||||
className={clsx(
|
||||
'w-full overflow-hidden truncate',
|
||||
isActive
|
||||
? 'text-foreground'
|
||||
: 'text-foreground-light group-hover:text-foreground/80',
|
||||
'text-sm transition overflow-hidden text-ellipsis'
|
||||
'text-sm transition-all overflow-hidden text-ellipsis',
|
||||
hasQueriesSelected && visibility === 'user'
|
||||
? 'ml-5'
|
||||
: visibility === 'user'
|
||||
? 'group-hover:ml-5'
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
{name}
|
||||
</p>
|
||||
</Link>
|
||||
<div className="pr-1">
|
||||
{
|
||||
</Link>
|
||||
{!hasQueriesSelected && (
|
||||
<QueryItemActions
|
||||
tabInfo={tabInfo}
|
||||
activeId={activeId}
|
||||
@@ -144,7 +156,7 @@ const QueryItem = ({ tabInfo }: QueryItemProps) => {
|
||||
onSelectShareQuery={() => setShareModalOpen(true)}
|
||||
onSelectDownloadQuery={() => setIsDownloadSnippetModalOpen(true)}
|
||||
/>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</TooltipTrigger_Shadcn_>
|
||||
@@ -200,7 +212,7 @@ const QueryItem = ({ tabInfo }: QueryItemProps) => {
|
||||
<div className="my-6">
|
||||
<div className="text-sm text-foreground-light grid gap-4">
|
||||
<div className="grid gap-1">
|
||||
{tabInfo.visibility === 'project' && (
|
||||
{visibility === 'project' && (
|
||||
<Alert_Shadcn_ variant="destructive">
|
||||
<IconAlertCircle strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>This SQL snippet will be lost forever</AlertTitle_Shadcn_>
|
||||
|
||||
@@ -54,7 +54,6 @@ export const QueryItemActions = ({
|
||||
const project = useSelectedProject()
|
||||
|
||||
const { id, name, visibility, content } = tabInfo || {}
|
||||
const isActive = id === activeId
|
||||
|
||||
const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', {
|
||||
resource: { type: 'sql', owner_id: profile?.id },
|
||||
@@ -111,20 +110,13 @@ export const QueryItemActions = ({
|
||||
{IS_PLATFORM ? (
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<span
|
||||
tabIndex={0}
|
||||
onClick={() => setOpen(true)}
|
||||
className={clsx(
|
||||
'rounded p-1 cursor-pointer',
|
||||
isActive
|
||||
? 'text-foreground-light hover:bg-border-stronger'
|
||||
: 'text-background hover:bg-overlay-hover group-hover:text-foreground-light'
|
||||
)}
|
||||
>
|
||||
<IconChevronDown size="tiny" strokeWidth={2} />
|
||||
</span>
|
||||
<Button
|
||||
type="text"
|
||||
className="px-1 opacity-50 hover:opacity-100"
|
||||
icon={<IconChevronDown size="tiny" strokeWidth={2} />}
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="bottom" align="end" className=" w-52 translate-x-[4px]">
|
||||
<DropdownMenuContent side="bottom" align="end" className=" w-52 translate-x-2">
|
||||
{isSnippetOwner && (
|
||||
<DropdownMenuItem onClick={onClickRename} className="space-x-2">
|
||||
<IconEdit2 size="tiny" />
|
||||
|
||||
@@ -3,12 +3,31 @@ import { useParams } from 'common'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { Button, IconPlus, IconSearch, IconX, Input, Menu, cn } from 'ui'
|
||||
import toast from 'react-hot-toast'
|
||||
import {
|
||||
AlertDescription_Shadcn_,
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
Button,
|
||||
IconPlus,
|
||||
IconSearch,
|
||||
IconX,
|
||||
Input,
|
||||
Menu,
|
||||
Modal,
|
||||
Separator,
|
||||
TooltipContent_Shadcn_,
|
||||
TooltipTrigger_Shadcn_,
|
||||
Tooltip_Shadcn_,
|
||||
} from 'ui'
|
||||
|
||||
import { untitledSnippetTitle } from 'components/interfaces/SQLEditor/SQLEditor.constants'
|
||||
import { createSqlSnippetSkeleton } from 'components/interfaces/SQLEditor/SQLEditor.utils'
|
||||
import ConfirmationModal from 'components/ui/ConfirmationModal'
|
||||
import { WarningIcon } from 'components/ui/Icons'
|
||||
import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import { useContentDeleteMutation } from 'data/content/content-delete-mutation'
|
||||
import { SqlSnippet, useSqlSnippetsQuery } from 'data/content/sql-snippets-query'
|
||||
import { useCheckPermissions, useSelectedProject, useStore } from 'hooks'
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
@@ -18,11 +37,14 @@ import QueryItem from './QueryItem'
|
||||
|
||||
const SideBarContent = observer(() => {
|
||||
const { ui } = useStore()
|
||||
const { ref } = useParams()
|
||||
const { ref, id: activeId } = useParams()
|
||||
const router = useRouter()
|
||||
const { profile } = useProfile()
|
||||
const project = useSelectedProject()
|
||||
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||
const [selectedQueries, setSelectedQueries] = useState<string[]>([])
|
||||
|
||||
const snap = useSqlEditorStateSnapshot()
|
||||
const { isLoading, isSuccess } = useSqlSnippetsQuery(ref, {
|
||||
@@ -33,6 +55,29 @@ const SideBarContent = observer(() => {
|
||||
},
|
||||
})
|
||||
|
||||
const { mutate: deleteContent, isLoading: isDeleting } = useContentDeleteMutation({
|
||||
onSuccess: (data) => postDeleteCleanup(data),
|
||||
onError: (error, data) => {
|
||||
if (error.code === 404 && error.message.includes('Content not found')) {
|
||||
postDeleteCleanup(data.ids)
|
||||
} else {
|
||||
toast.error(`Failed to delete queries: ${error.message}`)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const postDeleteCleanup = (ids: string[]) => {
|
||||
setShowDeleteModal(false)
|
||||
setSelectedQueries([])
|
||||
if (ids.length > 0) ids.forEach((id) => snap.removeSnippet(id))
|
||||
const existingSnippetIds = (snap.orders[ref!] ?? []).filter((x) => !ids.includes(x))
|
||||
if (existingSnippetIds.length === 0) {
|
||||
router.push(`/project/${ref}/sql/new`)
|
||||
} else if (ids.includes(activeId as string)) {
|
||||
router.push(`/project/${ref}/sql/${existingSnippetIds[0]}`)
|
||||
}
|
||||
}
|
||||
|
||||
const snippets = useSnippets(ref)
|
||||
|
||||
const projectSnippets = useMemo(() => {
|
||||
@@ -77,6 +122,16 @@ const SideBarContent = observer(() => {
|
||||
subject: { id: profile?.id },
|
||||
})
|
||||
|
||||
const onSelectQuery = (id?: string) => {
|
||||
if (id !== undefined) {
|
||||
if (selectedQueries.includes(id)) {
|
||||
setSelectedQueries(selectedQueries.filter((x) => x !== id))
|
||||
} else {
|
||||
setSelectedQueries(selectedQueries.concat([id]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleNewQuery = async () => {
|
||||
if (!ref) return console.error('Project is required')
|
||||
if (!profile) return console.error('Profile is required')
|
||||
@@ -107,120 +162,206 @@ const SideBarContent = observer(() => {
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-6">
|
||||
<Menu type="pills">
|
||||
{isLoading ? (
|
||||
<div className="px-5 my-4 space-y-2">
|
||||
<ShimmeringLoader />
|
||||
<ShimmeringLoader className="w-3/4" />
|
||||
<ShimmeringLoader className="w-1/2" />
|
||||
</div>
|
||||
) : isSuccess ? (
|
||||
<div className="space-y-6">
|
||||
<div className="px-4 flex flex-col gap-2">
|
||||
<Button
|
||||
type="default"
|
||||
className="justify-start"
|
||||
onClick={() => handleNewQuery()}
|
||||
icon={<IconPlus size="tiny" />}
|
||||
>
|
||||
New query
|
||||
</Button>
|
||||
<Input
|
||||
className="table-editor-search border-none"
|
||||
icon={
|
||||
<IconSearch className="text-foreground-lighter" size={12} strokeWidth={1.5} />
|
||||
}
|
||||
placeholder="Search queries"
|
||||
onChange={(e) => setSearchText(e.target.value.trim())}
|
||||
value={searchText}
|
||||
size="tiny"
|
||||
actions={
|
||||
searchText && (
|
||||
<Button type="text" className="px-1" onClick={() => setSearchText('')}>
|
||||
<IconX size={12} strokeWidth={2} />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-6 px-4">
|
||||
{searchText.length === 0 && (
|
||||
<div>
|
||||
<ProductMenuItem
|
||||
name="Templates"
|
||||
isActive={router.asPath === `/project/${ref}/sql/templates`}
|
||||
url={`/project/${ref}/sql/templates`}
|
||||
/>
|
||||
<ProductMenuItem
|
||||
name="Quickstarts"
|
||||
isActive={router.asPath === `/project/${ref}/sql/quickstarts`}
|
||||
url={`/project/${ref}/sql/quickstarts`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
const onConfirmDelete = async () => {
|
||||
if (!ref) return console.error('Project ref is required')
|
||||
deleteContent({ projectRef: ref, ids: selectedQueries })
|
||||
}
|
||||
|
||||
{searchText.length > 0 &&
|
||||
filteredFavoriteSnippets.length === 0 &&
|
||||
filteredProjectSnippets.length === 0 &&
|
||||
personalSnippets.length === 0 && (
|
||||
<div className="h-32 border border-dashed flex flex-col gap-y-3 items-center justify-center rounded px-3">
|
||||
<div className="flex flex-col gap-y-1 items-center justify-center">
|
||||
<p className="text-xs text-foreground">No queries found</p>
|
||||
<p className="text-xs text-foreground-light text-center">
|
||||
Your search for "{searchText}" did not return any results
|
||||
</p>
|
||||
</div>
|
||||
<Button type="default" onClick={() => handleNewQuery()}>
|
||||
New query
|
||||
</Button>
|
||||
return (
|
||||
<>
|
||||
<div className="mt-6">
|
||||
<Menu type="pills">
|
||||
{isLoading ? (
|
||||
<div className="px-5 my-4 space-y-2">
|
||||
<ShimmeringLoader />
|
||||
<ShimmeringLoader className="w-3/4" />
|
||||
<ShimmeringLoader className="w-1/2" />
|
||||
</div>
|
||||
) : isSuccess ? (
|
||||
<div className="space-y-6">
|
||||
<div className="px-4 flex flex-col gap-2">
|
||||
<Button
|
||||
type="default"
|
||||
className="justify-start"
|
||||
onClick={() => handleNewQuery()}
|
||||
icon={<IconPlus size="tiny" />}
|
||||
>
|
||||
New query
|
||||
</Button>
|
||||
<Input
|
||||
className="table-editor-search border-none"
|
||||
icon={
|
||||
<IconSearch className="text-foreground-lighter" size={12} strokeWidth={1.5} />
|
||||
}
|
||||
placeholder="Search queries"
|
||||
onChange={(e) => setSearchText(e.target.value.trim())}
|
||||
value={searchText}
|
||||
size="tiny"
|
||||
actions={
|
||||
searchText && (
|
||||
<Button type="text" className="px-1" onClick={() => setSearchText('')}>
|
||||
<IconX size={12} strokeWidth={2} />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-6 px-2">
|
||||
{searchText.length === 0 && (
|
||||
<div>
|
||||
<ProductMenuItem
|
||||
name="Templates"
|
||||
isActive={router.asPath === `/project/${ref}/sql/templates`}
|
||||
url={`/project/${ref}/sql/templates`}
|
||||
/>
|
||||
<ProductMenuItem
|
||||
name="Quickstarts"
|
||||
isActive={router.asPath === `/project/${ref}/sql/quickstarts`}
|
||||
url={`/project/${ref}/sql/quickstarts`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filteredProjectSnippets.length > 0 ? (
|
||||
<div className="editor-product-menu">
|
||||
<Menu.Group title="Project queries" />
|
||||
{searchText.length > 0 &&
|
||||
filteredFavoriteSnippets.length === 0 &&
|
||||
filteredProjectSnippets.length === 0 &&
|
||||
personalSnippets.length === 0 && (
|
||||
<div className="h-32 border border-dashed flex flex-col gap-y-3 items-center justify-center rounded px-3">
|
||||
<div className="flex flex-col gap-y-1 items-center justify-center">
|
||||
<p className="text-xs text-foreground">No queries found</p>
|
||||
<p className="text-xs text-foreground-light text-center">
|
||||
Your search for "{searchText}" did not return any results
|
||||
</p>
|
||||
</div>
|
||||
<Button type="default" onClick={() => handleNewQuery()}>
|
||||
New query
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-1">
|
||||
{filteredProjectSnippets.map((tabInfo) => {
|
||||
const { id } = tabInfo || {}
|
||||
return <QueryItem key={id} tabInfo={tabInfo} />
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{filteredProjectSnippets.length > 0 ? (
|
||||
<div className="editor-product-menu">
|
||||
<Menu.Group title="Project queries" />
|
||||
|
||||
{filteredFavoriteSnippets.length > 0 ? (
|
||||
<div className="editor-product-menu">
|
||||
<Menu.Group title="Favorites" />
|
||||
<div className="space-y-1">
|
||||
{filteredFavoriteSnippets.map((tabInfo) => {
|
||||
const { id } = tabInfo || {}
|
||||
return <QueryItem key={id} tabInfo={tabInfo} />
|
||||
})}
|
||||
<div className="space-y-1">
|
||||
{filteredProjectSnippets.map((tabInfo) => {
|
||||
const { id } = tabInfo || {}
|
||||
return (
|
||||
<QueryItem
|
||||
key={id}
|
||||
tabInfo={tabInfo}
|
||||
hasQueriesSelected={selectedQueries.length > 0}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
) : null}
|
||||
|
||||
{personalSnippets.length > 0 ? (
|
||||
<div className="editor-product-menu">
|
||||
<Menu.Group title="Your queries" />
|
||||
<div className="space-y-1 pb-8">
|
||||
{personalSnippets.map((tabInfo) => {
|
||||
const { id } = tabInfo || {}
|
||||
return <QueryItem key={id} tabInfo={tabInfo} />
|
||||
})}
|
||||
{selectedQueries.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="px-2 flex items-center gap-x-2">
|
||||
<Button block type="default" onClick={() => setShowDeleteModal(true)}>
|
||||
Delete {selectedQueries.length.toLocaleString()} quer
|
||||
{selectedQueries.length > 1 ? 'ies' : 'y'}
|
||||
</Button>
|
||||
<Tooltip_Shadcn_ delayDuration={100}>
|
||||
<TooltipTrigger_Shadcn_ asChild>
|
||||
<Button
|
||||
type="default"
|
||||
className="px-1"
|
||||
icon={<IconX />}
|
||||
onClick={() => setSelectedQueries([])}
|
||||
/>
|
||||
</TooltipTrigger_Shadcn_>
|
||||
<TooltipContent_Shadcn_ side="bottom">
|
||||
Clear selection
|
||||
</TooltipContent_Shadcn_>
|
||||
</Tooltip_Shadcn_>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{filteredFavoriteSnippets.length > 0 ? (
|
||||
<div className="editor-product-menu">
|
||||
<Menu.Group title="Favorites" />
|
||||
<div className="space-y-1">
|
||||
{filteredFavoriteSnippets.map((tabInfo) => {
|
||||
const { id } = tabInfo || {}
|
||||
return (
|
||||
<QueryItem
|
||||
key={id}
|
||||
tabInfo={tabInfo}
|
||||
isSelected={selectedQueries.includes(id as string)}
|
||||
hasQueriesSelected={selectedQueries.length > 0}
|
||||
onSelectQuery={() => onSelectQuery(id)}
|
||||
onDeleteQuery={postDeleteCleanup}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
) : null}
|
||||
|
||||
{personalSnippets.length > 0 ? (
|
||||
<div className="editor-product-menu">
|
||||
<Menu.Group title="Your queries" />
|
||||
<div className="space-y-1 pb-8">
|
||||
{personalSnippets.map((tabInfo) => {
|
||||
const { id } = tabInfo || {}
|
||||
return (
|
||||
<QueryItem
|
||||
key={id}
|
||||
tabInfo={tabInfo}
|
||||
isSelected={selectedQueries.includes(id as string)}
|
||||
hasQueriesSelected={selectedQueries.length > 0}
|
||||
onSelectQuery={() => onSelectQuery(id)}
|
||||
onDeleteQuery={postDeleteCleanup}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div></div>
|
||||
)}
|
||||
</Menu>
|
||||
</div>
|
||||
<ConfirmationModal
|
||||
header="Confirm to delete query"
|
||||
buttonLabel="Delete query"
|
||||
buttonLoadingLabel="Deleting query"
|
||||
size="medium"
|
||||
loading={isDeleting}
|
||||
visible={showDeleteModal}
|
||||
onSelectConfirm={onConfirmDelete}
|
||||
onSelectCancel={() => setShowDeleteModal(false)}
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="my-6">
|
||||
<div className="text-sm text-foreground-light grid gap-4">
|
||||
<div className="grid gap-y-4">
|
||||
<Alert_Shadcn_ variant="destructive">
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>This action cannot be undone</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
The selected SQL snippets cannot be recovered once deleted
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
<p>
|
||||
Are you sure you want to delete the selected {selectedQueries.length} quer
|
||||
{selectedQueries.length > 1 ? 'ies' : 'y'}?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div></div>
|
||||
)}
|
||||
</Menu>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</ConfirmationModal>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
+3
-3
@@ -13,13 +13,13 @@ const FileExplorerHeaderSelection = () => {
|
||||
} = storageExplorerStore
|
||||
|
||||
return (
|
||||
<div className="z-10 flex h-[40px] items-center rounded-t-md bg-brand-400 px-2 py-1 shadow [[data-theme*=dark]_&]:bg-brand-600">
|
||||
<div className="z-10 flex h-[40px] items-center rounded-t-md bg-brand-400 px-2 py-1 shadow [[data-theme*=dark]_&]:bg-brand-500">
|
||||
<Button
|
||||
icon={<IconX size={16} strokeWidth={2} />}
|
||||
type="text"
|
||||
onClick={() => clearSelectedItems()}
|
||||
/>
|
||||
<div className="ml-4 flex items-center space-x-3">
|
||||
<div className="ml-1 flex items-center space-x-3">
|
||||
<p className="mb-0 text-sm text-foreground">
|
||||
<span style={{ fontVariantNumeric: 'tabular-nums' }}>{selectedItems.length}</span> items
|
||||
selected
|
||||
@@ -37,7 +37,7 @@ const FileExplorerHeaderSelection = () => {
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
<div className="border-r border-green-800 py-3 opacity-50" />
|
||||
<div className="border-r border-green-900 py-3 opacity-50" />
|
||||
<Button
|
||||
icon={<IconTrash2 size={16} strokeWidth={2} />}
|
||||
type="primary"
|
||||
|
||||
Reference in new issue
Block a user