diff --git a/apps/database-new/tsconfig.json b/apps/database-new/tsconfig.json index 2b727367ee7..881d250a669 100644 --- a/apps/database-new/tsconfig.json +++ b/apps/database-new/tsconfig.json @@ -15,15 +15,15 @@ "incremental": true, "plugins": [ { - "name": "next", - }, + "name": "next" + } ], "paths": { "@/*": ["./*"], // handle ui package paths - "@ui/*": ["./../../packages/ui/src/*"], - }, + "@ui/*": ["./../../packages/ui/src/*"] + } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"], + "exclude": ["node_modules"] } diff --git a/apps/docs/generator/tsconfig.json b/apps/docs/generator/tsconfig.json index 457c226ceae..8615cb5639e 100644 --- a/apps/docs/generator/tsconfig.json +++ b/apps/docs/generator/tsconfig.json @@ -5,9 +5,9 @@ "noImplicitAny": false, "baseUrl": ".", "paths": { - "~/*": ["./*"], - }, + "~/*": ["./*"] + } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"], + "exclude": ["node_modules"] } diff --git a/apps/docs/internals/tsconfig.json b/apps/docs/internals/tsconfig.json index 3f7e8747893..de1ecb67a32 100644 --- a/apps/docs/internals/tsconfig.json +++ b/apps/docs/internals/tsconfig.json @@ -7,9 +7,9 @@ "resolveJsonModule": true, "baseUrl": ".", "paths": { - "~/*": ["./*"], - }, + "~/*": ["./*"] + } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"], + "exclude": ["node_modules"] } diff --git a/apps/docs/scripts/tsconfig.json b/apps/docs/scripts/tsconfig.json index 3f7e8747893..de1ecb67a32 100644 --- a/apps/docs/scripts/tsconfig.json +++ b/apps/docs/scripts/tsconfig.json @@ -7,9 +7,9 @@ "resolveJsonModule": true, "baseUrl": ".", "paths": { - "~/*": ["./*"], - }, + "~/*": ["./*"] + } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"], + "exclude": ["node_modules"] } diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json index 8a55df06d90..cbc90dcb0d5 100644 --- a/apps/docs/tsconfig.json +++ b/apps/docs/tsconfig.json @@ -5,7 +5,7 @@ "baseUrl": ".", "paths": { "~/*": ["./*"], - "@ui/*": ["./../../packages/ui/src/*"], // handle ui package paths + "@ui/*": ["./../../packages/ui/src/*"] // handle ui package paths }, "target": "es2015", "lib": ["dom", "dom.iterable", "esnext"], @@ -19,14 +19,14 @@ "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", - "module": "esnext", + "module": "esnext" }, "include": [ "next-env.d.ts", "**/*.ts", "**/*.tsx", "pages/guides/append-test.js", - "jest.config.mjs", + "jest.config.mjs" ], - "exclude": ["node_modules"], + "exclude": ["node_modules"] } diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.SavedQueriesItem.tsx b/apps/studio/components/interfaces/Settings/Logs/Logs.SavedQueriesItem.tsx index c369d26505f..e2dfae21c65 100644 --- a/apps/studio/components/interfaces/Settings/Logs/Logs.SavedQueriesItem.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/Logs.SavedQueriesItem.tsx @@ -44,7 +44,7 @@ const SavedQueriesItem = ({ item }: SavedQueriesItemProps) => { console.error('Invalid project reference') return } - await deleteContent({ projectRef: ref, ids: [item.id] }) + await deleteContent({ projectRef: ref, id: item.id }) setShowConfirmModal(false) toast.success('Query deleted') } catch (error) { diff --git a/apps/studio/components/layouts/ProjectLayout/ProductMenuBar.tsx b/apps/studio/components/layouts/ProjectLayout/ProductMenuBar.tsx index 8c0c63f9956..9ef6665a7ad 100644 --- a/apps/studio/components/layouts/ProjectLayout/ProductMenuBar.tsx +++ b/apps/studio/components/layouts/ProjectLayout/ProductMenuBar.tsx @@ -9,8 +9,8 @@ const ProductMenuBar = ({ title, children }: PropsWithChildren

{title}

-
{children}
+
+ {children} +
) } diff --git a/apps/studio/components/layouts/SQLEditorLayout/QueryItem.tsx b/apps/studio/components/layouts/SQLEditorLayout/QueryItem.tsx index df50924b80d..32218fbf595 100644 --- a/apps/studio/components/layouts/SQLEditorLayout/QueryItem.tsx +++ b/apps/studio/components/layouts/SQLEditorLayout/QueryItem.tsx @@ -1,19 +1,29 @@ +import { PermissionAction } from '@supabase/shared-types/out/constants' import clsx from 'clsx' import { useParams } from 'common' -import { noop } from 'lodash' +import { observer } from 'mobx-react-lite' import Link from 'next/link' import { useRouter } from 'next/router' import { useEffect, useRef, useState } from 'react' -import toast from 'react-hot-toast' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, - Checkbox, - Checkbox_Shadcn_, + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, IconAlertCircle, IconAlertTriangle, + IconChevronDown, + IconCopy, + IconDownload, + IconEdit2, IconEye, + IconShare, + IconTrash, IconUnlock, Modal, TooltipContent_Shadcn_, @@ -23,66 +33,28 @@ import { import DownloadSnippetModal from 'components/interfaces/SQLEditor/DownloadSnippetModal' import RenameQueryModal from 'components/interfaces/SQLEditor/RenameQueryModal' +import { createSqlSnippetSkeleton } from 'components/interfaces/SQLEditor/SQLEditor.utils' import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' import ConfirmationModal from 'components/ui/ConfirmationModal' import CopyButton from 'components/ui/CopyButton' import { useContentDeleteMutation } from 'data/content/content-delete-mutation' import { SqlSnippet } from 'data/content/sql-snippets-query' +import { useCheckPermissions, useSelectedProject, useStore } from 'hooks' +import { IS_PLATFORM } from 'lib/constants' +import { uuidv4 } from 'lib/helpers' +import { useProfile } from 'lib/profile' import { useSqlEditorStateSnapshot } from 'state/sql-editor' -import { QueryItemActions } from './QueryItemActions' export interface QueryItemProps { tabInfo: SqlSnippet - isSelected?: boolean - hasQueriesSelected?: boolean - onSelectQuery?: (isShiftHeld: boolean) => void - onDeleteQuery?: (ids: string[]) => void } -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) - const [deleteModalOpen, setDeleteModalOpen] = useState(false) - const [isDownloadSnippetModalOpen, setIsDownloadSnippetModalOpen] = useState(false) - +const QueryItem = ({ tabInfo }: QueryItemProps) => { const { ref, id: activeId } = useParams() - const snap = useSqlEditorStateSnapshot() - const { id, name, description, content, visibility } = tabInfo || {} + const { id, name, description, content } = tabInfo || {} const isActive = id === activeId const activeItemRef = useRef(null) - const hideTooltip = - open || - isActive || - renameModalOpen || - shareModalOpen || - deleteModalOpen || - isDownloadSnippetModalOpen - - const { mutate: deleteContent, isLoading: isDeleting } = useContentDeleteMutation({ - onSuccess: (data) => onDeleteQuery(data), - onError: (error, data) => { - if (error.code === 404 && error.message.includes('Content not found')) { - onDeleteQuery(data.ids) - } else { - toast.error(`Failed to delete query: ${error.message}`) - } - }, - }) - - const onConfirmDelete = async () => { - if (!ref) return console.error('Project ref is required') - if (!id) return console.error('Snippet ID is required') - deleteContent({ projectRef: ref, ids: [id] }) - } - useEffect(() => { // scroll to active item if (isActive && activeItemRef.current) { @@ -93,113 +65,262 @@ const QueryItem = ({ } }) + return ( + + +
) : null} + > + +

+ {name} +

+ +
{}
+
+
+ {!isActive && ( + +

Query preview:

+
+ {content.sql.trim() ? ( + + {content.sql.replaceAll('\n', ' ').replaceAll(/\s+/g, ' ').slice(0, 43) + + `${content.sql.length > 43 ? '...' : ''}`} + + ) : ( +

This query is empty

+ )} + {content.sql.trim() && ( + + )} +
+
+ )} +
+ ) +} + +export default QueryItem + +interface QueryItemActionsProps { + tabInfo: SqlSnippet + activeId: string | undefined +} + +const QueryItemActions = observer(({ tabInfo, activeId }: QueryItemActionsProps) => { + const { ui } = useStore() + const { ref } = useParams() + const router = useRouter() + const { profile } = useProfile() + + const snap = useSqlEditorStateSnapshot() + const project = useSelectedProject() + + const { id: snippetID } = tabInfo || {} + const snippet = + snippetID !== undefined && snap.snippets && snap.snippets[snippetID] !== undefined + ? snap.snippets[snippetID] + : null + + const isSnippetOwner = profile?.id === snippet?.snippet.owner_id + + const { mutate: deleteContent, isLoading: isDeleting } = useContentDeleteMutation({ + onSuccess(data) { + if (data.id) snap.removeSnippet(data.id) + + 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) { + if (error.code === 404 && error.message.includes('Content not found')) { + if (data.id) snap.removeSnippet(data.id) + 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]}`) + } + } else { + ui.setNotification({ + category: 'error', + message: `Failed to delete query: ${error.message}`, + }) + } + }, + }) + + const { id, name, visibility, content } = tabInfo || {} + const [renameModalOpen, setRenameModalOpen] = useState(false) + const [shareModalOpen, setShareModalOpen] = useState(false) + const [deleteModalOpen, setDeleteModalOpen] = useState(false) + const [isDownloadSnippetModalOpen, setIsDownloadSnippetModalOpen] = useState(false) + const isActive = id === activeId + + const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', { + resource: { type: 'sql', owner_id: profile?.id }, + subject: { id: profile?.id }, + }) + + const onCloseRenameModal = () => { + setRenameModalOpen(false) + } + + const onClickRename = (e: any) => { + e.stopPropagation() + setRenameModalOpen(true) + } + + const onClickShare = (e: any) => { + e.stopPropagation() + setShareModalOpen(true) + } + + const onClickDelete = (e: any) => { + e.stopPropagation() + setDeleteModalOpen(true) + } + const onConfirmShare = async () => { if (id) { try { snap.shareSnippet(id, 'project') return Promise.resolve() } catch (error: any) { - toast.error(`Failed to share query: ${error.message}`) + ui.setNotification({ + error, + category: 'error', + message: `Failed to share query: ${error.message}`, + }) } } } + const onConfirmDelete = async () => { + if (!ref) return console.error('Project ref is required') + if (!id) return console.error('Snippet ID is required') + deleteContent({ projectRef: ref, id }) + } + + const createPersonalCopy = async () => { + if (!ref) return console.error('Project ref is required') + if (!id) return console.error('Snippet ID is required') + try { + const snippet = createSqlSnippetSkeleton({ + id: uuidv4(), + name, + sql: content.sql, + owner_id: profile?.id, + project_id: project?.id, + }) + snap.addSnippet(snippet as SqlSnippet, ref) + snap.addNeedsSaving(snippet.id!) + router.push(`/project/${ref}/sql/${snippet.id}`) + } catch (error: any) { + ui.setNotification({ + category: 'error', + message: `Failed to create a personal copy of this query: ${error.message}`, + }) + } + } + return ( - <> - - -
+ {IS_PLATFORM ? ( + + + + + + + + {isSnippetOwner && ( + + +

Rename query

+
)} - ref={isActive ? (activeItemRef as React.RefObject) : null} - > - {visibility === 'user' && ( - input]:border-foreground-lighter', - hasQueriesSelected ? '' : 'opacity-0 group-hover:opacity-100' - )} - checked={isSelected} - onChange={(event) => { - onSelectQuery((event.nativeEvent as KeyboardEvent).shiftKey) - }} - /> + + {visibility === 'user' && canCreateSQLSnippet && ( + + +

Share query

+
)} -
- + +

Duplicate personal copy

+ + )} + + {IS_PLATFORM && ( + setIsDownloadSnippetModalOpen(!isDownloadSnippetModalOpen)} + className="space-x-2" > - {name} - - {!hasQueriesSelected && ( - setDeleteModalOpen(true)} - onSelectRenameQuery={() => setRenameModalOpen(true)} - onSelectShareQuery={() => setShareModalOpen(true)} - onSelectDownloadQuery={() => setIsDownloadSnippetModalOpen(true)} - /> - )} -
-
-
- {!hideTooltip && ( - -

Query preview:

-
- {content.sql.trim() ? ( - - {content.sql.replaceAll('\n', ' ').replaceAll(/\s+/g, ' ').slice(0, 43) + - `${content.sql.length > 43 ? '...' : ''}`} - - ) : ( -

This query is empty

- )} - {content.sql.trim() && ( - - )} -
-
- )} -
+ +

Download as migration file

+ + )} + {isSnippetOwner && ( + <> + + + +

Delete query

+
+ + )} + + + ) : ( + + )} setRenameModalOpen(false)} - onComplete={() => setRenameModalOpen(false)} + onCancel={onCloseRenameModal} + onComplete={onCloseRenameModal} /> setIsDownloadSnippetModalOpen(false)} /> - + ) -} - -export default QueryItem +}) diff --git a/apps/studio/components/layouts/SQLEditorLayout/QueryItemActions.tsx b/apps/studio/components/layouts/SQLEditorLayout/QueryItemActions.tsx deleted file mode 100644 index a01b5a7c930..00000000000 --- a/apps/studio/components/layouts/SQLEditorLayout/QueryItemActions.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' -import clsx from 'clsx' -import { IS_PLATFORM, useParams } from 'common' -import { useRouter } from 'next/router' -import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, - IconChevronDown, - IconCopy, - IconDownload, - IconEdit2, - IconShare, - IconTrash, -} from 'ui' - -import { createSqlSnippetSkeleton } from 'components/interfaces/SQLEditor/SQLEditor.utils' -import { SqlSnippet } from 'data/content/sql-snippets-query' -import { useCheckPermissions, useSelectedProject, useStore } from 'hooks' -import { uuidv4 } from 'lib/helpers' -import { useProfile } from 'lib/profile' -import { useSqlEditorStateSnapshot } from 'state/sql-editor' - -interface QueryItemActionsProps { - tabInfo: SqlSnippet - activeId: string | undefined - open: boolean - setOpen: (value: boolean) => void - onSelectRenameQuery: () => void - onSelectDeleteQuery: () => void - onSelectShareQuery: () => void - onSelectDownloadQuery: () => void -} - -export const QueryItemActions = ({ - tabInfo, - activeId, - open, - setOpen, - onSelectRenameQuery, - onSelectDeleteQuery, - onSelectShareQuery, - onSelectDownloadQuery, -}: QueryItemActionsProps) => { - const { ui } = useStore() - const { ref } = useParams() - const router = useRouter() - const { profile } = useProfile() - - const snap = useSqlEditorStateSnapshot() - const project = useSelectedProject() - - const { id, name, visibility, content } = tabInfo || {} - - const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', { - resource: { type: 'sql', owner_id: profile?.id }, - subject: { id: profile?.id }, - }) - - const { id: snippetID } = tabInfo || {} - const snippet = - snippetID !== undefined && snap.snippets && snap.snippets[snippetID] !== undefined - ? snap.snippets[snippetID] - : null - - const isSnippetOwner = profile?.id === snippet?.snippet.owner_id - - const onClickRename = (e: any) => { - e.stopPropagation() - onSelectRenameQuery() - } - - const onClickShare = (e: any) => { - e.stopPropagation() - onSelectShareQuery() - } - - const onClickDelete = (e: any) => { - e.stopPropagation() - onSelectDeleteQuery() - } - - const createPersonalCopy = async () => { - if (!ref) return console.error('Project ref is required') - if (!id) return console.error('Snippet ID is required') - try { - const snippet = createSqlSnippetSkeleton({ - id: uuidv4(), - name, - sql: content.sql, - owner_id: profile?.id, - project_id: project?.id, - }) - snap.addSnippet(snippet as SqlSnippet, ref) - snap.addNeedsSaving(snippet.id!) - router.push(`/project/${ref}/sql/${snippet.id}`) - } catch (error: any) { - ui.setNotification({ - category: 'error', - message: `Failed to create a personal copy of this query: ${error.message}`, - }) - } - } - - return ( -
- {IS_PLATFORM ? ( - - - - )} -
- ) -} diff --git a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorLayout.utils.ts b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorLayout.utils.ts deleted file mode 100644 index f11d4973e29..00000000000 --- a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorLayout.utils.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { uniq } from 'lodash' - -export const selectItemsInRange = (id: string, snippets: any[], selectedSnippets: string[]) => { - const lastSelectedItemIndex = snippets.findIndex( - (x) => x.id === selectedSnippets[selectedSnippets.length - 1] - ) - const selectedItemIndex = snippets.findIndex((x) => x.id === id) - - // Get the start and end index of the range to select - const start = Math.min(selectedItemIndex, lastSelectedItemIndex) - const end = Math.max(selectedItemIndex, lastSelectedItemIndex) - - // Get the range to select and reverse the order if necessary - const rangeToSelect = snippets.slice(start, end + 1) - if (selectedItemIndex < lastSelectedItemIndex) rangeToSelect.reverse() - - if (selectedSnippets.includes(id)) { - const rangeToDeselectIds = rangeToSelect.map((item) => item.id) - return selectedSnippets.filter((x) => x === id || !rangeToDeselectIds.includes(x)) - } else { - return uniq(selectedSnippets.concat(rangeToSelect.map((x) => x.id) as string[])) - } -} diff --git a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx index 2be0ab6e683..f186c21f9d2 100644 --- a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx +++ b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx @@ -3,50 +3,32 @@ import { useParams } from 'common' import { observer } from 'mobx-react-lite' import { useRouter } from 'next/router' import { useMemo, useState } from 'react' -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 { Button, IconPlus, IconSearch, IconX, Input, Menu, cn } 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' import { useProfile } from 'lib/profile' import { useSnippets, useSqlEditorStateSnapshot } from 'state/sql-editor' import QueryItem from './QueryItem' -import { partition, uniq, uniqBy } from 'lodash' -import { selectItemsInRange } from './SQLEditorLayout.utils' const SideBarContent = observer(() => { const { ui } = useStore() - const { ref, id: activeId } = useParams() + const { ref } = useParams() const router = useRouter() const { profile } = useProfile() const project = useSelectedProject() - const [searchText, setSearchText] = useState('') - const [showDeleteModal, setShowDeleteModal] = useState(false) - const [selectedQueries, setSelectedQueries] = useState([]) + const [personalSnippetsFilterString, setPersonalSnippetsFilterString] = useState('') + const [projectSnippetsFilterString, setProjectSnippetsFilterString] = useState('') + const [favoritesFilterString, setFavoritesFilterString] = useState('') + const [isPersonalSnippetsFilterOpen, setIsPersonalSnippetsFilterOpen] = useState(false) + const [isProjectSnippetsFilterOpen, setIsProjectSnippetsFilterOpen] = useState(false) + const [isFavoritesFilterOpen, setIsFavoritesFilterOpen] = useState(false) const snap = useSqlEditorStateSnapshot() const { isLoading, isSuccess } = useSqlSnippetsQuery(ref, { @@ -57,29 +39,6 @@ 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(() => { @@ -87,90 +46,45 @@ const SideBarContent = observer(() => { }, [snippets]) const filteredProjectSnippets = useMemo(() => { - if (searchText.length > 0) { + if (projectSnippetsFilterString.length > 0) { return projectSnippets.filter((tab) => - tab.name.toLowerCase().includes(searchText.toLowerCase()) + tab.name.toLowerCase().includes(projectSnippetsFilterString.toLowerCase()) ) } return projectSnippets - }, [projectSnippets, searchText]) + }, [projectSnippets, projectSnippetsFilterString]) const personalSnippets = useMemo(() => { const ss = snippets.filter( (snippet) => snippet.visibility === 'user' && !snippet.content.favorite ) - if (searchText.length > 0) { - return ss.filter((tab) => tab.name.toLowerCase().includes(searchText.toLowerCase())) + if (personalSnippetsFilterString.length > 0) { + return ss.filter((tab) => + tab.name.toLowerCase().includes(personalSnippetsFilterString.toLowerCase()) + ) } return ss - }, [searchText, snippets]) + }, [personalSnippetsFilterString, snippets]) const favoriteSnippets = useMemo(() => { return snippets.filter((snippet) => snippet.content.favorite) }, [snippets]) const filteredFavoriteSnippets = useMemo(() => { - if (searchText.length > 0) { + if (favoritesFilterString.length > 0) { return favoriteSnippets.filter((tab) => - tab.name.toLowerCase().includes(searchText.toLowerCase()) + tab.name.toLowerCase().includes(favoritesFilterString.toLowerCase()) ) } return favoriteSnippets - }, [favoriteSnippets, searchText]) + }, [favoriteSnippets, favoritesFilterString]) const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', { resource: { type: 'sql', owner_id: profile?.id }, subject: { id: profile?.id }, }) - const onSelectQuery = (id?: string, isShiftHeld: boolean = false) => { - if (id === undefined) return - - if (isShiftHeld) { - const [selectedFavSnippets, selectedOtherSnippets] = partition( - selectedQueries, - (id) => snippets.find((x) => x.id === id)?.content.favorite - ) - const selectedSnippet = snippets.find((x) => x.id === id) - const isFavourite = selectedSnippet?.content.favorite - - if (isFavourite) { - if (selectedFavSnippets.length > 0) { - const updatedSelectedFavSnippets = selectItemsInRange( - id, - filteredFavoriteSnippets, - selectedFavSnippets - ) - setSelectedQueries(updatedSelectedFavSnippets.concat(selectedOtherSnippets)) - } else { - selectQuery(id) - } - } else { - if (selectedOtherSnippets.length > 0) { - const updatedSelectedOtherSnippets = selectItemsInRange( - id, - personalSnippets, - selectedOtherSnippets - ) - setSelectedQueries(updatedSelectedOtherSnippets.concat(selectedFavSnippets)) - } else { - selectQuery(id) - } - } - } else { - selectQuery(id) - } - } - - const selectQuery = (id: string) => { - 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') @@ -192,7 +106,13 @@ const SideBarContent = observer(() => { snap.addSnippet(snippet as SqlSnippet, ref) router.push(`/project/${ref}/sql/${snippet.id}`) - setSearchText('') + // reset all search inputs when a new query is added + setPersonalSnippetsFilterString('') + setProjectSnippetsFilterString('') + setFavoritesFilterString('') + setIsPersonalSnippetsFilterOpen(false) + setIsProjectSnippetsFilterOpen(false) + setIsFavoritesFilterOpen(false) } catch (error: any) { ui.setNotification({ category: 'error', @@ -201,206 +121,254 @@ const SideBarContent = observer(() => { } } - const onConfirmDelete = async () => { - if (!ref) return console.error('Project ref is required') - deleteContent({ projectRef: ref, ids: selectedQueries }) - } - return ( - <> -
- - {isLoading ? ( -
- - - +
+ + {isLoading ? ( +
+ + + +
+ ) : isSuccess ? ( +
+
+
- ) : isSuccess ? ( -
-
- - - } - placeholder="Search queries" - onChange={(e) => setSearchText(e.target.value.trim())} - value={searchText} - size="tiny" - actions={ - searchText && ( - - ) - } +
+
+ +
-
- {searchText.length === 0 && ( -
- - + {favoriteSnippets.length >= 1 && ( +
+
+ +
- )} - - {searchText.length > 0 && - filteredFavoriteSnippets.length === 0 && - filteredProjectSnippets.length === 0 && - personalSnippets.length === 0 && ( -
-
-

No queries found

-

- Your search for "{searchText}" did not return any results -

-
- + {isFavoritesFilterOpen && ( +
+ } + placeholder="Filter" + disabled={isLoading} + onChange={(e) => setFavoritesFilterString(e.target.value)} + value={favoritesFilterString} + onKeyDown={(e) => { + if (e.key === 'Escape') { + setIsFavoritesFilterOpen(false) + setFavoritesFilterString('') + } + }} + actions={ + favoritesFilterString && ( + setFavoritesFilterString('')} + /> + ) + } + />
)} - - {filteredProjectSnippets.length > 0 ? ( -
- - -
- {filteredProjectSnippets.map((tabInfo) => { - const { id } = tabInfo || {} - return ( - 0} - /> - ) - })} -
-
- ) : null} - - {selectedQueries.length > 0 && ( - <> - -
- - - -
- - )} - - {filteredFavoriteSnippets.length > 0 ? ( -
- + {filteredFavoriteSnippets.length > 0 ? (
{filteredFavoriteSnippets.map((tabInfo) => { const { id } = tabInfo || {} - return ( - 0} - onSelectQuery={(isShiftHeld) => onSelectQuery(id, isShiftHeld)} - onDeleteQuery={postDeleteCleanup} - /> - ) + return })}
-
- ) : null} + ) : ( +
+ No queries found +
+ )} +
+ )} - {personalSnippets.length > 0 ? ( -
- -
- {personalSnippets.map((tabInfo) => { + {projectSnippets.length >= 1 && ( +
+
+ + +
+ {isProjectSnippetsFilterOpen && ( +
+ } + placeholder="Filter" + disabled={isLoading} + onChange={(e) => setProjectSnippetsFilterString(e.target.value)} + value={projectSnippetsFilterString} + onKeyDown={(e) => { + if (e.key === 'Escape') { + setIsProjectSnippetsFilterOpen(false) + setProjectSnippetsFilterString('') + } + }} + actions={ + projectSnippetsFilterString && ( + setProjectSnippetsFilterString('')} + /> + ) + } + /> +
+ )} + {filteredProjectSnippets.length > 0 ? ( +
+ {filteredProjectSnippets.map((tabInfo) => { const { id } = tabInfo || {} - return ( - 0} - onSelectQuery={(isShiftHeld) => onSelectQuery(id, isShiftHeld)} - onDeleteQuery={postDeleteCleanup} - /> - ) + return })}
+ ) : ( +
+ No queries found +
+ )} +
+ )} + +
+
+ + +
+ {isPersonalSnippetsFilterOpen && ( +
+ } + placeholder="Filter" + disabled={isLoading} + onChange={(e) => setPersonalSnippetsFilterString(e.target.value)} + value={personalSnippetsFilterString} + onKeyDown={(e) => { + if (e.key === 'Escape') { + setIsPersonalSnippetsFilterOpen(false) + setPersonalSnippetsFilterString('') + } + }} + actions={ + personalSnippetsFilterString && ( + setPersonalSnippetsFilterString('')} + /> + ) + } + />
- ) : null} -
-
- ) : ( -
- )} -
-
- setShowDeleteModal(false)} - > - -
-
-
- - - This action cannot be undone - - The selected SQL snippets cannot be recovered once deleted - - -

- Are you sure you want to delete the selected {selectedQueries.length} quer - {selectedQueries.length > 1 ? 'ies' : 'y'}? -

+ )} + {personalSnippets.length > 0 ? ( +
+ {personalSnippets.map((tabInfo) => { + const { id } = tabInfo || {} + return + })} +
+ ) : ( +
+ {filteredFavoriteSnippets.length === 0 && ( + No queries found + )} + +
+ )}
-
-
- + ) : ( +
+ )} +
+
) }) diff --git a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx index 8a1a498e4ad..c371007a0a3 100644 --- a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx @@ -191,7 +191,7 @@ const TableEditorMenu = () => { size="tiny" actions={ searchText && ( - ) diff --git a/apps/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeaderSelection.tsx b/apps/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeaderSelection.tsx index 06d4de34363..ceb23b01f9e 100644 --- a/apps/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeaderSelection.tsx +++ b/apps/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeaderSelection.tsx @@ -13,13 +13,13 @@ const FileExplorerHeaderSelection = () => { } = storageExplorerStore return ( -
+
-
+